@vincemakes/kiso-tui 0.16.1 → 0.16.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/dist/editor.js +93 -0
- package/package.json +2 -2
package/dist/editor.js
CHANGED
|
@@ -270,6 +270,19 @@ export class Editor {
|
|
|
270
270
|
#history = [];
|
|
271
271
|
#historyIdx = null;
|
|
272
272
|
#preBrowse = [];
|
|
273
|
+
// UD-1: minimal draft undo. Two stacks of FROZEN snapshots beside
|
|
274
|
+
// the one mutable buffer (the KC1 flat-buffer discipline holds —
|
|
275
|
+
// the stacks are history, never a second projection). A checkpoint
|
|
276
|
+
// is pushed only by a gesture about to discard ≥1 code point (the
|
|
277
|
+
// kills, the menu-esc clear, each queue-pop replacement, the
|
|
278
|
+
// @-apply splice); typing and single backspace push nothing — v1
|
|
279
|
+
// is loss-recovery, not char-granular edit history. ctrl+z
|
|
280
|
+
// restores text+cursor exactly; ctrl+y mirrors; undo never
|
|
281
|
+
// discards (what it replaces always lands on the redo stack).
|
|
282
|
+
// Both stacks clear on submit/clearLine — a sent turn is in the
|
|
283
|
+
// durable log and the ↑ history, not a loss.
|
|
284
|
+
#undoStack = [];
|
|
285
|
+
#redoStack = [];
|
|
273
286
|
// W22: the pending-turn queue's bound state — the CLI's live slots
|
|
274
287
|
// (chat.ts). ↑ pops the LAST queued message into the buffer and
|
|
275
288
|
// enters the pop-mode (the walk: repeated ↑ pop older ones, each
|
|
@@ -355,6 +368,8 @@ export class Editor {
|
|
|
355
368
|
return this.#sheetOpen;
|
|
356
369
|
}
|
|
357
370
|
clearLine() {
|
|
371
|
+
this.#undoStack.length = 0; // UD-1
|
|
372
|
+
this.#redoStack.length = 0;
|
|
358
373
|
this.#chars = [];
|
|
359
374
|
this.#cursor = 0;
|
|
360
375
|
this.#scroll = 0;
|
|
@@ -577,6 +592,8 @@ export class Editor {
|
|
|
577
592
|
if (view === null)
|
|
578
593
|
return;
|
|
579
594
|
const insert = [...`@${view.matches[view.selected].path} `].map((ch) => ch.codePointAt(0));
|
|
595
|
+
if (this.#cursor - view.start >= 1)
|
|
596
|
+
this.#checkpoint(); // UD-1
|
|
580
597
|
this.#chars.splice(view.start, this.#cursor - view.start, ...insert);
|
|
581
598
|
this.#cursor = view.start + insert.length;
|
|
582
599
|
this.#atClose();
|
|
@@ -1102,6 +1119,8 @@ export class Editor {
|
|
|
1102
1119
|
// v3 §04: Esc closes the menu and clears the buffer.
|
|
1103
1120
|
// CA-4: the closing esc consumes its burst (the `i += 1`
|
|
1104
1121
|
// convention) — a double-esc can never abort the turn.
|
|
1122
|
+
if (this.#chars.length > 0)
|
|
1123
|
+
this.#checkpoint(); // UD-1
|
|
1105
1124
|
this.#chars = [];
|
|
1106
1125
|
this.#cursor = 0;
|
|
1107
1126
|
this.#scroll = 0;
|
|
@@ -1201,6 +1220,14 @@ export class Editor {
|
|
|
1201
1220
|
this.#killWord();
|
|
1202
1221
|
i += 1;
|
|
1203
1222
|
}
|
|
1223
|
+
else if (c === "\x1a" || c === "\x1f") {
|
|
1224
|
+
this.#undoOp(); // UD-1: ctrl+z (and the readline ctrl+_)
|
|
1225
|
+
i += 1;
|
|
1226
|
+
}
|
|
1227
|
+
else if (c === "\x19") {
|
|
1228
|
+
this.#redoOp(); // UD-1: ctrl+y
|
|
1229
|
+
i += 1;
|
|
1230
|
+
}
|
|
1204
1231
|
else if (c === "\x01") {
|
|
1205
1232
|
this.#cursor = this.#cursorBounds().start; // A3: line-local (a single line starts at 0 — unchanged)
|
|
1206
1233
|
this.#reflow();
|
|
@@ -1891,8 +1918,66 @@ export class Editor {
|
|
|
1891
1918
|
if (!this.#pasting)
|
|
1892
1919
|
this.#onRender();
|
|
1893
1920
|
}
|
|
1921
|
+
// ---- UD-1: the undo machinery ----
|
|
1922
|
+
/** The caps: entries and total code points (a D13-class 260KB paste
|
|
1923
|
+
* fits with room). Evict oldest; both stacks share the shape. */
|
|
1924
|
+
static #UNDO_CAP = 64;
|
|
1925
|
+
static #UNDO_CP_CAP = 2 * 1024 * 1024;
|
|
1926
|
+
#snap() {
|
|
1927
|
+
return { chars: [...this.#chars], cursor: this.#cursor };
|
|
1928
|
+
}
|
|
1929
|
+
#sameSnap(a, b) {
|
|
1930
|
+
return a.cursor === b.cursor && a.chars.length === b.chars.length && a.chars.every((c, i) => c === b.chars[i]);
|
|
1931
|
+
}
|
|
1932
|
+
#capStack(stack) {
|
|
1933
|
+
while (stack.length > _a.#UNDO_CAP)
|
|
1934
|
+
stack.shift();
|
|
1935
|
+
let total = stack.reduce((n, s) => n + s.chars.length, 0);
|
|
1936
|
+
while (stack.length > 1 && total > _a.#UNDO_CP_CAP)
|
|
1937
|
+
total -= stack.shift().chars.length;
|
|
1938
|
+
}
|
|
1939
|
+
/** Push the CURRENT state before a destructive gesture. Always
|
|
1940
|
+
* clears the redo stack (a new destruction forks history); the
|
|
1941
|
+
* push itself is deduped against the top. */
|
|
1942
|
+
#checkpoint() {
|
|
1943
|
+
this.#redoStack.length = 0;
|
|
1944
|
+
const cur = this.#snap();
|
|
1945
|
+
const top = this.#undoStack.at(-1);
|
|
1946
|
+
if (top !== undefined && this.#sameSnap(top, cur))
|
|
1947
|
+
return;
|
|
1948
|
+
this.#undoStack.push(cur);
|
|
1949
|
+
this.#capStack(this.#undoStack);
|
|
1950
|
+
}
|
|
1951
|
+
#restoreSnap(s) {
|
|
1952
|
+
this.#chars = [...s.chars];
|
|
1953
|
+
this.#cursor = s.cursor;
|
|
1954
|
+
this.#scroll = 0;
|
|
1955
|
+
this.#verticalGoalCol = null;
|
|
1956
|
+
this.#reflow();
|
|
1957
|
+
this.#refreshMenu();
|
|
1958
|
+
if (!this.#pasting)
|
|
1959
|
+
this.#onRender();
|
|
1960
|
+
}
|
|
1961
|
+
#undoOp() {
|
|
1962
|
+
const prev = this.#undoStack.pop();
|
|
1963
|
+
if (prev === undefined)
|
|
1964
|
+
return;
|
|
1965
|
+
this.#redoStack.push(this.#snap());
|
|
1966
|
+
this.#capStack(this.#redoStack);
|
|
1967
|
+
this.#restoreSnap(prev);
|
|
1968
|
+
}
|
|
1969
|
+
#redoOp() {
|
|
1970
|
+
const next = this.#redoStack.pop();
|
|
1971
|
+
if (next === undefined)
|
|
1972
|
+
return;
|
|
1973
|
+
this.#undoStack.push(this.#snap());
|
|
1974
|
+
this.#capStack(this.#undoStack);
|
|
1975
|
+
this.#restoreSnap(next);
|
|
1976
|
+
}
|
|
1894
1977
|
#killToStart() {
|
|
1895
1978
|
const { start } = this.#cursorBounds(); // A3: line-local (0 on a single line — unchanged)
|
|
1979
|
+
if (this.#cursor > start)
|
|
1980
|
+
this.#checkpoint(); // UD-1
|
|
1896
1981
|
this.#chars.splice(start, this.#cursor - start);
|
|
1897
1982
|
this.#cursor = start;
|
|
1898
1983
|
this.#reflow();
|
|
@@ -1901,6 +1986,8 @@ export class Editor {
|
|
|
1901
1986
|
}
|
|
1902
1987
|
#killToEnd() {
|
|
1903
1988
|
const { end } = this.#cursorBounds(); // A3: line-local (the buffer's end on a single line — unchanged)
|
|
1989
|
+
if (end > this.#cursor)
|
|
1990
|
+
this.#checkpoint(); // UD-1
|
|
1904
1991
|
this.#chars.splice(this.#cursor, end - this.#cursor);
|
|
1905
1992
|
this.#reflow();
|
|
1906
1993
|
if (!this.#pasting)
|
|
@@ -1915,6 +2002,8 @@ export class Editor {
|
|
|
1915
2002
|
i -= 1; // trailing spaces
|
|
1916
2003
|
while (i > 0 && this.#chars[i - 1] !== 0x20)
|
|
1917
2004
|
i -= 1; // the word
|
|
2005
|
+
if (i < this.#cursor)
|
|
2006
|
+
this.#checkpoint(); // UD-1
|
|
1918
2007
|
this.#chars.splice(i, this.#cursor - i);
|
|
1919
2008
|
this.#cursor = i;
|
|
1920
2009
|
this.#reflow();
|
|
@@ -2029,6 +2118,8 @@ export class Editor {
|
|
|
2029
2118
|
}
|
|
2030
2119
|
#takeLine() {
|
|
2031
2120
|
const line = String.fromCodePoint(...this.#chars);
|
|
2121
|
+
this.#undoStack.length = 0; // UD-1: a sent turn is not a loss
|
|
2122
|
+
this.#redoStack.length = 0;
|
|
2032
2123
|
this.#chars = [];
|
|
2033
2124
|
this.#cursor = 0;
|
|
2034
2125
|
this.#scroll = 0;
|
|
@@ -2196,6 +2287,8 @@ export class Editor {
|
|
|
2196
2287
|
const line = this.#queuePop();
|
|
2197
2288
|
if (line === null)
|
|
2198
2289
|
return;
|
|
2290
|
+
if (this.#chars.length > 0)
|
|
2291
|
+
this.#checkpoint(); // UD-1: a mid-walk edit is recoverable
|
|
2199
2292
|
this.#chars = [...line].map((ch) => ch.codePointAt(0));
|
|
2200
2293
|
this.#cursor = this.#chars.length;
|
|
2201
2294
|
this.#scroll = 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-tui",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.2",
|
|
4
4
|
"description": "kiso tui \u2014 the pure terminal layer (cell renderer, dock, raw editor, diff, palette). Zero runtime dependencies: input is data, output is bytes.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -35,6 +35,6 @@
|
|
|
35
35
|
},
|
|
36
36
|
"homepage": "https://github.com/vincemakes/kiso/tree/main/packages/tui#readme",
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@vincemakes/kiso-tui-cells": "0.16.
|
|
38
|
+
"@vincemakes/kiso-tui-cells": "0.16.2"
|
|
39
39
|
}
|
|
40
40
|
}
|