Skip to content

Commit 8d72a4c

Browse files
committed
feat(vnext): add CodeMirror completion adapter
1 parent 1b4c1af commit 8d72a4c

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)