Unnamed repository; edit this file 'description' to name the repository.
-rw-r--r--helix-term/src/commands.rs49
-rw-r--r--helix-term/src/keymap.rs1
2 files changed, 47 insertions, 3 deletions
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index 835283ad..9ccd8ea1 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -176,9 +176,16 @@ where
use helix_view::{align_view, Align};
-/// A MappableCommand is either a static command like "jump_view_up" or a Typable command like
-/// :format. It causes a side-effect on the state (usually by creating and applying a transaction).
-/// Both of these types of commands can be mapped with keybindings in the config.toml.
+/// MappableCommands are commands that can be bound to keys, executable in
+/// normal, insert or select mode.
+///
+/// There are three kinds:
+///
+/// * Static: commands usually bound to keys and used for editing, movement,
+/// etc., for example `move_char_left`.
+/// * Typable: commands executable from command mode, prefixed with a `:`,
+/// for example `:write!`.
+/// * Macro: a sequence of keys to execute, for example `@miw`.
#[derive(Clone)]
pub enum MappableCommand {
Typable {
@@ -191,6 +198,10 @@ pub enum MappableCommand {
fun: fn(cx: &mut Context),
doc: &'static str,
},
+ Macro {
+ name: String,
+ keys: Vec<KeyEvent>,
+ },
}
macro_rules! static_commands {
@@ -227,6 +238,23 @@ impl MappableCommand {
}
}
Self::Static { fun, .. } => (fun)(cx),
+ Self::Macro { keys, .. } => {
+ // Protect against recursive macros.
+ if cx.editor.macro_replaying.contains(&'@') {
+ cx.editor.set_error(
+ "Cannot execute macro because the [@] register is already playing a macro",
+ );
+ return;
+ }
+ cx.editor.macro_replaying.push('@');
+ let keys = keys.clone();
+ cx.callback.push(Box::new(move |compositor, cx| {
+ for key in keys.into_iter() {
+ compositor.handle_event(&compositor::Event::Key(key), cx);
+ }
+ cx.editor.macro_replaying.pop();
+ }));
+ }
}
}
@@ -234,6 +262,7 @@ impl MappableCommand {
match &self {
Self::Typable { name, .. } => name,
Self::Static { name, .. } => name,
+ Self::Macro { name, .. } => name,
}
}
@@ -241,6 +270,7 @@ impl MappableCommand {
match &self {
Self::Typable { doc, .. } => doc,
Self::Static { doc, .. } => doc,
+ Self::Macro { name, .. } => name,
}
}
@@ -551,6 +581,11 @@ impl fmt::Debug for MappableCommand {
.field(name)
.field(args)
.finish(),
+ MappableCommand::Macro { name, keys, .. } => f
+ .debug_tuple("MappableCommand")
+ .field(name)
+ .field(keys)
+ .finish(),
}
}
}
@@ -581,6 +616,11 @@ impl std::str::FromStr for MappableCommand {
args,
})
.ok_or_else(|| anyhow!("No TypableCommand named '{}'", s))
+ } else if let Some(suffix) = s.strip_prefix('@') {
+ helix_view::input::parse_macro(suffix).map(|keys| Self::Macro {
+ name: s.to_string(),
+ keys,
+ })
} else {
MappableCommand::STATIC_COMMAND_LIST
.iter()
@@ -3185,6 +3225,9 @@ pub fn command_palette(cx: &mut Context) {
ui::PickerColumn::new("name", |item, _| match item {
MappableCommand::Typable { name, .. } => format!(":{name}").into(),
MappableCommand::Static { name, .. } => (*name).into(),
+ MappableCommand::Macro { .. } => {
+ unreachable!("macros aren't included in the command palette")
+ }
}),
ui::PickerColumn::new(
"bindings",
diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs
index 975274ed..4d8d13d6 100644
--- a/helix-term/src/keymap.rs
+++ b/helix-term/src/keymap.rs
@@ -199,6 +199,7 @@ impl KeyTrie {
// recursively visit all nodes in keymap
fn map_node(cmd_map: &mut ReverseKeymap, node: &KeyTrie, keys: &mut Vec<KeyEvent>) {
match node {
+ KeyTrie::MappableCommand(MappableCommand::Macro { .. }) => {}
KeyTrie::MappableCommand(cmd) => {
let name = cmd.name();
if name != "no_op" {