u-foo 2.3.13 → 2.3.14
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/package.json +1 -1
- package/src/chat/inputListenerController.js +196 -54
- package/src/chat/layout.js +18 -2
- package/src/code/tui.js +405 -50
package/package.json
CHANGED
|
@@ -59,6 +59,82 @@ function createInputListenerController(options = {}) {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
function clampCursorPos(pos = 0, value = "") {
|
|
63
|
+
const text = String(value || "");
|
|
64
|
+
const normalized = Number.isFinite(pos) ? Math.floor(pos) : 0;
|
|
65
|
+
return Math.max(0, Math.min(text.length, normalized));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function refreshAfterEdit(textarea) {
|
|
69
|
+
resizeInput();
|
|
70
|
+
ensureInputCursorVisible();
|
|
71
|
+
updateCursor(textarea);
|
|
72
|
+
updateDraftFromInput();
|
|
73
|
+
|
|
74
|
+
if (textarea && shouldShowCompletion(textarea.value)) {
|
|
75
|
+
completionController.show(textarea.value);
|
|
76
|
+
} else {
|
|
77
|
+
completionController.hide();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
render(textarea);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function replaceInputRange(textarea, start, end, replacement = "") {
|
|
84
|
+
if (!textarea) return;
|
|
85
|
+
const value = String(textarea.value || "");
|
|
86
|
+
const safeStart = clampCursorPos(start, value);
|
|
87
|
+
const safeEnd = clampCursorPos(end, value);
|
|
88
|
+
const from = Math.min(safeStart, safeEnd);
|
|
89
|
+
const to = Math.max(safeStart, safeEnd);
|
|
90
|
+
const insert = String(replacement || "");
|
|
91
|
+
textarea.value = value.slice(0, from) + insert + value.slice(to);
|
|
92
|
+
setCursorPos(from + insert.length);
|
|
93
|
+
resetPreferredCol();
|
|
94
|
+
refreshAfterEdit(textarea);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function deleteWordBefore(textarea) {
|
|
98
|
+
const value = String((textarea && textarea.value) || "");
|
|
99
|
+
const cursorPos = clampCursorPos(getCursorPos(), value);
|
|
100
|
+
if (cursorPos <= 0) return;
|
|
101
|
+
const before = value.slice(0, cursorPos);
|
|
102
|
+
const match = before.match(/\s*\S+\s*$/);
|
|
103
|
+
const start = match ? cursorPos - match[0].length : Math.max(0, cursorPos - 1);
|
|
104
|
+
replaceInputRange(textarea, start, cursorPos, "");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function moveCursorByWord(textarea, direction = "forward") {
|
|
108
|
+
const value = String((textarea && textarea.value) || "");
|
|
109
|
+
const cursorPos = clampCursorPos(getCursorPos(), value);
|
|
110
|
+
if (direction === "backward") {
|
|
111
|
+
const before = value.slice(0, cursorPos);
|
|
112
|
+
const trimmedEnd = before.search(/\S\s*$/) >= 0 ? before.replace(/\s+$/, "") : before;
|
|
113
|
+
const match = trimmedEnd.match(/\S+$/);
|
|
114
|
+
return match ? trimmedEnd.length - match[0].length : 0;
|
|
115
|
+
}
|
|
116
|
+
const after = value.slice(cursorPos);
|
|
117
|
+
const match = after.match(/^\s*\S+/);
|
|
118
|
+
return match ? Math.min(value.length, cursorPos + match[0].length) : value.length;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function moveCursorToVisualBoundary(textarea, boundary = "start") {
|
|
122
|
+
const width = getWrapWidth();
|
|
123
|
+
const value = String((textarea && textarea.value) || "");
|
|
124
|
+
if (width <= 0) return boundary === "end" ? value.length : 0;
|
|
125
|
+
const cursorPos = clampCursorPos(getCursorPos(), value);
|
|
126
|
+
const { row } = getCursorRowCol(value, cursorPos, width);
|
|
127
|
+
const targetCol = boundary === "end" ? width : 0;
|
|
128
|
+
return getCursorPosForRowCol(value, row, targetCol, width);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function setCursorAndRender(textarea, nextPos) {
|
|
132
|
+
setCursorPos(clampCursorPos(nextPos, (textarea && textarea.value) || ""));
|
|
133
|
+
ensureInputCursorVisible();
|
|
134
|
+
updateCursor(textarea);
|
|
135
|
+
render(textarea);
|
|
136
|
+
}
|
|
137
|
+
|
|
62
138
|
function handleKey(ch, key = {}, textarea) {
|
|
63
139
|
const keyName = key && key.name;
|
|
64
140
|
|
|
@@ -136,8 +212,12 @@ function createInputListenerController(options = {}) {
|
|
|
136
212
|
}
|
|
137
213
|
|
|
138
214
|
if (keyName === "return" || keyName === "enter") {
|
|
139
|
-
|
|
215
|
+
const value = String((textarea && textarea.value) || "");
|
|
216
|
+
const cursorPos = clampCursorPos(getCursorPos(), value);
|
|
217
|
+
if (key && (key.shift || key.meta)) {
|
|
140
218
|
insertTextAtCursor("\n");
|
|
219
|
+
} else if (cursorPos > 0 && value[cursorPos - 1] === "\\") {
|
|
220
|
+
replaceInputRange(textarea, cursorPos - 1, cursorPos, "\n");
|
|
141
221
|
} else {
|
|
142
222
|
resetPreferredCol();
|
|
143
223
|
if (textarea && typeof textarea._done === "function") {
|
|
@@ -147,6 +227,87 @@ function createInputListenerController(options = {}) {
|
|
|
147
227
|
return;
|
|
148
228
|
}
|
|
149
229
|
|
|
230
|
+
if (key && key.ctrl) {
|
|
231
|
+
if (keyName === "a") {
|
|
232
|
+
setCursorAndRender(textarea, moveCursorToVisualBoundary(textarea, "start"));
|
|
233
|
+
resetPreferredCol();
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (keyName === "e") {
|
|
237
|
+
setCursorAndRender(textarea, moveCursorToVisualBoundary(textarea, "end"));
|
|
238
|
+
resetPreferredCol();
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (keyName === "b") {
|
|
242
|
+
const cursorPos = getCursorPos();
|
|
243
|
+
if (cursorPos > 0) setCursorAndRender(textarea, cursorPos - 1);
|
|
244
|
+
resetPreferredCol();
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
if (keyName === "f") {
|
|
248
|
+
const cursorPos = getCursorPos();
|
|
249
|
+
const value = String((textarea && textarea.value) || "");
|
|
250
|
+
if (cursorPos < value.length) setCursorAndRender(textarea, cursorPos + 1);
|
|
251
|
+
resetPreferredCol();
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (keyName === "d") {
|
|
255
|
+
const cursorPos = getCursorPos();
|
|
256
|
+
const value = String((textarea && textarea.value) || "");
|
|
257
|
+
if (cursorPos < value.length) replaceInputRange(textarea, cursorPos, cursorPos + 1, "");
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
if (keyName === "h") {
|
|
261
|
+
const cursorPos = getCursorPos();
|
|
262
|
+
if (cursorPos > 0) replaceInputRange(textarea, cursorPos - 1, cursorPos, "");
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
if (keyName === "k") {
|
|
266
|
+
const cursorPos = getCursorPos();
|
|
267
|
+
const value = String((textarea && textarea.value) || "");
|
|
268
|
+
const target = moveCursorToVisualBoundary(textarea, "end");
|
|
269
|
+
if (target === cursorPos && value[cursorPos] === "\n") {
|
|
270
|
+
replaceInputRange(textarea, cursorPos, cursorPos + 1, "");
|
|
271
|
+
} else {
|
|
272
|
+
replaceInputRange(textarea, cursorPos, target, "");
|
|
273
|
+
}
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (keyName === "u") {
|
|
277
|
+
const cursorPos = getCursorPos();
|
|
278
|
+
const value = String((textarea && textarea.value) || "");
|
|
279
|
+
const target = moveCursorToVisualBoundary(textarea, "start");
|
|
280
|
+
if (target === cursorPos && value[cursorPos - 1] === "\n") {
|
|
281
|
+
replaceInputRange(textarea, cursorPos - 1, cursorPos, "");
|
|
282
|
+
} else {
|
|
283
|
+
replaceInputRange(textarea, target, cursorPos, "");
|
|
284
|
+
}
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
if (keyName === "w") {
|
|
288
|
+
deleteWordBefore(textarea);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (key && key.meta) {
|
|
294
|
+
if (keyName === "b") {
|
|
295
|
+
setCursorAndRender(textarea, moveCursorByWord(textarea, "backward"));
|
|
296
|
+
resetPreferredCol();
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
if (keyName === "f") {
|
|
300
|
+
setCursorAndRender(textarea, moveCursorByWord(textarea, "forward"));
|
|
301
|
+
resetPreferredCol();
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
if (keyName === "d") {
|
|
305
|
+
const cursorPos = getCursorPos();
|
|
306
|
+
replaceInputRange(textarea, cursorPos, moveCursorByWord(textarea, "forward"), "");
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
150
311
|
if (keyName === "left") {
|
|
151
312
|
const cursorPos = getCursorPos();
|
|
152
313
|
if (cursorPos > 0) setCursorPos(cursorPos - 1);
|
|
@@ -170,20 +331,14 @@ function createInputListenerController(options = {}) {
|
|
|
170
331
|
}
|
|
171
332
|
|
|
172
333
|
if (keyName === "home") {
|
|
173
|
-
|
|
334
|
+
setCursorAndRender(textarea, moveCursorToVisualBoundary(textarea, "start"));
|
|
174
335
|
resetPreferredCol();
|
|
175
|
-
ensureInputCursorVisible();
|
|
176
|
-
updateCursor(textarea);
|
|
177
|
-
render(textarea);
|
|
178
336
|
return;
|
|
179
337
|
}
|
|
180
338
|
|
|
181
339
|
if (keyName === "end") {
|
|
182
|
-
|
|
340
|
+
setCursorAndRender(textarea, moveCursorToVisualBoundary(textarea, "end"));
|
|
183
341
|
resetPreferredCol();
|
|
184
|
-
ensureInputCursorVisible();
|
|
185
|
-
updateCursor(textarea);
|
|
186
|
-
render(textarea);
|
|
187
342
|
return;
|
|
188
343
|
}
|
|
189
344
|
|
|
@@ -192,17 +347,6 @@ function createInputListenerController(options = {}) {
|
|
|
192
347
|
completionController.jumpToLast();
|
|
193
348
|
return;
|
|
194
349
|
}
|
|
195
|
-
if (historyUp()) {
|
|
196
|
-
completionController.hide();
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
if (keyName === "down") {
|
|
202
|
-
if (historyDown()) {
|
|
203
|
-
completionController.hide();
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
350
|
}
|
|
207
351
|
|
|
208
352
|
if (keyName === "up" || keyName === "down") {
|
|
@@ -220,11 +364,34 @@ function createInputListenerController(options = {}) {
|
|
|
220
364
|
|
|
221
365
|
const cursorPos = getCursorPos();
|
|
222
366
|
const value = (textarea && textarea.value) || "";
|
|
367
|
+
if (!value) {
|
|
368
|
+
if (keyName === "up") {
|
|
369
|
+
if (historyUp()) completionController.hide();
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
if (historyDown()) {
|
|
373
|
+
completionController.hide();
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
enterDashboardMode();
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
223
379
|
const { row, col } = getCursorRowCol(value, cursorPos, innerWidth);
|
|
224
380
|
if (getPreferredCol() === null) setPreferredCol(col);
|
|
225
381
|
const totalRows = countLines(value, innerWidth);
|
|
226
382
|
|
|
383
|
+
if (keyName === "up" && row <= 0) {
|
|
384
|
+
if (historyUp()) {
|
|
385
|
+
completionController.hide();
|
|
386
|
+
}
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
|
|
227
390
|
if (keyName === "down" && row >= totalRows - 1) {
|
|
391
|
+
if (historyDown()) {
|
|
392
|
+
completionController.hide();
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
228
395
|
enterDashboardMode();
|
|
229
396
|
return;
|
|
230
397
|
}
|
|
@@ -261,21 +428,11 @@ function createInputListenerController(options = {}) {
|
|
|
261
428
|
if (keyName === "backspace") {
|
|
262
429
|
const cursorPos = getCursorPos();
|
|
263
430
|
if (cursorPos > 0 && textarea) {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
resetPreferredCol();
|
|
267
|
-
resizeInput();
|
|
268
|
-
ensureInputCursorVisible();
|
|
269
|
-
updateCursor(textarea);
|
|
270
|
-
updateDraftFromInput();
|
|
271
|
-
|
|
272
|
-
if (shouldShowCompletion(textarea.value)) {
|
|
273
|
-
completionController.show(textarea.value);
|
|
431
|
+
if (key && (key.ctrl || key.meta)) {
|
|
432
|
+
deleteWordBefore(textarea);
|
|
274
433
|
} else {
|
|
275
|
-
|
|
434
|
+
replaceInputRange(textarea, cursorPos - 1, cursorPos, "");
|
|
276
435
|
}
|
|
277
|
-
|
|
278
|
-
render(textarea);
|
|
279
436
|
}
|
|
280
437
|
return;
|
|
281
438
|
}
|
|
@@ -283,13 +440,11 @@ function createInputListenerController(options = {}) {
|
|
|
283
440
|
if (keyName === "delete") {
|
|
284
441
|
const cursorPos = getCursorPos();
|
|
285
442
|
if (textarea && cursorPos < textarea.value.length) {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
render(textarea);
|
|
292
|
-
updateDraftFromInput();
|
|
443
|
+
if (key && key.meta) {
|
|
444
|
+
replaceInputRange(textarea, cursorPos, moveCursorToVisualBoundary(textarea, "end"), "");
|
|
445
|
+
} else {
|
|
446
|
+
replaceInputRange(textarea, cursorPos, cursorPos + 1, "");
|
|
447
|
+
}
|
|
293
448
|
}
|
|
294
449
|
return;
|
|
295
450
|
}
|
|
@@ -300,21 +455,8 @@ function createInputListenerController(options = {}) {
|
|
|
300
455
|
|
|
301
456
|
if (insertChar && !/^[\x00-\x08\x0b-\x0c\x0e-\x1f\x7f]$/.test(insertChar) && textarea) {
|
|
302
457
|
const cursorPos = getCursorPos();
|
|
303
|
-
textarea.value = textarea.value.slice(0, cursorPos) + insertChar + textarea.value.slice(cursorPos);
|
|
304
|
-
setCursorPos(cursorPos + 1);
|
|
305
458
|
normalizeCommandPrefix();
|
|
306
|
-
|
|
307
|
-
resizeInput();
|
|
308
|
-
updateCursor(textarea);
|
|
309
|
-
updateDraftFromInput();
|
|
310
|
-
|
|
311
|
-
if (shouldShowCompletion(textarea.value)) {
|
|
312
|
-
completionController.show(textarea.value);
|
|
313
|
-
} else if (completionController.isActive()) {
|
|
314
|
-
completionController.hide();
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
render(textarea);
|
|
459
|
+
replaceInputRange(textarea, cursorPos, cursorPos, insertChar);
|
|
318
460
|
}
|
|
319
461
|
}
|
|
320
462
|
|
package/src/chat/layout.js
CHANGED
|
@@ -4,6 +4,8 @@ function createChatLayout(options = {}) {
|
|
|
4
4
|
currentInputHeight = 4,
|
|
5
5
|
dashboardHeight = 1,
|
|
6
6
|
version = "unknown",
|
|
7
|
+
logBorder = false,
|
|
8
|
+
logScrollbar = false,
|
|
7
9
|
} = options;
|
|
8
10
|
const normalizedDashboardHeight = Number.isFinite(dashboardHeight) && dashboardHeight > 0
|
|
9
11
|
? Math.floor(dashboardHeight)
|
|
@@ -32,24 +34,38 @@ function createChatLayout(options = {}) {
|
|
|
32
34
|
}
|
|
33
35
|
}
|
|
34
36
|
|
|
35
|
-
// Log area
|
|
37
|
+
// Log area
|
|
36
38
|
const logBox = blessed.log({
|
|
37
39
|
parent: screen,
|
|
38
40
|
top: 0,
|
|
39
41
|
left: 0,
|
|
40
42
|
width: "100%",
|
|
41
43
|
height: `100%-${reservedBottomLines}`, // Will be adjusted dynamically
|
|
44
|
+
border: logBorder ? { type: "line" } : null,
|
|
42
45
|
tags: true,
|
|
43
46
|
scrollable: true,
|
|
44
47
|
alwaysScroll: true,
|
|
45
48
|
scrollback: 10000,
|
|
46
|
-
scrollbar:
|
|
49
|
+
scrollbar: logScrollbar
|
|
50
|
+
? {
|
|
51
|
+
ch: " ",
|
|
52
|
+
track: { bg: "black" },
|
|
53
|
+
style: { bg: "gray" },
|
|
54
|
+
}
|
|
55
|
+
: null,
|
|
47
56
|
keys: true,
|
|
48
57
|
vi: true,
|
|
49
58
|
// Mouse handled globally (toggleable) to keep copy working
|
|
50
59
|
mouse: false,
|
|
51
60
|
// Ensure proper wrapping and width calculation
|
|
52
61
|
wrap: true,
|
|
62
|
+
padding: logBorder ? { left: 1, right: 1 } : undefined,
|
|
63
|
+
style: logBorder
|
|
64
|
+
? {
|
|
65
|
+
border: { fg: "gray" },
|
|
66
|
+
scrollbar: { bg: "gray" },
|
|
67
|
+
}
|
|
68
|
+
: undefined,
|
|
53
69
|
});
|
|
54
70
|
|
|
55
71
|
// Status line just above input
|
package/src/code/tui.js
CHANGED
|
@@ -293,6 +293,111 @@ function moveCursorHorizontally(cursorPos = 0, inputValue = "", direction = "rig
|
|
|
293
293
|
return Math.min(max, pos + 1);
|
|
294
294
|
}
|
|
295
295
|
|
|
296
|
+
function clampCursorPos(cursorPos = 0, inputValue = "") {
|
|
297
|
+
const text = String(inputValue || "");
|
|
298
|
+
const pos = Number.isFinite(cursorPos) ? Math.floor(cursorPos) : 0;
|
|
299
|
+
return Math.max(0, Math.min(text.length, pos));
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function findLogicalLineStart(inputValue = "", cursorPos = 0) {
|
|
303
|
+
const text = String(inputValue || "");
|
|
304
|
+
const pos = clampCursorPos(cursorPos, text);
|
|
305
|
+
const prevNewline = text.lastIndexOf("\n", Math.max(0, pos - 1));
|
|
306
|
+
return prevNewline === -1 ? 0 : prevNewline + 1;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function findLogicalLineEnd(inputValue = "", cursorPos = 0) {
|
|
310
|
+
const text = String(inputValue || "");
|
|
311
|
+
const pos = clampCursorPos(cursorPos, text);
|
|
312
|
+
const nextNewline = text.indexOf("\n", pos);
|
|
313
|
+
return nextNewline === -1 ? text.length : nextNewline;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function moveCursorToVisualLineBoundary({
|
|
317
|
+
cursorPos = 0,
|
|
318
|
+
inputValue = "",
|
|
319
|
+
width = 80,
|
|
320
|
+
boundary = "start",
|
|
321
|
+
strWidth,
|
|
322
|
+
} = {}) {
|
|
323
|
+
const inputMath = require("../chat/inputMath");
|
|
324
|
+
const text = String(inputValue || "");
|
|
325
|
+
const normalizedWidth = Number.isFinite(width) ? Math.max(1, Math.floor(width)) : 1;
|
|
326
|
+
const pos = clampCursorPos(cursorPos, text);
|
|
327
|
+
const { row } = inputMath.getCursorRowCol(text, pos, normalizedWidth, strWidth);
|
|
328
|
+
if (boundary === "end") {
|
|
329
|
+
return inputMath.getCursorPosForRowCol(text, row, normalizedWidth, normalizedWidth, strWidth);
|
|
330
|
+
}
|
|
331
|
+
return inputMath.getCursorPosForRowCol(text, row, 0, normalizedWidth, strWidth);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function moveCursorVertically({
|
|
335
|
+
cursorPos = 0,
|
|
336
|
+
inputValue = "",
|
|
337
|
+
width = 80,
|
|
338
|
+
direction = "down",
|
|
339
|
+
preferredCol = null,
|
|
340
|
+
strWidth,
|
|
341
|
+
} = {}) {
|
|
342
|
+
const inputMath = require("../chat/inputMath");
|
|
343
|
+
const text = String(inputValue || "");
|
|
344
|
+
const normalizedWidth = Number.isFinite(width) ? Math.max(1, Math.floor(width)) : 1;
|
|
345
|
+
const pos = clampCursorPos(cursorPos, text);
|
|
346
|
+
const { row, col } = inputMath.getCursorRowCol(text, pos, normalizedWidth, strWidth);
|
|
347
|
+
const totalRows = inputMath.countLines(text, normalizedWidth, strWidth);
|
|
348
|
+
const targetCol = Number.isFinite(preferredCol) ? preferredCol : col;
|
|
349
|
+
|
|
350
|
+
if (direction === "up") {
|
|
351
|
+
if (row <= 0) {
|
|
352
|
+
return { moved: false, nextCursorPos: pos, preferredCol: targetCol, boundary: "top" };
|
|
353
|
+
}
|
|
354
|
+
return {
|
|
355
|
+
moved: true,
|
|
356
|
+
nextCursorPos: inputMath.getCursorPosForRowCol(text, row - 1, targetCol, normalizedWidth, strWidth),
|
|
357
|
+
preferredCol: targetCol,
|
|
358
|
+
boundary: "",
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (row >= totalRows - 1) {
|
|
363
|
+
return { moved: false, nextCursorPos: pos, preferredCol: targetCol, boundary: "bottom" };
|
|
364
|
+
}
|
|
365
|
+
return {
|
|
366
|
+
moved: true,
|
|
367
|
+
nextCursorPos: inputMath.getCursorPosForRowCol(text, row + 1, targetCol, normalizedWidth, strWidth),
|
|
368
|
+
preferredCol: targetCol,
|
|
369
|
+
boundary: "",
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function deleteWordBeforeCursor(inputValue = "", cursorPos = 0) {
|
|
374
|
+
const text = String(inputValue || "");
|
|
375
|
+
const pos = clampCursorPos(cursorPos, text);
|
|
376
|
+
if (pos <= 0) return { value: text, cursorPos: pos };
|
|
377
|
+
const before = text.slice(0, pos);
|
|
378
|
+
const after = text.slice(pos);
|
|
379
|
+
const match = before.match(/\s*\S+\s*$/);
|
|
380
|
+
const start = match ? pos - match[0].length : Math.max(0, pos - 1);
|
|
381
|
+
return {
|
|
382
|
+
value: before.slice(0, start) + after,
|
|
383
|
+
cursorPos: start,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function moveCursorByWord(inputValue = "", cursorPos = 0, direction = "forward") {
|
|
388
|
+
const text = String(inputValue || "");
|
|
389
|
+
const pos = clampCursorPos(cursorPos, text);
|
|
390
|
+
if (direction === "backward") {
|
|
391
|
+
const before = text.slice(0, pos);
|
|
392
|
+
const trimmedEnd = before.search(/\S\s*$/) >= 0 ? before.replace(/\s+$/, "") : before;
|
|
393
|
+
const match = trimmedEnd.match(/\S+$/);
|
|
394
|
+
return match ? trimmedEnd.length - match[0].length : 0;
|
|
395
|
+
}
|
|
396
|
+
const after = text.slice(pos);
|
|
397
|
+
const match = after.match(/^\s*\S+/);
|
|
398
|
+
return match ? Math.min(text.length, pos + match[0].length) : text.length;
|
|
399
|
+
}
|
|
400
|
+
|
|
296
401
|
function resolveHistoryDownTransition({
|
|
297
402
|
inputHistory = [],
|
|
298
403
|
historyIndex = 0,
|
|
@@ -499,6 +604,11 @@ function runUcodeTui({
|
|
|
499
604
|
let lastMergedToolGroup = null;
|
|
500
605
|
let toolMergeId = 0;
|
|
501
606
|
let cursorPos = 0;
|
|
607
|
+
let preferredCol = null;
|
|
608
|
+
let currentInputHeight = 4;
|
|
609
|
+
const MIN_INPUT_CONTENT_HEIGHT = 1;
|
|
610
|
+
const MAX_INPUT_CONTENT_HEIGHT = 8;
|
|
611
|
+
const DASHBOARD_HEIGHT = 1;
|
|
502
612
|
let autoBusTimer = null;
|
|
503
613
|
let autoBusQueued = false;
|
|
504
614
|
let autoBusError = "";
|
|
@@ -510,12 +620,15 @@ function runUcodeTui({
|
|
|
510
620
|
statusLine,
|
|
511
621
|
completionPanel,
|
|
512
622
|
dashboard,
|
|
623
|
+
inputTopLine,
|
|
513
624
|
promptBox,
|
|
514
625
|
input,
|
|
515
626
|
} = createChatLayout({
|
|
516
627
|
blessed,
|
|
517
628
|
currentInputHeight: 4,
|
|
518
629
|
version: UCODE_VERSION,
|
|
630
|
+
logBorder: true,
|
|
631
|
+
logScrollbar: true,
|
|
519
632
|
});
|
|
520
633
|
|
|
521
634
|
if (completionPanel && typeof completionPanel.hide === "function") {
|
|
@@ -555,6 +668,7 @@ function runUcodeTui({
|
|
|
555
668
|
promptBox.width = Math.max(2, plain.length + 1);
|
|
556
669
|
input.left = promptBox.width;
|
|
557
670
|
input.width = `100%-${promptBox.width}`;
|
|
671
|
+
resizeInput();
|
|
558
672
|
};
|
|
559
673
|
|
|
560
674
|
// --- Cursor position helpers (mirrors chat inputListenerController) ---
|
|
@@ -565,6 +679,10 @@ function runUcodeTui({
|
|
|
565
679
|
|
|
566
680
|
const getWrapWidth = () => inputMath.getWrapWidth(input, getInnerWidth());
|
|
567
681
|
|
|
682
|
+
const resetPreferredCol = () => {
|
|
683
|
+
preferredCol = null;
|
|
684
|
+
};
|
|
685
|
+
|
|
568
686
|
const ensureInputCursorVisible = () => {
|
|
569
687
|
const innerWidth = getWrapWidth();
|
|
570
688
|
if (innerWidth <= 0) return;
|
|
@@ -583,6 +701,105 @@ function runUcodeTui({
|
|
|
583
701
|
}
|
|
584
702
|
};
|
|
585
703
|
|
|
704
|
+
const resizeInput = () => {
|
|
705
|
+
const innerWidth = getWrapWidth();
|
|
706
|
+
if (innerWidth <= 0) return;
|
|
707
|
+
const totalRows = inputMath.countLines(input.value || "", innerWidth, (v) => input.strWidth(v));
|
|
708
|
+
const contentHeight = Math.min(
|
|
709
|
+
MAX_INPUT_CONTENT_HEIGHT,
|
|
710
|
+
Math.max(MIN_INPUT_CONTENT_HEIGHT, totalRows)
|
|
711
|
+
);
|
|
712
|
+
const targetHeight = contentHeight + DASHBOARD_HEIGHT + 2;
|
|
713
|
+
if (targetHeight !== currentInputHeight) {
|
|
714
|
+
currentInputHeight = targetHeight;
|
|
715
|
+
input.height = contentHeight;
|
|
716
|
+
promptBox.height = contentHeight;
|
|
717
|
+
if (inputTopLine) inputTopLine.bottom = currentInputHeight - 1;
|
|
718
|
+
}
|
|
719
|
+
statusLine.bottom = currentInputHeight;
|
|
720
|
+
logBox.height = Math.max(1, screen.height - currentInputHeight - 1);
|
|
721
|
+
ensureInputCursorVisible();
|
|
722
|
+
};
|
|
723
|
+
|
|
724
|
+
const renderInput = () => {
|
|
725
|
+
resizeInput();
|
|
726
|
+
ensureInputCursorVisible();
|
|
727
|
+
input._updateCursor();
|
|
728
|
+
screen.render();
|
|
729
|
+
};
|
|
730
|
+
|
|
731
|
+
const setCursor = (nextPos) => {
|
|
732
|
+
cursorPos = clampCursorPos(nextPos, input.value || "");
|
|
733
|
+
ensureInputCursorVisible();
|
|
734
|
+
input._updateCursor();
|
|
735
|
+
screen.render();
|
|
736
|
+
};
|
|
737
|
+
|
|
738
|
+
const setInputValue = (value) => {
|
|
739
|
+
input.setValue(value || "");
|
|
740
|
+
cursorPos = (value || "").length;
|
|
741
|
+
resetPreferredCol();
|
|
742
|
+
renderInput();
|
|
743
|
+
};
|
|
744
|
+
|
|
745
|
+
const replaceInputRange = (start, end, replacement = "") => {
|
|
746
|
+
const value = input.value || "";
|
|
747
|
+
const safeStart = clampCursorPos(start, value);
|
|
748
|
+
const safeEnd = clampCursorPos(end, value);
|
|
749
|
+
const from = Math.min(safeStart, safeEnd);
|
|
750
|
+
const to = Math.max(safeStart, safeEnd);
|
|
751
|
+
input.value = value.slice(0, from) + String(replacement || "") + value.slice(to);
|
|
752
|
+
cursorPos = from + String(replacement || "").length;
|
|
753
|
+
resetPreferredCol();
|
|
754
|
+
renderInput();
|
|
755
|
+
};
|
|
756
|
+
|
|
757
|
+
const insertTextAtCursor = (text = "") => {
|
|
758
|
+
const normalized = inputMath.normalizePaste(text);
|
|
759
|
+
if (!normalized) return;
|
|
760
|
+
replaceInputRange(cursorPos, cursorPos, normalized);
|
|
761
|
+
};
|
|
762
|
+
|
|
763
|
+
const deleteBeforeCursor = () => {
|
|
764
|
+
if (cursorPos <= 0) return;
|
|
765
|
+
replaceInputRange(cursorPos - 1, cursorPos, "");
|
|
766
|
+
};
|
|
767
|
+
|
|
768
|
+
const deleteAtCursor = () => {
|
|
769
|
+
const value = input.value || "";
|
|
770
|
+
if (cursorPos >= value.length) return;
|
|
771
|
+
replaceInputRange(cursorPos, cursorPos + 1, "");
|
|
772
|
+
};
|
|
773
|
+
|
|
774
|
+
const deleteToBoundary = (boundary) => {
|
|
775
|
+
const value = input.value || "";
|
|
776
|
+
const innerWidth = getWrapWidth();
|
|
777
|
+
const target = boundary === "end"
|
|
778
|
+
? moveCursorToVisualLineBoundary({
|
|
779
|
+
cursorPos,
|
|
780
|
+
inputValue: value,
|
|
781
|
+
width: innerWidth,
|
|
782
|
+
boundary: "end",
|
|
783
|
+
strWidth: (v) => input.strWidth(v),
|
|
784
|
+
})
|
|
785
|
+
: moveCursorToVisualLineBoundary({
|
|
786
|
+
cursorPos,
|
|
787
|
+
inputValue: value,
|
|
788
|
+
width: innerWidth,
|
|
789
|
+
boundary: "start",
|
|
790
|
+
strWidth: (v) => input.strWidth(v),
|
|
791
|
+
});
|
|
792
|
+
if (target === cursorPos && boundary === "end" && value[cursorPos] === "\n") {
|
|
793
|
+
replaceInputRange(cursorPos, cursorPos + 1, "");
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
if (target === cursorPos && boundary === "start" && value[cursorPos - 1] === "\n") {
|
|
797
|
+
replaceInputRange(cursorPos - 1, cursorPos, "");
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
replaceInputRange(Math.min(cursorPos, target), Math.max(cursorPos, target), "");
|
|
801
|
+
};
|
|
802
|
+
|
|
586
803
|
// Override _updateCursor to use our tracked cursorPos
|
|
587
804
|
input._updateCursor = function () {
|
|
588
805
|
if (this.screen.focused !== this) return;
|
|
@@ -603,8 +820,8 @@ function runUcodeTui({
|
|
|
603
820
|
};
|
|
604
821
|
|
|
605
822
|
// Override _listener to support cursor-aware editing
|
|
606
|
-
const origDone = input._done ? input._done.bind(input) : null;
|
|
607
823
|
let lastKeyRef = null;
|
|
824
|
+
let skipSubmitKeyRef = null;
|
|
608
825
|
input._listener = function (ch, key) {
|
|
609
826
|
const keyName = key && key.name;
|
|
610
827
|
|
|
@@ -614,69 +831,161 @@ function runUcodeTui({
|
|
|
614
831
|
if (key && key === lastKeyRef) return;
|
|
615
832
|
lastKeyRef = key || null;
|
|
616
833
|
|
|
617
|
-
|
|
618
|
-
|
|
834
|
+
if (keyName === "escape") return;
|
|
835
|
+
|
|
836
|
+
if (keyName === "return" || keyName === "enter") {
|
|
837
|
+
const value = this.value || "";
|
|
838
|
+
if (key && (key.shift || key.meta)) {
|
|
839
|
+
insertTextAtCursor("\n");
|
|
840
|
+
skipSubmitKeyRef = key || true;
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
if (cursorPos > 0 && value[cursorPos - 1] === "\\") {
|
|
844
|
+
replaceInputRange(cursorPos - 1, cursorPos, "\n");
|
|
845
|
+
skipSubmitKeyRef = key || true;
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
619
850
|
|
|
620
851
|
// Arrow keys handled by input.key() handlers below
|
|
621
852
|
if (keyName === "left" || keyName === "right" || keyName === "up" || keyName === "down") return;
|
|
622
853
|
|
|
854
|
+
if (key && key.ctrl) {
|
|
855
|
+
if (keyName === "a") {
|
|
856
|
+
setCursor(moveCursorToVisualLineBoundary({
|
|
857
|
+
cursorPos,
|
|
858
|
+
inputValue: this.value || "",
|
|
859
|
+
width: getWrapWidth(),
|
|
860
|
+
boundary: "start",
|
|
861
|
+
strWidth: (v) => this.strWidth(v),
|
|
862
|
+
}));
|
|
863
|
+
resetPreferredCol();
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
if (keyName === "e") {
|
|
867
|
+
setCursor(moveCursorToVisualLineBoundary({
|
|
868
|
+
cursorPos,
|
|
869
|
+
inputValue: this.value || "",
|
|
870
|
+
width: getWrapWidth(),
|
|
871
|
+
boundary: "end",
|
|
872
|
+
strWidth: (v) => this.strWidth(v),
|
|
873
|
+
}));
|
|
874
|
+
resetPreferredCol();
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
if (keyName === "b") {
|
|
878
|
+
setCursor(moveCursorHorizontally(cursorPos, this.value || "", "left"));
|
|
879
|
+
resetPreferredCol();
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
if (keyName === "f") {
|
|
883
|
+
setCursor(moveCursorHorizontally(cursorPos, this.value || "", "right"));
|
|
884
|
+
resetPreferredCol();
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
if (keyName === "d") {
|
|
888
|
+
deleteAtCursor();
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
if (keyName === "h") {
|
|
892
|
+
deleteBeforeCursor();
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
if (keyName === "k") {
|
|
896
|
+
deleteToBoundary("end");
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
if (keyName === "u") {
|
|
900
|
+
deleteToBoundary("start");
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
if (keyName === "w") {
|
|
904
|
+
const next = deleteWordBeforeCursor(this.value || "", cursorPos);
|
|
905
|
+
this.value = next.value;
|
|
906
|
+
cursorPos = next.cursorPos;
|
|
907
|
+
resetPreferredCol();
|
|
908
|
+
renderInput();
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
if (key && key.meta) {
|
|
914
|
+
if (keyName === "b") {
|
|
915
|
+
setCursor(moveCursorByWord(this.value || "", cursorPos, "backward"));
|
|
916
|
+
resetPreferredCol();
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
if (keyName === "f") {
|
|
920
|
+
setCursor(moveCursorByWord(this.value || "", cursorPos, "forward"));
|
|
921
|
+
resetPreferredCol();
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
if (keyName === "d") {
|
|
925
|
+
const end = moveCursorByWord(this.value || "", cursorPos, "forward");
|
|
926
|
+
replaceInputRange(cursorPos, end, "");
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
623
931
|
if (keyName === "backspace") {
|
|
624
|
-
if (
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
932
|
+
if (key && (key.meta || key.ctrl)) {
|
|
933
|
+
const next = deleteWordBeforeCursor(this.value || "", cursorPos);
|
|
934
|
+
this.value = next.value;
|
|
935
|
+
cursorPos = next.cursorPos;
|
|
936
|
+
resetPreferredCol();
|
|
937
|
+
renderInput();
|
|
938
|
+
} else {
|
|
939
|
+
deleteBeforeCursor();
|
|
630
940
|
}
|
|
631
941
|
return;
|
|
632
942
|
}
|
|
633
943
|
|
|
634
944
|
if (keyName === "delete") {
|
|
635
|
-
if (
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
this.screen.render();
|
|
945
|
+
if (key && key.meta) {
|
|
946
|
+
deleteToBoundary("end");
|
|
947
|
+
} else {
|
|
948
|
+
deleteAtCursor();
|
|
640
949
|
}
|
|
641
950
|
return;
|
|
642
951
|
}
|
|
643
952
|
|
|
644
953
|
if (keyName === "home") {
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
954
|
+
setCursor(moveCursorToVisualLineBoundary({
|
|
955
|
+
cursorPos,
|
|
956
|
+
inputValue: this.value || "",
|
|
957
|
+
width: getWrapWidth(),
|
|
958
|
+
boundary: "start",
|
|
959
|
+
strWidth: (v) => this.strWidth(v),
|
|
960
|
+
}));
|
|
961
|
+
resetPreferredCol();
|
|
649
962
|
return;
|
|
650
963
|
}
|
|
651
964
|
|
|
652
965
|
if (keyName === "end") {
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
966
|
+
setCursor(moveCursorToVisualLineBoundary({
|
|
967
|
+
cursorPos,
|
|
968
|
+
inputValue: this.value || "",
|
|
969
|
+
width: getWrapWidth(),
|
|
970
|
+
boundary: "end",
|
|
971
|
+
strWidth: (v) => this.strWidth(v),
|
|
972
|
+
}));
|
|
973
|
+
resetPreferredCol();
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
if (ch && ch.length > 1 && (!keyName || keyName.length !== 1)) {
|
|
978
|
+
insertTextAtCursor(ch);
|
|
657
979
|
return;
|
|
658
980
|
}
|
|
659
981
|
|
|
660
982
|
// Normal character insertion at cursor position
|
|
661
983
|
const insertChar = (ch && ch.length === 1) ? ch : (keyName && keyName.length === 1 ? keyName : null);
|
|
662
984
|
if (insertChar && !/^[\x00-\x08\x0b-\x0c\x0e-\x1f\x7f]$/.test(insertChar)) {
|
|
663
|
-
|
|
664
|
-
cursorPos += 1;
|
|
665
|
-
ensureInputCursorVisible();
|
|
666
|
-
this._updateCursor();
|
|
667
|
-
this.screen.render();
|
|
985
|
+
insertTextAtCursor(insertChar);
|
|
668
986
|
}
|
|
669
987
|
};
|
|
670
988
|
|
|
671
|
-
// Helper to set input value and reset cursor to end
|
|
672
|
-
const setInputValue = (value) => {
|
|
673
|
-
input.setValue(value || "");
|
|
674
|
-
cursorPos = (value || "").length;
|
|
675
|
-
ensureInputCursorVisible();
|
|
676
|
-
input._updateCursor();
|
|
677
|
-
screen.render();
|
|
678
|
-
};
|
|
679
|
-
|
|
680
989
|
const renderDashboard = () => {
|
|
681
990
|
let hint = "No target agents";
|
|
682
991
|
if (activeAgents.length > 0) {
|
|
@@ -922,7 +1231,7 @@ function runUcodeTui({
|
|
|
922
1231
|
statusInterval = null;
|
|
923
1232
|
}
|
|
924
1233
|
if (!message) {
|
|
925
|
-
statusLine.setContent(escapeBlessed(`UCODE · Ready${getBackgroundSuffix()}`));
|
|
1234
|
+
statusLine.setContent(escapeBlessed(`UCODE · Ready · Enter send · Shift/Alt+Enter newline · PgUp/PgDn log · Ctrl+O tools${getBackgroundSuffix()}`));
|
|
926
1235
|
screen.render();
|
|
927
1236
|
return;
|
|
928
1237
|
}
|
|
@@ -1321,6 +1630,8 @@ function runUcodeTui({
|
|
|
1321
1630
|
const trimmed = raw.trim();
|
|
1322
1631
|
input.setValue("");
|
|
1323
1632
|
cursorPos = 0;
|
|
1633
|
+
resetPreferredCol();
|
|
1634
|
+
resizeInput();
|
|
1324
1635
|
screen.render();
|
|
1325
1636
|
agentSelectionMode = false;
|
|
1326
1637
|
|
|
@@ -1345,7 +1656,11 @@ function runUcodeTui({
|
|
|
1345
1656
|
});
|
|
1346
1657
|
};
|
|
1347
1658
|
|
|
1348
|
-
input.key(["enter"], () => {
|
|
1659
|
+
input.key(["enter"], (ch, key) => {
|
|
1660
|
+
if (skipSubmitKeyRef && (!key || skipSubmitKeyRef === key || skipSubmitKeyRef === true)) {
|
|
1661
|
+
skipSubmitKeyRef = null;
|
|
1662
|
+
return false;
|
|
1663
|
+
}
|
|
1349
1664
|
submitInput(input.getValue());
|
|
1350
1665
|
return false;
|
|
1351
1666
|
});
|
|
@@ -1355,7 +1670,6 @@ function runUcodeTui({
|
|
|
1355
1670
|
agentSelectionMode,
|
|
1356
1671
|
inputValue: currentValue,
|
|
1357
1672
|
})) {
|
|
1358
|
-
const previousTarget = targetAgent;
|
|
1359
1673
|
targetAgent = null;
|
|
1360
1674
|
selectedAgentIndex = -1;
|
|
1361
1675
|
agentSelectionMode = false;
|
|
@@ -1365,12 +1679,43 @@ function runUcodeTui({
|
|
|
1365
1679
|
input.focus();
|
|
1366
1680
|
return false;
|
|
1367
1681
|
}
|
|
1368
|
-
if (
|
|
1682
|
+
if (currentValue) {
|
|
1683
|
+
const move = moveCursorVertically({
|
|
1684
|
+
cursorPos,
|
|
1685
|
+
inputValue: currentValue,
|
|
1686
|
+
width: getWrapWidth(),
|
|
1687
|
+
direction: "up",
|
|
1688
|
+
preferredCol,
|
|
1689
|
+
strWidth: (v) => input.strWidth(v),
|
|
1690
|
+
});
|
|
1691
|
+
preferredCol = move.preferredCol;
|
|
1692
|
+
if (move.moved) {
|
|
1693
|
+
setCursor(move.nextCursorPos);
|
|
1694
|
+
return false;
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
if (inputHistory.length === 0) return false;
|
|
1369
1698
|
historyIndex = Math.max(0, historyIndex - 1);
|
|
1370
1699
|
setInputValue(inputHistory[historyIndex] || "");
|
|
1700
|
+
return false;
|
|
1371
1701
|
});
|
|
1372
1702
|
input.key(["down"], () => {
|
|
1373
1703
|
const currentValue = input.getValue();
|
|
1704
|
+
if (currentValue) {
|
|
1705
|
+
const move = moveCursorVertically({
|
|
1706
|
+
cursorPos,
|
|
1707
|
+
inputValue: currentValue,
|
|
1708
|
+
width: getWrapWidth(),
|
|
1709
|
+
direction: "down",
|
|
1710
|
+
preferredCol,
|
|
1711
|
+
strWidth: (v) => input.strWidth(v),
|
|
1712
|
+
});
|
|
1713
|
+
preferredCol = move.preferredCol;
|
|
1714
|
+
if (move.moved) {
|
|
1715
|
+
setCursor(move.nextCursorPos);
|
|
1716
|
+
return false;
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1374
1719
|
const historyTransition = resolveHistoryDownTransition({
|
|
1375
1720
|
inputHistory,
|
|
1376
1721
|
historyIndex,
|
|
@@ -1428,10 +1773,8 @@ function runUcodeTui({
|
|
|
1428
1773
|
}
|
|
1429
1774
|
const next = moveCursorHorizontally(cursorPos, currentValue, "left");
|
|
1430
1775
|
if (next !== cursorPos) {
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
input._updateCursor();
|
|
1434
|
-
screen.render();
|
|
1776
|
+
setCursor(next);
|
|
1777
|
+
resetPreferredCol();
|
|
1435
1778
|
}
|
|
1436
1779
|
return false;
|
|
1437
1780
|
});
|
|
@@ -1450,10 +1793,8 @@ function runUcodeTui({
|
|
|
1450
1793
|
}
|
|
1451
1794
|
const next = moveCursorHorizontally(cursorPos, currentValue, "right");
|
|
1452
1795
|
if (next !== cursorPos) {
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
input._updateCursor();
|
|
1456
|
-
screen.render();
|
|
1796
|
+
setCursor(next);
|
|
1797
|
+
resetPreferredCol();
|
|
1457
1798
|
}
|
|
1458
1799
|
return false;
|
|
1459
1800
|
});
|
|
@@ -1496,6 +1837,14 @@ function runUcodeTui({
|
|
|
1496
1837
|
}
|
|
1497
1838
|
screen.render();
|
|
1498
1839
|
});
|
|
1840
|
+
screen.key(["pageup"], () => {
|
|
1841
|
+
logBox.scroll(-Math.max(1, Math.floor((logBox.height || 10) / 2)));
|
|
1842
|
+
screen.render();
|
|
1843
|
+
});
|
|
1844
|
+
screen.key(["pagedown"], () => {
|
|
1845
|
+
logBox.scroll(Math.max(1, Math.floor((logBox.height || 10) / 2)));
|
|
1846
|
+
screen.render();
|
|
1847
|
+
});
|
|
1499
1848
|
input.key(["escape"], () => {
|
|
1500
1849
|
if (pendingTask && pendingTask.abortController && !pendingTask.abortController.signal.aborted) {
|
|
1501
1850
|
try {
|
|
@@ -1510,11 +1859,10 @@ function runUcodeTui({
|
|
|
1510
1859
|
});
|
|
1511
1860
|
return false;
|
|
1512
1861
|
}
|
|
1513
|
-
const previousTarget = targetAgent;
|
|
1514
1862
|
targetAgent = null;
|
|
1515
1863
|
selectedAgentIndex = -1;
|
|
1516
1864
|
agentSelectionMode = false;
|
|
1517
|
-
|
|
1865
|
+
setInputValue("");
|
|
1518
1866
|
setPrompt();
|
|
1519
1867
|
renderDashboard();
|
|
1520
1868
|
// Target selection cleared - removed redundant log
|
|
@@ -1573,6 +1921,13 @@ module.exports = {
|
|
|
1573
1921
|
cycleAgentSelectionIndex,
|
|
1574
1922
|
shouldClearAgentSelectionOnUp,
|
|
1575
1923
|
moveCursorHorizontally,
|
|
1924
|
+
clampCursorPos,
|
|
1925
|
+
findLogicalLineStart,
|
|
1926
|
+
findLogicalLineEnd,
|
|
1927
|
+
moveCursorToVisualLineBoundary,
|
|
1928
|
+
moveCursorVertically,
|
|
1929
|
+
deleteWordBeforeCursor,
|
|
1930
|
+
moveCursorByWord,
|
|
1576
1931
|
resolveHistoryDownTransition,
|
|
1577
1932
|
filterSelectableAgents,
|
|
1578
1933
|
stripLeakedEscapeTags,
|