Files
dbx/apps/desktop/src/lib/sqlExecutionTarget.ts
T
uicky 4d2afcad90 feat(editor): add SQL execution target picker with preview decoration
When the user triggers SQL execution via shortcut/toolbar/context-menu
without a manual selection, a floating picker now surfaces the available
execution targets:

- "Current SQL" — the statement at the cursor
- "All SQL"   — the entire editor content

Each candidate is highlighted as a preview decoration in the editor as
the user navigates the picker with arrow keys. Only a single entry is
shown when the cursor statement equals the full document.

前端 SQL 范围解析器 (sqlStatementRanges.ts) 按分号拆分语句,同时跳过
字符串、引号标识符、注释和美元引用体中的分隔符,与后端 dbx-core splitter
逻辑一致。选取器支持键盘导航、鼠标悬停高亮、Enter 确认、Esc/失焦取消。

Changes:
- sqlExecutionTarget.ts: add SqlExecutionCandidate, SqlExecutionOverride types
- sqlStatementRanges.ts: frontend SQL tokeniser (new, 848 lines)
- SqlExecutionTargetPicker.vue: accessible floating listbox (new, 109 lines)
- QueryEditor.vue: preview StateEffect/StateField; requestExecute() replaces
  executeCurrentSql() to route through picker
- ContentArea.vue: expose requestQueryEditorExecute() to parent
- App.vue: toolbar + global shortcut → requestActiveEditorExecute()
- i18n: editor.executionPicker keys for all locales
- tests: 26 new unit tests for sqlStatementRanges (all 1121 tests pass)
2026-06-20 22:28:02 +08:00

61 lines
1.9 KiB
TypeScript

import * as api from "./api";
import type { DatabaseType } from "@/types/database";
export type ExecuteMode = "all" | "current";
export interface SqlExecutionSnapshot {
fullSql: string;
selectedSql: string;
cursorPos: number;
}
export type SqlExecutionOverride = string | SqlExecutionSnapshot;
export type SqlExecutionTargetKind = "cursor" | "all";
/**
* A candidate execution target surfaced by the execution target picker.
* `from`/`to` are offsets into the full document so the editor can highlight
* the corresponding range as a preview while the picker is open.
*/
export interface SqlExecutionCandidate {
kind: SqlExecutionTargetKind;
label: string;
sql: string;
from: number;
to: number;
}
export interface SqlExecutionChoiceRequest {
fullSql: string;
selectedSql: string;
cursorPos: number;
candidates: SqlExecutionCandidate[];
}
export function isSqlExecutionSnapshot(value: SqlExecutionOverride | undefined): value is SqlExecutionSnapshot {
return typeof value === "object" && value !== null && typeof value.fullSql === "string" && typeof value.selectedSql === "string" && typeof value.cursorPos === "number";
}
export function resolveExecutableSql(fullSql: string, selectedSql: string, options?: { mode?: ExecuteMode; cursorPos?: number }): string {
const trimmedSelection = selectedSql.trim();
if (trimmedSelection) return trimmedSelection;
if (options?.mode === "current" && options.cursorPos !== undefined) {
return fullSql;
}
return fullSql;
}
export async function resolveExecutableSqlWithBackend(fullSql: string, selectedSql: string, options?: { mode?: ExecuteMode; cursorPos?: number; databaseType?: DatabaseType }): Promise<string> {
const trimmedSelection = selectedSql.trim();
if (trimmedSelection) return trimmedSelection;
if (options?.mode === "current" && options.cursorPos !== undefined) {
return await api.findStatementAtCursor(fullSql, options.cursorPos, options.databaseType);
}
return fullSql;
}