diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-01-15 18:43:23 +0000 |
---|---|---|
committer | GitHub <[email protected]> | 2020-01-15 18:43:23 +0000 |
commit | aa2e13b37f4508168fb064a79d0190fa705d8a47 (patch) | |
tree | 15d4b618885813c2c9efadd2ea0d25a7173807c8 /crates/ra_assists/src | |
parent | 01422cc31d1917aaef4b1f402eda05abfff1e75f (diff) | |
parent | 79b77403b65877e4d20bbbac6dd853a3beead445 (diff) |
Merge #2716
2716: Allow assists with multiple selectable actions r=SomeoneToIgnore a=SomeoneToIgnore
This PR prepares an infra for https://github.com/rust-analyzer/rust-analyzer/issues/2180 task by adding a possibility to specify multiple actions in one assist as multiple edit parameters to the `applySourceChange` command.
When this is done, the command opens a selection dialog, allowing the user to pick the edit to be applied.
I have no working example to test in this PR, but here's a demo of an auto import feature (a separate PR coming later for that one) using this functionality:
![out](https://user-images.githubusercontent.com/2690773/71633614-f8ea4d80-2c1d-11ea-9b15-0e13611a7aa4.gif)
The PR is not that massive as it may seem: all the assist files' changes are very generic and similar.
Co-authored-by: Kirill Bulatov <[email protected]>
Diffstat (limited to 'crates/ra_assists/src')
-rw-r--r-- | crates/ra_assists/src/assist_ctx.rs | 50 | ||||
-rw-r--r-- | crates/ra_assists/src/assists/inline_local_variable.rs | 4 | ||||
-rw-r--r-- | crates/ra_assists/src/doc_tests.rs | 8 | ||||
-rw-r--r-- | crates/ra_assists/src/lib.rs | 39 |
4 files changed, 77 insertions, 24 deletions
diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_ctx.rs index 1a65b5dc0..9d533fa0c 100644 --- a/crates/ra_assists/src/assist_ctx.rs +++ b/crates/ra_assists/src/assist_ctx.rs | |||
@@ -1,4 +1,5 @@ | |||
1 | //! This module defines `AssistCtx` -- the API surface that is exposed to assists. | 1 | //! This module defines `AssistCtx` -- the API surface that is exposed to assists. |
2 | use either::Either; | ||
2 | use hir::{db::HirDatabase, InFile, SourceAnalyzer}; | 3 | use hir::{db::HirDatabase, InFile, SourceAnalyzer}; |
3 | use ra_db::FileRange; | 4 | use ra_db::FileRange; |
4 | use ra_fmt::{leading_indent, reindent}; | 5 | use ra_fmt::{leading_indent, reindent}; |
@@ -9,12 +10,12 @@ use ra_syntax::{ | |||
9 | }; | 10 | }; |
10 | use ra_text_edit::TextEditBuilder; | 11 | use ra_text_edit::TextEditBuilder; |
11 | 12 | ||
12 | use crate::{AssistAction, AssistId, AssistLabel}; | 13 | use crate::{AssistAction, AssistId, AssistLabel, ResolvedAssist}; |
13 | 14 | ||
14 | #[derive(Clone, Debug)] | 15 | #[derive(Clone, Debug)] |
15 | pub(crate) enum Assist { | 16 | pub(crate) enum Assist { |
16 | Unresolved { label: AssistLabel }, | 17 | Unresolved { label: AssistLabel }, |
17 | Resolved { label: AssistLabel, action: AssistAction }, | 18 | Resolved { assist: ResolvedAssist }, |
18 | } | 19 | } |
19 | 20 | ||
20 | /// `AssistCtx` allows to apply an assist or check if it could be applied. | 21 | /// `AssistCtx` allows to apply an assist or check if it could be applied. |
@@ -81,18 +82,45 @@ impl<'a, DB: HirDatabase> AssistCtx<'a, DB> { | |||
81 | self, | 82 | self, |
82 | id: AssistId, | 83 | id: AssistId, |
83 | label: impl Into<String>, | 84 | label: impl Into<String>, |
84 | f: impl FnOnce(&mut AssistBuilder), | 85 | f: impl FnOnce(&mut ActionBuilder), |
85 | ) -> Option<Assist> { | 86 | ) -> Option<Assist> { |
86 | let label = AssistLabel { label: label.into(), id }; | 87 | let label = AssistLabel { label: label.into(), id }; |
87 | assert!(label.label.chars().nth(0).unwrap().is_uppercase()); | 88 | assert!(label.label.chars().nth(0).unwrap().is_uppercase()); |
88 | 89 | ||
89 | let assist = if self.should_compute_edit { | 90 | let assist = if self.should_compute_edit { |
90 | let action = { | 91 | let action = { |
91 | let mut edit = AssistBuilder::default(); | 92 | let mut edit = ActionBuilder::default(); |
92 | f(&mut edit); | 93 | f(&mut edit); |
93 | edit.build() | 94 | edit.build() |
94 | }; | 95 | }; |
95 | Assist::Resolved { label, action } | 96 | Assist::Resolved { assist: ResolvedAssist { label, action_data: Either::Left(action) } } |
97 | } else { | ||
98 | Assist::Unresolved { label } | ||
99 | }; | ||
100 | |||
101 | Some(assist) | ||
102 | } | ||
103 | |||
104 | #[allow(dead_code)] // will be used for auto import assist with multiple actions | ||
105 | pub(crate) fn add_assist_group( | ||
106 | self, | ||
107 | id: AssistId, | ||
108 | label: impl Into<String>, | ||
109 | f: impl FnOnce() -> Vec<ActionBuilder>, | ||
110 | ) -> Option<Assist> { | ||
111 | let label = AssistLabel { label: label.into(), id }; | ||
112 | let assist = if self.should_compute_edit { | ||
113 | let actions = f(); | ||
114 | assert!(!actions.is_empty(), "Assist cannot have no"); | ||
115 | |||
116 | Assist::Resolved { | ||
117 | assist: ResolvedAssist { | ||
118 | label, | ||
119 | action_data: Either::Right( | ||
120 | actions.into_iter().map(ActionBuilder::build).collect(), | ||
121 | ), | ||
122 | }, | ||
123 | } | ||
96 | } else { | 124 | } else { |
97 | Assist::Unresolved { label } | 125 | Assist::Unresolved { label } |
98 | }; | 126 | }; |
@@ -128,13 +156,20 @@ impl<'a, DB: HirDatabase> AssistCtx<'a, DB> { | |||
128 | } | 156 | } |
129 | 157 | ||
130 | #[derive(Default)] | 158 | #[derive(Default)] |
131 | pub(crate) struct AssistBuilder { | 159 | pub(crate) struct ActionBuilder { |
132 | edit: TextEditBuilder, | 160 | edit: TextEditBuilder, |
133 | cursor_position: Option<TextUnit>, | 161 | cursor_position: Option<TextUnit>, |
134 | target: Option<TextRange>, | 162 | target: Option<TextRange>, |
163 | label: Option<String>, | ||
135 | } | 164 | } |
136 | 165 | ||
137 | impl AssistBuilder { | 166 | impl ActionBuilder { |
167 | #[allow(dead_code)] | ||
168 | /// Adds a custom label to the action, if it needs to be different from the assist label | ||
169 | pub(crate) fn label(&mut self, label: impl Into<String>) { | ||
170 | self.label = Some(label.into()) | ||
171 | } | ||
172 | |||
138 | /// Replaces specified `range` of text with a given string. | 173 | /// Replaces specified `range` of text with a given string. |
139 | pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) { | 174 | pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) { |
140 | self.edit.replace(range, replace_with.into()) | 175 | self.edit.replace(range, replace_with.into()) |
@@ -193,6 +228,7 @@ impl AssistBuilder { | |||
193 | edit: self.edit.finish(), | 228 | edit: self.edit.finish(), |
194 | cursor_position: self.cursor_position, | 229 | cursor_position: self.cursor_position, |
195 | target: self.target, | 230 | target: self.target, |
231 | label: self.label, | ||
196 | } | 232 | } |
197 | } | 233 | } |
198 | } | 234 | } |
diff --git a/crates/ra_assists/src/assists/inline_local_variable.rs b/crates/ra_assists/src/assists/inline_local_variable.rs index 164aee90c..45e0f983f 100644 --- a/crates/ra_assists/src/assists/inline_local_variable.rs +++ b/crates/ra_assists/src/assists/inline_local_variable.rs | |||
@@ -4,7 +4,7 @@ use ra_syntax::{ | |||
4 | TextRange, | 4 | TextRange, |
5 | }; | 5 | }; |
6 | 6 | ||
7 | use crate::assist_ctx::AssistBuilder; | 7 | use crate::assist_ctx::ActionBuilder; |
8 | use crate::{Assist, AssistCtx, AssistId}; | 8 | use crate::{Assist, AssistCtx, AssistId}; |
9 | 9 | ||
10 | // Assist: inline_local_variable | 10 | // Assist: inline_local_variable |
@@ -94,7 +94,7 @@ pub(crate) fn inline_local_varialbe(ctx: AssistCtx<impl HirDatabase>) -> Option< | |||
94 | ctx.add_assist( | 94 | ctx.add_assist( |
95 | AssistId("inline_local_variable"), | 95 | AssistId("inline_local_variable"), |
96 | "Inline variable", | 96 | "Inline variable", |
97 | move |edit: &mut AssistBuilder| { | 97 | move |edit: &mut ActionBuilder| { |
98 | edit.delete(delete_range); | 98 | edit.delete(delete_range); |
99 | for (desc, should_wrap) in refs.iter().zip(wrap_in_parens) { | 99 | for (desc, should_wrap) in refs.iter().zip(wrap_in_parens) { |
100 | if should_wrap { | 100 | if should_wrap { |
diff --git a/crates/ra_assists/src/doc_tests.rs b/crates/ra_assists/src/doc_tests.rs index a8f8446cb..5dc1ee233 100644 --- a/crates/ra_assists/src/doc_tests.rs +++ b/crates/ra_assists/src/doc_tests.rs | |||
@@ -15,21 +15,21 @@ fn check(assist_id: &str, before: &str, after: &str) { | |||
15 | let (db, file_id) = TestDB::with_single_file(&before); | 15 | let (db, file_id) = TestDB::with_single_file(&before); |
16 | let frange = FileRange { file_id, range: selection.into() }; | 16 | let frange = FileRange { file_id, range: selection.into() }; |
17 | 17 | ||
18 | let (_assist_id, action) = crate::assists(&db, frange) | 18 | let assist = crate::assists(&db, frange) |
19 | .into_iter() | 19 | .into_iter() |
20 | .find(|(id, _)| id.id.0 == assist_id) | 20 | .find(|assist| assist.label.id.0 == assist_id) |
21 | .unwrap_or_else(|| { | 21 | .unwrap_or_else(|| { |
22 | panic!( | 22 | panic!( |
23 | "\n\nAssist is not applicable: {}\nAvailable assists: {}", | 23 | "\n\nAssist is not applicable: {}\nAvailable assists: {}", |
24 | assist_id, | 24 | assist_id, |
25 | crate::assists(&db, frange) | 25 | crate::assists(&db, frange) |
26 | .into_iter() | 26 | .into_iter() |
27 | .map(|(id, _)| id.id.0) | 27 | .map(|assist| assist.label.id.0) |
28 | .collect::<Vec<_>>() | 28 | .collect::<Vec<_>>() |
29 | .join(", ") | 29 | .join(", ") |
30 | ) | 30 | ) |
31 | }); | 31 | }); |
32 | 32 | ||
33 | let actual = action.edit.apply(&before); | 33 | let actual = assist.get_first_action().edit.apply(&before); |
34 | assert_eq_text!(after, &actual); | 34 | assert_eq_text!(after, &actual); |
35 | } | 35 | } |
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index 150b34ac7..d45b58966 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs | |||
@@ -13,6 +13,7 @@ mod doc_tests; | |||
13 | mod test_db; | 13 | mod test_db; |
14 | pub mod ast_transform; | 14 | pub mod ast_transform; |
15 | 15 | ||
16 | use either::Either; | ||
16 | use hir::db::HirDatabase; | 17 | use hir::db::HirDatabase; |
17 | use ra_db::FileRange; | 18 | use ra_db::FileRange; |
18 | use ra_syntax::{TextRange, TextUnit}; | 19 | use ra_syntax::{TextRange, TextUnit}; |
@@ -35,11 +36,27 @@ pub struct AssistLabel { | |||
35 | 36 | ||
36 | #[derive(Debug, Clone)] | 37 | #[derive(Debug, Clone)] |
37 | pub struct AssistAction { | 38 | pub struct AssistAction { |
39 | pub label: Option<String>, | ||
38 | pub edit: TextEdit, | 40 | pub edit: TextEdit, |
39 | pub cursor_position: Option<TextUnit>, | 41 | pub cursor_position: Option<TextUnit>, |
40 | pub target: Option<TextRange>, | 42 | pub target: Option<TextRange>, |
41 | } | 43 | } |
42 | 44 | ||
45 | #[derive(Debug, Clone)] | ||
46 | pub struct ResolvedAssist { | ||
47 | pub label: AssistLabel, | ||
48 | pub action_data: Either<AssistAction, Vec<AssistAction>>, | ||
49 | } | ||
50 | |||
51 | impl ResolvedAssist { | ||
52 | pub fn get_first_action(&self) -> AssistAction { | ||
53 | match &self.action_data { | ||
54 | Either::Left(action) => action.clone(), | ||
55 | Either::Right(actions) => actions[0].clone(), | ||
56 | } | ||
57 | } | ||
58 | } | ||
59 | |||
43 | /// Return all the assists applicable at the given position. | 60 | /// Return all the assists applicable at the given position. |
44 | /// | 61 | /// |
45 | /// Assists are returned in the "unresolved" state, that is only labels are | 62 | /// Assists are returned in the "unresolved" state, that is only labels are |
@@ -64,7 +81,7 @@ where | |||
64 | /// | 81 | /// |
65 | /// Assists are returned in the "resolved" state, that is with edit fully | 82 | /// Assists are returned in the "resolved" state, that is with edit fully |
66 | /// computed. | 83 | /// computed. |
67 | pub fn assists<H>(db: &H, range: FileRange) -> Vec<(AssistLabel, AssistAction)> | 84 | pub fn assists<H>(db: &H, range: FileRange) -> Vec<ResolvedAssist> |
68 | where | 85 | where |
69 | H: HirDatabase + 'static, | 86 | H: HirDatabase + 'static, |
70 | { | 87 | { |
@@ -75,11 +92,11 @@ where | |||
75 | .iter() | 92 | .iter() |
76 | .filter_map(|f| f(ctx.clone())) | 93 | .filter_map(|f| f(ctx.clone())) |
77 | .map(|a| match a { | 94 | .map(|a| match a { |
78 | Assist::Resolved { label, action } => (label, action), | 95 | Assist::Resolved { assist } => assist, |
79 | Assist::Unresolved { .. } => unreachable!(), | 96 | Assist::Unresolved { .. } => unreachable!(), |
80 | }) | 97 | }) |
81 | .collect::<Vec<_>>(); | 98 | .collect::<Vec<_>>(); |
82 | a.sort_by(|a, b| match (a.1.target, b.1.target) { | 99 | a.sort_by(|a, b| match (a.get_first_action().target, b.get_first_action().target) { |
83 | (Some(a), Some(b)) => a.len().cmp(&b.len()), | 100 | (Some(a), Some(b)) => a.len().cmp(&b.len()), |
84 | (Some(_), None) => Ordering::Less, | 101 | (Some(_), None) => Ordering::Less, |
85 | (None, Some(_)) => Ordering::Greater, | 102 | (None, Some(_)) => Ordering::Greater, |
@@ -174,7 +191,7 @@ mod helpers { | |||
174 | AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); | 191 | AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); |
175 | let action = match assist { | 192 | let action = match assist { |
176 | Assist::Unresolved { .. } => unreachable!(), | 193 | Assist::Unresolved { .. } => unreachable!(), |
177 | Assist::Resolved { action, .. } => action, | 194 | Assist::Resolved { assist } => assist.get_first_action(), |
178 | }; | 195 | }; |
179 | 196 | ||
180 | let actual = action.edit.apply(&before); | 197 | let actual = action.edit.apply(&before); |
@@ -201,7 +218,7 @@ mod helpers { | |||
201 | AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); | 218 | AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); |
202 | let action = match assist { | 219 | let action = match assist { |
203 | Assist::Unresolved { .. } => unreachable!(), | 220 | Assist::Unresolved { .. } => unreachable!(), |
204 | Assist::Resolved { action, .. } => action, | 221 | Assist::Resolved { assist } => assist.get_first_action(), |
205 | }; | 222 | }; |
206 | 223 | ||
207 | let mut actual = action.edit.apply(&before); | 224 | let mut actual = action.edit.apply(&before); |
@@ -224,7 +241,7 @@ mod helpers { | |||
224 | AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); | 241 | AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); |
225 | let action = match assist { | 242 | let action = match assist { |
226 | Assist::Unresolved { .. } => unreachable!(), | 243 | Assist::Unresolved { .. } => unreachable!(), |
227 | Assist::Resolved { action, .. } => action, | 244 | Assist::Resolved { assist } => assist.get_first_action(), |
228 | }; | 245 | }; |
229 | 246 | ||
230 | let range = action.target.expect("expected target on action"); | 247 | let range = action.target.expect("expected target on action"); |
@@ -243,7 +260,7 @@ mod helpers { | |||
243 | AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); | 260 | AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); |
244 | let action = match assist { | 261 | let action = match assist { |
245 | Assist::Unresolved { .. } => unreachable!(), | 262 | Assist::Unresolved { .. } => unreachable!(), |
246 | Assist::Resolved { action, .. } => action, | 263 | Assist::Resolved { assist } => assist.get_first_action(), |
247 | }; | 264 | }; |
248 | 265 | ||
249 | let range = action.target.expect("expected target on action"); | 266 | let range = action.target.expect("expected target on action"); |
@@ -293,10 +310,10 @@ mod tests { | |||
293 | let mut assists = assists.iter(); | 310 | let mut assists = assists.iter(); |
294 | 311 | ||
295 | assert_eq!( | 312 | assert_eq!( |
296 | assists.next().expect("expected assist").0.label, | 313 | assists.next().expect("expected assist").label.label, |
297 | "Change visibility to pub(crate)" | 314 | "Change visibility to pub(crate)" |
298 | ); | 315 | ); |
299 | assert_eq!(assists.next().expect("expected assist").0.label, "Add `#[derive]`"); | 316 | assert_eq!(assists.next().expect("expected assist").label.label, "Add `#[derive]`"); |
300 | } | 317 | } |
301 | 318 | ||
302 | #[test] | 319 | #[test] |
@@ -315,7 +332,7 @@ mod tests { | |||
315 | let assists = super::assists(&db, frange); | 332 | let assists = super::assists(&db, frange); |
316 | let mut assists = assists.iter(); | 333 | let mut assists = assists.iter(); |
317 | 334 | ||
318 | assert_eq!(assists.next().expect("expected assist").0.label, "Extract into variable"); | 335 | assert_eq!(assists.next().expect("expected assist").label.label, "Extract into variable"); |
319 | assert_eq!(assists.next().expect("expected assist").0.label, "Replace with match"); | 336 | assert_eq!(assists.next().expect("expected assist").label.label, "Replace with match"); |
320 | } | 337 | } |
321 | } | 338 | } |