Editor: CodeLens + completion to scaffold Vega-Lite params; shared snippet core

This commit is contained in:
2026-07-02 22:15:50 +03:00
parent c5e4c4c76d
commit e42a535726
15 changed files with 1160 additions and 281 deletions
+34 -1
View File
@@ -294,6 +294,19 @@ logic is pure `src/core/`; the Monaco glue is thin app-layer services.
calculate, aggregate, bin, timeUnit, window, joinaggregate, fold, lookup); each builder takes the
columns in scope and seeds each `${n:default}` tab stop with a type-appropriate one. A test
round-trips every snippet through `snippetToPlain` to assert it's valid JSON.
- `spec-params` — the parameter counterpart: `paramSiteAt` resolves both homes a parameter can take
(the **root** spec for a variable widget, the **nearest enclosing unit** for a selection —
grammar-confirmed against the bundled schema: a nested unit's `params[]` takes selections only),
and `paramPlacementAt` classifies a `params[]` slot (root → both families, nested → selections only)
for the completion. `PARAMS` is the catalog (slider, dropdown, radio, checkbox; point, interval); a
slider's bounds seed from the numeric field's `numericExtent`, a point selection's field from a
categorical column. A test validates every seeded entry against the bundled Vega-Lite schema.
- `spec-snippet` — the snippet-insertion text math both scaffolds share: `snippetToPlain`
(tab stops → default text, so catalogs and edits round-trip through `JSON.parse` in tests),
`arrayAffixes`/`appendEntryEdit` (the comma affixing that keeps a spliced array valid), and
`createArrayPropertyEdit` (the inline `"key": [ … ]` first-property insertion and its indent
math). Pure `(text, site) → { offset, snippet }` — a bug here writes invalid JSON into the
user's editor, so the edits are table-tested applied-and-reparsed, off the Monaco glue.
- `spec-data` — the Vega-Lite data model: classify a `data` block (`classifyData`, mirroring
`isNamedData`), the library reference name (`libraryRefName`), and the data binding in scope
at a cursor path (`dataBindingAtPath`, honoring a view's data inheritance from its parent).
@@ -344,7 +357,8 @@ logic is pure `src/core/`; the Monaco glue is thin app-layer services.
**Services (app, store-aware via `getState`):** `spec-transform-actions` (the
wrap/simplify/add-view operations and their surfaces), `spec-transform-scaffold` (the
data-transform scaffold — a CodeLens plus a completion), `spec-dataset-hints` (data-column completion, hover,
data-transform scaffold — a CodeLens plus a completion), `spec-param-scaffold` (the parameter
scaffold — likewise a CodeLens plus a completion), `spec-dataset-hints` (data-column completion, hover,
inlay providers), `spec-expression-hints` (expression completion, signature help, hover, and
the diagnostic markers), `active-dataset` (`dataInfoAt(text, offset)` — the columns/types/stats
plus derived fields the draft sees at the cursor). `SpecEditor` does the wiring.
@@ -356,6 +370,14 @@ Decision rules:
schema and formatter; per-editor registration would duplicate them on remount. Pieces that
need the editor handle — the `addAction` context/F1 commands, and the CodeLens whose command
runs `executeEdits` — are installed **per editor** and disposed with it.
- **Cursor-aware CodeLenses share one skeleton.** The composition lens
(`spec-transform-actions`), the transform scaffold, and the parameter scaffold are the same
per-editor shape: a draft-gated provider that re-resolves the site under the cursor and refreshes
only when a keyed summary of it changes. That skeleton is `installCursorLens(editor, { resolve,
keyOf, lensesOf })` (`services/editor-cursor-lens`) — a new cursor lens supplies those three and its
own commands, nothing else. The tab-through snippet splice and the completion-slot plumbing both
scaffolds perform are shared the same way via `editor-snippet` — the Monaco glue over
`core/spec-snippet`'s tested edit math.
- **Transform scope.** A transform targets, in order: an explicit selection → the view the
cursor sits in (`findViewRange`) → the whole document. A "view" is a composition-array
element or a facet/repeat `spec` child; a flat unit spec has no inner view, so it scopes to
@@ -434,6 +456,17 @@ Decision rules:
the same catalog. A step's `scope` (`shared` when the array is on a composition parent, so it
feeds every child) is surfaced so the placement is not a surprise, and comma affixing keeps the
array valid whether the slot is empty, between elements, or appended after one without a trailing comma.
- **Parameter scaffolding splits by family, because `params` has two homes.** Unlike a `transform`
(on every view node), a **variable** widget (slider/dropdown/radio/checkbox, bound via `bind`) is only
legal in the **root** `params[]` — a document-global input any view's `filter` can read — while a
**selection** (point/interval, via `select`) attaches to the **unit** whose marks it reads. So the
scaffold does not reuse `transformSiteAt`: `paramSiteAt` resolves both homes, and the CodeLens is
cursor-scoped to match — variable widgets show at the top level (or a single-view spec, where the
root _is_ the unit, so both families land on one line), selections on the unit the cursor is in, and a
nested unit hides the variable widgets it cannot hold. The completion offers both families in the root
array, selections only in a nested unit's. Defaults are data-seeded where the data allows (a slider's
`min`/`max` from the numeric field's extent, a point's `fields` from a categorical column) and
structured placeholders otherwise (a dropdown's options).
- **Code-action menu icons are kind-derived** (a wrench for the `refactor.*` kinds) — Monaco's
`CodeAction` carries no icon field. Custom iconography lives only where it is supported:
CodeLens titles (`$(codicon)`), completion-item kinds, and glyph-margin decorations.