mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
Editor: Vega-Lite expression intelligence; single-home render errors
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user