resolveActiveKeyBindings function
Resolves the discoverable key bindings active in manager's focus context.
Resolution follows the same rules as key dispatch: the deepest local
binding wins each sequence, and the walk ends at a KeyBindings with
KeyBindings.modal set, such as the one around a dialog shown with
context.present, so bindings beyond it are left out. The modal scope's
own bindings are listed, including one that lets its key through with
KeyBindingEvent.bubble: whether a handler bubbles is decided when it
runs, so the key is listed under that binding's label. Resolution also
applies the framework's user-facing discovery rules:
- bindings need an explicit KeyBinding.label;
- disabled and KeyBinding.hideFromHintBar bindings are omitted;
- bare printable sequences swallowed by a focused text input are omitted;
- multi-alias bindings remain visible through any alias that can fire;
- duplicate and shadowed aliases are removed using canonical sequence identity, not their rendered label.
The returned list is deepest-first and immutable. This is the canonical resolution API for hint bars, help overlays, and keymap inspection; those surfaces should not independently walk FocusManager.activeChain.
A command's shortcut is enabled by predicates over app state that no rebuild tracks. Called from a widget's build, the resolution remembers what they answered for that widget, and the focus manager asks them again at the start of each frame: when one answers differently, the widget rebuilds. A resolution outside a build remembers nothing.
Implementation
List<ActiveKeyBinding> resolveActiveKeyBindings(FocusManager manager) {
final result = <ActiveKeyBinding>[];
// Canonical sequence identity mirrors dispatch. Differently spelled aliases
// for the same firing event must not evade deeper-binding precedence.
final seenSequences = <KeySequence>{};
final textFocused = manager.focusedNodeClaimsText;
List<(KeyBinding, bool)>? liveAnswers;
void consider(KeyBinding binding) {
if (binding.label == null) return;
if (binding.hideFromHintBar) return;
final availability = binding._availability;
final reader = Element.current;
if (availability != null && reader != null) {
dependOnListenable(reader, availability);
}
final enabled = binding.enabled;
if (binding.isLive) (liveAnswers ??= []).add((binding, enabled));
if (!enabled) return;
final firable = [
for (final sequence in binding.sequences)
if (!textFocused || !sequence.isShadowedByTextInput) sequence,
];
if (firable.isEmpty) return;
final owned = <KeySequence>[];
for (final sequence in firable) {
if (seenSequences.contains(sequence)) continue;
if (!owned.contains(sequence)) owned.add(sequence);
}
if (owned.isEmpty) return;
// Claim every firable alias, including aliases already represented by a
// sibling alias in [owned]. Shallower bindings cannot fire those.
seenSequences.addAll(firable);
result.add(ActiveKeyBinding._(binding, owned));
}
for (final node in manager.activeChain()) {
final source = node.bindingSource;
if (source == null) continue;
for (final binding in source.activeBindings) {
consider(binding);
}
// Dispatch stops here too, for every key no binding at this scope
// handled (see `InputDispatcher`'s plain dispatch).
if (source.isModalScope) break;
}
manager.recordLiveAnswers(liveAnswers);
return List<ActiveKeyBinding>.unmodifiable(result);
}