@toddzheng024/dscode-bundle 0.7.11 → 0.7.13

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 (154) hide show
  1. package/THIRD_PARTY_NOTICES.md +3 -3
  2. package/cordis.patch.yml +1 -1
  3. package/package.json +5 -4
  4. package/plugins/i18n/messages.mjs +6 -6
  5. package/plugins/session-metrics/view.mjs +16 -5
  6. package/plugins/tui-tools/index.mjs +14 -1
  7. package/vendor/tui/lib/app.mjs +6674 -0
  8. package/vendor/tui/lib/approval.mjs +122 -0
  9. package/vendor/tui/lib/attachments.mjs +218 -0
  10. package/vendor/tui/lib/authorization-panel.mjs +242 -0
  11. package/vendor/tui/lib/authorization.mjs +111 -0
  12. package/vendor/tui/lib/commands.mjs +123 -0
  13. package/vendor/tui/lib/dscode/chat.mjs +64 -0
  14. package/vendor/tui/lib/dscode/flags.mjs +32 -0
  15. package/vendor/tui/lib/dscode/model-search.mjs +68 -0
  16. package/vendor/tui/lib/dscode/paste.mjs +87 -0
  17. package/vendor/tui/lib/dscode/telemetry.mjs +82 -0
  18. package/vendor/tui/lib/dscode/welcome.mjs +100 -0
  19. package/vendor/tui/lib/editor-keys.mjs +346 -0
  20. package/vendor/tui/lib/editor.mjs +50 -0
  21. package/vendor/tui/lib/fork.mjs +24 -0
  22. package/vendor/tui/lib/git-workflow.mjs +278 -0
  23. package/vendor/tui/{types/history.d.ts → lib/history.mjs} +69 -26
  24. package/vendor/tui/{types/i18n.d.ts → lib/i18n.mjs} +23 -15
  25. package/vendor/tui/lib/index.mjs +2198 -0
  26. package/vendor/tui/lib/input-split.mjs +170 -0
  27. package/vendor/tui/lib/internals.mjs +90 -0
  28. package/vendor/tui/lib/invariant.mjs +22 -0
  29. package/vendor/tui/lib/kernel-panels.mjs +1265 -0
  30. package/vendor/tui/lib/keyboard.mjs +283 -0
  31. package/vendor/tui/lib/language-panel.mjs +41 -0
  32. package/vendor/tui/lib/locales/en.mjs +495 -0
  33. package/vendor/tui/lib/locales/zh.mjs +495 -0
  34. package/vendor/tui/lib/mentions.mjs +150 -0
  35. package/vendor/tui/lib/model-capabilities.mjs +245 -0
  36. package/vendor/tui/lib/models.mjs +215 -0
  37. package/vendor/tui/{types/panel-accent.d.ts → lib/panel-accent.mjs} +11 -7
  38. package/vendor/tui/lib/permissions.mjs +45 -0
  39. package/vendor/tui/lib/plugin-inventory.mjs +27 -0
  40. package/vendor/tui/lib/presets.mjs +46 -0
  41. package/vendor/tui/lib/provider-settings.mjs +600 -0
  42. package/vendor/tui/lib/questions.mjs +116 -0
  43. package/vendor/tui/lib/rainbow.mjs +181 -0
  44. package/vendor/tui/lib/render/animations.mjs +665 -0
  45. package/vendor/tui/lib/render/editor.mjs +437 -0
  46. package/vendor/tui/lib/render/export.mjs +119 -0
  47. package/vendor/tui/lib/render/fuzzy.mjs +79 -0
  48. package/vendor/tui/lib/render/ime-cursor.mjs +135 -0
  49. package/vendor/tui/lib/render/inspector.mjs +93 -0
  50. package/vendor/tui/lib/render/lines.mjs +538 -0
  51. package/vendor/tui/lib/render/markdown.mjs +592 -0
  52. package/vendor/tui/lib/render/projection.mjs +1862 -0
  53. package/vendor/tui/lib/render/status.mjs +591 -0
  54. package/vendor/tui/lib/render/text.mjs +157 -0
  55. package/vendor/tui/lib/render/tool-detail.mjs +187 -0
  56. package/vendor/tui/lib/render/tool-preview.mjs +79 -0
  57. package/vendor/tui/lib/render/usage.mjs +336 -0
  58. package/vendor/tui/lib/render/width.mjs +191 -0
  59. package/vendor/tui/lib/session-directory.mjs +329 -0
  60. package/vendor/tui/lib/session-query.mjs +184 -0
  61. package/vendor/tui/lib/session-switch.mjs +51 -0
  62. package/vendor/tui/lib/settings-file.mjs +75 -0
  63. package/vendor/tui/lib/skills.mjs +119 -0
  64. package/vendor/tui/lib/startup.mjs +111 -0
  65. package/vendor/tui/lib/store.mjs +129 -0
  66. package/vendor/tui/lib/subagents.mjs +200 -0
  67. package/vendor/tui/lib/terminal-title.mjs +186 -0
  68. package/vendor/tui/lib/theme-panel.mjs +60 -0
  69. package/vendor/tui/lib/theme.mjs +379 -0
  70. package/vendor/tui/lib/update-panel.mjs +239 -0
  71. package/vendor/tui/lib/update.mjs +101 -0
  72. package/vendor/tui/lib/version.mjs +105 -0
  73. package/vendor/tui/lib/whale-glyph.mjs +20 -0
  74. package/vendor/tui/LICENSE +0 -21
  75. package/vendor/tui/devtools-CdTl3MNy.mjs +0 -3643
  76. package/vendor/tui/dscode-email/cli.mjs +0 -32
  77. package/vendor/tui/dscode-email/contacts.mjs +0 -36
  78. package/vendor/tui/dscode-email/gmail-oauth.mjs +0 -69
  79. package/vendor/tui/dscode-email/gmail-store.mjs +0 -2
  80. package/vendor/tui/dscode-email/gmail.mjs +0 -194
  81. package/vendor/tui/dscode-email/imap.mjs +0 -136
  82. package/vendor/tui/dscode-email/inbox.d.mts +0 -25
  83. package/vendor/tui/dscode-email/inbox.mjs +0 -76
  84. package/vendor/tui/dscode-email/smtp.mjs +0 -69
  85. package/vendor/tui/dscode-email/store.mjs +0 -32
  86. package/vendor/tui/dscode-providers/catalog.mjs +0 -136
  87. package/vendor/tui/dscode-providers/effort.mjs +0 -35
  88. package/vendor/tui/dscode-providers/openrouter-account.mjs +0 -171
  89. package/vendor/tui/index.mjs +0 -44483
  90. package/vendor/tui/invariant.mjs +0 -21
  91. package/vendor/tui/rolldown-runtime-CMFfr-1z.mjs +0 -26
  92. package/vendor/tui/session-query.mjs +0 -150
  93. package/vendor/tui/startup.mjs +0 -109
  94. package/vendor/tui/theme-7u5Qo3dF.mjs +0 -1265
  95. package/vendor/tui/types/app.d.ts +0 -348
  96. package/vendor/tui/types/approval.d.ts +0 -59
  97. package/vendor/tui/types/attachments.d.ts +0 -52
  98. package/vendor/tui/types/authorization-panel.d.ts +0 -22
  99. package/vendor/tui/types/authorization.d.ts +0 -36
  100. package/vendor/tui/types/commands.d.ts +0 -52
  101. package/vendor/tui/types/editor-keys.d.ts +0 -105
  102. package/vendor/tui/types/editor.d.ts +0 -6
  103. package/vendor/tui/types/fork.d.ts +0 -8
  104. package/vendor/tui/types/git-workflow.d.ts +0 -121
  105. package/vendor/tui/types/index.d.ts +0 -223
  106. package/vendor/tui/types/input-split.d.ts +0 -54
  107. package/vendor/tui/types/internals.d.ts +0 -26
  108. package/vendor/tui/types/invariant.d.ts +0 -15
  109. package/vendor/tui/types/kernel-panels.d.ts +0 -245
  110. package/vendor/tui/types/keyboard.d.ts +0 -80
  111. package/vendor/tui/types/language-panel.d.ts +0 -12
  112. package/vendor/tui/types/locales/en.d.ts +0 -450
  113. package/vendor/tui/types/locales/zh.d.ts +0 -9
  114. package/vendor/tui/types/mentions.d.ts +0 -85
  115. package/vendor/tui/types/model-capabilities.d.ts +0 -82
  116. package/vendor/tui/types/models.d.ts +0 -133
  117. package/vendor/tui/types/permissions.d.ts +0 -27
  118. package/vendor/tui/types/plugin-inventory.d.ts +0 -11
  119. package/vendor/tui/types/presets.d.ts +0 -22
  120. package/vendor/tui/types/provider-settings.d.ts +0 -239
  121. package/vendor/tui/types/questions.d.ts +0 -54
  122. package/vendor/tui/types/rainbow.d.ts +0 -69
  123. package/vendor/tui/types/render/animations.d.ts +0 -307
  124. package/vendor/tui/types/render/editor.d.ts +0 -163
  125. package/vendor/tui/types/render/export.d.ts +0 -9
  126. package/vendor/tui/types/render/fuzzy.d.ts +0 -21
  127. package/vendor/tui/types/render/ime-cursor.d.ts +0 -60
  128. package/vendor/tui/types/render/inspector.d.ts +0 -62
  129. package/vendor/tui/types/render/lines.d.ts +0 -86
  130. package/vendor/tui/types/render/markdown.d.ts +0 -29
  131. package/vendor/tui/types/render/projection.d.ts +0 -587
  132. package/vendor/tui/types/render/status.d.ts +0 -196
  133. package/vendor/tui/types/render/text.d.ts +0 -64
  134. package/vendor/tui/types/render/tool-detail.d.ts +0 -94
  135. package/vendor/tui/types/render/tool-preview.d.ts +0 -28
  136. package/vendor/tui/types/render/usage.d.ts +0 -113
  137. package/vendor/tui/types/render/width.d.ts +0 -29
  138. package/vendor/tui/types/session-directory.d.ts +0 -190
  139. package/vendor/tui/types/session-query.d.ts +0 -92
  140. package/vendor/tui/types/session-switch.d.ts +0 -17
  141. package/vendor/tui/types/settings-file.d.ts +0 -41
  142. package/vendor/tui/types/skills.d.ts +0 -47
  143. package/vendor/tui/types/startup.d.ts +0 -65
  144. package/vendor/tui/types/store.d.ts +0 -58
  145. package/vendor/tui/types/subagents.d.ts +0 -70
  146. package/vendor/tui/types/terminal-title.d.ts +0 -66
  147. package/vendor/tui/types/theme-panel.d.ts +0 -24
  148. package/vendor/tui/types/theme.d.ts +0 -434
  149. package/vendor/tui/types/update-panel.d.ts +0 -49
  150. package/vendor/tui/types/update.d.ts +0 -75
  151. package/vendor/tui/types/version.d.ts +0 -19
  152. package/vendor/tui/types/whale-glyph.d.ts +0 -6
  153. /package/vendor/tui/{dscode-clipboard-image → lib/dscode/clipboard-image}/clipboard-image.swift +0 -0
  154. /package/vendor/tui/{dscode-clipboard-image → lib/dscode/clipboard-image}/index.mjs +0 -0
@@ -0,0 +1,283 @@
1
+ /**
2
+ * Keyboard enhancement protocol (Codex `keyboard_modes` parity) and the
3
+ * kitty CSI-u normalization layer.
4
+ *
5
+ * The TUI pushes the kitty keyboard protocol with DISAMBIGUATE_ESCAPE_CODES
6
+ * and REPORT_ALTERNATE_KEYS (flags 1|4 = `\x1b[>5u`). Event types are
7
+ * deliberately NOT requested: Ink 5's parser cannot decode the
8
+ * `:event-type` suffix, and repeat/release reporting buys this surface
9
+ * nothing.
10
+ *
11
+ * Ink 5 also cannot parse most CSI-u forms at all — they fall through its
12
+ * regex as unnamed sequences and get INSERTED AS DRAFT TEXT. The composer's
13
+ * stdin read patch therefore rewrites every CSI-u form it can decode back
14
+ * to the legacy byte or canonical sequence the existing key handling
15
+ * already understands, before Ink ever parses the chunk.
16
+ * @module @deepseek-ai/dsh-code/keyboard
17
+ */
18
+ /** Push keyboard enhancement (modifyOtherKeys off, kitty flags 1|4). */
19
+ export const KEYBOARD_ENHANCE_ENABLE = '\x1b[>4;0m\x1b[>5u';
20
+ /** Pop the enhancement stack and reset modifyOtherKeys (exit path). */
21
+ export const KEYBOARD_ENHANCE_DISABLE = '\x1b[<u\x1b[>4;0m';
22
+ /** Explicit environment overrides for terminal keyboard enhancement. */
23
+ export const DSH_DISABLE_KEYBOARD_ENHANCEMENT = 'DSH_DISABLE_KEYBOARD_ENHANCEMENT';
24
+ export const DSH_ENABLE_KEYBOARD_ENHANCEMENT = 'DSH_ENABLE_KEYBOARD_ENHANCEMENT';
25
+ function parseBooleanEnv(value) {
26
+ if (value === undefined)
27
+ return undefined;
28
+ const normalized = value.trim().toLowerCase();
29
+ if (normalized === '1' || normalized === 'true' || normalized === 'yes')
30
+ return true;
31
+ if (normalized === '0' || normalized === 'false' || normalized === 'no')
32
+ return false;
33
+ return undefined;
34
+ }
35
+ /** True when the process is running inside the VS Code integrated terminal. */
36
+ export function isVsCodeTerminalEnv(env = process.env) {
37
+ return env.TERM_PROGRAM?.trim().toLowerCase() === 'vscode' || env.VSCODE_INJECTION === '1';
38
+ }
39
+ /** Whether to push Kitty keyboard enhancement for the current terminal. */
40
+ export function shouldEnableKeyboardEnhancement(env = process.env) {
41
+ const explicitDisable = parseBooleanEnv(env[DSH_DISABLE_KEYBOARD_ENHANCEMENT]);
42
+ if (explicitDisable === true)
43
+ return false;
44
+ const explicitEnable = parseBooleanEnv(env[DSH_ENABLE_KEYBOARD_ENHANCEMENT]);
45
+ if (explicitEnable !== undefined)
46
+ return explicitEnable;
47
+ return !isVsCodeTerminalEnv(env);
48
+ }
49
+ /** Enable bracketed paste reporting. */
50
+ export const BRACKETED_PASTE_ENABLE = '\x1b[?2004h';
51
+ /** Disable bracketed paste reporting. */
52
+ export const BRACKETED_PASTE_DISABLE = '\x1b[?2004l';
53
+ /** Enable terminal focus-in/focus-out reporting (xterm focus protocol). */
54
+ export const TERMINAL_FOCUS_REPORT_ENABLE = '\x1b[?1004h';
55
+ /** Disable terminal focus-in/focus-out reporting. */
56
+ export const TERMINAL_FOCUS_REPORT_DISABLE = '\x1b[?1004l';
57
+ /**
58
+ * Remove xterm focus reports from one input chunk and update the caller's
59
+ * focus state. Focus reports are terminal protocol, not composer text.
60
+ */
61
+ export function stripTerminalFocusEvents(chunk, onFocus) {
62
+ return chunk.replace(/\x1b\[(I|O)/gu, (_whole, event) => {
63
+ onFocus(event === 'I');
64
+ return '';
65
+ });
66
+ }
67
+ /** Bracketed paste markers as Ink delivers them (it strips the leading ESC). */
68
+ export const PASTE_START_MARKER = '[200~';
69
+ export const PASTE_END_MARKER = '[201~';
70
+ /**
71
+ * How long an unterminated bracketed-paste block may hold buffered bytes
72
+ * before the input splitter strips its start marker and releases them: a
73
+ * terminal that loses the end marker must never take the whole keyboard
74
+ * hostage (Esc/Ctrl+C included). Shared by the splitter and the composer's
75
+ * lost-paste safety net so both use one window.
76
+ */
77
+ export const PASTE_BRACKET_TIMEOUT_MS = 1_000;
78
+ /**
79
+ * Remove bracketed paste markers from one input chunk. Panel drafts accept raw
80
+ * `input` text, where an unhandled paste would otherwise persist the literal
81
+ * "[200~"/"[201~" markers Ink leaves after stripping the ESC byte.
82
+ */
83
+ export function stripPasteMarkers(text) {
84
+ return text
85
+ .replaceAll(`\x1b${PASTE_START_MARKER}`, '')
86
+ .replaceAll(`\x1b${PASTE_END_MARKER}`, '')
87
+ .replaceAll(PASTE_START_MARKER, '')
88
+ .replaceAll(PASTE_END_MARKER, '');
89
+ }
90
+ /** Match one CSI-u sequence (code, optional ;modifiers, then :event or ;alternate). */
91
+ const CSI_U_SOURCE = '\x1b\\[(\\d+)(?:;(\\d+))?(?:(:|;)(\\d+))?u';
92
+ /** Kitty private-use keycodes for the numeric keypad and keypad Enter. */
93
+ const KITTY_KEYPAD_CODES = {
94
+ 57399: '0',
95
+ 57400: '1',
96
+ 57401: '2',
97
+ 57402: '3',
98
+ 57403: '4',
99
+ 57404: '5',
100
+ 57405: '6',
101
+ 57406: '7',
102
+ 57407: '8',
103
+ 57408: '9',
104
+ 57414: '\r',
105
+ };
106
+ /** Legacy equivalent for one decoded CSI-u key, or undefined to pass through. */
107
+ function legacyForKey(key) {
108
+ const bits = Math.max(0, key.modifiers - 1);
109
+ const shift = (bits & 1) !== 0;
110
+ const alt = (bits & 2) !== 0;
111
+ const ctrl = (bits & 4) !== 0;
112
+ if (key.code === 13) {
113
+ // Modified Enter is the composer's newline family (Codex insert_newline):
114
+ // Ctrl+Enter and Shift+Enter collapse to the LF byte Ctrl+J already sends,
115
+ // Alt keeps its legacy escape form, and plain Enter stays the submit key.
116
+ if (ctrl || shift)
117
+ return '\n';
118
+ if (alt)
119
+ return '\x1b\r';
120
+ return '\r';
121
+ }
122
+ if (key.code === 27)
123
+ return '\x1b';
124
+ if (key.code === 9)
125
+ return shift ? '\x1b[Z' : '\t';
126
+ if (key.code === 127)
127
+ return alt || ctrl ? '\x1b\x7f' : '\x7f';
128
+ const keypad = KITTY_KEYPAD_CODES[key.code];
129
+ if (keypad !== undefined) {
130
+ if (alt)
131
+ return '\x1b' + keypad;
132
+ return keypad;
133
+ }
134
+ // Kitty disambiguate mode reports the six legacy functional keys as CSI u
135
+ // codes 1-6 (Home, Insert, Delete, End, PageUp, PageDown). Ink 5 cannot
136
+ // parse these forms and would insert literal "[3u" text into the draft, so
137
+ // rewrite them to the legacy sequences the input layer already annotates.
138
+ // The modifier parameter passes through: kitty and xterm share the same
139
+ // 1+bit-field encoding (shift 2, alt 3, ctrl 5, ...). Lock-key bits are
140
+ // dropped because the legacy sequences cannot express them.
141
+ if (key.code >= 1 && key.code <= 6) {
142
+ const mask = (shift ? 1 : 0) + (alt ? 2 : 0) + (ctrl ? 4 : 0);
143
+ const mods = mask === 0 ? '' : `;${mask + 1}`;
144
+ if (key.code === 1)
145
+ return mods === '' ? '\x1b[H' : `\x1b[1${mods}H`;
146
+ if (key.code === 4)
147
+ return mods === '' ? '\x1b[F' : `\x1b[1${mods}F`;
148
+ return `\x1b[${key.code}${mods}~`;
149
+ }
150
+ if (key.code >= 97 && key.code <= 122) {
151
+ const letter = String.fromCodePoint(key.code);
152
+ if (ctrl)
153
+ return String.fromCodePoint(key.code - 96);
154
+ if (alt)
155
+ return '\x1b' + letter;
156
+ if (shift)
157
+ return String.fromCodePoint(key.alternate ?? key.code - 32);
158
+ return letter;
159
+ }
160
+ if (key.code >= 65 && key.code <= 90) {
161
+ if (ctrl)
162
+ return String.fromCodePoint(key.code + 32 - 96);
163
+ if (alt)
164
+ return '\x1b' + String.fromCodePoint(key.code + 32);
165
+ return String.fromCodePoint(key.code);
166
+ }
167
+ if (key.code >= 32 && key.code <= 126) {
168
+ // Kitty reports ordinary printable keys as CSI-u with no alternate code
169
+ // (for example space is `ESC[32u` and `1` is `ESC[49u`). Keep the
170
+ // alternate form when present, but never leave a plain printable key as
171
+ // an unknown escape sequence for Ink to swallow.
172
+ const base = key.alternate ?? key.code;
173
+ if (ctrl && base - 96 >= 1 && base - 96 <= 26)
174
+ return String.fromCodePoint(base - 96);
175
+ if (alt)
176
+ return '\x1b' + String.fromCodePoint(base);
177
+ return String.fromCodePoint(base);
178
+ }
179
+ return undefined;
180
+ }
181
+ /**
182
+ * Rewrite every decodable kitty CSI-u sequence in one stdin chunk to the
183
+ * legacy form the input layer already handles. Undecodable or non-key
184
+ * sequences pass through untouched, so terminals without the protocol are
185
+ * unaffected.
186
+ */
187
+ export function normalizeKeyboardChunk(chunk) {
188
+ // dscode: xterm's modifyOtherKeys sends Shift+Enter as this CSI sequence and Ink
189
+ // leaves it unparsed, so rewrite it to a newline before the Kitty path.
190
+ chunk = chunk.replace(/\x1b\[27;2;13~/g, '\n');
191
+ if (!chunk.includes('\x1b[') || !chunk.includes('u'))
192
+ return chunk;
193
+ const pattern = new RegExp(CSI_U_SOURCE, 'g');
194
+ return chunk.replace(pattern, (whole, code, mods, separator, third) => {
195
+ const legacy = legacyForKey({
196
+ code: Number.parseInt(code, 10),
197
+ modifiers: mods === undefined || mods === '' ? 1 : Math.max(1, Number.parseInt(mods, 10)),
198
+ // A colon introduces Kitty's optional event type (`:1` = press), not
199
+ // an alternate key code. Semicolon introduces the alternate code.
200
+ alternate: separator === ';' && third !== undefined && third !== '' ? Number.parseInt(third, 10) : undefined,
201
+ });
202
+ return legacy ?? whole;
203
+ });
204
+ }
205
+ const HOME_SEQUENCES = ['\x1b[H', '\x1b[1~', '\x1b[7~', '\x1bOH'];
206
+ const END_SEQUENCES = ['\x1b[F', '\x1b[4~', '\x1b[8~', '\x1bOF'];
207
+ /** Parse one CSI functional-key sequence at `offset`. */
208
+ function functionalToken(chunk, offset) {
209
+ const tail = chunk.slice(offset);
210
+ for (const sequence of HOME_SEQUENCES) {
211
+ if (tail.startsWith(sequence))
212
+ return { token: { kind: 'home' }, length: sequence.length };
213
+ }
214
+ for (const sequence of END_SEQUENCES) {
215
+ if (tail.startsWith(sequence))
216
+ return { token: { kind: 'end' }, length: sequence.length };
217
+ }
218
+ const modifiedHome = /^\x1b\[1;(\d+)H/u.exec(tail);
219
+ if (modifiedHome !== null)
220
+ return { token: { kind: 'home' }, length: modifiedHome[0].length };
221
+ const modifiedEnd = /^\x1b\[1;(\d+)F/u.exec(tail);
222
+ if (modifiedEnd !== null)
223
+ return { token: { kind: 'end' }, length: modifiedEnd[0].length };
224
+ const modifiedDelete = /^\x1b\[3(?:;(\d+))?~/u.exec(tail);
225
+ if (modifiedDelete !== null) {
226
+ const modifiers = Number.parseInt(modifiedDelete[1] ?? '1', 10) - 1;
227
+ const byWord = (modifiers & 2) !== 0 || (modifiers & 4) !== 0;
228
+ return {
229
+ token: { kind: byWord ? 'delete-word-forward' : 'delete-forward' },
230
+ length: modifiedDelete[0].length,
231
+ };
232
+ }
233
+ return undefined;
234
+ }
235
+ /**
236
+ * Tokenize a stdin chunk containing at least one editor-only key. Ink calls
237
+ * `useInput` once for a pasted/batched chunk, so preserving each action here
238
+ * prevents repeated Backspace/Home/End/Delete presses from collapsing into
239
+ * one blurred key event. Unknown escape sequences return undefined and stay
240
+ * under Ink's ownership.
241
+ */
242
+ export function tokenizeRawEditorChunk(chunk) {
243
+ const tokens = [];
244
+ let text = '';
245
+ let special = false;
246
+ const flushText = () => {
247
+ if (text === '')
248
+ return;
249
+ tokens.push({ kind: 'text', text });
250
+ text = '';
251
+ };
252
+ for (let offset = 0; offset < chunk.length;) {
253
+ if (chunk.startsWith('\x1b\x7f', offset) || chunk.startsWith('\x1b\b', offset)) {
254
+ flushText();
255
+ tokens.push({ kind: 'delete-word-backward' });
256
+ special = true;
257
+ offset += 2;
258
+ continue;
259
+ }
260
+ const functional = functionalToken(chunk, offset);
261
+ if (functional !== undefined) {
262
+ flushText();
263
+ tokens.push(functional.token);
264
+ special = true;
265
+ offset += functional.length;
266
+ continue;
267
+ }
268
+ const char = chunk[offset];
269
+ if (char === '\x7f' || char === '\b') {
270
+ flushText();
271
+ tokens.push({ kind: 'delete-backward' });
272
+ special = true;
273
+ offset += 1;
274
+ continue;
275
+ }
276
+ if (char === '\x1b')
277
+ return undefined;
278
+ text += char;
279
+ offset += 1;
280
+ }
281
+ flushText();
282
+ return special ? tokens : undefined;
283
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * The /language picker: one row per interface language, the active one
3
+ * marked, arrow selection, Enter applies (persisting through the runner and
4
+ * repainting the whole screen so Static-region text switches too).
5
+ */
6
+ import { createElement, useState } from 'react';
7
+ import { Box, Text, useInput, useStdout } from 'ink';
8
+ import { panelViewport } from './render/inspector.mjs';
9
+ import { truncateColumns } from './render/text.mjs';
10
+ import { LANGUAGES, t } from './i18n.mjs';
11
+ import { getPalette, inkColor } from './theme.mjs';
12
+ export function LanguagePanel({ current, select, close }) {
13
+ const [cursor, setCursor] = useState(() => {
14
+ const index = LANGUAGES.findIndex(language => language.id === current);
15
+ return index < 0 ? 0 : index;
16
+ });
17
+ const stdout = useStdout().stdout;
18
+ const viewport = panelViewport(stdout?.columns ?? 80, stdout?.rows ?? 30);
19
+ useInput((input, key) => {
20
+ if (key.escape || input === 'q')
21
+ return close();
22
+ if (key.upArrow)
23
+ return setCursor(value => (value + LANGUAGES.length - 1) % LANGUAGES.length);
24
+ if (key.downArrow)
25
+ return setCursor(value => (value + 1) % LANGUAGES.length);
26
+ if (key.return)
27
+ return select(LANGUAGES[cursor].id);
28
+ });
29
+ if (viewport.maxHeight === 0 || viewport.compact) {
30
+ return createElement(Text, { wrap: 'truncate-end' }, truncateColumns('/language · esc/q close', viewport.contentColumns));
31
+ }
32
+ return createElement(Box, { width: viewport.outerColumns, borderStyle: 'round', borderColor: inkColor(getPalette().dim), flexDirection: 'column', paddingX: 1 }, createElement(Text, { color: inkColor(getPalette().brandBright), wrap: 'truncate-end' }, truncateColumns(t('language.title'), viewport.contentColumns)), ...LANGUAGES.map((language, index) => {
33
+ const selected = index === cursor;
34
+ const active = language.id === current;
35
+ return createElement(Text, {
36
+ key: language.id,
37
+ color: selected ? inkColor(getPalette().brandBright) : undefined,
38
+ wrap: 'truncate-end',
39
+ }, truncateColumns(`${selected ? '› ' : ' '}${language.label}${active ? ' ●' : ''}`, viewport.contentColumns));
40
+ }), createElement(Text, { dimColor: true, wrap: 'truncate-end' }, truncateColumns(t('language.hint'), viewport.contentColumns)));
41
+ }