fix(search): improve quick-open ranking and simplify settings

This commit is contained in:
zipg authored and GitHub committed 2026-08-12 00:08:42 +08:00
1 parent cb8d7ad014
commit bce120f870
16 files changed
+316 -86

No files matched your search

@@ -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(() => {
<Switch id="editor-execute-all-on-blank-line" v-model="editExecuteAllOnBlankLine" :disabled="editExecuteMode !== 'current'" class="mt-0.5" />
</div>
<div class="flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2">
<div class="space-y-1">
<Label for="editor-global-connect-timeout">{{ t("settings.globalConnectTimeout") }}</Label>
<p class="text-xs text-muted-foreground">
{{ t("settings.globalConnectTimeoutDescription") }}
</p>
</div>
<Input id="editor-global-connect-timeout" v-model.number="editGlobalConnectTimeoutSecs" type="number" min="1" max="300" step="1" class="w-24" />
</div>
<div class="flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2">
<div class="space-y-1">
<Label for="editor-global-query-timeout">{{ t("settings.globalQueryTimeout") }}</Label>
<p class="text-xs text-muted-foreground">
{{ t("settings.globalQueryTimeoutDescription") }}
</p>
</div>
<Input id="editor-global-query-timeout" v-model.number="editGlobalQueryTimeoutSecs" type="number" min="0" max="300" step="1" class="w-24" />
</div>
<div class="flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2">
<div class="space-y-1">
<Label for="editor-show-execution-target-picker">{{ t("settings.showExecutionTargetPicker") }}</Label>
+140 -1
View File
@@ -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: [
+138 -35
View File
@@ -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<string, PrefixState>, 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<string, PrefixState>([["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);
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -5472,7 +5472,7 @@ export default withEnglishFallback({
shortcutNewQuery: "新しいクエリ",
shortcutOpenSettings: "設定を開く",
shortcutCloseTab: "タブを閉じる",
shortcutFocusSearch: "検索にフォーカス",
shortcutFocusSearch: "現在のビューの検索にフォーカス",
shortcutQuickOpen: "クイックオープン (すべてのデータベースオブジェクトを検索)",
shortcutSwitchToPreviousTab: "前のタブに切り替え",
shortcutSwitchToNextTab: "次のタブに切り替え",
+1 -1
View File
@@ -5220,7 +5220,7 @@ export default withEnglishFallback({
shortcutNewQuery: "새 쿼리",
shortcutOpenSettings: "설정 열기",
shortcutCloseTab: "탭 닫기",
shortcutFocusSearch: "검색에 포커스",
shortcutFocusSearch: "현재 화면 검색에 포커스",
shortcutQuickOpen: "빠른 열기 (모든 데이터베이스 객체 검색)",
shortcutSwitchToPreviousTab: "이전 탭으로 전환",
shortcutSwitchToNextTab: "다음 탭으로 전환",
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -5661,7 +5661,7 @@ export default withEnglishFallback({
shortcutOpenSettings: "打开设置",
shortcutCloseTab: "关闭标签页",
shortcutToggleSidebar: "切换侧边栏",
shortcutFocusSearch: "聚焦搜索",
shortcutFocusSearch: "聚焦当前页面搜索",
shortcutQuickOpen: "快速打开 (搜索所有数据库对象)",
shortcutSwitchToPreviousTab: "切换到左侧标签页",
shortcutSwitchToNextTab: "切换到右侧标签页",
+1 -1
View File
@@ -4889,7 +4889,7 @@ export default withEnglishFallback({
shortcutOpenSettings: "開啟設定",
shortcutCloseTab: "關閉分頁",
shortcutToggleSidebar: "切換側邊欄",
shortcutFocusSearch: "聚焦搜尋",
shortcutFocusSearch: "聚焦目前頁面搜尋",
shortcutQuickOpen: "快速開啟 (搜尋所有資料庫物件)",
shortcutSwitchToPreviousTab: "切換到左側分頁",
shortcutSwitchToNextTab: "切換到右側分頁",
@@ -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");
@@ -32,6 +32,11 @@ function makeSettings(overrides: Partial<EditorSettings> = {}): 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);
@@ -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"]);
@@ -14,8 +14,6 @@ export const EDITOR_SETTINGS_DRAFT_KEYS = [
"activeCustomThemeId",
"executeMode",
"executeAllOnBlankLine",
"globalConnectTimeoutSecs",
"globalQueryTimeoutSecs",
"showExecutionTargetPicker",
"showStatementRunButtons",
"showCurrentStatementFrame",
@@ -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" },