aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--helix-term/src/commands.rs35
-rw-r--r--helix-view/src/input.rs153
2 files changed, 155 insertions, 33 deletions
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index 4e0f3ef7..524c50ce 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -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 {
diff --git a/helix-view/src/input.rs b/helix-view/src/input.rs
index 92caa517..14dadc3b 100644
--- a/helix-view/src/input.rs
+++ b/helix-view/src/input.rs
@@ -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());
+ }
}