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 {
|
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 {
|
function performNormalTab(view: EditorViewType): boolean {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { readFileSync } from "node:fs";
|
import { readFileSync } from "node:fs";
|
||||||
import ts from "typescript";
|
import ts from "typescript";
|
||||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
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");
|
const queryEditorSource = readFileSync(new URL("../../../components/editor/QueryEditor.vue", import.meta.url), "utf8");
|
||||||
|
|
||||||
@@ -59,6 +60,7 @@ function createHarness(options: {
|
|||||||
insertNewlineKeepIndent?: (view: MockView) => boolean;
|
insertNewlineKeepIndent?: (view: MockView) => boolean;
|
||||||
nextSnippetField?: (view: MockView) => boolean;
|
nextSnippetField?: (view: MockView) => boolean;
|
||||||
indentMore?: (view: MockView) => boolean;
|
indentMore?: (view: MockView) => boolean;
|
||||||
|
acceptCompletionShortcut?: string;
|
||||||
}): TabHarness {
|
}): TabHarness {
|
||||||
const source = [
|
const source = [
|
||||||
extractDeclaration(/const COMPLETION_REMOTE_LATENCY_BUDGET_MS = \d+;/, "remote completion latency budget"),
|
extractDeclaration(/const COMPLETION_REMOTE_LATENCY_BUDGET_MS = \d+;/, "remote completion latency budget"),
|
||||||
@@ -69,6 +71,8 @@ function createHarness(options: {
|
|||||||
"let suppressNextSqlCompletionAutoStartUntil = 0;",
|
"let suppressNextSqlCompletionAutoStartUntil = 0;",
|
||||||
extractFunction("editorIndentUnit"),
|
extractFunction("editorIndentUnit"),
|
||||||
extractFunction("handleTab"),
|
extractFunction("handleTab"),
|
||||||
|
extractFunction("tabKeyAcceptsCompletion"),
|
||||||
|
extractFunction("handleTabWithoutAcceptingCompletion"),
|
||||||
extractFunction("performNormalTab"),
|
extractFunction("performNormalTab"),
|
||||||
extractFunction("insertNewlineWithoutCompletion"),
|
extractFunction("insertNewlineWithoutCompletion"),
|
||||||
extractFunction("acceptCompletionOrNextSnippetField"),
|
extractFunction("acceptCompletionOrNextSnippetField"),
|
||||||
@@ -87,11 +91,26 @@ function createHarness(options: {
|
|||||||
"codeMirrorNextSnippetField",
|
"codeMirrorNextSnippetField",
|
||||||
"codeMirrorIndentMore",
|
"codeMirrorIndentMore",
|
||||||
"settingsStore",
|
"settingsStore",
|
||||||
|
"normalizeShortcutSettings",
|
||||||
|
"shortcutToCodeMirrorKey",
|
||||||
`${javascript}\nreturn { handleTab, insertNewlineWithoutCompletion, acceptCompletionOrNextSnippetField, clearPendingCompletionTab, consumeSqlCompletionAutoStartSuppression };`,
|
`${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), {
|
return factory(
|
||||||
editorSettings: { sqlFormatter: { useTabs: false, tabWidth: 2 } },
|
options.completionStatus,
|
||||||
}) as TabHarness;
|
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 {
|
function createView(text = "SELECT", position = text.length): MockView {
|
||||||
@@ -237,6 +256,27 @@ describe("QueryEditor completion Tab keymap", () => {
|
|||||||
expect(view.dispatch).not.toHaveBeenCalled();
|
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 () => {
|
it("falls back to normal Tab when pending completion has no candidate", async () => {
|
||||||
vi.useFakeTimers();
|
vi.useFakeTimers();
|
||||||
let status: "active" | "pending" | null = "pending";
|
let status: "active" | "pending" | null = "pending";
|
||||||
|
|||||||
Reference in new issue
Block a user