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_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/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
37 files changed, 914 insertions, 534 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_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/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);