feat: auto-execute simple slash commands on Enter (#13985)

This commit is contained in:
Jack Wotherspoon
2025-12-01 12:29:03 -05:00
committed by GitHub
parent 844d3a4dfa
commit f918af82fe
38 changed files with 393 additions and 9 deletions
@@ -42,6 +42,17 @@ export interface UseCommandCompletionReturn {
navigateDown: () => void;
handleAutocomplete: (indexToUse: number) => void;
promptCompletion: PromptCompletion;
getCommandFromSuggestion: (
suggestion: Suggestion,
) => SlashCommand | undefined;
slashCompletionRange: {
completionStart: number;
completionEnd: number;
getCommandFromSuggestion: (
suggestion: Suggestion,
) => SlashCommand | undefined;
};
getCompletedText: (suggestion: Suggestion) => string | null;
}
export function useCommandCompletion(
@@ -200,12 +211,16 @@ export function useCommandCompletion(
setShowSuggestions,
]);
const handleAutocomplete = useCallback(
(indexToUse: number) => {
if (indexToUse < 0 || indexToUse >= suggestions.length) {
return;
}
const suggestion = suggestions[indexToUse].value;
/**
* Gets the completed text by replacing the completion range with the suggestion value.
* This is the core string replacement logic used by both autocomplete and auto-execute.
*
* @param suggestion The suggestion to apply
* @returns The completed text with the suggestion applied, or null if invalid
*/
const getCompletedText = useCallback(
(suggestion: Suggestion): string | null => {
const currentLine = buffer.lines[cursorRow] || '';
let start = completionStart;
let end = completionEnd;
@@ -215,10 +230,56 @@ export function useCommandCompletion(
}
if (start === -1 || end === -1) {
return null;
}
// Apply space padding for slash commands (needed for subcommands like "/chat list")
let suggestionText = suggestion.value;
if (completionMode === CompletionMode.SLASH) {
// Add leading space if completing a subcommand (cursor is after parent command with no space)
if (start === end && start > 1 && currentLine[start - 1] !== ' ') {
suggestionText = ' ' + suggestionText;
}
}
// Build the completed text with proper spacing
return (
currentLine.substring(0, start) +
suggestionText +
currentLine.substring(end)
);
},
[
cursorRow,
buffer.lines,
completionMode,
completionStart,
completionEnd,
slashCompletionRange,
],
);
const handleAutocomplete = useCallback(
(indexToUse: number) => {
if (indexToUse < 0 || indexToUse >= suggestions.length) {
return;
}
const suggestion = suggestions[indexToUse];
const completedText = getCompletedText(suggestion);
if (completedText === null) {
return;
}
let suggestionText = suggestion;
let start = completionStart;
let end = completionEnd;
if (completionMode === CompletionMode.SLASH) {
start = slashCompletionRange.completionStart;
end = slashCompletionRange.completionEnd;
}
// Add space padding for Tab completion (auto-execute gets padding from getCompletedText)
let suggestionText = suggestion.value;
if (completionMode === CompletionMode.SLASH) {
if (
start === end &&
@@ -253,6 +314,7 @@ export function useCommandCompletion(
completionStart,
completionEnd,
slashCompletionRange,
getCompletedText,
],
);
@@ -270,5 +332,8 @@ export function useCommandCompletion(
navigateDown,
handleAutocomplete,
promptCompletion,
getCommandFromSuggestion: slashCompletionRange.getCommandFromSuggestion,
slashCompletionRange,
getCompletedText,
};
}
@@ -376,6 +376,32 @@ function usePerfectMatch(
}, [parserResult]);
}
/**
* Gets the SlashCommand object for a given suggestion by navigating the command hierarchy
* based on the current parser state.
* @param suggestion The suggestion object
* @param parserResult The current parser result with hierarchy information
* @returns The matching SlashCommand or undefined
*/
function getCommandFromSuggestion(
suggestion: Suggestion,
parserResult: CommandParserResult,
): SlashCommand | undefined {
const { currentLevel } = parserResult;
if (!currentLevel) {
return undefined;
}
// suggestion.value is just the command name at the current level (e.g., "list")
// Find it in the current level's commands
const command = currentLevel.find((cmd) =>
matchesCommand(cmd, suggestion.value),
);
return command;
}
export interface UseSlashCompletionProps {
enabled: boolean;
query: string | null;
@@ -389,6 +415,9 @@ export interface UseSlashCompletionProps {
export function useSlashCompletion(props: UseSlashCompletionProps): {
completionStart: number;
completionEnd: number;
getCommandFromSuggestion: (
suggestion: Suggestion,
) => SlashCommand | undefined;
} {
const {
enabled,
@@ -536,5 +565,7 @@ export function useSlashCompletion(props: UseSlashCompletionProps): {
return {
completionStart,
completionEnd,
getCommandFromSuggestion: (suggestion: Suggestion) =>
getCommandFromSuggestion(suggestion, parserResult),
};
}