Performance optimization

This commit is contained in:
2025-07-01 20:11:27 +08:00
parent 1604564e63
commit 3e45e6aa9b
2 changed files with 230 additions and 53 deletions

View File

@@ -296,7 +296,7 @@ onUnmounted(() => {
<!-- 选择器按钮 --> <!-- 选择器按钮 -->
<button class="doc-btn" @click="toggleMenu"> <button class="doc-btn" @click="toggleMenu">
<span class="doc-name">{{ currentDocName }}</span> <span class="doc-name">{{ currentDocName }}</span>
<span class="arrow" :class="{ open: showMenu }"></span> <span class="arrow" :class="{ open: showMenu }"></span>
</button> </button>
<!-- 菜单 --> <!-- 菜单 -->
@@ -432,7 +432,7 @@ onUnmounted(() => {
margin-left: 2px; margin-left: 2px;
transition: transform 0.2s ease; transition: transform 0.2s ease;
&.open { &:not(.open) {
transform: rotate(180deg); transform: rotate(180deg);
} }
} }

View File

@@ -1,13 +1,13 @@
import {defineStore} from 'pinia'; import {defineStore} from 'pinia';
import {ref, watch, nextTick} from 'vue'; import {nextTick, ref, watch} from 'vue';
import {EditorView} from '@codemirror/view'; import {EditorView} from '@codemirror/view';
import {EditorState, Extension} from '@codemirror/state'; import {EditorState, Extension} from '@codemirror/state';
import {useConfigStore} from './configStore'; import {useConfigStore} from './configStore';
import {useDocumentStore} from './documentStore'; import {useDocumentStore} from './documentStore';
import {useThemeStore} from './themeStore'; import {useThemeStore} from './themeStore';
import {SystemThemeType, ExtensionID} from '@/../bindings/voidraft/internal/models/models'; import {ExtensionID, SystemThemeType} from '@/../bindings/voidraft/internal/models/models';
import {DocumentService} from '@/../bindings/voidraft/internal/services'; import {DocumentService, ExtensionService} from '@/../bindings/voidraft/internal/services';
import {ensureSyntaxTree} from "@codemirror/language" import {ensureSyntaxTree} from "@codemirror/language";
import {createBasicSetup} from '@/views/editor/basic/basicSetup'; import {createBasicSetup} from '@/views/editor/basic/basicSetup';
import {createThemeExtension, updateEditorTheme} from '@/views/editor/basic/themeExtension'; import {createThemeExtension, updateEditorTheme} from '@/views/editor/basic/themeExtension';
import {getTabExtensions, updateTabConfig} from '@/views/editor/basic/tabExtension'; import {getTabExtensions, updateTabConfig} from '@/views/editor/basic/tabExtension';
@@ -17,7 +17,6 @@ import {createContentChangePlugin} from '@/views/editor/basic/contentChangeExten
import {createDynamicKeymapExtension, updateKeymapExtension} from '@/views/editor/keymap'; import {createDynamicKeymapExtension, updateKeymapExtension} from '@/views/editor/keymap';
import {createDynamicExtensions, getExtensionManager, setExtensionManagerView} from '@/views/editor/manager'; import {createDynamicExtensions, getExtensionManager, setExtensionManagerView} from '@/views/editor/manager';
import {useExtensionStore} from './extensionStore'; import {useExtensionStore} from './extensionStore';
import {ExtensionService} from '@/../bindings/voidraft/internal/services';
import createCodeBlockExtension from "@/views/editor/extensions/codeblock"; import createCodeBlockExtension from "@/views/editor/extensions/codeblock";
const NUM_EDITOR_INSTANCES = 5; // 最多缓存5个编辑器实例 const NUM_EDITOR_INSTANCES = 5; // 最多缓存5个编辑器实例
@@ -35,6 +34,11 @@ interface EditorInstance {
isDirty: boolean; isDirty: boolean;
lastModified: Date; lastModified: Date;
autoSaveTimer: number | null; autoSaveTimer: number | null;
syntaxTreeCache: {
lastDocLength: number;
lastContentHash: string;
lastParsed: Date;
} | null;
} }
export const useEditorStore = defineStore('editor', () => { export const useEditorStore = defineStore('editor', () => {
@@ -62,17 +66,97 @@ export const useEditorStore = defineStore('editor', () => {
selectedCharacters: 0 selectedCharacters: 0
}); });
// 异步操作竞态条件控制
const operationSequence = ref(0);
const pendingOperations = ref(new Map<number, AbortController>());
const currentLoadingDocumentId = ref<number | null>(null);
// 自动保存设置 - 从配置动态获取 // 自动保存设置 - 从配置动态获取
const getAutoSaveDelay = () => configStore.config.editing.autoSaveDelay; const getAutoSaveDelay = () => configStore.config.editing.autoSaveDelay;
// 生成新的操作序列号
const getNextOperationId = () => ++operationSequence.value;
// 取消之前的操作
const cancelPreviousOperations = (excludeId?: number) => {
pendingOperations.value.forEach((controller, id) => {
if (id !== excludeId) {
controller.abort();
pendingOperations.value.delete(id);
}
});
};
// 检查操作是否仍然有效
const isOperationValid = (operationId: number, documentId: number) => {
return (
pendingOperations.value.has(operationId) &&
!pendingOperations.value.get(operationId)?.signal.aborted &&
currentLoadingDocumentId.value === documentId
);
};
// === 私有方法 === // === 私有方法 ===
// 生成内容哈希
const generateContentHash = (content: string): string => {
let hash = 0;
for (let i = 0; i < content.length; i++) {
const char = content.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32bit integer
}
return hash.toString();
};
// 缓存化的语法树确保方法
const ensureSyntaxTreeCached = (view: EditorView, documentId: number): void => {
const instance = editorCache.value.instances[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()) > 30000; // 30秒过期
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): Promise<EditorView> => { const createEditorInstance = async (
content: string,
operationId: number,
documentId: number
): Promise<EditorView> => {
if (!editorCache.value.containerElement) { if (!editorCache.value.containerElement) {
throw new Error('Editor container not set'); throw new Error('Editor container not set');
} }
// 检查操作是否仍然有效
if (!isOperationValid(operationId, documentId)) {
throw new Error('Operation cancelled');
}
// 获取基本扩展 // 获取基本扩展
const basicExtensions = createBasicSetup(); const basicExtensions = createBasicSetup();
@@ -108,12 +192,27 @@ export const useEditorStore = defineStore('editor', () => {
enableAutoDetection: true enableAutoDetection: true
}); });
// 快捷键扩展 // 再次检查操作有效性
if (!isOperationValid(operationId, documentId)) {
throw new Error('Operation cancelled');
}
// 快捷键扩展(异步)
const keymapExtension = await createDynamicKeymapExtension(); const keymapExtension = await createDynamicKeymapExtension();
// 动态扩展 // 检查操作有效性
if (!isOperationValid(operationId, documentId)) {
throw new Error('Operation cancelled');
}
// 动态扩展(异步)
const dynamicExtensions = await createDynamicExtensions(); const dynamicExtensions = await createDynamicExtensions();
// 最终检查操作有效性
if (!isOperationValid(operationId, documentId)) {
throw new Error('Operation cancelled');
}
// 组合所有扩展 // 组合所有扩展
const extensions: Extension[] = [ const extensions: Extension[] = [
keymapExtension, keymapExtension,
@@ -138,13 +237,11 @@ export const useEditorStore = defineStore('editor', () => {
state state
}); });
// 初始化语法树 // 将光标定位到文档末尾并滚动到该位置
ensureSyntaxTree(view.state, view.state.doc.length, 5000);
// 将光标定位到文档末尾
const docLength = view.state.doc.length; const docLength = view.state.doc.length;
view.dispatch({ view.dispatch({
selection: { anchor: docLength, head: docLength } selection: {anchor: docLength, head: docLength},
scrollIntoView: true
}); });
return view; return view;
@@ -177,11 +274,15 @@ export const useEditorStore = defineStore('editor', () => {
content, content,
isDirty: false, isDirty: false,
lastModified: new Date(), lastModified: new Date(),
autoSaveTimer: null autoSaveTimer: null,
syntaxTreeCache: null
}; };
// 添加到LRU列表 // 添加到LRU列表
editorCache.value.lru.push(documentId); editorCache.value.lru.push(documentId);
// 初始化语法树缓存
ensureSyntaxTreeCached(view, documentId);
}; };
// 更新LRU // 更新LRU
@@ -195,7 +296,11 @@ export const useEditorStore = defineStore('editor', () => {
}; };
// 获取或创建编辑器 // 获取或创建编辑器
const getOrCreateEditor = async (documentId: number, content: string): Promise<EditorView> => { const getOrCreateEditor = async (
documentId: number,
content: string,
operationId: number
): Promise<EditorView> => {
// 检查缓存 // 检查缓存
const cached = editorCache.value.instances[documentId]; const cached = editorCache.value.instances[documentId];
if (cached) { if (cached) {
@@ -203,8 +308,21 @@ export const useEditorStore = defineStore('editor', () => {
return cached.view; return cached.view;
} }
// 检查操作是否仍然有效
if (!isOperationValid(operationId, documentId)) {
throw new Error('Operation cancelled');
}
// 创建新的编辑器实例 // 创建新的编辑器实例
const view = await createEditorInstance(content); const view = await createEditorInstance(content, operationId, documentId);
// 最终检查操作有效性
if (!isOperationValid(operationId, documentId)) {
// 如果操作已取消,清理创建的实例
view.destroy();
throw new Error('Operation cancelled');
}
addEditorToCache(documentId, view, content); addEditorToCache(documentId, view, content);
return view; return view;
@@ -236,13 +354,18 @@ export const useEditorStore = defineStore('editor', () => {
// 重新测量和聚焦编辑器 // 重新测量和聚焦编辑器
nextTick(() => { nextTick(() => {
instance.view.requestMeasure(); // 将光标定位到文档末尾并滚动到该位置
// 将光标定位到文档末尾
const docLength = instance.view.state.doc.length; const docLength = instance.view.state.doc.length;
instance.view.dispatch({ instance.view.dispatch({
selection: { anchor: docLength, head: docLength } selection: {anchor: docLength, head: docLength},
scrollIntoView: true
}); });
// 滚动到文档底部(将光标位置滚动到可见区域)
instance.view.focus(); instance.view.focus();
// 使用缓存的语法树确保方法
ensureSyntaxTreeCached(instance.view, documentId);
}); });
} catch (error) { } catch (error) {
console.error('Error showing editor:', error); console.error('Error showing editor:', error);
@@ -256,11 +379,17 @@ export const useEditorStore = defineStore('editor', () => {
try { try {
const content = instance.view.state.doc.toString(); const content = instance.view.state.doc.toString();
const lastModified = instance.lastModified;
await DocumentService.UpdateDocumentContent(documentId, content); await DocumentService.UpdateDocumentContent(documentId, content);
instance.content = content; // 检查在保存期间内容是否又被修改了
instance.isDirty = false; if (instance.lastModified === lastModified) {
instance.lastModified = new Date(); instance.content = content;
instance.isDirty = false;
instance.lastModified = new Date();
}
// 如果内容在保存期间被修改了,保持 isDirty 状态
return true; return true;
} catch (error) { } catch (error) {
@@ -276,6 +405,9 @@ export const useEditorStore = defineStore('editor', () => {
instance.isDirty = true; instance.isDirty = true;
instance.lastModified = new Date(); instance.lastModified = new Date();
// 清理语法树缓存,下次访问时重新构建
instance.syntaxTreeCache = null;
// 清除之前的定时器 // 清除之前的定时器
if (instance.autoSaveTimer) { if (instance.autoSaveTimer) {
@@ -293,7 +425,7 @@ export const useEditorStore = defineStore('editor', () => {
// 设置编辑器容器 // 设置编辑器容器
const setEditorContainer = (container: HTMLElement | null) => { const setEditorContainer = (container: HTMLElement | null) => {
editorCache.value.containerElement = container; editorCache.value.containerElement = container;
// 如果设置容器时已有当前文档,立即加载编辑器 // 如果设置容器时已有当前文档,立即加载编辑器
if (container && documentStore.currentDocument) { if (container && documentStore.currentDocument) {
loadEditor(documentStore.currentDocument.id, documentStore.currentDocument.content); loadEditor(documentStore.currentDocument.id, documentStore.currentDocument.content);
@@ -302,22 +434,41 @@ export const useEditorStore = defineStore('editor', () => {
// 加载编辑器 // 加载编辑器
const loadEditor = async (documentId: number, content: string) => { const loadEditor = async (documentId: number, content: string) => {
// 生成新的操作ID
const operationId = getNextOperationId();
const abortController = new AbortController();
try { try {
// 验证参数 // 验证参数
if (!documentId) { if (!documentId) {
throw new Error('Invalid parameters for loadEditor'); throw new Error('Invalid parameters for loadEditor');
} }
// 取消之前的操作并设置当前操作
cancelPreviousOperations();
currentLoadingDocumentId.value = documentId;
pendingOperations.value.set(operationId, abortController);
// 保存当前编辑器内容 // 保存当前编辑器内容
if (currentEditor.value) { if (currentEditor.value) {
const currentDocId = documentStore.currentDocumentId; const currentDocId = documentStore.currentDocumentId;
if (currentDocId && currentDocId !== documentId) { if (currentDocId && currentDocId !== documentId) {
await saveEditorContent(currentDocId); await saveEditorContent(currentDocId);
// 检查操作是否仍然有效
if (!isOperationValid(operationId, documentId)) {
return;
}
} }
} }
// 获取或创建编辑器 // 获取或创建编辑器
const view = await getOrCreateEditor(documentId, content); const view = await getOrCreateEditor(documentId, content, operationId);
// 检查操作是否仍然有效
if (!isOperationValid(operationId, documentId)) {
return;
}
// 更新内容(如果需要) // 更新内容(如果需要)
const instance = editorCache.value.instances[documentId]; const instance = editorCache.value.instances[documentId];
@@ -333,14 +484,31 @@ export const useEditorStore = defineStore('editor', () => {
}); });
instance.content = content; instance.content = content;
instance.isDirty = false; instance.isDirty = false;
// 清理语法树缓存,因为内容已更新
instance.syntaxTreeCache = null;
} }
} }
// 最终检查操作有效性
if (!isOperationValid(operationId, documentId)) {
return;
}
// 显示编辑器 // 显示编辑器
showEditor(documentId); showEditor(documentId);
} catch (error) { } catch (error) {
if (error instanceof Error && error.message === 'Operation cancelled') {
console.log(`Editor loading cancelled for document ${documentId}`);
return;
}
console.error('Failed to load editor:', error); console.error('Failed to load editor:', error);
} finally {
// 清理操作记录
pendingOperations.value.delete(operationId);
if (currentLoadingDocumentId.value === documentId) {
currentLoadingDocumentId.value = null;
}
} }
}; };
@@ -349,27 +517,33 @@ export const useEditorStore = defineStore('editor', () => {
const instance = editorCache.value.instances[documentId]; const instance = editorCache.value.instances[documentId];
if (instance) { if (instance) {
try { try {
// 如果正在加载这个文档,取消操作
if (currentLoadingDocumentId.value === documentId) {
cancelPreviousOperations();
currentLoadingDocumentId.value = null;
}
// 清除自动保存定时器 // 清除自动保存定时器
if (instance.autoSaveTimer) { if (instance.autoSaveTimer) {
clearTimeout(instance.autoSaveTimer); clearTimeout(instance.autoSaveTimer);
instance.autoSaveTimer = null; instance.autoSaveTimer = null;
} }
// 移除DOM元素 // 移除DOM元素
if (instance.view && instance.view.dom && instance.view.dom.parentElement) { if (instance.view && instance.view.dom && instance.view.dom.parentElement) {
instance.view.dom.remove(); instance.view.dom.remove();
} }
// 销毁编辑器 // 销毁编辑器
if (instance.view && instance.view.destroy) { if (instance.view && instance.view.destroy) {
instance.view.destroy(); instance.view.destroy();
} }
// 清理引用 // 清理引用
if (currentEditor.value === instance.view) { if (currentEditor.value === instance.view) {
currentEditor.value = null; currentEditor.value = null;
} }
delete editorCache.value.instances[documentId]; delete editorCache.value.instances[documentId];
const lruIndex = editorCache.value.lru.indexOf(documentId); const lruIndex = editorCache.value.lru.indexOf(documentId);
@@ -431,6 +605,10 @@ export const useEditorStore = defineStore('editor', () => {
// 清空所有编辑器 // 清空所有编辑器
const clearAllEditors = () => { const clearAllEditors = () => {
// 取消所有挂起的操作
cancelPreviousOperations();
currentLoadingDocumentId.value = null;
Object.values(editorCache.value.instances).forEach(instance => { Object.values(editorCache.value.instances).forEach(instance => {
// 清除自动保存定时器 // 清除自动保存定时器
if (instance.autoSaveTimer) { if (instance.autoSaveTimer) {
@@ -451,37 +629,36 @@ export const useEditorStore = defineStore('editor', () => {
// 更新扩展 // 更新扩展
const updateExtension = async (id: ExtensionID, enabled: boolean, config?: any) => { const updateExtension = async (id: ExtensionID, enabled: boolean, config?: any) => {
try { // 如果只是更新启用状态
// 如果只是更新启用状态 if (config === undefined) {
if (config === undefined) { await ExtensionService.UpdateExtensionEnabled(id, enabled);
await ExtensionService.UpdateExtensionEnabled(id, enabled); } else {
} else { // 如果需要更新配置
// 如果需要更新配置 await ExtensionService.UpdateExtensionState(id, enabled, config);
await ExtensionService.UpdateExtensionState(id, enabled, config); }
}
// 更新前端编辑器扩展 // 更新前端编辑器扩展
const manager = getExtensionManager(); const manager = getExtensionManager();
if (manager) { if (manager) {
manager.updateExtension(id, enabled, config || {}); manager.updateExtension(id, enabled, config || {});
} }
// 重新加载扩展配置 // 重新加载扩展配置
await extensionStore.loadExtensions(); await extensionStore.loadExtensions();
// 更新快捷键映射 // 更新快捷键映射
if (currentEditor.value) { if (currentEditor.value) {
updateKeymapExtension(currentEditor.value); updateKeymapExtension(currentEditor.value);
}
} catch (error) {
throw error;
} }
}; };
// 监听文档切换 // 监听文档切换
watch(() => documentStore.currentDocument, (newDoc) => { watch(() => documentStore.currentDocument, (newDoc) => {
if (newDoc && editorCache.value.containerElement) { if (newDoc && editorCache.value.containerElement) {
loadEditor(newDoc.id, newDoc.content); // 使用 nextTick 确保DOM更新完成后再加载编辑器
nextTick(() => {
loadEditor(newDoc.id, newDoc.content);
});
} }
}); });
@@ -512,7 +689,7 @@ export const useEditorStore = defineStore('editor', () => {
applyThemeSettings, applyThemeSettings,
applyTabSettings, applyTabSettings,
applyKeymapSettings, applyKeymapSettings,
// 扩展管理方法 // 扩展管理方法
updateExtension, updateExtension,