diff options
author | Aleksey Kladov <[email protected]> | 2020-02-09 14:32:53 +0000 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2020-02-09 15:03:54 +0000 |
commit | 9769c5140c9c406a4cc880e698593a6c4bcc6826 (patch) | |
tree | bb0bf39dee21d5720e5f1070b09805dfbc120cd9 /crates/ra_assists/src | |
parent | fb99831cb044e5fbf171bdd950a7486a01ce0d51 (diff) |
Simplify Assists interface
Instead of building a physical tree structure, just "tag" related
assists with the same group
Diffstat (limited to 'crates/ra_assists/src')
-rw-r--r-- | crates/ra_assists/src/assist_ctx.rs | 83 | ||||
-rw-r--r-- | crates/ra_assists/src/doc_tests.rs | 2 | ||||
-rw-r--r-- | crates/ra_assists/src/lib.rs | 64 |
3 files changed, 54 insertions, 95 deletions
diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_ctx.rs index 5924a3fd5..5aab5fb8b 100644 --- a/crates/ra_assists/src/assist_ctx.rs +++ b/crates/ra_assists/src/assist_ctx.rs | |||
@@ -1,5 +1,4 @@ | |||
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; | ||
3 | use hir::{InFile, SourceAnalyzer, SourceBinder}; | 2 | use hir::{InFile, SourceAnalyzer, SourceBinder}; |
4 | use ra_db::{FileRange, SourceDatabase}; | 3 | use ra_db::{FileRange, SourceDatabase}; |
5 | use ra_fmt::{leading_indent, reindent}; | 4 | use ra_fmt::{leading_indent, reindent}; |
@@ -11,12 +10,36 @@ use ra_syntax::{ | |||
11 | }; | 10 | }; |
12 | use ra_text_edit::TextEditBuilder; | 11 | use ra_text_edit::TextEditBuilder; |
13 | 12 | ||
14 | use crate::{AssistAction, AssistId, AssistLabel, ResolvedAssist}; | 13 | use crate::{AssistAction, AssistId, AssistLabel, GroupLabel, ResolvedAssist}; |
15 | 14 | ||
16 | #[derive(Clone, Debug)] | 15 | #[derive(Clone, Debug)] |
17 | pub(crate) enum Assist { | 16 | pub(crate) struct Assist(pub(crate) Vec<AssistInfo>); |
18 | Unresolved { label: AssistLabel }, | 17 | |
19 | Resolved { assist: ResolvedAssist }, | 18 | #[derive(Clone, Debug)] |
19 | pub(crate) struct AssistInfo { | ||
20 | pub(crate) label: AssistLabel, | ||
21 | pub(crate) group_label: Option<GroupLabel>, | ||
22 | pub(crate) action: Option<AssistAction>, | ||
23 | } | ||
24 | |||
25 | impl AssistInfo { | ||
26 | fn new(label: AssistLabel) -> AssistInfo { | ||
27 | AssistInfo { label, group_label: None, action: None } | ||
28 | } | ||
29 | |||
30 | fn resolved(self, action: AssistAction) -> AssistInfo { | ||
31 | AssistInfo { action: Some(action), ..self } | ||
32 | } | ||
33 | |||
34 | fn with_group(self, group_label: GroupLabel) -> AssistInfo { | ||
35 | AssistInfo { group_label: Some(group_label), ..self } | ||
36 | } | ||
37 | |||
38 | pub(crate) fn into_resolved(self) -> Option<ResolvedAssist> { | ||
39 | let label = self.label; | ||
40 | let group_label = self.group_label; | ||
41 | self.action.map(|action| ResolvedAssist { label, group_label, action }) | ||
42 | } | ||
20 | } | 43 | } |
21 | 44 | ||
22 | pub(crate) type AssistHandler = fn(AssistCtx) -> Option<Assist>; | 45 | pub(crate) type AssistHandler = fn(AssistCtx) -> Option<Assist>; |
@@ -84,18 +107,17 @@ impl<'a> AssistCtx<'a> { | |||
84 | ) -> Option<Assist> { | 107 | ) -> Option<Assist> { |
85 | let label = AssistLabel::new(label.into(), id); | 108 | let label = AssistLabel::new(label.into(), id); |
86 | 109 | ||
87 | let assist = if self.should_compute_edit { | 110 | let mut info = AssistInfo::new(label); |
111 | if self.should_compute_edit { | ||
88 | let action = { | 112 | let action = { |
89 | let mut edit = ActionBuilder::default(); | 113 | let mut edit = ActionBuilder::default(); |
90 | f(&mut edit); | 114 | f(&mut edit); |
91 | edit.build() | 115 | edit.build() |
92 | }; | 116 | }; |
93 | Assist::Resolved { assist: ResolvedAssist { label, action_data: Either::Left(action) } } | 117 | info = info.resolved(action) |
94 | } else { | ||
95 | Assist::Unresolved { label } | ||
96 | }; | 118 | }; |
97 | 119 | ||
98 | Some(assist) | 120 | Some(Assist(vec![info])) |
99 | } | 121 | } |
100 | 122 | ||
101 | pub(crate) fn add_assist_group(self, group_name: impl Into<String>) -> AssistGroup<'a> { | 123 | pub(crate) fn add_assist_group(self, group_name: impl Into<String>) -> AssistGroup<'a> { |
@@ -136,7 +158,7 @@ impl<'a> AssistCtx<'a> { | |||
136 | pub(crate) struct AssistGroup<'a> { | 158 | pub(crate) struct AssistGroup<'a> { |
137 | ctx: AssistCtx<'a>, | 159 | ctx: AssistCtx<'a>, |
138 | group_name: String, | 160 | group_name: String, |
139 | assists: Vec<Assist>, | 161 | assists: Vec<AssistInfo>, |
140 | } | 162 | } |
141 | 163 | ||
142 | impl<'a> AssistGroup<'a> { | 164 | impl<'a> AssistGroup<'a> { |
@@ -148,49 +170,22 @@ impl<'a> AssistGroup<'a> { | |||
148 | ) { | 170 | ) { |
149 | let label = AssistLabel::new(label.into(), id); | 171 | let label = AssistLabel::new(label.into(), id); |
150 | 172 | ||
151 | let assist = if self.ctx.should_compute_edit { | 173 | let mut info = AssistInfo::new(label).with_group(GroupLabel(self.group_name.clone())); |
174 | if self.ctx.should_compute_edit { | ||
152 | let action = { | 175 | let action = { |
153 | let mut edit = ActionBuilder::default(); | 176 | let mut edit = ActionBuilder::default(); |
154 | f(&mut edit); | 177 | f(&mut edit); |
155 | edit.build() | 178 | edit.build() |
156 | }; | 179 | }; |
157 | Assist::Resolved { assist: ResolvedAssist { label, action_data: Either::Left(action) } } | 180 | info = info.resolved(action) |
158 | } else { | ||
159 | Assist::Unresolved { label } | ||
160 | }; | 181 | }; |
161 | 182 | ||
162 | self.assists.push(assist) | 183 | self.assists.push(info) |
163 | } | 184 | } |
164 | 185 | ||
165 | pub(crate) fn finish(self) -> Option<Assist> { | 186 | pub(crate) fn finish(self) -> Option<Assist> { |
166 | assert!(!self.assists.is_empty()); | 187 | assert!(!self.assists.is_empty()); |
167 | let mut label = match &self.assists[0] { | 188 | Some(Assist(self.assists)) |
168 | Assist::Unresolved { label } => label.clone(), | ||
169 | Assist::Resolved { assist } => assist.label.clone(), | ||
170 | }; | ||
171 | label.label = self.group_name; | ||
172 | let assist = if self.ctx.should_compute_edit { | ||
173 | Assist::Resolved { | ||
174 | assist: ResolvedAssist { | ||
175 | label, | ||
176 | action_data: Either::Right( | ||
177 | self.assists | ||
178 | .into_iter() | ||
179 | .map(|assist| match assist { | ||
180 | Assist::Resolved { | ||
181 | assist: | ||
182 | ResolvedAssist { label: _, action_data: Either::Left(it) }, | ||
183 | } => it, | ||
184 | _ => unreachable!(), | ||
185 | }) | ||
186 | .collect(), | ||
187 | ), | ||
188 | }, | ||
189 | } | ||
190 | } else { | ||
191 | Assist::Unresolved { label } | ||
192 | }; | ||
193 | Some(assist) | ||
194 | } | 189 | } |
195 | } | 190 | } |
196 | 191 | ||
@@ -199,7 +194,6 @@ pub(crate) struct ActionBuilder { | |||
199 | edit: TextEditBuilder, | 194 | edit: TextEditBuilder, |
200 | cursor_position: Option<TextUnit>, | 195 | cursor_position: Option<TextUnit>, |
201 | target: Option<TextRange>, | 196 | target: Option<TextRange>, |
202 | label: Option<String>, | ||
203 | } | 197 | } |
204 | 198 | ||
205 | impl ActionBuilder { | 199 | impl ActionBuilder { |
@@ -261,7 +255,6 @@ impl ActionBuilder { | |||
261 | edit: self.edit.finish(), | 255 | edit: self.edit.finish(), |
262 | cursor_position: self.cursor_position, | 256 | cursor_position: self.cursor_position, |
263 | target: self.target, | 257 | target: self.target, |
264 | label: self.label, | ||
265 | } | 258 | } |
266 | } | 259 | } |
267 | } | 260 | } |
diff --git a/crates/ra_assists/src/doc_tests.rs b/crates/ra_assists/src/doc_tests.rs index ae0e5605c..c0f9bc1fb 100644 --- a/crates/ra_assists/src/doc_tests.rs +++ b/crates/ra_assists/src/doc_tests.rs | |||
@@ -30,6 +30,6 @@ fn check(assist_id: &str, before: &str, after: &str) { | |||
30 | ) | 30 | ) |
31 | }); | 31 | }); |
32 | 32 | ||
33 | let actual = assist.get_first_action().edit.apply(&before); | 33 | let actual = assist.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 f79189ae8..828a8e9e8 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs | |||
@@ -12,9 +12,6 @@ mod doc_tests; | |||
12 | mod utils; | 12 | mod utils; |
13 | pub mod ast_transform; | 13 | pub mod ast_transform; |
14 | 14 | ||
15 | use std::cmp::Ordering; | ||
16 | |||
17 | use either::Either; | ||
18 | use ra_db::FileRange; | 15 | use ra_db::FileRange; |
19 | use ra_ide_db::RootDatabase; | 16 | use ra_ide_db::RootDatabase; |
20 | use ra_syntax::{TextRange, TextUnit}; | 17 | use ra_syntax::{TextRange, TextUnit}; |
@@ -35,6 +32,9 @@ pub struct AssistLabel { | |||
35 | pub id: AssistId, | 32 | pub id: AssistId, |
36 | } | 33 | } |
37 | 34 | ||
35 | #[derive(Clone, Debug)] | ||
36 | pub struct GroupLabel(pub String); | ||
37 | |||
38 | impl AssistLabel { | 38 | impl AssistLabel { |
39 | pub(crate) fn new(label: String, id: AssistId) -> AssistLabel { | 39 | pub(crate) fn new(label: String, id: AssistId) -> AssistLabel { |
40 | // FIXME: make fields private, so that this invariant can't be broken | 40 | // FIXME: make fields private, so that this invariant can't be broken |
@@ -45,7 +45,6 @@ impl AssistLabel { | |||
45 | 45 | ||
46 | #[derive(Debug, Clone)] | 46 | #[derive(Debug, Clone)] |
47 | pub struct AssistAction { | 47 | pub struct AssistAction { |
48 | pub label: Option<String>, | ||
49 | pub edit: TextEdit, | 48 | pub edit: TextEdit, |
50 | pub cursor_position: Option<TextUnit>, | 49 | pub cursor_position: Option<TextUnit>, |
51 | // FIXME: This belongs to `AssistLabel` | 50 | // FIXME: This belongs to `AssistLabel` |
@@ -55,16 +54,8 @@ pub struct AssistAction { | |||
55 | #[derive(Debug, Clone)] | 54 | #[derive(Debug, Clone)] |
56 | pub struct ResolvedAssist { | 55 | pub struct ResolvedAssist { |
57 | pub label: AssistLabel, | 56 | pub label: AssistLabel, |
58 | pub action_data: Either<AssistAction, Vec<AssistAction>>, | 57 | pub group_label: Option<GroupLabel>, |
59 | } | 58 | pub action: AssistAction, |
60 | |||
61 | impl ResolvedAssist { | ||
62 | pub fn get_first_action(&self) -> AssistAction { | ||
63 | match &self.action_data { | ||
64 | Either::Left(action) => action.clone(), | ||
65 | Either::Right(actions) => actions[0].clone(), | ||
66 | } | ||
67 | } | ||
68 | } | 59 | } |
69 | 60 | ||
70 | /// Return all the assists applicable at the given position. | 61 | /// Return all the assists applicable at the given position. |
@@ -76,10 +67,8 @@ pub fn unresolved_assists(db: &RootDatabase, range: FileRange) -> Vec<AssistLabe | |||
76 | handlers::all() | 67 | handlers::all() |
77 | .iter() | 68 | .iter() |
78 | .filter_map(|f| f(ctx.clone())) | 69 | .filter_map(|f| f(ctx.clone())) |
79 | .map(|a| match a { | 70 | .flat_map(|it| it.0) |
80 | Assist::Unresolved { label } => label, | 71 | .map(|a| a.label) |
81 | Assist::Resolved { .. } => unreachable!(), | ||
82 | }) | ||
83 | .collect() | 72 | .collect() |
84 | } | 73 | } |
85 | 74 | ||
@@ -92,24 +81,13 @@ pub fn resolved_assists(db: &RootDatabase, range: FileRange) -> Vec<ResolvedAssi | |||
92 | let mut a = handlers::all() | 81 | let mut a = handlers::all() |
93 | .iter() | 82 | .iter() |
94 | .filter_map(|f| f(ctx.clone())) | 83 | .filter_map(|f| f(ctx.clone())) |
95 | .map(|a| match a { | 84 | .flat_map(|it| it.0) |
96 | Assist::Resolved { assist } => assist, | 85 | .map(|it| it.into_resolved().unwrap()) |
97 | Assist::Unresolved { .. } => unreachable!(), | ||
98 | }) | ||
99 | .collect::<Vec<_>>(); | 86 | .collect::<Vec<_>>(); |
100 | sort_assists(&mut a); | 87 | a.sort_by_key(|it| it.action.target.map_or(TextUnit::from(!0u32), |it| it.len())); |
101 | a | 88 | a |
102 | } | 89 | } |
103 | 90 | ||
104 | fn sort_assists(assists: &mut [ResolvedAssist]) { | ||
105 | assists.sort_by(|a, b| match (a.get_first_action().target, b.get_first_action().target) { | ||
106 | (Some(a), Some(b)) => a.len().cmp(&b.len()), | ||
107 | (Some(_), None) => Ordering::Less, | ||
108 | (None, Some(_)) => Ordering::Greater, | ||
109 | (None, None) => Ordering::Equal, | ||
110 | }); | ||
111 | } | ||
112 | |||
113 | mod handlers { | 91 | mod handlers { |
114 | use crate::AssistHandler; | 92 | use crate::AssistHandler; |
115 | 93 | ||
@@ -184,7 +162,7 @@ mod helpers { | |||
184 | use ra_syntax::TextRange; | 162 | use ra_syntax::TextRange; |
185 | use test_utils::{add_cursor, assert_eq_text, extract_offset, extract_range}; | 163 | use test_utils::{add_cursor, assert_eq_text, extract_offset, extract_range}; |
186 | 164 | ||
187 | use crate::{Assist, AssistCtx, AssistHandler}; | 165 | use crate::{AssistCtx, AssistHandler}; |
188 | 166 | ||
189 | pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) { | 167 | pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) { |
190 | let (mut db, file_id) = RootDatabase::with_single_file(text); | 168 | let (mut db, file_id) = RootDatabase::with_single_file(text); |
@@ -202,10 +180,7 @@ mod helpers { | |||
202 | FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) }; | 180 | FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) }; |
203 | let assist = | 181 | let assist = |
204 | assist(AssistCtx::new(&db, frange, true)).expect("code action is not applicable"); | 182 | assist(AssistCtx::new(&db, frange, true)).expect("code action is not applicable"); |
205 | let action = match assist { | 183 | let action = assist.0[0].action.clone().unwrap(); |
206 | Assist::Unresolved { .. } => unreachable!(), | ||
207 | Assist::Resolved { assist } => assist.get_first_action(), | ||
208 | }; | ||
209 | 184 | ||
210 | let actual = action.edit.apply(&before); | 185 | let actual = action.edit.apply(&before); |
211 | let actual_cursor_pos = match action.cursor_position { | 186 | let actual_cursor_pos = match action.cursor_position { |
@@ -225,10 +200,7 @@ mod helpers { | |||
225 | let frange = FileRange { file_id, range }; | 200 | let frange = FileRange { file_id, range }; |
226 | let assist = | 201 | let assist = |
227 | assist(AssistCtx::new(&db, frange, true)).expect("code action is not applicable"); | 202 | assist(AssistCtx::new(&db, frange, true)).expect("code action is not applicable"); |
228 | let action = match assist { | 203 | let action = assist.0[0].action.clone().unwrap(); |
229 | Assist::Unresolved { .. } => unreachable!(), | ||
230 | Assist::Resolved { assist } => assist.get_first_action(), | ||
231 | }; | ||
232 | 204 | ||
233 | let mut actual = action.edit.apply(&before); | 205 | let mut actual = action.edit.apply(&before); |
234 | if let Some(pos) = action.cursor_position { | 206 | if let Some(pos) = action.cursor_position { |
@@ -244,10 +216,7 @@ mod helpers { | |||
244 | FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) }; | 216 | FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) }; |
245 | let assist = | 217 | let assist = |
246 | assist(AssistCtx::new(&db, frange, true)).expect("code action is not applicable"); | 218 | assist(AssistCtx::new(&db, frange, true)).expect("code action is not applicable"); |
247 | let action = match assist { | 219 | let action = assist.0[0].action.clone().unwrap(); |
248 | Assist::Unresolved { .. } => unreachable!(), | ||
249 | Assist::Resolved { assist } => assist.get_first_action(), | ||
250 | }; | ||
251 | 220 | ||
252 | let range = action.target.expect("expected target on action"); | 221 | let range = action.target.expect("expected target on action"); |
253 | assert_eq_text!(&before[range.start().to_usize()..range.end().to_usize()], target); | 222 | assert_eq_text!(&before[range.start().to_usize()..range.end().to_usize()], target); |
@@ -259,10 +228,7 @@ mod helpers { | |||
259 | let frange = FileRange { file_id, range }; | 228 | let frange = FileRange { file_id, range }; |
260 | let assist = | 229 | let assist = |
261 | assist(AssistCtx::new(&db, frange, true)).expect("code action is not applicable"); | 230 | assist(AssistCtx::new(&db, frange, true)).expect("code action is not applicable"); |
262 | let action = match assist { | 231 | let action = assist.0[0].action.clone().unwrap(); |
263 | Assist::Unresolved { .. } => unreachable!(), | ||
264 | Assist::Resolved { assist } => assist.get_first_action(), | ||
265 | }; | ||
266 | 232 | ||
267 | let range = action.target.expect("expected target on action"); | 233 | let range = action.target.expect("expected target on action"); |
268 | assert_eq_text!(&before[range.start().to_usize()..range.end().to_usize()], target); | 234 | assert_eq_text!(&before[range.start().to_usize()..range.end().to_usize()], target); |