From ff73a1df42bb7d87937b38b26baa0af63fa8bc6e Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Fri, 3 Jul 2026 00:00:30 +0800 Subject: [PATCH] fix(editor): keep semicolon cursor on statement --- .../src/lib/__tests__/sqlExecutionTarget.spec.ts | 12 ++++++++++++ .../src/lib/__tests__/sqlStatementRanges.spec.ts | 14 ++++++++++++++ apps/desktop/src/lib/sqlStatementRanges.ts | 8 +++++++- 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 apps/desktop/src/lib/__tests__/sqlExecutionTarget.spec.ts diff --git a/apps/desktop/src/lib/__tests__/sqlExecutionTarget.spec.ts b/apps/desktop/src/lib/__tests__/sqlExecutionTarget.spec.ts new file mode 100644 index 0000000000..fb4d8f0d8f --- /dev/null +++ b/apps/desktop/src/lib/__tests__/sqlExecutionTarget.spec.ts @@ -0,0 +1,12 @@ +import { describe, expect, it } from "vitest"; +import { resolveExecutableSql } from "../sqlExecutionTarget"; + +describe("resolveExecutableSql", () => { + it("uses selected SQL before cursor-mode resolution", () => { + const sql = "select 1;\n\nselect 2;"; + const selectedSql = " select 2; "; + const cursorAfterFirstSemicolon = sql.indexOf(";") + 1; + + expect(resolveExecutableSql(sql, selectedSql, { mode: "current", cursorPos: cursorAfterFirstSemicolon })).toBe("select 2;"); + }); +}); diff --git a/apps/desktop/src/lib/__tests__/sqlStatementRanges.spec.ts b/apps/desktop/src/lib/__tests__/sqlStatementRanges.spec.ts index e2e1cf0944..bea6bfdaac 100644 --- a/apps/desktop/src/lib/__tests__/sqlStatementRanges.spec.ts +++ b/apps/desktop/src/lib/__tests__/sqlStatementRanges.spec.ts @@ -172,6 +172,13 @@ describe("statementRangeAtCursor", () => { expect(range?.sql.trim()).toBe("SELECT *\nFROM system_dept"); }); + it("keeps a semicolon-line-end cursor on the current multi-line statement", () => { + const sql = "SELECT *\nFROM system_dept;"; + const gapPos = sql.indexOf(";") + 1; + const range = statementRangeAtCursor(sql, gapPos); + expect(range?.sql.trim()).toBe("SELECT *\nFROM system_dept"); + }); + it("returns the next same-line statement when the cursor is inside it", () => { const sql = "SELECT 1; SELECT 2;"; const pos = indexOf(sql, "SELECT 2") + 1; @@ -409,6 +416,13 @@ describe("buildExecutionCandidates", () => { expect(candidateLabels(candidates)).toEqual(["currentStatement", "allStatements"]); }); + it("uses the current statement when the cursor is immediately after its semicolon before a blank line", () => { + const sql = "select 1;\n\nselect 2;"; + const cursorAfterFirstSemicolon = sql.indexOf(";") + 1; + const candidates = buildExecutionCandidates(sql, cursorAfterFirstSemicolon); + expect(candidateSummaries(candidates)).toEqual(["cursor:select 1", "all:select 1;\n\nselect 2;"]); + }); + it("dedupes when the cursor statement equals the full document", () => { const sql = "SELECT 1;"; const candidates = buildExecutionCandidates(sql, indexOf(sql, "1")); diff --git a/apps/desktop/src/lib/sqlStatementRanges.ts b/apps/desktop/src/lib/sqlStatementRanges.ts index 7adf14ca1b..487bba2ccf 100644 --- a/apps/desktop/src/lib/sqlStatementRanges.ts +++ b/apps/desktop/src/lib/sqlStatementRanges.ts @@ -356,6 +356,13 @@ export function statementRangeAtCursor(sql: string, cursorPos: number, databaseT if (pos >= statement.from && pos <= statement.to) { return rangeForCursorInSoftRanges(sql, softRanges, pos) ?? rangeFor(statement, sql); } + const next = statements[index + 1]; + // A caret after a statement's semicolon still belongs to that statement + // until the next statement's text begins. + if (pos > statement.to && (!next || pos < next.from) && isCursorInSameLineDelimiterGap(sql, statement.to, pos)) { + return rangeForCursorInSoftRanges(sql, softRanges, pos) ?? rangeFor(statement, sql); + } + // Cursor in indentation or inter-statement whitespace immediately before // the statement should still target that statement, while the returned // execution range remains tight around the SQL text itself. @@ -368,7 +375,6 @@ export function statementRangeAtCursor(sql: string, cursorPos: number, databaseT return rangeForCursorInSoftRanges(sql, softRanges, pos) ?? rangeFor(statement, sql); } - const next = statements[index + 1]; if (pos > statement.to && (!next || pos < next.hitFrom) && isCursorOnStatementLine(sql, pos, statement)) { return rangeForCursorInSoftRanges(sql, softRanges, pos) ?? rangeFor(statement, sql); }