aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_analysis
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_analysis')
-rw-r--r--crates/ra_analysis/Cargo.toml4
-rw-r--r--crates/ra_analysis/src/completion/complete_dot.rs17
-rw-r--r--crates/ra_analysis/src/completion/complete_keyword.rs82
-rw-r--r--crates/ra_analysis/src/completion/complete_path.rs51
-rw-r--r--crates/ra_analysis/src/completion/complete_scope.rs41
-rw-r--r--crates/ra_analysis/src/completion/complete_snippet.rs2
-rw-r--r--crates/ra_analysis/src/completion/completion_context.rs37
-rw-r--r--crates/ra_analysis/src/completion/completion_item.rs30
-rw-r--r--crates/ra_analysis/src/db.rs1
-rw-r--r--crates/ra_analysis/src/extend_selection.rs51
-rw-r--r--crates/ra_analysis/src/imp.rs86
-rw-r--r--crates/ra_analysis/src/lib.rs37
-rw-r--r--crates/ra_analysis/src/macros.rs75
-rw-r--r--crates/ra_analysis/src/mock_analysis.rs17
-rw-r--r--crates/ra_analysis/src/syntax_highlighting.rs63
15 files changed, 454 insertions, 140 deletions
diff --git a/crates/ra_analysis/Cargo.toml b/crates/ra_analysis/Cargo.toml
index 4a7b99947..a5d4f65ab 100644
--- a/crates/ra_analysis/Cargo.toml
+++ b/crates/ra_analysis/Cargo.toml
@@ -9,9 +9,9 @@ log = "0.4.5"
9relative-path = "0.4.0" 9relative-path = "0.4.0"
10rayon = "1.0.2" 10rayon = "1.0.2"
11fst = "0.3.1" 11fst = "0.3.1"
12salsa = "0.8.0" 12salsa = "0.9.0"
13rustc-hash = "1.0" 13rustc-hash = "1.0"
14parking_lot = "0.6.4" 14parking_lot = "0.7.0"
15ra_syntax = { path = "../ra_syntax" } 15ra_syntax = { path = "../ra_syntax" }
16ra_editor = { path = "../ra_editor" } 16ra_editor = { path = "../ra_editor" }
17ra_text_edit = { path = "../ra_text_edit" } 17ra_text_edit = { path = "../ra_text_edit" }
diff --git a/crates/ra_analysis/src/completion/complete_dot.rs b/crates/ra_analysis/src/completion/complete_dot.rs
index 93d657576..f24835d17 100644
--- a/crates/ra_analysis/src/completion/complete_dot.rs
+++ b/crates/ra_analysis/src/completion/complete_dot.rs
@@ -6,20 +6,9 @@ use crate::completion::{CompletionContext, Completions, CompletionKind, Completi
6 6
7/// Complete dot accesses, i.e. fields or methods (currently only fields). 7/// Complete dot accesses, i.e. fields or methods (currently only fields).
8pub(super) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) -> Cancelable<()> { 8pub(super) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) -> Cancelable<()> {
9 let module = if let Some(module) = &ctx.module { 9 let (function, receiver) = match (&ctx.function, ctx.dot_receiver) {
10 module 10 (Some(function), Some(receiver)) => (function, receiver),
11 } else { 11 _ => return Ok(()),
12 return Ok(());
13 };
14 let function = if let Some(fn_def) = ctx.enclosing_fn {
15 hir::source_binder::function_from_module(ctx.db, module, fn_def)
16 } else {
17 return Ok(());
18 };
19 let receiver = if let Some(receiver) = ctx.dot_receiver {
20 receiver
21 } else {
22 return Ok(());
23 }; 12 };
24 let infer_result = function.infer(ctx.db)?; 13 let infer_result = function.infer(ctx.db)?;
25 let receiver_ty = if let Some(ty) = infer_result.type_of_node(receiver.syntax()) { 14 let receiver_ty = if let Some(ty) = infer_result.type_of_node(receiver.syntax()) {
diff --git a/crates/ra_analysis/src/completion/complete_keyword.rs b/crates/ra_analysis/src/completion/complete_keyword.rs
index 5427fcb11..b2486104a 100644
--- a/crates/ra_analysis/src/completion/complete_keyword.rs
+++ b/crates/ra_analysis/src/completion/complete_keyword.rs
@@ -18,7 +18,7 @@ pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte
18 if !ctx.is_trivial_path { 18 if !ctx.is_trivial_path {
19 return; 19 return;
20 } 20 }
21 let fn_def = match ctx.enclosing_fn { 21 let fn_def = match ctx.function_syntax {
22 Some(it) => it, 22 Some(it) => it,
23 None => return, 23 None => return,
24 }; 24 };
@@ -32,10 +32,15 @@ pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte
32 acc.add(keyword("else if", "else if $0 {}")); 32 acc.add(keyword("else if", "else if $0 {}"));
33 } 33 }
34 if is_in_loop_body(ctx.leaf) { 34 if is_in_loop_body(ctx.leaf) {
35 acc.add(keyword("continue", "continue")); 35 if ctx.can_be_stmt {
36 acc.add(keyword("break", "break")); 36 acc.add(keyword("continue", "continue;"));
37 acc.add(keyword("break", "break;"));
38 } else {
39 acc.add(keyword("continue", "continue"));
40 acc.add(keyword("break", "break"));
41 }
37 } 42 }
38 acc.add_all(complete_return(fn_def, ctx.is_stmt)); 43 acc.add_all(complete_return(fn_def, ctx.can_be_stmt));
39} 44}
40 45
41fn is_in_loop_body(leaf: SyntaxNodeRef) -> bool { 46fn is_in_loop_body(leaf: SyntaxNodeRef) -> bool {
@@ -57,8 +62,8 @@ fn is_in_loop_body(leaf: SyntaxNodeRef) -> bool {
57 false 62 false
58} 63}
59 64
60fn complete_return(fn_def: ast::FnDef, is_stmt: bool) -> Option<CompletionItem> { 65fn complete_return(fn_def: ast::FnDef, can_be_stmt: bool) -> Option<CompletionItem> {
61 let snip = match (is_stmt, fn_def.ret_type().is_some()) { 66 let snip = match (can_be_stmt, fn_def.ret_type().is_some()) {
62 (true, true) => "return $0;", 67 (true, true) => "return $0;",
63 (true, false) => "return;", 68 (true, false) => "return;",
64 (false, true) => "return $0", 69 (false, true) => "return $0",
@@ -75,7 +80,7 @@ mod tests {
75 } 80 }
76 81
77 #[test] 82 #[test]
78 fn test_completion_kewords() { 83 fn completes_various_keywords_in_function() {
79 check_keyword_completion( 84 check_keyword_completion(
80 r" 85 r"
81 fn quux() { 86 fn quux() {
@@ -87,13 +92,13 @@ mod tests {
87 match "match $0 {}" 92 match "match $0 {}"
88 while "while $0 {}" 93 while "while $0 {}"
89 loop "loop {$0}" 94 loop "loop {$0}"
90 return "return" 95 return "return;"
91 "#, 96 "#,
92 ); 97 );
93 } 98 }
94 99
95 #[test] 100 #[test]
96 fn test_completion_else() { 101 fn completes_else_after_if() {
97 check_keyword_completion( 102 check_keyword_completion(
98 r" 103 r"
99 fn quux() { 104 fn quux() {
@@ -109,7 +114,7 @@ mod tests {
109 loop "loop {$0}" 114 loop "loop {$0}"
110 else "else {$0}" 115 else "else {$0}"
111 else if "else if $0 {}" 116 else if "else if $0 {}"
112 return "return" 117 return "return;"
113 "#, 118 "#,
114 ); 119 );
115 } 120 }
@@ -149,7 +154,7 @@ mod tests {
149 } 154 }
150 155
151 #[test] 156 #[test]
152 fn test_completion_return_no_stmt() { 157 fn dont_add_semi_after_return_if_not_a_statement() {
153 check_keyword_completion( 158 check_keyword_completion(
154 r" 159 r"
155 fn quux() -> i32 { 160 fn quux() -> i32 {
@@ -169,7 +174,27 @@ mod tests {
169 } 174 }
170 175
171 #[test] 176 #[test]
172 fn test_continue_break_completion() { 177 fn last_return_in_block_has_semi() {
178 check_keyword_completion(
179 r"
180 fn quux() -> i32 {
181 if condition {
182 <|>
183 }
184 }
185 ",
186 r#"
187 if "if $0 {}"
188 match "match $0 {}"
189 while "while $0 {}"
190 loop "loop {$0}"
191 return "return $0;"
192 "#,
193 );
194 }
195
196 #[test]
197 fn completes_break_and_continue_in_loops() {
173 check_keyword_completion( 198 check_keyword_completion(
174 r" 199 r"
175 fn quux() -> i32 { 200 fn quux() -> i32 {
@@ -181,11 +206,12 @@ mod tests {
181 match "match $0 {}" 206 match "match $0 {}"
182 while "while $0 {}" 207 while "while $0 {}"
183 loop "loop {$0}" 208 loop "loop {$0}"
184 continue "continue" 209 continue "continue;"
185 break "break" 210 break "break;"
186 return "return $0" 211 return "return $0;"
187 "#, 212 "#,
188 ); 213 );
214 // No completion: lambda isolates control flow
189 check_keyword_completion( 215 check_keyword_completion(
190 r" 216 r"
191 fn quux() -> i32 { 217 fn quux() -> i32 {
@@ -197,8 +223,32 @@ mod tests {
197 match "match $0 {}" 223 match "match $0 {}"
198 while "while $0 {}" 224 while "while $0 {}"
199 loop "loop {$0}" 225 loop "loop {$0}"
200 return "return $0" 226 return "return $0;"
201 "#, 227 "#,
202 ); 228 );
203 } 229 }
230
231 #[test]
232 fn no_semi_after_break_continue_in_expr() {
233 check_keyword_completion(
234 r"
235 fn f() {
236 loop {
237 match () {
238 () => br<|>
239 }
240 }
241 }
242 ",
243 r#"
244 if "if $0 {}"
245 match "match $0 {}"
246 while "while $0 {}"
247 loop "loop {$0}"
248 continue "continue"
249 break "break"
250 return "return"
251 "#,
252 )
253 }
204} 254}
diff --git a/crates/ra_analysis/src/completion/complete_path.rs b/crates/ra_analysis/src/completion/complete_path.rs
index aaa2c7cee..4723a65a6 100644
--- a/crates/ra_analysis/src/completion/complete_path.rs
+++ b/crates/ra_analysis/src/completion/complete_path.rs
@@ -1,6 +1,6 @@
1use crate::{ 1use crate::{
2 Cancelable, 2 Cancelable,
3 completion::{CompletionItem, Completions, CompletionKind, CompletionContext}, 3 completion::{CompletionItem, CompletionItemKind, Completions, CompletionKind, CompletionContext},
4}; 4};
5 5
6pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) -> Cancelable<()> { 6pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) -> Cancelable<()> {
@@ -12,16 +12,25 @@ pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) -> C
12 Some(it) => it, 12 Some(it) => it,
13 None => return Ok(()), 13 None => return Ok(()),
14 }; 14 };
15 let target_module = match def_id.resolve(ctx.db)? { 15 match def_id.resolve(ctx.db)? {
16 hir::Def::Module(it) => it, 16 hir::Def::Module(module) => {
17 let module_scope = module.scope(ctx.db)?;
18 module_scope.entries().for_each(|(name, res)| {
19 CompletionItem::new(CompletionKind::Reference, name.to_string())
20 .from_resolution(ctx, res)
21 .add_to(acc)
22 });
23 }
24 hir::Def::Enum(e) => e
25 .variants(ctx.db)?
26 .into_iter()
27 .for_each(|(name, _variant)| {
28 CompletionItem::new(CompletionKind::Reference, name.to_string())
29 .kind(CompletionItemKind::EnumVariant)
30 .add_to(acc)
31 }),
17 _ => return Ok(()), 32 _ => return Ok(()),
18 }; 33 };
19 let module_scope = target_module.scope(ctx.db)?;
20 module_scope.entries().for_each(|(name, res)| {
21 CompletionItem::new(CompletionKind::Reference, name.to_string())
22 .from_resolution(ctx.db, res)
23 .add_to(acc)
24 });
25 Ok(()) 34 Ok(())
26} 35}
27 36
@@ -92,4 +101,28 @@ mod tests {
92 "Spam", 101 "Spam",
93 ); 102 );
94 } 103 }
104
105 #[test]
106 fn completes_enum_variant() {
107 check_reference_completion(
108 "
109 //- /lib.rs
110 enum E { Foo, Bar(i32) }
111 fn foo() { let _ = E::<|> }
112 ",
113 "Foo;Bar",
114 );
115 }
116
117 #[test]
118 fn dont_render_function_parens_in_use_item() {
119 check_reference_completion(
120 "
121 //- /lib.rs
122 mod m { pub fn foo() {} }
123 use crate::m::f<|>;
124 ",
125 "foo",
126 )
127 }
95} 128}
diff --git a/crates/ra_analysis/src/completion/complete_scope.rs b/crates/ra_analysis/src/completion/complete_scope.rs
index a57670e3b..daf666505 100644
--- a/crates/ra_analysis/src/completion/complete_scope.rs
+++ b/crates/ra_analysis/src/completion/complete_scope.rs
@@ -14,8 +14,7 @@ pub(super) fn complete_scope(acc: &mut Completions, ctx: &CompletionContext) ->
14 Some(it) => it, 14 Some(it) => it,
15 None => return Ok(()), 15 None => return Ok(()),
16 }; 16 };
17 if let Some(fn_def) = ctx.enclosing_fn { 17 if let Some(function) = &ctx.function {
18 let function = hir::source_binder::function_from_module(ctx.db, module, fn_def);
19 let scopes = function.scopes(ctx.db); 18 let scopes = function.scopes(ctx.db);
20 complete_fn(acc, &scopes, ctx.offset); 19 complete_fn(acc, &scopes, ctx.offset);
21 } 20 }
@@ -35,7 +34,7 @@ pub(super) fn complete_scope(acc: &mut Completions, ctx: &CompletionContext) ->
35 }) 34 })
36 .for_each(|(name, res)| { 35 .for_each(|(name, res)| {
37 CompletionItem::new(CompletionKind::Reference, name.to_string()) 36 CompletionItem::new(CompletionKind::Reference, name.to_string())
38 .from_resolution(ctx.db, res) 37 .from_resolution(ctx, res)
39 .add_to(acc) 38 .add_to(acc)
40 }); 39 });
41 Ok(()) 40 Ok(())
@@ -75,7 +74,7 @@ mod tests {
75 let z = (); 74 let z = ();
76 } 75 }
77 ", 76 ",
78 "y;x;quux", 77 r#"y;x;quux "quux($0)""#,
79 ); 78 );
80 } 79 }
81 80
@@ -93,7 +92,7 @@ mod tests {
93 } 92 }
94 } 93 }
95 ", 94 ",
96 "b;a;quux", 95 r#"b;a;quux "quux()$0""#,
97 ); 96 );
98 } 97 }
99 98
@@ -107,7 +106,7 @@ mod tests {
107 } 106 }
108 } 107 }
109 ", 108 ",
110 "x;quux", 109 r#"x;quux "quux()$0""#,
111 ); 110 );
112 } 111 }
113 112
@@ -121,7 +120,7 @@ mod tests {
121 <|> 120 <|>
122 } 121 }
123 ", 122 ",
124 "quux;Foo;Baz", 123 r#"quux "quux()$0";Foo;Baz"#,
125 ); 124 );
126 } 125 }
127 126
@@ -135,7 +134,7 @@ mod tests {
135 fn quux() { <|> } 134 fn quux() { <|> }
136 } 135 }
137 ", 136 ",
138 "quux;Bar", 137 r#"quux "quux()$0";Bar"#,
139 ); 138 );
140 } 139 }
141 140
@@ -146,12 +145,12 @@ mod tests {
146 struct Foo; 145 struct Foo;
147 fn x() -> <|> 146 fn x() -> <|>
148 ", 147 ",
149 "Foo;x", 148 r#"Foo;x "x()$0""#,
150 ) 149 )
151 } 150 }
152 151
153 #[test] 152 #[test]
154 fn dont_show_to_completions_for_shadowing() { 153 fn dont_show_both_completions_for_shadowing() {
155 check_reference_completion( 154 check_reference_completion(
156 r" 155 r"
157 fn foo() -> { 156 fn foo() -> {
@@ -162,7 +161,7 @@ mod tests {
162 } 161 }
163 } 162 }
164 ", 163 ",
165 "bar;foo", 164 r#"bar;foo "foo()$0""#,
166 ) 165 )
167 } 166 }
168 167
@@ -170,4 +169,24 @@ mod tests {
170 fn completes_self_in_methods() { 169 fn completes_self_in_methods() {
171 check_reference_completion(r"impl S { fn foo(&self) { <|> } }", "self") 170 check_reference_completion(r"impl S { fn foo(&self) { <|> } }", "self")
172 } 171 }
172
173 #[test]
174 fn inserts_parens_for_function_calls() {
175 check_reference_completion(
176 r"
177 fn no_args() {}
178 fn main() { no_<|> }
179 ",
180 r#"no_args "no_args()$0"
181 main "main()$0""#,
182 );
183 check_reference_completion(
184 r"
185 fn with_args(x: i32, y: String) {}
186 fn main() { with_<|> }
187 ",
188 r#"main "main()$0"
189 with_args "with_args($0)""#,
190 );
191 }
173} 192}
diff --git a/crates/ra_analysis/src/completion/complete_snippet.rs b/crates/ra_analysis/src/completion/complete_snippet.rs
index fb9da0a4f..a495751dd 100644
--- a/crates/ra_analysis/src/completion/complete_snippet.rs
+++ b/crates/ra_analysis/src/completion/complete_snippet.rs
@@ -7,7 +7,7 @@ fn snippet(label: &str, snippet: &str) -> Builder {
7} 7}
8 8
9pub(super) fn complete_expr_snippet(acc: &mut Completions, ctx: &CompletionContext) { 9pub(super) fn complete_expr_snippet(acc: &mut Completions, ctx: &CompletionContext) {
10 if !(ctx.is_trivial_path && ctx.enclosing_fn.is_some()) { 10 if !(ctx.is_trivial_path && ctx.function_syntax.is_some()) {
11 return; 11 return;
12 } 12 }
13 snippet("pd", "eprintln!(\"$0 = {:?}\", $0);").add_to(acc); 13 snippet("pd", "eprintln!(\"$0 = {:?}\", $0);").add_to(acc);
diff --git a/crates/ra_analysis/src/completion/completion_context.rs b/crates/ra_analysis/src/completion/completion_context.rs
index 978772fd4..4685c9328 100644
--- a/crates/ra_analysis/src/completion/completion_context.rs
+++ b/crates/ra_analysis/src/completion/completion_context.rs
@@ -22,14 +22,17 @@ pub(super) struct CompletionContext<'a> {
22 pub(super) offset: TextUnit, 22 pub(super) offset: TextUnit,
23 pub(super) leaf: SyntaxNodeRef<'a>, 23 pub(super) leaf: SyntaxNodeRef<'a>,
24 pub(super) module: Option<hir::Module>, 24 pub(super) module: Option<hir::Module>,
25 pub(super) enclosing_fn: Option<ast::FnDef<'a>>, 25 pub(super) function: Option<hir::Function>,
26 pub(super) function_syntax: Option<ast::FnDef<'a>>,
27 pub(super) use_item_syntax: Option<ast::UseItem<'a>>,
26 pub(super) is_param: bool, 28 pub(super) is_param: bool,
27 /// A single-indent path, like `foo`. 29 /// A single-indent path, like `foo`.
28 pub(super) is_trivial_path: bool, 30 pub(super) is_trivial_path: bool,
29 /// If not a trivial, path, the prefix (qualifier). 31 /// If not a trivial, path, the prefix (qualifier).
30 pub(super) path_prefix: Option<hir::Path>, 32 pub(super) path_prefix: Option<hir::Path>,
31 pub(super) after_if: bool, 33 pub(super) after_if: bool,
32 pub(super) is_stmt: bool, 34 /// `true` if we are a statement or a last expr in the block.
35 pub(super) can_be_stmt: bool,
33 /// Something is typed at the "top" level, in module or impl/trait. 36 /// Something is typed at the "top" level, in module or impl/trait.
34 pub(super) is_new_item: bool, 37 pub(super) is_new_item: bool,
35 /// The receiver if this is a field or method access, i.e. writing something.<|> 38 /// The receiver if this is a field or method access, i.e. writing something.<|>
@@ -52,12 +55,14 @@ impl<'a> CompletionContext<'a> {
52 leaf, 55 leaf,
53 offset: position.offset, 56 offset: position.offset,
54 module, 57 module,
55 enclosing_fn: None, 58 function: None,
59 function_syntax: None,
60 use_item_syntax: None,
56 is_param: false, 61 is_param: false,
57 is_trivial_path: false, 62 is_trivial_path: false,
58 path_prefix: None, 63 path_prefix: None,
59 after_if: false, 64 after_if: false,
60 is_stmt: false, 65 can_be_stmt: false,
61 is_new_item: false, 66 is_new_item: false,
62 dot_receiver: None, 67 dot_receiver: None,
63 is_method_call: false, 68 is_method_call: false,
@@ -112,11 +117,20 @@ impl<'a> CompletionContext<'a> {
112 _ => (), 117 _ => (),
113 } 118 }
114 119
115 self.enclosing_fn = self 120 self.use_item_syntax = self.leaf.ancestors().find_map(ast::UseItem::cast);
121
122 self.function_syntax = self
116 .leaf 123 .leaf
117 .ancestors() 124 .ancestors()
118 .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) 125 .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
119 .find_map(ast::FnDef::cast); 126 .find_map(ast::FnDef::cast);
127 match (&self.module, self.function_syntax) {
128 (Some(module), Some(fn_def)) => {
129 let function = source_binder::function_from_module(self.db, module, fn_def);
130 self.function = Some(function);
131 }
132 _ => (),
133 }
120 134
121 let parent = match name_ref.syntax().parent() { 135 let parent = match name_ref.syntax().parent() {
122 Some(it) => it, 136 Some(it) => it,
@@ -134,13 +148,22 @@ impl<'a> CompletionContext<'a> {
134 if path.qualifier().is_none() { 148 if path.qualifier().is_none() {
135 self.is_trivial_path = true; 149 self.is_trivial_path = true;
136 150
137 self.is_stmt = match name_ref 151 self.can_be_stmt = match name_ref
138 .syntax() 152 .syntax()
139 .ancestors() 153 .ancestors()
140 .filter_map(ast::ExprStmt::cast) 154 .filter_map(ast::ExprStmt::cast)
141 .next() 155 .next()
142 { 156 {
143 None => false, 157 None => {
158 name_ref
159 .syntax()
160 .ancestors()
161 .filter_map(ast::Block::cast)
162 .next()
163 .and_then(|block| block.expr())
164 .map(|e| e.syntax().range())
165 == Some(name_ref.syntax().range())
166 }
144 Some(expr_stmt) => expr_stmt.syntax().range() == name_ref.syntax().range(), 167 Some(expr_stmt) => expr_stmt.syntax().range() == name_ref.syntax().range(),
145 }; 168 };
146 169
diff --git a/crates/ra_analysis/src/completion/completion_item.rs b/crates/ra_analysis/src/completion/completion_item.rs
index c9f9f495d..cd4d529f9 100644
--- a/crates/ra_analysis/src/completion/completion_item.rs
+++ b/crates/ra_analysis/src/completion/completion_item.rs
@@ -1,7 +1,7 @@
1use crate::db;
2
3use hir::PerNs; 1use hir::PerNs;
4 2
3use crate::completion::CompletionContext;
4
5/// `CompletionItem` describes a single completion variant in the editor pop-up. 5/// `CompletionItem` describes a single completion variant in the editor pop-up.
6/// It is basically a POD with various properties. To construct a 6/// It is basically a POD with various properties. To construct a
7/// `CompletionItem`, use `new` method and the `Builder` struct. 7/// `CompletionItem`, use `new` method and the `Builder` struct.
@@ -29,6 +29,7 @@ pub enum CompletionItemKind {
29 Function, 29 Function,
30 Struct, 30 Struct,
31 Enum, 31 Enum,
32 EnumVariant,
32 Binding, 33 Binding,
33 Field, 34 Field,
34} 35}
@@ -117,12 +118,12 @@ impl Builder {
117 self.kind = Some(kind); 118 self.kind = Some(kind);
118 self 119 self
119 } 120 }
120 pub(crate) fn from_resolution( 121 pub(super) fn from_resolution(
121 mut self, 122 mut self,
122 db: &db::RootDatabase, 123 ctx: &CompletionContext,
123 resolution: &hir::Resolution, 124 resolution: &hir::Resolution,
124 ) -> Builder { 125 ) -> Builder {
125 let resolved = resolution.def_id.and_then(|d| d.resolve(db).ok()); 126 let resolved = resolution.def_id.and_then(|d| d.resolve(ctx.db).ok());
126 let kind = match resolved { 127 let kind = match resolved {
127 PerNs { 128 PerNs {
128 types: Some(hir::Def::Module(..)), 129 types: Some(hir::Def::Module(..)),
@@ -137,14 +138,29 @@ impl Builder {
137 .. 138 ..
138 } => CompletionItemKind::Enum, 139 } => CompletionItemKind::Enum,
139 PerNs { 140 PerNs {
140 values: Some(hir::Def::Function(..)), 141 values: Some(hir::Def::Function(function)),
141 .. 142 ..
142 } => CompletionItemKind::Function, 143 } => return self.from_function(ctx, function),
143 _ => return self, 144 _ => return self,
144 }; 145 };
145 self.kind = Some(kind); 146 self.kind = Some(kind);
146 self 147 self
147 } 148 }
149
150 fn from_function(mut self, ctx: &CompletionContext, function: hir::Function) -> Builder {
151 // If not an import, add parenthesis automatically.
152 if ctx.use_item_syntax.is_none() {
153 if let Some(sig_info) = function.signature_info(ctx.db) {
154 if sig_info.params.is_empty() {
155 self.snippet = Some(format!("{}()$0", self.label));
156 } else {
157 self.snippet = Some(format!("{}($0)", self.label));
158 }
159 }
160 }
161 self.kind = Some(CompletionItemKind::Function);
162 self
163 }
148} 164}
149 165
150impl Into<CompletionItem> for Builder { 166impl Into<CompletionItem> for Builder {
diff --git a/crates/ra_analysis/src/db.rs b/crates/ra_analysis/src/db.rs
index 036e284bf..b072a5eba 100644
--- a/crates/ra_analysis/src/db.rs
+++ b/crates/ra_analysis/src/db.rs
@@ -91,7 +91,6 @@ salsa::database_storage! {
91 fn file_item() for hir::db::FileItemQuery; 91 fn file_item() for hir::db::FileItemQuery;
92 fn input_module_items() for hir::db::InputModuleItemsQuery; 92 fn input_module_items() for hir::db::InputModuleItemsQuery;
93 fn item_map() for hir::db::ItemMapQuery; 93 fn item_map() for hir::db::ItemMapQuery;
94 fn fn_syntax() for hir::db::FnSyntaxQuery;
95 fn submodules() for hir::db::SubmodulesQuery; 94 fn submodules() for hir::db::SubmodulesQuery;
96 fn infer() for hir::db::InferQuery; 95 fn infer() for hir::db::InferQuery;
97 fn type_for_def() for hir::db::TypeForDefQuery; 96 fn type_for_def() for hir::db::TypeForDefQuery;
diff --git a/crates/ra_analysis/src/extend_selection.rs b/crates/ra_analysis/src/extend_selection.rs
new file mode 100644
index 000000000..cde6ee101
--- /dev/null
+++ b/crates/ra_analysis/src/extend_selection.rs
@@ -0,0 +1,51 @@
1use ra_db::SyntaxDatabase;
2use ra_syntax::{
3 SyntaxNodeRef, AstNode,
4 ast, algo::find_covering_node,
5};
6
7use crate::{
8 TextRange, FileRange,
9 db::RootDatabase,
10};
11
12pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange {
13 let source_file = db.source_file(frange.file_id);
14 if let Some(macro_call) = find_macro_call(source_file.syntax(), frange.range) {
15 if let Some(exp) = crate::macros::expand(db, frange.file_id, macro_call) {
16 if let Some(dst_range) = exp.map_range_forward(frange.range) {
17 if let Some(dst_range) = ra_editor::extend_selection(exp.source_file(), dst_range) {
18 if let Some(src_range) = exp.map_range_back(dst_range) {
19 return src_range;
20 }
21 }
22 }
23 }
24 }
25 ra_editor::extend_selection(&source_file, frange.range).unwrap_or(frange.range)
26}
27
28fn find_macro_call(node: SyntaxNodeRef, range: TextRange) -> Option<ast::MacroCall> {
29 find_covering_node(node, range)
30 .ancestors()
31 .find_map(ast::MacroCall::cast)
32}
33
34#[cfg(test)]
35mod tests {
36 use crate::mock_analysis::single_file_with_range;
37 use test_utils::assert_eq_dbg;
38
39 #[test]
40 fn extend_selection_inside_macros() {
41 let (analysis, frange) = single_file_with_range(
42 "
43 fn main() {
44 ctry!(foo(|x| <|>x<|>));
45 }
46 ",
47 );
48 let r = analysis.extend_selection(frange);
49 assert_eq_dbg("[51; 56)", &r);
50 }
51}
diff --git a/crates/ra_analysis/src/imp.rs b/crates/ra_analysis/src/imp.rs
index 38a5c1a7d..bff2e00c9 100644
--- a/crates/ra_analysis/src/imp.rs
+++ b/crates/ra_analysis/src/imp.rs
@@ -7,10 +7,7 @@ use rayon::prelude::*;
7use salsa::{Database, ParallelDatabase}; 7use salsa::{Database, ParallelDatabase};
8 8
9use hir::{ 9use hir::{
10 self, 10 self, FnSignatureInfo, Problem, source_binder,
11 FnSignatureInfo,
12 Problem,
13 source_binder,
14}; 11};
15use ra_db::{FilesDatabase, SourceRoot, SourceRootId, SyntaxDatabase}; 12use ra_db::{FilesDatabase, SourceRoot, SourceRootId, SyntaxDatabase};
16use ra_editor::{self, FileSymbol, find_node_at_offset, LineIndex, LocalEdit, Severity}; 13use ra_editor::{self, FileSymbol, find_node_at_offset, LineIndex, LocalEdit, Severity};
@@ -26,7 +23,7 @@ use crate::{
26 AnalysisChange, 23 AnalysisChange,
27 Cancelable, 24 Cancelable,
28 completion::{CompletionItem, completions}, 25 completion::{CompletionItem, completions},
29 CrateId, db, Diagnostic, FileId, FilePosition, FileSystemEdit, 26 CrateId, db, Diagnostic, FileId, FilePosition, FileRange, FileSystemEdit,
30 Query, ReferenceResolution, RootChange, SourceChange, SourceFileEdit, 27 Query, ReferenceResolution, RootChange, SourceChange, SourceFileEdit,
31 symbol_index::{LibrarySymbolsQuery, SymbolIndex, SymbolsDatabase}, 28 symbol_index::{LibrarySymbolsQuery, SymbolIndex, SymbolsDatabase},
32}; 29};
@@ -123,9 +120,6 @@ impl AnalysisHostImpl {
123 .query(ra_db::SourceFileQuery) 120 .query(ra_db::SourceFileQuery)
124 .sweep(salsa::SweepStrategy::default().discard_values()); 121 .sweep(salsa::SweepStrategy::default().discard_values());
125 self.db 122 self.db
126 .query(hir::db::FnSyntaxQuery)
127 .sweep(salsa::SweepStrategy::default().discard_values());
128 self.db
129 .query(hir::db::SourceFileItemsQuery) 123 .query(hir::db::SourceFileItemsQuery)
130 .sweep(salsa::SweepStrategy::default().discard_values()); 124 .sweep(salsa::SweepStrategy::default().discard_values());
131 self.db 125 self.db
@@ -146,6 +140,9 @@ impl fmt::Debug for AnalysisImpl {
146} 140}
147 141
148impl AnalysisImpl { 142impl AnalysisImpl {
143 pub fn file_text(&self, file_id: FileId) -> Arc<String> {
144 self.db.file_text(file_id)
145 }
149 pub fn file_syntax(&self, file_id: FileId) -> SourceFileNode { 146 pub fn file_syntax(&self, file_id: FileId) -> SourceFileNode {
150 self.db.source_file(file_id) 147 self.db.source_file(file_id)
151 } 148 }
@@ -243,7 +240,7 @@ impl AnalysisImpl {
243 rr.add_resolution( 240 rr.add_resolution(
244 position.file_id, 241 position.file_id,
245 FileSymbol { 242 FileSymbol {
246 name: entry.name().clone(), 243 name: entry.name().to_string().into(),
247 node_range: entry.ptr().range(), 244 node_range: entry.ptr().range(),
248 kind: NAME, 245 kind: NAME,
249 }, 246 },
@@ -261,23 +258,21 @@ impl AnalysisImpl {
261 let mut rr = ReferenceResolution::new(name.syntax().range()); 258 let mut rr = ReferenceResolution::new(name.syntax().range());
262 if let Some(module) = name.syntax().parent().and_then(ast::Module::cast) { 259 if let Some(module) = name.syntax().parent().and_then(ast::Module::cast) {
263 if module.has_semi() { 260 if module.has_semi() {
264 let parent_module = 261 if let Some(child_module) =
265 source_binder::module_from_file_id(&*self.db, position.file_id)?; 262 source_binder::module_from_declaration(&*self.db, position.file_id, module)?
266 let child_name = module.name(); 263 {
267 match (parent_module, child_name) { 264 let file_id = child_module.source().file_id();
268 (Some(parent_module), Some(child_name)) => { 265 let name = match child_module.name() {
269 if let Some(child) = parent_module.child(&child_name.text()) { 266 Some(name) => name.to_string().into(),
270 let file_id = child.source().file_id(); 267 None => "".into(),
271 let symbol = FileSymbol { 268 };
272 name: child_name.text(), 269 let symbol = FileSymbol {
273 node_range: TextRange::offset_len(0.into(), 0.into()), 270 name,
274 kind: MODULE, 271 node_range: TextRange::offset_len(0.into(), 0.into()),
275 }; 272 kind: MODULE,
276 rr.add_resolution(file_id, symbol); 273 };
277 return Ok(Some(rr)); 274 rr.add_resolution(file_id, symbol);
278 } 275 return Ok(Some(rr));
279 }
280 _ => (),
281 } 276 }
282 } 277 }
283 } 278 }
@@ -334,16 +329,6 @@ impl AnalysisImpl {
334 Ok(Some((binding, descr))) 329 Ok(Some((binding, descr)))
335 } 330 }
336 } 331 }
337
338 pub fn doc_comment_for(
339 &self,
340 file_id: FileId,
341 symbol: FileSymbol,
342 ) -> Cancelable<Option<String>> {
343 let file = self.db.source_file(file_id);
344
345 Ok(symbol.docs(&file))
346 }
347 pub fn doc_text_for(&self, file_id: FileId, symbol: FileSymbol) -> Cancelable<Option<String>> { 332 pub fn doc_text_for(&self, file_id: FileId, symbol: FileSymbol) -> Cancelable<Option<String>> {
348 let file = self.db.source_file(file_id); 333 let file = self.db.source_file(file_id);
349 let result = match (symbol.description(&file), symbol.docs(&file)) { 334 let result = match (symbol.description(&file), symbol.docs(&file)) {
@@ -422,19 +407,21 @@ impl AnalysisImpl {
422 Ok(res) 407 Ok(res)
423 } 408 }
424 409
425 pub fn assists(&self, file_id: FileId, range: TextRange) -> Vec<SourceChange> { 410 pub fn assists(&self, frange: FileRange) -> Vec<SourceChange> {
426 let file = self.file_syntax(file_id); 411 let file = self.file_syntax(frange.file_id);
427 let offset = range.start(); 412 let offset = frange.range.start();
428 let actions = vec![ 413 let actions = vec![
429 ra_editor::flip_comma(&file, offset).map(|f| f()), 414 ra_editor::flip_comma(&file, offset).map(|f| f()),
430 ra_editor::add_derive(&file, offset).map(|f| f()), 415 ra_editor::add_derive(&file, offset).map(|f| f()),
431 ra_editor::add_impl(&file, offset).map(|f| f()), 416 ra_editor::add_impl(&file, offset).map(|f| f()),
432 ra_editor::make_pub_crate(&file, offset).map(|f| f()), 417 ra_editor::make_pub_crate(&file, offset).map(|f| f()),
433 ra_editor::introduce_variable(&file, range).map(|f| f()), 418 ra_editor::introduce_variable(&file, frange.range).map(|f| f()),
434 ]; 419 ];
435 actions 420 actions
436 .into_iter() 421 .into_iter()
437 .filter_map(|local_edit| Some(SourceChange::from_local_edit(file_id, local_edit?))) 422 .filter_map(|local_edit| {
423 Some(SourceChange::from_local_edit(frange.file_id, local_edit?))
424 })
438 .collect() 425 .collect()
439 } 426 }
440 427
@@ -505,18 +492,15 @@ impl AnalysisImpl {
505 Ok(None) 492 Ok(None)
506 } 493 }
507 494
508 pub fn type_of(&self, file_id: FileId, range: TextRange) -> Cancelable<Option<String>> { 495 pub fn type_of(&self, frange: FileRange) -> Cancelable<Option<String>> {
509 let file = self.db.source_file(file_id); 496 let file = self.db.source_file(frange.file_id);
510 let syntax = file.syntax(); 497 let syntax = file.syntax();
511 let node = find_covering_node(syntax, range); 498 let node = find_covering_node(syntax, frange.range);
512 let parent_fn = node.ancestors().filter_map(FnDef::cast).next(); 499 let parent_fn = ctry!(node.ancestors().find_map(FnDef::cast));
513 let parent_fn = if let Some(p) = parent_fn {
514 p
515 } else {
516 return Ok(None);
517 };
518 let function = ctry!(source_binder::function_from_source( 500 let function = ctry!(source_binder::function_from_source(
519 &*self.db, file_id, parent_fn 501 &*self.db,
502 frange.file_id,
503 parent_fn
520 )?); 504 )?);
521 let infer = function.infer(&*self.db)?; 505 let infer = function.infer(&*self.db)?;
522 Ok(infer.type_of_node(node).map(|t| t.to_string())) 506 Ok(infer.type_of_node(node).map(|t| t.to_string()))
diff --git a/crates/ra_analysis/src/lib.rs b/crates/ra_analysis/src/lib.rs
index 476d1b438..9f5e9f358 100644
--- a/crates/ra_analysis/src/lib.rs
+++ b/crates/ra_analysis/src/lib.rs
@@ -16,6 +16,10 @@ mod completion;
16mod symbol_index; 16mod symbol_index;
17pub mod mock_analysis; 17pub mod mock_analysis;
18 18
19mod extend_selection;
20mod syntax_highlighting;
21mod macros;
22
19use std::{fmt, sync::Arc}; 23use std::{fmt, sync::Arc};
20 24
21use rustc_hash::FxHashMap; 25use rustc_hash::FxHashMap;
@@ -37,7 +41,7 @@ pub use ra_editor::{
37pub use hir::FnSignatureInfo; 41pub use hir::FnSignatureInfo;
38 42
39pub use ra_db::{ 43pub use ra_db::{
40 Canceled, Cancelable, FilePosition, 44 Canceled, Cancelable, FilePosition, FileRange,
41 CrateGraph, CrateId, SourceRootId, FileId 45 CrateGraph, CrateId, SourceRootId, FileId
42}; 46};
43 47
@@ -270,14 +274,17 @@ pub struct Analysis {
270} 274}
271 275
272impl Analysis { 276impl Analysis {
277 pub fn file_text(&self, file_id: FileId) -> Arc<String> {
278 self.imp.file_text(file_id)
279 }
273 pub fn file_syntax(&self, file_id: FileId) -> SourceFileNode { 280 pub fn file_syntax(&self, file_id: FileId) -> SourceFileNode {
274 self.imp.file_syntax(file_id).clone() 281 self.imp.file_syntax(file_id).clone()
275 } 282 }
276 pub fn file_line_index(&self, file_id: FileId) -> Arc<LineIndex> { 283 pub fn file_line_index(&self, file_id: FileId) -> Arc<LineIndex> {
277 self.imp.file_line_index(file_id) 284 self.imp.file_line_index(file_id)
278 } 285 }
279 pub fn extend_selection(&self, file: &SourceFileNode, range: TextRange) -> TextRange { 286 pub fn extend_selection(&self, frange: FileRange) -> TextRange {
280 ra_editor::extend_selection(file, range).unwrap_or(range) 287 extend_selection::extend_selection(&self.imp.db, frange)
281 } 288 }
282 pub fn matching_brace(&self, file: &SourceFileNode, offset: TextUnit) -> Option<TextUnit> { 289 pub fn matching_brace(&self, file: &SourceFileNode, offset: TextUnit) -> Option<TextUnit> {
283 ra_editor::matching_brace(file, offset) 290 ra_editor::matching_brace(file, offset)
@@ -286,9 +293,9 @@ impl Analysis {
286 let file = self.imp.file_syntax(file_id); 293 let file = self.imp.file_syntax(file_id);
287 ra_editor::syntax_tree(&file) 294 ra_editor::syntax_tree(&file)
288 } 295 }
289 pub fn join_lines(&self, file_id: FileId, range: TextRange) -> SourceChange { 296 pub fn join_lines(&self, frange: FileRange) -> SourceChange {
290 let file = self.imp.file_syntax(file_id); 297 let file = self.imp.file_syntax(frange.file_id);
291 SourceChange::from_local_edit(file_id, ra_editor::join_lines(&file, range)) 298 SourceChange::from_local_edit(frange.file_id, ra_editor::join_lines(&file, frange.range))
292 } 299 }
293 pub fn on_enter(&self, position: FilePosition) -> Option<SourceChange> { 300 pub fn on_enter(&self, position: FilePosition) -> Option<SourceChange> {
294 let file = self.imp.file_syntax(position.file_id); 301 let file = self.imp.file_syntax(position.file_id);
@@ -323,13 +330,6 @@ impl Analysis {
323 pub fn find_all_refs(&self, position: FilePosition) -> Cancelable<Vec<(FileId, TextRange)>> { 330 pub fn find_all_refs(&self, position: FilePosition) -> Cancelable<Vec<(FileId, TextRange)>> {
324 self.imp.find_all_refs(position) 331 self.imp.find_all_refs(position)
325 } 332 }
326 pub fn doc_comment_for(
327 &self,
328 file_id: FileId,
329 symbol: FileSymbol,
330 ) -> Cancelable<Option<String>> {
331 self.imp.doc_comment_for(file_id, symbol)
332 }
333 pub fn doc_text_for(&self, file_id: FileId, symbol: FileSymbol) -> Cancelable<Option<String>> { 333 pub fn doc_text_for(&self, file_id: FileId, symbol: FileSymbol) -> Cancelable<Option<String>> {
334 self.imp.doc_text_for(file_id, symbol) 334 self.imp.doc_text_for(file_id, symbol)
335 } 335 }
@@ -347,14 +347,13 @@ impl Analysis {
347 Ok(ra_editor::runnables(&file)) 347 Ok(ra_editor::runnables(&file))
348 } 348 }
349 pub fn highlight(&self, file_id: FileId) -> Cancelable<Vec<HighlightedRange>> { 349 pub fn highlight(&self, file_id: FileId) -> Cancelable<Vec<HighlightedRange>> {
350 let file = self.imp.file_syntax(file_id); 350 syntax_highlighting::highlight(&*self.imp.db, file_id)
351 Ok(ra_editor::highlight(&file))
352 } 351 }
353 pub fn completions(&self, position: FilePosition) -> Cancelable<Option<Vec<CompletionItem>>> { 352 pub fn completions(&self, position: FilePosition) -> Cancelable<Option<Vec<CompletionItem>>> {
354 self.imp.completions(position) 353 self.imp.completions(position)
355 } 354 }
356 pub fn assists(&self, file_id: FileId, range: TextRange) -> Cancelable<Vec<SourceChange>> { 355 pub fn assists(&self, frange: FileRange) -> Cancelable<Vec<SourceChange>> {
357 Ok(self.imp.assists(file_id, range)) 356 Ok(self.imp.assists(frange))
358 } 357 }
359 pub fn diagnostics(&self, file_id: FileId) -> Cancelable<Vec<Diagnostic>> { 358 pub fn diagnostics(&self, file_id: FileId) -> Cancelable<Vec<Diagnostic>> {
360 self.imp.diagnostics(file_id) 359 self.imp.diagnostics(file_id)
@@ -365,8 +364,8 @@ impl Analysis {
365 ) -> Cancelable<Option<(FnSignatureInfo, Option<usize>)>> { 364 ) -> Cancelable<Option<(FnSignatureInfo, Option<usize>)>> {
366 self.imp.resolve_callable(position) 365 self.imp.resolve_callable(position)
367 } 366 }
368 pub fn type_of(&self, file_id: FileId, range: TextRange) -> Cancelable<Option<String>> { 367 pub fn type_of(&self, frange: FileRange) -> Cancelable<Option<String>> {
369 self.imp.type_of(file_id, range) 368 self.imp.type_of(frange)
370 } 369 }
371} 370}
372 371
diff --git a/crates/ra_analysis/src/macros.rs b/crates/ra_analysis/src/macros.rs
new file mode 100644
index 000000000..b9feb7fad
--- /dev/null
+++ b/crates/ra_analysis/src/macros.rs
@@ -0,0 +1,75 @@
1/// Begining of macro expansion.
2///
3/// This code should be moved out of ra_analysis into hir (?) ideally.
4use ra_syntax::{ast, AstNode, SourceFileNode, TextRange};
5
6use crate::{db::RootDatabase, FileId};
7
8pub(crate) fn expand(
9 _db: &RootDatabase,
10 _file_id: FileId,
11 macro_call: ast::MacroCall,
12) -> Option<MacroExpansion> {
13 let path = macro_call.path()?;
14 if path.qualifier().is_some() {
15 return None;
16 }
17 let name_ref = path.segment()?.name_ref()?;
18 if name_ref.text() != "ctry" {
19 return None;
20 }
21
22 let arg = macro_call.token_tree()?;
23 let text = format!(
24 r"
25 fn dummy() {{
26 match {} {{
27 None => return Ok(None),
28 Some(it) => it,
29 }}
30 }}",
31 arg.syntax().text()
32 );
33 let file = SourceFileNode::parse(&text);
34 let match_expr = file.syntax().descendants().find_map(ast::MatchExpr::cast)?;
35 let match_arg = match_expr.expr()?;
36 let ranges_map = vec![(arg.syntax().range(), match_arg.syntax().range())];
37 let res = MacroExpansion {
38 source_file: file,
39 ranges_map,
40 };
41 Some(res)
42}
43
44pub(crate) struct MacroExpansion {
45 pub(crate) source_file: SourceFileNode,
46 pub(crate) ranges_map: Vec<(TextRange, TextRange)>,
47}
48
49impl MacroExpansion {
50 pub(crate) fn source_file(&self) -> &SourceFileNode {
51 &self.source_file
52 }
53 pub(crate) fn map_range_back(&self, tgt_range: TextRange) -> Option<TextRange> {
54 for (s_range, t_range) in self.ranges_map.iter() {
55 if tgt_range.is_subrange(&t_range) {
56 let tgt_at_zero_range = tgt_range - tgt_range.start();
57 let tgt_range_offset = tgt_range.start() - t_range.start();
58 let src_range = tgt_at_zero_range + tgt_range_offset + s_range.start();
59 return Some(src_range);
60 }
61 }
62 None
63 }
64 pub(crate) fn map_range_forward(&self, src_range: TextRange) -> Option<TextRange> {
65 for (s_range, t_range) in self.ranges_map.iter() {
66 if src_range.is_subrange(&s_range) {
67 let src_at_zero_range = src_range - src_range.start();
68 let src_range_offset = src_range.start() - s_range.start();
69 let src_range = src_at_zero_range + src_range_offset + t_range.start();
70 return Some(src_range);
71 }
72 }
73 None
74 }
75}
diff --git a/crates/ra_analysis/src/mock_analysis.rs b/crates/ra_analysis/src/mock_analysis.rs
index 5ce2aa2b4..960529404 100644
--- a/crates/ra_analysis/src/mock_analysis.rs
+++ b/crates/ra_analysis/src/mock_analysis.rs
@@ -1,10 +1,10 @@
1use std::sync::Arc; 1use std::sync::Arc;
2 2
3use relative_path::RelativePathBuf; 3use relative_path::RelativePathBuf;
4use test_utils::{extract_offset, parse_fixture, CURSOR_MARKER}; 4use test_utils::{extract_offset, extract_range, parse_fixture, CURSOR_MARKER};
5use ra_db::mock::FileMap; 5use ra_db::mock::FileMap;
6 6
7use crate::{Analysis, AnalysisChange, AnalysisHost, FileId, FilePosition, SourceRootId}; 7use crate::{Analysis, AnalysisChange, AnalysisHost, FileId, FilePosition, FileRange, SourceRootId};
8 8
9/// Mock analysis is used in test to bootstrap an AnalysisHost/Analysis 9/// Mock analysis is used in test to bootstrap an AnalysisHost/Analysis
10/// from a set of in-memory files. 10/// from a set of in-memory files.
@@ -66,6 +66,12 @@ impl MockAnalysis {
66 self.files.push((path.to_string(), text.to_string())); 66 self.files.push((path.to_string(), text.to_string()));
67 FilePosition { file_id, offset } 67 FilePosition { file_id, offset }
68 } 68 }
69 pub fn add_file_with_range(&mut self, path: &str, text: &str) -> FileRange {
70 let (range, text) = extract_range(text);
71 let file_id = FileId((self.files.len() + 1) as u32);
72 self.files.push((path.to_string(), text.to_string()));
73 FileRange { file_id, range }
74 }
69 pub fn id_of(&self, path: &str) -> FileId { 75 pub fn id_of(&self, path: &str) -> FileId {
70 let (idx, _) = self 76 let (idx, _) = self
71 .files 77 .files
@@ -115,3 +121,10 @@ pub fn single_file_with_position(code: &str) -> (Analysis, FilePosition) {
115 let pos = mock.add_file_with_position("/main.rs", code); 121 let pos = mock.add_file_with_position("/main.rs", code);
116 (mock.analysis(), pos) 122 (mock.analysis(), pos)
117} 123}
124
125/// Creates analysis for a single file, returns range marked with a pair of <|>.
126pub fn single_file_with_range(code: &str) -> (Analysis, FileRange) {
127 let mut mock = MockAnalysis::new();
128 let pos = mock.add_file_with_range("/main.rs", code);
129 (mock.analysis(), pos)
130}
diff --git a/crates/ra_analysis/src/syntax_highlighting.rs b/crates/ra_analysis/src/syntax_highlighting.rs
new file mode 100644
index 000000000..38219da71
--- /dev/null
+++ b/crates/ra_analysis/src/syntax_highlighting.rs
@@ -0,0 +1,63 @@
1use ra_syntax::{ast, AstNode,};
2use ra_editor::HighlightedRange;
3use ra_db::SyntaxDatabase;
4
5use crate::{
6 db::RootDatabase,
7 FileId, Cancelable,
8};
9
10pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Cancelable<Vec<HighlightedRange>> {
11 let source_file = db.source_file(file_id);
12 let mut res = ra_editor::highlight(&source_file);
13 for macro_call in source_file
14 .syntax()
15 .descendants()
16 .filter_map(ast::MacroCall::cast)
17 {
18 if let Some(exp) = crate::macros::expand(db, file_id, macro_call) {
19 let mapped_ranges = ra_editor::highlight(exp.source_file())
20 .into_iter()
21 .filter_map(|r| {
22 let mapped_range = exp.map_range_back(r.range)?;
23 let res = HighlightedRange {
24 range: mapped_range,
25 tag: r.tag,
26 };
27 Some(res)
28 });
29 res.extend(mapped_ranges);
30 }
31 }
32 Ok(res)
33}
34
35#[cfg(test)]
36mod tests {
37 use crate::mock_analysis::single_file;
38 use test_utils::assert_eq_dbg;
39
40 #[test]
41 fn highlights_code_inside_macros() {
42 let (analysis, file_id) = single_file(
43 "
44 fn main() {
45 ctry!({ let x = 92; x});
46 }
47 ",
48 );
49 let highlights = analysis.highlight(file_id).unwrap();
50 assert_eq_dbg(
51 r#"[HighlightedRange { range: [13; 15), tag: "keyword" },
52 HighlightedRange { range: [16; 20), tag: "function" },
53 HighlightedRange { range: [41; 46), tag: "macro" },
54 HighlightedRange { range: [49; 52), tag: "keyword" },
55 HighlightedRange { range: [57; 59), tag: "literal" },
56 HighlightedRange { range: [49; 52), tag: "keyword" },
57 HighlightedRange { range: [53; 54), tag: "function" },
58 HighlightedRange { range: [57; 59), tag: "literal" },
59 HighlightedRange { range: [61; 62), tag: "text" }]"#,
60 &highlights,
61 )
62 }
63}