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

Change default formatter for any language #2942

merged 21 commits into from
Aug 4, 2022

Conversation

PiergiorgioZagaria
Copy link
Contributor

I have never worked on a big project in rust, so I don't know if the Result/Option handling is ok.

This PR aims to allow the user to choose which formatter to use instead of always relying on lsp. Discussion about this in issue #2362.

Currently the only problem is that write_all_impl saves only the viewed buffers, this is because the formatter needs to reload or apply the transaction to the document. How do we get around this?

@PiergiorgioZagaria
Copy link
Contributor Author

Another bug I found using zig fmt --stdin, the editor hangs because the formatter doesn't understand that stdin has wrote everything. I tried pushing a 0 to the collected vector and flushing stdin, but neither seems to work.

The expected usage of zig fmt --stdin is: $ cat sometext.zig | zig fmt --stdin

@archseer
Copy link
Member

archseer commented Jul 2, 2022

You probably need to close stdin when you're done.

@PiergiorgioZagaria
Copy link
Contributor Author

You probably need to close stdin when you're done.

Yeah that was it, thanks

helix-view/src/document.rs Outdated Show resolved Hide resolved
helix-core/src/syntax.rs Outdated Show resolved Hide resolved
helix-view/src/document.rs Outdated Show resolved Hide resolved
helix-view/src/document.rs Outdated Show resolved Hide resolved
helix-view/src/document.rs Outdated Show resolved Hide resolved
helix-view/src/document.rs Outdated Show resolved Hide resolved
@PiergiorgioZagaria
Copy link
Contributor Author

Thanks for the style guides btw.

@PiergiorgioZagaria
Copy link
Contributor Author

I found another bug. Basically, when using Stdio as the formatter type, when saving, the document is formatted and saved, but the editor shows that the file is still unsaved

helix-view/src/document.rs Outdated Show resolved Hide resolved
helix-view/src/document.rs Outdated Show resolved Hide resolved
@PiergiorgioZagaria
Copy link
Contributor Author

I don't know anything about async, ideas on how to make this work?

// let fut = async move {
//     helix_core::diff::compare_ropes(self.text(), &Rope::from_str(str))
// }
// return Ok(fut);

line 458 of document.rs.
(Btw how do I put the reference to the code in the comment?)

@sudormrfbin
Copy link
Member

I don't know anything about async, ideas on how to make this work?

// let fut = async move {
//     helix_core::diff::compare_ropes(self.text(), &Rope::from_str(str))
// }
// return Ok(fut);

line 458 of document.rs.

Document::format() originally returned an Option<impl Future<...>> which used the impl Trait syntax in the return type of the function. This means that the function will return some type implementing Future, but it also enforces that we can only really return a single concrete type (see this SO post about using impl Trait syntax for returning multiple types).

Now, the actual problem is that different async blocks generate different types that implement Future and therefore we can't use impl Future in our return type. We can use a trait object to solve this, and a BoxFuture is simply a type alias for Pin<Box<dyn Future<Output = ...>>> provided by the futures_util crate (which is what I ended up using).

(Btw how do I put the reference to the code in the comment?)

https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-a-permanent-link-to-a-code-snippet

@PiergiorgioZagaria
Copy link
Contributor Author

Thanks for all the help and the explanation.

I guess the only problem now is the fact that write_all only writes to viewed buffers instead of all buffers, because we need the view for reloading the document if we are using the file formatter.

Maybe we could remove the file formatter type entirely? It depends on whether there are other known formatters that only work like that.

@PiergiorgioZagaria
Copy link
Contributor Author

Since I haven't found any formatter that doesn't support the stdio type, I suggest removing formatter type completely and use only stdio.
If there is a need for a file formatter type it should be implemented in a separate PR.

helix-core/src/syntax.rs Outdated Show resolved Hide resolved
@samholmes
Copy link

This PR should include changes to documentation?

@PiergiorgioZagaria
Copy link
Contributor Author

Probably we should mention it in the languages configuration section

@samholmes
Copy link

Can we access the current file name for the formatter config? This is needed for eslint:

formatter = { command = "eslint", args = ["--fix", "$HELIX_FILE"] }

Is there something like this?

@PiergiorgioZagaria
Copy link
Contributor Author

Helix sends the content of the file through stdin to the formatter. I think eslint has a --stdin flag that should work

@kirawi
Copy link
Member

kirawi commented Jul 22, 2022

Probably we should mention it in the languages configuration section

You could add it here: https://docs.helix-editor.com/guides/adding_languages.html

@samholmes
Copy link

The problem with eslint is that it wont return the file if it's able to fix. Instead, it returns a formatted response possibly containing the file output. For example, specifying the json format:

/usr/local/bin/eslintfmt

#!/bin/zsh

eslint --stdin --fix-dry-run --format=json | jq '.[0].output' <&0

This is close, except if the output in the JSON is not define because there was an error, then the file is emptied of contents. This script would need to parse the output and return the formatted output if there are no issues, otherwise return the stdin. I'm guessing this has already been written somewhere.

helix-view/src/document.rs Outdated Show resolved Hide resolved
helix-view/src/document.rs Outdated Show resolved Hide resolved
@archseer
Copy link
Member

archseer commented Aug 4, 2022

Let's tackle the write_all problem in a separate PR, the fix there should be to just pick any view_id that already has a selection for that document. I had to do that somewhere else in commands.rs if I remember correctly

Copy link
Member

@archseer archseer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work on this!

@archseer archseer merged commit 219d2c2 into helix-editor:master Aug 4, 2022
@PiergiorgioZagaria
Copy link
Contributor Author

I think I forgot to mention it, but when we removed the file formatter in favour of just using stdio formatting, the problem with write_all was also solved. That is because with the file formatter we needed the view_id to reload the window, but now that we just use transactions the problem doesn't exist

@David-Else
Copy link
Contributor

Brilliant! Could anyone provide an example for prettier to format markdown files?

@PiergiorgioZagaria
Copy link
Contributor Author

I don't know the cli flags for prettier, assuming that the default behaviour is that it takes from stdin and prints to stdout, so for example you would call it like
cat file.md | prettier. Then you would just need to put

[[language]]
name = "markdown"
formatter = { command = "prettier" }

In your languages.toml

@David-Else
Copy link
Contributor

David-Else commented Aug 4, 2022

Cheers! I tested it with cat file.md | It should be:

[[language]]
name = "markdown"
formatter = { command = "prettier --parser markdown" }

Do I need any escape charcters for the command? In Neovim it was --parser\ markdown. Also, if there is an error does it abort? In Neovim the buffer just became the error message.

@PiergiorgioZagaria
Copy link
Contributor Author

Then it's
formatter = { command = "prettier", args = ["--parser", "markdown"] }

@kirawi
Copy link
Member

kirawi commented Aug 4, 2022

Also, if there is an error does it abort? In Neovim the buffer just became the error message.

It'll return an error:

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);
}

@dam5h
Copy link

dam5h commented Aug 5, 2022

@PiergiorgioZagaria , thank you for this! It's great to be able to use black as a python formater but pyright for LSP. pyright doesn't support formatting.

One question is related to the auto-format option in the editor config, as the default is true, shouldn't this happen automatically on saving a file, or was that circumvented when we gave this formatter priority over lsp?

EDIT: Just realized there is more local auto-format setting in the languages config. Even though editor had true, python had false. So all is good, thanks again!

.await
.map_err(|_| FormatterError::WaitForOutputFailed)?;

if !output.stderr.is_empty() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stderr doesn't always mean there's an error. There are programs that use it to print diagnostic messages so that e.g. you can still pipe stdout to a file and see informational messages in the output. The only indication of error we can really use reliably is the exit code. What we should probably do is move this whole if expression inside the following one that checks the exit code. The enum variants can probably change the enum to get rid of Stderr and add an Option<String> to NonZeroExitStatus.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I'll open a new PR to fix it.
Btw the function shell_impl in helix-term/src/commands.rs, which is used when calling the shell with ! Or | doesn't implement these checks, should we add them there too?

Copy link
Member

@dead10ck dead10ck Aug 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, in that case, yeah, those need to be fixed too. The presence of output on stderr does not necessarily mean there was an error.

return Err(FormatterError::NonZeroExitStatus);
}

let str = String::from_utf8(output.stdout)
Copy link
Member

@kirawi kirawi Aug 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use from_reader here instead. I think it is more efficient. Or str::from_utf8.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

std::str::from_utf8 works, but str::from_utf8 doesn't. Btw for further fixes to this part you can comment on #3349

@theherk
Copy link

theherk commented Aug 8, 2022

It's great to be able to use black as a python formater but pyright for LSP. pyright doesn't support formatting.

I case anybody stumbles in here searching for how to configure black, this is the configuration that does the trick.

[[language]]
name = "python"
auto-format = true
formatter = { command = "black", args = ["-", "-q"] }

thomasskk pushed a commit to thomasskk/helix that referenced this pull request Sep 9, 2022
* Change default formatter for any language

* Fix clippy error

* Close stdin for Stdio formatters

* Better indentation and pattern matching

* Return Result<Option<...>> for fn format instead of Option

* Remove unwrap for stdin

* Handle FormatterErrors instead of Result<Option<...>>

* 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 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Change default formatter for any language
10 participants