feat(editor): handle tabs after connection disconnect

This commit is contained in:
javalover123 authored and GitHub committed 2026-09-20 01:12:21 +08:00
1 parent 3cb1faf1c6
commit 28cc1b9768
31 files changed
+1137 -60

No files matched your search

+103
View File
@@ -673,6 +673,13 @@ export const SIDEBAR_INDENT_DEFAULT = 16;
const DISCONNECT_TAB_HANDLING_MODES = ["close-tabs", "keep-tabs-clear-results", "keep-tabs-keep-results"] as const;
export type DisconnectTabHandlingMode = (typeof DISCONNECT_TAB_HANDLING_MODES)[number];
/**
* 删除连接(连接配置已从磁盘移除,无法再重连)时,对该连接已打开页签的处理策略。
* 与 {@link DisconnectTabHandlingMode} 分开:断开连接仍可重连,删除则不会。
*/
const DELETE_CONNECTION_TAB_HANDLING_MODES = ["close-tabs", "keep-sql-tabs", "keep-pinned-sql-tabs", "keep-all-tabs"] as const;
export type DeleteConnectionTabHandlingMode = (typeof DELETE_CONNECTION_TAB_HANDLING_MODES)[number];
const CLICK_TABLE_NAVIGATION_TARGETS = ["data", "ddl"] as const;
export type ClickTableNavigationTarget = (typeof CLICK_TABLE_NAVIGATION_TARGETS)[number];
@@ -744,6 +751,16 @@ export const DEFAULT_CUSTOM_THEMES: CustomTheme[] = [
export type SidebarObjectInfoMode = "comment-inline" | "comment-aligned" | "comment-right" | "size" | "hidden";
/**
* 删除连接时记住的「连接名 → 数据库」。带上 `dbType` 是为了只在新建同名**同类型**
* 连接时回填——像 "test"/"local" 这类名字常被不同数据库类型复用,跨类型回填会把
* 无意义的库名写进新连接配置。
*/
export interface RememberedConnectionDatabase {
database: string;
dbType: string;
}
export interface EditorSettings {
fontFamily: string;
fontSize: number;
@@ -863,6 +880,11 @@ export interface EditorSettings {
sidebarBrowseObjectsOnDatabaseActivationMigrationVersion: number;
openTabsRestoreMode: OpenTabsRestoreMode;
disconnectTabHandlingMode: DisconnectTabHandlingMode;
deleteConnectionTabHandlingMode: DeleteConnectionTabHandlingMode;
/** 删除连接时记住「连接名 → 数据库名」,新建同名同类型连接时自动回填并重绑保留的 SQL 页签。 */
rememberConnectionDatabaseOnDelete: boolean;
/** 已记住的「连接名 → { 数据库名, 数据库类型 }」映射,用于新建同名连接时自动选中数据库。 */
rememberedConnectionDatabases: Record<string, RememberedConnectionDatabase>;
dataTabReuseMode: DataTabReuseMode;
openDataTabsNextToActive: boolean;
prefillNewQueryWithSelect: boolean;
@@ -1121,6 +1143,9 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = {
sidebarBrowseObjectsOnDatabaseActivationMigrationVersion: SIDEBAR_BROWSE_OBJECTS_MIGRATION_VERSION,
openTabsRestoreMode: "all",
disconnectTabHandlingMode: "close-tabs",
deleteConnectionTabHandlingMode: "close-tabs",
rememberConnectionDatabaseOnDelete: true,
rememberedConnectionDatabases: {},
dataTabReuseMode: DEFAULT_DATA_TAB_REUSE_MODE,
openDataTabsNextToActive: false,
prefillNewQueryWithSelect: true,
@@ -1310,6 +1335,35 @@ function normalizeDisconnectTabHandlingMode(value: unknown, legacyCloseTabsOnDis
return DEFAULT_EDITOR_SETTINGS.disconnectTabHandlingMode;
}
function normalizeDeleteConnectionTabHandlingMode(value: unknown): DeleteConnectionTabHandlingMode {
if (DELETE_CONNECTION_TAB_HANDLING_MODES.includes(value as DeleteConnectionTabHandlingMode)) {
return value as DeleteConnectionTabHandlingMode;
}
// 兼容早期试验值:把「保留页签」统一收敛到最接近的正式取值。
if (value === "keep-tabs" || value === "keep-sql") return "keep-sql-tabs";
if (value === "keep-pinned" || value === "keep-fixed-tabs") return "keep-pinned-sql-tabs";
if (value === "keep-all") return "keep-all-tabs";
return DEFAULT_EDITOR_SETTINGS.deleteConnectionTabHandlingMode;
}
/** 记住的连接名 → 数据库映射:只保留合法条目,并限制条目数量避免无限增长。 */
const REMEMBERED_CONNECTION_DATABASES_LIMIT = 200;
function normalizeRememberedConnectionDatabases(value: unknown): Record<string, RememberedConnectionDatabase> {
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
const entries: [string, RememberedConnectionDatabase][] = [];
for (const [name, entry] of Object.entries(value as Record<string, unknown>)) {
const key = name.trim();
if (!key || !entry || typeof entry !== "object" || Array.isArray(entry)) continue;
const { database, dbType } = entry as { database?: unknown; dbType?: unknown };
if (typeof database !== "string" || !database.trim() || typeof dbType !== "string") continue;
entries.push([key, { database, dbType }]);
}
if (entries.length <= REMEMBERED_CONNECTION_DATABASES_LIMIT) return Object.fromEntries(entries);
// 超出上限时保留最后写入的部分(对象插入顺序即写入顺序)。
return Object.fromEntries(entries.slice(entries.length - REMEMBERED_CONNECTION_DATABASES_LIMIT));
}
function normalizeClickTableNavigationTarget(value: unknown): ClickTableNavigationTarget {
return value === "data" || value === "ddl" ? value : DEFAULT_EDITOR_SETTINGS.clickTableNavigationTarget;
}
@@ -1650,6 +1704,9 @@ export function normalizeEditorSettings(settings: Partial<EditorSettings>, exist
}
).closeQueryTabsOnDisconnect,
),
deleteConnectionTabHandlingMode: normalizeDeleteConnectionTabHandlingMode(settings.deleteConnectionTabHandlingMode),
rememberConnectionDatabaseOnDelete: typeof settings.rememberConnectionDatabaseOnDelete === "boolean" ? settings.rememberConnectionDatabaseOnDelete : DEFAULT_EDITOR_SETTINGS.rememberConnectionDatabaseOnDelete,
rememberedConnectionDatabases: normalizeRememberedConnectionDatabases(settings.rememberedConnectionDatabases),
dataTabReuseMode: normalizeDataTabReuseMode(
settings.dataTabReuseMode,
(
@@ -2414,6 +2471,9 @@ export const useSettingsStore = defineStore("settings", () => {
if (partial.sidebarBrowseObjectsOnDatabaseActivation !== undefined) editorSettings.value.sidebarBrowseObjectsOnDatabaseActivation = partial.sidebarBrowseObjectsOnDatabaseActivation === true;
if (partial.openTabsRestoreMode !== undefined) editorSettings.value.openTabsRestoreMode = normalizeOpenTabsRestoreMode(partial.openTabsRestoreMode);
if (partial.disconnectTabHandlingMode !== undefined) editorSettings.value.disconnectTabHandlingMode = normalizeDisconnectTabHandlingMode(partial.disconnectTabHandlingMode);
if (partial.deleteConnectionTabHandlingMode !== undefined) editorSettings.value.deleteConnectionTabHandlingMode = normalizeDeleteConnectionTabHandlingMode(partial.deleteConnectionTabHandlingMode);
if (partial.rememberConnectionDatabaseOnDelete !== undefined) editorSettings.value.rememberConnectionDatabaseOnDelete = partial.rememberConnectionDatabaseOnDelete === true;
if (partial.rememberedConnectionDatabases !== undefined) editorSettings.value.rememberedConnectionDatabases = normalizeRememberedConnectionDatabases(partial.rememberedConnectionDatabases);
if (partial.dataTabReuseMode !== undefined) editorSettings.value.dataTabReuseMode = normalizeDataTabReuseMode(partial.dataTabReuseMode);
if (partial.openDataTabsNextToActive !== undefined) editorSettings.value.openDataTabsNextToActive = partial.openDataTabsNextToActive === true;
if (partial.prefillNewQueryWithSelect !== undefined) editorSettings.value.prefillNewQueryWithSelect = partial.prefillNewQueryWithSelect;
@@ -2526,6 +2586,46 @@ export const useSettingsStore = defineStore("settings", () => {
});
}
/**
* 批量记住「连接名 → 数据库名 + 类型」;数据库名或类型为空表示清除该条目。
* 删除连接时写入,新建同名同类型连接时回填。整体只触发一次设置落盘。
*/
function rememberConnectionDatabases(entries: Iterable<readonly [string, string | undefined, string | undefined]>) {
const next = { ...editorSettings.value.rememberedConnectionDatabases };
let changed = false;
for (const [connectionName, database, dbType] of entries) {
const name = connectionName.trim();
const value = database?.trim();
if (!name || !value || !dbType) {
if (name && name in next) {
delete next[name];
changed = true;
}
continue;
}
const previous = next[name];
if (previous?.database === value && previous.dbType === dbType) continue;
// 重新插入以刷新写入顺序,让上限裁剪优先淘汰最旧的条目。
delete next[name];
next[name] = { database: value, dbType };
changed = true;
}
if (!changed) return;
updateEditorSettings({ rememberedConnectionDatabases: next });
}
/** 只在连接名与数据库类型都匹配时返回记住的数据库;类型不同视为无关记录。 */
function rememberedDatabaseForConnection(connectionName: string, dbType: string): string {
const name = connectionName.trim();
if (!name || !dbType) return "";
const entry = editorSettings.value.rememberedConnectionDatabases[name];
return entry && entry.dbType === dbType ? entry.database : "";
}
function clearRememberedConnectionDatabases() {
updateEditorSettings({ rememberedConnectionDatabases: {} });
}
function updateColumnFormatter(key: string, formatter: ColumnFormatterConfig | undefined) {
const columnFormatters = { ...editorSettings.value.columnFormatters };
const normalized = normalizeColumnFormatter(formatter);
@@ -2677,6 +2777,9 @@ export const useSettingsStore = defineStore("settings", () => {
initMcpGlobalPolicy,
updateMcpGlobalPolicy,
updateColumnFormatter,
rememberConnectionDatabases,
rememberedDatabaseForConnection,
clearRememberedConnectionDatabases,
customColumnFormatterDeleteVersion,
upsertCustomColumnFormatter,
deleteCustomColumnFormatter,