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

Commit 6bda4d5

Browse files
authored
Merge pull request #990 from Xanewok/external-rustfmt
Support using external Rustfmt
2 parents f92b4e8 + fe62a7c commit 6bda4d5

File tree

7 files changed

+286
-158
lines changed

7 files changed

+286
-158
lines changed

Cargo.lock

Lines changed: 51 additions & 46 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,15 @@ languageserver-types = "0.45"
2424
lazy_static = "1"
2525
log = "0.4"
2626
num_cpus = "1"
27-
racer = { version = "2.1.4", default-features = false }
27+
racer = { version = "2.1.5", default-features = false }
2828
rayon = "1"
2929
rls-analysis = "0.16"
3030
rls-blacklist = "0.1.2"
3131
rls-data = { version = "0.18", features = ["serialize-serde"] }
3232
rls-rustc = "0.5.0"
3333
rls-span = { version = "0.4", features = ["serialize-serde"] }
3434
rls-vfs = "0.4.6"
35-
rustfmt-nightly = "0.99.2"
35+
rustfmt-nightly = "0.99.4"
3636
serde = "1.0"
3737
serde_json = "1.0"
3838
serde_derive = "1.0"

src/actions/format.rs

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
//! Code formatting using Rustfmt - by default using statically-linked one or
12+
//! possibly running Rustfmt binary specified by the user.
13+
14+
use std::env::temp_dir;
15+
use std::fs::File;
16+
use std::io::Write;
17+
use std::path::{Path, PathBuf};
18+
use std::process::{Command, Stdio};
19+
20+
use rand::{Rng, thread_rng};
21+
use log::{log, debug};
22+
use rustfmt_nightly::{Config, Session, Input};
23+
use serde_json;
24+
25+
/// Specified which `rustfmt` to use.
26+
#[derive(Clone)]
27+
pub enum Rustfmt {
28+
/// (Path to external `rustfmt`, cwd where it should be spawned at)
29+
External(PathBuf, PathBuf),
30+
/// Statically linked `rustfmt`
31+
Internal
32+
}
33+
34+
impl From<Option<(String, PathBuf)>> for Rustfmt {
35+
fn from(value: Option<(String, PathBuf)>) -> Rustfmt {
36+
match value {
37+
Some((path, cwd)) => Rustfmt::External(PathBuf::from(path), cwd),
38+
None => Rustfmt::Internal
39+
}
40+
}
41+
}
42+
43+
impl Rustfmt {
44+
pub fn format(&self, input: String, cfg: Config) -> Result<String, String> {
45+
match self {
46+
Rustfmt::Internal => format_internal(input, cfg),
47+
Rustfmt::External(path, cwd) => format_external(path, cwd, input, cfg),
48+
}
49+
}
50+
}
51+
52+
fn format_external(path: &PathBuf, cwd: &PathBuf, input: String, cfg: Config) -> Result<String, String> {
53+
let (_file_handle, config_path) = gen_config_file(&cfg)?;
54+
let args = rustfmt_args(&cfg, &config_path);
55+
56+
let mut rustfmt = Command::new(path)
57+
.args(args)
58+
.current_dir(cwd)
59+
.stdin(Stdio::piped())
60+
.stdout(Stdio::piped())
61+
.spawn()
62+
.map_err(|_| format!("Couldn't spawn `{}`", path.display()))?;
63+
64+
{
65+
let stdin = rustfmt.stdin.as_mut()
66+
.ok_or_else(|| "Failed to open rustfmt stdin".to_string())?;
67+
stdin.write_all(input.as_bytes())
68+
.map_err(|_| "Failed to pass input to rustfmt".to_string())?;
69+
}
70+
71+
rustfmt.wait_with_output()
72+
.map_err(|err| format!("Error running rustfmt: {}", err))
73+
.and_then(|out| String::from_utf8(out.stdout)
74+
.map_err(|_| "Formatted code is not valid UTF-8".to_string()))
75+
}
76+
77+
fn format_internal(input: String, config:Config) -> Result<String, String> {
78+
let mut buf = Vec::<u8>::new();
79+
80+
{
81+
let mut session = Session::new(config, Some(&mut buf));
82+
83+
match session.format(Input::Text(input)) {
84+
Ok(report) => {
85+
// Session::format returns Ok even if there are any errors, i.e., parsing errors.
86+
if session.has_operational_errors() || session.has_parsing_errors() {
87+
debug!(
88+
"reformat: format_input failed: has errors, report = {}",
89+
report
90+
);
91+
92+
return Err("Reformat failed to complete successfully".into());
93+
}
94+
}
95+
Err(e) => {
96+
debug!("Reformat failed: {:?}", e);
97+
98+
return Err("Reformat failed to complete successfully".into());
99+
}
100+
}
101+
}
102+
103+
String::from_utf8(buf)
104+
.map_err(|_| "Reformat output is not a valid UTF-8".into())
105+
}
106+
107+
fn random_file() -> Result<(File, PathBuf), String> {
108+
const SUFFIX_LEN: usize = 10;
109+
110+
let suffix: String = thread_rng().gen_ascii_chars().take(SUFFIX_LEN).collect();
111+
let path = temp_dir().join(suffix);
112+
113+
Ok(File::create(&path)
114+
.map(|file| (file, path))
115+
.map_err(|_| "Config file could not be created".to_string())?)
116+
}
117+
118+
fn gen_config_file(config: &Config) -> Result<(File, PathBuf), String> {
119+
let (mut file, path) = random_file()?;
120+
let toml = config.all_options().to_toml()?;
121+
file.write(toml.as_bytes())
122+
.map_err(|_| "Could not write config TOML file contents".to_string())?;
123+
124+
Ok((file, path))
125+
}
126+
127+
fn rustfmt_args(config: &Config, config_path: &Path) -> Vec<String> {
128+
let mut args = vec![
129+
"--unstable-features".into(),
130+
"--skip-children".into(),
131+
"--emit".into(),
132+
"stdout".into(),
133+
"--quiet".into(),
134+
];
135+
136+
args.push("--file-lines".into());
137+
let file_lines_json = config.file_lines().to_json_spans();
138+
let lines: String = serde_json::to_string(&file_lines_json).unwrap();
139+
args.push(lines);
140+
141+
args.push("--config-path".into());
142+
args.push(config_path.to_str().map(|x| x.to_string()).unwrap());
143+
144+
args
145+
}

0 commit comments

Comments
 (0)