1use git::Config;
2
3use crate::{errors::ConfigError, utils::get_input};
4
5fn map_single_ascii_to_lower(s: &str) -> String {
6 if s.is_ascii() && s.len() == 1 {
7 s.to_lowercase()
8 }
9 else {
10 String::from(s)
11 }
12}
13
14#[derive(Clone, Debug)]
16#[non_exhaustive]
17pub struct KeyBindings {
18 pub abort: Vec<String>,
20 pub action_break: Vec<String>,
22 pub action_drop: Vec<String>,
24 pub action_edit: Vec<String>,
26 pub action_fixup: Vec<String>,
28 pub action_pick: Vec<String>,
30 pub action_reword: Vec<String>,
32 pub action_squash: Vec<String>,
34 pub confirm_no: Vec<String>,
36 pub confirm_yes: Vec<String>,
38 pub edit: Vec<String>,
40 pub force_abort: Vec<String>,
42 pub force_rebase: Vec<String>,
44 pub help: Vec<String>,
46 pub insert_line: Vec<String>,
48
49 pub move_down: Vec<String>,
51 pub move_end: Vec<String>,
53 pub move_home: Vec<String>,
55 pub move_left: Vec<String>,
57 pub move_right: Vec<String>,
59 pub move_up: Vec<String>,
61 pub move_down_step: Vec<String>,
63 pub move_up_step: Vec<String>,
65 pub move_selection_down: Vec<String>,
67 pub move_selection_up: Vec<String>,
69
70 pub scroll_down: Vec<String>,
72 pub scroll_end: Vec<String>,
74 pub scroll_home: Vec<String>,
76 pub scroll_left: Vec<String>,
78 pub scroll_right: Vec<String>,
80 pub scroll_up: Vec<String>,
82 pub scroll_step_down: Vec<String>,
84 pub scroll_step_up: Vec<String>,
86
87 pub open_in_external_editor: Vec<String>,
89 pub rebase: Vec<String>,
91 pub redo: Vec<String>,
93 pub remove_line: Vec<String>,
95 pub search_start: Vec<String>,
97 pub search_next: Vec<String>,
99 pub search_previous: Vec<String>,
101 pub show_commit: Vec<String>,
103 pub show_diff: Vec<String>,
105 pub toggle_visual_mode: Vec<String>,
107 pub undo: Vec<String>,
109 pub fixup_keep_message_with_editor: Vec<String>,
111 pub fixup_keep_message: Vec<String>,
113}
114
115impl KeyBindings {
116 #[must_use]
118 #[inline]
119 #[allow(clippy::missing_panics_doc)]
120 pub fn new() -> Self {
121 Self::new_with_config(None).unwrap() }
123
124 pub(super) fn new_with_config(git_config: Option<&Config>) -> Result<Self, ConfigError> {
125 let confirm_no = get_input(git_config, "interactive-rebase-tool.inputConfirmNo", "n")?
126 .iter()
127 .map(|s| map_single_ascii_to_lower(s))
128 .collect();
129 let confirm_yes = get_input(git_config, "interactive-rebase-tool.inputConfirmYes", "y")?
130 .iter()
131 .map(|s| map_single_ascii_to_lower(s))
132 .collect();
133 Ok(Self {
134 abort: get_input(git_config, "interactive-rebase-tool.inputAbort", "q")?,
135 action_break: get_input(git_config, "interactive-rebase-tool.inputActionBreak", "b")?,
136 action_drop: get_input(git_config, "interactive-rebase-tool.inputActionDrop", "d")?,
137 action_edit: get_input(git_config, "interactive-rebase-tool.inputActionEdit", "e")?,
138 action_fixup: get_input(git_config, "interactive-rebase-tool.inputActionFixup", "f")?,
139 action_pick: get_input(git_config, "interactive-rebase-tool.inputActionPick", "p")?,
140 action_reword: get_input(git_config, "interactive-rebase-tool.inputActionReword", "r")?,
141 action_squash: get_input(git_config, "interactive-rebase-tool.inputActionSquash", "s")?,
142 confirm_no,
143 confirm_yes,
144 edit: get_input(git_config, "interactive-rebase-tool.inputEdit", "E")?,
145 force_abort: get_input(git_config, "interactive-rebase-tool.inputForceAbort", "Q")?,
146 force_rebase: get_input(git_config, "interactive-rebase-tool.inputForceRebase", "W")?,
147 help: get_input(git_config, "interactive-rebase-tool.inputHelp", "?")?,
148 insert_line: get_input(git_config, "interactive-rebase-tool.insertLine", "I")?,
149 move_down: get_input(git_config, "interactive-rebase-tool.inputMoveDown", "Down")?,
150 move_end: get_input(git_config, "interactive-rebase-tool.inputMoveEnd", "End")?,
151 move_home: get_input(git_config, "interactive-rebase-tool.inputMoveHome", "Home")?,
152 move_left: get_input(git_config, "interactive-rebase-tool.inputMoveLeft", "Left")?,
153 move_right: get_input(git_config, "interactive-rebase-tool.inputMoveRight", "Right")?,
154 move_down_step: get_input(git_config, "interactive-rebase-tool.inputMoveStepDown", "PageDown")?,
155 move_up_step: get_input(git_config, "interactive-rebase-tool.inputMoveStepUp", "PageUp")?,
156 move_up: get_input(git_config, "interactive-rebase-tool.inputMoveUp", "Up")?,
157 move_selection_down: get_input(git_config, "interactive-rebase-tool.inputMoveSelectionDown", "j")?,
158 move_selection_up: get_input(git_config, "interactive-rebase-tool.inputMoveSelectionUp", "k")?,
159 scroll_down: get_input(git_config, "interactive-rebase-tool.inputScrollDown", "Down")?,
160 scroll_end: get_input(git_config, "interactive-rebase-tool.inputScrollEnd", "End")?,
161 scroll_home: get_input(git_config, "interactive-rebase-tool.inputScrollHome", "Home")?,
162 scroll_left: get_input(git_config, "interactive-rebase-tool.inputScrollLeft", "Left")?,
163 scroll_right: get_input(git_config, "interactive-rebase-tool.inputScrollRight", "Right")?,
164 scroll_up: get_input(git_config, "interactive-rebase-tool.inputScrollUp", "Up")?,
165 scroll_step_down: get_input(git_config, "interactive-rebase-tool.inputScrollStepDown", "PageDown")?,
166 scroll_step_up: get_input(git_config, "interactive-rebase-tool.inputScrollStepUp", "PageUp")?,
167 open_in_external_editor: get_input(git_config, "interactive-rebase-tool.inputOpenInExternalEditor", "!")?,
168 rebase: get_input(git_config, "interactive-rebase-tool.inputRebase", "w")?,
169 redo: get_input(git_config, "interactive-rebase-tool.inputRedo", "control+y")?,
170 remove_line: get_input(git_config, "interactive-rebase-tool.removeLine", "delete")?,
171 search_start: get_input(git_config, "interactive-rebase-tool.searchStart", "/")?,
172 search_next: get_input(git_config, "interactive-rebase-tool.searchNext", "n")?,
173 search_previous: get_input(git_config, "interactive-rebase-tool.searchPrevious", "N")?,
174 show_commit: get_input(git_config, "interactive-rebase-tool.inputShowCommit", "c")?,
175 show_diff: get_input(git_config, "interactive-rebase-tool.inputShowDiff", "d")?,
176 toggle_visual_mode: get_input(git_config, "interactive-rebase-tool.inputToggleVisualMode", "v")?,
177 undo: get_input(git_config, "interactive-rebase-tool.inputUndo", "control+z")?,
178 fixup_keep_message_with_editor: get_input(
179 git_config,
180 "interactive-rebase-tool.fixupKeepMessageWithEditor",
181 "U",
182 )?,
183 fixup_keep_message: get_input(git_config, "interactive-rebase-tool.fixupKeepMessage", "u")?,
184 })
185 }
186}
187
188impl TryFrom<&Config> for KeyBindings {
189 type Error = ConfigError;
190
191 #[inline]
192 fn try_from(config: &Config) -> Result<Self, Self::Error> {
193 Self::new_with_config(Some(config))
194 }
195}
196
197#[cfg(test)]
198mod tests {
199 use claim::assert_ok;
200
201 use super::*;
202 use crate::testutils::with_git_config;
203
204 macro_rules! config_test {
205 ($key:ident, $config_name:literal, $default:literal) => {
206 let config = KeyBindings::new();
207 let value = config.$key[0].as_str();
208 assert_eq!(
209 value,
210 String::from($default),
211 "Default value for key binding '{}' was expected to be '{}' but '{}' was found",
212 stringify!($key),
213 $default,
214 value
215 );
216
217 let config_value = format!("{} = \"f255\"", $config_name);
218 with_git_config(
219 &["[interactive-rebase-tool]", config_value.as_str()],
220 |git_config| {
221 let config = KeyBindings::new_with_config(Some(&git_config)).unwrap();
222 assert_eq!(
223 config.$key[0].as_str(),
224 "F255",
225 "Value for key binding '{}' was expected to be changed but was not",
226 stringify!($key)
227 );
228 },
229 );
230 };
231 }
232
233 #[test]
234 fn new() {
235 let _config = KeyBindings::new();
236 }
237
238 #[test]
239 fn try_from_git_config() {
240 with_git_config(&[], |git_config| {
241 assert_ok!(KeyBindings::try_from(&git_config));
242 });
243 }
244
245 #[test]
246 fn try_from_git_config_error() {
247 with_git_config(&["[interactive-rebase-tool]", "inputAbort = invalid"], |git_config| {
248 _ = KeyBindings::try_from(&git_config).unwrap_err();
249 });
250 }
251
252 #[test]
253 fn key_bindings() {
254 config_test!(abort, "inputAbort", "q");
255 config_test!(action_break, "inputActionBreak", "b");
256 config_test!(action_drop, "inputActionDrop", "d");
257 config_test!(action_edit, "inputActionEdit", "e");
258 config_test!(action_fixup, "inputActionFixup", "f");
259 config_test!(action_pick, "inputActionPick", "p");
260 config_test!(action_reword, "inputActionReword", "r");
261 config_test!(action_squash, "inputActionSquash", "s");
262 config_test!(confirm_no, "inputConfirmNo", "n");
263 config_test!(confirm_yes, "inputConfirmYes", "y");
264 config_test!(edit, "inputEdit", "E");
265 config_test!(force_abort, "inputForceAbort", "Q");
266 config_test!(force_rebase, "inputForceRebase", "W");
267 config_test!(help, "inputHelp", "?");
268 config_test!(insert_line, "insertLine", "I");
269 config_test!(move_down, "inputMoveDown", "Down");
270 config_test!(move_end, "inputMoveEnd", "End");
271 config_test!(move_home, "inputMoveHome", "Home");
272 config_test!(move_left, "inputMoveLeft", "Left");
273 config_test!(move_right, "inputMoveRight", "Right");
274 config_test!(move_up, "inputMoveUp", "Up");
275 config_test!(move_down_step, "inputMoveStepDown", "PageDown");
276 config_test!(move_up_step, "inputMoveStepUp", "PageUp");
277 config_test!(move_selection_down, "inputMoveSelectionDown", "j");
278 config_test!(move_selection_up, "inputMoveSelectionUp", "k");
279 config_test!(scroll_down, "inputScrollDown", "Down");
280 config_test!(scroll_end, "inputScrollEnd", "End");
281 config_test!(scroll_home, "inputScrollHome", "Home");
282 config_test!(scroll_left, "inputScrollLeft", "Left");
283 config_test!(scroll_right, "inputScrollRight", "Right");
284 config_test!(scroll_up, "inputScrollUp", "Up");
285 config_test!(scroll_step_down, "inputScrollStepDown", "PageDown");
286 config_test!(scroll_step_up, "inputScrollStepUp", "PageUp");
287 config_test!(open_in_external_editor, "inputOpenInExternalEditor", "!");
288 config_test!(rebase, "inputRebase", "w");
289 config_test!(redo, "inputRedo", "Controly");
290 config_test!(remove_line, "removeLine", "Delete");
291 config_test!(search_start, "searchStart", "/");
292 config_test!(search_next, "searchNext", "n");
293 config_test!(search_previous, "searchPrevious", "N");
294 config_test!(show_commit, "inputShowCommit", "c");
295 config_test!(show_diff, "inputShowDiff", "d");
296 config_test!(toggle_visual_mode, "inputToggleVisualMode", "v");
297 config_test!(undo, "inputUndo", "Controlz");
298 config_test!(fixup_keep_message_with_editor, "fixupKeepMessageWithEditor", "U");
299 config_test!(fixup_keep_message, "fixupKeepMessage", "u");
300 }
301}