diff options
Diffstat (limited to 'crates/ra_ide')
27 files changed, 2097 insertions, 867 deletions
diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs index a721e23c6..e1fcf379d 100644 --- a/crates/ra_ide/src/completion.rs +++ b/crates/ra_ide/src/completion.rs | |||
@@ -15,6 +15,7 @@ mod complete_unqualified_path; | |||
15 | mod complete_postfix; | 15 | mod complete_postfix; |
16 | mod complete_macro_in_item_position; | 16 | mod complete_macro_in_item_position; |
17 | mod complete_trait_impl; | 17 | mod complete_trait_impl; |
18 | mod patterns; | ||
18 | #[cfg(test)] | 19 | #[cfg(test)] |
19 | mod test_utils; | 20 | mod test_utils; |
20 | 21 | ||
diff --git a/crates/ra_ide/src/completion/complete_keyword.rs b/crates/ra_ide/src/completion/complete_keyword.rs index fd95bc410..b2f621a11 100644 --- a/crates/ra_ide/src/completion/complete_keyword.rs +++ b/crates/ra_ide/src/completion/complete_keyword.rs | |||
@@ -1,11 +1,6 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! FIXME: write short doc here |
2 | 2 | ||
3 | use ra_syntax::{ | 3 | use ra_syntax::ast; |
4 | ast::{self, LoopBodyOwner}, | ||
5 | match_ast, AstNode, | ||
6 | SyntaxKind::*, | ||
7 | SyntaxToken, | ||
8 | }; | ||
9 | 4 | ||
10 | use crate::completion::{ | 5 | use crate::completion::{ |
11 | CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions, | 6 | CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions, |
@@ -41,68 +36,122 @@ pub(super) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionC | |||
41 | } | 36 | } |
42 | } | 37 | } |
43 | 38 | ||
44 | fn keyword(ctx: &CompletionContext, kw: &str, snippet: &str) -> CompletionItem { | 39 | pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) { |
45 | let res = CompletionItem::new(CompletionKind::Keyword, ctx.source_range(), kw) | 40 | let has_trait_or_impl_parent = ctx.has_impl_parent || ctx.has_trait_parent; |
46 | .kind(CompletionItemKind::Keyword); | 41 | if ctx.trait_as_prev_sibling || ctx.impl_as_prev_sibling { |
47 | 42 | add_keyword(ctx, acc, "where", "where "); | |
48 | match ctx.config.snippet_cap { | 43 | return; |
49 | Some(cap) => res.insert_snippet(cap, snippet), | ||
50 | _ => res.insert_text(if snippet.contains('$') { kw } else { snippet }), | ||
51 | } | 44 | } |
52 | .build() | 45 | if ctx.unsafe_is_prev { |
53 | } | 46 | if ctx.has_item_list_or_source_file_parent || ctx.block_expr_parent { |
47 | add_keyword(ctx, acc, "fn", "fn $0() {}") | ||
48 | } | ||
49 | |||
50 | if (ctx.has_item_list_or_source_file_parent && !has_trait_or_impl_parent) | ||
51 | || ctx.block_expr_parent | ||
52 | { | ||
53 | add_keyword(ctx, acc, "trait", "trait $0 {}"); | ||
54 | add_keyword(ctx, acc, "impl", "impl $0 {}"); | ||
55 | } | ||
54 | 56 | ||
55 | pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) { | ||
56 | if !ctx.is_trivial_path { | ||
57 | return; | 57 | return; |
58 | } | 58 | } |
59 | if ctx.has_item_list_or_source_file_parent || ctx.block_expr_parent { | ||
60 | add_keyword(ctx, acc, "fn", "fn $0() {}"); | ||
61 | } | ||
62 | if (ctx.has_item_list_or_source_file_parent && !has_trait_or_impl_parent) | ||
63 | || ctx.block_expr_parent | ||
64 | { | ||
65 | add_keyword(ctx, acc, "use", "use "); | ||
66 | add_keyword(ctx, acc, "impl", "impl $0 {}"); | ||
67 | add_keyword(ctx, acc, "trait", "trait $0 {}"); | ||
68 | } | ||
59 | 69 | ||
60 | let fn_def = match &ctx.function_syntax { | 70 | if ctx.has_item_list_or_source_file_parent && !has_trait_or_impl_parent { |
61 | Some(it) => it, | 71 | add_keyword(ctx, acc, "enum", "enum $0 {}"); |
62 | None => return, | 72 | add_keyword(ctx, acc, "struct", "struct $0 {}"); |
63 | }; | 73 | add_keyword(ctx, acc, "union", "union $0 {}"); |
64 | acc.add(keyword(ctx, "if", "if $0 {}")); | 74 | } |
65 | acc.add(keyword(ctx, "match", "match $0 {}")); | ||
66 | acc.add(keyword(ctx, "while", "while $0 {}")); | ||
67 | acc.add(keyword(ctx, "loop", "loop {$0}")); | ||
68 | 75 | ||
76 | if ctx.block_expr_parent || ctx.is_match_arm { | ||
77 | add_keyword(ctx, acc, "match", "match $0 {}"); | ||
78 | add_keyword(ctx, acc, "loop", "loop {$0}"); | ||
79 | } | ||
80 | if ctx.block_expr_parent { | ||
81 | add_keyword(ctx, acc, "while", "while $0 {}"); | ||
82 | } | ||
83 | if ctx.if_is_prev || ctx.block_expr_parent { | ||
84 | add_keyword(ctx, acc, "let", "let "); | ||
85 | } | ||
86 | if ctx.if_is_prev || ctx.block_expr_parent || ctx.is_match_arm { | ||
87 | add_keyword(ctx, acc, "if", "if "); | ||
88 | add_keyword(ctx, acc, "if let", "if let "); | ||
89 | } | ||
69 | if ctx.after_if { | 90 | if ctx.after_if { |
70 | acc.add(keyword(ctx, "else", "else {$0}")); | 91 | add_keyword(ctx, acc, "else", "else {$0}"); |
71 | acc.add(keyword(ctx, "else if", "else if $0 {}")); | 92 | add_keyword(ctx, acc, "else if", "else if $0 {}"); |
93 | } | ||
94 | if (ctx.has_item_list_or_source_file_parent && !has_trait_or_impl_parent) | ||
95 | || ctx.block_expr_parent | ||
96 | { | ||
97 | add_keyword(ctx, acc, "mod", "mod $0 {}"); | ||
98 | } | ||
99 | if ctx.bind_pat_parent || ctx.ref_pat_parent { | ||
100 | add_keyword(ctx, acc, "mut", "mut "); | ||
72 | } | 101 | } |
73 | if is_in_loop_body(&ctx.token) { | 102 | if ctx.has_item_list_or_source_file_parent || ctx.block_expr_parent { |
103 | add_keyword(ctx, acc, "const", "const "); | ||
104 | add_keyword(ctx, acc, "type", "type "); | ||
105 | } | ||
106 | if (ctx.has_item_list_or_source_file_parent && !has_trait_or_impl_parent) | ||
107 | || ctx.block_expr_parent | ||
108 | { | ||
109 | add_keyword(ctx, acc, "static", "static "); | ||
110 | }; | ||
111 | if (ctx.has_item_list_or_source_file_parent && !has_trait_or_impl_parent) | ||
112 | || ctx.block_expr_parent | ||
113 | { | ||
114 | add_keyword(ctx, acc, "extern", "extern "); | ||
115 | } | ||
116 | if ctx.has_item_list_or_source_file_parent || ctx.block_expr_parent || ctx.is_match_arm { | ||
117 | add_keyword(ctx, acc, "unsafe", "unsafe "); | ||
118 | } | ||
119 | if ctx.in_loop_body { | ||
74 | if ctx.can_be_stmt { | 120 | if ctx.can_be_stmt { |
75 | acc.add(keyword(ctx, "continue", "continue;")); | 121 | add_keyword(ctx, acc, "continue", "continue;"); |
76 | acc.add(keyword(ctx, "break", "break;")); | 122 | add_keyword(ctx, acc, "break", "break;"); |
77 | } else { | 123 | } else { |
78 | acc.add(keyword(ctx, "continue", "continue")); | 124 | add_keyword(ctx, acc, "continue", "continue"); |
79 | acc.add(keyword(ctx, "break", "break")); | 125 | add_keyword(ctx, acc, "break", "break"); |
80 | } | 126 | } |
81 | } | 127 | } |
128 | if ctx.has_item_list_or_source_file_parent && !ctx.has_trait_parent { | ||
129 | add_keyword(ctx, acc, "pub", "pub ") | ||
130 | } | ||
131 | |||
132 | if !ctx.is_trivial_path { | ||
133 | return; | ||
134 | } | ||
135 | let fn_def = match &ctx.function_syntax { | ||
136 | Some(it) => it, | ||
137 | None => return, | ||
138 | }; | ||
82 | acc.add_all(complete_return(ctx, &fn_def, ctx.can_be_stmt)); | 139 | acc.add_all(complete_return(ctx, &fn_def, ctx.can_be_stmt)); |
83 | } | 140 | } |
84 | 141 | ||
85 | fn is_in_loop_body(leaf: &SyntaxToken) -> bool { | 142 | fn keyword(ctx: &CompletionContext, kw: &str, snippet: &str) -> CompletionItem { |
86 | // FIXME move this to CompletionContext and make it handle macros | 143 | let res = CompletionItem::new(CompletionKind::Keyword, ctx.source_range(), kw) |
87 | for node in leaf.parent().ancestors() { | 144 | .kind(CompletionItemKind::Keyword); |
88 | if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR { | 145 | |
89 | break; | 146 | match ctx.config.snippet_cap { |
90 | } | 147 | Some(cap) => res.insert_snippet(cap, snippet), |
91 | let loop_body = match_ast! { | 148 | _ => res.insert_text(if snippet.contains('$') { kw } else { snippet }), |
92 | match node { | ||
93 | ast::ForExpr(it) => it.loop_body(), | ||
94 | ast::WhileExpr(it) => it.loop_body(), | ||
95 | ast::LoopExpr(it) => it.loop_body(), | ||
96 | _ => None, | ||
97 | } | ||
98 | }; | ||
99 | if let Some(body) = loop_body { | ||
100 | if body.syntax().text_range().contains_range(leaf.text_range()) { | ||
101 | return true; | ||
102 | } | ||
103 | } | ||
104 | } | 149 | } |
105 | false | 150 | .build() |
151 | } | ||
152 | |||
153 | fn add_keyword(ctx: &CompletionContext, acc: &mut Completions, kw: &str, snippet: &str) { | ||
154 | acc.add(keyword(ctx, kw, snippet)); | ||
106 | } | 155 | } |
107 | 156 | ||
108 | fn complete_return( | 157 | fn complete_return( |
@@ -121,327 +170,156 @@ fn complete_return( | |||
121 | 170 | ||
122 | #[cfg(test)] | 171 | #[cfg(test)] |
123 | mod tests { | 172 | mod tests { |
124 | use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; | 173 | use crate::completion::{test_utils::completion_list, CompletionKind}; |
125 | use insta::assert_debug_snapshot; | 174 | use insta::assert_snapshot; |
126 | 175 | ||
127 | fn do_keyword_completion(code: &str) -> Vec<CompletionItem> { | 176 | fn get_keyword_completions(code: &str) -> String { |
128 | do_completion(code, CompletionKind::Keyword) | 177 | completion_list(code, CompletionKind::Keyword) |
129 | } | 178 | } |
130 | 179 | ||
131 | #[test] | 180 | #[test] |
132 | fn completes_keywords_in_use_stmt() { | 181 | fn test_keywords_in_use_stmt() { |
133 | assert_debug_snapshot!( | 182 | assert_snapshot!( |
134 | do_keyword_completion( | 183 | get_keyword_completions(r"use <|>"), |
135 | r" | ||
136 | use <|> | ||
137 | ", | ||
138 | ), | ||
139 | @r###" | 184 | @r###" |
140 | [ | 185 | kw crate |
141 | CompletionItem { | 186 | kw self |
142 | label: "crate", | 187 | kw super |
143 | source_range: 21..21, | ||
144 | delete: 21..21, | ||
145 | insert: "crate::", | ||
146 | kind: Keyword, | ||
147 | }, | ||
148 | CompletionItem { | ||
149 | label: "self", | ||
150 | source_range: 21..21, | ||
151 | delete: 21..21, | ||
152 | insert: "self", | ||
153 | kind: Keyword, | ||
154 | }, | ||
155 | CompletionItem { | ||
156 | label: "super", | ||
157 | source_range: 21..21, | ||
158 | delete: 21..21, | ||
159 | insert: "super::", | ||
160 | kind: Keyword, | ||
161 | }, | ||
162 | ] | ||
163 | "### | 188 | "### |
164 | ); | 189 | ); |
165 | 190 | ||
166 | assert_debug_snapshot!( | 191 | assert_snapshot!( |
167 | do_keyword_completion( | 192 | get_keyword_completions(r"use a::<|>"), |
168 | r" | ||
169 | use a::<|> | ||
170 | ", | ||
171 | ), | ||
172 | @r###" | 193 | @r###" |
173 | [ | 194 | kw self |
174 | CompletionItem { | 195 | kw super |
175 | label: "self", | ||
176 | source_range: 24..24, | ||
177 | delete: 24..24, | ||
178 | insert: "self", | ||
179 | kind: Keyword, | ||
180 | }, | ||
181 | CompletionItem { | ||
182 | label: "super", | ||
183 | source_range: 24..24, | ||
184 | delete: 24..24, | ||
185 | insert: "super::", | ||
186 | kind: Keyword, | ||
187 | }, | ||
188 | ] | ||
189 | "### | 196 | "### |
190 | ); | 197 | ); |
191 | 198 | ||
192 | assert_debug_snapshot!( | 199 | assert_snapshot!( |
193 | do_keyword_completion( | 200 | get_keyword_completions(r"use a::{b, <|>}"), |
194 | r" | ||
195 | use a::{b, <|>} | ||
196 | ", | ||
197 | ), | ||
198 | @r###" | 201 | @r###" |
199 | [ | 202 | kw self |
200 | CompletionItem { | 203 | kw super |
201 | label: "self", | ||
202 | source_range: 28..28, | ||
203 | delete: 28..28, | ||
204 | insert: "self", | ||
205 | kind: Keyword, | ||
206 | }, | ||
207 | CompletionItem { | ||
208 | label: "super", | ||
209 | source_range: 28..28, | ||
210 | delete: 28..28, | ||
211 | insert: "super::", | ||
212 | kind: Keyword, | ||
213 | }, | ||
214 | ] | ||
215 | "### | 204 | "### |
216 | ); | 205 | ); |
217 | } | 206 | } |
218 | 207 | ||
219 | #[test] | 208 | #[test] |
220 | fn completes_various_keywords_in_function() { | 209 | fn test_keywords_at_source_file_level() { |
221 | assert_debug_snapshot!( | 210 | assert_snapshot!( |
222 | do_keyword_completion( | 211 | get_keyword_completions(r"m<|>"), |
223 | r" | ||
224 | fn quux() { | ||
225 | <|> | ||
226 | } | ||
227 | ", | ||
228 | ), | ||
229 | @r###" | 212 | @r###" |
230 | [ | 213 | kw const |
231 | CompletionItem { | 214 | kw enum |
232 | label: "if", | 215 | kw extern |
233 | source_range: 49..49, | 216 | kw fn |
234 | delete: 49..49, | 217 | kw impl |
235 | insert: "if $0 {}", | 218 | kw mod |
236 | kind: Keyword, | 219 | kw pub |
237 | }, | 220 | kw static |
238 | CompletionItem { | 221 | kw struct |
239 | label: "loop", | 222 | kw trait |
240 | source_range: 49..49, | 223 | kw type |
241 | delete: 49..49, | 224 | kw union |
242 | insert: "loop {$0}", | 225 | kw unsafe |
243 | kind: Keyword, | 226 | kw use |
244 | }, | ||
245 | CompletionItem { | ||
246 | label: "match", | ||
247 | source_range: 49..49, | ||
248 | delete: 49..49, | ||
249 | insert: "match $0 {}", | ||
250 | kind: Keyword, | ||
251 | }, | ||
252 | CompletionItem { | ||
253 | label: "return", | ||
254 | source_range: 49..49, | ||
255 | delete: 49..49, | ||
256 | insert: "return;", | ||
257 | kind: Keyword, | ||
258 | }, | ||
259 | CompletionItem { | ||
260 | label: "while", | ||
261 | source_range: 49..49, | ||
262 | delete: 49..49, | ||
263 | insert: "while $0 {}", | ||
264 | kind: Keyword, | ||
265 | }, | ||
266 | ] | ||
267 | "### | 227 | "### |
268 | ); | 228 | ); |
269 | } | 229 | } |
270 | 230 | ||
271 | #[test] | 231 | #[test] |
272 | fn completes_else_after_if() { | 232 | fn test_keywords_in_function() { |
273 | assert_debug_snapshot!( | 233 | assert_snapshot!( |
274 | do_keyword_completion( | 234 | get_keyword_completions(r"fn quux() { <|> }"), |
275 | r" | ||
276 | fn quux() { | ||
277 | if true { | ||
278 | () | ||
279 | } <|> | ||
280 | } | ||
281 | ", | ||
282 | ), | ||
283 | @r###" | 235 | @r###" |
284 | [ | 236 | kw const |
285 | CompletionItem { | 237 | kw extern |
286 | label: "else", | 238 | kw fn |
287 | source_range: 108..108, | 239 | kw if |
288 | delete: 108..108, | 240 | kw if let |
289 | insert: "else {$0}", | 241 | kw impl |
290 | kind: Keyword, | 242 | kw let |
291 | }, | 243 | kw loop |
292 | CompletionItem { | 244 | kw match |
293 | label: "else if", | 245 | kw mod |
294 | source_range: 108..108, | 246 | kw return |
295 | delete: 108..108, | 247 | kw static |
296 | insert: "else if $0 {}", | 248 | kw trait |
297 | kind: Keyword, | 249 | kw type |
298 | }, | 250 | kw unsafe |
299 | CompletionItem { | 251 | kw use |
300 | label: "if", | 252 | kw while |
301 | source_range: 108..108, | ||
302 | delete: 108..108, | ||
303 | insert: "if $0 {}", | ||
304 | kind: Keyword, | ||
305 | }, | ||
306 | CompletionItem { | ||
307 | label: "loop", | ||
308 | source_range: 108..108, | ||
309 | delete: 108..108, | ||
310 | insert: "loop {$0}", | ||
311 | kind: Keyword, | ||
312 | }, | ||
313 | CompletionItem { | ||
314 | label: "match", | ||
315 | source_range: 108..108, | ||
316 | delete: 108..108, | ||
317 | insert: "match $0 {}", | ||
318 | kind: Keyword, | ||
319 | }, | ||
320 | CompletionItem { | ||
321 | label: "return", | ||
322 | source_range: 108..108, | ||
323 | delete: 108..108, | ||
324 | insert: "return;", | ||
325 | kind: Keyword, | ||
326 | }, | ||
327 | CompletionItem { | ||
328 | label: "while", | ||
329 | source_range: 108..108, | ||
330 | delete: 108..108, | ||
331 | insert: "while $0 {}", | ||
332 | kind: Keyword, | ||
333 | }, | ||
334 | ] | ||
335 | "### | 253 | "### |
336 | ); | 254 | ); |
337 | } | 255 | } |
338 | 256 | ||
339 | #[test] | 257 | #[test] |
340 | fn test_completion_return_value() { | 258 | fn test_keywords_inside_block() { |
341 | assert_debug_snapshot!( | 259 | assert_snapshot!( |
342 | do_keyword_completion( | 260 | get_keyword_completions(r"fn quux() { if true { <|> } }"), |
343 | r" | ||
344 | fn quux() -> i32 { | ||
345 | <|> | ||
346 | 92 | ||
347 | } | ||
348 | ", | ||
349 | ), | ||
350 | @r###" | 261 | @r###" |
351 | [ | 262 | kw const |
352 | CompletionItem { | 263 | kw extern |
353 | label: "if", | 264 | kw fn |
354 | source_range: 56..56, | 265 | kw if |
355 | delete: 56..56, | 266 | kw if let |
356 | insert: "if $0 {}", | 267 | kw impl |
357 | kind: Keyword, | 268 | kw let |
358 | }, | 269 | kw loop |
359 | CompletionItem { | 270 | kw match |
360 | label: "loop", | 271 | kw mod |
361 | source_range: 56..56, | 272 | kw return |
362 | delete: 56..56, | 273 | kw static |
363 | insert: "loop {$0}", | 274 | kw trait |
364 | kind: Keyword, | 275 | kw type |
365 | }, | 276 | kw unsafe |
366 | CompletionItem { | 277 | kw use |
367 | label: "match", | 278 | kw while |
368 | source_range: 56..56, | ||
369 | delete: 56..56, | ||
370 | insert: "match $0 {}", | ||
371 | kind: Keyword, | ||
372 | }, | ||
373 | CompletionItem { | ||
374 | label: "return", | ||
375 | source_range: 56..56, | ||
376 | delete: 56..56, | ||
377 | insert: "return $0;", | ||
378 | kind: Keyword, | ||
379 | }, | ||
380 | CompletionItem { | ||
381 | label: "while", | ||
382 | source_range: 56..56, | ||
383 | delete: 56..56, | ||
384 | insert: "while $0 {}", | ||
385 | kind: Keyword, | ||
386 | }, | ||
387 | ] | ||
388 | "### | 279 | "### |
389 | ); | 280 | ); |
390 | assert_debug_snapshot!( | 281 | } |
391 | do_keyword_completion( | 282 | |
283 | #[test] | ||
284 | fn test_keywords_after_if() { | ||
285 | assert_snapshot!( | ||
286 | get_keyword_completions( | ||
392 | r" | 287 | r" |
393 | fn quux() { | 288 | fn quux() { |
394 | <|> | 289 | if true { |
395 | 92 | 290 | () |
291 | } <|> | ||
396 | } | 292 | } |
397 | ", | 293 | ", |
398 | ), | 294 | ), |
399 | @r###" | 295 | @r###" |
400 | [ | 296 | kw const |
401 | CompletionItem { | 297 | kw else |
402 | label: "if", | 298 | kw else if |
403 | source_range: 49..49, | 299 | kw extern |
404 | delete: 49..49, | 300 | kw fn |
405 | insert: "if $0 {}", | 301 | kw if |
406 | kind: Keyword, | 302 | kw if let |
407 | }, | 303 | kw impl |
408 | CompletionItem { | 304 | kw let |
409 | label: "loop", | 305 | kw loop |
410 | source_range: 49..49, | 306 | kw match |
411 | delete: 49..49, | 307 | kw mod |
412 | insert: "loop {$0}", | 308 | kw return |
413 | kind: Keyword, | 309 | kw static |
414 | }, | 310 | kw trait |
415 | CompletionItem { | 311 | kw type |
416 | label: "match", | 312 | kw unsafe |
417 | source_range: 49..49, | 313 | kw use |
418 | delete: 49..49, | 314 | kw while |
419 | insert: "match $0 {}", | ||
420 | kind: Keyword, | ||
421 | }, | ||
422 | CompletionItem { | ||
423 | label: "return", | ||
424 | source_range: 49..49, | ||
425 | delete: 49..49, | ||
426 | insert: "return;", | ||
427 | kind: Keyword, | ||
428 | }, | ||
429 | CompletionItem { | ||
430 | label: "while", | ||
431 | source_range: 49..49, | ||
432 | delete: 49..49, | ||
433 | insert: "while $0 {}", | ||
434 | kind: Keyword, | ||
435 | }, | ||
436 | ] | ||
437 | "### | 315 | "### |
438 | ); | 316 | ); |
439 | } | 317 | } |
440 | 318 | ||
441 | #[test] | 319 | #[test] |
442 | fn dont_add_semi_after_return_if_not_a_statement() { | 320 | fn test_keywords_in_match_arm() { |
443 | assert_debug_snapshot!( | 321 | assert_snapshot!( |
444 | do_keyword_completion( | 322 | get_keyword_completions( |
445 | r" | 323 | r" |
446 | fn quux() -> i32 { | 324 | fn quux() -> i32 { |
447 | match () { | 325 | match () { |
@@ -451,336 +329,130 @@ mod tests { | |||
451 | ", | 329 | ", |
452 | ), | 330 | ), |
453 | @r###" | 331 | @r###" |
454 | [ | 332 | kw if |
455 | CompletionItem { | 333 | kw if let |
456 | label: "if", | 334 | kw loop |
457 | source_range: 97..97, | 335 | kw match |
458 | delete: 97..97, | 336 | kw return |
459 | insert: "if $0 {}", | 337 | kw unsafe |
460 | kind: Keyword, | ||
461 | }, | ||
462 | CompletionItem { | ||
463 | label: "loop", | ||
464 | source_range: 97..97, | ||
465 | delete: 97..97, | ||
466 | insert: "loop {$0}", | ||
467 | kind: Keyword, | ||
468 | }, | ||
469 | CompletionItem { | ||
470 | label: "match", | ||
471 | source_range: 97..97, | ||
472 | delete: 97..97, | ||
473 | insert: "match $0 {}", | ||
474 | kind: Keyword, | ||
475 | }, | ||
476 | CompletionItem { | ||
477 | label: "return", | ||
478 | source_range: 97..97, | ||
479 | delete: 97..97, | ||
480 | insert: "return $0", | ||
481 | kind: Keyword, | ||
482 | }, | ||
483 | CompletionItem { | ||
484 | label: "while", | ||
485 | source_range: 97..97, | ||
486 | delete: 97..97, | ||
487 | insert: "while $0 {}", | ||
488 | kind: Keyword, | ||
489 | }, | ||
490 | ] | ||
491 | "### | 338 | "### |
492 | ); | 339 | ); |
493 | } | 340 | } |
494 | 341 | ||
495 | #[test] | 342 | #[test] |
496 | fn last_return_in_block_has_semi() { | 343 | fn test_keywords_in_trait_def() { |
497 | assert_debug_snapshot!( | 344 | assert_snapshot!( |
498 | do_keyword_completion( | 345 | get_keyword_completions(r"trait My { <|> }"), |
499 | r" | ||
500 | fn quux() -> i32 { | ||
501 | if condition { | ||
502 | <|> | ||
503 | } | ||
504 | } | ||
505 | ", | ||
506 | ), | ||
507 | @r###" | 346 | @r###" |
508 | [ | 347 | kw const |
509 | CompletionItem { | 348 | kw fn |
510 | label: "if", | 349 | kw type |
511 | source_range: 95..95, | 350 | kw unsafe |
512 | delete: 95..95, | ||
513 | insert: "if $0 {}", | ||
514 | kind: Keyword, | ||
515 | }, | ||
516 | CompletionItem { | ||
517 | label: "loop", | ||
518 | source_range: 95..95, | ||
519 | delete: 95..95, | ||
520 | insert: "loop {$0}", | ||
521 | kind: Keyword, | ||
522 | }, | ||
523 | CompletionItem { | ||
524 | label: "match", | ||
525 | source_range: 95..95, | ||
526 | delete: 95..95, | ||
527 | insert: "match $0 {}", | ||
528 | kind: Keyword, | ||
529 | }, | ||
530 | CompletionItem { | ||
531 | label: "return", | ||
532 | source_range: 95..95, | ||
533 | delete: 95..95, | ||
534 | insert: "return $0;", | ||
535 | kind: Keyword, | ||
536 | }, | ||
537 | CompletionItem { | ||
538 | label: "while", | ||
539 | source_range: 95..95, | ||
540 | delete: 95..95, | ||
541 | insert: "while $0 {}", | ||
542 | kind: Keyword, | ||
543 | }, | ||
544 | ] | ||
545 | "### | 351 | "### |
546 | ); | 352 | ); |
547 | assert_debug_snapshot!( | 353 | } |
548 | do_keyword_completion( | 354 | |
549 | r" | 355 | #[test] |
550 | fn quux() -> i32 { | 356 | fn test_keywords_in_impl_def() { |
551 | if condition { | 357 | assert_snapshot!( |
552 | <|> | 358 | get_keyword_completions(r"impl My { <|> }"), |
553 | } | ||
554 | let x = 92; | ||
555 | x | ||
556 | } | ||
557 | ", | ||
558 | ), | ||
559 | @r###" | 359 | @r###" |
560 | [ | 360 | kw const |
561 | CompletionItem { | 361 | kw fn |
562 | label: "if", | 362 | kw pub |
563 | source_range: 95..95, | 363 | kw type |
564 | delete: 95..95, | 364 | kw unsafe |
565 | insert: "if $0 {}", | ||
566 | kind: Keyword, | ||
567 | }, | ||
568 | CompletionItem { | ||
569 | label: "loop", | ||
570 | source_range: 95..95, | ||
571 | delete: 95..95, | ||
572 | insert: "loop {$0}", | ||
573 | kind: Keyword, | ||
574 | }, | ||
575 | CompletionItem { | ||
576 | label: "match", | ||
577 | source_range: 95..95, | ||
578 | delete: 95..95, | ||
579 | insert: "match $0 {}", | ||
580 | kind: Keyword, | ||
581 | }, | ||
582 | CompletionItem { | ||
583 | label: "return", | ||
584 | source_range: 95..95, | ||
585 | delete: 95..95, | ||
586 | insert: "return $0;", | ||
587 | kind: Keyword, | ||
588 | }, | ||
589 | CompletionItem { | ||
590 | label: "while", | ||
591 | source_range: 95..95, | ||
592 | delete: 95..95, | ||
593 | insert: "while $0 {}", | ||
594 | kind: Keyword, | ||
595 | }, | ||
596 | ] | ||
597 | "### | 365 | "### |
598 | ); | 366 | ); |
599 | } | 367 | } |
600 | 368 | ||
601 | #[test] | 369 | #[test] |
602 | fn completes_break_and_continue_in_loops() { | 370 | fn test_keywords_in_loop() { |
603 | assert_debug_snapshot!( | 371 | assert_snapshot!( |
604 | do_keyword_completion( | 372 | get_keyword_completions(r"fn my() { loop { <|> } }"), |
605 | r" | ||
606 | fn quux() -> i32 { | ||
607 | loop { <|> } | ||
608 | } | ||
609 | ", | ||
610 | ), | ||
611 | @r###" | 373 | @r###" |
612 | [ | 374 | kw break |
613 | CompletionItem { | 375 | kw const |
614 | label: "break", | 376 | kw continue |
615 | source_range: 63..63, | 377 | kw extern |
616 | delete: 63..63, | 378 | kw fn |
617 | insert: "break;", | 379 | kw if |
618 | kind: Keyword, | 380 | kw if let |
619 | }, | 381 | kw impl |
620 | CompletionItem { | 382 | kw let |
621 | label: "continue", | 383 | kw loop |
622 | source_range: 63..63, | 384 | kw match |
623 | delete: 63..63, | 385 | kw mod |
624 | insert: "continue;", | 386 | kw return |
625 | kind: Keyword, | 387 | kw static |
626 | }, | 388 | kw trait |
627 | CompletionItem { | 389 | kw type |
628 | label: "if", | 390 | kw unsafe |
629 | source_range: 63..63, | 391 | kw use |
630 | delete: 63..63, | 392 | kw while |
631 | insert: "if $0 {}", | ||
632 | kind: Keyword, | ||
633 | }, | ||
634 | CompletionItem { | ||
635 | label: "loop", | ||
636 | source_range: 63..63, | ||
637 | delete: 63..63, | ||
638 | insert: "loop {$0}", | ||
639 | kind: Keyword, | ||
640 | }, | ||
641 | CompletionItem { | ||
642 | label: "match", | ||
643 | source_range: 63..63, | ||
644 | delete: 63..63, | ||
645 | insert: "match $0 {}", | ||
646 | kind: Keyword, | ||
647 | }, | ||
648 | CompletionItem { | ||
649 | label: "return", | ||
650 | source_range: 63..63, | ||
651 | delete: 63..63, | ||
652 | insert: "return $0;", | ||
653 | kind: Keyword, | ||
654 | }, | ||
655 | CompletionItem { | ||
656 | label: "while", | ||
657 | source_range: 63..63, | ||
658 | delete: 63..63, | ||
659 | insert: "while $0 {}", | ||
660 | kind: Keyword, | ||
661 | }, | ||
662 | ] | ||
663 | "### | 393 | "### |
664 | ); | 394 | ); |
395 | } | ||
665 | 396 | ||
666 | // No completion: lambda isolates control flow | 397 | #[test] |
667 | assert_debug_snapshot!( | 398 | fn test_keywords_after_unsafe_in_item_list() { |
668 | do_keyword_completion( | 399 | assert_snapshot!( |
669 | r" | 400 | get_keyword_completions(r"unsafe <|>"), |
670 | fn quux() -> i32 { | ||
671 | loop { || { <|> } } | ||
672 | } | ||
673 | ", | ||
674 | ), | ||
675 | @r###" | 401 | @r###" |
676 | [ | 402 | kw fn |
677 | CompletionItem { | 403 | kw impl |
678 | label: "if", | 404 | kw trait |
679 | source_range: 68..68, | ||
680 | delete: 68..68, | ||
681 | insert: "if $0 {}", | ||
682 | kind: Keyword, | ||
683 | }, | ||
684 | CompletionItem { | ||
685 | label: "loop", | ||
686 | source_range: 68..68, | ||
687 | delete: 68..68, | ||
688 | insert: "loop {$0}", | ||
689 | kind: Keyword, | ||
690 | }, | ||
691 | CompletionItem { | ||
692 | label: "match", | ||
693 | source_range: 68..68, | ||
694 | delete: 68..68, | ||
695 | insert: "match $0 {}", | ||
696 | kind: Keyword, | ||
697 | }, | ||
698 | CompletionItem { | ||
699 | label: "return", | ||
700 | source_range: 68..68, | ||
701 | delete: 68..68, | ||
702 | insert: "return $0;", | ||
703 | kind: Keyword, | ||
704 | }, | ||
705 | CompletionItem { | ||
706 | label: "while", | ||
707 | source_range: 68..68, | ||
708 | delete: 68..68, | ||
709 | insert: "while $0 {}", | ||
710 | kind: Keyword, | ||
711 | }, | ||
712 | ] | ||
713 | "### | 405 | "### |
714 | ); | 406 | ); |
715 | } | 407 | } |
716 | 408 | ||
717 | #[test] | 409 | #[test] |
718 | fn no_semi_after_break_continue_in_expr() { | 410 | fn test_keywords_after_unsafe_in_block_expr() { |
719 | assert_debug_snapshot!( | 411 | assert_snapshot!( |
720 | do_keyword_completion( | 412 | get_keyword_completions(r"fn my_fn() { unsafe <|> }"), |
721 | r" | ||
722 | fn f() { | ||
723 | loop { | ||
724 | match () { | ||
725 | () => br<|> | ||
726 | } | ||
727 | } | ||
728 | } | ||
729 | ", | ||
730 | ), | ||
731 | @r###" | 413 | @r###" |
732 | [ | 414 | kw fn |
733 | CompletionItem { | 415 | kw impl |
734 | label: "break", | 416 | kw trait |
735 | source_range: 122..124, | ||
736 | delete: 122..124, | ||
737 | insert: "break", | ||
738 | kind: Keyword, | ||
739 | }, | ||
740 | CompletionItem { | ||
741 | label: "continue", | ||
742 | source_range: 122..124, | ||
743 | delete: 122..124, | ||
744 | insert: "continue", | ||
745 | kind: Keyword, | ||
746 | }, | ||
747 | CompletionItem { | ||
748 | label: "if", | ||
749 | source_range: 122..124, | ||
750 | delete: 122..124, | ||
751 | insert: "if $0 {}", | ||
752 | kind: Keyword, | ||
753 | }, | ||
754 | CompletionItem { | ||
755 | label: "loop", | ||
756 | source_range: 122..124, | ||
757 | delete: 122..124, | ||
758 | insert: "loop {$0}", | ||
759 | kind: Keyword, | ||
760 | }, | ||
761 | CompletionItem { | ||
762 | label: "match", | ||
763 | source_range: 122..124, | ||
764 | delete: 122..124, | ||
765 | insert: "match $0 {}", | ||
766 | kind: Keyword, | ||
767 | }, | ||
768 | CompletionItem { | ||
769 | label: "return", | ||
770 | source_range: 122..124, | ||
771 | delete: 122..124, | ||
772 | insert: "return", | ||
773 | kind: Keyword, | ||
774 | }, | ||
775 | CompletionItem { | ||
776 | label: "while", | ||
777 | source_range: 122..124, | ||
778 | delete: 122..124, | ||
779 | insert: "while $0 {}", | ||
780 | kind: Keyword, | ||
781 | }, | ||
782 | ] | ||
783 | "### | 417 | "### |
784 | ) | 418 | ); |
419 | } | ||
420 | |||
421 | #[test] | ||
422 | fn test_mut_in_ref_and_in_fn_parameters_list() { | ||
423 | assert_snapshot!( | ||
424 | get_keyword_completions(r"fn my_fn(&<|>) {}"), | ||
425 | @r###" | ||
426 | kw mut | ||
427 | "### | ||
428 | ); | ||
429 | assert_snapshot!( | ||
430 | get_keyword_completions(r"fn my_fn(<|>) {}"), | ||
431 | @r###" | ||
432 | kw mut | ||
433 | "### | ||
434 | ); | ||
435 | assert_snapshot!( | ||
436 | get_keyword_completions(r"fn my_fn() { let &<|> }"), | ||
437 | @r###" | ||
438 | kw mut | ||
439 | "### | ||
440 | ); | ||
441 | } | ||
442 | |||
443 | #[test] | ||
444 | fn test_where_keyword() { | ||
445 | assert_snapshot!( | ||
446 | get_keyword_completions(r"trait A <|>"), | ||
447 | @r###" | ||
448 | kw where | ||
449 | "### | ||
450 | ); | ||
451 | assert_snapshot!( | ||
452 | get_keyword_completions(r"impl A <|>"), | ||
453 | @r###" | ||
454 | kw where | ||
455 | "### | ||
456 | ); | ||
785 | } | 457 | } |
786 | } | 458 | } |
diff --git a/crates/ra_ide/src/completion/complete_postfix.rs b/crates/ra_ide/src/completion/complete_postfix.rs index 59b58bf98..b878aeb0a 100644 --- a/crates/ra_ide/src/completion/complete_postfix.rs +++ b/crates/ra_ide/src/completion/complete_postfix.rs | |||
@@ -91,7 +91,7 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { | |||
91 | &dot_receiver, | 91 | &dot_receiver, |
92 | "if", | 92 | "if", |
93 | "if expr {}", | 93 | "if expr {}", |
94 | &format!("if {} {{$0}}", receiver_text), | 94 | &format!("if {} {{\n $0\n}}", receiver_text), |
95 | ) | 95 | ) |
96 | .add_to(acc); | 96 | .add_to(acc); |
97 | postfix_snippet( | 97 | postfix_snippet( |
@@ -100,7 +100,7 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { | |||
100 | &dot_receiver, | 100 | &dot_receiver, |
101 | "while", | 101 | "while", |
102 | "while expr {}", | 102 | "while expr {}", |
103 | &format!("while {} {{\n$0\n}}", receiver_text), | 103 | &format!("while {} {{\n $0\n}}", receiver_text), |
104 | ) | 104 | ) |
105 | .add_to(acc); | 105 | .add_to(acc); |
106 | } | 106 | } |
@@ -283,7 +283,7 @@ mod tests { | |||
283 | label: "if", | 283 | label: "if", |
284 | source_range: 89..89, | 284 | source_range: 89..89, |
285 | delete: 85..89, | 285 | delete: 85..89, |
286 | insert: "if bar {$0}", | 286 | insert: "if bar {\n $0\n}", |
287 | detail: "if expr {}", | 287 | detail: "if expr {}", |
288 | }, | 288 | }, |
289 | CompletionItem { | 289 | CompletionItem { |
@@ -318,7 +318,7 @@ mod tests { | |||
318 | label: "while", | 318 | label: "while", |
319 | source_range: 89..89, | 319 | source_range: 89..89, |
320 | delete: 85..89, | 320 | delete: 85..89, |
321 | insert: "while bar {\n$0\n}", | 321 | insert: "while bar {\n $0\n}", |
322 | detail: "while expr {}", | 322 | detail: "while expr {}", |
323 | }, | 323 | }, |
324 | ] | 324 | ] |
diff --git a/crates/ra_ide/src/completion/completion_context.rs b/crates/ra_ide/src/completion/completion_context.rs index c4646b727..560fb19e6 100644 --- a/crates/ra_ide/src/completion/completion_context.rs +++ b/crates/ra_ide/src/completion/completion_context.rs | |||
@@ -5,12 +5,17 @@ use ra_db::SourceDatabase; | |||
5 | use ra_ide_db::RootDatabase; | 5 | use ra_ide_db::RootDatabase; |
6 | use ra_syntax::{ | 6 | use ra_syntax::{ |
7 | algo::{find_covering_element, find_node_at_offset}, | 7 | algo::{find_covering_element, find_node_at_offset}, |
8 | ast, match_ast, AstNode, | 8 | ast, match_ast, AstNode, NodeOrToken, |
9 | SyntaxKind::*, | 9 | SyntaxKind::*, |
10 | SyntaxNode, SyntaxToken, TextRange, TextSize, | 10 | SyntaxNode, SyntaxToken, TextRange, TextSize, |
11 | }; | 11 | }; |
12 | use ra_text_edit::Indel; | 12 | use ra_text_edit::Indel; |
13 | 13 | ||
14 | use super::patterns::{ | ||
15 | has_bind_pat_parent, has_block_expr_parent, has_impl_as_prev_sibling, has_impl_parent, | ||
16 | has_item_list_or_source_file_parent, has_ref_parent, has_trait_as_prev_sibling, | ||
17 | has_trait_parent, if_is_prev, is_in_loop_body, is_match_arm, unsafe_is_prev, | ||
18 | }; | ||
14 | use crate::{call_info::ActiveParameter, completion::CompletionConfig, FilePosition}; | 19 | use crate::{call_info::ActiveParameter, completion::CompletionConfig, FilePosition}; |
15 | use test_utils::mark; | 20 | use test_utils::mark; |
16 | 21 | ||
@@ -60,6 +65,18 @@ pub(crate) struct CompletionContext<'a> { | |||
60 | pub(super) is_path_type: bool, | 65 | pub(super) is_path_type: bool, |
61 | pub(super) has_type_args: bool, | 66 | pub(super) has_type_args: bool, |
62 | pub(super) attribute_under_caret: Option<ast::Attr>, | 67 | pub(super) attribute_under_caret: Option<ast::Attr>, |
68 | pub(super) unsafe_is_prev: bool, | ||
69 | pub(super) if_is_prev: bool, | ||
70 | pub(super) block_expr_parent: bool, | ||
71 | pub(super) bind_pat_parent: bool, | ||
72 | pub(super) ref_pat_parent: bool, | ||
73 | pub(super) in_loop_body: bool, | ||
74 | pub(super) has_trait_parent: bool, | ||
75 | pub(super) has_impl_parent: bool, | ||
76 | pub(super) trait_as_prev_sibling: bool, | ||
77 | pub(super) impl_as_prev_sibling: bool, | ||
78 | pub(super) is_match_arm: bool, | ||
79 | pub(super) has_item_list_or_source_file_parent: bool, | ||
63 | } | 80 | } |
64 | 81 | ||
65 | impl<'a> CompletionContext<'a> { | 82 | impl<'a> CompletionContext<'a> { |
@@ -118,6 +135,18 @@ impl<'a> CompletionContext<'a> { | |||
118 | has_type_args: false, | 135 | has_type_args: false, |
119 | dot_receiver_is_ambiguous_float_literal: false, | 136 | dot_receiver_is_ambiguous_float_literal: false, |
120 | attribute_under_caret: None, | 137 | attribute_under_caret: None, |
138 | unsafe_is_prev: false, | ||
139 | in_loop_body: false, | ||
140 | ref_pat_parent: false, | ||
141 | bind_pat_parent: false, | ||
142 | block_expr_parent: false, | ||
143 | has_trait_parent: false, | ||
144 | has_impl_parent: false, | ||
145 | trait_as_prev_sibling: false, | ||
146 | impl_as_prev_sibling: false, | ||
147 | if_is_prev: false, | ||
148 | is_match_arm: false, | ||
149 | has_item_list_or_source_file_parent: false, | ||
121 | }; | 150 | }; |
122 | 151 | ||
123 | let mut original_file = original_file.syntax().clone(); | 152 | let mut original_file = original_file.syntax().clone(); |
@@ -159,7 +188,7 @@ impl<'a> CompletionContext<'a> { | |||
159 | break; | 188 | break; |
160 | } | 189 | } |
161 | } | 190 | } |
162 | 191 | ctx.fill_keyword_patterns(&hypothetical_file, offset); | |
163 | ctx.fill(&original_file, hypothetical_file, offset); | 192 | ctx.fill(&original_file, hypothetical_file, offset); |
164 | Some(ctx) | 193 | Some(ctx) |
165 | } | 194 | } |
@@ -188,6 +217,24 @@ impl<'a> CompletionContext<'a> { | |||
188 | self.sema.scope_at_offset(&self.token.parent(), self.offset) | 217 | self.sema.scope_at_offset(&self.token.parent(), self.offset) |
189 | } | 218 | } |
190 | 219 | ||
220 | fn fill_keyword_patterns(&mut self, file_with_fake_ident: &SyntaxNode, offset: TextSize) { | ||
221 | let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap(); | ||
222 | let syntax_element = NodeOrToken::Token(fake_ident_token.clone()); | ||
223 | self.block_expr_parent = has_block_expr_parent(syntax_element.clone()); | ||
224 | self.unsafe_is_prev = unsafe_is_prev(syntax_element.clone()); | ||
225 | self.if_is_prev = if_is_prev(syntax_element.clone()); | ||
226 | self.bind_pat_parent = has_bind_pat_parent(syntax_element.clone()); | ||
227 | self.ref_pat_parent = has_ref_parent(syntax_element.clone()); | ||
228 | self.in_loop_body = is_in_loop_body(syntax_element.clone()); | ||
229 | self.has_trait_parent = has_trait_parent(syntax_element.clone()); | ||
230 | self.has_impl_parent = has_impl_parent(syntax_element.clone()); | ||
231 | self.impl_as_prev_sibling = has_impl_as_prev_sibling(syntax_element.clone()); | ||
232 | self.trait_as_prev_sibling = has_trait_as_prev_sibling(syntax_element.clone()); | ||
233 | self.is_match_arm = is_match_arm(syntax_element.clone()); | ||
234 | self.has_item_list_or_source_file_parent = | ||
235 | has_item_list_or_source_file_parent(syntax_element.clone()); | ||
236 | } | ||
237 | |||
191 | fn fill( | 238 | fn fill( |
192 | &mut self, | 239 | &mut self, |
193 | original_file: &SyntaxNode, | 240 | original_file: &SyntaxNode, |
@@ -334,6 +381,7 @@ impl<'a> CompletionContext<'a> { | |||
334 | self.is_path_type = path.syntax().parent().and_then(ast::PathType::cast).is_some(); | 381 | self.is_path_type = path.syntax().parent().and_then(ast::PathType::cast).is_some(); |
335 | self.has_type_args = segment.type_arg_list().is_some(); | 382 | self.has_type_args = segment.type_arg_list().is_some(); |
336 | 383 | ||
384 | #[allow(deprecated)] | ||
337 | if let Some(path) = hir::Path::from_ast(path.clone()) { | 385 | if let Some(path) = hir::Path::from_ast(path.clone()) { |
338 | if let Some(path_prefix) = path.qualifier() { | 386 | if let Some(path_prefix) = path.qualifier() { |
339 | self.path_prefix = Some(path_prefix); | 387 | self.path_prefix = Some(path_prefix); |
diff --git a/crates/ra_ide/src/completion/completion_item.rs b/crates/ra_ide/src/completion/completion_item.rs index cfb7c1e38..98348b349 100644 --- a/crates/ra_ide/src/completion/completion_item.rs +++ b/crates/ra_ide/src/completion/completion_item.rs | |||
@@ -125,6 +125,32 @@ pub enum CompletionItemKind { | |||
125 | Attribute, | 125 | Attribute, |
126 | } | 126 | } |
127 | 127 | ||
128 | impl CompletionItemKind { | ||
129 | #[cfg(test)] | ||
130 | pub(crate) fn tag(&self) -> &'static str { | ||
131 | match self { | ||
132 | CompletionItemKind::Snippet => "sn", | ||
133 | CompletionItemKind::Keyword => "kw", | ||
134 | CompletionItemKind::Module => "md", | ||
135 | CompletionItemKind::Function => "fn", | ||
136 | CompletionItemKind::BuiltinType => "bt", | ||
137 | CompletionItemKind::Struct => "st", | ||
138 | CompletionItemKind::Enum => "en", | ||
139 | CompletionItemKind::EnumVariant => "ev", | ||
140 | CompletionItemKind::Binding => "bn", | ||
141 | CompletionItemKind::Field => "fd", | ||
142 | CompletionItemKind::Static => "sc", | ||
143 | CompletionItemKind::Const => "ct", | ||
144 | CompletionItemKind::Trait => "tt", | ||
145 | CompletionItemKind::TypeAlias => "ta", | ||
146 | CompletionItemKind::Method => "me", | ||
147 | CompletionItemKind::TypeParam => "tp", | ||
148 | CompletionItemKind::Macro => "ma", | ||
149 | CompletionItemKind::Attribute => "at", | ||
150 | } | ||
151 | } | ||
152 | } | ||
153 | |||
128 | #[derive(Debug, PartialEq, Eq, Copy, Clone)] | 154 | #[derive(Debug, PartialEq, Eq, Copy, Clone)] |
129 | pub(crate) enum CompletionKind { | 155 | pub(crate) enum CompletionKind { |
130 | /// Parser-based keyword completion. | 156 | /// Parser-based keyword completion. |
diff --git a/crates/ra_ide/src/completion/patterns.rs b/crates/ra_ide/src/completion/patterns.rs new file mode 100644 index 000000000..b2fe13280 --- /dev/null +++ b/crates/ra_ide/src/completion/patterns.rs | |||
@@ -0,0 +1,194 @@ | |||
1 | //! Patterns telling us certain facts about current syntax element, they are used in completion context | ||
2 | |||
3 | use ra_syntax::{ | ||
4 | algo::non_trivia_sibling, | ||
5 | ast::{self, LoopBodyOwner}, | ||
6 | match_ast, AstNode, Direction, NodeOrToken, SyntaxElement, | ||
7 | SyntaxKind::*, | ||
8 | SyntaxNode, SyntaxToken, | ||
9 | }; | ||
10 | |||
11 | #[cfg(test)] | ||
12 | use crate::completion::test_utils::check_pattern_is_applicable; | ||
13 | |||
14 | pub(crate) fn has_trait_parent(element: SyntaxElement) -> bool { | ||
15 | not_same_range_ancestor(element) | ||
16 | .filter(|it| it.kind() == ITEM_LIST) | ||
17 | .and_then(|it| it.parent()) | ||
18 | .filter(|it| it.kind() == TRAIT_DEF) | ||
19 | .is_some() | ||
20 | } | ||
21 | #[test] | ||
22 | fn test_has_trait_parent() { | ||
23 | check_pattern_is_applicable(r"trait A { f<|> }", has_trait_parent); | ||
24 | } | ||
25 | |||
26 | pub(crate) fn has_impl_parent(element: SyntaxElement) -> bool { | ||
27 | not_same_range_ancestor(element) | ||
28 | .filter(|it| it.kind() == ITEM_LIST) | ||
29 | .and_then(|it| it.parent()) | ||
30 | .filter(|it| it.kind() == IMPL_DEF) | ||
31 | .is_some() | ||
32 | } | ||
33 | #[test] | ||
34 | fn test_has_impl_parent() { | ||
35 | check_pattern_is_applicable(r"impl A { f<|> }", has_impl_parent); | ||
36 | } | ||
37 | |||
38 | pub(crate) fn has_block_expr_parent(element: SyntaxElement) -> bool { | ||
39 | not_same_range_ancestor(element).filter(|it| it.kind() == BLOCK_EXPR).is_some() | ||
40 | } | ||
41 | #[test] | ||
42 | fn test_has_block_expr_parent() { | ||
43 | check_pattern_is_applicable(r"fn my_fn() { let a = 2; f<|> }", has_block_expr_parent); | ||
44 | } | ||
45 | |||
46 | pub(crate) fn has_bind_pat_parent(element: SyntaxElement) -> bool { | ||
47 | element.ancestors().find(|it| it.kind() == BIND_PAT).is_some() | ||
48 | } | ||
49 | #[test] | ||
50 | fn test_has_bind_pat_parent() { | ||
51 | check_pattern_is_applicable(r"fn my_fn(m<|>) {}", has_bind_pat_parent); | ||
52 | check_pattern_is_applicable(r"fn my_fn() { let m<|> }", has_bind_pat_parent); | ||
53 | } | ||
54 | |||
55 | pub(crate) fn has_ref_parent(element: SyntaxElement) -> bool { | ||
56 | not_same_range_ancestor(element) | ||
57 | .filter(|it| it.kind() == REF_PAT || it.kind() == REF_EXPR) | ||
58 | .is_some() | ||
59 | } | ||
60 | #[test] | ||
61 | fn test_has_ref_parent() { | ||
62 | check_pattern_is_applicable(r"fn my_fn(&m<|>) {}", has_ref_parent); | ||
63 | check_pattern_is_applicable(r"fn my() { let &m<|> }", has_ref_parent); | ||
64 | } | ||
65 | |||
66 | pub(crate) fn has_item_list_or_source_file_parent(element: SyntaxElement) -> bool { | ||
67 | let ancestor = not_same_range_ancestor(element); | ||
68 | if !ancestor.is_some() { | ||
69 | return true; | ||
70 | } | ||
71 | ancestor.filter(|it| it.kind() == SOURCE_FILE || it.kind() == ITEM_LIST).is_some() | ||
72 | } | ||
73 | #[test] | ||
74 | fn test_has_item_list_or_source_file_parent() { | ||
75 | check_pattern_is_applicable(r"i<|>", has_item_list_or_source_file_parent); | ||
76 | check_pattern_is_applicable(r"impl { f<|> }", has_item_list_or_source_file_parent); | ||
77 | } | ||
78 | |||
79 | pub(crate) fn is_match_arm(element: SyntaxElement) -> bool { | ||
80 | not_same_range_ancestor(element.clone()).filter(|it| it.kind() == MATCH_ARM).is_some() | ||
81 | && previous_sibling_or_ancestor_sibling(element) | ||
82 | .and_then(|it| it.into_token()) | ||
83 | .filter(|it| it.kind() == FAT_ARROW) | ||
84 | .is_some() | ||
85 | } | ||
86 | #[test] | ||
87 | fn test_is_match_arm() { | ||
88 | check_pattern_is_applicable(r"fn my_fn() { match () { () => m<|> } }", is_match_arm); | ||
89 | } | ||
90 | |||
91 | pub(crate) fn unsafe_is_prev(element: SyntaxElement) -> bool { | ||
92 | element | ||
93 | .into_token() | ||
94 | .and_then(|it| previous_non_trivia_token(it)) | ||
95 | .filter(|it| it.kind() == UNSAFE_KW) | ||
96 | .is_some() | ||
97 | } | ||
98 | #[test] | ||
99 | fn test_unsafe_is_prev() { | ||
100 | check_pattern_is_applicable(r"unsafe i<|>", unsafe_is_prev); | ||
101 | } | ||
102 | |||
103 | pub(crate) fn if_is_prev(element: SyntaxElement) -> bool { | ||
104 | element | ||
105 | .into_token() | ||
106 | .and_then(|it| previous_non_trivia_token(it)) | ||
107 | .filter(|it| it.kind() == IF_KW) | ||
108 | .is_some() | ||
109 | } | ||
110 | #[test] | ||
111 | fn test_if_is_prev() { | ||
112 | check_pattern_is_applicable(r"if l<|>", if_is_prev); | ||
113 | } | ||
114 | |||
115 | pub(crate) fn has_trait_as_prev_sibling(element: SyntaxElement) -> bool { | ||
116 | previous_sibling_or_ancestor_sibling(element).filter(|it| it.kind() == TRAIT_DEF).is_some() | ||
117 | } | ||
118 | #[test] | ||
119 | fn test_has_trait_as_prev_sibling() { | ||
120 | check_pattern_is_applicable(r"trait A w<|> {}", has_trait_as_prev_sibling); | ||
121 | } | ||
122 | |||
123 | pub(crate) fn has_impl_as_prev_sibling(element: SyntaxElement) -> bool { | ||
124 | previous_sibling_or_ancestor_sibling(element).filter(|it| it.kind() == IMPL_DEF).is_some() | ||
125 | } | ||
126 | #[test] | ||
127 | fn test_has_impl_as_prev_sibling() { | ||
128 | check_pattern_is_applicable(r"impl A w<|> {}", has_impl_as_prev_sibling); | ||
129 | } | ||
130 | |||
131 | pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool { | ||
132 | let leaf = match element { | ||
133 | NodeOrToken::Node(node) => node, | ||
134 | NodeOrToken::Token(token) => token.parent(), | ||
135 | }; | ||
136 | for node in leaf.ancestors() { | ||
137 | if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR { | ||
138 | break; | ||
139 | } | ||
140 | let loop_body = match_ast! { | ||
141 | match node { | ||
142 | ast::ForExpr(it) => it.loop_body(), | ||
143 | ast::WhileExpr(it) => it.loop_body(), | ||
144 | ast::LoopExpr(it) => it.loop_body(), | ||
145 | _ => None, | ||
146 | } | ||
147 | }; | ||
148 | if let Some(body) = loop_body { | ||
149 | if body.syntax().text_range().contains_range(leaf.text_range()) { | ||
150 | return true; | ||
151 | } | ||
152 | } | ||
153 | } | ||
154 | false | ||
155 | } | ||
156 | |||
157 | fn not_same_range_ancestor(element: SyntaxElement) -> Option<SyntaxNode> { | ||
158 | element | ||
159 | .ancestors() | ||
160 | .take_while(|it| it.text_range() == element.text_range()) | ||
161 | .last() | ||
162 | .and_then(|it| it.parent()) | ||
163 | } | ||
164 | |||
165 | fn previous_non_trivia_token(token: SyntaxToken) -> Option<SyntaxToken> { | ||
166 | let mut token = token.prev_token(); | ||
167 | while let Some(inner) = token.clone() { | ||
168 | if !inner.kind().is_trivia() { | ||
169 | return Some(inner); | ||
170 | } else { | ||
171 | token = inner.prev_token(); | ||
172 | } | ||
173 | } | ||
174 | None | ||
175 | } | ||
176 | |||
177 | fn previous_sibling_or_ancestor_sibling(element: SyntaxElement) -> Option<SyntaxElement> { | ||
178 | let token_sibling = non_trivia_sibling(element.clone(), Direction::Prev); | ||
179 | if let Some(sibling) = token_sibling { | ||
180 | Some(sibling) | ||
181 | } else { | ||
182 | // if not trying to find first ancestor which has such a sibling | ||
183 | let node = match element { | ||
184 | NodeOrToken::Node(node) => node, | ||
185 | NodeOrToken::Token(token) => token.parent(), | ||
186 | }; | ||
187 | let range = node.text_range(); | ||
188 | let top_node = node.ancestors().take_while(|it| it.text_range() == range).last()?; | ||
189 | let prev_sibling_node = top_node.ancestors().find(|it| { | ||
190 | non_trivia_sibling(NodeOrToken::Node(it.to_owned()), Direction::Prev).is_some() | ||
191 | })?; | ||
192 | non_trivia_sibling(NodeOrToken::Node(prev_sibling_node), Direction::Prev) | ||
193 | } | ||
194 | } | ||
diff --git a/crates/ra_ide/src/completion/test_utils.rs b/crates/ra_ide/src/completion/test_utils.rs index bf22452a2..1e16a43ca 100644 --- a/crates/ra_ide/src/completion/test_utils.rs +++ b/crates/ra_ide/src/completion/test_utils.rs | |||
@@ -5,25 +5,63 @@ use crate::{ | |||
5 | mock_analysis::{analysis_and_position, single_file_with_position}, | 5 | mock_analysis::{analysis_and_position, single_file_with_position}, |
6 | CompletionItem, | 6 | CompletionItem, |
7 | }; | 7 | }; |
8 | use hir::Semantics; | ||
9 | use ra_syntax::{AstNode, NodeOrToken, SyntaxElement}; | ||
8 | 10 | ||
9 | pub(crate) fn do_completion(code: &str, kind: CompletionKind) -> Vec<CompletionItem> { | 11 | pub(crate) fn do_completion(code: &str, kind: CompletionKind) -> Vec<CompletionItem> { |
10 | do_completion_with_options(code, kind, &CompletionConfig::default()) | 12 | do_completion_with_options(code, kind, &CompletionConfig::default()) |
11 | } | 13 | } |
12 | 14 | ||
15 | pub(crate) fn completion_list(code: &str, kind: CompletionKind) -> String { | ||
16 | completion_list_with_options(code, kind, &CompletionConfig::default()) | ||
17 | } | ||
18 | |||
13 | pub(crate) fn do_completion_with_options( | 19 | pub(crate) fn do_completion_with_options( |
14 | code: &str, | 20 | code: &str, |
15 | kind: CompletionKind, | 21 | kind: CompletionKind, |
16 | options: &CompletionConfig, | 22 | options: &CompletionConfig, |
17 | ) -> Vec<CompletionItem> { | 23 | ) -> Vec<CompletionItem> { |
24 | let mut kind_completions: Vec<CompletionItem> = get_all_completion_items(code, options) | ||
25 | .into_iter() | ||
26 | .filter(|c| c.completion_kind == kind) | ||
27 | .collect(); | ||
28 | kind_completions.sort_by(|l, r| l.label().cmp(r.label())); | ||
29 | kind_completions | ||
30 | } | ||
31 | |||
32 | fn get_all_completion_items(code: &str, options: &CompletionConfig) -> Vec<CompletionItem> { | ||
18 | let (analysis, position) = if code.contains("//-") { | 33 | let (analysis, position) = if code.contains("//-") { |
19 | analysis_and_position(code) | 34 | analysis_and_position(code) |
20 | } else { | 35 | } else { |
21 | single_file_with_position(code) | 36 | single_file_with_position(code) |
22 | }; | 37 | }; |
23 | let completions = analysis.completions(options, position).unwrap().unwrap(); | 38 | analysis.completions(options, position).unwrap().unwrap().into() |
24 | let completion_items: Vec<CompletionItem> = completions.into(); | 39 | } |
25 | let mut kind_completions: Vec<CompletionItem> = | 40 | |
26 | completion_items.into_iter().filter(|c| c.completion_kind == kind).collect(); | 41 | pub(crate) fn completion_list_with_options( |
42 | code: &str, | ||
43 | kind: CompletionKind, | ||
44 | options: &CompletionConfig, | ||
45 | ) -> String { | ||
46 | let mut kind_completions: Vec<CompletionItem> = get_all_completion_items(code, options) | ||
47 | .into_iter() | ||
48 | .filter(|c| c.completion_kind == kind) | ||
49 | .collect(); | ||
27 | kind_completions.sort_by_key(|c| c.label().to_owned()); | 50 | kind_completions.sort_by_key(|c| c.label().to_owned()); |
28 | kind_completions | 51 | kind_completions |
52 | .into_iter() | ||
53 | .map(|it| format!("{} {}\n", it.kind().unwrap().tag(), it.label())) | ||
54 | .collect() | ||
55 | } | ||
56 | |||
57 | pub(crate) fn check_pattern_is_applicable(code: &str, check: fn(SyntaxElement) -> bool) { | ||
58 | let (analysis, pos) = single_file_with_position(code); | ||
59 | analysis | ||
60 | .with_db(|db| { | ||
61 | let sema = Semantics::new(db); | ||
62 | let original_file = sema.parse(pos.file_id); | ||
63 | let token = original_file.syntax().token_at_offset(pos.offset).left_biased().unwrap(); | ||
64 | assert!(check(NodeOrToken::Token(token))); | ||
65 | }) | ||
66 | .unwrap(); | ||
29 | } | 67 | } |
diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs index 365d52168..a88a978d7 100644 --- a/crates/ra_ide/src/diagnostics.rs +++ b/crates/ra_ide/src/diagnostics.rs | |||
@@ -11,7 +11,7 @@ use hir::{ | |||
11 | HasSource, HirDisplay, Semantics, VariantDef, | 11 | HasSource, HirDisplay, Semantics, VariantDef, |
12 | }; | 12 | }; |
13 | use itertools::Itertools; | 13 | use itertools::Itertools; |
14 | use ra_db::{RelativePath, SourceDatabase, SourceDatabaseExt}; | 14 | use ra_db::SourceDatabase; |
15 | use ra_ide_db::RootDatabase; | 15 | use ra_ide_db::RootDatabase; |
16 | use ra_prof::profile; | 16 | use ra_prof::profile; |
17 | use ra_syntax::{ | 17 | use ra_syntax::{ |
@@ -57,14 +57,10 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic> | |||
57 | }) | 57 | }) |
58 | .on::<hir::diagnostics::UnresolvedModule, _>(|d| { | 58 | .on::<hir::diagnostics::UnresolvedModule, _>(|d| { |
59 | let original_file = d.source().file_id.original_file(db); | 59 | let original_file = d.source().file_id.original_file(db); |
60 | let source_root = db.file_source_root(original_file); | 60 | let fix = Fix::new( |
61 | let path = db | 61 | "Create module", |
62 | .file_relative_path(original_file) | 62 | FileSystemEdit::CreateFile { anchor: original_file, dst: d.candidate.clone() }.into(), |
63 | .parent() | 63 | ); |
64 | .unwrap_or_else(|| RelativePath::new("")) | ||
65 | .join(&d.candidate); | ||
66 | let fix = | ||
67 | Fix::new("Create module", FileSystemEdit::CreateFile { source_root, path }.into()); | ||
68 | res.borrow_mut().push(Diagnostic { | 64 | res.borrow_mut().push(Diagnostic { |
69 | range: sema.diagnostics_range(d).range, | 65 | range: sema.diagnostics_range(d).range, |
70 | message: d.message(), | 66 | message: d.message(), |
@@ -683,10 +679,10 @@ mod tests { | |||
683 | source_file_edits: [], | 679 | source_file_edits: [], |
684 | file_system_edits: [ | 680 | file_system_edits: [ |
685 | CreateFile { | 681 | CreateFile { |
686 | source_root: SourceRootId( | 682 | anchor: FileId( |
687 | 0, | 683 | 1, |
688 | ), | 684 | ), |
689 | path: "foo.rs", | 685 | dst: "foo.rs", |
690 | }, | 686 | }, |
691 | ], | 687 | ], |
692 | is_snippet: false, | 688 | is_snippet: false, |
diff --git a/crates/ra_ide/src/display/navigation_target.rs b/crates/ra_ide/src/display/navigation_target.rs index c7bb1e69f..0b52b01ab 100644 --- a/crates/ra_ide/src/display/navigation_target.rs +++ b/crates/ra_ide/src/display/navigation_target.rs | |||
@@ -135,8 +135,8 @@ impl NavigationTarget { | |||
135 | db: &RootDatabase, | 135 | db: &RootDatabase, |
136 | node: InFile<&dyn ast::NameOwner>, | 136 | node: InFile<&dyn ast::NameOwner>, |
137 | ) -> NavigationTarget { | 137 | ) -> NavigationTarget { |
138 | //FIXME: use `_` instead of empty string | 138 | let name = |
139 | let name = node.value.name().map(|it| it.text().clone()).unwrap_or_default(); | 139 | node.value.name().map(|it| it.text().clone()).unwrap_or_else(|| SmolStr::new("_")); |
140 | let focus_range = | 140 | let focus_range = |
141 | node.value.name().map(|it| original_range(db, node.with_value(it.syntax())).range); | 141 | node.value.name().map(|it| original_range(db, node.with_value(it.syntax())).range); |
142 | let frange = original_range(db, node.map(|it| it.syntax())); | 142 | let frange = original_range(db, node.map(|it| it.syntax())); |
@@ -150,6 +150,25 @@ impl NavigationTarget { | |||
150 | ) | 150 | ) |
151 | } | 151 | } |
152 | 152 | ||
153 | /// Allows `NavigationTarget` to be created from a `DocCommentsOwner` and a `NameOwner` | ||
154 | pub(crate) fn from_doc_commented( | ||
155 | db: &RootDatabase, | ||
156 | named: InFile<&dyn ast::NameOwner>, | ||
157 | node: InFile<&dyn ast::DocCommentsOwner>, | ||
158 | ) -> NavigationTarget { | ||
159 | let name = | ||
160 | named.value.name().map(|it| it.text().clone()).unwrap_or_else(|| SmolStr::new("_")); | ||
161 | let frange = original_range(db, node.map(|it| it.syntax())); | ||
162 | |||
163 | NavigationTarget::from_syntax( | ||
164 | frange.file_id, | ||
165 | name, | ||
166 | None, | ||
167 | frange.range, | ||
168 | node.value.syntax().kind(), | ||
169 | ) | ||
170 | } | ||
171 | |||
153 | fn from_syntax( | 172 | fn from_syntax( |
154 | file_id: FileId, | 173 | file_id: FileId, |
155 | name: SmolStr, | 174 | name: SmolStr, |
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index ad78b7671..d870e4cbc 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs | |||
@@ -2,7 +2,7 @@ use std::iter::once; | |||
2 | 2 | ||
3 | use hir::{ | 3 | use hir::{ |
4 | Adt, AsAssocItem, AssocItemContainer, Documentation, FieldSource, HasSource, HirDisplay, | 4 | Adt, AsAssocItem, AssocItemContainer, Documentation, FieldSource, HasSource, HirDisplay, |
5 | ModuleDef, ModuleSource, Semantics, | 5 | Module, ModuleDef, ModuleSource, Semantics, |
6 | }; | 6 | }; |
7 | use itertools::Itertools; | 7 | use itertools::Itertools; |
8 | use ra_db::SourceDatabase; | 8 | use ra_db::SourceDatabase; |
@@ -13,7 +13,9 @@ use ra_ide_db::{ | |||
13 | use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; | 13 | use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; |
14 | 14 | ||
15 | use crate::{ | 15 | use crate::{ |
16 | display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel, ToNav}, | 16 | display::{ |
17 | macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel, ToNav, TryToNav, | ||
18 | }, | ||
17 | runnables::runnable, | 19 | runnables::runnable, |
18 | FileId, FilePosition, NavigationTarget, RangeInfo, Runnable, | 20 | FileId, FilePosition, NavigationTarget, RangeInfo, Runnable, |
19 | }; | 21 | }; |
@@ -24,19 +26,21 @@ pub struct HoverConfig { | |||
24 | pub implementations: bool, | 26 | pub implementations: bool, |
25 | pub run: bool, | 27 | pub run: bool, |
26 | pub debug: bool, | 28 | pub debug: bool, |
29 | pub goto_type_def: bool, | ||
27 | } | 30 | } |
28 | 31 | ||
29 | impl Default for HoverConfig { | 32 | impl Default for HoverConfig { |
30 | fn default() -> Self { | 33 | fn default() -> Self { |
31 | Self { implementations: true, run: true, debug: true } | 34 | Self { implementations: true, run: true, debug: true, goto_type_def: true } |
32 | } | 35 | } |
33 | } | 36 | } |
34 | 37 | ||
35 | impl HoverConfig { | 38 | impl HoverConfig { |
36 | pub const NO_ACTIONS: Self = Self { implementations: false, run: false, debug: false }; | 39 | pub const NO_ACTIONS: Self = |
40 | Self { implementations: false, run: false, debug: false, goto_type_def: false }; | ||
37 | 41 | ||
38 | pub fn any(&self) -> bool { | 42 | pub fn any(&self) -> bool { |
39 | self.implementations || self.runnable() | 43 | self.implementations || self.runnable() || self.goto_type_def |
40 | } | 44 | } |
41 | 45 | ||
42 | pub fn none(&self) -> bool { | 46 | pub fn none(&self) -> bool { |
@@ -52,6 +56,13 @@ impl HoverConfig { | |||
52 | pub enum HoverAction { | 56 | pub enum HoverAction { |
53 | Runnable(Runnable), | 57 | Runnable(Runnable), |
54 | Implementaion(FilePosition), | 58 | Implementaion(FilePosition), |
59 | GoToType(Vec<HoverGotoTypeData>), | ||
60 | } | ||
61 | |||
62 | #[derive(Debug, Clone, Eq, PartialEq)] | ||
63 | pub struct HoverGotoTypeData { | ||
64 | pub mod_path: String, | ||
65 | pub nav: NavigationTarget, | ||
55 | } | 66 | } |
56 | 67 | ||
57 | /// Contains the results when hovering over an item | 68 | /// Contains the results when hovering over an item |
@@ -138,6 +149,10 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn | |||
138 | res.push_action(action); | 149 | res.push_action(action); |
139 | } | 150 | } |
140 | 151 | ||
152 | if let Some(action) = goto_type_action(db, name_kind) { | ||
153 | res.push_action(action); | ||
154 | } | ||
155 | |||
141 | return Some(RangeInfo::new(range, res)); | 156 | return Some(RangeInfo::new(range, res)); |
142 | } | 157 | } |
143 | } | 158 | } |
@@ -218,6 +233,44 @@ fn runnable_action( | |||
218 | } | 233 | } |
219 | } | 234 | } |
220 | 235 | ||
236 | fn goto_type_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> { | ||
237 | match def { | ||
238 | Definition::Local(it) => { | ||
239 | let mut targets: Vec<ModuleDef> = Vec::new(); | ||
240 | let mut push_new_def = |item: ModuleDef| { | ||
241 | if !targets.contains(&item) { | ||
242 | targets.push(item); | ||
243 | } | ||
244 | }; | ||
245 | |||
246 | it.ty(db).walk(db, |t| { | ||
247 | if let Some(adt) = t.as_adt() { | ||
248 | push_new_def(adt.into()); | ||
249 | } else if let Some(trait_) = t.as_dyn_trait() { | ||
250 | push_new_def(trait_.into()); | ||
251 | } else if let Some(traits) = t.as_impl_traits(db) { | ||
252 | traits.into_iter().for_each(|it| push_new_def(it.into())); | ||
253 | } else if let Some(trait_) = t.as_associated_type_parent_trait(db) { | ||
254 | push_new_def(trait_.into()); | ||
255 | } | ||
256 | }); | ||
257 | |||
258 | let targets = targets | ||
259 | .into_iter() | ||
260 | .filter_map(|it| { | ||
261 | Some(HoverGotoTypeData { | ||
262 | mod_path: mod_path(db, &it)?, | ||
263 | nav: it.try_to_nav(db)?, | ||
264 | }) | ||
265 | }) | ||
266 | .collect(); | ||
267 | |||
268 | Some(HoverAction::GoToType(targets)) | ||
269 | } | ||
270 | _ => None, | ||
271 | } | ||
272 | } | ||
273 | |||
221 | fn hover_text( | 274 | fn hover_text( |
222 | docs: Option<String>, | 275 | docs: Option<String>, |
223 | desc: Option<String>, | 276 | desc: Option<String>, |
@@ -248,25 +301,31 @@ fn definition_owner_name(db: &RootDatabase, def: &Definition) -> Option<String> | |||
248 | .map(|name| name.to_string()) | 301 | .map(|name| name.to_string()) |
249 | } | 302 | } |
250 | 303 | ||
251 | fn determine_mod_path(db: &RootDatabase, def: &Definition) -> Option<String> { | 304 | fn determine_mod_path(db: &RootDatabase, module: Module, name: Option<String>) -> String { |
252 | let mod_path = def.module(db).map(|module| { | 305 | once(db.crate_graph()[module.krate().into()].display_name.as_ref().map(ToString::to_string)) |
253 | once(db.crate_graph()[module.krate().into()].display_name.as_ref().map(ToString::to_string)) | 306 | .chain( |
254 | .chain( | 307 | module |
255 | module | 308 | .path_to_root(db) |
256 | .path_to_root(db) | 309 | .into_iter() |
257 | .into_iter() | 310 | .rev() |
258 | .rev() | 311 | .map(|it| it.name(db).map(|name| name.to_string())), |
259 | .map(|it| it.name(db).map(|name| name.to_string())), | 312 | ) |
260 | ) | 313 | .chain(once(name)) |
261 | .chain(once(definition_owner_name(db, def))) | 314 | .flatten() |
262 | .flatten() | 315 | .join("::") |
263 | .join("::") | 316 | } |
264 | }); | 317 | |
265 | mod_path | 318 | // returns None only for ModuleDef::BuiltinType |
319 | fn mod_path(db: &RootDatabase, item: &ModuleDef) -> Option<String> { | ||
320 | Some(determine_mod_path(db, item.module(db)?, item.name(db).map(|name| name.to_string()))) | ||
321 | } | ||
322 | |||
323 | fn definition_mod_path(db: &RootDatabase, def: &Definition) -> Option<String> { | ||
324 | def.module(db).map(|module| determine_mod_path(db, module, definition_owner_name(db, def))) | ||
266 | } | 325 | } |
267 | 326 | ||
268 | fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<String> { | 327 | fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<String> { |
269 | let mod_path = determine_mod_path(db, &def); | 328 | let mod_path = definition_mod_path(db, &def); |
270 | return match def { | 329 | return match def { |
271 | Definition::Macro(it) => { | 330 | Definition::Macro(it) => { |
272 | let src = it.source(db); | 331 | let src = it.source(db); |
@@ -1310,4 +1369,1045 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
1310 | ] | 1369 | ] |
1311 | "###); | 1370 | "###); |
1312 | } | 1371 | } |
1372 | |||
1373 | #[test] | ||
1374 | fn test_hover_struct_has_goto_type_action() { | ||
1375 | let (_, actions) = check_hover_result( | ||
1376 | " | ||
1377 | //- /main.rs | ||
1378 | struct S{ f1: u32 } | ||
1379 | |||
1380 | fn main() { | ||
1381 | let s<|>t = S{ f1:0 }; | ||
1382 | } | ||
1383 | ", | ||
1384 | &["S"], | ||
1385 | ); | ||
1386 | assert_debug_snapshot!(actions, | ||
1387 | @r###" | ||
1388 | [ | ||
1389 | GoToType( | ||
1390 | [ | ||
1391 | HoverGotoTypeData { | ||
1392 | mod_path: "S", | ||
1393 | nav: NavigationTarget { | ||
1394 | file_id: FileId( | ||
1395 | 1, | ||
1396 | ), | ||
1397 | full_range: 0..19, | ||
1398 | name: "S", | ||
1399 | kind: STRUCT_DEF, | ||
1400 | focus_range: Some( | ||
1401 | 7..8, | ||
1402 | ), | ||
1403 | container_name: None, | ||
1404 | description: Some( | ||
1405 | "struct S", | ||
1406 | ), | ||
1407 | docs: None, | ||
1408 | }, | ||
1409 | }, | ||
1410 | ], | ||
1411 | ), | ||
1412 | ] | ||
1413 | "###); | ||
1414 | } | ||
1415 | |||
1416 | #[test] | ||
1417 | fn test_hover_generic_struct_has_goto_type_actions() { | ||
1418 | let (_, actions) = check_hover_result( | ||
1419 | " | ||
1420 | //- /main.rs | ||
1421 | struct Arg(u32); | ||
1422 | struct S<T>{ f1: T } | ||
1423 | |||
1424 | fn main() { | ||
1425 | let s<|>t = S{ f1:Arg(0) }; | ||
1426 | } | ||
1427 | ", | ||
1428 | &["S<Arg>"], | ||
1429 | ); | ||
1430 | assert_debug_snapshot!(actions, | ||
1431 | @r###" | ||
1432 | [ | ||
1433 | GoToType( | ||
1434 | [ | ||
1435 | HoverGotoTypeData { | ||
1436 | mod_path: "S", | ||
1437 | nav: NavigationTarget { | ||
1438 | file_id: FileId( | ||
1439 | 1, | ||
1440 | ), | ||
1441 | full_range: 17..37, | ||
1442 | name: "S", | ||
1443 | kind: STRUCT_DEF, | ||
1444 | focus_range: Some( | ||
1445 | 24..25, | ||
1446 | ), | ||
1447 | container_name: None, | ||
1448 | description: Some( | ||
1449 | "struct S", | ||
1450 | ), | ||
1451 | docs: None, | ||
1452 | }, | ||
1453 | }, | ||
1454 | HoverGotoTypeData { | ||
1455 | mod_path: "Arg", | ||
1456 | nav: NavigationTarget { | ||
1457 | file_id: FileId( | ||
1458 | 1, | ||
1459 | ), | ||
1460 | full_range: 0..16, | ||
1461 | name: "Arg", | ||
1462 | kind: STRUCT_DEF, | ||
1463 | focus_range: Some( | ||
1464 | 7..10, | ||
1465 | ), | ||
1466 | container_name: None, | ||
1467 | description: Some( | ||
1468 | "struct Arg", | ||
1469 | ), | ||
1470 | docs: None, | ||
1471 | }, | ||
1472 | }, | ||
1473 | ], | ||
1474 | ), | ||
1475 | ] | ||
1476 | "###); | ||
1477 | } | ||
1478 | |||
1479 | #[test] | ||
1480 | fn test_hover_generic_struct_has_flattened_goto_type_actions() { | ||
1481 | let (_, actions) = check_hover_result( | ||
1482 | " | ||
1483 | //- /main.rs | ||
1484 | struct Arg(u32); | ||
1485 | struct S<T>{ f1: T } | ||
1486 | |||
1487 | fn main() { | ||
1488 | let s<|>t = S{ f1: S{ f1: Arg(0) } }; | ||
1489 | } | ||
1490 | ", | ||
1491 | &["S<S<Arg>>"], | ||
1492 | ); | ||
1493 | assert_debug_snapshot!(actions, | ||
1494 | @r###" | ||
1495 | [ | ||
1496 | GoToType( | ||
1497 | [ | ||
1498 | HoverGotoTypeData { | ||
1499 | mod_path: "S", | ||
1500 | nav: NavigationTarget { | ||
1501 | file_id: FileId( | ||
1502 | 1, | ||
1503 | ), | ||
1504 | full_range: 17..37, | ||
1505 | name: "S", | ||
1506 | kind: STRUCT_DEF, | ||
1507 | focus_range: Some( | ||
1508 | 24..25, | ||
1509 | ), | ||
1510 | container_name: None, | ||
1511 | description: Some( | ||
1512 | "struct S", | ||
1513 | ), | ||
1514 | docs: None, | ||
1515 | }, | ||
1516 | }, | ||
1517 | HoverGotoTypeData { | ||
1518 | mod_path: "Arg", | ||
1519 | nav: NavigationTarget { | ||
1520 | file_id: FileId( | ||
1521 | 1, | ||
1522 | ), | ||
1523 | full_range: 0..16, | ||
1524 | name: "Arg", | ||
1525 | kind: STRUCT_DEF, | ||
1526 | focus_range: Some( | ||
1527 | 7..10, | ||
1528 | ), | ||
1529 | container_name: None, | ||
1530 | description: Some( | ||
1531 | "struct Arg", | ||
1532 | ), | ||
1533 | docs: None, | ||
1534 | }, | ||
1535 | }, | ||
1536 | ], | ||
1537 | ), | ||
1538 | ] | ||
1539 | "###); | ||
1540 | } | ||
1541 | |||
1542 | #[test] | ||
1543 | fn test_hover_tuple_has_goto_type_actions() { | ||
1544 | let (_, actions) = check_hover_result( | ||
1545 | " | ||
1546 | //- /main.rs | ||
1547 | struct A(u32); | ||
1548 | struct B(u32); | ||
1549 | mod M { | ||
1550 | pub struct C(u32); | ||
1551 | } | ||
1552 | |||
1553 | fn main() { | ||
1554 | let s<|>t = (A(1), B(2), M::C(3) ); | ||
1555 | } | ||
1556 | ", | ||
1557 | &["(A, B, C)"], | ||
1558 | ); | ||
1559 | assert_debug_snapshot!(actions, | ||
1560 | @r###" | ||
1561 | [ | ||
1562 | GoToType( | ||
1563 | [ | ||
1564 | HoverGotoTypeData { | ||
1565 | mod_path: "A", | ||
1566 | nav: NavigationTarget { | ||
1567 | file_id: FileId( | ||
1568 | 1, | ||
1569 | ), | ||
1570 | full_range: 0..14, | ||
1571 | name: "A", | ||
1572 | kind: STRUCT_DEF, | ||
1573 | focus_range: Some( | ||
1574 | 7..8, | ||
1575 | ), | ||
1576 | container_name: None, | ||
1577 | description: Some( | ||
1578 | "struct A", | ||
1579 | ), | ||
1580 | docs: None, | ||
1581 | }, | ||
1582 | }, | ||
1583 | HoverGotoTypeData { | ||
1584 | mod_path: "B", | ||
1585 | nav: NavigationTarget { | ||
1586 | file_id: FileId( | ||
1587 | 1, | ||
1588 | ), | ||
1589 | full_range: 15..29, | ||
1590 | name: "B", | ||
1591 | kind: STRUCT_DEF, | ||
1592 | focus_range: Some( | ||
1593 | 22..23, | ||
1594 | ), | ||
1595 | container_name: None, | ||
1596 | description: Some( | ||
1597 | "struct B", | ||
1598 | ), | ||
1599 | docs: None, | ||
1600 | }, | ||
1601 | }, | ||
1602 | HoverGotoTypeData { | ||
1603 | mod_path: "M::C", | ||
1604 | nav: NavigationTarget { | ||
1605 | file_id: FileId( | ||
1606 | 1, | ||
1607 | ), | ||
1608 | full_range: 42..60, | ||
1609 | name: "C", | ||
1610 | kind: STRUCT_DEF, | ||
1611 | focus_range: Some( | ||
1612 | 53..54, | ||
1613 | ), | ||
1614 | container_name: None, | ||
1615 | description: Some( | ||
1616 | "pub struct C", | ||
1617 | ), | ||
1618 | docs: None, | ||
1619 | }, | ||
1620 | }, | ||
1621 | ], | ||
1622 | ), | ||
1623 | ] | ||
1624 | "###); | ||
1625 | } | ||
1626 | |||
1627 | #[test] | ||
1628 | fn test_hover_return_impl_trait_has_goto_type_action() { | ||
1629 | let (_, actions) = check_hover_result( | ||
1630 | " | ||
1631 | //- /main.rs | ||
1632 | trait Foo {} | ||
1633 | |||
1634 | fn foo() -> impl Foo {} | ||
1635 | |||
1636 | fn main() { | ||
1637 | let s<|>t = foo(); | ||
1638 | } | ||
1639 | ", | ||
1640 | &["impl Foo"], | ||
1641 | ); | ||
1642 | assert_debug_snapshot!(actions, | ||
1643 | @r###" | ||
1644 | [ | ||
1645 | GoToType( | ||
1646 | [ | ||
1647 | HoverGotoTypeData { | ||
1648 | mod_path: "Foo", | ||
1649 | nav: NavigationTarget { | ||
1650 | file_id: FileId( | ||
1651 | 1, | ||
1652 | ), | ||
1653 | full_range: 0..12, | ||
1654 | name: "Foo", | ||
1655 | kind: TRAIT_DEF, | ||
1656 | focus_range: Some( | ||
1657 | 6..9, | ||
1658 | ), | ||
1659 | container_name: None, | ||
1660 | description: Some( | ||
1661 | "trait Foo", | ||
1662 | ), | ||
1663 | docs: None, | ||
1664 | }, | ||
1665 | }, | ||
1666 | ], | ||
1667 | ), | ||
1668 | ] | ||
1669 | "###); | ||
1670 | } | ||
1671 | |||
1672 | #[test] | ||
1673 | fn test_hover_generic_return_impl_trait_has_goto_type_action() { | ||
1674 | let (_, actions) = check_hover_result( | ||
1675 | " | ||
1676 | //- /main.rs | ||
1677 | trait Foo<T> {} | ||
1678 | struct S; | ||
1679 | |||
1680 | fn foo() -> impl Foo<S> {} | ||
1681 | |||
1682 | fn main() { | ||
1683 | let s<|>t = foo(); | ||
1684 | } | ||
1685 | ", | ||
1686 | &["impl Foo<S>"], | ||
1687 | ); | ||
1688 | assert_debug_snapshot!(actions, | ||
1689 | @r###" | ||
1690 | [ | ||
1691 | GoToType( | ||
1692 | [ | ||
1693 | HoverGotoTypeData { | ||
1694 | mod_path: "Foo", | ||
1695 | nav: NavigationTarget { | ||
1696 | file_id: FileId( | ||
1697 | 1, | ||
1698 | ), | ||
1699 | full_range: 0..15, | ||
1700 | name: "Foo", | ||
1701 | kind: TRAIT_DEF, | ||
1702 | focus_range: Some( | ||
1703 | 6..9, | ||
1704 | ), | ||
1705 | container_name: None, | ||
1706 | description: Some( | ||
1707 | "trait Foo", | ||
1708 | ), | ||
1709 | docs: None, | ||
1710 | }, | ||
1711 | }, | ||
1712 | HoverGotoTypeData { | ||
1713 | mod_path: "S", | ||
1714 | nav: NavigationTarget { | ||
1715 | file_id: FileId( | ||
1716 | 1, | ||
1717 | ), | ||
1718 | full_range: 16..25, | ||
1719 | name: "S", | ||
1720 | kind: STRUCT_DEF, | ||
1721 | focus_range: Some( | ||
1722 | 23..24, | ||
1723 | ), | ||
1724 | container_name: None, | ||
1725 | description: Some( | ||
1726 | "struct S", | ||
1727 | ), | ||
1728 | docs: None, | ||
1729 | }, | ||
1730 | }, | ||
1731 | ], | ||
1732 | ), | ||
1733 | ] | ||
1734 | "###); | ||
1735 | } | ||
1736 | |||
1737 | #[test] | ||
1738 | fn test_hover_return_impl_traits_has_goto_type_action() { | ||
1739 | let (_, actions) = check_hover_result( | ||
1740 | " | ||
1741 | //- /main.rs | ||
1742 | trait Foo {} | ||
1743 | trait Bar {} | ||
1744 | |||
1745 | fn foo() -> impl Foo + Bar {} | ||
1746 | |||
1747 | fn main() { | ||
1748 | let s<|>t = foo(); | ||
1749 | } | ||
1750 | ", | ||
1751 | &["impl Foo + Bar"], | ||
1752 | ); | ||
1753 | assert_debug_snapshot!(actions, | ||
1754 | @r###" | ||
1755 | [ | ||
1756 | GoToType( | ||
1757 | [ | ||
1758 | HoverGotoTypeData { | ||
1759 | mod_path: "Foo", | ||
1760 | nav: NavigationTarget { | ||
1761 | file_id: FileId( | ||
1762 | 1, | ||
1763 | ), | ||
1764 | full_range: 0..12, | ||
1765 | name: "Foo", | ||
1766 | kind: TRAIT_DEF, | ||
1767 | focus_range: Some( | ||
1768 | 6..9, | ||
1769 | ), | ||
1770 | container_name: None, | ||
1771 | description: Some( | ||
1772 | "trait Foo", | ||
1773 | ), | ||
1774 | docs: None, | ||
1775 | }, | ||
1776 | }, | ||
1777 | HoverGotoTypeData { | ||
1778 | mod_path: "Bar", | ||
1779 | nav: NavigationTarget { | ||
1780 | file_id: FileId( | ||
1781 | 1, | ||
1782 | ), | ||
1783 | full_range: 13..25, | ||
1784 | name: "Bar", | ||
1785 | kind: TRAIT_DEF, | ||
1786 | focus_range: Some( | ||
1787 | 19..22, | ||
1788 | ), | ||
1789 | container_name: None, | ||
1790 | description: Some( | ||
1791 | "trait Bar", | ||
1792 | ), | ||
1793 | docs: None, | ||
1794 | }, | ||
1795 | }, | ||
1796 | ], | ||
1797 | ), | ||
1798 | ] | ||
1799 | "###); | ||
1800 | } | ||
1801 | |||
1802 | #[test] | ||
1803 | fn test_hover_generic_return_impl_traits_has_goto_type_action() { | ||
1804 | let (_, actions) = check_hover_result( | ||
1805 | " | ||
1806 | //- /main.rs | ||
1807 | trait Foo<T> {} | ||
1808 | trait Bar<T> {} | ||
1809 | struct S1 {} | ||
1810 | struct S2 {} | ||
1811 | |||
1812 | fn foo() -> impl Foo<S1> + Bar<S2> {} | ||
1813 | |||
1814 | fn main() { | ||
1815 | let s<|>t = foo(); | ||
1816 | } | ||
1817 | ", | ||
1818 | &["impl Foo<S1> + Bar<S2>"], | ||
1819 | ); | ||
1820 | assert_debug_snapshot!(actions, | ||
1821 | @r###" | ||
1822 | [ | ||
1823 | GoToType( | ||
1824 | [ | ||
1825 | HoverGotoTypeData { | ||
1826 | mod_path: "Foo", | ||
1827 | nav: NavigationTarget { | ||
1828 | file_id: FileId( | ||
1829 | 1, | ||
1830 | ), | ||
1831 | full_range: 0..15, | ||
1832 | name: "Foo", | ||
1833 | kind: TRAIT_DEF, | ||
1834 | focus_range: Some( | ||
1835 | 6..9, | ||
1836 | ), | ||
1837 | container_name: None, | ||
1838 | description: Some( | ||
1839 | "trait Foo", | ||
1840 | ), | ||
1841 | docs: None, | ||
1842 | }, | ||
1843 | }, | ||
1844 | HoverGotoTypeData { | ||
1845 | mod_path: "Bar", | ||
1846 | nav: NavigationTarget { | ||
1847 | file_id: FileId( | ||
1848 | 1, | ||
1849 | ), | ||
1850 | full_range: 16..31, | ||
1851 | name: "Bar", | ||
1852 | kind: TRAIT_DEF, | ||
1853 | focus_range: Some( | ||
1854 | 22..25, | ||
1855 | ), | ||
1856 | container_name: None, | ||
1857 | description: Some( | ||
1858 | "trait Bar", | ||
1859 | ), | ||
1860 | docs: None, | ||
1861 | }, | ||
1862 | }, | ||
1863 | HoverGotoTypeData { | ||
1864 | mod_path: "S1", | ||
1865 | nav: NavigationTarget { | ||
1866 | file_id: FileId( | ||
1867 | 1, | ||
1868 | ), | ||
1869 | full_range: 32..44, | ||
1870 | name: "S1", | ||
1871 | kind: STRUCT_DEF, | ||
1872 | focus_range: Some( | ||
1873 | 39..41, | ||
1874 | ), | ||
1875 | container_name: None, | ||
1876 | description: Some( | ||
1877 | "struct S1", | ||
1878 | ), | ||
1879 | docs: None, | ||
1880 | }, | ||
1881 | }, | ||
1882 | HoverGotoTypeData { | ||
1883 | mod_path: "S2", | ||
1884 | nav: NavigationTarget { | ||
1885 | file_id: FileId( | ||
1886 | 1, | ||
1887 | ), | ||
1888 | full_range: 45..57, | ||
1889 | name: "S2", | ||
1890 | kind: STRUCT_DEF, | ||
1891 | focus_range: Some( | ||
1892 | 52..54, | ||
1893 | ), | ||
1894 | container_name: None, | ||
1895 | description: Some( | ||
1896 | "struct S2", | ||
1897 | ), | ||
1898 | docs: None, | ||
1899 | }, | ||
1900 | }, | ||
1901 | ], | ||
1902 | ), | ||
1903 | ] | ||
1904 | "###); | ||
1905 | } | ||
1906 | |||
1907 | #[test] | ||
1908 | fn test_hover_arg_impl_trait_has_goto_type_action() { | ||
1909 | let (_, actions) = check_hover_result( | ||
1910 | " | ||
1911 | //- /lib.rs | ||
1912 | trait Foo {} | ||
1913 | fn foo(ar<|>g: &impl Foo) {} | ||
1914 | ", | ||
1915 | &["&impl Foo"], | ||
1916 | ); | ||
1917 | assert_debug_snapshot!(actions, | ||
1918 | @r###" | ||
1919 | [ | ||
1920 | GoToType( | ||
1921 | [ | ||
1922 | HoverGotoTypeData { | ||
1923 | mod_path: "Foo", | ||
1924 | nav: NavigationTarget { | ||
1925 | file_id: FileId( | ||
1926 | 1, | ||
1927 | ), | ||
1928 | full_range: 0..12, | ||
1929 | name: "Foo", | ||
1930 | kind: TRAIT_DEF, | ||
1931 | focus_range: Some( | ||
1932 | 6..9, | ||
1933 | ), | ||
1934 | container_name: None, | ||
1935 | description: Some( | ||
1936 | "trait Foo", | ||
1937 | ), | ||
1938 | docs: None, | ||
1939 | }, | ||
1940 | }, | ||
1941 | ], | ||
1942 | ), | ||
1943 | ] | ||
1944 | "###); | ||
1945 | } | ||
1946 | |||
1947 | #[test] | ||
1948 | fn test_hover_arg_impl_traits_has_goto_type_action() { | ||
1949 | let (_, actions) = check_hover_result( | ||
1950 | " | ||
1951 | //- /lib.rs | ||
1952 | trait Foo {} | ||
1953 | trait Bar<T> {} | ||
1954 | struct S{} | ||
1955 | |||
1956 | fn foo(ar<|>g: &impl Foo + Bar<S>) {} | ||
1957 | ", | ||
1958 | &["&impl Foo + Bar<S>"], | ||
1959 | ); | ||
1960 | assert_debug_snapshot!(actions, | ||
1961 | @r###" | ||
1962 | [ | ||
1963 | GoToType( | ||
1964 | [ | ||
1965 | HoverGotoTypeData { | ||
1966 | mod_path: "Foo", | ||
1967 | nav: NavigationTarget { | ||
1968 | file_id: FileId( | ||
1969 | 1, | ||
1970 | ), | ||
1971 | full_range: 0..12, | ||
1972 | name: "Foo", | ||
1973 | kind: TRAIT_DEF, | ||
1974 | focus_range: Some( | ||
1975 | 6..9, | ||
1976 | ), | ||
1977 | container_name: None, | ||
1978 | description: Some( | ||
1979 | "trait Foo", | ||
1980 | ), | ||
1981 | docs: None, | ||
1982 | }, | ||
1983 | }, | ||
1984 | HoverGotoTypeData { | ||
1985 | mod_path: "Bar", | ||
1986 | nav: NavigationTarget { | ||
1987 | file_id: FileId( | ||
1988 | 1, | ||
1989 | ), | ||
1990 | full_range: 13..28, | ||
1991 | name: "Bar", | ||
1992 | kind: TRAIT_DEF, | ||
1993 | focus_range: Some( | ||
1994 | 19..22, | ||
1995 | ), | ||
1996 | container_name: None, | ||
1997 | description: Some( | ||
1998 | "trait Bar", | ||
1999 | ), | ||
2000 | docs: None, | ||
2001 | }, | ||
2002 | }, | ||
2003 | HoverGotoTypeData { | ||
2004 | mod_path: "S", | ||
2005 | nav: NavigationTarget { | ||
2006 | file_id: FileId( | ||
2007 | 1, | ||
2008 | ), | ||
2009 | full_range: 29..39, | ||
2010 | name: "S", | ||
2011 | kind: STRUCT_DEF, | ||
2012 | focus_range: Some( | ||
2013 | 36..37, | ||
2014 | ), | ||
2015 | container_name: None, | ||
2016 | description: Some( | ||
2017 | "struct S", | ||
2018 | ), | ||
2019 | docs: None, | ||
2020 | }, | ||
2021 | }, | ||
2022 | ], | ||
2023 | ), | ||
2024 | ] | ||
2025 | "###); | ||
2026 | } | ||
2027 | |||
2028 | #[test] | ||
2029 | fn test_hover_arg_generic_impl_trait_has_goto_type_action() { | ||
2030 | let (_, actions) = check_hover_result( | ||
2031 | " | ||
2032 | //- /lib.rs | ||
2033 | trait Foo<T> {} | ||
2034 | struct S {} | ||
2035 | fn foo(ar<|>g: &impl Foo<S>) {} | ||
2036 | ", | ||
2037 | &["&impl Foo<S>"], | ||
2038 | ); | ||
2039 | assert_debug_snapshot!(actions, | ||
2040 | @r###" | ||
2041 | [ | ||
2042 | GoToType( | ||
2043 | [ | ||
2044 | HoverGotoTypeData { | ||
2045 | mod_path: "Foo", | ||
2046 | nav: NavigationTarget { | ||
2047 | file_id: FileId( | ||
2048 | 1, | ||
2049 | ), | ||
2050 | full_range: 0..15, | ||
2051 | name: "Foo", | ||
2052 | kind: TRAIT_DEF, | ||
2053 | focus_range: Some( | ||
2054 | 6..9, | ||
2055 | ), | ||
2056 | container_name: None, | ||
2057 | description: Some( | ||
2058 | "trait Foo", | ||
2059 | ), | ||
2060 | docs: None, | ||
2061 | }, | ||
2062 | }, | ||
2063 | HoverGotoTypeData { | ||
2064 | mod_path: "S", | ||
2065 | nav: NavigationTarget { | ||
2066 | file_id: FileId( | ||
2067 | 1, | ||
2068 | ), | ||
2069 | full_range: 16..27, | ||
2070 | name: "S", | ||
2071 | kind: STRUCT_DEF, | ||
2072 | focus_range: Some( | ||
2073 | 23..24, | ||
2074 | ), | ||
2075 | container_name: None, | ||
2076 | description: Some( | ||
2077 | "struct S", | ||
2078 | ), | ||
2079 | docs: None, | ||
2080 | }, | ||
2081 | }, | ||
2082 | ], | ||
2083 | ), | ||
2084 | ] | ||
2085 | "###); | ||
2086 | } | ||
2087 | |||
2088 | #[test] | ||
2089 | fn test_hover_dyn_return_has_goto_type_action() { | ||
2090 | let (_, actions) = check_hover_result( | ||
2091 | " | ||
2092 | //- /main.rs | ||
2093 | trait Foo {} | ||
2094 | struct S; | ||
2095 | impl Foo for S {} | ||
2096 | |||
2097 | struct B<T>{} | ||
2098 | |||
2099 | fn foo() -> B<dyn Foo> {} | ||
2100 | |||
2101 | fn main() { | ||
2102 | let s<|>t = foo(); | ||
2103 | } | ||
2104 | ", | ||
2105 | &["B<dyn Foo>"], | ||
2106 | ); | ||
2107 | assert_debug_snapshot!(actions, | ||
2108 | @r###" | ||
2109 | [ | ||
2110 | GoToType( | ||
2111 | [ | ||
2112 | HoverGotoTypeData { | ||
2113 | mod_path: "B", | ||
2114 | nav: NavigationTarget { | ||
2115 | file_id: FileId( | ||
2116 | 1, | ||
2117 | ), | ||
2118 | full_range: 41..54, | ||
2119 | name: "B", | ||
2120 | kind: STRUCT_DEF, | ||
2121 | focus_range: Some( | ||
2122 | 48..49, | ||
2123 | ), | ||
2124 | container_name: None, | ||
2125 | description: Some( | ||
2126 | "struct B", | ||
2127 | ), | ||
2128 | docs: None, | ||
2129 | }, | ||
2130 | }, | ||
2131 | HoverGotoTypeData { | ||
2132 | mod_path: "Foo", | ||
2133 | nav: NavigationTarget { | ||
2134 | file_id: FileId( | ||
2135 | 1, | ||
2136 | ), | ||
2137 | full_range: 0..12, | ||
2138 | name: "Foo", | ||
2139 | kind: TRAIT_DEF, | ||
2140 | focus_range: Some( | ||
2141 | 6..9, | ||
2142 | ), | ||
2143 | container_name: None, | ||
2144 | description: Some( | ||
2145 | "trait Foo", | ||
2146 | ), | ||
2147 | docs: None, | ||
2148 | }, | ||
2149 | }, | ||
2150 | ], | ||
2151 | ), | ||
2152 | ] | ||
2153 | "###); | ||
2154 | } | ||
2155 | |||
2156 | #[test] | ||
2157 | fn test_hover_dyn_arg_has_goto_type_action() { | ||
2158 | let (_, actions) = check_hover_result( | ||
2159 | " | ||
2160 | //- /lib.rs | ||
2161 | trait Foo {} | ||
2162 | fn foo(ar<|>g: &dyn Foo) {} | ||
2163 | ", | ||
2164 | &["&dyn Foo"], | ||
2165 | ); | ||
2166 | assert_debug_snapshot!(actions, | ||
2167 | @r###" | ||
2168 | [ | ||
2169 | GoToType( | ||
2170 | [ | ||
2171 | HoverGotoTypeData { | ||
2172 | mod_path: "Foo", | ||
2173 | nav: NavigationTarget { | ||
2174 | file_id: FileId( | ||
2175 | 1, | ||
2176 | ), | ||
2177 | full_range: 0..12, | ||
2178 | name: "Foo", | ||
2179 | kind: TRAIT_DEF, | ||
2180 | focus_range: Some( | ||
2181 | 6..9, | ||
2182 | ), | ||
2183 | container_name: None, | ||
2184 | description: Some( | ||
2185 | "trait Foo", | ||
2186 | ), | ||
2187 | docs: None, | ||
2188 | }, | ||
2189 | }, | ||
2190 | ], | ||
2191 | ), | ||
2192 | ] | ||
2193 | "###); | ||
2194 | } | ||
2195 | |||
2196 | #[test] | ||
2197 | fn test_hover_generic_dyn_arg_has_goto_type_action() { | ||
2198 | let (_, actions) = check_hover_result( | ||
2199 | " | ||
2200 | //- /lib.rs | ||
2201 | trait Foo<T> {} | ||
2202 | struct S {} | ||
2203 | fn foo(ar<|>g: &dyn Foo<S>) {} | ||
2204 | ", | ||
2205 | &["&dyn Foo<S>"], | ||
2206 | ); | ||
2207 | assert_debug_snapshot!(actions, | ||
2208 | @r###" | ||
2209 | [ | ||
2210 | GoToType( | ||
2211 | [ | ||
2212 | HoverGotoTypeData { | ||
2213 | mod_path: "Foo", | ||
2214 | nav: NavigationTarget { | ||
2215 | file_id: FileId( | ||
2216 | 1, | ||
2217 | ), | ||
2218 | full_range: 0..15, | ||
2219 | name: "Foo", | ||
2220 | kind: TRAIT_DEF, | ||
2221 | focus_range: Some( | ||
2222 | 6..9, | ||
2223 | ), | ||
2224 | container_name: None, | ||
2225 | description: Some( | ||
2226 | "trait Foo", | ||
2227 | ), | ||
2228 | docs: None, | ||
2229 | }, | ||
2230 | }, | ||
2231 | HoverGotoTypeData { | ||
2232 | mod_path: "S", | ||
2233 | nav: NavigationTarget { | ||
2234 | file_id: FileId( | ||
2235 | 1, | ||
2236 | ), | ||
2237 | full_range: 16..27, | ||
2238 | name: "S", | ||
2239 | kind: STRUCT_DEF, | ||
2240 | focus_range: Some( | ||
2241 | 23..24, | ||
2242 | ), | ||
2243 | container_name: None, | ||
2244 | description: Some( | ||
2245 | "struct S", | ||
2246 | ), | ||
2247 | docs: None, | ||
2248 | }, | ||
2249 | }, | ||
2250 | ], | ||
2251 | ), | ||
2252 | ] | ||
2253 | "###); | ||
2254 | } | ||
2255 | |||
2256 | #[test] | ||
2257 | fn test_hover_goto_type_action_links_order() { | ||
2258 | let (_, actions) = check_hover_result( | ||
2259 | " | ||
2260 | //- /lib.rs | ||
2261 | trait ImplTrait<T> {} | ||
2262 | trait DynTrait<T> {} | ||
2263 | struct B<T> {} | ||
2264 | struct S {} | ||
2265 | |||
2266 | fn foo(a<|>rg: &impl ImplTrait<B<dyn DynTrait<B<S>>>>) {} | ||
2267 | ", | ||
2268 | &["&impl ImplTrait<B<dyn DynTrait<B<S>>>>"], | ||
2269 | ); | ||
2270 | assert_debug_snapshot!(actions, | ||
2271 | @r###" | ||
2272 | [ | ||
2273 | GoToType( | ||
2274 | [ | ||
2275 | HoverGotoTypeData { | ||
2276 | mod_path: "ImplTrait", | ||
2277 | nav: NavigationTarget { | ||
2278 | file_id: FileId( | ||
2279 | 1, | ||
2280 | ), | ||
2281 | full_range: 0..21, | ||
2282 | name: "ImplTrait", | ||
2283 | kind: TRAIT_DEF, | ||
2284 | focus_range: Some( | ||
2285 | 6..15, | ||
2286 | ), | ||
2287 | container_name: None, | ||
2288 | description: Some( | ||
2289 | "trait ImplTrait", | ||
2290 | ), | ||
2291 | docs: None, | ||
2292 | }, | ||
2293 | }, | ||
2294 | HoverGotoTypeData { | ||
2295 | mod_path: "B", | ||
2296 | nav: NavigationTarget { | ||
2297 | file_id: FileId( | ||
2298 | 1, | ||
2299 | ), | ||
2300 | full_range: 43..57, | ||
2301 | name: "B", | ||
2302 | kind: STRUCT_DEF, | ||
2303 | focus_range: Some( | ||
2304 | 50..51, | ||
2305 | ), | ||
2306 | container_name: None, | ||
2307 | description: Some( | ||
2308 | "struct B", | ||
2309 | ), | ||
2310 | docs: None, | ||
2311 | }, | ||
2312 | }, | ||
2313 | HoverGotoTypeData { | ||
2314 | mod_path: "DynTrait", | ||
2315 | nav: NavigationTarget { | ||
2316 | file_id: FileId( | ||
2317 | 1, | ||
2318 | ), | ||
2319 | full_range: 22..42, | ||
2320 | name: "DynTrait", | ||
2321 | kind: TRAIT_DEF, | ||
2322 | focus_range: Some( | ||
2323 | 28..36, | ||
2324 | ), | ||
2325 | container_name: None, | ||
2326 | description: Some( | ||
2327 | "trait DynTrait", | ||
2328 | ), | ||
2329 | docs: None, | ||
2330 | }, | ||
2331 | }, | ||
2332 | HoverGotoTypeData { | ||
2333 | mod_path: "S", | ||
2334 | nav: NavigationTarget { | ||
2335 | file_id: FileId( | ||
2336 | 1, | ||
2337 | ), | ||
2338 | full_range: 58..69, | ||
2339 | name: "S", | ||
2340 | kind: STRUCT_DEF, | ||
2341 | focus_range: Some( | ||
2342 | 65..66, | ||
2343 | ), | ||
2344 | container_name: None, | ||
2345 | description: Some( | ||
2346 | "struct S", | ||
2347 | ), | ||
2348 | docs: None, | ||
2349 | }, | ||
2350 | }, | ||
2351 | ], | ||
2352 | ), | ||
2353 | ] | ||
2354 | "###); | ||
2355 | } | ||
2356 | |||
2357 | #[test] | ||
2358 | fn test_hover_associated_type_has_goto_type_action() { | ||
2359 | let (_, actions) = check_hover_result( | ||
2360 | " | ||
2361 | //- /main.rs | ||
2362 | trait Foo { | ||
2363 | type Item; | ||
2364 | fn get(self) -> Self::Item {} | ||
2365 | } | ||
2366 | |||
2367 | struct Bar{} | ||
2368 | struct S{} | ||
2369 | |||
2370 | impl Foo for S{ | ||
2371 | type Item = Bar; | ||
2372 | } | ||
2373 | |||
2374 | fn test() -> impl Foo { | ||
2375 | S{} | ||
2376 | } | ||
2377 | |||
2378 | fn main() { | ||
2379 | let s<|>t = test().get(); | ||
2380 | } | ||
2381 | ", | ||
2382 | &["Foo::Item<impl Foo>"], | ||
2383 | ); | ||
2384 | assert_debug_snapshot!(actions, | ||
2385 | @r###" | ||
2386 | [ | ||
2387 | GoToType( | ||
2388 | [ | ||
2389 | HoverGotoTypeData { | ||
2390 | mod_path: "Foo", | ||
2391 | nav: NavigationTarget { | ||
2392 | file_id: FileId( | ||
2393 | 1, | ||
2394 | ), | ||
2395 | full_range: 0..62, | ||
2396 | name: "Foo", | ||
2397 | kind: TRAIT_DEF, | ||
2398 | focus_range: Some( | ||
2399 | 6..9, | ||
2400 | ), | ||
2401 | container_name: None, | ||
2402 | description: Some( | ||
2403 | "trait Foo", | ||
2404 | ), | ||
2405 | docs: None, | ||
2406 | }, | ||
2407 | }, | ||
2408 | ], | ||
2409 | ), | ||
2410 | ] | ||
2411 | "###); | ||
2412 | } | ||
1313 | } | 2413 | } |
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs index 28f686767..be9ab62c0 100644 --- a/crates/ra_ide/src/lib.rs +++ b/crates/ra_ide/src/lib.rs | |||
@@ -66,7 +66,7 @@ pub use crate::{ | |||
66 | display::{file_structure, FunctionSignature, NavigationTarget, StructureNode}, | 66 | display::{file_structure, FunctionSignature, NavigationTarget, StructureNode}, |
67 | expand_macro::ExpandedMacro, | 67 | expand_macro::ExpandedMacro, |
68 | folding_ranges::{Fold, FoldKind}, | 68 | folding_ranges::{Fold, FoldKind}, |
69 | hover::{HoverAction, HoverConfig, HoverResult}, | 69 | hover::{HoverAction, HoverConfig, HoverGotoTypeData, HoverResult}, |
70 | inlay_hints::{InlayHint, InlayHintsConfig, InlayKind}, | 70 | inlay_hints::{InlayHint, InlayHintsConfig, InlayKind}, |
71 | references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult}, | 71 | references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult}, |
72 | runnables::{Runnable, RunnableKind, TestId}, | 72 | runnables::{Runnable, RunnableKind, TestId}, |
@@ -82,7 +82,7 @@ pub use ra_db::{ | |||
82 | Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRootId, | 82 | Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRootId, |
83 | }; | 83 | }; |
84 | pub use ra_ide_db::{ | 84 | pub use ra_ide_db::{ |
85 | change::{AnalysisChange, LibraryData}, | 85 | change::AnalysisChange, |
86 | line_index::{LineCol, LineIndex}, | 86 | line_index::{LineCol, LineIndex}, |
87 | search::SearchScope, | 87 | search::SearchScope, |
88 | source_change::{FileSystemEdit, SourceChange, SourceFileEdit}, | 88 | source_change::{FileSystemEdit, SourceChange, SourceFileEdit}, |
@@ -440,12 +440,14 @@ impl Analysis { | |||
440 | 440 | ||
441 | /// Computes syntax highlighting for the given file | 441 | /// Computes syntax highlighting for the given file |
442 | pub fn highlight(&self, file_id: FileId) -> Cancelable<Vec<HighlightedRange>> { | 442 | pub fn highlight(&self, file_id: FileId) -> Cancelable<Vec<HighlightedRange>> { |
443 | self.with_db(|db| syntax_highlighting::highlight(db, file_id, None)) | 443 | self.with_db(|db| syntax_highlighting::highlight(db, file_id, None, false)) |
444 | } | 444 | } |
445 | 445 | ||
446 | /// Computes syntax highlighting for the given file range. | 446 | /// Computes syntax highlighting for the given file range. |
447 | pub fn highlight_range(&self, frange: FileRange) -> Cancelable<Vec<HighlightedRange>> { | 447 | pub fn highlight_range(&self, frange: FileRange) -> Cancelable<Vec<HighlightedRange>> { |
448 | self.with_db(|db| syntax_highlighting::highlight(db, frange.file_id, Some(frange.range))) | 448 | self.with_db(|db| { |
449 | syntax_highlighting::highlight(db, frange.file_id, Some(frange.range), false) | ||
450 | }) | ||
449 | } | 451 | } |
450 | 452 | ||
451 | /// Computes syntax highlighting for the given file. | 453 | /// Computes syntax highlighting for the given file. |
diff --git a/crates/ra_ide/src/prime_caches.rs b/crates/ra_ide/src/prime_caches.rs index 90bf7d25f..c5ab5a1d8 100644 --- a/crates/ra_ide/src/prime_caches.rs +++ b/crates/ra_ide/src/prime_caches.rs | |||
@@ -7,6 +7,6 @@ use crate::{FileId, RootDatabase}; | |||
7 | 7 | ||
8 | pub(crate) fn prime_caches(db: &RootDatabase, files: Vec<FileId>) { | 8 | pub(crate) fn prime_caches(db: &RootDatabase, files: Vec<FileId>) { |
9 | for file in files { | 9 | for file in files { |
10 | let _ = crate::syntax_highlighting::highlight(db, file, None); | 10 | let _ = crate::syntax_highlighting::highlight(db, file, None, false); |
11 | } | 11 | } |
12 | } | 12 | } |
diff --git a/crates/ra_ide/src/references/rename.rs b/crates/ra_ide/src/references/rename.rs index 915d4f4d3..c4f07f905 100644 --- a/crates/ra_ide/src/references/rename.rs +++ b/crates/ra_ide/src/references/rename.rs | |||
@@ -1,7 +1,7 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! FIXME: write short doc here |
2 | 2 | ||
3 | use hir::{ModuleSource, Semantics}; | 3 | use hir::{ModuleSource, Semantics}; |
4 | use ra_db::{RelativePath, RelativePathBuf, SourceDatabaseExt}; | 4 | use ra_db::{RelativePathBuf, SourceDatabaseExt}; |
5 | use ra_ide_db::RootDatabase; | 5 | use ra_ide_db::RootDatabase; |
6 | use ra_syntax::{ | 6 | use ra_syntax::{ |
7 | algo::find_node_at_offset, ast, ast::TypeAscriptionOwner, lex_single_valid_syntax_kind, | 7 | algo::find_node_at_offset, ast, ast::TypeAscriptionOwner, lex_single_valid_syntax_kind, |
@@ -92,23 +92,14 @@ fn rename_mod( | |||
92 | ModuleSource::SourceFile(..) => { | 92 | ModuleSource::SourceFile(..) => { |
93 | let mod_path: RelativePathBuf = sema.db.file_relative_path(file_id); | 93 | let mod_path: RelativePathBuf = sema.db.file_relative_path(file_id); |
94 | // mod is defined in path/to/dir/mod.rs | 94 | // mod is defined in path/to/dir/mod.rs |
95 | let dst_path = if mod_path.file_stem() == Some("mod") { | 95 | let dst = if mod_path.file_stem() == Some("mod") { |
96 | mod_path | 96 | format!("../{}/mod.rs", new_name) |
97 | .parent() | ||
98 | .and_then(|p| p.parent()) | ||
99 | .or_else(|| Some(RelativePath::new(""))) | ||
100 | .map(|p| p.join(new_name).join("mod.rs")) | ||
101 | } else { | 97 | } else { |
102 | Some(mod_path.with_file_name(new_name).with_extension("rs")) | 98 | format!("{}.rs", new_name) |
103 | }; | 99 | }; |
104 | if let Some(path) = dst_path { | 100 | let move_file = |
105 | let move_file = FileSystemEdit::MoveFile { | 101 | FileSystemEdit::MoveFile { src: file_id, anchor: position.file_id, dst }; |
106 | src: file_id, | 102 | file_system_edits.push(move_file); |
107 | dst_source_root: sema.db.file_source_root(position.file_id), | ||
108 | dst_path: path, | ||
109 | }; | ||
110 | file_system_edits.push(move_file); | ||
111 | } | ||
112 | } | 103 | } |
113 | ModuleSource::Module(..) => {} | 104 | ModuleSource::Module(..) => {} |
114 | } | 105 | } |
@@ -623,16 +614,16 @@ mod tests { | |||
623 | #[test] | 614 | #[test] |
624 | fn test_rename_mod() { | 615 | fn test_rename_mod() { |
625 | let (analysis, position) = analysis_and_position( | 616 | let (analysis, position) = analysis_and_position( |
626 | " | 617 | r#" |
627 | //- /lib.rs | 618 | //- /lib.rs |
628 | mod bar; | 619 | mod bar; |
629 | 620 | ||
630 | //- /bar.rs | 621 | //- /bar.rs |
631 | mod foo<|>; | 622 | mod foo<|>; |
632 | 623 | ||
633 | //- /bar/foo.rs | 624 | //- /bar/foo.rs |
634 | // emtpy | 625 | // emtpy |
635 | ", | 626 | "#, |
636 | ); | 627 | ); |
637 | let new_name = "foo2"; | 628 | let new_name = "foo2"; |
638 | let source_change = analysis.rename(position, new_name).unwrap(); | 629 | let source_change = analysis.rename(position, new_name).unwrap(); |
@@ -662,10 +653,10 @@ mod tests { | |||
662 | src: FileId( | 653 | src: FileId( |
663 | 3, | 654 | 3, |
664 | ), | 655 | ), |
665 | dst_source_root: SourceRootId( | 656 | anchor: FileId( |
666 | 0, | 657 | 2, |
667 | ), | 658 | ), |
668 | dst_path: "bar/foo2.rs", | 659 | dst: "foo2.rs", |
669 | }, | 660 | }, |
670 | ], | 661 | ], |
671 | is_snippet: false, | 662 | is_snippet: false, |
@@ -678,12 +669,12 @@ mod tests { | |||
678 | #[test] | 669 | #[test] |
679 | fn test_rename_mod_in_dir() { | 670 | fn test_rename_mod_in_dir() { |
680 | let (analysis, position) = analysis_and_position( | 671 | let (analysis, position) = analysis_and_position( |
681 | " | 672 | r#" |
682 | //- /lib.rs | 673 | //- /lib.rs |
683 | mod fo<|>o; | 674 | mod fo<|>o; |
684 | //- /foo/mod.rs | 675 | //- /foo/mod.rs |
685 | // emtpy | 676 | // emtpy |
686 | ", | 677 | "#, |
687 | ); | 678 | ); |
688 | let new_name = "foo2"; | 679 | let new_name = "foo2"; |
689 | let source_change = analysis.rename(position, new_name).unwrap(); | 680 | let source_change = analysis.rename(position, new_name).unwrap(); |
@@ -713,10 +704,10 @@ mod tests { | |||
713 | src: FileId( | 704 | src: FileId( |
714 | 2, | 705 | 2, |
715 | ), | 706 | ), |
716 | dst_source_root: SourceRootId( | 707 | anchor: FileId( |
717 | 0, | 708 | 1, |
718 | ), | 709 | ), |
719 | dst_path: "foo2/mod.rs", | 710 | dst: "../foo2/mod.rs", |
720 | }, | 711 | }, |
721 | ], | 712 | ], |
722 | is_snippet: false, | 713 | is_snippet: false, |
@@ -753,19 +744,19 @@ mod tests { | |||
753 | #[test] | 744 | #[test] |
754 | fn test_rename_mod_filename_and_path() { | 745 | fn test_rename_mod_filename_and_path() { |
755 | let (analysis, position) = analysis_and_position( | 746 | let (analysis, position) = analysis_and_position( |
756 | " | 747 | r#" |
757 | //- /lib.rs | 748 | //- /lib.rs |
758 | mod bar; | 749 | mod bar; |
759 | fn f() { | 750 | fn f() { |
760 | bar::foo::fun() | 751 | bar::foo::fun() |
761 | } | 752 | } |
762 | 753 | ||
763 | //- /bar.rs | 754 | //- /bar.rs |
764 | pub mod foo<|>; | 755 | pub mod foo<|>; |
765 | 756 | ||
766 | //- /bar/foo.rs | 757 | //- /bar/foo.rs |
767 | // pub fn fun() {} | 758 | // pub fn fun() {} |
768 | ", | 759 | "#, |
769 | ); | 760 | ); |
770 | let new_name = "foo2"; | 761 | let new_name = "foo2"; |
771 | let source_change = analysis.rename(position, new_name).unwrap(); | 762 | let source_change = analysis.rename(position, new_name).unwrap(); |
@@ -808,10 +799,10 @@ mod tests { | |||
808 | src: FileId( | 799 | src: FileId( |
809 | 3, | 800 | 3, |
810 | ), | 801 | ), |
811 | dst_source_root: SourceRootId( | 802 | anchor: FileId( |
812 | 0, | 803 | 2, |
813 | ), | 804 | ), |
814 | dst_path: "bar/foo2.rs", | 805 | dst: "foo2.rs", |
815 | }, | 806 | }, |
816 | ], | 807 | ], |
817 | is_snippet: false, | 808 | is_snippet: false, |
diff --git a/crates/ra_ide/src/runnables.rs b/crates/ra_ide/src/runnables.rs index fc57dc33d..8105ef373 100644 --- a/crates/ra_ide/src/runnables.rs +++ b/crates/ra_ide/src/runnables.rs | |||
@@ -171,7 +171,15 @@ fn runnable_fn( | |||
171 | let cfg_exprs = | 171 | let cfg_exprs = |
172 | attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect(); | 172 | attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect(); |
173 | 173 | ||
174 | let nav = NavigationTarget::from_named(sema.db, InFile::new(file_id.into(), &fn_def)); | 174 | let nav = if let RunnableKind::DocTest { .. } = kind { |
175 | NavigationTarget::from_doc_commented( | ||
176 | sema.db, | ||
177 | InFile::new(file_id.into(), &fn_def), | ||
178 | InFile::new(file_id.into(), &fn_def), | ||
179 | ) | ||
180 | } else { | ||
181 | NavigationTarget::from_named(sema.db, InFile::new(file_id.into(), &fn_def)) | ||
182 | }; | ||
175 | Some(Runnable { nav, kind, cfg_exprs }) | 183 | Some(Runnable { nav, kind, cfg_exprs }) |
176 | } | 184 | } |
177 | 185 | ||
@@ -419,9 +427,7 @@ mod tests { | |||
419 | full_range: 22..64, | 427 | full_range: 22..64, |
420 | name: "foo", | 428 | name: "foo", |
421 | kind: FN_DEF, | 429 | kind: FN_DEF, |
422 | focus_range: Some( | 430 | focus_range: None, |
423 | 56..59, | ||
424 | ), | ||
425 | container_name: None, | 431 | container_name: None, |
426 | description: None, | 432 | description: None, |
427 | docs: None, | 433 | docs: None, |
@@ -486,9 +492,7 @@ mod tests { | |||
486 | full_range: 51..105, | 492 | full_range: 51..105, |
487 | name: "foo", | 493 | name: "foo", |
488 | kind: FN_DEF, | 494 | kind: FN_DEF, |
489 | focus_range: Some( | 495 | focus_range: None, |
490 | 97..100, | ||
491 | ), | ||
492 | container_name: None, | 496 | container_name: None, |
493 | description: None, | 497 | description: None, |
494 | docs: None, | 498 | docs: None, |
diff --git a/crates/ra_ide/src/snapshots/highlight_doctest.html b/crates/ra_ide/src/snapshots/highlight_doctest.html index 0ae8c7efc..f61c0daa5 100644 --- a/crates/ra_ide/src/snapshots/highlight_doctest.html +++ b/crates/ra_ide/src/snapshots/highlight_doctest.html | |||
@@ -25,46 +25,62 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
25 | .variable { color: #DCDCCC; } | 25 | .variable { color: #DCDCCC; } |
26 | .format_specifier { color: #CC696B; } | 26 | .format_specifier { color: #CC696B; } |
27 | .mutable { text-decoration: underline; } | 27 | .mutable { text-decoration: underline; } |
28 | .unresolved_reference { color: #FC5555; } | ||
29 | .escape_sequence { color: #94BFF3; } | ||
28 | 30 | ||
29 | .keyword { color: #F0DFAF; font-weight: bold; } | 31 | .keyword { color: #F0DFAF; font-weight: bold; } |
30 | .keyword.unsafe { color: #BC8383; font-weight: bold; } | 32 | .keyword.unsafe { color: #BC8383; font-weight: bold; } |
31 | .control { font-style: italic; } | 33 | .control { font-style: italic; } |
32 | </style> | 34 | </style> |
33 | <pre><code><span class="keyword">impl</span> <span class="unresolved_reference">Foo</span> { | 35 | <pre><code><span class="keyword">struct</span> <span class="struct declaration">Foo</span> { |
34 | <span class="comment">/// Constructs a new `Foo`.</span> | 36 | <span class="field declaration">bar</span>: <span class="builtin_type">bool</span>, |
35 | <span class="comment">///</span> | 37 | } |
36 | <span class="comment">/// # Examples</span> | 38 | |
37 | <span class="comment">///</span> | 39 | <span class="keyword">impl</span> <span class="struct">Foo</span> { |
38 | <span class="comment">/// ```</span> | 40 | <span class="keyword">pub</span> <span class="keyword">const</span> <span class="constant declaration">bar</span>: <span class="builtin_type">bool</span> = <span class="bool_literal">true</span>; |
39 | <span class="comment">/// #</span> <span class="attribute">#![</span><span class="function attribute">allow</span><span class="attribute">(unused_mut)]</span> | 41 | |
40 | <span class="comment">/// </span><span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">foo</span>: <span class="unresolved_reference">Foo</span> = <span class="unresolved_reference">Foo</span>::<span class="unresolved_reference">new</span>(); | 42 | <span class="comment documentation">/// Constructs a new `Foo`.</span> |
41 | <span class="comment">/// ```</span> | 43 | <span class="comment documentation">///</span> |
42 | <span class="keyword">pub</span> <span class="keyword">const</span> <span class="keyword">fn</span> <span class="function declaration">new</span>() -> <span class="unresolved_reference">Foo</span> { | 44 | <span class="comment documentation">/// # Examples</span> |
43 | <span class="unresolved_reference">Foo</span> { } | 45 | <span class="comment documentation">///</span> |
46 | <span class="comment documentation">/// ```</span> | ||
47 | <span class="comment documentation">/// #</span> <span class="attribute">#![</span><span class="function attribute">allow</span><span class="attribute">(unused_mut)]</span> | ||
48 | <span class="comment documentation">/// </span><span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">foo</span>: <span class="struct">Foo</span> = <span class="struct">Foo</span>::<span class="function">new</span>(); | ||
49 | <span class="comment documentation">/// ```</span> | ||
50 | <span class="keyword">pub</span> <span class="keyword">const</span> <span class="keyword">fn</span> <span class="function declaration">new</span>() -> <span class="struct">Foo</span> { | ||
51 | <span class="struct">Foo</span> { <span class="field">bar</span>: <span class="bool_literal">true</span> } | ||
44 | } | 52 | } |
45 | 53 | ||
46 | <span class="comment">/// `bar` method on `Foo`.</span> | 54 | <span class="comment documentation">/// `bar` method on `Foo`.</span> |
47 | <span class="comment">///</span> | 55 | <span class="comment documentation">///</span> |
48 | <span class="comment">/// # Examples</span> | 56 | <span class="comment documentation">/// # Examples</span> |
49 | <span class="comment">///</span> | 57 | <span class="comment documentation">///</span> |
50 | <span class="comment">/// ```</span> | 58 | <span class="comment documentation">/// ```</span> |
51 | <span class="comment">/// </span><span class="keyword">let</span> <span class="variable declaration">foo</span> = <span class="unresolved_reference">Foo</span>::<span class="unresolved_reference">new</span>(); | 59 | <span class="comment documentation">/// </span><span class="keyword">use</span> <span class="module">x</span>::<span class="module">y</span>; |
52 | <span class="comment">///</span> | 60 | <span class="comment documentation">///</span> |
53 | <span class="comment">/// </span><span class="comment">// calls bar on foo</span> | 61 | <span class="comment documentation">/// </span><span class="keyword">let</span> <span class="variable declaration">foo</span> = <span class="struct">Foo</span>::<span class="function">new</span>(); |
54 | <span class="comment">/// </span><span class="macro">assert!</span>(foo.bar()); | 62 | <span class="comment documentation">///</span> |
55 | <span class="comment">///</span> | 63 | <span class="comment documentation">/// </span><span class="comment">// calls bar on foo</span> |
56 | <span class="comment">/// </span><span class="comment">/* multi-line | 64 | <span class="comment documentation">/// </span><span class="macro">assert!</span>(foo.bar()); |
57 | </span><span class="comment">/// </span><span class="comment"> comment */</span> | 65 | <span class="comment documentation">///</span> |
58 | <span class="comment">///</span> | 66 | <span class="comment documentation">/// </span><span class="keyword">let</span> <span class="variable declaration">bar</span> = <span class="variable">foo</span>.<span class="field">bar</span> || <span class="struct">Foo</span>::<span class="constant">bar</span>; |
59 | <span class="comment">/// </span><span class="keyword">let</span> <span class="variable declaration">multi_line_string</span> = <span class="string_literal">"Foo | 67 | <span class="comment documentation">///</span> |
60 | </span><span class="comment">/// </span><span class="string_literal"> bar | 68 | <span class="comment documentation">/// </span><span class="comment">/* multi-line |
61 | </span><span class="comment">/// </span><span class="string_literal"> "</span>; | 69 | </span><span class="comment documentation">/// </span><span class="comment"> comment */</span> |
62 | <span class="comment">///</span> | 70 | <span class="comment documentation">///</span> |
63 | <span class="comment">/// ```</span> | 71 | <span class="comment documentation">/// </span><span class="keyword">let</span> <span class="variable declaration">multi_line_string</span> = <span class="string_literal">"Foo |
64 | <span class="comment">///</span> | 72 | </span><span class="comment documentation">/// </span><span class="string_literal"> bar |
65 | <span class="comment">/// ```</span> | 73 | </span><span class="comment documentation">/// </span><span class="string_literal"> "</span>; |
66 | <span class="comment">/// </span><span class="keyword">let</span> <span class="variable declaration">foobar</span> = <span class="unresolved_reference">Foo</span>::<span class="unresolved_reference">new</span>().<span class="unresolved_reference">bar</span>(); | 74 | <span class="comment documentation">///</span> |
67 | <span class="comment">/// ```</span> | 75 | <span class="comment documentation">/// ```</span> |
76 | <span class="comment documentation">///</span> | ||
77 | <span class="comment documentation">/// ```rust,no_run</span> | ||
78 | <span class="comment documentation">/// </span><span class="keyword">let</span> <span class="variable declaration">foobar</span> = <span class="struct">Foo</span>::<span class="function">new</span>().<span class="function">bar</span>(); | ||
79 | <span class="comment documentation">/// ```</span> | ||
80 | <span class="comment documentation">///</span> | ||
81 | <span class="comment documentation">/// ```sh</span> | ||
82 | <span class="comment documentation">/// echo 1</span> | ||
83 | <span class="comment documentation">/// ```</span> | ||
68 | <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration">foo</span>(&<span class="self_keyword">self</span>) -> <span class="builtin_type">bool</span> { | 84 | <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration">foo</span>(&<span class="self_keyword">self</span>) -> <span class="builtin_type">bool</span> { |
69 | <span class="bool_literal">true</span> | 85 | <span class="bool_literal">true</span> |
70 | } | 86 | } |
diff --git a/crates/ra_ide/src/snapshots/highlight_injection.html b/crates/ra_ide/src/snapshots/highlight_injection.html index dec06eb51..47dbd7bc8 100644 --- a/crates/ra_ide/src/snapshots/highlight_injection.html +++ b/crates/ra_ide/src/snapshots/highlight_injection.html | |||
@@ -25,6 +25,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
25 | .variable { color: #DCDCCC; } | 25 | .variable { color: #DCDCCC; } |
26 | .format_specifier { color: #CC696B; } | 26 | .format_specifier { color: #CC696B; } |
27 | .mutable { text-decoration: underline; } | 27 | .mutable { text-decoration: underline; } |
28 | .unresolved_reference { color: #FC5555; } | ||
29 | .escape_sequence { color: #94BFF3; } | ||
28 | 30 | ||
29 | .keyword { color: #F0DFAF; font-weight: bold; } | 31 | .keyword { color: #F0DFAF; font-weight: bold; } |
30 | .keyword.unsafe { color: #BC8383; font-weight: bold; } | 32 | .keyword.unsafe { color: #BC8383; font-weight: bold; } |
diff --git a/crates/ra_ide/src/snapshots/highlight_strings.html b/crates/ra_ide/src/snapshots/highlight_strings.html index 849eb3b73..b46fa44c6 100644 --- a/crates/ra_ide/src/snapshots/highlight_strings.html +++ b/crates/ra_ide/src/snapshots/highlight_strings.html | |||
@@ -25,6 +25,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
25 | .variable { color: #DCDCCC; } | 25 | .variable { color: #DCDCCC; } |
26 | .format_specifier { color: #CC696B; } | 26 | .format_specifier { color: #CC696B; } |
27 | .mutable { text-decoration: underline; } | 27 | .mutable { text-decoration: underline; } |
28 | .unresolved_reference { color: #FC5555; } | ||
29 | .escape_sequence { color: #94BFF3; } | ||
28 | 30 | ||
29 | .keyword { color: #F0DFAF; font-weight: bold; } | 31 | .keyword { color: #F0DFAF; font-weight: bold; } |
30 | .keyword.unsafe { color: #BC8383; font-weight: bold; } | 32 | .keyword.unsafe { color: #BC8383; font-weight: bold; } |
@@ -82,6 +84,10 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
82 | 84 | ||
83 | <span class="macro">println!</span>(<span class="string_literal">r"Hello, </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="string_literal">"world"</span>); | 85 | <span class="macro">println!</span>(<span class="string_literal">r"Hello, </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="string_literal">"world"</span>); |
84 | 86 | ||
85 | <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">\x41</span><span class="format_specifier">}</span><span class="string_literal">"</span>, A = <span class="numeric_literal">92</span>); | 87 | <span class="comment">// escape sequences</span> |
88 | <span class="macro">println!</span>(<span class="string_literal">"Hello</span><span class="escape_sequence">\n</span><span class="string_literal">World"</span>); | ||
89 | <span class="macro">println!</span>(<span class="string_literal">"</span><span class="escape_sequence">\u{48}</span><span class="escape_sequence">\x65</span><span class="escape_sequence">\x6C</span><span class="escape_sequence">\x6C</span><span class="escape_sequence">\x6F</span><span class="string_literal"> World"</span>); | ||
90 | |||
91 | <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="escape_sequence">\x41</span><span class="format_specifier">}</span><span class="string_literal">"</span>, A = <span class="numeric_literal">92</span>); | ||
86 | <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">ничоси</span><span class="format_specifier">}</span><span class="string_literal">"</span>, ничоси = <span class="numeric_literal">92</span>); | 92 | <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">ничоси</span><span class="format_specifier">}</span><span class="string_literal">"</span>, ничоси = <span class="numeric_literal">92</span>); |
87 | }</code></pre> \ No newline at end of file | 93 | }</code></pre> \ No newline at end of file |
diff --git a/crates/ra_ide/src/snapshots/highlight_unsafe.html b/crates/ra_ide/src/snapshots/highlight_unsafe.html index bd24e6e38..73438fbb4 100644 --- a/crates/ra_ide/src/snapshots/highlight_unsafe.html +++ b/crates/ra_ide/src/snapshots/highlight_unsafe.html | |||
@@ -25,6 +25,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
25 | .variable { color: #DCDCCC; } | 25 | .variable { color: #DCDCCC; } |
26 | .format_specifier { color: #CC696B; } | 26 | .format_specifier { color: #CC696B; } |
27 | .mutable { text-decoration: underline; } | 27 | .mutable { text-decoration: underline; } |
28 | .unresolved_reference { color: #FC5555; } | ||
29 | .escape_sequence { color: #94BFF3; } | ||
28 | 30 | ||
29 | .keyword { color: #F0DFAF; font-weight: bold; } | 31 | .keyword { color: #F0DFAF; font-weight: bold; } |
30 | .keyword.unsafe { color: #BC8383; font-weight: bold; } | 32 | .keyword.unsafe { color: #BC8383; font-weight: bold; } |
diff --git a/crates/ra_ide/src/snapshots/highlighting.html b/crates/ra_ide/src/snapshots/highlighting.html index 33548d43c..0c4f0a018 100644 --- a/crates/ra_ide/src/snapshots/highlighting.html +++ b/crates/ra_ide/src/snapshots/highlighting.html | |||
@@ -25,6 +25,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
25 | .variable { color: #DCDCCC; } | 25 | .variable { color: #DCDCCC; } |
26 | .format_specifier { color: #CC696B; } | 26 | .format_specifier { color: #CC696B; } |
27 | .mutable { text-decoration: underline; } | 27 | .mutable { text-decoration: underline; } |
28 | .unresolved_reference { color: #FC5555; } | ||
29 | .escape_sequence { color: #94BFF3; } | ||
28 | 30 | ||
29 | .keyword { color: #F0DFAF; font-weight: bold; } | 31 | .keyword { color: #F0DFAF; font-weight: bold; } |
30 | .keyword.unsafe { color: #BC8383; font-weight: bold; } | 32 | .keyword.unsafe { color: #BC8383; font-weight: bold; } |
@@ -62,6 +64,12 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
62 | } | 64 | } |
63 | } | 65 | } |
64 | 66 | ||
67 | <span class="macro">macro_rules!</span> <span class="macro declaration">noop</span> { | ||
68 | ($expr:expr) => { | ||
69 | $expr | ||
70 | } | ||
71 | } | ||
72 | |||
65 | <span class="comment">// comment</span> | 73 | <span class="comment">// comment</span> |
66 | <span class="keyword">fn</span> <span class="function declaration">main</span>() { | 74 | <span class="keyword">fn</span> <span class="function declaration">main</span>() { |
67 | <span class="macro">println!</span>(<span class="string_literal">"Hello, {}!"</span>, <span class="numeric_literal">92</span>); | 75 | <span class="macro">println!</span>(<span class="string_literal">"Hello, {}!"</span>, <span class="numeric_literal">92</span>); |
@@ -80,6 +88,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
80 | <span class="comment">// Do nothing</span> | 88 | <span class="comment">// Do nothing</span> |
81 | } | 89 | } |
82 | 90 | ||
91 | <span class="macro">noop!</span>(<span class="macro">noop</span><span class="macro">!</span>(<span class="numeric_literal">1</span>)); | ||
92 | |||
83 | <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">x</span> = <span class="numeric_literal">42</span>; | 93 | <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">x</span> = <span class="numeric_literal">42</span>; |
84 | <span class="keyword">let</span> <span class="variable declaration mutable">y</span> = &<span class="keyword">mut</span> <span class="variable mutable">x</span>; | 94 | <span class="keyword">let</span> <span class="variable declaration mutable">y</span> = &<span class="keyword">mut</span> <span class="variable mutable">x</span>; |
85 | <span class="keyword">let</span> <span class="variable declaration">z</span> = &<span class="variable mutable">y</span>; | 95 | <span class="keyword">let</span> <span class="variable declaration">z</span> = &<span class="variable mutable">y</span>; |
diff --git a/crates/ra_ide/src/snapshots/rainbow_highlighting.html b/crates/ra_ide/src/snapshots/rainbow_highlighting.html index 1ab06182c..a74a70069 100644 --- a/crates/ra_ide/src/snapshots/rainbow_highlighting.html +++ b/crates/ra_ide/src/snapshots/rainbow_highlighting.html | |||
@@ -25,6 +25,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
25 | .variable { color: #DCDCCC; } | 25 | .variable { color: #DCDCCC; } |
26 | .format_specifier { color: #CC696B; } | 26 | .format_specifier { color: #CC696B; } |
27 | .mutable { text-decoration: underline; } | 27 | .mutable { text-decoration: underline; } |
28 | .unresolved_reference { color: #FC5555; } | ||
29 | .escape_sequence { color: #94BFF3; } | ||
28 | 30 | ||
29 | .keyword { color: #F0DFAF; font-weight: bold; } | 31 | .keyword { color: #F0DFAF; font-weight: bold; } |
30 | .keyword.unsafe { color: #BC8383; font-weight: bold; } | 32 | .keyword.unsafe { color: #BC8383; font-weight: bold; } |
diff --git a/crates/ra_ide/src/ssr.rs b/crates/ra_ide/src/ssr.rs index 93e9aee1d..762aab962 100644 --- a/crates/ra_ide/src/ssr.rs +++ b/crates/ra_ide/src/ssr.rs | |||
@@ -27,11 +27,11 @@ impl std::error::Error for SsrError {} | |||
27 | // | 27 | // |
28 | // Search and replace with named wildcards that will match any expression. | 28 | // Search and replace with named wildcards that will match any expression. |
29 | // The syntax for a structural search replace command is `<search_pattern> ==>> <replace_pattern>`. | 29 | // The syntax for a structural search replace command is `<search_pattern> ==>> <replace_pattern>`. |
30 | // A `$<name>:expr` placeholder in the search pattern will match any expression and `$<name>` will reference it in the replacement. | 30 | // A `$<name>` placeholder in the search pattern will match any AST node and `$<name>` will reference it in the replacement. |
31 | // Available via the command `rust-analyzer.ssr`. | 31 | // Available via the command `rust-analyzer.ssr`. |
32 | // | 32 | // |
33 | // ```rust | 33 | // ```rust |
34 | // // Using structural search replace command [foo($a:expr, $b:expr) ==>> ($a).foo($b)] | 34 | // // Using structural search replace command [foo($a, $b) ==>> ($a).foo($b)] |
35 | // | 35 | // |
36 | // // BEFORE | 36 | // // BEFORE |
37 | // String::from(foo(y + 5, z)) | 37 | // String::from(foo(y + 5, z)) |
@@ -79,7 +79,7 @@ struct SsrPattern { | |||
79 | vars: Vec<Var>, | 79 | vars: Vec<Var>, |
80 | } | 80 | } |
81 | 81 | ||
82 | /// represents an `$var` in an SSR query | 82 | /// Represents a `$var` in an SSR query. |
83 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] | 83 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] |
84 | struct Var(String); | 84 | struct Var(String); |
85 | 85 | ||
@@ -122,8 +122,7 @@ impl FromStr for SsrQuery { | |||
122 | let mut pattern = it.next().expect("something").to_string(); | 122 | let mut pattern = it.next().expect("something").to_string(); |
123 | 123 | ||
124 | for part in it.map(split_by_var) { | 124 | for part in it.map(split_by_var) { |
125 | let (var, var_type, remainder) = part?; | 125 | let (var, remainder) = part?; |
126 | is_expr(var_type)?; | ||
127 | let new_var = create_name(var, &mut vars)?; | 126 | let new_var = create_name(var, &mut vars)?; |
128 | pattern.push_str(new_var); | 127 | pattern.push_str(new_var); |
129 | pattern.push_str(remainder); | 128 | pattern.push_str(remainder); |
@@ -166,15 +165,11 @@ fn traverse(node: &SyntaxNode, go: &mut impl FnMut(&SyntaxNode) -> bool) { | |||
166 | } | 165 | } |
167 | } | 166 | } |
168 | 167 | ||
169 | fn split_by_var(s: &str) -> Result<(&str, &str, &str), SsrError> { | 168 | fn split_by_var(s: &str) -> Result<(&str, &str), SsrError> { |
170 | let end_of_name = s.find(':').ok_or_else(|| SsrError("Use $<name>:expr".into()))?; | 169 | let end_of_name = s.find(|c| !char::is_ascii_alphanumeric(&c)).unwrap_or_else(|| s.len()); |
171 | let name = &s[0..end_of_name]; | 170 | let name = &s[..end_of_name]; |
172 | is_name(name)?; | 171 | is_name(name)?; |
173 | let type_begin = end_of_name + 1; | 172 | Ok((name, &s[end_of_name..])) |
174 | let type_length = | ||
175 | s[type_begin..].find(|c| !char::is_ascii_alphanumeric(&c)).unwrap_or_else(|| s.len()); | ||
176 | let type_name = &s[type_begin..type_begin + type_length]; | ||
177 | Ok((name, type_name, &s[type_begin + type_length..])) | ||
178 | } | 173 | } |
179 | 174 | ||
180 | fn is_name(s: &str) -> Result<(), SsrError> { | 175 | fn is_name(s: &str) -> Result<(), SsrError> { |
@@ -185,14 +180,6 @@ fn is_name(s: &str) -> Result<(), SsrError> { | |||
185 | } | 180 | } |
186 | } | 181 | } |
187 | 182 | ||
188 | fn is_expr(s: &str) -> Result<(), SsrError> { | ||
189 | if s == "expr" { | ||
190 | Ok(()) | ||
191 | } else { | ||
192 | Err(SsrError("Only $<name>:expr is supported".into())) | ||
193 | } | ||
194 | } | ||
195 | |||
196 | fn replace_in_template(template: String, var: &str, new_var: &str) -> String { | 183 | fn replace_in_template(template: String, var: &str, new_var: &str) -> String { |
197 | let name = format!("${}", var); | 184 | let name = format!("${}", var); |
198 | template.replace(&name, new_var) | 185 | template.replace(&name, new_var) |
@@ -450,7 +437,7 @@ mod tests { | |||
450 | 437 | ||
451 | #[test] | 438 | #[test] |
452 | fn parser_happy_case() { | 439 | fn parser_happy_case() { |
453 | let result: SsrQuery = "foo($a:expr, $b:expr) ==>> bar($b, $a)".parse().unwrap(); | 440 | let result: SsrQuery = "foo($a, $b) ==>> bar($b, $a)".parse().unwrap(); |
454 | assert_eq!(&result.pattern.pattern.text(), "foo(__search_pattern_a, __search_pattern_b)"); | 441 | assert_eq!(&result.pattern.pattern.text(), "foo(__search_pattern_a, __search_pattern_b)"); |
455 | assert_eq!(result.pattern.vars.len(), 2); | 442 | assert_eq!(result.pattern.vars.len(), 2); |
456 | assert_eq!(result.pattern.vars[0].0, "__search_pattern_a"); | 443 | assert_eq!(result.pattern.vars[0].0, "__search_pattern_a"); |
@@ -477,30 +464,9 @@ mod tests { | |||
477 | } | 464 | } |
478 | 465 | ||
479 | #[test] | 466 | #[test] |
480 | fn parser_no_pattern_type() { | ||
481 | assert_eq!(parse_error_text("foo($a) ==>>"), "Parse error: Use $<name>:expr"); | ||
482 | } | ||
483 | |||
484 | #[test] | ||
485 | fn parser_invalid_name() { | ||
486 | assert_eq!( | ||
487 | parse_error_text("foo($a+:expr) ==>>"), | ||
488 | "Parse error: Name can contain only alphanumerics and _" | ||
489 | ); | ||
490 | } | ||
491 | |||
492 | #[test] | ||
493 | fn parser_invalid_type() { | ||
494 | assert_eq!( | ||
495 | parse_error_text("foo($a:ident) ==>>"), | ||
496 | "Parse error: Only $<name>:expr is supported" | ||
497 | ); | ||
498 | } | ||
499 | |||
500 | #[test] | ||
501 | fn parser_repeated_name() { | 467 | fn parser_repeated_name() { |
502 | assert_eq!( | 468 | assert_eq!( |
503 | parse_error_text("foo($a:expr, $a:expr) ==>>"), | 469 | parse_error_text("foo($a, $a) ==>>"), |
504 | "Parse error: Name `a` repeats more than once" | 470 | "Parse error: Name `a` repeats more than once" |
505 | ); | 471 | ); |
506 | } | 472 | } |
@@ -517,7 +483,7 @@ mod tests { | |||
517 | 483 | ||
518 | #[test] | 484 | #[test] |
519 | fn parse_match_replace() { | 485 | fn parse_match_replace() { |
520 | let query: SsrQuery = "foo($x:expr) ==>> bar($x)".parse().unwrap(); | 486 | let query: SsrQuery = "foo($x) ==>> bar($x)".parse().unwrap(); |
521 | let input = "fn main() { foo(1+2); }"; | 487 | let input = "fn main() { foo(1+2); }"; |
522 | 488 | ||
523 | let code = SourceFile::parse(input).tree(); | 489 | let code = SourceFile::parse(input).tree(); |
@@ -549,7 +515,7 @@ mod tests { | |||
549 | #[test] | 515 | #[test] |
550 | fn ssr_function_to_method() { | 516 | fn ssr_function_to_method() { |
551 | assert_ssr_transform( | 517 | assert_ssr_transform( |
552 | "my_function($a:expr, $b:expr) ==>> ($a).my_method($b)", | 518 | "my_function($a, $b) ==>> ($a).my_method($b)", |
553 | "loop { my_function( other_func(x, y), z + w) }", | 519 | "loop { my_function( other_func(x, y), z + w) }", |
554 | "loop { (other_func(x, y)).my_method(z + w) }", | 520 | "loop { (other_func(x, y)).my_method(z + w) }", |
555 | ) | 521 | ) |
@@ -558,7 +524,7 @@ mod tests { | |||
558 | #[test] | 524 | #[test] |
559 | fn ssr_nested_function() { | 525 | fn ssr_nested_function() { |
560 | assert_ssr_transform( | 526 | assert_ssr_transform( |
561 | "foo($a:expr, $b:expr, $c:expr) ==>> bar($c, baz($a, $b))", | 527 | "foo($a, $b, $c) ==>> bar($c, baz($a, $b))", |
562 | "fn main { foo (x + value.method(b), x+y-z, true && false) }", | 528 | "fn main { foo (x + value.method(b), x+y-z, true && false) }", |
563 | "fn main { bar(true && false, baz(x + value.method(b), x+y-z)) }", | 529 | "fn main { bar(true && false, baz(x + value.method(b), x+y-z)) }", |
564 | ) | 530 | ) |
@@ -567,7 +533,7 @@ mod tests { | |||
567 | #[test] | 533 | #[test] |
568 | fn ssr_expected_spacing() { | 534 | fn ssr_expected_spacing() { |
569 | assert_ssr_transform( | 535 | assert_ssr_transform( |
570 | "foo($x:expr) + bar() ==>> bar($x)", | 536 | "foo($x) + bar() ==>> bar($x)", |
571 | "fn main() { foo(5) + bar() }", | 537 | "fn main() { foo(5) + bar() }", |
572 | "fn main() { bar(5) }", | 538 | "fn main() { bar(5) }", |
573 | ); | 539 | ); |
@@ -576,7 +542,7 @@ mod tests { | |||
576 | #[test] | 542 | #[test] |
577 | fn ssr_with_extra_space() { | 543 | fn ssr_with_extra_space() { |
578 | assert_ssr_transform( | 544 | assert_ssr_transform( |
579 | "foo($x:expr ) + bar() ==>> bar($x)", | 545 | "foo($x ) + bar() ==>> bar($x)", |
580 | "fn main() { foo( 5 ) +bar( ) }", | 546 | "fn main() { foo( 5 ) +bar( ) }", |
581 | "fn main() { bar(5) }", | 547 | "fn main() { bar(5) }", |
582 | ); | 548 | ); |
@@ -585,7 +551,7 @@ mod tests { | |||
585 | #[test] | 551 | #[test] |
586 | fn ssr_keeps_nested_comment() { | 552 | fn ssr_keeps_nested_comment() { |
587 | assert_ssr_transform( | 553 | assert_ssr_transform( |
588 | "foo($x:expr) ==>> bar($x)", | 554 | "foo($x) ==>> bar($x)", |
589 | "fn main() { foo(other(5 /* using 5 */)) }", | 555 | "fn main() { foo(other(5 /* using 5 */)) }", |
590 | "fn main() { bar(other(5 /* using 5 */)) }", | 556 | "fn main() { bar(other(5 /* using 5 */)) }", |
591 | ) | 557 | ) |
@@ -594,7 +560,7 @@ mod tests { | |||
594 | #[test] | 560 | #[test] |
595 | fn ssr_keeps_comment() { | 561 | fn ssr_keeps_comment() { |
596 | assert_ssr_transform( | 562 | assert_ssr_transform( |
597 | "foo($x:expr) ==>> bar($x)", | 563 | "foo($x) ==>> bar($x)", |
598 | "fn main() { foo(5 /* using 5 */) }", | 564 | "fn main() { foo(5 /* using 5 */) }", |
599 | "fn main() { bar(5)/* using 5 */ }", | 565 | "fn main() { bar(5)/* using 5 */ }", |
600 | ) | 566 | ) |
@@ -603,7 +569,7 @@ mod tests { | |||
603 | #[test] | 569 | #[test] |
604 | fn ssr_struct_lit() { | 570 | fn ssr_struct_lit() { |
605 | assert_ssr_transform( | 571 | assert_ssr_transform( |
606 | "foo{a: $a:expr, b: $b:expr} ==>> foo::new($a, $b)", | 572 | "foo{a: $a, b: $b} ==>> foo::new($a, $b)", |
607 | "fn main() { foo{b:2, a:1} }", | 573 | "fn main() { foo{b:2, a:1} }", |
608 | "fn main() { foo::new(1, 2) }", | 574 | "fn main() { foo::new(1, 2) }", |
609 | ) | 575 | ) |
@@ -612,7 +578,7 @@ mod tests { | |||
612 | #[test] | 578 | #[test] |
613 | fn ssr_call_and_method_call() { | 579 | fn ssr_call_and_method_call() { |
614 | assert_ssr_transform( | 580 | assert_ssr_transform( |
615 | "foo::<'a>($a:expr, $b:expr)) ==>> foo2($a, $b)", | 581 | "foo::<'a>($a, $b)) ==>> foo2($a, $b)", |
616 | "fn main() { get().bar.foo::<'a>(1); }", | 582 | "fn main() { get().bar.foo::<'a>(1); }", |
617 | "fn main() { foo2(get().bar, 1); }", | 583 | "fn main() { foo2(get().bar, 1); }", |
618 | ) | 584 | ) |
@@ -621,7 +587,7 @@ mod tests { | |||
621 | #[test] | 587 | #[test] |
622 | fn ssr_method_call_and_call() { | 588 | fn ssr_method_call_and_call() { |
623 | assert_ssr_transform( | 589 | assert_ssr_transform( |
624 | "$o:expr.foo::<i32>($a:expr)) ==>> $o.foo2($a)", | 590 | "$o.foo::<i32>($a)) ==>> $o.foo2($a)", |
625 | "fn main() { X::foo::<i32>(x, 1); }", | 591 | "fn main() { X::foo::<i32>(x, 1); }", |
626 | "fn main() { x.foo2(1); }", | 592 | "fn main() { x.foo2(1); }", |
627 | ) | 593 | ) |
diff --git a/crates/ra_ide/src/status.rs b/crates/ra_ide/src/status.rs index 5b7992920..45411b357 100644 --- a/crates/ra_ide/src/status.rs +++ b/crates/ra_ide/src/status.rs | |||
@@ -16,6 +16,7 @@ use ra_prof::{memory_usage, Bytes}; | |||
16 | use ra_syntax::{ast, Parse, SyntaxNode}; | 16 | use ra_syntax::{ast, Parse, SyntaxNode}; |
17 | 17 | ||
18 | use crate::FileId; | 18 | use crate::FileId; |
19 | use rustc_hash::FxHashMap; | ||
19 | 20 | ||
20 | fn syntax_tree_stats(db: &RootDatabase) -> SyntaxTreeStats { | 21 | fn syntax_tree_stats(db: &RootDatabase) -> SyntaxTreeStats { |
21 | db.query(ra_db::ParseQuery).entries::<SyntaxTreeStats>() | 22 | db.query(ra_db::ParseQuery).entries::<SyntaxTreeStats>() |
@@ -123,20 +124,24 @@ struct LibrarySymbolsStats { | |||
123 | 124 | ||
124 | impl fmt::Display for LibrarySymbolsStats { | 125 | impl fmt::Display for LibrarySymbolsStats { |
125 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { | 126 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { |
126 | write!(fmt, "{} ({}) symbols", self.total, self.size,) | 127 | write!(fmt, "{} ({}) symbols", self.total, self.size) |
127 | } | 128 | } |
128 | } | 129 | } |
129 | 130 | ||
130 | impl FromIterator<TableEntry<SourceRootId, Arc<SymbolIndex>>> for LibrarySymbolsStats { | 131 | impl FromIterator<TableEntry<(), Arc<FxHashMap<SourceRootId, SymbolIndex>>>> |
132 | for LibrarySymbolsStats | ||
133 | { | ||
131 | fn from_iter<T>(iter: T) -> LibrarySymbolsStats | 134 | fn from_iter<T>(iter: T) -> LibrarySymbolsStats |
132 | where | 135 | where |
133 | T: IntoIterator<Item = TableEntry<SourceRootId, Arc<SymbolIndex>>>, | 136 | T: IntoIterator<Item = TableEntry<(), Arc<FxHashMap<SourceRootId, SymbolIndex>>>>, |
134 | { | 137 | { |
135 | let mut res = LibrarySymbolsStats::default(); | 138 | let mut res = LibrarySymbolsStats::default(); |
136 | for entry in iter { | 139 | for entry in iter { |
137 | let value = entry.value.unwrap(); | 140 | let value = entry.value.unwrap(); |
138 | res.total += value.len(); | 141 | for symbols in value.values() { |
139 | res.size += value.memory_size(); | 142 | res.total += symbols.len(); |
143 | res.size += symbols.memory_size(); | ||
144 | } | ||
140 | } | 145 | } |
141 | res | 146 | res |
142 | } | 147 | } |
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs index ab45c364a..f8f790e59 100644 --- a/crates/ra_ide/src/syntax_highlighting.rs +++ b/crates/ra_ide/src/syntax_highlighting.rs | |||
@@ -44,6 +44,7 @@ pub(crate) fn highlight( | |||
44 | db: &RootDatabase, | 44 | db: &RootDatabase, |
45 | file_id: FileId, | 45 | file_id: FileId, |
46 | range_to_highlight: Option<TextRange>, | 46 | range_to_highlight: Option<TextRange>, |
47 | syntactic_name_ref_highlighting: bool, | ||
47 | ) -> Vec<HighlightedRange> { | 48 | ) -> Vec<HighlightedRange> { |
48 | let _p = profile("highlight"); | 49 | let _p = profile("highlight"); |
49 | let sema = Semantics::new(db); | 50 | let sema = Semantics::new(db); |
@@ -104,6 +105,7 @@ pub(crate) fn highlight( | |||
104 | if let Some((highlight, binding_hash)) = highlight_element( | 105 | if let Some((highlight, binding_hash)) = highlight_element( |
105 | &sema, | 106 | &sema, |
106 | &mut bindings_shadow_count, | 107 | &mut bindings_shadow_count, |
108 | syntactic_name_ref_highlighting, | ||
107 | name.syntax().clone().into(), | 109 | name.syntax().clone().into(), |
108 | ) { | 110 | ) { |
109 | stack.add(HighlightedRange { | 111 | stack.add(HighlightedRange { |
@@ -160,23 +162,25 @@ pub(crate) fn highlight( | |||
160 | // Check if macro takes a format string and remember it for highlighting later. | 162 | // Check if macro takes a format string and remember it for highlighting later. |
161 | // The macros that accept a format string expand to a compiler builtin macros | 163 | // The macros that accept a format string expand to a compiler builtin macros |
162 | // `format_args` and `format_args_nl`. | 164 | // `format_args` and `format_args_nl`. |
163 | if let Some(fmt_macro_call) = parent.parent().and_then(ast::MacroCall::cast) { | 165 | if let Some(name) = parent |
164 | if let Some(name) = | 166 | .parent() |
165 | fmt_macro_call.path().and_then(|p| p.segment()).and_then(|s| s.name_ref()) | 167 | .and_then(ast::MacroCall::cast) |
166 | { | 168 | .and_then(|mc| mc.path()) |
167 | match name.text().as_str() { | 169 | .and_then(|p| p.segment()) |
168 | "format_args" | "format_args_nl" => { | 170 | .and_then(|s| s.name_ref()) |
169 | format_string = parent | 171 | { |
170 | .children_with_tokens() | 172 | match name.text().as_str() { |
171 | .filter(|t| t.kind() != WHITESPACE) | 173 | "format_args" | "format_args_nl" => { |
172 | .nth(1) | 174 | format_string = parent |
173 | .filter(|e| { | 175 | .children_with_tokens() |
174 | ast::String::can_cast(e.kind()) | 176 | .filter(|t| t.kind() != WHITESPACE) |
175 | || ast::RawString::can_cast(e.kind()) | 177 | .nth(1) |
176 | }) | 178 | .filter(|e| { |
177 | } | 179 | ast::String::can_cast(e.kind()) |
178 | _ => {} | 180 | || ast::RawString::can_cast(e.kind()) |
181 | }) | ||
179 | } | 182 | } |
183 | _ => {} | ||
180 | } | 184 | } |
181 | } | 185 | } |
182 | 186 | ||
@@ -198,15 +202,18 @@ pub(crate) fn highlight( | |||
198 | 202 | ||
199 | let is_format_string = format_string.as_ref() == Some(&element_to_highlight); | 203 | let is_format_string = format_string.as_ref() == Some(&element_to_highlight); |
200 | 204 | ||
201 | if let Some((highlight, binding_hash)) = | 205 | if let Some((highlight, binding_hash)) = highlight_element( |
202 | highlight_element(&sema, &mut bindings_shadow_count, element_to_highlight.clone()) | 206 | &sema, |
203 | { | 207 | &mut bindings_shadow_count, |
208 | syntactic_name_ref_highlighting, | ||
209 | element_to_highlight.clone(), | ||
210 | ) { | ||
204 | stack.add(HighlightedRange { range, highlight, binding_hash }); | 211 | stack.add(HighlightedRange { range, highlight, binding_hash }); |
205 | if let Some(string) = | 212 | if let Some(string) = |
206 | element_to_highlight.as_token().cloned().and_then(ast::String::cast) | 213 | element_to_highlight.as_token().cloned().and_then(ast::String::cast) |
207 | { | 214 | { |
208 | stack.push(); | ||
209 | if is_format_string { | 215 | if is_format_string { |
216 | stack.push(); | ||
210 | string.lex_format_specifier(|piece_range, kind| { | 217 | string.lex_format_specifier(|piece_range, kind| { |
211 | if let Some(highlight) = highlight_format_specifier(kind) { | 218 | if let Some(highlight) = highlight_format_specifier(kind) { |
212 | stack.add(HighlightedRange { | 219 | stack.add(HighlightedRange { |
@@ -216,13 +223,27 @@ pub(crate) fn highlight( | |||
216 | }); | 223 | }); |
217 | } | 224 | } |
218 | }); | 225 | }); |
226 | stack.pop(); | ||
227 | } | ||
228 | // Highlight escape sequences | ||
229 | if let Some(char_ranges) = string.char_ranges() { | ||
230 | stack.push(); | ||
231 | for (piece_range, _) in char_ranges.iter().filter(|(_, char)| char.is_ok()) { | ||
232 | if string.text()[piece_range.start().into()..].starts_with('\\') { | ||
233 | stack.add(HighlightedRange { | ||
234 | range: piece_range + range.start(), | ||
235 | highlight: HighlightTag::EscapeSequence.into(), | ||
236 | binding_hash: None, | ||
237 | }); | ||
238 | } | ||
239 | } | ||
240 | stack.pop_and_inject(false); | ||
219 | } | 241 | } |
220 | stack.pop(); | ||
221 | } else if let Some(string) = | 242 | } else if let Some(string) = |
222 | element_to_highlight.as_token().cloned().and_then(ast::RawString::cast) | 243 | element_to_highlight.as_token().cloned().and_then(ast::RawString::cast) |
223 | { | 244 | { |
224 | stack.push(); | ||
225 | if is_format_string { | 245 | if is_format_string { |
246 | stack.push(); | ||
226 | string.lex_format_specifier(|piece_range, kind| { | 247 | string.lex_format_specifier(|piece_range, kind| { |
227 | if let Some(highlight) = highlight_format_specifier(kind) { | 248 | if let Some(highlight) = highlight_format_specifier(kind) { |
228 | stack.add(HighlightedRange { | 249 | stack.add(HighlightedRange { |
@@ -232,8 +253,8 @@ pub(crate) fn highlight( | |||
232 | }); | 253 | }); |
233 | } | 254 | } |
234 | }); | 255 | }); |
256 | stack.pop(); | ||
235 | } | 257 | } |
236 | stack.pop(); | ||
237 | } | 258 | } |
238 | } | 259 | } |
239 | } | 260 | } |
@@ -408,6 +429,7 @@ fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> { | |||
408 | fn highlight_element( | 429 | fn highlight_element( |
409 | sema: &Semantics<RootDatabase>, | 430 | sema: &Semantics<RootDatabase>, |
410 | bindings_shadow_count: &mut FxHashMap<Name, u32>, | 431 | bindings_shadow_count: &mut FxHashMap<Name, u32>, |
432 | syntactic_name_ref_highlighting: bool, | ||
411 | element: SyntaxElement, | 433 | element: SyntaxElement, |
412 | ) -> Option<(Highlight, Option<u64>)> { | 434 | ) -> Option<(Highlight, Option<u64>)> { |
413 | let db = sema.db; | 435 | let db = sema.db; |
@@ -461,12 +483,20 @@ fn highlight_element( | |||
461 | } | 483 | } |
462 | NameRefClass::FieldShorthand { .. } => HighlightTag::Field.into(), | 484 | NameRefClass::FieldShorthand { .. } => HighlightTag::Field.into(), |
463 | }, | 485 | }, |
486 | None if syntactic_name_ref_highlighting => highlight_name_ref_by_syntax(name_ref), | ||
464 | None => HighlightTag::UnresolvedReference.into(), | 487 | None => HighlightTag::UnresolvedReference.into(), |
465 | } | 488 | } |
466 | } | 489 | } |
467 | 490 | ||
468 | // Simple token-based highlighting | 491 | // Simple token-based highlighting |
469 | COMMENT => HighlightTag::Comment.into(), | 492 | COMMENT => { |
493 | let comment = element.into_token().and_then(ast::Comment::cast)?; | ||
494 | let h = HighlightTag::Comment; | ||
495 | match comment.kind().doc { | ||
496 | Some(_) => h | HighlightModifier::Documentation, | ||
497 | None => h.into(), | ||
498 | } | ||
499 | } | ||
470 | STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => HighlightTag::StringLiteral.into(), | 500 | STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => HighlightTag::StringLiteral.into(), |
471 | ATTR => HighlightTag::Attribute.into(), | 501 | ATTR => HighlightTag::Attribute.into(), |
472 | INT_NUMBER | FLOAT_NUMBER => HighlightTag::NumericLiteral.into(), | 502 | INT_NUMBER | FLOAT_NUMBER => HighlightTag::NumericLiteral.into(), |
@@ -493,6 +523,9 @@ fn highlight_element( | |||
493 | h |= HighlightModifier::Unsafe; | 523 | h |= HighlightModifier::Unsafe; |
494 | h | 524 | h |
495 | } | 525 | } |
526 | T![!] if element.parent().and_then(ast::MacroCall::cast).is_some() => { | ||
527 | Highlight::new(HighlightTag::Macro) | ||
528 | } | ||
496 | 529 | ||
497 | k if k.is_keyword() => { | 530 | k if k.is_keyword() => { |
498 | let h = Highlight::new(HighlightTag::Keyword); | 531 | let h = Highlight::new(HighlightTag::Keyword); |
@@ -609,3 +642,53 @@ fn highlight_name_by_syntax(name: ast::Name) -> Highlight { | |||
609 | 642 | ||
610 | tag.into() | 643 | tag.into() |
611 | } | 644 | } |
645 | |||
646 | fn highlight_name_ref_by_syntax(name: ast::NameRef) -> Highlight { | ||
647 | let default = HighlightTag::UnresolvedReference; | ||
648 | |||
649 | let parent = match name.syntax().parent() { | ||
650 | Some(it) => it, | ||
651 | _ => return default.into(), | ||
652 | }; | ||
653 | |||
654 | let tag = match parent.kind() { | ||
655 | METHOD_CALL_EXPR => HighlightTag::Function, | ||
656 | FIELD_EXPR => HighlightTag::Field, | ||
657 | PATH_SEGMENT => { | ||
658 | let path = match parent.parent().and_then(ast::Path::cast) { | ||
659 | Some(it) => it, | ||
660 | _ => return default.into(), | ||
661 | }; | ||
662 | let expr = match path.syntax().parent().and_then(ast::PathExpr::cast) { | ||
663 | Some(it) => it, | ||
664 | _ => { | ||
665 | // within path, decide whether it is module or adt by checking for uppercase name | ||
666 | return if name.text().chars().next().unwrap_or_default().is_uppercase() { | ||
667 | HighlightTag::Struct | ||
668 | } else { | ||
669 | HighlightTag::Module | ||
670 | } | ||
671 | .into(); | ||
672 | } | ||
673 | }; | ||
674 | let parent = match expr.syntax().parent() { | ||
675 | Some(it) => it, | ||
676 | None => return default.into(), | ||
677 | }; | ||
678 | |||
679 | match parent.kind() { | ||
680 | CALL_EXPR => HighlightTag::Function, | ||
681 | _ => { | ||
682 | if name.text().chars().next().unwrap_or_default().is_uppercase() { | ||
683 | HighlightTag::Struct | ||
684 | } else { | ||
685 | HighlightTag::Constant | ||
686 | } | ||
687 | } | ||
688 | } | ||
689 | } | ||
690 | _ => default, | ||
691 | }; | ||
692 | |||
693 | tag.into() | ||
694 | } | ||
diff --git a/crates/ra_ide/src/syntax_highlighting/html.rs b/crates/ra_ide/src/syntax_highlighting/html.rs index 5bada6252..99b6b25ab 100644 --- a/crates/ra_ide/src/syntax_highlighting/html.rs +++ b/crates/ra_ide/src/syntax_highlighting/html.rs | |||
@@ -19,7 +19,7 @@ pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: boo | |||
19 | ) | 19 | ) |
20 | } | 20 | } |
21 | 21 | ||
22 | let ranges = highlight(db, file_id, None); | 22 | let ranges = highlight(db, file_id, None, false); |
23 | let text = parse.tree().syntax().to_string(); | 23 | let text = parse.tree().syntax().to_string(); |
24 | let mut prev_pos = TextSize::from(0); | 24 | let mut prev_pos = TextSize::from(0); |
25 | let mut buf = String::new(); | 25 | let mut buf = String::new(); |
@@ -84,6 +84,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
84 | .variable { color: #DCDCCC; } | 84 | .variable { color: #DCDCCC; } |
85 | .format_specifier { color: #CC696B; } | 85 | .format_specifier { color: #CC696B; } |
86 | .mutable { text-decoration: underline; } | 86 | .mutable { text-decoration: underline; } |
87 | .unresolved_reference { color: #FC5555; } | ||
88 | .escape_sequence { color: #94BFF3; } | ||
87 | 89 | ||
88 | .keyword { color: #F0DFAF; font-weight: bold; } | 90 | .keyword { color: #F0DFAF; font-weight: bold; } |
89 | .keyword.unsafe { color: #BC8383; font-weight: bold; } | 91 | .keyword.unsafe { color: #BC8383; font-weight: bold; } |
diff --git a/crates/ra_ide/src/syntax_highlighting/injection.rs b/crates/ra_ide/src/syntax_highlighting/injection.rs index 3575a0fc6..415f24a6d 100644 --- a/crates/ra_ide/src/syntax_highlighting/injection.rs +++ b/crates/ra_ide/src/syntax_highlighting/injection.rs | |||
@@ -7,7 +7,10 @@ use hir::Semantics; | |||
7 | use ra_syntax::{ast, AstToken, SyntaxNode, SyntaxToken, TextRange, TextSize}; | 7 | use ra_syntax::{ast, AstToken, SyntaxNode, SyntaxToken, TextRange, TextSize}; |
8 | use stdx::SepBy; | 8 | use stdx::SepBy; |
9 | 9 | ||
10 | use crate::{call_info::ActiveParameter, Analysis, HighlightTag, HighlightedRange, RootDatabase}; | 10 | use crate::{ |
11 | call_info::ActiveParameter, Analysis, HighlightModifier, HighlightTag, HighlightedRange, | ||
12 | RootDatabase, | ||
13 | }; | ||
11 | 14 | ||
12 | use super::HighlightedRangeStack; | 15 | use super::HighlightedRangeStack; |
13 | 16 | ||
@@ -53,6 +56,10 @@ pub(super) fn highlight_injection( | |||
53 | /// Mapping from extracted documentation code to original code | 56 | /// Mapping from extracted documentation code to original code |
54 | type RangesMap = BTreeMap<TextSize, TextSize>; | 57 | type RangesMap = BTreeMap<TextSize, TextSize>; |
55 | 58 | ||
59 | const RUSTDOC_FENCE: &'static str = "```"; | ||
60 | const RUSTDOC_FENCE_TOKENS: &[&'static str] = | ||
61 | &["", "rust", "should_panic", "ignore", "no_run", "compile_fail", "edition2015", "edition2018"]; | ||
62 | |||
56 | /// Extracts Rust code from documentation comments as well as a mapping from | 63 | /// Extracts Rust code from documentation comments as well as a mapping from |
57 | /// the extracted source code back to the original source ranges. | 64 | /// the extracted source code back to the original source ranges. |
58 | /// Lastly, a vector of new comment highlight ranges (spanning only the | 65 | /// Lastly, a vector of new comment highlight ranges (spanning only the |
@@ -67,6 +74,7 @@ pub(super) fn extract_doc_comments( | |||
67 | // Mapping from extracted documentation code to original code | 74 | // Mapping from extracted documentation code to original code |
68 | let mut range_mapping: RangesMap = BTreeMap::new(); | 75 | let mut range_mapping: RangesMap = BTreeMap::new(); |
69 | let mut line_start = TextSize::try_from(prefix.len()).unwrap(); | 76 | let mut line_start = TextSize::try_from(prefix.len()).unwrap(); |
77 | let mut is_codeblock = false; | ||
70 | let mut is_doctest = false; | 78 | let mut is_doctest = false; |
71 | // Replace the original, line-spanning comment ranges by new, only comment-prefix | 79 | // Replace the original, line-spanning comment ranges by new, only comment-prefix |
72 | // spanning comment ranges. | 80 | // spanning comment ranges. |
@@ -76,8 +84,13 @@ pub(super) fn extract_doc_comments( | |||
76 | .filter_map(|el| el.into_token().and_then(ast::Comment::cast)) | 84 | .filter_map(|el| el.into_token().and_then(ast::Comment::cast)) |
77 | .filter(|comment| comment.kind().doc.is_some()) | 85 | .filter(|comment| comment.kind().doc.is_some()) |
78 | .filter(|comment| { | 86 | .filter(|comment| { |
79 | if comment.text().contains("```") { | 87 | if let Some(idx) = comment.text().find(RUSTDOC_FENCE) { |
80 | is_doctest = !is_doctest; | 88 | is_codeblock = !is_codeblock; |
89 | // Check whether code is rust by inspecting fence guards | ||
90 | let guards = &comment.text()[idx + RUSTDOC_FENCE.len()..]; | ||
91 | let is_rust = | ||
92 | guards.split(',').all(|sub| RUSTDOC_FENCE_TOKENS.contains(&sub.trim())); | ||
93 | is_doctest = is_codeblock && is_rust; | ||
81 | false | 94 | false |
82 | } else { | 95 | } else { |
83 | is_doctest | 96 | is_doctest |
@@ -108,7 +121,7 @@ pub(super) fn extract_doc_comments( | |||
108 | range.start(), | 121 | range.start(), |
109 | range.start() + TextSize::try_from(pos).unwrap(), | 122 | range.start() + TextSize::try_from(pos).unwrap(), |
110 | ), | 123 | ), |
111 | highlight: HighlightTag::Comment.into(), | 124 | highlight: HighlightTag::Comment | HighlightModifier::Documentation, |
112 | binding_hash: None, | 125 | binding_hash: None, |
113 | }); | 126 | }); |
114 | line_start += range.len() - TextSize::try_from(pos).unwrap(); | 127 | line_start += range.len() - TextSize::try_from(pos).unwrap(); |
@@ -137,7 +150,7 @@ pub(super) fn highlight_doc_comment( | |||
137 | let (analysis, tmp_file_id) = Analysis::from_single_file(text); | 150 | let (analysis, tmp_file_id) = Analysis::from_single_file(text); |
138 | 151 | ||
139 | stack.push(); | 152 | stack.push(); |
140 | for mut h in analysis.highlight(tmp_file_id).unwrap() { | 153 | for mut h in analysis.with_db(|db| super::highlight(db, tmp_file_id, None, true)).unwrap() { |
141 | // Determine start offset and end offset in case of multi-line ranges | 154 | // Determine start offset and end offset in case of multi-line ranges |
142 | let mut start_offset = None; | 155 | let mut start_offset = None; |
143 | let mut end_offset = None; | 156 | let mut end_offset = None; |
@@ -154,6 +167,7 @@ pub(super) fn highlight_doc_comment( | |||
154 | h.range.start() + start_offset, | 167 | h.range.start() + start_offset, |
155 | h.range.end() + end_offset.unwrap_or(start_offset), | 168 | h.range.end() + end_offset.unwrap_or(start_offset), |
156 | ); | 169 | ); |
170 | |||
157 | stack.add(h); | 171 | stack.add(h); |
158 | } | 172 | } |
159 | } | 173 | } |
diff --git a/crates/ra_ide/src/syntax_highlighting/tags.rs b/crates/ra_ide/src/syntax_highlighting/tags.rs index 94f466966..93bbb4b4d 100644 --- a/crates/ra_ide/src/syntax_highlighting/tags.rs +++ b/crates/ra_ide/src/syntax_highlighting/tags.rs | |||
@@ -23,6 +23,7 @@ pub enum HighlightTag { | |||
23 | Constant, | 23 | Constant, |
24 | Enum, | 24 | Enum, |
25 | EnumVariant, | 25 | EnumVariant, |
26 | EscapeSequence, | ||
26 | Field, | 27 | Field, |
27 | FormatSpecifier, | 28 | FormatSpecifier, |
28 | Function, | 29 | Function, |
@@ -55,6 +56,7 @@ pub enum HighlightModifier { | |||
55 | /// `foo` in `fn foo(x: i32)` is a definition, `foo` in `foo(90 + 2)` is | 56 | /// `foo` in `fn foo(x: i32)` is a definition, `foo` in `foo(90 + 2)` is |
56 | /// not. | 57 | /// not. |
57 | Definition, | 58 | Definition, |
59 | Documentation, | ||
58 | Mutable, | 60 | Mutable, |
59 | Unsafe, | 61 | Unsafe, |
60 | } | 62 | } |
@@ -71,6 +73,7 @@ impl HighlightTag { | |||
71 | HighlightTag::Constant => "constant", | 73 | HighlightTag::Constant => "constant", |
72 | HighlightTag::Enum => "enum", | 74 | HighlightTag::Enum => "enum", |
73 | HighlightTag::EnumVariant => "enum_variant", | 75 | HighlightTag::EnumVariant => "enum_variant", |
76 | HighlightTag::EscapeSequence => "escape_sequence", | ||
74 | HighlightTag::Field => "field", | 77 | HighlightTag::Field => "field", |
75 | HighlightTag::FormatSpecifier => "format_specifier", | 78 | HighlightTag::FormatSpecifier => "format_specifier", |
76 | HighlightTag::Function => "function", | 79 | HighlightTag::Function => "function", |
@@ -106,6 +109,7 @@ impl HighlightModifier { | |||
106 | HighlightModifier::Attribute, | 109 | HighlightModifier::Attribute, |
107 | HighlightModifier::ControlFlow, | 110 | HighlightModifier::ControlFlow, |
108 | HighlightModifier::Definition, | 111 | HighlightModifier::Definition, |
112 | HighlightModifier::Documentation, | ||
109 | HighlightModifier::Mutable, | 113 | HighlightModifier::Mutable, |
110 | HighlightModifier::Unsafe, | 114 | HighlightModifier::Unsafe, |
111 | ]; | 115 | ]; |
@@ -115,6 +119,7 @@ impl HighlightModifier { | |||
115 | HighlightModifier::Attribute => "attribute", | 119 | HighlightModifier::Attribute => "attribute", |
116 | HighlightModifier::ControlFlow => "control", | 120 | HighlightModifier::ControlFlow => "control", |
117 | HighlightModifier::Definition => "declaration", | 121 | HighlightModifier::Definition => "declaration", |
122 | HighlightModifier::Documentation => "documentation", | ||
118 | HighlightModifier::Mutable => "mutable", | 123 | HighlightModifier::Mutable => "mutable", |
119 | HighlightModifier::Unsafe => "unsafe", | 124 | HighlightModifier::Unsafe => "unsafe", |
120 | } | 125 | } |
diff --git a/crates/ra_ide/src/syntax_highlighting/tests.rs b/crates/ra_ide/src/syntax_highlighting/tests.rs index 949bf59a0..b4d56a7a0 100644 --- a/crates/ra_ide/src/syntax_highlighting/tests.rs +++ b/crates/ra_ide/src/syntax_highlighting/tests.rs | |||
@@ -43,6 +43,12 @@ def_fn! { | |||
43 | } | 43 | } |
44 | } | 44 | } |
45 | 45 | ||
46 | macro_rules! noop { | ||
47 | ($expr:expr) => { | ||
48 | $expr | ||
49 | } | ||
50 | } | ||
51 | |||
46 | // comment | 52 | // comment |
47 | fn main() { | 53 | fn main() { |
48 | println!("Hello, {}!", 92); | 54 | println!("Hello, {}!", 92); |
@@ -61,6 +67,8 @@ fn main() { | |||
61 | // Do nothing | 67 | // Do nothing |
62 | } | 68 | } |
63 | 69 | ||
70 | noop!(noop!(1)); | ||
71 | |||
64 | let mut x = 42; | 72 | let mut x = 42; |
65 | let y = &mut x; | 73 | let y = &mut x; |
66 | let z = &y; | 74 | let z = &y; |
@@ -238,6 +246,10 @@ fn main() { | |||
238 | 246 | ||
239 | println!(r"Hello, {}!", "world"); | 247 | println!(r"Hello, {}!", "world"); |
240 | 248 | ||
249 | // escape sequences | ||
250 | println!("Hello\nWorld"); | ||
251 | println!("\u{48}\x65\x6C\x6C\x6F World"); | ||
252 | |||
241 | println!("{\x41}", A = 92); | 253 | println!("{\x41}", A = 92); |
242 | println!("{ничоси}", ничоси = 92); | 254 | println!("{ничоси}", ничоси = 92); |
243 | }"# | 255 | }"# |
@@ -279,7 +291,13 @@ fn main() { | |||
279 | fn test_highlight_doctest() { | 291 | fn test_highlight_doctest() { |
280 | check_highlighting( | 292 | check_highlighting( |
281 | r#" | 293 | r#" |
294 | struct Foo { | ||
295 | bar: bool, | ||
296 | } | ||
297 | |||
282 | impl Foo { | 298 | impl Foo { |
299 | pub const bar: bool = true; | ||
300 | |||
283 | /// Constructs a new `Foo`. | 301 | /// Constructs a new `Foo`. |
284 | /// | 302 | /// |
285 | /// # Examples | 303 | /// # Examples |
@@ -289,7 +307,7 @@ impl Foo { | |||
289 | /// let mut foo: Foo = Foo::new(); | 307 | /// let mut foo: Foo = Foo::new(); |
290 | /// ``` | 308 | /// ``` |
291 | pub const fn new() -> Foo { | 309 | pub const fn new() -> Foo { |
292 | Foo { } | 310 | Foo { bar: true } |
293 | } | 311 | } |
294 | 312 | ||
295 | /// `bar` method on `Foo`. | 313 | /// `bar` method on `Foo`. |
@@ -297,11 +315,15 @@ impl Foo { | |||
297 | /// # Examples | 315 | /// # Examples |
298 | /// | 316 | /// |
299 | /// ``` | 317 | /// ``` |
318 | /// use x::y; | ||
319 | /// | ||
300 | /// let foo = Foo::new(); | 320 | /// let foo = Foo::new(); |
301 | /// | 321 | /// |
302 | /// // calls bar on foo | 322 | /// // calls bar on foo |
303 | /// assert!(foo.bar()); | 323 | /// assert!(foo.bar()); |
304 | /// | 324 | /// |
325 | /// let bar = foo.bar || Foo::bar; | ||
326 | /// | ||
305 | /// /* multi-line | 327 | /// /* multi-line |
306 | /// comment */ | 328 | /// comment */ |
307 | /// | 329 | /// |
@@ -311,9 +333,13 @@ impl Foo { | |||
311 | /// | 333 | /// |
312 | /// ``` | 334 | /// ``` |
313 | /// | 335 | /// |
314 | /// ``` | 336 | /// ```rust,no_run |
315 | /// let foobar = Foo::new().bar(); | 337 | /// let foobar = Foo::new().bar(); |
316 | /// ``` | 338 | /// ``` |
339 | /// | ||
340 | /// ```sh | ||
341 | /// echo 1 | ||
342 | /// ``` | ||
317 | pub fn foo(&self) -> bool { | 343 | pub fn foo(&self) -> bool { |
318 | true | 344 | true |
319 | } | 345 | } |
@@ -322,7 +348,7 @@ impl Foo { | |||
322 | .trim(), | 348 | .trim(), |
323 | "crates/ra_ide/src/snapshots/highlight_doctest.html", | 349 | "crates/ra_ide/src/snapshots/highlight_doctest.html", |
324 | false, | 350 | false, |
325 | ) | 351 | ); |
326 | } | 352 | } |
327 | 353 | ||
328 | /// Highlights the code given by the `ra_fixture` argument, renders the | 354 | /// Highlights the code given by the `ra_fixture` argument, renders the |