wave-code 0.8.0 → 0.8.2
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.js +1 -1
- package/dist/components/CommandSelector.d.ts.map +1 -1
- package/dist/components/CommandSelector.js +1 -38
- package/dist/components/ConfirmationSelector.d.ts.map +1 -1
- package/dist/components/ConfirmationSelector.js +11 -3
- package/dist/components/HelpView.d.ts +2 -0
- package/dist/components/HelpView.d.ts.map +1 -1
- package/dist/components/HelpView.js +49 -5
- package/dist/components/InputBox.d.ts.map +1 -1
- package/dist/components/InputBox.js +1 -1
- package/dist/components/MessageList.d.ts +2 -2
- package/dist/components/MessageList.d.ts.map +1 -1
- package/dist/components/MessageList.js +5 -6
- package/dist/constants/commands.d.ts +3 -0
- package/dist/constants/commands.d.ts.map +1 -0
- package/dist/constants/commands.js +38 -0
- package/dist/contexts/useChat.d.ts.map +1 -1
- package/dist/contexts/useChat.js +2 -17
- package/dist/hooks/useInputManager.d.ts +7 -8
- package/dist/hooks/useInputManager.d.ts.map +1 -1
- package/dist/hooks/useInputManager.js +224 -232
- package/dist/managers/inputHandlers.d.ts +28 -0
- package/dist/managers/inputHandlers.d.ts.map +1 -0
- package/dist/managers/inputHandlers.js +378 -0
- package/dist/managers/inputReducer.d.ts +157 -0
- package/dist/managers/inputReducer.d.ts.map +1 -0
- package/dist/managers/inputReducer.js +242 -0
- package/dist/utils/highlightUtils.d.ts.map +1 -1
- package/dist/utils/highlightUtils.js +66 -42
- package/package.json +2 -2
- package/src/components/ChatInterface.tsx +1 -1
- package/src/components/CommandSelector.tsx +1 -40
- package/src/components/ConfirmationSelector.tsx +13 -3
- package/src/components/HelpView.tsx +129 -16
- package/src/components/InputBox.tsx +3 -1
- package/src/components/MessageList.tsx +5 -6
- package/src/constants/commands.ts +41 -0
- package/src/contexts/useChat.tsx +2 -17
- package/src/hooks/useInputManager.ts +352 -299
- package/src/managers/inputHandlers.ts +560 -0
- package/src/managers/inputReducer.ts +367 -0
- package/src/utils/highlightUtils.ts +66 -42
- package/dist/managers/InputManager.d.ts +0 -156
- package/dist/managers/InputManager.d.ts.map +0 -1
- package/dist/managers/InputManager.js +0 -749
- package/src/managers/InputManager.ts +0 -1024
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
import { Key } from "ink";
|
|
2
|
+
import { PromptHistoryManager, PermissionMode } from "wave-agent-sdk";
|
|
3
|
+
import { readClipboardImage } from "../utils/clipboard.js";
|
|
4
|
+
import {
|
|
5
|
+
InputState,
|
|
6
|
+
InputAction,
|
|
7
|
+
InputManagerCallbacks,
|
|
8
|
+
} from "./inputReducer.js";
|
|
9
|
+
|
|
10
|
+
export const expandLongTextPlaceholders = (
|
|
11
|
+
text: string,
|
|
12
|
+
longTextMap: Record<string, string>,
|
|
13
|
+
): string => {
|
|
14
|
+
let expandedText = text;
|
|
15
|
+
const longTextRegex = /\[LongText#(\d+)\]/g;
|
|
16
|
+
const matches = [...text.matchAll(longTextRegex)];
|
|
17
|
+
|
|
18
|
+
for (const match of matches) {
|
|
19
|
+
const placeholder = match[0];
|
|
20
|
+
const originalText = longTextMap[placeholder];
|
|
21
|
+
if (originalText) {
|
|
22
|
+
expandedText = expandedText.replace(placeholder, originalText);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return expandedText;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const handleSubmit = async (
|
|
30
|
+
state: InputState,
|
|
31
|
+
dispatch: React.Dispatch<InputAction>,
|
|
32
|
+
callbacks: Partial<InputManagerCallbacks>,
|
|
33
|
+
isLoading: boolean = false,
|
|
34
|
+
isCommandRunning: boolean = false,
|
|
35
|
+
attachedImagesOverride?: Array<{
|
|
36
|
+
id: number;
|
|
37
|
+
path: string;
|
|
38
|
+
mimeType: string;
|
|
39
|
+
}>,
|
|
40
|
+
): Promise<void> => {
|
|
41
|
+
if (isLoading || isCommandRunning) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (state.inputText.trim()) {
|
|
46
|
+
const imageRegex = /\[Image #(\d+)\]/g;
|
|
47
|
+
const matches = [...state.inputText.matchAll(imageRegex)];
|
|
48
|
+
const attachedImages = attachedImagesOverride || state.attachedImages;
|
|
49
|
+
const referencedImages = matches
|
|
50
|
+
.map((match) => {
|
|
51
|
+
const imageId = parseInt(match[1], 10);
|
|
52
|
+
return attachedImages.find((img) => img.id === imageId);
|
|
53
|
+
})
|
|
54
|
+
.filter(
|
|
55
|
+
(img): img is { id: number; path: string; mimeType: string } =>
|
|
56
|
+
img !== undefined,
|
|
57
|
+
)
|
|
58
|
+
.map((img) => ({ path: img.path, mimeType: img.mimeType }));
|
|
59
|
+
|
|
60
|
+
let cleanContent = state.inputText.replace(imageRegex, "").trim();
|
|
61
|
+
cleanContent = expandLongTextPlaceholders(cleanContent, state.longTextMap);
|
|
62
|
+
|
|
63
|
+
PromptHistoryManager.addEntry(cleanContent).catch((err: unknown) => {
|
|
64
|
+
callbacks.logger?.error("Failed to save prompt history", err);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
callbacks.onSendMessage?.(
|
|
68
|
+
cleanContent,
|
|
69
|
+
referencedImages.length > 0 ? referencedImages : undefined,
|
|
70
|
+
);
|
|
71
|
+
dispatch({ type: "CLEAR_INPUT" });
|
|
72
|
+
callbacks.onResetHistoryNavigation?.();
|
|
73
|
+
dispatch({ type: "CLEAR_LONG_TEXT_MAP" });
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const handlePasteImage = async (
|
|
78
|
+
dispatch: React.Dispatch<InputAction>,
|
|
79
|
+
): Promise<boolean> => {
|
|
80
|
+
try {
|
|
81
|
+
const result = await readClipboardImage();
|
|
82
|
+
|
|
83
|
+
if (result.success && result.imagePath && result.mimeType) {
|
|
84
|
+
dispatch({
|
|
85
|
+
type: "ADD_IMAGE_AND_INSERT_PLACEHOLDER",
|
|
86
|
+
payload: { path: result.imagePath, mimeType: result.mimeType },
|
|
87
|
+
});
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return false;
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.warn("Failed to paste image from clipboard:", error);
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export const cyclePermissionMode = (
|
|
99
|
+
currentMode: PermissionMode,
|
|
100
|
+
dispatch: React.Dispatch<InputAction>,
|
|
101
|
+
callbacks: Partial<InputManagerCallbacks>,
|
|
102
|
+
) => {
|
|
103
|
+
const modes: PermissionMode[] = ["default", "acceptEdits", "plan"];
|
|
104
|
+
const currentIndex = modes.indexOf(currentMode);
|
|
105
|
+
const nextIndex = currentIndex === -1 ? 0 : (currentIndex + 1) % modes.length;
|
|
106
|
+
const nextMode = modes[nextIndex];
|
|
107
|
+
|
|
108
|
+
callbacks.logger?.debug("Cycling permission mode", {
|
|
109
|
+
from: currentMode,
|
|
110
|
+
to: nextMode,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
dispatch({ type: "SET_PERMISSION_MODE", payload: nextMode });
|
|
114
|
+
callbacks.onPermissionModeChange?.(nextMode);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export const updateSearchQueriesForActiveSelectors = (
|
|
118
|
+
state: InputState,
|
|
119
|
+
dispatch: React.Dispatch<InputAction>,
|
|
120
|
+
inputText: string,
|
|
121
|
+
cursorPosition: number,
|
|
122
|
+
): void => {
|
|
123
|
+
if (state.showFileSelector && state.atPosition >= 0) {
|
|
124
|
+
const queryStart = state.atPosition + 1;
|
|
125
|
+
const queryEnd = cursorPosition;
|
|
126
|
+
const newQuery = inputText.substring(queryStart, queryEnd);
|
|
127
|
+
dispatch({ type: "SET_FILE_SEARCH_QUERY", payload: newQuery });
|
|
128
|
+
} else if (state.showCommandSelector && state.slashPosition >= 0) {
|
|
129
|
+
const queryStart = state.slashPosition + 1;
|
|
130
|
+
const queryEnd = cursorPosition;
|
|
131
|
+
const newQuery = inputText.substring(queryStart, queryEnd);
|
|
132
|
+
dispatch({ type: "SET_COMMAND_SEARCH_QUERY", payload: newQuery });
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export const handleSpecialCharInput = (
|
|
137
|
+
state: InputState,
|
|
138
|
+
dispatch: React.Dispatch<InputAction>,
|
|
139
|
+
char: string,
|
|
140
|
+
cursorPosition: number,
|
|
141
|
+
inputText: string,
|
|
142
|
+
): void => {
|
|
143
|
+
if (char === "@") {
|
|
144
|
+
dispatch({ type: "ACTIVATE_FILE_SELECTOR", payload: cursorPosition - 1 });
|
|
145
|
+
} else if (char === "/" && !state.showFileSelector && cursorPosition === 1) {
|
|
146
|
+
dispatch({
|
|
147
|
+
type: "ACTIVATE_COMMAND_SELECTOR",
|
|
148
|
+
payload: cursorPosition - 1,
|
|
149
|
+
});
|
|
150
|
+
} else {
|
|
151
|
+
updateSearchQueriesForActiveSelectors(
|
|
152
|
+
state,
|
|
153
|
+
dispatch,
|
|
154
|
+
inputText,
|
|
155
|
+
cursorPosition,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
export const handlePasteInput = (
|
|
161
|
+
state: InputState,
|
|
162
|
+
dispatch: React.Dispatch<InputAction>,
|
|
163
|
+
callbacks: Partial<InputManagerCallbacks>,
|
|
164
|
+
input: string,
|
|
165
|
+
): void => {
|
|
166
|
+
const inputString = input;
|
|
167
|
+
const isPasteOperation =
|
|
168
|
+
inputString.length > 1 ||
|
|
169
|
+
inputString.includes("\n") ||
|
|
170
|
+
inputString.includes("\r");
|
|
171
|
+
|
|
172
|
+
if (isPasteOperation) {
|
|
173
|
+
if (!state.isPasting) {
|
|
174
|
+
dispatch({
|
|
175
|
+
type: "START_PASTE",
|
|
176
|
+
payload: { buffer: inputString, cursorPosition: state.cursorPosition },
|
|
177
|
+
});
|
|
178
|
+
} else {
|
|
179
|
+
dispatch({ type: "APPEND_PASTE_BUFFER", payload: inputString });
|
|
180
|
+
}
|
|
181
|
+
} else {
|
|
182
|
+
let char = inputString;
|
|
183
|
+
if (char === "!" && state.cursorPosition === 0) {
|
|
184
|
+
char = "!";
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
callbacks.onResetHistoryNavigation?.();
|
|
188
|
+
dispatch({ type: "INSERT_TEXT", payload: char });
|
|
189
|
+
|
|
190
|
+
// Calculate new state for special char handling
|
|
191
|
+
const newCursorPosition = state.cursorPosition + char.length;
|
|
192
|
+
const beforeCursor = state.inputText.substring(0, state.cursorPosition);
|
|
193
|
+
const afterCursor = state.inputText.substring(state.cursorPosition);
|
|
194
|
+
const newInputText = beforeCursor + char + afterCursor;
|
|
195
|
+
|
|
196
|
+
handleSpecialCharInput(
|
|
197
|
+
state,
|
|
198
|
+
dispatch,
|
|
199
|
+
char,
|
|
200
|
+
newCursorPosition,
|
|
201
|
+
newInputText,
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
export const handleCommandSelect = (
|
|
207
|
+
state: InputState,
|
|
208
|
+
dispatch: React.Dispatch<InputAction>,
|
|
209
|
+
callbacks: Partial<InputManagerCallbacks>,
|
|
210
|
+
command: string,
|
|
211
|
+
) => {
|
|
212
|
+
if (state.slashPosition >= 0) {
|
|
213
|
+
const beforeSlash = state.inputText.substring(0, state.slashPosition);
|
|
214
|
+
const afterQuery = state.inputText.substring(state.cursorPosition);
|
|
215
|
+
const newInput = beforeSlash + afterQuery;
|
|
216
|
+
const newCursorPosition = beforeSlash.length;
|
|
217
|
+
|
|
218
|
+
dispatch({ type: "SET_INPUT_TEXT", payload: newInput });
|
|
219
|
+
dispatch({ type: "SET_CURSOR_POSITION", payload: newCursorPosition });
|
|
220
|
+
|
|
221
|
+
// Execute command asynchronously
|
|
222
|
+
(async () => {
|
|
223
|
+
let commandExecuted = false;
|
|
224
|
+
if (callbacks.onSendMessage && callbacks.onHasSlashCommand?.(command)) {
|
|
225
|
+
const fullCommand = `/${command}`;
|
|
226
|
+
try {
|
|
227
|
+
await callbacks.onSendMessage(fullCommand);
|
|
228
|
+
commandExecuted = true;
|
|
229
|
+
} catch (error) {
|
|
230
|
+
console.error("Failed to execute slash command:", error);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (!commandExecuted) {
|
|
235
|
+
if (command === "clear") {
|
|
236
|
+
callbacks.onClearMessages?.();
|
|
237
|
+
} else if (command === "tasks") {
|
|
238
|
+
dispatch({ type: "SET_SHOW_BACKGROUND_TASK_MANAGER", payload: true });
|
|
239
|
+
} else if (command === "mcp") {
|
|
240
|
+
dispatch({ type: "SET_SHOW_MCP_MANAGER", payload: true });
|
|
241
|
+
} else if (command === "rewind") {
|
|
242
|
+
dispatch({ type: "SET_SHOW_REWIND_MANAGER", payload: true });
|
|
243
|
+
} else if (command === "help") {
|
|
244
|
+
dispatch({ type: "SET_SHOW_HELP", payload: true });
|
|
245
|
+
} else if (command === "status") {
|
|
246
|
+
dispatch({ type: "SET_SHOW_STATUS_COMMAND", payload: true });
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
})();
|
|
250
|
+
|
|
251
|
+
dispatch({ type: "CANCEL_COMMAND_SELECTOR" });
|
|
252
|
+
callbacks.onInputTextChange?.(newInput);
|
|
253
|
+
callbacks.onCursorPositionChange?.(newCursorPosition);
|
|
254
|
+
|
|
255
|
+
return { newInput, newCursorPosition };
|
|
256
|
+
}
|
|
257
|
+
return { newInput: state.inputText, newCursorPosition: state.cursorPosition };
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
export const handleFileSelect = (
|
|
261
|
+
state: InputState,
|
|
262
|
+
dispatch: React.Dispatch<InputAction>,
|
|
263
|
+
callbacks: Partial<InputManagerCallbacks>,
|
|
264
|
+
filePath: string,
|
|
265
|
+
) => {
|
|
266
|
+
if (state.atPosition >= 0) {
|
|
267
|
+
const beforeAt = state.inputText.substring(0, state.atPosition);
|
|
268
|
+
const afterQuery = state.inputText.substring(state.cursorPosition);
|
|
269
|
+
const newInput = beforeAt + `${filePath} ` + afterQuery;
|
|
270
|
+
const newCursorPosition = beforeAt.length + filePath.length + 1;
|
|
271
|
+
|
|
272
|
+
dispatch({ type: "SET_INPUT_TEXT", payload: newInput });
|
|
273
|
+
dispatch({ type: "SET_CURSOR_POSITION", payload: newCursorPosition });
|
|
274
|
+
dispatch({ type: "CANCEL_FILE_SELECTOR" });
|
|
275
|
+
|
|
276
|
+
callbacks.onInputTextChange?.(newInput);
|
|
277
|
+
callbacks.onCursorPositionChange?.(newCursorPosition);
|
|
278
|
+
|
|
279
|
+
return { newInput, newCursorPosition };
|
|
280
|
+
}
|
|
281
|
+
return { newInput: state.inputText, newCursorPosition: state.cursorPosition };
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
export const checkForAtDeletion = (
|
|
285
|
+
state: InputState,
|
|
286
|
+
dispatch: React.Dispatch<InputAction>,
|
|
287
|
+
cursorPosition: number,
|
|
288
|
+
): boolean => {
|
|
289
|
+
if (state.showFileSelector && cursorPosition <= state.atPosition) {
|
|
290
|
+
dispatch({ type: "CANCEL_FILE_SELECTOR" });
|
|
291
|
+
return true;
|
|
292
|
+
}
|
|
293
|
+
return false;
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
export const checkForSlashDeletion = (
|
|
297
|
+
state: InputState,
|
|
298
|
+
dispatch: React.Dispatch<InputAction>,
|
|
299
|
+
cursorPosition: number,
|
|
300
|
+
): boolean => {
|
|
301
|
+
if (state.showCommandSelector && cursorPosition <= state.slashPosition) {
|
|
302
|
+
dispatch({ type: "CANCEL_COMMAND_SELECTOR" });
|
|
303
|
+
return true;
|
|
304
|
+
}
|
|
305
|
+
return false;
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
export const handleSelectorInput = (
|
|
309
|
+
state: InputState,
|
|
310
|
+
dispatch: React.Dispatch<InputAction>,
|
|
311
|
+
callbacks: Partial<InputManagerCallbacks>,
|
|
312
|
+
input: string,
|
|
313
|
+
key: Key,
|
|
314
|
+
): boolean => {
|
|
315
|
+
if (key.backspace || key.delete) {
|
|
316
|
+
if (state.cursorPosition > 0) {
|
|
317
|
+
const newCursorPosition = state.cursorPosition - 1;
|
|
318
|
+
const beforeCursor = state.inputText.substring(
|
|
319
|
+
0,
|
|
320
|
+
state.cursorPosition - 1,
|
|
321
|
+
);
|
|
322
|
+
const afterCursor = state.inputText.substring(state.cursorPosition);
|
|
323
|
+
const newInputText = beforeCursor + afterCursor;
|
|
324
|
+
|
|
325
|
+
dispatch({ type: "DELETE_CHAR" });
|
|
326
|
+
|
|
327
|
+
// Check for special character deletion
|
|
328
|
+
checkForAtDeletion(state, dispatch, newCursorPosition);
|
|
329
|
+
checkForSlashDeletion(state, dispatch, newCursorPosition);
|
|
330
|
+
|
|
331
|
+
// Update search queries
|
|
332
|
+
updateSearchQueriesForActiveSelectors(
|
|
333
|
+
state,
|
|
334
|
+
dispatch,
|
|
335
|
+
newInputText,
|
|
336
|
+
newCursorPosition,
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
return true;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (key.upArrow || key.downArrow || key.return || key.tab) {
|
|
343
|
+
return true;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (
|
|
347
|
+
input &&
|
|
348
|
+
!key.ctrl &&
|
|
349
|
+
!("alt" in key && key.alt) &&
|
|
350
|
+
!key.meta &&
|
|
351
|
+
!key.return &&
|
|
352
|
+
!key.tab &&
|
|
353
|
+
!key.escape &&
|
|
354
|
+
!key.leftArrow &&
|
|
355
|
+
!key.rightArrow &&
|
|
356
|
+
!("home" in key && key.home) &&
|
|
357
|
+
!("end" in key && key.end)
|
|
358
|
+
) {
|
|
359
|
+
dispatch({ type: "INSERT_TEXT", payload: input });
|
|
360
|
+
|
|
361
|
+
// Calculate new state for special char handling
|
|
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
|
+
);
|
|
374
|
+
return true;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return false;
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
export const handleNormalInput = async (
|
|
381
|
+
state: InputState,
|
|
382
|
+
dispatch: React.Dispatch<InputAction>,
|
|
383
|
+
callbacks: Partial<InputManagerCallbacks>,
|
|
384
|
+
input: string,
|
|
385
|
+
key: Key,
|
|
386
|
+
isLoading: boolean = false,
|
|
387
|
+
isCommandRunning: boolean = false,
|
|
388
|
+
clearImages?: () => void,
|
|
389
|
+
): Promise<boolean> => {
|
|
390
|
+
if (key.return) {
|
|
391
|
+
await handleSubmit(state, dispatch, callbacks, isLoading, isCommandRunning);
|
|
392
|
+
clearImages?.();
|
|
393
|
+
return true;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (key.escape) {
|
|
397
|
+
if (state.showFileSelector) {
|
|
398
|
+
dispatch({ type: "CANCEL_FILE_SELECTOR" });
|
|
399
|
+
} else if (state.showCommandSelector) {
|
|
400
|
+
dispatch({ type: "CANCEL_COMMAND_SELECTOR" });
|
|
401
|
+
}
|
|
402
|
+
return true;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (key.backspace || key.delete) {
|
|
406
|
+
if (state.cursorPosition > 0) {
|
|
407
|
+
const newCursorPosition = state.cursorPosition - 1;
|
|
408
|
+
dispatch({ type: "DELETE_CHAR" });
|
|
409
|
+
callbacks.onResetHistoryNavigation?.();
|
|
410
|
+
|
|
411
|
+
checkForAtDeletion(state, dispatch, newCursorPosition);
|
|
412
|
+
checkForSlashDeletion(state, dispatch, newCursorPosition);
|
|
413
|
+
}
|
|
414
|
+
return true;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (key.leftArrow) {
|
|
418
|
+
dispatch({ type: "MOVE_CURSOR", payload: -1 });
|
|
419
|
+
return true;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (key.rightArrow) {
|
|
423
|
+
dispatch({ type: "MOVE_CURSOR", payload: 1 });
|
|
424
|
+
return true;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (key.ctrl && input === "v") {
|
|
428
|
+
handlePasteImage(dispatch).catch((error) => {
|
|
429
|
+
console.warn("Failed to handle paste image:", error);
|
|
430
|
+
});
|
|
431
|
+
return true;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (key.ctrl && input === "r") {
|
|
435
|
+
dispatch({ type: "ACTIVATE_HISTORY_SEARCH" });
|
|
436
|
+
return true;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (key.ctrl && input === "b") {
|
|
440
|
+
callbacks.onBackgroundCurrentTask?.();
|
|
441
|
+
return true;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (
|
|
445
|
+
input &&
|
|
446
|
+
!key.ctrl &&
|
|
447
|
+
!("alt" in key && key.alt) &&
|
|
448
|
+
!key.meta &&
|
|
449
|
+
!key.return &&
|
|
450
|
+
!key.escape &&
|
|
451
|
+
!key.backspace &&
|
|
452
|
+
!key.delete &&
|
|
453
|
+
!key.leftArrow &&
|
|
454
|
+
!key.rightArrow &&
|
|
455
|
+
!("home" in key && key.home) &&
|
|
456
|
+
!("end" in key && key.end)
|
|
457
|
+
) {
|
|
458
|
+
handlePasteInput(state, dispatch, callbacks, input);
|
|
459
|
+
return true;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
return false;
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
export const handleInput = async (
|
|
466
|
+
state: InputState,
|
|
467
|
+
dispatch: React.Dispatch<InputAction>,
|
|
468
|
+
callbacks: Partial<InputManagerCallbacks>,
|
|
469
|
+
input: string,
|
|
470
|
+
key: Key,
|
|
471
|
+
isLoading: boolean = false,
|
|
472
|
+
isCommandRunning: boolean = false,
|
|
473
|
+
clearImages?: () => void,
|
|
474
|
+
): Promise<boolean> => {
|
|
475
|
+
if (state.selectorJustUsed) {
|
|
476
|
+
return true;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
if (key.escape) {
|
|
480
|
+
if (
|
|
481
|
+
(isLoading || isCommandRunning) &&
|
|
482
|
+
!(
|
|
483
|
+
state.showFileSelector ||
|
|
484
|
+
state.showCommandSelector ||
|
|
485
|
+
state.showHistorySearch ||
|
|
486
|
+
state.showBackgroundTaskManager ||
|
|
487
|
+
state.showMcpManager ||
|
|
488
|
+
state.showRewindManager ||
|
|
489
|
+
state.showHelp ||
|
|
490
|
+
state.showStatusCommand
|
|
491
|
+
)
|
|
492
|
+
) {
|
|
493
|
+
callbacks.onAbortMessage?.();
|
|
494
|
+
return true;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
if (key.tab && key.shift) {
|
|
499
|
+
cyclePermissionMode(state.permissionMode, dispatch, callbacks);
|
|
500
|
+
return true;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (
|
|
504
|
+
state.showFileSelector ||
|
|
505
|
+
state.showCommandSelector ||
|
|
506
|
+
state.showHistorySearch ||
|
|
507
|
+
state.showBackgroundTaskManager ||
|
|
508
|
+
state.showMcpManager ||
|
|
509
|
+
state.showRewindManager ||
|
|
510
|
+
state.showHelp ||
|
|
511
|
+
state.showStatusCommand
|
|
512
|
+
) {
|
|
513
|
+
if (
|
|
514
|
+
state.showBackgroundTaskManager ||
|
|
515
|
+
state.showMcpManager ||
|
|
516
|
+
state.showRewindManager ||
|
|
517
|
+
state.showHelp ||
|
|
518
|
+
state.showStatusCommand
|
|
519
|
+
) {
|
|
520
|
+
return true;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
if (state.showHistorySearch) {
|
|
524
|
+
if (key.escape) {
|
|
525
|
+
dispatch({ type: "CANCEL_HISTORY_SEARCH" });
|
|
526
|
+
return true;
|
|
527
|
+
}
|
|
528
|
+
if (key.backspace || key.delete) {
|
|
529
|
+
if (state.historySearchQuery.length > 0) {
|
|
530
|
+
dispatch({
|
|
531
|
+
type: "SET_HISTORY_SEARCH_QUERY",
|
|
532
|
+
payload: state.historySearchQuery.slice(0, -1),
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
return true;
|
|
536
|
+
}
|
|
537
|
+
if (input && !key.ctrl && !key.meta && !key.return && !key.tab) {
|
|
538
|
+
dispatch({
|
|
539
|
+
type: "SET_HISTORY_SEARCH_QUERY",
|
|
540
|
+
payload: state.historySearchQuery + input,
|
|
541
|
+
});
|
|
542
|
+
return true;
|
|
543
|
+
}
|
|
544
|
+
return true;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
return handleSelectorInput(state, dispatch, callbacks, input, key);
|
|
548
|
+
} else {
|
|
549
|
+
return await handleNormalInput(
|
|
550
|
+
state,
|
|
551
|
+
dispatch,
|
|
552
|
+
callbacks,
|
|
553
|
+
input,
|
|
554
|
+
key,
|
|
555
|
+
isLoading,
|
|
556
|
+
isCommandRunning,
|
|
557
|
+
clearImages,
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
};
|