From 9418e37c046ca4e2ded02b305e60b02dde1b65c8 Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Mon, 22 Jun 2026 12:05:47 +0800 Subject: [PATCH] feat(settings): add show execution target picker option and related UI elements --- .../components/editor/EditorSettingsDialog.vue | 16 +++++++++++++++- .../src/components/editor/QueryEditor.vue | 6 ++++++ apps/desktop/src/i18n/locales/en.ts | 2 ++ apps/desktop/src/i18n/locales/es.ts | 2 ++ apps/desktop/src/i18n/locales/it.ts | 2 ++ apps/desktop/src/i18n/locales/ja.ts | 2 ++ apps/desktop/src/i18n/locales/pt-BR.ts | 2 ++ apps/desktop/src/i18n/locales/zh-CN.ts | 2 ++ apps/desktop/src/i18n/locales/zh-TW.ts | 2 ++ apps/desktop/src/stores/settingsStore.ts | 4 ++++ crates/dbx-core/src/db/sqlserver.rs | 12 ++++++++++++ 11 files changed, 51 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/components/editor/EditorSettingsDialog.vue b/apps/desktop/src/components/editor/EditorSettingsDialog.vue index 232f9049d..202d11c37 100644 --- a/apps/desktop/src/components/editor/EditorSettingsDialog.vue +++ b/apps/desktop/src/components/editor/EditorSettingsDialog.vue @@ -102,6 +102,7 @@ const editCustomThemes = ref([...settingsStore.editorSettings.cus const editActiveCustomThemeId = ref(settingsStore.editorSettings.activeCustomThemeId); const showThemeCustomizer = ref(false); const editExecuteMode = ref(settingsStore.editorSettings.executeMode); +const editShowExecutionTargetPicker = ref(settingsStore.editorSettings.showExecutionTargetPicker); const editWordWrap = ref(settingsStore.editorSettings.wordWrap); const editConfirmDangerousSqlExecution = ref(settingsStore.editorSettings.confirmDangerousSqlExecution); const editAppLayout = ref(settingsStore.editorSettings.appLayout); @@ -363,6 +364,7 @@ watch( editCustomThemes.value = [...settingsStore.editorSettings.customThemes]; editActiveCustomThemeId.value = settingsStore.editorSettings.activeCustomThemeId; editExecuteMode.value = settingsStore.editorSettings.executeMode; + editShowExecutionTargetPicker.value = settingsStore.editorSettings.showExecutionTargetPicker; editWordWrap.value = settingsStore.editorSettings.wordWrap; editConfirmDangerousSqlExecution.value = settingsStore.editorSettings.confirmDangerousSqlExecution; editAppLayout.value = settingsStore.editorSettings.appLayout; @@ -420,6 +422,7 @@ function hasChanges(): boolean { JSON.stringify(editCustomThemes.value) !== JSON.stringify(settingsStore.editorSettings.customThemes) || editActiveCustomThemeId.value !== settingsStore.editorSettings.activeCustomThemeId || editExecuteMode.value !== settingsStore.editorSettings.executeMode || + editShowExecutionTargetPicker.value !== settingsStore.editorSettings.showExecutionTargetPicker || editWordWrap.value !== settingsStore.editorSettings.wordWrap || editConfirmDangerousSqlExecution.value !== settingsStore.editorSettings.confirmDangerousSqlExecution || editAppLayout.value !== settingsStore.editorSettings.appLayout || @@ -462,6 +465,7 @@ async function persistSettings() { customThemes: editCustomThemes.value, activeCustomThemeId: editActiveCustomThemeId.value, executeMode: editExecuteMode.value, + showExecutionTargetPicker: editShowExecutionTargetPicker.value, wordWrap: editWordWrap.value, confirmDangerousSqlExecution: editConfirmDangerousSqlExecution.value, appLayout: editAppLayout.value, @@ -514,6 +518,7 @@ function resetDefaultsForTab(tab: SettingsCategory) { editFontFamily.value = DEFAULT_EDITOR_SETTINGS.fontFamily; editFontSize.value = DEFAULT_EDITOR_SETTINGS.fontSize; editExecuteMode.value = DEFAULT_EDITOR_SETTINGS.executeMode; + editShowExecutionTargetPicker.value = DEFAULT_EDITOR_SETTINGS.showExecutionTargetPicker; editWordWrap.value = DEFAULT_EDITOR_SETTINGS.wordWrap; editConfirmDangerousSqlExecution.value = DEFAULT_EDITOR_SETTINGS.confirmDangerousSqlExecution; } else if (tab === "formatter") { @@ -566,6 +571,7 @@ function resetAllDefaults() { editCustomThemes.value = [...DEFAULT_EDITOR_SETTINGS.customThemes]; editActiveCustomThemeId.value = DEFAULT_EDITOR_SETTINGS.activeCustomThemeId; editExecuteMode.value = DEFAULT_EDITOR_SETTINGS.executeMode; + editShowExecutionTargetPicker.value = DEFAULT_EDITOR_SETTINGS.showExecutionTargetPicker; editWordWrap.value = DEFAULT_EDITOR_SETTINGS.wordWrap; editConfirmDangerousSqlExecution.value = DEFAULT_EDITOR_SETTINGS.confirmDangerousSqlExecution; editAppLayout.value = DEFAULT_EDITOR_SETTINGS.appLayout; @@ -1676,7 +1682,15 @@ watch( -
+
+
+ +

{{ t("settings.showExecutionTargetPickerDescription") }}

+
+ +
+ +

{{ t("settings.wordWrapDescription") }}

diff --git a/apps/desktop/src/components/editor/QueryEditor.vue b/apps/desktop/src/components/editor/QueryEditor.vue index 04f36991a..a2be76bd9 100644 --- a/apps/desktop/src/components/editor/QueryEditor.vue +++ b/apps/desktop/src/components/editor/QueryEditor.vue @@ -319,6 +319,12 @@ function requestExecute() { const cursorPos = selection.head; const candidates = buildExecutionCandidates(doc, cursorPos, props.databaseType); if (candidates.length === 0) return false; + if (!settingsStore.editorSettings.showExecutionTargetPicker) { + const preferredKind = settingsStore.editorSettings.executeMode === "current" ? "cursor" : "all"; + const candidate = candidates.find((item) => item.kind === preferredKind) ?? candidates[0]; + emit("execute", candidate.sql); + return true; + } closePicker(); pickerCandidates.value = candidates; pickerActiveIndex.value = 0; diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts index f399848bc..6677b8ffe 100644 --- a/apps/desktop/src/i18n/locales/en.ts +++ b/apps/desktop/src/i18n/locales/en.ts @@ -2408,6 +2408,8 @@ export default { executeMode: "Execute Mode (Cmd+Enter)", executeModeAll: "Execute all SQL", executeModeCurrent: "Execute statement at cursor", + showExecutionTargetPicker: "Show execution target picker", + showExecutionTargetPickerDescription: "When enabled, running without a selection lets you choose between the current statement and all SQL.", wordWrap: "Word wrap", wordWrapDescription: "Wrap long SQL lines within the editor width", confirmDangerousSqlExecution: "Confirm before dangerous SQL", diff --git a/apps/desktop/src/i18n/locales/es.ts b/apps/desktop/src/i18n/locales/es.ts index fe2b0aa31..658ad3641 100644 --- a/apps/desktop/src/i18n/locales/es.ts +++ b/apps/desktop/src/i18n/locales/es.ts @@ -2068,6 +2068,8 @@ export default { executeMode: "Modo de ejecución (Cmd+Enter)", executeModeAll: "Ejecutar todo el SQL", executeModeCurrent: "Ejecutar sentencia en el cursor", + showExecutionTargetPicker: "Mostrar selector de objetivo", + showExecutionTargetPickerDescription: "Al activarlo, ejecutar sin selección permite elegir entre la sentencia actual y todo el SQL.", wordWrap: "Ajuste de línea", wordWrapDescription: "Ajustar las líneas largas de SQL al ancho del editor", confirmDangerousSqlExecution: "Confirmar antes de SQL peligroso", diff --git a/apps/desktop/src/i18n/locales/it.ts b/apps/desktop/src/i18n/locales/it.ts index 84aecbb3c..289062932 100644 --- a/apps/desktop/src/i18n/locales/it.ts +++ b/apps/desktop/src/i18n/locales/it.ts @@ -2143,6 +2143,8 @@ export default { executeMode: "Modalità Esecuzione (Cmd+Enter)", executeModeAll: "Esegui tutto l'SQL", executeModeCurrent: "Esegui istruzione al cursore", + showExecutionTargetPicker: "Mostra selettore destinazione", + showExecutionTargetPickerDescription: "Se attivo, l'esecuzione senza selezione permette di scegliere tra istruzione corrente e tutto l'SQL.", wordWrap: "A capo automatico", wordWrapDescription: "Incolonna le righe SQL lunghe entro la larghezza dell'editor", confirmDangerousSqlExecution: "Conferma prima di SQL pericoloso", diff --git a/apps/desktop/src/i18n/locales/ja.ts b/apps/desktop/src/i18n/locales/ja.ts index 98de2b358..49e4f2225 100644 --- a/apps/desktop/src/i18n/locales/ja.ts +++ b/apps/desktop/src/i18n/locales/ja.ts @@ -2362,6 +2362,8 @@ export default { executeMode: "実行モード (Cmd+Enter)", executeModeAll: "すべてのSQLを実行", executeModeCurrent: "カーソル位置の文を実行", + showExecutionTargetPicker: "実行対象ピッカーを表示", + showExecutionTargetPickerDescription: "有効にすると、選択なしで実行するときに現在の文とすべてのSQLを一時的に選べます。", wordWrap: "折り返し", wordWrapDescription: "長いSQL行をエディタ幅内で折り返します", confirmDangerousSqlExecution: "危険なSQLの前に確認", diff --git a/apps/desktop/src/i18n/locales/pt-BR.ts b/apps/desktop/src/i18n/locales/pt-BR.ts index 43ec18244..bced4d0d2 100644 --- a/apps/desktop/src/i18n/locales/pt-BR.ts +++ b/apps/desktop/src/i18n/locales/pt-BR.ts @@ -2154,6 +2154,8 @@ export default { executeMode: "Modo de execução (Cmd+Enter)", executeModeAll: "Executar todo o SQL", executeModeCurrent: "Executar instrução no cursor", + showExecutionTargetPicker: "Mostrar seletor de destino", + showExecutionTargetPickerDescription: "Quando ativado, executar sem seleção permite escolher entre a instrução atual e todo o SQL.", wordWrap: "Quebra de linha", wordWrapDescription: "Quebrar linhas SQL longas dentro da largura do editor", confirmDangerousSqlExecution: "Confirmar antes de SQL perigoso", diff --git a/apps/desktop/src/i18n/locales/zh-CN.ts b/apps/desktop/src/i18n/locales/zh-CN.ts index 18c5a1ea1..ae2f50e6e 100644 --- a/apps/desktop/src/i18n/locales/zh-CN.ts +++ b/apps/desktop/src/i18n/locales/zh-CN.ts @@ -2432,6 +2432,8 @@ export default { executeMode: "执行模式 (Cmd+Enter)", executeModeAll: "执行全部 SQL", executeModeCurrent: "执行光标所在语句", + showExecutionTargetPicker: "显示执行目标选择器", + showExecutionTargetPickerDescription: "开启后,无选区执行时可在当前语句和全部 SQL 之间临时选择。", wordWrap: "自动换行", wordWrapDescription: "长 SQL 在编辑器宽度内自动折行显示", confirmDangerousSqlExecution: "执行危险 SQL 前弹出确认", diff --git a/apps/desktop/src/i18n/locales/zh-TW.ts b/apps/desktop/src/i18n/locales/zh-TW.ts index fea0b2f6a..e9f438f98 100644 --- a/apps/desktop/src/i18n/locales/zh-TW.ts +++ b/apps/desktop/src/i18n/locales/zh-TW.ts @@ -2190,6 +2190,8 @@ export default { executeMode: "執行模式 (Cmd+Enter)", executeModeAll: "執行全部 SQL", executeModeCurrent: "執行指標所在語句", + showExecutionTargetPicker: "顯示執行目標選擇器", + showExecutionTargetPickerDescription: "啟用後,無選取執行時可在目前語句與全部 SQL 之間臨時選擇。", wordWrap: "自動換行", wordWrapDescription: "長 SQL 在編輯器寬度內自動折行顯示", confirmDangerousSqlExecution: "執行危險 SQL 前彈出確認", diff --git a/apps/desktop/src/stores/settingsStore.ts b/apps/desktop/src/stores/settingsStore.ts index 28cca0839..6c427efcc 100644 --- a/apps/desktop/src/stores/settingsStore.ts +++ b/apps/desktop/src/stores/settingsStore.ts @@ -307,6 +307,7 @@ export interface EditorSettings { customThemes: CustomTheme[]; activeCustomThemeId: string; executeMode: "all" | "current"; + showExecutionTargetPicker: boolean; wordWrap: boolean; confirmDangerousSqlExecution: boolean; compactTabTitle: boolean; @@ -405,6 +406,7 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = { customThemes: [...DEFAULT_CUSTOM_THEMES], activeCustomThemeId: "default", executeMode: "all", + showExecutionTargetPicker: false, wordWrap: false, confirmDangerousSqlExecution: true, compactTabTitle: false, @@ -568,6 +570,7 @@ export function normalizeEditorSettings(settings: Partial, exist })(), activeCustomThemeId: settings.activeCustomThemeId ?? "default", executeMode: settings.executeMode ?? DEFAULT_EDITOR_SETTINGS.executeMode, + showExecutionTargetPicker: settings.showExecutionTargetPicker ?? DEFAULT_EDITOR_SETTINGS.showExecutionTargetPicker, wordWrap: settings.wordWrap ?? DEFAULT_EDITOR_SETTINGS.wordWrap, confirmDangerousSqlExecution: settings.confirmDangerousSqlExecution ?? DEFAULT_EDITOR_SETTINGS.confirmDangerousSqlExecution, compactTabTitle: settings.compactTabTitle ?? DEFAULT_EDITOR_SETTINGS.compactTabTitle, @@ -726,6 +729,7 @@ export const useSettingsStore = defineStore("settings", () => { } } if (partial.executeMode !== undefined) editorSettings.value.executeMode = partial.executeMode; + if (partial.showExecutionTargetPicker !== undefined) editorSettings.value.showExecutionTargetPicker = partial.showExecutionTargetPicker; if (partial.wordWrap !== undefined) editorSettings.value.wordWrap = partial.wordWrap; if (partial.confirmDangerousSqlExecution !== undefined) editorSettings.value.confirmDangerousSqlExecution = partial.confirmDangerousSqlExecution; if (partial.compactTabTitle !== undefined) editorSettings.value.compactTabTitle = partial.compactTabTitle; diff --git a/crates/dbx-core/src/db/sqlserver.rs b/crates/dbx-core/src/db/sqlserver.rs index 79258da0f..3556986c5 100644 --- a/crates/dbx-core/src/db/sqlserver.rs +++ b/crates/dbx-core/src/db/sqlserver.rs @@ -559,6 +559,18 @@ fn sqlserver_cell_to_json(cell: &ColumnData<'static>) -> serde_json::Value { return serde_json::Value::String(v.to_string()); } if let Ok(Some(v)) = as tiberius::FromSqlOwned>::from_sql_owned(cell.clone()) { + // Try to decode as UTF-16 LE (SQL Server NVARCHAR encoding) with lossy conversion + // This handles cases where sys.sql_modules.definition contains invalid UTF-8 sequences + if v.len() >= 2 && v.len() % 2 == 0 { + let utf16_units: Vec = + v.chunks_exact(2).map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]])).collect(); + if let Ok(decoded) = String::from_utf16(&utf16_units) { + return serde_json::Value::String(decoded); + } + // Use lossy conversion as fallback for invalid UTF-16 sequences + let lossy_decoded = String::from_utf16_lossy(&utf16_units); + return serde_json::Value::String(lossy_decoded); + } return super::binary_value_to_json(&v); } serde_json::Value::Null