aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock21
-rw-r--r--crates/ra_hir_ty/src/infer.rs20
-rw-r--r--crates/ra_hir_ty/src/tests/traits.rs36
-rw-r--r--crates/ra_ide/src/completion/complete_attribute.rs973
-rw-r--r--crates/ra_ide_db/src/change.rs47
-rw-r--r--crates/rust-analyzer/src/cli/analysis_stats.rs18
-rw-r--r--crates/rust-analyzer/src/config.rs5
-rw-r--r--docs/user/manual.adoc56
-rw-r--r--editors/code/package.json29
-rw-r--r--editors/code/src/config.ts6
-rw-r--r--editors/code/src/debug.ts16
-rw-r--r--editors/code/src/run.ts35
-rw-r--r--editors/code/tests/unit/runnable_env.test.ts118
13 files changed, 513 insertions, 867 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 61ae8157a..ce88b7403 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -208,20 +208,6 @@ dependencies = [
208] 208]
209 209
210[[package]] 210[[package]]
211name = "crossbeam"
212version = "0.7.3"
213source = "registry+https://github.com/rust-lang/crates.io-index"
214checksum = "69323bff1fb41c635347b8ead484a5ca6c3f11914d784170b158d8449ab07f8e"
215dependencies = [
216 "cfg-if",
217 "crossbeam-channel",
218 "crossbeam-deque",
219 "crossbeam-epoch",
220 "crossbeam-queue",
221 "crossbeam-utils",
222]
223
224[[package]]
225name = "crossbeam-channel" 211name = "crossbeam-channel"
226version = "0.4.2" 212version = "0.4.2"
227source = "registry+https://github.com/rust-lang/crates.io-index" 213source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1504,12 +1490,13 @@ checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
1504 1490
1505[[package]] 1491[[package]]
1506name = "salsa" 1492name = "salsa"
1507version = "0.14.2" 1493version = "0.14.3"
1508source = "registry+https://github.com/rust-lang/crates.io-index" 1494source = "registry+https://github.com/rust-lang/crates.io-index"
1509checksum = "6a976dce155e392af3f1aa540ca23a6fc7303a7fef425cb431c464deb263eb54" 1495checksum = "4cd4f099cf7f1d6147aeb313d3fb093d2be1bf71a9fac0122d171c7593206af3"
1510dependencies = [ 1496dependencies = [
1511 "crossbeam", 1497 "crossbeam-utils",
1512 "indexmap", 1498 "indexmap",
1499 "lock_api",
1513 "log", 1500 "log",
1514 "parking_lot", 1501 "parking_lot",
1515 "rand", 1502 "rand",
diff --git a/crates/ra_hir_ty/src/infer.rs b/crates/ra_hir_ty/src/infer.rs
index 3719f76a6..5c56c2eb0 100644
--- a/crates/ra_hir_ty/src/infer.rs
+++ b/crates/ra_hir_ty/src/infer.rs
@@ -28,8 +28,8 @@ use hir_def::{
28 path::{path, Path}, 28 path::{path, Path},
29 resolver::{HasResolver, Resolver, TypeNs}, 29 resolver::{HasResolver, Resolver, TypeNs},
30 type_ref::{Mutability, TypeRef}, 30 type_ref::{Mutability, TypeRef},
31 AdtId, AssocItemId, DefWithBodyId, EnumVariantId, FieldId, FunctionId, TraitId, TypeAliasId, 31 AdtId, AssocItemId, DefWithBodyId, EnumVariantId, FieldId, FunctionId, Lookup, TraitId,
32 VariantId, 32 TypeAliasId, VariantId,
33}; 33};
34use hir_expand::{diagnostics::DiagnosticSink, name::name}; 34use hir_expand::{diagnostics::DiagnosticSink, name::name};
35use ra_arena::map::ArenaMap; 35use ra_arena::map::ArenaMap;
@@ -376,17 +376,21 @@ impl<'a> InferenceContext<'a> {
376 ) -> Ty { 376 ) -> Ty {
377 match assoc_ty { 377 match assoc_ty {
378 Some(res_assoc_ty) => { 378 Some(res_assoc_ty) => {
379 let trait_ = match res_assoc_ty.lookup(self.db.upcast()).container {
380 hir_def::AssocContainerId::TraitId(trait_) => trait_,
381 _ => panic!("resolve_associated_type called with non-associated type"),
382 };
379 let ty = self.table.new_type_var(); 383 let ty = self.table.new_type_var();
380 let builder = Substs::build_for_def(self.db, res_assoc_ty) 384 let substs = Substs::build_for_def(self.db, res_assoc_ty)
381 .push(inner_ty) 385 .push(inner_ty)
382 .fill(params.iter().cloned()); 386 .fill(params.iter().cloned())
387 .build();
388 let trait_ref = TraitRef { trait_, substs: substs.clone() };
383 let projection = ProjectionPredicate { 389 let projection = ProjectionPredicate {
384 ty: ty.clone(), 390 ty: ty.clone(),
385 projection_ty: ProjectionTy { 391 projection_ty: ProjectionTy { associated_ty: res_assoc_ty, parameters: substs },
386 associated_ty: res_assoc_ty,
387 parameters: builder.build(),
388 },
389 }; 392 };
393 self.obligations.push(Obligation::Trait(trait_ref));
390 self.obligations.push(Obligation::Projection(projection)); 394 self.obligations.push(Obligation::Projection(projection));
391 self.resolve_ty_as_possible(ty) 395 self.resolve_ty_as_possible(ty)
392 } 396 }
diff --git a/crates/ra_hir_ty/src/tests/traits.rs b/crates/ra_hir_ty/src/tests/traits.rs
index 766790576..529d9e253 100644
--- a/crates/ra_hir_ty/src/tests/traits.rs
+++ b/crates/ra_hir_ty/src/tests/traits.rs
@@ -541,6 +541,42 @@ mod ops {
541} 541}
542 542
543#[test] 543#[test]
544fn infer_ops_index_int() {
545 check_types(
546 r#"
547//- /main.rs crate:main deps:std
548struct Bar;
549struct Foo;
550
551impl std::ops::Index<u32> for Bar {
552 type Output = Foo;
553}
554
555struct Range;
556impl std::ops::Index<Range> for Bar {
557 type Output = Bar;
558}
559
560fn test() {
561 let a = Bar;
562 let b = a[1];
563 b;
564 //^ Foo
565}
566
567//- /std.rs crate:std
568#[prelude_import] use ops::*;
569mod ops {
570 #[lang = "index"]
571 pub trait Index<Idx> {
572 type Output;
573 }
574}
575"#,
576 );
577}
578
579#[test]
544fn infer_ops_index_autoderef() { 580fn infer_ops_index_autoderef() {
545 check_types( 581 check_types(
546 r#" 582 r#"
diff --git a/crates/ra_ide/src/completion/complete_attribute.rs b/crates/ra_ide/src/completion/complete_attribute.rs
index 6beeca457..9db317509 100644
--- a/crates/ra_ide/src/completion/complete_attribute.rs
+++ b/crates/ra_ide/src/completion/complete_attribute.rs
@@ -46,7 +46,7 @@ fn complete_attribute_start(acc: &mut Completions, ctx: &CompletionContext, attr
46 _ => {} 46 _ => {}
47 } 47 }
48 48
49 if attribute.kind() == ast::AttrKind::Inner || !attr_completion.should_be_inner { 49 if attribute.kind() == ast::AttrKind::Inner || !attr_completion.prefer_inner {
50 acc.add(item); 50 acc.add(item);
51 } 51 }
52 } 52 }
@@ -56,159 +56,72 @@ struct AttrCompletion {
56 label: &'static str, 56 label: &'static str,
57 lookup: Option<&'static str>, 57 lookup: Option<&'static str>,
58 snippet: Option<&'static str>, 58 snippet: Option<&'static str>,
59 should_be_inner: bool, 59 prefer_inner: bool,
60}
61
62impl AttrCompletion {
63 const fn prefer_inner(self) -> AttrCompletion {
64 AttrCompletion { prefer_inner: true, ..self }
65 }
66}
67
68const fn attr(
69 label: &'static str,
70 lookup: Option<&'static str>,
71 snippet: Option<&'static str>,
72) -> AttrCompletion {
73 AttrCompletion { label, lookup, snippet, prefer_inner: false }
60} 74}
61 75
62const ATTRIBUTES: &[AttrCompletion] = &[ 76const ATTRIBUTES: &[AttrCompletion] = &[
63 AttrCompletion { 77 attr("allow(…)", Some("allow"), Some("allow(${0:lint})")),
64 label: "allow(…)", 78 attr("cfg_attr(…)", Some("cfg_attr"), Some("cfg_attr(${1:predicate}, ${0:attr})")),
65 snippet: Some("allow(${0:lint})"), 79 attr("cfg(…)", Some("cfg"), Some("cfg(${0:predicate})")),
66 should_be_inner: false, 80 attr("deny(…)", Some("deny"), Some("deny(${0:lint})")),
67 lookup: Some("allow"), 81 attr(r#"deprecated = "…""#, Some("deprecated"), Some(r#"deprecated = "${0:reason}""#)),
68 }, 82 attr("derive(…)", Some("derive"), Some(r#"derive(${0:Debug})"#)),
69 AttrCompletion { 83 attr(r#"doc = "…""#, Some("doc"), Some(r#"doc = "${0:docs}""#)),
70 label: "cfg_attr(…)", 84 attr("feature(…)", Some("feature"), Some("feature(${0:flag})")).prefer_inner(),
71 snippet: Some("cfg_attr(${1:predicate}, ${0:attr})"), 85 attr("forbid(…)", Some("forbid"), Some("forbid(${0:lint})")),
72 should_be_inner: false,
73 lookup: Some("cfg_attr"),
74 },
75 AttrCompletion {
76 label: "cfg(…)",
77 snippet: Some("cfg(${0:predicate})"),
78 should_be_inner: false,
79 lookup: Some("cfg"),
80 },
81 AttrCompletion {
82 label: "deny(…)",
83 snippet: Some("deny(${0:lint})"),
84 should_be_inner: false,
85 lookup: Some("deny"),
86 },
87 AttrCompletion {
88 label: r#"deprecated = "…""#,
89 snippet: Some(r#"deprecated = "${0:reason}""#),
90 should_be_inner: false,
91 lookup: Some("deprecated"),
92 },
93 AttrCompletion {
94 label: "derive(…)",
95 snippet: Some(r#"derive(${0:Debug})"#),
96 should_be_inner: false,
97 lookup: Some("derive"),
98 },
99 AttrCompletion {
100 label: r#"doc = "…""#,
101 snippet: Some(r#"doc = "${0:docs}""#),
102 should_be_inner: false,
103 lookup: Some("doc"),
104 },
105 AttrCompletion {
106 label: "feature(…)",
107 snippet: Some("feature(${0:flag})"),
108 should_be_inner: true,
109 lookup: Some("feature"),
110 },
111 AttrCompletion {
112 label: "forbid(…)",
113 snippet: Some("forbid(${0:lint})"),
114 should_be_inner: false,
115 lookup: Some("forbid"),
116 },
117 // FIXME: resolve through macro resolution? 86 // FIXME: resolve through macro resolution?
118 AttrCompletion { 87 attr("global_allocator", None, None).prefer_inner(),
119 label: "global_allocator", 88 attr("ignore(…)", Some("ignore"), Some("ignore(${0:lint})")),
120 snippet: None, 89 attr("inline(…)", Some("inline"), Some("inline(${0:lint})")),
121 should_be_inner: true, 90 attr(r#"link_name = "…""#, Some("link_name"), Some(r#"link_name = "${0:symbol_name}""#)),
122 lookup: None, 91 attr("link", None, None),
123 }, 92 attr("macro_export", None, None),
124 AttrCompletion { 93 attr("macro_use", None, None),
125 label: "ignore(…)", 94 attr(r#"must_use = "…""#, Some("must_use"), Some(r#"must_use = "${0:reason}""#)),
126 snippet: Some("ignore(${0:lint})"), 95 attr("no_mangle", None, None),
127 should_be_inner: false, 96 attr("no_std", None, None).prefer_inner(),
128 lookup: Some("ignore"), 97 attr("non_exhaustive", None, None),
129 }, 98 attr("panic_handler", None, None).prefer_inner(),
130 AttrCompletion { 99 attr("path = \"…\"", Some("path"), Some("path =\"${0:path}\"")),
131 label: "inline(…)", 100 attr("proc_macro", None, None),
132 snippet: Some("inline(${0:lint})"), 101 attr("proc_macro_attribute", None, None),
133 should_be_inner: false, 102 attr("proc_macro_derive(…)", Some("proc_macro_derive"), Some("proc_macro_derive(${0:Trait})")),
134 lookup: Some("inline"), 103 attr("recursion_limit = …", Some("recursion_limit"), Some("recursion_limit = ${0:128}"))
135 }, 104 .prefer_inner(),
136 AttrCompletion { 105 attr("repr(…)", Some("repr"), Some("repr(${0:C})")),
137 label: r#"link_name = "…""#, 106 attr(
138 snippet: Some(r#"link_name = "${0:symbol_name}""#), 107 "should_panic(…)",
139 should_be_inner: false, 108 Some("should_panic"),
140 lookup: Some("link_name"), 109 Some(r#"should_panic(expected = "${0:reason}")"#),
141 }, 110 ),
142 AttrCompletion { label: "link", snippet: None, should_be_inner: false, lookup: None }, 111 attr(
143 AttrCompletion { label: "macro_export", snippet: None, should_be_inner: false, lookup: None }, 112 r#"target_feature = "…""#,
144 AttrCompletion { label: "macro_use", snippet: None, should_be_inner: false, lookup: None }, 113 Some("target_feature"),
145 AttrCompletion { 114 Some("target_feature = \"${0:feature}\""),
146 label: r#"must_use = "…""#, 115 ),
147 snippet: Some(r#"must_use = "${0:reason}""#), 116 attr("test", None, None),
148 should_be_inner: false, 117 attr("used", None, None),
149 lookup: Some("must_use"), 118 attr("warn(…)", Some("warn"), Some("warn(${0:lint})")),
150 }, 119 attr(
151 AttrCompletion { label: "no_mangle", snippet: None, should_be_inner: false, lookup: None }, 120 r#"windows_subsystem = "…""#,
152 AttrCompletion { label: "no_std", snippet: None, should_be_inner: true, lookup: None }, 121 Some("windows_subsystem"),
153 AttrCompletion { label: "non_exhaustive", snippet: None, should_be_inner: false, lookup: None }, 122 Some(r#"windows_subsystem = "${0:subsystem}""#),
154 AttrCompletion { label: "panic_handler", snippet: None, should_be_inner: true, lookup: None }, 123 )
155 AttrCompletion { 124 .prefer_inner(),
156 label: "path = \"…\"",
157 snippet: Some("path =\"${0:path}\""),
158 should_be_inner: false,
159 lookup: Some("path"),
160 },
161 AttrCompletion { label: "proc_macro", snippet: None, should_be_inner: false, lookup: None },
162 AttrCompletion {
163 label: "proc_macro_attribute",
164 snippet: None,
165 should_be_inner: false,
166 lookup: None,
167 },
168 AttrCompletion {
169 label: "proc_macro_derive(…)",
170 snippet: Some("proc_macro_derive(${0:Trait})"),
171 should_be_inner: false,
172 lookup: Some("proc_macro_derive"),
173 },
174 AttrCompletion {
175 label: "recursion_limit = …",
176 snippet: Some("recursion_limit = ${0:128}"),
177 should_be_inner: true,
178 lookup: Some("recursion_limit"),
179 },
180 AttrCompletion {
181 label: "repr(…)",
182 snippet: Some("repr(${0:C})"),
183 should_be_inner: false,
184 lookup: Some("repr"),
185 },
186 AttrCompletion {
187 label: "should_panic(…)",
188 snippet: Some(r#"should_panic(expected = "${0:reason}")"#),
189 should_be_inner: false,
190 lookup: Some("should_panic"),
191 },
192 AttrCompletion {
193 label: r#"target_feature = "…""#,
194 snippet: Some("target_feature = \"${0:feature}\""),
195 should_be_inner: false,
196 lookup: Some("target_feature"),
197 },
198 AttrCompletion { label: "test", snippet: None, should_be_inner: false, lookup: None },
199 AttrCompletion { label: "used", snippet: None, should_be_inner: false, lookup: None },
200 AttrCompletion {
201 label: "warn(…)",
202 snippet: Some("warn(${0:lint})"),
203 should_be_inner: false,
204 lookup: Some("warn"),
205 },
206 AttrCompletion {
207 label: r#"windows_subsystem = "…""#,
208 snippet: Some(r#"windows_subsystem = "${0:subsystem}""#),
209 should_be_inner: true,
210 lookup: Some("windows_subsystem"),
211 },
212]; 125];
213 126
214fn complete_derive(acc: &mut Completions, ctx: &CompletionContext, derive_input: ast::TokenTree) { 127fn complete_derive(acc: &mut Completions, ctx: &CompletionContext, derive_input: ast::TokenTree) {
@@ -313,677 +226,147 @@ const DEFAULT_DERIVE_COMPLETIONS: &[DeriveCompletion] = &[
313 226
314#[cfg(test)] 227#[cfg(test)]
315mod tests { 228mod tests {
316 use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; 229 use expect::{expect, Expect};
317 use insta::assert_debug_snapshot;
318 230
319 fn do_attr_completion(code: &str) -> Vec<CompletionItem> { 231 use crate::completion::{test_utils::completion_list, CompletionKind};
320 do_completion(code, CompletionKind::Attribute) 232
233 fn check(ra_fixture: &str, expect: Expect) {
234 let actual = completion_list(ra_fixture, CompletionKind::Attribute);
235 expect.assert_eq(&actual);
321 } 236 }
322 237
323 #[test] 238 #[test]
324 fn empty_derive_completion() { 239 fn empty_derive_completion() {
325 assert_debug_snapshot!( 240 check(
326 do_attr_completion( 241 r#"
327 r" 242#[derive(<|>)]
328 #[derive(<|>)] 243struct Test {}
329 struct Test {} 244 "#,
330 ", 245 expect![[r#"
331 ), 246 at Clone
332 @r###" 247 at Copy, Clone
333 [ 248 at Debug
334 CompletionItem { 249 at Default
335 label: "Clone", 250 at Eq, PartialEq
336 source_range: 9..9, 251 at Hash
337 delete: 9..9, 252 at Ord, PartialOrd, Eq, PartialEq
338 insert: "Clone", 253 at PartialEq
339 kind: Attribute, 254 at PartialOrd, PartialEq
340 }, 255 "#]],
341 CompletionItem {
342 label: "Copy, Clone",
343 source_range: 9..9,
344 delete: 9..9,
345 insert: "Copy, Clone",
346 kind: Attribute,
347 },
348 CompletionItem {
349 label: "Debug",
350 source_range: 9..9,
351 delete: 9..9,
352 insert: "Debug",
353 kind: Attribute,
354 },
355 CompletionItem {
356 label: "Default",
357 source_range: 9..9,
358 delete: 9..9,
359 insert: "Default",
360 kind: Attribute,
361 },
362 CompletionItem {
363 label: "Eq, PartialEq",
364 source_range: 9..9,
365 delete: 9..9,
366 insert: "Eq, PartialEq",
367 kind: Attribute,
368 },
369 CompletionItem {
370 label: "Hash",
371 source_range: 9..9,
372 delete: 9..9,
373 insert: "Hash",
374 kind: Attribute,
375 },
376 CompletionItem {
377 label: "Ord, PartialOrd, Eq, PartialEq",
378 source_range: 9..9,
379 delete: 9..9,
380 insert: "Ord, PartialOrd, Eq, PartialEq",
381 kind: Attribute,
382 },
383 CompletionItem {
384 label: "PartialEq",
385 source_range: 9..9,
386 delete: 9..9,
387 insert: "PartialEq",
388 kind: Attribute,
389 },
390 CompletionItem {
391 label: "PartialOrd, PartialEq",
392 source_range: 9..9,
393 delete: 9..9,
394 insert: "PartialOrd, PartialEq",
395 kind: Attribute,
396 },
397 ]
398 "###
399 ); 256 );
400 } 257 }
401 258
402 #[test] 259 #[test]
403 fn no_completion_for_incorrect_derive() { 260 fn no_completion_for_incorrect_derive() {
404 assert_debug_snapshot!( 261 check(
405 do_attr_completion( 262 r#"
406 r" 263#[derive{<|>)]
407 #[derive{<|>)] 264struct Test {}
408 struct Test {} 265"#,
409 ", 266 expect![[r#""#]],
410 ), 267 )
411 @"[]"
412 );
413 } 268 }
414 269
415 #[test] 270 #[test]
416 fn derive_with_input_completion() { 271 fn derive_with_input_completion() {
417 assert_debug_snapshot!( 272 check(
418 do_attr_completion( 273 r#"
419 r" 274#[derive(serde::Serialize, PartialEq, <|>)]
420 #[derive(serde::Serialize, PartialEq, <|>)] 275struct Test {}
421 struct Test {} 276"#,
422 ", 277 expect![[r#"
423 ), 278 at Clone
424 @r###" 279 at Copy, Clone
425 [ 280 at Debug
426 CompletionItem { 281 at Default
427 label: "Clone", 282 at Eq
428 source_range: 38..38, 283 at Hash
429 delete: 38..38, 284 at Ord, PartialOrd, Eq
430 insert: "Clone", 285 at PartialOrd
431 kind: Attribute, 286 "#]],
432 }, 287 )
433 CompletionItem {
434 label: "Copy, Clone",
435 source_range: 38..38,
436 delete: 38..38,
437 insert: "Copy, Clone",
438 kind: Attribute,
439 },
440 CompletionItem {
441 label: "Debug",
442 source_range: 38..38,
443 delete: 38..38,
444 insert: "Debug",
445 kind: Attribute,
446 },
447 CompletionItem {
448 label: "Default",
449 source_range: 38..38,
450 delete: 38..38,
451 insert: "Default",
452 kind: Attribute,
453 },
454 CompletionItem {
455 label: "Eq",
456 source_range: 38..38,
457 delete: 38..38,
458 insert: "Eq",
459 kind: Attribute,
460 },
461 CompletionItem {
462 label: "Hash",
463 source_range: 38..38,
464 delete: 38..38,
465 insert: "Hash",
466 kind: Attribute,
467 },
468 CompletionItem {
469 label: "Ord, PartialOrd, Eq",
470 source_range: 38..38,
471 delete: 38..38,
472 insert: "Ord, PartialOrd, Eq",
473 kind: Attribute,
474 },
475 CompletionItem {
476 label: "PartialOrd",
477 source_range: 38..38,
478 delete: 38..38,
479 insert: "PartialOrd",
480 kind: Attribute,
481 },
482 ]
483 "###
484 );
485 } 288 }
486 289
487 #[test] 290 #[test]
488 fn test_attribute_completion() { 291 fn test_attribute_completion() {
489 assert_debug_snapshot!( 292 check(
490 do_attr_completion( 293 r#"#[<|>]"#,
491 r" 294 expect![[r#"
492 #[<|>] 295 at allow(…)
493 ", 296 at cfg(…)
494 ), 297 at cfg_attr(…)
495 @r###" 298 at deny(…)
496 [ 299 at deprecated = "…"
497 CompletionItem { 300 at derive(…)
498 label: "allow(…)", 301 at doc = "…"
499 source_range: 2..2, 302 at forbid(…)
500 delete: 2..2, 303 at ignore(…)
501 insert: "allow(${0:lint})", 304 at inline(…)
502 kind: Attribute, 305 at link
503 lookup: "allow", 306 at link_name = "…"
504 }, 307 at macro_export
505 CompletionItem { 308 at macro_use
506 label: "cfg(…)", 309 at must_use = "…"
507 source_range: 2..2, 310 at no_mangle
508 delete: 2..2, 311 at non_exhaustive
509 insert: "cfg(${0:predicate})", 312 at path = "…"
510 kind: Attribute, 313 at proc_macro
511 lookup: "cfg", 314 at proc_macro_attribute
512 }, 315 at proc_macro_derive(…)
513 CompletionItem { 316 at repr(…)
514 label: "cfg_attr(…)", 317 at should_panic(…)
515 source_range: 2..2, 318 at target_feature = "…"
516 delete: 2..2, 319 at test
517 insert: "cfg_attr(${1:predicate}, ${0:attr})", 320 at used
518 kind: Attribute, 321 at warn(…)
519 lookup: "cfg_attr", 322 "#]],
520 }, 323 )
521 CompletionItem {
522 label: "deny(…)",
523 source_range: 2..2,
524 delete: 2..2,
525 insert: "deny(${0:lint})",
526 kind: Attribute,
527 lookup: "deny",
528 },
529 CompletionItem {
530 label: "deprecated = \"…\"",
531 source_range: 2..2,
532 delete: 2..2,
533 insert: "deprecated = \"${0:reason}\"",
534 kind: Attribute,
535 lookup: "deprecated",
536 },
537 CompletionItem {
538 label: "derive(…)",
539 source_range: 2..2,
540 delete: 2..2,
541 insert: "derive(${0:Debug})",
542 kind: Attribute,
543 lookup: "derive",
544 },
545 CompletionItem {
546 label: "doc = \"…\"",
547 source_range: 2..2,
548 delete: 2..2,
549 insert: "doc = \"${0:docs}\"",
550 kind: Attribute,
551 lookup: "doc",
552 },
553 CompletionItem {
554 label: "forbid(…)",
555 source_range: 2..2,
556 delete: 2..2,
557 insert: "forbid(${0:lint})",
558 kind: Attribute,
559 lookup: "forbid",
560 },
561 CompletionItem {
562 label: "ignore(…)",
563 source_range: 2..2,
564 delete: 2..2,
565 insert: "ignore(${0:lint})",
566 kind: Attribute,
567 lookup: "ignore",
568 },
569 CompletionItem {
570 label: "inline(…)",
571 source_range: 2..2,
572 delete: 2..2,
573 insert: "inline(${0:lint})",
574 kind: Attribute,
575 lookup: "inline",
576 },
577 CompletionItem {
578 label: "link",
579 source_range: 2..2,
580 delete: 2..2,
581 insert: "link",
582 kind: Attribute,
583 },
584 CompletionItem {
585 label: "link_name = \"…\"",
586 source_range: 2..2,
587 delete: 2..2,
588 insert: "link_name = \"${0:symbol_name}\"",
589 kind: Attribute,
590 lookup: "link_name",
591 },
592 CompletionItem {
593 label: "macro_export",
594 source_range: 2..2,
595 delete: 2..2,
596 insert: "macro_export",
597 kind: Attribute,
598 },
599 CompletionItem {
600 label: "macro_use",
601 source_range: 2..2,
602 delete: 2..2,
603 insert: "macro_use",
604 kind: Attribute,
605 },
606 CompletionItem {
607 label: "must_use = \"…\"",
608 source_range: 2..2,
609 delete: 2..2,
610 insert: "must_use = \"${0:reason}\"",
611 kind: Attribute,
612 lookup: "must_use",
613 },
614 CompletionItem {
615 label: "no_mangle",
616 source_range: 2..2,
617 delete: 2..2,
618 insert: "no_mangle",
619 kind: Attribute,
620 },
621 CompletionItem {
622 label: "non_exhaustive",
623 source_range: 2..2,
624 delete: 2..2,
625 insert: "non_exhaustive",
626 kind: Attribute,
627 },
628 CompletionItem {
629 label: "path = \"…\"",
630 source_range: 2..2,
631 delete: 2..2,
632 insert: "path =\"${0:path}\"",
633 kind: Attribute,
634 lookup: "path",
635 },
636 CompletionItem {
637 label: "proc_macro",
638 source_range: 2..2,
639 delete: 2..2,
640 insert: "proc_macro",
641 kind: Attribute,
642 },
643 CompletionItem {
644 label: "proc_macro_attribute",
645 source_range: 2..2,
646 delete: 2..2,
647 insert: "proc_macro_attribute",
648 kind: Attribute,
649 },
650 CompletionItem {
651 label: "proc_macro_derive(…)",
652 source_range: 2..2,
653 delete: 2..2,
654 insert: "proc_macro_derive(${0:Trait})",
655 kind: Attribute,
656 lookup: "proc_macro_derive",
657 },
658 CompletionItem {
659 label: "repr(…)",
660 source_range: 2..2,
661 delete: 2..2,
662 insert: "repr(${0:C})",
663 kind: Attribute,
664 lookup: "repr",
665 },
666 CompletionItem {
667 label: "should_panic(…)",
668 source_range: 2..2,
669 delete: 2..2,
670 insert: "should_panic(expected = \"${0:reason}\")",
671 kind: Attribute,
672 lookup: "should_panic",
673 },
674 CompletionItem {
675 label: "target_feature = \"…\"",
676 source_range: 2..2,
677 delete: 2..2,
678 insert: "target_feature = \"${0:feature}\"",
679 kind: Attribute,
680 lookup: "target_feature",
681 },
682 CompletionItem {
683 label: "test",
684 source_range: 2..2,
685 delete: 2..2,
686 insert: "test",
687 kind: Attribute,
688 },
689 CompletionItem {
690 label: "used",
691 source_range: 2..2,
692 delete: 2..2,
693 insert: "used",
694 kind: Attribute,
695 },
696 CompletionItem {
697 label: "warn(…)",
698 source_range: 2..2,
699 delete: 2..2,
700 insert: "warn(${0:lint})",
701 kind: Attribute,
702 lookup: "warn",
703 },
704 ]
705 "###
706 );
707 } 324 }
708 325
709 #[test] 326 #[test]
710 fn test_attribute_completion_inside_nested_attr() { 327 fn test_attribute_completion_inside_nested_attr() {
711 assert_debug_snapshot!( 328 check(r#"#[allow(<|>)]"#, expect![[]])
712 do_attr_completion(
713 r"
714 #[allow(<|>)]
715 ",
716 ),
717 @r###"
718 []
719 "###
720 );
721 } 329 }
722 330
723 #[test] 331 #[test]
724 fn test_inner_attribute_completion() { 332 fn test_inner_attribute_completion() {
725 assert_debug_snapshot!( 333 check(
726 do_attr_completion( 334 r"#![<|>]",
727 r" 335 expect![[r#"
728 #![<|>] 336 at allow(…)
729 ", 337 at cfg(…)
730 ), 338 at cfg_attr(…)
731 @r###" 339 at deny(…)
732 [ 340 at deprecated = "…"
733 CompletionItem { 341 at derive(…)
734 label: "allow(…)", 342 at doc = "…"
735 source_range: 3..3, 343 at feature(…)
736 delete: 3..3, 344 at forbid(…)
737 insert: "allow(${0:lint})", 345 at global_allocator
738 kind: Attribute, 346 at ignore(…)
739 lookup: "allow", 347 at inline(…)
740 }, 348 at link
741 CompletionItem { 349 at link_name = "…"
742 label: "cfg(…)", 350 at macro_export
743 source_range: 3..3, 351 at macro_use
744 delete: 3..3, 352 at must_use = "…"
745 insert: "cfg(${0:predicate})", 353 at no_mangle
746 kind: Attribute, 354 at no_std
747 lookup: "cfg", 355 at non_exhaustive
748 }, 356 at panic_handler
749 CompletionItem { 357 at path = "…"
750 label: "cfg_attr(…)", 358 at proc_macro
751 source_range: 3..3, 359 at proc_macro_attribute
752 delete: 3..3, 360 at proc_macro_derive(…)
753 insert: "cfg_attr(${1:predicate}, ${0:attr})", 361 at recursion_limit = …
754 kind: Attribute, 362 at repr(…)
755 lookup: "cfg_attr", 363 at should_panic(…)
756 }, 364 at target_feature = "…"
757 CompletionItem { 365 at test
758 label: "deny(…)", 366 at used
759 source_range: 3..3, 367 at warn(…)
760 delete: 3..3, 368 at windows_subsystem = "…"
761 insert: "deny(${0:lint})", 369 "#]],
762 kind: Attribute,
763 lookup: "deny",
764 },
765 CompletionItem {
766 label: "deprecated = \"…\"",
767 source_range: 3..3,
768 delete: 3..3,
769 insert: "deprecated = \"${0:reason}\"",
770 kind: Attribute,
771 lookup: "deprecated",
772 },
773 CompletionItem {
774 label: "derive(…)",
775 source_range: 3..3,
776 delete: 3..3,
777 insert: "derive(${0:Debug})",
778 kind: Attribute,
779 lookup: "derive",
780 },
781 CompletionItem {
782 label: "doc = \"…\"",
783 source_range: 3..3,
784 delete: 3..3,
785 insert: "doc = \"${0:docs}\"",
786 kind: Attribute,
787 lookup: "doc",
788 },
789 CompletionItem {
790 label: "feature(…)",
791 source_range: 3..3,
792 delete: 3..3,
793 insert: "feature(${0:flag})",
794 kind: Attribute,
795 lookup: "feature",
796 },
797 CompletionItem {
798 label: "forbid(…)",
799 source_range: 3..3,
800 delete: 3..3,
801 insert: "forbid(${0:lint})",
802 kind: Attribute,
803 lookup: "forbid",
804 },
805 CompletionItem {
806 label: "global_allocator",
807 source_range: 3..3,
808 delete: 3..3,
809 insert: "global_allocator",
810 kind: Attribute,
811 },
812 CompletionItem {
813 label: "ignore(…)",
814 source_range: 3..3,
815 delete: 3..3,
816 insert: "ignore(${0:lint})",
817 kind: Attribute,
818 lookup: "ignore",
819 },
820 CompletionItem {
821 label: "inline(…)",
822 source_range: 3..3,
823 delete: 3..3,
824 insert: "inline(${0:lint})",
825 kind: Attribute,
826 lookup: "inline",
827 },
828 CompletionItem {
829 label: "link",
830 source_range: 3..3,
831 delete: 3..3,
832 insert: "link",
833 kind: Attribute,
834 },
835 CompletionItem {
836 label: "link_name = \"…\"",
837 source_range: 3..3,
838 delete: 3..3,
839 insert: "link_name = \"${0:symbol_name}\"",
840 kind: Attribute,
841 lookup: "link_name",
842 },
843 CompletionItem {
844 label: "macro_export",
845 source_range: 3..3,
846 delete: 3..3,
847 insert: "macro_export",
848 kind: Attribute,
849 },
850 CompletionItem {
851 label: "macro_use",
852 source_range: 3..3,
853 delete: 3..3,
854 insert: "macro_use",
855 kind: Attribute,
856 },
857 CompletionItem {
858 label: "must_use = \"…\"",
859 source_range: 3..3,
860 delete: 3..3,
861 insert: "must_use = \"${0:reason}\"",
862 kind: Attribute,
863 lookup: "must_use",
864 },
865 CompletionItem {
866 label: "no_mangle",
867 source_range: 3..3,
868 delete: 3..3,
869 insert: "no_mangle",
870 kind: Attribute,
871 },
872 CompletionItem {
873 label: "no_std",
874 source_range: 3..3,
875 delete: 3..3,
876 insert: "no_std",
877 kind: Attribute,
878 },
879 CompletionItem {
880 label: "non_exhaustive",
881 source_range: 3..3,
882 delete: 3..3,
883 insert: "non_exhaustive",
884 kind: Attribute,
885 },
886 CompletionItem {
887 label: "panic_handler",
888 source_range: 3..3,
889 delete: 3..3,
890 insert: "panic_handler",
891 kind: Attribute,
892 },
893 CompletionItem {
894 label: "path = \"…\"",
895 source_range: 3..3,
896 delete: 3..3,
897 insert: "path =\"${0:path}\"",
898 kind: Attribute,
899 lookup: "path",
900 },
901 CompletionItem {
902 label: "proc_macro",
903 source_range: 3..3,
904 delete: 3..3,
905 insert: "proc_macro",
906 kind: Attribute,
907 },
908 CompletionItem {
909 label: "proc_macro_attribute",
910 source_range: 3..3,
911 delete: 3..3,
912 insert: "proc_macro_attribute",
913 kind: Attribute,
914 },
915 CompletionItem {
916 label: "proc_macro_derive(…)",
917 source_range: 3..3,
918 delete: 3..3,
919 insert: "proc_macro_derive(${0:Trait})",
920 kind: Attribute,
921 lookup: "proc_macro_derive",
922 },
923 CompletionItem {
924 label: "recursion_limit = …",
925 source_range: 3..3,
926 delete: 3..3,
927 insert: "recursion_limit = ${0:128}",
928 kind: Attribute,
929 lookup: "recursion_limit",
930 },
931 CompletionItem {
932 label: "repr(…)",
933 source_range: 3..3,
934 delete: 3..3,
935 insert: "repr(${0:C})",
936 kind: Attribute,
937 lookup: "repr",
938 },
939 CompletionItem {
940 label: "should_panic(…)",
941 source_range: 3..3,
942 delete: 3..3,
943 insert: "should_panic(expected = \"${0:reason}\")",
944 kind: Attribute,
945 lookup: "should_panic",
946 },
947 CompletionItem {
948 label: "target_feature = \"…\"",
949 source_range: 3..3,
950 delete: 3..3,
951 insert: "target_feature = \"${0:feature}\"",
952 kind: Attribute,
953 lookup: "target_feature",
954 },
955 CompletionItem {
956 label: "test",
957 source_range: 3..3,
958 delete: 3..3,
959 insert: "test",
960 kind: Attribute,
961 },
962 CompletionItem {
963 label: "used",
964 source_range: 3..3,
965 delete: 3..3,
966 insert: "used",
967 kind: Attribute,
968 },
969 CompletionItem {
970 label: "warn(…)",
971 source_range: 3..3,
972 delete: 3..3,
973 insert: "warn(${0:lint})",
974 kind: Attribute,
975 lookup: "warn",
976 },
977 CompletionItem {
978 label: "windows_subsystem = \"…\"",
979 source_range: 3..3,
980 delete: 3..3,
981 insert: "windows_subsystem = \"${0:subsystem}\"",
982 kind: Attribute,
983 lookup: "windows_subsystem",
984 },
985 ]
986 "###
987 ); 370 );
988 } 371 }
989} 372}
diff --git a/crates/ra_ide_db/src/change.rs b/crates/ra_ide_db/src/change.rs
index dbe6eacc5..2504d7a33 100644
--- a/crates/ra_ide_db/src/change.rs
+++ b/crates/ra_ide_db/src/change.rs
@@ -191,12 +191,10 @@ impl RootDatabase {
191 191
192 // AstDatabase 192 // AstDatabase
193 hir::db::AstIdMapQuery 193 hir::db::AstIdMapQuery
194 hir::db::InternMacroQuery
195 hir::db::MacroArgQuery 194 hir::db::MacroArgQuery
196 hir::db::MacroDefQuery 195 hir::db::MacroDefQuery
197 hir::db::ParseMacroQuery 196 hir::db::ParseMacroQuery
198 hir::db::MacroExpandQuery 197 hir::db::MacroExpandQuery
199 hir::db::InternEagerExpansionQuery
200 198
201 // DefDatabase 199 // DefDatabase
202 hir::db::ItemTreeQuery 200 hir::db::ItemTreeQuery
@@ -221,17 +219,6 @@ impl RootDatabase {
221 hir::db::DocumentationQuery 219 hir::db::DocumentationQuery
222 hir::db::ImportMapQuery 220 hir::db::ImportMapQuery
223 221
224 // InternDatabase
225 hir::db::InternFunctionQuery
226 hir::db::InternStructQuery
227 hir::db::InternUnionQuery
228 hir::db::InternEnumQuery
229 hir::db::InternConstQuery
230 hir::db::InternStaticQuery
231 hir::db::InternTraitQuery
232 hir::db::InternTypeAliasQuery
233 hir::db::InternImplQuery
234
235 // HirDatabase 222 // HirDatabase
236 hir::db::InferQueryQuery 223 hir::db::InferQueryQuery
237 hir::db::TyQuery 224 hir::db::TyQuery
@@ -246,10 +233,6 @@ impl RootDatabase {
246 hir::db::InherentImplsInCrateQuery 233 hir::db::InherentImplsInCrateQuery
247 hir::db::TraitImplsInCrateQuery 234 hir::db::TraitImplsInCrateQuery
248 hir::db::TraitImplsInDepsQuery 235 hir::db::TraitImplsInDepsQuery
249 hir::db::InternTypeCtorQuery
250 hir::db::InternTypeParamIdQuery
251 hir::db::InternChalkImplQuery
252 hir::db::InternAssocTyValueQuery
253 hir::db::AssociatedTyDataQuery 236 hir::db::AssociatedTyDataQuery
254 hir::db::TraitDatumQuery 237 hir::db::TraitDatumQuery
255 hir::db::StructDatumQuery 238 hir::db::StructDatumQuery
@@ -264,6 +247,36 @@ impl RootDatabase {
264 // LineIndexDatabase 247 // LineIndexDatabase
265 crate::LineIndexQuery 248 crate::LineIndexQuery
266 ]; 249 ];
250
251 // To collect interned data, we need to bump the revision counter by performing a synthetic
252 // write.
253 // We do this after collecting the non-interned queries to correctly attribute memory used
254 // by interned data.
255 self.runtime.synthetic_write(Durability::HIGH);
256
257 sweep_each_query![
258 // AstDatabase
259 hir::db::InternMacroQuery
260 hir::db::InternEagerExpansionQuery
261
262 // InternDatabase
263 hir::db::InternFunctionQuery
264 hir::db::InternStructQuery
265 hir::db::InternUnionQuery
266 hir::db::InternEnumQuery
267 hir::db::InternConstQuery
268 hir::db::InternStaticQuery
269 hir::db::InternTraitQuery
270 hir::db::InternTypeAliasQuery
271 hir::db::InternImplQuery
272
273 // HirDatabase
274 hir::db::InternTypeCtorQuery
275 hir::db::InternTypeParamIdQuery
276 hir::db::InternChalkImplQuery
277 hir::db::InternAssocTyValueQuery
278 ];
279
267 acc.sort_by_key(|it| std::cmp::Reverse(it.1)); 280 acc.sort_by_key(|it| std::cmp::Reverse(it.1));
268 acc 281 acc
269 } 282 }
diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs
index 14982919d..846264046 100644
--- a/crates/rust-analyzer/src/cli/analysis_stats.rs
+++ b/crates/rust-analyzer/src/cli/analysis_stats.rs
@@ -273,12 +273,22 @@ pub fn analysis_stats(
273 println!("Total: {:?}, {}", analysis_time.elapsed(), ra_prof::memory_usage()); 273 println!("Total: {:?}, {}", analysis_time.elapsed(), ra_prof::memory_usage());
274 274
275 if memory_usage { 275 if memory_usage {
276 for (name, bytes) in host.per_query_memory_usage() { 276 let mut mem = host.per_query_memory_usage();
277 println!("{:>8} {}", bytes, name) 277
278 } 278 let before = ra_prof::memory_usage();
279 drop(vfs);
280 let vfs = before.allocated - ra_prof::memory_usage().allocated;
281 mem.push(("VFS".into(), vfs));
282
279 let before = ra_prof::memory_usage(); 283 let before = ra_prof::memory_usage();
280 drop(host); 284 drop(host);
281 println!("leftover: {}", before.allocated - ra_prof::memory_usage().allocated) 285 mem.push(("Unaccounted".into(), before.allocated - ra_prof::memory_usage().allocated));
286
287 mem.push(("Remaining".into(), ra_prof::memory_usage().allocated));
288
289 for (name, bytes) in mem {
290 println!("{:>8} {}", bytes, name)
291 }
282 } 292 }
283 293
284 Ok(()) 294 Ok(())
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 21acfe644..0c7c36716 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -9,7 +9,6 @@
9 9
10use std::{ffi::OsString, path::PathBuf}; 10use std::{ffi::OsString, path::PathBuf};
11 11
12use crate::diagnostics::DiagnosticsConfig;
13use flycheck::FlycheckConfig; 12use flycheck::FlycheckConfig;
14use lsp_types::ClientCapabilities; 13use lsp_types::ClientCapabilities;
15use ra_db::AbsPathBuf; 14use ra_db::AbsPathBuf;
@@ -17,6 +16,8 @@ use ra_ide::{AssistConfig, CompletionConfig, HoverConfig, InlayHintsConfig};
17use ra_project_model::{CargoConfig, ProjectJson, ProjectJsonData, ProjectManifest}; 16use ra_project_model::{CargoConfig, ProjectJson, ProjectJsonData, ProjectManifest};
18use serde::Deserialize; 17use serde::Deserialize;
19 18
19use crate::diagnostics::DiagnosticsConfig;
20
20#[derive(Debug, Clone)] 21#[derive(Debug, Clone)]
21pub struct Config { 22pub struct Config {
22 pub client_caps: ClientCapsConfig, 23 pub client_caps: ClientCapsConfig,
@@ -182,8 +183,10 @@ impl Config {
182 log::info!("Config::update({:#})", value); 183 log::info!("Config::update({:#})", value);
183 184
184 let client_caps = self.client_caps.clone(); 185 let client_caps = self.client_caps.clone();
186 let linked_projects = self.linked_projects.clone();
185 *self = Config::new(self.root_path.clone()); 187 *self = Config::new(self.root_path.clone());
186 self.client_caps = client_caps; 188 self.client_caps = client_caps;
189 self.linked_projects = linked_projects;
187 190
188 set(value, "/withSysroot", &mut self.with_sysroot); 191 set(value, "/withSysroot", &mut self.with_sysroot);
189 set(value, "/diagnostics/enable", &mut self.publish_diagnostics); 192 set(value, "/diagnostics/enable", &mut self.publish_diagnostics);
diff --git a/docs/user/manual.adoc b/docs/user/manual.adoc
index b763958fe..7816287e4 100644
--- a/docs/user/manual.adoc
+++ b/docs/user/manual.adoc
@@ -109,18 +109,6 @@ Here are some useful self-diagnostic commands:
109* To log all LSP requests, add `"rust-analyzer.trace.server": "verbose"` to the settings and look for `Server Trace` in the panel. 109* To log all LSP requests, add `"rust-analyzer.trace.server": "verbose"` to the settings and look for `Server Trace` in the panel.
110* To enable client-side logging, add `"rust-analyzer.trace.extension": true` to the settings and open the `Console` tab of VS Code developer tools. 110* To enable client-side logging, add `"rust-analyzer.trace.extension": true` to the settings and open the `Console` tab of VS Code developer tools.
111 111
112==== Special `when` clause context for keybindings.
113You may use `inRustProject` context to configure keybindings for rust projects only. For example:
114[source,json]
115----
116{
117 "key": "ctrl+i",
118 "command": "rust-analyzer.toggleInlayHints",
119 "when": "inRustProject"
120}
121----
122More about `when` clause contexts https://code.visualstudio.com/docs/getstarted/keybindings#_when-clause-contexts[here].
123
124=== rust-analyzer Language Server Binary 112=== rust-analyzer Language Server Binary
125 113
126Other editors generally require the `rust-analyzer` binary to be in `$PATH`. 114Other editors generally require the `rust-analyzer` binary to be in `$PATH`.
@@ -337,3 +325,47 @@ They are usually triggered by a shortcut or by clicking a light bulb icon in the
337Cursor position or selection is signified by `┃` character. 325Cursor position or selection is signified by `┃` character.
338 326
339include::./generated_assists.adoc[] 327include::./generated_assists.adoc[]
328
329== Editor Features
330=== VS Code
331==== Special `when` clause context for keybindings.
332You may use `inRustProject` context to configure keybindings for rust projects only. For example:
333[source,json]
334----
335{
336 "key": "ctrl+i",
337 "command": "rust-analyzer.toggleInlayHints",
338 "when": "inRustProject"
339}
340----
341More about `when` clause contexts https://code.visualstudio.com/docs/getstarted/keybindings#_when-clause-contexts[here].
342
343==== Setting runnable environment variables
344You can use "rust-analyzer.runnableEnv" setting to define runnable environment-specific substitution variables.
345The simplest way for all runnables in a bunch:
346```jsonc
347"rust-analyzer.runnableEnv": {
348 "RUN_SLOW_TESTS": "1"
349}
350```
351
352Or it is possible to specify vars more granularly:
353```jsonc
354"rust-analyzer.runnableEnv": [
355 {
356 // "mask": null, // null mask means that this rule will be applied for all runnables
357 env: {
358 "APP_ID": "1",
359 "APP_DATA": "asdf"
360 }
361 },
362 {
363 "mask": "test_name",
364 "env": {
365 "APP_ID": "2", // overwrites only APP_ID
366 }
367 }
368]
369```
370
371You can use any valid RegExp as a mask. Also note that a full runnable name is something like *run bin_or_example_name*, *test some::mod::test_name* or *test-mod some::mod*, so it is possible to distinguish binaries, single tests, and test modules with this masks: `"^run"`, `"^test "` (the trailing space matters!), and `"^test-mod"` respectively.
diff --git a/editors/code/package.json b/editors/code/package.json
index 938df475f..4b47fc9d3 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -344,6 +344,35 @@
344 "default": null, 344 "default": null,
345 "description": "Custom cargo runner extension ID." 345 "description": "Custom cargo runner extension ID."
346 }, 346 },
347 "rust-analyzer.runnableEnv": {
348 "anyOf": [
349 {
350 "type": "null"
351 },
352 {
353 "type": "array",
354 "items": {
355 "type": "object",
356 "properties": {
357 "mask": {
358 "type": "string",
359 "description": "Runnable name mask"
360 },
361 "env": {
362 "type": "object",
363 "description": "Variables in form of { \"key\": \"value\"}"
364 }
365 }
366 }
367 },
368 {
369 "type": "object",
370 "description": "Variables in form of { \"key\": \"value\"}"
371 }
372 ],
373 "default": null,
374 "description": "Environment variables passed to the runnable launched using `Test ` or `Debug` lens or `rust-analyzer.run` command."
375 },
347 "rust-analyzer.inlayHints.enable": { 376 "rust-analyzer.inlayHints.enable": {
348 "type": "boolean", 377 "type": "boolean",
349 "default": true, 378 "default": true,
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts
index fc95a7de6..23975c726 100644
--- a/editors/code/src/config.ts
+++ b/editors/code/src/config.ts
@@ -5,6 +5,8 @@ export type UpdatesChannel = "stable" | "nightly";
5 5
6export const NIGHTLY_TAG = "nightly"; 6export const NIGHTLY_TAG = "nightly";
7 7
8export type RunnableEnvCfg = undefined | Record<string, string> | { mask?: string; env: Record<string, string> }[];
9
8export class Config { 10export class Config {
9 readonly extensionId = "matklad.rust-analyzer"; 11 readonly extensionId = "matklad.rust-analyzer";
10 12
@@ -114,6 +116,10 @@ export class Config {
114 return this.get<string | undefined>("cargoRunner"); 116 return this.get<string | undefined>("cargoRunner");
115 } 117 }
116 118
119 get runnableEnv() {
120 return this.get<RunnableEnvCfg>("runnableEnv");
121 }
122
117 get debug() { 123 get debug() {
118 // "/rustc/<id>" used by suggestions only. 124 // "/rustc/<id>" used by suggestions only.
119 const { ["/rustc/<id>"]: _, ...sourceFileMap } = this.get<Record<string, string>>("debug.sourceFileMap"); 125 const { ["/rustc/<id>"]: _, ...sourceFileMap } = this.get<Record<string, string>>("debug.sourceFileMap");
diff --git a/editors/code/src/debug.ts b/editors/code/src/debug.ts
index 61c12dbe0..bd92c5b6d 100644
--- a/editors/code/src/debug.ts
+++ b/editors/code/src/debug.ts
@@ -5,9 +5,10 @@ import * as ra from './lsp_ext';
5 5
6import { Cargo } from './toolchain'; 6import { Cargo } from './toolchain';
7import { Ctx } from "./ctx"; 7import { Ctx } from "./ctx";
8import { prepareEnv } from "./run";
8 9
9const debugOutput = vscode.window.createOutputChannel("Debug"); 10const debugOutput = vscode.window.createOutputChannel("Debug");
10type DebugConfigProvider = (config: ra.Runnable, executable: string, sourceFileMap?: Record<string, string>) => vscode.DebugConfiguration; 11type DebugConfigProvider = (config: ra.Runnable, executable: string, env: Record<string, string>, sourceFileMap?: Record<string, string>) => vscode.DebugConfiguration;
11 12
12export async function makeDebugConfig(ctx: Ctx, runnable: ra.Runnable): Promise<void> { 13export async function makeDebugConfig(ctx: Ctx, runnable: ra.Runnable): Promise<void> {
13 const scope = ctx.activeRustEditor?.document.uri; 14 const scope = ctx.activeRustEditor?.document.uri;
@@ -92,7 +93,8 @@ async function getDebugConfiguration(ctx: Ctx, runnable: ra.Runnable): Promise<v
92 } 93 }
93 94
94 const executable = await getDebugExecutable(runnable); 95 const executable = await getDebugExecutable(runnable);
95 const debugConfig = knownEngines[debugEngine.id](runnable, simplifyPath(executable), debugOptions.sourceFileMap); 96 const env = prepareEnv(runnable, ctx.config.runnableEnv);
97 const debugConfig = knownEngines[debugEngine.id](runnable, simplifyPath(executable), env, debugOptions.sourceFileMap);
96 if (debugConfig.type in debugOptions.engineSettings) { 98 if (debugConfig.type in debugOptions.engineSettings) {
97 const settingsMap = (debugOptions.engineSettings as any)[debugConfig.type]; 99 const settingsMap = (debugOptions.engineSettings as any)[debugConfig.type];
98 for (var key in settingsMap) { 100 for (var key in settingsMap) {
@@ -121,7 +123,7 @@ async function getDebugExecutable(runnable: ra.Runnable): Promise<string> {
121 return executable; 123 return executable;
122} 124}
123 125
124function getLldbDebugConfig(runnable: ra.Runnable, executable: string, sourceFileMap?: Record<string, string>): vscode.DebugConfiguration { 126function getLldbDebugConfig(runnable: ra.Runnable, executable: string, env: Record<string, string>, sourceFileMap?: Record<string, string>): vscode.DebugConfiguration {
125 return { 127 return {
126 type: "lldb", 128 type: "lldb",
127 request: "launch", 129 request: "launch",
@@ -130,11 +132,12 @@ function getLldbDebugConfig(runnable: ra.Runnable, executable: string, sourceFil
130 args: runnable.args.executableArgs, 132 args: runnable.args.executableArgs,
131 cwd: runnable.args.workspaceRoot, 133 cwd: runnable.args.workspaceRoot,
132 sourceMap: sourceFileMap, 134 sourceMap: sourceFileMap,
133 sourceLanguages: ["rust"] 135 sourceLanguages: ["rust"],
136 env
134 }; 137 };
135} 138}
136 139
137function getCppvsDebugConfig(runnable: ra.Runnable, executable: string, sourceFileMap?: Record<string, string>): vscode.DebugConfiguration { 140function getCppvsDebugConfig(runnable: ra.Runnable, executable: string, env: Record<string, string>, sourceFileMap?: Record<string, string>): vscode.DebugConfiguration {
138 return { 141 return {
139 type: (os.platform() === "win32") ? "cppvsdbg" : "cppdbg", 142 type: (os.platform() === "win32") ? "cppvsdbg" : "cppdbg",
140 request: "launch", 143 request: "launch",
@@ -142,6 +145,7 @@ function getCppvsDebugConfig(runnable: ra.Runnable, executable: string, sourceFi
142 program: executable, 145 program: executable,
143 args: runnable.args.executableArgs, 146 args: runnable.args.executableArgs,
144 cwd: runnable.args.workspaceRoot, 147 cwd: runnable.args.workspaceRoot,
145 sourceFileMap: sourceFileMap, 148 sourceFileMap,
149 env,
146 }; 150 };
147} 151}
diff --git a/editors/code/src/run.ts b/editors/code/src/run.ts
index e1430e31f..de68f27ae 100644
--- a/editors/code/src/run.ts
+++ b/editors/code/src/run.ts
@@ -5,7 +5,7 @@ import * as tasks from './tasks';
5 5
6import { Ctx } from './ctx'; 6import { Ctx } from './ctx';
7import { makeDebugConfig } from './debug'; 7import { makeDebugConfig } from './debug';
8import { Config } from './config'; 8import { Config, RunnableEnvCfg } from './config';
9 9
10const quickPickButtons = [{ iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configurtation." }]; 10const quickPickButtons = [{ iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configurtation." }];
11 11
@@ -96,6 +96,30 @@ export class RunnableQuickPick implements vscode.QuickPickItem {
96 } 96 }
97} 97}
98 98
99export function prepareEnv(runnable: ra.Runnable, runnableEnvCfg: RunnableEnvCfg): Record<string, string> {
100 const env: Record<string, string> = { "RUST_BACKTRACE": "short" };
101
102 if (runnable.args.expectTest) {
103 env["UPDATE_EXPECT"] = "1";
104 }
105
106 Object.assign(env, process.env as { [key: string]: string });
107
108 if (runnableEnvCfg) {
109 if (Array.isArray(runnableEnvCfg)) {
110 for (const it of runnableEnvCfg) {
111 if (!it.mask || new RegExp(it.mask).test(runnable.label)) {
112 Object.assign(env, it.env);
113 }
114 }
115 } else {
116 Object.assign(env, runnableEnvCfg);
117 }
118 }
119
120 return env;
121}
122
99export async function createTask(runnable: ra.Runnable, config: Config): Promise<vscode.Task> { 123export async function createTask(runnable: ra.Runnable, config: Config): Promise<vscode.Task> {
100 if (runnable.kind !== "cargo") { 124 if (runnable.kind !== "cargo") {
101 // rust-analyzer supports only one kind, "cargo" 125 // rust-analyzer supports only one kind, "cargo"
@@ -108,16 +132,13 @@ export async function createTask(runnable: ra.Runnable, config: Config): Promise
108 if (runnable.args.executableArgs.length > 0) { 132 if (runnable.args.executableArgs.length > 0) {
109 args.push('--', ...runnable.args.executableArgs); 133 args.push('--', ...runnable.args.executableArgs);
110 } 134 }
111 const env: { [key: string]: string } = { "RUST_BACKTRACE": "short" }; 135
112 if (runnable.args.expectTest) {
113 env["UPDATE_EXPECT"] = "1";
114 }
115 const definition: tasks.CargoTaskDefinition = { 136 const definition: tasks.CargoTaskDefinition = {
116 type: tasks.TASK_TYPE, 137 type: tasks.TASK_TYPE,
117 command: args[0], // run, test, etc... 138 command: args[0], // run, test, etc...
118 args: args.slice(1), 139 args: args.slice(1),
119 cwd: runnable.args.workspaceRoot, 140 cwd: runnable.args.workspaceRoot || ".",
120 env: Object.assign({}, process.env as { [key: string]: string }, env), 141 env: prepareEnv(runnable, config.runnableEnv),
121 }; 142 };
122 143
123 const target = vscode.workspace.workspaceFolders![0]; // safe, see main activate() 144 const target = vscode.workspace.workspaceFolders![0]; // safe, see main activate()
diff --git a/editors/code/tests/unit/runnable_env.test.ts b/editors/code/tests/unit/runnable_env.test.ts
new file mode 100644
index 000000000..f2f53e91a
--- /dev/null
+++ b/editors/code/tests/unit/runnable_env.test.ts
@@ -0,0 +1,118 @@
1import * as assert from 'assert';
2import { prepareEnv } from '../../src/run';
3import { RunnableEnvCfg } from '../../src/config';
4import * as ra from '../../src/lsp_ext';
5
6function makeRunnable(label: string): ra.Runnable {
7 return {
8 label,
9 kind: "cargo",
10 args: {
11 cargoArgs: [],
12 executableArgs: []
13 }
14 };
15}
16
17function fakePrepareEnv(runnableName: string, config: RunnableEnvCfg): Record<string, string> {
18 const runnable = makeRunnable(runnableName);
19 return prepareEnv(runnable, config);
20}
21
22suite('Runnable env', () => {
23 test('Global config works', () => {
24 const binEnv = fakePrepareEnv("run project_name", { "GLOBAL": "g" });
25 assert.equal(binEnv["GLOBAL"], "g");
26
27 const testEnv = fakePrepareEnv("test some::mod::test_name", { "GLOBAL": "g" });
28 assert.equal(testEnv["GLOBAL"], "g");
29 });
30
31 test('null mask works', () => {
32 const config = [
33 {
34 env: { DATA: "data" }
35 }
36 ];
37 const binEnv = fakePrepareEnv("run project_name", config);
38 assert.equal(binEnv["DATA"], "data");
39
40 const testEnv = fakePrepareEnv("test some::mod::test_name", config);
41 assert.equal(testEnv["DATA"], "data");
42 });
43
44 test('order works', () => {
45 const config = [
46 {
47 env: { DATA: "data" }
48 },
49 {
50 env: { DATA: "newdata" }
51 }
52 ];
53 const binEnv = fakePrepareEnv("run project_name", config);
54 assert.equal(binEnv["DATA"], "newdata");
55
56 const testEnv = fakePrepareEnv("test some::mod::test_name", config);
57 assert.equal(testEnv["DATA"], "newdata");
58 });
59
60 test('mask works', () => {
61 const config = [
62 {
63 env: { DATA: "data" }
64 },
65 {
66 mask: "^run",
67 env: { DATA: "rundata" }
68 },
69 {
70 mask: "special_test$",
71 env: { DATA: "special_test" }
72 }
73 ];
74 const binEnv = fakePrepareEnv("run project_name", config);
75 assert.equal(binEnv["DATA"], "rundata");
76
77 const testEnv = fakePrepareEnv("test some::mod::test_name", config);
78 assert.equal(testEnv["DATA"], "data");
79
80 const specialTestEnv = fakePrepareEnv("test some::mod::special_test", config);
81 assert.equal(specialTestEnv["DATA"], "special_test");
82 });
83
84 test('exact test name works', () => {
85 const config = [
86 {
87 env: { DATA: "data" }
88 },
89 {
90 mask: "some::mod::test_name",
91 env: { DATA: "test special" }
92 }
93 ];
94 const testEnv = fakePrepareEnv("test some::mod::test_name", config);
95 assert.equal(testEnv["DATA"], "test special");
96
97 const specialTestEnv = fakePrepareEnv("test some::mod::another_test", config);
98 assert.equal(specialTestEnv["DATA"], "data");
99 });
100
101 test('test mod name works', () => {
102 const config = [
103 {
104 env: { DATA: "data" }
105 },
106 {
107 mask: "some::mod",
108 env: { DATA: "mod special" }
109 }
110 ];
111 const testEnv = fakePrepareEnv("test some::mod::test_name", config);
112 assert.equal(testEnv["DATA"], "mod special");
113
114 const specialTestEnv = fakePrepareEnv("test some::mod::another_test", config);
115 assert.equal(specialTestEnv["DATA"], "mod special");
116 });
117
118});