aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock1
-rw-r--r--crates/ra_hir/src/code_model.rs8
-rw-r--r--crates/ra_hir_def/src/data.rs6
-rw-r--r--crates/ra_ide/src/display/navigation_target.rs66
-rw-r--r--crates/ra_ide/src/runnables.rs250
-rw-r--r--crates/ra_ide/src/snapshots/highlight_injection.html1
-rw-r--r--crates/ra_ide/src/snapshots/highlight_strings.html2
-rw-r--r--crates/ra_ide/src/snapshots/highlight_unsafe.html48
-rw-r--r--crates/ra_ide/src/snapshots/highlighting.html1
-rw-r--r--crates/ra_ide/src/snapshots/rainbow_highlighting.html1
-rw-r--r--crates/ra_ide/src/syntax_highlighting.rs25
-rw-r--r--crates/ra_ide/src/syntax_highlighting/html.rs1
-rw-r--r--crates/ra_ide/src/syntax_highlighting/tags.rs8
-rw-r--r--crates/ra_ide/src/syntax_highlighting/tests.rs32
-rw-r--r--crates/ra_parser/src/grammar.rs7
-rw-r--r--crates/ra_proc_macro_srv/Cargo.toml1
-rw-r--r--crates/ra_proc_macro_srv/src/tests/utils.rs3
-rw-r--r--crates/ra_project_model/src/cargo_workspace.rs2
-rw-r--r--crates/ra_syntax/src/ast/tokens.rs5
-rw-r--r--crates/rust-analyzer/src/caps.rs3
-rw-r--r--crates/rust-analyzer/src/cargo_target_spec.rs85
-rw-r--r--crates/rust-analyzer/src/config.rs2
-rw-r--r--crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable.snap2
-rw-r--r--crates/rust-analyzer/src/diagnostics/to_proto.rs3
-rw-r--r--crates/rust-analyzer/src/lsp_ext.rs30
-rw-r--r--crates/rust-analyzer/src/main_loop/handlers.rs162
-rw-r--r--crates/rust-analyzer/src/to_proto.rs40
-rw-r--r--crates/rust-analyzer/tests/heavy_tests/main.rs113
-rw-r--r--docs/dev/README.md105
-rw-r--r--docs/dev/lsp-extensions.md80
-rw-r--r--editors/code/package.json4
-rw-r--r--editors/code/src/commands.ts74
-rw-r--r--editors/code/src/debug.ts121
-rw-r--r--editors/code/src/lsp_ext.ts16
-rw-r--r--editors/code/src/run.ts96
-rw-r--r--editors/code/src/tasks.ts7
-rw-r--r--editors/code/src/toolchain.ts (renamed from editors/code/src/cargo.ts)117
-rw-r--r--editors/code/src/util.ts18
-rw-r--r--editors/code/tests/unit/launch_config.test.ts14
39 files changed, 1022 insertions, 538 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 9981a2e33..af27bfc85 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1119,6 +1119,7 @@ dependencies = [
1119 "memmap", 1119 "memmap",
1120 "ra_mbe", 1120 "ra_mbe",
1121 "ra_proc_macro", 1121 "ra_proc_macro",
1122 "ra_toolchain",
1122 "ra_tt", 1123 "ra_tt",
1123 "serde_derive", 1124 "serde_derive",
1124 "test_utils", 1125 "test_utils",
diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs
index e40aeffbc..4a06f3bcd 100644
--- a/crates/ra_hir/src/code_model.rs
+++ b/crates/ra_hir/src/code_model.rs
@@ -637,6 +637,10 @@ impl Function {
637 db.function_data(self.id).params.clone() 637 db.function_data(self.id).params.clone()
638 } 638 }
639 639
640 pub fn is_unsafe(self, db: &dyn HirDatabase) -> bool {
641 db.function_data(self.id).is_unsafe
642 }
643
640 pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) { 644 pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) {
641 let _p = profile("Function::diagnostics"); 645 let _p = profile("Function::diagnostics");
642 let infer = db.infer(self.id.into()); 646 let infer = db.infer(self.id.into());
@@ -1190,6 +1194,10 @@ impl Type {
1190 ) 1194 )
1191 } 1195 }
1192 1196
1197 pub fn is_raw_ptr(&self) -> bool {
1198 matches!(&self.ty.value, Ty::Apply(ApplicationTy { ctor: TypeCtor::RawPtr(..), .. }))
1199 }
1200
1193 pub fn contains_unknown(&self) -> bool { 1201 pub fn contains_unknown(&self) -> bool {
1194 return go(&self.ty.value); 1202 return go(&self.ty.value);
1195 1203
diff --git a/crates/ra_hir_def/src/data.rs b/crates/ra_hir_def/src/data.rs
index e2130d931..807195d25 100644
--- a/crates/ra_hir_def/src/data.rs
+++ b/crates/ra_hir_def/src/data.rs
@@ -34,6 +34,7 @@ pub struct FunctionData {
34 /// True if the first param is `self`. This is relevant to decide whether this 34 /// True if the first param is `self`. This is relevant to decide whether this
35 /// can be called as a method. 35 /// can be called as a method.
36 pub has_self_param: bool, 36 pub has_self_param: bool,
37 pub is_unsafe: bool,
37 pub visibility: RawVisibility, 38 pub visibility: RawVisibility,
38} 39}
39 40
@@ -85,11 +86,14 @@ impl FunctionData {
85 ret_type 86 ret_type
86 }; 87 };
87 88
89 let is_unsafe = src.value.unsafe_token().is_some();
90
88 let vis_default = RawVisibility::default_for_container(loc.container); 91 let vis_default = RawVisibility::default_for_container(loc.container);
89 let visibility = 92 let visibility =
90 RawVisibility::from_ast_with_default(db, vis_default, src.map(|s| s.visibility())); 93 RawVisibility::from_ast_with_default(db, vis_default, src.map(|s| s.visibility()));
91 94
92 let sig = FunctionData { name, params, ret_type, has_self_param, visibility, attrs }; 95 let sig =
96 FunctionData { name, params, ret_type, has_self_param, is_unsafe, visibility, attrs };
93 Arc::new(sig) 97 Arc::new(sig)
94 } 98 }
95} 99}
diff --git a/crates/ra_ide/src/display/navigation_target.rs b/crates/ra_ide/src/display/navigation_target.rs
index 5da28edd2..c7bb1e69f 100644
--- a/crates/ra_ide/src/display/navigation_target.rs
+++ b/crates/ra_ide/src/display/navigation_target.rs
@@ -92,15 +92,16 @@ impl NavigationTarget {
92 let name = module.name(db).map(|it| it.to_string().into()).unwrap_or_default(); 92 let name = module.name(db).map(|it| it.to_string().into()).unwrap_or_default();
93 if let Some(src) = module.declaration_source(db) { 93 if let Some(src) = module.declaration_source(db) {
94 let frange = original_range(db, src.as_ref().map(|it| it.syntax())); 94 let frange = original_range(db, src.as_ref().map(|it| it.syntax()));
95 return NavigationTarget::from_syntax( 95 let mut res = NavigationTarget::from_syntax(
96 frange.file_id, 96 frange.file_id,
97 name, 97 name,
98 None, 98 None,
99 frange.range, 99 frange.range,
100 src.value.syntax().kind(), 100 src.value.syntax().kind(),
101 src.value.doc_comment_text(),
102 src.value.short_label(),
103 ); 101 );
102 res.docs = src.value.doc_comment_text();
103 res.description = src.value.short_label();
104 return res;
104 } 105 }
105 module.to_nav(db) 106 module.to_nav(db)
106 } 107 }
@@ -130,11 +131,9 @@ impl NavigationTarget {
130 } 131 }
131 132
132 /// Allows `NavigationTarget` to be created from a `NameOwner` 133 /// Allows `NavigationTarget` to be created from a `NameOwner`
133 fn from_named( 134 pub(crate) fn from_named(
134 db: &RootDatabase, 135 db: &RootDatabase,
135 node: InFile<&dyn ast::NameOwner>, 136 node: InFile<&dyn ast::NameOwner>,
136 docs: Option<String>,
137 description: Option<String>,
138 ) -> NavigationTarget { 137 ) -> NavigationTarget {
139 //FIXME: use `_` instead of empty string 138 //FIXME: use `_` instead of empty string
140 let name = node.value.name().map(|it| it.text().clone()).unwrap_or_default(); 139 let name = node.value.name().map(|it| it.text().clone()).unwrap_or_default();
@@ -148,8 +147,6 @@ impl NavigationTarget {
148 focus_range, 147 focus_range,
149 frange.range, 148 frange.range,
150 node.value.syntax().kind(), 149 node.value.syntax().kind(),
151 docs,
152 description,
153 ) 150 )
154 } 151 }
155 152
@@ -159,8 +156,6 @@ impl NavigationTarget {
159 focus_range: Option<TextRange>, 156 focus_range: Option<TextRange>,
160 full_range: TextRange, 157 full_range: TextRange,
161 kind: SyntaxKind, 158 kind: SyntaxKind,
162 docs: Option<String>,
163 description: Option<String>,
164 ) -> NavigationTarget { 159 ) -> NavigationTarget {
165 NavigationTarget { 160 NavigationTarget {
166 file_id, 161 file_id,
@@ -169,8 +164,8 @@ impl NavigationTarget {
169 full_range, 164 full_range,
170 focus_range, 165 focus_range,
171 container_name: None, 166 container_name: None,
172 description, 167 description: None,
173 docs, 168 docs: None,
174 } 169 }
175 } 170 }
176} 171}
@@ -238,12 +233,11 @@ where
238{ 233{
239 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { 234 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
240 let src = self.source(db); 235 let src = self.source(db);
241 NavigationTarget::from_named( 236 let mut res =
242 db, 237 NavigationTarget::from_named(db, src.as_ref().map(|it| it as &dyn ast::NameOwner));
243 src.as_ref().map(|it| it as &dyn ast::NameOwner), 238 res.docs = src.value.doc_comment_text();
244 src.value.doc_comment_text(), 239 res.description = src.value.short_label();
245 src.value.short_label(), 240 res
246 )
247 } 241 }
248} 242}
249 243
@@ -258,15 +252,7 @@ impl ToNav for hir::Module {
258 } 252 }
259 }; 253 };
260 let frange = original_range(db, src.with_value(syntax)); 254 let frange = original_range(db, src.with_value(syntax));
261 NavigationTarget::from_syntax( 255 NavigationTarget::from_syntax(frange.file_id, name, focus, frange.range, syntax.kind())
262 frange.file_id,
263 name,
264 focus,
265 frange.range,
266 syntax.kind(),
267 None,
268 None,
269 )
270 } 256 }
271} 257}
272 258
@@ -285,8 +271,6 @@ impl ToNav for hir::ImplDef {
285 None, 271 None,
286 frange.range, 272 frange.range,
287 src.value.syntax().kind(), 273 src.value.syntax().kind(),
288 None,
289 None,
290 ) 274 )
291 } 275 }
292} 276}
@@ -296,12 +280,12 @@ impl ToNav for hir::Field {
296 let src = self.source(db); 280 let src = self.source(db);
297 281
298 match &src.value { 282 match &src.value {
299 FieldSource::Named(it) => NavigationTarget::from_named( 283 FieldSource::Named(it) => {
300 db, 284 let mut res = NavigationTarget::from_named(db, src.with_value(it));
301 src.with_value(it), 285 res.docs = it.doc_comment_text();
302 it.doc_comment_text(), 286 res.description = it.short_label();
303 it.short_label(), 287 res
304 ), 288 }
305 FieldSource::Pos(it) => { 289 FieldSource::Pos(it) => {
306 let frange = original_range(db, src.with_value(it.syntax())); 290 let frange = original_range(db, src.with_value(it.syntax()));
307 NavigationTarget::from_syntax( 291 NavigationTarget::from_syntax(
@@ -310,8 +294,6 @@ impl ToNav for hir::Field {
310 None, 294 None,
311 frange.range, 295 frange.range,
312 it.syntax().kind(), 296 it.syntax().kind(),
313 None,
314 None,
315 ) 297 )
316 } 298 }
317 } 299 }
@@ -322,12 +304,10 @@ impl ToNav for hir::MacroDef {
322 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { 304 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
323 let src = self.source(db); 305 let src = self.source(db);
324 log::debug!("nav target {:#?}", src.value.syntax()); 306 log::debug!("nav target {:#?}", src.value.syntax());
325 NavigationTarget::from_named( 307 let mut res =
326 db, 308 NavigationTarget::from_named(db, src.as_ref().map(|it| it as &dyn ast::NameOwner));
327 src.as_ref().map(|it| it as &dyn ast::NameOwner), 309 res.docs = src.value.doc_comment_text();
328 src.value.doc_comment_text(), 310 res
329 None,
330 )
331 } 311 }
332} 312}
333 313
diff --git a/crates/ra_ide/src/runnables.rs b/crates/ra_ide/src/runnables.rs
index 286d45eee..f32ce0d22 100644
--- a/crates/ra_ide/src/runnables.rs
+++ b/crates/ra_ide/src/runnables.rs
@@ -1,19 +1,19 @@
1use std::fmt;
2
1use hir::{AsAssocItem, Attrs, HirFileId, InFile, Semantics}; 3use hir::{AsAssocItem, Attrs, HirFileId, InFile, Semantics};
2use itertools::Itertools; 4use itertools::Itertools;
5use ra_cfg::CfgExpr;
3use ra_ide_db::RootDatabase; 6use ra_ide_db::RootDatabase;
4use ra_syntax::{ 7use ra_syntax::{
5 ast::{self, AstNode, AttrsOwner, ModuleItemOwner, NameOwner}, 8 ast::{self, AstNode, AttrsOwner, DocCommentsOwner, ModuleItemOwner, NameOwner},
6 match_ast, SyntaxNode, TextRange, 9 match_ast, SyntaxNode,
7}; 10};
8 11
9use crate::FileId; 12use crate::{display::ToNav, FileId, NavigationTarget};
10use ast::DocCommentsOwner;
11use ra_cfg::CfgExpr;
12use std::fmt::Display;
13 13
14#[derive(Debug)] 14#[derive(Debug)]
15pub struct Runnable { 15pub struct Runnable {
16 pub range: TextRange, 16 pub nav: NavigationTarget,
17 pub kind: RunnableKind, 17 pub kind: RunnableKind,
18 pub cfg_exprs: Vec<CfgExpr>, 18 pub cfg_exprs: Vec<CfgExpr>,
19} 19}
@@ -24,8 +24,8 @@ pub enum TestId {
24 Path(String), 24 Path(String),
25} 25}
26 26
27impl Display for TestId { 27impl fmt::Display for TestId {
28 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 28 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
29 match self { 29 match self {
30 TestId::Name(name) => write!(f, "{}", name), 30 TestId::Name(name) => write!(f, "{}", name),
31 TestId::Path(path) => write!(f, "{}", path), 31 TestId::Path(path) => write!(f, "{}", path),
@@ -131,7 +131,8 @@ fn runnable_fn(
131 let cfg_exprs = 131 let cfg_exprs =
132 attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect(); 132 attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect();
133 133
134 Some(Runnable { range: fn_def.syntax().text_range(), kind, cfg_exprs }) 134 let nav = NavigationTarget::from_named(sema.db, InFile::new(file_id.into(), &fn_def));
135 Some(Runnable { nav, kind, cfg_exprs })
135} 136}
136 137
137#[derive(Debug)] 138#[derive(Debug)]
@@ -183,7 +184,6 @@ fn runnable_mod(
183 if !has_test_function { 184 if !has_test_function {
184 return None; 185 return None;
185 } 186 }
186 let range = module.syntax().text_range();
187 let module_def = sema.to_def(&module)?; 187 let module_def = sema.to_def(&module)?;
188 188
189 let path = module_def 189 let path = module_def
@@ -197,7 +197,8 @@ fn runnable_mod(
197 let cfg_exprs = 197 let cfg_exprs =
198 attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect(); 198 attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect();
199 199
200 Some(Runnable { range, kind: RunnableKind::TestMod { path }, cfg_exprs }) 200 let nav = module_def.to_nav(sema.db);
201 Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg_exprs })
201} 202}
202 203
203#[cfg(test)] 204#[cfg(test)]
@@ -227,12 +228,38 @@ mod tests {
227 @r###" 228 @r###"
228 [ 229 [
229 Runnable { 230 Runnable {
230 range: 1..21, 231 nav: NavigationTarget {
232 file_id: FileId(
233 1,
234 ),
235 full_range: 1..21,
236 name: "main",
237 kind: FN_DEF,
238 focus_range: Some(
239 12..16,
240 ),
241 container_name: None,
242 description: None,
243 docs: None,
244 },
231 kind: Bin, 245 kind: Bin,
232 cfg_exprs: [], 246 cfg_exprs: [],
233 }, 247 },
234 Runnable { 248 Runnable {
235 range: 22..46, 249 nav: NavigationTarget {
250 file_id: FileId(
251 1,
252 ),
253 full_range: 22..46,
254 name: "test_foo",
255 kind: FN_DEF,
256 focus_range: Some(
257 33..41,
258 ),
259 container_name: None,
260 description: None,
261 docs: None,
262 },
236 kind: Test { 263 kind: Test {
237 test_id: Path( 264 test_id: Path(
238 "test_foo", 265 "test_foo",
@@ -244,7 +271,20 @@ mod tests {
244 cfg_exprs: [], 271 cfg_exprs: [],
245 }, 272 },
246 Runnable { 273 Runnable {
247 range: 47..81, 274 nav: NavigationTarget {
275 file_id: FileId(
276 1,
277 ),
278 full_range: 47..81,
279 name: "test_foo",
280 kind: FN_DEF,
281 focus_range: Some(
282 68..76,
283 ),
284 container_name: None,
285 description: None,
286 docs: None,
287 },
248 kind: Test { 288 kind: Test {
249 test_id: Path( 289 test_id: Path(
250 "test_foo", 290 "test_foo",
@@ -279,12 +319,38 @@ mod tests {
279 @r###" 319 @r###"
280 [ 320 [
281 Runnable { 321 Runnable {
282 range: 1..21, 322 nav: NavigationTarget {
323 file_id: FileId(
324 1,
325 ),
326 full_range: 1..21,
327 name: "main",
328 kind: FN_DEF,
329 focus_range: Some(
330 12..16,
331 ),
332 container_name: None,
333 description: None,
334 docs: None,
335 },
283 kind: Bin, 336 kind: Bin,
284 cfg_exprs: [], 337 cfg_exprs: [],
285 }, 338 },
286 Runnable { 339 Runnable {
287 range: 22..64, 340 nav: NavigationTarget {
341 file_id: FileId(
342 1,
343 ),
344 full_range: 22..64,
345 name: "foo",
346 kind: FN_DEF,
347 focus_range: Some(
348 56..59,
349 ),
350 container_name: None,
351 description: None,
352 docs: None,
353 },
288 kind: DocTest { 354 kind: DocTest {
289 test_id: Path( 355 test_id: Path(
290 "foo", 356 "foo",
@@ -319,12 +385,38 @@ mod tests {
319 @r###" 385 @r###"
320 [ 386 [
321 Runnable { 387 Runnable {
322 range: 1..21, 388 nav: NavigationTarget {
389 file_id: FileId(
390 1,
391 ),
392 full_range: 1..21,
393 name: "main",
394 kind: FN_DEF,
395 focus_range: Some(
396 12..16,
397 ),
398 container_name: None,
399 description: None,
400 docs: None,
401 },
323 kind: Bin, 402 kind: Bin,
324 cfg_exprs: [], 403 cfg_exprs: [],
325 }, 404 },
326 Runnable { 405 Runnable {
327 range: 51..105, 406 nav: NavigationTarget {
407 file_id: FileId(
408 1,
409 ),
410 full_range: 51..105,
411 name: "foo",
412 kind: FN_DEF,
413 focus_range: Some(
414 97..100,
415 ),
416 container_name: None,
417 description: None,
418 docs: None,
419 },
328 kind: DocTest { 420 kind: DocTest {
329 test_id: Path( 421 test_id: Path(
330 "Data::foo", 422 "Data::foo",
@@ -354,14 +446,40 @@ mod tests {
354 @r###" 446 @r###"
355 [ 447 [
356 Runnable { 448 Runnable {
357 range: 1..59, 449 nav: NavigationTarget {
450 file_id: FileId(
451 1,
452 ),
453 full_range: 1..59,
454 name: "test_mod",
455 kind: MODULE,
456 focus_range: Some(
457 13..21,
458 ),
459 container_name: None,
460 description: None,
461 docs: None,
462 },
358 kind: TestMod { 463 kind: TestMod {
359 path: "test_mod", 464 path: "test_mod",
360 }, 465 },
361 cfg_exprs: [], 466 cfg_exprs: [],
362 }, 467 },
363 Runnable { 468 Runnable {
364 range: 28..57, 469 nav: NavigationTarget {
470 file_id: FileId(
471 1,
472 ),
473 full_range: 28..57,
474 name: "test_foo1",
475 kind: FN_DEF,
476 focus_range: Some(
477 43..52,
478 ),
479 container_name: None,
480 description: None,
481 docs: None,
482 },
365 kind: Test { 483 kind: Test {
366 test_id: Path( 484 test_id: Path(
367 "test_mod::test_foo1", 485 "test_mod::test_foo1",
@@ -396,14 +514,40 @@ mod tests {
396 @r###" 514 @r###"
397 [ 515 [
398 Runnable { 516 Runnable {
399 range: 23..85, 517 nav: NavigationTarget {
518 file_id: FileId(
519 1,
520 ),
521 full_range: 23..85,
522 name: "test_mod",
523 kind: MODULE,
524 focus_range: Some(
525 27..35,
526 ),
527 container_name: None,
528 description: None,
529 docs: None,
530 },
400 kind: TestMod { 531 kind: TestMod {
401 path: "foo::test_mod", 532 path: "foo::test_mod",
402 }, 533 },
403 cfg_exprs: [], 534 cfg_exprs: [],
404 }, 535 },
405 Runnable { 536 Runnable {
406 range: 46..79, 537 nav: NavigationTarget {
538 file_id: FileId(
539 1,
540 ),
541 full_range: 46..79,
542 name: "test_foo1",
543 kind: FN_DEF,
544 focus_range: Some(
545 65..74,
546 ),
547 container_name: None,
548 description: None,
549 docs: None,
550 },
407 kind: Test { 551 kind: Test {
408 test_id: Path( 552 test_id: Path(
409 "foo::test_mod::test_foo1", 553 "foo::test_mod::test_foo1",
@@ -440,14 +584,40 @@ mod tests {
440 @r###" 584 @r###"
441 [ 585 [
442 Runnable { 586 Runnable {
443 range: 41..115, 587 nav: NavigationTarget {
588 file_id: FileId(
589 1,
590 ),
591 full_range: 41..115,
592 name: "test_mod",
593 kind: MODULE,
594 focus_range: Some(
595 45..53,
596 ),
597 container_name: None,
598 description: None,
599 docs: None,
600 },
444 kind: TestMod { 601 kind: TestMod {
445 path: "foo::bar::test_mod", 602 path: "foo::bar::test_mod",
446 }, 603 },
447 cfg_exprs: [], 604 cfg_exprs: [],
448 }, 605 },
449 Runnable { 606 Runnable {
450 range: 68..105, 607 nav: NavigationTarget {
608 file_id: FileId(
609 1,
610 ),
611 full_range: 68..105,
612 name: "test_foo1",
613 kind: FN_DEF,
614 focus_range: Some(
615 91..100,
616 ),
617 container_name: None,
618 description: None,
619 docs: None,
620 },
451 kind: Test { 621 kind: Test {
452 test_id: Path( 622 test_id: Path(
453 "foo::bar::test_mod::test_foo1", 623 "foo::bar::test_mod::test_foo1",
@@ -479,7 +649,20 @@ mod tests {
479 @r###" 649 @r###"
480 [ 650 [
481 Runnable { 651 Runnable {
482 range: 1..58, 652 nav: NavigationTarget {
653 file_id: FileId(
654 1,
655 ),
656 full_range: 1..58,
657 name: "test_foo1",
658 kind: FN_DEF,
659 focus_range: Some(
660 44..53,
661 ),
662 container_name: None,
663 description: None,
664 docs: None,
665 },
483 kind: Test { 666 kind: Test {
484 test_id: Path( 667 test_id: Path(
485 "test_foo1", 668 "test_foo1",
@@ -516,7 +699,20 @@ mod tests {
516 @r###" 699 @r###"
517 [ 700 [
518 Runnable { 701 Runnable {
519 range: 1..80, 702 nav: NavigationTarget {
703 file_id: FileId(
704 1,
705 ),
706 full_range: 1..80,
707 name: "test_foo1",
708 kind: FN_DEF,
709 focus_range: Some(
710 66..75,
711 ),
712 container_name: None,
713 description: None,
714 docs: None,
715 },
520 kind: Test { 716 kind: Test {
521 test_id: Path( 717 test_id: Path(
522 "test_foo1", 718 "test_foo1",
diff --git a/crates/ra_ide/src/snapshots/highlight_injection.html b/crates/ra_ide/src/snapshots/highlight_injection.html
index 68fc589bc..fcdc98201 100644
--- a/crates/ra_ide/src/snapshots/highlight_injection.html
+++ b/crates/ra_ide/src/snapshots/highlight_injection.html
@@ -10,6 +10,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
10.string_literal { color: #CC9393; } 10.string_literal { color: #CC9393; }
11.field { color: #94BFF3; } 11.field { color: #94BFF3; }
12.function { color: #93E0E3; } 12.function { color: #93E0E3; }
13.operator.unsafe { color: #E28C14; }
13.parameter { color: #94BFF3; } 14.parameter { color: #94BFF3; }
14.text { color: #DCDCCC; } 15.text { color: #DCDCCC; }
15.type { color: #7CB8BB; } 16.type { color: #7CB8BB; }
diff --git a/crates/ra_ide/src/snapshots/highlight_strings.html b/crates/ra_ide/src/snapshots/highlight_strings.html
index 326744361..e97192b61 100644
--- a/crates/ra_ide/src/snapshots/highlight_strings.html
+++ b/crates/ra_ide/src/snapshots/highlight_strings.html
@@ -10,6 +10,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
10.string_literal { color: #CC9393; } 10.string_literal { color: #CC9393; }
11.field { color: #94BFF3; } 11.field { color: #94BFF3; }
12.function { color: #93E0E3; } 12.function { color: #93E0E3; }
13.operator.unsafe { color: #E28C14; }
13.parameter { color: #94BFF3; } 14.parameter { color: #94BFF3; }
14.text { color: #DCDCCC; } 15.text { color: #DCDCCC; }
15.type { color: #7CB8BB; } 16.type { color: #7CB8BB; }
@@ -52,6 +53,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
52 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">argument</span><span class="format_specifier">}</span><span class="string_literal">"</span>, argument = <span class="string_literal">"test"</span>); <span class="comment">// =&gt; "test"</span> 53 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">argument</span><span class="format_specifier">}</span><span class="string_literal">"</span>, argument = <span class="string_literal">"test"</span>); <span class="comment">// =&gt; "test"</span>
53 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">name</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">"</span>, <span class="numeric_literal">1</span>, name = <span class="numeric_literal">2</span>); <span class="comment">// =&gt; "2 1"</span> 54 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">name</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">"</span>, <span class="numeric_literal">1</span>, name = <span class="numeric_literal">2</span>); <span class="comment">// =&gt; "2 1"</span>
54 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">a</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="variable">c</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="variable">b</span><span class="format_specifier">}</span><span class="string_literal">"</span>, a=<span class="string_literal">"a"</span>, b=<span class="char_literal">'b'</span>, c=<span class="numeric_literal">3</span>); <span class="comment">// =&gt; "a 3 b"</span> 55 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">a</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="variable">c</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="variable">b</span><span class="format_specifier">}</span><span class="string_literal">"</span>, a=<span class="string_literal">"a"</span>, b=<span class="char_literal">'b'</span>, c=<span class="numeric_literal">3</span>); <span class="comment">// =&gt; "a 3 b"</span>
56 <span class="macro">println!</span>(<span class="string_literal">"{{</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">}}"</span>, <span class="numeric_literal">2</span>); <span class="comment">// =&gt; "{2}"</span>
55 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>); 57 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>);
56 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">1</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>, <span class="numeric_literal">5</span>); 58 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">1</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>, <span class="numeric_literal">5</span>);
57 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="numeric_literal">1</span><span class="format_specifier">:</span><span class="numeric_literal">0</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">5</span>, <span class="string_literal">"x"</span>); 59 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="numeric_literal">1</span><span class="format_specifier">:</span><span class="numeric_literal">0</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">5</span>, <span class="string_literal">"x"</span>);
diff --git a/crates/ra_ide/src/snapshots/highlight_unsafe.html b/crates/ra_ide/src/snapshots/highlight_unsafe.html
new file mode 100644
index 000000000..17ffc727c
--- /dev/null
+++ b/crates/ra_ide/src/snapshots/highlight_unsafe.html
@@ -0,0 +1,48 @@
1
2<style>
3body { margin: 0; }
4pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
5
6.lifetime { color: #DFAF8F; font-style: italic; }
7.comment { color: #7F9F7F; }
8.struct, .enum { color: #7CB8BB; }
9.enum_variant { color: #BDE0F3; }
10.string_literal { color: #CC9393; }
11.field { color: #94BFF3; }
12.function { color: #93E0E3; }
13.operator.unsafe { color: #E28C14; }
14.parameter { color: #94BFF3; }
15.text { color: #DCDCCC; }
16.type { color: #7CB8BB; }
17.builtin_type { color: #8CD0D3; }
18.type_param { color: #DFAF8F; }
19.attribute { color: #94BFF3; }
20.numeric_literal { color: #BFEBBF; }
21.bool_literal { color: #BFE6EB; }
22.macro { color: #94BFF3; }
23.module { color: #AFD8AF; }
24.variable { color: #DCDCCC; }
25.format_specifier { color: #CC696B; }
26.mutable { text-decoration: underline; }
27
28.keyword { color: #F0DFAF; font-weight: bold; }
29.keyword.unsafe { color: #BC8383; font-weight: bold; }
30.control { font-style: italic; }
31</style>
32<pre><code><span class="keyword unsafe">unsafe</span> <span class="keyword">fn</span> <span class="function declaration unsafe">unsafe_fn</span>() {}
33
34<span class="keyword">struct</span> <span class="struct declaration">HasUnsafeFn</span>;
35
36<span class="keyword">impl</span> <span class="struct">HasUnsafeFn</span> {
37 <span class="keyword unsafe">unsafe</span> <span class="keyword">fn</span> <span class="function declaration unsafe">unsafe_method</span>(&<span class="self_keyword">self</span>) {}
38}
39
40<span class="keyword">fn</span> <span class="function declaration">main</span>() {
41 <span class="keyword">let</span> <span class="variable declaration">x</span> = &<span class="numeric_literal">5</span> <span class="keyword">as</span> *<span class="keyword">const</span> <span class="builtin_type">usize</span>;
42 <span class="keyword unsafe">unsafe</span> {
43 <span class="function unsafe">unsafe_fn</span>();
44 <span class="struct">HasUnsafeFn</span>.<span class="function unsafe">unsafe_method</span>();
45 <span class="keyword">let</span> <span class="variable declaration">y</span> = <span class="operator unsafe">*</span><span class="variable">x</span>;
46 <span class="keyword">let</span> <span class="variable declaration">z</span> = -<span class="variable">x</span>;
47 }
48}</code></pre> \ No newline at end of file
diff --git a/crates/ra_ide/src/snapshots/highlighting.html b/crates/ra_ide/src/snapshots/highlighting.html
index 352e35095..42c5f3e55 100644
--- a/crates/ra_ide/src/snapshots/highlighting.html
+++ b/crates/ra_ide/src/snapshots/highlighting.html
@@ -10,6 +10,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
10.string_literal { color: #CC9393; } 10.string_literal { color: #CC9393; }
11.field { color: #94BFF3; } 11.field { color: #94BFF3; }
12.function { color: #93E0E3; } 12.function { color: #93E0E3; }
13.operator.unsafe { color: #E28C14; }
13.parameter { color: #94BFF3; } 14.parameter { color: #94BFF3; }
14.text { color: #DCDCCC; } 15.text { color: #DCDCCC; }
15.type { color: #7CB8BB; } 16.type { color: #7CB8BB; }
diff --git a/crates/ra_ide/src/snapshots/rainbow_highlighting.html b/crates/ra_ide/src/snapshots/rainbow_highlighting.html
index 2a0294f71..2dd61d20d 100644
--- a/crates/ra_ide/src/snapshots/rainbow_highlighting.html
+++ b/crates/ra_ide/src/snapshots/rainbow_highlighting.html
@@ -10,6 +10,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
10.string_literal { color: #CC9393; } 10.string_literal { color: #CC9393; }
11.field { color: #94BFF3; } 11.field { color: #94BFF3; }
12.function { color: #93E0E3; } 12.function { color: #93E0E3; }
13.operator.unsafe { color: #E28C14; }
13.parameter { color: #94BFF3; } 14.parameter { color: #94BFF3; }
14.text { color: #DCDCCC; } 15.text { color: #DCDCCC; }
15.type { color: #7CB8BB; } 16.type { color: #7CB8BB; }
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs
index 0b53ebe69..19ecd54d6 100644
--- a/crates/ra_ide/src/syntax_highlighting.rs
+++ b/crates/ra_ide/src/syntax_highlighting.rs
@@ -406,6 +406,23 @@ fn highlight_element(
406 _ => h, 406 _ => h,
407 } 407 }
408 } 408 }
409 PREFIX_EXPR => {
410 let prefix_expr = element.into_node().and_then(ast::PrefixExpr::cast)?;
411 match prefix_expr.op_kind() {
412 Some(ast::PrefixOp::Deref) => {}
413 _ => return None,
414 }
415
416 let expr = prefix_expr.expr()?;
417 let ty = sema.type_of_expr(&expr)?;
418 if !ty.is_raw_ptr() {
419 return None;
420 }
421
422 let mut h = Highlight::new(HighlightTag::Operator);
423 h |= HighlightModifier::Unsafe;
424 h
425 }
409 426
410 k if k.is_keyword() => { 427 k if k.is_keyword() => {
411 let h = Highlight::new(HighlightTag::Keyword); 428 let h = Highlight::new(HighlightTag::Keyword);
@@ -458,7 +475,13 @@ fn highlight_name(db: &RootDatabase, def: Definition) -> Highlight {
458 Definition::Field(_) => HighlightTag::Field, 475 Definition::Field(_) => HighlightTag::Field,
459 Definition::ModuleDef(def) => match def { 476 Definition::ModuleDef(def) => match def {
460 hir::ModuleDef::Module(_) => HighlightTag::Module, 477 hir::ModuleDef::Module(_) => HighlightTag::Module,
461 hir::ModuleDef::Function(_) => HighlightTag::Function, 478 hir::ModuleDef::Function(func) => {
479 let mut h = HighlightTag::Function.into();
480 if func.is_unsafe(db) {
481 h |= HighlightModifier::Unsafe;
482 }
483 return h;
484 }
462 hir::ModuleDef::Adt(hir::Adt::Struct(_)) => HighlightTag::Struct, 485 hir::ModuleDef::Adt(hir::Adt::Struct(_)) => HighlightTag::Struct,
463 hir::ModuleDef::Adt(hir::Adt::Enum(_)) => HighlightTag::Enum, 486 hir::ModuleDef::Adt(hir::Adt::Enum(_)) => HighlightTag::Enum,
464 hir::ModuleDef::Adt(hir::Adt::Union(_)) => HighlightTag::Union, 487 hir::ModuleDef::Adt(hir::Adt::Union(_)) => HighlightTag::Union,
diff --git a/crates/ra_ide/src/syntax_highlighting/html.rs b/crates/ra_ide/src/syntax_highlighting/html.rs
index edfe61f39..7d946c98d 100644
--- a/crates/ra_ide/src/syntax_highlighting/html.rs
+++ b/crates/ra_ide/src/syntax_highlighting/html.rs
@@ -69,6 +69,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
69.string_literal { color: #CC9393; } 69.string_literal { color: #CC9393; }
70.field { color: #94BFF3; } 70.field { color: #94BFF3; }
71.function { color: #93E0E3; } 71.function { color: #93E0E3; }
72.operator.unsafe { color: #E28C14; }
72.parameter { color: #94BFF3; } 73.parameter { color: #94BFF3; }
73.text { color: #DCDCCC; } 74.text { color: #DCDCCC; }
74.type { color: #7CB8BB; } 75.type { color: #7CB8BB; }
diff --git a/crates/ra_ide/src/syntax_highlighting/tags.rs b/crates/ra_ide/src/syntax_highlighting/tags.rs
index 1514531de..94f466966 100644
--- a/crates/ra_ide/src/syntax_highlighting/tags.rs
+++ b/crates/ra_ide/src/syntax_highlighting/tags.rs
@@ -24,12 +24,14 @@ pub enum HighlightTag {
24 Enum, 24 Enum,
25 EnumVariant, 25 EnumVariant,
26 Field, 26 Field,
27 FormatSpecifier,
27 Function, 28 Function,
28 Keyword, 29 Keyword,
29 Lifetime, 30 Lifetime,
30 Macro, 31 Macro,
31 Module, 32 Module,
32 NumericLiteral, 33 NumericLiteral,
34 Operator,
33 SelfKeyword, 35 SelfKeyword,
34 SelfType, 36 SelfType,
35 Static, 37 Static,
@@ -41,8 +43,6 @@ pub enum HighlightTag {
41 Union, 43 Union,
42 Local, 44 Local,
43 UnresolvedReference, 45 UnresolvedReference,
44 FormatSpecifier,
45 Operator,
46} 46}
47 47
48#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] 48#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
@@ -72,12 +72,14 @@ impl HighlightTag {
72 HighlightTag::Enum => "enum", 72 HighlightTag::Enum => "enum",
73 HighlightTag::EnumVariant => "enum_variant", 73 HighlightTag::EnumVariant => "enum_variant",
74 HighlightTag::Field => "field", 74 HighlightTag::Field => "field",
75 HighlightTag::FormatSpecifier => "format_specifier",
75 HighlightTag::Function => "function", 76 HighlightTag::Function => "function",
76 HighlightTag::Keyword => "keyword", 77 HighlightTag::Keyword => "keyword",
77 HighlightTag::Lifetime => "lifetime", 78 HighlightTag::Lifetime => "lifetime",
78 HighlightTag::Macro => "macro", 79 HighlightTag::Macro => "macro",
79 HighlightTag::Module => "module", 80 HighlightTag::Module => "module",
80 HighlightTag::NumericLiteral => "numeric_literal", 81 HighlightTag::NumericLiteral => "numeric_literal",
82 HighlightTag::Operator => "operator",
81 HighlightTag::SelfKeyword => "self_keyword", 83 HighlightTag::SelfKeyword => "self_keyword",
82 HighlightTag::SelfType => "self_type", 84 HighlightTag::SelfType => "self_type",
83 HighlightTag::Static => "static", 85 HighlightTag::Static => "static",
@@ -89,8 +91,6 @@ impl HighlightTag {
89 HighlightTag::Union => "union", 91 HighlightTag::Union => "union",
90 HighlightTag::Local => "variable", 92 HighlightTag::Local => "variable",
91 HighlightTag::UnresolvedReference => "unresolved_reference", 93 HighlightTag::UnresolvedReference => "unresolved_reference",
92 HighlightTag::FormatSpecifier => "format_specifier",
93 HighlightTag::Operator => "operator",
94 } 94 }
95 } 95 }
96} 96}
diff --git a/crates/ra_ide/src/syntax_highlighting/tests.rs b/crates/ra_ide/src/syntax_highlighting/tests.rs
index eb43a23da..36a1aa419 100644
--- a/crates/ra_ide/src/syntax_highlighting/tests.rs
+++ b/crates/ra_ide/src/syntax_highlighting/tests.rs
@@ -218,6 +218,7 @@ fn main() {
218 println!("{argument}", argument = "test"); // => "test" 218 println!("{argument}", argument = "test"); // => "test"
219 println!("{name} {}", 1, name = 2); // => "2 1" 219 println!("{name} {}", 1, name = 2); // => "2 1"
220 println!("{a} {c} {b}", a="a", b='b', c=3); // => "a 3 b" 220 println!("{a} {c} {b}", a="a", b='b', c=3); // => "a 3 b"
221 println!("{{{}}}", 2); // => "{2}"
221 println!("Hello {:5}!", "x"); 222 println!("Hello {:5}!", "x");
222 println!("Hello {:1$}!", "x", 5); 223 println!("Hello {:1$}!", "x", 5);
223 println!("Hello {1:0$}!", 5, "x"); 224 println!("Hello {1:0$}!", 5, "x");
@@ -257,3 +258,34 @@ fn main() {
257 fs::write(dst_file, &actual_html).unwrap(); 258 fs::write(dst_file, &actual_html).unwrap();
258 assert_eq_text!(expected_html, actual_html); 259 assert_eq_text!(expected_html, actual_html);
259} 260}
261
262#[test]
263fn test_unsafe_highlighting() {
264 let (analysis, file_id) = single_file(
265 r#"
266unsafe fn unsafe_fn() {}
267
268struct HasUnsafeFn;
269
270impl HasUnsafeFn {
271 unsafe fn unsafe_method(&self) {}
272}
273
274fn main() {
275 let x = &5 as *const usize;
276 unsafe {
277 unsafe_fn();
278 HasUnsafeFn.unsafe_method();
279 let y = *x;
280 let z = -x;
281 }
282}
283"#
284 .trim(),
285 );
286 let dst_file = project_dir().join("crates/ra_ide/src/snapshots/highlight_unsafe.html");
287 let actual_html = &analysis.highlight_as_html(file_id, false).unwrap();
288 let expected_html = &read_text(&dst_file);
289 fs::write(dst_file, &actual_html).unwrap();
290 assert_eq_text!(expected_html, actual_html);
291}
diff --git a/crates/ra_parser/src/grammar.rs b/crates/ra_parser/src/grammar.rs
index be0cd5661..293baecf6 100644
--- a/crates/ra_parser/src/grammar.rs
+++ b/crates/ra_parser/src/grammar.rs
@@ -18,9 +18,10 @@
18//! // fn foo() {} 18//! // fn foo() {}
19//! ``` 19//! ```
20//! 20//!
21//! After adding a new inline-test, run `cargo collect-tests` to extract 21//! After adding a new inline-test, run `cargo xtask codegen` to
22//! it as a standalone text-fixture into `tests/data/parser/inline`, and 22//! extract it as a standalone text-fixture into
23//! run `cargo test` once to create the "gold" value. 23//! `crates/ra_syntax/test_data/parser/`, and run `cargo test` once to
24//! create the "gold" value.
24//! 25//!
25//! Coding convention: rules like `where_clause` always produce either a 26//! Coding convention: rules like `where_clause` always produce either a
26//! node or an error, rules like `opt_where_clause` may produce nothing. 27//! node or an error, rules like `opt_where_clause` may produce nothing.
diff --git a/crates/ra_proc_macro_srv/Cargo.toml b/crates/ra_proc_macro_srv/Cargo.toml
index bb3003278..582102945 100644
--- a/crates/ra_proc_macro_srv/Cargo.toml
+++ b/crates/ra_proc_macro_srv/Cargo.toml
@@ -22,3 +22,4 @@ cargo_metadata = "0.10.0"
22difference = "2.0.0" 22difference = "2.0.0"
23# used as proc macro test target 23# used as proc macro test target
24serde_derive = "1.0.106" 24serde_derive = "1.0.106"
25ra_toolchain = { path = "../ra_toolchain" }
diff --git a/crates/ra_proc_macro_srv/src/tests/utils.rs b/crates/ra_proc_macro_srv/src/tests/utils.rs
index 84348b5de..8d85f2d8a 100644
--- a/crates/ra_proc_macro_srv/src/tests/utils.rs
+++ b/crates/ra_proc_macro_srv/src/tests/utils.rs
@@ -2,7 +2,6 @@
2 2
3use crate::dylib; 3use crate::dylib;
4use crate::ProcMacroSrv; 4use crate::ProcMacroSrv;
5pub use difference::Changeset as __Changeset;
6use ra_proc_macro::ListMacrosTask; 5use ra_proc_macro::ListMacrosTask;
7use std::str::FromStr; 6use std::str::FromStr;
8use test_utils::assert_eq_text; 7use test_utils::assert_eq_text;
@@ -13,7 +12,7 @@ mod fixtures {
13 12
14 // Use current project metadata to get the proc-macro dylib path 13 // Use current project metadata to get the proc-macro dylib path
15 pub fn dylib_path(crate_name: &str, version: &str) -> std::path::PathBuf { 14 pub fn dylib_path(crate_name: &str, version: &str) -> std::path::PathBuf {
16 let command = Command::new("cargo") 15 let command = Command::new(ra_toolchain::cargo())
17 .args(&["check", "--message-format", "json"]) 16 .args(&["check", "--message-format", "json"])
18 .output() 17 .output()
19 .unwrap() 18 .unwrap()
diff --git a/crates/ra_project_model/src/cargo_workspace.rs b/crates/ra_project_model/src/cargo_workspace.rs
index a306ce95f..4b7444039 100644
--- a/crates/ra_project_model/src/cargo_workspace.rs
+++ b/crates/ra_project_model/src/cargo_workspace.rs
@@ -64,7 +64,7 @@ impl Default for CargoConfig {
64 fn default() -> Self { 64 fn default() -> Self {
65 CargoConfig { 65 CargoConfig {
66 no_default_features: false, 66 no_default_features: false,
67 all_features: true, 67 all_features: false,
68 features: Vec::new(), 68 features: Vec::new(),
69 load_out_dirs_from_check: false, 69 load_out_dirs_from_check: false,
70 target: None, 70 target: None,
diff --git a/crates/ra_syntax/src/ast/tokens.rs b/crates/ra_syntax/src/ast/tokens.rs
index 3cd6d99c3..04b0a4480 100644
--- a/crates/ra_syntax/src/ast/tokens.rs
+++ b/crates/ra_syntax/src/ast/tokens.rs
@@ -418,14 +418,9 @@ pub trait HasFormatSpecifier: AstToken {
418 418
419 let mut cloned = chars.clone().take(2); 419 let mut cloned = chars.clone().take(2);
420 let first = cloned.next().and_then(|next| next.1.as_ref().ok()).copied(); 420 let first = cloned.next().and_then(|next| next.1.as_ref().ok()).copied();
421 let second = cloned.next().and_then(|next| next.1.as_ref().ok()).copied();
422 if first != Some('}') { 421 if first != Some('}') {
423 continue; 422 continue;
424 } 423 }
425 if second == Some('}') {
426 // Escaped format end specifier, `}}`
427 continue;
428 }
429 skip_char_and_emit(&mut chars, FormatSpecifier::Close, &mut callback); 424 skip_char_and_emit(&mut chars, FormatSpecifier::Close, &mut callback);
430 } 425 }
431 _ => { 426 _ => {
diff --git a/crates/rust-analyzer/src/caps.rs b/crates/rust-analyzer/src/caps.rs
index 345693524..673795e78 100644
--- a/crates/rust-analyzer/src/caps.rs
+++ b/crates/rust-analyzer/src/caps.rs
@@ -87,6 +87,9 @@ pub fn server_capabilities(client_caps: &ClientCapabilities) -> ServerCapabiliti
87 "ssr": true, 87 "ssr": true,
88 "onEnter": true, 88 "onEnter": true,
89 "parentModule": true, 89 "parentModule": true,
90 "runnables": {
91 "kinds": [ "cargo" ],
92 },
90 })), 93 })),
91 } 94 }
92} 95}
diff --git a/crates/rust-analyzer/src/cargo_target_spec.rs b/crates/rust-analyzer/src/cargo_target_spec.rs
index 441fb61df..008518a08 100644
--- a/crates/rust-analyzer/src/cargo_target_spec.rs
+++ b/crates/rust-analyzer/src/cargo_target_spec.rs
@@ -1,10 +1,10 @@
1//! See `CargoTargetSpec` 1//! See `CargoTargetSpec`
2 2
3use ra_cfg::CfgExpr;
3use ra_ide::{FileId, RunnableKind, TestId}; 4use ra_ide::{FileId, RunnableKind, TestId};
4use ra_project_model::{self, ProjectWorkspace, TargetKind}; 5use ra_project_model::{self, ProjectWorkspace, TargetKind};
5 6
6use crate::{world::WorldSnapshot, Result}; 7use crate::{world::WorldSnapshot, Result};
7use ra_syntax::SmolStr;
8 8
9/// Abstract representation of Cargo target. 9/// Abstract representation of Cargo target.
10/// 10///
@@ -21,7 +21,7 @@ impl CargoTargetSpec {
21 pub(crate) fn runnable_args( 21 pub(crate) fn runnable_args(
22 spec: Option<CargoTargetSpec>, 22 spec: Option<CargoTargetSpec>,
23 kind: &RunnableKind, 23 kind: &RunnableKind,
24 features_needed: &Vec<SmolStr>, 24 cfgs: &[CfgExpr],
25 ) -> Result<(Vec<String>, Vec<String>)> { 25 ) -> Result<(Vec<String>, Vec<String>)> {
26 let mut args = Vec::new(); 26 let mut args = Vec::new();
27 let mut extra_args = Vec::new(); 27 let mut extra_args = Vec::new();
@@ -76,10 +76,14 @@ impl CargoTargetSpec {
76 } 76 }
77 } 77 }
78 78
79 features_needed.iter().for_each(|feature| { 79 let mut features = Vec::new();
80 for cfg in cfgs {
81 required_features(cfg, &mut features);
82 }
83 for feature in features {
80 args.push("--features".to_string()); 84 args.push("--features".to_string());
81 args.push(feature.to_string()); 85 args.push(feature);
82 }); 86 }
83 87
84 Ok((args, extra_args)) 88 Ok((args, extra_args))
85 } 89 }
@@ -140,3 +144,74 @@ impl CargoTargetSpec {
140 } 144 }
141 } 145 }
142} 146}
147
148/// Fill minimal features needed
149fn required_features(cfg_expr: &CfgExpr, features: &mut Vec<String>) {
150 match cfg_expr {
151 CfgExpr::KeyValue { key, value } if key == "feature" => features.push(value.to_string()),
152 CfgExpr::All(preds) => {
153 preds.iter().for_each(|cfg| required_features(cfg, features));
154 }
155 CfgExpr::Any(preds) => {
156 for cfg in preds {
157 let len_features = features.len();
158 required_features(cfg, features);
159 if len_features != features.len() {
160 break;
161 }
162 }
163 }
164 _ => {}
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171
172 use mbe::{ast_to_token_tree, TokenMap};
173 use ra_cfg::parse_cfg;
174 use ra_syntax::{
175 ast::{self, AstNode},
176 SmolStr,
177 };
178
179 fn get_token_tree_generated(input: &str) -> (tt::Subtree, TokenMap) {
180 let source_file = ast::SourceFile::parse(input).ok().unwrap();
181 let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap();
182 ast_to_token_tree(&tt).unwrap()
183 }
184
185 #[test]
186 fn test_cfg_expr_minimal_features_needed() {
187 let (subtree, _) = get_token_tree_generated(r#"#![cfg(feature = "baz")]"#);
188 let cfg_expr = parse_cfg(&subtree);
189 let mut min_features = vec![];
190 required_features(&cfg_expr, &mut min_features);
191
192 assert_eq!(min_features, vec![SmolStr::new("baz")]);
193
194 let (subtree, _) =
195 get_token_tree_generated(r#"#![cfg(all(feature = "baz", feature = "foo"))]"#);
196 let cfg_expr = parse_cfg(&subtree);
197
198 let mut min_features = vec![];
199 required_features(&cfg_expr, &mut min_features);
200 assert_eq!(min_features, vec![SmolStr::new("baz"), SmolStr::new("foo")]);
201
202 let (subtree, _) =
203 get_token_tree_generated(r#"#![cfg(any(feature = "baz", feature = "foo", unix))]"#);
204 let cfg_expr = parse_cfg(&subtree);
205
206 let mut min_features = vec![];
207 required_features(&cfg_expr, &mut min_features);
208 assert_eq!(min_features, vec![SmolStr::new("baz")]);
209
210 let (subtree, _) = get_token_tree_generated(r#"#![cfg(foo)]"#);
211 let cfg_expr = parse_cfg(&subtree);
212
213 let mut min_features = vec![];
214 required_features(&cfg_expr, &mut min_features);
215 assert!(min_features.is_empty());
216 }
217}
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index c0f7c2c0c..9c6e369d2 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -122,7 +122,7 @@ impl Default for Config {
122 check: Some(FlycheckConfig::CargoCommand { 122 check: Some(FlycheckConfig::CargoCommand {
123 command: "check".to_string(), 123 command: "check".to_string(),
124 all_targets: true, 124 all_targets: true,
125 all_features: true, 125 all_features: false,
126 extra_args: Vec::new(), 126 extra_args: Vec::new(),
127 }), 127 }),
128 128
diff --git a/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable.snap b/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable.snap
index 6dd3fcb2e..33b516e26 100644
--- a/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable.snap
+++ b/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable.snap
@@ -29,7 +29,7 @@ expression: diag
29 }, 29 },
30 }, 30 },
31 severity: Some( 31 severity: Some(
32 Warning, 32 Hint,
33 ), 33 ),
34 code: Some( 34 code: Some(
35 String( 35 String(
diff --git a/crates/rust-analyzer/src/diagnostics/to_proto.rs b/crates/rust-analyzer/src/diagnostics/to_proto.rs
index a500d670a..6a6e7b457 100644
--- a/crates/rust-analyzer/src/diagnostics/to_proto.rs
+++ b/crates/rust-analyzer/src/diagnostics/to_proto.rs
@@ -183,7 +183,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
183 return Vec::new(); 183 return Vec::new();
184 } 184 }
185 185
186 let severity = map_level_to_severity(rd.level); 186 let mut severity = map_level_to_severity(rd.level);
187 187
188 let mut source = String::from("rustc"); 188 let mut source = String::from("rustc");
189 let mut code = rd.code.as_ref().map(|c| c.code.clone()); 189 let mut code = rd.code.as_ref().map(|c| c.code.clone());
@@ -225,6 +225,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
225 } 225 }
226 226
227 if is_unused_or_unnecessary(rd) { 227 if is_unused_or_unnecessary(rd) {
228 severity = Some(DiagnosticSeverity::Hint);
228 tags.push(DiagnosticTag::Unnecessary); 229 tags.push(DiagnosticTag::Unnecessary);
229 } 230 }
230 231
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs
index acb1dacb6..ec24ce5e0 100644
--- a/crates/rust-analyzer/src/lsp_ext.rs
+++ b/crates/rust-analyzer/src/lsp_ext.rs
@@ -4,7 +4,6 @@ use std::{collections::HashMap, path::PathBuf};
4 4
5use lsp_types::request::Request; 5use lsp_types::request::Request;
6use lsp_types::{Position, Range, TextDocumentIdentifier}; 6use lsp_types::{Position, Range, TextDocumentIdentifier};
7use rustc_hash::FxHashMap;
8use serde::{Deserialize, Serialize}; 7use serde::{Deserialize, Serialize};
9 8
10pub enum AnalyzerStatus {} 9pub enum AnalyzerStatus {}
@@ -111,7 +110,7 @@ pub enum Runnables {}
111impl Request for Runnables { 110impl Request for Runnables {
112 type Params = RunnablesParams; 111 type Params = RunnablesParams;
113 type Result = Vec<Runnable>; 112 type Result = Vec<Runnable>;
114 const METHOD: &'static str = "rust-analyzer/runnables"; 113 const METHOD: &'static str = "experimental/runnables";
115} 114}
116 115
117#[derive(Serialize, Deserialize, Debug)] 116#[derive(Serialize, Deserialize, Debug)]
@@ -124,13 +123,28 @@ pub struct RunnablesParams {
124#[derive(Deserialize, Serialize, Debug)] 123#[derive(Deserialize, Serialize, Debug)]
125#[serde(rename_all = "camelCase")] 124#[serde(rename_all = "camelCase")]
126pub struct Runnable { 125pub struct Runnable {
127 pub range: Range,
128 pub label: String, 126 pub label: String,
129 pub bin: String, 127 #[serde(skip_serializing_if = "Option::is_none")]
130 pub args: Vec<String>, 128 pub location: Option<lsp_types::LocationLink>,
131 pub extra_args: Vec<String>, 129 pub kind: RunnableKind,
132 pub env: FxHashMap<String, String>, 130 pub args: CargoRunnable,
133 pub cwd: Option<PathBuf>, 131}
132
133#[derive(Serialize, Deserialize, Debug)]
134#[serde(rename_all = "lowercase")]
135pub enum RunnableKind {
136 Cargo,
137}
138
139#[derive(Deserialize, Serialize, Debug)]
140#[serde(rename_all = "camelCase")]
141pub struct CargoRunnable {
142 #[serde(skip_serializing_if = "Option::is_none")]
143 pub workspace_root: Option<PathBuf>,
144 // command, --package and --lib stuff
145 pub cargo_args: Vec<String>,
146 // stuff after --
147 pub executable_args: Vec<String>,
134} 148}
135 149
136pub enum InlayHints {} 150pub enum InlayHints {}
diff --git a/crates/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs
index 1f910ff82..7fd691764 100644
--- a/crates/rust-analyzer/src/main_loop/handlers.rs
+++ b/crates/rust-analyzer/src/main_loop/handlers.rs
@@ -17,15 +17,12 @@ use lsp_types::{
17 SemanticTokensParams, SemanticTokensRangeParams, SemanticTokensRangeResult, 17 SemanticTokensParams, SemanticTokensRangeParams, SemanticTokensRangeResult,
18 SemanticTokensResult, SymbolInformation, TextDocumentIdentifier, Url, WorkspaceEdit, 18 SemanticTokensResult, SymbolInformation, TextDocumentIdentifier, Url, WorkspaceEdit,
19}; 19};
20use ra_cfg::CfgExpr;
21use ra_ide::{ 20use ra_ide::{
22 FileId, FilePosition, FileRange, Query, RangeInfo, Runnable, RunnableKind, SearchScope, 21 FileId, FilePosition, FileRange, Query, RangeInfo, RunnableKind, SearchScope, TextEdit,
23 TextEdit,
24}; 22};
25use ra_prof::profile; 23use ra_prof::profile;
26use ra_project_model::TargetKind; 24use ra_project_model::TargetKind;
27use ra_syntax::{AstNode, SmolStr, SyntaxKind, TextRange, TextSize}; 25use ra_syntax::{AstNode, SyntaxKind, TextRange, TextSize};
28use rustc_hash::FxHashMap;
29use serde::{Deserialize, Serialize}; 26use serde::{Deserialize, Serialize};
30use serde_json::to_value; 27use serde_json::to_value;
31use stdx::format_to; 28use stdx::format_to;
@@ -403,7 +400,7 @@ pub fn handle_runnables(
403 let cargo_spec = CargoTargetSpec::for_file(&world, file_id)?; 400 let cargo_spec = CargoTargetSpec::for_file(&world, file_id)?;
404 for runnable in world.analysis().runnables(file_id)? { 401 for runnable in world.analysis().runnables(file_id)? {
405 if let Some(offset) = offset { 402 if let Some(offset) = offset {
406 if !runnable.range.contains_inclusive(offset) { 403 if !runnable.nav.full_range().contains_inclusive(offset) {
407 continue; 404 continue;
408 } 405 }
409 } 406 }
@@ -416,7 +413,7 @@ pub fn handle_runnables(
416 } 413 }
417 } 414 }
418 } 415 }
419 res.push(to_lsp_runnable(&world, file_id, runnable)?); 416 res.push(to_proto::runnable(&world, file_id, runnable)?);
420 } 417 }
421 418
422 // Add `cargo check` and `cargo test` for the whole package 419 // Add `cargo check` and `cargo test` for the whole package
@@ -424,25 +421,31 @@ pub fn handle_runnables(
424 Some(spec) => { 421 Some(spec) => {
425 for &cmd in ["check", "test"].iter() { 422 for &cmd in ["check", "test"].iter() {
426 res.push(lsp_ext::Runnable { 423 res.push(lsp_ext::Runnable {
427 range: Default::default(),
428 label: format!("cargo {} -p {}", cmd, spec.package), 424 label: format!("cargo {} -p {}", cmd, spec.package),
429 bin: "cargo".to_string(), 425 location: None,
430 args: vec![cmd.to_string(), "--package".to_string(), spec.package.clone()], 426 kind: lsp_ext::RunnableKind::Cargo,
431 extra_args: Vec::new(), 427 args: lsp_ext::CargoRunnable {
432 env: FxHashMap::default(), 428 workspace_root: workspace_root.map(|root| root.to_owned()),
433 cwd: workspace_root.map(|root| root.to_owned()), 429 cargo_args: vec![
430 cmd.to_string(),
431 "--package".to_string(),
432 spec.package.clone(),
433 ],
434 executable_args: Vec::new(),
435 },
434 }) 436 })
435 } 437 }
436 } 438 }
437 None => { 439 None => {
438 res.push(lsp_ext::Runnable { 440 res.push(lsp_ext::Runnable {
439 range: Default::default(),
440 label: "cargo check --workspace".to_string(), 441 label: "cargo check --workspace".to_string(),
441 bin: "cargo".to_string(), 442 location: None,
442 args: vec!["check".to_string(), "--workspace".to_string()], 443 kind: lsp_ext::RunnableKind::Cargo,
443 extra_args: Vec::new(), 444 args: lsp_ext::CargoRunnable {
444 env: FxHashMap::default(), 445 workspace_root: workspace_root.map(|root| root.to_owned()),
445 cwd: workspace_root.map(|root| root.to_owned()), 446 cargo_args: vec!["check".to_string(), "--workspace".to_string()],
447 executable_args: Vec::new(),
448 },
446 }); 449 });
447 } 450 }
448 } 451 }
@@ -784,10 +787,11 @@ pub fn handle_code_lens(
784 } 787 }
785 }; 788 };
786 789
787 let mut r = to_lsp_runnable(&world, file_id, runnable)?; 790 let range = to_proto::range(&line_index, runnable.nav.range());
791 let r = to_proto::runnable(&world, file_id, runnable)?;
788 if world.config.lens.run { 792 if world.config.lens.run {
789 let lens = CodeLens { 793 let lens = CodeLens {
790 range: r.range, 794 range,
791 command: Some(Command { 795 command: Some(Command {
792 title: run_title.to_string(), 796 title: run_title.to_string(),
793 command: "rust-analyzer.runSingle".into(), 797 command: "rust-analyzer.runSingle".into(),
@@ -799,13 +803,8 @@ pub fn handle_code_lens(
799 } 803 }
800 804
801 if debugee && world.config.lens.debug { 805 if debugee && world.config.lens.debug {
802 if r.args[0] == "run" {
803 r.args[0] = "build".into();
804 } else {
805 r.args.push("--no-run".into());
806 }
807 let debug_lens = CodeLens { 806 let debug_lens = CodeLens {
808 range: r.range, 807 range,
809 command: Some(Command { 808 command: Some(Command {
810 title: "Debug".into(), 809 title: "Debug".into(),
811 command: "rust-analyzer.debugSingle".into(), 810 command: "rust-analyzer.debugSingle".into(),
@@ -959,64 +958,6 @@ pub fn publish_diagnostics(world: &WorldSnapshot, file_id: FileId) -> Result<Dia
959 Ok(DiagnosticTask::SetNative(file_id, diagnostics)) 958 Ok(DiagnosticTask::SetNative(file_id, diagnostics))
960} 959}
961 960
962fn to_lsp_runnable(
963 world: &WorldSnapshot,
964 file_id: FileId,
965 runnable: Runnable,
966) -> Result<lsp_ext::Runnable> {
967 let spec = CargoTargetSpec::for_file(world, file_id)?;
968 let target = spec.as_ref().map(|s| s.target.clone());
969 let mut features_needed = vec![];
970 for cfg_expr in &runnable.cfg_exprs {
971 collect_minimal_features_needed(cfg_expr, &mut features_needed);
972 }
973 let (args, extra_args) =
974 CargoTargetSpec::runnable_args(spec, &runnable.kind, &features_needed)?;
975 let line_index = world.analysis().file_line_index(file_id)?;
976 let label = match &runnable.kind {
977 RunnableKind::Test { test_id, .. } => format!("test {}", test_id),
978 RunnableKind::TestMod { path } => format!("test-mod {}", path),
979 RunnableKind::Bench { test_id } => format!("bench {}", test_id),
980 RunnableKind::DocTest { test_id, .. } => format!("doctest {}", test_id),
981 RunnableKind::Bin => {
982 target.map_or_else(|| "run binary".to_string(), |t| format!("run {}", t))
983 }
984 };
985 Ok(lsp_ext::Runnable {
986 range: to_proto::range(&line_index, runnable.range),
987 label,
988 bin: "cargo".to_string(),
989 args,
990 extra_args,
991 env: {
992 let mut m = FxHashMap::default();
993 m.insert("RUST_BACKTRACE".to_string(), "short".to_string());
994 m
995 },
996 cwd: world.workspace_root_for(file_id).map(|root| root.to_owned()),
997 })
998}
999
1000/// Fill minimal features needed
1001fn collect_minimal_features_needed(cfg_expr: &CfgExpr, features: &mut Vec<SmolStr>) {
1002 match cfg_expr {
1003 CfgExpr::KeyValue { key, value } if key == "feature" => features.push(value.clone()),
1004 CfgExpr::All(preds) => {
1005 preds.iter().for_each(|cfg| collect_minimal_features_needed(cfg, features));
1006 }
1007 CfgExpr::Any(preds) => {
1008 for cfg in preds {
1009 let len_features = features.len();
1010 collect_minimal_features_needed(cfg, features);
1011 if len_features != features.len() {
1012 break;
1013 }
1014 }
1015 }
1016 _ => {}
1017 }
1018}
1019
1020pub fn handle_inlay_hints( 961pub fn handle_inlay_hints(
1021 world: WorldSnapshot, 962 world: WorldSnapshot,
1022 params: InlayHintsParams, 963 params: InlayHintsParams,
@@ -1153,54 +1094,3 @@ pub fn handle_semantic_tokens_range(
1153 let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights); 1094 let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights);
1154 Ok(Some(semantic_tokens.into())) 1095 Ok(Some(semantic_tokens.into()))
1155} 1096}
1156
1157#[cfg(test)]
1158mod tests {
1159 use super::*;
1160
1161 use mbe::{ast_to_token_tree, TokenMap};
1162 use ra_cfg::parse_cfg;
1163 use ra_syntax::{
1164 ast::{self, AstNode},
1165 SmolStr,
1166 };
1167
1168 fn get_token_tree_generated(input: &str) -> (tt::Subtree, TokenMap) {
1169 let source_file = ast::SourceFile::parse(input).ok().unwrap();
1170 let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap();
1171 ast_to_token_tree(&tt).unwrap()
1172 }
1173
1174 #[test]
1175 fn test_cfg_expr_minimal_features_needed() {
1176 let (subtree, _) = get_token_tree_generated(r#"#![cfg(feature = "baz")]"#);
1177 let cfg_expr = parse_cfg(&subtree);
1178 let mut min_features = vec![];
1179 collect_minimal_features_needed(&cfg_expr, &mut min_features);
1180
1181 assert_eq!(min_features, vec![SmolStr::new("baz")]);
1182
1183 let (subtree, _) =
1184 get_token_tree_generated(r#"#![cfg(all(feature = "baz", feature = "foo"))]"#);
1185 let cfg_expr = parse_cfg(&subtree);
1186
1187 let mut min_features = vec![];
1188 collect_minimal_features_needed(&cfg_expr, &mut min_features);
1189 assert_eq!(min_features, vec![SmolStr::new("baz"), SmolStr::new("foo")]);
1190
1191 let (subtree, _) =
1192 get_token_tree_generated(r#"#![cfg(any(feature = "baz", feature = "foo", unix))]"#);
1193 let cfg_expr = parse_cfg(&subtree);
1194
1195 let mut min_features = vec![];
1196 collect_minimal_features_needed(&cfg_expr, &mut min_features);
1197 assert_eq!(min_features, vec![SmolStr::new("baz")]);
1198
1199 let (subtree, _) = get_token_tree_generated(r#"#![cfg(foo)]"#);
1200 let cfg_expr = parse_cfg(&subtree);
1201
1202 let mut min_features = vec![];
1203 collect_minimal_features_needed(&cfg_expr, &mut min_features);
1204 assert!(min_features.is_empty());
1205 }
1206}
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index 2fbbb4e63..85304aa87 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -3,13 +3,15 @@ use ra_db::{FileId, FileRange};
3use ra_ide::{ 3use ra_ide::{
4 Assist, CompletionItem, CompletionItemKind, Documentation, FileSystemEdit, Fold, FoldKind, 4 Assist, CompletionItem, CompletionItemKind, Documentation, FileSystemEdit, Fold, FoldKind,
5 FunctionSignature, Highlight, HighlightModifier, HighlightTag, HighlightedRange, Indel, 5 FunctionSignature, Highlight, HighlightModifier, HighlightTag, HighlightedRange, Indel,
6 InlayHint, InlayKind, InsertTextFormat, LineIndex, NavigationTarget, ReferenceAccess, Severity, 6 InlayHint, InlayKind, InsertTextFormat, LineIndex, NavigationTarget, ReferenceAccess, Runnable,
7 SourceChange, SourceFileEdit, TextEdit, 7 RunnableKind, Severity, SourceChange, SourceFileEdit, TextEdit,
8}; 8};
9use ra_syntax::{SyntaxKind, TextRange, TextSize}; 9use ra_syntax::{SyntaxKind, TextRange, TextSize};
10use ra_vfs::LineEndings; 10use ra_vfs::LineEndings;
11 11
12use crate::{lsp_ext, semantic_tokens, world::WorldSnapshot, Result}; 12use crate::{
13 cargo_target_spec::CargoTargetSpec, lsp_ext, semantic_tokens, world::WorldSnapshot, Result,
14};
13 15
14pub(crate) fn position(line_index: &LineIndex, offset: TextSize) -> lsp_types::Position { 16pub(crate) fn position(line_index: &LineIndex, offset: TextSize) -> lsp_types::Position {
15 let line_col = line_index.line_col(offset); 17 let line_col = line_index.line_col(offset);
@@ -627,3 +629,35 @@ pub(crate) fn code_action(world: &WorldSnapshot, assist: Assist) -> Result<lsp_e
627 }; 629 };
628 Ok(res) 630 Ok(res)
629} 631}
632
633pub(crate) fn runnable(
634 world: &WorldSnapshot,
635 file_id: FileId,
636 runnable: Runnable,
637) -> Result<lsp_ext::Runnable> {
638 let spec = CargoTargetSpec::for_file(world, file_id)?;
639 let target = spec.as_ref().map(|s| s.target.clone());
640 let (cargo_args, executable_args) =
641 CargoTargetSpec::runnable_args(spec, &runnable.kind, &runnable.cfg_exprs)?;
642 let label = match &runnable.kind {
643 RunnableKind::Test { test_id, .. } => format!("test {}", test_id),
644 RunnableKind::TestMod { path } => format!("test-mod {}", path),
645 RunnableKind::Bench { test_id } => format!("bench {}", test_id),
646 RunnableKind::DocTest { test_id, .. } => format!("doctest {}", test_id),
647 RunnableKind::Bin => {
648 target.map_or_else(|| "run binary".to_string(), |t| format!("run {}", t))
649 }
650 };
651 let location = location_link(world, None, runnable.nav)?;
652
653 Ok(lsp_ext::Runnable {
654 label,
655 location: Some(location),
656 kind: lsp_ext::RunnableKind::Cargo,
657 args: lsp_ext::CargoRunnable {
658 workspace_root: world.workspace_root_for(file_id).map(|root| root.to_owned()),
659 cargo_args,
660 executable_args,
661 },
662 })
663}
diff --git a/crates/rust-analyzer/tests/heavy_tests/main.rs b/crates/rust-analyzer/tests/heavy_tests/main.rs
index 405ddb362..e18f973b8 100644
--- a/crates/rust-analyzer/tests/heavy_tests/main.rs
+++ b/crates/rust-analyzer/tests/heavy_tests/main.rs
@@ -76,30 +76,33 @@ fn foo() {
76 server.request::<Runnables>( 76 server.request::<Runnables>(
77 RunnablesParams { text_document: server.doc_id("lib.rs"), position: None }, 77 RunnablesParams { text_document: server.doc_id("lib.rs"), position: None },
78 json!([ 78 json!([
79 { 79 {
80 "args": [ "test" ], 80 "args": {
81 "extraArgs": [ "foo", "--nocapture" ], 81 "cargoArgs": ["test"],
82 "bin": "cargo", 82 "executableArgs": ["foo", "--nocapture"],
83 "env": { "RUST_BACKTRACE": "short" }, 83 },
84 "cwd": null, 84 "kind": "cargo",
85 "label": "test foo", 85 "label": "test foo",
86 "range": { 86 "location": {
87 "end": { "character": 1, "line": 2 }, 87 "targetRange": {
88 "start": { "character": 0, "line": 0 } 88 "end": { "character": 1, "line": 2 },
89 } 89 "start": { "character": 0, "line": 0 }
90 }, 90 },
91 { 91 "targetSelectionRange": {
92 "args": ["check", "--workspace"], 92 "end": { "character": 6, "line": 1 },
93 "extraArgs": [], 93 "start": { "character": 3, "line": 1 }
94 "bin": "cargo", 94 },
95 "env": {}, 95 "targetUri": "file:///[..]/lib.rs"
96 "cwd": null, 96 }
97 "label": "cargo check --workspace", 97 },
98 "range": { 98 {
99 "end": { "character": 0, "line": 0 }, 99 "args": {
100 "start": { "character": 0, "line": 0 } 100 "cargoArgs": ["check", "--workspace"],
101 "executableArgs": [],
102 },
103 "kind": "cargo",
104 "label": "cargo check --workspace"
101 } 105 }
102 }
103 ]), 106 ]),
104 ); 107 );
105} 108}
@@ -138,42 +141,44 @@ fn main() {}
138 server.request::<Runnables>( 141 server.request::<Runnables>(
139 RunnablesParams { text_document: server.doc_id("foo/tests/spam.rs"), position: None }, 142 RunnablesParams { text_document: server.doc_id("foo/tests/spam.rs"), position: None },
140 json!([ 143 json!([
141 { 144 {
142 "args": [ "test", "--package", "foo", "--test", "spam" ], 145 "args": {
143 "extraArgs": [ "test_eggs", "--exact", "--nocapture" ], 146 "cargoArgs": ["test", "--package", "foo", "--test", "spam"],
144 "bin": "cargo", 147 "executableArgs": ["test_eggs", "--exact", "--nocapture"],
145 "env": { "RUST_BACKTRACE": "short" }, 148 "workspaceRoot": server.path().join("foo")
146 "label": "test test_eggs",
147 "range": {
148 "end": { "character": 17, "line": 1 },
149 "start": { "character": 0, "line": 0 }
150 },
151 "cwd": server.path().join("foo")
152 }, 149 },
153 { 150 "kind": "cargo",
154 "args": [ "check", "--package", "foo" ], 151 "label": "test test_eggs",
155 "extraArgs": [], 152 "location": {
156 "bin": "cargo", 153 "targetRange": {
157 "env": {}, 154 "end": { "character": 17, "line": 1 },
158 "label": "cargo check -p foo",
159 "range": {
160 "end": { "character": 0, "line": 0 },
161 "start": { "character": 0, "line": 0 } 155 "start": { "character": 0, "line": 0 }
162 }, 156 },
163 "cwd": server.path().join("foo") 157 "targetSelectionRange": {
164 }, 158 "end": { "character": 12, "line": 1 },
165 { 159 "start": { "character": 3, "line": 1 }
166 "args": [ "test", "--package", "foo" ],
167 "extraArgs": [],
168 "bin": "cargo",
169 "env": {},
170 "label": "cargo test -p foo",
171 "range": {
172 "end": { "character": 0, "line": 0 },
173 "start": { "character": 0, "line": 0 }
174 }, 160 },
175 "cwd": server.path().join("foo") 161 "targetUri": "file:///[..]/tests/spam.rs"
176 } 162 }
163 },
164 {
165 "args": {
166 "cargoArgs": ["check", "--package", "foo"],
167 "executableArgs": [],
168 "workspaceRoot": server.path().join("foo")
169 },
170 "kind": "cargo",
171 "label": "cargo check -p foo"
172 },
173 {
174 "args": {
175 "cargoArgs": ["test", "--package", "foo"],
176 "executableArgs": [],
177 "workspaceRoot": server.path().join("foo")
178 },
179 "kind": "cargo",
180 "label": "cargo test -p foo"
181 }
177 ]), 182 ]),
178 ); 183 );
179} 184}
diff --git a/docs/dev/README.md b/docs/dev/README.md
index 65cc9fc12..1de5a2aab 100644
--- a/docs/dev/README.md
+++ b/docs/dev/README.md
@@ -30,7 +30,7 @@ https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Fwg-rls-2.2E0
30 30
31* [good-first-issue](https://github.com/rust-analyzer/rust-analyzer/labels/good%20first%20issue) 31* [good-first-issue](https://github.com/rust-analyzer/rust-analyzer/labels/good%20first%20issue)
32 are good issues to get into the project. 32 are good issues to get into the project.
33* [E-mentor](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-mentor) 33* [E-has-instructions](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-has-instructions)
34 issues have links to the code in question and tests. 34 issues have links to the code in question and tests.
35* [E-easy](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-easy), 35* [E-easy](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-easy),
36 [E-medium](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-medium), 36 [E-medium](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-medium),
@@ -117,6 +117,109 @@ Additionally, I use `cargo run --release -p rust-analyzer -- analysis-stats
117path/to/some/rust/crate` to run a batch analysis. This is primarily useful for 117path/to/some/rust/crate` to run a batch analysis. This is primarily useful for
118performance optimizations, or for bug minimization. 118performance optimizations, or for bug minimization.
119 119
120# Code Style & Review Process
121
122Our approach to "clean code" is two fold:
123
124* We generally don't block PRs on style changes.
125* At the same time, all code in rust-analyzer is constantly refactored.
126
127It is explicitly OK for reviewer to flag only some nits in the PR, and than send a follow up cleanup PR for things which are easier to explain by example, cc-ing the original author.
128Sending small cleanup PRs (like rename a single local variable) is encouraged.
129
130## Scale of Changes
131
132Everyone knows that it's better to send small & focused pull requests.
133The problem is, sometimes you *have* to, eg, rewrite the whole compiler, and that just doesn't fit into a set of isolated PRs.
134
135The main thing too keep an eye on is the boundaries between various components.
136There are three kinds of changes:
137
1381. Internals of a single component are changed.
139 Specifically, you don't change any `pub` items.
140 A good example here would be an addition of a new assist.
141
1422. API of a component is expanded.
143 Specifically, you add a new `pub` function which wasn't there before.
144 A good example here would be expansion of assist API, for example, to implement lazy assists or assists groups.
145
1463. A new dependency between components is introduced.
147 Specifically, you add a `pub use` reexport from another crate or you add a new line to `[dependencies]` section of `Cargo.toml`.
148 A good example here would be adding reference search capability to the assists crates.
149
150For the first group, the change is generally merged as long as:
151
152* it works for the happy case,
153* it has tests,
154* it doesn't panic for unhappy case.
155
156For the second group, the change would be subjected to quite a bit of scrutiny and iteration.
157The new API needs to be right (or at least easy to change later).
158The actual implementation doesn't matter that much.
159It's very important to minimize the amount of changed lines of code for changes of the second kind.
160Often, you start doing change of the first kind, only to realise that you need to elevate to a change of the second kind.
161In this case, we'll probably ask you to split API changes into a separate PR.
162
163Changes of the third group should be pretty rare, so we don't specify any specific process for them.
164That said, adding an innocent-looking `pub use` is a very simple way to break encapsulation, keep an eye on it!
165
166Note: if you enjoyed this abstract hand-waving about boundaries, you might appreciate
167https://www.tedinski.com/2018/02/06/system-boundaries.html
168
169## Order of Imports
170
171We separate import groups with blank lines
172
173```
174mod x;
175mod y;
176
177use std::{ ... }
178
179use crate_foo::{ ... }
180use crate_bar::{ ... }
181
182use crate::{}
183
184use super::{} // but prefer `use crate::`
185```
186
187## Order of Items
188
189Optimize for the reader who sees the file for the first time, and wants to get the general idea about what's going on.
190People read things from top to bottom, so place most important things first.
191
192Specifically, if all items except one are private, always put the non-private item on top.
193
194Put `struct`s and `enum`s first, functions and impls last.
195
196Do
197
198```
199// Good
200struct Foo {
201 bars: Vec<Bar>
202}
203
204struct Bar;
205```
206
207rather than
208
209```
210// Not as good
211struct Bar;
212
213struct Foo {
214 bars: Vec<Bar>
215}
216```
217
218## Documentation
219
220For `.md` and `.adoc` files, prefer a sentence-per-line format, don't wrap lines.
221If the line is too long, you want to split the sentence in two :-)
222
120# Logging 223# Logging
121 224
122Logging is done by both rust-analyzer and VS Code, so it might be tricky to 225Logging is done by both rust-analyzer and VS Code, so it might be tricky to
diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md
index c57a93f12..647cf6107 100644
--- a/docs/dev/lsp-extensions.md
+++ b/docs/dev/lsp-extensions.md
@@ -311,6 +311,50 @@ Moreover, it would be cool if editors didn't need to implement even basic langua
311 This is how `SelectionRange` request works. 311 This is how `SelectionRange` request works.
312* Alternatively, should we perhaps flag certain `SelectionRange`s as being brace pairs? 312* Alternatively, should we perhaps flag certain `SelectionRange`s as being brace pairs?
313 313
314## Runnables
315
316**Issue:** https://github.com/microsoft/language-server-protocol/issues/944
317
318**Server Capability:** `{ "runnables": { "kinds": string[] } }`
319
320This request is send from client to server to get the list of things that can be run (tests, binaries, `cargo check -p`).
321
322**Method:** `experimental/runnables`
323
324**Request:**
325
326```typescript
327interface RunnablesParams {
328 textDocument: TextDocumentIdentifier;
329 /// If null, compute runnables for the whole file.
330 position?: Position;
331}
332```
333
334**Response:** `Runnable[]`
335
336```typescript
337interface Runnable {
338 label: string;
339 /// If this Runnable is associated with a specific function/module, etc, the location of this item
340 location?: LocationLink;
341 /// Running things is necessary technology specific, `kind` needs to be advertised via server capabilities,
342 // the type of `args` is specific to `kind`. The actual running is handled by the client.
343 kind: string;
344 args: any;
345}
346```
347
348rust-analyzer supports only one `kind`, `"cargo"`. The `args` for `"cargo"` look like this:
349
350```typescript
351{
352 workspaceRoot?: string;
353 cargoArgs: string[];
354 executableArgs: string[];
355}
356```
357
314## Analyzer Status 358## Analyzer Status
315 359
316**Method:** `rust-analyzer/analyzerStatus` 360**Method:** `rust-analyzer/analyzerStatus`
@@ -399,39 +443,3 @@ interface InlayHint {
399 label: string, 443 label: string,
400} 444}
401``` 445```
402
403## Runnables
404
405**Method:** `rust-analyzer/runnables`
406
407This request is send from client to server to get the list of things that can be run (tests, binaries, `cargo check -p`).
408Note that we plan to move this request to `experimental/runnables`, as it is not really Rust-specific, but the current API is not necessary the right one.
409Upstream issue: https://github.com/microsoft/language-server-protocol/issues/944
410
411**Request:**
412
413```typescript
414interface RunnablesParams {
415 textDocument: TextDocumentIdentifier;
416 /// If null, compute runnables for the whole file.
417 position?: Position;
418}
419```
420
421**Response:** `Runnable[]`
422
423```typescript
424interface Runnable {
425 /// The range this runnable is applicable for.
426 range: lc.Range;
427 /// The label to show in the UI.
428 label: string;
429 /// The following fields describe a process to spawn.
430 bin: string;
431 args: string[];
432 /// Args for cargo after `--`.
433 extraArgs: string[];
434 env: { [key: string]: string };
435 cwd: string | null;
436}
437```
diff --git a/editors/code/package.json b/editors/code/package.json
index 75dbafc05..d8f4287fd 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -238,7 +238,7 @@
238 }, 238 },
239 "rust-analyzer.cargo.allFeatures": { 239 "rust-analyzer.cargo.allFeatures": {
240 "type": "boolean", 240 "type": "boolean",
241 "default": true, 241 "default": false,
242 "description": "Activate all available features" 242 "description": "Activate all available features"
243 }, 243 },
244 "rust-analyzer.cargo.features": { 244 "rust-analyzer.cargo.features": {
@@ -319,7 +319,7 @@
319 }, 319 },
320 "rust-analyzer.checkOnSave.allFeatures": { 320 "rust-analyzer.checkOnSave.allFeatures": {
321 "type": "boolean", 321 "type": "boolean",
322 "default": true, 322 "default": false,
323 "markdownDescription": "Check with all features (will be passed as `--all-features`)" 323 "markdownDescription": "Check with all features (will be passed as `--all-features`)"
324 }, 324 },
325 "rust-analyzer.inlayHints.enable": { 325 "rust-analyzer.inlayHints.enable": {
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts
index 86302db37..534d2a984 100644
--- a/editors/code/src/commands.ts
+++ b/editors/code/src/commands.ts
@@ -8,6 +8,7 @@ import { spawnSync } from 'child_process';
8import { RunnableQuickPick, selectRunnable, createTask } from './run'; 8import { RunnableQuickPick, selectRunnable, createTask } from './run';
9import { AstInspector } from './ast_inspector'; 9import { AstInspector } from './ast_inspector';
10import { isRustDocument, sleep, isRustEditor } from './util'; 10import { isRustDocument, sleep, isRustEditor } from './util';
11import { startDebugSession, makeDebugConfig } from './debug';
11 12
12export * from './ast_inspector'; 13export * from './ast_inspector';
13export * from './run'; 14export * from './run';
@@ -197,20 +198,6 @@ export function toggleInlayHints(ctx: Ctx): Cmd {
197 }; 198 };
198} 199}
199 200
200export function run(ctx: Ctx): Cmd {
201 let prevRunnable: RunnableQuickPick | undefined;
202
203 return async () => {
204 const item = await selectRunnable(ctx, prevRunnable);
205 if (!item) return;
206
207 item.detail = 'rerun';
208 prevRunnable = item;
209 const task = createTask(item.runnable);
210 return await vscode.tasks.executeTask(task);
211 };
212}
213
214// Opens the virtual file that will show the syntax tree 201// Opens the virtual file that will show the syntax tree
215// 202//
216// The contents of the file come from the `TextDocumentContentProvider` 203// The contents of the file come from the `TextDocumentContentProvider`
@@ -368,3 +355,62 @@ export function applySnippetWorkspaceEditCommand(_ctx: Ctx): Cmd {
368 await applySnippetWorkspaceEdit(edit); 355 await applySnippetWorkspaceEdit(edit);
369 }; 356 };
370} 357}
358
359export function run(ctx: Ctx): Cmd {
360 let prevRunnable: RunnableQuickPick | undefined;
361
362 return async () => {
363 const item = await selectRunnable(ctx, prevRunnable);
364 if (!item) return;
365
366 item.detail = 'rerun';
367 prevRunnable = item;
368 const task = createTask(item.runnable);
369 return await vscode.tasks.executeTask(task);
370 };
371}
372
373export function runSingle(ctx: Ctx): Cmd {
374 return async (runnable: ra.Runnable) => {
375 const editor = ctx.activeRustEditor;
376 if (!editor) return;
377
378 const task = createTask(runnable);
379 task.group = vscode.TaskGroup.Build;
380 task.presentationOptions = {
381 reveal: vscode.TaskRevealKind.Always,
382 panel: vscode.TaskPanelKind.Dedicated,
383 clear: true,
384 };
385
386 return vscode.tasks.executeTask(task);
387 };
388}
389
390export function debug(ctx: Ctx): Cmd {
391 let prevDebuggee: RunnableQuickPick | undefined;
392
393 return async () => {
394 const item = await selectRunnable(ctx, prevDebuggee, true);
395 if (!item) return;
396
397 item.detail = 'restart';
398 prevDebuggee = item;
399 return await startDebugSession(ctx, item.runnable);
400 };
401}
402
403export function debugSingle(ctx: Ctx): Cmd {
404 return async (config: ra.Runnable) => {
405 await startDebugSession(ctx, config);
406 };
407}
408
409export function newDebugConfig(ctx: Ctx): Cmd {
410 return async () => {
411 const item = await selectRunnable(ctx, undefined, true, false);
412 if (!item) return;
413
414 await makeDebugConfig(ctx, item.runnable);
415 };
416}
diff --git a/editors/code/src/debug.ts b/editors/code/src/debug.ts
index 027504ecd..a0c9b3ab2 100644
--- a/editors/code/src/debug.ts
+++ b/editors/code/src/debug.ts
@@ -3,46 +3,59 @@ import * as vscode from 'vscode';
3import * as path from 'path'; 3import * as path from 'path';
4import * as ra from './lsp_ext'; 4import * as ra from './lsp_ext';
5 5
6import { Cargo } from './cargo'; 6import { Cargo } from './toolchain';
7import { Ctx } from "./ctx"; 7import { Ctx } from "./ctx";
8 8
9const debugOutput = vscode.window.createOutputChannel("Debug"); 9const debugOutput = vscode.window.createOutputChannel("Debug");
10type DebugConfigProvider = (config: ra.Runnable, executable: string, sourceFileMap?: Record<string, string>) => vscode.DebugConfiguration; 10type DebugConfigProvider = (config: ra.Runnable, executable: string, sourceFileMap?: Record<string, string>) => vscode.DebugConfiguration;
11 11
12function getLldbDebugConfig(config: ra.Runnable, executable: string, sourceFileMap?: Record<string, string>): vscode.DebugConfiguration { 12export async function makeDebugConfig(ctx: Ctx, runnable: ra.Runnable): Promise<void> {
13 return { 13 const scope = ctx.activeRustEditor?.document.uri;
14 type: "lldb", 14 if (!scope) return;
15 request: "launch",
16 name: config.label,
17 program: executable,
18 args: config.extraArgs,
19 cwd: config.cwd,
20 sourceMap: sourceFileMap,
21 sourceLanguages: ["rust"]
22 };
23}
24 15
25function getCppvsDebugConfig(config: ra.Runnable, executable: string, sourceFileMap?: Record<string, string>): vscode.DebugConfiguration { 16 const debugConfig = await getDebugConfiguration(ctx, runnable);
26 return { 17 if (!debugConfig) return;
27 type: (os.platform() === "win32") ? "cppvsdbg" : "cppdbg", 18
28 request: "launch", 19 const wsLaunchSection = vscode.workspace.getConfiguration("launch", scope);
29 name: config.label, 20 const configurations = wsLaunchSection.get<any[]>("configurations") || [];
30 program: executable, 21
31 args: config.extraArgs, 22 const index = configurations.findIndex(c => c.name === debugConfig.name);
32 cwd: config.cwd, 23 if (index !== -1) {
33 sourceFileMap: sourceFileMap, 24 const answer = await vscode.window.showErrorMessage(`Launch configuration '${debugConfig.name}' already exists!`, 'Cancel', 'Update');
34 }; 25 if (answer === "Cancel") return;
26
27 configurations[index] = debugConfig;
28 } else {
29 configurations.push(debugConfig);
30 }
31
32 await wsLaunchSection.update("configurations", configurations);
35} 33}
36 34
37async function getDebugExecutable(config: ra.Runnable): Promise<string> { 35export async function startDebugSession(ctx: Ctx, runnable: ra.Runnable): Promise<boolean> {
38 const cargo = new Cargo(config.cwd || '.', debugOutput); 36 let debugConfig: vscode.DebugConfiguration | undefined = undefined;
39 const executable = await cargo.executableFromArgs(config.args); 37 let message = "";
40 38
41 // if we are here, there were no compilation errors. 39 const wsLaunchSection = vscode.workspace.getConfiguration("launch");
42 return executable; 40 const configurations = wsLaunchSection.get<any[]>("configurations") || [];
41
42 const index = configurations.findIndex(c => c.name === runnable.label);
43 if (-1 !== index) {
44 debugConfig = configurations[index];
45 message = " (from launch.json)";
46 debugOutput.clear();
47 } else {
48 debugConfig = await getDebugConfiguration(ctx, runnable);
49 }
50
51 if (!debugConfig) return false;
52
53 debugOutput.appendLine(`Launching debug configuration${message}:`);
54 debugOutput.appendLine(JSON.stringify(debugConfig, null, 2));
55 return vscode.debug.startDebugging(undefined, debugConfig);
43} 56}
44 57
45export async function getDebugConfiguration(ctx: Ctx, config: ra.Runnable): Promise<vscode.DebugConfiguration | undefined> { 58async function getDebugConfiguration(ctx: Ctx, runnable: ra.Runnable): Promise<vscode.DebugConfiguration | undefined> {
46 const editor = ctx.activeRustEditor; 59 const editor = ctx.activeRustEditor;
47 if (!editor) return; 60 if (!editor) return;
48 61
@@ -78,8 +91,8 @@ export async function getDebugConfiguration(ctx: Ctx, config: ra.Runnable): Prom
78 return path.normalize(p).replace(wsFolder, '${workspaceRoot}'); 91 return path.normalize(p).replace(wsFolder, '${workspaceRoot}');
79 } 92 }
80 93
81 const executable = await getDebugExecutable(config); 94 const executable = await getDebugExecutable(runnable);
82 const debugConfig = knownEngines[debugEngine.id](config, simplifyPath(executable), debugOptions.sourceFileMap); 95 const debugConfig = knownEngines[debugEngine.id](runnable, simplifyPath(executable), debugOptions.sourceFileMap);
83 if (debugConfig.type in debugOptions.engineSettings) { 96 if (debugConfig.type in debugOptions.engineSettings) {
84 const settingsMap = (debugOptions.engineSettings as any)[debugConfig.type]; 97 const settingsMap = (debugOptions.engineSettings as any)[debugConfig.type];
85 for (var key in settingsMap) { 98 for (var key in settingsMap) {
@@ -100,25 +113,35 @@ export async function getDebugConfiguration(ctx: Ctx, config: ra.Runnable): Prom
100 return debugConfig; 113 return debugConfig;
101} 114}
102 115
103export async function startDebugSession(ctx: Ctx, config: ra.Runnable): Promise<boolean> { 116async function getDebugExecutable(runnable: ra.Runnable): Promise<string> {
104 let debugConfig: vscode.DebugConfiguration | undefined = undefined; 117 const cargo = new Cargo(runnable.args.workspaceRoot || '.', debugOutput);
105 let message = ""; 118 const executable = await cargo.executableFromArgs(runnable.args.cargoArgs);
106
107 const wsLaunchSection = vscode.workspace.getConfiguration("launch");
108 const configurations = wsLaunchSection.get<any[]>("configurations") || [];
109 119
110 const index = configurations.findIndex(c => c.name === config.label); 120 // if we are here, there were no compilation errors.
111 if (-1 !== index) { 121 return executable;
112 debugConfig = configurations[index]; 122}
113 message = " (from launch.json)";
114 debugOutput.clear();
115 } else {
116 debugConfig = await getDebugConfiguration(ctx, config);
117 }
118 123
119 if (!debugConfig) return false; 124function getLldbDebugConfig(runnable: ra.Runnable, executable: string, sourceFileMap?: Record<string, string>): vscode.DebugConfiguration {
125 return {
126 type: "lldb",
127 request: "launch",
128 name: runnable.label,
129 program: executable,
130 args: runnable.args.executableArgs,
131 cwd: runnable.args.workspaceRoot,
132 sourceMap: sourceFileMap,
133 sourceLanguages: ["rust"]
134 };
135}
120 136
121 debugOutput.appendLine(`Launching debug configuration${message}:`); 137function getCppvsDebugConfig(runnable: ra.Runnable, executable: string, sourceFileMap?: Record<string, string>): vscode.DebugConfiguration {
122 debugOutput.appendLine(JSON.stringify(debugConfig, null, 2)); 138 return {
123 return vscode.debug.startDebugging(undefined, debugConfig); 139 type: (os.platform() === "win32") ? "cppvsdbg" : "cppdbg",
140 request: "launch",
141 name: runnable.label,
142 program: executable,
143 args: runnable.args.executableArgs,
144 cwd: runnable.args.workspaceRoot,
145 sourceFileMap: sourceFileMap,
146 };
124} 147}
diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts
index 4da12eb30..c51acfccb 100644
--- a/editors/code/src/lsp_ext.ts
+++ b/editors/code/src/lsp_ext.ts
@@ -45,16 +45,18 @@ export interface RunnablesParams {
45 textDocument: lc.TextDocumentIdentifier; 45 textDocument: lc.TextDocumentIdentifier;
46 position: lc.Position | null; 46 position: lc.Position | null;
47} 47}
48
48export interface Runnable { 49export interface Runnable {
49 range: lc.Range;
50 label: string; 50 label: string;
51 bin: string; 51 location?: lc.LocationLink;
52 args: string[]; 52 kind: "cargo";
53 extraArgs: string[]; 53 args: {
54 env: { [key: string]: string }; 54 workspaceRoot?: string;
55 cwd: string | null; 55 cargoArgs: string[];
56 executableArgs: string[];
57 };
56} 58}
57export const runnables = new lc.RequestType<RunnablesParams, Runnable[], void>("rust-analyzer/runnables"); 59export const runnables = new lc.RequestType<RunnablesParams, Runnable[], void>("experimental/runnables");
58 60
59export type InlayHint = InlayHint.TypeHint | InlayHint.ParamHint | InlayHint.ChainingHint; 61export type InlayHint = InlayHint.TypeHint | InlayHint.ParamHint | InlayHint.ChainingHint;
60 62
diff --git a/editors/code/src/run.ts b/editors/code/src/run.ts
index 8e1ba83ed..5c790741f 100644
--- a/editors/code/src/run.ts
+++ b/editors/code/src/run.ts
@@ -1,9 +1,10 @@
1import * as vscode from 'vscode'; 1import * as vscode from 'vscode';
2import * as lc from 'vscode-languageclient'; 2import * as lc from 'vscode-languageclient';
3import * as ra from './lsp_ext'; 3import * as ra from './lsp_ext';
4import * as toolchain from "./toolchain";
4 5
5import { Ctx, Cmd } from './ctx'; 6import { Ctx } from './ctx';
6import { startDebugSession, getDebugConfiguration } from './debug'; 7import { makeDebugConfig } from './debug';
7 8
8const quickPickButtons = [{ iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configurtation." }]; 9const quickPickButtons = [{ iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configurtation." }];
9 10
@@ -64,7 +65,7 @@ export async function selectRunnable(ctx: Ctx, prevRunnable?: RunnableQuickPick,
64 quickPick.onDidHide(() => close()), 65 quickPick.onDidHide(() => close()),
65 quickPick.onDidAccept(() => close(quickPick.selectedItems[0])), 66 quickPick.onDidAccept(() => close(quickPick.selectedItems[0])),
66 quickPick.onDidTriggerButton((_button) => { 67 quickPick.onDidTriggerButton((_button) => {
67 (async () => await makeDebugConfig(ctx, quickPick.activeItems[0]))(); 68 (async () => await makeDebugConfig(ctx, quickPick.activeItems[0].runnable))();
68 close(); 69 close();
69 }), 70 }),
70 quickPick.onDidChangeActive((active) => { 71 quickPick.onDidChangeActive((active) => {
@@ -83,74 +84,6 @@ export async function selectRunnable(ctx: Ctx, prevRunnable?: RunnableQuickPick,
83 }); 84 });
84} 85}
85 86
86export function runSingle(ctx: Ctx): Cmd {
87 return async (runnable: ra.Runnable) => {
88 const editor = ctx.activeRustEditor;
89 if (!editor) return;
90
91 const task = createTask(runnable);
92 task.group = vscode.TaskGroup.Build;
93 task.presentationOptions = {
94 reveal: vscode.TaskRevealKind.Always,
95 panel: vscode.TaskPanelKind.Dedicated,
96 clear: true,
97 };
98
99 return vscode.tasks.executeTask(task);
100 };
101}
102
103export function debug(ctx: Ctx): Cmd {
104 let prevDebuggee: RunnableQuickPick | undefined;
105
106 return async () => {
107 const item = await selectRunnable(ctx, prevDebuggee, true);
108 if (!item) return;
109
110 item.detail = 'restart';
111 prevDebuggee = item;
112 return await startDebugSession(ctx, item.runnable);
113 };
114}
115
116export function debugSingle(ctx: Ctx): Cmd {
117 return async (config: ra.Runnable) => {
118 await startDebugSession(ctx, config);
119 };
120}
121
122async function makeDebugConfig(ctx: Ctx, item: RunnableQuickPick): Promise<void> {
123 const scope = ctx.activeRustEditor?.document.uri;
124 if (!scope) return;
125
126 const debugConfig = await getDebugConfiguration(ctx, item.runnable);
127 if (!debugConfig) return;
128
129 const wsLaunchSection = vscode.workspace.getConfiguration("launch", scope);
130 const configurations = wsLaunchSection.get<any[]>("configurations") || [];
131
132 const index = configurations.findIndex(c => c.name === debugConfig.name);
133 if (index !== -1) {
134 const answer = await vscode.window.showErrorMessage(`Launch configuration '${debugConfig.name}' already exists!`, 'Cancel', 'Update');
135 if (answer === "Cancel") return;
136
137 configurations[index] = debugConfig;
138 } else {
139 configurations.push(debugConfig);
140 }
141
142 await wsLaunchSection.update("configurations", configurations);
143}
144
145export function newDebugConfig(ctx: Ctx): Cmd {
146 return async () => {
147 const item = await selectRunnable(ctx, undefined, true, false);
148 if (!item) return;
149
150 await makeDebugConfig(ctx, item);
151 };
152}
153
154export class RunnableQuickPick implements vscode.QuickPickItem { 87export class RunnableQuickPick implements vscode.QuickPickItem {
155 public label: string; 88 public label: string;
156 public description?: string | undefined; 89 public description?: string | undefined;
@@ -170,18 +103,27 @@ interface CargoTaskDefinition extends vscode.TaskDefinition {
170 env?: { [key: string]: string }; 103 env?: { [key: string]: string };
171} 104}
172 105
173export function createTask(spec: ra.Runnable): vscode.Task { 106export function createTask(runnable: ra.Runnable): vscode.Task {
174 const TASK_SOURCE = 'Rust'; 107 const TASK_SOURCE = 'Rust';
108
109 let command;
110 switch (runnable.kind) {
111 case "cargo": command = toolchain.getPathForExecutable("cargo");
112 }
113 const args = runnable.args.cargoArgs;
114 if (runnable.args.executableArgs.length > 0) {
115 args.push('--', ...runnable.args.executableArgs);
116 }
175 const definition: CargoTaskDefinition = { 117 const definition: CargoTaskDefinition = {
176 type: 'cargo', 118 type: 'cargo',
177 label: spec.label, 119 label: runnable.label,
178 command: spec.bin, 120 command,
179 args: spec.extraArgs ? [...spec.args, '--', ...spec.extraArgs] : spec.args, 121 args,
180 env: Object.assign({}, process.env, spec.env), 122 env: Object.assign({}, process.env as { [key: string]: string }, { "RUST_BACKTRACE": "short" }),
181 }; 123 };
182 124
183 const execOption: vscode.ShellExecutionOptions = { 125 const execOption: vscode.ShellExecutionOptions = {
184 cwd: spec.cwd || '.', 126 cwd: runnable.args.workspaceRoot || '.',
185 env: definition.env, 127 env: definition.env,
186 }; 128 };
187 const exec = new vscode.ShellExecution( 129 const exec = new vscode.ShellExecution(
diff --git a/editors/code/src/tasks.ts b/editors/code/src/tasks.ts
index 1366c76d6..9748824df 100644
--- a/editors/code/src/tasks.ts
+++ b/editors/code/src/tasks.ts
@@ -1,4 +1,5 @@
1import * as vscode from 'vscode'; 1import * as vscode from 'vscode';
2import * as toolchain from "./toolchain";
2 3
3// This ends up as the `type` key in tasks.json. RLS also uses `cargo` and 4// This ends up as the `type` key in tasks.json. RLS also uses `cargo` and
4// our configuration should be compatible with it so use the same key. 5// our configuration should be compatible with it so use the same key.
@@ -24,6 +25,8 @@ class CargoTaskProvider implements vscode.TaskProvider {
24 // set of tasks that always exist. These tasks cannot be removed in 25 // set of tasks that always exist. These tasks cannot be removed in
25 // tasks.json - only tweaked. 26 // tasks.json - only tweaked.
26 27
28 const cargoPath = toolchain.cargoPath();
29
27 return [ 30 return [
28 { command: 'build', group: vscode.TaskGroup.Build }, 31 { command: 'build', group: vscode.TaskGroup.Build },
29 { command: 'check', group: vscode.TaskGroup.Build }, 32 { command: 'check', group: vscode.TaskGroup.Build },
@@ -46,7 +49,7 @@ class CargoTaskProvider implements vscode.TaskProvider {
46 `cargo ${command}`, 49 `cargo ${command}`,
47 'rust', 50 'rust',
48 // What to do when this command is executed. 51 // What to do when this command is executed.
49 new vscode.ShellExecution('cargo', [command]), 52 new vscode.ShellExecution(cargoPath, [command]),
50 // Problem matchers. 53 // Problem matchers.
51 ['$rustc'], 54 ['$rustc'],
52 ); 55 );
@@ -80,4 +83,4 @@ class CargoTaskProvider implements vscode.TaskProvider {
80export function activateTaskProvider(target: vscode.WorkspaceFolder): vscode.Disposable { 83export function activateTaskProvider(target: vscode.WorkspaceFolder): vscode.Disposable {
81 const provider = new CargoTaskProvider(target); 84 const provider = new CargoTaskProvider(target);
82 return vscode.tasks.registerTaskProvider(TASK_TYPE, provider); 85 return vscode.tasks.registerTaskProvider(TASK_TYPE, provider);
83} \ No newline at end of file 86}
diff --git a/editors/code/src/cargo.ts b/editors/code/src/toolchain.ts
index a55b2f860..80a7915e9 100644
--- a/editors/code/src/cargo.ts
+++ b/editors/code/src/toolchain.ts
@@ -1,9 +1,10 @@
1import * as cp from 'child_process'; 1import * as cp from 'child_process';
2import * as os from 'os'; 2import * as os from 'os';
3import * as path from 'path'; 3import * as path from 'path';
4import * as fs from 'fs';
4import * as readline from 'readline'; 5import * as readline from 'readline';
5import { OutputChannel } from 'vscode'; 6import { OutputChannel } from 'vscode';
6import { isValidExecutable } from './util'; 7import { log, memoize } from './util';
7 8
8interface CompilationArtifact { 9interface CompilationArtifact {
9 fileName: string; 10 fileName: string;
@@ -17,33 +18,34 @@ export interface ArtifactSpec {
17 filter?: (artifacts: CompilationArtifact[]) => CompilationArtifact[]; 18 filter?: (artifacts: CompilationArtifact[]) => CompilationArtifact[];
18} 19}
19 20
20export function artifactSpec(args: readonly string[]): ArtifactSpec { 21export class Cargo {
21 const cargoArgs = [...args, "--message-format=json"]; 22 constructor(readonly rootFolder: string, readonly output: OutputChannel) { }
22 23
23 // arguments for a runnable from the quick pick should be updated. 24 // Made public for testing purposes
24 // see crates\rust-analyzer\src\main_loop\handlers.rs, handle_code_lens 25 static artifactSpec(args: readonly string[]): ArtifactSpec {
25 switch (cargoArgs[0]) { 26 const cargoArgs = [...args, "--message-format=json"];
26 case "run": cargoArgs[0] = "build"; break; 27
27 case "test": { 28 // arguments for a runnable from the quick pick should be updated.
28 if (!cargoArgs.includes("--no-run")) { 29 // see crates\rust-analyzer\src\main_loop\handlers.rs, handle_code_lens
29 cargoArgs.push("--no-run"); 30 switch (cargoArgs[0]) {
31 case "run": cargoArgs[0] = "build"; break;
32 case "test": {
33 if (!cargoArgs.includes("--no-run")) {
34 cargoArgs.push("--no-run");
35 }
36 break;
30 } 37 }
31 break;
32 } 38 }
33 }
34 39
35 const result: ArtifactSpec = { cargoArgs: cargoArgs }; 40 const result: ArtifactSpec = { cargoArgs: cargoArgs };
36 if (cargoArgs[0] === "test") { 41 if (cargoArgs[0] === "test") {
37 // for instance, `crates\rust-analyzer\tests\heavy_tests\main.rs` tests 42 // for instance, `crates\rust-analyzer\tests\heavy_tests\main.rs` tests
38 // produce 2 artifacts: {"kind": "bin"} and {"kind": "test"} 43 // produce 2 artifacts: {"kind": "bin"} and {"kind": "test"}
39 result.filter = (artifacts) => artifacts.filter(it => it.isTest); 44 result.filter = (artifacts) => artifacts.filter(it => it.isTest);
40 } 45 }
41
42 return result;
43}
44 46
45export class Cargo { 47 return result;
46 constructor(readonly rootFolder: string, readonly output: OutputChannel) { } 48 }
47 49
48 private async getArtifacts(spec: ArtifactSpec): Promise<CompilationArtifact[]> { 50 private async getArtifacts(spec: ArtifactSpec): Promise<CompilationArtifact[]> {
49 const artifacts: CompilationArtifact[] = []; 51 const artifacts: CompilationArtifact[] = [];
@@ -77,7 +79,7 @@ export class Cargo {
77 } 79 }
78 80
79 async executableFromArgs(args: readonly string[]): Promise<string> { 81 async executableFromArgs(args: readonly string[]): Promise<string> {
80 const artifacts = await this.getArtifacts(artifactSpec(args)); 82 const artifacts = await this.getArtifacts(Cargo.artifactSpec(args));
81 83
82 if (artifacts.length === 0) { 84 if (artifacts.length === 0) {
83 throw new Error('No compilation artifacts'); 85 throw new Error('No compilation artifacts');
@@ -94,14 +96,7 @@ export class Cargo {
94 onStderrString: (data: string) => void 96 onStderrString: (data: string) => void
95 ): Promise<number> { 97 ): Promise<number> {
96 return new Promise((resolve, reject) => { 98 return new Promise((resolve, reject) => {
97 let cargoPath; 99 const cargo = cp.spawn(cargoPath(), cargoArgs, {
98 try {
99 cargoPath = getCargoPathOrFail();
100 } catch (err) {
101 return reject(err);
102 }
103
104 const cargo = cp.spawn(cargoPath, cargoArgs, {
105 stdio: ['ignore', 'pipe', 'pipe'], 100 stdio: ['ignore', 'pipe', 'pipe'],
106 cwd: this.rootFolder 101 cwd: this.rootFolder
107 }); 102 });
@@ -126,26 +121,54 @@ export class Cargo {
126 } 121 }
127} 122}
128 123
129// Mirrors `ra_env::get_path_for_executable` implementation 124/** Mirrors `ra_toolchain::cargo()` implementation */
130function getCargoPathOrFail(): string { 125export function cargoPath(): string {
131 const envVar = process.env.CARGO; 126 return getPathForExecutable("cargo");
132 const executableName = "cargo"; 127}
128
129/** Mirrors `ra_toolchain::get_path_for_executable()` implementation */
130export const getPathForExecutable = memoize(
131 // We apply caching to decrease file-system interactions
132 (executableName: "cargo" | "rustc" | "rustup"): string => {
133 {
134 const envVar = process.env[executableName.toUpperCase()];
135 if (envVar) return envVar;
136 }
137
138 if (lookupInPath(executableName)) return executableName;
133 139
134 if (envVar) { 140 try {
135 if (isValidExecutable(envVar)) return envVar; 141 // hmm, `os.homedir()` seems to be infallible
142 // it is not mentioned in docs and cannot be infered by the type signature...
143 const standardPath = path.join(os.homedir(), ".cargo", "bin", executableName);
136 144
137 throw new Error(`\`${envVar}\` environment variable points to something that's not a valid executable`); 145 if (isFile(standardPath)) return standardPath;
146 } catch (err) {
147 log.error("Failed to read the fs info", err);
148 }
149 return executableName;
138 } 150 }
151);
139 152
140 if (isValidExecutable(executableName)) return executableName; 153function lookupInPath(exec: string): boolean {
154 const paths = process.env.PATH ?? "";;
141 155
142 const standardLocation = path.join(os.homedir(), '.cargo', 'bin', executableName); 156 const candidates = paths.split(path.delimiter).flatMap(dirInPath => {
157 const candidate = path.join(dirInPath, exec);
158 return os.type() === "Windows_NT"
159 ? [candidate, `${candidate}.exe`]
160 : [candidate];
161 });
143 162
144 if (isValidExecutable(standardLocation)) return standardLocation; 163 return candidates.some(isFile);
164}
145 165
146 throw new Error( 166function isFile(suspectPath: string): boolean {
147 `Failed to find \`${executableName}\` executable. ` + 167 // It is not mentionned in docs, but `statSync()` throws an error when
148 `Make sure \`${executableName}\` is in \`$PATH\`, ` + 168 // the path doesn't exist
149 `or set \`${envVar}\` to point to a valid executable.` 169 try {
150 ); 170 return fs.statSync(suspectPath).isFile();
171 } catch {
172 return false;
173 }
151} 174}
diff --git a/editors/code/src/util.ts b/editors/code/src/util.ts
index 352ef9162..fe3fb71cd 100644
--- a/editors/code/src/util.ts
+++ b/editors/code/src/util.ts
@@ -99,3 +99,21 @@ export function isValidExecutable(path: string): boolean {
99export function setContextValue(key: string, value: any): Thenable<void> { 99export function setContextValue(key: string, value: any): Thenable<void> {
100 return vscode.commands.executeCommand('setContext', key, value); 100 return vscode.commands.executeCommand('setContext', key, value);
101} 101}
102
103/**
104 * Returns a higher-order function that caches the results of invoking the
105 * underlying function.
106 */
107export function memoize<Ret, TThis, Param extends string>(func: (this: TThis, arg: Param) => Ret) {
108 const cache = new Map<string, Ret>();
109
110 return function(this: TThis, arg: Param) {
111 const cached = cache.get(arg);
112 if (cached) return cached;
113
114 const result = func.call(this, arg);
115 cache.set(arg, result);
116
117 return result;
118 };
119}
diff --git a/editors/code/tests/unit/launch_config.test.ts b/editors/code/tests/unit/launch_config.test.ts
index d5cf1b74e..68794d53e 100644
--- a/editors/code/tests/unit/launch_config.test.ts
+++ b/editors/code/tests/unit/launch_config.test.ts
@@ -1,25 +1,25 @@
1import * as assert from 'assert'; 1import * as assert from 'assert';
2import * as cargo from '../../src/cargo'; 2import { Cargo } from '../../src/toolchain';
3 3
4suite('Launch configuration', () => { 4suite('Launch configuration', () => {
5 5
6 suite('Lens', () => { 6 suite('Lens', () => {
7 test('A binary', async () => { 7 test('A binary', async () => {
8 const args = cargo.artifactSpec(["build", "--package", "pkg_name", "--bin", "pkg_name"]); 8 const args = Cargo.artifactSpec(["build", "--package", "pkg_name", "--bin", "pkg_name"]);
9 9
10 assert.deepEqual(args.cargoArgs, ["build", "--package", "pkg_name", "--bin", "pkg_name", "--message-format=json"]); 10 assert.deepEqual(args.cargoArgs, ["build", "--package", "pkg_name", "--bin", "pkg_name", "--message-format=json"]);
11 assert.deepEqual(args.filter, undefined); 11 assert.deepEqual(args.filter, undefined);
12 }); 12 });
13 13
14 test('One of Multiple Binaries', async () => { 14 test('One of Multiple Binaries', async () => {
15 const args = cargo.artifactSpec(["build", "--package", "pkg_name", "--bin", "bin1"]); 15 const args = Cargo.artifactSpec(["build", "--package", "pkg_name", "--bin", "bin1"]);
16 16
17 assert.deepEqual(args.cargoArgs, ["build", "--package", "pkg_name", "--bin", "bin1", "--message-format=json"]); 17 assert.deepEqual(args.cargoArgs, ["build", "--package", "pkg_name", "--bin", "bin1", "--message-format=json"]);
18 assert.deepEqual(args.filter, undefined); 18 assert.deepEqual(args.filter, undefined);
19 }); 19 });
20 20
21 test('A test', async () => { 21 test('A test', async () => {
22 const args = cargo.artifactSpec(["test", "--package", "pkg_name", "--lib", "--no-run"]); 22 const args = Cargo.artifactSpec(["test", "--package", "pkg_name", "--lib", "--no-run"]);
23 23
24 assert.deepEqual(args.cargoArgs, ["test", "--package", "pkg_name", "--lib", "--no-run", "--message-format=json"]); 24 assert.deepEqual(args.cargoArgs, ["test", "--package", "pkg_name", "--lib", "--no-run", "--message-format=json"]);
25 assert.notDeepEqual(args.filter, undefined); 25 assert.notDeepEqual(args.filter, undefined);
@@ -28,7 +28,7 @@ suite('Launch configuration', () => {
28 28
29 suite('QuickPick', () => { 29 suite('QuickPick', () => {
30 test('A binary', async () => { 30 test('A binary', async () => {
31 const args = cargo.artifactSpec(["run", "--package", "pkg_name", "--bin", "pkg_name"]); 31 const args = Cargo.artifactSpec(["run", "--package", "pkg_name", "--bin", "pkg_name"]);
32 32
33 assert.deepEqual(args.cargoArgs, ["build", "--package", "pkg_name", "--bin", "pkg_name", "--message-format=json"]); 33 assert.deepEqual(args.cargoArgs, ["build", "--package", "pkg_name", "--bin", "pkg_name", "--message-format=json"]);
34 assert.deepEqual(args.filter, undefined); 34 assert.deepEqual(args.filter, undefined);
@@ -36,14 +36,14 @@ suite('Launch configuration', () => {
36 36
37 37
38 test('One of Multiple Binaries', async () => { 38 test('One of Multiple Binaries', async () => {
39 const args = cargo.artifactSpec(["run", "--package", "pkg_name", "--bin", "bin2"]); 39 const args = Cargo.artifactSpec(["run", "--package", "pkg_name", "--bin", "bin2"]);
40 40
41 assert.deepEqual(args.cargoArgs, ["build", "--package", "pkg_name", "--bin", "bin2", "--message-format=json"]); 41 assert.deepEqual(args.cargoArgs, ["build", "--package", "pkg_name", "--bin", "bin2", "--message-format=json"]);
42 assert.deepEqual(args.filter, undefined); 42 assert.deepEqual(args.filter, undefined);
43 }); 43 });
44 44
45 test('A test', async () => { 45 test('A test', async () => {
46 const args = cargo.artifactSpec(["test", "--package", "pkg_name", "--lib"]); 46 const args = Cargo.artifactSpec(["test", "--package", "pkg_name", "--lib"]);
47 47
48 assert.deepEqual(args.cargoArgs, ["test", "--package", "pkg_name", "--lib", "--message-format=json", "--no-run"]); 48 assert.deepEqual(args.cargoArgs, ["test", "--package", "pkg_name", "--lib", "--message-format=json", "--no-run"]);
49 assert.notDeepEqual(args.filter, undefined); 49 assert.notDeepEqual(args.filter, undefined);