Skip to content
This repository has been archived by the owner on Dec 29, 2022. It is now read-only.

Support using external Rustfmt #990

Merged
merged 7 commits into from
Aug 28, 2018
Merged

Conversation

Xanewok
Copy link
Member

@Xanewok Xanewok commented Aug 14, 2018

Fixes #812. (Uses https://github.com/Xanewok/rustfmt/tree/file-lines-ser-json)

High-level overview:

  • Introduces trait Formatter { fn format(&self, String, rustfmt::Config) -> Result<String, String>; }
  • Introduces a Rustfmt enum as a convenient interface which implements the trait, accessible via ctx.formatter()
  • Rustfmt::Internal variant implementation is what was currently used, running included Rustfmt in-process
  • Rustfmt::External spawns the provided command, using the Rustfmt stdin/stdout mode.

Assuming that Rust input should be a valid UTF-8 and so Rustfmt should also reformat it to a valid UTF-8, returning Err if that's not the case.

This is not entirely correct yet, since we should pass full Config in a file to executed Rustfmt, since LSP may request additional formatting options and this currently just uses default Rustfmt configuration either via rustfmt.toml or by using default, but with some RLS-specific options.

Have to finish this one bit, but in general this should be ready for a review!

@Xanewok Xanewok changed the title External rustfmt Support using external Rustfmt Aug 14, 2018
fn gen_config_file(config: &Config) -> Result<(File, PathBuf), String> {
let (mut file, path) = random_file()?;
let toml = config.used_options().to_toml()?;
let toml = config.all_options().to_toml()?;
Copy link
Member Author

Choose a reason for hiding this comment

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

Well, that was easier than expected!

@Xanewok
Copy link
Member Author

Xanewok commented Aug 14, 2018

The drawback to using the external Rustfmt is that we have to pass the entire config via file, so here it's done by creating a temporary file.
However, it's worth noting that we also format hover messages with Rustfmt, which means that for every such invocation we shell out to Rustfmt, create the config file, dump all options etc.

It might be a good idea to just use the statically linked Rustfmt for the tooltips, since most of the time there's almost null work to be done there and any minor difference in indenting might be worth avoiding the overhead and creating these temp files all the time.

@jsgf
Copy link

jsgf commented Aug 14, 2018

cc @sunshowers

@jsgf
Copy link

jsgf commented Aug 14, 2018

It might be a good idea to just use the statically linked Rustfmt for the tooltips, since most of the time there's almost null work to be done there and any minor difference in indenting might be worth avoiding the overhead and creating these temp files all the time.

I think the specific formatting only matters if its persisted in a file. I agree the precise formatting of transient output is less important.

@sunshowers
Copy link
Contributor

sunshowers commented Aug 14, 2018

Thanks, this is definitely the direction I was envisioning.

This is not entirely correct yet, since we should pass full Config in a file to executed Rustfmt, since LSP may request additional formatting options and this currently just uses default Rustfmt configuration either via rustfmt.toml or by using default, but with some RLS-specific options.

What sorts of configs could the LSP pass in here? To some extent, using an external formatter means that you want that to determine what to do. In particular, for the Facebook use case we want a single consistent format result controlled entirely by rustfmt.toml and independent of whatever each user's LSP config.

Could potential version skew be a problem wrt formatter configs? e.g. RLS is newer and passes in a config that an older rustfmt doesn't understand.

@Xanewok
Copy link
Member Author

Xanewok commented Aug 16, 2018

What sorts of configs could the LSP pass in here? To some extent, using an external formatter means that you want that to determine what to do. In particular, for the Facebook use case we want a single consistent format result controlled entirely by rustfmt.toml and independent of whatever each user's LSP config.

Agreed, however the LSP provides FormattingOptions which specifies at least tabSize and insertSpaces, so we try to at least respect that, regardless of the formatting options specified. It'd might be worthwile to change that on the LSP side of things to let the server/formatter figure out the default values itself.

Could potential version skew be a problem wrt formatter configs? e.g. RLS is newer and passes in a config that an older rustfmt doesn't understand.

That's technically possible now, since we use the statically linked Rustfmt config/capabilities and encode it for the external Rustfmt to use.
It seems that we only care about the in-process config when we set the LSP formatting options (indent, tabs/spaces) but in other places where we do we'll prefer to use the internal Rustfmt anyway, so it might be actually possible to omit the config and just rely to the default one (e.g. in rustfmt.toml etc.)

@Xanewok Xanewok force-pushed the external-rustfmt branch 3 times, most recently from 895488d to 454d013 Compare August 20, 2018 14:30
@nrc
Copy link
Member

nrc commented Aug 24, 2018

The drawback to using the external Rustfmt is that we have to pass the entire config via file, so here it's done by creating a temporary file.

We should add some CLI API to Rustfmt to get around this, e.g., passing a top-level config as an env var

@nrc
Copy link
Member

nrc commented Aug 24, 2018

Could potential version skew be a problem wrt formatter configs? e.g. RLS is newer and passes in a config that an older rustfmt doesn't understand.

We should only pass stable options to external Rustfmt, since we can't promise that it was built using nightly and therefore might not accept unstable options. Given that, we should only have a problem if we pass new args to an old rustfmt, but I think we should be very conservative about what we pass, so should be OK.

Copy link
Member

@nrc nrc left a comment

Choose a reason for hiding this comment

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

Code looks good. Please remember to add the new option to the VSCode extension too.

Cargo.toml Outdated
@@ -12,6 +12,9 @@ categories = ["development-tools"]

build = "build.rs"

[patch.crates-io]
rustfmt-nightly = { git = "https://github.com/Xanewok/rustfmt", branch = "file-lines-ser-json" }
Copy link
Member

Choose a reason for hiding this comment

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

Can we remove this now?

}


pub trait Formatter {
Copy link
Member

Choose a reason for hiding this comment

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

This trait seems like over-abstraction. Since we have the enum why not just use an inherent function on the enum and call different functions. Alternatively, keep the trait but dump the enum, since they are basically doing the same thing.

Copy link
Member Author

Choose a reason for hiding this comment

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

It somehow felt cleaner doing this this way than writing two bare format_internal(String, Config) -> .. and format_external(PathBuf, PathBuf, String, Config) -> ... to be called from inherent function but you're right that there's no benefit apart from being able to implement the same function signature for different variants; I'll change that.

@nrc
Copy link
Member

nrc commented Aug 24, 2018

Have to finish this one bit, but in general this should be ready for a review!

Looks like we're still waiting on this? Should I land this and follow up or do you want to wait?

@Xanewok
Copy link
Member Author

Xanewok commented Aug 24, 2018

When trying to use rustfmt 0.99.3 instead of the branch:

error: found removed `do catch` syntax                 
    --> /home/xanewok/.cargo/registry/src/github.com-1ecc6299db9ec823/rustc-ap-syntax-230.0.0/parse/parser.rs:1761:43
     |                                                                    
1761 |             let pat_arg: PResult<'a, _> = do catch {
     |                                           ^^
     |                                                              
     = help: Following RFC #2388, the new non-placeholder syntax is `try`
                                                                  
error: aborting due to previous error            
                                                    
error: Could not compile `rustc-ap-syntax`.  

@nrc I believe we need the rustc-ap-*-235 from rust-lang/rustfmt@04d804c (0.99.4? 😢 ) now

@Xanewok
Copy link
Member Author

Xanewok commented Aug 24, 2018

Wait, no, this error happens because of latest rustc
rustc 1.30.0-nightly (63d66494a 2018-08-23)
both here and Rustfmt master

EDIT: Still blocked on rust-lang/rustfmt#2965 (comment)

FIXME: It would be clean to support formatting options passed via
Formatter:format() trait method rather than relying on it to 'just'
work for now.
External ones have to create temporary files to pass configs and
whatnot and we can technically issue a lot of these hover requests fast
- these will be short and the exact formatting does not matter, so
we'll just use the internal one instead.
Also bump Racer to use single version of rustc-ap-* libs (v370)
@Xanewok
Copy link
Member Author

Xanewok commented Aug 27, 2018

@nrc this should be ready to review now

Xanewok added a commit to Xanewok/rls-vscode that referenced this pull request Aug 27, 2018
.map_err(|_| "Config file could not be created".to_string())?)
}

fn gen_config_file(config: &Config) -> Result<(File, PathBuf), String> {
Copy link
Member

Choose a reason for hiding this comment

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

Could you open an issue for passing a config file as an env var please? And maybe stick a FIXME in here.

@nrc nrc merged commit 6bda4d5 into rust-lang:master Aug 28, 2018
@nrc
Copy link
Member

nrc commented Aug 28, 2018

Thanks, looks good!

bors added a commit to rust-lang/rust that referenced this pull request Sep 4, 2018
Update RLS and Rustfmt

RLS
* Allow project model to download crates ([#1020](rust-lang/rls#1020))
* Support simple external builds ([#988](rust-lang/rls#988))
* Support using external Rustfmt ([#990](rust-lang/rls#990))

Rustfmt (0.99.4)
* Format chains with comment ([#2899](https://github.com/rust-lang-nursery/rls/pull/2899))
* Do not show wildcard pattern in slice pattern ([#2912](https://github.com/rust-lang-nursery/rls/pull/2912))
* Impl only use ([#2951](https://github.com/rust-lang-nursery/rls/pull/2951))
* ... and [more](rust-lang/rustfmt@5c9a2b6...1c40881)

Bumped in tandem to pull a single version of `rustc-ap-*` libs.

r? @nrc
fj pushed a commit to fj/rls-vscode that referenced this pull request Feb 17, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants