@oh-my-pi/pi-coding-agent 3.14.0 → 3.15.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 +79 -0
- package/docs/theme.md +38 -5
- package/examples/sdk/11-sessions.ts +2 -2
- package/package.json +7 -4
- package/src/cli/file-processor.ts +51 -2
- package/src/cli/plugin-cli.ts +25 -19
- package/src/cli/update-cli.ts +4 -3
- package/src/core/agent-session.ts +31 -4
- package/src/core/compaction/branch-summarization.ts +4 -32
- package/src/core/compaction/compaction.ts +6 -84
- package/src/core/compaction/utils.ts +2 -3
- package/src/core/custom-tools/types.ts +2 -0
- package/src/core/export-html/index.ts +1 -1
- package/src/core/hooks/tool-wrapper.ts +0 -1
- package/src/core/hooks/types.ts +2 -2
- package/src/core/plugins/doctor.ts +9 -1
- package/src/core/sdk.ts +2 -1
- package/src/core/session-manager.ts +518 -40
- package/src/core/settings-manager.ts +174 -0
- package/src/core/system-prompt.ts +9 -14
- package/src/core/title-generator.ts +2 -8
- package/src/core/tools/ask.ts +19 -37
- package/src/core/tools/bash.ts +2 -37
- package/src/core/tools/edit.ts +2 -9
- package/src/core/tools/exa/render.ts +52 -48
- package/src/core/tools/find.ts +10 -8
- package/src/core/tools/grep.ts +45 -17
- package/src/core/tools/ls.ts +22 -2
- package/src/core/tools/lsp/clients/biome-client.ts +207 -0
- package/src/core/tools/lsp/clients/index.ts +49 -0
- package/src/core/tools/lsp/clients/lsp-linter-client.ts +98 -0
- package/src/core/tools/lsp/config.ts +3 -0
- package/src/core/tools/lsp/index.ts +107 -55
- package/src/core/tools/lsp/render.ts +192 -79
- package/src/core/tools/lsp/types.ts +27 -0
- package/src/core/tools/lsp/utils.ts +62 -22
- package/src/core/tools/notebook.ts +9 -1
- package/src/core/tools/output.ts +37 -14
- package/src/core/tools/read.ts +349 -34
- package/src/core/tools/renderers.ts +290 -89
- package/src/core/tools/review.ts +12 -5
- package/src/core/tools/task/agents.ts +5 -5
- package/src/core/tools/task/commands.ts +3 -3
- package/src/core/tools/task/executor.ts +33 -1
- package/src/core/tools/task/index.ts +93 -6
- package/src/core/tools/task/render.ts +147 -66
- package/src/core/tools/task/types.ts +14 -9
- package/src/core/tools/web-fetch.ts +242 -103
- package/src/core/tools/web-search/index.ts +64 -20
- package/src/core/tools/web-search/providers/exa.ts +68 -172
- package/src/core/tools/web-search/render.ts +264 -74
- package/src/core/tools/write.ts +2 -8
- package/src/main.ts +10 -6
- package/src/modes/cleanup.ts +23 -0
- package/src/modes/index.ts +9 -4
- package/src/modes/interactive/components/bash-execution.ts +6 -3
- package/src/modes/interactive/components/branch-summary-message.ts +1 -1
- package/src/modes/interactive/components/compaction-summary-message.ts +1 -1
- package/src/modes/interactive/components/dynamic-border.ts +1 -1
- package/src/modes/interactive/components/extensions/extension-dashboard.ts +4 -5
- package/src/modes/interactive/components/extensions/extension-list.ts +18 -16
- package/src/modes/interactive/components/extensions/inspector-panel.ts +8 -8
- package/src/modes/interactive/components/hook-message.ts +2 -2
- package/src/modes/interactive/components/hook-selector.ts +1 -1
- package/src/modes/interactive/components/model-selector.ts +22 -9
- package/src/modes/interactive/components/oauth-selector.ts +20 -4
- package/src/modes/interactive/components/plugin-settings.ts +4 -2
- package/src/modes/interactive/components/session-selector.ts +9 -6
- package/src/modes/interactive/components/settings-defs.ts +285 -1
- package/src/modes/interactive/components/settings-selector.ts +176 -3
- package/src/modes/interactive/components/status-line/index.ts +4 -0
- package/src/modes/interactive/components/status-line/presets.ts +94 -0
- package/src/modes/interactive/components/status-line/segments.ts +350 -0
- package/src/modes/interactive/components/status-line/separators.ts +55 -0
- package/src/modes/interactive/components/status-line/types.ts +81 -0
- package/src/modes/interactive/components/status-line-segment-editor.ts +357 -0
- package/src/modes/interactive/components/status-line.ts +170 -223
- package/src/modes/interactive/components/tool-execution.ts +446 -211
- package/src/modes/interactive/components/tree-selector.ts +17 -6
- package/src/modes/interactive/components/ttsr-notification.ts +4 -4
- package/src/modes/interactive/components/welcome.ts +27 -19
- package/src/modes/interactive/interactive-mode.ts +98 -13
- package/src/modes/interactive/theme/dark.json +3 -2
- package/src/modes/interactive/theme/defaults/dark-arctic.json +111 -0
- package/src/modes/interactive/theme/defaults/dark-catppuccin.json +106 -0
- package/src/modes/interactive/theme/defaults/dark-cyberpunk.json +109 -0
- package/src/modes/interactive/theme/defaults/dark-dracula.json +105 -0
- package/src/modes/interactive/theme/defaults/dark-forest.json +103 -0
- package/src/modes/interactive/theme/defaults/dark-github.json +112 -0
- package/src/modes/interactive/theme/defaults/dark-gruvbox.json +119 -0
- package/src/modes/interactive/theme/defaults/dark-monochrome.json +101 -0
- package/src/modes/interactive/theme/defaults/dark-monokai.json +105 -0
- package/src/modes/interactive/theme/defaults/dark-nord.json +104 -0
- package/src/modes/interactive/theme/defaults/dark-ocean.json +108 -0
- package/src/modes/interactive/theme/defaults/dark-one.json +107 -0
- package/src/modes/interactive/theme/defaults/dark-retro.json +99 -0
- package/src/modes/interactive/theme/defaults/dark-rose-pine.json +95 -0
- package/src/modes/interactive/theme/defaults/dark-solarized.json +96 -0
- package/src/modes/interactive/theme/defaults/dark-sunset.json +106 -0
- package/src/modes/interactive/theme/defaults/dark-synthwave.json +102 -0
- package/src/modes/interactive/theme/defaults/dark-tokyo-night.json +108 -0
- package/src/modes/interactive/theme/defaults/index.ts +67 -0
- package/src/modes/interactive/theme/defaults/light-arctic.json +106 -0
- package/src/modes/interactive/theme/defaults/light-catppuccin.json +105 -0
- package/src/modes/interactive/theme/defaults/light-cyberpunk.json +103 -0
- package/src/modes/interactive/theme/defaults/light-forest.json +107 -0
- package/src/modes/interactive/theme/defaults/light-github.json +114 -0
- package/src/modes/interactive/theme/defaults/light-gruvbox.json +115 -0
- package/src/modes/interactive/theme/defaults/light-monochrome.json +100 -0
- package/src/modes/interactive/theme/defaults/light-ocean.json +106 -0
- package/src/modes/interactive/theme/defaults/light-one.json +105 -0
- package/src/modes/interactive/theme/defaults/light-retro.json +105 -0
- package/src/modes/interactive/theme/defaults/light-solarized.json +101 -0
- package/src/modes/interactive/theme/defaults/light-sunset.json +106 -0
- package/src/modes/interactive/theme/defaults/light-synthwave.json +105 -0
- package/src/modes/interactive/theme/defaults/light-tokyo-night.json +118 -0
- package/src/modes/interactive/theme/light.json +3 -2
- package/src/modes/interactive/theme/theme-schema.json +120 -4
- package/src/modes/interactive/theme/theme.ts +1228 -14
- package/src/prompts/branch-summary-preamble.md +3 -0
- package/src/prompts/branch-summary.md +28 -0
- package/src/prompts/compaction-summary.md +34 -0
- package/src/prompts/compaction-turn-prefix.md +16 -0
- package/src/prompts/compaction-update-summary.md +41 -0
- package/src/prompts/init.md +30 -0
- package/src/{core/tools/task/bundled-agents → prompts}/reviewer.md +6 -0
- package/src/prompts/summarization-system.md +3 -0
- package/src/prompts/system-prompt.md +27 -0
- package/src/{core/tools/task/bundled-agents → prompts}/task.md +2 -0
- package/src/prompts/title-system.md +8 -0
- package/src/prompts/tools/ask.md +24 -0
- package/src/prompts/tools/bash.md +23 -0
- package/src/prompts/tools/edit.md +9 -0
- package/src/prompts/tools/find.md +6 -0
- package/src/prompts/tools/grep.md +12 -0
- package/src/prompts/tools/lsp.md +14 -0
- package/src/prompts/tools/output.md +23 -0
- package/src/prompts/tools/read.md +25 -0
- package/src/prompts/tools/web-fetch.md +8 -0
- package/src/prompts/tools/web-search.md +10 -0
- package/src/prompts/tools/write.md +10 -0
- package/src/commands/init.md +0 -20
- /package/src/{core/tools/task/bundled-commands → prompts}/architect-plan.md +0 -0
- /package/src/{core/tools/task/bundled-agents → prompts}/browser.md +0 -0
- /package/src/{core/tools/task/bundled-agents → prompts}/explore.md +0 -0
- /package/src/{core/tools/task/bundled-commands → prompts}/implement-with-critic.md +0 -0
- /package/src/{core/tools/task/bundled-commands → prompts}/implement.md +0 -0
- /package/src/{core/tools/task/bundled-agents → prompts}/plan.md +0 -0
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Status Line Segment Editor
|
|
3
|
+
*
|
|
4
|
+
* Interactive component for configuring status line segments.
|
|
5
|
+
* - Three-column layout: Left | Right | Disabled
|
|
6
|
+
* - Space: Toggle segment visibility (disabled ↔ left)
|
|
7
|
+
* - Tab: Cycle segment between columns (left → right → disabled → left)
|
|
8
|
+
* - Shift+J/K: Reorder segment within column
|
|
9
|
+
* - Live preview shown in the actual status line above
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { Container, isArrowDown, isArrowUp, isEnter, isEscape, isShiftTab, isTab } from "@oh-my-pi/pi-tui";
|
|
13
|
+
import type { StatusLineSegmentId } from "../../../core/settings-manager";
|
|
14
|
+
import { theme } from "../theme/theme";
|
|
15
|
+
import { ALL_SEGMENT_IDS } from "./status-line/segments";
|
|
16
|
+
|
|
17
|
+
// Segment display names and short descriptions
|
|
18
|
+
const SEGMENT_INFO: Record<StatusLineSegmentId, { label: string; short: string }> = {
|
|
19
|
+
pi: { label: "Pi", short: "π icon" },
|
|
20
|
+
model: { label: "Model", short: "model name" },
|
|
21
|
+
path: { label: "Path", short: "working dir" },
|
|
22
|
+
git: { label: "Git", short: "branch/status" },
|
|
23
|
+
subagents: { label: "Agents", short: "subagent count" },
|
|
24
|
+
token_in: { label: "Tokens In", short: "input tokens" },
|
|
25
|
+
token_out: { label: "Tokens Out", short: "output tokens" },
|
|
26
|
+
token_total: { label: "Tokens", short: "total tokens" },
|
|
27
|
+
cost: { label: "Cost", short: "session cost" },
|
|
28
|
+
context_pct: { label: "Context %", short: "context usage" },
|
|
29
|
+
context_total: { label: "Context", short: "context window" },
|
|
30
|
+
time_spent: { label: "Elapsed", short: "session time" },
|
|
31
|
+
time: { label: "Clock", short: "current time" },
|
|
32
|
+
session: { label: "Session", short: "session ID" },
|
|
33
|
+
hostname: { label: "Host", short: "hostname" },
|
|
34
|
+
cache_read: { label: "Cache ↓", short: "cache read" },
|
|
35
|
+
cache_write: { label: "Cache ↑", short: "cache write" },
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
type Column = "left" | "right" | "disabled";
|
|
39
|
+
|
|
40
|
+
interface SegmentState {
|
|
41
|
+
id: StatusLineSegmentId;
|
|
42
|
+
column: Column;
|
|
43
|
+
order: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface SegmentEditorCallbacks {
|
|
47
|
+
onSave: (leftSegments: StatusLineSegmentId[], rightSegments: StatusLineSegmentId[]) => void;
|
|
48
|
+
onCancel: () => void;
|
|
49
|
+
onPreview?: (leftSegments: StatusLineSegmentId[], rightSegments: StatusLineSegmentId[]) => void;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export class StatusLineSegmentEditorComponent extends Container {
|
|
53
|
+
private segments: SegmentState[];
|
|
54
|
+
private selectedIndex: number = 0;
|
|
55
|
+
private focusColumn: "left" | "right" | "disabled" = "left";
|
|
56
|
+
private callbacks: SegmentEditorCallbacks;
|
|
57
|
+
|
|
58
|
+
constructor(
|
|
59
|
+
currentLeft: StatusLineSegmentId[],
|
|
60
|
+
currentRight: StatusLineSegmentId[],
|
|
61
|
+
callbacks: SegmentEditorCallbacks,
|
|
62
|
+
) {
|
|
63
|
+
super();
|
|
64
|
+
this.callbacks = callbacks;
|
|
65
|
+
|
|
66
|
+
// Initialize segment states
|
|
67
|
+
this.segments = [];
|
|
68
|
+
const usedIds = new Set<StatusLineSegmentId>();
|
|
69
|
+
|
|
70
|
+
// Add left segments in order
|
|
71
|
+
for (let i = 0; i < currentLeft.length; i++) {
|
|
72
|
+
const id = currentLeft[i];
|
|
73
|
+
this.segments.push({ id, column: "left", order: i });
|
|
74
|
+
usedIds.add(id);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Add right segments in order
|
|
78
|
+
for (let i = 0; i < currentRight.length; i++) {
|
|
79
|
+
const id = currentRight[i];
|
|
80
|
+
this.segments.push({ id, column: "right", order: i });
|
|
81
|
+
usedIds.add(id);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Add remaining segments as disabled
|
|
85
|
+
for (const id of ALL_SEGMENT_IDS) {
|
|
86
|
+
if (!usedIds.has(id)) {
|
|
87
|
+
this.segments.push({ id, column: "disabled", order: 999 });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Trigger initial preview
|
|
92
|
+
this.triggerPreview();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private getSegmentsForColumn(column: Column): SegmentState[] {
|
|
96
|
+
return this.segments.filter((s) => s.column === column).sort((a, b) => a.order - b.order);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private getCurrentColumnSegments(): SegmentState[] {
|
|
100
|
+
return this.getSegmentsForColumn(this.focusColumn);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private triggerPreview(): void {
|
|
104
|
+
const left = this.getSegmentsForColumn("left").map((s) => s.id);
|
|
105
|
+
const right = this.getSegmentsForColumn("right").map((s) => s.id);
|
|
106
|
+
this.callbacks.onPreview?.(left, right);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
handleInput(data: string): void {
|
|
110
|
+
const columnSegments = this.getCurrentColumnSegments();
|
|
111
|
+
|
|
112
|
+
if (isArrowUp(data) || data === "k") {
|
|
113
|
+
// Move selection up within column, or jump to previous column
|
|
114
|
+
if (this.selectedIndex > 0) {
|
|
115
|
+
this.selectedIndex--;
|
|
116
|
+
} else {
|
|
117
|
+
// Jump to previous column
|
|
118
|
+
if (this.focusColumn === "disabled") {
|
|
119
|
+
const rightSegs = this.getSegmentsForColumn("right");
|
|
120
|
+
if (rightSegs.length > 0) {
|
|
121
|
+
this.focusColumn = "right";
|
|
122
|
+
this.selectedIndex = rightSegs.length - 1;
|
|
123
|
+
} else {
|
|
124
|
+
const leftSegs = this.getSegmentsForColumn("left");
|
|
125
|
+
if (leftSegs.length > 0) {
|
|
126
|
+
this.focusColumn = "left";
|
|
127
|
+
this.selectedIndex = leftSegs.length - 1;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
} else if (this.focusColumn === "right") {
|
|
131
|
+
const leftSegs = this.getSegmentsForColumn("left");
|
|
132
|
+
if (leftSegs.length > 0) {
|
|
133
|
+
this.focusColumn = "left";
|
|
134
|
+
this.selectedIndex = leftSegs.length - 1;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
} else if (isArrowDown(data) || data === "j") {
|
|
139
|
+
// Move selection down within column, or jump to next column
|
|
140
|
+
if (this.selectedIndex < columnSegments.length - 1) {
|
|
141
|
+
this.selectedIndex++;
|
|
142
|
+
} else {
|
|
143
|
+
// Jump to next column
|
|
144
|
+
if (this.focusColumn === "left") {
|
|
145
|
+
const rightSegs = this.getSegmentsForColumn("right");
|
|
146
|
+
if (rightSegs.length > 0) {
|
|
147
|
+
this.focusColumn = "right";
|
|
148
|
+
this.selectedIndex = 0;
|
|
149
|
+
} else {
|
|
150
|
+
const disabledSegs = this.getSegmentsForColumn("disabled");
|
|
151
|
+
if (disabledSegs.length > 0) {
|
|
152
|
+
this.focusColumn = "disabled";
|
|
153
|
+
this.selectedIndex = 0;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
} else if (this.focusColumn === "right") {
|
|
157
|
+
const disabledSegs = this.getSegmentsForColumn("disabled");
|
|
158
|
+
if (disabledSegs.length > 0) {
|
|
159
|
+
this.focusColumn = "disabled";
|
|
160
|
+
this.selectedIndex = 0;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
} else if (isTab(data)) {
|
|
165
|
+
// Cycle segment: left → right → disabled → left
|
|
166
|
+
const seg = columnSegments[this.selectedIndex];
|
|
167
|
+
if (seg) {
|
|
168
|
+
const oldColumn = seg.column;
|
|
169
|
+
if (seg.column === "left") {
|
|
170
|
+
seg.column = "right";
|
|
171
|
+
seg.order = this.getSegmentsForColumn("right").length;
|
|
172
|
+
} else if (seg.column === "right") {
|
|
173
|
+
seg.column = "disabled";
|
|
174
|
+
seg.order = 999;
|
|
175
|
+
} else {
|
|
176
|
+
seg.column = "left";
|
|
177
|
+
seg.order = this.getSegmentsForColumn("left").length;
|
|
178
|
+
}
|
|
179
|
+
// Recompact orders in old column
|
|
180
|
+
this.recompactColumn(oldColumn);
|
|
181
|
+
this.triggerPreview();
|
|
182
|
+
}
|
|
183
|
+
} else if (isShiftTab(data)) {
|
|
184
|
+
// Reverse cycle: left ← right ← disabled ← left
|
|
185
|
+
const seg = columnSegments[this.selectedIndex];
|
|
186
|
+
if (seg) {
|
|
187
|
+
const oldColumn = seg.column;
|
|
188
|
+
if (seg.column === "left") {
|
|
189
|
+
seg.column = "disabled";
|
|
190
|
+
seg.order = 999;
|
|
191
|
+
} else if (seg.column === "right") {
|
|
192
|
+
seg.column = "left";
|
|
193
|
+
seg.order = this.getSegmentsForColumn("left").length;
|
|
194
|
+
} else {
|
|
195
|
+
seg.column = "right";
|
|
196
|
+
seg.order = this.getSegmentsForColumn("right").length;
|
|
197
|
+
}
|
|
198
|
+
this.recompactColumn(oldColumn);
|
|
199
|
+
this.triggerPreview();
|
|
200
|
+
}
|
|
201
|
+
} else if (data === " ") {
|
|
202
|
+
// Quick toggle: disabled ↔ left
|
|
203
|
+
const seg = columnSegments[this.selectedIndex];
|
|
204
|
+
if (seg) {
|
|
205
|
+
const oldColumn = seg.column;
|
|
206
|
+
if (seg.column === "disabled") {
|
|
207
|
+
seg.column = "left";
|
|
208
|
+
seg.order = this.getSegmentsForColumn("left").length;
|
|
209
|
+
} else {
|
|
210
|
+
seg.column = "disabled";
|
|
211
|
+
seg.order = 999;
|
|
212
|
+
}
|
|
213
|
+
this.recompactColumn(oldColumn);
|
|
214
|
+
this.triggerPreview();
|
|
215
|
+
}
|
|
216
|
+
} else if (data === "K") {
|
|
217
|
+
// Move segment up in order (Shift+K)
|
|
218
|
+
const seg = columnSegments[this.selectedIndex];
|
|
219
|
+
if (seg && seg.column !== "disabled" && this.selectedIndex > 0) {
|
|
220
|
+
const prevSeg = columnSegments[this.selectedIndex - 1];
|
|
221
|
+
const tempOrder = seg.order;
|
|
222
|
+
seg.order = prevSeg.order;
|
|
223
|
+
prevSeg.order = tempOrder;
|
|
224
|
+
this.selectedIndex--;
|
|
225
|
+
this.triggerPreview();
|
|
226
|
+
}
|
|
227
|
+
} else if (data === "J") {
|
|
228
|
+
// Move segment down in order (Shift+J)
|
|
229
|
+
const seg = columnSegments[this.selectedIndex];
|
|
230
|
+
if (seg && seg.column !== "disabled" && this.selectedIndex < columnSegments.length - 1) {
|
|
231
|
+
const nextSeg = columnSegments[this.selectedIndex + 1];
|
|
232
|
+
const tempOrder = seg.order;
|
|
233
|
+
seg.order = nextSeg.order;
|
|
234
|
+
nextSeg.order = tempOrder;
|
|
235
|
+
this.selectedIndex++;
|
|
236
|
+
this.triggerPreview();
|
|
237
|
+
}
|
|
238
|
+
} else if (isEnter(data)) {
|
|
239
|
+
const left = this.getSegmentsForColumn("left").map((s) => s.id);
|
|
240
|
+
const right = this.getSegmentsForColumn("right").map((s) => s.id);
|
|
241
|
+
this.callbacks.onSave(left, right);
|
|
242
|
+
} else if (isEscape(data)) {
|
|
243
|
+
this.callbacks.onCancel();
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private recompactColumn(column: Column): void {
|
|
248
|
+
if (column === "disabled") return;
|
|
249
|
+
const segs = this.getSegmentsForColumn(column);
|
|
250
|
+
for (let i = 0; i < segs.length; i++) {
|
|
251
|
+
segs[i].order = i;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
render(width: number): string[] {
|
|
256
|
+
const lines: string[] = [];
|
|
257
|
+
|
|
258
|
+
// Title with live preview indicator
|
|
259
|
+
lines.push(theme.bold(theme.fg("accent", "Configure Status Line Segments")));
|
|
260
|
+
lines.push(theme.fg("dim", "Live preview shown in status line above"));
|
|
261
|
+
lines.push("");
|
|
262
|
+
|
|
263
|
+
// Key bindings
|
|
264
|
+
lines.push(
|
|
265
|
+
theme.fg("muted", "Space") +
|
|
266
|
+
" toggle " +
|
|
267
|
+
theme.fg("muted", "Tab/S-Tab") +
|
|
268
|
+
" cycle column " +
|
|
269
|
+
theme.fg("muted", "J/K") +
|
|
270
|
+
" reorder " +
|
|
271
|
+
theme.fg("muted", "Enter") +
|
|
272
|
+
" save " +
|
|
273
|
+
theme.fg("muted", "Esc") +
|
|
274
|
+
" cancel",
|
|
275
|
+
);
|
|
276
|
+
lines.push("");
|
|
277
|
+
|
|
278
|
+
// Get segments for each column
|
|
279
|
+
const leftSegs = this.getSegmentsForColumn("left");
|
|
280
|
+
const rightSegs = this.getSegmentsForColumn("right");
|
|
281
|
+
const disabledSegs = this.getSegmentsForColumn("disabled");
|
|
282
|
+
|
|
283
|
+
// Calculate column widths
|
|
284
|
+
const colWidth = Math.max(18, Math.floor((width - 6) / 3));
|
|
285
|
+
|
|
286
|
+
// Column headers
|
|
287
|
+
const activeMarker = theme.nav.back;
|
|
288
|
+
const leftHeader =
|
|
289
|
+
this.focusColumn === "left"
|
|
290
|
+
? theme.bold(theme.fg("accent", `${activeMarker} LEFT`))
|
|
291
|
+
: theme.fg("muted", " LEFT");
|
|
292
|
+
const rightHeader =
|
|
293
|
+
this.focusColumn === "right"
|
|
294
|
+
? theme.bold(theme.fg("accent", `${activeMarker} RIGHT`))
|
|
295
|
+
: theme.fg("muted", " RIGHT");
|
|
296
|
+
const disabledHeader =
|
|
297
|
+
this.focusColumn === "disabled"
|
|
298
|
+
? theme.bold(theme.fg("accent", `${activeMarker} AVAILABLE`))
|
|
299
|
+
: theme.fg("muted", " AVAILABLE");
|
|
300
|
+
|
|
301
|
+
lines.push(`${leftHeader.padEnd(colWidth + 8)}${rightHeader.padEnd(colWidth + 8)}${disabledHeader}`);
|
|
302
|
+
lines.push(theme.fg("dim", theme.boxRound.horizontal.repeat(Math.min(width - 2, colWidth * 3 + 6))));
|
|
303
|
+
|
|
304
|
+
// Render rows
|
|
305
|
+
const maxRows = Math.max(leftSegs.length, rightSegs.length, disabledSegs.length, 1);
|
|
306
|
+
|
|
307
|
+
for (let row = 0; row < maxRows; row++) {
|
|
308
|
+
let line = "";
|
|
309
|
+
|
|
310
|
+
// Left column
|
|
311
|
+
line += this.renderSegmentCell(leftSegs[row], "left", row, colWidth);
|
|
312
|
+
|
|
313
|
+
// Right column
|
|
314
|
+
line += this.renderSegmentCell(rightSegs[row], "right", row, colWidth);
|
|
315
|
+
|
|
316
|
+
// Disabled column
|
|
317
|
+
line += this.renderSegmentCell(disabledSegs[row], "disabled", row, colWidth);
|
|
318
|
+
|
|
319
|
+
lines.push(line);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Summary line
|
|
323
|
+
lines.push("");
|
|
324
|
+
const leftCount = leftSegs.length;
|
|
325
|
+
const rightCount = rightSegs.length;
|
|
326
|
+
const summary = theme.fg(
|
|
327
|
+
"dim",
|
|
328
|
+
`${leftCount} left ${theme.sep.dot} ${rightCount} right ${theme.sep.dot} ${disabledSegs.length} available`,
|
|
329
|
+
);
|
|
330
|
+
lines.push(summary);
|
|
331
|
+
|
|
332
|
+
return lines;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
private renderSegmentCell(seg: SegmentState | undefined, column: Column, row: number, colWidth: number): string {
|
|
336
|
+
if (!seg) {
|
|
337
|
+
return "".padEnd(colWidth + 2);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const isSelected = this.focusColumn === column && this.selectedIndex === row;
|
|
341
|
+
const info = SEGMENT_INFO[seg.id];
|
|
342
|
+
const label = info?.label ?? seg.id;
|
|
343
|
+
|
|
344
|
+
let text: string;
|
|
345
|
+
if (isSelected) {
|
|
346
|
+
text = theme.bg("selectedBg", theme.fg("text", ` ${label} `));
|
|
347
|
+
} else if (column === "disabled") {
|
|
348
|
+
text = theme.fg("dim", ` ${label}`);
|
|
349
|
+
} else {
|
|
350
|
+
text = theme.fg("text", ` ${label}`);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Pad to column width (accounting for ANSI codes)
|
|
354
|
+
const padding = colWidth - label.length - 1;
|
|
355
|
+
return text + " ".repeat(Math.max(0, padding));
|
|
356
|
+
}
|
|
357
|
+
}
|