fix(sql): stop completion at the next top-level statement

This commit is contained in:
dienaso authored and GitHub committed 2026-09-18 01:35:53 +08:00
1 parent 7b164d0084
commit 4cdf18d652
2 files changed
+137 -3

No files matched your search

+78 -3
View File
@@ -2134,18 +2134,93 @@ function currentSqlLikeLineBlockSpan(sql: string, cursor: number, activeStatemen
return { start, end: blockEnd == null ? activeStatementSpan.end : Math.min(activeStatementSpan.end, blockEnd) };
}
// Equal-length masking of string/comment characters, so parenthesis depth and
// line-start keywords can be scanned on the masked copy while offsets stay
// valid against the original text.
function maskSqlForStructureScan(sql: string): string {
let out = "";
let i = 0;
while (i < sql.length) {
const ch = sql[i]!;
if (ch === "'" || ch === '"' || ch === "`") {
const quote = ch;
out += ch;
i += 1;
while (i < sql.length) {
if (sql[i] === "\\" && i + 1 < sql.length) {
out += " ";
i += 2;
continue;
}
const same = sql[i] === quote;
out += same ? quote : " ";
i += 1;
if (same) break;
}
continue;
}
if (ch === "-" && sql[i + 1] === "-") {
while (i < sql.length && sql[i] !== "\n") {
out += " ";
i += 1;
}
continue;
}
if (ch === "/" && sql[i + 1] === "*") {
out += " ";
i += 2;
while (i < sql.length) {
if (sql[i] === "*" && sql[i + 1] === "/") {
out += " ";
i += 2;
break;
}
out += sql[i] === "\n" ? "\n" : " ";
i += 1;
}
continue;
}
out += ch;
i += 1;
}
return out;
}
// Statements users type without a trailing semicolon: a line starting one of
// these at top level begins a new statement block (t8y2/dbx#9370).
const STATEMENT_START_LINE_PATTERN = /^(?:select|with|insert|update|delete|create|drop|alter)\b/i;
function currentLineBlockEnd(sql: string, cursor: number, start: number): number | null {
const masked = maskSqlForStructureScan(sql);
let lineStart = sql.lastIndexOf("\n", cursor - 1) + 1;
let depth = 0;
for (let i = start; i < lineStart; i += 1) {
const ch = masked[i];
if (ch === "(") depth += 1;
else if (ch === ")") depth = Math.max(0, depth - 1);
}
let atCursorLine = true;
while (lineStart < sql.length) {
const lineEnd = sql.indexOf("\n", lineStart);
const boundedLineEnd = lineEnd >= 0 ? lineEnd : sql.length;
const line = sql.slice(lineStart, boundedLineEnd);
const trimmed = line.trimStart();
if (lineStart > start && (!trimmed || /^(get|post|put|delete|patch|head)\s+\//i.test(trimmed))) {
const originalTrimmed = sql.slice(lineStart, boundedLineEnd).trimStart();
const trimmed = masked.slice(lineStart, boundedLineEnd).trimStart();
if (lineStart > start && (!originalTrimmed || /^(get|post|put|delete|patch|head)\s+\//i.test(originalTrimmed))) {
return lineStart;
}
// The cursor's own line always belongs to the block; only a following
// top-level statement line ends it.
if (!atCursorLine && lineStart > start && depth === 0 && STATEMENT_START_LINE_PATTERN.test(trimmed)) {
return lineStart;
}
if (lineEnd < 0) break;
for (let i = lineStart; i < boundedLineEnd; i += 1) {
const ch = masked[i];
if (ch === "(") depth += 1;
else if (ch === ")") depth = Math.max(0, depth - 1);
}
lineStart = lineEnd + 1;
atCursorLine = false;
}
return null;
}