import {defineStore} from 'pinia'; import {computed, nextTick, ref, watch} from 'vue'; import {EditorView} from '@codemirror/view'; import {EditorState, Extension} from '@codemirror/state'; import {useConfigStore} from './configStore'; import {useDocumentStore} from './documentStore'; import {usePanelStore} from './panelStore'; import {ExtensionID} from '@/../bindings/voidraft/internal/models/models'; import {DocumentService, ExtensionService} from '@/../bindings/voidraft/internal/services'; import {ensureSyntaxTree} from "@codemirror/language"; import {createBasicSetup} from '@/views/editor/basic/basicSetup'; import {createThemeExtension, updateEditorTheme} from '@/views/editor/basic/themeExtension'; import {getTabExtensions, updateTabConfig} from '@/views/editor/basic/tabExtension'; import {createFontExtensionFromBackend, updateFontConfig} from '@/views/editor/basic/fontExtension'; import {createStatsUpdateExtension} from '@/views/editor/basic/statsExtension'; import {createContentChangePlugin} from '@/views/editor/basic/contentChangeExtension'; import {createWheelZoomExtension} from '@/views/editor/basic/wheelZoomExtension'; import {createDynamicKeymapExtension, updateKeymapExtension} from '@/views/editor/keymap'; import { createDynamicExtensions, getExtensionManager, removeExtensionManagerView, setExtensionManagerView } from '@/views/editor/manager'; import {useExtensionStore} from './extensionStore'; import createCodeBlockExtension, {blockState} from "@/views/editor/extensions/codeblock"; import {LruCache} from '@/common/utils/lruCache'; import {AsyncManager} from '@/common/utils/asyncManager'; import {generateContentHash} from "@/common/utils/hashUtils"; import {createTimerManager, type TimerManager} from '@/common/utils/timerUtils'; import {EDITOR_CONFIG} from '@/common/constant/editor'; import {createHttpClientExtension} from "@/views/editor/extensions/httpclient"; import {markdownPreviewExtension, updateMarkdownPreviewTheme} from "@/views/editor/extensions/markdownPreview"; import {createDebounce} from '@/common/utils/debounce'; export interface DocumentStats { lines: number; characters: number; selectedCharacters: number; } // 修复:只保存光标位置,恢复时自动滚动到光标处(更简单可靠) export interface EditorViewState { cursorPos: number; } interface EditorInstance { view: EditorView; documentId: number; content: string; isDirty: boolean; lastModified: Date; autoSaveTimer: TimerManager; syntaxTreeCache: { lastDocLength: number; lastContentHash: string; lastParsed: Date; } | null; // 修复:使用统一的类型,可选但不是 undefined | {...} editorState?: EditorViewState; } export const useEditorStore = defineStore('editor', () => { // === 依赖store === const configStore = useConfigStore(); const documentStore = useDocumentStore(); const extensionStore = useExtensionStore(); // === 核心状态 === const editorCache = new LruCache(EDITOR_CONFIG.MAX_INSTANCES); const containerElement = ref(null); const currentEditor = ref(null); const documentStats = ref({ lines: 0, characters: 0, selectedCharacters: 0 }); // 编辑器加载状态 const isLoading = ref(false); // 修复:使用操作计数器精确管理加载状态 const loadingOperations = ref(0); // 异步操作管理器 const operationManager = new AsyncManager(); // 自动保存设置 - 从配置动态获取 const getAutoSaveDelay = () => configStore.config.editing.autoSaveDelay; // 创建防抖的语法树缓存清理函数 const debouncedClearSyntaxCache = createDebounce((instance) => { if (instance) { instance.syntaxTreeCache = null; } }, { delay: 500 }); // 500ms 内的多次输入只清理一次 // === 私有方法 === /** * 检查位置是否在代码块分隔符区域内 */ const isPositionInDelimiter = (view: EditorView, pos: number): boolean => { try { const blocks = view.state.field(blockState, false); if (!blocks) return false; for (const block of blocks) { if (pos >= block.delimiter.from && pos < block.delimiter.to) { return true; } } return false; } catch { return false; } }; /** * 调整光标位置到有效的内容区域 * 如果位置在分隔符内,移动到该块的内容开始位置 */ const adjustCursorPosition = (view: EditorView, pos: number): number => { try { const blocks = view.state.field(blockState, false); if (!blocks || blocks.length === 0) return pos; // 如果位置在分隔符内,移动到该块的内容开始位置 for (const block of blocks) { if (pos >= block.delimiter.from && pos < block.delimiter.to) { return block.content.from; } } return pos; } catch { return pos; } }; /** * 恢复编辑器的光标位置(自动滚动到光标处) */ const restoreEditorState = (instance: EditorInstance, documentId: number): void => { const savedState = instance.editorState; if (savedState) { // 有保存的状态,恢复光标位置 let pos = Math.min(savedState.cursorPos, instance.view.state.doc.length); // 确保位置不在分隔符上 if (isPositionInDelimiter(instance.view, pos)) { pos = adjustCursorPosition(instance.view, pos); } // 修复:设置光标位置并居中滚动(更好的用户体验) instance.view.dispatch({ selection: {anchor: pos, head: pos}, effects: EditorView.scrollIntoView(pos, { y: "center", // 垂直居中显示 yMargin: 100 // 上下留一些边距 }) }); } else { // 首次打开或没有记录,光标在文档末尾 const docLength = instance.view.state.doc.length; instance.view.dispatch({ selection: {anchor: docLength, head: docLength}, effects: EditorView.scrollIntoView(docLength, { y: "center", yMargin: 100 }) }); } }; // 缓存化的语法树确保方法 const ensureSyntaxTreeCached = (view: EditorView, documentId: number): void => { const instance = editorCache.get(documentId); if (!instance) return; const docLength = view.state.doc.length; const content = view.state.doc.toString(); const contentHash = generateContentHash(content); const now = new Date(); // 检查是否需要重新构建语法树 const cache = instance.syntaxTreeCache; const shouldRebuild = !cache || cache.lastDocLength !== docLength || cache.lastContentHash !== contentHash || (now.getTime() - cache.lastParsed.getTime()) > EDITOR_CONFIG.SYNTAX_TREE_CACHE_TIMEOUT; if (shouldRebuild) { try { ensureSyntaxTree(view.state, docLength, 5000); // 更新缓存 instance.syntaxTreeCache = { lastDocLength: docLength, lastContentHash: contentHash, lastParsed: now }; } catch (error) { console.warn('Failed to ensure syntax tree:', error); } } }; // 创建编辑器实例 const createEditorInstance = async ( content: string, operationId: number, documentId: number ): Promise => { if (!containerElement.value) { throw new Error('Editor container not set'); } // 检查操作是否仍然有效 if (!operationManager.isOperationValid(operationId, documentId)) { throw new Error('Operation cancelled'); } // 获取基本扩展 const basicExtensions = createBasicSetup(); // 获取主题扩展 const themeExtension = createThemeExtension(); // Tab相关扩展 const tabExtensions = getTabExtensions( configStore.config.editing.tabSize, configStore.config.editing.enableTabIndent, configStore.config.editing.tabType ); // 字体扩展 const fontExtension = createFontExtensionFromBackend({ fontFamily: configStore.config.editing.fontFamily, fontSize: configStore.config.editing.fontSize, lineHeight: configStore.config.editing.lineHeight, fontWeight: configStore.config.editing.fontWeight }); const wheelZoomExtension = createWheelZoomExtension( () => configStore.increaseFontSize(), () => configStore.decreaseFontSize() ); // 统计扩展 const statsExtension = createStatsUpdateExtension(updateDocumentStats); // 内容变化扩展 const contentChangeExtension = createContentChangePlugin(); // 代码块扩展 const codeBlockExtension = createCodeBlockExtension({ showBackground: true, enableAutoDetection: true }); const httpExtension = createHttpClientExtension(); // Markdown预览扩展 const previewExtension = markdownPreviewExtension(); // 再次检查操作有效性 if (!operationManager.isOperationValid(operationId, documentId)) { throw new Error('Operation cancelled'); } // 快捷键扩展 const keymapExtension = await createDynamicKeymapExtension(); // 检查操作有效性 if (!operationManager.isOperationValid(operationId, documentId)) { throw new Error('Operation cancelled'); } // 动态扩展,传递文档ID以便扩展管理器可以预初始化 const dynamicExtensions = await createDynamicExtensions(documentId); // 最终检查操作有效性 if (!operationManager.isOperationValid(operationId, documentId)) { throw new Error('Operation cancelled'); } // 组合所有扩展 const extensions: Extension[] = [ keymapExtension, ...basicExtensions, themeExtension, ...tabExtensions, fontExtension, wheelZoomExtension, statsExtension, contentChangeExtension, codeBlockExtension, ...dynamicExtensions, ...httpExtension, previewExtension ]; // 创建编辑器状态 const state = EditorState.create({ doc: content, extensions }); return new EditorView({ state }); }; // 添加编辑器到缓存 const addEditorToCache = (documentId: number, view: EditorView, content: string) => { const instance: EditorInstance = { view, documentId, content, isDirty: false, lastModified: new Date(), autoSaveTimer: createTimerManager(), syntaxTreeCache: null, // 修复:创建实例时从 documentStore 读取持久化的编辑器状态 editorState: documentStore.documentStates[documentId] }; // 使用LRU缓存的onEvict回调处理被驱逐的实例 editorCache.set(documentId, instance, (_evictedKey, evictedInstance) => { // 清除自动保存定时器 evictedInstance.autoSaveTimer.clear(); // 移除DOM元素 if (evictedInstance.view.dom.parentElement) { evictedInstance.view.dom.remove(); } evictedInstance.view.destroy(); }); // 初始化语法树缓存 ensureSyntaxTreeCached(view, documentId); }; // 获取或创建编辑器 const getOrCreateEditor = async ( documentId: number, content: string, operationId: number ): Promise => { // 检查缓存 const cached = editorCache.get(documentId); if (cached) { return cached.view; } // 检查操作是否仍然有效 if (!operationManager.isOperationValid(operationId, documentId)) { throw new Error('Operation cancelled'); } // 创建新的编辑器实例 const view = await createEditorInstance(content, operationId, documentId); // 完善取消操作时的清理逻辑 if (!operationManager.isOperationValid(operationId, documentId)) { // 如果操作已取消,彻底清理创建的实例 try { // 移除 DOM 元素(如果已添加到文档) if (view.dom && view.dom.parentElement) { view.dom.remove(); } // 销毁编辑器视图 view.destroy(); } catch (error) { console.error('Error cleaning up cancelled editor:', error); } throw new Error('Operation cancelled'); } addEditorToCache(documentId, view, content); return view; }; // 显示编辑器 const showEditor = (documentId: number) => { const instance = editorCache.get(documentId); if (!instance || !containerElement.value) return; try { // 移除当前编辑器DOM if (currentEditor.value && currentEditor.value.dom && currentEditor.value.dom.parentElement) { currentEditor.value.dom.remove(); } // 将目标编辑器DOM添加到容器 containerElement.value.appendChild(instance.view.dom); currentEditor.value = instance.view; // 设置扩展管理器视图 setExtensionManagerView(instance.view, documentId); //使用 nextTick + requestAnimationFrame 确保 DOM 完全渲染 nextTick(() => { requestAnimationFrame(() => { // 恢复编辑器状态(光标位置和滚动位置) restoreEditorState(instance, documentId); // 聚焦编辑器 instance.view.focus(); // 使用缓存的语法树确保方法 ensureSyntaxTreeCached(instance.view, documentId); }); }); } catch (error) { console.error('Error showing editor:', error); } }; // 保存编辑器内容 const saveEditorContent = async (documentId: number): Promise => { const instance = editorCache.get(documentId); if (!instance || !instance.isDirty) return true; try { const content = instance.view.state.doc.toString(); const lastModified = instance.lastModified; await DocumentService.UpdateDocumentContent(documentId, content); // 检查在保存期间内容是否又被修改了 if (instance.lastModified === lastModified) { instance.content = content; instance.isDirty = false; instance.lastModified = new Date(); } // 如果内容在保存期间被修改了,保持 isDirty 状态 return true; } catch (error) { console.error('Failed to save editor content:', error); return false; } }; // 内容变化处理 const onContentChange = () => { const documentId = documentStore.currentDocumentId; if (!documentId) return; const instance = editorCache.get(documentId); if (!instance) return; // 立即设置脏标记和修改时间(切换文档时需要判断) instance.isDirty = true; instance.lastModified = new Date(); // 优使用防抖清理语法树缓存 debouncedClearSyntaxCache.debouncedFn(instance); // 设置自动保存定时器(已经是防抖效果:每次重置定时器) instance.autoSaveTimer.set(() => { saveEditorContent(documentId); }, getAutoSaveDelay()); }; // === 公共API === // 设置编辑器容器 const setEditorContainer = (container: HTMLElement | null) => { containerElement.value = container; // 如果设置容器时已有当前文档,立即加载编辑器 if (container && documentStore.currentDocument) { loadEditor(documentStore.currentDocument.id, documentStore.currentDocument.content); } }; // 加载编辑器 const loadEditor = async (documentId: number, content: string) => { // 修复:使用计数器精确管理加载状态 loadingOperations.value++; isLoading.value = true; // 开始新的操作 const { operationId } = operationManager.startOperation(documentId); try { // 验证参数 if (!documentId) { throw new Error('Invalid parameters for loadEditor'); } // 保存当前编辑器内容 if (currentEditor.value) { const currentDocId = documentStore.currentDocumentId; if (currentDocId && currentDocId !== documentId) { await saveEditorContent(currentDocId); // 检查操作是否仍然有效 if (!operationManager.isOperationValid(operationId, documentId)) { return; } } } // 获取或创建编辑器 const view = await getOrCreateEditor(documentId, content, operationId); // 检查操作是否仍然有效 if (!operationManager.isOperationValid(operationId, documentId)) { return; } // 更新内容(如果需要) const instance = editorCache.get(documentId); if (instance && instance.content !== content) { // 确保编辑器视图有效 if (view && view.state && view.dispatch) { view.dispatch({ changes: { from: 0, to: view.state.doc.length, insert: content } }); instance.content = content; instance.isDirty = false; // 清理语法树缓存,因为内容已更新 instance.syntaxTreeCache = null; // 修复:内容变了,清空光标位置,避免越界 instance.editorState = undefined; delete documentStore.documentStates[documentId]; } } // 最终检查操作有效性 if (!operationManager.isOperationValid(operationId, documentId)) { return; } // 显示编辑器 showEditor(documentId); } catch (error) { if (error instanceof Error && error.message === 'Operation cancelled') { console.log(`Editor loading cancelled for document ${documentId}`); } else { console.error('Failed to load editor:', error); } } finally { // 完成操作 operationManager.completeOperation(operationId); // 修复:使用计数器精确管理加载状态,避免快速切换时状态不准确 loadingOperations.value--; // 延迟一段时间后再取消加载状态,但要确保所有操作都完成了 setTimeout(() => { if (loadingOperations.value <= 0) { loadingOperations.value = 0; isLoading.value = false; } }, EDITOR_CONFIG.LOADING_DELAY); } }; // 移除编辑器 const removeEditor = async (documentId: number) => { const instance = editorCache.get(documentId); if (instance) { try { // 如果正在加载这个文档,取消操作 if (operationManager.getCurrentContext() === documentId) { operationManager.cancelAllOperations(); } // 修复:移除前先保存内容(如果有未保存的修改) if (instance.isDirty) { await saveEditorContent(documentId); } // 保存光标位置 if (instance.view && instance.view.state) { const currentState: EditorViewState = { cursorPos: instance.view.state.selection.main.head }; // 保存到 documentStore 用于持久化 documentStore.documentStates[documentId] = currentState; } // 清除自动保存定时器 instance.autoSaveTimer.clear(); // 从扩展管理器中移除视图 removeExtensionManagerView(documentId); // 移除DOM元素 if (instance.view && instance.view.dom && instance.view.dom.parentElement) { instance.view.dom.remove(); } // 销毁编辑器 if (instance.view && instance.view.destroy) { instance.view.destroy(); } // 清理引用 if (currentEditor.value === instance.view) { currentEditor.value = null; } // 从缓存中删除 editorCache.delete(documentId); } catch (error) { console.error('Error removing editor:', error); } } }; // 更新文档统计 const updateDocumentStats = (stats: DocumentStats) => { documentStats.value = stats; }; // 应用字体设置 const applyFontSettings = () => { editorCache.values().forEach(instance => { updateFontConfig(instance.view, { fontFamily: configStore.config.editing.fontFamily, fontSize: configStore.config.editing.fontSize, lineHeight: configStore.config.editing.lineHeight, fontWeight: configStore.config.editing.fontWeight }); }); }; // 应用主题设置 const applyThemeSettings = () => { editorCache.values().forEach(instance => { updateEditorTheme(instance.view); }); }; // 应用 Markdown 预览主题 const applyPreviewThemeSettings = () => { editorCache.values().forEach(instance => { updateMarkdownPreviewTheme(instance.view); }); }; // 应用Tab设置 const applyTabSettings = () => { editorCache.values().forEach(instance => { updateTabConfig( instance.view, configStore.config.editing.tabSize, configStore.config.editing.enableTabIndent, configStore.config.editing.tabType ); }); }; // 应用快捷键设置 const applyKeymapSettings = async () => { // 确保所有编辑器实例的快捷键都更新 await Promise.all( editorCache.values().map(instance => updateKeymapExtension(instance.view) ) ); }; // 清空所有编辑器 const clearAllEditors = () => { // 取消所有挂起的操作 operationManager.cancelAllOperations(); editorCache.clear((_documentId, instance) => { // 修复:清空前只保存光标位置 if (instance.view) { const currentState: EditorViewState = { cursorPos: instance.view.state.selection.main.head }; // 同时保存到实例和 documentStore instance.editorState = currentState; documentStore.documentStates[instance.documentId] = currentState; } // 清除自动保存定时器 instance.autoSaveTimer.clear(); // 从扩展管理器移除 removeExtensionManagerView(instance.documentId); // 移除DOM元素 if (instance.view.dom.parentElement) { instance.view.dom.remove(); } // 销毁编辑器 instance.view.destroy(); }); // 清理 panelStore 状态(导航离开编辑器页面时) const panelStore = usePanelStore(); panelStore.reset(); currentEditor.value = null; }; // 更新扩展 const updateExtension = async (id: ExtensionID, enabled: boolean, config?: any) => { // 如果只是更新启用状态 if (config === undefined) { await ExtensionService.UpdateExtensionEnabled(id, enabled); } else { // 如果需要更新配置 await ExtensionService.UpdateExtensionState(id, enabled, config); } // 更新前端编辑器扩展 - 应用于所有实例 const manager = getExtensionManager(); if (manager) { // 直接更新前端扩展至所有视图 manager.updateExtension(id, enabled, config); } // 重新加载扩展配置 await extensionStore.loadExtensions(); if (manager) { manager.initExtensions(extensionStore.extensions); } await applyKeymapSettings(); }; // 监听文档切换 watch(() => documentStore.currentDocument, async (newDoc, oldDoc) => { if (newDoc && containerElement.value) { // 修复:在切换到新文档前,只保存旧文档的光标位置 if (oldDoc && oldDoc.id !== newDoc.id && currentEditor.value) { const oldInstance = editorCache.get(oldDoc.id); if (oldInstance) { const currentState: EditorViewState = { cursorPos: currentEditor.value.state.selection.main.head }; // 同时保存到实例和 documentStore oldInstance.editorState = currentState; documentStore.documentStates[oldDoc.id] = currentState; } } // 等待 DOM 更新完成,再加载新文档的编辑器 await nextTick(); loadEditor(newDoc.id, newDoc.content); } }); // 创建字体配置的计算属性 const fontConfig = computed(() => ({ fontSize: configStore.config.editing.fontSize, fontFamily: configStore.config.editing.fontFamily, lineHeight: configStore.config.editing.lineHeight, fontWeight: configStore.config.editing.fontWeight })); // 创建Tab配置的计算属性 const tabConfig = computed(() => ({ tabSize: configStore.config.editing.tabSize, enableTabIndent: configStore.config.editing.enableTabIndent, tabType: configStore.config.editing.tabType })); // 监听字体配置变化 watch(fontConfig, applyFontSettings, { deep: true }); // 监听Tab配置变化 watch(tabConfig, applyTabSettings, { deep: true }); return { // 状态 currentEditor, documentStats, isLoading, // 方法 setEditorContainer, loadEditor, removeEditor, clearAllEditors, onContentChange, // 配置更新方法 applyFontSettings, applyThemeSettings, applyPreviewThemeSettings, applyTabSettings, applyKeymapSettings, // 扩展管理方法 updateExtension, editorView: currentEditor, }; });