aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2020-02-09 14:32:53 +0000
committerAleksey Kladov <[email protected]>2020-02-09 15:03:54 +0000
commit9769c5140c9c406a4cc880e698593a6c4bcc6826 (patch)
treebb0bf39dee21d5720e5f1070b09805dfbc120cd9
parentfb99831cb044e5fbf171bdd950a7486a01ce0d51 (diff)
Simplify Assists interface
Instead of building a physical tree structure, just "tag" related assists with the same group
-rw-r--r--crates/ra_assists/src/assist_ctx.rs83
-rw-r--r--crates/ra_assists/src/doc_tests.rs2
-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
5 files changed, 108 insertions, 141 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.
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,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> {
136pub(crate) struct AssistGroup<'a> { 158pub(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
142impl<'a> AssistGroup<'a> { 164impl<'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
205impl ActionBuilder { 199impl 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;
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),