mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-11-10 15:14:32 +00:00
Merge #358
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:
commit
8d1df9834c
11 changed files with 107 additions and 4 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -4,3 +4,4 @@ crates/*/target
|
|||
.idea/*
|
||||
.vscode/*
|
||||
*.log
|
||||
*.iml
|
||||
|
|
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -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)",
|
||||
]
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in a new issue