@opentui/keymap 0.0.0-20260424-2e949539 → 0.0.0-20260429-00ce2fef
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 +1 -2
- package/addons/index.js +132 -39
- package/addons/opentui/index.js +169 -76
- package/extras/index.js +122 -0
- package/html.js +81 -37
- package/index.js +81 -37
- package/opentui.js +81 -37
- package/package.json +9 -4
- 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 +10 -0
- package/src/extras/command-bindings.d.ts +19 -0
- package/src/extras/index.d.ts +4 -0
- package/src/index.d.ts +1 -1
- package/src/keymap.d.ts +4 -3
- package/src/services/command-catalog.d.ts +1 -3
- package/src/services/command-executor.d.ts +1 -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 +12 -2
- package/src/keymap-benchmark.d.ts +0 -1
- package/src/tests/diagnostic-harness.d.ts +0 -23
- package/src/tests/keymap.test-support.d.ts +0 -41
package/README.md
CHANGED
|
@@ -65,8 +65,7 @@ bun install @opentui/keymap
|
|
|
65
65
|
bun run build
|
|
66
66
|
bun run test
|
|
67
67
|
bun src/keymap-benchmark.ts
|
|
68
|
-
bun run serve:keymap-html
|
|
69
68
|
```
|
|
70
69
|
|
|
71
70
|
- `bun src/keymap-benchmark.ts` runs the benchmark suite from `src/keymap-benchmark.ts`.
|
|
72
|
-
-
|
|
71
|
+
- The HTML demo now lives in the docs app at `/demos/keymap-html/` under `packages/web`.
|
package/addons/index.js
CHANGED
|
@@ -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;
|
|
@@ -1881,21 +1883,32 @@ function applyBindingEventEffects(binding, event) {
|
|
|
1881
1883
|
}
|
|
1882
1884
|
|
|
1883
1885
|
// src/services/primitives/binding-inputs.ts
|
|
1884
|
-
function
|
|
1885
|
-
|
|
1886
|
-
|
|
1886
|
+
function isKeyLike(value) {
|
|
1887
|
+
return typeof value === "string" || !!value && typeof value === "object" && !Array.isArray(value);
|
|
1888
|
+
}
|
|
1889
|
+
function validateBindingInputs(bindings) {
|
|
1890
|
+
if (!Array.isArray(bindings)) {
|
|
1891
|
+
return { ok: false, reason: "Invalid keymap bindings: expected an array of binding objects" };
|
|
1887
1892
|
}
|
|
1888
|
-
const
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1893
|
+
for (const [index, binding] of bindings.entries()) {
|
|
1894
|
+
if (!binding || typeof binding !== "object" || Array.isArray(binding)) {
|
|
1895
|
+
return { ok: false, reason: `Invalid keymap binding at index ${index}: expected a binding object` };
|
|
1896
|
+
}
|
|
1897
|
+
if (!isKeyLike(binding.key)) {
|
|
1898
|
+
return {
|
|
1899
|
+
ok: false,
|
|
1900
|
+
reason: `Invalid keymap binding at index ${index}: expected "key" to be a string or keystroke object`
|
|
1901
|
+
};
|
|
1892
1902
|
}
|
|
1893
|
-
normalized.push({ key, cmd });
|
|
1894
1903
|
}
|
|
1895
|
-
return
|
|
1904
|
+
return { ok: true };
|
|
1896
1905
|
}
|
|
1897
1906
|
function snapshotBindingInputs(bindings) {
|
|
1898
|
-
|
|
1907
|
+
const validation = validateBindingInputs(bindings);
|
|
1908
|
+
if (!validation.ok) {
|
|
1909
|
+
throw new Error(validation.reason);
|
|
1910
|
+
}
|
|
1911
|
+
return bindings.map((binding) => ({
|
|
1899
1912
|
...binding,
|
|
1900
1913
|
key: typeof binding.key === "string" ? binding.key : { ...binding.key }
|
|
1901
1914
|
}));
|
|
@@ -3219,6 +3232,15 @@ class EnvironmentService {
|
|
|
3219
3232
|
prependBindingTransformer(transformer) {
|
|
3220
3233
|
return this.state.environment.bindingTransformers.prepend(transformer);
|
|
3221
3234
|
}
|
|
3235
|
+
prependLayerBindingsTransformer(transformer) {
|
|
3236
|
+
return this.state.environment.layerBindingsTransformers.prepend(transformer);
|
|
3237
|
+
}
|
|
3238
|
+
appendLayerBindingsTransformer(transformer) {
|
|
3239
|
+
return this.state.environment.layerBindingsTransformers.append(transformer);
|
|
3240
|
+
}
|
|
3241
|
+
clearLayerBindingsTransformers() {
|
|
3242
|
+
this.state.environment.layerBindingsTransformers.clear();
|
|
3243
|
+
}
|
|
3222
3244
|
appendBindingTransformer(transformer) {
|
|
3223
3245
|
return this.state.environment.bindingTransformers.append(transformer);
|
|
3224
3246
|
}
|
|
@@ -3417,7 +3439,7 @@ class LayerService {
|
|
|
3417
3439
|
let targetMode;
|
|
3418
3440
|
try {
|
|
3419
3441
|
targetMode = this.normalizeTargetMode(layer);
|
|
3420
|
-
bindingInputs = snapshotBindingInputs(layer.bindings ?? []);
|
|
3442
|
+
bindingInputs = this.applyLayerBindingsTransformers(snapshotBindingInputs(layer.bindings ?? []), layer);
|
|
3421
3443
|
commands = !layer.commands || layer.commands.length === 0 ? [] : this.options.commands.normalizeCommands(layer.commands);
|
|
3422
3444
|
commandLookup = createCommandLookup(commands);
|
|
3423
3445
|
({ requires, matchers, conditionKeys, hasUnkeyedMatchers, compileFields } = this.compileLayerRuntimeState(layer));
|
|
@@ -3570,6 +3592,24 @@ class LayerService {
|
|
|
3570
3592
|
}
|
|
3571
3593
|
return layer.target ? "focus-within" : undefined;
|
|
3572
3594
|
}
|
|
3595
|
+
applyLayerBindingsTransformers(bindings, layer) {
|
|
3596
|
+
const transformers = this.state.environment.layerBindingsTransformers.values();
|
|
3597
|
+
if (transformers.length === 0) {
|
|
3598
|
+
return bindings;
|
|
3599
|
+
}
|
|
3600
|
+
let current = bindings;
|
|
3601
|
+
for (const transformer of transformers) {
|
|
3602
|
+
const next = transformer(current, {
|
|
3603
|
+
layer,
|
|
3604
|
+
validateBindings: (bindings2) => validateBindingInputs(bindings2)
|
|
3605
|
+
});
|
|
3606
|
+
if (!next) {
|
|
3607
|
+
continue;
|
|
3608
|
+
}
|
|
3609
|
+
current = snapshotBindingInputs(next);
|
|
3610
|
+
}
|
|
3611
|
+
return current;
|
|
3612
|
+
}
|
|
3573
3613
|
runLayerAnalyzers(options) {
|
|
3574
3614
|
const analyzers = this.state.layers.layerAnalyzers.values();
|
|
3575
3615
|
if (analyzers.length === 0) {
|
|
@@ -4043,6 +4083,7 @@ function createKeymapState() {
|
|
|
4043
4083
|
environment: {
|
|
4044
4084
|
tokens: new Map,
|
|
4045
4085
|
layerFields: new Map,
|
|
4086
|
+
layerBindingsTransformers: new OrderedRegistry,
|
|
4046
4087
|
bindingExpanders: new OrderedRegistry,
|
|
4047
4088
|
bindingParsers: new OrderedRegistry,
|
|
4048
4089
|
bindingTransformers: new OrderedRegistry,
|
|
@@ -4254,12 +4295,6 @@ class Keymap {
|
|
|
4254
4295
|
getCommandEntries(query) {
|
|
4255
4296
|
return this.catalog.getCommandEntries(query);
|
|
4256
4297
|
}
|
|
4257
|
-
normalizeCommandName(name) {
|
|
4258
|
-
return normalizeCommandName(name);
|
|
4259
|
-
}
|
|
4260
|
-
normalizeBindings(bindings) {
|
|
4261
|
-
return normalizeBindingInputs(bindings);
|
|
4262
|
-
}
|
|
4263
4298
|
acquireResource(key, setup) {
|
|
4264
4299
|
if (this.cleanedUp || this.host.isDestroyed) {
|
|
4265
4300
|
throw new Error("Cannot use a keymap after its host was destroyed");
|
|
@@ -4305,6 +4340,15 @@ class Keymap {
|
|
|
4305
4340
|
registerLayerFields(fields) {
|
|
4306
4341
|
return this.environment.registerLayerFields(fields);
|
|
4307
4342
|
}
|
|
4343
|
+
prependLayerBindingsTransformer(transformer) {
|
|
4344
|
+
return this.environment.prependLayerBindingsTransformer(transformer);
|
|
4345
|
+
}
|
|
4346
|
+
appendLayerBindingsTransformer(transformer) {
|
|
4347
|
+
return this.environment.appendLayerBindingsTransformer(transformer);
|
|
4348
|
+
}
|
|
4349
|
+
clearLayerBindingsTransformers() {
|
|
4350
|
+
this.environment.clearLayerBindingsTransformers();
|
|
4351
|
+
}
|
|
4308
4352
|
prependBindingTransformer(transformer) {
|
|
4309
4353
|
return this.environment.prependBindingTransformer(transformer);
|
|
4310
4354
|
}
|
|
@@ -4695,6 +4739,48 @@ function registerDefaultKeys(keymap) {
|
|
|
4695
4739
|
offParser();
|
|
4696
4740
|
};
|
|
4697
4741
|
}
|
|
4742
|
+
// src/addons/universal/binding-overrides.ts
|
|
4743
|
+
function normalizeBindingOverrides(value) {
|
|
4744
|
+
if (!Array.isArray(value)) {
|
|
4745
|
+
throw new Error('Keymap layer field "bindingOverrides" must be an array of binding objects');
|
|
4746
|
+
}
|
|
4747
|
+
return value;
|
|
4748
|
+
}
|
|
4749
|
+
function getBindingOverrides(layer) {
|
|
4750
|
+
const overrides = layer.bindingOverrides;
|
|
4751
|
+
if (!overrides || !Array.isArray(overrides)) {
|
|
4752
|
+
return;
|
|
4753
|
+
}
|
|
4754
|
+
return normalizeBindingOverrides(overrides);
|
|
4755
|
+
}
|
|
4756
|
+
function registerBindingOverrides(keymap) {
|
|
4757
|
+
const offLayerField = keymap.registerLayerFields({
|
|
4758
|
+
bindingOverrides(value) {
|
|
4759
|
+
normalizeBindingOverrides(value);
|
|
4760
|
+
}
|
|
4761
|
+
});
|
|
4762
|
+
const offTransformer = keymap.appendLayerBindingsTransformer((bindings, ctx) => {
|
|
4763
|
+
const overrides = getBindingOverrides(ctx.layer);
|
|
4764
|
+
if (!overrides) {
|
|
4765
|
+
return;
|
|
4766
|
+
}
|
|
4767
|
+
const validation = ctx.validateBindings(overrides);
|
|
4768
|
+
if (!validation.ok) {
|
|
4769
|
+
throw new Error(validation.reason);
|
|
4770
|
+
}
|
|
4771
|
+
const overrideCommands = new Set(overrides.flatMap((binding) => typeof binding.cmd === "string" ? [binding.cmd.trim()] : []));
|
|
4772
|
+
return [
|
|
4773
|
+
...overrides,
|
|
4774
|
+
...bindings.filter((binding) => {
|
|
4775
|
+
return typeof binding.cmd !== "string" || !overrideCommands.has(binding.cmd.trim());
|
|
4776
|
+
})
|
|
4777
|
+
];
|
|
4778
|
+
});
|
|
4779
|
+
return () => {
|
|
4780
|
+
offTransformer();
|
|
4781
|
+
offLayerField();
|
|
4782
|
+
};
|
|
4783
|
+
}
|
|
4698
4784
|
// src/addons/universal/aliases.ts
|
|
4699
4785
|
function normalizeAliases(value) {
|
|
4700
4786
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -4964,8 +5050,14 @@ function registerEmacsBindings(keymap) {
|
|
|
4964
5050
|
}
|
|
4965
5051
|
// src/addons/universal/ex-commands.ts
|
|
4966
5052
|
var EMPTY_FIELDS = Object.freeze({});
|
|
4967
|
-
function normalizeExCommandName(
|
|
4968
|
-
const normalized =
|
|
5053
|
+
function normalizeExCommandName(_keymap, name) {
|
|
5054
|
+
const normalized = name.trim();
|
|
5055
|
+
if (!normalized) {
|
|
5056
|
+
throw new Error("Invalid keymap command name: name cannot be empty");
|
|
5057
|
+
}
|
|
5058
|
+
if (/\s/.test(normalized)) {
|
|
5059
|
+
throw new Error(`Invalid keymap command name "${name}": command names cannot contain whitespace`);
|
|
5060
|
+
}
|
|
4969
5061
|
if (normalized.startsWith(":")) {
|
|
4970
5062
|
return normalized;
|
|
4971
5063
|
}
|
|
@@ -5233,6 +5325,7 @@ export {
|
|
|
5233
5325
|
registerDefaultBindingParser,
|
|
5234
5326
|
registerDeadBindingWarnings,
|
|
5235
5327
|
registerCommaBindings,
|
|
5328
|
+
registerBindingOverrides,
|
|
5236
5329
|
registerBackspacePopsPendingSequence,
|
|
5237
5330
|
registerAliasesField,
|
|
5238
5331
|
defaultEventMatchResolver,
|
package/addons/opentui/index.js
CHANGED
|
@@ -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;
|
|
@@ -1881,21 +1883,32 @@ function applyBindingEventEffects(binding, event) {
|
|
|
1881
1883
|
}
|
|
1882
1884
|
|
|
1883
1885
|
// src/services/primitives/binding-inputs.ts
|
|
1884
|
-
function
|
|
1885
|
-
|
|
1886
|
-
|
|
1886
|
+
function isKeyLike(value) {
|
|
1887
|
+
return typeof value === "string" || !!value && typeof value === "object" && !Array.isArray(value);
|
|
1888
|
+
}
|
|
1889
|
+
function validateBindingInputs(bindings) {
|
|
1890
|
+
if (!Array.isArray(bindings)) {
|
|
1891
|
+
return { ok: false, reason: "Invalid keymap bindings: expected an array of binding objects" };
|
|
1887
1892
|
}
|
|
1888
|
-
const
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1893
|
+
for (const [index, binding] of bindings.entries()) {
|
|
1894
|
+
if (!binding || typeof binding !== "object" || Array.isArray(binding)) {
|
|
1895
|
+
return { ok: false, reason: `Invalid keymap binding at index ${index}: expected a binding object` };
|
|
1896
|
+
}
|
|
1897
|
+
if (!isKeyLike(binding.key)) {
|
|
1898
|
+
return {
|
|
1899
|
+
ok: false,
|
|
1900
|
+
reason: `Invalid keymap binding at index ${index}: expected "key" to be a string or keystroke object`
|
|
1901
|
+
};
|
|
1892
1902
|
}
|
|
1893
|
-
normalized.push({ key, cmd });
|
|
1894
1903
|
}
|
|
1895
|
-
return
|
|
1904
|
+
return { ok: true };
|
|
1896
1905
|
}
|
|
1897
1906
|
function snapshotBindingInputs(bindings) {
|
|
1898
|
-
|
|
1907
|
+
const validation = validateBindingInputs(bindings);
|
|
1908
|
+
if (!validation.ok) {
|
|
1909
|
+
throw new Error(validation.reason);
|
|
1910
|
+
}
|
|
1911
|
+
return bindings.map((binding) => ({
|
|
1899
1912
|
...binding,
|
|
1900
1913
|
key: typeof binding.key === "string" ? binding.key : { ...binding.key }
|
|
1901
1914
|
}));
|
|
@@ -3219,6 +3232,15 @@ class EnvironmentService {
|
|
|
3219
3232
|
prependBindingTransformer(transformer) {
|
|
3220
3233
|
return this.state.environment.bindingTransformers.prepend(transformer);
|
|
3221
3234
|
}
|
|
3235
|
+
prependLayerBindingsTransformer(transformer) {
|
|
3236
|
+
return this.state.environment.layerBindingsTransformers.prepend(transformer);
|
|
3237
|
+
}
|
|
3238
|
+
appendLayerBindingsTransformer(transformer) {
|
|
3239
|
+
return this.state.environment.layerBindingsTransformers.append(transformer);
|
|
3240
|
+
}
|
|
3241
|
+
clearLayerBindingsTransformers() {
|
|
3242
|
+
this.state.environment.layerBindingsTransformers.clear();
|
|
3243
|
+
}
|
|
3222
3244
|
appendBindingTransformer(transformer) {
|
|
3223
3245
|
return this.state.environment.bindingTransformers.append(transformer);
|
|
3224
3246
|
}
|
|
@@ -3417,7 +3439,7 @@ class LayerService {
|
|
|
3417
3439
|
let targetMode;
|
|
3418
3440
|
try {
|
|
3419
3441
|
targetMode = this.normalizeTargetMode(layer);
|
|
3420
|
-
bindingInputs = snapshotBindingInputs(layer.bindings ?? []);
|
|
3442
|
+
bindingInputs = this.applyLayerBindingsTransformers(snapshotBindingInputs(layer.bindings ?? []), layer);
|
|
3421
3443
|
commands = !layer.commands || layer.commands.length === 0 ? [] : this.options.commands.normalizeCommands(layer.commands);
|
|
3422
3444
|
commandLookup = createCommandLookup(commands);
|
|
3423
3445
|
({ requires, matchers, conditionKeys, hasUnkeyedMatchers, compileFields } = this.compileLayerRuntimeState(layer));
|
|
@@ -3570,6 +3592,24 @@ class LayerService {
|
|
|
3570
3592
|
}
|
|
3571
3593
|
return layer.target ? "focus-within" : undefined;
|
|
3572
3594
|
}
|
|
3595
|
+
applyLayerBindingsTransformers(bindings, layer) {
|
|
3596
|
+
const transformers = this.state.environment.layerBindingsTransformers.values();
|
|
3597
|
+
if (transformers.length === 0) {
|
|
3598
|
+
return bindings;
|
|
3599
|
+
}
|
|
3600
|
+
let current = bindings;
|
|
3601
|
+
for (const transformer of transformers) {
|
|
3602
|
+
const next = transformer(current, {
|
|
3603
|
+
layer,
|
|
3604
|
+
validateBindings: (bindings2) => validateBindingInputs(bindings2)
|
|
3605
|
+
});
|
|
3606
|
+
if (!next) {
|
|
3607
|
+
continue;
|
|
3608
|
+
}
|
|
3609
|
+
current = snapshotBindingInputs(next);
|
|
3610
|
+
}
|
|
3611
|
+
return current;
|
|
3612
|
+
}
|
|
3573
3613
|
runLayerAnalyzers(options) {
|
|
3574
3614
|
const analyzers = this.state.layers.layerAnalyzers.values();
|
|
3575
3615
|
if (analyzers.length === 0) {
|
|
@@ -4043,6 +4083,7 @@ function createKeymapState() {
|
|
|
4043
4083
|
environment: {
|
|
4044
4084
|
tokens: new Map,
|
|
4045
4085
|
layerFields: new Map,
|
|
4086
|
+
layerBindingsTransformers: new OrderedRegistry,
|
|
4046
4087
|
bindingExpanders: new OrderedRegistry,
|
|
4047
4088
|
bindingParsers: new OrderedRegistry,
|
|
4048
4089
|
bindingTransformers: new OrderedRegistry,
|
|
@@ -4254,12 +4295,6 @@ class Keymap {
|
|
|
4254
4295
|
getCommandEntries(query) {
|
|
4255
4296
|
return this.catalog.getCommandEntries(query);
|
|
4256
4297
|
}
|
|
4257
|
-
normalizeCommandName(name) {
|
|
4258
|
-
return normalizeCommandName(name);
|
|
4259
|
-
}
|
|
4260
|
-
normalizeBindings(bindings) {
|
|
4261
|
-
return normalizeBindingInputs(bindings);
|
|
4262
|
-
}
|
|
4263
4298
|
acquireResource(key, setup) {
|
|
4264
4299
|
if (this.cleanedUp || this.host.isDestroyed) {
|
|
4265
4300
|
throw new Error("Cannot use a keymap after its host was destroyed");
|
|
@@ -4305,6 +4340,15 @@ class Keymap {
|
|
|
4305
4340
|
registerLayerFields(fields) {
|
|
4306
4341
|
return this.environment.registerLayerFields(fields);
|
|
4307
4342
|
}
|
|
4343
|
+
prependLayerBindingsTransformer(transformer) {
|
|
4344
|
+
return this.environment.prependLayerBindingsTransformer(transformer);
|
|
4345
|
+
}
|
|
4346
|
+
appendLayerBindingsTransformer(transformer) {
|
|
4347
|
+
return this.environment.appendLayerBindingsTransformer(transformer);
|
|
4348
|
+
}
|
|
4349
|
+
clearLayerBindingsTransformers() {
|
|
4350
|
+
this.environment.clearLayerBindingsTransformers();
|
|
4351
|
+
}
|
|
4308
4352
|
prependBindingTransformer(transformer) {
|
|
4309
4353
|
return this.environment.prependBindingTransformer(transformer);
|
|
4310
4354
|
}
|
|
@@ -4695,6 +4739,48 @@ function registerDefaultKeys(keymap) {
|
|
|
4695
4739
|
offParser();
|
|
4696
4740
|
};
|
|
4697
4741
|
}
|
|
4742
|
+
// src/addons/universal/binding-overrides.ts
|
|
4743
|
+
function normalizeBindingOverrides(value) {
|
|
4744
|
+
if (!Array.isArray(value)) {
|
|
4745
|
+
throw new Error('Keymap layer field "bindingOverrides" must be an array of binding objects');
|
|
4746
|
+
}
|
|
4747
|
+
return value;
|
|
4748
|
+
}
|
|
4749
|
+
function getBindingOverrides(layer) {
|
|
4750
|
+
const overrides = layer.bindingOverrides;
|
|
4751
|
+
if (!overrides || !Array.isArray(overrides)) {
|
|
4752
|
+
return;
|
|
4753
|
+
}
|
|
4754
|
+
return normalizeBindingOverrides(overrides);
|
|
4755
|
+
}
|
|
4756
|
+
function registerBindingOverrides(keymap) {
|
|
4757
|
+
const offLayerField = keymap.registerLayerFields({
|
|
4758
|
+
bindingOverrides(value) {
|
|
4759
|
+
normalizeBindingOverrides(value);
|
|
4760
|
+
}
|
|
4761
|
+
});
|
|
4762
|
+
const offTransformer = keymap.appendLayerBindingsTransformer((bindings, ctx) => {
|
|
4763
|
+
const overrides = getBindingOverrides(ctx.layer);
|
|
4764
|
+
if (!overrides) {
|
|
4765
|
+
return;
|
|
4766
|
+
}
|
|
4767
|
+
const validation = ctx.validateBindings(overrides);
|
|
4768
|
+
if (!validation.ok) {
|
|
4769
|
+
throw new Error(validation.reason);
|
|
4770
|
+
}
|
|
4771
|
+
const overrideCommands = new Set(overrides.flatMap((binding) => typeof binding.cmd === "string" ? [binding.cmd.trim()] : []));
|
|
4772
|
+
return [
|
|
4773
|
+
...overrides,
|
|
4774
|
+
...bindings.filter((binding) => {
|
|
4775
|
+
return typeof binding.cmd !== "string" || !overrideCommands.has(binding.cmd.trim());
|
|
4776
|
+
})
|
|
4777
|
+
];
|
|
4778
|
+
});
|
|
4779
|
+
return () => {
|
|
4780
|
+
offTransformer();
|
|
4781
|
+
offLayerField();
|
|
4782
|
+
};
|
|
4783
|
+
}
|
|
4698
4784
|
// src/addons/universal/aliases.ts
|
|
4699
4785
|
function normalizeAliases(value) {
|
|
4700
4786
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -4964,8 +5050,14 @@ function registerEmacsBindings(keymap) {
|
|
|
4964
5050
|
}
|
|
4965
5051
|
// src/addons/universal/ex-commands.ts
|
|
4966
5052
|
var EMPTY_FIELDS = Object.freeze({});
|
|
4967
|
-
function normalizeExCommandName(
|
|
4968
|
-
const normalized =
|
|
5053
|
+
function normalizeExCommandName(_keymap, name) {
|
|
5054
|
+
const normalized = name.trim();
|
|
5055
|
+
if (!normalized) {
|
|
5056
|
+
throw new Error("Invalid keymap command name: name cannot be empty");
|
|
5057
|
+
}
|
|
5058
|
+
if (/\s/.test(normalized)) {
|
|
5059
|
+
throw new Error(`Invalid keymap command name "${name}": command names cannot contain whitespace`);
|
|
5060
|
+
}
|
|
4969
5061
|
if (normalized.startsWith(":")) {
|
|
4970
5062
|
return normalized;
|
|
4971
5063
|
}
|
|
@@ -5310,42 +5402,42 @@ var editBufferActions = [
|
|
|
5310
5402
|
"submit"
|
|
5311
5403
|
];
|
|
5312
5404
|
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"
|
|
5405
|
+
"move-left": "input.move.left",
|
|
5406
|
+
"move-right": "input.move.right",
|
|
5407
|
+
"move-up": "input.move.up",
|
|
5408
|
+
"move-down": "input.move.down",
|
|
5409
|
+
"select-left": "input.select.left",
|
|
5410
|
+
"select-right": "input.select.right",
|
|
5411
|
+
"select-up": "input.select.up",
|
|
5412
|
+
"select-down": "input.select.down",
|
|
5413
|
+
"line-home": "input.line.home",
|
|
5414
|
+
"line-end": "input.line.end",
|
|
5415
|
+
"select-line-home": "input.select.line.home",
|
|
5416
|
+
"select-line-end": "input.select.line.end",
|
|
5417
|
+
"visual-line-home": "input.visual.line.home",
|
|
5418
|
+
"visual-line-end": "input.visual.line.end",
|
|
5419
|
+
"select-visual-line-home": "input.select.visual.line.home",
|
|
5420
|
+
"select-visual-line-end": "input.select.visual.line.end",
|
|
5421
|
+
"buffer-home": "input.buffer.home",
|
|
5422
|
+
"buffer-end": "input.buffer.end",
|
|
5423
|
+
"select-buffer-home": "input.select.buffer.home",
|
|
5424
|
+
"select-buffer-end": "input.select.buffer.end",
|
|
5425
|
+
"delete-line": "input.delete.line",
|
|
5426
|
+
"delete-to-line-end": "input.delete.to.line.end",
|
|
5427
|
+
"delete-to-line-start": "input.delete.to.line.start",
|
|
5428
|
+
backspace: "input.backspace",
|
|
5429
|
+
delete: "input.delete",
|
|
5430
|
+
newline: "input.newline",
|
|
5431
|
+
undo: "input.undo",
|
|
5432
|
+
redo: "input.redo",
|
|
5433
|
+
"word-forward": "input.word.forward",
|
|
5434
|
+
"word-backward": "input.word.backward",
|
|
5435
|
+
"select-word-forward": "input.select.word.forward",
|
|
5436
|
+
"select-word-backward": "input.select.word.backward",
|
|
5437
|
+
"delete-word-forward": "input.delete.word.forward",
|
|
5438
|
+
"delete-word-backward": "input.delete.word.backward",
|
|
5439
|
+
"select-all": "input.select.all",
|
|
5440
|
+
submit: "input.submit"
|
|
5349
5441
|
};
|
|
5350
5442
|
var editBufferCommandDescriptions = {
|
|
5351
5443
|
"move-left": "Cursor left",
|
|
@@ -5592,7 +5684,7 @@ function registerManagedTextareaLayer(keymap, renderer, layer, options) {
|
|
|
5592
5684
|
const { bindings, target: _ignoredTarget, targetMode: _ignoredTargetMode, ...rest } = layer;
|
|
5593
5685
|
const offLayer = keymap.registerLayer({
|
|
5594
5686
|
...rest,
|
|
5595
|
-
bindings: createTextareaBindingsWithResolvedOptions(bindings
|
|
5687
|
+
bindings: createTextareaBindingsWithResolvedOptions(bindings, commandNames, descriptions)
|
|
5596
5688
|
});
|
|
5597
5689
|
return () => {
|
|
5598
5690
|
offLayer();
|
|
@@ -5623,6 +5715,7 @@ export {
|
|
|
5623
5715
|
registerDefaultBindingParser,
|
|
5624
5716
|
registerDeadBindingWarnings,
|
|
5625
5717
|
registerCommaBindings,
|
|
5718
|
+
registerBindingOverrides,
|
|
5626
5719
|
registerBaseLayoutFallback,
|
|
5627
5720
|
registerBackspacePopsPendingSequence,
|
|
5628
5721
|
registerAliasesField,
|