mirror of https://github.com/helix-editor/helix
Expand buffer_name variable in format command
parent
99d442ab8a
commit
7555f30f15
|
@ -5,6 +5,7 @@ use futures_util::future::BoxFuture;
|
||||||
use futures_util::FutureExt;
|
use futures_util::FutureExt;
|
||||||
use helix_core::auto_pairs::AutoPairs;
|
use helix_core::auto_pairs::AutoPairs;
|
||||||
use helix_core::chars::char_is_word;
|
use helix_core::chars::char_is_word;
|
||||||
|
use helix_core::command_line::{ExpansionKind, TokenKind, Tokenizer};
|
||||||
use helix_core::diagnostic::DiagnosticProvider;
|
use helix_core::diagnostic::DiagnosticProvider;
|
||||||
use helix_core::doc_formatter::TextFormat;
|
use helix_core::doc_formatter::TextFormat;
|
||||||
use helix_core::encoding::Encoding;
|
use helix_core::encoding::Encoding;
|
||||||
|
@ -42,6 +43,7 @@ use helix_core::{
|
||||||
ChangeSet, Diagnostic, LineEnding, Range, Rope, RopeBuilder, Selection, Syntax, Transaction,
|
ChangeSet, Diagnostic, LineEnding, Range, Rope, RopeBuilder, Selection, Syntax, Transaction,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::expansion::Variable;
|
||||||
use crate::{
|
use crate::{
|
||||||
editor::Config,
|
editor::Config,
|
||||||
events::{DocumentDidChange, SelectionDidChange},
|
events::{DocumentDidChange, SelectionDidChange},
|
||||||
|
@ -814,70 +816,120 @@ impl Document {
|
||||||
process.current_dir(doc_dir);
|
process.current_dir(doc_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
let filename_in_args = fmt_args.iter().any(|arg| arg.contains("{}"));
|
let fmt_args = fmt_args
|
||||||
let fmt_args = filename_in_args
|
.iter()
|
||||||
.then_some(self.path().map(|path| {
|
.map(|content| {
|
||||||
let path = path.to_string_lossy();
|
let mut escaped = String::new();
|
||||||
Cow::Owned(
|
let mut start = 0;
|
||||||
fmt_args
|
|
||||||
.iter()
|
|
||||||
.map(|arg| arg.replace("{}", &path))
|
|
||||||
.collect(),
|
|
||||||
)
|
|
||||||
}))
|
|
||||||
.flatten()
|
|
||||||
.unwrap_or(Cow::Borrowed(fmt_args));
|
|
||||||
|
|
||||||
process
|
while let Some(offset) = content[start..].find('%') {
|
||||||
.args(fmt_args.as_ref())
|
let idx = start + offset;
|
||||||
.stdin(Stdio::piped())
|
if content.as_bytes().get(idx + '%'.len_utf8()).copied() == Some(b'%') {
|
||||||
.stdout(Stdio::piped())
|
// Treat two percents in a row as an escaped percent.
|
||||||
.stderr(Stdio::piped());
|
escaped.push_str(&content[start..=idx]);
|
||||||
|
// Skip over both percents.
|
||||||
let formatting_future = async move {
|
start = idx + ('%'.len_utf8() * 2);
|
||||||
let mut process = process
|
} else {
|
||||||
.spawn()
|
// Otherwise interpret the percent as an expansion. Push up to (but not
|
||||||
.map_err(|e| FormatterError::SpawningFailed {
|
// including) the percent token.
|
||||||
command: fmt_cmd.to_string_lossy().into(),
|
escaped.push_str(&content[start..idx]);
|
||||||
error: e.kind(),
|
// Then parse the expansion,
|
||||||
})?;
|
let mut tokenizer = Tokenizer::new(&content[idx..], true);
|
||||||
|
let token = tokenizer
|
||||||
let mut stdin = process.stdin.take().ok_or(FormatterError::BrokenStdin)?;
|
.parse_percent_token()
|
||||||
let input_text = text.clone();
|
.unwrap()
|
||||||
let input_task = tokio::spawn(async move {
|
.map_err(|err| anyhow!("{err}"))?;
|
||||||
to_writer(&mut stdin, (encoding::UTF_8, false), &input_text).await
|
if matches!(token.kind, TokenKind::Expansion(ExpansionKind::Variable)) {
|
||||||
// Note that `stdin` is dropped here, causing the pipe to close. This can
|
let var = Variable::from_name(&token.content).ok_or_else(|| {
|
||||||
// avoid a deadlock with `wait_with_output` below if the process is waiting on
|
anyhow!("unknown variable '{}'", token.content)
|
||||||
// stdin to close before exiting.
|
})?;
|
||||||
});
|
if matches!(var, Variable::BufferName) {
|
||||||
let (input_result, output_result) = tokio::join! {
|
let expanded = if let Some(path) = self.relative_path() {
|
||||||
input_task,
|
Cow::Owned(path.to_string_lossy().into_owned())
|
||||||
process.wait_with_output(),
|
} else {
|
||||||
};
|
Cow::Borrowed(crate::document::SCRATCH_BUFFER_NAME)
|
||||||
let _ = input_result.map_err(|_| FormatterError::BrokenStdin)?;
|
};
|
||||||
let output = output_result.map_err(|_| FormatterError::WaitForOutputFailed)?;
|
escaped.push_str(expanded.as_ref());
|
||||||
|
} else {
|
||||||
if !output.status.success() {
|
bail!(
|
||||||
if !output.stderr.is_empty() {
|
"unexpected variable in format command '{}'",
|
||||||
let err = String::from_utf8_lossy(&output.stderr).to_string();
|
var.as_str()
|
||||||
log::error!("Formatter error: {}", err);
|
);
|
||||||
return Err(FormatterError::NonZeroExitStatus(Some(err)));
|
}
|
||||||
|
// and move forward to the end of the expansion.
|
||||||
|
start = idx + tokenizer.pos();
|
||||||
|
} else {
|
||||||
|
bail!("unexpected token in format command '{}'", token.content);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Err(FormatterError::NonZeroExitStatus(None));
|
if escaped.is_empty() {
|
||||||
} else if !output.stderr.is_empty() {
|
Ok(Cow::Borrowed(content))
|
||||||
log::debug!(
|
} else {
|
||||||
"Formatter printed to stderr: {}",
|
escaped.push_str(&content[start..]);
|
||||||
String::from_utf8_lossy(&output.stderr)
|
Ok(Cow::Owned(escaped))
|
||||||
);
|
}
|
||||||
|
})
|
||||||
|
.collect::<anyhow::Result<Vec<_>>>();
|
||||||
|
|
||||||
|
match fmt_args {
|
||||||
|
Ok(fmt_args) => {
|
||||||
|
process
|
||||||
|
.args(fmt_args.iter().map(Cow::as_ref))
|
||||||
|
.stdin(Stdio::piped())
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::piped());
|
||||||
|
|
||||||
|
let formatting_future = async move {
|
||||||
|
let mut process =
|
||||||
|
process
|
||||||
|
.spawn()
|
||||||
|
.map_err(|e| FormatterError::SpawningFailed {
|
||||||
|
command: fmt_cmd.to_string_lossy().into(),
|
||||||
|
error: e.kind(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut stdin = process.stdin.take().ok_or(FormatterError::BrokenStdin)?;
|
||||||
|
let input_text = text.clone();
|
||||||
|
let input_task = tokio::spawn(async move {
|
||||||
|
to_writer(&mut stdin, (encoding::UTF_8, false), &input_text).await
|
||||||
|
// Note that `stdin` is dropped here, causing the pipe to close. This can
|
||||||
|
// avoid a deadlock with `wait_with_output` below if the process is waiting on
|
||||||
|
// stdin to close before exiting.
|
||||||
|
});
|
||||||
|
let (input_result, output_result) = tokio::join! {
|
||||||
|
input_task,
|
||||||
|
process.wait_with_output(),
|
||||||
|
};
|
||||||
|
let _ = input_result.map_err(|_| FormatterError::BrokenStdin)?;
|
||||||
|
let output =
|
||||||
|
output_result.map_err(|_| FormatterError::WaitForOutputFailed)?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
if !output.stderr.is_empty() {
|
||||||
|
let err = String::from_utf8_lossy(&output.stderr).to_string();
|
||||||
|
log::error!("Formatter error: {}", err);
|
||||||
|
return Err(FormatterError::NonZeroExitStatus(Some(err)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Err(FormatterError::NonZeroExitStatus(None));
|
||||||
|
} else if !output.stderr.is_empty() {
|
||||||
|
log::debug!(
|
||||||
|
"Formatter printed to stderr: {}",
|
||||||
|
String::from_utf8_lossy(&output.stderr)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let str = std::str::from_utf8(&output.stdout)
|
||||||
|
.map_err(|_| FormatterError::InvalidUtf8Output)?;
|
||||||
|
|
||||||
|
Ok(helix_core::diff::compare_ropes(&text, &Rope::from(str)))
|
||||||
|
};
|
||||||
|
return Some(formatting_future.boxed());
|
||||||
}
|
}
|
||||||
|
Err(e) => log::error!("{e}"),
|
||||||
let str = std::str::from_utf8(&output.stdout)
|
}
|
||||||
.map_err(|_| FormatterError::InvalidUtf8Output)?;
|
|
||||||
|
|
||||||
Ok(helix_core::diff::compare_ropes(&text, &Rope::from(str)))
|
|
||||||
};
|
|
||||||
return Some(formatting_future.boxed());
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let text = self.text.clone();
|
let text = self.text.clone();
|
||||||
|
|
Loading…
Reference in New Issue