Unnamed repository; edit this file 'description' to name the repository.
| -rw-r--r-- | book/src/command-line.md | 1 | ||||
| -rw-r--r-- | helix-core/src/command_line.rs | 9 | ||||
| -rw-r--r-- | helix-term/src/commands/typed.rs | 19 | ||||
| -rw-r--r-- | helix-term/tests/test/command_line.rs | 42 | ||||
| -rw-r--r-- | helix-view/src/expansion.rs | 19 |
5 files changed, 88 insertions, 2 deletions
diff --git a/book/src/command-line.md b/book/src/command-line.md index 0e492219..3d9be122 100644 --- a/book/src/command-line.md +++ b/book/src/command-line.md @@ -59,6 +59,7 @@ Aside from editor variables, the following expansions may be used: * Unicode `%u{..}`. The contents may contain up to six hexadecimal numbers corresponding to a Unicode codepoint value. For example `:echo %u{25CF}` prints `●` to the statusline. * Shell `%sh{..}`. The contents are passed to the configured shell command. For example `:echo %sh{echo "20 * 5" | bc}` may print `100` on the statusline on when using a shell with `echo` and the `bc` calculator installed. Shell expansions are evaluated recursively. `%sh{echo '%{buffer_name}:%{cursor_line}'}` for example executes a command like `echo 'README.md:1'`: the variables within the `%sh{..}` expansion are evaluated before executing the shell command. +* Register `%reg{..}`. The contents should be a single character representing the register name. For example, `:set-register a hello world` followed by `echo %reg{a}` prints `hello world` to the statusline. As mentioned above, double quotes can be used to surround arguments containing spaces but also support expansions within the quoted content unlike single quotes or backticks. For example `:echo "circle: %u{25CF}"` prints `circle: ●` to the statusline while `:echo 'circle: %u{25CF}'` prints `circle: %u{25CF}`. diff --git a/helix-core/src/command_line.rs b/helix-core/src/command_line.rs index 8e209d61..db7a71d7 100644 --- a/helix-core/src/command_line.rs +++ b/helix-core/src/command_line.rs @@ -253,16 +253,22 @@ pub enum ExpansionKind { /// /// For example `%sh{echo hello}`. Shell, + /// Expand registers from the editor's state. + /// + /// For example `%reg{a}`. + Register, } impl ExpansionKind { - pub const VARIANTS: &'static [Self] = &[Self::Variable, Self::Unicode, Self::Shell]; + pub const VARIANTS: &'static [Self] = + &[Self::Variable, Self::Unicode, Self::Shell, Self::Register]; pub const fn as_str(&self) -> &'static str { match self { Self::Variable => "", Self::Unicode => "u", Self::Shell => "sh", + Self::Register => "reg", } } @@ -271,6 +277,7 @@ impl ExpansionKind { "" => Some(Self::Variable), "u" => Some(Self::Unicode), "sh" => Some(Self::Shell), + "reg" => Some(Self::Register), _ => None, } } diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 6354e68f..85cf6b1d 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -4261,6 +4261,9 @@ pub fn complete_command_args( complete_variable_expansion(&token.content, offset + token.content_start) } TokenKind::Expansion(ExpansionKind::Unicode) => Vec::new(), + TokenKind::Expansion(ExpansionKind::Register) => { + complete_register_expansion(editor, &token.content, offset + token.content_start) + } TokenKind::ExpansionKind => { complete_expansion_kind(&token.content, offset + token.content_start) } @@ -4386,6 +4389,22 @@ fn complete_variable_expansion(content: &str, offset: usize) -> Vec<ui::prompt:: .collect() } +fn complete_register_expansion( + editor: &Editor, + content: &str, + offset: usize, +) -> Vec<ui::prompt::Completion> { + let register_names: Vec<String> = editor + .registers + .iter_preview() + .map(|(ch, _)| ch.to_string()) + .collect(); + fuzzy_match(content, register_names, false) + .into_iter() + .map(|(name, _)| (offset.., name.to_string().into())) + .collect() +} + fn complete_expansion_kind(content: &str, offset: usize) -> Vec<ui::prompt::Completion> { use command_line::ExpansionKind; diff --git a/helix-term/tests/test/command_line.rs b/helix-term/tests/test/command_line.rs index a26f1422..74197790 100644 --- a/helix-term/tests/test/command_line.rs +++ b/helix-term/tests/test/command_line.rs @@ -107,6 +107,48 @@ async fn shell_expansion() -> anyhow::Result<()> { } #[tokio::test(flavor = "multi_thread")] +async fn register_expansion() -> anyhow::Result<()> { + test_statusline( + r#":set-register a hello world<ret>:echo %reg{a}"#, + "hello world", + Severity::Info, + ) + .await?; + test_statusline(r#":echo %reg{a}"#, "", Severity::Info).await?; + test_statusline( + r#":echo %reg{abc}"#, + "'echo': Invalid register `abc`: should only be a single character", + Severity::Error, + ) + .await?; + + // Register expansion evaluation is *not* recursive. + test_statusline( + r#":set-register a b<ret>:set-register b hello<ret>:echo %reg{%reg{a}}"#, + "'echo': Invalid register `%reg{a}`: should only be a single character", + Severity::Error, + ) + .await?; + test_statusline( + r#":set-register a hello<ret>:set-register b %%reg{a}<ret>:echo %reg{b}"#, + "%reg{a}", + Severity::Info, + ) + .await?; + + // However, you can copy the contents of one register into another with this expansion if you + // want to. + test_statusline( + r#":set-register a hello<ret>:set-register b %reg{a}<ret>:echo %reg{b}"#, + "hello", + Severity::Info, + ) + .await?; + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] async fn percent_escaping() -> anyhow::Result<()> { test_statusline( r#":sh echo hello 10%"#, diff --git a/helix-view/src/expansion.rs b/helix-view/src/expansion.rs index d2660dd0..db68c614 100644 --- a/helix-view/src/expansion.rs +++ b/helix-view/src/expansion.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use helix_core::command_line::{ExpansionKind, Token, TokenKind, Tokenizer}; -use anyhow::{anyhow, bail, Result}; +use anyhow::{anyhow, bail, ensure, Result}; use crate::Editor; @@ -128,6 +128,7 @@ pub fn expand<'a>(editor: &Editor, token: Token<'a>) -> Result<Cow<'a, str>> { } TokenKind::Expand => expand_inner(editor, token.content), TokenKind::Expansion(ExpansionKind::Shell) => expand_shell(editor, token.content), + TokenKind::Expansion(ExpansionKind::Register) => expand_register(editor, token.content), // Note: see the docs for this variant. TokenKind::ExpansionKind => unreachable!( "expansion name tokens cannot be emitted when command line validation is enabled" @@ -183,6 +184,22 @@ pub fn expand_shell<'a>(editor: &Editor, content: Cow<'a, str>) -> Result<Cow<'a Ok(Cow::Owned(text)) } +/// Expand a register's contents +pub fn expand_register<'a>(editor: &Editor, content: Cow<'a, str>) -> Result<Cow<'a, str>> { + let mut chars = content.chars(); + let Some(r) = chars.next() else { + bail!("Missing register name"); + }; + ensure!( + chars.next().is_none(), + "Invalid register `{content}`: should only be a single character" + ); + match editor.registers.read(r, editor) { + Some(values) => Ok(Cow::Owned(values.collect())), + None => Ok(Cow::Borrowed("")), + } +} + /// Expand a token's contents recursively. fn expand_inner<'a>(editor: &Editor, content: Cow<'a, str>) -> Result<Cow<'a, str>> { let mut escaped = String::new(); |