@coldsmirk/inkstone-codemirror 0.9.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,16 +1,18 @@
1
1
  # @coldsmirk/inkstone-codemirror
2
2
 
3
- Framework-agnostic [CodeMirror 6](https://codemirror.net/) plumbing: a controlled editor host (create-once, external-value reconcile, in-place theme swap), the standard document extension bundle, lazy per-language grammar loading, and precise [MiniJinja](https://github.com/mitsuhiko/minijinja) template support with schema-driven context completion.
3
+ Framework-agnostic [CodeMirror 6](https://codemirror.net/) plumbing: controlled editor and merge-view hosts (create-once, external-value reconcile, in-place theme swap), the standard document extension bundle, lazy per-language grammar loading, and precise [MiniJinja](https://github.com/mitsuhiko/minijinja) template support with schema-driven context completion.
4
4
 
5
5
  Part of [inkstone](https://github.com/coldsmirk/inkstone). For a drop-in React component use [`@coldsmirk/inkstone-react`](https://www.npmjs.com/package/@coldsmirk/inkstone-react)'s `<CodeMirrorEditor>`, which is built on this package; reach for this layer to wire the same plumbing into your own components (any framework).
6
6
 
7
7
  ## What you get
8
8
 
9
9
  - **`ControlledEditorHost`** — the controlled-editor lifecycle solved once: the view is created once, external value changes reconcile into the document without resetting cursor/undo, and theme/extension swaps happen in place.
10
+ - **`ControlledMergeHost`** — the same discipline for [`@codemirror/merge`](https://github.com/codemirror/merge)'s side-by-side `MergeView`: both documents reconcile per side without caret resets or callback echo, dynamic extensions swap in place on both editors, and every merge-level option (`orientation`, `revertControls`, `collapseUnchanged`, …) passes straight through. Policy-free — pin either side read-only via `originalExtensions` / `modifiedExtensions` to match how your documents are controlled.
10
11
  - **`documentExtensions(options)`** — the standard document editing bundle (history, brackets, completion UI, optional line numbers / wrapping / search), so hosts start from working defaults instead of a bare `EditorState`.
11
12
  - **`codeMirrorLanguages` / `loadLanguage(language)`** — the supported `CodeMirrorLanguage` names and their lazy loaders: one dynamic `import()` per grammar, so only the selected language is fetched, never the whole catalog. Two tiers back the ids: the official `@codemirror/lang-*` (Lezer) parsers where upstream ships one, plus the full [`@codemirror/legacy-modes`](https://github.com/codemirror/legacy-modes) catalog (stream-mode highlighting) for everything upstream never wrote a Lezer grammar for — `shell`, `dockerfile`, `ini` / `env` / `properties`, `toml`, `nginx`, `powershell`, `lua`, and a hundred more.
12
- - **MiniJinja** — `language: "minijinja"` for standalone templates, or `minijinja-<host>` mixed variants (`minijinja-html`, `-xml`, `-json`, `-yaml`, `-css`, `-scss`, `-sass`, `-less`, `-markdown`; the SQL family — `-sql` plus every dialect id: `-mysql`, `-pgsql`, `-sqlite`, `-mssql`, `-mariadb`, `-plsql`, `-cassandra`, `-hive`, `-sparksql`, `-gql`, `-gpsql`, `-esper`; and the config/deploy hosts `-toml`, `-ini`, `-env`, `-shell`, `-dockerfile`, `-nginx`) that overlay MiniJinja tooling on the host grammar. A linter flags non-MiniJinja tags; autocomplete is restricted to MiniJinja's real tags / filters / tests / functions.
13
+ - **MiniJinja** — `language: "minijinja"` for standalone templates, or `minijinja-<host>` mixed variants (`minijinja-html`, `-xml`, `-json`, `-yaml`, `-css`, `-scss`, `-sass`, `-less`, `-markdown`; the SQL family — `-sql` plus every dialect id: `-mysql`, `-pgsql`, `-sqlite`, `-mssql`, `-mariadb`, `-plsql`, `-cassandra`, `-hive`, `-sparksql`, `-gql`, `-gpsql`, `-esper`; and the config/deploy hosts `-toml`, `-ini`, `-env`, `-shell`, `-dockerfile`, `-nginx`) that overlay MiniJinja tooling on the host grammar. A parser adapter supports MiniJinja whitespace control, dotted namespace assignments, and comma-separated `set` assignments beyond the upstream Jinja grammar; a linter flags non-MiniJinja tags, and autocomplete is restricted to MiniJinja's real tags / filters / tests / functions.
13
14
  - **Context completion** — `normalizeContext({ schema })` (JSON Schema) or `normalizeContext({ sample })` (representative value) builds an `EditorContext`; dispatch it with `setMinijinjaContext` to make `{{ }}` / `{% if %}` / `{% for %}` complete the render context's variables and walk member access (`user.address.city`). `minijinjaContextField`, `resolveMembers`, and `typeAtPath` expose the underlying pieces.
15
+ - **SQL schema completion** — dispatch `setSqlSchema.of({ tables })` for dialect-aware table and column completion in the Lezer-backed `sql`, `mysql`, `pgsql`, `sqlite`, `mssql`, `mariadb`, `plsql`, and `cassandra` languages plus their `minijinja-*` variants. Legacy stream-mode SQL languages do not consume schemas. Controlled hosts install the data channel eagerly, so the schema may arrive before the lazy grammar.
14
16
  - **`searchPhrasesZhCn`** — an opt-in `EditorState.phrases` table localizing the search panel to Simplified Chinese.
15
17
 
16
18
  ## Install
@@ -20,7 +22,7 @@ pnpm add @coldsmirk/inkstone-codemirror \
20
22
  @codemirror/state @codemirror/view @codemirror/language @codemirror/autocomplete @codemirror/commands @codemirror/search
21
23
  ```
22
24
 
23
- The `@codemirror/*` packages are **peer dependencies** on purpose: `@codemirror/state` breaks at runtime if two copies load, so your app owns the single copy and every extension is guaranteed to be built against it. Node.js >= 22 for build / SSR hosts.
25
+ The `@codemirror/*` packages are **peer dependencies** on purpose: `@codemirror/state` breaks at runtime if two copies load, so your app owns the single copy and every extension is guaranteed to be built against it. Node.js >= 24 for build / SSR hosts.
24
26
 
25
27
  ## Quick start
26
28
 
@@ -38,6 +40,29 @@ const host = new ControlledEditorHost({
38
40
  host.setValue(remoteValue);
39
41
  ```
40
42
 
43
+ For a side-by-side diff:
44
+
45
+ ```ts
46
+ import { EditorState } from "@codemirror/state";
47
+ import { EditorView } from "@codemirror/view";
48
+ import { ControlledMergeHost, documentExtensions } from "@coldsmirk/inkstone-codemirror";
49
+
50
+ const diff = new ControlledMergeHost({
51
+ parent: document.querySelector("#diff")!,
52
+ original: before,
53
+ modified: after,
54
+ extensions: [documentExtensions({ showLineNumbers: true })],
55
+ // The original side follows setOriginal only — no change channel, so lock it.
56
+ originalExtensions: [EditorState.readOnly.of(true), EditorView.editable.of(false)],
57
+ onModifiedChange: next => save(next),
58
+ revertControls: "a-to-b"
59
+ });
60
+
61
+ // Both sides reconcile externally, same rules as setValue.
62
+ diff.setOriginal(nextBefore);
63
+ diff.setModified(nextAfter);
64
+ ```
65
+
41
66
  For MiniJinja with context completion:
42
67
 
43
68
  ```ts
@@ -47,6 +72,11 @@ import { loadLanguage, normalizeContext, setMinijinjaContext } from "@coldsmirk/
47
72
  view.dispatch({ effects: setMinijinjaContext.of(normalizeContext({ schema })) });
48
73
  ```
49
74
 
75
+ The controlled hosts install both context and SQL-schema fields before any grammar resolves, so
76
+ applications may dispatch either effect immediately and load/reconfigure the grammar later.
77
+ Hand-built `EditorState` instances should install `minijinjaContextField` / `sqlSchemaField`
78
+ explicitly when they need the same ordering.
79
+
50
80
  The completion is **structural** — it walks the declared shape (member access along a literal path), not types produced by filters or arithmetic. For a Rust backend the schema is nearly free: derive it from the same `serde` struct you render with via [`schemars`](https://docs.rs/schemars), and the completion can't drift from the real context.
51
81
 
52
82
  ## License
@@ -32,7 +32,7 @@ function resolveMembers(ir, path, scope) {
32
32
  required: true,
33
33
  nullable: false
34
34
  };
35
- }), ...top];
35
+ }), ...top.filter((member) => !scope.has(member.name))];
36
36
  }
37
37
  const type = typeAtPath(ir, path, scope);
38
38
  return type ? membersOf(ir, type) : [];
@@ -157,12 +157,13 @@ function defType(name, context) {
157
157
  function convert(node, context) {
158
158
  if (typeof node === "boolean" || !isObject(node)) return ANY;
159
159
  const ref = refName(node.$ref);
160
- if (ref !== null) return {
160
+ const reference = ref === null ? null : {
161
161
  kind: "ref",
162
162
  name: ref
163
163
  };
164
164
  if (Array.isArray(node.allOf)) {
165
165
  const branches = node.allOf.map((entry) => convert(entry, context));
166
+ if (reference) branches.unshift(reference);
166
167
  return requireFields(mergeAll(isObject(node.properties) ? [...branches, objectType(node, context)] : branches, context, "all"), node.required, context);
167
168
  }
168
169
  const union = node.anyOf ?? node.oneOf;
@@ -172,7 +173,13 @@ function convert(node, context) {
172
173
  if (viable.length === 0) return ANY;
173
174
  if (nonNull.length === 0) return NULL;
174
175
  const merged = nonNull.length === 1 ? convert(nonNull[0], context) : mergeAll(nonNull.map((entry) => convert(entry, context)), context, "any");
175
- return requireFields(isObject(node.properties) ? mergeAll([merged, objectType(node, context)], context, "all") : merged, node.required, context);
176
+ const withOwn = isObject(node.properties) ? mergeAll([merged, objectType(node, context)], context, "all") : merged;
177
+ return requireFields(reference ? mergeAll([reference, withOwn], context, "all") : withOwn, node.required, context);
178
+ }
179
+ if (reference) {
180
+ const hasProperties = isObject(node.properties);
181
+ if (!(hasProperties || Array.isArray(node.required) || primaryType(node) === "object")) return reference;
182
+ return requireFields(hasProperties ? mergeAll([reference, objectType(node, context)], context, "all") : reference, node.required, context);
176
183
  }
177
184
  if (Array.isArray(node.enum)) return {
178
185
  kind: "enum",
@@ -186,7 +193,7 @@ function convert(node, context) {
186
193
  if (isObjectShape(node)) return objectType(node, context);
187
194
  if (type === "array") return {
188
195
  kind: "array",
189
- element: convert(Array.isArray(node.items) ? node.items[0] : node.items, context)
196
+ element: convert((Array.isArray(node.prefixItems) ? node.prefixItems[0] : void 0) ?? (Array.isArray(node.items) ? node.items[0] : node.items), context)
190
197
  };
191
198
  if (type === "string") return { kind: "string" };
192
199
  if (type === "integer" || type === "number") return { kind: "number" };
@@ -320,7 +327,7 @@ function sampleObjectType(value, ancestors) {
320
327
  }
321
328
  function refName(ref) {
322
329
  if (typeof ref !== "string") return null;
323
- for (const prefix of REF_PREFIXES) if (ref.startsWith(prefix)) return decodeURIComponent(ref.slice(prefix.length));
330
+ for (const prefix of REF_PREFIXES) if (ref.startsWith(prefix)) return decodeURIComponent(ref.slice(prefix.length)).replaceAll("~1", "/").replaceAll("~0", "~");
324
331
  return null;
325
332
  }
326
333
  function primaryType(node) {
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Extension, StateField } from "@codemirror/state";
2
2
  import { EditorView } from "@codemirror/view";
3
3
  import { LanguageSupport } from "@codemirror/language";
4
+ import { DirectMergeConfig, MergeConfig, MergeConfig as MergeConfig$1, MergeView, MergeView as MergeView$1 } from "@codemirror/merge";
4
5
 
5
6
  //#region src/context.d.ts
6
7
  /**
@@ -61,8 +62,9 @@ type EditorContext = {
61
62
  * Reactively set (or clear) the render-context schema an editor completes against. Dispatch this
62
63
  * effect — `setMinijinjaContext.of(normalizeContext(...))`, or `.of(null)` to turn it off — to
63
64
  * update completion without recreating the editor, matching the create-once discipline used
64
- * throughout the toolkit. Defined here (not with the MiniJinja grammar) so it and the field stay
65
- * in the always-loaded chunk, leaving the grammar lazily code-split.
65
+ * throughout the toolkit. Controlled editor hosts install the field before any lazy grammar
66
+ * resolves; a hand-built EditorState should install {@link minijinjaContextField} itself when it
67
+ * needs to dispatch this effect before adding the MiniJinja support.
66
68
  */
67
69
  declare const setMinijinjaContext: import("@codemirror/state").StateEffectType<IrSchema | null>;
68
70
  /**
@@ -554,4 +556,130 @@ declare const codeMirrorLanguages: readonly CodeMirrorLanguage[];
554
556
  */
555
557
  declare function loadLanguage(language: CodeMirrorLanguage): Promise<Extension>;
556
558
  //#endregion
557
- export { type CodeMirrorLanguage, ControlledEditorHost, type ControlledEditorOptions, type DocumentExtensionsOptions, type EditorContext, type IrField, type IrSchema, type IrType, type Member, codeMirrorLanguages, documentExtensions, loadLanguage, minijinjaContextField, normalizeContext, resolveMembers, searchPhrasesZhCn, setMinijinjaContext, typeAtPath };
559
+ //#region src/merge-host.d.ts
560
+ interface ControlledMergeOptions extends MergeConfig$1 {
561
+ /**
562
+ * The element the merge view mounts into.
563
+ */
564
+ parent: HTMLElement;
565
+ /**
566
+ * Document or shadow root the merge view is mounted in. Required when `parent` lives outside
567
+ * the global document because the two editors are created before the merge DOM is attached.
568
+ */
569
+ root?: DirectMergeConfig["root"];
570
+ /**
571
+ * Initial text of the original document (editor A — the left side in the default
572
+ * orientation).
573
+ */
574
+ original: string;
575
+ /**
576
+ * Initial text of the modified document (editor B).
577
+ */
578
+ modified: string;
579
+ /**
580
+ * Called with the full original document after every edit made *in* editor A — user
581
+ * typing, or a `revertControls: "b-to-a"` copy. Not called for
582
+ * {@link ControlledMergeHost.setOriginal} reconciliations. Leave editor A read-only
583
+ * (via {@link ControlledMergeOptions.originalExtensions}) when nothing consumes this.
584
+ */
585
+ onOriginalChange?: (value: string) => void;
586
+ /**
587
+ * Called with the full modified document after every edit made *in* editor B — user
588
+ * typing, or a `revertControls: "a-to-b"` copy. Not called for
589
+ * {@link ControlledMergeHost.setModified} reconciliations.
590
+ */
591
+ onModifiedChange?: (value: string) => void;
592
+ /**
593
+ * Static extensions applied to both editors, fixed for the merge view's lifetime.
594
+ */
595
+ extensions?: Extension[];
596
+ /**
597
+ * Static extensions for editor A only — the place to make the original side read-only
598
+ * (`EditorState.readOnly.of(true)`, `EditorView.editable.of(false)`).
599
+ */
600
+ originalExtensions?: Extension[];
601
+ /**
602
+ * Static extensions for editor B only.
603
+ */
604
+ modifiedExtensions?: Extension[];
605
+ /**
606
+ * Extensions that change at runtime (theme, language config …), applied to both editors
607
+ * and swapped in place via {@link ControlledMergeHost.reconfigure}.
608
+ */
609
+ dynamicExtensions?: Extension[];
610
+ }
611
+ /**
612
+ * A controlled CodeMirror 6 merge view — {@link ControlledEditorHost}'s discipline applied to
613
+ * `@codemirror/merge`'s side-by-side {@link MergeView}:
614
+ *
615
+ * - **External value reconcile, per side.** {@link setOriginal} / {@link setModified} diff the
616
+ * incoming value against the live document and dispatch a minimal replace, so a store-driven
617
+ * update never resets the caret and never echoes back through the change callbacks.
618
+ * - **In-place reconfiguration.** `dynamicExtensions` live in one {@link Compartment} per
619
+ * editor; {@link reconfigure} swaps both (light/dark theme, language options) without
620
+ * rebuilding the view. Merge-level options (`orientation`, `revertControls`,
621
+ * `collapseUnchanged` …) reconfigure through {@link MergeView.reconfigure} on {@link view}.
622
+ *
623
+ * Policy-free plumbing: neither side is read-only by default — wire that through
624
+ * `originalExtensions` / `modifiedExtensions` to match how the documents are controlled.
625
+ * Framework-agnostic, same as `ControlledEditorHost`.
626
+ */
627
+ declare class ControlledMergeHost {
628
+ private readonly originalCompartment;
629
+ private readonly modifiedCompartment;
630
+ private silent;
631
+ readonly view: MergeView$1;
632
+ constructor(options: ControlledMergeOptions);
633
+ private reconcile;
634
+ /**
635
+ * Reconcile an externally-changed original value into editor A without resetting its caret.
636
+ * No-op when the document already matches; the write never re-enters `onOriginalChange`.
637
+ */
638
+ setOriginal(value: string): void;
639
+ /**
640
+ * Reconcile an externally-changed modified value into editor B without resetting its caret.
641
+ * No-op when the document already matches; the write never re-enters `onModifiedChange`.
642
+ */
643
+ setModified(value: string): void;
644
+ /**
645
+ * Swap the dynamic extensions (theme, language configuration) in place, on both editors.
646
+ */
647
+ reconfigure(dynamicExtensions: Extension[]): void;
648
+ destroy(): void;
649
+ }
650
+ //#endregion
651
+ //#region src/sql-schema.d.ts
652
+ /**
653
+ * A database's completable surface. Table keys may be schema-qualified (`"analytics.events"`);
654
+ * unqualified keys complete against `defaultSchema` per `@codemirror/lang-sql` semantics.
655
+ */
656
+ interface SqlSchema {
657
+ /**
658
+ * Table name (optionally schema-qualified) → column names, in declaration order.
659
+ */
660
+ readonly tables: Readonly<Record<string, readonly string[]>>;
661
+ /**
662
+ * Table whose columns complete unqualified (no `table.` prefix).
663
+ */
664
+ readonly defaultTable?: string;
665
+ /**
666
+ * Schema whose tables complete unqualified.
667
+ */
668
+ readonly defaultSchema?: string;
669
+ }
670
+ /**
671
+ * Reactively set (or clear) the database schema SQL completion reads. Dispatch
672
+ * `setSqlSchema.of(schema)` — or `.of(null)` to turn table/column completion off — to update a
673
+ * live editor without recreating it, matching the create-once discipline used throughout the
674
+ * toolkit.
675
+ */
676
+ declare const setSqlSchema: import("@codemirror/state").StateEffectType<SqlSchema | null>;
677
+ /**
678
+ * Holds the schema that SQL table/column completion reads. Controlled editor hosts install it
679
+ * eagerly; the `sql`-family language supports also include it for hand-built editors. `null`
680
+ * (the default) means no schema — keyword completion still works, table/column completion stays
681
+ * off.
682
+ */
683
+ declare const sqlSchemaField: StateField<SqlSchema | null>;
684
+ //#endregion
685
+ export { type CodeMirrorLanguage, ControlledEditorHost, type ControlledEditorOptions, ControlledMergeHost, type ControlledMergeOptions, type DocumentExtensionsOptions, type EditorContext, type IrField, type IrSchema, type IrType, type Member, type MergeConfig, type MergeView, type SqlSchema, codeMirrorLanguages, documentExtensions, loadLanguage, minijinjaContextField, normalizeContext, resolveMembers, searchPhrasesZhCn, setMinijinjaContext, setSqlSchema, sqlSchemaField, typeAtPath };
package/dist/index.js CHANGED
@@ -1,10 +1,77 @@
1
- import { a as setMinijinjaContext, i as resolveMembers, n as minijinjaContextField, o as typeAtPath, r as normalizeContext } from "./context-D86o2jK3.js";
2
- import { Compartment, EditorState } from "@codemirror/state";
1
+ import { a as setMinijinjaContext, i as resolveMembers, n as minijinjaContextField, o as typeAtPath, r as normalizeContext } from "./context-BJzyiP0A.js";
2
+ import { n as sqlSchemaField, t as setSqlSchema } from "./sql-schema-DiPx5-bu.js";
3
+ import { Compartment, EditorState, Transaction } from "@codemirror/state";
3
4
  import { EditorView, drawSelection, highlightActiveLine, keymap, lineNumbers, placeholder } from "@codemirror/view";
4
5
  import { autocompletion, closeBrackets, closeBracketsKeymap, completionKeymap } from "@codemirror/autocomplete";
5
6
  import { defaultKeymap, history, historyKeymap, indentWithTab } from "@codemirror/commands";
6
7
  import { LanguageSupport, StreamLanguage, bracketMatching, codeFolding, foldGutter, foldKeymap, indentOnInput } from "@codemirror/language";
7
8
  import { highlightSelectionMatches, searchKeymap } from "@codemirror/search";
9
+ import { MergeView } from "@codemirror/merge";
10
+ //#region src/minimal-replace.ts
11
+ function minimalReplace(state, externalValue) {
12
+ const current = state.doc;
13
+ const value = state.toText(externalValue);
14
+ if (value.eq(current)) return null;
15
+ const from = commonLength(current, value, 1, Math.min(current.length, value.length));
16
+ const suffix = commonLength(current, value, -1, Math.min(current.length - from, value.length - from));
17
+ const currentEnd = current.length - suffix;
18
+ const valueEnd = value.length - suffix;
19
+ return {
20
+ from,
21
+ to: currentEnd,
22
+ insert: value.slice(from, valueEnd)
23
+ };
24
+ }
25
+ function commonLength(a, b, direction, maximum) {
26
+ const aIterator = a.iter(direction);
27
+ const bIterator = b.iter(direction);
28
+ let length = 0;
29
+ aIterator.next();
30
+ bIterator.next();
31
+ while (length < maximum && !aIterator.done && !bIterator.done) {
32
+ if (aIterator.lineBreak || bIterator.lineBreak) {
33
+ if (aIterator.lineBreak !== bIterator.lineBreak) break;
34
+ length += 1;
35
+ aIterator.next();
36
+ bIterator.next();
37
+ continue;
38
+ }
39
+ const available = Math.min(aIterator.value.length, bIterator.value.length, maximum - length);
40
+ if (aIterator.value !== bIterator.value) {
41
+ length += commonStringLength(aIterator.value, bIterator.value, direction, available);
42
+ break;
43
+ }
44
+ if (available < aIterator.value.length) {
45
+ length += available;
46
+ break;
47
+ }
48
+ length += available;
49
+ aIterator.next();
50
+ bIterator.next();
51
+ }
52
+ return length;
53
+ }
54
+ function commonStringLength(a, b, direction, maximum) {
55
+ let length = 0;
56
+ let previous = -1;
57
+ while (length < maximum) {
58
+ const aCode = a.charCodeAt(direction === 1 ? length : a.length - length - 1);
59
+ if (aCode !== b.charCodeAt(direction === 1 ? length : b.length - length - 1)) {
60
+ if (direction === 1 && isHighSurrogate(previous) || direction === -1 && isLowSurrogate(previous)) length -= 1;
61
+ break;
62
+ }
63
+ previous = aCode;
64
+ length += 1;
65
+ }
66
+ return length;
67
+ }
68
+ function isHighSurrogate(codeUnit) {
69
+ return codeUnit >= 55296 && codeUnit <= 56319;
70
+ }
71
+ function isLowSurrogate(codeUnit) {
72
+ return codeUnit >= 56320 && codeUnit <= 57343;
73
+ }
74
+ //#endregion
8
75
  //#region src/controlled-host.ts
9
76
  var ControlledEditorHost = class {
10
77
  compartment = new Compartment();
@@ -16,34 +83,26 @@ var ControlledEditorHost = class {
16
83
  state: EditorState.create({
17
84
  doc,
18
85
  extensions: [
86
+ minijinjaContextField,
87
+ sqlSchemaField,
19
88
  ...extensions,
20
89
  this.compartment.of(dynamicExtensions),
21
90
  EditorView.updateListener.of((update) => {
22
- if (update.docChanged && !this.silent) onChange(update.state.doc.toString());
91
+ if (update.docChanged && !this.silent) onChange(update.state.sliceDoc());
23
92
  })
24
93
  ]
25
94
  })
26
95
  });
27
96
  }
28
97
  setValue(value) {
29
- const current = this.view.state.doc.toString();
30
- if (value === current) return;
31
- const shorter = Math.min(current.length, value.length);
32
- let from = 0;
33
- while (from < shorter && current.codePointAt(from) === value.codePointAt(from)) from += 1;
34
- let currentEnd = current.length;
35
- let valueEnd = value.length;
36
- while (currentEnd > from && valueEnd > from && current.codePointAt(currentEnd - 1) === value.codePointAt(valueEnd - 1)) {
37
- currentEnd -= 1;
38
- valueEnd -= 1;
39
- }
98
+ const changes = minimalReplace(this.view.state, value);
99
+ if (!changes) return;
40
100
  this.silent = true;
41
101
  try {
42
- this.view.dispatch({ changes: {
43
- from,
44
- to: currentEnd,
45
- insert: value.slice(from, valueEnd)
46
- } });
102
+ this.view.dispatch({
103
+ changes,
104
+ annotations: Transaction.addToHistory.of(false)
105
+ });
47
106
  } finally {
48
107
  this.silent = false;
49
108
  }
@@ -98,7 +157,7 @@ function documentExtensions({ placeholder: placeholder$1, lineWrapping = false,
98
157
  ...completionKeymap,
99
158
  indentWithTab
100
159
  ]),
101
- placeholder(placeholder$1 ?? ""),
160
+ ...placeholder$1 === void 0 ? [] : [placeholder(placeholder$1)],
102
161
  ...spellcheck ? [EditorView.contentAttributes.of({ spellcheck: "true" })] : []
103
162
  ];
104
163
  }
@@ -136,14 +195,14 @@ const loaders = {
136
195
  liquid: () => import("@codemirror/lang-liquid").then((mod) => mod.liquid()),
137
196
  wast: () => import("@codemirror/lang-wast").then((mod) => mod.wast()),
138
197
  lezer: () => import("@codemirror/lang-lezer").then((mod) => mod.lezer()),
139
- sql: () => import("@codemirror/lang-sql").then((mod) => mod.sql()),
140
- mysql: () => import("@codemirror/lang-sql").then((mod) => mod.sql({ dialect: mod.MySQL })),
141
- pgsql: () => import("@codemirror/lang-sql").then((mod) => mod.sql({ dialect: mod.PostgreSQL })),
142
- sqlite: () => import("@codemirror/lang-sql").then((mod) => mod.sql({ dialect: mod.SQLite })),
143
- mssql: () => import("@codemirror/lang-sql").then((mod) => mod.sql({ dialect: mod.MSSQL })),
144
- mariadb: () => import("@codemirror/lang-sql").then((mod) => mod.sql({ dialect: mod.MariaSQL })),
145
- plsql: () => import("@codemirror/lang-sql").then((mod) => mod.sql({ dialect: mod.PLSQL })),
146
- cassandra: () => import("@codemirror/lang-sql").then((mod) => mod.sql({ dialect: mod.Cassandra })),
198
+ sql: () => Promise.all([import("./sql-support-Gk7Wv9Kj.js"), import("@codemirror/lang-sql")]).then(([schema, mod]) => schema.withSqlSchema(mod.sql(), mod.StandardSQL)),
199
+ mysql: () => Promise.all([import("./sql-support-Gk7Wv9Kj.js"), import("@codemirror/lang-sql")]).then(([schema, mod]) => schema.withSqlSchema(mod.sql({ dialect: mod.MySQL }), mod.MySQL)),
200
+ pgsql: () => Promise.all([import("./sql-support-Gk7Wv9Kj.js"), import("@codemirror/lang-sql")]).then(([schema, mod]) => schema.withSqlSchema(mod.sql({ dialect: mod.PostgreSQL }), mod.PostgreSQL)),
201
+ sqlite: () => Promise.all([import("./sql-support-Gk7Wv9Kj.js"), import("@codemirror/lang-sql")]).then(([schema, mod]) => schema.withSqlSchema(mod.sql({ dialect: mod.SQLite }), mod.SQLite)),
202
+ mssql: () => Promise.all([import("./sql-support-Gk7Wv9Kj.js"), import("@codemirror/lang-sql")]).then(([schema, mod]) => schema.withSqlSchema(mod.sql({ dialect: mod.MSSQL }), mod.MSSQL)),
203
+ mariadb: () => Promise.all([import("./sql-support-Gk7Wv9Kj.js"), import("@codemirror/lang-sql")]).then(([schema, mod]) => schema.withSqlSchema(mod.sql({ dialect: mod.MariaSQL }), mod.MariaSQL)),
204
+ plsql: () => Promise.all([import("./sql-support-Gk7Wv9Kj.js"), import("@codemirror/lang-sql")]).then(([schema, mod]) => schema.withSqlSchema(mod.sql({ dialect: mod.PLSQL }), mod.PLSQL)),
205
+ cassandra: () => Promise.all([import("./sql-support-Gk7Wv9Kj.js"), import("@codemirror/lang-sql")]).then(([schema, mod]) => schema.withSqlSchema(mod.sql({ dialect: mod.Cassandra }), mod.Cassandra)),
147
206
  apl: legacy(() => import("@codemirror/legacy-modes/mode/apl").then((mod) => mod.apl)),
148
207
  asciiarmor: legacy(() => import("@codemirror/legacy-modes/mode/asciiarmor").then((mod) => mod.asciiArmor)),
149
208
  asn1: legacy(() => import("@codemirror/legacy-modes/mode/asn1").then((mod) => mod.asn1({}))),
@@ -264,39 +323,138 @@ const loaders = {
264
323
  xu: legacy(() => import("@codemirror/legacy-modes/mode/mscgen").then((mod) => mod.xu)),
265
324
  yacas: legacy(() => import("@codemirror/legacy-modes/mode/yacas").then((mod) => mod.yacas)),
266
325
  z80: legacy(() => import("@codemirror/legacy-modes/mode/z80").then((mod) => mod.z80)),
267
- minijinja: () => import("./minijinja-D_5erBa7.js").then((mod) => mod.minijinja()),
268
- "minijinja-html": () => Promise.all([import("./minijinja-D_5erBa7.js"), import("@codemirror/lang-html")]).then(([mj, mod]) => mj.minijinja({ base: mod.html() })),
269
- "minijinja-xml": () => Promise.all([import("./minijinja-D_5erBa7.js"), import("@codemirror/lang-xml")]).then(([mj, mod]) => mj.minijinja({ base: mod.xml() })),
270
- "minijinja-json": () => Promise.all([import("./minijinja-D_5erBa7.js"), import("@codemirror/lang-json")]).then(([mj, mod]) => mj.minijinja({ base: mod.json() })),
271
- "minijinja-yaml": () => Promise.all([import("./minijinja-D_5erBa7.js"), import("@codemirror/lang-yaml")]).then(([mj, mod]) => mj.minijinja({ base: mod.yaml() })),
272
- "minijinja-css": () => Promise.all([import("./minijinja-D_5erBa7.js"), import("@codemirror/lang-css")]).then(([mj, mod]) => mj.minijinja({ base: mod.css() })),
273
- "minijinja-scss": () => Promise.all([import("./minijinja-D_5erBa7.js"), import("@codemirror/lang-sass")]).then(([mj, mod]) => mj.minijinja({ base: mod.sass() })),
274
- "minijinja-sass": () => Promise.all([import("./minijinja-D_5erBa7.js"), import("@codemirror/lang-sass")]).then(([mj, mod]) => mj.minijinja({ base: mod.sass({ indented: true }) })),
275
- "minijinja-less": () => Promise.all([import("./minijinja-D_5erBa7.js"), import("@codemirror/lang-less")]).then(([mj, mod]) => mj.minijinja({ base: mod.less() })),
276
- "minijinja-markdown": () => Promise.all([import("./minijinja-D_5erBa7.js"), import("@codemirror/lang-markdown")]).then(([mj, mod]) => mj.minijinja({ base: mod.markdown() })),
277
- "minijinja-sql": () => Promise.all([import("./minijinja-D_5erBa7.js"), import("@codemirror/lang-sql")]).then(([mj, mod]) => mj.minijinja({ base: mod.sql() })),
278
- "minijinja-mysql": () => Promise.all([import("./minijinja-D_5erBa7.js"), import("@codemirror/lang-sql")]).then(([mj, mod]) => mj.minijinja({ base: mod.sql({ dialect: mod.MySQL }) })),
279
- "minijinja-pgsql": () => Promise.all([import("./minijinja-D_5erBa7.js"), import("@codemirror/lang-sql")]).then(([mj, mod]) => mj.minijinja({ base: mod.sql({ dialect: mod.PostgreSQL }) })),
280
- "minijinja-sqlite": () => Promise.all([import("./minijinja-D_5erBa7.js"), import("@codemirror/lang-sql")]).then(([mj, mod]) => mj.minijinja({ base: mod.sql({ dialect: mod.SQLite }) })),
281
- "minijinja-mssql": () => Promise.all([import("./minijinja-D_5erBa7.js"), import("@codemirror/lang-sql")]).then(([mj, mod]) => mj.minijinja({ base: mod.sql({ dialect: mod.MSSQL }) })),
282
- "minijinja-mariadb": () => Promise.all([import("./minijinja-D_5erBa7.js"), import("@codemirror/lang-sql")]).then(([mj, mod]) => mj.minijinja({ base: mod.sql({ dialect: mod.MariaSQL }) })),
283
- "minijinja-plsql": () => Promise.all([import("./minijinja-D_5erBa7.js"), import("@codemirror/lang-sql")]).then(([mj, mod]) => mj.minijinja({ base: mod.sql({ dialect: mod.PLSQL }) })),
284
- "minijinja-cassandra": () => Promise.all([import("./minijinja-D_5erBa7.js"), import("@codemirror/lang-sql")]).then(([mj, mod]) => mj.minijinja({ base: mod.sql({ dialect: mod.Cassandra }) })),
285
- "minijinja-hive": () => Promise.all([import("./minijinja-D_5erBa7.js"), import("@codemirror/legacy-modes/mode/sql")]).then(([mj, mod]) => mj.minijinja({ base: new LanguageSupport(StreamLanguage.define(mod.hive)) })),
286
- "minijinja-sparksql": () => Promise.all([import("./minijinja-D_5erBa7.js"), import("@codemirror/legacy-modes/mode/sql")]).then(([mj, mod]) => mj.minijinja({ base: new LanguageSupport(StreamLanguage.define(mod.sparkSQL)) })),
287
- "minijinja-gql": () => Promise.all([import("./minijinja-D_5erBa7.js"), import("@codemirror/legacy-modes/mode/sql")]).then(([mj, mod]) => mj.minijinja({ base: new LanguageSupport(StreamLanguage.define(mod.gql)) })),
288
- "minijinja-gpsql": () => Promise.all([import("./minijinja-D_5erBa7.js"), import("@codemirror/legacy-modes/mode/sql")]).then(([mj, mod]) => mj.minijinja({ base: new LanguageSupport(StreamLanguage.define(mod.gpSQL)) })),
289
- "minijinja-esper": () => Promise.all([import("./minijinja-D_5erBa7.js"), import("@codemirror/legacy-modes/mode/sql")]).then(([mj, mod]) => mj.minijinja({ base: new LanguageSupport(StreamLanguage.define(mod.esper)) })),
290
- "minijinja-toml": () => Promise.all([import("./minijinja-D_5erBa7.js"), import("@codemirror/legacy-modes/mode/toml")]).then(([mj, mod]) => mj.minijinja({ base: new LanguageSupport(StreamLanguage.define(mod.toml)) })),
291
- "minijinja-ini": () => Promise.all([import("./minijinja-D_5erBa7.js"), import("@codemirror/legacy-modes/mode/properties")]).then(([mj, mod]) => mj.minijinja({ base: new LanguageSupport(StreamLanguage.define(mod.properties)) })),
292
- "minijinja-env": () => Promise.all([import("./minijinja-D_5erBa7.js"), import("@codemirror/legacy-modes/mode/properties")]).then(([mj, mod]) => mj.minijinja({ base: new LanguageSupport(StreamLanguage.define(mod.properties)) })),
293
- "minijinja-shell": () => Promise.all([import("./minijinja-D_5erBa7.js"), import("@codemirror/legacy-modes/mode/shell")]).then(([mj, mod]) => mj.minijinja({ base: new LanguageSupport(StreamLanguage.define(mod.shell)) })),
294
- "minijinja-dockerfile": () => Promise.all([import("./minijinja-D_5erBa7.js"), import("@codemirror/legacy-modes/mode/dockerfile")]).then(([mj, mod]) => mj.minijinja({ base: new LanguageSupport(StreamLanguage.define(mod.dockerFile)) })),
295
- "minijinja-nginx": () => Promise.all([import("./minijinja-D_5erBa7.js"), import("@codemirror/legacy-modes/mode/nginx")]).then(([mj, mod]) => mj.minijinja({ base: new LanguageSupport(StreamLanguage.define(mod.nginx)) }))
326
+ minijinja: () => import("./minijinja-D5ANOjtx.js").then((mod) => mod.minijinja()),
327
+ "minijinja-html": () => Promise.all([import("./minijinja-D5ANOjtx.js"), import("@codemirror/lang-html")]).then(([mj, mod]) => mj.minijinja({ base: mod.html() })),
328
+ "minijinja-xml": () => Promise.all([import("./minijinja-D5ANOjtx.js"), import("@codemirror/lang-xml")]).then(([mj, mod]) => mj.minijinja({ base: mod.xml() })),
329
+ "minijinja-json": () => Promise.all([import("./minijinja-D5ANOjtx.js"), import("@codemirror/lang-json")]).then(([mj, mod]) => mj.minijinja({ base: mod.json() })),
330
+ "minijinja-yaml": () => Promise.all([import("./minijinja-D5ANOjtx.js"), import("@codemirror/lang-yaml")]).then(([mj, mod]) => mj.minijinja({ base: mod.yaml() })),
331
+ "minijinja-css": () => Promise.all([import("./minijinja-D5ANOjtx.js"), import("@codemirror/lang-css")]).then(([mj, mod]) => mj.minijinja({ base: mod.css() })),
332
+ "minijinja-scss": () => Promise.all([import("./minijinja-D5ANOjtx.js"), import("@codemirror/lang-sass")]).then(([mj, mod]) => mj.minijinja({ base: mod.sass() })),
333
+ "minijinja-sass": () => Promise.all([import("./minijinja-D5ANOjtx.js"), import("@codemirror/lang-sass")]).then(([mj, mod]) => mj.minijinja({ base: mod.sass({ indented: true }) })),
334
+ "minijinja-less": () => Promise.all([import("./minijinja-D5ANOjtx.js"), import("@codemirror/lang-less")]).then(([mj, mod]) => mj.minijinja({ base: mod.less() })),
335
+ "minijinja-markdown": () => Promise.all([import("./minijinja-D5ANOjtx.js"), import("@codemirror/lang-markdown")]).then(([mj, mod]) => mj.minijinja({ base: mod.markdown() })),
336
+ "minijinja-sql": () => Promise.all([
337
+ import("./minijinja-D5ANOjtx.js"),
338
+ import("./sql-support-Gk7Wv9Kj.js"),
339
+ import("@codemirror/lang-sql")
340
+ ]).then(([mj, schema, mod]) => mj.minijinja({ base: schema.withSqlSchema(mod.sql(), mod.StandardSQL) })),
341
+ "minijinja-mysql": () => Promise.all([
342
+ import("./minijinja-D5ANOjtx.js"),
343
+ import("./sql-support-Gk7Wv9Kj.js"),
344
+ import("@codemirror/lang-sql")
345
+ ]).then(([mj, schema, mod]) => mj.minijinja({ base: schema.withSqlSchema(mod.sql({ dialect: mod.MySQL }), mod.MySQL) })),
346
+ "minijinja-pgsql": () => Promise.all([
347
+ import("./minijinja-D5ANOjtx.js"),
348
+ import("./sql-support-Gk7Wv9Kj.js"),
349
+ import("@codemirror/lang-sql")
350
+ ]).then(([mj, schema, mod]) => mj.minijinja({ base: schema.withSqlSchema(mod.sql({ dialect: mod.PostgreSQL }), mod.PostgreSQL) })),
351
+ "minijinja-sqlite": () => Promise.all([
352
+ import("./minijinja-D5ANOjtx.js"),
353
+ import("./sql-support-Gk7Wv9Kj.js"),
354
+ import("@codemirror/lang-sql")
355
+ ]).then(([mj, schema, mod]) => mj.minijinja({ base: schema.withSqlSchema(mod.sql({ dialect: mod.SQLite }), mod.SQLite) })),
356
+ "minijinja-mssql": () => Promise.all([
357
+ import("./minijinja-D5ANOjtx.js"),
358
+ import("./sql-support-Gk7Wv9Kj.js"),
359
+ import("@codemirror/lang-sql")
360
+ ]).then(([mj, schema, mod]) => mj.minijinja({ base: schema.withSqlSchema(mod.sql({ dialect: mod.MSSQL }), mod.MSSQL) })),
361
+ "minijinja-mariadb": () => Promise.all([
362
+ import("./minijinja-D5ANOjtx.js"),
363
+ import("./sql-support-Gk7Wv9Kj.js"),
364
+ import("@codemirror/lang-sql")
365
+ ]).then(([mj, schema, mod]) => mj.minijinja({ base: schema.withSqlSchema(mod.sql({ dialect: mod.MariaSQL }), mod.MariaSQL) })),
366
+ "minijinja-plsql": () => Promise.all([
367
+ import("./minijinja-D5ANOjtx.js"),
368
+ import("./sql-support-Gk7Wv9Kj.js"),
369
+ import("@codemirror/lang-sql")
370
+ ]).then(([mj, schema, mod]) => mj.minijinja({ base: schema.withSqlSchema(mod.sql({ dialect: mod.PLSQL }), mod.PLSQL) })),
371
+ "minijinja-cassandra": () => Promise.all([
372
+ import("./minijinja-D5ANOjtx.js"),
373
+ import("./sql-support-Gk7Wv9Kj.js"),
374
+ import("@codemirror/lang-sql")
375
+ ]).then(([mj, schema, mod]) => mj.minijinja({ base: schema.withSqlSchema(mod.sql({ dialect: mod.Cassandra }), mod.Cassandra) })),
376
+ "minijinja-hive": () => Promise.all([import("./minijinja-D5ANOjtx.js"), import("@codemirror/legacy-modes/mode/sql")]).then(([mj, mod]) => mj.minijinja({ base: new LanguageSupport(StreamLanguage.define(mod.hive)) })),
377
+ "minijinja-sparksql": () => Promise.all([import("./minijinja-D5ANOjtx.js"), import("@codemirror/legacy-modes/mode/sql")]).then(([mj, mod]) => mj.minijinja({ base: new LanguageSupport(StreamLanguage.define(mod.sparkSQL)) })),
378
+ "minijinja-gql": () => Promise.all([import("./minijinja-D5ANOjtx.js"), import("@codemirror/legacy-modes/mode/sql")]).then(([mj, mod]) => mj.minijinja({ base: new LanguageSupport(StreamLanguage.define(mod.gql)) })),
379
+ "minijinja-gpsql": () => Promise.all([import("./minijinja-D5ANOjtx.js"), import("@codemirror/legacy-modes/mode/sql")]).then(([mj, mod]) => mj.minijinja({ base: new LanguageSupport(StreamLanguage.define(mod.gpSQL)) })),
380
+ "minijinja-esper": () => Promise.all([import("./minijinja-D5ANOjtx.js"), import("@codemirror/legacy-modes/mode/sql")]).then(([mj, mod]) => mj.minijinja({ base: new LanguageSupport(StreamLanguage.define(mod.esper)) })),
381
+ "minijinja-toml": () => Promise.all([import("./minijinja-D5ANOjtx.js"), import("@codemirror/legacy-modes/mode/toml")]).then(([mj, mod]) => mj.minijinja({ base: new LanguageSupport(StreamLanguage.define(mod.toml)) })),
382
+ "minijinja-ini": () => Promise.all([import("./minijinja-D5ANOjtx.js"), import("@codemirror/legacy-modes/mode/properties")]).then(([mj, mod]) => mj.minijinja({ base: new LanguageSupport(StreamLanguage.define(mod.properties)) })),
383
+ "minijinja-env": () => Promise.all([import("./minijinja-D5ANOjtx.js"), import("@codemirror/legacy-modes/mode/properties")]).then(([mj, mod]) => mj.minijinja({ base: new LanguageSupport(StreamLanguage.define(mod.properties)) })),
384
+ "minijinja-shell": () => Promise.all([import("./minijinja-D5ANOjtx.js"), import("@codemirror/legacy-modes/mode/shell")]).then(([mj, mod]) => mj.minijinja({ base: new LanguageSupport(StreamLanguage.define(mod.shell)) })),
385
+ "minijinja-dockerfile": () => Promise.all([import("./minijinja-D5ANOjtx.js"), import("@codemirror/legacy-modes/mode/dockerfile")]).then(([mj, mod]) => mj.minijinja({ base: new LanguageSupport(StreamLanguage.define(mod.dockerFile)) })),
386
+ "minijinja-nginx": () => Promise.all([import("./minijinja-D5ANOjtx.js"), import("@codemirror/legacy-modes/mode/nginx")]).then(([mj, mod]) => mj.minijinja({ base: new LanguageSupport(StreamLanguage.define(mod.nginx)) }))
296
387
  };
297
388
  const codeMirrorLanguages = Object.keys(loaders).toSorted();
298
389
  function loadLanguage(language) {
299
390
  return loaders[language]();
300
391
  }
301
392
  //#endregion
302
- export { ControlledEditorHost, codeMirrorLanguages, documentExtensions, loadLanguage, minijinjaContextField, normalizeContext, resolveMembers, searchPhrasesZhCn, setMinijinjaContext, typeAtPath };
393
+ //#region src/merge-host.ts
394
+ var ControlledMergeHost = class {
395
+ originalCompartment = new Compartment();
396
+ modifiedCompartment = new Compartment();
397
+ silent = false;
398
+ view;
399
+ constructor(options) {
400
+ const { parent, original, modified, onOriginalChange, onModifiedChange, extensions = [], originalExtensions = [], modifiedExtensions = [], dynamicExtensions = [], ...mergeConfig } = options;
401
+ this.view = new MergeView({
402
+ ...mergeConfig,
403
+ parent,
404
+ a: {
405
+ doc: original,
406
+ extensions: [
407
+ ...originalExtensions,
408
+ minijinjaContextField,
409
+ sqlSchemaField,
410
+ ...extensions,
411
+ this.originalCompartment.of(dynamicExtensions),
412
+ EditorView.updateListener.of((update) => {
413
+ if (update.docChanged && !this.silent) onOriginalChange?.(update.state.sliceDoc());
414
+ })
415
+ ]
416
+ },
417
+ b: {
418
+ doc: modified,
419
+ extensions: [
420
+ ...modifiedExtensions,
421
+ minijinjaContextField,
422
+ sqlSchemaField,
423
+ ...extensions,
424
+ this.modifiedCompartment.of(dynamicExtensions),
425
+ EditorView.updateListener.of((update) => {
426
+ if (update.docChanged && !this.silent) onModifiedChange?.(update.state.sliceDoc());
427
+ })
428
+ ]
429
+ }
430
+ });
431
+ }
432
+ reconcile(editor, value) {
433
+ const changes = minimalReplace(editor.state, value);
434
+ if (!changes) return;
435
+ this.silent = true;
436
+ try {
437
+ editor.dispatch({
438
+ changes,
439
+ annotations: Transaction.addToHistory.of(false)
440
+ });
441
+ } finally {
442
+ this.silent = false;
443
+ }
444
+ }
445
+ setOriginal(value) {
446
+ this.reconcile(this.view.a, value);
447
+ }
448
+ setModified(value) {
449
+ this.reconcile(this.view.b, value);
450
+ }
451
+ reconfigure(dynamicExtensions) {
452
+ this.view.a.dispatch({ effects: this.originalCompartment.reconfigure(dynamicExtensions) });
453
+ this.view.b.dispatch({ effects: this.modifiedCompartment.reconfigure(dynamicExtensions) });
454
+ }
455
+ destroy() {
456
+ this.view.destroy();
457
+ }
458
+ };
459
+ //#endregion
460
+ export { ControlledEditorHost, ControlledMergeHost, codeMirrorLanguages, documentExtensions, loadLanguage, minijinjaContextField, normalizeContext, resolveMembers, searchPhrasesZhCn, setMinijinjaContext, setSqlSchema, sqlSchemaField, typeAtPath };