pi-one-ui 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +106 -0
- package/extensions/app/config/renderer.ts +452 -0
- package/extensions/app/config/shell.ts +2484 -0
- package/extensions/app/ownership.ts +18 -0
- package/extensions/app/panel.ts +533 -0
- package/extensions/app/presets.ts +36 -0
- package/extensions/features/agent-summary/core.ts +194 -0
- package/extensions/features/agent-summary/index.ts +37 -0
- package/extensions/features/compact-thinking.ts +1262 -0
- package/extensions/features/context.ts +857 -0
- package/extensions/features/reference/index.ts +643 -0
- package/extensions/features/reference/session.ts +278 -0
- package/extensions/features/reference/subagent.ts +218 -0
- package/extensions/features/shell/aliases.ts +28 -0
- package/extensions/features/shell/flush-docked-bash.ts +39 -0
- package/extensions/features/shell/startup-header.ts +357 -0
- package/extensions/features/shell/working-message.ts +226 -0
- package/extensions/index.ts +52 -0
- package/extensions/shell/accent-rail-editor.ts +112 -0
- package/extensions/shell/accent-rail-layout-patch.ts +530 -0
- package/extensions/shell/completion-menu.ts +186 -0
- package/extensions/shell/editor-metadata-format.ts +287 -0
- package/extensions/shell/editor-transfer.ts +73 -0
- package/extensions/shell/extension-status.ts +102 -0
- package/extensions/shell/footer-format.ts +388 -0
- package/extensions/shell/footer-layout.ts +144 -0
- package/extensions/shell/footer.ts +1035 -0
- package/extensions/shell/format.ts +577 -0
- package/extensions/shell/git.ts +434 -0
- package/extensions/shell/icons.ts +261 -0
- package/extensions/shell/index.ts +1659 -0
- package/extensions/shell/interaction-summary.ts +1194 -0
- package/extensions/shell/live-context.ts +82 -0
- package/extensions/shell/minimalist-editor.ts +548 -0
- package/extensions/shell/package-version.ts +675 -0
- package/extensions/shell/project-refresh.ts +130 -0
- package/extensions/shell/project-state.ts +60 -0
- package/extensions/shell/prototype-patch-registry.ts +183 -0
- package/extensions/shell/runtime.ts +915 -0
- package/extensions/shell/selector-border.ts +109 -0
- package/extensions/shell/session-lifecycle.ts +60 -0
- package/extensions/shell/settings-command.ts +2144 -0
- package/extensions/shell/settings-previews.ts +137 -0
- package/extensions/shell/state.ts +83 -0
- package/extensions/shell/style.ts +480 -0
- package/extensions/shell/telemetry.ts +90 -0
- package/extensions/shell/ui.ts +1326 -0
- package/extensions/shell/user-message-osc.ts +279 -0
- package/extensions/shell/user-message-styles.ts +266 -0
- package/extensions/shell/user-message.ts +226 -0
- package/extensions/shell/working-line-messages.ts +24 -0
- package/extensions/shell/working-line-spinners.ts +54 -0
- package/extensions/shell/working-line.ts +1603 -0
- package/extensions/tools/ansi-color.ts +44 -0
- package/extensions/tools/ansi-text.ts +52 -0
- package/extensions/tools/component-tree.ts +37 -0
- package/extensions/tools/format.ts +28 -0
- package/extensions/tools/fullscreen-detect.ts +11 -0
- package/extensions/tools/patch-keys.ts +158 -0
- package/extensions/tools/sgr-mouse.ts +59 -0
- package/extensions/tools/tool-loading-icon.ts +20 -0
- package/extensions/tools/tool-result-sanitize.ts +27 -0
- package/extensions/transcript/index.ts +65 -0
- package/extensions/transcript/renderer/compact-mode.ts +1443 -0
- package/extensions/transcript/renderer/default-mode.ts +735 -0
- package/extensions/transcript/renderer/index.ts +286 -0
- package/extensions/transcript/renderer/markdown-enhance.ts +325 -0
- package/extensions/transcript/renderer/mouse/hover.ts +175 -0
- package/extensions/transcript/renderer/mouse/interaction.ts +1016 -0
- package/extensions/transcript/renderer/mouse/layout.ts +207 -0
- package/extensions/transcript/renderer/mouse/packets.ts +96 -0
- package/extensions/transcript/renderer/mouse/scroll.ts +211 -0
- package/extensions/transcript/renderer/tool/diff/ATTRIBUTION.md +28 -0
- package/extensions/transcript/renderer/tool/diff/ansi-utils.ts +128 -0
- package/extensions/transcript/renderer/tool/diff/diff-component.ts +112 -0
- package/extensions/transcript/renderer/tool/diff/diff-edit-render.ts +255 -0
- package/extensions/transcript/renderer/tool/diff/diff-header.ts +184 -0
- package/extensions/transcript/renderer/tool/diff/diff-highlight.ts +131 -0
- package/extensions/transcript/renderer/tool/diff/diff-inline.ts +350 -0
- package/extensions/transcript/renderer/tool/diff/diff-layout.ts +870 -0
- package/extensions/transcript/renderer/tool/diff/diff-limits.ts +189 -0
- package/extensions/transcript/renderer/tool/diff/diff-palette.ts +436 -0
- package/extensions/transcript/renderer/tool/diff/diff-parse.ts +536 -0
- package/extensions/transcript/renderer/tool/diff/diff-presentation.ts +79 -0
- package/extensions/transcript/renderer/tool/diff/diff-renderer.ts +29 -0
- package/extensions/transcript/renderer/tool/diff/diff-text.ts +57 -0
- package/extensions/transcript/renderer/tool/diff/diff-write-render.ts +629 -0
- package/extensions/transcript/renderer/tool/diff/index.ts +147 -0
- package/extensions/transcript/renderer/tool/diff/line-width-safety.ts +100 -0
- package/extensions/transcript/renderer/tool/diff/shiki-highlight.ts +104 -0
- package/extensions/transcript/renderer/tool/diff/write-execution.ts +124 -0
- package/extensions/transcript/renderer/tool/grouping.ts +619 -0
- package/extensions/transcript/renderer/tool/message-display.ts +183 -0
- package/extensions/transcript/renderer/tool/names.ts +195 -0
- package/extensions/transcript/renderer/tool/result.ts +842 -0
- package/extensions/transcript/renderer/tool/show-more-hint.ts +42 -0
- package/extensions/transcript/renderer/transcript-refresh.ts +79 -0
- package/package.json +58 -0
- package/themes/cc-dark.json +91 -0
- package/themes/cc-light.json +91 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { AssistantMessage, Usage } from "@earendil-works/pi-ai";
|
|
2
|
+
import type { SessionLifecycle } from "./session-lifecycle.ts";
|
|
3
|
+
|
|
4
|
+
export type LiveContextOverride = {
|
|
5
|
+
tokens: number;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
function usageComponent(value: unknown): number {
|
|
9
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0
|
|
10
|
+
? value
|
|
11
|
+
: 0;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function calculateLiveContextTokens(
|
|
15
|
+
usage: Usage | undefined,
|
|
16
|
+
): number | undefined {
|
|
17
|
+
if (!usage) return undefined;
|
|
18
|
+
const totalTokens = usageComponent(usage.totalTokens);
|
|
19
|
+
if (totalTokens > 0) return totalTokens;
|
|
20
|
+
const calculated =
|
|
21
|
+
usageComponent(usage.input) +
|
|
22
|
+
usageComponent(usage.output) +
|
|
23
|
+
usageComponent(usage.cacheRead) +
|
|
24
|
+
usageComponent(usage.cacheWrite);
|
|
25
|
+
return calculated > 0 ? calculated : undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function liveContextFromMessage(
|
|
29
|
+
message: unknown,
|
|
30
|
+
): LiveContextOverride | undefined {
|
|
31
|
+
if (!message || typeof message !== "object") return undefined;
|
|
32
|
+
const assistant = message as Partial<AssistantMessage>;
|
|
33
|
+
if (assistant.role !== "assistant") return undefined;
|
|
34
|
+
if (assistant.stopReason === "error" || assistant.stopReason === "aborted")
|
|
35
|
+
return undefined;
|
|
36
|
+
const tokens = calculateLiveContextTokens(assistant.usage);
|
|
37
|
+
return tokens === undefined ? undefined : { tokens };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class LiveContextController {
|
|
41
|
+
private readonly lifecycle: SessionLifecycle;
|
|
42
|
+
private readonly requestRender: () => void;
|
|
43
|
+
private override: LiveContextOverride | undefined;
|
|
44
|
+
private cancelScheduledRender: (() => void) | undefined;
|
|
45
|
+
private scheduledGeneration: number | undefined;
|
|
46
|
+
|
|
47
|
+
constructor(lifecycle: SessionLifecycle, requestRender: () => void) {
|
|
48
|
+
this.lifecycle = lifecycle;
|
|
49
|
+
this.requestRender = requestRender;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
get(): LiveContextOverride | undefined {
|
|
53
|
+
return this.override;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
update(message: unknown): boolean {
|
|
57
|
+
const next = liveContextFromMessage(message);
|
|
58
|
+
if (!next || !this.lifecycle.isCurrent()) return false;
|
|
59
|
+
this.override = next;
|
|
60
|
+
const generation = this.lifecycle.currentGeneration();
|
|
61
|
+
if (this.cancelScheduledRender && this.scheduledGeneration !== generation) {
|
|
62
|
+
this.cancelScheduledRender = undefined;
|
|
63
|
+
this.scheduledGeneration = undefined;
|
|
64
|
+
}
|
|
65
|
+
if (!this.cancelScheduledRender) {
|
|
66
|
+
this.scheduledGeneration = generation;
|
|
67
|
+
this.cancelScheduledRender = this.lifecycle.defer(() => {
|
|
68
|
+
this.cancelScheduledRender = undefined;
|
|
69
|
+
this.scheduledGeneration = undefined;
|
|
70
|
+
if (this.override) this.requestRender();
|
|
71
|
+
}, 250);
|
|
72
|
+
}
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
clear(): void {
|
|
77
|
+
this.override = undefined;
|
|
78
|
+
this.cancelScheduledRender?.();
|
|
79
|
+
this.cancelScheduledRender = undefined;
|
|
80
|
+
this.scheduledGeneration = undefined;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
import { basename, isAbsolute, relative, sep } from "node:path";
|
|
2
|
+
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
4
|
+
import type { ZentuiConfig } from "../app/config/shell.ts";
|
|
5
|
+
import { sanitizeEditorMetadataText } from "./editor-metadata-format.ts";
|
|
6
|
+
import {
|
|
7
|
+
buildContextGauge,
|
|
8
|
+
contextColorTier,
|
|
9
|
+
formatCount,
|
|
10
|
+
formatCwdLabel,
|
|
11
|
+
formatElapsedDuration,
|
|
12
|
+
} from "./format.ts";
|
|
13
|
+
|
|
14
|
+
export { formatElapsedDuration } from "./format.ts";
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
EDITOR_ACCENT_FALLBACK,
|
|
18
|
+
EDITOR_BORDER_FALLBACK,
|
|
19
|
+
renderStyleForSource,
|
|
20
|
+
renderStyleForSourceOrFallback,
|
|
21
|
+
safeThemeFg,
|
|
22
|
+
} from "./style.ts";
|
|
23
|
+
|
|
24
|
+
const MINIMALIST_MODEL_FALLBACK = {
|
|
25
|
+
theme: "syntaxKeyword",
|
|
26
|
+
terminal: "bold purple",
|
|
27
|
+
};
|
|
28
|
+
const MINIMALIST_THINKING_FALLBACK = {
|
|
29
|
+
theme: "warning",
|
|
30
|
+
terminal: "bold yellow",
|
|
31
|
+
};
|
|
32
|
+
const MINIMALIST_ADAPTIVE_TERMINAL_THINKING_FALLBACKS: Record<string, string> =
|
|
33
|
+
{
|
|
34
|
+
minimal: "bright-black",
|
|
35
|
+
low: "blue",
|
|
36
|
+
medium: "cyan",
|
|
37
|
+
high: "yellow",
|
|
38
|
+
xhigh: "red",
|
|
39
|
+
max: "bright-red",
|
|
40
|
+
};
|
|
41
|
+
const MINIMALIST_BRANCH_FALLBACK = {
|
|
42
|
+
theme: "bold syntaxKeyword",
|
|
43
|
+
terminal: "bold blue",
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export type MinimalistEditorMetadata = {
|
|
47
|
+
cwd: string;
|
|
48
|
+
projectRoot?: string;
|
|
49
|
+
branch?: string;
|
|
50
|
+
dirty?: boolean;
|
|
51
|
+
ahead?: number;
|
|
52
|
+
behind?: number;
|
|
53
|
+
costLabel?: string;
|
|
54
|
+
modelLabel?: string;
|
|
55
|
+
thinkingLevel?: string;
|
|
56
|
+
contextPercent?: number;
|
|
57
|
+
contextWindow?: number;
|
|
58
|
+
sessionName?: string;
|
|
59
|
+
agentDurationMs?: number;
|
|
60
|
+
agentActive?: boolean;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export type MinimalistFrameOptions = {
|
|
64
|
+
width: number;
|
|
65
|
+
editorLines: string[];
|
|
66
|
+
autocompleteLines?: string[];
|
|
67
|
+
viewport?: {
|
|
68
|
+
above?: string;
|
|
69
|
+
below?: string;
|
|
70
|
+
};
|
|
71
|
+
inputText: string;
|
|
72
|
+
metadata: MinimalistEditorMetadata;
|
|
73
|
+
uiTheme: Theme;
|
|
74
|
+
config: ZentuiConfig;
|
|
75
|
+
borderColor?: (text: string) => string;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
function fillLine(content: string, width: number): string {
|
|
79
|
+
const truncated = truncateToWidth(content, Math.max(0, width), "");
|
|
80
|
+
return `${truncated}${" ".repeat(Math.max(0, width - visibleWidth(truncated)))}`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function clampLines(lines: string[], width: number): string[] {
|
|
84
|
+
return lines.map((line) => truncateToWidth(line, Math.max(0, width), ""));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function renderFramedAutocompleteRows({
|
|
88
|
+
width,
|
|
89
|
+
lines,
|
|
90
|
+
renderBorder,
|
|
91
|
+
}: {
|
|
92
|
+
width: number;
|
|
93
|
+
lines: string[];
|
|
94
|
+
renderBorder: (text: string) => string;
|
|
95
|
+
}): string[] {
|
|
96
|
+
if (width <= 4 || lines.length === 0) return clampLines(lines, width);
|
|
97
|
+
const contentWidth = width - 4;
|
|
98
|
+
return clampLines(
|
|
99
|
+
[
|
|
100
|
+
`${renderBorder("├")}${renderBorder("─".repeat(width - 2))}${renderBorder("┤")}`,
|
|
101
|
+
...lines.map(
|
|
102
|
+
(line) =>
|
|
103
|
+
`${renderBorder("│")} ${fillLine(line, contentWidth)} ${renderBorder("│")}`,
|
|
104
|
+
),
|
|
105
|
+
],
|
|
106
|
+
width,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function joinStyled(parts: string[], separator: string): string {
|
|
111
|
+
return parts.filter(Boolean).join(separator);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function thinkingStyle(
|
|
115
|
+
config: ZentuiConfig,
|
|
116
|
+
level: string,
|
|
117
|
+
): string | undefined {
|
|
118
|
+
switch (level.toLowerCase()) {
|
|
119
|
+
case "minimal":
|
|
120
|
+
return (
|
|
121
|
+
config.colors.editorThinkingMinimal ?? config.colors.editorThinking
|
|
122
|
+
);
|
|
123
|
+
case "low":
|
|
124
|
+
return config.colors.editorThinkingLow ?? config.colors.editorThinking;
|
|
125
|
+
case "medium":
|
|
126
|
+
return config.colors.editorThinkingMedium ?? config.colors.editorThinking;
|
|
127
|
+
case "high":
|
|
128
|
+
return config.colors.editorThinkingHigh ?? config.colors.editorThinking;
|
|
129
|
+
case "xhigh":
|
|
130
|
+
return config.colors.editorThinkingXhigh ?? config.colors.editorThinking;
|
|
131
|
+
case "max":
|
|
132
|
+
return (
|
|
133
|
+
config.colors.editorThinkingMax ??
|
|
134
|
+
config.colors.editorThinkingXhigh ??
|
|
135
|
+
config.colors.editorThinking
|
|
136
|
+
);
|
|
137
|
+
default:
|
|
138
|
+
return config.colors.editorThinking;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function renderTopLeft(
|
|
143
|
+
inputText: string,
|
|
144
|
+
metadata: MinimalistEditorMetadata,
|
|
145
|
+
uiTheme: Theme,
|
|
146
|
+
config: ZentuiConfig,
|
|
147
|
+
includeSessionName = true,
|
|
148
|
+
): string {
|
|
149
|
+
const source = config.components.editor.colorSource;
|
|
150
|
+
const trimmed = inputText.trimStart();
|
|
151
|
+
const bashMode = trimmed.startsWith("!!")
|
|
152
|
+
? "no-context"
|
|
153
|
+
: trimmed.startsWith("!")
|
|
154
|
+
? "shell"
|
|
155
|
+
: "";
|
|
156
|
+
const parts: string[] = [];
|
|
157
|
+
if (bashMode) {
|
|
158
|
+
parts.push(
|
|
159
|
+
bashMode === "no-context"
|
|
160
|
+
? safeThemeFg(uiTheme, "muted", "$")
|
|
161
|
+
: safeThemeFg(uiTheme, "bashMode", "$"),
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
if (
|
|
165
|
+
config.components.editor.styles.minimalist.showTimer &&
|
|
166
|
+
metadata.agentDurationMs !== undefined
|
|
167
|
+
) {
|
|
168
|
+
const duration = formatElapsedDuration(metadata.agentDurationMs);
|
|
169
|
+
parts.push(
|
|
170
|
+
metadata.agentActive
|
|
171
|
+
? renderStyleForSourceOrFallback(
|
|
172
|
+
uiTheme,
|
|
173
|
+
source,
|
|
174
|
+
config.colors.sessionDuration,
|
|
175
|
+
EDITOR_ACCENT_FALLBACK,
|
|
176
|
+
duration,
|
|
177
|
+
)
|
|
178
|
+
: safeThemeFg(uiTheme, "muted", duration),
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
const sessionName = includeSessionName
|
|
182
|
+
? sanitizeEditorMetadataText(metadata.sessionName ?? "")
|
|
183
|
+
: "";
|
|
184
|
+
if (
|
|
185
|
+
config.components.editor.styles.minimalist.showSessionName &&
|
|
186
|
+
sessionName
|
|
187
|
+
) {
|
|
188
|
+
parts.push(
|
|
189
|
+
renderStyleForSource(
|
|
190
|
+
uiTheme,
|
|
191
|
+
source,
|
|
192
|
+
config.colors.sessionName,
|
|
193
|
+
sessionName,
|
|
194
|
+
),
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
return joinStyled(parts, safeThemeFg(uiTheme, "muted", " · "));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function renderTopRight(
|
|
201
|
+
metadata: MinimalistEditorMetadata,
|
|
202
|
+
uiTheme: Theme,
|
|
203
|
+
config: ZentuiConfig,
|
|
204
|
+
availableWidth: number,
|
|
205
|
+
renderBorder: (text: string) => string,
|
|
206
|
+
renderThinking: (text: string) => string,
|
|
207
|
+
): string {
|
|
208
|
+
const source = config.components.editor.colorSource;
|
|
209
|
+
const parts: string[] = [];
|
|
210
|
+
const joinParts = (values: string[]) =>
|
|
211
|
+
values
|
|
212
|
+
.map((part, index) =>
|
|
213
|
+
index > 0 ? `${renderBorder(" – ")}${part}` : part,
|
|
214
|
+
)
|
|
215
|
+
.join("");
|
|
216
|
+
const cost = config.components.editor.styles.minimalist.showCost
|
|
217
|
+
? sanitizeEditorMetadataText(metadata.costLabel ?? "")
|
|
218
|
+
: "";
|
|
219
|
+
if (cost) {
|
|
220
|
+
parts.push(renderStyleForSource(uiTheme, source, config.colors.cost, cost));
|
|
221
|
+
}
|
|
222
|
+
const model = sanitizeEditorMetadataText(metadata.modelLabel ?? "");
|
|
223
|
+
if (model) {
|
|
224
|
+
parts.push(
|
|
225
|
+
renderStyleForSourceOrFallback(
|
|
226
|
+
uiTheme,
|
|
227
|
+
source,
|
|
228
|
+
config.colors.editorModel,
|
|
229
|
+
MINIMALIST_MODEL_FALLBACK,
|
|
230
|
+
model,
|
|
231
|
+
),
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
const thinking = sanitizeEditorMetadataText(metadata.thinkingLevel ?? "");
|
|
235
|
+
if (thinking && thinking.toLowerCase() !== "off") {
|
|
236
|
+
parts.push(renderThinking(thinking));
|
|
237
|
+
}
|
|
238
|
+
if (
|
|
239
|
+
metadata.contextPercent !== undefined &&
|
|
240
|
+
Number.isFinite(metadata.contextPercent)
|
|
241
|
+
) {
|
|
242
|
+
const percent = Math.round(
|
|
243
|
+
Math.max(0, Math.min(999, metadata.contextPercent)),
|
|
244
|
+
);
|
|
245
|
+
const tier = contextColorTier(
|
|
246
|
+
percent,
|
|
247
|
+
config.components.editor.styles.minimalist.contextThresholds,
|
|
248
|
+
);
|
|
249
|
+
const style =
|
|
250
|
+
tier === "error"
|
|
251
|
+
? config.colors.contextError
|
|
252
|
+
: tier === "warning"
|
|
253
|
+
? config.colors.contextWarning
|
|
254
|
+
: config.colors.contextNormal;
|
|
255
|
+
const total =
|
|
256
|
+
config.components.editor.styles.minimalist.contextFormat ===
|
|
257
|
+
"percent-total" &&
|
|
258
|
+
metadata.contextWindow !== undefined &&
|
|
259
|
+
Number.isFinite(metadata.contextWindow) &&
|
|
260
|
+
metadata.contextWindow > 0
|
|
261
|
+
? `/${formatCount(metadata.contextWindow)}`
|
|
262
|
+
: "";
|
|
263
|
+
const text = `${percent}%${total}`;
|
|
264
|
+
let context = renderStyleForSource(uiTheme, source, style, text);
|
|
265
|
+
if (config.components.editor.styles.minimalist.contextGauge) {
|
|
266
|
+
for (const gaugeWidth of [5, 3]) {
|
|
267
|
+
const gauge = `[${buildContextGauge(percent, gaugeWidth, config.icons.mode === "ascii")}] ${text}`;
|
|
268
|
+
const styledGauge = renderStyleForSource(uiTheme, source, style, gauge);
|
|
269
|
+
const candidate = joinParts([...parts, styledGauge]);
|
|
270
|
+
if (visibleWidth(candidate) <= availableWidth) {
|
|
271
|
+
context = styledGauge;
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
parts.push(context);
|
|
277
|
+
}
|
|
278
|
+
return joinParts(parts);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function renderBottomLeft(
|
|
282
|
+
metadata: MinimalistEditorMetadata,
|
|
283
|
+
uiTheme: Theme,
|
|
284
|
+
config: ZentuiConfig,
|
|
285
|
+
): string {
|
|
286
|
+
if (!config.components.editor.styles.minimalist.showGit) return "";
|
|
287
|
+
const source = config.components.editor.colorSource;
|
|
288
|
+
const branch = sanitizeEditorMetadataText(metadata.branch ?? "");
|
|
289
|
+
const parts = branch
|
|
290
|
+
? [
|
|
291
|
+
renderStyleForSourceOrFallback(
|
|
292
|
+
uiTheme,
|
|
293
|
+
source,
|
|
294
|
+
config.colors.editorGitBranch,
|
|
295
|
+
MINIMALIST_BRANCH_FALLBACK,
|
|
296
|
+
branch,
|
|
297
|
+
),
|
|
298
|
+
]
|
|
299
|
+
: [];
|
|
300
|
+
if (metadata.dirty) {
|
|
301
|
+
parts.push(
|
|
302
|
+
renderStyleForSource(uiTheme, source, config.colors.gitStatus, "*"),
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
if ((metadata.ahead ?? 0) > 0) {
|
|
306
|
+
parts.push(safeThemeFg(uiTheme, "success", `↑${metadata.ahead}`));
|
|
307
|
+
}
|
|
308
|
+
if ((metadata.behind ?? 0) > 0) {
|
|
309
|
+
parts.push(safeThemeFg(uiTheme, "error", `↓${metadata.behind}`));
|
|
310
|
+
}
|
|
311
|
+
return parts.join(" ");
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function minimalistCwdLabel(
|
|
315
|
+
metadata: MinimalistEditorMetadata,
|
|
316
|
+
config: ZentuiConfig,
|
|
317
|
+
): string {
|
|
318
|
+
const full = () =>
|
|
319
|
+
formatCwdLabel(metadata.cwd, "", { mode: "full", depth: 0 });
|
|
320
|
+
if (config.components.editor.styles.minimalist.pathDisplay === "full")
|
|
321
|
+
return full();
|
|
322
|
+
if (config.components.editor.styles.minimalist.pathDisplay === "compact")
|
|
323
|
+
return basename(metadata.cwd) || metadata.cwd;
|
|
324
|
+
if (!metadata.projectRoot) return full();
|
|
325
|
+
|
|
326
|
+
const pathFromRoot = relative(metadata.projectRoot, metadata.cwd);
|
|
327
|
+
if (
|
|
328
|
+
pathFromRoot === ".." ||
|
|
329
|
+
pathFromRoot.startsWith(`..${sep}`) ||
|
|
330
|
+
isAbsolute(pathFromRoot)
|
|
331
|
+
) {
|
|
332
|
+
return full();
|
|
333
|
+
}
|
|
334
|
+
const project = basename(metadata.projectRoot) || metadata.projectRoot;
|
|
335
|
+
return pathFromRoot ? `${project}/${pathFromRoot}` : project;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function renderBottomRight(
|
|
339
|
+
metadata: MinimalistEditorMetadata,
|
|
340
|
+
uiTheme: Theme,
|
|
341
|
+
config: ZentuiConfig,
|
|
342
|
+
): string {
|
|
343
|
+
const cwd = sanitizeEditorMetadataText(minimalistCwdLabel(metadata, config));
|
|
344
|
+
return cwd
|
|
345
|
+
? renderStyleForSource(
|
|
346
|
+
uiTheme,
|
|
347
|
+
config.components.editor.colorSource,
|
|
348
|
+
config.colors.cwd,
|
|
349
|
+
cwd,
|
|
350
|
+
)
|
|
351
|
+
: "";
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function renderLabeledBorder(options: {
|
|
355
|
+
width: number;
|
|
356
|
+
left: string;
|
|
357
|
+
leftFallbacks?: string[];
|
|
358
|
+
right: string;
|
|
359
|
+
leftCorner: string;
|
|
360
|
+
rightCorner: string;
|
|
361
|
+
renderBorder: (text: string) => string;
|
|
362
|
+
}): string {
|
|
363
|
+
const innerWidth = Math.max(0, options.width - 2);
|
|
364
|
+
let left = options.left;
|
|
365
|
+
let right = options.right;
|
|
366
|
+
const fitLabels = () => {
|
|
367
|
+
const overhead = (left ? 3 : 1) + (right ? 3 : 1);
|
|
368
|
+
const budget = Math.max(0, innerWidth - overhead);
|
|
369
|
+
const leftNatural = visibleWidth(left);
|
|
370
|
+
const rightNatural = visibleWidth(right);
|
|
371
|
+
if (leftNatural + rightNatural <= budget) return false;
|
|
372
|
+
|
|
373
|
+
let leftBudget = left ? budget : 0;
|
|
374
|
+
let rightBudget = right ? budget : 0;
|
|
375
|
+
if (left && right) {
|
|
376
|
+
leftBudget = Math.ceil(budget / 2);
|
|
377
|
+
rightBudget = budget - leftBudget;
|
|
378
|
+
if (leftNatural < leftBudget) {
|
|
379
|
+
leftBudget = leftNatural;
|
|
380
|
+
rightBudget = budget - leftBudget;
|
|
381
|
+
} else if (rightNatural < rightBudget) {
|
|
382
|
+
rightBudget = rightNatural;
|
|
383
|
+
leftBudget = budget - rightBudget;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
left = leftBudget > 0 ? truncateToWidth(left, leftBudget, "…") : "";
|
|
387
|
+
right = rightBudget > 0 ? truncateToWidth(right, rightBudget, "…") : "";
|
|
388
|
+
return leftBudget < leftNatural;
|
|
389
|
+
};
|
|
390
|
+
let leftTruncated = fitLabels();
|
|
391
|
+
for (const fallback of options.leftFallbacks ?? []) {
|
|
392
|
+
if (!leftTruncated) break;
|
|
393
|
+
left = fallback;
|
|
394
|
+
right = options.right;
|
|
395
|
+
leftTruncated = fitLabels();
|
|
396
|
+
}
|
|
397
|
+
const partWidth = (label: string) => (label ? visibleWidth(label) + 3 : 1);
|
|
398
|
+
let leftWidth = partWidth(left);
|
|
399
|
+
let rightWidth = partWidth(right);
|
|
400
|
+
if (leftWidth + rightWidth > innerWidth) {
|
|
401
|
+
left = "";
|
|
402
|
+
right = "";
|
|
403
|
+
leftWidth = 1;
|
|
404
|
+
rightWidth = 1;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const fillWidth = Math.max(0, innerWidth - leftWidth - rightWidth);
|
|
408
|
+
const leftPart = left
|
|
409
|
+
? `${options.renderBorder("─ ")}${left}${options.renderBorder(" ")}`
|
|
410
|
+
: options.renderBorder("─");
|
|
411
|
+
const rightPart = right
|
|
412
|
+
? `${options.renderBorder(" ")}${right}${options.renderBorder(" ─")}`
|
|
413
|
+
: options.renderBorder("─");
|
|
414
|
+
return `${options.renderBorder(options.leftCorner)}${leftPart}${options.renderBorder(
|
|
415
|
+
"─".repeat(fillWidth),
|
|
416
|
+
)}${rightPart}${options.renderBorder(options.rightCorner)}`;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
export function renderMinimalistFrame({
|
|
420
|
+
width,
|
|
421
|
+
editorLines,
|
|
422
|
+
autocompleteLines = [],
|
|
423
|
+
viewport,
|
|
424
|
+
inputText,
|
|
425
|
+
metadata,
|
|
426
|
+
uiTheme,
|
|
427
|
+
config,
|
|
428
|
+
borderColor,
|
|
429
|
+
}: MinimalistFrameOptions): string[] {
|
|
430
|
+
if (width <= 4) return clampLines(editorLines, width);
|
|
431
|
+
const contentWidth = Math.max(0, width - 4);
|
|
432
|
+
const source = config.components.editor.colorSource;
|
|
433
|
+
const adaptive = config.components.editor.borderColorMode === "adaptive";
|
|
434
|
+
const thinking = sanitizeEditorMetadataText(metadata.thinkingLevel ?? "");
|
|
435
|
+
const activeThinking =
|
|
436
|
+
thinking && thinking.toLowerCase() !== "off" ? thinking : "";
|
|
437
|
+
const renderStaticBorder = (text: string) =>
|
|
438
|
+
renderStyleForSourceOrFallback(
|
|
439
|
+
uiTheme,
|
|
440
|
+
source,
|
|
441
|
+
config.colors.editorBorder,
|
|
442
|
+
EDITOR_BORDER_FALLBACK,
|
|
443
|
+
text,
|
|
444
|
+
);
|
|
445
|
+
const terminalAdaptiveThinkingStyle = activeThinking
|
|
446
|
+
? (thinkingStyle(config, activeThinking) ??
|
|
447
|
+
MINIMALIST_ADAPTIVE_TERMINAL_THINKING_FALLBACKS[
|
|
448
|
+
activeThinking.toLowerCase()
|
|
449
|
+
] ??
|
|
450
|
+
MINIMALIST_THINKING_FALLBACK.terminal)
|
|
451
|
+
: undefined;
|
|
452
|
+
const renderBorder = (text: string) => {
|
|
453
|
+
if (!adaptive) return renderStaticBorder(text);
|
|
454
|
+
if (source === "terminal") {
|
|
455
|
+
return terminalAdaptiveThinkingStyle
|
|
456
|
+
? renderStyleForSource(
|
|
457
|
+
uiTheme,
|
|
458
|
+
source,
|
|
459
|
+
terminalAdaptiveThinkingStyle,
|
|
460
|
+
text,
|
|
461
|
+
)
|
|
462
|
+
: renderStaticBorder(text);
|
|
463
|
+
}
|
|
464
|
+
if (!borderColor) return renderStaticBorder(text);
|
|
465
|
+
try {
|
|
466
|
+
const rendered = borderColor(text);
|
|
467
|
+
return typeof rendered === "string" ? rendered : renderStaticBorder(text);
|
|
468
|
+
} catch {
|
|
469
|
+
return renderStaticBorder(text);
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
const renderStaticThinking = (text: string) =>
|
|
473
|
+
renderStyleForSourceOrFallback(
|
|
474
|
+
uiTheme,
|
|
475
|
+
source,
|
|
476
|
+
thinkingStyle(config, text),
|
|
477
|
+
MINIMALIST_THINKING_FALLBACK,
|
|
478
|
+
text,
|
|
479
|
+
);
|
|
480
|
+
const renderThinking = adaptive ? renderBorder : renderStaticThinking;
|
|
481
|
+
const separator = safeThemeFg(uiTheme, "muted", " · ");
|
|
482
|
+
const viewportLabel = (
|
|
483
|
+
direction: "above" | "below",
|
|
484
|
+
count: string | undefined,
|
|
485
|
+
) => {
|
|
486
|
+
if (!count || !/^[1-9]\d*$/.test(count)) return "";
|
|
487
|
+
return safeThemeFg(
|
|
488
|
+
uiTheme,
|
|
489
|
+
"muted",
|
|
490
|
+
`${direction === "above" ? "↑" : "↓"} ${count} more`,
|
|
491
|
+
);
|
|
492
|
+
};
|
|
493
|
+
const topMetadata = renderTopLeft(inputText, metadata, uiTheme, config);
|
|
494
|
+
const topOperational = renderTopLeft(
|
|
495
|
+
inputText,
|
|
496
|
+
metadata,
|
|
497
|
+
uiTheme,
|
|
498
|
+
config,
|
|
499
|
+
false,
|
|
500
|
+
);
|
|
501
|
+
const topViewport = viewportLabel("above", viewport?.above);
|
|
502
|
+
const topLeft = joinStyled([topViewport, topMetadata], separator);
|
|
503
|
+
const topRightBudget = Math.max(0, width - 8 - visibleWidth(topLeft));
|
|
504
|
+
const topFallbacks = [
|
|
505
|
+
joinStyled([topViewport, topOperational], separator),
|
|
506
|
+
topOperational,
|
|
507
|
+
].filter(
|
|
508
|
+
(value, index, values) =>
|
|
509
|
+
value !== topLeft && values.indexOf(value) === index,
|
|
510
|
+
);
|
|
511
|
+
const top = renderLabeledBorder({
|
|
512
|
+
width,
|
|
513
|
+
left: topLeft,
|
|
514
|
+
leftFallbacks: topFallbacks,
|
|
515
|
+
right: renderTopRight(
|
|
516
|
+
metadata,
|
|
517
|
+
uiTheme,
|
|
518
|
+
config,
|
|
519
|
+
topRightBudget,
|
|
520
|
+
renderBorder,
|
|
521
|
+
renderThinking,
|
|
522
|
+
),
|
|
523
|
+
leftCorner: "╭",
|
|
524
|
+
rightCorner: "╮",
|
|
525
|
+
renderBorder,
|
|
526
|
+
});
|
|
527
|
+
const bottomMetadata = renderBottomLeft(metadata, uiTheme, config);
|
|
528
|
+
const bottomViewport = viewportLabel("below", viewport?.below);
|
|
529
|
+
const bottom = renderLabeledBorder({
|
|
530
|
+
width,
|
|
531
|
+
left: joinStyled([bottomViewport, bottomMetadata], separator),
|
|
532
|
+
leftFallbacks: bottomViewport ? [bottomMetadata] : undefined,
|
|
533
|
+
right: renderBottomRight(metadata, uiTheme, config),
|
|
534
|
+
leftCorner: "╰",
|
|
535
|
+
rightCorner: "╯",
|
|
536
|
+
renderBorder,
|
|
537
|
+
});
|
|
538
|
+
const content = editorLines.map(
|
|
539
|
+
(line) =>
|
|
540
|
+
`${renderBorder("│")} ${fillLine(line, contentWidth)} ${renderBorder("│")}`,
|
|
541
|
+
);
|
|
542
|
+
const autocomplete = renderFramedAutocompleteRows({
|
|
543
|
+
width,
|
|
544
|
+
lines: autocompleteLines,
|
|
545
|
+
renderBorder,
|
|
546
|
+
});
|
|
547
|
+
return clampLines([top, ...content, ...autocomplete, bottom], width);
|
|
548
|
+
}
|