fix(editor): restore custom snippet trigger matching
This commit is contained in:
5 files changed
+80
-2
No files matched your search
@@ -65,6 +65,7 @@ import { EDITOR_FONT_FAMILY_CSS_VAR, EDITOR_FONT_SIZE_CSS_VAR, loadEditorTheme,
|
||||
import { createStatementGutterMarkerDom, shouldShowStatementGutter } from "@/lib/editor/codemirrorStatementGutter";
|
||||
import { createQueryEditorSearchKeymap } from "@/lib/editor/queryEditorSearchKeymap";
|
||||
import { appendSqlCompletionSpace } from "@/lib/editor/sqlCompletionInsertion";
|
||||
import { completionLabelPresentation } from "@/lib/editor/sqlCompletionPresentation";
|
||||
import { clampEditorFontSize, createEditorZoomCommitScheduler, fontSizeFromGestureScale, fontSizeFromWheelDelta } from "@/lib/editor/editorZoom";
|
||||
import { normalizeShortcutSettings, shortcutToCodeMirrorKey } from "@/lib/editor/shortcutRegistry";
|
||||
import { trimmedSelectionLayer } from "@/lib/editor/codemirrorTrimmedSelectionLayer";
|
||||
@@ -2490,12 +2491,14 @@ function shouldInsertSqlCompletionSpace(): boolean {
|
||||
}
|
||||
|
||||
function completionOptionForItem(item: QueryCompletionItem) {
|
||||
const filterText = "filterText" in item && typeof item.filterText === "string" ? item.filterText : undefined;
|
||||
const labelPresentation = completionLabelPresentation(item.label, filterText);
|
||||
const record = () => {
|
||||
recordCompletionSelection(item.label, item.type);
|
||||
};
|
||||
if ((item.type === "snippet" || item.type === "function") && item.apply) {
|
||||
const completion = codeMirrorSnippetCompletion(item.apply, {
|
||||
label: item.label,
|
||||
...labelPresentation,
|
||||
type: item.type,
|
||||
detail: item.detail,
|
||||
info: item.info,
|
||||
@@ -2520,7 +2523,7 @@ function completionOptionForItem(item: QueryCompletionItem) {
|
||||
};
|
||||
}
|
||||
return {
|
||||
label: item.label,
|
||||
...labelPresentation,
|
||||
type: item.type,
|
||||
detail: item.detail,
|
||||
info: item.info,
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import { acceptCompletion, autocompletion, completionStatus, currentCompletions, snippetCompletion, startCompletion } from "@codemirror/autocomplete";
|
||||
import { EditorState } from "@codemirror/state";
|
||||
import { EditorView } from "@codemirror/view";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { completionLabelPresentation } from "@/lib/editor/sqlCompletionPresentation";
|
||||
|
||||
describe("SQL completion label presentation", () => {
|
||||
it("filters a custom snippet by trigger while preserving its display label", () => {
|
||||
expect(completionLabelPresentation("SELECT *", "ssf")).toEqual({
|
||||
label: "ssf SELECT *",
|
||||
displayLabel: "SELECT *",
|
||||
sortText: "SELECT *",
|
||||
});
|
||||
});
|
||||
|
||||
it("leaves ordinary completion labels unchanged", () => {
|
||||
expect(completionLabelPresentation("SELECT")).toEqual({ label: "SELECT" });
|
||||
expect(completionLabelPresentation("select *", "sel")).toEqual({ label: "select *" });
|
||||
});
|
||||
|
||||
it("keeps a differently named snippet visible and applicable in CodeMirror", async () => {
|
||||
const presentation = completionLabelPresentation("SELECT *", "ssf");
|
||||
const view = new EditorView({
|
||||
parent: document.createElement("div"),
|
||||
state: EditorState.create({
|
||||
doc: "ssf",
|
||||
selection: { anchor: 3 },
|
||||
extensions: [
|
||||
autocompletion({
|
||||
interactionDelay: 0,
|
||||
override: [() => ({ from: 0, options: [snippetCompletion("SELECT * FROM", { ...presentation, type: "snippet" })] })],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
startCompletion(view);
|
||||
await expect.poll(() => completionStatus(view.state)).toBe("active");
|
||||
expect(currentCompletions(view.state)[0]).toMatchObject({ label: "ssf SELECT *", displayLabel: "SELECT *" });
|
||||
expect(acceptCompletion(view)).toBe(true);
|
||||
expect(view.state.doc.toString()).toBe("SELECT * FROM");
|
||||
view.destroy();
|
||||
});
|
||||
});
|
||||
@@ -50,6 +50,18 @@ describe("buildSnippetItems", () => {
|
||||
expect(items[0].apply).toBe("SELECT *\nFROM my_table;");
|
||||
});
|
||||
|
||||
it("keeps a distinct trigger available to CodeMirror filtering", () => {
|
||||
const items = buildSnippetItemsForTest("ssf", [{ id: "custom", label: "SELECT *", prefix: "ssf", body: "SELECT * FROM" }]);
|
||||
|
||||
expect(items).toEqual([
|
||||
expect.objectContaining({
|
||||
label: "SELECT *",
|
||||
filterText: "ssf",
|
||||
type: "snippet",
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it("uses CodeMirror placeholders for the built-in select snippet", () => {
|
||||
const items = buildSnippetItemsForTest("sel", [BUILTIN_SELECT]);
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
export interface CompletionLabelPresentation {
|
||||
label: string;
|
||||
displayLabel?: string;
|
||||
sortText?: string;
|
||||
}
|
||||
|
||||
export function completionLabelPresentation(label: string, filterText?: string): CompletionLabelPresentation {
|
||||
const normalizedFilterText = filterText?.trim();
|
||||
if (!normalizedFilterText || label.toLowerCase().startsWith(normalizedFilterText.toLowerCase())) return { label };
|
||||
return {
|
||||
label: `${normalizedFilterText} ${label}`,
|
||||
displayLabel: label,
|
||||
sortText: label,
|
||||
};
|
||||
}
|
||||
@@ -1191,6 +1191,7 @@ export interface SqlCompletionForeignKey {
|
||||
|
||||
export interface SqlCompletionItem {
|
||||
label: string;
|
||||
filterText?: string;
|
||||
type: "keyword" | "table" | "column" | "snippet" | "function" | "schema";
|
||||
detail?: string;
|
||||
info?: string;
|
||||
@@ -3971,6 +3972,7 @@ function buildSnippetItems(prefix: string, snippets: SqlSnippet[], keywordCase?:
|
||||
const apply = applyBuiltinSnippetKeywordCase(snippet, applyBuiltinSnippetPlaceholders(snippet, resolvedBody), keywordCase);
|
||||
return {
|
||||
label: snippet.label,
|
||||
filterText: snippet.prefix,
|
||||
type: "snippet" as const,
|
||||
detail: body,
|
||||
apply,
|
||||
|
||||
Reference in new issue
Block a user