diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2019-12-02 15:52:11 +0000 |
---|---|---|
committer | GitHub <[email protected]> | 2019-12-02 15:52:11 +0000 |
commit | 3376c08052a563a5d2db487c458972378edebf44 (patch) | |
tree | b36bc027c62ac8dd4cf89fe8415b89666f501b5d /crates/ra_assists/src/assists | |
parent | c5b322e3df0f3c9d81676bece76bcb76eac6e1ba (diff) | |
parent | 5b2d52c8df5235fce9d2ae78adc3182a9659b268 (diff) |
Merge #2018
2018: assists: add assist for custom implementation for derived trait r=matklad a=paulolieuthier
Please, tell me if something could be more idiomatic or efficient.
Fixes #1256.
Co-authored-by: Paulo Lieuthier <[email protected]>
Diffstat (limited to 'crates/ra_assists/src/assists')
-rw-r--r-- | crates/ra_assists/src/assists/add_custom_impl.rs | 206 |
1 files changed, 206 insertions, 0 deletions
diff --git a/crates/ra_assists/src/assists/add_custom_impl.rs b/crates/ra_assists/src/assists/add_custom_impl.rs new file mode 100644 index 000000000..037306fd6 --- /dev/null +++ b/crates/ra_assists/src/assists/add_custom_impl.rs | |||
@@ -0,0 +1,206 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use crate::{Assist, AssistCtx, AssistId}; | ||
4 | use hir::db::HirDatabase; | ||
5 | use join_to_string::join; | ||
6 | use ra_syntax::{ | ||
7 | ast::{self, AstNode}, | ||
8 | Direction, SmolStr, | ||
9 | SyntaxKind::{IDENT, WHITESPACE}, | ||
10 | TextRange, TextUnit, | ||
11 | }; | ||
12 | |||
13 | const DERIVE_TRAIT: &'static str = "derive"; | ||
14 | |||
15 | // Assist: add_custom_impl | ||
16 | // | ||
17 | // Adds impl block for derived trait. | ||
18 | // | ||
19 | // ``` | ||
20 | // #[derive(Deb<|>ug, Display)] | ||
21 | // struct S; | ||
22 | // ``` | ||
23 | // -> | ||
24 | // ``` | ||
25 | // #[derive(Display)] | ||
26 | // struct S; | ||
27 | // | ||
28 | // impl Debug for S { | ||
29 | // | ||
30 | // } | ||
31 | // ``` | ||
32 | pub(crate) fn add_custom_impl(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
33 | let input = ctx.find_node_at_offset::<ast::AttrInput>()?; | ||
34 | let attr = input.syntax().parent().and_then(ast::Attr::cast)?; | ||
35 | |||
36 | let attr_name = attr | ||
37 | .syntax() | ||
38 | .descendants_with_tokens() | ||
39 | .filter(|t| t.kind() == IDENT) | ||
40 | .find_map(|i| i.into_token()) | ||
41 | .filter(|t| *t.text() == DERIVE_TRAIT)? | ||
42 | .text() | ||
43 | .clone(); | ||
44 | |||
45 | let trait_token = | ||
46 | ctx.token_at_offset().filter(|t| t.kind() == IDENT && *t.text() != attr_name).next()?; | ||
47 | |||
48 | let annotated = attr.syntax().siblings(Direction::Next).find_map(|s| ast::Name::cast(s))?; | ||
49 | let annotated_name = annotated.syntax().text().to_string(); | ||
50 | let start_offset = annotated.syntax().parent()?.text_range().end(); | ||
51 | |||
52 | ctx.add_assist(AssistId("add_custom_impl"), "add custom impl", |edit| { | ||
53 | edit.target(attr.syntax().text_range()); | ||
54 | |||
55 | let new_attr_input = input | ||
56 | .syntax() | ||
57 | .descendants_with_tokens() | ||
58 | .filter(|t| t.kind() == IDENT) | ||
59 | .filter_map(|t| t.into_token().map(|t| t.text().clone())) | ||
60 | .filter(|t| t != trait_token.text()) | ||
61 | .collect::<Vec<SmolStr>>(); | ||
62 | let has_more_derives = new_attr_input.len() > 0; | ||
63 | let new_attr_input = | ||
64 | join(new_attr_input.iter()).separator(", ").surround_with("(", ")").to_string(); | ||
65 | let new_attr_input_len = new_attr_input.len(); | ||
66 | |||
67 | let mut buf = String::new(); | ||
68 | buf.push_str("\n\nimpl "); | ||
69 | buf.push_str(trait_token.text().as_str()); | ||
70 | buf.push_str(" for "); | ||
71 | buf.push_str(annotated_name.as_str()); | ||
72 | buf.push_str(" {\n"); | ||
73 | |||
74 | let cursor_delta = if has_more_derives { | ||
75 | edit.replace(input.syntax().text_range(), new_attr_input); | ||
76 | input.syntax().text_range().len() - TextUnit::from_usize(new_attr_input_len) | ||
77 | } else { | ||
78 | let attr_range = attr.syntax().text_range(); | ||
79 | edit.delete(attr_range); | ||
80 | |||
81 | let line_break_range = attr | ||
82 | .syntax() | ||
83 | .next_sibling_or_token() | ||
84 | .filter(|t| t.kind() == WHITESPACE) | ||
85 | .map(|t| t.text_range()) | ||
86 | .unwrap_or(TextRange::from_to(TextUnit::from(0), TextUnit::from(0))); | ||
87 | edit.delete(line_break_range); | ||
88 | |||
89 | attr_range.len() + line_break_range.len() | ||
90 | }; | ||
91 | |||
92 | edit.set_cursor(start_offset + TextUnit::of_str(&buf) - cursor_delta); | ||
93 | buf.push_str("\n}"); | ||
94 | edit.insert(start_offset, buf); | ||
95 | }) | ||
96 | } | ||
97 | |||
98 | #[cfg(test)] | ||
99 | mod tests { | ||
100 | use super::*; | ||
101 | use crate::helpers::{check_assist, check_assist_not_applicable}; | ||
102 | |||
103 | #[test] | ||
104 | fn add_custom_impl_for_unique_input() { | ||
105 | check_assist( | ||
106 | add_custom_impl, | ||
107 | " | ||
108 | #[derive(Debu<|>g)] | ||
109 | struct Foo { | ||
110 | bar: String, | ||
111 | } | ||
112 | ", | ||
113 | " | ||
114 | struct Foo { | ||
115 | bar: String, | ||
116 | } | ||
117 | |||
118 | impl Debug for Foo { | ||
119 | <|> | ||
120 | } | ||
121 | ", | ||
122 | ) | ||
123 | } | ||
124 | |||
125 | #[test] | ||
126 | fn add_custom_impl_for_with_visibility_modifier() { | ||
127 | check_assist( | ||
128 | add_custom_impl, | ||
129 | " | ||
130 | #[derive(Debug<|>)] | ||
131 | pub struct Foo { | ||
132 | bar: String, | ||
133 | } | ||
134 | ", | ||
135 | " | ||
136 | pub struct Foo { | ||
137 | bar: String, | ||
138 | } | ||
139 | |||
140 | impl Debug for Foo { | ||
141 | <|> | ||
142 | } | ||
143 | ", | ||
144 | ) | ||
145 | } | ||
146 | |||
147 | #[test] | ||
148 | fn add_custom_impl_when_multiple_inputs() { | ||
149 | check_assist( | ||
150 | add_custom_impl, | ||
151 | " | ||
152 | #[derive(Display, Debug<|>, Serialize)] | ||
153 | struct Foo {} | ||
154 | ", | ||
155 | " | ||
156 | #[derive(Display, Serialize)] | ||
157 | struct Foo {} | ||
158 | |||
159 | impl Debug for Foo { | ||
160 | <|> | ||
161 | } | ||
162 | ", | ||
163 | ) | ||
164 | } | ||
165 | |||
166 | #[test] | ||
167 | fn test_ignore_derive_macro_without_input() { | ||
168 | check_assist_not_applicable( | ||
169 | add_custom_impl, | ||
170 | " | ||
171 | #[derive(<|>)] | ||
172 | struct Foo {} | ||
173 | ", | ||
174 | ) | ||
175 | } | ||
176 | |||
177 | #[test] | ||
178 | fn test_ignore_if_cursor_on_param() { | ||
179 | check_assist_not_applicable( | ||
180 | add_custom_impl, | ||
181 | " | ||
182 | #[derive<|>(Debug)] | ||
183 | struct Foo {} | ||
184 | ", | ||
185 | ); | ||
186 | |||
187 | check_assist_not_applicable( | ||
188 | add_custom_impl, | ||
189 | " | ||
190 | #[derive(Debug)<|>] | ||
191 | struct Foo {} | ||
192 | ", | ||
193 | ) | ||
194 | } | ||
195 | |||
196 | #[test] | ||
197 | fn test_ignore_if_not_derive() { | ||
198 | check_assist_not_applicable( | ||
199 | add_custom_impl, | ||
200 | " | ||
201 | #[allow(non_camel_<|>case_types)] | ||
202 | struct Foo {} | ||
203 | ", | ||
204 | ) | ||
205 | } | ||
206 | } | ||