pi-ui-extend 0.1.32 → 0.1.33
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/README.md +1 -1
- package/dist/app/app.d.ts +2 -0
- package/dist/app/app.js +28 -0
- package/dist/app/commands/command-session-actions.js +29 -1
- package/dist/app/constants.d.ts +1 -1
- package/dist/app/constants.js +2 -2
- package/dist/app/icons.d.ts +4 -9
- package/dist/app/icons.js +11 -34
- package/dist/app/rendering/conversation-entry-renderer.d.ts +1 -0
- package/dist/app/rendering/conversation-tool-renderer.d.ts +1 -0
- package/dist/app/rendering/conversation-tool-renderer.js +12 -18
- package/dist/app/rendering/conversation-viewport.d.ts +4 -0
- package/dist/app/rendering/conversation-viewport.js +144 -13
- package/dist/app/rendering/dcp-stats.js +42 -16
- package/dist/app/rendering/render-controller.js +4 -0
- package/dist/app/rendering/status-line-renderer.d.ts +7 -1
- package/dist/app/rendering/status-line-renderer.js +21 -0
- package/dist/app/rendering/tab-line-renderer.js +2 -2
- package/dist/app/rendering/tool-block-renderer.d.ts +1 -0
- package/dist/app/rendering/tool-block-renderer.js +37 -11
- package/dist/app/runtime.js +1 -1
- package/dist/app/screen/mouse-controller.d.ts +5 -1
- package/dist/app/screen/mouse-controller.js +16 -0
- package/dist/app/screen/scroll-controller.d.ts +20 -0
- package/dist/app/screen/scroll-controller.js +127 -10
- package/dist/app/session/lazy-session-manager.js +35 -5
- package/dist/app/session/pix-system-message.d.ts +1 -0
- package/dist/app/session/pix-system-message.js +14 -3
- package/dist/app/session/queued-message-controller.d.ts +11 -4
- package/dist/app/session/queued-message-controller.js +74 -59
- package/dist/app/session/queued-message-entries.d.ts +2 -1
- package/dist/app/session/queued-message-entries.js +12 -1
- package/dist/app/session/session-event-controller.d.ts +42 -1
- package/dist/app/session/session-event-controller.js +473 -29
- package/dist/app/session/session-history.js +23 -4
- package/dist/app/session/tabs-controller.d.ts +11 -1
- package/dist/app/session/tabs-controller.js +102 -21
- package/dist/app/types.d.ts +14 -1
- package/dist/bundled-extensions/question/contract.d.ts +25 -0
- package/dist/bundled-extensions/question/contract.js +94 -0
- package/dist/bundled-extensions/question/index.d.ts +7 -0
- package/dist/bundled-extensions/question/index.js +28 -0
- package/dist/bundled-extensions/question/render.d.ts +4 -0
- package/dist/bundled-extensions/question/render.js +27 -0
- package/dist/bundled-extensions/question/result.d.ts +6 -0
- package/dist/bundled-extensions/question/result.js +84 -0
- package/dist/bundled-extensions/question/tool-description.d.ts +7 -0
- package/dist/bundled-extensions/question/tool-description.js +11 -0
- package/dist/bundled-extensions/question/tui.d.ts +2 -0
- package/dist/bundled-extensions/question/tui.js +577 -0
- package/dist/bundled-extensions/question/types.d.ts +103 -0
- package/dist/bundled-extensions/question/types.js +1 -0
- package/dist/bundled-extensions/session-title/config.d.ts +17 -0
- package/dist/bundled-extensions/session-title/config.js +150 -0
- package/dist/bundled-extensions/session-title/index.d.ts +5 -0
- package/dist/bundled-extensions/session-title/index.js +384 -0
- package/dist/bundled-extensions/session-title/title-generation.d.ts +26 -0
- package/dist/bundled-extensions/session-title/title-generation.js +141 -0
- package/dist/bundled-extensions/terminal-bell/index.d.ts +14 -0
- package/dist/bundled-extensions/terminal-bell/index.js +491 -0
- package/dist/config.d.ts +1 -1
- package/dist/config.js +2 -1
- package/dist/default-pix-config.js +2 -1
- package/dist/icon-theme.d.ts +7 -0
- package/dist/icon-theme.js +36 -0
- package/dist/schemas/pi-tools-suite-schema.d.ts +4 -0
- package/dist/schemas/pi-tools-suite-schema.js +5 -0
- package/dist/schemas/pix-schema.d.ts +1 -0
- package/dist/schemas/pix-schema.js +1 -0
- package/external/pi-tools-suite/README.md +7 -7
- package/external/pi-tools-suite/src/async-subagents/async-subagents.sample.jsonc +16 -16
- package/external/pi-tools-suite/src/async-subagents/core/state.ts +18 -4
- package/external/pi-tools-suite/src/async-subagents/core/types.ts +4 -0
- package/external/pi-tools-suite/src/async-subagents/tools/result.ts +14 -26
- package/external/pi-tools-suite/src/async-subagents/tools/subagents.ts +0 -1
- package/external/pi-tools-suite/src/dcp/config.ts +14 -14
- package/external/pi-tools-suite/src/dcp/index.ts +31 -43
- package/external/pi-tools-suite/src/dcp/state-persistence.ts +151 -0
- package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +25 -18
- package/external/pi-tools-suite/src/tool-descriptions.ts +6 -7
- package/package.json +3 -2
- package/schemas/pi-tools-suite.json +14 -0
- package/schemas/pix.json +7 -0
- package/extensions/question/contract.ts +0 -100
- package/extensions/question/index.ts +0 -34
- package/extensions/question/render.ts +0 -28
- package/extensions/question/result.ts +0 -86
- package/extensions/question/tool-description.ts +0 -11
- package/extensions/question/tui.ts +0 -629
- package/extensions/question/types.ts +0 -123
- package/extensions/session-title/config.ts +0 -164
- package/extensions/session-title/index.ts +0 -502
- package/extensions/terminal-bell/index.ts +0 -345
package/README.md
CHANGED
|
@@ -212,7 +212,7 @@ Type `/` in the prompt to open the command picker. Commands that accept argument
|
|
|
212
212
|
| `/import` | `<path.jsonl>` | Import and resume a session from a JSONL file. |
|
|
213
213
|
| `/share` | — | Share the session as a private GitHub gist (requires `gh` CLI). |
|
|
214
214
|
| `/copy` | — | Copy the last agent message to the clipboard. |
|
|
215
|
-
| `/name` | `[name]` |
|
|
215
|
+
| `/name` | `[name]` | Set the session display name. Without arguments, generate one automatically using the session-title logic. |
|
|
216
216
|
| `/session` | — | Show session info: message counts, token usage, and cost. |
|
|
217
217
|
| `/usage` | — | Show local account quota usage and context window utilization. |
|
|
218
218
|
| `/changelog` | — | Show the Pi package changelog. |
|
package/dist/app/app.d.ts
CHANGED
|
@@ -109,6 +109,8 @@ export declare class PiUiExtendApp {
|
|
|
109
109
|
private render;
|
|
110
110
|
private scheduleRender;
|
|
111
111
|
private syncScrollAfterInputEditorChange;
|
|
112
|
+
private conversationQuickScrollDirections;
|
|
113
|
+
private scrollConversationQuick;
|
|
112
114
|
private renderStatusLine;
|
|
113
115
|
private terminalColumns;
|
|
114
116
|
private terminalRows;
|
package/dist/app/app.js
CHANGED
|
@@ -183,10 +183,13 @@ export class PiUiExtendApp {
|
|
|
183
183
|
loadSessionHistoryAsync: (options) => this.loadSessionHistoryAsync(options),
|
|
184
184
|
captureSessionView: () => this.captureSessionView(),
|
|
185
185
|
restoreSessionView: (view) => this.restoreSessionView(view),
|
|
186
|
+
restoreScrollState: (state) => this.scrollController.restoreState(state),
|
|
186
187
|
syncUserSessionEntryMetadata: () => this.workspaceActions.syncUserSessionEntryMetadata(),
|
|
187
188
|
captureInputState: () => this.inputEditor.draftState,
|
|
188
189
|
restoreInputState: (state) => this.restoreTabInputState(state),
|
|
189
190
|
closeMenusForTabSwitch: () => this.popupMenus.closeMenusForTabSwitch(),
|
|
191
|
+
captureAutoUserMessages: () => this.queuedMessages.captureAutoUserMessages(),
|
|
192
|
+
restoreAutoUserMessages: (messages) => this.queuedMessages.restoreAutoUserMessages(messages),
|
|
190
193
|
captureDeferredUserMessages: () => this.queuedMessages.captureDeferredUserMessages(),
|
|
191
194
|
restoreDeferredUserMessages: (messages) => this.queuedMessages.restoreDeferredUserMessages(messages),
|
|
192
195
|
addEntry: (entry) => this.addEntry(entry),
|
|
@@ -283,6 +286,7 @@ export class PiUiExtendApp {
|
|
|
283
286
|
terminalBellSoundStatusWidgetEnabled: () => this.terminalBellSoundController.isEnabled(),
|
|
284
287
|
voiceStatusWidgetText: () => this.voiceController.statusWidgetText(),
|
|
285
288
|
voiceStatusWidgetActive: () => this.voiceController.statusWidgetActive(),
|
|
289
|
+
conversationQuickScrollDirections: () => this.conversationQuickScrollDirections(),
|
|
286
290
|
queueableInputActive: () => this.inputEditor.promptText.trimEnd().length > 0 || this.inputEditor.images.length > 0,
|
|
287
291
|
userMessageJumpMenuActive: () => this.popupMenus.directMenu === "user-message-jump",
|
|
288
292
|
allThinkingExpandedActive: () => this.allThinkingExpanded,
|
|
@@ -364,6 +368,7 @@ export class PiUiExtendApp {
|
|
|
364
368
|
setSessionStatus: (session) => this.setSessionStatus(session),
|
|
365
369
|
setSessionActivity: (activity) => this.setSessionActivity(activity),
|
|
366
370
|
updateQueuedMessageStatus: () => this.queuedMessages.updateQueuedMessageStatus(),
|
|
371
|
+
flushAutoUserMessages: () => { void this.queuedMessages.flushAutoUserMessages(); },
|
|
367
372
|
prepareWorkspaceMutation: (toolName, args) => this.workspaceActions.prepareWorkspaceMutation(toolName, args),
|
|
368
373
|
workspaceMutationFromToolExecution: (input) => this.workspaceActions.workspaceMutationFromToolExecution(input),
|
|
369
374
|
recordWorkspaceMutationForUserEntry: (entryId, mutation) => this.workspaceActions.recordWorkspaceMutationForUserEntry(entryId, mutation),
|
|
@@ -420,6 +425,7 @@ export class PiUiExtendApp {
|
|
|
420
425
|
colors: this.theme.colors,
|
|
421
426
|
pixConfig: this.pixConfig,
|
|
422
427
|
outputFilters: this.outputFilters,
|
|
428
|
+
availableThinkingLevels: () => this.runtime?.session.getAvailableThinkingLevels(),
|
|
423
429
|
hasDynamicConversationBlock: () => this.popupMenus.hasDynamicConversationBlock(),
|
|
424
430
|
isDynamicConversationBlock: (entry) => this.popupMenus.isDynamicConversationBlock(entry),
|
|
425
431
|
renderInlineUserMessageMenu: (entry, context) => this.popupMenus.renderInlineUserMessageMenu(entry, context),
|
|
@@ -433,6 +439,9 @@ export class PiUiExtendApp {
|
|
|
433
439
|
hasOlderSessionHistory: () => this.sessionEvents.hasOlderSessionHistory(),
|
|
434
440
|
isLoadingOlderSessionHistory: () => this.sessionEvents.isLoadingOlderSessionHistory(),
|
|
435
441
|
loadOlderSessionHistory: (options) => this.sessionEvents.loadOlderSessionHistory(options),
|
|
442
|
+
hasNewerSessionHistory: () => this.sessionEvents.hasNewerSessionHistory(),
|
|
443
|
+
isLoadingNewerSessionHistory: () => this.sessionEvents.isLoadingNewerSessionHistory(),
|
|
444
|
+
loadNewerSessionHistory: (options) => this.sessionEvents.loadNewerSessionHistory(options),
|
|
436
445
|
render: () => this.render(),
|
|
437
446
|
});
|
|
438
447
|
this.commandController = new AppCommandController({
|
|
@@ -564,6 +573,7 @@ export class PiUiExtendApp {
|
|
|
564
573
|
this.render();
|
|
565
574
|
});
|
|
566
575
|
},
|
|
576
|
+
scrollConversationQuick: (direction) => this.scrollConversationQuick(direction),
|
|
567
577
|
toggleAllThinkingExpanded: () => {
|
|
568
578
|
this.allThinkingExpanded = !this.allThinkingExpanded;
|
|
569
579
|
this.render();
|
|
@@ -904,12 +914,14 @@ export class PiUiExtendApp {
|
|
|
904
914
|
return {
|
|
905
915
|
entries: [...this.entries],
|
|
906
916
|
eventState: this.sessionEvents.snapshotState(),
|
|
917
|
+
scrollState: this.scrollController.captureState(),
|
|
907
918
|
};
|
|
908
919
|
}
|
|
909
920
|
restoreSessionView(view) {
|
|
910
921
|
this.entries.splice(0, this.entries.length, ...view.entries);
|
|
911
922
|
this.sessionEvents.restoreState(view.eventState);
|
|
912
923
|
this.conversationViewport.clear();
|
|
924
|
+
this.scrollController.restoreState(view.scrollState);
|
|
913
925
|
this.workspaceActions.syncUserSessionEntryMetadata();
|
|
914
926
|
}
|
|
915
927
|
loadSessionHistory() {
|
|
@@ -1109,6 +1121,22 @@ export class PiUiExtendApp {
|
|
|
1109
1121
|
this.lastInputEditorContentVersion = contentVersion;
|
|
1110
1122
|
this.scrollController.scrollToBottom();
|
|
1111
1123
|
}
|
|
1124
|
+
conversationQuickScrollDirections() {
|
|
1125
|
+
const columns = this.terminalColumns();
|
|
1126
|
+
const terminalRows = this.terminalRows();
|
|
1127
|
+
const rows = Math.max(1, terminalRows - this.tabLineRenderer.panelRows(terminalRows));
|
|
1128
|
+
const { bodyHeight } = this.editorLayoutRenderer.computeLayout(columns, rows);
|
|
1129
|
+
if (bodyHeight <= 0)
|
|
1130
|
+
return { up: false, down: false };
|
|
1131
|
+
return this.scrollController.quickScrollDirections(columns, bodyHeight);
|
|
1132
|
+
}
|
|
1133
|
+
async scrollConversationQuick(direction) {
|
|
1134
|
+
const changed = direction === "up"
|
|
1135
|
+
? await this.scrollController.scrollToAbsoluteTop()
|
|
1136
|
+
: await this.scrollController.scrollToAbsoluteBottom();
|
|
1137
|
+
if (changed)
|
|
1138
|
+
this.render();
|
|
1139
|
+
}
|
|
1112
1140
|
renderStatusLine() {
|
|
1113
1141
|
this.renderController.renderStatusLine();
|
|
1114
1142
|
}
|
|
@@ -10,6 +10,8 @@ import { copyTextToClipboard } from "../screen/clipboard.js";
|
|
|
10
10
|
import { formatAccountUsageReport, queryAccountUsageReport } from "../model/model-usage-status.js";
|
|
11
11
|
import { checkPixUpdate, formatPixUpdateCheck, parsePixUpdateArgs, pixUpdateUsage } from "../cli/update.js";
|
|
12
12
|
import { createStartupInfoMessage } from "../cli/startup-info.js";
|
|
13
|
+
import { loadSessionTitleConfig } from "../../bundled-extensions/session-title/config.js";
|
|
14
|
+
import { fallbackSessionTitleFromInput, firstUserMessageText, generateSessionTitle, sessionTitleModelRefs, } from "../../bundled-extensions/session-title/title-generation.js";
|
|
13
15
|
export class SessionCommandActions {
|
|
14
16
|
host;
|
|
15
17
|
constructor(host) {
|
|
@@ -100,8 +102,34 @@ export class SessionCommandActions {
|
|
|
100
102
|
return;
|
|
101
103
|
const name = argumentsText.trim();
|
|
102
104
|
if (!name) {
|
|
103
|
-
|
|
105
|
+
const branch = runtime.session.sessionManager.getBranch();
|
|
106
|
+
const firstPrompt = firstUserMessageText(branch);
|
|
107
|
+
if (!firstPrompt)
|
|
108
|
+
throw new Error("Cannot auto-name this session yet: no user messages found.");
|
|
109
|
+
const config = loadSessionTitleConfig(runtime.cwd);
|
|
110
|
+
const fallbackTitle = fallbackSessionTitleFromInput(firstPrompt, config.maxTitleChars);
|
|
111
|
+
const modelRefs = sessionTitleModelRefs(config);
|
|
112
|
+
let generatedName;
|
|
113
|
+
this.host.setStatus("generating session name");
|
|
114
|
+
this.host.render();
|
|
115
|
+
if (config.enabled) {
|
|
116
|
+
for (const modelRef of modelRefs) {
|
|
117
|
+
for (let attempt = 0; attempt < config.generationAttempts; attempt++) {
|
|
118
|
+
generatedName = await generateSessionTitle(firstPrompt.slice(0, config.maxInputChars).trim(), runtime.services.modelRegistry, config, modelRef, AbortSignal.timeout(config.timeoutMs));
|
|
119
|
+
if (generatedName)
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
if (generatedName)
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const nextName = generatedName ?? fallbackTitle;
|
|
127
|
+
if (!nextName)
|
|
128
|
+
throw new Error("Could not generate a session name for this session.");
|
|
129
|
+
runtime.session.setSessionName(nextName);
|
|
130
|
+
this.host.addEntry({ id: createId("system"), kind: "system", text: `Session name set: ${nextName}` });
|
|
104
131
|
this.host.setSessionStatus(runtime.session);
|
|
132
|
+
this.host.render();
|
|
105
133
|
return;
|
|
106
134
|
}
|
|
107
135
|
runtime.session.setSessionName(name);
|
package/dist/app/constants.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ResolvedToolRule } from "../config.js";
|
|
2
2
|
export declare const THINKING_LEVELS: readonly ["off", "minimal", "low", "medium", "high", "xhigh"];
|
|
3
3
|
export declare const THINKING_MENU_MAX_ROWS: number;
|
|
4
|
-
export declare const PI_FAVORITE_MODEL_REFS: readonly ["amazon-bedrock/us.anthropic.claude-opus-4-6-v1", "anthropic/claude-opus-4-8", "openai/gpt-5.4", "azure-openai-responses/gpt-5.4", "openai-codex/gpt-5.5", "deepseek/deepseek-v4-pro", "google/gemini-3.1-pro-preview", "google-vertex/gemini-3.1-pro-preview", "github-copilot/gpt-5.4", "openrouter/moonshotai/kimi-k2.6", "vercel-ai-gateway/zai/glm-5.
|
|
4
|
+
export declare const PI_FAVORITE_MODEL_REFS: readonly ["amazon-bedrock/us.anthropic.claude-opus-4-6-v1", "anthropic/claude-opus-4-8", "openai/gpt-5.4", "azure-openai-responses/gpt-5.4", "openai-codex/gpt-5.5", "deepseek/deepseek-v4-pro", "google/gemini-3.1-pro-preview", "google-vertex/gemini-3.1-pro-preview", "github-copilot/gpt-5.4", "openrouter/moonshotai/kimi-k2.6", "vercel-ai-gateway/zai/glm-5.2", "xai/grok-4.20-0309-reasoning", "groq/openai/gpt-oss-120b", "cerebras/zai-glm-4.7", "zai/glm-5.2", "mistral/devstral-medium-latest", "minimax/MiniMax-M2.7", "minimax-cn/MiniMax-M2.7", "moonshotai/kimi-k2.6", "moonshotai-cn/kimi-k2.6", "huggingface/moonshotai/Kimi-K2.6", "fireworks/accounts/fireworks/models/kimi-k2p6", "together/moonshotai/Kimi-K2.6", "opencode/kimi-k2.6", "opencode-go/kimi-k2.6", "kimi-coding/kimi-for-coding", "cloudflare-workers-ai/@cf/moonshotai/kimi-k2.6", "cloudflare-ai-gateway/workers-ai/@cf/moonshotai/kimi-k2.6", "xiaomi/mimo-v2.5-pro", "xiaomi-token-plan-cn/mimo-v2.5-pro", "xiaomi-token-plan-ams/mimo-v2.5-pro", "xiaomi-token-plan-sgp/mimo-v2.5-pro"];
|
|
5
5
|
export declare const SLASH_COMMAND_MENU_MAX_ROWS = 6;
|
|
6
6
|
export declare const RESUME_MENU_MAX_ROWS = 20;
|
|
7
7
|
export declare const RESUME_MENU_INITIAL_SESSION_ROWS = 30;
|
package/dist/app/constants.js
CHANGED
|
@@ -12,11 +12,11 @@ export const PI_FAVORITE_MODEL_REFS = [
|
|
|
12
12
|
"google-vertex/gemini-3.1-pro-preview",
|
|
13
13
|
"github-copilot/gpt-5.4",
|
|
14
14
|
"openrouter/moonshotai/kimi-k2.6",
|
|
15
|
-
"vercel-ai-gateway/zai/glm-5.
|
|
15
|
+
"vercel-ai-gateway/zai/glm-5.2",
|
|
16
16
|
"xai/grok-4.20-0309-reasoning",
|
|
17
17
|
"groq/openai/gpt-oss-120b",
|
|
18
18
|
"cerebras/zai-glm-4.7",
|
|
19
|
-
"zai/glm-5.
|
|
19
|
+
"zai/glm-5.2",
|
|
20
20
|
"mistral/devstral-medium-latest",
|
|
21
21
|
"minimax/MiniMax-M2.7",
|
|
22
22
|
"minimax-cn/MiniMax-M2.7",
|
package/dist/app/icons.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
1
|
+
import { type AppIconThemeName } from "../icon-theme.js";
|
|
2
|
+
export { appIconThemeFromFallbackFlag, appIconThemeOverrideFromEnv, parseAppIconThemeName, resolveAppIconThemeNameFromEnv, PIX_ICON_THEME_ENV, PIX_USE_FALLBACK_ICONS_ENV, type AppIconThemeName, } from "../icon-theme.js";
|
|
3
3
|
declare const NERD_FONT_ICONS: {
|
|
4
4
|
readonly alert: "";
|
|
5
5
|
readonly autoFix: "";
|
|
@@ -28,17 +28,12 @@ declare const NERD_FONT_ICONS: {
|
|
|
28
28
|
readonly toolBodyEnd: "└";
|
|
29
29
|
readonly toolBodyGutter: "│";
|
|
30
30
|
readonly toolPreviewTruncated: "⊞";
|
|
31
|
-
readonly
|
|
31
|
+
readonly up: "↑";
|
|
32
|
+
readonly down: "↓";
|
|
32
33
|
};
|
|
33
34
|
export type AppIconName = keyof typeof NERD_FONT_ICONS;
|
|
34
35
|
export type AppIconMap = Record<AppIconName, string>;
|
|
35
|
-
export type AppIconThemeName = "nerdFont" | "fallback";
|
|
36
36
|
export declare const APP_ICON_THEMES: Record<AppIconThemeName, AppIconMap>;
|
|
37
37
|
export declare const APP_ICONS: AppIconMap;
|
|
38
38
|
export declare function currentAppIconTheme(): AppIconThemeName;
|
|
39
|
-
export declare function parseAppIconThemeName(value: unknown): AppIconThemeName | undefined;
|
|
40
|
-
export declare function appIconThemeFromFallbackFlag(value: unknown): AppIconThemeName | undefined;
|
|
41
|
-
export declare function resolveAppIconThemeNameFromEnv(env?: NodeJS.ProcessEnv): AppIconThemeName;
|
|
42
|
-
export declare function appIconThemeOverrideFromEnv(env?: NodeJS.ProcessEnv): AppIconThemeName | undefined;
|
|
43
39
|
export declare function setAppIconTheme(themeName: AppIconThemeName): void;
|
|
44
|
-
export {};
|
package/dist/app/icons.js
CHANGED
|
@@ -4,8 +4,13 @@
|
|
|
4
4
|
//
|
|
5
5
|
// Use codepoint escapes for private-use characters so editors cannot silently
|
|
6
6
|
// substitute visually similar glyphs.
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
//
|
|
8
|
+
// Theme-name parsing/resolution lives in the dependency-free src/icon-theme.ts so
|
|
9
|
+
// configuration loading can resolve the theme without importing this app/ module
|
|
10
|
+
// (breaks the src/config.ts <-> src/app/icons.ts import cycle).
|
|
11
|
+
import { resolveAppIconThemeNameFromEnv, } from "../icon-theme.js";
|
|
12
|
+
// Re-exported for existing importers; the canonical home is src/icon-theme.ts.
|
|
13
|
+
export { appIconThemeFromFallbackFlag, appIconThemeOverrideFromEnv, parseAppIconThemeName, resolveAppIconThemeNameFromEnv, PIX_ICON_THEME_ENV, PIX_USE_FALLBACK_ICONS_ENV, } from "../icon-theme.js";
|
|
9
14
|
const NERD_FONT_ICONS = {
|
|
10
15
|
alert: "\u{f0026}",
|
|
11
16
|
autoFix: "\u{f0068}",
|
|
@@ -34,7 +39,8 @@ const NERD_FONT_ICONS = {
|
|
|
34
39
|
toolBodyEnd: "└",
|
|
35
40
|
toolBodyGutter: "│",
|
|
36
41
|
toolPreviewTruncated: "⊞",
|
|
37
|
-
|
|
42
|
+
up: "↑",
|
|
43
|
+
down: "↓",
|
|
38
44
|
};
|
|
39
45
|
const FALLBACK_ICONS = {
|
|
40
46
|
alert: "!",
|
|
@@ -64,7 +70,8 @@ const FALLBACK_ICONS = {
|
|
|
64
70
|
toolBodyEnd: "`",
|
|
65
71
|
toolBodyGutter: "|",
|
|
66
72
|
toolPreviewTruncated: "+",
|
|
67
|
-
|
|
73
|
+
up: "↑",
|
|
74
|
+
down: "↓",
|
|
68
75
|
};
|
|
69
76
|
export const APP_ICON_THEMES = {
|
|
70
77
|
nerdFont: NERD_FONT_ICONS,
|
|
@@ -75,36 +82,6 @@ export const APP_ICONS = { ...APP_ICON_THEMES[currentAppIconThemeName] };
|
|
|
75
82
|
export function currentAppIconTheme() {
|
|
76
83
|
return currentAppIconThemeName;
|
|
77
84
|
}
|
|
78
|
-
export function parseAppIconThemeName(value) {
|
|
79
|
-
if (typeof value !== "string")
|
|
80
|
-
return undefined;
|
|
81
|
-
const normalized = value.trim().toLowerCase().replace(/[\s_-]+/gu, "");
|
|
82
|
-
if (normalized === "fallback" || normalized === "plain" || normalized === "ascii")
|
|
83
|
-
return "fallback";
|
|
84
|
-
if (normalized === "nerdfont" || normalized === "font" || normalized === "icons")
|
|
85
|
-
return "nerdFont";
|
|
86
|
-
return undefined;
|
|
87
|
-
}
|
|
88
|
-
export function appIconThemeFromFallbackFlag(value) {
|
|
89
|
-
if (typeof value === "boolean")
|
|
90
|
-
return value ? "fallback" : "nerdFont";
|
|
91
|
-
if (typeof value !== "string")
|
|
92
|
-
return undefined;
|
|
93
|
-
const normalized = value.trim().toLowerCase();
|
|
94
|
-
if (["1", "true", "yes", "on", "fallback"].includes(normalized))
|
|
95
|
-
return "fallback";
|
|
96
|
-
if (["0", "false", "no", "off", "nerdfont", "nerd-font"].includes(normalized))
|
|
97
|
-
return "nerdFont";
|
|
98
|
-
return undefined;
|
|
99
|
-
}
|
|
100
|
-
export function resolveAppIconThemeNameFromEnv(env = process.env) {
|
|
101
|
-
return appIconThemeOverrideFromEnv(env) ?? "nerdFont";
|
|
102
|
-
}
|
|
103
|
-
export function appIconThemeOverrideFromEnv(env = process.env) {
|
|
104
|
-
return appIconThemeFromFallbackFlag(env[PIX_USE_FALLBACK_ICONS_ENV])
|
|
105
|
-
?? parseAppIconThemeName(env[PIX_ICON_THEME_ENV])
|
|
106
|
-
?? undefined;
|
|
107
|
-
}
|
|
108
85
|
export function setAppIconTheme(themeName) {
|
|
109
86
|
currentAppIconThemeName = themeName;
|
|
110
87
|
Object.assign(APP_ICONS, APP_ICON_THEMES[themeName]);
|
|
@@ -11,6 +11,7 @@ export type ConversationEntryRenderOptions = {
|
|
|
11
11
|
colors: Theme["colors"];
|
|
12
12
|
pixConfig: PixConfig;
|
|
13
13
|
outputFilters: readonly RegExp[];
|
|
14
|
+
availableThinkingLevels?: readonly string[];
|
|
14
15
|
superCompactTools?: boolean;
|
|
15
16
|
allThinkingExpanded?: boolean;
|
|
16
17
|
renderInlineUserMessageMenu: (entry: Extract<Entry, {
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { resolveColor, resolveToolRule } from "../../config.js";
|
|
2
2
|
import { formatMarkdownTables, markdownSyntaxHighlightsForText } from "../../markdown-format.js";
|
|
3
3
|
import { renderToolDisplay } from "../../tool-renderers/index.js";
|
|
4
|
-
import {
|
|
4
|
+
import { SUBAGENT_STATUSES, THINKING_TOOL_NAME, TODO_TOOL_NAME } from "../constants.js";
|
|
5
5
|
import { attachImageClickTargets } from "../screen/image-click-targets.js";
|
|
6
6
|
import { formatStructuredText } from "./message-content.js";
|
|
7
7
|
import { formatSubagentTimestamp, isSubagentRunRenderDetails, isSubagentsToolName, subagentRunName, subagentStatusIcon, taskPreviewMap, } from "../subagents/subagents-model.js";
|
|
8
8
|
import { formatTodoTaskLine, isTodoDetails, visibleTodoTasks } from "../todo/todo-model.js";
|
|
9
9
|
import { renderToolBlock } from "./tool-block-renderer.js";
|
|
10
|
+
import { thinkingLevelThemeColor } from "./status-line-renderer.js";
|
|
10
11
|
export function renderConversationToolEntry(entry, width, options) {
|
|
11
12
|
const todoLines = renderTodoToolEntry(entry, width, options);
|
|
12
13
|
if (todoLines)
|
|
@@ -46,12 +47,15 @@ export function renderConversationToolEntry(entry, width, options) {
|
|
|
46
47
|
return attachImageClickTargets(lines, entry.id, entry.images, { foreground: options.colors.info, underline: true });
|
|
47
48
|
}
|
|
48
49
|
export function renderThinkingEntry(entry, width, options) {
|
|
49
|
-
const rule =
|
|
50
|
+
const rule = resolveToolRule(THINKING_TOOL_NAME, options.pixConfig.toolRenderer);
|
|
50
51
|
const markdownText = entry.text ? formatMarkdownTables(entry.text, Math.max(1, width - 2)) : "";
|
|
51
52
|
const expandedText = trimTrailingBlankLines(markdownText);
|
|
52
53
|
const forceExpanded = Boolean(options.allThinkingExpanded);
|
|
53
54
|
const compactExpandedText = options.superCompactTools && forceExpanded ? removeBlankLines(expandedText) : expandedText;
|
|
54
55
|
const expanded = forceExpanded || (entry.expanded && expandedText.trim().length > 0);
|
|
56
|
+
const headerColorOverride = entry.level
|
|
57
|
+
? thinkingLevelThemeColor(entry.level, options.colors, options.availableThinkingLevels)
|
|
58
|
+
: undefined;
|
|
55
59
|
return renderToolBlock({
|
|
56
60
|
id: entry.id,
|
|
57
61
|
toolName: THINKING_TOOL_NAME,
|
|
@@ -63,7 +67,12 @@ export function renderThinkingEntry(entry, width, options) {
|
|
|
63
67
|
expandedText: compactExpandedText || "(empty)",
|
|
64
68
|
bodyWrap: "word",
|
|
65
69
|
syntaxHighlight: compactExpandedText ? markdownSyntaxHighlightsForText(compactExpandedText) : undefined,
|
|
66
|
-
}, rule, width, options.colors, {
|
|
70
|
+
}, rule, width, options.colors, {
|
|
71
|
+
superCompact: Boolean(options.superCompactTools && !forceExpanded),
|
|
72
|
+
backgroundOverride: options.colors.thinkingMessageBackground,
|
|
73
|
+
showGutter: true,
|
|
74
|
+
...(headerColorOverride === undefined ? {} : { headerColorOverride }),
|
|
75
|
+
});
|
|
67
76
|
}
|
|
68
77
|
function trimTrailingBlankLines(text) {
|
|
69
78
|
return text.replace(/(?:\r?\n[ \t]*)+$/u, "");
|
|
@@ -199,18 +208,3 @@ function formatSubagentToolLine(agent, preview) {
|
|
|
199
208
|
parts.push(`retry:${agent.retryCount}`);
|
|
200
209
|
return parts.join(" ");
|
|
201
210
|
}
|
|
202
|
-
function resolveThinkingToolRule(pixConfig) {
|
|
203
|
-
const configured = pixConfig.toolRenderer.tools[THINKING_TOOL_NAME];
|
|
204
|
-
if (!configured)
|
|
205
|
-
return DEFAULT_THINKING_TOOL_RULE;
|
|
206
|
-
const rule = {
|
|
207
|
-
previewLines: configured.previewLines ?? DEFAULT_THINKING_TOOL_RULE.previewLines,
|
|
208
|
-
direction: configured.direction ?? DEFAULT_THINKING_TOOL_RULE.direction,
|
|
209
|
-
color: configured.color ?? DEFAULT_THINKING_TOOL_RULE.color,
|
|
210
|
-
};
|
|
211
|
-
if (configured.compactHidden != null)
|
|
212
|
-
rule.compactHidden = configured.compactHidden;
|
|
213
|
-
if (configured.hidden != null)
|
|
214
|
-
rule.hidden = configured.hidden;
|
|
215
|
-
return rule;
|
|
216
|
-
}
|
|
@@ -9,6 +9,7 @@ export type ConversationViewportHost = {
|
|
|
9
9
|
readonly colors: Theme["colors"];
|
|
10
10
|
readonly pixConfig: PixConfig;
|
|
11
11
|
readonly outputFilters: readonly RegExp[];
|
|
12
|
+
availableThinkingLevels?(): readonly string[] | undefined;
|
|
12
13
|
readonly superCompactTools?: boolean;
|
|
13
14
|
readonly allThinkingExpanded?: boolean;
|
|
14
15
|
hasDynamicConversationBlock?(): boolean;
|
|
@@ -36,11 +37,14 @@ export declare class ConversationViewport {
|
|
|
36
37
|
entries(): Entry[];
|
|
37
38
|
blockForEntry(entry: Entry, width: number): ConversationBlockCache;
|
|
38
39
|
entryBlockPositions(width: number): ConversationEntryBlockPosition[];
|
|
40
|
+
entryBlockPositionById(width: number, entryId: string): ConversationEntryBlockPosition | undefined;
|
|
39
41
|
measuredLineCountForEntries(width: number, entryIds: readonly string[]): number;
|
|
40
42
|
private layoutForWidth;
|
|
41
43
|
private buildLayout;
|
|
42
44
|
private previousMeasuredLineCount;
|
|
43
45
|
private layoutStructureChanged;
|
|
46
|
+
private syncLayoutStructure;
|
|
47
|
+
private rebuildOffsets;
|
|
44
48
|
private refreshDirtyLayoutEntries;
|
|
45
49
|
private blockCacheForWidth;
|
|
46
50
|
private refreshDynamicLayoutEntries;
|
|
@@ -73,11 +73,13 @@ export class ConversationViewport {
|
|
|
73
73
|
const dynamic = this.host.isDynamicConversationBlock(entry);
|
|
74
74
|
if (!dynamic && cached?.version === version)
|
|
75
75
|
return cached;
|
|
76
|
+
const availableThinkingLevels = this.host.availableThinkingLevels?.();
|
|
76
77
|
const lines = renderConversationEntryLines(entry, width, {
|
|
77
78
|
cwd: this.host.cwd,
|
|
78
79
|
colors: this.host.colors,
|
|
79
80
|
pixConfig: this.host.pixConfig,
|
|
80
81
|
outputFilters: this.host.outputFilters,
|
|
82
|
+
...(availableThinkingLevels ? { availableThinkingLevels } : {}),
|
|
81
83
|
superCompactTools: Boolean(this.host.superCompactTools),
|
|
82
84
|
allThinkingExpanded: Boolean(this.host.allThinkingExpanded),
|
|
83
85
|
renderInlineUserMessageMenu: (userEntry, context) => this.host.renderInlineUserMessageMenu(userEntry, context),
|
|
@@ -102,6 +104,23 @@ export class ConversationViewport {
|
|
|
102
104
|
block: this.blockForEntry(entry, width),
|
|
103
105
|
}));
|
|
104
106
|
}
|
|
107
|
+
entryBlockPositionById(width, entryId) {
|
|
108
|
+
const layout = this.layoutForWidth(width);
|
|
109
|
+
const targetIndex = layout.positions.get(entryId);
|
|
110
|
+
if (targetIndex === undefined)
|
|
111
|
+
return undefined;
|
|
112
|
+
for (let index = 0; index <= targetIndex; index += 1)
|
|
113
|
+
this.ensureEntryMeasured(layout, width, index);
|
|
114
|
+
const entry = layout.entries[targetIndex];
|
|
115
|
+
if (!entry)
|
|
116
|
+
return undefined;
|
|
117
|
+
return {
|
|
118
|
+
entry,
|
|
119
|
+
offset: layout.offsets[targetIndex] ?? 0,
|
|
120
|
+
lineCount: layout.lineCounts[targetIndex] ?? 0,
|
|
121
|
+
block: this.blockForEntry(entry, width),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
105
124
|
measuredLineCountForEntries(width, entryIds) {
|
|
106
125
|
if (entryIds.length === 0)
|
|
107
126
|
return 0;
|
|
@@ -122,16 +141,21 @@ export class ConversationViewport {
|
|
|
122
141
|
const superCompactTools = Boolean(this.host.superCompactTools);
|
|
123
142
|
const allThinkingExpanded = Boolean(this.host.allThinkingExpanded);
|
|
124
143
|
let layout = this.layoutCachesByWidth.get(width);
|
|
125
|
-
if (!layout
|
|
126
|
-
|
|
127
|
-
? layout
|
|
128
|
-
: undefined;
|
|
129
|
-
layout = this.buildLayout(entries, width, superCompactTools, allThinkingExpanded, previousLayout);
|
|
144
|
+
if (!layout) {
|
|
145
|
+
layout = this.buildLayout(entries, width, superCompactTools, allThinkingExpanded);
|
|
130
146
|
this.layoutCachesByWidth.set(width, layout);
|
|
131
147
|
}
|
|
132
|
-
else {
|
|
133
|
-
this.
|
|
148
|
+
else if (this.layoutStructureChanged(layout, entries, superCompactTools, allThinkingExpanded)) {
|
|
149
|
+
const synced = this.syncLayoutStructure(layout, entries, width, superCompactTools, allThinkingExpanded);
|
|
150
|
+
if (!synced) {
|
|
151
|
+
const previousLayout = layout.superCompactTools === superCompactTools && layout.allThinkingExpanded === allThinkingExpanded
|
|
152
|
+
? layout
|
|
153
|
+
: undefined;
|
|
154
|
+
layout = this.buildLayout(entries, width, superCompactTools, allThinkingExpanded, previousLayout);
|
|
155
|
+
this.layoutCachesByWidth.set(width, layout);
|
|
156
|
+
}
|
|
134
157
|
}
|
|
158
|
+
this.refreshDirtyLayoutEntries(layout, width);
|
|
135
159
|
if (this.host.hasDynamicConversationBlock?.()) {
|
|
136
160
|
this.refreshDynamicLayoutEntries(layout, width);
|
|
137
161
|
}
|
|
@@ -173,12 +197,61 @@ export class ConversationViewport {
|
|
|
173
197
|
return previousLayout.lineCounts[previousIndex];
|
|
174
198
|
}
|
|
175
199
|
layoutStructureChanged(layout, entries, superCompactTools, allThinkingExpanded) {
|
|
176
|
-
if (layout.
|
|
200
|
+
if (layout.entryIds.length !== entries.length || layout.superCompactTools !== superCompactTools || layout.allThinkingExpanded !== allThinkingExpanded)
|
|
177
201
|
return true;
|
|
178
|
-
if (layout.
|
|
202
|
+
if (layout.entryIds.length === 0)
|
|
179
203
|
return false;
|
|
180
204
|
return layout.entryIds[0] !== entries[0]?.id || layout.entryIds[layout.entryIds.length - 1] !== entries[entries.length - 1]?.id;
|
|
181
205
|
}
|
|
206
|
+
syncLayoutStructure(layout, entries, width, superCompactTools, allThinkingExpanded) {
|
|
207
|
+
if (layout.superCompactTools !== superCompactTools || layout.allThinkingExpanded !== allThinkingExpanded)
|
|
208
|
+
return false;
|
|
209
|
+
const currentEntryIds = entries.map((entry) => entry.id);
|
|
210
|
+
const overlap = layoutOverlap(layout.entryIds, currentEntryIds);
|
|
211
|
+
if (!overlap)
|
|
212
|
+
return false;
|
|
213
|
+
const estimatedBlockLineCounts = entries.map((entry) => this.estimatedBlockLineCountForEntry(entry, width));
|
|
214
|
+
const lineCounts = [];
|
|
215
|
+
const measuredLineCounts = [];
|
|
216
|
+
const positions = new Map();
|
|
217
|
+
for (let index = 0; index < entries.length; index += 1) {
|
|
218
|
+
const entry = entries[index];
|
|
219
|
+
positions.set(entry.id, index);
|
|
220
|
+
const oldIndex = index >= overlap.currentStart && index < overlap.currentStart + overlap.length
|
|
221
|
+
? overlap.previousStart + index - overlap.currentStart
|
|
222
|
+
: undefined;
|
|
223
|
+
if (oldIndex !== undefined) {
|
|
224
|
+
lineCounts.push(layout.lineCounts[oldIndex] ?? 0);
|
|
225
|
+
measuredLineCounts.push(layout.measuredLineCounts[oldIndex] === true);
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
lineCounts.push(this.estimatedLineCountForEntry(entry, entries, index, estimatedBlockLineCounts));
|
|
229
|
+
measuredLineCounts.push(false);
|
|
230
|
+
}
|
|
231
|
+
layout.entries = entries;
|
|
232
|
+
layout.entryIds = currentEntryIds;
|
|
233
|
+
layout.lineCounts = lineCounts;
|
|
234
|
+
layout.measuredLineCounts = measuredLineCounts;
|
|
235
|
+
layout.positions = positions;
|
|
236
|
+
this.rebuildOffsets(layout);
|
|
237
|
+
const changedNextIndexes = new Set([overlap.currentStart - 1, overlap.currentStart + overlap.length - 1]);
|
|
238
|
+
for (const index of changedNextIndexes) {
|
|
239
|
+
if (index >= 0 && index < entries.length)
|
|
240
|
+
this.refreshLayoutEntry(layout, width, index, layout.measuredLineCounts[index] === true);
|
|
241
|
+
}
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
rebuildOffsets(layout) {
|
|
245
|
+
const offsets = [];
|
|
246
|
+
let totalLineCount = 0;
|
|
247
|
+
for (const lineCount of layout.lineCounts) {
|
|
248
|
+
offsets.push(totalLineCount);
|
|
249
|
+
totalLineCount += lineCount;
|
|
250
|
+
}
|
|
251
|
+
offsets.push(totalLineCount);
|
|
252
|
+
layout.offsets = offsets;
|
|
253
|
+
layout.totalLineCount = totalLineCount;
|
|
254
|
+
}
|
|
182
255
|
refreshDirtyLayoutEntries(layout, width) {
|
|
183
256
|
if (layout.dirtyEntryIds.size === 0)
|
|
184
257
|
return;
|
|
@@ -224,7 +297,7 @@ export class ConversationViewport {
|
|
|
224
297
|
const previousLineCount = layout.lineCounts[index] ?? 0;
|
|
225
298
|
const nextLineCount = measure
|
|
226
299
|
? this.measuredLineCountForEntry(entry, layout.entries, index, width)
|
|
227
|
-
: this.estimatedLineCountForEntry(entry, layout.entries, index, width);
|
|
300
|
+
: this.estimatedLineCountForEntry(entry, layout.entries, index, layout.entries.map((candidate) => this.estimatedBlockLineCountForEntry(candidate, width)));
|
|
228
301
|
layout.measuredLineCounts[index] = measure;
|
|
229
302
|
if (previousLineCount === nextLineCount)
|
|
230
303
|
return false;
|
|
@@ -240,9 +313,8 @@ export class ConversationViewport {
|
|
|
240
313
|
const block = this.blockForEntry(entry, width);
|
|
241
314
|
return this.lineCountWithGap(entry, block.lineCount, this.nextVisibleEntry(entries, index, width));
|
|
242
315
|
}
|
|
243
|
-
estimatedLineCountForEntry(entry, entries, index,
|
|
244
|
-
const blockLineCount =
|
|
245
|
-
const blockLineCounts = entries.map((candidate) => this.estimatedBlockLineCountForEntry(candidate, width));
|
|
316
|
+
estimatedLineCountForEntry(entry, entries, index, blockLineCounts) {
|
|
317
|
+
const blockLineCount = blockLineCounts[index] ?? 0;
|
|
246
318
|
return this.lineCountWithGap(entry, blockLineCount, this.nextEstimatedVisibleEntry(entries, blockLineCounts, index));
|
|
247
319
|
}
|
|
248
320
|
lineCountWithGap(entry, blockLineCount, nextEntry) {
|
|
@@ -339,6 +411,65 @@ function estimateWrappedLineCount(text, width) {
|
|
|
339
411
|
}
|
|
340
412
|
return count;
|
|
341
413
|
}
|
|
414
|
+
function layoutOverlap(previousIds, currentIds) {
|
|
415
|
+
if (currentIds.length === 0 || previousIds.length === 0)
|
|
416
|
+
return { previousStart: 0, currentStart: 0, length: 0 };
|
|
417
|
+
const candidates = [];
|
|
418
|
+
const prefixLength = commonPrefixLength(previousIds, currentIds);
|
|
419
|
+
if (prefixLength > 0)
|
|
420
|
+
candidates.push({ previousStart: 0, currentStart: 0, length: prefixLength });
|
|
421
|
+
const suffixLength = commonSuffixLength(previousIds, currentIds);
|
|
422
|
+
if (suffixLength > 0)
|
|
423
|
+
candidates.push({
|
|
424
|
+
previousStart: previousIds.length - suffixLength,
|
|
425
|
+
currentStart: currentIds.length - suffixLength,
|
|
426
|
+
length: suffixLength,
|
|
427
|
+
});
|
|
428
|
+
const suffixPrefixLength = maxSuffixPrefixOverlap(previousIds, currentIds);
|
|
429
|
+
if (suffixPrefixLength > 0)
|
|
430
|
+
candidates.push({
|
|
431
|
+
previousStart: previousIds.length - suffixPrefixLength,
|
|
432
|
+
currentStart: 0,
|
|
433
|
+
length: suffixPrefixLength,
|
|
434
|
+
});
|
|
435
|
+
const prefixSuffixLength = maxSuffixPrefixOverlap(currentIds, previousIds);
|
|
436
|
+
if (prefixSuffixLength > 0)
|
|
437
|
+
candidates.push({
|
|
438
|
+
previousStart: 0,
|
|
439
|
+
currentStart: currentIds.length - prefixSuffixLength,
|
|
440
|
+
length: prefixSuffixLength,
|
|
441
|
+
});
|
|
442
|
+
return candidates.sort((left, right) => right.length - left.length)[0];
|
|
443
|
+
}
|
|
444
|
+
function commonPrefixLength(left, right) {
|
|
445
|
+
const maxLength = Math.min(left.length, right.length);
|
|
446
|
+
let length = 0;
|
|
447
|
+
while (length < maxLength && left[length] === right[length])
|
|
448
|
+
length += 1;
|
|
449
|
+
return length;
|
|
450
|
+
}
|
|
451
|
+
function commonSuffixLength(left, right) {
|
|
452
|
+
const maxLength = Math.min(left.length, right.length);
|
|
453
|
+
let length = 0;
|
|
454
|
+
while (length < maxLength && left[left.length - length - 1] === right[right.length - length - 1])
|
|
455
|
+
length += 1;
|
|
456
|
+
return length;
|
|
457
|
+
}
|
|
458
|
+
function maxSuffixPrefixOverlap(left, right) {
|
|
459
|
+
const maxLength = Math.min(left.length, right.length);
|
|
460
|
+
for (let length = maxLength; length > 0; length -= 1) {
|
|
461
|
+
let matches = true;
|
|
462
|
+
for (let offset = 0; offset < length; offset += 1) {
|
|
463
|
+
if (left[left.length - length + offset] !== right[offset]) {
|
|
464
|
+
matches = false;
|
|
465
|
+
break;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
if (matches)
|
|
469
|
+
return length;
|
|
470
|
+
}
|
|
471
|
+
return 0;
|
|
472
|
+
}
|
|
342
473
|
function estimateToolLikeLineCount(toolName, expanded, output, width, pixConfig, superCompactTools, includeStatusLine) {
|
|
343
474
|
const rule = resolveToolRule(toolName, pixConfig.toolRenderer);
|
|
344
475
|
if (rule.hidden)
|