Add in-app help, formula reference outlines, underline and auto-closing
- Help dialog (?, F1, Datei > Hilfe): user topics, all shortcuts and the formula functions with one search over everything. Topics and shortcuts are plain data in data/help.ts; the function list comes from codeReference.ts so new built-ins appear by themselves. - While a formula is edited, the grid outlines each referenced cell/range in the colour of its position in the text, and the outline can be dragged to move or resize the reference, as in Excel. Point-mode picks (click, drag, arrow keys) are outlined too, so keyboard picking is no longer blind. - Underline as a cell format, plus Mod+Shift+B/I/U toggles that switch on unless every selected cell already has the format. - A formula committed with open parentheses or an open text literal gets the missing closers appended; anything that still doesn't parse is left alone so the usual error shows instead of a guess. - Cells and tables with a fill but no text colour get dark or light text by WCAG contrast instead of the theme colour (no black on black).
This commit is contained in:
@@ -763,6 +763,53 @@ export function parseFormula(src: string, paramNames?: readonly string[]): Node
|
||||
return new Parser(toks, paramNames, paramNames === undefined).parse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Repair the one mistake that is almost always just forgotten typing: a
|
||||
* formula committed with Enter while parentheses (or a "..." text literal)
|
||||
* are still open, e.g. `=SUMME(A1:A5` or `=WENN(A1>0;"ja`. Same as Excel and
|
||||
* Numbers, the missing closers are appended at the end.
|
||||
*
|
||||
* Deliberately conservative: anything that already parses, isn't a formula,
|
||||
* or still doesn't parse after closing (`=SUMME(A1;`, a stray `)` too many)
|
||||
* comes back unchanged, so the cell shows its usual parse error instead of a
|
||||
* "repair" that guessed something the user never meant.
|
||||
*/
|
||||
export function completeFormula(input: string): string {
|
||||
if (!input.startsWith('=')) return input;
|
||||
const parses = (src: string) => {
|
||||
try {
|
||||
parseFormula(src);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
if (parses(input)) return input;
|
||||
|
||||
let fixed = input;
|
||||
let toks: Token[];
|
||||
try {
|
||||
toks = tokenize(fixed.slice(1));
|
||||
} catch (e) {
|
||||
// an unterminated "..." literal swallows everything up to the end, so
|
||||
// closing it first is the only way to count the parentheses before it
|
||||
if (!(e instanceof LexError) || !e.message.startsWith('Unterminated string')) return input;
|
||||
fixed += '"';
|
||||
try {
|
||||
toks = tokenize(fixed.slice(1));
|
||||
} catch {
|
||||
return input;
|
||||
}
|
||||
}
|
||||
let depth = 0;
|
||||
for (const t of toks) {
|
||||
if (t.type === 'LPAREN') depth++;
|
||||
else if (t.type === 'RPAREN' && --depth < 0) return input;
|
||||
}
|
||||
fixed += ')'.repeat(depth);
|
||||
return fixed !== input && parses(fixed) ? fixed : input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a "block" function body -- the statement grammar (assignments,
|
||||
* `FOR`/`DO`/`END`, `IF`/`THEN`/`ELSE`/`END`, `RETURN` anywhere) used by a
|
||||
|
||||
Reference in New Issue
Block a user