UNPKG

vue-shiki-monaco

Version:
679 lines (678 loc) 23.8 kB
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