Unnamed repository; edit this file 'description' to name the repository.
-rw-r--r--book/src/command-line.md1
-rw-r--r--helix-core/src/command_line.rs9
-rw-r--r--helix-term/src/commands/typed.rs19
-rw-r--r--helix-term/tests/test/command_line.rs42
-rw-r--r--helix-view/src/expansion.rs19
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();