@vincemakes/kiso-code 0.1.14 → 0.1.16
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/dist/body.d.ts +121 -0
- package/dist/body.js +478 -0
- package/dist/diff.d.ts +32 -0
- package/dist/diff.js +122 -0
- package/dist/dock.d.ts +13 -15
- package/dist/dock.js +11 -37
- package/dist/index.js +231 -142
- package/dist/mode.d.ts +33 -0
- package/dist/mode.js +93 -0
- package/dist/render.d.ts +1 -0
- package/dist/render.js +2 -2
- package/package.json +7 -7
package/dist/diff.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v2e — the diff renderer: edit/write changes as inline ± lines, zero
|
|
3
|
+
* dependencies, no syntax highlighting (the spec's scope line). Shown at
|
|
4
|
+
* the approval moment ONLY — the frozen summary stays one line (v2d's
|
|
5
|
+
* anti-leak principle), /last has the full data.
|
|
6
|
+
*
|
|
7
|
+
* edit_file diffs IN PLACE (the search→replace windows are known — no
|
|
8
|
+
* general engine needed); write_file does a row-level LCS over the old
|
|
9
|
+
* file (small files are the target). Context: 2 rows each side. The
|
|
10
|
+
* RENDERER truncates (18 head + 18 tail + "… N lines"); the stats come
|
|
11
|
+
* from the full diff.
|
|
12
|
+
*/
|
|
13
|
+
/** A line-level LCS diff — the classic two-row DP, ~small inputs. */
|
|
14
|
+
function lcsDiff(oldLines, newLines) {
|
|
15
|
+
const n = oldLines.length;
|
|
16
|
+
const m = newLines.length;
|
|
17
|
+
const dp = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));
|
|
18
|
+
for (let i = n - 1; i >= 0; i -= 1) {
|
|
19
|
+
for (let j = m - 1; j >= 0; j -= 1) {
|
|
20
|
+
dp[i][j] = oldLines[i] === newLines[j] ? dp[i + 1][j + 1] + 1 : Math.max(dp[i + 1][j], dp[i][j + 1]);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const out = [];
|
|
24
|
+
let i = 0;
|
|
25
|
+
let j = 0;
|
|
26
|
+
while (i < n && j < m) {
|
|
27
|
+
if (oldLines[i] === newLines[j]) {
|
|
28
|
+
out.push({ kind: " ", text: oldLines[i] });
|
|
29
|
+
i += 1;
|
|
30
|
+
j += 1;
|
|
31
|
+
}
|
|
32
|
+
else if (dp[i + 1][j] >= dp[i][j + 1]) {
|
|
33
|
+
out.push({ kind: "-", text: oldLines[i] });
|
|
34
|
+
i += 1;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
out.push({ kind: "+", text: newLines[j] });
|
|
38
|
+
j += 1;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
while (i < n) {
|
|
42
|
+
out.push({ kind: "-", text: oldLines[i] });
|
|
43
|
+
i += 1;
|
|
44
|
+
}
|
|
45
|
+
while (j < m) {
|
|
46
|
+
out.push({ kind: "+", text: newLines[j] });
|
|
47
|
+
j += 1;
|
|
48
|
+
}
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
/** Keep 2 context rows around each change — the unified-style window. */
|
|
52
|
+
function withContext(diff) {
|
|
53
|
+
const out = [];
|
|
54
|
+
let lastAdded = -10;
|
|
55
|
+
for (let k = 0; k < diff.length; k += 1) {
|
|
56
|
+
if (diff[k].kind === " ")
|
|
57
|
+
continue;
|
|
58
|
+
const from = Math.max(0, k - 2);
|
|
59
|
+
const to = Math.min(diff.length - 1, k + 2);
|
|
60
|
+
for (let c = from; c <= to; c += 1) {
|
|
61
|
+
if (c > lastAdded) {
|
|
62
|
+
out.push(diff[c]);
|
|
63
|
+
lastAdded = c;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
lastAdded = to;
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
const MAX_DIFF_LINES = 40; // the RENDERED cap
|
|
71
|
+
const TRUNCATE_KEEP = 18;
|
|
72
|
+
/** The RENDERER's truncation: head + "… N lines (/last for full)" + tail. */
|
|
73
|
+
export function truncateDiff(diff) {
|
|
74
|
+
if (diff.length <= MAX_DIFF_LINES)
|
|
75
|
+
return diff;
|
|
76
|
+
const omitted = diff.length - 2 * TRUNCATE_KEEP;
|
|
77
|
+
return [
|
|
78
|
+
...diff.slice(0, TRUNCATE_KEEP),
|
|
79
|
+
{ kind: " ", text: `… ${omitted} lines (/last for full)` },
|
|
80
|
+
...diff.slice(diff.length - TRUNCATE_KEEP),
|
|
81
|
+
];
|
|
82
|
+
}
|
|
83
|
+
function stats(diff) {
|
|
84
|
+
let added = 0;
|
|
85
|
+
let removed = 0;
|
|
86
|
+
for (const d of diff) {
|
|
87
|
+
if (d.kind === "+")
|
|
88
|
+
added += 1;
|
|
89
|
+
else if (d.kind === "-")
|
|
90
|
+
removed += 1;
|
|
91
|
+
}
|
|
92
|
+
return { added, removed };
|
|
93
|
+
}
|
|
94
|
+
/** edit_file: the search→replace windows replace in place — the changed
|
|
95
|
+
* region is KNOWN, so the diff is the old window vs the new window,
|
|
96
|
+
* context from the surrounding file. */
|
|
97
|
+
export function editFileDiff(oldContent, search, replace) {
|
|
98
|
+
const oldLines = oldContent.split("\n");
|
|
99
|
+
const searchLines = search.split("\n");
|
|
100
|
+
const replaceLines = replace.split("\n");
|
|
101
|
+
// Locate the search window (the first occurrence — the edit tool's own
|
|
102
|
+
// semantics); no occurrence → the whole file is the old side.
|
|
103
|
+
let at = -1;
|
|
104
|
+
for (let i = 0; i + searchLines.length <= oldLines.length; i += 1) {
|
|
105
|
+
if (oldLines.slice(i, i + searchLines.length).join("\n") === search) {
|
|
106
|
+
at = i;
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const lines = at < 0 ? withContext(lcsDiff(oldLines, replaceLines)) : withContext(lcsDiff(oldLines, [...oldLines.slice(0, at), ...replaceLines, ...oldLines.slice(at + searchLines.length)]));
|
|
111
|
+
return { lines, ...stats(lines) };
|
|
112
|
+
}
|
|
113
|
+
/** write_file: a new file is all +; an existing file diffs row-level
|
|
114
|
+
* against its old content. */
|
|
115
|
+
export function writeFileDiff(oldContent, newContent) {
|
|
116
|
+
if (oldContent === null) {
|
|
117
|
+
const lines = newContent.split("\n").map((text) => ({ kind: "+", text }));
|
|
118
|
+
return { lines, added: lines.length, removed: 0 };
|
|
119
|
+
}
|
|
120
|
+
const lines = withContext(lcsDiff(oldContent.split("\n"), newContent.split("\n")));
|
|
121
|
+
return { lines, ...stats(lines) };
|
|
122
|
+
}
|
package/dist/dock.d.ts
CHANGED
|
@@ -27,7 +27,11 @@ export declare class Dock {
|
|
|
27
27
|
line: string;
|
|
28
28
|
cursor: number;
|
|
29
29
|
}, prompt: string): void;
|
|
30
|
-
/** Enter docked mode:
|
|
30
|
+
/** Enter docked mode: draw the chrome. #13 (P1): the DECSTBM scroll
|
|
31
|
+
* region is GONE — v2d-B (ADR-0040): the body uses plain LF scrolling
|
|
32
|
+
* so frozen lines enter the native scrollback deterministically
|
|
33
|
+
* (region-scrolled lines are terminal-dependent — some terminals drop
|
|
34
|
+
* them). The dock rows are redrawn by the body after every scroll. A
|
|
31
35
|
* TTY without a real window size (rows < 4) stays in the v2a line
|
|
32
36
|
* mode — the bottom three rows need room to exist. */
|
|
33
37
|
enter(): void;
|
|
@@ -36,21 +40,15 @@ export declare class Dock {
|
|
|
36
40
|
* from main's finally on EVERY exit path (kill -9 excepted — README:
|
|
37
41
|
* `reset` saves it). */
|
|
38
42
|
exit(): void;
|
|
39
|
-
/** SIGWINCH: recompute the
|
|
43
|
+
/** SIGWINCH: recompute the size, redraw the chrome. */
|
|
40
44
|
onResize(): void;
|
|
41
|
-
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* width
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
|
|
48
|
-
* cosmetics: readline tracks its cursor internally and NEVER
|
|
49
|
-
* re-syncs after an external move — a body write that left the
|
|
50
|
-
* cursor at column 1 made the next keystroke overwrite the prompt
|
|
51
|
-
* (probe-confirmed; the dock's redraw self-repaired ~200ms later,
|
|
52
|
-
* which read the user as cursor drift). */
|
|
53
|
-
writeBody(text: string): void;
|
|
45
|
+
/** The input line's edit column — prompt width + cursor + 1. The
|
|
46
|
+
* dock's redraw and the body's cursor return both end here, so the
|
|
47
|
+
* ACTUAL cursor always equals what the editor tracks. The width is
|
|
48
|
+
* DISPLAY width (the editor's cursor column is already width-based —
|
|
49
|
+
* the CJK drift root cause, editor.ts). v2d: public — the Body's
|
|
50
|
+
* render loop ends at this column. */
|
|
51
|
+
editCol(): number;
|
|
54
52
|
/** The status bar's base text (usage, ctx, session, …). */
|
|
55
53
|
setStatus(text: string): void;
|
|
56
54
|
/** The live tail — the spinner glyph or "running <tool> Ns". */
|
package/dist/dock.js
CHANGED
|
@@ -41,7 +41,11 @@ export class Dock {
|
|
|
41
41
|
this.#inputState = state;
|
|
42
42
|
this.#inputPrompt = prompt;
|
|
43
43
|
}
|
|
44
|
-
/** Enter docked mode:
|
|
44
|
+
/** Enter docked mode: draw the chrome. #13 (P1): the DECSTBM scroll
|
|
45
|
+
* region is GONE — v2d-B (ADR-0040): the body uses plain LF scrolling
|
|
46
|
+
* so frozen lines enter the native scrollback deterministically
|
|
47
|
+
* (region-scrolled lines are terminal-dependent — some terminals drop
|
|
48
|
+
* them). The dock rows are redrawn by the body after every scroll. A
|
|
45
49
|
* TTY without a real window size (rows < 4) stays in the v2a line
|
|
46
50
|
* mode — the bottom three rows need room to exist. */
|
|
47
51
|
enter() {
|
|
@@ -53,7 +57,6 @@ export class Dock {
|
|
|
53
57
|
this.#width = process.stdout.columns ?? 80;
|
|
54
58
|
this.#bodyRow = 1;
|
|
55
59
|
this.#bodyCol = 1;
|
|
56
|
-
process.stdout.write(`\x1b[1;${this.#height - 3}r`); // scroll region: top .. H-3
|
|
57
60
|
this.redraw();
|
|
58
61
|
this.#resizeHandler = () => this.onResize();
|
|
59
62
|
process.stdout.on("resize", this.#resizeHandler);
|
|
@@ -77,52 +80,23 @@ export class Dock {
|
|
|
77
80
|
}
|
|
78
81
|
process.stdout.write(`\x1b[${H};1H`);
|
|
79
82
|
}
|
|
80
|
-
/** SIGWINCH: recompute the
|
|
83
|
+
/** SIGWINCH: recompute the size, redraw the chrome. */
|
|
81
84
|
onResize() {
|
|
82
85
|
if (!this.#active)
|
|
83
86
|
return;
|
|
84
87
|
this.#height = process.stdout.rows ?? this.#height;
|
|
85
88
|
this.#width = process.stdout.columns ?? this.#width;
|
|
86
|
-
process.stdout.write(`\x1b[1;${this.#height - 3}r`);
|
|
87
89
|
this.redraw();
|
|
88
90
|
}
|
|
89
|
-
/** Body output: position the cursor inside the scroll region at the
|
|
90
|
-
* body's tracked position, write, and hand the cursor back to the
|
|
91
|
-
* input line's EDIT position. The row/col tracking is approximate for
|
|
92
|
-
* width-wrapped and wide-char lines (documented) — the region clamp
|
|
93
|
-
* keeps the bottom rows safe regardless.
|
|
94
|
-
*
|
|
95
|
-
* The edit-position return is a correctness requirement, not
|
|
96
|
-
* cosmetics: readline tracks its cursor internally and NEVER
|
|
97
|
-
* re-syncs after an external move — a body write that left the
|
|
98
|
-
* cursor at column 1 made the next keystroke overwrite the prompt
|
|
99
|
-
* (probe-confirmed; the dock's redraw self-repaired ~200ms later,
|
|
100
|
-
* which read the user as cursor drift). */
|
|
101
|
-
writeBody(text) {
|
|
102
|
-
if (!this.#active) {
|
|
103
|
-
process.stdout.write(text);
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
const row = Math.min(this.#bodyRow, this.#height - 3);
|
|
107
|
-
const col = this.#bodyCol > this.#width ? this.#width : this.#bodyCol;
|
|
108
|
-
process.stdout.write(`\x1b[${row};${col}H`);
|
|
109
|
-
process.stdout.write(text);
|
|
110
|
-
for (const ch of text) {
|
|
111
|
-
if (ch === "\n") {
|
|
112
|
-
this.#bodyRow += 1;
|
|
113
|
-
this.#bodyCol = 1;
|
|
114
|
-
}
|
|
115
|
-
else {
|
|
116
|
-
this.#bodyCol += 1;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
process.stdout.write(`\x1b[${this.#height};${this.#inputCol()}H`); // back to the edit position
|
|
120
|
-
}
|
|
121
91
|
/** The input line's edit column — prompt width + cursor + 1. The
|
|
122
92
|
* dock's redraw and the body's cursor return both end here, so the
|
|
123
93
|
* ACTUAL cursor always equals what the editor tracks. The width is
|
|
124
94
|
* DISPLAY width (the editor's cursor column is already width-based —
|
|
125
|
-
* the CJK drift root cause, editor.ts).
|
|
95
|
+
* the CJK drift root cause, editor.ts). v2d: public — the Body's
|
|
96
|
+
* render loop ends at this column. */
|
|
97
|
+
editCol() {
|
|
98
|
+
return this.#inputCol();
|
|
99
|
+
}
|
|
126
100
|
#inputCol() {
|
|
127
101
|
const inp = this.#inputState();
|
|
128
102
|
const promptWidth = displayWidth(this.#inputPrompt.replace(/\x1b\[[0-9;]*m/g, ""));
|