@coldsmirk/inkstone-monaco 0.17.0 → 0.18.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 -1
- package/dist/index.d.ts +62 -6
- package/dist/index.js +164 -4
- package/dist/vite.js +2 -2
- package/dist/workers/ts.worker.js +53 -4
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -10,7 +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.
|
|
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
15
|
- **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`.
|
|
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.
|
package/dist/index.d.ts
CHANGED
|
@@ -51,6 +51,47 @@ type MonacoModule = typeof monacoEditor;
|
|
|
51
51
|
*/
|
|
52
52
|
declare function ensureMonacoHost(options?: MonacoHostOptions): Promise<MonacoModule>;
|
|
53
53
|
//#endregion
|
|
54
|
+
//#region src/auto-imports.d.ts
|
|
55
|
+
/**
|
|
56
|
+
* Options for {@link installAutoImportCompletions}.
|
|
57
|
+
*/
|
|
58
|
+
interface AutoImportCompletionOptions {
|
|
59
|
+
/**
|
|
60
|
+
* The module specifiers admitted as auto-import sources for this model, or undefined /
|
|
61
|
+
* empty to offer none. Consulted on every completion request, so the answer may follow the
|
|
62
|
+
* app's active editing surface.
|
|
63
|
+
*/
|
|
64
|
+
admittedSources: (model: monacoEditor.editor.ITextModel) => readonly string[] | undefined;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Registers the auto-import completion provider on `monaco`'s `typescript` language id (the
|
|
68
|
+
* id also carries `.tsx` models — the model path decides JSX-ness). Additive beside the
|
|
69
|
+
* stock provider: entries are only unimported module exports from sources the app admits,
|
|
70
|
+
* labeled with their module, sorted after in-scope symbols by the language service's own
|
|
71
|
+
* `sortText`, and resolving one computes the import statement (merging into an existing
|
|
72
|
+
* import from the same module) as `additionalTextEdits`. A routed worker without the bundled
|
|
73
|
+
* subclass (a `getWorker` override) is detected on the first answer and the install disarms
|
|
74
|
+
* silently. Install once per app; repeated calls return the first install's disposable.
|
|
75
|
+
*/
|
|
76
|
+
declare function installAutoImportCompletions(monaco: MonacoModule, options: AutoImportCompletionOptions): monacoEditor.IDisposable;
|
|
77
|
+
//#endregion
|
|
78
|
+
//#region src/hover-dead-band.d.ts
|
|
79
|
+
/**
|
|
80
|
+
* The slice of a standalone editor the install touches — structural, so the install is
|
|
81
|
+
* testable without evaluating the monaco module (which jsdom cannot do).
|
|
82
|
+
*/
|
|
83
|
+
interface HoverDeadBandTargetEditor {
|
|
84
|
+
getContribution: (id: string) => unknown;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Keeps `editorInstance`'s content hover reachable by a slowly moving pointer: overrides the
|
|
88
|
+
* controller's on-the-widget test so the widget's pointer-active frame counts as the widget
|
|
89
|
+
* (see the file comment for the dead band this compensates). Installed by `<MonacoEditor>`
|
|
90
|
+
* on every editor; a monaco whose hover controller no longer matches the pinned shape is
|
|
91
|
+
* left untouched. Dispose to restore the stock test.
|
|
92
|
+
*/
|
|
93
|
+
declare function installHoverDeadBandFix(editorInstance: HoverDeadBandTargetEditor): monacoEditor.IDisposable;
|
|
94
|
+
//#endregion
|
|
54
95
|
//#region src/jsx-tag-closing.d.ts
|
|
55
96
|
/**
|
|
56
97
|
* The slice of a text model the install reads — a structural type, so the install is testable
|
|
@@ -63,22 +104,37 @@ interface JsxTagClosingTargetModel {
|
|
|
63
104
|
isDisposed: () => boolean;
|
|
64
105
|
getPositionAt: (offset: number) => monacoEditor.IPosition;
|
|
65
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* The caret shape the reply-time guard reads — the four corners of a (possibly collapsed)
|
|
109
|
+
* selection, as monaco's `Selection` carries them.
|
|
110
|
+
*/
|
|
111
|
+
interface JsxTagClosingCursor {
|
|
112
|
+
readonly selectionStartLineNumber: number;
|
|
113
|
+
readonly selectionStartColumn: number;
|
|
114
|
+
readonly positionLineNumber: number;
|
|
115
|
+
readonly positionColumn: number;
|
|
116
|
+
}
|
|
66
117
|
/**
|
|
67
118
|
* The slice of a standalone editor the install touches — structural, same rationale as
|
|
68
119
|
* {@link JsxTagClosingTargetModel}.
|
|
69
120
|
*/
|
|
70
121
|
interface JsxTagClosingTargetEditor {
|
|
71
122
|
getModel: () => JsxTagClosingTargetModel | null;
|
|
123
|
+
getOption: (id: monacoEditor.editor.EditorOption) => unknown;
|
|
124
|
+
getSelection: () => JsxTagClosingCursor | null;
|
|
125
|
+
pushUndoStop: () => boolean;
|
|
72
126
|
onDidChangeModelContent: (listener: (event: monacoEditor.editor.IModelContentChangedEvent) => void) => monacoEditor.IDisposable;
|
|
73
127
|
executeEdits: (source: string, edits: monacoEditor.editor.IIdentifiedSingleEditOperation[], endCursorState: monacoEditor.Selection[]) => boolean;
|
|
74
128
|
}
|
|
75
129
|
/**
|
|
76
130
|
* Watch `editorInstance` for tag-completing `">"` keystrokes and insert the matching JSX
|
|
77
|
-
* closing tag, keeping the cursor between the pair. Installed by
|
|
78
|
-
* editor; the keystroke handler gates itself, so only
|
|
79
|
-
* path ends in `.tsx` / `.jsx` —
|
|
80
|
-
*
|
|
81
|
-
*
|
|
131
|
+
* closing tag as its own undo step, keeping the cursor between the pair. Installed by
|
|
132
|
+
* `<MonacoEditor>` on every editor; the keystroke handler gates itself, so only a bare ">"
|
|
133
|
+
* typed into a writable TypeScript / JavaScript model whose path ends in `.tsx` / `.jsx` —
|
|
134
|
+
* the extensions that make the language service parse JSX — ever reaches the worker. A
|
|
135
|
+
* routed worker without the lookup (a `getWorker` override bypassing the bundled entry) is
|
|
136
|
+
* detected on its first answer and the install disarms silently. Listener lifetime follows
|
|
137
|
+
* the editor; dispose the returned subscription to detach earlier.
|
|
82
138
|
*/
|
|
83
139
|
declare function installJsxTagClosing(monaco: MonacoModule, editorInstance: JsxTagClosingTargetEditor): monacoEditor.IDisposable;
|
|
84
140
|
//#endregion
|
|
@@ -310,4 +366,4 @@ declare class StreamingEditorController {
|
|
|
310
366
|
discard(): void;
|
|
311
367
|
}
|
|
312
368
|
//#endregion
|
|
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 };
|
|
369
|
+
export { type AutoImportCompletionOptions, DEFAULT_MONACO_OPTIONS, type HoverDeadBandTargetEditor, type JsxTagClosingTargetEditor, type JsxTagClosingTargetModel, type KeybindingTargetEditor, type MonacoBuiltInLanguage, type MonacoHostOptions, type MonacoLanguage, type MonacoModule, type MonacoUiLocale, type StreamTargetEditor, StreamingEditorController, type SwallowedKeybindings, disabledKeybindings, ensureMonacoHost, installAutoImportCompletions, installDisabledKeybindings, installHoverDeadBandFix, installJsxTagClosing, modelValueMatches, monacoLanguages, reconcileModelValue, registerShikiHighlighting, replaceInEditor };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,128 @@
|
|
|
1
1
|
import { CODE_FONT_FAMILY } from "@coldsmirk/inkstone-core";
|
|
2
2
|
import { shikiToMonaco } from "@shikijs/monaco";
|
|
3
|
+
//#region src/auto-imports.ts
|
|
4
|
+
const MISSING_LOOKUP_REJECTION$1 = "Missing requestHandler or method: getAutoImportCompletionsAtPosition";
|
|
5
|
+
const MODE_BOOTING_REJECTIONS$1 = /* @__PURE__ */ new Set(["TypeScript not registered!", "JavaScript not registered!"]);
|
|
6
|
+
const ENTRY_KINDS = {
|
|
7
|
+
function: "Function",
|
|
8
|
+
method: "Function",
|
|
9
|
+
class: "Class",
|
|
10
|
+
interface: "Interface",
|
|
11
|
+
module: "Module",
|
|
12
|
+
type: "Variable",
|
|
13
|
+
alias: "Variable"
|
|
14
|
+
};
|
|
15
|
+
function itemKind(monaco, kind) {
|
|
16
|
+
return monaco.languages.CompletionItemKind[ENTRY_KINDS[kind] ?? "Variable"];
|
|
17
|
+
}
|
|
18
|
+
function partsText(parts) {
|
|
19
|
+
return (parts ?? []).map((part) => part.text).join("");
|
|
20
|
+
}
|
|
21
|
+
const LEDGER_KEY = Symbol.for("coldsmirk.inkstone.autoImportCompletions");
|
|
22
|
+
function ledgerOf() {
|
|
23
|
+
const holder = globalThis;
|
|
24
|
+
let ledger = holder[LEDGER_KEY];
|
|
25
|
+
if (!ledger) {
|
|
26
|
+
ledger = /* @__PURE__ */ new WeakMap();
|
|
27
|
+
holder[LEDGER_KEY] = ledger;
|
|
28
|
+
}
|
|
29
|
+
return ledger;
|
|
30
|
+
}
|
|
31
|
+
function classify(error) {
|
|
32
|
+
const message = error instanceof Error ? error.message : typeof error === "string" ? error : "";
|
|
33
|
+
if (message === MISSING_LOOKUP_REJECTION$1) return "disarm";
|
|
34
|
+
return MODE_BOOTING_REJECTIONS$1.has(message) ? "booting" : "failure";
|
|
35
|
+
}
|
|
36
|
+
function installAutoImportCompletions(monaco, options) {
|
|
37
|
+
const ledger = ledgerOf();
|
|
38
|
+
const installed = ledger.get(monaco);
|
|
39
|
+
if (installed) return installed;
|
|
40
|
+
let lookupUnsupported = false;
|
|
41
|
+
const disposable = monaco.languages.registerCompletionItemProvider("typescript", {
|
|
42
|
+
async provideCompletionItems(model, position) {
|
|
43
|
+
const sources = options.admittedSources(model);
|
|
44
|
+
if (lookupUnsupported || !sources || sources.length === 0) return { suggestions: [] };
|
|
45
|
+
const wordInfo = model.getWordUntilPosition(position);
|
|
46
|
+
const offset = model.getOffsetAt(position);
|
|
47
|
+
try {
|
|
48
|
+
const workerFor = await monaco.typescript.getTypeScriptWorker();
|
|
49
|
+
if (model.isDisposed()) return { suggestions: [] };
|
|
50
|
+
const info = await (await workerFor(model.uri)).getAutoImportCompletionsAtPosition(model.uri.toString(), offset, sources);
|
|
51
|
+
if (!info || model.isDisposed()) return { suggestions: [] };
|
|
52
|
+
const range = {
|
|
53
|
+
startLineNumber: position.lineNumber,
|
|
54
|
+
startColumn: wordInfo.startColumn,
|
|
55
|
+
endLineNumber: position.lineNumber,
|
|
56
|
+
endColumn: wordInfo.endColumn
|
|
57
|
+
};
|
|
58
|
+
return {
|
|
59
|
+
suggestions: info.entries.map((entry) => {
|
|
60
|
+
return {
|
|
61
|
+
label: {
|
|
62
|
+
label: entry.name,
|
|
63
|
+
description: entry.source
|
|
64
|
+
},
|
|
65
|
+
kind: itemKind(monaco, entry.kind),
|
|
66
|
+
insertText: entry.insertText ?? entry.name,
|
|
67
|
+
filterText: entry.name,
|
|
68
|
+
sortText: entry.sortText,
|
|
69
|
+
range,
|
|
70
|
+
uri: model.uri,
|
|
71
|
+
offset,
|
|
72
|
+
entry
|
|
73
|
+
};
|
|
74
|
+
}),
|
|
75
|
+
incomplete: info.isIncomplete
|
|
76
|
+
};
|
|
77
|
+
} catch (error) {
|
|
78
|
+
const verdict = classify(error);
|
|
79
|
+
if (verdict === "disarm") lookupUnsupported = true;
|
|
80
|
+
else if (verdict === "failure") console.error("inkstone: auto-import completion lookup failed", error);
|
|
81
|
+
return { suggestions: [] };
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
async resolveCompletionItem(item) {
|
|
85
|
+
const myItem = item;
|
|
86
|
+
if (!myItem.entry) return item;
|
|
87
|
+
try {
|
|
88
|
+
const details = await (await (await monaco.typescript.getTypeScriptWorker())(myItem.uri)).getAutoImportCompletionEntryDetails(myItem.uri.toString(), myItem.offset, myItem.entry.name, myItem.entry.source, myItem.entry.data);
|
|
89
|
+
const model = monaco.editor.getModel(myItem.uri);
|
|
90
|
+
if (!details || !model || model.isDisposed()) return item;
|
|
91
|
+
const fileName = myItem.uri.toString();
|
|
92
|
+
const additionalTextEdits = (details.codeActions ?? []).flatMap((action) => action.changes).filter((change) => change.fileName === fileName).flatMap((change) => change.textChanges).map((textChange) => {
|
|
93
|
+
const start = model.getPositionAt(textChange.span.start);
|
|
94
|
+
const end = model.getPositionAt(textChange.span.start + textChange.span.length);
|
|
95
|
+
return {
|
|
96
|
+
range: {
|
|
97
|
+
startLineNumber: start.lineNumber,
|
|
98
|
+
startColumn: start.column,
|
|
99
|
+
endLineNumber: end.lineNumber,
|
|
100
|
+
endColumn: end.column
|
|
101
|
+
},
|
|
102
|
+
text: textChange.newText
|
|
103
|
+
};
|
|
104
|
+
});
|
|
105
|
+
const documentation = partsText(details.documentation);
|
|
106
|
+
return {
|
|
107
|
+
...myItem,
|
|
108
|
+
additionalTextEdits,
|
|
109
|
+
detail: details.codeActions?.[0]?.description ?? partsText(details.displayParts),
|
|
110
|
+
...documentation && { documentation: { value: documentation } }
|
|
111
|
+
};
|
|
112
|
+
} catch (error) {
|
|
113
|
+
if (classify(error) === "failure") console.error("inkstone: auto-import details lookup failed", error);
|
|
114
|
+
return item;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
const install = { dispose: () => {
|
|
119
|
+
ledger.delete(monaco);
|
|
120
|
+
disposable.dispose();
|
|
121
|
+
} };
|
|
122
|
+
ledger.set(monaco, install);
|
|
123
|
+
return install;
|
|
124
|
+
}
|
|
125
|
+
//#endregion
|
|
3
126
|
//#region src/geometry-fixes.ts
|
|
4
127
|
const HOVER_GEOMETRY_STYLE_SELECTOR = "style[data-inkstone=\"monaco-hover-geometry\"]";
|
|
5
128
|
function installHoverGeometryFix() {
|
|
@@ -101,13 +224,40 @@ function ensureMonacoHost(options = {}) {
|
|
|
101
224
|
return attempt;
|
|
102
225
|
}
|
|
103
226
|
//#endregion
|
|
227
|
+
//#region src/hover-dead-band.ts
|
|
228
|
+
const CONTENT_HOVER_CONTRIBUTION = "editor.contrib.contentHover";
|
|
229
|
+
const TOLERANCE = 4;
|
|
230
|
+
function isContentHoverController(candidate) {
|
|
231
|
+
return typeof candidate === "object" && candidate !== null && typeof candidate._isMouseOnContentHoverWidget === "function";
|
|
232
|
+
}
|
|
233
|
+
function installHoverDeadBandFix(editorInstance) {
|
|
234
|
+
const controller = editorInstance.getContribution(CONTENT_HOVER_CONTRIBUTION);
|
|
235
|
+
if (!isContentHoverController(controller) || Object.hasOwn(controller, "_isMouseOnContentHoverWidget")) return { dispose: () => {} };
|
|
236
|
+
controller._isMouseOnContentHoverWidget = (mouseEvent) => {
|
|
237
|
+
const dom = controller._contentWidget?.getDomNode();
|
|
238
|
+
if (!dom) return false;
|
|
239
|
+
const rect = dom.getBoundingClientRect();
|
|
240
|
+
const view = dom.ownerDocument.defaultView;
|
|
241
|
+
const x = mouseEvent.event.posx - (view?.scrollX ?? 0);
|
|
242
|
+
const y = mouseEvent.event.posy - (view?.scrollY ?? 0);
|
|
243
|
+
return rect.width > 0 && rect.height > 0 && x >= rect.left - TOLERANCE && x <= rect.right + TOLERANCE && y >= rect.top - TOLERANCE && y <= rect.bottom + TOLERANCE;
|
|
244
|
+
};
|
|
245
|
+
return { dispose: () => {
|
|
246
|
+
delete controller._isMouseOnContentHoverWidget;
|
|
247
|
+
} };
|
|
248
|
+
}
|
|
249
|
+
//#endregion
|
|
104
250
|
//#region src/jsx-tag-closing.ts
|
|
251
|
+
const MISSING_LOOKUP_REJECTION = "Missing requestHandler or method: getJsxClosingTagAtPosition";
|
|
252
|
+
const MODE_BOOTING_REJECTIONS = /* @__PURE__ */ new Set(["TypeScript not registered!", "JavaScript not registered!"]);
|
|
105
253
|
function installJsxTagClosing(monaco, editorInstance) {
|
|
254
|
+
let lookupUnsupported = false;
|
|
106
255
|
return editorInstance.onDidChangeModelContent((event) => {
|
|
107
|
-
if (event.isFlush || event.isUndoing || event.isRedoing || event.changes.length !== 1) return;
|
|
256
|
+
if (lookupUnsupported || event.isFlush || event.isUndoing || event.isRedoing || event.changes.length !== 1) return;
|
|
108
257
|
const change = event.changes[0];
|
|
109
258
|
const model = editorInstance.getModel();
|
|
110
|
-
if (change?.text !== ">" || !model) return;
|
|
259
|
+
if (change?.text !== ">" || change.rangeLength !== 0 || !model) return;
|
|
260
|
+
if (editorInstance.getOption(monaco.editor.EditorOption.readOnly) === true) return;
|
|
111
261
|
const { path } = model.uri;
|
|
112
262
|
if (!path.endsWith(".tsx") && !path.endsWith(".jsx")) return;
|
|
113
263
|
const language = model.getLanguageId();
|
|
@@ -116,16 +266,26 @@ function installJsxTagClosing(monaco, editorInstance) {
|
|
|
116
266
|
const version = model.getVersionId();
|
|
117
267
|
(async () => {
|
|
118
268
|
try {
|
|
119
|
-
const closing = await (await (language === "typescript" ? await monaco.typescript.getTypeScriptWorker() : await monaco.typescript.getJavaScriptWorker())(model.uri)).getJsxClosingTagAtPosition
|
|
269
|
+
const closing = await (await (language === "typescript" ? await monaco.typescript.getTypeScriptWorker() : await monaco.typescript.getJavaScriptWorker())(model.uri)).getJsxClosingTagAtPosition(model.uri.toString(), offset);
|
|
120
270
|
if (!closing || model.isDisposed() || model.getVersionId() !== version || editorInstance.getModel() !== model) return;
|
|
121
271
|
const position = model.getPositionAt(offset);
|
|
272
|
+
const selection = editorInstance.getSelection();
|
|
273
|
+
if (!selection || selection.selectionStartLineNumber !== position.lineNumber || selection.selectionStartColumn !== position.column || selection.positionLineNumber !== position.lineNumber || selection.positionColumn !== position.column) return;
|
|
122
274
|
const cursor = new monaco.Selection(position.lineNumber, position.column, position.lineNumber, position.column);
|
|
275
|
+
editorInstance.pushUndoStop();
|
|
123
276
|
editorInstance.executeEdits("jsx-tag-closing", [{
|
|
124
277
|
range: new monaco.Range(position.lineNumber, position.column, position.lineNumber, position.column),
|
|
125
278
|
text: closing.newText,
|
|
126
279
|
forceMoveMarkers: false
|
|
127
280
|
}], [cursor]);
|
|
281
|
+
editorInstance.pushUndoStop();
|
|
128
282
|
} catch (error) {
|
|
283
|
+
const message = error instanceof Error ? error.message : typeof error === "string" ? error : "";
|
|
284
|
+
if (message === MISSING_LOOKUP_REJECTION) {
|
|
285
|
+
lookupUnsupported = true;
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
if (MODE_BOOTING_REJECTIONS.has(message)) return;
|
|
129
289
|
console.error("inkstone: jsx closing-tag lookup failed", error);
|
|
130
290
|
}
|
|
131
291
|
})();
|
|
@@ -506,4 +666,4 @@ var StreamingEditorController = class {
|
|
|
506
666
|
}
|
|
507
667
|
};
|
|
508
668
|
//#endregion
|
|
509
|
-
export { DEFAULT_MONACO_OPTIONS, StreamingEditorController, disabledKeybindings, ensureMonacoHost, installDisabledKeybindings, installJsxTagClosing, modelValueMatches, monacoLanguages, reconcileModelValue, registerShikiHighlighting, replaceInEditor };
|
|
669
|
+
export { DEFAULT_MONACO_OPTIONS, StreamingEditorController, disabledKeybindings, ensureMonacoHost, installAutoImportCompletions, installDisabledKeybindings, installHoverDeadBandFix, installJsxTagClosing, modelValueMatches, monacoLanguages, reconcileModelValue, registerShikiHighlighting, replaceInEditor };
|
package/dist/vite.js
CHANGED
|
@@ -4,7 +4,7 @@ function dropMonacoWorkerFallbacks() {
|
|
|
4
4
|
return {
|
|
5
5
|
name: "inkstone:drop-monaco-worker-fallbacks",
|
|
6
6
|
transform(code, id) {
|
|
7
|
-
if (!id.includes("monaco-editor") || !id.endsWith("workerManager.js")) return;
|
|
7
|
+
if (!id.includes("/node_modules/monaco-editor/") || !id.endsWith("workerManager.js")) return;
|
|
8
8
|
let matched = false;
|
|
9
9
|
const transformed = code.replaceAll(WORKER_FALLBACK, (_expression, file) => {
|
|
10
10
|
matched = true;
|
|
@@ -16,7 +16,7 @@ function dropMonacoWorkerFallbacks() {
|
|
|
16
16
|
}
|
|
17
17
|
return {
|
|
18
18
|
code: transformed,
|
|
19
|
-
map:
|
|
19
|
+
map: { mappings: "" }
|
|
20
20
|
};
|
|
21
21
|
}
|
|
22
22
|
};
|
|
@@ -2,16 +2,65 @@
|
|
|
2
2
|
// See editor.worker.js for why this indirection exists.
|
|
3
3
|
//
|
|
4
4
|
// Unlike its siblings this entry is not a bare re-export: it subclasses the stock worker to
|
|
5
|
-
// expose
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
5
|
+
// expose language-service calls monaco's own worker never proxied — the host's worker proxy
|
|
6
|
+
// forwards any method name, so the subclass only has to exist here; nothing on the main
|
|
7
|
+
// thread changes for it to be callable:
|
|
8
|
+
// - `getJsxClosingTagAtPosition`, behind auto-closing JSX tags (`installJsxTagClosing` is
|
|
9
|
+
// the editor-side half);
|
|
10
|
+
// - the auto-import completion pair, behind `installAutoImportCompletions`: the stock worker
|
|
11
|
+
// calls `getCompletionsAtPosition` without preferences, so the service never surfaces
|
|
12
|
+
// unimported module exports, and its details call drops `source`/`data`, so the import
|
|
13
|
+
// text edit (`codeActions`) can never be requested. These two pass the module-export
|
|
14
|
+
// preferences through and return the entries verbatim.
|
|
9
15
|
import { initialize, TypeScriptWorker } from "monaco-editor/language/typescript/ts.worker.js";
|
|
10
16
|
|
|
17
|
+
// The preferences that make the language service surface unimported module exports and, on
|
|
18
|
+
// the details call, compute their import edits. `allowIncompleteCompletions` lets it bound a
|
|
19
|
+
// broad enumeration by the typed prefix and say so (`isIncomplete` → monaco re-queries).
|
|
20
|
+
const AUTO_IMPORT_PREFERENCES = {
|
|
21
|
+
includeCompletionsForModuleExports: true,
|
|
22
|
+
includeCompletionsWithInsertText: true,
|
|
23
|
+
allowIncompleteCompletions: true
|
|
24
|
+
};
|
|
25
|
+
|
|
11
26
|
class JsxTypeScriptWorker extends TypeScriptWorker {
|
|
12
27
|
getJsxClosingTagAtPosition(fileName, position) {
|
|
13
28
|
return this.getLanguageService().getJsxClosingTagAtPosition(fileName, position);
|
|
14
29
|
}
|
|
30
|
+
|
|
31
|
+
getAutoImportCompletionsAtPosition(fileName, position, admittedSources) {
|
|
32
|
+
const info = this.getLanguageService().getCompletionsAtPosition(fileName, position, AUTO_IMPORT_PREFERENCES);
|
|
33
|
+
|
|
34
|
+
if (!info) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Only entries importable from an admitted specifier: the language service also offers
|
|
39
|
+
// every other module in the program (deep package paths included), which the editor-side
|
|
40
|
+
// policy has ruled out — filtering here keeps them off the wire entirely.
|
|
41
|
+
const admitted = new Set(admittedSources);
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
isIncomplete: info.isIncomplete === true,
|
|
45
|
+
entries: info.entries.filter(entry => entry.source !== undefined && admitted.has(entry.source))
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
getAutoImportCompletionEntryDetails(fileName, position, entryName, source, data) {
|
|
50
|
+
// `data` must round-trip from the completion entry: without it the service cannot compute
|
|
51
|
+
// the import edit for a node_modules package (an ambient module happens to survive, but
|
|
52
|
+
// the contract is the pair). `semicolons: "insert"` makes the generated statement end in
|
|
53
|
+
// one, matching the house style of every template the editors seed.
|
|
54
|
+
return this.getLanguageService().getCompletionEntryDetails(
|
|
55
|
+
fileName,
|
|
56
|
+
position,
|
|
57
|
+
entryName,
|
|
58
|
+
{ semicolons: "insert" },
|
|
59
|
+
source,
|
|
60
|
+
AUTO_IMPORT_PREFERENCES,
|
|
61
|
+
data
|
|
62
|
+
);
|
|
63
|
+
}
|
|
15
64
|
}
|
|
16
65
|
|
|
17
66
|
// Monaco's worker handshake is two-staged (`internal/common/initialize.js`, 0.56): a first
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coldsmirk/inkstone-monaco",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.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,7 +40,7 @@
|
|
|
40
40
|
],
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@shikijs/monaco": "^4.4.3",
|
|
43
|
-
"@coldsmirk/inkstone-core": "^0.
|
|
43
|
+
"@coldsmirk/inkstone-core": "^0.18.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"monaco-editor": "^0.56.0",
|