@coldsmirk/inkstone-monaco 0.29.0 → 0.31.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 +9 -8
- package/dist/bounds-CZ_iAUoc.js +120 -0
- package/dist/index.d.ts +46 -34
- package/dist/index.js +397 -62
- package/dist/vite.d.ts +2 -3
- package/dist/workers/textmate.worker.js +296 -0
- package/dist/workers/ts.worker.js +1 -1
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -6,20 +6,21 @@ Part of [inkstone](https://github.com/coldsmirk/inkstone). For a drop-in React c
|
|
|
6
6
|
|
|
7
7
|
## What you get
|
|
8
8
|
|
|
9
|
-
- **`ensureMonacoHost(options?)`** — the process-wide host: resolves the local `monaco-editor` module (never the CDN), routes **all** built-in language workers (base editor, TypeScript/JavaScript, JSON, CSS/scss/less, HTML/handlebars/razor) as bundler-emitted chunks so everything works offline, and applies `locale: "zh-cn"` via Monaco's official NLS catalog when asked — plus, on every editor created under that host, zh-CN descriptions for the six command-palette actions Monaco describes in English literals that no catalog can reach (Fold, Unfold, Find with Arguments, Refactor..., Source Action..., Paste As...), each held to the installed `monaco-editor` by an upgrade-tripwire test. Idempotent — every caller gets the same module; a load failure is not cached, so a fresh call retries.
|
|
9
|
+
- **`ensureMonacoHost(options?)`** — the process-wide host: resolves the local `monaco-editor` module (never the CDN) and the parts of it the Shiki tokenization stands on, routes **all** built-in language workers (base editor, TypeScript/JavaScript, JSON, CSS/scss/less, HTML/handlebars/razor) and inkstone's own tokenization worker (the `textmate` label) as bundler-emitted chunks so everything works offline, and applies `locale: "zh-cn"` via Monaco's official NLS catalog when asked — plus, on every editor created under that host, zh-CN descriptions for the six command-palette actions Monaco describes in English literals that no catalog can reach (Fold, Unfold, Find with Arguments, Refactor..., Source Action..., Paste As...), each held to the installed `monaco-editor` by an upgrade-tripwire test. Idempotent — every caller gets the same module; a load failure is not cached, so a fresh call retries.
|
|
10
10
|
- **`getWorker` extension point** — plug third-party language workers (e.g. monaco-yaml) into the routing; return `undefined` to fall back to the built-in routes.
|
|
11
11
|
- **`monacoLanguages`** — the sorted catalog of every built-in language id the installed Monaco registers (with the `MonacoBuiltInLanguage` union type behind it), held to the installed `monaco-editor` by an upgrade-tripwire test. These are *ids*, not display names (`proto`, `sol`, `aes`); an unknown id silently falls back to `plaintext`.
|
|
12
12
|
- **`disabledKeybindings` / `installDisabledKeybindings`** — swallow the find/replace chords and the command palette's F1 for editors that opt in. Monaco's standalone keybinding service is page-global and its rules are permanent, so the install registers each chord once per monaco module's service (duplicate copies of this package share the ledger; independent monaco bundles register their own) and scopes it with a per-capability context key created on the opted-in editor; the chord table is held to the installed `monaco-editor` by an upgrade-tripwire test.
|
|
13
13
|
- **`installJsxTagClosing`** — VS Code's auto-closing JSX tags: a typed `>` that completes an opening tag inserts the matching closing tag and keeps the caret between the pair. Parser-backed: the bundled TS worker subclasses monaco's stock worker to expose `getJsxClosingTagAtPosition` — a language-service call monaco never proxied (VS Code ships the feature in its TypeScript extension, outside the editor core) — so generics, comparisons, arrow functions, and already-closed elements never trigger. Only a *typed* `>` triggers: the gates require one bare single-character insertion into a **writable** TypeScript/JavaScript model whose path ends in `.tsx` / `.jsx`, and the reply lands only while the caret still sits right after that `>` — so `replaceInEditor` replacements, `StreamingEditorController` appends (the stream holds the editor read-only), and other programmatic writes never earn a closing tag, and the insert arrives as its own undo step (one undo removes just the closing tag). A `getWorker` override that routes the `typescript` label to a worker without the subclass is detected on its first lookup and the install disarms silently. Multi-cursor typing and the diff editor's panes are deliberately out of scope. `<MonacoEditor>` installs it automatically.
|
|
14
14
|
- **`dropMonacoWorkerFallbacks()`** (from **`@coldsmirk/inkstone-monaco/vite`**) — a Vite plugin that removes monaco's built-in worker fallbacks at build time. Monaco's language workerManagers statically reference their workers as `new Worker(new URL(...))` fallbacks that never run under `ensureMonacoHost` (the environment check prefers `getWorker`), yet bundlers emit a chunk per reference — including a ~7MB TypeScript worker duplicate that is never fetched. The plugin rewrites the fallbacks into located throws so the duplicates are never emitted; if a monaco bump reshapes the pattern it warns and leaves the file untouched (the dead chunk returns, nothing breaks).
|
|
15
|
-
- **Embedded-editor fixes** — works around [microsoft/monaco-editor#5177](https://github.com/microsoft/monaco-editor/issues/5177) (find-widget button tooltips landing on top of the buttons they describe) with a `width: max-content` hover-layer style tagged `data-inkstone="monaco-hover-geometry"`; deleted the moment upstream fixes it.
|
|
15
|
+
- **Embedded-editor fixes** — works around [microsoft/monaco-editor#5177](https://github.com/microsoft/monaco-editor/issues/5177) (find-widget button tooltips landing on top of the buttons they describe) with a `width: max-content` hover-layer style tagged `data-inkstone="monaco-hover-geometry"`; deleted the moment upstream fixes it. And 0.57 draws an editor's context menu under `fixedOverflowWidgets` (inkstone's default) in a shadow root hosted in the node the editor was created in, which no theme color reaches — a transparent menu, its items over the code ([microsoft/monaco-editor#5511](https://github.com/microsoft/monaco-editor/issues/5511)); the host makes that node a `.monaco-component`, the class Monaco writes its theme's colors on, so the menu inherits them, and `<MonacoEditor>` keeps the class in the `className` it renders.
|
|
16
16
|
- **`StreamingEditorController`** — stream generated text into an editor without routing every token through app state: `begin()` / `append(chunk)` / `end()` / `discard()`, `attach(editor)` / `detach(editor)` with buffered replay for late-mounting editors (and for a model swapped onto the attached editor mid-stream, including after the final chunk), and `isStreaming()` for the onChange guard. The stream locks every editor it touches read-only and `end()` / `discard()` restore each one's own setting — by handle, so an editor detached mid-stream is restored too; `discard()` puts every displaced document back into the model it came from, and `end()` does the same for every model except the one keeping the streamed text. Full-buffer writes and restorations preserve each model's UTF-8 BOM. Editors and models the app disposed mid-stream are skipped, never touched — teardown can race the stream without wedging it.
|
|
17
17
|
- **`replaceInEditor(editor, old, new, all)`** — exact find-and-replace on an editor handle via `executeEdits`, preserving undo/cursor/folds; returns whether the edit landed (`false` = no editor / empty search / no match / a read-only editor refused it — e.g. one locked by a stream in progress), so a tool-call handler knows to fall back to a state write.
|
|
18
|
-
- **`registerShikiHighlighting(monaco, highlighter)`** — wire `@coldsmirk/inkstone-core`'s shared Shiki highlighter onto a monaco module, once per distinct module: defines the highlighter's themes as Monaco themes, registers each loaded grammar's canonical name as a Monaco language where Monaco has none and
|
|
19
|
-
- **
|
|
20
|
-
-
|
|
21
|
-
- **`
|
|
22
|
-
- **`
|
|
18
|
+
- **`registerShikiHighlighting(monaco, highlighter)`** — wire `@coldsmirk/inkstone-core`'s shared Shiki highlighter onto a monaco module (one from `ensureMonacoHost`), once per distinct module: defines the highlighter's themes as Monaco themes, registers each loaded grammar's canonical name as a Monaco language where Monaco has none and its Shiki tokenization in Monaco's registry — over Monaco's own, JSON's included (its mode's tokenizer is turned off through its configuration, its other features left on) — and patches `setTheme` so the Shiki theme ids route through the highlighter while non-Shiki ids (vs/vs-dark, caller-defined) keep Monaco's original path. Called again on a bridged module, it registers the grammars loaded since. A failed registration is restored and retried on the next call.
|
|
19
|
+
- **Tokenization off the page's thread** — VS Code's design, through the same door in Monaco: each model an editor shows is tokenized whole in a worker of its own, built from what the shared highlighter was built from (`highlighterSources`), the tokens and the state each line ends in sent back as they are made and moved past the edits the worker has not yet seen. The page's thread tokenizes only what Monaco still asks of it before the worker reaches it — a model's first lines in view, the lines an edit touches — and of those only a line it can afford: one of 1,000 characters or more, or one the grammar does not finish in 50 ms, is drawn in the default color until the worker's tokens arrive, the line after it read from the state before it (a minified line costs the grammar about 10 ms per 1,000 characters, 370 over 10,000, and one in view would hold the page that long). The worker bounds a line as VS Code does, at 20,000 characters and 500 ms. Where the page passes a line over in the lines it paints first, its own states for the lines after it stand — Monaco keeps a state it computed over one sent later — so an edit there is drawn from them until the worker's tokens arrive. A highlighter inkstone did not build, or a host that routes no worker for `textmate`, leaves every model to Monaco's own pass on the page's thread, under the same bound.
|
|
20
|
+
- **Which regex engine** — the worker builds the engine kind the shared highlighter was configured with: Oniguruma wherever an engine was configured, the strict JavaScript engine where none was, so the page and the worker tokenize alike. The JavaScript engine translates each grammar's patterns at the first tokenization — in the worker for a model, and on the page for the lines it tokenizes itself (a TypeScript file's first sixty lines: about 170 ms) — so an app that opens files in Monaco should configure `onigurumaEngine` from `@coldsmirk/inkstone-core` (`configureHighlighter({ engine: onigurumaEngine })`, before the first `getHighlighter()`): its WASM is inlined into your bundle and compiles the same lines in about 20 ms.
|
|
21
|
+
- **`registerShikiLanguage(monaco, highlighter, language)`** — the lazy path: an app that loads grammars as files are opened calls this after each `highlighter.loadLanguage(...)` and before a model names the language. Registers the language where Monaco lacks it (`toml`, `shellscript`), its tokenization once under the grammar's canonical name (an alias like `ts` collapses), the models of a grammar shiki has built again since (a language it embeds lazily arrived) tokenized again under the new one, and leaves the theme the editor shows alone — the one thing re-running `@shikijs/monaco`'s `shikiToMonaco` cannot promise, which is why the bridge is inkstone's own. Throws for a grammar the highlighter has not loaded.
|
|
22
|
+
- **`installDesignTokens()`** — defines the VS Code design tokens monaco-editor's chrome has read since 0.56 and the standalone editor still never does ([microsoft/monaco-editor#5408](https://github.com/microsoft/monaco-editor/issues/5408)): `--vscode-cornerRadius-{small,medium,large,xLarge,circle}` and `--vscode-shadow-{md,lg,xl}`, on `.monaco-editor` (a host's `overflowWidgetsDomNode` carries the class) and on `.monaco-component` — every context view, the class Monaco writes its own theme colors on: an element's tooltip and, since 0.57 under `fixedOverflowWidgets`, the editor's context menu stand in the node the editor was created in, outside `.monaco-editor`. Without them the context menu, completion, hover, and find widgets ship square and shadowless. 0.57 also took the completion list off `--vscode-shadow-lg` for a literal `0 2px 8px` shadow; it is put back on the token, so it wears the host's shadow as the hover and the context menu do. Each resolves through the host's `--inkstone-radius-*` / `--inkstone-shadow-*` token first (see `@coldsmirk/inkstone-core`), then a fallback scale in the spirit of 0.55's look. It also keeps an input box's focus ring single: the standalone editor outlines both the `.monaco-inputbox` container (its `synthetic-focus` ring, which follows the box's radius) and the inner `<input>` (square, 1px inside), and the inner ring's corners poke out as soon as the box has corners — the container's ring is the one ring. `ensureMonacoHost` installs it; idempotent.
|
|
23
|
+
- **`DEFAULT_MONACO_OPTIONS`** — the inkstone construction-option baseline (no minimap, 14px/1.6 type on the shared `--inkstone-font-family-code` token, embedded-friendly find widget, tuned completion, thin scrollbars). Spread it to extend: `{ ...DEFAULT_MONACO_OPTIONS, wordWrap: "on" }`. Left out on purpose: `fullwidthCharacterWidth: "twoCells"`, which centers each full-width character — CJK text among it — in exactly two cells of a monospace font, so it lines up with the grid the way a terminal draws it. It draws every such character as a span of its own, which costs on text dense with them (a Chinese JSON body, a document of prose), so it is a host's to turn on where the alignment matters more.
|
|
23
24
|
- **`reconcileModelValue` / `modelValueMatches`** — the controlled-value reconcile: diff an external value against a live model (normalized to the model's EOL) and apply one minimal `applyEdits` replace that never lands on the undo stack or moves a caret outside the changed region.
|
|
24
25
|
|
|
25
26
|
## Install
|
|
@@ -28,7 +29,7 @@ Part of [inkstone](https://github.com/coldsmirk/inkstone). For a drop-in React c
|
|
|
28
29
|
pnpm add @coldsmirk/inkstone-monaco monaco-editor
|
|
29
30
|
```
|
|
30
31
|
|
|
31
|
-
`monaco-editor` is a **peer dependency** — your app owns the version. Node.js >= 24 for build / SSR hosts. Workers are bundled from dependency-relative `new Worker(new URL(...))` references; Vite and webpack 5 handle this form natively. Plain Rollup does not emit these workers by itself, so configure worker/asset handling that bundles the referenced modules or implement `getWorker` for every label your app can request.
|
|
32
|
+
`monaco-editor` is a **peer dependency** — your app owns the version. Node.js >= 24 for build / SSR hosts. Workers are bundled from dependency-relative `new Worker(new URL(...))` references — monaco's five and inkstone's tokenization worker (`workers/textmate.worker.js`, under the `textmate` label); Vite and webpack 5 handle this form natively. Plain Rollup does not emit these workers by itself, so configure worker/asset handling that bundles the referenced modules or implement `getWorker` for every label your app can request.
|
|
32
33
|
|
|
33
34
|
## Quick start
|
|
34
35
|
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { INITIAL } from "@shikijs/vscode-textmate";
|
|
2
|
+
//#region src/tokenization/content.ts
|
|
3
|
+
const LINE_BREAK = /\r\n|\r|\n/;
|
|
4
|
+
function lineCount(text) {
|
|
5
|
+
let count = 1;
|
|
6
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
7
|
+
const char = text.codePointAt(index);
|
|
8
|
+
if (char === 10) count += 1;
|
|
9
|
+
else if (char === 13) {
|
|
10
|
+
count += 1;
|
|
11
|
+
if (text.codePointAt(index + 1) === 10) index += 1;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return count;
|
|
15
|
+
}
|
|
16
|
+
function lineSplice({ range, text }) {
|
|
17
|
+
return {
|
|
18
|
+
start: range.startLineNumber - 1,
|
|
19
|
+
removed: range.endLineNumber - range.startLineNumber + 1,
|
|
20
|
+
inserted: lineCount(text)
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function applySplice(array, { start, removed, inserted }, fill) {
|
|
24
|
+
const RUN = 8192;
|
|
25
|
+
const items = Array.from({ length: inserted }, (_, index) => fill(index));
|
|
26
|
+
array.splice(start, removed, ...items.slice(0, RUN));
|
|
27
|
+
for (let from = RUN; from < items.length; from += RUN) array.splice(start + from, 0, ...items.slice(from, from + RUN));
|
|
28
|
+
}
|
|
29
|
+
function applyChange(lines, change) {
|
|
30
|
+
const { range, text } = change;
|
|
31
|
+
const written = `${(lines[range.startLineNumber - 1] ?? "").slice(0, range.startColumn - 1)}${text}${(lines[range.endLineNumber - 1] ?? "").slice(range.endColumn - 1)}`.split(LINE_BREAK);
|
|
32
|
+
applySplice(lines, lineSplice(change), (index) => written[index] ?? "");
|
|
33
|
+
}
|
|
34
|
+
function lineAfter(line, edits) {
|
|
35
|
+
let at = line;
|
|
36
|
+
for (const { changes } of edits) for (const change of changes) {
|
|
37
|
+
const { start, removed, inserted } = lineSplice(change);
|
|
38
|
+
if (at - 1 >= start + removed) at += inserted - removed;
|
|
39
|
+
else if (at - 1 >= start) return;
|
|
40
|
+
}
|
|
41
|
+
return at;
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/tokenization/states.ts
|
|
45
|
+
const Stack = INITIAL.constructor;
|
|
46
|
+
function stateDiff(previous, next) {
|
|
47
|
+
let pops = 0;
|
|
48
|
+
const newFrames = [];
|
|
49
|
+
let first = previous;
|
|
50
|
+
let second = next;
|
|
51
|
+
while (first !== second) if (first && (!second || first.depth >= second.depth)) {
|
|
52
|
+
pops += 1;
|
|
53
|
+
first = first.parent;
|
|
54
|
+
} else if (second) {
|
|
55
|
+
newFrames.push(second.toStateStackFrame());
|
|
56
|
+
second = second.parent;
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
pops,
|
|
60
|
+
newFrames: newFrames.toReversed()
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function applyStateDiff(previous, diff) {
|
|
64
|
+
let stack = previous;
|
|
65
|
+
for (let pop = 0; pop < diff.pops; pop += 1) {
|
|
66
|
+
if (!stack) return null;
|
|
67
|
+
stack = stack.parent;
|
|
68
|
+
}
|
|
69
|
+
for (const frame of diff.newFrames) stack = Stack.pushFrame(stack, frame);
|
|
70
|
+
return stack;
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/tokenization/metadata.ts
|
|
74
|
+
const LANGUAGE_MASK = 255;
|
|
75
|
+
const KEPT_MASK = 32512;
|
|
76
|
+
const FOREGROUND_MASK = 16744448;
|
|
77
|
+
const FOREGROUND_OFFSET = 15;
|
|
78
|
+
const BACKGROUND_OFFSET = 24;
|
|
79
|
+
const DEFAULT_FOREGROUND = 1;
|
|
80
|
+
const DEFAULT_BACKGROUND = 2;
|
|
81
|
+
function hexOf(color) {
|
|
82
|
+
const hex = color.trim().toLowerCase();
|
|
83
|
+
return hex.length === 9 && hex.endsWith("ff") ? hex.slice(0, 7) : hex;
|
|
84
|
+
}
|
|
85
|
+
function channel(value) {
|
|
86
|
+
return Math.round(value).toString(16).padStart(2, "0");
|
|
87
|
+
}
|
|
88
|
+
function hexOfMapColor({ rgba }) {
|
|
89
|
+
const opaque = `#${channel(rgba.r)}${channel(rgba.g)}${channel(rgba.b)}`;
|
|
90
|
+
return rgba.a >= 1 ? opaque : `${opaque}${channel(rgba.a * 255)}`;
|
|
91
|
+
}
|
|
92
|
+
function colorIdsFor(highlighterColors, monacoColors) {
|
|
93
|
+
const monacoIds = /* @__PURE__ */ new Map();
|
|
94
|
+
const colors = monacoColors ?? [];
|
|
95
|
+
for (const [id, color] of colors.entries()) if (color && !monacoIds.has(hexOfMapColor(color))) monacoIds.set(hexOfMapColor(color), id);
|
|
96
|
+
return Uint16Array.from(highlighterColors, (color) => monacoIds.get(hexOf(color ?? "")) ?? DEFAULT_FOREGROUND);
|
|
97
|
+
}
|
|
98
|
+
function monacoMetadata(metadata, languageId, colorIds) {
|
|
99
|
+
const foreground = colorIds[(metadata & FOREGROUND_MASK) >>> FOREGROUND_OFFSET] ?? DEFAULT_FOREGROUND;
|
|
100
|
+
return (languageId & LANGUAGE_MASK | metadata & KEPT_MASK | foreground << FOREGROUND_OFFSET | DEFAULT_BACKGROUND << BACKGROUND_OFFSET) >>> 0;
|
|
101
|
+
}
|
|
102
|
+
function defaultMetadata(languageId) {
|
|
103
|
+
return (languageId & LANGUAGE_MASK | 33587200) >>> 0;
|
|
104
|
+
}
|
|
105
|
+
function toMonacoTokens(tokens, languageId, colorIds) {
|
|
106
|
+
for (let index = 1; index < tokens.length; index += 2) tokens[index] = monacoMetadata(tokens[index] ?? 0, languageId, colorIds);
|
|
107
|
+
return tokens;
|
|
108
|
+
}
|
|
109
|
+
function withEndOffsets(tokens, lineLength) {
|
|
110
|
+
const last = tokens.length - 2;
|
|
111
|
+
for (let index = 0; index < last; index += 2) tokens[index] = tokens[index + 2] ?? lineLength;
|
|
112
|
+
if (last >= 0) tokens[last] = lineLength;
|
|
113
|
+
return tokens;
|
|
114
|
+
}
|
|
115
|
+
//#endregion
|
|
116
|
+
//#region src/tokenization/bounds.ts
|
|
117
|
+
const SYNC_LINE_BOUND = 1e3;
|
|
118
|
+
const WORKER_LINE_BOUND = 2e4;
|
|
119
|
+
//#endregion
|
|
120
|
+
export { toMonacoTokens as a, stateDiff as c, lineAfter as d, lineSplice as f, defaultMetadata as i, applyChange as l, WORKER_LINE_BOUND as n, withEndOffsets as o, colorIdsFor as r, applyStateDiff as s, SYNC_LINE_BOUND as t, applySplice as u };
|
package/dist/index.d.ts
CHANGED
|
@@ -33,8 +33,9 @@ interface MonacoHostOptions {
|
|
|
33
33
|
*/
|
|
34
34
|
type MonacoModule = typeof monacoEditor;
|
|
35
35
|
/**
|
|
36
|
-
* Bring up the shared Monaco host: UI locale, then the monaco module
|
|
37
|
-
* fully offline, no CDN, workers bundled by
|
|
36
|
+
* Bring up the shared Monaco host: UI locale, then the monaco module and the parts of it the
|
|
37
|
+
* Shiki tokenization stands on, then worker routing — fully offline, no CDN, workers bundled by
|
|
38
|
+
* the consumer's bundler.
|
|
38
39
|
*
|
|
39
40
|
* Idempotent and async-safe: every caller shares one promise, and the first call wins the
|
|
40
41
|
* configuration (`globalThis.MonacoEnvironment` is a single global — per-editor configuration
|
|
@@ -50,7 +51,7 @@ type MonacoModule = typeof monacoEditor;
|
|
|
50
51
|
* (both are idempotent), instead of replaying one transient failure — a chunk 404 after a
|
|
51
52
|
* redeploy, a flaky network — for the rest of the session.
|
|
52
53
|
*/
|
|
53
|
-
declare function ensureMonacoHost(options?: MonacoHostOptions): Promise<MonacoModule>;
|
|
54
|
+
export declare function ensureMonacoHost(options?: MonacoHostOptions): Promise<MonacoModule>;
|
|
54
55
|
//#endregion
|
|
55
56
|
//#region src/auto-imports.d.ts
|
|
56
57
|
/**
|
|
@@ -74,18 +75,19 @@ interface AutoImportCompletionOptions {
|
|
|
74
75
|
* subclass (a `getWorker` override) is detected on the first answer and the install disarms
|
|
75
76
|
* silently. Install once per app; repeated calls return the first install's disposable.
|
|
76
77
|
*/
|
|
77
|
-
declare function installAutoImportCompletions(monaco: MonacoModule, options: AutoImportCompletionOptions): monacoEditor.IDisposable;
|
|
78
|
+
export declare function installAutoImportCompletions(monaco: MonacoModule, options: AutoImportCompletionOptions): monacoEditor.IDisposable;
|
|
78
79
|
//#endregion
|
|
79
80
|
//#region src/design-tokens.d.ts
|
|
80
81
|
/**
|
|
81
82
|
* Define the VS Code design tokens Monaco's chrome reads but the standalone editor lacks —
|
|
82
|
-
* corner radii and widget shadows —
|
|
83
|
-
* `--inkstone-radius-*` / `--inkstone-shadow-*` tokens with inkstone's
|
|
84
|
-
* input box's focus ring single now that the box has corners (see
|
|
83
|
+
* corner radii and widget shadows — where Monaco writes its theme's colors, resolved
|
|
84
|
+
* through the host's `--inkstone-radius-*` / `--inkstone-shadow-*` tokens with inkstone's
|
|
85
|
+
* fallbacks; keep an input box's focus ring single now that the box has corners (see
|
|
86
|
+
* `INPUTBOX_FOCUS_RULE`), and the completion list on the shadow token (`SUGGEST_SHADOW_RULE`).
|
|
85
87
|
* Installed by `ensureMonacoHost`; idempotent, so a host that brings monaco up on its own can
|
|
86
88
|
* call it too.
|
|
87
89
|
*/
|
|
88
|
-
declare function installDesignTokens(): void;
|
|
90
|
+
export declare function installDesignTokens(): void;
|
|
89
91
|
//#endregion
|
|
90
92
|
//#region src/hover-dead-band.d.ts
|
|
91
93
|
/**
|
|
@@ -102,7 +104,7 @@ interface HoverDeadBandTargetEditor {
|
|
|
102
104
|
* on every editor; a monaco whose hover controller no longer matches the pinned shape is
|
|
103
105
|
* left untouched. Dispose to restore the stock test.
|
|
104
106
|
*/
|
|
105
|
-
declare function installHoverDeadBandFix(editorInstance: HoverDeadBandTargetEditor): monacoEditor.IDisposable;
|
|
107
|
+
export declare function installHoverDeadBandFix(editorInstance: HoverDeadBandTargetEditor): monacoEditor.IDisposable;
|
|
106
108
|
//#endregion
|
|
107
109
|
//#region src/jsx-tag-closing.d.ts
|
|
108
110
|
/**
|
|
@@ -148,7 +150,7 @@ interface JsxTagClosingTargetEditor {
|
|
|
148
150
|
* detected on its first answer and the install disarms silently. Listener lifetime follows
|
|
149
151
|
* the editor; dispose the returned subscription to detach earlier.
|
|
150
152
|
*/
|
|
151
|
-
declare function installJsxTagClosing(monaco: MonacoModule, editorInstance: JsxTagClosingTargetEditor): monacoEditor.IDisposable;
|
|
153
|
+
export declare function installJsxTagClosing(monaco: MonacoModule, editorInstance: JsxTagClosingTargetEditor): monacoEditor.IDisposable;
|
|
152
154
|
//#endregion
|
|
153
155
|
//#region src/keybindings.d.ts
|
|
154
156
|
/**
|
|
@@ -163,13 +165,13 @@ interface SwallowedKeybindings {
|
|
|
163
165
|
* The keybindings `disableFind` / `disableCommandPalette` swallow: the find/replace widget's
|
|
164
166
|
* entries (Ctrl/Cmd+F to find; Ctrl+H and Cmd+Alt+F, the per-platform replace bindings) and
|
|
165
167
|
* the command palette's F1 — Monaco never binds Ctrl+Shift+P (a VS Code binding). Sourced
|
|
166
|
-
* from monaco-editor 0.
|
|
168
|
+
* from monaco-editor 0.57.0 (`findController.js`, `standaloneCommandsQuickAccess.js`).
|
|
167
169
|
*
|
|
168
170
|
* Both replace chords are swallowed on every platform rather than branching: `CtrlCmd` already
|
|
169
171
|
* resolves per platform, and the off-platform chord (Ctrl+Alt+F on Windows, Cmd+H on macOS, where
|
|
170
172
|
* the OS takes the key before the page sees it) is one Monaco never bound.
|
|
171
173
|
*/
|
|
172
|
-
declare function disabledKeybindings(monaco: MonacoModule, disableFind: boolean, disableCommandPalette: boolean): SwallowedKeybindings[];
|
|
174
|
+
export declare function disabledKeybindings(monaco: MonacoModule, disableFind: boolean, disableCommandPalette: boolean): SwallowedKeybindings[];
|
|
173
175
|
/**
|
|
174
176
|
* The slice of a standalone editor the install touches — a structural type, so the install is
|
|
175
177
|
* testable without evaluating the monaco module (which jsdom cannot do).
|
|
@@ -184,7 +186,7 @@ interface KeybindingTargetEditor {
|
|
|
184
186
|
* the page-global service only the first time any editor of that monaco module asks for it —
|
|
185
187
|
* monaco offers no way to unregister one.
|
|
186
188
|
*/
|
|
187
|
-
declare function installDisabledKeybindings(monaco: MonacoModule, editorInstance: KeybindingTargetEditor, groups: readonly SwallowedKeybindings[]): void;
|
|
189
|
+
export declare function installDisabledKeybindings(monaco: MonacoModule, editorInstance: KeybindingTargetEditor, groups: readonly SwallowedKeybindings[]): void;
|
|
188
190
|
//#endregion
|
|
189
191
|
//#region src/languages.d.ts
|
|
190
192
|
/**
|
|
@@ -208,7 +210,7 @@ type MonacoBuiltInLanguage = (typeof BUILT_IN_LANGUAGES)[number];
|
|
|
208
210
|
/**
|
|
209
211
|
* Every built-in Monaco language id, sorted.
|
|
210
212
|
*/
|
|
211
|
-
declare const monacoLanguages: readonly MonacoBuiltInLanguage[];
|
|
213
|
+
export declare const monacoLanguages: readonly MonacoBuiltInLanguage[];
|
|
212
214
|
/**
|
|
213
215
|
* The `<MonacoEditor language>` type. Monaco also accepts any custom-registered id (e.g. a
|
|
214
216
|
* language wired through `getWorker` such as monaco-yaml), so the type stays open: the union
|
|
@@ -223,7 +225,7 @@ type MonacoLanguage = MonacoBuiltInLanguage | (string & Record<never, never>);
|
|
|
223
225
|
* tuned for API-assisted editing, thin scrollbars, and an embedded-friendly find widget.
|
|
224
226
|
* Spread these to extend rather than replace: `options={{ ...DEFAULT_MONACO_OPTIONS, wordWrap: "on" }}`.
|
|
225
227
|
*/
|
|
226
|
-
declare const DEFAULT_MONACO_OPTIONS: editor.IStandaloneEditorConstructionOptions;
|
|
228
|
+
export declare const DEFAULT_MONACO_OPTIONS: editor.IStandaloneEditorConstructionOptions;
|
|
227
229
|
//#endregion
|
|
228
230
|
//#region src/reconcile.d.ts
|
|
229
231
|
/**
|
|
@@ -231,11 +233,11 @@ declare const DEFAULT_MONACO_OPTIONS: editor.IStandaloneEditorConstructionOption
|
|
|
231
233
|
* documents already match. Fires the model's content-change event synchronously when it
|
|
232
234
|
* edits — callers that must not re-enter their change callback silence it around this call.
|
|
233
235
|
*/
|
|
234
|
-
declare function reconcileModelValue(model: editor.ITextModel, externalValue: string): void;
|
|
236
|
+
export declare function reconcileModelValue(model: editor.ITextModel, externalValue: string): void;
|
|
235
237
|
/**
|
|
236
238
|
* Whether an external value already matches the model after applying its EOL convention.
|
|
237
239
|
*/
|
|
238
|
-
declare function modelValueMatches(model: editor.ITextModel, externalValue: string): boolean;
|
|
240
|
+
export declare function modelValueMatches(model: editor.ITextModel, externalValue: string): boolean;
|
|
239
241
|
//#endregion
|
|
240
242
|
//#region src/replace.d.ts
|
|
241
243
|
/**
|
|
@@ -245,36 +247,46 @@ declare function modelValueMatches(model: editor.ITextModel, externalValue: stri
|
|
|
245
247
|
* because it is read-only — e.g. locked by a stream in progress; the caller falls back to a
|
|
246
248
|
* state write).
|
|
247
249
|
*/
|
|
248
|
-
declare function replaceInEditor(target: editor.ICodeEditor | null, search: string, replaceWith: string, all: boolean): boolean;
|
|
250
|
+
export declare function replaceInEditor(target: editor.ICodeEditor | null, search: string, replaceWith: string, all: boolean): boolean;
|
|
249
251
|
//#endregion
|
|
250
252
|
//#region src/shiki-bridge.d.ts
|
|
251
253
|
/**
|
|
252
|
-
* Register the shared Shiki highlighter's languages, themes, and
|
|
253
|
-
* once per distinct module, whichever caller gets there first — and, on a module already
|
|
254
|
-
* bridged,
|
|
254
|
+
* Register the shared Shiki highlighter's languages, themes, and tokenization on a monaco module
|
|
255
|
+
* — once per distinct module, whichever caller gets there first — and, on a module already
|
|
256
|
+
* bridged, the tokenization of any grammar loaded since. Language services are unaffected:
|
|
255
257
|
* Shiki replaces syntax highlighting only. The module is recorded only after every registration
|
|
256
|
-
* succeeds, so a transient failure can retry on a later call.
|
|
258
|
+
* succeeds, so a transient failure can retry on a later call. The module must be the host's
|
|
259
|
+
* (`ensureMonacoHost`), which brings up the parts of Monaco the tokenization stands on.
|
|
260
|
+
*
|
|
261
|
+
* A model is tokenized whole in a worker of its own, built from what the highlighter was built
|
|
262
|
+
* from (`@coldsmirk/inkstone-core`'s `highlighterSources`) — VS Code's design, through the same
|
|
263
|
+
* door in Monaco: the page's thread tokenizes only the lines Monaco asks of it before the worker
|
|
264
|
+
* reaches them — a model's first lines in view, the lines an edit touches — and of those only a
|
|
265
|
+
* line it can afford; a longer one waits for the worker's colours (`tokenization/bounds.ts`).
|
|
266
|
+
* A highlighter inkstone did not build, or a host that routes no worker for the `textmate`
|
|
267
|
+
* label, leaves every model to Monaco's own pass on the page's thread, under the same bound.
|
|
257
268
|
*
|
|
258
269
|
* Monaco's own `setTheme` is patched to route the Shiki theme ids through the highlighter, so
|
|
259
|
-
* the
|
|
260
|
-
* caller-defined themes keep Monaco's original path. `create` is patched so its `theme`
|
|
261
|
-
* reaches that patch. The shape is `@shikijs/monaco`'s, kept here because that package
|
|
270
|
+
* the tokenization paints in the theme the editor shows; Monaco's built-in themes (vs/vs-dark)
|
|
271
|
+
* and caller-defined themes keep Monaco's original path. `create` is patched so its `theme`
|
|
272
|
+
* option reaches that patch. The shape is `@shikijs/monaco`'s, kept here because that package
|
|
262
273
|
* installs tokenizers only for the grammars loaded at the moment it is called, and re-calling
|
|
263
274
|
* it for a grammar loaded later resets the editor's theme to the first one.
|
|
264
275
|
*
|
|
265
276
|
* A grammar loaded into the highlighter *after* this call is not tokenized until it is
|
|
266
277
|
* registered — pass it to {@link registerShikiLanguage} (or call this again).
|
|
267
278
|
*/
|
|
268
|
-
declare function registerShikiHighlighting(monaco: MonacoModule, highlighter: HighlighterCore): void;
|
|
279
|
+
export declare function registerShikiHighlighting(monaco: MonacoModule, highlighter: HighlighterCore): void;
|
|
269
280
|
/**
|
|
270
|
-
*
|
|
271
|
-
* — the lazy path: an app that loads grammars as files are opened registers each here,
|
|
272
|
-
* its `loadLanguage`, and before a model names it. `language` may be an alias (`ts`); it
|
|
281
|
+
* Register the tokenization of one grammar the highlighter has loaded since the module was
|
|
282
|
+
* bridged — the lazy path: an app that loads grammars as files are opened registers each here,
|
|
283
|
+
* after its `loadLanguage`, and before a model names it. `language` may be an alias (`ts`); it
|
|
273
284
|
* collapses to the grammar's canonical name. Bridges the module first where nothing has, and
|
|
274
|
-
* is nothing for a grammar already registered
|
|
275
|
-
*
|
|
285
|
+
* is nothing for a grammar already registered — but a grammar shiki has built again since, as
|
|
286
|
+
* a language it embeds lazily arrived, has its models tokenized again under the new one. Throws
|
|
287
|
+
* when the highlighter has not loaded the grammar: there is nothing to tokenize with.
|
|
276
288
|
*/
|
|
277
|
-
declare function registerShikiLanguage(monaco: MonacoModule, highlighter: HighlighterCore, language: string): void;
|
|
289
|
+
export declare function registerShikiLanguage(monaco: MonacoModule, highlighter: HighlighterCore, language: string): void;
|
|
278
290
|
//#endregion
|
|
279
291
|
//#region src/streaming.d.ts
|
|
280
292
|
/**
|
|
@@ -297,7 +309,7 @@ type StreamTargetEditor = editor.ICodeEditor;
|
|
|
297
309
|
* accumulate in a buffer that is replayed on {@link attach}, so the final document is
|
|
298
310
|
* complete even if the editor never mounts.
|
|
299
311
|
*/
|
|
300
|
-
declare class StreamingEditorController {
|
|
312
|
+
export declare class StreamingEditorController {
|
|
301
313
|
private editor;
|
|
302
314
|
private writing;
|
|
303
315
|
private buffer;
|
|
@@ -398,4 +410,4 @@ declare class StreamingEditorController {
|
|
|
398
410
|
discard(): void;
|
|
399
411
|
}
|
|
400
412
|
//#endregion
|
|
401
|
-
export {
|
|
413
|
+
export type { AutoImportCompletionOptions, HoverDeadBandTargetEditor, JsxTagClosingTargetEditor, JsxTagClosingTargetModel, KeybindingTargetEditor, MonacoBuiltInLanguage, MonacoHostOptions, MonacoLanguage, MonacoModule, MonacoUiLocale, StreamTargetEditor, SwallowedKeybindings };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as toMonacoTokens, d as lineAfter, i as defaultMetadata, r as colorIdsFor, s as applyStateDiff } from "./bounds-CZ_iAUoc.js";
|
|
2
|
+
import { CODE_FONT_FAMILY, highlighterSources, inkstoneRadius, inkstoneShadow } from "@coldsmirk/inkstone-core";
|
|
2
3
|
import { EncodedTokenMetadata, INITIAL } from "@shikijs/vscode-textmate";
|
|
3
4
|
//#region src/auto-imports.ts
|
|
4
5
|
const MISSING_LOOKUP_REJECTION$1 = "Missing requestHandler or method: getAutoImportCompletionsAtPosition";
|
|
@@ -130,16 +131,18 @@ const DESIGN_TOKENS = [
|
|
|
130
131
|
`--vscode-cornerRadius-medium: ${inkstoneRadius("medium", "4px")}`,
|
|
131
132
|
`--vscode-cornerRadius-large: ${inkstoneRadius("large", "6px")}`,
|
|
132
133
|
`--vscode-cornerRadius-xLarge: ${inkstoneRadius("xlarge", "8px")}`,
|
|
134
|
+
"--vscode-cornerRadius-circle: 9999px",
|
|
133
135
|
`--vscode-shadow-md: ${inkstoneShadow("medium", "0 1px 4px var(--vscode-widget-shadow)")}`,
|
|
134
136
|
`--vscode-shadow-lg: ${inkstoneShadow("large", "0 2px 8px var(--vscode-widget-shadow)")}`,
|
|
135
137
|
`--vscode-shadow-xl: ${inkstoneShadow("xlarge", "0 4px 16px var(--vscode-widget-shadow)")}`
|
|
136
138
|
];
|
|
137
139
|
const INPUTBOX_FOCUS_RULE = ".monaco-editor .monaco-inputbox > .ibwrapper > .input:focus { outline: none; }";
|
|
140
|
+
const SUGGEST_SHADOW_RULE = ".monaco-editor:not(.hc-black):not(.hc-light) .suggest-widget { box-shadow: var(--vscode-shadow-lg); }";
|
|
138
141
|
function installDesignTokens() {
|
|
139
142
|
if (document.querySelector(DESIGN_TOKENS_STYLE_SELECTOR)) return;
|
|
140
143
|
const style = document.createElement("style");
|
|
141
144
|
style.dataset.inkstone = "monaco-design-tokens";
|
|
142
|
-
style.textContent = `.monaco-editor { ${DESIGN_TOKENS.join("; ")}; }\n${INPUTBOX_FOCUS_RULE}`;
|
|
145
|
+
style.textContent = `.monaco-editor, .monaco-diff-editor, .monaco-component { ${DESIGN_TOKENS.join("; ")}; }\n${INPUTBOX_FOCUS_RULE}\n${SUGGEST_SHADOW_RULE}`;
|
|
143
146
|
document.head.append(style);
|
|
144
147
|
}
|
|
145
148
|
//#endregion
|
|
@@ -180,6 +183,14 @@ function localizeActionDescriptions(editorInstance) {
|
|
|
180
183
|
}
|
|
181
184
|
}
|
|
182
185
|
//#endregion
|
|
186
|
+
//#region src/context-menu-fix.ts
|
|
187
|
+
const COMPONENT_CLASS = "monaco-component";
|
|
188
|
+
function installContextMenuThemeFix(monaco) {
|
|
189
|
+
monaco.editor.onDidCreateEditor((editorInstance) => {
|
|
190
|
+
editorInstance.getContainerDomNode().classList.add(COMPONENT_CLASS);
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
//#endregion
|
|
183
194
|
//#region src/geometry-fixes.ts
|
|
184
195
|
const HOVER_GEOMETRY_STYLE_SELECTOR = "style[data-inkstone=\"monaco-hover-geometry\"]";
|
|
185
196
|
function installHoverGeometryFix() {
|
|
@@ -197,14 +208,6 @@ function installSuggestGeometryFix() {
|
|
|
197
208
|
style.textContent = ".monaco-editor .suggest-widget .monaco-list .monaco-list-row > .contents > .main > .right > span.readMore.codicon.codicon-suggest-more-info::before { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }";
|
|
198
209
|
document.head.append(style);
|
|
199
210
|
}
|
|
200
|
-
const QUICK_INPUT_GEOMETRY_STYLE_SELECTOR = "style[data-inkstone=\"monaco-quick-input-geometry\"]";
|
|
201
|
-
function installQuickInputGeometryFix() {
|
|
202
|
-
if (document.querySelector(QUICK_INPUT_GEOMETRY_STYLE_SELECTOR)) return;
|
|
203
|
-
const style = document.createElement("style");
|
|
204
|
-
style.dataset.inkstone = "monaco-quick-input-geometry";
|
|
205
|
-
style.textContent = ".monaco-editor .quick-input-list { position: relative; }";
|
|
206
|
-
document.head.append(style);
|
|
207
|
-
}
|
|
208
211
|
const HOVER_FOCUS_STYLE_SELECTOR = "style[data-inkstone=\"monaco-hover-focus\"]";
|
|
209
212
|
function installHoverFocusFix() {
|
|
210
213
|
if (document.querySelector(HOVER_FOCUS_STYLE_SELECTOR)) return;
|
|
@@ -214,6 +217,33 @@ function installHoverFocusFix() {
|
|
|
214
217
|
document.head.append(style);
|
|
215
218
|
}
|
|
216
219
|
//#endregion
|
|
220
|
+
//#region src/tokenization/internals.ts
|
|
221
|
+
async function loadMonacoInternals() {
|
|
222
|
+
const [languages, tokens] = await Promise.all([import("monaco-editor/editor/common/languages.js"), import("monaco-editor/editor/common/tokens/contiguousMultilineTokensBuilder.js")]);
|
|
223
|
+
return {
|
|
224
|
+
registry: languages.TokenizationRegistry,
|
|
225
|
+
encoded: (encoded, endState) => new languages.EncodedTokenizationResult(encoded, [], endState),
|
|
226
|
+
classic: (classic, language, endState) => new languages.TokenizationResult(classic.map((token) => new languages.Token(token.offset, token.type, language)), endState),
|
|
227
|
+
multiline: (lines) => {
|
|
228
|
+
const builder = new tokens.ContiguousMultilineTokensBuilder();
|
|
229
|
+
for (const [lineNumber, lineTokens] of lines) builder.add(lineNumber, lineTokens);
|
|
230
|
+
return builder.finalize();
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
const INTERNALS_KEY = Symbol.for("coldsmirk.inkstone.monaco.internals");
|
|
235
|
+
function ledger() {
|
|
236
|
+
const host = globalThis;
|
|
237
|
+
host[INTERNALS_KEY] ??= /* @__PURE__ */ new WeakMap();
|
|
238
|
+
return host[INTERNALS_KEY];
|
|
239
|
+
}
|
|
240
|
+
function rememberInternals(monaco, internals) {
|
|
241
|
+
ledger().set(monaco, internals);
|
|
242
|
+
}
|
|
243
|
+
function internalsOf(monaco) {
|
|
244
|
+
return ledger().get(monaco);
|
|
245
|
+
}
|
|
246
|
+
//#endregion
|
|
217
247
|
//#region src/host.ts
|
|
218
248
|
const HOST_PROMISE_KEY = Symbol.for("coldsmirk.inkstone.monaco.hostPromise");
|
|
219
249
|
const NLS_REGISTRY_KEY = Symbol.for("coldsmirk.inkstone.monaco.nlsRegistry");
|
|
@@ -254,11 +284,13 @@ async function applyZhCnCatalog() {
|
|
|
254
284
|
function editorWorker() {
|
|
255
285
|
return new Worker(new URL("workers/editor.worker.js", import.meta.url), { type: "module" });
|
|
256
286
|
}
|
|
287
|
+
const TEXTMATE_WORKER_LABEL = "textmate";
|
|
257
288
|
const WORKER_BY_LABEL = {
|
|
258
289
|
typescript: () => new Worker(new URL("workers/ts.worker.js", import.meta.url), { type: "module" }),
|
|
259
290
|
json: () => new Worker(new URL("workers/json.worker.js", import.meta.url), { type: "module" }),
|
|
260
291
|
css: () => new Worker(new URL("workers/css.worker.js", import.meta.url), { type: "module" }),
|
|
261
|
-
html: () => new Worker(new URL("workers/html.worker.js", import.meta.url), { type: "module" })
|
|
292
|
+
html: () => new Worker(new URL("workers/html.worker.js", import.meta.url), { type: "module" }),
|
|
293
|
+
[TEXTMATE_WORKER_LABEL]: () => new Worker(new URL("workers/textmate.worker.js", import.meta.url), { type: "module" })
|
|
262
294
|
};
|
|
263
295
|
const WORKER_ALIASES = {
|
|
264
296
|
javascript: "typescript",
|
|
@@ -283,10 +315,11 @@ function ensureMonacoHost(options = {}) {
|
|
|
283
315
|
installDesignTokens();
|
|
284
316
|
installHoverGeometryFix();
|
|
285
317
|
installSuggestGeometryFix();
|
|
286
|
-
installQuickInputGeometryFix();
|
|
287
318
|
installHoverFocusFix();
|
|
288
319
|
const monaco = await import("monaco-editor");
|
|
320
|
+
rememberInternals(monaco, await loadMonacoInternals());
|
|
289
321
|
globalThis.MonacoEnvironment = { getWorker: (workerId, label) => getWorker?.(workerId, label) ?? builtinGetWorker(label) };
|
|
322
|
+
installContextMenuThemeFix(monaco);
|
|
290
323
|
if (locale === "zh-cn") monaco.editor.onDidCreateEditor(localizeActionDescriptions);
|
|
291
324
|
return monaco;
|
|
292
325
|
} catch (error) {
|
|
@@ -600,6 +633,280 @@ function replaceInEditor(target, search, replaceWith, all) {
|
|
|
600
633
|
return target.executeEdits("inkstone-replace", edits);
|
|
601
634
|
}
|
|
602
635
|
//#endregion
|
|
636
|
+
//#region src/tokenization/controller.ts
|
|
637
|
+
var WorkerTokenizerController = class {
|
|
638
|
+
model;
|
|
639
|
+
store;
|
|
640
|
+
internals;
|
|
641
|
+
channel;
|
|
642
|
+
attachment;
|
|
643
|
+
session;
|
|
644
|
+
disposed = false;
|
|
645
|
+
language;
|
|
646
|
+
constructor(options) {
|
|
647
|
+
this.language = options.language;
|
|
648
|
+
this.model = options.model;
|
|
649
|
+
this.store = options.store;
|
|
650
|
+
this.internals = options.internals;
|
|
651
|
+
this.channel = options.channel;
|
|
652
|
+
this.attachment = this.model.onDidChangeAttached(() => {
|
|
653
|
+
this.follow();
|
|
654
|
+
});
|
|
655
|
+
queueMicrotask(() => {
|
|
656
|
+
this.follow();
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
follow() {
|
|
660
|
+
if (this.disposed) return;
|
|
661
|
+
const attached = this.model.isAttachedToEditor();
|
|
662
|
+
if (attached && !this.session) this.connect();
|
|
663
|
+
else if (!attached && this.session) this.disconnect();
|
|
664
|
+
}
|
|
665
|
+
connect() {
|
|
666
|
+
const id = this.channel.open(this, this.model.getLinesContent(), this.model.getVersionId());
|
|
667
|
+
const pending = [];
|
|
668
|
+
const listener = this.model.onDidChangeContent((event) => {
|
|
669
|
+
const changes = event.changes.map(({ range, text }) => {
|
|
670
|
+
return {
|
|
671
|
+
range: {
|
|
672
|
+
startLineNumber: range.startLineNumber,
|
|
673
|
+
startColumn: range.startColumn,
|
|
674
|
+
endLineNumber: range.endLineNumber,
|
|
675
|
+
endColumn: range.endColumn
|
|
676
|
+
},
|
|
677
|
+
text
|
|
678
|
+
};
|
|
679
|
+
});
|
|
680
|
+
pending.push({
|
|
681
|
+
version: event.versionId,
|
|
682
|
+
changes
|
|
683
|
+
});
|
|
684
|
+
this.channel.send({
|
|
685
|
+
kind: "change",
|
|
686
|
+
model: id,
|
|
687
|
+
version: event.versionId,
|
|
688
|
+
changes
|
|
689
|
+
});
|
|
690
|
+
});
|
|
691
|
+
this.session = {
|
|
692
|
+
id,
|
|
693
|
+
pending,
|
|
694
|
+
listener
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
disconnect() {
|
|
698
|
+
if (!this.session) return;
|
|
699
|
+
this.session.listener.dispose();
|
|
700
|
+
this.channel.close(this.session.id);
|
|
701
|
+
this.session = void 0;
|
|
702
|
+
}
|
|
703
|
+
keepStates(message) {
|
|
704
|
+
let previous = null;
|
|
705
|
+
for (const [index, line] of message.lines.entries()) {
|
|
706
|
+
const diff = message.states[index];
|
|
707
|
+
const follows = index > 0 && message.lines[index - 1] === line - 1;
|
|
708
|
+
let state = null;
|
|
709
|
+
if (diff && (!follows || previous)) try {
|
|
710
|
+
state = applyStateDiff(follows ? previous : null, diff);
|
|
711
|
+
} catch {
|
|
712
|
+
state = null;
|
|
713
|
+
}
|
|
714
|
+
if (state) this.store.setEndState(line, state);
|
|
715
|
+
previous = state;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
receiveTokens(message) {
|
|
719
|
+
const { session } = this;
|
|
720
|
+
if (session?.id !== message.model) return;
|
|
721
|
+
const { pending } = session;
|
|
722
|
+
while (pending[0] && pending[0].version <= message.version) pending.shift();
|
|
723
|
+
const lines = [];
|
|
724
|
+
let offset = 0;
|
|
725
|
+
for (const [index, sent] of message.lines.entries()) {
|
|
726
|
+
const length = message.lengths[index] ?? 0;
|
|
727
|
+
const tokens = message.tokens.subarray(offset, offset + length);
|
|
728
|
+
const line = lineAfter(sent, pending);
|
|
729
|
+
offset += length;
|
|
730
|
+
if (line !== void 0) lines.push([line, tokens]);
|
|
731
|
+
}
|
|
732
|
+
if (lines.length > 0) this.store.setTokens(this.internals.multiline(lines));
|
|
733
|
+
if (pending.length === 0) this.keepStates(message);
|
|
734
|
+
}
|
|
735
|
+
finished(model, version) {
|
|
736
|
+
const { session } = this;
|
|
737
|
+
if (session?.id === model && session.pending.length === 0 && version === this.model.getVersionId()) this.store.backgroundTokenizationFinished();
|
|
738
|
+
}
|
|
739
|
+
requestTokens(startLineNumber, endLineNumberExclusive) {
|
|
740
|
+
if (this.session) this.channel.send({
|
|
741
|
+
kind: "retokenize",
|
|
742
|
+
model: this.session.id,
|
|
743
|
+
start: startLineNumber,
|
|
744
|
+
end: endLineNumberExclusive
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
dispose() {
|
|
748
|
+
if (this.disposed) return;
|
|
749
|
+
this.disposed = true;
|
|
750
|
+
this.attachment.dispose();
|
|
751
|
+
this.disconnect();
|
|
752
|
+
}
|
|
753
|
+
};
|
|
754
|
+
//#endregion
|
|
755
|
+
//#region src/tokenization/client.ts
|
|
756
|
+
var TokenizationClient = class {
|
|
757
|
+
options;
|
|
758
|
+
controllers = /* @__PURE__ */ new Map();
|
|
759
|
+
worker;
|
|
760
|
+
sentLanguages = 0;
|
|
761
|
+
nextModel = 1;
|
|
762
|
+
dead = false;
|
|
763
|
+
constructor(options) {
|
|
764
|
+
this.options = options;
|
|
765
|
+
}
|
|
766
|
+
start() {
|
|
767
|
+
if (this.dead) return false;
|
|
768
|
+
if (this.worker) return true;
|
|
769
|
+
let created;
|
|
770
|
+
try {
|
|
771
|
+
created = this.options.createWorker();
|
|
772
|
+
} catch {
|
|
773
|
+
this.dead = true;
|
|
774
|
+
return false;
|
|
775
|
+
}
|
|
776
|
+
this.worker = Promise.resolve(created);
|
|
777
|
+
this.worker.then((worker) => {
|
|
778
|
+
worker.addEventListener("message", (event) => {
|
|
779
|
+
this.receive(event.data);
|
|
780
|
+
});
|
|
781
|
+
worker.addEventListener("error", () => {
|
|
782
|
+
this.fail();
|
|
783
|
+
});
|
|
784
|
+
}, () => {
|
|
785
|
+
this.fail();
|
|
786
|
+
});
|
|
787
|
+
const { sources } = this.options;
|
|
788
|
+
this.sentLanguages = sources.languages.length;
|
|
789
|
+
this.post({
|
|
790
|
+
kind: "build",
|
|
791
|
+
engine: sources.engine,
|
|
792
|
+
themes: this.options.themes(),
|
|
793
|
+
languages: [...sources.languages]
|
|
794
|
+
});
|
|
795
|
+
return true;
|
|
796
|
+
}
|
|
797
|
+
sendLanguages() {
|
|
798
|
+
const { languages } = this.options.sources;
|
|
799
|
+
if (languages.length > this.sentLanguages) {
|
|
800
|
+
this.post({
|
|
801
|
+
kind: "languages",
|
|
802
|
+
languages: languages.slice(this.sentLanguages)
|
|
803
|
+
});
|
|
804
|
+
this.sentLanguages = languages.length;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
post(message) {
|
|
808
|
+
this.worker?.then((worker) => {
|
|
809
|
+
worker.postMessage(message);
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
receive(message) {
|
|
813
|
+
switch (message.kind) {
|
|
814
|
+
case "tokens":
|
|
815
|
+
this.controllers.get(message.model)?.receiveTokens(message);
|
|
816
|
+
break;
|
|
817
|
+
case "finished":
|
|
818
|
+
this.controllers.get(message.model)?.finished(message.model, message.version);
|
|
819
|
+
break;
|
|
820
|
+
case "unavailable": {
|
|
821
|
+
const controller = this.controllers.get(message.model);
|
|
822
|
+
if (controller) this.options.unavailable([controller.language]);
|
|
823
|
+
break;
|
|
824
|
+
}
|
|
825
|
+
case "failed": this.fail();
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
fail() {
|
|
829
|
+
if (this.dead) return;
|
|
830
|
+
this.dead = true;
|
|
831
|
+
this.worker?.then((worker) => {
|
|
832
|
+
worker.terminate();
|
|
833
|
+
});
|
|
834
|
+
this.worker = void 0;
|
|
835
|
+
this.options.unavailable("all");
|
|
836
|
+
}
|
|
837
|
+
open(model, store, tokenization) {
|
|
838
|
+
if (!this.start()) return;
|
|
839
|
+
return new WorkerTokenizerController({
|
|
840
|
+
language: tokenization.language,
|
|
841
|
+
model,
|
|
842
|
+
store,
|
|
843
|
+
internals: this.options.internals,
|
|
844
|
+
channel: {
|
|
845
|
+
open: (controller, lines, version) => {
|
|
846
|
+
const id = this.nextModel;
|
|
847
|
+
this.nextModel += 1;
|
|
848
|
+
this.sendLanguages();
|
|
849
|
+
this.controllers.set(id, controller);
|
|
850
|
+
this.post({
|
|
851
|
+
kind: "open",
|
|
852
|
+
model: id,
|
|
853
|
+
...tokenization,
|
|
854
|
+
version,
|
|
855
|
+
lines
|
|
856
|
+
});
|
|
857
|
+
return id;
|
|
858
|
+
},
|
|
859
|
+
send: (message) => {
|
|
860
|
+
this.post(message);
|
|
861
|
+
},
|
|
862
|
+
close: (id) => {
|
|
863
|
+
this.controllers.delete(id);
|
|
864
|
+
this.post({
|
|
865
|
+
kind: "close",
|
|
866
|
+
model: id
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
};
|
|
873
|
+
//#endregion
|
|
874
|
+
//#region src/tokenization/support.ts
|
|
875
|
+
function shikiTokenization(options) {
|
|
876
|
+
const { internals, highlighter, language, languageId, theme, colorIds, scopeOf, background } = options;
|
|
877
|
+
const tokenizeOnPage = (line, state) => {
|
|
878
|
+
if (line.length >= 1e3) return;
|
|
879
|
+
try {
|
|
880
|
+
highlighter.setTheme(theme());
|
|
881
|
+
const result = highlighter.getLanguage(language).tokenizeLine2(line, state, 50);
|
|
882
|
+
return result.stoppedEarly ? void 0 : result;
|
|
883
|
+
} catch {
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
886
|
+
};
|
|
887
|
+
return {
|
|
888
|
+
getInitialState: () => INITIAL,
|
|
889
|
+
tokenizeEncoded: (line, _hasEOL, state) => {
|
|
890
|
+
const result = tokenizeOnPage(line, state);
|
|
891
|
+
return result ? internals.encoded(toMonacoTokens(result.tokens, languageId, colorIds()), result.ruleStack) : internals.encoded(Uint32Array.of(0, defaultMetadata(languageId)), state);
|
|
892
|
+
},
|
|
893
|
+
tokenize: (line, _hasEOL, state) => {
|
|
894
|
+
const result = tokenizeOnPage(line, state);
|
|
895
|
+
if (!result) return internals.classic([{
|
|
896
|
+
offset: 0,
|
|
897
|
+
type: ""
|
|
898
|
+
}], language, state);
|
|
899
|
+
const tokens = [];
|
|
900
|
+
for (let index = 0; index < result.tokens.length; index += 2) tokens.push({
|
|
901
|
+
offset: result.tokens[index] ?? 0,
|
|
902
|
+
type: scopeOf(result.tokens[index + 1] ?? 0)
|
|
903
|
+
});
|
|
904
|
+
return internals.classic(tokens, language, result.ruleStack);
|
|
905
|
+
},
|
|
906
|
+
createBackgroundTokenizer: background
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
//#endregion
|
|
603
910
|
//#region src/shiki-bridge.ts
|
|
604
911
|
const SHIKI_REGISTRY_KEY = Symbol.for("coldsmirk.inkstone.monaco.shikiRegistry");
|
|
605
912
|
function bridges() {
|
|
@@ -610,8 +917,6 @@ function bridges() {
|
|
|
610
917
|
host[SHIKI_REGISTRY_KEY] = created;
|
|
611
918
|
return created;
|
|
612
919
|
}
|
|
613
|
-
const TOKENIZE_MAX_LINE_LENGTH = 2e4;
|
|
614
|
-
const TOKENIZE_TIME_LIMIT_MS = 500;
|
|
615
920
|
const ITALIC = 1;
|
|
616
921
|
const BOLD = 2;
|
|
617
922
|
const UNDERLINE = 4;
|
|
@@ -675,6 +980,7 @@ function monacoThemeOf(theme) {
|
|
|
675
980
|
function adoptTheme(bridge, themeName) {
|
|
676
981
|
const { colorMap } = bridge.highlighter.setTheme(themeName);
|
|
677
982
|
const theme = bridge.themes.get(themeName);
|
|
983
|
+
bridge.theme = themeName;
|
|
678
984
|
bridge.colorMap.length = colorMap.length;
|
|
679
985
|
for (const [index, color] of colorMap.entries()) bridge.colorMap[index] = color;
|
|
680
986
|
bridge.scopeOf.clear();
|
|
@@ -686,58 +992,78 @@ function adoptTheme(bridge, themeName) {
|
|
|
686
992
|
if (!bridge.scopeOf.has(key)) bridge.scopeOf.set(key, rule.token);
|
|
687
993
|
}
|
|
688
994
|
}
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
};
|
|
701
|
-
function tokensProvider(bridge, lang) {
|
|
702
|
-
return {
|
|
703
|
-
getInitialState() {
|
|
704
|
-
return new TokenizerState(INITIAL);
|
|
705
|
-
},
|
|
706
|
-
tokenize(line, state) {
|
|
707
|
-
if (line.length >= TOKENIZE_MAX_LINE_LENGTH) return {
|
|
708
|
-
endState: state,
|
|
709
|
-
tokens: [{
|
|
710
|
-
startIndex: 0,
|
|
711
|
-
scopes: ""
|
|
712
|
-
}]
|
|
713
|
-
};
|
|
714
|
-
const ruleStack = state instanceof TokenizerState ? state.ruleStack : INITIAL;
|
|
715
|
-
const result = bridge.highlighter.getLanguage(lang).tokenizeLine2(line, ruleStack, TOKENIZE_TIME_LIMIT_MS);
|
|
716
|
-
if (result.stoppedEarly) console.warn(`inkstone: time limit reached when tokenizing a line: ${line.slice(0, 100)}`);
|
|
717
|
-
const tokens = [];
|
|
718
|
-
for (let index = 0; index < result.tokens.length; index += 2) {
|
|
719
|
-
const startIndex = result.tokens[index] ?? 0;
|
|
720
|
-
const metadata = result.tokens[index + 1] ?? 0;
|
|
721
|
-
const color = normalizeColor(bridge.colorMap[EncodedTokenMetadata.getForeground(metadata)]);
|
|
722
|
-
const fontStyle = normalizeFontStyleBits(EncodedTokenMetadata.getFontStyle(metadata));
|
|
723
|
-
const scopes = color ? bridge.scopeOf.get(colorStyleKey(color, fontStyle)) ?? "" : "";
|
|
724
|
-
tokens.push({
|
|
725
|
-
startIndex,
|
|
726
|
-
scopes
|
|
727
|
-
});
|
|
728
|
-
}
|
|
729
|
-
return {
|
|
730
|
-
endState: new TokenizerState(result.ruleStack),
|
|
731
|
-
tokens
|
|
732
|
-
};
|
|
733
|
-
}
|
|
995
|
+
function scopeOfMetadata(bridge, metadata) {
|
|
996
|
+
const color = normalizeColor(bridge.colorMap[EncodedTokenMetadata.getForeground(metadata)]);
|
|
997
|
+
const fontStyle = normalizeFontStyleBits(EncodedTokenMetadata.getFontStyle(metadata));
|
|
998
|
+
return color ? bridge.scopeOf.get(colorStyleKey(color, fontStyle)) ?? "" : "";
|
|
999
|
+
}
|
|
1000
|
+
function colorIds(bridge) {
|
|
1001
|
+
const monacoColors = bridge.internals.registry.getColorMap();
|
|
1002
|
+
if (bridge.colorIds?.theme !== bridge.theme || bridge.colorIds.monacoColors !== monacoColors) bridge.colorIds = {
|
|
1003
|
+
theme: bridge.theme,
|
|
1004
|
+
monacoColors,
|
|
1005
|
+
ids: colorIdsFor(bridge.colorMap, monacoColors)
|
|
734
1006
|
};
|
|
1007
|
+
return bridge.colorIds.ids;
|
|
1008
|
+
}
|
|
1009
|
+
function clientOf(bridge) {
|
|
1010
|
+
if (bridge.client !== void 0) return bridge.client;
|
|
1011
|
+
const sources = highlighterSources(bridge.highlighter);
|
|
1012
|
+
const getWorker = globalThis.MonacoEnvironment?.getWorker;
|
|
1013
|
+
bridge.client = sources && getWorker ? new TokenizationClient({
|
|
1014
|
+
internals: bridge.internals,
|
|
1015
|
+
sources,
|
|
1016
|
+
themes: () => bridge.highlighter.getLoadedThemes().map((id) => bridge.highlighter.getTheme(id)),
|
|
1017
|
+
createWorker: () => getWorker("inkstone-textmate", TEXTMATE_WORKER_LABEL),
|
|
1018
|
+
unavailable: (languages) => {
|
|
1019
|
+
if (languages === "all") bridge.client = null;
|
|
1020
|
+
else for (const language of languages) bridge.workerless.add(language);
|
|
1021
|
+
bridge.internals.registry.handleChange(languages === "all" ? bridge.registered.keys().toArray() : [...languages]);
|
|
1022
|
+
}
|
|
1023
|
+
}) : null;
|
|
1024
|
+
return bridge.client;
|
|
1025
|
+
}
|
|
1026
|
+
function quietBuiltInTokenizer(monaco, language) {
|
|
1027
|
+
if (language !== "json") return;
|
|
1028
|
+
const { jsonDefaults } = monaco.json;
|
|
1029
|
+
if (jsonDefaults.modeConfiguration.tokens !== false) jsonDefaults.setModeConfiguration({
|
|
1030
|
+
...jsonDefaults.modeConfiguration,
|
|
1031
|
+
tokens: false
|
|
1032
|
+
});
|
|
735
1033
|
}
|
|
736
1034
|
function install(monaco, bridge, canonical) {
|
|
737
1035
|
if (bridge.registered.has(canonical)) return;
|
|
738
1036
|
if (monaco.languages.getLanguages().every((language) => language.id !== canonical)) monaco.languages.register({ id: canonical });
|
|
739
|
-
monaco
|
|
740
|
-
bridge.registered.
|
|
1037
|
+
quietBuiltInTokenizer(monaco, canonical);
|
|
1038
|
+
bridge.registered.set(canonical, bridge.highlighter.getLanguage(canonical));
|
|
1039
|
+
bridge.internals.registry.register(canonical, shikiTokenization({
|
|
1040
|
+
internals: bridge.internals,
|
|
1041
|
+
highlighter: bridge.highlighter,
|
|
1042
|
+
language: canonical,
|
|
1043
|
+
languageId: monaco.languages.getEncodedLanguageId(canonical),
|
|
1044
|
+
theme: () => bridge.theme,
|
|
1045
|
+
colorIds: () => colorIds(bridge),
|
|
1046
|
+
scopeOf: (metadata) => scopeOfMetadata(bridge, metadata),
|
|
1047
|
+
background: (model, store) => {
|
|
1048
|
+
return (bridge.workerless.has(canonical) ? null : clientOf(bridge))?.open(model, store, {
|
|
1049
|
+
language: canonical,
|
|
1050
|
+
languageId: monaco.languages.getEncodedLanguageId(canonical),
|
|
1051
|
+
theme: bridge.theme,
|
|
1052
|
+
colorIds: colorIds(bridge)
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
}));
|
|
1056
|
+
}
|
|
1057
|
+
function retokenizeRebuilt(bridge) {
|
|
1058
|
+
const rebuilt = [];
|
|
1059
|
+
for (const [language, grammar] of bridge.registered) {
|
|
1060
|
+
const current = bridge.highlighter.getLanguage(language);
|
|
1061
|
+
if (current !== grammar) {
|
|
1062
|
+
bridge.registered.set(language, current);
|
|
1063
|
+
rebuilt.push(language);
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
if (rebuilt.length > 0) bridge.internals.registry.handleChange(rebuilt);
|
|
741
1067
|
}
|
|
742
1068
|
function canonicalLanguages(highlighter) {
|
|
743
1069
|
return new Set(highlighter.getLoadedLanguages().map((id) => highlighter.getLanguage(id).name));
|
|
@@ -747,14 +1073,22 @@ function registerShikiHighlighting(monaco, highlighter) {
|
|
|
747
1073
|
const bridged = registry.get(monaco);
|
|
748
1074
|
if (bridged) {
|
|
749
1075
|
for (const canonical of canonicalLanguages(highlighter)) install(monaco, bridged, canonical);
|
|
1076
|
+
retokenizeRebuilt(bridged);
|
|
750
1077
|
return;
|
|
751
1078
|
}
|
|
1079
|
+
const internals = internalsOf(monaco);
|
|
1080
|
+
if (!internals) throw new Error("inkstone: Shiki highlighting needs the monaco module ensureMonacoHost brought up");
|
|
752
1081
|
const bridge = {
|
|
1082
|
+
internals,
|
|
753
1083
|
highlighter,
|
|
754
1084
|
themes: /* @__PURE__ */ new Map(),
|
|
1085
|
+
theme: "",
|
|
755
1086
|
colorMap: [],
|
|
1087
|
+
colorIds: void 0,
|
|
756
1088
|
scopeOf: /* @__PURE__ */ new Map(),
|
|
757
|
-
registered: /* @__PURE__ */ new
|
|
1089
|
+
registered: /* @__PURE__ */ new Map(),
|
|
1090
|
+
client: void 0,
|
|
1091
|
+
workerless: /* @__PURE__ */ new Set()
|
|
758
1092
|
};
|
|
759
1093
|
const originalCreate = monaco.editor.create;
|
|
760
1094
|
const originalSetTheme = monaco.editor.setTheme;
|
|
@@ -791,6 +1125,7 @@ function registerShikiLanguage(monaco, highlighter, language) {
|
|
|
791
1125
|
}
|
|
792
1126
|
if (!bridge) throw new Error("inkstone: the Shiki bridge is not installed on this monaco module");
|
|
793
1127
|
install(monaco, bridge, highlighter.getLanguage(language).name);
|
|
1128
|
+
retokenizeRebuilt(bridge);
|
|
794
1129
|
}
|
|
795
1130
|
//#endregion
|
|
796
1131
|
//#region src/streaming.ts
|
package/dist/vite.d.ts
CHANGED
|
@@ -17,6 +17,5 @@ import { Plugin } from "vite";
|
|
|
17
17
|
* time and leaves the file untouched — the duplicate chunk returns (dead weight, nothing
|
|
18
18
|
* breaks) until the pattern catches up.
|
|
19
19
|
*/
|
|
20
|
-
declare function dropMonacoWorkerFallbacks(): Plugin;
|
|
21
|
-
//#endregion
|
|
22
|
-
export { dropMonacoWorkerFallbacks };
|
|
20
|
+
export declare function dropMonacoWorkerFallbacks(): Plugin;
|
|
21
|
+
//#endregion
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { a as toMonacoTokens, c as stateDiff, f as lineSplice, i as defaultMetadata, l as applyChange, o as withEndOffsets, u as applySplice } from "../bounds-CZ_iAUoc.js";
|
|
2
|
+
import { INITIAL } from "@shikijs/vscode-textmate";
|
|
3
|
+
import { createHighlighterCore } from "shiki/core";
|
|
4
|
+
//#region src/tokenization/invalid-lines.ts
|
|
5
|
+
var InvalidLines = class {
|
|
6
|
+
runs = [];
|
|
7
|
+
constructor(lineCount = 0) {
|
|
8
|
+
this.add(0, lineCount);
|
|
9
|
+
}
|
|
10
|
+
first() {
|
|
11
|
+
return this.runs[0]?.start;
|
|
12
|
+
}
|
|
13
|
+
has(line) {
|
|
14
|
+
return this.runs.some((run) => run.start <= line && line < run.end);
|
|
15
|
+
}
|
|
16
|
+
add(start, end) {
|
|
17
|
+
if (end <= start) return;
|
|
18
|
+
let from = start;
|
|
19
|
+
let to = end;
|
|
20
|
+
const kept = this.runs.filter((run) => {
|
|
21
|
+
if (run.end < from || to < run.start) return true;
|
|
22
|
+
from = Math.min(from, run.start);
|
|
23
|
+
to = Math.max(to, run.end);
|
|
24
|
+
return false;
|
|
25
|
+
});
|
|
26
|
+
kept.push({
|
|
27
|
+
start: from,
|
|
28
|
+
end: to
|
|
29
|
+
});
|
|
30
|
+
kept.sort((one, other) => one.start - other.start);
|
|
31
|
+
this.runs.splice(0, this.runs.length, ...kept);
|
|
32
|
+
}
|
|
33
|
+
delete(line) {
|
|
34
|
+
const at = this.runs.findIndex((run) => run.start <= line && line < run.end);
|
|
35
|
+
const run = this.runs[at];
|
|
36
|
+
if (!run) return;
|
|
37
|
+
const pieces = [{
|
|
38
|
+
start: run.start,
|
|
39
|
+
end: line
|
|
40
|
+
}, {
|
|
41
|
+
start: line + 1,
|
|
42
|
+
end: run.end
|
|
43
|
+
}].filter((piece) => piece.start < piece.end);
|
|
44
|
+
this.runs.splice(at, 1, ...pieces);
|
|
45
|
+
}
|
|
46
|
+
splice({ start, removed, inserted }) {
|
|
47
|
+
const shift = inserted - removed;
|
|
48
|
+
const moved = this.runs.flatMap((run) => {
|
|
49
|
+
return [{
|
|
50
|
+
start: run.start,
|
|
51
|
+
end: Math.min(run.end, start)
|
|
52
|
+
}, {
|
|
53
|
+
start: Math.max(run.start, start + removed) + shift,
|
|
54
|
+
end: run.end + shift
|
|
55
|
+
}].filter((piece) => piece.start < piece.end);
|
|
56
|
+
});
|
|
57
|
+
this.runs.splice(0, this.runs.length, ...moved);
|
|
58
|
+
this.add(start, start + inserted);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
//#endregion
|
|
62
|
+
//#region src/tokenization/worker-runtime.ts
|
|
63
|
+
function hasWork(model) {
|
|
64
|
+
return model.requested.length > 0 || model.invalid.first() !== void 0;
|
|
65
|
+
}
|
|
66
|
+
async function engineOf(kind) {
|
|
67
|
+
if (kind === "oniguruma") {
|
|
68
|
+
const { createOnigurumaEngine } = await import("shiki/engine/oniguruma");
|
|
69
|
+
return createOnigurumaEngine(import("shiki/wasm"));
|
|
70
|
+
}
|
|
71
|
+
const { createJavaScriptRegexEngine } = await import("shiki/engine/javascript");
|
|
72
|
+
return createJavaScriptRegexEngine();
|
|
73
|
+
}
|
|
74
|
+
function tokenizeLine(grammar, model, line, state) {
|
|
75
|
+
if (line.length >= 2e4) return {
|
|
76
|
+
tokens: Uint32Array.of(line.length, defaultMetadata(model.languageId)),
|
|
77
|
+
endState: state
|
|
78
|
+
};
|
|
79
|
+
const result = grammar.tokenizeLine2(line, state, 500);
|
|
80
|
+
return {
|
|
81
|
+
tokens: withEndOffsets(toMonacoTokens(result.tokens, model.languageId, model.colorIds), line.length),
|
|
82
|
+
endState: result.stoppedEarly ? state : result.ruleStack
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
var Batch = class {
|
|
86
|
+
lines = [];
|
|
87
|
+
lengths = [];
|
|
88
|
+
parts = [];
|
|
89
|
+
states = [];
|
|
90
|
+
last;
|
|
91
|
+
get empty() {
|
|
92
|
+
return this.lines.length === 0;
|
|
93
|
+
}
|
|
94
|
+
add(line, tokens, state) {
|
|
95
|
+
const previous = this.last?.line === line - 1 ? this.last.state : null;
|
|
96
|
+
this.lines.push(line + 1);
|
|
97
|
+
this.lengths.push(tokens.length);
|
|
98
|
+
this.parts.push(tokens);
|
|
99
|
+
this.states.push(stateDiff(previous, state));
|
|
100
|
+
this.last = {
|
|
101
|
+
line,
|
|
102
|
+
state
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
post(post, model, version) {
|
|
106
|
+
const tokens = new Uint32Array(this.lengths.reduce((sum, length) => sum + length, 0));
|
|
107
|
+
let offset = 0;
|
|
108
|
+
for (const part of this.parts) {
|
|
109
|
+
tokens.set(part, offset);
|
|
110
|
+
offset += part.length;
|
|
111
|
+
}
|
|
112
|
+
const lines = Uint32Array.from(this.lines);
|
|
113
|
+
const lengths = Uint32Array.from(this.lengths);
|
|
114
|
+
post({
|
|
115
|
+
kind: "tokens",
|
|
116
|
+
model,
|
|
117
|
+
version,
|
|
118
|
+
lines,
|
|
119
|
+
lengths,
|
|
120
|
+
tokens,
|
|
121
|
+
states: this.states
|
|
122
|
+
}, [
|
|
123
|
+
tokens.buffer,
|
|
124
|
+
lines.buffer,
|
|
125
|
+
lengths.buffer
|
|
126
|
+
]);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
var TokenizationWorker = class {
|
|
130
|
+
send;
|
|
131
|
+
schedule;
|
|
132
|
+
now;
|
|
133
|
+
highlighter;
|
|
134
|
+
models = /* @__PURE__ */ new Map();
|
|
135
|
+
scheduled = false;
|
|
136
|
+
failed = false;
|
|
137
|
+
constructor(send, schedule = (work) => {
|
|
138
|
+
setTimeout(() => {
|
|
139
|
+
work();
|
|
140
|
+
}, 0);
|
|
141
|
+
}, now = () => performance.now()) {
|
|
142
|
+
this.send = send;
|
|
143
|
+
this.schedule = schedule;
|
|
144
|
+
this.now = now;
|
|
145
|
+
}
|
|
146
|
+
wake() {
|
|
147
|
+
if (this.scheduled || this.failed || !this.highlighter || this.models.values().every((model) => !hasWork(model))) return;
|
|
148
|
+
this.scheduled = true;
|
|
149
|
+
this.schedule(() => this.work());
|
|
150
|
+
}
|
|
151
|
+
async work() {
|
|
152
|
+
try {
|
|
153
|
+
const highlighter = await this.highlighter;
|
|
154
|
+
if (highlighter) {
|
|
155
|
+
const deadline = this.now() + 8;
|
|
156
|
+
const turns = this.models.values().toArray();
|
|
157
|
+
for (const model of turns) {
|
|
158
|
+
if (this.now() >= deadline) break;
|
|
159
|
+
if (hasWork(model)) {
|
|
160
|
+
this.tokenizeSlice(highlighter, model, deadline);
|
|
161
|
+
if (this.models.delete(model.id)) this.models.set(model.id, model);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
} catch (error) {
|
|
166
|
+
this.failed = true;
|
|
167
|
+
this.send({
|
|
168
|
+
kind: "failed",
|
|
169
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
170
|
+
}, []);
|
|
171
|
+
} finally {
|
|
172
|
+
this.scheduled = false;
|
|
173
|
+
}
|
|
174
|
+
this.wake();
|
|
175
|
+
}
|
|
176
|
+
tokenizeSlice(highlighter, model, deadline) {
|
|
177
|
+
let grammar;
|
|
178
|
+
try {
|
|
179
|
+
highlighter.setTheme(model.theme);
|
|
180
|
+
grammar = highlighter.getLanguage(model.language);
|
|
181
|
+
} catch {
|
|
182
|
+
this.models.delete(model.id);
|
|
183
|
+
this.send({
|
|
184
|
+
kind: "unavailable",
|
|
185
|
+
model: model.id
|
|
186
|
+
}, []);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const batch = new Batch();
|
|
190
|
+
const requested = [...model.requested];
|
|
191
|
+
model.requested.length = 0;
|
|
192
|
+
for (const { start, end } of requested) for (let line = start; line < Math.min(end, model.lines.length); line += 1) {
|
|
193
|
+
const tokens = model.tokens[line];
|
|
194
|
+
const state = model.states[line];
|
|
195
|
+
if (tokens && state && !model.invalid.has(line)) batch.add(line, tokens, state);
|
|
196
|
+
}
|
|
197
|
+
while (this.now() < deadline) {
|
|
198
|
+
const line = model.invalid.first();
|
|
199
|
+
if (line === void 0) break;
|
|
200
|
+
const start = line === 0 ? INITIAL : model.states[line - 1] ?? INITIAL;
|
|
201
|
+
const { tokens, endState } = tokenizeLine(grammar, model, model.lines[line] ?? "", start);
|
|
202
|
+
const previous = model.states[line];
|
|
203
|
+
model.states[line] = endState;
|
|
204
|
+
model.tokens[line] = tokens;
|
|
205
|
+
model.invalid.delete(line);
|
|
206
|
+
if (line + 1 < model.lines.length && !previous?.equals(endState)) model.invalid.add(line + 1, line + 2);
|
|
207
|
+
batch.add(line, tokens, endState);
|
|
208
|
+
}
|
|
209
|
+
if (!batch.empty) batch.post(this.send, model.id, model.version);
|
|
210
|
+
if (model.invalid.first() === void 0 && !model.finished) {
|
|
211
|
+
model.finished = true;
|
|
212
|
+
this.send({
|
|
213
|
+
kind: "finished",
|
|
214
|
+
model: model.id,
|
|
215
|
+
version: model.version
|
|
216
|
+
}, []);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
receive(message) {
|
|
220
|
+
switch (message.kind) {
|
|
221
|
+
case "build":
|
|
222
|
+
this.highlighter = (async () => createHighlighterCore({
|
|
223
|
+
themes: [...message.themes],
|
|
224
|
+
langs: [...message.languages],
|
|
225
|
+
engine: await engineOf(message.engine)
|
|
226
|
+
}))();
|
|
227
|
+
break;
|
|
228
|
+
case "languages":
|
|
229
|
+
this.highlighter = this.highlighter?.then((highlighter) => {
|
|
230
|
+
try {
|
|
231
|
+
highlighter.loadLanguageSync([...message.languages]);
|
|
232
|
+
} catch (error) {
|
|
233
|
+
console.warn("inkstone: the tokenization worker could not load a grammar", error);
|
|
234
|
+
}
|
|
235
|
+
return highlighter;
|
|
236
|
+
});
|
|
237
|
+
break;
|
|
238
|
+
case "open":
|
|
239
|
+
this.models.set(message.model, {
|
|
240
|
+
id: message.model,
|
|
241
|
+
language: message.language,
|
|
242
|
+
languageId: message.languageId,
|
|
243
|
+
theme: message.theme,
|
|
244
|
+
colorIds: message.colorIds,
|
|
245
|
+
version: message.version,
|
|
246
|
+
lines: [...message.lines],
|
|
247
|
+
states: [],
|
|
248
|
+
tokens: [],
|
|
249
|
+
invalid: new InvalidLines(message.lines.length),
|
|
250
|
+
requested: [],
|
|
251
|
+
finished: false
|
|
252
|
+
});
|
|
253
|
+
break;
|
|
254
|
+
case "change": {
|
|
255
|
+
const model = this.models.get(message.model);
|
|
256
|
+
if (model) {
|
|
257
|
+
for (const change of message.changes) {
|
|
258
|
+
const splice = lineSplice(change);
|
|
259
|
+
applyChange(model.lines, change);
|
|
260
|
+
applySplice(model.states, splice, () => void 0);
|
|
261
|
+
applySplice(model.tokens, splice, () => void 0);
|
|
262
|
+
model.invalid.splice(splice);
|
|
263
|
+
}
|
|
264
|
+
model.version = message.version;
|
|
265
|
+
model.finished = false;
|
|
266
|
+
model.requested.length = 0;
|
|
267
|
+
}
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
case "retokenize":
|
|
271
|
+
this.models.get(message.model)?.requested.push({
|
|
272
|
+
start: message.start - 1,
|
|
273
|
+
end: message.end - 1
|
|
274
|
+
});
|
|
275
|
+
break;
|
|
276
|
+
case "close": this.models.delete(message.model);
|
|
277
|
+
}
|
|
278
|
+
this.wake();
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
//#endregion
|
|
282
|
+
//#region src/tokenization/worker-entry.ts
|
|
283
|
+
function startTokenizationWorker() {
|
|
284
|
+
const scope = globalThis;
|
|
285
|
+
const worker = new TokenizationWorker((message, transfer) => {
|
|
286
|
+
scope.postMessage(message, transfer);
|
|
287
|
+
});
|
|
288
|
+
scope.addEventListener("message", (event) => {
|
|
289
|
+
worker.receive(event.data);
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
//#endregion
|
|
293
|
+
//#region src/workers/textmate.worker.js
|
|
294
|
+
startTokenizationWorker();
|
|
295
|
+
//#endregion
|
|
296
|
+
export {};
|
|
@@ -63,7 +63,7 @@ class JsxTypeScriptWorker extends TypeScriptWorker {
|
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
// Monaco's worker handshake is two-staged (`internal/common/initialize.js`, 0.
|
|
66
|
+
// Monaco's worker handshake is two-staged (`internal/common/initialize.js`, 0.57): a first
|
|
67
67
|
// message merely triggers `initialize`, whose own handler then reads the *second* message as
|
|
68
68
|
// createData. The stock entry import above installed the stage-one handler for its own
|
|
69
69
|
// factory; this assignment must *replace* it with one building the subclass — calling
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coldsmirk/inkstone-monaco",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.0",
|
|
4
4
|
"description": "Framework-agnostic Monaco assembly: lazy offline host (all language workers bundled, no CDN), opt-in Simplified-Chinese UI via monaco's official NLS catalog, a streaming editor controller for AI-typed content, and the shared Shiki highlighting bridge.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"monaco",
|
|
@@ -40,14 +40,15 @@
|
|
|
40
40
|
],
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@shikijs/vscode-textmate": "^10.0.2",
|
|
43
|
-
"
|
|
43
|
+
"shiki": "^4.5.0",
|
|
44
|
+
"@coldsmirk/inkstone-core": "^0.31.0"
|
|
44
45
|
},
|
|
45
46
|
"devDependencies": {
|
|
46
|
-
"monaco-editor": "^0.
|
|
47
|
-
"vite": "^8.
|
|
47
|
+
"monaco-editor": "^0.57.0",
|
|
48
|
+
"vite": "^8.3.4"
|
|
48
49
|
},
|
|
49
50
|
"peerDependencies": {
|
|
50
|
-
"monaco-editor": "^0.
|
|
51
|
+
"monaco-editor": "^0.57.0",
|
|
51
52
|
"vite": ">=6"
|
|
52
53
|
},
|
|
53
54
|
"peerDependenciesMeta": {
|