@@ -1,4 +1,6 @@
11use anyhow::{anyhow, bail, Context, Error};
2+use futures_util::future::BoxFuture;
3+use futures_util::FutureExt;
24use helix_core::auto_pairs::AutoPairs;
35use helix_core::Range;
46use serde::de::{self, Deserialize, Deserializer};
@@ -20,7 +22,6 @@ use helix_core::{
2022 ChangeSet, Diagnostic, LineEnding, Rope, RopeBuilder, Selection, State, Syntax, Transaction,
2123 DEFAULT_LINE_ENDING,
2224};
23−use helix_lsp::util::LspFormatting;
2425
2526use crate::{DocumentId, Editor, ViewId};
2627
@@ -397,7 +398,7 @@ impl Document {
397398
398399 /// The same as [`format`], but only returns formatting changes if auto-formatting
399400 /// is configured.
400− pub fn auto_format(&self) -> Option<impl Future<Output = LspFormatting> + 'static> {
401+ pub fn auto_format(&self) -> Option<BoxFuture<'static, Result<Transaction, FormatterError>>> {
401402 if self.language_config()?.auto_format {
402403 self.format()
403404 } else {
@@ -407,7 +408,56 @@ impl Document {
407408
408409 /// If supported, returns the changes that should be applied to this document in order
409410 /// to format it nicely.
410− pub fn format(&self) -> Option<impl Future<Output = LspFormatting> + 'static> {
411+ // We can't use anyhow::Result here since the output of the future has to be
412+ // clonable to be used as shared future. So use a custom error type.
413+ pub fn format(&self) -> Option<BoxFuture<'static, Result<Transaction, FormatterError>>> {
414+ if let Some(formatter) = self.language_config().and_then(|c| c.formatter.clone()) {
415+ use std::process::Stdio;
416+ let text = self.text().clone();
417+ let mut process = tokio::process::Command::new(&formatter.command);
418+ process
419+ .args(&formatter.args)
420+ .stdin(Stdio::piped())
421+ .stdout(Stdio::piped())
422+ .stderr(Stdio::piped());
423+
424+ let formatting_future = async move {
425+ let mut process = process
426+ .spawn()
427+ .map_err(|e| FormatterError::SpawningFailed {
428+ command: formatter.command.clone(),
429+ error: e.kind(),
430+ })?;
431+ {
432+ let mut stdin = process.stdin.take().ok_or(FormatterError::BrokenStdin)?;
433+ to_writer(&mut stdin, encoding::UTF_8, &text)
434+ .await
435+ .map_err(|_| FormatterError::BrokenStdin)?;
436+ }
437+
438+ let output = process
439+ .wait_with_output()
440+ .await
441+ .map_err(|_| FormatterError::WaitForOutputFailed)?;
442+
443+ if !output.stderr.is_empty() {
444+ return Err(FormatterError::Stderr(
445+ String::from_utf8_lossy(&output.stderr).to_string(),
446+ ));
447+ }
448+
449+ if !output.status.success() {
450+ return Err(FormatterError::NonZeroExitStatus);
451+ }
452+
453+ let str = String::from_utf8(output.stdout)
454+ .map_err(|_| FormatterError::InvalidUtf8Output)?;
455+
456+ Ok(helix_core::diff::compare_ropes(&text, &Rope::from(str)))
457+ };
458+ return Some(formatting_future.boxed());
459+ };
460+
411461 let language_server = self.language_server()?;
412462 let text = self.text.clone();
413463 let offset_encoding = language_server.offset_encoding();
@@ -427,13 +477,13 @@ impl Document {
427477 log::warn!("LSP formatting failed: {}", e);
428478 Default::default()
429479 });
430− LspFormatting {
431− doc: text,
480+ Ok(helix_lsp::util::generate_transaction_from_edits(
481+ &text,
432482 edits,
433483 offset_encoding,
434− }
484+ ))
435485 };
436− Some(fut)
486+ Some(fut.boxed())
437487 }
438488
439489 pub fn save(&mut self, force: bool) -> impl Future<Output = Result<(), anyhow::Error>> {
@@ -442,7 +492,7 @@ impl Document {
442492
443493 pub fn format_and_save(
444494 &mut self,
445− formatting: Option<impl Future<Output = LspFormatting>>,
495+ formatting: Option<impl Future<Output = Result<Transaction, FormatterError>>>,
446496 force: bool,
447497 ) -> impl Future<Output = anyhow::Result<()>> {
448498 self.save_impl(formatting, force)
@@ -454,7 +504,7 @@ impl Document {
454504 /// at its `path()`.
455505 ///
456506 /// If `formatting` is present, it supplies some changes that we apply to the text before saving.
457− fn save_impl<F: Future<Output = LspFormatting>>(
507+ fn save_impl<F: Future<Output = Result<Transaction, FormatterError>>>(
458508 &mut self,
459509 formatting: Option<F>,
460510 force: bool,
@@ -488,7 +538,8 @@ impl Document {
488538 }
489539
490540 if let Some(fmt) = formatting {
491− let success = Transaction::from(fmt.await).changes().apply(&mut text);
541+ let transaction = fmt.await?;
542+ let success = transaction.changes().apply(&mut text);
492543 if !success {
493544 // This shouldn't happen, because the transaction changes were generated
494545 // from the same text we're saving.
@@ -1034,6 +1085,38 @@ impl Default for Document {
10341085 }
10351086}
10361087
1088+#[derive(Clone, Debug)]
1089+pub enum FormatterError {
1090+ SpawningFailed {
1091+ command: String,
1092+ error: std::io::ErrorKind,
1093+ },
1094+ BrokenStdin,
1095+ WaitForOutputFailed,
1096+ Stderr(String),
1097+ InvalidUtf8Output,
1098+ DiskReloadError(String),
1099+ NonZeroExitStatus,
1100+}
1101+
1102+impl std::error::Error for FormatterError {}
1103+
1104+impl Display for FormatterError {
1105+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1106+ match self {
1107+ Self::SpawningFailed { command, error } => {
1108+ write!(f, "Failed to spawn formatter {}: {:?}", command, error)
1109+ }
1110+ Self::BrokenStdin => write!(f, "Could not write to formatter stdin"),
1111+ Self::WaitForOutputFailed => write!(f, "Waiting for formatter output failed"),
1112+ Self::Stderr(output) => write!(f, "Formatter error: {}", output),
1113+ Self::InvalidUtf8Output => write!(f, "Invalid UTF-8 formatter output"),
1114+ Self::DiskReloadError(error) => write!(f, "Error reloading file from disk: {}", error),
1115+ Self::NonZeroExitStatus => write!(f, "Formatter exited with non zero exit status:"),
1116+ }
1117+ }
1118+}
1119+
10371120#[cfg(test)]
10381121mod test {
10391122 use super::*;