aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide')
-rw-r--r--crates/ra_ide/src/assists.rs47
-rw-r--r--crates/ra_ide/src/call_hierarchy.rs29
-rw-r--r--crates/ra_ide/src/call_info.rs8
-rw-r--r--crates/ra_ide/src/completion.rs52
-rw-r--r--crates/ra_ide/src/completion/complete_attribute.rs306
-rw-r--r--crates/ra_ide/src/completion/complete_postfix.rs317
-rw-r--r--crates/ra_ide/src/completion/complete_qualified_path.rs63
-rw-r--r--crates/ra_ide/src/completion/complete_snippet.rs26
-rw-r--r--crates/ra_ide/src/completion/complete_trait_impl.rs57
-rw-r--r--crates/ra_ide/src/completion/complete_unqualified_path.rs65
-rw-r--r--crates/ra_ide/src/completion/completion_context.rs27
-rw-r--r--crates/ra_ide/src/completion/completion_item.rs7
-rw-r--r--crates/ra_ide/src/completion/presentation.rs142
-rw-r--r--crates/ra_ide/src/completion/test_utils.rs2
-rw-r--r--crates/ra_ide/src/diagnostics.rs127
-rw-r--r--crates/ra_ide/src/display.rs7
-rw-r--r--crates/ra_ide/src/display/function_signature.rs23
-rw-r--r--crates/ra_ide/src/display/navigation_target.rs85
-rw-r--r--crates/ra_ide/src/display/short_label.rs6
-rw-r--r--crates/ra_ide/src/display/structure.rs19
-rw-r--r--crates/ra_ide/src/expand_macro.rs11
-rw-r--r--crates/ra_ide/src/extend_selection.rs12
-rw-r--r--crates/ra_ide/src/folding_ranges.rs2
-rw-r--r--crates/ra_ide/src/goto_definition.rs62
-rw-r--r--crates/ra_ide/src/goto_implementation.rs (renamed from crates/ra_ide/src/impls.rs)11
-rw-r--r--crates/ra_ide/src/goto_type_definition.rs11
-rw-r--r--crates/ra_ide/src/hover.rs212
-rw-r--r--crates/ra_ide/src/inlay_hints.rs96
-rw-r--r--crates/ra_ide/src/join_lines.rs44
-rw-r--r--crates/ra_ide/src/lib.rs88
-rw-r--r--crates/ra_ide/src/marks.rs14
-rw-r--r--crates/ra_ide/src/matching_brace.rs13
-rw-r--r--crates/ra_ide/src/mock_analysis.rs123
-rw-r--r--crates/ra_ide/src/parent_module.rs20
-rw-r--r--crates/ra_ide/src/references.rs55
-rw-r--r--crates/ra_ide/src/references/rename.rs329
-rw-r--r--crates/ra_ide/src/runnables.rs511
-rw-r--r--crates/ra_ide/src/snapshots/highlight_injection.html2
-rw-r--r--crates/ra_ide/src/snapshots/highlight_strings.html7
-rw-r--r--crates/ra_ide/src/snapshots/highlight_unsafe.html48
-rw-r--r--crates/ra_ide/src/snapshots/highlighting.html31
-rw-r--r--crates/ra_ide/src/snapshots/rainbow_highlighting.html2
-rw-r--r--crates/ra_ide/src/source_change.rs119
-rw-r--r--crates/ra_ide/src/ssr.rs76
-rw-r--r--crates/ra_ide/src/status.rs11
-rw-r--r--crates/ra_ide/src/syntax_highlighting.rs256
-rw-r--r--crates/ra_ide/src/syntax_highlighting/html.rs2
-rw-r--r--crates/ra_ide/src/syntax_highlighting/tags.rs16
-rw-r--r--crates/ra_ide/src/syntax_highlighting/tests.rs53
-rw-r--r--crates/ra_ide/src/syntax_tree.rs93
-rw-r--r--crates/ra_ide/src/test_utils.rs21
-rw-r--r--crates/ra_ide/src/typing.rs67
-rw-r--r--crates/ra_ide/src/typing/on_enter.rs32
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
3use ra_assists::{resolved_assists, AssistAction, AssistLabel};
4use ra_db::{FilePosition, FileRange};
5use ra_ide_db::RootDatabase;
6
7use crate::{FileId, SourceChange, SourceFileEdit};
8
9pub use ra_assists::AssistId;
10
11#[derive(Debug)]
12pub struct Assist {
13 pub id: AssistId,
14 pub label: String,
15 pub group_label: Option<String>,
16 pub source_change: SourceChange,
17}
18
19pub(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
35fn 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};
8use test_utils::tested_by; 8use test_utils::mark;
9 9
10use crate::{CallInfo, FilePosition, FunctionSignature}; 10use 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)]
215mod tests { 215mod 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
3mod completion_config; 1mod completion_config;
4mod completion_item; 2mod completion_item;
5mod completion_context; 3mod 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).
60pub(crate) fn completions( 103pub(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
6use super::completion_context::CompletionContext; 6use ra_syntax::{ast, AstNode, SyntaxKind};
7use super::completion_item::{CompletionItem, CompletionItemKind, CompletionKind, Completions}; 7use rustc_hash::FxHashSet;
8use ra_syntax::{ 8
9 ast::{Attr, AttrKind}, 9use crate::completion::{
10 AstNode, 10 completion_context::CompletionContext,
11 completion_item::{CompletionItem, CompletionItemKind, CompletionKind, Completions},
11}; 12};
12 13
13pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) { 14pub(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
28fn 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
133fn 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
167fn 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
202fn 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
214struct 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)
221const 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)]
130mod tests { 234mod 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 2use ra_assists::utils::TryEnum;
3use ra_syntax::{ 3use ra_syntax::{
4 ast::{self, AstNode}, 4 ast::{self, AstNode},
5 TextRange, TextSize, 5 TextRange, TextSize,
6}; 6};
7use ra_text_edit::TextEdit; 7use ra_text_edit::TextEdit;
8 8
9use super::completion_config::SnippetCap;
10use crate::{ 9use 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
17use super::completion_config::SnippetCap;
18
18pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { 19pub(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
3use hir::{Adt, HasVisibility, PathResolution, ScopeDef}; 3use hir::{Adt, HasVisibility, PathResolution, ScopeDef};
4use ra_syntax::AstNode; 4use ra_syntax::AstNode;
5use test_utils::tested_by; 5use rustc_hash::FxHashSet;
6use test_utils::mark;
6 7
7use crate::completion::{CompletionContext, Completions}; 8use crate::completion::{CompletionContext, Completions};
8use rustc_hash::FxHashSet;
9 9
10pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionContext) { 10pub(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)]
144mod tests { 149mod 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)]
42mod 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
34use hir::{self, Docs, HasSource}; 34use hir::{self, Docs, HasSource};
35use ra_assists::utils::get_missing_impl_items; 35use ra_assists::utils::get_missing_assoc_items;
36use ra_syntax::{ 36use 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::{
49pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext) { 49pub(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
3use hir::ScopeDef; 3use hir::ScopeDef;
4use test_utils::tested_by; 4use test_utils::mark;
5 5
6use crate::completion::{CompletionContext, Completions}; 6use crate::completion::{CompletionContext, Completions};
7use hir::{Adt, ModuleDef, Type}; 7use hir::{Adt, ModuleDef, Type};
8use ra_syntax::AstNode; 8use ra_syntax::AstNode;
9 9
10pub(super) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) { 10pub(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)]
64mod tests { 67mod 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};
12use ra_text_edit::AtomTextEdit; 12use ra_text_edit::Indel;
13 13
14use crate::{call_info::ActiveParameter, completion::CompletionConfig, FilePosition}; 14use crate::{call_info::ActiveParameter, completion::CompletionConfig, FilePosition};
15use 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
64impl<'a> CompletionContext<'a> { 65impl<'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
3use std::fmt; 3use std::fmt;
4 4
5use super::completion_config::SnippetCap;
6use hir::Documentation; 5use hir::Documentation;
7use ra_syntax::TextRange; 6use ra_syntax::TextRange;
8use ra_text_edit::TextEdit; 7use ra_text_edit::TextEdit;
9 8
9use 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 @@
3use hir::{Docs, HasAttrs, HasSource, HirDisplay, ModPath, ScopeDef, StructKind, Type}; 3use hir::{Docs, HasAttrs, HasSource, HirDisplay, ModPath, ScopeDef, StructKind, Type};
4use ra_syntax::ast::NameOwner; 4use ra_syntax::ast::NameOwner;
5use stdx::SepBy; 5use stdx::SepBy;
6use test_utils::tested_by; 6use test_utils::mark;
7 7
8use crate::{ 8use crate::{
9 completion::{ 9 completion::{
@@ -17,12 +17,11 @@ use crate::{
17impl Completions { 17impl 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
320pub(crate) fn compute_score( 325pub(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)]
452mod tests { 458mod 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};
22use ra_text_edit::{TextEdit, TextEditBuilder}; 22use ra_text_edit::{TextEdit, TextEditBuilder};
23 23
24use crate::{Diagnostic, FileId, FileSystemEdit, SourceChange, SourceFileEdit}; 24use crate::{Diagnostic, FileId, FileSystemEdit, Fix, SourceChange, SourceFileEdit};
25 25
26#[derive(Debug, Copy, Clone)] 26#[derive(Debug, Copy, Clone)]
27pub enum Severity { 27pub 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)
3use std::{ 5use 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
14use crate::FileSymbol; 14use crate::{FileRange, FileSymbol};
15 15
16use super::short_label::ShortLabel; 16use 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)]
24pub struct NavigationTarget { 24pub 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 {
376impl ToNav for hir::TypeParam { 361impl 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
34impl ShortLabel for ast::TraitDef { 34impl 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
3use crate::TextRange;
4
5use ra_syntax::{ 1use 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// |===
21pub fn file_structure(file: &SourceFile) -> Vec<StructureNode> { 30pub 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
3use hir::Semantics; 1use hir::Semantics;
4use ra_ide_db::RootDatabase; 2use ra_ide_db::RootDatabase;
5use ra_syntax::{ 3use 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// |===
17pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<ExpandedMacro> { 24pub(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
3use std::iter::successors; 1use std::iter::successors;
4 2
5use hir::Semantics; 3use hir::Semantics;
@@ -14,6 +12,16 @@ use ra_syntax::{
14 12
15use crate::FileRange; 13use 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// |===
17pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange { 25pub(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
3use hir::Semantics; 1use hir::Semantics;
4use ra_ide_db::{ 2use 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// |===
20pub(crate) fn goto_definition( 27pub(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)]
95mod tests { 102mod 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
3use hir::{Crate, ImplDef, Semantics}; 1use hir::{Crate, ImplDef, Semantics};
4use ra_ide_db::RootDatabase; 2use ra_ide_db::RootDatabase;
5use ra_syntax::{algo::find_node_at_offset, ast, AstNode}; 3use ra_syntax::{algo::find_node_at_offset, ast, AstNode};
6 4
7use crate::{display::ToNav, FilePosition, NavigationTarget, RangeInfo}; 5use 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// |===
9pub(crate) fn goto_implementation( 16pub(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
3use ra_ide_db::RootDatabase; 1use ra_ide_db::RootDatabase;
4use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; 2use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset};
5 3
6use crate::{display::ToNav, FilePosition, NavigationTarget, RangeInfo}; 4use 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// |===
8pub(crate) fn goto_type_definition( 15pub(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 1use std::iter::once;
2//! source code items (e.g. function call, struct field, variable symbol...)
3 2
4use hir::{ 3use 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};
7use itertools::Itertools;
8use ra_db::SourceDatabase; 8use ra_db::SourceDatabase;
9use ra_ide_db::{ 9use 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};
24use itertools::Itertools;
25use 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.
67pub(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
65fn hover_text( 120fn 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
163pub(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
216fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { 218fn 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> {
417fn main() { 460fn 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 &["
464Option 507Option
508```
509
510```rust
465None 511None
466``` 512```
513___
467 514
468The None variant 515The None variant
469 " 516 "
@@ -483,8 +530,12 @@ The None variant
483 "#, 530 "#,
484 &[" 531 &["
485Option 532Option
533```
534
535```rust
486Some 536Some
487``` 537```
538___
488 539
489The Some variant 540The 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
3use hir::{Adt, HirDisplay, Semantics, Type}; 1use hir::{Adt, HirDisplay, Semantics, Type};
4use ra_ide_db::RootDatabase; 2use ra_ide_db::RootDatabase;
5use ra_prof::profile; 3use ra_prof::profile;
@@ -9,6 +7,7 @@ use ra_syntax::{
9}; 7};
10 8
11use crate::{FileId, FunctionSignature}; 9use crate::{FileId, FunctionSignature};
10use stdx::to_lower_snake_case;
12 11
13#[derive(Clone, Debug, PartialEq, Eq)] 12#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct InlayHintsConfig { 13pub 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// |===
41pub(crate) fn inlay_hints( 60pub(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
182fn pat_is_enum_variant(db: &RootDatabase, bind_pat: &ast::BindPat, pat_ty: &Type) -> bool { 201fn 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
233fn should_show_param_hint( 252fn should_show_param_name_hint(
253 sema: &Semantics<RootDatabase>,
234 fn_signature: &FunctionSignature, 254 fn_signature: &FunctionSignature,
235 param_name: &str,