fix(editor): honor configured accept-completion shortcut
This commit is contained in:
1 parent
1aec3ae000
commit
057b24b5e8
2 files changed
+62
-4
No files matched your search
@@ -748,7 +748,25 @@ function editorIndentUnit(): string {
|
||||
}
|
||||
|
||||
function handleTab(view: EditorViewType): boolean {
|
||||
return acceptCompletionOrNextSnippetField(view) || performNormalTab(view);
|
||||
if (tabKeyAcceptsCompletion()) {
|
||||
return acceptCompletionOrNextSnippetField(view) || performNormalTab(view);
|
||||
}
|
||||
return handleTabWithoutAcceptingCompletion(view) || performNormalTab(view);
|
||||
}
|
||||
|
||||
// The Tab key is always wired up for indentation and snippet-field navigation,
|
||||
// but it must only accept an open completion popup when the user's configured
|
||||
// "accept completion" shortcut is actually Tab — otherwise a user who remapped
|
||||
// that shortcut (e.g. to Enter) would find Tab silently accepting completions
|
||||
// anyway, ignoring their setting (dbx#6236).
|
||||
function tabKeyAcceptsCompletion(): boolean {
|
||||
const shortcuts = normalizeShortcutSettings(settingsStore.editorSettings.shortcuts);
|
||||
return shortcutToCodeMirrorKey(shortcuts.acceptCompletion) === "Tab";
|
||||
}
|
||||
|
||||
function handleTabWithoutAcceptingCompletion(view: EditorViewType): boolean {
|
||||
if (codeMirrorCompletionStatus?.(view.state)) return false;
|
||||
return codeMirrorNextSnippetField?.(view) ?? false;
|
||||
}
|
||||
|
||||
function performNormalTab(view: EditorViewType): boolean {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import ts from "typescript";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { DEFAULT_SHORTCUT_SETTINGS, normalizeShortcutSettings, shortcutToCodeMirrorKey } from "@/lib/editor/shortcutRegistry";
|
||||
|
||||
const queryEditorSource = readFileSync(new URL("../../../components/editor/QueryEditor.vue", import.meta.url), "utf8");
|
||||
|
||||
@@ -59,6 +60,7 @@ function createHarness(options: {
|
||||
insertNewlineKeepIndent?: (view: MockView) => boolean;
|
||||
nextSnippetField?: (view: MockView) => boolean;
|
||||
indentMore?: (view: MockView) => boolean;
|
||||
acceptCompletionShortcut?: string;
|
||||
}): TabHarness {
|
||||
const source = [
|
||||
extractDeclaration(/const COMPLETION_REMOTE_LATENCY_BUDGET_MS = \d+;/, "remote completion latency budget"),
|
||||
@@ -69,6 +71,8 @@ function createHarness(options: {
|
||||
"let suppressNextSqlCompletionAutoStartUntil = 0;",
|
||||
extractFunction("editorIndentUnit"),
|
||||
extractFunction("handleTab"),
|
||||
extractFunction("tabKeyAcceptsCompletion"),
|
||||
extractFunction("handleTabWithoutAcceptingCompletion"),
|
||||
extractFunction("performNormalTab"),
|
||||
extractFunction("insertNewlineWithoutCompletion"),
|
||||
extractFunction("acceptCompletionOrNextSnippetField"),
|
||||
@@ -87,11 +91,26 @@ function createHarness(options: {
|
||||
"codeMirrorNextSnippetField",
|
||||
"codeMirrorIndentMore",
|
||||
"settingsStore",
|
||||
"normalizeShortcutSettings",
|
||||
"shortcutToCodeMirrorKey",
|
||||
`${javascript}\nreturn { handleTab, insertNewlineWithoutCompletion, acceptCompletionOrNextSnippetField, clearPendingCompletionTab, consumeSqlCompletionAutoStartSuppression };`,
|
||||
);
|
||||
return factory(options.completionStatus, options.acceptCompletion ?? (() => false), options.closeCompletion ?? (() => false), options.insertNewlineKeepIndent ?? (() => false), options.nextSnippetField ?? (() => false), options.indentMore ?? (() => false), {
|
||||
editorSettings: { sqlFormatter: { useTabs: false, tabWidth: 2 } },
|
||||
}) as TabHarness;
|
||||
return factory(
|
||||
options.completionStatus,
|
||||
options.acceptCompletion ?? (() => false),
|
||||
options.closeCompletion ?? (() => false),
|
||||
options.insertNewlineKeepIndent ?? (() => false),
|
||||
options.nextSnippetField ?? (() => false),
|
||||
options.indentMore ?? (() => false),
|
||||
{
|
||||
editorSettings: {
|
||||
sqlFormatter: { useTabs: false, tabWidth: 2 },
|
||||
shortcuts: { ...DEFAULT_SHORTCUT_SETTINGS, acceptCompletion: options.acceptCompletionShortcut ?? DEFAULT_SHORTCUT_SETTINGS.acceptCompletion },
|
||||
},
|
||||
},
|
||||
normalizeShortcutSettings,
|
||||
shortcutToCodeMirrorKey,
|
||||
) as TabHarness;
|
||||
}
|
||||
|
||||
function createView(text = "SELECT", position = text.length): MockView {
|
||||
@@ -237,6 +256,27 @@ describe("QueryEditor completion Tab keymap", () => {
|
||||
expect(view.dispatch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not accept an open completion popup on Tab when the configured accept-completion shortcut is Enter (dbx#6236)", () => {
|
||||
const acceptCompletion = vi.fn(() => true);
|
||||
const nextSnippetField = vi.fn(() => true);
|
||||
const harness = createHarness({ completionStatus: () => "active", acceptCompletion, nextSnippetField, acceptCompletionShortcut: "Enter" });
|
||||
const view = createView();
|
||||
|
||||
expect(harness.handleTab(view)).toBe(true);
|
||||
expect(acceptCompletion).not.toHaveBeenCalled();
|
||||
expect(nextSnippetField).not.toHaveBeenCalled();
|
||||
expect(view.state.replaceSelection).toHaveBeenCalledWith(" ");
|
||||
});
|
||||
|
||||
it("still accepts an open completion popup on Tab when the configured shortcut is left at its Tab default", () => {
|
||||
const acceptCompletion = vi.fn(() => true);
|
||||
const harness = createHarness({ completionStatus: () => "active", acceptCompletion, acceptCompletionShortcut: "Tab" });
|
||||
const view = createView();
|
||||
|
||||
expect(harness.handleTab(view)).toBe(true);
|
||||
expect(acceptCompletion).toHaveBeenCalledWith(view);
|
||||
});
|
||||
|
||||
it("falls back to normal Tab when pending completion has no candidate", async () => {
|
||||
vi.useFakeTimers();
|
||||
let status: "active" | "pending" | null = "pending";
|
||||
|
||||
Reference in new issue
Block a user