diff options
author | Zac Pullar-Strecker <[email protected]> | 2020-08-24 10:19:53 +0100 |
---|---|---|
committer | Zac Pullar-Strecker <[email protected]> | 2020-08-24 10:20:13 +0100 |
commit | 7bbca7a1b3f9293d2f5cc5745199bc5f8396f2f0 (patch) | |
tree | bdb47765991cb973b2cd5481a088fac636bd326c /crates/assists/src/handlers/add_custom_impl.rs | |
parent | ca464650eeaca6195891199a93f4f76cf3e7e697 (diff) | |
parent | e65d48d1fb3d4d91d9dc1148a7a836ff5c9a3c87 (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.rs | 208 |
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 @@ | |||
1 | use itertools::Itertools; | ||
2 | use syntax::{ | ||
3 | ast::{self, AstNode}, | ||
4 | Direction, SmolStr, | ||
5 | SyntaxKind::{IDENT, WHITESPACE}, | ||
6 | TextRange, TextSize, | ||
7 | }; | ||
8 | |||
9 | use 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 | // ``` | ||
31 | pub(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)] | ||
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 | } | ||