mirror of
https://github.com/t8y2/dbx.git
synced 2026-11-01 08:26:35 +00:00
fix(editor): refresh SQL snippet completion after settings changes
This commit is contained in:
@@ -145,12 +145,15 @@ let codeMirrorTheme: import("@codemirror/state").Compartment | null = null;
|
||||
let wordWrapComp: import("@codemirror/state").Compartment | null = null;
|
||||
let readOnlyComp: import("@codemirror/state").Compartment | null = null;
|
||||
let runKeymapComp: import("@codemirror/state").Compartment | null = null;
|
||||
let completionComp: import("@codemirror/state").Compartment | null = null;
|
||||
let diagnosticComp: import("@codemirror/state").Compartment | null = null;
|
||||
let buildSqlDiagnosticExtension: (() => import("@codemirror/state").Extension) | null = null;
|
||||
let buildSqlSignatureExtension: (() => import("@codemirror/state").Extension) | null = null;
|
||||
let buildSqlCompletionExtension: (() => import("@codemirror/state").Extension) | null = null;
|
||||
let codeMirrorSnippetCompletion: typeof import("@codemirror/autocomplete").snippetCompletion;
|
||||
let codeMirrorCompletionStatus: typeof import("@codemirror/autocomplete").completionStatus | null = null;
|
||||
let codeMirrorAcceptCompletion: typeof import("@codemirror/autocomplete").acceptCompletion | null = null;
|
||||
let codeMirrorStartCompletion: typeof import("@codemirror/autocomplete").startCompletion | null = null;
|
||||
let codeMirrorIndentMore: typeof import("@codemirror/commands").indentMore | null = null;
|
||||
let codeMirrorInsertNewlineKeepIndent: typeof import("@codemirror/commands").insertNewlineKeepIndent | null = null;
|
||||
let setSqlDiagnosticsEffect: import("@codemirror/state").StateEffectType<SqlSemanticDiagnostic[]> | null = null;
|
||||
@@ -698,7 +701,8 @@ async function provideSqlCompletions(
|
||||
position: number,
|
||||
explicit: boolean,
|
||||
) {
|
||||
if (!props.connectionId || props.database == null) return null;
|
||||
if (!props.connectionId) return null;
|
||||
const hasDatabase = props.database != null;
|
||||
|
||||
const epoch = ++completionEpoch;
|
||||
|
||||
@@ -708,6 +712,37 @@ async function provideSqlCompletions(
|
||||
|
||||
const completionContext = getSqlCompletionContext(fullDoc, position);
|
||||
|
||||
if (!hasDatabase) {
|
||||
const items = buildSqlCompletionItemsFromContext(completionContext, {
|
||||
tables: [],
|
||||
columnsByTable: new Map(),
|
||||
schemas: [],
|
||||
translations: completionTranslations.value,
|
||||
snippets: settingsStore.editorSettings.snippets,
|
||||
});
|
||||
if (items.length === 0) return null;
|
||||
return {
|
||||
from: position - completionContext.prefix.length,
|
||||
filter: false,
|
||||
options: items.map((item) =>
|
||||
(item.type === "snippet" || item.type === "function") && item.apply
|
||||
? codeMirrorSnippetCompletion(item.apply, {
|
||||
label: item.label,
|
||||
type: item.type,
|
||||
detail: item.detail,
|
||||
boost: item.boost,
|
||||
})
|
||||
: {
|
||||
label: item.label,
|
||||
type: item.type,
|
||||
detail: item.detail,
|
||||
boost: item.boost,
|
||||
},
|
||||
),
|
||||
validFor: getSqlCompletionResultValidFor(fullDoc, position),
|
||||
};
|
||||
}
|
||||
|
||||
// Handle INSERT column list: fetch columns for the target table
|
||||
let insertColumnsByTable = new Map<string, SqlCompletionColumn[]>();
|
||||
if (completionContext.insertTable) {
|
||||
@@ -889,8 +924,9 @@ async function provideSqlCompletions(
|
||||
|
||||
return {
|
||||
from: position - completionContext.prefix.length,
|
||||
filter: false,
|
||||
options: items.map((item) =>
|
||||
item.type === "snippet" && item.apply
|
||||
(item.type === "snippet" || item.type === "function") && item.apply
|
||||
? codeMirrorSnippetCompletion(item.apply, {
|
||||
label: item.label,
|
||||
type: item.type,
|
||||
@@ -968,10 +1004,12 @@ onMounted(async () => {
|
||||
wordWrapComp = new Compartment();
|
||||
readOnlyComp = new Compartment();
|
||||
runKeymapComp = new Compartment();
|
||||
completionComp = new Compartment();
|
||||
diagnosticComp = new Compartment();
|
||||
setSqlDiagnosticsEffect = StateEffect.define<SqlSemanticDiagnostic[]>();
|
||||
codeMirrorCompletionStatus = completionStatus;
|
||||
codeMirrorAcceptCompletion = acceptCompletion;
|
||||
codeMirrorStartCompletion = startCompletion;
|
||||
codeMirrorIndentMore = indentMore;
|
||||
codeMirrorInsertNewlineKeepIndent = insertNewlineKeepIndent;
|
||||
|
||||
@@ -1029,6 +1067,14 @@ onMounted(async () => {
|
||||
};
|
||||
});
|
||||
|
||||
buildSqlCompletionExtension = () =>
|
||||
autocompletion({
|
||||
activateOnTyping: true,
|
||||
override: [
|
||||
async (context: CompletionContext) => provideSqlCompletions(context.state, context.pos, context.explicit),
|
||||
],
|
||||
});
|
||||
|
||||
const ss = settingsStore.editorSettings;
|
||||
|
||||
const baseDialect = props.dialect === "postgres" ? PostgreSQL : props.dialect === "sqlserver" ? MSSQL : MySQL;
|
||||
@@ -1095,12 +1141,7 @@ onMounted(async () => {
|
||||
keymap.of([...defaultKeymap, ...searchKeymap, ...historyKeymap, ...foldKeymap, ...completionKeymap]),
|
||||
sql({ dialect }),
|
||||
tooltips({ parent: document.body }),
|
||||
autocompletion({
|
||||
activateOnTyping: true,
|
||||
override: [
|
||||
async (context: CompletionContext) => provideSqlCompletions(context.state, context.pos, context.explicit),
|
||||
],
|
||||
}),
|
||||
completionComp.of(buildSqlCompletionExtension()),
|
||||
sqlCompletionTheme(EditorView),
|
||||
codeMirrorTheme.of(theme),
|
||||
closeBrackets(),
|
||||
@@ -1373,6 +1414,21 @@ watch(
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
() => settingsStore.editorSettings.snippets,
|
||||
() => {
|
||||
completionEpoch++;
|
||||
if (!view.value || !completionComp || !buildSqlCompletionExtension) return;
|
||||
view.value.dispatch({
|
||||
effects: completionComp.reconfigure(buildSqlCompletionExtension()),
|
||||
});
|
||||
if (codeMirrorCompletionStatus?.(view.value.state) === "active") {
|
||||
codeMirrorStartCompletion?.(view.value);
|
||||
}
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
zoomCommitScheduler.dispose();
|
||||
if (semanticDiagnosticTimer) clearTimeout(semanticDiagnosticTimer);
|
||||
|
||||
@@ -21,6 +21,21 @@ describe("buildSnippetItems", () => {
|
||||
expect(items[0].label).toBe("select all");
|
||||
});
|
||||
|
||||
it("does not keep matching a renamed snippet by its old short label prefix", () => {
|
||||
const items = buildSnippetItemsForTest("sel", [
|
||||
{ id: "1", label: "select all", prefix: "fff", body: "SELECT *\nFROM my_table;" },
|
||||
]);
|
||||
expect(items).toEqual([]);
|
||||
});
|
||||
|
||||
it("still matches a renamed snippet by label when typing a longer descriptive query", () => {
|
||||
const items = buildSnippetItemsForTest("select", [
|
||||
{ id: "1", label: "select all", prefix: "fff", body: "SELECT *\nFROM my_table;" },
|
||||
]);
|
||||
expect(items).toHaveLength(1);
|
||||
expect(items[0].label).toBe("select all");
|
||||
});
|
||||
|
||||
it("returns empty for no match", () => {
|
||||
const items = buildSnippetItemsForTest("zzz", TEST_SNIPPETS);
|
||||
expect(items).toEqual([]);
|
||||
|
||||
@@ -610,7 +610,7 @@ export interface SqlCompletionColumn {
|
||||
|
||||
export interface SqlCompletionItem {
|
||||
label: string;
|
||||
type: "keyword" | "table" | "column" | "snippet" | "schema";
|
||||
type: "keyword" | "table" | "column" | "snippet" | "function" | "schema";
|
||||
detail?: string;
|
||||
apply?: string;
|
||||
boost: number;
|
||||
@@ -1934,16 +1934,20 @@ export function buildSnippetItemsForTest(prefix: string, snippets: SqlSnippet[])
|
||||
function buildSnippetItems(prefix: string, snippets: SqlSnippet[]): SqlCompletionItem[] {
|
||||
if (!prefix) return [];
|
||||
return snippets
|
||||
.filter((snippet) => matchesPrefix(snippet.prefix, prefix) || matchesPrefix(snippet.label, prefix))
|
||||
.filter((snippet) => {
|
||||
const matchesSnippetPrefix = matchesPrefix(snippet.prefix, prefix);
|
||||
const matchesSnippetLabel = prefix.length > snippet.prefix.length && matchesPrefix(snippet.label, prefix);
|
||||
return matchesSnippetPrefix || matchesSnippetLabel;
|
||||
})
|
||||
.map((snippet) => {
|
||||
const boostByPrefix = computeBoost(snippet.prefix, prefix);
|
||||
const boostByLabel = computeBoost(snippet.label, prefix);
|
||||
return {
|
||||
label: snippet.label,
|
||||
type: "snippet" as const,
|
||||
detail: snippet.label,
|
||||
detail: snippet.body,
|
||||
apply: snippet.body,
|
||||
boost: Math.max(boostByPrefix, boostByLabel) - 1100,
|
||||
boost: Math.max(boostByPrefix, boostByLabel) + 4000,
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -1956,7 +1960,7 @@ function buildFunctionSnippetItems(prefix: string, functionDescriptions: Map<str
|
||||
const paramStr = parameters.length > 0 ? parameters.map((p) => `\${${p}}`).join(", ") : "";
|
||||
items.push({
|
||||
label: name,
|
||||
type: "snippet" as const,
|
||||
type: "function" as const,
|
||||
detail: functionDescriptions.get(name) ?? "function",
|
||||
apply: `${name}(${paramStr})`,
|
||||
boost: computeBoost(name, prefix) + 300,
|
||||
@@ -1968,7 +1972,7 @@ function buildFunctionSnippetItems(prefix: string, functionDescriptions: Map<str
|
||||
if (!matchesPrefix(name, prefix)) continue;
|
||||
items.push({
|
||||
label: name,
|
||||
type: "snippet" as const,
|
||||
type: "function" as const,
|
||||
detail: "window function",
|
||||
apply: `${name}() OVER (PARTITION BY \${col} ORDER BY \${col})`,
|
||||
boost: computeBoost(name, prefix) + 250,
|
||||
|
||||
Reference in New Issue
Block a user