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:
@@ -871,6 +871,36 @@ describe('custom functions (document-level library)', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('functionSuggestions (formula autocomplete)', () => {
|
||||
it('filters built-ins by prefix, case-insensitively', () => {
|
||||
const s = fresh();
|
||||
const names = s.functionSuggestions('su').map((x) => x.name);
|
||||
expect(names).toContain('SUM');
|
||||
expect(names).not.toContain('AVG');
|
||||
});
|
||||
|
||||
it('an empty prefix suggests nothing', () => {
|
||||
const s = fresh();
|
||||
expect(s.functionSuggestions('')).toEqual([]);
|
||||
});
|
||||
|
||||
it('mixes in custom functions, sorted alphabetically with built-ins', () => {
|
||||
const s = fresh();
|
||||
s.addCustomFunction('SUperMarge', ['x'], 'x');
|
||||
const names = s.functionSuggestions('SU').map((x) => x.name);
|
||||
expect(names).toEqual(['SUM', 'SUperMarge']); // SUM < SUperMarge
|
||||
const custom = s.functionSuggestions('SU').find((x) => x.name === 'SUperMarge');
|
||||
expect(custom).toEqual({ name: 'SUperMarge', hint: 'SUperMarge(x)', kind: 'custom' });
|
||||
});
|
||||
|
||||
it('a built-in hint always starts with its own name', () => {
|
||||
const s = fresh();
|
||||
for (const x of s.functionSuggestions('X')) {
|
||||
expect(x.hint.startsWith(x.name)).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('custom functions: "block" kind (FOR/IF/RETURN "code")', () => {
|
||||
const productBody = 'ergebnis = 1 FOR x IN bereich DO ergebnis = ergebnis * x END RETURN ergebnis';
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
findInvalidForSource,
|
||||
blockAlwaysReturns,
|
||||
parseDateTimeLiteral,
|
||||
BUILTIN_SIGNATURES,
|
||||
type CellValue,
|
||||
type TableModel,
|
||||
type Token,
|
||||
@@ -52,6 +53,7 @@ import type {
|
||||
ChartObject,
|
||||
CustomFunction,
|
||||
DocumentState,
|
||||
FnSuggestion,
|
||||
Sheet,
|
||||
SelectionState,
|
||||
SheetObject,
|
||||
@@ -1471,6 +1473,23 @@ function createStore() {
|
||||
function formulaPointModeActive(): boolean {
|
||||
return !!formulaEditor.value && formulaEditor.value.getState().text.startsWith('=');
|
||||
}
|
||||
/** Function-name autocomplete candidates for a prefix already typed (built-in
|
||||
* + this document's custom functions), case-insensitive, name-sorted. Used
|
||||
* by src/composables/useFormulaAutocomplete.ts, driven by
|
||||
* currentIdentifierAtCaret() finding the word being typed. Empty prefix ->
|
||||
* no suggestions (matches Excel/Sheets: autocomplete only kicks in once
|
||||
* something's actually been typed, not on a bare "="). */
|
||||
function functionSuggestions(prefix: string): FnSuggestion[] {
|
||||
const p = prefix.toUpperCase();
|
||||
if (!p) return [];
|
||||
const builtins: FnSuggestion[] = BUILTIN_SIGNATURES.filter((s) => s.name.startsWith(p)).map(
|
||||
(s) => ({ name: s.name, hint: s.hint, kind: 'builtin' }),
|
||||
);
|
||||
const custom: FnSuggestion[] = doc.customFunctions
|
||||
.filter((f) => f.name.toUpperCase().startsWith(p))
|
||||
.map((f) => ({ name: f.name, hint: `${f.name}(${f.params.join('; ')})`, kind: 'custom' }));
|
||||
return [...builtins, ...custom].sort((a, b) => a.name.localeCompare(b.name));
|
||||
}
|
||||
/** `'` needs quoting whenever a name isn't a bare identifier (spaces,
|
||||
* punctuation, ...) -- shared by the table- and sheet-qualifier cases. */
|
||||
function quoteQualifier(name: string): string {
|
||||
@@ -1768,6 +1787,7 @@ function createStore() {
|
||||
registerFormulaEditor,
|
||||
noteFormulaTyped,
|
||||
formulaPointModeActive,
|
||||
functionSuggestions,
|
||||
pointClick,
|
||||
pointDrag,
|
||||
// document
|
||||
|
||||
@@ -173,6 +173,15 @@ export interface CustomFunction {
|
||||
kind: 'expression' | 'block';
|
||||
}
|
||||
|
||||
/** One entry in the formula-autocomplete dropdown (see document.ts's
|
||||
* `functionSuggestions()`). */
|
||||
export interface FnSuggestion {
|
||||
name: string;
|
||||
/** one-line signature hint, e.g. "SUM(Zahl1; Zahl2; …)" */
|
||||
hint: string;
|
||||
kind: 'builtin' | 'custom';
|
||||
}
|
||||
|
||||
export interface DocumentState {
|
||||
version: 2;
|
||||
sheets: Sheet[];
|
||||
|
||||
Reference in New Issue
Block a user