@plusonelabs/cue 0.0.38 → 0.0.39
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/backup-input/Cursor.backup.ts +261 -0
- package/backup-input/input-component.backup.tsx +121 -0
- package/backup-input/prompt-input.backup.tsx +80 -0
- package/backup-input/useTextInput.backup.ts +632 -0
- package/cue-v0.0.39.tar.gz +0 -0
- package/dist/cli.mjs +293 -287
- package/package.json +1 -1
- package/cue-v0.0.38.tar.gz +0 -0
|
@@ -0,0 +1,632 @@
|
|
|
1
|
+
import { useInput } from 'ink';
|
|
2
|
+
import { useState, useCallback, useRef, useEffect } from 'react';
|
|
3
|
+
import { useDoublePress } from './useDoublePress.js';
|
|
4
|
+
import { Cursor } from '../utils/Cursor.js';
|
|
5
|
+
import { detectCursorCommand } from '../utils/keyMapping.js';
|
|
6
|
+
|
|
7
|
+
const ESC = '\u001B';
|
|
8
|
+
const PASTE_START = `[200~`;
|
|
9
|
+
const PASTE_END = `[201~`;
|
|
10
|
+
const PASTE_START_WITH_ESC = `${ESC}[200~`;
|
|
11
|
+
const PASTE_END_WITH_ESC = `${ESC}[201~`;
|
|
12
|
+
|
|
13
|
+
type Props = {
|
|
14
|
+
value: string;
|
|
15
|
+
onChange: (value: string) => void;
|
|
16
|
+
onSubmit: (value: string, isSubmittingSlashCommand?: boolean) => void;
|
|
17
|
+
commands: { name: string; description: string }[];
|
|
18
|
+
onToggleShortcuts?: () => void;
|
|
19
|
+
onClearShortcuts?: () => void;
|
|
20
|
+
onCancel?: () => boolean;
|
|
21
|
+
isLoading?: boolean;
|
|
22
|
+
disabled?: boolean;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export function useTextInput({
|
|
26
|
+
value,
|
|
27
|
+
onChange,
|
|
28
|
+
onSubmit,
|
|
29
|
+
commands,
|
|
30
|
+
onToggleShortcuts,
|
|
31
|
+
onClearShortcuts,
|
|
32
|
+
onCancel,
|
|
33
|
+
isLoading = false,
|
|
34
|
+
disabled = false,
|
|
35
|
+
}: Props) {
|
|
36
|
+
const [suggestions, setSuggestions] = useState<string[]>([]);
|
|
37
|
+
const [selectedSuggestion, setSelectedSuggestion] = useState(-1);
|
|
38
|
+
const [message, setMessage] = useState<{ show: boolean; text?: string }>({
|
|
39
|
+
show: false,
|
|
40
|
+
});
|
|
41
|
+
const [offset, setOffset] = useState(value.length);
|
|
42
|
+
const cursor = Cursor.fromText(value, 80, offset);
|
|
43
|
+
const [selection, setSelection] = useState<{
|
|
44
|
+
start: number;
|
|
45
|
+
end: number;
|
|
46
|
+
} | null>(null);
|
|
47
|
+
const [pasteInfo, setPasteInfo] = useState<{
|
|
48
|
+
pastes: Array<{
|
|
49
|
+
content: string;
|
|
50
|
+
lineCount: number;
|
|
51
|
+
charCount: number;
|
|
52
|
+
}>;
|
|
53
|
+
userInput: string;
|
|
54
|
+
totalLineCount: number;
|
|
55
|
+
totalCharCount: number;
|
|
56
|
+
isSummarized: boolean;
|
|
57
|
+
} | null>(null);
|
|
58
|
+
|
|
59
|
+
const [isPasteMode, setIsPasteMode] = useState(false);
|
|
60
|
+
const isPasteModeRef = useRef(false);
|
|
61
|
+
const [pasteBuffer, setPasteBuffer] = useState<string>('');
|
|
62
|
+
const pasteBufferRef = useRef<string>('');
|
|
63
|
+
const accumulatedInputRef = useRef<string>('');
|
|
64
|
+
const pasteTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
65
|
+
const lastInputTimeRef = useRef<number>(Date.now());
|
|
66
|
+
const [isPasting, setIsPasting] = useState(false);
|
|
67
|
+
const PASTE_TIMEOUT = 500;
|
|
68
|
+
const lastDeleteTimeRef = useRef<number>(0);
|
|
69
|
+
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
process.stdout.write(`${ESC}[?2004h`);
|
|
72
|
+
return () => {
|
|
73
|
+
process.stdout.write(`${ESC}[?2004l`);
|
|
74
|
+
};
|
|
75
|
+
}, []);
|
|
76
|
+
|
|
77
|
+
const finalizePaste = useCallback(
|
|
78
|
+
(buffer: string) => {
|
|
79
|
+
const LINES_THRESHOLD = 30;
|
|
80
|
+
const CHARS_THRESHOLD = 1000;
|
|
81
|
+
const normalizedBuffer = buffer
|
|
82
|
+
.replace(/\r\n/g, '\n')
|
|
83
|
+
.replace(/\r/g, '\n');
|
|
84
|
+
|
|
85
|
+
let lines = normalizedBuffer.split('\n');
|
|
86
|
+
if (lines.length > 0 && lines[lines.length - 1] === '') {
|
|
87
|
+
lines = lines.slice(0, -1);
|
|
88
|
+
}
|
|
89
|
+
const lineCount = lines.length;
|
|
90
|
+
const charCount = normalizedBuffer.length;
|
|
91
|
+
const shouldSummarize =
|
|
92
|
+
lineCount > LINES_THRESHOLD && charCount > CHARS_THRESHOLD;
|
|
93
|
+
|
|
94
|
+
if (shouldSummarize && process.stdout.isTTY) {
|
|
95
|
+
process.stdout.write('\x1b[2K\r');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (shouldSummarize) {
|
|
99
|
+
setPasteInfo((prev) => {
|
|
100
|
+
const newPaste = {
|
|
101
|
+
content: normalizedBuffer,
|
|
102
|
+
lineCount: lineCount,
|
|
103
|
+
charCount: charCount,
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
if (prev) {
|
|
107
|
+
const updatedPastes = [...prev.pastes, newPaste];
|
|
108
|
+
onChange(prev.userInput);
|
|
109
|
+
setOffset(prev.userInput.length);
|
|
110
|
+
return {
|
|
111
|
+
pastes: updatedPastes,
|
|
112
|
+
userInput: prev.userInput,
|
|
113
|
+
totalLineCount: prev.totalLineCount + lineCount,
|
|
114
|
+
totalCharCount: prev.totalCharCount + charCount,
|
|
115
|
+
isSummarized: true,
|
|
116
|
+
};
|
|
117
|
+
} else {
|
|
118
|
+
onChange('');
|
|
119
|
+
setOffset(0);
|
|
120
|
+
return {
|
|
121
|
+
pastes: [newPaste],
|
|
122
|
+
userInput: '',
|
|
123
|
+
totalLineCount: lineCount,
|
|
124
|
+
totalCharCount: charCount,
|
|
125
|
+
isSummarized: true,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
} else {
|
|
130
|
+
const newCursor = cursor.insert(normalizedBuffer);
|
|
131
|
+
onChange(newCursor.text);
|
|
132
|
+
setOffset(newCursor.offset);
|
|
133
|
+
setSelection(null);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
setIsPasting(false);
|
|
137
|
+
setPasteBuffer('');
|
|
138
|
+
setMessage({ show: false });
|
|
139
|
+
},
|
|
140
|
+
[onChange, cursor, setPasteInfo]
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
const processAccumulatedInput = useCallback(() => {
|
|
144
|
+
const input = accumulatedInputRef.current;
|
|
145
|
+
if (!input) return;
|
|
146
|
+
|
|
147
|
+
let pasteStartIdx = input.indexOf(PASTE_START_WITH_ESC);
|
|
148
|
+
let pasteEndIdx = input.indexOf(PASTE_END_WITH_ESC);
|
|
149
|
+
let pasteStartLen = PASTE_START_WITH_ESC.length;
|
|
150
|
+
let pasteEndLen = PASTE_END_WITH_ESC.length;
|
|
151
|
+
|
|
152
|
+
if (pasteStartIdx === -1) {
|
|
153
|
+
pasteStartIdx = input.indexOf(PASTE_START);
|
|
154
|
+
pasteStartLen = PASTE_START.length;
|
|
155
|
+
}
|
|
156
|
+
if (pasteEndIdx === -1) {
|
|
157
|
+
pasteEndIdx = input.indexOf(PASTE_END);
|
|
158
|
+
pasteEndLen = PASTE_END.length;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (
|
|
162
|
+
pasteStartIdx !== -1 &&
|
|
163
|
+
pasteEndIdx !== -1 &&
|
|
164
|
+
pasteEndIdx > pasteStartIdx
|
|
165
|
+
) {
|
|
166
|
+
const beforePaste = input.substring(0, pasteStartIdx);
|
|
167
|
+
const pasteContent = input.substring(
|
|
168
|
+
pasteStartIdx + pasteStartLen,
|
|
169
|
+
pasteEndIdx
|
|
170
|
+
);
|
|
171
|
+
const afterPaste = input.substring(pasteEndIdx + pasteEndLen);
|
|
172
|
+
|
|
173
|
+
if (beforePaste) {
|
|
174
|
+
const newCursor = cursor.insert(beforePaste);
|
|
175
|
+
onChange(newCursor.text);
|
|
176
|
+
setOffset(newCursor.offset);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
finalizePaste(pasteContent);
|
|
180
|
+
accumulatedInputRef.current = afterPaste;
|
|
181
|
+
setIsPasteMode(false);
|
|
182
|
+
|
|
183
|
+
if (afterPaste) {
|
|
184
|
+
setTimeout(() => processAccumulatedInput(), 0);
|
|
185
|
+
}
|
|
186
|
+
} else if (pasteStartIdx !== -1 && pasteEndIdx === -1) {
|
|
187
|
+
setIsPasteMode(true);
|
|
188
|
+
} else if (pasteEndIdx !== -1 && pasteStartIdx === -1) {
|
|
189
|
+
const pasteContent = input.substring(0, pasteEndIdx);
|
|
190
|
+
if (isPasteMode && pasteBuffer) {
|
|
191
|
+
finalizePaste(pasteBuffer + pasteContent);
|
|
192
|
+
}
|
|
193
|
+
accumulatedInputRef.current = input.substring(pasteEndIdx + pasteEndLen);
|
|
194
|
+
setIsPasteMode(false);
|
|
195
|
+
setPasteBuffer('');
|
|
196
|
+
} else if (!isPasteMode) {
|
|
197
|
+
if (input.length > 100 || input.includes('\n') || input.includes('\r')) {
|
|
198
|
+
const toProcess = input;
|
|
199
|
+
accumulatedInputRef.current = '';
|
|
200
|
+
return toProcess;
|
|
201
|
+
}
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return null;
|
|
206
|
+
}, [cursor, onChange, isPasteMode, pasteBuffer, finalizePaste]);
|
|
207
|
+
|
|
208
|
+
const handleEscape = useDoublePress(
|
|
209
|
+
(show) => {
|
|
210
|
+
onClearShortcuts?.();
|
|
211
|
+
// If loading and no input, show cancel message instead
|
|
212
|
+
if (isLoading && !value && !pasteInfo) {
|
|
213
|
+
setMessage({
|
|
214
|
+
show: show,
|
|
215
|
+
text: 'Press Escape again to cancel',
|
|
216
|
+
});
|
|
217
|
+
} else {
|
|
218
|
+
const hasContent = !!(value || pasteInfo);
|
|
219
|
+
setMessage({
|
|
220
|
+
show: hasContent && show,
|
|
221
|
+
text: 'Press Escape again to clear',
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
() => {
|
|
226
|
+
// First try to cancel if loading and no input
|
|
227
|
+
if (isLoading && !value && !pasteInfo && onCancel) {
|
|
228
|
+
const cancelled = onCancel();
|
|
229
|
+
if (cancelled) {
|
|
230
|
+
setMessage({ show: false });
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Otherwise clear input/paste as before
|
|
236
|
+
if (value || pasteInfo) {
|
|
237
|
+
onChange('');
|
|
238
|
+
updateSuggestions('');
|
|
239
|
+
setPasteInfo(null);
|
|
240
|
+
setPasteBuffer('');
|
|
241
|
+
setIsPasting(false);
|
|
242
|
+
setOffset(0);
|
|
243
|
+
if (pasteTimeoutRef.current) {
|
|
244
|
+
clearTimeout(pasteTimeoutRef.current);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
function updateSuggestions(newValue: string) {
|
|
251
|
+
if (newValue.startsWith('/') && newValue.length > 0) {
|
|
252
|
+
const input = newValue.slice(1).toLowerCase();
|
|
253
|
+
|
|
254
|
+
if (input.includes(' ')) {
|
|
255
|
+
setSuggestions([]);
|
|
256
|
+
setSelectedSuggestion(-1);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const matchingCommands = commands.filter((cmd) =>
|
|
261
|
+
cmd.name.toLowerCase().startsWith(input)
|
|
262
|
+
);
|
|
263
|
+
const filtered = matchingCommands.map((cmd) => cmd.name);
|
|
264
|
+
setSuggestions(filtered);
|
|
265
|
+
|
|
266
|
+
setSelectedSuggestion(filtered.length > 0 ? 0 : -1);
|
|
267
|
+
} else {
|
|
268
|
+
setSuggestions([]);
|
|
269
|
+
setSelectedSuggestion(-1);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const clearSuggestions = useCallback(() => {
|
|
274
|
+
setSuggestions([]);
|
|
275
|
+
setSelectedSuggestion(-1);
|
|
276
|
+
}, []);
|
|
277
|
+
|
|
278
|
+
useInput((inputChar, key) => {
|
|
279
|
+
// Don't handle input when disabled (e.g., when a modal is open)
|
|
280
|
+
if (disabled) {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const isRestrictedMode = pasteInfo?.isSummarized || false;
|
|
285
|
+
|
|
286
|
+
if (key.escape) {
|
|
287
|
+
handleEscape();
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (key.delete || inputChar === '\x7f' || inputChar === '\x08') {
|
|
292
|
+
if (isRestrictedMode && pasteInfo) {
|
|
293
|
+
if (cursor.offset <= 0 && pasteInfo.userInput.length === 0) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (pasteInfo.userInput.length > 0) {
|
|
297
|
+
const newUserInput = pasteInfo.userInput.slice(0, -1);
|
|
298
|
+
setPasteInfo({
|
|
299
|
+
...pasteInfo,
|
|
300
|
+
userInput: newUserInput,
|
|
301
|
+
});
|
|
302
|
+
onChange(newUserInput);
|
|
303
|
+
setOffset(newUserInput.length);
|
|
304
|
+
}
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const newCursor = cursor.backspace();
|
|
309
|
+
onChange(newCursor.text);
|
|
310
|
+
setOffset(newCursor.offset);
|
|
311
|
+
updateSuggestions(newCursor.text);
|
|
312
|
+
setMessage({ show: false });
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const cursorCommand = detectCursorCommand(inputChar, key);
|
|
317
|
+
|
|
318
|
+
if (
|
|
319
|
+
cursorCommand &&
|
|
320
|
+
(suggestions.length === 0 ||
|
|
321
|
+
cursorCommand === 'deleteBackward' ||
|
|
322
|
+
cursorCommand === 'deleteForward' ||
|
|
323
|
+
cursorCommand.startsWith('select'))
|
|
324
|
+
) {
|
|
325
|
+
let newCursor: Cursor = cursor;
|
|
326
|
+
|
|
327
|
+
switch (cursorCommand) {
|
|
328
|
+
case 'moveLeft':
|
|
329
|
+
if (isRestrictedMode) {
|
|
330
|
+
if (cursor.offset <= 0) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
newCursor = cursor.left();
|
|
335
|
+
setSelection(null);
|
|
336
|
+
break;
|
|
337
|
+
case 'moveRight':
|
|
338
|
+
newCursor = cursor.right();
|
|
339
|
+
setSelection(null);
|
|
340
|
+
break;
|
|
341
|
+
case 'moveToStart':
|
|
342
|
+
if (isRestrictedMode) {
|
|
343
|
+
newCursor = new Cursor(value, 0);
|
|
344
|
+
} else {
|
|
345
|
+
newCursor = cursor.startOfLine();
|
|
346
|
+
}
|
|
347
|
+
setSelection(null);
|
|
348
|
+
break;
|
|
349
|
+
case 'moveToEnd':
|
|
350
|
+
newCursor = cursor.endOfLine();
|
|
351
|
+
setSelection(null);
|
|
352
|
+
break;
|
|
353
|
+
case 'moveWordLeft':
|
|
354
|
+
newCursor = cursor.prevWord();
|
|
355
|
+
if (isRestrictedMode && pasteInfo) {
|
|
356
|
+
if (newCursor.offset < 0) {
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
setSelection(null);
|
|
361
|
+
break;
|
|
362
|
+
case 'moveWordRight':
|
|
363
|
+
newCursor = cursor.nextWord();
|
|
364
|
+
setSelection(null);
|
|
365
|
+
break;
|
|
366
|
+
case 'selectLeft':
|
|
367
|
+
newCursor = cursor.left();
|
|
368
|
+
break;
|
|
369
|
+
case 'selectRight':
|
|
370
|
+
newCursor = cursor.right();
|
|
371
|
+
break;
|
|
372
|
+
case 'selectToStart':
|
|
373
|
+
newCursor = cursor.startOfLine();
|
|
374
|
+
break;
|
|
375
|
+
case 'selectToEnd':
|
|
376
|
+
newCursor = cursor.endOfLine();
|
|
377
|
+
break;
|
|
378
|
+
case 'selectWordLeft':
|
|
379
|
+
newCursor = cursor.prevWord();
|
|
380
|
+
break;
|
|
381
|
+
case 'selectWordRight':
|
|
382
|
+
newCursor = cursor.nextWord();
|
|
383
|
+
break;
|
|
384
|
+
case 'deleteBackward':
|
|
385
|
+
if (isRestrictedMode && pasteInfo) {
|
|
386
|
+
if (cursor.offset <= 0 && pasteInfo.userInput.length === 0) {
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
if (pasteInfo.userInput.length > 0) {
|
|
390
|
+
const newUserInput = pasteInfo.userInput.slice(0, -1);
|
|
391
|
+
setPasteInfo({
|
|
392
|
+
...pasteInfo,
|
|
393
|
+
userInput: newUserInput,
|
|
394
|
+
});
|
|
395
|
+
onChange(newUserInput);
|
|
396
|
+
setOffset(newUserInput.length);
|
|
397
|
+
}
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
newCursor = cursor.backspace();
|
|
401
|
+
onChange(newCursor.text);
|
|
402
|
+
setOffset(newCursor.offset);
|
|
403
|
+
updateSuggestions(newCursor.text);
|
|
404
|
+
setMessage({ show: false });
|
|
405
|
+
return;
|
|
406
|
+
case 'deleteForward': {
|
|
407
|
+
const now = Date.now();
|
|
408
|
+
if (now - lastDeleteTimeRef.current < 50) {
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
lastDeleteTimeRef.current = now;
|
|
412
|
+
newCursor = cursor.del();
|
|
413
|
+
onChange(newCursor.text);
|
|
414
|
+
setOffset(newCursor.offset);
|
|
415
|
+
updateSuggestions(newCursor.text);
|
|
416
|
+
setMessage({ show: false });
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
default:
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
setOffset(newCursor.offset);
|
|
424
|
+
setSelection(null);
|
|
425
|
+
setMessage({ show: false });
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (key.upArrow) {
|
|
430
|
+
if (suggestions.length > 0) {
|
|
431
|
+
setSelectedSuggestion((prev) =>
|
|
432
|
+
prev <= 0 ? suggestions.length - 1 : prev - 1
|
|
433
|
+
);
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
if (isRestrictedMode) {
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
const newCursor = cursor.up();
|
|
440
|
+
setOffset(newCursor.offset);
|
|
441
|
+
onChange(newCursor.text);
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (key.downArrow) {
|
|
446
|
+
if (suggestions.length > 0) {
|
|
447
|
+
setSelectedSuggestion((prev) =>
|
|
448
|
+
prev >= suggestions.length - 1 ? 0 : prev + 1
|
|
449
|
+
);
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
const newCursor = cursor.down();
|
|
453
|
+
setOffset(newCursor.offset);
|
|
454
|
+
onChange(newCursor.text);
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Handle other navigation when suggestions are present
|
|
459
|
+
if (suggestions.length > 0) {
|
|
460
|
+
if (key.tab || (key.return && selectedSuggestion >= 0)) {
|
|
461
|
+
const suggestionIndex =
|
|
462
|
+
selectedSuggestion >= 0 ? selectedSuggestion : 0;
|
|
463
|
+
const suggestion = suggestions[suggestionIndex];
|
|
464
|
+
if (suggestion) {
|
|
465
|
+
const input = '/' + suggestion + ' ';
|
|
466
|
+
onChange(input);
|
|
467
|
+
setSuggestions([]);
|
|
468
|
+
setSelectedSuggestion(-1);
|
|
469
|
+
|
|
470
|
+
if (key.return) {
|
|
471
|
+
onSubmit(input, true);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Handle return
|
|
479
|
+
if (key.return) {
|
|
480
|
+
// Reconstruct full content if we have summarized pastes
|
|
481
|
+
let submitValue = value;
|
|
482
|
+
if (pasteInfo?.isSummarized) {
|
|
483
|
+
const totalPasteContent = pasteInfo.pastes
|
|
484
|
+
.map((p) => p.content)
|
|
485
|
+
.join('');
|
|
486
|
+
submitValue = totalPasteContent + pasteInfo.userInput;
|
|
487
|
+
}
|
|
488
|
+
if (submitValue.trim()) {
|
|
489
|
+
onSubmit(submitValue.trim());
|
|
490
|
+
setPasteInfo(null);
|
|
491
|
+
}
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Handle ? shortcut for shortcuts (only if first character, don't add to input)
|
|
496
|
+
if (inputChar === '?' && !key.ctrl && !key.meta && value === '') {
|
|
497
|
+
onToggleShortcuts?.();
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Handle regular character input (including pasted text)
|
|
502
|
+
// Note: Terminal may chunk long pastes at ~50 character boundaries
|
|
503
|
+
if (
|
|
504
|
+
inputChar &&
|
|
505
|
+
!key.ctrl &&
|
|
506
|
+
!key.escape &&
|
|
507
|
+
!key.tab &&
|
|
508
|
+
!key.return &&
|
|
509
|
+
!key.upArrow &&
|
|
510
|
+
!key.downArrow &&
|
|
511
|
+
!key.leftArrow &&
|
|
512
|
+
!key.rightArrow &&
|
|
513
|
+
!key.backspace &&
|
|
514
|
+
!key.delete
|
|
515
|
+
) {
|
|
516
|
+
const now = Date.now();
|
|
517
|
+
const timeSinceLastInput = now - lastInputTimeRef.current;
|
|
518
|
+
lastInputTimeRef.current = now;
|
|
519
|
+
|
|
520
|
+
// Check if we're in the middle of a bracketed paste FIRST (use ref for immediate check)
|
|
521
|
+
if (isPasteModeRef.current && !inputChar.includes('[201~')) {
|
|
522
|
+
const newBuffer = pasteBufferRef.current + inputChar;
|
|
523
|
+
pasteBufferRef.current = newBuffer;
|
|
524
|
+
setPasteBuffer(newBuffer);
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Then check if input contains bracketed paste sequences
|
|
529
|
+
if (inputChar.includes('[200~') || inputChar.includes('[201~')) {
|
|
530
|
+
const startIdx = inputChar.indexOf('[200~');
|
|
531
|
+
const endIdx = inputChar.indexOf('[201~');
|
|
532
|
+
|
|
533
|
+
if (startIdx !== -1 && endIdx !== -1) {
|
|
534
|
+
// Complete paste in one chunk
|
|
535
|
+
const pasteContent = inputChar.substring(startIdx + 5, endIdx);
|
|
536
|
+
finalizePaste(pasteContent);
|
|
537
|
+
return;
|
|
538
|
+
} else if (startIdx !== -1) {
|
|
539
|
+
// Paste started, accumulate
|
|
540
|
+
const pasteContent = inputChar.substring(startIdx + 5);
|
|
541
|
+
pasteBufferRef.current = pasteContent; // Update ref immediately
|
|
542
|
+
setPasteBuffer(pasteContent);
|
|
543
|
+
setIsPasting(true);
|
|
544
|
+
isPasteModeRef.current = true; // Update ref immediately
|
|
545
|
+
setIsPasteMode(true);
|
|
546
|
+
return;
|
|
547
|
+
} else if (endIdx !== -1 && (isPasting || isPasteModeRef.current)) {
|
|
548
|
+
// Paste ending
|
|
549
|
+
const beforeEndMarker = inputChar.substring(0, endIdx);
|
|
550
|
+
const pasteContent = pasteBufferRef.current + beforeEndMarker;
|
|
551
|
+
finalizePaste(pasteContent);
|
|
552
|
+
setIsPasting(false);
|
|
553
|
+
setPasteBuffer('');
|
|
554
|
+
pasteBufferRef.current = ''; // Reset ref
|
|
555
|
+
setIsPasteMode(false);
|
|
556
|
+
isPasteModeRef.current = false; // Reset ref
|
|
557
|
+
// Clear any pending timeout
|
|
558
|
+
if (pasteTimeoutRef.current) {
|
|
559
|
+
clearTimeout(pasteTimeoutRef.current);
|
|
560
|
+
pasteTimeoutRef.current = null;
|
|
561
|
+
}
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// If we already have a summarized paste and this is not a paste, handle as user input
|
|
567
|
+
if (
|
|
568
|
+
pasteInfo?.isSummarized &&
|
|
569
|
+
timeSinceLastInput > 50 &&
|
|
570
|
+
!inputChar.includes('\n') &&
|
|
571
|
+
!inputChar.includes('\r')
|
|
572
|
+
) {
|
|
573
|
+
const newUserInput = pasteInfo.userInput + inputChar;
|
|
574
|
+
setPasteInfo({
|
|
575
|
+
...pasteInfo,
|
|
576
|
+
userInput: newUserInput,
|
|
577
|
+
});
|
|
578
|
+
// Only show user input when summarized
|
|
579
|
+
onChange(newUserInput);
|
|
580
|
+
setOffset(newUserInput.length);
|
|
581
|
+
setMessage({ show: false });
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// Fallback: timeout-based detection for rapid input (only if not in bracketed paste mode)
|
|
586
|
+
if (
|
|
587
|
+
!isPasteModeRef.current &&
|
|
588
|
+
(timeSinceLastInput < 50 ||
|
|
589
|
+
inputChar.includes('\n') ||
|
|
590
|
+
inputChar.includes('\r'))
|
|
591
|
+
) {
|
|
592
|
+
// Likely part of a paste
|
|
593
|
+
const newBuffer = pasteBuffer + inputChar;
|
|
594
|
+
setPasteBuffer(newBuffer);
|
|
595
|
+
setIsPasting(true);
|
|
596
|
+
|
|
597
|
+
if (pasteTimeoutRef.current) {
|
|
598
|
+
clearTimeout(pasteTimeoutRef.current);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
pasteTimeoutRef.current = setTimeout(() => {
|
|
602
|
+
finalizePaste(newBuffer);
|
|
603
|
+
setIsPasting(false);
|
|
604
|
+
setPasteBuffer('');
|
|
605
|
+
accumulatedInputRef.current = ''; // Clear accumulator after paste
|
|
606
|
+
}, PASTE_TIMEOUT);
|
|
607
|
+
} else {
|
|
608
|
+
// Normal typing - but skip if we have a summarized paste to avoid garbled text
|
|
609
|
+
if (pasteInfo?.isSummarized) {
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
const newCursor = cursor.insert(inputChar);
|
|
614
|
+
onChange(newCursor.text);
|
|
615
|
+
setOffset(newCursor.offset);
|
|
616
|
+
setSelection(null);
|
|
617
|
+
updateSuggestions(newCursor.text);
|
|
618
|
+
setMessage({ show: false });
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
return {
|
|
624
|
+
suggestions,
|
|
625
|
+
selectedSuggestion,
|
|
626
|
+
message,
|
|
627
|
+
clearSuggestions,
|
|
628
|
+
cursor: offset,
|
|
629
|
+
selection,
|
|
630
|
+
pasteInfo,
|
|
631
|
+
};
|
|
632
|
+
}
|
|
Binary file
|