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 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 "emacs" => self.emacs.update(val, path, errors),
144 "helix_normal" => self.helix_normal.update(val, path, errors),
145 "helix_select" => self.helix_select.update(val, path, errors),
146 "helix_insert" => self.helix_insert.update(val, path, errors),
147 _ => errors.unknown_option(path, val),
148 }
149 }
150 }
151}
152
153#[derive(Clone, Copy, Debug, Default, IntoValue, PartialEq, Eq, Serialize, Deserialize)]
154pub enum EditBindings {
155 Vi,
156 #[default]
157 Emacs,
158 Helix,
161}
162
163impl FromStr for EditBindings {
164 type Err = &'static str;
165
166 fn from_str(s: &str) -> Result<Self, Self::Err> {
167 match s.to_ascii_lowercase().as_str() {
168 "vi" => Ok(Self::Vi),
169 "emacs" => Ok(Self::Emacs),
170 "helix" => Ok(Self::Helix),
171 _ => Err("'emacs', 'vi' or 'helix'"),
172 }
173 }
174}
175
176impl UpdateFromValue for EditBindings {
177 fn update(&mut self, value: &Value, path: &mut ConfigPath, errors: &mut ConfigErrors) {
178 config_update_string_enum(self, value, path, errors)
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185
186 fn kb(modifier: &str, keycode: &str, mode: Value) -> ParsedKeybinding {
187 ParsedKeybinding {
188 name: None,
189 modifier: Value::test_string(modifier),
190 keycode: Value::test_string(keycode),
191 event: Value::test_nothing(),
192 mode,
193 }
194 }
195
196 #[test]
197 fn a_bare_mode_and_its_singleton_list_are_the_same_key() {
198 let bare = kb("control", "char_r", Value::test_string("emacs"));
199 let listed = kb(
200 "control",
201 "char_r",
202 Value::test_list(vec![Value::test_string("emacs")]),
203 );
204 assert_eq!(KeyIdentity::of(&bare), KeyIdentity::of(&listed));
205 }
206
207 #[test]
208 fn mode_list_order_does_not_matter() {
209 let forward = kb(
210 "control",
211 "char_r",
212 Value::test_list(vec![
213 Value::test_string("emacs"),
214 Value::test_string("vi_insert"),
215 ]),
216 );
217 let reversed = kb(
218 "control",
219 "char_r",
220 Value::test_list(vec![
221 Value::test_string("vi_insert"),
222 Value::test_string("emacs"),
223 ]),
224 );
225 assert_eq!(KeyIdentity::of(&forward), KeyIdentity::of(&reversed));
226 }
227
228 #[test]
229 fn spelling_case_does_not_matter() {
230 let lower = kb("control", "char_r", Value::test_string("emacs"));
231 let upper = kb("Control", "Char_R", Value::test_string("Emacs"));
232 assert_eq!(KeyIdentity::of(&lower), KeyIdentity::of(&upper));
233 }
234
235 #[test]
240 fn modifier_component_order_does_not_matter() {
241 let cs = kb("control_shift", "char_r", Value::test_string("emacs"));
242 let sc = kb("shift_control", "char_r", Value::test_string("emacs"));
243 assert_eq!(KeyIdentity::of(&cs), KeyIdentity::of(&sc));
244 }
245
246 #[test]
247 fn esc_and_escape_are_the_same_key() {
248 let esc = kb("none", "esc", Value::test_string("emacs"));
249 let escape = kb("none", "escape", Value::test_string("emacs"));
250 assert_eq!(KeyIdentity::of(&esc), KeyIdentity::of(&escape));
251 }
252
253 #[test]
254 fn mode_names_are_not_split_on_underscores() {
255 let whole = kb("none", "char_r", Value::test_string("vi_normal"));
257 let parts = kb(
258 "none",
259 "char_r",
260 Value::test_list(vec![Value::test_string("vi"), Value::test_string("normal")]),
261 );
262 assert_ne!(KeyIdentity::of(&whole), KeyIdentity::of(&parts));
263 }
264
265 #[test]
266 fn a_different_key_is_a_different_identity() {
267 let ctrl_r = kb("control", "char_r", Value::test_string("emacs"));
268 let up = kb("none", "up", Value::test_string("emacs"));
269 assert_ne!(KeyIdentity::of(&ctrl_r), KeyIdentity::of(&up));
270 }
271}