358: Add support for formatting entire document with rustfmt r=matklad a=aleksanb

Attempting to format a document when rustfmt isn't installed will result
in an error being returned to the frontend. An alternative
implementation would be returning zero replacements.

Part of https://github.com/rust-analyzer/rust-analyzer/issues/160.

Co-authored-by: Aleksander Vognild Burkow <aleksanderburkow@gmail.com>
This commit is contained in:
bors[bot] 2018-12-30 10:16:22 +00:00
commit 8d1df9834c
11 changed files with 107 additions and 4 deletions

1
.gitignore vendored
View file

@ -4,3 +4,4 @@ crates/*/target
.idea/*
.vscode/*
*.log
*.iml

1
Cargo.lock generated
View file

@ -790,6 +790,7 @@ dependencies = [
"text_unit 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"thread_worker 0.1.0",
"threadpool 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"tools 0.1.0",
"url_serde 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"walkdir 2.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
]

View file

@ -140,6 +140,9 @@ impl fmt::Debug for AnalysisImpl {
}
impl AnalysisImpl {
pub fn file_text(&self, file_id: FileId) -> Arc<String> {
self.db.file_text(file_id)
}
pub fn file_syntax(&self, file_id: FileId) -> SourceFileNode {
self.db.source_file(file_id)
}

View file

@ -274,6 +274,9 @@ pub struct Analysis {
}
impl Analysis {
pub fn file_text(&self, file_id: FileId) -> Arc<String> {
self.imp.file_text(file_id)
}
pub fn file_syntax(&self, file_id: FileId) -> SourceFileNode {
self.imp.file_syntax(file_id).clone()
}

View file

@ -33,6 +33,7 @@ ra_text_edit = { path = "../ra_text_edit" }
ra_analysis = { path = "../ra_analysis" }
gen_lsp_server = { path = "../gen_lsp_server" }
ra_vfs = { path = "../ra_vfs" }
tools = { path = "../tools" }
[dev-dependencies]
tempdir = "0.3.7"

View file

@ -33,7 +33,7 @@ pub fn server_capabilities() -> ServerCapabilities {
workspace_symbol_provider: Some(true),
code_action_provider: Some(CodeActionProviderCapability::Simple(true)),
code_lens_provider: None,
document_formatting_provider: None,
document_formatting_provider: Some(true),
document_range_formatting_provider: None,
document_on_type_formatting_provider: Some(DocumentOnTypeFormattingOptions {
first_trigger_character: "=".to_string(),

View file

@ -295,6 +295,7 @@ fn on_request(
.on::<req::PrepareRenameRequest>(handlers::handle_prepare_rename)?
.on::<req::Rename>(handlers::handle_rename)?
.on::<req::References>(handlers::handle_references)?
.on::<req::Formatting>(handlers::handle_formatting)?
.finish();
match req {
Ok(id) => {

View file

@ -6,13 +6,16 @@ use languageserver_types::{
DiagnosticSeverity, DocumentSymbol, Documentation, FoldingRange, FoldingRangeKind,
FoldingRangeParams, Location, MarkupContent, MarkupKind, MarkedString, Position,
PrepareRenameResponse, RenameParams, SymbolInformation, TextDocumentIdentifier, TextEdit,
Range,
WorkspaceEdit, ParameterInformation, ParameterLabel, SignatureInformation, Hover, HoverContents,
DocumentFormattingParams,
};
use ra_analysis::{FileId, FoldKind, Query, RunnableKind, FileRange, FilePosition, Severity};
use ra_syntax::{TextUnit, text_utils::intersect};
use ra_text_edit::text_utils::contains_offset_nonstrict;
use rustc_hash::FxHashMap;
use serde_json::to_value;
use std::io::Write;
use crate::{
conv::{to_location, Conv, ConvWith, MapConvWith, TryConvWith},
@ -601,6 +604,40 @@ pub fn handle_references(
))
}
pub fn handle_formatting(
world: ServerWorld,
params: DocumentFormattingParams,
) -> Result<Option<Vec<TextEdit>>> {
let file_id = params.text_document.try_conv_with(&world)?;
let file = world.analysis().file_text(file_id);
let file_line_index = world.analysis().file_line_index(file_id);
let end_position = TextUnit::of_str(&file).conv_with(&file_line_index);
use std::process;
let mut rustfmt = process::Command::new("rustfmt")
.stdin(process::Stdio::piped())
.stdout(process::Stdio::piped())
.spawn()?;
rustfmt.stdin.as_mut().unwrap().write_all(file.as_bytes())?;
let output = rustfmt.wait_with_output()?;
let captured_stdout = String::from_utf8(output.stdout)?;
if !output.status.success() {
failure::bail!(
"rustfmt exited with error code {}: {}.",
output.status,
captured_stdout,
);
}
Ok(Some(vec![TextEdit {
range: Range::new(Position::new(0, 0), end_position),
new_text: captured_stdout,
}]))
}
pub fn handle_code_action(
world: ServerWorld,
params: req::CodeActionParams,

View file

@ -1,8 +1,8 @@
mod support;
use serde_json::json;
use ra_lsp_server::req::{Runnables, RunnablesParams, CodeActionRequest, CodeActionParams};
use languageserver_types::{Position, Range, CodeActionContext};
use ra_lsp_server::req::{Runnables, RunnablesParams, CodeActionRequest, CodeActionParams, Formatting};
use languageserver_types::{Position, Range, CodeActionContext, DocumentFormattingParams, FormattingOptions};
use crate::support::project;
@ -118,6 +118,60 @@ fn test_eggs() {}
);
}
use std::collections::HashMap;
#[test]
fn test_format_document() {
tools::install_rustfmt().unwrap();
let server = project(
r#"
[package]
name = "foo"
version = "0.0.0"
//- src/lib.rs
mod bar;
fn main() {
}
pub use std::collections::HashMap;
"#,
);
server.wait_for_feedback("workspace loaded");
server.request::<Formatting>(
DocumentFormattingParams {
text_document: server.doc_id("src/lib.rs"),
options: FormattingOptions {
tab_size: 4,
insert_spaces: false,
properties: HashMap::new(),
},
},
json!([
{
"newText": r#"mod bar;
fn main() {}
pub use std::collections::HashMap;
"#,
"range": {
"end": {
"character": 0,
"line": 6
},
"start": {
"character": 0,
"line": 0
}
}
}
]),
);
}
#[test]
fn test_missing_module_code_action() {
let server = project(

View file

@ -117,7 +117,7 @@ pub fn run_rustfmt(mode: Mode) -> Result<()> {
Ok(())
}
fn install_rustfmt() -> Result<()> {
pub fn install_rustfmt() -> Result<()> {
run(&format!("rustup install {}", TOOLCHAIN), ".")?;
run(
&format!("rustup component add rustfmt --toolchain {}", TOOLCHAIN),

View file

@ -45,6 +45,8 @@ It's better to remove existing Rust plugins to avoid interference.
`#[test]`, this action runs this specific test. If the cursor is
outside of the test function, this re-runs the last test. Do bind
this to a shortcut!
- **Format document**. Formats the current file with rustfmt.
Rustfmt must be installed separately with `rustup component add rustfmt`.
* Typing assists
- typing `let =` tries to smartly add `;` if `=` is followed by an existing expression.