1use std::collections::BTreeSet;
2
3use super::{config_update_string_enum, prelude::*};
4use crate as nu_protocol;
5use crate::{FromValue, engine::Closure};
6
7#[derive(Clone, Debug, FromValue, IntoValue, Serialize, Deserialize)]
9pub struct ParsedKeybinding {
10 pub name: Option<Value>,
11 pub modifier: Value,
12 pub keycode: Value,
13 pub event: Value,
14 pub mode: Value,
15}
16
17pub(crate) fn name_of(kb: &ParsedKeybinding) -> Option<String> {
18 kb.name
19 .as_ref()
20 .and_then(|v| v.coerce_str().ok())
21 .map(|s| s.to_string())
22}
23
24#[derive(Debug, PartialEq, Eq)]
25pub(super) struct KeyIdentity {
26 modifier: BTreeSet<String>,
27 keycode: String,
28 modes: BTreeSet<String>,
29}
30
31impl KeyIdentity {
32 pub(crate) fn of(kb: &ParsedKeybinding) -> Self {
33 let lower = |v: &Value| {
34 v.coerce_str()
35 .map(|s| s.to_ascii_lowercase())
36 .unwrap_or_default()
37 };
38 let modes = match &kb.mode {
39 Value::List { vals, .. } => vals.iter().map(lower).collect(),
40 v => BTreeSet::from([lower(v)]),
41 };
42
43 Self {
44 modifier: lower(&kb.modifier).split('_').map(|s| s.into()).collect(),
50 keycode: match lower(&kb.keycode).as_str() {
51 "esc" => "escape".into(),
52 other => other.into(),
53 },
54 modes,
55 }
56 }
57}
58
59#[derive(Clone, Debug, FromValue, IntoValue, Serialize, Deserialize)]
61pub struct ParsedMenu {
62 pub name: Value,
63 pub marker: Value,
64 pub only_buffer_difference: Option<Value>,
67 pub input_mode: Option<Value>,
70 pub output_mode: Option<Value>,
72 pub style: Value,
73 pub r#type: Value,
74 pub source: Option<Closure>,
75}
76
77#[derive(Clone, Copy, Debug, Default, IntoValue, PartialEq, Eq, Serialize, Deserialize)]
79pub enum NuCursorShape {
80 Underscore,
81 Line,
82 Block,
83 BlinkUnderscore,
84 BlinkLine,
85 BlinkBlock,
86 #[default]
87 Inherit,
88}
89
90impl FromStr for NuCursorShape {
91 type Err = &'static str;
92
93 fn from_str(s: &str) -> Result<NuCursorShape, &'static str> {
94 match s.to_ascii_lowercase().as_str() {
95 "line" => Ok(NuCursorShape::Line),
96 "block" => Ok(NuCursorShape::Block),
97 "underscore" => Ok(NuCursorShape::Underscore),
98 "blink_line" => Ok(NuCursorShape::BlinkLine),
99 "blink_block" => Ok(NuCursorShape::BlinkBlock),
100 "blink_underscore" => Ok(NuCursorShape::BlinkUnderscore),
101 "inherit" => Ok(NuCursorShape::Inherit),
102 _ => Err(
103 "'line', 'block', 'underscore', 'blink_line', 'blink_block', 'blink_underscore' or 'inherit'",
104 ),
105 }
106 }
107}
108
109impl UpdateFromValue for NuCursorShape {
110 fn update(&mut self, value: &Value, path: &mut ConfigPath, errors: &mut ConfigErrors) {
111 config_update_string_enum(self, value, path, errors)
112 }
113}
114
115#[derive(Clone, Copy, Debug, Default, IntoValue, PartialEq, Eq, Serialize, Deserialize)]
116pub struct CursorShapeConfig {
117 pub emacs: NuCursorShape,
118 pub vi_insert: NuCursorShape,
119 pub vi_normal: NuCursorShape,
120 pub vi_visual: NuCursorShape,
121 pub helix_normal: NuCursorShape,
122 pub helix_select: NuCursorShape,
123 pub helix_insert: NuCursorShape,
124}
125
126impl UpdateFromValue for CursorShapeConfig {
127 fn update<'a>(
128 &mut self,
129 value: &'a Value,
130 path: &mut ConfigPath<'a>,
131 errors: &mut ConfigErrors,
132 ) {
133 let Value::Record { val: record, .. } = value else {
134 errors.type_mismatch(path, Type::record(), value);
135 return;
136 };
137
138 for (col, val) in record.iter() {
139 let path = &mut path.push(col);
140 match col.as_str() {
141 "vi_insert" => self.vi_insert.update(val, path, errors),
142 "vi_normal" => self.vi_normal.update(val, path, errors),
143 "vi_visual" => self.vi_visual.update(val, path, errors),
144 "emacs" => self.emacs.update(val, path, errors),
145 "helix_normal" => self.helix_normal.update(val, path, errors),
146 "helix_select" => self.helix_select.update(val, path, errors),
147 "helix_insert" => self.helix_insert.update(val, path, errors),
148 _ => errors.unknown_option(path, val),
149 }
150 }
151 }
152}
153
154#[derive(Clone, Copy, Debug, Default, IntoValue, PartialEq, Eq, Serialize, Deserialize)]
155pub enum EditBindings {
156 Vi,
157 #[default]
158 Emacs,
159 Helix,
160}
161
162impl FromStr for EditBindings {
163 type Err = &'static str;
164
165 fn from_str(s: &str) -> Result<Self, Self::Err> {
166 match s.to_ascii_lowercase().as_str() {
167 "vi" => Ok(Self::Vi),
168 "emacs" => Ok(Self::Emacs),
169 "helix" => Ok(Self::Helix),
170 _ => Err("'emacs', 'vi' or 'helix'"),
171 }
172 }
173}
174
175impl UpdateFromValue for EditBindings {
176 fn update(&mut self, value: &Value, path: &mut ConfigPath, errors: &mut ConfigErrors) {
177 config_update_string_enum(self, value, path, errors)
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184
185 fn kb(modifier: &str, keycode: &str, mode: Value) -> ParsedKeybinding {
186 ParsedKeybinding {
187 name: None,
188 modifier: Value::test_string(modifier),
189 keycode: Value::test_string(keycode),
190 event: Value::test_nothing(),
191 mode,
192 }
193 }
194
195 #[test]
196 fn a_bare_mode_and_its_singleton_list_are_the_same_key() {
197 let bare = kb("control", "char_r", Value::test_string("emacs"));
198 let listed = kb(
199 "control",
200 "char_r",
201 Value::test_list(vec![Value::test_string("emacs")]),
202 );
203 assert_eq!(KeyIdentity::of(&bare), KeyIdentity::of(&listed));
204 }
205
206 #[test]
207 fn mode_list_order_does_not_matter() {
208 let forward = kb(
209 "control",
210 "char_r",
211 Value::test_list(vec![
212 Value::test_string("emacs"),
213 Value::test_string("vi_insert"),
214 ]),
215 );
216 let reversed = kb(
217 "control",
218 "char_r",
219 Value::test_list(vec![
220 Value::test_string("vi_insert"),
221 Value::test_string("emacs"),
222 ]),
223 );
224 assert_eq!(KeyIdentity::of(&forward), KeyIdentity::of(&reversed));
225 }
226
227 #[test]
228 fn spelling_case_does_not_matter() {
229 let lower = kb("control", "char_r", Value::test_string("emacs"));
230 let upper = kb("Control", "Char_R", Value::test_string("Emacs"));
231 assert_eq!(KeyIdentity::of(&lower), KeyIdentity::of(&upper));
232 }
233
234 #[test]
239 fn modifier_component_order_does_not_matter() {
240 let cs = kb("control_shift", "char_r", Value::test_string("emacs"));
241 let sc = kb("shift_control", "char_r", Value::test_string("emacs"));
242 assert_eq!(KeyIdentity::of(&cs), KeyIdentity::of(&sc));
243 }
244
245 #[test]
246 fn esc_and_escape_are_the_same_key() {
247 let esc = kb("none", "esc", Value::test_string("emacs"));
248 let escape = kb("none", "escape", Value::test_string("emacs"));
249 assert_eq!(KeyIdentity::of(&esc), KeyIdentity::of(&escape));
250 }
251
252 #[test]
253 fn mode_names_are_not_split_on_underscores() {
254 let whole = kb("none", "char_r", Value::test_string("vi_normal"));
256 let parts = kb(
257 "none",
258 "char_r",
259 Value::test_list(vec![Value::test_string("vi"), Value::test_string("normal")]),
260 );
261 assert_ne!(KeyIdentity::of(&whole), KeyIdentity::of(&parts));
262 }
263
264 #[test]
265 fn a_different_key_is_a_different_identity() {
266 let ctrl_r = kb("control", "char_r", Value::test_string("emacs"));
267 let up = kb("none", "up", Value::test_string("emacs"));
268 assert_ne!(KeyIdentity::of(&ctrl_r), KeyIdentity::of(&up));
269 }
270}