Skip to main content

zellij_utils/input/
keybinds.rs

1use std::collections::{BTreeMap, HashMap};
2
3use super::actions::Action;
4use crate::data::{BareKey, InputMode, KeyWithModifier, KeybindsVec};
5
6use serde::{Deserialize, Serialize};
7use std::fmt;
8
9/// Used in the config struct
10#[derive(Clone, PartialEq, Deserialize, Serialize, Default)]
11pub struct Keybinds(pub HashMap<InputMode, HashMap<KeyWithModifier, Vec<Action>>>);
12
13impl fmt::Debug for Keybinds {
14    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
15        let mut stable_sorted = BTreeMap::new();
16        for (mode, keybinds) in self.0.iter() {
17            let mut stable_sorted_mode_keybinds = BTreeMap::new();
18            for (key, actions) in keybinds {
19                stable_sorted_mode_keybinds.insert(key, actions);
20            }
21            stable_sorted.insert(mode, stable_sorted_mode_keybinds);
22        }
23        write!(f, "{:#?}", stable_sorted)
24    }
25}
26
27impl Keybinds {
28    pub fn get_actions_for_key_in_mode(
29        &self,
30        mode: &InputMode,
31        key: &KeyWithModifier,
32    ) -> Option<&Vec<Action>> {
33        self.0
34            .get(mode)
35            .and_then(|normal_mode_keybindings| normal_mode_keybindings.get(key))
36    }
37    pub fn get_actions_for_key_in_mode_or_default_action(
38        &self,
39        mode: &InputMode,
40        key_with_modifier: &KeyWithModifier,
41        raw_bytes: Vec<u8>,
42        default_input_mode: InputMode,
43        key_is_kitty_protocol: bool,
44    ) -> Vec<Action> {
45        self.0
46            .get(mode)
47            .and_then(|mode_keybindings| {
48                if raw_bytes == &[10] {
49                    handle_ctrl_j(&mode_keybindings, &raw_bytes, key_is_kitty_protocol)
50                } else {
51                    mode_keybindings.get(key_with_modifier).cloned()
52                }
53            })
54            .unwrap_or_else(|| {
55                vec![self.default_action_for_mode(
56                    mode,
57                    Some(key_with_modifier),
58                    raw_bytes,
59                    default_input_mode,
60                    key_is_kitty_protocol,
61                )]
62            })
63    }
64    pub fn get_input_mode_mut(
65        &mut self,
66        input_mode: &InputMode,
67    ) -> &mut HashMap<KeyWithModifier, Vec<Action>> {
68        self.0.entry(*input_mode).or_insert_with(HashMap::new)
69    }
70    pub fn default_action_for_mode(
71        &self,
72        mode: &InputMode,
73        key_with_modifier: Option<&KeyWithModifier>,
74        raw_bytes: Vec<u8>,
75        default_input_mode: InputMode,
76        key_is_kitty_protocol: bool,
77    ) -> Action {
78        match *mode {
79            InputMode::Locked => Action::Write {
80                key_with_modifier: key_with_modifier.cloned(),
81                bytes: raw_bytes,
82                is_kitty_keyboard_protocol: key_is_kitty_protocol,
83            },
84            mode if mode == default_input_mode => Action::Write {
85                key_with_modifier: key_with_modifier.cloned(),
86                bytes: raw_bytes,
87                is_kitty_keyboard_protocol: key_is_kitty_protocol,
88            },
89            InputMode::RenameTab => Action::TabNameInput { input: raw_bytes },
90            InputMode::RenamePane => Action::PaneNameInput { input: raw_bytes },
91            InputMode::EnterSearch => Action::SearchInput { input: raw_bytes },
92            _ => Action::NoOp,
93        }
94    }
95    pub fn to_keybinds_vec(&self) -> KeybindsVec {
96        let mut ret = vec![];
97        for (mode, mode_binds) in &self.0 {
98            let mut mode_binds_vec: Vec<(KeyWithModifier, Vec<Action>)> = vec![];
99            for (key, actions) in mode_binds {
100                mode_binds_vec.push((key.clone(), actions.clone()));
101            }
102            ret.push((*mode, mode_binds_vec))
103        }
104        ret
105    }
106    pub fn merge(&mut self, mut other: Keybinds) {
107        for (other_input_mode, mut other_input_mode_keybinds) in other.0.drain() {
108            let input_mode_keybinds = self
109                .0
110                .entry(other_input_mode)
111                .or_insert_with(|| Default::default());
112            for (other_action, other_action_keybinds) in other_input_mode_keybinds.drain() {
113                input_mode_keybinds.insert(other_action, other_action_keybinds);
114            }
115        }
116    }
117}
118
119const MAX_SHORTCUT_SEARCH_DEPTH: usize = 4;
120
121pub fn shortcut_for_action(
122    keybinds: &KeybindsVec,
123    base_mode: InputMode,
124    is_target_action: impl Fn(&Action) -> bool,
125) -> Option<Vec<KeyWithModifier>> {
126    let mode_binds = |mode: InputMode| -> Vec<(KeyWithModifier, Vec<Action>)> {
127        let mut binds = keybinds
128            .iter()
129            .find(|(bind_mode, _)| *bind_mode == mode)
130            .map(|(_, binds)| binds.clone())
131            .unwrap_or_default();
132        binds.sort_by(|(a, _), (b, _)| a.cmp(b));
133        binds
134    };
135    let mut visited: Vec<InputMode> = vec![base_mode];
136    let mut queue: Vec<(InputMode, Vec<KeyWithModifier>)> = vec![(base_mode, vec![])];
137    for _ in 0..MAX_SHORTCUT_SEARCH_DEPTH {
138        let mut next_queue: Vec<(InputMode, Vec<KeyWithModifier>)> = vec![];
139        for (mode, path) in &queue {
140            for (key, actions) in mode_binds(*mode) {
141                if actions.iter().any(&is_target_action) {
142                    let mut shortcut = path.clone();
143                    shortcut.push(key);
144                    return Some(shortcut);
145                }
146            }
147        }
148        for (mode, path) in &queue {
149            for (key, actions) in mode_binds(*mode) {
150                for action in &actions {
151                    if let Action::SwitchToMode {
152                        input_mode: next_mode,
153                    } = action
154                    {
155                        if !visited.contains(next_mode) {
156                            visited.push(*next_mode);
157                            let mut next_path = path.clone();
158                            next_path.push(key.clone());
159                            next_queue.push((*next_mode, next_path));
160                        }
161                    }
162                }
163            }
164        }
165        if next_queue.is_empty() {
166            return None;
167        }
168        queue = next_queue;
169    }
170    None
171}
172
173// we need to do this because [10] in standard STDIN, [10] is both Enter (without a carriage
174// return) and ctrl-j - so here, if ctrl-j is bound we return its bound action, and otherwise we
175// just write the raw bytes to the terminal and let whichever program is there decide what they are
176fn handle_ctrl_j(
177    mode_keybindings: &HashMap<KeyWithModifier, Vec<Action>>,
178    raw_bytes: &[u8],
179    key_is_kitty_protocol: bool,
180) -> Option<Vec<Action>> {
181    let ctrl_j = KeyWithModifier::new(BareKey::Char('j')).with_ctrl_modifier();
182    if mode_keybindings.get(&ctrl_j).is_some() {
183        mode_keybindings.get(&ctrl_j).cloned()
184    } else {
185        Some(vec![Action::Write {
186            key_with_modifier: Some(ctrl_j),
187            bytes: raw_bytes.to_vec().clone(),
188            is_kitty_keyboard_protocol: key_is_kitty_protocol,
189        }])
190    }
191}
192
193// The unit test location.
194#[cfg(test)]
195#[path = "./unit/keybinds_test.rs"]
196mod keybinds_test;