feat(settings): add show execution target picker option and related UI elements

This commit is contained in:
t8y2 2026-06-22 12:05:47 +08:00
parent d7e52779c3
commit 9418e37c04
11 changed files with 51 additions and 1 deletions

View File

@ -102,6 +102,7 @@ const editCustomThemes = ref<CustomTheme[]>([...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(
</Select>
</div>
<div class="flex items-center justify-between gap-4 self-end rounded-md border bg-muted/20 px-3 py-2">
<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>
<p class="text-xs text-muted-foreground">{{ t("settings.showExecutionTargetPickerDescription") }}</p>
</div>
<Switch id="editor-show-execution-target-picker" v-model="editShowExecutionTargetPicker" 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-word-wrap">{{ t("settings.wordWrap") }}</Label>
<p class="text-xs text-muted-foreground">{{ t("settings.wordWrapDescription") }}</p>

View File

@ -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;

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -2362,6 +2362,8 @@ export default {
executeMode: "実行モード (Cmd+Enter)",
executeModeAll: "すべてのSQLを実行",
executeModeCurrent: "カーソル位置の文を実行",
showExecutionTargetPicker: "実行対象ピッカーを表示",
showExecutionTargetPickerDescription: "有効にすると、選択なしで実行するときに現在の文とすべてのSQLを一時的に選べます。",
wordWrap: "折り返し",
wordWrapDescription: "長いSQL行をエディタ幅内で折り返します",
confirmDangerousSqlExecution: "危険なSQLの前に確認",

View File

@ -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",

View File

@ -2432,6 +2432,8 @@ export default {
executeMode: "执行模式 (Cmd+Enter)",
executeModeAll: "执行全部 SQL",
executeModeCurrent: "执行光标所在语句",
showExecutionTargetPicker: "显示执行目标选择器",
showExecutionTargetPickerDescription: "开启后,无选区执行时可在当前语句和全部 SQL 之间临时选择。",
wordWrap: "自动换行",
wordWrapDescription: "长 SQL 在编辑器宽度内自动折行显示",
confirmDangerousSqlExecution: "执行危险 SQL 前弹出确认",

View File

@ -2190,6 +2190,8 @@ export default {
executeMode: "執行模式 (Cmd+Enter)",
executeModeAll: "執行全部 SQL",
executeModeCurrent: "執行指標所在語句",
showExecutionTargetPicker: "顯示執行目標選擇器",
showExecutionTargetPickerDescription: "啟用後,無選取執行時可在目前語句與全部 SQL 之間臨時選擇。",
wordWrap: "自動換行",
wordWrapDescription: "長 SQL 在編輯器寬度內自動折行顯示",
confirmDangerousSqlExecution: "執行危險 SQL 前彈出確認",

View File

@ -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<EditorSettings>, 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;

View File

@ -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)) = <Vec<u8> 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<u16> =
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