@vincemakes/kiso-tui 0.6.0 → 0.8.0
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/ask-panel.d.ts +95 -0
- package/dist/ask-panel.js +268 -0
- package/dist/at-picker.d.ts +159 -0
- package/dist/at-picker.js +228 -0
- package/dist/compositor.d.ts +16 -1
- package/dist/compositor.js +48 -7
- package/dist/editor.d.ts +16 -1
- package/dist/editor.js +271 -7
- package/dist/index.d.ts +4 -0
- package/dist/index.js +14 -0
- package/dist/strings.d.ts +6 -0
- package/dist/strings.js +6 -0
- package/package.json +2 -2
package/dist/editor.js
CHANGED
|
@@ -26,7 +26,10 @@ import { charWidth, displayWidth, leadWidth, widthOf } from "./width.js";
|
|
|
26
26
|
// authority) — re-exported so the editor's public surface is unchanged.
|
|
27
27
|
export { charWidth, displayWidth, widthOf };
|
|
28
28
|
import { palette } from "./render.js";
|
|
29
|
-
|
|
29
|
+
// KC3.5: the panel-slot dispatchers — the ask branch folded into the
|
|
30
|
+
// W21 lead/rows, so this file keeps ONE panel and one key owner.
|
|
31
|
+
import { askCommitCustom, askKey, askStart, panelLead } from "./ask-panel.js";
|
|
32
|
+
import { AT_VISIBLE, atFilter } from "./at-picker.js";
|
|
30
33
|
// TUI v4 #16d: the input row is the blue brick + the edit area — the
|
|
31
34
|
// "you>" text is gone (the brick IS the prompt; the pipe path's readline
|
|
32
35
|
// prompt keeps its own "you> " — v2a line mode, byte-for-byte).
|
|
@@ -44,6 +47,13 @@ export const MENU_ITEMS = [
|
|
|
44
47
|
/** KC1 §3 — the newline code point. Every source (paste, Ctrl+J, the
|
|
45
48
|
* Shift+Enter encodings, a CRLF pair) normalizes to exactly ONE. */
|
|
46
49
|
const NEWLINE = 0x0a;
|
|
50
|
+
/** KC3 §3 — the picker's sigil, and the two characters that count as a
|
|
51
|
+
* word boundary before it. A `@` anywhere else (vince@example.com) is
|
|
52
|
+
* an ordinary character: the reference is a thing you START, not a
|
|
53
|
+
* thing an address accidentally becomes. */
|
|
54
|
+
const AT = 0x40;
|
|
55
|
+
const SPACE = 0x20;
|
|
56
|
+
const TAB = 0x09;
|
|
47
57
|
/** KC1 §5 — the composer's CEILING (adjudication A1): at most 6 visible
|
|
48
58
|
* rows. A ceiling only — N_visible clamps by the terminal's height so
|
|
49
59
|
* the geometry stays legal down to the compositor's enter gate (H = 4
|
|
@@ -101,6 +111,22 @@ export class Editor {
|
|
|
101
111
|
#onRender;
|
|
102
112
|
#menuOpen = false; // v3 §04: the slash-command menu
|
|
103
113
|
#menuSel = 0;
|
|
114
|
+
// KC3 §3 — the @ file picker. THREE fields and no more: the armed
|
|
115
|
+
// bit, the selection, and the per-open SNAPSHOT of the file list.
|
|
116
|
+
// The query is deliberately NOT stored — it is derived from the
|
|
117
|
+
// buffer and the cursor on every read (the KC1 flat-buffer
|
|
118
|
+
// discipline: never a second mutable model). That is what makes
|
|
119
|
+
// backspacing past the `@` close the picker with no handler
|
|
120
|
+
// anywhere, and what keeps every existing op — the kills, paste,
|
|
121
|
+
// the history stash, the queue-pop replace — correct for free.
|
|
122
|
+
#atOpen = false;
|
|
123
|
+
#atSel = 0;
|
|
124
|
+
// the list is snapshotted AT OPEN and held for that open's lifetime
|
|
125
|
+
// (§4: no index, no watcher, no re-listing per keystroke). An armed
|
|
126
|
+
// bit with no token under the cursor is inert by construction — the
|
|
127
|
+
// next open re-snapshots, so a stale list can never be shown.
|
|
128
|
+
#atList = null;
|
|
129
|
+
#atItems = null;
|
|
104
130
|
// A2 (the feel): the session-scoped input history — every submitted TURN
|
|
105
131
|
// line (never a question answer), capped at 100, never persisted. ↑↓
|
|
106
132
|
// navigate it ONLY from an empty input or while already browsing.
|
|
@@ -215,7 +241,7 @@ export class Editor {
|
|
|
215
241
|
* the frame's clamp is the authority. */
|
|
216
242
|
#visibleRows(lineCount) {
|
|
217
243
|
const H = process.stdout.rows ?? 24;
|
|
218
|
-
const bands = (this.#menuOpen ? this.#menuFiltered().length : 0) + this.#queueState().length;
|
|
244
|
+
const bands = (this.#menuOpen ? this.#menuFiltered().length : 0) + this.#atRows() + this.#queueState().length;
|
|
219
245
|
return Math.max(1, Math.min(lineCount, N_MAX, Math.max(1, H - 3 - bands)));
|
|
220
246
|
}
|
|
221
247
|
/** The dock's input-row state — ADDITIVE (§5): `line` + `cursor` keep
|
|
@@ -276,6 +302,128 @@ export class Editor {
|
|
|
276
302
|
this.#menuSel = 0;
|
|
277
303
|
this.#onRender();
|
|
278
304
|
}
|
|
305
|
+
/** KC3 §3 — bind the file source. The tui owns no file list and
|
|
306
|
+
* never touches a disk (input is data, output is bytes): the CLI
|
|
307
|
+
* feeds the paths, and until it does, the picker cannot open at
|
|
308
|
+
* all — which is exactly why every non-@ scenario and every
|
|
309
|
+
* consumer that does not bind (the recovery flow, the existing
|
|
310
|
+
* gates) is byte-identical. */
|
|
311
|
+
bindAtItems(source) {
|
|
312
|
+
this.#atItems = source;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* KC3 §3 — the token under the cursor, DERIVED. Scans back from the
|
|
316
|
+
* cursor within the CURSOR'S LINE for the `@` that opens it:
|
|
317
|
+
* - whitespace before finding one → there is no token (the space
|
|
318
|
+
* ended it);
|
|
319
|
+
* - an `@` that is not itself at a word boundary → inert (the
|
|
320
|
+
* email case: the `@` of vince@example.com opens nothing);
|
|
321
|
+
* - otherwise the token runs from that `@` to the CURSOR — never
|
|
322
|
+
* to the end of the line, so `@ra|.js` narrows on "ra".
|
|
323
|
+
* Line-local: the start of any line of a multi-line composer is a
|
|
324
|
+
* boundary, exactly like the start of the buffer.
|
|
325
|
+
*/
|
|
326
|
+
#atToken() {
|
|
327
|
+
const b = this.#cursorBounds();
|
|
328
|
+
for (let i = this.#cursor - 1; i >= b.start; i -= 1) {
|
|
329
|
+
const cp = this.#chars[i];
|
|
330
|
+
if (cp === SPACE || cp === TAB)
|
|
331
|
+
return null;
|
|
332
|
+
if (cp !== AT)
|
|
333
|
+
continue;
|
|
334
|
+
const before = i > b.start ? this.#chars[i - 1] : null;
|
|
335
|
+
if (before !== null && before !== SPACE && before !== TAB)
|
|
336
|
+
return null; // mid-word
|
|
337
|
+
return { start: i, query: String.fromCodePoint(...this.#chars.slice(i + 1, this.#cursor)) };
|
|
338
|
+
}
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
/** KC3 §3 — the picker's full state, or null when it is not up. Up
|
|
342
|
+
* requires ALL of: armed, nobody with higher precedence holding the
|
|
343
|
+
* keys, a live token under the cursor, and at least one match (the
|
|
344
|
+
* menu's precedent — a panel with nothing in it is noise, and the
|
|
345
|
+
* keys fall back to their ordinary meanings). */
|
|
346
|
+
#atView() {
|
|
347
|
+
if (!this.#atOpen || this.#atList === null)
|
|
348
|
+
return null;
|
|
349
|
+
if (this.#panel !== null || this.#menuOpen)
|
|
350
|
+
return null;
|
|
351
|
+
const token = this.#atToken();
|
|
352
|
+
if (token === null)
|
|
353
|
+
return null;
|
|
354
|
+
const { matches, capped } = atFilter(this.#atList, token.query);
|
|
355
|
+
if (matches.length === 0)
|
|
356
|
+
return null;
|
|
357
|
+
// the selection CLAMPS at read time rather than being corrected
|
|
358
|
+
// on every edit — narrowing the query can only ever shrink the
|
|
359
|
+
// list, and a clamp is the whole correction that needs
|
|
360
|
+
return { matches, selected: Math.min(this.#atSel, matches.length - 1), capped, start: token.start };
|
|
361
|
+
}
|
|
362
|
+
#atUp() {
|
|
363
|
+
return this.#atView() !== null;
|
|
364
|
+
}
|
|
365
|
+
/** KC3 §4 — the picker's visible state for the dock; null when
|
|
366
|
+
* closed. The compositor windows it and draws the counter. */
|
|
367
|
+
atState() {
|
|
368
|
+
const view = this.#atView();
|
|
369
|
+
if (view === null)
|
|
370
|
+
return null;
|
|
371
|
+
return { matches: view.matches, selected: view.selected, capped: view.capped };
|
|
372
|
+
}
|
|
373
|
+
/** KC3 §3 — arm the picker at a freshly typed `@`. The gate is the
|
|
374
|
+
* KC2 precedence pattern: the approval panel, the slash menu and a
|
|
375
|
+
* pending question each own the keys first. A paste is literal text
|
|
376
|
+
* (guarded by the caller). The history browse and the queue-pop
|
|
377
|
+
* walk are NOT re-tested here because typing has already ended them
|
|
378
|
+
* — #insert leaves both before a character ever lands. */
|
|
379
|
+
#atArm() {
|
|
380
|
+
if (this.#atItems === null)
|
|
381
|
+
return;
|
|
382
|
+
if (this.#panel !== null || this.#menuOpen || this.#questionCb !== null)
|
|
383
|
+
return;
|
|
384
|
+
if (this.#atToken() === null)
|
|
385
|
+
return; // not at a word boundary
|
|
386
|
+
this.#atOpen = true;
|
|
387
|
+
this.#atSel = 0;
|
|
388
|
+
this.#atList = this.#atItems(); // §5: listed per OPEN, never per keystroke
|
|
389
|
+
}
|
|
390
|
+
#atClose() {
|
|
391
|
+
this.#atOpen = false;
|
|
392
|
+
this.#atSel = 0;
|
|
393
|
+
this.#atList = null;
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* KC3 §3 — accept: the token becomes `@<path> `.
|
|
397
|
+
*
|
|
398
|
+
* The CANONICAL PATH and a trailing space, and nothing else — the
|
|
399
|
+
* file's CONTENT is never inserted. That is the whole product
|
|
400
|
+
* decision: the model is handed a reference it can choose to read,
|
|
401
|
+
* so an @ mention costs a path's worth of tokens instead of a
|
|
402
|
+
* file's, and the model's own read_file call is what pays for the
|
|
403
|
+
* bytes it actually needs.
|
|
404
|
+
*
|
|
405
|
+
* Only [token.start, cursor) is replaced, so text after the cursor
|
|
406
|
+
* survives and a multi-line buffer keeps every other line.
|
|
407
|
+
*/
|
|
408
|
+
#atAccept() {
|
|
409
|
+
const view = this.#atView();
|
|
410
|
+
if (view === null)
|
|
411
|
+
return;
|
|
412
|
+
const insert = [...`@${view.matches[view.selected].path} `].map((ch) => ch.codePointAt(0));
|
|
413
|
+
this.#chars.splice(view.start, this.#cursor - view.start, ...insert);
|
|
414
|
+
this.#cursor = view.start + insert.length;
|
|
415
|
+
this.#atClose();
|
|
416
|
+
this.#reflow();
|
|
417
|
+
this.#onRender();
|
|
418
|
+
}
|
|
419
|
+
/** KC3 §4 — the picker's band height, the editor's honest estimate
|
|
420
|
+
* (the compositor re-applies the clamp against the frame's REAL
|
|
421
|
+
* folded rows, exactly as it does for the menu): the windowed rows
|
|
422
|
+
* plus the counter row. */
|
|
423
|
+
#atRows() {
|
|
424
|
+
const view = this.#atView();
|
|
425
|
+
return view === null ? 0 : Math.min(view.matches.length, AT_VISIBLE) + 1;
|
|
426
|
+
}
|
|
279
427
|
/** One-shot question mode: the NEXT submit answers, not a turn. */
|
|
280
428
|
question(_query, cb) {
|
|
281
429
|
this.#questionCb = cb;
|
|
@@ -293,6 +441,7 @@ export class Editor {
|
|
|
293
441
|
view,
|
|
294
442
|
phase: "options",
|
|
295
443
|
sel: 0,
|
|
444
|
+
ask: view.ask === undefined ? null : askStart(view.ask),
|
|
296
445
|
amend: "yes",
|
|
297
446
|
onCommit,
|
|
298
447
|
stash: { chars: this.#chars, cursor: this.#cursor, scroll: this.#scroll },
|
|
@@ -304,6 +453,7 @@ export class Editor {
|
|
|
304
453
|
this.#menuOpen = false;
|
|
305
454
|
this.#menuSel = 0;
|
|
306
455
|
this.#queuePopMode = false; // W22: the panel owns the keys while up
|
|
456
|
+
this.#atClose(); // KC3 §3: and the picker closes with everything else
|
|
307
457
|
this.#onRender();
|
|
308
458
|
}
|
|
309
459
|
/** W21: cancel the panel — the SIGINT path's pair to beginPanel. */
|
|
@@ -316,7 +466,7 @@ export class Editor {
|
|
|
316
466
|
const panel = this.#panel;
|
|
317
467
|
if (panel === null)
|
|
318
468
|
return null;
|
|
319
|
-
return { view: panel.view, phase: panel.phase, sel: panel.sel };
|
|
469
|
+
return { view: panel.view, phase: panel.phase, sel: panel.sel, ...(panel.ask === null ? {} : { ask: panel.ask }) };
|
|
320
470
|
}
|
|
321
471
|
enter() {
|
|
322
472
|
if (this.#entered)
|
|
@@ -346,7 +496,7 @@ export class Editor {
|
|
|
346
496
|
// W21: the panel's lead owns the row while up (the brick returns
|
|
347
497
|
// when the panel closes).
|
|
348
498
|
const panel = this.#panel;
|
|
349
|
-
const lead = panel !== null ? panelLead(panel.view, panel.phase, panel.sel) : `${p.bold}${PROMPT}${p.reset}`;
|
|
499
|
+
const lead = panel !== null ? panelLead(panel.view, panel.phase, panel.sel, panel.ask ?? undefined) : `${p.bold}${PROMPT}${p.reset}`;
|
|
350
500
|
// W23: the ONE width authority — leadWidth(lead), the ANSI-stripped
|
|
351
501
|
// visible width (the styled panel lead / the styled brick measure
|
|
352
502
|
// the same as their plain text — a lead can never measure
|
|
@@ -373,6 +523,45 @@ export class Editor {
|
|
|
373
523
|
// text); ctrl-c still rides the SIGINT handler (which
|
|
374
524
|
// cancels the panel).
|
|
375
525
|
const panel = this.#panel;
|
|
526
|
+
// KC3.5: an ASK panel routes its own keys — the digits pick
|
|
527
|
+
// (single-select advances, multi toggles), space toggles at
|
|
528
|
+
// the cursor, `t` opens the type-your-own line (the
|
|
529
|
+
// rule-input phase's shape: the buffer is the editor's, so
|
|
530
|
+
// only esc and enter are intercepted while typing), esc
|
|
531
|
+
// declines the whole call. Everything else falls through to
|
|
532
|
+
// the ordinary editing chain below.
|
|
533
|
+
if (panel.ask !== null) {
|
|
534
|
+
const typing = panel.ask.phase === "custom";
|
|
535
|
+
if (c === "\x1b" && !text.slice(i + 1).startsWith("[") && !text.slice(i + 1).startsWith("O")) {
|
|
536
|
+
this.#askStep("esc");
|
|
537
|
+
i += 1;
|
|
538
|
+
continue;
|
|
539
|
+
}
|
|
540
|
+
if (c === "\x0d" || c === "\x0a") {
|
|
541
|
+
this.#askStep(typing ? "commit" : "enter");
|
|
542
|
+
i += 1;
|
|
543
|
+
continue;
|
|
544
|
+
}
|
|
545
|
+
if (!typing && (c === " " || (c !== undefined && c >= "1" && c <= "4") || c === "t" || c === "T")) {
|
|
546
|
+
this.#askStep(c === " " ? "space" : c === "T" ? "t" : c);
|
|
547
|
+
i += 1;
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
550
|
+
// an ask at rest swallows stray PRINTABLE keys — the panel
|
|
551
|
+
// owns them, and a typed "/" or "@" must not arm the menu
|
|
552
|
+
// or the picker underneath. Two things are never
|
|
553
|
+
// swallowed: the CSI/SS3 introducer, because ←/↑/↓ are
|
|
554
|
+
// the ask's own keys and the parser below routes them
|
|
555
|
+
// (the T-Q1 red), and the CONTROL characters, because
|
|
556
|
+
// ctrl-c must still reach the SIGINT handler that
|
|
557
|
+
// cancels the panel — W21's own rule, and what the T-Q6
|
|
558
|
+
// race red caught: an abort with the panel up did
|
|
559
|
+
// nothing at all.
|
|
560
|
+
if (!typing && c !== undefined && c >= " " && c !== "\x7f") {
|
|
561
|
+
i += 1;
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
376
565
|
if (c === "\x1b" && !text.slice(i + 1).startsWith("[") && !text.slice(i + 1).startsWith("O")) {
|
|
377
566
|
this.#panelEsc();
|
|
378
567
|
i += 1;
|
|
@@ -443,6 +632,17 @@ export class Editor {
|
|
|
443
632
|
this.#refreshMenu();
|
|
444
633
|
i += 1;
|
|
445
634
|
}
|
|
635
|
+
else if (this.#atUp()) {
|
|
636
|
+
// KC3 §3: esc closes the picker and leaves the BUFFER
|
|
637
|
+
// ALONE — unlike the menu's esc, which clears it. The
|
|
638
|
+
// sentence around the reference is still being written,
|
|
639
|
+
// and a dismissed picker must not take it away. CA-4:
|
|
640
|
+
// the closing esc consumes its burst, so it can never
|
|
641
|
+
// also abort the run.
|
|
642
|
+
this.#atClose();
|
|
643
|
+
this.#onRender();
|
|
644
|
+
i += 1;
|
|
645
|
+
}
|
|
446
646
|
else if (this.#queuePopMode) {
|
|
447
647
|
// W22: esc in the pop-mode — ONE more pop, then the
|
|
448
648
|
// mode ends: the next esc at rest rides the escapeCbs
|
|
@@ -539,6 +739,12 @@ export class Editor {
|
|
|
539
739
|
}
|
|
540
740
|
i += 1;
|
|
541
741
|
}
|
|
742
|
+
else if (c === "\t" && this.#atUp()) {
|
|
743
|
+
// KC3 §3: Tab accepts the selected path — the token becomes
|
|
744
|
+
// `@<path> `. Never the file's content.
|
|
745
|
+
this.#atAccept();
|
|
746
|
+
i += 1;
|
|
747
|
+
}
|
|
542
748
|
else if (c === "\x12") {
|
|
543
749
|
// W15: the expand key (ctrl+r) — rides the chain like a
|
|
544
750
|
// command, the editor just forwards it.
|
|
@@ -596,7 +802,10 @@ export class Editor {
|
|
|
596
802
|
// panel owns the keys while up (↑↓ do nothing — the panel has no
|
|
597
803
|
// ↑↓ role).
|
|
598
804
|
if (this.#panel !== null) {
|
|
599
|
-
|
|
805
|
+
// W21: the panel owns the keys. KC3.5: an ask uses ↑↓ for
|
|
806
|
+
// the option cursor (the approval panel still has no ↑↓ role).
|
|
807
|
+
if (this.#panel.ask !== null && this.#panel.ask.phase === "options")
|
|
808
|
+
this.#askStep(final === "A" ? "up" : "down");
|
|
600
809
|
}
|
|
601
810
|
else if (this.#menuOpen) {
|
|
602
811
|
if (final === "A")
|
|
@@ -604,6 +813,14 @@ export class Editor {
|
|
|
604
813
|
else
|
|
605
814
|
this.#menuSel = Math.min(this.#menuFiltered().length - 1, this.#menuSel + 1);
|
|
606
815
|
}
|
|
816
|
+
else if (this.#atUp()) {
|
|
817
|
+
// KC3 §3: the picker owns ↑↓ while up — the SELECTION, never
|
|
818
|
+
// the cursor and never the composer's line walk. It sits
|
|
819
|
+
// ABOVE the multi-line branch on purpose: a picker opened on
|
|
820
|
+
// line 2 of a composer must still select.
|
|
821
|
+
const view = this.#atView();
|
|
822
|
+
this.#atSel = final === "A" ? Math.max(0, view.selected - 1) : Math.min(view.matches.length - 1, view.selected + 1);
|
|
823
|
+
}
|
|
607
824
|
else if (this.#chars.includes(NEWLINE)) {
|
|
608
825
|
// KC1 §4: a MULTI-LINE buffer's ↑↓ walk its lines. The
|
|
609
826
|
// history and the queue-pop below stay gated on an EMPTY
|
|
@@ -626,7 +843,12 @@ export class Editor {
|
|
|
626
843
|
this.#onRender();
|
|
627
844
|
}
|
|
628
845
|
else if (final === "D") {
|
|
629
|
-
|
|
846
|
+
// KC3.5: ← walks the ask BACK a question (the ‹ n/m › walk); at
|
|
847
|
+
// question one it stays put — esc is the decline, never ←.
|
|
848
|
+
if (this.#panel?.ask != null && this.#panel.ask.phase === "options")
|
|
849
|
+
this.#askStep("left");
|
|
850
|
+
else
|
|
851
|
+
this.#move(-1);
|
|
630
852
|
}
|
|
631
853
|
else if (final === "C") {
|
|
632
854
|
this.#move(1);
|
|
@@ -741,6 +963,33 @@ export class Editor {
|
|
|
741
963
|
else if (panel.sel === 3)
|
|
742
964
|
this.#panelClose({ action: "deny", reason: "" });
|
|
743
965
|
}
|
|
966
|
+
/**
|
|
967
|
+
* KC3.5 — one ask key: the pure reducer decides, this method applies.
|
|
968
|
+
* The buffer is cleared on every phase change so the type-your-own
|
|
969
|
+
* line starts empty and its text never leaks back into the options
|
|
970
|
+
* (the rule-input phase's own discipline). A step that produced a
|
|
971
|
+
* RESULT closes the panel with it — the stash/restore is the W21
|
|
972
|
+
* path, identical for an answer and for a decline.
|
|
973
|
+
*/
|
|
974
|
+
#askStep(key) {
|
|
975
|
+
const panel = this.#panel;
|
|
976
|
+
if (panel === null || panel.ask === null)
|
|
977
|
+
return;
|
|
978
|
+
const spec = panel.view.ask;
|
|
979
|
+
const before = panel.ask.phase;
|
|
980
|
+
const step = key === "commit" ? askCommitCustom(spec, panel.ask, this.line()) : askKey(spec, panel.ask, key);
|
|
981
|
+
panel.ask = step.state;
|
|
982
|
+
if (step.state.phase !== before) {
|
|
983
|
+
this.#chars = [];
|
|
984
|
+
this.#cursor = 0;
|
|
985
|
+
this.#scroll = 0;
|
|
986
|
+
}
|
|
987
|
+
if (step.result !== undefined) {
|
|
988
|
+
this.#panelClose({ action: "answers", result: step.result });
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
991
|
+
this.#onRender();
|
|
992
|
+
}
|
|
744
993
|
#panelClose(verdict) {
|
|
745
994
|
const panel = this.#panel;
|
|
746
995
|
if (panel === null)
|
|
@@ -764,6 +1013,11 @@ export class Editor {
|
|
|
764
1013
|
this.#reflow();
|
|
765
1014
|
if (!this.#pasting)
|
|
766
1015
|
this.#refreshMenu();
|
|
1016
|
+
// KC3 §3: a TYPED `@` arms the picker; a PASTED one never does —
|
|
1017
|
+
// a paste is content, and content that happens to contain an
|
|
1018
|
+
// address must not open a file browser mid-sentence.
|
|
1019
|
+
if (cp === AT && !this.#pasting)
|
|
1020
|
+
this.#atArm();
|
|
767
1021
|
}
|
|
768
1022
|
#backspace() {
|
|
769
1023
|
if (this.#cursor === 0)
|
|
@@ -833,6 +1087,7 @@ export class Editor {
|
|
|
833
1087
|
this.#menuOpen = false;
|
|
834
1088
|
this.#menuSel = 0;
|
|
835
1089
|
this.#queuePopMode = false;
|
|
1090
|
+
this.#atClose(); // KC3 §3: a departing line takes its picker with it
|
|
836
1091
|
return line;
|
|
837
1092
|
}
|
|
838
1093
|
/** A2: the history remembers submitted TURN lines — never question
|
|
@@ -857,6 +1112,7 @@ export class Editor {
|
|
|
857
1112
|
#composerIdle() {
|
|
858
1113
|
return (this.#panel === null &&
|
|
859
1114
|
!this.#menuOpen &&
|
|
1115
|
+
!this.#atUp() && // KC3 §3: the @ picker owns the keys while up, exactly like the menu
|
|
860
1116
|
this.#historyIdx === null &&
|
|
861
1117
|
!this.#queuePopMode &&
|
|
862
1118
|
!this.#pasting &&
|
|
@@ -889,6 +1145,14 @@ export class Editor {
|
|
|
889
1145
|
this.#onRender();
|
|
890
1146
|
}
|
|
891
1147
|
#submit() {
|
|
1148
|
+
// KC3 §3: Enter ACCEPTS while the picker is up — the same rule the
|
|
1149
|
+
// menu's A1 feel established (complete first, let the user read
|
|
1150
|
+
// what they got, and let the NEXT Enter send it). An @ reference
|
|
1151
|
+
// that submitted on the first Enter would send the fragment.
|
|
1152
|
+
if (this.#atUp()) {
|
|
1153
|
+
this.#atAccept();
|
|
1154
|
+
return;
|
|
1155
|
+
}
|
|
892
1156
|
if (this.#menuOpen) {
|
|
893
1157
|
// A1 (the feel): Enter submits the EXACT selection directly; a
|
|
894
1158
|
// PARTIAL selection COMPLETES the buffer (the Tab semantics)
|
|
@@ -975,7 +1239,7 @@ export class Editor {
|
|
|
975
1239
|
// W23: the ONE width authority — leadWidth(lead) — the cap follows
|
|
976
1240
|
// the lead the editor itself renders (the panel lead when the panel
|
|
977
1241
|
// owns the keys, the brick otherwise): maxW = W − walls − lead.
|
|
978
|
-
const lead = this.#panel !== null ? panelLead(this.#panel.view, this.#panel.phase, this.#panel.sel) : PROMPT;
|
|
1242
|
+
const lead = this.#panel !== null ? panelLead(this.#panel.view, this.#panel.phase, this.#panel.sel, this.#panel.ask ?? undefined) : PROMPT;
|
|
979
1243
|
const leadW = leadWidth(lead);
|
|
980
1244
|
const maxW = Math.max(1, W - leadW - 4); // W6: the box's walls (2+2) — the visible line fits the box's inner width; the "…" rides inside
|
|
981
1245
|
// KC1: the scroll is the CURSOR LINE's own offset — a single-line
|
package/dist/index.d.ts
CHANGED
|
@@ -12,3 +12,7 @@ export { Editor, MENU_ITEMS, PROMPT, PROMPT_WIDTH, displayWidth, charWidth, widt
|
|
|
12
12
|
export { bannerLines, COLOR_OFF, COLOR_ON, escapeTerminal, foldResult, foldThinking, kUnit, palette, renderEvent, renderRecap, renderResumeList, renderSessionLine, renderStatusLine, relativeTime, renderTerminalGap, renderToolSummary, TAGLINE, toolTarget, truncateRow, type Palette, type PathResolver, type RecapStats, type ResumeMeta, type RenderInput, type RenderResult, type RunUsage, } from "./render.js";
|
|
13
13
|
export { editFileDiff, truncateDiff, writeFileDiff, type DiffLine, type DiffResult } from "./diff.js";
|
|
14
14
|
export { STATUS_GLYPHS, idleStatus, runningStatus } from "./status.js";
|
|
15
|
+
export { interactivePrompt, projectTrustRows, projectTrustView, projectUntrustedNote, uncertainView, type TrustArtifact } from "./strings.js";
|
|
16
|
+
export { AT_CAP, AT_SKIP, AT_VISIBLE, atEmbed, atFilter, atPanelRows, atWindow, longestRun, type AtItem, type AtMatch } from "./at-picker.js";
|
|
17
|
+
export { ASK_HEADER_CAP, ASK_MAX_OPTIONS, ASK_MAX_QUESTIONS, ASK_MIN_OPTIONS, askAffordance, askAnswers, askBlockRows, askCommitCustom, askDeclineAll, askDeclineList, askKey, askLeadPlain, askStart, askStatus, askView, type AskAnswer, type AskOption, type AskQuestion, type AskResult, type AskRuntime, type AskSpec, type AskStep, } from "./ask-panel.js";
|
|
18
|
+
export { extensionsBannerText, helpRows, unansweredAskView, type BannerExtension } from "./strings.js";
|
package/dist/index.js
CHANGED
|
@@ -17,3 +17,17 @@ export { editFileDiff, truncateDiff, writeFileDiff } from "./diff.js";
|
|
|
17
17
|
// KC2 §5: the status rows' formatters — the CLI keeps the state and the
|
|
18
18
|
// repaint, the terminal layer owns what the row says.
|
|
19
19
|
export { STATUS_GLYPHS, idleStatus, runningStatus } from "./status.js";
|
|
20
|
+
// KC3 §1 (the extraction): the human-facing strings — the prompt, the
|
|
21
|
+
// project-trust listing/view/note, the uncertain execution's view. The
|
|
22
|
+
// FLOW (who is asked, what a verdict means) stays in the cli.
|
|
23
|
+
export { interactivePrompt, projectTrustRows, projectTrustView, projectUntrustedNote, uncertainView } from "./strings.js";
|
|
24
|
+
// KC3 §3/§5: the @ file picker's pure half — the subsequence filter, the
|
|
25
|
+
// deterministic rank, and the ONE cap the CLI's file source shares.
|
|
26
|
+
export { AT_CAP, AT_SKIP, AT_VISIBLE, atEmbed, atFilter, atPanelRows, atWindow, longestRun } from "./at-picker.js";
|
|
27
|
+
// KC3.5 (the ask round): the ask view — the panel machinery generalized.
|
|
28
|
+
// The cli composes the view and hands the answers to the tool; the keys,
|
|
29
|
+
// the rows and the walk are the terminal layer's.
|
|
30
|
+
export { ASK_HEADER_CAP, ASK_MAX_OPTIONS, ASK_MAX_QUESTIONS, ASK_MIN_OPTIONS, askAffordance, askAnswers, askBlockRows, askCommitCustom, askDeclineAll, askDeclineList, askKey, askLeadPlain, askStart, askStatus, askView, } from "./ask-panel.js";
|
|
31
|
+
// KC3.5 §4: the interrupted-ask copy — the SAME uncertainty gate, said
|
|
32
|
+
// honestly for a question nobody answered (the ① probe's surface).
|
|
33
|
+
export { extensionsBannerText, helpRows, unansweredAskView } from "./strings.js";
|
package/dist/strings.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-tui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "kiso tui — 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.
|
|
38
|
+
"@vincemakes/kiso-tui-cells": "0.8.0"
|
|
39
39
|
}
|
|
40
40
|
}
|