diff options
Diffstat (limited to 'crates/ra_ide')
24 files changed, 487 insertions, 329 deletions
diff --git a/crates/ra_ide/src/assists.rs b/crates/ra_ide/src/assists.rs deleted file mode 100644 index 2b5d11681..000000000 --- a/crates/ra_ide/src/assists.rs +++ /dev/null | |||
@@ -1,47 +0,0 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use ra_assists::{resolved_assists, AssistAction, AssistLabel}; | ||
4 | use ra_db::{FilePosition, FileRange}; | ||
5 | use ra_ide_db::RootDatabase; | ||
6 | |||
7 | use crate::{FileId, SourceChange, SourceFileEdit}; | ||
8 | |||
9 | pub use ra_assists::AssistId; | ||
10 | |||
11 | #[derive(Debug)] | ||
12 | pub struct Assist { | ||
13 | pub id: AssistId, | ||
14 | pub label: String, | ||
15 | pub group_label: Option<String>, | ||
16 | pub source_change: SourceChange, | ||
17 | } | ||
18 | |||
19 | pub(crate) fn assists(db: &RootDatabase, frange: FileRange) -> Vec<Assist> { | ||
20 | resolved_assists(db, frange) | ||
21 | .into_iter() | ||
22 | .map(|assist| { | ||
23 | let file_id = frange.file_id; | ||
24 | let assist_label = &assist.label; | ||
25 | Assist { | ||
26 | id: assist_label.id, | ||
27 | label: assist_label.label.clone(), | ||
28 | group_label: assist.group_label.map(|it| it.0), | ||
29 | source_change: action_to_edit(assist.action, file_id, assist_label), | ||
30 | } | ||
31 | }) | ||
32 | .collect() | ||
33 | } | ||
34 | |||
35 | fn action_to_edit( | ||
36 | action: AssistAction, | ||
37 | file_id: FileId, | ||
38 | assist_label: &AssistLabel, | ||
39 | ) -> SourceChange { | ||
40 | let file_id = match action.file { | ||
41 | ra_assists::AssistFile::TargetFile(it) => it, | ||
42 | _ => file_id, | ||
43 | }; | ||
44 | let file_edit = SourceFileEdit { file_id, edit: action.edit }; | ||
45 | SourceChange::source_file_edit(assist_label.label.clone(), file_edit) | ||
46 | .with_cursor_opt(action.cursor_position.map(|offset| FilePosition { offset, file_id })) | ||
47 | } | ||
diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs index a0e06faa2..8bdc43b1a 100644 --- a/crates/ra_ide/src/completion.rs +++ b/crates/ra_ide/src/completion.rs | |||
@@ -65,23 +65,20 @@ pub(crate) fn completions( | |||
65 | let ctx = CompletionContext::new(db, position, config)?; | 65 | let ctx = CompletionContext::new(db, position, config)?; |
66 | 66 | ||
67 | let mut acc = Completions::default(); | 67 | let mut acc = Completions::default(); |
68 | if ctx.attribute_under_caret.is_some() { | 68 | complete_attribute::complete_attribute(&mut acc, &ctx); |
69 | complete_attribute::complete_attribute(&mut acc, &ctx); | 69 | complete_fn_param::complete_fn_param(&mut acc, &ctx); |
70 | } else { | 70 | complete_keyword::complete_expr_keyword(&mut acc, &ctx); |
71 | complete_fn_param::complete_fn_param(&mut acc, &ctx); | 71 | complete_keyword::complete_use_tree_keyword(&mut acc, &ctx); |
72 | complete_keyword::complete_expr_keyword(&mut acc, &ctx); | 72 | complete_snippet::complete_expr_snippet(&mut acc, &ctx); |
73 | complete_keyword::complete_use_tree_keyword(&mut acc, &ctx); | 73 | complete_snippet::complete_item_snippet(&mut acc, &ctx); |
74 | complete_snippet::complete_expr_snippet(&mut acc, &ctx); | 74 | complete_qualified_path::complete_qualified_path(&mut acc, &ctx); |
75 | complete_snippet::complete_item_snippet(&mut acc, &ctx); | 75 | complete_unqualified_path::complete_unqualified_path(&mut acc, &ctx); |
76 | complete_qualified_path::complete_qualified_path(&mut acc, &ctx); | 76 | complete_dot::complete_dot(&mut acc, &ctx); |
77 | complete_unqualified_path::complete_unqualified_path(&mut acc, &ctx); | 77 | complete_record::complete_record(&mut acc, &ctx); |
78 | complete_dot::complete_dot(&mut acc, &ctx); | 78 | complete_pattern::complete_pattern(&mut acc, &ctx); |
79 | complete_record::complete_record(&mut acc, &ctx); | 79 | complete_postfix::complete_postfix(&mut acc, &ctx); |
80 | complete_pattern::complete_pattern(&mut acc, &ctx); | 80 | complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx); |
81 | complete_postfix::complete_postfix(&mut acc, &ctx); | 81 | complete_trait_impl::complete_trait_impl(&mut acc, &ctx); |
82 | complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx); | ||
83 | complete_trait_impl::complete_trait_impl(&mut acc, &ctx); | ||
84 | } | ||
85 | 82 | ||
86 | Some(acc) | 83 | Some(acc) |
87 | } | 84 | } |
diff --git a/crates/ra_ide/src/completion/complete_attribute.rs b/crates/ra_ide/src/completion/complete_attribute.rs index 20e6edc17..f17266221 100644 --- a/crates/ra_ide/src/completion/complete_attribute.rs +++ b/crates/ra_ide/src/completion/complete_attribute.rs | |||
@@ -3,20 +3,21 @@ | |||
3 | //! This module uses a bit of static metadata to provide completions | 3 | //! This module uses a bit of static metadata to provide completions |
4 | //! for built-in attributes. | 4 | //! for built-in attributes. |
5 | 5 | ||
6 | use super::completion_context::CompletionContext; | 6 | use ra_syntax::{ast, AstNode, SyntaxKind}; |
7 | use super::completion_item::{CompletionItem, CompletionItemKind, CompletionKind, Completions}; | ||
8 | use ast::AttrInput; | ||
9 | use ra_syntax::{ | ||
10 | ast::{self, AttrKind}, | ||
11 | AstNode, SyntaxKind, | ||
12 | }; | ||
13 | use rustc_hash::FxHashSet; | 7 | use rustc_hash::FxHashSet; |
14 | 8 | ||
9 | use crate::completion::{ | ||
10 | completion_context::CompletionContext, | ||
11 | completion_item::{CompletionItem, CompletionItemKind, CompletionKind, Completions}, | ||
12 | }; | ||
13 | |||
15 | pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { | 14 | pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { |
16 | let attribute = ctx.attribute_under_caret.as_ref()?; | 15 | let attribute = ctx.attribute_under_caret.as_ref()?; |
17 | 16 | ||
18 | match (attribute.path(), attribute.input()) { | 17 | match (attribute.path(), attribute.input()) { |
19 | (Some(path), Some(AttrInput::TokenTree(token_tree))) if path.to_string() == "derive" => { | 18 | (Some(path), Some(ast::AttrInput::TokenTree(token_tree))) |
19 | if path.to_string() == "derive" => | ||
20 | { | ||
20 | complete_derive(acc, ctx, token_tree) | 21 | complete_derive(acc, ctx, token_tree) |
21 | } | 22 | } |
22 | _ => complete_attribute_start(acc, ctx, attribute), | 23 | _ => complete_attribute_start(acc, ctx, attribute), |
@@ -40,7 +41,7 @@ fn complete_attribute_start(acc: &mut Completions, ctx: &CompletionContext, attr | |||
40 | _ => {} | 41 | _ => {} |
41 | } | 42 | } |
42 | 43 | ||
43 | if attribute.kind() == AttrKind::Inner || !attr_completion.should_be_inner { | 44 | if attribute.kind() == ast::AttrKind::Inner || !attr_completion.should_be_inner { |
44 | acc.add(item); | 45 | acc.add(item); |
45 | } | 46 | } |
46 | } | 47 | } |
diff --git a/crates/ra_ide/src/completion/complete_qualified_path.rs b/crates/ra_ide/src/completion/complete_qualified_path.rs index aa56a5cd8..7fcd22525 100644 --- a/crates/ra_ide/src/completion/complete_qualified_path.rs +++ b/crates/ra_ide/src/completion/complete_qualified_path.rs | |||
@@ -2,16 +2,21 @@ | |||
2 | 2 | ||
3 | use hir::{Adt, HasVisibility, PathResolution, ScopeDef}; | 3 | use hir::{Adt, HasVisibility, PathResolution, ScopeDef}; |
4 | use ra_syntax::AstNode; | 4 | use ra_syntax::AstNode; |
5 | use rustc_hash::FxHashSet; | ||
5 | use test_utils::tested_by; | 6 | use test_utils::tested_by; |
6 | 7 | ||
7 | use crate::completion::{CompletionContext, Completions}; | 8 | use crate::completion::{CompletionContext, Completions}; |
8 | use rustc_hash::FxHashSet; | ||
9 | 9 | ||
10 | pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionContext) { | 10 | pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionContext) { |
11 | let path = match &ctx.path_prefix { | 11 | let path = match &ctx.path_prefix { |
12 | Some(path) => path.clone(), | 12 | Some(path) => path.clone(), |
13 | _ => return, | 13 | None => return, |
14 | }; | 14 | }; |
15 | |||
16 | if ctx.attribute_under_caret.is_some() { | ||
17 | return; | ||
18 | } | ||
19 | |||
15 | let scope = ctx.scope(); | 20 | let scope = ctx.scope(); |
16 | let context_module = scope.module(); | 21 | let context_module = scope.module(); |
17 | 22 | ||
@@ -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 | } |
@@ -1325,4 +1330,18 @@ mod tests { | |||
1325 | "### | 1330 | "### |
1326 | ); | 1331 | ); |
1327 | } | 1332 | } |
1333 | |||
1334 | #[test] | ||
1335 | fn dont_complete_attr() { | ||
1336 | assert_debug_snapshot!( | ||
1337 | do_reference_completion( | ||
1338 | r" | ||
1339 | mod foo { pub struct Foo; } | ||
1340 | #[foo::<|>] | ||
1341 | fn f() {} | ||
1342 | " | ||
1343 | ), | ||
1344 | @r###"[]"### | ||
1345 | ) | ||
1346 | } | ||
1328 | } | 1347 | } |
diff --git a/crates/ra_ide/src/completion/complete_trait_impl.rs b/crates/ra_ide/src/completion/complete_trait_impl.rs index ee32d1ff6..039df03e0 100644 --- a/crates/ra_ide/src/completion/complete_trait_impl.rs +++ b/crates/ra_ide/src/completion/complete_trait_impl.rs | |||
@@ -32,7 +32,7 @@ | |||
32 | //! ``` | 32 | //! ``` |
33 | 33 | ||
34 | use hir::{self, Docs, HasSource}; | 34 | use hir::{self, Docs, HasSource}; |
35 | use ra_assists::utils::get_missing_impl_items; | 35 | use ra_assists::utils::get_missing_assoc_items; |
36 | use ra_syntax::{ | 36 | use ra_syntax::{ |
37 | ast::{self, edit, ImplDef}, | 37 | ast::{self, edit, ImplDef}, |
38 | AstNode, SyntaxKind, SyntaxNode, TextRange, T, | 38 | AstNode, SyntaxKind, SyntaxNode, TextRange, T, |
@@ -50,7 +50,7 @@ pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext | |||
50 | if let Some((trigger, impl_def)) = completion_match(ctx) { | 50 | if let Some((trigger, impl_def)) = completion_match(ctx) { |
51 | match trigger.kind() { | 51 | match trigger.kind() { |
52 | SyntaxKind::NAME_REF => { | 52 | SyntaxKind::NAME_REF => { |
53 | get_missing_impl_items(&ctx.sema, &impl_def).iter().for_each(|item| match item { | 53 | get_missing_assoc_items(&ctx.sema, &impl_def).iter().for_each(|item| match item { |
54 | hir::AssocItem::Function(fn_item) => { | 54 | hir::AssocItem::Function(fn_item) => { |
55 | add_function_impl(&trigger, acc, ctx, &fn_item) | 55 | add_function_impl(&trigger, acc, ctx, &fn_item) |
56 | } | 56 | } |
@@ -64,34 +64,40 @@ pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext | |||
64 | } | 64 | } |
65 | 65 | ||
66 | SyntaxKind::FN_DEF => { | 66 | SyntaxKind::FN_DEF => { |
67 | for missing_fn in get_missing_impl_items(&ctx.sema, &impl_def).iter().filter_map( | 67 | for missing_fn in |
68 | |item| match item { | 68 | get_missing_assoc_items(&ctx.sema, &impl_def).iter().filter_map(|item| { |
69 | hir::AssocItem::Function(fn_item) => Some(fn_item), | 69 | match item { |
70 | _ => None, | 70 | hir::AssocItem::Function(fn_item) => Some(fn_item), |
71 | }, | 71 | _ => None, |
72 | ) { | 72 | } |
73 | }) | ||
74 | { | ||
73 | add_function_impl(&trigger, acc, ctx, &missing_fn); | 75 | add_function_impl(&trigger, acc, ctx, &missing_fn); |
74 | } | 76 | } |
75 | } | 77 | } |
76 | 78 | ||
77 | SyntaxKind::TYPE_ALIAS_DEF => { | 79 | SyntaxKind::TYPE_ALIAS_DEF => { |
78 | for missing_fn in get_missing_impl_items(&ctx.sema, &impl_def).iter().filter_map( | 80 | for missing_fn in |
79 | |item| match item { | 81 | get_missing_assoc_items(&ctx.sema, &impl_def).iter().filter_map(|item| { |
80 | hir::AssocItem::TypeAlias(type_item) => Some(type_item), | 82 | match item { |
81 | _ => None, | 83 | hir::AssocItem::TypeAlias(type_item) => Some(type_item), |
82 | }, | 84 | _ => None, |
83 | ) { | 85 | } |
86 | }) | ||
87 | { | ||
84 | add_type_alias_impl(&trigger, acc, ctx, &missing_fn); | 88 | add_type_alias_impl(&trigger, acc, ctx, &missing_fn); |
85 | } | 89 | } |
86 | } | 90 | } |
87 | 91 | ||
88 | SyntaxKind::CONST_DEF => { | 92 | SyntaxKind::CONST_DEF => { |
89 | for missing_fn in get_missing_impl_items(&ctx.sema, &impl_def).iter().filter_map( | 93 | for missing_fn in |
90 | |item| match item { | 94 | get_missing_assoc_items(&ctx.sema, &impl_def).iter().filter_map(|item| { |
91 | hir::AssocItem::Const(const_item) => Some(const_item), | 95 | match item { |
92 | _ => None, | 96 | hir::AssocItem::Const(const_item) => Some(const_item), |
93 | }, | 97 | _ => None, |
94 | ) { | 98 | } |
99 | }) | ||
100 | { | ||
95 | add_const_impl(&trigger, acc, ctx, &missing_fn); | 101 | add_const_impl(&trigger, acc, ctx, &missing_fn); |
96 | } | 102 | } |
97 | } | 103 | } |
diff --git a/crates/ra_ide/src/completion/complete_unqualified_path.rs b/crates/ra_ide/src/completion/complete_unqualified_path.rs index a6a5568de..bd40af1cb 100644 --- a/crates/ra_ide/src/completion/complete_unqualified_path.rs +++ b/crates/ra_ide/src/completion/complete_unqualified_path.rs | |||
@@ -8,9 +8,12 @@ use hir::{Adt, ModuleDef, Type}; | |||
8 | use ra_syntax::AstNode; | 8 | use ra_syntax::AstNode; |
9 | 9 | ||
10 | pub(super) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) { | 10 | pub(super) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) { |
11 | if (!ctx.is_trivial_path && !ctx.is_pat_binding_or_const) | 11 | if !(ctx.is_trivial_path || ctx.is_pat_binding_or_const) { |
12 | || ctx.record_lit_syntax.is_some() | 12 | return; |
13 | } | ||
14 | if ctx.record_lit_syntax.is_some() | ||
13 | || ctx.record_pat_syntax.is_some() | 15 | || ctx.record_pat_syntax.is_some() |
16 | || ctx.attribute_under_caret.is_some() | ||
14 | { | 17 | { |
15 | return; | 18 | return; |
16 | } | 19 | } |
@@ -1369,4 +1372,18 @@ mod tests { | |||
1369 | "### | 1372 | "### |
1370 | ) | 1373 | ) |
1371 | } | 1374 | } |
1375 | |||
1376 | #[test] | ||
1377 | fn dont_complete_attr() { | ||
1378 | assert_debug_snapshot!( | ||
1379 | do_reference_completion( | ||
1380 | r" | ||
1381 | struct Foo; | ||
1382 | #[<|>] | ||
1383 | fn f() {} | ||
1384 | " | ||
1385 | ), | ||
1386 | @r###"[]"### | ||
1387 | ) | ||
1388 | } | ||
1372 | } | 1389 | } |
diff --git a/crates/ra_ide/src/completion/completion_context.rs b/crates/ra_ide/src/completion/completion_context.rs index dd87bd119..b6b9627de 100644 --- a/crates/ra_ide/src/completion/completion_context.rs +++ b/crates/ra_ide/src/completion/completion_context.rs | |||
@@ -9,7 +9,7 @@ use ra_syntax::{ | |||
9 | SyntaxKind::*, | 9 | SyntaxKind::*, |
10 | SyntaxNode, SyntaxToken, TextRange, TextSize, | 10 | SyntaxNode, SyntaxToken, TextRange, TextSize, |
11 | }; | 11 | }; |
12 | use ra_text_edit::AtomTextEdit; | 12 | use ra_text_edit::Indel; |
13 | 13 | ||
14 | use crate::{call_info::ActiveParameter, completion::CompletionConfig, FilePosition}; | 14 | use crate::{call_info::ActiveParameter, completion::CompletionConfig, FilePosition}; |
15 | 15 | ||
@@ -76,7 +76,7 @@ impl<'a> CompletionContext<'a> { | |||
76 | // actual completion. | 76 | // actual completion. |
77 | let file_with_fake_ident = { | 77 | let file_with_fake_ident = { |
78 | let parse = db.parse(position.file_id); | 78 | let parse = db.parse(position.file_id); |
79 | let edit = AtomTextEdit::insert(position.offset, "intellijRulezz".to_string()); | 79 | let edit = Indel::insert(position.offset, "intellijRulezz".to_string()); |
80 | parse.reparse(&edit).tree() | 80 | parse.reparse(&edit).tree() |
81 | }; | 81 | }; |
82 | let fake_ident_token = | 82 | let fake_ident_token = |
diff --git a/crates/ra_ide/src/completion/completion_item.rs b/crates/ra_ide/src/completion/completion_item.rs index 5936fb8f7..6021f7279 100644 --- a/crates/ra_ide/src/completion/completion_item.rs +++ b/crates/ra_ide/src/completion/completion_item.rs | |||
@@ -2,11 +2,12 @@ | |||
2 | 2 | ||
3 | use std::fmt; | 3 | use std::fmt; |
4 | 4 | ||
5 | use super::completion_config::SnippetCap; | ||
6 | use hir::Documentation; | 5 | use hir::Documentation; |
7 | use ra_syntax::TextRange; | 6 | use ra_syntax::TextRange; |
8 | use ra_text_edit::TextEdit; | 7 | use ra_text_edit::TextEdit; |
9 | 8 | ||
9 | use crate::completion::completion_config::SnippetCap; | ||
10 | |||
10 | /// `CompletionItem` describes a single completion variant in the editor pop-up. | 11 | /// `CompletionItem` describes a single completion variant in the editor pop-up. |
11 | /// It is basically a POD with various properties. To construct a | 12 | /// It is basically a POD with various properties. To construct a |
12 | /// `CompletionItem`, use `new` method and the `Builder` struct. | 13 | /// `CompletionItem`, use `new` method and the `Builder` struct. |
@@ -62,8 +63,8 @@ impl fmt::Debug for CompletionItem { | |||
62 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | 63 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
63 | let mut s = f.debug_struct("CompletionItem"); | 64 | let mut s = f.debug_struct("CompletionItem"); |
64 | s.field("label", &self.label()).field("source_range", &self.source_range()); | 65 | s.field("label", &self.label()).field("source_range", &self.source_range()); |
65 | if self.text_edit().as_atoms().len() == 1 { | 66 | if self.text_edit().as_indels().len() == 1 { |
66 | let atom = &self.text_edit().as_atoms()[0]; | 67 | let atom = &self.text_edit().as_indels()[0]; |
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/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs index a6b4c2c28..87a0b80f1 100644 --- a/crates/ra_ide/src/diagnostics.rs +++ b/crates/ra_ide/src/diagnostics.rs | |||
@@ -64,7 +64,7 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic> | |||
64 | .unwrap_or_else(|| RelativePath::new("")) | 64 | .unwrap_or_else(|| RelativePath::new("")) |
65 | .join(&d.candidate); | 65 | .join(&d.candidate); |
66 | let create_file = FileSystemEdit::CreateFile { source_root, path }; | 66 | let create_file = FileSystemEdit::CreateFile { source_root, path }; |
67 | let fix = SourceChange::file_system_edit("create module", create_file); | 67 | let fix = SourceChange::file_system_edit("Create module", create_file); |
68 | res.borrow_mut().push(Diagnostic { | 68 | res.borrow_mut().push(Diagnostic { |
69 | range: sema.diagnostics_range(d).range, | 69 | range: sema.diagnostics_range(d).range, |
70 | message: d.message(), | 70 | message: d.message(), |
@@ -92,7 +92,7 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic> | |||
92 | algo::diff(&d.ast(db).syntax(), &field_list.syntax()).into_text_edit(&mut builder); | 92 | algo::diff(&d.ast(db).syntax(), &field_list.syntax()).into_text_edit(&mut builder); |
93 | 93 | ||
94 | Some(SourceChange::source_file_edit_from( | 94 | Some(SourceChange::source_file_edit_from( |
95 | "fill struct fields", | 95 | "Fill struct fields", |
96 | file_id, | 96 | file_id, |
97 | builder.finish(), | 97 | builder.finish(), |
98 | )) | 98 | )) |
@@ -117,7 +117,7 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic> | |||
117 | let node = d.ast(db); | 117 | let node = d.ast(db); |
118 | let replacement = format!("Ok({})", node.syntax()); | 118 | let replacement = format!("Ok({})", node.syntax()); |
119 | let edit = TextEdit::replace(node.syntax().text_range(), replacement); | 119 | let edit = TextEdit::replace(node.syntax().text_range(), replacement); |
120 | let fix = SourceChange::source_file_edit_from("wrap with ok", file_id, edit); | 120 | let fix = SourceChange::source_file_edit_from("Wrap with ok", file_id, edit); |
121 | res.borrow_mut().push(Diagnostic { | 121 | res.borrow_mut().push(Diagnostic { |
122 | range: sema.diagnostics_range(d).range, | 122 | range: sema.diagnostics_range(d).range, |
123 | message: d.message(), | 123 | message: d.message(), |
@@ -199,7 +199,7 @@ fn check_struct_shorthand_initialization( | |||
199 | message: "Shorthand struct initialization".to_string(), | 199 | message: "Shorthand struct initialization".to_string(), |
200 | severity: Severity::WeakWarning, | 200 | severity: Severity::WeakWarning, |
201 | fix: Some(SourceChange::source_file_edit( | 201 | fix: Some(SourceChange::source_file_edit( |
202 | "use struct shorthand initialization", | 202 | "Use struct shorthand initialization", |
203 | SourceFileEdit { file_id, edit }, | 203 | SourceFileEdit { file_id, edit }, |
204 | )), | 204 | )), |
205 | }); | 205 | }); |
@@ -241,7 +241,11 @@ mod tests { | |||
241 | diagnostics.pop().unwrap_or_else(|| panic!("no diagnostics for:\n{}\n", before)); | 241 | diagnostics.pop().unwrap_or_else(|| panic!("no diagnostics for:\n{}\n", before)); |
242 | let mut fix = diagnostic.fix.unwrap(); | 242 | let mut fix = diagnostic.fix.unwrap(); |
243 | let edit = fix.source_file_edits.pop().unwrap().edit; | 243 | let edit = fix.source_file_edits.pop().unwrap().edit; |
244 | let actual = edit.apply(&before); | 244 | let actual = { |
245 | let mut actual = before.to_string(); | ||
246 | edit.apply(&mut actual); | ||
247 | actual | ||
248 | }; | ||
245 | assert_eq_text!(after, &actual); | 249 | assert_eq_text!(after, &actual); |
246 | } | 250 | } |
247 | 251 | ||
@@ -256,7 +260,11 @@ mod tests { | |||
256 | let mut fix = diagnostic.fix.unwrap(); | 260 | let mut fix = diagnostic.fix.unwrap(); |
257 | let edit = fix.source_file_edits.pop().unwrap().edit; | 261 | let edit = fix.source_file_edits.pop().unwrap().edit; |
258 | let target_file_contents = analysis.file_text(file_position.file_id).unwrap(); | 262 | let target_file_contents = analysis.file_text(file_position.file_id).unwrap(); |
259 | let actual = edit.apply(&target_file_contents); | 263 | let actual = { |
264 | let mut actual = target_file_contents.to_string(); | ||
265 | edit.apply(&mut actual); | ||
266 | actual | ||
267 | }; | ||
260 | 268 | ||
261 | // Strip indent and empty lines from `after`, to match the behaviour of | 269 | // Strip indent and empty lines from `after`, to match the behaviour of |
262 | // `parse_fixture` called from `analysis_and_position`. | 270 | // `parse_fixture` called from `analysis_and_position`. |
@@ -288,7 +296,11 @@ mod tests { | |||
288 | let diagnostic = analysis.diagnostics(file_id).unwrap().pop().unwrap(); | 296 | let diagnostic = analysis.diagnostics(file_id).unwrap().pop().unwrap(); |
289 | let mut fix = diagnostic.fix.unwrap(); | 297 | let mut fix = diagnostic.fix.unwrap(); |
290 | let edit = fix.source_file_edits.pop().unwrap().edit; | 298 | let edit = fix.source_file_edits.pop().unwrap().edit; |
291 | let actual = edit.apply(&before); | 299 | let actual = { |
300 | let mut actual = before.to_string(); | ||
301 | edit.apply(&mut actual); | ||
302 | actual | ||
303 | }; | ||
292 | assert_eq_text!(after, &actual); | 304 | assert_eq_text!(after, &actual); |
293 | } | 305 | } |
294 | 306 | ||
@@ -606,7 +618,7 @@ mod tests { | |||
606 | range: 0..8, | 618 | range: 0..8, |
607 | fix: Some( | 619 | fix: Some( |
608 | SourceChange { | 620 | SourceChange { |
609 | label: "create module", | 621 | label: "Create module", |
610 | source_file_edits: [], | 622 | source_file_edits: [], |
611 | file_system_edits: [ | 623 | file_system_edits: [ |
612 | CreateFile { | 624 | CreateFile { |
@@ -655,17 +667,17 @@ mod tests { | |||
655 | range: 224..233, | 667 | range: 224..233, |
656 | fix: Some( | 668 | fix: Some( |
657 | SourceChange { | 669 | SourceChange { |
658 | label: "fill struct fields", | 670 | label: "Fill struct fields", |
659 | source_file_edits: [ | 671 | source_file_edits: [ |
660 | SourceFileEdit { | 672 | SourceFileEdit { |
661 | file_id: FileId( | 673 | file_id: FileId( |
662 | 1, | 674 | 1, |
663 | ), | 675 | ), |
664 | edit: TextEdit { | 676 | edit: TextEdit { |
665 | atoms: [ | 677 | indels: [ |
666 | AtomTextEdit { | 678 | Indel { |
667 | delete: 3..9, | ||
668 | insert: "{a:42, b: ()}", | 679 | insert: "{a:42, b: ()}", |
680 | delete: 3..9, | ||
669 | }, | 681 | }, |
670 | ], | 682 | ], |
671 | }, | 683 | }, |
diff --git a/crates/ra_ide/src/display/function_signature.rs b/crates/ra_ide/src/display/function_signature.rs index db3907fe6..f16d42276 100644 --- a/crates/ra_ide/src/display/function_signature.rs +++ b/crates/ra_ide/src/display/function_signature.rs | |||
@@ -1,5 +1,7 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! FIXME: write short doc here |
2 | 2 | ||
3 | // FIXME: this modules relies on strings and AST way too much, and it should be | ||
4 | // rewritten (matklad 2020-05-07) | ||
3 | use std::{ | 5 | use std::{ |
4 | convert::From, | 6 | convert::From, |
5 | fmt::{self, Display}, | 7 | fmt::{self, Display}, |
@@ -202,7 +204,11 @@ impl From<&'_ ast::FnDef> for FunctionSignature { | |||
202 | 204 | ||
203 | res.extend(param_list.params().map(|param| param.syntax().text().to_string())); | 205 | res.extend(param_list.params().map(|param| param.syntax().text().to_string())); |
204 | res_types.extend(param_list.params().map(|param| { | 206 | res_types.extend(param_list.params().map(|param| { |
205 | param.syntax().text().to_string().split(':').nth(1).unwrap()[1..].to_string() | 207 | let param_text = param.syntax().text().to_string(); |
208 | match param_text.split(':').nth(1) { | ||
209 | Some(it) => it[1..].to_string(), | ||
210 | None => param_text, | ||
211 | } | ||
206 | })); | 212 | })); |
207 | } | 213 | } |
208 | (has_self_param, res, res_types) | 214 | (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..de35c6711 100644 --- a/crates/ra_ide/src/display/navigation_target.rs +++ b/crates/ra_ide/src/display/navigation_target.rs | |||
@@ -376,16 +376,20 @@ impl ToNav for hir::Local { | |||
376 | impl ToNav for hir::TypeParam { | 376 | impl ToNav for hir::TypeParam { |
377 | fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { | 377 | fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { |
378 | let src = self.source(db); | 378 | let src = self.source(db); |
379 | let range = match src.value { | 379 | let full_range = match &src.value { |
380 | Either::Left(it) => it.syntax().text_range(), | 380 | Either::Left(it) => it.syntax().text_range(), |
381 | Either::Right(it) => it.syntax().text_range(), | 381 | Either::Right(it) => it.syntax().text_range(), |
382 | }; | 382 | }; |
383 | let focus_range = match &src.value { | ||
384 | Either::Left(_) => None, | ||
385 | Either::Right(it) => it.name().map(|it| it.syntax().text_range()), | ||
386 | }; | ||
383 | NavigationTarget { | 387 | NavigationTarget { |
384 | file_id: src.file_id.original_file(db), | 388 | file_id: src.file_id.original_file(db), |
385 | name: self.name(db).to_string().into(), | 389 | name: self.name(db).to_string().into(), |
386 | kind: TYPE_PARAM, | 390 | kind: TYPE_PARAM, |
387 | full_range: range, | 391 | full_range, |
388 | focus_range: None, | 392 | focus_range, |
389 | container_name: None, | 393 | container_name: None, |
390 | description: None, | 394 | description: None, |
391 | docs: None, | 395 | docs: None, |
diff --git a/crates/ra_ide/src/goto_definition.rs b/crates/ra_ide/src/goto_definition.rs index 1dfca819d..150895abb 100644 --- a/crates/ra_ide/src/goto_definition.rs +++ b/crates/ra_ide/src/goto_definition.rs | |||
@@ -244,6 +244,39 @@ mod tests { | |||
244 | } | 244 | } |
245 | 245 | ||
246 | #[test] | 246 | #[test] |
247 | fn goto_def_for_use_alias() { | ||
248 | covers!(ra_ide_db::goto_def_for_use_alias); | ||
249 | check_goto( | ||
250 | " | ||
251 | //- /lib.rs | ||
252 | use foo as bar<|>; | ||
253 | |||
254 | |||
255 | //- /foo/lib.rs | ||
256 | #[macro_export] | ||
257 | macro_rules! foo { () => { () } }", | ||
258 | "SOURCE_FILE FileId(2) 0..50", | ||
259 | "#[macro_export]\nmacro_rules! foo { () => { () } }\n", | ||
260 | ); | ||
261 | } | ||
262 | |||
263 | #[test] | ||
264 | fn goto_def_for_use_alias_foo_macro() { | ||
265 | check_goto( | ||
266 | " | ||
267 | //- /lib.rs | ||
268 | use foo::foo as bar<|>; | ||
269 | |||
270 | //- /foo/lib.rs | ||
271 | #[macro_export] | ||
272 | macro_rules! foo { () => { () } } | ||
273 | ", | ||
274 | "foo MACRO_CALL FileId(2) 0..49 29..32", | ||
275 | "#[macro_export]\nmacro_rules! foo { () => { () } }|foo", | ||
276 | ); | ||
277 | } | ||
278 | |||
279 | #[test] | ||
247 | fn goto_def_for_macros_in_use_tree() { | 280 | fn goto_def_for_macros_in_use_tree() { |
248 | check_goto( | 281 | check_goto( |
249 | " | 282 | " |
@@ -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 | ||
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index 54d318858..06d4f1c63 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs | |||
@@ -143,7 +143,7 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<Strin | |||
143 | ModuleDef::TypeAlias(it) => from_def_source(db, it, mod_path), | 143 | ModuleDef::TypeAlias(it) => from_def_source(db, it, mod_path), |
144 | ModuleDef::BuiltinType(it) => Some(it.to_string()), | 144 | ModuleDef::BuiltinType(it) => Some(it.to_string()), |
145 | }, | 145 | }, |
146 | Definition::Local(it) => Some(rust_code_markup(&it.ty(db).display_truncated(db, None))), | 146 | Definition::Local(it) => Some(rust_code_markup(&it.ty(db).display(db))), |
147 | Definition::TypeParam(_) | Definition::SelfType(_) => { | 147 | Definition::TypeParam(_) | Definition::SelfType(_) => { |
148 | // FIXME: Hover for generic param | 148 | // FIXME: Hover for generic param |
149 | None | 149 | None |
@@ -208,7 +208,7 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn | |||
208 | } | 208 | } |
209 | }?; | 209 | }?; |
210 | 210 | ||
211 | res.extend(Some(rust_code_markup(&ty.display_truncated(db, None)))); | 211 | res.extend(Some(rust_code_markup(&ty.display(db)))); |
212 | let range = sema.original_range(&node).range; | 212 | let range = sema.original_range(&node).range; |
213 | Some(RangeInfo::new(range, res)) | 213 | Some(RangeInfo::new(range, res)) |
214 | } | 214 | } |
@@ -280,6 +280,47 @@ mod tests { | |||
280 | } | 280 | } |
281 | 281 | ||
282 | #[test] | 282 | #[test] |
283 | fn hover_shows_long_type_of_an_expression() { | ||
284 | check_hover_result( | ||
285 | r#" | ||
286 | //- /main.rs | ||
287 | struct Scan<A, B, C> { | ||
288 | a: A, | ||
289 | b: B, | ||
290 | c: C, | ||
291 | } | ||
292 | |||
293 | struct FakeIter<I> { | ||
294 | inner: I, | ||
295 | } | ||
296 | |||
297 | struct OtherStruct<T> { | ||
298 | i: T, | ||
299 | } | ||
300 | |||
301 | enum FakeOption<T> { | ||
302 | Some(T), | ||
303 | None, | ||
304 | } | ||
305 | |||
306 | fn scan<A, B, C>(a: A, b: B, c: C) -> FakeIter<Scan<OtherStruct<A>, B, C>> { | ||
307 | FakeIter { inner: Scan { a, b, c } } | ||
308 | } | ||
309 | |||
310 | fn main() { | ||
311 | let num: i32 = 55; | ||
312 | let closure = |memo: &mut u32, value: &u32, _another: &mut u32| -> FakeOption<u32> { | ||
313 | FakeOption::Some(*memo + value) | ||
314 | }; | ||
315 | let number = 5u32; | ||
316 | let mut iter<|> = scan(OtherStruct { i: num }, closure, number); | ||
317 | } | ||
318 | "#, | ||
319 | &["FakeIter<Scan<OtherStruct<OtherStruct<i32>>, |&mut u32, &u32, &mut u32| -> FakeOption<u32>, u32>>"], | ||
320 | ); | ||
321 | } | ||
322 | |||
323 | #[test] | ||
283 | fn hover_shows_fn_signature() { | 324 | fn hover_shows_fn_signature() { |
284 | // Single file with result | 325 | // Single file with result |
285 | check_hover_result( | 326 | check_hover_result( |
@@ -405,7 +446,7 @@ mod tests { | |||
405 | } | 446 | } |
406 | 447 | ||
407 | #[test] | 448 | #[test] |
408 | fn hover_omits_default_generic_types() { | 449 | fn hover_default_generic_types() { |
409 | check_hover_result( | 450 | check_hover_result( |
410 | r#" | 451 | r#" |
411 | //- /main.rs | 452 | //- /main.rs |
@@ -417,7 +458,7 @@ struct Test<K, T = u8> { | |||
417 | fn main() { | 458 | fn main() { |
418 | let zz<|> = Test { t: 23, k: 33 }; | 459 | let zz<|> = Test { t: 23, k: 33 }; |
419 | }"#, | 460 | }"#, |
420 | &["Test<i32>"], | 461 | &["Test<i32, u8>"], |
421 | ); | 462 | ); |
422 | } | 463 | } |
423 | 464 | ||
diff --git a/crates/ra_ide/src/inlay_hints.rs b/crates/ra_ide/src/inlay_hints.rs index 98483df32..b391f903a 100644 --- a/crates/ra_ide/src/inlay_hints.rs +++ b/crates/ra_ide/src/inlay_hints.rs | |||
@@ -9,6 +9,7 @@ use ra_syntax::{ | |||
9 | }; | 9 | }; |
10 | 10 | ||
11 | use crate::{FileId, FunctionSignature}; | 11 | use crate::{FileId, FunctionSignature}; |
12 | use stdx::to_lower_snake_case; | ||
12 | 13 | ||
13 | #[derive(Clone, Debug, PartialEq, Eq)] | 14 | #[derive(Clone, Debug, PartialEq, Eq)] |
14 | pub struct InlayHintsConfig { | 15 | pub struct InlayHintsConfig { |
@@ -144,7 +145,7 @@ fn get_param_name_hints( | |||
144 | .iter() | 145 | .iter() |
145 | .skip(n_params_to_skip) | 146 | .skip(n_params_to_skip) |
146 | .zip(args) | 147 | .zip(args) |
147 | .filter(|(param, arg)| should_show_param_hint(&fn_signature, param, &arg)) | 148 | .filter(|(param, arg)| should_show_param_name_hint(sema, &fn_signature, param, &arg)) |
148 | .map(|(param_name, arg)| InlayHint { | 149 | .map(|(param_name, arg)| InlayHint { |
149 | range: arg.syntax().text_range(), | 150 | range: arg.syntax().text_range(), |
150 | kind: InlayKind::ParameterHint, | 151 | kind: InlayKind::ParameterHint, |
@@ -181,7 +182,7 @@ fn get_bind_pat_hints( | |||
181 | 182 | ||
182 | fn pat_is_enum_variant(db: &RootDatabase, bind_pat: &ast::BindPat, pat_ty: &Type) -> bool { | 183 | fn pat_is_enum_variant(db: &RootDatabase, bind_pat: &ast::BindPat, pat_ty: &Type) -> bool { |
183 | if let Some(Adt::Enum(enum_data)) = pat_ty.as_adt() { | 184 | if let Some(Adt::Enum(enum_data)) = pat_ty.as_adt() { |
184 | let pat_text = bind_pat.syntax().to_string(); | 185 | let pat_text = bind_pat.to_string(); |
185 | enum_data | 186 | enum_data |
186 | .variants(db) | 187 | .variants(db) |
187 | .into_iter() | 188 | .into_iter() |
@@ -198,7 +199,7 @@ fn should_not_display_type_hint(db: &RootDatabase, bind_pat: &ast::BindPat, pat_ | |||
198 | } | 199 | } |
199 | 200 | ||
200 | if let Some(Adt::Struct(s)) = pat_ty.as_adt() { | 201 | if let Some(Adt::Struct(s)) = pat_ty.as_adt() { |
201 | if s.fields(db).is_empty() && s.name(db).to_string() == bind_pat.syntax().to_string() { | 202 | if s.fields(db).is_empty() && s.name(db).to_string() == bind_pat.to_string() { |
202 | return true; | 203 | return true; |
203 | } | 204 | } |
204 | } | 205 | } |
@@ -230,15 +231,16 @@ fn should_not_display_type_hint(db: &RootDatabase, bind_pat: &ast::BindPat, pat_ | |||
230 | false | 231 | false |
231 | } | 232 | } |
232 | 233 | ||
233 | fn should_show_param_hint( | 234 | fn should_show_param_name_hint( |
235 | sema: &Semantics<RootDatabase>, | ||
234 | fn_signature: &FunctionSignature, | 236 | fn_signature: &FunctionSignature, |
235 | param_name: &str, | 237 | param_name: &str, |
236 | argument: &ast::Expr, | 238 | argument: &ast::Expr, |
237 | ) -> bool { | 239 | ) -> bool { |
240 | let param_name = param_name.trim_start_matches('_'); | ||
238 | if param_name.is_empty() | 241 | if param_name.is_empty() |
239 | || is_argument_similar_to_param(argument, param_name) | 242 | || Some(param_name) == fn_signature.name.as_ref().map(|s| s.trim_start_matches('_')) |
240 | || Some(param_name.trim_start_matches('_')) | 243 | || is_argument_similar_to_param_name(sema, argument, param_name) |
241 | == fn_signature.name.as_ref().map(|s| s.trim_start_matches('_')) | ||
242 | { | 244 | { |
243 | return false; | 245 | return false; |
244 | } | 246 | } |
@@ -254,20 +256,42 @@ fn should_show_param_hint( | |||
254 | parameters_len != 1 || !is_obvious_param(param_name) | 256 | parameters_len != 1 || !is_obvious_param(param_name) |
255 | } | 257 | } |
256 | 258 | ||
257 | fn is_argument_similar_to_param(argument: &ast::Expr, param_name: &str) -> bool { | 259 | fn is_argument_similar_to_param_name( |
258 | let argument_string = remove_ref(argument.clone()).syntax().to_string(); | 260 | sema: &Semantics<RootDatabase>, |
259 | let param_name = param_name.trim_start_matches('_'); | 261 | argument: &ast::Expr, |
260 | let argument_string = argument_string.trim_start_matches('_'); | 262 | param_name: &str, |
261 | argument_string.starts_with(¶m_name) || argument_string.ends_with(¶m_name) | 263 | ) -> bool { |
264 | if is_enum_name_similar_to_param_name(sema, argument, param_name) { | ||
265 | return true; | ||
266 | } | ||
267 | match get_string_representation(argument) { | ||
268 | None => false, | ||
269 | Some(repr) => { | ||
270 | let argument_string = repr.trim_start_matches('_'); | ||
271 | argument_string.starts_with(param_name) || argument_string.ends_with(param_name) | ||
272 | } | ||
273 | } | ||
274 | } | ||
275 | |||
276 | fn is_enum_name_similar_to_param_name( | ||
277 | sema: &Semantics<RootDatabase>, | ||
278 | argument: &ast::Expr, | ||
279 | param_name: &str, | ||
280 | ) -> bool { | ||
281 | match sema.type_of_expr(argument).and_then(|t| t.as_adt()) { | ||
282 | Some(Adt::Enum(e)) => to_lower_snake_case(&e.name(sema.db).to_string()) == param_name, | ||
283 | _ => false, | ||
284 | } | ||
262 | } | 285 | } |
263 | 286 | ||
264 | fn remove_ref(expr: ast::Expr) -> ast::Expr { | 287 | fn get_string_representation(expr: &ast::Expr) -> Option<String> { |
265 | if let ast::Expr::RefExpr(ref_expr) = &expr { | 288 | match expr { |
266 | if let Some(inner) = ref_expr.expr() { | 289 | ast::Expr::MethodCallExpr(method_call_expr) => { |
267 | return inner; | 290 | Some(method_call_expr.name_ref()?.to_string()) |
268 | } | 291 | } |
292 | ast::Expr::RefExpr(ref_expr) => get_string_representation(&ref_expr.expr()?), | ||
293 | _ => Some(expr.to_string()), | ||
269 | } | 294 | } |
270 | expr | ||
271 | } | 295 | } |
272 | 296 | ||
273 | fn is_obvious_param(param_name: &str) -> bool { | 297 | fn is_obvious_param(param_name: &str) -> bool { |
@@ -1073,6 +1097,12 @@ struct TestVarContainer { | |||
1073 | test_var: i32, | 1097 | test_var: i32, |
1074 | } | 1098 | } |
1075 | 1099 | ||
1100 | impl TestVarContainer { | ||
1101 | fn test_var(&self) -> i32 { | ||
1102 | self.test_var | ||
1103 | } | ||
1104 | } | ||
1105 | |||
1076 | struct Test {} | 1106 | struct Test {} |
1077 | 1107 | ||
1078 | impl Test { | 1108 | impl Test { |
@@ -1098,10 +1128,15 @@ struct Param {} | |||
1098 | fn different_order(param: &Param) {} | 1128 | fn different_order(param: &Param) {} |
1099 | fn different_order_mut(param: &mut Param) {} | 1129 | fn different_order_mut(param: &mut Param) {} |
1100 | fn has_underscore(_param: bool) {} | 1130 | fn has_underscore(_param: bool) {} |
1131 | fn enum_matches_param_name(completion_kind: CompletionKind) {} | ||
1101 | 1132 | ||
1102 | fn twiddle(twiddle: bool) {} | 1133 | fn twiddle(twiddle: bool) {} |
1103 | fn doo(_doo: bool) {} | 1134 | fn doo(_doo: bool) {} |
1104 | 1135 | ||
1136 | enum CompletionKind { | ||
1137 | Keyword, | ||
1138 | } | ||
1139 | |||
1105 | fn main() { | 1140 | fn main() { |
1106 | let container: TestVarContainer = TestVarContainer { test_var: 42 }; | 1141 | let container: TestVarContainer = TestVarContainer { test_var: 42 }; |
1107 | let test: Test = Test {}; | 1142 | let test: Test = Test {}; |
@@ -1114,18 +1149,21 @@ fn main() { | |||
1114 | let test_var: i32 = 55; | 1149 | let test_var: i32 = 55; |
1115 | test_processed.no_hints_expected(22, test_var); | 1150 | test_processed.no_hints_expected(22, test_var); |
1116 | test_processed.no_hints_expected(33, container.test_var); | 1151 | test_processed.no_hints_expected(33, container.test_var); |
1152 | test_processed.no_hints_expected(44, container.test_var()); | ||
1117 | test_processed.frob(false); | 1153 | test_processed.frob(false); |
1118 | 1154 | ||
1119 | twiddle(true); | 1155 | twiddle(true); |
1120 | doo(true); | 1156 | doo(true); |
1121 | 1157 | ||
1122 | let param_begin: Param = Param {}; | 1158 | let mut param_begin: Param = Param {}; |
1123 | different_order(¶m_begin); | 1159 | different_order(¶m_begin); |
1124 | different_order(&mut param_begin); | 1160 | different_order(&mut param_begin); |
1125 | 1161 | ||
1126 | let param: bool = true; | 1162 | let param: bool = true; |
1127 | has_underscore(param); | 1163 | has_underscore(param); |
1128 | 1164 | ||
1165 | enum_matches_param_name(CompletionKind::Keyword); | ||
1166 | |||
1129 | let a: f64 = 7.0; | 1167 | let a: f64 = 7.0; |
1130 | let b: f64 = 4.0; | 1168 | let b: f64 = 4.0; |
1131 | let _: f64 = a.div_euclid(b); | 1169 | let _: f64 = a.div_euclid(b); |
diff --git a/crates/ra_ide/src/join_lines.rs b/crates/ra_ide/src/join_lines.rs index 63fd6b3e4..d3af780c4 100644 --- a/crates/ra_ide/src/join_lines.rs +++ b/crates/ra_ide/src/join_lines.rs | |||
@@ -569,7 +569,11 @@ fn foo() { | |||
569 | let (sel, before) = extract_range(before); | 569 | let (sel, before) = extract_range(before); |
570 | let parse = SourceFile::parse(&before); | 570 | let parse = SourceFile::parse(&before); |
571 | let result = join_lines(&parse.tree(), sel); | 571 | let result = join_lines(&parse.tree(), sel); |
572 | let actual = result.apply(&before); | 572 | let actual = { |
573 | let mut actual = before.to_string(); | ||
574 | result.apply(&mut actual); | ||
575 | actual | ||
576 | }; | ||
573 | assert_eq_text!(after, &actual); | 577 | assert_eq_text!(after, &actual); |
574 | } | 578 | } |
575 | 579 | ||
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs index 09f602fe1..915199bd8 100644 --- a/crates/ra_ide/src/lib.rs +++ b/crates/ra_ide/src/lib.rs | |||
@@ -16,7 +16,6 @@ macro_rules! eprintln { | |||
16 | } | 16 | } |
17 | 17 | ||
18 | pub mod mock_analysis; | 18 | pub mod mock_analysis; |
19 | mod source_change; | ||
20 | 19 | ||
21 | mod prime_caches; | 20 | mod prime_caches; |
22 | mod status; | 21 | mod status; |
@@ -32,7 +31,6 @@ mod syntax_highlighting; | |||
32 | mod parent_module; | 31 | mod parent_module; |
33 | mod references; | 32 | mod references; |
34 | mod impls; | 33 | mod impls; |
35 | mod assists; | ||
36 | mod diagnostics; | 34 | mod diagnostics; |
37 | mod syntax_tree; | 35 | mod syntax_tree; |
38 | mod folding_ranges; | 36 | mod folding_ranges; |
@@ -65,7 +63,6 @@ use ra_syntax::{SourceFile, TextRange, TextSize}; | |||
65 | use crate::display::ToNav; | 63 | use crate::display::ToNav; |
66 | 64 | ||
67 | pub use crate::{ | 65 | pub use crate::{ |
68 | assists::{Assist, AssistId}, | ||
69 | call_hierarchy::CallItem, | 66 | call_hierarchy::CallItem, |
70 | completion::{ | 67 | completion::{ |
71 | CompletionConfig, CompletionItem, CompletionItemKind, CompletionScore, InsertTextFormat, | 68 | CompletionConfig, CompletionItem, CompletionItemKind, CompletionScore, InsertTextFormat, |
@@ -78,7 +75,6 @@ pub use crate::{ | |||
78 | inlay_hints::{InlayHint, InlayHintsConfig, InlayKind}, | 75 | inlay_hints::{InlayHint, InlayHintsConfig, InlayKind}, |
79 | references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult}, | 76 | references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult}, |
80 | runnables::{Runnable, RunnableKind, TestId}, | 77 | runnables::{Runnable, RunnableKind, TestId}, |
81 | source_change::{FileSystemEdit, SourceChange, SourceFileEdit}, | ||
82 | ssr::SsrError, | 78 | ssr::SsrError, |
83 | syntax_highlighting::{ | 79 | syntax_highlighting::{ |
84 | Highlight, HighlightModifier, HighlightModifiers, HighlightTag, HighlightedRange, | 80 | Highlight, HighlightModifier, HighlightModifiers, HighlightTag, HighlightedRange, |
@@ -86,6 +82,7 @@ pub use crate::{ | |||
86 | }; | 82 | }; |
87 | 83 | ||
88 | pub use hir::Documentation; | 84 | pub use hir::Documentation; |
85 | pub use ra_assists::AssistId; | ||
89 | pub use ra_db::{ | 86 | pub use ra_db::{ |
90 | Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRootId, | 87 | Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRootId, |
91 | }; | 88 | }; |
@@ -94,6 +91,7 @@ pub use ra_ide_db::{ | |||
94 | line_index::{LineCol, LineIndex}, | 91 | line_index::{LineCol, LineIndex}, |
95 | line_index_utils::translate_offset_with_edit, | 92 | line_index_utils::translate_offset_with_edit, |
96 | search::SearchScope, | 93 | search::SearchScope, |
94 | source_change::{FileSystemEdit, SourceChange, SourceFileEdit}, | ||
97 | symbol_index::Query, | 95 | symbol_index::Query, |
98 | RootDatabase, | 96 | RootDatabase, |
99 | }; | 97 | }; |
@@ -135,10 +133,12 @@ pub struct AnalysisHost { | |||
135 | db: RootDatabase, | 133 | db: RootDatabase, |
136 | } | 134 | } |
137 | 135 | ||
138 | impl Default for AnalysisHost { | 136 | #[derive(Debug)] |
139 | fn default() -> AnalysisHost { | 137 | pub struct Assist { |
140 | AnalysisHost::new(None) | 138 | pub id: AssistId, |
141 | } | 139 | pub label: String, |
140 | pub group_label: Option<String>, | ||
141 | pub source_change: SourceChange, | ||
142 | } | 142 | } |
143 | 143 | ||
144 | impl AnalysisHost { | 144 | impl AnalysisHost { |
@@ -188,6 +188,12 @@ impl AnalysisHost { | |||
188 | } | 188 | } |
189 | } | 189 | } |
190 | 190 | ||
191 | impl Default for AnalysisHost { | ||
192 | fn default() -> AnalysisHost { | ||
193 | AnalysisHost::new(None) | ||
194 | } | ||
195 | } | ||
196 | |||
191 | /// Analysis is a snapshot of a world state at a moment in time. It is the main | 197 | /// Analysis is a snapshot of a world state at a moment in time. It is the main |
192 | /// entry point for asking semantic information about the world. When the world | 198 | /// entry point for asking semantic information about the world. When the world |
193 | /// state is advanced using `AnalysisHost::apply_change` method, all existing | 199 | /// state is advanced using `AnalysisHost::apply_change` method, all existing |
@@ -296,7 +302,7 @@ impl Analysis { | |||
296 | file_id: frange.file_id, | 302 | file_id: frange.file_id, |
297 | edit: join_lines::join_lines(&parse.tree(), frange.range), | 303 | edit: join_lines::join_lines(&parse.tree(), frange.range), |
298 | }; | 304 | }; |
299 | SourceChange::source_file_edit("join lines", file_edit) | 305 | SourceChange::source_file_edit("Join lines", file_edit) |
300 | }) | 306 | }) |
301 | } | 307 | } |
302 | 308 | ||
@@ -465,7 +471,17 @@ impl Analysis { | |||
465 | /// Computes assists (aka code actions aka intentions) for the given | 471 | /// Computes assists (aka code actions aka intentions) for the given |
466 | /// position. | 472 | /// position. |
467 | pub fn assists(&self, frange: FileRange) -> Cancelable<Vec<Assist>> { | 473 | pub fn assists(&self, frange: FileRange) -> Cancelable<Vec<Assist>> { |
468 | self.with_db(|db| assists::assists(db, frange)) | 474 | self.with_db(|db| { |
475 | ra_assists::Assist::resolved(db, frange) | ||
476 | .into_iter() | ||
477 | .map(|assist| Assist { | ||
478 | id: assist.assist.id, | ||
479 | label: assist.assist.label, | ||
480 | group_label: assist.assist.group.map(|it| it.0), | ||
481 | source_change: assist.source_change, | ||
482 | }) | ||
483 | .collect() | ||
484 | }) | ||
469 | } | 485 | } |
470 | 486 | ||
471 | /// Computes the set of diagnostics for the given file. | 487 | /// Computes the set of diagnostics for the given file. |
@@ -490,7 +506,7 @@ impl Analysis { | |||
490 | ) -> Cancelable<Result<SourceChange, SsrError>> { | 506 | ) -> Cancelable<Result<SourceChange, SsrError>> { |
491 | self.with_db(|db| { | 507 | self.with_db(|db| { |
492 | let edits = ssr::parse_search_replace(query, parse_only, db)?; | 508 | let edits = ssr::parse_search_replace(query, parse_only, db)?; |
493 | Ok(SourceChange::source_file_edits("ssr", edits)) | 509 | Ok(SourceChange::source_file_edits("Structural Search Replace", edits)) |
494 | }) | 510 | }) |
495 | } | 511 | } |
496 | 512 | ||
diff --git a/crates/ra_ide/src/marks.rs b/crates/ra_ide/src/marks.rs index bea30fe2a..51ca4dde3 100644 --- a/crates/ra_ide/src/marks.rs +++ b/crates/ra_ide/src/marks.rs | |||
@@ -11,4 +11,6 @@ test_utils::marks!( | |||
11 | self_fulfilling_completion | 11 | self_fulfilling_completion |
12 | test_struct_field_completion_in_func_call | 12 | test_struct_field_completion_in_func_call |
13 | test_struct_field_completion_in_record_lit | 13 | test_struct_field_completion_in_record_lit |
14 | test_rename_struct_field_for_shorthand | ||
15 | test_rename_local_for_field_shorthand | ||
14 | ); | 16 | ); |
diff --git a/crates/ra_ide/src/references/rename.rs b/crates/ra_ide/src/references/rename.rs index fd17bc9f2..2cbb82c1a 100644 --- a/crates/ra_ide/src/references/rename.rs +++ b/crates/ra_ide/src/references/rename.rs | |||
@@ -7,14 +7,13 @@ use ra_syntax::{ | |||
7 | algo::find_node_at_offset, ast, lex_single_valid_syntax_kind, AstNode, SyntaxKind, SyntaxNode, | 7 | algo::find_node_at_offset, ast, lex_single_valid_syntax_kind, AstNode, SyntaxKind, SyntaxNode, |
8 | }; | 8 | }; |
9 | use ra_text_edit::TextEdit; | 9 | use ra_text_edit::TextEdit; |
10 | use test_utils::tested_by; | ||
10 | 11 | ||
11 | use crate::{ | 12 | use crate::{ |
12 | FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind, SourceChange, | 13 | references::find_all_refs, FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind, |
13 | SourceFileEdit, TextRange, | 14 | SourceChange, SourceFileEdit, TextRange, |
14 | }; | 15 | }; |
15 | 16 | ||
16 | use super::find_all_refs; | ||
17 | |||
18 | pub(crate) fn rename( | 17 | pub(crate) fn rename( |
19 | db: &RootDatabase, | 18 | db: &RootDatabase, |
20 | position: FilePosition, | 19 | position: FilePosition, |
@@ -52,11 +51,13 @@ fn source_edit_from_reference(reference: Reference, new_name: &str) -> SourceFil | |||
52 | let file_id = reference.file_range.file_id; | 51 | let file_id = reference.file_range.file_id; |
53 | let range = match reference.kind { | 52 | let range = match reference.kind { |
54 | ReferenceKind::FieldShorthandForField => { | 53 | ReferenceKind::FieldShorthandForField => { |
54 | tested_by!(test_rename_struct_field_for_shorthand); | ||
55 | replacement_text.push_str(new_name); | 55 | replacement_text.push_str(new_name); |
56 | replacement_text.push_str(": "); | 56 | replacement_text.push_str(": "); |
57 | TextRange::new(reference.file_range.range.start(), reference.file_range.range.start()) | 57 | TextRange::new(reference.file_range.range.start(), reference.file_range.range.start()) |
58 | } | 58 | } |
59 | ReferenceKind::FieldShorthandForLocal => { | 59 | ReferenceKind::FieldShorthandForLocal => { |
60 | tested_by!(test_rename_local_for_field_shorthand); | ||
60 | replacement_text.push_str(": "); | 61 | replacement_text.push_str(": "); |
61 | replacement_text.push_str(new_name); | 62 | replacement_text.push_str(new_name); |
62 | TextRange::new(reference.file_range.range.end(), reference.file_range.range.end()) | 63 | TextRange::new(reference.file_range.range.end(), reference.file_range.range.end()) |
@@ -121,7 +122,7 @@ fn rename_mod( | |||
121 | source_file_edits.extend(ref_edits); | 122 | source_file_edits.extend(ref_edits); |
122 | } | 123 | } |
123 | 124 | ||
124 | Some(SourceChange::from_edits("rename", source_file_edits, file_system_edits)) | 125 | Some(SourceChange::from_edits("Rename", source_file_edits, file_system_edits)) |
125 | } | 126 | } |
126 | 127 | ||
127 | fn rename_reference( | 128 | fn rename_reference( |
@@ -140,14 +141,14 @@ fn rename_reference( | |||
140 | return None; | 141 | return None; |
141 | } | 142 | } |
142 | 143 | ||
143 | Some(RangeInfo::new(range, SourceChange::source_file_edits("rename", edit))) | 144 | Some(RangeInfo::new(range, SourceChange::source_file_edits("Rename", edit))) |
144 | } | 145 | } |
145 | 146 | ||
146 | #[cfg(test)] | 147 | #[cfg(test)] |
147 | mod tests { | 148 | mod tests { |
148 | use insta::assert_debug_snapshot; | 149 | use insta::assert_debug_snapshot; |
149 | use ra_text_edit::TextEditBuilder; | 150 | use ra_text_edit::TextEditBuilder; |
150 | use test_utils::assert_eq_text; | 151 | use test_utils::{assert_eq_text, covers}; |
151 | 152 | ||
152 | use crate::{ | 153 | use crate::{ |
153 | mock_analysis::analysis_and_position, mock_analysis::single_file_with_position, FileId, | 154 | mock_analysis::analysis_and_position, mock_analysis::single_file_with_position, FileId, |
@@ -379,6 +380,7 @@ mod tests { | |||
379 | 380 | ||
380 | #[test] | 381 | #[test] |
381 | fn test_rename_struct_field_for_shorthand() { | 382 | fn test_rename_struct_field_for_shorthand() { |
383 | covers!(test_rename_struct_field_for_shorthand); | ||
382 | test_rename( | 384 | test_rename( |
383 | r#" | 385 | r#" |
384 | struct Foo { | 386 | struct Foo { |
@@ -408,6 +410,7 @@ mod tests { | |||
408 | 410 | ||
409 | #[test] | 411 | #[test] |
410 | fn test_rename_local_for_field_shorthand() { | 412 | fn test_rename_local_for_field_shorthand() { |
413 | covers!(test_rename_local_for_field_shorthand); | ||
411 | test_rename( | 414 | test_rename( |
412 | r#" | 415 | r#" |
413 | struct Foo { | 416 | struct Foo { |
@@ -527,17 +530,17 @@ mod tests { | |||
527 | RangeInfo { | 530 | RangeInfo { |
528 | range: 4..7, | 531 | range: 4..7, |
529 | info: SourceChange { | 532 | info: SourceChange { |
530 | label: "rename", | 533 | label: "Rename", |
531 | source_file_edits: [ | 534 | source_file_edits: [ |
532 | SourceFileEdit { | 535 | SourceFileEdit { |
533 | file_id: FileId( | 536 | file_id: FileId( |
534 | 2, | 537 | 2, |
535 | ), | 538 | ), |
536 | edit: TextEdit { | 539 | edit: TextEdit { |
537 | atoms: [ | 540 | indels: [ |
538 | AtomTextEdit { | 541 | Indel { |
539 | delete: 4..7, | ||
540 | insert: "foo2", | 542 | insert: "foo2", |
543 | delete: 4..7, | ||
541 | }, | 544 | }, |
542 | ], | 545 | ], |
543 | }, | 546 | }, |
@@ -579,17 +582,17 @@ mod tests { | |||
579 | RangeInfo { | 582 | RangeInfo { |
580 | range: 4..7, | 583 | range: 4..7, |
581 | info: SourceChange { | 584 | info: SourceChange { |
582 | label: "rename", | 585 | label: "Rename", |
583 | source_file_edits: [ | 586 | source_file_edits: [ |
584 | SourceFileEdit { | 587 | SourceFileEdit { |
585 | file_id: FileId( | 588 | file_id: FileId( |
586 | 1, | 589 | 1, |
587 | ), | 590 | ), |
588 | edit: TextEdit { | 591 | edit: TextEdit { |
589 | atoms: [ | 592 | indels: [ |
590 | AtomTextEdit { | 593 | Indel { |
591 | delete: 4..7, | ||
592 | insert: "foo2", | 594 | insert: "foo2", |
595 | delete: 4..7, | ||
593 | }, | 596 | }, |
594 | ], | 597 | ], |
595 | }, | 598 | }, |
@@ -662,17 +665,17 @@ mod tests { | |||
662 | RangeInfo { | 665 | RangeInfo { |
663 | range: 8..11, | 666 | range: 8..11, |
664 | info: SourceChange { | 667 | info: SourceChange { |
665 | label: "rename", | 668 | label: "Rename", |
666 | source_file_edits: [ | 669 | source_file_edits: [ |
667 | SourceFileEdit { | 670 | SourceFileEdit { |
668 | file_id: FileId( | 671 | file_id: FileId( |
669 | 2, | 672 | 2, |
670 | ), | 673 | ), |
671 | edit: TextEdit { | 674 | edit: TextEdit { |
672 | atoms: [ | 675 | indels: [ |
673 | AtomTextEdit { | 676 | Indel { |
674 | delete: 8..11, | ||
675 | insert: "foo2", | 677 | insert: "foo2", |
678 | delete: 8..11, | ||
676 | }, | 679 | }, |
677 | ], | 680 | ], |
678 | }, | 681 | }, |
@@ -682,10 +685,10 @@ mod tests { | |||
682 | 1, | 685 | 1, |
683 | ), | 686 | ), |
684 | edit: TextEdit { | 687 | edit: TextEdit { |
685 | atoms: [ | 688 | indels: [ |
686 | AtomTextEdit { | 689 | Indel { |
687 | delete: 27..30, | ||
688 | insert: "foo2", | 690 | insert: "foo2", |
691 | delete: 27..30, | ||
689 | }, | 692 | }, |
690 | ], | 693 | ], |
691 | }, | 694 | }, |
@@ -709,6 +712,68 @@ mod tests { | |||
709 | "###); | 712 | "###); |
710 | } | 713 | } |
711 | 714 | ||
715 | #[test] | ||
716 | fn test_enum_variant_from_module_1() { | ||
717 | test_rename( | ||
718 | r#" | ||
719 | mod foo { | ||
720 | pub enum Foo { | ||
721 | Bar<|>, | ||
722 | } | ||
723 | } | ||
724 | |||
725 | fn func(f: foo::Foo) { | ||
726 | match f { | ||
727 | foo::Foo::Bar => {} | ||
728 | } | ||
729 | } | ||
730 | "#, | ||
731 | "Baz", | ||
732 | r#" | ||
733 | mod foo { | ||
734 | pub enum Foo { | ||
735 | Baz, | ||
736 | } | ||
737 | } | ||
738 | |||
739 | fn func(f: foo::Foo) { | ||
740 | match f { | ||
741 | foo::Foo::Baz => {} | ||
742 | } | ||
743 | } | ||
744 | "#, | ||
745 | ); | ||
746 | } | ||
747 | |||
748 | #[test] | ||
749 | fn test_enum_variant_from_module_2() { | ||
750 | test_rename( | ||
751 | r#" | ||
752 | mod foo { | ||
753 | pub struct Foo { | ||
754 | pub bar<|>: uint, | ||
755 | } | ||
756 | } | ||
757 | |||
758 | fn foo(f: foo::Foo) { | ||
759 | let _ = f.bar; | ||
760 | } | ||
761 | "#, | ||
762 | "baz", | ||
763 | r#" | ||
764 | mod foo { | ||
765 | pub struct Foo { | ||
766 | pub baz: uint, | ||
767 | } | ||
768 | } | ||
769 | |||
770 | fn foo(f: foo::Foo) { | ||
771 | let _ = f.baz; | ||
772 | } | ||
773 | "#, | ||
774 | ); | ||
775 | } | ||
776 | |||
712 | fn test_rename(text: &str, new_name: &str, expected: &str) { | 777 | fn test_rename(text: &str, new_name: &str, expected: &str) { |
713 | let (analysis, position) = single_file_with_position(text); | 778 | let (analysis, position) = single_file_with_position(text); |
714 | let source_change = analysis.rename(position, new_name).unwrap(); | 779 | let source_change = analysis.rename(position, new_name).unwrap(); |
@@ -717,13 +782,13 @@ mod tests { | |||
717 | if let Some(change) = source_change { | 782 | if let Some(change) = source_change { |
718 | for edit in change.info.source_file_edits { | 783 | for edit in change.info.source_file_edits { |
719 | file_id = Some(edit.file_id); | 784 | file_id = Some(edit.file_id); |
720 | for atom in edit.edit.as_atoms() { | 785 | for indel in edit.edit.as_indels() { |
721 | text_edit_builder.replace(atom.delete, atom.insert.clone()); | 786 | text_edit_builder.replace(indel.delete, indel.insert.clone()); |
722 | } | 787 | } |
723 | } | 788 | } |
724 | } | 789 | } |
725 | let result = | 790 | let mut result = analysis.file_text(file_id.unwrap()).unwrap().to_string(); |
726 | text_edit_builder.finish().apply(&*analysis.file_text(file_id.unwrap()).unwrap()); | 791 | text_edit_builder.finish().apply(&mut result); |
727 | assert_eq_text!(expected, &*result); | 792 | assert_eq_text!(expected, &*result); |
728 | } | 793 | } |
729 | } | 794 | } |
diff --git a/crates/ra_ide/src/runnables.rs b/crates/ra_ide/src/runnables.rs index 38637c19c..fa8a9d92c 100644 --- a/crates/ra_ide/src/runnables.rs +++ b/crates/ra_ide/src/runnables.rs | |||
@@ -9,6 +9,7 @@ use ra_syntax::{ | |||
9 | }; | 9 | }; |
10 | 10 | ||
11 | use crate::FileId; | 11 | use crate::FileId; |
12 | use ast::DocCommentsOwner; | ||
12 | use std::fmt::Display; | 13 | use std::fmt::Display; |
13 | 14 | ||
14 | #[derive(Debug)] | 15 | #[derive(Debug)] |
@@ -37,6 +38,7 @@ pub enum RunnableKind { | |||
37 | Test { test_id: TestId, attr: TestAttr }, | 38 | Test { test_id: TestId, attr: TestAttr }, |
38 | TestMod { path: String }, | 39 | TestMod { path: String }, |
39 | Bench { test_id: TestId }, | 40 | Bench { test_id: TestId }, |
41 | DocTest { test_id: TestId }, | ||
40 | Bin, | 42 | Bin, |
41 | } | 43 | } |
42 | 44 | ||
@@ -81,6 +83,8 @@ fn runnable_fn(sema: &Semantics<RootDatabase>, fn_def: ast::FnDef) -> Option<Run | |||
81 | RunnableKind::Test { test_id, attr } | 83 | RunnableKind::Test { test_id, attr } |
82 | } else if fn_def.has_atom_attr("bench") { | 84 | } else if fn_def.has_atom_attr("bench") { |
83 | RunnableKind::Bench { test_id } | 85 | RunnableKind::Bench { test_id } |
86 | } else if has_doc_test(&fn_def) { | ||
87 | RunnableKind::DocTest { test_id } | ||
84 | } else { | 88 | } else { |
85 | return None; | 89 | return None; |
86 | } | 90 | } |
@@ -117,6 +121,10 @@ fn has_test_related_attribute(fn_def: &ast::FnDef) -> bool { | |||
117 | .any(|attribute_text| attribute_text.contains("test")) | 121 | .any(|attribute_text| attribute_text.contains("test")) |
118 | } | 122 | } |
119 | 123 | ||
124 | fn has_doc_test(fn_def: &ast::FnDef) -> bool { | ||
125 | fn_def.doc_comment_text().map_or(false, |comment| comment.contains("```")) | ||
126 | } | ||
127 | |||
120 | fn runnable_mod(sema: &Semantics<RootDatabase>, module: ast::Module) -> Option<Runnable> { | 128 | fn runnable_mod(sema: &Semantics<RootDatabase>, module: ast::Module) -> Option<Runnable> { |
121 | let has_test_function = module | 129 | let has_test_function = module |
122 | .item_list()? | 130 | .item_list()? |
@@ -195,6 +203,41 @@ mod tests { | |||
195 | } | 203 | } |
196 | 204 | ||
197 | #[test] | 205 | #[test] |
206 | fn test_runnables_doc_test() { | ||
207 | let (analysis, pos) = analysis_and_position( | ||
208 | r#" | ||
209 | //- /lib.rs | ||
210 | <|> //empty | ||
211 | fn main() {} | ||
212 | |||
213 | /// ``` | ||
214 | /// let x = 5; | ||
215 | /// ``` | ||
216 | fn foo() {} | ||
217 | "#, | ||
218 | ); | ||
219 | let runnables = analysis.runnables(pos.file_id).unwrap(); | ||
220 | assert_debug_snapshot!(&runnables, | ||
221 | @r###" | ||
222 | [ | ||
223 | Runnable { | ||
224 | range: 1..21, | ||
225 | kind: Bin, | ||
226 | }, | ||
227 | Runnable { | ||
228 | range: 22..64, | ||
229 | kind: DocTest { | ||
230 | test_id: Path( | ||
231 | "foo", | ||
232 | ), | ||
233 | }, | ||
234 | }, | ||
235 | ] | ||
236 | "### | ||
237 | ); | ||
238 | } | ||
239 | |||
240 | #[test] | ||
198 | fn test_runnables_module() { | 241 | fn test_runnables_module() { |
199 | let (analysis, pos) = analysis_and_position( | 242 | let (analysis, pos) = analysis_and_position( |
200 | r#" | 243 | r#" |
diff --git a/crates/ra_ide/src/source_change.rs b/crates/ra_ide/src/source_change.rs deleted file mode 100644 index 71b0e8f75..000000000 --- a/crates/ra_ide/src/source_change.rs +++ /dev/null | |||
@@ -1,119 +0,0 @@ | |||
1 | //! This modules defines type to represent changes to the source code, that flow | ||
2 | //! from the server to the client. | ||
3 | //! | ||
4 | //! It can be viewed as a dual for `AnalysisChange`. | ||
5 | |||
6 | use ra_db::RelativePathBuf; | ||
7 | use ra_text_edit::TextEdit; | ||
8 | |||
9 | use crate::{FileId, FilePosition, SourceRootId, TextSize}; | ||
10 | |||
11 | #[derive(Debug)] | ||
12 | pub struct SourceChange { | ||
13 | pub label: String, | ||
14 | pub source_file_edits: Vec<SourceFileEdit>, | ||
15 | pub file_system_edits: Vec<FileSystemEdit>, | ||
16 | pub cursor_position: Option<FilePosition>, | ||
17 | } | ||
18 | |||
19 | impl SourceChange { | ||
20 | /// Creates a new SourceChange with the given label | ||
21 | /// from the edits. | ||
22 | pub(crate) fn from_edits<L: Into<String>>( | ||
23 | label: L, | ||
24 | source_file_edits: Vec<SourceFileEdit>, | ||
25 | file_system_edits: Vec<FileSystemEdit>, | ||
26 | ) -> Self { | ||
27 | SourceChange { | ||
28 | label: label.into(), | ||
29 | source_file_edits, | ||
30 | file_system_edits, | ||
31 | cursor_position: None, | ||
32 | } | ||
33 | } | ||
34 | |||
35 | /// Creates a new SourceChange with the given label, | ||
36 | /// containing only the given `SourceFileEdits`. | ||
37 | pub(crate) fn source_file_edits<L: Into<String>>(label: L, edits: Vec<SourceFileEdit>) -> Self { | ||
38 | SourceChange { | ||
39 | label: label.into(), | ||
40 | source_file_edits: edits, | ||
41 | file_system_edits: vec![], | ||
42 | cursor_position: None, | ||
43 | } | ||
44 | } | ||
45 | |||
46 | /// Creates a new SourceChange with the given label, | ||
47 | /// containing only the given `FileSystemEdits`. | ||
48 | pub(crate) fn file_system_edits<L: Into<String>>(label: L, edits: Vec<FileSystemEdit>) -> Self { | ||
49 | SourceChange { | ||
50 | label: label.into(), | ||
51 | source_file_edits: vec![], | ||
52 | file_system_edits: edits, | ||
53 | cursor_position: None, | ||
54 | } | ||
55 | } | ||
56 | |||
57 | /// Creates a new SourceChange with the given label, | ||
58 | /// containing only a single `SourceFileEdit`. | ||
59 | pub(crate) fn source_file_edit<L: Into<String>>(label: L, edit: SourceFileEdit) -> Self { | ||
60 | SourceChange::source_file_edits(label, vec![edit]) | ||
61 | } | ||
62 | |||
63 | /// Creates a new SourceChange with the given label | ||
64 | /// from the given `FileId` and `TextEdit` | ||
65 | pub(crate) fn source_file_edit_from<L: Into<String>>( | ||
66 | label: L, | ||
67 | file_id: FileId, | ||
68 | edit: TextEdit, | ||
69 | ) -> Self { | ||
70 | SourceChange::source_file_edit(label, SourceFileEdit { file_id, edit }) | ||
71 | } | ||
72 | |||
73 | /// Creates a new SourceChange with the given label | ||
74 | /// from the given `FileId` and `TextEdit` | ||
75 | pub(crate) fn file_system_edit<L: Into<String>>(label: L, edit: FileSystemEdit) -> Self { | ||
76 | SourceChange::file_system_edits(label, vec![edit]) | ||
77 | } | ||
78 | |||
79 | /// Sets the cursor position to the given `FilePosition` | ||
80 | pub(crate) fn with_cursor(mut self, cursor_position: FilePosition) -> Self { | ||
81 | self.cursor_position = Some(cursor_position); | ||
82 | self | ||
83 | } | ||
84 | |||
85 | /// Sets the cursor position to the given `FilePosition` | ||
86 | pub(crate) fn with_cursor_opt(mut self, cursor_position: Option<FilePosition>) -> Self { | ||
87 | self.cursor_position = cursor_position; | ||
88 | self | ||
89 | } | ||
90 | } | ||
91 | |||
92 | #[derive(Debug)] | ||
93 | pub struct SourceFileEdit { | ||
94 | pub file_id: FileId, | ||
95 | pub edit: TextEdit, | ||
96 | } | ||
97 | |||
98 | #[derive(Debug)] | ||
99 | pub enum FileSystemEdit { | ||
100 | CreateFile { source_root: SourceRootId, path: RelativePathBuf }, | ||
101 | MoveFile { src: FileId, dst_source_root: SourceRootId, dst_path: RelativePathBuf }, | ||
102 | } | ||
103 | |||
104 | pub(crate) struct SingleFileChange { | ||
105 | pub label: String, | ||
106 | pub edit: TextEdit, | ||
107 | pub cursor_position: Option<TextSize>, | ||
108 | } | ||
109 | |||
110 | impl SingleFileChange { | ||
111 | pub(crate) fn into_source_change(self, file_id: FileId) -> SourceChange { | ||
112 | SourceChange { | ||
113 | label: self.label, | ||
114 | source_file_edits: vec![SourceFileEdit { file_id, edit: self.edit }], | ||
115 | file_system_edits: Vec::new(), | ||
116 | cursor_position: self.cursor_position.map(|offset| FilePosition { file_id, offset }), | ||
117 | } | ||
118 | } | ||
119 | } | ||
diff --git a/crates/ra_ide/src/ssr.rs b/crates/ra_ide/src/ssr.rs index 7b93ff2d2..1873d1d0d 100644 --- a/crates/ra_ide/src/ssr.rs +++ b/crates/ra_ide/src/ssr.rs | |||
@@ -1,18 +1,18 @@ | |||
1 | //! structural search replace | 1 | //! structural search replace |
2 | 2 | ||
3 | use crate::source_change::SourceFileEdit; | 3 | use std::{collections::HashMap, iter::once, str::FromStr}; |
4 | |||
4 | use ra_db::{SourceDatabase, SourceDatabaseExt}; | 5 | use ra_db::{SourceDatabase, SourceDatabaseExt}; |
5 | use ra_ide_db::symbol_index::SymbolsDatabase; | 6 | use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase}; |
6 | use ra_ide_db::RootDatabase; | ||
7 | use ra_syntax::ast::make::try_expr_from_text; | ||
8 | use ra_syntax::ast::{ | 7 | use ra_syntax::ast::{ |
9 | ArgList, AstToken, CallExpr, Comment, Expr, MethodCallExpr, RecordField, RecordLit, | 8 | make::try_expr_from_text, ArgList, AstToken, CallExpr, Comment, Expr, MethodCallExpr, |
9 | RecordField, RecordLit, | ||
10 | }; | 10 | }; |
11 | use ra_syntax::{AstNode, SyntaxElement, SyntaxKind, SyntaxNode}; | 11 | use ra_syntax::{AstNode, SyntaxElement, SyntaxKind, SyntaxNode}; |
12 | use ra_text_edit::{TextEdit, TextEditBuilder}; | 12 | use ra_text_edit::{TextEdit, TextEditBuilder}; |
13 | use rustc_hash::FxHashMap; | 13 | use rustc_hash::FxHashMap; |
14 | use std::collections::HashMap; | 14 | |
15 | use std::{iter::once, str::FromStr}; | 15 | use crate::SourceFileEdit; |
16 | 16 | ||
17 | #[derive(Debug, PartialEq)] | 17 | #[derive(Debug, PartialEq)] |
18 | pub struct SsrError(String); | 18 | pub struct SsrError(String); |
@@ -401,16 +401,22 @@ fn render_replace( | |||
401 | ignored_comments: &Vec<Comment>, | 401 | ignored_comments: &Vec<Comment>, |
402 | template: &SsrTemplate, | 402 | template: &SsrTemplate, |
403 | ) -> String { | 403 | ) -> String { |
404 | let mut builder = TextEditBuilder::default(); | 404 | let edit = { |
405 | for element in template.template.descendants() { | 405 | let mut builder = TextEditBuilder::default(); |
406 | if let Some(var) = template.placeholders.get(&element) { | 406 | for element in template.template.descendants() { |
407 | builder.replace(element.text_range(), binding[var].to_string()) | 407 | if let Some(var) = template.placeholders.get(&element) { |
408 | builder.replace(element.text_range(), binding[var].to_string()) | ||
409 | } | ||
408 | } | 410 | } |
409 | } | 411 | for comment in ignored_comments { |
410 | for comment in ignored_comments { | 412 | builder.insert(template.template.text_range().end(), comment.syntax().to_string()) |
411 | builder.insert(template.template.text_range().end(), comment.syntax().to_string()) | 413 | } |
412 | } | 414 | builder.finish() |
413 | builder.finish().apply(&template.template.text().to_string()) | 415 | }; |
416 | |||
417 | let mut text = template.template.text().to_string(); | ||
418 | edit.apply(&mut text); | ||
419 | text | ||
414 | } | 420 | } |
415 | 421 | ||
416 | #[cfg(test)] | 422 | #[cfg(test)] |
@@ -505,7 +511,9 @@ mod tests { | |||
505 | ); | 511 | ); |
506 | 512 | ||
507 | let edit = replace(&matches, &query.template); | 513 | let edit = replace(&matches, &query.template); |
508 | assert_eq!(edit.apply(input), "fn main() { bar(1+2); }"); | 514 | let mut after = input.to_string(); |
515 | edit.apply(&mut after); | ||
516 | assert_eq!(after, "fn main() { bar(1+2); }"); | ||
509 | } | 517 | } |
510 | 518 | ||
511 | fn assert_ssr_transform(query: &str, input: &str, result: &str) { | 519 | fn assert_ssr_transform(query: &str, input: &str, result: &str) { |
@@ -513,7 +521,9 @@ mod tests { | |||
513 | let code = SourceFile::parse(input).tree(); | 521 | let code = SourceFile::parse(input).tree(); |
514 | let matches = find(&query.pattern, code.syntax()); | 522 | let matches = find(&query.pattern, code.syntax()); |
515 | let edit = replace(&matches, &query.template); | 523 | let edit = replace(&matches, &query.template); |
516 | assert_eq!(edit.apply(input), result); | 524 | let mut after = input.to_string(); |
525 | edit.apply(&mut after); | ||
526 | assert_eq!(after, result); | ||
517 | } | 527 | } |
518 | 528 | ||
519 | #[test] | 529 | #[test] |
diff --git a/crates/ra_ide/src/test_utils.rs b/crates/ra_ide/src/test_utils.rs index f14533e14..48c8fd1f4 100644 --- a/crates/ra_ide/src/test_utils.rs +++ b/crates/ra_ide/src/test_utils.rs | |||
@@ -13,7 +13,11 @@ pub fn check_action<F: Fn(&SourceFile, TextSize) -> Option<TextEdit>>( | |||
13 | let (before_cursor_pos, before) = extract_offset(before); | 13 | let (before_cursor_pos, before) = extract_offset(before); |
14 | let file = SourceFile::parse(&before).ok().unwrap(); | 14 | let file = SourceFile::parse(&before).ok().unwrap(); |
15 | let result = f(&file, before_cursor_pos).expect("code action is not applicable"); | 15 | let result = f(&file, before_cursor_pos).expect("code action is not applicable"); |
16 | let actual = result.apply(&before); | 16 | let actual = { |
17 | let mut actual = before.to_string(); | ||
18 | result.apply(&mut actual); | ||
19 | actual | ||
20 | }; | ||
17 | let actual_cursor_pos = | 21 | let actual_cursor_pos = |
18 | result.apply_to_offset(before_cursor_pos).expect("cursor position is affected by the edit"); | 22 | result.apply_to_offset(before_cursor_pos).expect("cursor position is affected by the edit"); |
19 | let actual = add_cursor(&actual, actual_cursor_pos); | 23 | let actual = add_cursor(&actual, actual_cursor_pos); |
diff --git a/crates/ra_ide/src/typing.rs b/crates/ra_ide/src/typing.rs index 2a8b4327f..6f04f0be4 100644 --- a/crates/ra_ide/src/typing.rs +++ b/crates/ra_ide/src/typing.rs | |||
@@ -17,15 +17,16 @@ mod on_enter; | |||
17 | 17 | ||
18 | use ra_db::{FilePosition, SourceDatabase}; | 18 | use ra_db::{FilePosition, SourceDatabase}; |
19 | use ra_fmt::leading_indent; | 19 | use ra_fmt::leading_indent; |
20 | use ra_ide_db::RootDatabase; | 20 | use ra_ide_db::{source_change::SingleFileChange, RootDatabase}; |
21 | use ra_syntax::{ | 21 | use ra_syntax::{ |
22 | algo::find_node_at_offset, | 22 | algo::find_node_at_offset, |
23 | ast::{self, AstToken}, | 23 | ast::{self, AstToken}, |
24 | AstNode, SourceFile, TextRange, TextSize, | 24 | AstNode, SourceFile, TextRange, TextSize, |
25 | }; | 25 | }; |
26 | |||
26 | use ra_text_edit::TextEdit; | 27 | use ra_text_edit::TextEdit; |
27 | 28 | ||
28 | use crate::{source_change::SingleFileChange, SourceChange}; | 29 | use crate::SourceChange; |
29 | 30 | ||
30 | pub(crate) use on_enter::on_enter; | 31 | pub(crate) use on_enter::on_enter; |
31 | 32 | ||
@@ -142,10 +143,13 @@ mod tests { | |||
142 | fn do_type_char(char_typed: char, before: &str) -> Option<(String, SingleFileChange)> { | 143 | fn do_type_char(char_typed: char, before: &str) -> Option<(String, SingleFileChange)> { |
143 | let (offset, before) = extract_offset(before); | 144 | let (offset, before) = extract_offset(before); |
144 | let edit = TextEdit::insert(offset, char_typed.to_string()); | 145 | let edit = TextEdit::insert(offset, char_typed.to_string()); |
145 | let before = edit.apply(&before); | 146 | let mut before = before.to_string(); |
147 | edit.apply(&mut before); | ||
146 | let parse = SourceFile::parse(&before); | 148 | let parse = SourceFile::parse(&before); |
147 | on_char_typed_inner(&parse.tree(), offset, char_typed) | 149 | on_char_typed_inner(&parse.tree(), offset, char_typed).map(|it| { |
148 | .map(|it| (it.edit.apply(&before), it)) | 150 | it.edit.apply(&mut before); |
151 | (before.to_string(), it) | ||
152 | }) | ||
149 | } | 153 | } |
150 | 154 | ||
151 | fn type_char(char_typed: char, before: &str, after: &str) { | 155 | fn type_char(char_typed: char, before: &str, after: &str) { |
diff --git a/crates/ra_ide/src/typing/on_enter.rs b/crates/ra_ide/src/typing/on_enter.rs index 30c8c5572..78a40cc94 100644 --- a/crates/ra_ide/src/typing/on_enter.rs +++ b/crates/ra_ide/src/typing/on_enter.rs | |||
@@ -44,7 +44,7 @@ pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<Sour | |||
44 | 44 | ||
45 | Some( | 45 | Some( |
46 | SourceChange::source_file_edit( | 46 | SourceChange::source_file_edit( |
47 | "on enter", | 47 | "On enter", |
48 | SourceFileEdit { edit, file_id: position.file_id }, | 48 | SourceFileEdit { edit, file_id: position.file_id }, |
49 | ) | 49 | ) |
50 | .with_cursor(FilePosition { offset: cursor_position, file_id: position.file_id }), | 50 | .with_cursor(FilePosition { offset: cursor_position, file_id: position.file_id }), |
@@ -96,7 +96,8 @@ mod tests { | |||
96 | let result = analysis.on_enter(FilePosition { offset, file_id }).unwrap()?; | 96 | let result = analysis.on_enter(FilePosition { offset, file_id }).unwrap()?; |
97 | 97 | ||
98 | assert_eq!(result.source_file_edits.len(), 1); | 98 | assert_eq!(result.source_file_edits.len(), 1); |
99 | let actual = result.source_file_edits[0].edit.apply(&before); | 99 | let mut actual = before.to_string(); |
100 | result.source_file_edits[0].edit.apply(&mut actual); | ||
100 | let actual = add_cursor(&actual, result.cursor_position.unwrap().offset); | 101 | let actual = add_cursor(&actual, result.cursor_position.unwrap().offset); |
101 | Some(actual) | 102 | Some(actual) |
102 | } | 103 | } |