aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_assists/src/assist_ctx.rs25
-rw-r--r--crates/ra_assists/src/doc_tests.rs8
-rw-r--r--crates/ra_assists/src/lib.rs40
-rw-r--r--crates/ra_ide/src/assists.rs23
-rw-r--r--crates/ra_lsp_server/Cargo.toml1
-rw-r--r--crates/ra_lsp_server/src/main_loop/handlers.rs28
6 files changed, 79 insertions, 46 deletions
diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_ctx.rs
index 879216a36..e13f969c7 100644
--- a/crates/ra_assists/src/assist_ctx.rs
+++ b/crates/ra_assists/src/assist_ctx.rs
@@ -1,5 +1,6 @@
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 hir::{db::HirDatabase, InFile, SourceAnalyzer}; 2use hir::{db::HirDatabase, InFile, SourceAnalyzer};
3use itertools::Either;
3use ra_db::FileRange; 4use ra_db::FileRange;
4use ra_fmt::{leading_indent, reindent}; 5use ra_fmt::{leading_indent, reindent};
5use ra_syntax::{ 6use ra_syntax::{
@@ -9,12 +10,12 @@ use ra_syntax::{
9}; 10};
10use ra_text_edit::TextEditBuilder; 11use ra_text_edit::TextEditBuilder;
11 12
12use crate::{AssistAction, AssistId, AssistLabel}; 13use crate::{AssistAction, AssistId, AssistLabel, ResolvedAssist};
13 14
14#[derive(Clone, Debug)] 15#[derive(Clone, Debug)]
15pub(crate) enum Assist { 16pub(crate) enum Assist {
16 Unresolved { label: AssistLabel }, 17 Unresolved { label: AssistLabel },
17 Resolved { label: AssistLabel, action: AssistAction, alternative_actions: Vec<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.
@@ -92,7 +93,7 @@ impl<'a, DB: HirDatabase> AssistCtx<'a, DB> {
92 f(&mut edit); 93 f(&mut edit);
93 edit.build() 94 edit.build()
94 }; 95 };
95 Assist::Resolved { label, action, alternative_actions: Vec::default() } 96 Assist::Resolved { assist: ResolvedAssist { label, action_data: Either::Left(action) } }
96 } else { 97 } else {
97 Assist::Unresolved { label } 98 Assist::Unresolved { label }
98 }; 99 };
@@ -105,18 +106,20 @@ impl<'a, DB: HirDatabase> AssistCtx<'a, DB> {
105 self, 106 self,
106 id: AssistId, 107 id: AssistId,
107 label: impl Into<String>, 108 label: impl Into<String>,
108 f: impl FnOnce() -> (ActionBuilder, Vec<ActionBuilder>), 109 f: impl FnOnce() -> Vec<ActionBuilder>,
109 ) -> Option<Assist> { 110 ) -> Option<Assist> {
110 let label = AssistLabel { label: label.into(), id }; 111 let label = AssistLabel { label: label.into(), id };
111 let assist = if self.should_compute_edit { 112 let assist = if self.should_compute_edit {
112 let (action, alternative_actions) = f(); 113 let actions = f();
114 assert!(!actions.is_empty(), "Assist cannot have no");
115
113 Assist::Resolved { 116 Assist::Resolved {
114 label, 117 assist: ResolvedAssist {
115 action: action.build(), 118 label,
116 alternative_actions: alternative_actions 119 action_data: Either::Right(
117 .into_iter() 120 actions.into_iter().map(ActionBuilder::build).collect(),
118 .map(ActionBuilder::build) 121 ),
119 .collect(), 122 },
120 } 123 }
121 } else { 124 } else {
122 Assist::Unresolved { label } 125 Assist::Unresolved { label }
diff --git a/crates/ra_assists/src/doc_tests.rs b/crates/ra_assists/src/doc_tests.rs
index 21bee228d..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 95a530821..a2983ae87 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -14,6 +14,7 @@ mod test_db;
14pub mod ast_transform; 14pub mod ast_transform;
15 15
16use hir::db::HirDatabase; 16use hir::db::HirDatabase;
17use itertools::Either;
17use ra_db::FileRange; 18use ra_db::FileRange;
18use ra_syntax::{TextRange, TextUnit}; 19use ra_syntax::{TextRange, TextUnit};
19use ra_text_edit::TextEdit; 20use ra_text_edit::TextEdit;
@@ -41,6 +42,21 @@ pub struct AssistAction {
41 pub target: Option<TextRange>, 42 pub target: Option<TextRange>,
42} 43}
43 44
45#[derive(Debug, Clone)]
46pub struct ResolvedAssist {
47 pub label: AssistLabel,
48 pub action_data: Either<AssistAction, Vec<AssistAction>>,
49}
50
51impl 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
44/// Return all the assists applicable at the given position. 60/// Return all the assists applicable at the given position.
45/// 61///
46/// 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
@@ -65,7 +81,7 @@ where
65/// 81///
66/// 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
67/// computed. 83/// computed.
68pub fn assists<H>(db: &H, range: FileRange) -> Vec<(AssistLabel, AssistAction, Vec<AssistAction>)> 84pub fn assists<H>(db: &H, range: FileRange) -> Vec<ResolvedAssist>
69where 85where
70 H: HirDatabase + 'static, 86 H: HirDatabase + 'static,
71{ 87{
@@ -76,13 +92,11 @@ where
76 .iter() 92 .iter()
77 .filter_map(|f| f(ctx.clone())) 93 .filter_map(|f| f(ctx.clone()))
78 .map(|a| match a { 94 .map(|a| match a {
79 Assist::Resolved { label, action, alternative_actions } => { 95 Assist::Resolved { assist } => assist,
80 (label, action, alternative_actions)
81 }
82 Assist::Unresolved { .. } => unreachable!(), 96 Assist::Unresolved { .. } => unreachable!(),
83 }) 97 })
84 .collect::<Vec<_>>(); 98 .collect::<Vec<_>>();
85 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) {
86 (Some(a), Some(b)) => a.len().cmp(&b.len()), 100 (Some(a), Some(b)) => a.len().cmp(&b.len()),
87 (Some(_), None) => Ordering::Less, 101 (Some(_), None) => Ordering::Less,
88 (None, Some(_)) => Ordering::Greater, 102 (None, Some(_)) => Ordering::Greater,
@@ -177,7 +191,7 @@ mod helpers {
177 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");
178 let action = match assist { 192 let action = match assist {
179 Assist::Unresolved { .. } => unreachable!(), 193 Assist::Unresolved { .. } => unreachable!(),
180 Assist::Resolved { action, .. } => action, 194 Assist::Resolved { assist } => assist.get_first_action(),
181 }; 195 };
182 196
183 let actual = action.edit.apply(&before); 197 let actual = action.edit.apply(&before);
@@ -204,7 +218,7 @@ mod helpers {
204 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");
205 let action = match assist { 219 let action = match assist {
206 Assist::Unresolved { .. } => unreachable!(), 220 Assist::Unresolved { .. } => unreachable!(),
207 Assist::Resolved { action, .. } => action, 221 Assist::Resolved { assist } => assist.get_first_action(),
208 }; 222 };
209 223
210 let mut actual = action.edit.apply(&before); 224 let mut actual = action.edit.apply(&before);
@@ -227,7 +241,7 @@ mod helpers {
227 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");
228 let action = match assist { 242 let action = match assist {
229 Assist::Unresolved { .. } => unreachable!(), 243 Assist::Unresolved { .. } => unreachable!(),
230 Assist::Resolved { action, .. } => action, 244 Assist::Resolved { assist } => assist.get_first_action(),
231 }; 245 };
232 246
233 let range = action.target.expect("expected target on action"); 247 let range = action.target.expect("expected target on action");
@@ -246,7 +260,7 @@ mod helpers {
246 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");
247 let action = match assist { 261 let action = match assist {
248 Assist::Unresolved { .. } => unreachable!(), 262 Assist::Unresolved { .. } => unreachable!(),
249 Assist::Resolved { action, .. } => action, 263 Assist::Resolved { assist } => assist.get_first_action(),
250 }; 264 };
251 265
252 let range = action.target.expect("expected target on action"); 266 let range = action.target.expect("expected target on action");
@@ -296,10 +310,10 @@ mod tests {
296 let mut assists = assists.iter(); 310 let mut assists = assists.iter();
297 311
298 assert_eq!( 312 assert_eq!(
299 assists.next().expect("expected assist").0.label, 313 assists.next().expect("expected assist").label.label,
300 "Change visibility to pub(crate)" 314 "Change visibility to pub(crate)"
301 ); 315 );
302 assert_eq!(assists.next().expect("expected assist").0.label, "Add `#[derive]`"); 316 assert_eq!(assists.next().expect("expected assist").label.label, "Add `#[derive]`");
303 } 317 }
304 318
305 #[test] 319 #[test]
@@ -318,7 +332,7 @@ mod tests {
318 let assists = super::assists(&db, frange); 332 let assists = super::assists(&db, frange);
319 let mut assists = assists.iter(); 333 let mut assists = assists.iter();
320 334
321 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");
322 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");
323 } 337 }
324} 338}
diff --git a/crates/ra_ide/src/assists.rs b/crates/ra_ide/src/assists.rs
index db6e4e8b7..e30eee5f4 100644
--- a/crates/ra_ide/src/assists.rs
+++ b/crates/ra_ide/src/assists.rs
@@ -4,30 +4,37 @@ use ra_db::{FilePosition, FileRange};
4 4
5use crate::{db::RootDatabase, FileId, SourceChange, SourceFileEdit}; 5use crate::{db::RootDatabase, FileId, SourceChange, SourceFileEdit};
6 6
7use itertools::Either;
7pub use ra_assists::AssistId; 8pub use ra_assists::AssistId;
8use ra_assists::{AssistAction, AssistLabel}; 9use ra_assists::{AssistAction, AssistLabel};
9 10
10#[derive(Debug)] 11#[derive(Debug)]
11pub struct Assist { 12pub struct Assist {
12 pub id: AssistId, 13 pub id: AssistId,
13 pub change: SourceChange,
14 pub label: String, 14 pub label: String,
15 pub alternative_changes: Vec<SourceChange>, 15 pub change_data: Either<SourceChange, Vec<SourceChange>>,
16} 16}
17 17
18pub(crate) fn assists(db: &RootDatabase, frange: FileRange) -> Vec<Assist> { 18pub(crate) fn assists(db: &RootDatabase, frange: FileRange) -> Vec<Assist> {
19 ra_assists::assists(db, frange) 19 ra_assists::assists(db, frange)
20 .into_iter() 20 .into_iter()
21 .map(|(assist_label, action, alternative_actions)| { 21 .map(|assist| {
22 let file_id = frange.file_id; 22 let file_id = frange.file_id;
23 let assist_label = &assist.label;
23 Assist { 24 Assist {
24 id: assist_label.id, 25 id: assist_label.id,
25 label: assist_label.label.clone(), 26 label: assist_label.label.clone(),
26 change: action_to_edit(action, file_id, &assist_label), 27 change_data: match assist.action_data {
27 alternative_changes: alternative_actions 28 Either::Left(action) => {
28 .into_iter() 29 Either::Left(action_to_edit(action, file_id, assist_label))
29 .map(|action| action_to_edit(action, file_id, &assist_label)) 30 }
30 .collect(), 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 },
31 } 38 }
32 }) 39 })
33 .collect() 40 .collect()
diff --git a/crates/ra_lsp_server/Cargo.toml b/crates/ra_lsp_server/Cargo.toml
index c08e67b8e..73bce8b08 100644
--- a/crates/ra_lsp_server/Cargo.toml
+++ b/crates/ra_lsp_server/Cargo.toml
@@ -28,6 +28,7 @@ ra_prof = { path = "../ra_prof" }
28ra_vfs_glob = { path = "../ra_vfs_glob" } 28ra_vfs_glob = { path = "../ra_vfs_glob" }
29env_logger = { version = "0.7.1", default-features = false, features = ["humantime"] } 29env_logger = { version = "0.7.1", default-features = false, features = ["humantime"] }
30ra_cargo_watch = { path = "../ra_cargo_watch" } 30ra_cargo_watch = { path = "../ra_cargo_watch" }
31itertools = "0.8"
31 32
32[dev-dependencies] 33[dev-dependencies]
33tempfile = "3" 34tempfile = "3"
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs
index ec3c0a557..d76639474 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
4use std::{fmt::Write as _, io::Write as _}; 4use std::{fmt::Write as _, io::Write as _};
5 5
6use itertools::Either;
6use lsp_server::ErrorCode; 7use lsp_server::ErrorCode;
7use lsp_types::{ 8use lsp_types::{
8 CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem, 9 CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem,
@@ -698,18 +699,25 @@ pub fn handle_code_action(
698 699
699 for assist in world.analysis().assists(FileRange { file_id, range })?.into_iter() { 700 for assist in world.analysis().assists(FileRange { file_id, range })?.into_iter() {
700 let title = assist.label.clone(); 701 let title = assist.label.clone();
701 let edit = assist.change.try_conv_with(&world)?;
702 let alternative_edits = assist
703 .alternative_changes
704 .into_iter()
705 .map(|change| change.try_conv_with(&world))
706 .collect::<Result<Vec<_>>>()?;
707 702
708 let command = Command { 703 let command = match assist.change_data {
709 title, 704 Either::Left(change) => Command {
710 command: "rust-analyzer.applySourceChange".to_string(), 705 title,
711 arguments: Some(vec![to_value(edit).unwrap(), to_value(alternative_edits).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 },
712 }; 719 };
720
713 let action = CodeAction { 721 let action = CodeAction {
714 title: command.title.clone(), 722 title: command.title.clone(),
715 kind: match assist.id { 723 kind: match assist.id {