diff options
-rw-r--r-- | crates/ra_assists/src/assist_ctx.rs | 45 | ||||
-rw-r--r-- | crates/ra_assists/src/assists/inline_local_variable.rs | 4 | ||||
-rw-r--r-- | crates/ra_assists/src/doc_tests.rs | 6 | ||||
-rw-r--r-- | crates/ra_assists/src/lib.rs | 7 | ||||
-rw-r--r-- | crates/ra_ide/src/assists.rs | 35 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/main_loop/handlers.rs | 12 | ||||
-rw-r--r-- | editors/code/src/commands/index.ts | 4 | ||||
-rw-r--r-- | editors/code/src/source_change.ts | 12 |
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)] |
15 | pub(crate) enum Assist { | 15 | pub(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)] |
131 | pub(crate) struct AssistBuilder { | 156 | pub(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 | ||
137 | impl AssistBuilder { | 163 | impl 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 | ||
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..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)] |
37 | pub struct AssistAction { | 37 | pub 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. |
67 | pub fn assists<H>(db: &H, range: FileRange) -> Vec<(AssistLabel, AssistAction)> | 68 | pub fn assists<H>(db: &H, range: FileRange) -> Vec<(AssistLabel, AssistAction, Vec<AssistAction>)> |
68 | where | 69 | where |
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 | ||
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 | pub use ra_assists::AssistId; | 7 | pub use ra_assists::AssistId; |
8 | use ra_assists::{AssistAction, AssistLabel}; | ||
8 | 9 | ||
9 | #[derive(Debug)] | 10 | #[derive(Debug)] |
10 | pub struct Assist { | 11 | pub 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 | ||
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_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 | |||
36 | fn 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 | ||
36 | function applySourceChange(ctx: Ctx): Cmd { | 36 | function 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 | ||
12 | export async function applySourceChange(ctx: Ctx, change: SourceChange) { | 12 | async 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 | |||
59 | export 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 | } | ||