@opentui/keymap 0.0.0-20260423-618ea9b1 → 0.1.106
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 +19 -2
- package/addons/index.js +925 -280
- package/addons/opentui/index.js +926 -282
- package/html.js +960 -257
- package/index.js +906 -257
- package/opentui.js +960 -257
- package/package.json +4 -4
- package/react/index.js +14 -15
- package/solid/index.js +6 -7
- package/src/addons/opentui/edit-buffer-bindings.d.ts +4 -2
- package/src/addons/universal/enabled.d.ts +3 -8
- package/src/addons/universal/index.d.ts +3 -2
- package/src/addons/universal/metadata.d.ts +3 -0
- package/src/addons/universal/neovim-disambiguation.d.ts +10 -0
- package/src/index.d.ts +1 -1
- package/src/keymap.d.ts +5 -1
- package/src/react/index.d.ts +2 -4
- package/src/services/activation.d.ts +16 -6
- package/src/services/command-catalog.d.ts +13 -1
- package/src/services/command-executor.d.ts +1 -0
- package/src/services/compiler.d.ts +2 -2
- package/src/services/conditions.d.ts +2 -2
- package/src/services/dispatch.d.ts +29 -2
- package/src/services/layers.d.ts +9 -2
- package/src/services/notify.d.ts +1 -0
- package/src/services/primitives/active-layers.d.ts +6 -3
- package/src/services/state.d.ts +23 -10
- package/src/solid/index.d.ts +2 -4
- package/src/types.d.ts +52 -22
- package/src/keymap-benchmark.d.ts +0 -1
package/addons/opentui/index.js
CHANGED
|
@@ -18,41 +18,49 @@ function forEachActivationTarget(host, focused, visit) {
|
|
|
18
18
|
isFocusedTarget = false;
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
-
function
|
|
21
|
+
function getActivationPath(host, focused) {
|
|
22
|
+
const path = new Set;
|
|
23
|
+
forEachActivationTarget(host, focused, (current) => {
|
|
24
|
+
path.add(current);
|
|
25
|
+
});
|
|
26
|
+
return path;
|
|
27
|
+
}
|
|
28
|
+
function getActiveLayersForFocused(state, host, focused) {
|
|
29
|
+
if (state.activeLayersCacheVersion === state.activeLayersVersion && state.activeLayersCacheFocused === focused) {
|
|
30
|
+
return state.activeLayersCache;
|
|
31
|
+
}
|
|
22
32
|
const activeLayers = [];
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
if (isFocusedTarget) {
|
|
29
|
-
activeLayers.push(...bucket.focusLayers);
|
|
33
|
+
const activationPath = getActivationPath(host, focused);
|
|
34
|
+
for (const layer of state.sortedLayers) {
|
|
35
|
+
if (isLayerActiveForFocused(host, layer, focused, activationPath)) {
|
|
36
|
+
activeLayers.push(layer);
|
|
30
37
|
}
|
|
31
|
-
|
|
32
|
-
|
|
38
|
+
}
|
|
39
|
+
state.activeLayersCacheVersion = state.activeLayersVersion;
|
|
40
|
+
state.activeLayersCacheFocused = focused;
|
|
41
|
+
state.activeLayersCache = activeLayers;
|
|
33
42
|
return activeLayers;
|
|
34
43
|
}
|
|
35
|
-
function
|
|
36
|
-
|
|
44
|
+
function invalidateCachedActiveLayers(state) {
|
|
45
|
+
state.activeLayersCacheVersion = -1;
|
|
46
|
+
state.activeLayersCacheFocused = undefined;
|
|
47
|
+
state.activeLayersCache = [];
|
|
48
|
+
}
|
|
49
|
+
function isLayerActiveForFocused(host, layer, focused, activationPath = getActivationPath(host, focused)) {
|
|
50
|
+
const target = layer.target;
|
|
51
|
+
if (!target) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
37
54
|
if (host.isTargetDestroyed(target)) {
|
|
38
55
|
return false;
|
|
39
56
|
}
|
|
40
|
-
if (layer.
|
|
57
|
+
if (layer.targetMode === "focus") {
|
|
41
58
|
return target === focused;
|
|
42
59
|
}
|
|
43
|
-
|
|
44
|
-
forEachActivationTarget(host, focused, (current) => {
|
|
45
|
-
if (current === target) {
|
|
46
|
-
isActive = true;
|
|
47
|
-
return false;
|
|
48
|
-
}
|
|
49
|
-
return true;
|
|
50
|
-
});
|
|
51
|
-
return isActive;
|
|
60
|
+
return activationPath.has(target);
|
|
52
61
|
}
|
|
53
62
|
|
|
54
63
|
// src/services/keys.ts
|
|
55
|
-
var keyMatches = new Map;
|
|
56
64
|
function normalizeBindingTokenName(token) {
|
|
57
65
|
const normalized = token.trim().toLowerCase();
|
|
58
66
|
if (!normalized) {
|
|
@@ -117,14 +125,14 @@ function resolveKeyMatch(input) {
|
|
|
117
125
|
return createKeyMatch(input);
|
|
118
126
|
}
|
|
119
127
|
function createKeyMatch(input) {
|
|
120
|
-
return
|
|
128
|
+
return `key:${buildKeyMatchId(normalizeKeyStroke(input))}`;
|
|
121
129
|
}
|
|
122
130
|
function createTextKeyMatch(id) {
|
|
123
131
|
const normalized = id.trim();
|
|
124
132
|
if (!normalized) {
|
|
125
133
|
throw new Error("Invalid keymap match id: id cannot be empty");
|
|
126
134
|
}
|
|
127
|
-
return
|
|
135
|
+
return `text:${normalized}`;
|
|
128
136
|
}
|
|
129
137
|
function stringifyKeyStroke(input, options) {
|
|
130
138
|
if ("stroke" in input) {
|
|
@@ -161,15 +169,6 @@ function stringifyCanonicalStroke(stroke) {
|
|
|
161
169
|
function buildKeyMatchId(stroke) {
|
|
162
170
|
return `${stroke.name}:${stroke.ctrl ? 1 : 0}:${stroke.shift ? 1 : 0}:${stroke.meta ? 1 : 0}:${stroke.super ? 1 : 0}:${stroke.hyper ? 1 : 0}`;
|
|
163
171
|
}
|
|
164
|
-
function getOrCreateKeyMatch(id) {
|
|
165
|
-
const existing = keyMatches.get(id);
|
|
166
|
-
if (existing) {
|
|
167
|
-
return existing;
|
|
168
|
-
}
|
|
169
|
-
const match = Symbol(id);
|
|
170
|
-
keyMatches.set(id, match);
|
|
171
|
-
return match;
|
|
172
|
-
}
|
|
173
172
|
|
|
174
173
|
// src/services/activation.ts
|
|
175
174
|
function getLiveHost(host) {
|
|
@@ -205,13 +204,15 @@ class ActivationService {
|
|
|
205
204
|
notify;
|
|
206
205
|
conditions;
|
|
207
206
|
catalog;
|
|
208
|
-
|
|
207
|
+
options;
|
|
208
|
+
constructor(state, host, hooks, notify, conditions, catalog, options = {}) {
|
|
209
209
|
this.state = state;
|
|
210
210
|
this.host = host;
|
|
211
211
|
this.hooks = hooks;
|
|
212
212
|
this.notify = notify;
|
|
213
213
|
this.conditions = conditions;
|
|
214
214
|
this.catalog = catalog;
|
|
215
|
+
this.options = options;
|
|
215
216
|
}
|
|
216
217
|
getFocusedTarget() {
|
|
217
218
|
return getLiveHost(this.host).getFocusedTarget();
|
|
@@ -220,10 +221,12 @@ class ActivationService {
|
|
|
220
221
|
return getFocusedTargetIfAvailable(this.host);
|
|
221
222
|
}
|
|
222
223
|
setPendingSequence(next) {
|
|
223
|
-
|
|
224
|
+
const previous = this.state.projection.pendingSequence;
|
|
225
|
+
if (isSamePendingSequence(previous, next)) {
|
|
224
226
|
return;
|
|
225
227
|
}
|
|
226
228
|
this.state.projection.pendingSequence = next;
|
|
229
|
+
this.options.onPendingSequenceChanged?.(previous, next);
|
|
227
230
|
this.invalidateCaches();
|
|
228
231
|
this.notifyPendingSequenceChange();
|
|
229
232
|
this.notify.queueStateChange();
|
|
@@ -246,6 +249,15 @@ class ActivationService {
|
|
|
246
249
|
}
|
|
247
250
|
return this.state.projection.pendingSequence ?? undefined;
|
|
248
251
|
}
|
|
252
|
+
revalidatePendingSequenceIfNeeded() {
|
|
253
|
+
if (this.host.isDestroyed || !this.state.projection.pendingSequence) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
this.ensureValidPendingSequence();
|
|
257
|
+
}
|
|
258
|
+
hasPendingSequenceState() {
|
|
259
|
+
return !this.host.isDestroyed && this.state.projection.pendingSequence !== null;
|
|
260
|
+
}
|
|
249
261
|
getPendingSequence() {
|
|
250
262
|
const projections = this.state.projection;
|
|
251
263
|
const derivedStateVersion = this.state.notify.derivedStateVersion;
|
|
@@ -335,11 +347,24 @@ class ActivationService {
|
|
|
335
347
|
}
|
|
336
348
|
return activeKeys;
|
|
337
349
|
}
|
|
350
|
+
getActiveKeysForCaptures(captures, options) {
|
|
351
|
+
const includeBindings = options?.includeBindings === true;
|
|
352
|
+
const includeMetadata = options?.includeMetadata === true;
|
|
353
|
+
const focused = this.getFocusedTarget();
|
|
354
|
+
const activeView = this.catalog.getActiveCommandView(focused);
|
|
355
|
+
return this.collectActiveKeysFromPending(captures, includeBindings, includeMetadata, focused, activeView);
|
|
356
|
+
}
|
|
338
357
|
nodeHasReachableBindings(node, focused) {
|
|
339
358
|
return this.hasMatchingBindings(node.reachableBindings, focused, this.catalog.getActiveCommandView(focused));
|
|
340
359
|
}
|
|
341
360
|
getActiveLayers(focused) {
|
|
342
|
-
return getActiveLayersForFocused(this.state.layers
|
|
361
|
+
return getActiveLayersForFocused(this.state.layers, this.host, focused);
|
|
362
|
+
}
|
|
363
|
+
refreshActiveLayers(focused = this.getFocusedTargetIfAvailable()) {
|
|
364
|
+
getActiveLayersForFocused(this.state.layers, this.host, focused);
|
|
365
|
+
}
|
|
366
|
+
invalidateActiveLayers() {
|
|
367
|
+
invalidateCachedActiveLayers(this.state.layers);
|
|
343
368
|
}
|
|
344
369
|
isLayerActiveForFocused(layer, focused) {
|
|
345
370
|
return isLayerActiveForFocused(this.host, layer, focused);
|
|
@@ -415,7 +440,7 @@ class ActivationService {
|
|
|
415
440
|
}
|
|
416
441
|
return parts;
|
|
417
442
|
}
|
|
418
|
-
|
|
443
|
+
collectMatchingBindings(bindings, focused, activeView) {
|
|
419
444
|
const matches = [];
|
|
420
445
|
for (const binding of bindings) {
|
|
421
446
|
if (this.conditions.matchesConditions(binding) && this.catalog.isBindingVisible(binding, focused, activeView)) {
|
|
@@ -432,7 +457,7 @@ class ActivationService {
|
|
|
432
457
|
}
|
|
433
458
|
return false;
|
|
434
459
|
}
|
|
435
|
-
getNodePresentation(node, focused, activeView, reachableBindings = this.
|
|
460
|
+
getNodePresentation(node, focused, activeView, reachableBindings = this.collectMatchingBindings(node.reachableBindings, focused, activeView)) {
|
|
436
461
|
if (!node.stroke) {
|
|
437
462
|
return { display: "" };
|
|
438
463
|
}
|
|
@@ -561,16 +586,17 @@ class ActivationService {
|
|
|
561
586
|
if (!node.stroke) {
|
|
562
587
|
return;
|
|
563
588
|
}
|
|
564
|
-
const reachableBindings = this.
|
|
589
|
+
const reachableBindings = this.collectMatchingBindings(node.reachableBindings, focused, activeView);
|
|
565
590
|
if (reachableBindings.length === 0) {
|
|
566
591
|
return;
|
|
567
592
|
}
|
|
568
|
-
const prefixBindings = this.
|
|
593
|
+
const prefixBindings = this.selectActiveBindings(node.bindings, focused, activeView);
|
|
569
594
|
return {
|
|
570
595
|
...this.getNodePresentation(node, focused, activeView, reachableBindings),
|
|
571
596
|
continues: true,
|
|
572
|
-
firstBinding: prefixBindings[0],
|
|
573
|
-
|
|
597
|
+
firstBinding: prefixBindings?.bindings[0],
|
|
598
|
+
commandBinding: prefixBindings?.commandBinding,
|
|
599
|
+
bindings: includeBindings && prefixBindings && prefixBindings.bindings.length > 0 ? [...prefixBindings.bindings] : undefined,
|
|
574
600
|
stop: true
|
|
575
601
|
};
|
|
576
602
|
}
|
|
@@ -701,7 +727,7 @@ class ActivationService {
|
|
|
701
727
|
// src/schema.ts
|
|
702
728
|
var RESERVED_COMMAND_FIELDS = new Set(["name", "run"]);
|
|
703
729
|
var RESERVED_BINDING_FIELDS = new Set(["key", "cmd", "event", "preventDefault", "fallthrough"]);
|
|
704
|
-
var RESERVED_LAYER_FIELDS = new Set(["target", "
|
|
730
|
+
var RESERVED_LAYER_FIELDS = new Set(["target", "targetMode", "priority", "bindings", "commands"]);
|
|
705
731
|
|
|
706
732
|
// src/services/primitives/field-invariants.ts
|
|
707
733
|
function mergeRequirement(target, name, value, source) {
|
|
@@ -770,6 +796,16 @@ var SNAPSHOT_FROZEN_COMMAND_METADATA_OPTIONS = Object.freeze({
|
|
|
770
796
|
preserveNonPlainObjects: true
|
|
771
797
|
});
|
|
772
798
|
var EMPTY_COMMAND_FIELDS = Object.freeze({});
|
|
799
|
+
function createCommandChainCacheState() {
|
|
800
|
+
return {
|
|
801
|
+
resolvedWithoutRecordChains: new Map,
|
|
802
|
+
resolvedWithRecordChains: new Map,
|
|
803
|
+
fallbackWithoutRecord: new Map,
|
|
804
|
+
fallbackWithRecord: new Map,
|
|
805
|
+
fallbackWithoutRecordErrors: new Set,
|
|
806
|
+
fallbackWithRecordErrors: new Set
|
|
807
|
+
};
|
|
808
|
+
}
|
|
773
809
|
function normalizeBindingCommand(command) {
|
|
774
810
|
if (command === undefined || typeof command === "function") {
|
|
775
811
|
return command;
|
|
@@ -864,10 +900,41 @@ class CommandCatalogService {
|
|
|
864
900
|
}
|
|
865
901
|
getResolvedCommandChain(command, focused, includeRecord) {
|
|
866
902
|
const view = this.getActiveCommandView(focused);
|
|
867
|
-
const entries = this.getResolvedCommandChainFromView(view, command, focused, includeRecord);
|
|
903
|
+
const entries = this.getResolvedCommandChainFromView(view, command, focused, includeRecord, "active", view.chainsByName.get(command));
|
|
868
904
|
const hadError = (includeRecord ? view.fallbackWithRecordErrors : view.fallbackWithoutRecordErrors).has(command);
|
|
869
905
|
return { entries, hadError };
|
|
870
906
|
}
|
|
907
|
+
getRegisteredResolvedEntries(command, includeRecord) {
|
|
908
|
+
const view = this.getRegisteredCommandView();
|
|
909
|
+
const cache = includeRecord ? view.resolvedWithRecordChains : view.resolvedWithoutRecordChains;
|
|
910
|
+
const cached = cache.get(command);
|
|
911
|
+
if (cached) {
|
|
912
|
+
return cached.length > 0 ? cached : undefined;
|
|
913
|
+
}
|
|
914
|
+
const chain = view.chainsByName.get(command);
|
|
915
|
+
if (!chain || chain.length === 0) {
|
|
916
|
+
cache.set(command, []);
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
const resolved = [];
|
|
920
|
+
for (const entry of chain) {
|
|
921
|
+
resolved.push({
|
|
922
|
+
target: entry.layer.target,
|
|
923
|
+
resolved: resolveRegisteredCommand(entry.command, { includeRecord })
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
cache.set(command, resolved);
|
|
927
|
+
return resolved;
|
|
928
|
+
}
|
|
929
|
+
getRegisteredResolverFallback(command, includeRecord) {
|
|
930
|
+
const view = this.getRegisteredCommandView();
|
|
931
|
+
const fallback = this.getFallbackResolvedCommand(view, command, null, includeRecord, "registered");
|
|
932
|
+
const hadError = (includeRecord ? view.fallbackWithRecordErrors : view.fallbackWithoutRecordErrors).has(command);
|
|
933
|
+
return {
|
|
934
|
+
resolved: fallback?.resolved,
|
|
935
|
+
hadError
|
|
936
|
+
};
|
|
937
|
+
}
|
|
871
938
|
getCommandAttrs(command, focused) {
|
|
872
939
|
const top = this.getTopResolvedCommand(command, focused, false);
|
|
873
940
|
return top?.resolved.attrs;
|
|
@@ -876,6 +943,36 @@ class CommandCatalogService {
|
|
|
876
943
|
const top = this.getTopResolvedCommand(command, focused, true);
|
|
877
944
|
return top?.resolved.record;
|
|
878
945
|
}
|
|
946
|
+
getTopRegisteredCommandRecord(command) {
|
|
947
|
+
const top = this.getTopRegisteredCommand(command);
|
|
948
|
+
return top ? getRegisteredCommandRecord(top.command) : undefined;
|
|
949
|
+
}
|
|
950
|
+
getDispatchUnavailableCommandState(command, focused, includeRecord) {
|
|
951
|
+
const view = this.getRegisteredCommandView();
|
|
952
|
+
const chain = view.chainsByName.get(command);
|
|
953
|
+
if (!chain || chain.length === 0) {
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
let inactiveEntry;
|
|
957
|
+
let disabledEntry;
|
|
958
|
+
for (const entry of chain) {
|
|
959
|
+
if (!isLayerActiveForFocused(this.host, entry.layer, focused)) {
|
|
960
|
+
inactiveEntry ??= entry;
|
|
961
|
+
continue;
|
|
962
|
+
}
|
|
963
|
+
if (!this.conditions.layerMatchesRuntimeState(entry.layer) || !this.conditions.matchesConditions(entry.command)) {
|
|
964
|
+
disabledEntry ??= entry;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
const unavailableEntry = disabledEntry ?? inactiveEntry;
|
|
968
|
+
if (!unavailableEntry) {
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
return {
|
|
972
|
+
reason: disabledEntry ? "disabled" : "inactive",
|
|
973
|
+
command: includeRecord ? getRegisteredCommandRecord(unavailableEntry.command) : undefined
|
|
974
|
+
};
|
|
975
|
+
}
|
|
879
976
|
getActiveCommandView(focused) {
|
|
880
977
|
const currentFocused = getFocusedTargetIfAvailable(this.host);
|
|
881
978
|
const derivedStateVersion = this.state.notify.derivedStateVersion;
|
|
@@ -888,7 +985,7 @@ class CommandCatalogService {
|
|
|
888
985
|
const chainsByName = new Map;
|
|
889
986
|
let cacheable = true;
|
|
890
987
|
if (this.state.layers.layersWithCommands > 0) {
|
|
891
|
-
for (const layer of getActiveLayersForFocused(this.state.layers
|
|
988
|
+
for (const layer of getActiveLayersForFocused(this.state.layers, this.host, focused)) {
|
|
892
989
|
if (layer.commands.length === 0 || !this.conditions.layerMatchesRuntimeState(layer)) {
|
|
893
990
|
continue;
|
|
894
991
|
}
|
|
@@ -923,12 +1020,7 @@ class CommandCatalogService {
|
|
|
923
1020
|
reachable,
|
|
924
1021
|
reachableByName,
|
|
925
1022
|
chainsByName,
|
|
926
|
-
|
|
927
|
-
resolvedWithRecordChains: new Map,
|
|
928
|
-
fallbackWithoutRecord: new Map,
|
|
929
|
-
fallbackWithRecord: new Map,
|
|
930
|
-
fallbackWithoutRecordErrors: new Set,
|
|
931
|
-
fallbackWithRecordErrors: new Set
|
|
1023
|
+
...createCommandChainCacheState()
|
|
932
1024
|
};
|
|
933
1025
|
if (focused === currentFocused && view.cacheable) {
|
|
934
1026
|
this.state.commands.activeCommandViewVersion = derivedStateVersion;
|
|
@@ -936,6 +1028,37 @@ class CommandCatalogService {
|
|
|
936
1028
|
}
|
|
937
1029
|
return view;
|
|
938
1030
|
}
|
|
1031
|
+
getRegisteredCommandView() {
|
|
1032
|
+
const cacheVersion = this.state.commands.commandMetadataVersion;
|
|
1033
|
+
if (this.state.commands.registeredCommandViewVersion === cacheVersion && this.state.commands.registeredCommandView) {
|
|
1034
|
+
return this.state.commands.registeredCommandView;
|
|
1035
|
+
}
|
|
1036
|
+
const entries = [];
|
|
1037
|
+
const chainsByName = new Map;
|
|
1038
|
+
for (const layer of this.state.layers.sortedLayers) {
|
|
1039
|
+
if (layer.commands.length === 0) {
|
|
1040
|
+
continue;
|
|
1041
|
+
}
|
|
1042
|
+
for (const command of layer.commands) {
|
|
1043
|
+
const entry = { layer, command };
|
|
1044
|
+
entries.push(entry);
|
|
1045
|
+
const existing = chainsByName.get(command.name);
|
|
1046
|
+
if (existing) {
|
|
1047
|
+
existing.push(entry);
|
|
1048
|
+
} else {
|
|
1049
|
+
chainsByName.set(command.name, [entry]);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
const view = {
|
|
1054
|
+
entries,
|
|
1055
|
+
chainsByName,
|
|
1056
|
+
...createCommandChainCacheState()
|
|
1057
|
+
};
|
|
1058
|
+
this.state.commands.registeredCommandViewVersion = cacheVersion;
|
|
1059
|
+
this.state.commands.registeredCommandView = view;
|
|
1060
|
+
return view;
|
|
1061
|
+
}
|
|
939
1062
|
isBindingVisible(binding, focused, activeView) {
|
|
940
1063
|
if (binding.command === undefined || binding.run) {
|
|
941
1064
|
return true;
|
|
@@ -946,7 +1069,7 @@ class CommandCatalogService {
|
|
|
946
1069
|
if (activeView.reachableByName.has(binding.command)) {
|
|
947
1070
|
return true;
|
|
948
1071
|
}
|
|
949
|
-
return this.getFallbackResolvedCommand(activeView, binding.command, focused, false) !== undefined;
|
|
1072
|
+
return this.getFallbackResolvedCommand(activeView, binding.command, focused, false, "active") !== undefined;
|
|
950
1073
|
}
|
|
951
1074
|
getBindingCommandAttrs(binding, focused, activeView) {
|
|
952
1075
|
if (typeof binding.command !== "string") {
|
|
@@ -956,7 +1079,7 @@ class CommandCatalogService {
|
|
|
956
1079
|
if (active) {
|
|
957
1080
|
return active.command.attrs;
|
|
958
1081
|
}
|
|
959
|
-
const fallback = this.getFallbackResolvedCommand(activeView, binding.command, focused, false);
|
|
1082
|
+
const fallback = this.getFallbackResolvedCommand(activeView, binding.command, focused, false, "active");
|
|
960
1083
|
return fallback?.resolved.attrs;
|
|
961
1084
|
}
|
|
962
1085
|
getCommandResolutionStatus(command, layerCommands) {
|
|
@@ -997,16 +1120,20 @@ class CommandCatalogService {
|
|
|
997
1120
|
resolved: resolveRegisteredCommand(active.command, { includeRecord })
|
|
998
1121
|
};
|
|
999
1122
|
}
|
|
1000
|
-
return this.getFallbackResolvedCommand(activeView, command, focused, includeRecord);
|
|
1123
|
+
return this.getFallbackResolvedCommand(activeView, command, focused, includeRecord, "active");
|
|
1001
1124
|
}
|
|
1002
|
-
|
|
1003
|
-
const
|
|
1004
|
-
|
|
1125
|
+
getTopRegisteredCommand(command) {
|
|
1126
|
+
const view = this.getRegisteredCommandView();
|
|
1127
|
+
return view.chainsByName.get(command)?.[0];
|
|
1128
|
+
}
|
|
1129
|
+
getFallbackResolvedCommand(view, command, focused, includeRecord, mode) {
|
|
1130
|
+
const cache = includeRecord ? view.fallbackWithRecord : view.fallbackWithoutRecord;
|
|
1131
|
+
const errorCache = includeRecord ? view.fallbackWithRecordErrors : view.fallbackWithoutRecordErrors;
|
|
1005
1132
|
if (cache.has(command)) {
|
|
1006
1133
|
const cached = cache.get(command);
|
|
1007
1134
|
return cached ? { resolved: cached } : undefined;
|
|
1008
1135
|
}
|
|
1009
|
-
const lookup = this.resolveCommandWithResolvers(command, focused, { includeRecord });
|
|
1136
|
+
const lookup = this.resolveCommandWithResolvers(command, focused, { includeRecord, mode });
|
|
1010
1137
|
cache.set(command, lookup.resolved ?? null);
|
|
1011
1138
|
if (lookup.hadError) {
|
|
1012
1139
|
errorCache.add(command);
|
|
@@ -1016,23 +1143,23 @@ class CommandCatalogService {
|
|
|
1016
1143
|
}
|
|
1017
1144
|
return { resolved: lookup.resolved };
|
|
1018
1145
|
}
|
|
1019
|
-
getResolvedCommandChainFromView(
|
|
1020
|
-
const cache = includeRecord ?
|
|
1146
|
+
getResolvedCommandChainFromView(view, command, focused, includeRecord, mode, activeChain) {
|
|
1147
|
+
const cache = includeRecord ? view.resolvedWithRecordChains : view.resolvedWithoutRecordChains;
|
|
1021
1148
|
const cached = cache.get(command);
|
|
1022
1149
|
if (cached) {
|
|
1023
1150
|
return cached.length > 0 ? cached : undefined;
|
|
1024
1151
|
}
|
|
1025
1152
|
const resolved = [];
|
|
1026
|
-
const
|
|
1027
|
-
if (
|
|
1028
|
-
for (const entry of
|
|
1153
|
+
const chain = activeChain;
|
|
1154
|
+
if (chain) {
|
|
1155
|
+
for (const entry of chain) {
|
|
1029
1156
|
resolved.push({
|
|
1030
1157
|
target: entry.layer.target,
|
|
1031
1158
|
resolved: resolveRegisteredCommand(entry.command, { includeRecord })
|
|
1032
1159
|
});
|
|
1033
1160
|
}
|
|
1034
1161
|
}
|
|
1035
|
-
const fallback = this.getFallbackResolvedCommand(
|
|
1162
|
+
const fallback = this.getFallbackResolvedCommand(view, command, focused, includeRecord, mode);
|
|
1036
1163
|
if (fallback) {
|
|
1037
1164
|
resolved.push(fallback);
|
|
1038
1165
|
}
|
|
@@ -1101,7 +1228,7 @@ class CommandCatalogService {
|
|
|
1101
1228
|
if (!activeView) {
|
|
1102
1229
|
return;
|
|
1103
1230
|
}
|
|
1104
|
-
for (const layer of getActiveLayersForFocused(this.state.layers
|
|
1231
|
+
for (const layer of getActiveLayersForFocused(this.state.layers, this.host, context.focused)) {
|
|
1105
1232
|
if (layer.compiledBindings.length === 0 || !this.conditions.layerMatchesRuntimeState(layer)) {
|
|
1106
1233
|
continue;
|
|
1107
1234
|
}
|
|
@@ -1139,20 +1266,26 @@ class CommandCatalogService {
|
|
|
1139
1266
|
}
|
|
1140
1267
|
resolveCommandWithResolvers(command, focused, options) {
|
|
1141
1268
|
const includeRecord = options?.includeRecord === true;
|
|
1142
|
-
const context = this.createCommandResolverContext(includeRecord, focused);
|
|
1269
|
+
const context = this.createCommandResolverContext(includeRecord, focused, options?.mode ?? "active");
|
|
1143
1270
|
return resolveCommandWithResolvers(command, this.state.commands.commandResolvers.values(), context, (error) => {
|
|
1144
1271
|
this.notify.emitError("command-resolver-error", error, `[Keymap] Error in command resolver for "${command}":`);
|
|
1145
1272
|
});
|
|
1146
1273
|
}
|
|
1147
|
-
createCommandResolverContext(includeRecord, focused) {
|
|
1274
|
+
createCommandResolverContext(includeRecord, focused, mode) {
|
|
1148
1275
|
return {
|
|
1149
1276
|
getCommandAttrs: (name) => {
|
|
1277
|
+
if (mode === "registered") {
|
|
1278
|
+
return this.getTopRegisteredCommand(name)?.command.attrs;
|
|
1279
|
+
}
|
|
1150
1280
|
return this.getCommandAttrs(name, focused);
|
|
1151
1281
|
},
|
|
1152
1282
|
getCommandRecord: (name) => {
|
|
1153
1283
|
if (!includeRecord) {
|
|
1154
1284
|
return;
|
|
1155
1285
|
}
|
|
1286
|
+
if (mode === "registered") {
|
|
1287
|
+
return this.getTopRegisteredCommandRecord(name);
|
|
1288
|
+
}
|
|
1156
1289
|
return this.getTopCommandRecord(name, focused);
|
|
1157
1290
|
}
|
|
1158
1291
|
};
|
|
@@ -1523,6 +1656,72 @@ class CommandExecutorService {
|
|
|
1523
1656
|
this.options = options;
|
|
1524
1657
|
}
|
|
1525
1658
|
runCommand(cmd, options) {
|
|
1659
|
+
let normalized;
|
|
1660
|
+
try {
|
|
1661
|
+
normalized = normalizeBindingCommand(cmd);
|
|
1662
|
+
} catch {
|
|
1663
|
+
return { ok: false, reason: "invalid-args" };
|
|
1664
|
+
}
|
|
1665
|
+
if (typeof normalized !== "string") {
|
|
1666
|
+
return { ok: false, reason: "not-found" };
|
|
1667
|
+
}
|
|
1668
|
+
const includeRecord = options?.includeCommand === true;
|
|
1669
|
+
const focused = options?.focused ?? this.activation.getFocusedTargetIfAvailable();
|
|
1670
|
+
const event = options?.event ?? this.options.createCommandEvent();
|
|
1671
|
+
const data = this.runtime.getReadonlyData();
|
|
1672
|
+
const chain = this.catalog.getRegisteredResolvedEntries(normalized, includeRecord);
|
|
1673
|
+
let rejectedResult;
|
|
1674
|
+
if (chain?.length === 1) {
|
|
1675
|
+
const [entry] = chain;
|
|
1676
|
+
if (entry) {
|
|
1677
|
+
const execution = this.executeResolvedCommand(normalized, entry.resolved, {
|
|
1678
|
+
keymap: this.options.keymap,
|
|
1679
|
+
event,
|
|
1680
|
+
focused,
|
|
1681
|
+
target: options?.target ?? entry.target ?? null,
|
|
1682
|
+
data
|
|
1683
|
+
});
|
|
1684
|
+
if (execution.status === "handled" || execution.status === "error") {
|
|
1685
|
+
return execution.result;
|
|
1686
|
+
}
|
|
1687
|
+
rejectedResult = execution.result;
|
|
1688
|
+
}
|
|
1689
|
+
} else if (chain) {
|
|
1690
|
+
for (const entry of chain) {
|
|
1691
|
+
const context = {
|
|
1692
|
+
keymap: this.options.keymap,
|
|
1693
|
+
event,
|
|
1694
|
+
focused,
|
|
1695
|
+
target: options?.target ?? entry.target ?? null,
|
|
1696
|
+
data
|
|
1697
|
+
};
|
|
1698
|
+
const execution = this.executeResolvedCommand(normalized, entry.resolved, context);
|
|
1699
|
+
if (execution.status === "handled" || execution.status === "error") {
|
|
1700
|
+
return execution.result;
|
|
1701
|
+
}
|
|
1702
|
+
rejectedResult = execution.result;
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
const fallback = this.catalog.getRegisteredResolverFallback(normalized, includeRecord);
|
|
1706
|
+
if (fallback.resolved) {
|
|
1707
|
+
const execution = this.executeResolvedCommand(normalized, fallback.resolved, {
|
|
1708
|
+
keymap: this.options.keymap,
|
|
1709
|
+
event,
|
|
1710
|
+
focused,
|
|
1711
|
+
target: options?.target ?? null,
|
|
1712
|
+
data
|
|
1713
|
+
});
|
|
1714
|
+
if (execution.status === "handled" || execution.status === "error") {
|
|
1715
|
+
return execution.result;
|
|
1716
|
+
}
|
|
1717
|
+
rejectedResult = execution.result;
|
|
1718
|
+
}
|
|
1719
|
+
if (fallback.hadError) {
|
|
1720
|
+
return { ok: false, reason: "error" };
|
|
1721
|
+
}
|
|
1722
|
+
return rejectedResult ?? { ok: false, reason: "not-found" };
|
|
1723
|
+
}
|
|
1724
|
+
dispatchCommand(cmd, options) {
|
|
1526
1725
|
let normalized;
|
|
1527
1726
|
try {
|
|
1528
1727
|
normalized = normalizeBindingCommand(cmd);
|
|
@@ -1539,7 +1738,22 @@ class CommandExecutorService {
|
|
|
1539
1738
|
const chainLookup = this.catalog.getResolvedCommandChain(normalized, focused, includeRecord);
|
|
1540
1739
|
const chain = chainLookup.entries;
|
|
1541
1740
|
let rejectedResult;
|
|
1542
|
-
if (chain) {
|
|
1741
|
+
if (chain?.length === 1) {
|
|
1742
|
+
const [entry] = chain;
|
|
1743
|
+
if (entry) {
|
|
1744
|
+
const execution = this.executeResolvedCommand(normalized, entry.resolved, {
|
|
1745
|
+
keymap: this.options.keymap,
|
|
1746
|
+
event,
|
|
1747
|
+
focused,
|
|
1748
|
+
target: options?.target ?? entry.target ?? null,
|
|
1749
|
+
data
|
|
1750
|
+
});
|
|
1751
|
+
if (execution.status === "handled" || execution.status === "error") {
|
|
1752
|
+
return execution.result;
|
|
1753
|
+
}
|
|
1754
|
+
rejectedResult = execution.result;
|
|
1755
|
+
}
|
|
1756
|
+
} else if (chain) {
|
|
1543
1757
|
for (const entry of chain) {
|
|
1544
1758
|
const context = {
|
|
1545
1759
|
keymap: this.options.keymap,
|
|
@@ -1558,6 +1772,10 @@ class CommandExecutorService {
|
|
|
1558
1772
|
if (chainLookup.hadError) {
|
|
1559
1773
|
return { ok: false, reason: "error" };
|
|
1560
1774
|
}
|
|
1775
|
+
const unavailable = this.catalog.getDispatchUnavailableCommandState(normalized, focused, includeRecord);
|
|
1776
|
+
if (unavailable) {
|
|
1777
|
+
return unavailable.command ? { ok: false, reason: unavailable.reason, command: unavailable.command } : { ok: false, reason: unavailable.reason };
|
|
1778
|
+
}
|
|
1561
1779
|
return rejectedResult ?? { ok: false, reason: "not-found" };
|
|
1562
1780
|
}
|
|
1563
1781
|
runBinding(bindingLayer, binding, event, focused) {
|
|
@@ -1580,7 +1798,23 @@ class CommandExecutorService {
|
|
|
1580
1798
|
return false;
|
|
1581
1799
|
}
|
|
1582
1800
|
const chain = this.catalog.getResolvedCommandChain(binding.command, focused, false).entries;
|
|
1583
|
-
if (chain) {
|
|
1801
|
+
if (chain?.length === 1) {
|
|
1802
|
+
const [entry] = chain;
|
|
1803
|
+
if (entry) {
|
|
1804
|
+
const execution = this.executeResolvedCommand(binding.command, entry.resolved, {
|
|
1805
|
+
keymap: this.options.keymap,
|
|
1806
|
+
event,
|
|
1807
|
+
focused,
|
|
1808
|
+
target: entry.target ?? bindingLayer.target ?? null,
|
|
1809
|
+
data
|
|
1810
|
+
});
|
|
1811
|
+
if (execution.status === "rejected") {
|
|
1812
|
+
return false;
|
|
1813
|
+
}
|
|
1814
|
+
applyBindingEventEffects(binding, event);
|
|
1815
|
+
return true;
|
|
1816
|
+
}
|
|
1817
|
+
} else if (chain) {
|
|
1584
1818
|
for (const entry of chain) {
|
|
1585
1819
|
const context = {
|
|
1586
1820
|
keymap: this.options.keymap,
|
|
@@ -1714,13 +1948,14 @@ class CompilerService {
|
|
|
1714
1948
|
parseObjectKey: (value, options) => this.parseObjectKeyPart(value, options)
|
|
1715
1949
|
});
|
|
1716
1950
|
}
|
|
1717
|
-
compileBindings(bindings, tokens,
|
|
1951
|
+
compileBindings(bindings, tokens, sourceTarget, sourceLayerOrder, compileFields) {
|
|
1718
1952
|
const root = createSequenceNode(null, null, null);
|
|
1719
1953
|
const compiledBindings = [];
|
|
1720
1954
|
let hasTokenBindings = false;
|
|
1721
1955
|
const bindingExpanders = this.state.environment.bindingExpanders.values();
|
|
1722
1956
|
const bindingParsers = this.state.environment.bindingParsers.values();
|
|
1723
1957
|
const bindingFieldCompilers = this.state.environment.bindingFields;
|
|
1958
|
+
const allowExactPrefixAmbiguity = this.state.dispatch.disambiguationResolvers.has();
|
|
1724
1959
|
const warnUnknownField = this.options.warnUnknownField;
|
|
1725
1960
|
const warnUnknownToken = this.options.warnUnknownToken;
|
|
1726
1961
|
const conditions = this.conditions;
|
|
@@ -1817,7 +2052,6 @@ class CompilerService {
|
|
|
1817
2052
|
command,
|
|
1818
2053
|
event,
|
|
1819
2054
|
sourceBinding: snapshotParsedBindingInput(compiledInput),
|
|
1820
|
-
sourceScope,
|
|
1821
2055
|
sourceTarget,
|
|
1822
2056
|
sourceLayerOrder,
|
|
1823
2057
|
sourceBindingIndex: bindingIndex,
|
|
@@ -1842,7 +2076,7 @@ class CompilerService {
|
|
|
1842
2076
|
throw new Error("Keymap release bindings only support a single key stroke");
|
|
1843
2077
|
}
|
|
1844
2078
|
if (event === "press") {
|
|
1845
|
-
this.insertBinding(root, compiledBinding);
|
|
2079
|
+
this.insertBinding(root, compiledBinding, allowExactPrefixAmbiguity);
|
|
1846
2080
|
}
|
|
1847
2081
|
compiledBindings.push(compiledBinding);
|
|
1848
2082
|
} catch (error) {
|
|
@@ -1911,13 +2145,13 @@ class CompilerService {
|
|
|
1911
2145
|
}
|
|
1912
2146
|
return [parsedBinding, ...extraBindings];
|
|
1913
2147
|
}
|
|
1914
|
-
insertBinding(root, binding) {
|
|
2148
|
+
insertBinding(root, binding, allowExactPrefixAmbiguity) {
|
|
1915
2149
|
let node = root;
|
|
1916
2150
|
const touchedNodes = [];
|
|
1917
2151
|
const createdNodes = [];
|
|
1918
2152
|
try {
|
|
1919
2153
|
for (const part of binding.sequence) {
|
|
1920
|
-
if (node.bindings.some((candidate) => candidate.command !== undefined)) {
|
|
2154
|
+
if (!allowExactPrefixAmbiguity && node.bindings.some((candidate) => candidate.command !== undefined)) {
|
|
1921
2155
|
throw new Error("Keymap bindings cannot use the same sequence as both an exact match and a prefix in the same layer");
|
|
1922
2156
|
}
|
|
1923
2157
|
const bindingKey = part.match;
|
|
@@ -1931,7 +2165,7 @@ class CompilerService {
|
|
|
1931
2165
|
touchedNodes.push(child);
|
|
1932
2166
|
node = child;
|
|
1933
2167
|
}
|
|
1934
|
-
if (binding.command !== undefined && node.children.size > 0) {
|
|
2168
|
+
if (!allowExactPrefixAmbiguity && binding.command !== undefined && node.children.size > 0) {
|
|
1935
2169
|
throw new Error("Keymap bindings cannot use the same sequence as both an exact match and a prefix in the same layer");
|
|
1936
2170
|
}
|
|
1937
2171
|
node.bindings = [...node.bindings, binding];
|
|
@@ -2096,20 +2330,7 @@ class ConditionService {
|
|
|
2096
2330
|
hasNoConditions(target) {
|
|
2097
2331
|
return target.requires.length === 0 && target.matchers.length === 0;
|
|
2098
2332
|
}
|
|
2099
|
-
|
|
2100
|
-
for (const matcher of target.matchers) {
|
|
2101
|
-
if (!matcher.subscribe) {
|
|
2102
|
-
continue;
|
|
2103
|
-
}
|
|
2104
|
-
try {
|
|
2105
|
-
matcher.dispose = matcher.subscribe(() => {
|
|
2106
|
-
target.matchCacheDirty = true;
|
|
2107
|
-
this.notify.queueStateChange();
|
|
2108
|
-
});
|
|
2109
|
-
} catch (error) {
|
|
2110
|
-
this.notify.emitError("reactive-matcher-subscribe-error", error, getErrorMessage(error, `Failed to subscribe to reactive matcher from ${matcher.source}`));
|
|
2111
|
-
}
|
|
2112
|
-
}
|
|
2333
|
+
indexRuntimeMatchable(target) {
|
|
2113
2334
|
if (target.conditionKeys.length > 0) {
|
|
2114
2335
|
for (const key of target.conditionKeys) {
|
|
2115
2336
|
const dependents = this.state.conditions.runtimeKeyDependents.get(key);
|
|
@@ -2124,18 +2345,7 @@ class ConditionService {
|
|
|
2124
2345
|
target.matchCacheDirty = true;
|
|
2125
2346
|
}
|
|
2126
2347
|
}
|
|
2127
|
-
|
|
2128
|
-
for (const matcher of target.matchers) {
|
|
2129
|
-
if (!matcher.dispose) {
|
|
2130
|
-
continue;
|
|
2131
|
-
}
|
|
2132
|
-
try {
|
|
2133
|
-
matcher.dispose();
|
|
2134
|
-
} catch (error) {
|
|
2135
|
-
this.notify.emitError("reactive-matcher-dispose-error", error, getErrorMessage(error, `Failed to dispose reactive matcher from ${matcher.source}`));
|
|
2136
|
-
}
|
|
2137
|
-
matcher.dispose = undefined;
|
|
2138
|
-
}
|
|
2348
|
+
unindexRuntimeMatchable(target) {
|
|
2139
2349
|
if (target.conditionKeys.length === 0) {
|
|
2140
2350
|
return;
|
|
2141
2351
|
}
|
|
@@ -2225,7 +2435,31 @@ class ConditionService {
|
|
|
2225
2435
|
}
|
|
2226
2436
|
}
|
|
2227
2437
|
|
|
2438
|
+
// src/types.ts
|
|
2439
|
+
var KEY_DISAMBIGUATION_DECISION = Symbol("keymap-disambiguation-decision");
|
|
2440
|
+
var KEY_DEFERRED_DISAMBIGUATION_DECISION = Symbol("keymap-deferred-disambiguation-decision");
|
|
2441
|
+
|
|
2228
2442
|
// src/services/dispatch.ts
|
|
2443
|
+
function createSyncDecision(action, handler) {
|
|
2444
|
+
return {
|
|
2445
|
+
[KEY_DISAMBIGUATION_DECISION]: true,
|
|
2446
|
+
action,
|
|
2447
|
+
handler
|
|
2448
|
+
};
|
|
2449
|
+
}
|
|
2450
|
+
function createDeferredDecision(action) {
|
|
2451
|
+
return {
|
|
2452
|
+
[KEY_DEFERRED_DISAMBIGUATION_DECISION]: true,
|
|
2453
|
+
action
|
|
2454
|
+
};
|
|
2455
|
+
}
|
|
2456
|
+
function isSyncDecision(value) {
|
|
2457
|
+
return !!value && typeof value === "object" && value[KEY_DISAMBIGUATION_DECISION] === true;
|
|
2458
|
+
}
|
|
2459
|
+
function isDeferredDecision(value) {
|
|
2460
|
+
return !!value && typeof value === "object" && value[KEY_DEFERRED_DISAMBIGUATION_DECISION] === true;
|
|
2461
|
+
}
|
|
2462
|
+
|
|
2229
2463
|
class DispatchService {
|
|
2230
2464
|
state;
|
|
2231
2465
|
notify;
|
|
@@ -2234,8 +2468,12 @@ class DispatchService {
|
|
|
2234
2468
|
conditions;
|
|
2235
2469
|
executor;
|
|
2236
2470
|
compiler;
|
|
2471
|
+
catalog;
|
|
2472
|
+
layers;
|
|
2237
2473
|
eventMatchResolverContext;
|
|
2238
|
-
|
|
2474
|
+
pendingDisambiguation = null;
|
|
2475
|
+
nextPendingDisambiguationId = 0;
|
|
2476
|
+
constructor(state, notify, runtime, activation, conditions, executor, compiler, catalog, layers) {
|
|
2239
2477
|
this.state = state;
|
|
2240
2478
|
this.notify = notify;
|
|
2241
2479
|
this.runtime = runtime;
|
|
@@ -2243,6 +2481,8 @@ class DispatchService {
|
|
|
2243
2481
|
this.conditions = conditions;
|
|
2244
2482
|
this.executor = executor;
|
|
2245
2483
|
this.compiler = compiler;
|
|
2484
|
+
this.catalog = catalog;
|
|
2485
|
+
this.layers = layers;
|
|
2246
2486
|
this.eventMatchResolverContext = {
|
|
2247
2487
|
resolveKey: (key) => {
|
|
2248
2488
|
return this.compiler.parseTokenKey(key).match;
|
|
@@ -2271,6 +2511,27 @@ class DispatchService {
|
|
|
2271
2511
|
clearEventMatchResolvers() {
|
|
2272
2512
|
this.state.dispatch.eventMatchResolvers.clear();
|
|
2273
2513
|
}
|
|
2514
|
+
prependDisambiguationResolver(resolver) {
|
|
2515
|
+
return this.mutateDisambiguationResolvers(() => this.state.dispatch.disambiguationResolvers.prepend(resolver), resolver);
|
|
2516
|
+
}
|
|
2517
|
+
appendDisambiguationResolver(resolver) {
|
|
2518
|
+
return this.mutateDisambiguationResolvers(() => this.state.dispatch.disambiguationResolvers.append(resolver), resolver);
|
|
2519
|
+
}
|
|
2520
|
+
clearDisambiguationResolvers() {
|
|
2521
|
+
if (!this.state.dispatch.disambiguationResolvers.has()) {
|
|
2522
|
+
return;
|
|
2523
|
+
}
|
|
2524
|
+
this.notify.runWithStateChangeBatch(() => {
|
|
2525
|
+
this.state.dispatch.disambiguationResolvers.clear();
|
|
2526
|
+
this.layers.recompileBindings();
|
|
2527
|
+
});
|
|
2528
|
+
}
|
|
2529
|
+
handlePendingSequenceChange(_previous, _next) {
|
|
2530
|
+
if (!this.pendingDisambiguation) {
|
|
2531
|
+
return;
|
|
2532
|
+
}
|
|
2533
|
+
this.cancelPendingDisambiguation();
|
|
2534
|
+
}
|
|
2274
2535
|
handleRawSequence(sequence) {
|
|
2275
2536
|
const hooks = this.state.dispatch.rawHooks.entries();
|
|
2276
2537
|
if (hooks.length === 0) {
|
|
@@ -2296,6 +2557,9 @@ class DispatchService {
|
|
|
2296
2557
|
return false;
|
|
2297
2558
|
}
|
|
2298
2559
|
handleKeyEvent(event, release) {
|
|
2560
|
+
if (!release) {
|
|
2561
|
+
this.cancelPendingDisambiguation();
|
|
2562
|
+
}
|
|
2299
2563
|
const hooks = this.state.dispatch.keyHooks.entries();
|
|
2300
2564
|
const context = {
|
|
2301
2565
|
event,
|
|
@@ -2335,6 +2599,27 @@ class DispatchService {
|
|
|
2335
2599
|
}
|
|
2336
2600
|
this.dispatchLayers(event);
|
|
2337
2601
|
}
|
|
2602
|
+
mutateDisambiguationResolvers(register, resolver) {
|
|
2603
|
+
return this.notify.runWithStateChangeBatch(() => {
|
|
2604
|
+
const hadResolvers = this.state.dispatch.disambiguationResolvers.has();
|
|
2605
|
+
const off = register();
|
|
2606
|
+
if (!hadResolvers && this.state.dispatch.disambiguationResolvers.has()) {
|
|
2607
|
+
this.layers.recompileBindings();
|
|
2608
|
+
}
|
|
2609
|
+
return () => {
|
|
2610
|
+
this.notify.runWithStateChangeBatch(() => {
|
|
2611
|
+
const hadBeforeRemoval = this.state.dispatch.disambiguationResolvers.has();
|
|
2612
|
+
off();
|
|
2613
|
+
if (this.state.dispatch.disambiguationResolvers.values().includes(resolver)) {
|
|
2614
|
+
return;
|
|
2615
|
+
}
|
|
2616
|
+
if (hadBeforeRemoval && !this.state.dispatch.disambiguationResolvers.has()) {
|
|
2617
|
+
this.layers.recompileBindings();
|
|
2618
|
+
}
|
|
2619
|
+
});
|
|
2620
|
+
};
|
|
2621
|
+
});
|
|
2622
|
+
}
|
|
2338
2623
|
dispatchReleaseLayers(event) {
|
|
2339
2624
|
const focused = this.activation.getFocusedTarget();
|
|
2340
2625
|
const activeLayers = this.activation.getActiveLayers(focused);
|
|
@@ -2387,74 +2672,357 @@ class DispatchService {
|
|
|
2387
2672
|
this.activation.setPendingSequence(null);
|
|
2388
2673
|
return;
|
|
2389
2674
|
}
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
this.activation.setPendingSequence({
|
|
2402
|
-
captures: advancedCaptures.filter((candidate, candidateIndex) => {
|
|
2403
|
-
return candidateIndex >= index && candidate.node.children.size > 0;
|
|
2404
|
-
})
|
|
2405
|
-
});
|
|
2406
|
-
event.preventDefault();
|
|
2407
|
-
event.stopPropagation();
|
|
2408
|
-
return;
|
|
2409
|
-
}
|
|
2410
|
-
const result = this.runBindings(capture.layer, capture.node.bindings, event, focused);
|
|
2411
|
-
if (!result.handled) {
|
|
2675
|
+
this.dispatchPendingCapturesFromIndex(advancedCaptures, 0, false, event, focused);
|
|
2676
|
+
}
|
|
2677
|
+
dispatchPendingCapturesFromIndex(advancedCaptures, startIndex, handledExact, event, focused) {
|
|
2678
|
+
let hasHandledExact = handledExact;
|
|
2679
|
+
for (let index = startIndex;index < advancedCaptures.length; index += 1) {
|
|
2680
|
+
const capture = advancedCaptures[index];
|
|
2681
|
+
if (!capture) {
|
|
2682
|
+
continue;
|
|
2683
|
+
}
|
|
2684
|
+
if (capture.node.children.size > 0) {
|
|
2685
|
+
if (hasHandledExact) {
|
|
2412
2686
|
continue;
|
|
2413
2687
|
}
|
|
2414
|
-
|
|
2415
|
-
if (
|
|
2416
|
-
this.activation.setPendingSequence(null);
|
|
2688
|
+
const continuationCaptures = this.collectPendingCapturesFromAdvanced(advancedCaptures, index);
|
|
2689
|
+
if (this.tryResolvePendingAmbiguity(advancedCaptures, index, continuationCaptures, capture, event, focused, hasHandledExact)) {
|
|
2417
2690
|
return;
|
|
2418
2691
|
}
|
|
2419
|
-
|
|
2692
|
+
this.activation.setPendingSequence({ captures: continuationCaptures });
|
|
2693
|
+
event.preventDefault();
|
|
2694
|
+
event.stopPropagation();
|
|
2695
|
+
return;
|
|
2696
|
+
}
|
|
2697
|
+
const result = this.runBindings(capture.layer, capture.node.bindings, event, focused);
|
|
2698
|
+
if (!result.handled) {
|
|
2699
|
+
continue;
|
|
2420
2700
|
}
|
|
2701
|
+
hasHandledExact = true;
|
|
2702
|
+
if (result.stop) {
|
|
2703
|
+
this.activation.setPendingSequence(null);
|
|
2704
|
+
return;
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2421
2707
|
this.activation.setPendingSequence(null);
|
|
2422
2708
|
}
|
|
2423
2709
|
dispatchFromRoot(activeLayers, matchKeys, event, focused) {
|
|
2710
|
+
this.dispatchFromRootAtIndex(activeLayers, 0, matchKeys, event, focused);
|
|
2711
|
+
}
|
|
2712
|
+
dispatchFromRootAtIndex(activeLayers, startIndex, matchKeys, event, focused) {
|
|
2424
2713
|
const hasLayerConditions = this.state.layers.layersWithConditions > 0;
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
captures: this.collectPendingCapturesFromRoot(activeLayers, index, matchKeys, focused)
|
|
2444
|
-
});
|
|
2445
|
-
event.preventDefault();
|
|
2446
|
-
event.stopPropagation();
|
|
2714
|
+
for (let index = startIndex;index < activeLayers.length; index += 1) {
|
|
2715
|
+
const layer = activeLayers[index];
|
|
2716
|
+
if (!layer) {
|
|
2717
|
+
continue;
|
|
2718
|
+
}
|
|
2719
|
+
if (layer.root.children.size === 0) {
|
|
2720
|
+
continue;
|
|
2721
|
+
}
|
|
2722
|
+
if (hasLayerConditions && !this.conditions.hasNoConditions(layer) && !this.conditions.matchesConditions(layer)) {
|
|
2723
|
+
continue;
|
|
2724
|
+
}
|
|
2725
|
+
const nextNode = this.getReachableChild(layer.root, matchKeys, focused);
|
|
2726
|
+
if (!nextNode) {
|
|
2727
|
+
continue;
|
|
2728
|
+
}
|
|
2729
|
+
if (nextNode.children.size > 0) {
|
|
2730
|
+
const continuationCaptures = this.collectPendingCapturesFromRoot(activeLayers, index, matchKeys, focused);
|
|
2731
|
+
if (this.tryResolveRootAmbiguity(activeLayers, index, matchKeys, continuationCaptures, layer, nextNode, event, focused)) {
|
|
2447
2732
|
return;
|
|
2448
2733
|
}
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2734
|
+
this.activation.setPendingSequence({ captures: continuationCaptures });
|
|
2735
|
+
event.preventDefault();
|
|
2736
|
+
event.stopPropagation();
|
|
2737
|
+
return;
|
|
2738
|
+
}
|
|
2739
|
+
const result = this.runBindings(layer, nextNode.bindings, event, focused);
|
|
2740
|
+
if (!result.handled) {
|
|
2741
|
+
continue;
|
|
2742
|
+
}
|
|
2743
|
+
if (result.stop) {
|
|
2744
|
+
return;
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
tryResolveRootAmbiguity(activeLayers, layerIndex, matchKeys, continuationCaptures, layer, node, event, focused) {
|
|
2749
|
+
const applyExact = () => {
|
|
2750
|
+
this.activation.setPendingSequence(null);
|
|
2751
|
+
const result = this.runBindings(layer, node.bindings, event, focused);
|
|
2752
|
+
if (!result.stop) {
|
|
2753
|
+
this.dispatchFromRootAtIndex(activeLayers, layerIndex + 1, matchKeys, event, focused);
|
|
2754
|
+
}
|
|
2755
|
+
};
|
|
2756
|
+
return this.tryResolveAmbiguity({
|
|
2757
|
+
event,
|
|
2758
|
+
focused,
|
|
2759
|
+
continuationCaptures,
|
|
2760
|
+
exactBindingsSource: node.bindings,
|
|
2761
|
+
runExact: applyExact
|
|
2762
|
+
});
|
|
2763
|
+
}
|
|
2764
|
+
tryResolvePendingAmbiguity(advancedCaptures, captureIndex, continuationCaptures, capture, event, focused, handledExact) {
|
|
2765
|
+
const applyExact = () => {
|
|
2766
|
+
this.activation.setPendingSequence(null);
|
|
2767
|
+
const result = this.runBindings(capture.layer, capture.node.bindings, event, focused);
|
|
2768
|
+
if (result.stop) {
|
|
2769
|
+
return;
|
|
2770
|
+
}
|
|
2771
|
+
this.dispatchPendingCapturesFromIndex(advancedCaptures, captureIndex + 1, handledExact || result.handled, event, focused);
|
|
2772
|
+
};
|
|
2773
|
+
return this.tryResolveAmbiguity({
|
|
2774
|
+
event,
|
|
2775
|
+
focused,
|
|
2776
|
+
continuationCaptures,
|
|
2777
|
+
exactBindingsSource: capture.node.bindings,
|
|
2778
|
+
runExact: applyExact
|
|
2779
|
+
});
|
|
2780
|
+
}
|
|
2781
|
+
tryResolveAmbiguity(options) {
|
|
2782
|
+
const { event, focused, continuationCaptures, exactBindingsSource, runExact } = options;
|
|
2783
|
+
if (!this.state.dispatch.disambiguationResolvers.has() || continuationCaptures.length === 0) {
|
|
2784
|
+
return false;
|
|
2785
|
+
}
|
|
2786
|
+
const activeView = this.catalog.getActiveCommandView(focused);
|
|
2787
|
+
const exactBindings = this.activation.collectMatchingBindings(exactBindingsSource, focused, activeView);
|
|
2788
|
+
if (!exactBindings.some((binding) => binding.command !== undefined)) {
|
|
2789
|
+
return false;
|
|
2790
|
+
}
|
|
2791
|
+
const continueSequence = () => {
|
|
2792
|
+
this.activation.setPendingSequence({ captures: continuationCaptures });
|
|
2793
|
+
event.preventDefault();
|
|
2794
|
+
event.stopPropagation();
|
|
2795
|
+
};
|
|
2796
|
+
const clear = () => {
|
|
2797
|
+
this.activation.setPendingSequence(null);
|
|
2798
|
+
event.preventDefault();
|
|
2799
|
+
event.stopPropagation();
|
|
2800
|
+
};
|
|
2801
|
+
let sequence;
|
|
2802
|
+
const getSequence = () => {
|
|
2803
|
+
sequence ??= this.activation.collectSequencePartsFromPending({ captures: continuationCaptures });
|
|
2804
|
+
return sequence;
|
|
2805
|
+
};
|
|
2806
|
+
const decision = this.resolveDisambiguation({
|
|
2807
|
+
event,
|
|
2808
|
+
focused,
|
|
2809
|
+
getSequence,
|
|
2810
|
+
exactBindings,
|
|
2811
|
+
continuationCaptures,
|
|
2812
|
+
activeView
|
|
2813
|
+
});
|
|
2814
|
+
if (!decision) {
|
|
2815
|
+
this.warnUnresolvedAmbiguity(getSequence());
|
|
2816
|
+
continueSequence();
|
|
2817
|
+
return true;
|
|
2818
|
+
}
|
|
2819
|
+
return this.applySyncDecision(decision, continuationCaptures, runExact, continueSequence, clear, focused, getSequence);
|
|
2820
|
+
}
|
|
2821
|
+
applySyncDecision(decision, continuationCaptures, runExact, continueSequence, clear, focused, getSequence) {
|
|
2822
|
+
if (decision.action === "run-exact") {
|
|
2823
|
+
runExact();
|
|
2824
|
+
return true;
|
|
2825
|
+
}
|
|
2826
|
+
if (decision.action === "continue-sequence") {
|
|
2827
|
+
continueSequence();
|
|
2828
|
+
return true;
|
|
2829
|
+
}
|
|
2830
|
+
if (decision.action === "clear") {
|
|
2831
|
+
clear();
|
|
2832
|
+
return true;
|
|
2833
|
+
}
|
|
2834
|
+
continueSequence();
|
|
2835
|
+
this.scheduleDeferredDisambiguation(continuationCaptures, decision.handler, focused, getSequence(), (nextDecision) => {
|
|
2836
|
+
if (!nextDecision) {
|
|
2837
|
+
return;
|
|
2838
|
+
}
|
|
2839
|
+
if (nextDecision.action === "run-exact") {
|
|
2840
|
+
runExact();
|
|
2841
|
+
return;
|
|
2842
|
+
}
|
|
2843
|
+
if (nextDecision.action === "continue-sequence") {
|
|
2844
|
+
continueSequence();
|
|
2845
|
+
return;
|
|
2846
|
+
}
|
|
2847
|
+
clear();
|
|
2848
|
+
});
|
|
2849
|
+
return true;
|
|
2850
|
+
}
|
|
2851
|
+
resolveDisambiguation(options) {
|
|
2852
|
+
const activation = this.activation;
|
|
2853
|
+
const runtime = this.runtime;
|
|
2854
|
+
let sequence;
|
|
2855
|
+
let exact;
|
|
2856
|
+
let continuations;
|
|
2857
|
+
let strokePart;
|
|
2858
|
+
const ctx = {
|
|
2859
|
+
event: options.event,
|
|
2860
|
+
focused: options.focused,
|
|
2861
|
+
get sequence() {
|
|
2862
|
+
sequence ??= cloneKeySequence(options.getSequence());
|
|
2863
|
+
return sequence;
|
|
2864
|
+
},
|
|
2865
|
+
get stroke() {
|
|
2866
|
+
const stroke = options.getSequence().at(-1);
|
|
2867
|
+
if (!stroke) {
|
|
2868
|
+
throw new Error("Disambiguation context expected a non-empty sequence");
|
|
2452
2869
|
}
|
|
2453
|
-
|
|
2870
|
+
strokePart ??= {
|
|
2871
|
+
...stroke,
|
|
2872
|
+
stroke: cloneKeyStroke(stroke.stroke)
|
|
2873
|
+
};
|
|
2874
|
+
return strokePart;
|
|
2875
|
+
},
|
|
2876
|
+
get exact() {
|
|
2877
|
+
exact ??= activation.collectActiveBindings(options.exactBindings, options.focused, options.activeView).map((binding) => ({
|
|
2878
|
+
...binding,
|
|
2879
|
+
sequence: cloneKeySequence(binding.sequence)
|
|
2880
|
+
}));
|
|
2881
|
+
return exact;
|
|
2882
|
+
},
|
|
2883
|
+
get continuations() {
|
|
2884
|
+
continuations ??= activation.getActiveKeysForCaptures(options.continuationCaptures, {
|
|
2885
|
+
includeBindings: true,
|
|
2886
|
+
includeMetadata: true
|
|
2887
|
+
});
|
|
2888
|
+
return continuations;
|
|
2889
|
+
},
|
|
2890
|
+
getData: (name) => {
|
|
2891
|
+
return runtime.getData(name);
|
|
2892
|
+
},
|
|
2893
|
+
setData: (name, value) => {
|
|
2894
|
+
runtime.setData(name, value);
|
|
2895
|
+
},
|
|
2896
|
+
runExact: () => createSyncDecision("run-exact"),
|
|
2897
|
+
continueSequence: () => createSyncDecision("continue-sequence"),
|
|
2898
|
+
clear: () => createSyncDecision("clear"),
|
|
2899
|
+
defer: (run) => createSyncDecision("defer", run)
|
|
2900
|
+
};
|
|
2901
|
+
for (const resolver of this.state.dispatch.disambiguationResolvers.values()) {
|
|
2902
|
+
let result;
|
|
2903
|
+
try {
|
|
2904
|
+
result = resolver(ctx);
|
|
2905
|
+
} catch (error) {
|
|
2906
|
+
this.notify.emitError("disambiguation-resolver-error", error, "[Keymap] Error in disambiguation resolver:");
|
|
2907
|
+
continue;
|
|
2908
|
+
}
|
|
2909
|
+
if (result === undefined) {
|
|
2910
|
+
continue;
|
|
2911
|
+
}
|
|
2912
|
+
if (isPromiseLike(result)) {
|
|
2913
|
+
this.notify.emitError("invalid-disambiguation-resolver-return", result, "[Keymap] Disambiguation resolvers must return synchronously; use ctx.defer(...) for async handling");
|
|
2914
|
+
continue;
|
|
2915
|
+
}
|
|
2916
|
+
if (!isSyncDecision(result)) {
|
|
2917
|
+
this.notify.emitError("invalid-disambiguation-decision", result, "[Keymap] Invalid disambiguation decision returned by resolver:");
|
|
2918
|
+
continue;
|
|
2919
|
+
}
|
|
2920
|
+
return result;
|
|
2921
|
+
}
|
|
2922
|
+
return;
|
|
2923
|
+
}
|
|
2924
|
+
scheduleDeferredDisambiguation(captures, handler, focused, sequence, apply) {
|
|
2925
|
+
this.cancelPendingDisambiguation();
|
|
2926
|
+
const controller = new AbortController;
|
|
2927
|
+
const pending = {
|
|
2928
|
+
id: this.nextPendingDisambiguationId++,
|
|
2929
|
+
controller,
|
|
2930
|
+
captures,
|
|
2931
|
+
apply
|
|
2932
|
+
};
|
|
2933
|
+
this.pendingDisambiguation = pending;
|
|
2934
|
+
queueMicrotask(() => {
|
|
2935
|
+
this.executeDeferredDisambiguation(pending, handler, focused, sequence);
|
|
2936
|
+
});
|
|
2937
|
+
}
|
|
2938
|
+
executeDeferredDisambiguation(pending, handler, focused, sequence) {
|
|
2939
|
+
if (!this.isPendingDisambiguationCurrent(pending)) {
|
|
2940
|
+
return;
|
|
2941
|
+
}
|
|
2942
|
+
const ctx = {
|
|
2943
|
+
signal: pending.controller.signal,
|
|
2944
|
+
sequence: cloneKeySequence(sequence),
|
|
2945
|
+
focused,
|
|
2946
|
+
sleep: (ms) => {
|
|
2947
|
+
return this.sleepWithSignal(ms, pending.controller.signal);
|
|
2948
|
+
},
|
|
2949
|
+
runExact: () => createDeferredDecision("run-exact"),
|
|
2950
|
+
continueSequence: () => createDeferredDecision("continue-sequence"),
|
|
2951
|
+
clear: () => createDeferredDecision("clear")
|
|
2952
|
+
};
|
|
2953
|
+
let result;
|
|
2954
|
+
try {
|
|
2955
|
+
result = handler(ctx);
|
|
2956
|
+
} catch (error) {
|
|
2957
|
+
if (this.isPendingDisambiguationCurrent(pending)) {
|
|
2958
|
+
this.notify.emitError("deferred-disambiguation-error", error, "[Keymap] Error in deferred disambiguation handler:");
|
|
2959
|
+
this.finishPendingDisambiguation(pending);
|
|
2960
|
+
}
|
|
2961
|
+
return;
|
|
2962
|
+
}
|
|
2963
|
+
if (isPromiseLike(result)) {
|
|
2964
|
+
result.then((resolved) => {
|
|
2965
|
+
this.applyDeferredDisambiguationResult(pending, resolved);
|
|
2966
|
+
}).catch((error) => {
|
|
2967
|
+
if (!this.isPendingDisambiguationCurrent(pending)) {
|
|
2454
2968
|
return;
|
|
2455
2969
|
}
|
|
2456
|
-
|
|
2457
|
-
|
|
2970
|
+
this.notify.emitError("deferred-disambiguation-error", error, "[Keymap] Error in deferred disambiguation handler:");
|
|
2971
|
+
this.finishPendingDisambiguation(pending);
|
|
2972
|
+
});
|
|
2973
|
+
return;
|
|
2974
|
+
}
|
|
2975
|
+
this.applyDeferredDisambiguationResult(pending, result);
|
|
2976
|
+
}
|
|
2977
|
+
applyDeferredDisambiguationResult(pending, result) {
|
|
2978
|
+
if (!this.isPendingDisambiguationCurrent(pending)) {
|
|
2979
|
+
return;
|
|
2980
|
+
}
|
|
2981
|
+
if (result !== undefined && !isDeferredDecision(result)) {
|
|
2982
|
+
this.notify.emitError("invalid-deferred-disambiguation-decision", result, "[Keymap] Invalid deferred disambiguation decision returned by handler:");
|
|
2983
|
+
this.finishPendingDisambiguation(pending);
|
|
2984
|
+
return;
|
|
2985
|
+
}
|
|
2986
|
+
this.finishPendingDisambiguation(pending);
|
|
2987
|
+
pending.apply(result);
|
|
2988
|
+
}
|
|
2989
|
+
finishPendingDisambiguation(pending) {
|
|
2990
|
+
if (!this.isPendingDisambiguationCurrent(pending)) {
|
|
2991
|
+
return;
|
|
2992
|
+
}
|
|
2993
|
+
this.pendingDisambiguation = null;
|
|
2994
|
+
}
|
|
2995
|
+
cancelPendingDisambiguation() {
|
|
2996
|
+
const pending = this.pendingDisambiguation;
|
|
2997
|
+
if (!pending) {
|
|
2998
|
+
return;
|
|
2999
|
+
}
|
|
3000
|
+
this.pendingDisambiguation = null;
|
|
3001
|
+
pending.controller.abort();
|
|
3002
|
+
}
|
|
3003
|
+
isPendingDisambiguationCurrent(pending) {
|
|
3004
|
+
return this.pendingDisambiguation === pending;
|
|
3005
|
+
}
|
|
3006
|
+
sleepWithSignal(ms, signal) {
|
|
3007
|
+
if (signal.aborted) {
|
|
3008
|
+
return Promise.resolve(false);
|
|
3009
|
+
}
|
|
3010
|
+
return new Promise((resolve) => {
|
|
3011
|
+
const timeout = setTimeout(() => {
|
|
3012
|
+
signal.removeEventListener("abort", onAbort);
|
|
3013
|
+
resolve(true);
|
|
3014
|
+
}, Math.max(0, ms));
|
|
3015
|
+
const onAbort = () => {
|
|
3016
|
+
clearTimeout(timeout);
|
|
3017
|
+
signal.removeEventListener("abort", onAbort);
|
|
3018
|
+
resolve(false);
|
|
3019
|
+
};
|
|
3020
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
3021
|
+
});
|
|
3022
|
+
}
|
|
3023
|
+
warnUnresolvedAmbiguity(sequence) {
|
|
3024
|
+
const display = stringifyKeySequence(sequence, { preferDisplay: true });
|
|
3025
|
+
this.notify.warnOnce(`unresolved-disambiguation:${display}`, "unresolved-disambiguation", { sequence: display }, `[Keymap] Ambiguous exact/prefix sequence "${display}" fell back to prefix handling because no disambiguation resolver resolved it`);
|
|
2458
3026
|
}
|
|
2459
3027
|
collectPendingCapturesFromRoot(activeLayers, startIndex, matchKeys, focused) {
|
|
2460
3028
|
const captures = [];
|
|
@@ -2478,6 +3046,11 @@ class DispatchService {
|
|
|
2478
3046
|
}
|
|
2479
3047
|
return captures;
|
|
2480
3048
|
}
|
|
3049
|
+
collectPendingCapturesFromAdvanced(advancedCaptures, startIndex) {
|
|
3050
|
+
return advancedCaptures.filter((candidate, candidateIndex) => {
|
|
3051
|
+
return candidateIndex >= startIndex && candidate.node.children.size > 0;
|
|
3052
|
+
});
|
|
3053
|
+
}
|
|
2481
3054
|
resolveEventMatchKeys(event) {
|
|
2482
3055
|
const resolvers = this.state.dispatch.eventMatchResolvers.values();
|
|
2483
3056
|
if (resolvers.length === 0) {
|
|
@@ -2500,7 +3073,7 @@ class DispatchService {
|
|
|
2500
3073
|
continue;
|
|
2501
3074
|
}
|
|
2502
3075
|
for (const candidate of resolved) {
|
|
2503
|
-
if (typeof candidate !== "
|
|
3076
|
+
if (typeof candidate !== "string") {
|
|
2504
3077
|
this.notify.emitError("invalid-event-match-resolver-candidate", candidate, "[Keymap] Invalid event match resolver candidate:");
|
|
2505
3078
|
continue;
|
|
2506
3079
|
}
|
|
@@ -2578,7 +3151,7 @@ function resolveSingleEventMatchKeys(resolver, event, ctx, notify) {
|
|
|
2578
3151
|
}
|
|
2579
3152
|
if (resolved.length === 1) {
|
|
2580
3153
|
const [candidate] = resolved;
|
|
2581
|
-
if (typeof candidate !== "
|
|
3154
|
+
if (typeof candidate !== "string") {
|
|
2582
3155
|
notify.emitError("invalid-event-match-resolver-candidate", candidate, "[Keymap] Invalid event match resolver candidate:");
|
|
2583
3156
|
return [];
|
|
2584
3157
|
}
|
|
@@ -2587,7 +3160,7 @@ function resolveSingleEventMatchKeys(resolver, event, ctx, notify) {
|
|
|
2587
3160
|
const keys = [];
|
|
2588
3161
|
const seen = new Set;
|
|
2589
3162
|
for (const candidate of resolved) {
|
|
2590
|
-
if (typeof candidate !== "
|
|
3163
|
+
if (typeof candidate !== "string") {
|
|
2591
3164
|
notify.emitError("invalid-event-match-resolver-candidate", candidate, "[Keymap] Invalid event match resolver candidate:");
|
|
2592
3165
|
continue;
|
|
2593
3166
|
}
|
|
@@ -2749,14 +3322,13 @@ class EnvironmentService {
|
|
|
2749
3322
|
|
|
2750
3323
|
// src/services/layers.ts
|
|
2751
3324
|
var NOOP2 = () => {};
|
|
2752
|
-
function
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
const priorityDiff = b.priority - a.priority;
|
|
3325
|
+
function sortLayers(layers) {
|
|
3326
|
+
return [...layers].sort((left, right) => {
|
|
3327
|
+
const priorityDiff = right.priority - left.priority;
|
|
2756
3328
|
if (priorityDiff !== 0) {
|
|
2757
3329
|
return priorityDiff;
|
|
2758
3330
|
}
|
|
2759
|
-
return
|
|
3331
|
+
return right.order - left.order;
|
|
2760
3332
|
});
|
|
2761
3333
|
}
|
|
2762
3334
|
function createCommandLookup(commands) {
|
|
@@ -2805,7 +3377,6 @@ function buildLayerBindingAnalyses(root, compiledBindings) {
|
|
|
2805
3377
|
preventDefault: binding.preventDefault,
|
|
2806
3378
|
fallthrough: binding.fallthrough,
|
|
2807
3379
|
sourceBinding: snapshotParsedBindingInput(binding.sourceBinding),
|
|
2808
|
-
sourceScope: binding.sourceScope,
|
|
2809
3380
|
sourceTarget: binding.sourceTarget,
|
|
2810
3381
|
sourceLayerOrder: binding.sourceLayerOrder,
|
|
2811
3382
|
sourceBindingIndex: binding.sourceBindingIndex,
|
|
@@ -2835,7 +3406,6 @@ class LayerService {
|
|
|
2835
3406
|
this.notify.emitError("destroyed-layer-target", { target }, "Cannot register a keymap layer for a destroyed keymap target");
|
|
2836
3407
|
return NOOP2;
|
|
2837
3408
|
}
|
|
2838
|
-
let scope;
|
|
2839
3409
|
let bindingInputs;
|
|
2840
3410
|
let requires;
|
|
2841
3411
|
let matchers;
|
|
@@ -2844,10 +3414,9 @@ class LayerService {
|
|
|
2844
3414
|
let compileFields;
|
|
2845
3415
|
let commands;
|
|
2846
3416
|
let commandLookup;
|
|
2847
|
-
let
|
|
3417
|
+
let targetMode;
|
|
2848
3418
|
try {
|
|
2849
|
-
|
|
2850
|
-
indexTarget = layer.target ?? this.options.host.rootTarget;
|
|
3419
|
+
targetMode = this.normalizeTargetMode(layer);
|
|
2851
3420
|
bindingInputs = snapshotBindingInputs(layer.bindings ?? []);
|
|
2852
3421
|
commands = !layer.commands || layer.commands.length === 0 ? [] : this.options.commands.normalizeCommands(layer.commands);
|
|
2853
3422
|
commandLookup = createCommandLookup(commands);
|
|
@@ -2857,12 +3426,11 @@ class LayerService {
|
|
|
2857
3426
|
return NOOP2;
|
|
2858
3427
|
}
|
|
2859
3428
|
const order = this.state.core.order++;
|
|
2860
|
-
const compiledBindings = this.options.compiler.compileBindings(bindingInputs, this.state.environment.tokens,
|
|
3429
|
+
const compiledBindings = this.options.compiler.compileBindings(bindingInputs, this.state.environment.tokens, target, order, compileFields);
|
|
2861
3430
|
if (compiledBindings.bindings.length === 0 && !compiledBindings.hasTokenBindings && commands.length === 0) {
|
|
2862
3431
|
return NOOP2;
|
|
2863
3432
|
}
|
|
2864
3433
|
this.runLayerAnalyzers({
|
|
2865
|
-
scope,
|
|
2866
3434
|
target,
|
|
2867
3435
|
order,
|
|
2868
3436
|
commandLookup,
|
|
@@ -2874,8 +3442,7 @@ class LayerService {
|
|
|
2874
3442
|
const registeredLayer = {
|
|
2875
3443
|
order,
|
|
2876
3444
|
target,
|
|
2877
|
-
|
|
2878
|
-
scope,
|
|
3445
|
+
targetMode,
|
|
2879
3446
|
priority: layer.priority ?? 0,
|
|
2880
3447
|
requires,
|
|
2881
3448
|
matchers,
|
|
@@ -2901,14 +3468,16 @@ class LayerService {
|
|
|
2901
3468
|
if (registeredLayer.requires.length > 0 || registeredLayer.matchers.length > 0) {
|
|
2902
3469
|
this.state.layers.layersWithConditions += 1;
|
|
2903
3470
|
}
|
|
2904
|
-
this.
|
|
3471
|
+
this.connectRuntimeMatchable(registeredLayer);
|
|
2905
3472
|
for (const command of registeredLayer.commands) {
|
|
2906
|
-
this.
|
|
3473
|
+
this.connectRuntimeMatchable(command);
|
|
2907
3474
|
}
|
|
2908
3475
|
for (const binding of registeredLayer.compiledBindings) {
|
|
2909
|
-
this.
|
|
3476
|
+
this.connectRuntimeMatchable(binding);
|
|
2910
3477
|
}
|
|
2911
3478
|
this.indexLayer(registeredLayer);
|
|
3479
|
+
this.activation.invalidateActiveLayers();
|
|
3480
|
+
this.activation.refreshActiveLayers();
|
|
2912
3481
|
if (target) {
|
|
2913
3482
|
const onTargetDestroy = () => {
|
|
2914
3483
|
this.unregisterLayer(registeredLayer);
|
|
@@ -2931,37 +3500,41 @@ class LayerService {
|
|
|
2931
3500
|
if (!layer.hasTokenBindings) {
|
|
2932
3501
|
continue;
|
|
2933
3502
|
}
|
|
2934
|
-
nextCompilations.set(layer, this.
|
|
3503
|
+
nextCompilations.set(layer, this.compileLayerBindings(layer, nextTokens));
|
|
2935
3504
|
}
|
|
2936
3505
|
this.state.environment.tokens = nextTokens;
|
|
2937
3506
|
let shouldClearPending = false;
|
|
2938
3507
|
for (const [layer, compilation] of nextCompilations) {
|
|
2939
|
-
this.
|
|
2940
|
-
|
|
2941
|
-
target: layer.target,
|
|
2942
|
-
order: layer.order,
|
|
2943
|
-
commandLookup: layer.commandLookup,
|
|
2944
|
-
bindingInputs: layer.bindingInputs,
|
|
2945
|
-
compiledBindings: compilation.bindings,
|
|
2946
|
-
root: compilation.root,
|
|
2947
|
-
hasTokenBindings: compilation.hasTokenBindings
|
|
2948
|
-
});
|
|
2949
|
-
for (const binding of layer.compiledBindings) {
|
|
2950
|
-
this.conditions.unregisterRuntimeMatchable(binding);
|
|
3508
|
+
if (this.applyCompiledBindings(layer, compilation)) {
|
|
3509
|
+
shouldClearPending = true;
|
|
2951
3510
|
}
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
3511
|
+
}
|
|
3512
|
+
if (shouldClearPending) {
|
|
3513
|
+
this.activation.setPendingSequence(null);
|
|
3514
|
+
}
|
|
3515
|
+
if (nextCompilations.size > 0) {
|
|
3516
|
+
this.notify.queueStateChange();
|
|
3517
|
+
}
|
|
3518
|
+
});
|
|
3519
|
+
}
|
|
3520
|
+
recompileBindings() {
|
|
3521
|
+
this.notify.runWithStateChangeBatch(() => {
|
|
3522
|
+
let recompiledLayers = 0;
|
|
3523
|
+
let shouldClearPending = false;
|
|
3524
|
+
for (const layer of this.state.layers.layers) {
|
|
3525
|
+
if (layer.bindingInputs.length === 0) {
|
|
3526
|
+
continue;
|
|
2956
3527
|
}
|
|
2957
|
-
|
|
3528
|
+
const compilation = this.compileLayerBindings(layer, this.state.environment.tokens);
|
|
3529
|
+
if (this.applyCompiledBindings(layer, compilation)) {
|
|
2958
3530
|
shouldClearPending = true;
|
|
2959
3531
|
}
|
|
3532
|
+
recompiledLayers += 1;
|
|
2960
3533
|
}
|
|
2961
3534
|
if (shouldClearPending) {
|
|
2962
3535
|
this.activation.setPendingSequence(null);
|
|
2963
3536
|
}
|
|
2964
|
-
if (
|
|
3537
|
+
if (recompiledLayers > 0) {
|
|
2965
3538
|
this.notify.queueStateChange();
|
|
2966
3539
|
}
|
|
2967
3540
|
});
|
|
@@ -2975,17 +3548,27 @@ class LayerService {
|
|
|
2975
3548
|
clearLayerAnalyzers() {
|
|
2976
3549
|
this.state.layers.layerAnalyzers.clear();
|
|
2977
3550
|
}
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
3551
|
+
cleanup() {
|
|
3552
|
+
for (const layer of this.state.layers.layers) {
|
|
3553
|
+
this.disconnectRuntimeMatchable(layer);
|
|
3554
|
+
for (const command of layer.commands) {
|
|
3555
|
+
this.disconnectRuntimeMatchable(command);
|
|
3556
|
+
}
|
|
3557
|
+
for (const binding of layer.compiledBindings) {
|
|
3558
|
+
this.disconnectRuntimeMatchable(binding);
|
|
2982
3559
|
}
|
|
2983
|
-
|
|
3560
|
+
layer.offTargetDestroy?.();
|
|
3561
|
+
layer.offTargetDestroy = undefined;
|
|
2984
3562
|
}
|
|
2985
|
-
|
|
2986
|
-
|
|
3563
|
+
}
|
|
3564
|
+
normalizeTargetMode(layer) {
|
|
3565
|
+
if (layer.targetMode) {
|
|
3566
|
+
if (!layer.target) {
|
|
3567
|
+
throw new Error(`Keymap targetMode "${layer.targetMode}" requires a target`);
|
|
3568
|
+
}
|
|
3569
|
+
return layer.targetMode;
|
|
2987
3570
|
}
|
|
2988
|
-
return "
|
|
3571
|
+
return layer.target ? "focus-within" : undefined;
|
|
2989
3572
|
}
|
|
2990
3573
|
runLayerAnalyzers(options) {
|
|
2991
3574
|
const analyzers = this.state.layers.layerAnalyzers.values();
|
|
@@ -2994,7 +3577,6 @@ class LayerService {
|
|
|
2994
3577
|
}
|
|
2995
3578
|
const bindings = buildLayerBindingAnalyses(options.root, options.compiledBindings);
|
|
2996
3579
|
const ctx = {
|
|
2997
|
-
scope: options.scope,
|
|
2998
3580
|
target: options.target,
|
|
2999
3581
|
order: options.order,
|
|
3000
3582
|
bindingInputs: options.bindingInputs,
|
|
@@ -3062,41 +3644,38 @@ class LayerService {
|
|
|
3062
3644
|
compileFields: Object.keys(compileFields).length > 0 ? Object.freeze(compileFields) : undefined
|
|
3063
3645
|
};
|
|
3064
3646
|
}
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3647
|
+
compileLayerBindings(layer, tokens) {
|
|
3648
|
+
return this.options.compiler.compileBindings(layer.bindingInputs, tokens, layer.target, layer.order, layer.compileFields);
|
|
3649
|
+
}
|
|
3650
|
+
applyCompiledBindings(layer, compilation) {
|
|
3651
|
+
this.runLayerAnalyzers({
|
|
3652
|
+
target: layer.target,
|
|
3653
|
+
order: layer.order,
|
|
3654
|
+
commandLookup: layer.commandLookup,
|
|
3655
|
+
bindingInputs: layer.bindingInputs,
|
|
3656
|
+
compiledBindings: compilation.bindings,
|
|
3657
|
+
root: compilation.root,
|
|
3658
|
+
hasTokenBindings: compilation.hasTokenBindings
|
|
3659
|
+
});
|
|
3660
|
+
for (const binding of layer.compiledBindings) {
|
|
3661
|
+
this.disconnectRuntimeMatchable(binding);
|
|
3069
3662
|
}
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3663
|
+
layer.root = compilation.root;
|
|
3664
|
+
layer.compiledBindings = compilation.bindings;
|
|
3665
|
+
layer.hasUnkeyedBindings = compilation.bindings.some((binding) => binding.hasUnkeyedMatchers);
|
|
3666
|
+
layer.hasTokenBindings = compilation.hasTokenBindings;
|
|
3667
|
+
for (const binding of layer.compiledBindings) {
|
|
3668
|
+
this.connectRuntimeMatchable(binding);
|
|
3669
|
+
}
|
|
3670
|
+
return this.state.projection.pendingSequence?.captures.some((capture) => capture.layer === layer) ?? false;
|
|
3076
3671
|
}
|
|
3077
3672
|
indexLayer(layer) {
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
bucket.focusLayers = sortByPriorityAndOrder([...bucket.focusLayers, layer], { order: "desc" });
|
|
3081
|
-
} else {
|
|
3082
|
-
bucket.focusWithinLayers = sortByPriorityAndOrder([...bucket.focusWithinLayers, layer], { order: "desc" });
|
|
3083
|
-
}
|
|
3084
|
-
layer.bucket = bucket;
|
|
3673
|
+
this.state.layers.sortedLayers = sortLayers([...this.state.layers.sortedLayers, layer]);
|
|
3674
|
+
this.state.layers.activeLayersVersion += 1;
|
|
3085
3675
|
}
|
|
3086
3676
|
removeLayerFromIndex(layer) {
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
return;
|
|
3090
|
-
}
|
|
3091
|
-
if (layer.scope === "focus") {
|
|
3092
|
-
bucket.focusLayers = bucket.focusLayers.filter((candidate) => candidate !== layer);
|
|
3093
|
-
} else {
|
|
3094
|
-
bucket.focusWithinLayers = bucket.focusWithinLayers.filter((candidate) => candidate !== layer);
|
|
3095
|
-
}
|
|
3096
|
-
if (bucket.focusLayers.length === 0 && bucket.focusWithinLayers.length === 0) {
|
|
3097
|
-
this.state.layers.targetLayers.delete(layer.indexTarget);
|
|
3098
|
-
}
|
|
3099
|
-
layer.bucket = undefined;
|
|
3677
|
+
this.state.layers.sortedLayers = this.state.layers.sortedLayers.filter((candidate) => candidate !== layer);
|
|
3678
|
+
this.state.layers.activeLayersVersion += 1;
|
|
3100
3679
|
}
|
|
3101
3680
|
unregisterLayer(layer) {
|
|
3102
3681
|
this.notify.runWithStateChangeBatch(() => {
|
|
@@ -3111,14 +3690,16 @@ class LayerService {
|
|
|
3111
3690
|
this.state.commands.commandMetadataVersion += 1;
|
|
3112
3691
|
removeRegisteredCommandNames(this.state.commands.registeredNames, layer.commands);
|
|
3113
3692
|
}
|
|
3114
|
-
this.
|
|
3693
|
+
this.disconnectRuntimeMatchable(layer);
|
|
3115
3694
|
for (const command of layer.commands) {
|
|
3116
|
-
this.
|
|
3695
|
+
this.disconnectRuntimeMatchable(command);
|
|
3117
3696
|
}
|
|
3118
3697
|
for (const binding of layer.compiledBindings) {
|
|
3119
|
-
this.
|
|
3698
|
+
this.disconnectRuntimeMatchable(binding);
|
|
3120
3699
|
}
|
|
3121
3700
|
this.removeLayerFromIndex(layer);
|
|
3701
|
+
this.activation.invalidateActiveLayers();
|
|
3702
|
+
this.activation.refreshActiveLayers();
|
|
3122
3703
|
layer.offTargetDestroy?.();
|
|
3123
3704
|
layer.offTargetDestroy = undefined;
|
|
3124
3705
|
if (this.state.projection.pendingSequence?.captures.some((capture) => capture.layer === layer)) {
|
|
@@ -3129,6 +3710,49 @@ class LayerService {
|
|
|
3129
3710
|
this.notify.queueStateChange();
|
|
3130
3711
|
});
|
|
3131
3712
|
}
|
|
3713
|
+
connectRuntimeMatchable(target) {
|
|
3714
|
+
this.attachReactiveMatchers(target);
|
|
3715
|
+
this.conditions.indexRuntimeMatchable(target);
|
|
3716
|
+
}
|
|
3717
|
+
disconnectRuntimeMatchable(target) {
|
|
3718
|
+
this.detachReactiveMatchers(target);
|
|
3719
|
+
this.conditions.unindexRuntimeMatchable(target);
|
|
3720
|
+
}
|
|
3721
|
+
attachReactiveMatchers(target) {
|
|
3722
|
+
for (const matcher of target.matchers) {
|
|
3723
|
+
if (!matcher.subscribe) {
|
|
3724
|
+
continue;
|
|
3725
|
+
}
|
|
3726
|
+
try {
|
|
3727
|
+
matcher.dispose = matcher.subscribe(() => {
|
|
3728
|
+
target.matchCacheDirty = true;
|
|
3729
|
+
if (!this.activation.hasPendingSequenceState()) {
|
|
3730
|
+
this.notify.queueStateChange();
|
|
3731
|
+
return;
|
|
3732
|
+
}
|
|
3733
|
+
this.notify.runWithStateChangeBatch(() => {
|
|
3734
|
+
this.activation.revalidatePendingSequenceIfNeeded();
|
|
3735
|
+
this.notify.queueStateChange();
|
|
3736
|
+
});
|
|
3737
|
+
});
|
|
3738
|
+
} catch (error) {
|
|
3739
|
+
this.notify.emitError("reactive-matcher-subscribe-error", error, getErrorMessage(error, `Failed to subscribe to reactive matcher from ${matcher.source}`));
|
|
3740
|
+
}
|
|
3741
|
+
}
|
|
3742
|
+
}
|
|
3743
|
+
detachReactiveMatchers(target) {
|
|
3744
|
+
for (const matcher of target.matchers) {
|
|
3745
|
+
if (!matcher.dispose) {
|
|
3746
|
+
continue;
|
|
3747
|
+
}
|
|
3748
|
+
try {
|
|
3749
|
+
matcher.dispose();
|
|
3750
|
+
} catch (error) {
|
|
3751
|
+
this.notify.emitError("reactive-matcher-dispose-error", error, getErrorMessage(error, `Failed to dispose reactive matcher from ${matcher.source}`));
|
|
3752
|
+
}
|
|
3753
|
+
matcher.dispose = undefined;
|
|
3754
|
+
}
|
|
3755
|
+
}
|
|
3132
3756
|
}
|
|
3133
3757
|
|
|
3134
3758
|
// src/lib/emitter.ts
|
|
@@ -3195,6 +3819,8 @@ class Emitter {
|
|
|
3195
3819
|
}
|
|
3196
3820
|
|
|
3197
3821
|
// src/services/notify.ts
|
|
3822
|
+
var MAX_STATE_CHANGE_FLUSH_ITERATIONS = 1000;
|
|
3823
|
+
|
|
3198
3824
|
class NotificationService {
|
|
3199
3825
|
state;
|
|
3200
3826
|
events;
|
|
@@ -3227,10 +3853,11 @@ class NotificationService {
|
|
|
3227
3853
|
}
|
|
3228
3854
|
emitWarning(code, warning, message) {
|
|
3229
3855
|
if (!this.events.has("warning")) {
|
|
3856
|
+
const consoleMessage = `[${code}] ${message}`;
|
|
3230
3857
|
if (warning instanceof Error) {
|
|
3231
|
-
console.warn(
|
|
3858
|
+
console.warn(consoleMessage, warning);
|
|
3232
3859
|
} else {
|
|
3233
|
-
console.warn(
|
|
3860
|
+
console.warn(consoleMessage);
|
|
3234
3861
|
}
|
|
3235
3862
|
return;
|
|
3236
3863
|
}
|
|
@@ -3238,10 +3865,11 @@ class NotificationService {
|
|
|
3238
3865
|
}
|
|
3239
3866
|
emitError(code, error, message) {
|
|
3240
3867
|
if (!this.events.has("error")) {
|
|
3868
|
+
const consoleMessage = `[${code}] ${message}`;
|
|
3241
3869
|
if (error instanceof Error) {
|
|
3242
|
-
console.error(
|
|
3870
|
+
console.error(consoleMessage, error);
|
|
3243
3871
|
} else {
|
|
3244
|
-
console.error(
|
|
3872
|
+
console.error(consoleMessage);
|
|
3245
3873
|
}
|
|
3246
3874
|
return;
|
|
3247
3875
|
}
|
|
@@ -3270,7 +3898,14 @@ class NotificationService {
|
|
|
3270
3898
|
}
|
|
3271
3899
|
this.state.notify.flushingStateChange = true;
|
|
3272
3900
|
try {
|
|
3901
|
+
let iterations = 0;
|
|
3273
3902
|
while (this.state.notify.stateChangePending && this.state.notify.stateChangeDepth === 0) {
|
|
3903
|
+
if (iterations >= MAX_STATE_CHANGE_FLUSH_ITERATIONS) {
|
|
3904
|
+
this.state.notify.stateChangePending = false;
|
|
3905
|
+
this.emitError("state-change-feedback-loop", { iterations: MAX_STATE_CHANGE_FLUSH_ITERATIONS }, `[Keymap] Possible infinite state listener feedback loop detected after ${MAX_STATE_CHANGE_FLUSH_ITERATIONS} iterations; pending state notifications were dropped`);
|
|
3906
|
+
break;
|
|
3907
|
+
}
|
|
3908
|
+
iterations += 1;
|
|
3274
3909
|
this.state.notify.stateChangePending = false;
|
|
3275
3910
|
this.hooks.emit("state");
|
|
3276
3911
|
}
|
|
@@ -3416,12 +4051,17 @@ function createKeymapState() {
|
|
|
3416
4051
|
},
|
|
3417
4052
|
dispatch: {
|
|
3418
4053
|
eventMatchResolvers: new OrderedRegistry,
|
|
4054
|
+
disambiguationResolvers: new OrderedRegistry,
|
|
3419
4055
|
keyHooks: new PriorityRegistry,
|
|
3420
4056
|
rawHooks: new PriorityRegistry
|
|
3421
4057
|
},
|
|
3422
4058
|
layers: {
|
|
3423
4059
|
layers: new Set,
|
|
3424
|
-
|
|
4060
|
+
sortedLayers: [],
|
|
4061
|
+
activeLayersVersion: 0,
|
|
4062
|
+
activeLayersCacheVersion: -1,
|
|
4063
|
+
activeLayersCacheFocused: undefined,
|
|
4064
|
+
activeLayersCache: [],
|
|
3425
4065
|
layersWithConditions: 0,
|
|
3426
4066
|
layersWithCommands: 0,
|
|
3427
4067
|
layerAnalyzers: new OrderedRegistry
|
|
@@ -3432,6 +4072,8 @@ function createKeymapState() {
|
|
|
3432
4072
|
commandResolvers: new OrderedRegistry,
|
|
3433
4073
|
activeCommandViewVersion: -1,
|
|
3434
4074
|
activeCommandView: undefined,
|
|
4075
|
+
registeredCommandViewVersion: -1,
|
|
4076
|
+
registeredCommandView: undefined,
|
|
3435
4077
|
registeredCommandEntriesCacheVersion: -1,
|
|
3436
4078
|
registeredCommandEntriesCache: []
|
|
3437
4079
|
},
|
|
@@ -3509,7 +4151,11 @@ class Keymap {
|
|
|
3509
4151
|
this.activation.ensureValidPendingSequence();
|
|
3510
4152
|
}
|
|
3511
4153
|
});
|
|
3512
|
-
this.activation = new ActivationService(this.state, this.host, this.hooks, this.notify, this.conditions, this.catalog
|
|
4154
|
+
this.activation = new ActivationService(this.state, this.host, this.hooks, this.notify, this.conditions, this.catalog, {
|
|
4155
|
+
onPendingSequenceChanged: (previous, next) => {
|
|
4156
|
+
this.dispatch?.handlePendingSequenceChange(previous, next);
|
|
4157
|
+
}
|
|
4158
|
+
});
|
|
3513
4159
|
this.runtime = new RuntimeService(this.state, this.notify, this.conditions, this.activation);
|
|
3514
4160
|
this.executor = new CommandExecutorService(this.notify, this.runtime, this.activation, this.catalog, {
|
|
3515
4161
|
keymap: this,
|
|
@@ -3532,7 +4178,7 @@ class Keymap {
|
|
|
3532
4178
|
}
|
|
3533
4179
|
});
|
|
3534
4180
|
this.environment = new EnvironmentService(this.state, this.notify, this.compiler, this.layers);
|
|
3535
|
-
this.dispatch = new DispatchService(this.state, this.notify, this.runtime, this.activation, this.conditions, this.executor, this.compiler);
|
|
4181
|
+
this.dispatch = new DispatchService(this.state, this.notify, this.runtime, this.activation, this.conditions, this.executor, this.compiler, this.catalog, this.layers);
|
|
3536
4182
|
this.keypressListener = (event) => {
|
|
3537
4183
|
this.dispatch.handleKeyEvent(event, false);
|
|
3538
4184
|
};
|
|
@@ -3567,18 +4213,7 @@ class Keymap {
|
|
|
3567
4213
|
resource.dispose();
|
|
3568
4214
|
}
|
|
3569
4215
|
this.resources.clear();
|
|
3570
|
-
|
|
3571
|
-
this.conditions.unregisterRuntimeMatchable(layer);
|
|
3572
|
-
for (const command of layer.commands) {
|
|
3573
|
-
this.conditions.unregisterRuntimeMatchable(command);
|
|
3574
|
-
}
|
|
3575
|
-
for (const binding of layer.compiledBindings) {
|
|
3576
|
-
this.conditions.unregisterRuntimeMatchable(binding);
|
|
3577
|
-
}
|
|
3578
|
-
layer.offTargetDestroy?.();
|
|
3579
|
-
layer.offTargetDestroy = undefined;
|
|
3580
|
-
layer.bucket = undefined;
|
|
3581
|
-
}
|
|
4216
|
+
this.layers.cleanup();
|
|
3582
4217
|
for (const cleanupListener of this.cleanupListeners.splice(0)) {
|
|
3583
4218
|
cleanupListener();
|
|
3584
4219
|
}
|
|
@@ -3646,6 +4281,9 @@ class Keymap {
|
|
|
3646
4281
|
runCommand(cmd, options) {
|
|
3647
4282
|
return this.executor.runCommand(cmd, options);
|
|
3648
4283
|
}
|
|
4284
|
+
dispatchCommand(cmd, options) {
|
|
4285
|
+
return this.executor.dispatchCommand(cmd, options);
|
|
4286
|
+
}
|
|
3649
4287
|
on(name, fn) {
|
|
3650
4288
|
if (name === "warning") {
|
|
3651
4289
|
return this.events.hook(name, fn);
|
|
@@ -3730,9 +4368,20 @@ class Keymap {
|
|
|
3730
4368
|
clearEventMatchResolvers() {
|
|
3731
4369
|
this.dispatch.clearEventMatchResolvers();
|
|
3732
4370
|
}
|
|
4371
|
+
prependDisambiguationResolver(resolver) {
|
|
4372
|
+
return this.dispatch.prependDisambiguationResolver(resolver);
|
|
4373
|
+
}
|
|
4374
|
+
appendDisambiguationResolver(resolver) {
|
|
4375
|
+
return this.dispatch.appendDisambiguationResolver(resolver);
|
|
4376
|
+
}
|
|
4377
|
+
clearDisambiguationResolvers() {
|
|
4378
|
+
this.dispatch.clearDisambiguationResolvers();
|
|
4379
|
+
}
|
|
3733
4380
|
handleFocusedTargetChange(_focused) {
|
|
3734
4381
|
this.notify.runWithStateChangeBatch(() => {
|
|
3735
4382
|
this.activation.setPendingSequence(null);
|
|
4383
|
+
this.activation.invalidateActiveLayers();
|
|
4384
|
+
this.activation.refreshActiveLayers(_focused);
|
|
3736
4385
|
this.notify.queueStateChange();
|
|
3737
4386
|
});
|
|
3738
4387
|
}
|
|
@@ -4157,9 +4806,8 @@ function warnDeadMetadataOnlyBinding(ctx, binding) {
|
|
|
4157
4806
|
const warningKey = `dead-binding:${binding.sourceLayerOrder}:${binding.sourceBindingIndex}:${sourceKey}`;
|
|
4158
4807
|
ctx.warnOnce(warningKey, "dead-binding", {
|
|
4159
4808
|
binding: binding.sourceBinding,
|
|
4160
|
-
scope: binding.sourceScope,
|
|
4161
4809
|
target: binding.sourceTarget
|
|
4162
|
-
}, `[Keymap] Binding "${sequence}"
|
|
4810
|
+
}, `[Keymap] Binding "${sequence}" has no command and no reachable continuations; it will never trigger`);
|
|
4163
4811
|
}
|
|
4164
4812
|
function registerDeadBindingWarnings(keymap) {
|
|
4165
4813
|
return keymap.appendLayerAnalyzer((ctx) => {
|
|
@@ -4207,7 +4855,7 @@ function normalizeEnabledValue(fieldName, value) {
|
|
|
4207
4855
|
}
|
|
4208
4856
|
throw new Error(`Keymap enabled field "${fieldName}" must be a boolean, a function, or a reactive matcher`);
|
|
4209
4857
|
}
|
|
4210
|
-
function
|
|
4858
|
+
function registerEnabledFields(keymap) {
|
|
4211
4859
|
const offLayerFields = keymap.registerLayerFields({
|
|
4212
4860
|
enabled(value, ctx) {
|
|
4213
4861
|
const normalized = normalizeEnabledValue("enabled", value);
|
|
@@ -4239,21 +4887,6 @@ function registerEnabledField(keymap) {
|
|
|
4239
4887
|
offLayerFields();
|
|
4240
4888
|
};
|
|
4241
4889
|
}
|
|
4242
|
-
function registerEnabledCommandField(keymap) {
|
|
4243
|
-
return keymap.registerCommandFields({
|
|
4244
|
-
enabled(value, ctx) {
|
|
4245
|
-
const normalized = normalizeEnabledValue("enabled", value);
|
|
4246
|
-
if (normalized === true) {
|
|
4247
|
-
return;
|
|
4248
|
-
}
|
|
4249
|
-
if (normalized === false) {
|
|
4250
|
-
ctx.activeWhen(() => false);
|
|
4251
|
-
return;
|
|
4252
|
-
}
|
|
4253
|
-
ctx.activeWhen(normalized);
|
|
4254
|
-
}
|
|
4255
|
-
});
|
|
4256
|
-
}
|
|
4257
4890
|
// src/addons/universal/emacs-bindings.ts
|
|
4258
4891
|
function parseEmacsStroke(input, sequence, parseObjectKey) {
|
|
4259
4892
|
const parts = input.split("+");
|
|
@@ -4410,7 +5043,7 @@ function registerExCommands(keymap, commands) {
|
|
|
4410
5043
|
});
|
|
4411
5044
|
}
|
|
4412
5045
|
}
|
|
4413
|
-
const offCommands = keymap.registerLayer({
|
|
5046
|
+
const offCommands = keymap.registerLayer({ commands: registrations });
|
|
4414
5047
|
const offResolver = keymap.appendCommandResolver((input, ctx) => {
|
|
4415
5048
|
if (!input.startsWith(":")) {
|
|
4416
5049
|
return;
|
|
@@ -4495,6 +5128,19 @@ function registerMetadataFields(keymap) {
|
|
|
4495
5128
|
offBindingFields();
|
|
4496
5129
|
};
|
|
4497
5130
|
}
|
|
5131
|
+
// src/addons/universal/neovim-disambiguation.ts
|
|
5132
|
+
function registerNeovimDisambiguation(keymap, options) {
|
|
5133
|
+
const timeoutMs = options?.timeoutMs ?? 300;
|
|
5134
|
+
return keymap.appendDisambiguationResolver((ctx) => {
|
|
5135
|
+
return ctx.defer(async (deferred) => {
|
|
5136
|
+
const elapsed = await deferred.sleep(timeoutMs);
|
|
5137
|
+
if (!elapsed) {
|
|
5138
|
+
return;
|
|
5139
|
+
}
|
|
5140
|
+
return deferred.runExact();
|
|
5141
|
+
});
|
|
5142
|
+
});
|
|
5143
|
+
}
|
|
4498
5144
|
// src/addons/universal/timed-leader.ts
|
|
4499
5145
|
function registerTimedLeader(keymap, options) {
|
|
4500
5146
|
const matchesTrigger = keymap.createKeyMatcher(options.trigger);
|
|
@@ -4561,10 +5207,9 @@ function warnUnresolvedCommand(ctx, binding) {
|
|
|
4561
5207
|
const warning = {
|
|
4562
5208
|
command: binding.command,
|
|
4563
5209
|
binding: binding.sourceBinding,
|
|
4564
|
-
scope: binding.sourceScope,
|
|
4565
5210
|
target: binding.sourceTarget
|
|
4566
5211
|
};
|
|
4567
|
-
ctx.warnOnce(`unresolved:${binding.sourceLayerOrder}:${binding.sourceBindingIndex}:${binding.command}:${sourceKey}`, "unresolved-command", warning, `[Keymap] Unresolved command "${binding.command}" for binding "${sequence}"
|
|
5212
|
+
ctx.warnOnce(`unresolved:${binding.sourceLayerOrder}:${binding.sourceBindingIndex}:${binding.command}:${sourceKey}`, "unresolved-command", warning, `[Keymap] Unresolved command "${binding.command}" for binding "${sequence}"`);
|
|
4568
5213
|
}
|
|
4569
5214
|
function registerUnresolvedCommandWarnings(keymap) {
|
|
4570
5215
|
return keymap.appendLayerAnalyzer((ctx) => {
|
|
@@ -4934,7 +5579,6 @@ function registerEditBufferCommands(keymap, renderer, options) {
|
|
|
4934
5579
|
const descriptions = resolveEditBufferCommandDescriptions(options);
|
|
4935
5580
|
return keymap.acquireResource(EDIT_BUFFER_COMMANDS_RESOURCE, () => {
|
|
4936
5581
|
return keymap.registerLayer({
|
|
4937
|
-
scope: "global",
|
|
4938
5582
|
commands: createEditBufferCommands(renderer, commandNames, descriptions)
|
|
4939
5583
|
});
|
|
4940
5584
|
});
|
|
@@ -4945,7 +5589,7 @@ function registerManagedTextareaLayer(keymap, renderer, layer, options) {
|
|
|
4945
5589
|
const offCommands = registerEditBufferCommands(keymap, renderer, options);
|
|
4946
5590
|
const offSuspension = registerTextareaMappingSuspension(keymap, renderer);
|
|
4947
5591
|
try {
|
|
4948
|
-
const { bindings, ...rest } = layer;
|
|
5592
|
+
const { bindings, target: _ignoredTarget, targetMode: _ignoredTargetMode, ...rest } = layer;
|
|
4949
5593
|
const offLayer = keymap.registerLayer({
|
|
4950
5594
|
...rest,
|
|
4951
5595
|
bindings: createTextareaBindingsWithResolvedOptions(bindings ? keymap.normalizeBindings(bindings) : undefined, commandNames, descriptions)
|
|
@@ -4965,13 +5609,13 @@ export {
|
|
|
4965
5609
|
registerUnresolvedCommandWarnings,
|
|
4966
5610
|
registerTimedLeader,
|
|
4967
5611
|
registerTextareaMappingSuspension,
|
|
5612
|
+
registerNeovimDisambiguation,
|
|
4968
5613
|
registerMetadataFields,
|
|
4969
5614
|
registerManagedTextareaLayer,
|
|
4970
5615
|
registerLeader,
|
|
4971
5616
|
registerExCommands,
|
|
4972
5617
|
registerEscapeClearsPendingSequence,
|
|
4973
|
-
|
|
4974
|
-
registerEnabledCommandField,
|
|
5618
|
+
registerEnabledFields,
|
|
4975
5619
|
registerEmacsBindings,
|
|
4976
5620
|
registerEditBufferCommands,
|
|
4977
5621
|
registerDefaultKeys,
|