diff options
Diffstat (limited to 'crates/assists')
-rw-r--r-- | crates/assists/src/handlers/add_custom_impl.rs | 152 |
1 files changed, 114 insertions, 38 deletions
diff --git a/crates/assists/src/handlers/add_custom_impl.rs b/crates/assists/src/handlers/add_custom_impl.rs index 8757fa33f..669dd9b21 100644 --- a/crates/assists/src/handlers/add_custom_impl.rs +++ b/crates/assists/src/handlers/add_custom_impl.rs | |||
@@ -1,13 +1,16 @@ | |||
1 | use ide_db::imports_locator; | ||
1 | use itertools::Itertools; | 2 | use itertools::Itertools; |
2 | use syntax::{ | 3 | use syntax::{ |
3 | ast::{self, AstNode}, | 4 | ast::{self, make, AstNode}, |
4 | Direction, SmolStr, | 5 | Direction, SmolStr, |
5 | SyntaxKind::{IDENT, WHITESPACE}, | 6 | SyntaxKind::{IDENT, WHITESPACE}, |
6 | TextRange, TextSize, | 7 | TextRange, TextSize, |
7 | }; | 8 | }; |
8 | 9 | ||
9 | use crate::{ | 10 | use crate::{ |
10 | assist_context::{AssistContext, Assists}, | 11 | assist_config::SnippetCap, |
12 | assist_context::{AssistBuilder, AssistContext, Assists}, | ||
13 | utils::mod_path_to_ast, | ||
11 | AssistId, AssistKind, | 14 | AssistId, AssistKind, |
12 | }; | 15 | }; |
13 | 16 | ||
@@ -30,72 +33,116 @@ use crate::{ | |||
30 | // ``` | 33 | // ``` |
31 | pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 34 | pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
32 | let attr = ctx.find_node_at_offset::<ast::Attr>()?; | 35 | let attr = ctx.find_node_at_offset::<ast::Attr>()?; |
33 | let input = attr.token_tree()?; | ||
34 | 36 | ||
35 | let attr_name = attr | 37 | let attr_name = attr |
36 | .syntax() | 38 | .syntax() |
37 | .descendants_with_tokens() | 39 | .descendants_with_tokens() |
38 | .filter(|t| t.kind() == IDENT) | 40 | .filter(|t| t.kind() == IDENT) |
39 | .find_map(|i| i.into_token()) | 41 | .find_map(syntax::NodeOrToken::into_token) |
40 | .filter(|t| *t.text() == "derive")? | 42 | .filter(|t| t.text() == "derive")? |
41 | .text() | 43 | .text() |
42 | .clone(); | 44 | .clone(); |
43 | 45 | ||
44 | let trait_token = | 46 | let trait_token = |
45 | ctx.token_at_offset().find(|t| t.kind() == IDENT && *t.text() != attr_name)?; | 47 | ctx.token_at_offset().find(|t| t.kind() == IDENT && *t.text() != attr_name)?; |
48 | let trait_path = make::path_unqualified(make::path_segment(make::name_ref(trait_token.text()))); | ||
46 | 49 | ||
47 | let annotated = attr.syntax().siblings(Direction::Next).find_map(ast::Name::cast)?; | 50 | let annotated = attr.syntax().siblings(Direction::Next).find_map(ast::Name::cast)?; |
48 | let annotated_name = annotated.syntax().text().to_string(); | 51 | let annotated_name = annotated.syntax().text().to_string(); |
49 | let start_offset = annotated.syntax().parent()?.text_range().end(); | 52 | let insert_pos = annotated.syntax().parent()?.text_range().end(); |
53 | |||
54 | let current_module = ctx.sema.scope(annotated.syntax()).module()?; | ||
55 | let current_crate = current_module.krate(); | ||
56 | |||
57 | let found_traits = imports_locator::find_imports(&ctx.sema, current_crate, trait_token.text()) | ||
58 | .into_iter() | ||
59 | .filter_map(|candidate: either::Either<hir::ModuleDef, hir::MacroDef>| match candidate { | ||
60 | either::Either::Left(hir::ModuleDef::Trait(trait_)) => Some(trait_), | ||
61 | _ => None, | ||
62 | }) | ||
63 | .flat_map(|trait_| { | ||
64 | current_module | ||
65 | .find_use_path(ctx.sema.db, hir::ModuleDef::Trait(trait_)) | ||
66 | .as_ref() | ||
67 | .map(mod_path_to_ast) | ||
68 | .zip(Some(trait_)) | ||
69 | }); | ||
50 | 70 | ||
51 | let label = | 71 | let mut no_traits_found = true; |
52 | format!("Add custom impl `{}` for `{}`", trait_token.text().as_str(), annotated_name); | 72 | for (trait_path, _trait) in found_traits.inspect(|_| no_traits_found = false) { |
73 | add_assist(acc, ctx.config.snippet_cap, &attr, &trait_path, &annotated_name, insert_pos)?; | ||
74 | } | ||
75 | if no_traits_found { | ||
76 | add_assist(acc, ctx.config.snippet_cap, &attr, &trait_path, &annotated_name, insert_pos)?; | ||
77 | } | ||
78 | Some(()) | ||
79 | } | ||
53 | 80 | ||
81 | fn add_assist( | ||
82 | acc: &mut Assists, | ||
83 | snippet_cap: Option<SnippetCap>, | ||
84 | attr: &ast::Attr, | ||
85 | trait_path: &ast::Path, | ||
86 | annotated_name: &str, | ||
87 | insert_pos: TextSize, | ||
88 | ) -> Option<()> { | ||
54 | let target = attr.syntax().text_range(); | 89 | let target = attr.syntax().text_range(); |
55 | acc.add(AssistId("add_custom_impl", AssistKind::Refactor), label, target, |builder| { | 90 | let input = attr.token_tree()?; |
56 | let new_attr_input = input | 91 | let label = format!("Add custom impl `{}` for `{}`", trait_path, annotated_name); |
57 | .syntax() | 92 | let trait_name = trait_path.segment().and_then(|seg| seg.name_ref())?; |
58 | .descendants_with_tokens() | ||
59 | .filter(|t| t.kind() == IDENT) | ||
60 | .filter_map(|t| t.into_token().map(|t| t.text().clone())) | ||
61 | .filter(|t| t != trait_token.text()) | ||
62 | .collect::<Vec<SmolStr>>(); | ||
63 | let has_more_derives = !new_attr_input.is_empty(); | ||
64 | |||
65 | if has_more_derives { | ||
66 | let new_attr_input = format!("({})", new_attr_input.iter().format(", ")); | ||
67 | builder.replace(input.syntax().text_range(), new_attr_input); | ||
68 | } else { | ||
69 | let attr_range = attr.syntax().text_range(); | ||
70 | builder.delete(attr_range); | ||
71 | |||
72 | let line_break_range = attr | ||
73 | .syntax() | ||
74 | .next_sibling_or_token() | ||
75 | .filter(|t| t.kind() == WHITESPACE) | ||
76 | .map(|t| t.text_range()) | ||
77 | .unwrap_or_else(|| TextRange::new(TextSize::from(0), TextSize::from(0))); | ||
78 | builder.delete(line_break_range); | ||
79 | } | ||
80 | 93 | ||
81 | match ctx.config.snippet_cap { | 94 | acc.add(AssistId("add_custom_impl", AssistKind::Refactor), label, target, |builder| { |
95 | update_attribute(builder, &input, &trait_name, &attr); | ||
96 | match snippet_cap { | ||
82 | Some(cap) => { | 97 | Some(cap) => { |
83 | builder.insert_snippet( | 98 | builder.insert_snippet( |
84 | cap, | 99 | cap, |
85 | start_offset, | 100 | insert_pos, |
86 | format!("\n\nimpl {} for {} {{\n $0\n}}", trait_token, annotated_name), | 101 | format!("\n\nimpl {} for {} {{\n $0\n}}", trait_path, annotated_name), |
87 | ); | 102 | ); |
88 | } | 103 | } |
89 | None => { | 104 | None => { |
90 | builder.insert( | 105 | builder.insert( |
91 | start_offset, | 106 | insert_pos, |
92 | format!("\n\nimpl {} for {} {{\n\n}}", trait_token, annotated_name), | 107 | format!("\n\nimpl {} for {} {{\n\n}}", trait_path, annotated_name), |
93 | ); | 108 | ); |
94 | } | 109 | } |
95 | } | 110 | } |
96 | }) | 111 | }) |
97 | } | 112 | } |
98 | 113 | ||
114 | fn update_attribute( | ||
115 | builder: &mut AssistBuilder, | ||
116 | input: &ast::TokenTree, | ||
117 | trait_name: &ast::NameRef, | ||
118 | attr: &ast::Attr, | ||
119 | ) { | ||
120 | let new_attr_input = input | ||
121 | .syntax() | ||
122 | .descendants_with_tokens() | ||
123 | .filter(|t| t.kind() == IDENT) | ||
124 | .filter_map(|t| t.into_token().map(|t| t.text().clone())) | ||
125 | .filter(|t| t != trait_name.text()) | ||
126 | .collect::<Vec<SmolStr>>(); | ||
127 | let has_more_derives = !new_attr_input.is_empty(); | ||
128 | |||
129 | if has_more_derives { | ||
130 | let new_attr_input = format!("({})", new_attr_input.iter().format(", ")); | ||
131 | builder.replace(input.syntax().text_range(), new_attr_input); | ||
132 | } else { | ||
133 | let attr_range = attr.syntax().text_range(); | ||
134 | builder.delete(attr_range); | ||
135 | |||
136 | let line_break_range = attr | ||
137 | .syntax() | ||
138 | .next_sibling_or_token() | ||
139 | .filter(|t| t.kind() == WHITESPACE) | ||
140 | .map(|t| t.text_range()) | ||
141 | .unwrap_or_else(|| TextRange::new(TextSize::from(0), TextSize::from(0))); | ||
142 | builder.delete(line_break_range); | ||
143 | } | ||
144 | } | ||
145 | |||
99 | #[cfg(test)] | 146 | #[cfg(test)] |
100 | mod tests { | 147 | mod tests { |
101 | use crate::tests::{check_assist, check_assist_not_applicable}; | 148 | use crate::tests::{check_assist, check_assist_not_applicable}; |
@@ -103,6 +150,35 @@ mod tests { | |||
103 | use super::*; | 150 | use super::*; |
104 | 151 | ||
105 | #[test] | 152 | #[test] |
153 | fn add_custom_impl_qualified() { | ||
154 | check_assist( | ||
155 | add_custom_impl, | ||
156 | " | ||
157 | mod fmt { | ||
158 | pub trait Debug {} | ||
159 | } | ||
160 | |||
161 | #[derive(Debu<|>g)] | ||
162 | struct Foo { | ||
163 | bar: String, | ||
164 | } | ||
165 | ", | ||
166 | " | ||
167 | mod fmt { | ||
168 | pub trait Debug {} | ||
169 | } | ||
170 | |||
171 | struct Foo { | ||
172 | bar: String, | ||
173 | } | ||
174 | |||
175 | impl fmt::Debug for Foo { | ||
176 | $0 | ||
177 | } | ||
178 | ", | ||
179 | ) | ||
180 | } | ||
181 | #[test] | ||
106 | fn add_custom_impl_for_unique_input() { | 182 | fn add_custom_impl_for_unique_input() { |
107 | check_assist( | 183 | check_assist( |
108 | add_custom_impl, | 184 | add_custom_impl, |