Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change default formatter for any language #2942

Merged
merged 21 commits into from
Aug 4, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
163d262
Change default formatter for any language
PiergiorgioZagaria Jul 2, 2022
9965cd4
Fix clippy error
PiergiorgioZagaria Jul 2, 2022
1af38a0
Close stdin for Stdio formatters
PiergiorgioZagaria Jul 2, 2022
29e24b6
Better indentation and pattern matching
PiergiorgioZagaria Jul 2, 2022
124dd5d
Return Result<Option<...>> for fn format instead of Option
PiergiorgioZagaria Jul 2, 2022
b5dc579
Remove unwrap for stdin
PiergiorgioZagaria Jul 2, 2022
2e70770
Handle FormatterErrors instead of Result<Option<...>>
PiergiorgioZagaria Jul 3, 2022
06ad531
Use Transaction instead of LspFormatting
PiergiorgioZagaria Jul 3, 2022
7416d6f
Use Transaction directly in Document::format
sudormrfbin Jul 3, 2022
b819c21
Perform stdin type formatting asynchronously
sudormrfbin Jul 4, 2022
48d69b2
Rename formatter.type values to kebab-case
sudormrfbin Jul 4, 2022
15685fe
Debug format for displaying io::ErrorKind (msrv fix)
sudormrfbin Jul 4, 2022
57bb4fc
Solve conflict?
PiergiorgioZagaria Jul 5, 2022
016e724
Merge branch 'master' into master
PiergiorgioZagaria Jul 6, 2022
f7ecf9c
Use only stdio type formatters
PiergiorgioZagaria Jul 8, 2022
acdce30
Remove FormatterType enum
PiergiorgioZagaria Jul 12, 2022
aeb4f8f
Remove old comment
PiergiorgioZagaria Jul 22, 2022
d711be0
Check if the formatter exited correctly
PiergiorgioZagaria Jul 23, 2022
7ec0288
Add formatter configuration to the book
PiergiorgioZagaria Jul 23, 2022
1ae13bb
Avoid allocations when writing to stdin and formatting errors
PiergiorgioZagaria Aug 2, 2022
fb93d1e
Remove unused import
PiergiorgioZagaria Aug 2, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions helix-core/src/syntax.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ pub struct LanguageConfiguration {
#[serde(default)]
pub auto_format: bool,

#[serde(skip_serializing_if = "Option::is_none")]
pub formatter: Option<FormatterConfiguration>,

#[serde(default)]
pub diagnostic_severity: Severity,

Expand Down Expand Up @@ -126,6 +129,22 @@ pub struct LanguageServerConfiguration {
pub language_id: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct FormatterConfiguration {
pub command: String,
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub args: Vec<String>,
pub formatter_type: FormatterType,
PiergiorgioZagaria marked this conversation as resolved.
Show resolved Hide resolved
}

#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Deserialize, Serialize)]
pub enum FormatterType {
PiergiorgioZagaria marked this conversation as resolved.
Show resolved Hide resolved
Stdio,
File,
}

#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct AdvancedCompletion {
Expand Down
16 changes: 10 additions & 6 deletions helix-term/src/commands/typed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ fn write_impl(
) -> anyhow::Result<()> {
let auto_format = cx.editor.config().auto_format;
let jobs = &mut cx.jobs;
let doc = doc_mut!(cx.editor);
let (view, doc) = current!(cx.editor);

if let Some(ref path) = path {
doc.set_path(Some(path.as_ref().as_ref()))
Expand All @@ -213,7 +213,7 @@ fn write_impl(
bail!("cannot write a buffer without a filename");
}
let fmt = if auto_format {
doc.auto_format().map(|fmt| {
doc.auto_format(view.id).map(|fmt| {
let shared = fmt.shared();
let callback = make_format_callback(
doc.id(),
Expand Down Expand Up @@ -270,8 +270,8 @@ fn format(
_args: &[Cow<str>],
_event: PromptEvent,
) -> anyhow::Result<()> {
let doc = doc!(cx.editor);
if let Some(format) = doc.format() {
let (view, doc) = current!(cx.editor);
if let Some(format) = doc.format(view.id) {
let callback =
make_format_callback(doc.id(), doc.version(), Modified::LeaveModified, format);
cx.jobs.callback(callback);
Expand Down Expand Up @@ -471,7 +471,11 @@ fn write_all_impl(
let auto_format = cx.editor.config().auto_format;
let jobs = &mut cx.jobs;
// save all documents
for doc in &mut cx.editor.documents.values_mut() {
// Saves only visible buffers? How do we reload the not visible ones?
PiergiorgioZagaria marked this conversation as resolved.
Show resolved Hide resolved
for (view, _) in cx.editor.tree.views() {
let id = view.doc;
let doc = cx.editor.documents.get_mut(&id).unwrap();

if doc.path().is_none() {
errors.push_str("cannot write a buffer without a filename\n");
continue;
Expand All @@ -482,7 +486,7 @@ fn write_all_impl(
}

let fmt = if auto_format {
doc.auto_format().map(|fmt| {
doc.auto_format(view.id).map(|fmt| {
let shared = fmt.shared();
let callback = make_format_callback(
doc.id(),
Expand Down
93 changes: 90 additions & 3 deletions helix-view/src/document.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use anyhow::{anyhow, bail, Context, Error};
use helix_core::auto_pairs::AutoPairs;
use helix_core::syntax::FormatterType;
use helix_core::Range;
use serde::de::{self, Deserialize, Deserializer};
use serde::Serialize;
Expand Down Expand Up @@ -397,17 +398,103 @@ impl Document {

/// The same as [`format`], but only returns formatting changes if auto-formatting
/// is configured.
pub fn auto_format(&self) -> Option<impl Future<Output = LspFormatting> + 'static> {
pub fn auto_format(
&mut self,
view_id: ViewId,
) -> Option<impl Future<Output = LspFormatting> + 'static> {
if self.language_config()?.auto_format {
self.format()
self.format(view_id)
} else {
None
}
}

/// If supported, returns the changes that should be applied to this document in order
/// to format it nicely.
pub fn format(&self) -> Option<impl Future<Output = LspFormatting> + 'static> {
pub fn format(
&mut self,
view_id: ViewId,
) -> Option<impl Future<Output = LspFormatting> + 'static> {
PiergiorgioZagaria marked this conversation as resolved.
Show resolved Hide resolved
PiergiorgioZagaria marked this conversation as resolved.
Show resolved Hide resolved
if let Some(language_config) = self.language_config() {
if let Some(formatter) = &language_config.formatter {
PiergiorgioZagaria marked this conversation as resolved.
Show resolved Hide resolved
use std::process::{Command, Stdio};
match formatter.formatter_type {
FormatterType::Stdio => {
use std::io::Write;
let mut process = match Command::new(&formatter.command)
.args(&formatter.args)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
{
Ok(process) => process,
Err(e) => {
log::error!("Failed to format with {}. {}", formatter.command, e);
return None;
}
};
PiergiorgioZagaria marked this conversation as resolved.
Show resolved Hide resolved

{
let mut stdin = process.stdin.take().unwrap();
// let mut text = self.text().bytes().collect::<Vec<u8>>();
// text.push(0);
stdin
.write_all(&self.text().bytes().collect::<Vec<u8>>())
.unwrap();
}

let output = process.wait_with_output().ok()?;

if !output.stderr.is_empty() {
log::error!("Formatter {}", String::from_utf8_lossy(&output.stderr));
}

let str = std::str::from_utf8(&output.stdout)
.map_err(|_| anyhow!("Process did not output valid UTF-8"))
PiergiorgioZagaria marked this conversation as resolved.
Show resolved Hide resolved
.ok()?;
self.apply(
&helix_core::diff::compare_ropes(self.text(), &Rope::from_str(str)),
view_id,
);
}
FormatterType::File => {
if let Some(path) = self.path() {
PiergiorgioZagaria marked this conversation as resolved.
Show resolved Hide resolved
let process = match Command::new(&formatter.command)
.args(&formatter.args)
.arg(path.to_str().unwrap_or(""))
.stderr(Stdio::piped())
.spawn()
{
Ok(process) => process,
Err(e) => {
log::error!(
"Failed to format with {}. {}",
formatter.command,
e
);
return None;
}
};

let output = process.wait_with_output().ok()?;

if !output.stderr.is_empty() {
log::error!(
"Formatter {}",
String::from_utf8_lossy(&output.stderr)
);
} else if let Err(e) = self.reload(view_id) {
log::error!("Error reloading after formatting {}", e);
}
} else {
log::error!("Cannot format file without filepath");
}
}
}
return None;
}
}
let language_server = self.language_server()?;
let text = self.text.clone();
let offset_encoding = language_server.offset_encoding();
Expand Down