aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_db/src/input.rs14
-rw-r--r--crates/ra_hir/src/ty/method_resolution.rs63
-rw-r--r--crates/ra_ide_api/src/change.rs3
-rw-r--r--crates/ra_ide_api/src/completion/complete_postfix.rs2
-rw-r--r--crates/ra_ide_api/src/completion/snapshots/completion_item__postfix_completion_works_for_trivial_path_expression.snap9
-rw-r--r--crates/ra_parser/src/grammar/items.rs29
-rw-r--r--crates/ra_syntax/tests/data/parser/inline/err/0014_default_fn_type.rs4
-rw-r--r--crates/ra_syntax/tests/data/parser/inline/err/0014_default_fn_type.txt58
-rw-r--r--crates/ra_syntax/tests/data/parser/inline/ok/0132_default_fn_type.rs4
-rw-r--r--crates/ra_syntax/tests/data/parser/inline/ok/0132_default_fn_type.txt55
-rw-r--r--editors/code/package.json9
-rw-r--r--editors/code/src/commands/cargo_watch.ts198
-rw-r--r--editors/code/src/commands/watch_status.ts10
-rw-r--r--editors/code/src/config.ts21
-rw-r--r--editors/code/src/utils/rust_diagnostics.ts226
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)]
33pub struct SourceRoot { 33pub 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
42impl 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.
5use std::sync::Arc; 5use std::sync::Arc;
6 6
7use arrayvec::ArrayVec;
7use rustc_hash::FxHashMap; 8use rustc_hash::FxHashMap;
8 9
9use crate::{ 10use crate::{
@@ -113,19 +114,32 @@ impl CrateImplBlocks {
113 } 114 }
114} 115}
115 116
116fn def_crate(db: &impl HirDatabase, cur_crate: Crate, ty: &Ty) -> Option<Crate> { 117fn 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---
2created: "2019-05-23T22:23:35.118738523Z" 2created: "2019-06-23T13:01:08.775536006Z"
3creator: [email protected] 3creator: [email protected]
4source: crates/ra_ide_api/src/completion/completion_item.rs 4source: crates/ra_ide_api/src/completion/completion_item.rs
5expression: kind_completions 5expression: 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 @@
1trait 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 @@
1SOURCE_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"
53error 21: expected EXCL
54error 21: expected `{`, `[`, `(`
55error 21: expected SEMI
56error 47: expected EXCL
57error 47: expected `{`, `[`, `(`
58error 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 @@
1impl 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 @@
1SOURCE_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';
4import * as vscode from 'vscode'; 4import * as vscode from 'vscode';
5import { Server } from '../server'; 5import { Server } from '../server';
6import { terminate } from '../utils/processes'; 6import { terminate } from '../utils/processes';
7import {
8 mapRustDiagnosticToVsCode,
9 RustDiagnostic
10} from '../utils/rust_diagnostics';
7import { LineBuffer } from './line_buffer'; 11import { LineBuffer } from './line_buffer';
8import { StatusDisplay } from './watch_status'; 12import { StatusDisplay } from './watch_status';
9 13
@@ -33,20 +37,39 @@ export function registerCargoWatchProvider(
33 return provider; 37 return provider;
34} 38}
35 39
36export class CargoWatchProvider implements vscode.Disposable { 40export 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 @@
1import * as vscode from 'vscode'; 1import * as vscode from 'vscode';
2 2
3import { strict } from 'assert';
3import { Server } from './server'; 4import { Server } from './server';
4 5
5const RA_LSP_DEBUG = process.env.__RA_LSP_SERVER_DEBUG; 6const RA_LSP_DEBUG = process.env.__RA_LSP_SERVER_DEBUG;
@@ -9,7 +10,8 @@ export type CargoWatchTraceOptions = 'off' | 'error' | 'verbose';
9 10
10export interface CargoWatchOptions { 11export 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 @@
1import * as path from 'path';
2import * as vscode from 'vscode';
3
4// Reference:
5// https://github.com/rust-lang/rust/blob/master/src/libsyntax/json.rs
6export 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
22export 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
33export interface MappedRustDiagnostic {
34 location: vscode.Location;
35 diagnostic: vscode.Diagnostic;
36 codeActions: vscode.CodeAction[];
37}
38
39interface 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 */
48function 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 */
61function 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 */
78function 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 */
93function 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 */
119function 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 */
173export 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}