Files
voidraft/frontend/src/stores/editorCacheStore.ts

271 lines
6.9 KiB
TypeScript

import { defineStore } from 'pinia';
import { computed, shallowRef, type ShallowRef } from 'vue';
import { EditorView } from '@codemirror/view';
import { ensureSyntaxTree } from '@codemirror/language';
import { LruCache, type CacheItem, type DisposableCacheItem, createHash } from '@/common/cache';
import { removeExtensionManagerView } from '@/views/editor/manager';
/** 语法树缓存信息 */
interface SyntaxTreeCache {
readonly lastDocLength: number;
readonly lastContentHash: string;
readonly lastParsed: Date;
}
/** 编辑器状态 */
interface EditorState {
content: string;
isDirty: boolean;
lastModified: Date;
}
/** 编辑器缓存项 */
export interface EditorCacheItem extends CacheItem, DisposableCacheItem {
readonly view: EditorView;
readonly documentId: number;
state: EditorState;
autoSaveTimer: number | null;
syntaxTreeCache: SyntaxTreeCache | null;
}
// === 缓存配置 ===
const CACHE_CONFIG = {
maxSize: 5,
syntaxTreeExpireTime: 30000, // 30秒
} as const;
export const useEditorCacheStore = defineStore('editorCache', () => {
// === 状态 ===
const containerElement: ShallowRef<HTMLElement | null> = shallowRef(null);
// === 内部方法 ===
const cleanupEditor = (item: EditorCacheItem): void => {
try {
// 清除自动保存定时器
if (item.autoSaveTimer) {
clearTimeout(item.autoSaveTimer);
item.autoSaveTimer = null;
}
// 从扩展管理器中移除视图
removeExtensionManagerView(item.documentId);
// 移除DOM元素
item.view.dom?.remove();
// 销毁编辑器
item.view.destroy?.();
} catch (error) {
console.error(`Failed to cleanup editor ${item.documentId}:`, error);
}
};
const createEditorCacheItem = (
documentId: number,
view: EditorView,
content: string
): EditorCacheItem => {
const now = new Date();
const item: EditorCacheItem = {
id: documentId,
lastAccessed: now,
createdAt: now,
view,
documentId,
state: {
content,
isDirty: false,
lastModified: now
},
autoSaveTimer: null,
syntaxTreeCache: null,
dispose: () => cleanupEditor(item)
};
return item;
};
const shouldRebuildSyntaxTree = (
item: EditorCacheItem,
docLength: number,
contentHash: string
): boolean => {
const { syntaxTreeCache } = item;
if (!syntaxTreeCache) return true;
const now = Date.now();
const isExpired = (now - syntaxTreeCache.lastParsed.getTime()) > CACHE_CONFIG.syntaxTreeExpireTime;
const isContentChanged = syntaxTreeCache.lastDocLength !== docLength ||
syntaxTreeCache.lastContentHash !== contentHash;
return isExpired || isContentChanged;
};
const buildSyntaxTree = (view: EditorView, item: EditorCacheItem): void => {
const docLength = view.state.doc.length;
const content = view.state.doc.toString();
const contentHash = createHash(content);
if (!shouldRebuildSyntaxTree(item, docLength, contentHash)) {
return;
}
try {
ensureSyntaxTree(view.state, docLength, 5000);
item.syntaxTreeCache = {
lastDocLength: docLength,
lastContentHash: contentHash,
lastParsed: new Date()
};
} catch (error) {
console.warn(`Failed to build syntax tree for editor ${item.documentId}:`, error);
}
};
// === 缓存实例 ===
const cache = new LruCache<EditorCacheItem>({
maxSize: CACHE_CONFIG.maxSize,
onEvict: cleanupEditor
});
// === 计算属性 ===
const cacheSize = computed(() => cache.size());
const cacheStats = computed(() => cache.getStats());
const allEditors = computed(() => cache.getAll());
const dirtyEditors = computed(() =>
allEditors.value.filter(item => item.state.isDirty)
);
// === 公共方法 ===
// 容器管理
const setContainer = (element: HTMLElement | null): void => {
containerElement.value = element;
};
const getContainer = (): HTMLElement | null => containerElement.value;
// 基础缓存操作
const addEditor = (documentId: number, view: EditorView, content: string): void => {
const item = createEditorCacheItem(documentId, view, content);
cache.set(documentId, item);
// 初始化语法树缓存
buildSyntaxTree(view, item);
};
const getEditor = (documentId: number): EditorCacheItem | null => {
return cache.get(documentId);
};
const hasEditor = (documentId: number): boolean => {
return cache.has(documentId);
};
const removeEditor = (documentId: number): boolean => {
return cache.remove(documentId);
};
const clearAll = (): void => {
cache.clear();
};
// 编辑器状态管理
const updateEditorContent = (documentId: number, content: string): boolean => {
const item = cache.get(documentId);
if (!item) return false;
item.state.content = content;
item.state.isDirty = false;
item.state.lastModified = new Date();
item.syntaxTreeCache = null; // 清理语法树缓存
return true;
};
const markEditorDirty = (documentId: number): boolean => {
const item = cache.get(documentId);
if (!item) return false;
item.state.isDirty = true;
item.state.lastModified = new Date();
item.syntaxTreeCache = null; // 清理语法树缓存
return true;
};
// 自动保存管理
const setAutoSaveTimer = (documentId: number, timer: number): boolean => {
const item = cache.get(documentId);
if (!item) return false;
// 清除之前的定时器
if (item.autoSaveTimer) {
clearTimeout(item.autoSaveTimer);
}
item.autoSaveTimer = timer;
return true;
};
const clearAutoSaveTimer = (documentId: number): boolean => {
const item = cache.get(documentId);
if (!item || !item.autoSaveTimer) return false;
clearTimeout(item.autoSaveTimer);
item.autoSaveTimer = null;
return true;
};
// 语法树管理
const ensureSyntaxTreeCached = (view: EditorView, documentId: number): void => {
const item = cache.get(documentId);
if (!item) return;
buildSyntaxTree(view, item);
};
const cleanupExpiredSyntaxTrees = (): void => {
const now = Date.now();
allEditors.value.forEach(item => {
if (item.syntaxTreeCache &&
(now - item.syntaxTreeCache.lastParsed.getTime()) > CACHE_CONFIG.syntaxTreeExpireTime) {
item.syntaxTreeCache = null;
}
});
};
return {
// 容器管理
setContainer,
getContainer,
// 基础缓存操作
addEditor,
getEditor,
hasEditor,
removeEditor,
clearAll,
// 编辑器状态管理
updateEditorContent,
markEditorDirty,
// 自动保存管理
setAutoSaveTimer,
clearAutoSaveTimer,
// 语法树管理
ensureSyntaxTreeCached,
cleanupExpiredSyntaxTrees,
// 计算属性
cacheSize,
cacheStats,
allEditors,
dirtyEditors
};
});