diff options
Diffstat (limited to 'crates/ra_editor')
-rw-r--r-- | crates/ra_editor/src/assists.rs | 188 | ||||
-rw-r--r-- | crates/ra_editor/src/assists/add_derive.rs | 84 | ||||
-rw-r--r-- | crates/ra_editor/src/assists/add_impl.rs | 66 | ||||
-rw-r--r-- | crates/ra_editor/src/assists/change_visibility.rs | 116 | ||||
-rw-r--r-- | crates/ra_editor/src/assists/flip_comma.rs | 31 | ||||
-rw-r--r-- | crates/ra_editor/src/assists/introduce_variable.rs | 144 | ||||
-rw-r--r-- | crates/ra_editor/src/assists/split_import.rs | 44 | ||||
-rw-r--r-- | crates/ra_editor/src/code_actions.rs | 415 | ||||
-rw-r--r-- | crates/ra_editor/src/diagnostics.rs | 6 | ||||
-rw-r--r-- | crates/ra_editor/src/lib.rs | 6 | ||||
-rw-r--r-- | crates/ra_editor/src/structure.rs | 6 | ||||
-rw-r--r-- | crates/ra_editor/src/typing.rs | 68 |
12 files changed, 744 insertions, 430 deletions
diff --git a/crates/ra_editor/src/assists.rs b/crates/ra_editor/src/assists.rs new file mode 100644 index 000000000..57b78342a --- /dev/null +++ b/crates/ra_editor/src/assists.rs | |||
@@ -0,0 +1,188 @@ | |||
1 | //! This modules contains various "assits": suggestions for source code edits | ||
2 | //! which are likely to occur at a given cursor positon. For example, if the | ||
3 | //! cursor is on the `,`, a possible assist is swapping the elments around the | ||
4 | //! comma. | ||
5 | |||
6 | mod flip_comma; | ||
7 | mod add_derive; | ||
8 | mod add_impl; | ||
9 | mod introduce_variable; | ||
10 | mod change_visibility; | ||
11 | mod split_import; | ||
12 | |||
13 | use ra_text_edit::{TextEdit, TextEditBuilder}; | ||
14 | use ra_syntax::{ | ||
15 | Direction, SyntaxNodeRef, TextUnit, TextRange,SourceFileNode, AstNode, | ||
16 | algo::{find_leaf_at_offset, find_covering_node, LeafAtOffset}, | ||
17 | }; | ||
18 | |||
19 | use crate::find_node_at_offset; | ||
20 | |||
21 | pub use self::{ | ||
22 | flip_comma::flip_comma, | ||
23 | add_derive::add_derive, | ||
24 | add_impl::add_impl, | ||
25 | introduce_variable::introduce_variable, | ||
26 | change_visibility::change_visibility, | ||
27 | split_import::split_import, | ||
28 | }; | ||
29 | |||
30 | /// Return all the assists applicable at the given position. | ||
31 | pub fn assists(file: &SourceFileNode, range: TextRange) -> Vec<LocalEdit> { | ||
32 | let ctx = AssistCtx::new(file, range); | ||
33 | [ | ||
34 | flip_comma, | ||
35 | add_derive, | ||
36 | add_impl, | ||
37 | introduce_variable, | ||
38 | change_visibility, | ||
39 | split_import, | ||
40 | ] | ||
41 | .iter() | ||
42 | .filter_map(|&assist| ctx.clone().apply(assist)) | ||
43 | .collect() | ||
44 | } | ||
45 | |||
46 | #[derive(Debug)] | ||
47 | pub struct LocalEdit { | ||
48 | pub label: String, | ||
49 | pub edit: TextEdit, | ||
50 | pub cursor_position: Option<TextUnit>, | ||
51 | } | ||
52 | |||
53 | fn non_trivia_sibling(node: SyntaxNodeRef, direction: Direction) -> Option<SyntaxNodeRef> { | ||
54 | node.siblings(direction) | ||
55 | .skip(1) | ||
56 | .find(|node| !node.kind().is_trivia()) | ||
57 | } | ||
58 | |||
59 | /// `AssistCtx` allows to apply an assist or check if it could be applied. | ||
60 | /// | ||
61 | /// Assists use a somewhat overengeneered approach, given the current needs. The | ||
62 | /// assists workflow consists of two phases. In the first phase, a user asks for | ||
63 | /// the list of available assists. In the second phase, the user picks a | ||
64 | /// particular assist and it gets applied. | ||
65 | /// | ||
66 | /// There are two peculiarities here: | ||
67 | /// | ||
68 | /// * first, we ideally avoid computing more things then neccessary to answer | ||
69 | /// "is assist applicable" in the first phase. | ||
70 | /// * second, when we are appling assist, we don't have a gurantee that there | ||
71 | /// weren't any changes between the point when user asked for assists and when | ||
72 | /// they applied a particular assist. So, when applying assist, we need to do | ||
73 | /// all the checks from scratch. | ||
74 | /// | ||
75 | /// To avoid repeating the same code twice for both "check" and "apply" | ||
76 | /// functions, we use an approach remeniscent of that of Django's function based | ||
77 | /// views dealing with forms. Each assist receives a runtime parameter, | ||
78 | /// `should_compute_edit`. It first check if an edit is applicable (potentially | ||
79 | /// computing info required to compute the actual edit). If it is applicable, | ||
80 | /// and `should_compute_edit` is `true`, it then computes the actual edit. | ||
81 | /// | ||
82 | /// So, to implement the original assists workflow, we can first apply each edit | ||
83 | /// with `should_compute_edit = false`, and then applying the selected edit | ||
84 | /// again, with `should_compute_edit = true` this time. | ||
85 | /// | ||
86 | /// Note, however, that we don't actually use such two-phase logic at the | ||
87 | /// moment, because the LSP API is pretty awkward in this place, and it's much | ||
88 | /// easier to just compute the edit eagarly :-) | ||
89 | #[derive(Debug, Clone)] | ||
90 | pub struct AssistCtx<'a> { | ||
91 | source_file: &'a SourceFileNode, | ||
92 | range: TextRange, | ||
93 | should_compute_edit: bool, | ||
94 | } | ||
95 | |||
96 | #[derive(Debug)] | ||
97 | pub enum Assist { | ||
98 | Applicable, | ||
99 | Edit(LocalEdit), | ||
100 | } | ||
101 | |||
102 | #[derive(Default)] | ||
103 | struct AssistBuilder { | ||
104 | edit: TextEditBuilder, | ||
105 | cursor_position: Option<TextUnit>, | ||
106 | } | ||
107 | |||
108 | impl<'a> AssistCtx<'a> { | ||
109 | pub fn new(source_file: &'a SourceFileNode, range: TextRange) -> AssistCtx { | ||
110 | AssistCtx { | ||
111 | source_file, | ||
112 | range, | ||
113 | should_compute_edit: false, | ||
114 | } | ||
115 | } | ||
116 | |||
117 | pub fn apply(mut self, assist: fn(AssistCtx) -> Option<Assist>) -> Option<LocalEdit> { | ||
118 | self.should_compute_edit = true; | ||
119 | match assist(self) { | ||
120 | None => None, | ||
121 | Some(Assist::Edit(e)) => Some(e), | ||
122 | Some(Assist::Applicable) => unreachable!(), | ||
123 | } | ||
124 | } | ||
125 | |||
126 | pub fn check(mut self, assist: fn(AssistCtx) -> Option<Assist>) -> bool { | ||
127 | self.should_compute_edit = false; | ||
128 | match assist(self) { | ||
129 | None => false, | ||
130 | Some(Assist::Edit(_)) => unreachable!(), | ||
131 | Some(Assist::Applicable) => true, | ||
132 | } | ||
133 | } | ||
134 | |||
135 | fn build(self, label: impl Into<String>, f: impl FnOnce(&mut AssistBuilder)) -> Option<Assist> { | ||
136 | if !self.should_compute_edit { | ||
137 | return Some(Assist::Applicable); | ||
138 | } | ||
139 | let mut edit = AssistBuilder::default(); | ||
140 | f(&mut edit); | ||
141 | Some(Assist::Edit(LocalEdit { | ||
142 | label: label.into(), | ||
143 | edit: edit.edit.finish(), | ||
144 | cursor_position: edit.cursor_position, | ||
145 | })) | ||
146 | } | ||
147 | |||
148 | pub(crate) fn leaf_at_offset(&self) -> LeafAtOffset<SyntaxNodeRef<'a>> { | ||
149 | find_leaf_at_offset(self.source_file.syntax(), self.range.start()) | ||
150 | } | ||
151 | pub(crate) fn node_at_offset<N: AstNode<'a>>(&self) -> Option<N> { | ||
152 | find_node_at_offset(self.source_file.syntax(), self.range.start()) | ||
153 | } | ||
154 | pub(crate) fn covering_node(&self) -> SyntaxNodeRef<'a> { | ||
155 | find_covering_node(self.source_file.syntax(), self.range) | ||
156 | } | ||
157 | } | ||
158 | |||
159 | impl AssistBuilder { | ||
160 | fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) { | ||
161 | self.edit.replace(range, replace_with.into()) | ||
162 | } | ||
163 | #[allow(unused)] | ||
164 | fn delete(&mut self, range: TextRange) { | ||
165 | self.edit.delete(range) | ||
166 | } | ||
167 | fn insert(&mut self, offset: TextUnit, text: impl Into<String>) { | ||
168 | self.edit.insert(offset, text.into()) | ||
169 | } | ||
170 | fn set_cursor(&mut self, offset: TextUnit) { | ||
171 | self.cursor_position = Some(offset) | ||
172 | } | ||
173 | } | ||
174 | |||
175 | #[cfg(test)] | ||
176 | fn check_assist(assist: fn(AssistCtx) -> Option<Assist>, before: &str, after: &str) { | ||
177 | crate::test_utils::check_action(before, after, |file, off| { | ||
178 | let range = TextRange::offset_len(off, 0.into()); | ||
179 | AssistCtx::new(file, range).apply(assist) | ||
180 | }) | ||
181 | } | ||
182 | |||
183 | #[cfg(test)] | ||
184 | fn check_assist_range(assist: fn(AssistCtx) -> Option<Assist>, before: &str, after: &str) { | ||
185 | crate::test_utils::check_action_range(before, after, |file, range| { | ||
186 | AssistCtx::new(file, range).apply(assist) | ||
187 | }) | ||
188 | } | ||
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..1e2cd4f30 --- /dev/null +++ b/crates/ra_editor/src/assists/add_derive.rs | |||
@@ -0,0 +1,84 @@ | |||
1 | use ra_syntax::{ | ||
2 | ast::{self, AstNode, AttrsOwner}, | ||
3 | SyntaxKind::{WHITESPACE, COMMENT}, | ||
4 | TextUnit, | ||
5 | }; | ||
6 | |||
7 | use crate::assists::{AssistCtx, Assist}; | ||
8 | |||
9 | pub fn add_derive(ctx: AssistCtx) -> Option<Assist> { | ||
10 | let nominal = ctx.node_at_offset::<ast::NominalDef>()?; | ||
11 | let node_start = derive_insertion_offset(nominal)?; | ||
12 | ctx.build("add `#[derive]`", |edit| { | ||
13 | let derive_attr = nominal | ||
14 | .attrs() | ||
15 | .filter_map(|x| x.as_call()) | ||
16 | .filter(|(name, _arg)| name == "derive") | ||
17 | .map(|(_name, arg)| arg) | ||
18 | .next(); | ||
19 | let offset = match derive_attr { | ||
20 | None => { | ||
21 | edit.insert(node_start, "#[derive()]\n"); | ||
22 | node_start + TextUnit::of_str("#[derive(") | ||
23 | } | ||
24 | Some(tt) => tt.syntax().range().end() - TextUnit::of_char(')'), | ||
25 | }; | ||
26 | edit.set_cursor(offset) | ||
27 | }) | ||
28 | } | ||
29 | |||
30 | // Insert `derive` after doc comments. | ||
31 | fn derive_insertion_offset(nominal: ast::NominalDef) -> Option<TextUnit> { | ||
32 | let non_ws_child = nominal | ||
33 | .syntax() | ||
34 | .children() | ||
35 | .find(|it| it.kind() != COMMENT && it.kind() != WHITESPACE)?; | ||
36 | Some(non_ws_child.range().start()) | ||
37 | } | ||
38 | |||
39 | #[cfg(test)] | ||
40 | mod tests { | ||
41 | use super::*; | ||
42 | use crate::assists::check_assist; | ||
43 | |||
44 | #[test] | ||
45 | fn add_derive_new() { | ||
46 | check_assist( | ||
47 | add_derive, | ||
48 | "struct Foo { a: i32, <|>}", | ||
49 | "#[derive(<|>)]\nstruct Foo { a: i32, }", | ||
50 | ); | ||
51 | check_assist( | ||
52 | add_derive, | ||
53 | "struct Foo { <|> a: i32, }", | ||
54 | "#[derive(<|>)]\nstruct Foo { a: i32, }", | ||
55 | ); | ||
56 | } | ||
57 | |||
58 | #[test] | ||
59 | fn add_derive_existing() { | ||
60 | check_assist( | ||
61 | add_derive, | ||
62 | "#[derive(Clone)]\nstruct Foo { a: i32<|>, }", | ||
63 | "#[derive(Clone<|>)]\nstruct Foo { a: i32, }", | ||
64 | ); | ||
65 | } | ||
66 | |||
67 | #[test] | ||
68 | fn add_derive_new_with_doc_comment() { | ||
69 | check_assist( | ||
70 | add_derive, | ||
71 | " | ||
72 | /// `Foo` is a pretty important struct. | ||
73 | /// It does stuff. | ||
74 | struct Foo { a: i32<|>, } | ||
75 | ", | ||
76 | " | ||
77 | /// `Foo` is a pretty important struct. | ||
78 | /// It does stuff. | ||
79 | #[derive(<|>)] | ||
80 | struct Foo { a: i32, } | ||
81 | ", | ||
82 | ); | ||
83 | } | ||
84 | } | ||
diff --git a/crates/ra_editor/src/assists/add_impl.rs b/crates/ra_editor/src/assists/add_impl.rs new file mode 100644 index 000000000..9353e2717 --- /dev/null +++ b/crates/ra_editor/src/assists/add_impl.rs | |||
@@ -0,0 +1,66 @@ | |||
1 | use join_to_string::join; | ||
2 | use ra_syntax::{ | ||
3 | ast::{self, AstNode, NameOwner, TypeParamsOwner}, | ||
4 | TextUnit, | ||
5 | }; | ||
6 | |||
7 | use crate::assists::{AssistCtx, Assist}; | ||
8 | |||
9 | pub fn add_impl(ctx: AssistCtx) -> Option<Assist> { | ||
10 | let nominal = ctx.node_at_offset::<ast::NominalDef>()?; | ||
11 | let name = nominal.name()?; | ||
12 | ctx.build("add impl", |edit| { | ||
13 | let type_params = nominal.type_param_list(); | ||
14 | let start_offset = nominal.syntax().range().end(); | ||
15 | let mut buf = String::new(); | ||
16 | buf.push_str("\n\nimpl"); | ||
17 | if let Some(type_params) = type_params { | ||
18 | type_params.syntax().text().push_to(&mut buf); | ||
19 | } | ||
20 | buf.push_str(" "); | ||
21 | buf.push_str(name.text().as_str()); | ||
22 | if let Some(type_params) = type_params { | ||
23 | let lifetime_params = type_params | ||
24 | .lifetime_params() | ||
25 | .filter_map(|it| it.lifetime()) | ||
26 | .map(|it| it.text()); | ||
27 | let type_params = type_params | ||
28 | .type_params() | ||
29 | .filter_map(|it| it.name()) | ||
30 | .map(|it| it.text()); | ||
31 | join(lifetime_params.chain(type_params)) | ||
32 | .surround_with("<", ">") | ||
33 | .to_buf(&mut buf); | ||
34 | } | ||
35 | buf.push_str(" {\n"); | ||
36 | edit.set_cursor(start_offset + TextUnit::of_str(&buf)); | ||
37 | buf.push_str("\n}"); | ||
38 | edit.insert(start_offset, buf); | ||
39 | }) | ||
40 | } | ||
41 | |||
42 | #[cfg(test)] | ||
43 | mod tests { | ||
44 | use super::*; | ||
45 | use crate::assists::check_assist; | ||
46 | |||
47 | #[test] | ||
48 | fn test_add_impl() { | ||
49 | check_assist( | ||
50 | add_impl, | ||
51 | "struct Foo {<|>}\n", | ||
52 | "struct Foo {}\n\nimpl Foo {\n<|>\n}\n", | ||
53 | ); | ||
54 | check_assist( | ||
55 | add_impl, | ||
56 | "struct Foo<T: Clone> {<|>}", | ||
57 | "struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n<|>\n}", | ||
58 | ); | ||
59 | check_assist( | ||
60 | add_impl, | ||
61 | "struct Foo<'a, T: Foo<'a>> {<|>}", | ||
62 | "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n<|>\n}", | ||
63 | ); | ||
64 | } | ||
65 | |||
66 | } | ||
diff --git a/crates/ra_editor/src/assists/change_visibility.rs b/crates/ra_editor/src/assists/change_visibility.rs new file mode 100644 index 000000000..6c8466394 --- /dev/null +++ b/crates/ra_editor/src/assists/change_visibility.rs | |||
@@ -0,0 +1,116 @@ | |||
1 | use ra_syntax::{ | ||
2 | AstNode, | ||
3 | ast::{self, VisibilityOwner, NameOwner}, | ||
4 | SyntaxKind::{VISIBILITY, FN_KW, MOD_KW, STRUCT_KW, ENUM_KW, TRAIT_KW, FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF, IDENT}, | ||
5 | }; | ||
6 | |||
7 | use crate::assists::{AssistCtx, Assist}; | ||
8 | |||
9 | pub fn change_visibility(ctx: AssistCtx) -> Option<Assist> { | ||
10 | if let Some(vis) = ctx.node_at_offset::<ast::Visibility>() { | ||
11 | return change_vis(ctx, vis); | ||
12 | } | ||
13 | add_vis(ctx) | ||
14 | } | ||
15 | |||
16 | fn add_vis(ctx: AssistCtx) -> Option<Assist> { | ||
17 | let item_keyword = ctx.leaf_at_offset().find(|leaf| match leaf.kind() { | ||
18 | FN_KW | MOD_KW | STRUCT_KW | ENUM_KW | TRAIT_KW => true, | ||
19 | _ => false, | ||
20 | }); | ||
21 | |||
22 | let offset = if let Some(keyword) = item_keyword { | ||
23 | let parent = keyword.parent()?; | ||
24 | let def_kws = vec![FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF]; | ||
25 | // Parent is not a definition, can't add visibility | ||
26 | if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) { | ||
27 | return None; | ||
28 | } | ||
29 | // Already have visibility, do nothing | ||
30 | if parent.children().any(|child| child.kind() == VISIBILITY) { | ||
31 | return None; | ||
32 | } | ||
33 | parent.range().start() | ||
34 | } else { | ||
35 | let ident = ctx.leaf_at_offset().find(|leaf| leaf.kind() == IDENT)?; | ||
36 | let field = ident.ancestors().find_map(ast::NamedFieldDef::cast)?; | ||
37 | if field.name()?.syntax().range() != ident.range() && field.visibility().is_some() { | ||
38 | return None; | ||
39 | } | ||
40 | field.syntax().range().start() | ||
41 | }; | ||
42 | |||
43 | ctx.build("make pub(crate)", |edit| { | ||
44 | edit.insert(offset, "pub(crate) "); | ||
45 | edit.set_cursor(offset); | ||
46 | }) | ||
47 | } | ||
48 | |||
49 | fn change_vis(ctx: AssistCtx, vis: ast::Visibility) -> Option<Assist> { | ||
50 | if vis.syntax().text() != "pub" { | ||
51 | return None; | ||
52 | } | ||
53 | ctx.build("chage to pub(crate)", |edit| { | ||
54 | edit.replace(vis.syntax().range(), "pub(crate)"); | ||
55 | edit.set_cursor(vis.syntax().range().start()); | ||
56 | }) | ||
57 | } | ||
58 | |||
59 | #[cfg(test)] | ||
60 | mod tests { | ||
61 | use super::*; | ||
62 | use crate::assists::check_assist; | ||
63 | |||
64 | #[test] | ||
65 | fn change_visibility_adds_pub_crate_to_items() { | ||
66 | check_assist( | ||
67 | change_visibility, | ||
68 | "<|>fn foo() {}", | ||
69 | "<|>pub(crate) fn foo() {}", | ||
70 | ); | ||
71 | check_assist( | ||
72 | change_visibility, | ||
73 | "f<|>n foo() {}", | ||
74 | "<|>pub(crate) fn foo() {}", | ||
75 | ); | ||
76 | check_assist( | ||
77 | change_visibility, | ||
78 | "<|>struct Foo {}", | ||
79 | "<|>pub(crate) struct Foo {}", | ||
80 | ); | ||
81 | check_assist( | ||
82 | change_visibility, | ||
83 | "<|>mod foo {}", | ||
84 | "<|>pub(crate) mod foo {}", | ||
85 | ); | ||
86 | check_assist( | ||
87 | change_visibility, | ||
88 | "<|>trait Foo {}", | ||
89 | "<|>pub(crate) trait Foo {}", | ||
90 | ); | ||
91 | check_assist(change_visibility, "m<|>od {}", "<|>pub(crate) mod {}"); | ||
92 | check_assist( | ||
93 | change_visibility, | ||
94 | "unsafe f<|>n foo() {}", | ||
95 | "<|>pub(crate) unsafe fn foo() {}", | ||
96 | ); | ||
97 | } | ||
98 | |||
99 | #[test] | ||
100 | fn change_visibility_works_with_struct_fields() { | ||
101 | check_assist( | ||
102 | change_visibility, | ||
103 | "struct S { <|>field: u32 }", | ||
104 | "struct S { <|>pub(crate) field: u32 }", | ||
105 | ) | ||
106 | } | ||
107 | |||
108 | #[test] | ||
109 | fn change_visibility_pub_to_pub_crate() { | ||
110 | check_assist( | ||
111 | change_visibility, | ||
112 | "<|>pub fn foo() {}", | ||
113 | "<|>pub(crate) fn foo() {}", | ||
114 | ) | ||
115 | } | ||
116 | } | ||
diff --git a/crates/ra_editor/src/assists/flip_comma.rs b/crates/ra_editor/src/assists/flip_comma.rs new file mode 100644 index 000000000..a343413cc --- /dev/null +++ b/crates/ra_editor/src/assists/flip_comma.rs | |||
@@ -0,0 +1,31 @@ | |||
1 | use ra_syntax::{ | ||
2 | Direction, | ||
3 | SyntaxKind::COMMA, | ||
4 | }; | ||
5 | |||
6 | use crate::assists::{non_trivia_sibling, AssistCtx, Assist}; | ||
7 | |||
8 | pub fn flip_comma(ctx: AssistCtx) -> Option<Assist> { | ||
9 | let comma = ctx.leaf_at_offset().find(|leaf| leaf.kind() == COMMA)?; | ||
10 | let prev = non_trivia_sibling(comma, Direction::Prev)?; | ||
11 | let next = non_trivia_sibling(comma, Direction::Next)?; | ||
12 | ctx.build("flip comma", |edit| { | ||
13 | edit.replace(prev.range(), next.text()); | ||
14 | edit.replace(next.range(), prev.text()); | ||
15 | }) | ||
16 | } | ||
17 | |||
18 | #[cfg(test)] | ||
19 | mod tests { | ||
20 | use super::*; | ||
21 | use crate::assists::check_assist; | ||
22 | |||
23 | #[test] | ||
24 | fn flip_comma_works_for_function_parameters() { | ||
25 | check_assist( | ||
26 | flip_comma, | ||
27 | "fn foo(x: i32,<|> y: Result<(), ()>) {}", | ||
28 | "fn foo(y: Result<(), ()>,<|> x: i32) {}", | ||
29 | ) | ||
30 | } | ||
31 | } | ||
diff --git a/crates/ra_editor/src/assists/introduce_variable.rs b/crates/ra_editor/src/assists/introduce_variable.rs new file mode 100644 index 000000000..782861023 --- /dev/null +++ b/crates/ra_editor/src/assists/introduce_variable.rs | |||
@@ -0,0 +1,144 @@ | |||
1 | use ra_syntax::{ | ||
2 | ast::{self, AstNode}, | ||
3 | SyntaxKind::WHITESPACE, | ||
4 | SyntaxNodeRef, TextUnit, | ||
5 | }; | ||
6 | |||
7 | use crate::assists::{AssistCtx, Assist}; | ||
8 | |||
9 | pub fn introduce_variable<'a>(ctx: AssistCtx) -> Option<Assist> { | ||
10 | let node = ctx.covering_node(); | ||
11 | let expr = node.ancestors().filter_map(ast::Expr::cast).next()?; | ||
12 | |||
13 | let anchor_stmt = anchor_stmt(expr)?; | ||
14 | let indent = anchor_stmt.prev_sibling()?; | ||
15 | if indent.kind() != WHITESPACE { | ||
16 | return None; | ||
17 | } | ||
18 | ctx.build("introduce variable", move |edit| { | ||
19 | let mut buf = String::new(); | ||
20 | |||
21 | buf.push_str("let var_name = "); | ||
22 | expr.syntax().text().push_to(&mut buf); | ||
23 | let is_full_stmt = if let Some(expr_stmt) = ast::ExprStmt::cast(anchor_stmt) { | ||
24 | Some(expr.syntax()) == expr_stmt.expr().map(|e| e.syntax()) | ||
25 | } else { | ||
26 | false | ||
27 | }; | ||
28 | if is_full_stmt { | ||
29 | edit.replace(expr.syntax().range(), buf); | ||
30 | } else { | ||
31 | buf.push_str(";"); | ||
32 | indent.text().push_to(&mut buf); | ||
33 | edit.replace(expr.syntax().range(), "var_name".to_string()); | ||
34 | edit.insert(anchor_stmt.range().start(), buf); | ||
35 | } | ||
36 | edit.set_cursor(anchor_stmt.range().start() + TextUnit::of_str("let ")); | ||
37 | }) | ||
38 | } | ||
39 | |||
40 | /// Statement or last in the block expression, which will follow | ||
41 | /// the freshly introduced var. | ||
42 | fn anchor_stmt(expr: ast::Expr) -> Option<SyntaxNodeRef> { | ||
43 | expr.syntax().ancestors().find(|&node| { | ||
44 | if ast::Stmt::cast(node).is_some() { | ||
45 | return true; | ||
46 | } | ||
47 | if let Some(expr) = node | ||
48 | .parent() | ||
49 | .and_then(ast::Block::cast) | ||
50 | .and_then(|it| it.expr()) | ||
51 | { | ||
52 | if expr.syntax() == node { | ||
53 | return true; | ||
54 | } | ||
55 | } | ||
56 | false | ||
57 | }) | ||
58 | } | ||
59 | |||
60 | #[cfg(test)] | ||
61 | mod tests { | ||
62 | use super::*; | ||
63 | use crate::assists::check_assist_range; | ||
64 | |||
65 | #[test] | ||
66 | fn test_introduce_var_simple() { | ||
67 | check_assist_range( | ||
68 | introduce_variable, | ||
69 | " | ||
70 | fn foo() { | ||
71 | foo(<|>1 + 1<|>); | ||
72 | }", | ||
73 | " | ||
74 | fn foo() { | ||
75 | let <|>var_name = 1 + 1; | ||
76 | foo(var_name); | ||
77 | }", | ||
78 | ); | ||
79 | } | ||
80 | |||
81 | #[test] | ||
82 | fn test_introduce_var_expr_stmt() { | ||
83 | check_assist_range( | ||
84 | introduce_variable, | ||
85 | " | ||
86 | fn foo() { | ||
87 | <|>1 + 1<|>; | ||
88 | }", | ||
89 | " | ||
90 | fn foo() { | ||
91 | let <|>var_name = 1 + 1; | ||
92 | }", | ||
93 | ); | ||
94 | } | ||
95 | |||
96 | #[test] | ||
97 | fn test_introduce_var_part_of_expr_stmt() { | ||
98 | check_assist_range( | ||
99 | introduce_variable, | ||
100 | " | ||
101 | fn foo() { | ||
102 | <|>1<|> + 1; | ||
103 | }", | ||
104 | " | ||
105 | fn foo() { | ||
106 | let <|>var_name = 1; | ||
107 | var_name + 1; | ||
108 | }", | ||
109 | ); | ||
110 | } | ||
111 | |||
112 | #[test] | ||
113 | fn test_introduce_var_last_expr() { | ||
114 | check_assist_range( | ||
115 | introduce_variable, | ||
116 | " | ||
117 | fn foo() { | ||
118 | bar(<|>1 + 1<|>) | ||
119 | }", | ||
120 | " | ||
121 | fn foo() { | ||
122 | let <|>var_name = 1 + 1; | ||
123 | bar(var_name) | ||
124 | }", | ||
125 | ); | ||
126 | } | ||
127 | |||
128 | #[test] | ||
129 | fn test_introduce_var_last_full_expr() { | ||
130 | check_assist_range( | ||
131 | introduce_variable, | ||
132 | " | ||
133 | fn foo() { | ||
134 | <|>bar(1 + 1)<|> | ||
135 | }", | ||
136 | " | ||
137 | fn foo() { | ||
138 | let <|>var_name = bar(1 + 1); | ||
139 | var_name | ||
140 | }", | ||
141 | ); | ||
142 | } | ||
143 | |||
144 | } | ||
diff --git a/crates/ra_editor/src/assists/split_import.rs b/crates/ra_editor/src/assists/split_import.rs new file mode 100644 index 000000000..75f9e8dfb --- /dev/null +++ b/crates/ra_editor/src/assists/split_import.rs | |||
@@ -0,0 +1,44 @@ | |||
1 | use ra_syntax::{ | ||
2 | TextUnit, AstNode, SyntaxKind::COLONCOLON, | ||
3 | ast, | ||
4 | algo::generate, | ||
5 | }; | ||
6 | |||
7 | use crate::assists::{AssistCtx, Assist}; | ||
8 | |||
9 | pub fn split_import(ctx: AssistCtx) -> Option<Assist> { | ||
10 | let colon_colon = ctx | ||
11 | .leaf_at_offset() | ||
12 | .find(|leaf| leaf.kind() == COLONCOLON)?; | ||
13 | let path = colon_colon.parent().and_then(ast::Path::cast)?; | ||
14 | let top_path = generate(Some(path), |it| it.parent_path()).last()?; | ||
15 | |||
16 | let use_tree = top_path.syntax().ancestors().find_map(ast::UseTree::cast); | ||
17 | if use_tree.is_none() { | ||
18 | return None; | ||
19 | } | ||
20 | |||
21 | let l_curly = colon_colon.range().end(); | ||
22 | let r_curly = top_path.syntax().range().end(); | ||
23 | |||
24 | ctx.build("split import", |edit| { | ||
25 | edit.insert(l_curly, "{"); | ||
26 | edit.insert(r_curly, "}"); | ||
27 | edit.set_cursor(l_curly + TextUnit::of_str("{")); | ||
28 | }) | ||
29 | } | ||
30 | |||
31 | #[cfg(test)] | ||
32 | mod tests { | ||
33 | use super::*; | ||
34 | use crate::assists::check_assist; | ||
35 | |||
36 | #[test] | ||
37 | fn test_split_import() { | ||
38 | check_assist( | ||
39 | split_import, | ||
40 | "use crate::<|>db::RootDatabase;", | ||
41 | "use crate::{<|>db::RootDatabase};", | ||
42 | ) | ||
43 | } | ||
44 | } | ||
diff --git a/crates/ra_editor/src/code_actions.rs b/crates/ra_editor/src/code_actions.rs deleted file mode 100644 index 7615f37a6..000000000 --- a/crates/ra_editor/src/code_actions.rs +++ /dev/null | |||
@@ -1,415 +0,0 @@ | |||
1 | use join_to_string::join; | ||
2 | |||
3 | use ra_syntax::{ | ||
4 | algo::{find_covering_node, find_leaf_at_offset}, | ||
5 | ast::{self, AstNode, AttrsOwner, NameOwner, TypeParamsOwner}, | ||
6 | Direction, SourceFileNode, | ||
7 | SyntaxKind::{COMMA, WHITESPACE, COMMENT, VISIBILITY, FN_KW, MOD_KW, STRUCT_KW, ENUM_KW, TRAIT_KW, FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF}, | ||
8 | SyntaxNodeRef, TextRange, TextUnit, | ||
9 | }; | ||
10 | |||
11 | use crate::{find_node_at_offset, TextEdit, TextEditBuilder}; | ||
12 | |||
13 | #[derive(Debug)] | ||
14 | pub struct LocalEdit { | ||
15 | pub label: String, | ||
16 | pub edit: TextEdit, | ||
17 | pub cursor_position: Option<TextUnit>, | ||
18 | } | ||
19 | |||
20 | pub fn flip_comma<'a>( | ||
21 | file: &'a SourceFileNode, | ||
22 | offset: TextUnit, | ||
23 | ) -> Option<impl FnOnce() -> LocalEdit + 'a> { | ||
24 | let syntax = file.syntax(); | ||
25 | |||
26 | let comma = find_leaf_at_offset(syntax, offset).find(|leaf| leaf.kind() == COMMA)?; | ||
27 | let prev = non_trivia_sibling(comma, Direction::Prev)?; | ||
28 | let next = non_trivia_sibling(comma, Direction::Next)?; | ||
29 | Some(move || { | ||
30 | let mut edit = TextEditBuilder::new(); | ||
31 | edit.replace(prev.range(), next.text().to_string()); | ||
32 | edit.replace(next.range(), prev.text().to_string()); | ||
33 | LocalEdit { | ||
34 | label: "flip comma".to_string(), | ||
35 | edit: edit.finish(), | ||
36 | cursor_position: None, | ||
37 | } | ||
38 | }) | ||
39 | } | ||
40 | |||
41 | pub fn add_derive<'a>( | ||
42 | file: &'a SourceFileNode, | ||
43 | offset: TextUnit, | ||
44 | ) -> Option<impl FnOnce() -> LocalEdit + 'a> { | ||
45 | let nominal = find_node_at_offset::<ast::NominalDef>(file.syntax(), offset)?; | ||
46 | let node_start = derive_insertion_offset(nominal)?; | ||
47 | return Some(move || { | ||
48 | let derive_attr = nominal | ||
49 | .attrs() | ||
50 | .filter_map(|x| x.as_call()) | ||
51 | .filter(|(name, _arg)| name == "derive") | ||
52 | .map(|(_name, arg)| arg) | ||
53 | .next(); | ||
54 | let mut edit = TextEditBuilder::new(); | ||
55 | let offset = match derive_attr { | ||
56 | None => { | ||
57 | edit.insert(node_start, "#[derive()]\n".to_string()); | ||
58 | node_start + TextUnit::of_str("#[derive(") | ||
59 | } | ||
60 | Some(tt) => tt.syntax().range().end() - TextUnit::of_char(')'), | ||
61 | }; | ||
62 | LocalEdit { | ||
63 | label: "add `#[derive]`".to_string(), | ||
64 | edit: edit.finish(), | ||
65 | cursor_position: Some(offset), | ||
66 | } | ||
67 | }); | ||
68 | |||
69 | // Insert `derive` after doc comments. | ||
70 | fn derive_insertion_offset(nominal: ast::NominalDef) -> Option<TextUnit> { | ||
71 | let non_ws_child = nominal | ||
72 | .syntax() | ||
73 | .children() | ||
74 | .find(|it| it.kind() != COMMENT && it.kind() != WHITESPACE)?; | ||
75 | Some(non_ws_child.range().start()) | ||
76 | } | ||
77 | } | ||
78 | |||
79 | pub fn add_impl<'a>( | ||
80 | file: &'a SourceFileNode, | ||
81 | offset: TextUnit, | ||
82 | ) -> Option<impl FnOnce() -> LocalEdit + 'a> { | ||
83 | let nominal = find_node_at_offset::<ast::NominalDef>(file.syntax(), offset)?; | ||
84 | let name = nominal.name()?; | ||
85 | |||
86 | Some(move || { | ||
87 | let type_params = nominal.type_param_list(); | ||
88 | let mut edit = TextEditBuilder::new(); | ||
89 | let start_offset = nominal.syntax().range().end(); | ||
90 | let mut buf = String::new(); | ||
91 | buf.push_str("\n\nimpl"); | ||
92 | if let Some(type_params) = type_params { | ||
93 | type_params.syntax().text().push_to(&mut buf); | ||
94 | } | ||
95 | buf.push_str(" "); | ||
96 | buf.push_str(name.text().as_str()); | ||
97 | if let Some(type_params) = type_params { | ||
98 | let lifetime_params = type_params | ||
99 | .lifetime_params() | ||
100 | .filter_map(|it| it.lifetime()) | ||
101 | .map(|it| it.text()); | ||
102 | let type_params = type_params | ||
103 | .type_params() | ||
104 | .filter_map(|it| it.name()) | ||
105 | .map(|it| it.text()); | ||
106 | join(lifetime_params.chain(type_params)) | ||
107 | .surround_with("<", ">") | ||
108 | .to_buf(&mut buf); | ||
109 | } | ||
110 | buf.push_str(" {\n"); | ||
111 | let offset = start_offset + TextUnit::of_str(&buf); | ||
112 | buf.push_str("\n}"); | ||
113 | edit.insert(start_offset, buf); | ||
114 | LocalEdit { | ||
115 | label: "add impl".to_string(), | ||
116 | edit: edit.finish(), | ||
117 | cursor_position: Some(offset), | ||
118 | } | ||
119 | }) | ||
120 | } | ||
121 | |||
122 | pub fn introduce_variable<'a>( | ||
123 | file: &'a SourceFileNode, | ||
124 | range: TextRange, | ||
125 | ) -> Option<impl FnOnce() -> LocalEdit + 'a> { | ||
126 | let node = find_covering_node(file.syntax(), range); | ||
127 | let expr = node.ancestors().filter_map(ast::Expr::cast).next()?; | ||
128 | |||
129 | let anchor_stmt = anchor_stmt(expr)?; | ||
130 | let indent = anchor_stmt.prev_sibling()?; | ||
131 | if indent.kind() != WHITESPACE { | ||
132 | return None; | ||
133 | } | ||
134 | return Some(move || { | ||
135 | let mut buf = String::new(); | ||
136 | let mut edit = TextEditBuilder::new(); | ||
137 | |||
138 | buf.push_str("let var_name = "); | ||
139 | expr.syntax().text().push_to(&mut buf); | ||
140 | let is_full_stmt = if let Some(expr_stmt) = ast::ExprStmt::cast(anchor_stmt) { | ||
141 | Some(expr.syntax()) == expr_stmt.expr().map(|e| e.syntax()) | ||
142 | } else { | ||
143 | false | ||
144 | }; | ||
145 | if is_full_stmt { | ||
146 | edit.replace(expr.syntax().range(), buf); | ||
147 | } else { | ||
148 | buf.push_str(";"); | ||
149 | indent.text().push_to(&mut buf); | ||
150 | edit.replace(expr.syntax().range(), "var_name".to_string()); | ||
151 | edit.insert(anchor_stmt.range().start(), buf); | ||
152 | } | ||
153 | let cursor_position = anchor_stmt.range().start() + TextUnit::of_str("let "); | ||
154 | LocalEdit { | ||
155 | label: "introduce variable".to_string(), | ||
156 | edit: edit.finish(), | ||
157 | cursor_position: Some(cursor_position), | ||
158 | } | ||
159 | }); | ||
160 | |||
161 | /// Statement or last in the block expression, which will follow | ||
162 | /// the freshly introduced var. | ||
163 | fn anchor_stmt(expr: ast::Expr) -> Option<SyntaxNodeRef> { | ||
164 | expr.syntax().ancestors().find(|&node| { | ||
165 | if ast::Stmt::cast(node).is_some() { | ||
166 | return true; | ||
167 | } | ||
168 | if let Some(expr) = node | ||
169 | .parent() | ||
170 | .and_then(ast::Block::cast) | ||
171 | .and_then(|it| it.expr()) | ||
172 | { | ||
173 | if expr.syntax() == node { | ||
174 | return true; | ||
175 | } | ||
176 | } | ||
177 | false | ||
178 | }) | ||
179 | } | ||
180 | } | ||
181 | |||
182 | pub fn make_pub_crate<'a>( | ||
183 | file: &'a SourceFileNode, | ||
184 | offset: TextUnit, | ||
185 | ) -> Option<impl FnOnce() -> LocalEdit + 'a> { | ||
186 | let syntax = file.syntax(); | ||
187 | |||
188 | let keyword = find_leaf_at_offset(syntax, offset).find(|leaf| match leaf.kind() { | ||
189 | FN_KW | MOD_KW | STRUCT_KW | ENUM_KW | TRAIT_KW => true, | ||
190 | _ => false, | ||
191 | })?; | ||
192 | let parent = keyword.parent()?; | ||
193 | let def_kws = vec![FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF]; | ||
194 | let node_start = parent.range().start(); | ||
195 | Some(move || { | ||
196 | let mut edit = TextEditBuilder::new(); | ||
197 | |||
198 | if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) | ||
199 | || parent.children().any(|child| child.kind() == VISIBILITY) | ||
200 | { | ||
201 | return LocalEdit { | ||
202 | label: "make pub crate".to_string(), | ||
203 | edit: edit.finish(), | ||
204 | cursor_position: Some(offset), | ||
205 | }; | ||
206 | } | ||
207 | |||
208 | edit.insert(node_start, "pub(crate) ".to_string()); | ||
209 | LocalEdit { | ||
210 | label: "make pub crate".to_string(), | ||
211 | edit: edit.finish(), | ||
212 | cursor_position: Some(node_start), | ||
213 | } | ||
214 | }) | ||
215 | } | ||
216 | |||
217 | fn non_trivia_sibling(node: SyntaxNodeRef, direction: Direction) -> Option<SyntaxNodeRef> { | ||
218 | node.siblings(direction) | ||
219 | .skip(1) | ||
220 | .find(|node| !node.kind().is_trivia()) | ||
221 | } | ||
222 | |||
223 | #[cfg(test)] | ||
224 | mod tests { | ||
225 | use super::*; | ||
226 | use crate::test_utils::{check_action, check_action_range}; | ||
227 | |||
228 | #[test] | ||
229 | fn test_swap_comma() { | ||
230 | check_action( | ||
231 | "fn foo(x: i32,<|> y: Result<(), ()>) {}", | ||
232 | "fn foo(y: Result<(), ()>,<|> x: i32) {}", | ||
233 | |file, off| flip_comma(file, off).map(|f| f()), | ||
234 | ) | ||
235 | } | ||
236 | |||
237 | #[test] | ||
238 | fn add_derive_new() { | ||
239 | check_action( | ||
240 | "struct Foo { a: i32, <|>}", | ||
241 | "#[derive(<|>)]\nstruct Foo { a: i32, }", | ||
242 | |file, off| add_derive(file, off).map(|f| f()), | ||
243 | ); | ||
244 | check_action( | ||
245 | "struct Foo { <|> a: i32, }", | ||
246 | "#[derive(<|>)]\nstruct Foo { a: i32, }", | ||
247 | |file, off| add_derive(file, off).map(|f| f()), | ||
248 | ); | ||
249 | } | ||
250 | |||
251 | #[test] | ||
252 | fn add_derive_existing() { | ||
253 | check_action( | ||
254 | "#[derive(Clone)]\nstruct Foo { a: i32<|>, }", | ||
255 | "#[derive(Clone<|>)]\nstruct Foo { a: i32, }", | ||
256 | |file, off| add_derive(file, off).map(|f| f()), | ||
257 | ); | ||
258 | } | ||
259 | |||
260 | #[test] | ||
261 | fn add_derive_new_with_doc_comment() { | ||
262 | check_action( | ||
263 | " | ||
264 | /// `Foo` is a pretty important struct. | ||
265 | /// It does stuff. | ||
266 | struct Foo { a: i32<|>, } | ||
267 | ", | ||
268 | " | ||
269 | /// `Foo` is a pretty important struct. | ||
270 | /// It does stuff. | ||
271 | #[derive(<|>)] | ||
272 | struct Foo { a: i32, } | ||
273 | ", | ||
274 | |file, off| add_derive(file, off).map(|f| f()), | ||
275 | ); | ||
276 | } | ||
277 | |||
278 | #[test] | ||
279 | fn test_add_impl() { | ||
280 | check_action( | ||
281 | "struct Foo {<|>}\n", | ||
282 | "struct Foo {}\n\nimpl Foo {\n<|>\n}\n", | ||
283 | |file, off| add_impl(file, off).map(|f| f()), | ||
284 | ); | ||
285 | check_action( | ||
286 | "struct Foo<T: Clone> {<|>}", | ||
287 | "struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n<|>\n}", | ||
288 | |file, off| add_impl(file, off).map(|f| f()), | ||
289 | ); | ||
290 | check_action( | ||
291 | "struct Foo<'a, T: Foo<'a>> {<|>}", | ||
292 | "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n<|>\n}", | ||
293 | |file, off| add_impl(file, off).map(|f| f()), | ||
294 | ); | ||
295 | } | ||
296 | |||
297 | #[test] | ||
298 | fn test_introduce_var_simple() { | ||
299 | check_action_range( | ||
300 | " | ||
301 | fn foo() { | ||
302 | foo(<|>1 + 1<|>); | ||
303 | }", | ||
304 | " | ||
305 | fn foo() { | ||
306 | let <|>var_name = 1 + 1; | ||
307 | foo(var_name); | ||
308 | }", | ||
309 | |file, range| introduce_variable(file, range).map(|f| f()), | ||
310 | ); | ||
311 | } | ||
312 | |||
313 | #[test] | ||
314 | fn test_introduce_var_expr_stmt() { | ||
315 | check_action_range( | ||
316 | " | ||
317 | fn foo() { | ||
318 | <|>1 + 1<|>; | ||
319 | }", | ||
320 | " | ||
321 | fn foo() { | ||
322 | let <|>var_name = 1 + 1; | ||
323 | }", | ||
324 | |file, range| introduce_variable(file, range).map(|f| f()), | ||
325 | ); | ||
326 | } | ||
327 | |||
328 | #[test] | ||
329 | fn test_introduce_var_part_of_expr_stmt() { | ||
330 | check_action_range( | ||
331 | " | ||
332 | fn foo() { | ||
333 | <|>1<|> + 1; | ||
334 | }", | ||
335 | " | ||
336 | fn foo() { | ||
337 | let <|>var_name = 1; | ||
338 | var_name + 1; | ||
339 | }", | ||
340 | |file, range| introduce_variable(file, range).map(|f| f()), | ||
341 | ); | ||
342 | } | ||
343 | |||
344 | #[test] | ||
345 | fn test_introduce_var_last_expr() { | ||
346 | check_action_range( | ||
347 | " | ||
348 | fn foo() { | ||
349 | bar(<|>1 + 1<|>) | ||
350 | }", | ||
351 | " | ||
352 | fn foo() { | ||
353 | let <|>var_name = 1 + 1; | ||
354 | bar(var_name) | ||
355 | }", | ||
356 | |file, range| introduce_variable(file, range).map(|f| f()), | ||
357 | ); | ||
358 | } | ||
359 | |||
360 | #[test] | ||
361 | fn test_introduce_var_last_full_expr() { | ||
362 | check_action_range( | ||
363 | " | ||
364 | fn foo() { | ||
365 | <|>bar(1 + 1)<|> | ||
366 | }", | ||
367 | " | ||
368 | fn foo() { | ||
369 | let <|>var_name = bar(1 + 1); | ||
370 | var_name | ||
371 | }", | ||
372 | |file, range| introduce_variable(file, range).map(|f| f()), | ||
373 | ); | ||
374 | } | ||
375 | |||
376 | #[test] | ||
377 | fn test_make_pub_crate() { | ||
378 | check_action( | ||
379 | "<|>fn foo() {}", | ||
380 | "<|>pub(crate) fn foo() {}", | ||
381 | |file, off| make_pub_crate(file, off).map(|f| f()), | ||
382 | ); | ||
383 | check_action( | ||
384 | "f<|>n foo() {}", | ||
385 | "<|>pub(crate) fn foo() {}", | ||
386 | |file, off| make_pub_crate(file, off).map(|f| f()), | ||
387 | ); | ||
388 | check_action( | ||
389 | "<|>struct Foo {}", | ||
390 | "<|>pub(crate) struct Foo {}", | ||
391 | |file, off| make_pub_crate(file, off).map(|f| f()), | ||
392 | ); | ||
393 | check_action("<|>mod foo {}", "<|>pub(crate) mod foo {}", |file, off| { | ||
394 | make_pub_crate(file, off).map(|f| f()) | ||
395 | }); | ||
396 | check_action( | ||
397 | "<|>trait Foo {}", | ||
398 | "<|>pub(crate) trait Foo {}", | ||
399 | |file, off| make_pub_crate(file, off).map(|f| f()), | ||
400 | ); | ||
401 | check_action("m<|>od {}", "<|>pub(crate) mod {}", |file, off| { | ||
402 | make_pub_crate(file, off).map(|f| f()) | ||
403 | }); | ||
404 | check_action( | ||
405 | "pub(crate) f<|>n foo() {}", | ||
406 | "pub(crate) f<|>n foo() {}", | ||
407 | |file, off| make_pub_crate(file, off).map(|f| f()), | ||
408 | ); | ||
409 | check_action( | ||
410 | "unsafe f<|>n foo() {}", | ||
411 | "<|>pub(crate) unsafe fn foo() {}", | ||
412 | |file, off| make_pub_crate(file, off).map(|f| f()), | ||
413 | ); | ||
414 | } | ||
415 | } | ||
diff --git a/crates/ra_editor/src/diagnostics.rs b/crates/ra_editor/src/diagnostics.rs index 1b336cfe2..199b0e502 100644 --- a/crates/ra_editor/src/diagnostics.rs +++ b/crates/ra_editor/src/diagnostics.rs | |||
@@ -57,7 +57,7 @@ fn check_unnecessary_braces_in_use_statement( | |||
57 | text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(single_use_tree) | 57 | text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(single_use_tree) |
58 | .unwrap_or_else(|| { | 58 | .unwrap_or_else(|| { |
59 | let to_replace = single_use_tree.syntax().text().to_string(); | 59 | let to_replace = single_use_tree.syntax().text().to_string(); |
60 | let mut edit_builder = TextEditBuilder::new(); | 60 | let mut edit_builder = TextEditBuilder::default(); |
61 | edit_builder.delete(range); | 61 | edit_builder.delete(range); |
62 | edit_builder.insert(range.start(), to_replace); | 62 | edit_builder.insert(range.start(), to_replace); |
63 | edit_builder.finish() | 63 | edit_builder.finish() |
@@ -93,7 +93,7 @@ fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement( | |||
93 | let start = use_tree_list_node.prev_sibling()?.range().start(); | 93 | let start = use_tree_list_node.prev_sibling()?.range().start(); |
94 | let end = use_tree_list_node.range().end(); | 94 | let end = use_tree_list_node.range().end(); |
95 | let range = TextRange::from_to(start, end); | 95 | let range = TextRange::from_to(start, end); |
96 | let mut edit_builder = TextEditBuilder::new(); | 96 | let mut edit_builder = TextEditBuilder::default(); |
97 | edit_builder.delete(range); | 97 | edit_builder.delete(range); |
98 | return Some(edit_builder.finish()); | 98 | return Some(edit_builder.finish()); |
99 | } | 99 | } |
@@ -111,7 +111,7 @@ fn check_struct_shorthand_initialization( | |||
111 | let field_name = name_ref.syntax().text().to_string(); | 111 | let field_name = name_ref.syntax().text().to_string(); |
112 | let field_expr = expr.syntax().text().to_string(); | 112 | let field_expr = expr.syntax().text().to_string(); |
113 | if field_name == field_expr { | 113 | if field_name == field_expr { |
114 | let mut edit_builder = TextEditBuilder::new(); | 114 | let mut edit_builder = TextEditBuilder::default(); |
115 | edit_builder.delete(named_field.syntax().range()); | 115 | edit_builder.delete(named_field.syntax().range()); |
116 | edit_builder.insert(named_field.syntax().range().start(), field_name); | 116 | edit_builder.insert(named_field.syntax().range().start(), field_name); |
117 | let edit = edit_builder.finish(); | 117 | let edit = edit_builder.finish(); |
diff --git a/crates/ra_editor/src/lib.rs b/crates/ra_editor/src/lib.rs index bfc745e58..ac283e2e0 100644 --- a/crates/ra_editor/src/lib.rs +++ b/crates/ra_editor/src/lib.rs | |||
@@ -1,4 +1,4 @@ | |||
1 | mod code_actions; | 1 | pub mod assists; |
2 | mod extend_selection; | 2 | mod extend_selection; |
3 | mod folding_ranges; | 3 | mod folding_ranges; |
4 | mod line_index; | 4 | mod line_index; |
@@ -10,7 +10,7 @@ mod typing; | |||
10 | mod diagnostics; | 10 | mod diagnostics; |
11 | 11 | ||
12 | pub use self::{ | 12 | pub use self::{ |
13 | code_actions::{add_derive, add_impl, flip_comma, introduce_variable, make_pub_crate, LocalEdit}, | 13 | assists::LocalEdit, |
14 | extend_selection::extend_selection, | 14 | extend_selection::extend_selection, |
15 | folding_ranges::{folding_ranges, Fold, FoldKind}, | 15 | folding_ranges::{folding_ranges, Fold, FoldKind}, |
16 | line_index::{LineCol, LineIndex}, | 16 | line_index::{LineCol, LineIndex}, |
@@ -19,7 +19,7 @@ pub use self::{ | |||
19 | typing::{join_lines, on_enter, on_eq_typed}, | 19 | typing::{join_lines, on_enter, on_eq_typed}, |
20 | diagnostics::diagnostics | 20 | diagnostics::diagnostics |
21 | }; | 21 | }; |
22 | use ra_text_edit::{TextEdit, TextEditBuilder}; | 22 | use ra_text_edit::TextEditBuilder; |
23 | use ra_syntax::{ | 23 | use ra_syntax::{ |
24 | algo::find_leaf_at_offset, | 24 | algo::find_leaf_at_offset, |
25 | ast::{self, AstNode}, | 25 | ast::{self, AstNode}, |
diff --git a/crates/ra_editor/src/structure.rs b/crates/ra_editor/src/structure.rs index 2292b1ddf..32d59e335 100644 --- a/crates/ra_editor/src/structure.rs +++ b/crates/ra_editor/src/structure.rs | |||
@@ -60,7 +60,7 @@ fn structure_node(node: SyntaxNodeRef) -> Option<StructureNode> { | |||
60 | .visit(decl::<ast::TypeDef>) | 60 | .visit(decl::<ast::TypeDef>) |
61 | .visit(decl::<ast::ConstDef>) | 61 | .visit(decl::<ast::ConstDef>) |
62 | .visit(decl::<ast::StaticDef>) | 62 | .visit(decl::<ast::StaticDef>) |
63 | .visit(|im: ast::ImplItem| { | 63 | .visit(|im: ast::ImplBlock| { |
64 | let target_type = im.target_type()?; | 64 | let target_type = im.target_type()?; |
65 | let target_trait = im.target_trait(); | 65 | let target_trait = im.target_trait(); |
66 | let label = match target_trait { | 66 | let label = match target_trait { |
@@ -121,8 +121,8 @@ impl fmt::Debug for E {} | |||
121 | StructureNode { parent: None, label: "T", navigation_range: [81; 82), node_range: [76; 88), kind: TYPE_DEF }, | 121 | StructureNode { parent: None, label: "T", navigation_range: [81; 82), node_range: [76; 88), kind: TYPE_DEF }, |
122 | StructureNode { parent: None, label: "S", navigation_range: [96; 97), node_range: [89; 108), kind: STATIC_DEF }, | 122 | StructureNode { parent: None, label: "S", navigation_range: [96; 97), node_range: [89; 108), kind: STATIC_DEF }, |
123 | StructureNode { parent: None, label: "C", navigation_range: [115; 116), node_range: [109; 127), kind: CONST_DEF }, | 123 | StructureNode { parent: None, label: "C", navigation_range: [115; 116), node_range: [109; 127), kind: CONST_DEF }, |
124 | StructureNode { parent: None, label: "impl E", navigation_range: [134; 135), node_range: [129; 138), kind: IMPL_ITEM }, | 124 | StructureNode { parent: None, label: "impl E", navigation_range: [134; 135), node_range: [129; 138), kind: IMPL_BLOCK }, |
125 | StructureNode { parent: None, label: "impl fmt::Debug for E", navigation_range: [160; 161), node_range: [140; 164), kind: IMPL_ITEM }]"#, | 125 | StructureNode { parent: None, label: "impl fmt::Debug for E", navigation_range: [160; 161), node_range: [140; 164), kind: IMPL_BLOCK }]"#, |
126 | &structure, | 126 | &structure, |
127 | ) | 127 | ) |
128 | } | 128 | } |
diff --git a/crates/ra_editor/src/typing.rs b/crates/ra_editor/src/typing.rs index 21d068a7b..1b568e96c 100644 --- a/crates/ra_editor/src/typing.rs +++ b/crates/ra_editor/src/typing.rs | |||
@@ -21,7 +21,7 @@ pub fn join_lines(file: &SourceFileNode, range: TextRange) -> LocalEdit { | |||
21 | None => { | 21 | None => { |
22 | return LocalEdit { | 22 | return LocalEdit { |
23 | label: "join lines".to_string(), | 23 | label: "join lines".to_string(), |
24 | edit: TextEditBuilder::new().finish(), | 24 | edit: TextEditBuilder::default().finish(), |
25 | cursor_position: None, | 25 | cursor_position: None, |
26 | }; | 26 | }; |
27 | } | 27 | } |
@@ -33,7 +33,7 @@ pub fn join_lines(file: &SourceFileNode, range: TextRange) -> LocalEdit { | |||
33 | }; | 33 | }; |
34 | 34 | ||
35 | let node = find_covering_node(file.syntax(), range); | 35 | let node = find_covering_node(file.syntax(), range); |
36 | let mut edit = TextEditBuilder::new(); | 36 | let mut edit = TextEditBuilder::default(); |
37 | for node in node.descendants() { | 37 | for node in node.descendants() { |
38 | let text = match node.leaf_text() { | 38 | let text = match node.leaf_text() { |
39 | Some(text) => text, | 39 | Some(text) => text, |
@@ -76,7 +76,7 @@ pub fn on_enter(file: &SourceFileNode, offset: TextUnit) -> Option<LocalEdit> { | |||
76 | let indent = node_indent(file, comment.syntax())?; | 76 | let indent = node_indent(file, comment.syntax())?; |
77 | let inserted = format!("\n{}{} ", indent, prefix); | 77 | let inserted = format!("\n{}{} ", indent, prefix); |
78 | let cursor_position = offset + TextUnit::of_str(&inserted); | 78 | let cursor_position = offset + TextUnit::of_str(&inserted); |
79 | let mut edit = TextEditBuilder::new(); | 79 | let mut edit = TextEditBuilder::default(); |
80 | edit.insert(offset, inserted); | 80 | edit.insert(offset, inserted); |
81 | Some(LocalEdit { | 81 | Some(LocalEdit { |
82 | label: "on enter".to_string(), | 82 | label: "on enter".to_string(), |
@@ -127,7 +127,7 @@ pub fn on_eq_typed(file: &SourceFileNode, offset: TextUnit) -> Option<LocalEdit> | |||
127 | return None; | 127 | return None; |
128 | } | 128 | } |
129 | let offset = let_stmt.syntax().range().end(); | 129 | let offset = let_stmt.syntax().range().end(); |
130 | let mut edit = TextEditBuilder::new(); | 130 | let mut edit = TextEditBuilder::default(); |
131 | edit.insert(offset, ";".to_string()); | 131 | edit.insert(offset, ";".to_string()); |
132 | Some(LocalEdit { | 132 | Some(LocalEdit { |
133 | label: "add semicolon".to_string(), | 133 | label: "add semicolon".to_string(), |
@@ -188,10 +188,14 @@ fn remove_newline( | |||
188 | edit.delete(TextRange::from_to(prev.range().start(), node.range().end())); | 188 | edit.delete(TextRange::from_to(prev.range().start(), node.range().end())); |
189 | } else if prev.kind() == COMMA && next.kind() == R_CURLY { | 189 | } else if prev.kind() == COMMA && next.kind() == R_CURLY { |
190 | // Removes: comma, newline (incl. surrounding whitespace) | 190 | // Removes: comma, newline (incl. surrounding whitespace) |
191 | // Adds: a single whitespace | 191 | let space = if let Some(left) = prev.prev_sibling() { |
192 | compute_ws(left, next) | ||
193 | } else { | ||
194 | " " | ||
195 | }; | ||
192 | edit.replace( | 196 | edit.replace( |
193 | TextRange::from_to(prev.range().start(), node.range().end()), | 197 | TextRange::from_to(prev.range().start(), node.range().end()), |
194 | " ".to_string(), | 198 | space.to_string(), |
195 | ); | 199 | ); |
196 | } else if let (Some(_), Some(next)) = (ast::Comment::cast(prev), ast::Comment::cast(next)) { | 200 | } else if let (Some(_), Some(next)) = (ast::Comment::cast(prev), ast::Comment::cast(next)) { |
197 | // Removes: newline (incl. surrounding whitespace), start of the next comment | 201 | // Removes: newline (incl. surrounding whitespace), start of the next comment |
@@ -256,10 +260,20 @@ fn join_single_use_tree(edit: &mut TextEditBuilder, node: SyntaxNodeRef) -> Opti | |||
256 | fn compute_ws(left: SyntaxNodeRef, right: SyntaxNodeRef) -> &'static str { | 260 | fn compute_ws(left: SyntaxNodeRef, right: SyntaxNodeRef) -> &'static str { |
257 | match left.kind() { | 261 | match left.kind() { |
258 | L_PAREN | L_BRACK => return "", | 262 | L_PAREN | L_BRACK => return "", |
263 | L_CURLY => { | ||
264 | if let USE_TREE = right.kind() { | ||
265 | return ""; | ||
266 | } | ||
267 | } | ||
259 | _ => (), | 268 | _ => (), |
260 | } | 269 | } |
261 | match right.kind() { | 270 | match right.kind() { |
262 | R_PAREN | R_BRACK => return "", | 271 | R_PAREN | R_BRACK => return "", |
272 | R_CURLY => { | ||
273 | if let USE_TREE = left.kind() { | ||
274 | return ""; | ||
275 | } | ||
276 | } | ||
263 | DOT => return "", | 277 | DOT => return "", |
264 | _ => (), | 278 | _ => (), |
265 | } | 279 | } |
@@ -331,6 +345,48 @@ fn foo() { | |||
331 | } | 345 | } |
332 | 346 | ||
333 | #[test] | 347 | #[test] |
348 | fn test_join_lines_use_items_left() { | ||
349 | // No space after the '{' | ||
350 | check_join_lines( | ||
351 | r" | ||
352 | <|>use ra_syntax::{ | ||
353 | TextUnit, TextRange, | ||
354 | };", | ||
355 | r" | ||
356 | <|>use ra_syntax::{TextUnit, TextRange, | ||
357 | };", | ||
358 | ); | ||
359 | } | ||
360 | |||
361 | #[test] | ||
362 | fn test_join_lines_use_items_right() { | ||
363 | // No space after the '}' | ||
364 | check_join_lines( | ||
365 | r" | ||
366 | use ra_syntax::{ | ||
367 | <|> TextUnit, TextRange | ||
368 | };", | ||
369 | r" | ||
370 | use ra_syntax::{ | ||
371 | <|> TextUnit, TextRange};", | ||
372 | ); | ||
373 | } | ||
374 | |||
375 | #[test] | ||
376 | fn test_join_lines_use_items_right_comma() { | ||
377 | // No space after the '}' | ||
378 | check_join_lines( | ||
379 | r" | ||
380 | use ra_syntax::{ | ||
381 | <|> TextUnit, TextRange, | ||
382 | };", | ||
383 | r" | ||
384 | use ra_syntax::{ | ||
385 | <|> TextUnit, TextRange};", | ||
386 | ); | ||
387 | } | ||
388 | |||
389 | #[test] | ||
334 | fn test_join_lines_use_tree() { | 390 | fn test_join_lines_use_tree() { |
335 | check_join_lines( | 391 | check_join_lines( |
336 | r" | 392 | r" |