♻️ Refactor cursor position cache

This commit is contained in:
2025-12-17 00:12:59 +08:00
parent 8fce8bdca4
commit 8a10b8fe0f
4 changed files with 94 additions and 36 deletions

View File

@@ -70,12 +70,11 @@ export const useDocumentStore = defineStore('document', () => {
// 在新窗口中打开文档 // 在新窗口中打开文档
const openDocumentInNewWindow = async (docId: number): Promise<boolean> => { const openDocumentInNewWindow = async (docId: number): Promise<boolean> => {
try { try {
await OpenDocumentWindow(docId);
const tabStore = useTabStore(); const tabStore = useTabStore();
if (tabStore.isTabsEnabled && tabStore.hasTab(docId)) { if (tabStore.isTabsEnabled && tabStore.hasTab(docId)) {
tabStore.closeTab(docId); tabStore.closeTab(docId);
} }
await OpenDocumentWindow(docId);
return true; return true;
} catch (error) { } catch (error) {
console.error('Failed to open document in new window:', error); console.error('Failed to open document in new window:', error);

View File

@@ -13,6 +13,7 @@ import {createFontExtensionFromBackend, updateFontConfig} from '@/views/editor/b
import {createStatsUpdateExtension} from '@/views/editor/basic/statsExtension'; import {createStatsUpdateExtension} from '@/views/editor/basic/statsExtension';
import {createContentChangePlugin} from '@/views/editor/basic/contentChangeExtension'; import {createContentChangePlugin} from '@/views/editor/basic/contentChangeExtension';
import {createWheelZoomExtension} from '@/views/editor/basic/wheelZoomExtension'; import {createWheelZoomExtension} from '@/views/editor/basic/wheelZoomExtension';
import {createCursorPositionExtension, scrollToCursor} from '@/views/editor/basic/cursorPositionExtension';
import {createDynamicKeymapExtension, updateKeymapExtension} from '@/views/editor/keymap'; import {createDynamicKeymapExtension, updateKeymapExtension} from '@/views/editor/keymap';
import { import {
createDynamicExtensions, createDynamicExtensions,
@@ -21,7 +22,7 @@ import {
setExtensionManagerView setExtensionManagerView
} from '@/views/editor/manager'; } from '@/views/editor/manager';
import {useExtensionStore} from './extensionStore'; import {useExtensionStore} from './extensionStore';
import createCodeBlockExtension, {blockState} from "@/views/editor/extensions/codeblock"; import createCodeBlockExtension from "@/views/editor/extensions/codeblock";
import {LruCache} from '@/common/utils/lruCache'; import {LruCache} from '@/common/utils/lruCache';
import {AsyncManager} from '@/common/utils/asyncManager'; import {AsyncManager} from '@/common/utils/asyncManager';
import {generateContentHash} from "@/common/utils/hashUtils"; import {generateContentHash} from "@/common/utils/hashUtils";
@@ -35,7 +36,7 @@ export interface DocumentStats {
selectedCharacters: number; selectedCharacters: number;
} }
// 修复:只保存光标位置,恢复时自动滚动到光标处(更简单可靠) // 修复:只保存光标位置,恢复时自动滚动到光标处
export interface EditorViewState { export interface EditorViewState {
cursorPos: number; cursorPos: number;
} }
@@ -180,6 +181,9 @@ export const useEditorStore = defineStore('editor', () => {
enableAutoDetection: true enableAutoDetection: true
}); });
// 光标位置持久化扩展
const cursorPositionExtension = createCursorPositionExtension(documentId);
// 再次检查操作有效性 // 再次检查操作有效性
if (!operationManager.isOperationValid(operationId, documentId)) { if (!operationManager.isOperationValid(operationId, documentId)) {
throw new Error('Operation cancelled'); throw new Error('Operation cancelled');
@@ -212,13 +216,23 @@ export const useEditorStore = defineStore('editor', () => {
statsExtension, statsExtension,
contentChangeExtension, contentChangeExtension,
codeBlockExtension, codeBlockExtension,
cursorPositionExtension,
...dynamicExtensions, ...dynamicExtensions,
]; ];
// 创建编辑器状态 // 获取保存的光标位置
const savedState = documentStore.documentStates[documentId];
const docLength = content.length;
const initialCursorPos = savedState?.cursorPos !== undefined
? Math.min(savedState.cursorPos, docLength)
: docLength;
// 创建编辑器状态,设置初始光标位置
const state = EditorState.create({ const state = EditorState.create({
doc: content, doc: content,
extensions extensions,
selection: { anchor: initialCursorPos, head: initialCursorPos }
}); });
return new EditorView({ return new EditorView({
@@ -316,6 +330,9 @@ export const useEditorStore = defineStore('editor', () => {
//使用 nextTick + requestAnimationFrame 确保 DOM 完全渲染 //使用 nextTick + requestAnimationFrame 确保 DOM 完全渲染
nextTick(() => { nextTick(() => {
requestAnimationFrame(() => { requestAnimationFrame(() => {
// 滚动到当前光标位置
scrollToCursor(instance.view);
// 聚焦编辑器 // 聚焦编辑器
instance.view.focus(); instance.view.focus();
@@ -487,15 +504,6 @@ export const useEditorStore = defineStore('editor', () => {
await saveEditorContent(documentId); 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(); instance.autoSaveTimer.clear();
@@ -578,22 +586,10 @@ export const useEditorStore = defineStore('editor', () => {
operationManager.cancelAllOperations(); operationManager.cancelAllOperations();
editorCache.clear((_documentId, instance) => { 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(); instance.autoSaveTimer.clear();
// 从扩展管理器移除 // 从扩展管理器移除
removeExtensionManagerView(instance.documentId); removeExtensionManagerView(instance.documentId);
// 移除DOM元素 // 移除DOM元素
if (instance.view.dom.parentElement) { if (instance.view.dom.parentElement) {
instance.view.dom.remove(); instance.view.dom.remove();

View File

@@ -156,10 +156,10 @@ export const useTabStore = defineStore('tab', () => {
*/ */
const validateTabs = () => { const validateTabs = () => {
const validDocIds = Object.keys(documentStore.documents).map(Number); const validDocIds = Object.keys(documentStore.documents).map(Number);
// 找出无效的标签页(文档已被删除) // 找出无效的标签页(文档已被删除)
const invalidTabIds = tabOrder.value.filter(docId => !validDocIds.includes(docId)); const invalidTabIds = tabOrder.value.filter(docId => !validDocIds.includes(docId));
if (invalidTabIds.length > 0) { if (invalidTabIds.length > 0) {
// 批量清理无效标签页 // 批量清理无效标签页
invalidTabIds.forEach(docId => { invalidTabIds.forEach(docId => {
@@ -175,7 +175,7 @@ export const useTabStore = defineStore('tab', () => {
const initializeTab = () => { const initializeTab = () => {
// 先验证并清理无效的标签页(处理持久化的脏数据) // 先验证并清理无效的标签页(处理持久化的脏数据)
validateTabs(); validateTabs();
if (isTabsEnabled.value) { if (isTabsEnabled.value) {
const currentDoc = documentStore.currentDocument; const currentDoc = documentStore.currentDocument;
if (currentDoc) { if (currentDoc) {
@@ -254,7 +254,7 @@ export const useTabStore = defineStore('tab', () => {
return { return {
tabsMap, tabsMap,
tabOrder, tabOrder,
// 状态 // 状态
tabs: readonly(tabs), tabs: readonly(tabs),
draggedTabId, draggedTabId,
@@ -283,9 +283,5 @@ export const useTabStore = defineStore('tab', () => {
getTab getTab
}; };
}, { }, {
persist: { persist: false,
key: 'voidraft-tabs',
storage: localStorage,
pick: ['tabsMap', 'tabOrder'],
},
}); });

View File

@@ -0,0 +1,67 @@
import {EditorView, ViewPlugin, ViewUpdate} from '@codemirror/view';
import {useDocumentStore} from '@/stores/documentStore';
import {createDebounce} from '@/common/utils/debounce';
/**
* 光标位置持久化扩展
* 实时监听光标位置变化并持久化到 documentStore
*/
export function createCursorPositionExtension(documentId: number) {
return ViewPlugin.fromClass(
class CursorPositionPlugin {
private readonly documentStore = useDocumentStore();
private readonly debouncedSave;
constructor(private view: EditorView) {
const { debouncedFn, flush } = createDebounce(
() => this.saveCursorPosition(),
{ delay: 400 }
);
this.debouncedSave = { fn: debouncedFn, flush };
// 初始化时保存一次光标位置
this.saveCursorPosition();
}
update(update: ViewUpdate) {
// 只在选择变化时触发
if (!update.selectionSet) {
return;
}
// 防抖保存光标位置
this.debouncedSave.fn();
}
destroy() {
// 销毁时立即执行待保存的操作
this.debouncedSave.flush();
// 再保存一次确保最新状态
this.saveCursorPosition();
}
private saveCursorPosition() {
const cursorPos = this.view.state.selection.main.head;
// 持久化到 documentStore
this.documentStore.documentStates[documentId] = {
cursorPos
};
}
}
);
}
/**
* 滚动到当前光标位置(视口中心)
* @param view 编辑器视图
*/
export function scrollToCursor(view: EditorView) {
const cursorPos = view.state.selection.main.head;
view.dispatch({
effects: EditorView.scrollIntoView(cursorPos, {
y: 'center',
x: 'center'
})
});
}