diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-02-09 15:04:20 +0000 |
---|---|---|
committer | GitHub <[email protected]> | 2020-02-09 15:04:20 +0000 |
commit | a836247de45d7187a3902c2f14034d2817dbfbc3 (patch) | |
tree | bb0bf39dee21d5720e5f1070b09805dfbc120cd9 | |
parent | aa5f80aed1464881575fe635557c3965313a7ecf (diff) | |
parent | 9769c5140c9c406a4cc880e698593a6c4bcc6826 (diff) |
Merge #3069
3069: Simplify Assists interface r=matklad a=matklad
Instead of building a physical tree structure, just tag related
assists with the same group
Co-authored-by: Aleksey Kladov <[email protected]>
-rw-r--r-- | crates/ra_assists/src/assist_ctx.rs | 109 | ||||
-rw-r--r-- | crates/ra_assists/src/doc_tests.rs | 2 | ||||
-rw-r--r-- | crates/ra_assists/src/handlers/auto_import.rs | 33 | ||||
-rw-r--r-- | crates/ra_assists/src/lib.rs | 64 | ||||
-rw-r--r-- | crates/ra_ide/src/assists.rs | 24 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/main_loop/handlers.rs | 76 |
6 files changed, 152 insertions, 156 deletions
diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_ctx.rs index 81f999090..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,44 +107,21 @@ 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( | 123 | pub(crate) fn add_assist_group(self, group_name: impl Into<String>) -> AssistGroup<'a> { |
102 | self, | 124 | AssistGroup { ctx: self, group_name: group_name.into(), assists: Vec::new() } |
103 | id: AssistId, | ||
104 | label: impl Into<String>, | ||
105 | f: impl FnOnce() -> Vec<ActionBuilder>, | ||
106 | ) -> Option<Assist> { | ||
107 | let label = AssistLabel::new(label.into(), id); | ||
108 | let assist = if self.should_compute_edit { | ||
109 | let actions = f(); | ||
110 | assert!(!actions.is_empty(), "Assist cannot have no"); | ||
111 | |||
112 | Assist::Resolved { | ||
113 | assist: ResolvedAssist { | ||
114 | label, | ||
115 | action_data: Either::Right( | ||
116 | actions.into_iter().map(ActionBuilder::build).collect(), | ||
117 | ), | ||
118 | }, | ||
119 | } | ||
120 | } else { | ||
121 | Assist::Unresolved { label } | ||
122 | }; | ||
123 | |||
124 | Some(assist) | ||
125 | } | 125 | } |
126 | 126 | ||
127 | pub(crate) fn token_at_offset(&self) -> TokenAtOffset<SyntaxToken> { | 127 | pub(crate) fn token_at_offset(&self) -> TokenAtOffset<SyntaxToken> { |
@@ -155,20 +155,48 @@ impl<'a> AssistCtx<'a> { | |||
155 | } | 155 | } |
156 | } | 156 | } |
157 | 157 | ||
158 | pub(crate) struct AssistGroup<'a> { | ||
159 | ctx: AssistCtx<'a>, | ||
160 | group_name: String, | ||
161 | assists: Vec<AssistInfo>, | ||
162 | } | ||
163 | |||
164 | impl<'a> AssistGroup<'a> { | ||
165 | pub(crate) fn add_assist( | ||
166 | &mut self, | ||
167 | id: AssistId, | ||
168 | label: impl Into<String>, | ||
169 | f: impl FnOnce(&mut ActionBuilder), | ||
170 | ) { | ||
171 | let label = AssistLabel::new(label.into(), id); | ||
172 | |||
173 | let mut info = AssistInfo::new(label).with_group(GroupLabel(self.group_name.clone())); | ||
174 | if self.ctx.should_compute_edit { | ||
175 | let action = { | ||
176 | let mut edit = ActionBuilder::default(); | ||
177 | f(&mut edit); | ||
178 | edit.build() | ||
179 | }; | ||
180 | info = info.resolved(action) | ||
181 | }; | ||
182 | |||
183 | self.assists.push(info) | ||
184 | } | ||
185 | |||
186 | pub(crate) fn finish(self) -> Option<Assist> { | ||
187 | assert!(!self.assists.is_empty()); | ||
188 | Some(Assist(self.assists)) | ||
189 | } | ||
190 | } | ||
191 | |||
158 | #[derive(Default)] | 192 | #[derive(Default)] |
159 | pub(crate) struct ActionBuilder { | 193 | pub(crate) struct ActionBuilder { |
160 | edit: TextEditBuilder, | 194 | edit: TextEditBuilder, |
161 | cursor_position: Option<TextUnit>, | 195 | cursor_position: Option<TextUnit>, |
162 | target: Option<TextRange>, | 196 | target: Option<TextRange>, |
163 | label: Option<String>, | ||
164 | } | 197 | } |
165 | 198 | ||
166 | impl ActionBuilder { | 199 | impl ActionBuilder { |
167 | /// Adds a custom label to the action, if it needs to be different from the assist label | ||
168 | pub(crate) fn label(&mut self, label: impl Into<String>) { | ||
169 | self.label = Some(label.into()) | ||
170 | } | ||
171 | |||
172 | /// Replaces specified `range` of text with a given string. | 200 | /// Replaces specified `range` of text with a given string. |
173 | pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) { | 201 | pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) { |
174 | self.edit.replace(range, replace_with.into()) | 202 | self.edit.replace(range, replace_with.into()) |
@@ -227,7 +255,6 @@ impl ActionBuilder { | |||
227 | edit: self.edit.finish(), | 255 | edit: self.edit.finish(), |
228 | cursor_position: self.cursor_position, | 256 | cursor_position: self.cursor_position, |
229 | target: self.target, | 257 | target: self.target, |
230 | label: self.label, | ||
231 | } | 258 | } |
232 | } | 259 | } |
233 | } | 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/handlers/auto_import.rs b/crates/ra_assists/src/handlers/auto_import.rs index 4514b8691..d13332f37 100644 --- a/crates/ra_assists/src/handlers/auto_import.rs +++ b/crates/ra_assists/src/handlers/auto_import.rs | |||
@@ -1,12 +1,8 @@ | |||
1 | use hir::ModPath; | ||
2 | use ra_ide_db::imports_locator::ImportsLocator; | 1 | use ra_ide_db::imports_locator::ImportsLocator; |
3 | use ra_syntax::{ | 2 | use ra_syntax::ast::{self, AstNode}; |
4 | ast::{self, AstNode}, | ||
5 | SyntaxNode, | ||
6 | }; | ||
7 | 3 | ||
8 | use crate::{ | 4 | use crate::{ |
9 | assist_ctx::{ActionBuilder, Assist, AssistCtx}, | 5 | assist_ctx::{Assist, AssistCtx}, |
10 | insert_use_statement, AssistId, | 6 | insert_use_statement, AssistId, |
11 | }; | 7 | }; |
12 | use std::collections::BTreeSet; | 8 | use std::collections::BTreeSet; |
@@ -67,19 +63,18 @@ pub(crate) fn auto_import(ctx: AssistCtx) -> Option<Assist> { | |||
67 | return None; | 63 | return None; |
68 | } | 64 | } |
69 | 65 | ||
70 | ctx.add_assist_group(AssistId("auto_import"), format!("Import {}", name_to_import), || { | 66 | let mut group = ctx.add_assist_group(format!("Import {}", name_to_import)); |
71 | proposed_imports | 67 | for import in proposed_imports { |
72 | .into_iter() | 68 | group.add_assist(AssistId("auto_import"), format!("Import `{}`", &import), |edit| { |
73 | .map(|import| import_to_action(import, &position, &path_to_import_syntax)) | 69 | insert_use_statement( |
74 | .collect() | 70 | &position, |
75 | }) | 71 | path_to_import_syntax, |
76 | } | 72 | &import, |
77 | 73 | edit.text_edit_builder(), | |
78 | fn import_to_action(import: ModPath, position: &SyntaxNode, anchor: &SyntaxNode) -> ActionBuilder { | 74 | ); |
79 | let mut action_builder = ActionBuilder::default(); | 75 | }); |
80 | action_builder.label(format!("Import `{}`", &import)); | 76 | } |
81 | insert_use_statement(position, anchor, &import, action_builder.text_edit_builder()); | 77 | group.finish() |
82 | action_builder | ||
83 | } | 78 | } |
84 | 79 | ||
85 | #[cfg(test)] | 80 | #[cfg(test)] |
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); |
diff --git a/crates/ra_ide/src/assists.rs b/crates/ra_ide/src/assists.rs index b60b1a60d..40d56a4f7 100644 --- a/crates/ra_ide/src/assists.rs +++ b/crates/ra_ide/src/assists.rs | |||
@@ -1,6 +1,5 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! FIXME: write short doc here |
2 | 2 | ||
3 | use either::Either; | ||
4 | use ra_assists::{resolved_assists, AssistAction, AssistLabel}; | 3 | use ra_assists::{resolved_assists, AssistAction, AssistLabel}; |
5 | use ra_db::{FilePosition, FileRange}; | 4 | use ra_db::{FilePosition, FileRange}; |
6 | use ra_ide_db::RootDatabase; | 5 | use ra_ide_db::RootDatabase; |
@@ -13,7 +12,8 @@ pub use ra_assists::AssistId; | |||
13 | pub struct Assist { | 12 | pub struct Assist { |
14 | pub id: AssistId, | 13 | pub id: AssistId, |
15 | pub label: String, | 14 | pub label: String, |
16 | pub change_data: Either<SourceChange, Vec<SourceChange>>, | 15 | pub group_label: Option<String>, |
16 | pub source_change: SourceChange, | ||
17 | } | 17 | } |
18 | 18 | ||
19 | pub(crate) fn assists(db: &RootDatabase, frange: FileRange) -> Vec<Assist> { | 19 | pub(crate) fn assists(db: &RootDatabase, frange: FileRange) -> Vec<Assist> { |
@@ -25,17 +25,8 @@ pub(crate) fn assists(db: &RootDatabase, frange: FileRange) -> Vec<Assist> { | |||
25 | Assist { | 25 | Assist { |
26 | id: assist_label.id, | 26 | id: assist_label.id, |
27 | label: assist_label.label.clone(), | 27 | label: assist_label.label.clone(), |
28 | change_data: match assist.action_data { | 28 | group_label: assist.group_label.map(|it| it.0), |
29 | Either::Left(action) => { | 29 | source_change: action_to_edit(assist.action, file_id, assist_label), |
30 | Either::Left(action_to_edit(action, file_id, assist_label)) | ||
31 | } | ||
32 | Either::Right(actions) => Either::Right( | ||
33 | actions | ||
34 | .into_iter() | ||
35 | .map(|action| action_to_edit(action, file_id, assist_label)) | ||
36 | .collect(), | ||
37 | ), | ||
38 | }, | ||
39 | } | 30 | } |
40 | }) | 31 | }) |
41 | .collect() | 32 | .collect() |
@@ -47,9 +38,6 @@ fn action_to_edit( | |||
47 | assist_label: &AssistLabel, | 38 | assist_label: &AssistLabel, |
48 | ) -> SourceChange { | 39 | ) -> SourceChange { |
49 | let file_edit = SourceFileEdit { file_id, edit: action.edit }; | 40 | let file_edit = SourceFileEdit { file_id, edit: action.edit }; |
50 | SourceChange::source_file_edit( | 41 | SourceChange::source_file_edit(assist_label.label.clone(), file_edit) |
51 | action.label.unwrap_or_else(|| assist_label.label.clone()), | 42 | .with_cursor_opt(action.cursor_position.map(|offset| FilePosition { offset, file_id })) |
52 | file_edit, | ||
53 | ) | ||
54 | .with_cursor_opt(action.cursor_position.map(|offset| FilePosition { offset, file_id })) | ||
55 | } | 43 | } |
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs index 282f6e8fc..65e8bc856 100644 --- a/crates/ra_lsp_server/src/main_loop/handlers.rs +++ b/crates/ra_lsp_server/src/main_loop/handlers.rs | |||
@@ -2,20 +2,21 @@ | |||
2 | //! The majority of requests are fulfilled by calling into the `ra_ide` crate. | 2 | //! The majority of requests are fulfilled by calling into the `ra_ide` crate. |
3 | 3 | ||
4 | use std::{ | 4 | use std::{ |
5 | collections::hash_map::Entry, | ||
5 | fmt::Write as _, | 6 | fmt::Write as _, |
6 | io::Write as _, | 7 | io::Write as _, |
7 | process::{self, Stdio}, | 8 | process::{self, Stdio}, |
8 | }; | 9 | }; |
9 | 10 | ||
10 | use either::Either; | ||
11 | use lsp_server::ErrorCode; | 11 | use lsp_server::ErrorCode; |
12 | use lsp_types::{ | 12 | use lsp_types::{ |
13 | CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem, | 13 | CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem, |
14 | CallHierarchyOutgoingCall, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams, | 14 | CallHierarchyOutgoingCall, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams, |
15 | CodeAction, CodeActionResponse, CodeLens, Command, CompletionItem, Diagnostic, | 15 | CodeAction, CodeActionOrCommand, CodeActionResponse, CodeLens, Command, CompletionItem, |
16 | DocumentFormattingParams, DocumentHighlight, DocumentSymbol, FoldingRange, FoldingRangeParams, | 16 | Diagnostic, DocumentFormattingParams, DocumentHighlight, DocumentSymbol, FoldingRange, |
17 | Hover, HoverContents, Location, MarkupContent, MarkupKind, Position, PrepareRenameResponse, | 17 | FoldingRangeParams, Hover, HoverContents, Location, MarkupContent, MarkupKind, Position, |
18 | Range, RenameParams, SymbolInformation, TextDocumentIdentifier, TextEdit, WorkspaceEdit, | 18 | PrepareRenameResponse, Range, RenameParams, SymbolInformation, TextDocumentIdentifier, |
19 | TextEdit, WorkspaceEdit, | ||
19 | }; | 20 | }; |
20 | use ra_ide::{ | 21 | use ra_ide::{ |
21 | AssistId, FileId, FilePosition, FileRange, Query, RangeInfo, Runnable, RunnableKind, | 22 | AssistId, FileId, FilePosition, FileRange, Query, RangeInfo, Runnable, RunnableKind, |
@@ -685,34 +686,53 @@ pub fn handle_code_action( | |||
685 | res.push(fix.action.clone()); | 686 | res.push(fix.action.clone()); |
686 | } | 687 | } |
687 | 688 | ||
689 | let mut groups = FxHashMap::default(); | ||
688 | for assist in world.analysis().assists(FileRange { file_id, range })?.into_iter() { | 690 | for assist in world.analysis().assists(FileRange { file_id, range })?.into_iter() { |
689 | let title = assist.label.clone(); | 691 | let arg = to_value(assist.source_change.try_conv_with(&world)?)?; |
692 | |||
693 | let (command, title, arg) = match assist.group_label { | ||
694 | None => ("rust-analyzer.applySourceChange", assist.label.clone(), arg), | ||
695 | |||
696 | // Group all assists with the same `group_label` into a single CodeAction. | ||
697 | Some(group_label) => { | ||
698 | match groups.entry(group_label.clone()) { | ||
699 | Entry::Occupied(entry) => { | ||
700 | let idx: usize = *entry.get(); | ||
701 | match &mut res[idx] { | ||
702 | CodeActionOrCommand::CodeAction(CodeAction { | ||
703 | command: Some(Command { arguments: Some(arguments), .. }), | ||
704 | .. | ||
705 | }) => match arguments.as_mut_slice() { | ||
706 | [serde_json::Value::Array(arguments)] => arguments.push(arg), | ||
707 | _ => panic!("invalid group"), | ||
708 | }, | ||
709 | _ => panic!("invalid group"), | ||
710 | } | ||
711 | continue; | ||
712 | } | ||
713 | Entry::Vacant(entry) => { | ||
714 | entry.insert(res.len()); | ||
715 | } | ||
716 | } | ||
717 | ("rust-analyzer.selectAndApplySourceChange", group_label, to_value(vec![arg])?) | ||
718 | } | ||
719 | }; | ||
690 | 720 | ||
691 | let command = match assist.change_data { | 721 | let command = Command { |
692 | Either::Left(change) => Command { | 722 | title: assist.label.clone(), |
693 | title, | 723 | command: command.to_string(), |
694 | command: "rust-analyzer.applySourceChange".to_string(), | 724 | arguments: Some(vec![arg]), |
695 | arguments: Some(vec![to_value(change.try_conv_with(&world)?)?]), | 725 | }; |
696 | }, | 726 | |
697 | Either::Right(changes) => Command { | 727 | let kind = match assist.id { |
698 | title, | 728 | AssistId("introduce_variable") => Some("refactor.extract.variable".to_string()), |
699 | command: "rust-analyzer.selectAndApplySourceChange".to_string(), | 729 | AssistId("add_custom_impl") => Some("refactor.rewrite.add_custom_impl".to_string()), |
700 | arguments: Some(vec![to_value( | 730 | _ => None, |
701 | changes | ||
702 | .into_iter() | ||
703 | .map(|change| change.try_conv_with(&world)) | ||
704 | .collect::<Result<Vec<_>>>()?, | ||
705 | )?]), | ||
706 | }, | ||
707 | }; | 731 | }; |
708 | 732 | ||
709 | let action = CodeAction { | 733 | let action = CodeAction { |
710 | title: command.title.clone(), | 734 | title, |
711 | kind: match assist.id { | 735 | kind, |
712 | AssistId("introduce_variable") => Some("refactor.extract.variable".to_string()), | ||
713 | AssistId("add_custom_impl") => Some("refactor.rewrite.add_custom_impl".to_string()), | ||
714 | _ => None, | ||
715 | }, | ||
716 | diagnostics: None, | 736 | diagnostics: None, |
717 | edit: None, | 737 | edit: None, |
718 | command: Some(command), | 738 | command: Some(command), |