diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-11-04 12:30:09 +0000 |
---|---|---|
committer | GitHub <[email protected]> | 2020-11-04 12:30:09 +0000 |
commit | 99a8e59f68db7d66a8b5f83c3566f67028ed2675 (patch) | |
tree | 2e5f04ab2783a90efeabdc39abe7cee5e829ac85 /crates/assists/src/handlers | |
parent | bdfffa372be37cc57facc2fbead920fcf3134a91 (diff) | |
parent | 4992b75e518ae1427a040efa7cc2601186e8898e (diff) |
Merge #6458
6458: Qualify trait impl created by add_custom_impl assist r=matklad a=Veykril
When we find at least one trait with the same name as the derive accessible from the current module we now generate a qualified path to that trait in the generated impl.
If we don't find any we just do what was done before and emit the trait name in the generated impl.
Co-authored-by: Lukas Wirth <[email protected]>
Diffstat (limited to 'crates/assists/src/handlers')
-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, |