diff options
-rw-r--r-- | Cargo.lock | 3 | ||||
-rw-r--r-- | crates/ra_assists/Cargo.toml | 2 | ||||
-rw-r--r-- | crates/ra_assists/src/assist_ctx.rs | 50 | ||||
-rw-r--r-- | crates/ra_assists/src/assists/inline_local_variable.rs | 4 | ||||
-rw-r--r-- | crates/ra_assists/src/doc_tests.rs | 8 | ||||
-rw-r--r-- | crates/ra_assists/src/lib.rs | 39 | ||||
-rw-r--r-- | crates/ra_ide/src/assists.rs | 44 | ||||
-rw-r--r-- | crates/ra_lsp_server/Cargo.toml | 1 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/main_loop/handlers.rs | 28 | ||||
-rw-r--r-- | editors/code/src/commands/index.ts | 13 | ||||
-rw-r--r-- | editors/code/src/main.ts | 1 |
11 files changed, 150 insertions, 43 deletions
diff --git a/Cargo.lock b/Cargo.lock index a0444e97a..45c4de2b6 100644 --- a/Cargo.lock +++ b/Cargo.lock | |||
@@ -869,8 +869,8 @@ version = "0.1.0" | |||
869 | name = "ra_assists" | 869 | name = "ra_assists" |
870 | version = "0.1.0" | 870 | version = "0.1.0" |
871 | dependencies = [ | 871 | dependencies = [ |
872 | "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", | ||
872 | "format-buf 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", | 873 | "format-buf 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", |
873 | "itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", | ||
874 | "join_to_string 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", | 874 | "join_to_string 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", |
875 | "ra_db 0.1.0", | 875 | "ra_db 0.1.0", |
876 | "ra_fmt 0.1.0", | 876 | "ra_fmt 0.1.0", |
@@ -1066,6 +1066,7 @@ name = "ra_lsp_server" | |||
1066 | version = "0.1.0" | 1066 | version = "0.1.0" |
1067 | dependencies = [ | 1067 | dependencies = [ |
1068 | "crossbeam-channel 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", | 1068 | "crossbeam-channel 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1069 | "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", | ||
1069 | "env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", | 1070 | "env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", |
1070 | "jod-thread 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", | 1071 | "jod-thread 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1071 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", | 1072 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", |
diff --git a/crates/ra_assists/Cargo.toml b/crates/ra_assists/Cargo.toml index 50be8d9bc..0d2109e4e 100644 --- a/crates/ra_assists/Cargo.toml +++ b/crates/ra_assists/Cargo.toml | |||
@@ -11,7 +11,7 @@ doctest = false | |||
11 | format-buf = "1.0.0" | 11 | format-buf = "1.0.0" |
12 | join_to_string = "0.1.3" | 12 | join_to_string = "0.1.3" |
13 | rustc-hash = "1.0" | 13 | rustc-hash = "1.0" |
14 | itertools = "0.8.0" | 14 | either = "1.5" |
15 | 15 | ||
16 | ra_syntax = { path = "../ra_syntax" } | 16 | ra_syntax = { path = "../ra_syntax" } |
17 | ra_text_edit = { path = "../ra_text_edit" } | 17 | ra_text_edit = { path = "../ra_text_edit" } |
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. |
2 | use either::Either; | ||
2 | use hir::{db::HirDatabase, InFile, SourceAnalyzer}; | 3 | use hir::{db::HirDatabase, InFile, SourceAnalyzer}; |
3 | use ra_db::FileRange; | 4 | use ra_db::FileRange; |
4 | use ra_fmt::{leading_indent, reindent}; | 5 | use ra_fmt::{leading_indent, reindent}; |
@@ -9,12 +10,12 @@ use ra_syntax::{ | |||
9 | }; | 10 | }; |
10 | use ra_text_edit::TextEditBuilder; | 11 | use ra_text_edit::TextEditBuilder; |
11 | 12 | ||
12 | use crate::{AssistAction, AssistId, AssistLabel}; | 13 | use crate::{AssistAction, AssistId, AssistLabel, ResolvedAssist}; |
13 | 14 | ||
14 | #[derive(Clone, Debug)] | 15 | #[derive(Clone, Debug)] |
15 | pub(crate) enum Assist { | 16 | pub(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)] |
131 | pub(crate) struct AssistBuilder { | 159 | pub(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 | ||
137 | impl AssistBuilder { | 166 | impl 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 | ||
7 | use crate::assist_ctx::AssistBuilder; | 7 | use crate::assist_ctx::ActionBuilder; |
8 | use crate::{Assist, AssistCtx, AssistId}; | 8 | use 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; | |||
13 | mod test_db; | 13 | mod test_db; |
14 | pub mod ast_transform; | 14 | pub mod ast_transform; |
15 | 15 | ||
16 | use either::Either; | ||
16 | use hir::db::HirDatabase; | 17 | use hir::db::HirDatabase; |
17 | use ra_db::FileRange; | 18 | use ra_db::FileRange; |
18 | use ra_syntax::{TextRange, TextUnit}; | 19 | use ra_syntax::{TextRange, TextUnit}; |
@@ -35,11 +36,27 @@ pub struct AssistLabel { | |||
35 | 36 | ||
36 | #[derive(Debug, Clone)] | 37 | #[derive(Debug, Clone)] |
37 | pub struct AssistAction { | 38 | pub 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)] | ||
46 | pub struct ResolvedAssist { | ||
47 | pub label: AssistLabel, | ||
48 | pub action_data: Either<AssistAction, Vec<AssistAction>>, | ||
49 | } | ||
50 | |||
51 | impl 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. |
67 | pub fn assists<H>(db: &H, range: FileRange) -> Vec<(AssistLabel, AssistAction)> | 84 | pub fn assists<H>(db: &H, range: FileRange) -> Vec<ResolvedAssist> |
68 | where | 85 | where |
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 | } |
diff --git a/crates/ra_ide/src/assists.rs b/crates/ra_ide/src/assists.rs index e00589733..a936900da 100644 --- a/crates/ra_ide/src/assists.rs +++ b/crates/ra_ide/src/assists.rs | |||
@@ -2,27 +2,53 @@ | |||
2 | 2 | ||
3 | use ra_db::{FilePosition, FileRange}; | 3 | use ra_db::{FilePosition, FileRange}; |
4 | 4 | ||
5 | use crate::{db::RootDatabase, SourceChange, SourceFileEdit}; | 5 | use crate::{db::RootDatabase, FileId, SourceChange, SourceFileEdit}; |
6 | 6 | ||
7 | use either::Either; | ||
7 | pub use ra_assists::AssistId; | 8 | pub use ra_assists::AssistId; |
9 | use ra_assists::{AssistAction, AssistLabel}; | ||
8 | 10 | ||
9 | #[derive(Debug)] | 11 | #[derive(Debug)] |
10 | pub struct Assist { | 12 | pub struct Assist { |
11 | pub id: AssistId, | 13 | pub id: AssistId, |
12 | pub change: SourceChange, | 14 | pub label: String, |
15 | pub change_data: Either<SourceChange, Vec<SourceChange>>, | ||
13 | } | 16 | } |
14 | 17 | ||
15 | pub(crate) fn assists(db: &RootDatabase, frange: FileRange) -> Vec<Assist> { | 18 | pub(crate) fn assists(db: &RootDatabase, frange: FileRange) -> Vec<Assist> { |
16 | ra_assists::assists(db, frange) | 19 | ra_assists::assists(db, frange) |
17 | .into_iter() | 20 | .into_iter() |
18 | .map(|(label, action)| { | 21 | .map(|assist| { |
19 | let file_id = frange.file_id; | 22 | let file_id = frange.file_id; |
20 | let file_edit = SourceFileEdit { file_id, edit: action.edit }; | 23 | let assist_label = &assist.label; |
21 | let id = label.id; | 24 | Assist { |
22 | let change = SourceChange::source_file_edit(label.label, file_edit).with_cursor_opt( | 25 | id: assist_label.id, |
23 | action.cursor_position.map(|offset| FilePosition { offset, file_id }), | 26 | label: assist_label.label.clone(), |
24 | ); | 27 | change_data: match assist.action_data { |
25 | Assist { id, change } | 28 | Either::Left(action) => { |
29 | Either::Left(action_to_edit(action, file_id, assist_label)) | ||
30 | } | ||
31 | Either::Right(actions) => Either::Right( | ||
32 | actions | ||
33 | .into_iter() | ||
34 | .map(|action| action_to_edit(action, file_id, assist_label)) | ||
35 | .collect(), | ||
36 | ), | ||
37 | }, | ||
38 | } | ||
26 | }) | 39 | }) |
27 | .collect() | 40 | .collect() |
28 | } | 41 | } |
42 | |||
43 | fn action_to_edit( | ||
44 | action: AssistAction, | ||
45 | file_id: FileId, | ||
46 | assist_label: &AssistLabel, | ||
47 | ) -> SourceChange { | ||
48 | let file_edit = SourceFileEdit { file_id, edit: action.edit }; | ||
49 | SourceChange::source_file_edit( | ||
50 | action.label.unwrap_or_else(|| assist_label.label.clone()), | ||
51 | file_edit, | ||
52 | ) | ||
53 | .with_cursor_opt(action.cursor_position.map(|offset| FilePosition { offset, file_id })) | ||
54 | } | ||
diff --git a/crates/ra_lsp_server/Cargo.toml b/crates/ra_lsp_server/Cargo.toml index c08e67b8e..579158780 100644 --- a/crates/ra_lsp_server/Cargo.toml +++ b/crates/ra_lsp_server/Cargo.toml | |||
@@ -28,6 +28,7 @@ ra_prof = { path = "../ra_prof" } | |||
28 | ra_vfs_glob = { path = "../ra_vfs_glob" } | 28 | ra_vfs_glob = { path = "../ra_vfs_glob" } |
29 | env_logger = { version = "0.7.1", default-features = false, features = ["humantime"] } | 29 | env_logger = { version = "0.7.1", default-features = false, features = ["humantime"] } |
30 | ra_cargo_watch = { path = "../ra_cargo_watch" } | 30 | ra_cargo_watch = { path = "../ra_cargo_watch" } |
31 | either = "1.5" | ||
31 | 32 | ||
32 | [dev-dependencies] | 33 | [dev-dependencies] |
33 | tempfile = "3" | 34 | tempfile = "3" |
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs index f2db575ea..9e9964880 100644 --- a/crates/ra_lsp_server/src/main_loop/handlers.rs +++ b/crates/ra_lsp_server/src/main_loop/handlers.rs | |||
@@ -3,6 +3,7 @@ | |||
3 | 3 | ||
4 | use std::{fmt::Write as _, io::Write as _}; | 4 | use std::{fmt::Write as _, io::Write as _}; |
5 | 5 | ||
6 | use either::Either; | ||
6 | use lsp_server::ErrorCode; | 7 | use lsp_server::ErrorCode; |
7 | use lsp_types::{ | 8 | use lsp_types::{ |
8 | CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem, | 9 | CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem, |
@@ -644,7 +645,6 @@ pub fn handle_code_action( | |||
644 | let line_index = world.analysis().file_line_index(file_id)?; | 645 | let line_index = world.analysis().file_line_index(file_id)?; |
645 | let range = params.range.conv_with(&line_index); | 646 | let range = params.range.conv_with(&line_index); |
646 | 647 | ||
647 | let assists = world.analysis().assists(FileRange { file_id, range })?.into_iter(); | ||
648 | let diagnostics = world.analysis().diagnostics(file_id)?; | 648 | let diagnostics = world.analysis().diagnostics(file_id)?; |
649 | let mut res = CodeActionResponse::default(); | 649 | let mut res = CodeActionResponse::default(); |
650 | 650 | ||
@@ -697,15 +697,27 @@ pub fn handle_code_action( | |||
697 | res.push(action.into()); | 697 | res.push(action.into()); |
698 | } | 698 | } |
699 | 699 | ||
700 | for assist in assists { | 700 | for assist in world.analysis().assists(FileRange { file_id, range })?.into_iter() { |
701 | let title = assist.change.label.clone(); | 701 | let title = assist.label.clone(); |
702 | let edit = assist.change.try_conv_with(&world)?; | ||
703 | 702 | ||
704 | let command = Command { | 703 | let command = match assist.change_data { |
705 | title, | 704 | Either::Left(change) => Command { |
706 | command: "rust-analyzer.applySourceChange".to_string(), | 705 | title, |
707 | arguments: Some(vec![to_value(edit).unwrap()]), | 706 | command: "rust-analyzer.applySourceChange".to_string(), |
707 | arguments: Some(vec![to_value(change.try_conv_with(&world)?)?]), | ||
708 | }, | ||
709 | Either::Right(changes) => Command { | ||
710 | title, | ||
711 | command: "rust-analyzer.selectAndApplySourceChange".to_string(), | ||
712 | arguments: Some(vec![to_value( | ||
713 | changes | ||
714 | .into_iter() | ||
715 | .map(|change| change.try_conv_with(&world)) | ||
716 | .collect::<Result<Vec<_>>>()?, | ||
717 | )?]), | ||
718 | }, | ||
708 | }; | 719 | }; |
720 | |||
709 | let action = CodeAction { | 721 | let action = CodeAction { |
710 | title: command.title.clone(), | 722 | title: command.title.clone(), |
711 | kind: match assist.id { | 723 | kind: match assist.id { |
diff --git a/editors/code/src/commands/index.ts b/editors/code/src/commands/index.ts index 9a1697dcb..dc075aa82 100644 --- a/editors/code/src/commands/index.ts +++ b/editors/code/src/commands/index.ts | |||
@@ -39,6 +39,18 @@ function applySourceChange(ctx: Ctx): Cmd { | |||
39 | }; | 39 | }; |
40 | } | 40 | } |
41 | 41 | ||
42 | function selectAndApplySourceChange(ctx: Ctx): Cmd { | ||
43 | return async (changes: sourceChange.SourceChange[]) => { | ||
44 | if (changes.length === 1) { | ||
45 | await sourceChange.applySourceChange(ctx, changes[0]); | ||
46 | } else if (changes.length > 0) { | ||
47 | const selectedChange = await vscode.window.showQuickPick(changes); | ||
48 | if (!selectedChange) return; | ||
49 | await sourceChange.applySourceChange(ctx, selectedChange); | ||
50 | } | ||
51 | }; | ||
52 | } | ||
53 | |||
42 | function reload(ctx: Ctx): Cmd { | 54 | function reload(ctx: Ctx): Cmd { |
43 | return async () => { | 55 | return async () => { |
44 | vscode.window.showInformationMessage('Reloading rust-analyzer...'); | 56 | vscode.window.showInformationMessage('Reloading rust-analyzer...'); |
@@ -59,5 +71,6 @@ export { | |||
59 | runSingle, | 71 | runSingle, |
60 | showReferences, | 72 | showReferences, |
61 | applySourceChange, | 73 | applySourceChange, |
74 | selectAndApplySourceChange, | ||
62 | reload | 75 | reload |
63 | }; | 76 | }; |
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index 430ad31b4..0494ccf63 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts | |||
@@ -26,6 +26,7 @@ export async function activate(context: vscode.ExtensionContext) { | |||
26 | ctx.registerCommand('runSingle', commands.runSingle); | 26 | ctx.registerCommand('runSingle', commands.runSingle); |
27 | ctx.registerCommand('showReferences', commands.showReferences); | 27 | ctx.registerCommand('showReferences', commands.showReferences); |
28 | ctx.registerCommand('applySourceChange', commands.applySourceChange); | 28 | ctx.registerCommand('applySourceChange', commands.applySourceChange); |
29 | ctx.registerCommand('selectAndApplySourceChange', commands.selectAndApplySourceChange); | ||
29 | 30 | ||
30 | if (ctx.config.enableEnhancedTyping) { | 31 | if (ctx.config.enableEnhancedTyping) { |
31 | ctx.overrideCommand('type', commands.onEnter); | 32 | ctx.overrideCommand('type', commands.onEnter); |