From 8c6c93436c1597923fa56b6ac2cbe43d4251036f Mon Sep 17 00:00:00 2001 From: Ulyssa Date: Fri, 11 Sep 2026 20:06:02 -0400 Subject: [PATCH] Add `CommandMachine::add_alias` to support custom command aliasing --- crates/modalkit/src/commands.rs | 18 +++++++++++++--- crates/modalkit/src/env/vim/command/mod.rs | 25 ++++++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/crates/modalkit/src/commands.rs b/crates/modalkit/src/commands.rs index ec2879a..890d99b 100644 --- a/crates/modalkit/src/commands.rs +++ b/crates/modalkit/src/commands.rs @@ -113,6 +113,13 @@ impl CommandMachine { self.names.insert(cmd.name(), cmd); } + /// Map a new alias to an existing command. + pub fn add_alias(&mut self, alias: &str, cmd: &str) -> Result<(), CommandError> { + let c = self.get(cmd)?; + self.aliases.insert(alias.to_owned(), c.clone()); + Ok(()) + } + /// Generate a list of completion candidates for command names. pub fn complete_name(&self, prefix: &str) -> Vec { completion_keys(&self.names, prefix) @@ -123,11 +130,16 @@ impl CommandMachine { completion_keys(&self.aliases, prefix) } - /// Get the previously executed command. + /// Get the specified command by alias or name. + /// + /// Aliases are checked first, so that [Command::add_alias] overrides can take precedence, + /// and then the actual command names. + /// + /// This returns [CommandError::InvalidCommand] if there is nothing mapped. pub fn get(&self, name: &str) -> Result<&C, CommandError> { - if let Some(m) = self.names.get(name) { + if let Some(m) = self.aliases.get(name) { Ok(m) - } else if let Some(m) = self.aliases.get(name) { + } else if let Some(m) = self.names.get(name) { Ok(m) } else { Err(CommandError::InvalidCommand(name.into())) diff --git a/crates/modalkit/src/env/vim/command/mod.rs b/crates/modalkit/src/env/vim/command/mod.rs index ae567ad..bcc6fec 100644 --- a/crates/modalkit/src/env/vim/command/mod.rs +++ b/crates/modalkit/src/env/vim/command/mod.rs @@ -1150,6 +1150,31 @@ mod tests { assert_eq!(res.unwrap(), vec![(act.clone(), ctx.clone())]); } + #[test] + fn test_alias_overrides() { + let (mut cmds, ctx) = mkcmd(); + + let exp = vec![( + WindowAction::Split(OpenTarget::Current, Horizontal, Previous, 1.into()).into(), + ctx.clone(), + )]; + + // Can overwrite an existing command name ("read") to instead map to an already + // existing alias name ("sp"): + cmds.add_alias("read", "sp").unwrap(); + let res = cmds.input_cmd("read", ctx.clone()); + assert_eq!(res.unwrap(), exp); + + // Can overwrite an existing alias with another command name: + cmds.add_alias("r", "split").unwrap(); + let res = cmds.input_cmd("r", ctx.clone()); + assert_eq!(res.unwrap(), exp); + + // Fails if the target command doesn't exist: + let res = cmds.add_alias("q", "foobar"); + assert!(res.is_err()); + } + #[test] fn test_split_direction() { let (mut cmds, ctx) = mkcmd();