Skip to content

Commit 9adbf23

Browse files
authored
fix(keybindings): honor null overrides and terminal shortcut forwarding (Acode-Foundation#2024)
1 parent b6ae63c commit 9adbf23

File tree

2 files changed

+73
-7
lines changed

2 files changed

+73
-7
lines changed

src/cm/commandRegistry.js

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ const commandKeymapCompartment = new Compartment();
108108
* run: (view?: EditorView | null) => boolean | void;
109109
* requiresView?: boolean;
110110
* defaultDescription?: string;
111+
* defaultKey?: string | null;
111112
* key?: string | null;
112113
* }} CommandEntry
113114
*/
@@ -118,6 +119,11 @@ const commandMap = new Map();
118119
/** @type {Record<string, any>} */
119120
let resolvedKeyBindings = keyBindings;
120121

122+
/** @type {Record<string, any>} */
123+
let cachedResolvedKeyBindings = {};
124+
125+
let resolvedKeyBindingsVersion = 0;
126+
121127
/** @type {import("@codemirror/view").KeyBinding[]} */
122128
let cachedKeymap = [];
123129

@@ -1177,6 +1183,7 @@ function addCommand(entry) {
11771183
const command = {
11781184
...entry,
11791185
defaultDescription: entry.description || entry.name,
1186+
defaultKey: entry.key ?? null,
11801187
key: entry.key ?? null,
11811188
};
11821189
commandMap.set(entry.name, command);
@@ -1314,6 +1321,39 @@ function parseKeyString(keyString) {
13141321
.filter(Boolean);
13151322
}
13161323

1324+
function hasOwnBindingOverride(name) {
1325+
return Object.prototype.hasOwnProperty.call(resolvedKeyBindings ?? {}, name);
1326+
}
1327+
1328+
function resolveBindingInfo(name) {
1329+
const baseBinding = keyBindings[name] ?? null;
1330+
if (!hasOwnBindingOverride(name)) return baseBinding;
1331+
1332+
const override = resolvedKeyBindings?.[name];
1333+
if (override === null) {
1334+
return baseBinding ? { ...baseBinding, key: null } : { key: null };
1335+
}
1336+
1337+
if (!override || typeof override !== "object") {
1338+
return baseBinding;
1339+
}
1340+
1341+
return baseBinding ? { ...baseBinding, ...override } : override;
1342+
}
1343+
1344+
function buildResolvedKeyBindingsSnapshot() {
1345+
const bindingNames = new Set([
1346+
...Object.keys(keyBindings),
1347+
...Object.keys(resolvedKeyBindings ?? {}),
1348+
]);
1349+
1350+
return Object.fromEntries(
1351+
Array.from(bindingNames, (name) => [name, resolveBindingInfo(name)]).filter(
1352+
([, binding]) => binding,
1353+
),
1354+
);
1355+
}
1356+
13171357
function toCodeMirrorKey(combo) {
13181358
if (!combo) return null;
13191359
const parts = combo
@@ -1355,11 +1395,15 @@ function toCodeMirrorKey(combo) {
13551395

13561396
function rebuildKeymap() {
13571397
const bindings = [];
1398+
cachedResolvedKeyBindings = buildResolvedKeyBindingsSnapshot();
13581399
commandMap.forEach((command, name) => {
1359-
const bindingInfo = resolvedKeyBindings?.[name];
1400+
const bindingInfo = resolveBindingInfo(name);
13601401
command.description =
13611402
bindingInfo?.description || command.defaultDescription;
1362-
const keySource = bindingInfo?.key ?? command.key ?? null;
1403+
const keySource =
1404+
bindingInfo && Object.prototype.hasOwnProperty.call(bindingInfo, "key")
1405+
? bindingInfo.key
1406+
: (command.defaultKey ?? null);
13631407
command.key = keySource;
13641408
const combos = parseKeyString(keySource);
13651409
combos.forEach((combo) => {
@@ -1373,6 +1417,7 @@ function rebuildKeymap() {
13731417
});
13741418
});
13751419
cachedKeymap = bindings;
1420+
resolvedKeyBindingsVersion += 1;
13761421
return bindings;
13771422
}
13781423

@@ -1410,6 +1455,14 @@ export function getRegisteredCommands() {
14101455
}));
14111456
}
14121457

1458+
export function getResolvedKeyBindings() {
1459+
return cachedResolvedKeyBindings;
1460+
}
1461+
1462+
export function getResolvedKeyBindingsVersion() {
1463+
return resolvedKeyBindingsVersion;
1464+
}
1465+
14131466
export function getCommandKeymapExtension() {
14141467
return commandKeymapCompartment.of(keymap.of(cachedKeymap));
14151468
}

src/components/terminal/terminal.js

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,13 @@ import { Unicode11Addon } from "@xterm/addon-unicode11";
1111
import { WebLinksAddon } from "@xterm/addon-web-links";
1212
import { WebglAddon } from "@xterm/addon-webgl";
1313
import { Terminal as Xterm } from "@xterm/xterm";
14+
import {
15+
getResolvedKeyBindings,
16+
getResolvedKeyBindingsVersion,
17+
} from "cm/commandRegistry";
1418
import toast from "components/toast";
1519
import confirm from "dialogs/confirm";
1620
import fonts from "lib/fonts";
17-
import keyBindings from "lib/keyBindings";
1821
import appSettings from "lib/settings";
1922
import LigaturesAddon from "./ligatures";
2023
import { getTerminalSettings } from "./terminalDefaults";
@@ -61,6 +64,8 @@ export default class TerminalComponent {
6164
this.isConnected = false;
6265
this.serverMode = options.serverMode !== false; // Default true
6366
this.touchSelection = null;
67+
this.parsedAppKeybindings = [];
68+
this.parsedAppKeybindingsVersion = -1;
6469

6570
this.init();
6671
}
@@ -335,9 +340,14 @@ export default class TerminalComponent {
335340
* Parse app keybindings into a format usable by the keyboard handler
336341
*/
337342
parseAppKeybindings() {
343+
const version = getResolvedKeyBindingsVersion();
344+
if (this.parsedAppKeybindingsVersion === version) {
345+
return this.parsedAppKeybindings;
346+
}
347+
338348
const parsedBindings = [];
339349

340-
Object.values(keyBindings).forEach((binding) => {
350+
Object.values(getResolvedKeyBindings()).forEach((binding) => {
341351
if (!binding.key) return;
342352

343353
// Skip editor-only keybindings in terminal
@@ -368,7 +378,7 @@ export default class TerminalComponent {
368378
parsed.meta = true;
369379
} else {
370380
// This is the actual key
371-
parsed.key = part;
381+
parsed.key = part.toLowerCase();
372382
}
373383
});
374384

@@ -378,7 +388,10 @@ export default class TerminalComponent {
378388
});
379389
});
380390

381-
return parsedBindings;
391+
this.parsedAppKeybindings = parsedBindings;
392+
this.parsedAppKeybindingsVersion = version;
393+
394+
return this.parsedAppKeybindings;
382395
}
383396

384397
/**
@@ -432,7 +445,7 @@ export default class TerminalComponent {
432445
binding.shift === event.shiftKey &&
433446
binding.alt === event.altKey &&
434447
binding.meta === event.metaKey &&
435-
binding.key === event.key,
448+
binding.key === event.key.toLowerCase(),
436449
);
437450

438451
if (isAppKeybinding) {

0 commit comments

Comments
 (0)