Add configurable hotkey for editor autocomplete trigger (#350)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Gregory Schier
2026-01-07 15:10:33 -08:00
committed by GitHub
parent 873abe69a1
commit ebcdee9be0
4 changed files with 120 additions and 28 deletions

View File

@@ -14,6 +14,7 @@ export type HotkeyAction =
| 'app.zoom_out'
| 'app.zoom_reset'
| 'command_palette.toggle'
| 'editor.autocomplete'
| 'environment_editor.toggle'
| 'hotkeys.showHelp'
| 'model.create'
@@ -41,6 +42,7 @@ const defaultHotkeysMac: Record<HotkeyAction, string[]> = {
'app.zoom_out': ['Meta+Minus'],
'app.zoom_reset': ['Meta+0'],
'command_palette.toggle': ['Meta+k'],
'editor.autocomplete': ['Control+Space'],
'environment_editor.toggle': ['Meta+Shift+e'],
'request.rename': ['Control+Shift+r'],
'request.send': ['Meta+Enter', 'Meta+r'],
@@ -69,6 +71,7 @@ const defaultHotkeysOther: Record<HotkeyAction, string[]> = {
'app.zoom_out': ['Control+Minus'],
'app.zoom_reset': ['Control+0'],
'command_palette.toggle': ['Control+k'],
'editor.autocomplete': ['Control+Space'],
'environment_editor.toggle': ['Control+Shift+e'],
'request.rename': ['F2'],
'request.send': ['Control+Enter', 'Control+r'],
@@ -122,6 +125,7 @@ const hotkeyLabels: Record<HotkeyAction, string> = {
'app.zoom_out': 'Zoom Out',
'app.zoom_reset': 'Zoom to Actual Size',
'command_palette.toggle': 'Toggle Command Palette',
'editor.autocomplete': 'Trigger Autocomplete',
'environment_editor.toggle': 'Edit Environments',
'hotkeys.showHelp': 'Show Keyboard Shortcuts',
'model.create': 'New Request',
@@ -144,7 +148,14 @@ const hotkeyLabels: Record<HotkeyAction, string> = {
'workspace_settings.show': 'Open Workspace Settings',
};
const layoutInsensitiveKeys = ['Equal', 'Minus', 'BracketLeft', 'BracketRight', 'Backquote'];
const layoutInsensitiveKeys = [
'Equal',
'Minus',
'BracketLeft',
'BracketRight',
'Backquote',
'Space',
];
export const hotkeyActions: HotkeyAction[] = (
Object.keys(defaultHotkeys) as (keyof typeof defaultHotkeys)[]
@@ -265,29 +276,20 @@ function handleKeyDown(e: KeyboardEvent) {
}
const executed: string[] = [];
const hotkeys = getHotkeys();
outer: for (const { action, callback, options } of jotaiStore.get(sortedCallbacksAtom)) {
for (const [hkAction, hkKeys] of Object.entries(hotkeys) as [HotkeyAction, string[]][]) {
if (hkAction !== action) {
continue;
}
const enable = typeof options.enable === 'function' ? options.enable() : options.enable;
if (enable === false) {
continue;
}
for (const { action, callback, options } of jotaiStore.get(sortedCallbacksAtom)) {
const enable = typeof options.enable === 'function' ? options.enable() : options.enable;
if (enable === false) {
continue;
}
for (const hkKey of hkKeys) {
const keys = hkKey.split('+');
if (compareKeys(keys, Array.from(currentKeysWithModifiers))) {
if (!options.allowDefault) {
e.preventDefault();
e.stopPropagation();
}
callback(e);
executed.push(`${action} ${options.priority ?? 0}`);
break outer;
}
if (keysMatchAction(Array.from(currentKeysWithModifiers), action)) {
if (!options.allowDefault) {
e.preventDefault();
e.stopPropagation();
}
callback(e);
executed.push(`${action} ${options.priority ?? 0}`);
break;
}
}
@@ -336,12 +338,16 @@ export function formatHotkeyString(trigger: string): string[] {
labelParts.push('+');
} else if (p === 'Equal') {
labelParts.push('=');
} else if (p === 'Space') {
labelParts.push('Space');
} else {
labelParts.push(capitalize(p));
}
} else {
if (p === 'Control') {
labelParts.push('Ctrl');
} else if (p === 'Space') {
labelParts.push('Space');
} else {
labelParts.push(capitalize(p));
}
@@ -376,3 +382,39 @@ function compareKeys(keysA: string[], keysB: string[]) {
.join('::');
return sortedA === sortedB;
}
/** Build the full key combination from a KeyboardEvent including modifiers */
function getKeysFromEvent(e: KeyboardEvent): string[] {
const keys: string[] = [];
if (e.altKey) keys.push('Alt');
if (e.ctrlKey) keys.push('Control');
if (e.metaKey) keys.push('Meta');
if (e.shiftKey) keys.push('Shift');
// Add the actual key (use code for layout-insensitive keys)
const keyToAdd = layoutInsensitiveKeys.includes(e.code) ? e.code : e.key;
keys.push(keyToAdd);
return keys;
}
/** Check if a set of pressed keys matches any hotkey for the given action */
function keysMatchAction(keys: string[], action: HotkeyAction): boolean {
const hotkeys = getHotkeys();
const hkKeys = hotkeys[action];
if (!hkKeys || hkKeys.length === 0) return false;
for (const hkKey of hkKeys) {
const hotkeyParts = hkKey.split('+');
if (compareKeys(hotkeyParts, keys)) {
return true;
}
}
return false;
}
/** Check if a KeyboardEvent matches a hotkey action */
export function eventMatchesHotkey(e: KeyboardEvent, action: HotkeyAction): boolean {
const keys = getKeysFromEvent(e);
return keysMatchAction(keys, action);
}