Skip to content

Commit ad09f38

Browse files
fix: 修复在已有文本前输入斜杠命令无法触发自动补全,以及 Tab 补全覆盖后续文本的问题
当用户在已输入文本前插入 /command 时,光标后的文本包含空格,导致补全逻辑误判命令已有参数而跳过建议。 修复方式:只取光标前的文本(commandInput)进行命令解析和补全生成。 同时修复 Tab 补全斜杠命令时覆盖光标后文本的问题,改为在光标位置拼接补全结果。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent b0a3ef9 commit ad09f38

2 files changed

Lines changed: 402 additions & 19 deletions

File tree

src/hooks/useTypeahead.tsx

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -792,26 +792,30 @@ export function useTypeahead({
792792
}
793793

794794
// Determine whether to display the argument hint and command suggestions.
795+
// Only consider text up to the cursor — when the cursor is mid-input (e.g.,
796+
// user typed "/com" before existing text), text after the cursor shouldn't
797+
// affect command matching or argument detection.
798+
const commandInput = value.substring(0, effectiveCursorOffset);
795799
if (
796800
mode === 'prompt' &&
797-
isCommandInput(value) &&
801+
isCommandInput(commandInput) &&
798802
effectiveCursorOffset > 0 &&
799-
!hasCommandWithArguments(isAtEndWithWhitespace, value)
803+
!hasCommandWithArguments(isAtEndWithWhitespace, commandInput)
800804
) {
801805
let commandArgumentHint: string | undefined;
802-
if (value.length > 1) {
806+
if (commandInput.length > 1) {
803807
// We have a partial or complete command without arguments
804808
// Check if it matches a command exactly and has an argument hint
805809

806810
// Extract command name: everything after / until the first space (or end)
807-
const spaceIndex = value.indexOf(' ');
808-
const commandName = spaceIndex === -1 ? value.slice(1) : value.slice(1, spaceIndex);
811+
const spaceIndex = commandInput.indexOf(' ');
812+
const commandName = spaceIndex === -1 ? commandInput.slice(1) : commandInput.slice(1, spaceIndex);
809813

810814
// Check if there are real arguments (non-whitespace after the command)
811-
const hasRealArguments = spaceIndex !== -1 && value.slice(spaceIndex + 1).trim().length > 0;
815+
const hasRealArguments = spaceIndex !== -1 && commandInput.slice(spaceIndex + 1).trim().length > 0;
812816

813817
// Check if input is exactly "command + single space" (ready for arguments)
814-
const hasExactlyOneTrailingSpace = spaceIndex !== -1 && value.length === spaceIndex + 1;
818+
const hasExactlyOneTrailingSpace = spaceIndex !== -1 && commandInput.length === spaceIndex + 1;
815819

816820
// If input has a space after the command, don't show suggestions
817821
// This prevents Enter from selecting a different command after Tab completion
@@ -826,8 +830,8 @@ export function useTypeahead({
826830
commandArgumentHint = exactMatch.argumentHint;
827831
}
828832
// Priority 2: Progressive hint from argNames (show when trailing space)
829-
else if (exactMatch?.type === 'prompt' && exactMatch.argNames?.length && value.endsWith(' ')) {
830-
const argsText = value.slice(spaceIndex + 1);
833+
else if (exactMatch?.type === 'prompt' && exactMatch.argNames?.length && commandInput.endsWith(' ')) {
834+
const argsText = commandInput.slice(spaceIndex + 1);
831835
const typedArgs = parseArguments(argsText);
832836
commandArgumentHint = generateProgressiveArgumentHint(exactMatch.argNames, typedArgs);
833837
}
@@ -846,7 +850,7 @@ export function useTypeahead({
846850
// (set above when hasExactlyOneTrailingSpace is true)
847851
}
848852

849-
const commandItems = generateCommandSuggestions(value, commands);
853+
const commandItems = generateCommandSuggestions(commandInput, commands);
850854
setSuggestionsState(() => ({
851855
commandArgumentHint,
852856
suggestions: commandItems,
@@ -867,7 +871,7 @@ export function useTypeahead({
867871
// because there may be relevant @ symbol and file suggestions.
868872
debouncedFetchFileSuggestions.cancel();
869873
clearSuggestions();
870-
} else if (isCommandInput(value) && hasCommandWithArguments(isAtEndWithWhitespace, value)) {
874+
} else if (isCommandInput(commandInput) && hasCommandWithArguments(isAtEndWithWhitespace, commandInput)) {
871875
// If we have a command with arguments (no trailing space), clear any stale hint
872876
// This prevents the hint from flashing when transitioning between states
873877
setSuggestionsState(prev => (prev.commandArgumentHint ? { ...prev, commandArgumentHint: undefined } : prev));
@@ -1030,14 +1034,20 @@ export function useTypeahead({
10301034

10311035
if (suggestionType === 'command' && index < suggestions.length) {
10321036
if (suggestion) {
1033-
applyCommandSuggestion(
1034-
suggestion,
1035-
false, // don't execute on tab
1036-
commands,
1037-
onInputChange,
1038-
setCursorOffset,
1039-
onSubmit,
1040-
);
1037+
// Splice the completed command at the cursor position, preserving
1038+
// any text after the cursor (e.g., user typed "/com" before existing text).
1039+
const metadata = suggestion.metadata;
1040+
if (
1041+
metadata &&
1042+
typeof metadata === 'object' &&
1043+
'name' in metadata &&
1044+
'type' in metadata
1045+
) {
1046+
const commandName = getCommandName(metadata as Command);
1047+
const replacement = `/${commandName} `;
1048+
onInputChange(replacement + input.slice(cursorOffset));
1049+
setCursorOffset(replacement.length);
1050+
}
10411051
clearSuggestions();
10421052
}
10431053
} else if (suggestionType === 'custom-title' && suggestions.length > 0) {

0 commit comments

Comments
 (0)