diff options
Diffstat (limited to 'crates/ra_editor/src/assists/add_derive.rs')
-rw-r--r-- | crates/ra_editor/src/assists/add_derive.rs | 97 |
1 files changed, 97 insertions, 0 deletions
diff --git a/crates/ra_editor/src/assists/add_derive.rs b/crates/ra_editor/src/assists/add_derive.rs new file mode 100644 index 000000000..33d9d2c31 --- /dev/null +++ b/crates/ra_editor/src/assists/add_derive.rs | |||
@@ -0,0 +1,97 @@ | |||
1 | use ra_text_edit::TextEditBuilder; | ||
2 | use ra_syntax::{ | ||
3 | ast::{self, AstNode, AttrsOwner}, | ||
4 | SourceFileNode, | ||
5 | SyntaxKind::{WHITESPACE, COMMENT}, | ||
6 | TextUnit, | ||
7 | }; | ||
8 | |||
9 | use crate::{ | ||
10 | find_node_at_offset, | ||
11 | assists::LocalEdit, | ||
12 | }; | ||
13 | |||
14 | pub fn add_derive<'a>( | ||
15 | file: &'a SourceFileNode, | ||
16 | offset: TextUnit, | ||
17 | ) -> Option<impl FnOnce() -> LocalEdit + 'a> { | ||
18 | let nominal = find_node_at_offset::<ast::NominalDef>(file.syntax(), offset)?; | ||
19 | let node_start = derive_insertion_offset(nominal)?; | ||
20 | return Some(move || { | ||
21 | let derive_attr = nominal | ||
22 | .attrs() | ||
23 | .filter_map(|x| x.as_call()) | ||
24 | .filter(|(name, _arg)| name == "derive") | ||
25 | .map(|(_name, arg)| arg) | ||
26 | .next(); | ||
27 | let mut edit = TextEditBuilder::new(); | ||
28 | let offset = match derive_attr { | ||
29 | None => { | ||
30 | edit.insert(node_start, "#[derive()]\n".to_string()); | ||
31 | node_start + TextUnit::of_str("#[derive(") | ||
32 | } | ||
33 | Some(tt) => tt.syntax().range().end() - TextUnit::of_char(')'), | ||
34 | }; | ||
35 | LocalEdit { | ||
36 | label: "add `#[derive]`".to_string(), | ||
37 | edit: edit.finish(), | ||
38 | cursor_position: Some(offset), | ||
39 | } | ||
40 | }); | ||
41 | |||
42 | // Insert `derive` after doc comments. | ||
43 | fn derive_insertion_offset(nominal: ast::NominalDef) -> Option<TextUnit> { | ||
44 | let non_ws_child = nominal | ||
45 | .syntax() | ||
46 | .children() | ||
47 | .find(|it| it.kind() != COMMENT && it.kind() != WHITESPACE)?; | ||
48 | Some(non_ws_child.range().start()) | ||
49 | } | ||
50 | } | ||
51 | |||
52 | #[cfg(test)] | ||
53 | mod tests { | ||
54 | use super::*; | ||
55 | use crate::test_utils::check_action; | ||
56 | |||
57 | #[test] | ||
58 | fn add_derive_new() { | ||
59 | check_action( | ||
60 | "struct Foo { a: i32, <|>}", | ||
61 | "#[derive(<|>)]\nstruct Foo { a: i32, }", | ||
62 | |file, off| add_derive(file, off).map(|f| f()), | ||
63 | ); | ||
64 | check_action( | ||
65 | "struct Foo { <|> a: i32, }", | ||
66 | "#[derive(<|>)]\nstruct Foo { a: i32, }", | ||
67 | |file, off| add_derive(file, off).map(|f| f()), | ||
68 | ); | ||
69 | } | ||
70 | |||
71 | #[test] | ||
72 | fn add_derive_existing() { | ||
73 | check_action( | ||
74 | "#[derive(Clone)]\nstruct Foo { a: i32<|>, }", | ||
75 | "#[derive(Clone<|>)]\nstruct Foo { a: i32, }", | ||
76 | |file, off| add_derive(file, off).map(|f| f()), | ||
77 | ); | ||
78 | } | ||
79 | |||
80 | #[test] | ||
81 | fn add_derive_new_with_doc_comment() { | ||
82 | check_action( | ||
83 | " | ||
84 | /// `Foo` is a pretty important struct. | ||
85 | /// It does stuff. | ||
86 | struct Foo { a: i32<|>, } | ||
87 | ", | ||
88 | " | ||
89 | /// `Foo` is a pretty important struct. | ||
90 | /// It does stuff. | ||
91 | #[derive(<|>)] | ||
92 | struct Foo { a: i32, } | ||
93 | ", | ||
94 | |file, off| add_derive(file, off).map(|f| f()), | ||
95 | ); | ||
96 | } | ||
97 | } | ||