aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_assists/src')
-rw-r--r--crates/ra_assists/src/assists/add_custom_impl.rs189
-rw-r--r--crates/ra_assists/src/lib.rs2
2 files changed, 191 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
3use crate::{Assist, AssistCtx, AssistId};
4use hir::db::HirDatabase;
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: &'static str = "derive";
14
15pub(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)]
82mod 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)]
92struct Foo {
93 bar: String,
94}
95 ",
96 "
97struct Foo {
98 bar: String,
99}
100
101impl 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<|>)]
114pub struct Foo {
115 bar: String,
116}
117 ",
118 "
119pub struct Foo {
120 bar: String,
121}
122
123impl 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)]
136struct Foo {}
137 ",
138 "
139#[derive(Display, Serialize)]
140struct Foo {}
141
142impl 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(<|>)]
155struct 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)]
166struct Foo {}
167 ",
168 );
169
170 check_assist_not_applicable(
171 add_custom_impl,
172 "
173#[derive(Debug)<|>]
174struct 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)]
185struct Foo {}
186 ",
187 )
188 }
189}
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs
index a372bd8b9..98fb20b22 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -95,6 +95,7 @@ mod assists {
95 mod add_derive; 95 mod add_derive;
96 mod add_explicit_type; 96 mod add_explicit_type;
97 mod add_impl; 97 mod add_impl;
98 mod add_custom_impl;
98 mod add_new; 99 mod add_new;
99 mod apply_demorgan; 100 mod apply_demorgan;
100 mod invert_if; 101 mod invert_if;
@@ -121,6 +122,7 @@ mod assists {
121 add_derive::add_derive, 122 add_derive::add_derive,
122 add_explicit_type::add_explicit_type, 123 add_explicit_type::add_explicit_type,
123 add_impl::add_impl, 124 add_impl::add_impl,
125 add_custom_impl::add_custom_impl,
124 add_new::add_new, 126 add_new::add_new,
125 apply_demorgan::apply_demorgan, 127 apply_demorgan::apply_demorgan,
126 invert_if::invert_if, 128 invert_if::invert_if,