aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-01-15 18:43:23 +0000
committerGitHub <[email protected]>2020-01-15 18:43:23 +0000
commitaa2e13b37f4508168fb064a79d0190fa705d8a47 (patch)
tree15d4b618885813c2c9efadd2ea0d25a7173807c8 /crates/ra_assists/src
parent01422cc31d1917aaef4b1f402eda05abfff1e75f (diff)
parent79b77403b65877e4d20bbbac6dd853a3beead445 (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.rs50
-rw-r--r--crates/ra_assists/src/assists/inline_local_variable.rs4
-rw-r--r--crates/ra_assists/src/doc_tests.rs8
-rw-r--r--crates/ra_assists/src/lib.rs39
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.
2use either::Either;
2use hir::{db::HirDatabase, InFile, SourceAnalyzer}; 3use hir::{db::HirDatabase, InFile, SourceAnalyzer};
3use ra_db::FileRange; 4use ra_db::FileRange;
4use ra_fmt::{leading_indent, reindent}; 5use ra_fmt::{leading_indent, reindent};
@@ -9,12 +10,12 @@ use ra_syntax::{
9}; 10};
10use ra_text_edit::TextEditBuilder; 11use ra_text_edit::TextEditBuilder;
11 12
12use crate::{AssistAction, AssistId, AssistLabel}; 13use crate::{AssistAction, AssistId, AssistLabel, ResolvedAssist};
13 14
14#[derive(Clone, Debug)] 15#[derive(Clone, Debug)]
15pub(crate) enum Assist { 16pub(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)]
131pub(crate) struct AssistBuilder { 159pub(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
137impl AssistBuilder { 166impl 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
7use crate::assist_ctx::AssistBuilder; 7use crate::assist_ctx::ActionBuilder;
8use crate::{Assist, AssistCtx, AssistId}; 8use 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;
13mod test_db; 13mod test_db;
14pub mod ast_transform; 14pub mod ast_transform;
15 15
16use either::Either;
16use hir::db::HirDatabase; 17use hir::db::HirDatabase;
17use ra_db::FileRange; 18use ra_db::FileRange;
18use ra_syntax::{TextRange, TextUnit}; 19use ra_syntax::{TextRange, TextUnit};
@@ -35,11 +36,27 @@ pub struct AssistLabel {
35 36
36#[derive(Debug, Clone)] 37#[derive(Debug, Clone)]
37pub struct AssistAction { 38pub 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)]
46pub struct ResolvedAssist {
47 pub label: AssistLabel,
48 pub action_data: Either<AssistAction, Vec<AssistAction>>,
49}
50
51impl 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.
67pub fn assists<H>(db: &H, range: FileRange) -> Vec<(AssistLabel, AssistAction)> 84pub fn assists<H>(db: &H, range: FileRange) -> Vec<ResolvedAssist>
68where 85where
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}