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