aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src/handlers/add_custom_impl.rs
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2020-02-07 14:53:31 +0000
committerAleksey Kladov <[email protected]>2020-02-07 16:28:02 +0000
commit561b4b11ff1d87ea1ff2477dcba6ae1f396573a3 (patch)
tree0da58d08d5a2ff27f43c3eb6163ba9aced2f5782 /crates/ra_assists/src/handlers/add_custom_impl.rs
parentaa64a84b493aa9c0b22f36b472a445d622cd2172 (diff)
Name assist handlers
Diffstat (limited to 'crates/ra_assists/src/handlers/add_custom_impl.rs')
-rw-r--r--crates/ra_assists/src/handlers/add_custom_impl.rs209
1 files changed, 209 insertions, 0 deletions
diff --git a/crates/ra_assists/src/handlers/add_custom_impl.rs b/crates/ra_assists/src/handlers/add_custom_impl.rs
new file mode 100644
index 000000000..7fdd816bf
--- /dev/null
+++ b/crates/ra_assists/src/handlers/add_custom_impl.rs
@@ -0,0 +1,209 @@
1//! FIXME: write short doc here
2
3use crate::{Assist, AssistCtx, AssistId};
4
5use join_to_string::join;
6use ra_syntax::{
7 ast::{self, AstNode},
8 Direction, SmolStr,
9 SyntaxKind::{IDENT, WHITESPACE},
10 TextRange, TextUnit,
11};
12
13const DERIVE_TRAIT: &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// ```
32pub(crate) fn add_custom_impl(ctx: AssistCtx) -> 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 let label =
53 format!("Add custom impl '{}' for '{}'", trait_token.text().as_str(), annotated_name);
54
55 ctx.add_assist(AssistId("add_custom_impl"), label, |edit| {
56 edit.target(attr.syntax().text_range());
57
58 let new_attr_input = input
59 .syntax()
60 .descendants_with_tokens()
61 .filter(|t| t.kind() == IDENT)
62 .filter_map(|t| t.into_token().map(|t| t.text().clone()))
63 .filter(|t| t != trait_token.text())
64 .collect::<Vec<SmolStr>>();
65 let has_more_derives = new_attr_input.len() > 0;
66 let new_attr_input =
67 join(new_attr_input.iter()).separator(", ").surround_with("(", ")").to_string();
68 let new_attr_input_len = new_attr_input.len();
69
70 let mut buf = String::new();
71 buf.push_str("\n\nimpl ");
72 buf.push_str(trait_token.text().as_str());
73 buf.push_str(" for ");
74 buf.push_str(annotated_name.as_str());
75 buf.push_str(" {\n");
76
77 let cursor_delta = if has_more_derives {
78 edit.replace(input.syntax().text_range(), new_attr_input);
79 input.syntax().text_range().len() - TextUnit::from_usize(new_attr_input_len)
80 } else {
81 let attr_range = attr.syntax().text_range();
82 edit.delete(attr_range);
83
84 let line_break_range = attr
85 .syntax()
86 .next_sibling_or_token()
87 .filter(|t| t.kind() == WHITESPACE)
88 .map(|t| t.text_range())
89 .unwrap_or(TextRange::from_to(TextUnit::from(0), TextUnit::from(0)));
90 edit.delete(line_break_range);
91
92 attr_range.len() + line_break_range.len()
93 };
94
95 edit.set_cursor(start_offset + TextUnit::of_str(&buf) - cursor_delta);
96 buf.push_str("\n}");
97 edit.insert(start_offset, buf);
98 })
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104 use crate::helpers::{check_assist, check_assist_not_applicable};
105
106 #[test]
107 fn add_custom_impl_for_unique_input() {
108 check_assist(
109 add_custom_impl,
110 "
111#[derive(Debu<|>g)]
112struct Foo {
113 bar: String,
114}
115 ",
116 "
117struct Foo {
118 bar: String,
119}
120
121impl Debug for Foo {
122<|>
123}
124 ",
125 )
126 }
127
128 #[test]
129 fn add_custom_impl_for_with_visibility_modifier() {
130 check_assist(
131 add_custom_impl,
132 "
133#[derive(Debug<|>)]
134pub struct Foo {
135 bar: String,
136}
137 ",
138 "
139pub struct Foo {
140 bar: String,
141}
142
143impl Debug for Foo {
144<|>
145}
146 ",
147 )
148 }
149
150 #[test]
151 fn add_custom_impl_when_multiple_inputs() {
152 check_assist(
153 add_custom_impl,
154 "
155#[derive(Display, Debug<|>, Serialize)]
156struct Foo {}
157 ",
158 "
159#[derive(Display, Serialize)]
160struct Foo {}
161
162impl Debug for Foo {
163<|>
164}
165 ",
166 )
167 }
168
169 #[test]
170 fn test_ignore_derive_macro_without_input() {
171 check_assist_not_applicable(
172 add_custom_impl,
173 "
174#[derive(<|>)]
175struct Foo {}
176 ",
177 )
178 }
179
180 #[test]
181 fn test_ignore_if_cursor_on_param() {
182 check_assist_not_applicable(
183 add_custom_impl,
184 "
185#[derive<|>(Debug)]
186struct Foo {}
187 ",
188 );
189
190 check_assist_not_applicable(
191 add_custom_impl,
192 "
193#[derive(Debug)<|>]
194struct Foo {}
195 ",
196 )
197 }
198
199 #[test]
200 fn test_ignore_if_not_derive() {
201 check_assist_not_applicable(
202 add_custom_impl,
203 "
204#[allow(non_camel_<|>case_types)]
205struct Foo {}
206 ",
207 )
208 }
209}