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