fix(editor): keep semicolon cursor on statement

This commit is contained in:
t8y2
2026-07-03 00:00:44 +08:00
parent 8be2136658
commit ff73a1df42
3 changed files with 33 additions and 1 deletions
@@ -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;");
});
});
@@ -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"));
+7 -1
View File
@@ -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);
}