aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock1
-rw-r--r--crates/ra_hir/Cargo.toml2
-rw-r--r--crates/ra_hir/src/semantics.rs35
-rw-r--r--crates/ra_hir_expand/src/db.rs71
-rw-r--r--crates/ra_ide/src/completion/complete_dot.rs102
-rw-r--r--crates/ra_ide/src/completion/complete_keyword.rs1
-rw-r--r--crates/ra_ide/src/completion/complete_path.rs37
-rw-r--r--crates/ra_ide/src/completion/complete_pattern.rs18
-rw-r--r--crates/ra_ide/src/completion/complete_postfix.rs65
-rw-r--r--crates/ra_ide/src/completion/complete_record_literal.rs25
-rw-r--r--crates/ra_ide/src/completion/complete_record_pattern.rs28
-rw-r--r--crates/ra_ide/src/completion/complete_scope.rs70
-rw-r--r--crates/ra_ide/src/completion/completion_context.rs111
-rw-r--r--crates/ra_mbe/src/mbe_expander/matcher.rs4
-rw-r--r--docs/user/readme.adoc19
-rw-r--r--editors/code/package.json5
-rw-r--r--editors/code/src/config.ts11
-rw-r--r--editors/code/src/installation/interfaces.ts15
-rw-r--r--editors/code/src/installation/server.ts28
19 files changed, 582 insertions, 66 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 80e778bcf..2e052d267 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -960,6 +960,7 @@ name = "ra_hir"
960version = "0.1.0" 960version = "0.1.0"
961dependencies = [ 961dependencies = [
962 "either", 962 "either",
963 "itertools",
963 "log", 964 "log",
964 "ra_db", 965 "ra_db",
965 "ra_hir_def", 966 "ra_hir_def",
diff --git a/crates/ra_hir/Cargo.toml b/crates/ra_hir/Cargo.toml
index 0555a0de7..266c4cff3 100644
--- a/crates/ra_hir/Cargo.toml
+++ b/crates/ra_hir/Cargo.toml
@@ -12,6 +12,8 @@ log = "0.4.8"
12rustc-hash = "1.1.0" 12rustc-hash = "1.1.0"
13either = "1.5.3" 13either = "1.5.3"
14 14
15itertools = "0.8.2"
16
15ra_syntax = { path = "../ra_syntax" } 17ra_syntax = { path = "../ra_syntax" }
16ra_db = { path = "../ra_db" } 18ra_db = { path = "../ra_db" }
17ra_prof = { path = "../ra_prof" } 19ra_prof = { path = "../ra_prof" }
diff --git a/crates/ra_hir/src/semantics.rs b/crates/ra_hir/src/semantics.rs
index 965d185a4..3782a9984 100644
--- a/crates/ra_hir/src/semantics.rs
+++ b/crates/ra_hir/src/semantics.rs
@@ -6,7 +6,7 @@ use std::{cell::RefCell, fmt, iter::successors};
6 6
7use hir_def::{ 7use hir_def::{
8 resolver::{self, HasResolver, Resolver}, 8 resolver::{self, HasResolver, Resolver},
9 TraitId, 9 AsMacroCall, TraitId,
10}; 10};
11use hir_expand::ExpansionInfo; 11use hir_expand::ExpansionInfo;
12use ra_db::{FileId, FileRange}; 12use ra_db::{FileId, FileRange};
@@ -70,6 +70,20 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
70 Some(node) 70 Some(node)
71 } 71 }
72 72
73 pub fn expand_hypothetical(
74 &self,
75 actual_macro_call: &ast::MacroCall,
76 hypothetical_args: &ast::TokenTree,
77 token_to_map: SyntaxToken,
78 ) -> Option<(SyntaxNode, SyntaxToken)> {
79 let macro_call =
80 self.find_file(actual_macro_call.syntax().clone()).with_value(actual_macro_call);
81 let sa = self.analyze2(macro_call.map(|it| it.syntax()), None);
82 let macro_call_id = macro_call
83 .as_call_id(self.db, |path| sa.resolver.resolve_path_as_macro(self.db, &path))?;
84 hir_expand::db::expand_hypothetical(self.db, macro_call_id, hypothetical_args, token_to_map)
85 }
86
73 pub fn descend_into_macros(&self, token: SyntaxToken) -> SyntaxToken { 87 pub fn descend_into_macros(&self, token: SyntaxToken) -> SyntaxToken {
74 let parent = token.parent(); 88 let parent = token.parent();
75 let parent = self.find_file(parent); 89 let parent = self.find_file(parent);
@@ -104,6 +118,25 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
104 node.ancestors_with_macros(self.db).map(|it| it.value) 118 node.ancestors_with_macros(self.db).map(|it| it.value)
105 } 119 }
106 120
121 pub fn ancestors_at_offset_with_macros(
122 &self,
123 node: &SyntaxNode,
124 offset: TextUnit,
125 ) -> impl Iterator<Item = SyntaxNode> + '_ {
126 use itertools::Itertools;
127 node.token_at_offset(offset)
128 .map(|token| self.ancestors_with_macros(token.parent()))
129 .kmerge_by(|node1, node2| node1.text_range().len() < node2.text_range().len())
130 }
131
132 pub fn find_node_at_offset_with_macros<N: AstNode>(
133 &self,
134 node: &SyntaxNode,
135 offset: TextUnit,
136 ) -> Option<N> {
137 self.ancestors_at_offset_with_macros(node, offset).find_map(N::cast)
138 }
139
107 pub fn type_of_expr(&self, expr: &ast::Expr) -> Option<Type> { 140 pub fn type_of_expr(&self, expr: &ast::Expr) -> Option<Type> {
108 self.analyze(expr.syntax()).type_of(self.db, &expr) 141 self.analyze(expr.syntax()).type_of(self.db, &expr)
109 } 142 }
diff --git a/crates/ra_hir_expand/src/db.rs b/crates/ra_hir_expand/src/db.rs
index f3a84cacc..29dde3d80 100644
--- a/crates/ra_hir_expand/src/db.rs
+++ b/crates/ra_hir_expand/src/db.rs
@@ -72,6 +72,30 @@ pub trait AstDatabase: SourceDatabase {
72 fn intern_eager_expansion(&self, eager: EagerCallLoc) -> EagerMacroId; 72 fn intern_eager_expansion(&self, eager: EagerCallLoc) -> EagerMacroId;
73} 73}
74 74
75/// This expands the given macro call, but with different arguments. This is
76/// used for completion, where we want to see what 'would happen' if we insert a
77/// token. The `token_to_map` mapped down into the expansion, with the mapped
78/// token returned.
79pub fn expand_hypothetical(
80 db: &impl AstDatabase,
81 actual_macro_call: MacroCallId,
82 hypothetical_args: &ra_syntax::ast::TokenTree,
83 token_to_map: ra_syntax::SyntaxToken,
84) -> Option<(SyntaxNode, ra_syntax::SyntaxToken)> {
85 let macro_file = MacroFile { macro_call_id: actual_macro_call };
86 let (tt, tmap_1) = mbe::syntax_node_to_token_tree(hypothetical_args.syntax()).unwrap();
87 let range =
88 token_to_map.text_range().checked_sub(hypothetical_args.syntax().text_range().start())?;
89 let token_id = tmap_1.token_by_range(range)?;
90 let macro_def = expander(db, actual_macro_call)?;
91 let (node, tmap_2) =
92 parse_macro_with_arg(db, macro_file, Some(std::sync::Arc::new((tt, tmap_1))))?;
93 let token_id = macro_def.0.map_id_down(token_id);
94 let range = tmap_2.range_by_token(token_id)?.by_kind(token_to_map.kind())?;
95 let token = ra_syntax::algo::find_covering_element(&node.syntax_node(), range).into_token()?;
96 Some((node.syntax_node(), token))
97}
98
75pub(crate) fn ast_id_map(db: &dyn AstDatabase, file_id: HirFileId) -> Arc<AstIdMap> { 99pub(crate) fn ast_id_map(db: &dyn AstDatabase, file_id: HirFileId) -> Arc<AstIdMap> {
76 let map = 100 let map =
77 db.parse_or_expand(file_id).map_or_else(AstIdMap::default, |it| AstIdMap::from_source(&it)); 101 db.parse_or_expand(file_id).map_or_else(AstIdMap::default, |it| AstIdMap::from_source(&it));
@@ -130,15 +154,42 @@ pub(crate) fn macro_expand(
130 db: &dyn AstDatabase, 154 db: &dyn AstDatabase,
131 id: MacroCallId, 155 id: MacroCallId,
132) -> Result<Arc<tt::Subtree>, String> { 156) -> Result<Arc<tt::Subtree>, String> {
157 macro_expand_with_arg(db, id, None)
158}
159
160fn expander(db: &dyn AstDatabase, id: MacroCallId) -> Option<Arc<(TokenExpander, mbe::TokenMap)>> {
161 let lazy_id = match id {
162 MacroCallId::LazyMacro(id) => id,
163 MacroCallId::EagerMacro(_id) => {
164 return None;
165 }
166 };
167
168 let loc = db.lookup_intern_macro(lazy_id);
169 let macro_rules = db.macro_def(loc.def)?;
170 Some(macro_rules)
171}
172
173fn macro_expand_with_arg(
174 db: &dyn AstDatabase,
175 id: MacroCallId,
176 arg: Option<Arc<(tt::Subtree, mbe::TokenMap)>>,
177) -> Result<Arc<tt::Subtree>, String> {
133 let lazy_id = match id { 178 let lazy_id = match id {
134 MacroCallId::LazyMacro(id) => id, 179 MacroCallId::LazyMacro(id) => id,
135 MacroCallId::EagerMacro(id) => { 180 MacroCallId::EagerMacro(id) => {
136 return Ok(db.lookup_intern_eager_expansion(id).subtree); 181 if arg.is_some() {
182 return Err(
183 "hypothetical macro expansion not implemented for eager macro".to_owned()
184 );
185 } else {
186 return Ok(db.lookup_intern_eager_expansion(id).subtree);
187 }
137 } 188 }
138 }; 189 };
139 190
140 let loc = db.lookup_intern_macro(lazy_id); 191 let loc = db.lookup_intern_macro(lazy_id);
141 let macro_arg = db.macro_arg(id).ok_or("Fail to args in to tt::TokenTree")?; 192 let macro_arg = arg.or_else(|| db.macro_arg(id)).ok_or("Fail to args in to tt::TokenTree")?;
142 193
143 let macro_rules = db.macro_def(loc.def).ok_or("Fail to find macro definition")?; 194 let macro_rules = db.macro_def(loc.def).ok_or("Fail to find macro definition")?;
144 let tt = macro_rules.0.expand(db, lazy_id, &macro_arg.0).map_err(|err| format!("{:?}", err))?; 195 let tt = macro_rules.0.expand(db, lazy_id, &macro_arg.0).map_err(|err| format!("{:?}", err))?;
@@ -163,11 +214,23 @@ pub(crate) fn parse_macro(
163 db: &dyn AstDatabase, 214 db: &dyn AstDatabase,
164 macro_file: MacroFile, 215 macro_file: MacroFile,
165) -> Option<(Parse<SyntaxNode>, Arc<mbe::TokenMap>)> { 216) -> Option<(Parse<SyntaxNode>, Arc<mbe::TokenMap>)> {
217 parse_macro_with_arg(db, macro_file, None)
218}
219
220pub fn parse_macro_with_arg(
221 db: &dyn AstDatabase,
222 macro_file: MacroFile,
223 arg: Option<Arc<(tt::Subtree, mbe::TokenMap)>>,
224) -> Option<(Parse<SyntaxNode>, Arc<mbe::TokenMap>)> {
166 let _p = profile("parse_macro_query"); 225 let _p = profile("parse_macro_query");
167 226
168 let macro_call_id = macro_file.macro_call_id; 227 let macro_call_id = macro_file.macro_call_id;
169 let tt = db 228 let expansion = if let Some(arg) = arg {
170 .macro_expand(macro_call_id) 229 macro_expand_with_arg(db, macro_call_id, Some(arg))
230 } else {
231 db.macro_expand(macro_call_id)
232 };
233 let tt = expansion
171 .map_err(|err| { 234 .map_err(|err| {
172 // Note: 235 // Note:
173 // The final goal we would like to make all parse_macro success, 236 // The final goal we would like to make all parse_macro success,
diff --git a/crates/ra_ide/src/completion/complete_dot.rs b/crates/ra_ide/src/completion/complete_dot.rs
index acada48ae..f275305e2 100644
--- a/crates/ra_ide/src/completion/complete_dot.rs
+++ b/crates/ra_ide/src/completion/complete_dot.rs
@@ -38,7 +38,7 @@ pub(super) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) {
38fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) { 38fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) {
39 for receiver in receiver.autoderef(ctx.db) { 39 for receiver in receiver.autoderef(ctx.db) {
40 for (field, ty) in receiver.fields(ctx.db) { 40 for (field, ty) in receiver.fields(ctx.db) {
41 if ctx.module.map_or(false, |m| !field.is_visible_from(ctx.db, m)) { 41 if ctx.scope().module().map_or(false, |m| !field.is_visible_from(ctx.db, m)) {
42 // Skip private field. FIXME: If the definition location of the 42 // Skip private field. FIXME: If the definition location of the
43 // field is editable, we should show the completion 43 // field is editable, we should show the completion
44 continue; 44 continue;
@@ -53,7 +53,7 @@ fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, receiver: &Ty
53} 53}
54 54
55fn complete_methods(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) { 55fn complete_methods(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) {
56 if let Some(krate) = ctx.module.map(|it| it.krate()) { 56 if let Some(krate) = ctx.krate {
57 let mut seen_methods = FxHashSet::default(); 57 let mut seen_methods = FxHashSet::default();
58 let traits_in_scope = ctx.scope().traits_in_scope(); 58 let traits_in_scope = ctx.scope().traits_in_scope();
59 receiver.iterate_method_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, func| { 59 receiver.iterate_method_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, func| {
@@ -620,4 +620,102 @@ mod tests {
620 "### 620 "###
621 ); 621 );
622 } 622 }
623
624 #[test]
625 fn works_in_simple_macro_1() {
626 assert_debug_snapshot!(
627 do_ref_completion(
628 r"
629 macro_rules! m { ($e:expr) => { $e } }
630 struct A { the_field: u32 }
631 fn foo(a: A) {
632 m!(a.x<|>)
633 }
634 ",
635 ),
636 @r###"
637 [
638 CompletionItem {
639 label: "the_field",
640 source_range: [156; 157),
641 delete: [156; 157),
642 insert: "the_field",
643 kind: Field,
644 detail: "u32",
645 },
646 ]
647 "###
648 );
649 }
650
651 #[test]
652 fn works_in_simple_macro_recursive() {
653 assert_debug_snapshot!(
654 do_ref_completion(
655 r"
656 macro_rules! m { ($e:expr) => { $e } }
657 struct A { the_field: u32 }
658 fn foo(a: A) {
659 m!(a.x<|>)
660 }
661 ",
662 ),
663 @r###"
664 [
665 CompletionItem {
666 label: "the_field",
667 source_range: [156; 157),
668 delete: [156; 157),
669 insert: "the_field",
670 kind: Field,
671 detail: "u32",
672 },
673 ]
674 "###
675 );
676 }
677
678 #[test]
679 fn works_in_simple_macro_2() {
680 // this doesn't work yet because the macro doesn't expand without the token -- maybe it can be fixed with better recovery
681 assert_debug_snapshot!(
682 do_ref_completion(
683 r"
684 macro_rules! m { ($e:expr) => { $e } }
685 struct A { the_field: u32 }
686 fn foo(a: A) {
687 m!(a.<|>)
688 }
689 ",
690 ),
691 @r###"[]"###
692 );
693 }
694
695 #[test]
696 fn works_in_simple_macro_recursive_1() {
697 assert_debug_snapshot!(
698 do_ref_completion(
699 r"
700 macro_rules! m { ($e:expr) => { $e } }
701 struct A { the_field: u32 }
702 fn foo(a: A) {
703 m!(m!(m!(a.x<|>)))
704 }
705 ",
706 ),
707 @r###"
708 [
709 CompletionItem {
710 label: "the_field",
711 source_range: [162; 163),
712 delete: [162; 163),
713 insert: "the_field",
714 kind: Field,
715 detail: "u32",
716 },
717 ]
718 "###
719 );
720 }
623} 721}
diff --git a/crates/ra_ide/src/completion/complete_keyword.rs b/crates/ra_ide/src/completion/complete_keyword.rs
index eb7cd9ac2..e1c0ffb1f 100644
--- a/crates/ra_ide/src/completion/complete_keyword.rs
+++ b/crates/ra_ide/src/completion/complete_keyword.rs
@@ -79,6 +79,7 @@ pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte
79} 79}
80 80
81fn is_in_loop_body(leaf: &SyntaxToken) -> bool { 81fn is_in_loop_body(leaf: &SyntaxToken) -> bool {
82 // FIXME move this to CompletionContext and make it handle macros
82 for node in leaf.parent().ancestors() { 83 for node in leaf.parent().ancestors() {
83 if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR { 84 if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR {
84 break; 85 break;
diff --git a/crates/ra_ide/src/completion/complete_path.rs b/crates/ra_ide/src/completion/complete_path.rs
index d2c758571..3c4a70561 100644
--- a/crates/ra_ide/src/completion/complete_path.rs
+++ b/crates/ra_ide/src/completion/complete_path.rs
@@ -1,4 +1,4 @@
1//! Completion of paths, including when writing a single name. 1//! Completion of paths, i.e. `some::prefix::<|>`.
2 2
3use hir::{Adt, HasVisibility, PathResolution, ScopeDef}; 3use hir::{Adt, HasVisibility, PathResolution, ScopeDef};
4use ra_syntax::AstNode; 4use ra_syntax::AstNode;
@@ -48,7 +48,7 @@ pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) {
48 }; 48 };
49 // Iterate assoc types separately 49 // Iterate assoc types separately
50 // FIXME: complete T::AssocType 50 // FIXME: complete T::AssocType
51 let krate = ctx.module.map(|m| m.krate()); 51 let krate = ctx.krate;
52 if let Some(krate) = krate { 52 if let Some(krate) = krate {
53 let traits_in_scope = ctx.scope().traits_in_scope(); 53 let traits_in_scope = ctx.scope().traits_in_scope();
54 ty.iterate_path_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, item| { 54 ty.iterate_path_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, item| {
@@ -934,4 +934,37 @@ mod tests {
934 "### 934 "###
935 ); 935 );
936 } 936 }
937
938 #[test]
939 fn completes_in_simple_macro_call() {
940 let completions = do_reference_completion(
941 r#"
942 macro_rules! m { ($e:expr) => { $e } }
943 fn main() { m!(self::f<|>); }
944 fn foo() {}
945 "#,
946 );
947 assert_debug_snapshot!(completions, @r###"
948 [
949 CompletionItem {
950 label: "foo()",
951 source_range: [93; 94),
952 delete: [93; 94),
953 insert: "foo()$0",
954 kind: Function,
955 lookup: "foo",
956 detail: "fn foo()",
957 },
958 CompletionItem {
959 label: "main()",
960 source_range: [93; 94),
961 delete: [93; 94),
962 insert: "main()$0",
963 kind: Function,
964 lookup: "main",
965 detail: "fn main()",
966 },
967 ]
968 "###);
969 }
937} 970}
diff --git a/crates/ra_ide/src/completion/complete_pattern.rs b/crates/ra_ide/src/completion/complete_pattern.rs
index c2c6ca002..fa8aeceda 100644
--- a/crates/ra_ide/src/completion/complete_pattern.rs
+++ b/crates/ra_ide/src/completion/complete_pattern.rs
@@ -86,4 +86,22 @@ mod tests {
86 ] 86 ]
87 "###); 87 "###);
88 } 88 }
89
90 #[test]
91 fn completes_in_simple_macro_call() {
92 // FIXME: doesn't work yet because of missing error recovery in macro expansion
93 let completions = complete(
94 r"
95 macro_rules! m { ($e:expr) => { $e } }
96 enum E { X }
97
98 fn foo() {
99 m!(match E::X {
100 <|>
101 })
102 }
103 ",
104 );
105 assert_debug_snapshot!(completions, @r###"[]"###);
106 }
89} 107}
diff --git a/crates/ra_ide/src/completion/complete_postfix.rs b/crates/ra_ide/src/completion/complete_postfix.rs
index 8a74f993a..65ecea125 100644
--- a/crates/ra_ide/src/completion/complete_postfix.rs
+++ b/crates/ra_ide/src/completion/complete_postfix.rs
@@ -67,8 +67,8 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
67 67
68fn postfix_snippet(ctx: &CompletionContext, label: &str, detail: &str, snippet: &str) -> Builder { 68fn postfix_snippet(ctx: &CompletionContext, label: &str, detail: &str, snippet: &str) -> Builder {
69 let edit = { 69 let edit = {
70 let receiver_range = 70 let receiver_syntax = ctx.dot_receiver.as_ref().expect("no receiver available").syntax();
71 ctx.dot_receiver.as_ref().expect("no receiver available").syntax().text_range(); 71 let receiver_range = ctx.sema.original_range(receiver_syntax).range;
72 let delete_range = TextRange::from_to(receiver_range.start(), ctx.source_range().end()); 72 let delete_range = TextRange::from_to(receiver_range.start(), ctx.source_range().end());
73 TextEdit::replace(delete_range, snippet.to_string()) 73 TextEdit::replace(delete_range, snippet.to_string())
74 }; 74 };
@@ -279,4 +279,65 @@ mod tests {
279 "### 279 "###
280 ); 280 );
281 } 281 }
282
283 #[test]
284 fn works_in_simple_macro() {
285 assert_debug_snapshot!(
286 do_postfix_completion(
287 r#"
288 macro_rules! m { ($e:expr) => { $e } }
289 fn main() {
290 let bar: u8 = 12;
291 m!(bar.b<|>)
292 }
293 "#,
294 ),
295 @r###"
296 [
297 CompletionItem {
298 label: "box",
299 source_range: [149; 150),
300 delete: [145; 150),
301 insert: "Box::new(bar)",
302 detail: "Box::new(expr)",
303 },
304 CompletionItem {
305 label: "dbg",
306 source_range: [149; 150),
307 delete: [145; 150),
308 insert: "dbg!(bar)",
309 detail: "dbg!(expr)",
310 },
311 CompletionItem {
312 label: "match",
313 source_range: [149; 150),
314 delete: [145; 150),
315 insert: "match bar {\n ${1:_} => {$0\\},\n}",
316 detail: "match expr {}",
317 },
318 CompletionItem {
319 label: "not",
320 source_range: [149; 150),
321 delete: [145; 150),
322 insert: "!bar",
323 detail: "!expr",
324 },
325 CompletionItem {
326 label: "ref",
327 source_range: [149; 150),
328 delete: [145; 150),
329 insert: "&bar",
330 detail: "&expr",
331 },
332 CompletionItem {
333 label: "refm",
334 source_range: [149; 150),
335 delete: [145; 150),
336 insert: "&mut bar",
337 detail: "&mut expr",
338 },
339 ]
340 "###
341 );
342 }
282} 343}
diff --git a/crates/ra_ide/src/completion/complete_record_literal.rs b/crates/ra_ide/src/completion/complete_record_literal.rs
index f98353d76..be6e4194f 100644
--- a/crates/ra_ide/src/completion/complete_record_literal.rs
+++ b/crates/ra_ide/src/completion/complete_record_literal.rs
@@ -153,4 +153,29 @@ mod tests {
153 ] 153 ]
154 "###); 154 "###);
155 } 155 }
156
157 #[test]
158 fn test_record_literal_field_in_simple_macro() {
159 let completions = complete(
160 r"
161 macro_rules! m { ($e:expr) => { $e } }
162 struct A { the_field: u32 }
163 fn foo() {
164 m!(A { the<|> })
165 }
166 ",
167 );
168 assert_debug_snapshot!(completions, @r###"
169 [
170 CompletionItem {
171 label: "the_field",
172 source_range: [137; 140),
173 delete: [137; 140),
174 insert: "the_field",
175 kind: Field,
176 detail: "u32",
177 },
178 ]
179 "###);
180 }
156} 181}
diff --git a/crates/ra_ide/src/completion/complete_record_pattern.rs b/crates/ra_ide/src/completion/complete_record_pattern.rs
index 9bdeae49f..687c57d3e 100644
--- a/crates/ra_ide/src/completion/complete_record_pattern.rs
+++ b/crates/ra_ide/src/completion/complete_record_pattern.rs
@@ -87,4 +87,32 @@ mod tests {
87 ] 87 ]
88 "###); 88 "###);
89 } 89 }
90
91 #[test]
92 fn test_record_pattern_field_in_simple_macro() {
93 let completions = complete(
94 r"
95 macro_rules! m { ($e:expr) => { $e } }
96 struct S { foo: u32 }
97
98 fn process(f: S) {
99 m!(match f {
100 S { f<|>: 92 } => (),
101 })
102 }
103 ",
104 );
105 assert_debug_snapshot!(completions, @r###"
106 [
107 CompletionItem {
108 label: "foo",
109 source_range: [171; 172),
110 delete: [171; 172),
111 insert: "foo",
112 kind: Field,
113 detail: "u32",
114 },
115 ]
116 "###);
117 }
90} 118}
diff --git a/crates/ra_ide/src/completion/complete_scope.rs b/crates/ra_ide/src/completion/complete_scope.rs
index 2b9a0e556..eb3c8cf1b 100644
--- a/crates/ra_ide/src/completion/complete_scope.rs
+++ b/crates/ra_ide/src/completion/complete_scope.rs
@@ -1,4 +1,4 @@
1//! FIXME: write short doc here 1//! Completion of names from the current scope, e.g. locals and imported items.
2 2
3use crate::completion::{CompletionContext, Completions}; 3use crate::completion::{CompletionContext, Completions};
4 4
@@ -797,4 +797,72 @@ mod tests {
797 "### 797 "###
798 ) 798 )
799 } 799 }
800
801 #[test]
802 fn completes_in_simple_macro_1() {
803 assert_debug_snapshot!(
804 do_reference_completion(
805 r"
806 macro_rules! m { ($e:expr) => { $e } }
807 fn quux(x: i32) {
808 let y = 92;
809 m!(<|>);
810 }
811 "
812 ),
813 @"[]"
814 );
815 }
816
817 #[test]
818 fn completes_in_simple_macro_2() {
819 assert_debug_snapshot!(
820 do_reference_completion(
821 r"
822 macro_rules! m { ($e:expr) => { $e } }
823 fn quux(x: i32) {
824 let y = 92;
825 m!(x<|>);
826 }
827 "
828 ),
829 @r###"
830 [
831 CompletionItem {
832 label: "m!",
833 source_range: [145; 146),
834 delete: [145; 146),
835 insert: "m!($0)",
836 kind: Macro,
837 detail: "macro_rules! m",
838 },
839 CompletionItem {
840 label: "quux(…)",
841 source_range: [145; 146),
842 delete: [145; 146),
843 insert: "quux(${1:x})$0",
844 kind: Function,
845 lookup: "quux",
846 detail: "fn quux(x: i32)",
847 },
848 CompletionItem {
849 label: "x",
850 source_range: [145; 146),
851 delete: [145; 146),
852 insert: "x",
853 kind: Binding,
854 detail: "i32",
855 },
856 CompletionItem {
857 label: "y",
858 source_range: [145; 146),
859 delete: [145; 146),
860 insert: "y",
861 kind: Binding,
862 detail: "i32",
863 },
864 ]
865 "###
866 );
867 }
800} 868}
diff --git a/crates/ra_ide/src/completion/completion_context.rs b/crates/ra_ide/src/completion/completion_context.rs
index 9aa5a705d..40535c09e 100644
--- a/crates/ra_ide/src/completion/completion_context.rs
+++ b/crates/ra_ide/src/completion/completion_context.rs
@@ -5,7 +5,7 @@ use ra_db::SourceDatabase;
5use ra_ide_db::RootDatabase; 5use ra_ide_db::RootDatabase;
6use ra_syntax::{ 6use ra_syntax::{
7 algo::{find_covering_element, find_node_at_offset}, 7 algo::{find_covering_element, find_node_at_offset},
8 ast, AstNode, SourceFile, 8 ast, AstNode,
9 SyntaxKind::*, 9 SyntaxKind::*,
10 SyntaxNode, SyntaxToken, TextRange, TextUnit, 10 SyntaxNode, SyntaxToken, TextRange, TextUnit,
11}; 11};
@@ -20,8 +20,11 @@ pub(crate) struct CompletionContext<'a> {
20 pub(super) sema: Semantics<'a, RootDatabase>, 20 pub(super) sema: Semantics<'a, RootDatabase>,
21 pub(super) db: &'a RootDatabase, 21 pub(super) db: &'a RootDatabase,
22 pub(super) offset: TextUnit, 22 pub(super) offset: TextUnit,
23 /// The token before the cursor, in the original file.
24 pub(super) original_token: SyntaxToken,
25 /// The token before the cursor, in the macro-expanded file.
23 pub(super) token: SyntaxToken, 26 pub(super) token: SyntaxToken,
24 pub(super) module: Option<hir::Module>, 27 pub(super) krate: Option<hir::Crate>,
25 pub(super) name_ref_syntax: Option<ast::NameRef>, 28 pub(super) name_ref_syntax: Option<ast::NameRef>,
26 pub(super) function_syntax: Option<ast::FnDef>, 29 pub(super) function_syntax: Option<ast::FnDef>,
27 pub(super) use_item_syntax: Option<ast::UseItem>, 30 pub(super) use_item_syntax: Option<ast::UseItem>,
@@ -67,15 +70,20 @@ impl<'a> CompletionContext<'a> {
67 let edit = AtomTextEdit::insert(position.offset, "intellijRulezz".to_string()); 70 let edit = AtomTextEdit::insert(position.offset, "intellijRulezz".to_string());
68 parse.reparse(&edit).tree() 71 parse.reparse(&edit).tree()
69 }; 72 };
73 let fake_ident_token =
74 file_with_fake_ident.syntax().token_at_offset(position.offset).right_biased().unwrap();
70 75
71 let module = sema.to_module_def(position.file_id); 76 let krate = sema.to_module_def(position.file_id).map(|m| m.krate());
72 let token = original_file.syntax().token_at_offset(position.offset).left_biased()?; 77 let original_token =
78 original_file.syntax().token_at_offset(position.offset).left_biased()?;
79 let token = sema.descend_into_macros(original_token.clone());
73 let mut ctx = CompletionContext { 80 let mut ctx = CompletionContext {
74 sema, 81 sema,
75 db, 82 db,
83 original_token,
76 token, 84 token,
77 offset: position.offset, 85 offset: position.offset,
78 module, 86 krate,
79 name_ref_syntax: None, 87 name_ref_syntax: None,
80 function_syntax: None, 88 function_syntax: None,
81 use_item_syntax: None, 89 use_item_syntax: None,
@@ -95,15 +103,57 @@ impl<'a> CompletionContext<'a> {
95 has_type_args: false, 103 has_type_args: false,
96 dot_receiver_is_ambiguous_float_literal: false, 104 dot_receiver_is_ambiguous_float_literal: false,
97 }; 105 };
98 ctx.fill(&original_file, file_with_fake_ident, position.offset); 106
107 let mut original_file = original_file.syntax().clone();
108 let mut hypothetical_file = file_with_fake_ident.syntax().clone();
109 let mut offset = position.offset;
110 let mut fake_ident_token = fake_ident_token;
111
112 // Are we inside a macro call?
113 while let (Some(actual_macro_call), Some(macro_call_with_fake_ident)) = (
114 find_node_at_offset::<ast::MacroCall>(&original_file, offset),
115 find_node_at_offset::<ast::MacroCall>(&hypothetical_file, offset),
116 ) {
117 if actual_macro_call.path().as_ref().map(|s| s.syntax().text())
118 != macro_call_with_fake_ident.path().as_ref().map(|s| s.syntax().text())
119 {
120 break;
121 }
122 let hypothetical_args = match macro_call_with_fake_ident.token_tree() {
123 Some(tt) => tt,
124 None => break,
125 };
126 if let (Some(actual_expansion), Some(hypothetical_expansion)) = (
127 ctx.sema.expand(&actual_macro_call),
128 ctx.sema.expand_hypothetical(
129 &actual_macro_call,
130 &hypothetical_args,
131 fake_ident_token,
132 ),
133 ) {
134 let new_offset = hypothetical_expansion.1.text_range().start();
135 if new_offset >= actual_expansion.text_range().end() {
136 break;
137 }
138 original_file = actual_expansion;
139 hypothetical_file = hypothetical_expansion.0;
140 fake_ident_token = hypothetical_expansion.1;
141 offset = new_offset;
142 } else {
143 break;
144 }
145 }
146
147 ctx.fill(&original_file, hypothetical_file, offset);
99 Some(ctx) 148 Some(ctx)
100 } 149 }
101 150
102 // The range of the identifier that is being completed. 151 // The range of the identifier that is being completed.
103 pub(crate) fn source_range(&self) -> TextRange { 152 pub(crate) fn source_range(&self) -> TextRange {
153 // check kind of macro-expanded token, but use range of original token
104 match self.token.kind() { 154 match self.token.kind() {
105 // workaroud when completion is triggered by trigger characters. 155 // workaroud when completion is triggered by trigger characters.
106 IDENT => self.token.text_range(), 156 IDENT => self.original_token.text_range(),
107 _ => TextRange::offset_len(self.offset, 0.into()), 157 _ => TextRange::offset_len(self.offset, 0.into()),
108 } 158 }
109 } 159 }
@@ -114,27 +164,24 @@ impl<'a> CompletionContext<'a> {
114 164
115 fn fill( 165 fn fill(
116 &mut self, 166 &mut self,
117 original_file: &ast::SourceFile, 167 original_file: &SyntaxNode,
118 file_with_fake_ident: ast::SourceFile, 168 file_with_fake_ident: SyntaxNode,
119 offset: TextUnit, 169 offset: TextUnit,
120 ) { 170 ) {
121 // First, let's try to complete a reference to some declaration. 171 // First, let's try to complete a reference to some declaration.
122 if let Some(name_ref) = 172 if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(&file_with_fake_ident, offset) {
123 find_node_at_offset::<ast::NameRef>(file_with_fake_ident.syntax(), offset)
124 {
125 // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`. 173 // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`.
126 // See RFC#1685. 174 // See RFC#1685.
127 if is_node::<ast::Param>(name_ref.syntax()) { 175 if is_node::<ast::Param>(name_ref.syntax()) {
128 self.is_param = true; 176 self.is_param = true;
129 return; 177 return;
130 } 178 }
131 self.classify_name_ref(original_file, name_ref); 179 self.classify_name_ref(original_file, name_ref, offset);
132 } 180 }
133 181
134 // Otherwise, see if this is a declaration. We can use heuristics to 182 // Otherwise, see if this is a declaration. We can use heuristics to
135 // suggest declaration names, see `CompletionKind::Magic`. 183 // suggest declaration names, see `CompletionKind::Magic`.
136 if let Some(name) = find_node_at_offset::<ast::Name>(file_with_fake_ident.syntax(), offset) 184 if let Some(name) = find_node_at_offset::<ast::Name>(&file_with_fake_ident, offset) {
137 {
138 if let Some(bind_pat) = name.syntax().ancestors().find_map(ast::BindPat::cast) { 185 if let Some(bind_pat) = name.syntax().ancestors().find_map(ast::BindPat::cast) {
139 let parent = bind_pat.syntax().parent(); 186 let parent = bind_pat.syntax().parent();
140 if parent.clone().and_then(ast::MatchArm::cast).is_some() 187 if parent.clone().and_then(ast::MatchArm::cast).is_some()
@@ -148,23 +195,29 @@ impl<'a> CompletionContext<'a> {
148 return; 195 return;
149 } 196 }
150 if name.syntax().ancestors().find_map(ast::RecordFieldPatList::cast).is_some() { 197 if name.syntax().ancestors().find_map(ast::RecordFieldPatList::cast).is_some() {
151 self.record_lit_pat = find_node_at_offset(original_file.syntax(), self.offset); 198 self.record_lit_pat =
199 self.sema.find_node_at_offset_with_macros(&original_file, offset);
152 } 200 }
153 } 201 }
154 } 202 }
155 203
156 fn classify_name_ref(&mut self, original_file: &SourceFile, name_ref: ast::NameRef) { 204 fn classify_name_ref(
205 &mut self,
206 original_file: &SyntaxNode,
207 name_ref: ast::NameRef,
208 offset: TextUnit,
209 ) {
157 self.name_ref_syntax = 210 self.name_ref_syntax =
158 find_node_at_offset(original_file.syntax(), name_ref.syntax().text_range().start()); 211 find_node_at_offset(&original_file, name_ref.syntax().text_range().start());
159 let name_range = name_ref.syntax().text_range(); 212 let name_range = name_ref.syntax().text_range();
160 if name_ref.syntax().parent().and_then(ast::RecordField::cast).is_some() { 213 if name_ref.syntax().parent().and_then(ast::RecordField::cast).is_some() {
161 self.record_lit_syntax = find_node_at_offset(original_file.syntax(), self.offset); 214 self.record_lit_syntax =
215 self.sema.find_node_at_offset_with_macros(&original_file, offset);
162 } 216 }
163 217
164 self.impl_def = self 218 self.impl_def = self
165 .token 219 .sema
166 .parent() 220 .ancestors_with_macros(self.token.parent())
167 .ancestors()
168 .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) 221 .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
169 .find_map(ast::ImplDef::cast); 222 .find_map(ast::ImplDef::cast);
170 223
@@ -183,12 +236,12 @@ impl<'a> CompletionContext<'a> {
183 _ => (), 236 _ => (),
184 } 237 }
185 238
186 self.use_item_syntax = self.token.parent().ancestors().find_map(ast::UseItem::cast); 239 self.use_item_syntax =
240 self.sema.ancestors_with_macros(self.token.parent()).find_map(ast::UseItem::cast);
187 241
188 self.function_syntax = self 242 self.function_syntax = self
189 .token 243 .sema
190 .parent() 244 .ancestors_with_macros(self.token.parent())
191 .ancestors()
192 .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) 245 .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
193 .find_map(ast::FnDef::cast); 246 .find_map(ast::FnDef::cast);
194 247
@@ -242,7 +295,7 @@ impl<'a> CompletionContext<'a> {
242 295
243 if let Some(off) = name_ref.syntax().text_range().start().checked_sub(2.into()) { 296 if let Some(off) = name_ref.syntax().text_range().start().checked_sub(2.into()) {
244 if let Some(if_expr) = 297 if let Some(if_expr) =
245 find_node_at_offset::<ast::IfExpr>(original_file.syntax(), off) 298 self.sema.find_node_at_offset_with_macros::<ast::IfExpr>(original_file, off)
246 { 299 {
247 if if_expr.syntax().text_range().end() 300 if if_expr.syntax().text_range().end()
248 < name_ref.syntax().text_range().start() 301 < name_ref.syntax().text_range().start()
@@ -259,7 +312,7 @@ impl<'a> CompletionContext<'a> {
259 self.dot_receiver = field_expr 312 self.dot_receiver = field_expr
260 .expr() 313 .expr()
261 .map(|e| e.syntax().text_range()) 314 .map(|e| e.syntax().text_range())
262 .and_then(|r| find_node_with_range(original_file.syntax(), r)); 315 .and_then(|r| find_node_with_range(original_file, r));
263 self.dot_receiver_is_ambiguous_float_literal = 316 self.dot_receiver_is_ambiguous_float_literal =
264 if let Some(ast::Expr::Literal(l)) = &self.dot_receiver { 317 if let Some(ast::Expr::Literal(l)) = &self.dot_receiver {
265 match l.kind() { 318 match l.kind() {
@@ -275,7 +328,7 @@ impl<'a> CompletionContext<'a> {
275 self.dot_receiver = method_call_expr 328 self.dot_receiver = method_call_expr
276 .expr() 329 .expr()
277 .map(|e| e.syntax().text_range()) 330 .map(|e| e.syntax().text_range())
278 .and_then(|r| find_node_with_range(original_file.syntax(), r)); 331 .and_then(|r| find_node_with_range(original_file, r));
279 self.is_call = true; 332 self.is_call = true;
280 } 333 }
281 } 334 }
diff --git a/crates/ra_mbe/src/mbe_expander/matcher.rs b/crates/ra_mbe/src/mbe_expander/matcher.rs
index ffba03898..49c53183a 100644
--- a/crates/ra_mbe/src/mbe_expander/matcher.rs
+++ b/crates/ra_mbe/src/mbe_expander/matcher.rs
@@ -247,6 +247,7 @@ impl<'a> TtIter<'a> {
247 ra_parser::parse_fragment(&mut src, &mut sink, fragment_kind); 247 ra_parser::parse_fragment(&mut src, &mut sink, fragment_kind);
248 248
249 if !sink.cursor.is_root() || sink.error { 249 if !sink.cursor.is_root() || sink.error {
250 // FIXME better recovery in this case would help completion inside macros immensely
250 return Err(()); 251 return Err(());
251 } 252 }
252 253
@@ -375,7 +376,8 @@ fn match_meta_var(kind: &str, input: &mut TtIter) -> Result<Option<Fragment>, Ex
375 return Ok(Some(Fragment::Tokens(tt))); 376 return Ok(Some(Fragment::Tokens(tt)));
376 } 377 }
377 }; 378 };
378 let tt = input.expect_fragment(fragment).map_err(|()| err!())?; 379 let tt =
380 input.expect_fragment(fragment).map_err(|()| err!("fragment did not parse as {}", kind))?;
379 let fragment = if kind == "expr" { Fragment::Ast(tt) } else { Fragment::Tokens(tt) }; 381 let fragment = if kind == "expr" { Fragment::Ast(tt) } else { Fragment::Tokens(tt) };
380 Ok(Some(fragment)) 382 Ok(Some(fragment))
381} 383}
diff --git a/docs/user/readme.adoc b/docs/user/readme.adoc
index f1386a8f9..e5843ed70 100644
--- a/docs/user/readme.adoc
+++ b/docs/user/readme.adoc
@@ -2,6 +2,13 @@
2:toc: preamble 2:toc: preamble
3:sectanchors: 3:sectanchors:
4:page-layout: post 4:page-layout: post
5// https://gist.github.com/dcode/0cfbf2699a1fe9b46ff04c41721dda74#admonitions
6:tip-caption: :bulb:
7:note-caption: :information_source:
8:important-caption: :heavy_exclamation_mark:
9:caution-caption: :fire:
10:warning-caption: :warning:
11
5 12
6 13
7// Master copy of this document lives in the https://github.com/rust-analyzer/rust-analyzer repository 14// Master copy of this document lives in the https://github.com/rust-analyzer/rust-analyzer repository
@@ -30,7 +37,7 @@ $ rustup component add rust-src
30 37
31=== VS Code 38=== VS Code
32 39
33This the best supported editor at the moment. 40This is the best supported editor at the moment.
34rust-analyzer plugin for VS Code is maintained 41rust-analyzer plugin for VS Code is maintained
35https://github.com/rust-analyzer/rust-analyzer/tree/master/editors/code[in tree]. 42https://github.com/rust-analyzer/rust-analyzer/tree/master/editors/code[in tree].
36 43
@@ -40,6 +47,16 @@ By default, the plugin will prompt you to download the matching version of the s
40 47
41image::https://user-images.githubusercontent.com/9021944/75067008-17502500-54ba-11ea-835a-f92aac50e866.png[] 48image::https://user-images.githubusercontent.com/9021944/75067008-17502500-54ba-11ea-835a-f92aac50e866.png[]
42 49
50[NOTE]
51====
52To disable this notification put the following to `settings.json`
53
54[source,json]
55----
56{ "rust-analyzer.updates.askBeforeDownload": false }
57----
58====
59
43The server binary is stored in `~/.config/Code/User/globalStorage/matklad.rust-analyzer`. 60The server binary is stored in `~/.config/Code/User/globalStorage/matklad.rust-analyzer`.
44 61
45Note that we only support the latest version of VS Code. 62Note that we only support the latest version of VS Code.
diff --git a/editors/code/package.json b/editors/code/package.json
index fd44d2bd5..7a4a93e30 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -219,6 +219,11 @@
219 } 219 }
220 } 220 }
221 }, 221 },
222 "rust-analyzer.updates.askBeforeDownload": {
223 "type": "boolean",
224 "default": true,
225 "description": "Whether to ask for permission before downloading any files from the Internet"
226 },
222 "rust-analyzer.serverPath": { 227 "rust-analyzer.serverPath": {
223 "type": [ 228 "type": [
224 "null", 229 "null",
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts
index bf915102c..b72206d3c 100644
--- a/editors/code/src/config.ts
+++ b/editors/code/src/config.ts
@@ -1,6 +1,6 @@
1import * as os from "os"; 1import * as os from "os";
2import * as vscode from 'vscode'; 2import * as vscode from 'vscode';
3import { BinarySource } from "./installation/interfaces"; 3import { ArtifactSource } from "./installation/interfaces";
4import { log } from "./util"; 4import { log } from "./util";
5 5
6const RA_LSP_DEBUG = process.env.__RA_LSP_SERVER_DEBUG; 6const RA_LSP_DEBUG = process.env.__RA_LSP_SERVER_DEBUG;
@@ -114,12 +114,12 @@ export class Config {
114 } 114 }
115 } 115 }
116 116
117 get serverSource(): null | BinarySource { 117 get serverSource(): null | ArtifactSource {
118 const serverPath = RA_LSP_DEBUG ?? this.cfg.get<null | string>("serverPath"); 118 const serverPath = RA_LSP_DEBUG ?? this.cfg.get<null | string>("serverPath");
119 119
120 if (serverPath) { 120 if (serverPath) {
121 return { 121 return {
122 type: BinarySource.Type.ExplicitPath, 122 type: ArtifactSource.Type.ExplicitPath,
123 path: Config.replaceTildeWithHomeDir(serverPath) 123 path: Config.replaceTildeWithHomeDir(serverPath)
124 }; 124 };
125 } 125 }
@@ -129,11 +129,12 @@ export class Config {
129 if (!prebuiltBinaryName) return null; 129 if (!prebuiltBinaryName) return null;
130 130
131 return { 131 return {
132 type: BinarySource.Type.GithubRelease, 132 type: ArtifactSource.Type.GithubRelease,
133 dir: this.ctx.globalStoragePath, 133 dir: this.ctx.globalStoragePath,
134 file: prebuiltBinaryName, 134 file: prebuiltBinaryName,
135 storage: this.ctx.globalState, 135 storage: this.ctx.globalState,
136 version: Config.extensionVersion, 136 tag: Config.extensionVersion,
137 askBeforeDownload: this.cfg.get("updates.askBeforeDownload") as boolean,
137 repo: { 138 repo: {
138 name: "rust-analyzer", 139 name: "rust-analyzer",
139 owner: "rust-analyzer", 140 owner: "rust-analyzer",
diff --git a/editors/code/src/installation/interfaces.ts b/editors/code/src/installation/interfaces.ts
index e40839e4b..50b635921 100644
--- a/editors/code/src/installation/interfaces.ts
+++ b/editors/code/src/installation/interfaces.ts
@@ -14,14 +14,14 @@ export interface ArtifactReleaseInfo {
14} 14}
15 15
16/** 16/**
17 * Represents the source of a binary artifact which is either specified by the user 17 * Represents the source of a an artifact which is either specified by the user
18 * explicitly, or bundled by this extension from GitHub releases. 18 * explicitly, or bundled by this extension from GitHub releases.
19 */ 19 */
20export type BinarySource = BinarySource.ExplicitPath | BinarySource.GithubRelease; 20export type ArtifactSource = ArtifactSource.ExplicitPath | ArtifactSource.GithubRelease;
21 21
22export namespace BinarySource { 22export namespace ArtifactSource {
23 /** 23 /**
24 * Type tag for `BinarySource` discriminated union. 24 * Type tag for `ArtifactSource` discriminated union.
25 */ 25 */
26 export const enum Type { ExplicitPath, GithubRelease } 26 export const enum Type { ExplicitPath, GithubRelease }
27 27
@@ -56,13 +56,18 @@ export namespace BinarySource {
56 /** 56 /**
57 * Tag of github release that denotes a version required by this extension. 57 * Tag of github release that denotes a version required by this extension.
58 */ 58 */
59 version: string; 59 tag: string;
60 60
61 /** 61 /**
62 * Object that provides `get()/update()` operations to store metadata 62 * Object that provides `get()/update()` operations to store metadata
63 * about the actual binary, e.g. its actual version. 63 * about the actual binary, e.g. its actual version.
64 */ 64 */
65 storage: vscode.Memento; 65 storage: vscode.Memento;
66
67 /**
68 * Ask for the user permission before downloading the artifact.
69 */
70 askBeforeDownload: boolean;
66 } 71 }
67 72
68} 73}
diff --git a/editors/code/src/installation/server.ts b/editors/code/src/installation/server.ts
index 6a6cf4f8c..ef1c45ff6 100644
--- a/editors/code/src/installation/server.ts
+++ b/editors/code/src/installation/server.ts
@@ -3,12 +3,12 @@ import * as path from "path";
3import { promises as dns } from "dns"; 3import { promises as dns } from "dns";
4import { spawnSync } from "child_process"; 4import { spawnSync } from "child_process";
5 5
6import { BinarySource } from "./interfaces"; 6import { ArtifactSource } from "./interfaces";
7import { fetchArtifactReleaseInfo } from "./fetch_artifact_release_info"; 7import { fetchArtifactReleaseInfo } from "./fetch_artifact_release_info";
8import { downloadArtifact } from "./download_artifact"; 8import { downloadArtifact } from "./download_artifact";
9import { log, assert } from "../util"; 9import { log, assert } from "../util";
10 10
11export async function ensureServerBinary(source: null | BinarySource): Promise<null | string> { 11export async function ensureServerBinary(source: null | ArtifactSource): Promise<null | string> {
12 if (!source) { 12 if (!source) {
13 vscode.window.showErrorMessage( 13 vscode.window.showErrorMessage(
14 "Unfortunately we don't ship binaries for your platform yet. " + 14 "Unfortunately we don't ship binaries for your platform yet. " +
@@ -22,7 +22,7 @@ export async function ensureServerBinary(source: null | BinarySource): Promise<n
22 } 22 }
23 23
24 switch (source.type) { 24 switch (source.type) {
25 case BinarySource.Type.ExplicitPath: { 25 case ArtifactSource.Type.ExplicitPath: {
26 if (isBinaryAvailable(source.path)) { 26 if (isBinaryAvailable(source.path)) {
27 return source.path; 27 return source.path;
28 } 28 }
@@ -34,11 +34,11 @@ export async function ensureServerBinary(source: null | BinarySource): Promise<n
34 ); 34 );
35 return null; 35 return null;
36 } 36 }
37 case BinarySource.Type.GithubRelease: { 37 case ArtifactSource.Type.GithubRelease: {
38 const prebuiltBinaryPath = path.join(source.dir, source.file); 38 const prebuiltBinaryPath = path.join(source.dir, source.file);
39 39
40 const installedVersion: null | string = getServerVersion(source.storage); 40 const installedVersion: null | string = getServerVersion(source.storage);
41 const requiredVersion: string = source.version; 41 const requiredVersion: string = source.tag;
42 42
43 log.debug("Installed version:", installedVersion, "required:", requiredVersion); 43 log.debug("Installed version:", installedVersion, "required:", requiredVersion);
44 44
@@ -46,12 +46,14 @@ export async function ensureServerBinary(source: null | BinarySource): Promise<n
46 return prebuiltBinaryPath; 46 return prebuiltBinaryPath;
47 } 47 }
48 48
49 const userResponse = await vscode.window.showInformationMessage( 49 if (source.askBeforeDownload) {
50 `Language server version ${source.version} for rust-analyzer is not installed. ` + 50 const userResponse = await vscode.window.showInformationMessage(
51 "Do you want to download it now?", 51 `Language server version ${source.tag} for rust-analyzer is not installed. ` +
52 "Download now", "Cancel" 52 "Do you want to download it now?",
53 ); 53 "Download now", "Cancel"
54 if (userResponse !== "Download now") return null; 54 );
55 if (userResponse !== "Download now") return null;
56 }
55 57
56 if (!await downloadServer(source)) return null; 58 if (!await downloadServer(source)) return null;
57 59
@@ -60,9 +62,9 @@ export async function ensureServerBinary(source: null | BinarySource): Promise<n
60 } 62 }
61} 63}
62 64
63async function downloadServer(source: BinarySource.GithubRelease): Promise<boolean> { 65async function downloadServer(source: ArtifactSource.GithubRelease): Promise<boolean> {
64 try { 66 try {
65 const releaseInfo = await fetchArtifactReleaseInfo(source.repo, source.file, source.version); 67 const releaseInfo = await fetchArtifactReleaseInfo(source.repo, source.file, source.tag);
66 68
67 await downloadArtifact(releaseInfo, source.file, source.dir, "language server"); 69 await downloadArtifact(releaseInfo, source.file, source.dir, "language server");
68 await setServerVersion(source.storage, releaseInfo.releaseName); 70 await setServerVersion(source.storage, releaseInfo.releaseName);