@lazyingart/agintiflow 0.12.1 → 0.12.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 +1 -1
- package/package.json +1 -1
- package/scripts/smoke-cli-chat.js +17 -2
- package/src/interactive-cli.js +374 -27
package/README.md
CHANGED
|
@@ -67,7 +67,7 @@ aginti
|
|
|
67
67
|
aginti chat
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
-
Inside chat, type normal requests such as `write a small Python CLI app with tests`. The default is Docker workspace mode with approved package installs, so coding, plotting, and LaTeX tasks can set up project-local tools without touching the host. Use `/help` for commands, `/login` or `/auth` to paste a provider key, `/latex on` for PDF work, `/docker off` only when you intentionally want host mode, `/sessions` to list project runs, and `/resume latest` or `/resume <session-id>` to continue work. Type `/` then Tab for command completion. `Ctrl+J` inserts a new line in the colored input panel,
|
|
70
|
+
Inside chat, type normal requests such as `write a small Python CLI app with tests`. The default is Docker workspace mode with approved package installs, so coding, plotting, and LaTeX tasks can set up project-local tools without touching the host. Use `/help` for commands, `/login` or `/auth` to paste a provider key, `/latex on` for PDF work, `/docker off` only when you intentionally want host mode, `/sessions` to list project runs, and `/resume latest` or `/resume <session-id>` to continue work. Type `/` then Tab for command completion. `Ctrl+J` inserts a new line in the colored input panel, Enter sends, arrow keys move through wrapped multiline input, and `Ctrl+A`/`Ctrl+E` jump to the current line start/end. Assistant responses render common Markdown, including headings, inline code, bold text, lists, quotes, code fences, and tables. Esc or Ctrl+C stops the active run cleanly and prints the resume command.
|
|
71
71
|
|
|
72
72
|
For code edits, AgInTiFlow routes patch/refactor/database-style tasks to DeepSeek v4 pro by default and exposes `apply_patch` as a deterministic workspace tool. It supports exact replacements, Codex-style patch envelopes, and unified diffs, with preflight checks, path guardrails, hashes, and compact per-file diffs. See [docs/patch-tools.md](docs/patch-tools.md).
|
|
73
73
|
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@ import fs from "node:fs/promises";
|
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
-
import { stripMarkdown } from "../src/interactive-cli.js";
|
|
7
|
+
import { buildPromptLayout, stripMarkdown } from "../src/interactive-cli.js";
|
|
8
8
|
|
|
9
9
|
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
10
10
|
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "agintiflow-cli-chat-"));
|
|
@@ -70,6 +70,21 @@ try {
|
|
|
70
70
|
throw new Error("terminal markdown renderer dropped table content");
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
const promptLayout = buildPromptLayout(`${"x".repeat(180)}\nsecond line`, 95, 80, 24);
|
|
74
|
+
const visibleLengths = promptLayout.renderedRows.map((line) =>
|
|
75
|
+
line.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, "").length
|
|
76
|
+
);
|
|
77
|
+
if (promptLayout.rows.length < 4 || Math.max(...visibleLengths) > 79) {
|
|
78
|
+
throw new Error("terminal prompt layout did not wrap long multiline input safely");
|
|
79
|
+
}
|
|
80
|
+
if (promptLayout.cursorRow < 0 || promptLayout.cursorColumn < 0) {
|
|
81
|
+
throw new Error("terminal prompt layout returned an invalid cursor location");
|
|
82
|
+
}
|
|
83
|
+
const hugePromptLayout = buildPromptLayout(Array.from({ length: 30 }, (_unused, index) => `line ${index + 1}`).join("\n"), 120, 80, 20);
|
|
84
|
+
if (hugePromptLayout.renderedRows.length > 12 || !hugePromptLayout.renderedRows.some((line) => line.includes("earlier input row"))) {
|
|
85
|
+
throw new Error("terminal prompt layout did not bound redraw size for large prompts");
|
|
86
|
+
}
|
|
87
|
+
|
|
73
88
|
const result = await runChat("Create notes/interactive.md with a short CLI chat smoke message\n/exit\n");
|
|
74
89
|
const written = await fs.readFile(path.join(tempRoot, "notes/interactive.md"), "utf8");
|
|
75
90
|
if (!written.includes("Created by AgInTiFlow mock mode.")) {
|
|
@@ -92,7 +107,7 @@ try {
|
|
|
92
107
|
{
|
|
93
108
|
ok: true,
|
|
94
109
|
projectRoot: tempRoot,
|
|
95
|
-
checks: ["markdown-render", "interactive-chat", "mock-file-write", "run-status", "resume-latest"],
|
|
110
|
+
checks: ["markdown-render", "prompt-layout", "interactive-chat", "mock-file-write", "run-status", "resume-latest"],
|
|
96
111
|
},
|
|
97
112
|
null,
|
|
98
113
|
2
|
package/src/interactive-cli.js
CHANGED
|
@@ -53,6 +53,7 @@ const SLASH_COMMANDS = [
|
|
|
53
53
|
"/web",
|
|
54
54
|
"/exit",
|
|
55
55
|
];
|
|
56
|
+
const promptHistory = [];
|
|
56
57
|
|
|
57
58
|
function color(value, ...codes) {
|
|
58
59
|
if (!useColor || codes.length === 0) return String(value);
|
|
@@ -71,6 +72,18 @@ function terminalWidth() {
|
|
|
71
72
|
return Math.max(Number(output.columns) || 80, 40);
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
function terminalHeight() {
|
|
76
|
+
return Math.max(Number(output.rows) || 24, 10);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function editorWidth(width = terminalWidth()) {
|
|
80
|
+
return Math.max(Number(width) - 1, 39);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function promptViewportRows(height = terminalHeight()) {
|
|
84
|
+
return Math.max(Math.min(Math.floor(Number(height) * 0.42), 10), 4);
|
|
85
|
+
}
|
|
86
|
+
|
|
74
87
|
function visualLength(value) {
|
|
75
88
|
return stripAnsi(value).length;
|
|
76
89
|
}
|
|
@@ -80,10 +93,11 @@ function padVisible(value, width) {
|
|
|
80
93
|
return `${value}${" ".repeat(padding)}`;
|
|
81
94
|
}
|
|
82
95
|
|
|
83
|
-
function panelLine(content = "", bgCode = ansi.systemBg) {
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
96
|
+
function panelLine(content = "", bgCode = ansi.systemBg, width = editorWidth()) {
|
|
97
|
+
const raw = String(content || "");
|
|
98
|
+
const safeContent = visualLength(raw) > width ? stripAnsi(raw).slice(0, width) : raw;
|
|
99
|
+
if (!useColor) return padVisible(safeContent, width);
|
|
100
|
+
const padded = padVisible(safeContent, width).replaceAll(ansi.reset, `${ansi.reset}${bgCode}`);
|
|
87
101
|
return `${bgCode}${padded}${ansi.reset}`;
|
|
88
102
|
}
|
|
89
103
|
|
|
@@ -113,6 +127,10 @@ function commandSuggestions(line = "") {
|
|
|
113
127
|
return SLASH_COMMANDS.filter((command) => command.startsWith(trimmed)).slice(0, 8);
|
|
114
128
|
}
|
|
115
129
|
|
|
130
|
+
function clamp(value, min, max) {
|
|
131
|
+
return Math.min(Math.max(value, min), max);
|
|
132
|
+
}
|
|
133
|
+
|
|
116
134
|
export function stripMarkdown(text) {
|
|
117
135
|
const lines = String(text || "").split(/\r?\n/);
|
|
118
136
|
let inFence = false;
|
|
@@ -344,29 +362,236 @@ function printHelp() {
|
|
|
344
362
|
);
|
|
345
363
|
}
|
|
346
364
|
|
|
347
|
-
function
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
365
|
+
function logicalLinesWithOffsets(buffer = "") {
|
|
366
|
+
const lines = String(buffer).split("\n");
|
|
367
|
+
let offset = 0;
|
|
368
|
+
return lines.map((line, index) => {
|
|
369
|
+
const start = offset;
|
|
370
|
+
const end = start + line.length;
|
|
371
|
+
offset = end + 1;
|
|
372
|
+
return {
|
|
373
|
+
text: line,
|
|
374
|
+
start,
|
|
375
|
+
end,
|
|
376
|
+
hasNewline: index < lines.length - 1,
|
|
377
|
+
};
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function promptVisibleWindow(rows, cursorRow, height = terminalHeight()) {
|
|
382
|
+
const maxRows = promptViewportRows(height);
|
|
383
|
+
if (rows.length <= maxRows) {
|
|
384
|
+
return { start: 0, end: rows.length, topHidden: 0, bottomHidden: 0 };
|
|
351
385
|
}
|
|
386
|
+
const half = Math.floor(maxRows / 2);
|
|
387
|
+
const start = clamp(cursorRow - half, 0, rows.length - maxRows);
|
|
388
|
+
const end = start + maxRows;
|
|
389
|
+
return {
|
|
390
|
+
start,
|
|
391
|
+
end,
|
|
392
|
+
topHidden: start,
|
|
393
|
+
bottomHidden: rows.length - end,
|
|
394
|
+
};
|
|
395
|
+
}
|
|
352
396
|
|
|
353
|
-
|
|
354
|
-
const
|
|
355
|
-
const
|
|
397
|
+
export function buildPromptLayout(buffer = "", cursor = 0, width = terminalWidth(), height = terminalHeight()) {
|
|
398
|
+
const safeBuffer = String(buffer || "");
|
|
399
|
+
const safeCursor = clamp(Number(cursor) || 0, 0, safeBuffer.length);
|
|
400
|
+
const lineWidth = editorWidth(width);
|
|
356
401
|
const firstPrefix = " user ";
|
|
357
402
|
const nextPrefix = " ... ";
|
|
358
|
-
const
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
403
|
+
const firstInnerWidth = Math.max(lineWidth - firstPrefix.length, 8);
|
|
404
|
+
const nextInnerWidth = Math.max(lineWidth - nextPrefix.length, 8);
|
|
405
|
+
const rows = [];
|
|
406
|
+
|
|
407
|
+
for (const [lineIndex, line] of logicalLinesWithOffsets(safeBuffer).entries()) {
|
|
408
|
+
let localOffset = 0;
|
|
409
|
+
const text = line.text;
|
|
410
|
+
if (!text) {
|
|
411
|
+
rows.push({
|
|
412
|
+
prefix: lineIndex === 0 ? firstPrefix : nextPrefix,
|
|
413
|
+
text: "",
|
|
414
|
+
start: line.start,
|
|
415
|
+
end: line.start,
|
|
416
|
+
innerWidth: lineIndex === 0 ? firstInnerWidth : nextInnerWidth,
|
|
417
|
+
lineStart: line.start,
|
|
418
|
+
lineEnd: line.end,
|
|
419
|
+
hasNewline: line.hasNewline,
|
|
420
|
+
});
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
while (localOffset < text.length) {
|
|
425
|
+
const prefix = lineIndex === 0 && localOffset === 0 ? firstPrefix : nextPrefix;
|
|
426
|
+
const innerWidth = prefix === firstPrefix ? firstInnerWidth : nextInnerWidth;
|
|
427
|
+
const chunk = text.slice(localOffset, localOffset + innerWidth);
|
|
428
|
+
rows.push({
|
|
429
|
+
prefix,
|
|
430
|
+
text: chunk,
|
|
431
|
+
start: line.start + localOffset,
|
|
432
|
+
end: line.start + localOffset + chunk.length,
|
|
433
|
+
innerWidth,
|
|
434
|
+
lineStart: line.start,
|
|
435
|
+
lineEnd: line.end,
|
|
436
|
+
hasNewline: line.hasNewline,
|
|
437
|
+
});
|
|
438
|
+
localOffset += chunk.length;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (rows.length === 0) {
|
|
443
|
+
rows.push({
|
|
444
|
+
prefix: firstPrefix,
|
|
445
|
+
text: "",
|
|
446
|
+
start: 0,
|
|
447
|
+
end: 0,
|
|
448
|
+
innerWidth: firstInnerWidth,
|
|
449
|
+
lineStart: 0,
|
|
450
|
+
lineEnd: 0,
|
|
451
|
+
hasNewline: false,
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const last = rows[rows.length - 1];
|
|
456
|
+
if (safeCursor === safeBuffer.length && last.end === safeCursor && last.text.length >= last.innerWidth) {
|
|
457
|
+
rows.push({
|
|
458
|
+
prefix: nextPrefix,
|
|
459
|
+
text: "",
|
|
460
|
+
start: safeCursor,
|
|
461
|
+
end: safeCursor,
|
|
462
|
+
innerWidth: nextInnerWidth,
|
|
463
|
+
lineStart: safeCursor,
|
|
464
|
+
lineEnd: safeCursor,
|
|
465
|
+
hasNewline: false,
|
|
466
|
+
});
|
|
364
467
|
}
|
|
468
|
+
|
|
469
|
+
let cursorRow = rows.length - 1;
|
|
470
|
+
let cursorColumn = rows[cursorRow].prefix.length;
|
|
471
|
+
for (let index = 0; index < rows.length; index += 1) {
|
|
472
|
+
const row = rows[index];
|
|
473
|
+
const next = rows[index + 1];
|
|
474
|
+
if (safeCursor < row.end) {
|
|
475
|
+
cursorRow = index;
|
|
476
|
+
cursorColumn = row.prefix.length + safeCursor - row.start;
|
|
477
|
+
break;
|
|
478
|
+
}
|
|
479
|
+
if (safeCursor === row.end) {
|
|
480
|
+
if (next && next.start === safeCursor && row.text.length >= row.innerWidth) continue;
|
|
481
|
+
cursorRow = index;
|
|
482
|
+
cursorColumn = row.prefix.length + safeCursor - row.start;
|
|
483
|
+
break;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const suggestions = commandSuggestions(safeBuffer.split("\n")[0] || "");
|
|
488
|
+
const emptyHint = "type a request, /help, Enter to send, Ctrl+J for newline";
|
|
489
|
+
const visible = promptVisibleWindow(rows, cursorRow, height);
|
|
490
|
+
const renderedRows = [];
|
|
491
|
+
let renderedCursorRow = cursorRow - visible.start;
|
|
492
|
+
|
|
493
|
+
if (visible.topHidden > 0) {
|
|
494
|
+
renderedRows.push(panelLine(` ... ${visible.topHidden} earlier input row${visible.topHidden === 1 ? "" : "s"}`, ansi.systemBg, lineWidth));
|
|
495
|
+
renderedCursorRow += 1;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
for (const row of rows.slice(visible.start, visible.end)) {
|
|
499
|
+
const content = safeBuffer ? `${row.prefix}${row.text}` : `${row.prefix}${emptyHint}`;
|
|
500
|
+
renderedRows.push(panelLine(content, ansi.userBg, lineWidth));
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (visible.bottomHidden > 0) {
|
|
504
|
+
renderedRows.push(panelLine(` ... ${visible.bottomHidden} later input row${visible.bottomHidden === 1 ? "" : "s"}`, ansi.systemBg, lineWidth));
|
|
505
|
+
}
|
|
506
|
+
|
|
365
507
|
if (suggestions.length > 0) {
|
|
366
|
-
|
|
508
|
+
renderedRows.push(panelLine(` hint ${suggestions.join(" ")}`, ansi.systemBg, lineWidth));
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return {
|
|
512
|
+
rows,
|
|
513
|
+
renderedRows,
|
|
514
|
+
cursorRow: renderedCursorRow,
|
|
515
|
+
absoluteCursorRow: cursorRow,
|
|
516
|
+
cursorColumn: clamp(cursorColumn, 0, editorWidth(width) - 1),
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function cursorLocation(layout, cursor) {
|
|
521
|
+
for (let index = 0; index < layout.rows.length; index += 1) {
|
|
522
|
+
const row = layout.rows[index];
|
|
523
|
+
const next = layout.rows[index + 1];
|
|
524
|
+
if (cursor < row.end) return { rowIndex: index, column: cursor - row.start };
|
|
525
|
+
if (cursor === row.end) {
|
|
526
|
+
if (next && next.start === cursor && row.text.length >= row.innerWidth) continue;
|
|
527
|
+
return { rowIndex: index, column: cursor - row.start };
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
const rowIndex = Math.max(layout.rows.length - 1, 0);
|
|
531
|
+
const row = layout.rows[rowIndex];
|
|
532
|
+
return { rowIndex, column: Math.max(row.end - row.start, 0) };
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function clearRenderedPrompt(previous) {
|
|
536
|
+
if (!previous.lineCount) return;
|
|
537
|
+
const below = previous.lineCount - 1 - previous.cursorRow;
|
|
538
|
+
if (below > 0) output.write(`\x1b[${below}B`);
|
|
539
|
+
output.write(`\r${ansi.clearLine}`);
|
|
540
|
+
for (let index = 1; index < previous.lineCount; index += 1) {
|
|
541
|
+
output.write(`\x1b[1A\r${ansi.clearLine}`);
|
|
367
542
|
}
|
|
368
|
-
|
|
369
|
-
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function renderPromptBuffer(buffer, cursor, previous = { lineCount: 0, cursorRow: 0 }) {
|
|
546
|
+
output.write(ansi.cursorHide);
|
|
547
|
+
clearRenderedPrompt(previous);
|
|
548
|
+
const layout = buildPromptLayout(buffer, cursor);
|
|
549
|
+
output.write(layout.renderedRows.join("\n"));
|
|
550
|
+
const below = layout.renderedRows.length - 1 - layout.cursorRow;
|
|
551
|
+
if (below > 0) output.write(`\x1b[${below}A`);
|
|
552
|
+
output.write(`\r\x1b[${layout.cursorColumn + 1}G`);
|
|
553
|
+
output.write(ansi.cursorShow);
|
|
554
|
+
return {
|
|
555
|
+
lineCount: layout.renderedRows.length,
|
|
556
|
+
cursorRow: layout.cursorRow,
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function moveToPromptBottom(rendered) {
|
|
561
|
+
const below = Math.max((rendered?.lineCount || 1) - 1 - (rendered?.cursorRow || 0), 0);
|
|
562
|
+
if (below > 0) output.write(`\x1b[${below}B`);
|
|
563
|
+
output.write("\r");
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function lineBounds(buffer, cursor) {
|
|
567
|
+
const safeCursor = clamp(cursor, 0, buffer.length);
|
|
568
|
+
const start = buffer.lastIndexOf("\n", safeCursor - 1) + 1;
|
|
569
|
+
const nextNewline = buffer.indexOf("\n", safeCursor);
|
|
570
|
+
const end = nextNewline === -1 ? buffer.length : nextNewline;
|
|
571
|
+
return { start, end };
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function insertAt(buffer, cursor, text) {
|
|
575
|
+
return {
|
|
576
|
+
buffer: `${buffer.slice(0, cursor)}${text}${buffer.slice(cursor)}`,
|
|
577
|
+
cursor: cursor + text.length,
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function removeBefore(buffer, cursor) {
|
|
582
|
+
if (cursor <= 0) return { buffer, cursor };
|
|
583
|
+
return {
|
|
584
|
+
buffer: `${buffer.slice(0, cursor - 1)}${buffer.slice(cursor)}`,
|
|
585
|
+
cursor: cursor - 1,
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function removeAt(buffer, cursor) {
|
|
590
|
+
if (cursor >= buffer.length) return { buffer, cursor };
|
|
591
|
+
return {
|
|
592
|
+
buffer: `${buffer.slice(0, cursor)}${buffer.slice(cursor + 1)}`,
|
|
593
|
+
cursor,
|
|
594
|
+
};
|
|
370
595
|
}
|
|
371
596
|
|
|
372
597
|
function createAbortError(message = "Aborted with Ctrl+C") {
|
|
@@ -381,34 +606,97 @@ function readTtyPrompt() {
|
|
|
381
606
|
emitKeypressEvents(input);
|
|
382
607
|
const wasRaw = Boolean(input.isRaw);
|
|
383
608
|
let buffer = "";
|
|
384
|
-
let
|
|
609
|
+
let cursor = 0;
|
|
610
|
+
let rendered = { lineCount: 0, cursorRow: 0 };
|
|
611
|
+
let preferredColumn = null;
|
|
612
|
+
let historyIndex = promptHistory.length;
|
|
613
|
+
let draft = "";
|
|
614
|
+
let redrawHandle = null;
|
|
385
615
|
|
|
386
616
|
const cleanup = () => {
|
|
617
|
+
if (redrawHandle) {
|
|
618
|
+
clearImmediate(redrawHandle);
|
|
619
|
+
redrawHandle = null;
|
|
620
|
+
}
|
|
387
621
|
input.off("keypress", handler);
|
|
388
622
|
if (typeof input.setRawMode === "function") input.setRawMode(wasRaw);
|
|
389
623
|
input.pause();
|
|
390
624
|
output.write(ansi.cursorShow);
|
|
391
625
|
};
|
|
392
626
|
|
|
627
|
+
const renderNow = () => {
|
|
628
|
+
if (redrawHandle) {
|
|
629
|
+
clearImmediate(redrawHandle);
|
|
630
|
+
redrawHandle = null;
|
|
631
|
+
}
|
|
632
|
+
rendered = renderPromptBuffer(buffer, cursor, rendered);
|
|
633
|
+
};
|
|
634
|
+
|
|
393
635
|
const redraw = () => {
|
|
394
|
-
|
|
636
|
+
if (redrawHandle) return;
|
|
637
|
+
redrawHandle = setImmediate(() => {
|
|
638
|
+
redrawHandle = null;
|
|
639
|
+
rendered = renderPromptBuffer(buffer, cursor, rendered);
|
|
640
|
+
});
|
|
395
641
|
};
|
|
396
642
|
|
|
397
643
|
const submit = () => {
|
|
644
|
+
renderNow();
|
|
645
|
+
moveToPromptBottom(rendered);
|
|
398
646
|
cleanup();
|
|
399
647
|
output.write("\n");
|
|
648
|
+
const saved = buffer.trim();
|
|
649
|
+
if (saved && promptHistory[promptHistory.length - 1] !== buffer) promptHistory.push(buffer);
|
|
400
650
|
resolve(buffer);
|
|
401
651
|
};
|
|
402
652
|
|
|
653
|
+
const setBuffer = (nextBuffer, nextCursor = nextBuffer.length) => {
|
|
654
|
+
buffer = nextBuffer;
|
|
655
|
+
cursor = clamp(nextCursor, 0, buffer.length);
|
|
656
|
+
preferredColumn = null;
|
|
657
|
+
redraw();
|
|
658
|
+
};
|
|
659
|
+
|
|
660
|
+
const moveVertical = (direction) => {
|
|
661
|
+
const layout = buildPromptLayout(buffer, cursor);
|
|
662
|
+
const location = cursorLocation(layout, cursor);
|
|
663
|
+
const targetRowIndex = location.rowIndex + direction;
|
|
664
|
+
if (targetRowIndex < 0) {
|
|
665
|
+
if (promptHistory.length === 0) return;
|
|
666
|
+
if (historyIndex === promptHistory.length) draft = buffer;
|
|
667
|
+
historyIndex = Math.max(historyIndex - 1, 0);
|
|
668
|
+
setBuffer(promptHistory[historyIndex], promptHistory[historyIndex].length);
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
if (targetRowIndex >= layout.rows.length) {
|
|
672
|
+
if (historyIndex < promptHistory.length - 1) {
|
|
673
|
+
historyIndex += 1;
|
|
674
|
+
setBuffer(promptHistory[historyIndex], promptHistory[historyIndex].length);
|
|
675
|
+
} else if (historyIndex < promptHistory.length) {
|
|
676
|
+
historyIndex = promptHistory.length;
|
|
677
|
+
setBuffer(draft, draft.length);
|
|
678
|
+
}
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
const currentColumn = preferredColumn ?? location.column;
|
|
682
|
+
const targetRow = layout.rows[targetRowIndex];
|
|
683
|
+
cursor = targetRow.start + Math.min(currentColumn, targetRow.end - targetRow.start);
|
|
684
|
+
preferredColumn = currentColumn;
|
|
685
|
+
redraw();
|
|
686
|
+
};
|
|
687
|
+
|
|
403
688
|
const handler = (str = "", key = {}) => {
|
|
404
689
|
if (key.ctrl && key.name === "c") {
|
|
690
|
+
renderNow();
|
|
691
|
+
moveToPromptBottom(rendered);
|
|
405
692
|
cleanup();
|
|
406
693
|
output.write("\n");
|
|
407
694
|
reject(createAbortError());
|
|
408
695
|
return;
|
|
409
696
|
}
|
|
410
697
|
if ((key.ctrl && key.name === "j") || key.sequence === "\n") {
|
|
411
|
-
buffer
|
|
698
|
+
({ buffer, cursor } = insertAt(buffer, cursor, "\n"));
|
|
699
|
+
preferredColumn = null;
|
|
412
700
|
redraw();
|
|
413
701
|
return;
|
|
414
702
|
}
|
|
@@ -417,7 +705,61 @@ function readTtyPrompt() {
|
|
|
417
705
|
return;
|
|
418
706
|
}
|
|
419
707
|
if (key.name === "backspace") {
|
|
420
|
-
buffer = buffer
|
|
708
|
+
({ buffer, cursor } = removeBefore(buffer, cursor));
|
|
709
|
+
preferredColumn = null;
|
|
710
|
+
redraw();
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
if (key.name === "delete") {
|
|
714
|
+
({ buffer, cursor } = removeAt(buffer, cursor));
|
|
715
|
+
preferredColumn = null;
|
|
716
|
+
redraw();
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
if (key.name === "left") {
|
|
720
|
+
cursor = Math.max(cursor - 1, 0);
|
|
721
|
+
preferredColumn = null;
|
|
722
|
+
redraw();
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
if (key.name === "right") {
|
|
726
|
+
cursor = Math.min(cursor + 1, buffer.length);
|
|
727
|
+
preferredColumn = null;
|
|
728
|
+
redraw();
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
if (key.name === "up") {
|
|
732
|
+
moveVertical(-1);
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
if (key.name === "down") {
|
|
736
|
+
moveVertical(1);
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
if ((key.ctrl && key.name === "a") || key.name === "home") {
|
|
740
|
+
cursor = lineBounds(buffer, cursor).start;
|
|
741
|
+
preferredColumn = null;
|
|
742
|
+
redraw();
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
if ((key.ctrl && key.name === "e") || key.name === "end") {
|
|
746
|
+
cursor = lineBounds(buffer, cursor).end;
|
|
747
|
+
preferredColumn = null;
|
|
748
|
+
redraw();
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
if (key.ctrl && key.name === "u") {
|
|
752
|
+
const bounds = lineBounds(buffer, cursor);
|
|
753
|
+
buffer = `${buffer.slice(0, bounds.start)}${buffer.slice(cursor)}`;
|
|
754
|
+
cursor = bounds.start;
|
|
755
|
+
preferredColumn = null;
|
|
756
|
+
redraw();
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
if (key.ctrl && key.name === "k") {
|
|
760
|
+
const bounds = lineBounds(buffer, cursor);
|
|
761
|
+
buffer = `${buffer.slice(0, cursor)}${buffer.slice(bounds.end)}`;
|
|
762
|
+
preferredColumn = null;
|
|
421
763
|
redraw();
|
|
422
764
|
return;
|
|
423
765
|
}
|
|
@@ -425,27 +767,32 @@ function readTtyPrompt() {
|
|
|
425
767
|
const suggestions = commandSuggestions(buffer.split("\n")[0] || "");
|
|
426
768
|
if (suggestions.length === 1) {
|
|
427
769
|
buffer = suggestions[0];
|
|
770
|
+
cursor = buffer.length;
|
|
428
771
|
}
|
|
772
|
+
preferredColumn = null;
|
|
429
773
|
redraw();
|
|
430
774
|
return;
|
|
431
775
|
}
|
|
432
776
|
if (key.name === "escape") {
|
|
433
777
|
buffer = "";
|
|
778
|
+
cursor = 0;
|
|
779
|
+
preferredColumn = null;
|
|
434
780
|
redraw();
|
|
435
781
|
return;
|
|
436
782
|
}
|
|
437
783
|
if (key.ctrl || key.meta) return;
|
|
438
784
|
if (str && !key.sequence?.startsWith("\x1b")) {
|
|
439
|
-
|
|
785
|
+
const text = str.replace(/\r/g, "");
|
|
786
|
+
({ buffer, cursor } = insertAt(buffer, cursor, text));
|
|
787
|
+
preferredColumn = null;
|
|
440
788
|
redraw();
|
|
441
789
|
}
|
|
442
790
|
};
|
|
443
791
|
|
|
444
792
|
input.resume();
|
|
445
793
|
input.setRawMode(true);
|
|
446
|
-
output.write(ansi.cursorHide);
|
|
447
794
|
input.on("keypress", handler);
|
|
448
|
-
|
|
795
|
+
renderNow();
|
|
449
796
|
});
|
|
450
797
|
}
|
|
451
798
|
|