Improvements to suggestions & slash commands (#344)

Co-authored-by: N. Taylor Mullen <ntaylormullen@google.com>
This commit is contained in:
Miguel Solorio 2025-05-14 16:01:29 -07:00 committed by GitHub
parent 89aa1cad41
commit 416813452e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 46 additions and 25 deletions

View File

@ -314,7 +314,7 @@ export const App = ({
resetCompletion={completion.resetCompletionState} resetCompletion={completion.resetCompletionState}
/> />
{completion.showSuggestions && ( {completion.showSuggestions && (
<Box marginTop={1}> <Box>
<SuggestionsDisplay <SuggestionsDisplay
suggestions={completion.suggestions} suggestions={completion.suggestions}
activeIndex={completion.activeSuggestionIndex} activeIndex={completion.activeSuggestionIndex}

View File

@ -76,6 +76,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
const newValue = base + selectedSuggestion.value; const newValue = base + selectedSuggestion.value;
onChangeAndMoveCursor(newValue); onChangeAndMoveCursor(newValue);
onSubmit(newValue); // Execute the command onSubmit(newValue); // Execute the command
onChangeAndMoveCursor(''); // Clear query after submit
} else { } else {
// Handle @ command completion // Handle @ command completion
const atIndex = query.lastIndexOf('@'); const atIndex = query.lastIndexOf('@');

View File

@ -9,6 +9,7 @@ import { Colors } from '../colors.js';
export interface Suggestion { export interface Suggestion {
label: string; label: string;
value: string; value: string;
description?: string;
} }
interface SuggestionsDisplayProps { interface SuggestionsDisplayProps {
suggestions: Suggestion[]; suggestions: Suggestion[];
@ -29,7 +30,7 @@ export function SuggestionsDisplay({
}: SuggestionsDisplayProps) { }: SuggestionsDisplayProps) {
if (isLoading) { if (isLoading) {
return ( return (
<Box borderStyle="round" paddingX={1} width={width}> <Box paddingX={1} width={width}>
<Text color="gray">Loading suggestions...</Text> <Text color="gray">Loading suggestions...</Text>
</Box> </Box>
); );
@ -48,20 +49,29 @@ export function SuggestionsDisplay({
const visibleSuggestions = suggestions.slice(startIndex, endIndex); const visibleSuggestions = suggestions.slice(startIndex, endIndex);
return ( return (
<Box borderStyle="round" flexDirection="column" paddingX={1} width={width}> <Box flexDirection="column" paddingX={1} width={width}>
{scrollOffset > 0 && <Text color={Colors.Foreground}></Text>} {scrollOffset > 0 && <Text color={Colors.Foreground}></Text>}
{visibleSuggestions.map((suggestion, index) => { {visibleSuggestions.map((suggestion, index) => {
const originalIndex = startIndex + index; const originalIndex = startIndex + index;
const isActive = originalIndex === activeIndex; const isActive = originalIndex === activeIndex;
const textColor = isActive ? Colors.AccentPurple : Colors.SubtleComment;
return ( return (
<Text <Box key={`${suggestion}-${originalIndex}`} width={width}>
key={`${suggestion}-${originalIndex}`} <Box flexDirection="row">
color={isActive ? Colors.Background : Colors.Foreground} <Box width={20} flexShrink={0}>
backgroundColor={isActive ? Colors.AccentBlue : undefined} <Text color={textColor}>{suggestion.label}</Text>
> </Box>
{suggestion.label} {suggestion.description ? (
</Text> <Box flexGrow={1}>
<Text color={textColor} wrap="wrap">
{suggestion.description}
</Text>
</Box>
) : null}
</Box>
</Box>
); );
})} })}
{endIndex < suggestions.length && <Text color="gray"></Text>} {endIndex < suggestions.length && <Text color="gray"></Text>}

View File

@ -94,7 +94,7 @@ export const useSlashCommandProcessor = (
{ {
name: 'quit', name: 'quit',
altName: 'exit', altName: 'exit',
description: '', description: 'exit the cli',
action: (_value: PartListUnion | string) => { action: (_value: PartListUnion | string) => {
onDebugMessage('Quitting. Good-bye.'); onDebugMessage('Quitting. Good-bye.');
process.exit(0); process.exit(0);

View File

@ -119,20 +119,30 @@ export function useCompletion(
// --- Handle Slash Command Completion --- // --- Handle Slash Command Completion ---
if (trimmedQuery.startsWith('/')) { if (trimmedQuery.startsWith('/')) {
const partialCommand = trimmedQuery.substring(1); const partialCommand = trimmedQuery.substring(1);
const commands = slashCommands const filteredSuggestions = slashCommands
.map((cmd) => cmd.name) .filter(
.concat( (cmd) =>
slashCommands cmd.name.startsWith(partialCommand) ||
.map((cmd) => cmd.altName) cmd.altName?.startsWith(partialCommand),
.filter((cmd) => cmd !== undefined), )
); // Filter out ? and any other single character commands unless it's the only char
.filter((cmd) => {
const filteredSuggestions = commands const nameMatch = cmd.name.startsWith(partialCommand);
.filter((name) => name.startsWith(partialCommand)) const altNameMatch = cmd.altName?.startsWith(partialCommand);
// Filter out ? and any other single character commands if (partialCommand.length === 1) {
.filter((name) => name.length > 1) return nameMatch || altNameMatch; // Allow single char match if query is single char
.map((name) => ({ label: name, value: name })) }
.sort(); return (
(nameMatch && cmd.name.length > 1) ||
(altNameMatch && cmd.altName && cmd.altName.length > 1)
);
})
.map((cmd) => ({
label: cmd.name, // Always show the main name as label
value: cmd.name, // Value should be the main command name for execution
description: cmd.description,
}))
.sort((a, b) => a.label.localeCompare(b.label));
setSuggestions(filteredSuggestions); setSuggestions(filteredSuggestions);
setShowSuggestions(filteredSuggestions.length > 0); setShowSuggestions(filteredSuggestions.length > 0);