Editor: Vega-Lite expression intelligence; single-home render errors

This commit is contained in:
2026-07-01 05:00:47 +03:00
parent 9b2618cac6
commit da6a675982
20 changed files with 1001 additions and 260 deletions
+57
View File
@@ -85,6 +85,63 @@ export function referencedFields(expr: string): string[] {
return fields;
}
/** The function call a cursor sits inside, and which argument it is on. */
export interface ActiveCall {
/** The called function's name (the identifier before the open paren). */
name: string;
/** Zero-based index of the argument the cursor is in (commas seen so far). */
activeParam: number;
}
/**
* Given the expression text from its start up to the cursor, the innermost
* function call the cursor sits inside — its name and the argument index — or
* `null` when the cursor is not within a call. Used to drive editor signature
* help. A forward scan keeps a stack of bracket frames (parens and square
* brackets), skips string literals, and counts commas per frame; the nearest
* unclosed frame whose open paren follows an identifier is the active call, and
* that frame's comma count is the active argument.
*/
export function activeCall(prefix: string): ActiveCall | null {
interface Frame {
name: string | null;
commas: number;
}
const stack: Frame[] = [];
for (let i = 0; i < prefix.length; i++) {
const c = prefix[i];
if (c === '\\') {
i++; // an escape consumes the next character
continue;
}
if (c === '"' || c === "'") {
// Skip a string literal so its parens/commas don't disturb the scan.
const quote = c;
i++;
while (i < prefix.length && prefix[i] !== quote) {
if (prefix[i] === '\\') i++;
i++;
}
continue;
}
if (c === '(') {
const name = /([A-Za-z_$][A-Za-z0-9_$]*)\s*$/.exec(prefix.slice(0, i));
stack.push({ name: name ? name[1] : null, commas: 0 });
} else if (c === '[') {
stack.push({ name: null, commas: 0 });
} else if (c === ')' || c === ']') {
stack.pop();
} else if (c === ',' && stack.length > 0) {
stack[stack.length - 1].commas++;
}
}
for (let i = stack.length - 1; i >= 0; i--) {
const frame = stack[i];
if (frame.name !== null) return { name: frame.name, activeParam: frame.commas };
}
return null;
}
/**
* The static field name of a `datum.<name>` / `datum['name']` member access, or
* `null` when the node isn't such an access (a different object, computed-dynamic