mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-29 16:18:55 +00:00
feat: add execute mode settings and cursor position handling in SQL editor
This commit is contained in:
+10
-1
@@ -124,6 +124,7 @@ const showUpdateDialog = ref(false);
|
||||
const dangerSql = ref("");
|
||||
const pendingDangerSql = ref("");
|
||||
const selectedSql = ref("");
|
||||
const cursorPos = ref(0);
|
||||
const formatSqlRequestId = ref(0);
|
||||
const activeOutputView = ref<"result" | "explain">("result");
|
||||
const showDangerDialog = ref(false);
|
||||
@@ -455,7 +456,10 @@ const activeTab = computed(() =>
|
||||
|
||||
const executableSql = computed(() => {
|
||||
const tab = activeTab.value;
|
||||
return tab ? resolveExecutableSql(tab.sql, selectedSql.value) : "";
|
||||
return tab ? resolveExecutableSql(tab.sql, selectedSql.value, {
|
||||
mode: settingsStore.editorSettings.executeMode,
|
||||
cursorPos: cursorPos.value,
|
||||
}) : "";
|
||||
});
|
||||
|
||||
watch(() => queryStore.activeTabId, () => {
|
||||
@@ -615,6 +619,10 @@ function onEditorSelectionChange(val: string) {
|
||||
selectedSql.value = val;
|
||||
}
|
||||
|
||||
function onEditorCursorChange(pos: number) {
|
||||
cursorPos.value = pos;
|
||||
}
|
||||
|
||||
function formatActiveSql() {
|
||||
const tab = activeTab.value;
|
||||
if (!tab || tab.mode !== "query" || !tab.sql.trim()) return;
|
||||
@@ -1464,6 +1472,7 @@ async function setupFileDrop() {
|
||||
:format-request-id="formatSqlRequestId"
|
||||
@update:model-value="onEditorUpdate"
|
||||
@selection-change="onEditorSelectionChange"
|
||||
@cursor-change="onEditorCursorChange"
|
||||
@format-error="onFormatSqlError"
|
||||
@execute="tryExecute"
|
||||
/>
|
||||
|
||||
@@ -32,6 +32,7 @@ const emit = defineEmits<{
|
||||
const editFontFamily = ref(settingsStore.editorSettings.fontFamily);
|
||||
const editFontSize = ref(settingsStore.editorSettings.fontSize);
|
||||
const editTheme = ref(settingsStore.editorSettings.theme);
|
||||
const editExecuteMode = ref(settingsStore.editorSettings.executeMode);
|
||||
|
||||
// Sync from store when dialog opens
|
||||
watch(() => props.open, (open) => {
|
||||
@@ -39,6 +40,7 @@ watch(() => props.open, (open) => {
|
||||
editFontFamily.value = settingsStore.editorSettings.fontFamily;
|
||||
editFontSize.value = settingsStore.editorSettings.fontSize;
|
||||
editTheme.value = settingsStore.editorSettings.theme;
|
||||
editExecuteMode.value = settingsStore.editorSettings.executeMode;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -46,7 +48,8 @@ function hasChanges(): boolean {
|
||||
return (
|
||||
editFontFamily.value !== settingsStore.editorSettings.fontFamily ||
|
||||
editFontSize.value !== settingsStore.editorSettings.fontSize ||
|
||||
editTheme.value !== settingsStore.editorSettings.theme
|
||||
editTheme.value !== settingsStore.editorSettings.theme ||
|
||||
editExecuteMode.value !== settingsStore.editorSettings.executeMode
|
||||
);
|
||||
}
|
||||
|
||||
@@ -55,6 +58,7 @@ function applySettings() {
|
||||
fontFamily: editFontFamily.value,
|
||||
fontSize: editFontSize.value,
|
||||
theme: editTheme.value,
|
||||
executeMode: editExecuteMode.value,
|
||||
});
|
||||
emit("update:open", false);
|
||||
}
|
||||
@@ -63,6 +67,11 @@ function resetDefaults() {
|
||||
editFontFamily.value = DEFAULT_EDITOR_SETTINGS.fontFamily;
|
||||
editFontSize.value = DEFAULT_EDITOR_SETTINGS.fontSize;
|
||||
editTheme.value = DEFAULT_EDITOR_SETTINGS.theme;
|
||||
editExecuteMode.value = DEFAULT_EDITOR_SETTINGS.executeMode;
|
||||
}
|
||||
|
||||
function onExecuteModeChange(v: any) {
|
||||
if (v === "all" || v === "current") editExecuteMode.value = v;
|
||||
}
|
||||
|
||||
function onFontFamilyChange(v: any) {
|
||||
@@ -160,7 +169,7 @@ watch(() => props.open, (open) => {
|
||||
|
||||
<template>
|
||||
<Dialog :open="open" @update:open="(v: boolean) => emit('update:open', v)">
|
||||
<DialogContent class="sm:max-w-[720px] max-h-[calc(100vh-80px)] overflow-y-auto">
|
||||
<DialogContent class="sm:max-w-[720px] max-h-[calc(100vh-80px)] overflow-y-auto overflow-x-hidden">
|
||||
<DialogHeader>
|
||||
<DialogTitle class="flex items-center gap-2">
|
||||
<Settings class="h-4 w-4" />
|
||||
@@ -243,6 +252,19 @@ watch(() => props.open, (open) => {
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label>{{ t('settings.executeMode') }}</Label>
|
||||
<Select :model-value="editExecuteMode" @update:model-value="onExecuteModeChange">
|
||||
<SelectTrigger>
|
||||
<SelectValue :placeholder="t('settings.executeMode')" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">{{ t('settings.executeModeAll') }}</SelectItem>
|
||||
<SelectItem value="current">{{ t('settings.executeModeCurrent') }}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<!-- Live Preview -->
|
||||
@@ -252,7 +274,7 @@ watch(() => props.open, (open) => {
|
||||
class="rounded-md border overflow-auto max-w-full"
|
||||
:class="editTheme === 'vscode-light' || editTheme === 'duotone-light' || editTheme === 'xcode' ? 'border-border' : 'border-border/50'"
|
||||
>
|
||||
<div ref="previewRef" style="height: 160px; min-width: 100%" />
|
||||
<div ref="previewRef" style="min-width: 100%" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -24,6 +24,7 @@ const props = defineProps<{
|
||||
const emit = defineEmits<{
|
||||
"update:modelValue": [value: string];
|
||||
"selectionChange": [value: string];
|
||||
"cursorChange": [pos: number];
|
||||
"formatError": [message: string];
|
||||
execute: [sql: string];
|
||||
}>();
|
||||
@@ -245,6 +246,7 @@ onMounted(async () => {
|
||||
}
|
||||
if (update.selectionSet || update.docChanged) {
|
||||
emit("selectionChange", selectedSqlFromView(update.view));
|
||||
emit("cursorChange", update.state.selection.main.head);
|
||||
}
|
||||
}),
|
||||
fontThemeComp.of(
|
||||
|
||||
@@ -610,6 +610,9 @@ export default {
|
||||
selectTheme: "Select theme...",
|
||||
apply: "Apply",
|
||||
resetDefaults: "Reset Defaults",
|
||||
executeMode: "Execute Mode (Cmd+Enter)",
|
||||
executeModeAll: "Execute all SQL",
|
||||
executeModeCurrent: "Execute statement at cursor",
|
||||
preview: "Live Preview",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -608,6 +608,9 @@ export default {
|
||||
selectTheme: "选择主题...",
|
||||
apply: "应用",
|
||||
resetDefaults: "恢复默认",
|
||||
executeMode: "执行模式 (Cmd+Enter)",
|
||||
executeModeAll: "执行全部 SQL",
|
||||
executeModeCurrent: "执行光标所在语句",
|
||||
preview: "实时预览",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,4 +1,18 @@
|
||||
export function resolveExecutableSql(fullSql: string, selectedSql: string): string {
|
||||
import { findStatementAtCursor } from "./sqlStatementSplit";
|
||||
|
||||
export type ExecuteMode = "all" | "current";
|
||||
|
||||
export function resolveExecutableSql(
|
||||
fullSql: string,
|
||||
selectedSql: string,
|
||||
options?: { mode?: ExecuteMode; cursorPos?: number },
|
||||
): string {
|
||||
const trimmedSelection = selectedSql.trim();
|
||||
return trimmedSelection ? trimmedSelection : fullSql;
|
||||
if (trimmedSelection) return trimmedSelection;
|
||||
|
||||
if (options?.mode === "current" && options.cursorPos !== undefined) {
|
||||
return findStatementAtCursor(fullSql, options.cursorPos);
|
||||
}
|
||||
|
||||
return fullSql;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
export function findStatementAtCursor(sql: string, cursorPos: number): string {
|
||||
const statements = splitStatements(sql);
|
||||
for (const stmt of statements) {
|
||||
if (cursorPos >= stmt.start && cursorPos <= stmt.end) {
|
||||
return stmt.text;
|
||||
}
|
||||
}
|
||||
if (statements.length > 0) {
|
||||
const last = statements[statements.length - 1];
|
||||
if (cursorPos >= last.end) return last.text;
|
||||
}
|
||||
return sql.trim();
|
||||
}
|
||||
|
||||
interface StatementRange {
|
||||
text: string;
|
||||
start: number;
|
||||
end: number;
|
||||
}
|
||||
|
||||
function splitStatements(sql: string): StatementRange[] {
|
||||
const results: StatementRange[] = [];
|
||||
let i = 0;
|
||||
let stmtStart = 0;
|
||||
|
||||
while (i < sql.length) {
|
||||
const ch = sql[i];
|
||||
|
||||
if (ch === "-" && sql[i + 1] === "-") {
|
||||
i = skipLineComment(sql, i);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ch === "/" && sql[i + 1] === "*") {
|
||||
i = skipBlockComment(sql, i);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ch === "'" || ch === '"') {
|
||||
i = skipQuoted(sql, i, ch);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ch === "`") {
|
||||
i = skipQuoted(sql, i, "`");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ch === ";") {
|
||||
const text = sql.slice(stmtStart, i).trim();
|
||||
if (text) {
|
||||
results.push({ text, start: stmtStart, end: i + 1 });
|
||||
}
|
||||
stmtStart = i + 1;
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
const trailing = sql.slice(stmtStart).trim();
|
||||
if (trailing) {
|
||||
results.push({ text: trailing, start: stmtStart, end: sql.length });
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
function skipLineComment(sql: string, pos: number): number {
|
||||
let i = pos + 2;
|
||||
while (i < sql.length && sql[i] !== "\n") i++;
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
function skipBlockComment(sql: string, pos: number): number {
|
||||
let i = pos + 2;
|
||||
while (i < sql.length - 1) {
|
||||
if (sql[i] === "*" && sql[i + 1] === "/") return i + 2;
|
||||
i++;
|
||||
}
|
||||
return sql.length;
|
||||
}
|
||||
|
||||
function skipQuoted(sql: string, pos: number, quote: string): number {
|
||||
let i = pos + 1;
|
||||
while (i < sql.length) {
|
||||
if (sql[i] === quote) {
|
||||
if (sql[i + 1] === quote) {
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
return i + 1;
|
||||
}
|
||||
if (sql[i] === "\\" && quote === "'") {
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return sql.length;
|
||||
}
|
||||
@@ -25,6 +25,7 @@ export interface EditorSettings {
|
||||
fontFamily: string;
|
||||
fontSize: number;
|
||||
theme: EditorTheme;
|
||||
executeMode: "all" | "current";
|
||||
}
|
||||
|
||||
export const EDITOR_THEMES: { value: EditorTheme; label: string; dark: boolean }[] = [
|
||||
@@ -53,6 +54,7 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = {
|
||||
fontFamily: "'JetBrains Mono', 'Fira Code', monospace",
|
||||
fontSize: 13,
|
||||
theme: "one-dark",
|
||||
executeMode: "all",
|
||||
};
|
||||
|
||||
export const STORAGE_KEY = "dbx-editor-settings";
|
||||
@@ -68,6 +70,7 @@ function loadEditorSettings(): EditorSettings {
|
||||
fontFamily: parsed.fontFamily ?? DEFAULT_EDITOR_SETTINGS.fontFamily,
|
||||
fontSize: parsed.fontSize ?? DEFAULT_EDITOR_SETTINGS.fontSize,
|
||||
theme: parsed.theme ?? DEFAULT_EDITOR_SETTINGS.theme,
|
||||
executeMode: parsed.executeMode ?? DEFAULT_EDITOR_SETTINGS.executeMode,
|
||||
};
|
||||
}
|
||||
} catch { /* ignore */ }
|
||||
|
||||
Reference in New Issue
Block a user