@juliusbrussee/caveman-tui 0.65.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +767 -0
- package/dist/autocomplete.d.ts +52 -0
- package/dist/autocomplete.d.ts.map +1 -0
- package/dist/autocomplete.js +623 -0
- package/dist/autocomplete.js.map +1 -0
- package/dist/chord.d.ts +57 -0
- package/dist/chord.d.ts.map +1 -0
- package/dist/chord.js +97 -0
- package/dist/chord.js.map +1 -0
- package/dist/color-depth.d.ts +17 -0
- package/dist/color-depth.d.ts.map +1 -0
- package/dist/color-depth.js +147 -0
- package/dist/color-depth.js.map +1 -0
- package/dist/components/Chapters.d.ts +41 -0
- package/dist/components/Chapters.d.ts.map +1 -0
- package/dist/components/Chapters.js +103 -0
- package/dist/components/Chapters.js.map +1 -0
- package/dist/components/DiffView.d.ts +75 -0
- package/dist/components/DiffView.d.ts.map +1 -0
- package/dist/components/DiffView.js +170 -0
- package/dist/components/DiffView.js.map +1 -0
- package/dist/components/StatusLine.d.ts +135 -0
- package/dist/components/StatusLine.d.ts.map +1 -0
- package/dist/components/StatusLine.js +133 -0
- package/dist/components/StatusLine.js.map +1 -0
- package/dist/components/SubagentOverlay.d.ts +63 -0
- package/dist/components/SubagentOverlay.d.ts.map +1 -0
- package/dist/components/SubagentOverlay.js +124 -0
- package/dist/components/SubagentOverlay.js.map +1 -0
- package/dist/components/box.d.ts +22 -0
- package/dist/components/box.d.ts.map +1 -0
- package/dist/components/box.js +104 -0
- package/dist/components/box.js.map +1 -0
- package/dist/components/cancellable-loader.d.ts +22 -0
- package/dist/components/cancellable-loader.d.ts.map +1 -0
- package/dist/components/cancellable-loader.js +35 -0
- package/dist/components/cancellable-loader.js.map +1 -0
- package/dist/components/editor.d.ts +244 -0
- package/dist/components/editor.d.ts.map +1 -0
- package/dist/components/editor.js +1861 -0
- package/dist/components/editor.js.map +1 -0
- package/dist/components/grouped-select-list.d.ts +60 -0
- package/dist/components/grouped-select-list.d.ts.map +1 -0
- package/dist/components/grouped-select-list.js +312 -0
- package/dist/components/grouped-select-list.js.map +1 -0
- package/dist/components/image.d.ts +28 -0
- package/dist/components/image.d.ts.map +1 -0
- package/dist/components/image.js +69 -0
- package/dist/components/image.js.map +1 -0
- package/dist/components/input.d.ts +37 -0
- package/dist/components/input.d.ts.map +1 -0
- package/dist/components/input.js +426 -0
- package/dist/components/input.js.map +1 -0
- package/dist/components/loader.d.ts +26 -0
- package/dist/components/loader.d.ts.map +1 -0
- package/dist/components/loader.js +67 -0
- package/dist/components/loader.js.map +1 -0
- package/dist/components/markdown.d.ts +95 -0
- package/dist/components/markdown.d.ts.map +1 -0
- package/dist/components/markdown.js +663 -0
- package/dist/components/markdown.js.map +1 -0
- package/dist/components/select-list.d.ts +50 -0
- package/dist/components/select-list.d.ts.map +1 -0
- package/dist/components/select-list.js +159 -0
- package/dist/components/select-list.js.map +1 -0
- package/dist/components/settings-list.d.ts +50 -0
- package/dist/components/settings-list.d.ts.map +1 -0
- package/dist/components/settings-list.js +185 -0
- package/dist/components/settings-list.js.map +1 -0
- package/dist/components/spacer.d.ts +12 -0
- package/dist/components/spacer.d.ts.map +1 -0
- package/dist/components/spacer.js +23 -0
- package/dist/components/spacer.js.map +1 -0
- package/dist/components/spinner.d.ts +35 -0
- package/dist/components/spinner.d.ts.map +1 -0
- package/dist/components/spinner.js +77 -0
- package/dist/components/spinner.js.map +1 -0
- package/dist/components/streaming-markdown.d.ts +39 -0
- package/dist/components/streaming-markdown.d.ts.map +1 -0
- package/dist/components/streaming-markdown.js +137 -0
- package/dist/components/streaming-markdown.js.map +1 -0
- package/dist/components/text.d.ts +19 -0
- package/dist/components/text.d.ts.map +1 -0
- package/dist/components/text.js +89 -0
- package/dist/components/text.js.map +1 -0
- package/dist/components/truncated-text.d.ts +13 -0
- package/dist/components/truncated-text.d.ts.map +1 -0
- package/dist/components/truncated-text.js +51 -0
- package/dist/components/truncated-text.js.map +1 -0
- package/dist/editor-component.d.ts +39 -0
- package/dist/editor-component.d.ts.map +1 -0
- package/dist/editor-component.js +2 -0
- package/dist/editor-component.js.map +1 -0
- package/dist/fuzzy.d.ts +16 -0
- package/dist/fuzzy.d.ts.map +1 -0
- package/dist/fuzzy.js +107 -0
- package/dist/fuzzy.js.map +1 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +59 -0
- package/dist/index.js.map +1 -0
- package/dist/keybindings.d.ts +193 -0
- package/dist/keybindings.d.ts.map +1 -0
- package/dist/keybindings.js +174 -0
- package/dist/keybindings.js.map +1 -0
- package/dist/keys.d.ts +170 -0
- package/dist/keys.d.ts.map +1 -0
- package/dist/keys.js +1124 -0
- package/dist/keys.js.map +1 -0
- package/dist/kill-ring.d.ts +28 -0
- package/dist/kill-ring.d.ts.map +1 -0
- package/dist/kill-ring.js +44 -0
- package/dist/kill-ring.js.map +1 -0
- package/dist/notifications.d.ts +35 -0
- package/dist/notifications.d.ts.map +1 -0
- package/dist/notifications.js +62 -0
- package/dist/notifications.js.map +1 -0
- package/dist/osc52.d.ts +28 -0
- package/dist/osc52.d.ts.map +1 -0
- package/dist/osc52.js +53 -0
- package/dist/osc52.js.map +1 -0
- package/dist/scroll-buffer.d.ts +67 -0
- package/dist/scroll-buffer.d.ts.map +1 -0
- package/dist/scroll-buffer.js +222 -0
- package/dist/scroll-buffer.js.map +1 -0
- package/dist/spinners.d.ts +26 -0
- package/dist/spinners.d.ts.map +1 -0
- package/dist/spinners.js +136 -0
- package/dist/spinners.js.map +1 -0
- package/dist/stdin-buffer.d.ts +48 -0
- package/dist/stdin-buffer.d.ts.map +1 -0
- package/dist/stdin-buffer.js +317 -0
- package/dist/stdin-buffer.js.map +1 -0
- package/dist/sync-output.d.ts +58 -0
- package/dist/sync-output.d.ts.map +1 -0
- package/dist/sync-output.js +79 -0
- package/dist/sync-output.js.map +1 -0
- package/dist/terminal-detect.d.ts +66 -0
- package/dist/terminal-detect.d.ts.map +1 -0
- package/dist/terminal-detect.js +315 -0
- package/dist/terminal-detect.js.map +1 -0
- package/dist/terminal-image.d.ts +68 -0
- package/dist/terminal-image.d.ts.map +1 -0
- package/dist/terminal-image.js +288 -0
- package/dist/terminal-image.js.map +1 -0
- package/dist/terminal.d.ts +105 -0
- package/dist/terminal.d.ts.map +1 -0
- package/dist/terminal.js +427 -0
- package/dist/terminal.js.map +1 -0
- package/dist/tui.d.ts +268 -0
- package/dist/tui.d.ts.map +1 -0
- package/dist/tui.js +1161 -0
- package/dist/tui.js.map +1 -0
- package/dist/undo-stack.d.ts +17 -0
- package/dist/undo-stack.d.ts.map +1 -0
- package/dist/undo-stack.js +25 -0
- package/dist/undo-stack.js.map +1 -0
- package/dist/utils.d.ts +78 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +960 -0
- package/dist/utils.js.map +1 -0
- package/package.json +59 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import { getKeybindings } from "../keybindings.js";
|
|
2
|
+
import { matchesKey } from "../keys.js";
|
|
3
|
+
import { truncateToWidth } from "../utils.js";
|
|
4
|
+
export class GroupedSelectList {
|
|
5
|
+
options;
|
|
6
|
+
groups = [];
|
|
7
|
+
folded = new Set();
|
|
8
|
+
seen = new Set();
|
|
9
|
+
rows = [];
|
|
10
|
+
selectedIndex = 0;
|
|
11
|
+
scrollOffset = 0;
|
|
12
|
+
onSelect;
|
|
13
|
+
onCancel;
|
|
14
|
+
onSelectionChange;
|
|
15
|
+
onToggleGroup;
|
|
16
|
+
constructor(options) {
|
|
17
|
+
this.options = options;
|
|
18
|
+
}
|
|
19
|
+
setGroups(groups, preserveSelection = false) {
|
|
20
|
+
const previousSelection = preserveSelection ? this.currentSelection() : null;
|
|
21
|
+
const previouslyFolded = new Set(this.folded);
|
|
22
|
+
this.groups = groups;
|
|
23
|
+
// Carry over fold state for already-known groups; only apply
|
|
24
|
+
// `initiallyCollapsed` the first time a group id appears.
|
|
25
|
+
this.folded = new Set();
|
|
26
|
+
for (const g of groups) {
|
|
27
|
+
if (this.seen.has(g.id)) {
|
|
28
|
+
if (previouslyFolded.has(g.id))
|
|
29
|
+
this.folded.add(g.id);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
this.seen.add(g.id);
|
|
33
|
+
if (g.initiallyCollapsed)
|
|
34
|
+
this.folded.add(g.id);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
this.flatten();
|
|
38
|
+
if (previousSelection) {
|
|
39
|
+
this.restoreSelection(previousSelection);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
this.selectedIndex = this.firstSelectableIndex();
|
|
43
|
+
}
|
|
44
|
+
this.notifyChange();
|
|
45
|
+
}
|
|
46
|
+
flatten() {
|
|
47
|
+
const rows = [];
|
|
48
|
+
for (let g = 0; g < this.groups.length; g++) {
|
|
49
|
+
const group = this.groups[g];
|
|
50
|
+
rows.push({ kind: "header", groupIndex: g });
|
|
51
|
+
if (this.folded.has(group.id) || group.disabled)
|
|
52
|
+
continue;
|
|
53
|
+
if (group.items.length === 0) {
|
|
54
|
+
if (group.emptyHint)
|
|
55
|
+
rows.push({ kind: "empty", groupIndex: g });
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
for (let i = 0; i < group.items.length; i++) {
|
|
59
|
+
rows.push({ kind: "item", groupIndex: g, itemIndex: i, item: group.items[i] });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
this.rows = rows;
|
|
63
|
+
if (this.selectedIndex >= rows.length) {
|
|
64
|
+
this.selectedIndex = Math.max(0, rows.length - 1);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
firstSelectableIndex() {
|
|
68
|
+
for (let i = 0; i < this.rows.length; i++) {
|
|
69
|
+
if (this.rows[i].kind !== "empty")
|
|
70
|
+
return i;
|
|
71
|
+
}
|
|
72
|
+
return 0;
|
|
73
|
+
}
|
|
74
|
+
currentSelection() {
|
|
75
|
+
const row = this.rows[this.selectedIndex];
|
|
76
|
+
if (!row)
|
|
77
|
+
return null;
|
|
78
|
+
const group = this.groups[row.groupIndex];
|
|
79
|
+
if (!group)
|
|
80
|
+
return null;
|
|
81
|
+
if (row.kind === "header")
|
|
82
|
+
return { kind: "header", group };
|
|
83
|
+
if (row.kind === "item")
|
|
84
|
+
return { kind: "item", group, item: row.item };
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
restoreSelection(target) {
|
|
88
|
+
// Try exact match first, then fall back to header of the same group, then top.
|
|
89
|
+
for (let i = 0; i < this.rows.length; i++) {
|
|
90
|
+
const row = this.rows[i];
|
|
91
|
+
const group = this.groups[row.groupIndex];
|
|
92
|
+
if (target.kind === "item" &&
|
|
93
|
+
row.kind === "item" &&
|
|
94
|
+
group.id === target.group.id &&
|
|
95
|
+
row.item === target.item) {
|
|
96
|
+
this.selectedIndex = i;
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (target.kind === "header" && row.kind === "header" && group.id === target.group.id) {
|
|
100
|
+
this.selectedIndex = i;
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
for (let i = 0; i < this.rows.length; i++) {
|
|
105
|
+
const row = this.rows[i];
|
|
106
|
+
if (row.kind === "header" && this.groups[row.groupIndex]?.id === target.group.id) {
|
|
107
|
+
this.selectedIndex = i;
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
this.selectedIndex = this.firstSelectableIndex();
|
|
112
|
+
}
|
|
113
|
+
notifyChange() {
|
|
114
|
+
this.onSelectionChange?.(this.currentSelection());
|
|
115
|
+
}
|
|
116
|
+
getRows() {
|
|
117
|
+
return this.rows.length;
|
|
118
|
+
}
|
|
119
|
+
getMaxVisible() {
|
|
120
|
+
return this.options.maxVisible;
|
|
121
|
+
}
|
|
122
|
+
isExpanded(groupId) {
|
|
123
|
+
return !this.folded.has(groupId);
|
|
124
|
+
}
|
|
125
|
+
toggleGroup(groupId) {
|
|
126
|
+
const group = this.groups.find((g) => g.id === groupId);
|
|
127
|
+
if (!group || group.disabled)
|
|
128
|
+
return;
|
|
129
|
+
if (this.folded.has(groupId))
|
|
130
|
+
this.folded.delete(groupId);
|
|
131
|
+
else
|
|
132
|
+
this.folded.add(groupId);
|
|
133
|
+
const previous = this.currentSelection();
|
|
134
|
+
this.flatten();
|
|
135
|
+
if (previous)
|
|
136
|
+
this.restoreSelection(previous);
|
|
137
|
+
this.onToggleGroup?.(group, !this.folded.has(groupId));
|
|
138
|
+
this.notifyChange();
|
|
139
|
+
}
|
|
140
|
+
expandGroup(groupId) {
|
|
141
|
+
if (!this.folded.has(groupId))
|
|
142
|
+
return;
|
|
143
|
+
this.toggleGroup(groupId);
|
|
144
|
+
}
|
|
145
|
+
collapseGroup(groupId) {
|
|
146
|
+
if (this.folded.has(groupId))
|
|
147
|
+
return;
|
|
148
|
+
this.toggleGroup(groupId);
|
|
149
|
+
}
|
|
150
|
+
expandAll() {
|
|
151
|
+
this.folded.clear();
|
|
152
|
+
const previous = this.currentSelection();
|
|
153
|
+
this.flatten();
|
|
154
|
+
if (previous)
|
|
155
|
+
this.restoreSelection(previous);
|
|
156
|
+
this.notifyChange();
|
|
157
|
+
}
|
|
158
|
+
collapseAll() {
|
|
159
|
+
for (const g of this.groups)
|
|
160
|
+
this.folded.add(g.id);
|
|
161
|
+
const previous = this.currentSelection();
|
|
162
|
+
this.flatten();
|
|
163
|
+
if (previous)
|
|
164
|
+
this.restoreSelection(previous);
|
|
165
|
+
this.notifyChange();
|
|
166
|
+
}
|
|
167
|
+
moveBy(delta) {
|
|
168
|
+
if (this.rows.length === 0)
|
|
169
|
+
return;
|
|
170
|
+
let i = this.selectedIndex;
|
|
171
|
+
const step = delta > 0 ? 1 : -1;
|
|
172
|
+
const total = Math.abs(delta);
|
|
173
|
+
for (let k = 0; k < total; k++) {
|
|
174
|
+
let next = i + step;
|
|
175
|
+
// Skip empty rows when navigating with arrow keys.
|
|
176
|
+
while (this.rows[next]?.kind === "empty")
|
|
177
|
+
next += step;
|
|
178
|
+
if (next < 0)
|
|
179
|
+
next = this.rows.length - 1;
|
|
180
|
+
if (next >= this.rows.length)
|
|
181
|
+
next = 0;
|
|
182
|
+
while (this.rows[next]?.kind === "empty")
|
|
183
|
+
next += step;
|
|
184
|
+
if (next < 0)
|
|
185
|
+
next = this.rows.length - 1;
|
|
186
|
+
if (next >= this.rows.length)
|
|
187
|
+
next = 0;
|
|
188
|
+
i = next;
|
|
189
|
+
}
|
|
190
|
+
if (i !== this.selectedIndex) {
|
|
191
|
+
this.selectedIndex = i;
|
|
192
|
+
this.notifyChange();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
moveToFirstOfGroup(groupIndex) {
|
|
196
|
+
for (let i = 0; i < this.rows.length; i++) {
|
|
197
|
+
if (this.rows[i].groupIndex === groupIndex && this.rows[i].kind === "header") {
|
|
198
|
+
this.selectedIndex = i;
|
|
199
|
+
this.notifyChange();
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
getSelectedGroupIndex() {
|
|
205
|
+
const row = this.rows[this.selectedIndex];
|
|
206
|
+
return row ? row.groupIndex : null;
|
|
207
|
+
}
|
|
208
|
+
currentRowKind() {
|
|
209
|
+
return this.rows[this.selectedIndex]?.kind ?? null;
|
|
210
|
+
}
|
|
211
|
+
invalidate() { }
|
|
212
|
+
render(width) {
|
|
213
|
+
if (this.rows.length === 0) {
|
|
214
|
+
return [this.options.noMatch ?? " No matches"];
|
|
215
|
+
}
|
|
216
|
+
const maxVisible = Math.max(1, this.options.maxVisible);
|
|
217
|
+
// Keep the selected row centered when there are more rows than fit on screen.
|
|
218
|
+
const half = Math.floor(maxVisible / 2);
|
|
219
|
+
const desiredOffset = Math.max(0, Math.min(this.selectedIndex - half, this.rows.length - maxVisible));
|
|
220
|
+
this.scrollOffset = Math.max(0, desiredOffset);
|
|
221
|
+
const start = this.scrollOffset;
|
|
222
|
+
const end = Math.min(start + maxVisible, this.rows.length);
|
|
223
|
+
const out = [];
|
|
224
|
+
for (let i = start; i < end; i++) {
|
|
225
|
+
const row = this.rows[i];
|
|
226
|
+
const group = this.groups[row.groupIndex];
|
|
227
|
+
const selected = i === this.selectedIndex;
|
|
228
|
+
let line;
|
|
229
|
+
if (row.kind === "header") {
|
|
230
|
+
const expanded = !this.folded.has(group.id) && !group.disabled;
|
|
231
|
+
line = this.options.renderHeader(group, selected, expanded);
|
|
232
|
+
}
|
|
233
|
+
else if (row.kind === "item") {
|
|
234
|
+
line = this.options.renderItem(row.item, group, selected);
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
line = this.options.renderEmpty?.(group) ?? "";
|
|
238
|
+
}
|
|
239
|
+
out.push(truncateToWidth(line, Math.max(1, width), ""));
|
|
240
|
+
}
|
|
241
|
+
if (start > 0 || end < this.rows.length) {
|
|
242
|
+
out.push(truncateToWidth(` (${this.selectedIndex + 1}/${this.rows.length})`, Math.max(1, width), ""));
|
|
243
|
+
}
|
|
244
|
+
return out;
|
|
245
|
+
}
|
|
246
|
+
handleInput(keyData) {
|
|
247
|
+
const kb = getKeybindings();
|
|
248
|
+
if (kb.matches(keyData, "tui.select.up")) {
|
|
249
|
+
this.moveBy(-1);
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
if (kb.matches(keyData, "tui.select.down")) {
|
|
253
|
+
this.moveBy(1);
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
if (kb.matches(keyData, "tui.select.pageUp")) {
|
|
257
|
+
this.moveBy(-Math.max(1, Math.floor(this.options.maxVisible / 2)));
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
if (kb.matches(keyData, "tui.select.pageDown")) {
|
|
261
|
+
this.moveBy(Math.max(1, Math.floor(this.options.maxVisible / 2)));
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
if (matchesKey(keyData, "left")) {
|
|
265
|
+
const row = this.rows[this.selectedIndex];
|
|
266
|
+
if (row) {
|
|
267
|
+
const group = this.groups[row.groupIndex];
|
|
268
|
+
if (group) {
|
|
269
|
+
if (this.folded.has(group.id) || row.kind !== "header") {
|
|
270
|
+
this.moveToFirstOfGroup(row.groupIndex);
|
|
271
|
+
}
|
|
272
|
+
this.collapseGroup(group.id);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
277
|
+
if (matchesKey(keyData, "right")) {
|
|
278
|
+
const row = this.rows[this.selectedIndex];
|
|
279
|
+
if (row) {
|
|
280
|
+
const group = this.groups[row.groupIndex];
|
|
281
|
+
if (group) {
|
|
282
|
+
if (this.folded.has(group.id)) {
|
|
283
|
+
this.expandGroup(group.id);
|
|
284
|
+
this.moveBy(1);
|
|
285
|
+
}
|
|
286
|
+
else if (row.kind === "header" && group.items.length > 0) {
|
|
287
|
+
this.moveBy(1);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return true;
|
|
292
|
+
}
|
|
293
|
+
if (kb.matches(keyData, "tui.select.confirm")) {
|
|
294
|
+
const sel = this.currentSelection();
|
|
295
|
+
if (!sel)
|
|
296
|
+
return true;
|
|
297
|
+
if (sel.kind === "header") {
|
|
298
|
+
if (!sel.group.disabled)
|
|
299
|
+
this.toggleGroup(sel.group.id);
|
|
300
|
+
return true;
|
|
301
|
+
}
|
|
302
|
+
this.onSelect?.(sel);
|
|
303
|
+
return true;
|
|
304
|
+
}
|
|
305
|
+
if (kb.matches(keyData, "tui.select.cancel")) {
|
|
306
|
+
this.onCancel?.();
|
|
307
|
+
return true;
|
|
308
|
+
}
|
|
309
|
+
return false;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
//# sourceMappingURL=grouped-select-list.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grouped-select-list.js","sourceRoot":"","sources":["../../src/components/grouped-select-list.ts"],"names":[],"mappings":"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","sourcesContent":["import { getKeybindings } from \"../keybindings.js\";\nimport { matchesKey } from \"../keys.js\";\nimport type { Component } from \"../tui.js\";\nimport { truncateToWidth } from \"../utils.js\";\n\nexport interface GroupedSelectGroup<T> {\n\tid: string;\n\theader: string;\n\titems: T[];\n\tinitiallyCollapsed?: boolean;\n\temptyHint?: string;\n\tdisabled?: boolean;\n}\n\nexport interface GroupedSelectListOptions<T> {\n\tmaxVisible: number;\n\trenderHeader: (group: GroupedSelectGroup<T>, isSelected: boolean, expanded: boolean) => string;\n\trenderItem: (item: T, group: GroupedSelectGroup<T>, isSelected: boolean) => string;\n\trenderEmpty?: (group: GroupedSelectGroup<T>) => string;\n\tnoMatch?: string;\n}\n\ninterface HeaderRow {\n\tkind: \"header\";\n\tgroupIndex: number;\n}\ninterface ItemRow<T> {\n\tkind: \"item\";\n\tgroupIndex: number;\n\titemIndex: number;\n\titem: T;\n}\ninterface EmptyRow {\n\tkind: \"empty\";\n\tgroupIndex: number;\n}\ntype Row<T> = HeaderRow | ItemRow<T> | EmptyRow;\n\nexport type GroupedSelection<T> =\n\t| { kind: \"header\"; group: GroupedSelectGroup<T> }\n\t| { kind: \"item\"; group: GroupedSelectGroup<T>; item: T };\n\nexport class GroupedSelectList<T> implements Component {\n\tprivate groups: GroupedSelectGroup<T>[] = [];\n\tprivate folded: Set<string> = new Set();\n\tprivate seen: Set<string> = new Set();\n\tprivate rows: Row<T>[] = [];\n\tprivate selectedIndex = 0;\n\tprivate scrollOffset = 0;\n\n\tpublic onSelect?: (selection: GroupedSelection<T>) => void;\n\tpublic onCancel?: () => void;\n\tpublic onSelectionChange?: (selection: GroupedSelection<T> | null) => void;\n\tpublic onToggleGroup?: (group: GroupedSelectGroup<T>, expanded: boolean) => void;\n\n\tconstructor(private options: GroupedSelectListOptions<T>) {}\n\n\tsetGroups(groups: GroupedSelectGroup<T>[], preserveSelection: boolean = false): void {\n\t\tconst previousSelection = preserveSelection ? this.currentSelection() : null;\n\t\tconst previouslyFolded = new Set(this.folded);\n\t\tthis.groups = groups;\n\t\t// Carry over fold state for already-known groups; only apply\n\t\t// `initiallyCollapsed` the first time a group id appears.\n\t\tthis.folded = new Set();\n\t\tfor (const g of groups) {\n\t\t\tif (this.seen.has(g.id)) {\n\t\t\t\tif (previouslyFolded.has(g.id)) this.folded.add(g.id);\n\t\t\t} else {\n\t\t\t\tthis.seen.add(g.id);\n\t\t\t\tif (g.initiallyCollapsed) this.folded.add(g.id);\n\t\t\t}\n\t\t}\n\t\tthis.flatten();\n\t\tif (previousSelection) {\n\t\t\tthis.restoreSelection(previousSelection);\n\t\t} else {\n\t\t\tthis.selectedIndex = this.firstSelectableIndex();\n\t\t}\n\t\tthis.notifyChange();\n\t}\n\n\tprivate flatten(): void {\n\t\tconst rows: Row<T>[] = [];\n\t\tfor (let g = 0; g < this.groups.length; g++) {\n\t\t\tconst group = this.groups[g]!;\n\t\t\trows.push({ kind: \"header\", groupIndex: g });\n\t\t\tif (this.folded.has(group.id) || group.disabled) continue;\n\t\t\tif (group.items.length === 0) {\n\t\t\t\tif (group.emptyHint) rows.push({ kind: \"empty\", groupIndex: g });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tfor (let i = 0; i < group.items.length; i++) {\n\t\t\t\trows.push({ kind: \"item\", groupIndex: g, itemIndex: i, item: group.items[i]! });\n\t\t\t}\n\t\t}\n\t\tthis.rows = rows;\n\t\tif (this.selectedIndex >= rows.length) {\n\t\t\tthis.selectedIndex = Math.max(0, rows.length - 1);\n\t\t}\n\t}\n\n\tprivate firstSelectableIndex(): number {\n\t\tfor (let i = 0; i < this.rows.length; i++) {\n\t\t\tif (this.rows[i]!.kind !== \"empty\") return i;\n\t\t}\n\t\treturn 0;\n\t}\n\n\tcurrentSelection(): GroupedSelection<T> | null {\n\t\tconst row = this.rows[this.selectedIndex];\n\t\tif (!row) return null;\n\t\tconst group = this.groups[row.groupIndex];\n\t\tif (!group) return null;\n\t\tif (row.kind === \"header\") return { kind: \"header\", group };\n\t\tif (row.kind === \"item\") return { kind: \"item\", group, item: row.item };\n\t\treturn null;\n\t}\n\n\tprivate restoreSelection(target: GroupedSelection<T>): void {\n\t\t// Try exact match first, then fall back to header of the same group, then top.\n\t\tfor (let i = 0; i < this.rows.length; i++) {\n\t\t\tconst row = this.rows[i]!;\n\t\t\tconst group = this.groups[row.groupIndex]!;\n\t\t\tif (\n\t\t\t\ttarget.kind === \"item\" &&\n\t\t\t\trow.kind === \"item\" &&\n\t\t\t\tgroup.id === target.group.id &&\n\t\t\t\trow.item === target.item\n\t\t\t) {\n\t\t\t\tthis.selectedIndex = i;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (target.kind === \"header\" && row.kind === \"header\" && group.id === target.group.id) {\n\t\t\t\tthis.selectedIndex = i;\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\tfor (let i = 0; i < this.rows.length; i++) {\n\t\t\tconst row = this.rows[i]!;\n\t\t\tif (row.kind === \"header\" && this.groups[row.groupIndex]?.id === target.group.id) {\n\t\t\t\tthis.selectedIndex = i;\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\tthis.selectedIndex = this.firstSelectableIndex();\n\t}\n\n\tprivate notifyChange(): void {\n\t\tthis.onSelectionChange?.(this.currentSelection());\n\t}\n\n\tgetRows(): number {\n\t\treturn this.rows.length;\n\t}\n\n\tgetMaxVisible(): number {\n\t\treturn this.options.maxVisible;\n\t}\n\n\tisExpanded(groupId: string): boolean {\n\t\treturn !this.folded.has(groupId);\n\t}\n\n\ttoggleGroup(groupId: string): void {\n\t\tconst group = this.groups.find((g) => g.id === groupId);\n\t\tif (!group || group.disabled) return;\n\t\tif (this.folded.has(groupId)) this.folded.delete(groupId);\n\t\telse this.folded.add(groupId);\n\t\tconst previous = this.currentSelection();\n\t\tthis.flatten();\n\t\tif (previous) this.restoreSelection(previous);\n\t\tthis.onToggleGroup?.(group, !this.folded.has(groupId));\n\t\tthis.notifyChange();\n\t}\n\n\texpandGroup(groupId: string): void {\n\t\tif (!this.folded.has(groupId)) return;\n\t\tthis.toggleGroup(groupId);\n\t}\n\n\tcollapseGroup(groupId: string): void {\n\t\tif (this.folded.has(groupId)) return;\n\t\tthis.toggleGroup(groupId);\n\t}\n\n\texpandAll(): void {\n\t\tthis.folded.clear();\n\t\tconst previous = this.currentSelection();\n\t\tthis.flatten();\n\t\tif (previous) this.restoreSelection(previous);\n\t\tthis.notifyChange();\n\t}\n\n\tcollapseAll(): void {\n\t\tfor (const g of this.groups) this.folded.add(g.id);\n\t\tconst previous = this.currentSelection();\n\t\tthis.flatten();\n\t\tif (previous) this.restoreSelection(previous);\n\t\tthis.notifyChange();\n\t}\n\n\tmoveBy(delta: number): void {\n\t\tif (this.rows.length === 0) return;\n\t\tlet i = this.selectedIndex;\n\t\tconst step = delta > 0 ? 1 : -1;\n\t\tconst total = Math.abs(delta);\n\t\tfor (let k = 0; k < total; k++) {\n\t\t\tlet next = i + step;\n\t\t\t// Skip empty rows when navigating with arrow keys.\n\t\t\twhile (this.rows[next]?.kind === \"empty\") next += step;\n\t\t\tif (next < 0) next = this.rows.length - 1;\n\t\t\tif (next >= this.rows.length) next = 0;\n\t\t\twhile (this.rows[next]?.kind === \"empty\") next += step;\n\t\t\tif (next < 0) next = this.rows.length - 1;\n\t\t\tif (next >= this.rows.length) next = 0;\n\t\t\ti = next;\n\t\t}\n\t\tif (i !== this.selectedIndex) {\n\t\t\tthis.selectedIndex = i;\n\t\t\tthis.notifyChange();\n\t\t}\n\t}\n\n\tmoveToFirstOfGroup(groupIndex: number): void {\n\t\tfor (let i = 0; i < this.rows.length; i++) {\n\t\t\tif (this.rows[i]!.groupIndex === groupIndex && this.rows[i]!.kind === \"header\") {\n\t\t\t\tthis.selectedIndex = i;\n\t\t\t\tthis.notifyChange();\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\n\tgetSelectedGroupIndex(): number | null {\n\t\tconst row = this.rows[this.selectedIndex];\n\t\treturn row ? row.groupIndex : null;\n\t}\n\n\tcurrentRowKind(): \"header\" | \"item\" | \"empty\" | null {\n\t\treturn this.rows[this.selectedIndex]?.kind ?? null;\n\t}\n\n\tinvalidate(): void {}\n\n\trender(width: number): string[] {\n\t\tif (this.rows.length === 0) {\n\t\t\treturn [this.options.noMatch ?? \" No matches\"];\n\t\t}\n\n\t\tconst maxVisible = Math.max(1, this.options.maxVisible);\n\t\t// Keep the selected row centered when there are more rows than fit on screen.\n\t\tconst half = Math.floor(maxVisible / 2);\n\t\tconst desiredOffset = Math.max(0, Math.min(this.selectedIndex - half, this.rows.length - maxVisible));\n\t\tthis.scrollOffset = Math.max(0, desiredOffset);\n\t\tconst start = this.scrollOffset;\n\t\tconst end = Math.min(start + maxVisible, this.rows.length);\n\n\t\tconst out: string[] = [];\n\t\tfor (let i = start; i < end; i++) {\n\t\t\tconst row = this.rows[i]!;\n\t\t\tconst group = this.groups[row.groupIndex]!;\n\t\t\tconst selected = i === this.selectedIndex;\n\t\t\tlet line: string;\n\t\t\tif (row.kind === \"header\") {\n\t\t\t\tconst expanded = !this.folded.has(group.id) && !group.disabled;\n\t\t\t\tline = this.options.renderHeader(group, selected, expanded);\n\t\t\t} else if (row.kind === \"item\") {\n\t\t\t\tline = this.options.renderItem(row.item, group, selected);\n\t\t\t} else {\n\t\t\t\tline = this.options.renderEmpty?.(group) ?? \"\";\n\t\t\t}\n\t\t\tout.push(truncateToWidth(line, Math.max(1, width), \"\"));\n\t\t}\n\n\t\tif (start > 0 || end < this.rows.length) {\n\t\t\tout.push(truncateToWidth(` (${this.selectedIndex + 1}/${this.rows.length})`, Math.max(1, width), \"\"));\n\t\t}\n\n\t\treturn out;\n\t}\n\n\thandleInput(keyData: string): boolean {\n\t\tconst kb = getKeybindings();\n\t\tif (kb.matches(keyData, \"tui.select.up\")) {\n\t\t\tthis.moveBy(-1);\n\t\t\treturn true;\n\t\t}\n\t\tif (kb.matches(keyData, \"tui.select.down\")) {\n\t\t\tthis.moveBy(1);\n\t\t\treturn true;\n\t\t}\n\t\tif (kb.matches(keyData, \"tui.select.pageUp\")) {\n\t\t\tthis.moveBy(-Math.max(1, Math.floor(this.options.maxVisible / 2)));\n\t\t\treturn true;\n\t\t}\n\t\tif (kb.matches(keyData, \"tui.select.pageDown\")) {\n\t\t\tthis.moveBy(Math.max(1, Math.floor(this.options.maxVisible / 2)));\n\t\t\treturn true;\n\t\t}\n\t\tif (matchesKey(keyData, \"left\")) {\n\t\t\tconst row = this.rows[this.selectedIndex];\n\t\t\tif (row) {\n\t\t\t\tconst group = this.groups[row.groupIndex];\n\t\t\t\tif (group) {\n\t\t\t\t\tif (this.folded.has(group.id) || row.kind !== \"header\") {\n\t\t\t\t\t\tthis.moveToFirstOfGroup(row.groupIndex);\n\t\t\t\t\t}\n\t\t\t\t\tthis.collapseGroup(group.id);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\t\tif (matchesKey(keyData, \"right\")) {\n\t\t\tconst row = this.rows[this.selectedIndex];\n\t\t\tif (row) {\n\t\t\t\tconst group = this.groups[row.groupIndex];\n\t\t\t\tif (group) {\n\t\t\t\t\tif (this.folded.has(group.id)) {\n\t\t\t\t\t\tthis.expandGroup(group.id);\n\t\t\t\t\t\tthis.moveBy(1);\n\t\t\t\t\t} else if (row.kind === \"header\" && group.items.length > 0) {\n\t\t\t\t\t\tthis.moveBy(1);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\t\tif (kb.matches(keyData, \"tui.select.confirm\")) {\n\t\t\tconst sel = this.currentSelection();\n\t\t\tif (!sel) return true;\n\t\t\tif (sel.kind === \"header\") {\n\t\t\t\tif (!sel.group.disabled) this.toggleGroup(sel.group.id);\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tthis.onSelect?.(sel);\n\t\t\treturn true;\n\t\t}\n\t\tif (kb.matches(keyData, \"tui.select.cancel\")) {\n\t\t\tthis.onCancel?.();\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n}\n"]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type ImageDimensions } from "../terminal-image.js";
|
|
2
|
+
import type { Component } from "../tui.js";
|
|
3
|
+
export interface ImageTheme {
|
|
4
|
+
fallbackColor: (str: string) => string;
|
|
5
|
+
}
|
|
6
|
+
export interface ImageOptions {
|
|
7
|
+
maxWidthCells?: number;
|
|
8
|
+
maxHeightCells?: number;
|
|
9
|
+
filename?: string;
|
|
10
|
+
/** Kitty image ID. If provided, reuses this ID (for animations/updates). */
|
|
11
|
+
imageId?: number;
|
|
12
|
+
}
|
|
13
|
+
export declare class Image implements Component {
|
|
14
|
+
private base64Data;
|
|
15
|
+
private mimeType;
|
|
16
|
+
private dimensions;
|
|
17
|
+
private theme;
|
|
18
|
+
private options;
|
|
19
|
+
private imageId?;
|
|
20
|
+
private cachedLines?;
|
|
21
|
+
private cachedWidth?;
|
|
22
|
+
constructor(base64Data: string, mimeType: string, theme: ImageTheme, options?: ImageOptions, dimensions?: ImageDimensions);
|
|
23
|
+
/** Get the Kitty image ID used by this image (if any). */
|
|
24
|
+
getImageId(): number | undefined;
|
|
25
|
+
invalidate(): void;
|
|
26
|
+
render(width: number): string[];
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=image.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../../src/components/image.ts"],"names":[],"mappings":"AAAA,OAAO,EAGN,KAAK,eAAe,EAGpB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAE3C,MAAM,WAAW,UAAU;IAC1B,aAAa,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;CACvC;AAED,MAAM,WAAW,YAAY;IAC5B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,KAAM,YAAW,SAAS;IACtC,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,UAAU,CAAkB;IACpC,OAAO,CAAC,KAAK,CAAa;IAC1B,OAAO,CAAC,OAAO,CAAe;IAC9B,OAAO,CAAC,OAAO,CAAC,CAAS;IAEzB,OAAO,CAAC,WAAW,CAAC,CAAW;IAC/B,OAAO,CAAC,WAAW,CAAC,CAAS;IAE7B,YACC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,UAAU,EACjB,OAAO,GAAE,YAAiB,EAC1B,UAAU,CAAC,EAAE,eAAe,EAQ5B;IAED,0DAA0D;IAC1D,UAAU,IAAI,MAAM,GAAG,SAAS,CAE/B;IAED,UAAU,IAAI,IAAI,CAGjB;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CA6C9B;CACD","sourcesContent":["import {\n\tgetCapabilities,\n\tgetImageDimensions,\n\ttype ImageDimensions,\n\timageFallback,\n\trenderImage,\n} from \"../terminal-image.js\";\nimport type { Component } from \"../tui.js\";\n\nexport interface ImageTheme {\n\tfallbackColor: (str: string) => string;\n}\n\nexport interface ImageOptions {\n\tmaxWidthCells?: number;\n\tmaxHeightCells?: number;\n\tfilename?: string;\n\t/** Kitty image ID. If provided, reuses this ID (for animations/updates). */\n\timageId?: number;\n}\n\nexport class Image implements Component {\n\tprivate base64Data: string;\n\tprivate mimeType: string;\n\tprivate dimensions: ImageDimensions;\n\tprivate theme: ImageTheme;\n\tprivate options: ImageOptions;\n\tprivate imageId?: number;\n\n\tprivate cachedLines?: string[];\n\tprivate cachedWidth?: number;\n\n\tconstructor(\n\t\tbase64Data: string,\n\t\tmimeType: string,\n\t\ttheme: ImageTheme,\n\t\toptions: ImageOptions = {},\n\t\tdimensions?: ImageDimensions,\n\t) {\n\t\tthis.base64Data = base64Data;\n\t\tthis.mimeType = mimeType;\n\t\tthis.theme = theme;\n\t\tthis.options = options;\n\t\tthis.dimensions = dimensions || getImageDimensions(base64Data, mimeType) || { widthPx: 800, heightPx: 600 };\n\t\tthis.imageId = options.imageId;\n\t}\n\n\t/** Get the Kitty image ID used by this image (if any). */\n\tgetImageId(): number | undefined {\n\t\treturn this.imageId;\n\t}\n\n\tinvalidate(): void {\n\t\tthis.cachedLines = undefined;\n\t\tthis.cachedWidth = undefined;\n\t}\n\n\trender(width: number): string[] {\n\t\tif (this.cachedLines && this.cachedWidth === width) {\n\t\t\treturn this.cachedLines;\n\t\t}\n\n\t\tconst maxWidth = Math.min(width - 2, this.options.maxWidthCells ?? 60);\n\n\t\tconst caps = getCapabilities();\n\t\tlet lines: string[];\n\n\t\tif (caps.images) {\n\t\t\tconst result = renderImage(this.base64Data, this.dimensions, {\n\t\t\t\tmaxWidthCells: maxWidth,\n\t\t\t\timageId: this.imageId,\n\t\t\t});\n\n\t\t\tif (result) {\n\t\t\t\t// Store the image ID for later cleanup\n\t\t\t\tif (result.imageId) {\n\t\t\t\t\tthis.imageId = result.imageId;\n\t\t\t\t}\n\n\t\t\t\t// Return `rows` lines so TUI accounts for image height\n\t\t\t\t// First (rows-1) lines are empty (TUI clears them)\n\t\t\t\t// Last line: move cursor back up, then output image sequence\n\t\t\t\tlines = [];\n\t\t\t\tfor (let i = 0; i < result.rows - 1; i++) {\n\t\t\t\t\tlines.push(\"\");\n\t\t\t\t}\n\t\t\t\t// Move cursor up to first row, then output image\n\t\t\t\tconst moveUp = result.rows > 1 ? `\\x1b[${result.rows - 1}A` : \"\";\n\t\t\t\tlines.push(moveUp + result.sequence);\n\t\t\t} else {\n\t\t\t\tconst fallback = imageFallback(this.mimeType, this.dimensions, this.options.filename);\n\t\t\t\tlines = [this.theme.fallbackColor(fallback)];\n\t\t\t}\n\t\t} else {\n\t\t\tconst fallback = imageFallback(this.mimeType, this.dimensions, this.options.filename);\n\t\t\tlines = [this.theme.fallbackColor(fallback)];\n\t\t}\n\n\t\tthis.cachedLines = lines;\n\t\tthis.cachedWidth = width;\n\n\t\treturn lines;\n\t}\n}\n"]}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { getCapabilities, getImageDimensions, imageFallback, renderImage, } from "../terminal-image.js";
|
|
2
|
+
export class Image {
|
|
3
|
+
base64Data;
|
|
4
|
+
mimeType;
|
|
5
|
+
dimensions;
|
|
6
|
+
theme;
|
|
7
|
+
options;
|
|
8
|
+
imageId;
|
|
9
|
+
cachedLines;
|
|
10
|
+
cachedWidth;
|
|
11
|
+
constructor(base64Data, mimeType, theme, options = {}, dimensions) {
|
|
12
|
+
this.base64Data = base64Data;
|
|
13
|
+
this.mimeType = mimeType;
|
|
14
|
+
this.theme = theme;
|
|
15
|
+
this.options = options;
|
|
16
|
+
this.dimensions = dimensions || getImageDimensions(base64Data, mimeType) || { widthPx: 800, heightPx: 600 };
|
|
17
|
+
this.imageId = options.imageId;
|
|
18
|
+
}
|
|
19
|
+
/** Get the Kitty image ID used by this image (if any). */
|
|
20
|
+
getImageId() {
|
|
21
|
+
return this.imageId;
|
|
22
|
+
}
|
|
23
|
+
invalidate() {
|
|
24
|
+
this.cachedLines = undefined;
|
|
25
|
+
this.cachedWidth = undefined;
|
|
26
|
+
}
|
|
27
|
+
render(width) {
|
|
28
|
+
if (this.cachedLines && this.cachedWidth === width) {
|
|
29
|
+
return this.cachedLines;
|
|
30
|
+
}
|
|
31
|
+
const maxWidth = Math.min(width - 2, this.options.maxWidthCells ?? 60);
|
|
32
|
+
const caps = getCapabilities();
|
|
33
|
+
let lines;
|
|
34
|
+
if (caps.images) {
|
|
35
|
+
const result = renderImage(this.base64Data, this.dimensions, {
|
|
36
|
+
maxWidthCells: maxWidth,
|
|
37
|
+
imageId: this.imageId,
|
|
38
|
+
});
|
|
39
|
+
if (result) {
|
|
40
|
+
// Store the image ID for later cleanup
|
|
41
|
+
if (result.imageId) {
|
|
42
|
+
this.imageId = result.imageId;
|
|
43
|
+
}
|
|
44
|
+
// Return `rows` lines so TUI accounts for image height
|
|
45
|
+
// First (rows-1) lines are empty (TUI clears them)
|
|
46
|
+
// Last line: move cursor back up, then output image sequence
|
|
47
|
+
lines = [];
|
|
48
|
+
for (let i = 0; i < result.rows - 1; i++) {
|
|
49
|
+
lines.push("");
|
|
50
|
+
}
|
|
51
|
+
// Move cursor up to first row, then output image
|
|
52
|
+
const moveUp = result.rows > 1 ? `\x1b[${result.rows - 1}A` : "";
|
|
53
|
+
lines.push(moveUp + result.sequence);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
const fallback = imageFallback(this.mimeType, this.dimensions, this.options.filename);
|
|
57
|
+
lines = [this.theme.fallbackColor(fallback)];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
const fallback = imageFallback(this.mimeType, this.dimensions, this.options.filename);
|
|
62
|
+
lines = [this.theme.fallbackColor(fallback)];
|
|
63
|
+
}
|
|
64
|
+
this.cachedLines = lines;
|
|
65
|
+
this.cachedWidth = width;
|
|
66
|
+
return lines;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=image.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image.js","sourceRoot":"","sources":["../../src/components/image.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\tgetCapabilities,\n\tgetImageDimensions,\n\ttype ImageDimensions,\n\timageFallback,\n\trenderImage,\n} from \"../terminal-image.js\";\nimport type { Component } from \"../tui.js\";\n\nexport interface ImageTheme {\n\tfallbackColor: (str: string) => string;\n}\n\nexport interface ImageOptions {\n\tmaxWidthCells?: number;\n\tmaxHeightCells?: number;\n\tfilename?: string;\n\t/** Kitty image ID. If provided, reuses this ID (for animations/updates). */\n\timageId?: number;\n}\n\nexport class Image implements Component {\n\tprivate base64Data: string;\n\tprivate mimeType: string;\n\tprivate dimensions: ImageDimensions;\n\tprivate theme: ImageTheme;\n\tprivate options: ImageOptions;\n\tprivate imageId?: number;\n\n\tprivate cachedLines?: string[];\n\tprivate cachedWidth?: number;\n\n\tconstructor(\n\t\tbase64Data: string,\n\t\tmimeType: string,\n\t\ttheme: ImageTheme,\n\t\toptions: ImageOptions = {},\n\t\tdimensions?: ImageDimensions,\n\t) {\n\t\tthis.base64Data = base64Data;\n\t\tthis.mimeType = mimeType;\n\t\tthis.theme = theme;\n\t\tthis.options = options;\n\t\tthis.dimensions = dimensions || getImageDimensions(base64Data, mimeType) || { widthPx: 800, heightPx: 600 };\n\t\tthis.imageId = options.imageId;\n\t}\n\n\t/** Get the Kitty image ID used by this image (if any). */\n\tgetImageId(): number | undefined {\n\t\treturn this.imageId;\n\t}\n\n\tinvalidate(): void {\n\t\tthis.cachedLines = undefined;\n\t\tthis.cachedWidth = undefined;\n\t}\n\n\trender(width: number): string[] {\n\t\tif (this.cachedLines && this.cachedWidth === width) {\n\t\t\treturn this.cachedLines;\n\t\t}\n\n\t\tconst maxWidth = Math.min(width - 2, this.options.maxWidthCells ?? 60);\n\n\t\tconst caps = getCapabilities();\n\t\tlet lines: string[];\n\n\t\tif (caps.images) {\n\t\t\tconst result = renderImage(this.base64Data, this.dimensions, {\n\t\t\t\tmaxWidthCells: maxWidth,\n\t\t\t\timageId: this.imageId,\n\t\t\t});\n\n\t\t\tif (result) {\n\t\t\t\t// Store the image ID for later cleanup\n\t\t\t\tif (result.imageId) {\n\t\t\t\t\tthis.imageId = result.imageId;\n\t\t\t\t}\n\n\t\t\t\t// Return `rows` lines so TUI accounts for image height\n\t\t\t\t// First (rows-1) lines are empty (TUI clears them)\n\t\t\t\t// Last line: move cursor back up, then output image sequence\n\t\t\t\tlines = [];\n\t\t\t\tfor (let i = 0; i < result.rows - 1; i++) {\n\t\t\t\t\tlines.push(\"\");\n\t\t\t\t}\n\t\t\t\t// Move cursor up to first row, then output image\n\t\t\t\tconst moveUp = result.rows > 1 ? `\\x1b[${result.rows - 1}A` : \"\";\n\t\t\t\tlines.push(moveUp + result.sequence);\n\t\t\t} else {\n\t\t\t\tconst fallback = imageFallback(this.mimeType, this.dimensions, this.options.filename);\n\t\t\t\tlines = [this.theme.fallbackColor(fallback)];\n\t\t\t}\n\t\t} else {\n\t\t\tconst fallback = imageFallback(this.mimeType, this.dimensions, this.options.filename);\n\t\t\tlines = [this.theme.fallbackColor(fallback)];\n\t\t}\n\n\t\tthis.cachedLines = lines;\n\t\tthis.cachedWidth = width;\n\n\t\treturn lines;\n\t}\n}\n"]}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type Component, type Focusable } from "../tui.js";
|
|
2
|
+
/**
|
|
3
|
+
* Input component - single-line text input with horizontal scrolling
|
|
4
|
+
*/
|
|
5
|
+
export declare class Input implements Component, Focusable {
|
|
6
|
+
private value;
|
|
7
|
+
private cursor;
|
|
8
|
+
onSubmit?: (value: string) => void;
|
|
9
|
+
onEscape?: () => void;
|
|
10
|
+
/** Focusable interface - set by TUI when focus changes */
|
|
11
|
+
focused: boolean;
|
|
12
|
+
private pasteBuffer;
|
|
13
|
+
private isInPaste;
|
|
14
|
+
private killRing;
|
|
15
|
+
private lastAction;
|
|
16
|
+
private undoStack;
|
|
17
|
+
getValue(): string;
|
|
18
|
+
setValue(value: string): void;
|
|
19
|
+
handleInput(data: string): void;
|
|
20
|
+
private insertCharacter;
|
|
21
|
+
private handleBackspace;
|
|
22
|
+
private handleForwardDelete;
|
|
23
|
+
private deleteToLineStart;
|
|
24
|
+
private deleteToLineEnd;
|
|
25
|
+
private deleteWordBackwards;
|
|
26
|
+
private deleteWordForward;
|
|
27
|
+
private yank;
|
|
28
|
+
private yankPop;
|
|
29
|
+
private pushUndo;
|
|
30
|
+
private undo;
|
|
31
|
+
private moveWordBackwards;
|
|
32
|
+
private moveWordForwards;
|
|
33
|
+
private handlePaste;
|
|
34
|
+
invalidate(): void;
|
|
35
|
+
render(width: number): string[];
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=input.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../../src/components/input.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,SAAS,EAAiB,KAAK,SAAS,EAAE,MAAM,WAAW,CAAC;AAW1E;;GAEG;AACH,qBAAa,KAAM,YAAW,SAAS,EAAE,SAAS;IACjD,OAAO,CAAC,KAAK,CAAc;IAC3B,OAAO,CAAC,MAAM,CAAa;IACpB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IAE7B,0DAA0D;IAC1D,OAAO,EAAE,OAAO,CAAS;IAGzB,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,SAAS,CAAkB;IAGnC,OAAO,CAAC,QAAQ,CAAkB;IAClC,OAAO,CAAC,UAAU,CAA8C;IAGhE,OAAO,CAAC,SAAS,CAA+B;IAEhD,QAAQ,IAAI,MAAM,CAEjB;IAED,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAG5B;IAED,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAmK9B;IAED,OAAO,CAAC,eAAe;IAWvB,OAAO,CAAC,eAAe;IAavB,OAAO,CAAC,mBAAmB;IAY3B,OAAO,CAAC,iBAAiB;IAUzB,OAAO,CAAC,eAAe;IASvB,OAAO,CAAC,mBAAmB;IAqB3B,OAAO,CAAC,iBAAiB;IAoBzB,OAAO,CAAC,IAAI;IAWZ,OAAO,CAAC,OAAO;IAkBf,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,IAAI;IAQZ,OAAO,CAAC,iBAAiB;IAkCzB,OAAO,CAAC,gBAAgB;IAmCxB,OAAO,CAAC,WAAW;IAYnB,UAAU,IAAI,IAAI,CAEjB;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAoE9B;CACD","sourcesContent":["import { getKeybindings } from \"../keybindings.js\";\nimport { decodeKittyPrintable } from \"../keys.js\";\nimport { KillRing } from \"../kill-ring.js\";\nimport { type Component, CURSOR_MARKER, type Focusable } from \"../tui.js\";\nimport { UndoStack } from \"../undo-stack.js\";\nimport { getSegmenter, isPunctuationChar, isWhitespaceChar, sliceByColumn, visibleWidth } from \"../utils.js\";\n\nconst segmenter = getSegmenter();\n\ninterface InputState {\n\tvalue: string;\n\tcursor: number;\n}\n\n/**\n * Input component - single-line text input with horizontal scrolling\n */\nexport class Input implements Component, Focusable {\n\tprivate value: string = \"\";\n\tprivate cursor: number = 0; // Cursor position in the value\n\tpublic onSubmit?: (value: string) => void;\n\tpublic onEscape?: () => void;\n\n\t/** Focusable interface - set by TUI when focus changes */\n\tfocused: boolean = false;\n\n\t// Bracketed paste mode buffering\n\tprivate pasteBuffer: string = \"\";\n\tprivate isInPaste: boolean = false;\n\n\t// Kill ring for Emacs-style kill/yank operations\n\tprivate killRing = new KillRing();\n\tprivate lastAction: \"kill\" | \"yank\" | \"type-word\" | null = null;\n\n\t// Undo support\n\tprivate undoStack = new UndoStack<InputState>();\n\n\tgetValue(): string {\n\t\treturn this.value;\n\t}\n\n\tsetValue(value: string): void {\n\t\tthis.value = value;\n\t\tthis.cursor = Math.min(this.cursor, value.length);\n\t}\n\n\thandleInput(data: string): void {\n\t\t// Handle bracketed paste mode\n\t\t// Start of paste: \\x1b[200~\n\t\t// End of paste: \\x1b[201~\n\n\t\t// Check if we're starting a bracketed paste\n\t\tif (data.includes(\"\\x1b[200~\")) {\n\t\t\tthis.isInPaste = true;\n\t\t\tthis.pasteBuffer = \"\";\n\t\t\tdata = data.replace(\"\\x1b[200~\", \"\");\n\t\t}\n\n\t\t// If we're in a paste, buffer the data\n\t\tif (this.isInPaste) {\n\t\t\t// Check if this chunk contains the end marker\n\t\t\tthis.pasteBuffer += data;\n\n\t\t\tconst endIndex = this.pasteBuffer.indexOf(\"\\x1b[201~\");\n\t\t\tif (endIndex !== -1) {\n\t\t\t\t// Extract the pasted content\n\t\t\t\tconst pasteContent = this.pasteBuffer.substring(0, endIndex);\n\n\t\t\t\t// Process the complete paste\n\t\t\t\tthis.handlePaste(pasteContent);\n\n\t\t\t\t// Reset paste state\n\t\t\t\tthis.isInPaste = false;\n\n\t\t\t\t// Handle any remaining input after the paste marker\n\t\t\t\tconst remaining = this.pasteBuffer.substring(endIndex + 6); // 6 = length of \\x1b[201~\n\t\t\t\tthis.pasteBuffer = \"\";\n\t\t\t\tif (remaining) {\n\t\t\t\t\tthis.handleInput(remaining);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tconst kb = getKeybindings();\n\n\t\t// Escape/Cancel\n\t\tif (kb.matches(data, \"tui.select.cancel\")) {\n\t\t\tif (this.onEscape) this.onEscape();\n\t\t\treturn;\n\t\t}\n\n\t\t// Undo\n\t\tif (kb.matches(data, \"tui.editor.undo\")) {\n\t\t\tthis.undo();\n\t\t\treturn;\n\t\t}\n\n\t\t// Submit\n\t\tif (kb.matches(data, \"tui.input.submit\") || data === \"\\n\") {\n\t\t\tif (this.onSubmit) this.onSubmit(this.value);\n\t\t\treturn;\n\t\t}\n\n\t\t// Deletion\n\t\tif (kb.matches(data, \"tui.editor.deleteCharBackward\")) {\n\t\t\tthis.handleBackspace();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"tui.editor.deleteCharForward\")) {\n\t\t\tthis.handleForwardDelete();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"tui.editor.deleteWordBackward\")) {\n\t\t\tthis.deleteWordBackwards();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"tui.editor.deleteWordForward\")) {\n\t\t\tthis.deleteWordForward();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"tui.editor.deleteToLineStart\")) {\n\t\t\tthis.deleteToLineStart();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"tui.editor.deleteToLineEnd\")) {\n\t\t\tthis.deleteToLineEnd();\n\t\t\treturn;\n\t\t}\n\n\t\t// Kill ring actions\n\t\tif (kb.matches(data, \"tui.editor.yank\")) {\n\t\t\tthis.yank();\n\t\t\treturn;\n\t\t}\n\t\tif (kb.matches(data, \"tui.editor.yankPop\")) {\n\t\t\tthis.yankPop();\n\t\t\treturn;\n\t\t}\n\n\t\t// Cursor movement\n\t\tif (kb.matches(data, \"tui.editor.cursorLeft\")) {\n\t\t\tthis.lastAction = null;\n\t\t\tif (this.cursor > 0) {\n\t\t\t\tconst beforeCursor = this.value.slice(0, this.cursor);\n\t\t\t\tconst graphemes = [...segmenter.segment(beforeCursor)];\n\t\t\t\tconst lastGrapheme = graphemes[graphemes.length - 1];\n\t\t\t\tthis.cursor -= lastGrapheme ? lastGrapheme.segment.length : 1;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"tui.editor.cursorRight\")) {\n\t\t\tthis.lastAction = null;\n\t\t\tif (this.cursor < this.value.length) {\n\t\t\t\tconst afterCursor = this.value.slice(this.cursor);\n\t\t\t\tconst graphemes = [...segmenter.segment(afterCursor)];\n\t\t\t\tconst firstGrapheme = graphemes[0];\n\t\t\t\tthis.cursor += firstGrapheme ? firstGrapheme.segment.length : 1;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"tui.editor.cursorLineStart\")) {\n\t\t\tthis.lastAction = null;\n\t\t\tthis.cursor = 0;\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"tui.editor.cursorLineEnd\")) {\n\t\t\tthis.lastAction = null;\n\t\t\tthis.cursor = this.value.length;\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"tui.editor.cursorWordLeft\")) {\n\t\t\tthis.moveWordBackwards();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"tui.editor.cursorWordRight\")) {\n\t\t\tthis.moveWordForwards();\n\t\t\treturn;\n\t\t}\n\n\t\t// Kitty CSI-u printable character (e.g. \\x1b[97u for 'a').\n\t\t// Terminals with Kitty protocol flag 1 (disambiguate) send CSI-u for all keys,\n\t\t// including plain printable characters. Decode before the control-char check\n\t\t// since CSI-u sequences contain \\x1b which would be rejected.\n\t\tconst kittyPrintable = decodeKittyPrintable(data);\n\t\tif (kittyPrintable !== undefined) {\n\t\t\tthis.insertCharacter(kittyPrintable);\n\t\t\treturn;\n\t\t}\n\n\t\t// Regular character input - accept printable characters including Unicode,\n\t\t// but reject control characters (C0: 0x00-0x1F, DEL: 0x7F, C1: 0x80-0x9F)\n\t\tconst hasControlChars = [...data].some((ch) => {\n\t\t\tconst code = ch.charCodeAt(0);\n\t\t\treturn code < 32 || code === 0x7f || (code >= 0x80 && code <= 0x9f);\n\t\t});\n\t\tif (!hasControlChars) {\n\t\t\tthis.insertCharacter(data);\n\t\t}\n\t}\n\n\tprivate insertCharacter(char: string): void {\n\t\t// Undo coalescing: consecutive word chars coalesce into one undo unit\n\t\tif (isWhitespaceChar(char) || this.lastAction !== \"type-word\") {\n\t\t\tthis.pushUndo();\n\t\t}\n\t\tthis.lastAction = \"type-word\";\n\n\t\tthis.value = this.value.slice(0, this.cursor) + char + this.value.slice(this.cursor);\n\t\tthis.cursor += char.length;\n\t}\n\n\tprivate handleBackspace(): void {\n\t\tthis.lastAction = null;\n\t\tif (this.cursor > 0) {\n\t\t\tthis.pushUndo();\n\t\t\tconst beforeCursor = this.value.slice(0, this.cursor);\n\t\t\tconst graphemes = [...segmenter.segment(beforeCursor)];\n\t\t\tconst lastGrapheme = graphemes[graphemes.length - 1];\n\t\t\tconst graphemeLength = lastGrapheme ? lastGrapheme.segment.length : 1;\n\t\t\tthis.value = this.value.slice(0, this.cursor - graphemeLength) + this.value.slice(this.cursor);\n\t\t\tthis.cursor -= graphemeLength;\n\t\t}\n\t}\n\n\tprivate handleForwardDelete(): void {\n\t\tthis.lastAction = null;\n\t\tif (this.cursor < this.value.length) {\n\t\t\tthis.pushUndo();\n\t\t\tconst afterCursor = this.value.slice(this.cursor);\n\t\t\tconst graphemes = [...segmenter.segment(afterCursor)];\n\t\t\tconst firstGrapheme = graphemes[0];\n\t\t\tconst graphemeLength = firstGrapheme ? firstGrapheme.segment.length : 1;\n\t\t\tthis.value = this.value.slice(0, this.cursor) + this.value.slice(this.cursor + graphemeLength);\n\t\t}\n\t}\n\n\tprivate deleteToLineStart(): void {\n\t\tif (this.cursor === 0) return;\n\t\tthis.pushUndo();\n\t\tconst deletedText = this.value.slice(0, this.cursor);\n\t\tthis.killRing.push(deletedText, { prepend: true, accumulate: this.lastAction === \"kill\" });\n\t\tthis.lastAction = \"kill\";\n\t\tthis.value = this.value.slice(this.cursor);\n\t\tthis.cursor = 0;\n\t}\n\n\tprivate deleteToLineEnd(): void {\n\t\tif (this.cursor >= this.value.length) return;\n\t\tthis.pushUndo();\n\t\tconst deletedText = this.value.slice(this.cursor);\n\t\tthis.killRing.push(deletedText, { prepend: false, accumulate: this.lastAction === \"kill\" });\n\t\tthis.lastAction = \"kill\";\n\t\tthis.value = this.value.slice(0, this.cursor);\n\t}\n\n\tprivate deleteWordBackwards(): void {\n\t\tif (this.cursor === 0) return;\n\n\t\t// Save lastAction before cursor movement (moveWordBackwards resets it)\n\t\tconst wasKill = this.lastAction === \"kill\";\n\n\t\tthis.pushUndo();\n\n\t\tconst oldCursor = this.cursor;\n\t\tthis.moveWordBackwards();\n\t\tconst deleteFrom = this.cursor;\n\t\tthis.cursor = oldCursor;\n\n\t\tconst deletedText = this.value.slice(deleteFrom, this.cursor);\n\t\tthis.killRing.push(deletedText, { prepend: true, accumulate: wasKill });\n\t\tthis.lastAction = \"kill\";\n\n\t\tthis.value = this.value.slice(0, deleteFrom) + this.value.slice(this.cursor);\n\t\tthis.cursor = deleteFrom;\n\t}\n\n\tprivate deleteWordForward(): void {\n\t\tif (this.cursor >= this.value.length) return;\n\n\t\t// Save lastAction before cursor movement (moveWordForwards resets it)\n\t\tconst wasKill = this.lastAction === \"kill\";\n\n\t\tthis.pushUndo();\n\n\t\tconst oldCursor = this.cursor;\n\t\tthis.moveWordForwards();\n\t\tconst deleteTo = this.cursor;\n\t\tthis.cursor = oldCursor;\n\n\t\tconst deletedText = this.value.slice(this.cursor, deleteTo);\n\t\tthis.killRing.push(deletedText, { prepend: false, accumulate: wasKill });\n\t\tthis.lastAction = \"kill\";\n\n\t\tthis.value = this.value.slice(0, this.cursor) + this.value.slice(deleteTo);\n\t}\n\n\tprivate yank(): void {\n\t\tconst text = this.killRing.peek();\n\t\tif (!text) return;\n\n\t\tthis.pushUndo();\n\n\t\tthis.value = this.value.slice(0, this.cursor) + text + this.value.slice(this.cursor);\n\t\tthis.cursor += text.length;\n\t\tthis.lastAction = \"yank\";\n\t}\n\n\tprivate yankPop(): void {\n\t\tif (this.lastAction !== \"yank\" || this.killRing.length <= 1) return;\n\n\t\tthis.pushUndo();\n\n\t\t// Delete the previously yanked text (still at end of ring before rotation)\n\t\tconst prevText = this.killRing.peek() || \"\";\n\t\tthis.value = this.value.slice(0, this.cursor - prevText.length) + this.value.slice(this.cursor);\n\t\tthis.cursor -= prevText.length;\n\n\t\t// Rotate and insert new entry\n\t\tthis.killRing.rotate();\n\t\tconst text = this.killRing.peek() || \"\";\n\t\tthis.value = this.value.slice(0, this.cursor) + text + this.value.slice(this.cursor);\n\t\tthis.cursor += text.length;\n\t\tthis.lastAction = \"yank\";\n\t}\n\n\tprivate pushUndo(): void {\n\t\tthis.undoStack.push({ value: this.value, cursor: this.cursor });\n\t}\n\n\tprivate undo(): void {\n\t\tconst snapshot = this.undoStack.pop();\n\t\tif (!snapshot) return;\n\t\tthis.value = snapshot.value;\n\t\tthis.cursor = snapshot.cursor;\n\t\tthis.lastAction = null;\n\t}\n\n\tprivate moveWordBackwards(): void {\n\t\tif (this.cursor === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.lastAction = null;\n\t\tconst textBeforeCursor = this.value.slice(0, this.cursor);\n\t\tconst graphemes = [...segmenter.segment(textBeforeCursor)];\n\n\t\t// Skip trailing whitespace\n\t\twhile (graphemes.length > 0 && isWhitespaceChar(graphemes[graphemes.length - 1]?.segment || \"\")) {\n\t\t\tthis.cursor -= graphemes.pop()?.segment.length || 0;\n\t\t}\n\n\t\tif (graphemes.length > 0) {\n\t\t\tconst lastGrapheme = graphemes[graphemes.length - 1]?.segment || \"\";\n\t\t\tif (isPunctuationChar(lastGrapheme)) {\n\t\t\t\t// Skip punctuation run\n\t\t\t\twhile (graphemes.length > 0 && isPunctuationChar(graphemes[graphemes.length - 1]?.segment || \"\")) {\n\t\t\t\t\tthis.cursor -= graphemes.pop()?.segment.length || 0;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Skip word run\n\t\t\t\twhile (\n\t\t\t\t\tgraphemes.length > 0 &&\n\t\t\t\t\t!isWhitespaceChar(graphemes[graphemes.length - 1]?.segment || \"\") &&\n\t\t\t\t\t!isPunctuationChar(graphemes[graphemes.length - 1]?.segment || \"\")\n\t\t\t\t) {\n\t\t\t\t\tthis.cursor -= graphemes.pop()?.segment.length || 0;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate moveWordForwards(): void {\n\t\tif (this.cursor >= this.value.length) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.lastAction = null;\n\t\tconst textAfterCursor = this.value.slice(this.cursor);\n\t\tconst segments = segmenter.segment(textAfterCursor);\n\t\tconst iterator = segments[Symbol.iterator]();\n\t\tlet next = iterator.next();\n\n\t\t// Skip leading whitespace\n\t\twhile (!next.done && isWhitespaceChar(next.value.segment)) {\n\t\t\tthis.cursor += next.value.segment.length;\n\t\t\tnext = iterator.next();\n\t\t}\n\n\t\tif (!next.done) {\n\t\t\tconst firstGrapheme = next.value.segment;\n\t\t\tif (isPunctuationChar(firstGrapheme)) {\n\t\t\t\t// Skip punctuation run\n\t\t\t\twhile (!next.done && isPunctuationChar(next.value.segment)) {\n\t\t\t\t\tthis.cursor += next.value.segment.length;\n\t\t\t\t\tnext = iterator.next();\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Skip word run\n\t\t\t\twhile (!next.done && !isWhitespaceChar(next.value.segment) && !isPunctuationChar(next.value.segment)) {\n\t\t\t\t\tthis.cursor += next.value.segment.length;\n\t\t\t\t\tnext = iterator.next();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate handlePaste(pastedText: string): void {\n\t\tthis.lastAction = null;\n\t\tthis.pushUndo();\n\n\t\t// Clean the pasted text - remove newlines and carriage returns\n\t\tconst cleanText = pastedText.replace(/\\r\\n/g, \"\").replace(/\\r/g, \"\").replace(/\\n/g, \"\").replace(/\\t/g, \" \");\n\n\t\t// Insert at cursor position\n\t\tthis.value = this.value.slice(0, this.cursor) + cleanText + this.value.slice(this.cursor);\n\t\tthis.cursor += cleanText.length;\n\t}\n\n\tinvalidate(): void {\n\t\t// No cached state to invalidate currently\n\t}\n\n\trender(width: number): string[] {\n\t\t// Calculate visible window\n\t\tconst prompt = \"> \";\n\t\tconst availableWidth = width - prompt.length;\n\n\t\tif (availableWidth <= 0) {\n\t\t\treturn [prompt];\n\t\t}\n\n\t\tlet visibleText = \"\";\n\t\tlet cursorDisplay = this.cursor;\n\t\tconst totalWidth = visibleWidth(this.value);\n\n\t\tif (totalWidth < availableWidth) {\n\t\t\t// Everything fits (leave room for cursor at end)\n\t\t\tvisibleText = this.value;\n\t\t} else {\n\t\t\t// Need horizontal scrolling\n\t\t\t// Reserve one column for cursor if it's at the end\n\t\t\tconst scrollWidth = this.cursor === this.value.length ? availableWidth - 1 : availableWidth;\n\t\t\tconst cursorCol = visibleWidth(this.value.slice(0, this.cursor));\n\n\t\t\tif (scrollWidth > 0) {\n\t\t\t\tconst halfWidth = Math.floor(scrollWidth / 2);\n\t\t\t\tlet startCol = 0;\n\n\t\t\t\tif (cursorCol < halfWidth) {\n\t\t\t\t\t// Cursor near start\n\t\t\t\t\tstartCol = 0;\n\t\t\t\t} else if (cursorCol > totalWidth - halfWidth) {\n\t\t\t\t\t// Cursor near end\n\t\t\t\t\tstartCol = Math.max(0, totalWidth - scrollWidth);\n\t\t\t\t} else {\n\t\t\t\t\t// Cursor in middle\n\t\t\t\t\tstartCol = Math.max(0, cursorCol - halfWidth);\n\t\t\t\t}\n\n\t\t\t\tvisibleText = sliceByColumn(this.value, startCol, scrollWidth, true);\n\t\t\t\tconst beforeCursor = sliceByColumn(this.value, startCol, Math.max(0, cursorCol - startCol), true);\n\t\t\t\tcursorDisplay = beforeCursor.length;\n\t\t\t} else {\n\t\t\t\tvisibleText = \"\";\n\t\t\t\tcursorDisplay = 0;\n\t\t\t}\n\t\t}\n\n\t\t// Build line with fake cursor\n\t\t// Insert cursor character at cursor position\n\t\tconst graphemes = [...segmenter.segment(visibleText.slice(cursorDisplay))];\n\t\tconst cursorGrapheme = graphemes[0];\n\n\t\tconst beforeCursor = visibleText.slice(0, cursorDisplay);\n\t\tconst atCursor = cursorGrapheme?.segment ?? \" \"; // Character at cursor, or space if at end\n\t\tconst afterCursor = visibleText.slice(cursorDisplay + atCursor.length);\n\n\t\t// Hardware cursor marker (zero-width, emitted before fake cursor for IME positioning)\n\t\tconst marker = this.focused ? CURSOR_MARKER : \"\";\n\n\t\t// Use inverse video to show cursor\n\t\tconst cursorChar = `\\x1b[7m${atCursor}\\x1b[27m`; // ESC[7m = reverse video, ESC[27m = normal\n\t\tconst textWithCursor = beforeCursor + marker + cursorChar + afterCursor;\n\n\t\t// Calculate visual width\n\t\tconst visualLength = visibleWidth(textWithCursor);\n\t\tconst padding = \" \".repeat(Math.max(0, availableWidth - visualLength));\n\t\tconst line = prompt + textWithCursor + padding;\n\n\t\treturn [line];\n\t}\n}\n"]}
|