@opentui/keymap 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/package.json +30 -20
- package/{addons → src/addons}/index.js +310 -61
- package/{addons → src/addons}/opentui/index.js +347 -98
- package/src/addons/universal/binding-overrides.d.ts +6 -0
- package/src/addons/universal/index.d.ts +1 -0
- package/src/extras/binding-sections.d.ts +18 -0
- package/src/extras/command-bindings.d.ts +19 -0
- package/src/extras/formatting.d.ts +24 -0
- package/src/extras/index.d.ts +6 -0
- package/src/extras/index.js +226 -0
- package/{html.js → src/html.js} +259 -59
- package/src/index.d.ts +1 -1
- package/{index.js → src/index.js} +259 -59
- package/src/keymap.d.ts +7 -3
- package/{opentui.js → src/opentui.js} +259 -59
- package/src/runtime-modules.d.ts +18 -0
- package/src/runtime-modules.js +23 -0
- package/src/services/command-catalog.d.ts +12 -4
- package/src/services/command-executor.d.ts +1 -1
- package/src/services/compiler.d.ts +3 -1
- package/src/services/environment.d.ts +4 -1
- package/src/services/layers.d.ts +1 -0
- package/src/services/primitives/binding-inputs.d.ts +2 -2
- package/src/services/primitives/command-normalization.d.ts +3 -0
- package/src/services/state.d.ts +2 -1
- package/src/types.d.ts +18 -2
- /package/{react → src/react}/index.js +0 -0
- /package/{solid → src/solid}/index.js +0 -0
|
@@ -144,7 +144,7 @@ function stringifyKeyStroke(input, options) {
|
|
|
144
144
|
return stringifyCanonicalStroke(normalizeKeyStroke(input));
|
|
145
145
|
}
|
|
146
146
|
function stringifyKeySequence(input, options) {
|
|
147
|
-
return input.map((part) => stringifyKeyStroke(part, options)).join("");
|
|
147
|
+
return input.map((part) => stringifyKeyStroke(part, options)).join(options?.separator ?? "");
|
|
148
148
|
}
|
|
149
149
|
function stringifyCanonicalStroke(stroke) {
|
|
150
150
|
const parts = [];
|
|
@@ -729,6 +729,28 @@ var RESERVED_COMMAND_FIELDS = new Set(["name", "run"]);
|
|
|
729
729
|
var RESERVED_BINDING_FIELDS = new Set(["key", "cmd", "event", "preventDefault", "fallthrough"]);
|
|
730
730
|
var RESERVED_LAYER_FIELDS = new Set(["target", "targetMode", "priority", "bindings", "commands"]);
|
|
731
731
|
|
|
732
|
+
// src/services/primitives/command-normalization.ts
|
|
733
|
+
function normalizeBindingCommand(command) {
|
|
734
|
+
if (command === undefined || typeof command === "function") {
|
|
735
|
+
return command;
|
|
736
|
+
}
|
|
737
|
+
const trimmed = command.trim();
|
|
738
|
+
if (!trimmed) {
|
|
739
|
+
throw new Error("Invalid keymap command: command cannot be empty");
|
|
740
|
+
}
|
|
741
|
+
return trimmed;
|
|
742
|
+
}
|
|
743
|
+
function normalizeCommandName(name) {
|
|
744
|
+
const trimmed = name.trim();
|
|
745
|
+
if (!trimmed) {
|
|
746
|
+
throw new Error("Invalid keymap command name: name cannot be empty");
|
|
747
|
+
}
|
|
748
|
+
if (/\s/.test(trimmed)) {
|
|
749
|
+
throw new Error(`Invalid keymap command name "${name}": command names cannot contain whitespace`);
|
|
750
|
+
}
|
|
751
|
+
return trimmed;
|
|
752
|
+
}
|
|
753
|
+
|
|
732
754
|
// src/services/primitives/field-invariants.ts
|
|
733
755
|
function mergeRequirement(target, name, value, source) {
|
|
734
756
|
if (Object.prototype.hasOwnProperty.call(target, name) && !Object.is(target[name], value)) {
|
|
@@ -806,26 +828,6 @@ function createCommandChainCacheState() {
|
|
|
806
828
|
fallbackWithRecordErrors: new Set
|
|
807
829
|
};
|
|
808
830
|
}
|
|
809
|
-
function normalizeBindingCommand(command) {
|
|
810
|
-
if (command === undefined || typeof command === "function") {
|
|
811
|
-
return command;
|
|
812
|
-
}
|
|
813
|
-
const trimmed = command.trim();
|
|
814
|
-
if (!trimmed) {
|
|
815
|
-
throw new Error("Invalid keymap command: command cannot be empty");
|
|
816
|
-
}
|
|
817
|
-
return trimmed;
|
|
818
|
-
}
|
|
819
|
-
function normalizeCommandName(name) {
|
|
820
|
-
const trimmed = name.trim();
|
|
821
|
-
if (!trimmed) {
|
|
822
|
-
throw new Error("Invalid keymap command name: name cannot be empty");
|
|
823
|
-
}
|
|
824
|
-
if (/\s/.test(trimmed)) {
|
|
825
|
-
throw new Error(`Invalid keymap command name "${name}": command names cannot contain whitespace`);
|
|
826
|
-
}
|
|
827
|
-
return trimmed;
|
|
828
|
-
}
|
|
829
831
|
|
|
830
832
|
class CommandCatalogService {
|
|
831
833
|
state;
|
|
@@ -898,6 +900,19 @@ class CommandCatalogService {
|
|
|
898
900
|
bindings: item.bindings
|
|
899
901
|
}));
|
|
900
902
|
}
|
|
903
|
+
getCommandBindings(query) {
|
|
904
|
+
const bindingsByCommand = new Map;
|
|
905
|
+
for (const command of query.commands) {
|
|
906
|
+
if (!bindingsByCommand.has(command)) {
|
|
907
|
+
bindingsByCommand.set(command, []);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
if (bindingsByCommand.size === 0) {
|
|
911
|
+
return bindingsByCommand;
|
|
912
|
+
}
|
|
913
|
+
this.collectCommandBindings(bindingsByCommand, this.getCommandQueryContext(query));
|
|
914
|
+
return bindingsByCommand;
|
|
915
|
+
}
|
|
901
916
|
getResolvedCommandChain(command, focused, includeRecord) {
|
|
902
917
|
const view = this.getActiveCommandView(focused);
|
|
903
918
|
const entries = this.getResolvedCommandChainFromView(view, command, focused, includeRecord, "active", view.chainsByName.get(command));
|
|
@@ -926,14 +941,26 @@ class CommandCatalogService {
|
|
|
926
941
|
cache.set(command, resolved);
|
|
927
942
|
return resolved;
|
|
928
943
|
}
|
|
929
|
-
|
|
930
|
-
const view = this.
|
|
931
|
-
const
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
944
|
+
getActiveRegisteredResolvedEntries(command, focused, includeRecord) {
|
|
945
|
+
const view = this.getActiveCommandView(focused);
|
|
946
|
+
const chain = view.chainsByName.get(command);
|
|
947
|
+
if (!chain || chain.length === 0) {
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
const resolved = [];
|
|
951
|
+
for (const entry of chain) {
|
|
952
|
+
resolved.push({
|
|
953
|
+
target: entry.layer.target,
|
|
954
|
+
resolved: resolveRegisteredCommand(entry.command, { includeRecord })
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
return resolved;
|
|
958
|
+
}
|
|
959
|
+
resolveRegisteredResolverFallback(command, includeRecord) {
|
|
960
|
+
return this.resolveCommandWithResolvers(command, null, { includeRecord, mode: "registered" });
|
|
961
|
+
}
|
|
962
|
+
resolveActiveResolverFallback(command, focused, includeRecord) {
|
|
963
|
+
return this.resolveCommandWithResolvers(command, focused, { includeRecord, mode: "active" });
|
|
937
964
|
}
|
|
938
965
|
getCommandAttrs(command, focused) {
|
|
939
966
|
const top = this.getTopResolvedCommand(command, focused, false);
|
|
@@ -1240,6 +1267,31 @@ class CommandCatalogService {
|
|
|
1240
1267
|
}
|
|
1241
1268
|
}
|
|
1242
1269
|
}
|
|
1270
|
+
collectCommandBindings(bindingsByCommand, context) {
|
|
1271
|
+
if (context.visibility === "registered") {
|
|
1272
|
+
for (const layer of this.state.layers.layers) {
|
|
1273
|
+
for (const binding of layer.compiledBindings) {
|
|
1274
|
+
this.collectBindingForCommandBindings(bindingsByCommand, binding, context);
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
return;
|
|
1278
|
+
}
|
|
1279
|
+
const activeView = context.activeView;
|
|
1280
|
+
if (!activeView) {
|
|
1281
|
+
return;
|
|
1282
|
+
}
|
|
1283
|
+
for (const layer of getActiveLayersForFocused(this.state.layers, this.host, context.focused)) {
|
|
1284
|
+
if (layer.compiledBindings.length === 0 || !this.conditions.layerMatchesRuntimeState(layer)) {
|
|
1285
|
+
continue;
|
|
1286
|
+
}
|
|
1287
|
+
for (const binding of layer.compiledBindings) {
|
|
1288
|
+
if (!this.conditions.matchesConditions(binding) || !this.isBindingVisible(binding, context.focused, activeView)) {
|
|
1289
|
+
continue;
|
|
1290
|
+
}
|
|
1291
|
+
this.collectBindingForCommandBindings(bindingsByCommand, binding, context);
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1243
1295
|
collectBindingForCommandEntries(grouped, indexesByName, binding) {
|
|
1244
1296
|
if (typeof binding.command !== "string") {
|
|
1245
1297
|
return;
|
|
@@ -1253,16 +1305,42 @@ class CommandCatalogService {
|
|
|
1253
1305
|
if (!item) {
|
|
1254
1306
|
continue;
|
|
1255
1307
|
}
|
|
1256
|
-
item.bindings.push(
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1308
|
+
item.bindings.push(this.createActiveBinding(binding, item.command.attrs));
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
collectBindingForCommandBindings(bindingsByCommand, binding, context) {
|
|
1312
|
+
if (typeof binding.command !== "string") {
|
|
1313
|
+
return;
|
|
1314
|
+
}
|
|
1315
|
+
const bindings = bindingsByCommand.get(binding.command);
|
|
1316
|
+
if (!bindings) {
|
|
1317
|
+
return;
|
|
1318
|
+
}
|
|
1319
|
+
bindings.push(this.createActiveBinding(binding, this.getCommandBindingAttrs(binding, context)));
|
|
1320
|
+
}
|
|
1321
|
+
createActiveBinding(binding, commandAttrs) {
|
|
1322
|
+
return {
|
|
1323
|
+
sequence: binding.sequence,
|
|
1324
|
+
command: binding.command,
|
|
1325
|
+
commandAttrs,
|
|
1326
|
+
attrs: binding.attrs,
|
|
1327
|
+
event: binding.event,
|
|
1328
|
+
preventDefault: binding.preventDefault,
|
|
1329
|
+
fallthrough: binding.fallthrough
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1332
|
+
getCommandBindingAttrs(binding, context) {
|
|
1333
|
+
if (typeof binding.command !== "string") {
|
|
1334
|
+
return;
|
|
1265
1335
|
}
|
|
1336
|
+
if (context.visibility === "registered") {
|
|
1337
|
+
return this.getTopRegisteredCommand(binding.command)?.command.attrs;
|
|
1338
|
+
}
|
|
1339
|
+
const activeView = context.activeView;
|
|
1340
|
+
if (!activeView) {
|
|
1341
|
+
return;
|
|
1342
|
+
}
|
|
1343
|
+
return this.getBindingCommandAttrs(binding, context.focused, activeView);
|
|
1266
1344
|
}
|
|
1267
1345
|
resolveCommandWithResolvers(command, focused, options) {
|
|
1268
1346
|
const includeRecord = options?.includeRecord === true;
|
|
@@ -1457,12 +1535,53 @@ function queryLayerCommandEntries(options) {
|
|
|
1457
1535
|
const filter = options.query?.filter;
|
|
1458
1536
|
let filterEntries;
|
|
1459
1537
|
let filterPredicate;
|
|
1538
|
+
let exactNameFilter;
|
|
1460
1539
|
if (typeof filter === "function") {
|
|
1461
1540
|
filterPredicate = filter;
|
|
1462
1541
|
} else if (filter) {
|
|
1463
|
-
|
|
1542
|
+
const entries = Object.entries(filter);
|
|
1543
|
+
const remainingEntries = [];
|
|
1544
|
+
for (const [key, matcher] of entries) {
|
|
1545
|
+
if (key === "name") {
|
|
1546
|
+
if (typeof matcher === "string") {
|
|
1547
|
+
exactNameFilter = new Set([matcher]);
|
|
1548
|
+
continue;
|
|
1549
|
+
}
|
|
1550
|
+
if (Array.isArray(matcher)) {
|
|
1551
|
+
const names = new Set;
|
|
1552
|
+
for (const value of matcher) {
|
|
1553
|
+
if (typeof value === "string") {
|
|
1554
|
+
names.add(value);
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
exactNameFilter = names;
|
|
1558
|
+
continue;
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
remainingEntries.push([key, matcher]);
|
|
1562
|
+
}
|
|
1563
|
+
filterEntries = remainingEntries.length > 0 ? remainingEntries : undefined;
|
|
1464
1564
|
}
|
|
1465
1565
|
const results = [];
|
|
1566
|
+
if (exactNameFilter) {
|
|
1567
|
+
for (const entry of options.entries) {
|
|
1568
|
+
const command = entry.command;
|
|
1569
|
+
if (!commandMatchesNamespace(command, namespace)) {
|
|
1570
|
+
continue;
|
|
1571
|
+
}
|
|
1572
|
+
if (!commandMatchesSearch(command, normalizedSearch, searchKeys)) {
|
|
1573
|
+
continue;
|
|
1574
|
+
}
|
|
1575
|
+
if (!exactNameFilter.has(command.name)) {
|
|
1576
|
+
continue;
|
|
1577
|
+
}
|
|
1578
|
+
if (!commandMatchesFilters(command, filterEntries, options)) {
|
|
1579
|
+
continue;
|
|
1580
|
+
}
|
|
1581
|
+
results.push(entry);
|
|
1582
|
+
}
|
|
1583
|
+
return results;
|
|
1584
|
+
}
|
|
1466
1585
|
for (const entry of options.entries) {
|
|
1467
1586
|
const command = entry.command;
|
|
1468
1587
|
if (!commandMatchesNamespace(command, namespace)) {
|
|
@@ -1702,7 +1821,7 @@ class CommandExecutorService {
|
|
|
1702
1821
|
rejectedResult = execution.result;
|
|
1703
1822
|
}
|
|
1704
1823
|
}
|
|
1705
|
-
const fallback = this.catalog.
|
|
1824
|
+
const fallback = this.catalog.resolveRegisteredResolverFallback(normalized, includeRecord);
|
|
1706
1825
|
if (fallback.resolved) {
|
|
1707
1826
|
const execution = this.executeResolvedCommand(normalized, fallback.resolved, {
|
|
1708
1827
|
keymap: this.options.keymap,
|
|
@@ -1735,8 +1854,7 @@ class CommandExecutorService {
|
|
|
1735
1854
|
const focused = options?.focused ?? this.activation.getFocusedTargetIfAvailable();
|
|
1736
1855
|
const event = options?.event ?? this.options.createCommandEvent();
|
|
1737
1856
|
const data = this.runtime.getReadonlyData();
|
|
1738
|
-
const
|
|
1739
|
-
const chain = chainLookup.entries;
|
|
1857
|
+
const chain = this.catalog.getActiveRegisteredResolvedEntries(normalized, focused, includeRecord);
|
|
1740
1858
|
let rejectedResult;
|
|
1741
1859
|
if (chain?.length === 1) {
|
|
1742
1860
|
const [entry] = chain;
|
|
@@ -1769,7 +1887,21 @@ class CommandExecutorService {
|
|
|
1769
1887
|
rejectedResult = execution.result;
|
|
1770
1888
|
}
|
|
1771
1889
|
}
|
|
1772
|
-
|
|
1890
|
+
const fallback = this.catalog.resolveActiveResolverFallback(normalized, focused, includeRecord);
|
|
1891
|
+
if (fallback.resolved) {
|
|
1892
|
+
const execution = this.executeResolvedCommand(normalized, fallback.resolved, {
|
|
1893
|
+
keymap: this.options.keymap,
|
|
1894
|
+
event,
|
|
1895
|
+
focused,
|
|
1896
|
+
target: options?.target ?? null,
|
|
1897
|
+
data
|
|
1898
|
+
});
|
|
1899
|
+
if (execution.status === "handled" || execution.status === "error") {
|
|
1900
|
+
return execution.result;
|
|
1901
|
+
}
|
|
1902
|
+
rejectedResult = execution.result;
|
|
1903
|
+
}
|
|
1904
|
+
if (fallback.hadError) {
|
|
1773
1905
|
return { ok: false, reason: "error" };
|
|
1774
1906
|
}
|
|
1775
1907
|
const unavailable = this.catalog.getDispatchUnavailableCommandState(normalized, focused, includeRecord);
|
|
@@ -1881,21 +2013,32 @@ function applyBindingEventEffects(binding, event) {
|
|
|
1881
2013
|
}
|
|
1882
2014
|
|
|
1883
2015
|
// src/services/primitives/binding-inputs.ts
|
|
1884
|
-
function
|
|
1885
|
-
|
|
1886
|
-
|
|
2016
|
+
function isKeyLike(value) {
|
|
2017
|
+
return typeof value === "string" || !!value && typeof value === "object" && !Array.isArray(value);
|
|
2018
|
+
}
|
|
2019
|
+
function validateBindingInputs(bindings) {
|
|
2020
|
+
if (!Array.isArray(bindings)) {
|
|
2021
|
+
return { ok: false, reason: "Invalid keymap bindings: expected an array of binding objects" };
|
|
1887
2022
|
}
|
|
1888
|
-
const
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
2023
|
+
for (const [index, binding] of bindings.entries()) {
|
|
2024
|
+
if (!binding || typeof binding !== "object" || Array.isArray(binding)) {
|
|
2025
|
+
return { ok: false, reason: `Invalid keymap binding at index ${index}: expected a binding object` };
|
|
2026
|
+
}
|
|
2027
|
+
if (!isKeyLike(binding.key)) {
|
|
2028
|
+
return {
|
|
2029
|
+
ok: false,
|
|
2030
|
+
reason: `Invalid keymap binding at index ${index}: expected "key" to be a string or keystroke object`
|
|
2031
|
+
};
|
|
1892
2032
|
}
|
|
1893
|
-
normalized.push({ key, cmd });
|
|
1894
2033
|
}
|
|
1895
|
-
return
|
|
2034
|
+
return { ok: true };
|
|
1896
2035
|
}
|
|
1897
2036
|
function snapshotBindingInputs(bindings) {
|
|
1898
|
-
|
|
2037
|
+
const validation = validateBindingInputs(bindings);
|
|
2038
|
+
if (!validation.ok) {
|
|
2039
|
+
throw new Error(validation.reason);
|
|
2040
|
+
}
|
|
2041
|
+
return bindings.map((binding) => ({
|
|
1899
2042
|
...binding,
|
|
1900
2043
|
key: typeof binding.key === "string" ? binding.key : { ...binding.key }
|
|
1901
2044
|
}));
|
|
@@ -1948,6 +2091,23 @@ class CompilerService {
|
|
|
1948
2091
|
parseObjectKey: (value, options) => this.parseObjectKeyPart(value, options)
|
|
1949
2092
|
});
|
|
1950
2093
|
}
|
|
2094
|
+
parseKeySequence(key) {
|
|
2095
|
+
if (typeof key !== "string") {
|
|
2096
|
+
return [this.parseObjectKeyPart(key)];
|
|
2097
|
+
}
|
|
2098
|
+
const parsed = parseBindingSequenceWithParsers(key, this.state.environment.bindingParsers.values(), {
|
|
2099
|
+
tokens: this.state.environment.tokens,
|
|
2100
|
+
layer: EMPTY_COMPILE_FIELDS,
|
|
2101
|
+
parseObjectKey: (value, options) => this.parseObjectKeyPart(value, options)
|
|
2102
|
+
});
|
|
2103
|
+
for (const tokenName of parsed.unknownTokens) {
|
|
2104
|
+
this.options.warnUnknownToken(tokenName, key);
|
|
2105
|
+
}
|
|
2106
|
+
return parsed.parts;
|
|
2107
|
+
}
|
|
2108
|
+
formatKey(key, options) {
|
|
2109
|
+
return stringifyKeySequence(this.parseKeySequence(key), options);
|
|
2110
|
+
}
|
|
1951
2111
|
compileBindings(bindings, tokens, sourceTarget, sourceLayerOrder, compileFields) {
|
|
1952
2112
|
const root = createSequenceNode(null, null, null);
|
|
1953
2113
|
const compiledBindings = [];
|
|
@@ -3219,6 +3379,15 @@ class EnvironmentService {
|
|
|
3219
3379
|
prependBindingTransformer(transformer) {
|
|
3220
3380
|
return this.state.environment.bindingTransformers.prepend(transformer);
|
|
3221
3381
|
}
|
|
3382
|
+
prependLayerBindingsTransformer(transformer) {
|
|
3383
|
+
return this.state.environment.layerBindingsTransformers.prepend(transformer);
|
|
3384
|
+
}
|
|
3385
|
+
appendLayerBindingsTransformer(transformer) {
|
|
3386
|
+
return this.state.environment.layerBindingsTransformers.append(transformer);
|
|
3387
|
+
}
|
|
3388
|
+
clearLayerBindingsTransformers() {
|
|
3389
|
+
this.state.environment.layerBindingsTransformers.clear();
|
|
3390
|
+
}
|
|
3222
3391
|
appendBindingTransformer(transformer) {
|
|
3223
3392
|
return this.state.environment.bindingTransformers.append(transformer);
|
|
3224
3393
|
}
|
|
@@ -3417,7 +3586,7 @@ class LayerService {
|
|
|
3417
3586
|
let targetMode;
|
|
3418
3587
|
try {
|
|
3419
3588
|
targetMode = this.normalizeTargetMode(layer);
|
|
3420
|
-
bindingInputs = snapshotBindingInputs(layer.bindings ?? []);
|
|
3589
|
+
bindingInputs = this.applyLayerBindingsTransformers(snapshotBindingInputs(layer.bindings ?? []), layer);
|
|
3421
3590
|
commands = !layer.commands || layer.commands.length === 0 ? [] : this.options.commands.normalizeCommands(layer.commands);
|
|
3422
3591
|
commandLookup = createCommandLookup(commands);
|
|
3423
3592
|
({ requires, matchers, conditionKeys, hasUnkeyedMatchers, compileFields } = this.compileLayerRuntimeState(layer));
|
|
@@ -3570,6 +3739,24 @@ class LayerService {
|
|
|
3570
3739
|
}
|
|
3571
3740
|
return layer.target ? "focus-within" : undefined;
|
|
3572
3741
|
}
|
|
3742
|
+
applyLayerBindingsTransformers(bindings, layer) {
|
|
3743
|
+
const transformers = this.state.environment.layerBindingsTransformers.values();
|
|
3744
|
+
if (transformers.length === 0) {
|
|
3745
|
+
return bindings;
|
|
3746
|
+
}
|
|
3747
|
+
let current = bindings;
|
|
3748
|
+
for (const transformer of transformers) {
|
|
3749
|
+
const next = transformer(current, {
|
|
3750
|
+
layer,
|
|
3751
|
+
validateBindings: (bindings2) => validateBindingInputs(bindings2)
|
|
3752
|
+
});
|
|
3753
|
+
if (!next) {
|
|
3754
|
+
continue;
|
|
3755
|
+
}
|
|
3756
|
+
current = snapshotBindingInputs(next);
|
|
3757
|
+
}
|
|
3758
|
+
return current;
|
|
3759
|
+
}
|
|
3573
3760
|
runLayerAnalyzers(options) {
|
|
3574
3761
|
const analyzers = this.state.layers.layerAnalyzers.values();
|
|
3575
3762
|
if (analyzers.length === 0) {
|
|
@@ -4043,6 +4230,7 @@ function createKeymapState() {
|
|
|
4043
4230
|
environment: {
|
|
4044
4231
|
tokens: new Map,
|
|
4045
4232
|
layerFields: new Map,
|
|
4233
|
+
layerBindingsTransformers: new OrderedRegistry,
|
|
4046
4234
|
bindingExpanders: new OrderedRegistry,
|
|
4047
4235
|
bindingParsers: new OrderedRegistry,
|
|
4048
4236
|
bindingTransformers: new OrderedRegistry,
|
|
@@ -4239,6 +4427,12 @@ class Keymap {
|
|
|
4239
4427
|
return getKeyMatchKey(input) === match;
|
|
4240
4428
|
};
|
|
4241
4429
|
}
|
|
4430
|
+
parseKeySequence(key) {
|
|
4431
|
+
return this.compiler.parseKeySequence(key);
|
|
4432
|
+
}
|
|
4433
|
+
formatKey(key, options) {
|
|
4434
|
+
return this.compiler.formatKey(key, options);
|
|
4435
|
+
}
|
|
4242
4436
|
clearPendingSequence() {
|
|
4243
4437
|
this.activation.setPendingSequence(null);
|
|
4244
4438
|
}
|
|
@@ -4254,11 +4448,8 @@ class Keymap {
|
|
|
4254
4448
|
getCommandEntries(query) {
|
|
4255
4449
|
return this.catalog.getCommandEntries(query);
|
|
4256
4450
|
}
|
|
4257
|
-
|
|
4258
|
-
return
|
|
4259
|
-
}
|
|
4260
|
-
normalizeBindings(bindings) {
|
|
4261
|
-
return normalizeBindingInputs(bindings);
|
|
4451
|
+
getCommandBindings(query) {
|
|
4452
|
+
return this.catalog.getCommandBindings(query);
|
|
4262
4453
|
}
|
|
4263
4454
|
acquireResource(key, setup) {
|
|
4264
4455
|
if (this.cleanedUp || this.host.isDestroyed) {
|
|
@@ -4305,6 +4496,15 @@ class Keymap {
|
|
|
4305
4496
|
registerLayerFields(fields) {
|
|
4306
4497
|
return this.environment.registerLayerFields(fields);
|
|
4307
4498
|
}
|
|
4499
|
+
prependLayerBindingsTransformer(transformer) {
|
|
4500
|
+
return this.environment.prependLayerBindingsTransformer(transformer);
|
|
4501
|
+
}
|
|
4502
|
+
appendLayerBindingsTransformer(transformer) {
|
|
4503
|
+
return this.environment.appendLayerBindingsTransformer(transformer);
|
|
4504
|
+
}
|
|
4505
|
+
clearLayerBindingsTransformers() {
|
|
4506
|
+
this.environment.clearLayerBindingsTransformers();
|
|
4507
|
+
}
|
|
4308
4508
|
prependBindingTransformer(transformer) {
|
|
4309
4509
|
return this.environment.prependBindingTransformer(transformer);
|
|
4310
4510
|
}
|
|
@@ -4695,6 +4895,48 @@ function registerDefaultKeys(keymap) {
|
|
|
4695
4895
|
offParser();
|
|
4696
4896
|
};
|
|
4697
4897
|
}
|
|
4898
|
+
// src/addons/universal/binding-overrides.ts
|
|
4899
|
+
function normalizeBindingOverrides(value) {
|
|
4900
|
+
if (!Array.isArray(value)) {
|
|
4901
|
+
throw new Error('Keymap layer field "bindingOverrides" must be an array of binding objects');
|
|
4902
|
+
}
|
|
4903
|
+
return value;
|
|
4904
|
+
}
|
|
4905
|
+
function getBindingOverrides(layer) {
|
|
4906
|
+
const overrides = layer.bindingOverrides;
|
|
4907
|
+
if (!overrides || !Array.isArray(overrides)) {
|
|
4908
|
+
return;
|
|
4909
|
+
}
|
|
4910
|
+
return normalizeBindingOverrides(overrides);
|
|
4911
|
+
}
|
|
4912
|
+
function registerBindingOverrides(keymap) {
|
|
4913
|
+
const offLayerField = keymap.registerLayerFields({
|
|
4914
|
+
bindingOverrides(value) {
|
|
4915
|
+
normalizeBindingOverrides(value);
|
|
4916
|
+
}
|
|
4917
|
+
});
|
|
4918
|
+
const offTransformer = keymap.appendLayerBindingsTransformer((bindings, ctx) => {
|
|
4919
|
+
const overrides = getBindingOverrides(ctx.layer);
|
|
4920
|
+
if (!overrides) {
|
|
4921
|
+
return;
|
|
4922
|
+
}
|
|
4923
|
+
const validation = ctx.validateBindings(overrides);
|
|
4924
|
+
if (!validation.ok) {
|
|
4925
|
+
throw new Error(validation.reason);
|
|
4926
|
+
}
|
|
4927
|
+
const overrideCommands = new Set(overrides.flatMap((binding) => typeof binding.cmd === "string" ? [binding.cmd.trim()] : []));
|
|
4928
|
+
return [
|
|
4929
|
+
...overrides,
|
|
4930
|
+
...bindings.filter((binding) => {
|
|
4931
|
+
return typeof binding.cmd !== "string" || !overrideCommands.has(binding.cmd.trim());
|
|
4932
|
+
})
|
|
4933
|
+
];
|
|
4934
|
+
});
|
|
4935
|
+
return () => {
|
|
4936
|
+
offTransformer();
|
|
4937
|
+
offLayerField();
|
|
4938
|
+
};
|
|
4939
|
+
}
|
|
4698
4940
|
// src/addons/universal/aliases.ts
|
|
4699
4941
|
function normalizeAliases(value) {
|
|
4700
4942
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -4964,8 +5206,14 @@ function registerEmacsBindings(keymap) {
|
|
|
4964
5206
|
}
|
|
4965
5207
|
// src/addons/universal/ex-commands.ts
|
|
4966
5208
|
var EMPTY_FIELDS = Object.freeze({});
|
|
4967
|
-
function normalizeExCommandName(
|
|
4968
|
-
const normalized =
|
|
5209
|
+
function normalizeExCommandName(_keymap, name) {
|
|
5210
|
+
const normalized = name.trim();
|
|
5211
|
+
if (!normalized) {
|
|
5212
|
+
throw new Error("Invalid keymap command name: name cannot be empty");
|
|
5213
|
+
}
|
|
5214
|
+
if (/\s/.test(normalized)) {
|
|
5215
|
+
throw new Error(`Invalid keymap command name "${name}": command names cannot contain whitespace`);
|
|
5216
|
+
}
|
|
4969
5217
|
if (normalized.startsWith(":")) {
|
|
4970
5218
|
return normalized;
|
|
4971
5219
|
}
|
|
@@ -5310,42 +5558,42 @@ var editBufferActions = [
|
|
|
5310
5558
|
"submit"
|
|
5311
5559
|
];
|
|
5312
5560
|
var editBufferCommandNames = {
|
|
5313
|
-
"move-left": "move
|
|
5314
|
-
"move-right": "move
|
|
5315
|
-
"move-up": "move
|
|
5316
|
-
"move-down": "move
|
|
5317
|
-
"select-left": "select
|
|
5318
|
-
"select-right": "select
|
|
5319
|
-
"select-up": "select
|
|
5320
|
-
"select-down": "select
|
|
5321
|
-
"line-home": "line
|
|
5322
|
-
"line-end": "line
|
|
5323
|
-
"select-line-home": "select
|
|
5324
|
-
"select-line-end": "select
|
|
5325
|
-
"visual-line-home": "visual
|
|
5326
|
-
"visual-line-end": "visual
|
|
5327
|
-
"select-visual-line-home": "select
|
|
5328
|
-
"select-visual-line-end": "select
|
|
5329
|
-
"buffer-home": "buffer
|
|
5330
|
-
"buffer-end": "buffer
|
|
5331
|
-
"select-buffer-home": "select
|
|
5332
|
-
"select-buffer-end": "select
|
|
5333
|
-
"delete-line": "delete
|
|
5334
|
-
"delete-to-line-end": "delete
|
|
5335
|
-
"delete-to-line-start": "delete
|
|
5336
|
-
backspace: "backspace",
|
|
5337
|
-
delete: "delete",
|
|
5338
|
-
newline: "newline",
|
|
5339
|
-
undo: "undo",
|
|
5340
|
-
redo: "redo",
|
|
5341
|
-
"word-forward": "word
|
|
5342
|
-
"word-backward": "word
|
|
5343
|
-
"select-word-forward": "select
|
|
5344
|
-
"select-word-backward": "select
|
|
5345
|
-
"delete-word-forward": "delete
|
|
5346
|
-
"delete-word-backward": "delete
|
|
5347
|
-
"select-all": "select
|
|
5348
|
-
submit: "submit"
|
|
5561
|
+
"move-left": "input.move.left",
|
|
5562
|
+
"move-right": "input.move.right",
|
|
5563
|
+
"move-up": "input.move.up",
|
|
5564
|
+
"move-down": "input.move.down",
|
|
5565
|
+
"select-left": "input.select.left",
|
|
5566
|
+
"select-right": "input.select.right",
|
|
5567
|
+
"select-up": "input.select.up",
|
|
5568
|
+
"select-down": "input.select.down",
|
|
5569
|
+
"line-home": "input.line.home",
|
|
5570
|
+
"line-end": "input.line.end",
|
|
5571
|
+
"select-line-home": "input.select.line.home",
|
|
5572
|
+
"select-line-end": "input.select.line.end",
|
|
5573
|
+
"visual-line-home": "input.visual.line.home",
|
|
5574
|
+
"visual-line-end": "input.visual.line.end",
|
|
5575
|
+
"select-visual-line-home": "input.select.visual.line.home",
|
|
5576
|
+
"select-visual-line-end": "input.select.visual.line.end",
|
|
5577
|
+
"buffer-home": "input.buffer.home",
|
|
5578
|
+
"buffer-end": "input.buffer.end",
|
|
5579
|
+
"select-buffer-home": "input.select.buffer.home",
|
|
5580
|
+
"select-buffer-end": "input.select.buffer.end",
|
|
5581
|
+
"delete-line": "input.delete.line",
|
|
5582
|
+
"delete-to-line-end": "input.delete.to.line.end",
|
|
5583
|
+
"delete-to-line-start": "input.delete.to.line.start",
|
|
5584
|
+
backspace: "input.backspace",
|
|
5585
|
+
delete: "input.delete",
|
|
5586
|
+
newline: "input.newline",
|
|
5587
|
+
undo: "input.undo",
|
|
5588
|
+
redo: "input.redo",
|
|
5589
|
+
"word-forward": "input.word.forward",
|
|
5590
|
+
"word-backward": "input.word.backward",
|
|
5591
|
+
"select-word-forward": "input.select.word.forward",
|
|
5592
|
+
"select-word-backward": "input.select.word.backward",
|
|
5593
|
+
"delete-word-forward": "input.delete.word.forward",
|
|
5594
|
+
"delete-word-backward": "input.delete.word.backward",
|
|
5595
|
+
"select-all": "input.select.all",
|
|
5596
|
+
submit: "input.submit"
|
|
5349
5597
|
};
|
|
5350
5598
|
var editBufferCommandDescriptions = {
|
|
5351
5599
|
"move-left": "Cursor left",
|
|
@@ -5592,7 +5840,7 @@ function registerManagedTextareaLayer(keymap, renderer, layer, options) {
|
|
|
5592
5840
|
const { bindings, target: _ignoredTarget, targetMode: _ignoredTargetMode, ...rest } = layer;
|
|
5593
5841
|
const offLayer = keymap.registerLayer({
|
|
5594
5842
|
...rest,
|
|
5595
|
-
bindings: createTextareaBindingsWithResolvedOptions(bindings
|
|
5843
|
+
bindings: createTextareaBindingsWithResolvedOptions(bindings, commandNames, descriptions)
|
|
5596
5844
|
});
|
|
5597
5845
|
return () => {
|
|
5598
5846
|
offLayer();
|
|
@@ -5623,6 +5871,7 @@ export {
|
|
|
5623
5871
|
registerDefaultBindingParser,
|
|
5624
5872
|
registerDeadBindingWarnings,
|
|
5625
5873
|
registerCommaBindings,
|
|
5874
|
+
registerBindingOverrides,
|
|
5626
5875
|
registerBaseLayoutFallback,
|
|
5627
5876
|
registerBackspacePopsPendingSequence,
|
|
5628
5877
|
registerAliasesField,
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Keymap, KeymapEvent } from "../../index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Adds a `bindingOverrides` layer field that replaces bindings by string
|
|
4
|
+
* command name within that layer before compilation.
|
|
5
|
+
*/
|
|
6
|
+
export declare function registerBindingOverrides<TTarget extends object, TEvent extends KeymapEvent>(keymap: Keymap<TTarget, TEvent>): () => void;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { defaultBindingParser, defaultEventMatchResolver, registerDefaultBindingParser, registerDefaultEventMatchResolver, registerDefaultKeys, } from "./default-parser.js";
|
|
2
|
+
export { registerBindingOverrides } from "./binding-overrides.js";
|
|
2
3
|
export { registerAliasesField } from "./aliases.js";
|
|
3
4
|
export { registerBackspacePopsPendingSequence } from "./backspace-pops-pending-sequence.js";
|
|
4
5
|
export { registerCommaBindings } from "./comma-bindings.js";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { BindingInput, KeyLike, KeymapEvent } from "../types.js";
|
|
2
|
+
export type BindingSectionItem<TTarget extends object = object, TEvent extends KeymapEvent = KeymapEvent> = KeyLike | BindingInput<TTarget, TEvent>;
|
|
3
|
+
export type BindingValue<TTarget extends object = object, TEvent extends KeymapEvent = KeymapEvent> = false | "none" | BindingSectionItem<TTarget, TEvent> | readonly BindingSectionItem<TTarget, TEvent>[];
|
|
4
|
+
export type BindingSectionConfig<TTarget extends object = object, TEvent extends KeymapEvent = KeymapEvent> = Readonly<Record<string, BindingValue<TTarget, TEvent>>>;
|
|
5
|
+
export type BindingSectionsConfig<TTarget extends object = object, TEvent extends KeymapEvent = KeymapEvent> = Readonly<Record<string, BindingSectionConfig<TTarget, TEvent>>>;
|
|
6
|
+
type LiteralStringKeys<T> = string extends Extract<keyof T, string> ? never : Extract<keyof T, string>;
|
|
7
|
+
export interface ResolvedBindingSections<TTarget extends object = object, TEvent extends KeymapEvent = KeymapEvent, TSection extends string = string> {
|
|
8
|
+
sections: Record<TSection, BindingInput<TTarget, TEvent>[]>;
|
|
9
|
+
get(section: string, cmd: string): readonly BindingInput<TTarget, TEvent>[] | undefined;
|
|
10
|
+
}
|
|
11
|
+
export interface ResolveBindingSectionsOptions<TSection extends string = string> {
|
|
12
|
+
sections?: readonly TSection[];
|
|
13
|
+
}
|
|
14
|
+
export declare function resolveBindingSections<TTarget extends object = object, TEvent extends KeymapEvent = KeymapEvent, const TConfig extends BindingSectionsConfig<TTarget, TEvent> = BindingSectionsConfig<TTarget, TEvent>, const TSection extends string = string>(config: TConfig, options: ResolveBindingSectionsOptions<TSection> & {
|
|
15
|
+
sections: readonly TSection[];
|
|
16
|
+
}): ResolvedBindingSections<TTarget, TEvent, TSection | LiteralStringKeys<TConfig>>;
|
|
17
|
+
export declare function resolveBindingSections<TTarget extends object = object, TEvent extends KeymapEvent = KeymapEvent>(config: BindingSectionsConfig<TTarget, TEvent>, options?: ResolveBindingSectionsOptions): ResolvedBindingSections<TTarget, TEvent>;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { BindingInput, KeyLike, KeymapEvent } from "../types.js";
|
|
2
|
+
export type CommandBindingMap = Record<string, KeyLike>;
|
|
3
|
+
export interface CommandBindingsOverrideWarning {
|
|
4
|
+
code: "command-binding-override";
|
|
5
|
+
command: string;
|
|
6
|
+
previousKey: KeyLike;
|
|
7
|
+
nextKey: KeyLike;
|
|
8
|
+
}
|
|
9
|
+
export interface CommandBindingsError {
|
|
10
|
+
code: "invalid-command-binding";
|
|
11
|
+
command: string;
|
|
12
|
+
value: unknown;
|
|
13
|
+
reason: Error;
|
|
14
|
+
}
|
|
15
|
+
export interface CommandBindingsOptions {
|
|
16
|
+
onWarning?: (warning: CommandBindingsOverrideWarning) => void;
|
|
17
|
+
onError?: (error: CommandBindingsError) => void;
|
|
18
|
+
}
|
|
19
|
+
export declare function commandBindings<TTarget extends object = object, TEvent extends KeymapEvent = KeymapEvent>(bindings: Readonly<CommandBindingMap>, options?: CommandBindingsOptions): BindingInput<TTarget, TEvent>[];
|