/** * The searchable command reference shown next to the macro and custom- * function editors (components/CodeReference.vue). Each entry lists the * editing contexts it applies to -- a macro, a "Mit Schleife" function body * or a single-formula function body. Hand-maintained alongside * engine/evaluator.ts's `execAction()` (the write actions), its GET/POST/ * JSONGET handling and the statement grammar in engine/parser.ts -- the * formula-function entries are derived from `BUILTIN_SIGNATURES`, so a new * built-in shows up here automatically (with a generic description until * one is added to `FUNCTION_DESCRIPTIONS`). */ import { BUILTIN_SIGNATURES } from '../engine'; export type RefCategory = | 'Zellen' | 'Zeilen & Spalten' | 'Tabellen' | 'Blätter' | 'Ablauf' | 'Netzwerk & JSON' | 'Operatoren' | 'Formelfunktionen'; /** Which editor the reference is shown in. */ export type RefContext = 'macro' | 'block' | 'expression'; export interface RefEntry { name: string; /** Editors this entry applies to. */ contexts: RefContext[]; category: RefCategory; /** Signature as shown in the list. */ syntax: string; description: string; /** A complete, working example. */ example?: string; /** What "Einfügen" puts into the editor. */ snippet: string; /** Extra search terms (German synonyms etc.). */ keywords?: string; } export const REF_CATEGORIES: RefCategory[] = [ 'Zellen', 'Zeilen & Spalten', 'Tabellen', 'Blätter', 'Ablauf', 'Netzwerk & JSON', 'Operatoren', 'Formelfunktionen', ]; const ACTIONS: RefEntry[] = [ { contexts: ['macro'], name: 'SETVALUE', category: 'Zellen', syntax: 'SETVALUE(Ziel; Wert)', description: 'Schreibt einen Wert in eine Zelle. Ist das Ziel ein Bereich, erhält jede Zelle darin denselben Wert. Beginnt der Text mit „=", wird daraus eine echte Formel. Das Ziel muss die Tabelle nennen (Umsatz::A1, nie bloß A1).', example: 'SETVALUE(Umsatz::B2; Umsatz::B2 + 1)\nSETVALUE(Umsatz::F2; "=SUM(B2:E2)")', snippet: 'SETVALUE(Tabelle::A1; Wert)', keywords: 'schreiben setzen wert zelle bereich formel eintragen', }, { contexts: ['macro'], name: 'INSERTROW', category: 'Zeilen & Spalten', syntax: 'INSERTROW(Tabelle; Index)', description: 'Fügt eine leere Zeile ein. Index zählt ab 0: 0 fügt vor der ersten Zeile ein. Formelbezüge im ganzen Dokument werden wie beim manuellen Einfügen angepasst.', example: 'INSERTROW("Umsatz"; 1)', snippet: 'INSERTROW("Tabelle"; 0)', keywords: 'zeile einfügen hinzufügen neu', }, { contexts: ['macro'], name: 'DELETEROW', category: 'Zeilen & Spalten', syntax: 'DELETEROW(Tabelle; Index)', description: 'Löscht die Zeile mit dem Index (ab 0). Bezüge auf gelöschte Zellen werden zu #REF!.', example: 'DELETEROW("Umsatz"; 5)', snippet: 'DELETEROW("Tabelle"; 0)', keywords: 'zeile löschen entfernen', }, { contexts: ['macro'], name: 'INSERTCOL', category: 'Zeilen & Spalten', syntax: 'INSERTCOL(Tabelle; Index)', description: 'Fügt eine leere Spalte ein. Index zählt ab 0 (0 = vor Spalte A).', example: 'INSERTCOL("Umsatz"; 5)', snippet: 'INSERTCOL("Tabelle"; 0)', keywords: 'spalte einfügen hinzufügen neu', }, { contexts: ['macro'], name: 'DELETECOL', category: 'Zeilen & Spalten', syntax: 'DELETECOL(Tabelle; Index)', description: 'Löscht die Spalte mit dem Index (ab 0, 0 = Spalte A).', example: 'DELETECOL("Umsatz"; 7)', snippet: 'DELETECOL("Tabelle"; 0)', keywords: 'spalte löschen entfernen', }, { contexts: ['macro'], name: 'ADDTABLE', category: 'Tabellen', syntax: 'ADDTABLE(Blatt; Name)', description: 'Legt auf dem Blatt eine neue, leere Tabelle mit diesem Namen an.', example: 'ADDTABLE("Analyse"; "Auswertung")', snippet: 'ADDTABLE("Blatt"; "Name")', keywords: 'tabelle neu anlegen erstellen hinzufügen', }, { contexts: ['macro'], name: 'RENAMETABLE', category: 'Tabellen', syntax: 'RENAMETABLE(Tabelle; NeuerName)', description: 'Benennt eine Tabelle um. Formeln, die sie nennen, werden dokumentweit mit umgeschrieben.', example: 'RENAMETABLE("Umsatz"; "Umsatz 2026")', snippet: 'RENAMETABLE("Tabelle"; "NeuerName")', keywords: 'tabelle umbenennen name ändern', }, { contexts: ['macro'], name: 'DELETETABLE', category: 'Tabellen', syntax: 'DELETETABLE(Tabelle)', description: 'Löscht eine Tabelle. Bezüge darauf werden zu #REF!.', example: 'DELETETABLE("Blatt 2!Entwurf")', snippet: 'DELETETABLE("Tabelle")', keywords: 'tabelle löschen entfernen', }, { contexts: ['macro'], name: 'ADDSHEET', category: 'Blätter', syntax: 'ADDSHEET(Name)', description: 'Legt ein neues, leeres Blatt an. Ist der Name vergeben, wird eine Nummer angehängt.', example: 'ADDSHEET("Auswertung")', snippet: 'ADDSHEET("Name")', keywords: 'blatt neu anlegen erstellen hinzufügen sheet', }, { contexts: ['macro'], name: 'RENAMESHEET', category: 'Blätter', syntax: 'RENAMESHEET(Blatt; NeuerName)', description: 'Benennt ein Blatt um.', example: 'RENAMESHEET("Blatt 2"; "Archiv")', snippet: 'RENAMESHEET("Blatt"; "NeuerName")', keywords: 'blatt umbenennen name ändern sheet', }, { contexts: ['macro'], name: 'DELETESHEET', category: 'Blätter', syntax: 'DELETESHEET(Blatt)', description: 'Löscht ein Blatt samt Inhalt. Das letzte Blatt bleibt immer erhalten.', example: 'DELETESHEET("Archiv")', snippet: 'DELETESHEET("Blatt")', keywords: 'blatt löschen entfernen sheet', }, ]; const CONTROL_MACRO: RefEntry[] = [ { contexts: ['macro'], name: 'Zuweisung', category: 'Ablauf', syntax: 'name = Ausdruck', description: 'Speichert einen Wert unter einem Namen. Der Name bleibt für den restlichen Code sichtbar, auch nach dem Ende eines FOR/IF.', example: 'summe = SUM(Umsatz::B2:B6)\nSETVALUE(Umsatz::B8; summe)', snippet: 'name = Ausdruck', keywords: 'variable zuweisen speichern let', }, { contexts: ['macro'], name: 'FOR … IN … DO … END', category: 'Ablauf', syntax: 'FOR x IN Bereich DO … END', description: 'Wiederholt die Anweisungen für jede Zelle eines Bereichs; x ist dabei der jeweilige Zellbezug. Auch über ein JSON-Array (siehe GET).', example: 'FOR z IN Umsatz::B2:B6 DO\n SETVALUE(z; z * 1.1)\nEND', snippet: 'FOR x IN Tabelle::A1:A10 DO\n \nEND', keywords: 'schleife loop wiederholen jede zelle iteration', }, { contexts: ['macro'], name: 'IF … THEN … END', category: 'Ablauf', syntax: 'IF Bedingung THEN … END', description: 'Führt die Anweisungen nur aus, wenn die Bedingung wahr ist. Beliebig verschachtelbar.', example: 'IF Umsatz::B2 > 1000 THEN\n SETVALUE(Umsatz::C2; "Ziel erreicht")\nEND', snippet: 'IF Bedingung THEN\n \nEND', keywords: 'wenn bedingung verzweigung falls', }, { contexts: ['macro'], name: 'IF … THEN … ELSE … END', category: 'Ablauf', syntax: 'IF Bedingung THEN … ELSE … END', description: 'Wie IF, mit einem zweiten Zweig, der läuft, wenn die Bedingung falsch ist.', example: 'IF Umsatz::B2 > 0 THEN\n SETVALUE(Umsatz::C2; "Plus")\nELSE\n SETVALUE(Umsatz::C2; "Minus")\nEND', snippet: 'IF Bedingung THEN\n \nELSE\n \nEND', keywords: 'wenn sonst bedingung verzweigung', }, { contexts: ['macro'], name: 'RETURN', category: 'Ablauf', syntax: 'RETURN "Meldung"', description: 'Beendet das Makro sofort, auch mitten in einer Schleife. Der Text erscheint als Erfolgsmeldung statt „Makro ausgeführt.".', example: 'IF Umsatz::A2 = "" THEN RETURN "Nichts zu tun" END', snippet: 'RETURN "Fertig"', keywords: 'abbrechen beenden stop meldung', }, ]; const NETWORK: RefEntry[] = [ { contexts: ['macro'], name: 'GET', category: 'Netzwerk & JSON', syntax: 'r = GET(URL)', description: 'Ruft eine Adresse ab und speichert die JSON-Antwort in r. Nur als ganze rechte Seite einer Zuweisung erlaubt. Beim ersten Kontakt zu einem neuen Host fragt die App einmalig nach.', example: 'r = GET("https://api.example.com/kurse")\nSETVALUE(Kurse::B1; JSONGET(r; "eur.chf"))', snippet: 'r = GET("https://")', keywords: 'http abrufen laden api web url fetch request', }, { contexts: ['macro'], name: 'POST', category: 'Netzwerk & JSON', syntax: 'r = POST(URL; Body)', description: 'Sendet den Body-Text als JSON (Content-Type: application/json) und speichert die Antwort in r. Den Body setzt du selbst als Text zusammen, z. B. mit &; Anführungszeichen im Text schreibst du als \\".', example: 'r = POST("https://api.example.com/log"; "{\\"summe\\": " & Umsatz::B8 & "}")', snippet: 'r = POST("https://"; "{}")', keywords: 'http senden api web url request', }, { contexts: ['macro'], name: 'JSONGET', category: 'Netzwerk & JSON', syntax: 'JSONGET(Wert; "pfad")', description: 'Liest ein Feld aus einem JSON-Ergebnis. Pfad punktgetrennt, Zahlen indizieren Arrays ("daten.0.name"). Ein fehlender Pfad ergibt null, keinen Fehler.', example: 'r = GET("https://api.example.com/produkte")\nSETVALUE(Kurse::A1; JSONGET(r; "daten.0.name"))', snippet: 'JSONGET(r; "pfad")', keywords: 'json feld lesen pfad objekt', }, { contexts: ['macro'], name: 'FOR über JSON-Array', category: 'Netzwerk & JSON', syntax: 'FOR item IN r DO … END', description: 'Durchläuft die Elemente eines JSON-Arrays (z. B. aus GET oder JSONGET).', example: 'r = GET("https://api.example.com/produkte")\nFOR p IN JSONGET(r; "items") DO\n INSERTROW("Produkte"; 1)\n SETVALUE(Produkte::A2; JSONGET(p; "name"))\nEND', snippet: 'FOR item IN JSONGET(r; "pfad") DO\n \nEND', keywords: 'schleife array liste json iteration', }, ]; const OPERATORS_MACRO: RefEntry[] = [ { contexts: ['macro'], name: 'Rechnen', category: 'Operatoren', syntax: '+ - * / ^', description: 'Grundrechenarten und Potenz. ^ bindet am stärksten, dann * und /, dann + und -.', example: 'SETVALUE(Umsatz::H3; (Umsatz::B2 + Umsatz::C2) / 2)', snippet: ' + ', keywords: 'plus minus mal geteilt potenz rechnen', }, { contexts: ['macro'], name: 'Text verketten', category: 'Operatoren', syntax: 'Text & Text', description: 'Hängt Texte und Zahlen aneinander.', example: 'SETVALUE(Umsatz::A20; "Stand: " & TODAY())', snippet: ' & ', keywords: 'verketten zusammenfügen text string', }, { contexts: ['macro'], name: 'Vergleichen', category: 'Operatoren', syntax: '= <> < <= > >=', description: 'Vergleiche für IF-Bedingungen. Texte werden ohne Groß-/Kleinschreibung verglichen.', example: 'IF Umsatz::B2 >= 1000 THEN … END', snippet: ' >= ', keywords: 'gleich ungleich größer kleiner vergleich', }, { contexts: ['macro'], name: 'Bezüge', category: 'Operatoren', syntax: 'Tabelle::A1 Blatt!Tabelle::A1:B5', description: 'In Makros muss jeder Zell- und Bereichsbezug die Tabelle nennen. Namen mit Leerzeichen in einfache Anführungszeichen setzen: \'Umsatz 2026\'::A1.', example: "SETVALUE('Blatt 2'!Kosten::B2; 0)", snippet: 'Tabelle::A1', keywords: 'zelle bereich referenz adresse tabelle blatt', }, ]; // Function-body counterparts: same grammar, but no write actions -- examples // compute and RETURN a value instead of writing it. Parameter names (bereich, // umsatz, ...) stand for whatever the function declares. const CONTROL_FUNCTION: RefEntry[] = [ { contexts: ['block'], name: 'Zuweisung', category: 'Ablauf', syntax: 'name = Ausdruck', description: 'Speichert einen Zwischenwert unter einem Namen. Der Name bleibt für den restlichen Code sichtbar, auch nach dem Ende eines FOR/IF.', example: 'marge = (umsatz - kosten) / umsatz\nRETURN marge * 100', snippet: 'name = Ausdruck', keywords: 'variable zuweisen speichern zwischenwert', }, { contexts: ['block'], name: 'FOR … IN … DO … END', category: 'Ablauf', syntax: 'FOR x IN bereich DO … END', description: 'Wiederholt die Anweisungen für jedes Element eines Bereichs. Die Quelle ist ein Parameter, eine Zelle oder ein Bereich, kein berechneter Ausdruck.', example: 'ergebnis = 1\nFOR x IN bereich DO\n ergebnis = ergebnis * x\nEND\nRETURN ergebnis', snippet: 'FOR x IN bereich DO\n \nEND', keywords: 'schleife loop wiederholen jedes element iteration', }, { contexts: ['block'], name: 'IF … THEN … END', category: 'Ablauf', syntax: 'IF Bedingung THEN … END', description: 'Führt die Anweisungen nur aus, wenn die Bedingung wahr ist. Beliebig verschachtelbar, auch in einer Schleife.', example: 'IF umsatz = 0 THEN RETURN 0 END\nRETURN (umsatz - kosten) / umsatz', snippet: 'IF Bedingung THEN\n \nEND', keywords: 'wenn bedingung verzweigung falls', }, { contexts: ['block'], name: 'IF … THEN … ELSE … END', category: 'Ablauf', syntax: 'IF Bedingung THEN … ELSE … END', description: 'Wie IF, mit einem zweiten Zweig, der läuft, wenn die Bedingung falsch ist.', example: 'IF umsatz > kosten THEN\n RETURN "Gewinn"\nELSE\n RETURN "Verlust"\nEND', snippet: 'IF Bedingung THEN\n \nELSE\n \nEND', keywords: 'wenn sonst bedingung verzweigung', }, { contexts: ['block'], name: 'RETURN', category: 'Ablauf', syntax: 'RETURN Ausdruck', description: 'Liefert den Funktionswert und beendet die Ausführung sofort, auch mitten in einer Schleife oder einem IF-Zweig.', example: 'FOR x IN bereich DO\n IF x < 0 THEN RETURN "negativ" END\nEND\nRETURN "ok"', snippet: 'RETURN ', keywords: 'ergebnis zurückgeben beenden abbrechen', }, ]; const OPERATORS_FUNCTION: RefEntry[] = [ { contexts: ['block', 'expression'], name: 'Rechnen', category: 'Operatoren', syntax: '+ - * / ^', description: 'Grundrechenarten und Potenz. ^ bindet am stärksten, dann * und /, dann + und -.', example: '(umsatz - kosten) / umsatz * 100', snippet: ' + ', keywords: 'plus minus mal geteilt potenz rechnen', }, { contexts: ['block', 'expression'], name: 'Text verketten', category: 'Operatoren', syntax: 'Text & Text', description: 'Hängt Texte und Zahlen aneinander.', example: 'CONCAT("Marge: "; (umsatz - kosten) / umsatz * 100) & " %"', snippet: ' & ', keywords: 'verketten zusammenfügen text string', }, { contexts: ['block', 'expression'], name: 'Vergleichen', category: 'Operatoren', syntax: '= <> < <= > >=', description: 'Vergleiche für IF. Texte werden ohne Groß-/Kleinschreibung verglichen.', example: 'IF(umsatz >= kosten; "Gewinn"; "Verlust")', snippet: ' >= ', keywords: 'gleich ungleich größer kleiner vergleich', }, { contexts: ['block', 'expression'], name: 'Bereich als Parameter', category: 'Operatoren', syntax: 'SUM(bereich)', description: 'Ein Parameter kann ein Bereich sein, wenn er direkt an eine Funktion wie SUM, AVG, MIN oder MAX geht. Aufruf dann z. B. =SPANNE(Umsatz!B2:E6).', example: 'MAX(bereich) - MIN(bereich)', snippet: 'SUM(bereich)', keywords: 'bereich parameter range argument', }, ]; const FUNCTION_DESCRIPTIONS: Record = { SUM: 'Summe der Werte.', AVG: 'Mittelwert der Werte.', AVERAGE: 'Mittelwert der Werte (wie AVG).', MIN: 'Kleinster Wert.', MAX: 'Größter Wert.', COUNT: 'Anzahl der Zahlen.', COUNT2: 'Anzahl der nicht leeren Zellen.', CONCAT: 'Verkettet Texte.', CONCATENATE: 'Verkettet Texte (wie CONCAT).', IF: 'Wählt je nach Bedingung einen von zwei Werten.', AND: 'Wahr, wenn alle Bedingungen wahr sind.', OR: 'Wahr, wenn mindestens eine Bedingung wahr ist.', NOT: 'Kehrt eine Bedingung um.', DATE: 'Datum aus Jahr, Monat und Tag.', TIME: 'Uhrzeit aus Stunden, Minuten und Sekunden.', TODAY: 'Heutiges Datum.', NOW: 'Aktuelles Datum mit Uhrzeit.', XLOOKUP: 'Sucht einen Wert und gibt den passenden Eintrag aus einem zweiten Bereich zurück.', YEAR: 'Jahr eines Datums.', MONTH: 'Monat eines Datums (1–12).', DAY: 'Tag des Monats (1–31).', HOUR: 'Stunde einer Uhrzeit (0–23).', MINUTE: 'Minute einer Uhrzeit (0–59).', SECOND: 'Sekunde einer Uhrzeit (0–59).', WEEKDAY: 'Wochentag nach ISO: Montag = 1 … Sonntag = 7. Typ 1: Sonntag = 1, Typ 3: Montag = 0.', ISOWEEKNUM: 'Kalenderwoche nach ISO 8601 (Woche 1 enthält den ersten Donnerstag des Jahres).', WEEKNUM: 'Kalenderwoche, standardmäßig nach ISO. Typ 1/2: US-Zählung mit Wochenbeginn Sonntag/Montag.', EDATE: 'Gleicher Tag einige Monate später (negativ: früher), am Monatsende gekürzt.', EOMONTH: 'Letzter Tag des Monats, einige Monate vor oder nach dem Datum.', DAYS: 'Anzahl Kalendertage zwischen zwei Daten.', DATEDIF: 'Abstand zweier Daten in "Y" Jahren, "M" Monaten, "D" Tagen oder als Rest "YM", "MD", "YD".', NETWORKDAYS: 'Arbeitstage (Mo–Fr) zwischen zwei Daten, beide eingeschlossen, ohne Feiertage.', WORKDAY: 'Datum nach einer Anzahl Arbeitstage (Mo–Fr), Feiertage werden übersprungen.', DATEVALUE: 'Datum aus einem Text wie "2026-09-02" oder "02.09.2026".', TIMEVALUE: 'Uhrzeit aus einem Text wie "14:30".', }; /** German search terms for built-ins whose description doesn't already say it. */ const FUNCTION_KEYWORDS: Record = { SUM: 'summe addieren', AVG: 'durchschnitt mittelwert', AVERAGE: 'durchschnitt mittelwert', COUNT: 'anzahl zählen', COUNT2: 'anzahl zählen', IF: 'wenn falls bedingung', AND: 'und', OR: 'oder', NOT: 'nicht', TODAY: 'heute datum', NOW: 'jetzt zeit', XLOOKUP: 'suchen sverweis verweis nachschlagen', YEAR: 'jahr datum', MONTH: 'monat datum', DAY: 'tag datum', HOUR: 'stunde zeit uhrzeit', MINUTE: 'minute zeit uhrzeit', SECOND: 'sekunde zeit uhrzeit', WEEKDAY: 'wochentag tag woche', ISOWEEKNUM: 'kalenderwoche kw woche', WEEKNUM: 'kalenderwoche kw woche', EDATE: 'monat addieren datum', EOMONTH: 'monatsende ultimo', DAYS: 'tage differenz abstand', DATEDIF: 'differenz abstand alter jahre monate tage', NETWORKDAYS: 'arbeitstage werktage feiertage', WORKDAY: 'arbeitstag werktag feiertage frist', DATEVALUE: 'datum text umwandeln', TIMEVALUE: 'zeit uhrzeit text umwandeln', }; const FUNCTIONS: RefEntry[] = BUILTIN_SIGNATURES.map((s) => ({ contexts: ['macro', 'block', 'expression'], name: s.name, category: 'Formelfunktionen' as const, syntax: s.hint, description: FUNCTION_DESCRIPTIONS[s.name] ?? 'Formelfunktion.', keywords: FUNCTION_KEYWORDS[s.name], snippet: `${s.name}()`, })); export const CODE_REFERENCE: RefEntry[] = [ ...ACTIONS, ...CONTROL_MACRO, ...CONTROL_FUNCTION, ...NETWORK, ...OPERATORS_MACRO, ...OPERATORS_FUNCTION, ...FUNCTIONS, ]; export const referenceFor = (ctx: RefContext): RefEntry[] => CODE_REFERENCE.filter((e) => e.contexts.includes(ctx)); /** Case-insensitive match over name, syntax, description and keywords. */ export function matchesQuery( e: Pick, query: string, ): boolean { const words = query.toLowerCase().split(/\s+/).filter(Boolean); if (!words.length) return true; const hay = `${e.name} ${e.syntax} ${e.description} ${e.keywords ?? ''}`.toLowerCase(); return words.every((w) => hay.includes(w)); }