fix(editor): keep select column suggestions active

This commit is contained in:
t8y2 2026-06-22 16:58:32 +08:00
parent df94a7c1bf
commit 9c116e8f7e
2 changed files with 100 additions and 2 deletions

View File

@ -1547,6 +1547,9 @@ export function getSqlCompletionContext(sql: string, cursor: number): SqlComplet
const prioritizeSelectAliases = isInOrderOrGroupByContext(beforeCursor);
const inCallRoutineContext = isCallRoutineContext(beforeCursor);
const inPotentialPackageMemberContext = !!qualifier && !exclusiveTableSuggestions && !insertInfo && !oracleTableFunctionContext;
const suggestColumns = !!qualifier || !!updateInfo?.inSetClause || (inColumnContext && referencedTables.length > 0);
const preferColumnsOverGlobalRoutines = suggestColumns && referencedTables.length > 0 && !qualifier;
const suggestRoutines = inCallRoutineContext || oracleTableFunctionContext || inPotentialPackageMemberContext || (!preferColumnsOverGlobalRoutines && !exclusiveTableSuggestions && !exclusiveColumnSuggestions && !insertInfo && prefix.length >= 2);
const statementKind = detectStatementKind(beforeCursor || fullStatement);
const preferredKeywords = preferredKeywordsForCompletion(updateInfo, deleteInfo);
@ -1556,9 +1559,9 @@ export function getSqlCompletionContext(sql: string, cursor: number): SqlComplet
qualifier: insertInfo ? undefined : qualifier,
qualifierParts: insertInfo ? undefined : qualifierParts,
suggestTables: insertInfo ? false : afterTableTrigger,
suggestColumns: !!qualifier || !!updateInfo?.inSetClause || (inColumnContext && referencedTables.length > 0),
suggestColumns,
suggestKeywords: !exclusiveTableSuggestions && !exclusiveColumnSuggestions && !insertInfo && !inCallRoutineContext,
suggestRoutines: inCallRoutineContext || oracleTableFunctionContext || inPotentialPackageMemberContext || (!exclusiveTableSuggestions && !exclusiveColumnSuggestions && !insertInfo && prefix.length >= 2),
suggestRoutines,
suggestJoinConditions: insertInfo ? false : inJoinConditionContext && referencedTables.length >= 2,
exclusiveTableSuggestions: insertInfo ? false : exclusiveTableSuggestions,
exclusiveColumnSuggestions: exclusiveColumnSuggestions || !!insertInfo || !!updateInfo?.inSetClause,
@ -1667,6 +1670,8 @@ function parseTrailingIdentifierPart(input: string, endExclusive: number): { sta
function isInColumnContext(beforeCursor: string): boolean {
if (!beforeCursor) return false;
if (isInSelectListContext(beforeCursor)) return true;
// Strip string literals
const cleaned = beforeCursor.replace(/'[^']*'/g, "''").replace(/"[^"]*"/g, "''");
@ -1692,6 +1697,77 @@ function isInColumnContext(beforeCursor: string): boolean {
return false;
}
function isInSelectListContext(beforeCursor: string): boolean {
let depth = 0;
let inSingleQuote = false;
let inDoubleQuote = false;
let inBacktick = false;
const selectOpenByDepth = new Map<number, boolean>();
for (let i = 0; i < beforeCursor.length; i++) {
const ch = beforeCursor[i] ?? "";
const next = beforeCursor[i + 1] ?? "";
if (inSingleQuote) {
if (ch === "\\" && next) {
i++;
} else if (ch === "'" && next === "'") {
i++;
} else if (ch === "'") {
inSingleQuote = false;
}
continue;
}
if (inDoubleQuote) {
if (ch === '"' && next === '"') {
i++;
} else if (ch === '"') {
inDoubleQuote = false;
}
continue;
}
if (inBacktick) {
if (ch === "`") inBacktick = false;
continue;
}
if (ch === "'") {
inSingleQuote = true;
continue;
}
if (ch === '"') {
inDoubleQuote = true;
continue;
}
if (ch === "`") {
inBacktick = true;
continue;
}
if (ch === "(") {
depth++;
continue;
}
if (ch === ")") {
selectOpenByDepth.delete(depth);
depth = Math.max(0, depth - 1);
continue;
}
if (!/[A-Za-z_]/.test(ch)) continue;
let end = i + 1;
while (end < beforeCursor.length && /[A-Za-z0-9_$]/.test(beforeCursor[end] ?? "")) end++;
const word = beforeCursor.slice(i, end).toLowerCase();
if (word === "select") {
selectOpenByDepth.set(depth, true);
} else if (word === "from") {
selectOpenByDepth.set(depth, false);
}
i = end - 1;
}
return selectOpenByDepth.get(depth) === true;
}
function isInJoinConditionContext(beforeCursor: string): boolean {
const cleaned = beforeCursor
.replace(/'[^']*'/g, "''")

View File

@ -1841,6 +1841,28 @@ test("prefix matches still rank above fuzzy matches", () => {
assert.equal(items[0]?.label, "name");
});
test("suggests columns after multiple select-list expressions", () => {
const sql = "select project_name, review_accountant, doc from ypmng_archive LIMIT 100";
const cursor = "select project_name, review_accountant, doc".length;
const items = buildSqlCompletionItems(sql, cursor, {
tables: [{ name: "ypmng_archive", type: "table" }],
objects: [{ name: "proc_get_ypfmm_pd_score_list_with_template_doc_id", schema: "y_jnpf", type: "procedure" }],
columnsByTable: new Map([
[
"ypmng_archive",
[
{ name: "doc_id", table: "ypmng_archive", dataType: "bigint" },
{ name: "project_name", table: "ypmng_archive", dataType: "varchar" },
{ name: "review_accountant", table: "ypmng_archive", dataType: "varchar" },
],
],
]),
});
assert.ok(items.some((item) => item.label === "doc_id" && item.type === "column"));
assert.ok(!items.some((item) => item.type === "function" && item.label.startsWith("proc_")));
});
// --- Type-aware comparison hints ---
test("suggests NULL and IS NULL after comparison operator", () => {