diff options
author | Paulo Lieuthier <[email protected]> | 2019-10-15 19:29:20 +0100 |
---|---|---|
committer | Paulo Lieuthier <[email protected]> | 2019-11-28 19:56:06 +0000 |
commit | 439080f0274cf4def3f393f466ceb05c8cb8bcd2 (patch) | |
tree | 8b81b33c7ebb16d674e4ceff44e9295dd45e166d /crates/ra_assists/src/assists | |
parent | 922ec610d19aef7fb8b4df389ac318f4f2a0202c (diff) |
assists: add assist for custom implementation for derived trait
Diffstat (limited to 'crates/ra_assists/src/assists')
-rw-r--r-- | crates/ra_assists/src/assists/add_custom_impl.rs | 189 |
1 files changed, 189 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..7e64cd902 --- /dev/null +++ b/crates/ra_assists/src/assists/add_custom_impl.rs | |||
@@ -0,0 +1,189 @@ | |||
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 | pub(crate) fn add_custom_impl(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
16 | let input = ctx.find_node_at_offset::<ast::AttrInput>()?; | ||
17 | let attr = input.syntax().parent().and_then(ast::Attr::cast)?; | ||
18 | |||
19 | let attr_name = attr | ||
20 | .syntax() | ||
21 | .descendants_with_tokens() | ||
22 | .filter(|t| t.kind() == IDENT) | ||
23 | .find_map(|i| i.into_token()) | ||
24 | .filter(|t| *t.text() == DERIVE_TRAIT)? | ||
25 | .text() | ||
26 | .clone(); | ||
27 | |||
28 | let trait_token = | ||
29 | ctx.token_at_offset().filter(|t| t.kind() == IDENT && *t.text() != attr_name).next()?; | ||
30 | |||
31 | let annotated = attr.syntax().siblings(Direction::Next).find_map(|s| ast::Name::cast(s))?; | ||
32 | let annotated_name = annotated.syntax().text().to_string(); | ||
33 | let start_offset = annotated.syntax().parent()?.text_range().end(); | ||
34 | |||
35 | ctx.add_assist(AssistId("add_custom_impl"), "add custom impl", |edit| { | ||
36 | edit.target(attr.syntax().text_range()); | ||
37 | |||
38 | let new_attr_input = input | ||
39 | .syntax() | ||
40 | .descendants_with_tokens() | ||
41 | .filter(|t| t.kind() == IDENT) | ||
42 | .filter_map(|t| t.into_token().map(|t| t.text().clone())) | ||
43 | .filter(|t| t != trait_token.text()) | ||
44 | .collect::<Vec<SmolStr>>(); | ||
45 | let has_more_derives = new_attr_input.len() > 0; | ||
46 | let new_attr_input = | ||
47 | join(new_attr_input.iter()).separator(", ").surround_with("(", ")").to_string(); | ||
48 | let new_attr_input_len = new_attr_input.len(); | ||
49 | |||
50 | let mut buf = String::new(); | ||
51 | buf.push_str("\n\nimpl "); | ||
52 | buf.push_str(trait_token.text().as_str()); | ||
53 | buf.push_str(" for "); | ||
54 | buf.push_str(annotated_name.as_str()); | ||
55 | buf.push_str(" {\n"); | ||
56 | |||
57 | let cursor_delta = if has_more_derives { | ||
58 | edit.replace(input.syntax().text_range(), new_attr_input); | ||
59 | input.syntax().text_range().len() - TextUnit::from_usize(new_attr_input_len) | ||
60 | } else { | ||
61 | let attr_range = attr.syntax().text_range(); | ||
62 | edit.delete(attr_range); | ||
63 | |||
64 | let line_break_range = attr | ||
65 | .syntax() | ||
66 | .next_sibling_or_token() | ||
67 | .filter(|t| t.kind() == WHITESPACE) | ||
68 | .map(|t| t.text_range()) | ||
69 | .unwrap_or(TextRange::from_to(TextUnit::from(0), TextUnit::from(0))); | ||
70 | edit.delete(line_break_range); | ||
71 | |||
72 | attr_range.len() + line_break_range.len() | ||
73 | }; | ||
74 | |||
75 | edit.set_cursor(start_offset + TextUnit::of_str(&buf) - cursor_delta); | ||
76 | buf.push_str("\n}"); | ||
77 | edit.insert(start_offset, buf); | ||
78 | }) | ||
79 | } | ||
80 | |||
81 | #[cfg(test)] | ||
82 | mod tests { | ||
83 | use super::*; | ||
84 | use crate::helpers::{check_assist, check_assist_not_applicable}; | ||
85 | |||
86 | #[test] | ||
87 | fn add_custom_impl_for_unique_input() { | ||
88 | check_assist( | ||
89 | add_custom_impl, | ||
90 | " | ||
91 | #[derive(Debu<|>g)] | ||
92 | struct Foo { | ||
93 | bar: String, | ||
94 | } | ||
95 | ", | ||
96 | " | ||
97 | struct Foo { | ||
98 | bar: String, | ||
99 | } | ||
100 | |||
101 | impl Debug for Foo { | ||
102 | <|> | ||
103 | } | ||
104 | ", | ||
105 | ) | ||
106 | } | ||
107 | |||
108 | #[test] | ||
109 | fn add_custom_impl_for_with_visibility_modifier() { | ||
110 | check_assist( | ||
111 | add_custom_impl, | ||
112 | " | ||
113 | #[derive(Debug<|>)] | ||
114 | pub struct Foo { | ||
115 | bar: String, | ||
116 | } | ||
117 | ", | ||
118 | " | ||
119 | pub struct Foo { | ||
120 | bar: String, | ||
121 | } | ||
122 | |||
123 | impl Debug for Foo { | ||
124 | <|> | ||
125 | } | ||
126 | ", | ||
127 | ) | ||
128 | } | ||
129 | |||
130 | #[test] | ||
131 | fn add_custom_impl_when_multiple_inputs() { | ||
132 | check_assist( | ||
133 | add_custom_impl, | ||
134 | " | ||
135 | #[derive(Display, Debug<|>, Serialize)] | ||
136 | struct Foo {} | ||
137 | ", | ||
138 | " | ||
139 | #[derive(Display, Serialize)] | ||
140 | struct Foo {} | ||
141 | |||
142 | impl Debug for Foo { | ||
143 | <|> | ||
144 | } | ||
145 | ", | ||
146 | ) | ||
147 | } | ||
148 | |||
149 | #[test] | ||
150 | fn test_ignore_derive_macro_without_input() { | ||
151 | check_assist_not_applicable( | ||
152 | add_custom_impl, | ||
153 | " | ||
154 | #[derive(<|>)] | ||
155 | struct Foo {} | ||
156 | ", | ||
157 | ) | ||
158 | } | ||
159 | |||
160 | #[test] | ||
161 | fn test_ignore_if_cursor_on_param() { | ||
162 | check_assist_not_applicable( | ||
163 | add_custom_impl, | ||
164 | " | ||
165 | #[derive<|>(Debug)] | ||
166 | struct Foo {} | ||
167 | ", | ||
168 | ); | ||
169 | |||
170 | check_assist_not_applicable( | ||
171 | add_custom_impl, | ||
172 | " | ||
173 | #[derive(Debug)<|>] | ||
174 | struct Foo {} | ||
175 | ", | ||
176 | ) | ||
177 | } | ||
178 | |||
179 | #[test] | ||
180 | fn test_ignore_if_not_derive() { | ||
181 | check_assist_not_applicable( | ||
182 | add_custom_impl, | ||
183 | " | ||
184 | #[allow(non_camel_<|>case_types)] | ||
185 | struct Foo {} | ||
186 | ", | ||
187 | ) | ||
188 | } | ||
189 | } | ||