Funktions-Autovervollständigung in der Formelbearbeitung
Beim Tippen eines Funktionsnamens (nach "=", einem Operator, "(" oder
";" -- nicht bei einem Zellbezug wie A1) zeigt sowohl die Formelleiste
als auch der In-Zellen-Editor jetzt ein Dropdown mit passenden
eingebauten UND eigenen Funktionen samt Kurzsignatur. ↑/↓ navigieren,
Tab/Enter/Klick übernehmen (fügt "NAME(" ein), Esc schließt nur das
Dropdown, ohne die Bearbeitung abzubrechen.
- engine/refs.ts: neue reine Funktion currentIdentifierAtCaret() --
strukturell wie das schon vorhandene cycleReferenceLocks (=
abstreifen, tokenize, am Cursor nachsehen), aber nur der Text bis
zum Cursor wird tokenisiert und nur das letzte Token zählt; ein
IDENT, das bis an den Cursor reicht und nicht wie ein Zellbezug
aussieht, ist der Funktionsname. tokenize() hängt ein EOF-Token an
-- muss beim Suchen des "letzten Tokens" herausgefiltert werden
(von den eigenen Tests aufgedeckt).
- engine/functions.ts: BUILTIN_SIGNATURES (Name -> Kurzsignatur je
eingebauter Funktion), gegen FUNCTIONS' Keys testabgesichert, damit
eine künftige neue Funktion ohne Eintrag hier nicht still im
Dropdown fehlt.
- store/document.ts: functionSuggestions(prefix) kombiniert eingebaute
+ dokumenteigene Funktionen, case-insensitiv gefiltert, sortiert.
- composables/useFormulaAutocomplete.ts: pro Editor-Instanz eigener
State (suggestions/activeIndex), orchestriert die drei Bausteine
oben; die Übernahme eines Vorschlags läuft über dieselbe
replace(start,end,value)-Mechanik, die Point-Mode für den Bezug-
Einfügeklick schon nutzt -- keine neue Einfüge-Infrastruktur nötig.
- components/FormulaSuggest.vue: geteiltes, rein präsentationelles
Dropdown (position:absolute; top:100% relativ zum Editor-Wrapper,
kein Caret-Pixel-Tracking).
- FormulaBar.vue + TableGrid.vue: gleiches Einbindungsmuster in
beiden Editoren (Wrapper bekommt position:relative, onInput ruft
autocomplete.update(), onKeydown fängt Pfeiltasten/Tab/Enter/Esc
vor der bestehenden Logik ab, wenn Vorschläge offen sind, onBlur
schließt das Dropdown).
Tests: 13 neue (refs.spec.ts: 7 Fälle für currentIdentifierAtCaret;
functions.spec.ts neu: Signatur-Tabelle deckungsgleich mit FUNCTIONS;
document.spec.ts: functionSuggestions Filter/Mischung/Sortierung).
318 -> 331 Tests, alle grün. vue-tsc/eslint/build sauber. Live per
Playwright verifiziert in Formelleiste UND In-Zellen-Editor: Filterung,
Pfeiltasten-Navigation, Tab-Übernahme, Maus-Klick-Übernahme, Esc
schließt nur das Dropdown, eigene Funktion (MARGE) erscheint neben
eingebauten (MAX) korrekt einsortiert.
READMEs aktualisiert (neuer Abschnitt "Funktions-Autovervollständigung",
Tastaturtabelle, Testliste, Bekannte Grenzen -- dabei eine veraltete
Aussage korrigiert, die Autovervollständigung für benutzerdefinierte
Funktionen noch als "bewusst fehlend" auflistete).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -131,6 +131,49 @@ function nextLock(colAbs: boolean, rowAbs: boolean): [boolean, boolean] {
|
||||
return [false, false]; // colAbs && !rowAbs
|
||||
}
|
||||
|
||||
/**
|
||||
* If the caret sits inside (anywhere within, or right at the end of) a bare
|
||||
* identifier being typed in a formula -- not yet a resolved cell reference,
|
||||
* not the start of an already-open function call -- return the identifier's
|
||||
* text *up to the caret* and its start offset in `formula` (which still
|
||||
* includes the leading "="). Drives function-name autocomplete; `null`
|
||||
* means "nothing to suggest here" (not a formula at all, a cell reference
|
||||
* like "A1", the caret sitting in whitespace or right after a completed
|
||||
* token rather than still touching it, ...).
|
||||
*
|
||||
* Same shape as {@link cycleReferenceLocks} above: strip the leading "=",
|
||||
* tokenize, and inspect the token(s) around the caret -- except here only
|
||||
* the prefix up to the caret is tokenized (nothing after it is ever looked
|
||||
* at, same as how autocomplete works in most editors), so the "identifier"
|
||||
* found is always exactly what's to the *left* of the caret, regardless of
|
||||
* whether more of the same word continues to its right.
|
||||
*/
|
||||
export function currentIdentifierAtCaret(
|
||||
formula: string,
|
||||
caret: number,
|
||||
): { text: string; start: number } | null {
|
||||
if (!formula.startsWith('=')) return null;
|
||||
const pos = caret - 1; // caret position relative to body = formula.slice(1)
|
||||
if (pos < 0) return null;
|
||||
let toks: Token[];
|
||||
try {
|
||||
toks = tokenize(formula.slice(1, caret));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
// tokenize() appends a trailing EOF marker -- skip it to get the last
|
||||
// *real* token.
|
||||
const real = toks.filter((t) => t.type !== 'EOF');
|
||||
const last = real[real.length - 1];
|
||||
if (!last || last.type !== 'IDENT') return null;
|
||||
// the token must reach all the way to the caret -- if it doesn't, the
|
||||
// caret has moved past it (trailing whitespace, e.g. "=SUM |") and
|
||||
// there's no word actively being typed right at the caret any more.
|
||||
if (last.pos + last.value.length !== pos) return null;
|
||||
if (looksLikeCell(last.value)) return null; // looks like a cell reference, not a function name
|
||||
return { text: last.value, start: last.pos + 1 };
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand a rectangular range (given by two corners, any order) into the list of
|
||||
* cell coordinates it covers, row-major.
|
||||
|
||||
Reference in New Issue
Block a user