cvc-tui 0.1.0 → 0.4.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.
Files changed (130) hide show
  1. package/dist/app/completion.js +4 -0
  2. package/dist/app/createGatewayEventHandler.js +508 -0
  3. package/dist/app/createSlashHandler.js +101 -0
  4. package/dist/app/delegationStore.js +51 -0
  5. package/dist/app/gatewayContext.js +17 -0
  6. package/dist/app/historyStore.js +4 -0
  7. package/dist/app/inputBuffer.js +4 -0
  8. package/dist/app/inputSelectionStore.js +8 -0
  9. package/dist/app/inputStore.js +4 -0
  10. package/dist/app/interfaces.js +6 -0
  11. package/dist/app/overlayStore.js +40 -0
  12. package/dist/app/promptStore.js +4 -0
  13. package/dist/app/queueStore.js +4 -0
  14. package/dist/app/scroll.js +44 -0
  15. package/dist/app/setupHandoff.js +28 -0
  16. package/dist/app/slash/commands/core.js +421 -234
  17. package/dist/app/slash/commands/debug.js +39 -6
  18. package/dist/app/slash/commands/ops.js +471 -136
  19. package/dist/app/slash/commands/session.js +405 -65
  20. package/dist/app/slash/commands/setup.js +16 -43
  21. package/dist/app/slash/commands/toggles.js +4 -0
  22. package/dist/app/slash/registry.js +7 -68
  23. package/dist/app/slash/types.js +1 -16
  24. package/dist/app/spawnHistoryStore.js +105 -0
  25. package/dist/app/turnController.js +650 -0
  26. package/dist/app/turnStore.js +47 -59
  27. package/dist/app/uiStore.js +34 -29
  28. package/dist/app/useComposerState.js +265 -0
  29. package/dist/app/useConfigSync.js +144 -0
  30. package/dist/app/useInputHandlers.js +403 -0
  31. package/dist/app/useLongRunToolCharms.js +50 -0
  32. package/dist/app/useMainApp.js +629 -0
  33. package/dist/app/useSessionLifecycle.js +175 -0
  34. package/dist/app/useSubmission.js +287 -0
  35. package/dist/app.js +13 -217
  36. package/dist/banner.js +40 -3
  37. package/dist/components/agentsOverlay.js +474 -0
  38. package/dist/components/appChrome.js +252 -0
  39. package/dist/components/appLayout.js +121 -22
  40. package/dist/components/appOverlays.js +65 -0
  41. package/dist/components/branding.js +97 -6
  42. package/dist/components/fpsOverlay.js +22 -0
  43. package/dist/components/helpHint.js +21 -0
  44. package/dist/components/markdown.js +501 -0
  45. package/dist/components/maskedPrompt.js +12 -0
  46. package/dist/components/messageLine.js +82 -0
  47. package/dist/components/modelPicker.js +254 -0
  48. package/dist/components/overlayControls.js +30 -0
  49. package/dist/components/overlays/helpOverlay.js +1 -0
  50. package/dist/components/overlays/historySearch.js +1 -0
  51. package/dist/components/overlays/modelPicker.js +2 -1
  52. package/dist/components/overlays/overlayUtils.js +2 -1
  53. package/dist/components/overlays/secretPrompt.js +1 -0
  54. package/dist/components/overlays/sessionPicker.js +1 -0
  55. package/dist/components/overlays/skillsHub.js +1 -0
  56. package/dist/components/prompts.js +95 -0
  57. package/dist/components/queuedMessages.js +24 -0
  58. package/dist/components/sessionPicker.js +130 -0
  59. package/dist/components/skillsHub.js +165 -0
  60. package/dist/components/streamingAssistant.js +35 -0
  61. package/dist/components/streamingMarkdown.js +110 -186
  62. package/dist/components/textInput.js +748 -218
  63. package/dist/components/themed.js +12 -0
  64. package/dist/components/thinking.js +493 -36
  65. package/dist/components/todoPanel.js +40 -0
  66. package/dist/config/env.js +18 -0
  67. package/dist/config/limits.js +22 -0
  68. package/dist/config/timing.js +4 -0
  69. package/dist/content/charms.js +5 -0
  70. package/dist/content/faces.js +21 -0
  71. package/dist/content/fortunes.js +29 -0
  72. package/dist/content/hotkeys.js +38 -0
  73. package/dist/content/placeholders.js +15 -0
  74. package/dist/content/setup.js +14 -0
  75. package/dist/content/verbs.js +41 -0
  76. package/dist/domain/details.js +53 -0
  77. package/dist/domain/messages.js +63 -0
  78. package/dist/domain/paths.js +16 -0
  79. package/dist/domain/providers.js +11 -0
  80. package/dist/domain/roles.js +6 -0
  81. package/dist/domain/slash.js +11 -0
  82. package/dist/domain/usage.js +1 -0
  83. package/dist/domain/viewport.js +33 -0
  84. package/dist/entry.js +65 -40
  85. package/dist/gatewayClient.js +574 -0
  86. package/dist/gatewayTypes.js +1 -0
  87. package/dist/hooks/useCompletion.js +86 -0
  88. package/dist/hooks/useGitBranch.js +58 -0
  89. package/dist/hooks/useInputHistory.js +12 -0
  90. package/dist/hooks/useQueue.js +57 -0
  91. package/dist/hooks/useVirtualHistory.js +401 -0
  92. package/dist/lib/circularBuffer.js +43 -0
  93. package/dist/lib/clipboard.js +126 -0
  94. package/dist/lib/editor.js +41 -0
  95. package/dist/lib/editor.test.js +58 -0
  96. package/dist/lib/emoji.js +49 -0
  97. package/dist/lib/externalCli.js +11 -0
  98. package/dist/lib/forceTruecolor.js +26 -0
  99. package/dist/lib/fpsStore.js +36 -0
  100. package/dist/lib/gracefulExit.js +29 -0
  101. package/dist/lib/history.js +69 -0
  102. package/dist/lib/inputMetrics.js +143 -0
  103. package/dist/lib/liveProgress.js +51 -0
  104. package/dist/lib/liveProgress.test.js +89 -0
  105. package/dist/lib/mathUnicode.js +685 -0
  106. package/dist/lib/memory.js +123 -0
  107. package/dist/lib/memoryMonitor.js +76 -0
  108. package/dist/lib/messages.js +3 -0
  109. package/dist/lib/messages.test.js +25 -0
  110. package/dist/lib/osc52.js +53 -0
  111. package/dist/lib/perfPane.js +94 -0
  112. package/dist/lib/platform.js +312 -0
  113. package/dist/lib/precisionWheel.js +25 -0
  114. package/dist/lib/reasoning.js +39 -0
  115. package/dist/lib/rpc.js +26 -0
  116. package/dist/lib/subagentTree.js +287 -0
  117. package/dist/lib/syntax.js +89 -0
  118. package/dist/lib/terminalModes.js +46 -0
  119. package/dist/lib/terminalParity.js +48 -0
  120. package/dist/lib/terminalSetup.js +321 -0
  121. package/dist/lib/text.js +203 -0
  122. package/dist/lib/text.test.js +18 -0
  123. package/dist/lib/todo.js +2 -0
  124. package/dist/lib/todo.test.js +22 -0
  125. package/dist/lib/viewportStore.js +82 -0
  126. package/dist/lib/virtualHeights.js +61 -0
  127. package/dist/lib/wheelAccel.js +143 -0
  128. package/dist/theme.js +398 -0
  129. package/dist/types.js +1 -7
  130. package/package.json +2 -1
@@ -1,264 +1,794 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- // CVC power-input composer Hermes ui-tui parity.
3
- //
4
- // Features:
5
- // • Multi-line buffer (Alt+Enter / Shift+Enter newline; Enter submit)
6
- // • Word-wrap to terminal columns
7
- // • Visible inverse-block cursor
8
- // • Dim live char/line counter footer
9
- // • Up/Down history cycle (when on first/last logical line)
10
- // • Ctrl+R reverse-incremental search overlay
11
- // • Tab completion: slash commands + file paths
12
- // • Ctrl+L clears
13
- // • Queued submission indicator `[queued: N]`
14
- // • Hidden when promptStore has an active confirm/secret prompt
15
- //
16
- // Pure buffer math lives in ../app/inputBuffer.ts; this file is the React
17
- // glue + key dispatch.
18
- import { useCallback, useEffect, useState } from 'react';
19
- import { Box, Text, useInput } from 'ink';
20
- import { useStore } from '@nanostores/react';
21
- import { $buffer, $completion, resetCompletion, setBuffer, setBufferRaw, } from '../app/inputStore.js';
22
- import { emptyBuffer, insert, backspace, del as bufDel, moveLeft, moveRight, moveHome, moveEnd, moveUp, moveDown, onFirstLine, onLastLine, bufferStats, cursorRowCol, } from '../app/inputBuffer.js';
23
- import { $history, loadHistory, pushHistory, historyPrev, historyNext, resetHistoryBrowse, } from '../app/historyStore.js';
24
- import { $queue } from '../app/queueStore.js';
25
- import { $prompt } from '../app/promptStore.js';
26
- import { $ui } from '../app/uiStore.js';
27
- import { tokenAtCursor, candidatesFor, applyCandidate, } from '../app/completion.js';
28
- import { HistorySearch } from './overlays/historySearch.js';
29
- import { ConfirmPrompt } from './overlays/confirmPrompt.js';
30
- import { SecretPrompt } from './overlays/secretPrompt.js';
31
- const PROMPT_COLOR = '#e63946';
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import * as Ink from '@cvc/ink';
3
+ import { useEffect, useMemo, useRef, useState } from 'react';
4
+ import { setInputSelection } from '../app/inputSelectionStore.js';
5
+ import { readClipboardText, writeClipboardText } from '../lib/clipboard.js';
6
+ import { cursorLayout, offsetFromPosition } from '../lib/inputMetrics.js';
7
+ import { DEFAULT_VOICE_RECORD_KEY, isActionMod, isMac, isMacActionFallback, isVoiceToggleKey } from '../lib/platform.js';
8
+ const ink = Ink;
9
+ const { Box, Text, useStdin, useInput, useStdout, stringWidth, useDeclaredCursor, useTerminalFocus } = ink;
10
+ const ESC = '\x1b';
11
+ const INV = `${ESC}[7m`;
12
+ const INV_OFF = `${ESC}[27m`;
13
+ const DIM = `${ESC}[2m`;
14
+ const DIM_OFF = `${ESC}[22m`;
15
+ const FWD_DEL_RE = new RegExp(`${ESC}\\[3(?:[~$^]|;)`);
16
+ const PRINTABLE = /^[ -~\u00a0-\uffff]+$/;
17
+ const BRACKET_PASTE = new RegExp(`${ESC}?\\[20[01]~`, 'g');
18
+ const MULTI_CLICK_MS = 500;
19
+ const invert = (s) => INV + s + INV_OFF;
20
+ const dim = (s) => DIM + s + DIM_OFF;
21
+ let _seg = null;
22
+ const seg = () => (_seg ??= new Intl.Segmenter(undefined, { granularity: 'grapheme' }));
23
+ const STOP_CACHE_MAX = 32;
24
+ const stopCache = new Map();
25
+ function graphemeStops(s) {
26
+ const hit = stopCache.get(s);
27
+ if (hit) {
28
+ return hit;
29
+ }
30
+ const stops = [0];
31
+ for (const { index } of seg().segment(s)) {
32
+ if (index > 0) {
33
+ stops.push(index);
34
+ }
35
+ }
36
+ if (stops.at(-1) !== s.length) {
37
+ stops.push(s.length);
38
+ }
39
+ stopCache.set(s, stops);
40
+ if (stopCache.size > STOP_CACHE_MAX) {
41
+ const oldest = stopCache.keys().next().value;
42
+ if (oldest !== undefined) {
43
+ stopCache.delete(oldest);
44
+ }
45
+ }
46
+ return stops;
47
+ }
48
+ function snapPos(s, p) {
49
+ const pos = Math.max(0, Math.min(p, s.length));
50
+ let last = 0;
51
+ for (const stop of graphemeStops(s)) {
52
+ if (stop > pos) {
53
+ break;
54
+ }
55
+ last = stop;
56
+ }
57
+ return last;
58
+ }
59
+ function prevPos(s, p) {
60
+ const pos = snapPos(s, p);
61
+ let prev = 0;
62
+ for (const stop of graphemeStops(s)) {
63
+ if (stop >= pos) {
64
+ return prev;
65
+ }
66
+ prev = stop;
67
+ }
68
+ return prev;
69
+ }
70
+ function nextPos(s, p) {
71
+ const pos = snapPos(s, p);
72
+ for (const stop of graphemeStops(s)) {
73
+ if (stop > pos) {
74
+ return stop;
75
+ }
76
+ }
77
+ return s.length;
78
+ }
79
+ function wordLeft(s, p) {
80
+ let i = snapPos(s, p) - 1;
81
+ while (i > 0 && /\s/.test(s[i])) {
82
+ i--;
83
+ }
84
+ while (i > 0 && !/\s/.test(s[i - 1])) {
85
+ i--;
86
+ }
87
+ return Math.max(0, i);
88
+ }
89
+ function wordRight(s, p) {
90
+ let i = snapPos(s, p);
91
+ while (i < s.length && !/\s/.test(s[i])) {
92
+ i++;
93
+ }
94
+ while (i < s.length && /\s/.test(s[i])) {
95
+ i++;
96
+ }
97
+ return i;
98
+ }
32
99
  /**
33
- * Render the buffer with an inverse-block cursor at `cursor`. Wraps to `cols`
34
- * columns. Returns an array of visual lines (already containing ANSI invert
35
- * codes for the cursor cell).
100
+ * Move cursor one logical line up or down inside `s` while preserving the
101
+ * column offset from the current line's start. Returns `null` when the cursor
102
+ * is already on the first line (up) or last line (down) — callers use that
103
+ * signal to fall through to history cycling instead of eating the arrow key.
36
104
  */
37
- function renderWrapped(buf, _cols) {
38
- const text = buf.text;
39
- const cursor = Math.max(0, Math.min(buf.cursor, text.length));
40
- // Insert cursor sentinel as inverted character.
41
- const ESC = '\x1b';
42
- const INV = `${ESC}[7m`;
43
- const OFF = `${ESC}[27m`;
44
- const ch = cursor < text.length && text[cursor] !== '\n' ? text[cursor] : ' ';
45
- const withCursor = text.slice(0, cursor) + INV + ch + OFF + text.slice(cursor + (cursor < text.length && text[cursor] !== '\n' ? 1 : 0));
46
- // Wrap each logical line. We can't naively wrapText because of escape codes;
47
- // however the codes are zero-width so length-based wrap with the *raw* text
48
- // still produces visually correct output if we map back. For simplicity and
49
- // since terminal widths in our tests are generous, we wrap on raw text with
50
- // injected codes treated as part of the line — the codes themselves don't
51
- // consume cells, but wrapText sees them as chars. To stay correct, wrap the
52
- // raw line and re-inject the cursor cell.
53
- // Simpler: split on '\n' from withCursor and let Ink soft-wrap visually.
54
- return withCursor.split('\n');
105
+ export function lineNav(s, p, dir) {
106
+ const pos = snapPos(s, p);
107
+ const curStart = s.lastIndexOf('\n', pos - 1) + 1;
108
+ const col = pos - curStart;
109
+ if (dir < 0) {
110
+ if (curStart === 0) {
111
+ return null;
112
+ }
113
+ const prevStart = s.lastIndexOf('\n', curStart - 2) + 1;
114
+ return snapPos(s, Math.min(prevStart + col, curStart - 1));
115
+ }
116
+ const nextBreak = s.indexOf('\n', pos);
117
+ if (nextBreak < 0) {
118
+ return null;
119
+ }
120
+ const nextEnd = s.indexOf('\n', nextBreak + 1);
121
+ const lineEnd = nextEnd < 0 ? s.length : nextEnd;
122
+ return snapPos(s, Math.min(nextBreak + 1 + col, lineEnd));
55
123
  }
56
- /** Slim hint footer: `chars · lines · [queued: N]`. */
57
- const Hints = ({ chars, lineCount, queued, mode, }) => {
58
- const parts = [`${chars} chars`, `${lineCount} line${lineCount === 1 ? '' : 's'}`];
59
- if (queued > 0)
60
- parts.push(`queued: ${queued}`);
61
- if (mode)
62
- parts.push(mode);
63
- return (_jsx(Box, { children: _jsx(Text, { dimColor: true, children: parts.join(' · ') }) }));
64
- };
65
- export const Composer = ({ onSubmit, placeholder, prompt = '❯' }) => {
66
- const buf = useStore($buffer);
67
- const completion = useStore($completion);
68
- const ui = useStore($ui);
69
- const queue = useStore($queue);
70
- const prompts = useStore($prompt);
71
- const history = useStore($history);
72
- const [searchOpen, setSearchOpen] = useState(false);
73
- // One-time history load.
74
- useEffect(() => {
75
- if (history.length === 0)
76
- loadHistory();
77
- }, [history.length]);
78
- // If a prompt is active, show the appropriate overlay instead of the composer.
79
- // (Composer hides — Hermes parity.)
80
- if (prompts) {
81
- if (prompts.kind === 'confirm')
82
- return _jsx(ConfirmPrompt, {});
83
- if (prompts.kind === 'secret')
84
- return _jsx(SecretPrompt, {});
124
+ export { offsetFromPosition };
125
+ function renderWithCursor(value, cursor) {
126
+ const pos = Math.max(0, Math.min(cursor, value.length));
127
+ let out = '', done = false;
128
+ for (const { segment, index } of seg().segment(value)) {
129
+ if (!done && index >= pos) {
130
+ out += invert(index === pos && segment !== '\n' ? segment : ' ');
131
+ done = true;
132
+ if (index === pos && segment !== '\n') {
133
+ continue;
134
+ }
135
+ }
136
+ out += segment;
85
137
  }
86
- if (searchOpen) {
87
- return (_jsx(HistorySearch, { onAccept: (entry) => {
88
- setBuffer({ text: entry, cursor: entry.length });
89
- setSearchOpen(false);
90
- }, onCancel: () => setSearchOpen(false) }));
138
+ return done ? out : out + invert(' ');
139
+ }
140
+ function renderWithSelection(value, start, end) {
141
+ if (start >= end) {
142
+ return value;
91
143
  }
92
- const cols = Math.max(20, ui.cols ?? 80);
93
- const submit = useCallback(() => {
94
- const text = buf.text;
95
- if (!text.trim()) {
96
- // Empty submit: just clear.
97
- setBuffer(emptyBuffer());
144
+ return value.slice(0, start) + invert(value.slice(start, end) || ' ') + value.slice(end);
145
+ }
146
+ function useFwdDelete(active) {
147
+ const ref = useRef(false);
148
+ const { inputEmitter: ee } = useStdin();
149
+ useEffect(() => {
150
+ if (!active) {
98
151
  return;
99
152
  }
100
- pushHistory(text);
101
- resetHistoryBrowse();
102
- setBuffer(emptyBuffer());
103
- if (onSubmit)
104
- onSubmit(text);
105
- }, [buf.text, onSubmit]);
106
- const cycleCompletion = useCallback((dir) => {
107
- // Tab pressed: either start a new cycle or rotate the existing one.
108
- if (completion.active && completion.candidates.length > 0) {
109
- const n = completion.candidates.length;
110
- const nextIdx = (completion.index + dir + n) % n;
111
- const cand = completion.candidates[nextIdx];
112
- const replaced = applyCandidate(buf.text, completion.start, completion.prefix.length, cand);
113
- // Use raw setter so we don't reset the cycle.
114
- setBufferRaw({ text: replaced.text, cursor: replaced.cursor });
115
- $completion.set({
116
- ...completion,
117
- index: nextIdx,
118
- prefix: cand,
119
- });
153
+ const h = (d) => {
154
+ ref.current = FWD_DEL_RE.test(d);
155
+ };
156
+ ee.prependListener('input', h);
157
+ return () => {
158
+ ee.removeListener('input', h);
159
+ };
160
+ }, [active, ee]);
161
+ return ref;
162
+ }
163
+ const isPasteResultPromise = (value) => !!value && typeof value.then === 'function';
164
+ export function TextInput({ columns = 80, value, onChange, onPaste, onSubmit, mask, mouseApiRef, voiceRecordKey = DEFAULT_VOICE_RECORD_KEY, placeholder = '', focus = true }) {
165
+ const [cur, setCur] = useState(value.length);
166
+ const [sel, setSel] = useState(null);
167
+ const fwdDel = useFwdDelete(focus);
168
+ const termFocus = useTerminalFocus();
169
+ const { stdout } = useStdout();
170
+ const curRef = useRef(cur);
171
+ const selRef = useRef(null);
172
+ const vRef = useRef(value);
173
+ const self = useRef(false);
174
+ const pasteBuf = useRef('');
175
+ const pasteEnd = useRef(null);
176
+ const pasteTimer = useRef(null);
177
+ const pastePos = useRef(0);
178
+ const editVersionRef = useRef(0);
179
+ const parentChangeTimer = useRef(null);
180
+ const pendingParentValue = useRef(null);
181
+ const localRenderTimer = useRef(null);
182
+ const lineWidthRef = useRef(stringWidth(value.includes('\n') ? value.slice(value.lastIndexOf('\n') + 1) : value));
183
+ const mouseAnchorRef = useRef(null);
184
+ const lastClickRef = useRef({ at: 0, offset: -1 });
185
+ const undo = useRef([]);
186
+ const redo = useRef([]);
187
+ const cbChange = useRef(onChange);
188
+ const cbSubmit = useRef(onSubmit);
189
+ const cbPaste = useRef(onPaste);
190
+ cbChange.current = onChange;
191
+ cbSubmit.current = onSubmit;
192
+ cbPaste.current = onPaste;
193
+ const raw = self.current ? vRef.current : value;
194
+ const display = mask ? raw.replace(/[^\n]/g, mask[0] ?? '*') : raw;
195
+ const selected = useMemo(() => sel && sel.start !== sel.end ? { end: Math.max(sel.start, sel.end), start: Math.min(sel.start, sel.end) } : null, [sel]);
196
+ const layout = useMemo(() => cursorLayout(display, cur, columns), [columns, cur, display]);
197
+ const boxRef = useDeclaredCursor({
198
+ line: layout.line,
199
+ column: layout.column,
200
+ active: focus && termFocus && !selected
201
+ });
202
+ // Hide the hardware cursor while a selection is active (prevents
203
+ // auto-wrap onto the next row when inverted text fills the column
204
+ // exactly) or when the terminal loses focus (suppresses the hollow-rect
205
+ // ghost most terminals draw at the parked position).
206
+ const hideHardwareCursor = focus && !!stdout?.isTTY && (!!selected || !termFocus);
207
+ useEffect(() => {
208
+ if (!hideHardwareCursor || !stdout) {
120
209
  return;
121
210
  }
122
- const ctx = tokenAtCursor(buf.text, buf.cursor);
123
- if (!ctx)
124
- return;
125
- const cands = candidatesFor(ctx);
126
- if (cands.length === 0)
211
+ stdout.write('\x1b[?25l');
212
+ return () => {
213
+ stdout.write('\x1b[?25h');
214
+ };
215
+ }, [hideHardwareCursor, stdout]);
216
+ const nativeCursor = focus && termFocus && !selected && !!stdout?.isTTY;
217
+ // Placeholder text is just a hint, not a selection — render it dim
218
+ // without inverse styling. In a TTY the hardware cursor parks at column
219
+ // 0 and visually marks the input start. Non-TTY surfaces still need the
220
+ // synthetic inverse first-char to draw a cursor at all.
221
+ const rendered = useMemo(() => {
222
+ if (!focus) {
223
+ return display || dim(placeholder);
224
+ }
225
+ if (!display && placeholder) {
226
+ return nativeCursor ? dim(placeholder) : invert(placeholder[0] ?? ' ') + dim(placeholder.slice(1));
227
+ }
228
+ if (selected) {
229
+ return renderWithSelection(display, selected.start, selected.end);
230
+ }
231
+ return nativeCursor ? display || ' ' : renderWithCursor(display, cur);
232
+ }, [cur, display, focus, nativeCursor, placeholder, selected]);
233
+ useEffect(() => {
234
+ if (self.current) {
235
+ self.current = false;
236
+ }
237
+ else {
238
+ setCur(value.length);
239
+ setSel(null);
240
+ curRef.current = value.length;
241
+ selRef.current = null;
242
+ vRef.current = value;
243
+ lineWidthRef.current = stringWidth(value.includes('\n') ? value.slice(value.lastIndexOf('\n') + 1) : value);
244
+ undo.current = [];
245
+ redo.current = [];
246
+ }
247
+ }, [value]);
248
+ useEffect(() => {
249
+ if (!focus) {
127
250
  return;
128
- const first = cands[0];
129
- const replaced = applyCandidate(buf.text, ctx.start, ctx.token.length, first);
130
- setBufferRaw({ text: replaced.text, cursor: replaced.cursor });
131
- $completion.set({
132
- active: true,
133
- prefix: first,
134
- candidates: cands,
135
- index: 0,
136
- start: ctx.start,
251
+ }
252
+ const dropSel = () => {
253
+ if (!selRef.current) {
254
+ return;
255
+ }
256
+ selRef.current = null;
257
+ setSel(null);
258
+ };
259
+ setInputSelection({
260
+ clear: dropSel,
261
+ collapseToEnd: () => {
262
+ dropSel();
263
+ setCur(vRef.current.length);
264
+ curRef.current = vRef.current.length;
265
+ },
266
+ end: selected?.end ?? curRef.current,
267
+ start: selected?.start ?? curRef.current,
268
+ value: vRef.current
137
269
  });
138
- }, [buf.text, buf.cursor, completion]);
139
- useInput((input, key) => {
140
- // Ctrl+L: clear buffer.
141
- if (key.ctrl && input === 'l') {
142
- setBuffer(emptyBuffer());
270
+ return () => setInputSelection(null);
271
+ }, [cur, focus, selected]);
272
+ useEffect(() => () => {
273
+ if (pasteTimer.current) {
274
+ clearTimeout(pasteTimer.current);
275
+ }
276
+ if (parentChangeTimer.current) {
277
+ clearTimeout(parentChangeTimer.current);
278
+ }
279
+ if (localRenderTimer.current) {
280
+ clearTimeout(localRenderTimer.current);
281
+ }
282
+ }, []);
283
+ const flushParentChange = () => {
284
+ if (parentChangeTimer.current) {
285
+ clearTimeout(parentChangeTimer.current);
286
+ parentChangeTimer.current = null;
287
+ }
288
+ const next = pendingParentValue.current;
289
+ pendingParentValue.current = null;
290
+ if (next !== null) {
291
+ self.current = true;
292
+ cbChange.current(next);
293
+ }
294
+ };
295
+ const scheduleParentChange = (next) => {
296
+ pendingParentValue.current = next;
297
+ if (parentChangeTimer.current) {
143
298
  return;
144
299
  }
145
- // Ctrl+R: open reverse-search overlay.
146
- if (key.ctrl && input === 'r') {
147
- setSearchOpen(true);
300
+ parentChangeTimer.current = setTimeout(flushParentChange, 16);
301
+ };
302
+ const cancelLocalRender = () => {
303
+ if (localRenderTimer.current) {
304
+ clearTimeout(localRenderTimer.current);
305
+ localRenderTimer.current = null;
306
+ }
307
+ };
308
+ const scheduleLocalRender = () => {
309
+ if (localRenderTimer.current) {
148
310
  return;
149
311
  }
150
- // Ctrl+C: clear current input (don't crash; parent handles outer SIGINT).
151
- if (key.ctrl && input === 'c') {
152
- if (buf.text.length === 0) {
153
- process.exit(0);
312
+ localRenderTimer.current = setTimeout(() => {
313
+ localRenderTimer.current = null;
314
+ setCur(curRef.current);
315
+ }, 16);
316
+ };
317
+ const canFastEchoBase = () => focus && termFocus && !selected && !mask && !!stdout?.isTTY;
318
+ const canFastAppend = (current, cursor, text) => {
319
+ const sw = stringWidth(text);
320
+ return (canFastEchoBase() &&
321
+ cursor === current.length &&
322
+ current.length > 0 &&
323
+ !current.includes('\n') &&
324
+ sw === text.length &&
325
+ lineWidthRef.current + sw < Math.max(1, columns));
326
+ };
327
+ const canFastBackspace = (current, cursor) => {
328
+ if (!canFastEchoBase() || cursor !== current.length || cursor <= 0 || current.includes('\n')) {
329
+ return false;
330
+ }
331
+ return stringWidth(current.slice(prevPos(current, cursor), cursor)) === 1;
332
+ };
333
+ const commit = (next, nextCur, track = true, syncParent = true, syncLocal = true, nextLineWidth) => {
334
+ const prev = vRef.current;
335
+ const c = snapPos(next, nextCur);
336
+ editVersionRef.current += 1;
337
+ if (selRef.current) {
338
+ selRef.current = null;
339
+ setSel(null);
340
+ }
341
+ if (track && next !== prev) {
342
+ undo.current.push({ cursor: curRef.current, value: prev });
343
+ if (undo.current.length > 200) {
344
+ undo.current.shift();
345
+ }
346
+ redo.current = [];
347
+ }
348
+ if (syncLocal) {
349
+ cancelLocalRender();
350
+ setCur(c);
351
+ }
352
+ else {
353
+ scheduleLocalRender();
354
+ }
355
+ curRef.current = c;
356
+ vRef.current = next;
357
+ lineWidthRef.current =
358
+ nextLineWidth ?? stringWidth(next.includes('\n') ? next.slice(next.lastIndexOf('\n') + 1) : next);
359
+ if (next !== prev) {
360
+ if (syncParent) {
361
+ flushParentChange();
362
+ self.current = true;
363
+ cbChange.current(next);
364
+ }
365
+ else {
366
+ self.current = true;
367
+ scheduleParentChange(next);
154
368
  }
155
- setBuffer(emptyBuffer());
369
+ }
370
+ };
371
+ const swap = (from, to) => {
372
+ const entry = from.current.pop();
373
+ if (!entry) {
374
+ return;
375
+ }
376
+ to.current.push({ cursor: curRef.current, value: vRef.current });
377
+ commit(entry.value, entry.cursor, false);
378
+ };
379
+ const emitPaste = (e) => {
380
+ const startVersion = editVersionRef.current;
381
+ const h = cbPaste.current?.(e);
382
+ if (isPasteResultPromise(h)) {
383
+ const fallbackText = e.text;
384
+ void h
385
+ .then(result => {
386
+ if (result && editVersionRef.current === startVersion) {
387
+ commit(result.value, result.cursor);
388
+ }
389
+ else if (result && fallbackText && PRINTABLE.test(fallbackText)) {
390
+ // User typed while async paste was in-flight — fall back to raw text insert
391
+ // so the pasted content is not silently lost.
392
+ const cur = curRef.current;
393
+ const v = vRef.current;
394
+ commit(v.slice(0, cur) + fallbackText + v.slice(cur), cur + fallbackText.length);
395
+ }
396
+ })
397
+ .catch(() => { });
398
+ return true;
399
+ }
400
+ if (h) {
401
+ commit(h.value, h.cursor);
402
+ }
403
+ return !!h;
404
+ };
405
+ const flushPaste = () => {
406
+ const text = pasteBuf.current;
407
+ const at = pastePos.current;
408
+ const end = pasteEnd.current ?? at;
409
+ pasteBuf.current = '';
410
+ pasteEnd.current = null;
411
+ pasteTimer.current = null;
412
+ if (!text) {
413
+ return;
414
+ }
415
+ if (!emitPaste({ cursor: at, text, value: vRef.current }) && PRINTABLE.test(text)) {
416
+ commit(vRef.current.slice(0, at) + text + vRef.current.slice(end), at + text.length);
417
+ }
418
+ };
419
+ const clearSel = () => {
420
+ if (!selRef.current) {
156
421
  return;
157
422
  }
158
- // Tab / Shift+Tab.
159
- if (key.tab) {
160
- cycleCompletion(key.shift ? -1 : 1);
423
+ selRef.current = null;
424
+ setSel(null);
425
+ };
426
+ const selectAll = () => {
427
+ const end = vRef.current.length;
428
+ if (!end) {
161
429
  return;
162
430
  }
163
- // Enter / newline. Ink reports Alt+Enter via key.meta, Shift+Enter via key.shift.
164
- if (key.return) {
165
- if (key.meta || key.shift) {
166
- setBuffer(insert(buf, '\n'));
431
+ const next = { end, start: 0 };
432
+ selRef.current = next;
433
+ setSel(next);
434
+ setCur(end);
435
+ curRef.current = end;
436
+ };
437
+ const moveCursor = (next, extend = false) => {
438
+ const c = snapPos(vRef.current, next);
439
+ const anchor = selRef.current?.start ?? curRef.current;
440
+ if (!extend || anchor === c) {
441
+ clearSel();
442
+ }
443
+ else {
444
+ const nextSel = { end: c, start: anchor };
445
+ selRef.current = nextSel;
446
+ setSel(nextSel);
447
+ }
448
+ setCur(c);
449
+ curRef.current = c;
450
+ };
451
+ const selRange = () => {
452
+ const range = selRef.current;
453
+ return range && range.start !== range.end
454
+ ? { end: Math.max(range.start, range.end), start: Math.min(range.start, range.end) }
455
+ : null;
456
+ };
457
+ const ins = (v, c, s) => v.slice(0, c) + s + v.slice(c);
458
+ const pastePlainText = (text) => {
459
+ const cleaned = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
460
+ if (!cleaned) {
461
+ return;
462
+ }
463
+ const range = selRange();
464
+ const nextValue = range
465
+ ? vRef.current.slice(0, range.start) + cleaned + vRef.current.slice(range.end)
466
+ : vRef.current.slice(0, curRef.current) + cleaned + vRef.current.slice(curRef.current);
467
+ const nextCursor = range ? range.start + cleaned.length : curRef.current + cleaned.length;
468
+ commit(nextValue, nextCursor);
469
+ };
470
+ const startMouseSelection = (next) => {
471
+ const c = snapPos(vRef.current, next);
472
+ mouseAnchorRef.current = c;
473
+ selRef.current = { end: c, start: c };
474
+ setSel(null);
475
+ setCur(c);
476
+ curRef.current = c;
477
+ };
478
+ const dragMouseSelection = (next) => {
479
+ if (mouseAnchorRef.current === null) {
480
+ return;
481
+ }
482
+ const c = snapPos(vRef.current, next);
483
+ const range = { end: c, start: mouseAnchorRef.current };
484
+ selRef.current = range;
485
+ setSel(range.start === range.end ? null : range);
486
+ setCur(c);
487
+ curRef.current = c;
488
+ };
489
+ const endMouseSelection = () => {
490
+ mouseAnchorRef.current = null;
491
+ const range = selRef.current;
492
+ if (range && range.start === range.end) {
493
+ selRef.current = null;
494
+ setSel(null);
495
+ return;
496
+ }
497
+ const normalized = selRange();
498
+ if (isMac && normalized) {
499
+ void writeClipboardText(vRef.current.slice(normalized.start, normalized.end));
500
+ }
501
+ };
502
+ const offsetAt = (e) => offsetFromPosition(display, e.localRow ?? 0, e.localCol ?? 0, columns);
503
+ const isMultiClickAt = (offset) => {
504
+ const now = Date.now();
505
+ const last = lastClickRef.current;
506
+ lastClickRef.current = { at: now, offset };
507
+ return now - last.at < MULTI_CLICK_MS && offset === last.offset;
508
+ };
509
+ if (mouseApiRef) {
510
+ mouseApiRef.current = {
511
+ dragAt: (row, col) => dragMouseSelection(offsetFromPosition(display, row, col, columns)),
512
+ end: endMouseSelection,
513
+ startAtBeginning: () => startMouseSelection(0)
514
+ };
515
+ }
516
+ useInput((inp, k, event) => {
517
+ const eventRaw = event.keypress.raw;
518
+ // Configured voice shortcut wins over composer-level defaults like
519
+ // paste/copy so users who bind voice to ctrl+v / alt+v / cmd+v
520
+ // actually get voice toggled instead of a paste (Copilot round-7
521
+ // follow-up on #19835). The pass-through predicate is a no-op for
522
+ // ordinary typing and plain paste when voice is unbound to 'v'.
523
+ if (shouldPassThroughToGlobalHandler(inp, k, voiceRecordKey)) {
524
+ return;
525
+ }
526
+ if (eventRaw === '\x1bv' ||
527
+ eventRaw === '\x1bV' ||
528
+ eventRaw === '\x16' ||
529
+ (isMac && isActionMod(k) && inp.toLowerCase() === 'v')) {
530
+ if (cbPaste.current) {
531
+ return void emitPaste({ cursor: curRef.current, hotkey: true, text: '', value: vRef.current });
532
+ }
533
+ if (isMac) {
534
+ void readClipboardText().then(text => {
535
+ if (text) {
536
+ pastePlainText(text);
537
+ }
538
+ });
539
+ }
540
+ return;
541
+ }
542
+ if (isMac && isActionMod(k) && inp.toLowerCase() === 'c') {
543
+ const range = selRange();
544
+ if (range) {
545
+ const text = vRef.current.slice(range.start, range.end);
546
+ void writeClipboardText(text);
547
+ }
548
+ return;
549
+ }
550
+ if (k.upArrow || k.downArrow) {
551
+ const next = lineNav(vRef.current, curRef.current, k.upArrow ? -1 : 1);
552
+ if (next !== null) {
553
+ moveCursor(next, k.shift);
167
554
  return;
168
555
  }
169
- submit();
170
556
  return;
171
557
  }
172
- if (key.backspace || key.delete) {
173
- // Ink convention: `key.delete` is forward-delete on some terms; `key.backspace` for backspace.
174
- if (key.delete && !key.backspace) {
175
- setBuffer(bufDel(buf));
558
+ if (k.return) {
559
+ if (k.shift || k.ctrl || (isMac ? isActionMod(k) : k.meta)) {
560
+ flushParentChange();
561
+ commit(ins(vRef.current, curRef.current, '\n'), curRef.current + 1);
176
562
  }
177
563
  else {
178
- setBuffer(backspace(buf));
564
+ flushParentChange();
565
+ cbSubmit.current?.(vRef.current);
179
566
  }
180
567
  return;
181
568
  }
182
- if (key.leftArrow) {
183
- setBuffer(moveLeft(buf));
569
+ let c = curRef.current;
570
+ let v = vRef.current;
571
+ const mod = isActionMod(k);
572
+ const wordMod = mod || k.meta;
573
+ const actionHome = k.home || (!isMac && mod && inp === 'a') || isMacActionFallback(k, inp, 'a');
574
+ const actionEnd = k.end || (mod && inp === 'e') || isMacActionFallback(k, inp, 'e');
575
+ const actionDeleteToStart = (mod && inp === 'u') || isMacActionFallback(k, inp, 'u');
576
+ const actionKillToEnd = (mod && inp === 'k') || isMacActionFallback(k, inp, 'k');
577
+ const actionDeleteWord = (mod && inp === 'w') || isMacActionFallback(k, inp, 'w');
578
+ const range = selRange();
579
+ const delFwd = k.delete || fwdDel.current;
580
+ if (mod && inp === 'z') {
581
+ return swap(undo, redo);
582
+ }
583
+ if ((mod && inp === 'y') || (mod && k.shift && inp === 'z')) {
584
+ return swap(redo, undo);
585
+ }
586
+ if (isMac && mod && inp === 'a') {
587
+ return selectAll();
588
+ }
589
+ if (actionHome) {
590
+ c = 0;
591
+ moveCursor(c, k.shift);
184
592
  return;
185
593
  }
186
- if (key.rightArrow) {
187
- setBuffer(moveRight(buf));
594
+ else if (actionEnd) {
595
+ c = v.length;
596
+ moveCursor(c, k.shift);
188
597
  return;
189
598
  }
190
- if (key.upArrow) {
191
- // History cycle when on the first logical line.
192
- if (onFirstLine(buf)) {
193
- const entry = historyPrev(buf.text);
194
- if (entry !== null) {
195
- setBuffer({ text: entry, cursor: entry.length });
196
- }
197
- return;
599
+ else if (k.leftArrow) {
600
+ if (range && !wordMod && !k.shift) {
601
+ clearSel();
602
+ c = range.start;
603
+ }
604
+ else {
605
+ c = wordMod ? wordLeft(v, c) : prevPos(v, c);
198
606
  }
199
- setBuffer(moveUp(buf));
607
+ moveCursor(c, k.shift);
200
608
  return;
201
609
  }
202
- if (key.downArrow) {
203
- if (onLastLine(buf)) {
204
- const entry = historyNext();
205
- if (entry !== null) {
206
- setBuffer({ text: entry, cursor: entry.length });
207
- }
208
- return;
610
+ else if (k.rightArrow) {
611
+ if (range && !wordMod && !k.shift) {
612
+ clearSel();
613
+ c = range.end;
614
+ }
615
+ else {
616
+ c = wordMod ? wordRight(v, c) : nextPos(v, c);
209
617
  }
210
- setBuffer(moveDown(buf));
618
+ moveCursor(c, k.shift);
211
619
  return;
212
620
  }
213
- // Home / End Ink doesn't expose these directly; they often arrive as raw escapes.
214
- if (key.ctrl && input === 'a') {
215
- setBuffer(moveHome(buf));
216
- return;
621
+ else if (wordMod && inp === 'b') {
622
+ clearSel();
623
+ c = wordLeft(v, c);
217
624
  }
218
- if (key.ctrl && input === 'e') {
219
- setBuffer(moveEnd(buf));
220
- return;
625
+ else if (wordMod && inp === 'f') {
626
+ clearSel();
627
+ c = wordRight(v, c);
628
+ }
629
+ else if (range && (k.backspace || delFwd)) {
630
+ v = v.slice(0, range.start) + v.slice(range.end);
631
+ c = range.start;
221
632
  }
222
- if (key.escape) {
223
- // Esc: cancel completion, otherwise clear buffer.
224
- if (completion.active) {
225
- resetCompletion();
633
+ else if (k.backspace && c > 0) {
634
+ if (wordMod) {
635
+ const t = wordLeft(v, c);
636
+ v = v.slice(0, t) + v.slice(c);
637
+ c = t;
638
+ }
639
+ else if (canFastBackspace(v, c)) {
640
+ const t = prevPos(v, c);
641
+ v = v.slice(0, t) + v.slice(c);
642
+ c = t;
643
+ stdout.write('\b \b');
644
+ commit(v, c, true, false, false, Math.max(0, lineWidthRef.current - 1));
226
645
  return;
227
646
  }
228
- setBuffer(emptyBuffer());
229
- return;
647
+ else {
648
+ const t = prevPos(v, c);
649
+ v = v.slice(0, t) + v.slice(c);
650
+ c = t;
651
+ }
230
652
  }
231
- // Printable input.
232
- if (input && !key.ctrl && !key.meta) {
233
- setBuffer(insert(buf, input));
234
- return;
653
+ else if (delFwd && c < v.length) {
654
+ if (wordMod) {
655
+ const t = wordRight(v, c);
656
+ v = v.slice(0, c) + v.slice(t);
657
+ }
658
+ else {
659
+ v = v.slice(0, c) + v.slice(nextPos(v, c));
660
+ }
235
661
  }
236
- });
237
- const stats = bufferStats(buf.text);
238
- const wrapped = renderWrapped(buf, cols);
239
- const showPlaceholder = buf.text.length === 0 && placeholder;
240
- return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsxs(Text, { color: PROMPT_COLOR, children: [prompt, " "] }), showPlaceholder ? (_jsx(Text, { dimColor: true, children: placeholder })) : (_jsx(Box, { flexDirection: "column", children: wrapped.map((line, i) => (_jsx(Text, { children: line || ' ' }, i))) }))] }), _jsx(Hints, { chars: stats.chars, lineCount: stats.lineCount, queued: queue.length, mode: completion.active ? `tab ${completion.index + 1}/${completion.candidates.length}` : undefined })] }));
241
- };
242
- export const TextInput = ({ value, onChange, onSubmit, placeholder, prompt }) => {
243
- const buf = useStore($buffer);
244
- // Sync controlled value into the store on mount / external change.
245
- useEffect(() => {
246
- if (buf.text !== value) {
247
- setBuffer({ text: value, cursor: value.length });
662
+ else if (actionDeleteWord) {
663
+ if (range) {
664
+ v = v.slice(0, range.start) + v.slice(range.end);
665
+ c = range.start;
666
+ }
667
+ else if (c > 0) {
668
+ clearSel();
669
+ const t = wordLeft(v, c);
670
+ v = v.slice(0, t) + v.slice(c);
671
+ c = t;
672
+ }
673
+ else {
674
+ return;
675
+ }
248
676
  }
249
- // eslint-disable-next-line react-hooks/exhaustive-deps
250
- }, [value]);
251
- // Push buffer changes back to parent on every text change.
252
- useEffect(() => {
253
- if (buf.text !== value)
254
- onChange(buf.text);
255
- // eslint-disable-next-line react-hooks/exhaustive-deps
256
- }, [buf.text]);
257
- return (_jsx(Composer, { placeholder: placeholder, prompt: prompt, onSubmit: (t) => {
258
- onChange('');
259
- if (onSubmit)
260
- onSubmit(t);
261
- } }));
262
- };
263
- // Re-exports for tests / consumers.
264
- export { cursorRowCol };
677
+ else if (actionDeleteToStart) {
678
+ if (range) {
679
+ v = v.slice(0, range.start) + v.slice(range.end);
680
+ c = range.start;
681
+ }
682
+ else {
683
+ v = v.slice(c);
684
+ c = 0;
685
+ }
686
+ }
687
+ else if (actionKillToEnd) {
688
+ if (range) {
689
+ v = v.slice(0, range.start) + v.slice(range.end);
690
+ c = range.start;
691
+ }
692
+ else {
693
+ v = v.slice(0, c);
694
+ }
695
+ }
696
+ else if (event.keypress.isPasted || inp.length > 0) {
697
+ const bracketed = event.keypress.isPasted || inp.includes('[200~');
698
+ const text = inp.replace(BRACKET_PASTE, '').replace(/\r\n/g, '\n').replace(/\r/g, '\n');
699
+ if (bracketed && emitPaste({ bracketed: true, cursor: c, text, value: v })) {
700
+ return;
701
+ }
702
+ if (!text) {
703
+ return;
704
+ }
705
+ if (text === '\n') {
706
+ return commit(ins(v, c, '\n'), c + 1);
707
+ }
708
+ if (text.length > 1 || text.includes('\n')) {
709
+ if (!pasteBuf.current) {
710
+ pastePos.current = range ? range.start : c;
711
+ pasteEnd.current = range ? range.end : pastePos.current;
712
+ }
713
+ pasteBuf.current += text;
714
+ if (pasteTimer.current) {
715
+ clearTimeout(pasteTimer.current);
716
+ }
717
+ pasteTimer.current = setTimeout(flushPaste, 50);
718
+ return;
719
+ }
720
+ if (PRINTABLE.test(text)) {
721
+ if (range) {
722
+ v = v.slice(0, range.start) + text + v.slice(range.end);
723
+ c = range.start + text.length;
724
+ }
725
+ else {
726
+ const simpleAppend = canFastAppend(v, c, text);
727
+ v = v.slice(0, c) + text + v.slice(c);
728
+ c += text.length;
729
+ if (simpleAppend) {
730
+ stdout.write(text);
731
+ commit(v, c, true, false, false, lineWidthRef.current + stringWidth(text));
732
+ return;
733
+ }
734
+ }
735
+ }
736
+ else {
737
+ return;
738
+ }
739
+ }
740
+ else {
741
+ return;
742
+ }
743
+ commit(v, c);
744
+ }, { isActive: focus });
745
+ return (_jsx(Box, { onClick: (e) => {
746
+ if (!focus) {
747
+ return;
748
+ }
749
+ e.stopImmediatePropagation?.();
750
+ clearSel();
751
+ const next = offsetAt(e);
752
+ setCur(next);
753
+ curRef.current = next;
754
+ }, onMouseDown: (e) => {
755
+ if (!focus) {
756
+ return;
757
+ }
758
+ // Right-click → route through the same path as Alt+V so the composer
759
+ // clipboard RPC (text or image) handles it.
760
+ if (e.button === 2) {
761
+ e.stopImmediatePropagation?.();
762
+ emitPaste({ cursor: curRef.current, hotkey: true, text: '', value: vRef.current });
763
+ return;
764
+ }
765
+ if (e.button !== 0) {
766
+ return;
767
+ }
768
+ e.stopImmediatePropagation?.();
769
+ const offset = offsetAt(e);
770
+ if (isMultiClickAt(offset)) {
771
+ mouseAnchorRef.current = null;
772
+ selectAll();
773
+ return;
774
+ }
775
+ startMouseSelection(offset);
776
+ }, onMouseDrag: (e) => {
777
+ if (!focus || e.button !== 0 || mouseAnchorRef.current === null) {
778
+ return;
779
+ }
780
+ e.stopImmediatePropagation?.();
781
+ dragMouseSelection(offsetAt(e));
782
+ }, onMouseUp: (e) => {
783
+ e.stopImmediatePropagation?.();
784
+ endMouseSelection();
785
+ }, ref: boxRef, width: columns, children: _jsx(Text, { wrap: "wrap", children: rendered }) }));
786
+ }
787
+ export const shouldPassThroughToGlobalHandler = (input, key, voiceRecordKey = DEFAULT_VOICE_RECORD_KEY) => (key.ctrl && input === 'c') ||
788
+ (key.ctrl && input === 'x') ||
789
+ key.tab ||
790
+ (key.shift && key.tab) ||
791
+ key.pageUp ||
792
+ key.pageDown ||
793
+ key.escape ||
794
+ isVoiceToggleKey(key, input, voiceRecordKey);