Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 55 additions & 2 deletions src/cm/commandRegistry.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ const commandKeymapCompartment = new Compartment();
* run: (view?: EditorView | null) => boolean | void;
* requiresView?: boolean;
* defaultDescription?: string;
* defaultKey?: string | null;
* key?: string | null;
* }} CommandEntry
*/
Expand All @@ -118,6 +119,11 @@ const commandMap = new Map();
/** @type {Record<string, any>} */
let resolvedKeyBindings = keyBindings;

/** @type {Record<string, any>} */
let cachedResolvedKeyBindings = {};

let resolvedKeyBindingsVersion = 0;

/** @type {import("@codemirror/view").KeyBinding[]} */
let cachedKeymap = [];

Expand Down Expand Up @@ -1177,6 +1183,7 @@ function addCommand(entry) {
const command = {
...entry,
defaultDescription: entry.description || entry.name,
defaultKey: entry.key ?? null,
key: entry.key ?? null,
};
commandMap.set(entry.name, command);
Expand Down Expand Up @@ -1314,6 +1321,39 @@ function parseKeyString(keyString) {
.filter(Boolean);
}

function hasOwnBindingOverride(name) {
return Object.prototype.hasOwnProperty.call(resolvedKeyBindings ?? {}, name);
}

function resolveBindingInfo(name) {
const baseBinding = keyBindings[name] ?? null;
if (!hasOwnBindingOverride(name)) return baseBinding;

const override = resolvedKeyBindings?.[name];
if (override === null) {
return baseBinding ? { ...baseBinding, key: null } : { key: null };
}

if (!override || typeof override !== "object") {
return baseBinding;
}

return baseBinding ? { ...baseBinding, ...override } : override;
}

function buildResolvedKeyBindingsSnapshot() {
const bindingNames = new Set([
...Object.keys(keyBindings),
...Object.keys(resolvedKeyBindings ?? {}),
]);

return Object.fromEntries(
Array.from(bindingNames, (name) => [name, resolveBindingInfo(name)]).filter(
([, binding]) => binding,
),
);
}

function toCodeMirrorKey(combo) {
if (!combo) return null;
const parts = combo
Expand Down Expand Up @@ -1355,11 +1395,15 @@ function toCodeMirrorKey(combo) {

function rebuildKeymap() {
const bindings = [];
cachedResolvedKeyBindings = buildResolvedKeyBindingsSnapshot();
commandMap.forEach((command, name) => {
const bindingInfo = resolvedKeyBindings?.[name];
const bindingInfo = resolveBindingInfo(name);
command.description =
bindingInfo?.description || command.defaultDescription;
const keySource = bindingInfo?.key ?? command.key ?? null;
const keySource =
bindingInfo && Object.prototype.hasOwnProperty.call(bindingInfo, "key")
? bindingInfo.key
: (command.defaultKey ?? null);
command.key = keySource;
const combos = parseKeyString(keySource);
combos.forEach((combo) => {
Expand All @@ -1373,6 +1417,7 @@ function rebuildKeymap() {
});
});
cachedKeymap = bindings;
resolvedKeyBindingsVersion += 1;
return bindings;
}

Expand Down Expand Up @@ -1410,6 +1455,14 @@ export function getRegisteredCommands() {
}));
}

export function getResolvedKeyBindings() {
return cachedResolvedKeyBindings;
}

export function getResolvedKeyBindingsVersion() {
return resolvedKeyBindingsVersion;
}

export function getCommandKeymapExtension() {
return commandKeymapCompartment.of(keymap.of(cachedKeymap));
}
Expand Down
23 changes: 18 additions & 5 deletions src/components/terminal/terminal.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@ import { Unicode11Addon } from "@xterm/addon-unicode11";
import { WebLinksAddon } from "@xterm/addon-web-links";
import { WebglAddon } from "@xterm/addon-webgl";
import { Terminal as Xterm } from "@xterm/xterm";
import {
getResolvedKeyBindings,
getResolvedKeyBindingsVersion,
} from "cm/commandRegistry";
import toast from "components/toast";
import confirm from "dialogs/confirm";
import fonts from "lib/fonts";
import keyBindings from "lib/keyBindings";
import appSettings from "lib/settings";
import LigaturesAddon from "./ligatures";
import { getTerminalSettings } from "./terminalDefaults";
Expand Down Expand Up @@ -61,6 +64,8 @@ export default class TerminalComponent {
this.isConnected = false;
this.serverMode = options.serverMode !== false; // Default true
this.touchSelection = null;
this.parsedAppKeybindings = [];
this.parsedAppKeybindingsVersion = -1;

this.init();
}
Expand Down Expand Up @@ -335,9 +340,14 @@ export default class TerminalComponent {
* Parse app keybindings into a format usable by the keyboard handler
*/
parseAppKeybindings() {
const version = getResolvedKeyBindingsVersion();
if (this.parsedAppKeybindingsVersion === version) {
return this.parsedAppKeybindings;
}

const parsedBindings = [];

Object.values(keyBindings).forEach((binding) => {
Object.values(getResolvedKeyBindings()).forEach((binding) => {
if (!binding.key) return;

// Skip editor-only keybindings in terminal
Expand Down Expand Up @@ -368,7 +378,7 @@ export default class TerminalComponent {
parsed.meta = true;
} else {
// This is the actual key
parsed.key = part;
parsed.key = part.toLowerCase();
}
});

Expand All @@ -378,7 +388,10 @@ export default class TerminalComponent {
});
});

return parsedBindings;
this.parsedAppKeybindings = parsedBindings;
this.parsedAppKeybindingsVersion = version;

return this.parsedAppKeybindings;
}

/**
Expand Down Expand Up @@ -432,7 +445,7 @@ export default class TerminalComponent {
binding.shift === event.shiftKey &&
binding.alt === event.altKey &&
binding.meta === event.metaKey &&
binding.key === event.key,
binding.key === event.key.toLowerCase(),
);

if (isAppKeybinding) {
Expand Down