@coldsmirk/inkstone-monaco 0.14.0 → 0.16.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 +1 -0
- package/dist/index.d.ts +33 -3
- package/dist/index.js +33 -2
- package/dist/workers/css.worker.js +1 -1
- package/dist/workers/editor.worker.js +1 -1
- package/dist/workers/html.worker.js +1 -1
- package/dist/workers/json.worker.js +1 -1
- package/dist/workers/ts.worker.js +23 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -10,6 +10,7 @@ Part of [inkstone](https://github.com/coldsmirk/inkstone). For a drop-in React c
|
|
|
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
|
+
- **`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. The keystroke handler gates itself to TypeScript/JavaScript models whose path ends in `.tsx` / `.jsx`; a `getWorker` override that routes the `typescript` label to a worker without the subclass downgrades the feature to a no-op. `<MonacoEditor>` installs it automatically.
|
|
13
14
|
- **Embedded-editor fix** — 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. The tooltip layer positions against the editor's container — give that container `position: relative`.
|
|
14
15
|
- **`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.
|
|
15
16
|
- **`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.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { HighlighterCore } from "@coldsmirk/inkstone-core";
|
|
2
2
|
import * as monacoEditor from "monaco-editor";
|
|
3
3
|
import { editor } from "monaco-editor";
|
|
4
|
-
|
|
5
4
|
//#region src/host.d.ts
|
|
6
5
|
/**
|
|
7
6
|
* Supported UI locales for Monaco's built-in chrome (context menu, find widget, command
|
|
@@ -52,6 +51,37 @@ type MonacoModule = typeof monacoEditor;
|
|
|
52
51
|
*/
|
|
53
52
|
declare function ensureMonacoHost(options?: MonacoHostOptions): Promise<MonacoModule>;
|
|
54
53
|
//#endregion
|
|
54
|
+
//#region src/jsx-tag-closing.d.ts
|
|
55
|
+
/**
|
|
56
|
+
* The slice of a text model the install reads — a structural type, so the install is testable
|
|
57
|
+
* without evaluating the monaco module (which jsdom cannot do).
|
|
58
|
+
*/
|
|
59
|
+
interface JsxTagClosingTargetModel {
|
|
60
|
+
uri: monacoEditor.Uri;
|
|
61
|
+
getLanguageId: () => string;
|
|
62
|
+
getVersionId: () => number;
|
|
63
|
+
isDisposed: () => boolean;
|
|
64
|
+
getPositionAt: (offset: number) => monacoEditor.IPosition;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The slice of a standalone editor the install touches — structural, same rationale as
|
|
68
|
+
* {@link JsxTagClosingTargetModel}.
|
|
69
|
+
*/
|
|
70
|
+
interface JsxTagClosingTargetEditor {
|
|
71
|
+
getModel: () => JsxTagClosingTargetModel | null;
|
|
72
|
+
onDidChangeModelContent: (listener: (event: monacoEditor.editor.IModelContentChangedEvent) => void) => monacoEditor.IDisposable;
|
|
73
|
+
executeEdits: (source: string, edits: monacoEditor.editor.IIdentifiedSingleEditOperation[], endCursorState: monacoEditor.Selection[]) => boolean;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Watch `editorInstance` for tag-completing `">"` keystrokes and insert the matching JSX
|
|
77
|
+
* closing tag, keeping the cursor between the pair. Installed by `<MonacoEditor>` on every
|
|
78
|
+
* editor; the keystroke handler gates itself, so only TypeScript / JavaScript models whose
|
|
79
|
+
* path ends in `.tsx` / `.jsx` — the extensions that make the language service parse JSX —
|
|
80
|
+
* ever reach the worker. Listener lifetime follows the editor; dispose the returned
|
|
81
|
+
* subscription to detach earlier.
|
|
82
|
+
*/
|
|
83
|
+
declare function installJsxTagClosing(monaco: MonacoModule, editorInstance: JsxTagClosingTargetEditor): monacoEditor.IDisposable;
|
|
84
|
+
//#endregion
|
|
55
85
|
//#region src/keybindings.d.ts
|
|
56
86
|
/**
|
|
57
87
|
* A set of keybindings to swallow, plus the context key that scopes them to the editors that
|
|
@@ -65,7 +95,7 @@ interface SwallowedKeybindings {
|
|
|
65
95
|
* The keybindings `disableFind` / `disableCommandPalette` swallow: the find/replace widget's
|
|
66
96
|
* entries (Ctrl/Cmd+F to find; Ctrl+H and Cmd+Alt+F, the per-platform replace bindings) and
|
|
67
97
|
* the command palette's F1 — Monaco never binds Ctrl+Shift+P (a VS Code binding). Sourced
|
|
68
|
-
* from monaco-editor 0.
|
|
98
|
+
* from monaco-editor 0.56.0 (`findController.js`, `standaloneCommandsQuickAccess.js`).
|
|
69
99
|
*
|
|
70
100
|
* Both replace chords are swallowed on every platform rather than branching: `CtrlCmd` already
|
|
71
101
|
* resolves per platform, and the off-platform chord (Ctrl+Alt+F on Windows, Cmd+H on macOS, where
|
|
@@ -280,4 +310,4 @@ declare class StreamingEditorController {
|
|
|
280
310
|
discard(): void;
|
|
281
311
|
}
|
|
282
312
|
//#endregion
|
|
283
|
-
export { DEFAULT_MONACO_OPTIONS, type KeybindingTargetEditor, type MonacoBuiltInLanguage, type MonacoHostOptions, type MonacoLanguage, type MonacoModule, type MonacoUiLocale, type StreamTargetEditor, StreamingEditorController, type SwallowedKeybindings, disabledKeybindings, ensureMonacoHost, installDisabledKeybindings, modelValueMatches, monacoLanguages, reconcileModelValue, registerShikiHighlighting, replaceInEditor };
|
|
313
|
+
export { DEFAULT_MONACO_OPTIONS, type JsxTagClosingTargetEditor, type JsxTagClosingTargetModel, type KeybindingTargetEditor, type MonacoBuiltInLanguage, type MonacoHostOptions, type MonacoLanguage, type MonacoModule, type MonacoUiLocale, type StreamTargetEditor, StreamingEditorController, type SwallowedKeybindings, disabledKeybindings, ensureMonacoHost, installDisabledKeybindings, installJsxTagClosing, modelValueMatches, monacoLanguages, reconcileModelValue, registerShikiHighlighting, replaceInEditor };
|
package/dist/index.js
CHANGED
|
@@ -34,7 +34,7 @@ async function applyZhCnCatalog() {
|
|
|
34
34
|
const previousMessages = host._VSCODE_NLS_MESSAGES;
|
|
35
35
|
const previousOwner = registry.owner;
|
|
36
36
|
const owner = {};
|
|
37
|
-
const catalogModule = await import("monaco-editor/
|
|
37
|
+
const catalogModule = await import("monaco-editor/nls/lang/zh-cn.js");
|
|
38
38
|
let catalog = registry.catalogs.get(catalogModule);
|
|
39
39
|
if (!catalog) {
|
|
40
40
|
catalog = {
|
|
@@ -101,6 +101,37 @@ function ensureMonacoHost(options = {}) {
|
|
|
101
101
|
return attempt;
|
|
102
102
|
}
|
|
103
103
|
//#endregion
|
|
104
|
+
//#region src/jsx-tag-closing.ts
|
|
105
|
+
function installJsxTagClosing(monaco, editorInstance) {
|
|
106
|
+
return editorInstance.onDidChangeModelContent((event) => {
|
|
107
|
+
if (event.isFlush || event.isUndoing || event.isRedoing || event.changes.length !== 1) return;
|
|
108
|
+
const change = event.changes[0];
|
|
109
|
+
const model = editorInstance.getModel();
|
|
110
|
+
if (change?.text !== ">" || !model) return;
|
|
111
|
+
const { path } = model.uri;
|
|
112
|
+
if (!path.endsWith(".tsx") && !path.endsWith(".jsx")) return;
|
|
113
|
+
const language = model.getLanguageId();
|
|
114
|
+
if (language !== "typescript" && language !== "javascript") return;
|
|
115
|
+
const offset = change.rangeOffset + 1;
|
|
116
|
+
const version = model.getVersionId();
|
|
117
|
+
(async () => {
|
|
118
|
+
try {
|
|
119
|
+
const closing = await (await (language === "typescript" ? await monaco.typescript.getTypeScriptWorker() : await monaco.typescript.getJavaScriptWorker())(model.uri)).getJsxClosingTagAtPosition?.(model.uri.toString(), offset);
|
|
120
|
+
if (!closing || model.isDisposed() || model.getVersionId() !== version || editorInstance.getModel() !== model) return;
|
|
121
|
+
const position = model.getPositionAt(offset);
|
|
122
|
+
const cursor = new monaco.Selection(position.lineNumber, position.column, position.lineNumber, position.column);
|
|
123
|
+
editorInstance.executeEdits("jsx-tag-closing", [{
|
|
124
|
+
range: new monaco.Range(position.lineNumber, position.column, position.lineNumber, position.column),
|
|
125
|
+
text: closing.newText,
|
|
126
|
+
forceMoveMarkers: false
|
|
127
|
+
}], [cursor]);
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.error("inkstone: jsx closing-tag lookup failed", error);
|
|
130
|
+
}
|
|
131
|
+
})();
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
//#endregion
|
|
104
135
|
//#region src/keybindings.ts
|
|
105
136
|
const FIND_CONTEXT_KEY = "inkstoneFindDisabled";
|
|
106
137
|
const COMMAND_PALETTE_CONTEXT_KEY = "inkstoneCommandPaletteDisabled";
|
|
@@ -475,4 +506,4 @@ var StreamingEditorController = class {
|
|
|
475
506
|
}
|
|
476
507
|
};
|
|
477
508
|
//#endregion
|
|
478
|
-
export { DEFAULT_MONACO_OPTIONS, StreamingEditorController, disabledKeybindings, ensureMonacoHost, installDisabledKeybindings, modelValueMatches, monacoLanguages, reconcileModelValue, registerShikiHighlighting, replaceInEditor };
|
|
509
|
+
export { DEFAULT_MONACO_OPTIONS, StreamingEditorController, disabledKeybindings, ensureMonacoHost, installDisabledKeybindings, installJsxTagClosing, modelValueMatches, monacoLanguages, reconcileModelValue, registerShikiHighlighting, replaceInEditor };
|
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
// this package, so the host can reference it with `new URL("./workers/…", import.meta.url)`
|
|
3
3
|
// — the only worker form bundlers resolve inside a published dependency (Vite's `?worker`
|
|
4
4
|
// suffix fails to load from node_modules).
|
|
5
|
-
import "monaco-editor/
|
|
5
|
+
import "monaco-editor/editor/editor.worker.js";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Worker entry: the JSON language worker. See editor.worker.js for why this indirection exists.
|
|
2
|
-
import "monaco-editor/
|
|
2
|
+
import "monaco-editor/language/json/json.worker.js";
|
|
@@ -1,3 +1,25 @@
|
|
|
1
1
|
// Worker entry: the TypeScript worker, which also serves JavaScript IntelliSense.
|
|
2
2
|
// See editor.worker.js for why this indirection exists.
|
|
3
|
-
|
|
3
|
+
//
|
|
4
|
+
// Unlike its siblings this entry is not a bare re-export: it subclasses the stock worker to
|
|
5
|
+
// expose `getJsxClosingTagAtPosition` — the language-service call behind auto-closing JSX
|
|
6
|
+
// tags (`installJsxTagClosing` is the editor-side half) — which monaco's own worker never
|
|
7
|
+
// proxied. The host's worker proxy forwards any method name, so the subclass only has to
|
|
8
|
+
// exist here; nothing on the main thread changes.
|
|
9
|
+
import { initialize, TypeScriptWorker } from "monaco-editor/language/typescript/ts.worker.js";
|
|
10
|
+
|
|
11
|
+
class JsxTypeScriptWorker extends TypeScriptWorker {
|
|
12
|
+
getJsxClosingTagAtPosition(fileName, position) {
|
|
13
|
+
return this.getLanguageService().getJsxClosingTagAtPosition(fileName, position);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Monaco's worker handshake is two-staged (`internal/common/initialize.js`, 0.56): a first
|
|
18
|
+
// message merely triggers `initialize`, whose own handler then reads the *second* message as
|
|
19
|
+
// createData. The stock entry import above installed the stage-one handler for its own
|
|
20
|
+
// factory; this assignment must *replace* it with one building the subclass — calling
|
|
21
|
+
// `initialize` directly here instead would consume the trigger message as createData.
|
|
22
|
+
// eslint-disable-next-line unicorn/no-global-object-property-assignment, unicorn/prefer-add-event-listener -- the assignment must replace the stage-one handler; addEventListener would leave both firing
|
|
23
|
+
globalThis.onmessage = () => {
|
|
24
|
+
initialize((ctx, createData) => new JsxTypeScriptWorker(ctx, createData));
|
|
25
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coldsmirk/inkstone-monaco",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.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",
|
|
@@ -35,14 +35,14 @@
|
|
|
35
35
|
"dist"
|
|
36
36
|
],
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@shikijs/monaco": "^4.3
|
|
39
|
-
"@coldsmirk/inkstone-core": "^0.
|
|
38
|
+
"@shikijs/monaco": "^4.4.3",
|
|
39
|
+
"@coldsmirk/inkstone-core": "^0.16.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"monaco-editor": "^0.
|
|
42
|
+
"monaco-editor": "^0.56.0"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
|
-
"monaco-editor": "^0.
|
|
45
|
+
"monaco-editor": "^0.56.0"
|
|
46
46
|
},
|
|
47
47
|
"engines": {
|
|
48
48
|
"node": ">=24"
|