aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-02-09 15:04:20 +0000
committerGitHub <[email protected]>2020-02-09 15:04:20 +0000
commita836247de45d7187a3902c2f14034d2817dbfbc3 (patch)
treebb0bf39dee21d5720e5f1070b09805dfbc120cd9 /crates
parentaa5f80aed1464881575fe635557c3965313a7ecf (diff)
parent9769c5140c9c406a4cc880e698593a6c4bcc6826 (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]>
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_assists/src/assist_ctx.rs109
-rw-r--r--crates/ra_assists/src/doc_tests.rs2
-rw-r--r--crates/ra_assists/src/handlers/auto_import.rs33
-rw-r--r--crates/ra_assists/src/lib.rs64
-rw-r--r--crates/ra_ide/src/assists.rs24
-rw-r--r--crates/ra_lsp_server/src/main_loop/handlers.rs76
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.
2use either::Either;
3use hir::{InFile, SourceAnalyzer, SourceBinder}; 2use hir::{InFile, SourceAnalyzer, SourceBinder};
4use ra_db::{FileRange, SourceDatabase}; 3use ra_db::{FileRange, SourceDatabase};
5use ra_fmt::{leading_indent, reindent}; 4use ra_fmt::{leading_indent, reindent};
@@ -11,12 +10,36 @@ use ra_syntax::{
11}; 10};
12use ra_text_edit::TextEditBuilder; 11use ra_text_edit::TextEditBuilder;
13 12
14use crate::{AssistAction, AssistId, AssistLabel, ResolvedAssist}; 13use crate::{AssistAction, AssistId, AssistLabel, GroupLabel, ResolvedAssist};
15 14
16#[derive(Clone, Debug)] 15#[derive(Clone, Debug)]
17pub(crate) enum Assist { 16pub(crate) struct Assist(pub(crate) Vec<AssistInfo>);
18 Unresolved { label: AssistLabel }, 17
19 Resolved { assist: ResolvedAssist }, 18#[derive(Clone, Debug)]
19pub(crate) struct AssistInfo {
20 pub(crate) label: AssistLabel,
21 pub(crate) group_label: Option<GroupLabel>,
22 pub(crate) action: Option<AssistAction>,
23}
24
25impl 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
22pub(crate) type AssistHandler = fn(AssistCtx) -> Option<Assist>; 45pub(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
158pub(crate) struct AssistGroup<'a> {
159 ctx: AssistCtx<'a>,
160 group_name: String,
161 assists: Vec<AssistInfo>,
162}
163
164impl<'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)]
159pub(crate) struct ActionBuilder { 193pub(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
166impl ActionBuilder { 199impl 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 @@
1use hir::ModPath;
2use ra_ide_db::imports_locator::ImportsLocator; 1use ra_ide_db::imports_locator::ImportsLocator;
3use ra_syntax::{ 2use ra_syntax::ast::{self, AstNode};
4 ast::{self, AstNode},
5 SyntaxNode,
6};
7 3
8use crate::{ 4use crate::{
9 assist_ctx::{ActionBuilder, Assist, AssistCtx}, 5 assist_ctx::{Assist, AssistCtx},
10 insert_use_statement, AssistId, 6 insert_use_statement, AssistId,
11}; 7};
12use std::collections::BTreeSet; 8use 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(),
78fn 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;
12mod utils; 12mod utils;
13pub mod ast_transform; 13pub mod ast_transform;
14 14
15use std::cmp::Ordering;
16
17use either::Either;
18use ra_db::FileRange; 15use ra_db::FileRange;
19use ra_ide_db::RootDatabase; 16use ra_ide_db::RootDatabase;
20use ra_syntax::{TextRange, TextUnit}; 17use 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)]
36pub struct GroupLabel(pub String);
37
38impl AssistLabel { 38impl 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)]
47pub struct AssistAction { 47pub 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)]
56pub struct ResolvedAssist { 55pub 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
61impl 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
104fn 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
113mod handlers { 91mod 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
3use either::Either;
4use ra_assists::{resolved_assists, AssistAction, AssistLabel}; 3use ra_assists::{resolved_assists, AssistAction, AssistLabel};
5use ra_db::{FilePosition, FileRange}; 4use ra_db::{FilePosition, FileRange};
6use ra_ide_db::RootDatabase; 5use ra_ide_db::RootDatabase;
@@ -13,7 +12,8 @@ pub use ra_assists::AssistId;
13pub struct Assist { 12pub 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
19pub(crate) fn assists(db: &RootDatabase, frange: FileRange) -> Vec<Assist> { 19pub(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
4use std::{ 4use 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
10use either::Either;
11use lsp_server::ErrorCode; 11use lsp_server::ErrorCode;
12use lsp_types::{ 12use 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};
20use ra_ide::{ 21use 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),