ucode-agent 1.12.0 → 1.12.1
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/ui/screen.js +43 -13
package/package.json
CHANGED
package/src/ui/screen.js
CHANGED
|
@@ -92,6 +92,8 @@ const HIDE = `${ESC}[?25l`;
|
|
|
92
92
|
const SHOW = `${ESC}[?25h`;
|
|
93
93
|
const HOME = `${ESC}[H`;
|
|
94
94
|
const CLEAR_LINE = `${ESC}[K`;
|
|
95
|
+
/** Written out rather than inline, so no edit can turn it into a real break. */
|
|
96
|
+
const NEWLINE = String.fromCharCode(10);
|
|
95
97
|
const at = (row, col) => `${ESC}[${row};${col}H`;
|
|
96
98
|
const title = (t) => `${ESC}]0;${t}\x07`;
|
|
97
99
|
|
|
@@ -603,15 +605,47 @@ export class Screen {
|
|
|
603
605
|
// -- input box -----------------------------------------------------------
|
|
604
606
|
|
|
605
607
|
/** The typed line, wrapped to the inside of a box `width` characters across. */
|
|
608
|
+
/**
|
|
609
|
+
* The typed text, laid out as rows inside the box.
|
|
610
|
+
*
|
|
611
|
+
* A line break in the buffer is a row of its own before any wrapping is
|
|
612
|
+
* considered. Slicing the text into fixed widths without looking for one
|
|
613
|
+
* put the newline into the frame instead, and the terminal obeyed it — the
|
|
614
|
+
* pasted text walked out of the box and over the transcript beside it.
|
|
615
|
+
*
|
|
616
|
+
* `starts` records where each row begins in the text, so the caret can be
|
|
617
|
+
* placed by looking up rather than by counting characters a second way and
|
|
618
|
+
* hoping the two agree.
|
|
619
|
+
*/
|
|
606
620
|
inputLines(width = this.inner()) {
|
|
607
621
|
const prefix = this.pendingPrompt ? `${this.pendingPrompt} ` : '› ';
|
|
608
622
|
const full = prefix + this.buffer;
|
|
609
623
|
|
|
610
624
|
const rows = [];
|
|
611
|
-
|
|
612
|
-
|
|
625
|
+
const starts = [];
|
|
626
|
+
let at = 0;
|
|
627
|
+
|
|
628
|
+
for (const para of full.split(NEWLINE)) {
|
|
629
|
+
let i = 0;
|
|
630
|
+
do {
|
|
631
|
+
rows.push(para.slice(i, i + width));
|
|
632
|
+
starts.push(at + i);
|
|
633
|
+
i += width;
|
|
634
|
+
} while (i < para.length);
|
|
635
|
+
at += para.length + 1; // the newline itself
|
|
636
|
+
}
|
|
613
637
|
|
|
614
|
-
|
|
638
|
+
if (rows.length === 0) { rows.push(prefix); starts.push(0); }
|
|
639
|
+
return { rows, prefix, width, starts };
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/** Which row the caret sits on, and how far along it. */
|
|
643
|
+
caretAt(width) {
|
|
644
|
+
const { rows, prefix, starts } = this.inputLines(width);
|
|
645
|
+
const index = prefix.length + this.cursor;
|
|
646
|
+
let row = 0;
|
|
647
|
+
while (row + 1 < starts.length && starts[row + 1] <= index) row++;
|
|
648
|
+
return { row, col: Math.min(index - starts[row], rows[row].length), rows };
|
|
615
649
|
}
|
|
616
650
|
|
|
617
651
|
viewportHeight() {
|
|
@@ -1255,17 +1289,13 @@ export class Screen {
|
|
|
1255
1289
|
caret() {
|
|
1256
1290
|
if (this.welcoming()) {
|
|
1257
1291
|
const g = this.welcomeGeometry();
|
|
1258
|
-
const {
|
|
1259
|
-
const index = prefix.length + this.cursor;
|
|
1260
|
-
const row = Math.min(Math.floor(index / width), rows.length - 1);
|
|
1292
|
+
const { row, col } = this.caretAt(g.boxWidth - 4);
|
|
1261
1293
|
// g.boxTop is 0-based and the typed lines start one below the border.
|
|
1262
|
-
return [g.boxTop + 2 + row, g.left + 3 +
|
|
1294
|
+
return [g.boxTop + 2 + row, g.left + 3 + col];
|
|
1263
1295
|
}
|
|
1264
1296
|
|
|
1265
|
-
const {
|
|
1266
|
-
const
|
|
1267
|
-
const row = Math.min(Math.floor(index / width), rows.length - 1);
|
|
1268
|
-
const col = 3 + (index % width);
|
|
1297
|
+
const { row, col: at, rows } = this.caretAt();
|
|
1298
|
+
const col = 3 + at;
|
|
1269
1299
|
// Counting up from the bottom: the box border is the last row, the status
|
|
1270
1300
|
// row is above it, then the blank row, then the typed lines.
|
|
1271
1301
|
const firstRow = this.rows - 2 - rows.length;
|
|
@@ -1354,10 +1384,10 @@ export class Screen {
|
|
|
1354
1384
|
export function looksPasted(chunk) {
|
|
1355
1385
|
const text = String(chunk ?? '');
|
|
1356
1386
|
if (text.length < 2 || text.includes(ESC)) return false;
|
|
1357
|
-
const breaks = (text.match(/[\r\n]/g) ?? []).length;
|
|
1387
|
+
const breaks = (text.match(/[\r\n]/g) ?? []).length;
|
|
1358
1388
|
if (breaks === 0) return false;
|
|
1359
1389
|
// One trailing break is someone finishing a line, not pasting one.
|
|
1360
|
-
if (breaks === 1 && /[\r\n]$/.test(text)) return false;
|
|
1390
|
+
if (breaks === 1 && /[\r\n]$/.test(text)) return false;
|
|
1361
1391
|
return true;
|
|
1362
1392
|
}
|
|
1363
1393
|
|