aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_assists/src/assist_ctx.rs45
-rw-r--r--crates/ra_assists/src/assists/inline_local_variable.rs4
-rw-r--r--crates/ra_assists/src/doc_tests.rs6
-rw-r--r--crates/ra_assists/src/lib.rs7
-rw-r--r--crates/ra_ide/src/assists.rs35
-rw-r--r--crates/ra_lsp_server/src/main_loop/handlers.rs12
-rw-r--r--editors/code/src/commands/index.ts4
-rw-r--r--editors/code/src/source_change.ts12
8 files changed, 97 insertions, 28 deletions
diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_ctx.rs
index 1a65b5dc0..879216a36 100644
--- a/crates/ra_assists/src/assist_ctx.rs
+++ b/crates/ra_assists/src/assist_ctx.rs
@@ -14,7 +14,7 @@ use crate::{AssistAction, AssistId, AssistLabel};
14#[derive(Clone, Debug)] 14#[derive(Clone, Debug)]
15pub(crate) enum Assist { 15pub(crate) enum Assist {
16 Unresolved { label: AssistLabel }, 16 Unresolved { label: AssistLabel },
17 Resolved { label: AssistLabel, action: AssistAction }, 17 Resolved { label: AssistLabel, action: AssistAction, alternative_actions: Vec<AssistAction> },
18} 18}
19 19
20/// `AssistCtx` allows to apply an assist or check if it could be applied. 20/// `AssistCtx` allows to apply an assist or check if it could be applied.
@@ -81,18 +81,43 @@ impl<'a, DB: HirDatabase> AssistCtx<'a, DB> {
81 self, 81 self,
82 id: AssistId, 82 id: AssistId,
83 label: impl Into<String>, 83 label: impl Into<String>,
84 f: impl FnOnce(&mut AssistBuilder), 84 f: impl FnOnce(&mut ActionBuilder),
85 ) -> Option<Assist> { 85 ) -> Option<Assist> {
86 let label = AssistLabel { label: label.into(), id }; 86 let label = AssistLabel { label: label.into(), id };
87 assert!(label.label.chars().nth(0).unwrap().is_uppercase()); 87 assert!(label.label.chars().nth(0).unwrap().is_uppercase());
88 88
89 let assist = if self.should_compute_edit { 89 let assist = if self.should_compute_edit {
90 let action = { 90 let action = {
91 let mut edit = AssistBuilder::default(); 91 let mut edit = ActionBuilder::default();
92 f(&mut edit); 92 f(&mut edit);
93 edit.build() 93 edit.build()
94 }; 94 };
95 Assist::Resolved { label, action } 95 Assist::Resolved { label, action, alternative_actions: Vec::default() }
96 } else {
97 Assist::Unresolved { label }
98 };
99
100 Some(assist)
101 }
102
103 #[allow(dead_code)] // will be used for auto import assist with multiple actions
104 pub(crate) fn add_assist_group(
105 self,
106 id: AssistId,
107 label: impl Into<String>,
108 f: impl FnOnce() -> (ActionBuilder, Vec<ActionBuilder>),
109 ) -> Option<Assist> {
110 let label = AssistLabel { label: label.into(), id };
111 let assist = if self.should_compute_edit {
112 let (action, alternative_actions) = f();
113 Assist::Resolved {
114 label,
115 action: action.build(),
116 alternative_actions: alternative_actions
117 .into_iter()
118 .map(ActionBuilder::build)
119 .collect(),
120 }
96 } else { 121 } else {
97 Assist::Unresolved { label } 122 Assist::Unresolved { label }
98 }; 123 };
@@ -128,13 +153,20 @@ impl<'a, DB: HirDatabase> AssistCtx<'a, DB> {
128} 153}
129 154
130#[derive(Default)] 155#[derive(Default)]
131pub(crate) struct AssistBuilder { 156pub(crate) struct ActionBuilder {
132 edit: TextEditBuilder, 157 edit: TextEditBuilder,
133 cursor_position: Option<TextUnit>, 158 cursor_position: Option<TextUnit>,
134 target: Option<TextRange>, 159 target: Option<TextRange>,
160 label: Option<String>,
135} 161}
136 162
137impl AssistBuilder { 163impl ActionBuilder {
164 #[allow(dead_code)]
165 /// Adds a custom label to the action, if it needs to be different from the assist label
166 pub fn label(&mut self, label: impl Into<String>) {
167 self.label = Some(label.into())
168 }
169
138 /// Replaces specified `range` of text with a given string. 170 /// Replaces specified `range` of text with a given string.
139 pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) { 171 pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) {
140 self.edit.replace(range, replace_with.into()) 172 self.edit.replace(range, replace_with.into())
@@ -193,6 +225,7 @@ impl AssistBuilder {
193 edit: self.edit.finish(), 225 edit: self.edit.finish(),
194 cursor_position: self.cursor_position, 226 cursor_position: self.cursor_position,
195 target: self.target, 227 target: self.target,
228 label: self.label,
196 } 229 }
197 } 230 }
198} 231}
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
7use crate::assist_ctx::AssistBuilder; 7use crate::assist_ctx::ActionBuilder;
8use crate::{Assist, AssistCtx, AssistId}; 8use 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..21bee228d 100644
--- a/crates/ra_assists/src/doc_tests.rs
+++ b/crates/ra_assists/src/doc_tests.rs
@@ -15,16 +15,16 @@ 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_id, action, _) = crate::assists(&db, frange)
19 .into_iter() 19 .into_iter()
20 .find(|(id, _)| id.id.0 == assist_id) 20 .find(|(id, _, _)| id.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(|(id, _, _)| id.id.0)
28 .collect::<Vec<_>>() 28 .collect::<Vec<_>>()
29 .join(", ") 29 .join(", ")
30 ) 30 )
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs
index 150b34ac7..95a530821 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -35,6 +35,7 @@ pub struct AssistLabel {
35 35
36#[derive(Debug, Clone)] 36#[derive(Debug, Clone)]
37pub struct AssistAction { 37pub struct AssistAction {
38 pub label: Option<String>,
38 pub edit: TextEdit, 39 pub edit: TextEdit,
39 pub cursor_position: Option<TextUnit>, 40 pub cursor_position: Option<TextUnit>,
40 pub target: Option<TextRange>, 41 pub target: Option<TextRange>,
@@ -64,7 +65,7 @@ where
64/// 65///
65/// Assists are returned in the "resolved" state, that is with edit fully 66/// Assists are returned in the "resolved" state, that is with edit fully
66/// computed. 67/// computed.
67pub fn assists<H>(db: &H, range: FileRange) -> Vec<(AssistLabel, AssistAction)> 68pub fn assists<H>(db: &H, range: FileRange) -> Vec<(AssistLabel, AssistAction, Vec<AssistAction>)>
68where 69where
69 H: HirDatabase + 'static, 70 H: HirDatabase + 'static,
70{ 71{
@@ -75,7 +76,9 @@ where
75 .iter() 76 .iter()
76 .filter_map(|f| f(ctx.clone())) 77 .filter_map(|f| f(ctx.clone()))
77 .map(|a| match a { 78 .map(|a| match a {
78 Assist::Resolved { label, action } => (label, action), 79 Assist::Resolved { label, action, alternative_actions } => {
80 (label, action, alternative_actions)
81 }
79 Assist::Unresolved { .. } => unreachable!(), 82 Assist::Unresolved { .. } => unreachable!(),
80 }) 83 })
81 .collect::<Vec<_>>(); 84 .collect::<Vec<_>>();
diff --git a/crates/ra_ide/src/assists.rs b/crates/ra_ide/src/assists.rs
index e00589733..db6e4e8b7 100644
--- a/crates/ra_ide/src/assists.rs
+++ b/crates/ra_ide/src/assists.rs
@@ -2,27 +2,46 @@
2 2
3use ra_db::{FilePosition, FileRange}; 3use ra_db::{FilePosition, FileRange};
4 4
5use crate::{db::RootDatabase, SourceChange, SourceFileEdit}; 5use crate::{db::RootDatabase, FileId, SourceChange, SourceFileEdit};
6 6
7pub use ra_assists::AssistId; 7pub use ra_assists::AssistId;
8use ra_assists::{AssistAction, AssistLabel};
8 9
9#[derive(Debug)] 10#[derive(Debug)]
10pub struct Assist { 11pub struct Assist {
11 pub id: AssistId, 12 pub id: AssistId,
12 pub change: SourceChange, 13 pub change: SourceChange,
14 pub label: String,
15 pub alternative_changes: Vec<SourceChange>,
13} 16}
14 17
15pub(crate) fn assists(db: &RootDatabase, frange: FileRange) -> Vec<Assist> { 18pub(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_label, action, alternative_actions)| {
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 Assist {
21 let id = label.id; 24 id: assist_label.id,
22 let change = SourceChange::source_file_edit(label.label, file_edit).with_cursor_opt( 25 label: assist_label.label.clone(),
23 action.cursor_position.map(|offset| FilePosition { offset, file_id }), 26 change: action_to_edit(action, file_id, &assist_label),
24 ); 27 alternative_changes: alternative_actions
25 Assist { id, change } 28 .into_iter()
29 .map(|action| action_to_edit(action, file_id, &assist_label))
30 .collect(),
31 }
26 }) 32 })
27 .collect() 33 .collect()
28} 34}
35
36fn action_to_edit(
37 action: AssistAction,
38 file_id: FileId,
39 assist_label: &AssistLabel,
40) -> SourceChange {
41 let file_edit = SourceFileEdit { file_id, edit: action.edit };
42 SourceChange::source_file_edit(
43 action.label.unwrap_or_else(|| assist_label.label.clone()),
44 file_edit,
45 )
46 .with_cursor_opt(action.cursor_position.map(|offset| FilePosition { offset, file_id }))
47}
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs
index f2db575ea..ec3c0a557 100644
--- a/crates/ra_lsp_server/src/main_loop/handlers.rs
+++ b/crates/ra_lsp_server/src/main_loop/handlers.rs
@@ -644,7 +644,6 @@ pub fn handle_code_action(
644 let line_index = world.analysis().file_line_index(file_id)?; 644 let line_index = world.analysis().file_line_index(file_id)?;
645 let range = params.range.conv_with(&line_index); 645 let range = params.range.conv_with(&line_index);
646 646
647 let assists = world.analysis().assists(FileRange { file_id, range })?.into_iter();
648 let diagnostics = world.analysis().diagnostics(file_id)?; 647 let diagnostics = world.analysis().diagnostics(file_id)?;
649 let mut res = CodeActionResponse::default(); 648 let mut res = CodeActionResponse::default();
650 649
@@ -697,14 +696,19 @@ pub fn handle_code_action(
697 res.push(action.into()); 696 res.push(action.into());
698 } 697 }
699 698
700 for assist in assists { 699 for assist in world.analysis().assists(FileRange { file_id, range })?.into_iter() {
701 let title = assist.change.label.clone(); 700 let title = assist.label.clone();
702 let edit = assist.change.try_conv_with(&world)?; 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<_>>>()?;
703 707
704 let command = Command { 708 let command = Command {
705 title, 709 title,
706 command: "rust-analyzer.applySourceChange".to_string(), 710 command: "rust-analyzer.applySourceChange".to_string(),
707 arguments: Some(vec![to_value(edit).unwrap()]), 711 arguments: Some(vec![to_value(edit).unwrap(), to_value(alternative_edits).unwrap()]),
708 }; 712 };
709 let action = CodeAction { 713 let action = CodeAction {
710 title: command.title.clone(), 714 title: command.title.clone(),
diff --git a/editors/code/src/commands/index.ts b/editors/code/src/commands/index.ts
index 9a1697dcb..0ff708b1f 100644
--- a/editors/code/src/commands/index.ts
+++ b/editors/code/src/commands/index.ts
@@ -34,8 +34,8 @@ function showReferences(ctx: Ctx): Cmd {
34} 34}
35 35
36function applySourceChange(ctx: Ctx): Cmd { 36function applySourceChange(ctx: Ctx): Cmd {
37 return async (change: sourceChange.SourceChange) => { 37 return async (change: sourceChange.SourceChange, alternativeChanges: sourceChange.SourceChange[] | undefined) => {
38 sourceChange.applySourceChange(ctx, change); 38 sourceChange.applySourceChange(ctx, change, alternativeChanges);
39 }; 39 };
40} 40}
41 41
diff --git a/editors/code/src/source_change.ts b/editors/code/src/source_change.ts
index a336269ba..b19d325d5 100644
--- a/editors/code/src/source_change.ts
+++ b/editors/code/src/source_change.ts
@@ -9,7 +9,7 @@ export interface SourceChange {
9 cursorPosition?: lc.TextDocumentPositionParams; 9 cursorPosition?: lc.TextDocumentPositionParams;
10} 10}
11 11
12export async function applySourceChange(ctx: Ctx, change: SourceChange) { 12async function applySelectedSourceChange(ctx: Ctx, change: SourceChange) {
13 const client = ctx.client; 13 const client = ctx.client;
14 if (!client) return; 14 if (!client) return;
15 15
@@ -55,3 +55,13 @@ export async function applySourceChange(ctx: Ctx, change: SourceChange) {
55 ); 55 );
56 } 56 }
57} 57}
58
59export async function applySourceChange(ctx: Ctx, change: SourceChange, alternativeChanges: SourceChange[] | undefined) {
60 if (alternativeChanges !== undefined && alternativeChanges.length > 0) {
61 const selectedChange = await vscode.window.showQuickPick([change, ...alternativeChanges]);
62 if (!selectedChange) return;
63 await applySelectedSourceChange(ctx, selectedChange);
64 } else {
65 await applySelectedSourceChange(ctx, change);
66 }
67}