@timurproko/a1 0.2.4 → 0.2.5-dev.698
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/dist/app/session-shell/prompt-suggestion-controller.d.ts +2 -0
- package/dist/app/session-shell/prompt-suggestion-controller.js +10 -0
- package/dist/app/session-shell/session-shell-root.d.ts +1 -0
- package/dist/app/session-shell/session-shell-root.js +19 -6
- package/dist/app/session-shell/session-shell.js +77 -19
- package/dist/app/session-shell/share-operation.d.ts +3 -0
- package/dist/app/session-shell/share-operation.js +11 -0
- package/dist/composition/owned-ui.js +1 -0
- package/dist/composition/session-info-reference.d.ts +4 -0
- package/dist/composition/session-info-reference.js +20 -0
- package/dist/composition/settings-route-host.d.ts +2 -5
- package/dist/composition/settings-route-host.js +8 -12
- package/dist/contracts/presentation/index.d.ts +5 -0
- package/dist/contracts/presentation/index.js +2 -0
- package/dist/features/owned-ui/project-trust-dialog.js +7 -5
- package/dist/features/owned-ui/reference-screen-app.d.ts +4 -2
- package/dist/features/owned-ui/reference-screen-app.js +17 -7
- package/dist/features/owned-ui/settings-app.js +10 -5
- package/dist/foundation/release/progress-palette.generated.d.ts +2 -2
- package/dist/foundation/release/progress-palette.generated.js +2 -2
- package/dist/foundation/startup/startup-descriptor.js +2 -2
- package/dist/integrations/pi/components/index.d.ts +3 -2
- package/dist/integrations/pi/components/index.js +2 -1
- package/dist/integrations/pi/components/modal-frame.d.ts +11 -1
- package/dist/integrations/pi/components/modal-frame.js +29 -1
- package/dist/integrations/pi/components/models-dialog.js +17 -17
- package/dist/integrations/pi/components/resources/builtin-themes.d.ts +2 -2
- package/dist/integrations/pi/components/resources/builtin-themes.js +2 -2
- package/dist/integrations/pi/components/share-operation-dialog.d.ts +6 -0
- package/dist/integrations/pi/components/share-operation-dialog.js +40 -0
- package/dist/integrations/pi/components/shell-editor-autocomplete.js +15 -6
- package/dist/integrations/pi/components/shell-extension-ui.d.ts +3 -0
- package/dist/integrations/pi/components/shell-extension-ui.js +8 -4
- package/dist/integrations/pi/components/shell-presenters-info.d.ts +2 -0
- package/dist/integrations/pi/components/shell-presenters-info.js +1 -1
- package/dist/integrations/pi/components/shell-selectors-dialogs.d.ts +0 -4
- package/dist/integrations/pi/components/shell-selectors-dialogs.js +56 -34
- package/dist/integrations/pi/components/shell-session-info-reference.d.ts +10 -0
- package/dist/integrations/pi/components/shell-session-info-reference.js +94 -0
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +3 -1
- package/dist/integrations/pi/components/shell-shared-facade.js +13 -1
- package/dist/integrations/pi/components/skills-dialog.js +19 -6
- package/dist/integrations/pi/components/theme.d.ts +8 -1
- package/dist/integrations/pi/components/theme.js +28 -1
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +15 -7
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +21 -7
- package/dist/integrations/pi/components/upstream/assets/earendil-image.json +2 -2
- package/dist/integrations/pi/components/upstream/components/countdown-timer.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/earendil-announcement.js +1 -1
- package/dist/integrations/pi/components/upstream/components/extension-editor.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/extension-editor.js +3 -3
- package/dist/integrations/pi/components/upstream/components/extension-input.js +5 -6
- package/dist/integrations/pi/components/upstream/components/extension-selector.js +3 -4
- package/dist/integrations/pi/components/upstream/components/mermaid.js +1 -1
- package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/owned-editor.js +1 -1
- package/dist/integrations/pi/components/upstream/components/pi-logo.js +1 -1
- package/dist/integrations/pi/components/upstream/components/scoped-models-selector.d.ts +4 -3
- package/dist/integrations/pi/components/upstream/components/scoped-models-selector.js +4 -15
- package/dist/integrations/pi/components/upstream/components/session-footer.js +1 -1
- package/dist/integrations/pi/components/upstream/components/session-selector-search.js +1 -1
- package/dist/integrations/pi/components/upstream/components/session-selector.d.ts +2 -0
- package/dist/integrations/pi/components/upstream/components/session-selector.js +137 -110
- package/dist/integrations/pi/components/upstream/components/skill-invocation-message.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/skill-invocation-message.js +1 -1
- package/dist/integrations/pi/components/upstream/components/status-indicator.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/status-indicator.js +1 -1
- package/dist/integrations/pi/components/upstream/components/themed-text.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/themed-text.js +1 -1
- package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +9 -6
- package/dist/integrations/pi/components/upstream/components/thinking-selector.js +64 -27
- package/dist/integrations/pi/components/upstream/components/tool-execution.js +1 -1
- package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +30 -21
- package/dist/integrations/pi/components/upstream/components/tree-selector.js +246 -211
- package/dist/integrations/pi/components/upstream/components/trust-selector.js +14 -16
- package/dist/integrations/pi/components/upstream/external-editor.js +1 -1
- package/dist/integrations/pi/components/upstream/history/editor-core.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/history/editor-core.js +1 -1
- package/dist/integrations/pi/components/upstream/history/kill-ring.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/history/kill-ring.js +1 -1
- package/dist/integrations/pi/components/upstream/history/printable-key.js +1 -1
- package/dist/integrations/pi/components/upstream/history/text-helpers.js +1 -1
- package/dist/integrations/pi/components/upstream/history/undo-stack.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/history/undo-stack.js +1 -1
- package/dist/integrations/pi/components/upstream/history/word-navigation.js +1 -1
- package/dist/integrations/pi/components/upstream/model-search.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/theme/system-theme.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/theme/system-theme.js +1 -1
- package/dist/integrations/pi/components/upstream/theme/theme-controller.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/theme/theme.js +1 -1
- package/dist/integrations/pi/engine/adapter.js +21 -2
- package/dist/integrations/pi/engine/resources/changelog.json +4 -4
- package/dist/integrations/pi/engine/workflow-runner.js +2 -1
- package/dist/integrations/pi/startup-public.js +186 -81
- package/dist/integrations/pi/startup-public.manifest.json +107 -95
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +1 -1
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +1 -1
- package/dist/native/darwin-arm64/manifest.json +1 -1
- package/dist/native/linux-x64/manifest.json +1 -1
- package/dist/native/win32-x64/manifest.json +2 -2
- package/dist/native/win32-x64/process-guardian.exe +0 -0
- package/dist/runtime-payload-inventory.json +2717 -2630
- package/dist/ui/apps/host.d.ts +4 -3
- package/dist/ui/apps/host.js +9 -4
- package/dist/ui/components/dialog-panel.js +6 -7
- package/dist/ui/components/index.d.ts +1 -1
- package/dist/ui/components/index.js +1 -1
- package/dist/ui/components/list-view.d.ts +3 -3
- package/dist/ui/components/list-view.js +8 -9
- package/dist/ui/components/shortcut-hints.d.ts +2 -0
- package/dist/ui/components/shortcut-hints.js +17 -1
- package/dist/ui/components/theme.d.ts +1 -1
- package/docs/assets/readme/separator-dark.svg +2 -4
- package/docs/assets/readme/separator.svg +2 -4
- package/docs/releases/0.2.4.md +1 -5
- package/package.json +3 -3
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Provenance: @earendil-works/pi-coding-agent 1.0.
|
|
2
|
+
* Provenance: @earendil-works/pi-coding-agent 1.0.4 (MIT), commit 7c10bd4337495ee613f2224843ecdf349b80d1df,
|
|
3
3
|
* packages/coding-agent/src/modes/interactive/components/tree-selector.ts.
|
|
4
|
-
* Modifications:
|
|
5
|
-
* tree
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* Modifications: Port remaps public types/components plus owned keybindings/theme helpers while
|
|
5
|
+
* preserving tree behavior; bare A1 uses compact modal chrome and label editing, four-mode
|
|
6
|
+
* Models-style filter status with Tab/Shift+Tab directional cycling and concise all-first presentation
|
|
7
|
+
* excluding internal bookkeeping, standard search input, blue item-bounded menu-arrow selection that
|
|
8
|
+
* preserves per-entry semantic foregrounds and has no path bullets, accent entry labels, bracketed
|
|
9
|
+
* timestamps, and plain label-time status, semantic role colors with session naming for the system
|
|
10
|
+
* root, standard paging/first-last/non-root containing-branch folding keys with a permanently expanded
|
|
11
|
+
* system session entry even behind hidden metadata, single-character ellipses on both clipped edges
|
|
12
|
+
* with bracket-delimiter preservation and selected-fragment highlighting, and Models-ordered semantic
|
|
13
|
+
* shortcut footers without a redundant select hint.
|
|
14
|
+
* Deviations: owned-modal-shortcut-hints, owned-session-tree-dialog.
|
|
9
15
|
*/
|
|
10
|
-
import { Container, Input, Spacer, sliceByColumn, Text, truncateToWidth, visibleWidth, wrapTextWithAnsi, } from "@earendil-works/pi-tui";
|
|
16
|
+
import { Container, Input, matchesKey, Spacer, sliceByColumn, Text, truncateToWidth, visibleWidth, wrapTextWithAnsi, } from "@earendil-works/pi-tui";
|
|
11
17
|
import { DynamicBorder } from "../../../startup-public.js";
|
|
12
18
|
import { KeybindingsManager } from "../adjacent/core/keybindings.js";
|
|
13
19
|
import { addPiModalHeader, adoptPiModalFrame } from "../../modal-frame.js";
|
|
14
|
-
import { piTheme, renderPiModalShortcutHints } from "../../theme.js";
|
|
20
|
+
import { DIALOG_CLOSE_SHORTCUT_HINT, piTheme, renderPiModalShortcutHints } from "../../theme.js";
|
|
15
21
|
const theme = new Proxy({}, {
|
|
16
22
|
get(_target, property) {
|
|
17
23
|
const active = piTheme();
|
|
@@ -19,7 +25,7 @@ const theme = new Proxy({}, {
|
|
|
19
25
|
return typeof value === "function" ? value.bind(active) : value;
|
|
20
26
|
},
|
|
21
27
|
});
|
|
22
|
-
const treeKeybindings = KeybindingsManager.
|
|
28
|
+
const treeKeybindings = KeybindingsManager.createForOwnedInput();
|
|
23
29
|
function formatKeyText(key) {
|
|
24
30
|
return key
|
|
25
31
|
.split("/")
|
|
@@ -57,26 +63,44 @@ function renderHorizontalViewport(rows, width) {
|
|
|
57
63
|
}
|
|
58
64
|
// Clip only the body; the fixed-width gutter remains visible as navigation context.
|
|
59
65
|
return rows.map((row) => {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
66
|
+
if (viewportWidth === 0)
|
|
67
|
+
return truncateToWidth(row.gutter, width, "");
|
|
68
|
+
const leftClipped = horizontalScroll > 0;
|
|
69
|
+
const widthAfterLeftMarker = Math.max(0, viewportWidth - (leftClipped ? 1 : 0));
|
|
70
|
+
const rightClipped = row.bodyWidth - horizontalScroll > widthAfterLeftMarker;
|
|
71
|
+
const rightMarkerText = `…${row.rightClipSuffix}`;
|
|
72
|
+
const rightMarkerWidth = rightClipped ? visibleWidth(rightMarkerText) : 0;
|
|
73
|
+
const bodyWidth = Math.max(0, widthAfterLeftMarker - rightMarkerWidth);
|
|
74
|
+
const marker = (text) => row.isSelected
|
|
75
|
+
? theme.bg("selectedBg", theme.fg("muted", text))
|
|
76
|
+
: theme.fg("muted", text);
|
|
77
|
+
const body = sliceByColumn(row.body, horizontalScroll, bodyWidth, true);
|
|
78
|
+
const line = `${row.gutter}${leftClipped ? marker("…") : ""}${body}${rightClipped ? marker(rightMarkerText) : ""}\x1b[0m`;
|
|
63
79
|
return truncateToWidth(line, width, "");
|
|
64
80
|
});
|
|
65
81
|
}
|
|
82
|
+
const FILTER_MODES = ["all", "no-tools", "user-only", "labeled-only"];
|
|
83
|
+
const FILTER_LABELS = {
|
|
84
|
+
all: "all",
|
|
85
|
+
default: "all",
|
|
86
|
+
"no-tools": "no tools",
|
|
87
|
+
"user-only": "user",
|
|
88
|
+
"labeled-only": "labeled",
|
|
89
|
+
};
|
|
66
90
|
class TreeList {
|
|
67
91
|
flatNodes = [];
|
|
68
92
|
filteredNodes = [];
|
|
69
93
|
selectedIndex = 0;
|
|
70
94
|
currentLeafId;
|
|
71
95
|
maxVisibleLines;
|
|
72
|
-
filterMode = "
|
|
96
|
+
filterMode = "all";
|
|
73
97
|
searchQuery = "";
|
|
74
98
|
toolCallMap = new Map();
|
|
75
99
|
multipleRoots = false;
|
|
76
100
|
showLabelTimestamps = false;
|
|
77
|
-
activePathIds = new Set();
|
|
78
101
|
visibleParentMap = new Map();
|
|
79
102
|
visibleChildrenMap = new Map();
|
|
103
|
+
sessionRootIds = new Set();
|
|
80
104
|
lastSelectedId = null;
|
|
81
105
|
foldedNodes = new Set();
|
|
82
106
|
onSelect;
|
|
@@ -86,10 +110,9 @@ class TreeList {
|
|
|
86
110
|
constructor(tree, currentLeafId, maxVisibleLines, initialSelectedId, initialFilterMode) {
|
|
87
111
|
this.currentLeafId = currentLeafId;
|
|
88
112
|
this.maxVisibleLines = maxVisibleLines;
|
|
89
|
-
this.filterMode = initialFilterMode
|
|
113
|
+
this.filterMode = initialFilterMode && initialFilterMode !== "default" ? initialFilterMode : "all";
|
|
90
114
|
this.multipleRoots = tree.length > 1;
|
|
91
115
|
this.flatNodes = this.flattenTree(tree);
|
|
92
|
-
this.buildActivePath();
|
|
93
116
|
this.applyFilter();
|
|
94
117
|
// Start with initialSelectedId if provided, otherwise current leaf
|
|
95
118
|
const targetId = initialSelectedId ?? currentLeafId;
|
|
@@ -124,29 +147,10 @@ class TreeList {
|
|
|
124
147
|
// Fallback: last visible entry
|
|
125
148
|
return this.filteredNodes.length - 1;
|
|
126
149
|
}
|
|
127
|
-
/** Build the set of entry IDs on the path from root to current leaf */
|
|
128
|
-
buildActivePath() {
|
|
129
|
-
this.activePathIds.clear();
|
|
130
|
-
if (!this.currentLeafId)
|
|
131
|
-
return;
|
|
132
|
-
// Build a map of id -> entry for parent lookup
|
|
133
|
-
const entryMap = new Map();
|
|
134
|
-
for (const flatNode of this.flatNodes) {
|
|
135
|
-
entryMap.set(flatNode.node.entry.id, flatNode);
|
|
136
|
-
}
|
|
137
|
-
// Walk from leaf to root
|
|
138
|
-
let currentId = this.currentLeafId;
|
|
139
|
-
while (currentId) {
|
|
140
|
-
this.activePathIds.add(currentId);
|
|
141
|
-
const node = entryMap.get(currentId);
|
|
142
|
-
if (!node)
|
|
143
|
-
break;
|
|
144
|
-
currentId = node.node.entry.parentId ?? null;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
150
|
flattenTree(roots) {
|
|
148
151
|
const result = [];
|
|
149
152
|
this.toolCallMap.clear();
|
|
153
|
+
this.sessionRootIds.clear();
|
|
150
154
|
const stack = [];
|
|
151
155
|
// Determine which subtrees contain the active leaf (to sort current branch first)
|
|
152
156
|
// Use iterative post-order traversal to avoid stack overflow
|
|
@@ -186,8 +190,11 @@ class TreeList {
|
|
|
186
190
|
}
|
|
187
191
|
while (stack.length > 0) {
|
|
188
192
|
const [node, indent, justBranched, showConnector, isLast, gutters, isVirtualRootChild] = stack.pop();
|
|
189
|
-
// Extract tool calls from
|
|
193
|
+
// Extract structural roots and tool calls from messages for later lookup.
|
|
190
194
|
const entry = node.entry;
|
|
195
|
+
if (entry.type === "message" && entry.message.role === "system") {
|
|
196
|
+
this.sessionRootIds.add(entry.id);
|
|
197
|
+
}
|
|
191
198
|
if (entry.type === "message" && entry.message.role === "assistant") {
|
|
192
199
|
const content = entry.message.content;
|
|
193
200
|
if (Array.isArray(content)) {
|
|
@@ -266,7 +273,7 @@ class TreeList {
|
|
|
266
273
|
const searchTokens = this.searchQuery.toLowerCase().split(/\s+/).filter(Boolean);
|
|
267
274
|
this.filteredNodes = this.flatNodes.filter((flatNode) => {
|
|
268
275
|
const entry = flatNode.node.entry;
|
|
269
|
-
if (entry.type === "usage")
|
|
276
|
+
if (entry.type === "usage" || entry.type === "model_change" || entry.type === "thinking_level_change")
|
|
270
277
|
return false;
|
|
271
278
|
const isCurrentLeaf = entry.id === this.currentLeafId;
|
|
272
279
|
// Skip assistant messages with only tool calls (no text) unless error/aborted
|
|
@@ -286,8 +293,6 @@ class TreeList {
|
|
|
286
293
|
const isSettingsEntry = entry.type === "label" ||
|
|
287
294
|
entry.type === "context_edit" ||
|
|
288
295
|
entry.type === "custom" ||
|
|
289
|
-
entry.type === "model_change" ||
|
|
290
|
-
entry.type === "thinking_level_change" ||
|
|
291
296
|
entry.type === "session_info";
|
|
292
297
|
switch (this.filterMode) {
|
|
293
298
|
case "user-only":
|
|
@@ -303,11 +308,8 @@ class TreeList {
|
|
|
303
308
|
passesFilter = flatNode.node.label !== undefined;
|
|
304
309
|
break;
|
|
305
310
|
case "all":
|
|
306
|
-
// Show everything
|
|
307
|
-
passesFilter = true;
|
|
308
|
-
break;
|
|
309
311
|
default:
|
|
310
|
-
//
|
|
312
|
+
// Product "all" omits internal settings/bookkeeping entries.
|
|
311
313
|
passesFilter = !isSettingsEntry;
|
|
312
314
|
break;
|
|
313
315
|
}
|
|
@@ -523,6 +525,9 @@ class TreeList {
|
|
|
523
525
|
getSearchQuery() {
|
|
524
526
|
return this.searchQuery;
|
|
525
527
|
}
|
|
528
|
+
getFilterMode() {
|
|
529
|
+
return this.filterMode;
|
|
530
|
+
}
|
|
526
531
|
getSelectedNode() {
|
|
527
532
|
return this.filteredNodes[this.selectedIndex]?.node;
|
|
528
533
|
}
|
|
@@ -546,31 +551,15 @@ class TreeList {
|
|
|
546
551
|
}
|
|
547
552
|
}
|
|
548
553
|
getStatusLabels() {
|
|
549
|
-
|
|
550
|
-
switch (this.filterMode) {
|
|
551
|
-
case "no-tools":
|
|
552
|
-
labels += " [no-tools]";
|
|
553
|
-
break;
|
|
554
|
-
case "user-only":
|
|
555
|
-
labels += " [user]";
|
|
556
|
-
break;
|
|
557
|
-
case "labeled-only":
|
|
558
|
-
labels += " [labeled]";
|
|
559
|
-
break;
|
|
560
|
-
case "all":
|
|
561
|
-
labels += " [all]";
|
|
562
|
-
break;
|
|
563
|
-
}
|
|
564
|
-
if (this.showLabelTimestamps) {
|
|
565
|
-
labels += " [+label time]";
|
|
566
|
-
}
|
|
567
|
-
return labels;
|
|
554
|
+
return this.showLabelTimestamps ? " label time" : "";
|
|
568
555
|
}
|
|
569
556
|
render(width) {
|
|
570
557
|
const lines = [];
|
|
571
558
|
if (this.filteredNodes.length === 0) {
|
|
572
559
|
lines.push(truncateToWidth(theme.fg("muted", " No entries found"), width));
|
|
573
|
-
|
|
560
|
+
const status = this.getStatusLabels().trim();
|
|
561
|
+
if (status)
|
|
562
|
+
lines.push(truncateToWidth(theme.fg("muted", ` ${status}`), width));
|
|
574
563
|
return lines;
|
|
575
564
|
}
|
|
576
565
|
const startIndex = Math.max(0, Math.min(this.selectedIndex - Math.floor(this.maxVisibleLines / 2), this.filteredNodes.length - this.maxVisibleLines));
|
|
@@ -580,8 +569,8 @@ class TreeList {
|
|
|
580
569
|
const flatNode = this.filteredNodes[i];
|
|
581
570
|
const entry = flatNode.node.entry;
|
|
582
571
|
const isSelected = i === this.selectedIndex;
|
|
583
|
-
// Build line:
|
|
584
|
-
const cursor = isSelected ? theme.fg("accent", "
|
|
572
|
+
// Build line: menu arrow + tree prefix + path marker + label + content
|
|
573
|
+
const cursor = isSelected ? theme.fg("accent", "→ ") : " ";
|
|
585
574
|
// If multiple roots, shift display (roots at 0, not 1)
|
|
586
575
|
const displayIndent = this.multipleRoots ? Math.max(0, flatNode.indent - 1) : flatNode.indent;
|
|
587
576
|
// Build prefix with gutters at their correct positions
|
|
@@ -626,15 +615,12 @@ class TreeList {
|
|
|
626
615
|
// Fold marker for nodes without connectors (roots)
|
|
627
616
|
const showsFoldInConnector = flatNode.showConnector && !flatNode.isVirtualRootChild;
|
|
628
617
|
const foldMarker = isFolded && !showsFoldInConnector ? theme.fg("accent", "⊞ ") : "";
|
|
629
|
-
|
|
630
|
-
const isOnActivePath = this.activePathIds.has(entry.id);
|
|
631
|
-
const pathMarker = isOnActivePath ? theme.fg("accent", "• ") : "";
|
|
632
|
-
const label = flatNode.node.label ? theme.fg("warning", `[${flatNode.node.label}] `) : "";
|
|
618
|
+
const label = flatNode.node.label ? theme.fg("accent", `[${flatNode.node.label}] `) : "";
|
|
633
619
|
const labelTimestamp = this.showLabelTimestamps && flatNode.node.label && flatNode.node.labelTimestamp
|
|
634
|
-
? theme.fg("
|
|
620
|
+
? theme.fg("accent", `[${this.formatLabelTimestamp(flatNode.node.labelTimestamp)}] `)
|
|
635
621
|
: "";
|
|
636
|
-
const content = this.getEntryDisplayText(flatNode.node
|
|
637
|
-
const prefixPart = theme.fg("dim", prefix) + foldMarker
|
|
622
|
+
const content = this.getEntryDisplayText(flatNode.node);
|
|
623
|
+
const prefixPart = theme.fg("dim", prefix) + foldMarker;
|
|
638
624
|
const anchorCol = visibleWidth(prefixPart);
|
|
639
625
|
let gutter = cursor;
|
|
640
626
|
let body = prefixPart + label + labelTimestamp + content;
|
|
@@ -642,58 +628,53 @@ class TreeList {
|
|
|
642
628
|
gutter = theme.bg("selectedBg", gutter);
|
|
643
629
|
body = theme.bg("selectedBg", body);
|
|
644
630
|
}
|
|
645
|
-
|
|
631
|
+
const rightClipSuffix = entry.type === "message" && entry.message.role === "toolResult" ? "]" : "";
|
|
632
|
+
renderedRows.push({ gutter, body, anchorCol, bodyWidth: visibleWidth(body), rightClipSuffix, isSelected });
|
|
646
633
|
}
|
|
647
634
|
lines.push(...renderHorizontalViewport(renderedRows, width));
|
|
648
635
|
lines.push(truncateToWidth(theme.fg("muted", ` (${this.selectedIndex + 1}/${this.filteredNodes.length})${this.getStatusLabels()}`), width));
|
|
649
636
|
return lines;
|
|
650
637
|
}
|
|
651
|
-
getEntryDisplayText(node
|
|
638
|
+
getEntryDisplayText(node) {
|
|
652
639
|
const entry = node.entry;
|
|
653
640
|
let result;
|
|
654
641
|
const normalize = (s) => s.replace(/[\n\t]/g, " ").trim();
|
|
642
|
+
const primary = (color, text) => theme.fg(color, text);
|
|
643
|
+
const description = (text, color = "muted") => theme.fg(color, text);
|
|
655
644
|
switch (entry.type) {
|
|
656
645
|
case "message": {
|
|
657
646
|
const msg = entry.message;
|
|
658
647
|
const role = msg.role;
|
|
659
648
|
if (role === "user") {
|
|
660
|
-
const
|
|
661
|
-
|
|
662
|
-
result = theme.fg("accent", "user: ") + content;
|
|
649
|
+
const content = normalize(this.extractContent(msg.content));
|
|
650
|
+
result = primary("success", "user: ") + description(content);
|
|
663
651
|
}
|
|
664
652
|
else if (role === "assistant") {
|
|
665
|
-
const
|
|
666
|
-
const textContent = normalize(this.extractContent(
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
else if (
|
|
671
|
-
result =
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
result =
|
|
676
|
-
}
|
|
677
|
-
else {
|
|
678
|
-
result = theme.fg("success", "assistant: ") + theme.fg("muted", "(no content)");
|
|
679
|
-
}
|
|
653
|
+
const assistant = msg;
|
|
654
|
+
const textContent = normalize(this.extractContent(assistant.content));
|
|
655
|
+
const label = primary("warning", "assistant: ");
|
|
656
|
+
if (textContent)
|
|
657
|
+
result = label + description(textContent);
|
|
658
|
+
else if (assistant.stopReason === "aborted")
|
|
659
|
+
result = label + description("(aborted)", "muted");
|
|
660
|
+
else if (assistant.errorMessage)
|
|
661
|
+
result = label + description(normalize(assistant.errorMessage).slice(0, 80), "error");
|
|
662
|
+
else
|
|
663
|
+
result = label + description("(no content)", "muted");
|
|
680
664
|
}
|
|
681
665
|
else if (role === "toolResult") {
|
|
682
666
|
const toolMsg = msg;
|
|
683
667
|
const toolCall = toolMsg.toolCallId ? this.toolCallMap.get(toolMsg.toolCallId) : undefined;
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
else {
|
|
688
|
-
result = theme.fg("muted", `[${toolMsg.toolName ?? "tool"}]`);
|
|
689
|
-
}
|
|
668
|
+
result = primary("muted", toolCall
|
|
669
|
+
? this.formatToolCall(toolCall.name, toolCall.arguments)
|
|
670
|
+
: `[${toolMsg.toolName ?? "tool"}]`);
|
|
690
671
|
}
|
|
691
672
|
else if (role === "bashExecution") {
|
|
692
|
-
const
|
|
693
|
-
result =
|
|
673
|
+
const command = normalize(msg.command ?? "");
|
|
674
|
+
result = primary("dim", "[bash]: ") + description(command, "dim");
|
|
694
675
|
}
|
|
695
676
|
else {
|
|
696
|
-
result =
|
|
677
|
+
result = primary("dim", role === "system" ? "session" : `[${role}]`);
|
|
697
678
|
}
|
|
698
679
|
break;
|
|
699
680
|
}
|
|
@@ -704,41 +685,41 @@ class TreeList {
|
|
|
704
685
|
.filter((c) => c.type === "text")
|
|
705
686
|
.map((c) => c.text)
|
|
706
687
|
.join("");
|
|
707
|
-
result =
|
|
688
|
+
result = primary("customMessageLabel", `[${entry.customType}]: `) + description(normalize(content));
|
|
708
689
|
break;
|
|
709
690
|
}
|
|
710
691
|
case "compaction": {
|
|
711
692
|
const tokens = Math.round(entry.tokensBefore / 1000);
|
|
712
|
-
result =
|
|
693
|
+
result = primary("borderAccent", `[compaction: ${tokens}k tokens]`);
|
|
713
694
|
break;
|
|
714
695
|
}
|
|
715
696
|
case "branch_summary":
|
|
716
|
-
result =
|
|
697
|
+
result = primary("warning", "[branch summary]: ") + description(normalize(entry.summary));
|
|
717
698
|
break;
|
|
718
699
|
case "model_change":
|
|
719
|
-
result =
|
|
700
|
+
result = primary("dim", `[model: ${entry.modelId}]`);
|
|
720
701
|
break;
|
|
721
702
|
case "thinking_level_change":
|
|
722
|
-
result =
|
|
703
|
+
result = primary("dim", `[thinking: ${entry.thinkingLevel}]`);
|
|
723
704
|
break;
|
|
724
705
|
case "custom":
|
|
725
|
-
result =
|
|
706
|
+
result = primary("dim", `[custom: ${entry.customType}]`);
|
|
726
707
|
break;
|
|
727
708
|
case "context_edit":
|
|
728
|
-
result =
|
|
709
|
+
result = primary("dim", `[context ${entry.replacement === null ? "omit" : "replace"}: ${entry.targetId}]`);
|
|
729
710
|
break;
|
|
730
711
|
case "label":
|
|
731
|
-
result =
|
|
712
|
+
result = primary("dim", `[label: ${entry.label ?? "(cleared)"}]`);
|
|
732
713
|
break;
|
|
733
714
|
case "session_info":
|
|
734
715
|
result = entry.name
|
|
735
|
-
?
|
|
716
|
+
? primary("dim", `[title: ${entry.name}]`)
|
|
736
717
|
: [theme.fg("dim", "[title: "), theme.italic(theme.fg("dim", "empty")), theme.fg("dim", "]")].join("");
|
|
737
718
|
break;
|
|
738
719
|
default:
|
|
739
720
|
result = "";
|
|
740
721
|
}
|
|
741
|
-
return
|
|
722
|
+
return result;
|
|
742
723
|
}
|
|
743
724
|
formatLabelTimestamp(timestamp) {
|
|
744
725
|
const date = new Date(timestamp);
|
|
@@ -850,7 +831,7 @@ class TreeList {
|
|
|
850
831
|
.replace(/[\n\t]/g, " ")
|
|
851
832
|
.trim()
|
|
852
833
|
.slice(0, 50);
|
|
853
|
-
return `[bash: ${cmd}${rawCmd.length > 50 ? "
|
|
834
|
+
return `[bash: ${cmd}${rawCmd.length > 50 ? "…" : ""}]`;
|
|
854
835
|
}
|
|
855
836
|
case "grep": {
|
|
856
837
|
const pattern = String(args.pattern || "");
|
|
@@ -869,7 +850,7 @@ class TreeList {
|
|
|
869
850
|
default: {
|
|
870
851
|
// Custom tool - show name and truncated JSON args
|
|
871
852
|
const argsStr = JSON.stringify(args).slice(0, 40);
|
|
872
|
-
return `[${name}: ${argsStr}${JSON.stringify(args).length > 40 ? "
|
|
853
|
+
return `[${name}: ${argsStr}${JSON.stringify(args).length > 40 ? "…" : ""}]`;
|
|
873
854
|
}
|
|
874
855
|
}
|
|
875
856
|
}
|
|
@@ -881,6 +862,21 @@ class TreeList {
|
|
|
881
862
|
else if (kb.matches(keyData, "tui.select.down")) {
|
|
882
863
|
this.selectedIndex = this.selectedIndex === this.filteredNodes.length - 1 ? 0 : this.selectedIndex + 1;
|
|
883
864
|
}
|
|
865
|
+
else if (kb.matches(keyData, "owned.tree.collapse")) {
|
|
866
|
+
const currentId = this.filteredNodes[this.selectedIndex]?.node.entry.id;
|
|
867
|
+
const branchId = currentId ? this.findNearestExpandedBranch(currentId) : null;
|
|
868
|
+
if (branchId) {
|
|
869
|
+
this.foldedNodes.add(branchId);
|
|
870
|
+
this.applyFilter();
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
else if (kb.matches(keyData, "owned.tree.expand")) {
|
|
874
|
+
const currentId = this.filteredNodes[this.selectedIndex]?.node.entry.id;
|
|
875
|
+
if (currentId && this.foldedNodes.has(currentId)) {
|
|
876
|
+
this.foldedNodes.delete(currentId);
|
|
877
|
+
this.applyFilter();
|
|
878
|
+
}
|
|
879
|
+
}
|
|
884
880
|
else if (kb.matches(keyData, "app.tree.foldOrUp")) {
|
|
885
881
|
const currentId = this.filteredNodes[this.selectedIndex]?.node.entry.id;
|
|
886
882
|
if (currentId && this.isFoldable(currentId) && !this.foldedNodes.has(currentId)) {
|
|
@@ -901,14 +897,18 @@ class TreeList {
|
|
|
901
897
|
this.selectedIndex = this.findBranchSegmentStart("down");
|
|
902
898
|
}
|
|
903
899
|
}
|
|
904
|
-
else if (kb.matches(keyData, "tui.
|
|
905
|
-
// Page up
|
|
900
|
+
else if (kb.matches(keyData, "tui.select.pageUp")) {
|
|
906
901
|
this.selectedIndex = Math.max(0, this.selectedIndex - this.maxVisibleLines);
|
|
907
902
|
}
|
|
908
|
-
else if (kb.matches(keyData, "tui.
|
|
909
|
-
// Page down
|
|
903
|
+
else if (kb.matches(keyData, "tui.select.pageDown")) {
|
|
910
904
|
this.selectedIndex = Math.min(this.filteredNodes.length - 1, this.selectedIndex + this.maxVisibleLines);
|
|
911
905
|
}
|
|
906
|
+
else if (kb.matches(keyData, "owned.tree.first")) {
|
|
907
|
+
this.selectedIndex = 0;
|
|
908
|
+
}
|
|
909
|
+
else if (kb.matches(keyData, "owned.tree.last")) {
|
|
910
|
+
this.selectedIndex = Math.max(0, this.filteredNodes.length - 1);
|
|
911
|
+
}
|
|
912
912
|
else if (kb.matches(keyData, "tui.select.confirm")) {
|
|
913
913
|
const selected = this.filteredNodes[this.selectedIndex];
|
|
914
914
|
if (selected && this.onSelect) {
|
|
@@ -929,48 +929,42 @@ class TreeList {
|
|
|
929
929
|
}
|
|
930
930
|
}
|
|
931
931
|
else if (kb.matches(keyData, "app.tree.filter.default")) {
|
|
932
|
-
|
|
933
|
-
this.filterMode = "default";
|
|
932
|
+
this.filterMode = "all";
|
|
934
933
|
this.foldedNodes.clear();
|
|
935
934
|
this.applyFilter();
|
|
936
935
|
}
|
|
937
936
|
else if (kb.matches(keyData, "app.tree.filter.noTools")) {
|
|
938
|
-
// Toggle filter: no-tools ↔
|
|
939
|
-
this.filterMode = this.filterMode === "no-tools" ? "
|
|
937
|
+
// Toggle filter: no-tools ↔ all
|
|
938
|
+
this.filterMode = this.filterMode === "no-tools" ? "all" : "no-tools";
|
|
940
939
|
this.foldedNodes.clear();
|
|
941
940
|
this.applyFilter();
|
|
942
941
|
}
|
|
943
942
|
else if (kb.matches(keyData, "app.tree.filter.userOnly")) {
|
|
944
|
-
// Toggle filter: user-only ↔
|
|
945
|
-
this.filterMode = this.filterMode === "user-only" ? "
|
|
943
|
+
// Toggle filter: user-only ↔ all
|
|
944
|
+
this.filterMode = this.filterMode === "user-only" ? "all" : "user-only";
|
|
946
945
|
this.foldedNodes.clear();
|
|
947
946
|
this.applyFilter();
|
|
948
947
|
}
|
|
949
948
|
else if (kb.matches(keyData, "app.tree.filter.labeledOnly")) {
|
|
950
|
-
// Toggle filter: labeled-only ↔
|
|
951
|
-
this.filterMode = this.filterMode === "labeled-only" ? "
|
|
949
|
+
// Toggle filter: labeled-only ↔ all
|
|
950
|
+
this.filterMode = this.filterMode === "labeled-only" ? "all" : "labeled-only";
|
|
952
951
|
this.foldedNodes.clear();
|
|
953
952
|
this.applyFilter();
|
|
954
953
|
}
|
|
955
954
|
else if (kb.matches(keyData, "app.tree.filter.all")) {
|
|
956
|
-
|
|
957
|
-
this.filterMode = this.filterMode === "all" ? "default" : "all";
|
|
955
|
+
this.filterMode = "all";
|
|
958
956
|
this.foldedNodes.clear();
|
|
959
957
|
this.applyFilter();
|
|
960
958
|
}
|
|
961
|
-
else if (kb.matches(keyData, "app.tree.filter.cycleBackward")) {
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
const currentIndex = modes.indexOf(this.filterMode);
|
|
965
|
-
this.filterMode = modes[(currentIndex - 1 + modes.length) % modes.length];
|
|
959
|
+
else if (matchesKey(keyData, "shift+tab") || kb.matches(keyData, "app.tree.filter.cycleBackward")) {
|
|
960
|
+
const currentIndex = FILTER_MODES.indexOf(this.filterMode);
|
|
961
|
+
this.filterMode = FILTER_MODES[(currentIndex - 1 + FILTER_MODES.length) % FILTER_MODES.length];
|
|
966
962
|
this.foldedNodes.clear();
|
|
967
963
|
this.applyFilter();
|
|
968
964
|
}
|
|
969
965
|
else if (kb.matches(keyData, "app.tree.filter.cycleForward")) {
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
const currentIndex = modes.indexOf(this.filterMode);
|
|
973
|
-
this.filterMode = modes[(currentIndex + 1) % modes.length];
|
|
966
|
+
const currentIndex = FILTER_MODES.indexOf(this.filterMode);
|
|
967
|
+
this.filterMode = FILTER_MODES[(currentIndex + 1) % FILTER_MODES.length];
|
|
974
968
|
this.foldedNodes.clear();
|
|
975
969
|
this.applyFilter();
|
|
976
970
|
}
|
|
@@ -1003,11 +997,14 @@ class TreeList {
|
|
|
1003
997
|
}
|
|
1004
998
|
}
|
|
1005
999
|
/**
|
|
1006
|
-
* Whether a node can be folded.
|
|
1007
|
-
*
|
|
1008
|
-
*
|
|
1000
|
+
* Whether a node can be folded. The system entry rendered as the session root
|
|
1001
|
+
* stays expanded even when hidden metadata precedes it; other nodes are
|
|
1002
|
+
* foldable when they have visible children and either appear at the filtered
|
|
1003
|
+
* root or begin a segment below a visible branch point.
|
|
1009
1004
|
*/
|
|
1010
1005
|
isFoldable(entryId) {
|
|
1006
|
+
if (this.sessionRootIds.has(entryId))
|
|
1007
|
+
return false;
|
|
1011
1008
|
const children = this.visibleChildrenMap.get(entryId);
|
|
1012
1009
|
if (!children || children.length === 0)
|
|
1013
1010
|
return false;
|
|
@@ -1017,13 +1014,17 @@ class TreeList {
|
|
|
1017
1014
|
const siblings = this.visibleChildrenMap.get(parentId);
|
|
1018
1015
|
return siblings !== undefined && siblings.length > 1;
|
|
1019
1016
|
}
|
|
1020
|
-
/**
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1017
|
+
/** Resolve the nearest expanded branch containing the selected entry. */
|
|
1018
|
+
findNearestExpandedBranch(entryId) {
|
|
1019
|
+
let currentId = entryId;
|
|
1020
|
+
while (currentId !== null) {
|
|
1021
|
+
if (this.isFoldable(currentId) && !this.foldedNodes.has(currentId))
|
|
1022
|
+
return currentId;
|
|
1023
|
+
currentId = this.visibleParentMap.get(currentId) ?? null;
|
|
1024
|
+
}
|
|
1025
|
+
return null;
|
|
1026
|
+
}
|
|
1027
|
+
/** Find the next branch segment start for the retained modified-arrow shortcuts. */
|
|
1027
1028
|
findBranchSegmentStart(direction) {
|
|
1028
1029
|
const selectedId = this.filteredNodes[this.selectedIndex]?.node.entry.id;
|
|
1029
1030
|
if (!selectedId)
|
|
@@ -1040,7 +1041,6 @@ class TreeList {
|
|
|
1040
1041
|
currentId = children[0];
|
|
1041
1042
|
}
|
|
1042
1043
|
}
|
|
1043
|
-
// direction === "up"
|
|
1044
1044
|
while (true) {
|
|
1045
1045
|
const parentId = this.visibleParentMap.get(currentId) ?? null;
|
|
1046
1046
|
if (parentId === null)
|
|
@@ -1048,38 +1048,63 @@ class TreeList {
|
|
|
1048
1048
|
const children = this.visibleChildrenMap.get(parentId) ?? [];
|
|
1049
1049
|
if (children.length > 1) {
|
|
1050
1050
|
const segmentStart = indexByEntryId.get(currentId);
|
|
1051
|
-
if (segmentStart < this.selectedIndex)
|
|
1051
|
+
if (segmentStart < this.selectedIndex)
|
|
1052
1052
|
return segmentStart;
|
|
1053
|
-
}
|
|
1054
1053
|
}
|
|
1055
1054
|
currentId = parentId;
|
|
1056
1055
|
}
|
|
1057
1056
|
}
|
|
1058
1057
|
}
|
|
1059
|
-
|
|
1060
|
-
class SearchLine {
|
|
1058
|
+
class TreeSearchInput {
|
|
1061
1059
|
treeList;
|
|
1060
|
+
input = new Input();
|
|
1061
|
+
renderedQuery = "";
|
|
1062
1062
|
constructor(treeList) {
|
|
1063
1063
|
this.treeList = treeList;
|
|
1064
1064
|
}
|
|
1065
|
-
|
|
1065
|
+
get focused() {
|
|
1066
|
+
return this.input.focused;
|
|
1067
|
+
}
|
|
1068
|
+
set focused(value) {
|
|
1069
|
+
this.input.focused = value;
|
|
1070
|
+
}
|
|
1071
|
+
invalidate() {
|
|
1072
|
+
this.input.invalidate();
|
|
1073
|
+
}
|
|
1066
1074
|
render(width) {
|
|
1067
1075
|
const query = this.treeList.getSearchQuery();
|
|
1068
|
-
if (query) {
|
|
1069
|
-
|
|
1076
|
+
if (query !== this.renderedQuery) {
|
|
1077
|
+
this.input = Object.assign(new Input(), { focused: this.input.focused });
|
|
1078
|
+
this.input.handleInput(query);
|
|
1079
|
+
this.renderedQuery = query;
|
|
1070
1080
|
}
|
|
1071
|
-
return
|
|
1081
|
+
return this.input.render(width);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
/** Models-style summary of the active tree filter. */
|
|
1085
|
+
class TreeFilter {
|
|
1086
|
+
treeList;
|
|
1087
|
+
constructor(treeList) {
|
|
1088
|
+
this.treeList = treeList;
|
|
1089
|
+
}
|
|
1090
|
+
invalidate() { }
|
|
1091
|
+
render(width) {
|
|
1092
|
+
const active = this.treeList.getFilterMode();
|
|
1093
|
+
const choices = FILTER_MODES.map((mode) => theme.fg(mode === active ? "accent" : "muted", FILTER_LABELS[mode]));
|
|
1094
|
+
return [truncateToWidth(theme.fg("muted", "Filter: ") + choices.join(theme.fg("muted", " | ")), width)];
|
|
1072
1095
|
}
|
|
1073
|
-
handleInput(_keyData) { }
|
|
1074
1096
|
}
|
|
1075
1097
|
/** Component that renders tree help as semantic rows with chunk-aware wrapping */
|
|
1076
1098
|
class TreeHelp {
|
|
1077
1099
|
invalidate() { }
|
|
1078
1100
|
render(width) {
|
|
1079
|
-
const items =
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1101
|
+
const items = [
|
|
1102
|
+
...TREE_HELP_ITEMS.map(({ keys, label, labelFirst }) => {
|
|
1103
|
+
const key = formatHelpKeys(keys);
|
|
1104
|
+
return renderPiModalShortcutHints([key ? { key, action: label, ...(labelFirst === undefined ? {} : { actionFirst: labelFirst }) } : { action: label }]);
|
|
1105
|
+
}),
|
|
1106
|
+
renderPiModalShortcutHints([DIALOG_CLOSE_SHORTCUT_HINT]),
|
|
1107
|
+
];
|
|
1083
1108
|
const availableWidth = Math.max(1, width);
|
|
1084
1109
|
// The frame supplies the shared outer cell; help content needs no duplicate chrome inset.
|
|
1085
1110
|
const indent = "";
|
|
@@ -1106,24 +1131,15 @@ class TreeHelp {
|
|
|
1106
1131
|
}
|
|
1107
1132
|
}
|
|
1108
1133
|
const TREE_HELP_ITEMS = [
|
|
1109
|
-
{ keys: [
|
|
1110
|
-
{ keys: ["tui.
|
|
1111
|
-
{ keys: ["app.tree.
|
|
1134
|
+
{ keys: [], label: "type to search" },
|
|
1135
|
+
{ keys: ["tui.select.up", "tui.select.down"], label: "navigate" },
|
|
1136
|
+
{ keys: ["app.tree.filter.cycleForward"], label: "filter" },
|
|
1137
|
+
{ keys: ["tui.select.pageUp", "tui.select.pageDown"], label: "page" },
|
|
1138
|
+
{ keys: ["owned.tree.first", "owned.tree.last"], label: "first/last" },
|
|
1139
|
+
{ keys: ["owned.tree.collapse", "owned.tree.expand"], label: "branch" },
|
|
1112
1140
|
{ keys: ["app.message.copy"], label: "copy" },
|
|
1113
1141
|
{ keys: ["app.tree.editLabel"], label: "label" },
|
|
1114
1142
|
{ keys: ["app.tree.toggleLabelTimestamp"], label: "label time" },
|
|
1115
|
-
{
|
|
1116
|
-
keys: [
|
|
1117
|
-
"app.tree.filter.default",
|
|
1118
|
-
"app.tree.filter.noTools",
|
|
1119
|
-
"app.tree.filter.userOnly",
|
|
1120
|
-
"app.tree.filter.labeledOnly",
|
|
1121
|
-
"app.tree.filter.all",
|
|
1122
|
-
],
|
|
1123
|
-
label: "filters",
|
|
1124
|
-
labelFirst: true,
|
|
1125
|
-
},
|
|
1126
|
-
{ keys: ["app.tree.filter.cycleForward", "app.tree.filter.cycleBackward"], label: "cycle", labelFirst: true },
|
|
1127
1143
|
];
|
|
1128
1144
|
function formatHelpKeys(keybindings) {
|
|
1129
1145
|
const keys = [];
|
|
@@ -1135,6 +1151,7 @@ function formatHelpKeys(keybindings) {
|
|
|
1135
1151
|
if (keys.length === 0)
|
|
1136
1152
|
return "";
|
|
1137
1153
|
return formatKeyText(compactRawKeys(keys))
|
|
1154
|
+
.replace(/\btab\b/gi, "Tab")
|
|
1138
1155
|
.replace(/\bpageUp\b/g, "pgup")
|
|
1139
1156
|
.replace(/\bpageDown\b/g, "pgdn")
|
|
1140
1157
|
.replace(/\bup\b/g, "↑")
|
|
@@ -1174,22 +1191,23 @@ class LabelInput {
|
|
|
1174
1191
|
constructor(entryId, currentLabel) {
|
|
1175
1192
|
this.entryId = entryId;
|
|
1176
1193
|
this.input = new Input();
|
|
1177
|
-
if (currentLabel)
|
|
1178
|
-
this.input.
|
|
1179
|
-
|
|
1194
|
+
if (currentLabel)
|
|
1195
|
+
this.input.handleInput(currentLabel);
|
|
1196
|
+
}
|
|
1197
|
+
invalidate() {
|
|
1198
|
+
this.input.invalidate();
|
|
1180
1199
|
}
|
|
1181
|
-
invalidate() { }
|
|
1182
1200
|
render(width) {
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1201
|
+
return [
|
|
1202
|
+
truncateToWidth(theme.fg("muted", "Empty to remove"), width),
|
|
1203
|
+
"",
|
|
1204
|
+
...this.input.render(width),
|
|
1205
|
+
"",
|
|
1206
|
+
truncateToWidth(renderPiModalShortcutHints([
|
|
1207
|
+
shortcutHint("tui.select.confirm", "save"),
|
|
1208
|
+
DIALOG_CLOSE_SHORTCUT_HINT,
|
|
1209
|
+
]), width),
|
|
1210
|
+
];
|
|
1193
1211
|
}
|
|
1194
1212
|
handleInput(keyData) {
|
|
1195
1213
|
const kb = treeKeybindings;
|
|
@@ -1210,9 +1228,15 @@ class LabelInput {
|
|
|
1210
1228
|
*/
|
|
1211
1229
|
export class TreeSelectorComponent extends Container {
|
|
1212
1230
|
treeList;
|
|
1231
|
+
searchInput;
|
|
1232
|
+
treeFilter;
|
|
1233
|
+
titleText;
|
|
1234
|
+
searchInputContainer = new Container();
|
|
1235
|
+
treeContainer = new Container();
|
|
1236
|
+
labelInputContainer = new Container();
|
|
1237
|
+
footerContainer = new Container();
|
|
1238
|
+
treeHelp = new TreeHelp();
|
|
1213
1239
|
labelInput = null;
|
|
1214
|
-
labelInputContainer;
|
|
1215
|
-
treeContainer;
|
|
1216
1240
|
onLabelChangeCallback;
|
|
1217
1241
|
onCopy;
|
|
1218
1242
|
// Focusable implementation - propagate to labelInput when active for IME cursor positioning
|
|
@@ -1222,10 +1246,9 @@ export class TreeSelectorComponent extends Container {
|
|
|
1222
1246
|
}
|
|
1223
1247
|
set focused(value) {
|
|
1224
1248
|
this._focused = value;
|
|
1225
|
-
|
|
1226
|
-
if (this.labelInput)
|
|
1249
|
+
this.searchInput.focused = value && this.labelInput === null;
|
|
1250
|
+
if (this.labelInput)
|
|
1227
1251
|
this.labelInput.focused = value;
|
|
1228
|
-
}
|
|
1229
1252
|
}
|
|
1230
1253
|
constructor(tree, currentLeafId, terminalHeight, onSelect, onCancel, onLabelChange, initialSelectedId, initialFilterMode) {
|
|
1231
1254
|
super();
|
|
@@ -1236,30 +1259,38 @@ export class TreeSelectorComponent extends Container {
|
|
|
1236
1259
|
this.treeList.onCancel = onCancel;
|
|
1237
1260
|
this.treeList.onCopy = (text) => this.onCopy?.(text);
|
|
1238
1261
|
this.treeList.onLabelEdit = (entryId, currentLabel) => this.showLabelInput(entryId, currentLabel);
|
|
1239
|
-
this.
|
|
1240
|
-
this.
|
|
1241
|
-
this.
|
|
1242
|
-
this.
|
|
1243
|
-
const header = addPiModalHeader(this, new DynamicBorder(),
|
|
1244
|
-
this.addChild(
|
|
1245
|
-
this.addChild(new SearchLine(this.treeList));
|
|
1246
|
-
const separator = new DynamicBorder();
|
|
1247
|
-
this.addChild(separator);
|
|
1248
|
-
this.addChild(new Spacer(1));
|
|
1262
|
+
this.searchInput = new TreeSearchInput(this.treeList);
|
|
1263
|
+
this.treeFilter = new TreeFilter(this.treeList);
|
|
1264
|
+
this.titleText = new Text(theme.fg("accent", theme.bold("Session Tree")), 0, 0);
|
|
1265
|
+
this.restoreTreeContent();
|
|
1266
|
+
const header = addPiModalHeader(this, new DynamicBorder(), this.titleText);
|
|
1267
|
+
this.addChild(this.searchInputContainer);
|
|
1249
1268
|
this.addChild(this.treeContainer);
|
|
1250
1269
|
this.addChild(this.labelInputContainer);
|
|
1251
|
-
this.addChild(
|
|
1270
|
+
this.addChild(this.footerContainer);
|
|
1252
1271
|
this.addChild(new DynamicBorder());
|
|
1253
1272
|
adoptPiModalFrame(this, {
|
|
1254
|
-
topIndex:
|
|
1273
|
+
topIndex: 0,
|
|
1255
1274
|
bottomIndex: this.children.length - 1,
|
|
1256
1275
|
header,
|
|
1257
|
-
fullWidthContent: [separator],
|
|
1258
1276
|
});
|
|
1259
1277
|
if (tree.length === 0) {
|
|
1260
1278
|
setTimeout(() => onCancel(), 100);
|
|
1261
1279
|
}
|
|
1262
1280
|
}
|
|
1281
|
+
restoreTreeContent() {
|
|
1282
|
+
this.titleText.setText(theme.fg("accent", theme.bold("Session Tree")));
|
|
1283
|
+
this.searchInputContainer.clear();
|
|
1284
|
+
this.searchInputContainer.addChild(this.treeFilter);
|
|
1285
|
+
this.searchInputContainer.addChild(new Spacer(1));
|
|
1286
|
+
this.searchInputContainer.addChild(this.searchInput);
|
|
1287
|
+
this.searchInputContainer.addChild(new Spacer(1));
|
|
1288
|
+
this.treeContainer.clear();
|
|
1289
|
+
this.treeContainer.addChild(this.treeList);
|
|
1290
|
+
this.footerContainer.clear();
|
|
1291
|
+
this.footerContainer.addChild(new Spacer(1));
|
|
1292
|
+
this.footerContainer.addChild(this.treeHelp);
|
|
1293
|
+
}
|
|
1263
1294
|
showLabelInput(entryId, currentLabel) {
|
|
1264
1295
|
this.labelInput = new LabelInput(entryId, currentLabel);
|
|
1265
1296
|
this.labelInput.onSubmit = (id, label) => {
|
|
@@ -1268,17 +1299,21 @@ export class TreeSelectorComponent extends Container {
|
|
|
1268
1299
|
this.hideLabelInput();
|
|
1269
1300
|
};
|
|
1270
1301
|
this.labelInput.onCancel = () => this.hideLabelInput();
|
|
1271
|
-
//
|
|
1302
|
+
// Focus only the active editor.
|
|
1303
|
+
this.searchInput.focused = false;
|
|
1272
1304
|
this.labelInput.focused = this._focused;
|
|
1305
|
+
this.titleText.setText(theme.fg("accent", theme.bold("Label")));
|
|
1306
|
+
this.searchInputContainer.clear();
|
|
1273
1307
|
this.treeContainer.clear();
|
|
1274
1308
|
this.labelInputContainer.clear();
|
|
1275
1309
|
this.labelInputContainer.addChild(this.labelInput);
|
|
1310
|
+
this.footerContainer.clear();
|
|
1276
1311
|
}
|
|
1277
1312
|
hideLabelInput() {
|
|
1278
1313
|
this.labelInput = null;
|
|
1314
|
+
this.searchInput.focused = this._focused;
|
|
1279
1315
|
this.labelInputContainer.clear();
|
|
1280
|
-
this.
|
|
1281
|
-
this.treeContainer.addChild(this.treeList);
|
|
1316
|
+
this.restoreTreeContent();
|
|
1282
1317
|
}
|
|
1283
1318
|
handleInput(keyData) {
|
|
1284
1319
|
if (this.labelInput) {
|