dsh-ssh-tui 0.7.1 → 0.7.3

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.
Files changed (106) hide show
  1. package/README.en.md +92 -8
  2. package/README.md +64 -8
  3. package/cordis.patch.yml +15 -0
  4. package/docs/terminals.md +113 -0
  5. package/lib/approval-cache.js +12 -9
  6. package/lib/approval-cache.js.map +1 -1
  7. package/lib/color-depth.js +10 -4
  8. package/lib/color-depth.js.map +1 -1
  9. package/lib/diag.js +76 -17
  10. package/lib/diag.js.map +1 -1
  11. package/lib/dialogs.js.map +1 -1
  12. package/lib/display-sock.js +304 -29
  13. package/lib/display-sock.js.map +1 -1
  14. package/lib/doctor.js +71 -37
  15. package/lib/doctor.js.map +1 -1
  16. package/lib/dsh-compat.js +206 -88
  17. package/lib/dsh-compat.js.map +1 -1
  18. package/lib/footer.js +39 -9
  19. package/lib/footer.js.map +1 -1
  20. package/lib/gateway-protocol.js +202 -0
  21. package/lib/gateway-protocol.js.map +1 -0
  22. package/lib/i18n/en.js +79 -41
  23. package/lib/i18n/en.js.map +1 -1
  24. package/lib/i18n/index.js +13 -7
  25. package/lib/i18n/index.js.map +1 -1
  26. package/lib/i18n/zh.js +79 -41
  27. package/lib/i18n/zh.js.map +1 -1
  28. package/lib/index.js +132 -31
  29. package/lib/index.js.map +1 -1
  30. package/lib/job-label.js +99 -17
  31. package/lib/job-label.js.map +1 -1
  32. package/lib/paint.js +5 -2
  33. package/lib/paint.js.map +1 -1
  34. package/lib/picker.js +5 -3
  35. package/lib/picker.js.map +1 -1
  36. package/lib/plan.js +269 -17
  37. package/lib/plan.js.map +1 -1
  38. package/lib/platform.js +373 -0
  39. package/lib/platform.js.map +1 -0
  40. package/lib/preset-authoring.js +10 -14
  41. package/lib/preset-authoring.js.map +1 -1
  42. package/lib/preset-compat.js +100 -0
  43. package/lib/preset-compat.js.map +1 -0
  44. package/lib/preset-picker.js +5 -1
  45. package/lib/preset-picker.js.map +1 -1
  46. package/lib/preset-rows.js +83 -13
  47. package/lib/preset-rows.js.map +1 -1
  48. package/lib/provider-catalog.js +7 -4
  49. package/lib/provider-catalog.js.map +1 -1
  50. package/lib/route-memory.js +11 -4
  51. package/lib/route-memory.js.map +1 -1
  52. package/lib/session-index.js +5 -0
  53. package/lib/session-index.js.map +1 -1
  54. package/lib/session-list.js +18 -1
  55. package/lib/session-list.js.map +1 -1
  56. package/lib/session-lock.js +79 -24
  57. package/lib/session-lock.js.map +1 -1
  58. package/lib/session-route.js +334 -0
  59. package/lib/session-route.js.map +1 -0
  60. package/lib/settings-routes.js +10 -0
  61. package/lib/settings-routes.js.map +1 -0
  62. package/lib/settings-subagent.js +10 -0
  63. package/lib/settings-subagent.js.map +1 -0
  64. package/lib/subagent-model.js +84 -6
  65. package/lib/subagent-model.js.map +1 -1
  66. package/lib/supergrok-token.js +4 -0
  67. package/lib/supergrok-token.js.map +1 -1
  68. package/lib/term-text.js +7 -4
  69. package/lib/term-text.js.map +1 -1
  70. package/lib/terminal-caps.js +358 -0
  71. package/lib/terminal-caps.js.map +1 -0
  72. package/lib/tool-present.js +46 -14
  73. package/lib/tool-present.js.map +1 -1
  74. package/lib/tui.js +1949 -371
  75. package/lib/tui.js.map +1 -1
  76. package/lib/types/color-depth.d.ts +3 -3
  77. package/lib/types/diag.d.ts +18 -1
  78. package/lib/types/dialogs.d.ts +4 -0
  79. package/lib/types/display-sock.d.ts +94 -2
  80. package/lib/types/doctor.d.ts +8 -1
  81. package/lib/types/dsh-compat.d.ts +141 -44
  82. package/lib/types/footer.d.ts +21 -5
  83. package/lib/types/gateway-protocol.d.ts +88 -0
  84. package/lib/types/i18n/index.d.ts +18 -12
  85. package/lib/types/index.d.ts +45 -0
  86. package/lib/types/job-label.d.ts +38 -7
  87. package/lib/types/picker.d.ts +3 -0
  88. package/lib/types/plan.d.ts +94 -4
  89. package/lib/types/platform.d.ts +215 -0
  90. package/lib/types/preset-authoring.d.ts +8 -14
  91. package/lib/types/preset-compat.d.ts +58 -0
  92. package/lib/types/preset-picker.d.ts +1 -1
  93. package/lib/types/preset-rows.d.ts +51 -9
  94. package/lib/types/route-memory.d.ts +17 -22
  95. package/lib/types/session-list.d.ts +5 -0
  96. package/lib/types/session-lock.d.ts +9 -0
  97. package/lib/types/session-route.d.ts +191 -0
  98. package/lib/types/settings-routes.d.ts +16 -0
  99. package/lib/types/settings-subagent.d.ts +24 -0
  100. package/lib/types/subagent-model.d.ts +60 -7
  101. package/lib/types/term-text.d.ts +3 -2
  102. package/lib/types/terminal-caps.d.ts +105 -0
  103. package/lib/types/tool-present.d.ts +2 -17
  104. package/lib/types/transcript-types.d.ts +44 -1
  105. package/lib/types/tui.d.ts +333 -9
  106. package/package.json +82 -55
@@ -0,0 +1,358 @@
1
+ /**
2
+ * What the terminal in front of us can actually do.
3
+ *
4
+ * `color-depth.ts` answers the palette question; this answers the rest of the
5
+ * ones that change what we may safely emit: mouse reporting, bracketed paste,
6
+ * the alternate screen, clipboard writes (OSC 52) and hyperlinks (OSC 8), plus
7
+ * which family of terminal we are talking to.
8
+ *
9
+ * Why it is a module and not a few `if`s: these decisions are made from
10
+ * environment variables that nobody on the team can test by hand for every
11
+ * desktop — GNOME Terminal, XFCE Terminal, Konsole, xterm, tmux, the Linux
12
+ * virtual console, Windows Terminal, a legacy Windows console. So the input is
13
+ * a parameter, the table is data, and `tests/terminal-caps.test.mjs` pins one
14
+ * fixture per family on any platform.
15
+ *
16
+ * The bias is **not** "assume the best". A capability we claim but the terminal
17
+ * lacks costs the user something real: `?1000h` on a console that cannot report
18
+ * SGR coordinates swallows its mouse; OSC 52 where nothing reads it makes
19
+ * `/copy` look like it worked; the alternate screen on a serial console loses
20
+ * the scrollback. Where a terminal is known to be marginal, the answer is no,
21
+ * and `DSH_TUI_TERM_CAPS` is the escape hatch for the user who knows better.
22
+ *
23
+ * @module dsh-ssh-tui/terminal-caps
24
+ */
25
+ import { colorDepth } from './color-depth.js';
26
+ /** Capability names a user may force on or off through `DSH_TUI_TERM_CAPS`. */
27
+ const TOGGLEABLE = ['mouse', 'mouseSgr', 'mouseDrag', 'bracketedPaste', 'alternateScreen', 'osc52', 'osc8', 'title'];
28
+ const TRUTHY = new Set(['1', 'true', 'on', 'yes']);
29
+ const FALSY = new Set(['0', 'false', 'off', 'no']);
30
+ /**
31
+ * Parse `DSH_TUI_TERM_CAPS`.
32
+ *
33
+ * `no-mouse`, `mouse`, `mouse=false` and `mouse = false` all mean what they
34
+ * look like: the `=` binds first, so the spaced spelling cannot silently invert
35
+ * into the opposite answer (it did, and `osc52 = false` then *re-enabled* the
36
+ * clipboard promise). A value outside the two vocabularies, an unknown name or a
37
+ * bare `no-` is rejected rather than guessed, and the rejects are returned so
38
+ * `/diag` can show them — this variable is the only feedback channel a user has
39
+ * when an override does not appear to work.
40
+ */
41
+ export function parseCapsOverrideReport(raw) {
42
+ const overrides = {};
43
+ const ignored = [];
44
+ // Bind `=` before splitting on whitespace: `mouse = false` must not become the
45
+ // bare name `mouse` plus two junk tokens, which read as "on".
46
+ for (const piece of raw.replace(/\s*=\s*/gu, '=').split(/[\s,]+/u)) {
47
+ const token = piece.trim();
48
+ if (token === '')
49
+ continue;
50
+ const [rawName, rawValue] = token.split('=');
51
+ const name = (rawName ?? '').trim();
52
+ const value = rawValue === undefined ? undefined : rawValue.trim().toLowerCase();
53
+ const negated = name.startsWith('no-');
54
+ const key = (negated ? name.slice(3) : name);
55
+ const known = name !== 'no-' && TOGGLEABLE.includes(key);
56
+ if (!known) {
57
+ ignored.push(token);
58
+ continue;
59
+ }
60
+ if (negated) {
61
+ // `no-mouse=false` is not a spelling worth guessing at.
62
+ if (value !== undefined) {
63
+ ignored.push(token);
64
+ continue;
65
+ }
66
+ overrides[key] = false;
67
+ continue;
68
+ }
69
+ if (value === undefined || TRUTHY.has(value)) {
70
+ overrides[key] = true;
71
+ // Asking for the mouse asks for the whole set the table gives a mouse
72
+ // terminal: SGR (the only encoding this parser reads, so a bare `mouse`
73
+ // would otherwise be cleared by the cascade and appear to do nothing) and
74
+ // held-button motion, without which a drag never arrives. `no-mouseDrag`
75
+ // on its own still opts out of the drag.
76
+ if (key === 'mouse' || key === 'mouseSgr') {
77
+ overrides.mouse = true;
78
+ overrides.mouseSgr = true;
79
+ if (overrides.mouseDrag === undefined)
80
+ overrides.mouseDrag = true;
81
+ }
82
+ continue;
83
+ }
84
+ if (FALSY.has(value)) {
85
+ overrides[key] = false;
86
+ continue;
87
+ }
88
+ ignored.push(token);
89
+ }
90
+ return { overrides, ignored };
91
+ }
92
+ /** The overrides alone, for callers that do not report. */
93
+ export function parseCapsOverride(raw) {
94
+ return parseCapsOverrideReport(raw).overrides;
95
+ }
96
+ /** A version-ish number out of `VTE_VERSION` / `KONSOLE_VERSION`, or undefined. */
97
+ function numericVersion(raw) {
98
+ const text = (raw ?? '').trim();
99
+ if (text === '')
100
+ return undefined;
101
+ const match = /^(\d+)/u.exec(text);
102
+ return match === null ? undefined : Number(match[1]);
103
+ }
104
+ /** `true`/`false` from a boolean-ish env value, or undefined when unset. */
105
+ function envFlag(raw) {
106
+ const text = (raw ?? '').trim().toLowerCase();
107
+ if (text === '')
108
+ return undefined;
109
+ if (['1', 'true', 'on', 'yes'].includes(text))
110
+ return true;
111
+ if (['0', 'false', 'off', 'no'].includes(text))
112
+ return false;
113
+ return undefined;
114
+ }
115
+ /**
116
+ * Classify the terminal from the environment.
117
+ *
118
+ * Detection is by the markers terminals actually set: Windows Terminal exports
119
+ * `WT_SESSION`; the VTE family (GNOME Terminal, XFCE Terminal, MATE, Tilix,
120
+ * Terminator, Guake) exports `VTE_VERSION`; Konsole exports `KONSOLE_VERSION`;
121
+ * tmux and screen export `TMUX` / `STY` and set a `tmux*` / `screen*` TERM.
122
+ * Everything else falls back to the TERM name.
123
+ */
124
+ export function detectTerminalFamily(probe = {}) {
125
+ const env = probe.env ?? process.env;
126
+ const platform = probe.platform ?? process.platform;
127
+ const term = (env.TERM ?? '').trim().toLowerCase();
128
+ if ((env.TMUX ?? '') !== '' || term.startsWith('tmux')) {
129
+ return { family: 'tmux', label: `tmux (TERM=${term || 'unset'})` };
130
+ }
131
+ if ((env.STY ?? '') !== '' || term.startsWith('screen')) {
132
+ return { family: 'screen', label: `screen (TERM=${term || 'unset'})` };
133
+ }
134
+ if (term === 'dumb')
135
+ return { family: 'dumb', label: 'dumb terminal (assuming the xterm baseline)' };
136
+ // `linux` (and the old VT names) are POSIX console names: on Windows they can
137
+ // only come from a wrapper, and the capabilities they describe — no mouse, no
138
+ // paste, no alternate screen — are not what the ConPTY behind that wrapper
139
+ // has. The Windows classification below decides instead.
140
+ if (platform !== 'win32' && (term === 'linux' || term === 'vt100' || term === 'vt220')) {
141
+ return { family: 'linux-console', label: 'Linux virtual console' };
142
+ }
143
+ const vte = numericVersion(env.VTE_VERSION);
144
+ if (vte !== undefined) {
145
+ // VTE is the widget, not the app: name the app when it says so.
146
+ const program = (env.TERM_PROGRAM ?? '').trim();
147
+ const app = program !== '' ? program : 'GNOME/XFCE/Fork (VTE)';
148
+ return { family: 'vte', label: `${app} (VTE ${vte})` };
149
+ }
150
+ const konsole = numericVersion(env.KONSOLE_VERSION);
151
+ if (konsole !== undefined)
152
+ return { family: 'konsole', label: `Konsole (${konsole})` };
153
+ if (platform === 'win32') {
154
+ if ((env.WT_SESSION ?? '').trim() !== '') {
155
+ return { family: 'windows-terminal', label: 'Windows Terminal' };
156
+ }
157
+ // An empty TERM is what PowerShell and cmd.exe report: that is conhost, the
158
+ // classic console host, and it is the one Windows terminal without OSC 52.
159
+ // A *named* TERM means something else is driving the console — Git-Bash and
160
+ // MSYS2 set `xterm-256color`, mintty identifies itself the same way — and
161
+ // those are xterm-family terminals that do more than conhost, so they fall
162
+ // through to the TERM matching below rather than being read as conhost.
163
+ if (term === '')
164
+ return { family: 'windows-console', label: 'Windows console (conhost)' };
165
+ }
166
+ if (term === '') {
167
+ return platform === 'win32'
168
+ ? { family: 'windows-console', label: 'Windows console (conhost)' }
169
+ : { family: 'unknown', label: 'unknown (TERM unset)' };
170
+ }
171
+ if (term.includes('xterm') || term.includes('rxvt') || term.includes('alacritty') || term.includes('kitty') || term.includes('wezterm')) {
172
+ return { family: 'xterm', label: `${term}` };
173
+ }
174
+ if (platform === 'win32') {
175
+ // A Windows host whose TERM names nothing we know (a wrapper handing us
176
+ // `linux`, or anything else): conhost is what is actually underneath, and
177
+ // `unknown` would claim bracketed paste and hyperlinks this console lacks.
178
+ return { family: 'windows-console', label: 'Windows console (conhost)' };
179
+ }
180
+ return { family: 'unknown', label: term };
181
+ }
182
+ /**
183
+ * Every capability for the terminal in front of us.
184
+ *
185
+ * `DSH_TUI_TERM_CAPS` overrides individual answers (`no-mouse`, `osc52=false`);
186
+ * `DSH_TUI_NO_ALT_SCREEN` and `DSH_TUI_OSC8` keep working because they came
187
+ * first and are documented.
188
+ */
189
+ export function terminalCapabilities(probe = {}) {
190
+ const env = probe.env ?? process.env;
191
+ const platform = probe.platform ?? process.platform;
192
+ const { family, label } = detectTerminalFamily({ env, platform });
193
+ const colors = colorDepth(env, platform);
194
+ const base = {
195
+ // A terminal that paints nothing is not a terminal we may drive.
196
+ mouse: false,
197
+ mouseSgr: false,
198
+ mouseDrag: false,
199
+ bracketedPaste: false,
200
+ alternateScreen: false,
201
+ osc52: false,
202
+ osc8: false,
203
+ title: false,
204
+ };
205
+ switch (family) {
206
+ case 'windows-terminal':
207
+ Object.assign(base, {
208
+ mouse: true, mouseSgr: true, mouseDrag: true,
209
+ bracketedPaste: true, alternateScreen: true,
210
+ // Windows Terminal understands OSC 52 from 1.15 on; it is the only
211
+ // Windows host that does, conhost never has. Claiming it there is what
212
+ // makes `/copy` silently useless on the others.
213
+ osc52: true, osc8: true, title: true,
214
+ });
215
+ break;
216
+ case 'windows-console':
217
+ Object.assign(base, {
218
+ // conhost has had VT mouse input since Windows 10 1703, and mouse
219
+ // reporting is how cards expand, so it stays on — the same trade every
220
+ // other platform makes. What it genuinely lacks is the OSC pair:
221
+ // conhost never writes the clipboard and ignores hyperlinks, which is
222
+ // what `/copy` has to say out loud there.
223
+ mouse: true, mouseSgr: true, mouseDrag: true,
224
+ // Bracketed paste reached conhost only in 2022-11 (Windows 11 22H2), and
225
+ // Windows 10 is still the PowerShell/cmd case this row is about. The
226
+ // paste path survives without it (a multi-line burst is still treated as
227
+ // one paste), so under-claiming here costs nothing and the matrix stays
228
+ // true for the older console.
229
+ bracketedPaste: false,
230
+ alternateScreen: true,
231
+ osc52: false, osc8: false, title: true,
232
+ });
233
+ break;
234
+ case 'vte':
235
+ Object.assign(base, {
236
+ mouse: true, mouseSgr: true, mouseDrag: true,
237
+ bracketedPaste: true, alternateScreen: true,
238
+ // VTE has never implemented OSC 52: the request is still open upstream
239
+ // (GNOME/vte#125) and the widget only *parses* the sequence. Every VTE
240
+ // desktop shares the gap — GNOME Terminal, XFCE Terminal, MATE, Tilix,
241
+ // Terminator, Ptyxis — which made this the single most common terminal
242
+ // to promise a clipboard write it silently drops. `?` would be kinder
243
+ // than `no`, but the user cannot tell the difference when pasting.
244
+ osc52: false,
245
+ // OSC 8 arrived in VTE 0.50, so long-term distributions can lack it.
246
+ osc8: (numericVersion(env.VTE_VERSION) ?? 0) >= 5000,
247
+ title: true,
248
+ });
249
+ break;
250
+ case 'konsole':
251
+ Object.assign(base, {
252
+ mouse: true, mouseSgr: true, mouseDrag: true,
253
+ bracketedPaste: true, alternateScreen: true,
254
+ // Write-only OSC 52 landed in Konsole 24.12 (KDE bug 372116; the patch
255
+ // was merged 2024-07 and 24.08 missed the cut), and KONSOLE_VERSION is
256
+ // YYMMDD (`241200` is 24.12) — so Kubuntu 22.04/24.04 and Debian 12 all
257
+ // ignore the write. 21.12 was Konsole's OSC 8.
258
+ osc52: (numericVersion(env.KONSOLE_VERSION) ?? 0) >= 241200,
259
+ osc8: true, title: true,
260
+ });
261
+ break;
262
+ case 'xterm':
263
+ Object.assign(base, {
264
+ mouse: true, mouseSgr: true, mouseDrag: true,
265
+ bracketedPaste: true, alternateScreen: true,
266
+ // xterm gates clipboard writes behind `allowWindowOps`, which defaults
267
+ // to off in recent builds; assuming it works is how a user ends up with
268
+ // an empty clipboard and no explanation.
269
+ osc52: false, osc8: true, title: true,
270
+ });
271
+ break;
272
+ case 'tmux':
273
+ Object.assign(base, {
274
+ mouse: true, mouseSgr: true, mouseDrag: true,
275
+ bracketedPaste: true, alternateScreen: true,
276
+ // tmux forwards OSC 52 only with `set -g set-clipboard on`.
277
+ osc52: false, osc8: true, title: true,
278
+ });
279
+ break;
280
+ case 'screen':
281
+ Object.assign(base, {
282
+ mouse: true, mouseSgr: true, mouseDrag: true,
283
+ bracketedPaste: true, alternateScreen: true,
284
+ osc52: false, osc8: false, title: true,
285
+ });
286
+ break;
287
+ case 'linux-console':
288
+ Object.assign(base, {
289
+ mouse: false, mouseSgr: false, mouseDrag: false,
290
+ bracketedPaste: false,
291
+ // Keeping the TUI's own screen here costs the user the console
292
+ // scrollback they navigate with Shift+PgUp.
293
+ alternateScreen: false,
294
+ osc52: false, osc8: false, title: false,
295
+ });
296
+ break;
297
+ case 'dumb':
298
+ // A terminal that says `dumb` may still be a capable one behind a wrapper
299
+ // (or a CI pty), and a mode-setting escape it does not understand is
300
+ // ignored, while *not* sending it silently disables the feature. So the
301
+ // baseline stands; only the clipboard and hyperlink promises are dropped,
302
+ // because those are the claims a user cannot verify by looking.
303
+ Object.assign(base, {
304
+ mouse: true, mouseSgr: true, mouseDrag: true,
305
+ bracketedPaste: true, alternateScreen: true,
306
+ osc52: false, osc8: false, title: false,
307
+ });
308
+ break;
309
+ case 'unknown':
310
+ // Something we cannot name: assume the xterm baseline except for the two
311
+ // capabilities that cost the user when wrongly claimed.
312
+ Object.assign(base, {
313
+ mouse: true, mouseSgr: true, mouseDrag: true,
314
+ bracketedPaste: true, alternateScreen: true,
315
+ osc52: false, osc8: true, title: true,
316
+ });
317
+ break;
318
+ }
319
+ if (envFlag(env.DSH_TUI_NO_ALT_SCREEN) === true)
320
+ base.alternateScreen = false;
321
+ const osc8Flag = envFlag(env.DSH_TUI_OSC8);
322
+ if (osc8Flag !== undefined)
323
+ base.osc8 = osc8Flag;
324
+ const override = parseCapsOverrideReport(env.DSH_TUI_TERM_CAPS ?? '');
325
+ Object.assign(base, override.overrides);
326
+ if (base.mouse === false) {
327
+ base.mouseSgr = false;
328
+ base.mouseDrag = false;
329
+ }
330
+ if (base.mouseSgr === false) {
331
+ // The parser understands SGR reports only (`\x1b[<b;x;yM`); enabling the
332
+ // legacy X10 encoding would capture the terminal's mouse and deliver events
333
+ // the TUI drops — dead mouse *and* no native selection. So no SGR means no
334
+ // mouse. (Asking for the mouse turns SGR on with it — see the parser — so
335
+ // this only bites when SGR was refused on purpose.)
336
+ base.mouse = false;
337
+ base.mouseDrag = false;
338
+ }
339
+ return { family, label, colors, ignoredOverrides: override.ignored, ...base };
340
+ }
341
+ /** Mouse-tracking sequences to enable, in one write. Empty when unsupported. */
342
+ export function mouseEnableSequence(caps) {
343
+ if (!caps.mouse)
344
+ return '';
345
+ return `${caps.mouseSgr ? '\x1b[?1006h' : ''}${caps.mouseDrag ? '\x1b[?1002h' : ''}\x1b[?1000h`;
346
+ }
347
+ /** Mouse-tracking sequences to disable. Always safe to emit: `l` on a mode the
348
+ * terminal never enabled is ignored, and leaving a mode on is not. */
349
+ export function mouseDisableSequence() {
350
+ return '\x1b[?1000l\x1b[?1002l\x1b[?1006l';
351
+ }
352
+ /** Bracketed-paste enable/disable, or empty when the terminal lacks it. */
353
+ export function bracketedPasteSequence(caps, on) {
354
+ if (!caps.bracketedPaste)
355
+ return '';
356
+ return on ? '\x1b[?2004h' : '\x1b[?2004l';
357
+ }
358
+ //# sourceMappingURL=terminal-caps.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminal-caps.js","sourceRoot":"","sources":["../src/terminal-caps.ts"],"names":[],"mappings":"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"}
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import { SIDE_BY_SIDE_GUTTER, diffHunks, diffLayout, diffLines, sideBySideRows, wordDiffSpans, } from './line-diff.js';
5
5
  import { t } from './i18n/index.js';
6
- import { jobAlias } from './job-label.js';
6
+ import { jobAlias, looksLikeOpaqueId } from './job-label.js';
7
7
  import { padToWidth, sliceCodePoints, truncate, wrap } from './term-text.js';
8
8
  import { firstString, parseJsonArgs, scalarText } from './json-args.js';
9
9
  import { parsePlanTodos, planTitleFromMarkdown, todoProgressBar, todoProgressLabel, todoSummary, askSummary, TODO_STATUS_MARK, todoItemKind, planMarkdownFromArgs, } from './plan.js';
@@ -63,18 +63,37 @@ export function countDiffLines(hunks) {
63
63
  }
64
64
  return total;
65
65
  }
66
- /** Added / removed line counts for a diff (`oldText: null` means a new file). */
66
+ /**
67
+ * Added / removed line counts for a diff (`oldText: null` means a new file).
68
+ *
69
+ * The counts are the *diff's*, not the operands': a three-line snippet with one
70
+ * changed line is `+1 -1`, not `+3 -3`. Counting whole operands made the card
71
+ * header disagree with everything under it — the footer and, since the columns
72
+ * got their colour back, the coloured rows themselves said one line had changed
73
+ * while the header claimed the entire snippet had.
74
+ *
75
+ * The LCS behind `diffLines` is not free, and a paint asks for this stat on
76
+ * every edit card it draws, so the answer is memoized on the hunk array — the
77
+ * same array the row keeps for its whole life.
78
+ */
79
+ const diffStatCache = new WeakMap();
67
80
  export function countDiffAddDel(hunks) {
68
- const stat = { add: 0, del: 0 };
69
81
  if (hunks === undefined)
70
- return stat;
82
+ return { add: 0, del: 0 };
83
+ const cached = diffStatCache.get(hunks);
84
+ if (cached !== undefined)
85
+ return { ...cached };
86
+ const stat = { add: 0, del: 0 };
71
87
  for (const hunk of hunks) {
72
- stat.add += hunk.newText === '' ? 0 : hunk.newText.split('\n').length;
73
- if (hunk.oldText !== null) {
74
- stat.del += hunk.oldText === '' ? 0 : hunk.oldText.split('\n').length;
88
+ for (const line of diffLines(hunk.oldText ?? '', hunk.newText)) {
89
+ if (line.kind === 'add')
90
+ stat.add += 1;
91
+ else if (line.kind === 'del')
92
+ stat.del += 1;
75
93
  }
76
94
  }
77
- return stat;
95
+ diffStatCache.set(hunks, stat);
96
+ return { ...stat };
78
97
  }
79
98
  /**
80
99
  * Per-file change counts for the compact view's collapsed line.
@@ -234,7 +253,7 @@ const TOOL_TITLE_KEYS = [
234
253
  'web_search', 'web_fetch',
235
254
  ];
236
255
  export function toolTitle(name) {
237
- if (name === '' || name.startsWith('call-'))
256
+ if (name === '' || looksLikeOpaqueId(name))
238
257
  return t('card.tool');
239
258
  return t(`toolTitle.${name}`, undefined, name === 'tool' ? t('card.tool') : name);
240
259
  }
@@ -569,10 +588,12 @@ export function renderToolDiff(diffs, maxLines, width) {
569
588
  /**
570
589
  * The diff rows with the two columns of a wide terminal.
571
590
  *
572
- * A paired row is neutral rather than styled as an addition or a removal: it is
573
- * both, and styling it as one would mislabel half of it. The `-`/`+` markers
574
- * inside each column carry the meaning, and the emphasis still marks the
575
- * characters that differ.
591
+ * A paired row is a removal on the left and an addition on the right at the
592
+ * same time, so it carries one role *per column* (`columns`) instead of one kind
593
+ * for the row — the painter colours each side on its own and leaves the gutter
594
+ * alone. A row with a single side is wholly that change, and a context row,
595
+ * which reads the same on both sides, stays neutral. The `-`/`+` markers inside
596
+ * each column still carry the meaning on a terminal without colour.
576
597
  */
577
598
  function paintHunksSideBySide(perHunk, context, width) {
578
599
  const column = Math.max(20, Math.floor((width - 2 - SIDE_BY_SIDE_GUTTER.length) / 2));
@@ -594,10 +615,21 @@ function paintHunksSideBySide(perHunk, context, width) {
594
615
  ...row.leftSpans,
595
616
  ...row.rightSpans.map(span => ({ start: span.start + column + SIDE_BY_SIDE_GUTTER.length, end: span.end + column + SIDE_BY_SIDE_GUTTER.length })),
596
617
  ];
618
+ // A side with no line of its own gets no colour: a removal is red where
619
+ // the removal is, an addition green where the addition is, and the
620
+ // blank half of a one-sided change stays neutral instead of turning the
621
+ // whole row into a bar with nothing in it.
622
+ const columns = [
623
+ ...(row.context || row.left === '' ? [] : [{ start: 0, end: column, kind: 'diff-del' }]),
624
+ ...(row.context || row.right === ''
625
+ ? []
626
+ : [{ start: column + SIDE_BY_SIDE_GUTTER.length, end: text.length, kind: 'diff-add' }]),
627
+ ];
597
628
  rows.push({
598
- kind: row.context ? 'tool-result' : 'tool-result',
629
+ kind: 'tool-result',
599
630
  text,
600
631
  ...(spans.length === 0 ? {} : { spans }),
632
+ ...(columns.length === 0 ? {} : { columns }),
601
633
  });
602
634
  }
603
635
  }