@linxiraos/pi-tui 1.1.27 → 1.2.0
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/CHANGELOG.md +47 -463
- package/README.md +7 -5
- package/dist/types/app-keybindings.d.ts +1 -1
- package/dist/types/apps/cleanse-board.d.ts +1 -1
- package/dist/types/apps/debug/log-viewer.d.ts +4 -0
- package/dist/types/apps/debug/raw-sse-buffer.d.ts +2 -0
- package/dist/types/apps/git/git-tui.d.ts +2 -2
- package/dist/types/apps/git/help.d.ts +3 -1
- package/dist/types/apps/if-bench-board.d.ts +2 -2
- package/dist/types/apps/setup-model-picker.d.ts +1 -1
- package/dist/types/autocomplete.d.ts +5 -2
- package/dist/types/charts/chart-plan.d.ts +674 -0
- package/dist/types/charts/chart-svg.d.ts +59 -0
- package/dist/types/charts/table-data.d.ts +175 -0
- package/dist/types/chat/assistant-message.d.ts +5 -0
- package/dist/types/chat/chat-transcript-builder.d.ts +2 -0
- package/dist/types/chat/compaction-summary-message.d.ts +1 -0
- package/dist/types/chat/execution-shared.d.ts +6 -5
- package/dist/types/chat/fence-figure.d.ts +42 -0
- package/dist/types/chat/figure-markdown.d.ts +42 -0
- package/dist/types/chat/messages.d.ts +1 -1
- package/dist/types/chat/svg-figure.d.ts +33 -0
- package/dist/types/chat/svg-source.d.ts +49 -0
- package/dist/types/chat/table-chart.d.ts +89 -0
- package/dist/types/chat/tool-execution.d.ts +2 -0
- package/dist/types/chat/user-message.d.ts +15 -2
- package/dist/types/chrome/shared.d.ts +9 -0
- package/dist/types/chrome/transcript-container.d.ts +6 -0
- package/dist/types/components/box.d.ts +1 -0
- package/dist/types/components/disclosure.d.ts +1 -0
- package/dist/types/components/editor.d.ts +15 -4
- package/dist/types/components/form.d.ts +1 -0
- package/dist/types/components/image.d.ts +12 -44
- package/dist/types/components/key-value-list.d.ts +1 -0
- package/dist/types/components/layout/row.d.ts +1 -0
- package/dist/types/components/layout/stack.d.ts +1 -0
- package/dist/types/components/markdown.d.ts +36 -3
- package/dist/types/components/scroll-view.d.ts +1 -0
- package/dist/types/components/section.d.ts +1 -0
- package/dist/types/components/table.d.ts +1 -0
- package/dist/types/components/text.d.ts +1 -0
- package/dist/types/components/tree-view.d.ts +7 -0
- package/dist/types/components/truncated-text.d.ts +1 -0
- package/dist/types/fuzzy.d.ts +29 -1
- package/dist/types/key-hint-format.d.ts +1 -1
- package/dist/types/keybinding-matchers.d.ts +3 -0
- package/dist/types/keybindings.d.ts +1 -1
- package/dist/types/kitty-graphics.d.ts +2 -2
- package/dist/types/loop-watchdog.d.ts +8 -7
- package/dist/types/multiplexers/cmux.d.ts +16 -0
- package/dist/types/multiplexers/herdr.d.ts +26 -0
- package/dist/types/multiplexers/notify.d.ts +13 -0
- package/dist/types/multiplexers/orca.d.ts +12 -0
- package/dist/types/multiplexers/screen.d.ts +9 -0
- package/dist/types/multiplexers/tmux.d.ts +29 -0
- package/dist/types/multiplexers/types.d.ts +100 -0
- package/dist/types/multiplexers/wmux.d.ts +12 -0
- package/dist/types/multiplexers/zellij.d.ts +12 -0
- package/dist/types/native/backend.d.ts +3 -2
- package/dist/types/native/blobs.d.ts +7 -3
- package/dist/types/native/encode.d.ts +3 -4
- package/dist/types/native/overlay.d.ts +5 -0
- package/dist/types/overlays/agent-hub-projection.d.ts +6 -5
- package/dist/types/overlays/agent-hub-types.d.ts +9 -0
- package/dist/types/overlays/agent-hub.d.ts +2 -0
- package/dist/types/overlays/agent-transcript-viewer.d.ts +11 -2
- package/dist/types/overlays/agents-hub.d.ts +9 -0
- package/dist/types/overlays/annotation-overlay.d.ts +19 -2
- package/dist/types/overlays/annotation-types.d.ts +2 -0
- package/dist/types/overlays/ask-dialog.d.ts +5 -2
- package/dist/types/overlays/history-search.d.ts +10 -0
- package/dist/types/overlays/hook-selector.d.ts +9 -4
- package/dist/types/overlays/jobs-panel.d.ts +1 -7
- package/dist/types/overlays/model-browser.d.ts +54 -7
- package/dist/types/overlays/model-hub.d.ts +23 -0
- package/dist/types/overlays/oauth-selector.d.ts +0 -1
- package/dist/types/overlays/plan-review-overlay.d.ts +3 -2
- package/dist/types/overlays/session-observer-registry.d.ts +2 -0
- package/dist/types/overlays/session-selector.d.ts +6 -2
- package/dist/types/overlays/settings-defs.d.ts +1 -0
- package/dist/types/overlays/usage-dashboard.d.ts +8 -4
- package/dist/types/overlays/usage-display.d.ts +3 -1
- package/dist/types/prompt/composer.d.ts +1 -1
- package/dist/types/prompt/custom-editor.d.ts +11 -8
- package/dist/types/prompt/image-source.d.ts +2 -1
- package/dist/types/prompt/interactive-context-helpers.d.ts +4 -1
- package/dist/types/prompt/model-mention-autocomplete.d.ts +1 -1
- package/dist/types/prompt/welcome.d.ts +6 -1
- package/dist/types/render/composite.d.ts +2 -0
- package/dist/types/render/output-pane.d.ts +1 -0
- package/dist/types/render/render-utils.d.ts +1 -0
- package/dist/types/render/tool-card.d.ts +6 -0
- package/dist/types/render/width-aware-text.d.ts +2 -0
- package/dist/types/setup/scenes/types.d.ts +1 -0
- package/dist/types/status-line/component.d.ts +3 -0
- package/dist/types/status-line/host.d.ts +4 -1
- package/dist/types/status-line/segments.d.ts +2 -0
- package/dist/types/status-line/types.d.ts +14 -3
- package/dist/types/terminal-capabilities.d.ts +27 -35
- package/dist/types/terminal-multiplexer.d.ts +127 -14
- package/dist/types/terminal.d.ts +22 -6
- package/dist/types/theme/active-symbols.d.ts +1 -1
- package/dist/types/theme/schema.d.ts +11 -2
- package/dist/types/theme/symbols.d.ts +1 -1
- package/dist/types/theme/theme-class.d.ts +7 -0
- package/dist/types/theme/theme.d.ts +1 -1
- package/dist/types/theme/tui-adapters.d.ts +3 -0
- package/dist/types/tmux.d.ts +11 -4
- package/dist/types/tools/daemon.d.ts +1 -1
- package/dist/types/tools/eval.d.ts +22 -3
- package/dist/types/tools/glob.d.ts +4 -0
- package/dist/types/tools/native-view.d.ts +47 -0
- package/dist/types/tools/output-meta.d.ts +11 -1
- package/dist/types/tools/renderer.d.ts +37 -2
- package/dist/types/tools/streaming-output.d.ts +20 -1
- package/dist/types/tools/task.d.ts +0 -4
- package/dist/types/tools/write.d.ts +9 -2
- package/dist/types/tools/yield.d.ts +22 -0
- package/dist/types/tui.d.ts +31 -0
- package/dist/types/utils.d.ts +6 -0
- package/dist/types/vim.d.ts +19 -5
- package/package.json +13 -9
- package/src/app-keybindings.d.ts +1 -1
- package/src/app-keybindings.ts +1 -1
- package/src/apps/autoresearch-dashboard.ts +7 -3
- package/src/apps/cleanse-board.ts +2 -2
- package/src/apps/cleanse-picker.ts +1 -1
- package/src/apps/debug/log-viewer.ts +188 -72
- package/src/apps/debug/raw-sse-buffer.ts +8 -0
- package/src/apps/debug/raw-sse.ts +39 -5
- package/src/apps/git/diff-pane.ts +26 -5
- package/src/apps/git/git-tui.ts +2 -2
- package/src/apps/git/help.ts +29 -20
- package/src/apps/if-bench-board.ts +2 -2
- package/src/apps/ps-top.ts +2 -2
- package/src/apps/setup-model-picker.ts +1 -1
- package/src/autocomplete.d.ts +5 -2
- package/src/autocomplete.ts +67 -43
- package/src/charts/chart-plan.d.ts +674 -0
- package/src/charts/chart-plan.ts +5081 -0
- package/src/charts/chart-svg.d.ts +59 -0
- package/src/charts/chart-svg.ts +4657 -0
- package/src/charts/table-data.d.ts +175 -0
- package/src/charts/table-data.ts +1478 -0
- package/src/chat/advisor-message.ts +11 -0
- package/src/chat/assistant-message.d.ts +5 -0
- package/src/chat/assistant-message.ts +219 -50
- package/src/chat/chat-transcript-builder.d.ts +2 -0
- package/src/chat/chat-transcript-builder.ts +5 -3
- package/src/chat/compaction-summary-message.d.ts +1 -0
- package/src/chat/compaction-summary-message.ts +8 -0
- package/src/chat/execution-shared.d.ts +6 -5
- package/src/chat/execution-shared.ts +42 -25
- package/src/chat/fence-figure.d.ts +42 -0
- package/src/chat/fence-figure.ts +80 -0
- package/src/chat/figure-markdown.d.ts +42 -0
- package/src/chat/figure-markdown.ts +174 -0
- package/src/chat/messages.d.ts +1 -1
- package/src/chat/messages.ts +1 -1
- package/src/chat/read-tool-group.ts +6 -4
- package/src/chat/skill-message.ts +6 -0
- package/src/chat/svg-figure.d.ts +33 -0
- package/src/chat/svg-figure.ts +399 -0
- package/src/chat/svg-source.d.ts +49 -0
- package/src/chat/svg-source.ts +188 -0
- package/src/chat/table-chart.d.ts +89 -0
- package/src/chat/table-chart.ts +294 -0
- package/src/chat/tool-execution.d.ts +2 -0
- package/src/chat/tool-execution.ts +80 -6
- package/src/chat/user-message.d.ts +15 -2
- package/src/chat/user-message.ts +55 -3
- package/src/chrome/shared.ts +53 -0
- package/src/chrome/transcript-container.d.ts +6 -0
- package/src/chrome/transcript-container.ts +93 -13
- package/src/components/box.d.ts +1 -0
- package/src/components/box.ts +5 -0
- package/src/components/disclosure.d.ts +1 -0
- package/src/components/disclosure.ts +8 -0
- package/src/components/editor.d.ts +15 -4
- package/src/components/editor.ts +294 -46
- package/src/components/form.d.ts +1 -0
- package/src/components/form.ts +8 -0
- package/src/components/image.d.ts +12 -44
- package/src/components/image.ts +47 -151
- package/src/components/key-value-list.d.ts +1 -0
- package/src/components/key-value-list.ts +4 -0
- package/src/components/layout/row.d.ts +1 -0
- package/src/components/layout/row.ts +7 -0
- package/src/components/layout/stack.d.ts +1 -0
- package/src/components/layout/stack.ts +7 -0
- package/src/components/markdown.d.ts +36 -3
- package/src/components/markdown.ts +433 -318
- package/src/components/scroll-view.d.ts +1 -0
- package/src/components/scroll-view.ts +5 -0
- package/src/components/section.d.ts +1 -0
- package/src/components/section.ts +5 -0
- package/src/components/settings-list.ts +2 -0
- package/src/components/table.d.ts +1 -0
- package/src/components/table.ts +4 -0
- package/src/components/text.d.ts +1 -0
- package/src/components/text.ts +7 -0
- package/src/components/tree-view.d.ts +7 -0
- package/src/components/tree-view.ts +38 -8
- package/src/components/truncated-text.d.ts +1 -0
- package/src/components/truncated-text.ts +5 -0
- package/src/desktop-notify.ts +1 -1
- package/src/fuzzy.d.ts +29 -1
- package/src/fuzzy.ts +115 -30
- package/src/key-hint-format.d.ts +1 -1
- package/src/key-hint-format.ts +1 -1
- package/src/keybinding-matchers.d.ts +3 -0
- package/src/keybinding-matchers.ts +6 -2
- package/src/keybindings.d.ts +1 -1
- package/src/keybindings.ts +1 -1
- package/src/kitty-graphics.d.ts +2 -2
- package/src/kitty-graphics.ts +4 -4
- package/src/loop-watchdog.d.ts +8 -7
- package/src/loop-watchdog.ts +18 -13
- package/src/multiplexers/cmux.d.ts +16 -0
- package/src/multiplexers/cmux.ts +29 -0
- package/src/multiplexers/herdr.d.ts +26 -0
- package/src/multiplexers/herdr.ts +64 -0
- package/src/multiplexers/notify.d.ts +13 -0
- package/src/multiplexers/notify.ts +24 -0
- package/src/multiplexers/orca.d.ts +12 -0
- package/src/multiplexers/orca.ts +16 -0
- package/src/multiplexers/screen.d.ts +9 -0
- package/src/multiplexers/screen.ts +15 -0
- package/src/multiplexers/tmux.d.ts +29 -0
- package/src/multiplexers/tmux.ts +40 -0
- package/src/multiplexers/types.d.ts +100 -0
- package/src/multiplexers/types.ts +110 -0
- package/src/multiplexers/wmux.d.ts +12 -0
- package/src/multiplexers/wmux.ts +16 -0
- package/src/multiplexers/zellij.d.ts +12 -0
- package/src/multiplexers/zellij.ts +24 -0
- package/src/native/backend.d.ts +3 -2
- package/src/native/backend.ts +174 -18
- package/src/native/blobs.d.ts +7 -3
- package/src/native/blobs.ts +47 -12
- package/src/native/encode.d.ts +3 -4
- package/src/native/encode.ts +58 -31
- package/src/native/overlay.d.ts +5 -0
- package/src/native/overlay.ts +13 -0
- package/src/native/reconcile.ts +4 -0
- package/src/overlays/agent-hub-projection.d.ts +6 -5
- package/src/overlays/agent-hub-projection.ts +124 -13
- package/src/overlays/agent-hub-types.d.ts +9 -0
- package/src/overlays/agent-hub-types.ts +6 -0
- package/src/overlays/agent-hub.ts +44 -29
- package/src/overlays/agent-transcript-viewer.ts +91 -23
- package/src/overlays/agents-hub.ts +28 -8
- package/src/overlays/annotation-overlay.ts +293 -90
- package/src/overlays/annotation-types.ts +2 -0
- package/src/overlays/ask-dialog.ts +7 -10
- package/src/overlays/btw-history-panel.ts +12 -1
- package/src/overlays/copy-selector.ts +30 -8
- package/src/overlays/extensions/state-manager.ts +52 -12
- package/src/overlays/history-search.ts +100 -7
- package/src/overlays/hook-selector.ts +12 -5
- package/src/overlays/jobs-panel.ts +28 -46
- package/src/overlays/model-browser.ts +341 -110
- package/src/overlays/model-hub.ts +300 -49
- package/src/overlays/move-overlay.ts +28 -14
- package/src/overlays/oauth-selector.ts +54 -70
- package/src/overlays/plan-review-overlay.ts +194 -70
- package/src/overlays/plugin-settings.ts +3 -3
- package/src/overlays/report-panel.ts +2 -2
- package/src/overlays/rewind-selector.ts +86 -17
- package/src/overlays/session-observer-registry.d.ts +2 -0
- package/src/overlays/session-observer-registry.ts +10 -2
- package/src/overlays/session-selector.ts +64 -26
- package/src/overlays/settings-defs.ts +4 -3
- package/src/overlays/settings-selector.ts +33 -8
- package/src/overlays/snapcompact-shape-preview.ts +2 -3
- package/src/overlays/stats-notice.ts +1 -1
- package/src/overlays/tree-selector.ts +41 -20
- package/src/overlays/usage-dashboard.ts +100 -50
- package/src/overlays/usage-display.ts +10 -4
- package/src/prompt/attachment-chips.ts +15 -11
- package/src/prompt/composer-attachments.ts +6 -4
- package/src/prompt/composer.ts +4 -15
- package/src/prompt/custom-editor.ts +54 -26
- package/src/prompt/image-source.ts +2 -1
- package/src/prompt/interactive-context-helpers.ts +30 -18
- package/src/prompt/macos-spelling.ts +7 -0
- package/src/prompt/magic-keywords.ts +11 -2
- package/src/prompt/model-mention-autocomplete.ts +14 -14
- package/src/prompt/tips.txt +6 -5
- package/src/prompt/welcome.ts +55 -12
- package/src/prompt/word-completion.ts +38 -5
- package/src/render/composite.d.ts +2 -0
- package/src/render/composite.ts +64 -0
- package/src/render/output-pane.d.ts +1 -0
- package/src/render/output-pane.ts +6 -0
- package/src/render/render-utils.d.ts +1 -0
- package/src/render/render-utils.ts +11 -0
- package/src/render/tool-card.d.ts +6 -0
- package/src/render/tool-card.ts +15 -0
- package/src/render/width-aware-text.d.ts +2 -0
- package/src/render/width-aware-text.ts +5 -0
- package/src/setup/scenes/model.ts +2 -1
- package/src/setup/scenes/types.ts +1 -0
- package/src/status-line/component.ts +292 -78
- package/src/status-line/host.ts +4 -1
- package/src/status-line/segments.ts +40 -22
- package/src/status-line/types.ts +15 -3
- package/src/terminal-capabilities.d.ts +27 -35
- package/src/terminal-capabilities.ts +75 -189
- package/src/terminal-multiplexer.d.ts +127 -14
- package/src/terminal-multiplexer.ts +112 -33
- package/src/terminal.d.ts +22 -6
- package/src/terminal.ts +139 -37
- package/src/theme/active-symbols.d.ts +1 -1
- package/src/theme/active-symbols.ts +1 -1
- package/src/theme/schema-validation.ts +1 -0
- package/src/theme/schema.d.ts +11 -2
- package/src/theme/schema.ts +14 -1
- package/src/theme/session-color.ts +1 -7
- package/src/theme/symbols.d.ts +1 -1
- package/src/theme/symbols.ts +6 -1
- package/src/theme/theme-class.d.ts +7 -0
- package/src/theme/theme-class.ts +26 -1
- package/src/theme/theme-schema.json +5 -1
- package/src/theme/theme.d.ts +1 -1
- package/src/theme/theme.ts +8 -4
- package/src/theme/tui-adapters.d.ts +3 -0
- package/src/theme/tui-adapters.ts +68 -0
- package/src/tmux.d.ts +11 -4
- package/src/tmux.ts +47 -11
- package/src/tools/ast-edit.ts +59 -35
- package/src/tools/ast-grep.ts +35 -16
- package/src/tools/bash.ts +92 -42
- package/src/tools/daemon.d.ts +1 -1
- package/src/tools/daemon.ts +1 -1
- package/src/tools/default-renderer.ts +35 -19
- package/src/tools/edit.ts +142 -120
- package/src/tools/eval-format/index.ts +28 -2
- package/src/tools/eval-format/javascript.ts +7 -5
- package/src/tools/eval.d.ts +22 -3
- package/src/tools/eval.ts +364 -208
- package/src/tools/fetch.ts +5 -6
- package/src/tools/glob.d.ts +4 -0
- package/src/tools/glob.ts +33 -65
- package/src/tools/grep.ts +81 -48
- package/src/tools/index.ts +2 -0
- package/src/tools/lsp.ts +81 -59
- package/src/tools/mcp.ts +111 -68
- package/src/tools/native-view.d.ts +47 -0
- package/src/tools/native-view.ts +102 -1
- package/src/tools/output-meta.d.ts +11 -1
- package/src/tools/output-meta.ts +23 -3
- package/src/tools/read.ts +7 -5
- package/src/tools/renderer.d.ts +37 -2
- package/src/tools/renderer.ts +42 -2
- package/src/tools/streaming-output.d.ts +20 -1
- package/src/tools/streaming-output.ts +149 -50
- package/src/tools/task.d.ts +0 -4
- package/src/tools/task.ts +112 -59
- package/src/tools/todo.ts +119 -30
- package/src/tools/wait.ts +34 -24
- package/src/tools/web-search.ts +1 -1
- package/src/tools/write.d.ts +9 -2
- package/src/tools/write.ts +147 -95
- package/src/tools/yield.d.ts +22 -0
- package/src/tools/yield.ts +225 -0
- package/src/tui.d.ts +31 -0
- package/src/tui.ts +309 -160
- package/src/utils.d.ts +6 -0
- package/src/utils.ts +19 -0
- package/src/vim.d.ts +19 -5
- package/src/vim.ts +333 -18
- package/src/windows-input-mode.ts +8 -0
|
@@ -179,9 +179,13 @@ function sessionPreview(session: SessionSelectorEntry, forkedFrom: string | unde
|
|
|
179
179
|
]);
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
+
/** Absolute dates of week-old sessions; `toLocaleDateString` goes through Intl on every call. */
|
|
183
|
+
const localeDateCache = new WeakMap<Date, { time: number; text: string }>();
|
|
184
|
+
|
|
182
185
|
/** Relative age of a session's last modification (`"3 hours ago"`), falling back to the date after a week. */
|
|
183
186
|
function formatSessionDate(date: Date): string {
|
|
184
|
-
const
|
|
187
|
+
const time = date.getTime();
|
|
188
|
+
const diffMs = Date.now() - time;
|
|
185
189
|
const diffMins = Math.floor(diffMs / 60000);
|
|
186
190
|
const diffHours = Math.floor(diffMs / 3600000);
|
|
187
191
|
const diffDays = Math.floor(diffMs / 86400000);
|
|
@@ -192,7 +196,11 @@ function formatSessionDate(date: Date): string {
|
|
|
192
196
|
if (diffDays === 1) return "1 day ago";
|
|
193
197
|
if (diffDays < 7) return `${diffDays} days ago`;
|
|
194
198
|
|
|
195
|
-
|
|
199
|
+
const cached = localeDateCache.get(date);
|
|
200
|
+
if (cached?.time === time) return cached.text;
|
|
201
|
+
const text = date.toLocaleDateString();
|
|
202
|
+
localeDateCache.set(date, { time, text });
|
|
203
|
+
return text;
|
|
196
204
|
}
|
|
197
205
|
|
|
198
206
|
/** A cached native session item and the inputs it was built from. */
|
|
@@ -517,12 +525,12 @@ class SessionList<T extends SessionSelectorEntry> implements Component {
|
|
|
517
525
|
readonly #getCurrentSessionPath: () => string | undefined;
|
|
518
526
|
readonly #historyMatcher?: SessionHistoryMatcher;
|
|
519
527
|
#historyMergeTimer: NodeJS.Timeout | undefined;
|
|
520
|
-
/** Re-render hook for async list updates (fuzzy scan
|
|
528
|
+
/** Re-render hook for async list updates (fuzzy scan completion, history merge). */
|
|
521
529
|
onRequestRender?: () => void;
|
|
522
530
|
|
|
523
531
|
// ── Incremental search state ──────────────────────────────────────────
|
|
524
532
|
// The menu's visible list is always composed from these three inputs (see
|
|
525
|
-
// #composeFiltered), so late-
|
|
533
|
+
// #composeFiltered), so a late-finishing fuzzy scan and the debounced
|
|
526
534
|
// history merge can land in any order without clobbering each other.
|
|
527
535
|
/** Recency-ranked sessions whose text contains every query token verbatim. */
|
|
528
536
|
#literalRanked: RankedSessionMatch<T>[] = [];
|
|
@@ -535,8 +543,8 @@ class SessionList<T extends SessionSelectorEntry> implements Component {
|
|
|
535
543
|
#scanTimer: NodeJS.Timeout | undefined;
|
|
536
544
|
/**
|
|
537
545
|
* True once the user moved the selection for the current query; blocks the
|
|
538
|
-
* history merge from reordering the list under their cursor. (
|
|
539
|
-
* only
|
|
546
|
+
* history merge from reordering the list under their cursor. (The fuzzy scan
|
|
547
|
+
* only appends below the literal group, which never shifts existing rows.)
|
|
540
548
|
*/
|
|
541
549
|
#selectionMoved = false;
|
|
542
550
|
/** True after a nonempty query; empty refilter restores current only then. */
|
|
@@ -545,6 +553,8 @@ class SessionList<T extends SessionSelectorEntry> implements Component {
|
|
|
545
553
|
#lastFilterQuery = "";
|
|
546
554
|
/** Bumped whenever the visible session set may have changed (native memo key). */
|
|
547
555
|
#itemsVersion = 0;
|
|
556
|
+
/** Per-row line heights of the visible list for the ANSI window, reused until the set changes. */
|
|
557
|
+
#rowHeights: { items: readonly T[]; length: number; version: number; heights: readonly number[] } | undefined;
|
|
548
558
|
#itemsNative:
|
|
549
559
|
| { version: number; showCwd: boolean; currentPath: string | undefined; items: NativeNode[] }
|
|
550
560
|
| undefined;
|
|
@@ -677,38 +687,48 @@ class SessionList<T extends SessionSelectorEntry> implements Component {
|
|
|
677
687
|
// Fuzzy pass: building a fuzzy index per session is too expensive to run
|
|
678
688
|
// across a huge listing inside one keystroke, so scan a bounded slice now
|
|
679
689
|
// and spill the remainder into async chunks.
|
|
680
|
-
|
|
690
|
+
const inlineEnd = Math.min(rest.length, FUZZY_SCAN_INLINE_COUNT);
|
|
691
|
+
this.#scoreFuzzy(tokens, rest, 0, inlineEnd);
|
|
681
692
|
this.#composeFiltered();
|
|
693
|
+
if (inlineEnd < rest.length) {
|
|
694
|
+
this.#scheduleFuzzyScan(this.#scanGeneration, tokens, rest, inlineEnd, this.#fuzzyRanked.length);
|
|
695
|
+
}
|
|
682
696
|
// New query rebuilds ranking from scratch. Same-query refilter (delete)
|
|
683
|
-
// and async compose (fuzzy
|
|
697
|
+
// and async compose (fuzzy scan / history merge) only clamp so an
|
|
684
698
|
// arrow selection survives. A live-session index > 0 would otherwise
|
|
685
699
|
// land on a lower-ranked match after the first keystroke.
|
|
686
700
|
if (queryChanged) this.#menu.setSelectedIndex(0);
|
|
687
701
|
this.#scheduleHistoryMerge(query);
|
|
688
702
|
}
|
|
689
703
|
|
|
690
|
-
/**
|
|
691
|
-
|
|
692
|
-
* unfiltered list), then schedule the remainder on a macrotask so pending
|
|
693
|
-
* input events run first. Chunks that added matches recompose the visible
|
|
694
|
-
* list and request a render; a stale generation aborts silently.
|
|
695
|
-
*/
|
|
696
|
-
#scanFuzzySlice(generation: number, tokens: string[], rest: number[], start: number, budget: number): void {
|
|
704
|
+
/** Fuzzy-score `rest[start..end)` (indexes into the unfiltered list) into {@link #fuzzyRanked}. */
|
|
705
|
+
#scoreFuzzy(tokens: string[], rest: number[], start: number, end: number): void {
|
|
697
706
|
const all = this.#allSessions;
|
|
698
|
-
const end = Math.min(rest.length, start + budget);
|
|
699
707
|
for (let i = start; i < end; i++) {
|
|
700
708
|
const index = rest[i]!;
|
|
701
709
|
const session = all[index]!;
|
|
702
710
|
const match = scoreFuzzySession(session, index, tokens, new FuzzyText(sessionTextLower(session)));
|
|
703
711
|
if (match) this.#fuzzyRanked.push(match);
|
|
704
712
|
}
|
|
705
|
-
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Score `rest[start..]` in {@link FUZZY_SCAN_CHUNK_COUNT}-session macrotask
|
|
717
|
+
* chunks so pending input runs between them, then recompose the visible
|
|
718
|
+
* list once if the scan matched more than the `shown` fuzzy hits already
|
|
719
|
+
* composed. Publishing per chunk reorders the list dozens of times per
|
|
720
|
+
* keystroke on a large listing, and the native picker replays its
|
|
721
|
+
* row-arrival animation on every reorder. A stale generation aborts silently.
|
|
722
|
+
*/
|
|
723
|
+
#scheduleFuzzyScan(generation: number, tokens: string[], rest: number[], start: number, shown: number): void {
|
|
706
724
|
this.#scanTimer = setTimeout(() => {
|
|
707
725
|
this.#scanTimer = undefined;
|
|
708
726
|
if (generation !== this.#scanGeneration) return;
|
|
709
|
-
const
|
|
710
|
-
this.#
|
|
711
|
-
if (
|
|
727
|
+
const end = Math.min(rest.length, start + FUZZY_SCAN_CHUNK_COUNT);
|
|
728
|
+
this.#scoreFuzzy(tokens, rest, start, end);
|
|
729
|
+
if (end < rest.length) {
|
|
730
|
+
this.#scheduleFuzzyScan(generation, tokens, rest, end, shown);
|
|
731
|
+
} else if (this.#fuzzyRanked.length > shown) {
|
|
712
732
|
this.#composeFiltered();
|
|
713
733
|
this.onRequestRender?.();
|
|
714
734
|
}
|
|
@@ -730,7 +750,7 @@ class SessionList<T extends SessionSelectorEntry> implements Component {
|
|
|
730
750
|
tokenizeSessionQuery(this.#searchInput.getValue()),
|
|
731
751
|
this.#literalRanked,
|
|
732
752
|
);
|
|
733
|
-
//
|
|
753
|
+
// The fuzzy scan and the history merge only clamp the cursor so an arrow
|
|
734
754
|
// selection survives recomposition; the query path resets explicitly.
|
|
735
755
|
const keepIndex = Math.min(this.#menu.selectedIndex, Math.max(0, composed.length - 1));
|
|
736
756
|
this.#menu.setItems(composed, keepIndex >= 0 ? composed[keepIndex]?.path : undefined);
|
|
@@ -1130,14 +1150,13 @@ class SessionList<T extends SessionSelectorEntry> implements Component {
|
|
|
1130
1150
|
// worst-case count-based window would leave (then padded by
|
|
1131
1151
|
// fill-height).
|
|
1132
1152
|
const filtered = this.#menu.visibleItems;
|
|
1133
|
-
const itemHeight = (session: T): number => (session.title ? 4 : 3);
|
|
1134
1153
|
const budget = this.#lineBudget();
|
|
1135
1154
|
const {
|
|
1136
1155
|
startIndex,
|
|
1137
1156
|
endIndex,
|
|
1138
1157
|
rowOffset: offsetRows,
|
|
1139
1158
|
totalRows: rawTotalRows,
|
|
1140
|
-
} = getMenuWindow(filtered
|
|
1159
|
+
} = getMenuWindow(this.#visibleRowHeights(filtered), this.#menu.selectedIndex, budget);
|
|
1141
1160
|
|
|
1142
1161
|
// Each session block is built into sessionLines, then wrapped by ScrollView
|
|
1143
1162
|
// so the right-edge scrollbar is proportional at the physical-line level.
|
|
@@ -1232,6 +1251,17 @@ class SessionList<T extends SessionSelectorEntry> implements Component {
|
|
|
1232
1251
|
return lines;
|
|
1233
1252
|
}
|
|
1234
1253
|
|
|
1254
|
+
/** Line height per visible session (3, or 4 when a title adds a preview line), memoized per visible set. */
|
|
1255
|
+
#visibleRowHeights(items: readonly T[]): readonly number[] {
|
|
1256
|
+
const cached = this.#rowHeights;
|
|
1257
|
+
if (cached?.items === items && cached.length === items.length && cached.version === this.#itemsVersion) {
|
|
1258
|
+
return cached.heights;
|
|
1259
|
+
}
|
|
1260
|
+
const heights = items.map(session => (session.title ? 4 : 3));
|
|
1261
|
+
this.#rowHeights = { items, length: items.length, version: this.#itemsVersion, heights };
|
|
1262
|
+
return heights;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1235
1265
|
handleInput(keyData: string): void {
|
|
1236
1266
|
// Delete key — or Backspace on an empty search query — request delete
|
|
1237
1267
|
// confirmation from the parent. macOS laptops have no dedicated Forward
|
|
@@ -1310,7 +1340,11 @@ export interface SessionSelectorOptions<T extends SessionSelectorEntry = Session
|
|
|
1310
1340
|
title?: string;
|
|
1311
1341
|
/** Fixed scope label, or false to omit the scope suffix. */
|
|
1312
1342
|
scopeLabel?: string | false;
|
|
1313
|
-
/**
|
|
1343
|
+
/**
|
|
1344
|
+
* Show each session's working directory in the list. Defaults to on when
|
|
1345
|
+
* the session files live in more than one directory (e.g. the folder scope
|
|
1346
|
+
* merging a repository's worktrees).
|
|
1347
|
+
*/
|
|
1314
1348
|
showCwd?: boolean;
|
|
1315
1349
|
/**
|
|
1316
1350
|
* Reads the live terminal height so the visible window fits the viewport.
|
|
@@ -1354,6 +1388,7 @@ export class SessionSelectorComponent<T extends SessionSelectorEntry = SessionSe
|
|
|
1354
1388
|
#onRequestRender?: () => void;
|
|
1355
1389
|
readonly #loadAllSessions?: () => Promise<T[]>;
|
|
1356
1390
|
#folderSessions: T[];
|
|
1391
|
+
readonly #folderShowCwd: boolean;
|
|
1357
1392
|
#globalSessions: T[] | null = null;
|
|
1358
1393
|
#scope: "folder" | "all" = "folder";
|
|
1359
1394
|
#toggling = false;
|
|
@@ -1420,6 +1455,9 @@ export class SessionSelectorComponent<T extends SessionSelectorEntry = SessionSe
|
|
|
1420
1455
|
this.#onDelete = options.onDelete;
|
|
1421
1456
|
this.#loadAllSessions = options.loadAllSessions;
|
|
1422
1457
|
this.#folderSessions = sessions;
|
|
1458
|
+
// Storage directory, not recorded cwd: one folder's sessions may record
|
|
1459
|
+
// symlink aliases of the same path, which must not turn the column on.
|
|
1460
|
+
this.#folderShowCwd = options.showCwd ?? new Set(sessions.map(session => path.dirname(session.path))).size > 1;
|
|
1423
1461
|
this.#globalSessions = options.allSessions ?? null;
|
|
1424
1462
|
this.#getTerminalRows = options.getTerminalRows ?? (() => 24);
|
|
1425
1463
|
this.#fillHeight = options.fillHeight ?? false;
|
|
@@ -1437,7 +1475,7 @@ export class SessionSelectorComponent<T extends SessionSelectorEntry = SessionSe
|
|
|
1437
1475
|
// projects' history (issue #3099).
|
|
1438
1476
|
this.#sessionList = new SessionList(
|
|
1439
1477
|
sessions,
|
|
1440
|
-
|
|
1478
|
+
this.#folderShowCwd,
|
|
1441
1479
|
options.historyMatcher,
|
|
1442
1480
|
options.getTerminalRows,
|
|
1443
1481
|
options.pinnedIds,
|
|
@@ -1510,7 +1548,7 @@ export class SessionSelectorComponent<T extends SessionSelectorEntry = SessionSe
|
|
|
1510
1548
|
this.#sessionList.setSessions(global, true);
|
|
1511
1549
|
} else {
|
|
1512
1550
|
this.#scope = "folder";
|
|
1513
|
-
this.#sessionList.setSessions(this.#folderSessions,
|
|
1551
|
+
this.#sessionList.setSessions(this.#folderSessions, this.#folderShowCwd);
|
|
1514
1552
|
}
|
|
1515
1553
|
this.title = this.#headerLabel();
|
|
1516
1554
|
this.#onRequestRender?.();
|
|
@@ -180,6 +180,7 @@ export type AnyUiMetadata = UiBase & {
|
|
|
180
180
|
options?: ReadonlyArray<SubmenuOption> | "runtime";
|
|
181
181
|
secret?: boolean;
|
|
182
182
|
ordered?: boolean;
|
|
183
|
+
editor?: "json";
|
|
183
184
|
};
|
|
184
185
|
|
|
185
186
|
/** Structural schema entries supplied by the application host. */
|
|
@@ -330,9 +331,9 @@ function entryToSettingDef(entry: SettingsDisplayEntry): SettingDef | null {
|
|
|
330
331
|
}
|
|
331
332
|
|
|
332
333
|
if (schemaType === "array") {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
if (
|
|
334
|
+
if (ui.editor === "json") return { ...base, type: "text", secret: entry.credential === true };
|
|
335
|
+
if (!options) return null;
|
|
336
|
+
if (options === "runtime") return null;
|
|
336
337
|
return { ...base, type: "multiselect", options, ordered: ui.ordered === true };
|
|
337
338
|
}
|
|
338
339
|
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { ThinkingLevel } from "@linxiraos/pi-agent-core";
|
|
2
2
|
import type { Effort } from "@linxiraos/pi-ai";
|
|
3
|
+
import { CLI_BIN_NAME } from "@linxiraos/pi-utils";
|
|
3
4
|
import {
|
|
4
5
|
type Component,
|
|
5
6
|
Container,
|
|
6
7
|
extractPrintableText,
|
|
7
|
-
|
|
8
|
+
FuzzyCorpus,
|
|
8
9
|
getKeybindings,
|
|
9
10
|
getSettingItemFilterText,
|
|
10
11
|
type ImageBudget,
|
|
@@ -630,6 +631,12 @@ export class SettingsSelectorComponent implements Component {
|
|
|
630
631
|
#searchMatchCount = 0;
|
|
631
632
|
/** First matching item id per tab id, for Tab-key jumps while searching. */
|
|
632
633
|
#searchFirstMatch = new Map<string, string>();
|
|
634
|
+
/**
|
|
635
|
+
* Per-tab search candidates prepared once per search session. Dropped on
|
|
636
|
+
* search start/end and whenever a value or condition may have changed
|
|
637
|
+
* (`#refreshItems`, `#onSearchSettingChange`); rebuilt lazily by `#setSearchQuery`.
|
|
638
|
+
*/
|
|
639
|
+
#searchCorpora: { tab: SettingTab; corpus: FuzzyCorpus<SettingItem> }[] | null = null;
|
|
633
640
|
#textInputActive = false;
|
|
634
641
|
#hasSectionJump = false;
|
|
635
642
|
// Frame geometry from the last render, for mouse hit-testing (the
|
|
@@ -872,7 +879,7 @@ export class SettingsSelectorComponent implements Component {
|
|
|
872
879
|
}
|
|
873
880
|
|
|
874
881
|
const props: TspPrefsProps = {
|
|
875
|
-
title:
|
|
882
|
+
title: `${CLI_BIN_NAME} settings`,
|
|
876
883
|
pages,
|
|
877
884
|
page: searching ? this.#preSearchTabId : this.#currentTabId,
|
|
878
885
|
lead: searching ? undefined : (pluginPage?.lead ?? (tab ? TAB_LEADS[tab] : undefined)),
|
|
@@ -1111,8 +1118,10 @@ export class SettingsSelectorComponent implements Component {
|
|
|
1111
1118
|
|
|
1112
1119
|
/** Rebuilds the visible items after a change made outside a list's own `onChange` (changed flags, conditions). */
|
|
1113
1120
|
#refreshItems(): void {
|
|
1114
|
-
if (this.#searchList)
|
|
1115
|
-
|
|
1121
|
+
if (this.#searchList) {
|
|
1122
|
+
this.#searchCorpora = null;
|
|
1123
|
+
this.#setSearchQuery(this.#searchQuery);
|
|
1124
|
+
} else if (this.#currentTabId !== "plugins")
|
|
1116
1125
|
this.#refreshCurrentTabItems(getSettingsForTab(this.#context.settings.entries, this.#currentTabId));
|
|
1117
1126
|
}
|
|
1118
1127
|
|
|
@@ -1281,6 +1290,7 @@ export class SettingsSelectorComponent implements Component {
|
|
|
1281
1290
|
/** Swap the tab content for the global search result list. */
|
|
1282
1291
|
#startSearch(initialQuery: string): void {
|
|
1283
1292
|
this.#preSearchTabId = this.#currentTabId;
|
|
1293
|
+
this.#searchCorpora = null;
|
|
1284
1294
|
this.#searchInput = new Input();
|
|
1285
1295
|
this.#searchInput.prompt = "";
|
|
1286
1296
|
this.#searchInput.setValue(initialQuery);
|
|
@@ -1324,13 +1334,17 @@ export class SettingsSelectorComponent implements Component {
|
|
|
1324
1334
|
const tabResults: { tab: SettingTab; matched: SettingItem[]; bestScore: number; order: number }[] = [];
|
|
1325
1335
|
this.#searchFirstMatch.clear();
|
|
1326
1336
|
let total = 0;
|
|
1327
|
-
|
|
1337
|
+
this.#searchCorpora ??= SETTING_TABS.map(tab => {
|
|
1328
1338
|
const candidates: SettingItem[] = [];
|
|
1329
1339
|
for (const def of getSettingsForTab(this.#context.settings.entries, tab)) {
|
|
1330
1340
|
const item = this.#defToItem(def);
|
|
1331
1341
|
if (item) candidates.push(item);
|
|
1332
1342
|
}
|
|
1333
|
-
|
|
1343
|
+
return { tab, corpus: new FuzzyCorpus(candidates, getSettingItemFilterText) };
|
|
1344
|
+
});
|
|
1345
|
+
for (let order = 0; order < this.#searchCorpora.length; order++) {
|
|
1346
|
+
const { tab, corpus } = this.#searchCorpora[order]!;
|
|
1347
|
+
const ranked = corpus.rank(query);
|
|
1334
1348
|
const matched = ranked.map(result => result.item);
|
|
1335
1349
|
counts.set(tab, matched.length);
|
|
1336
1350
|
if (matched.length === 0) continue;
|
|
@@ -1339,7 +1353,7 @@ export class SettingsSelectorComponent implements Component {
|
|
|
1339
1353
|
tab,
|
|
1340
1354
|
matched,
|
|
1341
1355
|
bestScore: ranked[0]?.score ?? 0,
|
|
1342
|
-
order
|
|
1356
|
+
order,
|
|
1343
1357
|
});
|
|
1344
1358
|
}
|
|
1345
1359
|
|
|
@@ -1378,6 +1392,7 @@ export class SettingsSelectorComponent implements Component {
|
|
|
1378
1392
|
const targetTab: SettingTab | "plugins" = selectedDef?.tab ?? this.#preSearchTabId;
|
|
1379
1393
|
|
|
1380
1394
|
this.#searchQuery = "";
|
|
1395
|
+
this.#searchCorpora = null;
|
|
1381
1396
|
this.#searchFirstMatch.clear();
|
|
1382
1397
|
this.#searchMatchCount = 0;
|
|
1383
1398
|
this.#tabs = getSettingsTabs();
|
|
@@ -1441,6 +1456,7 @@ export class SettingsSelectorComponent implements Component {
|
|
|
1441
1456
|
}
|
|
1442
1457
|
// Values feed the searchable text and condition gates may have flipped:
|
|
1443
1458
|
// recompute results in place (selection is preserved by item id).
|
|
1459
|
+
this.#searchCorpora = null;
|
|
1444
1460
|
this.#setSearchQuery(this.#searchQuery);
|
|
1445
1461
|
}
|
|
1446
1462
|
|
|
@@ -1648,7 +1664,7 @@ export class SettingsSelectorComponent implements Component {
|
|
|
1648
1664
|
}
|
|
1649
1665
|
|
|
1650
1666
|
/**
|
|
1651
|
-
* Create a text input submenu for a
|
|
1667
|
+
* Create a text input submenu for a string or structured setting.
|
|
1652
1668
|
*/
|
|
1653
1669
|
#createTextInput(
|
|
1654
1670
|
def: SettingDef & { type: "text" },
|
|
@@ -1751,6 +1767,15 @@ export class SettingsSelectorComponent implements Component {
|
|
|
1751
1767
|
this.#context.settings.set(path, -1);
|
|
1752
1768
|
} else if (path === "compaction.thresholdTokens" && value === "default") {
|
|
1753
1769
|
this.#context.settings.set(path, -1);
|
|
1770
|
+
} else if (schemaType === "array") {
|
|
1771
|
+
let parsed: unknown;
|
|
1772
|
+
try {
|
|
1773
|
+
parsed = JSON.parse(value);
|
|
1774
|
+
} catch {
|
|
1775
|
+
throw new Error(`Invalid array JSON for ${path}`);
|
|
1776
|
+
}
|
|
1777
|
+
if (!Array.isArray(parsed)) throw new Error(`Invalid array JSON for ${path}`);
|
|
1778
|
+
this.#context.settings.set(path, parsed);
|
|
1754
1779
|
} else if (schemaType === "record") {
|
|
1755
1780
|
let parsed: unknown;
|
|
1756
1781
|
try {
|
|
@@ -31,7 +31,7 @@ import { theme } from "../theme/theme";
|
|
|
31
31
|
import sampleDoc from "./snapcompact-shape-preview-doc.md" with { type: "text" };
|
|
32
32
|
import type { DescribeContext, NativeNode } from "../native/node";
|
|
33
33
|
import { col, node, span, text } from "../native/describe";
|
|
34
|
-
import {
|
|
34
|
+
import { nativeImageNode } from "../native/blobs";
|
|
35
35
|
|
|
36
36
|
/** Mini-frame edge in px — a small page from the real rasterizer ≈ a zoomed crop. */
|
|
37
37
|
const SRC_FRAME_PX = 128;
|
|
@@ -100,8 +100,7 @@ export class SnapcompactShapePreview implements Component {
|
|
|
100
100
|
else if (entry.state === "rendering") sample = node("spinner", { label: [span("rendering sample…", "dim")] });
|
|
101
101
|
else if (entry.state === "failed") sample = text([span("(sample render failed)", "dim")]);
|
|
102
102
|
else {
|
|
103
|
-
sample =
|
|
104
|
-
blob: registerNativeBlob(entry.bytes, "image/png"),
|
|
103
|
+
sample = nativeImageNode(entry.bytes, "image/png", {
|
|
105
104
|
alt: `snapcompact ${label} sample`,
|
|
106
105
|
w: entry.edgePx,
|
|
107
106
|
h: entry.edgePx,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `/stats` result:
|
|
2
|
+
* `/stats` result: zetacode serves the stats dashboard in the browser. ANSI keeps
|
|
3
3
|
* the dim status line; natively it is an inline notice with an
|
|
4
4
|
* `Open dashboard ↗` button that opens the URL terminal-side.
|
|
5
5
|
*/
|
|
@@ -2,7 +2,8 @@ import { ThinkingLevel } from "@linxiraos/pi-agent-core";
|
|
|
2
2
|
import {
|
|
3
3
|
type Component,
|
|
4
4
|
Container,
|
|
5
|
-
|
|
5
|
+
FuzzyQuery,
|
|
6
|
+
FuzzyText,
|
|
6
7
|
Input,
|
|
7
8
|
matchesKey,
|
|
8
9
|
Spacer,
|
|
@@ -53,7 +54,7 @@ import { shortenPath } from "../render/render-utils";
|
|
|
53
54
|
import { canonicalizeMessage } from "../chat/thinking-display";
|
|
54
55
|
import { resolveAssistantErrorPresentation } from "../chat/transcript-render-helpers";
|
|
55
56
|
import { OverlayPanel, PanelDivider } from "../chrome/overlay-box";
|
|
56
|
-
import { TreeView, type TreeRow } from "../components/tree-view";
|
|
57
|
+
import { TreeView, type TreeAncestor, type TreeRow } from "../components/tree-view";
|
|
57
58
|
import { formatKeyHint, formatKeyHints } from "../app-keybindings";
|
|
58
59
|
import { boundKeys, editorKeys, interruptKey } from "../chrome/keybinding-hints";
|
|
59
60
|
import type { TspPickerItem, TspPickerProps, TspSpan, TspText } from "@linxiraos/pi-wire";
|
|
@@ -210,6 +211,8 @@ class TreeList implements Component {
|
|
|
210
211
|
#multipleRoots = false;
|
|
211
212
|
#activePathIds: Set<string> = new Set();
|
|
212
213
|
#containsActive: Map<TreeSelectorNode, boolean> = new Map();
|
|
214
|
+
/** Prepared search text per node; rebuilt when the node's label changes. */
|
|
215
|
+
#searchTextCache = new WeakMap<TreeSelectorNode, { label: string | undefined; text: FuzzyText }>();
|
|
213
216
|
|
|
214
217
|
onSelect?: (entryId: string, options: { summarize: boolean }) => void;
|
|
215
218
|
onCancel?: () => void;
|
|
@@ -253,6 +256,7 @@ class TreeList implements Component {
|
|
|
253
256
|
getChildren: node => this.#orderedChildren(node),
|
|
254
257
|
getChildDepth: (_node, row, children) =>
|
|
255
258
|
children.length > 1 || (this.#multipleRoots && row.parentKey === undefined) ? row.depth + 1 : row.depth,
|
|
259
|
+
compactSameDepthAncestors: true,
|
|
256
260
|
theme,
|
|
257
261
|
filter: this.#buildFilter(),
|
|
258
262
|
maxRows: maxVisibleLines,
|
|
@@ -373,7 +377,11 @@ class TreeList implements Component {
|
|
|
373
377
|
#buildFilter(): (node: TreeSelectorNode, row: TreeRow<TreeSelectorNode, string>) => boolean {
|
|
374
378
|
const filterMode = this.#filterMode;
|
|
375
379
|
const leafId = this.currentLeafId;
|
|
376
|
-
const searchTokens = this.getSearchQuery()
|
|
380
|
+
const searchTokens = this.getSearchQuery()
|
|
381
|
+
.toLowerCase()
|
|
382
|
+
.split(/\s+/)
|
|
383
|
+
.filter(Boolean)
|
|
384
|
+
.map(token => new FuzzyQuery(token));
|
|
377
385
|
return node => {
|
|
378
386
|
const entry = node.entry;
|
|
379
387
|
const isCurrentLeaf = entry.id === leafId;
|
|
@@ -435,14 +443,23 @@ class TreeList implements Component {
|
|
|
435
443
|
|
|
436
444
|
// Apply fuzzy search filter
|
|
437
445
|
if (searchTokens.length > 0) {
|
|
438
|
-
const nodeText = this.#
|
|
439
|
-
return searchTokens.every(token =>
|
|
446
|
+
const nodeText = this.#getFuzzyText(node);
|
|
447
|
+
return searchTokens.every(token => token.match(nodeText).matches);
|
|
440
448
|
}
|
|
441
449
|
|
|
442
450
|
return true;
|
|
443
451
|
};
|
|
444
452
|
}
|
|
445
453
|
|
|
454
|
+
/** Prepared fuzzy text for a node's searchable content, built once per label. */
|
|
455
|
+
#getFuzzyText(node: TreeSelectorNode): FuzzyText {
|
|
456
|
+
const cached = this.#searchTextCache.get(node);
|
|
457
|
+
if (cached !== undefined && cached.label === node.label) return cached.text;
|
|
458
|
+
const text = new FuzzyText(this.#getSearchableText(node));
|
|
459
|
+
this.#searchTextCache.set(node, { label: node.label, text });
|
|
460
|
+
return text;
|
|
461
|
+
}
|
|
462
|
+
|
|
446
463
|
/** Get searchable text content from a node */
|
|
447
464
|
#getSearchableText(node: TreeSelectorNode): string {
|
|
448
465
|
const entry = node.entry;
|
|
@@ -877,23 +894,26 @@ class TreeList implements Component {
|
|
|
877
894
|
// Linear rows reuse their branch head's depth. Existing sibling
|
|
878
895
|
// gutters remain visible; terminal gutters remain terminated.
|
|
879
896
|
|
|
897
|
+
// Branch points that can draw a gutter, indexed by rendered level: the
|
|
898
|
+
// ancestor list is as deep as the conversation, so it is scanned once
|
|
899
|
+
// per row rather than once per prefix cell.
|
|
900
|
+
// oxlint-disable-next-line unicorn/no-new-array -- length preallocation
|
|
901
|
+
const gutterByLevel: (TreeAncestor<string> | undefined)[] = new Array(renderedIndent);
|
|
902
|
+
for (const ancestor of row.ancestors) {
|
|
903
|
+
if (ancestor.siblingCount <= 1) continue;
|
|
904
|
+
if (this.#multipleRoots && this.#rootIds.has(ancestor.key)) continue;
|
|
905
|
+
const level = ancestor.depth - 1 - scrollOffset;
|
|
906
|
+
if (level >= 0 && level < renderedIndent && gutterByLevel[level] === undefined) {
|
|
907
|
+
gutterByLevel[level] = ancestor;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
880
910
|
// Build prefix char by char, placing gutters and connector at their positions
|
|
881
911
|
const totalChars = renderedIndent * 3;
|
|
882
912
|
const prefixChars: string[] = [];
|
|
883
913
|
for (let i = 0; i < totalChars; i++) {
|
|
884
914
|
const level = Math.floor(i / 3);
|
|
885
|
-
const originalDepth = level + scrollOffset;
|
|
886
915
|
const posInLevel = i % 3;
|
|
887
|
-
|
|
888
|
-
// An ancestor branch point draws its gutter at its own depth minus
|
|
889
|
-
// one — the level its connector occupied. Session roots never emit
|
|
890
|
-
// gutters; their connectors are suppressed.
|
|
891
|
-
const gutterAncestor = row.ancestors.find(
|
|
892
|
-
ancestor =>
|
|
893
|
-
ancestor.depth - 1 === originalDepth &&
|
|
894
|
-
ancestor.siblingCount > 1 &&
|
|
895
|
-
!(this.#multipleRoots && this.#rootIds.has(ancestor.key)),
|
|
896
|
-
);
|
|
916
|
+
const gutterAncestor = gutterByLevel[level];
|
|
897
917
|
if (gutterAncestor) {
|
|
898
918
|
// Gutters follow standard tree semantics: `│` only while more
|
|
899
919
|
// siblings continue below, space below a `└─`.
|
|
@@ -1036,12 +1056,12 @@ class TreeList implements Component {
|
|
|
1036
1056
|
}
|
|
1037
1057
|
|
|
1038
1058
|
#extractContent(content: unknown): string {
|
|
1039
|
-
return this.#joinTextContent(content
|
|
1059
|
+
return this.#joinTextContent(content, SEARCH_TEXT_LIMIT);
|
|
1040
1060
|
}
|
|
1041
1061
|
|
|
1042
|
-
/** Concatenate
|
|
1043
|
-
#joinTextContent(content: unknown): string {
|
|
1044
|
-
if (typeof content === "string") return content;
|
|
1062
|
+
/** Concatenate text blocks, stopping as soon as `limit` characters exist. */
|
|
1063
|
+
#joinTextContent(content: unknown, limit = Number.POSITIVE_INFINITY): string {
|
|
1064
|
+
if (typeof content === "string") return content.length > limit ? content.slice(0, limit) : content;
|
|
1045
1065
|
if (Array.isArray(content)) {
|
|
1046
1066
|
let result = "";
|
|
1047
1067
|
for (const c of content) {
|
|
@@ -1054,6 +1074,7 @@ class TreeList implements Component {
|
|
|
1054
1074
|
typeof c.text === "string"
|
|
1055
1075
|
) {
|
|
1056
1076
|
result += c.text;
|
|
1077
|
+
if (result.length >= limit) return result.slice(0, limit);
|
|
1057
1078
|
}
|
|
1058
1079
|
}
|
|
1059
1080
|
return result;
|