Skip to content

Commit 6764c33

Browse files
VulpesxMattCheely
authored andcommitted
feat: labels for sequences
1 parent 029f7b4 commit 6764c33

File tree

2 files changed

+100
-21
lines changed

2 files changed

+100
-21
lines changed

helix-term/src/config.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,8 @@ mod tests {
224224
[keys.normal]
225225
o = { label = "Edit Config", command = ":open ~/.config" }
226226
c = ":buffer-close"
227+
h = ["vsplit", "normal_mode", "swap_view_left"]
228+
j = {command = ["hsplit", "normal_mode", {}], label = "split down"}
227229
"#;
228230

229231
let config = Config::load_test(sample_keymaps);
@@ -249,6 +251,32 @@ mod tests {
249251
} else {
250252
panic!(":buffer-close command did not parse to typable command");
251253
}
254+
255+
let split_left = node.get(&KeyEvent::from_str("h").unwrap()).unwrap();
256+
if let keymap::KeyTrie::Sequence(label, cmds) = split_left {
257+
assert_eq!(label, KeyTrie::DEFAULT_SEQUENCE_LABEL);
258+
assert_eq!(
259+
*cmds,
260+
vec![
261+
MappableCommand::vsplit,
262+
MappableCommand::normal_mode,
263+
MappableCommand::swap_view_left
264+
]
265+
);
266+
}
267+
268+
let split_down = node.get(&KeyEvent::from_str("j").unwrap()).unwrap();
269+
if let keymap::KeyTrie::Sequence(label, cmds) = split_down {
270+
assert_eq!(label, "split down");
271+
assert_eq!(
272+
*cmds,
273+
vec![
274+
MappableCommand::hsplit,
275+
MappableCommand::normal_mode,
276+
MappableCommand::swap_view_down
277+
]
278+
);
279+
}
252280
} else {
253281
panic!("Config did not parse to trie");
254282
}

helix-term/src/keymap.rs

Lines changed: 72 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use std::{
1212
borrow::Cow,
1313
collections::{BTreeSet, HashMap},
1414
ops::{Deref, DerefMut},
15+
str::FromStr,
1516
sync::Arc,
1617
};
1718

@@ -83,7 +84,7 @@ impl KeyTrieNode {
8384
cmd.doc()
8485
}
8586
KeyTrie::Node(n) => &n.name,
86-
KeyTrie::Sequence(_) => "[Multiple commands]",
87+
KeyTrie::Sequence(..) => KeyTrie::DEFAULT_SEQUENCE_LABEL,
8788
};
8889
match body.iter().position(|(_, d)| d == &desc) {
8990
Some(pos) => {
@@ -133,10 +134,18 @@ impl DerefMut for KeyTrieNode {
133134
#[derive(Debug, Clone, PartialEq)]
134135
pub enum KeyTrie {
135136
MappableCommand(MappableCommand),
136-
Sequence(Vec<MappableCommand>),
137+
Sequence(String, Vec<MappableCommand>),
137138
Node(KeyTrieNode),
138139
}
139140

141+
impl KeyTrie {
142+
pub const DEFAULT_SEQUENCE_LABEL: &'static str = "[Multiple commands]";
143+
144+
pub fn sequence(commands: Vec<MappableCommand>) -> Self {
145+
Self::Sequence(Self::DEFAULT_SEQUENCE_LABEL.to_string(), commands)
146+
}
147+
}
148+
140149
impl<'de> Deserialize<'de> for KeyTrie {
141150
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
142151
where
@@ -190,7 +199,10 @@ impl<'de> serde::de::Visitor<'de> for KeyTrieVisitor {
190199
));
191200
}
192201

193-
Ok(KeyTrie::Sequence(commands))
202+
Ok(KeyTrie::Sequence(
203+
KeyTrie::DEFAULT_SEQUENCE_LABEL.to_string(),
204+
commands,
205+
))
194206
}
195207

196208
fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
@@ -205,7 +217,35 @@ impl<'de> serde::de::Visitor<'de> for KeyTrieVisitor {
205217
while let Some(key) = map.next_key::<String>()? {
206218
match &key as &str {
207219
"label" => label = map.next_value::<String>()?,
208-
"command" => command = Some(map.next_value::<MappableCommand>()?),
220+
"command" => {
221+
command = Some(match map.next_value::<toml::Value>()? {
222+
toml::Value::String(s) => {
223+
vec![MappableCommand::from_str(&s).map_err(serde::de::Error::custom)?]
224+
}
225+
toml::Value::Array(arr) => {
226+
let mut vec = Vec::with_capacity(arr.len());
227+
for value in arr {
228+
let toml::Value::String(s) = value else {
229+
return Err(serde::de::Error::invalid_type(
230+
serde::de::Unexpected::Other(value.type_str()),
231+
&"string",
232+
));
233+
};
234+
vec.push(
235+
MappableCommand::from_str(&s)
236+
.map_err(serde::de::Error::custom)?,
237+
);
238+
}
239+
vec
240+
}
241+
value => {
242+
return Err(serde::de::Error::invalid_type(
243+
serde::de::Unexpected::Other(value.type_str()),
244+
&"string or array",
245+
))
246+
}
247+
});
248+
}
209249
_ => {
210250
let key_event = key.parse::<KeyEvent>().map_err(serde::de::Error::custom)?;
211251
let key_trie = map.next_value::<KeyTrie>()?;
@@ -220,17 +260,28 @@ impl<'de> serde::de::Visitor<'de> for KeyTrieVisitor {
220260
Some(_command) if !order.is_empty() => {
221261
Err(serde::de::Error::custom("ambiguous mapping: 'command' is only valid with 'label', but I found other keys"))
222262
}
223-
Some(MappableCommand::Static { .. }) if !label.is_empty() => {
224-
Err(serde::de::Error::custom("custom labels are only available for typable commands (the ones starting with ':')"))
225-
}
226-
Some(MappableCommand::Typable { name, args, .. }) if !label.is_empty() => {
227-
Ok(KeyTrie::MappableCommand(MappableCommand::Typable {
228-
name,
229-
args,
230-
doc: label.to_string(),
231-
}))
263+
Some(mut commands) if commands.len() == 1 => match commands.pop() {
264+
None => Err(serde::de::Error::custom("UNREACHABLE!, vec is empty after checking len == 1")),
265+
Some(MappableCommand::Static { .. }) if !label.is_empty() => {
266+
Err(serde::de::Error::custom("custom labels are only available for typable commands (the ones starting with ':')"))
267+
}
268+
Some(MappableCommand::Typable { name, args, .. }) if !label.is_empty() => {
269+
Ok(KeyTrie::MappableCommand(MappableCommand::Typable {
270+
name,
271+
args,
272+
doc: label,
273+
}))
274+
}
275+
Some(command) => Ok(KeyTrie::MappableCommand(command)),
232276
}
233-
Some(command) => Ok(KeyTrie::MappableCommand(command)),
277+
Some(commands) => {
278+
let label = if label.is_empty() {
279+
KeyTrie::DEFAULT_SEQUENCE_LABEL.to_string()
280+
} else {
281+
label
282+
};
283+
Ok(KeyTrie::Sequence(label, commands))
284+
},
234285
}
235286
}
236287
}
@@ -254,7 +305,7 @@ impl KeyTrie {
254305
keys.pop();
255306
}
256307
}
257-
KeyTrie::Sequence(_) => {}
308+
KeyTrie::Sequence(..) => {}
258309
};
259310
}
260311

@@ -266,14 +317,14 @@ impl KeyTrie {
266317
pub fn node(&self) -> Option<&KeyTrieNode> {
267318
match *self {
268319
KeyTrie::Node(ref node) => Some(node),
269-
KeyTrie::MappableCommand(_) | KeyTrie::Sequence(_) => None,
320+
KeyTrie::MappableCommand(_) | KeyTrie::Sequence(..) => None,
270321
}
271322
}
272323

273324
pub fn node_mut(&mut self) -> Option<&mut KeyTrieNode> {
274325
match *self {
275326
KeyTrie::Node(ref mut node) => Some(node),
276-
KeyTrie::MappableCommand(_) | KeyTrie::Sequence(_) => None,
327+
KeyTrie::MappableCommand(_) | KeyTrie::Sequence(..) => None,
277328
}
278329
}
279330

@@ -290,7 +341,7 @@ impl KeyTrie {
290341
trie = match trie {
291342
KeyTrie::Node(map) => map.get(key),
292343
// leaf encountered while keys left to process
293-
KeyTrie::MappableCommand(_) | KeyTrie::Sequence(_) => None,
344+
KeyTrie::MappableCommand(_) | KeyTrie::Sequence(..) => None,
294345
}?
295346
}
296347
Some(trie)
@@ -380,7 +431,7 @@ impl Keymaps {
380431
Some(KeyTrie::MappableCommand(ref cmd)) => {
381432
return KeymapResult::Matched(cmd.clone());
382433
}
383-
Some(KeyTrie::Sequence(ref cmds)) => {
434+
Some(KeyTrie::Sequence(_, ref cmds)) => {
384435
return KeymapResult::MatchedSequence(cmds.clone());
385436
}
386437
None => return KeymapResult::NotFound,
@@ -400,7 +451,7 @@ impl Keymaps {
400451
self.state.clear();
401452
KeymapResult::Matched(cmd.clone())
402453
}
403-
Some(KeyTrie::Sequence(cmds)) => {
454+
Some(KeyTrie::Sequence(_, cmds)) => {
404455
self.state.clear();
405456
KeymapResult::MatchedSequence(cmds.clone())
406457
}
@@ -625,7 +676,7 @@ mod tests {
625676
let expectation = KeyTrie::Node(KeyTrieNode::new(
626677
"",
627678
hashmap! {
628-
key => KeyTrie::Sequence(vec!{
679+
key => KeyTrie::sequence(vec!{
629680
MappableCommand::select_all,
630681
MappableCommand::Typable {
631682
name: "pipe".to_string(),

0 commit comments

Comments
 (0)