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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/ui/screen.js +43 -13
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ucode-agent",
3
- "version": "1.12.0",
3
+ "version": "1.12.1",
4
4
  "description": "ucode - a terminal coding agent that reads, edits and runs your code, on NVIDIA and Cohere models.",
5
5
  "type": "module",
6
6
  "main": "ucode.js",
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
- for (let i = 0; i < full.length; i += width) rows.push(full.slice(i, i + width));
612
- if (rows.length === 0) rows.push(prefix);
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
- return { rows, prefix, width };
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 { rows, prefix, width } = this.inputLines(g.boxWidth - 4);
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 + (index % width)];
1294
+ return [g.boxTop + 2 + row, g.left + 3 + col];
1263
1295
  }
1264
1296
 
1265
- const { rows, prefix, width } = this.inputLines();
1266
- const index = prefix.length + this.cursor;
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