vue-shiki-monaco
Version:
一个封shiki和monaco-editor的Vue组件
679 lines (678 loc) • 23.8 kB
JavaScript
import { defineComponent, ref, watch, onMounted, onUnmounted, computed, onBeforeUnmount, createElementBlock, openBlock, normalizeClass, createBlock, createCommentVNode, createElementVNode, withCtx, toDisplayString, normalizeStyle, unref } from "vue";
import MonacoHeader from "./es/Monaco-Header/index.js";
import ContextMenu from "./es/ContextMenu/index.js";
import './global.css';import './index.css';/* empty css */
import { shikiToMonaco } from "@shikijs/monaco";
import * as monaco from "monaco-editor-core";
import { createHighlighter } from "shiki";
import { u as useContextMenu, M as MENU_PRESETS, c as createEditorContextMenu, b as MINIMAP_MENU_PRESETS, a as createMinimapContextMenu } from "./editorMenu-BFs5QkAV.js";
import { _ as _export_sfc } from "./_plugin-vue_export-helper-1tPrXgE0.js";
function useMonacoDiffEdit(options) {
let diffEditInstance = null;
let highlighter = null;
const loadedThemes = /* @__PURE__ */ new Set();
const loadedLanguages = /* @__PURE__ */ new Set();
let resizeObserver = null;
let autoResizeEnabled = false;
let originalContextMenuCallback = null;
let modifiedContextMenuCallback = null;
let minimapContextMenuCallback = null;
async function initMonacoDiffEdit() {
const {
target,
languages,
themes,
defaultTheme = "vitesse-light",
diffViewType = "default"
} = options;
try {
languages.forEach((lang) => {
monaco.languages.register({ id: lang });
loadedLanguages.add(lang);
});
highlighter = await createShikiHighlighter(themes, languages);
themes.forEach((theme) => loadedThemes.add(theme));
shikiToMonaco(highlighter, monaco);
const diffViewOptions = getDiffViewOptions(diffViewType);
diffEditInstance = monaco.editor.createDiffEditor(target, {
theme: defaultTheme,
contextmenu: !options.contextMenu,
// 禁用默认右键菜单
automaticLayout: true,
// 启用自动布局
minimap: { enabled: true },
fontSize: 16,
...diffViewOptions,
...options
});
if (options.contextMenu?.enabled !== false) {
setupCustomContextMenu(target);
}
return diffEditInstance;
} catch (error) {
throw error;
}
}
async function createShikiHighlighter(themes, langs) {
return await createHighlighter({
themes,
langs
});
}
function registerLanguage(language) {
monaco.languages.register({ id: language });
}
async function setTheme(theme) {
if (!diffEditInstance) {
throw new Error("Editor instance not initialized");
}
try {
if (!loadedThemes.has(theme)) {
loadedThemes.add(theme);
const newThemes = Array.from(loadedThemes);
options.themes = newThemes;
if (highlighter) {
highlighter = await createShikiHighlighter(
newThemes,
Array.from(loadedLanguages)
);
shikiToMonaco(highlighter, monaco);
}
}
monaco.editor.setTheme(theme);
} catch (error) {
throw error;
}
}
function createModel(code, language) {
return monaco.editor.createModel(code, language);
}
function getDiffViewOptions(type) {
switch (type) {
case "default":
return { originalEditable: true };
case "inline":
return {
// You can optionally disable the resizing
enableSplitViewResizing: false,
// Render the diff inline
renderSideBySide: false
};
default:
return {
// You can optionally disable the resizing
enableSplitViewResizing: false
};
}
}
function setDiffModel(model) {
if (!diffEditInstance) return;
diffEditInstance.setModel(model);
}
function destroy(target) {
if (diffEditInstance) {
if (target) {
target.removeEventListener("contextmenu", handleContextMenu);
}
diffEditInstance.dispose();
}
if (resizeObserver) {
resizeObserver.disconnect();
resizeObserver = null;
}
originalContextMenuCallback = null;
modifiedContextMenuCallback = null;
minimapContextMenuCallback = null;
}
function layout() {
if (diffEditInstance) {
diffEditInstance.layout();
}
}
function enableAutoResize() {
if (!diffEditInstance || autoResizeEnabled) return;
try {
resizeObserver = new ResizeObserver(() => {
if (diffEditInstance) {
requestAnimationFrame(() => {
diffEditInstance?.layout();
});
}
});
resizeObserver.observe(options.target);
autoResizeEnabled = true;
} catch (error) {
}
}
function disableAutoResize() {
if (resizeObserver) {
resizeObserver.disconnect();
resizeObserver = null;
autoResizeEnabled = false;
}
}
function setDiffViewOptions(type) {
if (!diffEditInstance) return;
const getOptions = getDiffViewOptions(type);
diffEditInstance.updateOptions({
...getOptions
});
}
function setupCustomContextMenu(target) {
if (!diffEditInstance) return;
if (target) {
target.addEventListener("contextmenu", handleContextMenu);
}
}
function handleContextMenu(event) {
event.preventDefault();
event.stopPropagation();
const target = event.target;
const isMinimapClick = target && (target.closest(".minimap") || target.closest(".minimap-slider") || target.closest(".minimap-decorations-layer") || target.closest(".minimap-shadow-visible") || target.classList.contains("minimap"));
const clickedEditor = getClickedEditor(target, event);
if (isMinimapClick && minimapContextMenuCallback) {
minimapContextMenuCallback(event);
} else if (clickedEditor === "original" && originalContextMenuCallback) {
originalContextMenuCallback(event);
} else if (clickedEditor === "modified" && modifiedContextMenuCallback) {
modifiedContextMenuCallback(event);
}
}
function getClickedEditor(_target, event) {
const clickTarget = event.target;
const editorContainer = clickTarget.closest(".monaco-editor");
if (!editorContainer) {
return "modified";
}
if (clickTarget.closest(".original-in-monaco-diff-editor")) {
return "original";
}
return "modified";
}
function onOriginalContextMenu(callback) {
originalContextMenuCallback = callback;
}
function onModifiedContextMenu(callback) {
modifiedContextMenuCallback = callback;
}
function onMinimapContextMenu(callback) {
minimapContextMenuCallback = callback;
}
function offOriginalContextMenu() {
originalContextMenuCallback = null;
}
function offModifiedContextMenu() {
modifiedContextMenuCallback = null;
}
function offMinimapContextMenu() {
minimapContextMenuCallback = null;
}
return {
initMonacoDiffEdit,
destroy,
registerLanguage,
setTheme,
createModel,
setDiffModel,
setDiffViewOptions,
layout,
enableAutoResize,
disableAutoResize,
diffEditInstance,
onOriginalContextMenu,
onModifiedContextMenu,
offOriginalContextMenu,
offModifiedContextMenu,
onMinimapContextMenu,
offMinimapContextMenu
};
}
const _hoisted_1 = { class: "file-language diff-info" };
const _hoisted_2 = ["disabled"];
const _hoisted_3 = ["disabled"];
const _hoisted_4 = ["title"];
const _hoisted_5 = {
key: 0,
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor"
};
const _hoisted_6 = {
key: 1,
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor"
};
const _sfc_main = /* @__PURE__ */ defineComponent({
__name: "index",
props: {
oldModel: {},
newModel: {},
diffViewType: { default: "default" },
currentLanguage: { default: "typescript" },
currentTheme: { default: "vitesse-light" },
languages: { default: () => ["typescript"] },
themes: { default: () => ["vitesse-light", "vitesse-dark"] },
height: { default: "400px" },
showToolbar: { type: Boolean, default: true },
autoResize: { type: Boolean, default: true },
monacoEditClass: {},
fileName: {},
contextMenu: { default: () => ({
enabled: true,
items: "full",
variant: "glass"
}) },
minimapContextMenu: { default: () => ({
enabled: true,
items: "basic",
variant: "glass"
}) },
teleportTarget: {}
},
emits: ["change", "ready"],
setup(__props, { expose: __expose, emit: __emit }) {
const props = __props;
const emit = __emit;
const diffEditorRef = ref();
let diffEditorInstance = null;
let diffMonacoEditHook = null;
let diffOriginalEditor = null;
let diffModifiedEditor = null;
const currentDiffIndex = ref(0);
const totalDiffs = ref(0);
const isCollapsed = ref(false);
const disposables = [];
const originalContextMenuItems = ref([]);
const modifiedContextMenuItems = ref([]);
const contextMenuItems = ref([]);
const contextMenu = useContextMenu({
items: contextMenuItems.value
});
const minimapContextMenuItems = ref([]);
const minimapContextMenu = useContextMenu({
items: minimapContextMenuItems.value
});
watch(
() => props.currentTheme,
async (newTheme) => {
if (diffMonacoEditHook && diffEditorInstance) {
try {
await diffMonacoEditHook.setTheme(newTheme);
} catch (error) {
}
}
}
);
const initializeEditor = async () => {
if (!diffEditorRef.value) return;
diffMonacoEditHook = useMonacoDiffEdit({
target: diffEditorRef.value,
languages: props.languages,
themes: props.themes,
defaultTheme: props.currentTheme,
contextMenu: props.contextMenu
});
try {
diffEditorInstance = await diffMonacoEditHook.initMonacoDiffEdit();
const oldModel = diffMonacoEditHook.createModel(
props.oldModel,
props.currentLanguage
);
const newModel = diffMonacoEditHook.createModel(
props.newModel,
props.currentLanguage
);
diffMonacoEditHook.setDiffModel({
original: oldModel,
modified: newModel
});
emit("ready", diffEditorInstance);
if (props.contextMenu?.enabled !== false) {
diffOriginalEditor = diffEditorInstance.getOriginalEditor();
diffModifiedEditor = diffEditorInstance.getModifiedEditor();
setupContextMenu();
}
setupDiffNavigation();
if (props.autoResize) {
diffMonacoEditHook.enableAutoResize();
}
} catch (error) {
throw error;
}
};
watch(
() => props.diffViewType,
(v) => {
diffMonacoEditHook?.setDiffViewOptions(v);
}
);
onMounted(async () => {
await initializeEditor();
});
onUnmounted(() => {
disposables.forEach((disposable) => {
try {
disposable?.dispose?.();
} catch (error) {
}
});
disposables.length = 0;
if (diffMonacoEditHook) {
diffEditorRef.value && diffMonacoEditHook.destroy(diffEditorRef.value);
}
});
const setupContextMenu = () => {
if (!diffEditorInstance || !diffMonacoEditHook || !diffOriginalEditor || !diffModifiedEditor)
return;
if (props.contextMenu?.enabled !== false) {
let enabledItems = [];
if (typeof props.contextMenu?.items === "string") {
enabledItems = MENU_PRESETS[props.contextMenu.items];
} else if (Array.isArray(props.contextMenu?.items)) {
enabledItems = props.contextMenu.items;
}
originalContextMenuItems.value = createEditorContextMenu({
editor: diffOriginalEditor,
enabledItems: [
"copy",
"find",
"replace",
"increaseFontSize",
"decreaseFontSize",
"resetFontSize"
],
customItems: []
});
modifiedContextMenuItems.value = createEditorContextMenu({
editor: diffModifiedEditor,
enabledItems,
customItems: props.contextMenu?.customItems ?? []
});
diffMonacoEditHook.onOriginalContextMenu(async (event) => {
if (minimapContextMenu.isVisible.value) {
minimapContextMenu.hide();
}
contextMenuItems.value = originalContextMenuItems.value;
contextMenu.show(event, originalContextMenuItems.value, diffEditorRef.value);
});
diffMonacoEditHook.onModifiedContextMenu(async (event) => {
if (minimapContextMenu.isVisible.value) {
minimapContextMenu.hide();
}
try {
if ("permissions" in navigator) {
await navigator.permissions.query({
name: "clipboard-read"
});
}
} catch (error) {
}
contextMenuItems.value = modifiedContextMenuItems.value;
contextMenu.show(event, modifiedContextMenuItems.value, diffEditorRef.value);
});
}
if (props.minimapContextMenu?.enabled !== false) {
let minimapEnabledItems = [];
if (typeof props.minimapContextMenu?.items === "string") {
minimapEnabledItems = MINIMAP_MENU_PRESETS[props.minimapContextMenu.items];
} else if (Array.isArray(props.minimapContextMenu?.items)) {
minimapEnabledItems = props.minimapContextMenu.items;
}
minimapContextMenuItems.value = createMinimapContextMenu({
editor: diffModifiedEditor,
// 使用修改编辑器
enabledItems: minimapEnabledItems,
customItems: props.minimapContextMenu?.customItems ?? []
});
diffMonacoEditHook.onMinimapContextMenu(async (event) => {
if (contextMenu.isVisible.value) {
contextMenu.hide();
}
minimapContextMenu.show(event, minimapContextMenuItems.value, diffEditorRef.value);
});
}
};
const setupDiffNavigation = () => {
if (!diffEditorInstance || !diffOriginalEditor || !diffModifiedEditor) return;
const updateDiffCount = () => {
if (!diffEditorInstance) return;
requestAnimationFrame(() => {
const changes = diffEditorInstance?.getLineChanges();
const newTotalDiffs = changes?.length || 0;
if (totalDiffs.value !== newTotalDiffs) {
totalDiffs.value = newTotalDiffs;
if (totalDiffs.value > 0 && currentDiffIndex.value >= totalDiffs.value) {
currentDiffIndex.value = Math.max(0, totalDiffs.value - 1);
} else if (totalDiffs.value === 0) {
currentDiffIndex.value = 0;
}
}
});
};
updateDiffCount();
const originalModel = diffOriginalEditor.getModel();
const modifiedModel = diffModifiedEditor.getModel();
if (originalModel) {
const originalDisposable = originalModel.onDidChangeContent(() => {
updateDiffCount();
});
disposables.push(originalDisposable);
}
if (modifiedModel) {
const modifiedDisposable = modifiedModel.onDidChangeContent(() => {
updateDiffCount();
});
disposables.push(modifiedDisposable);
}
try {
if (diffEditorInstance.onDidUpdateDiff) {
const diffUpdateDisposable = diffEditorInstance.onDidUpdateDiff(() => {
updateDiffCount();
});
disposables.push(diffUpdateDisposable);
}
} catch (error) {
}
};
const goToPreviousDiff = () => {
if (!diffEditorInstance || totalDiffs.value === 0) return;
currentDiffIndex.value = currentDiffIndex.value > 0 ? currentDiffIndex.value - 1 : totalDiffs.value - 1;
goToCurrentDiff();
};
const goToNextDiff = () => {
if (!diffEditorInstance || totalDiffs.value === 0) return;
currentDiffIndex.value = (currentDiffIndex.value + 1) % totalDiffs.value;
goToCurrentDiff();
};
const goToCurrentDiff = () => {
if (!diffEditorInstance || totalDiffs.value === 0) return;
const changes = diffEditorInstance.getLineChanges();
if (changes && changes[currentDiffIndex.value]) {
const change = changes[currentDiffIndex.value];
const lineNumber = change?.modifiedStartLineNumber || change?.originalStartLineNumber;
diffModifiedEditor?.revealLineInCenter(lineNumber ?? 0);
diffOriginalEditor?.revealLineInCenter(
(change?.originalStartLineNumber || lineNumber) ?? 0
);
}
};
const toggleUnchangedRegions = () => {
if (!diffEditorInstance) return;
isCollapsed.value = !isCollapsed.value;
try {
diffEditorInstance.updateOptions({
hideUnchangedRegions: {
enabled: isCollapsed.value,
minimumLineCount: 3,
// 最小显示行数
contextLineCount: 3
// 上下文行数
}
});
} catch (error) {
}
};
const handleHeaderCopy = async () => {
try {
const modifiedModel = diffModifiedEditor?.getModel();
if (!modifiedModel) {
return;
}
const content = modifiedModel.getValue();
await navigator.clipboard.writeText(content);
} catch (error) {
try {
const modifiedModel = diffModifiedEditor?.getModel();
if (modifiedModel) {
const content = modifiedModel.getValue();
const textArea = document.createElement("textarea");
textArea.value = content;
document.body.appendChild(textArea);
textArea.select();
document.execCommand("copy");
document.body.removeChild(textArea);
}
} catch (fallbackError) {
}
}
};
const handleContextMenuItemClick = (item) => {
if (item.type === "item") {
item.action();
contextMenu.hide();
}
};
const handleMinimapContextMenuItemClick = (item) => {
if (item.type === "item") {
item.action();
minimapContextMenu.hide();
}
};
const isDarkTheme = computed(() => {
return props.currentTheme && props.currentTheme.toLowerCase().includes("dark");
});
const themeClasses = computed(() => ({
"theme-light": !isDarkTheme.value,
"theme-dark": isDarkTheme.value
}));
onBeforeUnmount(() => {
if (diffMonacoEditHook) {
diffEditorRef.value && diffMonacoEditHook.destroy(diffEditorRef.value);
}
});
__expose({
getEditor: () => diffEditorInstance,
focus: () => diffEditorInstance?.focus(),
setTheme: (theme) => diffMonacoEditHook?.setTheme(theme),
layout: () => diffMonacoEditHook?.layout(),
enableAutoResize: () => diffMonacoEditHook?.enableAutoResize(),
disableAutoResize: () => diffMonacoEditHook?.disableAutoResize()
});
return (_ctx, _cache) => {
return openBlock(), createElementBlock("div", {
class: normalizeClass(["monaco-editor-wrapper monaco-editor-diff-wrapper", [props.monacoEditClass, themeClasses.value]])
}, [
props.showToolbar ? (openBlock(), createBlock(MonacoHeader, {
key: 0,
"current-language": props.currentLanguage,
"file-name": props.fileName ?? "Untitled",
theme: props.currentTheme,
onCopy: handleHeaderCopy
}, {
toolbar: withCtx(() => [
createElementVNode("div", _hoisted_1, toDisplayString(totalDiffs.value > 0 ? `${currentDiffIndex.value + 1} / ${totalDiffs.value}` : "0 个差异"), 1),
createElementVNode("button", {
class: "toolbar-btn",
disabled: totalDiffs.value === 0,
onClick: goToPreviousDiff,
title: "上一个差异"
}, _cache[0] || (_cache[0] = [
createElementVNode("svg", {
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor"
}, [
createElementVNode("path", {
"stroke-linecap": "round",
"stroke-linejoin": "round",
d: "M15 18l-6-6 6-6"
})
], -1)
]), 8, _hoisted_2),
createElementVNode("button", {
class: "toolbar-btn",
disabled: totalDiffs.value === 0,
onClick: goToNextDiff,
title: "下一个差异"
}, _cache[1] || (_cache[1] = [
createElementVNode("svg", {
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor"
}, [
createElementVNode("path", {
"stroke-linecap": "round",
"stroke-linejoin": "round",
d: "M9 18l6-6-6-6"
})
], -1)
]), 8, _hoisted_3),
createElementVNode("button", {
class: "toolbar-btn",
onClick: toggleUnchangedRegions,
title: isCollapsed.value ? "展开未更改区域" : "折叠未更改区域"
}, [
isCollapsed.value ? (openBlock(), createElementBlock("svg", _hoisted_5, _cache[2] || (_cache[2] = [
createElementVNode("path", {
"stroke-linecap": "round",
"stroke-linejoin": "round",
d: "M19 14l-7-7-7 7"
}, null, -1)
]))) : (openBlock(), createElementBlock("svg", _hoisted_6, _cache[3] || (_cache[3] = [
createElementVNode("path", {
"stroke-linecap": "round",
"stroke-linejoin": "round",
d: "M5 10l7 7 7-7"
}, null, -1)
])))
], 8, _hoisted_4)
]),
_: 1
}, 8, ["current-language", "file-name", "theme"])) : createCommentVNode("", true),
createElementVNode("div", {
ref_key: "diffEditorRef",
ref: diffEditorRef,
class: normalizeClass(["monaco-editor monaco-diff-editor", { noHeader: !props.showToolbar }]),
style: normalizeStyle({ height: props.height })
}, null, 6),
diffEditorRef.value ? (openBlock(), createBlock(ContextMenu, {
key: 1,
visible: unref(contextMenu).isVisible.value,
position: unref(contextMenu).position,
items: contextMenuItems.value,
variant: props.contextMenu?.variant || "glass",
theme: props.currentTheme,
onItemClick: handleContextMenuItemClick,
onHide: unref(contextMenu).hide
}, null, 8, ["visible", "position", "items", "variant", "theme", "onHide"])) : createCommentVNode("", true),
diffEditorRef.value && props.minimapContextMenu?.enabled !== false ? (openBlock(), createBlock(ContextMenu, {
key: 2,
visible: unref(minimapContextMenu).isVisible.value,
position: unref(minimapContextMenu).position,
items: minimapContextMenuItems.value,
variant: props.minimapContextMenu?.variant || "glass",
theme: props.currentTheme,
onItemClick: handleMinimapContextMenuItemClick,
onHide: unref(minimapContextMenu).hide
}, null, 8, ["visible", "position", "items", "variant", "theme", "onHide"])) : createCommentVNode("", true)
], 2);
};
}
});
const MonacoDiff = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-030be5a9"]]);
export {
MonacoDiff as M,
useMonacoDiffEdit as u
};
//# sourceMappingURL=index-BdOUOi2k.js.map