Parse and execute macro mappable commands
This commit is contained in:
parent
bb43a90b86
commit
1098a348aa
2 changed files with 47 additions and 3 deletions
|
@ -176,9 +176,16 @@ where
|
||||||
|
|
||||||
use helix_view::{align_view, Align};
|
use helix_view::{align_view, Align};
|
||||||
|
|
||||||
/// A MappableCommand is either a static command like "jump_view_up" or a Typable command like
|
/// MappableCommands are commands that can be bound to keys, executable in
|
||||||
/// :format. It causes a side-effect on the state (usually by creating and applying a transaction).
|
/// normal, insert or select mode.
|
||||||
/// Both of these types of commands can be mapped with keybindings in the config.toml.
|
///
|
||||||
|
/// 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)]
|
#[derive(Clone)]
|
||||||
pub enum MappableCommand {
|
pub enum MappableCommand {
|
||||||
Typable {
|
Typable {
|
||||||
|
@ -191,6 +198,10 @@ pub enum MappableCommand {
|
||||||
fun: fn(cx: &mut Context),
|
fun: fn(cx: &mut Context),
|
||||||
doc: &'static str,
|
doc: &'static str,
|
||||||
},
|
},
|
||||||
|
Macro {
|
||||||
|
name: String,
|
||||||
|
keys: Vec<KeyEvent>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! static_commands {
|
macro_rules! static_commands {
|
||||||
|
@ -227,6 +238,23 @@ impl MappableCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::Static { fun, .. } => (fun)(cx),
|
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 {
|
match &self {
|
||||||
Self::Typable { name, .. } => name,
|
Self::Typable { name, .. } => name,
|
||||||
Self::Static { name, .. } => name,
|
Self::Static { name, .. } => name,
|
||||||
|
Self::Macro { name, .. } => name,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -241,6 +270,7 @@ impl MappableCommand {
|
||||||
match &self {
|
match &self {
|
||||||
Self::Typable { doc, .. } => doc,
|
Self::Typable { doc, .. } => doc,
|
||||||
Self::Static { doc, .. } => doc,
|
Self::Static { doc, .. } => doc,
|
||||||
|
Self::Macro { name, .. } => name,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -551,6 +581,11 @@ impl fmt::Debug for MappableCommand {
|
||||||
.field(name)
|
.field(name)
|
||||||
.field(args)
|
.field(args)
|
||||||
.finish(),
|
.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,
|
args,
|
||||||
})
|
})
|
||||||
.ok_or_else(|| anyhow!("No TypableCommand named '{}'", s))
|
.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 {
|
} else {
|
||||||
MappableCommand::STATIC_COMMAND_LIST
|
MappableCommand::STATIC_COMMAND_LIST
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -3185,6 +3225,9 @@ pub fn command_palette(cx: &mut Context) {
|
||||||
ui::PickerColumn::new("name", |item, _| match item {
|
ui::PickerColumn::new("name", |item, _| match item {
|
||||||
MappableCommand::Typable { name, .. } => format!(":{name}").into(),
|
MappableCommand::Typable { name, .. } => format!(":{name}").into(),
|
||||||
MappableCommand::Static { name, .. } => (*name).into(),
|
MappableCommand::Static { name, .. } => (*name).into(),
|
||||||
|
MappableCommand::Macro { .. } => {
|
||||||
|
unreachable!("macros aren't included in the command palette")
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
ui::PickerColumn::new(
|
ui::PickerColumn::new(
|
||||||
"bindings",
|
"bindings",
|
||||||
|
|
|
@ -199,6 +199,7 @@ impl KeyTrie {
|
||||||
// recursively visit all nodes in keymap
|
// recursively visit all nodes in keymap
|
||||||
fn map_node(cmd_map: &mut ReverseKeymap, node: &KeyTrie, keys: &mut Vec<KeyEvent>) {
|
fn map_node(cmd_map: &mut ReverseKeymap, node: &KeyTrie, keys: &mut Vec<KeyEvent>) {
|
||||||
match node {
|
match node {
|
||||||
|
KeyTrie::MappableCommand(MappableCommand::Macro { .. }) => {}
|
||||||
KeyTrie::MappableCommand(cmd) => {
|
KeyTrie::MappableCommand(cmd) => {
|
||||||
let name = cmd.name();
|
let name = cmd.name();
|
||||||
if name != "no_op" {
|
if name != "no_op" {
|
||||||
|
|
Loading…
Add table
Reference in a new issue