feat(editor): 持久化SQL编辑器光标位置

Co-authored-by: zipg <4047349+zipg@users.noreply.github.com>
This commit is contained in:
zipgandzipg authored and GitHub committed 2026-09-08 11:01:41 +08:00
1 parent 772ce36de8
commit 5fb667716f
7 files changed
+52 -2

No files matched your search

+1
View File
@@ -3573,6 +3573,7 @@ onUnmounted(() => {
"
@editor-viewport-change="(tabId: string, viewport: { scrollTop: number; scrollLeft: number }) => queryStore.updateEditorViewport(tabId, viewport)"
@editor-selection-state-change="(tabId: string, selection: { anchor: number; head: number }) => queryStore.updateEditorSelection(tabId, selection)"
@editor-state-flushed="(tabId: string) => void queryStore.flushEditorState(tabId)"
@format-error="toast(t('toolbar.formatSqlFailed'))"
@save-sql="(tabId: string) => void openSaveSqlDialog(tabId)"
@reload="(tabId: string, sql: any, searchText: any, whereInput: any, orderBy: any, limit: any, offset: any, intent: any) => onReloadData(tabId, sql, searchText, whereInput, orderBy, limit, offset, intent)"
@@ -244,6 +244,7 @@ const emit = defineEmits<{
closeColumnPanel: [];
viewportChange: [viewport: { scrollTop: number; scrollLeft: number }];
selectionStateChange: [selection: { anchor: number; head: number }];
editorStateFlushed: [];
sendSelectionToAi: [sql: string];
}>();
@@ -6983,6 +6984,7 @@ function pauseQueryEditorBackgroundWork() {
cancelBatchColumnSelectionRefresh();
flushEditorViewport();
flushEditorSelection();
emit("editorStateFlushed");
clearTableNavigationHover();
clearPendingCompletionEnter();
clearPendingCompletionTab();
@@ -1305,6 +1305,7 @@ defineExpose({
@preview-changes-available="emit('previewChangesAvailable', activeTab.id, $event)"
@viewport-change="emit('editorViewportChange', activeTab.id, $event)"
@selection-state-change="emit('editorSelectionStateChange', activeTab.id, $event)"
@editor-state-flushed="emit('editorStateFlushed', activeTab.id)"
@format-error="emit('formatError', activeTab.id)"
@execute="emit('execute', activeTab.id, $event)"
@execute-in-new-result-tab="emit('executeInNewResultTab', activeTab.id, $event)"
@@ -75,6 +75,7 @@ export interface ContentAreaSurfaceEmits {
editorCursorChange: [tabId: string, pos: number];
editorViewportChange: [tabId: string, viewport: { scrollTop: number; scrollLeft: number }];
editorSelectionStateChange: [tabId: string, selection: { anchor: number; head: number }];
editorStateFlushed: [tabId: string];
formatError: [tabId: string];
reload: [tabId: string, sql?: string, searchText?: string, whereInput?: string, orderBy?: string, limit?: number, offset?: number, intent?: DataGridReloadIntent];
paginate: [tabId: string, offset: number, limit: number, whereInput?: string, orderBy?: string];
@@ -25,6 +25,8 @@ export interface SavedOpenTab {
catalog?: string;
schema?: string;
sql: string;
editorViewport?: QueryTab["editorViewport"];
editorSelection?: QueryTab["editorSelection"];
originalSql?: string;
savedSqlId?: string;
externalSqlPath?: string;
@@ -108,6 +110,24 @@ function restoredOriginalSql(tab: SavedOpenTab, mode: QueryTab["mode"], sql: str
return "";
}
function restoredEditorSelection(tab: SavedOpenTab, docLength: number): QueryTab["editorSelection"] {
const selection = tab.editorSelection;
if (!selection || !Number.isFinite(selection.anchor) || !Number.isFinite(selection.head)) return undefined;
return {
anchor: Math.min(Math.max(0, Math.trunc(selection.anchor)), docLength),
head: Math.min(Math.max(0, Math.trunc(selection.head)), docLength),
};
}
function restoredEditorViewport(tab: SavedOpenTab): QueryTab["editorViewport"] {
const viewport = tab.editorViewport;
if (!viewport || !Number.isFinite(viewport.scrollTop) || !Number.isFinite(viewport.scrollLeft)) return undefined;
return {
scrollTop: Math.max(0, viewport.scrollTop),
scrollLeft: Math.max(0, viewport.scrollLeft),
};
}
export function serializeOpenTabs(tabs: QueryTab[]): SavedOpenTab[] {
return tabs.map((tab) => ({
id: tab.id,
@@ -119,6 +139,8 @@ export function serializeOpenTabs(tabs: QueryTab[]): SavedOpenTab[] {
...(tab.catalog !== undefined ? { catalog: tab.catalog } : {}),
schema: tab.schema,
sql: shouldPersistTabSql(tab) ? tab.sql : "",
...(tab.editorViewport ? { editorViewport: tab.editorViewport } : {}),
...(tab.editorSelection ? { editorSelection: tab.editorSelection } : {}),
// Plain query tabs always round-trip originalSql. External-file tabs only persist it
// while dirty so their disk baseline survives restart without duplicating clean SQL.
...(tab.originalSql !== undefined && !tab.savedSqlId && (!tab.externalSqlPath || tab.sql !== tab.originalSql) ? { originalSql: tab.originalSql } : {}),
@@ -212,8 +234,8 @@ function restoreOpenTabsArray(parsed: unknown, rawActiveTabId: string | null, op
isCancelling: false,
queryExecutionStartedAt: undefined,
executingResultRunId: undefined,
editorViewport: undefined,
editorSelection: undefined,
editorViewport: restoredEditorViewport(tab),
editorSelection: restoredEditorSelection(tab, typeof tab.sql === "string" ? tab.sql.length : 0),
isExplaining: false,
originalSql: restoredOriginalSql(tab, mode, typeof tab.sql === "string" ? tab.sql : ""),
resultEvicted: mode === "data" ? undefined : tab.resultEvicted,
+10
View File
@@ -2228,6 +2228,8 @@ export const useQueryStore = defineStore("query", () => {
database: t.database,
schema: t.schema,
sql: t.sql,
editorViewport: t.editorViewport,
editorSelection: t.editorSelection,
savedSqlId: t.savedSqlId,
externalSqlPath: t.externalSqlPath,
externalSqlFileVersion: t.externalSqlFileVersion,
@@ -3237,6 +3239,13 @@ export const useQueryStore = defineStore("query", () => {
);
}
function flushEditorState(id: string): Promise<void> {
const tab = tabs.value.find((item) => item.id === id);
if (!tab) return Promise.resolve();
persistSavedSqlEditorPosition(tab);
return flushPendingPersist();
}
function queueSavedSqlEditorPositionPersist(tab: QueryTab | undefined) {
if (!tab?.savedSqlId || tab.mode !== "query") return;
const pending = savedSqlEditorPositionTimers.get(tab.savedSqlId);
@@ -7780,6 +7789,7 @@ export const useQueryStore = defineStore("query", () => {
updateDataGridHiddenColumnKeys,
updateEditorViewport,
updateEditorSelection,
flushEditorState,
updateObjectBrowserViewport,
updateObjectBrowserSearch,
updateNacosConfigEditorViewport,