diff options
Diffstat (limited to 'crates/ra_analysis')
-rw-r--r-- | crates/ra_analysis/Cargo.toml | 4 | ||||
-rw-r--r-- | crates/ra_analysis/src/completion/complete_dot.rs | 17 | ||||
-rw-r--r-- | crates/ra_analysis/src/completion/complete_keyword.rs | 82 | ||||
-rw-r--r-- | crates/ra_analysis/src/completion/complete_path.rs | 51 | ||||
-rw-r--r-- | crates/ra_analysis/src/completion/complete_scope.rs | 41 | ||||
-rw-r--r-- | crates/ra_analysis/src/completion/complete_snippet.rs | 2 | ||||
-rw-r--r-- | crates/ra_analysis/src/completion/completion_context.rs | 37 | ||||
-rw-r--r-- | crates/ra_analysis/src/completion/completion_item.rs | 30 | ||||
-rw-r--r-- | crates/ra_analysis/src/db.rs | 1 | ||||
-rw-r--r-- | crates/ra_analysis/src/extend_selection.rs | 51 | ||||
-rw-r--r-- | crates/ra_analysis/src/imp.rs | 86 | ||||
-rw-r--r-- | crates/ra_analysis/src/lib.rs | 37 | ||||
-rw-r--r-- | crates/ra_analysis/src/macros.rs | 75 | ||||
-rw-r--r-- | crates/ra_analysis/src/mock_analysis.rs | 17 | ||||
-rw-r--r-- | crates/ra_analysis/src/syntax_highlighting.rs | 63 |
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" | |||
9 | relative-path = "0.4.0" | 9 | relative-path = "0.4.0" |
10 | rayon = "1.0.2" | 10 | rayon = "1.0.2" |
11 | fst = "0.3.1" | 11 | fst = "0.3.1" |
12 | salsa = "0.8.0" | 12 | salsa = "0.9.0" |
13 | rustc-hash = "1.0" | 13 | rustc-hash = "1.0" |
14 | parking_lot = "0.6.4" | 14 | parking_lot = "0.7.0" |
15 | ra_syntax = { path = "../ra_syntax" } | 15 | ra_syntax = { path = "../ra_syntax" } |
16 | ra_editor = { path = "../ra_editor" } | 16 | ra_editor = { path = "../ra_editor" } |
17 | ra_text_edit = { path = "../ra_text_edit" } | 17 | ra_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). |
8 | pub(super) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) -> Cancelable<()> { | 8 | pub(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 | ||
41 | fn is_in_loop_body(leaf: SyntaxNodeRef) -> bool { | 46 | fn 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 | ||
60 | fn complete_return(fn_def: ast::FnDef, is_stmt: bool) -> Option<CompletionItem> { | 65 | fn 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 @@ | |||
1 | use crate::{ | 1 | use crate::{ |
2 | Cancelable, | 2 | Cancelable, |
3 | completion::{CompletionItem, Completions, CompletionKind, CompletionContext}, | 3 | completion::{CompletionItem, CompletionItemKind, Completions, CompletionKind, CompletionContext}, |
4 | }; | 4 | }; |
5 | 5 | ||
6 | pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) -> Cancelable<()> { | 6 | pub(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 | ||
9 | pub(super) fn complete_expr_snippet(acc: &mut Completions, ctx: &CompletionContext) { | 9 | pub(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 @@ | |||
1 | use crate::db; | ||
2 | |||
3 | use hir::PerNs; | 1 | use hir::PerNs; |
4 | 2 | ||
3 | use 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 | ||
150 | impl Into<CompletionItem> for Builder { | 166 | impl 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 @@ | |||
1 | use ra_db::SyntaxDatabase; | ||
2 | use ra_syntax::{ | ||
3 | SyntaxNodeRef, AstNode, | ||
4 | ast, algo::find_covering_node, | ||
5 | }; | ||
6 | |||
7 | use crate::{ | ||
8 | TextRange, FileRange, | ||
9 | db::RootDatabase, | ||
10 | }; | ||
11 | |||
12 | pub(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 | |||
28 | fn 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)] | ||
35 | mod 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::*; | |||
7 | use salsa::{Database, ParallelDatabase}; | 7 | use salsa::{Database, ParallelDatabase}; |
8 | 8 | ||
9 | use hir::{ | 9 | use hir::{ |
10 | self, | 10 | self, FnSignatureInfo, Problem, source_binder, |
11 | FnSignatureInfo, | ||
12 | Problem, | ||
13 | source_binder, | ||
14 | }; | 11 | }; |
15 | use ra_db::{FilesDatabase, SourceRoot, SourceRootId, SyntaxDatabase}; | 12 | use ra_db::{FilesDatabase, SourceRoot, SourceRootId, SyntaxDatabase}; |
16 | use ra_editor::{self, FileSymbol, find_node_at_offset, LineIndex, LocalEdit, Severity}; | 13 | use 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 | ||
148 | impl AnalysisImpl { | 142 | impl 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; | |||
16 | mod symbol_index; | 16 | mod symbol_index; |
17 | pub mod mock_analysis; | 17 | pub mod mock_analysis; |
18 | 18 | ||
19 | mod extend_selection; | ||
20 | mod syntax_highlighting; | ||
21 | mod macros; | ||
22 | |||
19 | use std::{fmt, sync::Arc}; | 23 | use std::{fmt, sync::Arc}; |
20 | 24 | ||
21 | use rustc_hash::FxHashMap; | 25 | use rustc_hash::FxHashMap; |
@@ -37,7 +41,7 @@ pub use ra_editor::{ | |||
37 | pub use hir::FnSignatureInfo; | 41 | pub use hir::FnSignatureInfo; |
38 | 42 | ||
39 | pub use ra_db::{ | 43 | pub 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 | ||
272 | impl Analysis { | 276 | impl 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. | ||
4 | use ra_syntax::{ast, AstNode, SourceFileNode, TextRange}; | ||
5 | |||
6 | use crate::{db::RootDatabase, FileId}; | ||
7 | |||
8 | pub(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 | |||
44 | pub(crate) struct MacroExpansion { | ||
45 | pub(crate) source_file: SourceFileNode, | ||
46 | pub(crate) ranges_map: Vec<(TextRange, TextRange)>, | ||
47 | } | ||
48 | |||
49 | impl 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 @@ | |||
1 | use std::sync::Arc; | 1 | use std::sync::Arc; |
2 | 2 | ||
3 | use relative_path::RelativePathBuf; | 3 | use relative_path::RelativePathBuf; |
4 | use test_utils::{extract_offset, parse_fixture, CURSOR_MARKER}; | 4 | use test_utils::{extract_offset, extract_range, parse_fixture, CURSOR_MARKER}; |
5 | use ra_db::mock::FileMap; | 5 | use ra_db::mock::FileMap; |
6 | 6 | ||
7 | use crate::{Analysis, AnalysisChange, AnalysisHost, FileId, FilePosition, SourceRootId}; | 7 | use 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 <|>. | ||
126 | pub 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 @@ | |||
1 | use ra_syntax::{ast, AstNode,}; | ||
2 | use ra_editor::HighlightedRange; | ||
3 | use ra_db::SyntaxDatabase; | ||
4 | |||
5 | use crate::{ | ||
6 | db::RootDatabase, | ||
7 | FileId, Cancelable, | ||
8 | }; | ||
9 | |||
10 | pub(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)] | ||
36 | mod 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 | } | ||