diff options
Diffstat (limited to 'crates/ra_ide')
53 files changed, 2936 insertions, 929 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_hierarchy.rs b/crates/ra_ide/src/call_hierarchy.rs index 85d1f0cb1..defd8176f 100644 --- a/crates/ra_ide/src/call_hierarchy.rs +++ b/crates/ra_ide/src/call_hierarchy.rs | |||
@@ -246,6 +246,35 @@ mod tests { | |||
246 | } | 246 | } |
247 | 247 | ||
248 | #[test] | 248 | #[test] |
249 | fn test_call_hierarchy_in_tests_mod() { | ||
250 | check_hierarchy( | ||
251 | r#" | ||
252 | //- /lib.rs cfg:test | ||
253 | fn callee() {} | ||
254 | fn caller1() { | ||
255 | call<|>ee(); | ||
256 | } | ||
257 | |||
258 | #[cfg(test)] | ||
259 | mod tests { | ||
260 | use super::*; | ||
261 | |||
262 | #[test] | ||
263 | fn test_caller() { | ||
264 | callee(); | ||
265 | } | ||
266 | } | ||
267 | "#, | ||
268 | "callee FN_DEF FileId(1) 0..14 3..9", | ||
269 | &[ | ||
270 | "caller1 FN_DEF FileId(1) 15..45 18..25 : [34..40]", | ||
271 | "test_caller FN_DEF FileId(1) 93..147 108..119 : [132..138]", | ||
272 | ], | ||
273 | &[], | ||
274 | ); | ||
275 | } | ||
276 | |||
277 | #[test] | ||
249 | fn test_call_hierarchy_in_different_files() { | 278 | fn test_call_hierarchy_in_different_files() { |
250 | check_hierarchy( | 279 | check_hierarchy( |
251 | r#" | 280 | r#" |
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..d890b69d2 100644 --- a/crates/ra_ide/src/completion.rs +++ b/crates/ra_ide/src/completion.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | mod completion_config; | 1 | mod completion_config; |
4 | mod completion_item; | 2 | mod completion_item; |
5 | mod completion_context; | 3 | mod completion_context; |
@@ -35,6 +33,51 @@ pub use crate::completion::{ | |||
35 | completion_item::{CompletionItem, CompletionItemKind, CompletionScore, InsertTextFormat}, | 33 | completion_item::{CompletionItem, CompletionItemKind, CompletionScore, InsertTextFormat}, |
36 | }; | 34 | }; |
37 | 35 | ||
36 | //FIXME: split the following feature into fine-grained features. | ||
37 | |||
38 | // Feature: Magic Completions | ||
39 | // | ||
40 | // In addition to usual reference completion, rust-analyzer provides some ✨magic✨ | ||
41 | // completions as well: | ||
42 | // | ||
43 | // Keywords like `if`, `else` `while`, `loop` are completed with braces, and cursor | ||
44 | // is placed at the appropriate position. Even though `if` is easy to type, you | ||
45 | // still want to complete it, to get ` { }` for free! `return` is inserted with a | ||
46 | // space or `;` depending on the return type of the function. | ||
47 | // | ||
48 | // When completing a function call, `()` are automatically inserted. If a function | ||
49 | // takes arguments, the cursor is positioned inside the parenthesis. | ||
50 | // | ||
51 | // There are postfix completions, which can be triggered by typing something like | ||
52 | // `foo().if`. The word after `.` determines postfix completion. Possible variants are: | ||
53 | // | ||
54 | // - `expr.if` -> `if expr {}` or `if let ... {}` for `Option` or `Result` | ||
55 | // - `expr.match` -> `match expr {}` | ||
56 | // - `expr.while` -> `while expr {}` or `while let ... {}` for `Option` or `Result` | ||
57 | // - `expr.ref` -> `&expr` | ||
58 | // - `expr.refm` -> `&mut expr` | ||
59 | // - `expr.not` -> `!expr` | ||
60 | // - `expr.dbg` -> `dbg!(expr)` | ||
61 | // | ||
62 | // There also snippet completions: | ||
63 | // | ||
64 | // .Expressions | ||
65 | // - `pd` -> `println!("{:?}")` | ||
66 | // - `ppd` -> `println!("{:#?}")` | ||
67 | // | ||
68 | // .Items | ||
69 | // - `tfn` -> `#[test] fn f(){}` | ||
70 | // - `tmod` -> | ||
71 | // ```rust | ||
72 | // #[cfg(test)] | ||
73 | // mod tests { | ||
74 | // use super::*; | ||
75 | // | ||
76 | // #[test] | ||
77 | // fn test_fn() {} | ||
78 | // } | ||
79 | // ``` | ||
80 | |||
38 | /// Main entry point for completion. We run completion as a two-phase process. | 81 | /// Main entry point for completion. We run completion as a two-phase process. |
39 | /// | 82 | /// |
40 | /// First, we look at the position and collect a so-called `CompletionContext. | 83 | /// First, we look at the position and collect a so-called `CompletionContext. |
@@ -59,13 +102,13 @@ pub use crate::completion::{ | |||
59 | /// with ordering of completions (currently this is done by the client). | 102 | /// with ordering of completions (currently this is done by the client). |
60 | pub(crate) fn completions( | 103 | pub(crate) fn completions( |
61 | db: &RootDatabase, | 104 | db: &RootDatabase, |
62 | position: FilePosition, | ||
63 | config: &CompletionConfig, | 105 | config: &CompletionConfig, |
106 | position: FilePosition, | ||
64 | ) -> Option<Completions> { | 107 | ) -> Option<Completions> { |
65 | let ctx = CompletionContext::new(db, position, config)?; | 108 | let ctx = CompletionContext::new(db, position, config)?; |
66 | 109 | ||
67 | let mut acc = Completions::default(); | 110 | let mut acc = Completions::default(); |
68 | 111 | complete_attribute::complete_attribute(&mut acc, &ctx); | |
69 | complete_fn_param::complete_fn_param(&mut acc, &ctx); | 112 | complete_fn_param::complete_fn_param(&mut acc, &ctx); |
70 | complete_keyword::complete_expr_keyword(&mut acc, &ctx); | 113 | complete_keyword::complete_expr_keyword(&mut acc, &ctx); |
71 | complete_keyword::complete_use_tree_keyword(&mut acc, &ctx); | 114 | complete_keyword::complete_use_tree_keyword(&mut acc, &ctx); |
@@ -79,7 +122,6 @@ pub(crate) fn completions( | |||
79 | complete_postfix::complete_postfix(&mut acc, &ctx); | 122 | complete_postfix::complete_postfix(&mut acc, &ctx); |
80 | complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx); | 123 | complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx); |
81 | complete_trait_impl::complete_trait_impl(&mut acc, &ctx); | 124 | complete_trait_impl::complete_trait_impl(&mut acc, &ctx); |
82 | complete_attribute::complete_attribute(&mut acc, &ctx); | ||
83 | 125 | ||
84 | Some(acc) | 126 | Some(acc) |
85 | } | 127 | } |
diff --git a/crates/ra_ide/src/completion/complete_attribute.rs b/crates/ra_ide/src/completion/complete_attribute.rs index 8bf952798..fb3f0b743 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 | } |
@@ -108,7 +112,7 @@ const ATTRIBUTES: &[AttrCompletion] = &[ | |||
108 | AttrCompletion { label: "repr", snippet: Some("repr(${0:C})"), should_be_inner: false }, | 112 | AttrCompletion { label: "repr", snippet: Some("repr(${0:C})"), should_be_inner: false }, |
109 | AttrCompletion { | 113 | AttrCompletion { |
110 | label: "should_panic", | 114 | label: "should_panic", |
111 | snippet: Some(r#"expected = "${0:reason}""#), | 115 | snippet: Some(r#"should_panic(expected = "${0:reason}")"#), |
112 | should_be_inner: false, | 116 | should_be_inner: false, |
113 | }, | 117 | }, |
114 | AttrCompletion { | 118 | AttrCompletion { |
@@ -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( |
@@ -303,7 +571,7 @@ mod tests { | |||
303 | label: "should_panic", | 571 | label: "should_panic", |
304 | source_range: 19..19, | 572 | source_range: 19..19, |
305 | delete: 19..19, | 573 | delete: 19..19, |
306 | insert: "expected = \"${0:reason}\"", | 574 | insert: "should_panic(expected = \"${0:reason}\")", |
307 | kind: Attribute, | 575 | kind: Attribute, |
308 | }, | 576 | }, |
309 | CompletionItem { | 577 | CompletionItem { |
@@ -542,7 +810,7 @@ mod tests { | |||
542 | label: "should_panic", | 810 | label: "should_panic", |
543 | source_range: 20..20, | 811 | source_range: 20..20, |
544 | delete: 20..20, | 812 | delete: 20..20, |
545 | insert: "expected = \"${0:reason}\"", | 813 | insert: "should_panic(expected = \"${0:reason}\")", |
546 | kind: Attribute, | 814 | kind: Attribute, |
547 | }, | 815 | }, |
548 | CompletionItem { | 816 | CompletionItem { |
diff --git a/crates/ra_ide/src/completion/complete_postfix.rs b/crates/ra_ide/src/completion/complete_postfix.rs index 6a0f0c72e..59b58bf98 100644 --- a/crates/ra_ide/src/completion/complete_postfix.rs +++ b/crates/ra_ide/src/completion/complete_postfix.rs | |||
@@ -1,12 +1,11 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! FIXME: write short doc here |
2 | 2 | use ra_assists::utils::TryEnum; | |
3 | use ra_syntax::{ | 3 | use ra_syntax::{ |
4 | ast::{self, AstNode}, | 4 | ast::{self, AstNode}, |
5 | TextRange, TextSize, | 5 | TextRange, TextSize, |
6 | }; | 6 | }; |
7 | use ra_text_edit::TextEdit; | 7 | use ra_text_edit::TextEdit; |
8 | 8 | ||
9 | use super::completion_config::SnippetCap; | ||
10 | use crate::{ | 9 | use crate::{ |
11 | completion::{ | 10 | completion::{ |
12 | completion_context::CompletionContext, | 11 | completion_context::CompletionContext, |
@@ -15,6 +14,8 @@ use crate::{ | |||
15 | CompletionItem, | 14 | CompletionItem, |
16 | }; | 15 | }; |
17 | 16 | ||
17 | use super::completion_config::SnippetCap; | ||
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 { |
20 | return; | 21 | return; |
@@ -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); | ||
54 | |||
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); | ||
40 | 75 | ||
41 | if receiver_ty.is_bool() || receiver_ty.is_unknown() { | 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,14 +125,53 @@ 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); |
128 | match try_enum { | ||
129 | Some(try_enum) => { | ||
130 | match try_enum { | ||
131 | TryEnum::Result => { | ||
132 | postfix_snippet( | ||
133 | ctx, | ||
134 | cap, | ||
135 | &dot_receiver, | ||
136 | "match", | ||
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 | } | ||
83 | 167 | ||
84 | postfix_snippet( | 168 | postfix_snippet( |
85 | ctx, | 169 | ctx, |
86 | cap, | 170 | cap, |
87 | &dot_receiver, | 171 | &dot_receiver, |
88 | "match", | 172 | "box", |
89 | "match expr {}", | 173 | "Box::new(expr)", |
90 | &format!("match {} {{\n ${{1:_}} => {{$0\\}},\n}}", receiver_text), | 174 | &format!("Box::new({})", receiver_text), |
91 | ) | 175 | ) |
92 | .add_to(acc); | 176 | .add_to(acc); |
93 | 177 | ||
@@ -95,9 +179,9 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { | |||
95 | ctx, | 179 | ctx, |
96 | cap, | 180 | cap, |
97 | &dot_receiver, | 181 | &dot_receiver, |
98 | "box", | 182 | "dbg", |
99 | "Box::new(expr)", | 183 | "dbg!(expr)", |
100 | &format!("Box::new({})", receiver_text), | 184 | &format!("dbg!({})", receiver_text), |
101 | ) | 185 | ) |
102 | .add_to(acc); | 186 | .add_to(acc); |
103 | 187 | ||
@@ -105,9 +189,9 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { | |||
105 | ctx, | 189 | ctx, |
106 | cap, | 190 | cap, |
107 | &dot_receiver, | 191 | &dot_receiver, |
108 | "dbg", | 192 | "call", |
109 | "dbg!(expr)", | 193 | "function(expr)", |
110 | &format!("dbg!({})", receiver_text), | 194 | &format!("${{1}}({})", receiver_text), |
111 | ) | 195 | ) |
112 | .add_to(acc); | 196 | .add_to(acc); |
113 | } | 197 | } |
@@ -182,6 +266,13 @@ mod tests { | |||
182 | detail: "Box::new(expr)", | 266 | detail: "Box::new(expr)", |
183 | }, | 267 | }, |
184 | CompletionItem { | 268 | CompletionItem { |
269 | label: "call", | ||
270 | source_range: 89..89, | ||
271 | delete: 85..89, | ||
272 | insert: "${1}(bar)", | ||
273 | detail: "function(expr)", | ||
274 | }, | ||
275 | CompletionItem { | ||
185 | label: "dbg", | 276 | label: "dbg", |
186 | source_range: 89..89, | 277 | source_range: 89..89, |
187 | delete: 85..89, | 278 | delete: 85..89, |
@@ -236,6 +327,178 @@ mod tests { | |||
236 | } | 327 | } |
237 | 328 | ||
238 | #[test] | 329 | #[test] |
330 | fn postfix_completion_works_for_option() { | ||
331 | assert_debug_snapshot!( | ||
332 | do_postfix_completion( | ||
333 | r#" | ||
334 | enum Option<T> { | ||
335 | Some(T), | ||
336 | None, | ||
337 | } | ||
338 | |||
339 | fn main() { | ||
340 | let bar = Option::Some(true); | ||
341 | bar.<|> | ||
342 | } | ||
343 | "#, | ||
344 | ), | ||
345 | @r###" | ||
346 | [ | ||
347 | CompletionItem { | ||
348 | label: "box", | ||
349 | source_range: 210..210, | ||
350 | delete: 206..210, | ||
351 | insert: "Box::new(bar)", | ||
352 | detail: "Box::new(expr)", | ||
353 | }, | ||
354 | CompletionItem { | ||
355 | label: "call", | ||
356 | source_range: 210..210, | ||
357 | delete: 206..210, | ||
358 | insert: "${1}(bar)", | ||
359 | detail: "function(expr)", | ||
360 | }, | ||
361 | CompletionItem { | ||
362 | label: "dbg", | ||
363 | source_range: 210..210, | ||
364 | delete: 206..210, | ||
365 | insert: "dbg!(bar)", | ||
366 | detail: "dbg!(expr)", | ||
367 | }, | ||
368 | CompletionItem { | ||
369 | label: "ifl", | ||
370 | source_range: 210..210, | ||
371 | delete: 206..210, | ||
372 | insert: "if let Some($1) = bar {\n $0\n}", | ||
373 | detail: "if let Some {}", | ||
374 | }, | ||
375 | CompletionItem { | ||
376 | label: "match", | ||
377 | source_range: 210..210, | ||
378 | delete: 206..210, | ||
379 | insert: "match bar {\n Some(${1:_}) => {$2\\},\n None => {$0\\},\n}", | ||
380 | detail: "match expr {}", | ||
381 | }, | ||
382 | CompletionItem { | ||
383 | label: "not", | ||
384 | source_range: 210..210, | ||
385 | delete: 206..210, | ||
386 | insert: "!bar", | ||
387 | detail: "!expr", | ||
388 | }, | ||
389 | CompletionItem { | ||
390 | label: "ref", | ||
391 | source_range: 210..210, | ||
392 | delete: 206..210, | ||
393 | insert: "&bar", | ||
394 | detail: "&expr", | ||
395 | }, | ||
396 | CompletionItem { | ||
397 | label: "refm", | ||
398 | source_range: 210..210, | ||
399 | delete: 206..210, | ||
400 | insert: "&mut bar", | ||
401 | detail: "&mut expr", | ||
402 | }, | ||
403 | CompletionItem { | ||
404 | label: "while", | ||
405 | source_range: 210..210, | ||
406 | delete: 206..210, | ||
407 | insert: "while let Some($1) = bar {\n $0\n}", | ||
408 | detail: "while let Some {}", | ||
409 | }, | ||
410 | ] | ||
411 | "### | ||
412 | ); | ||
413 | } | ||
414 | |||
415 | #[test] | ||
416 | fn postfix_completion_works_for_result() { | ||
417 | assert_debug_snapshot!( | ||
418 | do_postfix_completion( | ||
419 | r#" | ||
420 | enum Result<T, E> { | ||
421 | Ok(T), | ||
422 | Err(E), | ||
423 | } | ||
424 | |||
425 | fn main() { | ||
426 | let bar = Result::Ok(true); | ||
427 | bar.<|> | ||
428 | } | ||
429 | "#, | ||
430 | ), | ||
431 | @r###" | ||
432 | [ | ||
433 | CompletionItem { | ||
434 | label: "box", | ||
435 | source_range: 211..211, | ||
436 | delete: 207..211, | ||
437 | insert: "Box::new(bar)", | ||
438 | detail: "Box::new(expr)", | ||
439 | }, | ||
440 | CompletionItem { | ||
441 | label: "call", | ||
442 | source_range: 211..211, | ||
443 | delete: 207..211, | ||
444 | insert: "${1}(bar)", | ||
445 | detail: "function(expr)", | ||
446 | }, | ||
447 | CompletionItem { | ||
448 | label: "dbg", | ||
449 | source_range: 211..211, | ||
450 | delete: 207..211, | ||
451 | insert: "dbg!(bar)", | ||
452 | detail: "dbg!(expr)", | ||
453 | }, | ||
454 | CompletionItem { | ||
455 | label: "ifl", | ||
456 | source_range: 211..211, | ||
457 | delete: 207..211, | ||
458 | insert: "if let Ok($1) = bar {\n $0\n}", | ||
459 | detail: "if let Ok {}", | ||
460 | }, | ||
461 | CompletionItem { | ||
462 | label: "match", | ||
463 | source_range: 211..211, | ||
464 | delete: 207..211, | ||
465 | insert: "match bar {\n Ok(${1:_}) => {$2\\},\n Err(${3:_}) => {$0\\},\n}", | ||
466 | detail: "match expr {}", | ||
467 | }, | ||
468 | CompletionItem { | ||
469 | label: "not", | ||
470 | source_range: 211..211, | ||
471 | delete: 207..211, | ||
472 | insert: "!bar", | ||
473 | detail: "!expr", | ||
474 | }, | ||
475 | CompletionItem { | ||
476 | label: "ref", | ||
477 | source_range: 211..211, | ||
478 | delete: 207..211, | ||
479 | insert: "&bar", | ||
480 | detail: "&expr", | ||
481 | }, | ||
482 | CompletionItem { | ||
483 | label: "refm", | ||
484 | source_range: 211..211, | ||
485 | delete: 207..211, | ||
486 | insert: "&mut bar", | ||
487 | detail: "&mut expr", | ||
488 | }, | ||
489 | CompletionItem { | ||
490 | label: "while", | ||
491 | source_range: 211..211, | ||
492 | delete: 207..211, | ||
493 | insert: "while let Ok($1) = bar {\n $0\n}", | ||
494 | detail: "while let Ok {}", | ||
495 | }, | ||
496 | ] | ||
497 | "### | ||
498 | ); | ||
499 | } | ||
500 | |||
501 | #[test] | ||
239 | fn some_postfix_completions_ignored() { | 502 | fn some_postfix_completions_ignored() { |
240 | assert_debug_snapshot!( | 503 | assert_debug_snapshot!( |
241 | do_postfix_completion( | 504 | do_postfix_completion( |
@@ -256,6 +519,13 @@ mod tests { | |||
256 | detail: "Box::new(expr)", | 519 | detail: "Box::new(expr)", |
257 | }, | 520 | }, |
258 | CompletionItem { | 521 | CompletionItem { |
522 | label: "call", | ||
523 | source_range: 91..91, | ||
524 | delete: 87..91, | ||
525 | insert: "${1}(bar)", | ||
526 | detail: "function(expr)", | ||
527 | }, | ||
528 | CompletionItem { | ||
259 | label: "dbg", | 529 | label: "dbg", |
260 | source_range: 91..91, | 530 | source_range: 91..91, |
261 | delete: 87..91, | 531 | delete: 87..91, |
@@ -315,6 +585,13 @@ mod tests { | |||
315 | detail: "Box::new(expr)", | 585 | detail: "Box::new(expr)", |
316 | }, | 586 | }, |
317 | CompletionItem { | 587 | CompletionItem { |
588 | label: "call", | ||
589 | source_range: 52..52, | ||
590 | delete: 49..52, | ||
591 | insert: "${1}(42)", | ||
592 | detail: "function(expr)", | ||
593 | }, | ||
594 | CompletionItem { | ||
318 | label: "dbg", | 595 | label: "dbg", |
319 | source_range: 52..52, | 596 | source_range: 52..52, |
320 | delete: 49..52, | 597 | delete: 49..52, |
@@ -376,6 +653,13 @@ mod tests { | |||
376 | detail: "Box::new(expr)", | 653 | detail: "Box::new(expr)", |
377 | }, | 654 | }, |
378 | CompletionItem { | 655 | CompletionItem { |
656 | label: "call", | ||
657 | source_range: 149..150, | ||
658 | delete: 145..150, | ||
659 | insert: "${1}(bar)", | ||
660 | detail: "function(expr)", | ||
661 | }, | ||
662 | CompletionItem { | ||
379 | label: "dbg", | 663 | label: "dbg", |
380 | source_range: 149..150, | 664 | source_range: 149..150, |
381 | delete: 145..150, | 665 | delete: 145..150, |
@@ -435,6 +719,13 @@ mod tests { | |||
435 | detail: "Box::new(expr)", | 719 | detail: "Box::new(expr)", |
436 | }, | 720 | }, |
437 | CompletionItem { | 721 | CompletionItem { |
722 | label: "call", | ||
723 | source_range: 56..56, | ||
724 | delete: 49..56, | ||
725 | insert: "${1}(&&&&42)", | ||
726 | detail: "function(expr)", | ||
727 | }, | ||
728 | CompletionItem { | ||
438 | label: "dbg", | 729 | label: "dbg", |
439 | source_range: 56..56, | 730 | source_range: 56..56, |
440 | delete: 49..56, | 731 | delete: 49..56, |
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..21c9316e6 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, |
@@ -49,50 +49,53 @@ use crate::{ | |||
49 | pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext) { | 49 | 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 => get_missing_assoc_items(&ctx.sema, &impl_def) |
53 | get_missing_impl_items(&ctx.sema, &impl_def).iter().for_each(|item| match item { | 53 | .into_iter() |
54 | .for_each(|item| match item { | ||
54 | hir::AssocItem::Function(fn_item) => { | 55 | hir::AssocItem::Function(fn_item) => { |
55 | add_function_impl(&trigger, acc, ctx, &fn_item) | 56 | add_function_impl(&trigger, acc, ctx, fn_item) |
56 | } | 57 | } |
57 | hir::AssocItem::TypeAlias(type_item) => { | 58 | hir::AssocItem::TypeAlias(type_item) => { |
58 | add_type_alias_impl(&trigger, acc, ctx, &type_item) | 59 | add_type_alias_impl(&trigger, acc, ctx, type_item) |
59 | } | 60 | } |
60 | hir::AssocItem::Const(const_item) => { | 61 | hir::AssocItem::Const(const_item) => { |
61 | add_const_impl(&trigger, acc, ctx, &const_item) | 62 | add_const_impl(&trigger, acc, ctx, const_item) |
62 | } | 63 | } |
63 | }) | 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 get_missing_assoc_items(&ctx.sema, &impl_def) |
68 | |item| match item { | 68 | .into_iter() |
69 | .filter_map(|item| match item { | ||
69 | hir::AssocItem::Function(fn_item) => Some(fn_item), | 70 | hir::AssocItem::Function(fn_item) => Some(fn_item), |
70 | _ => None, | 71 | _ => None, |
71 | }, | 72 | }) |
72 | ) { | 73 | { |
73 | add_function_impl(&trigger, acc, ctx, &missing_fn); | 74 | add_function_impl(&trigger, acc, ctx, missing_fn); |
74 | } | 75 | } |
75 | } | 76 | } |
76 | 77 | ||
77 | SyntaxKind::TYPE_ALIAS_DEF => { | 78 | SyntaxKind::TYPE_ALIAS_DEF => { |
78 | for missing_fn in get_missing_impl_items(&ctx.sema, &impl_def).iter().filter_map( | 79 | for missing_fn in get_missing_assoc_items(&ctx.sema, &impl_def) |
79 | |item| match item { | 80 | .into_iter() |
81 | .filter_map(|item| match item { | ||
80 | hir::AssocItem::TypeAlias(type_item) => Some(type_item), | 82 | hir::AssocItem::TypeAlias(type_item) => Some(type_item), |
81 | _ => None, | 83 | _ => None, |
82 | }, | 84 | }) |
83 | ) { | 85 | { |
84 | add_type_alias_impl(&trigger, acc, ctx, &missing_fn); | 86 | add_type_alias_impl(&trigger, acc, ctx, missing_fn); |
85 | } | 87 | } |
86 | } | 88 | } |
87 | 89 | ||
88 | SyntaxKind::CONST_DEF => { | 90 | SyntaxKind::CONST_DEF => { |
89 | for missing_fn in get_missing_impl_items(&ctx.sema, &impl_def).iter().filter_map( | 91 | for missing_fn in get_missing_assoc_items(&ctx.sema, &impl_def) |
90 | |item| match item { | 92 | .into_iter() |
93 | .filter_map(|item| match item { | ||
91 | hir::AssocItem::Const(const_item) => Some(const_item), | 94 | hir::AssocItem::Const(const_item) => Some(const_item), |
92 | _ => None, | 95 | _ => None, |
93 | }, | 96 | }) |
94 | ) { | 97 | { |
95 | add_const_impl(&trigger, acc, ctx, &missing_fn); | 98 | add_const_impl(&trigger, acc, ctx, missing_fn); |
96 | } | 99 | } |
97 | } | 100 | } |
98 | 101 | ||
@@ -120,9 +123,9 @@ fn add_function_impl( | |||
120 | fn_def_node: &SyntaxNode, | 123 | fn_def_node: &SyntaxNode, |
121 | acc: &mut Completions, | 124 | acc: &mut Completions, |
122 | ctx: &CompletionContext, | 125 | ctx: &CompletionContext, |
123 | func: &hir::Function, | 126 | func: hir::Function, |
124 | ) { | 127 | ) { |
125 | let signature = FunctionSignature::from_hir(ctx.db, *func); | 128 | let signature = FunctionSignature::from_hir(ctx.db, func); |
126 | 129 | ||
127 | let fn_name = func.name(ctx.db).to_string(); | 130 | let fn_name = func.name(ctx.db).to_string(); |
128 | 131 | ||
@@ -161,7 +164,7 @@ fn add_type_alias_impl( | |||
161 | type_def_node: &SyntaxNode, | 164 | type_def_node: &SyntaxNode, |
162 | acc: &mut Completions, | 165 | acc: &mut Completions, |
163 | ctx: &CompletionContext, | 166 | ctx: &CompletionContext, |
164 | type_alias: &hir::TypeAlias, | 167 | type_alias: hir::TypeAlias, |
165 | ) { | 168 | ) { |
166 | let alias_name = type_alias.name(ctx.db).to_string(); | 169 | let alias_name = type_alias.name(ctx.db).to_string(); |
167 | 170 | ||
@@ -181,7 +184,7 @@ fn add_const_impl( | |||
181 | const_def_node: &SyntaxNode, | 184 | const_def_node: &SyntaxNode, |
182 | acc: &mut Completions, | 185 | acc: &mut Completions, |
183 | ctx: &CompletionContext, | 186 | ctx: &CompletionContext, |
184 | const_: &hir::Const, | 187 | const_: hir::Const, |
185 | ) { | 188 | ) { |
186 | let const_name = const_.name(ctx.db).map(|n| n.to_string()); | 189 | let const_name = const_.name(ctx.db).map(|n| n.to_string()); |
187 | 190 | ||
diff --git a/crates/ra_ide/src/completion/complete_unqualified_path.rs b/crates/ra_ide/src/completion/complete_unqualified_path.rs index a6a5568de..68032c37e 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#" |
@@ -295,6 +298,42 @@ mod tests { | |||
295 | } | 298 | } |
296 | 299 | ||
297 | #[test] | 300 | #[test] |
301 | fn completes_bindings_from_for_with_in_prefix() { | ||
302 | mark::check!(completes_bindings_from_for_with_in_prefix); | ||
303 | assert_debug_snapshot!( | ||
304 | do_reference_completion( | ||
305 | r" | ||
306 | fn test() { | ||
307 | for index in &[1, 2, 3] { | ||
308 | let t = in<|> | ||
309 | } | ||
310 | } | ||
311 | " | ||
312 | ), | ||
313 | @r###" | ||
314 | [ | ||
315 | CompletionItem { | ||
316 | label: "index", | ||
317 | source_range: 107..107, | ||
318 | delete: 107..107, | ||
319 | insert: "index", | ||
320 | kind: Binding, | ||
321 | }, | ||
322 | CompletionItem { | ||
323 | label: "test()", | ||
324 | source_range: 107..107, | ||
325 | delete: 107..107, | ||
326 | insert: "test()$0", | ||
327 | kind: Function, | ||
328 | lookup: "test", | ||
329 | detail: "fn test()", | ||
330 | }, | ||
331 | ] | ||
332 | "### | ||
333 | ); | ||
334 | } | ||
335 | |||
336 | #[test] | ||
298 | fn completes_generic_params() { | 337 | fn completes_generic_params() { |
299 | assert_debug_snapshot!( | 338 | assert_debug_snapshot!( |
300 | do_reference_completion( | 339 | do_reference_completion( |
@@ -1369,4 +1408,18 @@ mod tests { | |||
1369 | "### | 1408 | "### |
1370 | ) | 1409 | ) |
1371 | } | 1410 | } |
1411 | |||
1412 | #[test] | ||
1413 | fn dont_complete_attr() { | ||
1414 | assert_debug_snapshot!( | ||
1415 | do_reference_completion( | ||
1416 | r" | ||
1417 | struct Foo; | ||
1418 | #[<|>] | ||
1419 | fn f() {} | ||
1420 | " | ||
1421 | ), | ||
1422 | @r###"[]"### | ||
1423 | ) | ||
1424 | } | ||
1372 | } | 1425 | } |
diff --git a/crates/ra_ide/src/completion/completion_context.rs b/crates/ra_ide/src/completion/completion_context.rs index 118fceb2e..c4646b727 100644 --- a/crates/ra_ide/src/completion/completion_context.rs +++ b/crates/ra_ide/src/completion/completion_context.rs | |||
@@ -9,9 +9,10 @@ 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 | use test_utils::mark; | ||
15 | 16 | ||
16 | /// `CompletionContext` is created early during completion to figure out, where | 17 | /// `CompletionContext` is created early during completion to figure out, where |
17 | /// exactly is the cursor, syntax-wise. | 18 | /// exactly is the cursor, syntax-wise. |
@@ -34,7 +35,7 @@ pub(crate) struct CompletionContext<'a> { | |||
34 | pub(super) record_pat_syntax: Option<ast::RecordPat>, | 35 | pub(super) record_pat_syntax: Option<ast::RecordPat>, |
35 | pub(super) record_field_syntax: Option<ast::RecordField>, | 36 | pub(super) record_field_syntax: Option<ast::RecordField>, |
36 | pub(super) impl_def: Option<ast::ImplDef>, | 37 | pub(super) impl_def: Option<ast::ImplDef>, |
37 | /// FIXME: `ActiveParameter` is string-based, which is very wrong | 38 | /// FIXME: `ActiveParameter` is string-based, which is very very wrong |
38 | pub(super) active_parameter: Option<ActiveParameter>, | 39 | pub(super) active_parameter: Option<ActiveParameter>, |
39 | pub(super) is_param: bool, | 40 | pub(super) is_param: bool, |
40 | /// If a name-binding or reference to a const in a pattern. | 41 | /// If a name-binding or reference to a const in a pattern. |
@@ -58,7 +59,7 @@ pub(crate) struct CompletionContext<'a> { | |||
58 | pub(super) is_macro_call: bool, | 59 | pub(super) is_macro_call: bool, |
59 | pub(super) is_path_type: bool, | 60 | pub(super) is_path_type: bool, |
60 | pub(super) has_type_args: bool, | 61 | pub(super) has_type_args: bool, |
61 | pub(super) is_attribute: bool, | 62 | pub(super) attribute_under_caret: Option<ast::Attr>, |
62 | } | 63 | } |
63 | 64 | ||
64 | impl<'a> CompletionContext<'a> { | 65 | impl<'a> CompletionContext<'a> { |
@@ -76,7 +77,7 @@ impl<'a> CompletionContext<'a> { | |||
76 | // actual completion. | 77 | // actual completion. |
77 | let file_with_fake_ident = { | 78 | let file_with_fake_ident = { |
78 | let parse = db.parse(position.file_id); | 79 | let parse = db.parse(position.file_id); |
79 | let edit = AtomTextEdit::insert(position.offset, "intellijRulezz".to_string()); | 80 | let edit = Indel::insert(position.offset, "intellijRulezz".to_string()); |
80 | parse.reparse(&edit).tree() | 81 | parse.reparse(&edit).tree() |
81 | }; | 82 | }; |
82 | let fake_ident_token = | 83 | let fake_ident_token = |
@@ -116,7 +117,7 @@ impl<'a> CompletionContext<'a> { | |||
116 | is_path_type: false, | 117 | is_path_type: false, |
117 | has_type_args: false, | 118 | has_type_args: false, |
118 | dot_receiver_is_ambiguous_float_literal: false, | 119 | dot_receiver_is_ambiguous_float_literal: false, |
119 | is_attribute: false, | 120 | attribute_under_caret: None, |
120 | }; | 121 | }; |
121 | 122 | ||
122 | let mut original_file = original_file.syntax().clone(); | 123 | let mut original_file = original_file.syntax().clone(); |
@@ -169,7 +170,17 @@ impl<'a> CompletionContext<'a> { | |||
169 | match self.token.kind() { | 170 | match self.token.kind() { |
170 | // workaroud when completion is triggered by trigger characters. | 171 | // workaroud when completion is triggered by trigger characters. |
171 | IDENT => self.original_token.text_range(), | 172 | IDENT => self.original_token.text_range(), |
172 | _ => TextRange::empty(self.offset), | 173 | _ => { |
174 | // If we haven't characters between keyword and our cursor we take the keyword start range to edit | ||
175 | if self.token.kind().is_keyword() | ||
176 | && self.offset == self.original_token.text_range().end() | ||
177 | { | ||
178 | mark::hit!(completes_bindings_from_for_with_in_prefix); | ||
179 | TextRange::empty(self.original_token.text_range().start()) | ||
180 | } else { | ||
181 | TextRange::empty(self.offset) | ||
182 | } | ||
183 | } | ||
173 | } | 184 | } |
174 | } | 185 | } |
175 | 186 | ||
@@ -200,6 +211,7 @@ impl<'a> CompletionContext<'a> { | |||
200 | Some(ty) | 211 | Some(ty) |
201 | }) | 212 | }) |
202 | .flatten(); | 213 | .flatten(); |
214 | self.attribute_under_caret = find_node_at_offset(&file_with_fake_ident, offset); | ||
203 | 215 | ||
204 | // First, let's try to complete a reference to some declaration. | 216 | // 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) { | 217 | if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(&file_with_fake_ident, offset) { |
@@ -318,7 +330,6 @@ impl<'a> CompletionContext<'a> { | |||
318 | .and_then(|it| it.syntax().parent().and_then(ast::CallExpr::cast)) | 330 | .and_then(|it| it.syntax().parent().and_then(ast::CallExpr::cast)) |
319 | .is_some(); | 331 | .is_some(); |
320 | self.is_macro_call = path.syntax().parent().and_then(ast::MacroCall::cast).is_some(); | 332 | 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 | 333 | ||
323 | self.is_path_type = path.syntax().parent().and_then(ast::PathType::cast).is_some(); | 334 | 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(); | 335 | self.has_type_args = segment.type_arg_list().is_some(); |
@@ -344,7 +355,7 @@ impl<'a> CompletionContext<'a> { | |||
344 | stmt.syntax().text_range() == name_ref.syntax().text_range(), | 355 | stmt.syntax().text_range() == name_ref.syntax().text_range(), |
345 | ); | 356 | ); |
346 | } | 357 | } |
347 | if let Some(block) = ast::Block::cast(node) { | 358 | if let Some(block) = ast::BlockExpr::cast(node) { |
348 | return Some( | 359 | return Some( |
349 | block.expr().map(|e| e.syntax().text_range()) | 360 | block.expr().map(|e| e.syntax().text_range()) |
350 | == Some(name_ref.syntax().text_range()), | 361 | == 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..61565c84f 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 | }; |
@@ -206,7 +211,7 @@ impl Completions { | |||
206 | .parameter_names | 211 | .parameter_names |
207 | .iter() | 212 | .iter() |
208 | .skip(if function_signature.has_self_param { 1 } else { 0 }) | 213 | .skip(if function_signature.has_self_param { 1 } else { 0 }) |
209 | .cloned() | 214 | .map(|name| name.trim_start_matches('_').into()) |
210 | .collect(); | 215 | .collect(); |
211 | 216 | ||
212 | builder = builder.add_call_parens(ctx, name, Params::Named(params)); | 217 | builder = builder.add_call_parens(ctx, name, Params::Named(params)); |
@@ -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" |
@@ -666,6 +672,37 @@ mod tests { | |||
666 | assert_debug_snapshot!( | 672 | assert_debug_snapshot!( |
667 | do_reference_completion( | 673 | do_reference_completion( |
668 | r" | 674 | r" |
675 | fn with_ignored_args(_foo: i32, ___bar: bool, ho_ge_: String) {} | ||
676 | fn main() { with_<|> } | ||
677 | " | ||
678 | ), | ||
679 | @r###" | ||
680 | [ | ||
681 | CompletionItem { | ||
682 | label: "main()", | ||
683 | source_range: 110..115, | ||
684 | delete: 110..115, | ||
685 | insert: "main()$0", | ||
686 | kind: Function, | ||
687 | lookup: "main", | ||
688 | detail: "fn main()", | ||
689 | }, | ||
690 | CompletionItem { | ||
691 | label: "with_ignored_args(…)", | ||
692 | source_range: 110..115, | ||
693 | delete: 110..115, | ||
694 | insert: "with_ignored_args(${1:foo}, ${2:bar}, ${3:ho_ge_})$0", | ||
695 | kind: Function, | ||
696 | lookup: "with_ignored_args", | ||
697 | detail: "fn with_ignored_args(_foo: i32, ___bar: bool, ho_ge_: String)", | ||
698 | trigger_call_info: true, | ||
699 | }, | ||
700 | ] | ||
701 | "### | ||
702 | ); | ||
703 | assert_debug_snapshot!( | ||
704 | do_reference_completion( | ||
705 | r" | ||
669 | struct S {} | 706 | struct S {} |
670 | impl S { | 707 | impl S { |
671 | fn foo(&self) {} | 708 | fn foo(&self) {} |
@@ -689,6 +726,33 @@ mod tests { | |||
689 | ] | 726 | ] |
690 | "### | 727 | "### |
691 | ); | 728 | ); |
729 | assert_debug_snapshot!( | ||
730 | do_reference_completion( | ||
731 | r" | ||
732 | struct S {} | ||
733 | impl S { | ||
734 | fn foo_ignored_args(&self, _a: bool, b: i32) {} | ||
735 | } | ||
736 | fn bar(s: &S) { | ||
737 | s.f<|> | ||
738 | } | ||
739 | " | ||
740 | ), | ||
741 | @r###" | ||
742 | [ | ||
743 | CompletionItem { | ||
744 | label: "foo_ignored_args(…)", | ||
745 | source_range: 194..195, | ||
746 | delete: 194..195, | ||
747 | insert: "foo_ignored_args(${1:a}, ${2:b})$0", | ||
748 | kind: Method, | ||
749 | lookup: "foo_ignored_args", | ||
750 | detail: "fn foo_ignored_args(&self, _a: bool, b: i32)", | ||
751 | trigger_call_info: true, | ||
752 | }, | ||
753 | ] | ||
754 | "### | ||
755 | ); | ||
692 | } | 756 | } |
693 | 757 | ||
694 | #[test] | 758 | #[test] |
@@ -986,7 +1050,7 @@ mod tests { | |||
986 | 1050 | ||
987 | #[test] | 1051 | #[test] |
988 | fn inserts_angle_brackets_for_generics() { | 1052 | fn inserts_angle_brackets_for_generics() { |
989 | covers!(inserts_angle_brackets_for_generics); | 1053 | mark::check!(inserts_angle_brackets_for_generics); |
990 | assert_debug_snapshot!( | 1054 | assert_debug_snapshot!( |
991 | do_reference_completion( | 1055 | do_reference_completion( |
992 | r" | 1056 | r" |
@@ -1109,7 +1173,7 @@ mod tests { | |||
1109 | 1173 | ||
1110 | #[test] | 1174 | #[test] |
1111 | fn dont_insert_macro_call_parens_unncessary() { | 1175 | fn dont_insert_macro_call_parens_unncessary() { |
1112 | covers!(dont_insert_macro_call_parens_unncessary); | 1176 | mark::check!(dont_insert_macro_call_parens_unncessary); |
1113 | assert_debug_snapshot!( | 1177 | assert_debug_snapshot!( |
1114 | do_reference_completion( | 1178 | do_reference_completion( |
1115 | r" | 1179 | r" |
@@ -1175,7 +1239,7 @@ mod tests { | |||
1175 | 1239 | ||
1176 | #[test] | 1240 | #[test] |
1177 | fn test_struct_field_completion_in_func_call() { | 1241 | fn test_struct_field_completion_in_func_call() { |
1178 | covers!(test_struct_field_completion_in_func_call); | 1242 | mark::check!(test_struct_field_completion_in_func_call); |
1179 | assert_debug_snapshot!( | 1243 | assert_debug_snapshot!( |
1180 | do_reference_completion( | 1244 | do_reference_completion( |
1181 | r" | 1245 | r" |
@@ -1265,7 +1329,7 @@ mod tests { | |||
1265 | 1329 | ||
1266 | #[test] | 1330 | #[test] |
1267 | fn test_struct_field_completion_in_record_lit() { | 1331 | fn test_struct_field_completion_in_record_lit() { |
1268 | covers!(test_struct_field_completion_in_record_lit); | 1332 | mark::check!(test_struct_field_completion_in_record_lit); |
1269 | assert_debug_snapshot!( | 1333 | assert_debug_snapshot!( |
1270 | do_reference_completion( | 1334 | do_reference_completion( |
1271 | r" | 1335 | r" |
@@ -1405,4 +1469,48 @@ mod tests { | |||
1405 | "### | 1469 | "### |
1406 | ); | 1470 | ); |
1407 | } | 1471 | } |
1472 | |||
1473 | #[test] | ||
1474 | fn prioritize_exact_ref_match() { | ||
1475 | assert_debug_snapshot!( | ||
1476 | do_reference_completion( | ||
1477 | r" | ||
1478 | struct WorldSnapshot { _f: () }; | ||
1479 | fn go(world: &WorldSnapshot) { | ||
1480 | go(w<|>) | ||
1481 | } | ||
1482 | ", | ||
1483 | ), | ||
1484 | @r###" | ||
1485 | [ | ||
1486 | CompletionItem { | ||
1487 | label: "WorldSnapshot", | ||
1488 | source_range: 132..133, | ||
1489 | delete: 132..133, | ||
1490 | insert: "WorldSnapshot", | ||
1491 | kind: Struct, | ||
1492 | }, | ||
1493 | CompletionItem { | ||
1494 | label: "go(…)", | ||
1495 | source_range: 132..133, | ||
1496 | delete: 132..133, | ||
1497 | insert: "go(${1:world})$0", | ||
1498 | kind: Function, | ||
1499 | lookup: "go", | ||
1500 | detail: "fn go(world: &WorldSnapshot)", | ||
1501 | trigger_call_info: true, | ||
1502 | }, | ||
1503 | CompletionItem { | ||
1504 | label: "world", | ||
1505 | source_range: 132..133, | ||
1506 | delete: 132..133, | ||
1507 | insert: "world", | ||
1508 | kind: Binding, | ||
1509 | detail: "&WorldSnapshot", | ||
1510 | score: TypeAndNameMatch, | ||
1511 | }, | ||
1512 | ] | ||
1513 | "### | ||
1514 | ); | ||
1515 | } | ||
1408 | } | 1516 | } |
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..15dc50cf1 100644 --- a/crates/ra_ide/src/diagnostics.rs +++ b/crates/ra_ide/src/diagnostics.rs | |||
@@ -21,7 +21,7 @@ use ra_syntax::{ | |||
21 | }; | 21 | }; |
22 | use ra_text_edit::{TextEdit, TextEditBuilder}; | 22 | use ra_text_edit::{TextEdit, TextEditBuilder}; |
23 | 23 | ||
24 | use crate::{Diagnostic, FileId, FileSystemEdit, SourceChange, SourceFileEdit}; | 24 | use crate::{Diagnostic, FileId, FileSystemEdit, Fix, SourceChange, SourceFileEdit}; |
25 | 25 | ||
26 | #[derive(Debug, Copy, Clone)] | 26 | #[derive(Debug, Copy, Clone)] |
27 | pub enum Severity { | 27 | pub enum Severity { |
@@ -63,8 +63,8 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic> | |||
63 | .parent() | 63 | .parent() |
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 fix = |
67 | let fix = SourceChange::file_system_edit("create module", create_file); | 67 | Fix::new("Create module", FileSystemEdit::CreateFile { source_root, path }.into()); |
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(), |
@@ -88,14 +88,12 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic> | |||
88 | field_list = field_list.append_field(&field); | 88 | field_list = field_list.append_field(&field); |
89 | } | 89 | } |
90 | 90 | ||
91 | let mut builder = TextEditBuilder::default(); | 91 | let edit = { |
92 | algo::diff(&d.ast(db).syntax(), &field_list.syntax()).into_text_edit(&mut builder); | 92 | let mut builder = TextEditBuilder::default(); |
93 | 93 | algo::diff(&d.ast(db).syntax(), &field_list.syntax()).into_text_edit(&mut builder); | |
94 | Some(SourceChange::source_file_edit_from( | 94 | builder.finish() |
95 | "fill struct fields", | 95 | }; |
96 | file_id, | 96 | Some(Fix::new("Fill struct fields", SourceFileEdit { file_id, edit }.into())) |
97 | builder.finish(), | ||
98 | )) | ||
99 | }; | 97 | }; |
100 | 98 | ||
101 | res.borrow_mut().push(Diagnostic { | 99 | res.borrow_mut().push(Diagnostic { |
@@ -117,7 +115,8 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic> | |||
117 | let node = d.ast(db); | 115 | let node = d.ast(db); |
118 | let replacement = format!("Ok({})", node.syntax()); | 116 | let replacement = format!("Ok({})", node.syntax()); |
119 | let edit = TextEdit::replace(node.syntax().text_range(), replacement); | 117 | let edit = TextEdit::replace(node.syntax().text_range(), replacement); |
120 | let fix = SourceChange::source_file_edit_from("wrap with ok", file_id, edit); | 118 | let source_change = SourceChange::source_file_edit_from(file_id, edit); |
119 | let fix = Fix::new("Wrap with ok", source_change); | ||
121 | res.borrow_mut().push(Diagnostic { | 120 | res.borrow_mut().push(Diagnostic { |
122 | range: sema.diagnostics_range(d).range, | 121 | range: sema.diagnostics_range(d).range, |
123 | message: d.message(), | 122 | message: d.message(), |
@@ -154,9 +153,9 @@ fn check_unnecessary_braces_in_use_statement( | |||
154 | range, | 153 | range, |
155 | message: "Unnecessary braces in use statement".to_string(), | 154 | message: "Unnecessary braces in use statement".to_string(), |
156 | severity: Severity::WeakWarning, | 155 | severity: Severity::WeakWarning, |
157 | fix: Some(SourceChange::source_file_edit( | 156 | fix: Some(Fix::new( |
158 | "Remove unnecessary braces", | 157 | "Remove unnecessary braces", |
159 | SourceFileEdit { file_id, edit }, | 158 | SourceFileEdit { file_id, edit }.into(), |
160 | )), | 159 | )), |
161 | }); | 160 | }); |
162 | } | 161 | } |
@@ -198,9 +197,9 @@ fn check_struct_shorthand_initialization( | |||
198 | range: record_field.syntax().text_range(), | 197 | range: record_field.syntax().text_range(), |
199 | message: "Shorthand struct initialization".to_string(), | 198 | message: "Shorthand struct initialization".to_string(), |
200 | severity: Severity::WeakWarning, | 199 | severity: Severity::WeakWarning, |
201 | fix: Some(SourceChange::source_file_edit( | 200 | fix: Some(Fix::new( |
202 | "use struct shorthand initialization", | 201 | "Use struct shorthand initialization", |
203 | SourceFileEdit { file_id, edit }, | 202 | SourceFileEdit { file_id, edit }.into(), |
204 | )), | 203 | )), |
205 | }); | 204 | }); |
206 | } | 205 | } |
@@ -240,8 +239,12 @@ mod tests { | |||
240 | let diagnostic = | 239 | let diagnostic = |
241 | diagnostics.pop().unwrap_or_else(|| panic!("no diagnostics for:\n{}\n", before)); | 240 | diagnostics.pop().unwrap_or_else(|| panic!("no diagnostics for:\n{}\n", before)); |
242 | let mut fix = diagnostic.fix.unwrap(); | 241 | let mut fix = diagnostic.fix.unwrap(); |
243 | let edit = fix.source_file_edits.pop().unwrap().edit; | 242 | let edit = fix.source_change.source_file_edits.pop().unwrap().edit; |
244 | let actual = edit.apply(&before); | 243 | let actual = { |
244 | let mut actual = before.to_string(); | ||
245 | edit.apply(&mut actual); | ||
246 | actual | ||
247 | }; | ||
245 | assert_eq_text!(after, &actual); | 248 | assert_eq_text!(after, &actual); |
246 | } | 249 | } |
247 | 250 | ||
@@ -254,9 +257,13 @@ mod tests { | |||
254 | let (analysis, file_position) = analysis_and_position(fixture); | 257 | let (analysis, file_position) = analysis_and_position(fixture); |
255 | let diagnostic = analysis.diagnostics(file_position.file_id).unwrap().pop().unwrap(); | 258 | let diagnostic = analysis.diagnostics(file_position.file_id).unwrap().pop().unwrap(); |
256 | let mut fix = diagnostic.fix.unwrap(); | 259 | let mut fix = diagnostic.fix.unwrap(); |
257 | let edit = fix.source_file_edits.pop().unwrap().edit; | 260 | let edit = fix.source_change.source_file_edits.pop().unwrap().edit; |
258 | let target_file_contents = analysis.file_text(file_position.file_id).unwrap(); | 261 | let target_file_contents = analysis.file_text(file_position.file_id).unwrap(); |
259 | let actual = edit.apply(&target_file_contents); | 262 | let actual = { |
263 | let mut actual = target_file_contents.to_string(); | ||
264 | edit.apply(&mut actual); | ||
265 | actual | ||
266 | }; | ||
260 | 267 | ||
261 | // Strip indent and empty lines from `after`, to match the behaviour of | 268 | // Strip indent and empty lines from `after`, to match the behaviour of |
262 | // `parse_fixture` called from `analysis_and_position`. | 269 | // `parse_fixture` called from `analysis_and_position`. |
@@ -287,8 +294,12 @@ mod tests { | |||
287 | let (analysis, file_id) = single_file(before); | 294 | let (analysis, file_id) = single_file(before); |
288 | let diagnostic = analysis.diagnostics(file_id).unwrap().pop().unwrap(); | 295 | let diagnostic = analysis.diagnostics(file_id).unwrap().pop().unwrap(); |
289 | let mut fix = diagnostic.fix.unwrap(); | 296 | let mut fix = diagnostic.fix.unwrap(); |
290 | let edit = fix.source_file_edits.pop().unwrap().edit; | 297 | let edit = fix.source_change.source_file_edits.pop().unwrap().edit; |
291 | let actual = edit.apply(&before); | 298 | let actual = { |
299 | let mut actual = before.to_string(); | ||
300 | edit.apply(&mut actual); | ||
301 | actual | ||
302 | }; | ||
292 | assert_eq_text!(after, &actual); | 303 | assert_eq_text!(after, &actual); |
293 | } | 304 | } |
294 | 305 | ||
@@ -604,22 +615,24 @@ mod tests { | |||
604 | Diagnostic { | 615 | Diagnostic { |
605 | message: "unresolved module", | 616 | message: "unresolved module", |
606 | range: 0..8, | 617 | range: 0..8, |
618 | severity: Error, | ||
607 | fix: Some( | 619 | fix: Some( |
608 | SourceChange { | 620 | Fix { |
609 | label: "create module", | 621 | label: "Create module", |
610 | source_file_edits: [], | 622 | source_change: SourceChange { |
611 | file_system_edits: [ | 623 | source_file_edits: [], |
612 | CreateFile { | 624 | file_system_edits: [ |
613 | source_root: SourceRootId( | 625 | CreateFile { |
614 | 0, | 626 | source_root: SourceRootId( |
615 | ), | 627 | 0, |
616 | path: "foo.rs", | 628 | ), |
617 | }, | 629 | path: "foo.rs", |
618 | ], | 630 | }, |
619 | cursor_position: None, | 631 | ], |
632 | is_snippet: false, | ||
633 | }, | ||
620 | }, | 634 | }, |
621 | ), | 635 | ), |
622 | severity: Error, | ||
623 | }, | 636 | }, |
624 | ] | 637 | ] |
625 | "###); | 638 | "###); |
@@ -651,31 +664,33 @@ mod tests { | |||
651 | assert_debug_snapshot!(diagnostics, @r###" | 664 | assert_debug_snapshot!(diagnostics, @r###" |
652 | [ | 665 | [ |
653 | Diagnostic { | 666 | Diagnostic { |
654 | message: "Missing structure fields:\n- b", | 667 | message: "Missing structure fields:\n- b\n", |
655 | range: 224..233, | 668 | range: 224..233, |
669 | severity: Error, | ||
656 | fix: Some( | 670 | fix: Some( |
657 | SourceChange { | 671 | Fix { |
658 | label: "fill struct fields", | 672 | label: "Fill struct fields", |
659 | source_file_edits: [ | 673 | source_change: SourceChange { |
660 | SourceFileEdit { | 674 | source_file_edits: [ |
661 | file_id: FileId( | 675 | SourceFileEdit { |
662 | 1, | 676 | file_id: FileId( |
663 | ), | 677 | 1, |
664 | edit: TextEdit { | 678 | ), |
665 | atoms: [ | 679 | edit: TextEdit { |
666 | AtomTextEdit { | 680 | indels: [ |
667 | delete: 3..9, | 681 | Indel { |
668 | insert: "{a:42, b: ()}", | 682 | insert: "{a:42, b: ()}", |
669 | }, | 683 | delete: 3..9, |
670 | ], | 684 | }, |
685 | ], | ||
686 | }, | ||
671 | }, | 687 | }, |
672 | }, | 688 | ], |
673 | ], | 689 | file_system_edits: [], |
674 | file_system_edits: [], | 690 | is_snippet: false, |
675 | cursor_position: None, | 691 | }, |
676 | }, | 692 | }, |
677 | ), | 693 | ), |
678 | severity: Error, | ||
679 | }, | 694 | }, |
680 | ] | 695 | ] |
681 | "###); | 696 | "###); |
diff --git a/crates/ra_ide/src/display.rs b/crates/ra_ide/src/display.rs index 722092de9..827c094e7 100644 --- a/crates/ra_ide/src/display.rs +++ b/crates/ra_ide/src/display.rs | |||
@@ -79,16 +79,17 @@ pub(crate) fn rust_code_markup_with_doc( | |||
79 | doc: Option<&str>, | 79 | doc: Option<&str>, |
80 | mod_path: Option<&str>, | 80 | mod_path: Option<&str>, |
81 | ) -> String { | 81 | ) -> String { |
82 | let mut buf = "```rust\n".to_owned(); | 82 | let mut buf = String::new(); |
83 | 83 | ||
84 | if let Some(mod_path) = mod_path { | 84 | if let Some(mod_path) = mod_path { |
85 | if !mod_path.is_empty() { | 85 | if !mod_path.is_empty() { |
86 | format_to!(buf, "{}\n", mod_path); | 86 | format_to!(buf, "```rust\n{}\n```\n\n", mod_path); |
87 | } | 87 | } |
88 | } | 88 | } |
89 | format_to!(buf, "{}\n```", code); | 89 | format_to!(buf, "```rust\n{}\n```", code); |
90 | 90 | ||
91 | if let Some(doc) = doc { | 91 | if let Some(doc) = doc { |
92 | format_to!(buf, "\n___"); | ||
92 | format_to!(buf, "\n\n{}", doc); | 93 | format_to!(buf, "\n\n{}", doc); |
93 | } | 94 | } |
94 | 95 | ||
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..c7bb1e69f 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 | } |
@@ -87,15 +92,16 @@ impl NavigationTarget { | |||
87 | let name = module.name(db).map(|it| it.to_string().into()).unwrap_or_default(); | 92 | let name = module.name(db).map(|it| it.to_string().into()).unwrap_or_default(); |
88 | if let Some(src) = module.declaration_source(db) { | 93 | if let Some(src) = module.declaration_source(db) { |
89 | let frange = original_range(db, src.as_ref().map(|it| it.syntax())); | 94 | let frange = original_range(db, src.as_ref().map(|it| it.syntax())); |
90 | return NavigationTarget::from_syntax( | 95 | let mut res = NavigationTarget::from_syntax( |
91 | frange.file_id, | 96 | frange.file_id, |
92 | name, | 97 | name, |
93 | None, | 98 | None, |
94 | frange.range, | 99 | frange.range, |
95 | src.value.syntax().kind(), | 100 | src.value.syntax().kind(), |
96 | src.value.doc_comment_text(), | ||
97 | src.value.short_label(), | ||
98 | ); | 101 | ); |
102 | res.docs = src.value.doc_comment_text(); | ||
103 | res.description = src.value.short_label(); | ||
104 | return res; | ||
99 | } | 105 | } |
100 | module.to_nav(db) | 106 | module.to_nav(db) |
101 | } | 107 | } |
@@ -125,11 +131,9 @@ impl NavigationTarget { | |||
125 | } | 131 | } |
126 | 132 | ||
127 | /// Allows `NavigationTarget` to be created from a `NameOwner` | 133 | /// Allows `NavigationTarget` to be created from a `NameOwner` |
128 | fn from_named( | 134 | pub(crate) fn from_named( |
129 | db: &RootDatabase, | 135 | db: &RootDatabase, |
130 | node: InFile<&dyn ast::NameOwner>, | 136 | node: InFile<&dyn ast::NameOwner>, |
131 | docs: Option<String>, | ||
132 | description: Option<String>, | ||
133 | ) -> NavigationTarget { | 137 | ) -> NavigationTarget { |
134 | //FIXME: use `_` instead of empty string | 138 | //FIXME: use `_` instead of empty string |
135 | let name = node.value.name().map(|it| it.text().clone()).unwrap_or_default(); | 139 | let name = node.value.name().map(|it| it.text().clone()).unwrap_or_default(); |
@@ -143,8 +147,6 @@ impl NavigationTarget { | |||
143 | focus_range, | 147 | focus_range, |
144 | frange.range, | 148 | frange.range, |
145 | node.value.syntax().kind(), | 149 | node.value.syntax().kind(), |
146 | docs, | ||
147 | description, | ||
148 | ) | 150 | ) |
149 | } | 151 | } |
150 | 152 | ||
@@ -154,8 +156,6 @@ impl NavigationTarget { | |||
154 | focus_range: Option<TextRange>, | 156 | focus_range: Option<TextRange>, |
155 | full_range: TextRange, | 157 | full_range: TextRange, |
156 | kind: SyntaxKind, | 158 | kind: SyntaxKind, |
157 | docs: Option<String>, | ||
158 | description: Option<String>, | ||
159 | ) -> NavigationTarget { | 159 | ) -> NavigationTarget { |
160 | NavigationTarget { | 160 | NavigationTarget { |
161 | file_id, | 161 | file_id, |
@@ -164,8 +164,8 @@ impl NavigationTarget { | |||
164 | full_range, | 164 | full_range, |
165 | focus_range, | 165 | focus_range, |
166 | container_name: None, | 166 | container_name: None, |
167 | description, | 167 | description: None, |
168 | docs, | 168 | docs: None, |
169 | } | 169 | } |
170 | } | 170 | } |
171 | } | 171 | } |
@@ -233,12 +233,11 @@ where | |||
233 | { | 233 | { |
234 | fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { | 234 | fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { |
235 | let src = self.source(db); | 235 | let src = self.source(db); |
236 | NavigationTarget::from_named( | 236 | let mut res = |
237 | db, | 237 | NavigationTarget::from_named(db, src.as_ref().map(|it| it as &dyn ast::NameOwner)); |
238 | src.as_ref().map(|it| it as &dyn ast::NameOwner), | 238 | res.docs = src.value.doc_comment_text(); |
239 | src.value.doc_comment_text(), | 239 | res.description = src.value.short_label(); |
240 | src.value.short_label(), | 240 | res |
241 | ) | ||
242 | } | 241 | } |
243 | } | 242 | } |
244 | 243 | ||
@@ -253,15 +252,7 @@ impl ToNav for hir::Module { | |||
253 | } | 252 | } |
254 | }; | 253 | }; |
255 | let frange = original_range(db, src.with_value(syntax)); | 254 | let frange = original_range(db, src.with_value(syntax)); |
256 | NavigationTarget::from_syntax( | 255 | NavigationTarget::from_syntax(frange.file_id, name, focus, frange.range, syntax.kind()) |
257 | frange.file_id, | ||
258 | name, | ||
259 | focus, | ||
260 | frange.range, | ||
261 | syntax.kind(), | ||
262 | None, | ||
263 | None, | ||
264 | ) | ||
265 | } | 256 | } |
266 | } | 257 | } |
267 | 258 | ||
@@ -280,8 +271,6 @@ impl ToNav for hir::ImplDef { | |||
280 | None, | 271 | None, |
281 | frange.range, | 272 | frange.range, |
282 | src.value.syntax().kind(), | 273 | src.value.syntax().kind(), |
283 | None, | ||
284 | None, | ||
285 | ) | 274 | ) |
286 | } | 275 | } |
287 | } | 276 | } |
@@ -291,12 +280,12 @@ impl ToNav for hir::Field { | |||
291 | let src = self.source(db); | 280 | let src = self.source(db); |
292 | 281 | ||
293 | match &src.value { | 282 | match &src.value { |
294 | FieldSource::Named(it) => NavigationTarget::from_named( | 283 | FieldSource::Named(it) => { |
295 | db, | 284 | let mut res = NavigationTarget::from_named(db, src.with_value(it)); |
296 | src.with_value(it), | 285 | res.docs = it.doc_comment_text(); |
297 | it.doc_comment_text(), | 286 | res.description = it.short_label(); |
298 | it.short_label(), | 287 | res |
299 | ), | 288 | } |
300 | FieldSource::Pos(it) => { | 289 | FieldSource::Pos(it) => { |
301 | let frange = original_range(db, src.with_value(it.syntax())); | 290 | let frange = original_range(db, src.with_value(it.syntax())); |
302 | NavigationTarget::from_syntax( | 291 | NavigationTarget::from_syntax( |
@@ -305,8 +294,6 @@ impl ToNav for hir::Field { | |||
305 | None, | 294 | None, |
306 | frange.range, | 295 | frange.range, |
307 | it.syntax().kind(), | 296 | it.syntax().kind(), |
308 | None, | ||
309 | None, | ||
310 | ) | 297 | ) |
311 | } | 298 | } |
312 | } | 299 | } |
@@ -317,12 +304,10 @@ impl ToNav for hir::MacroDef { | |||
317 | fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { | 304 | fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { |
318 | let src = self.source(db); | 305 | let src = self.source(db); |
319 | log::debug!("nav target {:#?}", src.value.syntax()); | 306 | log::debug!("nav target {:#?}", src.value.syntax()); |
320 | NavigationTarget::from_named( | 307 | let mut res = |
321 | db, | 308 | NavigationTarget::from_named(db, src.as_ref().map(|it| it as &dyn ast::NameOwner)); |
322 | src.as_ref().map(|it| it as &dyn ast::NameOwner), | 309 | res.docs = src.value.doc_comment_text(); |
323 | src.value.doc_comment_text(), | 310 | res |
324 | None, | ||
325 | ) | ||
326 | } | 311 | } |
327 | } | 312 | } |
328 | 313 | ||
@@ -376,16 +361,20 @@ impl ToNav for hir::Local { | |||
376 | impl ToNav for hir::TypeParam { | 361 | impl ToNav for hir::TypeParam { |
377 | fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { | 362 | fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { |
378 | let src = self.source(db); | 363 | let src = self.source(db); |
379 | let range = match src.value { | 364 | let full_range = match &src.value { |
380 | Either::Left(it) => it.syntax().text_range(), | 365 | Either::Left(it) => it.syntax().text_range(), |
381 | Either::Right(it) => it.syntax().text_range(), | 366 | Either::Right(it) => it.syntax().text_range(), |
382 | }; | 367 | }; |
368 | let focus_range = match &src.value { | ||
369 | Either::Left(_) => None, | ||
370 | Either::Right(it) => it.name().map(|it| it.syntax().text_range()), | ||
371 | }; | ||
383 | NavigationTarget { | 372 | NavigationTarget { |
384 | file_id: src.file_id.original_file(db), | 373 | file_id: src.file_id.original_file(db), |
385 | name: self.name(db).to_string().into(), | 374 | name: self.name(db).to_string().into(), |
386 | kind: TYPE_PARAM, | 375 | kind: TYPE_PARAM, |
387 | full_range: range, | 376 | full_range, |
388 | focus_range: None, | 377 | focus_range, |
389 | container_name: None, | 378 | container_name: None, |
390 | description: None, | 379 | description: None, |
391 | docs: None, | 380 | docs: None, |
diff --git a/crates/ra_ide/src/display/short_label.rs b/crates/ra_ide/src/display/short_label.rs index 4b081bf6c..d37260e96 100644 --- a/crates/ra_ide/src/display/short_label.rs +++ b/crates/ra_ide/src/display/short_label.rs | |||
@@ -33,7 +33,11 @@ impl ShortLabel for ast::EnumDef { | |||
33 | 33 | ||
34 | impl ShortLabel for ast::TraitDef { | 34 | impl ShortLabel for ast::TraitDef { |
35 | fn short_label(&self) -> Option<String> { | 35 | fn short_label(&self) -> Option<String> { |
36 | short_label_from_node(self, "trait ") | 36 | if self.unsafe_token().is_some() { |
37 | short_label_from_node(self, "unsafe trait ") | ||
38 | } else { | ||
39 | short_label_from_node(self, "trait ") | ||
40 | } | ||
37 | } | 41 | } |
38 | } | 42 | } |
39 | 43 | ||
diff --git a/crates/ra_ide/src/display/structure.rs b/crates/ra_ide/src/display/structure.rs index 967eee5d2..aad5a8e4d 100644 --- a/crates/ra_ide/src/display/structure.rs +++ b/crates/ra_ide/src/display/structure.rs | |||
@@ -1,10 +1,6 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use crate::TextRange; | ||
4 | |||
5 | use ra_syntax::{ | 1 | use ra_syntax::{ |
6 | ast::{self, AttrsOwner, NameOwner, TypeAscriptionOwner, TypeParamsOwner}, | 2 | ast::{self, AttrsOwner, NameOwner, TypeAscriptionOwner, TypeParamsOwner}, |
7 | match_ast, AstNode, SourceFile, SyntaxKind, SyntaxNode, WalkEvent, | 3 | match_ast, AstNode, SourceFile, SyntaxKind, SyntaxNode, TextRange, WalkEvent, |
8 | }; | 4 | }; |
9 | 5 | ||
10 | #[derive(Debug, Clone)] | 6 | #[derive(Debug, Clone)] |
@@ -18,6 +14,19 @@ pub struct StructureNode { | |||
18 | pub deprecated: bool, | 14 | pub deprecated: bool, |
19 | } | 15 | } |
20 | 16 | ||
17 | // Feature: File Structure | ||
18 | // | ||
19 | // Provides a tree of the symbols defined in the file. Can be used to | ||
20 | // | ||
21 | // * fuzzy search symbol in a file (super useful) | ||
22 | // * draw breadcrumbs to describe the context around the cursor | ||
23 | // * draw outline of the file | ||
24 | // | ||
25 | // |=== | ||
26 | // | Editor | Shortcut | ||
27 | // | ||
28 | // | VS Code | kbd:[Ctrl+Shift+O] | ||
29 | // |=== | ||
21 | pub fn file_structure(file: &SourceFile) -> Vec<StructureNode> { | 30 | pub fn file_structure(file: &SourceFile) -> Vec<StructureNode> { |
22 | let mut res = Vec::new(); | 31 | let mut res = Vec::new(); |
23 | let mut stack = Vec::new(); | 32 | let mut stack = Vec::new(); |
diff --git a/crates/ra_ide/src/expand_macro.rs b/crates/ra_ide/src/expand_macro.rs index f536ba3e7..54a47aac0 100644 --- a/crates/ra_ide/src/expand_macro.rs +++ b/crates/ra_ide/src/expand_macro.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! This modules implements "expand macro" functionality in the IDE | ||
2 | |||
3 | use hir::Semantics; | 1 | use hir::Semantics; |
4 | use ra_ide_db::RootDatabase; | 2 | use ra_ide_db::RootDatabase; |
5 | use ra_syntax::{ | 3 | use ra_syntax::{ |
@@ -14,6 +12,15 @@ pub struct ExpandedMacro { | |||
14 | pub expansion: String, | 12 | pub expansion: String, |
15 | } | 13 | } |
16 | 14 | ||
15 | // Feature: Expand Macro Recursively | ||
16 | // | ||
17 | // Shows the full macro expansion of the macro at current cursor. | ||
18 | // | ||
19 | // |=== | ||
20 | // | Editor | Action Name | ||
21 | // | ||
22 | // | VS Code | **Rust Analyzer: Expand macro recursively** | ||
23 | // |=== | ||
17 | pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<ExpandedMacro> { | 24 | pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<ExpandedMacro> { |
18 | let sema = Semantics::new(db); | 25 | let sema = Semantics::new(db); |
19 | let file = sema.parse(position.file_id); | 26 | let file = sema.parse(position.file_id); |
diff --git a/crates/ra_ide/src/extend_selection.rs b/crates/ra_ide/src/extend_selection.rs index 554594a43..a4bc93cdb 100644 --- a/crates/ra_ide/src/extend_selection.rs +++ b/crates/ra_ide/src/extend_selection.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use std::iter::successors; | 1 | use std::iter::successors; |
4 | 2 | ||
5 | use hir::Semantics; | 3 | use hir::Semantics; |
@@ -14,6 +12,16 @@ use ra_syntax::{ | |||
14 | 12 | ||
15 | use crate::FileRange; | 13 | use crate::FileRange; |
16 | 14 | ||
15 | // Feature: Extend Selection | ||
16 | // | ||
17 | // Extends the current selection to the encompassing syntactic construct | ||
18 | // (expression, statement, item, module, etc). It works with multiple cursors. | ||
19 | // | ||
20 | // |=== | ||
21 | // | Editor | Shortcut | ||
22 | // | ||
23 | // | VS Code | kbd:[Ctrl+Shift+→] | ||
24 | // |=== | ||
17 | pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange { | 25 | pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange { |
18 | let sema = Semantics::new(db); | 26 | let sema = Semantics::new(db); |
19 | let src = sema.parse(frange.file_id); | 27 | let src = sema.parse(frange.file_id); |
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..a6c86e99c 100644 --- a/crates/ra_ide/src/goto_definition.rs +++ b/crates/ra_ide/src/goto_definition.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use hir::Semantics; | 1 | use hir::Semantics; |
4 | use ra_ide_db::{ | 2 | use ra_ide_db::{ |
5 | defs::{classify_name, classify_name_ref}, | 3 | defs::{classify_name, classify_name_ref}, |
@@ -17,6 +15,15 @@ use crate::{ | |||
17 | FilePosition, NavigationTarget, RangeInfo, | 15 | FilePosition, NavigationTarget, RangeInfo, |
18 | }; | 16 | }; |
19 | 17 | ||
18 | // Feature: Go to Definition | ||
19 | // | ||
20 | // Navigates to the definition of an identifier. | ||
21 | // | ||
22 | // |=== | ||
23 | // | Editor | Shortcut | ||
24 | // | ||
25 | // | VS Code | kbd:[F12] | ||
26 | // |=== | ||
20 | pub(crate) fn goto_definition( | 27 | pub(crate) fn goto_definition( |
21 | db: &RootDatabase, | 28 | db: &RootDatabase, |
22 | position: FilePosition, | 29 | position: FilePosition, |
@@ -93,7 +100,7 @@ pub(crate) fn reference_definition( | |||
93 | 100 | ||
94 | #[cfg(test)] | 101 | #[cfg(test)] |
95 | mod tests { | 102 | mod tests { |
96 | use test_utils::{assert_eq_text, covers}; | 103 | use test_utils::assert_eq_text; |
97 | 104 | ||
98 | use crate::mock_analysis::analysis_and_position; | 105 | use crate::mock_analysis::analysis_and_position; |
99 | 106 | ||
@@ -208,7 +215,6 @@ mod tests { | |||
208 | 215 | ||
209 | #[test] | 216 | #[test] |
210 | fn goto_def_for_macros() { | 217 | fn goto_def_for_macros() { |
211 | covers!(ra_ide_db::goto_def_for_macros); | ||
212 | check_goto( | 218 | check_goto( |
213 | " | 219 | " |
214 | //- /lib.rs | 220 | //- /lib.rs |
@@ -225,7 +231,6 @@ mod tests { | |||
225 | 231 | ||
226 | #[test] | 232 | #[test] |
227 | fn goto_def_for_macros_from_other_crates() { | 233 | fn goto_def_for_macros_from_other_crates() { |
228 | covers!(ra_ide_db::goto_def_for_macros); | ||
229 | check_goto( | 234 | check_goto( |
230 | " | 235 | " |
231 | //- /lib.rs | 236 | //- /lib.rs |
@@ -244,6 +249,38 @@ mod tests { | |||
244 | } | 249 | } |
245 | 250 | ||
246 | #[test] | 251 | #[test] |
252 | fn goto_def_for_use_alias() { | ||
253 | check_goto( | ||
254 | " | ||
255 | //- /lib.rs | ||
256 | use foo as bar<|>; | ||
257 | |||
258 | |||
259 | //- /foo/lib.rs | ||
260 | #[macro_export] | ||
261 | macro_rules! foo { () => { () } }", | ||
262 | "SOURCE_FILE FileId(2) 0..50", | ||
263 | "#[macro_export]\nmacro_rules! foo { () => { () } }\n", | ||
264 | ); | ||
265 | } | ||
266 | |||
267 | #[test] | ||
268 | fn goto_def_for_use_alias_foo_macro() { | ||
269 | check_goto( | ||
270 | " | ||
271 | //- /lib.rs | ||
272 | use foo::foo as bar<|>; | ||
273 | |||
274 | //- /foo/lib.rs | ||
275 | #[macro_export] | ||
276 | macro_rules! foo { () => { () } } | ||
277 | ", | ||
278 | "foo MACRO_CALL FileId(2) 0..49 29..32", | ||
279 | "#[macro_export]\nmacro_rules! foo { () => { () } }|foo", | ||
280 | ); | ||
281 | } | ||
282 | |||
283 | #[test] | ||
247 | fn goto_def_for_macros_in_use_tree() { | 284 | fn goto_def_for_macros_in_use_tree() { |
248 | check_goto( | 285 | check_goto( |
249 | " | 286 | " |
@@ -337,7 +374,6 @@ mod tests { | |||
337 | 374 | ||
338 | #[test] | 375 | #[test] |
339 | fn goto_def_for_methods() { | 376 | fn goto_def_for_methods() { |
340 | covers!(ra_ide_db::goto_def_for_methods); | ||
341 | check_goto( | 377 | check_goto( |
342 | " | 378 | " |
343 | //- /lib.rs | 379 | //- /lib.rs |
@@ -357,7 +393,6 @@ mod tests { | |||
357 | 393 | ||
358 | #[test] | 394 | #[test] |
359 | fn goto_def_for_fields() { | 395 | fn goto_def_for_fields() { |
360 | covers!(ra_ide_db::goto_def_for_fields); | ||
361 | check_goto( | 396 | check_goto( |
362 | r" | 397 | r" |
363 | //- /lib.rs | 398 | //- /lib.rs |
@@ -376,7 +411,6 @@ mod tests { | |||
376 | 411 | ||
377 | #[test] | 412 | #[test] |
378 | fn goto_def_for_record_fields() { | 413 | fn goto_def_for_record_fields() { |
379 | covers!(ra_ide_db::goto_def_for_record_fields); | ||
380 | check_goto( | 414 | check_goto( |
381 | r" | 415 | r" |
382 | //- /lib.rs | 416 | //- /lib.rs |
@@ -397,7 +431,6 @@ mod tests { | |||
397 | 431 | ||
398 | #[test] | 432 | #[test] |
399 | fn goto_def_for_record_pat_fields() { | 433 | fn goto_def_for_record_pat_fields() { |
400 | covers!(ra_ide_db::goto_def_for_record_field_pats); | ||
401 | check_goto( | 434 | check_goto( |
402 | r" | 435 | r" |
403 | //- /lib.rs | 436 | //- /lib.rs |
@@ -754,14 +787,14 @@ mod tests { | |||
754 | #[test] | 787 | #[test] |
755 | fn goto_for_type_param() { | 788 | fn goto_for_type_param() { |
756 | check_goto( | 789 | check_goto( |
757 | " | 790 | r#" |
758 | //- /lib.rs | 791 | //- /lib.rs |
759 | struct Foo<T> { | 792 | struct Foo<T: Clone> { |
760 | t: <|>T, | 793 | t: <|>T, |
761 | } | 794 | } |
762 | ", | 795 | "#, |
763 | "T TYPE_PARAM FileId(1) 11..12", | 796 | "T TYPE_PARAM FileId(1) 11..19 11..12", |
764 | "T", | 797 | "T: Clone|T", |
765 | ); | 798 | ); |
766 | } | 799 | } |
767 | 800 | ||
@@ -840,7 +873,6 @@ mod tests { | |||
840 | 873 | ||
841 | #[test] | 874 | #[test] |
842 | fn goto_def_for_field_init_shorthand() { | 875 | fn goto_def_for_field_init_shorthand() { |
843 | covers!(ra_ide_db::goto_def_for_field_init_shorthand); | ||
844 | check_goto( | 876 | check_goto( |
845 | " | 877 | " |
846 | //- /lib.rs | 878 | //- /lib.rs |
diff --git a/crates/ra_ide/src/impls.rs b/crates/ra_ide/src/goto_implementation.rs index ea2225f70..0cec0657e 100644 --- a/crates/ra_ide/src/impls.rs +++ b/crates/ra_ide/src/goto_implementation.rs | |||
@@ -1,11 +1,18 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use hir::{Crate, ImplDef, Semantics}; | 1 | use hir::{Crate, ImplDef, Semantics}; |
4 | use ra_ide_db::RootDatabase; | 2 | use ra_ide_db::RootDatabase; |
5 | use ra_syntax::{algo::find_node_at_offset, ast, AstNode}; | 3 | use ra_syntax::{algo::find_node_at_offset, ast, AstNode}; |
6 | 4 | ||
7 | use crate::{display::ToNav, FilePosition, NavigationTarget, RangeInfo}; | 5 | use crate::{display::ToNav, FilePosition, NavigationTarget, RangeInfo}; |
8 | 6 | ||
7 | // Feature: Go to Implementation | ||
8 | // | ||
9 | // Navigates to the impl block of structs, enums or traits. Also implemented as a code lens. | ||
10 | // | ||
11 | // |=== | ||
12 | // | Editor | Shortcut | ||
13 | // | ||
14 | // | VS Code | kbd:[Ctrl+F12] | ||
15 | // |=== | ||
9 | pub(crate) fn goto_implementation( | 16 | pub(crate) fn goto_implementation( |
10 | db: &RootDatabase, | 17 | db: &RootDatabase, |
11 | position: FilePosition, | 18 | position: FilePosition, |
diff --git a/crates/ra_ide/src/goto_type_definition.rs b/crates/ra_ide/src/goto_type_definition.rs index a84637489..91a3097fb 100644 --- a/crates/ra_ide/src/goto_type_definition.rs +++ b/crates/ra_ide/src/goto_type_definition.rs | |||
@@ -1,10 +1,17 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use ra_ide_db::RootDatabase; | 1 | use ra_ide_db::RootDatabase; |
4 | use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; | 2 | use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; |
5 | 3 | ||
6 | use crate::{display::ToNav, FilePosition, NavigationTarget, RangeInfo}; | 4 | use crate::{display::ToNav, FilePosition, NavigationTarget, RangeInfo}; |
7 | 5 | ||
6 | // Feature: Go to Type Definition | ||
7 | // | ||
8 | // Navigates to the type of an identifier. | ||
9 | // | ||
10 | // |=== | ||
11 | // | Editor | Action Name | ||
12 | // | ||
13 | // | VS Code | **Go to Type Definition* | ||
14 | // |=== | ||
8 | pub(crate) fn goto_type_definition( | 15 | pub(crate) fn goto_type_definition( |
9 | db: &RootDatabase, | 16 | db: &RootDatabase, |
10 | position: FilePosition, | 17 | position: FilePosition, |
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index a62f598f0..d96cb5596 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs | |||
@@ -1,10 +1,10 @@ | |||
1 | //! Logic for computing info that is displayed when the user hovers over any | 1 | use std::iter::once; |
2 | //! source code items (e.g. function call, struct field, variable symbol...) | ||
3 | 2 | ||
4 | use hir::{ | 3 | use hir::{ |
5 | Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef, | 4 | Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef, |
6 | ModuleSource, Semantics, | 5 | ModuleSource, Semantics, |
7 | }; | 6 | }; |
7 | use itertools::Itertools; | ||
8 | use ra_db::SourceDatabase; | 8 | use ra_db::SourceDatabase; |
9 | use ra_ide_db::{ | 9 | use ra_ide_db::{ |
10 | defs::{classify_name, classify_name_ref, Definition}, | 10 | defs::{classify_name, classify_name_ref, Definition}, |
@@ -21,8 +21,6 @@ use crate::{ | |||
21 | display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel}, | 21 | display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel}, |
22 | FilePosition, RangeInfo, | 22 | FilePosition, RangeInfo, |
23 | }; | 23 | }; |
24 | use itertools::Itertools; | ||
25 | use std::iter::once; | ||
26 | 24 | ||
27 | /// Contains the results when hovering over an item | 25 | /// Contains the results when hovering over an item |
28 | #[derive(Debug, Default)] | 26 | #[derive(Debug, Default)] |
@@ -62,6 +60,63 @@ impl HoverResult { | |||
62 | } | 60 | } |
63 | } | 61 | } |
64 | 62 | ||
63 | // Feature: Hover | ||
64 | // | ||
65 | // Shows additional information, like type of an expression or documentation for definition when "focusing" code. | ||
66 | // Focusing is usually hovering with a mouse, but can also be triggered with a shortcut. | ||
67 | pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<HoverResult>> { | ||
68 | let sema = Semantics::new(db); | ||
69 | let file = sema.parse(position.file_id).syntax().clone(); | ||
70 | let token = pick_best(file.token_at_offset(position.offset))?; | ||
71 | let token = sema.descend_into_macros(token); | ||
72 | |||
73 | let mut res = HoverResult::new(); | ||
74 | |||
75 | if let Some((node, name_kind)) = match_ast! { | ||
76 | match (token.parent()) { | ||
77 | ast::NameRef(name_ref) => { | ||
78 | classify_name_ref(&sema, &name_ref).map(|d| (name_ref.syntax().clone(), d.definition())) | ||
79 | }, | ||
80 | ast::Name(name) => { | ||
81 | classify_name(&sema, &name).map(|d| (name.syntax().clone(), d.definition())) | ||
82 | }, | ||
83 | _ => None, | ||
84 | } | ||
85 | } { | ||
86 | let range = sema.original_range(&node).range; | ||
87 | res.extend(hover_text_from_name_kind(db, name_kind)); | ||
88 | |||
89 | if !res.is_empty() { | ||
90 | return Some(RangeInfo::new(range, res)); | ||
91 | } | ||
92 | } | ||
93 | |||
94 | let node = token | ||
95 | .ancestors() | ||
96 | .find(|n| ast::Expr::cast(n.clone()).is_some() || ast::Pat::cast(n.clone()).is_some())?; | ||
97 | |||
98 | let ty = match_ast! { | ||
99 | match node { | ||
100 | ast::MacroCall(_it) => { | ||
101 | // If this node is a MACRO_CALL, it means that `descend_into_macros` failed to resolve. | ||
102 | // (e.g expanding a builtin macro). So we give up here. | ||
103 | return None; | ||
104 | }, | ||
105 | ast::Expr(it) => { | ||
106 | sema.type_of_expr(&it) | ||
107 | }, | ||
108 | ast::Pat(it) => { | ||
109 | sema.type_of_pat(&it) | ||
110 | }, | ||
111 | _ => None, | ||
112 | } | ||
113 | }?; | ||
114 | |||
115 | res.extend(Some(rust_code_markup(&ty.display(db)))); | ||
116 | let range = sema.original_range(&node).range; | ||
117 | Some(RangeInfo::new(range, res)) | ||
118 | } | ||
119 | |||
65 | fn hover_text( | 120 | fn hover_text( |
66 | docs: Option<String>, | 121 | docs: Option<String>, |
67 | desc: Option<String>, | 122 | desc: Option<String>, |
@@ -143,7 +198,7 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<Strin | |||
143 | ModuleDef::TypeAlias(it) => from_def_source(db, it, mod_path), | 198 | ModuleDef::TypeAlias(it) => from_def_source(db, it, mod_path), |
144 | ModuleDef::BuiltinType(it) => Some(it.to_string()), | 199 | ModuleDef::BuiltinType(it) => Some(it.to_string()), |
145 | }, | 200 | }, |
146 | Definition::Local(it) => Some(rust_code_markup(&it.ty(db).display_truncated(db, None))), | 201 | Definition::Local(it) => Some(rust_code_markup(&it.ty(db).display(db))), |
147 | Definition::TypeParam(_) | Definition::SelfType(_) => { | 202 | Definition::TypeParam(_) | Definition::SelfType(_) => { |
148 | // FIXME: Hover for generic param | 203 | // FIXME: Hover for generic param |
149 | None | 204 | None |
@@ -160,59 +215,6 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<Strin | |||
160 | } | 215 | } |
161 | } | 216 | } |
162 | 217 | ||
163 | pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<HoverResult>> { | ||
164 | let sema = Semantics::new(db); | ||
165 | let file = sema.parse(position.file_id).syntax().clone(); | ||
166 | let token = pick_best(file.token_at_offset(position.offset))?; | ||
167 | let token = sema.descend_into_macros(token); | ||
168 | |||
169 | let mut res = HoverResult::new(); | ||
170 | |||
171 | if let Some((node, name_kind)) = match_ast! { | ||
172 | match (token.parent()) { | ||
173 | ast::NameRef(name_ref) => { | ||
174 | classify_name_ref(&sema, &name_ref).map(|d| (name_ref.syntax().clone(), d.definition())) | ||
175 | }, | ||
176 | ast::Name(name) => { | ||
177 | classify_name(&sema, &name).map(|d| (name.syntax().clone(), d.definition())) | ||
178 | }, | ||
179 | _ => None, | ||
180 | } | ||
181 | } { | ||
182 | let range = sema.original_range(&node).range; | ||
183 | res.extend(hover_text_from_name_kind(db, name_kind)); | ||
184 | |||
185 | if !res.is_empty() { | ||
186 | return Some(RangeInfo::new(range, res)); | ||
187 | } | ||
188 | } | ||
189 | |||
190 | let node = token | ||
191 | .ancestors() | ||
192 | .find(|n| ast::Expr::cast(n.clone()).is_some() || ast::Pat::cast(n.clone()).is_some())?; | ||
193 | |||
194 | let ty = match_ast! { | ||
195 | match node { | ||
196 | ast::MacroCall(_it) => { | ||
197 | // If this node is a MACRO_CALL, it means that `descend_into_macros` failed to resolve. | ||
198 | // (e.g expanding a builtin macro). So we give up here. | ||
199 | return None; | ||
200 | }, | ||
201 | ast::Expr(it) => { | ||
202 | sema.type_of_expr(&it) | ||
203 | }, | ||
204 | ast::Pat(it) => { | ||
205 | sema.type_of_pat(&it) | ||
206 | }, | ||
207 | _ => None, | ||
208 | } | ||
209 | }?; | ||
210 | |||
211 | res.extend(Some(rust_code_markup(&ty.display_truncated(db, None)))); | ||
212 | let range = sema.original_range(&node).range; | ||
213 | Some(RangeInfo::new(range, res)) | ||
214 | } | ||
215 | |||
216 | fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { | 218 | fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { |
217 | return tokens.max_by_key(priority); | 219 | return tokens.max_by_key(priority); |
218 | fn priority(n: &SyntaxToken) -> usize { | 220 | fn priority(n: &SyntaxToken) -> usize { |
@@ -280,6 +282,47 @@ mod tests { | |||
280 | } | 282 | } |
281 | 283 | ||
282 | #[test] | 284 | #[test] |
285 | fn hover_shows_long_type_of_an_expression() { | ||
286 | check_hover_result( | ||
287 | r#" | ||
288 | //- /main.rs | ||
289 | struct Scan<A, B, C> { | ||
290 | a: A, | ||
291 | b: B, | ||
292 | c: C, | ||
293 | } | ||
294 | |||
295 | struct FakeIter<I> { | ||
296 | inner: I, | ||
297 | } | ||
298 | |||
299 | struct OtherStruct<T> { | ||
300 | i: T, | ||
301 | } | ||
302 | |||
303 | enum FakeOption<T> { | ||
304 | Some(T), | ||
305 | None, | ||
306 | } | ||
307 | |||
308 | fn scan<A, B, C>(a: A, b: B, c: C) -> FakeIter<Scan<OtherStruct<A>, B, C>> { | ||
309 | FakeIter { inner: Scan { a, b, c } } | ||
310 | } | ||
311 | |||
312 | fn main() { | ||
313 | let num: i32 = 55; | ||
314 | let closure = |memo: &mut u32, value: &u32, _another: &mut u32| -> FakeOption<u32> { | ||
315 | FakeOption::Some(*memo + value) | ||
316 | }; | ||
317 | let number = 5u32; | ||
318 | let mut iter<|> = scan(OtherStruct { i: num }, closure, number); | ||
319 | } | ||
320 | "#, | ||
321 | &["FakeIter<Scan<OtherStruct<OtherStruct<i32>>, |&mut u32, &u32, &mut u32| -> FakeOption<u32>, u32>>"], | ||
322 | ); | ||
323 | } | ||
324 | |||
325 | #[test] | ||
283 | fn hover_shows_fn_signature() { | 326 | fn hover_shows_fn_signature() { |
284 | // Single file with result | 327 | // Single file with result |
285 | check_hover_result( | 328 | check_hover_result( |
@@ -364,7 +407,7 @@ mod tests { | |||
364 | }; | 407 | }; |
365 | } | 408 | } |
366 | "#, | 409 | "#, |
367 | &["Foo\nfield_a: u32"], | 410 | &["Foo\n```\n\n```rust\nfield_a: u32"], |
368 | ); | 411 | ); |
369 | 412 | ||
370 | // Hovering over the field in the definition | 413 | // Hovering over the field in the definition |
@@ -381,7 +424,7 @@ mod tests { | |||
381 | }; | 424 | }; |
382 | } | 425 | } |
383 | "#, | 426 | "#, |
384 | &["Foo\nfield_a: u32"], | 427 | &["Foo\n```\n\n```rust\nfield_a: u32"], |
385 | ); | 428 | ); |
386 | } | 429 | } |
387 | 430 | ||
@@ -405,7 +448,7 @@ mod tests { | |||
405 | } | 448 | } |
406 | 449 | ||
407 | #[test] | 450 | #[test] |
408 | fn hover_omits_default_generic_types() { | 451 | fn hover_default_generic_types() { |
409 | check_hover_result( | 452 | check_hover_result( |
410 | r#" | 453 | r#" |
411 | //- /main.rs | 454 | //- /main.rs |
@@ -417,7 +460,7 @@ struct Test<K, T = u8> { | |||
417 | fn main() { | 460 | fn main() { |
418 | let zz<|> = Test { t: 23, k: 33 }; | 461 | let zz<|> = Test { t: 23, k: 33 }; |
419 | }"#, | 462 | }"#, |
420 | &["Test<i32>"], | 463 | &["Test<i32, u8>"], |
421 | ); | 464 | ); |
422 | } | 465 | } |
423 | 466 | ||
@@ -434,7 +477,7 @@ fn main() { | |||
434 | ", | 477 | ", |
435 | ); | 478 | ); |
436 | let hover = analysis.hover(position).unwrap().unwrap(); | 479 | let hover = analysis.hover(position).unwrap().unwrap(); |
437 | assert_eq!(trim_markup_opt(hover.info.first()), Some("Option\nSome")); | 480 | assert_eq!(trim_markup_opt(hover.info.first()), Some("Option\n```\n\n```rust\nSome")); |
438 | 481 | ||
439 | let (analysis, position) = single_file_with_position( | 482 | let (analysis, position) = single_file_with_position( |
440 | " | 483 | " |
@@ -462,8 +505,12 @@ fn main() { | |||
462 | "#, | 505 | "#, |
463 | &[" | 506 | &[" |
464 | Option | 507 | Option |
508 | ``` | ||
509 | |||
510 | ```rust | ||
465 | None | 511 | None |
466 | ``` | 512 | ``` |
513 | ___ | ||
467 | 514 | ||
468 | The None variant | 515 | The None variant |
469 | " | 516 | " |
@@ -483,8 +530,12 @@ The None variant | |||
483 | "#, | 530 | "#, |
484 | &[" | 531 | &[" |
485 | Option | 532 | Option |
533 | ``` | ||
534 | |||
535 | ```rust | ||
486 | Some | 536 | Some |
487 | ``` | 537 | ``` |
538 | ___ | ||
488 | 539 | ||
489 | The Some variant | 540 | The Some variant |
490 | " | 541 | " |
@@ -565,7 +616,10 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
565 | ", | 616 | ", |
566 | ); | 617 | ); |
567 | let hover = analysis.hover(position).unwrap().unwrap(); | 618 | let hover = analysis.hover(position).unwrap().unwrap(); |
568 | assert_eq!(trim_markup_opt(hover.info.first()), Some("wrapper::Thing\nfn new() -> Thing")); | 619 | assert_eq!( |
620 | trim_markup_opt(hover.info.first()), | ||
621 | Some("wrapper::Thing\n```\n\n```rust\nfn new() -> Thing") | ||
622 | ); | ||
569 | } | 623 | } |
570 | 624 | ||
571 | #[test] | 625 | #[test] |
@@ -841,7 +895,7 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
841 | fo<|>o(); | 895 | fo<|>o(); |
842 | } | 896 | } |
843 | ", | 897 | ", |
844 | &["fn foo()\n```\n\n<- `\u{3000}` here"], | 898 | &["fn foo()\n```\n___\n\n<- `\u{3000}` here"], |
845 | ); | 899 | ); |
846 | } | 900 | } |
847 | 901 | ||
@@ -869,4 +923,32 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
869 | &[r#"pub(crate) async unsafe extern "C" fn foo()"#], | 923 | &[r#"pub(crate) async unsafe extern "C" fn foo()"#], |
870 | ); | 924 | ); |
871 | } | 925 | } |
926 | |||
927 | #[test] | ||
928 | fn test_hover_trait_show_qualifiers() { | ||
929 | check_hover_result( | ||
930 | " | ||
931 | //- /lib.rs | ||
932 | unsafe trait foo<|>() {} | ||
933 | ", | ||
934 | &["unsafe trait foo"], | ||
935 | ); | ||
936 | } | ||
937 | |||
938 | #[test] | ||
939 | fn test_hover_mod_with_same_name_as_function() { | ||
940 | check_hover_result( | ||
941 | " | ||
942 | //- /lib.rs | ||
943 | use self::m<|>y::Bar; | ||
944 | |||
945 | mod my { | ||
946 | pub struct Bar; | ||
947 | } | ||
948 | |||
949 | fn my() {} | ||
950 | ", | ||
951 | &["mod my"], | ||
952 | ); | ||
953 | } | ||
872 | } | 954 | } |
diff --git a/crates/ra_ide/src/inlay_hints.rs b/crates/ra_ide/src/inlay_hints.rs index 98483df32..75bd3c96b 100644 --- a/crates/ra_ide/src/inlay_hints.rs +++ b/crates/ra_ide/src/inlay_hints.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! This module defines multiple types of inlay hints and their visibility | ||
2 | |||
3 | use hir::{Adt, HirDisplay, Semantics, Type}; | 1 | use hir::{Adt, HirDisplay, Semantics, Type}; |
4 | use ra_ide_db::RootDatabase; | 2 | use ra_ide_db::RootDatabase; |
5 | use ra_prof::profile; | 3 | use ra_prof::profile; |
@@ -9,6 +7,7 @@ use ra_syntax::{ | |||
9 | }; | 7 | }; |
10 | 8 | ||
11 | use crate::{FileId, FunctionSignature}; | 9 | use crate::{FileId, FunctionSignature}; |
10 | use stdx::to_lower_snake_case; | ||
12 | 11 | ||
13 | #[derive(Clone, Debug, PartialEq, Eq)] | 12 | #[derive(Clone, Debug, PartialEq, Eq)] |
14 | pub struct InlayHintsConfig { | 13 | pub struct InlayHintsConfig { |
@@ -38,6 +37,26 @@ pub struct InlayHint { | |||
38 | pub label: SmolStr, | 37 | pub label: SmolStr, |
39 | } | 38 | } |
40 | 39 | ||
40 | // Feature: Inlay Hints | ||
41 | // | ||
42 | // rust-analyzer shows additional information inline with the source code. | ||
43 | // Editors usually render this using read-only virtual text snippets interspersed with code. | ||
44 | // | ||
45 | // rust-analyzer shows hits for | ||
46 | // | ||
47 | // * types of local variables | ||
48 | // * names of function arguments | ||
49 | // * types of chained expressions | ||
50 | // | ||
51 | // **Note:** VS Code does not have native support for inlay hints https://github.com/microsoft/vscode/issues/16221[yet] and the hints are implemented using decorations. | ||
52 | // This approach has limitations, the caret movement and bracket highlighting near the edges of the hint may be weird: | ||
53 | // https://github.com/rust-analyzer/rust-analyzer/issues/1623[1], https://github.com/rust-analyzer/rust-analyzer/issues/3453[2]. | ||
54 | // | ||
55 | // |=== | ||
56 | // | Editor | Action Name | ||
57 | // | ||
58 | // | VS Code | **Rust Analyzer: Toggle inlay hints* | ||
59 | // |=== | ||
41 | pub(crate) fn inlay_hints( | 60 | pub(crate) fn inlay_hints( |
42 | db: &RootDatabase, | 61 | db: &RootDatabase, |
43 | file_id: FileId, | 62 | file_id: FileId, |
@@ -144,7 +163,7 @@ fn get_param_name_hints( | |||
144 | .iter() | 163 | .iter() |
145 | .skip(n_params_to_skip) | 164 | .skip(n_params_to_skip) |
146 | .zip(args) | 165 | .zip(args) |
147 | .filter(|(param, arg)| should_show_param_hint(&fn_signature, param, &arg)) | 166 | .filter(|(param, arg)| should_show_param_name_hint(sema, &fn_signature, param, &arg)) |
148 | .map(|(param_name, arg)| InlayHint { | 167 | .map(|(param_name, arg)| InlayHint { |
149 | range: arg.syntax().text_range(), | 168 | range: arg.syntax().text_range(), |
150 | kind: InlayKind::ParameterHint, | 169 | kind: InlayKind::ParameterHint, |
@@ -181,7 +200,7 @@ fn get_bind_pat_hints( | |||
181 | 200 | ||
182 | fn pat_is_enum_variant(db: &RootDatabase, bind_pat: &ast::BindPat, pat_ty: &Type) -> bool { | 201 | 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() { | 202 | if let Some(Adt::Enum(enum_data)) = pat_ty.as_adt() { |
184 | let pat_text = bind_pat.syntax().to_string(); | 203 | let pat_text = bind_pat.to_string(); |
185 | enum_data | 204 | enum_data |
186 | .variants(db) | 205 | .variants(db) |
187 | .into_iter() | 206 | .into_iter() |
@@ -198,7 +217,7 @@ fn should_not_display_type_hint(db: &RootDatabase, bind_pat: &ast::BindPat, pat_ | |||
198 | } | 217 | } |
199 | 218 | ||
200 | if let Some(Adt::Struct(s)) = pat_ty.as_adt() { | 219 | 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() { | 220 | if s.fields(db).is_empty() && s.name(db).to_string() == bind_pat.to_string() { |
202 | return true; | 221 | return true; |
203 | } | 222 | } |
204 | } | 223 | } |
@@ -230,15 +249,16 @@ fn should_not_display_type_hint(db: &RootDatabase, bind_pat: &ast::BindPat, pat_ | |||
230 | false | 249 | false |
231 | } | 250 | } |
232 | 251 | ||
233 | fn should_show_param_hint( | 252 | fn should_show_param_name_hint( |
253 | sema: &Semantics<RootDatabase>, | ||
234 | fn_signature: &FunctionSignature, | 254 | fn_signature: &FunctionSignature, |
235 | param_name: &str, |