diff options
Diffstat (limited to 'crates/ra_editor/src')
-rw-r--r-- | crates/ra_editor/src/assists.rs | 155 | ||||
-rw-r--r-- | crates/ra_editor/src/assists/add_derive.rs | 61 | ||||
-rw-r--r-- | crates/ra_editor/src/assists/add_impl.rs | 36 | ||||
-rw-r--r-- | crates/ra_editor/src/assists/change_visibility.rs | 85 | ||||
-rw-r--r-- | crates/ra_editor/src/assists/flip_comma.rs | 36 | ||||
-rw-r--r-- | crates/ra_editor/src/assists/introduce_variable.rs | 84 | ||||
-rw-r--r-- | crates/ra_editor/src/diagnostics.rs | 6 | ||||
-rw-r--r-- | crates/ra_editor/src/typing.rs | 8 |
8 files changed, 275 insertions, 196 deletions
diff --git a/crates/ra_editor/src/assists.rs b/crates/ra_editor/src/assists.rs index b6e6dd628..cc40ee4c8 100644 --- a/crates/ra_editor/src/assists.rs +++ b/crates/ra_editor/src/assists.rs | |||
@@ -9,8 +9,13 @@ mod add_impl; | |||
9 | mod introduce_variable; | 9 | mod introduce_variable; |
10 | mod change_visibility; | 10 | mod change_visibility; |
11 | 11 | ||
12 | use ra_text_edit::TextEdit; | 12 | use ra_text_edit::{TextEdit, TextEditBuilder}; |
13 | use ra_syntax::{Direction, SyntaxNodeRef, TextUnit}; | 13 | use ra_syntax::{ |
14 | Direction, SyntaxNodeRef, TextUnit, TextRange,SourceFileNode, AstNode, | ||
15 | algo::{find_leaf_at_offset, find_covering_node, LeafAtOffset}, | ||
16 | }; | ||
17 | |||
18 | use crate::find_node_at_offset; | ||
14 | 19 | ||
15 | pub use self::{ | 20 | pub use self::{ |
16 | flip_comma::flip_comma, | 21 | flip_comma::flip_comma, |
@@ -20,6 +25,21 @@ pub use self::{ | |||
20 | change_visibility::change_visibility, | 25 | change_visibility::change_visibility, |
21 | }; | 26 | }; |
22 | 27 | ||
28 | /// Return all the assists applicable at the given position. | ||
29 | pub fn assists(file: &SourceFileNode, range: TextRange) -> Vec<LocalEdit> { | ||
30 | let ctx = AssistCtx::new(file, range); | ||
31 | [ | ||
32 | flip_comma, | ||
33 | add_derive, | ||
34 | add_impl, | ||
35 | introduce_variable, | ||
36 | change_visibility, | ||
37 | ] | ||
38 | .iter() | ||
39 | .filter_map(|&assist| ctx.clone().apply(assist)) | ||
40 | .collect() | ||
41 | } | ||
42 | |||
23 | #[derive(Debug)] | 43 | #[derive(Debug)] |
24 | pub struct LocalEdit { | 44 | pub struct LocalEdit { |
25 | pub label: String, | 45 | pub label: String, |
@@ -32,3 +52,134 @@ fn non_trivia_sibling(node: SyntaxNodeRef, direction: Direction) -> Option<Synta | |||
32 | .skip(1) | 52 | .skip(1) |
33 | .find(|node| !node.kind().is_trivia()) | 53 | .find(|node| !node.kind().is_trivia()) |
34 | } | 54 | } |
55 | |||
56 | /// `AssistCtx` allows to apply an assist or check if it could be applied. | ||
57 | /// | ||
58 | /// Assists use a somewhat overengeneered approach, given the current needs. The | ||
59 | /// assists workflow consists of two phases. In the first phase, a user asks for | ||
60 | /// the list of available assists. In the second phase, the user picks a | ||
61 | /// particular assist and it gets applied. | ||
62 | /// | ||
63 | /// There are two peculiarities here: | ||
64 | /// | ||
65 | /// * first, we ideally avoid computing more things then neccessary to answer | ||
66 | /// "is assist applicable" in the first phase. | ||
67 | /// * second, when we are appling assist, we don't have a gurantee that there | ||
68 | /// weren't any changes between the point when user asked for assists and when | ||
69 | /// they applied a particular assist. So, when applying assist, we need to do | ||
70 | /// all the checks from scratch. | ||
71 | /// | ||
72 | /// To avoid repeating the same code twice for both "check" and "apply" | ||
73 | /// functions, we use an approach remeniscent of that of Django's function based | ||
74 | /// views dealing with forms. Each assist receives a runtime parameter, | ||
75 | /// `should_compute_edit`. It first check if an edit is applicable (potentially | ||
76 | /// computing info required to compute the actual edit). If it is applicable, | ||
77 | /// and `should_compute_edit` is `true`, it then computes the actual edit. | ||
78 | /// | ||
79 | /// So, to implement the original assists workflow, we can first apply each edit | ||
80 | /// with `should_compute_edit = false`, and then applying the selected edit | ||
81 | /// again, with `should_compute_edit = true` this time. | ||
82 | /// | ||
83 | /// Note, however, that we don't actually use such two-phase logic at the | ||
84 | /// moment, because the LSP API is pretty awkward in this place, and it's much | ||
85 | /// easier to just compute the edit eagarly :-) | ||
86 | #[derive(Debug, Clone)] | ||
87 | pub struct AssistCtx<'a> { | ||
88 | source_file: &'a SourceFileNode, | ||
89 | range: TextRange, | ||
90 | should_compute_edit: bool, | ||
91 | } | ||
92 | |||
93 | #[derive(Debug)] | ||
94 | pub enum Assist { | ||
95 | Applicable, | ||
96 | Edit(LocalEdit), | ||
97 | } | ||
98 | |||
99 | #[derive(Default)] | ||
100 | struct AssistBuilder { | ||
101 | edit: TextEditBuilder, | ||
102 | cursor_position: Option<TextUnit>, | ||
103 | } | ||
104 | |||
105 | impl<'a> AssistCtx<'a> { | ||
106 | pub fn new(source_file: &'a SourceFileNode, range: TextRange) -> AssistCtx { | ||
107 | AssistCtx { | ||
108 | source_file, | ||
109 | range, | ||
110 | should_compute_edit: false, | ||
111 | } | ||
112 | } | ||
113 | |||
114 | pub fn apply(mut self, assist: fn(AssistCtx) -> Option<Assist>) -> Option<LocalEdit> { | ||
115 | self.should_compute_edit = true; | ||
116 | match assist(self) { | ||
117 | None => None, | ||
118 | Some(Assist::Edit(e)) => Some(e), | ||
119 | Some(Assist::Applicable) => unreachable!(), | ||
120 | } | ||
121 | } | ||
122 | |||
123 | pub fn check(mut self, assist: fn(AssistCtx) -> Option<Assist>) -> bool { | ||
124 | self.should_compute_edit = false; | ||
125 | match assist(self) { | ||
126 | None => false, | ||
127 | Some(Assist::Edit(_)) => unreachable!(), | ||
128 | Some(Assist::Applicable) => true, | ||
129 | } | ||
130 | } | ||
131 | |||
132 | fn build(self, label: impl Into<String>, f: impl FnOnce(&mut AssistBuilder)) -> Option<Assist> { | ||
133 | if !self.should_compute_edit { | ||
134 | return Some(Assist::Applicable); | ||
135 | } | ||
136 | let mut edit = AssistBuilder::default(); | ||
137 | f(&mut edit); | ||
138 | Some(Assist::Edit(LocalEdit { | ||
139 | label: label.into(), | ||
140 | edit: edit.edit.finish(), | ||
141 | cursor_position: edit.cursor_position, | ||
142 | })) | ||
143 | } | ||
144 | |||
145 | pub(crate) fn leaf_at_offset(&self) -> LeafAtOffset<SyntaxNodeRef<'a>> { | ||
146 | find_leaf_at_offset(self.source_file.syntax(), self.range.start()) | ||
147 | } | ||
148 | pub(crate) fn node_at_offset<N: AstNode<'a>>(&self) -> Option<N> { | ||
149 | find_node_at_offset(self.source_file.syntax(), self.range.start()) | ||
150 | } | ||
151 | pub(crate) fn covering_node(&self) -> SyntaxNodeRef<'a> { | ||
152 | find_covering_node(self.source_file.syntax(), self.range) | ||
153 | } | ||
154 | } | ||
155 | |||
156 | impl AssistBuilder { | ||
157 | fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) { | ||
158 | self.edit.replace(range, replace_with.into()) | ||
159 | } | ||
160 | #[allow(unused)] | ||
161 | fn delete(&mut self, range: TextRange) { | ||
162 | self.edit.delete(range) | ||
163 | } | ||
164 | fn insert(&mut self, offset: TextUnit, text: impl Into<String>) { | ||
165 | self.edit.insert(offset, text.into()) | ||
166 | } | ||
167 | fn set_cursor(&mut self, offset: TextUnit) { | ||
168 | self.cursor_position = Some(offset) | ||
169 | } | ||
170 | } | ||
171 | |||
172 | #[cfg(test)] | ||
173 | fn check_assist(assist: fn(AssistCtx) -> Option<Assist>, before: &str, after: &str) { | ||
174 | crate::test_utils::check_action(before, after, |file, off| { | ||
175 | let range = TextRange::offset_len(off, 0.into()); | ||
176 | AssistCtx::new(file, range).apply(assist) | ||
177 | }) | ||
178 | } | ||
179 | |||
180 | #[cfg(test)] | ||
181 | fn check_assist_range(assist: fn(AssistCtx) -> Option<Assist>, before: &str, after: &str) { | ||
182 | crate::test_utils::check_action_range(before, after, |file, range| { | ||
183 | AssistCtx::new(file, range).apply(assist) | ||
184 | }) | ||
185 | } | ||
diff --git a/crates/ra_editor/src/assists/add_derive.rs b/crates/ra_editor/src/assists/add_derive.rs index 33d9d2c31..1e2cd4f30 100644 --- a/crates/ra_editor/src/assists/add_derive.rs +++ b/crates/ra_editor/src/assists/add_derive.rs | |||
@@ -1,85 +1,73 @@ | |||
1 | use ra_text_edit::TextEditBuilder; | ||
2 | use ra_syntax::{ | 1 | use ra_syntax::{ |
3 | ast::{self, AstNode, AttrsOwner}, | 2 | ast::{self, AstNode, AttrsOwner}, |
4 | SourceFileNode, | ||
5 | SyntaxKind::{WHITESPACE, COMMENT}, | 3 | SyntaxKind::{WHITESPACE, COMMENT}, |
6 | TextUnit, | 4 | TextUnit, |
7 | }; | 5 | }; |
8 | 6 | ||
9 | use crate::{ | 7 | use crate::assists::{AssistCtx, Assist}; |
10 | find_node_at_offset, | ||
11 | assists::LocalEdit, | ||
12 | }; | ||
13 | 8 | ||
14 | pub fn add_derive<'a>( | 9 | pub fn add_derive(ctx: AssistCtx) -> Option<Assist> { |
15 | file: &'a SourceFileNode, | 10 | let nominal = ctx.node_at_offset::<ast::NominalDef>()?; |
16 | offset: TextUnit, | ||
17 | ) -> Option<impl FnOnce() -> LocalEdit + 'a> { | ||
18 | let nominal = find_node_at_offset::<ast::NominalDef>(file.syntax(), offset)?; | ||
19 | let node_start = derive_insertion_offset(nominal)?; | 11 | let node_start = derive_insertion_offset(nominal)?; |
20 | return Some(move || { | 12 | ctx.build("add `#[derive]`", |edit| { |
21 | let derive_attr = nominal | 13 | let derive_attr = nominal |
22 | .attrs() | 14 | .attrs() |
23 | .filter_map(|x| x.as_call()) | 15 | .filter_map(|x| x.as_call()) |
24 | .filter(|(name, _arg)| name == "derive") | 16 | .filter(|(name, _arg)| name == "derive") |
25 | .map(|(_name, arg)| arg) | 17 | .map(|(_name, arg)| arg) |
26 | .next(); | 18 | .next(); |
27 | let mut edit = TextEditBuilder::new(); | ||
28 | let offset = match derive_attr { | 19 | let offset = match derive_attr { |
29 | None => { | 20 | None => { |
30 | edit.insert(node_start, "#[derive()]\n".to_string()); | 21 | edit.insert(node_start, "#[derive()]\n"); |
31 | node_start + TextUnit::of_str("#[derive(") | 22 | node_start + TextUnit::of_str("#[derive(") |
32 | } | 23 | } |
33 | Some(tt) => tt.syntax().range().end() - TextUnit::of_char(')'), | 24 | Some(tt) => tt.syntax().range().end() - TextUnit::of_char(')'), |
34 | }; | 25 | }; |
35 | LocalEdit { | 26 | edit.set_cursor(offset) |
36 | label: "add `#[derive]`".to_string(), | 27 | }) |
37 | edit: edit.finish(), | 28 | } |
38 | cursor_position: Some(offset), | ||
39 | } | ||
40 | }); | ||
41 | 29 | ||
42 | // Insert `derive` after doc comments. | 30 | // Insert `derive` after doc comments. |
43 | fn derive_insertion_offset(nominal: ast::NominalDef) -> Option<TextUnit> { | 31 | fn derive_insertion_offset(nominal: ast::NominalDef) -> Option<TextUnit> { |
44 | let non_ws_child = nominal | 32 | let non_ws_child = nominal |
45 | .syntax() | 33 | .syntax() |
46 | .children() | 34 | .children() |
47 | .find(|it| it.kind() != COMMENT && it.kind() != WHITESPACE)?; | 35 | .find(|it| it.kind() != COMMENT && it.kind() != WHITESPACE)?; |
48 | Some(non_ws_child.range().start()) | 36 | Some(non_ws_child.range().start()) |
49 | } | ||
50 | } | 37 | } |
51 | 38 | ||
52 | #[cfg(test)] | 39 | #[cfg(test)] |
53 | mod tests { | 40 | mod tests { |
54 | use super::*; | 41 | use super::*; |
55 | use crate::test_utils::check_action; | 42 | use crate::assists::check_assist; |
56 | 43 | ||
57 | #[test] | 44 | #[test] |
58 | fn add_derive_new() { | 45 | fn add_derive_new() { |
59 | check_action( | 46 | check_assist( |
47 | add_derive, | ||
60 | "struct Foo { a: i32, <|>}", | 48 | "struct Foo { a: i32, <|>}", |
61 | "#[derive(<|>)]\nstruct Foo { a: i32, }", | 49 | "#[derive(<|>)]\nstruct Foo { a: i32, }", |
62 | |file, off| add_derive(file, off).map(|f| f()), | ||
63 | ); | 50 | ); |
64 | check_action( | 51 | check_assist( |
52 | add_derive, | ||
65 | "struct Foo { <|> a: i32, }", | 53 | "struct Foo { <|> a: i32, }", |
66 | "#[derive(<|>)]\nstruct Foo { a: i32, }", | 54 | "#[derive(<|>)]\nstruct Foo { a: i32, }", |
67 | |file, off| add_derive(file, off).map(|f| f()), | ||
68 | ); | 55 | ); |
69 | } | 56 | } |
70 | 57 | ||
71 | #[test] | 58 | #[test] |
72 | fn add_derive_existing() { | 59 | fn add_derive_existing() { |
73 | check_action( | 60 | check_assist( |
61 | add_derive, | ||
74 | "#[derive(Clone)]\nstruct Foo { a: i32<|>, }", | 62 | "#[derive(Clone)]\nstruct Foo { a: i32<|>, }", |
75 | "#[derive(Clone<|>)]\nstruct Foo { a: i32, }", | 63 | "#[derive(Clone<|>)]\nstruct Foo { a: i32, }", |
76 | |file, off| add_derive(file, off).map(|f| f()), | ||
77 | ); | 64 | ); |
78 | } | 65 | } |
79 | 66 | ||
80 | #[test] | 67 | #[test] |
81 | fn add_derive_new_with_doc_comment() { | 68 | fn add_derive_new_with_doc_comment() { |
82 | check_action( | 69 | check_assist( |
70 | add_derive, | ||
83 | " | 71 | " |
84 | /// `Foo` is a pretty important struct. | 72 | /// `Foo` is a pretty important struct. |
85 | /// It does stuff. | 73 | /// It does stuff. |
@@ -91,7 +79,6 @@ struct Foo { a: i32<|>, } | |||
91 | #[derive(<|>)] | 79 | #[derive(<|>)] |
92 | struct Foo { a: i32, } | 80 | struct Foo { a: i32, } |
93 | ", | 81 | ", |
94 | |file, off| add_derive(file, off).map(|f| f()), | ||
95 | ); | 82 | ); |
96 | } | 83 | } |
97 | } | 84 | } |
diff --git a/crates/ra_editor/src/assists/add_impl.rs b/crates/ra_editor/src/assists/add_impl.rs index 50e00688e..9353e2717 100644 --- a/crates/ra_editor/src/assists/add_impl.rs +++ b/crates/ra_editor/src/assists/add_impl.rs | |||
@@ -1,23 +1,16 @@ | |||
1 | use join_to_string::join; | 1 | use join_to_string::join; |
2 | use ra_text_edit::TextEditBuilder; | ||
3 | use ra_syntax::{ | 2 | use ra_syntax::{ |
4 | ast::{self, AstNode, NameOwner, TypeParamsOwner}, | 3 | ast::{self, AstNode, NameOwner, TypeParamsOwner}, |
5 | SourceFileNode, | ||
6 | TextUnit, | 4 | TextUnit, |
7 | }; | 5 | }; |
8 | 6 | ||
9 | use crate::{find_node_at_offset, assists::LocalEdit}; | 7 | use crate::assists::{AssistCtx, Assist}; |
10 | 8 | ||
11 | pub fn add_impl<'a>( | 9 | pub fn add_impl(ctx: AssistCtx) -> Option<Assist> { |
12 | file: &'a SourceFileNode, | 10 | let nominal = ctx.node_at_offset::<ast::NominalDef>()?; |
13 | offset: TextUnit, | ||
14 | ) -> Option<impl FnOnce() -> LocalEdit + 'a> { | ||
15 | let nominal = find_node_at_offset::<ast::NominalDef>(file.syntax(), offset)?; | ||
16 | let name = nominal.name()?; | 11 | let name = nominal.name()?; |
17 | 12 | ctx.build("add impl", |edit| { | |
18 | Some(move || { | ||
19 | let type_params = nominal.type_param_list(); | 13 | let type_params = nominal.type_param_list(); |
20 | let mut edit = TextEditBuilder::new(); | ||
21 | let start_offset = nominal.syntax().range().end(); | 14 | let start_offset = nominal.syntax().range().end(); |
22 | let mut buf = String::new(); | 15 | let mut buf = String::new(); |
23 | buf.push_str("\n\nimpl"); | 16 | buf.push_str("\n\nimpl"); |
@@ -40,38 +33,33 @@ pub fn add_impl<'a>( | |||
40 | .to_buf(&mut buf); | 33 | .to_buf(&mut buf); |
41 | } | 34 | } |
42 | buf.push_str(" {\n"); | 35 | buf.push_str(" {\n"); |
43 | let offset = start_offset + TextUnit::of_str(&buf); | 36 | edit.set_cursor(start_offset + TextUnit::of_str(&buf)); |
44 | buf.push_str("\n}"); | 37 | buf.push_str("\n}"); |
45 | edit.insert(start_offset, buf); | 38 | edit.insert(start_offset, buf); |
46 | LocalEdit { | ||
47 | label: "add impl".to_string(), | ||
48 | edit: edit.finish(), | ||
49 | cursor_position: Some(offset), | ||
50 | } | ||
51 | }) | 39 | }) |
52 | } | 40 | } |
53 | 41 | ||
54 | #[cfg(test)] | 42 | #[cfg(test)] |
55 | mod tests { | 43 | mod tests { |
56 | use super::*; | 44 | use super::*; |
57 | use crate::test_utils::check_action; | 45 | use crate::assists::check_assist; |
58 | 46 | ||
59 | #[test] | 47 | #[test] |
60 | fn test_add_impl() { | 48 | fn test_add_impl() { |
61 | check_action( | 49 | check_assist( |
50 | add_impl, | ||
62 | "struct Foo {<|>}\n", | 51 | "struct Foo {<|>}\n", |
63 | "struct Foo {}\n\nimpl Foo {\n<|>\n}\n", | 52 | "struct Foo {}\n\nimpl Foo {\n<|>\n}\n", |
64 | |file, off| add_impl(file, off).map(|f| f()), | ||
65 | ); | 53 | ); |
66 | check_action( | 54 | check_assist( |
55 | add_impl, | ||
67 | "struct Foo<T: Clone> {<|>}", | 56 | "struct Foo<T: Clone> {<|>}", |
68 | "struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n<|>\n}", | 57 | "struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n<|>\n}", |
69 | |file, off| add_impl(file, off).map(|f| f()), | ||
70 | ); | 58 | ); |
71 | check_action( | 59 | check_assist( |
60 | add_impl, | ||
72 | "struct Foo<'a, T: Foo<'a>> {<|>}", | 61 | "struct Foo<'a, T: Foo<'a>> {<|>}", |
73 | "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n<|>\n}", | 62 | "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n<|>\n}", |
74 | |file, off| add_impl(file, off).map(|f| f()), | ||
75 | ); | 63 | ); |
76 | } | 64 | } |
77 | 65 | ||
diff --git a/crates/ra_editor/src/assists/change_visibility.rs b/crates/ra_editor/src/assists/change_visibility.rs index 98c218f32..e2cd9ffd1 100644 --- a/crates/ra_editor/src/assists/change_visibility.rs +++ b/crates/ra_editor/src/assists/change_visibility.rs | |||
@@ -1,90 +1,69 @@ | |||
1 | use ra_text_edit::TextEditBuilder; | ||
2 | use ra_syntax::{ | 1 | use ra_syntax::{ |
3 | SourceFileNode, | ||
4 | algo::find_leaf_at_offset, | ||
5 | SyntaxKind::{VISIBILITY, FN_KW, MOD_KW, STRUCT_KW, ENUM_KW, TRAIT_KW, FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF}, | 2 | SyntaxKind::{VISIBILITY, FN_KW, MOD_KW, STRUCT_KW, ENUM_KW, TRAIT_KW, FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF}, |
6 | TextUnit, | ||
7 | }; | 3 | }; |
8 | 4 | ||
9 | use crate::assists::LocalEdit; | 5 | use crate::assists::{AssistCtx, Assist}; |
10 | 6 | ||
11 | pub fn change_visibility<'a>( | 7 | pub fn change_visibility(ctx: AssistCtx) -> Option<Assist> { |
12 | file: &'a SourceFileNode, | 8 | let keyword = ctx.leaf_at_offset().find(|leaf| match leaf.kind() { |
13 | offset: TextUnit, | ||
14 | ) -> Option<impl FnOnce() -> LocalEdit + 'a> { | ||
15 | let syntax = file.syntax(); | ||
16 | |||
17 | let keyword = find_leaf_at_offset(syntax, offset).find(|leaf| match leaf.kind() { | ||
18 | FN_KW | MOD_KW | STRUCT_KW | ENUM_KW | TRAIT_KW => true, | 9 | FN_KW | MOD_KW | STRUCT_KW | ENUM_KW | TRAIT_KW => true, |
19 | _ => false, | 10 | _ => false, |
20 | })?; | 11 | })?; |
21 | let parent = keyword.parent()?; | 12 | let parent = keyword.parent()?; |
22 | let def_kws = vec![FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF]; | 13 | let def_kws = vec![FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF]; |
23 | let node_start = parent.range().start(); | 14 | // Parent is not a definition, can't add visibility |
24 | Some(move || { | 15 | if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) { |
25 | let mut edit = TextEditBuilder::new(); | 16 | return None; |
26 | 17 | } | |
27 | if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) | 18 | // Already have visibility, do nothing |
28 | || parent.children().any(|child| child.kind() == VISIBILITY) | 19 | if parent.children().any(|child| child.kind() == VISIBILITY) { |
29 | { | 20 | return None; |
30 | return LocalEdit { | 21 | } |
31 | label: "make pub crate".to_string(), | ||
32 | edit: edit.finish(), | ||
33 | cursor_position: Some(offset), | ||
34 | }; | ||
35 | } | ||
36 | 22 | ||
37 | edit.insert(node_start, "pub(crate) ".to_string()); | 23 | let node_start = parent.range().start(); |
38 | LocalEdit { | 24 | ctx.build("make pub crate", |edit| { |
39 | label: "make pub crate".to_string(), | 25 | edit.insert(node_start, "pub(crate) "); |
40 | edit: edit.finish(), | 26 | edit.set_cursor(node_start); |
41 | cursor_position: Some(node_start), | ||
42 | } | ||
43 | }) | 27 | }) |
44 | } | 28 | } |
45 | 29 | ||
46 | #[cfg(test)] | 30 | #[cfg(test)] |
47 | mod tests { | 31 | mod tests { |
48 | use super::*; | 32 | use super::*; |
49 | use crate::test_utils::check_action; | 33 | use crate::assists::check_assist; |
50 | 34 | ||
51 | #[test] | 35 | #[test] |
52 | fn test_change_visibility() { | 36 | fn test_change_visibility() { |
53 | check_action( | 37 | check_assist( |
38 | change_visibility, | ||
54 | "<|>fn foo() {}", | 39 | "<|>fn foo() {}", |
55 | "<|>pub(crate) fn foo() {}", | 40 | "<|>pub(crate) fn foo() {}", |
56 | |file, off| change_visibility(file, off).map(|f| f()), | ||
57 | ); | 41 | ); |
58 | check_action( | 42 | check_assist( |
43 | change_visibility, | ||
59 | "f<|>n foo() {}", | 44 | "f<|>n foo() {}", |
60 | "<|>pub(crate) fn foo() {}", | 45 | "<|>pub(crate) fn foo() {}", |
61 | |file, off| change_visibility(file, off).map(|f| f()), | ||
62 | ); | 46 | ); |
63 | check_action( | 47 | check_assist( |
48 | change_visibility, | ||
64 | "<|>struct Foo {}", | 49 | "<|>struct Foo {}", |
65 | "<|>pub(crate) struct Foo {}", | 50 | "<|>pub(crate) struct Foo {}", |
66 | |file, off| change_visibility(file, off).map(|f| f()), | ||
67 | ); | 51 | ); |
68 | check_action("<|>mod foo {}", "<|>pub(crate) mod foo {}", |file, off| { | 52 | check_assist( |
69 | change_visibility(file, off).map(|f| f()) | 53 | change_visibility, |
70 | }); | 54 | "<|>mod foo {}", |
71 | check_action( | 55 | "<|>pub(crate) mod foo {}", |
56 | ); | ||
57 | check_assist( | ||
58 | change_visibility, | ||
72 | "<|>trait Foo {}", | 59 | "<|>trait Foo {}", |
73 | "<|>pub(crate) trait Foo {}", | 60 | "<|>pub(crate) trait Foo {}", |
74 | |file, off| change_visibility(file, off).map(|f| f()), | ||
75 | ); | ||
76 | check_action("m<|>od {}", "<|>pub(crate) mod {}", |file, off| { | ||
77 | change_visibility(file, off).map(|f| f()) | ||
78 | }); | ||
79 | check_action( | ||
80 | "pub(crate) f<|>n foo() {}", | ||
81 | "pub(crate) f<|>n foo() {}", | ||
82 | |file, off| change_visibility(file, off).map(|f| f()), | ||
83 | ); | 61 | ); |
84 | check_action( | 62 | check_assist(change_visibility, "m<|>od {}", "<|>pub(crate) mod {}"); |
63 | check_assist( | ||
64 | change_visibility, | ||
85 | "unsafe f<|>n foo() {}", | 65 | "unsafe f<|>n foo() {}", |
86 | "<|>pub(crate) unsafe fn foo() {}", | 66 | "<|>pub(crate) unsafe fn foo() {}", |
87 | |file, off| change_visibility(file, off).map(|f| f()), | ||
88 | ); | 67 | ); |
89 | } | 68 | } |
90 | } | 69 | } |
diff --git a/crates/ra_editor/src/assists/flip_comma.rs b/crates/ra_editor/src/assists/flip_comma.rs index d8727db0d..a343413cc 100644 --- a/crates/ra_editor/src/assists/flip_comma.rs +++ b/crates/ra_editor/src/assists/flip_comma.rs | |||
@@ -1,45 +1,31 @@ | |||
1 | use ra_text_edit::TextEditBuilder; | ||
2 | use ra_syntax::{ | 1 | use ra_syntax::{ |
3 | algo::find_leaf_at_offset, | 2 | Direction, |
4 | Direction, SourceFileNode, | ||
5 | SyntaxKind::COMMA, | 3 | SyntaxKind::COMMA, |
6 | TextUnit, | ||
7 | }; | 4 | }; |
8 | 5 | ||
9 | use crate::assists::{LocalEdit, non_trivia_sibling}; | 6 | use crate::assists::{non_trivia_sibling, AssistCtx, Assist}; |
10 | 7 | ||
11 | pub fn flip_comma<'a>( | 8 | pub fn flip_comma(ctx: AssistCtx) -> Option<Assist> { |
12 | file: &'a SourceFileNode, | 9 | let comma = ctx.leaf_at_offset().find(|leaf| leaf.kind() == COMMA)?; |
13 | offset: TextUnit, | ||
14 | ) -> Option<impl FnOnce() -> LocalEdit + 'a> { | ||
15 | let syntax = file.syntax(); | ||
16 | |||
17 | let comma = find_leaf_at_offset(syntax, offset).find(|leaf| leaf.kind() == COMMA)?; | ||
18 | let prev = non_trivia_sibling(comma, Direction::Prev)?; | 10 | let prev = non_trivia_sibling(comma, Direction::Prev)?; |
19 | let next = non_trivia_sibling(comma, Direction::Next)?; | 11 | let next = non_trivia_sibling(comma, Direction::Next)?; |
20 | Some(move || { | 12 | ctx.build("flip comma", |edit| { |
21 | let mut edit = TextEditBuilder::new(); | 13 | edit.replace(prev.range(), next.text()); |
22 | edit.replace(prev.range(), next.text().to_string()); | 14 | edit.replace(next.range(), prev.text()); |
23 | edit.replace(next.range(), prev.text().to_string()); | ||
24 | LocalEdit { | ||
25 | label: "flip comma".to_string(), | ||
26 | edit: edit.finish(), | ||
27 | cursor_position: None, | ||
28 | } | ||
29 | }) | 15 | }) |
30 | } | 16 | } |
31 | 17 | ||
32 | #[cfg(test)] | 18 | #[cfg(test)] |
33 | mod tests { | 19 | mod tests { |
34 | use super::*; | 20 | use super::*; |
35 | use crate::test_utils::check_action; | 21 | use crate::assists::check_assist; |
36 | 22 | ||
37 | #[test] | 23 | #[test] |
38 | fn test_swap_comma() { | 24 | fn flip_comma_works_for_function_parameters() { |
39 | check_action( | 25 | check_assist( |
26 | flip_comma, | ||
40 | "fn foo(x: i32,<|> y: Result<(), ()>) {}", | 27 | "fn foo(x: i32,<|> y: Result<(), ()>) {}", |
41 | "fn foo(y: Result<(), ()>,<|> x: i32) {}", | 28 | "fn foo(y: Result<(), ()>,<|> x: i32) {}", |
42 | |file, off| flip_comma(file, off).map(|f| f()), | ||
43 | ) | 29 | ) |
44 | } | 30 | } |
45 | } | 31 | } |
diff --git a/crates/ra_editor/src/assists/introduce_variable.rs b/crates/ra_editor/src/assists/introduce_variable.rs index 17ab521fa..782861023 100644 --- a/crates/ra_editor/src/assists/introduce_variable.rs +++ b/crates/ra_editor/src/assists/introduce_variable.rs | |||
@@ -1,19 +1,13 @@ | |||
1 | use ra_text_edit::TextEditBuilder; | ||
2 | use ra_syntax::{ | 1 | use ra_syntax::{ |
3 | algo::{find_covering_node}, | ||
4 | ast::{self, AstNode}, | 2 | ast::{self, AstNode}, |
5 | SourceFileNode, | 3 | SyntaxKind::WHITESPACE, |
6 | SyntaxKind::{WHITESPACE}, | 4 | SyntaxNodeRef, TextUnit, |
7 | SyntaxNodeRef, TextRange, TextUnit, | ||
8 | }; | 5 | }; |
9 | 6 | ||
10 | use crate::assists::LocalEdit; | 7 | use crate::assists::{AssistCtx, Assist}; |
11 | 8 | ||
12 | pub fn introduce_variable<'a>( | 9 | pub fn introduce_variable<'a>(ctx: AssistCtx) -> Option<Assist> { |
13 | file: &'a SourceFileNode, | 10 | let node = ctx.covering_node(); |
14 | range: TextRange, | ||
15 | ) -> Option<impl FnOnce() -> LocalEdit + 'a> { | ||
16 | let node = find_covering_node(file.syntax(), range); | ||
17 | let expr = node.ancestors().filter_map(ast::Expr::cast).next()?; | 11 | let expr = node.ancestors().filter_map(ast::Expr::cast).next()?; |
18 | 12 | ||
19 | let anchor_stmt = anchor_stmt(expr)?; | 13 | let anchor_stmt = anchor_stmt(expr)?; |
@@ -21,9 +15,8 @@ pub fn introduce_variable<'a>( | |||
21 | if indent.kind() != WHITESPACE { | 15 | if indent.kind() != WHITESPACE { |
22 | return None; | 16 | return None; |
23 | } | 17 | } |
24 | return Some(move || { | 18 | ctx.build("introduce variable", move |edit| { |
25 | let mut buf = String::new(); | 19 | let mut buf = String::new(); |
26 | let mut edit = TextEditBuilder::new(); | ||
27 | 20 | ||
28 | buf.push_str("let var_name = "); | 21 | buf.push_str("let var_name = "); |
29 | expr.syntax().text().push_to(&mut buf); | 22 | expr.syntax().text().push_to(&mut buf); |
@@ -40,43 +33,39 @@ pub fn introduce_variable<'a>( | |||
40 | edit.replace(expr.syntax().range(), "var_name".to_string()); | 33 | edit.replace(expr.syntax().range(), "var_name".to_string()); |
41 | edit.insert(anchor_stmt.range().start(), buf); | 34 | edit.insert(anchor_stmt.range().start(), buf); |
42 | } | 35 | } |
43 | let cursor_position = anchor_stmt.range().start() + TextUnit::of_str("let "); | 36 | edit.set_cursor(anchor_stmt.range().start() + TextUnit::of_str("let ")); |
44 | LocalEdit { | 37 | }) |
45 | label: "introduce variable".to_string(), | 38 | } |
46 | edit: edit.finish(), | ||
47 | cursor_position: Some(cursor_position), | ||
48 | } | ||
49 | }); | ||
50 | 39 | ||
51 | /// Statement or last in the block expression, which will follow | 40 | /// Statement or last in the block expression, which will follow |
52 | /// the freshly introduced var. | 41 | /// the freshly introduced var. |
53 | fn anchor_stmt(expr: ast::Expr) -> Option<SyntaxNodeRef> { | 42 | fn anchor_stmt(expr: ast::Expr) -> Option<SyntaxNodeRef> { |
54 | expr.syntax().ancestors().find(|&node| { | 43 | expr.syntax().ancestors().find(|&node| { |
55 | if ast::Stmt::cast(node).is_some() { | 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 { | ||
56 | return true; | 53 | return true; |
57 | } | 54 | } |
58 | if let Some(expr) = node | 55 | } |
59 | .parent() | 56 | false |
60 | .and_then(ast::Block::cast) | 57 | }) |
61 | .and_then(|it| it.expr()) | ||
62 | { | ||
63 | if expr.syntax() == node { | ||
64 | return true; | ||
65 | } | ||
66 | } | ||
67 | false | ||
68 | }) | ||
69 | } | ||
70 | } | 58 | } |
71 | 59 | ||
72 | #[cfg(test)] | 60 | #[cfg(test)] |
73 | mod tests { | 61 | mod tests { |
74 | use super::*; | 62 | use super::*; |
75 | use crate::test_utils::check_action_range; | 63 | use crate::assists::check_assist_range; |
76 | 64 | ||
77 | #[test] | 65 | #[test] |
78 | fn test_introduce_var_simple() { | 66 | fn test_introduce_var_simple() { |
79 | check_action_range( | 67 | check_assist_range( |
68 | introduce_variable, | ||
80 | " | 69 | " |
81 | fn foo() { | 70 | fn foo() { |
82 | foo(<|>1 + 1<|>); | 71 | foo(<|>1 + 1<|>); |
@@ -86,13 +75,13 @@ fn foo() { | |||
86 | let <|>var_name = 1 + 1; | 75 | let <|>var_name = 1 + 1; |
87 | foo(var_name); | 76 | foo(var_name); |
88 | }", | 77 | }", |
89 | |file, range| introduce_variable(file, range).map(|f| f()), | ||
90 | ); | 78 | ); |
91 | } | 79 | } |
92 | 80 | ||
93 | #[test] | 81 | #[test] |
94 | fn test_introduce_var_expr_stmt() { | 82 | fn test_introduce_var_expr_stmt() { |
95 | check_action_range( | 83 | check_assist_range( |
84 | introduce_variable, | ||
96 | " | 85 | " |
97 | fn foo() { | 86 | fn foo() { |
98 | <|>1 + 1<|>; | 87 | <|>1 + 1<|>; |
@@ -101,13 +90,13 @@ fn foo() { | |||
101 | fn foo() { | 90 | fn foo() { |
102 | let <|>var_name = 1 + 1; | 91 | let <|>var_name = 1 + 1; |
103 | }", | 92 | }", |
104 | |file, range| introduce_variable(file, range).map(|f| f()), | ||
105 | ); | 93 | ); |
106 | } | 94 | } |
107 | 95 | ||
108 | #[test] | 96 | #[test] |
109 | fn test_introduce_var_part_of_expr_stmt() { | 97 | fn test_introduce_var_part_of_expr_stmt() { |
110 | check_action_range( | 98 | check_assist_range( |
99 | introduce_variable, | ||
111 | " | 100 | " |
112 | fn foo() { | 101 | fn foo() { |
113 | <|>1<|> + 1; | 102 | <|>1<|> + 1; |
@@ -117,13 +106,13 @@ fn foo() { | |||
117 | let <|>var_name = 1; | 106 | let <|>var_name = 1; |
118 | var_name + 1; | 107 | var_name + 1; |
119 | }", | 108 | }", |
120 | |file, range| introduce_variable(file, range).map(|f| f()), | ||
121 | ); | 109 | ); |
122 | } | 110 | } |
123 | 111 | ||
124 | #[test] | 112 | #[test] |
125 | fn test_introduce_var_last_expr() { | 113 | fn test_introduce_var_last_expr() { |
126 | check_action_range( | 114 | check_assist_range( |
115 | introduce_variable, | ||
127 | " | 116 | " |
128 | fn foo() { | 117 | fn foo() { |
129 | bar(<|>1 + 1<|>) | 118 | bar(<|>1 + 1<|>) |
@@ -133,13 +122,13 @@ fn foo() { | |||
133 | let <|>var_name = 1 + 1; | 122 | let <|>var_name = 1 + 1; |
134 | bar(var_name) | 123 | bar(var_name) |
135 | }", | 124 | }", |
136 | |file, range| introduce_variable(file, range).map(|f| f()), | ||
137 | ); | 125 | ); |
138 | } | 126 | } |
139 | 127 | ||
140 | #[test] | 128 | #[test] |
141 | fn test_introduce_var_last_full_expr() { | 129 | fn test_introduce_var_last_full_expr() { |
142 | check_action_range( | 130 | check_assist_range( |
131 | introduce_variable, | ||
143 | " | 132 | " |
144 | fn foo() { | 133 | fn foo() { |
145 | <|>bar(1 + 1)<|> | 134 | <|>bar(1 + 1)<|> |
@@ -149,7 +138,6 @@ fn foo() { | |||
149 | let <|>var_name = bar(1 + 1); | 138 | let <|>var_name = bar(1 + 1); |
150 | var_name | 139 | var_name |
151 | }", | 140 | }", |
152 | |file, range| introduce_variable(file, range).map(|f| f()), | ||
153 | ); | 141 | ); |
154 | } | 142 | } |
155 | 143 | ||
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/typing.rs b/crates/ra_editor/src/typing.rs index 21d068a7b..dd3d0f260 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(), |