aboutsummaryrefslogtreecommitdiff
path: root/crates/assists
diff options
context:
space:
mode:
Diffstat (limited to 'crates/assists')
-rw-r--r--crates/assists/src/handlers/add_custom_impl.rs152
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 @@
1use ide_db::imports_locator;
1use itertools::Itertools; 2use itertools::Itertools;
2use syntax::{ 3use 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
9use crate::{ 10use 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// ```
31pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 34pub(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
81fn 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
114fn 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)]
100mod tests { 147mod 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 "
157mod fmt {
158 pub trait Debug {}
159}
160
161#[derive(Debu<|>g)]
162struct Foo {
163 bar: String,
164}
165",
166 "
167mod fmt {
168 pub trait Debug {}
169}
170
171struct Foo {
172 bar: String,
173}
174
175impl 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,