Files
dbx/apps/desktop/src/lib/sql/statementDelimiter.ts
T

75 lines
2.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
export interface StatementDelimiterDocument {
readonly length: number;
sliceString(from: number, to: number): string;
}
type StatementDelimiterSource = string | StatementDelimiterDocument;
export function trailingStatementDelimiterPosition(source: StatementDelimiterSource, rangeTo: number): number | null {
let delimiterPos = rangeTo;
let lineBreakCount = 0;
while (delimiterPos < source.length) {
const char = sliceSource(source, delimiterPos, delimiterPos + 1);
if (!/\s/u.test(char)) break;
if (char === "\n" && ++lineBreakCount > 1) return null;
delimiterPos += 1;
}
return sliceSource(source, delimiterPos, delimiterPos + 1) === ";" ? delimiterPos : null;
}
export function cursorBelongsToTrailingStatementDelimiter(source: StatementDelimiterSource, rangeTo: number, cursorPos: number): boolean {
if (cursorPos < rangeTo) return false;
const delimiterPos = trailingStatementDelimiterPosition(source, rangeTo);
if (delimiterPos === null) return false;
if (cursorPos <= delimiterPos + 1) return true;
const afterDelimiter = sliceSource(source, delimiterPos + 1, cursorPos);
// 分号到光标之间允许是空白或尾部注释(如 `SELECT 1; -- 备注`),
// 块注释自身可以跨行,但注释之外的换行仍表示光标已进入下一行
// 否则行尾注释后的光标会被下一条语句的前置区域错误认领
return trailingGapIsWhitespaceOrComment(afterDelimiter);
}
/**
* 判断语句分号与光标之间的间隙是否只包含空白和注释。
* 块注释内部允许换行;注释之外的换行表示光标已离开分号所在行。
*/
function trailingGapIsWhitespaceOrComment(gap: string): boolean {
let i = 0;
while (i < gap.length) {
const ch = gap[i] ?? "";
const next = gap[i + 1] ?? "";
// 普通空白直接跳过
if (ch === " " || ch === "\t" || ch === "\r") {
i += 1;
continue;
}
if (ch === "\n") return false;
// 行注释:`--`(SQL 标准)或 `//`(MongoDB shell),其后内容直到行尾都是注释
if ((ch === "-" && next === "-") || (ch === "/" && next === "/")) return true;
// `#` 行注释(MySQL 等),但 `#{` 是 MyBatis 参数语法,不是注释
if (ch === "#" && next !== "{") return true;
// 块注释 /* ... */:跳过整段后继续检查剩余间隙
if (ch === "/" && next === "*") {
const close = gap.indexOf("*/", i + 2);
// 未闭合的块注释:剩余部分全部视为注释
if (close === -1) return true;
i = close + 2;
continue;
}
// 出现其他实际内容,说明光标后面还有语句文本,不能归属上一条语句
return false;
}
return true;
}
function sliceSource(source: StatementDelimiterSource, from: number, to: number): string {
return typeof source === "string" ? source.slice(from, to) : source.sliceString(from, to);
}