diff options
Diffstat (limited to 'crates/ra_ide')
37 files changed, 1614 insertions, 546 deletions
diff --git a/crates/ra_ide/src/assists.rs b/crates/ra_ide/src/assists.rs deleted file mode 100644 index 2b5d11681..000000000 --- a/crates/ra_ide/src/assists.rs +++ /dev/null | |||
@@ -1,47 +0,0 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use ra_assists::{resolved_assists, AssistAction, AssistLabel}; | ||
4 | use ra_db::{FilePosition, FileRange}; | ||
5 | use ra_ide_db::RootDatabase; | ||
6 | |||
7 | use crate::{FileId, SourceChange, SourceFileEdit}; | ||
8 | |||
9 | pub use ra_assists::AssistId; | ||
10 | |||
11 | #[derive(Debug)] | ||
12 | pub struct Assist { | ||
13 | pub id: AssistId, | ||
14 | pub label: String, | ||
15 | pub group_label: Option<String>, | ||
16 | pub source_change: SourceChange, | ||
17 | } | ||
18 | |||
19 | pub(crate) fn assists(db: &RootDatabase, frange: FileRange) -> Vec<Assist> { | ||
20 | resolved_assists(db, frange) | ||
21 | .into_iter() | ||
22 | .map(|assist| { | ||
23 | let file_id = frange.file_id; | ||
24 | let assist_label = &assist.label; | ||
25 | Assist { | ||
26 | id: assist_label.id, | ||
27 | label: assist_label.label.clone(), | ||
28 | group_label: assist.group_label.map(|it| it.0), | ||
29 | source_change: action_to_edit(assist.action, file_id, assist_label), | ||
30 | } | ||
31 | }) | ||
32 | .collect() | ||
33 | } | ||
34 | |||
35 | fn action_to_edit( | ||
36 | action: AssistAction, | ||
37 | file_id: FileId, | ||
38 | assist_label: &AssistLabel, | ||
39 | ) -> SourceChange { | ||
40 | let file_id = match action.file { | ||
41 | ra_assists::AssistFile::TargetFile(it) => it, | ||
42 | _ => file_id, | ||
43 | }; | ||
44 | let file_edit = SourceFileEdit { file_id, edit: action.edit }; | ||
45 | SourceChange::source_file_edit(assist_label.label.clone(), file_edit) | ||
46 | .with_cursor_opt(action.cursor_position.map(|offset| FilePosition { offset, file_id })) | ||
47 | } | ||
diff --git a/crates/ra_ide/src/call_info.rs b/crates/ra_ide/src/call_info.rs index 780a03c13..aa039e6fc 100644 --- a/crates/ra_ide/src/call_info.rs +++ b/crates/ra_ide/src/call_info.rs | |||
@@ -5,7 +5,7 @@ use ra_syntax::{ | |||
5 | ast::{self, ArgListOwner}, | 5 | ast::{self, ArgListOwner}, |
6 | match_ast, AstNode, SyntaxNode, SyntaxToken, | 6 | match_ast, AstNode, SyntaxNode, SyntaxToken, |
7 | }; | 7 | }; |
8 | use test_utils::tested_by; | 8 | use test_utils::mark; |
9 | 9 | ||
10 | use crate::{CallInfo, FilePosition, FunctionSignature}; | 10 | use crate::{CallInfo, FilePosition, FunctionSignature}; |
11 | 11 | ||
@@ -84,7 +84,7 @@ fn call_info_for_token(sema: &Semantics<RootDatabase>, token: SyntaxToken) -> Op | |||
84 | 84 | ||
85 | let arg_list_range = arg_list.syntax().text_range(); | 85 | let arg_list_range = arg_list.syntax().text_range(); |
86 | if !arg_list_range.contains_inclusive(token.text_range().start()) { | 86 | if !arg_list_range.contains_inclusive(token.text_range().start()) { |
87 | tested_by!(call_info_bad_offset); | 87 | mark::hit!(call_info_bad_offset); |
88 | return None; | 88 | return None; |
89 | } | 89 | } |
90 | 90 | ||
@@ -213,7 +213,7 @@ impl CallInfo { | |||
213 | 213 | ||
214 | #[cfg(test)] | 214 | #[cfg(test)] |
215 | mod tests { | 215 | mod tests { |
216 | use test_utils::covers; | 216 | use test_utils::mark; |
217 | 217 | ||
218 | use crate::mock_analysis::single_file_with_position; | 218 | use crate::mock_analysis::single_file_with_position; |
219 | 219 | ||
@@ -529,7 +529,7 @@ By default this method stops actor's `Context`."# | |||
529 | 529 | ||
530 | #[test] | 530 | #[test] |
531 | fn call_info_bad_offset() { | 531 | fn call_info_bad_offset() { |
532 | covers!(call_info_bad_offset); | 532 | mark::check!(call_info_bad_offset); |
533 | let (analysis, position) = single_file_with_position( | 533 | let (analysis, position) = single_file_with_position( |
534 | r#"fn foo(x: u32, y: u32) -> u32 {x + y} | 534 | r#"fn foo(x: u32, y: u32) -> u32 {x + y} |
535 | fn bar() { foo <|> (3, ); }"#, | 535 | fn bar() { foo <|> (3, ); }"#, |
diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs index 4ca0fdf4f..191300704 100644 --- a/crates/ra_ide/src/completion.rs +++ b/crates/ra_ide/src/completion.rs | |||
@@ -59,13 +59,13 @@ pub use crate::completion::{ | |||
59 | /// with ordering of completions (currently this is done by the client). | 59 | /// with ordering of completions (currently this is done by the client). |
60 | pub(crate) fn completions( | 60 | pub(crate) fn completions( |
61 | db: &RootDatabase, | 61 | db: &RootDatabase, |
62 | position: FilePosition, | ||
63 | config: &CompletionConfig, | 62 | config: &CompletionConfig, |
63 | position: FilePosition, | ||
64 | ) -> Option<Completions> { | 64 | ) -> Option<Completions> { |
65 | let ctx = CompletionContext::new(db, position, config)?; | 65 | let ctx = CompletionContext::new(db, position, config)?; |
66 | 66 | ||
67 | let mut acc = Completions::default(); | 67 | let mut acc = Completions::default(); |
68 | 68 | complete_attribute::complete_attribute(&mut acc, &ctx); | |
69 | complete_fn_param::complete_fn_param(&mut acc, &ctx); | 69 | complete_fn_param::complete_fn_param(&mut acc, &ctx); |
70 | complete_keyword::complete_expr_keyword(&mut acc, &ctx); | 70 | complete_keyword::complete_expr_keyword(&mut acc, &ctx); |
71 | complete_keyword::complete_use_tree_keyword(&mut acc, &ctx); | 71 | complete_keyword::complete_use_tree_keyword(&mut acc, &ctx); |
@@ -79,7 +79,6 @@ pub(crate) fn completions( | |||
79 | complete_postfix::complete_postfix(&mut acc, &ctx); | 79 | complete_postfix::complete_postfix(&mut acc, &ctx); |
80 | complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx); | 80 | complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx); |
81 | complete_trait_impl::complete_trait_impl(&mut acc, &ctx); | 81 | complete_trait_impl::complete_trait_impl(&mut acc, &ctx); |
82 | complete_attribute::complete_attribute(&mut acc, &ctx); | ||
83 | 82 | ||
84 | Some(acc) | 83 | Some(acc) |
85 | } | 84 | } |
diff --git a/crates/ra_ide/src/completion/complete_attribute.rs b/crates/ra_ide/src/completion/complete_attribute.rs index 8bf952798..f17266221 100644 --- a/crates/ra_ide/src/completion/complete_attribute.rs +++ b/crates/ra_ide/src/completion/complete_attribute.rs | |||
@@ -3,25 +3,29 @@ | |||
3 | //! This module uses a bit of static metadata to provide completions | 3 | //! This module uses a bit of static metadata to provide completions |
4 | //! for built-in attributes. | 4 | //! for built-in attributes. |
5 | 5 | ||
6 | use super::completion_context::CompletionContext; | 6 | use ra_syntax::{ast, AstNode, SyntaxKind}; |
7 | use super::completion_item::{CompletionItem, CompletionItemKind, CompletionKind, Completions}; | 7 | use rustc_hash::FxHashSet; |
8 | use ra_syntax::{ | 8 | |
9 | ast::{Attr, AttrKind}, | 9 | use crate::completion::{ |
10 | AstNode, | 10 | completion_context::CompletionContext, |
11 | completion_item::{CompletionItem, CompletionItemKind, CompletionKind, Completions}, | ||
11 | }; | 12 | }; |
12 | 13 | ||
13 | pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) { | 14 | pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { |
14 | if !ctx.is_attribute { | 15 | let attribute = ctx.attribute_under_caret.as_ref()?; |
15 | return; | ||
16 | } | ||
17 | 16 | ||
18 | let is_inner = ctx | 17 | match (attribute.path(), attribute.input()) { |
19 | .original_token | 18 | (Some(path), Some(ast::AttrInput::TokenTree(token_tree))) |
20 | .ancestors() | 19 | if path.to_string() == "derive" => |
21 | .find_map(Attr::cast) | 20 | { |
22 | .map(|attr| attr.kind() == AttrKind::Inner) | 21 | complete_derive(acc, ctx, token_tree) |
23 | .unwrap_or(false); | 22 | } |
23 | _ => complete_attribute_start(acc, ctx, attribute), | ||
24 | } | ||
25 | Some(()) | ||
26 | } | ||
24 | 27 | ||
28 | fn complete_attribute_start(acc: &mut Completions, ctx: &CompletionContext, attribute: &ast::Attr) { | ||
25 | for attr_completion in ATTRIBUTES { | 29 | for attr_completion in ATTRIBUTES { |
26 | let mut item = CompletionItem::new( | 30 | let mut item = CompletionItem::new( |
27 | CompletionKind::Attribute, | 31 | CompletionKind::Attribute, |
@@ -37,7 +41,7 @@ pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) | |||
37 | _ => {} | 41 | _ => {} |
38 | } | 42 | } |
39 | 43 | ||
40 | if is_inner || !attr_completion.should_be_inner { | 44 | if attribute.kind() == ast::AttrKind::Inner || !attr_completion.should_be_inner { |
41 | acc.add(item); | 45 | acc.add(item); |
42 | } | 46 | } |
43 | } | 47 | } |
@@ -126,6 +130,106 @@ const ATTRIBUTES: &[AttrCompletion] = &[ | |||
126 | }, | 130 | }, |
127 | ]; | 131 | ]; |
128 | 132 | ||
133 | fn complete_derive(acc: &mut Completions, ctx: &CompletionContext, derive_input: ast::TokenTree) { | ||
134 | if let Ok(existing_derives) = parse_derive_input(derive_input) { | ||
135 | for derive_completion in DEFAULT_DERIVE_COMPLETIONS | ||
136 | .into_iter() | ||
137 | .filter(|completion| !existing_derives.contains(completion.label)) | ||
138 | { | ||
139 | let mut label = derive_completion.label.to_owned(); | ||
140 | for dependency in derive_completion | ||
141 | .dependencies | ||
142 | .into_iter() | ||
143 | .filter(|&&dependency| !existing_derives.contains(dependency)) | ||
144 | { | ||
145 | label.push_str(", "); | ||
146 | label.push_str(dependency); | ||
147 | } | ||
148 | acc.add( | ||
149 | CompletionItem::new(CompletionKind::Attribute, ctx.source_range(), label) | ||
150 | .kind(CompletionItemKind::Attribute), | ||
151 | ); | ||
152 | } | ||
153 | |||
154 | for custom_derive_name in get_derive_names_in_scope(ctx).difference(&existing_derives) { | ||
155 | acc.add( | ||
156 | CompletionItem::new( | ||
157 | CompletionKind::Attribute, | ||
158 | ctx.source_range(), | ||
159 | custom_derive_name, | ||
160 | ) | ||
161 | .kind(CompletionItemKind::Attribute), | ||
162 | ); | ||
163 | } | ||
164 | } | ||
165 | } | ||
166 | |||
167 | fn parse_derive_input(derive_input: ast::TokenTree) -> Result<FxHashSet<String>, ()> { | ||
168 | match (derive_input.left_delimiter_token(), derive_input.right_delimiter_token()) { | ||
169 | (Some(left_paren), Some(right_paren)) | ||
170 | if left_paren.kind() == SyntaxKind::L_PAREN | ||
171 | && right_paren.kind() == SyntaxKind::R_PAREN => | ||
172 | { | ||
173 | let mut input_derives = FxHashSet::default(); | ||
174 | let mut current_derive = String::new(); | ||
175 | for token in derive_input | ||
176 | .syntax() | ||
177 | .children_with_tokens() | ||
178 | .filter_map(|token| token.into_token()) | ||
179 | .skip_while(|token| token != &left_paren) | ||
180 | .skip(1) | ||
181 | .take_while(|token| token != &right_paren) | ||
182 | { | ||
183 | if SyntaxKind::COMMA == token.kind() { | ||
184 | if !current_derive.is_empty() { | ||
185 | input_derives.insert(current_derive); | ||
186 | current_derive = String::new(); | ||
187 | } | ||
188 | } else { | ||
189 | current_derive.push_str(token.to_string().trim()); | ||
190 | } | ||
191 | } | ||
192 | |||
193 | if !current_derive.is_empty() { | ||
194 | input_derives.insert(current_derive); | ||
195 | } | ||
196 | Ok(input_derives) | ||
197 | } | ||
198 | _ => Err(()), | ||
199 | } | ||
200 | } | ||
201 | |||
202 | fn get_derive_names_in_scope(ctx: &CompletionContext) -> FxHashSet<String> { | ||
203 | let mut result = FxHashSet::default(); | ||
204 | ctx.scope().process_all_names(&mut |name, scope_def| { | ||
205 | if let hir::ScopeDef::MacroDef(mac) = scope_def { | ||
206 | if mac.is_derive_macro() { | ||
207 | result.insert(name.to_string()); | ||
208 | } | ||
209 | } | ||
210 | }); | ||
211 | result | ||
212 | } | ||
213 | |||
214 | struct DeriveCompletion { | ||
215 | label: &'static str, | ||
216 | dependencies: &'static [&'static str], | ||
217 | } | ||
218 | |||
219 | /// Standard Rust derives and the information about their dependencies | ||
220 | /// (the dependencies are needed so that the main derive don't break the compilation when added) | ||
221 | const DEFAULT_DERIVE_COMPLETIONS: &[DeriveCompletion] = &[ | ||
222 | DeriveCompletion { label: "Clone", dependencies: &[] }, | ||
223 | DeriveCompletion { label: "Copy", dependencies: &["Clone"] }, | ||
224 | DeriveCompletion { label: "Debug", dependencies: &[] }, | ||
225 | DeriveCompletion { label: "Default", dependencies: &[] }, | ||
226 | DeriveCompletion { label: "Hash", dependencies: &[] }, | ||
227 | DeriveCompletion { label: "PartialEq", dependencies: &[] }, | ||
228 | DeriveCompletion { label: "Eq", dependencies: &["PartialEq"] }, | ||
229 | DeriveCompletion { label: "PartialOrd", dependencies: &["PartialEq"] }, | ||
230 | DeriveCompletion { label: "Ord", dependencies: &["PartialOrd", "Eq", "PartialEq"] }, | ||
231 | ]; | ||
232 | |||
129 | #[cfg(test)] | 233 | #[cfg(test)] |
130 | mod tests { | 234 | mod tests { |
131 | use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; | 235 | use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; |
@@ -136,6 +240,170 @@ mod tests { | |||
136 | } | 240 | } |
137 | 241 | ||
138 | #[test] | 242 | #[test] |
243 | fn empty_derive_completion() { | ||
244 | assert_debug_snapshot!( | ||
245 | do_attr_completion( | ||
246 | r" | ||
247 | #[derive(<|>)] | ||
248 | struct Test {} | ||
249 | ", | ||
250 | ), | ||
251 | @r###" | ||
252 | [ | ||
253 | CompletionItem { | ||
254 | label: "Clone", | ||
255 | source_range: 30..30, | ||
256 | delete: 30..30, | ||
257 | insert: "Clone", | ||
258 | kind: Attribute, | ||
259 | }, | ||
260 | CompletionItem { | ||
261 | label: "Copy, Clone", | ||
262 | source_range: 30..30, | ||
263 | delete: 30..30, | ||
264 | insert: "Copy, Clone", | ||
265 | kind: Attribute, | ||
266 | }, | ||
267 | CompletionItem { | ||
268 | label: "Debug", | ||
269 | source_range: 30..30, | ||
270 | delete: 30..30, | ||
271 | insert: "Debug", | ||
272 | kind: Attribute, | ||
273 | }, | ||
274 | CompletionItem { | ||
275 | label: "Default", | ||
276 | source_range: 30..30, | ||
277 | delete: 30..30, | ||
278 | insert: "Default", | ||
279 | kind: Attribute, | ||
280 | }, | ||
281 | CompletionItem { | ||
282 | label: "Eq, PartialEq", | ||
283 | source_range: 30..30, | ||
284 | delete: 30..30, | ||
285 | insert: "Eq, PartialEq", | ||
286 | kind: Attribute, | ||
287 | }, | ||
288 | CompletionItem { | ||
289 | label: "Hash", | ||
290 | source_range: 30..30, | ||
291 | delete: 30..30, | ||
292 | insert: "Hash", | ||
293 | kind: Attribute, | ||
294 | }, | ||
295 | CompletionItem { | ||
296 | label: "Ord, PartialOrd, Eq, PartialEq", | ||
297 | source_range: 30..30, | ||
298 | delete: 30..30, | ||
299 | insert: "Ord, PartialOrd, Eq, PartialEq", | ||
300 | kind: Attribute, | ||
301 | }, | ||
302 | CompletionItem { | ||
303 | label: "PartialEq", | ||
304 | source_range: 30..30, | ||
305 | delete: 30..30, | ||
306 | insert: "PartialEq", | ||
307 | kind: Attribute, | ||
308 | }, | ||
309 | CompletionItem { | ||
310 | label: "PartialOrd, PartialEq", | ||
311 | source_range: 30..30, | ||
312 | delete: 30..30, | ||
313 | insert: "PartialOrd, PartialEq", | ||
314 | kind: Attribute, | ||
315 | }, | ||
316 | ] | ||
317 | "### | ||
318 | ); | ||
319 | } | ||
320 | |||
321 | #[test] | ||
322 | fn no_completion_for_incorrect_derive() { | ||
323 | assert_debug_snapshot!( | ||
324 | do_attr_completion( | ||
325 | r" | ||
326 | #[derive{<|>)] | ||
327 | struct Test {} | ||
328 | ", | ||
329 | ), | ||
330 | @"[]" | ||
331 | ); | ||
332 | } | ||
333 | |||
334 | #[test] | ||
335 | fn derive_with_input_completion() { | ||
336 | assert_debug_snapshot!( | ||
337 | do_attr_completion( | ||
338 | r" | ||
339 | #[derive(serde::Serialize, PartialEq, <|>)] | ||
340 | struct Test {} | ||
341 | ", | ||
342 | ), | ||
343 | @r###" | ||
344 | [ | ||
345 | CompletionItem { | ||
346 | label: "Clone", | ||
347 | source_range: 59..59, | ||
348 | delete: 59..59, | ||
349 | insert: "Clone", | ||
350 | kind: Attribute, | ||
351 | }, | ||
352 | CompletionItem { | ||
353 | label: "Copy, Clone", | ||
354 | source_range: 59..59, | ||
355 | delete: 59..59, | ||
356 | insert: "Copy, Clone", | ||
357 | kind: Attribute, | ||
358 | }, | ||
359 | CompletionItem { | ||
360 | label: "Debug", | ||
361 | source_range: 59..59, | ||
362 | delete: 59..59, | ||
363 | insert: "Debug", | ||
364 | kind: Attribute, | ||
365 | }, | ||
366 | CompletionItem { | ||
367 | label: "Default", | ||
368 | source_range: 59..59, | ||
369 | delete: 59..59, | ||
370 | insert: "Default", | ||
371 | kind: Attribute, | ||
372 | }, | ||
373 | CompletionItem { | ||
374 | label: "Eq", | ||
375 | source_range: 59..59, | ||
376 | delete: 59..59, | ||
377 | insert: "Eq", | ||
378 | kind: Attribute, | ||
379 | }, | ||
380 | CompletionItem { | ||
381 | label: "Hash", | ||
382 | source_range: 59..59, | ||
383 | delete: 59..59, | ||
384 | insert: "Hash", | ||
385 | kind: Attribute, | ||
386 | }, | ||
387 | CompletionItem { | ||
388 | label: "Ord, PartialOrd, Eq", | ||
389 | source_range: 59..59, | ||
390 | delete: 59..59, | ||
391 | insert: "Ord, PartialOrd, Eq", | ||
392 | kind: Attribute, | ||
393 | }, | ||
394 | CompletionItem { | ||
395 | label: "PartialOrd", | ||
396 | source_range: 59..59, | ||
397 | delete: 59..59, | ||
398 | insert: "PartialOrd", | ||
399 | kind: Attribute, | ||
400 | }, | ||
401 | ] | ||
402 | "### | ||
403 | ); | ||
404 | } | ||
405 | |||
406 | #[test] | ||
139 | fn test_attribute_completion() { | 407 | fn test_attribute_completion() { |
140 | assert_debug_snapshot!( | 408 | assert_debug_snapshot!( |
141 | do_attr_completion( | 409 | do_attr_completion( |
diff --git a/crates/ra_ide/src/completion/complete_postfix.rs b/crates/ra_ide/src/completion/complete_postfix.rs index 6a0f0c72e..f2a52a407 100644 --- a/crates/ra_ide/src/completion/complete_postfix.rs +++ b/crates/ra_ide/src/completion/complete_postfix.rs | |||
@@ -14,6 +14,7 @@ use crate::{ | |||
14 | }, | 14 | }, |
15 | CompletionItem, | 15 | CompletionItem, |
16 | }; | 16 | }; |
17 | use ra_assists::utils::TryEnum; | ||
17 | 18 | ||
18 | pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { | 19 | pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { |
19 | if !ctx.config.enable_postfix_completions { | 20 | if !ctx.config.enable_postfix_completions { |
@@ -37,8 +38,53 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { | |||
37 | Some(it) => it, | 38 | Some(it) => it, |
38 | None => return, | 39 | None => return, |
39 | }; | 40 | }; |
41 | let try_enum = TryEnum::from_ty(&ctx.sema, &receiver_ty); | ||
42 | if let Some(try_enum) = &try_enum { | ||
43 | match try_enum { | ||
44 | TryEnum::Result => { | ||
45 | postfix_snippet( | ||
46 | ctx, | ||
47 | cap, | ||
48 | &dot_receiver, | ||
49 | "ifl", | ||
50 | "if let Ok {}", | ||
51 | &format!("if let Ok($1) = {} {{\n $0\n}}", receiver_text), | ||
52 | ) | ||
53 | .add_to(acc); | ||
40 | 54 | ||
41 | if receiver_ty.is_bool() || receiver_ty.is_unknown() { | 55 | postfix_snippet( |
56 | ctx, | ||
57 | cap, | ||
58 | &dot_receiver, | ||
59 | "while", | ||
60 | "while let Ok {}", | ||
61 | &format!("while let Ok($1) = {} {{\n $0\n}}", receiver_text), | ||
62 | ) | ||
63 | .add_to(acc); | ||
64 | } | ||
65 | TryEnum::Option => { | ||
66 | postfix_snippet( | ||
67 | ctx, | ||
68 | cap, | ||
69 | &dot_receiver, | ||
70 | "ifl", | ||
71 | "if let Some {}", | ||
72 | &format!("if let Some($1) = {} {{\n $0\n}}", receiver_text), | ||
73 | ) | ||
74 | .add_to(acc); | ||
75 | |||
76 | postfix_snippet( | ||
77 | ctx, | ||
78 | cap, | ||
79 | &dot_receiver, | ||
80 | "while", | ||
81 | "while let Some {}", | ||
82 | &format!("while let Some($1) = {} {{\n $0\n}}", receiver_text), | ||
83 | ) | ||
84 | .add_to(acc); | ||
85 | } | ||
86 | } | ||
87 | } else if receiver_ty.is_bool() || receiver_ty.is_unknown() { | ||
42 | postfix_snippet( | 88 | postfix_snippet( |
43 | ctx, | 89 | ctx, |
44 | cap, | 90 | cap, |
@@ -58,7 +104,6 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { | |||
58 | ) | 104 | ) |
59 | .add_to(acc); | 105 | .add_to(acc); |
60 | } | 106 | } |
61 | |||
62 | // !&&&42 is a compiler error, ergo process it before considering the references | 107 | // !&&&42 is a compiler error, ergo process it before considering the references |
63 | postfix_snippet(ctx, cap, &dot_receiver, "not", "!expr", &format!("!{}", receiver_text)) | 108 | postfix_snippet(ctx, cap, &dot_receiver, "not", "!expr", &format!("!{}", receiver_text)) |
64 | .add_to(acc); | 109 | .add_to(acc); |
@@ -80,16 +125,45 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { | |||
80 | let dot_receiver = include_references(dot_receiver); | 125 | let dot_receiver = include_references(dot_receiver); |
81 | let receiver_text = | 126 | let receiver_text = |
82 | get_receiver_text(&dot_receiver, ctx.dot_receiver_is_ambiguous_float_literal); | 127 | get_receiver_text(&dot_receiver, ctx.dot_receiver_is_ambiguous_float_literal); |
83 | 128 | match try_enum { | |
84 | postfix_snippet( | 129 | Some(try_enum) => { |
85 | ctx, | 130 | match try_enum { |
86 | cap, | 131 | TryEnum::Result => { |
87 | &dot_receiver, | 132 | postfix_snippet( |
88 | "match", | 133 | ctx, |
89 | "match expr {}", | 134 | cap, |
90 | &format!("match {} {{\n ${{1:_}} => {{$0\\}},\n}}", receiver_text), | 135 | &dot_receiver, |
91 | ) | 136 | "match", |
92 | .add_to(acc); | 137 | "match expr {}", |
138 | &format!("match {} {{\n Ok(${{1:_}}) => {{$2\\}},\n Err(${{3:_}}) => {{$0\\}},\n}}", receiver_text), | ||
139 | ) | ||
140 | .add_to(acc); | ||
141 | } | ||
142 | TryEnum::Option => { | ||
143 | postfix_snippet( | ||
144 | ctx, | ||
145 | cap, | ||
146 | &dot_receiver, | ||
147 | "match", | ||
148 | "match expr {}", | ||
149 | &format!("match {} {{\n Some(${{1:_}}) => {{$2\\}},\n None => {{$0\\}},\n}}", receiver_text), | ||
150 | ) | ||
151 | .add_to(acc); | ||
152 | } | ||
153 | } | ||
154 | } | ||
155 | None => { | ||
156 | postfix_snippet( | ||
157 | ctx, | ||
158 | cap, | ||
159 | &dot_receiver, | ||
160 | "match", | ||
161 | "match expr {}", | ||
162 | &format!("match {} {{\n ${{1:_}} => {{$0\\}},\n}}", receiver_text), | ||
163 | ) | ||
164 | .add_to(acc); | ||
165 | } | ||
166 | } | ||
93 | 167 | ||
94 | postfix_snippet( | 168 | postfix_snippet( |
95 | ctx, | 169 | ctx, |
@@ -236,6 +310,164 @@ mod tests { | |||
236 | } | 310 | } |
237 | 311 | ||
238 | #[test] | 312 | #[test] |
313 | fn postfix_completion_works_for_option() { | ||
314 | assert_debug_snapshot!( | ||
315 | do_postfix_completion( | ||
316 | r#" | ||
317 | enum Option<T> { | ||
318 | Some(T), | ||
319 | None, | ||
320 | } | ||
321 | |||
322 | fn main() { | ||
323 | let bar = Option::Some(true); | ||
324 | bar.<|> | ||
325 | } | ||
326 | "#, | ||
327 | ), | ||
328 | @r###" | ||
329 | [ | ||
330 | CompletionItem { | ||
331 | label: "box", | ||
332 | source_range: 210..210, | ||
333 | delete: 206..210, | ||
334 | insert: "Box::new(bar)", | ||
335 | detail: "Box::new(expr)", | ||
336 | }, | ||
337 | CompletionItem { | ||
338 | label: "dbg", | ||
339 | source_range: 210..210, | ||
340 | delete: 206..210, | ||
341 | insert: "dbg!(bar)", | ||
342 | detail: "dbg!(expr)", | ||
343 | }, | ||
344 | CompletionItem { | ||
345 | label: "ifl", | ||
346 | source_range: 210..210, | ||
347 | delete: 206..210, | ||
348 | insert: "if let Some($1) = bar {\n $0\n}", | ||
349 | detail: "if let Some {}", | ||
350 | }, | ||
351 | CompletionItem { | ||
352 | label: "match", | ||
353 | source_range: 210..210, | ||
354 | delete: 206..210, | ||
355 | insert: "match bar {\n Some(${1:_}) => {$2\\},\n None => {$0\\},\n}", | ||
356 | detail: "match expr {}", | ||
357 | }, | ||
358 | CompletionItem { | ||
359 | label: "not", | ||
360 | source_range: 210..210, | ||
361 | delete: 206..210, | ||
362 | insert: "!bar", | ||
363 | detail: "!expr", | ||
364 | }, | ||
365 | CompletionItem { | ||
366 | label: "ref", | ||
367 | source_range: 210..210, | ||
368 | delete: 206..210, | ||
369 | insert: "&bar", | ||
370 | detail: "&expr", | ||
371 | }, | ||
372 | CompletionItem { | ||
373 | label: "refm", | ||
374 | source_range: 210..210, | ||
375 | delete: 206..210, | ||
376 | insert: "&mut bar", | ||
377 | detail: "&mut expr", | ||
378 | }, | ||
379 | CompletionItem { | ||
380 | label: "while", | ||
381 | source_range: 210..210, | ||
382 | delete: 206..210, | ||
383 | insert: "while let Some($1) = bar {\n $0\n}", | ||
384 | detail: "while let Some {}", | ||
385 | }, | ||
386 | ] | ||
387 | "### | ||
388 | ); | ||
389 | } | ||
390 | |||
391 | #[test] | ||
392 | fn postfix_completion_works_for_result() { | ||
393 | assert_debug_snapshot!( | ||
394 | do_postfix_completion( | ||
395 | r#" | ||
396 | enum Result<T, E> { | ||
397 | Ok(T), | ||
398 | Err(E), | ||
399 | } | ||
400 | |||
401 | fn main() { | ||
402 | let bar = Result::Ok(true); | ||
403 | bar.<|> | ||
404 | } | ||
405 | "#, | ||
406 | ), | ||
407 | @r###" | ||
408 | [ | ||
409 | CompletionItem { | ||
410 | label: "box", | ||
411 | source_range: 211..211, | ||
412 | delete: 207..211, | ||
413 | insert: "Box::new(bar)", | ||
414 | detail: "Box::new(expr)", | ||
415 | }, | ||
416 | CompletionItem { | ||
417 | label: "dbg", | ||
418 | source_range: 211..211, | ||
419 | delete: 207..211, | ||
420 | insert: "dbg!(bar)", | ||
421 | detail: "dbg!(expr)", | ||
422 | }, | ||
423 | CompletionItem { | ||
424 | label: "ifl", | ||
425 | source_range: 211..211, | ||
426 | delete: 207..211, | ||
427 | insert: "if let Ok($1) = bar {\n $0\n}", | ||
428 | detail: "if let Ok {}", | ||
429 | }, | ||
430 | CompletionItem { | ||
431 | label: "match", | ||
432 | source_range: 211..211, | ||
433 | delete: 207..211, | ||
434 | insert: "match bar {\n Ok(${1:_}) => {$2\\},\n Err(${3:_}) => {$0\\},\n}", | ||
435 | detail: "match expr {}", | ||
436 | }, | ||
437 | CompletionItem { | ||
438 | label: "not", | ||
439 | source_range: 211..211, | ||
440 | delete: 207..211, | ||
441 | insert: "!bar", | ||
442 | detail: "!expr", | ||
443 | }, | ||
444 | CompletionItem { | ||
445 | label: "ref", | ||
446 | source_range: 211..211, | ||
447 | delete: 207..211, | ||
448 | insert: "&bar", | ||
449 | detail: "&expr", | ||
450 | }, | ||
451 | CompletionItem { | ||
452 | label: "refm", | ||
453 | source_range: 211..211, | ||
454 | delete: 207..211, | ||
455 | insert: "&mut bar", | ||
456 | detail: "&mut expr", | ||
457 | }, | ||
458 | CompletionItem { | ||
459 | label: "while", | ||
460 | source_range: 211..211, | ||
461 | delete: 207..211, | ||
462 | insert: "while let Ok($1) = bar {\n $0\n}", | ||
463 | detail: "while let Ok {}", | ||
464 | }, | ||
465 | ] | ||
466 | "### | ||
467 | ); | ||
468 | } | ||
469 | |||
470 | #[test] | ||
239 | fn some_postfix_completions_ignored() { | 471 | fn some_postfix_completions_ignored() { |
240 | assert_debug_snapshot!( | 472 | assert_debug_snapshot!( |
241 | do_postfix_completion( | 473 | do_postfix_completion( |
diff --git a/crates/ra_ide/src/completion/complete_qualified_path.rs b/crates/ra_ide/src/completion/complete_qualified_path.rs index aa56a5cd8..02ac0166b 100644 --- a/crates/ra_ide/src/completion/complete_qualified_path.rs +++ b/crates/ra_ide/src/completion/complete_qualified_path.rs | |||
@@ -2,20 +2,25 @@ | |||
2 | 2 | ||
3 | use hir::{Adt, HasVisibility, PathResolution, ScopeDef}; | 3 | use hir::{Adt, HasVisibility, PathResolution, ScopeDef}; |
4 | use ra_syntax::AstNode; | 4 | use ra_syntax::AstNode; |
5 | use test_utils::tested_by; | 5 | use rustc_hash::FxHashSet; |
6 | use test_utils::mark; | ||
6 | 7 | ||
7 | use crate::completion::{CompletionContext, Completions}; | 8 | use crate::completion::{CompletionContext, Completions}; |
8 | use rustc_hash::FxHashSet; | ||
9 | 9 | ||
10 | pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionContext) { | 10 | pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionContext) { |
11 | let path = match &ctx.path_prefix { | 11 | let path = match &ctx.path_prefix { |
12 | Some(path) => path.clone(), | 12 | Some(path) => path.clone(), |
13 | _ => return, | 13 | None => return, |
14 | }; | 14 | }; |
15 | |||
16 | if ctx.attribute_under_caret.is_some() { | ||
17 | return; | ||
18 | } | ||
19 | |||
15 | let scope = ctx.scope(); | 20 | let scope = ctx.scope(); |
16 | let context_module = scope.module(); | 21 | let context_module = scope.module(); |
17 | 22 | ||
18 | let res = match scope.resolve_hir_path(&path) { | 23 | let res = match scope.resolve_hir_path_qualifier(&path) { |
19 | Some(res) => res, | 24 | Some(res) => res, |
20 | None => return, | 25 | None => return, |
21 | }; | 26 | }; |
@@ -35,7 +40,7 @@ pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon | |||
35 | if let Some(name_ref) = ctx.name_ref_syntax.as_ref() { | 40 | if let Some(name_ref) = ctx.name_ref_syntax.as_ref() { |
36 | if name_ref.syntax().text() == name.to_string().as_str() { | 41 | if name_ref.syntax().text() == name.to_string().as_str() { |
37 | // for `use self::foo<|>`, don't suggest `foo` as a completion | 42 | // for `use self::foo<|>`, don't suggest `foo` as a completion |
38 | tested_by!(dont_complete_current_use); | 43 | mark::hit!(dont_complete_current_use); |
39 | continue; | 44 | continue; |
40 | } | 45 | } |
41 | } | 46 | } |
@@ -79,7 +84,7 @@ pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon | |||
79 | }); | 84 | }); |
80 | 85 | ||
81 | // Iterate assoc types separately | 86 | // Iterate assoc types separately |
82 | ty.iterate_impl_items(ctx.db, krate, |item| { | 87 | ty.iterate_assoc_items(ctx.db, krate, |item| { |
83 | if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) { | 88 | if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) { |
84 | return None; | 89 | return None; |
85 | } | 90 | } |
@@ -142,7 +147,7 @@ pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon | |||
142 | 147 | ||
143 | #[cfg(test)] | 148 | #[cfg(test)] |
144 | mod tests { | 149 | mod tests { |
145 | use test_utils::covers; | 150 | use test_utils::mark; |
146 | 151 | ||
147 | use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; | 152 | use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; |
148 | use insta::assert_debug_snapshot; | 153 | use insta::assert_debug_snapshot; |
@@ -153,7 +158,7 @@ mod tests { | |||
153 | 158 | ||
154 | #[test] | 159 | #[test] |
155 | fn dont_complete_current_use() { | 160 | fn dont_complete_current_use() { |
156 | covers!(dont_complete_current_use); | 161 | mark::check!(dont_complete_current_use); |
157 | let completions = do_completion(r"use self::foo<|>;", CompletionKind::Reference); | 162 | let completions = do_completion(r"use self::foo<|>;", CompletionKind::Reference); |
158 | assert!(completions.is_empty()); | 163 | assert!(completions.is_empty()); |
159 | } | 164 | } |
@@ -221,6 +226,34 @@ mod tests { | |||
221 | } | 226 | } |
222 | 227 | ||
223 | #[test] | 228 | #[test] |
229 | fn completes_mod_with_same_name_as_function() { | ||
230 | assert_debug_snapshot!( | ||
231 | do_reference_completion( | ||
232 | r" | ||
233 | use self::my::<|>; | ||
234 | |||
235 | mod my { | ||
236 | pub struct Bar; | ||
237 | } | ||
238 | |||
239 | fn my() {} | ||
240 | " | ||
241 | ), | ||
242 | @r###" | ||
243 | [ | ||
244 | CompletionItem { | ||
245 | label: "Bar", | ||
246 | source_range: 31..31, | ||
247 | delete: 31..31, | ||
248 | insert: "Bar", | ||
249 | kind: Struct, | ||
250 | }, | ||
251 | ] | ||
252 | "### | ||
253 | ); | ||
254 | } | ||
255 | |||
256 | #[test] | ||
224 | fn path_visibility() { | 257 | fn path_visibility() { |
225 | assert_debug_snapshot!( | 258 | assert_debug_snapshot!( |
226 | do_reference_completion( | 259 | do_reference_completion( |
@@ -1325,4 +1358,18 @@ mod tests { | |||
1325 | "### | 1358 | "### |
1326 | ); | 1359 | ); |
1327 | } | 1360 | } |
1361 | |||
1362 | #[test] | ||
1363 | fn dont_complete_attr() { | ||
1364 | assert_debug_snapshot!( | ||
1365 | do_reference_completion( | ||
1366 | r" | ||
1367 | mod foo { pub struct Foo; } | ||
1368 | #[foo::<|>] | ||
1369 | fn f() {} | ||
1370 | " | ||
1371 | ), | ||
1372 | @r###"[]"### | ||
1373 | ) | ||
1374 | } | ||
1328 | } | 1375 | } |
diff --git a/crates/ra_ide/src/completion/complete_snippet.rs b/crates/ra_ide/src/completion/complete_snippet.rs index a3f5d1b6a..0568d9ccf 100644 --- a/crates/ra_ide/src/completion/complete_snippet.rs +++ b/crates/ra_ide/src/completion/complete_snippet.rs | |||
@@ -36,6 +36,24 @@ pub(super) fn complete_item_snippet(acc: &mut Completions, ctx: &CompletionConte | |||
36 | snippet( | 36 | snippet( |
37 | ctx, | 37 | ctx, |
38 | cap, | 38 | cap, |
39 | "Test module", | ||
40 | "\ | ||
41 | #[cfg(test)] | ||
42 | mod tests { | ||
43 | use super::*; | ||
44 | |||
45 | #[test] | ||
46 | fn ${1:test_name}() { | ||
47 | $0 | ||
48 | } | ||
49 | }", | ||
50 | ) | ||
51 | .lookup_by("tmod") | ||
52 | .add_to(acc); | ||
53 | |||
54 | snippet( | ||
55 | ctx, | ||
56 | cap, | ||
39 | "Test function", | 57 | "Test function", |
40 | "\ | 58 | "\ |
41 | #[test] | 59 | #[test] |
@@ -118,6 +136,14 @@ mod tests { | |||
118 | lookup: "tfn", | 136 | lookup: "tfn", |
119 | }, | 137 | }, |
120 | CompletionItem { | 138 | CompletionItem { |
139 | label: "Test module", | ||
140 | source_range: 78..78, | ||
141 | delete: 78..78, | ||
142 | insert: "#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn ${1:test_name}() {\n $0\n }\n}", | ||
143 | kind: Snippet, | ||
144 | lookup: "tmod", | ||
145 | }, | ||
146 | CompletionItem { | ||
121 | label: "macro_rules", | 147 | label: "macro_rules", |
122 | source_range: 78..78, | 148 | source_range: 78..78, |
123 | delete: 78..78, | 149 | delete: 78..78, |
diff --git a/crates/ra_ide/src/completion/complete_trait_impl.rs b/crates/ra_ide/src/completion/complete_trait_impl.rs index ee32d1ff6..039df03e0 100644 --- a/crates/ra_ide/src/completion/complete_trait_impl.rs +++ b/crates/ra_ide/src/completion/complete_trait_impl.rs | |||
@@ -32,7 +32,7 @@ | |||
32 | //! ``` | 32 | //! ``` |
33 | 33 | ||
34 | use hir::{self, Docs, HasSource}; | 34 | use hir::{self, Docs, HasSource}; |
35 | use ra_assists::utils::get_missing_impl_items; | 35 | use ra_assists::utils::get_missing_assoc_items; |
36 | use ra_syntax::{ | 36 | use ra_syntax::{ |
37 | ast::{self, edit, ImplDef}, | 37 | ast::{self, edit, ImplDef}, |
38 | AstNode, SyntaxKind, SyntaxNode, TextRange, T, | 38 | AstNode, SyntaxKind, SyntaxNode, TextRange, T, |
@@ -50,7 +50,7 @@ pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext | |||
50 | if let Some((trigger, impl_def)) = completion_match(ctx) { | 50 | if let Some((trigger, impl_def)) = completion_match(ctx) { |
51 | match trigger.kind() { | 51 | match trigger.kind() { |
52 | SyntaxKind::NAME_REF => { | 52 | SyntaxKind::NAME_REF => { |
53 | get_missing_impl_items(&ctx.sema, &impl_def).iter().for_each(|item| match item { | 53 | get_missing_assoc_items(&ctx.sema, &impl_def).iter().for_each(|item| match item { |
54 | hir::AssocItem::Function(fn_item) => { | 54 | hir::AssocItem::Function(fn_item) => { |
55 | add_function_impl(&trigger, acc, ctx, &fn_item) | 55 | add_function_impl(&trigger, acc, ctx, &fn_item) |
56 | } | 56 | } |
@@ -64,34 +64,40 @@ pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext | |||
64 | } | 64 | } |
65 | 65 | ||
66 | SyntaxKind::FN_DEF => { | 66 | SyntaxKind::FN_DEF => { |
67 | for missing_fn in get_missing_impl_items(&ctx.sema, &impl_def).iter().filter_map( | 67 | for missing_fn in |
68 | |item| match item { | 68 | get_missing_assoc_items(&ctx.sema, &impl_def).iter().filter_map(|item| { |
69 | hir::AssocItem::Function(fn_item) => Some(fn_item), | 69 | match item { |
70 | _ => None, | 70 | hir::AssocItem::Function(fn_item) => Some(fn_item), |
71 | }, | 71 | _ => None, |
72 | ) { | 72 | } |
73 | }) | ||
74 | { | ||
73 | add_function_impl(&trigger, acc, ctx, &missing_fn); | 75 | add_function_impl(&trigger, acc, ctx, &missing_fn); |
74 | } | 76 | } |
75 | } | 77 | } |
76 | 78 | ||
77 | SyntaxKind::TYPE_ALIAS_DEF => { | 79 | SyntaxKind::TYPE_ALIAS_DEF => { |
78 | for missing_fn in get_missing_impl_items(&ctx.sema, &impl_def).iter().filter_map( | 80 | for missing_fn in |
79 | |item| match item { | 81 | get_missing_assoc_items(&ctx.sema, &impl_def).iter().filter_map(|item| { |
80 | hir::AssocItem::TypeAlias(type_item) => Some(type_item), | 82 | match item { |
81 | _ => None, | 83 | hir::AssocItem::TypeAlias(type_item) => Some(type_item), |
82 | }, | 84 | _ => None, |
83 | ) { | 85 | } |
86 | }) | ||
87 | { | ||
84 | add_type_alias_impl(&trigger, acc, ctx, &missing_fn); | 88 | add_type_alias_impl(&trigger, acc, ctx, &missing_fn); |
85 | } | 89 | } |
86 | } | 90 | } |
87 | 91 | ||
88 | SyntaxKind::CONST_DEF => { | 92 | SyntaxKind::CONST_DEF => { |
89 | for missing_fn in get_missing_impl_items(&ctx.sema, &impl_def).iter().filter_map( | 93 | for missing_fn in |
90 | |item| match item { | 94 | get_missing_assoc_items(&ctx.sema, &impl_def).iter().filter_map(|item| { |
91 | hir::AssocItem::Const(const_item) => Some(const_item), | 95 | match item { |
92 | _ => None, | 96 | hir::AssocItem::Const(const_item) => Some(const_item), |
93 | }, | 97 | _ => None, |
94 | ) { | 98 | } |
99 | }) | ||
100 | { | ||
95 | add_const_impl(&trigger, acc, ctx, &missing_fn); | 101 | add_const_impl(&trigger, acc, ctx, &missing_fn); |
96 | } | 102 | } |
97 | } | 103 | } |
diff --git a/crates/ra_ide/src/completion/complete_unqualified_path.rs b/crates/ra_ide/src/completion/complete_unqualified_path.rs index a6a5568de..db791660a 100644 --- a/crates/ra_ide/src/completion/complete_unqualified_path.rs +++ b/crates/ra_ide/src/completion/complete_unqualified_path.rs | |||
@@ -1,16 +1,19 @@ | |||
1 | //! Completion of names from the current scope, e.g. locals and imported items. | 1 | //! Completion of names from the current scope, e.g. locals and imported items. |
2 | 2 | ||
3 | use hir::ScopeDef; | 3 | use hir::ScopeDef; |
4 | use test_utils::tested_by; | 4 | use test_utils::mark; |
5 | 5 | ||
6 | use crate::completion::{CompletionContext, Completions}; | 6 | use crate::completion::{CompletionContext, Completions}; |
7 | use hir::{Adt, ModuleDef, Type}; | 7 | use hir::{Adt, ModuleDef, Type}; |
8 | use ra_syntax::AstNode; | 8 | use ra_syntax::AstNode; |
9 | 9 | ||
10 | pub(super) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) { | 10 | pub(super) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) { |
11 | if (!ctx.is_trivial_path && !ctx.is_pat_binding_or_const) | 11 | if !(ctx.is_trivial_path || ctx.is_pat_binding_or_const) { |
12 | || ctx.record_lit_syntax.is_some() | 12 | return; |
13 | } | ||
14 | if ctx.record_lit_syntax.is_some() | ||
13 | || ctx.record_pat_syntax.is_some() | 15 | || ctx.record_pat_syntax.is_some() |
16 | || ctx.attribute_under_caret.is_some() | ||
14 | { | 17 | { |
15 | return; | 18 | return; |
16 | } | 19 | } |
@@ -27,7 +30,7 @@ pub(super) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC | |||
27 | if ctx.use_item_syntax.is_some() { | 30 | if ctx.use_item_syntax.is_some() { |
28 | if let (ScopeDef::Unknown, Some(name_ref)) = (&res, &ctx.name_ref_syntax) { | 31 | if let (ScopeDef::Unknown, Some(name_ref)) = (&res, &ctx.name_ref_syntax) { |
29 | if name_ref.syntax().text() == name.to_string().as_str() { | 32 | if name_ref.syntax().text() == name.to_string().as_str() { |
30 | tested_by!(self_fulfilling_completion); | 33 | mark::hit!(self_fulfilling_completion); |
31 | return; | 34 | return; |
32 | } | 35 | } |
33 | } | 36 | } |
@@ -63,7 +66,7 @@ fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &T | |||
63 | #[cfg(test)] | 66 | #[cfg(test)] |
64 | mod tests { | 67 | mod tests { |
65 | use insta::assert_debug_snapshot; | 68 | use insta::assert_debug_snapshot; |
66 | use test_utils::covers; | 69 | use test_utils::mark; |
67 | 70 | ||
68 | use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; | 71 | use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; |
69 | 72 | ||
@@ -73,7 +76,7 @@ mod tests { | |||
73 | 76 | ||
74 | #[test] | 77 | #[test] |
75 | fn self_fulfilling_completion() { | 78 | fn self_fulfilling_completion() { |
76 | covers!(self_fulfilling_completion); | 79 | mark::check!(self_fulfilling_completion); |
77 | assert_debug_snapshot!( | 80 | assert_debug_snapshot!( |
78 | do_reference_completion( | 81 | do_reference_completion( |
79 | r#" | 82 | r#" |
@@ -1369,4 +1372,18 @@ mod tests { | |||
1369 | "### | 1372 | "### |
1370 | ) | 1373 | ) |
1371 | } | 1374 | } |
1375 | |||
1376 | #[test] | ||
1377 | fn dont_complete_attr() { | ||
1378 | assert_debug_snapshot!( | ||
1379 | do_reference_completion( | ||
1380 | r" | ||
1381 | struct Foo; | ||
1382 | #[<|>] | ||
1383 | fn f() {} | ||
1384 | " | ||
1385 | ), | ||
1386 | @r###"[]"### | ||
1387 | ) | ||
1388 | } | ||
1372 | } | 1389 | } |
diff --git a/crates/ra_ide/src/completion/completion_context.rs b/crates/ra_ide/src/completion/completion_context.rs index 118fceb2e..da336973c 100644 --- a/crates/ra_ide/src/completion/completion_context.rs +++ b/crates/ra_ide/src/completion/completion_context.rs | |||
@@ -9,7 +9,7 @@ use ra_syntax::{ | |||
9 | SyntaxKind::*, | 9 | SyntaxKind::*, |
10 | SyntaxNode, SyntaxToken, TextRange, TextSize, | 10 | SyntaxNode, SyntaxToken, TextRange, TextSize, |
11 | }; | 11 | }; |
12 | use ra_text_edit::AtomTextEdit; | 12 | use ra_text_edit::Indel; |
13 | 13 | ||
14 | use crate::{call_info::ActiveParameter, completion::CompletionConfig, FilePosition}; | 14 | use crate::{call_info::ActiveParameter, completion::CompletionConfig, FilePosition}; |
15 | 15 | ||
@@ -34,7 +34,7 @@ pub(crate) struct CompletionContext<'a> { | |||
34 | pub(super) record_pat_syntax: Option<ast::RecordPat>, | 34 | pub(super) record_pat_syntax: Option<ast::RecordPat>, |
35 | pub(super) record_field_syntax: Option<ast::RecordField>, | 35 | pub(super) record_field_syntax: Option<ast::RecordField>, |
36 | pub(super) impl_def: Option<ast::ImplDef>, | 36 | pub(super) impl_def: Option<ast::ImplDef>, |
37 | /// FIXME: `ActiveParameter` is string-based, which is very wrong | 37 | /// FIXME: `ActiveParameter` is string-based, which is very very wrong |
38 | pub(super) active_parameter: Option<ActiveParameter>, | 38 | pub(super) active_parameter: Option<ActiveParameter>, |
39 | pub(super) is_param: bool, | 39 | pub(super) is_param: bool, |
40 | /// If a name-binding or reference to a const in a pattern. | 40 | /// If a name-binding or reference to a const in a pattern. |
@@ -58,7 +58,7 @@ pub(crate) struct CompletionContext<'a> { | |||
58 | pub(super) is_macro_call: bool, | 58 | pub(super) is_macro_call: bool, |
59 | pub(super) is_path_type: bool, | 59 | pub(super) is_path_type: bool, |
60 | pub(super) has_type_args: bool, | 60 | pub(super) has_type_args: bool, |
61 | pub(super) is_attribute: bool, | 61 | pub(super) attribute_under_caret: Option<ast::Attr>, |
62 | } | 62 | } |
63 | 63 | ||
64 | impl<'a> CompletionContext<'a> { | 64 | impl<'a> CompletionContext<'a> { |
@@ -76,7 +76,7 @@ impl<'a> CompletionContext<'a> { | |||
76 | // actual completion. | 76 | // actual completion. |
77 | let file_with_fake_ident = { | 77 | let file_with_fake_ident = { |
78 | let parse = db.parse(position.file_id); | 78 | let parse = db.parse(position.file_id); |
79 | let edit = AtomTextEdit::insert(position.offset, "intellijRulezz".to_string()); | 79 | let edit = Indel::insert(position.offset, "intellijRulezz".to_string()); |
80 | parse.reparse(&edit).tree() | 80 | parse.reparse(&edit).tree() |
81 | }; | 81 | }; |
82 | let fake_ident_token = | 82 | let fake_ident_token = |
@@ -116,7 +116,7 @@ impl<'a> CompletionContext<'a> { | |||
116 | is_path_type: false, | 116 | is_path_type: false, |
117 | has_type_args: false, | 117 | has_type_args: false, |
118 | dot_receiver_is_ambiguous_float_literal: false, | 118 | dot_receiver_is_ambiguous_float_literal: false, |
119 | is_attribute: false, | 119 | attribute_under_caret: None, |
120 | }; | 120 | }; |
121 | 121 | ||
122 | let mut original_file = original_file.syntax().clone(); | 122 | let mut original_file = original_file.syntax().clone(); |
@@ -200,6 +200,7 @@ impl<'a> CompletionContext<'a> { | |||
200 | Some(ty) | 200 | Some(ty) |
201 | }) | 201 | }) |
202 | .flatten(); | 202 | .flatten(); |
203 | self.attribute_under_caret = find_node_at_offset(&file_with_fake_ident, offset); | ||
203 | 204 | ||
204 | // First, let's try to complete a reference to some declaration. | 205 | // First, let's try to complete a reference to some declaration. |
205 | if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(&file_with_fake_ident, offset) { | 206 | if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(&file_with_fake_ident, offset) { |
@@ -318,7 +319,6 @@ impl<'a> CompletionContext<'a> { | |||
318 | .and_then(|it| it.syntax().parent().and_then(ast::CallExpr::cast)) | 319 | .and_then(|it| it.syntax().parent().and_then(ast::CallExpr::cast)) |
319 | .is_some(); | 320 | .is_some(); |
320 | self.is_macro_call = path.syntax().parent().and_then(ast::MacroCall::cast).is_some(); | 321 | self.is_macro_call = path.syntax().parent().and_then(ast::MacroCall::cast).is_some(); |
321 | self.is_attribute = path.syntax().parent().and_then(ast::Attr::cast).is_some(); | ||
322 | 322 | ||
323 | self.is_path_type = path.syntax().parent().and_then(ast::PathType::cast).is_some(); | 323 | self.is_path_type = path.syntax().parent().and_then(ast::PathType::cast).is_some(); |
324 | self.has_type_args = segment.type_arg_list().is_some(); | 324 | self.has_type_args = segment.type_arg_list().is_some(); |
@@ -344,7 +344,7 @@ impl<'a> CompletionContext<'a> { | |||
344 | stmt.syntax().text_range() == name_ref.syntax().text_range(), | 344 | stmt.syntax().text_range() == name_ref.syntax().text_range(), |
345 | ); | 345 | ); |
346 | } | 346 | } |
347 | if let Some(block) = ast::Block::cast(node) { | 347 | if let Some(block) = ast::BlockExpr::cast(node) { |
348 | return Some( | 348 | return Some( |
349 | block.expr().map(|e| e.syntax().text_range()) | 349 | block.expr().map(|e| e.syntax().text_range()) |
350 | == Some(name_ref.syntax().text_range()), | 350 | == Some(name_ref.syntax().text_range()), |
diff --git a/crates/ra_ide/src/completion/completion_item.rs b/crates/ra_ide/src/completion/completion_item.rs index 5936fb8f7..cfb7c1e38 100644 --- a/crates/ra_ide/src/completion/completion_item.rs +++ b/crates/ra_ide/src/completion/completion_item.rs | |||
@@ -2,11 +2,12 @@ | |||
2 | 2 | ||
3 | use std::fmt; | 3 | use std::fmt; |
4 | 4 | ||
5 | use super::completion_config::SnippetCap; | ||
6 | use hir::Documentation; | 5 | use hir::Documentation; |
7 | use ra_syntax::TextRange; | 6 | use ra_syntax::TextRange; |
8 | use ra_text_edit::TextEdit; | 7 | use ra_text_edit::TextEdit; |
9 | 8 | ||
9 | use crate::completion::completion_config::SnippetCap; | ||
10 | |||
10 | /// `CompletionItem` describes a single completion variant in the editor pop-up. | 11 | /// `CompletionItem` describes a single completion variant in the editor pop-up. |
11 | /// It is basically a POD with various properties. To construct a | 12 | /// It is basically a POD with various properties. To construct a |
12 | /// `CompletionItem`, use `new` method and the `Builder` struct. | 13 | /// `CompletionItem`, use `new` method and the `Builder` struct. |
@@ -62,8 +63,8 @@ impl fmt::Debug for CompletionItem { | |||
62 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | 63 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
63 | let mut s = f.debug_struct("CompletionItem"); | 64 | let mut s = f.debug_struct("CompletionItem"); |
64 | s.field("label", &self.label()).field("source_range", &self.source_range()); | 65 | s.field("label", &self.label()).field("source_range", &self.source_range()); |
65 | if self.text_edit().as_atoms().len() == 1 { | 66 | if self.text_edit().len() == 1 { |
66 | let atom = &self.text_edit().as_atoms()[0]; | 67 | let atom = &self.text_edit().iter().next().unwrap(); |
67 | s.field("delete", &atom.delete); | 68 | s.field("delete", &atom.delete); |
68 | s.field("insert", &atom.insert); | 69 | s.field("insert", &atom.insert); |
69 | } else { | 70 | } else { |
diff --git a/crates/ra_ide/src/completion/presentation.rs b/crates/ra_ide/src/completion/presentation.rs index 2edb130cf..440ffa31d 100644 --- a/crates/ra_ide/src/completion/presentation.rs +++ b/crates/ra_ide/src/completion/presentation.rs | |||
@@ -3,7 +3,7 @@ | |||
3 | use hir::{Docs, HasAttrs, HasSource, HirDisplay, ModPath, ScopeDef, StructKind, Type}; | 3 | use hir::{Docs, HasAttrs, HasSource, HirDisplay, ModPath, ScopeDef, StructKind, Type}; |
4 | use ra_syntax::ast::NameOwner; | 4 | use ra_syntax::ast::NameOwner; |
5 | use stdx::SepBy; | 5 | use stdx::SepBy; |
6 | use test_utils::tested_by; | 6 | use test_utils::mark; |
7 | 7 | ||
8 | use crate::{ | 8 | use crate::{ |
9 | completion::{ | 9 | completion::{ |
@@ -17,12 +17,11 @@ use crate::{ | |||
17 | impl Completions { | 17 | impl Completions { |
18 | pub(crate) fn add_field(&mut self, ctx: &CompletionContext, field: hir::Field, ty: &Type) { | 18 | pub(crate) fn add_field(&mut self, ctx: &CompletionContext, field: hir::Field, ty: &Type) { |
19 | let is_deprecated = is_deprecated(field, ctx.db); | 19 | let is_deprecated = is_deprecated(field, ctx.db); |
20 | let ty = ty.display(ctx.db).to_string(); | ||
21 | let name = field.name(ctx.db); | 20 | let name = field.name(ctx.db); |
22 | let mut completion_item = | 21 | let mut completion_item = |
23 | CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.to_string()) | 22 | CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.to_string()) |
24 | .kind(CompletionItemKind::Field) | 23 | .kind(CompletionItemKind::Field) |
25 | .detail(ty.clone()) | 24 | .detail(ty.display(ctx.db).to_string()) |
26 | .set_documentation(field.docs(ctx.db)) | 25 | .set_documentation(field.docs(ctx.db)) |
27 | .set_deprecated(is_deprecated); | 26 | .set_deprecated(is_deprecated); |
28 | 27 | ||
@@ -107,6 +106,12 @@ impl Completions { | |||
107 | } | 106 | } |
108 | }; | 107 | }; |
109 | 108 | ||
109 | if let ScopeDef::Local(local) = resolution { | ||
110 | if let Some(score) = compute_score(ctx, &local.ty(ctx.db), &local_name) { | ||
111 | completion_item = completion_item.set_score(score); | ||
112 | } | ||
113 | } | ||
114 | |||
110 | // Add `<>` for generic types | 115 | // Add `<>` for generic types |
111 | if ctx.is_path_type && !ctx.has_type_args && ctx.config.add_call_parenthesis { | 116 | if ctx.is_path_type && !ctx.has_type_args && ctx.config.add_call_parenthesis { |
112 | if let Some(cap) = ctx.config.snippet_cap { | 117 | if let Some(cap) = ctx.config.snippet_cap { |
@@ -116,7 +121,7 @@ impl Completions { | |||
116 | _ => false, | 121 | _ => false, |
117 | }; | 122 | }; |
118 | if has_non_default_type_params { | 123 | if has_non_default_type_params { |
119 | tested_by!(inserts_angle_brackets_for_generics); | 124 | mark::hit!(inserts_angle_brackets_for_generics); |
120 | completion_item = completion_item | 125 | completion_item = completion_item |
121 | .lookup_by(local_name.clone()) | 126 | .lookup_by(local_name.clone()) |
122 | .label(format!("{}<…>", local_name)) | 127 | .label(format!("{}<…>", local_name)) |
@@ -171,7 +176,7 @@ impl Completions { | |||
171 | } | 176 | } |
172 | None if needs_bang => builder.insert_text(format!("{}!", name)), | 177 | None if needs_bang => builder.insert_text(format!("{}!", name)), |
173 | _ => { | 178 | _ => { |
174 | tested_by!(dont_insert_macro_call_parens_unncessary); | 179 | mark::hit!(dont_insert_macro_call_parens_unncessary); |
175 | builder.insert_text(name) | 180 | builder.insert_text(name) |
176 | } | 181 | } |
177 | }; | 182 | }; |
@@ -319,19 +324,20 @@ impl Completions { | |||
319 | 324 | ||
320 | pub(crate) fn compute_score( | 325 | pub(crate) fn compute_score( |
321 | ctx: &CompletionContext, | 326 | ctx: &CompletionContext, |
322 | // FIXME: this definitely should be a `Type` | 327 | ty: &Type, |
323 | ty: &str, | ||
324 | name: &str, | 328 | name: &str, |
325 | ) -> Option<CompletionScore> { | 329 | ) -> Option<CompletionScore> { |
330 | // FIXME: this should not fall back to string equality. | ||
331 | let ty = &ty.display(ctx.db).to_string(); | ||
326 | let (active_name, active_type) = if let Some(record_field) = &ctx.record_field_syntax { | 332 | let (active_name, active_type) = if let Some(record_field) = &ctx.record_field_syntax { |
327 | tested_by!(test_struct_field_completion_in_record_lit); | 333 | mark::hit!(test_struct_field_completion_in_record_lit); |
328 | let (struct_field, _local) = ctx.sema.resolve_record_field(record_field)?; | 334 | let (struct_field, _local) = ctx.sema.resolve_record_field(record_field)?; |
329 | ( | 335 | ( |
330 | struct_field.name(ctx.db).to_string(), | 336 | struct_field.name(ctx.db).to_string(), |
331 | struct_field.signature_ty(ctx.db).display(ctx.db).to_string(), | 337 | struct_field.signature_ty(ctx.db).display(ctx.db).to_string(), |
332 | ) | 338 | ) |
333 | } else if let Some(active_parameter) = &ctx.active_parameter { | 339 | } else if let Some(active_parameter) = &ctx.active_parameter { |
334 | tested_by!(test_struct_field_completion_in_func_call); | 340 | mark::hit!(test_struct_field_completion_in_func_call); |
335 | (active_parameter.name.clone(), active_parameter.ty.clone()) | 341 | (active_parameter.name.clone(), active_parameter.ty.clone()) |
336 | } else { | 342 | } else { |
337 | return None; | 343 | return None; |
@@ -392,7 +398,7 @@ impl Builder { | |||
392 | None => return self, | 398 | None => return self, |
393 | }; | 399 | }; |
394 | // If not an import, add parenthesis automatically. | 400 | // If not an import, add parenthesis automatically. |
395 | tested_by!(inserts_parens_for_function_calls); | 401 | mark::hit!(inserts_parens_for_function_calls); |
396 | 402 | ||
397 | let (snippet, label) = if params.is_empty() { | 403 | let (snippet, label) = if params.is_empty() { |
398 | (format!("{}()$0", name), format!("{}()", name)) | 404 | (format!("{}()$0", name), format!("{}()", name)) |
@@ -451,7 +457,7 @@ fn guess_macro_braces(macro_name: &str, docs: &str) -> (&'static str, &'static s | |||
451 | #[cfg(test)] | 457 | #[cfg(test)] |
452 | mod tests { | 458 | mod tests { |
453 | use insta::assert_debug_snapshot; | 459 | use insta::assert_debug_snapshot; |
454 | use test_utils::covers; | 460 | use test_utils::mark; |
455 | 461 | ||
456 | use crate::completion::{ | 462 | use crate::completion::{ |
457 | test_utils::{do_completion, do_completion_with_options}, | 463 | test_utils::{do_completion, do_completion_with_options}, |
@@ -601,7 +607,7 @@ mod tests { | |||
601 | 607 | ||
602 | #[test] | 608 | #[test] |
603 | fn inserts_parens_for_function_calls() { | 609 | fn inserts_parens_for_function_calls() { |
604 | covers!(inserts_parens_for_function_calls); | 610 | mark::check!(inserts_parens_for_function_calls); |
605 | assert_debug_snapshot!( | 611 | assert_debug_snapshot!( |
606 | do_reference_completion( | 612 | do_reference_completion( |
607 | r" | 613 | r" |
@@ -986,7 +992,7 @@ mod tests { | |||
986 | 992 | ||
987 | #[test] | 993 | #[test] |
988 | fn inserts_angle_brackets_for_generics() { | 994 | fn inserts_angle_brackets_for_generics() { |
989 | covers!(inserts_angle_brackets_for_generics); | 995 | mark::check!(inserts_angle_brackets_for_generics); |
990 | assert_debug_snapshot!( | 996 | assert_debug_snapshot!( |
991 | do_reference_completion( | 997 | do_reference_completion( |
992 | r" | 998 | r" |
@@ -1109,7 +1115,7 @@ mod tests { | |||
1109 | 1115 | ||
1110 | #[test] | 1116 | #[test] |
1111 | fn dont_insert_macro_call_parens_unncessary() { | 1117 | fn dont_insert_macro_call_parens_unncessary() { |
1112 | covers!(dont_insert_macro_call_parens_unncessary); | 1118 | mark::check!(dont_insert_macro_call_parens_unncessary); |
1113 | assert_debug_snapshot!( | 1119 | assert_debug_snapshot!( |
1114 | do_reference_completion( | 1120 | do_reference_completion( |
1115 | r" | 1121 | r" |
@@ -1175,7 +1181,7 @@ mod tests { | |||
1175 | 1181 | ||
1176 | #[test] | 1182 | #[test] |
1177 | fn test_struct_field_completion_in_func_call() { | 1183 | fn test_struct_field_completion_in_func_call() { |
1178 | covers!(test_struct_field_completion_in_func_call); | 1184 | mark::check!(test_struct_field_completion_in_func_call); |
1179 | assert_debug_snapshot!( | 1185 | assert_debug_snapshot!( |
1180 | do_reference_completion( | 1186 | do_reference_completion( |
1181 | r" | 1187 | r" |
@@ -1265,7 +1271,7 @@ mod tests { | |||
1265 | 1271 | ||
1266 | #[test] | 1272 | #[test] |
1267 | fn test_struct_field_completion_in_record_lit() { | 1273 | fn test_struct_field_completion_in_record_lit() { |
1268 | covers!(test_struct_field_completion_in_record_lit); | 1274 | mark::check!(test_struct_field_completion_in_record_lit); |
1269 | assert_debug_snapshot!( | 1275 | assert_debug_snapshot!( |
1270 | do_reference_completion( | 1276 | do_reference_completion( |
1271 | r" | 1277 | r" |
@@ -1405,4 +1411,48 @@ mod tests { | |||
1405 | "### | 1411 | "### |
1406 | ); | 1412 | ); |
1407 | } | 1413 | } |
1414 | |||
1415 | #[test] | ||
1416 | fn prioritize_exact_ref_match() { | ||
1417 | assert_debug_snapshot!( | ||
1418 | do_reference_completion( | ||
1419 | r" | ||
1420 | struct WorldSnapshot { _f: () }; | ||
1421 | fn go(world: &WorldSnapshot) { | ||
1422 | go(w<|>) | ||
1423 | } | ||
1424 | ", | ||
1425 | ), | ||
1426 | @r###" | ||
1427 | [ | ||
1428 | CompletionItem { | ||
1429 | label: "WorldSnapshot", | ||
1430 | source_range: 132..133, | ||
1431 | delete: 132..133, | ||
1432 | insert: "WorldSnapshot", | ||
1433 | kind: Struct, | ||
1434 | }, | ||
1435 | CompletionItem { | ||
1436 | label: "go(…)", | ||
1437 | source_range: 132..133, | ||
1438 | delete: 132..133, | ||
1439 | insert: "go(${1:world})$0", | ||
1440 | kind: Function, | ||
1441 | lookup: "go", | ||
1442 | detail: "fn go(world: &WorldSnapshot)", | ||
1443 | trigger_call_info: true, | ||
1444 | }, | ||
1445 | CompletionItem { | ||
1446 | label: "world", | ||
1447 | source_range: 132..133, | ||
1448 | delete: 132..133, | ||
1449 | insert: "world", | ||
1450 | kind: Binding, | ||
1451 | detail: "&WorldSnapshot", | ||
1452 | score: TypeAndNameMatch, | ||
1453 | }, | ||
1454 | ] | ||
1455 | "### | ||
1456 | ); | ||
1457 | } | ||
1408 | } | 1458 | } |
diff --git a/crates/ra_ide/src/completion/test_utils.rs b/crates/ra_ide/src/completion/test_utils.rs index eb90b5279..bf22452a2 100644 --- a/crates/ra_ide/src/completion/test_utils.rs +++ b/crates/ra_ide/src/completion/test_utils.rs | |||
@@ -20,7 +20,7 @@ pub(crate) fn do_completion_with_options( | |||
20 | } else { | 20 | } else { |
21 | single_file_with_position(code) | 21 | single_file_with_position(code) |
22 | }; | 22 | }; |
23 | let completions = analysis.completions(position, options).unwrap().unwrap(); | 23 | let completions = analysis.completions(options, position).unwrap().unwrap(); |
24 | let completion_items: Vec<CompletionItem> = completions.into(); | 24 | let completion_items: Vec<CompletionItem> = completions.into(); |
25 | let mut kind_completions: Vec<CompletionItem> = | 25 | let mut kind_completions: Vec<CompletionItem> = |
26 | completion_items.into_iter().filter(|c| c.completion_kind == kind).collect(); | 26 | completion_items.into_iter().filter(|c| c.completion_kind == kind).collect(); |
diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs index a6b4c2c28..c2819bbf7 100644 --- a/crates/ra_ide/src/diagnostics.rs +++ b/crates/ra_ide/src/diagnostics.rs | |||
@@ -64,7 +64,7 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic> | |||
64 | .unwrap_or_else(|| RelativePath::new("")) | 64 | .unwrap_or_else(|| RelativePath::new("")) |
65 | .join(&d.candidate); | 65 | .join(&d.candidate); |
66 | let create_file = FileSystemEdit::CreateFile { source_root, path }; | 66 | let create_file = FileSystemEdit::CreateFile { source_root, path }; |
67 | let fix = SourceChange::file_system_edit("create module", create_file); | 67 | let fix = SourceChange::file_system_edit("Create module", create_file); |
68 | res.borrow_mut().push(Diagnostic { | 68 | res.borrow_mut().push(Diagnostic { |
69 | range: sema.diagnostics_range(d).range, | 69 | range: sema.diagnostics_range(d).range, |
70 | message: d.message(), | 70 | message: d.message(), |
@@ -92,7 +92,7 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic> | |||
92 | algo::diff(&d.ast(db).syntax(), &field_list.syntax()).into_text_edit(&mut builder); | 92 | algo::diff(&d.ast(db).syntax(), &field_list.syntax()).into_text_edit(&mut builder); |
93 | 93 | ||
94 | Some(SourceChange::source_file_edit_from( | 94 | Some(SourceChange::source_file_edit_from( |
95 | "fill struct fields", | 95 | "Fill struct fields", |
96 | file_id, | 96 | file_id, |
97 | builder.finish(), | 97 | builder.finish(), |
98 | )) | 98 | )) |
@@ -117,7 +117,7 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic> | |||
117 | let node = d.ast(db); | 117 | let node = d.ast(db); |
118 | let replacement = format!("Ok({})", node.syntax()); | 118 | let replacement = format!("Ok({})", node.syntax()); |
119 | let edit = TextEdit::replace(node.syntax().text_range(), replacement); | 119 | let edit = TextEdit::replace(node.syntax().text_range(), replacement); |
120 | let fix = SourceChange::source_file_edit_from("wrap with ok", file_id, edit); | 120 | let fix = SourceChange::source_file_edit_from("Wrap with ok", file_id, edit); |
121 | res.borrow_mut().push(Diagnostic { | 121 | res.borrow_mut().push(Diagnostic { |
122 | range: sema.diagnostics_range(d).range, | 122 | range: sema.diagnostics_range(d).range, |
123 | message: d.message(), | 123 | message: d.message(), |
@@ -199,7 +199,7 @@ fn check_struct_shorthand_initialization( | |||
199 | message: "Shorthand struct initialization".to_string(), | 199 | message: "Shorthand struct initialization".to_string(), |
200 | severity: Severity::WeakWarning, | 200 | severity: Severity::WeakWarning, |
201 | fix: Some(SourceChange::source_file_edit( | 201 | fix: Some(SourceChange::source_file_edit( |
202 | "use struct shorthand initialization", | 202 | "Use struct shorthand initialization", |
203 | SourceFileEdit { file_id, edit }, | 203 | SourceFileEdit { file_id, edit }, |
204 | )), | 204 | )), |
205 | }); | 205 | }); |
@@ -241,7 +241,11 @@ mod tests { | |||
241 | diagnostics.pop().unwrap_or_else(|| panic!("no diagnostics for:\n{}\n", before)); | 241 | diagnostics.pop().unwrap_or_else(|| panic!("no diagnostics for:\n{}\n", before)); |
242 | let mut fix = diagnostic.fix.unwrap(); | 242 | let mut fix = diagnostic.fix.unwrap(); |
243 | let edit = fix.source_file_edits.pop().unwrap().edit; | 243 | let edit = fix.source_file_edits.pop().unwrap().edit; |
244 | let actual = edit.apply(&before); | 244 | let actual = { |
245 | let mut actual = before.to_string(); | ||
246 | edit.apply(&mut actual); | ||
247 | actual | ||
248 | }; | ||
245 | assert_eq_text!(after, &actual); | 249 | assert_eq_text!(after, &actual); |
246 | } | 250 | } |
247 | 251 | ||
@@ -256,7 +260,11 @@ mod tests { | |||
256 | let mut fix = diagnostic.fix.unwrap(); | 260 | let mut fix = diagnostic.fix.unwrap(); |
257 | let edit = fix.source_file_edits.pop().unwrap().edit; | 261 | let edit = fix.source_file_edits.pop().unwrap().edit; |
258 | let target_file_contents = analysis.file_text(file_position.file_id).unwrap(); | 262 | let target_file_contents = analysis.file_text(file_position.file_id).unwrap(); |
259 | let actual = edit.apply(&target_file_contents); | 263 | let actual = { |
264 | let mut actual = target_file_contents.to_string(); | ||
265 | edit.apply(&mut actual); | ||
266 | actual | ||
267 | }; | ||
260 | 268 | ||
261 | // Strip indent and empty lines from `after`, to match the behaviour of | 269 | // Strip indent and empty lines from `after`, to match the behaviour of |
262 | // `parse_fixture` called from `analysis_and_position`. | 270 | // `parse_fixture` called from `analysis_and_position`. |
@@ -288,7 +296,11 @@ mod tests { | |||
288 | let diagnostic = analysis.diagnostics(file_id).unwrap().pop().unwrap(); | 296 | let diagnostic = analysis.diagnostics(file_id).unwrap().pop().unwrap(); |
289 | let mut fix = diagnostic.fix.unwrap(); | 297 | let mut fix = diagnostic.fix.unwrap(); |
290 | let edit = fix.source_file_edits.pop().unwrap().edit; | 298 | let edit = fix.source_file_edits.pop().unwrap().edit; |
291 | let actual = edit.apply(&before); | 299 | let actual = { |
300 | let mut actual = before.to_string(); | ||
301 | edit.apply(&mut actual); | ||
302 | actual | ||
303 | }; | ||
292 | assert_eq_text!(after, &actual); | 304 | assert_eq_text!(after, &actual); |
293 | } | 305 | } |
294 | 306 | ||
@@ -606,7 +618,7 @@ mod tests { | |||
606 | range: 0..8, | 618 | range: 0..8, |
607 | fix: Some( | 619 | fix: Some( |
608 | SourceChange { | 620 | SourceChange { |
609 | label: "create module", | 621 | label: "Create module", |
610 | source_file_edits: [], | 622 | source_file_edits: [], |
611 | file_system_edits: [ | 623 | file_system_edits: [ |
612 | CreateFile { | 624 | CreateFile { |
@@ -616,7 +628,7 @@ mod tests { | |||
616 | path: "foo.rs", | 628 | path: "foo.rs", |
617 | }, | 629 | }, |
618 | ], | 630 | ], |
619 | cursor_position: None, | 631 | is_snippet: false, |
620 | }, | 632 | }, |
621 | ), | 633 | ), |
622 | severity: Error, | 634 | severity: Error, |
@@ -655,24 +667,24 @@ mod tests { | |||
655 | range: 224..233, | 667 | range: 224..233, |
656 | fix: Some( | 668 | fix: Some( |
657 | SourceChange { | 669 | SourceChange { |
658 | label: "fill struct fields", | 670 | label: "Fill struct fields", |
659 | source_file_edits: [ | 671 | source_file_edits: [ |
660 | SourceFileEdit { | 672 | SourceFileEdit { |
661 | file_id: FileId( | 673 | file_id: FileId( |
662 | 1, | 674 | 1, |
663 | ), | 675 | ), |
664 | edit: TextEdit { | 676 | edit: TextEdit { |
665 | atoms: [ | 677 | indels: [ |
666 | AtomTextEdit { | 678 | Indel { |
667 | delete: 3..9, | ||
668 | insert: "{a:42, b: ()}", | 679 | insert: "{a:42, b: ()}", |
680 | delete: 3..9, | ||
669 | }, | 681 | }, |
670 | ], | 682 | ], |
671 | }, | 683 | }, |
672 | }, | 684 | }, |
673 | ], | 685 | ], |
674 | file_system_edits: [], | 686 | file_system_edits: [], |
675 | cursor_position: None, | 687 | is_snippet: false, |
676 | }, | 688 | }, |
677 | ), | 689 | ), |
678 | severity: Error, | 690 | severity: Error, |
diff --git a/crates/ra_ide/src/display/function_signature.rs b/crates/ra_ide/src/display/function_signature.rs index db3907fe6..9572debd8 100644 --- a/crates/ra_ide/src/display/function_signature.rs +++ b/crates/ra_ide/src/display/function_signature.rs | |||
@@ -1,5 +1,7 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! FIXME: write short doc here |
2 | 2 | ||
3 | // FIXME: this modules relies on strings and AST way too much, and it should be | ||
4 | // rewritten (matklad 2020-05-07) | ||
3 | use std::{ | 5 | use std::{ |
4 | convert::From, | 6 | convert::From, |
5 | fmt::{self, Display}, | 7 | fmt::{self, Display}, |
@@ -82,8 +84,8 @@ impl FunctionSignature { | |||
82 | let ty = field.signature_ty(db); | 84 | let ty = field.signature_ty(db); |
83 | let raw_param = format!("{}", ty.display(db)); | 85 | let raw_param = format!("{}", ty.display(db)); |
84 | 86 | ||
85 | if let Some(param_type) = raw_param.split(':').nth(1) { | 87 | if let Some(param_type) = raw_param.split(':').nth(1).and_then(|it| it.get(1..)) { |
86 | parameter_types.push(param_type[1..].to_string()); | 88 | parameter_types.push(param_type.to_string()); |
87 | } else { | 89 | } else { |
88 | // useful when you have tuple struct | 90 | // useful when you have tuple struct |
89 | parameter_types.push(raw_param.clone()); | 91 | parameter_types.push(raw_param.clone()); |
@@ -127,8 +129,8 @@ impl FunctionSignature { | |||
127 | for field in variant.fields(db).into_iter() { | 129 | for field in variant.fields(db).into_iter() { |
128 | let ty = field.signature_ty(db); | 130 | let ty = field.signature_ty(db); |
129 | let raw_param = format!("{}", ty.display(db)); | 131 | let raw_param = format!("{}", ty.display(db)); |
130 | if let Some(param_type) = raw_param.split(':').nth(1) { | 132 | if let Some(param_type) = raw_param.split(':').nth(1).and_then(|it| it.get(1..)) { |
131 | parameter_types.push(param_type[1..].to_string()); | 133 | parameter_types.push(param_type.to_string()); |
132 | } else { | 134 | } else { |
133 | // The unwrap_or_else is useful when you have tuple | 135 | // The unwrap_or_else is useful when you have tuple |
134 | parameter_types.push(raw_param); | 136 | parameter_types.push(raw_param); |
@@ -195,14 +197,23 @@ impl From<&'_ ast::FnDef> for FunctionSignature { | |||
195 | let raw_param = self_param.syntax().text().to_string(); | 197 | let raw_param = self_param.syntax().text().to_string(); |
196 | 198 | ||
197 | res_types.push( | 199 | res_types.push( |
198 | raw_param.split(':').nth(1).unwrap_or_else(|| " Self")[1..].to_string(), | 200 | raw_param |
201 | .split(':') | ||
202 | .nth(1) | ||
203 | .and_then(|it| it.get(1..)) | ||
204 | .unwrap_or_else(|| "Self") | ||
205 | .to_string(), | ||
199 | ); | 206 | ); |
200 | res.push(raw_param); | 207 | res.push(raw_param); |
201 | } | 208 | } |
202 | 209 | ||
203 | res.extend(param_list.params().map(|param| param.syntax().text().to_string())); | 210 | res.extend(param_list.params().map(|param| param.syntax().text().to_string())); |
204 | res_types.extend(param_list.params().map(|param| { | 211 | res_types.extend(param_list.params().map(|param| { |
205 | param.syntax().text().to_string().split(':').nth(1).unwrap()[1..].to_string() | 212 | let param_text = param.syntax().text().to_string(); |
213 | match param_text.split(':').nth(1).and_then(|it| it.get(1..)) { | ||
214 | Some(it) => it.to_string(), | ||
215 | None => param_text, | ||
216 | } | ||
206 | })); | 217 | })); |
207 | } | 218 | } |
208 | (has_self_param, res, res_types) | 219 | (has_self_param, res, res_types) |
diff --git a/crates/ra_ide/src/display/navigation_target.rs b/crates/ra_ide/src/display/navigation_target.rs index 914a8b471..5da28edd2 100644 --- a/crates/ra_ide/src/display/navigation_target.rs +++ b/crates/ra_ide/src/display/navigation_target.rs | |||
@@ -11,7 +11,7 @@ use ra_syntax::{ | |||
11 | TextRange, | 11 | TextRange, |
12 | }; | 12 | }; |
13 | 13 | ||
14 | use crate::FileSymbol; | 14 | use crate::{FileRange, FileSymbol}; |
15 | 15 | ||
16 | use super::short_label::ShortLabel; | 16 | use super::short_label::ShortLabel; |
17 | 17 | ||
@@ -22,10 +22,11 @@ use super::short_label::ShortLabel; | |||
22 | /// code, like a function or a struct, but this is not strictly required. | 22 | /// code, like a function or a struct, but this is not strictly required. |
23 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] | 23 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] |
24 | pub struct NavigationTarget { | 24 | pub struct NavigationTarget { |
25 | // FIXME: use FileRange? | ||
25 | file_id: FileId, | 26 | file_id: FileId, |
27 | full_range: TextRange, | ||
26 | name: SmolStr, | 28 | name: SmolStr, |
27 | kind: SyntaxKind, | 29 | kind: SyntaxKind, |
28 | full_range: TextRange, | ||
29 | focus_range: Option<TextRange>, | 30 | focus_range: Option<TextRange>, |
30 | container_name: Option<SmolStr>, | 31 | container_name: Option<SmolStr>, |
31 | description: Option<String>, | 32 | description: Option<String>, |
@@ -63,6 +64,10 @@ impl NavigationTarget { | |||
63 | self.file_id | 64 | self.file_id |
64 | } | 65 | } |
65 | 66 | ||
67 | pub fn file_range(&self) -> FileRange { | ||
68 | FileRange { file_id: self.file_id, range: self.full_range } | ||
69 | } | ||
70 | |||
66 | pub fn full_range(&self) -> TextRange { | 71 | pub fn full_range(&self) -> TextRange { |
67 | self.full_range | 72 | self.full_range |
68 | } | 73 | } |
@@ -376,16 +381,20 @@ impl ToNav for hir::Local { | |||
376 | impl ToNav for hir::TypeParam { | 381 | impl ToNav for hir::TypeParam { |
377 | fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { | 382 | fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { |
378 | let src = self.source(db); | 383 | let src = self.source(db); |
379 | let range = match src.value { | 384 | let full_range = match &src.value { |
380 | Either::Left(it) => it.syntax().text_range(), | 385 | Either::Left(it) => it.syntax().text_range(), |
381 | Either::Right(it) => it.syntax().text_range(), | 386 | Either::Right(it) => it.syntax().text_range(), |
382 | }; | 387 | }; |
388 | let focus_range = match &src.value { | ||
389 | Either::Left(_) => None, | ||
390 | Either::Right(it) => it.name().map(|it| it.syntax().text_range()), | ||
391 | }; | ||
383 | NavigationTarget { | 392 | NavigationTarget { |
384 | file_id: src.file_id.original_file(db), | 393 | file_id: src.file_id.original_file(db), |
385 | name: self.name(db).to_string().into(), | 394 | name: self.name(db).to_string().into(), |
386 | kind: TYPE_PARAM, | 395 | kind: TYPE_PARAM, |
387 | full_range: range, | 396 | full_range, |
388 | focus_range: None, | 397 | focus_range, |
389 | container_name: None, | 398 | container_name: None, |
390 | description: None, | 399 | description: None, |
391 | docs: None, | 400 | docs: None, |
diff --git a/crates/ra_ide/src/folding_ranges.rs b/crates/ra_ide/src/folding_ranges.rs index 4379005aa..8657377de 100644 --- a/crates/ra_ide/src/folding_ranges.rs +++ b/crates/ra_ide/src/folding_ranges.rs | |||
@@ -88,7 +88,7 @@ fn fold_kind(kind: SyntaxKind) -> Option<FoldKind> { | |||
88 | | ITEM_LIST | 88 | | ITEM_LIST |
89 | | EXTERN_ITEM_LIST | 89 | | EXTERN_ITEM_LIST |
90 | | USE_TREE_LIST | 90 | | USE_TREE_LIST |
91 | | BLOCK | 91 | | BLOCK_EXPR |
92 | | MATCH_ARM_LIST | 92 | | MATCH_ARM_LIST |
93 | | ENUM_VARIANT_LIST | 93 | | ENUM_VARIANT_LIST |
94 | | TOKEN_TREE => Some(FoldKind::Block), | 94 | | TOKEN_TREE => Some(FoldKind::Block), |
diff --git a/crates/ra_ide/src/goto_definition.rs b/crates/ra_ide/src/goto_definition.rs index 1dfca819d..90e85d419 100644 --- a/crates/ra_ide/src/goto_definition.rs +++ b/crates/ra_ide/src/goto_definition.rs | |||
@@ -93,7 +93,7 @@ pub(crate) fn reference_definition( | |||
93 | 93 | ||
94 | #[cfg(test)] | 94 | #[cfg(test)] |
95 | mod tests { | 95 | mod tests { |
96 | use test_utils::{assert_eq_text, covers}; | 96 | use test_utils::assert_eq_text; |
97 | 97 | ||
98 | use crate::mock_analysis::analysis_and_position; | 98 | use crate::mock_analysis::analysis_and_position; |
99 | 99 | ||
@@ -208,7 +208,6 @@ mod tests { | |||
208 | 208 | ||
209 | #[test] | 209 | #[test] |
210 | fn goto_def_for_macros() { | 210 | fn goto_def_for_macros() { |
211 | covers!(ra_ide_db::goto_def_for_macros); | ||
212 | check_goto( | 211 | check_goto( |
213 | " | 212 | " |
214 | //- /lib.rs | 213 | //- /lib.rs |
@@ -225,7 +224,6 @@ mod tests { | |||
225 | 224 | ||
226 | #[test] | 225 | #[test] |
227 | fn goto_def_for_macros_from_other_crates() { | 226 | fn goto_def_for_macros_from_other_crates() { |
228 | covers!(ra_ide_db::goto_def_for_macros); | ||
229 | check_goto( | 227 | check_goto( |
230 | " | 228 | " |
231 | //- /lib.rs | 229 | //- /lib.rs |
@@ -244,6 +242,38 @@ mod tests { | |||
244 | } | 242 | } |
245 | 243 | ||
246 | #[test] | 244 | #[test] |
245 | fn goto_def_for_use_alias() { | ||
246 | check_goto( | ||
247 | " | ||
248 | //- /lib.rs | ||
249 | use foo as bar<|>; | ||
250 | |||
251 | |||
252 | //- /foo/lib.rs | ||
253 | #[macro_export] | ||
254 | macro_rules! foo { () => { () } }", | ||
255 | "SOURCE_FILE FileId(2) 0..50", | ||
256 | "#[macro_export]\nmacro_rules! foo { () => { () } }\n", | ||
257 | ); | ||
258 | } | ||
259 | |||
260 | #[test] | ||
261 | fn goto_def_for_use_alias_foo_macro() { | ||
262 | check_goto( | ||
263 | " | ||
264 | //- /lib.rs | ||
265 | use foo::foo as bar<|>; | ||
266 | |||
267 | //- /foo/lib.rs | ||
268 | #[macro_export] | ||
269 | macro_rules! foo { () => { () } } | ||
270 | ", | ||
271 | "foo MACRO_CALL FileId(2) 0..49 29..32", | ||
272 | "#[macro_export]\nmacro_rules! foo { () => { () } }|foo", | ||
273 | ); | ||
274 | } | ||
275 | |||
276 | #[test] | ||
247 | fn goto_def_for_macros_in_use_tree() { | 277 | fn goto_def_for_macros_in_use_tree() { |
248 | check_goto( | 278 | check_goto( |
249 | " | 279 | " |
@@ -337,7 +367,6 @@ mod tests { | |||
337 | 367 | ||
338 | #[test] | 368 | #[test] |
339 | fn goto_def_for_methods() { | 369 | fn goto_def_for_methods() { |
340 | covers!(ra_ide_db::goto_def_for_methods); | ||
341 | check_goto( | 370 | check_goto( |
342 | " | 371 | " |
343 | //- /lib.rs | 372 | //- /lib.rs |
@@ -357,7 +386,6 @@ mod tests { | |||
357 | 386 | ||
358 | #[test] | 387 | #[test] |
359 | fn goto_def_for_fields() { | 388 | fn goto_def_for_fields() { |
360 | covers!(ra_ide_db::goto_def_for_fields); | ||
361 | check_goto( | 389 | check_goto( |
362 | r" | 390 | r" |
363 | //- /lib.rs | 391 | //- /lib.rs |
@@ -376,7 +404,6 @@ mod tests { | |||
376 | 404 | ||
377 | #[test] | 405 | #[test] |
378 | fn goto_def_for_record_fields() { | 406 | fn goto_def_for_record_fields() { |
379 | covers!(ra_ide_db::goto_def_for_record_fields); | ||
380 | check_goto( | 407 | check_goto( |
381 | r" | 408 | r" |
382 | //- /lib.rs | 409 | //- /lib.rs |
@@ -397,7 +424,6 @@ mod tests { | |||
397 | 424 | ||
398 | #[test] | 425 | #[test] |
399 | fn goto_def_for_record_pat_fields() { | 426 | fn goto_def_for_record_pat_fields() { |
400 | covers!(ra_ide_db::goto_def_for_record_field_pats); | ||
401 | check_goto( | 427 | check_goto( |
402 | r" | 428 | r" |
403 | //- /lib.rs | 429 | //- /lib.rs |
@@ -754,14 +780,14 @@ mod tests { | |||
754 | #[test] | 780 | #[test] |
755 | fn goto_for_type_param() { | 781 | fn goto_for_type_param() { |
756 | check_goto( | 782 | check_goto( |
757 | " | 783 | r#" |
758 | //- /lib.rs | 784 | //- /lib.rs |
759 | struct Foo<T> { | 785 | struct Foo<T: Clone> { |
760 | t: <|>T, | 786 | t: <|>T, |
761 | } | 787 | } |
762 | ", | 788 | "#, |
763 | "T TYPE_PARAM FileId(1) 11..12", | 789 | "T TYPE_PARAM FileId(1) 11..19 11..12", |
764 | "T", | 790 | "T: Clone|T", |
765 | ); | 791 | ); |
766 | } | 792 | } |
767 | 793 | ||
@@ -840,7 +866,6 @@ mod tests { | |||
840 | 866 | ||
841 | #[test] | 867 | #[test] |
842 | fn goto_def_for_field_init_shorthand() { | 868 | fn goto_def_for_field_init_shorthand() { |
843 | covers!(ra_ide_db::goto_def_for_field_init_shorthand); | ||
844 | check_goto( | 869 | check_goto( |
845 | " | 870 | " |
846 | //- /lib.rs | 871 | //- /lib.rs |
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index 54d318858..befa977c7 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs | |||
@@ -143,7 +143,7 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<Strin | |||
143 | ModuleDef::TypeAlias(it) => from_def_source(db, it, mod_path), | 143 | ModuleDef::TypeAlias(it) => from_def_source(db, it, mod_path), |
144 | ModuleDef::BuiltinType(it) => Some(it.to_string()), | 144 | ModuleDef::BuiltinType(it) => Some(it.to_string()), |
145 | }, | 145 | }, |
146 | Definition::Local(it) => Some(rust_code_markup(&it.ty(db).display_truncated(db, None))), | 146 | Definition::Local(it) => Some(rust_code_markup(&it.ty(db).display(db))), |
147 | Definition::TypeParam(_) | Definition::SelfType(_) => { | 147 | Definition::TypeParam(_) | Definition::SelfType(_) => { |
148 | // FIXME: Hover for generic param | 148 | // FIXME: Hover for generic param |
149 | None | 149 | None |
@@ -208,7 +208,7 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn | |||
208 | } | 208 | } |
209 | }?; | 209 | }?; |
210 | 210 | ||
211 | res.extend(Some(rust_code_markup(&ty.display_truncated(db, None)))); | 211 | res.extend(Some(rust_code_markup(&ty.display(db)))); |
212 | let range = sema.original_range(&node).range; | 212 | let range = sema.original_range(&node).range; |
213 | Some(RangeInfo::new(range, res)) | 213 | Some(RangeInfo::new(range, res)) |
214 | } | 214 | } |
@@ -280,6 +280,47 @@ mod tests { | |||
280 | } | 280 | } |
281 | 281 | ||
282 | #[test] | 282 | #[test] |
283 | fn hover_shows_long_type_of_an_expression() { | ||
284 | check_hover_result( | ||
285 | r#" | ||
286 | //- /main.rs | ||
287 | struct Scan<A, B, C> { | ||
288 | a: A, | ||
289 | b: B, | ||
290 | c: C, | ||
291 | } | ||
292 | |||
293 | struct FakeIter<I> { | ||
294 | inner: I, | ||
295 | } | ||
296 | |||
297 | struct OtherStruct<T> { | ||
298 | i: T, | ||
299 | } | ||
300 | |||
301 | enum FakeOption<T> { | ||
302 | Some(T), | ||
303 | None, | ||
304 | } | ||
305 | |||
306 | fn scan<A, B, C>(a: A, b: B, c: C) -> FakeIter<Scan<OtherStruct<A>, B, C>> { | ||
307 | FakeIter { inner: Scan { a, b, c } } | ||
308 | } | ||
309 | |||
310 | fn main() { | ||
311 | let num: i32 = 55; | ||
312 | let closure = |memo: &mut u32, value: &u32, _another: &mut u32| -> FakeOption<u32> { | ||
313 | FakeOption::Some(*memo + value) | ||
314 | }; | ||
315 | let number = 5u32; | ||
316 | let mut iter<|> = scan(OtherStruct { i: num }, closure, number); | ||
317 | } | ||
318 | "#, | ||
319 | &["FakeIter<Scan<OtherStruct<OtherStruct<i32>>, |&mut u32, &u32, &mut u32| -> FakeOption<u32>, u32>>"], | ||
320 | ); | ||
321 | } | ||
322 | |||
323 | #[test] | ||
283 | fn hover_shows_fn_signature() { | 324 | fn hover_shows_fn_signature() { |
284 | // Single file with result | 325 | // Single file with result |
285 | check_hover_result( | 326 | check_hover_result( |
@@ -405,7 +446,7 @@ mod tests { | |||
405 | } | 446 | } |
406 | 447 | ||
407 | #[test] | 448 | #[test] |
408 | fn hover_omits_default_generic_types() { | 449 | fn hover_default_generic_types() { |
409 | check_hover_result( | 450 | check_hover_result( |
410 | r#" | 451 | r#" |
411 | //- /main.rs | 452 | //- /main.rs |
@@ -417,7 +458,7 @@ struct Test<K, T = u8> { | |||
417 | fn main() { | 458 | fn main() { |
418 | let zz<|> = Test { t: 23, k: 33 }; | 459 | let zz<|> = Test { t: 23, k: 33 }; |
419 | }"#, | 460 | }"#, |
420 | &["Test<i32>"], | 461 | &["Test<i32, u8>"], |
421 | ); | 462 | ); |
422 | } | 463 | } |
423 | 464 | ||
@@ -880,4 +921,21 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
880 | &["unsafe trait foo"], | 921 | &["unsafe trait foo"], |
881 | ); | 922 | ); |
882 | } | 923 | } |
924 | |||
925 | #[test] | ||
926 | fn test_hover_mod_with_same_name_as_function() { | ||
927 | check_hover_result( | ||
928 | " | ||
929 | //- /lib.rs | ||
930 | use self::m<|>y::Bar; | ||
931 | |||
932 | mod my { | ||
933 | pub struct Bar; | ||
934 | } | ||
935 | |||
936 | fn my() {} | ||
937 | ", | ||
938 | &["mod my"], | ||
939 | ); | ||
940 | } | ||
883 | } | 941 | } |
diff --git a/crates/ra_ide/src/inlay_hints.rs b/crates/ra_ide/src/inlay_hints.rs index 98483df32..b391f903a 100644 --- a/crates/ra_ide/src/inlay_hints.rs +++ b/crates/ra_ide/src/inlay_hints.rs | |||
@@ -9,6 +9,7 @@ use ra_syntax::{ | |||
9 | }; | 9 | }; |
10 | 10 | ||
11 | use crate::{FileId, FunctionSignature}; | 11 | use crate::{FileId, FunctionSignature}; |
12 | use stdx::to_lower_snake_case; | ||
12 | 13 | ||
13 | #[derive(Clone, Debug, PartialEq, Eq)] | 14 | #[derive(Clone, Debug, PartialEq, Eq)] |
14 | pub struct InlayHintsConfig { | 15 | pub struct InlayHintsConfig { |
@@ -144,7 +145,7 @@ fn get_param_name_hints( | |||
144 | .iter() | 145 | .iter() |
145 | .skip(n_params_to_skip) | 146 | .skip(n_params_to_skip) |
146 | .zip(args) | 147 | .zip(args) |
147 | .filter(|(param, arg)| should_show_param_hint(&fn_signature, param, &arg)) | 148 | .filter(|(param, arg)| should_show_param_name_hint(sema, &fn_signature, param, &arg)) |
148 | .map(|(param_name, arg)| InlayHint { | 149 | .map(|(param_name, arg)| InlayHint { |
149 | range: arg.syntax().text_range(), | 150 | range: arg.syntax().text_range(), |
150 | kind: InlayKind::ParameterHint, | 151 | kind: InlayKind::ParameterHint, |
@@ -181,7 +182,7 @@ fn get_bind_pat_hints( | |||
181 | 182 | ||
182 | fn pat_is_enum_variant(db: &RootDatabase, bind_pat: &ast::BindPat, pat_ty: &Type) -> bool { | 183 | fn pat_is_enum_variant(db: &RootDatabase, bind_pat: &ast::BindPat, pat_ty: &Type) -> bool { |
183 | if let Some(Adt::Enum(enum_data)) = pat_ty.as_adt() { | 184 | if let Some(Adt::Enum(enum_data)) = pat_ty.as_adt() { |
184 | let pat_text = bind_pat.syntax().to_string(); | 185 | let pat_text = bind_pat.to_string(); |
185 | enum_data | 186 | enum_data |
186 | .variants(db) | 187 | .variants(db) |
187 | .into_iter() | 188 | .into_iter() |
@@ -198,7 +199,7 @@ fn should_not_display_type_hint(db: &RootDatabase, bind_pat: &ast::BindPat, pat_ | |||
198 | } | 199 | } |
199 | 200 | ||
200 | if let Some(Adt::Struct(s)) = pat_ty.as_adt() { | 201 | if let Some(Adt::Struct(s)) = pat_ty.as_adt() { |
201 | if s.fields(db).is_empty() && s.name(db).to_string() == bind_pat.syntax().to_string() { | 202 | if s.fields(db).is_empty() && s.name(db).to_string() == bind_pat.to_string() { |
202 | return true; | 203 | return true; |
203 | } | 204 | } |
204 | } | 205 | } |
@@ -230,15 +231,16 @@ fn should_not_display_type_hint(db: &RootDatabase, bind_pat: &ast::BindPat, pat_ | |||
230 | false | 231 | false |
231 | } | 232 | } |
232 | 233 | ||
233 | fn should_show_param_hint( | 234 | fn should_show_param_name_hint( |
235 | sema: &Semantics<RootDatabase>, | ||
234 | fn_signature: &FunctionSignature, | 236 | fn_signature: &FunctionSignature, |
235 | param_name: &str, | 237 | param_name: &str, |
236 | argument: &ast::Expr, | 238 | argument: &ast::Expr, |
237 | ) -> bool { | 239 | ) -> bool { |
240 | let param_name = param_name.trim_start_matches('_'); | ||
238 | if param_name.is_empty() | 241 | if param_name.is_empty() |
239 | || is_argument_similar_to_param(argument, param_name) | 242 | || Some(param_name) == fn_signature.name.as_ref().map(|s| s.trim_start_matches('_')) |
240 | || Some(param_name.trim_start_matches('_')) | 243 | || is_argument_similar_to_param_name(sema, argument, param_name) |
241 | == fn_signature.name.as_ref().map(|s| s.trim_start_matches('_')) | ||
242 | { | 244 | { |
243 | return false; | 245 | return false; |
244 | } | 246 | } |
@@ -254,20 +256,42 @@ fn should_show_param_hint( | |||
254 | parameters_len != 1 || !is_obvious_param(param_name) | 256 | parameters_len != 1 || !is_obvious_param(param_name) |
255 | } | 257 | } |
256 | 258 | ||
257 | fn is_argument_similar_to_param(argument: &ast::Expr, param_name: &str) -> bool { | 259 | fn is_argument_similar_to_param_name( |
258 | let argument_string = remove_ref(argument.clone()).syntax().to_string(); | 260 | sema: &Semantics<RootDatabase>, |
259 | let param_name = param_name.trim_start_matches('_'); | 261 | argument: &ast::Expr, |
260 | let argument_string = argument_string.trim_start_matches('_'); | 262 | param_name: &str, |
261 | argument_string.starts_with(¶m_name) || argument_string.ends_with(¶m_name) | 263 | ) -> bool { |
264 | if is_enum_name_similar_to_param_name(sema, argument, param_name) { | ||
265 | return true; | ||
266 | } | ||
267 | match get_string_representation(argument) { | ||
268 | None => false, | ||
269 | Some(repr) => { | ||
270 | let argument_string = repr.trim_start_matches('_'); | ||
271 | argument_string.starts_with(param_name) || argument_string.ends_with(param_name) | ||
272 | } | ||
273 | } | ||
274 | } | ||
275 | |||
276 | fn is_enum_name_similar_to_param_name( | ||
277 | sema: &Semantics<RootDatabase>, | ||
278 | argument: &ast::Expr, | ||
279 | param_name: &str, | ||
280 | ) -> bool { | ||
281 | match sema.type_of_expr(argument).and_then(|t| t.as_adt()) { | ||
282 | Some(Adt::Enum(e)) => to_lower_snake_case(&e.name(sema.db).to_string()) == param_name, | ||
283 | _ => false, | ||
284 | } | ||
262 | } | 285 | } |
263 | 286 | ||
264 | fn remove_ref(expr: ast::Expr) -> ast::Expr { | 287 | fn get_string_representation(expr: &ast::Expr) -> Option<String> { |
265 | if let ast::Expr::RefExpr(ref_expr) = &expr { | 288 | match expr { |
266 | if let Some(inner) = ref_expr.expr() { | 289 | ast::Expr::MethodCallExpr(method_call_expr) => { |
267 | return inner; | 290 | Some(method_call_expr.name_ref()?.to_string()) |
268 | } | 291 | } |
292 | ast::Expr::RefExpr(ref_expr) => get_string_representation(&ref_expr.expr()?), | ||
293 | _ => Some(expr.to_string()), | ||
269 | } | 294 | } |
270 | expr | ||
271 | } | 295 | } |
272 | 296 | ||
273 | fn is_obvious_param(param_name: &str) -> bool { | 297 | fn is_obvious_param(param_name: &str) -> bool { |
@@ -1073,6 +1097,12 @@ struct TestVarContainer { | |||
1073 | test_var: i32, | 1097 | test_var: i32, |
1074 | } | 1098 | } |
1075 | 1099 | ||
1100 | impl TestVarContainer { | ||
1101 | fn test_var(&self) -> i32 { | ||
1102 | self.test_var | ||
1103 | } | ||
1104 | } | ||
1105 | |||
1076 | struct Test {} | 1106 | struct Test {} |
1077 | 1107 | ||
1078 | impl Test { | 1108 | impl Test { |
@@ -1098,10 +1128,15 @@ struct Param {} | |||
1098 | fn different_order(param: &Param) {} | 1128 | fn different_order(param: &Param) {} |
1099 | fn different_order_mut(param: &mut Param) {} | 1129 | fn different_order_mut(param: &mut Param) {} |
1100 | fn has_underscore(_param: bool) {} | 1130 | fn has_underscore(_param: bool) {} |
1131 | fn enum_matches_param_name(completion_kind: CompletionKind) {} | ||
1101 | 1132 | ||
1102 | fn twiddle(twiddle: bool) {} | 1133 | fn twiddle(twiddle: bool) {} |
1103 | fn doo(_doo: bool) {} | 1134 | fn doo(_doo: bool) {} |
1104 | 1135 | ||
1136 | enum CompletionKind { | ||
1137 | Keyword, | ||
1138 | } | ||
1139 | |||
1105 | fn main() { | 1140 | fn main() { |
1106 | let container: TestVarContainer = TestVarContainer { test_var: 42 }; | 1141 | let container: TestVarContainer = TestVarContainer { test_var: 42 }; |
1107 | let test: Test = Test {}; | 1142 | let test: Test = Test {}; |
@@ -1114,18 +1149,21 @@ fn main() { | |||
1114 | let test_var: i32 = 55; | 1149 | let test_var: i32 = 55; |
1115 | test_processed.no_hints_expected(22, test_var); | 1150 | test_processed.no_hints_expected(22, test_var); |
1116 | test_processed.no_hints_expected(33, container.test_var); | 1151 | test_processed.no_hints_expected(33, container.test_var); |
1152 | test_processed.no_hints_expected(44, container.test_var()); | ||
1117 | test_processed.frob(false); | 1153 | test_processed.frob(false); |
1118 | 1154 | ||
1119 | twiddle(true); | 1155 | twiddle(true); |
1120 | doo(true); | 1156 | doo(true); |
1121 | 1157 | ||
1122 | let param_begin: Param = Param {}; | 1158 | let mut param_begin: Param = Param {}; |
1123 | different_order(¶m_begin); | 1159 | different_order(¶m_begin); |
1124 | different_order(&mut param_begin); | 1160 | different_order(&mut param_begin); |
1125 | 1161 | ||
1126 | let param: bool = true; | 1162 | let param: bool = true; |
1127 | has_underscore(param); | 1163 | has_underscore(param); |
1128 | 1164 | ||
1165 | enum_matches_param_name(CompletionKind::Keyword); | ||
1166 | |||
1129 | let a: f64 = 7.0; | 1167 | let a: f64 = 7.0; |
1130 | let b: f64 = 4.0; | 1168 | let b: f64 = 4.0; |
1131 | let _: f64 = a.div_euclid(b); | 1169 | let _: f64 = a.div_euclid(b); |
diff --git a/crates/ra_ide/src/join_lines.rs b/crates/ra_ide/src/join_lines.rs index d0def7eaa..af1ade8a1 100644 --- a/crates/ra_ide/src/join_lines.rs +++ b/crates/ra_ide/src/join_lines.rs | |||
@@ -129,8 +129,7 @@ fn has_comma_after(node: &SyntaxNode) -> bool { | |||
129 | } | 129 | } |
130 | 130 | ||
131 | fn join_single_expr_block(edit: &mut TextEditBuilder, token: &SyntaxToken) -> Option<()> { | 131 | fn join_single_expr_block(edit: &mut TextEditBuilder, token: &SyntaxToken) -> Option<()> { |
132 | let block = ast::Block::cast(token.parent())?; | 132 | let block_expr = ast::BlockExpr::cast(token.parent())?; |
133 | let block_expr = ast::BlockExpr::cast(block.syntax().parent()?)?; | ||
134 | if !block_expr.is_standalone() { | 133 | if !block_expr.is_standalone() { |
135 | return None; | 134 | return None; |
136 | } | 135 | } |
@@ -167,16 +166,28 @@ fn is_trailing_comma(left: SyntaxKind, right: SyntaxKind) -> bool { | |||
167 | 166 | ||
168 | #[cfg(test)] | 167 | #[cfg(test)] |
169 | mod tests { | 168 | mod tests { |
170 | use crate::test_utils::{assert_eq_text, check_action, extract_range}; | 169 | use ra_syntax::SourceFile; |
170 | use test_utils::{add_cursor, assert_eq_text, extract_offset, extract_range}; | ||
171 | 171 | ||
172 | use super::*; | 172 | use super::*; |
173 | 173 | ||
174 | fn check_join_lines(before: &str, after: &str) { | 174 | fn check_join_lines(before: &str, after: &str) { |
175 | check_action(before, after, |file, offset| { | 175 | let (before_cursor_pos, before) = extract_offset(before); |
176 | let range = TextRange::empty(offset); | 176 | let file = SourceFile::parse(&before).ok().unwrap(); |
177 | let res = join_lines(file, range); | 177 | |
178 | Some(res) | 178 | let range = TextRange::empty(before_cursor_pos); |
179 | }) | 179 | let result = join_lines(&file, range); |
180 | |||
181 | let actual = { | ||
182 | let mut actual = before.to_string(); | ||
183 | result.apply(&mut actual); | ||
184 | actual | ||
185 | }; | ||
186 | let actual_cursor_pos = result | ||
187 | .apply_to_offset(before_cursor_pos) | ||
188 | .expect("cursor position is affected by the edit"); | ||
189 | let actual = add_cursor(&actual, actual_cursor_pos); | ||
190 | assert_eq_text!(after, &actual); | ||
180 | } | 191 | } |
181 | 192 | ||
182 | #[test] | 193 | #[test] |
@@ -570,7 +581,11 @@ fn foo() { | |||
570 | let (sel, before) = extract_range(before); | 581 | let (sel, before) = extract_range(before); |
571 | let parse = SourceFile::parse(&before); | 582 | let parse = SourceFile::parse(&before); |
572 | let result = join_lines(&parse.tree(), sel); | 583 | let result = join_lines(&parse.tree(), sel); |
573 | let actual = result.apply(&before); | 584 | let actual = { |
585 | let mut actual = before.to_string(); | ||
586 | result.apply(&mut actual); | ||
587 | actual | ||
588 | }; | ||
574 | assert_eq_text!(after, &actual); | 589 | assert_eq_text!(after, &actual); |
575 | } | 590 | } |
576 | 591 | ||
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs index 09f602fe1..97ff67ee8 100644 --- a/crates/ra_ide/src/lib.rs +++ b/crates/ra_ide/src/lib.rs | |||
@@ -16,7 +16,6 @@ macro_rules! eprintln { | |||
16 | } | 16 | } |
17 | 17 | ||
18 | pub mod mock_analysis; | 18 | pub mod mock_analysis; |
19 | mod source_change; | ||
20 | 19 | ||
21 | mod prime_caches; | 20 | mod prime_caches; |
22 | mod status; | 21 | mod status; |
@@ -32,7 +31,6 @@ mod syntax_highlighting; | |||
32 | mod parent_module; | 31 | mod parent_module; |
33 | mod references; | 32 | mod references; |
34 | mod impls; | 33 | mod impls; |
35 | mod assists; | ||
36 | mod diagnostics; | 34 | mod diagnostics; |
37 | mod syntax_tree; | 35 | mod syntax_tree; |
38 | mod folding_ranges; | 36 | mod folding_ranges; |
@@ -44,11 +42,6 @@ mod inlay_hints; | |||
44 | mod expand_macro; | 42 | mod expand_macro; |
45 | mod ssr; | 43 | mod ssr; |
46 | 44 | ||
47 | #[cfg(test)] | ||
48 | mod marks; | ||
49 | #[cfg(test)] | ||
50 | mod test_utils; | ||
51 | |||
52 | use std::sync::Arc; | 45 | use std::sync::Arc; |
53 | 46 | ||
54 | use ra_cfg::CfgOptions; | 47 | use ra_cfg::CfgOptions; |
@@ -65,7 +58,6 @@ use ra_syntax::{SourceFile, TextRange, TextSize}; | |||
65 | use crate::display::ToNav; | 58 | use crate::display::ToNav; |
66 | 59 | ||
67 | pub use crate::{ | 60 | pub use crate::{ |
68 | assists::{Assist, AssistId}, | ||
69 | call_hierarchy::CallItem, | 61 | call_hierarchy::CallItem, |
70 | completion::{ | 62 | completion::{ |
71 | CompletionConfig, CompletionItem, CompletionItemKind, CompletionScore, InsertTextFormat, | 63 | CompletionConfig, CompletionItem, CompletionItemKind, CompletionScore, InsertTextFormat, |
@@ -78,7 +70,6 @@ pub use crate::{ | |||
78 | inlay_hints::{InlayHint, InlayHintsConfig, InlayKind}, | 70 | inlay_hints::{InlayHint, InlayHintsConfig, InlayKind}, |
79 | references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult}, | 71 | references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult}, |
80 | runnables::{Runnable, RunnableKind, TestId}, | 72 | runnables::{Runnable, RunnableKind, TestId}, |
81 | source_change::{FileSystemEdit, SourceChange, SourceFileEdit}, | ||
82 | ssr::SsrError, | 73 | ssr::SsrError, |
83 | syntax_highlighting::{ | 74 | syntax_highlighting::{ |
84 | Highlight, HighlightModifier, HighlightModifiers, HighlightTag, HighlightedRange, | 75 | Highlight, HighlightModifier, HighlightModifiers, HighlightTag, HighlightedRange, |
@@ -86,17 +77,19 @@ pub use crate::{ | |||
86 | }; | 77 | }; |
87 | 78 | ||
88 | pub use hir::Documentation; | 79 | pub use hir::Documentation; |
80 | pub use ra_assists::{AssistConfig, AssistId}; | ||
89 | pub use ra_db::{ | 81 | pub use ra_db::{ |
90 | Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRootId, | 82 | Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRootId, |
91 | }; | 83 | }; |
92 | pub use ra_ide_db::{ | 84 | pub use ra_ide_db::{ |
93 | change::{AnalysisChange, LibraryData}, | 85 | change::{AnalysisChange, LibraryData}, |
94 | line_index::{LineCol, LineIndex}, | 86 | line_index::{LineCol, LineIndex}, |
95 | line_index_utils::translate_offset_with_edit, | ||
96 | search::SearchScope, | 87 | search::SearchScope, |
88 | source_change::{FileSystemEdit, SourceChange, SourceFileEdit}, | ||
97 | symbol_index::Query, | 89 | symbol_index::Query, |
98 | RootDatabase, | 90 | RootDatabase, |
99 | }; | 91 | }; |
92 | pub use ra_text_edit::{Indel, TextEdit}; | ||
100 | 93 | ||
101 | pub type Cancelable<T> = Result<T, Canceled>; | 94 | pub type Cancelable<T> = Result<T, Canceled>; |
102 | 95 | ||
@@ -135,10 +128,12 @@ pub struct AnalysisHost { | |||
135 | db: RootDatabase, | 128 | db: RootDatabase, |
136 | } | 129 | } |
137 | 130 | ||
138 | impl Default for AnalysisHost { | 131 | #[derive(Debug)] |
139 | fn default() -> AnalysisHost { | 132 | pub struct Assist { |
140 | AnalysisHost::new(None) | 133 | pub id: AssistId, |
141 | } | 134 | pub label: String, |
135 | pub group_label: Option<String>, | ||
136 | pub source_change: SourceChange, | ||
142 | } | 137 | } |
143 | 138 | ||
144 | impl AnalysisHost { | 139 | impl AnalysisHost { |
@@ -176,18 +171,20 @@ impl AnalysisHost { | |||
176 | pub fn request_cancellation(&mut self) { | 171 | pub fn request_cancellation(&mut self) { |
177 | self.db.request_cancellation(); | 172 | self.db.request_cancellation(); |
178 | } | 173 | } |
179 | pub fn raw_database( | 174 | pub fn raw_database(&self) -> &RootDatabase { |
180 | &self, | ||
181 | ) -> &(impl hir::db::HirDatabase + salsa::Database + ra_db::SourceDatabaseExt) { | ||
182 | &self.db | 175 | &self.db |
183 | } | 176 | } |
184 | pub fn raw_database_mut( | 177 | pub fn raw_database_mut(&mut self) -> &mut RootDatabase { |
185 | &mut self, | ||
186 | ) -> &mut (impl hir::db::HirDatabase + salsa::Database + ra_db::SourceDatabaseExt) { | ||
187 | &mut self.db | 178 | &mut self.db |
188 | } | 179 | } |
189 | } | 180 | } |
190 | 181 | ||
182 | impl Default for AnalysisHost { | ||
183 | fn default() -> AnalysisHost { | ||
184 | AnalysisHost::new(None) | ||
185 | } | ||
186 | } | ||
187 | |||
191 | /// Analysis is a snapshot of a world state at a moment in time. It is the main | 188 | /// Analysis is a snapshot of a world state at a moment in time. It is the main |
192 | /// entry point for asking semantic information about the world. When the world | 189 | /// entry point for asking semantic information about the world. When the world |
193 | /// state is advanced using `AnalysisHost::apply_change` method, all existing | 190 | /// state is advanced using `AnalysisHost::apply_change` method, all existing |
@@ -289,14 +286,10 @@ impl Analysis { | |||
289 | 286 | ||
290 | /// Returns an edit to remove all newlines in the range, cleaning up minor | 287 | /// Returns an edit to remove all newlines in the range, cleaning up minor |
291 | /// stuff like trailing commas. | 288 | /// stuff like trailing commas. |
292 | pub fn join_lines(&self, frange: FileRange) -> Cancelable<SourceChange> { | 289 | pub fn join_lines(&self, frange: FileRange) -> Cancelable<TextEdit> { |
293 | self.with_db(|db| { | 290 | self.with_db(|db| { |
294 | let parse = db.parse(frange.file_id); | 291 | let parse = db.parse(frange.file_id); |
295 | let file_edit = SourceFileEdit { | 292 | join_lines::join_lines(&parse.tree(), frange.range) |
296 | file_id: frange.file_id, | ||
297 | edit: join_lines::join_lines(&parse.tree(), frange.range), | ||
298 | }; | ||
299 | SourceChange::source_file_edit("join lines", file_edit) | ||
300 | }) | 293 | }) |
301 | } | 294 | } |
302 | 295 | ||
@@ -456,16 +449,26 @@ impl Analysis { | |||
456 | /// Computes completions at the given position. | 449 | /// Computes completions at the given position. |
457 | pub fn completions( | 450 | pub fn completions( |
458 | &self, | 451 | &self, |
459 | position: FilePosition, | ||
460 | config: &CompletionConfig, | 452 | config: &CompletionConfig, |
453 | position: FilePosition, | ||
461 | ) -> Cancelable<Option<Vec<CompletionItem>>> { | 454 | ) -> Cancelable<Option<Vec<CompletionItem>>> { |
462 | self.with_db(|db| completion::completions(db, position, config).map(Into::into)) | 455 | self.with_db(|db| completion::completions(db, config, position).map(Into::into)) |
463 | } | 456 | } |
464 | 457 | ||
465 | /// Computes assists (aka code actions aka intentions) for the given | 458 | /// Computes assists (aka code actions aka intentions) for the given |
466 | /// position. | 459 | /// position. |
467 | pub fn assists(&self, frange: FileRange) -> Cancelable<Vec<Assist>> { | 460 | pub fn assists(&self, config: &AssistConfig, frange: FileRange) -> Cancelable<Vec<Assist>> { |
468 | self.with_db(|db| assists::assists(db, frange)) | 461 | self.with_db(|db| { |
462 | ra_assists::Assist::resolved(db, config, frange) | ||
463 | .into_iter() | ||
464 | .map(|assist| Assist { | ||
465 | id: assist.assist.id, | ||
466 | label: assist.assist.label, | ||
467 | group_label: assist.assist.group.map(|it| it.0), | ||
468 | source_change: assist.source_change, | ||
469 | }) | ||
470 | .collect() | ||
471 | }) | ||
469 | } | 472 | } |
470 | 473 | ||
471 | /// Computes the set of diagnostics for the given file. | 474 | /// Computes the set of diagnostics for the given file. |
@@ -490,7 +493,7 @@ impl Analysis { | |||
490 | ) -> Cancelable<Result<SourceChange, SsrError>> { | 493 | ) -> Cancelable<Result<SourceChange, SsrError>> { |
491 | self.with_db(|db| { | 494 | self.with_db(|db| { |
492 | let edits = ssr::parse_search_replace(query, parse_only, db)?; | 495 | let edits = ssr::parse_search_replace(query, parse_only, db)?; |
493 | Ok(SourceChange::source_file_edits("ssr", edits)) | 496 | Ok(SourceChange::source_file_edits("Structural Search Replace", edits)) |
494 | }) | 497 | }) |
495 | } | 498 | } |
496 | 499 | ||
diff --git a/crates/ra_ide/src/marks.rs b/crates/ra_ide/src/marks.rs deleted file mode 100644 index bea30fe2a..000000000 --- a/crates/ra_ide/src/marks.rs +++ /dev/null | |||
@@ -1,14 +0,0 @@ | |||
1 | //! See test_utils/src/marks.rs | ||
2 | |||
3 | test_utils::marks!( | ||
4 | inserts_angle_brackets_for_generics | ||
5 | inserts_parens_for_function_calls | ||
6 | call_info_bad_offset | ||
7 | dont_complete_current_use | ||
8 | test_resolve_parent_module_on_module_decl | ||
9 | search_filters_by_range | ||
10 | dont_insert_macro_call_parens_unncessary | ||
11 | self_fulfilling_completion | ||
12 | test_struct_field_completion_in_func_call | ||
13 | test_struct_field_completion_in_record_lit | ||
14 | ); | ||
diff --git a/crates/ra_ide/src/parent_module.rs b/crates/ra_ide/src/parent_module.rs index aaf4460df..a083fb1eb 100644 --- a/crates/ra_ide/src/parent_module.rs +++ b/crates/ra_ide/src/parent_module.rs | |||
@@ -7,7 +7,7 @@ use ra_syntax::{ | |||
7 | algo::find_node_at_offset, | 7 | algo::find_node_at_offset, |
8 | ast::{self, AstNode}, | 8 | ast::{self, AstNode}, |
9 | }; | 9 | }; |
10 | use test_utils::tested_by; | 10 | use test_utils::mark; |
11 | 11 | ||
12 | use crate::NavigationTarget; | 12 | use crate::NavigationTarget; |
13 | 13 | ||
@@ -25,7 +25,7 @@ pub(crate) fn parent_module(db: &RootDatabase, position: FilePosition) -> Vec<Na | |||
25 | .item_list() | 25 | .item_list() |
26 | .map_or(false, |it| it.syntax().text_range().contains_inclusive(position.offset)) | 26 | .map_or(false, |it| it.syntax().text_range().contains_inclusive(position.offset)) |
27 | { | 27 | { |
28 | tested_by!(test_resolve_parent_module_on_module_decl); | 28 | mark::hit!(test_resolve_parent_module_on_module_decl); |
29 | module = m.syntax().ancestors().skip(1).find_map(ast::Module::cast); | 29 | module = m.syntax().ancestors().skip(1).find_map(ast::Module::cast); |
30 | } | 30 | } |
31 | } | 31 | } |
@@ -57,7 +57,7 @@ pub(crate) fn crate_for(db: &RootDatabase, file_id: FileId) -> Vec<CrateId> { | |||
57 | mod tests { | 57 | mod tests { |
58 | use ra_cfg::CfgOptions; | 58 | use ra_cfg::CfgOptions; |
59 | use ra_db::Env; | 59 | use ra_db::Env; |
60 | use test_utils::covers; | 60 | use test_utils::mark; |
61 | 61 | ||
62 | use crate::{ | 62 | use crate::{ |
63 | mock_analysis::{analysis_and_position, MockAnalysis}, | 63 | mock_analysis::{analysis_and_position, MockAnalysis}, |
@@ -81,7 +81,7 @@ mod tests { | |||
81 | 81 | ||
82 | #[test] | 82 | #[test] |
83 | fn test_resolve_parent_module_on_module_decl() { | 83 | fn test_resolve_parent_module_on_module_decl() { |
84 | covers!(test_resolve_parent_module_on_module_decl); | 84 | mark::check!(test_resolve_parent_module_on_module_decl); |
85 | let (analysis, pos) = analysis_and_position( | 85 | let (analysis, pos) = analysis_and_position( |
86 | " | 86 | " |
87 | //- /lib.rs | 87 | //- /lib.rs |
diff --git a/crates/ra_ide/src/references.rs b/crates/ra_ide/src/references.rs index 555ccf295..96444bf6a 100644 --- a/crates/ra_ide/src/references.rs +++ b/crates/ra_ide/src/references.rs | |||
@@ -190,8 +190,6 @@ fn get_struct_def_name_for_struct_literal_search( | |||
190 | 190 | ||
191 | #[cfg(test)] | 191 | #[cfg(test)] |
192 | mod tests { | 192 | mod tests { |
193 | use test_utils::covers; | ||
194 | |||
195 | use crate::{ | 193 | use crate::{ |
196 | mock_analysis::{analysis_and_position, single_file_with_position, MockAnalysis}, | 194 | mock_analysis::{analysis_and_position, single_file_with_position, MockAnalysis}, |
197 | Declaration, Reference, ReferenceSearchResult, SearchScope, | 195 | Declaration, Reference, ReferenceSearchResult, SearchScope, |
@@ -301,7 +299,6 @@ mod tests { | |||
301 | 299 | ||
302 | #[test] | 300 | #[test] |
303 | fn search_filters_by_range() { | 301 | fn search_filters_by_range() { |
304 | covers!(ra_ide_db::search_filters_by_range); | ||
305 | let code = r#" | 302 | let code = r#" |
306 | fn foo() { | 303 | fn foo() { |
307 | let spam<|> = 92; | 304 | let spam<|> = 92; |
@@ -593,6 +590,31 @@ mod tests { | |||
593 | check_result(refs, "i BIND_PAT FileId(1) 36..37 Other", &["FileId(1) 51..52 Other Write"]); | 590 | check_result(refs, "i BIND_PAT FileId(1) 36..37 Other", &["FileId(1) 51..52 Other Write"]); |
594 | } | 591 | } |
595 | 592 | ||
593 | #[test] | ||
594 | fn test_find_struct_function_refs_outside_module() { | ||
595 | let code = r#" | ||
596 | mod foo { | ||
597 | pub struct Foo; | ||
598 | |||
599 | impl Foo { | ||
600 | pub fn new<|>() -> Foo { | ||
601 | Foo | ||
602 | } | ||
603 | } | ||
604 | } | ||
605 | |||
606 | fn main() { | ||
607 | let _f = foo::Foo::new(); | ||
608 | }"#; | ||
609 | |||
610 | let refs = get_all_refs(code); | ||
611 | check_result( | ||
612 | refs, | ||
613 | "new FN_DEF FileId(1) 87..150 94..97 Other", | ||
614 | &["FileId(1) 227..230 StructLiteral"], | ||
615 | ); | ||
616 | } | ||
617 | |||
596 | fn get_all_refs(text: &str) -> ReferenceSearchResult { | 618 | fn get_all_refs(text: &str) -> ReferenceSearchResult { |
597 | let (analysis, position) = single_file_with_position(text); | 619 | let (analysis, position) = single_file_with_position(text); |
598 | analysis.find_all_refs(position, None).unwrap().unwrap() | 620 | analysis.find_all_refs(position, None).unwrap().unwrap() |
diff --git a/crates/ra_ide/src/references/rename.rs b/crates/ra_ide/src/references/rename.rs index fd17bc9f2..fd2163dad 100644 --- a/crates/ra_ide/src/references/rename.rs +++ b/crates/ra_ide/src/references/rename.rs | |||
@@ -4,17 +4,18 @@ use hir::{ModuleSource, Semantics}; | |||
4 | use ra_db::{RelativePath, RelativePathBuf, SourceDatabaseExt}; | 4 | use ra_db::{RelativePath, 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, lex_single_valid_syntax_kind, AstNode, SyntaxKind, SyntaxNode, | 7 | algo::find_node_at_offset, ast, ast::TypeAscriptionOwner, lex_single_valid_syntax_kind, |
8 | AstNode, SyntaxKind, SyntaxNode, SyntaxToken, | ||
8 | }; | 9 | }; |
9 | use ra_text_edit::TextEdit; | 10 | use ra_text_edit::TextEdit; |
11 | use std::convert::TryInto; | ||
12 | use test_utils::mark; | ||
10 | 13 | ||
11 | use crate::{ | 14 | use crate::{ |
12 | FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind, SourceChange, | 15 | references::find_all_refs, FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind, |
13 | SourceFileEdit, TextRange, | 16 | SourceChange, SourceFileEdit, TextRange, TextSize, |
14 | }; | 17 | }; |
15 | 18 | ||
16 | use super::find_all_refs; | ||
17 | |||
18 | pub(crate) fn rename( | 19 | pub(crate) fn rename( |
19 | db: &RootDatabase, | 20 | db: &RootDatabase, |
20 | position: FilePosition, | 21 | position: FilePosition, |
@@ -22,17 +23,21 @@ pub(crate) fn rename( | |||
22 | ) -> Option<RangeInfo<SourceChange>> { | 23 | ) -> Option<RangeInfo<SourceChange>> { |
23 | match lex_single_valid_syntax_kind(new_name)? { | 24 | match lex_single_valid_syntax_kind(new_name)? { |
24 | SyntaxKind::IDENT | SyntaxKind::UNDERSCORE => (), | 25 | SyntaxKind::IDENT | SyntaxKind::UNDERSCORE => (), |
26 | SyntaxKind::SELF_KW => return rename_to_self(db, position), | ||
25 | _ => return None, | 27 | _ => return None, |
26 | } | 28 | } |
27 | 29 | ||
28 | let sema = Semantics::new(db); | 30 | let sema = Semantics::new(db); |
29 | let source_file = sema.parse(position.file_id); | 31 | let source_file = sema.parse(position.file_id); |
30 | if let Some((ast_name, ast_module)) = | 32 | let syntax = source_file.syntax(); |
31 | find_name_and_module_at_offset(source_file.syntax(), position) | 33 | if let Some((ast_name, ast_module)) = find_name_and_module_at_offset(syntax, position) { |
32 | { | ||
33 | let range = ast_name.syntax().text_range(); | 34 | let range = ast_name.syntax().text_range(); |
34 | rename_mod(&sema, &ast_name, &ast_module, position, new_name) | 35 | rename_mod(&sema, &ast_name, &ast_module, position, new_name) |
35 | .map(|info| RangeInfo::new(range, info)) | 36 | .map(|info| RangeInfo::new(range, info)) |
37 | } else if let Some(self_token) = | ||
38 | syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW) | ||
39 | { | ||
40 | rename_self_to_param(db, position, self_token, new_name) | ||
36 | } else { | 41 | } else { |
37 | rename_reference(sema.db, position, new_name) | 42 | rename_reference(sema.db, position, new_name) |
38 | } | 43 | } |
@@ -52,11 +57,13 @@ fn source_edit_from_reference(reference: Reference, new_name: &str) -> SourceFil | |||
52 | let file_id = reference.file_range.file_id; | 57 | let file_id = reference.file_range.file_id; |
53 | let range = match reference.kind { | 58 | let range = match reference.kind { |
54 | ReferenceKind::FieldShorthandForField => { | 59 | ReferenceKind::FieldShorthandForField => { |
60 | mark::hit!(test_rename_struct_field_for_shorthand); | ||
55 | replacement_text.push_str(new_name); | 61 | replacement_text.push_str(new_name); |
56 | replacement_text.push_str(": "); | 62 | replacement_text.push_str(": "); |
57 | TextRange::new(reference.file_range.range.start(), reference.file_range.range.start()) | 63 | TextRange::new(reference.file_range.range.start(), reference.file_range.range.start()) |
58 | } | 64 | } |
59 | ReferenceKind::FieldShorthandForLocal => { | 65 | ReferenceKind::FieldShorthandForLocal => { |
66 | mark::hit!(test_rename_local_for_field_shorthand); | ||
60 | replacement_text.push_str(": "); | 67 | replacement_text.push_str(": "); |
61 | replacement_text.push_str(new_name); | 68 | replacement_text.push_str(new_name); |
62 | TextRange::new(reference.file_range.range.end(), reference.file_range.range.end()) | 69 | TextRange::new(reference.file_range.range.end(), reference.file_range.range.end()) |
@@ -121,7 +128,113 @@ fn rename_mod( | |||
121 | source_file_edits.extend(ref_edits); | 128 | source_file_edits.extend(ref_edits); |
122 | } | 129 | } |
123 | 130 | ||
124 | Some(SourceChange::from_edits("rename", source_file_edits, file_system_edits)) | 131 | Some(SourceChange::from_edits("Rename", source_file_edits, file_system_edits)) |
132 | } | ||
133 | |||
134 | fn rename_to_self(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<SourceChange>> { | ||
135 | let sema = Semantics::new(db); | ||
136 | let source_file = sema.parse(position.file_id); | ||
137 | let syn = source_file.syntax(); | ||
138 | |||
139 | let fn_def = find_node_at_offset::<ast::FnDef>(syn, position.offset)?; | ||
140 | let params = fn_def.param_list()?; | ||
141 | if params.self_param().is_some() { | ||
142 | return None; // method already has self param | ||
143 | } | ||
144 | let first_param = params.params().next()?; | ||
145 | let mutable = match first_param.ascribed_type() { | ||
146 | Some(ast::TypeRef::ReferenceType(rt)) => rt.mut_token().is_some(), | ||
147 | _ => return None, // not renaming other types | ||
148 | }; | ||
149 | |||
150 | let RangeInfo { range, info: refs } = find_all_refs(db, position, None)?; | ||
151 | |||
152 | let param_range = first_param.syntax().text_range(); | ||
153 | let (param_ref, usages): (Vec<Reference>, Vec<Reference>) = refs | ||
154 | .into_iter() | ||
155 | .partition(|reference| param_range.intersect(reference.file_range.range).is_some()); | ||
156 | |||
157 | if param_ref.is_empty() { | ||
158 | return None; | ||
159 | } | ||
160 | |||
161 | let mut edits = usages | ||
162 | .into_iter() | ||
163 | .map(|reference| source_edit_from_reference(reference, "self")) | ||
164 | .collect::<Vec<_>>(); | ||
165 | |||
166 | edits.push(SourceFileEdit { | ||
167 | file_id: position.file_id, | ||
168 | edit: TextEdit::replace( | ||
169 | param_range, | ||
170 | String::from(if mutable { "&mut self" } else { "&self" }), | ||
171 | ), | ||
172 | }); | ||
173 | |||
174 | Some(RangeInfo::new(range, SourceChange::source_file_edits("Rename", edits))) | ||
175 | } | ||
176 | |||
177 | fn text_edit_from_self_param( | ||
178 | syn: &SyntaxNode, | ||
179 | self_param: &ast::SelfParam, | ||
180 | new_name: &str, | ||
181 | ) -> Option<TextEdit> { | ||
182 | fn target_type_name(impl_def: &ast::ImplDef) -> Option<String> { | ||
183 | if let Some(ast::TypeRef::PathType(p)) = impl_def.target_type() { | ||
184 | return Some(p.path()?.segment()?.name_ref()?.text().to_string()); | ||
185 | } | ||
186 | None | ||
187 | } | ||
188 | |||
189 | let impl_def = | ||
190 | find_node_at_offset::<ast::ImplDef>(syn, self_param.syntax().text_range().start())?; | ||
191 | let type_name = target_type_name(&impl_def)?; | ||
192 | |||
193 | let mut replacement_text = String::from(new_name); | ||
194 | replacement_text.push_str(": "); | ||
195 | replacement_text.push_str(self_param.mut_token().map_or("&", |_| "&mut ")); | ||
196 | replacement_text.push_str(type_name.as_str()); | ||
197 | |||
198 | Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text)) | ||
199 | } | ||
200 | |||
201 | fn rename_self_to_param( | ||
202 | db: &RootDatabase, | ||
203 | position: FilePosition, | ||
204 | self_token: SyntaxToken, | ||
205 | new_name: &str, | ||
206 | ) -> Option<RangeInfo<SourceChange>> { | ||
207 | let sema = Semantics::new(db); | ||
208 | let source_file = sema.parse(position.file_id); | ||
209 | let syn = source_file.syntax(); | ||
210 | |||
211 | let text = db.file_text(position.file_id); | ||
212 | let fn_def = find_node_at_offset::<ast::FnDef>(syn, position.offset)?; | ||
213 | let search_range = fn_def.syntax().text_range(); | ||
214 | |||
215 | let mut edits: Vec<SourceFileEdit> = vec![]; | ||
216 | |||
217 | for (idx, _) in text.match_indices("self") { | ||
218 | let offset: TextSize = idx.try_into().unwrap(); | ||
219 | if !search_range.contains_inclusive(offset) { | ||
220 | continue; | ||
221 | } | ||
222 | if let Some(ref usage) = | ||
223 | syn.token_at_offset(offset).find(|t| t.kind() == SyntaxKind::SELF_KW) | ||
224 | { | ||
225 | let edit = if let Some(ref self_param) = ast::SelfParam::cast(usage.parent()) { | ||
226 | text_edit_from_self_param(syn, self_param, new_name)? | ||
227 | } else { | ||
228 | TextEdit::replace(usage.text_range(), String::from(new_name)) | ||
229 | }; | ||
230 | edits.push(SourceFileEdit { file_id: position.file_id, edit }); | ||
231 | } | ||
232 | } | ||
233 | |||
234 | let range = ast::SelfParam::cast(self_token.parent()) | ||
235 | .map_or(self_token.text_range(), |p| p.syntax().text_range()); | ||
236 | |||
237 | Some(RangeInfo::new(range, SourceChange::source_file_edits("Rename", edits))) | ||
125 | } | 238 | } |
126 | 239 | ||
127 | fn rename_reference( | 240 | fn rename_reference( |
@@ -140,14 +253,14 @@ fn rename_reference( | |||
140 | return None; | 253 | return None; |
141 | } | 254 | } |
142 | 255 | ||
143 | Some(RangeInfo::new(range, SourceChange::source_file_edits("rename", edit))) | 256 | Some(RangeInfo::new(range, SourceChange::source_file_edits("Rename", edit))) |
144 | } | 257 | } |
145 | 258 | ||
146 | #[cfg(test)] | 259 | #[cfg(test)] |
147 | mod tests { | 260 | mod tests { |
148 | use insta::assert_debug_snapshot; | 261 | use insta::assert_debug_snapshot; |
149 | use ra_text_edit::TextEditBuilder; | 262 | use ra_text_edit::TextEditBuilder; |
150 | use test_utils::assert_eq_text; | 263 | use test_utils::{assert_eq_text, mark}; |
151 | 264 | ||
152 | use crate::{ | 265 | use crate::{ |
153 | mock_analysis::analysis_and_position, mock_analysis::single_file_with_position, FileId, | 266 | mock_analysis::analysis_and_position, mock_analysis::single_file_with_position, FileId, |
@@ -379,6 +492,7 @@ mod tests { | |||
379 | 492 | ||
380 | #[test] | 493 | #[test] |
381 | fn test_rename_struct_field_for_shorthand() { | 494 | fn test_rename_struct_field_for_shorthand() { |
495 | mark::check!(test_rename_struct_field_for_shorthand); | ||
382 | test_rename( | 496 | test_rename( |
383 | r#" | 497 | r#" |
384 | struct Foo { | 498 | struct Foo { |
@@ -408,6 +522,7 @@ mod tests { | |||
408 | 522 | ||
409 | #[test] | 523 | #[test] |
410 | fn test_rename_local_for_field_shorthand() { | 524 | fn test_rename_local_for_field_shorthand() { |
525 | mark::check!(test_rename_local_for_field_shorthand); | ||
411 | test_rename( | 526 | test_rename( |
412 | r#" | 527 | r#" |
413 | struct Foo { | 528 | struct Foo { |
@@ -527,17 +642,17 @@ mod tests { | |||
527 | RangeInfo { | 642 | RangeInfo { |
528 | range: 4..7, | 643 | range: 4..7, |
529 | info: SourceChange { | 644 | info: SourceChange { |
530 | label: "rename", | 645 | label: "Rename", |
531 | source_file_edits: [ | 646 | source_file_edits: [ |
532 | SourceFileEdit { | 647 | SourceFileEdit { |
533 | file_id: FileId( | 648 | file_id: FileId( |
534 | 2, | 649 | 2, |
535 | ), | 650 | ), |
536 | edit: TextEdit { | 651 | edit: TextEdit { |
537 | atoms: [ | 652 | indels: [ |
538 | AtomTextEdit { | 653 | Indel { |
539 | delete: 4..7, | ||
540 | insert: "foo2", | 654 | insert: "foo2", |
655 | delete: 4..7, | ||
541 | }, | 656 | }, |
542 | ], | 657 | ], |
543 | }, | 658 | }, |
@@ -554,7 +669,7 @@ mod tests { | |||
554 | dst_path: "bar/foo2.rs", | 669 | dst_path: "bar/foo2.rs", |
555 | }, | 670 | }, |
556 | ], | 671 | ], |
557 | cursor_position: None, | 672 | is_snippet: false, |
558 | }, | 673 | }, |
559 | }, | 674 | }, |
560 | ) | 675 | ) |
@@ -579,17 +694,17 @@ mod tests { | |||
579 | RangeInfo { | 694 | RangeInfo { |
580 | range: 4..7, | 695 | range: 4..7, |
581 | info: SourceChange { | 696 | info: SourceChange { |
582 | label: "rename", | 697 | label: "Rename", |
583 | source_file_edits: [ | 698 | source_file_edits: [ |
584 | SourceFileEdit { | 699 | SourceFileEdit { |
585 | file_id: FileId( | 700 | file_id: FileId( |
586 | 1, | 701 | 1, |
587 | ), | 702 | ), |
588 | edit: TextEdit { | 703 | edit: TextEdit { |
589 | atoms: [ | 704 | indels: [ |
590 | AtomTextEdit { | 705 | Indel { |
591 | delete: 4..7, | ||
592 | insert: "foo2", | 706 | insert: "foo2", |
707 | delete: 4..7, | ||
593 | }, | 708 | }, |
594 | ], | 709 | ], |
595 | }, | 710 | }, |
@@ -606,7 +721,7 @@ mod tests { | |||
606 | dst_path: "foo2/mod.rs", | 721 | dst_path: "foo2/mod.rs", |
607 | }, | 722 | }, |
608 | ], | 723 | ], |
609 | cursor_position: None, | 724 | is_snippet: false, |
610 | }, | 725 | }, |
611 | }, | 726 | }, |
612 | ) | 727 | ) |
@@ -662,17 +777,17 @@ mod tests { | |||
662 | RangeInfo { | 777 | RangeInfo { |
663 | range: 8..11, | 778 | range: 8..11, |
664 | info: SourceChange { | 779 | info: SourceChange { |
665 | label: "rename", | 780 | label: "Rename", |
666 | source_file_edits: [ | 781 | source_file_edits: [ |
667 | SourceFileEdit { | 782 | SourceFileEdit { |
668 | file_id: FileId( | 783 | file_id: FileId( |
669 | 2, | 784 | 2, |
670 | ), | 785 | ), |
671 | edit: TextEdit { | 786 | edit: TextEdit { |
672 | atoms: [ | 787 | indels: [ |
673 | AtomTextEdit { | 788 | Indel { |
674 | delete: 8..11, | ||
675 | insert: "foo2", | 789 | insert: "foo2", |
790 | delete: 8..11, | ||
676 | }, | 791 | }, |
677 | ], | 792 | ], |
678 | }, | 793 | }, |
@@ -682,10 +797,10 @@ mod tests { | |||
682 | 1, | 797 | 1, |
683 | ), | 798 | ), |
684 | edit: TextEdit { | 799 | edit: TextEdit { |
685 | atoms: [ | 800 | indels: [ |
686 | AtomTextEdit { | 801 | Indel { |
687 | delete: 27..30, | ||
688 | insert: "foo2", | 802 | insert: "foo2", |
803 | delete: 27..30, | ||
689 | }, | 804 | }, |
690 | ], | 805 | ], |
691 | }, | 806 | }, |
@@ -702,13 +817,164 @@ mod tests { | |||
702 | dst_path: "bar/foo2.rs", | 817 | dst_path: "bar/foo2.rs", |
703 | }, | 818 | }, |
704 | ], | 819 | ], |
705 | cursor_position: None, | 820 | is_snippet: false, |
706 | }, | 821 | }, |
707 | }, | 822 | }, |
708 | ) | 823 | ) |
709 | "###); | 824 | "###); |
710 | } | 825 | } |
711 | 826 | ||
827 | #[test] | ||
828 | fn test_enum_variant_from_module_1() { | ||
829 | test_rename( | ||
830 | r#" | ||
831 | mod foo { | ||
832 | pub enum Foo { | ||
833 | Bar<|>, | ||
834 | } | ||
835 | } | ||
836 | |||
837 | fn func(f: foo::Foo) { | ||
838 | match f { | ||
839 | foo::Foo::Bar => {} | ||
840 | } | ||
841 | } | ||
842 | "#, | ||
843 | "Baz", | ||
844 | r#" | ||
845 | mod foo { | ||
846 | pub enum Foo { | ||
847 | Baz, | ||
848 | } | ||
849 | } | ||
850 | |||
851 | fn func(f: foo::Foo) { | ||
852 | match f { | ||
853 | foo::Foo::Baz => {} | ||
854 | } | ||
855 | } | ||
856 | "#, | ||
857 | ); | ||
858 | } | ||
859 | |||
860 | #[test] | ||
861 | fn test_enum_variant_from_module_2() { | ||
862 | test_rename( | ||
863 | r#" | ||
864 | mod foo { | ||
865 | pub struct Foo { | ||
866 | pub bar<|>: uint, | ||
867 | } | ||
868 | } | ||
869 | |||
870 | fn foo(f: foo::Foo) { | ||
871 | let _ = f.bar; | ||
872 | } | ||
873 | "#, | ||
874 | "baz", | ||
875 | r#" | ||
876 | mod foo { | ||
877 | pub struct Foo { | ||
878 | pub baz: uint, | ||
879 | } | ||
880 | } | ||
881 | |||
882 | fn foo(f: foo::Foo) { | ||
883 | let _ = f.baz; | ||
884 | } | ||
885 | "#, | ||
886 | ); | ||
887 | } | ||
888 | |||
889 | #[test] | ||
890 | fn test_parameter_to_self() { | ||
891 | test_rename( | ||
892 | r#" | ||
893 | struct Foo { | ||
894 | i: i32, | ||
895 | } | ||
896 | |||
897 | impl Foo { | ||
898 | fn f(foo<|>: &mut Foo) -> i32 { | ||
899 | foo.i | ||
900 | } | ||
901 | } | ||
902 | "#, | ||
903 | "self", | ||
904 | r#" | ||
905 | struct Foo { | ||
906 | i: i32, | ||
907 | } | ||
908 | |||
909 | impl Foo { | ||
910 | fn f(&mut self) -> i32 { | ||
911 | self.i | ||
912 | } | ||
913 | } | ||
914 | "#, | ||
915 | ); | ||
916 | } | ||
917 | |||
918 | #[test] | ||
919 | fn test_self_to_parameter() { | ||
920 | test_rename( | ||
921 | r#" | ||
922 | struct Foo { | ||
923 | i: i32, | ||
924 | } | ||
925 | |||
926 | impl Foo { | ||
927 | fn f(&mut <|>self) -> i32 { | ||
928 | self.i | ||
929 | } | ||
930 | } | ||
931 | "#, | ||
932 | "foo", | ||
933 | r#" | ||
934 | struct Foo { | ||
935 | i: i32, | ||
936 | } | ||
937 | |||
938 | impl Foo { | ||
939 | fn f(foo: &mut Foo) -> i32 { | ||
940 | foo.i | ||
941 | } | ||
942 | } | ||
943 | "#, | ||
944 | ); | ||
945 | } | ||
946 | |||
947 | #[test] | ||
948 | fn test_self_in_path_to_parameter() { | ||
949 | test_rename( | ||
950 | r#" | ||
951 | struct Foo { | ||
952 | i: i32, | ||
953 | } | ||
954 | |||
955 | impl Foo { | ||
956 | fn f(&self) -> i32 { | ||
957 | let self_var = 1; | ||
958 | self<|>.i | ||
959 | } | ||
960 | } | ||
961 | "#, | ||
962 | "foo", | ||
963 | r#" | ||
964 | struct Foo { | ||
965 | i: i32, | ||
966 | } | ||
967 | |||
968 | impl Foo { | ||
969 | fn f(foo: &Foo) -> i32 { | ||
970 | let self_var = 1; | ||
971 | foo.i | ||
972 | } | ||
973 | } | ||
974 | "#, | ||
975 | ); | ||
976 | } | ||
977 | |||
712 | fn test_rename(text: &str, new_name: &str, expected: &str) { | 978 | fn test_rename(text: &str, new_name: &str, expected: &str) { |
713 | let (analysis, position) = single_file_with_position(text); | 979 | let (analysis, position) = single_file_with_position(text); |
714 | let source_change = analysis.rename(position, new_name).unwrap(); | 980 | let source_change = analysis.rename(position, new_name).unwrap(); |
@@ -717,13 +983,13 @@ mod tests { | |||
717 | if let Some(change) = source_change { | 983 | if let Some(change) = source_change { |
718 | for edit in change.info.source_file_edits { | 984 | for edit in change.info.source_file_edits { |
719 | file_id = Some(edit.file_id); | 985 | file_id = Some(edit.file_id); |
720 | for atom in edit.edit.as_atoms() { | 986 | for indel in edit.edit.into_iter() { |
721 | text_edit_builder.replace(atom.delete, atom.insert.clone()); | 987 | text_edit_builder.replace(indel.delete, indel.insert); |
722 | } | 988 | } |
723 | } | 989 | } |
724 | } | 990 | } |
725 | let result = | 991 | let mut result = analysis.file_text(file_id.unwrap()).unwrap().to_string(); |
726 | text_edit_builder.finish().apply(&*analysis.file_text(file_id.unwrap()).unwrap()); | 992 | text_edit_builder.finish().apply(&mut result); |
727 | assert_eq_text!(expected, &*result); | 993 | assert_eq_text!(expected, &*result); |
728 | } | 994 | } |
729 | } | 995 | } |
diff --git a/crates/ra_ide/src/runnables.rs b/crates/ra_ide/src/runnables.rs index 38637c19c..131b8f307 100644 --- a/crates/ra_ide/src/runnables.rs +++ b/crates/ra_ide/src/runnables.rs | |||
@@ -1,6 +1,6 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! FIXME: write short doc here |
2 | 2 | ||
3 | use hir::Semantics; | 3 | use hir::{AsAssocItem, Semantics}; |
4 | use itertools::Itertools; | 4 | use itertools::Itertools; |
5 | use ra_ide_db::RootDatabase; | 5 | use ra_ide_db::RootDatabase; |
6 | use ra_syntax::{ | 6 | use ra_syntax::{ |
@@ -9,6 +9,7 @@ use ra_syntax::{ | |||
9 | }; | 9 | }; |
10 | 10 | ||
11 | use crate::FileId; | 11 | use crate::FileId; |
12 | use ast::DocCommentsOwner; | ||
12 | use std::fmt::Display; | 13 | use std::fmt::Display; |
13 | 14 | ||
14 | #[derive(Debug)] | 15 | #[derive(Debug)] |
@@ -37,6 +38,7 @@ pub enum RunnableKind { | |||
37 | Test { test_id: TestId, attr: TestAttr }, | 38 | Test { test_id: TestId, attr: TestAttr }, |
38 | TestMod { path: String }, | 39 | TestMod { path: String }, |
39 | Bench { test_id: TestId }, | 40 | Bench { test_id: TestId }, |
41 | DocTest { test_id: TestId }, | ||
40 | Bin, | 42 | Bin, |
41 | } | 43 | } |
42 | 44 | ||
@@ -63,14 +65,36 @@ fn runnable_fn(sema: &Semantics<RootDatabase>, fn_def: ast::FnDef) -> Option<Run | |||
63 | RunnableKind::Bin | 65 | RunnableKind::Bin |
64 | } else { | 66 | } else { |
65 | let test_id = if let Some(module) = sema.to_def(&fn_def).map(|def| def.module(sema.db)) { | 67 | let test_id = if let Some(module) = sema.to_def(&fn_def).map(|def| def.module(sema.db)) { |
66 | let path = module | 68 | let def = sema.to_def(&fn_def)?; |
69 | let impl_trait_name = | ||
70 | def.as_assoc_item(sema.db).and_then(|assoc_item| { | ||
71 | match assoc_item.container(sema.db) { | ||
72 | hir::AssocItemContainer::Trait(trait_item) => { | ||
73 | Some(trait_item.name(sema.db).to_string()) | ||
74 | } | ||
75 | hir::AssocItemContainer::ImplDef(impl_def) => impl_def | ||
76 | .target_ty(sema.db) | ||
77 | .as_adt() | ||
78 | .map(|adt| adt.name(sema.db).to_string()), | ||
79 | } | ||
80 | }); | ||
81 | |||
82 | let path_iter = module | ||
67 | .path_to_root(sema.db) | 83 | .path_to_root(sema.db) |
68 | .into_iter() | 84 | .into_iter() |
69 | .rev() | 85 | .rev() |
70 | .filter_map(|it| it.name(sema.db)) | 86 | .filter_map(|it| it.name(sema.db)) |
71 | .map(|name| name.to_string()) | 87 | .map(|name| name.to_string()); |
72 | .chain(std::iter::once(name_string)) | 88 | |
73 | .join("::"); | 89 | let path = if let Some(impl_trait_name) = impl_trait_name { |
90 | path_iter | ||
91 | .chain(std::iter::once(impl_trait_name)) | ||
92 | .chain(std::iter::once(name_string)) | ||
93 | .join("::") | ||
94 | } else { | ||
95 | path_iter.chain(std::iter::once(name_string)).join("::") | ||
96 | }; | ||
97 | |||
74 | TestId::Path(path) | 98 | TestId::Path(path) |
75 | } else { | 99 | } else { |
76 | TestId::Name(name_string) | 100 | TestId::Name(name_string) |
@@ -81,6 +105,8 @@ fn runnable_fn(sema: &Semantics<RootDatabase>, fn_def: ast::FnDef) -> Option<Run | |||
81 | RunnableKind::Test { test_id, attr } | 105 | RunnableKind::Test { test_id, attr } |
82 | } else if fn_def.has_atom_attr("bench") { | 106 | } else if fn_def.has_atom_attr("bench") { |
83 | RunnableKind::Bench { test_id } | 107 | RunnableKind::Bench { test_id } |
108 | } else if has_doc_test(&fn_def) { | ||
109 | RunnableKind::DocTest { test_id } | ||
84 | } else { | 110 | } else { |
85 | return None; | 111 | return None; |
86 | } | 112 | } |
@@ -117,6 +143,10 @@ fn has_test_related_attribute(fn_def: &ast::FnDef) -> bool { | |||
117 | .any(|attribute_text| attribute_text.contains("test")) | 143 | .any(|attribute_text| attribute_text.contains("test")) |
118 | } | 144 | } |
119 | 145 | ||
146 | fn has_doc_test(fn_def: &ast::FnDef) -> bool { | ||
147 | fn_def.doc_comment_text().map_or(false, |comment| comment.contains("```")) | ||
148 | } | ||
149 | |||
120 | fn runnable_mod(sema: &Semantics<RootDatabase>, module: ast::Module) -> Option<Runnable> { | 150 | fn runnable_mod(sema: &Semantics<RootDatabase>, module: ast::Module) -> Option<Runnable> { |
121 | let has_test_function = module | 151 | let has_test_function = module |
122 | .item_list()? | 152 | .item_list()? |
@@ -195,6 +225,79 @@ mod tests { | |||
195 | } | 225 | } |
196 | 226 | ||
197 | #[test] | 227 | #[test] |
228 | fn test_runnables_doc_test() { | ||
229 | let (analysis, pos) = analysis_and_position( | ||
230 | r#" | ||
231 | //- /lib.rs | ||
232 | <|> //empty | ||
233 | fn main() {} | ||
234 | |||
235 | /// ``` | ||
236 | /// let x = 5; | ||
237 | /// ``` | ||
238 | fn foo() {} | ||
239 | "#, | ||
240 | ); | ||
241 | let runnables = analysis.runnables(pos.file_id).unwrap(); | ||
242 | assert_debug_snapshot!(&runnables, | ||
243 | @r###" | ||
244 | [ | ||
245 | Runnable { | ||
246 | range: 1..21, | ||
247 | kind: Bin, | ||
248 | }, | ||
249 | Runnable { | ||
250 | range: 22..64, | ||
251 | kind: DocTest { | ||
252 | test_id: Path( | ||
253 | "foo", | ||
254 | ), | ||
255 | }, | ||
256 | }, | ||
257 | ] | ||
258 | "### | ||
259 | ); | ||
260 | } | ||
261 | |||
262 | #[test] | ||
263 | fn test_runnables_doc_test_in_impl() { | ||
264 | let (analysis, pos) = analysis_and_position( | ||
265 | r#" | ||
266 | //- /lib.rs | ||
267 | <|> //empty | ||
268 | fn main() {} | ||
269 | |||
270 | struct Data; | ||
271 | impl Data { | ||
272 | /// ``` | ||
273 | /// let x = 5; | ||
274 | /// ``` | ||
275 | fn foo() {} | ||
276 | } | ||
277 | "#, | ||
278 | ); | ||
279 | let runnables = analysis.runnables(pos.file_id).unwrap(); | ||
280 | assert_debug_snapshot!(&runnables, | ||
281 | @r###" | ||
282 | [ | ||
283 | Runnable { | ||
284 | range: 1..21, | ||
285 | kind: Bin, | ||
286 | }, | ||
287 | Runnable { | ||
288 | range: 51..105, | ||
289 | kind: DocTest { | ||
290 | test_id: Path( | ||
291 | "Data::foo", | ||
292 | ), | ||
293 | }, | ||
294 | }, | ||
295 | ] | ||
296 | "### | ||
297 | ); | ||
298 | } | ||
299 | |||
300 | #[test] | ||
198 | fn test_runnables_module() { | 301 | fn test_runnables_module() { |
199 | let (analysis, pos) = analysis_and_position( | 302 | let (analysis, pos) = analysis_and_position( |
200 | r#" | 303 | r#" |
diff --git a/crates/ra_ide/src/snapshots/highlight_strings.html b/crates/ra_ide/src/snapshots/highlight_strings.html index de06daf72..752b487e8 100644 --- a/crates/ra_ide/src/snapshots/highlight_strings.html +++ b/crates/ra_ide/src/snapshots/highlight_strings.html | |||
@@ -27,13 +27,13 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
27 | .keyword.unsafe { color: #BC8383; font-weight: bold; } | 27 | .keyword.unsafe { color: #BC8383; font-weight: bold; } |
28 | .control { font-style: italic; } | 28 | .control { font-style: italic; } |
29 | </style> | 29 | </style> |
30 | <pre><code><span class="macro">macro_rules!</span> println { | 30 | <pre><code><span class="macro">macro_rules!</span> <span class="macro declaration">println</span> { |
31 | ($($arg:tt)*) => ({ | 31 | ($($arg:tt)*) => ({ |
32 | $<span class="keyword">crate</span>::io::_print($<span class="keyword">crate</span>::format_args_nl!($($arg)*)); | 32 | $<span class="keyword">crate</span>::io::_print($<span class="keyword">crate</span>::format_args_nl!($($arg)*)); |
33 | }) | 33 | }) |
34 | } | 34 | } |
35 | #[rustc_builtin_macro] | 35 | #[rustc_builtin_macro] |
36 | <span class="macro">macro_rules!</span> format_args_nl { | 36 | <span class="macro">macro_rules!</span> <span class="macro declaration">format_args_nl</span> { |
37 | ($fmt:expr) => {{ <span class="comment">/* compiler built-in */</span> }}; | 37 | ($fmt:expr) => {{ <span class="comment">/* compiler built-in */</span> }}; |
38 | ($fmt:expr, $($args:tt)*) => {{ <span class="comment">/* compiler built-in */</span> }}; | 38 | ($fmt:expr, $($args:tt)*) => {{ <span class="comment">/* compiler built-in */</span> }}; |
39 | } | 39 | } |
diff --git a/crates/ra_ide/src/snapshots/highlighting.html b/crates/ra_ide/src/snapshots/highlighting.html index 4b12fe823..635fe5cf9 100644 --- a/crates/ra_ide/src/snapshots/highlighting.html +++ b/crates/ra_ide/src/snapshots/highlighting.html | |||
@@ -33,11 +33,23 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
33 | <span class="keyword">pub</span> <span class="field declaration">y</span>: <span class="builtin_type">i32</span>, | 33 | <span class="keyword">pub</span> <span class="field declaration">y</span>: <span class="builtin_type">i32</span>, |
34 | } | 34 | } |
35 | 35 | ||
36 | <span class="keyword">trait</span> <span class="trait declaration">Bar</span> { | ||
37 | <span class="keyword">fn</span> <span class="function declaration">bar</span>(&<span class="keyword">self</span>) -> <span class="builtin_type">i32</span>; | ||
38 | } | ||
39 | |||
40 | <span class="keyword">impl</span> <span class="trait">Bar</span> <span class="keyword">for</span> <span class="struct">Foo</span> { | ||
41 | <span class="keyword">fn</span> <span class="function declaration">bar</span>(&<span class="keyword">self</span>) -> <span class="builtin_type">i32</span> { | ||
42 | <span class="keyword">self</span>.<span class="field">x</span> | ||
43 | } | ||
44 | } | ||
45 | |||
46 | <span class="keyword">static</span> <span class="keyword">mut</span> <span class="static declaration mutable">STATIC_MUT</span>: <span class="builtin_type">i32</span> = <span class="numeric_literal">0</span>; | ||
47 | |||
36 | <span class="keyword">fn</span> <span class="function declaration">foo</span><<span class="lifetime declaration">'a</span>, <span class="type_param declaration">T</span>>() -> <span class="type_param">T</span> { | 48 | <span class="keyword">fn</span> <span class="function declaration">foo</span><<span class="lifetime declaration">'a</span>, <span class="type_param declaration">T</span>>() -> <span class="type_param">T</span> { |
37 | <span class="function">foo</span>::<<span class="lifetime">'a</span>, <span class="builtin_type">i32</span>>() | 49 | <span class="function">foo</span>::<<span class="lifetime">'a</span>, <span class="builtin_type">i32</span>>() |
38 | } | 50 | } |
39 | 51 | ||
40 | <span class="macro">macro_rules!</span> def_fn { | 52 | <span class="macro">macro_rules!</span> <span class="macro declaration">def_fn</span> { |
41 | ($($tt:tt)*) => {$($tt)*} | 53 | ($($tt:tt)*) => {$($tt)*} |
42 | } | 54 | } |
43 | 55 | ||
@@ -56,7 +68,14 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
56 | <span class="keyword">let</span> <span class="variable declaration">x</span> = <span class="numeric_literal">92</span>; | 68 | <span class="keyword">let</span> <span class="variable declaration">x</span> = <span class="numeric_literal">92</span>; |
57 | <span class="variable mutable">vec</span>.<span class="unresolved_reference">push</span>(<span class="struct">Foo</span> { <span class="field">x</span>, <span class="field">y</span>: <span class="numeric_literal">1</span> }); | 69 | <span class="variable mutable">vec</span>.<span class="unresolved_reference">push</span>(<span class="struct">Foo</span> { <span class="field">x</span>, <span class="field">y</span>: <span class="numeric_literal">1</span> }); |
58 | } | 70 | } |
59 | <span class="keyword unsafe">unsafe</span> { <span class="variable mutable">vec</span>.<span class="unresolved_reference">set_len</span>(<span class="numeric_literal">0</span>); } | 71 | <span class="keyword unsafe">unsafe</span> { |
72 | <span class="variable mutable">vec</span>.<span class="unresolved_reference">set_len</span>(<span class="numeric_literal">0</span>); | ||
73 | <span class="static mutable">STATIC_MUT</span> = <span class="numeric_literal">1</span>; | ||
74 | } | ||
75 | |||
76 | <span class="keyword control">for</span> <span class="variable declaration">e</span> <span class="keyword control">in</span> <span class="variable mutable">vec</span> { | ||
77 | <span class="comment">// Do nothing</span> | ||
78 | } | ||
60 | 79 | ||
61 | <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">x</span> = <span class="numeric_literal">42</span>; | 80 | <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">x</span> = <span class="numeric_literal">42</span>; |
62 | <span class="keyword">let</span> <span class="variable declaration mutable">y</span> = &<span class="keyword">mut</span> <span class="variable mutable">x</span>; | 81 | <span class="keyword">let</span> <span class="variable declaration mutable">y</span> = &<span class="keyword">mut</span> <span class="variable mutable">x</span>; |
diff --git a/crates/ra_ide/src/source_change.rs b/crates/ra_ide/src/source_change.rs deleted file mode 100644 index 71b0e8f75..000000000 --- a/crates/ra_ide/src/source_change.rs +++ /dev/null | |||
@@ -1,119 +0,0 @@ | |||
1 | //! This modules defines type to represent changes to the source code, that flow | ||
2 | //! from the server to the client. | ||
3 | //! | ||
4 | //! It can be viewed as a dual for `AnalysisChange`. | ||
5 | |||
6 | use ra_db::RelativePathBuf; | ||
7 | use ra_text_edit::TextEdit; | ||
8 | |||
9 | use crate::{FileId, FilePosition, SourceRootId, TextSize}; | ||
10 | |||
11 | #[derive(Debug)] | ||
12 | pub struct SourceChange { | ||
13 | pub label: String, | ||
14 | pub source_file_edits: Vec<SourceFileEdit>, | ||
15 | pub file_system_edits: Vec<FileSystemEdit>, | ||
16 | pub cursor_position: Option<FilePosition>, | ||
17 | } | ||
18 | |||
19 | impl SourceChange { | ||
20 | /// Creates a new SourceChange with the given label | ||
21 | /// from the edits. | ||
22 | pub(crate) fn from_edits<L: Into<String>>( | ||
23 | label: L, | ||
24 | source_file_edits: Vec<SourceFileEdit>, | ||
25 | file_system_edits: Vec<FileSystemEdit>, | ||
26 | ) -> Self { | ||
27 | SourceChange { | ||
28 | label: label.into(), | ||
29 | source_file_edits, | ||
30 | file_system_edits, | ||
31 | cursor_position: None, | ||
32 | } | ||
33 | } | ||
34 | |||
35 | /// Creates a new SourceChange with the given label, | ||
36 | /// containing only the given `SourceFileEdits`. | ||
37 | pub(crate) fn source_file_edits<L: Into<String>>(label: L, edits: Vec<SourceFileEdit>) -> Self { | ||
38 | SourceChange { | ||
39 | label: label.into(), | ||
40 | source_file_edits: edits, | ||
41 | file_system_edits: vec![], | ||
42 | cursor_position: None, | ||
43 | } | ||
44 | } | ||
45 | |||
46 | /// Creates a new SourceChange with the given label, | ||
47 | /// containing only the given `FileSystemEdits`. | ||
48 | pub(crate) fn file_system_edits<L: Into<String>>(label: L, edits: Vec<FileSystemEdit>) -> Self { | ||
49 | SourceChange { | ||
50 | label: label.into(), | ||
51 | source_file_edits: vec![], | ||
52 | file_system_edits: edits, | ||
53 | cursor_position: None, | ||
54 | } | ||
55 | } | ||
56 | |||
57 | /// Creates a new SourceChange with the given label, | ||
58 | /// containing only a single `SourceFileEdit`. | ||
59 | pub(crate) fn source_file_edit<L: Into<String>>(label: L, edit: SourceFileEdit) -> Self { | ||
60 | SourceChange::source_file_edits(label, vec![edit]) | ||
61 | } | ||
62 | |||
63 | /// Creates a new SourceChange with the given label | ||
64 | /// from the given `FileId` and `TextEdit` | ||
65 | pub(crate) fn source_file_edit_from<L: Into<String>>( | ||
66 | label: L, | ||
67 | file_id: FileId, | ||
68 | edit: TextEdit, | ||
69 | ) -> Self { | ||
70 | SourceChange::source_file_edit(label, SourceFileEdit { file_id, edit }) | ||
71 | } | ||
72 | |||
73 | /// Creates a new SourceChange with the given label | ||
74 | /// from the given `FileId` and `TextEdit` | ||
75 | pub(crate) fn file_system_edit<L: Into<String>>(label: L, edit: FileSystemEdit) -> Self { | ||
76 | SourceChange::file_system_edits(label, vec![edit]) | ||
77 | } | ||
78 | |||
79 | /// Sets the cursor position to the given `FilePosition` | ||
80 | pub(crate) fn with_cursor(mut self, cursor_position: FilePosition) -> Self { | ||
81 | self.cursor_position = Some(cursor_position); | ||
82 | self | ||
83 | } | ||
84 | |||
85 | /// Sets the cursor position to the given `FilePosition` | ||
86 | pub(crate) fn with_cursor_opt(mut self, cursor_position: Option<FilePosition>) -> Self { | ||
87 | self.cursor_position = cursor_position; | ||
88 | self | ||
89 | } | ||
90 | } | ||
91 | |||
92 | #[derive(Debug)] | ||
93 | pub struct SourceFileEdit { | ||
94 | pub file_id: FileId, | ||
95 | pub edit: TextEdit, | ||
96 | } | ||
97 | |||
98 | #[derive(Debug)] | ||
99 | pub enum FileSystemEdit { | ||
100 | CreateFile { source_root: SourceRootId, path: RelativePathBuf }, | ||
101 | MoveFile { src: FileId, dst_source_root: SourceRootId, dst_path: RelativePathBuf }, | ||
102 | } | ||
103 | |||
104 | pub(crate) struct SingleFileChange { | ||
105 | pub label: String, | ||
106 | pub edit: TextEdit, | ||
107 | pub cursor_position: Option<TextSize>, | ||
108 | } | ||
109 | |||
110 | impl SingleFileChange { | ||
111 | pub(crate) fn into_source_change(self, file_id: FileId) -> SourceChange { | ||
112 | SourceChange { | ||
113 | label: self.label, | ||
114 | source_file_edits: vec![SourceFileEdit { file_id, edit: self.edit }], | ||
115 | file_system_edits: Vec::new(), | ||
116 | cursor_position: self.cursor_position.map(|offset| FilePosition { file_id, offset }), | ||
117 | } | ||
118 | } | ||
119 | } | ||
diff --git a/crates/ra_ide/src/ssr.rs b/crates/ra_ide/src/ssr.rs index 7b93ff2d2..1873d1d0d 100644 --- a/crates/ra_ide/src/ssr.rs +++ b/crates/ra_ide/src/ssr.rs | |||
@@ -1,18 +1,18 @@ | |||
1 | //! structural search replace | 1 | //! structural search replace |
2 | 2 | ||
3 | use crate::source_change::SourceFileEdit; | 3 | use std::{collections::HashMap, iter::once, str::FromStr}; |
4 | |||
4 | use ra_db::{SourceDatabase, SourceDatabaseExt}; | 5 | use ra_db::{SourceDatabase, SourceDatabaseExt}; |
5 | use ra_ide_db::symbol_index::SymbolsDatabase; | 6 | use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase}; |
6 | use ra_ide_db::RootDatabase; | ||
7 | use ra_syntax::ast::make::try_expr_from_text; | ||
8 | use ra_syntax::ast::{ | 7 | use ra_syntax::ast::{ |
9 | ArgList, AstToken, CallExpr, Comment, Expr, MethodCallExpr, RecordField, RecordLit, | 8 | make::try_expr_from_text, ArgList, AstToken, CallExpr, Comment, Expr, MethodCallExpr, |
9 | RecordField, RecordLit, | ||
10 | }; | 10 | }; |
11 | use ra_syntax::{AstNode, SyntaxElement, SyntaxKind, SyntaxNode}; | 11 | use ra_syntax::{AstNode, SyntaxElement, SyntaxKind, SyntaxNode}; |
12 | use ra_text_edit::{TextEdit, TextEditBuilder}; | 12 | use ra_text_edit::{TextEdit, TextEditBuilder}; |
13 | use rustc_hash::FxHashMap; | 13 | use rustc_hash::FxHashMap; |
14 | use std::collections::HashMap; | 14 | |
15 | use std::{iter::once, str::FromStr}; | 15 | use crate::SourceFileEdit; |
16 | 16 | ||
17 | #[derive(Debug, PartialEq)] | 17 | #[derive(Debug, PartialEq)] |
18 | pub struct SsrError(String); | 18 | pub struct SsrError(String); |
@@ -401,16 +401,22 @@ fn render_replace( | |||
401 | ignored_comments: &Vec<Comment>, | 401 | ignored_comments: &Vec<Comment>, |
402 | template: &SsrTemplate, | 402 | template: &SsrTemplate, |
403 | ) -> String { | 403 | ) -> String { |
404 | let mut builder = TextEditBuilder::default(); | 404 | let edit = { |
405 | for element in template.template.descendants() { | 405 | let mut builder = TextEditBuilder::default(); |
406 | if let Some(var) = template.placeholders.get(&element) { | 406 | for element in template.template.descendants() { |
407 | builder.replace(element.text_range(), binding[var].to_string()) | 407 | if let Some(var) = template.placeholders.get(&element) { |
408 | builder.replace(element.text_range(), binding[var].to_string()) | ||
409 | } | ||
408 | } | 410 | } |
409 | } | 411 | for comment in ignored_comments { |
410 | for comment in ignored_comments { | 412 | builder.insert(template.template.text_range().end(), comment.syntax().to_string()) |
411 | builder.insert(template.template.text_range().end(), comment.syntax().to_string()) | 413 | } |
412 | } | 414 | builder.finish() |
413 | builder.finish().apply(&template.template.text().to_string()) | 415 | }; |
416 | |||
417 | let mut text = template.template.text().to_string(); | ||
418 | edit.apply(&mut text); | ||
419 | text | ||
414 | } | 420 | } |
415 | 421 | ||
416 | #[cfg(test)] | 422 | #[cfg(test)] |
@@ -505,7 +511,9 @@ mod tests { | |||
505 | ); | 511 | ); |
506 | 512 | ||
507 | let edit = replace(&matches, &query.template); | 513 | let edit = replace(&matches, &query.template); |
508 | assert_eq!(edit.apply(input), "fn main() { bar(1+2); }"); | 514 | let mut after = input.to_string(); |
515 | edit.apply(&mut after); | ||
516 | assert_eq!(after, "fn main() { bar(1+2); }"); | ||
509 | } | 517 | } |
510 | 518 | ||
511 | fn assert_ssr_transform(query: &str, input: &str, result: &str) { | 519 | fn assert_ssr_transform(query: &str, input: &str, result: &str) { |
@@ -513,7 +521,9 @@ mod tests { | |||
513 | let code = SourceFile::parse(input).tree(); | 521 | let code = SourceFile::parse(input).tree(); |
514 | let matches = find(&query.pattern, code.syntax()); | 522 | let matches = find(&query.pattern, code.syntax()); |
515 | let edit = replace(&matches, &query.template); | 523 | let edit = replace(&matches, &query.template); |
516 | assert_eq!(edit.apply(input), result); | 524 | let mut after = input.to_string(); |
525 | edit.apply(&mut after); | ||
526 | assert_eq!(after, result); | ||
517 | } | 527 | } |
518 | 528 | ||
519 | #[test] | 529 | #[test] |
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs index 6658c7bb2..be57eeb0a 100644 --- a/crates/ra_ide/src/syntax_highlighting.rs +++ b/crates/ra_ide/src/syntax_highlighting.rs | |||
@@ -167,6 +167,19 @@ pub(crate) fn highlight( | |||
167 | binding_hash: None, | 167 | binding_hash: None, |
168 | }); | 168 | }); |
169 | } | 169 | } |
170 | if let Some(name) = mc.is_macro_rules() { | ||
171 | if let Some((highlight, binding_hash)) = highlight_element( | ||
172 | &sema, | ||
173 | &mut bindings_shadow_count, | ||
174 | name.syntax().clone().into(), | ||
175 | ) { | ||
176 | stack.add(HighlightedRange { | ||
177 | range: name.syntax().text_range(), | ||
178 | highlight, | ||
179 | binding_hash, | ||
180 | }); | ||
181 | } | ||
182 | } | ||
170 | continue; | 183 | continue; |
171 | } | 184 | } |
172 | WalkEvent::Leave(Some(mc)) => { | 185 | WalkEvent::Leave(Some(mc)) => { |
@@ -390,12 +403,13 @@ fn highlight_element( | |||
390 | T![break] | 403 | T![break] |
391 | | T![continue] | 404 | | T![continue] |
392 | | T![else] | 405 | | T![else] |
393 | | T![for] | ||
394 | | T![if] | 406 | | T![if] |
395 | | T![loop] | 407 | | T![loop] |
396 | | T![match] | 408 | | T![match] |
397 | | T![return] | 409 | | T![return] |
398 | | T![while] => h | HighlightModifier::ControlFlow, | 410 | | T![while] |
411 | | T![in] => h | HighlightModifier::ControlFlow, | ||
412 | T![for] if !is_child_of_impl(element) => h | HighlightModifier::ControlFlow, | ||
399 | T![unsafe] => h | HighlightModifier::Unsafe, | 413 | T![unsafe] => h | HighlightModifier::Unsafe, |
400 | _ => h, | 414 | _ => h, |
401 | } | 415 | } |
@@ -419,6 +433,13 @@ fn highlight_element( | |||
419 | } | 433 | } |
420 | } | 434 | } |
421 | 435 | ||
436 | fn is_child_of_impl(element: SyntaxElement) -> bool { | ||
437 | match element.parent() { | ||
438 | Some(e) => e.kind() == IMPL_DEF, | ||
439 | _ => false, | ||
440 | } | ||
441 | } | ||
442 | |||
422 | fn highlight_name(db: &RootDatabase, def: Definition) -> Highlight { | 443 | fn highlight_name(db: &RootDatabase, def: Definition) -> Highlight { |
423 | match def { | 444 | match def { |
424 | Definition::Macro(_) => HighlightTag::Macro, | 445 | Definition::Macro(_) => HighlightTag::Macro, |
@@ -431,10 +452,16 @@ fn highlight_name(db: &RootDatabase, def: Definition) -> Highlight { | |||
431 | hir::ModuleDef::Adt(hir::Adt::Union(_)) => HighlightTag::Union, | 452 | hir::ModuleDef::Adt(hir::Adt::Union(_)) => HighlightTag::Union, |
432 | hir::ModuleDef::EnumVariant(_) => HighlightTag::EnumVariant, | 453 | hir::ModuleDef::EnumVariant(_) => HighlightTag::EnumVariant, |
433 | hir::ModuleDef::Const(_) => HighlightTag::Constant, | 454 | hir::ModuleDef::Const(_) => HighlightTag::Constant, |
434 | hir::ModuleDef::Static(_) => HighlightTag::Static, | ||
435 | hir::ModuleDef::Trait(_) => HighlightTag::Trait, | 455 | hir::ModuleDef::Trait(_) => HighlightTag::Trait, |
436 | hir::ModuleDef::TypeAlias(_) => HighlightTag::TypeAlias, | 456 | hir::ModuleDef::TypeAlias(_) => HighlightTag::TypeAlias, |
437 | hir::ModuleDef::BuiltinType(_) => HighlightTag::BuiltinType, | 457 | hir::ModuleDef::BuiltinType(_) => HighlightTag::BuiltinType, |
458 | hir::ModuleDef::Static(s) => { | ||
459 | let mut h = Highlight::new(HighlightTag::Static); | ||
460 | if s.is_mut(db) { | ||
461 | h |= HighlightModifier::Mutable; | ||
462 | } | ||
463 | return h; | ||
464 | } | ||
438 | }, | 465 | }, |
439 | Definition::SelfType(_) => HighlightTag::SelfType, | 466 | Definition::SelfType(_) => HighlightTag::SelfType, |
440 | Definition::TypeParam(_) => HighlightTag::TypeParam, | 467 | Definition::TypeParam(_) => HighlightTag::TypeParam, |
diff --git a/crates/ra_ide/src/syntax_highlighting/tests.rs b/crates/ra_ide/src/syntax_highlighting/tests.rs index d2926ba78..eb43a23da 100644 --- a/crates/ra_ide/src/syntax_highlighting/tests.rs +++ b/crates/ra_ide/src/syntax_highlighting/tests.rs | |||
@@ -17,6 +17,18 @@ struct Foo { | |||
17 | pub y: i32, | 17 | pub y: i32, |
18 | } | 18 | } |
19 | 19 | ||
20 | trait Bar { | ||
21 | fn bar(&self) -> i32; | ||
22 | } | ||
23 | |||
24 | impl Bar for Foo { | ||
25 | fn bar(&self) -> i32 { | ||
26 | self.x | ||
27 | } | ||
28 | } | ||
29 | |||
30 | static mut STATIC_MUT: i32 = 0; | ||
31 | |||
20 | fn foo<'a, T>() -> T { | 32 | fn foo<'a, T>() -> T { |
21 | foo::<'a, i32>() | 33 | foo::<'a, i32>() |
22 | } | 34 | } |
@@ -40,7 +52,14 @@ fn main() { | |||
40 | let x = 92; | 52 | let x = 92; |
41 | vec.push(Foo { x, y: 1 }); | 53 | vec.push(Foo { x, y: 1 }); |
42 | } | 54 | } |
43 | unsafe { vec.set_len(0); } | 55 | unsafe { |
56 | vec.set_len(0); | ||
57 | STATIC_MUT = 1; | ||
58 | } | ||
59 | |||
60 | for e in vec { | ||
61 | // Do nothing | ||
62 | } | ||
44 | 63 | ||
45 | let mut x = 42; | 64 | let mut x = 42; |
46 | let y = &mut x; | 65 | let y = &mut x; |
diff --git a/crates/ra_ide/src/syntax_tree.rs b/crates/ra_ide/src/syntax_tree.rs index bf97f8c56..86c70ff83 100644 --- a/crates/ra_ide/src/syntax_tree.rs +++ b/crates/ra_ide/src/syntax_tree.rs | |||
@@ -120,9 +120,8 @@ [email protected] | |||
120 | [email protected] ")" | 120 | [email protected] ")" |
121 | [email protected] " " | 121 | [email protected] " " |
122 | [email protected] | 122 | [email protected] |
123 | [email protected] | 123 | [email protected] "{" |
124 | [email protected] "{" | 124 | [email protected] "}" |
125 | [email protected] "}" | ||
126 | "# | 125 | "# |
127 | .trim() | 126 | .trim() |
128 | ); | 127 | ); |
@@ -153,26 +152,25 @@ [email protected] | |||
153 | [email protected] ")" | 152 | [email protected] ")" |
154 | [email protected] " " | 153 | [email protected] " " |
155 | [email protected] | 154 | [email protected] |
156 | [email protected] | 155 | [email protected] "{" |
157 | [email protected] "{" | 156 | [email protected] "\n " |
158 | [email protected] "\n " | 157 | [email protected] |
159 | [email protected] | 158 | [email protected] |
160 | [email protected] | 159 | [email protected] |
161 | [email protected] | 160 | [email protected] |
162 | [email protected] | 161 | [email protected] |
163 | [email protected] | 162 | [email protected] "assert" |
164 | [email protected] "assert" | 163 | [email protected] "!" |
165 | [email protected] "!" | 164 | [email protected] |
166 | [email protected] | 165 | [email protected] "(" |
167 | [email protected] "(" | 166 | [email protected] "\"\n fn foo() {\n ..." |
168 | [email protected] "\"\n fn foo() {\n ..." | 167 | [email protected] "," |
169 | [email protected] "," | 168 | [email protected] " " |
170 | [email protected] " " | 169 | [email protected] "\"\"" |
171 | [email protected] "\"\"" | 170 | [email protected] ")" |
172 | [email protected] ")" | 171 | [email protected] ";" |
173 | [email protected] ";" | 172 | [email protected] "\n" |
174 | [email protected] "\n" | 173 | [email protected] "}" |
175 | [email protected] "}" | ||
176 | "# | 174 | "# |
177 | .trim() | 175 | .trim() |
178 | ); | 176 | ); |
@@ -196,9 +194,8 @@ [email protected] | |||
196 | [email protected] ")" | 194 | [email protected] ")" |
197 | [email protected] " " | 195 | [email protected] " " |
198 | [email protected] | 196 | [email protected] |
199 | [email protected] | 197 | [email protected] "{" |
200 | [email protected] "{" | 198 | [email protected] "}" |
201 | [email protected] "}" | ||
202 | "# | 199 | "# |
203 | .trim() | 200 | .trim() |
204 | ); | 201 | ); |
@@ -265,10 +262,9 @@ [email protected] | |||
265 | [email protected] ")" | 262 | [email protected] ")" |
266 | [email protected] " " | 263 | [email protected] " " |
267 | [email protected] | 264 | [email protected] |
268 | [email protected] | 265 | [email protected] "{" |
269 | [email protected] "{" | 266 | [email protected] "\n" |
270 | [email protected] "\n" | 267 | [email protected] "}" |
271 | [email protected] "}" | ||
272 | "# | 268 | "# |
273 | .trim() | 269 | .trim() |
274 | ); | 270 | ); |
@@ -300,10 +296,9 @@ [email protected] | |||
300 | [email protected] ")" | 296 | [email protected] ")" |
301 | [email protected] " " | 297 | [email protected] " " |
302 | [email protected] | 298 | [email protected] |
303 | [email protected] | 299 | [email protected] "{" |
304 | [email protected] "{" | 300 | [email protected] "\n" |
305 | [email protected] "\n" | 301 | [email protected] "}" |
306 | [email protected] "}" | ||
307 | "# | 302 | "# |
308 | .trim() | 303 | .trim() |
309 | ); | 304 | ); |
@@ -334,10 +329,9 @@ [email protected] | |||
334 | [email protected] ")" | 329 | [email protected] ")" |
335 | [email protected] " " | 330 | [email protected] " " |
336 | [email protected] | 331 | [email protected] |
337 | [email protected] | 332 | [email protected] "{" |
338 | [email protected] "{" | 333 | [email protected] "\n" |
339 | [email protected] "\n" | 334 | [email protected] "}" |
340 | [email protected] "}" | ||
341 | [email protected] "\n" | 335 | [email protected] "\n" |
342 | [email protected] | 336 | [email protected] |
343 | [email protected] "fn" | 337 | [email protected] "fn" |
@@ -349,10 +343,9 @@ [email protected] | |||
349 | [email protected] ")" | 343 | [email protected] ")" |
350 | [email protected] " " | 344 | [email protected] " " |
351 | [email protected] | 345 | [email protected] |
352 | [email protected] | 346 | [email protected] "{" |
353 | [email protected] "{" | 347 | [email protected] "\n" |
354 | [email protected] "\n" | 348 | [email protected] "}" |
355 | [email protected] "}" | ||
356 | "# | 349 | "# |
357 | .trim() | 350 | .trim() |
358 | ); | 351 | ); |
diff --git a/crates/ra_ide/src/test_utils.rs b/crates/ra_ide/src/test_utils.rs deleted file mode 100644 index f14533e14..000000000 --- a/crates/ra_ide/src/test_utils.rs +++ /dev/null | |||
@@ -1,21 +0,0 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use ra_syntax::{SourceFile, TextSize}; | ||
4 | use ra_text_edit::TextEdit; | ||
5 | |||
6 | pub use test_utils::*; | ||
7 | |||
8 | pub fn check_action<F: Fn(&SourceFile, TextSize) -> Option<TextEdit>>( | ||
9 | before: &str, | ||
10 | after: &str, | ||
11 | f: F, | ||
12 | ) { | ||
13 | let (before_cursor_pos, before) = extract_offset(before); | ||
14 | let file = SourceFile::parse(&before).ok().unwrap(); | ||
15 | let result = f(&file, before_cursor_pos).expect("code action is not applicable"); | ||
16 | let actual = result.apply(&before); | ||
17 | let actual_cursor_pos = | ||
18 | result.apply_to_offset(before_cursor_pos).expect("cursor position is affected by the edit"); | ||
19 | let actual = add_cursor(&actual, actual_cursor_pos); | ||
20 | assert_eq_text!(after, &actual); | ||
21 | } | ||
diff --git a/crates/ra_ide/src/typing.rs b/crates/ra_ide/src/typing.rs index 2a8b4327f..cd48cad93 100644 --- a/crates/ra_ide/src/typing.rs +++ b/crates/ra_ide/src/typing.rs | |||
@@ -17,15 +17,16 @@ mod on_enter; | |||
17 | 17 | ||
18 | use ra_db::{FilePosition, SourceDatabase}; | 18 | use ra_db::{FilePosition, SourceDatabase}; |
19 | use ra_fmt::leading_indent; | 19 | use ra_fmt::leading_indent; |
20 | use ra_ide_db::RootDatabase; | 20 | use ra_ide_db::{source_change::SingleFileChange, RootDatabase}; |
21 | use ra_syntax::{ | 21 | use ra_syntax::{ |
22 | algo::find_node_at_offset, | 22 | algo::find_node_at_offset, |
23 | ast::{self, AstToken}, | 23 | ast::{self, AstToken}, |
24 | AstNode, SourceFile, TextRange, TextSize, | 24 | AstNode, SourceFile, TextRange, TextSize, |
25 | }; | 25 | }; |
26 | |||
26 | use ra_text_edit::TextEdit; | 27 | use ra_text_edit::TextEdit; |
27 | 28 | ||
28 | use crate::{source_change::SingleFileChange, SourceChange}; | 29 | use crate::SourceChange; |
29 | 30 | ||
30 | pub(crate) use on_enter::on_enter; | 31 | pub(crate) use on_enter::on_enter; |
31 | 32 | ||
@@ -81,7 +82,6 @@ fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option<SingleFileChange> | |||
81 | Some(SingleFileChange { | 82 | Some(SingleFileChange { |
82 | label: "add semicolon".to_string(), | 83 | label: "add semicolon".to_string(), |
83 | edit: TextEdit::insert(offset, ";".to_string()), | 84 | edit: TextEdit::insert(offset, ";".to_string()), |
84 | cursor_position: None, | ||
85 | }) | 85 | }) |
86 | } | 86 | } |
87 | 87 | ||
@@ -110,7 +110,6 @@ fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option<SingleFileChange> | |||
110 | Some(SingleFileChange { | 110 | Some(SingleFileChange { |
111 | label: "reindent dot".to_string(), | 111 | label: "reindent dot".to_string(), |
112 | edit: TextEdit::replace(TextRange::new(offset - current_indent_len, offset), target_indent), | 112 | edit: TextEdit::replace(TextRange::new(offset - current_indent_len, offset), target_indent), |
113 | cursor_position: Some(offset + target_indent_len - current_indent_len + TextSize::of('.')), | ||
114 | }) | 113 | }) |
115 | } | 114 | } |
116 | 115 | ||
@@ -129,7 +128,6 @@ fn on_arrow_typed(file: &SourceFile, offset: TextSize) -> Option<SingleFileChang | |||
129 | Some(SingleFileChange { | 128 | Some(SingleFileChange { |
130 | label: "add space after return type".to_string(), | 129 | label: "add space after return type".to_string(), |
131 | edit: TextEdit::insert(after_arrow, " ".to_string()), | 130 | edit: TextEdit::insert(after_arrow, " ".to_string()), |
132 | cursor_position: Some(after_arrow), | ||
133 | }) | 131 | }) |
134 | } | 132 | } |
135 | 133 | ||
@@ -139,26 +137,23 @@ mod tests { | |||
139 | 137 | ||
140 | use super::*; | 138 | use super::*; |
141 | 139 | ||
142 | fn do_type_char(char_typed: char, before: &str) -> Option<(String, SingleFileChange)> { | 140 | fn do_type_char(char_typed: char, before: &str) -> Option<String> { |
143 | let (offset, before) = extract_offset(before); | 141 | let (offset, before) = extract_offset(before); |
144 | let edit = TextEdit::insert(offset, char_typed.to_string()); | 142 | let edit = TextEdit::insert(offset, char_typed.to_string()); |
145 | let before = edit.apply(&before); | 143 | let mut before = before.to_string(); |
144 | edit.apply(&mut before); | ||
146 | let parse = SourceFile::parse(&before); | 145 | let parse = SourceFile::parse(&before); |
147 | on_char_typed_inner(&parse.tree(), offset, char_typed) | 146 | on_char_typed_inner(&parse.tree(), offset, char_typed).map(|it| { |
148 | .map(|it| (it.edit.apply(&before), it)) | 147 | it.edit.apply(&mut before); |
148 | before.to_string() | ||
149 | }) | ||
149 | } | 150 | } |
150 | 151 | ||
151 | fn type_char(char_typed: char, before: &str, after: &str) { | 152 | fn type_char(char_typed: char, before: &str, after: &str) { |
152 | let (actual, file_change) = do_type_char(char_typed, before) | 153 | let actual = do_type_char(char_typed, before) |
153 | .unwrap_or_else(|| panic!("typing `{}` did nothing", char_typed)); | 154 | .unwrap_or_else(|| panic!("typing `{}` did nothing", char_typed)); |
154 | 155 | ||
155 | if after.contains("<|>") { | 156 | assert_eq_text!(after, &actual); |
156 | let (offset, after) = extract_offset(after); | ||
157 | assert_eq_text!(&after, &actual); | ||
158 | assert_eq!(file_change.cursor_position, Some(offset)) | ||
159 | } else { | ||
160 | assert_eq_text!(after, &actual); | ||
161 | } | ||
162 | } | 157 | } |
163 | 158 | ||
164 | fn type_char_noop(char_typed: char, before: &str) { | 159 | fn type_char_noop(char_typed: char, before: &str) { |
@@ -346,6 +341,6 @@ fn foo() { | |||
346 | 341 | ||
347 | #[test] | 342 | #[test] |
348 | fn adds_space_after_return_type() { | 343 | fn adds_space_after_return_type() { |
349 | type_char('>', "fn foo() -<|>{ 92 }", "fn foo() -><|> { 92 }") | 344 | type_char('>', "fn foo() -<|>{ 92 }", "fn foo() -> { 92 }") |
350 | } | 345 | } |
351 | } | 346 | } |
diff --git a/crates/ra_ide/src/typing/on_enter.rs b/crates/ra_ide/src/typing/on_enter.rs index 30c8c5572..85be14ad3 100644 --- a/crates/ra_ide/src/typing/on_enter.rs +++ b/crates/ra_ide/src/typing/on_enter.rs | |||
@@ -38,17 +38,15 @@ pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<Sour | |||
38 | } | 38 | } |
39 | 39 | ||
40 | let indent = node_indent(&file, comment.syntax())?; | 40 | let indent = node_indent(&file, comment.syntax())?; |
41 | let inserted = format!("\n{}{} ", indent, prefix); | 41 | let inserted = format!("\n{}{} $0", indent, prefix); |
42 | let cursor_position = position.offset + TextSize::of(&inserted); | ||
43 | let edit = TextEdit::insert(position.offset, inserted); | 42 | let edit = TextEdit::insert(position.offset, inserted); |
44 | 43 | ||
45 | Some( | 44 | let mut res = SourceChange::source_file_edit( |
46 | SourceChange::source_file_edit( | 45 | "On enter", |
47 | "on enter", | 46 | SourceFileEdit { edit, file_id: position.file_id }, |
48 | SourceFileEdit { edit, file_id: position.file_id }, | 47 | ); |
49 | ) | 48 | res.is_snippet = true; |
50 | .with_cursor(FilePosition { offset: cursor_position, file_id: position.file_id }), | 49 | Some(res) |
51 | ) | ||
52 | } | 50 | } |
53 | 51 | ||
54 | fn followed_by_comment(comment: &ast::Comment) -> bool { | 52 | fn followed_by_comment(comment: &ast::Comment) -> bool { |
@@ -84,7 +82,7 @@ fn node_indent(file: &SourceFile, token: &SyntaxToken) -> Option<SmolStr> { | |||
84 | 82 | ||
85 | #[cfg(test)] | 83 | #[cfg(test)] |
86 | mod tests { | 84 | mod tests { |
87 | use test_utils::{add_cursor, assert_eq_text, extract_offset}; | 85 | use test_utils::{assert_eq_text, extract_offset}; |
88 | 86 | ||
89 | use crate::mock_analysis::single_file; | 87 | use crate::mock_analysis::single_file; |
90 | 88 | ||
@@ -96,8 +94,8 @@ mod tests { | |||
96 | let result = analysis.on_enter(FilePosition { offset, file_id }).unwrap()?; | 94 | let result = analysis.on_enter(FilePosition { offset, file_id }).unwrap()?; |
97 | 95 | ||
98 | assert_eq!(result.source_file_edits.len(), 1); | 96 | assert_eq!(result.source_file_edits.len(), 1); |
99 | let actual = result.source_file_edits[0].edit.apply(&before); | 97 | let mut actual = before.to_string(); |
100 | let actual = add_cursor(&actual, result.cursor_position.unwrap().offset); | 98 | result.source_file_edits[0].edit.apply(&mut actual); |
101 | Some(actual) | 99 | Some(actual) |
102 | } | 100 | } |
103 | 101 | ||
@@ -120,7 +118,7 @@ fn foo() { | |||
120 | ", | 118 | ", |
121 | r" | 119 | r" |
122 | /// Some docs | 120 | /// Some docs |
123 | /// <|> | 121 | /// $0 |
124 | fn foo() { | 122 | fn foo() { |
125 | } | 123 | } |
126 | ", | 124 | ", |
@@ -136,7 +134,7 @@ impl S { | |||
136 | r" | 134 | r" |
137 | impl S { | 135 | impl S { |
138 | /// Some | 136 | /// Some |
139 | /// <|> docs. | 137 | /// $0 docs. |
140 | fn foo() {} | 138 | fn foo() {} |
141 | } | 139 | } |
142 | ", | 140 | ", |
@@ -150,7 +148,7 @@ fn foo() { | |||
150 | ", | 148 | ", |
151 | r" | 149 | r" |
152 | /// | 150 | /// |
153 | /// <|> Some docs | 151 | /// $0 Some docs |
154 | fn foo() { | 152 | fn foo() { |
155 | } | 153 | } |
156 | ", | 154 | ", |
@@ -174,7 +172,7 @@ fn main() { | |||
174 | r" | 172 | r" |
175 | fn main() { | 173 | fn main() { |
176 | // Fix | 174 | // Fix |
177 | // <|> me | 175 | // $0 me |
178 | let x = 1 + 1; | 176 | let x = 1 + 1; |
179 | } | 177 | } |
180 | ", | 178 | ", |
@@ -194,7 +192,7 @@ fn main() { | |||
194 | r" | 192 | r" |
195 | fn main() { | 193 | fn main() { |
196 | // Fix | 194 | // Fix |
197 | // <|> | 195 | // $0 |
198 | // me | 196 | // me |
199 | let x = 1 + 1; | 197 | let x = 1 + 1; |
200 | } | 198 | } |