@coldsmirk/inkstone-codemirror 0.11.0 → 0.12.0

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
@@ -12,7 +12,8 @@ Part of [inkstone](https://github.com/coldsmirk/inkstone). For a drop-in React c
12
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.
13
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.
14
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.
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. Install `sqlSchemaField` in your static extensions to dispatch the schema before the lazy grammar arrives.
16
+ - **Catppuccin theming** — `defaultThemeExtensions(dark, height?, width?, fontSize?, lineHeight?)`, the latte/macchiato flavor pair plus chrome polish (completion tooltip, icons, hover/lint tooltips, search panel) and sizing; `mergeTheme(dark)` / `installDiffChrome(root)` / `diffChromeColor(dark)` / `installDiffControlKeyboardSupport(container)` extend it to `@codemirror/merge`'s diff surfaces (see the `merge-chrome` module docs for the assembly). This is the identity `@coldsmirk/inkstone-react`'s components ship by default.
16
17
  - **`searchPhrasesZhCn`** — an opt-in `EditorState.phrases` table localizing the search panel to Simplified Chinese.
17
18
 
18
19
  ## Install
@@ -72,10 +73,11 @@ import { loadLanguage, normalizeContext, setMinijinjaContext } from "@coldsmirk/
72
73
  view.dispatch({ effects: setMinijinjaContext.of(normalizeContext({ schema })) });
73
74
  ```
74
75
 
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.
76
+ The hosts are policy-free and pre-install no data fields: when you dispatch `setMinijinjaContext`
77
+ or `setSqlSchema`, install `minijinjaContextField` / `sqlSchemaField` in your static `extensions`
78
+ (as `@coldsmirk/inkstone-react`'s components do). The fields live independently of the lazy
79
+ grammars, so either effect may be dispatched immediately and the grammar loaded/reconfigured
80
+ later.
79
81
 
80
82
  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.
81
83
 
package/dist/index.d.ts CHANGED
@@ -62,9 +62,9 @@ type EditorContext = {
62
62
  * Reactively set (or clear) the render-context schema an editor completes against. Dispatch this
63
63
  * effect — `setMinijinjaContext.of(normalizeContext(...))`, or `.of(null)` to turn it off — to
64
64
  * update completion without recreating the editor, matching the create-once discipline used
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.
65
+ * throughout the toolkit. The field lives independently of the lazy MiniJinja grammar, so an
66
+ * editor that installs {@link minijinjaContextField} in its static extensions (as
67
+ * `@coldsmirk/inkstone-react`'s components do) accepts this effect before the grammar resolves.
68
68
  */
69
69
  declare const setMinijinjaContext: import("@codemirror/state").StateEffectType<IrSchema | null>;
70
70
  /**
@@ -131,6 +131,11 @@ interface ControlledEditorOptions {
131
131
  * Framework-agnostic: a React wrapper calls `setValue` from a value effect and `reconfigure`
132
132
  * from a theme effect (see `@coldsmirk/inkstone-react`); any other framework does the same
133
133
  * from its own reactivity.
134
+ *
135
+ * Policy-free plumbing — no inkstone language data fields are pre-installed. A host that
136
+ * dispatches `setMinijinjaContext` / `setSqlSchema` installs `minijinjaContextField` /
137
+ * `sqlSchemaField` in its static `extensions` (they accept a dispatch before the lazy grammar
138
+ * that reads them resolves).
134
139
  */
135
140
  declare class ControlledEditorHost {
136
141
  private readonly compartment;
@@ -156,12 +161,6 @@ declare class ControlledEditorHost {
156
161
  }
157
162
  //#endregion
158
163
  //#region src/extensions.d.ts
159
- /**
160
- * Simplified-Chinese strings for the search / goto-line panels, as an opt-in extension —
161
- * inkstone's defaults are English, so pass this through `extensions` to localize the panel
162
- * (`search` must be on for the panel to exist at all).
163
- */
164
- declare const searchPhrasesZhCn: Extension;
165
164
  interface DocumentExtensionsOptions {
166
165
  /**
167
166
  * Placeholder text shown while the document is empty.
@@ -556,6 +555,56 @@ declare const codeMirrorLanguages: readonly CodeMirrorLanguage[];
556
555
  */
557
556
  declare function loadLanguage(language: CodeMirrorLanguage): Promise<Extension>;
558
557
  //#endregion
558
+ //#region src/locale.d.ts
559
+ /**
560
+ * Simplified-Chinese strings for the search / goto-line panels, as an opt-in extension —
561
+ * inkstone's defaults are English, so pass this through `extensions` to localize the panel
562
+ * (`search` must be on for the panel to exist at all).
563
+ */
564
+ declare const searchPhrasesZhCn: Extension;
565
+ //#endregion
566
+ //#region src/merge-chrome.d.ts
567
+ /**
568
+ * Chrome for the surfaces `@codemirror/merge` owns, in catppuccin: deletions read red on the
569
+ * original side, insertions green on the modified side (the package scopes sides through the
570
+ * cm-merge-a / cm-merge-b classes it places on each editor root), and the collapsed-lines bar
571
+ * drops its hardcoded grays for flavor colors. Regular theme precedence — it beats the merge
572
+ * package's baseTheme by CodeMirror's theme-over-base contract, and a caller theme layered
573
+ * after it still overrides it.
574
+ */
575
+ declare function mergeTheme(dark: boolean): Extension;
576
+ /**
577
+ * The foreground the injected diff chrome inherits: set it as the diff container's `color` so
578
+ * the revert buttons between the editors — which sit outside both editors' theme scopes —
579
+ * follow the flavor (and flip with `dark`). The editors paint their own flavor colors over it.
580
+ */
581
+ declare function diffChromeColor(dark: boolean): string;
582
+ /**
583
+ * The two merge-view surfaces an editor theme cannot reach, styled through one injected rule
584
+ * set per document/shadow root (the same pattern as the monaco host's hover-geometry fix),
585
+ * scoped to containers carrying the `data-inkstone-diff` attribute:
586
+ *
587
+ * - `.cm-mergeView` is the shared scroll surface *around* both editors; giving it the
588
+ * container's full height is what makes a container height scroll the diff as one unit.
589
+ * - The revert buttons live between the editors, outside either theme scope. They inherit
590
+ * the container's `color` (set from {@link diffChromeColor}, so dark flips reach them) and
591
+ * get a hover tint mixed from that same currentColor — no per-flavor rules needed.
592
+ */
593
+ declare function installDiffChrome(root: Document | ShadowRoot): void;
594
+ /**
595
+ * The document or shadow root a container mounts its editors into — what
596
+ * {@link installDiffChrome} takes, and what `ControlledMergeOptions.root` expects.
597
+ */
598
+ declare function editorRoot(container: HTMLElement): Document | ShadowRoot;
599
+ /**
600
+ * Keyboard access for the merge view's mouse-only controls, wired on the diff container:
601
+ * collapsed-lines bars become focusable buttons, revert buttons get an explicit `type` and a
602
+ * keyboard/programmatic-click bridge to the mousedown `@codemirror/merge` listens for. A
603
+ * MutationObserver keeps late-rendered controls covered; the returned function tears
604
+ * everything down.
605
+ */
606
+ declare function installDiffControlKeyboardSupport(container: HTMLElement): () => void;
607
+ //#endregion
559
608
  //#region src/merge-host.d.ts
560
609
  interface ControlledMergeOptions extends MergeConfig$1 {
561
610
  /**
@@ -621,7 +670,10 @@ interface ControlledMergeOptions extends MergeConfig$1 {
621
670
  * `collapseUnchanged` …) reconfigure through {@link MergeView.reconfigure} on {@link view}.
622
671
  *
623
672
  * Policy-free plumbing: neither side is read-only by default — wire that through
624
- * `originalExtensions` / `modifiedExtensions` to match how the documents are controlled.
673
+ * `originalExtensions` / `modifiedExtensions` to match how the documents are controlled
674
+ * and no inkstone language data fields are pre-installed: a host that dispatches
675
+ * `setMinijinjaContext` / `setSqlSchema` installs `minijinjaContextField` / `sqlSchemaField`
676
+ * in the shared `extensions` (same rule as `ControlledEditorHost`).
625
677
  * Framework-agnostic, same as `ControlledEditorHost`.
626
678
  */
627
679
  declare class ControlledMergeHost {
@@ -675,11 +727,20 @@ interface SqlSchema {
675
727
  */
676
728
  declare const setSqlSchema: import("@codemirror/state").StateEffectType<SqlSchema | null>;
677
729
  /**
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.
730
+ * Holds the schema that SQL table/column completion reads. `@coldsmirk/inkstone-react`'s
731
+ * components install it eagerly; the `sql`-family language supports also include it for
732
+ * hand-built editors. `null` (the default) means no schema — keyword completion still works,
733
+ * table/column completion stays off.
682
734
  */
683
735
  declare const sqlSchemaField: StateField<SqlSchema | null>;
684
736
  //#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 };
737
+ //#region src/theme.d.ts
738
+ declare function toCssSize(value: string | number | undefined): string | undefined;
739
+ /**
740
+ * inkstone's default dynamic theme layer: the catppuccin flavor picked by `dark`, plus
741
+ * optional height/width sizing. The catppuccin theme is injected at low precedence so a
742
+ * caller's own theme (layered after it, e.g. through a dynamic-extensions slot) overrides it.
743
+ */
744
+ declare function defaultThemeExtensions(dark: boolean, height?: string | number, width?: string | number, fontSize?: number, lineHeight?: number): Extension[];
745
+ //#endregion
746
+ 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, defaultThemeExtensions, diffChromeColor, documentExtensions, editorRoot, installDiffChrome, installDiffControlKeyboardSupport, loadLanguage, mergeTheme, minijinjaContextField, normalizeContext, resolveMembers, searchPhrasesZhCn, setMinijinjaContext, setSqlSchema, sqlSchemaField, toCssSize, typeAtPath };
package/dist/index.js CHANGED
@@ -1,11 +1,14 @@
1
1
  import { a as setMinijinjaContext, i as resolveMembers, n as minijinjaContextField, o as typeAtPath, r as normalizeContext } from "./context-BJzyiP0A.js";
2
2
  import { n as sqlSchemaField, t as setSqlSchema } from "./sql-schema-DiPx5-bu.js";
3
- import { Compartment, EditorState, Transaction } from "@codemirror/state";
3
+ import { Compartment, EditorState, Prec, Transaction } from "@codemirror/state";
4
4
  import { EditorView, drawSelection, highlightActiveLine, keymap, lineNumbers, placeholder } from "@codemirror/view";
5
5
  import { autocompletion, closeBrackets, closeBracketsKeymap, completionKeymap } from "@codemirror/autocomplete";
6
6
  import { defaultKeymap, history, historyKeymap, indentWithTab } from "@codemirror/commands";
7
7
  import { LanguageSupport, StreamLanguage, bracketMatching, codeFolding, foldGutter, foldKeymap, indentOnInput } from "@codemirror/language";
8
8
  import { highlightSelectionMatches, searchKeymap } from "@codemirror/search";
9
+ import { flavors } from "@catppuccin/palette";
10
+ import { catppuccinLatte, catppuccinMacchiato } from "@catppuccin/codemirror";
11
+ import { CODE_FONT_FAMILY } from "@coldsmirk/inkstone-core";
9
12
  import { MergeView } from "@codemirror/merge";
10
13
  //#region src/minimal-replace.ts
11
14
  function minimalReplace(state, externalValue) {
@@ -83,8 +86,6 @@ var ControlledEditorHost = class {
83
86
  state: EditorState.create({
84
87
  doc,
85
88
  extensions: [
86
- minijinjaContextField,
87
- sqlSchemaField,
88
89
  ...extensions,
89
90
  this.compartment.of(dynamicExtensions),
90
91
  EditorView.updateListener.of((update) => {
@@ -114,25 +115,8 @@ var ControlledEditorHost = class {
114
115
  this.view.destroy();
115
116
  }
116
117
  };
117
- const searchPhrasesZhCn = EditorState.phrases.of({
118
- Find: "查找",
119
- Replace: "替换",
120
- next: "下一个",
121
- previous: "上一个",
122
- all: "全部",
123
- "match case": "区分大小写",
124
- regexp: "正则表达式",
125
- "by word": "全字匹配",
126
- replace: "替换",
127
- "replace all": "全部替换",
128
- close: "关闭",
129
- "current match": "当前匹配",
130
- "on line": "所在行",
131
- "replaced $ matches": "已替换 $ 处匹配",
132
- "replaced match on line $": "已替换第 $ 行的匹配",
133
- "Go to line": "跳转到行",
134
- go: "跳转"
135
- });
118
+ //#endregion
119
+ //#region src/extensions.ts
136
120
  function documentExtensions({ placeholder: placeholder$1, lineWrapping = false, showLineNumbers = false, folding = false, spellcheck = false, search = false } = {}) {
137
121
  return [
138
122
  ...showLineNumbers ? [lineNumbers()] : [],
@@ -389,6 +373,378 @@ const codeMirrorLanguages = Object.keys(loaders).toSorted();
389
373
  function loadLanguage(language) {
390
374
  return loaders[language]();
391
375
  }
376
+ const searchPhrasesZhCn = EditorState.phrases.of({
377
+ Find: "查找",
378
+ Replace: "替换",
379
+ next: "下一个",
380
+ previous: "上一个",
381
+ all: "全部",
382
+ "match case": "区分大小写",
383
+ regexp: "正则表达式",
384
+ "by word": "全字匹配",
385
+ replace: "替换",
386
+ "replace all": "全部替换",
387
+ close: "关闭",
388
+ "current match": "当前匹配",
389
+ "on line": "所在行",
390
+ "replaced $ matches": "已替换 $ 处匹配",
391
+ "replaced match on line $": "已替换第 $ 行的匹配",
392
+ "Go to line": "跳转到行",
393
+ go: "跳转"
394
+ });
395
+ //#endregion
396
+ //#region src/theme.ts
397
+ function toCssSize(value) {
398
+ if (value === void 0) return;
399
+ return typeof value === "number" ? `${value}px` : value;
400
+ }
401
+ function alphaOf(color, alphaValue) {
402
+ return `rgba(${color.rgb.r}, ${color.rgb.g}, ${color.rgb.b}, ${alphaValue})`;
403
+ }
404
+ function completionIconMask(body) {
405
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" stroke="#000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">${body}</svg>`;
406
+ return `url("data:image/svg+xml,${encodeURIComponent(svg)}")`;
407
+ }
408
+ const COMPLETION_ICON_FALLBACK = completionIconMask("<circle cx=\"8\" cy=\"8\" r=\"2.5\"/>");
409
+ const COMPLETION_ICONS = {
410
+ keyword: completionIconMask("<path d=\"M5.5 5 2.5 8l3 3\"/><path d=\"m10.5 5 3 3-3 3\"/>"),
411
+ function: completionIconMask("<path d=\"M10.8 3.4c-2-.4-2.9.7-3.2 2.5L6.4 12c-.3 1.7-1.2 2.2-2.6 1.8\"/><path d=\"M4.6 7.5h5\"/>"),
412
+ method: completionIconMask("<path d=\"M10.8 3.4c-2-.4-2.9.7-3.2 2.5L6.4 12c-.3 1.7-1.2 2.2-2.6 1.8\"/><path d=\"M4.6 7.5h5\"/>"),
413
+ variable: completionIconMask("<path d=\"M4 5c2.2 0 5.3 6 8 6\"/><path d=\"M12 5c-2.7 0-5.8 6-8 6\"/>"),
414
+ constant: completionIconMask("<path d=\"M3.5 5.2h9\"/><path d=\"M6.2 5.2v6.3\"/><path d=\"M10.2 5.2v4.4c0 1.3.6 1.9 1.7 1.9\"/>"),
415
+ property: completionIconMask("<circle cx=\"4.9\" cy=\"11.1\" r=\"1.8\"/><circle cx=\"11.1\" cy=\"4.9\" r=\"1.8\"/><path d=\"M6.4 9.6l3.2-3.2\"/>"),
416
+ class: completionIconMask("<rect x=\"3.5\" y=\"3.5\" width=\"9\" height=\"9\" rx=\"2.2\"/>"),
417
+ interface: completionIconMask("<circle cx=\"8\" cy=\"8\" r=\"4.5\"/>"),
418
+ type: completionIconMask("<path d=\"M4 4.5h8\"/><path d=\"M8 4.5V12\"/>"),
419
+ enum: completionIconMask("<path d=\"M6.5 4.5h6\"/><path d=\"M6.5 8h6\"/><path d=\"M6.5 11.5h6\"/><path d=\"M3.4 4.5h.01\"/><path d=\"M3.4 8h.01\"/><path d=\"M3.4 11.5h.01\"/>"),
420
+ namespace: completionIconMask("<path d=\"M6.2 3.5c-1.3 0-1.9.6-1.9 1.9v1.2c0 .9-.4 1.4-1.3 1.4.9 0 1.3.5 1.3 1.4v1.2c0 1.3.6 1.9 1.9 1.9\"/><path d=\"M9.8 3.5c1.3 0 1.9.6 1.9 1.9v1.2c0 .9.4 1.4 1.3 1.4-.9 0-1.3.5-1.3 1.4v1.2c0 1.3-.6 1.9-1.9 1.9\"/>"),
421
+ text: completionIconMask("<path d=\"M3.5 5h9\"/><path d=\"M3.5 8h9\"/><path d=\"M3.5 11h5.5\"/>")
422
+ };
423
+ function completionIconRules() {
424
+ const rules = {};
425
+ for (const [kind, mask] of Object.entries(COMPLETION_ICONS)) rules[`.cm-completionIcon-${kind}:after`] = {
426
+ maskImage: mask,
427
+ WebkitMaskImage: mask
428
+ };
429
+ return rules;
430
+ }
431
+ function chromeTheme(dark) {
432
+ const { colors } = dark ? flavors.macchiato : flavors.latte;
433
+ const hairline = `1px solid ${alphaOf(colors.overlay0, .35)}`;
434
+ const frame = {
435
+ border: hairline,
436
+ borderRadius: "8px",
437
+ boxShadow: dark ? "0 8px 24px rgba(0, 0, 0, 0.5)" : "0 8px 24px rgba(0, 0, 0, 0.12)",
438
+ color: colors.text.hex
439
+ };
440
+ return EditorView.theme({
441
+ "&": { colorScheme: dark ? "dark" : "light" },
442
+ ".cm-scroller": { fontFamily: CODE_FONT_FAMILY },
443
+ ".cm-tooltip": frame,
444
+ ".cm-tooltip.cm-tooltip-autocomplete": {
445
+ ...frame,
446
+ "& > ul": {
447
+ borderRadius: "7px",
448
+ padding: "4px",
449
+ maxHeight: "16em"
450
+ },
451
+ "& > ul > li": {
452
+ display: "flex",
453
+ alignItems: "center",
454
+ padding: "3px 8px",
455
+ borderRadius: "5px",
456
+ lineHeight: "1.45"
457
+ }
458
+ },
459
+ ".cm-completionLabel": {
460
+ minWidth: "0",
461
+ overflow: "hidden",
462
+ textOverflow: "ellipsis"
463
+ },
464
+ ".cm-completionMatchedText": {
465
+ textDecoration: "none",
466
+ fontWeight: "600",
467
+ color: colors.blue.hex
468
+ },
469
+ ".cm-completionDetail": {
470
+ marginLeft: "auto",
471
+ paddingLeft: "12px",
472
+ fontStyle: "normal",
473
+ fontSize: "85%",
474
+ color: colors.overlay1.hex
475
+ },
476
+ ".cm-completionIcon": {
477
+ width: "14px",
478
+ paddingRight: "8px",
479
+ opacity: "1",
480
+ color: colors.overlay1.hex
481
+ },
482
+ ".cm-completionIcon:after": {
483
+ content: "''",
484
+ display: "block",
485
+ width: "12px",
486
+ height: "12px",
487
+ backgroundColor: "currentColor",
488
+ maskImage: COMPLETION_ICON_FALLBACK,
489
+ WebkitMaskImage: COMPLETION_ICON_FALLBACK,
490
+ maskRepeat: "no-repeat",
491
+ WebkitMaskRepeat: "no-repeat",
492
+ maskPosition: "center",
493
+ WebkitMaskPosition: "center",
494
+ maskSize: "contain",
495
+ WebkitMaskSize: "contain"
496
+ },
497
+ ...completionIconRules(),
498
+ ".cm-completionIcon-keyword": { color: colors.mauve.hex },
499
+ ".cm-completionIcon-function, .cm-completionIcon-method": { color: colors.blue.hex },
500
+ ".cm-completionIcon-variable": { color: colors.peach.hex },
501
+ ".cm-completionIcon-constant": { color: colors.sky.hex },
502
+ ".cm-completionIcon-property": { color: colors.teal.hex },
503
+ ".cm-completionIcon-namespace": { color: colors.sapphire.hex },
504
+ ".cm-completionIcon-text": { color: colors.overlay1.hex },
505
+ ".cm-completionIcon-class, .cm-completionIcon-type, .cm-completionIcon-interface, .cm-completionIcon-enum": { color: colors.yellow.hex },
506
+ ".cm-tooltip.cm-completionInfo": {
507
+ ...frame,
508
+ padding: "8px 10px",
509
+ maxWidth: "26em"
510
+ },
511
+ ".cm-tooltip-lint": {
512
+ padding: "4px",
513
+ maxWidth: "26em",
514
+ overflowWrap: "anywhere"
515
+ },
516
+ ".cm-diagnostic": {
517
+ margin: "0",
518
+ padding: "3px 8px",
519
+ borderLeftWidth: "3px",
520
+ borderRadius: "5px",
521
+ lineHeight: "1.45"
522
+ },
523
+ ".cm-diagnostic-error": { borderLeftColor: colors.red.hex },
524
+ ".cm-diagnostic-warning": { borderLeftColor: colors.yellow.hex },
525
+ ".cm-diagnostic-info": { borderLeftColor: colors.overlay1.hex },
526
+ ".cm-diagnostic-hint": { borderLeftColor: colors.blue.hex },
527
+ ".cm-diagnosticAction": {
528
+ padding: "2px 6px",
529
+ backgroundColor: colors.surface1.hex,
530
+ color: colors.text.hex,
531
+ borderRadius: "4px"
532
+ },
533
+ ".cm-diagnosticSource": {
534
+ fontSize: "85%",
535
+ color: colors.overlay1.hex,
536
+ opacity: "1"
537
+ },
538
+ "&.cm-focused": { outline: "none" },
539
+ ".cm-activeLine": { backgroundColor: alphaOf(colors.overlay0, .12) },
540
+ ".cm-activeLineGutter": { backgroundColor: alphaOf(colors.overlay0, .12) },
541
+ "&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": { backgroundColor: alphaOf(colors.blue, dark ? .35 : .25) },
542
+ ".cm-selectionBackground": { backgroundColor: alphaOf(colors.blue, dark ? .2 : .14) },
543
+ ".cm-content ::selection": { backgroundColor: alphaOf(colors.blue, dark ? .35 : .25) },
544
+ ".cm-scroller, .cm-tooltip-autocomplete > ul, .cm-tooltip.cm-completionInfo": {
545
+ scrollbarWidth: "thin",
546
+ scrollbarColor: `${alphaOf(colors.overlay0, .55)} transparent`
547
+ },
548
+ ".cm-panels.cm-panels-top": { borderBottom: hairline },
549
+ ".cm-panels.cm-panels-bottom": { borderTop: hairline },
550
+ ".cm-panel.cm-search, .cm-panel.cm-gotoLine": {
551
+ position: "relative",
552
+ display: "flex",
553
+ flexWrap: "wrap",
554
+ alignItems: "center",
555
+ columnGap: "8px",
556
+ rowGap: "8px",
557
+ margin: "0",
558
+ padding: "10px 44px 10px 14px"
559
+ },
560
+ ".cm-panel.cm-search br": {
561
+ flexBasis: "100%",
562
+ height: "0",
563
+ margin: "0",
564
+ border: "none"
565
+ },
566
+ ".cm-panel.cm-search label, .cm-panel.cm-gotoLine label": {
567
+ display: "inline-flex",
568
+ alignItems: "center",
569
+ gap: "6px",
570
+ margin: "0",
571
+ fontSize: "12.5px",
572
+ color: colors.subtext0.hex,
573
+ cursor: "pointer",
574
+ userSelect: "none"
575
+ },
576
+ ".cm-panel.cm-search input[type=checkbox]": {
577
+ margin: "0",
578
+ width: "14px",
579
+ height: "14px",
580
+ accentColor: colors.blue.hex
581
+ },
582
+ ".cm-panel.cm-search button[name=close]": {
583
+ position: "absolute",
584
+ top: "10px",
585
+ right: "10px",
586
+ boxSizing: "border-box",
587
+ width: "28px",
588
+ height: "28px",
589
+ padding: "0",
590
+ display: "inline-flex",
591
+ alignItems: "center",
592
+ justifyContent: "center",
593
+ fontSize: "16px",
594
+ lineHeight: "1",
595
+ color: colors.overlay1.hex,
596
+ background: "transparent",
597
+ border: "none",
598
+ borderRadius: "6px",
599
+ cursor: "pointer"
600
+ },
601
+ ".cm-panel.cm-search button[name=close]:hover": {
602
+ backgroundColor: colors.surface0.hex,
603
+ color: colors.text.hex
604
+ },
605
+ ".cm-panel .cm-textfield": {
606
+ boxSizing: "border-box",
607
+ height: "28px",
608
+ width: "240px",
609
+ maxWidth: "100%",
610
+ margin: "0",
611
+ padding: "0 10px",
612
+ fontSize: "12.5px",
613
+ color: colors.text.hex,
614
+ backgroundColor: colors.base.hex,
615
+ border: hairline,
616
+ borderRadius: "6px"
617
+ },
618
+ ".cm-panel .cm-textfield:focus": {
619
+ outline: "none",
620
+ borderColor: alphaOf(colors.blue, .6),
621
+ boxShadow: `0 0 0 2px ${alphaOf(colors.blue, .2)}`
622
+ },
623
+ ".cm-panel .cm-button": {
624
+ boxSizing: "border-box",
625
+ height: "28px",
626
+ margin: "0",
627
+ padding: "0 12px",
628
+ fontSize: "12.5px",
629
+ color: colors.text.hex,
630
+ backgroundImage: "none",
631
+ backgroundColor: colors.surface0.hex,
632
+ border: hairline,
633
+ borderRadius: "6px",
634
+ cursor: "pointer"
635
+ },
636
+ ".cm-panel .cm-button:hover": { backgroundColor: colors.surface1.hex },
637
+ ".cm-panel .cm-button:active": {
638
+ backgroundImage: "none",
639
+ backgroundColor: colors.surface1.hex
640
+ }
641
+ }, { dark });
642
+ }
643
+ function defaultThemeExtensions(dark, height, width, fontSize, lineHeight) {
644
+ const extensions = [Prec.low(chromeTheme(dark)), Prec.low(dark ? catppuccinMacchiato : catppuccinLatte)];
645
+ const cssHeight = toCssSize(height);
646
+ const cssWidth = toCssSize(width);
647
+ if (cssHeight !== void 0 || cssWidth !== void 0 || fontSize !== void 0 || lineHeight !== void 0) {
648
+ const root = {};
649
+ if (cssHeight !== void 0) root.height = cssHeight;
650
+ if (cssWidth !== void 0) root.width = cssWidth;
651
+ if (fontSize !== void 0) root.fontSize = `${fontSize}px`;
652
+ const spec = { "&": root };
653
+ const scroller = {};
654
+ if (cssHeight !== void 0) scroller.overflow = "auto";
655
+ if (lineHeight !== void 0) scroller.lineHeight = lineHeight < 8 ? String(lineHeight) : `${lineHeight}px`;
656
+ if (Object.keys(scroller).length > 0) spec[".cm-scroller"] = scroller;
657
+ extensions.push(EditorView.theme(spec));
658
+ }
659
+ return extensions;
660
+ }
661
+ //#endregion
662
+ //#region src/merge-chrome.ts
663
+ function mergeTheme(dark) {
664
+ const { colors } = dark ? flavors.macchiato : flavors.latte;
665
+ const removed = colors.red;
666
+ const added = colors.green;
667
+ const underline = (color) => `linear-gradient(${alphaOf(color, .5)}, ${alphaOf(color, .5)}) bottom/100% 2px no-repeat`;
668
+ return EditorView.theme({
669
+ "&.cm-merge-a .cm-changedLine": { backgroundColor: alphaOf(removed, dark ? .14 : .09) },
670
+ "&.cm-merge-b .cm-changedLine": { backgroundColor: alphaOf(added, dark ? .14 : .09) },
671
+ "&.cm-merge-a .cm-changedText": { background: underline(removed) },
672
+ "&.cm-merge-b .cm-changedText": { background: underline(added) },
673
+ "&.cm-merge-a .cm-changedLineGutter": { background: removed.hex },
674
+ "&.cm-merge-b .cm-changedLineGutter": { background: added.hex },
675
+ ".cm-collapsedLines": {
676
+ color: colors.subtext0.hex,
677
+ background: `linear-gradient(to bottom, transparent 0, ${colors.surface0.hex} 30%, ${colors.surface0.hex} 70%, transparent 100%)`
678
+ }
679
+ }, { dark });
680
+ }
681
+ function diffChromeColor(dark) {
682
+ return (dark ? flavors.macchiato : flavors.latte).colors.overlay1.hex;
683
+ }
684
+ const DIFF_CHROME_STYLE_SELECTOR = "style[data-inkstone=\"codemirror-diff\"]";
685
+ function installDiffChrome(root) {
686
+ if (root.querySelector(DIFF_CHROME_STYLE_SELECTOR)) return;
687
+ const style = (root.nodeType === 9 ? root : root.host.ownerDocument).createElement("style");
688
+ style.dataset.inkstone = "codemirror-diff";
689
+ style.textContent = [
690
+ "[data-inkstone-diff] > .cm-mergeView { height: 100%; }",
691
+ "[data-inkstone-diff] .cm-merge-revert button { color: inherit; border-radius: 4px; }",
692
+ "[data-inkstone-diff] .cm-merge-revert button:hover { background-color: color-mix(in srgb, currentColor 15%, transparent); }"
693
+ ].join("\n");
694
+ if (root.nodeType === 9) root.head.append(style);
695
+ else root.append(style);
696
+ }
697
+ function editorRoot(container) {
698
+ const root = container.getRootNode();
699
+ if (root.nodeType === 9 || root.nodeType === 11 && "host" in root) return root;
700
+ return container.ownerDocument;
701
+ }
702
+ function eventElement(event) {
703
+ const target = event.target;
704
+ return target?.nodeType === 1 ? target : target?.parentElement ?? null;
705
+ }
706
+ function installDiffControlKeyboardSupport(container) {
707
+ const enhanceControls = () => {
708
+ for (const control of container.querySelectorAll(":scope .cm-collapsedLines")) {
709
+ control.setAttribute("role", "button");
710
+ control.tabIndex = 0;
711
+ }
712
+ for (const button of container.querySelectorAll(":scope .cm-merge-revert button")) button.type = "button";
713
+ };
714
+ const handleClick = (event) => {
715
+ if (event.detail !== 0) return;
716
+ const button = eventElement(event)?.closest(".cm-merge-revert button");
717
+ if (!button || !container.contains(button)) return;
718
+ event.preventDefault();
719
+ const MouseEventConstructor = container.ownerDocument.defaultView?.MouseEvent ?? MouseEvent;
720
+ button.dispatchEvent(new MouseEventConstructor("mousedown", {
721
+ bubbles: true,
722
+ button: 0,
723
+ cancelable: true
724
+ }));
725
+ };
726
+ const handleKeyDown = (event) => {
727
+ if (event.key !== "Enter" && event.key !== " ") return;
728
+ const control = eventElement(event)?.closest(".cm-collapsedLines");
729
+ if (!control || !container.contains(control)) return;
730
+ event.preventDefault();
731
+ event.stopPropagation();
732
+ control.click();
733
+ };
734
+ enhanceControls();
735
+ const observer = new MutationObserver(enhanceControls);
736
+ observer.observe(container, {
737
+ childList: true,
738
+ subtree: true
739
+ });
740
+ container.addEventListener("click", handleClick);
741
+ container.addEventListener("keydown", handleKeyDown, { capture: true });
742
+ return () => {
743
+ observer.disconnect();
744
+ container.removeEventListener("click", handleClick);
745
+ container.removeEventListener("keydown", handleKeyDown, { capture: true });
746
+ };
747
+ }
392
748
  //#endregion
393
749
  //#region src/merge-host.ts
394
750
  var ControlledMergeHost = class {
@@ -405,8 +761,6 @@ var ControlledMergeHost = class {
405
761
  doc: original,
406
762
  extensions: [
407
763
  ...originalExtensions,
408
- minijinjaContextField,
409
- sqlSchemaField,
410
764
  ...extensions,
411
765
  this.originalCompartment.of(dynamicExtensions),
412
766
  EditorView.updateListener.of((update) => {
@@ -418,8 +772,6 @@ var ControlledMergeHost = class {
418
772
  doc: modified,
419
773
  extensions: [
420
774
  ...modifiedExtensions,
421
- minijinjaContextField,
422
- sqlSchemaField,
423
775
  ...extensions,
424
776
  this.modifiedCompartment.of(dynamicExtensions),
425
777
  EditorView.updateListener.of((update) => {
@@ -457,4 +809,4 @@ var ControlledMergeHost = class {
457
809
  }
458
810
  };
459
811
  //#endregion
460
- export { ControlledEditorHost, ControlledMergeHost, codeMirrorLanguages, documentExtensions, loadLanguage, minijinjaContextField, normalizeContext, resolveMembers, searchPhrasesZhCn, setMinijinjaContext, setSqlSchema, sqlSchemaField, typeAtPath };
812
+ export { ControlledEditorHost, ControlledMergeHost, codeMirrorLanguages, defaultThemeExtensions, diffChromeColor, documentExtensions, editorRoot, installDiffChrome, installDiffControlKeyboardSupport, loadLanguage, mergeTheme, minijinjaContextField, normalizeContext, resolveMembers, searchPhrasesZhCn, setMinijinjaContext, setSqlSchema, sqlSchemaField, toCssSize, typeAtPath };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@coldsmirk/inkstone-codemirror",
3
- "version": "0.11.0",
4
- "description": "Framework-agnostic CodeMirror 6 plumbing: a controlled editor host (create-once, external-value reconcile, in-place theme swap) and the standard document extension bundle.",
3
+ "version": "0.12.0",
4
+ "description": "Framework-agnostic CodeMirror 6 plumbing: a controlled editor host (create-once, external-value reconcile, in-place theme swap), the standard document extension bundle, and the editors' catppuccin theming.",
5
5
  "keywords": [
6
6
  "codemirror",
7
7
  "codemirror6",
@@ -34,6 +34,8 @@
34
34
  "dist"
35
35
  ],
36
36
  "dependencies": {
37
+ "@catppuccin/codemirror": "^1.0.1",
38
+ "@catppuccin/palette": "^1.8.0",
37
39
  "@codemirror/lang-angular": "^0.1.4",
38
40
  "@codemirror/lang-cpp": "^6.0.3",
39
41
  "@codemirror/lang-css": "^6.3.1",
@@ -59,7 +61,8 @@
59
61
  "@codemirror/legacy-modes": "^6.5.3",
60
62
  "@codemirror/lint": "^6.9.7",
61
63
  "@codemirror/merge": "^6.12.2",
62
- "@lezer/common": "^1.5.2"
64
+ "@lezer/common": "^1.5.2",
65
+ "@coldsmirk/inkstone-core": "^0.12.0"
63
66
  },
64
67
  "devDependencies": {
65
68
  "@codemirror/autocomplete": "^6.20.3",