wave-code 0.0.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/README.md +120 -0
- package/bin/wave-code.js +16 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +62 -0
- package/dist/components/App.d.ts +8 -0
- package/dist/components/App.d.ts.map +1 -0
- package/dist/components/App.js +10 -0
- package/dist/components/BashHistorySelector.d.ts +10 -0
- package/dist/components/BashHistorySelector.d.ts.map +1 -0
- package/dist/components/BashHistorySelector.js +83 -0
- package/dist/components/BashShellManager.d.ts +6 -0
- package/dist/components/BashShellManager.d.ts.map +1 -0
- package/dist/components/BashShellManager.js +116 -0
- package/dist/components/ChatInterface.d.ts +3 -0
- package/dist/components/ChatInterface.d.ts.map +1 -0
- package/dist/components/ChatInterface.js +31 -0
- package/dist/components/CommandOutputDisplay.d.ts +9 -0
- package/dist/components/CommandOutputDisplay.d.ts.map +1 -0
- package/dist/components/CommandOutputDisplay.js +40 -0
- package/dist/components/CommandSelector.d.ts +11 -0
- package/dist/components/CommandSelector.d.ts.map +1 -0
- package/dist/components/CommandSelector.js +60 -0
- package/dist/components/CompressDisplay.d.ts +9 -0
- package/dist/components/CompressDisplay.d.ts.map +1 -0
- package/dist/components/CompressDisplay.js +17 -0
- package/dist/components/DiffViewer.d.ts +9 -0
- package/dist/components/DiffViewer.d.ts.map +1 -0
- package/dist/components/DiffViewer.js +221 -0
- package/dist/components/FileSelector.d.ts +13 -0
- package/dist/components/FileSelector.d.ts.map +1 -0
- package/dist/components/FileSelector.js +48 -0
- package/dist/components/InputBox.d.ts +23 -0
- package/dist/components/InputBox.d.ts.map +1 -0
- package/dist/components/InputBox.js +124 -0
- package/dist/components/McpManager.d.ts +10 -0
- package/dist/components/McpManager.d.ts.map +1 -0
- package/dist/components/McpManager.js +123 -0
- package/dist/components/MemoryDisplay.d.ts +8 -0
- package/dist/components/MemoryDisplay.d.ts.map +1 -0
- package/dist/components/MemoryDisplay.js +25 -0
- package/dist/components/MemoryTypeSelector.d.ts +8 -0
- package/dist/components/MemoryTypeSelector.d.ts.map +1 -0
- package/dist/components/MemoryTypeSelector.js +38 -0
- package/dist/components/MessageList.d.ts +12 -0
- package/dist/components/MessageList.d.ts.map +1 -0
- package/dist/components/MessageList.js +36 -0
- package/dist/components/ToolResultDisplay.d.ts +9 -0
- package/dist/components/ToolResultDisplay.d.ts.map +1 -0
- package/dist/components/ToolResultDisplay.js +52 -0
- package/dist/contexts/useAppConfig.d.ts +11 -0
- package/dist/contexts/useAppConfig.d.ts.map +1 -0
- package/dist/contexts/useAppConfig.js +13 -0
- package/dist/contexts/useChat.d.ts +36 -0
- package/dist/contexts/useChat.d.ts.map +1 -0
- package/dist/contexts/useChat.js +208 -0
- package/dist/hooks/useBashHistorySelector.d.ts +15 -0
- package/dist/hooks/useBashHistorySelector.d.ts.map +1 -0
- package/dist/hooks/useBashHistorySelector.js +61 -0
- package/dist/hooks/useCommandSelector.d.ts +24 -0
- package/dist/hooks/useCommandSelector.d.ts.map +1 -0
- package/dist/hooks/useCommandSelector.js +98 -0
- package/dist/hooks/useFileSelector.d.ts +16 -0
- package/dist/hooks/useFileSelector.d.ts.map +1 -0
- package/dist/hooks/useFileSelector.js +174 -0
- package/dist/hooks/useImageManager.d.ts +13 -0
- package/dist/hooks/useImageManager.d.ts.map +1 -0
- package/dist/hooks/useImageManager.js +46 -0
- package/dist/hooks/useInputHistory.d.ts +11 -0
- package/dist/hooks/useInputHistory.d.ts.map +1 -0
- package/dist/hooks/useInputHistory.js +64 -0
- package/dist/hooks/useInputKeyboardHandler.d.ts +83 -0
- package/dist/hooks/useInputKeyboardHandler.d.ts.map +1 -0
- package/dist/hooks/useInputKeyboardHandler.js +507 -0
- package/dist/hooks/useInputState.d.ts +14 -0
- package/dist/hooks/useInputState.d.ts.map +1 -0
- package/dist/hooks/useInputState.js +57 -0
- package/dist/hooks/useMemoryTypeSelector.d.ts +9 -0
- package/dist/hooks/useMemoryTypeSelector.d.ts.map +1 -0
- package/dist/hooks/useMemoryTypeSelector.js +27 -0
- package/dist/hooks/usePagination.d.ts +20 -0
- package/dist/hooks/usePagination.d.ts.map +1 -0
- package/dist/hooks/usePagination.js +168 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +91 -0
- package/dist/plain-cli.d.ts +7 -0
- package/dist/plain-cli.d.ts.map +1 -0
- package/dist/plain-cli.js +49 -0
- package/dist/utils/clipboard.d.ts +22 -0
- package/dist/utils/clipboard.d.ts.map +1 -0
- package/dist/utils/clipboard.js +347 -0
- package/dist/utils/constants.d.ts +17 -0
- package/dist/utils/constants.d.ts.map +1 -0
- package/dist/utils/constants.js +18 -0
- package/dist/utils/logger.d.ts +72 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +245 -0
- package/package.json +60 -0
- package/src/cli.tsx +82 -0
- package/src/components/App.tsx +31 -0
- package/src/components/BashHistorySelector.tsx +163 -0
- package/src/components/BashShellManager.tsx +306 -0
- package/src/components/ChatInterface.tsx +88 -0
- package/src/components/CommandOutputDisplay.tsx +81 -0
- package/src/components/CommandSelector.tsx +144 -0
- package/src/components/CompressDisplay.tsx +58 -0
- package/src/components/DiffViewer.tsx +321 -0
- package/src/components/FileSelector.tsx +137 -0
- package/src/components/InputBox.tsx +310 -0
- package/src/components/McpManager.tsx +328 -0
- package/src/components/MemoryDisplay.tsx +62 -0
- package/src/components/MemoryTypeSelector.tsx +96 -0
- package/src/components/MessageList.tsx +215 -0
- package/src/components/ToolResultDisplay.tsx +138 -0
- package/src/contexts/useAppConfig.tsx +32 -0
- package/src/contexts/useChat.tsx +300 -0
- package/src/hooks/useBashHistorySelector.ts +77 -0
- package/src/hooks/useCommandSelector.ts +131 -0
- package/src/hooks/useFileSelector.ts +227 -0
- package/src/hooks/useImageManager.ts +64 -0
- package/src/hooks/useInputHistory.ts +74 -0
- package/src/hooks/useInputKeyboardHandler.ts +778 -0
- package/src/hooks/useInputState.ts +66 -0
- package/src/hooks/useMemoryTypeSelector.ts +40 -0
- package/src/hooks/usePagination.ts +203 -0
- package/src/index.ts +108 -0
- package/src/plain-cli.ts +66 -0
- package/src/utils/clipboard.ts +384 -0
- package/src/utils/constants.ts +22 -0
- package/src/utils/logger.ts +301 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { useState, useCallback, useEffect, useRef } from "react";
|
|
2
|
+
import { glob } from "glob";
|
|
3
|
+
import { getGlobIgnorePatterns } from "wave-agent-sdk";
|
|
4
|
+
import * as fs from "fs";
|
|
5
|
+
import * as path from "path";
|
|
6
|
+
import { FileItem } from "../components/FileSelector.js";
|
|
7
|
+
|
|
8
|
+
export const useFileSelector = () => {
|
|
9
|
+
const [showFileSelector, setShowFileSelector] = useState(false);
|
|
10
|
+
const [atPosition, setAtPosition] = useState(-1);
|
|
11
|
+
const [searchQuery, setSearchQuery] = useState("");
|
|
12
|
+
const [filteredFiles, setFilteredFiles] = useState<FileItem[]>([]);
|
|
13
|
+
const debounceTimerRef = useRef<NodeJS.Timeout | null>(null);
|
|
14
|
+
|
|
15
|
+
// Check if path is a directory
|
|
16
|
+
const isDirectory = useCallback((filePath: string): boolean => {
|
|
17
|
+
try {
|
|
18
|
+
const fullPath = path.isAbsolute(filePath)
|
|
19
|
+
? filePath
|
|
20
|
+
: path.join(process.cwd(), filePath);
|
|
21
|
+
return fs.statSync(fullPath).isDirectory();
|
|
22
|
+
} catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}, []);
|
|
26
|
+
|
|
27
|
+
// Convert string paths to FileItem objects
|
|
28
|
+
const convertToFileItems = useCallback(
|
|
29
|
+
(paths: string[]): FileItem[] => {
|
|
30
|
+
return paths.map((filePath) => ({
|
|
31
|
+
path: filePath,
|
|
32
|
+
type: isDirectory(filePath) ? "directory" : "file",
|
|
33
|
+
}));
|
|
34
|
+
},
|
|
35
|
+
[isDirectory],
|
|
36
|
+
);
|
|
37
|
+
// Use glob to search files and directories
|
|
38
|
+
const searchFiles = useCallback(
|
|
39
|
+
async (query: string) => {
|
|
40
|
+
try {
|
|
41
|
+
let files: string[] = [];
|
|
42
|
+
let directories: string[] = [];
|
|
43
|
+
|
|
44
|
+
const globOptions = {
|
|
45
|
+
ignore: getGlobIgnorePatterns(process.cwd()),
|
|
46
|
+
maxDepth: 10,
|
|
47
|
+
nocase: true, // Case insensitive
|
|
48
|
+
dot: true, // Include hidden files and directories
|
|
49
|
+
cwd: process.cwd(), // Specify search root directory
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
if (!query.trim()) {
|
|
53
|
+
// When query is empty, show some common file types and directories
|
|
54
|
+
const commonPatterns = [
|
|
55
|
+
"**/*.ts",
|
|
56
|
+
"**/*.tsx",
|
|
57
|
+
"**/*.js",
|
|
58
|
+
"**/*.jsx",
|
|
59
|
+
"**/*.json",
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
// Search files
|
|
63
|
+
const filePromises = commonPatterns.map((pattern) =>
|
|
64
|
+
glob(pattern, { ...globOptions, nodir: true }),
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
// Search directories (only search first level to avoid too many results)
|
|
68
|
+
const dirPromises = [glob("*/", { ...globOptions, maxDepth: 1 })];
|
|
69
|
+
|
|
70
|
+
const fileResults = await Promise.all(filePromises);
|
|
71
|
+
const dirResults = await Promise.all(dirPromises);
|
|
72
|
+
|
|
73
|
+
files = fileResults.flat();
|
|
74
|
+
directories = dirResults.flat().map((dir) => {
|
|
75
|
+
// glob returns string type paths, remove trailing slash
|
|
76
|
+
return String(dir).replace(/\/$/, "");
|
|
77
|
+
});
|
|
78
|
+
} else {
|
|
79
|
+
// Build multiple glob patterns to support more flexible search
|
|
80
|
+
const filePatterns = [
|
|
81
|
+
// Match files with filenames containing query
|
|
82
|
+
`**/*${query}*`,
|
|
83
|
+
// Match files with query in path (match directory names)
|
|
84
|
+
`**/${query}*/**/*`,
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
const dirPatterns = [
|
|
88
|
+
// Match directory names containing query
|
|
89
|
+
`**/*${query}*/`,
|
|
90
|
+
// Match directories containing query in path
|
|
91
|
+
`**/${query}*/`,
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
// Search files
|
|
95
|
+
const filePromises = filePatterns.map((pattern) =>
|
|
96
|
+
glob(pattern, { ...globOptions, nodir: true }),
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
// Search directories
|
|
100
|
+
const dirPromises = dirPatterns.map((pattern) =>
|
|
101
|
+
glob(pattern, { ...globOptions, nodir: false }),
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const fileResults = await Promise.all(filePromises);
|
|
105
|
+
const dirResults = await Promise.all(dirPromises);
|
|
106
|
+
|
|
107
|
+
files = fileResults.flat();
|
|
108
|
+
directories = dirResults.flat().map((dir) => {
|
|
109
|
+
// glob returns string type paths, remove trailing slash
|
|
110
|
+
return String(dir).replace(/\/$/, "");
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Deduplicate and merge files and directories
|
|
115
|
+
const uniqueFiles = Array.from(new Set(files));
|
|
116
|
+
const uniqueDirectories = Array.from(new Set(directories));
|
|
117
|
+
const allPaths = [...uniqueDirectories, ...uniqueFiles]; // Directories first
|
|
118
|
+
|
|
119
|
+
// Limit to maximum 10 results and convert to FileItem
|
|
120
|
+
const fileItems = convertToFileItems(allPaths.slice(0, 10));
|
|
121
|
+
setFilteredFiles(fileItems);
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.error("Glob search error:", error);
|
|
124
|
+
setFilteredFiles([]);
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
[convertToFileItems],
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
// Debounced search
|
|
131
|
+
const debouncedSearchFiles = useCallback(
|
|
132
|
+
(query: string) => {
|
|
133
|
+
// Clear previous timer
|
|
134
|
+
if (debounceTimerRef.current) {
|
|
135
|
+
clearTimeout(debounceTimerRef.current);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Set new timer, support environment variable configuration
|
|
139
|
+
const debounceDelay = parseInt(
|
|
140
|
+
process.env.FILE_SELECTOR_DEBOUNCE_MS || "300",
|
|
141
|
+
10,
|
|
142
|
+
);
|
|
143
|
+
debounceTimerRef.current = setTimeout(() => {
|
|
144
|
+
searchFiles(query);
|
|
145
|
+
}, debounceDelay);
|
|
146
|
+
},
|
|
147
|
+
[searchFiles],
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
// Trigger debounced search when search query changes
|
|
151
|
+
useEffect(() => {
|
|
152
|
+
debouncedSearchFiles(searchQuery);
|
|
153
|
+
|
|
154
|
+
// Cleanup function: clear timer when component unmounts
|
|
155
|
+
return () => {
|
|
156
|
+
if (debounceTimerRef.current) {
|
|
157
|
+
clearTimeout(debounceTimerRef.current);
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
}, [searchQuery, debouncedSearchFiles]);
|
|
161
|
+
|
|
162
|
+
const activateFileSelector = useCallback(
|
|
163
|
+
(position: number) => {
|
|
164
|
+
setShowFileSelector(true);
|
|
165
|
+
setAtPosition(position);
|
|
166
|
+
setSearchQuery("");
|
|
167
|
+
// Immediately trigger search to display initial file list, without waiting for debounce
|
|
168
|
+
searchFiles("");
|
|
169
|
+
},
|
|
170
|
+
[searchFiles],
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
const handleFileSelect = useCallback(
|
|
174
|
+
(filePath: string, inputText: string, cursorPosition: number) => {
|
|
175
|
+
if (atPosition >= 0) {
|
|
176
|
+
// Replace @ and search query with selected file path, remove @ symbol
|
|
177
|
+
const beforeAt = inputText.substring(0, atPosition);
|
|
178
|
+
const afterQuery = inputText.substring(cursorPosition);
|
|
179
|
+
const newInput = beforeAt + `${filePath} ` + afterQuery;
|
|
180
|
+
const newCursorPosition = beforeAt.length + filePath.length + 1;
|
|
181
|
+
|
|
182
|
+
setShowFileSelector(false);
|
|
183
|
+
setAtPosition(-1);
|
|
184
|
+
setSearchQuery("");
|
|
185
|
+
setFilteredFiles([]);
|
|
186
|
+
|
|
187
|
+
return { newInput, newCursorPosition };
|
|
188
|
+
}
|
|
189
|
+
return { newInput: inputText, newCursorPosition: cursorPosition };
|
|
190
|
+
},
|
|
191
|
+
[atPosition],
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
const handleCancelFileSelect = useCallback(() => {
|
|
195
|
+
setShowFileSelector(false);
|
|
196
|
+
setAtPosition(-1);
|
|
197
|
+
setSearchQuery("");
|
|
198
|
+
setFilteredFiles([]);
|
|
199
|
+
}, []);
|
|
200
|
+
|
|
201
|
+
const updateSearchQuery = useCallback((query: string) => {
|
|
202
|
+
setSearchQuery(query);
|
|
203
|
+
}, []);
|
|
204
|
+
|
|
205
|
+
const checkForAtDeletion = useCallback(
|
|
206
|
+
(cursorPosition: number) => {
|
|
207
|
+
if (showFileSelector && cursorPosition <= atPosition) {
|
|
208
|
+
handleCancelFileSelect();
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
return false;
|
|
212
|
+
},
|
|
213
|
+
[showFileSelector, atPosition, handleCancelFileSelect],
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
showFileSelector,
|
|
218
|
+
filteredFiles,
|
|
219
|
+
searchQuery,
|
|
220
|
+
activateFileSelector,
|
|
221
|
+
handleFileSelect,
|
|
222
|
+
handleCancelFileSelect,
|
|
223
|
+
updateSearchQuery,
|
|
224
|
+
checkForAtDeletion,
|
|
225
|
+
atPosition,
|
|
226
|
+
};
|
|
227
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { useState, useCallback } from "react";
|
|
2
|
+
import { readClipboardImage } from "../utils/clipboard.js";
|
|
3
|
+
|
|
4
|
+
export interface AttachedImage {
|
|
5
|
+
id: number;
|
|
6
|
+
path: string;
|
|
7
|
+
mimeType: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const useImageManager = (insertTextAtCursor: (text: string) => void) => {
|
|
11
|
+
const [attachedImages, setAttachedImages] = useState<AttachedImage[]>([]);
|
|
12
|
+
const [imageIdCounter, setImageIdCounter] = useState(1);
|
|
13
|
+
|
|
14
|
+
const addImage = useCallback(
|
|
15
|
+
(imagePath: string, mimeType: string) => {
|
|
16
|
+
const newImage: AttachedImage = {
|
|
17
|
+
id: imageIdCounter,
|
|
18
|
+
path: imagePath,
|
|
19
|
+
mimeType,
|
|
20
|
+
};
|
|
21
|
+
setAttachedImages((prev) => [...prev, newImage]);
|
|
22
|
+
setImageIdCounter((prev) => prev + 1);
|
|
23
|
+
return newImage;
|
|
24
|
+
},
|
|
25
|
+
[imageIdCounter],
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const removeImage = useCallback((imageId: number) => {
|
|
29
|
+
setAttachedImages((prev) => prev.filter((img) => img.id !== imageId));
|
|
30
|
+
}, []);
|
|
31
|
+
|
|
32
|
+
const clearImages = useCallback(() => {
|
|
33
|
+
setAttachedImages([]);
|
|
34
|
+
}, []);
|
|
35
|
+
|
|
36
|
+
const handlePasteImage = useCallback(async () => {
|
|
37
|
+
try {
|
|
38
|
+
const result = await readClipboardImage();
|
|
39
|
+
|
|
40
|
+
if (result.success && result.imagePath && result.mimeType) {
|
|
41
|
+
// Add image to manager
|
|
42
|
+
const attachedImage = addImage(result.imagePath, result.mimeType);
|
|
43
|
+
|
|
44
|
+
// Insert image placeholder at cursor position
|
|
45
|
+
insertTextAtCursor(`[Image #${attachedImage.id}]`);
|
|
46
|
+
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return false;
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.warn("Failed to paste image from clipboard:", error);
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}, [addImage, insertTextAtCursor]);
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
attachedImages,
|
|
59
|
+
addImage,
|
|
60
|
+
removeImage,
|
|
61
|
+
clearImages,
|
|
62
|
+
handlePasteImage,
|
|
63
|
+
};
|
|
64
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { useState, useCallback } from "react";
|
|
2
|
+
|
|
3
|
+
export interface UseInputHistoryParams {
|
|
4
|
+
userInputHistory: string[];
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const useInputHistory = ({
|
|
8
|
+
userInputHistory,
|
|
9
|
+
}: UseInputHistoryParams) => {
|
|
10
|
+
const [historyIndex, setHistoryIndex] = useState(-1);
|
|
11
|
+
const [currentDraftText, setCurrentDraftText] = useState("");
|
|
12
|
+
|
|
13
|
+
// Reset history navigation state
|
|
14
|
+
const resetHistoryNavigation = useCallback(() => {
|
|
15
|
+
setHistoryIndex(-1);
|
|
16
|
+
setCurrentDraftText("");
|
|
17
|
+
}, []);
|
|
18
|
+
|
|
19
|
+
// Handle history navigation
|
|
20
|
+
const navigateHistory = useCallback(
|
|
21
|
+
(direction: "up" | "down", inputText: string) => {
|
|
22
|
+
if (userInputHistory.length === 0)
|
|
23
|
+
return { newInput: inputText, newCursorPosition: inputText.length };
|
|
24
|
+
|
|
25
|
+
let newInput: string;
|
|
26
|
+
|
|
27
|
+
if (direction === "up") {
|
|
28
|
+
// Navigate up to earlier history records
|
|
29
|
+
if (historyIndex === -1) {
|
|
30
|
+
// First time pressing up key, save current input as draft
|
|
31
|
+
setCurrentDraftText(inputText);
|
|
32
|
+
const newIndex = userInputHistory.length - 1;
|
|
33
|
+
setHistoryIndex(newIndex);
|
|
34
|
+
newInput = userInputHistory[newIndex];
|
|
35
|
+
} else if (historyIndex > 0) {
|
|
36
|
+
const newIndex = historyIndex - 1;
|
|
37
|
+
setHistoryIndex(newIndex);
|
|
38
|
+
newInput = userInputHistory[newIndex];
|
|
39
|
+
} else {
|
|
40
|
+
newInput = inputText;
|
|
41
|
+
}
|
|
42
|
+
} else {
|
|
43
|
+
// Navigate down to newer history records
|
|
44
|
+
if (historyIndex >= 0) {
|
|
45
|
+
if (historyIndex < userInputHistory.length - 1) {
|
|
46
|
+
const newIndex = historyIndex + 1;
|
|
47
|
+
setHistoryIndex(newIndex);
|
|
48
|
+
newInput = userInputHistory[newIndex];
|
|
49
|
+
} else {
|
|
50
|
+
// Return to draft text
|
|
51
|
+
setHistoryIndex(-1);
|
|
52
|
+
newInput = currentDraftText;
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
// Already in draft state, pressing down key again clears input box
|
|
56
|
+
if (inputText.trim() !== "") {
|
|
57
|
+
setCurrentDraftText("");
|
|
58
|
+
newInput = "";
|
|
59
|
+
} else {
|
|
60
|
+
newInput = inputText;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return { newInput, newCursorPosition: newInput.length };
|
|
66
|
+
},
|
|
67
|
+
[userInputHistory, historyIndex, currentDraftText],
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
resetHistoryNavigation,
|
|
72
|
+
navigateHistory,
|
|
73
|
+
};
|
|
74
|
+
};
|