Skip to content

Commit 246fa6f

Browse files
authored
feat(vnext): add CodeMirror completion adapter (#199)
## Summary - add an explicit `@marimo-team/codemirror-sql/vnext/codemirror` entry point with one caller-owned service and one session per `EditorView` - translate composite CodeMirror document, context, and embedded-region effects into one atomic session update - map current relation results to exact CodeMirror edits with revision, document, selection, context, and embedded-region guards - coalesce exact-token catalog refreshes across active menus and empty loading results without synchronous provider-driven dispatch - contain Escape, close, blur, visibility, expiry, service-first disposal, stale rejection, and repeated teardown races ## Why The framework-independent service now has the identity and lease contracts needed for a thin editor boundary. This adapter makes those contracts usable without leaking CodeMirror into the core, while preserving marimo's external completion sources and treating transformed-document regions explicitly rather than guessing their mapping. ## Validation - 2,028 tests pass; 1 intentional expected failure - adapter coverage: 97.70% statements, 95.86% branches, 100% functions, 99.01% lines - all strict and loose-optional TypeScript configurations pass - oxlint and test-integrity checks pass - packed-consumer source covers the new package subpath; hosted package CI will run the isolated install - two independent exact-commit adversarial reviews approved `8d72a4c` <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Adds a CodeMirror 6 adapter for the vNext SQL language service, exposed as `@marimo-team/codemirror-sql/vnext/codemirror`. This enables exact, guarded completion edits and atomic document/context/region updates without leaking CodeMirror into the core. - **New Features** - New `sqlEditor` adapter with one document session per `EditorView`; caller owns the shared `service`. - Atomic updates: merges document changes with `support.contextEffect` and `support.embeddedRegionsEffect`. - Exact completion edits with revision/context/selection guards; maps unrelated embedded regions through its own edit; ignores overlaps. - Coalesced refresh intents keyed by service tokens; cancels on Escape, blur (configurable), visibility loss, selection change, or lease expiry; no synchronous provider-driven dispatch. - Single `autocompletion` configuration; supports `externalSources` for extra providers. - New docs: `docs/vnext/codemirror-adapter.md`; subpath export added in `package.json`; smoke test updated. - **Migration** - Import from `@marimo-team/codemirror-sql/vnext/codemirror`, create a service from `@marimo-team/codemirror-sql/vnext`, then add `support.extension` to the editor. - Do not install a second `autocompletion` extension; pass extra providers via `externalSources`. - Update context/regions via `support.setContext` or by dispatching `support.contextEffect` and `support.embeddedRegionsEffect`. If embedded regions exist, include the complete resulting set on every document-changing transaction. <sup>Written for commit 8d72a4c. Summary will update on new commits.</sup> <a href="https://cubic.dev/pr/marimo-team/codemirror-sql/pull/199?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
1 parent 1b4c1af commit 246fa6f

7 files changed

Lines changed: 1960 additions & 0 deletions

File tree

docs/vnext/codemirror-adapter.md

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# vNext CodeMirror Adapter
2+
3+
Status: experimental
4+
5+
Import: `@marimo-team/codemirror-sql/vnext/codemirror`
6+
7+
`sqlEditor` connects one caller-owned vNext language service to one or more
8+
CodeMirror 6 views. Each view owns a document session, cancellation state,
9+
catalog-refresh intent, and revision subscription. Destroying a view disposes
10+
that view's session but never disposes the shared service.
11+
12+
```ts
13+
import { EditorView } from "@codemirror/view";
14+
import {
15+
createSqlLanguageService,
16+
duckdbDialect,
17+
} from "@marimo-team/codemirror-sql/vnext";
18+
import {
19+
sqlEditor,
20+
} from "@marimo-team/codemirror-sql/vnext/codemirror";
21+
22+
const service = createSqlLanguageService({
23+
dialects: [duckdbDialect()],
24+
});
25+
const support = sqlEditor({
26+
initialContext: {
27+
catalog: { scope: "connection-incarnation:1" },
28+
dialect: "duckdb",
29+
},
30+
service,
31+
});
32+
const view = new EditorView({
33+
doc: "SELECT * FROM ",
34+
extensions: [support.extension],
35+
});
36+
37+
support.setContext(view, {
38+
catalog: { scope: "connection-incarnation:2" },
39+
dialect: "duckdb",
40+
});
41+
42+
view.destroy();
43+
service.dispose();
44+
```
45+
46+
The adapter installs one coherent `autocompletion` configuration. Additional
47+
sources belong in `autocomplete.externalSources`; consumers should not install
48+
a second independently configured `autocompletion` extension.
49+
50+
## Atomic inputs
51+
52+
Context and document changes can share one CodeMirror transaction:
53+
54+
```ts
55+
view.dispatch({
56+
changes,
57+
effects: support.contextEffect.of(nextContext),
58+
});
59+
```
60+
61+
CodeMirror's composite `ChangeSet` is converted to sorted, non-overlapping
62+
pre-update UTF-16 changes and sent to the session exactly once.
63+
64+
Embedded-language ranges are explicit. Their effect payload is the complete
65+
region set in the resulting document's coordinates:
66+
67+
```ts
68+
view.dispatch({
69+
changes,
70+
effects: support.embeddedRegionsEffect.of(nextRegions),
71+
});
72+
```
73+
74+
When the current document contains embedded regions, every document-changing
75+
transaction must include the complete resulting region set. The adapter fails
76+
closed instead of guessing how host-language delimiters map through edits.
77+
Region-only updates may use `setEmbeddedRegions`.
78+
79+
The adapter's own completion edit is the narrow exception: it maps unrelated
80+
regions through the exact edit and includes the complete mapped set in the same
81+
transaction. A completion edit that overlaps an embedded region is ignored.
82+
83+
## Completion currency
84+
85+
The adapter does not use `validFor` or result mapping in this first slice.
86+
Every result and completion application rechecks the session revision,
87+
document identity, selection, and context generation.
88+
89+
Loading completion results retain one bounded, token-correlated refresh intent,
90+
including empty results that open no menu. Only a session event carrying the
91+
exact task token can schedule a refresh. A newer request, document/context/
92+
region/selection change, Escape, configured blur, visibility loss, lease
93+
expiry, or view destruction cancels the intent. Refresh and close operations
94+
are always queued; provider and session callbacks never synchronously dispatch
95+
CodeMirror transactions.

docs/vnext/session-primitives.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ opaque revisions, dialect registration, lifecycle management, and
88
parser-independent relation completion. Diagnostics, hover, navigation, and
99
general expression completion are not yet available.
1010

11+
CodeMirror consumers should use the separate
12+
[vNext CodeMirror adapter](./codemirror-adapter.md).
13+
1114
See [source coordinates](./source-coordinates.md) for the shared UTF-16 range
1215
contract and the internal immutable source-snapshot model.
1316

package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,12 @@
9393
"default": "./dist/vnext/index.js"
9494
}
9595
},
96+
"./vnext/codemirror": {
97+
"import": {
98+
"types": "./dist/vnext/codemirror/index.d.ts",
99+
"default": "./dist/vnext/codemirror/index.js"
100+
}
101+
},
96102
"./data/common-keywords.json": "./src/data/common-keywords.json",
97103
"./data/duckdb-keywords.json": "./src/data/duckdb-keywords.json"
98104
},

scripts/package-smoke.mjs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ import {
231231
type SqlEmbeddedRegion,
232232
type SqlTextRange,
233233
} from "@marimo-team/codemirror-sql/vnext";
234+
import { sqlEditor } from "@marimo-team/codemirror-sql/vnext/codemirror";
234235
import commonKeywords from "@marimo-team/codemirror-sql/data/common-keywords.json" with { type: "json" };
235236
import duckdbKeywords from "@marimo-team/codemirror-sql/data/duckdb-keywords.json" with { type: "json" };
236237
@@ -246,6 +247,10 @@ const parser = new NodeSqlParser();
246247
const service = createSqlLanguageService<HostContext>({
247248
dialects: [duckdbDialect()],
248249
});
250+
const editorSupport = sqlEditor({
251+
initialContext: { dialect: "duckdb", engine: "local" },
252+
service,
253+
});
249254
const embeddedRegions: readonly SqlEmbeddedRegion[] = [
250255
{ from: 14, language: "python", to: 18 },
251256
];
@@ -262,6 +267,7 @@ session.update({
262267
});
263268
264269
void extensions;
270+
void editorSupport.extension;
265271
void parser;
266272
void range;
267273
void session;
@@ -278,6 +284,7 @@ void duckdbKeywords;
278284
import * as api from "@marimo-team/codemirror-sql";
279285
import * as dialects from "@marimo-team/codemirror-sql/dialects";
280286
import * as vnext from "@marimo-team/codemirror-sql/vnext";
287+
import * as vnextCodeMirror from "@marimo-team/codemirror-sql/vnext/codemirror";
281288
import commonKeywords from "@marimo-team/codemirror-sql/data/common-keywords.json" with { type: "json" };
282289
import duckdbKeywords from "@marimo-team/codemirror-sql/data/duckdb-keywords.json" with { type: "json" };
283290
@@ -296,6 +303,9 @@ if (
296303
) {
297304
throw new Error("vNext package exports are incomplete");
298305
}
306+
if (typeof vnextCodeMirror.sqlEditor !== "function") {
307+
throw new Error("vNext CodeMirror package exports are incomplete");
308+
}
299309
for (const dialect of [
300310
vnext.bigQueryDialect(),
301311
vnext.dremioDialect(),

0 commit comments

Comments
 (0)