aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/hir_def/src/path/lower.rs27
-rw-r--r--crates/hir_def/src/resolver.rs38
-rw-r--r--crates/hir_ty/src/display.rs6
-rw-r--r--crates/hir_ty/src/infer.rs7
-rw-r--r--crates/hir_ty/src/tests/traits.rs93
-rw-r--r--crates/ide/src/goto_implementation.rs70
-rw-r--r--crates/ide_completion/src/completions/record.rs10
-rw-r--r--crates/ide_completion/src/completions/snippet.rs6
-rw-r--r--crates/rust-analyzer/src/caps.rs4
-rw-r--r--crates/rust-analyzer/src/config.rs13
-rw-r--r--crates/rust-analyzer/src/handlers.rs239
-rw-r--r--crates/rust-analyzer/src/main_loop.rs1
-rw-r--r--crates/rust-analyzer/src/reload.rs6
-rw-r--r--docs/user/generated_config.adoc7
-rw-r--r--editors/code/package.json5
15 files changed, 387 insertions, 145 deletions
diff --git a/crates/hir_def/src/path/lower.rs b/crates/hir_def/src/path/lower.rs
index 5d5dd9c8f..54ede7393 100644
--- a/crates/hir_def/src/path/lower.rs
+++ b/crates/hir_def/src/path/lower.rs
@@ -205,15 +205,14 @@ fn lower_generic_args_from_fn_path(
205) -> Option<GenericArgs> { 205) -> Option<GenericArgs> {
206 let mut args = Vec::new(); 206 let mut args = Vec::new();
207 let mut bindings = Vec::new(); 207 let mut bindings = Vec::new();
208 if let Some(params) = params { 208 let params = params?;
209 let mut param_types = Vec::new(); 209 let mut param_types = Vec::new();
210 for param in params.params() { 210 for param in params.params() {
211 let type_ref = TypeRef::from_ast_opt(&ctx, param.ty()); 211 let type_ref = TypeRef::from_ast_opt(&ctx, param.ty());
212 param_types.push(type_ref); 212 param_types.push(type_ref);
213 }
214 let arg = GenericArg::Type(TypeRef::Tuple(param_types));
215 args.push(arg);
216 } 213 }
214 let arg = GenericArg::Type(TypeRef::Tuple(param_types));
215 args.push(arg);
217 if let Some(ret_type) = ret_type { 216 if let Some(ret_type) = ret_type {
218 let type_ref = TypeRef::from_ast_opt(&ctx, ret_type.ty()); 217 let type_ref = TypeRef::from_ast_opt(&ctx, ret_type.ty());
219 bindings.push(AssociatedTypeBinding { 218 bindings.push(AssociatedTypeBinding {
@@ -221,10 +220,14 @@ fn lower_generic_args_from_fn_path(
221 type_ref: Some(type_ref), 220 type_ref: Some(type_ref),
222 bounds: Vec::new(), 221 bounds: Vec::new(),
223 }); 222 });
224 }
225 if args.is_empty() && bindings.is_empty() {
226 None
227 } else { 223 } else {
228 Some(GenericArgs { args, has_self_type: false, bindings }) 224 // -> ()
225 let type_ref = TypeRef::Tuple(Vec::new());
226 bindings.push(AssociatedTypeBinding {
227 name: name![Output],
228 type_ref: Some(type_ref),
229 bounds: Vec::new(),
230 });
229 } 231 }
232 Some(GenericArgs { args, has_self_type: false, bindings })
230} 233}
diff --git a/crates/hir_def/src/resolver.rs b/crates/hir_def/src/resolver.rs
index 0391cc49b..fb8a6f260 100644
--- a/crates/hir_def/src/resolver.rs
+++ b/crates/hir_def/src/resolver.rs
@@ -337,22 +337,34 @@ impl Resolver {
337 pub fn traits_in_scope(&self, db: &dyn DefDatabase) -> FxHashSet<TraitId> { 337 pub fn traits_in_scope(&self, db: &dyn DefDatabase) -> FxHashSet<TraitId> {
338 let mut traits = FxHashSet::default(); 338 let mut traits = FxHashSet::default();
339 for scope in &self.scopes { 339 for scope in &self.scopes {
340 if let Scope::ModuleScope(m) = scope { 340 match scope {
341 if let Some(prelude) = m.def_map.prelude() { 341 Scope::ModuleScope(m) => {
342 let prelude_def_map = prelude.def_map(db); 342 if let Some(prelude) = m.def_map.prelude() {
343 traits.extend(prelude_def_map[prelude.local_id].scope.traits());
344 }
345 traits.extend(m.def_map[m.module_id].scope.traits());
346
347 // Add all traits that are in scope because of the containing DefMaps
348 m.def_map.with_ancestor_maps(db, m.module_id, &mut |def_map, module| {
349 if let Some(prelude) = def_map.prelude() {
350 let prelude_def_map = prelude.def_map(db); 343 let prelude_def_map = prelude.def_map(db);
351 traits.extend(prelude_def_map[prelude.local_id].scope.traits()); 344 traits.extend(prelude_def_map[prelude.local_id].scope.traits());
352 } 345 }
353 traits.extend(def_map[module].scope.traits()); 346 traits.extend(m.def_map[m.module_id].scope.traits());
354 None::<()> 347
355 }); 348 // Add all traits that are in scope because of the containing DefMaps
349 m.def_map.with_ancestor_maps(db, m.module_id, &mut |def_map, module| {
350 if let Some(prelude) = def_map.prelude() {
351 let prelude_def_map = prelude.def_map(db);
352 traits.extend(prelude_def_map[prelude.local_id].scope.traits());
353 }
354 traits.extend(def_map[module].scope.traits());
355 None::<()>
356 });
357 }
358 &Scope::ImplDefScope(impl_) => {
359 if let Some(target_trait) = &db.impl_data(impl_).target_trait {
360 if let Some(TypeNs::TraitId(trait_)) =
361 self.resolve_path_in_type_ns_fully(db, target_trait.path.mod_path())
362 {
363 traits.insert(trait_);
364 }
365 }
366 }
367 _ => (),
356 } 368 }
357 } 369 }
358 traits 370 traits
diff --git a/crates/hir_ty/src/display.rs b/crates/hir_ty/src/display.rs
index 637bbc634..44f843bf3 100644
--- a/crates/hir_ty/src/display.rs
+++ b/crates/hir_ty/src/display.rs
@@ -778,8 +778,10 @@ fn write_bounds_like_dyn_trait(
778 } 778 }
779 WhereClause::AliasEq(alias_eq) if is_fn_trait => { 779 WhereClause::AliasEq(alias_eq) if is_fn_trait => {
780 is_fn_trait = false; 780 is_fn_trait = false;
781 write!(f, " -> ")?; 781 if !alias_eq.ty.is_unit() {
782 alias_eq.ty.hir_fmt(f)?; 782 write!(f, " -> ")?;
783 alias_eq.ty.hir_fmt(f)?;
784 }
783 } 785 }
784 WhereClause::AliasEq(AliasEq { ty, alias }) => { 786 WhereClause::AliasEq(AliasEq { ty, alias }) => {
785 // in types in actual Rust, these will always come 787 // in types in actual Rust, these will always come
diff --git a/crates/hir_ty/src/infer.rs b/crates/hir_ty/src/infer.rs
index 174b7471e..8cefd80f3 100644
--- a/crates/hir_ty/src/infer.rs
+++ b/crates/hir_ty/src/infer.rs
@@ -579,9 +579,14 @@ impl<'a> InferenceContext<'a> {
579 } 579 }
580 580
581 fn resolve_ops_try_ok(&self) -> Option<TypeAliasId> { 581 fn resolve_ops_try_ok(&self) -> Option<TypeAliasId> {
582 // FIXME resolve via lang_item once try v2 is stable
582 let path = path![core::ops::Try]; 583 let path = path![core::ops::Try];
583 let trait_ = self.resolver.resolve_known_trait(self.db.upcast(), &path)?; 584 let trait_ = self.resolver.resolve_known_trait(self.db.upcast(), &path)?;
584 self.db.trait_data(trait_).associated_type_by_name(&name![Ok]) 585 let trait_data = self.db.trait_data(trait_);
586 trait_data
587 // FIXME remove once try v2 is stable
588 .associated_type_by_name(&name![Ok])
589 .or_else(|| trait_data.associated_type_by_name(&name![Output]))
585 } 590 }
586 591
587 fn resolve_ops_neg_output(&self) -> Option<TypeAliasId> { 592 fn resolve_ops_neg_output(&self) -> Option<TypeAliasId> {
diff --git a/crates/hir_ty/src/tests/traits.rs b/crates/hir_ty/src/tests/traits.rs
index a5a2df54c..6ad96bfe3 100644
--- a/crates/hir_ty/src/tests/traits.rs
+++ b/crates/hir_ty/src/tests/traits.rs
@@ -161,6 +161,43 @@ mod result {
161} 161}
162 162
163#[test] 163#[test]
164fn infer_tryv2() {
165 check_types(
166 r#"
167//- /main.rs crate:main deps:core
168fn test() {
169 let r: Result<i32, u64> = Result::Ok(1);
170 let v = r?;
171 v;
172} //^ i32
173
174//- /core.rs crate:core
175#[prelude_import] use ops::*;
176mod ops {
177 trait Try {
178 type Output;
179 type Residual;
180 }
181}
182
183#[prelude_import] use result::*;
184mod result {
185 enum Infallible {}
186 enum Result<O, E> {
187 Ok(O),
188 Err(E)
189 }
190
191 impl<O, E> crate::ops::Try for Result<O, E> {
192 type Output = O;
193 type Error = Result<Infallible, E>;
194 }
195}
196"#,
197 );
198}
199
200#[test]
164fn infer_for_loop() { 201fn infer_for_loop() {
165 check_types( 202 check_types(
166 r#" 203 r#"
@@ -3041,7 +3078,7 @@ fn infer_fn_trait_arg() {
3041 3078
3042#[test] 3079#[test]
3043fn infer_box_fn_arg() { 3080fn infer_box_fn_arg() {
3044 // The type mismatch is a bug 3081 // The type mismatch is because we don't define Unsize and CoerceUnsized
3045 check_infer_with_mismatches( 3082 check_infer_with_mismatches(
3046 r#" 3083 r#"
3047//- /lib.rs deps:std 3084//- /lib.rs deps:std
@@ -3101,7 +3138,7 @@ fn foo() {
3101 555..557 'ps': {unknown} 3138 555..557 'ps': {unknown}
3102 559..561 '{}': () 3139 559..561 '{}': ()
3103 568..569 'f': Box<dyn FnOnce(&Option<i32>)> 3140 568..569 'f': Box<dyn FnOnce(&Option<i32>)>
3104 568..573 'f(&s)': FnOnce::Output<dyn FnOnce(&Option<i32>), (&Option<i32>,)> 3141 568..573 'f(&s)': ()
3105 570..572 '&s': &Option<i32> 3142 570..572 '&s': &Option<i32>
3106 571..572 's': Option<i32> 3143 571..572 's': Option<i32>
3107 549..562: expected Box<dyn FnOnce(&Option<i32>)>, got Box<|{unknown}| -> ()> 3144 549..562: expected Box<dyn FnOnce(&Option<i32>)>, got Box<|{unknown}| -> ()>
@@ -3571,3 +3608,55 @@ fn main() {
3571 "#]], 3608 "#]],
3572 ) 3609 )
3573} 3610}
3611
3612#[test]
3613fn fn_returning_unit() {
3614 check_infer_with_mismatches(
3615 r#"
3616#[lang = "fn_once"]
3617trait FnOnce<Args> {
3618 type Output;
3619}
3620
3621fn test<F: FnOnce()>(f: F) {
3622 let _: () = f();
3623}"#,
3624 expect![[r#"
3625 82..83 'f': F
3626 88..112 '{ ...f(); }': ()
3627 98..99 '_': ()
3628 106..107 'f': F
3629 106..109 'f()': ()
3630 "#]],
3631 );
3632}
3633
3634#[test]
3635fn trait_in_scope_of_trait_impl() {
3636 check_infer(
3637 r#"
3638mod foo {
3639 pub trait Foo {
3640 fn foo(self);
3641 fn bar(self) -> usize { 0 }
3642 }
3643}
3644impl foo::Foo for u32 {
3645 fn foo(self) {
3646 let _x = self.bar();
3647 }
3648}
3649 "#,
3650 expect![[r#"
3651 45..49 'self': Self
3652 67..71 'self': Self
3653 82..87 '{ 0 }': usize
3654 84..85 '0': usize
3655 131..135 'self': u32
3656 137..173 '{ ... }': ()
3657 151..153 '_x': usize
3658 156..160 'self': u32
3659 156..166 'self.bar()': usize
3660 "#]],
3661 );
3662}
diff --git a/crates/ide/src/goto_implementation.rs b/crates/ide/src/goto_implementation.rs
index 05130a237..43356a94e 100644
--- a/crates/ide/src/goto_implementation.rs
+++ b/crates/ide/src/goto_implementation.rs
@@ -1,4 +1,4 @@
1use hir::{Impl, Semantics}; 1use hir::{AsAssocItem, Impl, Semantics};
2use ide_db::{ 2use ide_db::{
3 defs::{Definition, NameClass, NameRefClass}, 3 defs::{Definition, NameClass, NameRefClass},
4 RootDatabase, 4 RootDatabase,
@@ -36,6 +36,7 @@ pub(crate) fn goto_implementation(
36 } 36 }
37 ast::NameLike::Lifetime(_) => None, 37 ast::NameLike::Lifetime(_) => None,
38 }?; 38 }?;
39
39 let def = match def { 40 let def = match def {
40 Definition::ModuleDef(def) => def, 41 Definition::ModuleDef(def) => def,
41 _ => return None, 42 _ => return None,
@@ -48,6 +49,18 @@ pub(crate) fn goto_implementation(
48 let module = sema.to_module_def(position.file_id)?; 49 let module = sema.to_module_def(position.file_id)?;
49 impls_for_ty(&sema, builtin.ty(sema.db, module)) 50 impls_for_ty(&sema, builtin.ty(sema.db, module))
50 } 51 }
52 hir::ModuleDef::Function(f) => {
53 let assoc = f.as_assoc_item(sema.db)?;
54 let name = assoc.name(sema.db)?;
55 let trait_ = assoc.containing_trait(sema.db)?;
56 impls_for_trait_item(&sema, trait_, name)
57 }
58 hir::ModuleDef::Const(c) => {
59 let assoc = c.as_assoc_item(sema.db)?;
60 let name = assoc.name(sema.db)?;
61 let trait_ = assoc.containing_trait(sema.db)?;
62 impls_for_trait_item(&sema, trait_, name)
63 }
51 _ => return None, 64 _ => return None,
52 }; 65 };
53 Some(RangeInfo { range: node.syntax().text_range(), info: navs }) 66 Some(RangeInfo { range: node.syntax().text_range(), info: navs })
@@ -64,6 +77,23 @@ fn impls_for_trait(sema: &Semantics<RootDatabase>, trait_: hir::Trait) -> Vec<Na
64 .collect() 77 .collect()
65} 78}
66 79
80fn impls_for_trait_item(
81 sema: &Semantics<RootDatabase>,
82 trait_: hir::Trait,
83 fun_name: hir::Name,
84) -> Vec<NavigationTarget> {
85 Impl::all_for_trait(sema.db, trait_)
86 .into_iter()
87 .filter_map(|imp| {
88 let item = imp.items(sema.db).iter().find_map(|itm| {
89 let itm_name = itm.name(sema.db)?;
90 (itm_name == fun_name).then(|| itm.clone())
91 })?;
92 item.try_to_nav(sema.db)
93 })
94 .collect()
95}
96
67#[cfg(test)] 97#[cfg(test)]
68mod tests { 98mod tests {
69 use ide_db::base_db::FileRange; 99 use ide_db::base_db::FileRange;
@@ -262,4 +292,42 @@ impl bool {}
262"#, 292"#,
263 ); 293 );
264 } 294 }
295
296 #[test]
297 fn goto_implementation_trait_functions() {
298 check(
299 r#"
300trait Tr {
301 fn f$0();
302}
303
304struct S;
305
306impl Tr for S {
307 fn f() {
308 //^
309 println!("Hello, world!");
310 }
311}
312"#,
313 );
314 }
315
316 #[test]
317 fn goto_implementation_trait_assoc_const() {
318 check(
319 r#"
320trait Tr {
321 const C$0: usize;
322}
323
324struct S;
325
326impl Tr for S {
327 const C: usize = 4;
328 //^
329}
330"#,
331 );
332 }
265} 333}
diff --git a/crates/ide_completion/src/completions/record.rs b/crates/ide_completion/src/completions/record.rs
index 2f95b8687..40006fb74 100644
--- a/crates/ide_completion/src/completions/record.rs
+++ b/crates/ide_completion/src/completions/record.rs
@@ -108,8 +108,6 @@ fn process(f: S) {
108 check_snippet( 108 check_snippet(
109 test_code, 109 test_code,
110 expect![[r#" 110 expect![[r#"
111 sn pd
112 sn ppd
113 fd ..Default::default() 111 fd ..Default::default()
114 "#]], 112 "#]],
115 ); 113 );
@@ -179,13 +177,7 @@ fn process(f: S) {
179 "#]], 177 "#]],
180 ); 178 );
181 179
182 check_snippet( 180 check_snippet(test_code, expect![[r#""#]]);
183 test_code,
184 expect![[r#"
185 sn pd
186 sn ppd
187 "#]],
188 );
189 } 181 }
190 182
191 #[test] 183 #[test]
diff --git a/crates/ide_completion/src/completions/snippet.rs b/crates/ide_completion/src/completions/snippet.rs
index 7f7830976..14cfb61de 100644
--- a/crates/ide_completion/src/completions/snippet.rs
+++ b/crates/ide_completion/src/completions/snippet.rs
@@ -22,8 +22,10 @@ pub(crate) fn complete_expr_snippet(acc: &mut Completions, ctx: &CompletionConte
22 None => return, 22 None => return,
23 }; 23 };
24 24
25 snippet(ctx, cap, "pd", "eprintln!(\"$0 = {:?}\", $0);").add_to(acc); 25 if ctx.can_be_stmt {
26 snippet(ctx, cap, "ppd", "eprintln!(\"$0 = {:#?}\", $0);").add_to(acc); 26 snippet(ctx, cap, "pd", "eprintln!(\"$0 = {:?}\", $0);").add_to(acc);
27 snippet(ctx, cap, "ppd", "eprintln!(\"$0 = {:#?}\", $0);").add_to(acc);
28 }
27} 29}
28 30
29pub(crate) fn complete_item_snippet(acc: &mut Completions, ctx: &CompletionContext) { 31pub(crate) fn complete_item_snippet(acc: &mut Completions, ctx: &CompletionContext) {
diff --git a/crates/rust-analyzer/src/caps.rs b/crates/rust-analyzer/src/caps.rs
index b2317618a..4d88932ca 100644
--- a/crates/rust-analyzer/src/caps.rs
+++ b/crates/rust-analyzer/src/caps.rs
@@ -1,4 +1,4 @@
1//! Advertizes the capabilities of the LSP Server. 1//! Advertises the capabilities of the LSP Server.
2use std::env; 2use std::env;
3 3
4use lsp_types::{ 4use lsp_types::{
@@ -54,7 +54,7 @@ pub fn server_capabilities(client_caps: &ClientCapabilities) -> ServerCapabiliti
54 code_action_provider: Some(code_action_capabilities(client_caps)), 54 code_action_provider: Some(code_action_capabilities(client_caps)),
55 code_lens_provider: Some(CodeLensOptions { resolve_provider: Some(true) }), 55 code_lens_provider: Some(CodeLensOptions { resolve_provider: Some(true) }),
56 document_formatting_provider: Some(OneOf::Left(true)), 56 document_formatting_provider: Some(OneOf::Left(true)),
57 document_range_formatting_provider: None, 57 document_range_formatting_provider: Some(OneOf::Left(true)),
58 document_on_type_formatting_provider: Some(DocumentOnTypeFormattingOptions { 58 document_on_type_formatting_provider: Some(DocumentOnTypeFormattingOptions {
59 first_trigger_character: "=".to_string(), 59 first_trigger_character: "=".to_string(),
60 more_trigger_character: Some(vec![".".to_string(), ">".to_string(), "{".to_string()]), 60 more_trigger_character: Some(vec![".".to_string(), ">".to_string(), "{".to_string()]),
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 7c02a507c..7620a2fe1 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -218,6 +218,10 @@ config_data! {
218 /// Advanced option, fully override the command rust-analyzer uses for 218 /// Advanced option, fully override the command rust-analyzer uses for
219 /// formatting. 219 /// formatting.
220 rustfmt_overrideCommand: Option<Vec<String>> = "null", 220 rustfmt_overrideCommand: Option<Vec<String>> = "null",
221 /// Enables the use of rustfmt's unstable range formatting command for the
222 /// `textDocument/rangeFormatting` request. The rustfmt option is unstable and only
223 /// available on a nightly build.
224 rustfmt_enableRangeFormatting: bool = "false",
221 225
222 /// Workspace symbol search scope. 226 /// Workspace symbol search scope.
223 workspace_symbol_search_scope: WorskpaceSymbolSearchScopeDef = "\"workspace\"", 227 workspace_symbol_search_scope: WorskpaceSymbolSearchScopeDef = "\"workspace\"",
@@ -305,7 +309,7 @@ pub struct NotificationsConfig {
305 309
306#[derive(Debug, Clone)] 310#[derive(Debug, Clone)]
307pub enum RustfmtConfig { 311pub enum RustfmtConfig {
308 Rustfmt { extra_args: Vec<String> }, 312 Rustfmt { extra_args: Vec<String>, enable_range_formatting: bool },
309 CustomCommand { command: String, args: Vec<String> }, 313 CustomCommand { command: String, args: Vec<String> },
310} 314}
311 315
@@ -584,9 +588,10 @@ impl Config {
584 let command = args.remove(0); 588 let command = args.remove(0);
585 RustfmtConfig::CustomCommand { command, args } 589 RustfmtConfig::CustomCommand { command, args }
586 } 590 }
587 Some(_) | None => { 591 Some(_) | None => RustfmtConfig::Rustfmt {
588 RustfmtConfig::Rustfmt { extra_args: self.data.rustfmt_extraArgs.clone() } 592 extra_args: self.data.rustfmt_extraArgs.clone(),
589 } 593 enable_range_formatting: self.data.rustfmt_enableRangeFormatting,
594 },
590 } 595 }
591 } 596 }
592 pub fn flycheck(&self) -> Option<FlycheckConfig> { 597 pub fn flycheck(&self) -> Option<FlycheckConfig> {
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index f48210424..456744603 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -27,7 +27,7 @@ use lsp_types::{
27}; 27};
28use project_model::TargetKind; 28use project_model::TargetKind;
29use serde::{Deserialize, Serialize}; 29use serde::{Deserialize, Serialize};
30use serde_json::to_value; 30use serde_json::{json, to_value};
31use stdx::format_to; 31use stdx::format_to;
32use syntax::{algo, ast, AstNode, TextRange, TextSize}; 32use syntax::{algo, ast, AstNode, TextRange, TextSize};
33 33
@@ -955,104 +955,17 @@ pub(crate) fn handle_formatting(
955 params: DocumentFormattingParams, 955 params: DocumentFormattingParams,
956) -> Result<Option<Vec<lsp_types::TextEdit>>> { 956) -> Result<Option<Vec<lsp_types::TextEdit>>> {
957 let _p = profile::span("handle_formatting"); 957 let _p = profile::span("handle_formatting");
958 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
959 let file = snap.analysis.file_text(file_id)?;
960 let crate_ids = snap.analysis.crate_for(file_id)?;
961
962 let line_index = snap.file_line_index(file_id)?;
963
964 let mut rustfmt = match snap.config.rustfmt() {
965 RustfmtConfig::Rustfmt { extra_args } => {
966 let mut cmd = process::Command::new(toolchain::rustfmt());
967 cmd.args(extra_args);
968 // try to chdir to the file so we can respect `rustfmt.toml`
969 // FIXME: use `rustfmt --config-path` once
970 // https://github.com/rust-lang/rustfmt/issues/4660 gets fixed
971 match params.text_document.uri.to_file_path() {
972 Ok(mut path) => {
973 // pop off file name
974 if path.pop() && path.is_dir() {
975 cmd.current_dir(path);
976 }
977 }
978 Err(_) => {
979 log::error!(
980 "Unable to get file path for {}, rustfmt.toml might be ignored",
981 params.text_document.uri
982 );
983 }
984 }
985 if let Some(&crate_id) = crate_ids.first() {
986 // Assume all crates are in the same edition
987 let edition = snap.analysis.crate_edition(crate_id)?;
988 cmd.arg("--edition");
989 cmd.arg(edition.to_string());
990 }
991 cmd
992 }
993 RustfmtConfig::CustomCommand { command, args } => {
994 let mut cmd = process::Command::new(command);
995 cmd.args(args);
996 cmd
997 }
998 };
999 958
1000 let mut rustfmt = 959 run_rustfmt(&snap, params.text_document, None)
1001 rustfmt.stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::piped()).spawn()?; 960}
1002
1003 rustfmt.stdin.as_mut().unwrap().write_all(file.as_bytes())?;
1004
1005 let output = rustfmt.wait_with_output()?;
1006 let captured_stdout = String::from_utf8(output.stdout)?;
1007 let captured_stderr = String::from_utf8(output.stderr).unwrap_or_default();
1008
1009 if !output.status.success() {
1010 let rustfmt_not_installed =
1011 captured_stderr.contains("not installed") || captured_stderr.contains("not available");
1012
1013 return match output.status.code() {
1014 Some(1) if !rustfmt_not_installed => {
1015 // While `rustfmt` doesn't have a specific exit code for parse errors this is the
1016 // likely cause exiting with 1. Most Language Servers swallow parse errors on
1017 // formatting because otherwise an error is surfaced to the user on top of the
1018 // syntax error diagnostics they're already receiving. This is especially jarring
1019 // if they have format on save enabled.
1020 log::info!("rustfmt exited with status 1, assuming parse error and ignoring");
1021 Ok(None)
1022 }
1023 _ => {
1024 // Something else happened - e.g. `rustfmt` is missing or caught a signal
1025 Err(LspError::new(
1026 -32900,
1027 format!(
1028 r#"rustfmt exited with:
1029 Status: {}
1030 stdout: {}
1031 stderr: {}"#,
1032 output.status, captured_stdout, captured_stderr,
1033 ),
1034 )
1035 .into())
1036 }
1037 };
1038 }
1039 961
1040 let (new_text, new_line_endings) = LineEndings::normalize(captured_stdout); 962pub(crate) fn handle_range_formatting(
963 snap: GlobalStateSnapshot,
964 params: lsp_types::DocumentRangeFormattingParams,
965) -> Result<Option<Vec<lsp_types::TextEdit>>> {
966 let _p = profile::span("handle_range_formatting");
1041 967
1042 if line_index.endings != new_line_endings { 968 run_rustfmt(&snap, params.text_document, Some(params.range))
1043 // If line endings are different, send the entire file.
1044 // Diffing would not work here, as the line endings might be the only
1045 // difference.
1046 Ok(Some(to_proto::text_edit_vec(
1047 &line_index,
1048 TextEdit::replace(TextRange::up_to(TextSize::of(&*file)), new_text),
1049 )))
1050 } else if *file == new_text {
1051 // The document is already formatted correctly -- no edits needed.
1052 Ok(None)
1053 } else {
1054 Ok(Some(to_proto::text_edit_vec(&line_index, diff(&file, &new_text))))
1055 }
1056} 969}
1057 970
1058pub(crate) fn handle_code_action( 971pub(crate) fn handle_code_action(
@@ -1675,6 +1588,140 @@ fn should_skip_target(runnable: &Runnable, cargo_spec: Option<&CargoTargetSpec>)
1675 } 1588 }
1676} 1589}
1677 1590
1591fn run_rustfmt(
1592 snap: &GlobalStateSnapshot,
1593 text_document: TextDocumentIdentifier,
1594 range: Option<lsp_types::Range>,
1595) -> Result<Option<Vec<lsp_types::TextEdit>>> {
1596 let file_id = from_proto::file_id(&snap, &text_document.uri)?;
1597 let file = snap.analysis.file_text(file_id)?;
1598 let crate_ids = snap.analysis.crate_for(file_id)?;
1599
1600 let line_index = snap.file_line_index(file_id)?;
1601
1602 let mut rustfmt = match snap.config.rustfmt() {
1603 RustfmtConfig::Rustfmt { extra_args, enable_range_formatting } => {
1604 let mut cmd = process::Command::new(toolchain::rustfmt());
1605 cmd.args(extra_args);
1606 // try to chdir to the file so we can respect `rustfmt.toml`
1607 // FIXME: use `rustfmt --config-path` once
1608 // https://github.com/rust-lang/rustfmt/issues/4660 gets fixed
1609 match text_document.uri.to_file_path() {
1610 Ok(mut path) => {
1611 // pop off file name
1612 if path.pop() && path.is_dir() {
1613 cmd.current_dir(path);
1614 }
1615 }
1616 Err(_) => {
1617 log::error!(
1618 "Unable to get file path for {}, rustfmt.toml might be ignored",
1619 text_document.uri
1620 );
1621 }
1622 }
1623 if let Some(&crate_id) = crate_ids.first() {
1624 // Assume all crates are in the same edition
1625 let edition = snap.analysis.crate_edition(crate_id)?;
1626 cmd.arg("--edition");
1627 cmd.arg(edition.to_string());
1628 }
1629
1630 if let Some(range) = range {
1631 if !enable_range_formatting {
1632 return Err(LspError::new(
1633 ErrorCode::InvalidRequest as i32,
1634 String::from(
1635 "rustfmt range formatting is unstable. \
1636 Opt-in by using a nightly build of rustfmt and setting \
1637 `rustfmt.enableRangeFormatting` to true in your LSP configuration",
1638 ),
1639 )
1640 .into());
1641 }
1642
1643 let frange = from_proto::file_range(&snap, text_document.clone(), range)?;
1644 let start_line = line_index.index.line_col(frange.range.start()).line;
1645 let end_line = line_index.index.line_col(frange.range.end()).line;
1646
1647 cmd.arg("--unstable-features");
1648 cmd.arg("--file-lines");
1649 cmd.arg(
1650 json!([{
1651 "file": "stdin",
1652 "range": [start_line, end_line]
1653 }])
1654 .to_string(),
1655 );
1656 }
1657
1658 cmd
1659 }
1660 RustfmtConfig::CustomCommand { command, args } => {
1661 let mut cmd = process::Command::new(command);
1662 cmd.args(args);
1663 cmd
1664 }
1665 };
1666
1667 let mut rustfmt =
1668 rustfmt.stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::piped()).spawn()?;
1669
1670 rustfmt.stdin.as_mut().unwrap().write_all(file.as_bytes())?;
1671
1672 let output = rustfmt.wait_with_output()?;
1673 let captured_stdout = String::from_utf8(output.stdout)?;
1674 let captured_stderr = String::from_utf8(output.stderr).unwrap_or_default();
1675
1676 if !output.status.success() {
1677 let rustfmt_not_installed =
1678 captured_stderr.contains("not installed") || captured_stderr.contains("not available");
1679
1680 return match output.status.code() {
1681 Some(1) if !rustfmt_not_installed => {
1682 // While `rustfmt` doesn't have a specific exit code for parse errors this is the
1683 // likely cause exiting with 1. Most Language Servers swallow parse errors on
1684 // formatting because otherwise an error is surfaced to the user on top of the
1685 // syntax error diagnostics they're already receiving. This is especially jarring
1686 // if they have format on save enabled.
1687 log::info!("rustfmt exited with status 1, assuming parse error and ignoring");
1688 Ok(None)
1689 }
1690 _ => {
1691 // Something else happened - e.g. `rustfmt` is missing or caught a signal
1692 Err(LspError::new(
1693 -32900,
1694 format!(
1695 r#"rustfmt exited with:
1696 Status: {}
1697 stdout: {}
1698 stderr: {}"#,
1699 output.status, captured_stdout, captured_stderr,
1700 ),
1701 )
1702 .into())
1703 }
1704 };
1705 }
1706
1707 let (new_text, new_line_endings) = LineEndings::normalize(captured_stdout);
1708
1709 if line_index.endings != new_line_endings {
1710 // If line endings are different, send the entire file.
1711 // Diffing would not work here, as the line endings might be the only
1712 // difference.
1713 Ok(Some(to_proto::text_edit_vec(
1714 &line_index,
1715 TextEdit::replace(TextRange::up_to(TextSize::of(&*file)), new_text),
1716 )))
1717 } else if *file == new_text {
1718 // The document is already formatted correctly -- no edits needed.
1719 Ok(None)
1720 } else {
1721 Ok(Some(to_proto::text_edit_vec(&line_index, diff(&file, &new_text))))
1722 }
1723}
1724
1678#[derive(Debug, Serialize, Deserialize)] 1725#[derive(Debug, Serialize, Deserialize)]
1679struct CompletionResolveData { 1726struct CompletionResolveData {
1680 position: lsp_types::TextDocumentPositionParams, 1727 position: lsp_types::TextDocumentPositionParams,
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index cb002f700..008758ea0 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -543,6 +543,7 @@ impl GlobalState {
543 .on::<lsp_types::request::Rename>(handlers::handle_rename) 543 .on::<lsp_types::request::Rename>(handlers::handle_rename)
544 .on::<lsp_types::request::References>(handlers::handle_references) 544 .on::<lsp_types::request::References>(handlers::handle_references)
545 .on::<lsp_types::request::Formatting>(handlers::handle_formatting) 545 .on::<lsp_types::request::Formatting>(handlers::handle_formatting)
546 .on::<lsp_types::request::RangeFormatting>(handlers::handle_range_formatting)
546 .on::<lsp_types::request::DocumentHighlightRequest>(handlers::handle_document_highlight) 547 .on::<lsp_types::request::DocumentHighlightRequest>(handlers::handle_document_highlight)
547 .on::<lsp_types::request::CallHierarchyPrepare>(handlers::handle_call_hierarchy_prepare) 548 .on::<lsp_types::request::CallHierarchyPrepare>(handlers::handle_call_hierarchy_prepare)
548 .on::<lsp_types::request::CallHierarchyIncomingCalls>( 549 .on::<lsp_types::request::CallHierarchyIncomingCalls>(
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index 7a53e4a8b..93b5ff55f 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -455,7 +455,11 @@ impl ProjectFolders {
455 dirs.include.extend(root.include); 455 dirs.include.extend(root.include);
456 dirs.exclude.extend(root.exclude); 456 dirs.exclude.extend(root.exclude);
457 for excl in global_excludes { 457 for excl in global_excludes {
458 if dirs.include.iter().any(|incl| incl.starts_with(excl)) { 458 if dirs
459 .include
460 .iter()
461 .any(|incl| incl.starts_with(excl) || excl.starts_with(incl))
462 {
459 dirs.exclude.push(excl.clone()); 463 dirs.exclude.push(excl.clone());
460 } 464 }
461 } 465 }
diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc
index c02bab7cc..f3da82feb 100644
--- a/docs/user/generated_config.adoc
+++ b/docs/user/generated_config.adoc
@@ -346,6 +346,13 @@ Additional arguments to `rustfmt`.
346Advanced option, fully override the command rust-analyzer uses for 346Advanced option, fully override the command rust-analyzer uses for
347formatting. 347formatting.
348-- 348--
349[[rust-analyzer.rustfmt.enableRangeFormatting]]rust-analyzer.rustfmt.enableRangeFormatting (default: `false`)::
350+
351--
352Enables the use of rustfmt's unstable range formatting command for the
353`textDocument/rangeFormatting` request. The rustfmt option is unstable and only
354available on a nightly build.
355--
349[[rust-analyzer.workspace.symbol.search.scope]]rust-analyzer.workspace.symbol.search.scope (default: `"workspace"`):: 356[[rust-analyzer.workspace.symbol.search.scope]]rust-analyzer.workspace.symbol.search.scope (default: `"workspace"`)::
350+ 357+
351-- 358--
diff --git a/editors/code/package.json b/editors/code/package.json
index 17d9281ff..05cbccf94 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -795,6 +795,11 @@
795 "type": "string" 795 "type": "string"
796 } 796 }
797 }, 797 },
798 "rust-analyzer.rustfmt.enableRangeFormatting": {
799 "markdownDescription": "Enables the use of rustfmt's unstable range formatting command for the\n`textDocument/rangeFormatting` request. The rustfmt option is unstable and only\navailable on a nightly build.",
800 "default": false,
801 "type": "boolean"
802 },
798 "rust-analyzer.workspace.symbol.search.scope": { 803 "rust-analyzer.workspace.symbol.search.scope": {
799 "markdownDescription": "Workspace symbol search scope.", 804 "markdownDescription": "Workspace symbol search scope.",
800 "default": "workspace", 805 "default": "workspace",