/** * Function-name autocomplete for a formula editor (see FormulaBar.vue and * TableGrid.vue's in-cell editor, which both wire this in the same way). * * Unlike useTheme.ts/useToasts.ts (module-level singletons), this factory * creates fresh, independent state per call -- each formula editor gets its * own dropdown/selection, since two editors are never showing suggestions * for the same formula at once. */ import { ref } from 'vue'; import { currentIdentifierAtCaret } from '../engine'; import { useDocument } from '../store/document'; import type { FnSuggestion } from '../store/types'; export function useFormulaAutocomplete() { const store = useDocument(); const suggestions = ref([]); const activeIndex = ref(0); /** Recompute the dropdown for the current buffer + caret -- call this * after every edit to the text (an `input` handler). */ function update(text: string, caret: number) { const hit = currentIdentifierAtCaret(text, caret); suggestions.value = hit && hit.text ? store.functionSuggestions(hit.text) : []; activeIndex.value = 0; } function close() { suggestions.value = []; } /** Move the highlighted suggestion by `delta` (wraps). No-op while closed. */ function move(delta: number) { const n = suggestions.value.length; if (!n) return; activeIndex.value = (activeIndex.value + delta + n) % n; } /** The replacement the caller should apply via its own `replace(start, end, * value)` (the same one point-mode already uses) to accept the currently * highlighted suggestion -- `null` when no dropdown is open, so the * caller falls through to its normal Tab/Enter handling. Appends "(" , * ready for arguments, same as Excel/Sheets' own autocomplete. */ function accept(text: string, caret: number): { start: number; end: number; value: string } | null { if (!suggestions.value.length) return null; const hit = currentIdentifierAtCaret(text, caret); if (!hit) return null; return { start: hit.start, end: caret, value: suggestions.value[activeIndex.value].name + '(' }; } return { suggestions, activeIndex, update, close, move, accept }; }