diff options
-rw-r--r-- | crates/ra_db/src/input.rs | 14 | ||||
-rw-r--r-- | crates/ra_hir/src/ty/method_resolution.rs | 63 | ||||
-rw-r--r-- | crates/ra_ide_api/src/change.rs | 3 | ||||
-rw-r--r-- | crates/ra_ide_api/src/completion/complete_postfix.rs | 2 | ||||
-rw-r--r-- | crates/ra_ide_api/src/completion/snapshots/completion_item__postfix_completion_works_for_trivial_path_expression.snap | 9 | ||||
-rw-r--r-- | crates/ra_parser/src/grammar/items.rs | 29 | ||||
-rw-r--r-- | crates/ra_syntax/tests/data/parser/inline/err/0014_default_fn_type.rs | 4 | ||||
-rw-r--r-- | crates/ra_syntax/tests/data/parser/inline/err/0014_default_fn_type.txt | 58 | ||||
-rw-r--r-- | crates/ra_syntax/tests/data/parser/inline/ok/0132_default_fn_type.rs | 4 | ||||
-rw-r--r-- | crates/ra_syntax/tests/data/parser/inline/ok/0132_default_fn_type.txt | 55 | ||||
-rw-r--r-- | editors/code/package.json | 9 | ||||
-rw-r--r-- | editors/code/src/commands/cargo_watch.ts | 198 | ||||
-rw-r--r-- | editors/code/src/commands/watch_status.ts | 10 | ||||
-rw-r--r-- | editors/code/src/config.ts | 21 | ||||
-rw-r--r-- | editors/code/src/utils/rust_diagnostics.ts | 226 |
15 files changed, 609 insertions, 96 deletions
diff --git a/crates/ra_db/src/input.rs b/crates/ra_db/src/input.rs index a5f4e489f..c103503bd 100644 --- a/crates/ra_db/src/input.rs +++ b/crates/ra_db/src/input.rs | |||
@@ -31,9 +31,23 @@ pub struct SourceRootId(pub u32); | |||
31 | 31 | ||
32 | #[derive(Default, Clone, Debug, PartialEq, Eq)] | 32 | #[derive(Default, Clone, Debug, PartialEq, Eq)] |
33 | pub struct SourceRoot { | 33 | pub struct SourceRoot { |
34 | /// Sysroot or crates.io library. | ||
35 | /// | ||
36 | /// Libraries are considered mostly immutable, this assumption is used to | ||
37 | /// optimize salsa's query structure | ||
38 | pub is_library: bool, | ||
34 | pub files: FxHashMap<RelativePathBuf, FileId>, | 39 | pub files: FxHashMap<RelativePathBuf, FileId>, |
35 | } | 40 | } |
36 | 41 | ||
42 | impl SourceRoot { | ||
43 | pub fn new() -> SourceRoot { | ||
44 | Default::default() | ||
45 | } | ||
46 | pub fn new_library() -> SourceRoot { | ||
47 | SourceRoot { is_library: true, ..SourceRoot::new() } | ||
48 | } | ||
49 | } | ||
50 | |||
37 | /// `CrateGraph` is a bit of information which turns a set of text files into a | 51 | /// `CrateGraph` is a bit of information which turns a set of text files into a |
38 | /// number of Rust crates. Each crate is defined by the `FileId` of its root module, | 52 | /// number of Rust crates. Each crate is defined by the `FileId` of its root module, |
39 | /// the set of cfg flags (not yet implemented) and the set of dependencies. Note | 53 | /// the set of cfg flags (not yet implemented) and the set of dependencies. Note |
diff --git a/crates/ra_hir/src/ty/method_resolution.rs b/crates/ra_hir/src/ty/method_resolution.rs index 46ec136bd..e023ff25a 100644 --- a/crates/ra_hir/src/ty/method_resolution.rs +++ b/crates/ra_hir/src/ty/method_resolution.rs | |||
@@ -4,6 +4,7 @@ | |||
4 | //! and the corresponding code mostly in librustc_typeck/check/method/probe.rs. | 4 | //! and the corresponding code mostly in librustc_typeck/check/method/probe.rs. |
5 | use std::sync::Arc; | 5 | use std::sync::Arc; |
6 | 6 | ||
7 | use arrayvec::ArrayVec; | ||
7 | use rustc_hash::FxHashMap; | 8 | use rustc_hash::FxHashMap; |
8 | 9 | ||
9 | use crate::{ | 10 | use crate::{ |
@@ -113,19 +114,32 @@ impl CrateImplBlocks { | |||
113 | } | 114 | } |
114 | } | 115 | } |
115 | 116 | ||
116 | fn def_crate(db: &impl HirDatabase, cur_crate: Crate, ty: &Ty) -> Option<Crate> { | 117 | fn def_crates(db: &impl HirDatabase, cur_crate: Crate, ty: &Ty) -> Option<ArrayVec<[Crate; 2]>> { |
118 | // Types like slice can have inherent impls in several crates, (core and alloc). | ||
119 | // The correspoinding impls are marked with lang items, so we can use them to find the required crates. | ||
120 | macro_rules! lang_item_crate { | ||
121 | ($db:expr, $cur_crate:expr, $($name:expr),+ $(,)?) => {{ | ||
122 | let mut v = ArrayVec::<[Crate; 2]>::new(); | ||
123 | $( | ||
124 | v.push($db.lang_item($cur_crate, $name.into())?.krate($db)?); | ||
125 | )+ | ||
126 | Some(v) | ||
127 | }}; | ||
128 | } | ||
129 | |||
117 | match ty { | 130 | match ty { |
118 | Ty::Apply(a_ty) => match a_ty.ctor { | 131 | Ty::Apply(a_ty) => match a_ty.ctor { |
119 | TypeCtor::Adt(def_id) => def_id.krate(db), | 132 | TypeCtor::Adt(def_id) => Some(std::iter::once(def_id.krate(db)?).collect()), |
120 | TypeCtor::Bool => db.lang_item(cur_crate, "bool".into())?.krate(db), | 133 | TypeCtor::Bool => lang_item_crate!(db, cur_crate, "bool"), |
121 | TypeCtor::Char => db.lang_item(cur_crate, "char".into())?.krate(db), | 134 | TypeCtor::Char => lang_item_crate!(db, cur_crate, "char"), |
122 | TypeCtor::Float(UncertainFloatTy::Known(f)) => { | 135 | TypeCtor::Float(UncertainFloatTy::Known(f)) => { |
123 | db.lang_item(cur_crate, f.ty_to_string().into())?.krate(db) | 136 | lang_item_crate!(db, cur_crate, f.ty_to_string()) |
124 | } | 137 | } |
125 | TypeCtor::Int(UncertainIntTy::Known(i)) => { | 138 | TypeCtor::Int(UncertainIntTy::Known(i)) => { |
126 | db.lang_item(cur_crate, i.ty_to_string().into())?.krate(db) | 139 | lang_item_crate!(db, cur_crate, i.ty_to_string()) |
127 | } | 140 | } |
128 | TypeCtor::Str => db.lang_item(cur_crate, "str".into())?.krate(db), | 141 | TypeCtor::Str => lang_item_crate!(db, cur_crate, "str"), |
142 | TypeCtor::Slice => lang_item_crate!(db, cur_crate, "slice_alloc", "slice"), | ||
129 | _ => None, | 143 | _ => None, |
130 | }, | 144 | }, |
131 | _ => None, | 145 | _ => None, |
@@ -218,19 +232,17 @@ fn iterate_inherent_methods<T>( | |||
218 | krate: Crate, | 232 | krate: Crate, |
219 | mut callback: impl FnMut(&Ty, Function) -> Option<T>, | 233 | mut callback: impl FnMut(&Ty, Function) -> Option<T>, |
220 | ) -> Option<T> { | 234 | ) -> Option<T> { |
221 | let krate = match def_crate(db, krate, &ty.value) { | 235 | for krate in def_crates(db, krate, &ty.value)? { |
222 | Some(krate) => krate, | 236 | let impls = db.impls_in_crate(krate); |
223 | None => return None, | ||
224 | }; | ||
225 | let impls = db.impls_in_crate(krate); | ||
226 | 237 | ||
227 | for impl_block in impls.lookup_impl_blocks(&ty.value) { | 238 | for impl_block in impls.lookup_impl_blocks(&ty.value) { |
228 | for item in impl_block.items(db) { | 239 | for item in impl_block.items(db) { |
229 | if let ImplItem::Method(f) = item { | 240 | if let ImplItem::Method(f) = item { |
230 | let data = f.data(db); | 241 | let data = f.data(db); |
231 | if name.map_or(true, |name| data.name() == name) && data.has_self_param() { | 242 | if name.map_or(true, |name| data.name() == name) && data.has_self_param() { |
232 | if let Some(result) = callback(&ty.value, f) { | 243 | if let Some(result) = callback(&ty.value, f) { |
233 | return Some(result); | 244 | return Some(result); |
245 | } | ||
234 | } | 246 | } |
235 | } | 247 | } |
236 | } | 248 | } |
@@ -248,13 +260,14 @@ impl Ty { | |||
248 | krate: Crate, | 260 | krate: Crate, |
249 | mut callback: impl FnMut(ImplItem) -> Option<T>, | 261 | mut callback: impl FnMut(ImplItem) -> Option<T>, |
250 | ) -> Option<T> { | 262 | ) -> Option<T> { |
251 | let krate = def_crate(db, krate, &self)?; | 263 | for krate in def_crates(db, krate, &self)? { |
252 | let impls = db.impls_in_crate(krate); | 264 | let impls = db.impls_in_crate(krate); |
253 | 265 | ||
254 | for impl_block in impls.lookup_impl_blocks(&self) { | 266 | for impl_block in impls.lookup_impl_blocks(&self) { |
255 | for item in impl_block.items(db) { | 267 | for item in impl_block.items(db) { |
256 | if let Some(result) = callback(item) { | 268 | if let Some(result) = callback(item) { |
257 | return Some(result); | 269 | return Some(result); |
270 | } | ||
258 | } | 271 | } |
259 | } | 272 | } |
260 | } | 273 | } |
diff --git a/crates/ra_ide_api/src/change.rs b/crates/ra_ide_api/src/change.rs index 895b1e966..8d9918d16 100644 --- a/crates/ra_ide_api/src/change.rs +++ b/crates/ra_ide_api/src/change.rs | |||
@@ -163,7 +163,8 @@ impl RootDatabase { | |||
163 | if !change.new_roots.is_empty() { | 163 | if !change.new_roots.is_empty() { |
164 | let mut local_roots = Vec::clone(&self.local_roots()); | 164 | let mut local_roots = Vec::clone(&self.local_roots()); |
165 | for (root_id, is_local) in change.new_roots { | 165 | for (root_id, is_local) in change.new_roots { |
166 | self.set_source_root(root_id, Default::default()); | 166 | let root = if is_local { SourceRoot::new() } else { SourceRoot::new_library() }; |
167 | self.set_source_root(root_id, Arc::new(root)); | ||
167 | if is_local { | 168 | if is_local { |
168 | local_roots.push(root_id); | 169 | local_roots.push(root_id); |
169 | } | 170 | } |
diff --git a/crates/ra_ide_api/src/completion/complete_postfix.rs b/crates/ra_ide_api/src/completion/complete_postfix.rs index 278b1b797..e20a12e2a 100644 --- a/crates/ra_ide_api/src/completion/complete_postfix.rs +++ b/crates/ra_ide_api/src/completion/complete_postfix.rs | |||
@@ -51,6 +51,8 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { | |||
51 | ) | 51 | ) |
52 | .add_to(acc); | 52 | .add_to(acc); |
53 | postfix_snippet(ctx, "dbg", "dbg!(expr)", &format!("dbg!({})", receiver_text)).add_to(acc); | 53 | postfix_snippet(ctx, "dbg", "dbg!(expr)", &format!("dbg!({})", receiver_text)).add_to(acc); |
54 | postfix_snippet(ctx, "box", "Box::new(expr)", &format!("Box::new({})", receiver_text)) | ||
55 | .add_to(acc); | ||
54 | } | 56 | } |
55 | } | 57 | } |
56 | 58 | ||
diff --git a/crates/ra_ide_api/src/completion/snapshots/completion_item__postfix_completion_works_for_trivial_path_expression.snap b/crates/ra_ide_api/src/completion/snapshots/completion_item__postfix_completion_works_for_trivial_path_expression.snap index fcb292596..c1a40b7b4 100644 --- a/crates/ra_ide_api/src/completion/snapshots/completion_item__postfix_completion_works_for_trivial_path_expression.snap +++ b/crates/ra_ide_api/src/completion/snapshots/completion_item__postfix_completion_works_for_trivial_path_expression.snap | |||
@@ -1,11 +1,18 @@ | |||
1 | --- | 1 | --- |
2 | created: "2019-05-23T22:23:35.118738523Z" | 2 | created: "2019-06-23T13:01:08.775536006Z" |
3 | creator: [email protected] | 3 | creator: [email protected] |
4 | source: crates/ra_ide_api/src/completion/completion_item.rs | 4 | source: crates/ra_ide_api/src/completion/completion_item.rs |
5 | expression: kind_completions | 5 | expression: kind_completions |
6 | --- | 6 | --- |
7 | [ | 7 | [ |
8 | CompletionItem { | 8 | CompletionItem { |
9 | label: "box", | ||
10 | source_range: [76; 76), | ||
11 | delete: [72; 76), | ||
12 | insert: "Box::new(bar)", | ||
13 | detail: "Box::new(expr)", | ||
14 | }, | ||
15 | CompletionItem { | ||
9 | label: "dbg", | 16 | label: "dbg", |
10 | source_range: [76; 76), | 17 | source_range: [76; 76), |
11 | delete: [72; 76), | 18 | delete: [72; 76), |
diff --git a/crates/ra_parser/src/grammar/items.rs b/crates/ra_parser/src/grammar/items.rs index e85147e9e..424d0476d 100644 --- a/crates/ra_parser/src/grammar/items.rs +++ b/crates/ra_parser/src/grammar/items.rs | |||
@@ -103,7 +103,21 @@ pub(super) fn maybe_item(p: &mut Parser, m: Marker, flavor: ItemFlavor) -> Resul | |||
103 | p.bump_remap(T![auto]); | 103 | p.bump_remap(T![auto]); |
104 | has_mods = true; | 104 | has_mods = true; |
105 | } | 105 | } |
106 | if p.at(IDENT) && p.at_contextual_kw("default") && p.nth(1) == T![impl] { | 106 | |
107 | if p.at(IDENT) | ||
108 | && p.at_contextual_kw("default") | ||
109 | && (match p.nth(1) { | ||
110 | T![impl] => true, | ||
111 | T![fn] | T![type] => { | ||
112 | if let ItemFlavor::Mod = flavor { | ||
113 | true | ||
114 | } else { | ||
115 | false | ||
116 | } | ||
117 | } | ||
118 | _ => false, | ||
119 | }) | ||
120 | { | ||
107 | p.bump_remap(T![default]); | 121 | p.bump_remap(T![default]); |
108 | has_mods = true; | 122 | has_mods = true; |
109 | } | 123 | } |
@@ -163,12 +177,25 @@ pub(super) fn maybe_item(p: &mut Parser, m: Marker, flavor: ItemFlavor) -> Resul | |||
163 | // test default_impl | 177 | // test default_impl |
164 | // default impl Foo {} | 178 | // default impl Foo {} |
165 | 179 | ||
180 | // test_err default_fn_type | ||
181 | // trait T { | ||
182 | // default type T = Bar; | ||
183 | // default fn foo() {} | ||
184 | // } | ||
185 | |||
186 | // test default_fn_type | ||
187 | // impl T for Foo { | ||
188 | // default type T = Bar; | ||
189 | // default fn foo() {} | ||
190 | // } | ||
191 | |||
166 | // test unsafe_default_impl | 192 | // test unsafe_default_impl |
167 | // unsafe default impl Foo {} | 193 | // unsafe default impl Foo {} |
168 | T![impl] => { | 194 | T![impl] => { |
169 | traits::impl_block(p); | 195 | traits::impl_block(p); |
170 | m.complete(p, IMPL_BLOCK); | 196 | m.complete(p, IMPL_BLOCK); |
171 | } | 197 | } |
198 | |||
172 | // test existential_type | 199 | // test existential_type |
173 | // existential type Foo: Fn() -> usize; | 200 | // existential type Foo: Fn() -> usize; |
174 | T![type] => { | 201 | T![type] => { |
diff --git a/crates/ra_syntax/tests/data/parser/inline/err/0014_default_fn_type.rs b/crates/ra_syntax/tests/data/parser/inline/err/0014_default_fn_type.rs new file mode 100644 index 000000000..15ba8f4a8 --- /dev/null +++ b/crates/ra_syntax/tests/data/parser/inline/err/0014_default_fn_type.rs | |||
@@ -0,0 +1,4 @@ | |||
1 | trait T { | ||
2 | default type T = Bar; | ||
3 | default fn foo() {} | ||
4 | } | ||
diff --git a/crates/ra_syntax/tests/data/parser/inline/err/0014_default_fn_type.txt b/crates/ra_syntax/tests/data/parser/inline/err/0014_default_fn_type.txt new file mode 100644 index 000000000..7da4e243f --- /dev/null +++ b/crates/ra_syntax/tests/data/parser/inline/err/0014_default_fn_type.txt | |||
@@ -0,0 +1,58 @@ | |||
1 | SOURCE_FILE@[0; 62) | ||
2 | TRAIT_DEF@[0; 61) | ||
3 | TRAIT_KW@[0; 5) "trait" | ||
4 | WHITESPACE@[5; 6) " " | ||
5 | NAME@[6; 7) | ||
6 | IDENT@[6; 7) "T" | ||
7 | WHITESPACE@[7; 8) " " | ||
8 | ITEM_LIST@[8; 61) | ||
9 | L_CURLY@[8; 9) "{" | ||
10 | WHITESPACE@[9; 14) "\n " | ||
11 | MACRO_CALL@[14; 21) | ||
12 | PATH@[14; 21) | ||
13 | PATH_SEGMENT@[14; 21) | ||
14 | NAME_REF@[14; 21) | ||
15 | IDENT@[14; 21) "default" | ||
16 | WHITESPACE@[21; 22) " " | ||
17 | TYPE_ALIAS_DEF@[22; 35) | ||
18 | TYPE_KW@[22; 26) "type" | ||
19 | WHITESPACE@[26; 27) " " | ||
20 | NAME@[27; 28) | ||
21 | IDENT@[27; 28) "T" | ||
22 | WHITESPACE@[28; 29) " " | ||
23 | EQ@[29; 30) "=" | ||
24 | WHITESPACE@[30; 31) " " | ||
25 | PATH_TYPE@[31; 34) | ||
26 | PATH@[31; 34) | ||
27 | PATH_SEGMENT@[31; 34) | ||
28 | NAME_REF@[31; 34) | ||
29 | IDENT@[31; 34) "Bar" | ||
30 | SEMI@[34; 35) ";" | ||
31 | WHITESPACE@[35; 40) "\n " | ||
32 | MACRO_CALL@[40; 47) | ||
33 | PATH@[40; 47) | ||
34 | PATH_SEGMENT@[40; 47) | ||
35 | NAME_REF@[40; 47) | ||
36 | IDENT@[40; 47) "default" | ||
37 | WHITESPACE@[47; 48) " " | ||
38 | FN_DEF@[48; 59) | ||
39 | FN_KW@[48; 50) "fn" | ||
40 | WHITESPACE@[50; 51) " " | ||
41 | NAME@[51; 54) | ||
42 | IDENT@[51; 54) "foo" | ||
43 | PARAM_LIST@[54; 56) | ||
44 | L_PAREN@[54; 55) "(" | ||
45 | R_PAREN@[55; 56) ")" | ||
46 | WHITESPACE@[56; 57) " " | ||
47 | BLOCK@[57; 59) | ||
48 | L_CURLY@[57; 58) "{" | ||
49 | R_CURLY@[58; 59) "}" | ||
50 | WHITESPACE@[59; 60) "\n" | ||
51 | R_CURLY@[60; 61) "}" | ||
52 | WHITESPACE@[61; 62) "\n" | ||
53 | error 21: expected EXCL | ||
54 | error 21: expected `{`, `[`, `(` | ||
55 | error 21: expected SEMI | ||
56 | error 47: expected EXCL | ||
57 | error 47: expected `{`, `[`, `(` | ||
58 | error 47: expected SEMI | ||
diff --git a/crates/ra_syntax/tests/data/parser/inline/ok/0132_default_fn_type.rs b/crates/ra_syntax/tests/data/parser/inline/ok/0132_default_fn_type.rs new file mode 100644 index 000000000..8f5d61113 --- /dev/null +++ b/crates/ra_syntax/tests/data/parser/inline/ok/0132_default_fn_type.rs | |||
@@ -0,0 +1,4 @@ | |||
1 | impl T for Foo { | ||
2 | default type T = Bar; | ||
3 | default fn foo() {} | ||
4 | } | ||
diff --git a/crates/ra_syntax/tests/data/parser/inline/ok/0132_default_fn_type.txt b/crates/ra_syntax/tests/data/parser/inline/ok/0132_default_fn_type.txt new file mode 100644 index 000000000..384b203d3 --- /dev/null +++ b/crates/ra_syntax/tests/data/parser/inline/ok/0132_default_fn_type.txt | |||
@@ -0,0 +1,55 @@ | |||
1 | SOURCE_FILE@[0; 69) | ||
2 | IMPL_BLOCK@[0; 68) | ||
3 | IMPL_KW@[0; 4) "impl" | ||
4 | WHITESPACE@[4; 5) " " | ||
5 | PATH_TYPE@[5; 6) | ||
6 | PATH@[5; 6) | ||
7 | PATH_SEGMENT@[5; 6) | ||
8 | NAME_REF@[5; 6) | ||
9 | IDENT@[5; 6) "T" | ||
10 | WHITESPACE@[6; 7) " " | ||
11 | FOR_KW@[7; 10) "for" | ||
12 | WHITESPACE@[10; 11) " " | ||
13 | PATH_TYPE@[11; 14) | ||
14 | PATH@[11; 14) | ||
15 | PATH_SEGMENT@[11; 14) | ||
16 | NAME_REF@[11; 14) | ||
17 | IDENT@[11; 14) "Foo" | ||
18 | WHITESPACE@[14; 15) " " | ||
19 | ITEM_LIST@[15; 68) | ||
20 | L_CURLY@[15; 16) "{" | ||
21 | WHITESPACE@[16; 21) "\n " | ||
22 | TYPE_ALIAS_DEF@[21; 42) | ||
23 | DEFAULT_KW@[21; 28) "default" | ||
24 | WHITESPACE@[28; 29) " " | ||
25 | TYPE_KW@[29; 33) "type" | ||
26 | WHITESPACE@[33; 34) " " | ||
27 | NAME@[34; 35) | ||
28 | IDENT@[34; 35) "T" | ||
29 | WHITESPACE@[35; 36) " " | ||
30 | EQ@[36; 37) "=" | ||
31 | WHITESPACE@[37; 38) " " | ||
32 | PATH_TYPE@[38; 41) | ||
33 | PATH@[38; 41) | ||
34 | PATH_SEGMENT@[38; 41) | ||
35 | NAME_REF@[38; 41) | ||
36 | IDENT@[38; 41) "Bar" | ||
37 | SEMI@[41; 42) ";" | ||
38 | WHITESPACE@[42; 47) "\n " | ||
39 | FN_DEF@[47; 66) | ||
40 | DEFAULT_KW@[47; 54) "default" | ||
41 | WHITESPACE@[54; 55) " " | ||
42 | FN_KW@[55; 57) "fn" | ||
43 | WHITESPACE@[57; 58) " " | ||
44 | NAME@[58; 61) | ||
45 | IDENT@[58; 61) "foo" | ||
46 | PARAM_LIST@[61; 63) | ||
47 | L_PAREN@[61; 62) "(" | ||
48 | R_PAREN@[62; 63) ")" | ||
49 | WHITESPACE@[63; 64) " " | ||
50 | BLOCK@[64; 66) | ||
51 | L_CURLY@[64; 65) "{" | ||
52 | R_CURLY@[65; 66) "}" | ||
53 | WHITESPACE@[66; 67) "\n" | ||
54 | R_CURLY@[67; 68) "}" | ||
55 | WHITESPACE@[68; 69) "\n" | ||
diff --git a/editors/code/package.json b/editors/code/package.json index c2ed8d126..ac2ba82e3 100644 --- a/editors/code/package.json +++ b/editors/code/package.json | |||
@@ -201,11 +201,16 @@ | |||
201 | ], | 201 | ], |
202 | "description": "Whether to run `cargo watch` on startup" | 202 | "description": "Whether to run `cargo watch` on startup" |
203 | }, | 203 | }, |
204 | "rust-analyzer.cargo-watch.check-arguments": { | 204 | "rust-analyzer.cargo-watch.arguments": { |
205 | "type": "string", | 205 | "type": "string", |
206 | "description": "`cargo-watch` check arguments. (e.g: `--features=\"shumway,pdf\"` will run as `cargo watch -x \"check --features=\"shumway,pdf\"\"` )", | 206 | "description": "`cargo-watch` arguments. (e.g: `--features=\"shumway,pdf\"` will run as `cargo watch -x \"check --features=\"shumway,pdf\"\"` )", |
207 | "default": "" | 207 | "default": "" |
208 | }, | 208 | }, |
209 | "rust-analyzer.cargo-watch.command": { | ||
210 | "type": "string", | ||
211 | "description": "`cargo-watch` command. (e.g: `clippy` will run as `cargo watch -x clippy` )", | ||
212 | "default": "check" | ||
213 | }, | ||
209 | "rust-analyzer.trace.server": { | 214 | "rust-analyzer.trace.server": { |
210 | "type": "string", | 215 | "type": "string", |
211 | "scope": "window", | 216 | "scope": "window", |
diff --git a/editors/code/src/commands/cargo_watch.ts b/editors/code/src/commands/cargo_watch.ts index 6ba794bb3..126a8b1b3 100644 --- a/editors/code/src/commands/cargo_watch.ts +++ b/editors/code/src/commands/cargo_watch.ts | |||
@@ -4,6 +4,10 @@ import * as path from 'path'; | |||
4 | import * as vscode from 'vscode'; | 4 | import * as vscode from 'vscode'; |
5 | import { Server } from '../server'; | 5 | import { Server } from '../server'; |
6 | import { terminate } from '../utils/processes'; | 6 | import { terminate } from '../utils/processes'; |
7 | import { | ||
8 | mapRustDiagnosticToVsCode, | ||
9 | RustDiagnostic | ||
10 | } from '../utils/rust_diagnostics'; | ||
7 | import { LineBuffer } from './line_buffer'; | 11 | import { LineBuffer } from './line_buffer'; |
8 | import { StatusDisplay } from './watch_status'; | 12 | import { StatusDisplay } from './watch_status'; |
9 | 13 | ||
@@ -33,20 +37,39 @@ export function registerCargoWatchProvider( | |||
33 | return provider; | 37 | return provider; |
34 | } | 38 | } |
35 | 39 | ||
36 | export class CargoWatchProvider implements vscode.Disposable { | 40 | export class CargoWatchProvider |
41 | implements vscode.Disposable, vscode.CodeActionProvider { | ||
37 | private readonly diagnosticCollection: vscode.DiagnosticCollection; | 42 | private readonly diagnosticCollection: vscode.DiagnosticCollection; |
38 | private readonly statusDisplay: StatusDisplay; | 43 | private readonly statusDisplay: StatusDisplay; |
39 | private readonly outputChannel: vscode.OutputChannel; | 44 | private readonly outputChannel: vscode.OutputChannel; |
45 | |||
46 | private codeActions: { | ||
47 | [fileUri: string]: vscode.CodeAction[]; | ||
48 | }; | ||
49 | private readonly codeActionDispose: vscode.Disposable; | ||
50 | |||
40 | private cargoProcess?: child_process.ChildProcess; | 51 | private cargoProcess?: child_process.ChildProcess; |
41 | 52 | ||
42 | constructor() { | 53 | constructor() { |
43 | this.diagnosticCollection = vscode.languages.createDiagnosticCollection( | 54 | this.diagnosticCollection = vscode.languages.createDiagnosticCollection( |
44 | 'rustc' | 55 | 'rustc' |
45 | ); | 56 | ); |
46 | this.statusDisplay = new StatusDisplay(); | 57 | this.statusDisplay = new StatusDisplay( |
58 | Server.config.cargoWatchOptions.command | ||
59 | ); | ||
47 | this.outputChannel = vscode.window.createOutputChannel( | 60 | this.outputChannel = vscode.window.createOutputChannel( |
48 | 'Cargo Watch Trace' | 61 | 'Cargo Watch Trace' |
49 | ); | 62 | ); |
63 | |||
64 | // Register code actions for rustc's suggested fixes | ||
65 | this.codeActions = {}; | ||
66 | this.codeActionDispose = vscode.languages.registerCodeActionsProvider( | ||
67 | [{ scheme: 'file', language: 'rust' }], | ||
68 | this, | ||
69 | { | ||
70 | providedCodeActionKinds: [vscode.CodeActionKind.QuickFix] | ||
71 | } | ||
72 | ); | ||
50 | } | 73 | } |
51 | 74 | ||
52 | public start() { | 75 | public start() { |
@@ -57,10 +80,12 @@ export class CargoWatchProvider implements vscode.Disposable { | |||
57 | return; | 80 | return; |
58 | } | 81 | } |
59 | 82 | ||
60 | let args = 'check --all-targets --message-format json'; | 83 | let args = |
61 | if (Server.config.cargoWatchOptions.checkArguments.length > 0) { | 84 | Server.config.cargoWatchOptions.command + |
85 | ' --all-targets --message-format json'; | ||
86 | if (Server.config.cargoWatchOptions.command.length > 0) { | ||
62 | // Excape the double quote string: | 87 | // Excape the double quote string: |
63 | args += ' ' + Server.config.cargoWatchOptions.checkArguments; | 88 | args += ' ' + Server.config.cargoWatchOptions.arguments; |
64 | } | 89 | } |
65 | // Windows handles arguments differently than the unix-likes, so we need to wrap the args in double quotes | 90 | // Windows handles arguments differently than the unix-likes, so we need to wrap the args in double quotes |
66 | if (process.platform === 'win32') { | 91 | if (process.platform === 'win32') { |
@@ -123,6 +148,14 @@ export class CargoWatchProvider implements vscode.Disposable { | |||
123 | this.diagnosticCollection.dispose(); | 148 | this.diagnosticCollection.dispose(); |
124 | this.outputChannel.dispose(); | 149 | this.outputChannel.dispose(); |
125 | this.statusDisplay.dispose(); | 150 | this.statusDisplay.dispose(); |
151 | this.codeActionDispose.dispose(); | ||
152 | } | ||
153 | |||
154 | public provideCodeActions( | ||
155 | document: vscode.TextDocument | ||
156 | ): vscode.ProviderResult<Array<vscode.Command | vscode.CodeAction>> { | ||
157 | const documentActions = this.codeActions[document.uri.toString()]; | ||
158 | return documentActions || []; | ||
126 | } | 159 | } |
127 | 160 | ||
128 | private logInfo(line: string) { | 161 | private logInfo(line: string) { |
@@ -143,6 +176,7 @@ export class CargoWatchProvider implements vscode.Disposable { | |||
143 | private parseLine(line: string) { | 176 | private parseLine(line: string) { |
144 | if (line.startsWith('[Running')) { | 177 | if (line.startsWith('[Running')) { |
145 | this.diagnosticCollection.clear(); | 178 | this.diagnosticCollection.clear(); |
179 | this.codeActions = {}; | ||
146 | this.statusDisplay.show(); | 180 | this.statusDisplay.show(); |
147 | } | 181 | } |
148 | 182 | ||
@@ -150,34 +184,65 @@ export class CargoWatchProvider implements vscode.Disposable { | |||
150 | this.statusDisplay.hide(); | 184 | this.statusDisplay.hide(); |
151 | } | 185 | } |
152 | 186 | ||
153 | function getLevel(s: string): vscode.DiagnosticSeverity { | 187 | function areDiagnosticsEqual( |
154 | if (s === 'error') { | 188 | left: vscode.Diagnostic, |
155 | return vscode.DiagnosticSeverity.Error; | 189 | right: vscode.Diagnostic |
190 | ): boolean { | ||
191 | return ( | ||
192 | left.source === right.source && | ||
193 | left.severity === right.severity && | ||
194 | left.range.isEqual(right.range) && | ||
195 | left.message === right.message | ||
196 | ); | ||
197 | } | ||
198 | |||
199 | function areCodeActionsEqual( | ||
200 | left: vscode.CodeAction, | ||
201 | right: vscode.CodeAction | ||
202 | ): boolean { | ||
203 | if ( | ||
204 | left.kind !== right.kind || | ||
205 | left.title !== right.title || | ||
206 | !left.edit || | ||
207 | !right.edit | ||
208 | ) { | ||
209 | return false; | ||
156 | } | 210 | } |
157 | if (s.startsWith('warn')) { | 211 | |
158 | return vscode.DiagnosticSeverity.Warning; | 212 | const leftEditEntries = left.edit.entries(); |
213 | const rightEditEntries = right.edit.entries(); | ||
214 | |||
215 | if (leftEditEntries.length !== rightEditEntries.length) { | ||
216 | return false; | ||
159 | } | 217 | } |
160 | return vscode.DiagnosticSeverity.Information; | ||
161 | } | ||
162 | 218 | ||
163 | // Reference: | 219 | for (let i = 0; i < leftEditEntries.length; i++) { |
164 | // https://github.com/rust-lang/rust/blob/master/src/libsyntax/json.rs | 220 | const [leftUri, leftEdits] = leftEditEntries[i]; |
165 | interface RustDiagnosticSpan { | 221 | const [rightUri, rightEdits] = rightEditEntries[i]; |
166 | line_start: number; | 222 | |
167 | line_end: number; | 223 | if (leftUri.toString() !== rightUri.toString()) { |
168 | column_start: number; | 224 | return false; |
169 | column_end: number; | 225 | } |
170 | is_primary: boolean; | 226 | |
171 | file_name: string; | 227 | if (leftEdits.length !== rightEdits.length) { |
172 | } | 228 | return false; |
229 | } | ||
230 | |||
231 | for (let j = 0; j < leftEdits.length; j++) { | ||
232 | const leftEdit = leftEdits[j]; | ||
233 | const rightEdit = rightEdits[j]; | ||
234 | |||
235 | if (!leftEdit.range.isEqual(rightEdit.range)) { | ||
236 | return false; | ||
237 | } | ||
173 | 238 | ||
174 | interface RustDiagnostic { | 239 | if (leftEdit.newText !== rightEdit.newText) { |
175 | spans: RustDiagnosticSpan[]; | 240 | return false; |
176 | rendered: string; | 241 | } |
177 | level: string; | 242 | } |
178 | code?: { | 243 | } |
179 | code: string; | 244 | |
180 | }; | 245 | return true; |
181 | } | 246 | } |
182 | 247 | ||
183 | interface CargoArtifact { | 248 | interface CargoArtifact { |
@@ -211,41 +276,58 @@ export class CargoWatchProvider implements vscode.Disposable { | |||
211 | } else if (data.reason === 'compiler-message') { | 276 | } else if (data.reason === 'compiler-message') { |
212 | const msg = data.message as RustDiagnostic; | 277 | const msg = data.message as RustDiagnostic; |
213 | 278 | ||
214 | const spans = msg.spans.filter(o => o.is_primary); | 279 | const mapResult = mapRustDiagnosticToVsCode(msg); |
215 | 280 | if (!mapResult) { | |
216 | // We only handle primary span right now. | 281 | return; |
217 | if (spans.length > 0) { | 282 | } |
218 | const o = spans[0]; | ||
219 | 283 | ||
220 | const rendered = msg.rendered; | 284 | const { location, diagnostic, codeActions } = mapResult; |
221 | const level = getLevel(msg.level); | 285 | const fileUri = location.uri; |
222 | const range = new vscode.Range( | ||
223 | new vscode.Position(o.line_start - 1, o.column_start - 1), | ||
224 | new vscode.Position(o.line_end - 1, o.column_end - 1) | ||
225 | ); | ||
226 | 286 | ||
227 | const fileName = path.join( | 287 | const diagnostics: vscode.Diagnostic[] = [ |
228 | vscode.workspace.rootPath!, | 288 | ...(this.diagnosticCollection!.get(fileUri) || []) |
229 | o.file_name | 289 | ]; |
230 | ); | ||
231 | const diagnostic = new vscode.Diagnostic( | ||
232 | range, | ||
233 | rendered, | ||
234 | level | ||
235 | ); | ||
236 | 290 | ||
237 | diagnostic.source = 'rustc'; | 291 | // If we're building multiple targets it's possible we've already seen this diagnostic |
238 | diagnostic.code = msg.code ? msg.code.code : undefined; | 292 | const isDuplicate = diagnostics.some(d => |
239 | diagnostic.relatedInformation = []; | 293 | areDiagnosticsEqual(d, diagnostic) |
294 | ); | ||
240 | 295 | ||
241 | const fileUrl = vscode.Uri.file(fileName!); | 296 | if (isDuplicate) { |
297 | return; | ||
298 | } | ||
242 | 299 | ||
243 | const diagnostics: vscode.Diagnostic[] = [ | 300 | diagnostics.push(diagnostic); |
244 | ...(this.diagnosticCollection!.get(fileUrl) || []) | 301 | this.diagnosticCollection!.set(fileUri, diagnostics); |
245 | ]; | 302 | |
246 | diagnostics.push(diagnostic); | 303 | if (codeActions.length) { |
304 | const fileUriString = fileUri.toString(); | ||
305 | const existingActions = this.codeActions[fileUriString] || []; | ||
306 | |||
307 | for (const newAction of codeActions) { | ||
308 | const existingAction = existingActions.find(existing => | ||
309 | areCodeActionsEqual(existing, newAction) | ||
310 | ); | ||
311 | |||
312 | if (existingAction) { | ||
313 | if (!existingAction.diagnostics) { | ||
314 | existingAction.diagnostics = []; | ||
315 | } | ||
316 | // This action also applies to this diagnostic | ||
317 | existingAction.diagnostics.push(diagnostic); | ||
318 | } else { | ||
319 | newAction.diagnostics = [diagnostic]; | ||
320 | existingActions.push(newAction); | ||
321 | } | ||
322 | } | ||
247 | 323 | ||
248 | this.diagnosticCollection!.set(fileUrl, diagnostics); | 324 | // Have VsCode query us for the code actions |
325 | this.codeActions[fileUriString] = existingActions; | ||
326 | vscode.commands.executeCommand( | ||
327 | 'vscode.executeCodeActionProvider', | ||
328 | fileUri, | ||
329 | diagnostic.range | ||
330 | ); | ||
249 | } | 331 | } |
250 | } | 332 | } |
251 | } | 333 | } |
diff --git a/editors/code/src/commands/watch_status.ts b/editors/code/src/commands/watch_status.ts index a3b0178f2..6c1f9041b 100644 --- a/editors/code/src/commands/watch_status.ts +++ b/editors/code/src/commands/watch_status.ts | |||
@@ -7,13 +7,15 @@ export class StatusDisplay implements vscode.Disposable { | |||
7 | 7 | ||
8 | private i = 0; | 8 | private i = 0; |
9 | private statusBarItem: vscode.StatusBarItem; | 9 | private statusBarItem: vscode.StatusBarItem; |
10 | private command: string; | ||
10 | private timer?: NodeJS.Timeout; | 11 | private timer?: NodeJS.Timeout; |
11 | 12 | ||
12 | constructor() { | 13 | constructor(command: string) { |
13 | this.statusBarItem = vscode.window.createStatusBarItem( | 14 | this.statusBarItem = vscode.window.createStatusBarItem( |
14 | vscode.StatusBarAlignment.Left, | 15 | vscode.StatusBarAlignment.Left, |
15 | 10 | 16 | 10 |
16 | ); | 17 | ); |
18 | this.command = command; | ||
17 | this.statusBarItem.hide(); | 19 | this.statusBarItem.hide(); |
18 | } | 20 | } |
19 | 21 | ||
@@ -24,11 +26,13 @@ export class StatusDisplay implements vscode.Disposable { | |||
24 | this.timer || | 26 | this.timer || |
25 | setInterval(() => { | 27 | setInterval(() => { |
26 | if (this.packageName) { | 28 | if (this.packageName) { |
27 | this.statusBarItem!.text = `cargo check [${ | 29 | this.statusBarItem!.text = `cargo ${this.command} [${ |
28 | this.packageName | 30 | this.packageName |
29 | }] ${this.frame()}`; | 31 | }] ${this.frame()}`; |
30 | } else { | 32 | } else { |
31 | this.statusBarItem!.text = `cargo check ${this.frame()}`; | 33 | this.statusBarItem!.text = `cargo ${ |
34 | this.command | ||
35 | } ${this.frame()}`; | ||
32 | } | 36 | } |
33 | }, 300); | 37 | }, 300); |
34 | 38 | ||
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index 3024546d2..10e98d753 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts | |||
@@ -1,5 +1,6 @@ | |||
1 | import * as vscode from 'vscode'; | 1 | import * as vscode from 'vscode'; |
2 | 2 | ||
3 | import { strict } from 'assert'; | ||
3 | import { Server } from './server'; | 4 | import { Server } from './server'; |
4 | 5 | ||
5 | const RA_LSP_DEBUG = process.env.__RA_LSP_SERVER_DEBUG; | 6 | const RA_LSP_DEBUG = process.env.__RA_LSP_SERVER_DEBUG; |
@@ -9,7 +10,8 @@ export type CargoWatchTraceOptions = 'off' | 'error' | 'verbose'; | |||
9 | 10 | ||
10 | export interface CargoWatchOptions { | 11 | export interface CargoWatchOptions { |
11 | enableOnStartup: CargoWatchStartupOptions; | 12 | enableOnStartup: CargoWatchStartupOptions; |
12 | checkArguments: string; | 13 | arguments: string; |
14 | command: string; | ||
13 | trace: CargoWatchTraceOptions; | 15 | trace: CargoWatchTraceOptions; |
14 | } | 16 | } |
15 | 17 | ||
@@ -23,7 +25,8 @@ export class Config { | |||
23 | public cargoWatchOptions: CargoWatchOptions = { | 25 | public cargoWatchOptions: CargoWatchOptions = { |
24 | enableOnStartup: 'ask', | 26 | enableOnStartup: 'ask', |
25 | trace: 'off', | 27 | trace: 'off', |
26 | checkArguments: '' | 28 | arguments: '', |
29 | command: '' | ||
27 | }; | 30 | }; |
28 | 31 | ||
29 | private prevEnhancedTyping: null | boolean = null; | 32 | private prevEnhancedTyping: null | boolean = null; |
@@ -104,12 +107,20 @@ export class Config { | |||
104 | ); | 107 | ); |
105 | } | 108 | } |
106 | 109 | ||
107 | if (config.has('cargo-watch.check-arguments')) { | 110 | if (config.has('cargo-watch.arguments')) { |
108 | this.cargoWatchOptions.checkArguments = config.get<string>( | 111 | this.cargoWatchOptions.arguments = config.get<string>( |
109 | 'cargo-watch.check-arguments', | 112 | 'cargo-watch.arguments', |
110 | '' | 113 | '' |
111 | ); | 114 | ); |
112 | } | 115 | } |
116 | |||
117 | if (config.has('cargo-watch.command')) { | ||
118 | this.cargoWatchOptions.command = config.get<string>( | ||
119 | 'cargo-watch.command', | ||
120 | '' | ||
121 | ); | ||
122 | } | ||
123 | |||
113 | if (config.has('lruCapacity')) { | 124 | if (config.has('lruCapacity')) { |
114 | this.lruCapacity = config.get('lruCapacity') as number; | 125 | this.lruCapacity = config.get('lruCapacity') as number; |
115 | } | 126 | } |
diff --git a/editors/code/src/utils/rust_diagnostics.ts b/editors/code/src/utils/rust_diagnostics.ts new file mode 100644 index 000000000..ed049c95e --- /dev/null +++ b/editors/code/src/utils/rust_diagnostics.ts | |||
@@ -0,0 +1,226 @@ | |||
1 | import * as path from 'path'; | ||
2 | import * as vscode from 'vscode'; | ||
3 | |||
4 | // Reference: | ||
5 | // https://github.com/rust-lang/rust/blob/master/src/libsyntax/json.rs | ||
6 | export interface RustDiagnosticSpan { | ||
7 | line_start: number; | ||
8 | line_end: number; | ||
9 | column_start: number; | ||
10 | column_end: number; | ||
11 | is_primary: boolean; | ||
12 | file_name: string; | ||
13 | label?: string; | ||
14 | suggested_replacement?: string; | ||
15 | suggestion_applicability?: | ||
16 | | 'MachineApplicable' | ||
17 | | 'HasPlaceholders' | ||
18 | | 'MaybeIncorrect' | ||
19 | | 'Unspecified'; | ||
20 | } | ||
21 | |||
22 | export interface RustDiagnostic { | ||
23 | spans: RustDiagnosticSpan[]; | ||
24 | rendered: string; | ||
25 | message: string; | ||
26 | level: string; | ||
27 | code?: { | ||
28 | code: string; | ||
29 | }; | ||
30 | children: RustDiagnostic[]; | ||
31 | } | ||
32 | |||
33 | export interface MappedRustDiagnostic { | ||
34 | location: vscode.Location; | ||
35 | diagnostic: vscode.Diagnostic; | ||
36 | codeActions: vscode.CodeAction[]; | ||
37 | } | ||
38 | |||
39 | interface MappedRustChildDiagnostic { | ||
40 | related?: vscode.DiagnosticRelatedInformation; | ||
41 | codeAction?: vscode.CodeAction; | ||
42 | messageLine?: string; | ||
43 | } | ||
44 | |||
45 | /** | ||
46 | * Converts a Rust level string to a VsCode severity | ||
47 | */ | ||
48 | function mapLevelToSeverity(s: string): vscode.DiagnosticSeverity { | ||
49 | if (s === 'error') { | ||
50 | return vscode.DiagnosticSeverity.Error; | ||
51 | } | ||
52 | if (s.startsWith('warn')) { | ||
53 | return vscode.DiagnosticSeverity.Warning; | ||
54 | } | ||
55 | return vscode.DiagnosticSeverity.Information; | ||
56 | } | ||
57 | |||
58 | /** | ||
59 | * Converts a Rust span to a VsCode location | ||
60 | */ | ||
61 | function mapSpanToLocation(span: RustDiagnosticSpan): vscode.Location { | ||
62 | const fileName = path.join(vscode.workspace.rootPath!, span.file_name); | ||
63 | const fileUri = vscode.Uri.file(fileName); | ||
64 | |||
65 | const range = new vscode.Range( | ||
66 | new vscode.Position(span.line_start - 1, span.column_start - 1), | ||
67 | new vscode.Position(span.line_end - 1, span.column_end - 1) | ||
68 | ); | ||
69 | |||
70 | return new vscode.Location(fileUri, range); | ||
71 | } | ||
72 | |||
73 | /** | ||
74 | * Converts a secondary Rust span to a VsCode related information | ||
75 | * | ||
76 | * If the span is unlabelled this will return `undefined`. | ||
77 | */ | ||
78 | function mapSecondarySpanToRelated( | ||
79 | span: RustDiagnosticSpan | ||
80 | ): vscode.DiagnosticRelatedInformation | undefined { | ||
81 | if (!span.label) { | ||
82 | // Nothing to label this with | ||
83 | return; | ||
84 | } | ||
85 | |||
86 | const location = mapSpanToLocation(span); | ||
87 | return new vscode.DiagnosticRelatedInformation(location, span.label); | ||
88 | } | ||
89 | |||
90 | /** | ||
91 | * Determines if diagnostic is related to unused code | ||
92 | */ | ||
93 | function isUnusedOrUnnecessary(rd: RustDiagnostic): boolean { | ||
94 | if (!rd.code) { | ||
95 | return false; | ||
96 | } | ||
97 | |||
98 | return [ | ||
99 | 'dead_code', | ||
100 | 'unknown_lints', | ||
101 | 'unused_attributes', | ||
102 | 'unused_imports', | ||
103 | 'unused_macros', | ||
104 | 'unused_variables' | ||
105 | ].includes(rd.code.code); | ||
106 | } | ||
107 | |||
108 | /** | ||
109 | * Converts a Rust child diagnostic to a VsCode related information | ||
110 | * | ||
111 | * This can have three outcomes: | ||
112 | * | ||
113 | * 1. If this is no primary span this will return a `noteLine` | ||
114 | * 2. If there is a primary span with a suggested replacement it will return a | ||
115 | * `codeAction`. | ||
116 | * 3. If there is a primary span without a suggested replacement it will return | ||
117 | * a `related`. | ||
118 | */ | ||
119 | function mapRustChildDiagnostic(rd: RustDiagnostic): MappedRustChildDiagnostic { | ||
120 | const span = rd.spans.find(s => s.is_primary); | ||
121 | |||
122 | if (!span) { | ||
123 | // `rustc` uses these spanless children as a way to print multi-line | ||
124 | // messages | ||
125 | return { messageLine: rd.message }; | ||
126 | } | ||
127 | |||
128 | // If we have a primary span use its location, otherwise use the parent | ||
129 | const location = mapSpanToLocation(span); | ||
130 | |||
131 | // We need to distinguish `null` from an empty string | ||
132 | if (span && typeof span.suggested_replacement === 'string') { | ||
133 | const edit = new vscode.WorkspaceEdit(); | ||
134 | edit.replace(location.uri, location.range, span.suggested_replacement); | ||
135 | |||
136 | // Include our replacement in the label unless it's empty | ||
137 | const title = span.suggested_replacement | ||
138 | ? `${rd.message}: \`${span.suggested_replacement}\`` | ||
139 | : rd.message; | ||
140 | |||
141 | const codeAction = new vscode.CodeAction( | ||
142 | title, | ||
143 | vscode.CodeActionKind.QuickFix | ||
144 | ); | ||
145 | |||
146 | codeAction.edit = edit; | ||
147 | codeAction.isPreferred = | ||
148 | span.suggestion_applicability === 'MachineApplicable'; | ||
149 | |||
150 | return { codeAction }; | ||
151 | } else { | ||
152 | const related = new vscode.DiagnosticRelatedInformation( | ||
153 | location, | ||
154 | rd.message | ||
155 | ); | ||
156 | |||
157 | return { related }; | ||
158 | } | ||
159 | } | ||
160 | |||
161 | /** | ||
162 | * Converts a Rust root diagnostic to VsCode form | ||
163 | * | ||
164 | * This flattens the Rust diagnostic by: | ||
165 | * | ||
166 | * 1. Creating a `vscode.Diagnostic` with the root message and primary span. | ||
167 | * 2. Adding any labelled secondary spans to `relatedInformation` | ||
168 | * 3. Categorising child diagnostics as either Quick Fix actions, | ||
169 | * `relatedInformation` or additional message lines. | ||
170 | * | ||
171 | * If the diagnostic has no primary span this will return `undefined` | ||
172 | */ | ||
173 | export function mapRustDiagnosticToVsCode( | ||
174 | rd: RustDiagnostic | ||
175 | ): MappedRustDiagnostic | undefined { | ||
176 | const codeActions = []; | ||
177 | |||
178 | const primarySpan = rd.spans.find(s => s.is_primary); | ||
179 | if (!primarySpan) { | ||
180 | return; | ||
181 | } | ||
182 | |||
183 | const location = mapSpanToLocation(primarySpan); | ||
184 | const secondarySpans = rd.spans.filter(s => !s.is_primary); | ||
185 | |||
186 | const severity = mapLevelToSeverity(rd.level); | ||
187 | |||
188 | const vd = new vscode.Diagnostic(location.range, rd.message, severity); | ||
189 | |||
190 | vd.source = 'rustc'; | ||
191 | vd.code = rd.code ? rd.code.code : undefined; | ||
192 | vd.relatedInformation = []; | ||
193 | |||
194 | for (const secondarySpan of secondarySpans) { | ||
195 | const related = mapSecondarySpanToRelated(secondarySpan); | ||
196 | if (related) { | ||
197 | vd.relatedInformation.push(related); | ||
198 | } | ||
199 | } | ||
200 | |||
201 | for (const child of rd.children) { | ||
202 | const { related, codeAction, messageLine } = mapRustChildDiagnostic( | ||
203 | child | ||
204 | ); | ||
205 | |||
206 | if (related) { | ||
207 | vd.relatedInformation.push(related); | ||
208 | } | ||
209 | if (codeAction) { | ||
210 | codeActions.push(codeAction); | ||
211 | } | ||
212 | if (messageLine) { | ||
213 | vd.message += `\n${messageLine}`; | ||
214 | } | ||
215 | } | ||
216 | |||
217 | if (isUnusedOrUnnecessary(rd)) { | ||
218 | vd.tags = [vscode.DiagnosticTag.Unnecessary]; | ||
219 | } | ||
220 | |||
221 | return { | ||
222 | location, | ||
223 | diagnostic: vd, | ||
224 | codeActions | ||
225 | }; | ||
226 | } | ||