aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock24
-rw-r--r--crates/ra_assists/Cargo.toml2
-rw-r--r--crates/ra_assists/src/doc_tests/generated.rs4
-rw-r--r--crates/ra_assists/src/handlers/add_missing_impl_members.rs2
-rw-r--r--crates/ra_assists/src/handlers/auto_import.rs49
-rw-r--r--crates/ra_assists/src/handlers/fill_match_arms.rs319
-rw-r--r--crates/ra_assists/src/lib.rs29
-rw-r--r--crates/ra_fmt/Cargo.toml2
-rw-r--r--crates/ra_hir/Cargo.toml2
-rw-r--r--crates/ra_hir/src/code_model.rs22
-rw-r--r--crates/ra_hir/src/from_id.rs21
-rw-r--r--crates/ra_ide/Cargo.toml2
-rw-r--r--crates/ra_ide/src/completion/complete_postfix.rs140
-rw-r--r--crates/ra_ide/src/completion/complete_record_literal.rs62
-rw-r--r--crates/ra_ide/src/completion/complete_trait_impl.rs2
-rw-r--r--crates/ra_ide_db/Cargo.toml1
-rw-r--r--crates/ra_ide_db/src/imports_locator.rs8
-rw-r--r--crates/ra_syntax/Cargo.toml2
-rw-r--r--crates/ra_syntax/src/ast/edit.rs6
-rw-r--r--crates/ra_syntax/src/ast/make.rs17
-rw-r--r--crates/rust-analyzer/Cargo.toml2
-rw-r--r--docs/user/assists.md4
-rw-r--r--docs/user/readme.adoc10
-rw-r--r--editors/code/package.json12
-rw-r--r--editors/code/src/client.ts6
-rw-r--r--editors/code/src/config.ts124
-rw-r--r--editors/code/src/main.ts14
27 files changed, 685 insertions, 203 deletions
diff --git a/Cargo.lock b/Cargo.lock
index f8d26192d..908319f87 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -170,7 +170,7 @@ dependencies = [
170 "chalk-macros", 170 "chalk-macros",
171 "chalk-rust-ir", 171 "chalk-rust-ir",
172 "ena", 172 "ena",
173 "itertools", 173 "itertools 0.8.2",
174 "petgraph", 174 "petgraph",
175 "rustc-hash", 175 "rustc-hash",
176] 176]
@@ -521,6 +521,15 @@ dependencies = [
521] 521]
522 522
523[[package]] 523[[package]]
524name = "itertools"
525version = "0.9.0"
526source = "registry+https://github.com/rust-lang/crates.io-index"
527checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
528dependencies = [
529 "either",
530]
531
532[[package]]
524name = "itoa" 533name = "itoa"
525version = "0.4.5" 534version = "0.4.5"
526source = "registry+https://github.com/rust-lang/crates.io-index" 535source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -875,7 +884,9 @@ version = "0.1.0"
875name = "ra_assists" 884name = "ra_assists"
876version = "0.1.0" 885version = "0.1.0"
877dependencies = [ 886dependencies = [
887 "either",
878 "format-buf", 888 "format-buf",
889 "itertools 0.9.0",
879 "join_to_string", 890 "join_to_string",
880 "ra_db", 891 "ra_db",
881 "ra_fmt", 892 "ra_fmt",
@@ -927,7 +938,7 @@ dependencies = [
927name = "ra_fmt" 938name = "ra_fmt"
928version = "0.1.0" 939version = "0.1.0"
929dependencies = [ 940dependencies = [
930 "itertools", 941 "itertools 0.9.0",
931 "ra_syntax", 942 "ra_syntax",
932] 943]
933 944
@@ -937,7 +948,7 @@ version = "0.1.0"
937dependencies = [ 948dependencies = [
938 "arrayvec", 949 "arrayvec",
939 "either", 950 "either",
940 "itertools", 951 "itertools 0.9.0",
941 "log", 952 "log",
942 "ra_db", 953 "ra_db",
943 "ra_hir_def", 954 "ra_hir_def",
@@ -1014,7 +1025,7 @@ dependencies = [
1014 "format-buf", 1025 "format-buf",
1015 "indexmap", 1026 "indexmap",
1016 "insta", 1027 "insta",
1017 "itertools", 1028 "itertools 0.9.0",
1018 "join_to_string", 1029 "join_to_string",
1019 "log", 1030 "log",
1020 "ra_assists", 1031 "ra_assists",
@@ -1035,6 +1046,7 @@ dependencies = [
1035name = "ra_ide_db" 1046name = "ra_ide_db"
1036version = "0.1.0" 1047version = "0.1.0"
1037dependencies = [ 1048dependencies = [
1049 "either",
1038 "fst", 1050 "fst",
1039 "log", 1051 "log",
1040 "once_cell", 1052 "once_cell",
@@ -1100,7 +1112,7 @@ name = "ra_syntax"
1100version = "0.1.0" 1112version = "0.1.0"
1101dependencies = [ 1113dependencies = [
1102 "arrayvec", 1114 "arrayvec",
1103 "itertools", 1115 "itertools 0.9.0",
1104 "once_cell", 1116 "once_cell",
1105 "ra_parser", 1117 "ra_parser",
1106 "ra_text_edit", 1118 "ra_text_edit",
@@ -1277,7 +1289,7 @@ dependencies = [
1277 "crossbeam-channel", 1289 "crossbeam-channel",
1278 "env_logger", 1290 "env_logger",
1279 "globset", 1291 "globset",
1280 "itertools", 1292 "itertools 0.9.0",
1281 "jod-thread", 1293 "jod-thread",
1282 "log", 1294 "log",
1283 "lsp-server", 1295 "lsp-server",
diff --git a/crates/ra_assists/Cargo.toml b/crates/ra_assists/Cargo.toml
index d314dc8e6..a87f4052a 100644
--- a/crates/ra_assists/Cargo.toml
+++ b/crates/ra_assists/Cargo.toml
@@ -11,6 +11,8 @@ doctest = false
11format-buf = "1.0.0" 11format-buf = "1.0.0"
12join_to_string = "0.1.3" 12join_to_string = "0.1.3"
13rustc-hash = "1.1.0" 13rustc-hash = "1.1.0"
14itertools = "0.9.0"
15either = "1.5.3"
14 16
15ra_syntax = { path = "../ra_syntax" } 17ra_syntax = { path = "../ra_syntax" }
16ra_text_edit = { path = "../ra_text_edit" } 18ra_text_edit = { path = "../ra_text_edit" }
diff --git a/crates/ra_assists/src/doc_tests/generated.rs b/crates/ra_assists/src/doc_tests/generated.rs
index aef6793e8..62dcb3808 100644
--- a/crates/ra_assists/src/doc_tests/generated.rs
+++ b/crates/ra_assists/src/doc_tests/generated.rs
@@ -275,8 +275,8 @@ enum Action { Move { distance: u32 }, Stop }
275 275
276fn handle(action: Action) { 276fn handle(action: Action) {
277 match action { 277 match action {
278 Action::Move { distance } => (), 278 Action::Move { distance } => {}
279 Action::Stop => (), 279 Action::Stop => {}
280 } 280 }
281} 281}
282"#####, 282"#####,
diff --git a/crates/ra_assists/src/handlers/add_missing_impl_members.rs b/crates/ra_assists/src/handlers/add_missing_impl_members.rs
index e5920b6f6..722f207e2 100644
--- a/crates/ra_assists/src/handlers/add_missing_impl_members.rs
+++ b/crates/ra_assists/src/handlers/add_missing_impl_members.rs
@@ -151,7 +151,7 @@ fn add_missing_impl_members_inner(
151 ast::ImplItem::FnDef(def) => ast::ImplItem::FnDef(add_body(def)), 151 ast::ImplItem::FnDef(def) => ast::ImplItem::FnDef(add_body(def)),
152 _ => it, 152 _ => it,
153 }) 153 })
154 .map(|it| edit::strip_attrs_and_docs(&it)); 154 .map(|it| edit::remove_attrs_and_docs(&it));
155 let new_impl_item_list = impl_item_list.append_items(items); 155 let new_impl_item_list = impl_item_list.append_items(items);
156 let cursor_position = { 156 let cursor_position = {
157 let first_new_item = new_impl_item_list.impl_items().nth(n_existing_items).unwrap(); 157 let first_new_item = new_impl_item_list.impl_items().nth(n_existing_items).unwrap();
diff --git a/crates/ra_assists/src/handlers/auto_import.rs b/crates/ra_assists/src/handlers/auto_import.rs
index bb280f633..99682e023 100644
--- a/crates/ra_assists/src/handlers/auto_import.rs
+++ b/crates/ra_assists/src/handlers/auto_import.rs
@@ -17,6 +17,7 @@ use crate::{
17 utils::insert_use_statement, 17 utils::insert_use_statement,
18 AssistId, 18 AssistId,
19}; 19};
20use either::Either;
20 21
21// Assist: auto_import 22// Assist: auto_import
22// 23//
@@ -58,6 +59,7 @@ pub(crate) fn auto_import(ctx: AssistCtx) -> Option<Assist> {
58 group.finish() 59 group.finish()
59} 60}
60 61
62#[derive(Debug)]
61struct AutoImportAssets { 63struct AutoImportAssets {
62 import_candidate: ImportCandidate, 64 import_candidate: ImportCandidate,
63 module_with_name_to_import: Module, 65 module_with_name_to_import: Module,
@@ -127,14 +129,14 @@ impl AutoImportAssets {
127 ImportsLocator::new(db) 129 ImportsLocator::new(db)
128 .find_imports(&self.get_search_query()) 130 .find_imports(&self.get_search_query())
129 .into_iter() 131 .into_iter()
130 .filter_map(|module_def| match &self.import_candidate { 132 .filter_map(|candidate| match &self.import_candidate {
131 ImportCandidate::TraitAssocItem(assoc_item_type, _) => { 133 ImportCandidate::TraitAssocItem(assoc_item_type, _) => {
132 let located_assoc_item = match module_def { 134 let located_assoc_item = match candidate {
133 ModuleDef::Function(located_function) => located_function 135 Either::Left(ModuleDef::Function(located_function)) => located_function
134 .as_assoc_item(db) 136 .as_assoc_item(db)
135 .map(|assoc| assoc.container(db)) 137 .map(|assoc| assoc.container(db))
136 .and_then(Self::assoc_to_trait), 138 .and_then(Self::assoc_to_trait),
137 ModuleDef::Const(located_const) => located_const 139 Either::Left(ModuleDef::Const(located_const)) => located_const
138 .as_assoc_item(db) 140 .as_assoc_item(db)
139 .map(|assoc| assoc.container(db)) 141 .map(|assoc| assoc.container(db))
140 .and_then(Self::assoc_to_trait), 142 .and_then(Self::assoc_to_trait),
@@ -153,10 +155,11 @@ impl AutoImportAssets {
153 |_, assoc| Self::assoc_to_trait(assoc.container(db)), 155 |_, assoc| Self::assoc_to_trait(assoc.container(db)),
154 ) 156 )
155 .map(ModuleDef::from) 157 .map(ModuleDef::from)
158 .map(Either::Left)
156 } 159 }
157 ImportCandidate::TraitMethod(function_callee, _) => { 160 ImportCandidate::TraitMethod(function_callee, _) => {
158 let located_assoc_item = 161 let located_assoc_item =
159 if let ModuleDef::Function(located_function) = module_def { 162 if let Either::Left(ModuleDef::Function(located_function)) = candidate {
160 located_function 163 located_function
161 .as_assoc_item(db) 164 .as_assoc_item(db)
162 .map(|assoc| assoc.container(db)) 165 .map(|assoc| assoc.container(db))
@@ -179,10 +182,18 @@ impl AutoImportAssets {
179 }, 182 },
180 ) 183 )
181 .map(ModuleDef::from) 184 .map(ModuleDef::from)
185 .map(Either::Left)
186 }
187 _ => Some(candidate),
188 })
189 .filter_map(|candidate| match candidate {
190 Either::Left(module_def) => {
191 self.module_with_name_to_import.find_use_path(db, module_def)
192 }
193 Either::Right(macro_def) => {
194 self.module_with_name_to_import.find_use_path(db, macro_def)
182 } 195 }
183 _ => Some(module_def),
184 }) 196 })
185 .filter_map(|module_def| self.module_with_name_to_import.find_use_path(db, module_def))
186 .filter(|use_path| !use_path.segments.is_empty()) 197 .filter(|use_path| !use_path.segments.is_empty())
187 .take(20) 198 .take(20)
188 .collect::<BTreeSet<_>>() 199 .collect::<BTreeSet<_>>()
@@ -440,6 +451,30 @@ mod tests {
440 } 451 }
441 452
442 #[test] 453 #[test]
454 fn macro_import() {
455 check_assist(
456 auto_import,
457 r"
458 //- /lib.rs crate:crate_with_macro
459 #[macro_export]
460 macro_rules! foo {
461 () => ()
462 }
463
464 //- /main.rs crate:main deps:crate_with_macro
465 fn main() {
466 foo<|>
467 }",
468 r"use crate_with_macro::foo;
469
470fn main() {
471 foo<|>
472}
473",
474 );
475 }
476
477 #[test]
443 fn auto_import_target() { 478 fn auto_import_target() {
444 check_assist_target( 479 check_assist_target(
445 auto_import, 480 auto_import,
diff --git a/crates/ra_assists/src/handlers/fill_match_arms.rs b/crates/ra_assists/src/handlers/fill_match_arms.rs
index fbd6a3ec3..add82e5b1 100644
--- a/crates/ra_assists/src/handlers/fill_match_arms.rs
+++ b/crates/ra_assists/src/handlers/fill_match_arms.rs
@@ -2,7 +2,8 @@
2 2
3use std::iter; 3use std::iter;
4 4
5use hir::{Adt, HasSource, Semantics}; 5use hir::{Adt, HasSource, ModuleDef, Semantics};
6use itertools::Itertools;
6use ra_ide_db::RootDatabase; 7use ra_ide_db::RootDatabase;
7 8
8use crate::{Assist, AssistCtx, AssistId}; 9use crate::{Assist, AssistCtx, AssistId};
@@ -29,8 +30,8 @@ use ast::{MatchArm, Pat};
29// 30//
30// fn handle(action: Action) { 31// fn handle(action: Action) {
31// match action { 32// match action {
32// Action::Move { distance } => (), 33// Action::Move { distance } => {}
33// Action::Stop => (), 34// Action::Stop => {}
34// } 35// }
35// } 36// }
36// ``` 37// ```
@@ -39,13 +40,6 @@ pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option<Assist> {
39 let match_arm_list = match_expr.match_arm_list()?; 40 let match_arm_list = match_expr.match_arm_list()?;
40 41
41 let expr = match_expr.expr()?; 42 let expr = match_expr.expr()?;
42 let enum_def = resolve_enum_def(&ctx.sema, &expr)?;
43 let module = ctx.sema.scope(expr.syntax()).module()?;
44
45 let variants = enum_def.variants(ctx.db);
46 if variants.is_empty() {
47 return None;
48 }
49 43
50 let mut arms: Vec<MatchArm> = match_arm_list.arms().collect(); 44 let mut arms: Vec<MatchArm> = match_arm_list.arms().collect();
51 if arms.len() == 1 { 45 if arms.len() == 1 {
@@ -54,13 +48,49 @@ pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option<Assist> {
54 } 48 }
55 } 49 }
56 50
57 let db = ctx.db; 51 let module = ctx.sema.scope(expr.syntax()).module()?;
58 let missing_arms: Vec<MatchArm> = variants 52
59 .into_iter() 53 let missing_arms: Vec<MatchArm> = if let Some(enum_def) = resolve_enum_def(&ctx.sema, &expr) {
60 .filter_map(|variant| build_pat(db, module, variant)) 54 let variants = enum_def.variants(ctx.db);
61 .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat)) 55
62 .map(|pat| make::match_arm(iter::once(pat), make::expr_unit())) 56 variants
63 .collect(); 57 .into_iter()
58 .filter_map(|variant| build_pat(ctx.db, module, variant))
59 .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat))
60 .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block()))
61 .collect()
62 } else if let Some(enum_defs) = resolve_tuple_of_enum_def(&ctx.sema, &expr) {
63 // Partial fill not currently supported for tuple of enums.
64 if !arms.is_empty() {
65 return None;
66 }
67
68 // We do not currently support filling match arms for a tuple
69 // containing a single enum.
70 if enum_defs.len() < 2 {
71 return None;
72 }
73
74 // When calculating the match arms for a tuple of enums, we want
75 // to create a match arm for each possible combination of enum
76 // values. The `multi_cartesian_product` method transforms
77 // Vec<Vec<EnumVariant>> into Vec<(EnumVariant, .., EnumVariant)>
78 // where each tuple represents a proposed match arm.
79 enum_defs
80 .into_iter()
81 .map(|enum_def| enum_def.variants(ctx.db))
82 .multi_cartesian_product()
83 .map(|variants| {
84 let patterns =
85 variants.into_iter().filter_map(|variant| build_pat(ctx.db, module, variant));
86 ast::Pat::from(make::tuple_pat(patterns))
87 })
88 .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat))
89 .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block()))
90 .collect()
91 } else {
92 return None;
93 };
64 94
65 if missing_arms.is_empty() { 95 if missing_arms.is_empty() {
66 return None; 96 return None;
@@ -104,8 +134,27 @@ fn resolve_enum_def(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<
104 }) 134 })
105} 135}
106 136
137fn resolve_tuple_of_enum_def(
138 sema: &Semantics<RootDatabase>,
139 expr: &ast::Expr,
140) -> Option<Vec<hir::Enum>> {
141 sema.type_of_expr(&expr)?
142 .tuple_fields(sema.db)
143 .iter()
144 .map(|ty| {
145 ty.autoderef(sema.db).find_map(|ty| match ty.as_adt() {
146 Some(Adt::Enum(e)) => Some(e),
147 // For now we only handle expansion for a tuple of enums. Here
148 // we map non-enum items to None and rely on `collect` to
149 // convert Vec<Option<hir::Enum>> into Option<Vec<hir::Enum>>.
150 _ => None,
151 })
152 })
153 .collect()
154}
155
107fn build_pat(db: &RootDatabase, module: hir::Module, var: hir::EnumVariant) -> Option<ast::Pat> { 156fn build_pat(db: &RootDatabase, module: hir::Module, var: hir::EnumVariant) -> Option<ast::Pat> {
108 let path = crate::ast_transform::path_to_ast(module.find_use_path(db, var.into())?); 157 let path = crate::ast_transform::path_to_ast(module.find_use_path(db, ModuleDef::from(var))?);
109 158
110 // FIXME: use HIR for this; it doesn't currently expose struct vs. tuple vs. unit variants though 159 // FIXME: use HIR for this; it doesn't currently expose struct vs. tuple vs. unit variants though
111 let pat: ast::Pat = match var.source(db).value.kind() { 160 let pat: ast::Pat = match var.source(db).value.kind() {
@@ -143,8 +192,23 @@ mod tests {
143 fn main() { 192 fn main() {
144 match A::As<|> { 193 match A::As<|> {
145 A::As, 194 A::As,
146 A::Bs{x,y:Some(_)} => (), 195 A::Bs{x,y:Some(_)} => {}
147 A::Cs(_, Some(_)) => (), 196 A::Cs(_, Some(_)) => {}
197 }
198 }
199 "#,
200 );
201 }
202
203 #[test]
204 fn tuple_of_non_enum() {
205 // for now this case is not handled, although it potentially could be
206 // in the future
207 check_assist_not_applicable(
208 fill_match_arms,
209 r#"
210 fn main() {
211 match (0, false)<|> {
148 } 212 }
149 } 213 }
150 "#, 214 "#,
@@ -163,8 +227,8 @@ mod tests {
163 } 227 }
164 fn main() { 228 fn main() {
165 match A::As<|> { 229 match A::As<|> {
166 A::Bs{x,y:Some(_)} => (), 230 A::Bs{x,y:Some(_)} => {}
167 A::Cs(_, Some(_)) => (), 231 A::Cs(_, Some(_)) => {}
168 } 232 }
169 } 233 }
170 "#, 234 "#,
@@ -176,9 +240,9 @@ mod tests {
176 } 240 }
177 fn main() { 241 fn main() {
178 match <|>A::As { 242 match <|>A::As {
179 A::Bs{x,y:Some(_)} => (), 243 A::Bs{x,y:Some(_)} => {}
180 A::Cs(_, Some(_)) => (), 244 A::Cs(_, Some(_)) => {}
181 A::As => (), 245 A::As => {}
182 } 246 }
183 } 247 }
184 "#, 248 "#,
@@ -197,7 +261,7 @@ mod tests {
197 } 261 }
198 fn main() { 262 fn main() {
199 match A::As<|> { 263 match A::As<|> {
200 A::Cs(_) | A::Bs => (), 264 A::Cs(_) | A::Bs => {}
201 } 265 }
202 } 266 }
203 "#, 267 "#,
@@ -209,8 +273,8 @@ mod tests {
209 } 273 }
210 fn main() { 274 fn main() {
211 match <|>A::As { 275 match <|>A::As {
212 A::Cs(_) | A::Bs => (), 276 A::Cs(_) | A::Bs => {}
213 A::As => (), 277 A::As => {}
214 } 278 }
215 } 279 }
216 "#, 280 "#,
@@ -235,8 +299,8 @@ mod tests {
235 } 299 }
236 fn main() { 300 fn main() {
237 match A::As<|> { 301 match A::As<|> {
238 A::Bs if 0 < 1 => (), 302 A::Bs if 0 < 1 => {}
239 A::Ds(_value) => (), 303 A::Ds(_value) => { let x = 1; }
240 A::Es(B::Xs) => (), 304 A::Es(B::Xs) => (),
241 } 305 }
242 } 306 }
@@ -255,11 +319,11 @@ mod tests {
255 } 319 }
256 fn main() { 320 fn main() {
257 match <|>A::As { 321 match <|>A::As {
258 A::Bs if 0 < 1 => (), 322 A::Bs if 0 < 1 => {}
259 A::Ds(_value) => (), 323 A::Ds(_value) => { let x = 1; }
260 A::Es(B::Xs) => (), 324 A::Es(B::Xs) => (),
261 A::As => (), 325 A::As => {}
262 A::Cs => (), 326 A::Cs => {}
263 } 327 }
264 } 328 }
265 "#, 329 "#,
@@ -296,11 +360,174 @@ mod tests {
296 fn main() { 360 fn main() {
297 let a = A::As; 361 let a = A::As;
298 match <|>a { 362 match <|>a {
299 A::As => (), 363 A::As => {}
300 A::Bs => (), 364 A::Bs => {}
301 A::Cs(_) => (), 365 A::Cs(_) => {}
302 A::Ds(_, _) => (), 366 A::Ds(_, _) => {}
303 A::Es { x, y } => (), 367 A::Es { x, y } => {}
368 }
369 }
370 "#,
371 );
372 }
373
374 #[test]
375 fn fill_match_arms_tuple_of_enum() {
376 check_assist(
377 fill_match_arms,
378 r#"
379 enum A {
380 One,
381 Two,
382 }
383 enum B {
384 One,
385 Two,
386 }
387
388 fn main() {
389 let a = A::One;
390 let b = B::One;
391 match (a<|>, b) {}
392 }
393 "#,
394 r#"
395 enum A {
396 One,
397 Two,
398 }
399 enum B {
400 One,
401 Two,
402 }
403
404 fn main() {
405 let a = A::One;
406 let b = B::One;
407 match <|>(a, b) {
408 (A::One, B::One) => {}
409 (A::One, B::Two) => {}
410 (A::Two, B::One) => {}
411 (A::Two, B::Two) => {}
412 }
413 }
414 "#,
415 );
416 }
417
418 #[test]
419 fn fill_match_arms_tuple_of_enum_ref() {
420 check_assist(
421 fill_match_arms,
422 r#"
423 enum A {
424 One,
425 Two,
426 }
427 enum B {
428 One,
429 Two,
430 }
431
432 fn main() {
433 let a = A::One;
434 let b = B::One;
435 match (&a<|>, &b) {}
436 }
437 "#,
438 r#"
439 enum A {
440 One,
441 Two,
442 }
443 enum B {
444 One,
445 Two,
446 }
447
448 fn main() {
449 let a = A::One;
450 let b = B::One;
451 match <|>(&a, &b) {
452 (A::One, B::One) => {}
453 (A::One, B::Two) => {}
454 (A::Two, B::One) => {}
455 (A::Two, B::Two) => {}
456 }
457 }
458 "#,
459 );
460 }
461
462 #[test]
463 fn fill_match_arms_tuple_of_enum_partial() {
464 check_assist_not_applicable(
465 fill_match_arms,
466 r#"
467 enum A {
468 One,
469 Two,
470 }
471 enum B {
472 One,
473 Two,
474 }
475
476 fn main() {
477 let a = A::One;
478 let b = B::One;
479 match (a<|>, b) {
480 (A::Two, B::One) => {}
481 }
482 }
483 "#,
484 );
485 }
486
487 #[test]
488 fn fill_match_arms_tuple_of_enum_not_applicable() {
489 check_assist_not_applicable(
490 fill_match_arms,
491 r#"
492 enum A {
493 One,
494 Two,
495 }
496 enum B {
497 One,
498 Two,
499 }
500
501 fn main() {
502 let a = A::One;
503 let b = B::One;
504 match (a<|>, b) {
505 (A::Two, B::One) => {}
506 (A::One, B::One) => {}
507 (A::One, B::Two) => {}
508 (A::Two, B::Two) => {}
509 }
510 }
511 "#,
512 );
513 }
514
515 #[test]
516 fn fill_match_arms_single_element_tuple_of_enum() {
517 // For now we don't hande the case of a single element tuple, but
518 // we could handle this in the future if `make::tuple_pat` allowed
519 // creating a tuple with a single pattern.
520 check_assist_not_applicable(
521 fill_match_arms,
522 r#"
523 enum A {
524 One,
525 Two,
526 }
527
528 fn main() {
529 let a = A::One;
530 match (a<|>, ) {
304 } 531 }
305 } 532 }
306 "#, 533 "#,
@@ -328,7 +555,7 @@ mod tests {
328 555
329 fn foo(a: &A) { 556 fn foo(a: &A) {
330 match <|>a { 557 match <|>a {
331 A::As => (), 558 A::As => {}
332 } 559 }
333 } 560 }
334 "#, 561 "#,
@@ -353,7 +580,7 @@ mod tests {
353 580
354 fn foo(a: &mut A) { 581 fn foo(a: &mut A) {
355 match <|>a { 582 match <|>a {
356 A::Es { x, y } => (), 583 A::Es { x, y } => {}
357 } 584 }
358 } 585 }
359 "#, 586 "#,
@@ -384,7 +611,7 @@ mod tests {
384 611
385 fn main() { 612 fn main() {
386 match E::X { 613 match E::X {
387 <|>_ => {}, 614 <|>_ => {}
388 } 615 }
389 } 616 }
390 "#, 617 "#,
@@ -393,8 +620,8 @@ mod tests {
393 620
394 fn main() { 621 fn main() {
395 match <|>E::X { 622 match <|>E::X {
396 E::X => (), 623 E::X => {}
397 E::Y => (), 624 E::Y => {}
398 } 625 }
399 } 626 }
400 "#, 627 "#,
@@ -421,8 +648,8 @@ mod tests {
421 648
422 fn main() { 649 fn main() {
423 match <|>X { 650 match <|>X {
424 X => (), 651 X => {}
425 foo::E::Y => (), 652 foo::E::Y => {}
426 } 653 }
427 } 654 }
428 "#, 655 "#,
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs
index b8704ea7d..bcc9b3f10 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -165,7 +165,6 @@ mod helpers {
165 165
166 use ra_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt}; 166 use ra_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt};
167 use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase}; 167 use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase};
168 use ra_syntax::TextRange;
169 use test_utils::{add_cursor, assert_eq_text, extract_range_or_offset, RangeOrOffset}; 168 use test_utils::{add_cursor, assert_eq_text, extract_range_or_offset, RangeOrOffset};
170 169
171 use crate::{AssistCtx, AssistHandler}; 170 use crate::{AssistCtx, AssistHandler};
@@ -175,8 +174,7 @@ mod helpers {
175 let (mut db, file_id) = RootDatabase::with_single_file(text); 174 let (mut db, file_id) = RootDatabase::with_single_file(text);
176 // FIXME: ideally, this should be done by the above `RootDatabase::with_single_file`, 175 // FIXME: ideally, this should be done by the above `RootDatabase::with_single_file`,
177 // but it looks like this might need specialization? :( 176 // but it looks like this might need specialization? :(
178 let local_roots = vec![db.file_source_root(file_id)]; 177 db.set_local_roots(Arc::new(vec![db.file_source_root(file_id)]));
179 db.set_local_roots(Arc::new(local_roots));
180 (db, file_id) 178 (db, file_id)
181 } 179 }
182 180
@@ -206,11 +204,24 @@ mod helpers {
206 } 204 }
207 205
208 fn check(assist: AssistHandler, before: &str, expected: ExpectedResult) { 206 fn check(assist: AssistHandler, before: &str, expected: ExpectedResult) {
209 let (range_or_offset, before) = extract_range_or_offset(before); 207 let (text_without_caret, file_with_caret_id, range_or_offset, db) =
210 let range: TextRange = range_or_offset.into(); 208 if before.contains("//-") {
209 let (mut db, position) = RootDatabase::with_position(before);
210 db.set_local_roots(Arc::new(vec![db.file_source_root(position.file_id)]));
211 (
212 db.file_text(position.file_id).as_ref().to_owned(),
213 position.file_id,
214 RangeOrOffset::Offset(position.offset),
215 db,
216 )
217 } else {
218 let (range_or_offset, text_without_caret) = extract_range_or_offset(before);
219 let (db, file_id) = with_single_file(&text_without_caret);
220 (text_without_caret, file_id, range_or_offset, db)
221 };
222
223 let frange = FileRange { file_id: file_with_caret_id, range: range_or_offset.into() };
211 224
212 let (db, file_id) = with_single_file(&before);
213 let frange = FileRange { file_id, range };
214 let sema = Semantics::new(&db); 225 let sema = Semantics::new(&db);
215 let assist_ctx = AssistCtx::new(&sema, frange, true); 226 let assist_ctx = AssistCtx::new(&sema, frange, true);
216 227
@@ -218,7 +229,7 @@ mod helpers {
218 (Some(assist), ExpectedResult::After(after)) => { 229 (Some(assist), ExpectedResult::After(after)) => {
219 let action = assist.0[0].action.clone().unwrap(); 230 let action = assist.0[0].action.clone().unwrap();
220 231
221 let mut actual = action.edit.apply(&before); 232 let mut actual = action.edit.apply(&text_without_caret);
222 match action.cursor_position { 233 match action.cursor_position {
223 None => { 234 None => {
224 if let RangeOrOffset::Offset(before_cursor_pos) = range_or_offset { 235 if let RangeOrOffset::Offset(before_cursor_pos) = range_or_offset {
@@ -237,7 +248,7 @@ mod helpers {
237 (Some(assist), ExpectedResult::Target(target)) => { 248 (Some(assist), ExpectedResult::Target(target)) => {
238 let action = assist.0[0].action.clone().unwrap(); 249 let action = assist.0[0].action.clone().unwrap();
239 let range = action.target.expect("expected target on action"); 250 let range = action.target.expect("expected target on action");
240 assert_eq_text!(&before[range], target); 251 assert_eq_text!(&text_without_caret[range], target);
241 } 252 }
242 (Some(_), ExpectedResult::NotApplicable) => panic!("assist should not be applicable!"), 253 (Some(_), ExpectedResult::NotApplicable) => panic!("assist should not be applicable!"),
243 (None, ExpectedResult::After(_)) | (None, ExpectedResult::Target(_)) => { 254 (None, ExpectedResult::After(_)) | (None, ExpectedResult::Target(_)) => {
diff --git a/crates/ra_fmt/Cargo.toml b/crates/ra_fmt/Cargo.toml
index ea9befeaf..e9d057afc 100644
--- a/crates/ra_fmt/Cargo.toml
+++ b/crates/ra_fmt/Cargo.toml
@@ -9,6 +9,6 @@ publish = false
9doctest = false 9doctest = false
10 10
11[dependencies] 11[dependencies]
12itertools = "0.8.2" 12itertools = "0.9.0"
13 13
14ra_syntax = { path = "../ra_syntax" } 14ra_syntax = { path = "../ra_syntax" }
diff --git a/crates/ra_hir/Cargo.toml b/crates/ra_hir/Cargo.toml
index 42193b492..ba7b39a19 100644
--- a/crates/ra_hir/Cargo.toml
+++ b/crates/ra_hir/Cargo.toml
@@ -13,7 +13,7 @@ rustc-hash = "1.1.0"
13either = "1.5.3" 13either = "1.5.3"
14arrayvec = "0.5.1" 14arrayvec = "0.5.1"
15 15
16itertools = "0.8.2" 16itertools = "0.9.0"
17 17
18ra_syntax = { path = "../ra_syntax" } 18ra_syntax = { path = "../ra_syntax" }
19ra_db = { path = "../ra_db" } 19ra_db = { path = "../ra_db" }
diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs
index e91abf6f5..c5cfd875f 100644
--- a/crates/ra_hir/src/code_model.rs
+++ b/crates/ra_hir/src/code_model.rs
@@ -33,7 +33,11 @@ use ra_syntax::{
33}; 33};
34use rustc_hash::FxHashSet; 34use rustc_hash::FxHashSet;
35 35
36use crate::{db::HirDatabase, has_source::HasSource, CallableDef, HirDisplay, InFile, Name}; 36use crate::{
37 db::{DefDatabase, HirDatabase},
38 has_source::HasSource,
39 CallableDef, HirDisplay, InFile, Name,
40};
37 41
38/// hir::Crate describes a single crate. It's the main interface with which 42/// hir::Crate describes a single crate. It's the main interface with which
39/// a crate's dependencies interact. Mostly, it should be just a proxy for the 43/// a crate's dependencies interact. Mostly, it should be just a proxy for the
@@ -274,20 +278,10 @@ impl Module {
274 /// this module, if possible. 278 /// this module, if possible.
275 pub fn find_use_path( 279 pub fn find_use_path(
276 self, 280 self,
277 db: &dyn HirDatabase, 281 db: &dyn DefDatabase,
278 item: ModuleDef, 282 item: impl Into<ItemInNs>,
279 ) -> Option<hir_def::path::ModPath> { 283 ) -> Option<hir_def::path::ModPath> {
280 // FIXME expose namespace choice 284 hir_def::find_path::find_path(db, item.into(), self.into())
281 hir_def::find_path::find_path(db.upcast(), determine_item_namespace(item), self.into())
282 }
283}
284
285fn determine_item_namespace(module_def: ModuleDef) -> ItemInNs {
286 match module_def {
287 ModuleDef::Static(_) | ModuleDef::Const(_) | ModuleDef::Function(_) => {
288 ItemInNs::Values(module_def.into())
289 }
290 _ => ItemInNs::Types(module_def.into()),
291 } 285 }
292} 286}
293 287
diff --git a/crates/ra_hir/src/from_id.rs b/crates/ra_hir/src/from_id.rs
index c179b13c6..62fb52e72 100644
--- a/crates/ra_hir/src/from_id.rs
+++ b/crates/ra_hir/src/from_id.rs
@@ -9,8 +9,8 @@ use hir_def::{
9}; 9};
10 10
11use crate::{ 11use crate::{
12 Adt, AssocItem, AttrDef, DefWithBody, EnumVariant, GenericDef, Local, ModuleDef, StructField, 12 code_model::ItemInNs, Adt, AssocItem, AttrDef, DefWithBody, EnumVariant, GenericDef, Local,
13 VariantDef, 13 MacroDef, ModuleDef, StructField, VariantDef,
14}; 14};
15 15
16macro_rules! from_id { 16macro_rules! from_id {
@@ -228,3 +228,20 @@ impl From<(DefWithBodyId, PatId)> for Local {
228 Local { parent, pat_id } 228 Local { parent, pat_id }
229 } 229 }
230} 230}
231
232impl From<MacroDef> for ItemInNs {
233 fn from(macro_def: MacroDef) -> Self {
234 ItemInNs::Macros(macro_def.into())
235 }
236}
237
238impl From<ModuleDef> for ItemInNs {
239 fn from(module_def: ModuleDef) -> Self {
240 match module_def {
241 ModuleDef::Static(_) | ModuleDef::Const(_) | ModuleDef::Function(_) => {
242 ItemInNs::Values(module_def.into())
243 }
244 _ => ItemInNs::Types(module_def.into()),
245 }
246 }
247}
diff --git a/crates/ra_ide/Cargo.toml b/crates/ra_ide/Cargo.toml
index 7235c944c..36eec0e60 100644
--- a/crates/ra_ide/Cargo.toml
+++ b/crates/ra_ide/Cargo.toml
@@ -14,7 +14,7 @@ wasm = []
14either = "1.5.3" 14either = "1.5.3"
15format-buf = "1.0.0" 15format-buf = "1.0.0"
16indexmap = "1.3.2" 16indexmap = "1.3.2"
17itertools = "0.8.2" 17itertools = "0.9.0"
18join_to_string = "0.1.3" 18join_to_string = "0.1.3"
19log = "0.4.8" 19log = "0.4.8"
20rustc-hash = "1.1.0" 20rustc-hash = "1.1.0"
diff --git a/crates/ra_ide/src/completion/complete_postfix.rs b/crates/ra_ide/src/completion/complete_postfix.rs
index 0ba382165..0a00054b2 100644
--- a/crates/ra_ide/src/completion/complete_postfix.rs
+++ b/crates/ra_ide/src/completion/complete_postfix.rs
@@ -1,6 +1,9 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use ra_syntax::{ast::AstNode, TextRange, TextUnit}; 3use ra_syntax::{
4 ast::{self, AstNode},
5 TextRange, TextUnit,
6};
4use ra_text_edit::TextEdit; 7use ra_text_edit::TextEdit;
5 8
6use crate::{ 9use crate::{
@@ -21,13 +24,8 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
21 None => return, 24 None => return,
22 }; 25 };
23 26
24 let receiver_text = if ctx.dot_receiver_is_ambiguous_float_literal { 27 let receiver_text =
25 let text = dot_receiver.syntax().text(); 28 get_receiver_text(dot_receiver, ctx.dot_receiver_is_ambiguous_float_literal);
26 let without_dot = ..text.len() - TextUnit::of_char('.');
27 text.slice(without_dot).to_string()
28 } else {
29 dot_receiver.syntax().text().to_string()
30 };
31 29
32 let receiver_ty = match ctx.sema.type_of_expr(&dot_receiver) { 30 let receiver_ty = match ctx.sema.type_of_expr(&dot_receiver) {
33 Some(it) => it, 31 Some(it) => it,
@@ -35,10 +33,17 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
35 }; 33 };
36 34
37 if receiver_ty.is_bool() || receiver_ty.is_unknown() { 35 if receiver_ty.is_bool() || receiver_ty.is_unknown() {
38 postfix_snippet(ctx, "if", "if expr {}", &format!("if {} {{$0}}", receiver_text))
39 .add_to(acc);
40 postfix_snippet( 36 postfix_snippet(
41 ctx, 37 ctx,
38 &dot_receiver,
39 "if",
40 "if expr {}",
41 &format!("if {} {{$0}}", receiver_text),
42 )
43 .add_to(acc);
44 postfix_snippet(
45 ctx,
46 &dot_receiver,
42 "while", 47 "while",
43 "while expr {}", 48 "while expr {}",
44 &format!("while {} {{\n$0\n}}", receiver_text), 49 &format!("while {} {{\n$0\n}}", receiver_text),
@@ -46,28 +51,70 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
46 .add_to(acc); 51 .add_to(acc);
47 } 52 }
48 53
49 postfix_snippet(ctx, "not", "!expr", &format!("!{}", receiver_text)).add_to(acc); 54 // !&&&42 is a compiler error, ergo process it before considering the references
55 postfix_snippet(ctx, &dot_receiver, "not", "!expr", &format!("!{}", receiver_text)).add_to(acc);
50 56
51 postfix_snippet(ctx, "ref", "&expr", &format!("&{}", receiver_text)).add_to(acc); 57 postfix_snippet(ctx, &dot_receiver, "ref", "&expr", &format!("&{}", receiver_text)).add_to(acc);
52 postfix_snippet(ctx, "refm", "&mut expr", &format!("&mut {}", receiver_text)).add_to(acc); 58 postfix_snippet(ctx, &dot_receiver, "refm", "&mut expr", &format!("&mut {}", receiver_text))
59 .add_to(acc);
60
61 // The rest of the postfix completions create an expression that moves an argument,
62 // so it's better to consider references now to avoid breaking the compilation
63 let dot_receiver = include_references(dot_receiver);
64 let receiver_text =
65 get_receiver_text(&dot_receiver, ctx.dot_receiver_is_ambiguous_float_literal);
53 66
54 postfix_snippet( 67 postfix_snippet(
55 ctx, 68 ctx,
69 &dot_receiver,
56 "match", 70 "match",
57 "match expr {}", 71 "match expr {}",
58 &format!("match {} {{\n ${{1:_}} => {{$0\\}},\n}}", receiver_text), 72 &format!("match {} {{\n ${{1:_}} => {{$0\\}},\n}}", receiver_text),
59 ) 73 )
60 .add_to(acc); 74 .add_to(acc);
61 75
62 postfix_snippet(ctx, "dbg", "dbg!(expr)", &format!("dbg!({})", receiver_text)).add_to(acc); 76 postfix_snippet(
77 ctx,
78 &dot_receiver,
79 "box",
80 "Box::new(expr)",
81 &format!("Box::new({})", receiver_text),
82 )
83 .add_to(acc);
63 84
64 postfix_snippet(ctx, "box", "Box::new(expr)", &format!("Box::new({})", receiver_text)) 85 postfix_snippet(ctx, &dot_receiver, "dbg", "dbg!(expr)", &format!("dbg!({})", receiver_text))
65 .add_to(acc); 86 .add_to(acc);
66} 87}
67 88
68fn postfix_snippet(ctx: &CompletionContext, label: &str, detail: &str, snippet: &str) -> Builder { 89fn get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal: bool) -> String {
90 if receiver_is_ambiguous_float_literal {
91 let text = receiver.syntax().text();
92 let without_dot = ..text.len() - TextUnit::of_char('.');
93 text.slice(without_dot).to_string()
94 } else {
95 receiver.to_string()
96 }
97}
98
99fn include_references(initial_element: &ast::Expr) -> ast::Expr {
100 let mut resulting_element = initial_element.clone();
101 while let Some(parent_ref_element) =
102 resulting_element.syntax().parent().and_then(ast::RefExpr::cast)
103 {
104 resulting_element = ast::Expr::from(parent_ref_element);
105 }
106 resulting_element
107}
108
109fn postfix_snippet(
110 ctx: &CompletionContext,
111 receiver: &ast::Expr,
112 label: &str,
113 detail: &str,
114 snippet: &str,
115) -> Builder {
69 let edit = { 116 let edit = {
70 let receiver_syntax = ctx.dot_receiver.as_ref().expect("no receiver available").syntax(); 117 let receiver_syntax = receiver.syntax();
71 let receiver_range = ctx.sema.original_range(receiver_syntax).range; 118 let receiver_range = ctx.sema.original_range(receiver_syntax).range;
72 let delete_range = TextRange::from_to(receiver_range.start(), ctx.source_range().end()); 119 let delete_range = TextRange::from_to(receiver_range.start(), ctx.source_range().end());
73 TextEdit::replace(delete_range, snippet.to_string()) 120 TextEdit::replace(delete_range, snippet.to_string())
@@ -340,4 +387,63 @@ mod tests {
340 "### 387 "###
341 ); 388 );
342 } 389 }
390
391 #[test]
392 fn postfix_completion_for_references() {
393 assert_debug_snapshot!(
394 do_postfix_completion(
395 r#"
396 fn main() {
397 &&&&42.<|>
398 }
399 "#,
400 ),
401 @r###"
402 [
403 CompletionItem {
404 label: "box",
405 source_range: [56; 56),
406 delete: [49; 56),
407 insert: "Box::new(&&&&42)",
408 detail: "Box::new(expr)",
409 },
410 CompletionItem {
411 label: "dbg",
412 source_range: [56; 56),
413 delete: [49; 56),
414 insert: "dbg!(&&&&42)",
415 detail: "dbg!(expr)",
416 },
417 CompletionItem {
418 label: "match",
419 source_range: [56; 56),
420 delete: [49; 56),
421 insert: "match &&&&42 {\n ${1:_} => {$0\\},\n}",
422 detail: "match expr {}",
423 },
424 CompletionItem {
425 label: "not",
426 source_range: [56; 56),
427 delete: [53; 56),
428 insert: "!42",
429 detail: "!expr",
430 },
431 CompletionItem {
432 label: "ref",
433 source_range: [56; 56),
434 delete: [53; 56),
435 insert: "&42",
436 detail: "&expr",
437 },
438 CompletionItem {
439 label: "refm",
440 source_range: [56; 56),
441 delete: [53; 56),
442 insert: "&mut 42",
443 detail: "&mut expr",
444 },
445 ]
446 "###
447 );
448 }
343} 449}
diff --git a/crates/ra_ide/src/completion/complete_record_literal.rs b/crates/ra_ide/src/completion/complete_record_literal.rs
index 83ed1d52c..e4e764f58 100644
--- a/crates/ra_ide/src/completion/complete_record_literal.rs
+++ b/crates/ra_ide/src/completion/complete_record_literal.rs
@@ -1,6 +1,7 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use crate::completion::{CompletionContext, Completions}; 3use crate::completion::{CompletionContext, Completions};
4use ra_syntax::SmolStr;
4 5
5/// Complete fields in fields literals. 6/// Complete fields in fields literals.
6pub(super) fn complete_record_literal(acc: &mut Completions, ctx: &CompletionContext) { 7pub(super) fn complete_record_literal(acc: &mut Completions, ctx: &CompletionContext) {
@@ -11,8 +12,24 @@ pub(super) fn complete_record_literal(acc: &mut Completions, ctx: &CompletionCon
11 _ => return, 12 _ => return,
12 }; 13 };
13 14
15 let already_present_names: Vec<SmolStr> = ctx
16 .record_lit_syntax
17 .as_ref()
18 .and_then(|record_literal| record_literal.record_field_list())
19 .map(|field_list| field_list.fields())
20 .map(|fields| {
21 fields
22 .into_iter()
23 .filter_map(|field| field.name_ref())
24 .map(|name_ref| name_ref.text().clone())
25 .collect()
26 })
27 .unwrap_or_default();
28
14 for (field, field_ty) in ty.variant_fields(ctx.db, variant) { 29 for (field, field_ty) in ty.variant_fields(ctx.db, variant) {
15 acc.add_field(ctx, field, &field_ty); 30 if !already_present_names.contains(&SmolStr::from(field.name(ctx.db).to_string())) {
31 acc.add_field(ctx, field, &field_ty);
32 }
16 } 33 }
17} 34}
18 35
@@ -178,4 +195,47 @@ mod tests {
178 ] 195 ]
179 "###); 196 "###);
180 } 197 }
198
199 #[test]
200 fn only_missing_fields_are_completed() {
201 let completions = complete(
202 r"
203 struct S {
204 foo1: u32,
205 foo2: u32,
206 bar: u32,
207 baz: u32,
208 }
209
210 fn main() {
211 let foo1 = 1;
212 let s = S {
213 foo1,
214 foo2: 5,
215 <|>
216 }
217 }
218 ",
219 );
220 assert_debug_snapshot!(completions, @r###"
221 [
222 CompletionItem {
223 label: "bar",
224 source_range: [302; 302),
225 delete: [302; 302),
226 insert: "bar",
227 kind: Field,
228 detail: "u32",
229 },
230 CompletionItem {
231 label: "baz",
232 source_range: [302; 302),
233 delete: [302; 302),
234 insert: "baz",
235 kind: Field,
236 detail: "u32",
237 },
238 ]
239 "###);
240 }
181} 241}
diff --git a/crates/ra_ide/src/completion/complete_trait_impl.rs b/crates/ra_ide/src/completion/complete_trait_impl.rs
index 7fefa2c7a..ded1ff3bc 100644
--- a/crates/ra_ide/src/completion/complete_trait_impl.rs
+++ b/crates/ra_ide/src/completion/complete_trait_impl.rs
@@ -193,7 +193,7 @@ fn add_const_impl(
193} 193}
194 194
195fn make_const_compl_syntax(const_: &ast::ConstDef) -> String { 195fn make_const_compl_syntax(const_: &ast::ConstDef) -> String {
196 let const_ = edit::strip_attrs_and_docs(const_); 196 let const_ = edit::remove_attrs_and_docs(const_);
197 197
198 let const_start = const_.syntax().text_range().start(); 198 let const_start = const_.syntax().text_range().start();
199 let const_end = const_.syntax().text_range().end(); 199 let const_end = const_.syntax().text_range().end();
diff --git a/crates/ra_ide_db/Cargo.toml b/crates/ra_ide_db/Cargo.toml
index de4f5bce0..c3921bd40 100644
--- a/crates/ra_ide_db/Cargo.toml
+++ b/crates/ra_ide_db/Cargo.toml
@@ -17,6 +17,7 @@ fst = { version = "0.4", default-features = false }
17rustc-hash = "1.1.0" 17rustc-hash = "1.1.0"
18superslice = "1.0.0" 18superslice = "1.0.0"
19once_cell = "1.3.1" 19once_cell = "1.3.1"
20either = "1.5.3"
20 21
21ra_syntax = { path = "../ra_syntax" } 22ra_syntax = { path = "../ra_syntax" }
22ra_text_edit = { path = "../ra_text_edit" } 23ra_text_edit = { path = "../ra_text_edit" }
diff --git a/crates/ra_ide_db/src/imports_locator.rs b/crates/ra_ide_db/src/imports_locator.rs
index c96351982..bf0d8db60 100644
--- a/crates/ra_ide_db/src/imports_locator.rs
+++ b/crates/ra_ide_db/src/imports_locator.rs
@@ -1,7 +1,7 @@
1//! This module contains an import search funcionality that is provided to the ra_assists module. 1//! This module contains an import search funcionality that is provided to the ra_assists module.
2//! Later, this should be moved away to a separate crate that is accessible from the ra_assists module. 2//! Later, this should be moved away to a separate crate that is accessible from the ra_assists module.
3 3
4use hir::{ModuleDef, Semantics}; 4use hir::{MacroDef, ModuleDef, Semantics};
5use ra_prof::profile; 5use ra_prof::profile;
6use ra_syntax::{ast, AstNode, SyntaxKind::NAME}; 6use ra_syntax::{ast, AstNode, SyntaxKind::NAME};
7 7
@@ -10,6 +10,7 @@ use crate::{
10 symbol_index::{self, FileSymbol, Query}, 10 symbol_index::{self, FileSymbol, Query},
11 RootDatabase, 11 RootDatabase,
12}; 12};
13use either::Either;
13 14
14pub struct ImportsLocator<'a> { 15pub struct ImportsLocator<'a> {
15 sema: Semantics<'a, RootDatabase>, 16 sema: Semantics<'a, RootDatabase>,
@@ -20,7 +21,7 @@ impl<'a> ImportsLocator<'a> {
20 Self { sema: Semantics::new(db) } 21 Self { sema: Semantics::new(db) }
21 } 22 }
22 23
23 pub fn find_imports(&mut self, name_to_import: &str) -> Vec<ModuleDef> { 24 pub fn find_imports(&mut self, name_to_import: &str) -> Vec<Either<ModuleDef, MacroDef>> {
24 let _p = profile("search_for_imports"); 25 let _p = profile("search_for_imports");
25 let db = self.sema.db; 26 let db = self.sema.db;
26 27
@@ -43,7 +44,8 @@ impl<'a> ImportsLocator<'a> {
43 .chain(lib_results.into_iter()) 44 .chain(lib_results.into_iter())
44 .filter_map(|import_candidate| self.get_name_definition(&import_candidate)) 45 .filter_map(|import_candidate| self.get_name_definition(&import_candidate))
45 .filter_map(|name_definition_to_import| match name_definition_to_import { 46 .filter_map(|name_definition_to_import| match name_definition_to_import {
46 Definition::ModuleDef(module_def) => Some(module_def), 47 Definition::ModuleDef(module_def) => Some(Either::Left(module_def)),
48 Definition::Macro(macro_def) => Some(Either::Right(macro_def)),
47 _ => None, 49 _ => None,
48 }) 50 })
49 .collect() 51 .collect()
diff --git a/crates/ra_syntax/Cargo.toml b/crates/ra_syntax/Cargo.toml
index 8efc6b368..6fccc2303 100644
--- a/crates/ra_syntax/Cargo.toml
+++ b/crates/ra_syntax/Cargo.toml
@@ -11,7 +11,7 @@ repository = "https://github.com/rust-analyzer/rust-analyzer"
11doctest = false 11doctest = false
12 12
13[dependencies] 13[dependencies]
14itertools = "0.8.2" 14itertools = "0.9.0"
15rowan = "0.9.1" 15rowan = "0.9.1"
16rustc_lexer = "0.1.0" 16rustc_lexer = "0.1.0"
17rustc-hash = "1.1.0" 17rustc-hash = "1.1.0"
diff --git a/crates/ra_syntax/src/ast/edit.rs b/crates/ra_syntax/src/ast/edit.rs
index df4ffefbf..f74c9f9c6 100644
--- a/crates/ra_syntax/src/ast/edit.rs
+++ b/crates/ra_syntax/src/ast/edit.rs
@@ -334,11 +334,11 @@ impl ast::UseTree {
334} 334}
335 335
336#[must_use] 336#[must_use]
337pub fn strip_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N { 337pub fn remove_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N {
338 N::cast(strip_attrs_and_docs_inner(node.syntax().clone())).unwrap() 338 N::cast(remove_attrs_and_docs_inner(node.syntax().clone())).unwrap()
339} 339}
340 340
341fn strip_attrs_and_docs_inner(mut node: SyntaxNode) -> SyntaxNode { 341fn remove_attrs_and_docs_inner(mut node: SyntaxNode) -> SyntaxNode {
342 while let Some(start) = 342 while let Some(start) =
343 node.children_with_tokens().find(|it| it.kind() == ATTR || it.kind() == COMMENT) 343 node.children_with_tokens().find(|it| it.kind() == ATTR || it.kind() == COMMENT)
344 { 344 {
diff --git a/crates/ra_syntax/src/ast/make.rs b/crates/ra_syntax/src/ast/make.rs
index 9f6f1cc53..1145b69e8 100644
--- a/crates/ra_syntax/src/ast/make.rs
+++ b/crates/ra_syntax/src/ast/make.rs
@@ -87,6 +87,9 @@ pub fn block_from_expr(e: ast::Expr) -> ast::Block {
87pub fn expr_unit() -> ast::Expr { 87pub fn expr_unit() -> ast::Expr {
88 expr_from_text("()") 88 expr_from_text("()")
89} 89}
90pub fn expr_empty_block() -> ast::Expr {
91 expr_from_text("{}")
92}
90pub fn expr_unimplemented() -> ast::Expr { 93pub fn expr_unimplemented() -> ast::Expr {
91 expr_from_text("unimplemented!()") 94 expr_from_text("unimplemented!()")
92} 95}
@@ -136,6 +139,20 @@ pub fn placeholder_pat() -> ast::PlaceholderPat {
136 } 139 }
137} 140}
138 141
142/// Creates a tuple of patterns from an interator of patterns.
143///
144/// Invariant: `pats` must be length > 1
145///
146/// FIXME handle `pats` length == 1
147pub fn tuple_pat(pats: impl IntoIterator<Item = ast::Pat>) -> ast::TuplePat {
148 let pats_str = pats.into_iter().map(|p| p.to_string()).join(", ");
149 return from_text(&format!("({})", pats_str));
150
151 fn from_text(text: &str) -> ast::TuplePat {
152 ast_from_text(&format!("fn f({}: ())", text))
153 }
154}
155
139pub fn tuple_struct_pat( 156pub fn tuple_struct_pat(
140 path: ast::Path, 157 path: ast::Path,
141 pats: impl IntoIterator<Item = ast::Pat>, 158 pats: impl IntoIterator<Item = ast::Pat>,
diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml
index d44f0ef1d..e071e9b8d 100644
--- a/crates/rust-analyzer/Cargo.toml
+++ b/crates/rust-analyzer/Cargo.toml
@@ -17,7 +17,7 @@ anyhow = "1.0.26"
17crossbeam-channel = "0.4.0" 17crossbeam-channel = "0.4.0"
18env_logger = { version = "0.7.1", default-features = false } 18env_logger = { version = "0.7.1", default-features = false }
19globset = "0.4.4" 19globset = "0.4.4"
20itertools = "0.8.2" 20itertools = "0.9.0"
21jod-thread = "0.1.0" 21jod-thread = "0.1.0"
22log = "0.4.8" 22log = "0.4.8"
23lsp-types = { version = "0.73.0", features = ["proposed"] } 23lsp-types = { version = "0.73.0", features = ["proposed"] }
diff --git a/docs/user/assists.md b/docs/user/assists.md
index e2850b4dd..f3ce6b0e0 100644
--- a/docs/user/assists.md
+++ b/docs/user/assists.md
@@ -267,8 +267,8 @@ enum Action { Move { distance: u32 }, Stop }
267 267
268fn handle(action: Action) { 268fn handle(action: Action) {
269 match action { 269 match action {
270 Action::Move { distance } => (), 270 Action::Move { distance } => {}
271 Action::Stop => (), 271 Action::Stop => {}
272 } 272 }
273} 273}
274``` 274```
diff --git a/docs/user/readme.adoc b/docs/user/readme.adoc
index 0dfc12b52..e00d14dfb 100644
--- a/docs/user/readme.adoc
+++ b/docs/user/readme.adoc
@@ -98,6 +98,16 @@ You'll need Cargo, nodejs and npm for this.
98 98
99Note that installing via `xtask install` does not work for VS Code Remote, instead you'll need to install the `.vsix` manually. 99Note that installing via `xtask install` does not work for VS Code Remote, instead you'll need to install the `.vsix` manually.
100 100
101==== Troubleshooting
102
103Here are some useful self-diagnostic commands:
104
105* **Rust Analyzer: Show RA Version** shows the version of `rust-analyzer` binary
106* **Rust Analyzer: Status** prints some statistics about the server, like the few latest LSP requests
107* To enable server-side logging, run with `env RUST_LOG=info` and see `Output > Rust Analyzer Language Server` in VS Code's panel.
108* To log all LSP requests, add `"rust-analyzer.trace.server": "verbose"` to the settings and look for `Server Trace` in the panel.
109* 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
101=== Language Server Binary 111=== Language Server Binary
102 112
103Other editors generally require `rust-analyzer` binary to be in `$PATH`. 113Other editors generally require `rust-analyzer` binary to be in `$PATH`.
diff --git a/editors/code/package.json b/editors/code/package.json
index eb5748515..1d113ebb6 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -73,10 +73,18 @@
73 "type": "string" 73 "type": "string"
74 }, 74 },
75 "args": { 75 "args": {
76 "type": "array" 76 "type": "array",
77 "items": {
78 "type": "string"
79 }
77 }, 80 },
78 "env": { 81 "env": {
79 "type": "object" 82 "type": "object",
83 "patternProperties": {
84 ".+": {
85 "type": "string"
86 }
87 }
80 } 88 }
81 } 89 }
82 } 90 }
diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts
index 08d821dd0..82ca749f3 100644
--- a/editors/code/src/client.ts
+++ b/editors/code/src/client.ts
@@ -99,8 +99,10 @@ export async function createClient(config: Config, serverPath: string): Promise<
99 // Note that while the CallHierarchyFeature is stable the LSP protocol is not. 99 // Note that while the CallHierarchyFeature is stable the LSP protocol is not.
100 res.registerFeature(new CallHierarchyFeature(res)); 100 res.registerFeature(new CallHierarchyFeature(res));
101 101
102 if (config.highlightingSemanticTokens) { 102 if (config.package.enableProposedApi) {
103 res.registerFeature(new SemanticTokensFeature(res)); 103 if (config.highlightingSemanticTokens) {
104 res.registerFeature(new SemanticTokensFeature(res));
105 }
104 } 106 }
105 107
106 return res; 108 return res;
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts
index be5296fcf..7668c20b7 100644
--- a/editors/code/src/config.ts
+++ b/editors/code/src/config.ts
@@ -1,29 +1,10 @@
1import * as vscode from 'vscode'; 1import * as vscode from 'vscode';
2import { log } from "./util"; 2import { log } from "./util";
3 3
4export interface InlayHintOptions {
5 typeHints: boolean;
6 parameterHints: boolean;
7 maxLength: number | null;
8}
9
10export interface CargoWatchOptions {
11 enable: boolean;
12 arguments: string[];
13 command: string;
14 allTargets: boolean;
15}
16
17export interface CargoFeatures {
18 noDefaultFeatures: boolean;
19 allFeatures: boolean;
20 features: string[];
21 loadOutDirsFromCheck: boolean;
22}
23
24export type UpdatesChannel = "stable" | "nightly"; 4export type UpdatesChannel = "stable" | "nightly";
25 5
26export const NIGHTLY_TAG = "nightly"; 6export const NIGHTLY_TAG = "nightly";
7
27export class Config { 8export class Config {
28 readonly extensionId = "matklad.rust-analyzer"; 9 readonly extensionId = "matklad.rust-analyzer";
29 10
@@ -38,37 +19,30 @@ export class Config {
38 ] 19 ]
39 .map(opt => `${this.rootSection}.${opt}`); 20 .map(opt => `${this.rootSection}.${opt}`);
40 21
41 readonly packageJsonVersion: string = vscode 22 readonly package: {
42 .extensions 23 version: string;
43 .getExtension(this.extensionId)! 24 releaseTag: string | undefined;
44 .packageJSON 25 enableProposedApi: boolean | undefined;
45 .version; 26 } = vscode.extensions.getExtension(this.extensionId)!.packageJSON;
46
47 readonly releaseTag: string | undefined = vscode
48 .extensions
49 .getExtension(this.extensionId)!
50 .packageJSON
51 .releaseTag ?? undefined;
52 27
53 private cfg!: vscode.WorkspaceConfiguration; 28 readonly globalStoragePath: string;
54 29
55 constructor(private readonly ctx: vscode.ExtensionContext) { 30 constructor(ctx: vscode.ExtensionContext) {
56 vscode.workspace.onDidChangeConfiguration(this.onConfigChange, this, ctx.subscriptions); 31 this.globalStoragePath = ctx.globalStoragePath;
57 this.refreshConfig(); 32 vscode.workspace.onDidChangeConfiguration(this.onDidChangeConfiguration, this, ctx.subscriptions);
33 this.refreshLogging();
58 } 34 }
59 35
60 private refreshConfig() { 36 private refreshLogging() {
61 this.cfg = vscode.workspace.getConfiguration(this.rootSection); 37 log.setEnabled(this.traceExtension);
62 const enableLogging = this.cfg.get("trace.extension") as boolean;
63 log.setEnabled(enableLogging);
64 log.debug( 38 log.debug(
65 "Extension version:", this.packageJsonVersion, 39 "Extension version:", this.package.version,
66 "using configuration:", this.cfg 40 "using configuration:", this.cfg
67 ); 41 );
68 } 42 }
69 43
70 private async onConfigChange(event: vscode.ConfigurationChangeEvent) { 44 private async onDidChangeConfiguration(event: vscode.ConfigurationChangeEvent) {
71 this.refreshConfig(); 45 this.refreshLogging();
72 46
73 const requiresReloadOpt = this.requiresReloadOpts.find( 47 const requiresReloadOpt = this.requiresReloadOpts.find(
74 opt => event.affectsConfiguration(opt) 48 opt => event.affectsConfiguration(opt)
@@ -86,49 +60,53 @@ export class Config {
86 } 60 }
87 } 61 }
88 62
89 get globalStoragePath(): string { return this.ctx.globalStoragePath; }
90
91 // We don't do runtime config validation here for simplicity. More on stackoverflow: 63 // We don't do runtime config validation here for simplicity. More on stackoverflow:
92 // https://stackoverflow.com/questions/60135780/what-is-the-best-way-to-type-check-the-configuration-for-vscode-extension 64 // https://stackoverflow.com/questions/60135780/what-is-the-best-way-to-type-check-the-configuration-for-vscode-extension
93 65
94 get serverPath() { return this.cfg.get("serverPath") as null | string; } 66 private get cfg(): vscode.WorkspaceConfiguration {
95 get channel() { return this.cfg.get<"stable" | "nightly">("updates.channel")!; } 67 return vscode.workspace.getConfiguration(this.rootSection);
96 get askBeforeDownload() { return this.cfg.get("updates.askBeforeDownload") as boolean; } 68 }
97 get highlightingSemanticTokens() { return this.cfg.get("highlighting.semanticTokens") as boolean; } 69
98 get highlightingOn() { return this.cfg.get("highlightingOn") as boolean; } 70 get serverPath() { return this.cfg.get<null | string>("serverPath")!; }
99 get rainbowHighlightingOn() { return this.cfg.get("rainbowHighlightingOn") as boolean; } 71 get channel() { return this.cfg.get<UpdatesChannel>("updates.channel")!; }
100 get lruCapacity() { return this.cfg.get("lruCapacity") as null | number; } 72 get askBeforeDownload() { return this.cfg.get<boolean>("updates.askBeforeDownload")!; }
101 get inlayHints(): InlayHintOptions { 73 get highlightingSemanticTokens() { return this.cfg.get<boolean>("highlighting.semanticTokens")!; }
74 get highlightingOn() { return this.cfg.get<boolean>("highlightingOn")!; }
75 get rainbowHighlightingOn() { return this.cfg.get<boolean>("rainbowHighlightingOn")!; }
76 get lruCapacity() { return this.cfg.get<null | number>("lruCapacity")!; }
77 get excludeGlobs() { return this.cfg.get<string[]>("excludeGlobs")!; }
78 get useClientWatching() { return this.cfg.get<boolean>("useClientWatching")!; }
79 get featureFlags() { return this.cfg.get<Record<string, boolean>>("featureFlags")!; }
80 get rustfmtArgs() { return this.cfg.get<string[]>("rustfmtArgs")!; }
81 get loadOutDirsFromCheck() { return this.cfg.get<boolean>("loadOutDirsFromCheck")!; }
82 get traceExtension() { return this.cfg.get<boolean>("trace.extension")!; }
83
84 // for internal use
85 get withSysroot() { return this.cfg.get<boolean>("withSysroot", true)!; }
86
87 get inlayHints() {
102 return { 88 return {
103 typeHints: this.cfg.get("inlayHints.typeHints") as boolean, 89 typeHints: this.cfg.get<boolean>("inlayHints.typeHints")!,
104 parameterHints: this.cfg.get("inlayHints.parameterHints") as boolean, 90 parameterHints: this.cfg.get<boolean>("inlayHints.parameterHints")!,
105 maxLength: this.cfg.get("inlayHints.maxLength") as null | number, 91 maxLength: this.cfg.get<null | number>("inlayHints.maxLength")!,
106 }; 92 };
107 } 93 }
108 get excludeGlobs() { return this.cfg.get("excludeGlobs") as string[]; }
109 get useClientWatching() { return this.cfg.get("useClientWatching") as boolean; }
110 get featureFlags() { return this.cfg.get("featureFlags") as Record<string, boolean>; }
111 get rustfmtArgs() { return this.cfg.get("rustfmtArgs") as string[]; }
112 get loadOutDirsFromCheck() { return this.cfg.get("loadOutDirsFromCheck") as boolean; }
113 94
114 get cargoWatchOptions(): CargoWatchOptions { 95 get cargoWatchOptions() {
115 return { 96 return {
116 enable: this.cfg.get("cargo-watch.enable") as boolean, 97 enable: this.cfg.get<boolean>("cargo-watch.enable")!,
117 arguments: this.cfg.get("cargo-watch.arguments") as string[], 98 arguments: this.cfg.get<string[]>("cargo-watch.arguments")!,
118 allTargets: this.cfg.get("cargo-watch.allTargets") as boolean, 99 allTargets: this.cfg.get<boolean>("cargo-watch.allTargets")!,
119 command: this.cfg.get("cargo-watch.command") as string, 100 command: this.cfg.get<string>("cargo-watch.command")!,
120 }; 101 };
121 } 102 }
122 103
123 get cargoFeatures(): CargoFeatures { 104 get cargoFeatures() {
124 return { 105 return {
125 noDefaultFeatures: this.cfg.get("cargoFeatures.noDefaultFeatures") as boolean, 106 noDefaultFeatures: this.cfg.get<boolean>("cargoFeatures.noDefaultFeatures")!,
126 allFeatures: this.cfg.get("cargoFeatures.allFeatures") as boolean, 107 allFeatures: this.cfg.get<boolean>("cargoFeatures.allFeatures")!,
127 features: this.cfg.get("cargoFeatures.features") as string[], 108 features: this.cfg.get<string[]>("cargoFeatures.features")!,
128 loadOutDirsFromCheck: this.cfg.get("cargoFeatures.loadOutDirsFromCheck") as boolean, 109 loadOutDirsFromCheck: this.cfg.get<boolean>("cargoFeatures.loadOutDirsFromCheck")!,
129 }; 110 };
130 } 111 }
131
132 // for internal use
133 get withSysroot() { return this.cfg.get("withSysroot", true) as boolean; }
134} 112}
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts
index 5d2da9a76..7b7c19dfc 100644
--- a/editors/code/src/main.ts
+++ b/editors/code/src/main.ts
@@ -110,9 +110,9 @@ async function bootstrap(config: Config, state: PersistentState): Promise<string
110} 110}
111 111
112async function bootstrapExtension(config: Config, state: PersistentState): Promise<void> { 112async function bootstrapExtension(config: Config, state: PersistentState): Promise<void> {
113 if (config.releaseTag === undefined) return; 113 if (config.package.releaseTag === undefined) return;
114 if (config.channel === "stable") { 114 if (config.channel === "stable") {
115 if (config.releaseTag === NIGHTLY_TAG) { 115 if (config.package.releaseTag === NIGHTLY_TAG) {
116 vscode.window.showWarningMessage(`You are running a nightly version of rust-analyzer extension. 116 vscode.window.showWarningMessage(`You are running a nightly version of rust-analyzer extension.
117To switch to stable, uninstall the extension and re-install it from the marketplace`); 117To switch to stable, uninstall the extension and re-install it from the marketplace`);
118 } 118 }
@@ -185,7 +185,7 @@ async function getServer(config: Config, state: PersistentState): Promise<string
185 } 185 }
186 return explicitPath; 186 return explicitPath;
187 }; 187 };
188 if (config.releaseTag === undefined) return "rust-analyzer"; 188 if (config.package.releaseTag === undefined) return "rust-analyzer";
189 189
190 let binaryName: string | undefined = undefined; 190 let binaryName: string | undefined = undefined;
191 if (process.arch === "x64" || process.arch === "x32") { 191 if (process.arch === "x64" || process.arch === "x32") {
@@ -211,21 +211,21 @@ async function getServer(config: Config, state: PersistentState): Promise<string
211 await state.updateServerVersion(undefined); 211 await state.updateServerVersion(undefined);
212 } 212 }
213 213
214 if (state.serverVersion === config.packageJsonVersion) return dest; 214 if (state.serverVersion === config.package.version) return dest;
215 215
216 if (config.askBeforeDownload) { 216 if (config.askBeforeDownload) {
217 const userResponse = await vscode.window.showInformationMessage( 217 const userResponse = await vscode.window.showInformationMessage(
218 `Language server version ${config.packageJsonVersion} for rust-analyzer is not installed.`, 218 `Language server version ${config.package.version} for rust-analyzer is not installed.`,
219 "Download now" 219 "Download now"
220 ); 220 );
221 if (userResponse !== "Download now") return dest; 221 if (userResponse !== "Download now") return dest;
222 } 222 }
223 223
224 const release = await fetchRelease(config.releaseTag); 224 const release = await fetchRelease(config.package.releaseTag);
225 const artifact = release.assets.find(artifact => artifact.name === binaryName); 225 const artifact = release.assets.find(artifact => artifact.name === binaryName);
226 assert(!!artifact, `Bad release: ${JSON.stringify(release)}`); 226 assert(!!artifact, `Bad release: ${JSON.stringify(release)}`);
227 227
228 await download(artifact.browser_download_url, dest, "Downloading rust-analyzer server", { mode: 0o755 }); 228 await download(artifact.browser_download_url, dest, "Downloading rust-analyzer server", { mode: 0o755 });
229 await state.updateServerVersion(config.packageJsonVersion); 229 await state.updateServerVersion(config.package.version);
230 return dest; 230 return dest;
231} 231}