diff --git a/apps/desktop/src/components/editor/EditorSettingsDialog.vue b/apps/desktop/src/components/editor/EditorSettingsDialog.vue
index 8915c2ad6c..6388e931e0 100644
--- a/apps/desktop/src/components/editor/EditorSettingsDialog.vue
+++ b/apps/desktop/src/components/editor/EditorSettingsDialog.vue
@@ -275,8 +275,6 @@ const editActiveCustomThemeId = ref(settingsStore.editorSettings.activeCustomThe
const showThemeCustomizer = ref(false);
const editExecuteMode = ref(settingsStore.editorSettings.executeMode);
const editExecuteAllOnBlankLine = ref(settingsStore.editorSettings.executeAllOnBlankLine);
-const editGlobalConnectTimeoutSecs = ref(settingsStore.editorSettings.globalConnectTimeoutSecs);
-const editGlobalQueryTimeoutSecs = ref(settingsStore.editorSettings.globalQueryTimeoutSecs);
const editShowExecutionTargetPicker = ref(settingsStore.editorSettings.showExecutionTargetPicker);
const editShowStatementRunButtons = ref(settingsStore.editorSettings.showStatementRunButtons);
const editShowCurrentStatementFrame = ref(settingsStore.editorSettings.showCurrentStatementFrame);
@@ -465,8 +463,6 @@ function currentEditorSettingsDraft(): EditorSettingsDraft {
activeCustomThemeId: editActiveCustomThemeId.value,
executeMode: editExecuteMode.value,
executeAllOnBlankLine: editExecuteAllOnBlankLine.value,
- globalConnectTimeoutSecs: editGlobalConnectTimeoutSecs.value,
- globalQueryTimeoutSecs: editGlobalQueryTimeoutSecs.value,
showExecutionTargetPicker: editShowExecutionTargetPicker.value,
showStatementRunButtons: editShowStatementRunButtons.value,
showCurrentStatementFrame: editShowCurrentStatementFrame.value,
@@ -738,8 +734,6 @@ function syncEditorSettingsDraftFromStore() {
editActiveCustomThemeId.value = settingsStore.editorSettings.activeCustomThemeId;
editExecuteMode.value = settingsStore.editorSettings.executeMode;
editExecuteAllOnBlankLine.value = settingsStore.editorSettings.executeAllOnBlankLine;
- editGlobalConnectTimeoutSecs.value = settingsStore.editorSettings.globalConnectTimeoutSecs;
- editGlobalQueryTimeoutSecs.value = settingsStore.editorSettings.globalQueryTimeoutSecs;
editShowExecutionTargetPicker.value = settingsStore.editorSettings.showExecutionTargetPicker;
editShowStatementRunButtons.value = settingsStore.editorSettings.showStatementRunButtons;
editShowCurrentStatementFrame.value = settingsStore.editorSettings.showCurrentStatementFrame;
@@ -895,8 +889,6 @@ function hasChanges(): boolean {
async function persistSettings() {
if (hasApplyBlocker.value) return;
const editorSettingsPatch = editorSettingsPatchFromDraft(currentEditorSettingsDraft(), editEditorSettingsBase.value);
- const globalConnectTimeoutChanged = editorSettingsPatch.globalConnectTimeoutSecs !== undefined;
- const globalQueryTimeoutChanged = editorSettingsPatch.globalQueryTimeoutSecs !== undefined;
const sidebarObjectDisplayChanged = editorSettingsPatch.sidebarObjectDisplay !== undefined && editorSettingsPatch.sidebarObjectDisplay !== settingsStore.editorSettings.sidebarObjectDisplay;
const sidebarTablePageSizeChanged = editSidebarTablePageSize.value !== (settingsStore.desktopSettings.sidebar_table_page_size ?? DEFAULT_SIDEBAR_TABLE_PAGE_SIZE);
if (Object.keys(editorSettingsPatch).length > 0) {
@@ -904,12 +896,6 @@ async function persistSettings() {
await settingsStore.persistEditorSettings();
editEditorSettingsBase.value = editorSettingsDraftFromSettings(settingsStore.editorSettings);
}
- if (globalConnectTimeoutChanged || globalQueryTimeoutChanged) {
- await connectionStore.applyGlobalTimeouts({
- connectTimeoutSecs: globalConnectTimeoutChanged ? settingsStore.editorSettings.globalConnectTimeoutSecs : undefined,
- queryTimeoutSecs: globalQueryTimeoutChanged ? settingsStore.editorSettings.globalQueryTimeoutSecs : undefined,
- });
- }
await settingsStore.updateDesktopSettings({
show_tray_icon: editShowTrayIcon.value,
quit_on_close: editQuitOnClose.value,
@@ -957,8 +943,6 @@ function resetDefaultsForTab(tab: SettingsCategory) {
editFontSize.value = DEFAULT_EDITOR_SETTINGS.fontSize;
editExecuteMode.value = DEFAULT_EDITOR_SETTINGS.executeMode;
editExecuteAllOnBlankLine.value = DEFAULT_EDITOR_SETTINGS.executeAllOnBlankLine;
- editGlobalConnectTimeoutSecs.value = DEFAULT_EDITOR_SETTINGS.globalConnectTimeoutSecs;
- editGlobalQueryTimeoutSecs.value = DEFAULT_EDITOR_SETTINGS.globalQueryTimeoutSecs;
editShowExecutionTargetPicker.value = DEFAULT_EDITOR_SETTINGS.showExecutionTargetPicker;
editShowStatementRunButtons.value = DEFAULT_EDITOR_SETTINGS.showStatementRunButtons;
editShowCurrentStatementFrame.value = DEFAULT_EDITOR_SETTINGS.showCurrentStatementFrame;
@@ -1055,8 +1039,6 @@ function resetAllDefaults() {
editActiveCustomThemeId.value = DEFAULT_EDITOR_SETTINGS.activeCustomThemeId;
editExecuteMode.value = DEFAULT_EDITOR_SETTINGS.executeMode;
editExecuteAllOnBlankLine.value = DEFAULT_EDITOR_SETTINGS.executeAllOnBlankLine;
- editGlobalConnectTimeoutSecs.value = DEFAULT_EDITOR_SETTINGS.globalConnectTimeoutSecs;
- editGlobalQueryTimeoutSecs.value = DEFAULT_EDITOR_SETTINGS.globalQueryTimeoutSecs;
editShowExecutionTargetPicker.value = DEFAULT_EDITOR_SETTINGS.showExecutionTargetPicker;
editShowStatementRunButtons.value = DEFAULT_EDITOR_SETTINGS.showStatementRunButtons;
editShowCurrentStatementFrame.value = DEFAULT_EDITOR_SETTINGS.showCurrentStatementFrame;
@@ -3698,26 +3680,6 @@ onUnmounted(() => {
-
diff --git a/apps/desktop/src/composables/__tests__/useQuickOpen.spec.ts b/apps/desktop/src/composables/__tests__/useQuickOpen.spec.ts
index 071f70f584..212627ba45 100644
--- a/apps/desktop/src/composables/__tests__/useQuickOpen.spec.ts
+++ b/apps/desktop/src/composables/__tests__/useQuickOpen.spec.ts
@@ -1,6 +1,6 @@
import { nextTick } from "vue";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
-import { useQuickOpen } from "@/composables/useQuickOpen";
+import { matchQuickOpenText, useQuickOpen } from "@/composables/useQuickOpen";
import * as api from "@/lib/backend/api";
import { getSqlFileFolderPaths, sqlFileFoldersVersion } from "@/lib/sqlFile/sqlFileFolders";
import { useConnectionStore } from "@/stores/connectionStore";
@@ -84,6 +84,68 @@ describe("useQuickOpen", () => {
});
describe("fuzzyMatch function", () => {
+ it("ranks exact names, initials, prefixes, substrings, and fuzzy matches in order", () => {
+ const exact = matchQuickOpenText("shop", "shop");
+ const initials = matchQuickOpenText("gafi", "groupon_apply_finance_invoice");
+ const prefix = matchQuickOpenText("shop", "shop_attribute");
+ const substring = matchQuickOpenText("shop", "sale_payway_shop");
+ const fuzzy = matchQuickOpenText("gafi", "giftcard_define_item");
+
+ expect([exact?.kind, initials?.kind, prefix?.kind, substring?.kind, fuzzy?.kind]).toEqual(["exact", "initials", "prefix", "substring", "fuzzy"]);
+ expect(exact!.score).toBeLessThan(initials!.score);
+ expect(initials!.score).toBeLessThan(prefix!.score);
+ expect(prefix!.score).toBeLessThan(substring!.score);
+ expect(substring!.score).toBeLessThan(fuzzy!.score);
+ expect(initials?.indices).toEqual([0, 8, 14, 22]);
+ });
+
+ it("matches multi-word prefix combinations and highlights their source characters", () => {
+ const label = "giftcard_define_shop_log";
+
+ expect(matchQuickOpenText("gdsl", label)?.kind).toBe("initials");
+ for (const query of ["giftdsl", "gdshopl", "gdesl"]) {
+ const match = matchQuickOpenText(query, label);
+ expect(match?.kind).toBe("word-prefix");
+ expect(match?.indices.map((index) => label[index]).join("")).toBe(query);
+ }
+ expect(matchQuickOpenText("giftcard", label)?.kind).toBe("prefix");
+ expect(matchQuickOpenText("define", label)?.kind).toBe("substring");
+ expect(matchQuickOpenText("shop", label)?.kind).toBe("substring");
+ expect(matchQuickOpenText("cardshoplog", label)?.kind).toBe("fuzzy");
+ });
+
+ it("puts the exact shop result before prefixed and containing names", () => {
+ vi.mocked(useConnectionStore).mockReturnValue({
+ connections: [
+ { id: "contains", name: "sale_payway_shop", type: "mssql" },
+ { id: "prefix", name: "shop_attribute", type: "mssql" },
+ { id: "exact", name: "shop", type: "mssql" },
+ ],
+ treeNodes: [],
+ } as any);
+
+ const { filteredItems, setQuery } = useQuickOpen();
+ setQuery("shop");
+
+ expect(filteredItems.value.map((item) => item.label)).toEqual(["shop", "shop_attribute", "sale_payway_shop"]);
+ });
+
+ it("puts an exact identifier acronym before loose fuzzy matches", () => {
+ vi.mocked(useConnectionStore).mockReturnValue({
+ connections: [
+ { id: "fuzzy", name: "giftcard_define_item", type: "mssql" },
+ { id: "initials", name: "groupon_apply_finance_invoice", type: "mssql" },
+ ],
+ treeNodes: [],
+ } as any);
+
+ const { filteredItems, setQuery } = useQuickOpen();
+ setQuery("gafi");
+
+ expect(filteredItems.value.map((item) => item.label)).toEqual(["groupon_apply_finance_invoice", "giftcard_define_item"]);
+ expect(filteredItems.value[0].matchIndices).toEqual([0, 8, 14, 22]);
+ });
+
it("should return exact substring match with score 1", () => {
// Mock store with test data
const mockStore = {
@@ -186,6 +248,66 @@ describe("useQuickOpen", () => {
});
describe("filtering and searching", () => {
+ it("keeps database-object highlight indices relative to the visible label", () => {
+ vi.mocked(useConnectionStore).mockReturnValue({
+ connections: [{ id: "conn1", name: "Test TiDB", db_type: "mysql" }],
+ treeNodes: [
+ {
+ id: "conn1",
+ connectionId: "conn1",
+ type: "connection",
+ label: "Test TiDB",
+ children: [
+ {
+ id: "conn1:retail_mps",
+ connectionId: "conn1",
+ type: "database",
+ database: "retail_mps",
+ label: "retail_mps",
+ children: [
+ {
+ id: "table1",
+ connectionId: "conn1",
+ type: "table",
+ database: "retail_mps",
+ label: "groupon_apply_finance_invoice",
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ } as any);
+
+ const { filteredItems, setQuery } = useQuickOpen();
+ setQuery("gafi");
+
+ const table = filteredItems.value.find((item) => item.type === "table");
+ expect(table?.label).toBe("groupon_apply_finance_invoice");
+ expect(table?.matchIndices).toEqual([0, 8, 14, 22]);
+ });
+
+ it("does not highlight the label when only connection metadata matches", () => {
+ vi.mocked(useConnectionStore).mockReturnValue({
+ connections: [{ id: "conn1", name: "ProdConnection", db_type: "mysql" }],
+ treeNodes: [
+ {
+ connectionId: "conn1",
+ type: "database",
+ database: "UserDB",
+ label: "UserDB",
+ },
+ ],
+ } as any);
+
+ const { filteredItems, setQuery } = useQuickOpen();
+ setQuery("Prod");
+
+ const database = filteredItems.value.find((item) => item.type === "database");
+ expect(database?.matchIndices).toEqual([]);
+ expect(database?.matchScore).toBeGreaterThan(1000);
+ });
+
it("indexes database objects under connection groups", () => {
const mockStore = {
connections: [{ id: "conn1", name: "Grouped PG", db_type: "postgres" }],
@@ -503,6 +625,23 @@ describe("useQuickOpen", () => {
});
describe("reset and query setting", () => {
+ it("resets selection when v-model changes the search query directly", () => {
+ vi.mocked(useConnectionStore).mockReturnValue({
+ connections: [
+ { id: "conn1", name: "Connection1", type: "mssql" },
+ { id: "conn2", name: "Connection2", type: "mssql" },
+ ],
+ treeNodes: [],
+ } as any);
+
+ const { searchQuery, selectNext, selectedIndex } = useQuickOpen();
+ selectNext();
+ expect(selectedIndex.value).toBe(1);
+
+ searchQuery.value = "Connection";
+ expect(selectedIndex.value).toBe(0);
+ });
+
it("should reset selection to 0 when setQuery is called", () => {
const mockStore = {
connections: [
diff --git a/apps/desktop/src/composables/useQuickOpen.ts b/apps/desktop/src/composables/useQuickOpen.ts
index 0c417d849e..f4719882a1 100644
--- a/apps/desktop/src/composables/useQuickOpen.ts
+++ b/apps/desktop/src/composables/useQuickOpen.ts
@@ -37,45 +37,141 @@ export interface QuickOpenItem {
sqlFileId?: string; // For saved SQL library files
}
-/**
- * Fuzzy match function that checks if query matches text
- * Returns the matched indices for highlighting
- */
-function fuzzyMatch(query: string, text: string): { score: number; indices: number[] } | null {
- const lowerQuery = query.toLowerCase();
- const lowerText = text.toLowerCase();
+export type QuickOpenMatchKind = "exact" | "initials" | "prefix" | "word-prefix" | "substring" | "fuzzy";
- if (!lowerQuery) return { score: Infinity, indices: [] };
- if (lowerText.includes(lowerQuery)) {
- // Exact substring match gets highest score
- const startIdx = lowerText.indexOf(lowerQuery);
- return {
- score: 1,
- indices: Array.from({ length: lowerQuery.length }, (_, i) => startIdx + i),
- };
+export interface QuickOpenMatch {
+ kind: QuickOpenMatchKind;
+ score: number;
+ indices: number[];
+}
+
+interface IdentifierWord {
+ text: string;
+ start: number;
+}
+
+const IDENTIFIER_SEPARATOR_RE = /[_\-. /\\]/;
+
+function identifierWords(text: string): IdentifierWord[] {
+ const words: IdentifierWord[] = [];
+ let start = -1;
+
+ function pushWord(end: number): void {
+ if (start < 0 || end <= start) return;
+ words.push({ text: text.slice(start, end), start });
}
- // Fuzzy match: find all characters in order
- let queryIdx = 0;
- const indices: number[] = [];
- let score = 0;
- let lastMatchIdx = -1;
-
- for (let i = 0; i < lowerText.length && queryIdx < lowerQuery.length; i++) {
- if (lowerText[i] === lowerQuery[queryIdx]) {
- indices.push(i);
- // Score based on proximity (consecutive chars score better)
- score += lastMatchIdx === i - 1 ? 2 : 1;
- lastMatchIdx = i;
- queryIdx++;
+ for (let index = 0; index < text.length; index++) {
+ const char = text[index];
+ if (IDENTIFIER_SEPARATOR_RE.test(char)) {
+ pushWord(index);
+ start = -1;
+ continue;
+ }
+ if (start < 0) {
+ start = index;
+ continue;
+ }
+ const previous = text[index - 1];
+ if (previous >= "a" && previous <= "z" && char >= "A" && char <= "Z") {
+ pushWord(index);
+ start = index;
}
}
+ pushWord(text.length);
+ return words;
+}
- if (queryIdx === lowerQuery.length) {
- return { score: score / lowerQuery.length, indices };
+function rangeIndices(start: number, length: number): number[] {
+ return Array.from({ length }, (_, index) => start + index);
+}
+
+function matchWordPrefixes(words: IdentifierWord[], query: string): number[] | null {
+ interface PrefixState {
+ queryIndex: number;
+ firstWordIndex: number;
+ lastWordIndex: number;
+ usedWords: number;
+ indices: number[];
}
- return null;
+ function stateScore(state: PrefixState): number {
+ if (state.usedWords === 0) return 0;
+ return (state.lastWordIndex - state.firstWordIndex - state.usedWords + 1) * 10 + state.usedWords;
+ }
+
+ function retainBest(states: Map, candidate: PrefixState): void {
+ const key = `${candidate.queryIndex}:${candidate.usedWords}`;
+ const current = states.get(key);
+ if (!current || stateScore(candidate) < stateScore(current)) states.set(key, candidate);
+ }
+
+ let states = new Map([["0:0", { queryIndex: 0, firstWordIndex: -1, lastWordIndex: -1, usedWords: 0, indices: [] }]]);
+ for (let wordIndex = 0; wordIndex < words.length; wordIndex++) {
+ const nextStates = new Map(states);
+ const word = words[wordIndex];
+ const lowerWord = word.text.toLowerCase();
+ for (const state of states.values()) {
+ const maxLength = Math.min(lowerWord.length, query.length - state.queryIndex);
+ for (let length = 1; length <= maxLength; length++) {
+ if (lowerWord.slice(0, length) !== query.slice(state.queryIndex, state.queryIndex + length)) break;
+ retainBest(nextStates, {
+ queryIndex: state.queryIndex + length,
+ firstWordIndex: state.usedWords === 0 ? wordIndex : state.firstWordIndex,
+ lastWordIndex: wordIndex,
+ usedWords: state.usedWords + 1,
+ indices: [...state.indices, ...rangeIndices(word.start, length)],
+ });
+ }
+ }
+ states = nextStates;
+ }
+
+ return [...states.values()].filter((state) => state.queryIndex === query.length && state.usedWords >= 2).sort((a, b) => stateScore(a) - stateScore(b))[0]?.indices ?? null;
+}
+
+/** Match one quick-open field and return label-relative highlight indices. */
+export function matchQuickOpenText(query: string, text: string): QuickOpenMatch | null {
+ const lowerQuery = query.trim().toLowerCase();
+ const lowerText = text.toLowerCase();
+ if (!lowerQuery) return { kind: "exact", score: Infinity, indices: [] };
+
+ if (lowerText === lowerQuery) {
+ return { kind: "exact", score: 1, indices: rangeIndices(0, text.length) };
+ }
+
+ const words = identifierWords(text);
+ const initials = words.map((word) => word.text[0]?.toLowerCase() ?? "").join("");
+ if (words.length >= 2 && initials === lowerQuery) {
+ return { kind: "initials", score: 100 + Math.min(words.length, 99), indices: words.map((word) => word.start) };
+ }
+
+ if (lowerText.startsWith(lowerQuery)) {
+ return { kind: "prefix", score: 200 + Math.min(text.length - lowerQuery.length, 99), indices: rangeIndices(0, lowerQuery.length) };
+ }
+
+ const wordPrefixIndices = matchWordPrefixes(words, lowerQuery);
+ if (wordPrefixIndices) {
+ return { kind: "word-prefix", score: 300 + Math.min(text.length - lowerQuery.length, 99), indices: wordPrefixIndices };
+ }
+
+ const substringIndex = lowerText.indexOf(lowerQuery);
+ if (substringIndex >= 0) {
+ return { kind: "substring", score: 400 + Math.min(substringIndex, 99), indices: rangeIndices(substringIndex, lowerQuery.length) };
+ }
+
+ if (lowerQuery.length < 2) return null;
+ const indices: number[] = [];
+ let queryIndex = 0;
+ for (let index = 0; index < lowerText.length && queryIndex < lowerQuery.length; index++) {
+ if (lowerText[index] !== lowerQuery[queryIndex]) continue;
+ indices.push(index);
+ queryIndex++;
+ }
+ if (queryIndex !== lowerQuery.length) return null;
+
+ const span = indices[indices.length - 1] - indices[0] + 1;
+ return { kind: "fuzzy", score: 500 + Math.min(span - lowerQuery.length, 99), indices };
}
interface MatchedItem extends QuickOpenItem {
@@ -574,6 +670,7 @@ export function useQuickOpen() {
watch(
searchQuery,
(query) => {
+ selectedIndex.value = 0;
const generation = ++remoteSearchGeneration;
cancelStaleRemoteRequestWaiters(generation);
if (remoteSearchTimer) clearTimeout(remoteSearchTimer);
@@ -616,12 +713,14 @@ export function useQuickOpen() {
const key = quickOpenItemKey(item);
if (seen.has(key)) continue;
seen.add(key);
- const result = fuzzyMatch(searchQuery.value, item.searchText);
+ const labelMatch = matchQuickOpenText(searchQuery.value, item.label);
+ const metadataMatch = labelMatch ? null : matchQuickOpenText(searchQuery.value, item.searchText);
+ const result = labelMatch ?? metadataMatch;
if (result) {
matched.push({
...item,
- matchScore: result.score,
- matchIndices: result.indices,
+ matchScore: result.score + (labelMatch ? 0 : 1000),
+ matchIndices: labelMatch ? result.indices : [],
});
}
}
@@ -647,7 +746,11 @@ export function useQuickOpen() {
sql_library_file: 11,
sql_file: 12,
};
- return typeOrder[a.type] - typeOrder[b.type];
+ const typeDifference = typeOrder[a.type] - typeOrder[b.type];
+ if (typeDifference !== 0) return typeDifference;
+ const lengthDifference = a.label.length - b.label.length;
+ if (lengthDifference !== 0) return lengthDifference;
+ return a.label.localeCompare(b.label);
});
return matched.slice(0, QUICK_OPEN_MAX_RESULTS);
diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts
index 7b57425c97..8ea4c47c45 100644
--- a/apps/desktop/src/i18n/locales/en.ts
+++ b/apps/desktop/src/i18n/locales/en.ts
@@ -5736,7 +5736,7 @@ export default {
shortcutNewQuery: "New query",
shortcutOpenSettings: "Open settings",
shortcutCloseTab: "Close tab",
- shortcutFocusSearch: "Focus search",
+ shortcutFocusSearch: "Focus current view search",
shortcutQuickOpen: "Quick open (search all database objects)",
shortcutSwitchToPreviousTab: "Switch to previous tab",
shortcutSwitchToNextTab: "Switch to next tab",
diff --git a/apps/desktop/src/i18n/locales/es.ts b/apps/desktop/src/i18n/locales/es.ts
index 268aae0ee5..a1090662a9 100644
--- a/apps/desktop/src/i18n/locales/es.ts
+++ b/apps/desktop/src/i18n/locales/es.ts
@@ -5454,7 +5454,7 @@ export default withEnglishFallback({
shortcutOpenSettings: "Abrir configuración",
shortcutCloseTab: "Cerrar pestaña",
shortcutToggleSidebar: "Alternar barra lateral",
- shortcutFocusSearch: "Enfocar búsqueda",
+ shortcutFocusSearch: "Enfocar búsqueda de la vista actual",
shortcutQuickOpen: "Abrir rápido (buscar todos los objetos de base de datos)",
shortcutSwitchToPreviousTab: "Cambiar a la pestaña anterior",
shortcutSwitchToNextTab: "Cambiar a la pestaña siguiente",
diff --git a/apps/desktop/src/i18n/locales/it.ts b/apps/desktop/src/i18n/locales/it.ts
index 201ad74efe..721f41ff9d 100644
--- a/apps/desktop/src/i18n/locales/it.ts
+++ b/apps/desktop/src/i18n/locales/it.ts
@@ -5453,7 +5453,7 @@ export default withEnglishFallback({
shortcutNewQuery: "Nuova query",
shortcutOpenSettings: "Apri impostazioni",
shortcutCloseTab: "Chiudi scheda",
- shortcutFocusSearch: "Focalizza ricerca",
+ shortcutFocusSearch: "Focalizza la ricerca nella vista corrente",
shortcutQuickOpen: "Apertura rapida (ricerca tutti gli oggetti del database)",
shortcutSwitchToPreviousTab: "Passa alla scheda precedente",
shortcutSwitchToNextTab: "Passa alla scheda successiva",
diff --git a/apps/desktop/src/i18n/locales/ja.ts b/apps/desktop/src/i18n/locales/ja.ts
index b7d3de9cc2..e5e0be1184 100644
--- a/apps/desktop/src/i18n/locales/ja.ts
+++ b/apps/desktop/src/i18n/locales/ja.ts
@@ -5472,7 +5472,7 @@ export default withEnglishFallback({
shortcutNewQuery: "新しいクエリ",
shortcutOpenSettings: "設定を開く",
shortcutCloseTab: "タブを閉じる",
- shortcutFocusSearch: "検索にフォーカス",
+ shortcutFocusSearch: "現在のビューの検索にフォーカス",
shortcutQuickOpen: "クイックオープン (すべてのデータベースオブジェクトを検索)",
shortcutSwitchToPreviousTab: "前のタブに切り替え",
shortcutSwitchToNextTab: "次のタブに切り替え",
diff --git a/apps/desktop/src/i18n/locales/ko.ts b/apps/desktop/src/i18n/locales/ko.ts
index 23ebb5456d..7d94d4d265 100644
--- a/apps/desktop/src/i18n/locales/ko.ts
+++ b/apps/desktop/src/i18n/locales/ko.ts
@@ -5220,7 +5220,7 @@ export default withEnglishFallback({
shortcutNewQuery: "새 쿼리",
shortcutOpenSettings: "설정 열기",
shortcutCloseTab: "탭 닫기",
- shortcutFocusSearch: "검색에 포커스",
+ shortcutFocusSearch: "현재 화면 검색에 포커스",
shortcutQuickOpen: "빠른 열기 (모든 데이터베이스 객체 검색)",
shortcutSwitchToPreviousTab: "이전 탭으로 전환",
shortcutSwitchToNextTab: "다음 탭으로 전환",
diff --git a/apps/desktop/src/i18n/locales/pt-BR.ts b/apps/desktop/src/i18n/locales/pt-BR.ts
index 48b795eb50..3315c9e6f2 100644
--- a/apps/desktop/src/i18n/locales/pt-BR.ts
+++ b/apps/desktop/src/i18n/locales/pt-BR.ts
@@ -5456,7 +5456,7 @@ export default withEnglishFallback({
shortcutOpenSettings: "Abrir configurações",
shortcutCloseTab: "Fechar aba",
shortcutToggleSidebar: "Alternar barra lateral",
- shortcutFocusSearch: "Focar na pesquisa",
+ shortcutFocusSearch: "Focar na pesquisa da tela atual",
shortcutQuickOpen: "Abrir rapidamente (pesquisar todos os objetos do banco de dados)",
shortcutSwitchToPreviousTab: "Alternar para a aba anterior",
shortcutSwitchToNextTab: "Alternar para a próxima aba",
diff --git a/apps/desktop/src/i18n/locales/zh-CN.ts b/apps/desktop/src/i18n/locales/zh-CN.ts
index 2eceba2e7f..b29804a8cf 100644
--- a/apps/desktop/src/i18n/locales/zh-CN.ts
+++ b/apps/desktop/src/i18n/locales/zh-CN.ts
@@ -5661,7 +5661,7 @@ export default withEnglishFallback({
shortcutOpenSettings: "打开设置",
shortcutCloseTab: "关闭标签页",
shortcutToggleSidebar: "切换侧边栏",
- shortcutFocusSearch: "聚焦搜索",
+ shortcutFocusSearch: "聚焦当前页面搜索",
shortcutQuickOpen: "快速打开 (搜索所有数据库对象)",
shortcutSwitchToPreviousTab: "切换到左侧标签页",
shortcutSwitchToNextTab: "切换到右侧标签页",
diff --git a/apps/desktop/src/i18n/locales/zh-TW.ts b/apps/desktop/src/i18n/locales/zh-TW.ts
index ebb04eb1af..15178228e8 100644
--- a/apps/desktop/src/i18n/locales/zh-TW.ts
+++ b/apps/desktop/src/i18n/locales/zh-TW.ts
@@ -4889,7 +4889,7 @@ export default withEnglishFallback({
shortcutOpenSettings: "開啟設定",
shortcutCloseTab: "關閉分頁",
shortcutToggleSidebar: "切換側邊欄",
- shortcutFocusSearch: "聚焦搜尋",
+ shortcutFocusSearch: "聚焦目前頁面搜尋",
shortcutQuickOpen: "快速開啟 (搜尋所有資料庫物件)",
shortcutSwitchToPreviousTab: "切換到左側分頁",
shortcutSwitchToNextTab: "切換到右側分頁",
diff --git a/apps/desktop/src/lib/__tests__/editor/shortcutRegistry.spec.ts b/apps/desktop/src/lib/__tests__/editor/shortcutRegistry.spec.ts
index e9dfe9bdf4..714bf5b9d8 100644
--- a/apps/desktop/src/lib/__tests__/editor/shortcutRegistry.spec.ts
+++ b/apps/desktop/src/lib/__tests__/editor/shortcutRegistry.spec.ts
@@ -50,6 +50,16 @@ describe("shortcutRegistry editor actions", () => {
expect(findShortcutConflict("expandSelectStar", DEFAULT_SHORTCUT_SETTINGS.expandSelectStar, DEFAULT_SHORTCUT_SETTINGS)).toBeNull();
});
+ it("keeps current-view search and editor find contextual on Mod+F", () => {
+ const focusSearch = SHORTCUT_DEFINITIONS.find((item) => item.id === "focusSearch");
+ const find = SHORTCUT_DEFINITIONS.find((item) => item.id === "find");
+
+ expect(focusSearch).toMatchObject({ scope: "global", defaultShortcut: "Mod+F" });
+ expect(find).toMatchObject({ scope: "editor", defaultShortcut: "Mod+F" });
+ expect(findShortcutConflict("focusSearch", DEFAULT_SHORTCUT_SETTINGS.focusSearch, DEFAULT_SHORTCUT_SETTINGS)).toBeNull();
+ expect(findShortcutConflict("find", DEFAULT_SHORTCUT_SETTINGS.find, DEFAULT_SHORTCUT_SETTINGS)).toBeNull();
+ });
+
it("uses Shift+Enter for inserting a complete line below", () => {
const definition = SHORTCUT_DEFINITIONS.find((item) => item.id === "insertLineBelow");
diff --git a/apps/desktop/src/lib/settings/__tests__/editorSettingsDraft.spec.ts b/apps/desktop/src/lib/settings/__tests__/editorSettingsDraft.spec.ts
index 0f35a77ef4..65b049044d 100644
--- a/apps/desktop/src/lib/settings/__tests__/editorSettingsDraft.spec.ts
+++ b/apps/desktop/src/lib/settings/__tests__/editorSettingsDraft.spec.ts
@@ -32,6 +32,11 @@ function makeSettings(overrides: Partial = {}): EditorSettings {
}
describe("EDITOR_SETTINGS_DRAFT_KEYS", () => {
+ it("keeps connection and query timeout ownership outside editor settings", () => {
+ expect(EDITOR_SETTINGS_DRAFT_KEYS).not.toContain("globalConnectTimeoutSecs");
+ expect(EDITOR_SETTINGS_DRAFT_KEYS).not.toContain("globalQueryTimeoutSecs");
+ });
+
it("includes continueOnErrorOnBatch", () => {
expect(EDITOR_SETTINGS_DRAFT_KEYS).toContain("continueOnErrorOnBatch");
});
@@ -61,6 +66,16 @@ describe("EDITOR_SETTINGS_DRAFT_KEYS", () => {
});
describe("editorSettingsDraftFromSettings", () => {
+ it("does not include persisted global timeout values in editor drafts", () => {
+ const settings = makeSettings({ globalConnectTimeoutSecs: 17, globalQueryTimeoutSecs: 43 });
+ const draft = editorSettingsDraftFromSettings(settings);
+
+ expect(draft).not.toHaveProperty("globalConnectTimeoutSecs");
+ expect(draft).not.toHaveProperty("globalQueryTimeoutSecs");
+ expect(editorSettingsPatchFromDraft(draft, draft)).not.toHaveProperty("globalConnectTimeoutSecs");
+ expect(editorSettingsPatchFromDraft(draft, draft)).not.toHaveProperty("globalQueryTimeoutSecs");
+ });
+
it("maps continueOnErrorOnBatch from settings", () => {
const draft = editorSettingsDraftFromSettings(makeSettings({ continueOnErrorOnBatch: true }));
expect(draft.continueOnErrorOnBatch).toBe(true);
diff --git a/apps/desktop/src/lib/settings/__tests__/settingsSearch.spec.ts b/apps/desktop/src/lib/settings/__tests__/settingsSearch.spec.ts
index 4e61498e01..84fed15bd3 100644
--- a/apps/desktop/src/lib/settings/__tests__/settingsSearch.spec.ts
+++ b/apps/desktop/src/lib/settings/__tests__/settingsSearch.spec.ts
@@ -37,6 +37,11 @@ describe("settings search", () => {
{ id: "desktop", category: "about", titleKey: "hidden", visible: ({ isWeb }) => !isWeb },
];
+ it("does not index connection or query timeout under editor settings", () => {
+ expect(SETTINGS_SEARCH_DEFINITIONS.map((definition) => definition.id)).not.toContain("editor-global-connect-timeout");
+ expect(SETTINGS_SEARCH_DEFINITIONS.map((definition) => definition.id)).not.toContain("editor-global-query-timeout");
+ });
+
it("matches translated title, description, and category without changing declared order", () => {
const entries = resolveSettingsSearchEntries(definitions, { isWeb: false, visibleCategories: allCategories }, translate, categoryLabels);
expect(searchSettings(entries, "TYPEFACE", "en").map((entry) => entry.id)).toEqual(["font"]);
diff --git a/apps/desktop/src/lib/settings/editorSettingsDraft.ts b/apps/desktop/src/lib/settings/editorSettingsDraft.ts
index 6a154cd7d6..458c7b69a6 100644
--- a/apps/desktop/src/lib/settings/editorSettingsDraft.ts
+++ b/apps/desktop/src/lib/settings/editorSettingsDraft.ts
@@ -14,8 +14,6 @@ export const EDITOR_SETTINGS_DRAFT_KEYS = [
"activeCustomThemeId",
"executeMode",
"executeAllOnBlankLine",
- "globalConnectTimeoutSecs",
- "globalQueryTimeoutSecs",
"showExecutionTargetPicker",
"showStatementRunButtons",
"showCurrentStatementFrame",
diff --git a/apps/desktop/src/lib/settings/settingsSearch.ts b/apps/desktop/src/lib/settings/settingsSearch.ts
index 6e9de4e8d6..3b4036c9cf 100644
--- a/apps/desktop/src/lib/settings/settingsSearch.ts
+++ b/apps/desktop/src/lib/settings/settingsSearch.ts
@@ -107,8 +107,6 @@ export const SETTINGS_SEARCH_DEFINITIONS: readonly SettingsSearchDefinition[] =
{ id: "editor-font-size", category: "editor", titleKey: "settings.fontSize", targetId: "editor" },
{ id: "editor-execute-mode", category: "editor", titleKey: "settings.executeMode", targetId: "editor" },
{ id: "editor-execute-all-on-blank-line", category: "editor", titleKey: "settings.executeAllOnBlankLine", descriptionKey: "settings.executeAllOnBlankLineDescription", targetId: "editor" },
- { id: "editor-global-connect-timeout", category: "editor", titleKey: "settings.globalConnectTimeout", descriptionKey: "settings.globalConnectTimeoutDescription", targetId: "editor" },
- { id: "editor-global-query-timeout", category: "editor", titleKey: "settings.globalQueryTimeout", descriptionKey: "settings.globalQueryTimeoutDescription", targetId: "editor" },
{ id: "editor-execution-target", category: "editor", titleKey: "settings.showExecutionTargetPicker", descriptionKey: "settings.showExecutionTargetPickerDescription", targetId: "editor" },
{ id: "editor-run-buttons", category: "editor", titleKey: "settings.showStatementRunButtons", descriptionKey: "settings.showStatementRunButtonsDescription", targetId: "editor" },
{ id: "editor-statement-frame", category: "editor", titleKey: "settings.showCurrentStatementFrame", descriptionKey: "settings.showCurrentStatementFrameDescription", targetId: "editor" },