feat(completion): configurable SQL completion trigger modes

This commit is contained in:
Abeautifulsnow authored and GitHub committed 2026-08-08 12:00:50 +08:00
1 parent bad7f6b472
commit 5b85f9eae8
18 files changed
+457 -18

No files matched your search

@@ -0,0 +1,61 @@
export type SqlCompletionTriggerMode = "manual" | "require-prefix" | "positional";
export type SqlCompletionTriggerOrigin = "typing" | "explicit";
export const SQL_COMPLETION_TRIGGER_MODES: readonly SqlCompletionTriggerMode[] = ["manual", "require-prefix", "positional"];
export function originForTypedSqlCompletionStart(activeOrigin: SqlCompletionTriggerOrigin | null): SqlCompletionTriggerOrigin {
// Programmatic refreshes can run while a shortcut-opened session is active.
// Preserve that explicit origin so typing and metadata refreshes do not make
// the session subject to automatic-trigger mode gates.
return activeOrigin ?? "typing";
}
export function originForSqlCompletionProvider(activeOrigin: SqlCompletionTriggerOrigin | null, explicit: boolean): SqlCompletionTriggerOrigin {
// Programmatic starts set their origin before calling CodeMirror, while
// activate-on-typing calls the provider with explicit=false. Therefore an
// otherwise unmarked explicit call is always the manual shortcut session.
return activeOrigin ?? (explicit ? "explicit" : "typing");
}
export function normalizeCompletionTriggerMode(value: unknown): SqlCompletionTriggerMode {
if (typeof value === "string" && (value === "manual" || value === "require-prefix" || value === "positional")) {
return value;
}
return "positional";
}
export interface SqlCompletionTriggerFacts {
/** typing (DBX programmatic start / implicit) vs explicit (manual completion shortcut) */
origin: SqlCompletionTriggerOrigin;
/** completion context context.prefix.length > 0 (not trailing '.') */
hasIdentifierPrefix: boolean;
/** previousChar === "." && context.qualifier != null */
qualifierTriggered: boolean;
/** resolveSqlServerUseDatabaseCompletion().prefix ('m' / 'Bar' / '') */
useDatabasePrefix: string | null;
/** Only computed for positional mode: existing shouldAutoOpenSqlCompletion(sql, cursor, options) result */
positionalEligible?: boolean;
}
/**
* Pure trigger policy decision (the single gate, without sql/position itself).
*
* Decision rules (priority order):
* 1. suppressed context (comment / string literal) -> reject first (including explicit)
* 2. explicit -> allow directly (manual shortcut available in any mode)
* 3. manual -> false (return before any semantic model / metadata read)
* 4. require-prefix -> hasIdentifierPrefix || qualifierTriggered || (useDatabasePrefix != null && useDatabasePrefix.length > 0)
* 5. positional -> positionalEligible || useDatabasePrefix != null
*/
export function shouldAllowSqlCompletionTrigger(mode: SqlCompletionTriggerMode, facts: SqlCompletionTriggerFacts): boolean {
// Suppressed context (comment / string literal) is checked by the caller before
// constructing facts, so this function treats the caller as already gate-kept.
// However, for safety, explicit still bypasses mode checks.
if (facts.origin === "explicit") return true;
if (mode === "manual") return false;
if (mode === "require-prefix") {
return facts.hasIdentifierPrefix || facts.qualifierTriggered || (facts.useDatabasePrefix != null && facts.useDatabasePrefix.length > 0);
}
// positional
return (facts.positionalEligible ?? false) || facts.useDatabasePrefix != null;
}