mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-18 18:11:02 -07:00
155 lines
4.0 KiB
TypeScript
155 lines
4.0 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2025 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import { useState, useEffect, useMemo, useCallback, useRef } from 'react';
|
|
import { useCompletion } from './useCompletion.js';
|
|
import type { TextBuffer } from '../components/shared/text-buffer.js';
|
|
import type { Suggestion } from '../components/SuggestionsDisplay.js';
|
|
|
|
function useDebouncedValue<T>(value: T, delay = 200): T {
|
|
const [debounced, setDebounced] = useState(value);
|
|
useEffect(() => {
|
|
const handle = setTimeout(() => setDebounced(value), delay);
|
|
return () => clearTimeout(handle);
|
|
}, [value, delay]);
|
|
return debounced;
|
|
}
|
|
|
|
export interface UseReverseSearchCompletionReturn {
|
|
suggestions: Suggestion[];
|
|
activeSuggestionIndex: number;
|
|
visibleStartIndex: number;
|
|
showSuggestions: boolean;
|
|
isLoadingSuggestions: boolean;
|
|
navigateUp: () => void;
|
|
navigateDown: () => void;
|
|
handleAutocomplete: (i: number) => void;
|
|
resetCompletionState: () => void;
|
|
}
|
|
|
|
export function useReverseSearchCompletion(
|
|
buffer: TextBuffer,
|
|
history: readonly string[],
|
|
reverseSearchActive: boolean,
|
|
): UseReverseSearchCompletionReturn {
|
|
const {
|
|
suggestions,
|
|
activeSuggestionIndex,
|
|
visibleStartIndex,
|
|
isLoadingSuggestions,
|
|
setSuggestions,
|
|
setActiveSuggestionIndex,
|
|
resetCompletionState,
|
|
navigateUp,
|
|
navigateDown,
|
|
setVisibleStartIndex,
|
|
} = useCompletion();
|
|
|
|
const debouncedQuery = useDebouncedValue(buffer.text, 100);
|
|
|
|
// incremental search
|
|
const prevQueryRef = useRef<string>('');
|
|
const prevMatchesRef = useRef<Suggestion[]>([]);
|
|
|
|
// Clear incremental cache when activating reverse search
|
|
useEffect(() => {
|
|
if (reverseSearchActive) {
|
|
prevQueryRef.current = '';
|
|
prevMatchesRef.current = [];
|
|
}
|
|
}, [reverseSearchActive]);
|
|
|
|
// Also clear cache when history changes so new items are considered
|
|
useEffect(() => {
|
|
prevQueryRef.current = '';
|
|
prevMatchesRef.current = [];
|
|
}, [history]);
|
|
|
|
const searchHistory = useCallback(
|
|
(query: string, items: readonly string[]) => {
|
|
const out: Suggestion[] = [];
|
|
for (let i = 0; i < items.length; i++) {
|
|
const cmd = items[i];
|
|
const idx = cmd.toLowerCase().indexOf(query);
|
|
if (idx !== -1) {
|
|
out.push({ label: cmd, value: cmd, matchedIndex: idx });
|
|
}
|
|
}
|
|
return out;
|
|
},
|
|
[],
|
|
);
|
|
|
|
const matches = useMemo<Suggestion[]>(() => {
|
|
if (!reverseSearchActive) return [];
|
|
if (debouncedQuery.length === 0)
|
|
return history.map((cmd) => ({
|
|
label: cmd,
|
|
value: cmd,
|
|
matchedIndex: -1,
|
|
}));
|
|
|
|
const query = debouncedQuery.toLowerCase();
|
|
const canUseCache =
|
|
prevQueryRef.current &&
|
|
query.startsWith(prevQueryRef.current) &&
|
|
prevMatchesRef.current.length > 0;
|
|
|
|
const source = canUseCache
|
|
? prevMatchesRef.current.map((m) => m.value)
|
|
: history;
|
|
|
|
return searchHistory(query, source);
|
|
}, [debouncedQuery, history, reverseSearchActive, searchHistory]);
|
|
|
|
useEffect(() => {
|
|
if (!reverseSearchActive) {
|
|
resetCompletionState();
|
|
return;
|
|
}
|
|
|
|
setSuggestions(matches);
|
|
const hasAny = matches.length > 0;
|
|
setActiveSuggestionIndex(hasAny ? 0 : -1);
|
|
setVisibleStartIndex(0);
|
|
|
|
prevQueryRef.current = debouncedQuery.toLowerCase();
|
|
prevMatchesRef.current = matches;
|
|
}, [
|
|
debouncedQuery,
|
|
matches,
|
|
reverseSearchActive,
|
|
setSuggestions,
|
|
setActiveSuggestionIndex,
|
|
setVisibleStartIndex,
|
|
resetCompletionState,
|
|
]);
|
|
|
|
const showSuggestions =
|
|
reverseSearchActive && (isLoadingSuggestions || suggestions.length > 0);
|
|
|
|
const handleAutocomplete = useCallback(
|
|
(i: number) => {
|
|
if (i < 0 || i >= suggestions.length) return;
|
|
buffer.setText(suggestions[i].value);
|
|
resetCompletionState();
|
|
},
|
|
[buffer, suggestions, resetCompletionState],
|
|
);
|
|
|
|
return {
|
|
suggestions,
|
|
activeSuggestionIndex,
|
|
visibleStartIndex,
|
|
showSuggestions,
|
|
isLoadingSuggestions,
|
|
navigateUp,
|
|
navigateDown,
|
|
handleAutocomplete,
|
|
resetCompletionState,
|
|
};
|
|
}
|