mirror of https://github.com/helix-editor/helix
command expansion
parent
21fd654cc6
commit
731985c133
@ -0,0 +1,133 @@
|
||||
use super::*;
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_variable_expansion() -> anyhow::Result<()> {
|
||||
{
|
||||
let mut app = AppBuilder::new().build()?;
|
||||
|
||||
test_key_sequence(
|
||||
&mut app,
|
||||
Some("<esc>:echo %{filename}<ret>"),
|
||||
Some(&|app| {
|
||||
assert_eq!(
|
||||
app.editor.get_status().unwrap().0,
|
||||
helix_view::document::SCRATCH_BUFFER_NAME
|
||||
);
|
||||
}),
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut app = AppBuilder::new().build()?;
|
||||
|
||||
test_key_sequence(
|
||||
&mut app,
|
||||
Some("<esc>:echo %{basename}<ret>"),
|
||||
Some(&|app| {
|
||||
assert_eq!(
|
||||
app.editor.get_status().unwrap().0,
|
||||
helix_view::document::SCRATCH_BUFFER_NAME
|
||||
);
|
||||
}),
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut app = AppBuilder::new().build()?;
|
||||
|
||||
test_key_sequence(
|
||||
&mut app,
|
||||
Some("<esc>:echo %{dirname}<ret>"),
|
||||
Some(&|app| {
|
||||
assert_eq!(
|
||||
app.editor.get_status().unwrap().0,
|
||||
helix_view::document::SCRATCH_BUFFER_NAME
|
||||
);
|
||||
}),
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
{
|
||||
let file = tempfile::NamedTempFile::new()?;
|
||||
let mut app = AppBuilder::new().with_file(file.path(), None).build()?;
|
||||
|
||||
test_key_sequence(
|
||||
&mut app,
|
||||
Some("<esc>:echo %{filename}<ret>"),
|
||||
Some(&|app| {
|
||||
assert_eq!(
|
||||
app.editor.get_status().unwrap().0,
|
||||
helix_stdx::path::canonicalize(file.path())
|
||||
.to_str()
|
||||
.unwrap()
|
||||
);
|
||||
}),
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut app = AppBuilder::new().with_file(file.path(), None).build()?;
|
||||
|
||||
test_key_sequence(
|
||||
&mut app,
|
||||
Some("<esc>:echo %{basename}<ret>"),
|
||||
Some(&|app| {
|
||||
assert_eq!(
|
||||
app.editor.get_status().unwrap().0,
|
||||
file.path().file_name().unwrap().to_str().unwrap()
|
||||
);
|
||||
}),
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut app = AppBuilder::new().with_file(file.path(), None).build()?;
|
||||
|
||||
test_key_sequence(
|
||||
&mut app,
|
||||
Some("<esc>:echo %{dirname}<ret>"),
|
||||
Some(&|app| {
|
||||
assert_eq!(
|
||||
app.editor.get_status().unwrap().0,
|
||||
helix_stdx::path::canonicalize(file.path().parent().unwrap())
|
||||
.to_str()
|
||||
.unwrap()
|
||||
);
|
||||
}),
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
{
|
||||
let file = tempfile::NamedTempFile::new()?;
|
||||
let mut app = AppBuilder::new().with_file(file.path(), None).build()?;
|
||||
test_key_sequence(
|
||||
&mut app,
|
||||
Some("ihelix<esc>%:echo %{selection}<ret>"),
|
||||
Some(&|app| {
|
||||
assert_eq!(app.editor.get_status().unwrap().0, "helix");
|
||||
}),
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
{
|
||||
let file = tempfile::NamedTempFile::new()?;
|
||||
let mut app = AppBuilder::new().with_file(file.path(), None).build()?;
|
||||
test_key_sequence(
|
||||
&mut app,
|
||||
Some("ihelix<ret>helix<ret>helix<ret><esc>:echo %{linenumber}<ret>"),
|
||||
Some(&|app| {
|
||||
assert_eq!(app.editor.get_status().unwrap().0, "4");
|
||||
}),
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
@ -0,0 +1,147 @@
|
||||
use crate::Editor;
|
||||
use std::borrow::Cow;
|
||||
|
||||
impl Editor {
|
||||
pub fn expand_variables<'a>(&self, input: &'a str) -> anyhow::Result<Cow<'a, str>> {
|
||||
let (view, doc) = current_ref!(self);
|
||||
let shell = &self.config().shell;
|
||||
|
||||
let mut output: Option<String> = None;
|
||||
|
||||
let mut chars = input.char_indices();
|
||||
let mut last_push_end: usize = 0;
|
||||
|
||||
while let Some((index, char)) = chars.next() {
|
||||
if char == '%' {
|
||||
if let Some((_, char)) = chars.next() {
|
||||
if char == '{' {
|
||||
for (end, char) in chars.by_ref() {
|
||||
if char == '}' {
|
||||
if output.is_none() {
|
||||
output = Some(String::with_capacity(input.len()))
|
||||
}
|
||||
|
||||
if let Some(o) = output.as_mut() {
|
||||
o.push_str(&input[last_push_end..index]);
|
||||
last_push_end = end + 1;
|
||||
|
||||
let value = match &input[index + 2..end] {
|
||||
"basename" => doc
|
||||
.path()
|
||||
.and_then(|it| {
|
||||
it.file_name().and_then(|it| it.to_str())
|
||||
})
|
||||
.unwrap_or(crate::document::SCRATCH_BUFFER_NAME)
|
||||
.to_owned(),
|
||||
"filename" => doc
|
||||
.path()
|
||||
.and_then(|it| it.to_str())
|
||||
.unwrap_or(crate::document::SCRATCH_BUFFER_NAME)
|
||||
.to_owned(),
|
||||
"dirname" => doc
|
||||
.path()
|
||||
.and_then(|p| p.parent())
|
||||
.and_then(std::path::Path::to_str)
|
||||
.unwrap_or(crate::document::SCRATCH_BUFFER_NAME)
|
||||
.to_owned(),
|
||||
"cwd" => helix_stdx::env::current_working_dir()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_owned(),
|
||||
"linenumber" => (doc
|
||||
.selection(view.id)
|
||||
.primary()
|
||||
.cursor_line(doc.text().slice(..))
|
||||
+ 1)
|
||||
.to_string(),
|
||||
"selection" => doc
|
||||
.selection(view.id)
|
||||
.primary()
|
||||
.fragment(doc.text().slice(..))
|
||||
.to_string(),
|
||||
_ => anyhow::bail!("Unknown variable"),
|
||||
};
|
||||
|
||||
o.push_str(value.trim());
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if char == 's' {
|
||||
if let (Some((_, 'h')), Some((_, '{'))) = (chars.next(), chars.next()) {
|
||||
let mut right_bracket_remaining = 1;
|
||||
for (end, char) in chars.by_ref() {
|
||||
if char == '}' {
|
||||
right_bracket_remaining -= 1;
|
||||
|
||||
if right_bracket_remaining == 0 {
|
||||
if output.is_none() {
|
||||
output = Some(String::with_capacity(input.len()))
|
||||
}
|
||||
|
||||
if let Some(o) = output.as_mut() {
|
||||
let body =
|
||||
self.expand_variables(&input[index + 4..end])?;
|
||||
|
||||
let output = tokio::task::block_in_place(move || {
|
||||
helix_lsp::block_on(async move {
|
||||
let mut command =
|
||||
tokio::process::Command::new(&shell[0]);
|
||||
command.args(&shell[1..]).arg(&body[..]);
|
||||
|
||||
let output =
|
||||
command.output().await.map_err(|_| {
|
||||
anyhow::anyhow!(
|
||||
"Shell command failed: {body}"
|
||||
)
|
||||
})?;
|
||||
|
||||
if output.status.success() {
|
||||
String::from_utf8(output.stdout).map_err(
|
||||
|_| {
|
||||
anyhow::anyhow!(
|
||||
"Process did not output valid UTF-8"
|
||||
)
|
||||
},
|
||||
)
|
||||
} else if output.stderr.is_empty() {
|
||||
Err(anyhow::anyhow!(
|
||||
"Shell command failed: {body}"
|
||||
))
|
||||
} else {
|
||||
let stderr =
|
||||
String::from_utf8_lossy(&output.stderr);
|
||||
|
||||
Err(anyhow::anyhow!("{stderr}"))
|
||||
}
|
||||
})
|
||||
});
|
||||
o.push_str(&input[last_push_end..index]);
|
||||
last_push_end = end + 1;
|
||||
|
||||
o.push_str(output?.trim());
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if char == '{' {
|
||||
right_bracket_remaining += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(o) = output.as_mut() {
|
||||
o.push_str(&input[last_push_end..]);
|
||||
}
|
||||
|
||||
match output {
|
||||
Some(o) => Ok(Cow::Owned(o)),
|
||||
None => Ok(Cow::Borrowed(input)),
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue