fret_runtime/keymap/
display.rs1use crate::{CommandId, InputContext, InputDispatchPhase, KeyChord};
2use std::collections::{HashMap, HashSet};
3use std::sync::Arc;
4
5use super::Keymap;
6
7impl Keymap {
8 pub fn shortcut_for_command(
13 &self,
14 ctx: &InputContext,
15 command: &CommandId,
16 ) -> Option<KeyChord> {
17 self.shortcut_for_command_sequence(ctx, command)
18 .filter(|seq| seq.len() == 1)
19 .and_then(|seq| seq.first().copied())
20 }
21
22 pub fn shortcut_for_command_sequence(
23 &self,
24 ctx: &InputContext,
25 command: &CommandId,
26 ) -> Option<Vec<KeyChord>> {
27 self.shortcut_for_command_sequence_with_key_contexts(ctx, &[], command)
28 }
29
30 pub fn shortcut_for_command_sequence_with_key_contexts(
31 &self,
32 ctx: &InputContext,
33 key_contexts: &[Arc<str>],
34 command: &CommandId,
35 ) -> Option<Vec<KeyChord>> {
36 let mut order: Vec<Vec<KeyChord>> = Vec::new();
37 let mut seen: HashSet<Vec<KeyChord>> = HashSet::new();
38 let mut effective: HashMap<Vec<KeyChord>, Option<CommandId>> = HashMap::new();
39
40 for b in &self.bindings {
41 if !b.platform.matches(ctx.platform) {
42 continue;
43 }
44 if let Some(expr) = b.when.as_ref()
45 && !expr.eval_with_key_contexts(ctx, key_contexts)
46 {
47 continue;
48 }
49 if seen.insert(b.sequence.clone()) {
50 order.push(b.sequence.clone());
51 }
52 effective.insert(b.sequence.clone(), b.command.clone());
53 }
54
55 order.into_iter().find(|seq| {
56 effective
57 .get(seq)
58 .is_some_and(|c| c.as_ref() == Some(command))
59 })
60 }
61
62 pub fn display_shortcut_for_command(
80 &self,
81 base: &InputContext,
82 command: &CommandId,
83 ) -> Option<KeyChord> {
84 self.display_shortcut_for_command_sequence(base, command)
85 .filter(|seq| seq.len() == 1)
86 .and_then(|seq| seq.first().copied())
87 }
88
89 pub fn display_shortcut_for_command_sequence(
90 &self,
91 base: &InputContext,
92 command: &CommandId,
93 ) -> Option<Vec<KeyChord>> {
94 self.display_shortcut_for_command_sequence_with_key_contexts(base, &[], command)
95 }
96
97 pub fn display_shortcut_for_command_sequence_with_key_contexts(
98 &self,
99 base: &InputContext,
100 key_contexts: &[Arc<str>],
101 command: &CommandId,
102 ) -> Option<Vec<KeyChord>> {
103 #[derive(Debug)]
104 struct Candidate {
105 ctx_index: usize,
106 seq_len: usize,
107 binding_index: usize,
108 seq: Vec<KeyChord>,
109 }
110
111 fn default_display_contexts(base: &InputContext) -> [InputContext; 4] {
112 let mut c0 = base.clone();
113 c0.dispatch_phase = InputDispatchPhase::Bubble;
114 c0.ui_has_modal = false;
115 c0.focus_is_text_input = false;
116
117 let mut c1 = c0.clone();
118 c1.focus_is_text_input = true;
119
120 let mut c2 = c0.clone();
121 c2.ui_has_modal = true;
122 c2.focus_is_text_input = false;
123
124 let mut c3 = c2.clone();
125 c3.focus_is_text_input = true;
126
127 [c0, c1, c2, c3]
128 }
129
130 fn effective_command_for_sequence<'a>(
131 keymap: &'a Keymap,
132 ctx: &InputContext,
133 key_contexts: &[Arc<str>],
134 seq: &[KeyChord],
135 ) -> Option<(Option<&'a CommandId>, usize)> {
136 for (index, b) in keymap.bindings.iter().enumerate().rev() {
137 if b.sequence.as_slice() != seq {
138 continue;
139 }
140 if !b.platform.matches(ctx.platform) {
141 continue;
142 }
143 if let Some(expr) = b.when.as_ref()
144 && !expr.eval_with_key_contexts(ctx, key_contexts)
145 {
146 continue;
147 }
148 return Some((b.command.as_ref(), index));
149 }
150 None
151 }
152
153 let contexts = default_display_contexts(base);
154
155 let mut sequences: HashSet<Vec<KeyChord>> = HashSet::new();
156 for b in &self.bindings {
157 sequences.insert(b.sequence.clone());
158 }
159
160 let mut best: Option<Candidate> = None;
161 for seq in sequences.into_iter() {
162 for (ctx_index, ctx) in contexts.iter().enumerate() {
163 let Some((Some(cmd), binding_index)) =
164 effective_command_for_sequence(self, ctx, key_contexts, &seq)
165 else {
166 continue;
167 };
168 if cmd != command {
169 continue;
170 }
171
172 let cand = Candidate {
173 ctx_index,
174 seq_len: seq.len(),
175 binding_index,
176 seq,
177 };
178
179 best = match best {
180 None => Some(cand),
181 Some(prev) => {
182 let replace = (
183 cand.ctx_index,
184 cand.seq_len,
185 std::cmp::Reverse(cand.binding_index),
186 ) < (
187 prev.ctx_index,
188 prev.seq_len,
189 std::cmp::Reverse(prev.binding_index),
190 );
191 if replace { Some(cand) } else { Some(prev) }
192 }
193 };
194
195 break;
196 }
197 }
198
199 best.map(|c| c.seq)
200 }
201}