@opentui/keymap 0.2.1 → 0.2.3
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 +64 -30
- package/chunks/index-frk6sdcd.js +409 -0
- package/chunks/index-frk6sdcd.js.map +14 -0
- package/package.json +43 -23
- package/src/addons/index.js +1130 -0
- package/src/addons/index.js.map +25 -0
- package/src/addons/opentui/edit-buffer-bindings.d.ts +6 -2
- package/src/addons/opentui/index.d.ts +2 -2
- package/src/addons/opentui/index.js +467 -0
- package/src/addons/opentui/index.js.map +12 -0
- package/src/addons/universal/binding-overrides.d.ts +6 -0
- package/src/addons/universal/dead-bindings.d.ts +1 -1
- package/src/addons/universal/default-parser.d.ts +2 -2
- package/src/addons/universal/ex-commands.d.ts +11 -8
- package/src/addons/universal/index.d.ts +3 -1
- package/src/addons/universal/leader.d.ts +1 -1
- package/src/addons/universal/metadata.d.ts +2 -2
- package/src/addons/universal/mod-bindings.d.ts +6 -0
- package/src/addons/universal/unresolved-commands.d.ts +1 -1
- package/src/extras/binding-sections.d.ts +18 -0
- package/src/extras/command-bindings.d.ts +19 -0
- package/src/extras/formatting.d.ts +27 -0
- package/src/extras/graph.d.ts +9 -0
- package/src/extras/graph.js +373 -0
- package/src/extras/graph.js.map +11 -0
- package/src/extras/index.d.ts +6 -0
- package/src/extras/index.js +239 -0
- package/src/extras/index.js.map +12 -0
- package/src/extras/lib/graph-snapshot.d.ts +14 -0
- package/src/extras/lib/graph-types.d.ts +83 -0
- package/src/html.d.ts +3 -3
- package/src/html.js +297 -0
- package/src/html.js.map +10 -0
- package/src/index.d.ts +3 -1
- package/src/index.js +4492 -0
- package/src/index.js.map +34 -0
- package/src/keymap.d.ts +23 -35
- package/src/lib/emitter.d.ts +1 -2
- package/src/lib/registry.d.ts +2 -2
- package/src/lib/runtime-utils.d.ts +34 -0
- package/src/opentui.d.ts +1 -3
- package/src/opentui.js +133 -0
- package/src/opentui.js.map +10 -0
- package/src/react/index.d.ts +5 -19
- package/{react → src/react}/index.js +3 -0
- package/src/react/index.js.map +10 -0
- package/src/runtime-modules.d.ts +20 -0
- package/src/runtime-modules.js +28 -0
- package/src/runtime-modules.js.map +10 -0
- package/src/services/activation.d.ts +7 -33
- package/src/services/active-key-cache.d.ts +29 -0
- package/src/services/command-catalog.d.ts +28 -45
- package/src/services/command-executor.d.ts +7 -13
- package/src/services/compiler.d.ts +6 -12
- package/src/services/conditions.d.ts +4 -16
- package/src/services/dispatch-decisions.d.ts +21 -0
- package/src/services/dispatch-patterns.d.ts +5 -0
- package/src/services/dispatch.d.ts +6 -42
- package/src/services/environment.d.ts +6 -21
- package/src/services/extension-context.d.ts +16 -0
- package/src/services/layer-diagnostics.d.ts +10 -0
- package/src/services/layers.d.ts +15 -23
- package/src/services/notify.d.ts +6 -8
- package/src/services/pending-sequence.d.ts +4 -0
- package/src/services/primitives/active-layers.d.ts +2 -3
- package/src/services/primitives/bindings.d.ts +4 -0
- package/src/services/primitives/command-normalization.d.ts +3 -0
- package/src/services/primitives/field-invariants.d.ts +16 -1
- package/src/services/primitives/pending-captures.d.ts +5 -0
- package/src/services/runtime-view.d.ts +5 -0
- package/src/services/runtime.d.ts +2 -7
- package/src/services/sequence-index.d.ts +24 -0
- package/src/services/state.d.ts +46 -91
- package/src/solid/index.d.ts +5 -19
- package/{solid → src/solid}/index.js +3 -0
- package/src/solid/index.js.map +10 -0
- package/src/testing/index.d.ts +90 -0
- package/src/testing/index.js +276 -0
- package/src/testing/index.js.map +10 -0
- package/src/types.d.ts +194 -126
- package/addons/index.js +0 -5240
- package/addons/opentui/index.js +0 -5632
- package/html.js +0 -5042
- package/index.js +0 -4411
- package/opentui.js +0 -4887
- package/src/services/primitives/binding-inputs.d.ts +0 -4
package/index.js
DELETED
|
@@ -1,4411 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
// src/services/primitives/active-layers.ts
|
|
3
|
-
function getFocusedTargetIfAvailable(host) {
|
|
4
|
-
if (host.isDestroyed) {
|
|
5
|
-
return null;
|
|
6
|
-
}
|
|
7
|
-
return host.getFocusedTarget();
|
|
8
|
-
}
|
|
9
|
-
function forEachActivationTarget(host, focused, visit) {
|
|
10
|
-
let current = focused ?? host.rootTarget;
|
|
11
|
-
let isFocusedTarget = focused !== null;
|
|
12
|
-
while (current) {
|
|
13
|
-
const shouldContinue = visit(current, isFocusedTarget);
|
|
14
|
-
if (shouldContinue === false) {
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
current = host.getParentTarget(current);
|
|
18
|
-
isFocusedTarget = false;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
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
|
-
}
|
|
32
|
-
const activeLayers = [];
|
|
33
|
-
const activationPath = getActivationPath(host, focused);
|
|
34
|
-
for (const layer of state.sortedLayers) {
|
|
35
|
-
if (isLayerActiveForFocused(host, layer, focused, activationPath)) {
|
|
36
|
-
activeLayers.push(layer);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
state.activeLayersCacheVersion = state.activeLayersVersion;
|
|
40
|
-
state.activeLayersCacheFocused = focused;
|
|
41
|
-
state.activeLayersCache = activeLayers;
|
|
42
|
-
return activeLayers;
|
|
43
|
-
}
|
|
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
|
-
}
|
|
54
|
-
if (host.isTargetDestroyed(target)) {
|
|
55
|
-
return false;
|
|
56
|
-
}
|
|
57
|
-
if (layer.targetMode === "focus") {
|
|
58
|
-
return target === focused;
|
|
59
|
-
}
|
|
60
|
-
return activationPath.has(target);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// src/services/keys.ts
|
|
64
|
-
function normalizeBindingTokenName(token) {
|
|
65
|
-
const normalized = token.trim().toLowerCase();
|
|
66
|
-
if (!normalized) {
|
|
67
|
-
throw new Error("Invalid keymap token: token cannot be empty");
|
|
68
|
-
}
|
|
69
|
-
return normalized;
|
|
70
|
-
}
|
|
71
|
-
function normalizeKeyName(name) {
|
|
72
|
-
const normalized = name.trim().toLowerCase();
|
|
73
|
-
if (!normalized) {
|
|
74
|
-
throw new Error("Invalid key name: key name cannot be empty");
|
|
75
|
-
}
|
|
76
|
-
return normalized;
|
|
77
|
-
}
|
|
78
|
-
function normalizeKeyStroke(input) {
|
|
79
|
-
return {
|
|
80
|
-
name: normalizeKeyName(input.name),
|
|
81
|
-
ctrl: input.ctrl ?? false,
|
|
82
|
-
shift: input.shift ?? false,
|
|
83
|
-
meta: input.meta ?? false,
|
|
84
|
-
super: input.super ?? false,
|
|
85
|
-
hyper: input.hyper || undefined
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
function cloneKeyStroke(stroke) {
|
|
89
|
-
return {
|
|
90
|
-
name: stroke.name,
|
|
91
|
-
ctrl: stroke.ctrl,
|
|
92
|
-
shift: stroke.shift,
|
|
93
|
-
meta: stroke.meta,
|
|
94
|
-
super: stroke.super,
|
|
95
|
-
hyper: stroke.hyper || undefined
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
function createKeySequencePart(input, options) {
|
|
99
|
-
const stroke = cloneKeyStroke(normalizeKeyStroke(input));
|
|
100
|
-
return {
|
|
101
|
-
stroke,
|
|
102
|
-
display: options?.display ?? stringifyCanonicalStroke(stroke),
|
|
103
|
-
match: options?.match ?? createKeyMatch(stroke),
|
|
104
|
-
tokenName: options?.tokenName ? normalizeBindingTokenName(options.tokenName) : undefined
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
function cloneKeySequencePart(part) {
|
|
108
|
-
return {
|
|
109
|
-
stroke: cloneKeyStroke(part.stroke),
|
|
110
|
-
display: part.display,
|
|
111
|
-
match: part.match,
|
|
112
|
-
tokenName: part.tokenName
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
function cloneKeySequence(parts) {
|
|
116
|
-
return parts.map((part) => cloneKeySequencePart(part));
|
|
117
|
-
}
|
|
118
|
-
function resolveKeyMatch(input) {
|
|
119
|
-
if ("match" in input) {
|
|
120
|
-
return input.match;
|
|
121
|
-
}
|
|
122
|
-
if ("stroke" in input) {
|
|
123
|
-
return createKeyMatch(input.stroke);
|
|
124
|
-
}
|
|
125
|
-
return createKeyMatch(input);
|
|
126
|
-
}
|
|
127
|
-
function createKeyMatch(input) {
|
|
128
|
-
return `key:${buildKeyMatchId(normalizeKeyStroke(input))}`;
|
|
129
|
-
}
|
|
130
|
-
function createTextKeyMatch(id) {
|
|
131
|
-
const normalized = id.trim();
|
|
132
|
-
if (!normalized) {
|
|
133
|
-
throw new Error("Invalid keymap match id: id cannot be empty");
|
|
134
|
-
}
|
|
135
|
-
return `text:${normalized}`;
|
|
136
|
-
}
|
|
137
|
-
function stringifyKeyStroke(input, options) {
|
|
138
|
-
if ("stroke" in input) {
|
|
139
|
-
if (options?.preferDisplay && input.display) {
|
|
140
|
-
return input.display;
|
|
141
|
-
}
|
|
142
|
-
return stringifyCanonicalStroke(input.stroke);
|
|
143
|
-
}
|
|
144
|
-
return stringifyCanonicalStroke(normalizeKeyStroke(input));
|
|
145
|
-
}
|
|
146
|
-
function stringifyKeySequence(input, options) {
|
|
147
|
-
return input.map((part) => stringifyKeyStroke(part, options)).join("");
|
|
148
|
-
}
|
|
149
|
-
function stringifyCanonicalStroke(stroke) {
|
|
150
|
-
const parts = [];
|
|
151
|
-
if (stroke.ctrl) {
|
|
152
|
-
parts.push("ctrl");
|
|
153
|
-
}
|
|
154
|
-
if (stroke.shift) {
|
|
155
|
-
parts.push("shift");
|
|
156
|
-
}
|
|
157
|
-
if (stroke.meta) {
|
|
158
|
-
parts.push("meta");
|
|
159
|
-
}
|
|
160
|
-
if (stroke.super) {
|
|
161
|
-
parts.push("super");
|
|
162
|
-
}
|
|
163
|
-
if (stroke.hyper) {
|
|
164
|
-
parts.push("hyper");
|
|
165
|
-
}
|
|
166
|
-
parts.push(stroke.name === "return" ? "enter" : stroke.name);
|
|
167
|
-
return parts.join("+");
|
|
168
|
-
}
|
|
169
|
-
function buildKeyMatchId(stroke) {
|
|
170
|
-
return `${stroke.name}:${stroke.ctrl ? 1 : 0}:${stroke.shift ? 1 : 0}:${stroke.meta ? 1 : 0}:${stroke.super ? 1 : 0}:${stroke.hyper ? 1 : 0}`;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// src/services/activation.ts
|
|
174
|
-
function getLiveHost(host) {
|
|
175
|
-
if (host.isDestroyed) {
|
|
176
|
-
throw new Error("Cannot use a keymap after its host was destroyed");
|
|
177
|
-
}
|
|
178
|
-
return host;
|
|
179
|
-
}
|
|
180
|
-
function isSamePendingSequence(current, next) {
|
|
181
|
-
if (current === next) {
|
|
182
|
-
return true;
|
|
183
|
-
}
|
|
184
|
-
if (!current || !next) {
|
|
185
|
-
return false;
|
|
186
|
-
}
|
|
187
|
-
if (current.captures.length !== next.captures.length) {
|
|
188
|
-
return false;
|
|
189
|
-
}
|
|
190
|
-
for (let index = 0;index < current.captures.length; index += 1) {
|
|
191
|
-
const left = current.captures[index];
|
|
192
|
-
const right = next.captures[index];
|
|
193
|
-
if (!left || !right || left.layer !== right.layer || left.node !== right.node) {
|
|
194
|
-
return false;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
return true;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
class ActivationService {
|
|
201
|
-
state;
|
|
202
|
-
host;
|
|
203
|
-
hooks;
|
|
204
|
-
notify;
|
|
205
|
-
conditions;
|
|
206
|
-
catalog;
|
|
207
|
-
options;
|
|
208
|
-
constructor(state, host, hooks, notify, conditions, catalog, options = {}) {
|
|
209
|
-
this.state = state;
|
|
210
|
-
this.host = host;
|
|
211
|
-
this.hooks = hooks;
|
|
212
|
-
this.notify = notify;
|
|
213
|
-
this.conditions = conditions;
|
|
214
|
-
this.catalog = catalog;
|
|
215
|
-
this.options = options;
|
|
216
|
-
}
|
|
217
|
-
getFocusedTarget() {
|
|
218
|
-
return getLiveHost(this.host).getFocusedTarget();
|
|
219
|
-
}
|
|
220
|
-
getFocusedTargetIfAvailable() {
|
|
221
|
-
return getFocusedTargetIfAvailable(this.host);
|
|
222
|
-
}
|
|
223
|
-
setPendingSequence(next) {
|
|
224
|
-
const previous = this.state.projection.pendingSequence;
|
|
225
|
-
if (isSamePendingSequence(previous, next)) {
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
this.state.projection.pendingSequence = next;
|
|
229
|
-
this.options.onPendingSequenceChanged?.(previous, next);
|
|
230
|
-
this.invalidateCaches();
|
|
231
|
-
this.notifyPendingSequenceChange();
|
|
232
|
-
this.notify.queueStateChange();
|
|
233
|
-
}
|
|
234
|
-
ensureValidPendingSequence() {
|
|
235
|
-
const pending = this.state.projection.pendingSequence;
|
|
236
|
-
if (!pending) {
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
const focused = this.getFocusedTarget();
|
|
240
|
-
const captures = pending.captures.filter((capture) => {
|
|
241
|
-
return this.state.layers.layers.has(capture.layer) && this.isLayerActiveForFocused(capture.layer, focused) && this.conditions.layerMatchesRuntimeState(capture.layer) && this.nodeHasReachableBindings(capture.node, focused);
|
|
242
|
-
});
|
|
243
|
-
if (captures.length === 0) {
|
|
244
|
-
this.setPendingSequence(null);
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
if (captures.length !== pending.captures.length) {
|
|
248
|
-
this.setPendingSequence({ captures });
|
|
249
|
-
}
|
|
250
|
-
return this.state.projection.pendingSequence ?? undefined;
|
|
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
|
-
}
|
|
261
|
-
getPendingSequence() {
|
|
262
|
-
const projections = this.state.projection;
|
|
263
|
-
const derivedStateVersion = this.state.notify.derivedStateVersion;
|
|
264
|
-
if (projections.pendingSequenceCacheVersion === derivedStateVersion) {
|
|
265
|
-
return projections.pendingSequenceCache;
|
|
266
|
-
}
|
|
267
|
-
const pending = this.ensureValidPendingSequence();
|
|
268
|
-
const canUseCache = !pending || pending.captures.every((capture) => this.layerCanCacheActiveKeys(capture.layer));
|
|
269
|
-
const sequence = pending ? this.collectSequencePartsFromPending(pending) : [];
|
|
270
|
-
if (canUseCache) {
|
|
271
|
-
projections.pendingSequenceCacheVersion = derivedStateVersion;
|
|
272
|
-
projections.pendingSequenceCache = sequence;
|
|
273
|
-
}
|
|
274
|
-
return sequence;
|
|
275
|
-
}
|
|
276
|
-
popPendingSequence() {
|
|
277
|
-
const pending = this.ensureValidPendingSequence();
|
|
278
|
-
if (!pending) {
|
|
279
|
-
return false;
|
|
280
|
-
}
|
|
281
|
-
const firstCapture = pending.captures[0];
|
|
282
|
-
if (!firstCapture || firstCapture.node.depth <= 1) {
|
|
283
|
-
this.setPendingSequence(null);
|
|
284
|
-
return true;
|
|
285
|
-
}
|
|
286
|
-
const nextCaptures = [];
|
|
287
|
-
for (const capture of pending.captures) {
|
|
288
|
-
const parent = capture.node.parent;
|
|
289
|
-
if (!parent || !parent.stroke) {
|
|
290
|
-
continue;
|
|
291
|
-
}
|
|
292
|
-
nextCaptures.push({
|
|
293
|
-
layer: capture.layer,
|
|
294
|
-
node: parent
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
|
-
if (nextCaptures.length === 0) {
|
|
298
|
-
this.setPendingSequence(null);
|
|
299
|
-
return true;
|
|
300
|
-
}
|
|
301
|
-
this.setPendingSequence({ captures: nextCaptures });
|
|
302
|
-
return true;
|
|
303
|
-
}
|
|
304
|
-
getActiveKeys(options) {
|
|
305
|
-
const projections = this.state.projection;
|
|
306
|
-
const derivedStateVersion = this.state.notify.derivedStateVersion;
|
|
307
|
-
const includeBindings = options?.includeBindings === true;
|
|
308
|
-
const includeMetadata = options?.includeMetadata === true;
|
|
309
|
-
if (includeBindings) {
|
|
310
|
-
if (includeMetadata) {
|
|
311
|
-
if (projections.activeKeysBindingsAndMetadataCacheVersion === derivedStateVersion) {
|
|
312
|
-
return projections.activeKeysBindingsAndMetadataCache;
|
|
313
|
-
}
|
|
314
|
-
} else if (projections.activeKeysBindingsCacheVersion === derivedStateVersion) {
|
|
315
|
-
return projections.activeKeysBindingsCache;
|
|
316
|
-
}
|
|
317
|
-
} else if (includeMetadata) {
|
|
318
|
-
if (projections.activeKeysMetadataCacheVersion === derivedStateVersion) {
|
|
319
|
-
return projections.activeKeysMetadataCache;
|
|
320
|
-
}
|
|
321
|
-
} else if (projections.activeKeysPlainCacheVersion === derivedStateVersion) {
|
|
322
|
-
return projections.activeKeysPlainCache;
|
|
323
|
-
}
|
|
324
|
-
const focused = this.getFocusedTarget();
|
|
325
|
-
const activeView = this.catalog.getActiveCommandView(focused);
|
|
326
|
-
const pending = this.ensureValidPendingSequence();
|
|
327
|
-
const activeLayers = pending ? [] : this.getActiveLayers(focused);
|
|
328
|
-
const canUseCache = pending ? pending.captures.every((capture) => this.layerCanCacheActiveKeys(capture.layer)) : this.activeLayersCanCacheActiveKeys(activeLayers);
|
|
329
|
-
const activeKeys = pending ? this.collectActiveKeysFromPending(pending.captures, includeBindings, includeMetadata, focused, activeView) : this.collectActiveKeysAtRoot(activeLayers, includeBindings, includeMetadata, focused, activeView);
|
|
330
|
-
if (!canUseCache) {
|
|
331
|
-
return activeKeys;
|
|
332
|
-
}
|
|
333
|
-
if (includeBindings) {
|
|
334
|
-
if (includeMetadata) {
|
|
335
|
-
projections.activeKeysBindingsAndMetadataCacheVersion = derivedStateVersion;
|
|
336
|
-
projections.activeKeysBindingsAndMetadataCache = activeKeys;
|
|
337
|
-
} else {
|
|
338
|
-
projections.activeKeysBindingsCacheVersion = derivedStateVersion;
|
|
339
|
-
projections.activeKeysBindingsCache = activeKeys;
|
|
340
|
-
}
|
|
341
|
-
} else if (includeMetadata) {
|
|
342
|
-
projections.activeKeysMetadataCacheVersion = derivedStateVersion;
|
|
343
|
-
projections.activeKeysMetadataCache = activeKeys;
|
|
344
|
-
} else {
|
|
345
|
-
projections.activeKeysPlainCacheVersion = derivedStateVersion;
|
|
346
|
-
projections.activeKeysPlainCache = activeKeys;
|
|
347
|
-
}
|
|
348
|
-
return activeKeys;
|
|
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
|
-
}
|
|
357
|
-
nodeHasReachableBindings(node, focused) {
|
|
358
|
-
return this.hasMatchingBindings(node.reachableBindings, focused, this.catalog.getActiveCommandView(focused));
|
|
359
|
-
}
|
|
360
|
-
getActiveLayers(focused) {
|
|
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);
|
|
368
|
-
}
|
|
369
|
-
isLayerActiveForFocused(layer, focused) {
|
|
370
|
-
return isLayerActiveForFocused(this.host, layer, focused);
|
|
371
|
-
}
|
|
372
|
-
layerCanCacheActiveKeys(layer) {
|
|
373
|
-
return !layer.hasUnkeyedMatchers && !layer.hasUnkeyedCommands && !layer.hasUnkeyedBindings;
|
|
374
|
-
}
|
|
375
|
-
activeLayersCanCacheActiveKeys(activeLayers) {
|
|
376
|
-
for (const layer of activeLayers) {
|
|
377
|
-
if (!this.layerCanCacheActiveKeys(layer)) {
|
|
378
|
-
return false;
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
return true;
|
|
382
|
-
}
|
|
383
|
-
collectNodesFromNode(node) {
|
|
384
|
-
const nodes = [];
|
|
385
|
-
let current = node;
|
|
386
|
-
while (current && current.stroke) {
|
|
387
|
-
nodes.push(current);
|
|
388
|
-
current = current.parent;
|
|
389
|
-
}
|
|
390
|
-
nodes.reverse();
|
|
391
|
-
return nodes;
|
|
392
|
-
}
|
|
393
|
-
collectSequencePartsFromPending(pending) {
|
|
394
|
-
const focused = this.getFocusedTarget();
|
|
395
|
-
const activeView = this.catalog.getActiveCommandView(focused);
|
|
396
|
-
const paths = pending.captures.map((capture) => this.collectNodesFromNode(capture.node));
|
|
397
|
-
const firstPath = paths[0];
|
|
398
|
-
if (!firstPath || firstPath.length === 0) {
|
|
399
|
-
return [];
|
|
400
|
-
}
|
|
401
|
-
const parts = [];
|
|
402
|
-
for (let index = 0;index < firstPath.length; index += 1) {
|
|
403
|
-
const firstNode = firstPath[index];
|
|
404
|
-
if (!firstNode?.stroke || firstNode.match === null) {
|
|
405
|
-
continue;
|
|
406
|
-
}
|
|
407
|
-
let display;
|
|
408
|
-
let tokenName;
|
|
409
|
-
let hasDisplayConflict = false;
|
|
410
|
-
let hasTokenConflict = false;
|
|
411
|
-
for (const path of paths) {
|
|
412
|
-
const node = path[index];
|
|
413
|
-
if (!node) {
|
|
414
|
-
continue;
|
|
415
|
-
}
|
|
416
|
-
const presentation = this.getNodePresentation(node, focused, activeView);
|
|
417
|
-
if (display === undefined) {
|
|
418
|
-
display = presentation.display;
|
|
419
|
-
tokenName = presentation.tokenName;
|
|
420
|
-
continue;
|
|
421
|
-
}
|
|
422
|
-
if (!hasDisplayConflict && display !== presentation.display) {
|
|
423
|
-
hasDisplayConflict = true;
|
|
424
|
-
}
|
|
425
|
-
if (!hasTokenConflict && tokenName !== presentation.tokenName) {
|
|
426
|
-
hasTokenConflict = true;
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
if (display === undefined || hasDisplayConflict) {
|
|
430
|
-
display = stringifyKeyStroke(firstNode.stroke);
|
|
431
|
-
}
|
|
432
|
-
if (hasTokenConflict) {
|
|
433
|
-
tokenName = undefined;
|
|
434
|
-
}
|
|
435
|
-
parts.push(createKeySequencePart(firstNode.stroke, {
|
|
436
|
-
display,
|
|
437
|
-
match: firstNode.match,
|
|
438
|
-
tokenName
|
|
439
|
-
}));
|
|
440
|
-
}
|
|
441
|
-
return parts;
|
|
442
|
-
}
|
|
443
|
-
collectMatchingBindings(bindings, focused, activeView) {
|
|
444
|
-
const matches = [];
|
|
445
|
-
for (const binding of bindings) {
|
|
446
|
-
if (this.conditions.matchesConditions(binding) && this.catalog.isBindingVisible(binding, focused, activeView)) {
|
|
447
|
-
matches.push(binding);
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
return matches;
|
|
451
|
-
}
|
|
452
|
-
hasMatchingBindings(bindings, focused, activeView) {
|
|
453
|
-
for (const binding of bindings) {
|
|
454
|
-
if (this.conditions.matchesConditions(binding) && this.catalog.isBindingVisible(binding, focused, activeView)) {
|
|
455
|
-
return true;
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
return false;
|
|
459
|
-
}
|
|
460
|
-
getNodePresentation(node, focused, activeView, reachableBindings = this.collectMatchingBindings(node.reachableBindings, focused, activeView)) {
|
|
461
|
-
if (!node.stroke) {
|
|
462
|
-
return { display: "" };
|
|
463
|
-
}
|
|
464
|
-
const partIndex = node.depth - 1;
|
|
465
|
-
let display;
|
|
466
|
-
let tokenName;
|
|
467
|
-
let hasDisplayConflict = false;
|
|
468
|
-
let hasTokenConflict = false;
|
|
469
|
-
for (const binding of reachableBindings) {
|
|
470
|
-
const part = binding.sequence[partIndex];
|
|
471
|
-
if (!part) {
|
|
472
|
-
continue;
|
|
473
|
-
}
|
|
474
|
-
if (display === undefined) {
|
|
475
|
-
display = part.display;
|
|
476
|
-
tokenName = part.tokenName;
|
|
477
|
-
continue;
|
|
478
|
-
}
|
|
479
|
-
if (!hasDisplayConflict && display !== part.display) {
|
|
480
|
-
hasDisplayConflict = true;
|
|
481
|
-
}
|
|
482
|
-
if (!hasTokenConflict && tokenName !== part.tokenName) {
|
|
483
|
-
hasTokenConflict = true;
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
if (display === undefined || hasDisplayConflict) {
|
|
487
|
-
display = stringifyKeyStroke(node.stroke);
|
|
488
|
-
}
|
|
489
|
-
if (hasTokenConflict) {
|
|
490
|
-
tokenName = undefined;
|
|
491
|
-
}
|
|
492
|
-
return {
|
|
493
|
-
display,
|
|
494
|
-
tokenName
|
|
495
|
-
};
|
|
496
|
-
}
|
|
497
|
-
toActiveBinding(binding, focused, activeView) {
|
|
498
|
-
return {
|
|
499
|
-
sequence: binding.sequence,
|
|
500
|
-
command: binding.command,
|
|
501
|
-
commandAttrs: this.catalog.getBindingCommandAttrs(binding, focused, activeView),
|
|
502
|
-
attrs: binding.attrs,
|
|
503
|
-
event: binding.event,
|
|
504
|
-
preventDefault: binding.preventDefault,
|
|
505
|
-
fallthrough: binding.fallthrough
|
|
506
|
-
};
|
|
507
|
-
}
|
|
508
|
-
collectActiveBindings(bindings, focused, activeView) {
|
|
509
|
-
return bindings.map((binding) => this.toActiveBinding(binding, focused, activeView));
|
|
510
|
-
}
|
|
511
|
-
collectActiveKeysAtRoot(activeLayers, includeBindings, includeMetadata, focused, activeView) {
|
|
512
|
-
const activeKeys = new Map;
|
|
513
|
-
const stopped = new Set;
|
|
514
|
-
const hasLayerConditions = this.state.layers.layersWithConditions > 0;
|
|
515
|
-
for (const layer of activeLayers) {
|
|
516
|
-
if (layer.root.children.size === 0) {
|
|
517
|
-
continue;
|
|
518
|
-
}
|
|
519
|
-
if (hasLayerConditions && !this.conditions.hasNoConditions(layer) && !this.conditions.matchesConditions(layer)) {
|
|
520
|
-
continue;
|
|
521
|
-
}
|
|
522
|
-
for (const [bindingKey, child] of layer.root.children) {
|
|
523
|
-
if (stopped.has(bindingKey)) {
|
|
524
|
-
continue;
|
|
525
|
-
}
|
|
526
|
-
const selection = this.selectActiveKey(child, includeBindings, focused, activeView);
|
|
527
|
-
if (!selection) {
|
|
528
|
-
continue;
|
|
529
|
-
}
|
|
530
|
-
const existing = activeKeys.get(bindingKey);
|
|
531
|
-
if (!existing) {
|
|
532
|
-
activeKeys.set(bindingKey, this.createActiveKeyState(child.stroke, selection, includeBindings));
|
|
533
|
-
} else {
|
|
534
|
-
this.updateActiveKeyState(existing, selection, includeBindings);
|
|
535
|
-
}
|
|
536
|
-
if (selection.stop) {
|
|
537
|
-
stopped.add(bindingKey);
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
const materialized = [];
|
|
542
|
-
for (const state of activeKeys.values()) {
|
|
543
|
-
const activeKey = this.materializeActiveKey(state, includeBindings, includeMetadata, focused, activeView);
|
|
544
|
-
if (activeKey) {
|
|
545
|
-
materialized.push(activeKey);
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
return materialized;
|
|
549
|
-
}
|
|
550
|
-
collectActiveKeysFromPending(captures, includeBindings, includeMetadata, focused, activeView) {
|
|
551
|
-
const activeKeys = new Map;
|
|
552
|
-
const stopped = new Set;
|
|
553
|
-
for (const capture of captures) {
|
|
554
|
-
for (const [bindingKey, child] of capture.node.children) {
|
|
555
|
-
if (stopped.has(bindingKey)) {
|
|
556
|
-
continue;
|
|
557
|
-
}
|
|
558
|
-
const selection = this.selectActiveKey(child, includeBindings, focused, activeView);
|
|
559
|
-
if (!selection) {
|
|
560
|
-
continue;
|
|
561
|
-
}
|
|
562
|
-
const existing = activeKeys.get(bindingKey);
|
|
563
|
-
if (!existing) {
|
|
564
|
-
activeKeys.set(bindingKey, this.createActiveKeyState(child.stroke, selection, includeBindings));
|
|
565
|
-
} else {
|
|
566
|
-
this.updateActiveKeyState(existing, selection, includeBindings);
|
|
567
|
-
}
|
|
568
|
-
if (selection.stop) {
|
|
569
|
-
stopped.add(bindingKey);
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
const materialized = [];
|
|
574
|
-
for (const state of activeKeys.values()) {
|
|
575
|
-
const activeKey = this.materializeActiveKey(state, includeBindings, includeMetadata, focused, activeView);
|
|
576
|
-
if (activeKey) {
|
|
577
|
-
materialized.push(activeKey);
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
return materialized;
|
|
581
|
-
}
|
|
582
|
-
selectActiveKey(node, includeBindings, focused, activeView) {
|
|
583
|
-
return node.children.size > 0 ? this.selectPrefixActiveKey(node, includeBindings, focused, activeView) : this.selectExactActiveKey(node, includeBindings, focused, activeView);
|
|
584
|
-
}
|
|
585
|
-
selectPrefixActiveKey(node, includeBindings, focused, activeView) {
|
|
586
|
-
if (!node.stroke) {
|
|
587
|
-
return;
|
|
588
|
-
}
|
|
589
|
-
const reachableBindings = this.collectMatchingBindings(node.reachableBindings, focused, activeView);
|
|
590
|
-
if (reachableBindings.length === 0) {
|
|
591
|
-
return;
|
|
592
|
-
}
|
|
593
|
-
const prefixBindings = this.selectActiveBindings(node.bindings, focused, activeView);
|
|
594
|
-
return {
|
|
595
|
-
...this.getNodePresentation(node, focused, activeView, reachableBindings),
|
|
596
|
-
continues: true,
|
|
597
|
-
firstBinding: prefixBindings?.bindings[0],
|
|
598
|
-
commandBinding: prefixBindings?.commandBinding,
|
|
599
|
-
bindings: includeBindings && prefixBindings && prefixBindings.bindings.length > 0 ? [...prefixBindings.bindings] : undefined,
|
|
600
|
-
stop: true
|
|
601
|
-
};
|
|
602
|
-
}
|
|
603
|
-
selectExactActiveKey(node, includeBindings, focused, activeView) {
|
|
604
|
-
if (!node.stroke) {
|
|
605
|
-
return;
|
|
606
|
-
}
|
|
607
|
-
const selected = this.selectActiveBindings(node.bindings, focused, activeView);
|
|
608
|
-
if (!selected) {
|
|
609
|
-
return;
|
|
610
|
-
}
|
|
611
|
-
let display;
|
|
612
|
-
let tokenName;
|
|
613
|
-
if (selected.bindings.length === 1) {
|
|
614
|
-
const part = selected.bindings[0]?.sequence[node.depth - 1];
|
|
615
|
-
display = part?.display ?? stringifyKeyStroke(node.stroke);
|
|
616
|
-
tokenName = part?.tokenName;
|
|
617
|
-
} else {
|
|
618
|
-
const presentation = this.getNodePresentation(node, focused, activeView, selected.bindings);
|
|
619
|
-
display = presentation.display;
|
|
620
|
-
tokenName = presentation.tokenName;
|
|
621
|
-
}
|
|
622
|
-
return {
|
|
623
|
-
display,
|
|
624
|
-
tokenName,
|
|
625
|
-
continues: false,
|
|
626
|
-
firstBinding: selected.bindings[0],
|
|
627
|
-
commandBinding: selected.commandBinding,
|
|
628
|
-
bindings: includeBindings ? [...selected.bindings] : undefined,
|
|
629
|
-
stop: selected.stop
|
|
630
|
-
};
|
|
631
|
-
}
|
|
632
|
-
selectActiveBindings(bindings, focused, activeView) {
|
|
633
|
-
const selected = [];
|
|
634
|
-
let commandBinding;
|
|
635
|
-
for (const binding of bindings) {
|
|
636
|
-
if (!this.conditions.matchesConditions(binding) || !this.catalog.isBindingVisible(binding, focused, activeView)) {
|
|
637
|
-
continue;
|
|
638
|
-
}
|
|
639
|
-
selected.push(binding);
|
|
640
|
-
if (binding.command === undefined) {
|
|
641
|
-
continue;
|
|
642
|
-
}
|
|
643
|
-
commandBinding ??= binding;
|
|
644
|
-
if (!binding.fallthrough) {
|
|
645
|
-
return { bindings: selected, commandBinding, stop: true };
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
if (selected.length === 0) {
|
|
649
|
-
return;
|
|
650
|
-
}
|
|
651
|
-
return { bindings: selected, commandBinding, stop: false };
|
|
652
|
-
}
|
|
653
|
-
createActiveKeyState(stroke, selection, includeBindings) {
|
|
654
|
-
return {
|
|
655
|
-
stroke,
|
|
656
|
-
display: selection.display,
|
|
657
|
-
tokenName: selection.tokenName,
|
|
658
|
-
continues: selection.continues,
|
|
659
|
-
firstBinding: selection.firstBinding,
|
|
660
|
-
commandBinding: selection.commandBinding,
|
|
661
|
-
bindings: includeBindings && selection.bindings ? [...selection.bindings] : undefined
|
|
662
|
-
};
|
|
663
|
-
}
|
|
664
|
-
updateActiveKeyState(state, selection, includeBindings) {
|
|
665
|
-
if (!state.firstBinding && selection.firstBinding) {
|
|
666
|
-
state.firstBinding = selection.firstBinding;
|
|
667
|
-
}
|
|
668
|
-
if (!state.commandBinding && selection.commandBinding) {
|
|
669
|
-
state.commandBinding = selection.commandBinding;
|
|
670
|
-
}
|
|
671
|
-
if (selection.continues) {
|
|
672
|
-
state.continues = true;
|
|
673
|
-
}
|
|
674
|
-
if (!includeBindings || !selection.bindings || selection.bindings.length === 0) {
|
|
675
|
-
return;
|
|
676
|
-
}
|
|
677
|
-
if (!state.bindings) {
|
|
678
|
-
state.bindings = [...selection.bindings];
|
|
679
|
-
return;
|
|
680
|
-
}
|
|
681
|
-
state.bindings.push(...selection.bindings);
|
|
682
|
-
}
|
|
683
|
-
materializeActiveKey(state, includeBindings, includeMetadata, focused, activeView) {
|
|
684
|
-
if (!state.commandBinding && !state.continues) {
|
|
685
|
-
return;
|
|
686
|
-
}
|
|
687
|
-
const activeKey = {
|
|
688
|
-
stroke: cloneKeyStroke(state.stroke),
|
|
689
|
-
display: state.display,
|
|
690
|
-
continues: state.continues
|
|
691
|
-
};
|
|
692
|
-
if (state.tokenName) {
|
|
693
|
-
activeKey.tokenName = state.tokenName;
|
|
694
|
-
}
|
|
695
|
-
if (state.commandBinding) {
|
|
696
|
-
activeKey.command = state.commandBinding.command;
|
|
697
|
-
}
|
|
698
|
-
if (includeBindings && state.bindings && state.bindings.length > 0) {
|
|
699
|
-
activeKey.bindings = state.bindings.length === 1 ? [this.toActiveBinding(state.bindings[0], focused, activeView)] : this.collectActiveBindings(state.bindings, focused, activeView);
|
|
700
|
-
}
|
|
701
|
-
if (includeMetadata) {
|
|
702
|
-
if (state.firstBinding?.attrs) {
|
|
703
|
-
activeKey.bindingAttrs = state.firstBinding.attrs;
|
|
704
|
-
}
|
|
705
|
-
const commandAttrs = state.commandBinding ? this.catalog.getBindingCommandAttrs(state.commandBinding, focused, activeView) : undefined;
|
|
706
|
-
if (commandAttrs) {
|
|
707
|
-
activeKey.commandAttrs = commandAttrs;
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
return activeKey;
|
|
711
|
-
}
|
|
712
|
-
invalidateCaches() {
|
|
713
|
-
this.state.projection.pendingSequenceCacheVersion = -1;
|
|
714
|
-
this.state.projection.activeKeysPlainCacheVersion = -1;
|
|
715
|
-
this.state.projection.activeKeysBindingsCacheVersion = -1;
|
|
716
|
-
this.state.projection.activeKeysMetadataCacheVersion = -1;
|
|
717
|
-
this.state.projection.activeKeysBindingsAndMetadataCacheVersion = -1;
|
|
718
|
-
}
|
|
719
|
-
notifyPendingSequenceChange() {
|
|
720
|
-
if (!this.hooks.has("pendingSequence")) {
|
|
721
|
-
return;
|
|
722
|
-
}
|
|
723
|
-
this.hooks.emit("pendingSequence", this.state.projection.pendingSequence ? this.collectSequencePartsFromPending(this.state.projection.pendingSequence) : []);
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
// src/schema.ts
|
|
728
|
-
var RESERVED_COMMAND_FIELDS = new Set(["name", "run"]);
|
|
729
|
-
var RESERVED_BINDING_FIELDS = new Set(["key", "cmd", "event", "preventDefault", "fallthrough"]);
|
|
730
|
-
var RESERVED_LAYER_FIELDS = new Set(["target", "targetMode", "priority", "bindings", "commands"]);
|
|
731
|
-
|
|
732
|
-
// src/services/primitives/field-invariants.ts
|
|
733
|
-
function mergeRequirement(target, name, value, source) {
|
|
734
|
-
if (Object.prototype.hasOwnProperty.call(target, name) && !Object.is(target[name], value)) {
|
|
735
|
-
throw new Error(`Conflicting keymap requirement for "${name}" from ${source}`);
|
|
736
|
-
}
|
|
737
|
-
target[name] = value;
|
|
738
|
-
}
|
|
739
|
-
function mergeAttribute(target, name, value, source) {
|
|
740
|
-
if (Object.prototype.hasOwnProperty.call(target, name) && !Object.is(target[name], value)) {
|
|
741
|
-
throw new Error(`Conflicting keymap attribute for "${name}" from ${source}`);
|
|
742
|
-
}
|
|
743
|
-
target[name] = value;
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
// src/services/values.ts
|
|
747
|
-
function isPlainObject(value) {
|
|
748
|
-
const prototype = Object.getPrototypeOf(value);
|
|
749
|
-
return prototype === Object.prototype || prototype === null;
|
|
750
|
-
}
|
|
751
|
-
function getErrorMessage(error, fallback) {
|
|
752
|
-
if (error instanceof Error && error.message) {
|
|
753
|
-
return error.message;
|
|
754
|
-
}
|
|
755
|
-
return fallback;
|
|
756
|
-
}
|
|
757
|
-
function isPromiseLike(value) {
|
|
758
|
-
if (!value) {
|
|
759
|
-
return false;
|
|
760
|
-
}
|
|
761
|
-
if (typeof value !== "object" && typeof value !== "function") {
|
|
762
|
-
return false;
|
|
763
|
-
}
|
|
764
|
-
return typeof value.then === "function";
|
|
765
|
-
}
|
|
766
|
-
function snapshotDataValue(value, options) {
|
|
767
|
-
const deep = options?.deep === true;
|
|
768
|
-
const freeze = options?.freeze === true;
|
|
769
|
-
const preserveNonPlainObjects = options?.preserveNonPlainObjects === true;
|
|
770
|
-
if (Array.isArray(value)) {
|
|
771
|
-
const cloned = deep ? value.map((entry) => snapshotDataValue(entry, options)) : [...value];
|
|
772
|
-
return freeze ? Object.freeze(cloned) : cloned;
|
|
773
|
-
}
|
|
774
|
-
if (value && typeof value === "object") {
|
|
775
|
-
if (preserveNonPlainObjects && !isPlainObject(value)) {
|
|
776
|
-
return value;
|
|
777
|
-
}
|
|
778
|
-
const cloned = {};
|
|
779
|
-
for (const [key, entry] of Object.entries(value)) {
|
|
780
|
-
cloned[key] = deep ? snapshotDataValue(entry, options) : entry;
|
|
781
|
-
}
|
|
782
|
-
return freeze ? Object.freeze(cloned) : cloned;
|
|
783
|
-
}
|
|
784
|
-
return value;
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
// src/services/command-catalog.ts
|
|
788
|
-
var DEFAULT_COMMAND_SEARCH_FIELDS = ["name"];
|
|
789
|
-
var SNAPSHOT_COMMAND_METADATA_OPTIONS = Object.freeze({
|
|
790
|
-
deep: true,
|
|
791
|
-
preserveNonPlainObjects: true
|
|
792
|
-
});
|
|
793
|
-
var SNAPSHOT_FROZEN_COMMAND_METADATA_OPTIONS = Object.freeze({
|
|
794
|
-
deep: true,
|
|
795
|
-
freeze: true,
|
|
796
|
-
preserveNonPlainObjects: true
|
|
797
|
-
});
|
|
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
|
-
}
|
|
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
|
-
|
|
830
|
-
class CommandCatalogService {
|
|
831
|
-
state;
|
|
832
|
-
host;
|
|
833
|
-
notify;
|
|
834
|
-
conditions;
|
|
835
|
-
options;
|
|
836
|
-
constructor(state, host, notify, conditions, options) {
|
|
837
|
-
this.state = state;
|
|
838
|
-
this.host = host;
|
|
839
|
-
this.notify = notify;
|
|
840
|
-
this.conditions = conditions;
|
|
841
|
-
this.options = options;
|
|
842
|
-
}
|
|
843
|
-
normalizeCommands(commands) {
|
|
844
|
-
return normalizeRegisteredCommands({
|
|
845
|
-
commands,
|
|
846
|
-
commandFields: this.state.environment.commandFields,
|
|
847
|
-
conditions: this.conditions,
|
|
848
|
-
onError: (code, error, message) => {
|
|
849
|
-
this.notify.emitError(code, error, message);
|
|
850
|
-
}
|
|
851
|
-
});
|
|
852
|
-
}
|
|
853
|
-
prependCommandResolver(resolver) {
|
|
854
|
-
return this.mutateCommandResolvers(() => this.state.commands.commandResolvers.prepend(resolver), resolver);
|
|
855
|
-
}
|
|
856
|
-
appendCommandResolver(resolver) {
|
|
857
|
-
return this.mutateCommandResolvers(() => this.state.commands.commandResolvers.append(resolver), resolver);
|
|
858
|
-
}
|
|
859
|
-
clearCommandResolvers() {
|
|
860
|
-
if (!this.state.commands.commandResolvers.has()) {
|
|
861
|
-
return;
|
|
862
|
-
}
|
|
863
|
-
this.notify.runWithStateChangeBatch(() => {
|
|
864
|
-
this.state.commands.commandResolvers.clear();
|
|
865
|
-
this.state.commands.commandMetadataVersion += 1;
|
|
866
|
-
this.options.onCommandResolversChanged();
|
|
867
|
-
this.notify.queueStateChange();
|
|
868
|
-
});
|
|
869
|
-
}
|
|
870
|
-
getCommands(query) {
|
|
871
|
-
return this.getFilteredCommandEntries(query).map((entry) => getRegisteredCommandRecord(entry.command));
|
|
872
|
-
}
|
|
873
|
-
getCommandEntries(query) {
|
|
874
|
-
const context = this.getCommandQueryContext(query);
|
|
875
|
-
const filteredEntries = this.getFilteredCommandEntries(query, context);
|
|
876
|
-
if (filteredEntries.length === 0) {
|
|
877
|
-
return [];
|
|
878
|
-
}
|
|
879
|
-
const grouped = filteredEntries.map((entry) => ({
|
|
880
|
-
entry,
|
|
881
|
-
command: getRegisteredCommandRecord(entry.command),
|
|
882
|
-
bindings: []
|
|
883
|
-
}));
|
|
884
|
-
const indexesByName = new Map;
|
|
885
|
-
for (const [index, item] of grouped.entries()) {
|
|
886
|
-
const existing = indexesByName.get(item.command.name);
|
|
887
|
-
if (existing) {
|
|
888
|
-
existing.push(index);
|
|
889
|
-
} else {
|
|
890
|
-
indexesByName.set(item.command.name, [index]);
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
if (indexesByName.size > 0) {
|
|
894
|
-
this.collectCommandEntryBindings(grouped, indexesByName, context);
|
|
895
|
-
}
|
|
896
|
-
return grouped.map((item) => ({
|
|
897
|
-
command: item.command,
|
|
898
|
-
bindings: item.bindings
|
|
899
|
-
}));
|
|
900
|
-
}
|
|
901
|
-
getResolvedCommandChain(command, focused, includeRecord) {
|
|
902
|
-
const view = this.getActiveCommandView(focused);
|
|
903
|
-
const entries = this.getResolvedCommandChainFromView(view, command, focused, includeRecord, "active", view.chainsByName.get(command));
|
|
904
|
-
const hadError = (includeRecord ? view.fallbackWithRecordErrors : view.fallbackWithoutRecordErrors).has(command);
|
|
905
|
-
return { entries, hadError };
|
|
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
|
-
}
|
|
938
|
-
getCommandAttrs(command, focused) {
|
|
939
|
-
const top = this.getTopResolvedCommand(command, focused, false);
|
|
940
|
-
return top?.resolved.attrs;
|
|
941
|
-
}
|
|
942
|
-
getTopCommandRecord(command, focused) {
|
|
943
|
-
const top = this.getTopResolvedCommand(command, focused, true);
|
|
944
|
-
return top?.resolved.record;
|
|
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
|
-
}
|
|
976
|
-
getActiveCommandView(focused) {
|
|
977
|
-
const currentFocused = getFocusedTargetIfAvailable(this.host);
|
|
978
|
-
const derivedStateVersion = this.state.notify.derivedStateVersion;
|
|
979
|
-
if (focused === currentFocused && this.state.commands.activeCommandViewVersion === derivedStateVersion && this.state.commands.activeCommandView?.cacheable) {
|
|
980
|
-
return this.state.commands.activeCommandView;
|
|
981
|
-
}
|
|
982
|
-
const entries = [];
|
|
983
|
-
const reachable = [];
|
|
984
|
-
const reachableByName = new Map;
|
|
985
|
-
const chainsByName = new Map;
|
|
986
|
-
let cacheable = true;
|
|
987
|
-
if (this.state.layers.layersWithCommands > 0) {
|
|
988
|
-
for (const layer of getActiveLayersForFocused(this.state.layers, this.host, focused)) {
|
|
989
|
-
if (layer.commands.length === 0 || !this.conditions.layerMatchesRuntimeState(layer)) {
|
|
990
|
-
continue;
|
|
991
|
-
}
|
|
992
|
-
if (layer.hasUnkeyedMatchers) {
|
|
993
|
-
cacheable = false;
|
|
994
|
-
}
|
|
995
|
-
for (const command of layer.commands) {
|
|
996
|
-
if (command.hasUnkeyedMatchers) {
|
|
997
|
-
cacheable = false;
|
|
998
|
-
}
|
|
999
|
-
if (!this.conditions.matchesConditions(command)) {
|
|
1000
|
-
continue;
|
|
1001
|
-
}
|
|
1002
|
-
const entry = { layer, command };
|
|
1003
|
-
entries.push(entry);
|
|
1004
|
-
const existing = chainsByName.get(command.name);
|
|
1005
|
-
if (existing) {
|
|
1006
|
-
existing.push(entry);
|
|
1007
|
-
} else {
|
|
1008
|
-
chainsByName.set(command.name, [entry]);
|
|
1009
|
-
}
|
|
1010
|
-
if (!reachableByName.has(command.name)) {
|
|
1011
|
-
reachableByName.set(command.name, entry);
|
|
1012
|
-
reachable.push(entry);
|
|
1013
|
-
}
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1016
|
-
}
|
|
1017
|
-
const view = {
|
|
1018
|
-
cacheable,
|
|
1019
|
-
entries,
|
|
1020
|
-
reachable,
|
|
1021
|
-
reachableByName,
|
|
1022
|
-
chainsByName,
|
|
1023
|
-
...createCommandChainCacheState()
|
|
1024
|
-
};
|
|
1025
|
-
if (focused === currentFocused && view.cacheable) {
|
|
1026
|
-
this.state.commands.activeCommandViewVersion = derivedStateVersion;
|
|
1027
|
-
this.state.commands.activeCommandView = view;
|
|
1028
|
-
}
|
|
1029
|
-
return view;
|
|
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
|
-
}
|
|
1062
|
-
isBindingVisible(binding, focused, activeView) {
|
|
1063
|
-
if (binding.command === undefined || binding.run) {
|
|
1064
|
-
return true;
|
|
1065
|
-
}
|
|
1066
|
-
if (typeof binding.command !== "string") {
|
|
1067
|
-
return false;
|
|
1068
|
-
}
|
|
1069
|
-
if (activeView.reachableByName.has(binding.command)) {
|
|
1070
|
-
return true;
|
|
1071
|
-
}
|
|
1072
|
-
return this.getFallbackResolvedCommand(activeView, binding.command, focused, false, "active") !== undefined;
|
|
1073
|
-
}
|
|
1074
|
-
getBindingCommandAttrs(binding, focused, activeView) {
|
|
1075
|
-
if (typeof binding.command !== "string") {
|
|
1076
|
-
return;
|
|
1077
|
-
}
|
|
1078
|
-
const active = activeView.reachableByName.get(binding.command);
|
|
1079
|
-
if (active) {
|
|
1080
|
-
return active.command.attrs;
|
|
1081
|
-
}
|
|
1082
|
-
const fallback = this.getFallbackResolvedCommand(activeView, binding.command, focused, false, "active");
|
|
1083
|
-
return fallback?.resolved.attrs;
|
|
1084
|
-
}
|
|
1085
|
-
getCommandResolutionStatus(command, layerCommands) {
|
|
1086
|
-
if (layerCommands?.has(command) || this.state.commands.registeredNames.has(command)) {
|
|
1087
|
-
return "resolved";
|
|
1088
|
-
}
|
|
1089
|
-
const lookup = this.resolveCommandWithResolvers(command, getFocusedTargetIfAvailable(this.host));
|
|
1090
|
-
if (lookup.resolved || lookup.hadError) {
|
|
1091
|
-
return lookup.resolved ? "resolved" : "error";
|
|
1092
|
-
}
|
|
1093
|
-
return "unresolved";
|
|
1094
|
-
}
|
|
1095
|
-
mutateCommandResolvers(register, resolver) {
|
|
1096
|
-
return this.notify.runWithStateChangeBatch(() => {
|
|
1097
|
-
const off = register();
|
|
1098
|
-
this.state.commands.commandMetadataVersion += 1;
|
|
1099
|
-
this.options.onCommandResolversChanged();
|
|
1100
|
-
this.notify.queueStateChange();
|
|
1101
|
-
return () => {
|
|
1102
|
-
this.notify.runWithStateChangeBatch(() => {
|
|
1103
|
-
off();
|
|
1104
|
-
if (this.state.commands.commandResolvers.values().includes(resolver)) {
|
|
1105
|
-
return;
|
|
1106
|
-
}
|
|
1107
|
-
this.state.commands.commandMetadataVersion += 1;
|
|
1108
|
-
this.options.onCommandResolversChanged();
|
|
1109
|
-
this.notify.queueStateChange();
|
|
1110
|
-
});
|
|
1111
|
-
};
|
|
1112
|
-
});
|
|
1113
|
-
}
|
|
1114
|
-
getTopResolvedCommand(command, focused, includeRecord) {
|
|
1115
|
-
const activeView = this.getActiveCommandView(focused);
|
|
1116
|
-
const active = activeView.reachableByName.get(command);
|
|
1117
|
-
if (active) {
|
|
1118
|
-
return {
|
|
1119
|
-
target: active.layer.target,
|
|
1120
|
-
resolved: resolveRegisteredCommand(active.command, { includeRecord })
|
|
1121
|
-
};
|
|
1122
|
-
}
|
|
1123
|
-
return this.getFallbackResolvedCommand(activeView, command, focused, includeRecord, "active");
|
|
1124
|
-
}
|
|
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;
|
|
1132
|
-
if (cache.has(command)) {
|
|
1133
|
-
const cached = cache.get(command);
|
|
1134
|
-
return cached ? { resolved: cached } : undefined;
|
|
1135
|
-
}
|
|
1136
|
-
const lookup = this.resolveCommandWithResolvers(command, focused, { includeRecord, mode });
|
|
1137
|
-
cache.set(command, lookup.resolved ?? null);
|
|
1138
|
-
if (lookup.hadError) {
|
|
1139
|
-
errorCache.add(command);
|
|
1140
|
-
}
|
|
1141
|
-
if (!lookup.resolved) {
|
|
1142
|
-
return;
|
|
1143
|
-
}
|
|
1144
|
-
return { resolved: lookup.resolved };
|
|
1145
|
-
}
|
|
1146
|
-
getResolvedCommandChainFromView(view, command, focused, includeRecord, mode, activeChain) {
|
|
1147
|
-
const cache = includeRecord ? view.resolvedWithRecordChains : view.resolvedWithoutRecordChains;
|
|
1148
|
-
const cached = cache.get(command);
|
|
1149
|
-
if (cached) {
|
|
1150
|
-
return cached.length > 0 ? cached : undefined;
|
|
1151
|
-
}
|
|
1152
|
-
const resolved = [];
|
|
1153
|
-
const chain = activeChain;
|
|
1154
|
-
if (chain) {
|
|
1155
|
-
for (const entry of chain) {
|
|
1156
|
-
resolved.push({
|
|
1157
|
-
target: entry.layer.target,
|
|
1158
|
-
resolved: resolveRegisteredCommand(entry.command, { includeRecord })
|
|
1159
|
-
});
|
|
1160
|
-
}
|
|
1161
|
-
}
|
|
1162
|
-
const fallback = this.getFallbackResolvedCommand(view, command, focused, includeRecord, mode);
|
|
1163
|
-
if (fallback) {
|
|
1164
|
-
resolved.push(fallback);
|
|
1165
|
-
}
|
|
1166
|
-
cache.set(command, resolved);
|
|
1167
|
-
return resolved.length > 0 ? resolved : undefined;
|
|
1168
|
-
}
|
|
1169
|
-
getRegisteredLayerCommandEntries() {
|
|
1170
|
-
const cacheVersion = this.state.commands.commandMetadataVersion;
|
|
1171
|
-
if (this.state.commands.registeredCommandEntriesCacheVersion === cacheVersion) {
|
|
1172
|
-
return this.state.commands.registeredCommandEntriesCache;
|
|
1173
|
-
}
|
|
1174
|
-
const layers = [...this.state.layers.layers];
|
|
1175
|
-
layers.sort((left, right) => left.order - right.order);
|
|
1176
|
-
const entries = [];
|
|
1177
|
-
for (const layer of layers) {
|
|
1178
|
-
for (const command of layer.commands) {
|
|
1179
|
-
entries.push({ layer, command });
|
|
1180
|
-
}
|
|
1181
|
-
}
|
|
1182
|
-
this.state.commands.registeredCommandEntriesCacheVersion = cacheVersion;
|
|
1183
|
-
this.state.commands.registeredCommandEntriesCache = entries;
|
|
1184
|
-
return entries;
|
|
1185
|
-
}
|
|
1186
|
-
getCommandQueryContext(query) {
|
|
1187
|
-
const visibility = query?.visibility ?? "reachable";
|
|
1188
|
-
const focused = query && Object.prototype.hasOwnProperty.call(query, "focused") ? query.focused ?? null : getFocusedTargetIfAvailable(this.host);
|
|
1189
|
-
if (visibility === "registered") {
|
|
1190
|
-
return { visibility, focused };
|
|
1191
|
-
}
|
|
1192
|
-
return {
|
|
1193
|
-
visibility,
|
|
1194
|
-
focused,
|
|
1195
|
-
activeView: this.getActiveCommandView(focused)
|
|
1196
|
-
};
|
|
1197
|
-
}
|
|
1198
|
-
getFilteredCommandEntries(query, context = this.getCommandQueryContext(query)) {
|
|
1199
|
-
let entries;
|
|
1200
|
-
if (context.visibility === "registered") {
|
|
1201
|
-
entries = this.getRegisteredLayerCommandEntries();
|
|
1202
|
-
} else if (context.visibility === "active") {
|
|
1203
|
-
entries = context.activeView?.entries ?? [];
|
|
1204
|
-
} else {
|
|
1205
|
-
entries = context.activeView?.reachable ?? [];
|
|
1206
|
-
}
|
|
1207
|
-
return queryLayerCommandEntries({
|
|
1208
|
-
entries,
|
|
1209
|
-
query,
|
|
1210
|
-
getCommandRecord: (command) => getRegisteredCommandRecord(command),
|
|
1211
|
-
onFilterError: (error) => {
|
|
1212
|
-
this.notify.emitError("command-query-filter-error", error, "[Keymap] Error in command query filter:");
|
|
1213
|
-
}
|
|
1214
|
-
});
|
|
1215
|
-
}
|
|
1216
|
-
collectCommandEntryBindings(grouped, indexesByName, context) {
|
|
1217
|
-
if (context.visibility === "registered") {
|
|
1218
|
-
const layers = [...this.state.layers.layers];
|
|
1219
|
-
layers.sort((left, right) => left.order - right.order);
|
|
1220
|
-
for (const layer of layers) {
|
|
1221
|
-
for (const binding of layer.compiledBindings) {
|
|
1222
|
-
this.collectBindingForCommandEntries(grouped, indexesByName, binding);
|
|
1223
|
-
}
|
|
1224
|
-
}
|
|
1225
|
-
return;
|
|
1226
|
-
}
|
|
1227
|
-
const activeView = context.activeView;
|
|
1228
|
-
if (!activeView) {
|
|
1229
|
-
return;
|
|
1230
|
-
}
|
|
1231
|
-
for (const layer of getActiveLayersForFocused(this.state.layers, this.host, context.focused)) {
|
|
1232
|
-
if (layer.compiledBindings.length === 0 || !this.conditions.layerMatchesRuntimeState(layer)) {
|
|
1233
|
-
continue;
|
|
1234
|
-
}
|
|
1235
|
-
for (const binding of layer.compiledBindings) {
|
|
1236
|
-
if (!this.conditions.matchesConditions(binding) || !this.isBindingVisible(binding, context.focused, activeView)) {
|
|
1237
|
-
continue;
|
|
1238
|
-
}
|
|
1239
|
-
this.collectBindingForCommandEntries(grouped, indexesByName, binding);
|
|
1240
|
-
}
|
|
1241
|
-
}
|
|
1242
|
-
}
|
|
1243
|
-
collectBindingForCommandEntries(grouped, indexesByName, binding) {
|
|
1244
|
-
if (typeof binding.command !== "string") {
|
|
1245
|
-
return;
|
|
1246
|
-
}
|
|
1247
|
-
const indexes = indexesByName.get(binding.command);
|
|
1248
|
-
if (!indexes || indexes.length === 0) {
|
|
1249
|
-
return;
|
|
1250
|
-
}
|
|
1251
|
-
for (const index of indexes) {
|
|
1252
|
-
const item = grouped[index];
|
|
1253
|
-
if (!item) {
|
|
1254
|
-
continue;
|
|
1255
|
-
}
|
|
1256
|
-
item.bindings.push({
|
|
1257
|
-
sequence: binding.sequence,
|
|
1258
|
-
command: binding.command,
|
|
1259
|
-
commandAttrs: item.command.attrs,
|
|
1260
|
-
attrs: binding.attrs,
|
|
1261
|
-
event: binding.event,
|
|
1262
|
-
preventDefault: binding.preventDefault,
|
|
1263
|
-
fallthrough: binding.fallthrough
|
|
1264
|
-
});
|
|
1265
|
-
}
|
|
1266
|
-
}
|
|
1267
|
-
resolveCommandWithResolvers(command, focused, options) {
|
|
1268
|
-
const includeRecord = options?.includeRecord === true;
|
|
1269
|
-
const context = this.createCommandResolverContext(includeRecord, focused, options?.mode ?? "active");
|
|
1270
|
-
return resolveCommandWithResolvers(command, this.state.commands.commandResolvers.values(), context, (error) => {
|
|
1271
|
-
this.notify.emitError("command-resolver-error", error, `[Keymap] Error in command resolver for "${command}":`);
|
|
1272
|
-
});
|
|
1273
|
-
}
|
|
1274
|
-
createCommandResolverContext(includeRecord, focused, mode) {
|
|
1275
|
-
return {
|
|
1276
|
-
getCommandAttrs: (name) => {
|
|
1277
|
-
if (mode === "registered") {
|
|
1278
|
-
return this.getTopRegisteredCommand(name)?.command.attrs;
|
|
1279
|
-
}
|
|
1280
|
-
return this.getCommandAttrs(name, focused);
|
|
1281
|
-
},
|
|
1282
|
-
getCommandRecord: (name) => {
|
|
1283
|
-
if (!includeRecord) {
|
|
1284
|
-
return;
|
|
1285
|
-
}
|
|
1286
|
-
if (mode === "registered") {
|
|
1287
|
-
return this.getTopRegisteredCommandRecord(name);
|
|
1288
|
-
}
|
|
1289
|
-
return this.getTopCommandRecord(name, focused);
|
|
1290
|
-
}
|
|
1291
|
-
};
|
|
1292
|
-
}
|
|
1293
|
-
}
|
|
1294
|
-
function getRegisteredCommandRecord(command) {
|
|
1295
|
-
if (command.record) {
|
|
1296
|
-
return command.record;
|
|
1297
|
-
}
|
|
1298
|
-
let fields = EMPTY_COMMAND_FIELDS;
|
|
1299
|
-
if (command.fields !== EMPTY_COMMAND_FIELDS && Object.keys(command.fields).length > 0) {
|
|
1300
|
-
fields = snapshotDataValue(command.fields, SNAPSHOT_FROZEN_COMMAND_METADATA_OPTIONS);
|
|
1301
|
-
}
|
|
1302
|
-
const record = command.attrs ? Object.freeze({
|
|
1303
|
-
name: command.name,
|
|
1304
|
-
fields,
|
|
1305
|
-
attrs: snapshotDataValue(command.attrs, SNAPSHOT_FROZEN_COMMAND_METADATA_OPTIONS)
|
|
1306
|
-
}) : Object.freeze({
|
|
1307
|
-
name: command.name,
|
|
1308
|
-
fields
|
|
1309
|
-
});
|
|
1310
|
-
command.record = record;
|
|
1311
|
-
return record;
|
|
1312
|
-
}
|
|
1313
|
-
function resolveRegisteredCommand(command, options) {
|
|
1314
|
-
const includeRecord = options?.includeRecord === true;
|
|
1315
|
-
if (includeRecord) {
|
|
1316
|
-
const existing2 = command.resolvedWithRecord;
|
|
1317
|
-
if (existing2) {
|
|
1318
|
-
return existing2;
|
|
1319
|
-
}
|
|
1320
|
-
const resolved2 = {
|
|
1321
|
-
run: createRegisteredCommandRunner(command)
|
|
1322
|
-
};
|
|
1323
|
-
if (command.attrs) {
|
|
1324
|
-
resolved2.attrs = command.attrs;
|
|
1325
|
-
}
|
|
1326
|
-
resolved2.record = getRegisteredCommandRecord(command);
|
|
1327
|
-
command.resolvedWithRecord = resolved2;
|
|
1328
|
-
return resolved2;
|
|
1329
|
-
}
|
|
1330
|
-
const existing = command.resolved;
|
|
1331
|
-
if (existing) {
|
|
1332
|
-
return existing;
|
|
1333
|
-
}
|
|
1334
|
-
const resolved = {
|
|
1335
|
-
run: createRegisteredCommandRunner(command)
|
|
1336
|
-
};
|
|
1337
|
-
if (command.attrs) {
|
|
1338
|
-
resolved.attrs = command.attrs;
|
|
1339
|
-
}
|
|
1340
|
-
command.resolved = resolved;
|
|
1341
|
-
return resolved;
|
|
1342
|
-
}
|
|
1343
|
-
function normalizeRegisteredCommands(options) {
|
|
1344
|
-
const normalizedCommands = [];
|
|
1345
|
-
const seen = new Set;
|
|
1346
|
-
for (const command of options.commands) {
|
|
1347
|
-
let normalizedCommand;
|
|
1348
|
-
try {
|
|
1349
|
-
const mergedAttrs = {};
|
|
1350
|
-
const mergedFields = {};
|
|
1351
|
-
const mergedRequires = {};
|
|
1352
|
-
const matchers = [];
|
|
1353
|
-
const conditionKeys = new Set;
|
|
1354
|
-
let hasUnkeyedMatchers = false;
|
|
1355
|
-
const normalizedName = normalizeCommandName(command.name);
|
|
1356
|
-
if (seen.has(normalizedName)) {
|
|
1357
|
-
options.onError("duplicate-command", { command: normalizedName }, `Duplicate keymap command "${normalizedName}" in the same layer`);
|
|
1358
|
-
continue;
|
|
1359
|
-
}
|
|
1360
|
-
for (const [fieldName, value] of Object.entries(command)) {
|
|
1361
|
-
if (RESERVED_COMMAND_FIELDS.has(fieldName) || value === undefined) {
|
|
1362
|
-
continue;
|
|
1363
|
-
}
|
|
1364
|
-
mergedFields[fieldName] = snapshotDataValue(value, SNAPSHOT_COMMAND_METADATA_OPTIONS);
|
|
1365
|
-
const compiler = options.commandFields.get(fieldName);
|
|
1366
|
-
if (!compiler) {
|
|
1367
|
-
continue;
|
|
1368
|
-
}
|
|
1369
|
-
compiler(value, createCommandFieldContext(mergedAttrs, mergedRequires, conditionKeys, matchers, options.conditions, fieldName, {
|
|
1370
|
-
onUnkeyedMatcher() {
|
|
1371
|
-
hasUnkeyedMatchers = true;
|
|
1372
|
-
}
|
|
1373
|
-
}));
|
|
1374
|
-
}
|
|
1375
|
-
const attrs = Object.keys(mergedAttrs).length === 0 ? undefined : Object.freeze(mergedAttrs);
|
|
1376
|
-
const fields = Object.keys(mergedFields).length === 0 ? EMPTY_COMMAND_FIELDS : Object.freeze(mergedFields);
|
|
1377
|
-
normalizedCommand = {
|
|
1378
|
-
name: normalizedName,
|
|
1379
|
-
fields,
|
|
1380
|
-
run: command.run,
|
|
1381
|
-
requires: Object.entries(mergedRequires),
|
|
1382
|
-
matchers,
|
|
1383
|
-
conditionKeys: [...conditionKeys],
|
|
1384
|
-
hasUnkeyedMatchers,
|
|
1385
|
-
matchCacheDirty: true
|
|
1386
|
-
};
|
|
1387
|
-
if (attrs) {
|
|
1388
|
-
normalizedCommand.attrs = attrs;
|
|
1389
|
-
}
|
|
1390
|
-
} catch (error) {
|
|
1391
|
-
options.onError("register-command-failed", error, getErrorMessage(error, `Failed to register keymap command "${String(command.name)}"`));
|
|
1392
|
-
continue;
|
|
1393
|
-
}
|
|
1394
|
-
seen.add(normalizedCommand.name);
|
|
1395
|
-
normalizedCommands.push(normalizedCommand);
|
|
1396
|
-
}
|
|
1397
|
-
return normalizedCommands;
|
|
1398
|
-
}
|
|
1399
|
-
function createCommandFieldContext(mergedAttrs, mergedRequires, conditionKeys, matchers, conditions, fieldName, options) {
|
|
1400
|
-
return {
|
|
1401
|
-
require(name, requiredValue) {
|
|
1402
|
-
mergeRequirement(mergedRequires, name, requiredValue, `field ${fieldName}`);
|
|
1403
|
-
conditionKeys.add(name);
|
|
1404
|
-
},
|
|
1405
|
-
attr(name, attributeValue) {
|
|
1406
|
-
mergeAttribute(mergedAttrs, name, snapshotDataValue(attributeValue, SNAPSHOT_COMMAND_METADATA_OPTIONS), `field ${fieldName}`);
|
|
1407
|
-
},
|
|
1408
|
-
activeWhen(matcher) {
|
|
1409
|
-
const runtimeMatcher = conditions.buildRuntimeMatcher(matcher, `field ${fieldName}`);
|
|
1410
|
-
if (!runtimeMatcher.cacheable) {
|
|
1411
|
-
options.onUnkeyedMatcher();
|
|
1412
|
-
}
|
|
1413
|
-
matchers.push(runtimeMatcher);
|
|
1414
|
-
}
|
|
1415
|
-
};
|
|
1416
|
-
}
|
|
1417
|
-
function createRegisteredCommandRunner(command) {
|
|
1418
|
-
if (command.runner) {
|
|
1419
|
-
return command.runner;
|
|
1420
|
-
}
|
|
1421
|
-
const runner = (ctx) => {
|
|
1422
|
-
return command.run({
|
|
1423
|
-
...ctx,
|
|
1424
|
-
command: getRegisteredCommandRecord(command)
|
|
1425
|
-
});
|
|
1426
|
-
};
|
|
1427
|
-
command.runner = runner;
|
|
1428
|
-
return runner;
|
|
1429
|
-
}
|
|
1430
|
-
function resolveCommandWithResolvers(command, resolvers, context, onResolverError) {
|
|
1431
|
-
if (resolvers.length === 0) {
|
|
1432
|
-
return { hadError: false };
|
|
1433
|
-
}
|
|
1434
|
-
let hadError = false;
|
|
1435
|
-
for (const resolver of resolvers) {
|
|
1436
|
-
let resolved;
|
|
1437
|
-
try {
|
|
1438
|
-
resolved = resolver(command, context);
|
|
1439
|
-
} catch (error) {
|
|
1440
|
-
hadError = true;
|
|
1441
|
-
onResolverError(error);
|
|
1442
|
-
continue;
|
|
1443
|
-
}
|
|
1444
|
-
if (resolved) {
|
|
1445
|
-
return { hadError, resolved };
|
|
1446
|
-
}
|
|
1447
|
-
}
|
|
1448
|
-
return { hadError };
|
|
1449
|
-
}
|
|
1450
|
-
function queryLayerCommandEntries(options) {
|
|
1451
|
-
const namespace = options.query?.namespace;
|
|
1452
|
-
const normalizedSearch = options.query?.search?.trim().toLowerCase() ?? "";
|
|
1453
|
-
let searchKeys = DEFAULT_COMMAND_SEARCH_FIELDS;
|
|
1454
|
-
if (options.query?.searchIn && options.query.searchIn.length > 0) {
|
|
1455
|
-
searchKeys = options.query.searchIn;
|
|
1456
|
-
}
|
|
1457
|
-
const filter = options.query?.filter;
|
|
1458
|
-
let filterEntries;
|
|
1459
|
-
let filterPredicate;
|
|
1460
|
-
if (typeof filter === "function") {
|
|
1461
|
-
filterPredicate = filter;
|
|
1462
|
-
} else if (filter) {
|
|
1463
|
-
filterEntries = Object.entries(filter);
|
|
1464
|
-
}
|
|
1465
|
-
const results = [];
|
|
1466
|
-
for (const entry of options.entries) {
|
|
1467
|
-
const command = entry.command;
|
|
1468
|
-
if (!commandMatchesNamespace(command, namespace)) {
|
|
1469
|
-
continue;
|
|
1470
|
-
}
|
|
1471
|
-
if (!commandMatchesSearch(command, normalizedSearch, searchKeys)) {
|
|
1472
|
-
continue;
|
|
1473
|
-
}
|
|
1474
|
-
if (!commandMatchesFilters(command, filterEntries, options)) {
|
|
1475
|
-
continue;
|
|
1476
|
-
}
|
|
1477
|
-
const record = options.getCommandRecord(command);
|
|
1478
|
-
if (filterPredicate) {
|
|
1479
|
-
let matches = false;
|
|
1480
|
-
try {
|
|
1481
|
-
matches = filterPredicate(record);
|
|
1482
|
-
} catch (error) {
|
|
1483
|
-
options.onFilterError(error);
|
|
1484
|
-
continue;
|
|
1485
|
-
}
|
|
1486
|
-
if (!matches) {
|
|
1487
|
-
continue;
|
|
1488
|
-
}
|
|
1489
|
-
}
|
|
1490
|
-
results.push(entry);
|
|
1491
|
-
}
|
|
1492
|
-
return results;
|
|
1493
|
-
}
|
|
1494
|
-
function commandMatchesSearch(command, search, searchKeys) {
|
|
1495
|
-
if (!search) {
|
|
1496
|
-
return true;
|
|
1497
|
-
}
|
|
1498
|
-
for (const key of searchKeys) {
|
|
1499
|
-
if (commandKeyMatchesSearch(command, key, search)) {
|
|
1500
|
-
return true;
|
|
1501
|
-
}
|
|
1502
|
-
}
|
|
1503
|
-
return false;
|
|
1504
|
-
}
|
|
1505
|
-
function commandMatchesNamespace(command, namespace) {
|
|
1506
|
-
if (namespace === undefined) {
|
|
1507
|
-
return true;
|
|
1508
|
-
}
|
|
1509
|
-
if (!Object.prototype.hasOwnProperty.call(command.fields, "namespace")) {
|
|
1510
|
-
return false;
|
|
1511
|
-
}
|
|
1512
|
-
return commandValueMatchesFilter(command.fields.namespace, namespace);
|
|
1513
|
-
}
|
|
1514
|
-
function commandMatchesFilters(command, filters, options) {
|
|
1515
|
-
if (!filters) {
|
|
1516
|
-
return true;
|
|
1517
|
-
}
|
|
1518
|
-
for (const [key, matcher] of filters) {
|
|
1519
|
-
if (!commandKeyMatchesQuery(command, key, matcher, options)) {
|
|
1520
|
-
return false;
|
|
1521
|
-
}
|
|
1522
|
-
}
|
|
1523
|
-
return true;
|
|
1524
|
-
}
|
|
1525
|
-
function commandKeyMatchesSearch(command, key, search) {
|
|
1526
|
-
if (key === "name" && commandValueMatchesSearch(command.name, search)) {
|
|
1527
|
-
return true;
|
|
1528
|
-
}
|
|
1529
|
-
if (Object.prototype.hasOwnProperty.call(command.fields, key) && commandValueMatchesSearch(command.fields[key], search)) {
|
|
1530
|
-
return true;
|
|
1531
|
-
}
|
|
1532
|
-
if (command.attrs && Object.prototype.hasOwnProperty.call(command.attrs, key)) {
|
|
1533
|
-
return commandValueMatchesSearch(command.attrs[key], search);
|
|
1534
|
-
}
|
|
1535
|
-
return false;
|
|
1536
|
-
}
|
|
1537
|
-
function commandKeyMatchesQuery(command, key, matcher, options) {
|
|
1538
|
-
if (typeof matcher === "function") {
|
|
1539
|
-
let record;
|
|
1540
|
-
const getRecord = () => {
|
|
1541
|
-
if (!record) {
|
|
1542
|
-
record = options.getCommandRecord(command);
|
|
1543
|
-
}
|
|
1544
|
-
return record;
|
|
1545
|
-
};
|
|
1546
|
-
let foundValue = false;
|
|
1547
|
-
if (key === "name") {
|
|
1548
|
-
foundValue = true;
|
|
1549
|
-
try {
|
|
1550
|
-
if (matcher(command.name, getRecord())) {
|
|
1551
|
-
return true;
|
|
1552
|
-
}
|
|
1553
|
-
} catch (error) {
|
|
1554
|
-
options.onFilterError(error);
|
|
1555
|
-
return false;
|
|
1556
|
-
}
|
|
1557
|
-
}
|
|
1558
|
-
if (Object.prototype.hasOwnProperty.call(command.fields, key)) {
|
|
1559
|
-
foundValue = true;
|
|
1560
|
-
try {
|
|
1561
|
-
if (matcher(command.fields[key], getRecord())) {
|
|
1562
|
-
return true;
|
|
1563
|
-
}
|
|
1564
|
-
} catch (error) {
|
|
1565
|
-
options.onFilterError(error);
|
|
1566
|
-
return false;
|
|
1567
|
-
}
|
|
1568
|
-
}
|
|
1569
|
-
if (command.attrs && Object.prototype.hasOwnProperty.call(command.attrs, key)) {
|
|
1570
|
-
foundValue = true;
|
|
1571
|
-
try {
|
|
1572
|
-
if (matcher(command.attrs[key], getRecord())) {
|
|
1573
|
-
return true;
|
|
1574
|
-
}
|
|
1575
|
-
} catch (error) {
|
|
1576
|
-
options.onFilterError(error);
|
|
1577
|
-
return false;
|
|
1578
|
-
}
|
|
1579
|
-
}
|
|
1580
|
-
if (!foundValue) {
|
|
1581
|
-
try {
|
|
1582
|
-
return matcher(undefined, getRecord());
|
|
1583
|
-
} catch (error) {
|
|
1584
|
-
options.onFilterError(error);
|
|
1585
|
-
return false;
|
|
1586
|
-
}
|
|
1587
|
-
}
|
|
1588
|
-
return false;
|
|
1589
|
-
}
|
|
1590
|
-
return commandKeyMatchesExact(command, key, matcher);
|
|
1591
|
-
}
|
|
1592
|
-
function commandKeyMatchesExact(command, key, matcher) {
|
|
1593
|
-
if (key === "name" && commandValueMatchesFilter(command.name, matcher)) {
|
|
1594
|
-
return true;
|
|
1595
|
-
}
|
|
1596
|
-
if (Object.prototype.hasOwnProperty.call(command.fields, key) && commandValueMatchesFilter(command.fields[key], matcher)) {
|
|
1597
|
-
return true;
|
|
1598
|
-
}
|
|
1599
|
-
if (command.attrs && Object.prototype.hasOwnProperty.call(command.attrs, key)) {
|
|
1600
|
-
return commandValueMatchesFilter(command.attrs[key], matcher);
|
|
1601
|
-
}
|
|
1602
|
-
return false;
|
|
1603
|
-
}
|
|
1604
|
-
function commandValueMatchesFilter(value, matcher) {
|
|
1605
|
-
if (Array.isArray(matcher)) {
|
|
1606
|
-
for (const expected of matcher) {
|
|
1607
|
-
if (commandValueMatchesExact(value, expected)) {
|
|
1608
|
-
return true;
|
|
1609
|
-
}
|
|
1610
|
-
}
|
|
1611
|
-
return false;
|
|
1612
|
-
}
|
|
1613
|
-
return commandValueMatchesExact(value, matcher);
|
|
1614
|
-
}
|
|
1615
|
-
function commandValueMatchesExact(value, expected) {
|
|
1616
|
-
if (Array.isArray(value)) {
|
|
1617
|
-
for (const entry of value) {
|
|
1618
|
-
if (commandValueMatchesExact(entry, expected)) {
|
|
1619
|
-
return true;
|
|
1620
|
-
}
|
|
1621
|
-
}
|
|
1622
|
-
return false;
|
|
1623
|
-
}
|
|
1624
|
-
return Object.is(value, expected);
|
|
1625
|
-
}
|
|
1626
|
-
function commandValueMatchesSearch(value, search) {
|
|
1627
|
-
if (Array.isArray(value)) {
|
|
1628
|
-
for (const entry of value) {
|
|
1629
|
-
if (commandValueMatchesSearch(entry, search)) {
|
|
1630
|
-
return true;
|
|
1631
|
-
}
|
|
1632
|
-
}
|
|
1633
|
-
return false;
|
|
1634
|
-
}
|
|
1635
|
-
if (typeof value === "string") {
|
|
1636
|
-
return value.toLowerCase().includes(search);
|
|
1637
|
-
}
|
|
1638
|
-
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
1639
|
-
return String(value).toLowerCase().includes(search);
|
|
1640
|
-
}
|
|
1641
|
-
return false;
|
|
1642
|
-
}
|
|
1643
|
-
|
|
1644
|
-
// src/services/command-executor.ts
|
|
1645
|
-
class CommandExecutorService {
|
|
1646
|
-
notify;
|
|
1647
|
-
runtime;
|
|
1648
|
-
activation;
|
|
1649
|
-
catalog;
|
|
1650
|
-
options;
|
|
1651
|
-
constructor(notify, runtime, activation, catalog, options) {
|
|
1652
|
-
this.notify = notify;
|
|
1653
|
-
this.runtime = runtime;
|
|
1654
|
-
this.activation = activation;
|
|
1655
|
-
this.catalog = catalog;
|
|
1656
|
-
this.options = options;
|
|
1657
|
-
}
|
|
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) {
|
|
1725
|
-
let normalized;
|
|
1726
|
-
try {
|
|
1727
|
-
normalized = normalizeBindingCommand(cmd);
|
|
1728
|
-
} catch {
|
|
1729
|
-
return { ok: false, reason: "invalid-args" };
|
|
1730
|
-
}
|
|
1731
|
-
if (typeof normalized !== "string") {
|
|
1732
|
-
return { ok: false, reason: "not-found" };
|
|
1733
|
-
}
|
|
1734
|
-
const includeRecord = options?.includeCommand === true;
|
|
1735
|
-
const focused = options?.focused ?? this.activation.getFocusedTargetIfAvailable();
|
|
1736
|
-
const event = options?.event ?? this.options.createCommandEvent();
|
|
1737
|
-
const data = this.runtime.getReadonlyData();
|
|
1738
|
-
const chainLookup = this.catalog.getResolvedCommandChain(normalized, focused, includeRecord);
|
|
1739
|
-
const chain = chainLookup.entries;
|
|
1740
|
-
let rejectedResult;
|
|
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) {
|
|
1757
|
-
for (const entry of chain) {
|
|
1758
|
-
const context = {
|
|
1759
|
-
keymap: this.options.keymap,
|
|
1760
|
-
event,
|
|
1761
|
-
focused,
|
|
1762
|
-
target: options?.target ?? entry.target ?? null,
|
|
1763
|
-
data
|
|
1764
|
-
};
|
|
1765
|
-
const execution = this.executeResolvedCommand(normalized, entry.resolved, context);
|
|
1766
|
-
if (execution.status === "handled" || execution.status === "error") {
|
|
1767
|
-
return execution.result;
|
|
1768
|
-
}
|
|
1769
|
-
rejectedResult = execution.result;
|
|
1770
|
-
}
|
|
1771
|
-
}
|
|
1772
|
-
if (chainLookup.hadError) {
|
|
1773
|
-
return { ok: false, reason: "error" };
|
|
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
|
-
}
|
|
1779
|
-
return rejectedResult ?? { ok: false, reason: "not-found" };
|
|
1780
|
-
}
|
|
1781
|
-
runBinding(bindingLayer, binding, event, focused) {
|
|
1782
|
-
const data = this.runtime.getReadonlyData();
|
|
1783
|
-
if (binding.run) {
|
|
1784
|
-
const result = this.executeResolvedCommand(typeof binding.command === "string" ? binding.command : "<function>", { run: binding.run }, {
|
|
1785
|
-
keymap: this.options.keymap,
|
|
1786
|
-
event,
|
|
1787
|
-
focused,
|
|
1788
|
-
target: bindingLayer.target ?? null,
|
|
1789
|
-
data
|
|
1790
|
-
});
|
|
1791
|
-
if (result.status === "rejected") {
|
|
1792
|
-
return false;
|
|
1793
|
-
}
|
|
1794
|
-
applyBindingEventEffects(binding, event);
|
|
1795
|
-
return true;
|
|
1796
|
-
}
|
|
1797
|
-
if (typeof binding.command !== "string") {
|
|
1798
|
-
return false;
|
|
1799
|
-
}
|
|
1800
|
-
const chain = this.catalog.getResolvedCommandChain(binding.command, focused, false).entries;
|
|
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) {
|
|
1818
|
-
for (const entry of chain) {
|
|
1819
|
-
const context = {
|
|
1820
|
-
keymap: this.options.keymap,
|
|
1821
|
-
event,
|
|
1822
|
-
focused,
|
|
1823
|
-
target: entry.target ?? bindingLayer.target ?? null,
|
|
1824
|
-
data
|
|
1825
|
-
};
|
|
1826
|
-
const execution = this.executeResolvedCommand(binding.command, entry.resolved, context);
|
|
1827
|
-
if (execution.status === "rejected") {
|
|
1828
|
-
continue;
|
|
1829
|
-
}
|
|
1830
|
-
applyBindingEventEffects(binding, event);
|
|
1831
|
-
return true;
|
|
1832
|
-
}
|
|
1833
|
-
}
|
|
1834
|
-
return false;
|
|
1835
|
-
}
|
|
1836
|
-
executeResolvedCommand(commandName, resolved, context) {
|
|
1837
|
-
const command = resolved.record;
|
|
1838
|
-
let result;
|
|
1839
|
-
try {
|
|
1840
|
-
result = resolved.run(context);
|
|
1841
|
-
} catch (error) {
|
|
1842
|
-
this.notify.emitError("command-execution-error", error, `[Keymap] Error running command "${commandName}":`);
|
|
1843
|
-
return {
|
|
1844
|
-
status: "error",
|
|
1845
|
-
result: { ok: false, reason: "error", command }
|
|
1846
|
-
};
|
|
1847
|
-
}
|
|
1848
|
-
if (isPromiseLike(result)) {
|
|
1849
|
-
result.catch((error) => {
|
|
1850
|
-
this.notify.emitError("async-command-error", error, `[Keymap] Async error in command "${commandName}":`);
|
|
1851
|
-
});
|
|
1852
|
-
return {
|
|
1853
|
-
status: "handled",
|
|
1854
|
-
result: { ok: true, command }
|
|
1855
|
-
};
|
|
1856
|
-
}
|
|
1857
|
-
if (result === false) {
|
|
1858
|
-
if (resolved.rejectedResult) {
|
|
1859
|
-
return {
|
|
1860
|
-
status: "rejected",
|
|
1861
|
-
result: resolved.rejectedResult
|
|
1862
|
-
};
|
|
1863
|
-
}
|
|
1864
|
-
return {
|
|
1865
|
-
status: "rejected",
|
|
1866
|
-
result: { ok: false, reason: "rejected", command }
|
|
1867
|
-
};
|
|
1868
|
-
}
|
|
1869
|
-
return {
|
|
1870
|
-
status: "handled",
|
|
1871
|
-
result: { ok: true, command }
|
|
1872
|
-
};
|
|
1873
|
-
}
|
|
1874
|
-
}
|
|
1875
|
-
function applyBindingEventEffects(binding, event) {
|
|
1876
|
-
if (!binding.preventDefault) {
|
|
1877
|
-
return;
|
|
1878
|
-
}
|
|
1879
|
-
event.preventDefault();
|
|
1880
|
-
event.stopPropagation();
|
|
1881
|
-
}
|
|
1882
|
-
|
|
1883
|
-
// src/services/primitives/binding-inputs.ts
|
|
1884
|
-
function normalizeBindingInputs(bindings) {
|
|
1885
|
-
if (Array.isArray(bindings)) {
|
|
1886
|
-
return bindings;
|
|
1887
|
-
}
|
|
1888
|
-
const normalized = [];
|
|
1889
|
-
for (const [key, cmd] of Object.entries(bindings)) {
|
|
1890
|
-
if (typeof cmd !== "string" && typeof cmd !== "function") {
|
|
1891
|
-
throw new Error(`Invalid keymap binding for "${key}": shorthand bindings must map to string or function commands`);
|
|
1892
|
-
}
|
|
1893
|
-
normalized.push({ key, cmd });
|
|
1894
|
-
}
|
|
1895
|
-
return normalized;
|
|
1896
|
-
}
|
|
1897
|
-
function snapshotBindingInputs(bindings) {
|
|
1898
|
-
return normalizeBindingInputs(bindings).map((binding) => ({
|
|
1899
|
-
...binding,
|
|
1900
|
-
key: typeof binding.key === "string" ? binding.key : { ...binding.key }
|
|
1901
|
-
}));
|
|
1902
|
-
}
|
|
1903
|
-
function snapshotParsedBindingInput(binding) {
|
|
1904
|
-
return {
|
|
1905
|
-
...binding,
|
|
1906
|
-
sequence: cloneKeySequence(binding.sequence)
|
|
1907
|
-
};
|
|
1908
|
-
}
|
|
1909
|
-
|
|
1910
|
-
// src/services/compiler.ts
|
|
1911
|
-
var EMPTY_COMPILE_FIELDS = Object.freeze({});
|
|
1912
|
-
var EMPTY_REQUIRES = [];
|
|
1913
|
-
var EMPTY_MATCHERS = [];
|
|
1914
|
-
var EMPTY_CONDITION_KEYS = [];
|
|
1915
|
-
function createSequenceNode(parent, stroke, match) {
|
|
1916
|
-
return {
|
|
1917
|
-
parent,
|
|
1918
|
-
depth: parent ? parent.depth + 1 : 0,
|
|
1919
|
-
stroke,
|
|
1920
|
-
match,
|
|
1921
|
-
children: new Map,
|
|
1922
|
-
bindings: [],
|
|
1923
|
-
reachableBindings: []
|
|
1924
|
-
};
|
|
1925
|
-
}
|
|
1926
|
-
function snapshotAttributes(attrs) {
|
|
1927
|
-
if (Object.keys(attrs).length === 0) {
|
|
1928
|
-
return;
|
|
1929
|
-
}
|
|
1930
|
-
return snapshotDataValue(attrs, { freeze: true });
|
|
1931
|
-
}
|
|
1932
|
-
|
|
1933
|
-
class CompilerService {
|
|
1934
|
-
state;
|
|
1935
|
-
notify;
|
|
1936
|
-
conditions;
|
|
1937
|
-
options;
|
|
1938
|
-
constructor(state, notify, conditions, options) {
|
|
1939
|
-
this.state = state;
|
|
1940
|
-
this.notify = notify;
|
|
1941
|
-
this.conditions = conditions;
|
|
1942
|
-
this.options = options;
|
|
1943
|
-
}
|
|
1944
|
-
parseTokenKey(key) {
|
|
1945
|
-
return parseSingleKeyPartWithParsers(key, this.state.environment.bindingParsers.values(), {
|
|
1946
|
-
tokens: this.state.environment.tokens,
|
|
1947
|
-
layer: EMPTY_COMPILE_FIELDS,
|
|
1948
|
-
parseObjectKey: (value, options) => this.parseObjectKeyPart(value, options)
|
|
1949
|
-
});
|
|
1950
|
-
}
|
|
1951
|
-
compileBindings(bindings, tokens, sourceTarget, sourceLayerOrder, compileFields) {
|
|
1952
|
-
const root = createSequenceNode(null, null, null);
|
|
1953
|
-
const compiledBindings = [];
|
|
1954
|
-
let hasTokenBindings = false;
|
|
1955
|
-
const bindingExpanders = this.state.environment.bindingExpanders.values();
|
|
1956
|
-
const bindingParsers = this.state.environment.bindingParsers.values();
|
|
1957
|
-
const bindingFieldCompilers = this.state.environment.bindingFields;
|
|
1958
|
-
const allowExactPrefixAmbiguity = this.state.dispatch.disambiguationResolvers.has();
|
|
1959
|
-
const warnUnknownField = this.options.warnUnknownField;
|
|
1960
|
-
const warnUnknownToken = this.options.warnUnknownToken;
|
|
1961
|
-
const conditions = this.conditions;
|
|
1962
|
-
for (const [bindingIndex, binding] of bindings.entries()) {
|
|
1963
|
-
let expandedBindingKeys;
|
|
1964
|
-
try {
|
|
1965
|
-
expandedBindingKeys = expandBindingInputWithExpanders(binding.key, bindingExpanders, {
|
|
1966
|
-
layer: compileFields
|
|
1967
|
-
});
|
|
1968
|
-
} catch (error) {
|
|
1969
|
-
this.notify.emitError("binding-expand-error", error, getErrorMessage(error, "Failed to expand keymap binding"));
|
|
1970
|
-
continue;
|
|
1971
|
-
}
|
|
1972
|
-
for (const expandedBindingKey of expandedBindingKeys) {
|
|
1973
|
-
let parsed;
|
|
1974
|
-
try {
|
|
1975
|
-
parsed = typeof expandedBindingKey === "string" ? parseBindingSequenceWithParsers(expandedBindingKey, bindingParsers, {
|
|
1976
|
-
tokens,
|
|
1977
|
-
layer: compileFields,
|
|
1978
|
-
parseObjectKey: (value, options) => this.parseObjectKeyPart(value, options)
|
|
1979
|
-
}) : {
|
|
1980
|
-
parts: [this.parseObjectKeyPart(expandedBindingKey)],
|
|
1981
|
-
usedTokens: [],
|
|
1982
|
-
unknownTokens: [],
|
|
1983
|
-
hasTokenBindings: false
|
|
1984
|
-
};
|
|
1985
|
-
} catch (error) {
|
|
1986
|
-
this.notify.emitError("binding-parse-error", error, getErrorMessage(error, "Failed to parse keymap binding"));
|
|
1987
|
-
continue;
|
|
1988
|
-
}
|
|
1989
|
-
const sequence = parsed.parts;
|
|
1990
|
-
hasTokenBindings ||= parsed.hasTokenBindings;
|
|
1991
|
-
for (const tokenName of parsed.unknownTokens) {
|
|
1992
|
-
warnUnknownToken(tokenName, typeof expandedBindingKey === "string" ? expandedBindingKey : String(expandedBindingKey.name));
|
|
1993
|
-
}
|
|
1994
|
-
for (const compiledInput of this.applyBindingTransformers(binding, sequence, tokens, bindingParsers, compileFields)) {
|
|
1995
|
-
try {
|
|
1996
|
-
const event = this.normalizeBindingEvent(compiledInput.event);
|
|
1997
|
-
const compiledSequence = compiledInput.sequence;
|
|
1998
|
-
let mergedRequires;
|
|
1999
|
-
let mergedAttrs;
|
|
2000
|
-
let matchers;
|
|
2001
|
-
let conditionKeys;
|
|
2002
|
-
let hasUnkeyedMatchers = false;
|
|
2003
|
-
for (const fieldName in compiledInput) {
|
|
2004
|
-
if (fieldName === "sequence") {
|
|
2005
|
-
continue;
|
|
2006
|
-
}
|
|
2007
|
-
if (RESERVED_BINDING_FIELDS.has(fieldName)) {
|
|
2008
|
-
continue;
|
|
2009
|
-
}
|
|
2010
|
-
const value = compiledInput[fieldName];
|
|
2011
|
-
if (value === undefined) {
|
|
2012
|
-
continue;
|
|
2013
|
-
}
|
|
2014
|
-
const compiler = bindingFieldCompilers.get(fieldName);
|
|
2015
|
-
if (!compiler) {
|
|
2016
|
-
warnUnknownField("binding", fieldName);
|
|
2017
|
-
continue;
|
|
2018
|
-
}
|
|
2019
|
-
compiler(value, {
|
|
2020
|
-
require(name, requiredValue) {
|
|
2021
|
-
if (!mergedRequires) {
|
|
2022
|
-
mergedRequires = {};
|
|
2023
|
-
}
|
|
2024
|
-
mergeRequirement(mergedRequires, name, requiredValue, `field ${fieldName}`);
|
|
2025
|
-
if (!conditionKeys) {
|
|
2026
|
-
conditionKeys = new Set;
|
|
2027
|
-
}
|
|
2028
|
-
conditionKeys.add(name);
|
|
2029
|
-
},
|
|
2030
|
-
attr(name, attributeValue) {
|
|
2031
|
-
if (!mergedAttrs) {
|
|
2032
|
-
mergedAttrs = {};
|
|
2033
|
-
}
|
|
2034
|
-
mergeAttribute(mergedAttrs, name, attributeValue, `field ${fieldName}`);
|
|
2035
|
-
},
|
|
2036
|
-
activeWhen: (matcher) => {
|
|
2037
|
-
const runtimeMatcher = conditions.buildRuntimeMatcher(matcher, `field ${fieldName}`);
|
|
2038
|
-
if (!runtimeMatcher.cacheable) {
|
|
2039
|
-
hasUnkeyedMatchers = true;
|
|
2040
|
-
}
|
|
2041
|
-
if (!matchers) {
|
|
2042
|
-
matchers = [];
|
|
2043
|
-
}
|
|
2044
|
-
matchers.push(runtimeMatcher);
|
|
2045
|
-
}
|
|
2046
|
-
});
|
|
2047
|
-
}
|
|
2048
|
-
const attrs = mergedAttrs ? snapshotAttributes(mergedAttrs) : undefined;
|
|
2049
|
-
const command = normalizeBindingCommand(compiledInput.cmd);
|
|
2050
|
-
const compiledBinding = {
|
|
2051
|
-
sequence: compiledSequence,
|
|
2052
|
-
command,
|
|
2053
|
-
event,
|
|
2054
|
-
sourceBinding: snapshotParsedBindingInput(compiledInput),
|
|
2055
|
-
sourceTarget,
|
|
2056
|
-
sourceLayerOrder,
|
|
2057
|
-
sourceBindingIndex: bindingIndex,
|
|
2058
|
-
requires: mergedRequires ? Object.entries(mergedRequires) : EMPTY_REQUIRES,
|
|
2059
|
-
matchers: matchers ?? EMPTY_MATCHERS,
|
|
2060
|
-
conditionKeys: conditionKeys ? [...conditionKeys] : EMPTY_CONDITION_KEYS,
|
|
2061
|
-
hasUnkeyedMatchers,
|
|
2062
|
-
matchCacheDirty: true,
|
|
2063
|
-
preventDefault: compiledInput.preventDefault !== false,
|
|
2064
|
-
fallthrough: compiledInput.fallthrough ?? false
|
|
2065
|
-
};
|
|
2066
|
-
if (attrs) {
|
|
2067
|
-
compiledBinding.attrs = attrs;
|
|
2068
|
-
}
|
|
2069
|
-
if (typeof command === "function") {
|
|
2070
|
-
compiledBinding.run = command;
|
|
2071
|
-
}
|
|
2072
|
-
if (compiledSequence.length === 0) {
|
|
2073
|
-
continue;
|
|
2074
|
-
}
|
|
2075
|
-
if (event === "release" && compiledSequence.length > 1) {
|
|
2076
|
-
throw new Error("Keymap release bindings only support a single key stroke");
|
|
2077
|
-
}
|
|
2078
|
-
if (event === "press") {
|
|
2079
|
-
this.insertBinding(root, compiledBinding, allowExactPrefixAmbiguity);
|
|
2080
|
-
}
|
|
2081
|
-
compiledBindings.push(compiledBinding);
|
|
2082
|
-
} catch (error) {
|
|
2083
|
-
this.notify.emitError("binding-compile-error", error, getErrorMessage(error, "Failed to compile keymap binding"));
|
|
2084
|
-
}
|
|
2085
|
-
}
|
|
2086
|
-
}
|
|
2087
|
-
}
|
|
2088
|
-
return {
|
|
2089
|
-
root,
|
|
2090
|
-
bindings: compiledBindings,
|
|
2091
|
-
hasTokenBindings
|
|
2092
|
-
};
|
|
2093
|
-
}
|
|
2094
|
-
parseObjectKeyPart(key, options) {
|
|
2095
|
-
return createKeySequencePart(key, options);
|
|
2096
|
-
}
|
|
2097
|
-
normalizeBindingEvent(event) {
|
|
2098
|
-
if (event === undefined || event === "press") {
|
|
2099
|
-
return "press";
|
|
2100
|
-
}
|
|
2101
|
-
if (event === "release") {
|
|
2102
|
-
return "release";
|
|
2103
|
-
}
|
|
2104
|
-
throw new Error(`Invalid keymap binding event "${String(event)}": expected "press" or "release"`);
|
|
2105
|
-
}
|
|
2106
|
-
applyBindingTransformers(binding, sequence, tokens, bindingParsers, compileFields) {
|
|
2107
|
-
const bindingTransformers = this.state.environment.bindingTransformers.values();
|
|
2108
|
-
if (bindingTransformers.length === 0) {
|
|
2109
|
-
return [{ ...binding, sequence: cloneKeySequence(sequence) }];
|
|
2110
|
-
}
|
|
2111
|
-
const parsedBinding = {
|
|
2112
|
-
...binding,
|
|
2113
|
-
sequence: cloneKeySequence(sequence)
|
|
2114
|
-
};
|
|
2115
|
-
const extraBindings = [];
|
|
2116
|
-
let keepOriginal = true;
|
|
2117
|
-
const layer = compileFields ?? EMPTY_COMPILE_FIELDS;
|
|
2118
|
-
for (const transformer of bindingTransformers) {
|
|
2119
|
-
try {
|
|
2120
|
-
transformer(parsedBinding, {
|
|
2121
|
-
layer,
|
|
2122
|
-
parseKey: (key) => {
|
|
2123
|
-
return parseSingleKeyPartWithParsers(key, bindingParsers, {
|
|
2124
|
-
tokens,
|
|
2125
|
-
layer,
|
|
2126
|
-
parseObjectKey: (value, options) => this.parseObjectKeyPart(value, options)
|
|
2127
|
-
});
|
|
2128
|
-
},
|
|
2129
|
-
add: (nextBinding) => {
|
|
2130
|
-
extraBindings.push(snapshotParsedBindingInput(nextBinding));
|
|
2131
|
-
},
|
|
2132
|
-
skipOriginal: () => {
|
|
2133
|
-
keepOriginal = false;
|
|
2134
|
-
}
|
|
2135
|
-
});
|
|
2136
|
-
} catch (error) {
|
|
2137
|
-
this.notify.emitError("binding-transformer-error", error, "[Keymap] Error in binding transformer:");
|
|
2138
|
-
}
|
|
2139
|
-
}
|
|
2140
|
-
if (!keepOriginal) {
|
|
2141
|
-
return extraBindings;
|
|
2142
|
-
}
|
|
2143
|
-
if (extraBindings.length === 0) {
|
|
2144
|
-
return [parsedBinding];
|
|
2145
|
-
}
|
|
2146
|
-
return [parsedBinding, ...extraBindings];
|
|
2147
|
-
}
|
|
2148
|
-
insertBinding(root, binding, allowExactPrefixAmbiguity) {
|
|
2149
|
-
let node = root;
|
|
2150
|
-
const touchedNodes = [];
|
|
2151
|
-
const createdNodes = [];
|
|
2152
|
-
try {
|
|
2153
|
-
for (const part of binding.sequence) {
|
|
2154
|
-
if (!allowExactPrefixAmbiguity && node.bindings.some((candidate) => candidate.command !== undefined)) {
|
|
2155
|
-
throw new Error("Keymap bindings cannot use the same sequence as both an exact match and a prefix in the same layer");
|
|
2156
|
-
}
|
|
2157
|
-
const bindingKey = part.match;
|
|
2158
|
-
let child = node.children.get(bindingKey);
|
|
2159
|
-
if (!child) {
|
|
2160
|
-
child = createSequenceNode(node, cloneKeySequencePart(part).stroke, part.match);
|
|
2161
|
-
node.children.set(bindingKey, child);
|
|
2162
|
-
createdNodes.push({ parent: node, key: bindingKey });
|
|
2163
|
-
}
|
|
2164
|
-
child.reachableBindings.push(binding);
|
|
2165
|
-
touchedNodes.push(child);
|
|
2166
|
-
node = child;
|
|
2167
|
-
}
|
|
2168
|
-
if (!allowExactPrefixAmbiguity && binding.command !== undefined && node.children.size > 0) {
|
|
2169
|
-
throw new Error("Keymap bindings cannot use the same sequence as both an exact match and a prefix in the same layer");
|
|
2170
|
-
}
|
|
2171
|
-
node.bindings = [...node.bindings, binding];
|
|
2172
|
-
} catch (error) {
|
|
2173
|
-
for (let index = touchedNodes.length - 1;index >= 0; index -= 1) {
|
|
2174
|
-
const touchedNode = touchedNodes[index];
|
|
2175
|
-
if (!touchedNode) {
|
|
2176
|
-
continue;
|
|
2177
|
-
}
|
|
2178
|
-
if (touchedNode.reachableBindings.at(-1) === binding) {
|
|
2179
|
-
touchedNode.reachableBindings.pop();
|
|
2180
|
-
continue;
|
|
2181
|
-
}
|
|
2182
|
-
touchedNode.reachableBindings = touchedNode.reachableBindings.filter((candidate) => candidate !== binding);
|
|
2183
|
-
}
|
|
2184
|
-
for (let index = createdNodes.length - 1;index >= 0; index -= 1) {
|
|
2185
|
-
const createdNode = createdNodes[index];
|
|
2186
|
-
if (!createdNode) {
|
|
2187
|
-
continue;
|
|
2188
|
-
}
|
|
2189
|
-
const child = createdNode.parent.children.get(createdNode.key);
|
|
2190
|
-
if (!child) {
|
|
2191
|
-
continue;
|
|
2192
|
-
}
|
|
2193
|
-
if (child.children.size > 0 || child.reachableBindings.length > 0 || child.bindings.length > 0) {
|
|
2194
|
-
continue;
|
|
2195
|
-
}
|
|
2196
|
-
createdNode.parent.children.delete(createdNode.key);
|
|
2197
|
-
}
|
|
2198
|
-
throw error;
|
|
2199
|
-
}
|
|
2200
|
-
}
|
|
2201
|
-
}
|
|
2202
|
-
function expandBindingInputWithExpanders(key, expanders, options) {
|
|
2203
|
-
if (typeof key !== "string" || expanders.length === 0) {
|
|
2204
|
-
return [key];
|
|
2205
|
-
}
|
|
2206
|
-
const layer = options?.layer ?? EMPTY_COMPILE_FIELDS;
|
|
2207
|
-
let candidates = [key];
|
|
2208
|
-
for (const expander of expanders) {
|
|
2209
|
-
const nextCandidates = [];
|
|
2210
|
-
for (const input of candidates) {
|
|
2211
|
-
const result = expander({ input, layer });
|
|
2212
|
-
if (!result) {
|
|
2213
|
-
nextCandidates.push(input);
|
|
2214
|
-
continue;
|
|
2215
|
-
}
|
|
2216
|
-
if (result.length === 0) {
|
|
2217
|
-
throw new Error(`Keymap binding expander must return at least one key sequence for "${input}"`);
|
|
2218
|
-
}
|
|
2219
|
-
for (const expandedInput of result) {
|
|
2220
|
-
if (typeof expandedInput !== "string") {
|
|
2221
|
-
throw new Error(`Keymap binding expander must return string key sequences for "${input}"`);
|
|
2222
|
-
}
|
|
2223
|
-
nextCandidates.push(expandedInput);
|
|
2224
|
-
}
|
|
2225
|
-
}
|
|
2226
|
-
candidates = nextCandidates;
|
|
2227
|
-
}
|
|
2228
|
-
return candidates;
|
|
2229
|
-
}
|
|
2230
|
-
function parseBindingSequenceWithParsers(key, parsers, options) {
|
|
2231
|
-
if (key.length === 0) {
|
|
2232
|
-
throw new Error("Invalid key sequence: sequence cannot be empty");
|
|
2233
|
-
}
|
|
2234
|
-
if (parsers.length === 0) {
|
|
2235
|
-
throw new Error("No keymap binding parsers are registered");
|
|
2236
|
-
}
|
|
2237
|
-
const tokens = options.tokens ?? new Map;
|
|
2238
|
-
const layer = options.layer ?? EMPTY_COMPILE_FIELDS;
|
|
2239
|
-
const parseObjectKey = options.parseObjectKey;
|
|
2240
|
-
const parts = [];
|
|
2241
|
-
const usedTokens = new Set;
|
|
2242
|
-
const unknownTokens = new Set;
|
|
2243
|
-
let index = 0;
|
|
2244
|
-
while (index < key.length) {
|
|
2245
|
-
let matched = false;
|
|
2246
|
-
for (const parser of parsers) {
|
|
2247
|
-
const result = parser({
|
|
2248
|
-
input: key,
|
|
2249
|
-
index,
|
|
2250
|
-
layer,
|
|
2251
|
-
tokens,
|
|
2252
|
-
normalizeTokenName: normalizeBindingTokenName,
|
|
2253
|
-
createMatch: createTextKeyMatch,
|
|
2254
|
-
parseObjectKey
|
|
2255
|
-
});
|
|
2256
|
-
if (!result) {
|
|
2257
|
-
continue;
|
|
2258
|
-
}
|
|
2259
|
-
if (result.nextIndex <= index || result.nextIndex > key.length) {
|
|
2260
|
-
throw new Error(`Keymap binding parser must advance the input for "${key}" at index ${index}`);
|
|
2261
|
-
}
|
|
2262
|
-
parts.push(...result.parts);
|
|
2263
|
-
for (const tokenName of result.usedTokens ?? []) {
|
|
2264
|
-
usedTokens.add(tokenName);
|
|
2265
|
-
}
|
|
2266
|
-
for (const tokenName of result.unknownTokens ?? []) {
|
|
2267
|
-
unknownTokens.add(tokenName);
|
|
2268
|
-
}
|
|
2269
|
-
index = result.nextIndex;
|
|
2270
|
-
matched = true;
|
|
2271
|
-
break;
|
|
2272
|
-
}
|
|
2273
|
-
if (!matched) {
|
|
2274
|
-
throw new Error(`No keymap binding parser handled input at index ${index} in "${key}"`);
|
|
2275
|
-
}
|
|
2276
|
-
}
|
|
2277
|
-
return {
|
|
2278
|
-
parts,
|
|
2279
|
-
usedTokens: [...usedTokens],
|
|
2280
|
-
unknownTokens: [...unknownTokens],
|
|
2281
|
-
hasTokenBindings: usedTokens.size > 0 || unknownTokens.size > 0
|
|
2282
|
-
};
|
|
2283
|
-
}
|
|
2284
|
-
function parseSingleKeyPartWithParsers(key, parsers, options) {
|
|
2285
|
-
if (typeof key !== "string") {
|
|
2286
|
-
return options.parseObjectKey(key);
|
|
2287
|
-
}
|
|
2288
|
-
const { parts } = parseBindingSequenceWithParsers(key, parsers, options);
|
|
2289
|
-
const [part] = parts;
|
|
2290
|
-
if (!part || parts.length !== 1) {
|
|
2291
|
-
throw new Error(`Invalid key "${String(key)}": expected a single key stroke`);
|
|
2292
|
-
}
|
|
2293
|
-
return part;
|
|
2294
|
-
}
|
|
2295
|
-
|
|
2296
|
-
// src/services/conditions.ts
|
|
2297
|
-
function isReactiveMatcher(value) {
|
|
2298
|
-
if (!value || typeof value !== "object") {
|
|
2299
|
-
return false;
|
|
2300
|
-
}
|
|
2301
|
-
const candidate = value;
|
|
2302
|
-
return typeof candidate.get === "function" && typeof candidate.subscribe === "function";
|
|
2303
|
-
}
|
|
2304
|
-
|
|
2305
|
-
class ConditionService {
|
|
2306
|
-
state;
|
|
2307
|
-
notify;
|
|
2308
|
-
constructor(state, notify) {
|
|
2309
|
-
this.state = state;
|
|
2310
|
-
this.notify = notify;
|
|
2311
|
-
}
|
|
2312
|
-
buildRuntimeMatcher(matcher, source) {
|
|
2313
|
-
if (typeof matcher === "function") {
|
|
2314
|
-
return {
|
|
2315
|
-
source,
|
|
2316
|
-
match: matcher,
|
|
2317
|
-
cacheable: false
|
|
2318
|
-
};
|
|
2319
|
-
}
|
|
2320
|
-
if (isReactiveMatcher(matcher)) {
|
|
2321
|
-
return {
|
|
2322
|
-
source,
|
|
2323
|
-
match: () => matcher.get(),
|
|
2324
|
-
cacheable: true,
|
|
2325
|
-
subscribe: (onChange) => matcher.subscribe(onChange)
|
|
2326
|
-
};
|
|
2327
|
-
}
|
|
2328
|
-
throw new Error(`Keymap ${source} expected a function or a reactive matcher`);
|
|
2329
|
-
}
|
|
2330
|
-
hasNoConditions(target) {
|
|
2331
|
-
return target.requires.length === 0 && target.matchers.length === 0;
|
|
2332
|
-
}
|
|
2333
|
-
indexRuntimeMatchable(target) {
|
|
2334
|
-
if (target.conditionKeys.length > 0) {
|
|
2335
|
-
for (const key of target.conditionKeys) {
|
|
2336
|
-
const dependents = this.state.conditions.runtimeKeyDependents.get(key);
|
|
2337
|
-
if (dependents) {
|
|
2338
|
-
dependents.add(target);
|
|
2339
|
-
continue;
|
|
2340
|
-
}
|
|
2341
|
-
this.state.conditions.runtimeKeyDependents.set(key, new Set([target]));
|
|
2342
|
-
}
|
|
2343
|
-
}
|
|
2344
|
-
if (!target.hasUnkeyedMatchers) {
|
|
2345
|
-
target.matchCacheDirty = true;
|
|
2346
|
-
}
|
|
2347
|
-
}
|
|
2348
|
-
unindexRuntimeMatchable(target) {
|
|
2349
|
-
if (target.conditionKeys.length === 0) {
|
|
2350
|
-
return;
|
|
2351
|
-
}
|
|
2352
|
-
for (const key of target.conditionKeys) {
|
|
2353
|
-
const dependents = this.state.conditions.runtimeKeyDependents.get(key);
|
|
2354
|
-
if (!dependents) {
|
|
2355
|
-
continue;
|
|
2356
|
-
}
|
|
2357
|
-
dependents.delete(target);
|
|
2358
|
-
if (dependents.size === 0) {
|
|
2359
|
-
this.state.conditions.runtimeKeyDependents.delete(key);
|
|
2360
|
-
}
|
|
2361
|
-
}
|
|
2362
|
-
}
|
|
2363
|
-
invalidateRuntimeConditionKey(name) {
|
|
2364
|
-
const dependents = this.state.conditions.runtimeKeyDependents.get(name);
|
|
2365
|
-
if (!dependents) {
|
|
2366
|
-
return;
|
|
2367
|
-
}
|
|
2368
|
-
for (const target of dependents) {
|
|
2369
|
-
target.matchCacheDirty = true;
|
|
2370
|
-
}
|
|
2371
|
-
}
|
|
2372
|
-
matchesConditions(target) {
|
|
2373
|
-
if (this.hasNoConditions(target)) {
|
|
2374
|
-
return true;
|
|
2375
|
-
}
|
|
2376
|
-
if (this.hasFreshConditionCache(target)) {
|
|
2377
|
-
return target.matchCache === true;
|
|
2378
|
-
}
|
|
2379
|
-
const matched = this.matchRequirements(target.requires) && this.matchesRuntimeMatchers(target);
|
|
2380
|
-
this.updateConditionCache(target, matched);
|
|
2381
|
-
return matched;
|
|
2382
|
-
}
|
|
2383
|
-
layerMatchesRuntimeState(layer) {
|
|
2384
|
-
if (this.state.layers.layersWithConditions === 0 || this.hasNoConditions(layer)) {
|
|
2385
|
-
return true;
|
|
2386
|
-
}
|
|
2387
|
-
return this.matchesConditions(layer);
|
|
2388
|
-
}
|
|
2389
|
-
matchRequirements(requires) {
|
|
2390
|
-
if (requires.length === 0) {
|
|
2391
|
-
return true;
|
|
2392
|
-
}
|
|
2393
|
-
for (const [name, value] of requires) {
|
|
2394
|
-
if (!Object.is(this.state.runtime.data[name], value)) {
|
|
2395
|
-
return false;
|
|
2396
|
-
}
|
|
2397
|
-
}
|
|
2398
|
-
return true;
|
|
2399
|
-
}
|
|
2400
|
-
hasFreshConditionCache(target) {
|
|
2401
|
-
if (target.hasUnkeyedMatchers) {
|
|
2402
|
-
return false;
|
|
2403
|
-
}
|
|
2404
|
-
return target.matchCacheDirty !== true && target.matchCache !== undefined;
|
|
2405
|
-
}
|
|
2406
|
-
updateConditionCache(target, matched) {
|
|
2407
|
-
if (target.hasUnkeyedMatchers) {
|
|
2408
|
-
return;
|
|
2409
|
-
}
|
|
2410
|
-
target.matchCacheDirty = false;
|
|
2411
|
-
target.matchCache = matched;
|
|
2412
|
-
}
|
|
2413
|
-
matchesRuntimeMatcher(matcher) {
|
|
2414
|
-
try {
|
|
2415
|
-
return matcher.match();
|
|
2416
|
-
} catch (error) {
|
|
2417
|
-
this.notify.emitError("runtime-matcher-error", error, `[Keymap] Error evaluating runtime matcher from ${matcher.source}:`);
|
|
2418
|
-
return false;
|
|
2419
|
-
}
|
|
2420
|
-
}
|
|
2421
|
-
matchesRuntimeMatchers(target) {
|
|
2422
|
-
if (target.matchers.length === 0) {
|
|
2423
|
-
return true;
|
|
2424
|
-
}
|
|
2425
|
-
if (target.matchers.length === 1) {
|
|
2426
|
-
const [matcher] = target.matchers;
|
|
2427
|
-
return matcher ? this.matchesRuntimeMatcher(matcher) : true;
|
|
2428
|
-
}
|
|
2429
|
-
for (const matcher of target.matchers) {
|
|
2430
|
-
if (!this.matchesRuntimeMatcher(matcher)) {
|
|
2431
|
-
return false;
|
|
2432
|
-
}
|
|
2433
|
-
}
|
|
2434
|
-
return true;
|
|
2435
|
-
}
|
|
2436
|
-
}
|
|
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
|
-
|
|
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
|
-
|
|
2463
|
-
class DispatchService {
|
|
2464
|
-
state;
|
|
2465
|
-
notify;
|
|
2466
|
-
runtime;
|
|
2467
|
-
activation;
|
|
2468
|
-
conditions;
|
|
2469
|
-
executor;
|
|
2470
|
-
compiler;
|
|
2471
|
-
catalog;
|
|
2472
|
-
layers;
|
|
2473
|
-
eventMatchResolverContext;
|
|
2474
|
-
pendingDisambiguation = null;
|
|
2475
|
-
nextPendingDisambiguationId = 0;
|
|
2476
|
-
constructor(state, notify, runtime, activation, conditions, executor, compiler, catalog, layers) {
|
|
2477
|
-
this.state = state;
|
|
2478
|
-
this.notify = notify;
|
|
2479
|
-
this.runtime = runtime;
|
|
2480
|
-
this.activation = activation;
|
|
2481
|
-
this.conditions = conditions;
|
|
2482
|
-
this.executor = executor;
|
|
2483
|
-
this.compiler = compiler;
|
|
2484
|
-
this.catalog = catalog;
|
|
2485
|
-
this.layers = layers;
|
|
2486
|
-
this.eventMatchResolverContext = {
|
|
2487
|
-
resolveKey: (key) => {
|
|
2488
|
-
return this.compiler.parseTokenKey(key).match;
|
|
2489
|
-
}
|
|
2490
|
-
};
|
|
2491
|
-
}
|
|
2492
|
-
intercept(name, fn, options) {
|
|
2493
|
-
if (name === "key") {
|
|
2494
|
-
const keyOptions = options;
|
|
2495
|
-
return this.state.dispatch.keyHooks.register(fn, {
|
|
2496
|
-
priority: keyOptions?.priority ?? 0,
|
|
2497
|
-
release: keyOptions?.release ?? false
|
|
2498
|
-
});
|
|
2499
|
-
}
|
|
2500
|
-
const rawOptions = options;
|
|
2501
|
-
return this.state.dispatch.rawHooks.register(fn, {
|
|
2502
|
-
priority: rawOptions?.priority ?? 0
|
|
2503
|
-
});
|
|
2504
|
-
}
|
|
2505
|
-
prependEventMatchResolver(resolver) {
|
|
2506
|
-
return this.state.dispatch.eventMatchResolvers.prepend(resolver);
|
|
2507
|
-
}
|
|
2508
|
-
appendEventMatchResolver(resolver) {
|
|
2509
|
-
return this.state.dispatch.eventMatchResolvers.append(resolver);
|
|
2510
|
-
}
|
|
2511
|
-
clearEventMatchResolvers() {
|
|
2512
|
-
this.state.dispatch.eventMatchResolvers.clear();
|
|
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
|
-
}
|
|
2535
|
-
handleRawSequence(sequence) {
|
|
2536
|
-
const hooks = this.state.dispatch.rawHooks.entries();
|
|
2537
|
-
if (hooks.length === 0) {
|
|
2538
|
-
return false;
|
|
2539
|
-
}
|
|
2540
|
-
let stopped = false;
|
|
2541
|
-
const context = {
|
|
2542
|
-
sequence,
|
|
2543
|
-
stop() {
|
|
2544
|
-
stopped = true;
|
|
2545
|
-
}
|
|
2546
|
-
};
|
|
2547
|
-
for (const hook of hooks) {
|
|
2548
|
-
try {
|
|
2549
|
-
hook.listener(context);
|
|
2550
|
-
} catch (error) {
|
|
2551
|
-
this.notify.emitError("raw-intercept-error", error, "[Keymap] Error in raw intercept listener:");
|
|
2552
|
-
}
|
|
2553
|
-
if (stopped) {
|
|
2554
|
-
return true;
|
|
2555
|
-
}
|
|
2556
|
-
}
|
|
2557
|
-
return false;
|
|
2558
|
-
}
|
|
2559
|
-
handleKeyEvent(event, release) {
|
|
2560
|
-
if (!release) {
|
|
2561
|
-
this.cancelPendingDisambiguation();
|
|
2562
|
-
}
|
|
2563
|
-
const hooks = this.state.dispatch.keyHooks.entries();
|
|
2564
|
-
const context = {
|
|
2565
|
-
event,
|
|
2566
|
-
setData: (name, value) => {
|
|
2567
|
-
this.runtime.setData(name, value);
|
|
2568
|
-
},
|
|
2569
|
-
getData: (name) => {
|
|
2570
|
-
return this.runtime.getData(name);
|
|
2571
|
-
},
|
|
2572
|
-
consume: (options) => {
|
|
2573
|
-
const shouldPreventDefault = options?.preventDefault ?? true;
|
|
2574
|
-
const shouldStopPropagation = options?.stopPropagation ?? true;
|
|
2575
|
-
if (shouldPreventDefault) {
|
|
2576
|
-
event.preventDefault();
|
|
2577
|
-
}
|
|
2578
|
-
if (shouldStopPropagation) {
|
|
2579
|
-
event.stopPropagation();
|
|
2580
|
-
}
|
|
2581
|
-
}
|
|
2582
|
-
};
|
|
2583
|
-
for (const hook of hooks) {
|
|
2584
|
-
if (hook.release !== release) {
|
|
2585
|
-
continue;
|
|
2586
|
-
}
|
|
2587
|
-
try {
|
|
2588
|
-
hook.listener(context);
|
|
2589
|
-
} catch (error) {
|
|
2590
|
-
this.notify.emitError("key-intercept-error", error, "[Keymap] Error in key intercept listener:");
|
|
2591
|
-
}
|
|
2592
|
-
if (event.propagationStopped) {
|
|
2593
|
-
return;
|
|
2594
|
-
}
|
|
2595
|
-
}
|
|
2596
|
-
if (release) {
|
|
2597
|
-
this.dispatchReleaseLayers(event);
|
|
2598
|
-
return;
|
|
2599
|
-
}
|
|
2600
|
-
this.dispatchLayers(event);
|
|
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
|
-
}
|
|
2623
|
-
dispatchReleaseLayers(event) {
|
|
2624
|
-
const focused = this.activation.getFocusedTarget();
|
|
2625
|
-
const activeLayers = this.activation.getActiveLayers(focused);
|
|
2626
|
-
const hasLayerConditions = this.state.layers.layersWithConditions > 0;
|
|
2627
|
-
const matchKeys = this.resolveEventMatchKeys(event);
|
|
2628
|
-
layerLoop:
|
|
2629
|
-
for (const layer of activeLayers) {
|
|
2630
|
-
if (layer.compiledBindings.length === 0) {
|
|
2631
|
-
continue;
|
|
2632
|
-
}
|
|
2633
|
-
if (hasLayerConditions && !this.conditions.hasNoConditions(layer) && !this.conditions.matchesConditions(layer)) {
|
|
2634
|
-
continue;
|
|
2635
|
-
}
|
|
2636
|
-
for (const strokeKey of matchKeys) {
|
|
2637
|
-
const result = this.runReleaseBindings(layer, strokeKey, event, focused);
|
|
2638
|
-
if (!result.handled) {
|
|
2639
|
-
continue;
|
|
2640
|
-
}
|
|
2641
|
-
if (result.stop) {
|
|
2642
|
-
return;
|
|
2643
|
-
}
|
|
2644
|
-
continue layerLoop;
|
|
2645
|
-
}
|
|
2646
|
-
}
|
|
2647
|
-
}
|
|
2648
|
-
dispatchLayers(event) {
|
|
2649
|
-
const focused = this.activation.getFocusedTarget();
|
|
2650
|
-
const pending = this.activation.ensureValidPendingSequence();
|
|
2651
|
-
const matchKeys = this.resolveEventMatchKeys(event);
|
|
2652
|
-
if (pending) {
|
|
2653
|
-
this.dispatchPendingSequence(pending, matchKeys, event, focused);
|
|
2654
|
-
return;
|
|
2655
|
-
}
|
|
2656
|
-
const activeLayers = this.activation.getActiveLayers(focused);
|
|
2657
|
-
this.dispatchFromRoot(activeLayers, matchKeys, event, focused);
|
|
2658
|
-
}
|
|
2659
|
-
dispatchPendingSequence(pending, matchKeys, event, focused) {
|
|
2660
|
-
const advancedCaptures = [];
|
|
2661
|
-
for (const capture of pending.captures) {
|
|
2662
|
-
const nextNode = this.getReachableChild(capture.node, matchKeys, focused);
|
|
2663
|
-
if (!nextNode) {
|
|
2664
|
-
continue;
|
|
2665
|
-
}
|
|
2666
|
-
advancedCaptures.push({
|
|
2667
|
-
layer: capture.layer,
|
|
2668
|
-
node: nextNode
|
|
2669
|
-
});
|
|
2670
|
-
}
|
|
2671
|
-
if (advancedCaptures.length === 0) {
|
|
2672
|
-
this.activation.setPendingSequence(null);
|
|
2673
|
-
return;
|
|
2674
|
-
}
|
|
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) {
|
|
2686
|
-
continue;
|
|
2687
|
-
}
|
|
2688
|
-
const continuationCaptures = this.collectPendingCapturesFromAdvanced(advancedCaptures, index);
|
|
2689
|
-
if (this.tryResolvePendingAmbiguity(advancedCaptures, index, continuationCaptures, capture, event, focused, hasHandledExact)) {
|
|
2690
|
-
return;
|
|
2691
|
-
}
|
|
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;
|
|
2700
|
-
}
|
|
2701
|
-
hasHandledExact = true;
|
|
2702
|
-
if (result.stop) {
|
|
2703
|
-
this.activation.setPendingSequence(null);
|
|
2704
|
-
return;
|
|
2705
|
-
}
|
|
2706
|
-
}
|
|
2707
|
-
this.activation.setPendingSequence(null);
|
|
2708
|
-
}
|
|
2709
|
-
dispatchFromRoot(activeLayers, matchKeys, event, focused) {
|
|
2710
|
-
this.dispatchFromRootAtIndex(activeLayers, 0, matchKeys, event, focused);
|
|
2711
|
-
}
|
|
2712
|
-
dispatchFromRootAtIndex(activeLayers, startIndex, matchKeys, event, focused) {
|
|
2713
|
-
const hasLayerConditions = this.state.layers.layersWithConditions > 0;
|
|
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)) {
|
|
2732
|
-
return;
|
|
2733
|
-
}
|
|
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");
|
|
2869
|
-
}
|
|
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)) {
|
|
2968
|
-
return;
|
|
2969
|
-
}
|
|
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`);
|
|
3026
|
-
}
|
|
3027
|
-
collectPendingCapturesFromRoot(activeLayers, startIndex, matchKeys, focused) {
|
|
3028
|
-
const captures = [];
|
|
3029
|
-
const hasLayerConditions = this.state.layers.layersWithConditions > 0;
|
|
3030
|
-
for (let index = startIndex;index < activeLayers.length; index += 1) {
|
|
3031
|
-
const layer = activeLayers[index];
|
|
3032
|
-
if (!layer || layer.root.children.size === 0) {
|
|
3033
|
-
continue;
|
|
3034
|
-
}
|
|
3035
|
-
if (hasLayerConditions && !this.conditions.hasNoConditions(layer) && !this.conditions.matchesConditions(layer)) {
|
|
3036
|
-
continue;
|
|
3037
|
-
}
|
|
3038
|
-
const nextNode = this.getReachableChild(layer.root, matchKeys, focused);
|
|
3039
|
-
if (!nextNode || nextNode.children.size === 0) {
|
|
3040
|
-
continue;
|
|
3041
|
-
}
|
|
3042
|
-
captures.push({
|
|
3043
|
-
layer,
|
|
3044
|
-
node: nextNode
|
|
3045
|
-
});
|
|
3046
|
-
}
|
|
3047
|
-
return captures;
|
|
3048
|
-
}
|
|
3049
|
-
collectPendingCapturesFromAdvanced(advancedCaptures, startIndex) {
|
|
3050
|
-
return advancedCaptures.filter((candidate, candidateIndex) => {
|
|
3051
|
-
return candidateIndex >= startIndex && candidate.node.children.size > 0;
|
|
3052
|
-
});
|
|
3053
|
-
}
|
|
3054
|
-
resolveEventMatchKeys(event) {
|
|
3055
|
-
const resolvers = this.state.dispatch.eventMatchResolvers.values();
|
|
3056
|
-
if (resolvers.length === 0) {
|
|
3057
|
-
return [];
|
|
3058
|
-
}
|
|
3059
|
-
if (resolvers.length === 1) {
|
|
3060
|
-
return resolveSingleEventMatchKeys(resolvers[0], event, this.eventMatchResolverContext, this.notify);
|
|
3061
|
-
}
|
|
3062
|
-
const keys = [];
|
|
3063
|
-
const seen = new Set;
|
|
3064
|
-
for (const resolver of resolvers) {
|
|
3065
|
-
let resolved;
|
|
3066
|
-
try {
|
|
3067
|
-
resolved = resolver(event, this.eventMatchResolverContext);
|
|
3068
|
-
} catch (error) {
|
|
3069
|
-
this.notify.emitError("event-match-resolver-error", error, "[Keymap] Error in event match resolver:");
|
|
3070
|
-
continue;
|
|
3071
|
-
}
|
|
3072
|
-
if (!resolved || resolved.length === 0) {
|
|
3073
|
-
continue;
|
|
3074
|
-
}
|
|
3075
|
-
for (const candidate of resolved) {
|
|
3076
|
-
if (typeof candidate !== "string") {
|
|
3077
|
-
this.notify.emitError("invalid-event-match-resolver-candidate", candidate, "[Keymap] Invalid event match resolver candidate:");
|
|
3078
|
-
continue;
|
|
3079
|
-
}
|
|
3080
|
-
if (seen.has(candidate)) {
|
|
3081
|
-
continue;
|
|
3082
|
-
}
|
|
3083
|
-
seen.add(candidate);
|
|
3084
|
-
keys.push(candidate);
|
|
3085
|
-
}
|
|
3086
|
-
}
|
|
3087
|
-
return keys;
|
|
3088
|
-
}
|
|
3089
|
-
runReleaseBindings(layer, strokeKey, event, focused) {
|
|
3090
|
-
let handled = false;
|
|
3091
|
-
for (const binding of layer.compiledBindings) {
|
|
3092
|
-
if (binding.event !== "release") {
|
|
3093
|
-
continue;
|
|
3094
|
-
}
|
|
3095
|
-
const firstPart = binding.sequence[0];
|
|
3096
|
-
if (!firstPart || firstPart.match !== strokeKey) {
|
|
3097
|
-
continue;
|
|
3098
|
-
}
|
|
3099
|
-
if (!this.conditions.matchesConditions(binding)) {
|
|
3100
|
-
continue;
|
|
3101
|
-
}
|
|
3102
|
-
const bindingHandled = this.executor.runBinding(layer, binding, event, focused);
|
|
3103
|
-
if (!bindingHandled) {
|
|
3104
|
-
continue;
|
|
3105
|
-
}
|
|
3106
|
-
handled = true;
|
|
3107
|
-
if (!binding.fallthrough) {
|
|
3108
|
-
return { handled: true, stop: true };
|
|
3109
|
-
}
|
|
3110
|
-
}
|
|
3111
|
-
return { handled, stop: false };
|
|
3112
|
-
}
|
|
3113
|
-
getReachableChild(node, matchKeys, focused) {
|
|
3114
|
-
for (const strokeKey of matchKeys) {
|
|
3115
|
-
const child = node.children.get(strokeKey);
|
|
3116
|
-
if (!child || !this.activation.nodeHasReachableBindings(child, focused)) {
|
|
3117
|
-
continue;
|
|
3118
|
-
}
|
|
3119
|
-
return child;
|
|
3120
|
-
}
|
|
3121
|
-
return;
|
|
3122
|
-
}
|
|
3123
|
-
runBindings(layer, bindings, event, focused) {
|
|
3124
|
-
let handled = false;
|
|
3125
|
-
for (const binding of bindings) {
|
|
3126
|
-
if (!this.conditions.matchesConditions(binding)) {
|
|
3127
|
-
continue;
|
|
3128
|
-
}
|
|
3129
|
-
const bindingHandled = this.executor.runBinding(layer, binding, event, focused);
|
|
3130
|
-
if (!bindingHandled) {
|
|
3131
|
-
continue;
|
|
3132
|
-
}
|
|
3133
|
-
handled = true;
|
|
3134
|
-
if (!binding.fallthrough) {
|
|
3135
|
-
return { handled: true, stop: true };
|
|
3136
|
-
}
|
|
3137
|
-
}
|
|
3138
|
-
return { handled, stop: false };
|
|
3139
|
-
}
|
|
3140
|
-
}
|
|
3141
|
-
function resolveSingleEventMatchKeys(resolver, event, ctx, notify) {
|
|
3142
|
-
let resolved;
|
|
3143
|
-
try {
|
|
3144
|
-
resolved = resolver(event, ctx);
|
|
3145
|
-
} catch (error) {
|
|
3146
|
-
notify.emitError("event-match-resolver-error", error, "[Keymap] Error in event match resolver:");
|
|
3147
|
-
return [];
|
|
3148
|
-
}
|
|
3149
|
-
if (!resolved || resolved.length === 0) {
|
|
3150
|
-
return [];
|
|
3151
|
-
}
|
|
3152
|
-
if (resolved.length === 1) {
|
|
3153
|
-
const [candidate] = resolved;
|
|
3154
|
-
if (typeof candidate !== "string") {
|
|
3155
|
-
notify.emitError("invalid-event-match-resolver-candidate", candidate, "[Keymap] Invalid event match resolver candidate:");
|
|
3156
|
-
return [];
|
|
3157
|
-
}
|
|
3158
|
-
return [candidate];
|
|
3159
|
-
}
|
|
3160
|
-
const keys = [];
|
|
3161
|
-
const seen = new Set;
|
|
3162
|
-
for (const candidate of resolved) {
|
|
3163
|
-
if (typeof candidate !== "string") {
|
|
3164
|
-
notify.emitError("invalid-event-match-resolver-candidate", candidate, "[Keymap] Invalid event match resolver candidate:");
|
|
3165
|
-
continue;
|
|
3166
|
-
}
|
|
3167
|
-
if (seen.has(candidate)) {
|
|
3168
|
-
continue;
|
|
3169
|
-
}
|
|
3170
|
-
seen.add(candidate);
|
|
3171
|
-
keys.push(candidate);
|
|
3172
|
-
}
|
|
3173
|
-
return keys;
|
|
3174
|
-
}
|
|
3175
|
-
|
|
3176
|
-
// src/services/environment.ts
|
|
3177
|
-
var NOOP = () => {};
|
|
3178
|
-
function registerFieldCompilers(fields, options) {
|
|
3179
|
-
const { kind, reservedFields, registeredFields, emitError } = options;
|
|
3180
|
-
const entries = Object.entries(fields);
|
|
3181
|
-
const registered = [];
|
|
3182
|
-
for (const [name] of entries) {
|
|
3183
|
-
if (reservedFields.has(name)) {
|
|
3184
|
-
emitError(`reserved-${kind}-field`, { field: name, kind }, `Keymap ${kind} field "${name}" is reserved`);
|
|
3185
|
-
continue;
|
|
3186
|
-
}
|
|
3187
|
-
if (registeredFields.has(name)) {
|
|
3188
|
-
emitError(`duplicate-${kind}-field`, { field: name, kind }, `Keymap ${kind} field "${name}" is already registered`);
|
|
3189
|
-
}
|
|
3190
|
-
}
|
|
3191
|
-
for (const [name, compiler] of entries) {
|
|
3192
|
-
if (reservedFields.has(name) || registeredFields.has(name)) {
|
|
3193
|
-
continue;
|
|
3194
|
-
}
|
|
3195
|
-
registeredFields.set(name, compiler);
|
|
3196
|
-
registered.push([name, compiler]);
|
|
3197
|
-
}
|
|
3198
|
-
return () => {
|
|
3199
|
-
for (const [name, compiler] of registered) {
|
|
3200
|
-
const current = registeredFields.get(name);
|
|
3201
|
-
if (current === compiler) {
|
|
3202
|
-
registeredFields.delete(name);
|
|
3203
|
-
}
|
|
3204
|
-
}
|
|
3205
|
-
};
|
|
3206
|
-
}
|
|
3207
|
-
|
|
3208
|
-
class EnvironmentService {
|
|
3209
|
-
state;
|
|
3210
|
-
notify;
|
|
3211
|
-
compiler;
|
|
3212
|
-
layers;
|
|
3213
|
-
constructor(state, notify, compiler, layers) {
|
|
3214
|
-
this.state = state;
|
|
3215
|
-
this.notify = notify;
|
|
3216
|
-
this.compiler = compiler;
|
|
3217
|
-
this.layers = layers;
|
|
3218
|
-
}
|
|
3219
|
-
prependBindingTransformer(transformer) {
|
|
3220
|
-
return this.state.environment.bindingTransformers.prepend(transformer);
|
|
3221
|
-
}
|
|
3222
|
-
appendBindingTransformer(transformer) {
|
|
3223
|
-
return this.state.environment.bindingTransformers.append(transformer);
|
|
3224
|
-
}
|
|
3225
|
-
clearBindingTransformers() {
|
|
3226
|
-
this.state.environment.bindingTransformers.clear();
|
|
3227
|
-
}
|
|
3228
|
-
prependBindingParser(parser) {
|
|
3229
|
-
return this.state.environment.bindingParsers.prepend(parser);
|
|
3230
|
-
}
|
|
3231
|
-
appendBindingParser(parser) {
|
|
3232
|
-
return this.state.environment.bindingParsers.append(parser);
|
|
3233
|
-
}
|
|
3234
|
-
clearBindingParsers() {
|
|
3235
|
-
this.state.environment.bindingParsers.clear();
|
|
3236
|
-
}
|
|
3237
|
-
registerToken(token) {
|
|
3238
|
-
let normalizedToken;
|
|
3239
|
-
try {
|
|
3240
|
-
normalizedToken = normalizeBindingTokenName(token.name);
|
|
3241
|
-
} catch (error) {
|
|
3242
|
-
this.notify.emitError("token-name-normalize-error", error, getErrorMessage(error, "Failed to register keymap token"));
|
|
3243
|
-
return NOOP;
|
|
3244
|
-
}
|
|
3245
|
-
if (this.state.environment.tokens.has(normalizedToken)) {
|
|
3246
|
-
this.notify.emitError("duplicate-token", { token: normalizedToken }, `Keymap token "${normalizedToken}" is already registered`);
|
|
3247
|
-
return NOOP;
|
|
3248
|
-
}
|
|
3249
|
-
let parsedToken;
|
|
3250
|
-
try {
|
|
3251
|
-
parsedToken = this.compiler.parseTokenKey(token.key);
|
|
3252
|
-
} catch (error) {
|
|
3253
|
-
this.notify.emitError("token-parse-error", error, getErrorMessage(error, `Failed to register keymap token "${normalizedToken}"`));
|
|
3254
|
-
return NOOP;
|
|
3255
|
-
}
|
|
3256
|
-
const registeredToken = {
|
|
3257
|
-
stroke: parsedToken.stroke,
|
|
3258
|
-
match: parsedToken.match
|
|
3259
|
-
};
|
|
3260
|
-
const nextTokens = new Map(this.state.environment.tokens);
|
|
3261
|
-
nextTokens.set(normalizedToken, registeredToken);
|
|
3262
|
-
try {
|
|
3263
|
-
this.layers.applyTokenState(nextTokens);
|
|
3264
|
-
} catch (error) {
|
|
3265
|
-
this.notify.emitError("token-register-error", error, getErrorMessage(error, `Failed to register keymap token "${normalizedToken}"`));
|
|
3266
|
-
return NOOP;
|
|
3267
|
-
}
|
|
3268
|
-
return () => {
|
|
3269
|
-
const current = this.state.environment.tokens.get(normalizedToken);
|
|
3270
|
-
if (current !== registeredToken) {
|
|
3271
|
-
return;
|
|
3272
|
-
}
|
|
3273
|
-
const nextTokens2 = new Map(this.state.environment.tokens);
|
|
3274
|
-
nextTokens2.delete(normalizedToken);
|
|
3275
|
-
try {
|
|
3276
|
-
this.layers.applyTokenState(nextTokens2);
|
|
3277
|
-
} catch (error) {
|
|
3278
|
-
this.notify.emitError("token-unregister-error", error, getErrorMessage(error, `Failed to unregister keymap token "${normalizedToken}"`));
|
|
3279
|
-
}
|
|
3280
|
-
};
|
|
3281
|
-
}
|
|
3282
|
-
prependBindingExpander(expander) {
|
|
3283
|
-
return this.state.environment.bindingExpanders.prepend(expander);
|
|
3284
|
-
}
|
|
3285
|
-
appendBindingExpander(expander) {
|
|
3286
|
-
return this.state.environment.bindingExpanders.append(expander);
|
|
3287
|
-
}
|
|
3288
|
-
clearBindingExpanders() {
|
|
3289
|
-
this.state.environment.bindingExpanders.clear();
|
|
3290
|
-
}
|
|
3291
|
-
registerLayerFields(fields) {
|
|
3292
|
-
return registerFieldCompilers(fields, {
|
|
3293
|
-
kind: "layer",
|
|
3294
|
-
reservedFields: RESERVED_LAYER_FIELDS,
|
|
3295
|
-
registeredFields: this.state.environment.layerFields,
|
|
3296
|
-
emitError: (code, error, message) => {
|
|
3297
|
-
this.notify.emitError(code, error, message);
|
|
3298
|
-
}
|
|
3299
|
-
});
|
|
3300
|
-
}
|
|
3301
|
-
registerBindingFields(fields) {
|
|
3302
|
-
return registerFieldCompilers(fields, {
|
|
3303
|
-
kind: "binding",
|
|
3304
|
-
reservedFields: RESERVED_BINDING_FIELDS,
|
|
3305
|
-
registeredFields: this.state.environment.bindingFields,
|
|
3306
|
-
emitError: (code, error, message) => {
|
|
3307
|
-
this.notify.emitError(code, error, message);
|
|
3308
|
-
}
|
|
3309
|
-
});
|
|
3310
|
-
}
|
|
3311
|
-
registerCommandFields(fields) {
|
|
3312
|
-
return registerFieldCompilers(fields, {
|
|
3313
|
-
kind: "command",
|
|
3314
|
-
reservedFields: RESERVED_COMMAND_FIELDS,
|
|
3315
|
-
registeredFields: this.state.environment.commandFields,
|
|
3316
|
-
emitError: (code, error, message) => {
|
|
3317
|
-
this.notify.emitError(code, error, message);
|
|
3318
|
-
}
|
|
3319
|
-
});
|
|
3320
|
-
}
|
|
3321
|
-
}
|
|
3322
|
-
|
|
3323
|
-
// src/services/layers.ts
|
|
3324
|
-
var NOOP2 = () => {};
|
|
3325
|
-
function sortLayers(layers) {
|
|
3326
|
-
return [...layers].sort((left, right) => {
|
|
3327
|
-
const priorityDiff = right.priority - left.priority;
|
|
3328
|
-
if (priorityDiff !== 0) {
|
|
3329
|
-
return priorityDiff;
|
|
3330
|
-
}
|
|
3331
|
-
return right.order - left.order;
|
|
3332
|
-
});
|
|
3333
|
-
}
|
|
3334
|
-
function createCommandLookup(commands) {
|
|
3335
|
-
if (commands.length === 0) {
|
|
3336
|
-
return;
|
|
3337
|
-
}
|
|
3338
|
-
const lookup = new Map;
|
|
3339
|
-
for (const command of commands) {
|
|
3340
|
-
lookup.set(command.name, command);
|
|
3341
|
-
}
|
|
3342
|
-
return lookup;
|
|
3343
|
-
}
|
|
3344
|
-
function addRegisteredCommandNames(target, commands) {
|
|
3345
|
-
for (const command of commands) {
|
|
3346
|
-
target.set(command.name, (target.get(command.name) ?? 0) + 1);
|
|
3347
|
-
}
|
|
3348
|
-
}
|
|
3349
|
-
function removeRegisteredCommandNames(target, commands) {
|
|
3350
|
-
for (const command of commands) {
|
|
3351
|
-
const count = target.get(command.name);
|
|
3352
|
-
if (!count || count <= 1) {
|
|
3353
|
-
target.delete(command.name);
|
|
3354
|
-
continue;
|
|
3355
|
-
}
|
|
3356
|
-
target.set(command.name, count - 1);
|
|
3357
|
-
}
|
|
3358
|
-
}
|
|
3359
|
-
function getSequenceNode(root, sequence) {
|
|
3360
|
-
let node = root;
|
|
3361
|
-
for (const part of sequence) {
|
|
3362
|
-
node = node.children.get(part.match);
|
|
3363
|
-
if (!node) {
|
|
3364
|
-
return;
|
|
3365
|
-
}
|
|
3366
|
-
}
|
|
3367
|
-
return node;
|
|
3368
|
-
}
|
|
3369
|
-
function buildLayerBindingAnalyses(root, compiledBindings) {
|
|
3370
|
-
return compiledBindings.map((binding) => {
|
|
3371
|
-
const node = binding.event === "press" ? getSequenceNode(root, binding.sequence) : undefined;
|
|
3372
|
-
return {
|
|
3373
|
-
sequence: cloneKeySequence(binding.sequence),
|
|
3374
|
-
command: binding.command,
|
|
3375
|
-
attrs: binding.attrs,
|
|
3376
|
-
event: binding.event,
|
|
3377
|
-
preventDefault: binding.preventDefault,
|
|
3378
|
-
fallthrough: binding.fallthrough,
|
|
3379
|
-
sourceBinding: snapshotParsedBindingInput(binding.sourceBinding),
|
|
3380
|
-
sourceTarget: binding.sourceTarget,
|
|
3381
|
-
sourceLayerOrder: binding.sourceLayerOrder,
|
|
3382
|
-
sourceBindingIndex: binding.sourceBindingIndex,
|
|
3383
|
-
hasCommandAtSequence: node ? node.bindings.some((candidate) => candidate.command !== undefined) : false,
|
|
3384
|
-
hasContinuations: node ? node.children.size > 0 : false
|
|
3385
|
-
};
|
|
3386
|
-
});
|
|
3387
|
-
}
|
|
3388
|
-
|
|
3389
|
-
class LayerService {
|
|
3390
|
-
state;
|
|
3391
|
-
notify;
|
|
3392
|
-
conditions;
|
|
3393
|
-
activation;
|
|
3394
|
-
options;
|
|
3395
|
-
constructor(state, notify, conditions, activation, options) {
|
|
3396
|
-
this.state = state;
|
|
3397
|
-
this.notify = notify;
|
|
3398
|
-
this.conditions = conditions;
|
|
3399
|
-
this.activation = activation;
|
|
3400
|
-
this.options = options;
|
|
3401
|
-
}
|
|
3402
|
-
registerLayer(layer) {
|
|
3403
|
-
return this.notify.runWithStateChangeBatch(() => {
|
|
3404
|
-
const target = layer.target;
|
|
3405
|
-
if (target && this.options.host.isTargetDestroyed(target)) {
|
|
3406
|
-
this.notify.emitError("destroyed-layer-target", { target }, "Cannot register a keymap layer for a destroyed keymap target");
|
|
3407
|
-
return NOOP2;
|
|
3408
|
-
}
|
|
3409
|
-
let bindingInputs;
|
|
3410
|
-
let requires;
|
|
3411
|
-
let matchers;
|
|
3412
|
-
let conditionKeys;
|
|
3413
|
-
let hasUnkeyedMatchers;
|
|
3414
|
-
let compileFields;
|
|
3415
|
-
let commands;
|
|
3416
|
-
let commandLookup;
|
|
3417
|
-
let targetMode;
|
|
3418
|
-
try {
|
|
3419
|
-
targetMode = this.normalizeTargetMode(layer);
|
|
3420
|
-
bindingInputs = snapshotBindingInputs(layer.bindings ?? []);
|
|
3421
|
-
commands = !layer.commands || layer.commands.length === 0 ? [] : this.options.commands.normalizeCommands(layer.commands);
|
|
3422
|
-
commandLookup = createCommandLookup(commands);
|
|
3423
|
-
({ requires, matchers, conditionKeys, hasUnkeyedMatchers, compileFields } = this.compileLayerRuntimeState(layer));
|
|
3424
|
-
} catch (error) {
|
|
3425
|
-
this.notify.emitError("register-layer-failed", error, getErrorMessage(error, "Failed to register keymap layer"));
|
|
3426
|
-
return NOOP2;
|
|
3427
|
-
}
|
|
3428
|
-
const order = this.state.core.order++;
|
|
3429
|
-
const compiledBindings = this.options.compiler.compileBindings(bindingInputs, this.state.environment.tokens, target, order, compileFields);
|
|
3430
|
-
if (compiledBindings.bindings.length === 0 && !compiledBindings.hasTokenBindings && commands.length === 0) {
|
|
3431
|
-
return NOOP2;
|
|
3432
|
-
}
|
|
3433
|
-
this.runLayerAnalyzers({
|
|
3434
|
-
target,
|
|
3435
|
-
order,
|
|
3436
|
-
commandLookup,
|
|
3437
|
-
bindingInputs,
|
|
3438
|
-
compiledBindings: compiledBindings.bindings,
|
|
3439
|
-
root: compiledBindings.root,
|
|
3440
|
-
hasTokenBindings: compiledBindings.hasTokenBindings
|
|
3441
|
-
});
|
|
3442
|
-
const registeredLayer = {
|
|
3443
|
-
order,
|
|
3444
|
-
target,
|
|
3445
|
-
targetMode,
|
|
3446
|
-
priority: layer.priority ?? 0,
|
|
3447
|
-
requires,
|
|
3448
|
-
matchers,
|
|
3449
|
-
conditionKeys,
|
|
3450
|
-
hasUnkeyedMatchers,
|
|
3451
|
-
matchCacheDirty: true,
|
|
3452
|
-
compileFields,
|
|
3453
|
-
commands,
|
|
3454
|
-
commandLookup,
|
|
3455
|
-
bindingInputs,
|
|
3456
|
-
compiledBindings: compiledBindings.bindings,
|
|
3457
|
-
hasUnkeyedCommands: commands.some((command) => command.hasUnkeyedMatchers),
|
|
3458
|
-
hasUnkeyedBindings: compiledBindings.bindings.some((binding) => binding.hasUnkeyedMatchers),
|
|
3459
|
-
hasTokenBindings: compiledBindings.hasTokenBindings,
|
|
3460
|
-
root: compiledBindings.root
|
|
3461
|
-
};
|
|
3462
|
-
this.state.layers.layers.add(registeredLayer);
|
|
3463
|
-
if (registeredLayer.commands.length > 0) {
|
|
3464
|
-
this.state.layers.layersWithCommands += 1;
|
|
3465
|
-
this.state.commands.commandMetadataVersion += 1;
|
|
3466
|
-
addRegisteredCommandNames(this.state.commands.registeredNames, registeredLayer.commands);
|
|
3467
|
-
}
|
|
3468
|
-
if (registeredLayer.requires.length > 0 || registeredLayer.matchers.length > 0) {
|
|
3469
|
-
this.state.layers.layersWithConditions += 1;
|
|
3470
|
-
}
|
|
3471
|
-
this.connectRuntimeMatchable(registeredLayer);
|
|
3472
|
-
for (const command of registeredLayer.commands) {
|
|
3473
|
-
this.connectRuntimeMatchable(command);
|
|
3474
|
-
}
|
|
3475
|
-
for (const binding of registeredLayer.compiledBindings) {
|
|
3476
|
-
this.connectRuntimeMatchable(binding);
|
|
3477
|
-
}
|
|
3478
|
-
this.indexLayer(registeredLayer);
|
|
3479
|
-
this.activation.invalidateActiveLayers();
|
|
3480
|
-
this.activation.refreshActiveLayers();
|
|
3481
|
-
if (target) {
|
|
3482
|
-
const onTargetDestroy = () => {
|
|
3483
|
-
this.unregisterLayer(registeredLayer);
|
|
3484
|
-
};
|
|
3485
|
-
registeredLayer.offTargetDestroy = this.options.host.onTargetDestroy(target, onTargetDestroy);
|
|
3486
|
-
}
|
|
3487
|
-
if (registeredLayer.commands.length > 0) {
|
|
3488
|
-
this.activation.ensureValidPendingSequence();
|
|
3489
|
-
}
|
|
3490
|
-
this.notify.queueStateChange();
|
|
3491
|
-
return () => {
|
|
3492
|
-
this.unregisterLayer(registeredLayer);
|
|
3493
|
-
};
|
|
3494
|
-
});
|
|
3495
|
-
}
|
|
3496
|
-
applyTokenState(nextTokens) {
|
|
3497
|
-
this.notify.runWithStateChangeBatch(() => {
|
|
3498
|
-
const nextCompilations = new Map;
|
|
3499
|
-
for (const layer of this.state.layers.layers) {
|
|
3500
|
-
if (!layer.hasTokenBindings) {
|
|
3501
|
-
continue;
|
|
3502
|
-
}
|
|
3503
|
-
nextCompilations.set(layer, this.compileLayerBindings(layer, nextTokens));
|
|
3504
|
-
}
|
|
3505
|
-
this.state.environment.tokens = nextTokens;
|
|
3506
|
-
let shouldClearPending = false;
|
|
3507
|
-
for (const [layer, compilation] of nextCompilations) {
|
|
3508
|
-
if (this.applyCompiledBindings(layer, compilation)) {
|
|
3509
|
-
shouldClearPending = true;
|
|
3510
|
-
}
|
|
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;
|
|
3527
|
-
}
|
|
3528
|
-
const compilation = this.compileLayerBindings(layer, this.state.environment.tokens);
|
|
3529
|
-
if (this.applyCompiledBindings(layer, compilation)) {
|
|
3530
|
-
shouldClearPending = true;
|
|
3531
|
-
}
|
|
3532
|
-
recompiledLayers += 1;
|
|
3533
|
-
}
|
|
3534
|
-
if (shouldClearPending) {
|
|
3535
|
-
this.activation.setPendingSequence(null);
|
|
3536
|
-
}
|
|
3537
|
-
if (recompiledLayers > 0) {
|
|
3538
|
-
this.notify.queueStateChange();
|
|
3539
|
-
}
|
|
3540
|
-
});
|
|
3541
|
-
}
|
|
3542
|
-
prependLayerAnalyzer(analyzer) {
|
|
3543
|
-
return this.state.layers.layerAnalyzers.prepend(analyzer);
|
|
3544
|
-
}
|
|
3545
|
-
appendLayerAnalyzer(analyzer) {
|
|
3546
|
-
return this.state.layers.layerAnalyzers.append(analyzer);
|
|
3547
|
-
}
|
|
3548
|
-
clearLayerAnalyzers() {
|
|
3549
|
-
this.state.layers.layerAnalyzers.clear();
|
|
3550
|
-
}
|
|
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);
|
|
3559
|
-
}
|
|
3560
|
-
layer.offTargetDestroy?.();
|
|
3561
|
-
layer.offTargetDestroy = undefined;
|
|
3562
|
-
}
|
|
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;
|
|
3570
|
-
}
|
|
3571
|
-
return layer.target ? "focus-within" : undefined;
|
|
3572
|
-
}
|
|
3573
|
-
runLayerAnalyzers(options) {
|
|
3574
|
-
const analyzers = this.state.layers.layerAnalyzers.values();
|
|
3575
|
-
if (analyzers.length === 0) {
|
|
3576
|
-
return;
|
|
3577
|
-
}
|
|
3578
|
-
const bindings = buildLayerBindingAnalyses(options.root, options.compiledBindings);
|
|
3579
|
-
const ctx = {
|
|
3580
|
-
target: options.target,
|
|
3581
|
-
order: options.order,
|
|
3582
|
-
bindingInputs: options.bindingInputs,
|
|
3583
|
-
bindings,
|
|
3584
|
-
hasTokenBindings: options.hasTokenBindings,
|
|
3585
|
-
checkCommandResolution: (command) => {
|
|
3586
|
-
return this.options.commands.getCommandResolutionStatus(command, options.commandLookup);
|
|
3587
|
-
},
|
|
3588
|
-
warn: (code, warning, message) => {
|
|
3589
|
-
this.notify.emitWarning(code, warning, message);
|
|
3590
|
-
},
|
|
3591
|
-
warnOnce: (key, code, warning, message) => {
|
|
3592
|
-
this.notify.warnOnce(key, code, warning, message);
|
|
3593
|
-
},
|
|
3594
|
-
error: (code, error, message) => {
|
|
3595
|
-
this.notify.emitError(code, error, message);
|
|
3596
|
-
}
|
|
3597
|
-
};
|
|
3598
|
-
for (const analyzer of analyzers) {
|
|
3599
|
-
try {
|
|
3600
|
-
analyzer(ctx);
|
|
3601
|
-
} catch (error) {
|
|
3602
|
-
this.notify.emitError("layer-analyzer-error", error, "[Keymap] Error in layer analyzer:");
|
|
3603
|
-
}
|
|
3604
|
-
}
|
|
3605
|
-
}
|
|
3606
|
-
compileLayerRuntimeState(layer) {
|
|
3607
|
-
const mergedRequires = {};
|
|
3608
|
-
const matchers = [];
|
|
3609
|
-
const compileFields = Object.create(null);
|
|
3610
|
-
const conditionKeys = new Set;
|
|
3611
|
-
let hasUnkeyedMatchers = false;
|
|
3612
|
-
for (const [fieldName, value] of Object.entries(layer)) {
|
|
3613
|
-
if (RESERVED_LAYER_FIELDS.has(fieldName)) {
|
|
3614
|
-
continue;
|
|
3615
|
-
}
|
|
3616
|
-
if (value === undefined) {
|
|
3617
|
-
continue;
|
|
3618
|
-
}
|
|
3619
|
-
compileFields[fieldName] = snapshotDataValue(value);
|
|
3620
|
-
const compiler = this.state.environment.layerFields.get(fieldName);
|
|
3621
|
-
if (!compiler) {
|
|
3622
|
-
this.options.warnUnknownField("layer", fieldName);
|
|
3623
|
-
continue;
|
|
3624
|
-
}
|
|
3625
|
-
compiler(value, {
|
|
3626
|
-
require: (name, requiredValue) => {
|
|
3627
|
-
mergeRequirement(mergedRequires, name, requiredValue, `field ${fieldName}`);
|
|
3628
|
-
conditionKeys.add(name);
|
|
3629
|
-
},
|
|
3630
|
-
activeWhen: (matcher) => {
|
|
3631
|
-
const runtimeMatcher = this.conditions.buildRuntimeMatcher(matcher, `field ${fieldName}`);
|
|
3632
|
-
if (!runtimeMatcher.cacheable) {
|
|
3633
|
-
hasUnkeyedMatchers = true;
|
|
3634
|
-
}
|
|
3635
|
-
matchers.push(runtimeMatcher);
|
|
3636
|
-
}
|
|
3637
|
-
});
|
|
3638
|
-
}
|
|
3639
|
-
return {
|
|
3640
|
-
requires: Object.entries(mergedRequires),
|
|
3641
|
-
matchers,
|
|
3642
|
-
conditionKeys: [...conditionKeys],
|
|
3643
|
-
hasUnkeyedMatchers,
|
|
3644
|
-
compileFields: Object.keys(compileFields).length > 0 ? Object.freeze(compileFields) : undefined
|
|
3645
|
-
};
|
|
3646
|
-
}
|
|
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);
|
|
3662
|
-
}
|
|
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;
|
|
3671
|
-
}
|
|
3672
|
-
indexLayer(layer) {
|
|
3673
|
-
this.state.layers.sortedLayers = sortLayers([...this.state.layers.sortedLayers, layer]);
|
|
3674
|
-
this.state.layers.activeLayersVersion += 1;
|
|
3675
|
-
}
|
|
3676
|
-
removeLayerFromIndex(layer) {
|
|
3677
|
-
this.state.layers.sortedLayers = this.state.layers.sortedLayers.filter((candidate) => candidate !== layer);
|
|
3678
|
-
this.state.layers.activeLayersVersion += 1;
|
|
3679
|
-
}
|
|
3680
|
-
unregisterLayer(layer) {
|
|
3681
|
-
this.notify.runWithStateChangeBatch(() => {
|
|
3682
|
-
if (!this.state.layers.layers.delete(layer)) {
|
|
3683
|
-
return;
|
|
3684
|
-
}
|
|
3685
|
-
if (layer.requires.length > 0 || layer.matchers.length > 0) {
|
|
3686
|
-
this.state.layers.layersWithConditions -= 1;
|
|
3687
|
-
}
|
|
3688
|
-
if (layer.commands.length > 0) {
|
|
3689
|
-
this.state.layers.layersWithCommands -= 1;
|
|
3690
|
-
this.state.commands.commandMetadataVersion += 1;
|
|
3691
|
-
removeRegisteredCommandNames(this.state.commands.registeredNames, layer.commands);
|
|
3692
|
-
}
|
|
3693
|
-
this.disconnectRuntimeMatchable(layer);
|
|
3694
|
-
for (const command of layer.commands) {
|
|
3695
|
-
this.disconnectRuntimeMatchable(command);
|
|
3696
|
-
}
|
|
3697
|
-
for (const binding of layer.compiledBindings) {
|
|
3698
|
-
this.disconnectRuntimeMatchable(binding);
|
|
3699
|
-
}
|
|
3700
|
-
this.removeLayerFromIndex(layer);
|
|
3701
|
-
this.activation.invalidateActiveLayers();
|
|
3702
|
-
this.activation.refreshActiveLayers();
|
|
3703
|
-
layer.offTargetDestroy?.();
|
|
3704
|
-
layer.offTargetDestroy = undefined;
|
|
3705
|
-
if (this.state.projection.pendingSequence?.captures.some((capture) => capture.layer === layer)) {
|
|
3706
|
-
this.activation.setPendingSequence(null);
|
|
3707
|
-
} else if (layer.commands.length > 0 && !this.options.host.isDestroyed) {
|
|
3708
|
-
this.activation.ensureValidPendingSequence();
|
|
3709
|
-
}
|
|
3710
|
-
this.notify.queueStateChange();
|
|
3711
|
-
});
|
|
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
|
-
}
|
|
3756
|
-
}
|
|
3757
|
-
|
|
3758
|
-
// src/lib/emitter.ts
|
|
3759
|
-
class Emitter {
|
|
3760
|
-
onError;
|
|
3761
|
-
listeners = Object.create(null);
|
|
3762
|
-
constructor(onError) {
|
|
3763
|
-
this.onError = onError;
|
|
3764
|
-
}
|
|
3765
|
-
hook(name, listener) {
|
|
3766
|
-
const current = this.listeners[name] ?? [];
|
|
3767
|
-
this.listeners[name] = [...current, listener];
|
|
3768
|
-
return () => {
|
|
3769
|
-
const current2 = this.listeners[name];
|
|
3770
|
-
if (!current2 || current2.length === 0) {
|
|
3771
|
-
return;
|
|
3772
|
-
}
|
|
3773
|
-
const next = current2.filter((candidate) => candidate !== listener);
|
|
3774
|
-
if (next.length === 0) {
|
|
3775
|
-
delete this.listeners[name];
|
|
3776
|
-
return;
|
|
3777
|
-
}
|
|
3778
|
-
this.listeners[name] = next;
|
|
3779
|
-
};
|
|
3780
|
-
}
|
|
3781
|
-
has(name) {
|
|
3782
|
-
return (this.listeners[name]?.length ?? 0) > 0;
|
|
3783
|
-
}
|
|
3784
|
-
off(name, listener) {
|
|
3785
|
-
const current = this.listeners[name];
|
|
3786
|
-
if (!current || current.length === 0) {
|
|
3787
|
-
return;
|
|
3788
|
-
}
|
|
3789
|
-
const next = current.filter((candidate) => candidate !== listener);
|
|
3790
|
-
if (next.length === current.length) {
|
|
3791
|
-
return;
|
|
3792
|
-
}
|
|
3793
|
-
if (next.length === 0) {
|
|
3794
|
-
delete this.listeners[name];
|
|
3795
|
-
return;
|
|
3796
|
-
}
|
|
3797
|
-
this.listeners[name] = next;
|
|
3798
|
-
}
|
|
3799
|
-
clear() {
|
|
3800
|
-
this.listeners = Object.create(null);
|
|
3801
|
-
}
|
|
3802
|
-
emit(name, ...args) {
|
|
3803
|
-
const listeners = this.listeners[name];
|
|
3804
|
-
if (!listeners || listeners.length === 0) {
|
|
3805
|
-
return;
|
|
3806
|
-
}
|
|
3807
|
-
for (const listener of listeners) {
|
|
3808
|
-
try {
|
|
3809
|
-
if (args.length === 0) {
|
|
3810
|
-
listener();
|
|
3811
|
-
} else {
|
|
3812
|
-
listener(args[0]);
|
|
3813
|
-
}
|
|
3814
|
-
} catch (error) {
|
|
3815
|
-
this.onError(name, error);
|
|
3816
|
-
}
|
|
3817
|
-
}
|
|
3818
|
-
}
|
|
3819
|
-
}
|
|
3820
|
-
|
|
3821
|
-
// src/services/notify.ts
|
|
3822
|
-
var MAX_STATE_CHANGE_FLUSH_ITERATIONS = 1000;
|
|
3823
|
-
|
|
3824
|
-
class NotificationService {
|
|
3825
|
-
state;
|
|
3826
|
-
events;
|
|
3827
|
-
hooks;
|
|
3828
|
-
constructor(state, events, hooks) {
|
|
3829
|
-
this.state = state;
|
|
3830
|
-
this.events = events;
|
|
3831
|
-
this.hooks = hooks;
|
|
3832
|
-
}
|
|
3833
|
-
runWithStateChangeBatch(fn) {
|
|
3834
|
-
this.state.notify.stateChangeDepth += 1;
|
|
3835
|
-
try {
|
|
3836
|
-
return fn();
|
|
3837
|
-
} finally {
|
|
3838
|
-
this.state.notify.stateChangeDepth -= 1;
|
|
3839
|
-
if (this.state.notify.stateChangeDepth === 0) {
|
|
3840
|
-
this.flushStateChange();
|
|
3841
|
-
}
|
|
3842
|
-
}
|
|
3843
|
-
}
|
|
3844
|
-
queueStateChange() {
|
|
3845
|
-
this.state.notify.derivedStateVersion += 1;
|
|
3846
|
-
if (!this.hooks.has("state")) {
|
|
3847
|
-
return;
|
|
3848
|
-
}
|
|
3849
|
-
this.state.notify.stateChangePending = true;
|
|
3850
|
-
if (this.state.notify.stateChangeDepth === 0 && !this.state.notify.flushingStateChange) {
|
|
3851
|
-
this.flushStateChange();
|
|
3852
|
-
}
|
|
3853
|
-
}
|
|
3854
|
-
emitWarning(code, warning, message) {
|
|
3855
|
-
if (!this.events.has("warning")) {
|
|
3856
|
-
const consoleMessage = `[${code}] ${message}`;
|
|
3857
|
-
if (warning instanceof Error) {
|
|
3858
|
-
console.warn(consoleMessage, warning);
|
|
3859
|
-
} else {
|
|
3860
|
-
console.warn(consoleMessage);
|
|
3861
|
-
}
|
|
3862
|
-
return;
|
|
3863
|
-
}
|
|
3864
|
-
this.events.emit("warning", { code, message, warning });
|
|
3865
|
-
}
|
|
3866
|
-
emitError(code, error, message) {
|
|
3867
|
-
if (!this.events.has("error")) {
|
|
3868
|
-
const consoleMessage = `[${code}] ${message}`;
|
|
3869
|
-
if (error instanceof Error) {
|
|
3870
|
-
console.error(consoleMessage, error);
|
|
3871
|
-
} else {
|
|
3872
|
-
console.error(consoleMessage);
|
|
3873
|
-
}
|
|
3874
|
-
return;
|
|
3875
|
-
}
|
|
3876
|
-
this.events.emit("error", { code, message, error });
|
|
3877
|
-
}
|
|
3878
|
-
reportListenerError(name, error) {
|
|
3879
|
-
if (name === "state") {
|
|
3880
|
-
this.emitError("state-listener-error", error, "[Keymap] Error in state listener:");
|
|
3881
|
-
return;
|
|
3882
|
-
}
|
|
3883
|
-
if (name === "pendingSequence") {
|
|
3884
|
-
this.emitError("pending-sequence-listener-error", error, "[Keymap] Error in pending sequence listener:");
|
|
3885
|
-
return;
|
|
3886
|
-
}
|
|
3887
|
-
}
|
|
3888
|
-
warnOnce(key, code, warning, message) {
|
|
3889
|
-
if (this.state.notify.usedWarningKeys.has(key)) {
|
|
3890
|
-
return;
|
|
3891
|
-
}
|
|
3892
|
-
this.state.notify.usedWarningKeys.add(key);
|
|
3893
|
-
this.emitWarning(code, warning, message);
|
|
3894
|
-
}
|
|
3895
|
-
flushStateChange() {
|
|
3896
|
-
if (!this.state.notify.stateChangePending || this.state.notify.stateChangeDepth > 0 || this.state.notify.flushingStateChange) {
|
|
3897
|
-
return;
|
|
3898
|
-
}
|
|
3899
|
-
this.state.notify.flushingStateChange = true;
|
|
3900
|
-
try {
|
|
3901
|
-
let iterations = 0;
|
|
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;
|
|
3909
|
-
this.state.notify.stateChangePending = false;
|
|
3910
|
-
this.hooks.emit("state");
|
|
3911
|
-
}
|
|
3912
|
-
} finally {
|
|
3913
|
-
this.state.notify.flushingStateChange = false;
|
|
3914
|
-
}
|
|
3915
|
-
}
|
|
3916
|
-
}
|
|
3917
|
-
|
|
3918
|
-
// src/services/runtime.ts
|
|
3919
|
-
class RuntimeService {
|
|
3920
|
-
state;
|
|
3921
|
-
notify;
|
|
3922
|
-
conditions;
|
|
3923
|
-
activation;
|
|
3924
|
-
constructor(state, notify, conditions, activation) {
|
|
3925
|
-
this.state = state;
|
|
3926
|
-
this.notify = notify;
|
|
3927
|
-
this.conditions = conditions;
|
|
3928
|
-
this.activation = activation;
|
|
3929
|
-
}
|
|
3930
|
-
getData(name) {
|
|
3931
|
-
return this.state.runtime.data[name];
|
|
3932
|
-
}
|
|
3933
|
-
setData(name, value) {
|
|
3934
|
-
this.notify.runWithStateChangeBatch(() => {
|
|
3935
|
-
if (value === undefined) {
|
|
3936
|
-
if (!(name in this.state.runtime.data)) {
|
|
3937
|
-
return;
|
|
3938
|
-
}
|
|
3939
|
-
delete this.state.runtime.data[name];
|
|
3940
|
-
this.state.runtime.dataVersion += 1;
|
|
3941
|
-
this.conditions.invalidateRuntimeConditionKey(name);
|
|
3942
|
-
this.activation.ensureValidPendingSequence();
|
|
3943
|
-
this.notify.queueStateChange();
|
|
3944
|
-
return;
|
|
3945
|
-
}
|
|
3946
|
-
if (Object.is(this.state.runtime.data[name], value)) {
|
|
3947
|
-
return;
|
|
3948
|
-
}
|
|
3949
|
-
this.state.runtime.data[name] = value;
|
|
3950
|
-
this.state.runtime.dataVersion += 1;
|
|
3951
|
-
this.conditions.invalidateRuntimeConditionKey(name);
|
|
3952
|
-
this.activation.ensureValidPendingSequence();
|
|
3953
|
-
this.notify.queueStateChange();
|
|
3954
|
-
});
|
|
3955
|
-
}
|
|
3956
|
-
getReadonlyData() {
|
|
3957
|
-
if (this.state.runtime.readonlyDataVersion === this.state.runtime.dataVersion) {
|
|
3958
|
-
return this.state.runtime.readonlyData;
|
|
3959
|
-
}
|
|
3960
|
-
this.state.runtime.readonlyData = Object.freeze({ ...this.state.runtime.data });
|
|
3961
|
-
this.state.runtime.readonlyDataVersion = this.state.runtime.dataVersion;
|
|
3962
|
-
return this.state.runtime.readonlyData;
|
|
3963
|
-
}
|
|
3964
|
-
}
|
|
3965
|
-
|
|
3966
|
-
// src/lib/registry.ts
|
|
3967
|
-
class CopyOnWriteRegistry {
|
|
3968
|
-
items = [];
|
|
3969
|
-
getItems() {
|
|
3970
|
-
return this.items;
|
|
3971
|
-
}
|
|
3972
|
-
setItems(items) {
|
|
3973
|
-
this.items = items;
|
|
3974
|
-
}
|
|
3975
|
-
removeItem(value) {
|
|
3976
|
-
const current = this.items;
|
|
3977
|
-
if (current.length === 0) {
|
|
3978
|
-
return false;
|
|
3979
|
-
}
|
|
3980
|
-
const next = current.filter((candidate) => candidate !== value);
|
|
3981
|
-
if (next.length === current.length) {
|
|
3982
|
-
return false;
|
|
3983
|
-
}
|
|
3984
|
-
this.items = next;
|
|
3985
|
-
return true;
|
|
3986
|
-
}
|
|
3987
|
-
has() {
|
|
3988
|
-
return this.items.length > 0;
|
|
3989
|
-
}
|
|
3990
|
-
clear() {
|
|
3991
|
-
this.items = [];
|
|
3992
|
-
}
|
|
3993
|
-
}
|
|
3994
|
-
|
|
3995
|
-
class OrderedRegistry extends CopyOnWriteRegistry {
|
|
3996
|
-
append(value) {
|
|
3997
|
-
this.setItems([...this.getItems(), value]);
|
|
3998
|
-
return () => {
|
|
3999
|
-
this.remove(value);
|
|
4000
|
-
};
|
|
4001
|
-
}
|
|
4002
|
-
prepend(value) {
|
|
4003
|
-
this.setItems([value, ...this.getItems()]);
|
|
4004
|
-
return () => {
|
|
4005
|
-
this.remove(value);
|
|
4006
|
-
};
|
|
4007
|
-
}
|
|
4008
|
-
remove(value) {
|
|
4009
|
-
return this.removeItem(value);
|
|
4010
|
-
}
|
|
4011
|
-
values() {
|
|
4012
|
-
return this.getItems();
|
|
4013
|
-
}
|
|
4014
|
-
}
|
|
4015
|
-
|
|
4016
|
-
class PriorityRegistry extends CopyOnWriteRegistry {
|
|
4017
|
-
order = 0;
|
|
4018
|
-
register(listener, options) {
|
|
4019
|
-
const registered = { ...options, listener, order: this.order++ };
|
|
4020
|
-
this.setItems([...this.getItems(), registered].sort((left, right) => {
|
|
4021
|
-
const priorityDiff = right.priority - left.priority;
|
|
4022
|
-
if (priorityDiff !== 0) {
|
|
4023
|
-
return priorityDiff;
|
|
4024
|
-
}
|
|
4025
|
-
return left.order - right.order;
|
|
4026
|
-
}));
|
|
4027
|
-
return () => {
|
|
4028
|
-
this.removeItem(registered);
|
|
4029
|
-
};
|
|
4030
|
-
}
|
|
4031
|
-
entries() {
|
|
4032
|
-
return this.getItems();
|
|
4033
|
-
}
|
|
4034
|
-
}
|
|
4035
|
-
|
|
4036
|
-
// src/services/state.ts
|
|
4037
|
-
var EMPTY_DATA = Object.freeze({});
|
|
4038
|
-
function createKeymapState() {
|
|
4039
|
-
return {
|
|
4040
|
-
core: {
|
|
4041
|
-
order: 0
|
|
4042
|
-
},
|
|
4043
|
-
environment: {
|
|
4044
|
-
tokens: new Map,
|
|
4045
|
-
layerFields: new Map,
|
|
4046
|
-
bindingExpanders: new OrderedRegistry,
|
|
4047
|
-
bindingParsers: new OrderedRegistry,
|
|
4048
|
-
bindingTransformers: new OrderedRegistry,
|
|
4049
|
-
bindingFields: new Map,
|
|
4050
|
-
commandFields: new Map
|
|
4051
|
-
},
|
|
4052
|
-
dispatch: {
|
|
4053
|
-
eventMatchResolvers: new OrderedRegistry,
|
|
4054
|
-
disambiguationResolvers: new OrderedRegistry,
|
|
4055
|
-
keyHooks: new PriorityRegistry,
|
|
4056
|
-
rawHooks: new PriorityRegistry
|
|
4057
|
-
},
|
|
4058
|
-
layers: {
|
|
4059
|
-
layers: new Set,
|
|
4060
|
-
sortedLayers: [],
|
|
4061
|
-
activeLayersVersion: 0,
|
|
4062
|
-
activeLayersCacheVersion: -1,
|
|
4063
|
-
activeLayersCacheFocused: undefined,
|
|
4064
|
-
activeLayersCache: [],
|
|
4065
|
-
layersWithConditions: 0,
|
|
4066
|
-
layersWithCommands: 0,
|
|
4067
|
-
layerAnalyzers: new OrderedRegistry
|
|
4068
|
-
},
|
|
4069
|
-
commands: {
|
|
4070
|
-
commandMetadataVersion: 0,
|
|
4071
|
-
registeredNames: new Map,
|
|
4072
|
-
commandResolvers: new OrderedRegistry,
|
|
4073
|
-
activeCommandViewVersion: -1,
|
|
4074
|
-
activeCommandView: undefined,
|
|
4075
|
-
registeredCommandViewVersion: -1,
|
|
4076
|
-
registeredCommandView: undefined,
|
|
4077
|
-
registeredCommandEntriesCacheVersion: -1,
|
|
4078
|
-
registeredCommandEntriesCache: []
|
|
4079
|
-
},
|
|
4080
|
-
projection: {
|
|
4081
|
-
pendingSequence: null,
|
|
4082
|
-
pendingSequenceCacheVersion: -1,
|
|
4083
|
-
pendingSequenceCache: [],
|
|
4084
|
-
activeKeysPlainCacheVersion: -1,
|
|
4085
|
-
activeKeysPlainCache: [],
|
|
4086
|
-
activeKeysBindingsCacheVersion: -1,
|
|
4087
|
-
activeKeysBindingsCache: [],
|
|
4088
|
-
activeKeysMetadataCacheVersion: -1,
|
|
4089
|
-
activeKeysMetadataCache: [],
|
|
4090
|
-
activeKeysBindingsAndMetadataCacheVersion: -1,
|
|
4091
|
-
activeKeysBindingsAndMetadataCache: []
|
|
4092
|
-
},
|
|
4093
|
-
conditions: {
|
|
4094
|
-
runtimeKeyDependents: new Map
|
|
4095
|
-
},
|
|
4096
|
-
runtime: {
|
|
4097
|
-
data: {},
|
|
4098
|
-
dataVersion: 0,
|
|
4099
|
-
readonlyDataVersion: -1,
|
|
4100
|
-
readonlyData: EMPTY_DATA
|
|
4101
|
-
},
|
|
4102
|
-
notify: {
|
|
4103
|
-
derivedStateVersion: 0,
|
|
4104
|
-
stateChangeDepth: 0,
|
|
4105
|
-
stateChangePending: false,
|
|
4106
|
-
flushingStateChange: false,
|
|
4107
|
-
usedWarningKeys: new Set
|
|
4108
|
-
}
|
|
4109
|
-
};
|
|
4110
|
-
}
|
|
4111
|
-
|
|
4112
|
-
// src/keymap.ts
|
|
4113
|
-
function getKeyMatchKey(input) {
|
|
4114
|
-
return resolveKeyMatch(input);
|
|
4115
|
-
}
|
|
4116
|
-
|
|
4117
|
-
class Keymap {
|
|
4118
|
-
host;
|
|
4119
|
-
state = createKeymapState();
|
|
4120
|
-
cleanedUp = false;
|
|
4121
|
-
resources = new Map;
|
|
4122
|
-
cleanupListeners = [];
|
|
4123
|
-
events = new Emitter(() => {});
|
|
4124
|
-
hooks;
|
|
4125
|
-
notify;
|
|
4126
|
-
activation;
|
|
4127
|
-
runtime;
|
|
4128
|
-
conditions;
|
|
4129
|
-
catalog;
|
|
4130
|
-
executor;
|
|
4131
|
-
compiler;
|
|
4132
|
-
dispatch;
|
|
4133
|
-
layers;
|
|
4134
|
-
environment;
|
|
4135
|
-
keypressListener;
|
|
4136
|
-
keyreleaseListener;
|
|
4137
|
-
rawListener;
|
|
4138
|
-
focusedTargetListener;
|
|
4139
|
-
constructor(host) {
|
|
4140
|
-
this.host = host;
|
|
4141
|
-
if (host.isDestroyed) {
|
|
4142
|
-
throw new Error("Cannot create a keymap for a destroyed host");
|
|
4143
|
-
}
|
|
4144
|
-
this.hooks = new Emitter((name, error) => {
|
|
4145
|
-
this.notify.reportListenerError(name, error);
|
|
4146
|
-
});
|
|
4147
|
-
this.notify = new NotificationService(this.state, this.events, this.hooks);
|
|
4148
|
-
this.conditions = new ConditionService(this.state, this.notify);
|
|
4149
|
-
this.catalog = new CommandCatalogService(this.state, this.host, this.notify, this.conditions, {
|
|
4150
|
-
onCommandResolversChanged: () => {
|
|
4151
|
-
this.activation.ensureValidPendingSequence();
|
|
4152
|
-
}
|
|
4153
|
-
});
|
|
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
|
-
});
|
|
4159
|
-
this.runtime = new RuntimeService(this.state, this.notify, this.conditions, this.activation);
|
|
4160
|
-
this.executor = new CommandExecutorService(this.notify, this.runtime, this.activation, this.catalog, {
|
|
4161
|
-
keymap: this,
|
|
4162
|
-
createCommandEvent: () => this.host.createCommandEvent()
|
|
4163
|
-
});
|
|
4164
|
-
this.compiler = new CompilerService(this.state, this.notify, this.conditions, {
|
|
4165
|
-
warnUnknownField: (kind, fieldName) => {
|
|
4166
|
-
this.warnUnknownField(kind, fieldName);
|
|
4167
|
-
},
|
|
4168
|
-
warnUnknownToken: (token, sequence) => {
|
|
4169
|
-
this.warnUnknownToken(token, sequence);
|
|
4170
|
-
}
|
|
4171
|
-
});
|
|
4172
|
-
this.layers = new LayerService(this.state, this.notify, this.conditions, this.activation, {
|
|
4173
|
-
compiler: this.compiler,
|
|
4174
|
-
commands: this.catalog,
|
|
4175
|
-
host: this.host,
|
|
4176
|
-
warnUnknownField: (kind, fieldName) => {
|
|
4177
|
-
this.warnUnknownField(kind, fieldName);
|
|
4178
|
-
}
|
|
4179
|
-
});
|
|
4180
|
-
this.environment = new EnvironmentService(this.state, this.notify, this.compiler, this.layers);
|
|
4181
|
-
this.dispatch = new DispatchService(this.state, this.notify, this.runtime, this.activation, this.conditions, this.executor, this.compiler, this.catalog, this.layers);
|
|
4182
|
-
this.keypressListener = (event) => {
|
|
4183
|
-
this.dispatch.handleKeyEvent(event, false);
|
|
4184
|
-
};
|
|
4185
|
-
this.keyreleaseListener = (event) => {
|
|
4186
|
-
this.dispatch.handleKeyEvent(event, true);
|
|
4187
|
-
};
|
|
4188
|
-
this.rawListener = (sequence) => {
|
|
4189
|
-
return this.dispatch.handleRawSequence(sequence);
|
|
4190
|
-
};
|
|
4191
|
-
this.focusedTargetListener = (focused) => {
|
|
4192
|
-
this.handleFocusedTargetChange(focused);
|
|
4193
|
-
};
|
|
4194
|
-
this.cleanupListeners.push(this.host.onKeyPress(this.keypressListener));
|
|
4195
|
-
this.cleanupListeners.push(this.host.onKeyRelease(this.keyreleaseListener));
|
|
4196
|
-
if (this.host.onRawInput) {
|
|
4197
|
-
this.cleanupListeners.push(this.host.onRawInput(this.rawListener));
|
|
4198
|
-
}
|
|
4199
|
-
this.cleanupListeners.push(this.host.onFocusChange(this.focusedTargetListener));
|
|
4200
|
-
if (this.host.onDestroy) {
|
|
4201
|
-
this.cleanupListeners.push(this.host.onDestroy(() => {
|
|
4202
|
-
this.cleanup();
|
|
4203
|
-
}));
|
|
4204
|
-
}
|
|
4205
|
-
}
|
|
4206
|
-
cleanup() {
|
|
4207
|
-
if (this.cleanedUp) {
|
|
4208
|
-
return;
|
|
4209
|
-
}
|
|
4210
|
-
this.cleanedUp = true;
|
|
4211
|
-
this.activation.setPendingSequence(null);
|
|
4212
|
-
for (const resource of this.resources.values()) {
|
|
4213
|
-
resource.dispose();
|
|
4214
|
-
}
|
|
4215
|
-
this.resources.clear();
|
|
4216
|
-
this.layers.cleanup();
|
|
4217
|
-
for (const cleanupListener of this.cleanupListeners.splice(0)) {
|
|
4218
|
-
cleanupListener();
|
|
4219
|
-
}
|
|
4220
|
-
}
|
|
4221
|
-
setData(name, value) {
|
|
4222
|
-
this.runtime.setData(name, value);
|
|
4223
|
-
}
|
|
4224
|
-
getData(name) {
|
|
4225
|
-
return this.runtime.getData(name);
|
|
4226
|
-
}
|
|
4227
|
-
hasPendingSequence() {
|
|
4228
|
-
return this.activation.ensureValidPendingSequence() !== undefined;
|
|
4229
|
-
}
|
|
4230
|
-
getPendingSequence() {
|
|
4231
|
-
return this.activation.getPendingSequence();
|
|
4232
|
-
}
|
|
4233
|
-
createKeyMatcher(key) {
|
|
4234
|
-
const match = this.compiler.parseTokenKey(key).match;
|
|
4235
|
-
return (input) => {
|
|
4236
|
-
if (!input) {
|
|
4237
|
-
return false;
|
|
4238
|
-
}
|
|
4239
|
-
return getKeyMatchKey(input) === match;
|
|
4240
|
-
};
|
|
4241
|
-
}
|
|
4242
|
-
clearPendingSequence() {
|
|
4243
|
-
this.activation.setPendingSequence(null);
|
|
4244
|
-
}
|
|
4245
|
-
popPendingSequence() {
|
|
4246
|
-
return this.activation.popPendingSequence();
|
|
4247
|
-
}
|
|
4248
|
-
getActiveKeys(options) {
|
|
4249
|
-
return this.activation.getActiveKeys(options);
|
|
4250
|
-
}
|
|
4251
|
-
getCommands(query) {
|
|
4252
|
-
return this.catalog.getCommands(query);
|
|
4253
|
-
}
|
|
4254
|
-
getCommandEntries(query) {
|
|
4255
|
-
return this.catalog.getCommandEntries(query);
|
|
4256
|
-
}
|
|
4257
|
-
normalizeCommandName(name) {
|
|
4258
|
-
return normalizeCommandName(name);
|
|
4259
|
-
}
|
|
4260
|
-
normalizeBindings(bindings) {
|
|
4261
|
-
return normalizeBindingInputs(bindings);
|
|
4262
|
-
}
|
|
4263
|
-
acquireResource(key, setup) {
|
|
4264
|
-
if (this.cleanedUp || this.host.isDestroyed) {
|
|
4265
|
-
throw new Error("Cannot use a keymap after its host was destroyed");
|
|
4266
|
-
}
|
|
4267
|
-
const existing = this.resources.get(key);
|
|
4268
|
-
if (existing) {
|
|
4269
|
-
existing.count += 1;
|
|
4270
|
-
return () => {
|
|
4271
|
-
this.releaseResource(key, existing);
|
|
4272
|
-
};
|
|
4273
|
-
}
|
|
4274
|
-
const dispose = setup();
|
|
4275
|
-
const resource = { count: 1, dispose };
|
|
4276
|
-
this.resources.set(key, resource);
|
|
4277
|
-
return () => {
|
|
4278
|
-
this.releaseResource(key, resource);
|
|
4279
|
-
};
|
|
4280
|
-
}
|
|
4281
|
-
runCommand(cmd, options) {
|
|
4282
|
-
return this.executor.runCommand(cmd, options);
|
|
4283
|
-
}
|
|
4284
|
-
dispatchCommand(cmd, options) {
|
|
4285
|
-
return this.executor.dispatchCommand(cmd, options);
|
|
4286
|
-
}
|
|
4287
|
-
on(name, fn) {
|
|
4288
|
-
if (name === "warning") {
|
|
4289
|
-
return this.events.hook(name, fn);
|
|
4290
|
-
}
|
|
4291
|
-
if (name === "error") {
|
|
4292
|
-
return this.events.hook(name, fn);
|
|
4293
|
-
}
|
|
4294
|
-
return this.hooks.hook(name, fn);
|
|
4295
|
-
}
|
|
4296
|
-
intercept(name, fn, options) {
|
|
4297
|
-
if (name === "key") {
|
|
4298
|
-
return this.dispatch.intercept(name, fn, options);
|
|
4299
|
-
}
|
|
4300
|
-
return this.dispatch.intercept(name, fn, options);
|
|
4301
|
-
}
|
|
4302
|
-
registerLayer(layer) {
|
|
4303
|
-
return this.layers.registerLayer(layer);
|
|
4304
|
-
}
|
|
4305
|
-
registerLayerFields(fields) {
|
|
4306
|
-
return this.environment.registerLayerFields(fields);
|
|
4307
|
-
}
|
|
4308
|
-
prependBindingTransformer(transformer) {
|
|
4309
|
-
return this.environment.prependBindingTransformer(transformer);
|
|
4310
|
-
}
|
|
4311
|
-
appendBindingTransformer(transformer) {
|
|
4312
|
-
return this.environment.appendBindingTransformer(transformer);
|
|
4313
|
-
}
|
|
4314
|
-
clearBindingTransformers() {
|
|
4315
|
-
this.environment.clearBindingTransformers();
|
|
4316
|
-
}
|
|
4317
|
-
prependBindingParser(parser) {
|
|
4318
|
-
return this.environment.prependBindingParser(parser);
|
|
4319
|
-
}
|
|
4320
|
-
appendBindingParser(parser) {
|
|
4321
|
-
return this.environment.appendBindingParser(parser);
|
|
4322
|
-
}
|
|
4323
|
-
clearBindingParsers() {
|
|
4324
|
-
this.environment.clearBindingParsers();
|
|
4325
|
-
}
|
|
4326
|
-
registerToken(token) {
|
|
4327
|
-
return this.environment.registerToken(token);
|
|
4328
|
-
}
|
|
4329
|
-
prependBindingExpander(expander) {
|
|
4330
|
-
return this.environment.prependBindingExpander(expander);
|
|
4331
|
-
}
|
|
4332
|
-
appendBindingExpander(expander) {
|
|
4333
|
-
return this.environment.appendBindingExpander(expander);
|
|
4334
|
-
}
|
|
4335
|
-
clearBindingExpanders() {
|
|
4336
|
-
this.environment.clearBindingExpanders();
|
|
4337
|
-
}
|
|
4338
|
-
registerBindingFields(fields) {
|
|
4339
|
-
return this.environment.registerBindingFields(fields);
|
|
4340
|
-
}
|
|
4341
|
-
registerCommandFields(fields) {
|
|
4342
|
-
return this.environment.registerCommandFields(fields);
|
|
4343
|
-
}
|
|
4344
|
-
prependCommandResolver(resolver) {
|
|
4345
|
-
return this.catalog.prependCommandResolver(resolver);
|
|
4346
|
-
}
|
|
4347
|
-
appendCommandResolver(resolver) {
|
|
4348
|
-
return this.catalog.appendCommandResolver(resolver);
|
|
4349
|
-
}
|
|
4350
|
-
clearCommandResolvers() {
|
|
4351
|
-
this.catalog.clearCommandResolvers();
|
|
4352
|
-
}
|
|
4353
|
-
prependLayerAnalyzer(analyzer) {
|
|
4354
|
-
return this.layers.prependLayerAnalyzer(analyzer);
|
|
4355
|
-
}
|
|
4356
|
-
appendLayerAnalyzer(analyzer) {
|
|
4357
|
-
return this.layers.appendLayerAnalyzer(analyzer);
|
|
4358
|
-
}
|
|
4359
|
-
clearLayerAnalyzers() {
|
|
4360
|
-
this.layers.clearLayerAnalyzers();
|
|
4361
|
-
}
|
|
4362
|
-
prependEventMatchResolver(resolver) {
|
|
4363
|
-
return this.dispatch.prependEventMatchResolver(resolver);
|
|
4364
|
-
}
|
|
4365
|
-
appendEventMatchResolver(resolver) {
|
|
4366
|
-
return this.dispatch.appendEventMatchResolver(resolver);
|
|
4367
|
-
}
|
|
4368
|
-
clearEventMatchResolvers() {
|
|
4369
|
-
this.dispatch.clearEventMatchResolvers();
|
|
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
|
-
}
|
|
4380
|
-
handleFocusedTargetChange(_focused) {
|
|
4381
|
-
this.notify.runWithStateChangeBatch(() => {
|
|
4382
|
-
this.activation.setPendingSequence(null);
|
|
4383
|
-
this.activation.invalidateActiveLayers();
|
|
4384
|
-
this.activation.refreshActiveLayers(_focused);
|
|
4385
|
-
this.notify.queueStateChange();
|
|
4386
|
-
});
|
|
4387
|
-
}
|
|
4388
|
-
warnUnknownField(kind, fieldName) {
|
|
4389
|
-
this.notify.warnOnce(`${kind}:${fieldName}`, `unknown-${kind}-field`, { field: fieldName, kind }, `[Keymap] Unknown ${kind} field "${fieldName}" was ignored`);
|
|
4390
|
-
}
|
|
4391
|
-
warnUnknownToken(token, sequence) {
|
|
4392
|
-
this.notify.warnOnce(`token:${token}`, "unknown-token", { token, sequence }, `[Keymap] Unknown token "${token}" in key sequence "${sequence}" was ignored`);
|
|
4393
|
-
}
|
|
4394
|
-
releaseResource(key, resource) {
|
|
4395
|
-
const current = this.resources.get(key);
|
|
4396
|
-
if (current !== resource) {
|
|
4397
|
-
return;
|
|
4398
|
-
}
|
|
4399
|
-
resource.count -= 1;
|
|
4400
|
-
if (resource.count > 0) {
|
|
4401
|
-
return;
|
|
4402
|
-
}
|
|
4403
|
-
resource.dispose();
|
|
4404
|
-
this.resources.delete(key);
|
|
4405
|
-
}
|
|
4406
|
-
}
|
|
4407
|
-
export {
|
|
4408
|
-
stringifyKeyStroke,
|
|
4409
|
-
stringifyKeySequence,
|
|
4410
|
-
Keymap
|
|
4411
|
-
};
|