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:
wkh
2026-09-03 08:43:57 +02:00
co-authored by Claude Sonnet 5
parent 552f1cfae8
commit 0b7602b874
14 changed files with 493 additions and 41 deletions
+21
View File
@@ -139,6 +139,27 @@ export const FUNCTIONS: Record<string, FunctionFn> = {
// AVERAGE is an alias of AVG.
FUNCTIONS.AVERAGE = FUNCTIONS.AVG;
/** One-line signature hint per built-in, for the formula-autocomplete
* dropdown (see store/document.ts's `functionSuggestions()` and
* src/composables/useFormulaAutocomplete.ts). Hand-maintained -- kept
* honest by a test asserting its names exactly match `FUNCTIONS`'s keys,
* so a future new built-in without a matching entry here fails loudly
* instead of just silently missing from the dropdown. */
export const BUILTIN_SIGNATURES: ReadonlyArray<{ name: string; hint: string }> = [
{ name: 'SUM', hint: 'SUM(Zahl1; Zahl2; …)' },
{ name: 'AVG', hint: 'AVG(Zahl1; Zahl2; …)' },
{ name: 'AVERAGE', hint: 'AVERAGE(Zahl1; Zahl2; …)' },
{ name: 'MIN', hint: 'MIN(Zahl1; Zahl2; …)' },
{ name: 'MAX', hint: 'MAX(Zahl1; Zahl2; …)' },
{ name: 'COUNT', hint: 'COUNT(Zahl1; Zahl2; …)' },
{ name: 'IF', hint: 'IF(Bedingung; Dann; Sonst?)' },
{ name: 'DATE', hint: 'DATE(Jahr; Monat; Tag)' },
{ name: 'TIME', hint: 'TIME(Std; Min; Sek)' },
{ name: 'TODAY', hint: 'TODAY()' },
{ name: 'NOW', hint: 'NOW()' },
{ name: 'XLOOKUP', hint: 'XLOOKUP(Suchwert; Suchbereich; Rückgabebereich; …)' },
];
export function hasFunction(name: string): boolean {
return Object.prototype.hasOwnProperty.call(FUNCTIONS, name.toUpperCase());
}