vue-use-monaco
Version:
A Vue library for integrating Monaco Editor with Shiki syntax highlighting, supporting real-time updates.
1,359 lines (1,345 loc) • 59.4 kB
JavaScript
import * as monaco$3 from "monaco-editor";
import * as monaco$2 from "monaco-editor";
import * as monaco$1 from "monaco-editor";
import * as monaco from "monaco-editor";
import { computed, onUnmounted, watch } from "vue";
import { useDark } from "@vueuse/core";
import { shikiToMonaco } from "@shikijs/monaco";
import { createHighlighter } from "shiki";
//#region src/code.detect.ts
/**
* Language detection definitions
*/
const languages = [
[
"bash",
[/#!(\/usr)?\/bin\/bash/g, 500],
[/\b(if|elif|then|fi|echo)\b|\$/g, 10]
],
[
"html",
[/<\/?[a-z-][^\n>]*>/g, 10],
[/^\s+<!DOCTYPE\s+html/g, 500]
],
["http", [/^(GET|HEAD|POST|PUT|DELETE|PATCH|HTTP)\b/g, 500]],
["js", [/\b(console|await|async|function|export|import|this|class|for|let|const|map|join|require)\b/g, 10]],
["ts", [/\b(console|await|async|function|export|import|this|class|for|let|const|map|join|require|implements|interface|namespace)\b/g, 10]],
["py", [/\b(def|print|class|and|or|lambda)\b/g, 10]],
["sql", [/\b(SELECT|INSERT|FROM)\b/g, 50]],
[
"pl",
[/#!(\/usr)?\/bin\/perl/g, 500],
[/\b(use|print)\b|\$/g, 10]
],
["lua", [/#!(\/usr)?\/bin\/lua/g, 500]],
["make", [/\b(ifneq|endif|if|elif|then|fi|echo|.PHONY|^[a-z]+ ?:$)\b|\$/gm, 10]],
["uri", [/https?:|mailto:|tel:|ftp:/g, 30]],
["css", [/^(@import|@page|@media|(\.|#)[a-z]+)/gm, 20]],
[
"diff",
[/^[+><-]/gm, 10],
[/^@@[-+,0-9 ]+@@/gm, 25]
],
[
"md",
[/^(>|\t\*|\t\d+.|#{1,6} |-\s+|\*\s+)/gm, 25],
[/\[.*\](.*)/g, 10]
],
["docker", [/^(FROM|ENTRYPOINT|RUN)/gm, 500]],
[
"xml",
[/<\/?[a-z-][^\n>]*>/g, 10],
[/^<\?xml/g, 500]
],
["c", [/#include\b|\bprintf\s+\(/g, 100]],
["rs", [/^\s+(use|fn|mut|match)\b/gm, 100]],
["go", [/\b(func|fmt|package)\b/g, 100]],
["java", [/^import\s+java/gm, 500]],
["asm", [/^(section|global main|extern|\t(call|mov|ret))/gm, 100]],
["css", [/^(@import|@page|@media|(\.|#)[a-z]+)/gm, 20]],
["json", [/\b(true|false|null|\{\})\b|"[^"]+":/g, 10]],
["yaml", [/^(\s+)?[a-z][a-z0-9]*:/gim, 10]],
[
"toml",
[/^\s*\[.*\]\s*$/gm, 100],
[/^\s*[\w-]+ *= */gm, 20]
],
[
"mermaid",
[/^(graph|flowchart|sequenceDiagram|classDiagram|stateDiagram|erDiagram|gantt|pie|mindmap)/gm, 500],
[/\b(-->|--o|--x|=>|\[\]|[{}])\b/g, 10]
]
];
/**
* Try to find the language the given code belongs to
*
* @param {string} code The code to analyze
* @param {LanguageDefinition[]} [additionalLanguages] Additional language definitions to supplement the built-in ones
* @returns {CodeLanguage} The detected language of the code
*/
function detectLanguage(code, additionalLanguages) {
var _allLanguages$map$fil;
const allLanguages = additionalLanguages ? [...languages, ...additionalLanguages] : languages;
return ((_allLanguages$map$fil = allLanguages.map(([lang, ...features]) => [lang, features.reduce((acc, [match, score]) => acc + [...code.matchAll(match)].length * score, 0)]).filter(([, score]) => score > 20).sort((a, b) => b[1] - a[1])[0]) === null || _allLanguages$map$fil === void 0 ? void 0 : _allLanguages$map$fil[0]) || "plain";
}
function processedLanguage(language) {
if (/^(?:shellscript|bash|sh|shell|zsh)/i.test(language)) return "shell";
if (/^(?:powershell|ps1?)/i.test(language)) return "powershell";
return language.split(":")[0];
}
/**
* 使用示例:
*
* // 基本用法
* const language1 = detectLanguage('console.log("hello")') // 'js'
*
* // 使用自定义语言检测规则
* const customLanguages: LanguageDefinition[] = [
* ['vue', [/<template>/g, 100], [/<script>/g, 50], [/<style>/g, 50]],
* ['kotlin', [/\b(fun|class|val|var)\b/g, 20]]
* ]
*
* const language2 = detectLanguage(`
* <template>
* <div>Hello Vue</div>
* </template>
* `, customLanguages) // 'vue'
*
* const language3 = detectLanguage(`
* fun main() {
* val name = "Kotlin"
* println("Hello $name")
* }
* `, customLanguages) // 'kotlin'
*/
//#endregion
//#region src/constant.ts
const defaultLanguages = [
"jsx",
"tsx",
"vue",
"csharp",
"python",
"java",
"c",
"cpp",
"rust",
"go",
"powershell",
"sql",
"json",
"html",
"javascript",
"typescript",
"css",
"markdown",
"xml",
"yaml",
"toml",
"dockerfile",
"kotlin",
"objective-c",
"objective-cpp",
"php",
"ruby",
"scala",
"svelte",
"swift",
"erlang",
"angular-html",
"angular-ts",
"dart",
"lua",
"mermaid",
"cmake",
"nginx"
];
const defaultThemes = ["vitesse-dark", "vitesse-light"];
const defaultScrollbar = {
verticalScrollbarSize: 8,
horizontalScrollbarSize: 8,
handleMouseWheel: true,
alwaysConsumeMouseWheel: false
};
const padding = 16;
//#endregion
//#region src/minimalEdit.ts
/**
* Compute the minimal middle replacement between two UTF-16 strings.
* Returns null when prev === next (no edits required).
*/
function computeMinimalEdit(prev, next) {
if (prev === next) return null;
let start = 0;
const minLen = Math.min(prev.length, next.length);
while (start < minLen && prev.charCodeAt(start) === next.charCodeAt(start)) start++;
let endPrev = prev.length - 1;
let endNext = next.length - 1;
while (endPrev >= start && endNext >= start && prev.charCodeAt(endPrev) === next.charCodeAt(endNext)) {
endPrev--;
endNext--;
}
return {
start,
endPrevIncl: endPrev,
endNextIncl: endNext,
replaceText: next.slice(start, endNext + 1)
};
}
//#endregion
//#region src/utils/height.ts
function createHeightManager(container, computeNext) {
let raf = null;
let lastApplied = -1;
let suppressed = false;
function apply() {
const next = computeNext();
if (next === lastApplied) return;
suppressed = true;
container.style.height = `${next}px`;
lastApplied = next;
queueMicrotask(() => {
suppressed = false;
});
}
function update() {
if (raf != null) return;
raf = requestAnimationFrame(() => {
raf = null;
apply();
});
}
function dispose() {
if (raf != null) {
try {
cancelAnimationFrame(raf);
} catch {}
raf = null;
}
}
function isSuppressed() {
return suppressed;
}
function getLastApplied() {
return lastApplied;
}
return {
update,
dispose,
isSuppressed,
getLastApplied
};
}
//#endregion
//#region src/utils/raf.ts
function createRafScheduler(timeSource) {
const ts = timeSource ?? {
requestAnimationFrame: (cb) => requestAnimationFrame(cb),
cancelAnimationFrame: (id) => cancelAnimationFrame(id)
};
const ids = {};
function schedule(kind, cb) {
const existing = ids[kind];
if (existing != null) try {
ts.cancelAnimationFrame(existing);
} catch {}
ids[kind] = ts.requestAnimationFrame((t) => {
ids[kind] = null;
cb(t);
});
}
function cancel(kind) {
const id = ids[kind];
if (id != null) {
try {
ts.cancelAnimationFrame(id);
} catch {}
ids[kind] = null;
}
}
return {
schedule,
cancel
};
}
//#endregion
//#region src/utils/scroll.ts
function createScrollWatcherForEditor(ed, opts) {
try {
var _ed$getScrollTop, _ed$onDidScrollChange;
const initial = ((_ed$getScrollTop = ed.getScrollTop) === null || _ed$getScrollTop === void 0 ? void 0 : _ed$getScrollTop.call(ed)) ?? 0;
opts.setLast(initial);
const disp = ((_ed$onDidScrollChange = ed.onDidScrollChange) === null || _ed$onDidScrollChange === void 0 ? void 0 : _ed$onDidScrollChange.call(ed, (e) => {
var _ed$getScrollTop2;
const currentTop = e && typeof e.scrollTop === "number" ? e.scrollTop : ((_ed$getScrollTop2 = ed.getScrollTop) === null || _ed$getScrollTop2 === void 0 ? void 0 : _ed$getScrollTop2.call(ed)) ?? 0;
const delta = currentTop - opts.getLast();
opts.setLast(currentTop);
if (delta < 0) {
opts.onPause();
return;
}
opts.onMaybeResume();
})) ?? null;
return disp;
} catch {
return null;
}
}
//#endregion
//#region src/core/DiffEditorManager.ts
var DiffEditorManager = class {
diffEditorView = null;
originalModel = null;
modifiedModel = null;
lastContainer = null;
lastKnownOriginalCode = null;
lastKnownModifiedCode = null;
pendingDiffUpdate = null;
shouldAutoScrollDiff = true;
diffScrollWatcher = null;
lastScrollTopDiff = 0;
_hasScrollBar = false;
cachedScrollHeightDiff = null;
cachedLineHeightDiff = null;
cachedComputedHeightDiff = null;
appendBufferDiff = [];
appendBufferDiffScheduled = false;
rafScheduler = createRafScheduler();
diffHeightManager = null;
constructor(options, maxHeightValue, maxHeightCSS, autoScrollOnUpdate, autoScrollInitial, autoScrollThresholdPx, autoScrollThresholdLines, diffAutoScroll) {
this.options = options;
this.maxHeightValue = maxHeightValue;
this.maxHeightCSS = maxHeightCSS;
this.autoScrollOnUpdate = autoScrollOnUpdate;
this.autoScrollInitial = autoScrollInitial;
this.autoScrollThresholdPx = autoScrollThresholdPx;
this.autoScrollThresholdLines = autoScrollThresholdLines;
this.diffAutoScroll = diffAutoScroll;
}
computedHeight() {
var _originalEditor$getMo, _modifiedEditor$getMo;
if (!this.diffEditorView) return Math.min(1 * 18 + padding, this.maxHeightValue);
const modifiedEditor = this.diffEditorView.getModifiedEditor();
const originalEditor = this.diffEditorView.getOriginalEditor();
const lineHeight = modifiedEditor.getOption(monaco$3.editor.EditorOption.lineHeight);
const oCount = ((_originalEditor$getMo = originalEditor.getModel()) === null || _originalEditor$getMo === void 0 ? void 0 : _originalEditor$getMo.getLineCount()) ?? 1;
const mCount = ((_modifiedEditor$getMo = modifiedEditor.getModel()) === null || _modifiedEditor$getMo === void 0 ? void 0 : _modifiedEditor$getMo.getLineCount()) ?? 1;
const lineCount = Math.max(oCount, mCount);
return Math.min(lineCount * lineHeight + padding, this.maxHeightValue);
}
hasVerticalScrollbarModified() {
try {
if (!this.diffEditorView) return false;
if (this._hasScrollBar) return true;
const me = this.diffEditorView.getModifiedEditor();
const ch = this.cachedComputedHeightDiff ?? this.computedHeight();
return this._hasScrollBar = me.getScrollHeight() > ch + padding / 2;
} catch {
return false;
}
}
userIsNearBottomDiff() {
try {
var _me$getLayoutInfo, _me$getScrollTop, _me$getScrollHeight;
if (!this.diffEditorView) return true;
const me = this.diffEditorView.getModifiedEditor();
const li = (_me$getLayoutInfo = me.getLayoutInfo) === null || _me$getLayoutInfo === void 0 ? void 0 : _me$getLayoutInfo.call(me);
if (!li) return true;
const lineHeight = this.cachedLineHeightDiff ?? me.getOption(monaco$3.editor.EditorOption.lineHeight);
const lineThreshold = (this.autoScrollThresholdLines ?? 0) * lineHeight;
const threshold = Math.max(lineThreshold || 0, this.autoScrollThresholdPx || 0);
const st = ((_me$getScrollTop = me.getScrollTop) === null || _me$getScrollTop === void 0 ? void 0 : _me$getScrollTop.call(me)) ?? 0;
const sh = this.cachedScrollHeightDiff ?? ((_me$getScrollHeight = me.getScrollHeight) === null || _me$getScrollHeight === void 0 ? void 0 : _me$getScrollHeight.call(me)) ?? li.height;
const distance = sh - (st + li.height);
return distance <= threshold;
} catch {
return true;
}
}
maybeScrollDiffToBottom(targetLine) {
if (!this.diffEditorView) return;
if (this.diffAutoScroll && this.autoScrollOnUpdate && this.shouldAutoScrollDiff && this.hasVerticalScrollbarModified()) {
const me = this.diffEditorView.getModifiedEditor();
const model = me.getModel();
const line = targetLine ?? (model === null || model === void 0 ? void 0 : model.getLineCount()) ?? 1;
this.rafScheduler.schedule("revealDiff", () => {
try {
me.revealLine(line);
} catch {}
});
}
}
async createDiffEditor(container, originalCode, modifiedCode, language, currentTheme) {
var _oEditor$onDidContent, _mEditor$onDidContent;
this.cleanup();
this.lastContainer = container;
container.style.overflow = "auto";
container.style.maxHeight = this.maxHeightCSS;
const lang = processedLanguage(language) || language;
this.originalModel = monaco$3.editor.createModel(originalCode, lang);
this.modifiedModel = monaco$3.editor.createModel(modifiedCode, lang);
this.diffEditorView = monaco$3.editor.createDiffEditor(container, {
automaticLayout: true,
scrollBeyondLastLine: false,
renderSideBySide: true,
originalEditable: false,
readOnly: this.options.readOnly ?? true,
minimap: { enabled: false },
theme: currentTheme,
contextmenu: false,
scrollbar: {
...defaultScrollbar,
...this.options.scrollbar || {}
},
...this.options
});
this.diffEditorView.setModel({
original: this.originalModel,
modified: this.modifiedModel
});
this.lastKnownOriginalCode = originalCode;
this.lastKnownModifiedCode = modifiedCode;
this.shouldAutoScrollDiff = !!(this.autoScrollInitial && this.diffAutoScroll);
if (this.diffScrollWatcher) {
try {
this.diffScrollWatcher.dispose();
} catch {}
this.diffScrollWatcher = null;
}
if (this.diffAutoScroll) {
const me = this.diffEditorView.getModifiedEditor();
this.diffScrollWatcher = createScrollWatcherForEditor(me, {
onPause: () => {
this.shouldAutoScrollDiff = false;
},
onMaybeResume: () => {
this.shouldAutoScrollDiff = this.userIsNearBottomDiff();
},
getLast: () => this.lastScrollTopDiff,
setLast: (v) => {
this.lastScrollTopDiff = v;
}
});
}
this.maybeScrollDiffToBottom(this.modifiedModel.getLineCount());
if (this.diffHeightManager) {
try {
this.diffHeightManager.dispose();
} catch {}
this.diffHeightManager = null;
}
this.diffHeightManager = createHeightManager(container, () => this.computedHeight());
this.diffHeightManager.update();
try {
var _me$getScrollHeight2, _me$getOption;
const me = this.diffEditorView.getModifiedEditor();
this.cachedScrollHeightDiff = ((_me$getScrollHeight2 = me.getScrollHeight) === null || _me$getScrollHeight2 === void 0 ? void 0 : _me$getScrollHeight2.call(me)) ?? null;
this.cachedLineHeightDiff = ((_me$getOption = me.getOption) === null || _me$getOption === void 0 ? void 0 : _me$getOption.call(me, monaco$3.editor.EditorOption.lineHeight)) ?? null;
this.cachedComputedHeightDiff = this.computedHeight();
} catch {}
const oEditor = this.diffEditorView.getOriginalEditor();
const mEditor = this.diffEditorView.getModifiedEditor();
(_oEditor$onDidContent = oEditor.onDidContentSizeChange) === null || _oEditor$onDidContent === void 0 || _oEditor$onDidContent.call(oEditor, () => {
var _this$diffHeightManag, _this$diffHeightManag2;
this._hasScrollBar = false;
try {
var _oEditor$getScrollHei, _oEditor$getOption;
this.cachedScrollHeightDiff = ((_oEditor$getScrollHei = oEditor.getScrollHeight) === null || _oEditor$getScrollHei === void 0 ? void 0 : _oEditor$getScrollHei.call(oEditor)) ?? this.cachedScrollHeightDiff;
this.cachedLineHeightDiff = ((_oEditor$getOption = oEditor.getOption) === null || _oEditor$getOption === void 0 ? void 0 : _oEditor$getOption.call(oEditor, monaco$3.editor.EditorOption.lineHeight)) ?? this.cachedLineHeightDiff;
this.cachedComputedHeightDiff = this.computedHeight();
} catch {}
if ((_this$diffHeightManag = this.diffHeightManager) === null || _this$diffHeightManag === void 0 ? void 0 : _this$diffHeightManag.isSuppressed()) return;
(_this$diffHeightManag2 = this.diffHeightManager) === null || _this$diffHeightManag2 === void 0 || _this$diffHeightManag2.update();
});
(_mEditor$onDidContent = mEditor.onDidContentSizeChange) === null || _mEditor$onDidContent === void 0 || _mEditor$onDidContent.call(mEditor, () => {
var _this$diffHeightManag3, _this$diffHeightManag4;
this._hasScrollBar = false;
try {
var _mEditor$getScrollHei, _mEditor$getOption;
this.cachedScrollHeightDiff = ((_mEditor$getScrollHei = mEditor.getScrollHeight) === null || _mEditor$getScrollHei === void 0 ? void 0 : _mEditor$getScrollHei.call(mEditor)) ?? this.cachedScrollHeightDiff;
this.cachedLineHeightDiff = ((_mEditor$getOption = mEditor.getOption) === null || _mEditor$getOption === void 0 ? void 0 : _mEditor$getOption.call(mEditor, monaco$3.editor.EditorOption.lineHeight)) ?? this.cachedLineHeightDiff;
this.cachedComputedHeightDiff = this.computedHeight();
} catch {}
if ((_this$diffHeightManag3 = this.diffHeightManager) === null || _this$diffHeightManag3 === void 0 ? void 0 : _this$diffHeightManag3.isSuppressed()) return;
(_this$diffHeightManag4 = this.diffHeightManager) === null || _this$diffHeightManag4 === void 0 || _this$diffHeightManag4.update();
});
return this.diffEditorView;
}
updateDiff(originalCode, modifiedCode, codeLanguage) {
if (!this.diffEditorView || !this.originalModel || !this.modifiedModel) return;
const plang = codeLanguage ? processedLanguage(codeLanguage) : void 0;
if (plang && (this.originalModel.getLanguageId() !== plang || this.modifiedModel.getLanguageId() !== plang)) {
this.pendingDiffUpdate = {
original: originalCode,
modified: modifiedCode,
lang: codeLanguage
};
this.rafScheduler.schedule("diff", () => this.flushPendingDiffUpdate());
return;
}
if (this.lastKnownOriginalCode == null) this.lastKnownOriginalCode = this.originalModel.getValue();
if (this.lastKnownModifiedCode == null) this.lastKnownModifiedCode = this.modifiedModel.getValue();
const prevO = this.lastKnownOriginalCode;
const prevM = this.lastKnownModifiedCode;
let didImmediate = false;
if (originalCode !== prevO && originalCode.startsWith(prevO)) {
this.appendToModel(this.originalModel, originalCode.slice(prevO.length));
this.lastKnownOriginalCode = originalCode;
didImmediate = true;
}
if (modifiedCode !== prevM && modifiedCode.startsWith(prevM)) {
this.appendToModel(this.modifiedModel, modifiedCode.slice(prevM.length));
this.lastKnownModifiedCode = modifiedCode;
didImmediate = true;
this.maybeScrollDiffToBottom(this.modifiedModel.getLineCount());
}
if (originalCode !== this.lastKnownOriginalCode || modifiedCode !== this.lastKnownModifiedCode) {
this.pendingDiffUpdate = {
original: originalCode,
modified: modifiedCode
};
this.rafScheduler.schedule("diff", () => this.flushPendingDiffUpdate());
} else if (didImmediate) {}
}
updateOriginal(newCode, codeLanguage) {
if (!this.diffEditorView || !this.originalModel) return;
if (codeLanguage) {
const lang = processedLanguage(codeLanguage);
if (lang && this.originalModel.getLanguageId() !== lang) monaco$3.editor.setModelLanguage(this.originalModel, lang);
}
const prev = this.lastKnownOriginalCode ?? this.originalModel.getValue();
if (prev === newCode) return;
if (newCode.startsWith(prev) && prev.length < newCode.length) this.appendToModel(this.originalModel, newCode.slice(prev.length));
else this.applyMinimalEditToModel(this.originalModel, prev, newCode);
this.lastKnownOriginalCode = newCode;
}
updateModified(newCode, codeLanguage) {
if (!this.diffEditorView || !this.modifiedModel) return;
if (codeLanguage) {
const lang = processedLanguage(codeLanguage);
if (lang && this.modifiedModel.getLanguageId() !== lang) monaco$3.editor.setModelLanguage(this.modifiedModel, lang);
}
const prev = this.lastKnownModifiedCode ?? this.modifiedModel.getValue();
if (prev === newCode) return;
if (newCode.startsWith(prev) && prev.length < newCode.length) {
this.appendToModel(this.modifiedModel, newCode.slice(prev.length));
this.maybeScrollDiffToBottom(this.modifiedModel.getLineCount());
} else this.applyMinimalEditToModel(this.modifiedModel, prev, newCode);
this.lastKnownModifiedCode = newCode;
}
appendOriginal(appendText, codeLanguage) {
if (!this.diffEditorView || !this.originalModel || !appendText) return;
if (codeLanguage) {
const lang = processedLanguage(codeLanguage);
if (lang && this.originalModel.getLanguageId() !== lang) monaco$3.editor.setModelLanguage(this.originalModel, lang);
}
this.appendToModel(this.originalModel, appendText);
try {
this.lastKnownOriginalCode = this.originalModel.getValue();
} catch {}
}
appendModified(appendText, codeLanguage) {
if (!this.diffEditorView || !this.modifiedModel || !appendText) return;
if (codeLanguage) {
const lang = processedLanguage(codeLanguage);
if (lang && this.modifiedModel.getLanguageId() !== lang) monaco$3.editor.setModelLanguage(this.modifiedModel, lang);
}
this.appendToModel(this.modifiedModel, appendText);
try {
this.lastKnownModifiedCode = this.modifiedModel.getValue();
} catch {}
this.appendBufferDiff.push(appendText);
if (!this.appendBufferDiffScheduled) {
this.appendBufferDiffScheduled = true;
this.rafScheduler.schedule("appendDiff", () => this.flushAppendBufferDiff());
}
}
setLanguage(language, languages$1) {
if (!languages$1.includes(language)) {
console.warn(`Language "${language}" is not registered. Available languages: ${languages$1.join(", ")}`);
return;
}
if (this.originalModel && this.originalModel.getLanguageId() !== language) monaco$3.editor.setModelLanguage(this.originalModel, language);
if (this.modifiedModel && this.modifiedModel.getLanguageId() !== language) monaco$3.editor.setModelLanguage(this.modifiedModel, language);
}
getDiffEditorView() {
return this.diffEditorView;
}
getDiffModels() {
return {
original: this.originalModel,
modified: this.modifiedModel
};
}
cleanup() {
this.rafScheduler.cancel("diff");
this.pendingDiffUpdate = null;
this.rafScheduler.cancel("appendDiff");
this.appendBufferDiffScheduled = false;
this.appendBufferDiff.length = 0;
if (this.diffScrollWatcher) {
try {
this.diffScrollWatcher.dispose();
} catch {}
this.diffScrollWatcher = null;
}
if (this.diffHeightManager) {
try {
this.diffHeightManager.dispose();
} catch {}
this.diffHeightManager = null;
}
if (this.diffEditorView) {
try {
this.diffEditorView.dispose();
} catch {}
this.diffEditorView = null;
}
if (this.originalModel) {
try {
this.originalModel.dispose();
} catch {}
this.originalModel = null;
}
if (this.modifiedModel) {
try {
this.modifiedModel.dispose();
} catch {}
this.modifiedModel = null;
}
this.lastKnownOriginalCode = null;
this.lastKnownModifiedCode = null;
if (this.lastContainer) {
this.lastContainer.innerHTML = "";
this.lastContainer = null;
}
}
safeClean() {
this.rafScheduler.cancel("diff");
this.pendingDiffUpdate = null;
if (this.diffScrollWatcher) {
try {
this.diffScrollWatcher.dispose();
} catch {}
this.diffScrollWatcher = null;
}
this._hasScrollBar = false;
this.shouldAutoScrollDiff = !!(this.autoScrollInitial && this.diffAutoScroll);
this.lastScrollTopDiff = 0;
if (this.diffHeightManager) {
try {
this.diffHeightManager.dispose();
} catch {}
this.diffHeightManager = null;
}
}
flushPendingDiffUpdate() {
if (!this.pendingDiffUpdate || !this.diffEditorView) return;
const o = this.originalModel;
const m = this.modifiedModel;
if (!o || !m) {
this.pendingDiffUpdate = null;
return;
}
const { original, modified, lang } = this.pendingDiffUpdate;
this.pendingDiffUpdate = null;
if (lang) {
const plang = processedLanguage(lang);
if (plang) {
if (o.getLanguageId() !== plang) {
monaco$3.editor.setModelLanguage(o, plang);
monaco$3.editor.setModelLanguage(m, plang);
}
}
}
if (this.lastKnownOriginalCode == null) this.lastKnownOriginalCode = o.getValue();
if (this.lastKnownModifiedCode == null) this.lastKnownModifiedCode = m.getValue();
const prevO = this.lastKnownOriginalCode;
if (prevO !== original) {
if (original.startsWith(prevO) && prevO.length < original.length) this.appendToModel(o, original.slice(prevO.length));
else this.applyMinimalEditToModel(o, prevO, original);
this.lastKnownOriginalCode = original;
}
const prevM = this.lastKnownModifiedCode;
const prevMLineCount = m.getLineCount();
if (prevM !== modified) {
if (modified.startsWith(prevM) && prevM.length < modified.length) this.appendToModel(m, modified.slice(prevM.length));
else this.applyMinimalEditToModel(m, prevM, modified);
this.lastKnownModifiedCode = modified;
const newMLineCount = m.getLineCount();
if (newMLineCount !== prevMLineCount) this.maybeScrollDiffToBottom(newMLineCount);
}
}
flushAppendBufferDiff() {
if (!this.diffEditorView) return;
if (this.appendBufferDiff.length === 0) return;
this.appendBufferDiffScheduled = false;
const me = this.diffEditorView.getModifiedEditor();
const model = me.getModel();
if (!model) {
this.appendBufferDiff.length = 0;
return;
}
const text = this.appendBufferDiff.join("");
this.appendBufferDiff.length = 0;
try {
const lastLine = model.getLineCount();
const lastColumn = model.getLineMaxColumn(lastLine);
const range = new monaco$3.Range(lastLine, lastColumn, lastLine, lastColumn);
model.applyEdits([{
range,
text,
forceMoveMarkers: true
}]);
if (this.lastKnownModifiedCode != null) this.lastKnownModifiedCode = this.lastKnownModifiedCode + text;
try {
this.maybeScrollDiffToBottom(model.getLineCount());
} catch {}
} catch {}
}
applyMinimalEditToModel(model, prev, next) {
const res = computeMinimalEdit(prev, next);
if (!res) return;
const { start, endPrevIncl, replaceText } = res;
const rangeStart = model.getPositionAt(start);
const rangeEnd = model.getPositionAt(endPrevIncl + 1);
const range = new monaco$3.Range(rangeStart.lineNumber, rangeStart.column, rangeEnd.lineNumber, rangeEnd.column);
model.applyEdits([{
range,
text: replaceText,
forceMoveMarkers: true
}]);
}
appendToModel(model, appendText) {
if (!appendText) return;
const lastLine = model.getLineCount();
const lastColumn = model.getLineMaxColumn(lastLine);
const range = new monaco$3.Range(lastLine, lastColumn, lastLine, lastColumn);
model.applyEdits([{
range,
text: appendText,
forceMoveMarkers: true
}]);
}
};
//#endregion
//#region src/core/EditorManager.ts
var EditorManager = class {
editorView = null;
lastContainer = null;
lastKnownCode = null;
pendingUpdate = null;
_hasScrollBar = false;
shouldAutoScroll = true;
scrollWatcher = null;
lastScrollTop = 0;
cachedScrollHeight = null;
cachedLineHeight = null;
cachedComputedHeight = null;
appendBuffer = [];
appendBufferScheduled = false;
rafScheduler = createRafScheduler();
editorHeightManager = null;
constructor(options, maxHeightValue, maxHeightCSS, autoScrollOnUpdate, autoScrollInitial, autoScrollThresholdPx, autoScrollThresholdLines) {
this.options = options;
this.maxHeightValue = maxHeightValue;
this.maxHeightCSS = maxHeightCSS;
this.autoScrollOnUpdate = autoScrollOnUpdate;
this.autoScrollInitial = autoScrollInitial;
this.autoScrollThresholdPx = autoScrollThresholdPx;
this.autoScrollThresholdLines = autoScrollThresholdLines;
}
hasVerticalScrollbar() {
try {
if (!this.editorView) return false;
if (this._hasScrollBar) return true;
const ch = this.cachedComputedHeight ?? this.computedHeight(this.editorView);
return this._hasScrollBar = this.editorView.getScrollHeight() > ch + padding / 2;
} catch {
return false;
}
}
userIsNearBottom() {
try {
var _this$editorView$getL, _this$editorView, _this$editorView$getS, _this$editorView2, _this$editorView$getS2, _this$editorView3;
if (!this.editorView) return true;
const li = (_this$editorView$getL = (_this$editorView = this.editorView).getLayoutInfo) === null || _this$editorView$getL === void 0 ? void 0 : _this$editorView$getL.call(_this$editorView);
if (!li) return true;
const lineHeight = this.cachedLineHeight ?? this.editorView.getOption(monaco$2.editor.EditorOption.lineHeight);
const lineThreshold = (this.autoScrollThresholdLines ?? 0) * lineHeight;
const threshold = Math.max(lineThreshold || 0, this.autoScrollThresholdPx || 0);
const st = ((_this$editorView$getS = (_this$editorView2 = this.editorView).getScrollTop) === null || _this$editorView$getS === void 0 ? void 0 : _this$editorView$getS.call(_this$editorView2)) ?? 0;
const sh = this.cachedScrollHeight ?? ((_this$editorView$getS2 = (_this$editorView3 = this.editorView).getScrollHeight) === null || _this$editorView$getS2 === void 0 ? void 0 : _this$editorView$getS2.call(_this$editorView3)) ?? li.height;
const distance = sh - (st + li.height);
return distance <= threshold;
} catch {
return true;
}
}
computedHeight(editorView) {
var _editorView$getModel;
const lineCount = ((_editorView$getModel = editorView.getModel()) === null || _editorView$getModel === void 0 ? void 0 : _editorView$getModel.getLineCount()) ?? 1;
const lineHeight = editorView.getOption(monaco$2.editor.EditorOption.lineHeight);
const height = Math.min(lineCount * lineHeight + padding, this.maxHeightValue);
return height;
}
maybeScrollToBottom(targetLine) {
if (this.autoScrollOnUpdate && this.shouldAutoScroll && this.hasVerticalScrollbar()) {
const model = this.editorView.getModel();
const line = targetLine ?? (model === null || model === void 0 ? void 0 : model.getLineCount()) ?? 1;
this.rafScheduler.schedule("reveal", () => {
try {
this.editorView.revealLine(line);
} catch {}
});
}
}
async createEditor(container, code, language, currentTheme) {
var _this$editorView$onDi, _this$editorView6;
this.cleanup();
this.lastContainer = container;
container.style.overflow = "auto";
container.style.maxHeight = this.maxHeightCSS;
this.editorView = monaco$2.editor.create(container, {
value: code,
language: processedLanguage(language) || language,
theme: currentTheme,
scrollBeyondLastLine: false,
minimap: { enabled: false },
automaticLayout: true,
readOnly: this.options.readOnly ?? true,
contextmenu: false,
scrollbar: {
...defaultScrollbar,
...this.options.scrollbar || {}
},
...this.options
});
this.lastKnownCode = this.editorView.getValue();
if (this.editorHeightManager) {
try {
this.editorHeightManager.dispose();
} catch {}
this.editorHeightManager = null;
}
this.editorHeightManager = createHeightManager(container, () => this.computedHeight(this.editorView));
this.editorHeightManager.update();
try {
var _this$editorView$getS3, _this$editorView4, _this$editorView$getO, _this$editorView5;
this.cachedScrollHeight = ((_this$editorView$getS3 = (_this$editorView4 = this.editorView).getScrollHeight) === null || _this$editorView$getS3 === void 0 ? void 0 : _this$editorView$getS3.call(_this$editorView4)) ?? null;
this.cachedLineHeight = ((_this$editorView$getO = (_this$editorView5 = this.editorView).getOption) === null || _this$editorView$getO === void 0 ? void 0 : _this$editorView$getO.call(_this$editorView5, monaco$2.editor.EditorOption.lineHeight)) ?? null;
this.cachedComputedHeight = this.computedHeight(this.editorView);
} catch {}
(_this$editorView$onDi = (_this$editorView6 = this.editorView).onDidContentSizeChange) === null || _this$editorView$onDi === void 0 || _this$editorView$onDi.call(_this$editorView6, () => {
var _this$editorHeightMan, _this$editorHeightMan2;
this._hasScrollBar = false;
try {
var _getScrollHeight, _ref, _getOption, _ref2;
this.cachedScrollHeight = ((_getScrollHeight = (_ref = this.editorView).getScrollHeight) === null || _getScrollHeight === void 0 ? void 0 : _getScrollHeight.call(_ref)) ?? null;
this.cachedLineHeight = ((_getOption = (_ref2 = this.editorView).getOption) === null || _getOption === void 0 ? void 0 : _getOption.call(_ref2, monaco$2.editor.EditorOption.lineHeight)) ?? null;
this.cachedComputedHeight = this.computedHeight(this.editorView);
} catch {}
if ((_this$editorHeightMan = this.editorHeightManager) === null || _this$editorHeightMan === void 0 ? void 0 : _this$editorHeightMan.isSuppressed()) return;
(_this$editorHeightMan2 = this.editorHeightManager) === null || _this$editorHeightMan2 === void 0 || _this$editorHeightMan2.update();
});
this.editorView.onDidChangeModelContent(() => {
try {
this.lastKnownCode = this.editorView.getValue();
} catch {}
});
this.shouldAutoScroll = !!this.autoScrollInitial;
if (this.scrollWatcher) {
try {
this.scrollWatcher.dispose();
} catch {}
this.scrollWatcher = null;
}
this.scrollWatcher = createScrollWatcherForEditor(this.editorView, {
onPause: () => {
this.shouldAutoScroll = false;
},
onMaybeResume: () => {
this.shouldAutoScroll = this.userIsNearBottom();
},
getLast: () => this.lastScrollTop,
setLast: (v) => {
this.lastScrollTop = v;
}
});
this.maybeScrollToBottom();
return this.editorView;
}
updateCode(newCode, codeLanguage) {
this.pendingUpdate = {
code: newCode,
lang: codeLanguage
};
this.rafScheduler.schedule("update", () => this.flushPendingUpdate());
}
flushPendingUpdate() {
if (!this.pendingUpdate || !this.editorView) return;
const model = this.editorView.getModel();
if (!model) return;
const { code: newCode, lang: codeLanguage } = this.pendingUpdate;
this.pendingUpdate = null;
const processedCodeLanguage = processedLanguage(codeLanguage);
const languageId = model.getLanguageId();
if (languageId !== processedCodeLanguage) {
if (processedCodeLanguage) monaco$2.editor.setModelLanguage(model, processedCodeLanguage);
const prevLineCount$1 = model.getLineCount();
model.setValue(newCode);
this.lastKnownCode = newCode;
const newLineCount$1 = model.getLineCount();
if (newLineCount$1 !== prevLineCount$1) this.maybeScrollToBottom(newLineCount$1);
return;
}
const prevCode = this.lastKnownCode ?? this.editorView.getValue();
if (prevCode === newCode) return;
if (newCode.startsWith(prevCode) && prevCode.length < newCode.length) {
const suffix = newCode.slice(prevCode.length);
if (suffix) this.appendCode(suffix, codeLanguage);
this.lastKnownCode = newCode;
return;
}
const prevLineCount = model.getLineCount();
this.applyMinimalEdit(prevCode, newCode);
this.lastKnownCode = newCode;
const newLineCount = model.getLineCount();
if (newLineCount !== prevLineCount) this.maybeScrollToBottom(newLineCount);
}
appendCode(appendText, codeLanguage) {
if (!this.editorView) return;
const model = this.editorView.getModel();
if (!model) return;
const processedCodeLanguage = codeLanguage ? processedLanguage(codeLanguage) : model.getLanguageId();
if (processedCodeLanguage && model.getLanguageId() !== processedCodeLanguage) monaco$2.editor.setModelLanguage(model, processedCodeLanguage);
const lastLine = model.getLineCount();
const lastColumn = model.getLineMaxColumn(lastLine);
const range = new monaco$2.Range(lastLine, lastColumn, lastLine, lastColumn);
const isReadOnly = this.editorView.getOption(monaco$2.editor.EditorOption.readOnly);
if (isReadOnly) model.applyEdits([{
range,
text: appendText,
forceMoveMarkers: true
}]);
else this.editorView.executeEdits("append", [{
range,
text: appendText,
forceMoveMarkers: true
}]);
try {
this.lastKnownCode = model.getValue();
} catch {}
if (appendText) {
this.appendBuffer.push(appendText);
if (!this.appendBufferScheduled) {
this.appendBufferScheduled = true;
this.rafScheduler.schedule("append", () => this.flushAppendBuffer());
}
}
}
applyMinimalEdit(prev, next) {
if (!this.editorView) return;
const model = this.editorView.getModel();
if (!model) return;
const res = computeMinimalEdit(prev, next);
if (!res) return;
const { start, endPrevIncl, replaceText } = res;
const rangeStart = model.getPositionAt(start);
const rangeEnd = model.getPositionAt(endPrevIncl + 1);
const range = new monaco$2.Range(rangeStart.lineNumber, rangeStart.column, rangeEnd.lineNumber, rangeEnd.column);
const isReadOnly = this.editorView.getOption(monaco$2.editor.EditorOption.readOnly);
const edit = [{
range,
text: replaceText,
forceMoveMarkers: true
}];
if (isReadOnly) model.applyEdits(edit);
else this.editorView.executeEdits("minimal-replace", edit);
}
flushAppendBuffer() {
if (!this.editorView) return;
if (this.appendBuffer.length === 0) return;
this.appendBufferScheduled = false;
const model = this.editorView.getModel();
if (!model) {
this.appendBuffer.length = 0;
return;
}
const text = this.appendBuffer.join("");
this.appendBuffer.length = 0;
try {
const lastLine = model.getLineCount();
const lastColumn = model.getLineMaxColumn(lastLine);
const range = new monaco$2.Range(lastLine, lastColumn, lastLine, lastColumn);
const isReadOnly = this.editorView.getOption(monaco$2.editor.EditorOption.readOnly);
if (isReadOnly) model.applyEdits([{
range,
text,
forceMoveMarkers: true
}]);
else this.editorView.executeEdits("append", [{
range,
text,
forceMoveMarkers: true
}]);
if (this.lastKnownCode != null) this.lastKnownCode = this.lastKnownCode + text;
try {
this.maybeScrollToBottom(model.getLineCount());
} catch {}
} catch {}
}
setLanguage(language, languages$1) {
if (languages$1.includes(language)) {
if (this.editorView) {
const model = this.editorView.getModel();
if (model && model.getLanguageId() !== language) monaco$2.editor.setModelLanguage(model, language);
}
} else console.warn(`Language "${language}" is not registered. Available languages: ${languages$1.join(", ")}`);
}
getEditorView() {
return this.editorView;
}
cleanup() {
this.rafScheduler.cancel("update");
this.pendingUpdate = null;
this.rafScheduler.cancel("append");
this.appendBufferScheduled = false;
this.appendBuffer.length = 0;
if (this.editorView) {
this.editorView.dispose();
this.editorView = null;
}
this.lastKnownCode = null;
if (this.lastContainer) {
this.lastContainer.innerHTML = "";
this.lastContainer = null;
}
if (this.scrollWatcher) {
try {
this.scrollWatcher.dispose();
} catch {}
this.scrollWatcher = null;
}
if (this.editorHeightManager) {
try {
this.editorHeightManager.dispose();
} catch {}
this.editorHeightManager = null;
}
}
safeClean() {
this.rafScheduler.cancel("update");
this.pendingUpdate = null;
if (this.scrollWatcher) {
try {
this.scrollWatcher.dispose();
} catch {}
this.scrollWatcher = null;
}
this._hasScrollBar = false;
this.shouldAutoScroll = !!this.autoScrollInitial;
this.lastScrollTop = 0;
if (this.editorHeightManager) {
try {
this.editorHeightManager.dispose();
} catch {}
this.editorHeightManager = null;
}
}
};
//#endregion
//#region src/isDark.ts
const isDark = useDark();
//#endregion
//#region src/preloadMonacoWorkers.ts
async function preloadMonacoWorkers(options) {
if (typeof window === "undefined" || typeof document === "undefined") return;
const workerUrlJson = new URL("monaco-editor/esm/vs/language/json/json.worker.js", import.meta.url);
const workerUrlCss = new URL("monaco-editor/esm/vs/language/css/css.worker.js", import.meta.url);
const workerUrlHtml = new URL("monaco-editor/esm/vs/language/html/html.worker.js", import.meta.url);
const workerUrlTs = new URL("monaco-editor/esm/vs/language/typescript/ts.worker.js", import.meta.url);
const workerUrlEditor = new URL("monaco-editor/esm/vs/editor/editor.worker.js", import.meta.url);
const unique = Array.from(new Set([
String(workerUrlJson),
String(workerUrlCss),
String(workerUrlHtml),
String(workerUrlTs),
String(workerUrlEditor)
]));
const workerUrlByLabel = {
json: workerUrlJson,
css: workerUrlCss,
scss: workerUrlCss,
less: workerUrlCss,
html: workerUrlHtml,
handlebars: workerUrlHtml,
razor: workerUrlHtml,
typescript: workerUrlTs,
javascript: workerUrlTs
};
try {
for (const href of unique) if (!document.querySelector(`link[rel="modulepreload"][href="${href}"]`)) {
const link = document.createElement("link");
link.rel = "modulepreload";
link.href = href;
document.head.appendChild(link);
}
if (options === null || options === void 0 ? void 0 : options.fetch) await Promise.all(unique.map((u) => fetch(u, {
method: "GET",
cache: "force-cache"
}).catch(() => void 0)));
self.MonacoEnvironment = { getWorker(_, label) {
const url = workerUrlByLabel[label] ?? workerUrlEditor;
return new Worker(url, { type: "module" });
} };
} catch {}
}
//#endregion
//#region src/utils/arraysEqual.ts
function arraysEqual(a, b) {
if (a === b) return true;
if (!a || !b) return false;
if (a.length !== b.length) return false;
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
return true;
}
//#endregion
//#region src/utils/registerMonacoThemes.ts
let themesRegistered = false;
let languagesRegistered = false;
let currentThemes = [];
let currentLanguages = [];
let themeRegisterPromise = null;
function setThemeRegisterPromise(p) {
return themeRegisterPromise = p;
}
async function registerMonacoThemes(themes, languages$1) {
registerMonacoLanguages(languages$1);
if (themesRegistered && arraysEqual(themes, currentThemes) && arraysEqual(languages$1, currentLanguages)) return;
try {
const highlighter = await createHighlighter({
themes,
langs: languages$1
});
shikiToMonaco(highlighter, monaco$1);
themesRegistered = true;
currentThemes = themes;
currentLanguages = languages$1;
} catch (e) {
themeRegisterPromise = null;
throw e;
}
}
function registerMonacoLanguages(languages$1) {
if (languagesRegistered && arraysEqual(languages$1, currentLanguages)) return;
const existing = new Set(monaco$1.languages.getLanguages().map((l) => l.id));
for (const lang of languages$1) if (!existing.has(lang)) try {
monaco$1.languages.register({ id: lang });
} catch {}
languagesRegistered = true;
currentLanguages = languages$1;
}
//#endregion
//#region src/index.ts
preloadMonacoWorkers();
const disposals = [];
/**
* useMonaco 组合式函数
*
* 提供 Monaco 编辑器的创建、销毁、内容/主题/语言更新等能力。
* 支持主题自动切换、语言高亮、代码更新等功能。
*
* @param {MonacoOptions} [monacoOptions] - 编辑器初始化配置,支持 Monaco 原生配置及扩展项
* @param {number | string} [monacoOptions.MAX_HEIGHT] - 编辑器最大高度,可以是数字(像素)或 CSS 字符串(如 '100%', 'calc(100vh - 100px)')
* @param {boolean} [monacoOptions.readOnly] - 是否为只读模式
* @param {MonacoTheme[]} [monacoOptions.themes] - 主题数组,至少包含两个主题:[暗色主题, 亮色主题]
* @param {MonacoLanguage[]} [monacoOptions.languages] - 支持的编程语言数组
* @param {string} [monacoOptions.theme] - 初始主题名称
* @param {boolean} [monacoOptions.isCleanOnBeforeCreate] - 是否在创建前清理之前注册的资源, 默认为 true
* @param {(monaco: typeof import('monaco-editor')) => monaco.IDisposable[]} [monacoOptions.onBeforeCreate] - 编辑器创建前的钩子函数
*
* @returns {{
* createEditor: (container: HTMLElement, code: string, language: string) => Promise<monaco.editor.IStandaloneCodeEditor>,
* createDiffEditor: (
* container: HTMLElement,
* originalCode: string,
* modifiedCode: string,
* language: string,
* ) => Promise<monaco.editor.IStandaloneDiffEditor>,
* cleanupEditor: () => void,
* updateCode: (newCode: string, codeLanguage: string) => void,
* appendCode: (appendText: string, codeLanguage?: string) => void,
* updateDiff: (
* originalCode: string,
* modifiedCode: string,
* codeLanguage?: string,
* ) => void,
* updateOriginal: (newCode: string, codeLanguage?: string) => void,
* updateModified: (newCode: string, codeLanguage?: string) => void,
* appendOriginal: (appendText: string, codeLanguage?: string) => void,
* appendModified: (appendText: string, codeLanguage?: string) => void,
* setTheme: (theme: MonacoTheme) => void,
* setLanguage: (language: MonacoLanguage) => void,
* getCurrentTheme: () => string,
* getEditor: () => typeof monaco.editor,
* getEditorView: () => monaco.editor.IStandaloneCodeEditor | null,
* getDiffEditorView: () => monaco.editor.IStandaloneDiffEditor | null,
* getDiffModels: () => { original: monaco.editor.ITextModel | null, modified: monaco.editor.ITextModel | null },
* }} 返回对象包含以下方法和属性:
*
* @property {Function} createEditor - 创建并挂载 Monaco 编辑器到指定容器
* @property {Function} cleanupEditor - 销毁编辑器并清理容器
* @property {Function} updateCode - 更新编辑器内容和语言,必要时滚动到底部
* @property {Function} appendCode - 在编辑器末尾追加文本,必要时滚动到底部
* @property {Function} createDiffEditor - 创建并挂载 Diff 编辑器
* @property {Function} updateDiff - 更新 Diff 编辑器的 original/modified 内容(RAF 合并、增量更新)
* @property {Function} updateOriginal - 仅更新 Diff 的 original 内容(增量更新)
* @property {Function} updateModified - 仅更新 Diff 的 modified 内容(增量更新)
* @property {Function} appendOriginal - 在 Diff 的 original 末尾追加(显式流式场景)
* @property {Function} appendModified - 在 Diff 的 modified 末尾追加(显式流式场景)
* @property {Function} setTheme - 切换编辑器主题
* @property {Function} setLanguage - 切换编辑器语言
* @property {Function} getCurrentTheme - 获取当前主题名称
* @property {Function} getEditor - 获取 Monaco 的静态 editor 对象(用于静态方法调用)
* @property {Function} getEditorView - 获取当前编辑器实例
* @property {Function} getDiffEditorView - 获取当前 Diff 编辑器实例
* @property {Function} getDiffModels - 获取 Diff 的 original/modified 两个模型
*
* @throws {Error} 当主题数组不是数组或长度小于2时抛出错误
*
* @example
* ```typescript
* import { useMonaco } from 'vue-use-monaco'
*
* const { createEditor, updateCode, setTheme } = useMonaco({
* themes: ['vitesse-dark', 'vitesse-light'],
* languages: ['javascript', 'typescript'],
* readOnly: false
* })
*
* // 创建编辑器
* const editor = await createEditor(containerRef.value, 'console.log("hello")', 'javascript')
*
* // 更新代码
* updateCode('console.log("world")', 'javascript')
*
* // 切换主题
* setTheme('vitesse-light')
* ```
*/
function useMonaco(monacoOptions = {}) {
if (monacoOptions.isCleanOnBeforeCreate ?? true) disposals.forEach((d) => d.dispose());
if (monacoOptions.isCleanOnBeforeCreate ?? true) disposals.length = 0;
let editorView = null;
let editorMgr = null;
let diffEditorView = null;
let diffMgr = null;
let originalModel = null;
let modifiedModel = null;
let _hasScrollBar = false;
const themes = monacoOptions.themes ?? defaultThemes;
if (!Array.isArray(themes) || themes.length < 2) throw new Error("Monaco themes must be an array with at least two themes: [darkTheme, lightTheme]");
const languages$1 = monacoOptions.languages ?? defaultLanguages;
const MAX_HEIGHT = monacoOptions.MAX_HEIGHT ?? 500;
const autoScrollOnUpdate = monacoOptions.autoScrollOnUpdate ?? true;
const autoScrollInitial = monacoOptions.autoScrollInitial ?? true;
const autoScrollThresholdPx = monacoOptions.autoScrollThresholdPx ?? 32;
const autoScrollThresholdLines = monacoOptions.autoScrollThresholdLines ?? 2;
const diffAutoScroll = monacoOptions.diffAutoScroll ?? true;
const getMaxHeightValue = () => {
if (typeof MAX_HEIGHT === "number") return MAX_HEIGHT;
const match = MAX_HEIGHT.match(/^(\d+(?:\.\d+)?)/);
return match ? Number.parseFloat(match[1]) : 500;
};
const getMaxHeightCSS = () => {
if (typeof MAX_HEIGHT === "number") return `${MAX_HEIGHT}px`;
return MAX_HEIGHT;
};
const maxHeightValue = getMaxHeightValue();
const maxHeightCSS = getMaxHeightCSS();
let lastContainer = null;
let lastKnownCode = null;
let pendingUpdate = null;
let shouldAutoScroll = true;
let scrollWatcher = null;
const cachedComputedHeight = null;
const appendBuffer = [];
let appendBufferScheduled = false;
let lastAppliedTheme = null;
const currentTheme = computed(() => isDark.value ? typeof themes[0] === "string" ? themes[0] : themes[0].name : typeof themes[1] === "string" ? themes[1] : themes[1].name);
let themeWatcher = null;
const rafScheduler = createRafScheduler();
function hasVerticalScrollbar() {
try {
if (!editorView) return false;
if (_hasScrollBar) return true;
const ch = cachedComputedHeight ?? computedHeight(editorView);
return _hasScrollBar = editorView.getScrollHeight() > ch + padding / 2;
} catch {
return false;
}
}
function maybeScrollToBottom(targetLine) {
if (autoScrollOnUpdate && shouldAutoScroll && hasVerticalScrollbar()) {
const model = editorView.getModel();
const line = targetLine ?? (model === null || model === void 0 ? void 0 : model.getLineCount()) ?? 1;
rafScheduler.schedule("reveal", () => {
try {
editorView.revealLine(line);
} catch {}
});
}
}
async function createEditor(container, code, language) {
cleanupEditor();
lastContainer = container;
if (monacoOptions.isCleanOnBeforeCreate ?? true) {
disposals.forEach((d) => d.dispose());
disposals.length = 0;
}
if (monacoOptions.onBeforeCreate) {
const ds = monacoOptions.onBeforeCreate(monaco);
if (ds) disposals.push(...ds);
}
if (themeWatcher) {
themeWatcher();
themeWatcher = null;
}
await setThemeRegisterPromise(registerMonacoThemes(themes, languages$1));
editorMgr = new EditorManager(monacoOptions, maxHeightValue, maxHeightCSS, autoScrollOnUpdate, autoScrollInitial, autoScrollThresholdPx, autoScrollThresholdLines);
editorView = await editorMgr.createEditor(container, code, language, currentTheme.value);
themeWatcher = watch(() => isDark.value, () => {
if (currentTheme.value !== lastAppliedTheme) {
monaco.editor.setTheme(currentTheme.value);
lastAppliedTheme = currentTheme.value;
}
}, {
flush: "post",
immediate: true
});
try {
if (editorView) lastKnownCode = editorView.getValue();
} catch {}
return editorView;
}
function computedHeight(editorView$1) {
var _getModel;
const lineCount = ((_getModel = editorView$1.getModel()) === null || _getModel === void 0 ? void 0 : _getModel.getLineCount()) ?? 1;
const lineHeight = editorView$1.getOption(monaco.editor.EditorOption.lineHeight);
const height = Math.min(lineCount * lineHeight + padding, maxHeightValue);
return height;
}
async function createDiffEditor(container, originalCode, modifiedCode, language) {
cleanupEditor();
lastContainer = container;
if (monacoOptions.isCleanOnBeforeCreate ?? true) {
disposals.forEach((d) => d.dispose());
disposals.length = 0;
}
if (monacoOptions.onBeforeCreate) {
const ds = monacoOptions.onBeforeCreate(monaco);
if (ds) disposals.push(...ds);
}
if (themeWatcher) {
themeWatcher();