@supatest/cli 0.0.3 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +6586 -153
- package/package.json +4 -3
- package/dist/agent-runner.js +0 -589
- package/dist/commands/login.js +0 -392
- package/dist/commands/setup.js +0 -234
- package/dist/config.js +0 -29
- package/dist/core/agent.js +0 -270
- package/dist/modes/headless.js +0 -117
- package/dist/modes/interactive.js +0 -430
- package/dist/presenters/composite.js +0 -32
- package/dist/presenters/console.js +0 -163
- package/dist/presenters/react.js +0 -220
- package/dist/presenters/types.js +0 -1
- package/dist/presenters/web.js +0 -78
- package/dist/prompts/builder.js +0 -181
- package/dist/prompts/fixer.js +0 -148
- package/dist/prompts/headless.md +0 -97
- package/dist/prompts/index.js +0 -3
- package/dist/prompts/interactive.md +0 -43
- package/dist/prompts/plan.md +0 -41
- package/dist/prompts/planner.js +0 -70
- package/dist/prompts/prompts/builder.md +0 -97
- package/dist/prompts/prompts/fixer.md +0 -100
- package/dist/prompts/prompts/plan.md +0 -41
- package/dist/prompts/prompts/planner.md +0 -41
- package/dist/services/api-client.js +0 -244
- package/dist/services/event-streamer.js +0 -130
- package/dist/types.js +0 -1
- package/dist/ui/App.js +0 -322
- package/dist/ui/components/AuthBanner.js +0 -20
- package/dist/ui/components/AuthDialog.js +0 -32
- package/dist/ui/components/Banner.js +0 -12
- package/dist/ui/components/ExpandableSection.js +0 -17
- package/dist/ui/components/Header.js +0 -49
- package/dist/ui/components/HelpMenu.js +0 -89
- package/dist/ui/components/InputPrompt.js +0 -292
- package/dist/ui/components/MessageList.js +0 -42
- package/dist/ui/components/QueuedMessageDisplay.js +0 -31
- package/dist/ui/components/Scrollable.js +0 -103
- package/dist/ui/components/SessionSelector.js +0 -196
- package/dist/ui/components/StatusBar.js +0 -45
- package/dist/ui/components/messages/AssistantMessage.js +0 -20
- package/dist/ui/components/messages/ErrorMessage.js +0 -26
- package/dist/ui/components/messages/LoadingMessage.js +0 -28
- package/dist/ui/components/messages/ThinkingMessage.js +0 -17
- package/dist/ui/components/messages/TodoMessage.js +0 -44
- package/dist/ui/components/messages/ToolMessage.js +0 -218
- package/dist/ui/components/messages/UserMessage.js +0 -14
- package/dist/ui/contexts/KeypressContext.js +0 -527
- package/dist/ui/contexts/MouseContext.js +0 -98
- package/dist/ui/contexts/SessionContext.js +0 -131
- package/dist/ui/hooks/useAnimatedScrollbar.js +0 -83
- package/dist/ui/hooks/useBatchedScroll.js +0 -22
- package/dist/ui/hooks/useBracketedPaste.js +0 -31
- package/dist/ui/hooks/useFocus.js +0 -50
- package/dist/ui/hooks/useKeypress.js +0 -26
- package/dist/ui/hooks/useModeToggle.js +0 -25
- package/dist/ui/types/auth.js +0 -13
- package/dist/ui/utils/file-completion.js +0 -56
- package/dist/ui/utils/input.js +0 -50
- package/dist/ui/utils/markdown.js +0 -376
- package/dist/ui/utils/mouse.js +0 -189
- package/dist/ui/utils/theme.js +0 -59
- package/dist/utils/banner.js +0 -9
- package/dist/utils/encryption.js +0 -71
- package/dist/utils/events.js +0 -36
- package/dist/utils/keychain-storage.js +0 -120
- package/dist/utils/logger.js +0 -209
- package/dist/utils/node-version.js +0 -89
- package/dist/utils/plan-file.js +0 -75
- package/dist/utils/project-instructions.js +0 -23
- package/dist/utils/rich-logger.js +0 -208
- package/dist/utils/stdin.js +0 -25
- package/dist/utils/stdio.js +0 -80
- package/dist/utils/summary.js +0 -94
- package/dist/utils/token-storage.js +0 -242
|
@@ -1,527 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2025 Google LLC
|
|
4
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
-
*/
|
|
6
|
-
import { useStdin } from 'ink';
|
|
7
|
-
import React, { createContext, useCallback, useContext, useEffect, useRef, } from 'react';
|
|
8
|
-
// Simple debug logger stub
|
|
9
|
-
const debugLogger = {
|
|
10
|
-
warn: (...args) => console.warn(...args),
|
|
11
|
-
error: (...args) => console.error(...args),
|
|
12
|
-
debug: (...args) => console.debug(...args),
|
|
13
|
-
log: (...args) => console.log(...args),
|
|
14
|
-
};
|
|
15
|
-
const createStubConfig = () => ({
|
|
16
|
-
getDebugMode: () => false,
|
|
17
|
-
});
|
|
18
|
-
import { AppEvent, appEvents } from '../../utils/events';
|
|
19
|
-
import { FOCUS_IN, FOCUS_OUT } from '../hooks/useFocus';
|
|
20
|
-
import { ESC } from '../utils/input.js';
|
|
21
|
-
import { parseMouseEvent } from '../utils/mouse';
|
|
22
|
-
export const BACKSLASH_ENTER_TIMEOUT = 5;
|
|
23
|
-
export const ESC_TIMEOUT = 50;
|
|
24
|
-
export const PASTE_TIMEOUT = 30_000;
|
|
25
|
-
// Parse the key itself
|
|
26
|
-
const KEY_INFO_MAP = {
|
|
27
|
-
'[200~': { name: 'paste-start' },
|
|
28
|
-
'[201~': { name: 'paste-end' },
|
|
29
|
-
'[[A': { name: 'f1' },
|
|
30
|
-
'[[B': { name: 'f2' },
|
|
31
|
-
'[[C': { name: 'f3' },
|
|
32
|
-
'[[D': { name: 'f4' },
|
|
33
|
-
'[[E': { name: 'f5' },
|
|
34
|
-
'[1~': { name: 'home' },
|
|
35
|
-
'[2~': { name: 'insert' },
|
|
36
|
-
'[3~': { name: 'delete' },
|
|
37
|
-
'[4~': { name: 'end' },
|
|
38
|
-
'[5~': { name: 'pageup' },
|
|
39
|
-
'[6~': { name: 'pagedown' },
|
|
40
|
-
'[7~': { name: 'home' },
|
|
41
|
-
'[8~': { name: 'end' },
|
|
42
|
-
'[11~': { name: 'f1' },
|
|
43
|
-
'[12~': { name: 'f2' },
|
|
44
|
-
'[13~': { name: 'f3' },
|
|
45
|
-
'[14~': { name: 'f4' },
|
|
46
|
-
'[15~': { name: 'f5' },
|
|
47
|
-
'[17~': { name: 'f6' },
|
|
48
|
-
'[18~': { name: 'f7' },
|
|
49
|
-
'[19~': { name: 'f8' },
|
|
50
|
-
'[20~': { name: 'f9' },
|
|
51
|
-
'[21~': { name: 'f10' },
|
|
52
|
-
'[23~': { name: 'f11' },
|
|
53
|
-
'[24~': { name: 'f12' },
|
|
54
|
-
'[A': { name: 'up' },
|
|
55
|
-
'[B': { name: 'down' },
|
|
56
|
-
'[C': { name: 'right' },
|
|
57
|
-
'[D': { name: 'left' },
|
|
58
|
-
'[E': { name: 'clear' },
|
|
59
|
-
'[F': { name: 'end' },
|
|
60
|
-
'[H': { name: 'home' },
|
|
61
|
-
'[P': { name: 'f1' },
|
|
62
|
-
'[Q': { name: 'f2' },
|
|
63
|
-
'[R': { name: 'f3' },
|
|
64
|
-
'[S': { name: 'f4' },
|
|
65
|
-
OA: { name: 'up' },
|
|
66
|
-
OB: { name: 'down' },
|
|
67
|
-
OC: { name: 'right' },
|
|
68
|
-
OD: { name: 'left' },
|
|
69
|
-
OE: { name: 'clear' },
|
|
70
|
-
OF: { name: 'end' },
|
|
71
|
-
OH: { name: 'home' },
|
|
72
|
-
OP: { name: 'f1' },
|
|
73
|
-
OQ: { name: 'f2' },
|
|
74
|
-
OR: { name: 'f3' },
|
|
75
|
-
OS: { name: 'f4' },
|
|
76
|
-
'[[5~': { name: 'pageup' },
|
|
77
|
-
'[[6~': { name: 'pagedown' },
|
|
78
|
-
'[9u': { name: 'tab' },
|
|
79
|
-
'[13u': { name: 'return' },
|
|
80
|
-
'[27u': { name: 'escape' },
|
|
81
|
-
'[127u': { name: 'backspace' },
|
|
82
|
-
'[57414u': { name: 'return' }, // Numpad Enter
|
|
83
|
-
'[a': { name: 'up', shift: true },
|
|
84
|
-
'[b': { name: 'down', shift: true },
|
|
85
|
-
'[c': { name: 'right', shift: true },
|
|
86
|
-
'[d': { name: 'left', shift: true },
|
|
87
|
-
'[e': { name: 'clear', shift: true },
|
|
88
|
-
'[2$': { name: 'insert', shift: true },
|
|
89
|
-
'[3$': { name: 'delete', shift: true },
|
|
90
|
-
'[5$': { name: 'pageup', shift: true },
|
|
91
|
-
'[6$': { name: 'pagedown', shift: true },
|
|
92
|
-
'[7$': { name: 'home', shift: true },
|
|
93
|
-
'[8$': { name: 'end', shift: true },
|
|
94
|
-
'[Z': { name: 'tab', shift: true },
|
|
95
|
-
Oa: { name: 'up', ctrl: true },
|
|
96
|
-
Ob: { name: 'down', ctrl: true },
|
|
97
|
-
Oc: { name: 'right', ctrl: true },
|
|
98
|
-
Od: { name: 'left', ctrl: true },
|
|
99
|
-
Oe: { name: 'clear', ctrl: true },
|
|
100
|
-
'[2^': { name: 'insert', ctrl: true },
|
|
101
|
-
'[3^': { name: 'delete', ctrl: true },
|
|
102
|
-
'[5^': { name: 'pageup', ctrl: true },
|
|
103
|
-
'[6^': { name: 'pagedown', ctrl: true },
|
|
104
|
-
'[7^': { name: 'home', ctrl: true },
|
|
105
|
-
'[8^': { name: 'end', ctrl: true },
|
|
106
|
-
};
|
|
107
|
-
const kUTF16SurrogateThreshold = 0x10000; // 2 ** 16
|
|
108
|
-
function charLengthAt(str, i) {
|
|
109
|
-
if (str.length <= i) {
|
|
110
|
-
// Pretend to move to the right. This is necessary to autocomplete while
|
|
111
|
-
// moving to the right.
|
|
112
|
-
return 1;
|
|
113
|
-
}
|
|
114
|
-
const code = str.codePointAt(i);
|
|
115
|
-
return code !== undefined && code >= kUTF16SurrogateThreshold ? 2 : 1;
|
|
116
|
-
}
|
|
117
|
-
const MAC_ALT_KEY_CHARACTER_MAP = {
|
|
118
|
-
'\u222B': 'b', // "∫" back one word
|
|
119
|
-
'\u0192': 'f', // "ƒ" forward one word
|
|
120
|
-
'\u00B5': 'm', // "µ" toggle markup view
|
|
121
|
-
};
|
|
122
|
-
function nonKeyboardEventFilter(keypressHandler) {
|
|
123
|
-
return (key) => {
|
|
124
|
-
if (!parseMouseEvent(key.sequence) &&
|
|
125
|
-
key.sequence !== FOCUS_IN &&
|
|
126
|
-
key.sequence !== FOCUS_OUT) {
|
|
127
|
-
keypressHandler(key);
|
|
128
|
-
}
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* Buffers "/" keys to see if they are followed return.
|
|
133
|
-
* Will flush the buffer if no data is received for DRAG_COMPLETION_TIMEOUT_MS
|
|
134
|
-
* or when a null key is received.
|
|
135
|
-
*/
|
|
136
|
-
function bufferBackslashEnter(keypressHandler) {
|
|
137
|
-
const bufferer = (function* () {
|
|
138
|
-
while (true) {
|
|
139
|
-
const key = yield;
|
|
140
|
-
if (key == null) {
|
|
141
|
-
continue;
|
|
142
|
-
}
|
|
143
|
-
else if (key.sequence !== '\\') {
|
|
144
|
-
keypressHandler(key);
|
|
145
|
-
continue;
|
|
146
|
-
}
|
|
147
|
-
const timeoutId = setTimeout(() => bufferer.next(null), BACKSLASH_ENTER_TIMEOUT);
|
|
148
|
-
const nextKey = yield;
|
|
149
|
-
clearTimeout(timeoutId);
|
|
150
|
-
if (nextKey === null) {
|
|
151
|
-
keypressHandler(key);
|
|
152
|
-
}
|
|
153
|
-
else if (nextKey.name === 'return') {
|
|
154
|
-
keypressHandler({
|
|
155
|
-
...nextKey,
|
|
156
|
-
shift: true,
|
|
157
|
-
sequence: '\r', // Corrected escaping for newline
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
else {
|
|
161
|
-
keypressHandler(key);
|
|
162
|
-
keypressHandler(nextKey);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
})();
|
|
166
|
-
bufferer.next(); // prime the generator so it starts listening.
|
|
167
|
-
return (key) => bufferer.next(key);
|
|
168
|
-
}
|
|
169
|
-
/**
|
|
170
|
-
* Buffers paste events between paste-start and paste-end sequences.
|
|
171
|
-
* Will flush the buffer if no data is received for PASTE_TIMEOUT ms or
|
|
172
|
-
* when a null key is received.
|
|
173
|
-
*/
|
|
174
|
-
function bufferPaste(keypressHandler) {
|
|
175
|
-
const bufferer = (function* () {
|
|
176
|
-
while (true) {
|
|
177
|
-
let key = yield;
|
|
178
|
-
if (key === null) {
|
|
179
|
-
continue;
|
|
180
|
-
}
|
|
181
|
-
else if (key.name !== 'paste-start') {
|
|
182
|
-
keypressHandler(key);
|
|
183
|
-
continue;
|
|
184
|
-
}
|
|
185
|
-
let buffer = '';
|
|
186
|
-
while (true) {
|
|
187
|
-
const timeoutId = setTimeout(() => bufferer.next(null), PASTE_TIMEOUT);
|
|
188
|
-
key = yield;
|
|
189
|
-
clearTimeout(timeoutId);
|
|
190
|
-
if (key === null) {
|
|
191
|
-
appEvents.emit(AppEvent.PasteTimeout);
|
|
192
|
-
break;
|
|
193
|
-
}
|
|
194
|
-
if (key.name === 'paste-end') {
|
|
195
|
-
break;
|
|
196
|
-
}
|
|
197
|
-
buffer += key.sequence;
|
|
198
|
-
}
|
|
199
|
-
if (buffer.length > 0) {
|
|
200
|
-
keypressHandler({
|
|
201
|
-
name: '',
|
|
202
|
-
ctrl: false,
|
|
203
|
-
meta: false,
|
|
204
|
-
shift: false,
|
|
205
|
-
paste: true,
|
|
206
|
-
insertable: true,
|
|
207
|
-
sequence: buffer,
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
})();
|
|
212
|
-
bufferer.next(); // prime the generator so it starts listening.
|
|
213
|
-
return (key) => bufferer.next(key);
|
|
214
|
-
}
|
|
215
|
-
/**
|
|
216
|
-
* Turns raw data strings into keypress events sent to the provided handler.
|
|
217
|
-
* Buffers escape sequences until a full sequence is received or
|
|
218
|
-
* until a timeout occurs.
|
|
219
|
-
*/
|
|
220
|
-
function createDataListener(keypressHandler) {
|
|
221
|
-
const parser = emitKeys(keypressHandler);
|
|
222
|
-
parser.next(); // prime the generator so it starts listening.
|
|
223
|
-
let timeoutId;
|
|
224
|
-
return (data) => {
|
|
225
|
-
clearTimeout(timeoutId);
|
|
226
|
-
for (const char of data) {
|
|
227
|
-
parser.next(char);
|
|
228
|
-
}
|
|
229
|
-
if (data.length !== 0) {
|
|
230
|
-
timeoutId = setTimeout(() => parser.next(''), ESC_TIMEOUT);
|
|
231
|
-
}
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
/**
|
|
235
|
-
* Translates raw keypress characters into key events.
|
|
236
|
-
* Buffers escape sequences until a full sequence is received or
|
|
237
|
-
* until an empty string is sent to indicate a timeout.
|
|
238
|
-
*/
|
|
239
|
-
function* emitKeys(keypressHandler) {
|
|
240
|
-
while (true) {
|
|
241
|
-
let ch = yield;
|
|
242
|
-
let sequence = ch;
|
|
243
|
-
let escaped = false;
|
|
244
|
-
let name = undefined;
|
|
245
|
-
let ctrl = false;
|
|
246
|
-
let meta = false;
|
|
247
|
-
let shift = false;
|
|
248
|
-
let code = undefined;
|
|
249
|
-
let insertable = false;
|
|
250
|
-
if (ch === ESC) {
|
|
251
|
-
escaped = true;
|
|
252
|
-
ch = yield;
|
|
253
|
-
sequence += ch;
|
|
254
|
-
if (ch === ESC) {
|
|
255
|
-
ch = yield;
|
|
256
|
-
sequence += ch;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
if (escaped && (ch === 'O' || ch === '[')) {
|
|
260
|
-
// ANSI escape sequence
|
|
261
|
-
code = ch;
|
|
262
|
-
let modifier = 0;
|
|
263
|
-
if (ch === 'O') {
|
|
264
|
-
// ESC O letter
|
|
265
|
-
// ESC O modifier letter
|
|
266
|
-
ch = yield;
|
|
267
|
-
sequence += ch;
|
|
268
|
-
if (ch >= '0' && ch <= '9') {
|
|
269
|
-
modifier = parseInt(ch, 10) - 1;
|
|
270
|
-
ch = yield;
|
|
271
|
-
sequence += ch;
|
|
272
|
-
}
|
|
273
|
-
code += ch;
|
|
274
|
-
}
|
|
275
|
-
else if (ch === '[') {
|
|
276
|
-
// ESC [ letter
|
|
277
|
-
// ESC [ modifier letter
|
|
278
|
-
// ESC [ [ modifier letter
|
|
279
|
-
// ESC [ [ num char
|
|
280
|
-
ch = yield;
|
|
281
|
-
sequence += ch;
|
|
282
|
-
if (ch === '[') {
|
|
283
|
-
// \x1b[[A
|
|
284
|
-
// ^--- escape codes might have a second bracket
|
|
285
|
-
code += ch;
|
|
286
|
-
ch = yield;
|
|
287
|
-
sequence += ch;
|
|
288
|
-
}
|
|
289
|
-
/*
|
|
290
|
-
* Here and later we try to buffer just enough data to get
|
|
291
|
-
* a complete ascii sequence.
|
|
292
|
-
*
|
|
293
|
-
* We have basically two classes of ascii characters to process:
|
|
294
|
-
*
|
|
295
|
-
*
|
|
296
|
-
* 1. `\x1b[24;5~` should be parsed as { code: '[24~', modifier: 5 }
|
|
297
|
-
*
|
|
298
|
-
* This particular example is featuring Ctrl+F12 in xterm.
|
|
299
|
-
*
|
|
300
|
-
* - `;5` part is optional, e.g. it could be `\x1b[24~`
|
|
301
|
-
* - first part can contain one or two digits
|
|
302
|
-
* - there is also special case when there can be 3 digits
|
|
303
|
-
* but without modifier. They are the case of paste bracket mode
|
|
304
|
-
*
|
|
305
|
-
* So the generic regexp is like /^(?:\d\d?(;\d)?[~^$]|\d{3}~)$/
|
|
306
|
-
*
|
|
307
|
-
*
|
|
308
|
-
* 2. `\x1b[1;5H` should be parsed as { code: '[H', modifier: 5 }
|
|
309
|
-
*
|
|
310
|
-
* This particular example is featuring Ctrl+Home in xterm.
|
|
311
|
-
*
|
|
312
|
-
* - `1;5` part is optional, e.g. it could be `\x1b[H`
|
|
313
|
-
* - `1;` part is optional, e.g. it could be `\x1b[5H`
|
|
314
|
-
*
|
|
315
|
-
* So the generic regexp is like /^((\d;)?\d)?[A-Za-z]$/
|
|
316
|
-
*
|
|
317
|
-
*/
|
|
318
|
-
const cmdStart = sequence.length - 1;
|
|
319
|
-
// collect as many digits as possible
|
|
320
|
-
while (ch >= '0' && ch <= '9') {
|
|
321
|
-
ch = yield;
|
|
322
|
-
sequence += ch;
|
|
323
|
-
}
|
|
324
|
-
// skip modifier
|
|
325
|
-
if (ch === ';') {
|
|
326
|
-
while (ch === ';') {
|
|
327
|
-
ch = yield;
|
|
328
|
-
sequence += ch;
|
|
329
|
-
// collect as many digits as possible
|
|
330
|
-
while (ch >= '0' && ch <= '9') {
|
|
331
|
-
ch = yield;
|
|
332
|
-
sequence += ch;
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
else if (ch === '<') {
|
|
337
|
-
// SGR mouse mode
|
|
338
|
-
ch = yield;
|
|
339
|
-
sequence += ch;
|
|
340
|
-
// Don't skip on empty string here to avoid timeouts on slow events.
|
|
341
|
-
while (ch === '' || ch === ';' || (ch >= '0' && ch <= '9')) {
|
|
342
|
-
ch = yield;
|
|
343
|
-
sequence += ch;
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
else if (ch === 'M') {
|
|
347
|
-
// X11 mouse mode
|
|
348
|
-
// three characters after 'M'
|
|
349
|
-
ch = yield;
|
|
350
|
-
sequence += ch;
|
|
351
|
-
ch = yield;
|
|
352
|
-
sequence += ch;
|
|
353
|
-
ch = yield;
|
|
354
|
-
sequence += ch;
|
|
355
|
-
}
|
|
356
|
-
/*
|
|
357
|
-
* We buffered enough data, now trying to extract code
|
|
358
|
-
* and modifier from it
|
|
359
|
-
*/
|
|
360
|
-
const cmd = sequence.slice(cmdStart);
|
|
361
|
-
let match;
|
|
362
|
-
if ((match = /^(\d+)(?:;(\d+))?(?:;(\d+))?([~^$u])$/.exec(cmd))) {
|
|
363
|
-
if (match[1] === '27' && match[3] && match[4] === '~') {
|
|
364
|
-
// modifyOtherKeys format: CSI 27 ; modifier ; key ~
|
|
365
|
-
// Treat as CSI u: key + 'u'
|
|
366
|
-
code += match[3] + 'u';
|
|
367
|
-
modifier = parseInt(match[2] ?? '1', 10) - 1;
|
|
368
|
-
}
|
|
369
|
-
else {
|
|
370
|
-
code += match[1] + match[4];
|
|
371
|
-
// Defaults to '1' if no modifier exists, resulting in a 0 modifier value
|
|
372
|
-
modifier = parseInt(match[2] ?? '1', 10) - 1;
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
else if ((match = /^(\d+)?(?:;(\d+))?([A-Za-z])$/.exec(cmd))) {
|
|
376
|
-
code += match[3];
|
|
377
|
-
modifier = parseInt(match[2] ?? match[1] ?? '1', 10) - 1;
|
|
378
|
-
}
|
|
379
|
-
else {
|
|
380
|
-
code += cmd;
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
// Parse the key modifier
|
|
384
|
-
ctrl = !!(modifier & 4);
|
|
385
|
-
meta = !!(modifier & 10); // use 10 to catch both alt (2) and meta (8).
|
|
386
|
-
shift = !!(modifier & 1);
|
|
387
|
-
const keyInfo = KEY_INFO_MAP[code];
|
|
388
|
-
if (keyInfo) {
|
|
389
|
-
name = keyInfo.name;
|
|
390
|
-
if (keyInfo.shift) {
|
|
391
|
-
shift = true;
|
|
392
|
-
}
|
|
393
|
-
if (keyInfo.ctrl) {
|
|
394
|
-
ctrl = true;
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
else {
|
|
398
|
-
name = 'undefined';
|
|
399
|
-
if ((ctrl || meta) && (code.endsWith('u') || code.endsWith('~'))) {
|
|
400
|
-
// CSI-u or tilde-coded functional keys: ESC [ <code> ; <mods> (u|~)
|
|
401
|
-
const codeNumber = parseInt(code.slice(1, -1), 10);
|
|
402
|
-
if (codeNumber >= 'a'.charCodeAt(0) &&
|
|
403
|
-
codeNumber <= 'z'.charCodeAt(0)) {
|
|
404
|
-
name = String.fromCharCode(codeNumber);
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
else if (ch === '\r') {
|
|
410
|
-
// carriage return
|
|
411
|
-
name = 'return';
|
|
412
|
-
meta = escaped;
|
|
413
|
-
}
|
|
414
|
-
else if (ch === '\n') {
|
|
415
|
-
// Enter, should have been called linefeed
|
|
416
|
-
name = 'enter';
|
|
417
|
-
meta = escaped;
|
|
418
|
-
}
|
|
419
|
-
else if (ch === '\t') {
|
|
420
|
-
// tab
|
|
421
|
-
name = 'tab';
|
|
422
|
-
meta = escaped;
|
|
423
|
-
}
|
|
424
|
-
else if (ch === '\b' || ch === '\x7f') {
|
|
425
|
-
// backspace or ctrl+h
|
|
426
|
-
name = 'backspace';
|
|
427
|
-
meta = escaped;
|
|
428
|
-
}
|
|
429
|
-
else if (ch === ESC) {
|
|
430
|
-
// escape key
|
|
431
|
-
name = 'escape';
|
|
432
|
-
meta = escaped;
|
|
433
|
-
}
|
|
434
|
-
else if (ch === ' ') {
|
|
435
|
-
name = 'space';
|
|
436
|
-
meta = escaped;
|
|
437
|
-
insertable = true;
|
|
438
|
-
}
|
|
439
|
-
else if (!escaped && ch <= '\x1a') {
|
|
440
|
-
// ctrl+letter
|
|
441
|
-
name = String.fromCharCode(ch.charCodeAt(0) + 'a'.charCodeAt(0) - 1);
|
|
442
|
-
ctrl = true;
|
|
443
|
-
}
|
|
444
|
-
else if (/^[0-9A-Za-z]$/.exec(ch) !== null) {
|
|
445
|
-
// Letter, number, shift+letter
|
|
446
|
-
name = ch.toLowerCase();
|
|
447
|
-
shift = /^[A-Z]$/.exec(ch) !== null;
|
|
448
|
-
meta = escaped;
|
|
449
|
-
insertable = true;
|
|
450
|
-
}
|
|
451
|
-
else if (MAC_ALT_KEY_CHARACTER_MAP[ch] && process.platform === 'darwin') {
|
|
452
|
-
name = MAC_ALT_KEY_CHARACTER_MAP[ch];
|
|
453
|
-
meta = true;
|
|
454
|
-
}
|
|
455
|
-
else if (sequence === `${ESC}${ESC}`) {
|
|
456
|
-
// Double escape
|
|
457
|
-
name = 'escape';
|
|
458
|
-
meta = true;
|
|
459
|
-
// Emit first escape key here, then continue processing
|
|
460
|
-
keypressHandler({
|
|
461
|
-
name: 'escape',
|
|
462
|
-
ctrl,
|
|
463
|
-
meta,
|
|
464
|
-
shift,
|
|
465
|
-
paste: false,
|
|
466
|
-
insertable: false,
|
|
467
|
-
sequence: ESC,
|
|
468
|
-
});
|
|
469
|
-
}
|
|
470
|
-
else if (escaped) {
|
|
471
|
-
// Escape sequence timeout
|
|
472
|
-
name = ch.length ? undefined : 'escape';
|
|
473
|
-
meta = true;
|
|
474
|
-
}
|
|
475
|
-
else {
|
|
476
|
-
// Any other character is considered printable.
|
|
477
|
-
insertable = true;
|
|
478
|
-
}
|
|
479
|
-
if ((sequence.length !== 0 && (name !== undefined || escaped)) ||
|
|
480
|
-
charLengthAt(sequence, 0) === sequence.length) {
|
|
481
|
-
keypressHandler({
|
|
482
|
-
name: name || '',
|
|
483
|
-
ctrl,
|
|
484
|
-
meta,
|
|
485
|
-
shift,
|
|
486
|
-
paste: false,
|
|
487
|
-
insertable,
|
|
488
|
-
sequence,
|
|
489
|
-
});
|
|
490
|
-
}
|
|
491
|
-
// Unrecognized or broken escape sequence, don't emit anything
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
const KeypressContext = createContext(undefined);
|
|
495
|
-
export function useKeypressContext() {
|
|
496
|
-
const context = useContext(KeypressContext);
|
|
497
|
-
if (!context) {
|
|
498
|
-
throw new Error('useKeypressContext must be used within a KeypressProvider');
|
|
499
|
-
}
|
|
500
|
-
return context;
|
|
501
|
-
}
|
|
502
|
-
export function KeypressProvider({ children, config, debugKeystrokeLogging, }) {
|
|
503
|
-
const { stdin, setRawMode } = useStdin();
|
|
504
|
-
const subscribers = useRef(new Set()).current;
|
|
505
|
-
const subscribe = useCallback((handler) => subscribers.add(handler), [subscribers]);
|
|
506
|
-
const unsubscribe = useCallback((handler) => subscribers.delete(handler), [subscribers]);
|
|
507
|
-
const broadcast = useCallback((key) => subscribers.forEach((handler) => handler(key)), [subscribers]);
|
|
508
|
-
useEffect(() => {
|
|
509
|
-
const wasRaw = stdin.isRaw;
|
|
510
|
-
if (wasRaw === false) {
|
|
511
|
-
setRawMode(true);
|
|
512
|
-
}
|
|
513
|
-
process.stdin.setEncoding('utf8'); // Make data events emit strings
|
|
514
|
-
const mouseFilterer = nonKeyboardEventFilter(broadcast);
|
|
515
|
-
const backslashBufferer = bufferBackslashEnter(mouseFilterer);
|
|
516
|
-
const pasteBufferer = bufferPaste(backslashBufferer);
|
|
517
|
-
let dataListener = createDataListener(pasteBufferer);
|
|
518
|
-
stdin.on('data', dataListener);
|
|
519
|
-
return () => {
|
|
520
|
-
stdin.removeListener('data', dataListener);
|
|
521
|
-
if (wasRaw === false) {
|
|
522
|
-
setRawMode(false);
|
|
523
|
-
}
|
|
524
|
-
};
|
|
525
|
-
}, [stdin, setRawMode, config, debugKeystrokeLogging, broadcast]);
|
|
526
|
-
return (React.createElement(KeypressContext.Provider, { value: { subscribe, unsubscribe } }, children));
|
|
527
|
-
}
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2025 Google LLC
|
|
4
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
-
*/
|
|
6
|
-
import { useStdin } from 'ink';
|
|
7
|
-
import React, { createContext, useCallback, useContext, useEffect, useRef, } from 'react';
|
|
8
|
-
import { AppEvent, appEvents } from '../../utils/events';
|
|
9
|
-
import { ESC } from '../utils/input';
|
|
10
|
-
import { isIncompleteMouseSequence, parseMouseEvent, } from '../utils/mouse';
|
|
11
|
-
const MAX_MOUSE_BUFFER_SIZE = 4096;
|
|
12
|
-
const MouseContext = createContext(undefined);
|
|
13
|
-
export function useMouseContext() {
|
|
14
|
-
const context = useContext(MouseContext);
|
|
15
|
-
if (!context) {
|
|
16
|
-
throw new Error('useMouseContext must be used within a MouseProvider');
|
|
17
|
-
}
|
|
18
|
-
return context;
|
|
19
|
-
}
|
|
20
|
-
export function useMouse(handler, { isActive = true } = {}) {
|
|
21
|
-
const { subscribe, unsubscribe } = useMouseContext();
|
|
22
|
-
useEffect(() => {
|
|
23
|
-
if (!isActive) {
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
subscribe(handler);
|
|
27
|
-
return () => unsubscribe(handler);
|
|
28
|
-
}, [isActive, handler, subscribe, unsubscribe]);
|
|
29
|
-
}
|
|
30
|
-
export function MouseProvider({ children, mouseEventsEnabled, debugKeystrokeLogging, }) {
|
|
31
|
-
const { stdin } = useStdin();
|
|
32
|
-
const subscribers = useRef(new Set()).current;
|
|
33
|
-
const subscribe = useCallback((handler) => {
|
|
34
|
-
subscribers.add(handler);
|
|
35
|
-
}, [subscribers]);
|
|
36
|
-
const unsubscribe = useCallback((handler) => {
|
|
37
|
-
subscribers.delete(handler);
|
|
38
|
-
}, [subscribers]);
|
|
39
|
-
useEffect(() => {
|
|
40
|
-
if (!mouseEventsEnabled) {
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
let mouseBuffer = '';
|
|
44
|
-
const broadcast = (event) => {
|
|
45
|
-
let handled = false;
|
|
46
|
-
for (const handler of subscribers) {
|
|
47
|
-
if (handler(event) === true) {
|
|
48
|
-
handled = true;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
if (!handled &&
|
|
52
|
-
event.name === 'move' &&
|
|
53
|
-
event.col >= 0 &&
|
|
54
|
-
event.row >= 0 &&
|
|
55
|
-
event.button === 'left') {
|
|
56
|
-
// Terminal apps only receive mouse move events when the mouse is down
|
|
57
|
-
// so this always indicates a mouse drag that the user was expecting
|
|
58
|
-
// would trigger text selection but does not as we are handling mouse
|
|
59
|
-
// events not the terminal.
|
|
60
|
-
appEvents.emit(AppEvent.SelectionWarning);
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
const handleData = (data) => {
|
|
64
|
-
mouseBuffer += typeof data === 'string' ? data : data.toString('utf-8');
|
|
65
|
-
// Safety cap to prevent infinite buffer growth on garbage
|
|
66
|
-
if (mouseBuffer.length > MAX_MOUSE_BUFFER_SIZE) {
|
|
67
|
-
mouseBuffer = mouseBuffer.slice(-MAX_MOUSE_BUFFER_SIZE);
|
|
68
|
-
}
|
|
69
|
-
while (mouseBuffer.length > 0) {
|
|
70
|
-
const parsed = parseMouseEvent(mouseBuffer);
|
|
71
|
-
if (parsed) {
|
|
72
|
-
broadcast(parsed.event);
|
|
73
|
-
mouseBuffer = mouseBuffer.slice(parsed.length);
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
if (isIncompleteMouseSequence(mouseBuffer)) {
|
|
77
|
-
break; // Wait for more data
|
|
78
|
-
}
|
|
79
|
-
// Not a valid sequence at start, and not waiting for more data.
|
|
80
|
-
// Discard garbage until next possible sequence start.
|
|
81
|
-
const nextEsc = mouseBuffer.indexOf(ESC, 1);
|
|
82
|
-
if (nextEsc !== -1) {
|
|
83
|
-
mouseBuffer = mouseBuffer.slice(nextEsc);
|
|
84
|
-
// Loop continues to try parsing at new location
|
|
85
|
-
}
|
|
86
|
-
else {
|
|
87
|
-
mouseBuffer = '';
|
|
88
|
-
break;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
};
|
|
92
|
-
stdin.on('data', handleData);
|
|
93
|
-
return () => {
|
|
94
|
-
stdin.removeListener('data', handleData);
|
|
95
|
-
};
|
|
96
|
-
}, [stdin, mouseEventsEnabled, subscribers, debugKeystrokeLogging]);
|
|
97
|
-
return (React.createElement(MouseContext.Provider, { value: { subscribe, unsubscribe } }, children));
|
|
98
|
-
}
|