Extract macro parsing to helix-view
and add unit tests
This commit is contained in:
parent
2d4bc0aec7
commit
8340d73545
2 changed files with 155 additions and 33 deletions
|
@ -6037,39 +6037,8 @@ fn record_macro(cx: &mut Context) {
|
|||
|
||||
fn replay_macro(cx: &mut Context) {
|
||||
let reg = cx.register.unwrap_or('@');
|
||||
// TODO: macro keys should be parsed one by one and not space delimited (see kak)
|
||||
let keys: Vec<KeyEvent> = if let Some([keys_str]) = cx.editor.registers.read(reg) {
|
||||
let mut keys_res: anyhow::Result<_> = Ok(Vec::new());
|
||||
let mut i = 0;
|
||||
while let Ok(keys) = &mut keys_res {
|
||||
if i >= keys_str.len() {
|
||||
break;
|
||||
}
|
||||
if !keys_str.is_char_boundary(i) {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
let s = &keys_str[i..];
|
||||
let mut end_i = 1;
|
||||
while !s.is_char_boundary(end_i) {
|
||||
end_i += 1;
|
||||
}
|
||||
let c = &s[..end_i];
|
||||
if c != "<" {
|
||||
keys.push(c);
|
||||
i += end_i;
|
||||
} else {
|
||||
match s.find('>').context("'>' expected") {
|
||||
Ok(end_i) => {
|
||||
keys.push(&s[1..end_i]);
|
||||
i += end_i + 1;
|
||||
}
|
||||
Err(err) => keys_res = Err(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
match keys_res.and_then(|keys| keys.into_iter().map(str::parse).collect()) {
|
||||
match helix_view::input::parse_macro(keys_str) {
|
||||
Ok(keys) => keys,
|
||||
Err(err) => {
|
||||
cx.editor.set_error(format!("Invalid macro: {}", err));
|
||||
|
@ -6080,8 +6049,8 @@ fn replay_macro(cx: &mut Context) {
|
|||
cx.editor.set_error(format!("Register [{}] empty", reg));
|
||||
return;
|
||||
};
|
||||
let count = cx.count();
|
||||
|
||||
let count = cx.count();
|
||||
cx.callback = Some(Box::new(
|
||||
move |compositor: &mut Compositor, cx: &mut compositor::Context| {
|
||||
for _ in 0..count {
|
||||
|
|
|
@ -254,6 +254,43 @@ impl From<KeyEvent> for crossterm::event::KeyEvent {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn parse_macro(keys_str: &str) -> anyhow::Result<Vec<KeyEvent>> {
|
||||
use anyhow::Context;
|
||||
let mut keys_res: anyhow::Result<_> = Ok(Vec::new());
|
||||
let mut i = 0;
|
||||
while let Ok(keys) = &mut keys_res {
|
||||
if i >= keys_str.len() {
|
||||
break;
|
||||
}
|
||||
if !keys_str.is_char_boundary(i) {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
let s = &keys_str[i..];
|
||||
let mut end_i = 1;
|
||||
while !s.is_char_boundary(end_i) {
|
||||
end_i += 1;
|
||||
}
|
||||
let c = &s[..end_i];
|
||||
if c == ">" {
|
||||
keys_res = Err(anyhow!("Unmatched '>'"));
|
||||
} else if c != "<" {
|
||||
keys.push(c);
|
||||
i += end_i;
|
||||
} else {
|
||||
match s.find('>').context("'>' expected") {
|
||||
Ok(end_i) => {
|
||||
keys.push(&s[1..end_i]);
|
||||
i += end_i + 1;
|
||||
}
|
||||
Err(err) => keys_res = Err(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
keys_res.and_then(|keys| keys.into_iter().map(str::parse).collect())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
@ -339,4 +376,120 @@ mod test {
|
|||
assert!(str::parse::<KeyEvent>("123").is_err());
|
||||
assert!(str::parse::<KeyEvent>("S--").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parsing_valid_macros() {
|
||||
assert_eq!(
|
||||
parse_macro("xdo").ok(),
|
||||
Some(vec![
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('x'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
},
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('d'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
},
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('o'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
},
|
||||
]),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parse_macro("<C-w>v<C-w>h<C-o>xx<A-s>").ok(),
|
||||
Some(vec![
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('w'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
},
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('v'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
},
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('w'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
},
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('h'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
},
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('o'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
},
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('x'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
},
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('x'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
},
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('s'),
|
||||
modifiers: KeyModifiers::ALT,
|
||||
},
|
||||
])
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parse_macro(":o foo.bar<ret>").ok(),
|
||||
Some(vec![
|
||||
KeyEvent {
|
||||
code: KeyCode::Char(':'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
},
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('o'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
},
|
||||
KeyEvent {
|
||||
code: KeyCode::Char(' '),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
},
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('f'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
},
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('o'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
},
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('o'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
},
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('.'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
},
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('b'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
},
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('a'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
},
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('r'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
},
|
||||
KeyEvent {
|
||||
code: KeyCode::Enter,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
},
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parsing_invalid_macros_fails() {
|
||||
assert!(parse_macro("abc<C-").is_err());
|
||||
assert!(parse_macro("abc>123").is_err());
|
||||
assert!(parse_macro("wd<foo>").is_err());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue