diff options
Diffstat (limited to 'crates/ra_assists')
-rw-r--r-- | crates/ra_assists/src/add_missing_impl_members.rs | 72 |
1 files changed, 59 insertions, 13 deletions
diff --git a/crates/ra_assists/src/add_missing_impl_members.rs b/crates/ra_assists/src/add_missing_impl_members.rs index 120109d4b..4926a9b24 100644 --- a/crates/ra_assists/src/add_missing_impl_members.rs +++ b/crates/ra_assists/src/add_missing_impl_members.rs | |||
@@ -1,12 +1,15 @@ | |||
1 | use std::collections::HashSet; | 1 | use std::collections::HashSet; |
2 | 2 | ||
3 | use crate::assist_ctx::{Assist, AssistCtx}; | 3 | use crate::{Assist, AssistId, AssistCtx}; |
4 | 4 | ||
5 | use hir::Resolver; | 5 | use hir::Resolver; |
6 | use hir::db::HirDatabase; | 6 | use hir::db::HirDatabase; |
7 | use ra_syntax::{SmolStr, SyntaxKind, SyntaxNode, TreeArc}; | 7 | use ra_syntax::{SmolStr, SyntaxKind, SyntaxNode, TextUnit, TreeArc}; |
8 | use ra_syntax::ast::{self, AstNode, FnDef, ImplItem, ImplItemKind, NameOwner}; | 8 | use ra_syntax::ast::{self, AstNode, FnDef, ImplItem, ImplItemKind, NameOwner}; |
9 | use ra_db::FilePosition; | 9 | use ra_db::FilePosition; |
10 | use ra_fmt::{leading_indent, reindent}; | ||
11 | |||
12 | use itertools::Itertools; | ||
10 | 13 | ||
11 | /// Given an `ast::ImplBlock`, resolves the target trait (the one being | 14 | /// Given an `ast::ImplBlock`, resolves the target trait (the one being |
12 | /// implemented) to a `ast::TraitDef`. | 15 | /// implemented) to a `ast::TraitDef`. |
@@ -24,7 +27,21 @@ pub(crate) fn resolve_target_trait_def( | |||
24 | } | 27 | } |
25 | } | 28 | } |
26 | 29 | ||
27 | pub(crate) fn add_missing_impl_members(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 30 | pub(crate) fn build_func_body(def: &ast::FnDef) -> String { |
31 | let mut buf = String::new(); | ||
32 | |||
33 | for child in def.syntax().children() { | ||
34 | if child.kind() == SyntaxKind::SEMI { | ||
35 | buf.push_str(" { unimplemented!() }") | ||
36 | } else { | ||
37 | child.text().push_to(&mut buf); | ||
38 | } | ||
39 | } | ||
40 | |||
41 | buf.trim_end().to_string() | ||
42 | } | ||
43 | |||
44 | pub(crate) fn add_missing_impl_members(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
28 | use SyntaxKind::{IMPL_BLOCK, ITEM_LIST, WHITESPACE}; | 45 | use SyntaxKind::{IMPL_BLOCK, ITEM_LIST, WHITESPACE}; |
29 | 46 | ||
30 | let node = ctx.covering_node(); | 47 | let node = ctx.covering_node(); |
@@ -35,6 +52,7 @@ pub(crate) fn add_missing_impl_members(ctx: AssistCtx<impl HirDatabase>) -> Opti | |||
35 | } | 52 | } |
36 | 53 | ||
37 | let impl_node = node.ancestors().find_map(ast::ImplBlock::cast)?; | 54 | let impl_node = node.ancestors().find_map(ast::ImplBlock::cast)?; |
55 | let impl_item_list = impl_node.item_list()?; | ||
38 | 56 | ||
39 | let trait_def = { | 57 | let trait_def = { |
40 | let db = ctx.db; | 58 | let db = ctx.db; |
@@ -47,23 +65,49 @@ pub(crate) fn add_missing_impl_members(ctx: AssistCtx<impl HirDatabase>) -> Opti | |||
47 | }; | 65 | }; |
48 | 66 | ||
49 | let fn_def_opt = |kind| if let ImplItemKind::FnDef(def) = kind { Some(def) } else { None }; | 67 | let fn_def_opt = |kind| if let ImplItemKind::FnDef(def) = kind { Some(def) } else { None }; |
50 | let def_name = |&def| -> Option<&SmolStr> { FnDef::name(def).map(ast::Name::text) }; | 68 | let def_name = |def| -> Option<&SmolStr> { FnDef::name(def).map(ast::Name::text) }; |
51 | 69 | ||
52 | let trait_items = trait_def.syntax().descendants().find_map(ast::ItemList::cast)?.impl_items(); | 70 | let trait_items = trait_def.syntax().descendants().find_map(ast::ItemList::cast)?.impl_items(); |
53 | let impl_items = impl_node.item_list()?.impl_items(); | 71 | let impl_items = impl_item_list.impl_items(); |
54 | 72 | ||
55 | let trait_fns = trait_items.map(ImplItem::kind).filter_map(fn_def_opt).collect::<Vec<_>>(); | 73 | let trait_fns = trait_items.map(ImplItem::kind).filter_map(fn_def_opt).collect::<Vec<_>>(); |
56 | let impl_fns = impl_items.map(ImplItem::kind).filter_map(fn_def_opt).collect::<Vec<_>>(); | 74 | let impl_fns = impl_items.map(ImplItem::kind).filter_map(fn_def_opt).collect::<Vec<_>>(); |
57 | 75 | ||
58 | let trait_fn_names = trait_fns.iter().filter_map(def_name).collect::<HashSet<_>>(); | 76 | let trait_fn_names = trait_fns.iter().cloned().filter_map(def_name).collect::<HashSet<_>>(); |
59 | let impl_fn_names = impl_fns.iter().filter_map(def_name).collect::<HashSet<_>>(); | 77 | let impl_fn_names = impl_fns.iter().cloned().filter_map(def_name).collect::<HashSet<_>>(); |
60 | 78 | ||
61 | let missing_fn_names = trait_fn_names.difference(&impl_fn_names).collect::<HashSet<_>>(); | 79 | let missing_fn_names = trait_fn_names.difference(&impl_fn_names).collect::<HashSet<_>>(); |
62 | let missing_fns = trait_fns | 80 | let missing_fns: Vec<_> = trait_fns |
63 | .iter() | 81 | .iter() |
64 | .filter(|&t| def_name(t).map(|n| missing_fn_names.contains(&n)).unwrap_or(false)); | 82 | .cloned() |
83 | .filter(|t| def_name(t).map(|n| missing_fn_names.contains(&n)).unwrap_or(false)) | ||
84 | .collect(); | ||
65 | 85 | ||
66 | unimplemented!() | 86 | if missing_fns.is_empty() { |
87 | return None; | ||
88 | } | ||
89 | |||
90 | let last_whitespace_node = | ||
91 | impl_item_list.syntax().children().filter_map(ast::Whitespace::cast).last()?.syntax(); | ||
92 | |||
93 | ctx.add_action(AssistId("add_impl_missing_members"), "add impl missing members", |edit| { | ||
94 | let func_bodies = missing_fns.into_iter().map(build_func_body).join("\n"); | ||
95 | let func_bodies = String::from("\n") + &func_bodies; | ||
96 | |||
97 | let first_impl_item = impl_item_list.impl_items().next(); | ||
98 | // FIXME: We should respect the indent of the first item from the item list or the indent of leading block + some default indent (4?) | ||
99 | // Another approach is to not indent at all if there are no items here | ||
100 | let indent = first_impl_item.and_then(|i| leading_indent(i.syntax())).unwrap_or_default(); | ||
101 | let func_bodies = reindent(&func_bodies, indent) + "\n"; | ||
102 | |||
103 | let changed_range = last_whitespace_node.range(); | ||
104 | let replaced_text_range = TextUnit::of_str(&func_bodies); | ||
105 | |||
106 | edit.replace(changed_range, func_bodies); | ||
107 | edit.set_cursor(changed_range.start() + replaced_text_range - TextUnit::of_str("\n")); | ||
108 | }); | ||
109 | |||
110 | ctx.build() | ||
67 | } | 111 | } |
68 | 112 | ||
69 | #[cfg(test)] | 113 | #[cfg(test)] |
@@ -78,24 +122,26 @@ mod tests { | |||
78 | " | 122 | " |
79 | trait Foo { | 123 | trait Foo { |
80 | fn foo(&self); | 124 | fn foo(&self); |
125 | fn bar(&self); | ||
81 | } | 126 | } |
82 | 127 | ||
83 | struct S; | 128 | struct S; |
84 | 129 | ||
85 | impl Foo for S { | 130 | impl Foo for S { |
131 | fn bar(&self) {} | ||
86 | <|> | 132 | <|> |
87 | }", | 133 | }", |
88 | " | 134 | " |
89 | trait Foo { | 135 | trait Foo { |
90 | fn foo(&self); | 136 | fn foo(&self); |
137 | fn bar(&self); | ||
91 | } | 138 | } |
92 | 139 | ||
93 | struct S; | 140 | struct S; |
94 | 141 | ||
95 | impl Foo for S { | 142 | impl Foo for S { |
96 | fn foo(&self) { | 143 | fn bar(&self) {} |
97 | <|> | 144 | fn foo(&self) { unimplemented!() }<|> |
98 | } | ||
99 | }", | 145 | }", |
100 | ); | 146 | ); |
101 | } | 147 | } |