aboutsummaryrefslogtreecommitdiff
path: root/crates/assists/src/handlers/add_custom_impl.rs
diff options
context:
space:
mode:
authorZac Pullar-Strecker <[email protected]>2020-08-24 10:19:53 +0100
committerZac Pullar-Strecker <[email protected]>2020-08-24 10:20:13 +0100
commit7bbca7a1b3f9293d2f5cc5745199bc5f8396f2f0 (patch)
treebdb47765991cb973b2cd5481a088fac636bd326c /crates/assists/src/handlers/add_custom_impl.rs
parentca464650eeaca6195891199a93f4f76cf3e7e697 (diff)
parente65d48d1fb3d4d91d9dc1148a7a836ff5c9a3c87 (diff)
Merge remote-tracking branch 'upstream/master' into 503-hover-doc-links
Diffstat (limited to 'crates/assists/src/handlers/add_custom_impl.rs')
-rw-r--r--crates/assists/src/handlers/add_custom_impl.rs208
1 files changed, 208 insertions, 0 deletions
diff --git a/crates/assists/src/handlers/add_custom_impl.rs b/crates/assists/src/handlers/add_custom_impl.rs
new file mode 100644
index 000000000..8757fa33f
--- /dev/null
+++ b/crates/assists/src/handlers/add_custom_impl.rs
@@ -0,0 +1,208 @@
1use itertools::Itertools;
2use syntax::{
3 ast::{self, AstNode},
4 Direction, SmolStr,
5 SyntaxKind::{IDENT, WHITESPACE},
6 TextRange, TextSize,
7};
8
9use crate::{
10 assist_context::{AssistContext, Assists},
11 AssistId, AssistKind,
12};
13
14// Assist: add_custom_impl
15//
16// Adds impl block for derived trait.
17//
18// ```
19// #[derive(Deb<|>ug, Display)]
20// struct S;
21// ```
22// ->
23// ```
24// #[derive(Display)]
25// struct S;
26//
27// impl Debug for S {
28// $0
29// }
30// ```
31pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
32 let attr = ctx.find_node_at_offset::<ast::Attr>()?;
33 let input = attr.token_tree()?;
34
35 let attr_name = attr
36 .syntax()
37 .descendants_with_tokens()
38 .filter(|t| t.kind() == IDENT)
39 .find_map(|i| i.into_token())
40 .filter(|t| *t.text() == "derive")?
41 .text()
42 .clone();
43
44 let trait_token =
45 ctx.token_at_offset().find(|t| t.kind() == IDENT && *t.text() != attr_name)?;
46
47 let annotated = attr.syntax().siblings(Direction::Next).find_map(ast::Name::cast)?;
48 let annotated_name = annotated.syntax().text().to_string();
49 let start_offset = annotated.syntax().parent()?.text_range().end();
50
51 let label =
52 format!("Add custom impl `{}` for `{}`", trait_token.text().as_str(), annotated_name);
53
54 let target = attr.syntax().text_range();
55 acc.add(AssistId("add_custom_impl", AssistKind::Refactor), label, target, |builder| {
56 let new_attr_input = input
57 .syntax()
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
81 match ctx.config.snippet_cap {
82 Some(cap) => {
83 builder.insert_snippet(
84 cap,
85 start_offset,
86 format!("\n\nimpl {} for {} {{\n $0\n}}", trait_token, annotated_name),
87 );
88 }
89 None => {
90 builder.insert(
91 start_offset,
92 format!("\n\nimpl {} for {} {{\n\n}}", trait_token, annotated_name),
93 );
94 }
95 }
96 })
97}
98
99#[cfg(test)]
100mod tests {
101 use crate::tests::{check_assist, check_assist_not_applicable};
102
103 use super::*;
104
105 #[test]
106 fn add_custom_impl_for_unique_input() {
107 check_assist(
108 add_custom_impl,
109 "
110#[derive(Debu<|>g)]
111struct Foo {
112 bar: String,
113}
114 ",
115 "
116struct Foo {
117 bar: String,
118}
119
120impl Debug for Foo {
121 $0
122}
123 ",
124 )
125 }
126
127 #[test]
128 fn add_custom_impl_for_with_visibility_modifier() {
129 check_assist(
130 add_custom_impl,
131 "
132#[derive(Debug<|>)]
133pub struct Foo {
134 bar: String,
135}
136 ",
137 "
138pub struct Foo {
139 bar: String,
140}
141
142impl Debug for Foo {
143 $0
144}
145 ",
146 )
147 }
148
149 #[test]
150 fn add_custom_impl_when_multiple_inputs() {
151 check_assist(
152 add_custom_impl,
153 "
154#[derive(Display, Debug<|>, Serialize)]
155struct Foo {}
156 ",
157 "
158#[derive(Display, Serialize)]
159struct Foo {}
160
161impl Debug for Foo {
162 $0
163}
164 ",
165 )
166 }
167
168 #[test]
169 fn test_ignore_derive_macro_without_input() {
170 check_assist_not_applicable(
171 add_custom_impl,
172 "
173#[derive(<|>)]
174struct Foo {}
175 ",
176 )
177 }
178
179 #[test]
180 fn test_ignore_if_cursor_on_param() {
181 check_assist_not_applicable(
182 add_custom_impl,
183 "
184#[derive<|>(Debug)]
185struct Foo {}
186 ",
187 );
188
189 check_assist_not_applicable(
190 add_custom_impl,
191 "
192#[derive(Debug)<|>]
193struct Foo {}
194 ",
195 )
196 }
197
198 #[test]
199 fn test_ignore_if_not_derive() {
200 check_assist_not_applicable(
201 add_custom_impl,
202 "
203#[allow(non_camel_<|>case_types)]
204struct Foo {}
205 ",
206 )
207 }
208}