Add @ command suggestions in the UI. (#219)

This commit is contained in:
Allen Hutchison
2025-04-30 08:31:32 -07:00
committed by GitHub
parent 28fc2d0de3
commit 9f20c5f95e
5 changed files with 232 additions and 113 deletions

View File

@@ -8,8 +8,7 @@ import { useState, useEffect, useCallback } from 'react';
import * as fs from 'fs/promises';
import * as path from 'path';
import { isNodeError } from '@gemini-code/server';
const MAX_SUGGESTIONS_TO_SHOW = 8;
import { MAX_SUGGESTIONS_TO_SHOW } from '../components/SuggestionsDisplay.js';
export interface UseCompletionReturn {
suggestions: string[];
@@ -45,51 +44,64 @@ export function useCompletion(
setIsLoadingSuggestions(false);
}, []);
// --- Navigation Logic ---
const navigateUp = useCallback(() => {
if (suggestions.length === 0) return;
setActiveSuggestionIndex((prevIndex) => {
const newIndex = prevIndex <= 0 ? suggestions.length - 1 : prevIndex - 1;
setActiveSuggestionIndex((prevActiveIndex) => {
// Calculate new active index, handling wrap-around
const newActiveIndex =
prevActiveIndex <= 0 ? suggestions.length - 1 : prevActiveIndex - 1;
// Adjust visible window if needed (scrolling up)
if (newIndex < visibleStartIndex) {
setVisibleStartIndex(newIndex);
} else if (
newIndex === suggestions.length - 1 &&
suggestions.length > MAX_SUGGESTIONS_TO_SHOW
) {
// Handle wrapping from first to last item
setVisibleStartIndex(
Math.max(0, suggestions.length - MAX_SUGGESTIONS_TO_SHOW),
);
}
// Adjust scroll position based on the new active index
setVisibleStartIndex((prevVisibleStart) => {
// Case 1: Wrapped around to the last item
if (
newActiveIndex === suggestions.length - 1 &&
suggestions.length > MAX_SUGGESTIONS_TO_SHOW
) {
return Math.max(0, suggestions.length - MAX_SUGGESTIONS_TO_SHOW);
}
// Case 2: Scrolled above the current visible window
if (newActiveIndex < prevVisibleStart) {
return newActiveIndex;
}
// Otherwise, keep the current scroll position
return prevVisibleStart;
});
return newIndex;
return newActiveIndex;
});
}, [suggestions.length, visibleStartIndex]);
}, [suggestions.length]);
const navigateDown = useCallback(() => {
if (suggestions.length === 0) return;
setActiveSuggestionIndex((prevIndex) => {
const newIndex = prevIndex >= suggestions.length - 1 ? 0 : prevIndex + 1;
setActiveSuggestionIndex((prevActiveIndex) => {
// Calculate new active index, handling wrap-around
const newActiveIndex =
prevActiveIndex >= suggestions.length - 1 ? 0 : prevActiveIndex + 1;
// Adjust visible window if needed (scrolling down)
if (newIndex >= visibleStartIndex + MAX_SUGGESTIONS_TO_SHOW) {
setVisibleStartIndex(visibleStartIndex + 1);
} else if (
newIndex === 0 &&
suggestions.length > MAX_SUGGESTIONS_TO_SHOW
) {
// Handle wrapping from last to first item
setVisibleStartIndex(0);
}
// Adjust scroll position based on the new active index
setVisibleStartIndex((prevVisibleStart) => {
// Case 1: Wrapped around to the first item
if (
newActiveIndex === 0 &&
suggestions.length > MAX_SUGGESTIONS_TO_SHOW
) {
return 0;
}
// Case 2: Scrolled below the current visible window
const visibleEndIndex = prevVisibleStart + MAX_SUGGESTIONS_TO_SHOW;
if (newActiveIndex >= visibleEndIndex) {
return newActiveIndex - MAX_SUGGESTIONS_TO_SHOW + 1;
}
// Otherwise, keep the current scroll position
return prevVisibleStart;
});
return newIndex;
return newActiveIndex;
});
}, [suggestions.length, visibleStartIndex]);
// --- End Navigation Logic ---
}, [suggestions.length]);
useEffect(() => {
if (!isActive) {
@@ -137,8 +149,8 @@ export function useCompletion(
if (isMounted) {
setSuggestions(filteredSuggestions);
setShowSuggestions(filteredSuggestions.length > 0);
setActiveSuggestionIndex(-1); // Reset selection on new suggestions
setVisibleStartIndex(0); // Reset scroll on new suggestions
setActiveSuggestionIndex(-1);
setVisibleStartIndex(0);
}
} catch (error) {
if (isNodeError(error) && error.code === 'ENOENT') {
@@ -162,13 +174,11 @@ export function useCompletion(
}
};
// Debounce the fetch slightly
const debounceTimeout = setTimeout(fetchSuggestions, 100);
return () => {
isMounted = false;
clearTimeout(debounceTimeout);
// Don't reset loading state here, let the next effect handle it or resetCompletionState
};
}, [query, cwd, isActive, resetCompletionState]);