From 219d2c25156a496ed2923d4cef256352bb1302e5 Mon Sep 17 00:00:00 2001 From: PiergiorgioZagaria Date: Thu, 4 Aug 2022 06:01:48 +0200 Subject: Change default formatter for any language (#2942) * Change default formatter for any language * Fix clippy error * Close stdin for Stdio formatters * Better indentation and pattern matching * Return Result> for fn format instead of Option * Remove unwrap for stdin * Handle FormatterErrors instead of Result> * Use Transaction instead of LspFormatting * Use Transaction directly in Document::format * Perform stdin type formatting asynchronously * Rename formatter.type values to kebab-case * Debug format for displaying io::ErrorKind (msrv fix) * Solve conflict? * Use only stdio type formatters * Remove FormatterType enum * Remove old comment * Check if the formatter exited correctly * Add formatter configuration to the book * Avoid allocations when writing to stdin and formatting errors * Remove unused import Co-authored-by: Gokul Soumya --- helix-view/src/document.rs | 103 ++++++++++++++++++++++++++++++++++++++++----- helix-view/src/editor.rs | 3 +- 2 files changed, 94 insertions(+), 12 deletions(-) (limited to 'helix-view/src') diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 50c70af6..ab64f485 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -1,4 +1,6 @@ use anyhow::{anyhow, bail, Context, Error}; +use futures_util::future::BoxFuture; +use futures_util::FutureExt; use helix_core::auto_pairs::AutoPairs; use helix_core::Range; use serde::de::{self, Deserialize, Deserializer}; @@ -20,7 +22,6 @@ use helix_core::{ ChangeSet, Diagnostic, LineEnding, Rope, RopeBuilder, Selection, State, Syntax, Transaction, DEFAULT_LINE_ENDING, }; -use helix_lsp::util::LspFormatting; use crate::{DocumentId, Editor, ViewId}; @@ -397,7 +398,7 @@ impl Document { /// The same as [`format`], but only returns formatting changes if auto-formatting /// is configured. - pub fn auto_format(&self) -> Option + 'static> { + pub fn auto_format(&self) -> Option>> { if self.language_config()?.auto_format { self.format() } else { @@ -407,7 +408,56 @@ impl Document { /// If supported, returns the changes that should be applied to this document in order /// to format it nicely. - pub fn format(&self) -> Option + 'static> { + // We can't use anyhow::Result here since the output of the future has to be + // clonable to be used as shared future. So use a custom error type. + pub fn format(&self) -> Option>> { + if let Some(formatter) = self.language_config().and_then(|c| c.formatter.clone()) { + use std::process::Stdio; + let text = self.text().clone(); + let mut process = tokio::process::Command::new(&formatter.command); + process + .args(&formatter.args) + .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: formatter.command.clone(), + error: e.kind(), + })?; + { + let mut stdin = process.stdin.take().ok_or(FormatterError::BrokenStdin)?; + to_writer(&mut stdin, encoding::UTF_8, &text) + .await + .map_err(|_| FormatterError::BrokenStdin)?; + } + + let output = process + .wait_with_output() + .await + .map_err(|_| FormatterError::WaitForOutputFailed)?; + + if !output.stderr.is_empty() { + return Err(FormatterError::Stderr( + String::from_utf8_lossy(&output.stderr).to_string(), + )); + } + + if !output.status.success() { + return Err(FormatterError::NonZeroExitStatus); + } + + let str = String::from_utf8(output.stdout) + .map_err(|_| FormatterError::InvalidUtf8Output)?; + + Ok(helix_core::diff::compare_ropes(&text, &Rope::from(str))) + }; + return Some(formatting_future.boxed()); + }; + let language_server = self.language_server()?; let text = self.text.clone(); let offset_encoding = language_server.offset_encoding(); @@ -427,13 +477,13 @@ impl Document { log::warn!("LSP formatting failed: {}", e); Default::default() }); - LspFormatting { - doc: text, + Ok(helix_lsp::util::generate_transaction_from_edits( + &text, edits, offset_encoding, - } + )) }; - Some(fut) + Some(fut.boxed()) } pub fn save(&mut self, force: bool) -> impl Future> { @@ -442,7 +492,7 @@ impl Document { pub fn format_and_save( &mut self, - formatting: Option>, + formatting: Option>>, force: bool, ) -> impl Future> { self.save_impl(formatting, force) @@ -454,7 +504,7 @@ impl Document { /// at its `path()`. /// /// If `formatting` is present, it supplies some changes that we apply to the text before saving. - fn save_impl>( + fn save_impl>>( &mut self, formatting: Option, force: bool, @@ -488,7 +538,8 @@ impl Document { } if let Some(fmt) = formatting { - let success = Transaction::from(fmt.await).changes().apply(&mut text); + let transaction = fmt.await?; + let success = transaction.changes().apply(&mut text); if !success { // This shouldn't happen, because the transaction changes were generated // from the same text we're saving. @@ -1034,6 +1085,38 @@ impl Default for Document { } } +#[derive(Clone, Debug)] +pub enum FormatterError { + SpawningFailed { + command: String, + error: std::io::ErrorKind, + }, + BrokenStdin, + WaitForOutputFailed, + Stderr(String), + InvalidUtf8Output, + DiskReloadError(String), + NonZeroExitStatus, +} + +impl std::error::Error for FormatterError {} + +impl Display for FormatterError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::SpawningFailed { command, error } => { + write!(f, "Failed to spawn formatter {}: {:?}", command, error) + } + Self::BrokenStdin => write!(f, "Could not write to formatter stdin"), + Self::WaitForOutputFailed => write!(f, "Waiting for formatter output failed"), + Self::Stderr(output) => write!(f, "Formatter error: {}", output), + Self::InvalidUtf8Output => write!(f, "Invalid UTF-8 formatter output"), + Self::DiskReloadError(error) => write!(f, "Error reloading file from disk: {}", error), + Self::NonZeroExitStatus => write!(f, "Formatter exited with non zero exit status:"), + } + } +} + #[cfg(test)] mod test { use super::*; diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 501c3069..1e7f508c 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -613,7 +613,6 @@ impl Editor { syn_loader: Arc, config: Box>, ) -> Self { - let language_servers = helix_lsp::Registry::new(); let conf = config.load(); let auto_pairs = (&conf.auto_pairs).into(); @@ -629,7 +628,7 @@ impl Editor { macro_recording: None, macro_replaying: Vec::new(), theme: theme_loader.default(), - language_servers, + language_servers: helix_lsp::Registry::new(), diagnostics: BTreeMap::new(), debugger: None, debugger_events: SelectAll::new(), -- cgit v1.2.3-70-g09d2