Skip to content

Commit 3376c08

Browse files
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 <paulolieuthier@gmail.com>
2 parents c5b322e + 5b2d52c commit 3376c08

File tree

4 files changed

+245
-0
lines changed

4 files changed

+245
-0
lines changed
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}

crates/ra_assists/src/doc_tests/generated.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,25 @@
22
33
use super::check;
44

5+
#[test]
6+
fn doctest_add_custom_impl() {
7+
check(
8+
"add_custom_impl",
9+
r#####"
10+
#[derive(Deb<|>ug, Display)]
11+
struct S;
12+
"#####,
13+
r#####"
14+
#[derive(Display)]
15+
struct S;
16+
17+
impl Debug for S {
18+
19+
}
20+
"#####,
21+
)
22+
}
23+
524
#[test]
625
fn doctest_add_derive() {
726
check(

crates/ra_assists/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ mod assists {
9595
mod add_derive;
9696
mod add_explicit_type;
9797
mod add_impl;
98+
mod add_custom_impl;
9899
mod add_new;
99100
mod apply_demorgan;
100101
mod invert_if;
@@ -121,6 +122,7 @@ mod assists {
121122
add_derive::add_derive,
122123
add_explicit_type::add_explicit_type,
123124
add_impl::add_impl,
125+
add_custom_impl::add_custom_impl,
124126
add_new::add_new,
125127
apply_demorgan::apply_demorgan,
126128
invert_if::invert_if,

docs/user/assists.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,24 @@
33
Cursor position or selection is signified by `` character.
44

55

6+
## `add_custom_impl`
7+
8+
Adds impl block for derived trait.
9+
10+
```rust
11+
// BEFORE
12+
#[derive(Deb┃ug, Display)]
13+
struct S;
14+
15+
// AFTER
16+
#[derive(Display)]
17+
struct S;
18+
19+
impl Debug for S {
20+
21+
}
22+
```
23+
624
## `add_derive`
725

826
Adds a new `#[derive()]` clause to a struct or enum.

0 commit comments

Comments
 (0)