]> git.lizzy.rs Git - rust.git/commitdiff
Another attempt to add multiple edits
authorKirill Bulatov <mail4score@gmail.com>
Wed, 1 Jan 2020 23:39:01 +0000 (01:39 +0200)
committerKirill Bulatov <mail4score@gmail.com>
Wed, 15 Jan 2020 18:16:27 +0000 (20:16 +0200)
crates/ra_assists/src/assist_ctx.rs
crates/ra_assists/src/assists/inline_local_variable.rs
crates/ra_assists/src/doc_tests.rs
crates/ra_assists/src/lib.rs
crates/ra_ide/src/assists.rs
crates/ra_lsp_server/src/main_loop/handlers.rs
editors/code/src/commands/index.ts
editors/code/src/source_change.ts

index 1a65b5dc070ad920a58a9242dec63e495226d925..879216a3669a7ecc8d228c7dfeb66df31b5ceb8c 100644 (file)
@@ -14,7 +14,7 @@
 #[derive(Clone, Debug)]
 pub(crate) enum Assist {
     Unresolved { label: AssistLabel },
-    Resolved { label: AssistLabel, action: AssistAction },
+    Resolved { label: AssistLabel, action: AssistAction, alternative_actions: Vec<AssistAction> },
 }
 
 /// `AssistCtx` allows to apply an assist or check if it could be applied.
@@ -81,18 +81,43 @@ pub(crate) fn add_assist(
         self,
         id: AssistId,
         label: impl Into<String>,
-        f: impl FnOnce(&mut AssistBuilder),
+        f: impl FnOnce(&mut ActionBuilder),
     ) -> Option<Assist> {
         let label = AssistLabel { label: label.into(), id };
         assert!(label.label.chars().nth(0).unwrap().is_uppercase());
 
         let assist = if self.should_compute_edit {
             let action = {
-                let mut edit = AssistBuilder::default();
+                let mut edit = ActionBuilder::default();
                 f(&mut edit);
                 edit.build()
             };
-            Assist::Resolved { label, action }
+            Assist::Resolved { label, action, alternative_actions: Vec::default() }
+        } else {
+            Assist::Unresolved { label }
+        };
+
+        Some(assist)
+    }
+
+    #[allow(dead_code)] // will be used for auto import assist with multiple actions
+    pub(crate) fn add_assist_group(
+        self,
+        id: AssistId,
+        label: impl Into<String>,
+        f: impl FnOnce() -> (ActionBuilder, Vec<ActionBuilder>),
+    ) -> Option<Assist> {
+        let label = AssistLabel { label: label.into(), id };
+        let assist = if self.should_compute_edit {
+            let (action, alternative_actions) = f();
+            Assist::Resolved {
+                label,
+                action: action.build(),
+                alternative_actions: alternative_actions
+                    .into_iter()
+                    .map(ActionBuilder::build)
+                    .collect(),
+            }
         } else {
             Assist::Unresolved { label }
         };
@@ -128,13 +153,20 @@ pub(crate) fn covering_node_for_range(&self, range: TextRange) -> SyntaxElement
 }
 
 #[derive(Default)]
-pub(crate) struct AssistBuilder {
+pub(crate) struct ActionBuilder {
     edit: TextEditBuilder,
     cursor_position: Option<TextUnit>,
     target: Option<TextRange>,
+    label: Option<String>,
 }
 
-impl AssistBuilder {
+impl ActionBuilder {
+    #[allow(dead_code)]
+    /// Adds a custom label to the action, if it needs to be different from the assist label
+    pub fn label(&mut self, label: impl Into<String>) {
+        self.label = Some(label.into())
+    }
+
     /// Replaces specified `range` of text with a given string.
     pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) {
         self.edit.replace(range, replace_with.into())
@@ -193,6 +225,7 @@ fn build(self) -> AssistAction {
             edit: self.edit.finish(),
             cursor_position: self.cursor_position,
             target: self.target,
+            label: self.label,
         }
     }
 }
index 164aee90cdaaf29ecb3f54f7a6095a363a521240..45e0f983fecb3d44fca64a2f11f22749e4e7da4a 100644 (file)
@@ -4,7 +4,7 @@
     TextRange,
 };
 
-use crate::assist_ctx::AssistBuilder;
+use crate::assist_ctx::ActionBuilder;
 use crate::{Assist, AssistCtx, AssistId};
 
 // Assist: inline_local_variable
@@ -94,7 +94,7 @@ pub(crate) fn inline_local_varialbe(ctx: AssistCtx<impl HirDatabase>) -> Option<
     ctx.add_assist(
         AssistId("inline_local_variable"),
         "Inline variable",
-        move |edit: &mut AssistBuilder| {
+        move |edit: &mut ActionBuilder| {
             edit.delete(delete_range);
             for (desc, should_wrap) in refs.iter().zip(wrap_in_parens) {
                 if should_wrap {
index a8f8446cb637455120290576f9f4262c9ebb73c3..21bee228dadca58fdc2d52b97342b7374d263494 100644 (file)
@@ -15,16 +15,16 @@ fn check(assist_id: &str, before: &str, after: &str) {
     let (db, file_id) = TestDB::with_single_file(&before);
     let frange = FileRange { file_id, range: selection.into() };
 
-    let (_assist_id, action) = crate::assists(&db, frange)
+    let (_assist_id, action, _) = crate::assists(&db, frange)
         .into_iter()
-        .find(|(id, _)| id.id.0 == assist_id)
+        .find(|(id, _, _)| id.id.0 == assist_id)
         .unwrap_or_else(|| {
             panic!(
                 "\n\nAssist is not applicable: {}\nAvailable assists: {}",
                 assist_id,
                 crate::assists(&db, frange)
                     .into_iter()
-                    .map(|(id, _)| id.id.0)
+                    .map(|(id, _, _)| id.id.0)
                     .collect::<Vec<_>>()
                     .join(", ")
             )
index 150b34ac7124282daf3b70ed75f89ebf2d2bce29..95a530821ec6e3d73d1dbf5492ee86caefd38c98 100644 (file)
@@ -35,6 +35,7 @@ pub struct AssistLabel {
 
 #[derive(Debug, Clone)]
 pub struct AssistAction {
+    pub label: Option<String>,
     pub edit: TextEdit,
     pub cursor_position: Option<TextUnit>,
     pub target: Option<TextRange>,
@@ -64,7 +65,7 @@ pub fn applicable_assists<H>(db: &H, range: FileRange) -> Vec<AssistLabel>
 ///
 /// Assists are returned in the "resolved" state, that is with edit fully
 /// computed.
-pub fn assists<H>(db: &H, range: FileRange) -> Vec<(AssistLabel, AssistAction)>
+pub fn assists<H>(db: &H, range: FileRange) -> Vec<(AssistLabel, AssistAction, Vec<AssistAction>)>
 where
     H: HirDatabase + 'static,
 {
@@ -75,7 +76,9 @@ pub fn assists<H>(db: &H, range: FileRange) -> Vec<(AssistLabel, AssistAction)>
             .iter()
             .filter_map(|f| f(ctx.clone()))
             .map(|a| match a {
-                Assist::Resolved { label, action } => (label, action),
+                Assist::Resolved { label, action, alternative_actions } => {
+                    (label, action, alternative_actions)
+                }
                 Assist::Unresolved { .. } => unreachable!(),
             })
             .collect::<Vec<_>>();
index e00589733b7a6c96173b03cabe26452523ac9442..db6e4e8b704e50634ea1969398fbaa6f87dbe887 100644 (file)
@@ -2,27 +2,46 @@
 
 use ra_db::{FilePosition, FileRange};
 
-use crate::{db::RootDatabase, SourceChange, SourceFileEdit};
+use crate::{db::RootDatabase, FileId, SourceChange, SourceFileEdit};
 
 pub use ra_assists::AssistId;
+use ra_assists::{AssistAction, AssistLabel};
 
 #[derive(Debug)]
 pub struct Assist {
     pub id: AssistId,
     pub change: SourceChange,
+    pub label: String,
+    pub alternative_changes: Vec<SourceChange>,
 }
 
 pub(crate) fn assists(db: &RootDatabase, frange: FileRange) -> Vec<Assist> {
     ra_assists::assists(db, frange)
         .into_iter()
-        .map(|(label, action)| {
+        .map(|(assist_label, action, alternative_actions)| {
             let file_id = frange.file_id;
-            let file_edit = SourceFileEdit { file_id, edit: action.edit };
-            let id = label.id;
-            let change = SourceChange::source_file_edit(label.label, file_edit).with_cursor_opt(
-                action.cursor_position.map(|offset| FilePosition { offset, file_id }),
-            );
-            Assist { id, change }
+            Assist {
+                id: assist_label.id,
+                label: assist_label.label.clone(),
+                change: action_to_edit(action, file_id, &assist_label),
+                alternative_changes: alternative_actions
+                    .into_iter()
+                    .map(|action| action_to_edit(action, file_id, &assist_label))
+                    .collect(),
+            }
         })
         .collect()
 }
+
+fn action_to_edit(
+    action: AssistAction,
+    file_id: FileId,
+    assist_label: &AssistLabel,
+) -> SourceChange {
+    let file_edit = SourceFileEdit { file_id, edit: action.edit };
+    SourceChange::source_file_edit(
+        action.label.unwrap_or_else(|| assist_label.label.clone()),
+        file_edit,
+    )
+    .with_cursor_opt(action.cursor_position.map(|offset| FilePosition { offset, file_id }))
+}
index f2db575eabc317ca8a843985b372f4b5f78ae0c2..ec3c0a557ae74c6487733d3e906d129df016f619 100644 (file)
@@ -644,7 +644,6 @@ pub fn handle_code_action(
     let line_index = world.analysis().file_line_index(file_id)?;
     let range = params.range.conv_with(&line_index);
 
-    let assists = world.analysis().assists(FileRange { file_id, range })?.into_iter();
     let diagnostics = world.analysis().diagnostics(file_id)?;
     let mut res = CodeActionResponse::default();
 
@@ -697,14 +696,19 @@ pub fn handle_code_action(
         res.push(action.into());
     }
 
-    for assist in assists {
-        let title = assist.change.label.clone();
+    for assist in world.analysis().assists(FileRange { file_id, range })?.into_iter() {
+        let title = assist.label.clone();
         let edit = assist.change.try_conv_with(&world)?;
+        let alternative_edits = assist
+            .alternative_changes
+            .into_iter()
+            .map(|change| change.try_conv_with(&world))
+            .collect::<Result<Vec<_>>>()?;
 
         let command = Command {
             title,
             command: "rust-analyzer.applySourceChange".to_string(),
-            arguments: Some(vec![to_value(edit).unwrap()]),
+            arguments: Some(vec![to_value(edit).unwrap(), to_value(alternative_edits).unwrap()]),
         };
         let action = CodeAction {
             title: command.title.clone(),
index 9a1697dcbe6baf7b065c516b40cf5f5a75079e60..0ff708b1fed7b6c0305898c165a04d3f15642d46 100644 (file)
@@ -34,8 +34,8 @@ function showReferences(ctx: Ctx): Cmd {
 }
 
 function applySourceChange(ctx: Ctx): Cmd {
-    return async (change: sourceChange.SourceChange) => {
-        sourceChange.applySourceChange(ctx, change);
+    return async (change: sourceChange.SourceChange, alternativeChanges: sourceChange.SourceChange[] | undefined) => {
+        sourceChange.applySourceChange(ctx, change, alternativeChanges);
     };
 }
 
index a336269baa8bdec26658993a97eb709bb145b090..b19d325d5da39d25bd8214b746b375d7aeff0dcd 100644 (file)
@@ -9,7 +9,7 @@ export interface SourceChange {
     cursorPosition?: lc.TextDocumentPositionParams;
 }
 
-export async function applySourceChange(ctx: Ctx, change: SourceChange) {
+async function applySelectedSourceChange(ctx: Ctx, change: SourceChange) {
     const client = ctx.client;
     if (!client) return;
 
@@ -55,3 +55,13 @@ export async function applySourceChange(ctx: Ctx, change: SourceChange) {
         );
     }
 }
+
+export async function applySourceChange(ctx: Ctx, change: SourceChange, alternativeChanges: SourceChange[] | undefined) {
+    if (alternativeChanges !== undefined && alternativeChanges.length > 0) {
+        const selectedChange = await vscode.window.showQuickPick([change, ...alternativeChanges]);
+        if (!selectedChange) return;
+        await applySelectedSourceChange(ctx, selectedChange);
+    } else {
+        await applySelectedSourceChange(ctx, change);
+    }
+}