]> git.lizzy.rs Git - rust.git/blob - crates/ra_assists/src/handlers/add_custom_impl.rs
Centralize fixture parsing for assists
[rust.git] / crates / ra_assists / src / handlers / add_custom_impl.rs
1 use ra_syntax::{
2     ast::{self, AstNode},
3     Direction, SmolStr,
4     SyntaxKind::{IDENT, WHITESPACE},
5     TextRange, TextSize,
6 };
7 use stdx::SepBy;
8
9 use crate::{
10     assist_context::{AssistContext, Assists},
11     AssistId,
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 // ```
31 pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
32     let input = ctx.find_node_at_offset::<ast::AttrInput>()?;
33     let attr = input.syntax().parent().and_then(ast::Attr::cast)?;
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"), 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         let new_attr_input = new_attr_input.iter().sep_by(", ").surround_with("(", ")").to_string();
65
66         if has_more_derives {
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)]
100 mod 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)]
111 struct Foo {
112     bar: String,
113 }
114             ",
115             "
116 struct Foo {
117     bar: String,
118 }
119
120 impl 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<|>)]
133 pub struct Foo {
134     bar: String,
135 }
136             ",
137             "
138 pub struct Foo {
139     bar: String,
140 }
141
142 impl 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)]
155 struct Foo {}
156             ",
157             "
158 #[derive(Display, Serialize)]
159 struct Foo {}
160
161 impl 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(<|>)]
174 struct 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)]
185 struct Foo {}
186             ",
187         );
188
189         check_assist_not_applicable(
190             add_custom_impl,
191             "
192 #[derive(Debug)<|>]
193 struct 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)]
204 struct Foo {}
205             ",
206         )
207     }
208 }