@vincemakes/kiso-tui-cells 0.8.0 → 0.10.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/approval-panel.d.ts +75 -0
- package/dist/approval-panel.js +125 -12
- package/dist/components.d.ts +55 -6
- package/dist/components.js +466 -23
- package/dist/diff.d.ts +24 -4
- package/dist/diff.js +30 -16
- package/dist/index.d.ts +2 -1
- package/dist/index.js +4 -1
- package/dist/render.d.ts +8 -0
- package/dist/render.js +2 -2
- package/dist/strings.d.ts +57 -0
- package/dist/strings.js +171 -0
- package/dist/width.d.ts +16 -6
- package/dist/width.js +78 -6
- package/package.json +1 -1
package/dist/diff.js
CHANGED
|
@@ -91,23 +91,37 @@ function stats(diff) {
|
|
|
91
91
|
}
|
|
92
92
|
return { added, removed };
|
|
93
93
|
}
|
|
94
|
-
/** edit_file: the
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
94
|
+
/** edit_file: the preview of a CHARACTER splice.
|
|
95
|
+
*
|
|
96
|
+
* TUI2-R1.5 ② (VD-2): the locator is the tool's own, verbatim — the
|
|
97
|
+
* workspace edit_file does `i = text.indexOf(search)` and writes
|
|
98
|
+
* `text.slice(0, i) + replace + text.slice(i + search.length)`. This
|
|
99
|
+
* function mirrors those two lines and diffs the result against the
|
|
100
|
+
* original; it does not model the edit, it reproduces it.
|
|
101
|
+
*
|
|
102
|
+
* The retired locator required the search to align to FULL LINES. A
|
|
103
|
+
* mid-line search ("// OLD" inside " // OLD") therefore missed, and
|
|
104
|
+
* the miss branch rendered the WHOLE FILE as the old side: a one-line
|
|
105
|
+
* edit was drawn as a catastrophic rewrite, on the approval panel, at
|
|
106
|
+
* the moment a human was deciding whether to allow it. A preview that
|
|
107
|
+
* can be that wrong is worse than no preview.
|
|
108
|
+
*
|
|
109
|
+
* A genuine miss is now reported as a miss: the tool will return
|
|
110
|
+
* `pattern not found in <path>` and change nothing, so the panel says
|
|
111
|
+
* exactly that instead of inventing a diff for an edit that will not
|
|
112
|
+
* happen. `path` names the file in that note. */
|
|
113
|
+
export function editFileDiff(oldContent, search, replace, path) {
|
|
114
|
+
const at = oldContent.indexOf(search);
|
|
115
|
+
if (at < 0) {
|
|
116
|
+
return {
|
|
117
|
+
lines: [{ kind: " ", text: `pattern not found in ${path ?? "the file"}` }],
|
|
118
|
+
added: 0,
|
|
119
|
+
removed: 0,
|
|
120
|
+
notFound: true,
|
|
121
|
+
};
|
|
109
122
|
}
|
|
110
|
-
const
|
|
123
|
+
const result = oldContent.slice(0, at) + replace + oldContent.slice(at + search.length);
|
|
124
|
+
const lines = withContext(lcsDiff(oldContent.split("\n"), result.split("\n")));
|
|
111
125
|
return { lines, ...stats(lines) };
|
|
112
126
|
}
|
|
113
127
|
/** write_file: a new file is all +; an existing file diffs row-level
|
package/dist/index.d.ts
CHANGED
|
@@ -6,11 +6,12 @@
|
|
|
6
6
|
* package); the cli never imports it directly. Experimental — no
|
|
7
7
|
* API-stability promise yet.
|
|
8
8
|
*/
|
|
9
|
-
export { SPINNER, foldLine, visibleWidth, bodySpacing, Container, cellComponent, ROLLUP_NOUN, turnFold, CAP_TASK_LIVE, formatDuration, statusLine, boxTop, boxBottom, terminalPipe, type FrameCtx, type RenderLine, type Component, type BodyCell, } from "./components.js";
|
|
9
|
+
export { SPINNER, foldLine, foldWords, visibleWidth, bodySpacing, Container, cellComponent, focusToken, ROLLUP_NOUN, turnFold, CAP_TASK_LIVE, formatDuration, statusLine, boxTop, boxBottom, terminalPipe, type FrameCtx, type RenderLine, type Component, type BodyCell, } from "./components.js";
|
|
10
10
|
export { editFileDiff, truncateDiff, writeFileDiff, type DiffLine, type DiffResult } from "./diff.js";
|
|
11
11
|
export { pendingQueueRows } from "./components.js";
|
|
12
12
|
export { charWidth, displayWidth, leadWidth, widthOf } from "./width.js";
|
|
13
13
|
export { panelAffordance, panelBlockRows, panelLead, panelLeadPlain, panelLeadWidth, panelStatus, type PanelArgs, type PanelFlavor, type PanelPhase, type PanelSel, type PanelState, type PanelVerdict, type PanelView, type AskAnswer, type AskOption, type AskQuestion, type AskResult, type AskRuntime, type AskSpec, } from "./approval-panel.js";
|
|
14
14
|
export { interactivePrompt, projectTrustRows, projectTrustView, projectUntrustedNote, uncertainView, type TrustArtifact, } from "./strings.js";
|
|
15
15
|
export { extensionsBannerText, helpRows, unansweredAskView, type BannerExtension } from "./strings.js";
|
|
16
|
+
export { displayVerb } from "./strings.js";
|
|
16
17
|
export { bannerLines, COLOR_OFF, COLOR_ON, colorInlineCode, escapeTerminal, foldResult, foldThinking, kUnit, palette, relativeTime, renderResumeList, renderTerminalGap, renderToolSummary, TAGLINE, toolTarget, truncateRow, type Palette, type ResumeMeta, } from "./render.js";
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* package); the cli never imports it directly. Experimental — no
|
|
7
7
|
* API-stability promise yet.
|
|
8
8
|
*/
|
|
9
|
-
export { SPINNER, foldLine, visibleWidth, bodySpacing, Container, cellComponent, ROLLUP_NOUN, turnFold, CAP_TASK_LIVE, formatDuration, statusLine, boxTop, boxBottom, terminalPipe, } from "./components.js";
|
|
9
|
+
export { SPINNER, foldLine, foldWords, visibleWidth, bodySpacing, Container, cellComponent, focusToken, ROLLUP_NOUN, turnFold, CAP_TASK_LIVE, formatDuration, statusLine, boxTop, boxBottom, terminalPipe, } from "./components.js";
|
|
10
10
|
export { editFileDiff, truncateDiff, writeFileDiff } from "./diff.js";
|
|
11
11
|
// W22 (the v8 input round): the pending-queue chips — the SAME
|
|
12
12
|
// UserMessage chip with the □ gutter, pre-rendered above the input
|
|
@@ -25,4 +25,7 @@ export { panelAffordance, panelBlockRows, panelLead, panelLeadPlain, panelLeadWi
|
|
|
25
25
|
export { interactivePrompt, projectTrustRows, projectTrustView, projectUntrustedNote, uncertainView, } from "./strings.js";
|
|
26
26
|
// KC3.5: the interrupted-ask copy and the extracted /help table.
|
|
27
27
|
export { extensionsBannerText, helpRows, unansweredAskView } from "./strings.js";
|
|
28
|
+
// TUI2-R2pre ④: the ONE display-verb table — the screen names the act,
|
|
29
|
+
// the tool table names the call.
|
|
30
|
+
export { displayVerb } from "./strings.js";
|
|
28
31
|
export { bannerLines, COLOR_OFF, COLOR_ON, colorInlineCode, escapeTerminal, foldResult, foldThinking, kUnit, palette, relativeTime, renderResumeList, renderTerminalGap, renderToolSummary, TAGLINE, toolTarget, truncateRow, } from "./render.js";
|
package/dist/render.d.ts
CHANGED
|
@@ -34,6 +34,14 @@ export interface Palette {
|
|
|
34
34
|
readonly dim: string;
|
|
35
35
|
readonly red: string;
|
|
36
36
|
readonly green: string;
|
|
37
|
+
/** TUI2-R2 ①: the third functional exception, finally spelled. The
|
|
38
|
+
* mono-discipline ruling above names "green ✓, yellow warn and red
|
|
39
|
+
* error" as the ONLY functional colours; warn had no entry because
|
|
40
|
+
* nothing had needed it yet. The uncertain badge needs exactly it —
|
|
41
|
+
* a state that is neither success nor failure but a question
|
|
42
|
+
* addressed to the human. This is the ruling's own set gaining its
|
|
43
|
+
* missing member, not a fourth colour. */
|
|
44
|
+
readonly warn: string;
|
|
37
45
|
readonly code: string;
|
|
38
46
|
readonly rv: string;
|
|
39
47
|
readonly rvEnd: string;
|
package/dist/render.js
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* dependencies (the tui-cells package has none).
|
|
7
7
|
*/
|
|
8
8
|
import { charWidth, displayWidth } from "./width.js";
|
|
9
|
-
export const COLOR_ON = { bold: "\x1b[1m", dim: "\x1b[2m", red: "\x1b[31m", green: "\x1b[32m", code: "\x1b[38;5;252m", rv: "\x1b[7m", rvEnd: "\x1b[27m", reset: "\x1b[0m" };
|
|
10
|
-
export const COLOR_OFF = { bold: "", dim: "", red: "", green: "", code: "", rv: "", rvEnd: "", reset: "" };
|
|
9
|
+
export const COLOR_ON = { bold: "\x1b[1m", dim: "\x1b[2m", red: "\x1b[31m", green: "\x1b[32m", warn: "\x1b[33m", code: "\x1b[38;5;252m", rv: "\x1b[7m", rvEnd: "\x1b[27m", reset: "\x1b[0m" };
|
|
10
|
+
export const COLOR_OFF = { bold: "", dim: "", red: "", green: "", warn: "", code: "", rv: "", rvEnd: "", reset: "" };
|
|
11
11
|
export function palette() {
|
|
12
12
|
return process.env.NO_COLOR === undefined && process.stdout.isTTY ? COLOR_ON : COLOR_OFF;
|
|
13
13
|
}
|
package/dist/strings.d.ts
CHANGED
|
@@ -93,6 +93,63 @@ export interface BannerExtension {
|
|
|
93
93
|
* reads `built-in: mcp, skills, subagent`, from this one composition.
|
|
94
94
|
*/
|
|
95
95
|
export declare function extensionsBannerText(builtIn: readonly BannerExtension[], user: readonly BannerExtension[], project: readonly BannerExtension[]): string;
|
|
96
|
+
/** One gesture: what you press, and what it does. */
|
|
97
|
+
export interface KeyBinding {
|
|
98
|
+
readonly keys: string;
|
|
99
|
+
readonly what: string;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* TUI2-R1 (D) — THE key table. Every reader derives from it: the `?`
|
|
103
|
+
* sheet and /help's keys row. A sheet that has drifted from the keys is
|
|
104
|
+
* worse than no sheet, and the only way to make drift impossible is to
|
|
105
|
+
* have one table and no second copy of it.
|
|
106
|
+
*
|
|
107
|
+
* The order is the sheet's reading order, which is why it is grouped by
|
|
108
|
+
* WHAT A HUMAN IS DOING rather than alphabetically: the three ways to
|
|
109
|
+
* put something in (send, newline, files), the three ways to change
|
|
110
|
+
* course (stop, redirect, commands), then the walks (history, expand),
|
|
111
|
+
* then the completions.
|
|
112
|
+
*/
|
|
113
|
+
export declare const KEY_BINDINGS: readonly KeyBinding[];
|
|
114
|
+
/** A tool's name as the SCREEN says it. Display-only: the raw name stays
|
|
115
|
+
* on the cell, and dispatch, the mode gate, the policy keys, the /last
|
|
116
|
+
* RAW block and every model-facing byte keep reading that. */
|
|
117
|
+
export declare function displayVerb(name: string): string;
|
|
118
|
+
/** The panel keys, which belong to a panel rather than the composer —
|
|
119
|
+
* one dim line rather than four table rows, because they apply only
|
|
120
|
+
* while a panel is up. */
|
|
121
|
+
/**
|
|
122
|
+
* TUI2-R1.5 pin 6 — this row has to be true of BOTH panel flavors, and
|
|
123
|
+
* "digits select" was wrong in both directions at once.
|
|
124
|
+
*
|
|
125
|
+
* On an APPROVAL a digit only moves the selection (editor's #panelSelect
|
|
126
|
+
* sets `sel` and renders); ENTER is what resolves it. A reader who
|
|
127
|
+
* pressed 1 and walked away had approved nothing — the worst kind of
|
|
128
|
+
* false affordance, on the surface where the stakes are a side effect.
|
|
129
|
+
*
|
|
130
|
+
* On an ASK the opposite: a digit on a SINGLE-choice question answers it
|
|
131
|
+
* and advances the walk (ask-panel's askKey → advance), so "select"
|
|
132
|
+
* undersold it. A multi-select question toggles and waits for enter,
|
|
133
|
+
* like the approval.
|
|
134
|
+
*
|
|
135
|
+
* "digits pick · ⏎ confirms" is the sentence both flavors satisfy. The
|
|
136
|
+
* single-choice fast path — where the confirm is implicit — is the one
|
|
137
|
+
* thing a single row cannot also carry; it is an omission, never a lie.
|
|
138
|
+
*/
|
|
139
|
+
export declare const PANEL_KEYS_ROW = "panels: digits pick \u00B7 \u23CE confirms \u00B7 space toggles \u00B7 t types an answer";
|
|
140
|
+
/**
|
|
141
|
+
* TUI2-R1 (D) — the sheet, one screen, static.
|
|
142
|
+
*
|
|
143
|
+
* Rows CUT at the width rather than folding: the sheet's contract with
|
|
144
|
+
* the reader is "one screen", and a folded grid at 40 columns is two
|
|
145
|
+
* screens pretending to be one. A narrow terminal shows fewer columns
|
|
146
|
+
* of the same truth, which is the honest degradation.
|
|
147
|
+
*/
|
|
148
|
+
export declare function keysSheetRows(W: number): string[];
|
|
149
|
+
/** TUI2-R1 (D) — the keys as ONE line, for /help. The same table the
|
|
150
|
+
* sheet renders, joined — so the two can disagree only by deleting a
|
|
151
|
+
* test. The sheet is the readable form; this is the greppable one. */
|
|
152
|
+
export declare function keysHelpRow(): string;
|
|
96
153
|
/**
|
|
97
154
|
* KC3.5 slice ⓪ (the extraction) — the /help command table.
|
|
98
155
|
*
|
package/dist/strings.js
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
* looked up here.
|
|
19
19
|
*/
|
|
20
20
|
import { escapeTerminal, palette } from "./render.js";
|
|
21
|
+
import { displayWidth } from "./width.js";
|
|
21
22
|
/** v2a: the interactive prompt — the identity accent. readline owns the
|
|
22
23
|
* echo of what the user types; we own the prompt's color. (v2c: the
|
|
23
24
|
* readline prompt keeps "you> " — the brick ▌ is the dock's row only;
|
|
@@ -130,6 +131,169 @@ export function extensionsBannerText(builtIn, user, project) {
|
|
|
130
131
|
parts.push(`project: ${project.map(label).join(", ")}`);
|
|
131
132
|
return ` · [${total} extension${total === 1 ? "" : "s"}: ${parts.join(" · ")}]`;
|
|
132
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* TUI2-R1 (D) — THE key table. Every reader derives from it: the `?`
|
|
136
|
+
* sheet and /help's keys row. A sheet that has drifted from the keys is
|
|
137
|
+
* worse than no sheet, and the only way to make drift impossible is to
|
|
138
|
+
* have one table and no second copy of it.
|
|
139
|
+
*
|
|
140
|
+
* The order is the sheet's reading order, which is why it is grouped by
|
|
141
|
+
* WHAT A HUMAN IS DOING rather than alphabetically: the three ways to
|
|
142
|
+
* put something in (send, newline, files), the three ways to change
|
|
143
|
+
* course (stop, redirect, commands), then the walks (history, expand),
|
|
144
|
+
* then the completions.
|
|
145
|
+
*/
|
|
146
|
+
export const KEY_BINDINGS = [
|
|
147
|
+
{ keys: "enter", what: "send" },
|
|
148
|
+
{ keys: "ctrl+j / shift+⏎", what: "newline" },
|
|
149
|
+
{ keys: "@", what: "files" },
|
|
150
|
+
{ keys: "esc", what: "stop" },
|
|
151
|
+
{ keys: "alt+⏎ / ctrl+⏎", what: "redirect" },
|
|
152
|
+
{ keys: "/", what: "commands" },
|
|
153
|
+
{ keys: "↑↓", what: "history / queue pop" },
|
|
154
|
+
{ keys: "ctrl+r", what: "expand cells" },
|
|
155
|
+
{ keys: "tab", what: "complete (menu / @)" },
|
|
156
|
+
{ keys: "?", what: "this sheet" },
|
|
157
|
+
];
|
|
158
|
+
/**
|
|
159
|
+
* TUI2-R2pre ④ — THE display-verb table (the integrator's ruling).
|
|
160
|
+
*
|
|
161
|
+
* The screen names the ACT; the tool table names the CALL. Two
|
|
162
|
+
* audiences, two vocabularies, and only the human's one lives here: the
|
|
163
|
+
* API names DO NOT change, because the model-request surface is frozen
|
|
164
|
+
* rent and every byte of it is paid for on every turn. The
|
|
165
|
+
* rename-the-tools path is REJECTED by ruling.
|
|
166
|
+
*
|
|
167
|
+
* One table, for the same reason KEY_BINDINGS above is one table. The
|
|
168
|
+
* mapping used to exist three and a half times — a `.replace("_file",
|
|
169
|
+
* "")` in components.ts, another in render.ts, two more in the
|
|
170
|
+
* compositor, and a private three-tool table for the rollup's expanded
|
|
171
|
+
* list — and the drift was visible on a single screen: a card head
|
|
172
|
+
* reading `read` directly above one reading `list_dir`.
|
|
173
|
+
*
|
|
174
|
+
* An unmapped tool (an extension's, an MCP server's) renders its own
|
|
175
|
+
* name. Inventing a verb for a tool this package has never heard of
|
|
176
|
+
* would be a worse lie than printing what the model actually calls.
|
|
177
|
+
*/
|
|
178
|
+
const DISPLAY_VERB = {
|
|
179
|
+
read_file: "read",
|
|
180
|
+
list_dir: "list",
|
|
181
|
+
search_text: "search",
|
|
182
|
+
write_file: "write",
|
|
183
|
+
edit_file: "edit",
|
|
184
|
+
shell: "shell",
|
|
185
|
+
};
|
|
186
|
+
/** A tool's name as the SCREEN says it. Display-only: the raw name stays
|
|
187
|
+
* on the cell, and dispatch, the mode gate, the policy keys, the /last
|
|
188
|
+
* RAW block and every model-facing byte keep reading that. */
|
|
189
|
+
export function displayVerb(name) {
|
|
190
|
+
return DISPLAY_VERB[name] ?? name;
|
|
191
|
+
}
|
|
192
|
+
/** The panel keys, which belong to a panel rather than the composer —
|
|
193
|
+
* one dim line rather than four table rows, because they apply only
|
|
194
|
+
* while a panel is up. */
|
|
195
|
+
/**
|
|
196
|
+
* TUI2-R1.5 pin 6 — this row has to be true of BOTH panel flavors, and
|
|
197
|
+
* "digits select" was wrong in both directions at once.
|
|
198
|
+
*
|
|
199
|
+
* On an APPROVAL a digit only moves the selection (editor's #panelSelect
|
|
200
|
+
* sets `sel` and renders); ENTER is what resolves it. A reader who
|
|
201
|
+
* pressed 1 and walked away had approved nothing — the worst kind of
|
|
202
|
+
* false affordance, on the surface where the stakes are a side effect.
|
|
203
|
+
*
|
|
204
|
+
* On an ASK the opposite: a digit on a SINGLE-choice question answers it
|
|
205
|
+
* and advances the walk (ask-panel's askKey → advance), so "select"
|
|
206
|
+
* undersold it. A multi-select question toggles and waits for enter,
|
|
207
|
+
* like the approval.
|
|
208
|
+
*
|
|
209
|
+
* "digits pick · ⏎ confirms" is the sentence both flavors satisfy. The
|
|
210
|
+
* single-choice fast path — where the confirm is implicit — is the one
|
|
211
|
+
* thing a single row cannot also carry; it is an omission, never a lie.
|
|
212
|
+
*/
|
|
213
|
+
export const PANEL_KEYS_ROW = "panels: digits pick · ⏎ confirms · space toggles · t types an answer";
|
|
214
|
+
/** The sheet's grid: the first six bindings in two 3-column rows, the
|
|
215
|
+
* last four in two 2-column rows (the wide entries get the room). The
|
|
216
|
+
* COLUMN STOPS are the prototype's absolute positions, floored by the
|
|
217
|
+
* content (a future binding widens its column rather than overrunning
|
|
218
|
+
* it — the grid degrades to one space, never to a collision). */
|
|
219
|
+
const SHEET_GRID = [
|
|
220
|
+
[0, 1, 2],
|
|
221
|
+
[3, 4, 5],
|
|
222
|
+
[6, 7],
|
|
223
|
+
[8, 9],
|
|
224
|
+
];
|
|
225
|
+
const SHEET_STOPS = [
|
|
226
|
+
[16, 43],
|
|
227
|
+
[16, 43],
|
|
228
|
+
[36],
|
|
229
|
+
[36],
|
|
230
|
+
];
|
|
231
|
+
/**
|
|
232
|
+
* TUI2-R1 (D) — the sheet, one screen, static.
|
|
233
|
+
*
|
|
234
|
+
* Rows CUT at the width rather than folding: the sheet's contract with
|
|
235
|
+
* the reader is "one screen", and a folded grid at 40 columns is two
|
|
236
|
+
* screens pretending to be one. A narrow terminal shows fewer columns
|
|
237
|
+
* of the same truth, which is the honest degradation.
|
|
238
|
+
*/
|
|
239
|
+
export function keysSheetRows(W) {
|
|
240
|
+
const p = palette();
|
|
241
|
+
const cell = (i) => {
|
|
242
|
+
const b = KEY_BINDINGS[i];
|
|
243
|
+
return b === undefined ? "" : `${p.code}${b.keys}${p.reset} ${b.what}`;
|
|
244
|
+
};
|
|
245
|
+
const plainCell = (i) => {
|
|
246
|
+
const b = KEY_BINDINGS[i];
|
|
247
|
+
return b === undefined ? "" : `${b.keys} ${b.what}`;
|
|
248
|
+
};
|
|
249
|
+
const rows = [`${p.bold}keys${p.reset}`];
|
|
250
|
+
for (let r = 0; r < SHEET_GRID.length; r += 1) {
|
|
251
|
+
const indexes = SHEET_GRID[r];
|
|
252
|
+
let row = "";
|
|
253
|
+
let width = 0;
|
|
254
|
+
for (let c = 0; c < indexes.length; c += 1) {
|
|
255
|
+
row += cell(indexes[c]);
|
|
256
|
+
width += displayWidth(plainCell(indexes[c]));
|
|
257
|
+
const stop = SHEET_STOPS[r][c];
|
|
258
|
+
if (stop === undefined)
|
|
259
|
+
continue; // the last column pads nothing
|
|
260
|
+
const pad = Math.max(1, stop - width);
|
|
261
|
+
row += " ".repeat(pad);
|
|
262
|
+
width += pad;
|
|
263
|
+
}
|
|
264
|
+
rows.push(row);
|
|
265
|
+
}
|
|
266
|
+
rows.push(`${p.dim}${PANEL_KEYS_ROW}${p.reset}`);
|
|
267
|
+
return rows.map((row) => cutRow(row, W));
|
|
268
|
+
}
|
|
269
|
+
/** One row, cut at the width — SGR-aware, the ellipsis after the reset
|
|
270
|
+
* (the cutLine convention; duplicated here rather than imported so the
|
|
271
|
+
* strings module keeps its no-components-dependency shape). */
|
|
272
|
+
function cutRow(row, W) {
|
|
273
|
+
let out = "";
|
|
274
|
+
let width = 0;
|
|
275
|
+
for (let i = 0; i < row.length;) {
|
|
276
|
+
if (row[i] === "\x1b") {
|
|
277
|
+
const m = /^\x1b\[[0-9;]*m/.exec(row.slice(i))?.[0] ?? row[i];
|
|
278
|
+
out += m;
|
|
279
|
+
i += m.length;
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
const cw = displayWidth(row[i]);
|
|
283
|
+
if (width + cw > W)
|
|
284
|
+
return `${out}${palette().reset}`;
|
|
285
|
+
out += row[i];
|
|
286
|
+
width += cw;
|
|
287
|
+
i += 1;
|
|
288
|
+
}
|
|
289
|
+
return out;
|
|
290
|
+
}
|
|
291
|
+
/** TUI2-R1 (D) — the keys as ONE line, for /help. The same table the
|
|
292
|
+
* sheet renders, joined — so the two can disagree only by deleting a
|
|
293
|
+
* test. The sheet is the readable form; this is the greppable one. */
|
|
294
|
+
export function keysHelpRow() {
|
|
295
|
+
return KEY_BINDINGS.map((b) => `${b.keys} ${b.what}`).join(" · ");
|
|
296
|
+
}
|
|
133
297
|
/**
|
|
134
298
|
* KC3.5 slice ⓪ (the extraction) — the /help command table.
|
|
135
299
|
*
|
|
@@ -152,6 +316,13 @@ export function helpRows() {
|
|
|
152
316
|
cmd("/mode", "show the approval tier; /mode <name> switches (manual/default/accept-edits/plan/bypass)"),
|
|
153
317
|
cmd("/model", "list model profiles; /model <name|provider/model> switches"),
|
|
154
318
|
cmd("/compact", "summarize the older conversation to free context"),
|
|
319
|
+
// TUI2-R1 (D): DELIBERATELY UNCHANGED. Deriving this sentence from
|
|
320
|
+
// KEY_BINDINGS would be an improvement and it would also move an
|
|
321
|
+
// assertion outside the round's two declared supersession classes,
|
|
322
|
+
// so the sheet is the derived surface and this row keeps its bytes.
|
|
323
|
+
// `keysHelpRow()` exists for the round that is allowed to make the
|
|
324
|
+
// swap; until then the drift guard is the test that every binding
|
|
325
|
+
// in the table is mentioned here.
|
|
155
326
|
`${cmd("exit", "leave the session")}\n${cmd("keys", "enter sends · ctrl+J newline (shift+enter where encoded) · esc stops the run · alt+⏎ stops it and sends this instead · @ files · 1-4 answers an ask")}`,
|
|
156
327
|
];
|
|
157
328
|
}
|
package/dist/width.d.ts
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The display-width primitives — the SINGLE width authority (TUI v5
|
|
3
|
-
* #16e: "charWidth is the width authority"). The
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
3
|
+
* #16e: "charWidth is the width authority"). The table covers the East
|
|
4
|
+
* Asian Wide/Fullwidth ranges and Emoji_Presentation=Yes; everything
|
|
5
|
+
* else is one column, the box-drawing/brick glyphs █▀▄▞▸ and the text-
|
|
6
|
+
* presentation marks ✓ ✗ ⚠ ⏸ included.
|
|
7
|
+
*
|
|
8
|
+
* This is the compositor's FLOOR, not a cosmetic detail: a glyph scored
|
|
9
|
+
* one column that a terminal draws in two makes a line whose measured
|
|
10
|
+
* width is <= W really need W+1, the terminal soft-wraps the tail onto
|
|
11
|
+
* the row below, and the live region silently eats a row it never
|
|
12
|
+
* budgeted (TUI2-R2pre ① — the composer clobber).
|
|
13
|
+
*
|
|
14
|
+
* Known limitation, documented in the README: emoji ZWJ clusters and
|
|
15
|
+
* variation-selector sequences are not guaranteed perfect — each code
|
|
16
|
+
* point counts as its own width (U+26A0 + FE0F sums to 2, which is what
|
|
17
|
+
* a terminal draws, but that is arithmetic luck, not a model).
|
|
18
|
+
* Zero dependencies (importable from any module).
|
|
9
19
|
*/
|
|
10
20
|
/** A code point's display width: 2 for the wide ranges, 1 otherwise. */
|
|
11
21
|
export declare function charWidth(cp: number): number;
|
package/dist/width.js
CHANGED
|
@@ -1,12 +1,63 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The display-width primitives — the SINGLE width authority (TUI v5
|
|
3
|
-
* #16e: "charWidth is the width authority"). The
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
3
|
+
* #16e: "charWidth is the width authority"). The table covers the East
|
|
4
|
+
* Asian Wide/Fullwidth ranges and Emoji_Presentation=Yes; everything
|
|
5
|
+
* else is one column, the box-drawing/brick glyphs █▀▄▞▸ and the text-
|
|
6
|
+
* presentation marks ✓ ✗ ⚠ ⏸ included.
|
|
7
|
+
*
|
|
8
|
+
* This is the compositor's FLOOR, not a cosmetic detail: a glyph scored
|
|
9
|
+
* one column that a terminal draws in two makes a line whose measured
|
|
10
|
+
* width is <= W really need W+1, the terminal soft-wraps the tail onto
|
|
11
|
+
* the row below, and the live region silently eats a row it never
|
|
12
|
+
* budgeted (TUI2-R2pre ① — the composer clobber).
|
|
13
|
+
*
|
|
14
|
+
* Known limitation, documented in the README: emoji ZWJ clusters and
|
|
15
|
+
* variation-selector sequences are not guaranteed perfect — each code
|
|
16
|
+
* point counts as its own width (U+26A0 + FE0F sums to 2, which is what
|
|
17
|
+
* a terminal draws, but that is arithmetic luck, not a model).
|
|
18
|
+
* Zero dependencies (importable from any module).
|
|
9
19
|
*/
|
|
20
|
+
/** TUI2-R2pre ① — the Emoji_Presentation=Yes code points inside
|
|
21
|
+
* U+2000..U+2BFF. The rest of that span is TEXT presentation and stays
|
|
22
|
+
* one column: ✓ ✗ ⚠ ⏸ ▞ ▸ and the box-drawing rails are all narrow, and
|
|
23
|
+
* widening any of them would move every card head on the screen. Listed
|
|
24
|
+
* as ranges because that is what the property is — the singles are
|
|
25
|
+
* singles in Unicode too. */
|
|
26
|
+
const EMOJI_PRESENTATION = [
|
|
27
|
+
[0x231a, 0x231b],
|
|
28
|
+
[0x23e9, 0x23ec],
|
|
29
|
+
[0x23f0, 0x23f0],
|
|
30
|
+
[0x23f3, 0x23f3],
|
|
31
|
+
[0x25fd, 0x25fe],
|
|
32
|
+
[0x2614, 0x2615],
|
|
33
|
+
[0x2648, 0x2653],
|
|
34
|
+
[0x267f, 0x267f],
|
|
35
|
+
[0x2693, 0x2693],
|
|
36
|
+
[0x26a1, 0x26a1],
|
|
37
|
+
[0x26aa, 0x26ab],
|
|
38
|
+
[0x26bd, 0x26be],
|
|
39
|
+
[0x26c4, 0x26c5],
|
|
40
|
+
[0x26ce, 0x26ce],
|
|
41
|
+
[0x26d4, 0x26d4],
|
|
42
|
+
[0x26ea, 0x26ea],
|
|
43
|
+
[0x26f2, 0x26f3],
|
|
44
|
+
[0x26f5, 0x26f5],
|
|
45
|
+
[0x26fa, 0x26fa],
|
|
46
|
+
[0x26fd, 0x26fd],
|
|
47
|
+
[0x2705, 0x2705],
|
|
48
|
+
[0x270a, 0x270b],
|
|
49
|
+
[0x2728, 0x2728],
|
|
50
|
+
[0x274c, 0x274c],
|
|
51
|
+
[0x274e, 0x274e],
|
|
52
|
+
[0x2753, 0x2755],
|
|
53
|
+
[0x2757, 0x2757],
|
|
54
|
+
[0x2795, 0x2797],
|
|
55
|
+
[0x27b0, 0x27b0],
|
|
56
|
+
[0x27bf, 0x27bf],
|
|
57
|
+
[0x2b1b, 0x2b1c],
|
|
58
|
+
[0x2b50, 0x2b50],
|
|
59
|
+
[0x2b55, 0x2b55],
|
|
60
|
+
];
|
|
10
61
|
/** A code point's display width: 2 for the wide ranges, 1 otherwise. */
|
|
11
62
|
export function charWidth(cp) {
|
|
12
63
|
if (cp >= 0x1100 && cp <= 0x115f)
|
|
@@ -35,12 +86,33 @@ export function charWidth(cp) {
|
|
|
35
86
|
return 2; // fullwidth forms
|
|
36
87
|
if (cp >= 0xffe0 && cp <= 0xffe6)
|
|
37
88
|
return 2; // fullwidth signs
|
|
89
|
+
// TUI2-R1.5 shipped only two of the pictographic ranges; the holes
|
|
90
|
+
// (transport, mahjong/cards, enclosed, colored shapes, the extended
|
|
91
|
+
// block) were scored ONE column while every terminal draws them in
|
|
92
|
+
// two — the composer clobber of the owner's field report (①).
|
|
93
|
+
if (cp === 0x1f004 || cp === 0x1f0cf)
|
|
94
|
+
return 2; // mahjong red dragon, joker
|
|
95
|
+
if (cp >= 0x1f18e && cp <= 0x1f19a)
|
|
96
|
+
return 2; // enclosed alphanumerics
|
|
97
|
+
if (cp >= 0x1f200 && cp <= 0x1f251)
|
|
98
|
+
return 2; // enclosed ideographic
|
|
38
99
|
if (cp >= 0x1f300 && cp <= 0x1f64f)
|
|
39
100
|
return 2; // emoji (misc + emoticons)
|
|
101
|
+
if (cp >= 0x1f680 && cp <= 0x1f6ff)
|
|
102
|
+
return 2; // transport + map
|
|
103
|
+
if (cp >= 0x1f7e0 && cp <= 0x1f7eb)
|
|
104
|
+
return 2; // colored circles + squares
|
|
40
105
|
if (cp >= 0x1f900 && cp <= 0x1f9ff)
|
|
41
106
|
return 2; // supplemental emoji
|
|
107
|
+
if (cp >= 0x1fa70 && cp <= 0x1faff)
|
|
108
|
+
return 2; // symbols + pictographs ext-A
|
|
42
109
|
if (cp >= 0x20000 && cp <= 0x3fffd)
|
|
43
110
|
return 2; // CJK ext B..G
|
|
111
|
+
if (cp >= 0x231a && cp <= 0x2b55) {
|
|
112
|
+
for (const [lo, hi] of EMOJI_PRESENTATION)
|
|
113
|
+
if (cp >= lo && cp <= hi)
|
|
114
|
+
return 2;
|
|
115
|
+
}
|
|
44
116
|
return 1;
|
|
45
117
|
}
|
|
46
118
|
/** Display width of a code-point array (cursor math, scrolling). */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-tui-cells",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "kiso tui-cells — the components cell renderer (components, diff, width, the render slice). Zero runtime dependencies: input is data, output is bytes.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|