Skip to main content

ashlar_command/
keymap.rs

1//! Which key runs which command: the commands' default shortcuts plus the user's overrides.
2//!
3//! An override replaces all of a command's keys (an empty list unbinds it). Lookup prefers a user
4//! binding over a default one, then declaration order; [`Keymap::conflicts`] lists every chord that
5//! more than one command claims. Chords are compared as the keyboard of the keymap's platform
6//! produces them (off macOS, `Mod` and `Ctrl` are one key).
7
8use std::collections::{BTreeMap, HashMap};
9
10use serde::{Deserialize, Serialize};
11use serde_json::Value;
12
13use crate::shortcut::{Chord, Platform, parse_list};
14use crate::{Host, Registry};
15
16/// At most this many overridden commands are read from settings.
17const MAX_OVERRIDES: usize = 2000;
18/// At most this many keys per command.
19const MAX_KEYS: usize = 8;
20/// Longest command id read from settings.
21const MAX_ID_LEN: usize = 128;
22
23/// The user's overrides as saved in settings: command id → its keys (portable form, `[]` = no key).
24#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
25pub struct Overrides {
26    #[serde(default)]
27    pub bindings: BTreeMap<String, Vec<String>>,
28}
29
30/// A chord claimed by more than one command.
31#[derive(Debug, Clone, PartialEq, Serialize)]
32pub struct Conflict {
33    pub chord: Chord,
34    /// In lookup order: the first one wins.
35    pub ids: Vec<String>,
36}
37
38#[derive(Debug, Clone)]
39struct Binding {
40    id: String,
41    user: bool,
42    order: usize,
43}
44
45/// Default shortcuts plus user overrides, indexed for lookup by chord.
46#[derive(Debug, Clone)]
47pub struct Keymap {
48    platform: Platform,
49    /// Every known command with its default keys, in declaration order.
50    defaults: Vec<(String, Vec<Chord>)>,
51    positions: HashMap<String, usize>,
52    user: BTreeMap<String, Vec<Chord>>,
53    index: HashMap<Chord, Vec<Binding>>,
54    invalid: Vec<(String, String)>,
55}
56
57impl Keymap {
58    /// A keymap from `(command id, default shortcut text)` pairs. Commands without a default key
59    /// (`None`) are known, so the user can bind them. Unparseable defaults are skipped and listed
60    /// in [`Keymap::invalid_defaults`].
61    pub fn new<'a>(platform: Platform, defaults: impl IntoIterator<Item = (&'a str, Option<&'a str>)>) -> Self {
62        let mut map = Keymap { platform, defaults: vec![], positions: HashMap::new(), user: BTreeMap::new(), index: HashMap::new(), invalid: vec![] };
63        for (id, text) in defaults {
64            if map.positions.contains_key(id) {
65                continue;
66            }
67            let chords = match text.map(parse_list) {
68                None => vec![],
69                Some(Ok(c)) => c,
70                Some(Err(_)) => {
71                    map.invalid.push((id.to_string(), text.unwrap_or_default().to_string()));
72                    vec![]
73                }
74            };
75            map.positions.insert(id.to_string(), map.defaults.len());
76            map.defaults.push((id.to_string(), chords));
77        }
78        map.reindex();
79        map
80    }
81
82    /// The default keymap of a command table.
83    pub fn from_registry<S: Host>(registry: &Registry<S>, platform: Platform) -> Self {
84        Self::new(platform, registry.specs().iter().map(|c| (c.id, c.shortcut)))
85    }
86
87    pub fn platform(&self) -> Platform {
88        self.platform
89    }
90
91    /// Default shortcuts that did not parse: `(command id, text)`.
92    pub fn invalid_defaults(&self) -> &[(String, String)] {
93        &self.invalid
94    }
95
96    fn reindex(&mut self) {
97        self.index.clear();
98        let user = self.user.iter().map(|(id, c)| (id, c, true));
99        let defaults = self.defaults.iter().filter(|(id, _)| !self.user.contains_key(id)).map(|(id, c)| (id, c, false));
100        for (id, chords, is_user) in user.chain(defaults) {
101            let order = self.positions.get(id).copied().unwrap_or(usize::MAX);
102            for chord in chords {
103                let list = self.index.entry(chord.on(self.platform)).or_default();
104                if !list.iter().any(|b| &b.id == id) {
105                    list.push(Binding { id: id.clone(), user: is_user, order });
106                }
107            }
108        }
109        for list in self.index.values_mut() {
110            list.sort_by_key(|b| (!b.user, b.order));
111        }
112    }
113
114    /// The command a chord runs (user bindings first, then declaration order).
115    pub fn lookup(&self, chord: &Chord) -> Option<&str> {
116        self.index.get(&chord.on(self.platform)).and_then(|l| l.first()).map(|b| b.id.as_str())
117    }
118
119    /// Every command bound to a chord, in lookup order.
120    pub fn commands_for(&self, chord: &Chord) -> Vec<&str> {
121        self.index.get(&chord.on(self.platform)).map(|l| l.iter().map(|b| b.id.as_str()).collect()).unwrap_or_default()
122    }
123
124    /// The keys that run a command now (its overrides, else its defaults).
125    pub fn keys_for(&self, id: &str) -> &[Chord] {
126        if let Some(keys) = self.user.get(id) {
127            return keys;
128        }
129        self.positions.get(id).and_then(|&i| self.defaults.get(i)).map(|(_, c)| c.as_slice()).unwrap_or_default()
130    }
131
132    /// The default keys of a command.
133    pub fn default_keys(&self, id: &str) -> &[Chord] {
134        self.positions.get(id).and_then(|&i| self.defaults.get(i)).map(|(_, c)| c.as_slice()).unwrap_or_default()
135    }
136
137    /// Whether the command is known to this keymap.
138    pub fn knows(&self, id: &str) -> bool {
139        self.positions.contains_key(id)
140    }
141
142    /// Replace a command's keys (an empty list unbinds it). Returns `false` for an unknown command.
143    pub fn bind(&mut self, id: &str, mut keys: Vec<Chord>) -> bool {
144        if !self.knows(id) {
145            return false;
146        }
147        keys.truncate(MAX_KEYS);
148        keys.dedup();
149        if keys.as_slice() == self.default_keys(id) {
150            self.user.remove(id);
151        } else {
152            self.user.insert(id.to_string(), keys);
153        }
154        self.reindex();
155        true
156    }
157
158    /// Back to the default keys of one command.
159    pub fn reset(&mut self, id: &str) {
160        if self.user.remove(id).is_some() {
161            self.reindex();
162        }
163    }
164
165    pub fn reset_all(&mut self) {
166        self.user.clear();
167        self.reindex();
168    }
169
170    /// Commands other than `id` that already use `chord` (to warn before binding).
171    pub fn would_conflict(&self, id: &str, chord: &Chord) -> Vec<&str> {
172        self.commands_for(chord).into_iter().filter(|c| *c != id).collect()
173    }
174
175    /// Every chord claimed by more than one command, sorted by chord.
176    pub fn conflicts(&self) -> Vec<Conflict> {
177        let mut out: Vec<Conflict> = self
178            .index
179            .iter()
180            .filter(|(_, l)| l.len() > 1)
181            .map(|(chord, l)| Conflict { chord: *chord, ids: l.iter().map(|b| b.id.clone()).collect() })
182            .collect();
183        out.sort_by_key(|c| c.chord);
184        out
185    }
186
187    /// The overrides, to save in settings.
188    pub fn overrides(&self) -> Overrides {
189        Overrides { bindings: self.user.iter().map(|(id, keys)| (id.clone(), keys.iter().map(Chord::portable).collect())).collect() }
190    }
191
192    /// Apply saved overrides. Never fails: unknown commands and keys that don't parse are skipped
193    /// (their count is returned), and the input is capped.
194    pub fn apply_overrides(&mut self, overrides: &Overrides) -> usize {
195        let mut skipped = 0;
196        for (id, keys) in overrides.bindings.iter().take(MAX_OVERRIDES) {
197            if id.len() > MAX_ID_LEN || !self.knows(id) {
198                skipped += 1;
199                continue;
200            }
201            let mut chords = Vec::new();
202            for k in keys.iter().take(MAX_KEYS) {
203                match Chord::parse(k) {
204                    Ok(c) => chords.push(c),
205                    Err(_) => skipped += 1,
206                }
207            }
208            chords.dedup();
209            if chords.as_slice() == self.default_keys(id) {
210                self.user.remove(id);
211            } else {
212                self.user.insert(id.clone(), chords);
213            }
214        }
215        skipped += overrides.bindings.len().saturating_sub(MAX_OVERRIDES);
216        self.reindex();
217        skipped
218    }
219
220    /// Read overrides from a settings value (`{"bindings": {...}}`); anything malformed is ignored.
221    pub fn apply_overrides_json(&mut self, value: &Value) -> usize {
222        let mut o = Overrides::default();
223        let Some(b) = value.get("bindings").and_then(Value::as_object) else { return 0 };
224        let mut skipped = 0;
225        for (id, keys) in b.iter().take(MAX_OVERRIDES) {
226            match keys.as_array() {
227                Some(a) => {
228                    o.bindings.insert(id.clone(), a.iter().take(MAX_KEYS).filter_map(Value::as_str).map(str::to_string).collect());
229                }
230                None => skipped += 1,
231            }
232        }
233        skipped + self.apply_overrides(&o)
234    }
235}