wave-code 0.8.3 → 0.9.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.
- package/dist/components/ChatInterface.d.ts.map +1 -1
- package/dist/components/ChatInterface.js +26 -7
- package/dist/components/HelpView.d.ts.map +1 -1
- package/dist/components/HelpView.js +2 -0
- package/dist/components/InputBox.js +4 -3
- package/dist/components/MessageBlockItem.d.ts.map +1 -1
- package/dist/components/MessageBlockItem.js +1 -1
- package/dist/components/MessageList.d.ts +2 -1
- package/dist/components/MessageList.d.ts.map +1 -1
- package/dist/components/MessageList.js +14 -4
- package/dist/components/QueuedMessageList.d.ts +3 -0
- package/dist/components/QueuedMessageList.d.ts.map +1 -0
- package/dist/components/QueuedMessageList.js +17 -0
- package/dist/components/ReasoningDisplay.d.ts +1 -0
- package/dist/components/ReasoningDisplay.d.ts.map +1 -1
- package/dist/components/ReasoningDisplay.js +3 -3
- package/dist/components/RewindCommand.d.ts.map +1 -1
- package/dist/components/RewindCommand.js +10 -4
- package/dist/components/ToolDisplay.js +1 -1
- package/dist/contexts/useChat.d.ts +7 -0
- package/dist/contexts/useChat.d.ts.map +1 -1
- package/dist/contexts/useChat.js +17 -1
- package/dist/hooks/useInputManager.d.ts +3 -3
- package/dist/hooks/useInputManager.d.ts.map +1 -1
- package/dist/hooks/useInputManager.js +10 -9
- package/dist/managers/inputHandlers.d.ts +7 -4
- package/dist/managers/inputHandlers.d.ts.map +1 -1
- package/dist/managers/inputHandlers.js +165 -42
- package/package.json +2 -2
- package/src/components/ChatInterface.tsx +42 -15
- package/src/components/HelpView.tsx +2 -0
- package/src/components/InputBox.tsx +17 -17
- package/src/components/MessageBlockItem.tsx +8 -3
- package/src/components/MessageList.tsx +16 -3
- package/src/components/QueuedMessageList.tsx +31 -0
- package/src/components/ReasoningDisplay.tsx +8 -2
- package/src/components/RewindCommand.tsx +21 -5
- package/src/components/ToolDisplay.tsx +2 -2
- package/src/contexts/useChat.tsx +29 -1
- package/src/hooks/useInputManager.ts +9 -21
- package/src/managers/inputHandlers.ts +197 -56
|
@@ -35,6 +35,7 @@ export const RewindCommand: React.FC<RewindCommandProps> = ({
|
|
|
35
35
|
.map((msg, index) => ({ msg, index }))
|
|
36
36
|
.filter(({ msg }) => msg.role === "user");
|
|
37
37
|
|
|
38
|
+
const MAX_VISIBLE_ITEMS = 3;
|
|
38
39
|
const [selectedIndex, setSelectedIndex] = useState(checkpoints.length - 1);
|
|
39
40
|
|
|
40
41
|
// Update selectedIndex when checkpoints change (after loading full thread)
|
|
@@ -42,6 +43,19 @@ export const RewindCommand: React.FC<RewindCommandProps> = ({
|
|
|
42
43
|
setSelectedIndex(checkpoints.length - 1);
|
|
43
44
|
}, [checkpoints.length]);
|
|
44
45
|
|
|
46
|
+
// Calculate visible window
|
|
47
|
+
const startIndex = Math.max(
|
|
48
|
+
0,
|
|
49
|
+
Math.min(
|
|
50
|
+
selectedIndex - Math.floor(MAX_VISIBLE_ITEMS / 2),
|
|
51
|
+
Math.max(0, checkpoints.length - MAX_VISIBLE_ITEMS),
|
|
52
|
+
),
|
|
53
|
+
);
|
|
54
|
+
const visibleCheckpoints = checkpoints.slice(
|
|
55
|
+
startIndex,
|
|
56
|
+
startIndex + MAX_VISIBLE_ITEMS,
|
|
57
|
+
);
|
|
58
|
+
|
|
45
59
|
useInput((input, key) => {
|
|
46
60
|
if (key.return) {
|
|
47
61
|
if (checkpoints.length > 0 && selectedIndex >= 0) {
|
|
@@ -114,12 +128,14 @@ export const RewindCommand: React.FC<RewindCommandProps> = ({
|
|
|
114
128
|
</Box>
|
|
115
129
|
|
|
116
130
|
<Box flexDirection="column">
|
|
117
|
-
{
|
|
118
|
-
const
|
|
131
|
+
{visibleCheckpoints.map((checkpoint, index) => {
|
|
132
|
+
const actualIndex = startIndex + index;
|
|
133
|
+
const isSelected = actualIndex === selectedIndex;
|
|
119
134
|
const content = checkpoint.msg.blocks
|
|
120
135
|
.filter((b): b is TextBlock => b.type === "text")
|
|
121
136
|
.map((b) => b.content)
|
|
122
137
|
.join(" ")
|
|
138
|
+
.replace(/\n/g, "\\n")
|
|
123
139
|
.substring(0, 60);
|
|
124
140
|
|
|
125
141
|
return (
|
|
@@ -130,7 +146,7 @@ export const RewindCommand: React.FC<RewindCommandProps> = ({
|
|
|
130
146
|
>
|
|
131
147
|
{isSelected ? "▶ " : " "}[{checkpoint.index}]{" "}
|
|
132
148
|
{content || "(No text content)"}
|
|
133
|
-
{
|
|
149
|
+
{actualIndex === checkpoints.length - 1 ? " (Latest)" : ""}
|
|
134
150
|
</Text>
|
|
135
151
|
</Box>
|
|
136
152
|
);
|
|
@@ -142,8 +158,8 @@ export const RewindCommand: React.FC<RewindCommandProps> = ({
|
|
|
142
158
|
</Box>
|
|
143
159
|
<Box>
|
|
144
160
|
<Text color="red" dimColor>
|
|
145
|
-
|
|
146
|
-
|
|
161
|
+
Warning: This will delete all subsequent messages and revert file and
|
|
162
|
+
task list changes.
|
|
147
163
|
</Text>
|
|
148
164
|
</Box>
|
|
149
165
|
</Box>
|
|
@@ -129,8 +129,8 @@ export const ToolDisplay: React.FC<ToolDisplayProps> = ({
|
|
|
129
129
|
</Box>
|
|
130
130
|
)}
|
|
131
131
|
|
|
132
|
-
{/* Diff display - only show after tool execution completes and was successful */}
|
|
133
|
-
{stage === "end" && success && (
|
|
132
|
+
{/* Diff display - only show after tool execution completes and was successful, and NOT in expanded mode */}
|
|
133
|
+
{!isExpanded && stage === "end" && success && (
|
|
134
134
|
<DiffDisplay
|
|
135
135
|
toolName={name}
|
|
136
136
|
parameters={parameters}
|
package/src/contexts/useChat.tsx
CHANGED
|
@@ -37,6 +37,10 @@ export interface ChatContextType {
|
|
|
37
37
|
isExpanded: boolean;
|
|
38
38
|
isTaskListVisible: boolean;
|
|
39
39
|
setIsTaskListVisible: (visible: boolean) => void;
|
|
40
|
+
queuedMessages: Array<{
|
|
41
|
+
content: string;
|
|
42
|
+
images?: Array<{ path: string; mimeType: string }>;
|
|
43
|
+
}>;
|
|
40
44
|
// AI functionality
|
|
41
45
|
sessionId: string;
|
|
42
46
|
sendMessage: (
|
|
@@ -139,6 +143,13 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
139
143
|
|
|
140
144
|
const [isTaskListVisible, setIsTaskListVisible] = useState(true);
|
|
141
145
|
|
|
146
|
+
const [queuedMessages, setQueuedMessages] = useState<
|
|
147
|
+
Array<{
|
|
148
|
+
content: string;
|
|
149
|
+
images?: Array<{ path: string; mimeType: string }>;
|
|
150
|
+
}>
|
|
151
|
+
>([]);
|
|
152
|
+
|
|
142
153
|
// AI State
|
|
143
154
|
const [messages, setMessages] = useState<Message[]>([]);
|
|
144
155
|
|
|
@@ -393,6 +404,11 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
393
404
|
|
|
394
405
|
if (!hasTextContent && !hasImageAttachments) return;
|
|
395
406
|
|
|
407
|
+
if (isLoading || isCommandRunning) {
|
|
408
|
+
setQueuedMessages((prev) => [...prev, { content, images }]);
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
|
|
396
412
|
try {
|
|
397
413
|
// Handle bash mode - check if it's a bash command (starts with ! and only one line)
|
|
398
414
|
if (content.startsWith("!") && !content.includes("\n")) {
|
|
@@ -432,15 +448,26 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
432
448
|
// Loading state will be automatically updated by the useEffect that watches messages
|
|
433
449
|
}
|
|
434
450
|
},
|
|
435
|
-
[],
|
|
451
|
+
[isLoading, isCommandRunning],
|
|
436
452
|
);
|
|
437
453
|
|
|
454
|
+
// Process queued messages when idle
|
|
455
|
+
useEffect(() => {
|
|
456
|
+
if (!isLoading && !isCommandRunning && queuedMessages.length > 0) {
|
|
457
|
+
const nextMessage = queuedMessages[0];
|
|
458
|
+
setQueuedMessages((prev) => prev.slice(1));
|
|
459
|
+
sendMessage(nextMessage.content, nextMessage.images);
|
|
460
|
+
}
|
|
461
|
+
}, [isLoading, isCommandRunning, queuedMessages, sendMessage]);
|
|
462
|
+
|
|
438
463
|
// Unified interrupt method, interrupt both AI messages and command execution
|
|
439
464
|
const abortMessage = useCallback(() => {
|
|
465
|
+
setQueuedMessages([]);
|
|
440
466
|
agentRef.current?.abortMessage();
|
|
441
467
|
}, []);
|
|
442
468
|
|
|
443
469
|
const clearMessages = useCallback(() => {
|
|
470
|
+
setQueuedMessages([]);
|
|
444
471
|
agentRef.current?.clearMessages();
|
|
445
472
|
}, []);
|
|
446
473
|
|
|
@@ -604,6 +631,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
604
631
|
isExpanded,
|
|
605
632
|
isTaskListVisible,
|
|
606
633
|
setIsTaskListVisible,
|
|
634
|
+
queuedMessages,
|
|
607
635
|
sessionId,
|
|
608
636
|
sendMessage,
|
|
609
637
|
abortMessage,
|
|
@@ -204,14 +204,16 @@ export const useInputManager = (
|
|
|
204
204
|
const handleCommandInsert = useCallback((command: string) => {
|
|
205
205
|
const currentState = stateRef.current;
|
|
206
206
|
if (currentState.slashPosition >= 0) {
|
|
207
|
+
const wordEnd = handlers.getWordEnd(
|
|
208
|
+
currentState.inputText,
|
|
209
|
+
currentState.slashPosition,
|
|
210
|
+
);
|
|
207
211
|
const beforeSlash = currentState.inputText.substring(
|
|
208
212
|
0,
|
|
209
213
|
currentState.slashPosition,
|
|
210
214
|
);
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
);
|
|
214
|
-
const newInput = beforeSlash + `/${command} ` + afterQuery;
|
|
215
|
+
const afterWord = currentState.inputText.substring(wordEnd);
|
|
216
|
+
const newInput = beforeSlash + `/${command} ` + afterWord;
|
|
215
217
|
const newCursorPosition = beforeSlash.length + command.length + 2;
|
|
216
218
|
|
|
217
219
|
dispatch({ type: "SET_INPUT_TEXT", payload: newInput });
|
|
@@ -255,14 +257,8 @@ export const useInputManager = (
|
|
|
255
257
|
dispatch({ type: "CANCEL_HISTORY_SEARCH" });
|
|
256
258
|
}, []);
|
|
257
259
|
|
|
258
|
-
const
|
|
259
|
-
handlers.
|
|
260
|
-
stateRef.current,
|
|
261
|
-
dispatch,
|
|
262
|
-
char,
|
|
263
|
-
stateRef.current.cursorPosition,
|
|
264
|
-
stateRef.current.inputText,
|
|
265
|
-
);
|
|
260
|
+
const processSelectorInput = useCallback((char: string) => {
|
|
261
|
+
handlers.processSelectorInput(stateRef.current, dispatch, char);
|
|
266
262
|
}, []);
|
|
267
263
|
|
|
268
264
|
const setInputText = useCallback((text: string) => {
|
|
@@ -326,15 +322,11 @@ export const useInputManager = (
|
|
|
326
322
|
const handleSubmit = useCallback(
|
|
327
323
|
async (
|
|
328
324
|
attachedImages: Array<{ id: number; path: string; mimeType: string }>,
|
|
329
|
-
isLoading: boolean = false,
|
|
330
|
-
isCommandRunning: boolean = false,
|
|
331
325
|
) => {
|
|
332
326
|
await handlers.handleSubmit(
|
|
333
327
|
stateRef.current,
|
|
334
328
|
dispatch,
|
|
335
329
|
callbacksRef.current,
|
|
336
|
-
isLoading,
|
|
337
|
-
isCommandRunning,
|
|
338
330
|
attachedImages,
|
|
339
331
|
);
|
|
340
332
|
},
|
|
@@ -357,8 +349,6 @@ export const useInputManager = (
|
|
|
357
349
|
input: string,
|
|
358
350
|
key: Key,
|
|
359
351
|
attachedImages: Array<{ id: number; path: string; mimeType: string }>,
|
|
360
|
-
isLoading: boolean = false,
|
|
361
|
-
isCommandRunning: boolean = false,
|
|
362
352
|
clearImages?: () => void,
|
|
363
353
|
) => {
|
|
364
354
|
return await handlers.handleInput(
|
|
@@ -367,8 +357,6 @@ export const useInputManager = (
|
|
|
367
357
|
callbacksRef.current,
|
|
368
358
|
input,
|
|
369
359
|
key,
|
|
370
|
-
isLoading,
|
|
371
|
-
isCommandRunning,
|
|
372
360
|
clearImages,
|
|
373
361
|
);
|
|
374
362
|
},
|
|
@@ -424,7 +412,7 @@ export const useInputManager = (
|
|
|
424
412
|
handleCancelHistorySearch,
|
|
425
413
|
|
|
426
414
|
// Special handling
|
|
427
|
-
|
|
415
|
+
processSelectorInput,
|
|
428
416
|
|
|
429
417
|
// Bash/MCP Manager
|
|
430
418
|
setShowBackgroundTaskManager,
|
|
@@ -30,18 +30,12 @@ export const handleSubmit = async (
|
|
|
30
30
|
state: InputState,
|
|
31
31
|
dispatch: React.Dispatch<InputAction>,
|
|
32
32
|
callbacks: Partial<InputManagerCallbacks>,
|
|
33
|
-
isLoading: boolean = false,
|
|
34
|
-
isCommandRunning: boolean = false,
|
|
35
33
|
attachedImagesOverride?: Array<{
|
|
36
34
|
id: number;
|
|
37
35
|
path: string;
|
|
38
36
|
mimeType: string;
|
|
39
37
|
}>,
|
|
40
38
|
): Promise<void> => {
|
|
41
|
-
if (isLoading || isCommandRunning) {
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
39
|
if (state.inputText.trim()) {
|
|
46
40
|
const imageRegex = /\[Image #(\d+)\]/g;
|
|
47
41
|
const matches = [...state.inputText.matchAll(imageRegex)];
|
|
@@ -114,6 +108,72 @@ export const cyclePermissionMode = (
|
|
|
114
108
|
callbacks.onPermissionModeChange?.(nextMode);
|
|
115
109
|
};
|
|
116
110
|
|
|
111
|
+
const SELECTOR_TRIGGERS = [
|
|
112
|
+
{
|
|
113
|
+
char: "@",
|
|
114
|
+
type: "ACTIVATE_FILE_SELECTOR",
|
|
115
|
+
shouldActivate: (char: string, pos: number, text: string) =>
|
|
116
|
+
char === "@" && (pos === 1 || /\s/.test(text[pos - 2])),
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
char: "/",
|
|
120
|
+
type: "ACTIVATE_COMMAND_SELECTOR",
|
|
121
|
+
shouldActivate: (
|
|
122
|
+
char: string,
|
|
123
|
+
pos: number,
|
|
124
|
+
text: string,
|
|
125
|
+
state: InputState,
|
|
126
|
+
) =>
|
|
127
|
+
char === "/" &&
|
|
128
|
+
!state.showFileSelector &&
|
|
129
|
+
(pos === 1 || /\s/.test(text[pos - 2])),
|
|
130
|
+
},
|
|
131
|
+
] as const;
|
|
132
|
+
|
|
133
|
+
const getProjectedState = (state: InputState, char: string) => {
|
|
134
|
+
const beforeCursor = state.inputText.substring(0, state.cursorPosition);
|
|
135
|
+
const afterCursor = state.inputText.substring(state.cursorPosition);
|
|
136
|
+
const newInputText = beforeCursor + char + afterCursor;
|
|
137
|
+
const newCursorPosition = state.cursorPosition + char.length;
|
|
138
|
+
return { newInputText, newCursorPosition };
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
export const getAtSelectorPosition = (
|
|
142
|
+
text: string,
|
|
143
|
+
cursorPosition: number,
|
|
144
|
+
): number => {
|
|
145
|
+
let i = cursorPosition - 1;
|
|
146
|
+
while (i >= 0 && !/\s/.test(text[i])) {
|
|
147
|
+
if (text[i] === "@") {
|
|
148
|
+
// Check if this @ is at the start or preceded by whitespace
|
|
149
|
+
if (i === 0 || /\s/.test(text[i - 1])) {
|
|
150
|
+
return i;
|
|
151
|
+
}
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
i--;
|
|
155
|
+
}
|
|
156
|
+
return -1;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
export const getSlashSelectorPosition = (
|
|
160
|
+
text: string,
|
|
161
|
+
cursorPosition: number,
|
|
162
|
+
): number => {
|
|
163
|
+
let i = cursorPosition - 1;
|
|
164
|
+
while (i >= 0 && !/\s/.test(text[i])) {
|
|
165
|
+
if (text[i] === "/") {
|
|
166
|
+
// Check if this / is at the start or preceded by whitespace
|
|
167
|
+
if (i === 0 || /\s/.test(text[i - 1])) {
|
|
168
|
+
return i;
|
|
169
|
+
}
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
i--;
|
|
173
|
+
}
|
|
174
|
+
return -1;
|
|
175
|
+
};
|
|
176
|
+
|
|
117
177
|
export const updateSearchQueriesForActiveSelectors = (
|
|
118
178
|
state: InputState,
|
|
119
179
|
dispatch: React.Dispatch<InputAction>,
|
|
@@ -133,26 +193,58 @@ export const updateSearchQueriesForActiveSelectors = (
|
|
|
133
193
|
}
|
|
134
194
|
};
|
|
135
195
|
|
|
136
|
-
export const
|
|
196
|
+
export const processSelectorInput = (
|
|
137
197
|
state: InputState,
|
|
138
198
|
dispatch: React.Dispatch<InputAction>,
|
|
139
199
|
char: string,
|
|
140
|
-
cursorPosition: number,
|
|
141
|
-
inputText: string,
|
|
142
200
|
): void => {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
201
|
+
const { newInputText, newCursorPosition } = getProjectedState(state, char);
|
|
202
|
+
|
|
203
|
+
const trigger = SELECTOR_TRIGGERS.find((t) =>
|
|
204
|
+
t.shouldActivate(char, newCursorPosition, newInputText, state),
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
if (trigger) {
|
|
146
208
|
dispatch({
|
|
147
|
-
type:
|
|
148
|
-
payload:
|
|
149
|
-
});
|
|
209
|
+
type: trigger.type,
|
|
210
|
+
payload: newCursorPosition - 1,
|
|
211
|
+
} as InputAction);
|
|
150
212
|
} else {
|
|
213
|
+
const atPos = getAtSelectorPosition(newInputText, newCursorPosition);
|
|
214
|
+
let showFileSelector = state.showFileSelector;
|
|
215
|
+
let atPosition = state.atPosition;
|
|
216
|
+
if (atPos !== -1 && !state.showFileSelector) {
|
|
217
|
+
dispatch({
|
|
218
|
+
type: "ACTIVATE_FILE_SELECTOR",
|
|
219
|
+
payload: atPos,
|
|
220
|
+
});
|
|
221
|
+
showFileSelector = true;
|
|
222
|
+
atPosition = atPos;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const slashPos = getSlashSelectorPosition(newInputText, newCursorPosition);
|
|
226
|
+
let showCommandSelector = state.showCommandSelector;
|
|
227
|
+
let slashPosition = state.slashPosition;
|
|
228
|
+
if (slashPos !== -1 && !state.showCommandSelector) {
|
|
229
|
+
dispatch({
|
|
230
|
+
type: "ACTIVATE_COMMAND_SELECTOR",
|
|
231
|
+
payload: slashPos,
|
|
232
|
+
});
|
|
233
|
+
showCommandSelector = true;
|
|
234
|
+
slashPosition = slashPos;
|
|
235
|
+
}
|
|
236
|
+
|
|
151
237
|
updateSearchQueriesForActiveSelectors(
|
|
152
|
-
|
|
238
|
+
{
|
|
239
|
+
...state,
|
|
240
|
+
showFileSelector,
|
|
241
|
+
atPosition,
|
|
242
|
+
showCommandSelector,
|
|
243
|
+
slashPosition,
|
|
244
|
+
},
|
|
153
245
|
dispatch,
|
|
154
|
-
|
|
155
|
-
|
|
246
|
+
newInputText,
|
|
247
|
+
newCursorPosition,
|
|
156
248
|
);
|
|
157
249
|
}
|
|
158
250
|
};
|
|
@@ -187,20 +279,16 @@ export const handlePasteInput = (
|
|
|
187
279
|
callbacks.onResetHistoryNavigation?.();
|
|
188
280
|
dispatch({ type: "INSERT_TEXT", payload: char });
|
|
189
281
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
const afterCursor = state.inputText.substring(state.cursorPosition);
|
|
194
|
-
const newInputText = beforeCursor + char + afterCursor;
|
|
282
|
+
processSelectorInput(state, dispatch, char);
|
|
283
|
+
}
|
|
284
|
+
};
|
|
195
285
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
newCursorPosition,
|
|
201
|
-
newInputText,
|
|
202
|
-
);
|
|
286
|
+
export const getWordEnd = (text: string, startPos: number): number => {
|
|
287
|
+
let i = startPos;
|
|
288
|
+
while (i < text.length && !/\s/.test(text[i])) {
|
|
289
|
+
i++;
|
|
203
290
|
}
|
|
291
|
+
return i;
|
|
204
292
|
};
|
|
205
293
|
|
|
206
294
|
export const handleCommandSelect = (
|
|
@@ -210,9 +298,10 @@ export const handleCommandSelect = (
|
|
|
210
298
|
command: string,
|
|
211
299
|
) => {
|
|
212
300
|
if (state.slashPosition >= 0) {
|
|
301
|
+
const wordEnd = getWordEnd(state.inputText, state.slashPosition);
|
|
213
302
|
const beforeSlash = state.inputText.substring(0, state.slashPosition);
|
|
214
|
-
const
|
|
215
|
-
const newInput = beforeSlash +
|
|
303
|
+
const afterWord = state.inputText.substring(wordEnd);
|
|
304
|
+
const newInput = beforeSlash + afterWord;
|
|
216
305
|
const newCursorPosition = beforeSlash.length;
|
|
217
306
|
|
|
218
307
|
dispatch({ type: "SET_INPUT_TEXT", payload: newInput });
|
|
@@ -264,10 +353,11 @@ export const handleFileSelect = (
|
|
|
264
353
|
filePath: string,
|
|
265
354
|
) => {
|
|
266
355
|
if (state.atPosition >= 0) {
|
|
356
|
+
const wordEnd = getWordEnd(state.inputText, state.atPosition);
|
|
267
357
|
const beforeAt = state.inputText.substring(0, state.atPosition);
|
|
268
|
-
const
|
|
269
|
-
const newInput = beforeAt +
|
|
270
|
-
const newCursorPosition = beforeAt.length + filePath.length +
|
|
358
|
+
const afterWord = state.inputText.substring(wordEnd);
|
|
359
|
+
const newInput = beforeAt + `@${filePath} ` + afterWord;
|
|
360
|
+
const newCursorPosition = beforeAt.length + filePath.length + 2;
|
|
271
361
|
|
|
272
362
|
dispatch({ type: "SET_INPUT_TEXT", payload: newInput });
|
|
273
363
|
dispatch({ type: "SET_CURSOR_POSITION", payload: newCursorPosition });
|
|
@@ -343,6 +433,26 @@ export const handleSelectorInput = (
|
|
|
343
433
|
return true;
|
|
344
434
|
}
|
|
345
435
|
|
|
436
|
+
if (key.leftArrow) {
|
|
437
|
+
const newCursorPosition = state.cursorPosition - 1;
|
|
438
|
+
dispatch({ type: "MOVE_CURSOR", payload: -1 });
|
|
439
|
+
checkForAtDeletion(state, dispatch, newCursorPosition);
|
|
440
|
+
checkForSlashDeletion(state, dispatch, newCursorPosition);
|
|
441
|
+
return true;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (key.rightArrow) {
|
|
445
|
+
const newCursorPosition = state.cursorPosition + 1;
|
|
446
|
+
dispatch({ type: "MOVE_CURSOR", payload: 1 });
|
|
447
|
+
checkForAtDeletion(state, dispatch, newCursorPosition);
|
|
448
|
+
checkForSlashDeletion(state, dispatch, newCursorPosition);
|
|
449
|
+
return true;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
if (input === " " && state.showFileSelector) {
|
|
453
|
+
dispatch({ type: "CANCEL_FILE_SELECTOR" });
|
|
454
|
+
}
|
|
455
|
+
|
|
346
456
|
if (
|
|
347
457
|
input &&
|
|
348
458
|
!key.ctrl &&
|
|
@@ -358,19 +468,7 @@ export const handleSelectorInput = (
|
|
|
358
468
|
) {
|
|
359
469
|
dispatch({ type: "INSERT_TEXT", payload: input });
|
|
360
470
|
|
|
361
|
-
|
|
362
|
-
const newCursorPosition = state.cursorPosition + input.length;
|
|
363
|
-
const beforeCursor = state.inputText.substring(0, state.cursorPosition);
|
|
364
|
-
const afterCursor = state.inputText.substring(state.cursorPosition);
|
|
365
|
-
const newInputText = beforeCursor + input + afterCursor;
|
|
366
|
-
|
|
367
|
-
handleSpecialCharInput(
|
|
368
|
-
state,
|
|
369
|
-
dispatch,
|
|
370
|
-
input,
|
|
371
|
-
newCursorPosition,
|
|
372
|
-
newInputText,
|
|
373
|
-
);
|
|
471
|
+
processSelectorInput(state, dispatch, input);
|
|
374
472
|
return true;
|
|
375
473
|
}
|
|
376
474
|
|
|
@@ -383,12 +481,10 @@ export const handleNormalInput = async (
|
|
|
383
481
|
callbacks: Partial<InputManagerCallbacks>,
|
|
384
482
|
input: string,
|
|
385
483
|
key: Key,
|
|
386
|
-
isLoading: boolean = false,
|
|
387
|
-
isCommandRunning: boolean = false,
|
|
388
484
|
clearImages?: () => void,
|
|
389
485
|
): Promise<boolean> => {
|
|
390
486
|
if (key.return) {
|
|
391
|
-
await handleSubmit(state, dispatch, callbacks
|
|
487
|
+
await handleSubmit(state, dispatch, callbacks);
|
|
392
488
|
clearImages?.();
|
|
393
489
|
return true;
|
|
394
490
|
}
|
|
@@ -405,11 +501,61 @@ export const handleNormalInput = async (
|
|
|
405
501
|
if (key.backspace || key.delete) {
|
|
406
502
|
if (state.cursorPosition > 0) {
|
|
407
503
|
const newCursorPosition = state.cursorPosition - 1;
|
|
504
|
+
const beforeCursor = state.inputText.substring(
|
|
505
|
+
0,
|
|
506
|
+
state.cursorPosition - 1,
|
|
507
|
+
);
|
|
508
|
+
const afterCursor = state.inputText.substring(state.cursorPosition);
|
|
509
|
+
const newInputText = beforeCursor + afterCursor;
|
|
510
|
+
|
|
408
511
|
dispatch({ type: "DELETE_CHAR" });
|
|
409
512
|
callbacks.onResetHistoryNavigation?.();
|
|
410
513
|
|
|
411
514
|
checkForAtDeletion(state, dispatch, newCursorPosition);
|
|
412
515
|
checkForSlashDeletion(state, dispatch, newCursorPosition);
|
|
516
|
+
|
|
517
|
+
// Reactivate file selector if cursor is now within an @word
|
|
518
|
+
const atPos = getAtSelectorPosition(newInputText, newCursorPosition);
|
|
519
|
+
let showFileSelector = state.showFileSelector;
|
|
520
|
+
let atPosition = state.atPosition;
|
|
521
|
+
if (atPos !== -1 && !state.showFileSelector) {
|
|
522
|
+
dispatch({
|
|
523
|
+
type: "ACTIVATE_FILE_SELECTOR",
|
|
524
|
+
payload: atPos,
|
|
525
|
+
});
|
|
526
|
+
showFileSelector = true;
|
|
527
|
+
atPosition = atPos;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
const slashPos = getSlashSelectorPosition(
|
|
531
|
+
newInputText,
|
|
532
|
+
newCursorPosition,
|
|
533
|
+
);
|
|
534
|
+
let showCommandSelector = state.showCommandSelector;
|
|
535
|
+
let slashPosition = state.slashPosition;
|
|
536
|
+
if (slashPos !== -1 && !state.showCommandSelector) {
|
|
537
|
+
dispatch({
|
|
538
|
+
type: "ACTIVATE_COMMAND_SELECTOR",
|
|
539
|
+
payload: slashPos,
|
|
540
|
+
});
|
|
541
|
+
showCommandSelector = true;
|
|
542
|
+
slashPosition = slashPos;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
updateSearchQueriesForActiveSelectors(
|
|
546
|
+
{
|
|
547
|
+
...state,
|
|
548
|
+
inputText: newInputText,
|
|
549
|
+
cursorPosition: newCursorPosition,
|
|
550
|
+
showFileSelector,
|
|
551
|
+
atPosition,
|
|
552
|
+
showCommandSelector,
|
|
553
|
+
slashPosition,
|
|
554
|
+
},
|
|
555
|
+
dispatch,
|
|
556
|
+
newInputText,
|
|
557
|
+
newCursorPosition,
|
|
558
|
+
);
|
|
413
559
|
}
|
|
414
560
|
return true;
|
|
415
561
|
}
|
|
@@ -468,8 +614,6 @@ export const handleInput = async (
|
|
|
468
614
|
callbacks: Partial<InputManagerCallbacks>,
|
|
469
615
|
input: string,
|
|
470
616
|
key: Key,
|
|
471
|
-
isLoading: boolean = false,
|
|
472
|
-
isCommandRunning: boolean = false,
|
|
473
617
|
clearImages?: () => void,
|
|
474
618
|
): Promise<boolean> => {
|
|
475
619
|
if (state.selectorJustUsed) {
|
|
@@ -478,7 +622,6 @@ export const handleInput = async (
|
|
|
478
622
|
|
|
479
623
|
if (key.escape) {
|
|
480
624
|
if (
|
|
481
|
-
(isLoading || isCommandRunning) &&
|
|
482
625
|
!(
|
|
483
626
|
state.showFileSelector ||
|
|
484
627
|
state.showCommandSelector ||
|
|
@@ -552,8 +695,6 @@ export const handleInput = async (
|
|
|
552
695
|
callbacks,
|
|
553
696
|
input,
|
|
554
697
|
key,
|
|
555
|
-
isLoading,
|
|
556
|
-
isCommandRunning,
|
|
557
698
|
clearImages,
|
|
558
699
|
);
|
|
559
700
|
}
|