diff options
Diffstat (limited to 'crates/ra_ide_api_light')
-rw-r--r-- | crates/ra_ide_api_light/src/assists.rs | 215 | ||||
-rw-r--r-- | crates/ra_ide_api_light/src/assists/add_derive.rs | 84 | ||||
-rw-r--r-- | crates/ra_ide_api_light/src/assists/add_impl.rs | 66 | ||||
-rw-r--r-- | crates/ra_ide_api_light/src/assists/change_visibility.rs | 165 | ||||
-rw-r--r-- | crates/ra_ide_api_light/src/assists/flip_comma.rs | 31 | ||||
-rw-r--r-- | crates/ra_ide_api_light/src/assists/introduce_variable.rs | 431 | ||||
-rw-r--r-- | crates/ra_ide_api_light/src/assists/replace_if_let_with_match.rs | 81 | ||||
-rw-r--r-- | crates/ra_ide_api_light/src/assists/split_import.rs | 56 | ||||
-rw-r--r-- | crates/ra_ide_api_light/src/formatting.rs | 10 | ||||
-rw-r--r-- | crates/ra_ide_api_light/src/lib.rs | 11 | ||||
-rw-r--r-- | crates/ra_ide_api_light/src/test_utils.rs | 31 |
11 files changed, 17 insertions, 1164 deletions
diff --git a/crates/ra_ide_api_light/src/assists.rs b/crates/ra_ide_api_light/src/assists.rs deleted file mode 100644 index e578805f1..000000000 --- a/crates/ra_ide_api_light/src/assists.rs +++ /dev/null | |||
@@ -1,215 +0,0 @@ | |||
1 | //! This modules contains various "assists": suggestions for source code edits | ||
2 | //! which are likely to occur at a given cursor position. For example, if the | ||
3 | //! cursor is on the `,`, a possible assist is swapping the elements 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 | mod replace_if_let_with_match; | ||
13 | |||
14 | use ra_text_edit::{TextEdit, TextEditBuilder}; | ||
15 | use ra_syntax::{ | ||
16 | Direction, SyntaxNode, TextUnit, TextRange, SourceFile, AstNode, | ||
17 | algo::{find_leaf_at_offset, find_node_at_offset, find_covering_node, LeafAtOffset}, | ||
18 | }; | ||
19 | use itertools::Itertools; | ||
20 | |||
21 | use crate::formatting::leading_indent; | ||
22 | |||
23 | pub use self::{ | ||
24 | flip_comma::flip_comma, | ||
25 | add_derive::add_derive, | ||
26 | add_impl::add_impl, | ||
27 | introduce_variable::introduce_variable, | ||
28 | change_visibility::change_visibility, | ||
29 | split_import::split_import, | ||
30 | replace_if_let_with_match::replace_if_let_with_match, | ||
31 | }; | ||
32 | |||
33 | /// Return all the assists applicable at the given position. | ||
34 | pub fn assists(file: &SourceFile, range: TextRange) -> Vec<LocalEdit> { | ||
35 | let ctx = AssistCtx::new(file, range); | ||
36 | [ | ||
37 | flip_comma, | ||
38 | add_derive, | ||
39 | add_impl, | ||
40 | introduce_variable, | ||
41 | change_visibility, | ||
42 | split_import, | ||
43 | replace_if_let_with_match, | ||
44 | ] | ||
45 | .iter() | ||
46 | .filter_map(|&assist| ctx.clone().apply(assist)) | ||
47 | .collect() | ||
48 | } | ||
49 | |||
50 | #[derive(Debug)] | ||
51 | pub struct LocalEdit { | ||
52 | pub label: String, | ||
53 | pub edit: TextEdit, | ||
54 | pub cursor_position: Option<TextUnit>, | ||
55 | } | ||
56 | |||
57 | fn non_trivia_sibling(node: &SyntaxNode, direction: Direction) -> Option<&SyntaxNode> { | ||
58 | node.siblings(direction) | ||
59 | .skip(1) | ||
60 | .find(|node| !node.kind().is_trivia()) | ||
61 | } | ||
62 | |||
63 | /// `AssistCtx` allows to apply an assist or check if it could be applied. | ||
64 | /// | ||
65 | /// Assists use a somewhat overengineered approach, given the current needs. The | ||
66 | /// assists workflow consists of two phases. In the first phase, a user asks for | ||
67 | /// the list of available assists. In the second phase, the user picks a | ||
68 | /// particular assist and it gets applied. | ||
69 | /// | ||
70 | /// There are two peculiarities here: | ||
71 | /// | ||
72 | /// * first, we ideally avoid computing more things then necessary to answer | ||
73 | /// "is assist applicable" in the first phase. | ||
74 | /// * second, when we are applying assist, we don't have a guarantee that there | ||
75 | /// weren't any changes between the point when user asked for assists and when | ||
76 | /// they applied a particular assist. So, when applying assist, we need to do | ||
77 | /// all the checks from scratch. | ||
78 | /// | ||
79 | /// To avoid repeating the same code twice for both "check" and "apply" | ||
80 | /// functions, we use an approach reminiscent of that of Django's function based | ||
81 | /// views dealing with forms. Each assist receives a runtime parameter, | ||
82 | /// `should_compute_edit`. It first check if an edit is applicable (potentially | ||
83 | /// computing info required to compute the actual edit). If it is applicable, | ||
84 | /// and `should_compute_edit` is `true`, it then computes the actual edit. | ||
85 | /// | ||
86 | /// So, to implement the original assists workflow, we can first apply each edit | ||
87 | /// with `should_compute_edit = false`, and then applying the selected edit | ||
88 | /// again, with `should_compute_edit = true` this time. | ||
89 | /// | ||
90 | /// Note, however, that we don't actually use such two-phase logic at the | ||
91 | /// moment, because the LSP API is pretty awkward in this place, and it's much | ||
92 | /// easier to just compute the edit eagerly :-) | ||
93 | #[derive(Debug, Clone)] | ||
94 | pub struct AssistCtx<'a> { | ||
95 | source_file: &'a SourceFile, | ||
96 | range: TextRange, | ||
97 | should_compute_edit: bool, | ||
98 | } | ||
99 | |||
100 | #[derive(Debug)] | ||
101 | pub enum Assist { | ||
102 | Applicable, | ||
103 | Edit(LocalEdit), | ||
104 | } | ||
105 | |||
106 | #[derive(Default)] | ||
107 | pub struct AssistBuilder { | ||
108 | edit: TextEditBuilder, | ||
109 | cursor_position: Option<TextUnit>, | ||
110 | } | ||
111 | |||
112 | impl<'a> AssistCtx<'a> { | ||
113 | pub fn new(source_file: &'a SourceFile, range: TextRange) -> AssistCtx { | ||
114 | AssistCtx { | ||
115 | source_file, | ||
116 | range, | ||
117 | should_compute_edit: false, | ||
118 | } | ||
119 | } | ||
120 | |||
121 | pub fn apply(mut self, assist: fn(AssistCtx) -> Option<Assist>) -> Option<LocalEdit> { | ||
122 | self.should_compute_edit = true; | ||
123 | match assist(self) { | ||
124 | None => None, | ||
125 | Some(Assist::Edit(e)) => Some(e), | ||
126 | Some(Assist::Applicable) => unreachable!(), | ||
127 | } | ||
128 | } | ||
129 | |||
130 | pub fn check(mut self, assist: fn(AssistCtx) -> Option<Assist>) -> bool { | ||
131 | self.should_compute_edit = false; | ||
132 | match assist(self) { | ||
133 | None => false, | ||
134 | Some(Assist::Edit(_)) => unreachable!(), | ||
135 | Some(Assist::Applicable) => true, | ||
136 | } | ||
137 | } | ||
138 | |||
139 | fn build(self, label: impl Into<String>, f: impl FnOnce(&mut AssistBuilder)) -> Option<Assist> { | ||
140 | if !self.should_compute_edit { | ||
141 | return Some(Assist::Applicable); | ||
142 | } | ||
143 | let mut edit = AssistBuilder::default(); | ||
144 | f(&mut edit); | ||
145 | Some(edit.build(label)) | ||
146 | } | ||
147 | |||
148 | pub(crate) fn leaf_at_offset(&self) -> LeafAtOffset<&'a SyntaxNode> { | ||
149 | find_leaf_at_offset(self.source_file.syntax(), self.range.start()) | ||
150 | } | ||
151 | pub(crate) fn node_at_offset<N: AstNode>(&self) -> Option<&'a N> { | ||
152 | find_node_at_offset(self.source_file.syntax(), self.range.start()) | ||
153 | } | ||
154 | pub(crate) fn covering_node(&self) -> &'a SyntaxNode { | ||
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 | pub fn replace_node_and_indent(&mut self, node: &SyntaxNode, replace_with: impl Into<String>) { | ||
164 | let mut replace_with = replace_with.into(); | ||
165 | if let Some(indent) = leading_indent(node) { | ||
166 | replace_with = reindent(&replace_with, indent) | ||
167 | } | ||
168 | self.replace(node.range(), replace_with) | ||
169 | } | ||
170 | #[allow(unused)] | ||
171 | fn delete(&mut self, range: TextRange) { | ||
172 | self.edit.delete(range) | ||
173 | } | ||
174 | fn insert(&mut self, offset: TextUnit, text: impl Into<String>) { | ||
175 | self.edit.insert(offset, text.into()) | ||
176 | } | ||
177 | fn set_cursor(&mut self, offset: TextUnit) { | ||
178 | self.cursor_position = Some(offset) | ||
179 | } | ||
180 | pub fn build(self, label: impl Into<String>) -> Assist { | ||
181 | Assist::Edit(LocalEdit { | ||
182 | label: label.into(), | ||
183 | cursor_position: self.cursor_position, | ||
184 | edit: self.edit.finish(), | ||
185 | }) | ||
186 | } | ||
187 | } | ||
188 | |||
189 | fn reindent(text: &str, indent: &str) -> String { | ||
190 | let indent = format!("\n{}", indent); | ||
191 | text.lines().intersperse(&indent).collect() | ||
192 | } | ||
193 | |||
194 | #[cfg(test)] | ||
195 | fn check_assist(assist: fn(AssistCtx) -> Option<Assist>, before: &str, after: &str) { | ||
196 | crate::test_utils::check_action(before, after, |file, off| { | ||
197 | let range = TextRange::offset_len(off, 0.into()); | ||
198 | AssistCtx::new(file, range).apply(assist) | ||
199 | }) | ||
200 | } | ||
201 | |||
202 | #[cfg(test)] | ||
203 | fn check_assist_not_applicable(assist: fn(AssistCtx) -> Option<Assist>, text: &str) { | ||
204 | crate::test_utils::check_action_not_applicable(text, |file, off| { | ||
205 | let range = TextRange::offset_len(off, 0.into()); | ||
206 | AssistCtx::new(file, range).apply(assist) | ||
207 | }) | ||
208 | } | ||
209 | |||
210 | #[cfg(test)] | ||
211 | fn check_assist_range(assist: fn(AssistCtx) -> Option<Assist>, before: &str, after: &str) { | ||
212 | crate::test_utils::check_action_range(before, after, |file, range| { | ||
213 | AssistCtx::new(file, range).apply(assist) | ||
214 | }) | ||
215 | } | ||
diff --git a/crates/ra_ide_api_light/src/assists/add_derive.rs b/crates/ra_ide_api_light/src/assists/add_derive.rs deleted file mode 100644 index 6e964d011..000000000 --- a/crates/ra_ide_api_light/src/assists/add_derive.rs +++ /dev/null | |||
@@ -1,84 +0,0 @@ | |||
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_ide_api_light/src/assists/add_impl.rs b/crates/ra_ide_api_light/src/assists/add_impl.rs deleted file mode 100644 index 2eda7cae2..000000000 --- a/crates/ra_ide_api_light/src/assists/add_impl.rs +++ /dev/null | |||
@@ -1,66 +0,0 @@ | |||
1 | use join_to_string::join; | ||
2 | use ra_syntax::{ | ||
3 | ast::{self, AstNode, AstToken, 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_ide_api_light/src/assists/change_visibility.rs b/crates/ra_ide_api_light/src/assists/change_visibility.rs deleted file mode 100644 index 6e8bc2632..000000000 --- a/crates/ra_ide_api_light/src/assists/change_visibility.rs +++ /dev/null | |||
@@ -1,165 +0,0 @@ | |||
1 | use ra_syntax::{ | ||
2 | AstNode, SyntaxNode, TextUnit, | ||
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, WHITESPACE, COMMENT, ATTR}, | ||
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 | vis_offset(parent) | ||
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 | vis_offset(field.syntax()) | ||
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 vis_offset(node: &SyntaxNode) -> TextUnit { | ||
50 | node.children() | ||
51 | .skip_while(|it| match it.kind() { | ||
52 | WHITESPACE | COMMENT | ATTR => true, | ||
53 | _ => false, | ||
54 | }) | ||
55 | .next() | ||
56 | .map(|it| it.range().start()) | ||
57 | .unwrap_or(node.range().start()) | ||
58 | } | ||
59 | |||
60 | fn change_vis(ctx: AssistCtx, vis: &ast::Visibility) -> Option<Assist> { | ||
61 | if vis.syntax().text() == "pub" { | ||
62 | return ctx.build("chage to pub(crate)", |edit| { | ||
63 | edit.replace(vis.syntax().range(), "pub(crate)"); | ||
64 | edit.set_cursor(vis.syntax().range().start()); | ||
65 | }); | ||
66 | } | ||
67 | if vis.syntax().text() == "pub(crate)" { | ||
68 | return ctx.build("chage to pub", |edit| { | ||
69 | edit.replace(vis.syntax().range(), "pub"); | ||
70 | edit.set_cursor(vis.syntax().range().start()); | ||
71 | }); | ||
72 | } | ||
73 | None | ||
74 | } | ||
75 | |||
76 | #[cfg(test)] | ||
77 | mod tests { | ||
78 | use super::*; | ||
79 | use crate::assists::check_assist; | ||
80 | |||
81 | #[test] | ||
82 | fn change_visibility_adds_pub_crate_to_items() { | ||
83 | check_assist( | ||
84 | change_visibility, | ||
85 | "<|>fn foo() {}", | ||
86 | "<|>pub(crate) fn foo() {}", | ||
87 | ); | ||
88 | check_assist( | ||
89 | change_visibility, | ||
90 | "f<|>n foo() {}", | ||
91 | "<|>pub(crate) fn foo() {}", | ||
92 | ); | ||
93 | check_assist( | ||
94 | change_visibility, | ||
95 | "<|>struct Foo {}", | ||
96 | "<|>pub(crate) struct Foo {}", | ||
97 | ); | ||
98 | check_assist( | ||
99 | change_visibility, | ||
100 | "<|>mod foo {}", | ||
101 | "<|>pub(crate) mod foo {}", | ||
102 | ); | ||
103 | check_assist( | ||
104 | change_visibility, | ||
105 | "<|>trait Foo {}", | ||
106 | "<|>pub(crate) trait Foo {}", | ||
107 | ); | ||
108 | check_assist(change_visibility, "m<|>od {}", "<|>pub(crate) mod {}"); | ||
109 | check_assist( | ||
110 | change_visibility, | ||
111 | "unsafe f<|>n foo() {}", | ||
112 | "<|>pub(crate) unsafe fn foo() {}", | ||
113 | ); | ||
114 | } | ||
115 | |||
116 | #[test] | ||
117 | fn change_visibility_works_with_struct_fields() { | ||
118 | check_assist( | ||
119 | change_visibility, | ||
120 | "struct S { <|>field: u32 }", | ||
121 | "struct S { <|>pub(crate) field: u32 }", | ||
122 | ) | ||
123 | } | ||
124 | |||
125 | #[test] | ||
126 | fn change_visibility_pub_to_pub_crate() { | ||
127 | check_assist( | ||
128 | change_visibility, | ||
129 | "<|>pub fn foo() {}", | ||
130 | "<|>pub(crate) fn foo() {}", | ||
131 | ) | ||
132 | } | ||
133 | |||
134 | #[test] | ||
135 | fn change_visibility_pub_crate_to_pub() { | ||
136 | check_assist( | ||
137 | change_visibility, | ||
138 | "<|>pub(crate) fn foo() {}", | ||
139 | "<|>pub fn foo() {}", | ||
140 | ) | ||
141 | } | ||
142 | |||
143 | #[test] | ||
144 | fn change_visibility_handles_comment_attrs() { | ||
145 | check_assist( | ||
146 | change_visibility, | ||
147 | " | ||
148 | /// docs | ||
149 | |||
150 | // comments | ||
151 | |||
152 | #[derive(Debug)] | ||
153 | <|>struct Foo; | ||
154 | ", | ||
155 | " | ||
156 | /// docs | ||
157 | |||
158 | // comments | ||
159 | |||
160 | #[derive(Debug)] | ||
161 | <|>pub(crate) struct Foo; | ||
162 | ", | ||
163 | ) | ||
164 | } | ||
165 | } | ||
diff --git a/crates/ra_ide_api_light/src/assists/flip_comma.rs b/crates/ra_ide_api_light/src/assists/flip_comma.rs deleted file mode 100644 index a343413cc..000000000 --- a/crates/ra_ide_api_light/src/assists/flip_comma.rs +++ /dev/null | |||
@@ -1,31 +0,0 @@ | |||
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_ide_api_light/src/assists/introduce_variable.rs b/crates/ra_ide_api_light/src/assists/introduce_variable.rs deleted file mode 100644 index ed13bddc4..000000000 --- a/crates/ra_ide_api_light/src/assists/introduce_variable.rs +++ /dev/null | |||
@@ -1,431 +0,0 @@ | |||
1 | use ra_syntax::{ | ||
2 | ast::{self, AstNode}, | ||
3 | SyntaxKind::{ | ||
4 | WHITESPACE, MATCH_ARM, LAMBDA_EXPR, PATH_EXPR, BREAK_EXPR, LOOP_EXPR, RETURN_EXPR, COMMENT | ||
5 | }, SyntaxNode, TextUnit, | ||
6 | }; | ||
7 | |||
8 | use crate::assists::{AssistCtx, Assist}; | ||
9 | |||
10 | pub fn introduce_variable<'a>(ctx: AssistCtx) -> Option<Assist> { | ||
11 | let node = ctx.covering_node(); | ||
12 | if !valid_covering_node(node) { | ||
13 | return None; | ||
14 | } | ||
15 | let expr = node.ancestors().filter_map(valid_target_expr).next()?; | ||
16 | let (anchor_stmt, wrap_in_block) = anchor_stmt(expr)?; | ||
17 | let indent = anchor_stmt.prev_sibling()?; | ||
18 | if indent.kind() != WHITESPACE { | ||
19 | return None; | ||
20 | } | ||
21 | ctx.build("introduce variable", move |edit| { | ||
22 | let mut buf = String::new(); | ||
23 | |||
24 | let cursor_offset = if wrap_in_block { | ||
25 | buf.push_str("{ let var_name = "); | ||
26 | TextUnit::of_str("{ let ") | ||
27 | } else { | ||
28 | buf.push_str("let var_name = "); | ||
29 | TextUnit::of_str("let ") | ||
30 | }; | ||
31 | |||
32 | expr.syntax().text().push_to(&mut buf); | ||
33 | let full_stmt = ast::ExprStmt::cast(anchor_stmt); | ||
34 | let is_full_stmt = if let Some(expr_stmt) = full_stmt { | ||
35 | Some(expr.syntax()) == expr_stmt.expr().map(|e| e.syntax()) | ||
36 | } else { | ||
37 | false | ||
38 | }; | ||
39 | if is_full_stmt { | ||
40 | if !full_stmt.unwrap().has_semi() { | ||
41 | buf.push_str(";"); | ||
42 | } | ||
43 | edit.replace(expr.syntax().range(), buf); | ||
44 | } else { | ||
45 | buf.push_str(";"); | ||
46 | indent.text().push_to(&mut buf); | ||
47 | edit.replace(expr.syntax().range(), "var_name".to_string()); | ||
48 | edit.insert(anchor_stmt.range().start(), buf); | ||
49 | if wrap_in_block { | ||
50 | edit.insert(anchor_stmt.range().end(), " }"); | ||
51 | } | ||
52 | } | ||
53 | edit.set_cursor(anchor_stmt.range().start() + cursor_offset); | ||
54 | }) | ||
55 | } | ||
56 | |||
57 | fn valid_covering_node(node: &SyntaxNode) -> bool { | ||
58 | node.kind() != COMMENT | ||
59 | } | ||
60 | /// Check wether the node is a valid expression which can be extracted to a variable. | ||
61 | /// In general that's true for any expression, but in some cases that would produce invalid code. | ||
62 | fn valid_target_expr(node: &SyntaxNode) -> Option<&ast::Expr> { | ||
63 | return match node.kind() { | ||
64 | PATH_EXPR => None, | ||
65 | BREAK_EXPR => ast::BreakExpr::cast(node).and_then(|e| e.expr()), | ||
66 | RETURN_EXPR => ast::ReturnExpr::cast(node).and_then(|e| e.expr()), | ||
67 | LOOP_EXPR => ast::ReturnExpr::cast(node).and_then(|e| e.expr()), | ||
68 | _ => ast::Expr::cast(node), | ||
69 | }; | ||
70 | } | ||
71 | |||
72 | /// Returns the syntax node which will follow the freshly introduced var | ||
73 | /// and a boolean indicating whether we have to wrap it within a { } block | ||
74 | /// to produce correct code. | ||
75 | /// It can be a statement, the last in a block expression or a wanna be block | ||
76 | /// expression like a lamba or match arm. | ||
77 | fn anchor_stmt(expr: &ast::Expr) -> Option<(&SyntaxNode, bool)> { | ||
78 | expr.syntax().ancestors().find_map(|node| { | ||
79 | if ast::Stmt::cast(node).is_some() { | ||
80 | return Some((node, false)); | ||
81 | } | ||
82 | |||
83 | if let Some(expr) = node | ||
84 | .parent() | ||
85 | .and_then(ast::Block::cast) | ||
86 | .and_then(|it| it.expr()) | ||
87 | { | ||
88 | if expr.syntax() == node { | ||
89 | return Some((node, false)); | ||
90 | } | ||
91 | } | ||
92 | |||
93 | if let Some(parent) = node.parent() { | ||
94 | if parent.kind() == MATCH_ARM || parent.kind() == LAMBDA_EXPR { | ||
95 | return Some((node, true)); | ||
96 | } | ||
97 | } | ||
98 | |||
99 | None | ||
100 | }) | ||
101 | } | ||
102 | |||
103 | #[cfg(test)] | ||
104 | mod tests { | ||
105 | use super::*; | ||
106 | use crate::assists::{ check_assist, check_assist_not_applicable, check_assist_range }; | ||
107 | |||
108 | #[test] | ||
109 | fn test_introduce_var_simple() { | ||
110 | check_assist_range( | ||
111 | introduce_variable, | ||
112 | " | ||
113 | fn foo() { | ||
114 | foo(<|>1 + 1<|>); | ||
115 | }", | ||
116 | " | ||
117 | fn foo() { | ||
118 | let <|>var_name = 1 + 1; | ||
119 | foo(var_name); | ||
120 | }", | ||
121 | ); | ||
122 | } | ||
123 | |||
124 | #[test] | ||
125 | fn test_introduce_var_expr_stmt() { | ||
126 | check_assist_range( | ||
127 | introduce_variable, | ||
128 | " | ||
129 | fn foo() { | ||
130 | <|>1 + 1<|>; | ||
131 | }", | ||
132 | " | ||
133 | fn foo() { | ||
134 | let <|>var_name = 1 + 1; | ||
135 | }", | ||
136 | ); | ||
137 | } | ||
138 | |||
139 | #[test] | ||
140 | fn test_introduce_var_part_of_expr_stmt() { | ||
141 | check_assist_range( | ||
142 | introduce_variable, | ||
143 | " | ||
144 | fn foo() { | ||
145 | <|>1<|> + 1; | ||
146 | }", | ||
147 | " | ||
148 | fn foo() { | ||
149 | let <|>var_name = 1; | ||
150 | var_name + 1; | ||
151 | }", | ||
152 | ); | ||
153 | } | ||
154 | |||
155 | #[test] | ||
156 | fn test_introduce_var_last_expr() { | ||
157 | check_assist_range( | ||
158 | introduce_variable, | ||
159 | " | ||
160 | fn foo() { | ||
161 | bar(<|>1 + 1<|>) | ||
162 | }", | ||
163 | " | ||
164 | fn foo() { | ||
165 | let <|>var_name = 1 + 1; | ||
166 | bar(var_name) | ||
167 | }", | ||
168 | ); | ||
169 | } | ||
170 | |||
171 | #[test] | ||
172 | fn test_introduce_var_last_full_expr() { | ||
173 | check_assist_range( | ||
174 | introduce_variable, | ||
175 | " | ||
176 | fn foo() { | ||
177 | <|>bar(1 + 1)<|> | ||
178 | }", | ||
179 | " | ||
180 | fn foo() { | ||
181 | let <|>var_name = bar(1 + 1); | ||
182 | var_name | ||
183 | }", | ||
184 | ); | ||
185 | } | ||
186 | |||
187 | #[test] | ||
188 | fn test_introduce_var_block_expr_second_to_last() { | ||
189 | check_assist_range( | ||
190 | introduce_variable, | ||
191 | " | ||
192 | fn foo() { | ||
193 | <|>{ let x = 0; x }<|> | ||
194 | something_else(); | ||
195 | }", | ||
196 | " | ||
197 | fn foo() { | ||
198 | let <|>var_name = { let x = 0; x }; | ||
199 | something_else(); | ||
200 | }", | ||
201 | ); | ||
202 | } | ||
203 | |||
204 | #[test] | ||
205 | fn test_introduce_var_in_match_arm_no_block() { | ||
206 | check_assist_range( | ||
207 | introduce_variable, | ||
208 | " | ||
209 | fn main() { | ||
210 | let x = true; | ||
211 | let tuple = match x { | ||
212 | true => (<|>2 + 2<|>, true) | ||
213 | _ => (0, false) | ||
214 | }; | ||
215 | } | ||
216 | ", | ||
217 | " | ||
218 | fn main() { | ||
219 | let x = true; | ||
220 | let tuple = match x { | ||
221 | true => { let <|>var_name = 2 + 2; (var_name, true) } | ||
222 | _ => (0, false) | ||
223 | }; | ||
224 | } | ||
225 | ", | ||
226 | ); | ||
227 | } | ||
228 | |||
229 | #[test] | ||
230 | fn test_introduce_var_in_match_arm_with_block() { | ||
231 | check_assist_range( | ||
232 | introduce_variable, | ||
233 | " | ||
234 | fn main() { | ||
235 | let x = true; | ||
236 | let tuple = match x { | ||
237 | true => { | ||
238 | let y = 1; | ||
239 | (<|>2 + y<|>, true) | ||
240 | } | ||
241 | _ => (0, false) | ||
242 | }; | ||
243 | } | ||
244 | ", | ||
245 | " | ||
246 | fn main() { | ||
247 | let x = true; | ||
248 | let tuple = match x { | ||
249 | true => { | ||
250 | let y = 1; | ||
251 | let <|>var_name = 2 + y; | ||
252 | (var_name, true) | ||
253 | } | ||
254 | _ => (0, false) | ||
255 | }; | ||
256 | } | ||
257 | ", | ||
258 | ); | ||
259 | } | ||
260 | |||
261 | #[test] | ||
262 | fn test_introduce_var_in_closure_no_block() { | ||
263 | check_assist_range( | ||
264 | introduce_variable, | ||
265 | " | ||
266 | fn main() { | ||
267 | let lambda = |x: u32| <|>x * 2<|>; | ||
268 | } | ||
269 | ", | ||
270 | " | ||
271 | fn main() { | ||
272 | let lambda = |x: u32| { let <|>var_name = x * 2; var_name }; | ||
273 | } | ||
274 | ", | ||
275 | ); | ||
276 | } | ||
277 | |||
278 | #[test] | ||
279 | fn test_introduce_var_in_closure_with_block() { | ||
280 | check_assist_range( | ||
281 | introduce_variable, | ||
282 | " | ||
283 | fn main() { | ||
284 | let lambda = |x: u32| { <|>x * 2<|> }; | ||
285 | } | ||
286 | ", | ||
287 | " | ||
288 | fn main() { | ||
289 | let lambda = |x: u32| { let <|>var_name = x * 2; var_name }; | ||
290 | } | ||
291 | ", | ||
292 | ); | ||
293 | } | ||
294 | |||
295 | #[test] | ||
296 | fn test_introduce_var_path_simple() { | ||
297 | check_assist( | ||
298 | introduce_variable, | ||
299 | " | ||
300 | fn main() { | ||
301 | let o = S<|>ome(true); | ||
302 | } | ||
303 | ", | ||
304 | " | ||
305 | fn main() { | ||
306 | let <|>var_name = Some(true); | ||
307 | let o = var_name; | ||
308 | } | ||
309 | ", | ||
310 | ); | ||
311 | } | ||
312 | |||
313 | #[test] | ||
314 | fn test_introduce_var_path_method() { | ||
315 | check_assist( | ||
316 | introduce_variable, | ||
317 | " | ||
318 | fn main() { | ||
319 | let v = b<|>ar.foo(); | ||
320 | } | ||
321 | ", | ||
322 | " | ||
323 | fn main() { | ||
324 | let <|>var_name = bar.foo(); | ||
325 | let v = var_name; | ||
326 | } | ||
327 | ", | ||
328 | ); | ||
329 | } | ||
330 | |||
331 | #[test] | ||
332 | fn test_introduce_var_return() { | ||
333 | check_assist( | ||
334 | introduce_variable, | ||
335 | " | ||
336 | fn foo() -> u32 { | ||
337 | r<|>eturn 2 + 2; | ||
338 | } | ||
339 | ", | ||
340 | " | ||
341 | fn foo() -> u32 { | ||
342 | let <|>var_name = 2 + 2; | ||
343 | return var_name; | ||
344 | } | ||
345 | ", | ||
346 | ); | ||
347 | } | ||
348 | |||
349 | #[test] | ||
350 | fn test_introduce_var_break() { | ||
351 | check_assist( | ||
352 | introduce_variable, | ||
353 | " | ||
354 | fn main() { | ||
355 | let result = loop { | ||
356 | b<|>reak 2 + 2; | ||
357 | }; | ||
358 | } | ||
359 | ", | ||
360 | " | ||
361 | fn main() { | ||
362 | let result = loop { | ||
363 | let <|>var_name = 2 + 2; | ||
364 | break var_name; | ||
365 | }; | ||
366 | } | ||
367 | ", | ||
368 | ); | ||
369 | } | ||
370 | |||
371 | #[test] | ||
372 | fn test_introduce_var_for_cast() { | ||
373 | check_assist( | ||
374 | introduce_variable, | ||
375 | " | ||
376 | fn main() { | ||
377 | let v = 0f32 a<|>s u32; | ||
378 | } | ||
379 | ", | ||
380 | " | ||
381 | fn main() { | ||
382 | let <|>var_name = 0f32 as u32; | ||
383 | let v = var_name; | ||
384 | } | ||
385 | ", | ||
386 | ); | ||
387 | } | ||
388 | |||
389 | #[test] | ||
390 | fn test_introduce_var_for_return_not_applicable() { | ||
391 | check_assist_not_applicable( | ||
392 | introduce_variable, | ||
393 | " | ||
394 | fn foo() { | ||
395 | r<|>eturn; | ||
396 | } | ||
397 | ", | ||
398 | ); | ||
399 | } | ||
400 | |||
401 | #[test] | ||
402 | fn test_introduce_var_for_break_not_applicable() { | ||
403 | check_assist_not_applicable( | ||
404 | introduce_variable, | ||
405 | " | ||
406 | fn main() { | ||
407 | loop { | ||
408 | b<|>reak; | ||
409 | }; | ||
410 | } | ||
411 | ", | ||
412 | ); | ||
413 | } | ||
414 | |||
415 | #[test] | ||
416 | fn test_introduce_var_in_comment_not_applicable() { | ||
417 | check_assist_not_applicable( | ||
418 | introduce_variable, | ||
419 | " | ||
420 | fn main() { | ||
421 | let x = true; | ||
422 | let tuple = match x { | ||
423 | // c<|>omment | ||
424 | true => (2 + 2, true) | ||
425 | _ => (0, false) | ||
426 | }; | ||
427 | } | ||
428 | ", | ||
429 | ); | ||
430 | } | ||
431 | } | ||
diff --git a/crates/ra_ide_api_light/src/assists/replace_if_let_with_match.rs b/crates/ra_ide_api_light/src/assists/replace_if_let_with_match.rs deleted file mode 100644 index 71880b919..000000000 --- a/crates/ra_ide_api_light/src/assists/replace_if_let_with_match.rs +++ /dev/null | |||
@@ -1,81 +0,0 @@ | |||
1 | use ra_syntax::{AstNode, ast}; | ||
2 | |||
3 | use crate::{ | ||
4 | assists::{AssistCtx, Assist}, | ||
5 | formatting::extract_trivial_expression, | ||
6 | }; | ||
7 | |||
8 | pub fn replace_if_let_with_match(ctx: AssistCtx) -> Option<Assist> { | ||
9 | let if_expr: &ast::IfExpr = ctx.node_at_offset()?; | ||
10 | let cond = if_expr.condition()?; | ||
11 | let pat = cond.pat()?; | ||
12 | let expr = cond.expr()?; | ||
13 | let then_block = if_expr.then_branch()?; | ||
14 | let else_block = match if_expr.else_branch()? { | ||
15 | ast::ElseBranchFlavor::Block(it) => it, | ||
16 | ast::ElseBranchFlavor::IfExpr(_) => return None, | ||
17 | }; | ||
18 | |||
19 | ctx.build("replace with match", |edit| { | ||
20 | let match_expr = build_match_expr(expr, pat, then_block, else_block); | ||
21 | edit.replace_node_and_indent(if_expr.syntax(), match_expr); | ||
22 | edit.set_cursor(if_expr.syntax().range().start()) | ||
23 | }) | ||
24 | } | ||
25 | |||
26 | fn build_match_expr( | ||
27 | expr: &ast::Expr, | ||
28 | pat1: &ast::Pat, | ||
29 | arm1: &ast::Block, | ||
30 | arm2: &ast::Block, | ||
31 | ) -> String { | ||
32 | let mut buf = String::new(); | ||
33 | buf.push_str(&format!("match {} {{\n", expr.syntax().text())); | ||
34 | buf.push_str(&format!( | ||
35 | " {} => {}\n", | ||
36 | pat1.syntax().text(), | ||
37 | format_arm(arm1) | ||
38 | )); | ||
39 | buf.push_str(&format!(" _ => {}\n", format_arm(arm2))); | ||
40 | buf.push_str("}"); | ||
41 | buf | ||
42 | } | ||
43 | |||
44 | fn format_arm(block: &ast::Block) -> String { | ||
45 | match extract_trivial_expression(block) { | ||
46 | None => block.syntax().text().to_string(), | ||
47 | Some(e) => format!("{},", e.syntax().text()), | ||
48 | } | ||
49 | } | ||
50 | |||
51 | #[cfg(test)] | ||
52 | mod tests { | ||
53 | use super::*; | ||
54 | use crate::assists::check_assist; | ||
55 | |||
56 | #[test] | ||
57 | fn test_replace_if_let_with_match_unwraps_simple_expressions() { | ||
58 | check_assist( | ||
59 | replace_if_let_with_match, | ||
60 | " | ||
61 | impl VariantData { | ||
62 | pub fn is_struct(&self) -> bool { | ||
63 | if <|>let VariantData::Struct(..) = *self { | ||
64 | true | ||
65 | } else { | ||
66 | false | ||
67 | } | ||
68 | } | ||
69 | } ", | ||
70 | " | ||
71 | impl VariantData { | ||
72 | pub fn is_struct(&self) -> bool { | ||
73 | <|>match *self { | ||
74 | VariantData::Struct(..) => true, | ||
75 | _ => false, | ||
76 | } | ||
77 | } | ||
78 | } ", | ||
79 | ) | ||
80 | } | ||
81 | } | ||
diff --git a/crates/ra_ide_api_light/src/assists/split_import.rs b/crates/ra_ide_api_light/src/assists/split_import.rs deleted file mode 100644 index e4015f07d..000000000 --- a/crates/ra_ide_api_light/src/assists/split_import.rs +++ /dev/null | |||
@@ -1,56 +0,0 @@ | |||
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 = match top_path.syntax().parent().and_then(ast::UseTree::cast) { | ||
23 | Some(tree) => tree.syntax().range().end(), | ||
24 | None => top_path.syntax().range().end(), | ||
25 | }; | ||
26 | |||
27 | ctx.build("split import", |edit| { | ||
28 | edit.insert(l_curly, "{"); | ||
29 | edit.insert(r_curly, "}"); | ||
30 | edit.set_cursor(l_curly + TextUnit::of_str("{")); | ||
31 | }) | ||
32 | } | ||
33 | |||
34 | #[cfg(test)] | ||
35 | mod tests { | ||
36 | use super::*; | ||
37 | use crate::assists::check_assist; | ||
38 | |||
39 | #[test] | ||
40 | fn test_split_import() { | ||
41 | check_assist( | ||
42 | split_import, | ||
43 | "use crate::<|>db::RootDatabase;", | ||
44 | "use crate::{<|>db::RootDatabase};", | ||
45 | ) | ||
46 | } | ||
47 | |||
48 | #[test] | ||
49 | fn split_import_works_with_trees() { | ||
50 | check_assist( | ||
51 | split_import, | ||
52 | "use algo:<|>:visitor::{Visitor, visit}", | ||
53 | "use algo::{<|>visitor::{Visitor, visit}}", | ||
54 | ) | ||
55 | } | ||
56 | } | ||
diff --git a/crates/ra_ide_api_light/src/formatting.rs b/crates/ra_ide_api_light/src/formatting.rs index 1f34b85d6..46ffa7d96 100644 --- a/crates/ra_ide_api_light/src/formatting.rs +++ b/crates/ra_ide_api_light/src/formatting.rs | |||
@@ -1,3 +1,4 @@ | |||
1 | use itertools::Itertools; | ||
1 | use ra_syntax::{ | 2 | use ra_syntax::{ |
2 | AstNode, | 3 | AstNode, |
3 | SyntaxNode, SyntaxKind::*, | 4 | SyntaxNode, SyntaxKind::*, |
@@ -5,8 +6,13 @@ use ra_syntax::{ | |||
5 | algo::generate, | 6 | algo::generate, |
6 | }; | 7 | }; |
7 | 8 | ||
9 | pub fn reindent(text: &str, indent: &str) -> String { | ||
10 | let indent = format!("\n{}", indent); | ||
11 | text.lines().intersperse(&indent).collect() | ||
12 | } | ||
13 | |||
8 | /// If the node is on the beginning of the line, calculate indent. | 14 | /// If the node is on the beginning of the line, calculate indent. |
9 | pub(crate) fn leading_indent(node: &SyntaxNode) -> Option<&str> { | 15 | pub fn leading_indent(node: &SyntaxNode) -> Option<&str> { |
10 | for leaf in prev_leaves(node) { | 16 | for leaf in prev_leaves(node) { |
11 | if let Some(ws) = ast::Whitespace::cast(leaf) { | 17 | if let Some(ws) = ast::Whitespace::cast(leaf) { |
12 | let ws_text = ws.text(); | 18 | let ws_text = ws.text(); |
@@ -32,7 +38,7 @@ fn prev_leaf(node: &SyntaxNode) -> Option<&SyntaxNode> { | |||
32 | .last() | 38 | .last() |
33 | } | 39 | } |
34 | 40 | ||
35 | pub(crate) fn extract_trivial_expression(block: &ast::Block) -> Option<&ast::Expr> { | 41 | pub fn extract_trivial_expression(block: &ast::Block) -> Option<&ast::Expr> { |
36 | let expr = block.expr()?; | 42 | let expr = block.expr()?; |
37 | if expr.syntax().text().contains('\n') { | 43 | if expr.syntax().text().contains('\n') { |
38 | return None; | 44 | return None; |
diff --git a/crates/ra_ide_api_light/src/lib.rs b/crates/ra_ide_api_light/src/lib.rs index 9dd72701d..17044270c 100644 --- a/crates/ra_ide_api_light/src/lib.rs +++ b/crates/ra_ide_api_light/src/lib.rs | |||
@@ -3,7 +3,7 @@ | |||
3 | //! This usually means functions which take syntax tree as an input and produce | 3 | //! This usually means functions which take syntax tree as an input and produce |
4 | //! an edit or some auxiliary info. | 4 | //! an edit or some auxiliary info. |
5 | 5 | ||
6 | pub mod assists; | 6 | pub mod formatting; |
7 | mod extend_selection; | 7 | mod extend_selection; |
8 | mod folding_ranges; | 8 | mod folding_ranges; |
9 | mod line_index; | 9 | mod line_index; |
@@ -14,10 +14,15 @@ mod test_utils; | |||
14 | mod join_lines; | 14 | mod join_lines; |
15 | mod typing; | 15 | mod typing; |
16 | mod diagnostics; | 16 | mod diagnostics; |
17 | pub(crate) mod formatting; | 17 | |
18 | #[derive(Debug)] | ||
19 | pub struct LocalEdit { | ||
20 | pub label: String, | ||
21 | pub edit: ra_text_edit::TextEdit, | ||
22 | pub cursor_position: Option<TextUnit>, | ||
23 | } | ||
18 | 24 | ||
19 | pub use self::{ | 25 | pub use self::{ |
20 | assists::LocalEdit, | ||
21 | extend_selection::extend_selection, | 26 | extend_selection::extend_selection, |
22 | folding_ranges::{folding_ranges, Fold, FoldKind}, | 27 | folding_ranges::{folding_ranges, Fold, FoldKind}, |
23 | line_index::{LineCol, LineIndex}, | 28 | line_index::{LineCol, LineIndex}, |
diff --git a/crates/ra_ide_api_light/src/test_utils.rs b/crates/ra_ide_api_light/src/test_utils.rs index 22ded2435..bfac0fce3 100644 --- a/crates/ra_ide_api_light/src/test_utils.rs +++ b/crates/ra_ide_api_light/src/test_utils.rs | |||
@@ -1,4 +1,4 @@ | |||
1 | use ra_syntax::{SourceFile, TextRange, TextUnit}; | 1 | use ra_syntax::{SourceFile, TextUnit}; |
2 | 2 | ||
3 | use crate::LocalEdit; | 3 | use crate::LocalEdit; |
4 | pub use test_utils::*; | 4 | pub use test_utils::*; |
@@ -22,32 +22,3 @@ pub fn check_action<F: Fn(&SourceFile, TextUnit) -> Option<LocalEdit>>( | |||
22 | let actual = add_cursor(&actual, actual_cursor_pos); | 22 | let actual = add_cursor(&actual, actual_cursor_pos); |
23 | assert_eq_text!(after, &actual); | 23 | assert_eq_text!(after, &actual); |
24 | } | 24 | } |
25 | |||
26 | pub fn check_action_not_applicable<F: Fn(&SourceFile, TextUnit) -> Option<LocalEdit>>( | ||
27 | text: &str, | ||
28 | f: F, | ||
29 | ) { | ||
30 | let (text_cursor_pos, text) = extract_offset(text); | ||
31 | let file = SourceFile::parse(&text); | ||
32 | assert!( | ||
33 | f(&file, text_cursor_pos).is_none(), | ||
34 | "code action is applicable but it shouldn't" | ||
35 | ); | ||
36 | } | ||
37 | |||
38 | pub fn check_action_range<F: Fn(&SourceFile, TextRange) -> Option<LocalEdit>>( | ||
39 | before: &str, | ||
40 | after: &str, | ||
41 | f: F, | ||
42 | ) { | ||
43 | let (range, before) = extract_range(before); | ||
44 | let file = SourceFile::parse(&before); | ||
45 | let result = f(&file, range).expect("code action is not applicable"); | ||
46 | let actual = result.edit.apply(&before); | ||
47 | let actual_cursor_pos = match result.cursor_position { | ||
48 | None => result.edit.apply_to_offset(range.start()).unwrap(), | ||
49 | Some(off) => off, | ||
50 | }; | ||
51 | let actual = add_cursor(&actual, actual_cursor_pos); | ||
52 | assert_eq_text!(after, &actual); | ||
53 | } | ||