Skip to content

Commit 439080f

Browse files
assists: add assist for custom implementation for derived trait
1 parent 922ec61 commit 439080f

File tree

2 files changed

+191
-0
lines changed

2 files changed

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

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,

0 commit comments

Comments
 (0)