diff options
-rw-r--r-- | crates/hir_def/src/path/lower.rs | 27 | ||||
-rw-r--r-- | crates/hir_def/src/resolver.rs | 38 | ||||
-rw-r--r-- | crates/hir_ty/src/display.rs | 6 | ||||
-rw-r--r-- | crates/hir_ty/src/infer.rs | 7 | ||||
-rw-r--r-- | crates/hir_ty/src/tests/traits.rs | 93 | ||||
-rw-r--r-- | crates/ide/src/goto_implementation.rs | 70 | ||||
-rw-r--r-- | crates/ide_completion/src/completions/record.rs | 10 | ||||
-rw-r--r-- | crates/ide_completion/src/completions/snippet.rs | 6 | ||||
-rw-r--r-- | crates/rust-analyzer/src/caps.rs | 4 | ||||
-rw-r--r-- | crates/rust-analyzer/src/config.rs | 13 | ||||
-rw-r--r-- | crates/rust-analyzer/src/handlers.rs | 239 | ||||
-rw-r--r-- | crates/rust-analyzer/src/main_loop.rs | 1 | ||||
-rw-r--r-- | crates/rust-analyzer/src/reload.rs | 6 | ||||
-rw-r--r-- | docs/user/generated_config.adoc | 7 | ||||
-rw-r--r-- | editors/code/package.json | 5 |
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] |
164 | fn infer_tryv2() { | ||
165 | check_types( | ||
166 | r#" | ||
167 | //- /main.rs crate:main deps:core | ||
168 | fn 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::*; | ||
176 | mod ops { | ||
177 | trait Try { | ||
178 | type Output; | ||
179 | type Residual; | ||
180 | } | ||
181 | } | ||
182 | |||
183 | #[prelude_import] use result::*; | ||
184 | mod 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] | ||
164 | fn infer_for_loop() { | 201 | fn 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] |
3043 | fn infer_box_fn_arg() { | 3080 | fn 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] | ||
3613 | fn fn_returning_unit() { | ||
3614 | check_infer_with_mismatches( | ||
3615 | r#" | ||
3616 | #[lang = "fn_once"] | ||
3617 | trait FnOnce<Args> { | ||
3618 | type Output; | ||
3619 | } | ||
3620 | |||
3621 | fn 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] | ||
3635 | fn trait_in_scope_of_trait_impl() { | ||
3636 | check_infer( | ||
3637 | r#" | ||
3638 | mod foo { | ||
3639 | pub trait Foo { | ||
3640 | fn foo(self); | ||
3641 | fn bar(self) -> usize { 0 } | ||
3642 | } | ||
3643 | } | ||
3644 | impl 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 @@ | |||
1 | use hir::{Impl, Semantics}; | 1 | use hir::{AsAssocItem, Impl, Semantics}; |
2 | use ide_db::{ | 2 | use 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 | ||
80 | fn 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)] |
68 | mod tests { | 98 | mod 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#" | ||
300 | trait Tr { | ||
301 | fn f$0(); | ||
302 | } | ||
303 | |||
304 | struct S; | ||
305 | |||
306 | impl 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#" | ||
320 | trait Tr { | ||
321 | const C$0: usize; | ||
322 | } | ||
323 | |||
324 | struct S; | ||
325 | |||
326 | impl 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 | ||
29 | pub(crate) fn complete_item_snippet(acc: &mut Completions, ctx: &CompletionContext) { | 31 | pub(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. |
2 | use std::env; | 2 | use std::env; |
3 | 3 | ||
4 | use lsp_types::{ | 4 | use 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)] |
307 | pub enum RustfmtConfig { | 311 | pub 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 | }; |
28 | use project_model::TargetKind; | 28 | use project_model::TargetKind; |
29 | use serde::{Deserialize, Serialize}; | 29 | use serde::{Deserialize, Serialize}; |
30 | use serde_json::to_value; | 30 | use serde_json::{json, to_value}; |
31 | use stdx::format_to; | 31 | use stdx::format_to; |
32 | use syntax::{algo, ast, AstNode, TextRange, TextSize}; | 32 | use 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, ¶ms.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); | 962 | pub(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 | ||
1058 | pub(crate) fn handle_code_action( | 971 | pub(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 | ||
1591 | fn 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)] |
1679 | struct CompletionResolveData { | 1726 | struct 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`. | |||
346 | Advanced option, fully override the command rust-analyzer uses for | 346 | Advanced option, fully override the command rust-analyzer uses for |
347 | formatting. | 347 | formatting. |
348 | -- | 348 | -- |
349 | [[rust-analyzer.rustfmt.enableRangeFormatting]]rust-analyzer.rustfmt.enableRangeFormatting (default: `false`):: | ||
350 | + | ||
351 | -- | ||
352 | Enables the use of rustfmt's unstable range formatting command for the | ||
353 | `textDocument/rangeFormatting` request. The rustfmt option is unstable and only | ||
354 | available 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", |