Wire up keyboard interaction in code actions menu

Antonio Scandurra created

Change summary

crates/editor2/src/editor.rs  | 226 ++++++++++++++++++------------------
crates/editor2/src/element.rs |  12 +
2 files changed, 126 insertions(+), 112 deletions(-)

Detailed changes

crates/editor2/src/editor.rs 🔗

@@ -40,10 +40,11 @@ use fuzzy::{StringMatch, StringMatchCandidate};
 use git::diff_hunk_to_display;
 use gpui::{
     action, actions, div, point, px, relative, rems, size, uniform_list, AnyElement, AppContext,
-    BackgroundExecutor, Bounds, ClipboardItem, Component, Context, DispatchContext, EventEmitter,
-    FocusHandle, FontFeatures, FontStyle, FontWeight, HighlightStyle, Hsla, InputHandler, Model,
-    ParentElement, Pixels, Render, StatelessInteractive, Styled, Subscription, Task, TextStyle,
-    UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, WindowContext,
+    AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Component, Context,
+    DispatchContext, EventEmitter, FocusHandle, FontFeatures, FontStyle, FontWeight,
+    HighlightStyle, Hsla, InputHandler, Model, ParentElement, Pixels, Render, StatelessInteractive,
+    Styled, Subscription, Task, TextStyle, UniformListScrollHandle, View, ViewContext,
+    VisualContext, WeakView, WindowContext,
 };
 use highlight_matching_bracket::refresh_matching_bracket_highlights;
 use hover_popover::{hide_hover, HoverState};
@@ -68,7 +69,7 @@ pub use multi_buffer::{
 };
 use ordered_float::OrderedFloat;
 use parking_lot::{Mutex, RwLock};
-use project::{FormatTrigger, Location, Project};
+use project::{FormatTrigger, Location, Project, ProjectTransaction};
 use rand::prelude::*;
 use rpc::proto::*;
 use scroll::{
@@ -3901,6 +3902,7 @@ impl Editor {
                                 scroll_handle: UniformListScrollHandle::default(),
                                 deployed_from_indicator,
                             }));
+                        cx.notify();
                     }
                 }
             })?;
@@ -3910,117 +3912,121 @@ impl Editor {
         .detach_and_log_err(cx);
     }
 
-    //     pub fn confirm_code_action(
-    //         workspace: &mut Workspace,
-    //         action: &ConfirmCodeAction,
-    //         cx: &mut ViewContext<Workspace>,
-    //     ) -> Option<Task<Result<()>>> {
-    //         let editor = workspace.active_item(cx)?.act_as::<Editor>(cx)?;
-    //         let actions_menu = if let ContextMenu::CodeActions(menu) =
-    //             editor.update(cx, |editor, cx| editor.hide_context_menu(cx))?
-    //         {
-    //             menu
-    //         } else {
-    //             return None;
-    //         };
-    //         let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
-    //         let action = actions_menu.actions.get(action_ix)?.clone();
-    //         let title = action.lsp_action.title.clone();
-    //         let buffer = actions_menu.buffer;
-
-    //         let apply_code_actions = workspace.project().clone().update(cx, |project, cx| {
-    //             project.apply_code_action(buffer, action, true, cx)
-    //         });
-    //         let editor = editor.downgrade();
-    //         Some(cx.spawn(|workspace, cx| async move {
-    //             let project_transaction = apply_code_actions.await?;
-    //             Self::open_project_transaction(&editor, workspace, project_transaction, title, cx).await
-    //         }))
-    //     }
-
-    //     async fn open_project_transaction(
-    //         this: &WeakViewHandle<Editor
-    //         workspace: WeakViewHandle<Workspace
-    //         transaction: ProjectTransaction,
-    //         title: String,
-    //         mut cx: AsyncAppContext,
-    //     ) -> Result<()> {
-    //         let replica_id = this.read_with(&cx, |this, cx| this.replica_id(cx))?;
-
-    //         let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
-    //         entries.sort_unstable_by_key(|(buffer, _)| {
-    //             buffer.read_with(&cx, |buffer, _| buffer.file().map(|f| f.path().clone()))
-    //         });
+    pub fn confirm_code_action(
+        &mut self,
+        action: &ConfirmCodeAction,
+        cx: &mut ViewContext<Self>,
+    ) -> Option<Task<Result<()>>> {
+        let actions_menu = if let ContextMenu::CodeActions(menu) = self.hide_context_menu(cx)? {
+            menu
+        } else {
+            return None;
+        };
+        let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
+        let action = actions_menu.actions.get(action_ix)?.clone();
+        let title = action.lsp_action.title.clone();
+        let buffer = actions_menu.buffer;
+        let workspace = self.workspace()?;
 
-    //         // If the project transaction's edits are all contained within this editor, then
-    //         // avoid opening a new editor to display them.
+        let apply_code_actions = workspace
+            .read(cx)
+            .project()
+            .clone()
+            .update(cx, |project, cx| {
+                project.apply_code_action(buffer, action, true, cx)
+            });
+        let workspace = workspace.downgrade();
+        Some(cx.spawn(|editor, cx| async move {
+            let project_transaction = apply_code_actions.await?;
+            Self::open_project_transaction(&editor, workspace, project_transaction, title, cx).await
+        }))
+    }
 
-    //         if let Some((buffer, transaction)) = entries.first() {
-    //             if entries.len() == 1 {
-    //                 let excerpt = this.read_with(&cx, |editor, cx| {
-    //                     editor
-    //                         .buffer()
-    //                         .read(cx)
-    //                         .excerpt_containing(editor.selections.newest_anchor().head(), cx)
-    //                 })?;
-    //                 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
-    //                     if excerpted_buffer == *buffer {
-    //                         let all_edits_within_excerpt = buffer.read_with(&cx, |buffer, _| {
-    //                             let excerpt_range = excerpt_range.to_offset(buffer);
-    //                             buffer
-    //                                 .edited_ranges_for_transaction::<usize>(transaction)
-    //                                 .all(|range| {
-    //                                     excerpt_range.start <= range.start
-    //                                         && excerpt_range.end >= range.end
-    //                                 })
-    //                         });
+    async fn open_project_transaction(
+        this: &WeakView<Editor>,
+        workspace: WeakView<Workspace>,
+        transaction: ProjectTransaction,
+        title: String,
+        mut cx: AsyncWindowContext,
+    ) -> Result<()> {
+        let replica_id = this.update(&mut cx, |this, cx| this.replica_id(cx))?;
 
-    //                         if all_edits_within_excerpt {
-    //                             return Ok(());
-    //                         }
-    //                     }
-    //                 }
-    //             }
-    //         } else {
-    //             return Ok(());
-    //         }
+        let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
+        cx.update(|_, cx| {
+            entries.sort_unstable_by_key(|(buffer, _)| {
+                buffer.read(cx).file().map(|f| f.path().clone())
+            });
+        })?;
+
+        // If the project transaction's edits are all contained within this editor, then
+        // avoid opening a new editor to display them.
+
+        if let Some((buffer, transaction)) = entries.first() {
+            if entries.len() == 1 {
+                let excerpt = this.update(&mut cx, |editor, cx| {
+                    editor
+                        .buffer()
+                        .read(cx)
+                        .excerpt_containing(editor.selections.newest_anchor().head(), cx)
+                })?;
+                if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
+                    if excerpted_buffer == *buffer {
+                        let all_edits_within_excerpt = buffer.read_with(&cx, |buffer, _| {
+                            let excerpt_range = excerpt_range.to_offset(buffer);
+                            buffer
+                                .edited_ranges_for_transaction::<usize>(transaction)
+                                .all(|range| {
+                                    excerpt_range.start <= range.start
+                                        && excerpt_range.end >= range.end
+                                })
+                        })?;
 
-    //         let mut ranges_to_highlight = Vec::new();
-    //         let excerpt_buffer = cx.build_model(|cx| {
-    //             let mut multibuffer = MultiBuffer::new(replica_id).with_title(title);
-    //             for (buffer_handle, transaction) in &entries {
-    //                 let buffer = buffer_handle.read(cx);
-    //                 ranges_to_highlight.extend(
-    //                     multibuffer.push_excerpts_with_context_lines(
-    //                         buffer_handle.clone(),
-    //                         buffer
-    //                             .edited_ranges_for_transaction::<usize>(transaction)
-    //                             .collect(),
-    //                         1,
-    //                         cx,
-    //                     ),
-    //                 );
-    //             }
-    //             multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
-    //             multibuffer
-    //         });
+                        if all_edits_within_excerpt {
+                            return Ok(());
+                        }
+                    }
+                }
+            }
+        } else {
+            return Ok(());
+        }
 
-    //         workspace.update(&mut cx, |workspace, cx| {
-    //             let project = workspace.project().clone();
-    //             let editor =
-    //                 cx.add_view(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), cx));
-    //             workspace.add_item(Box::new(editor.clone()), cx);
-    //             editor.update(cx, |editor, cx| {
-    //                 editor.highlight_background::<Self>(
-    //                     ranges_to_highlight,
-    //                     |theme| theme.editor.highlighted_line_background,
-    //                     cx,
-    //                 );
-    //             });
-    //         })?;
+        let mut ranges_to_highlight = Vec::new();
+        let excerpt_buffer = cx.build_model(|cx| {
+            let mut multibuffer = MultiBuffer::new(replica_id).with_title(title);
+            for (buffer_handle, transaction) in &entries {
+                let buffer = buffer_handle.read(cx);
+                ranges_to_highlight.extend(
+                    multibuffer.push_excerpts_with_context_lines(
+                        buffer_handle.clone(),
+                        buffer
+                            .edited_ranges_for_transaction::<usize>(transaction)
+                            .collect(),
+                        1,
+                        cx,
+                    ),
+                );
+            }
+            multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
+            multibuffer
+        })?;
+
+        workspace.update(&mut cx, |workspace, cx| {
+            let project = workspace.project().clone();
+            let editor =
+                cx.build_view(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), cx));
+            workspace.add_item(Box::new(editor.clone()), cx);
+            editor.update(cx, |editor, cx| {
+                editor.highlight_background::<Self>(
+                    ranges_to_highlight,
+                    |theme| theme.editor_highlighted_line_background,
+                    cx,
+                );
+            });
+        })?;
 
-    //         Ok(())
-    //     }
+        Ok(())
+    }
 
     fn refresh_code_actions(&mut self, cx: &mut ViewContext<Self>) -> Option<()> {
         let project = self.project.clone()?;

crates/editor2/src/element.rs 🔗

@@ -4126,7 +4126,7 @@ fn build_key_listeners(
         build_action_listener(Editor::unfold_at),
         build_action_listener(Editor::fold_selected_ranges),
         build_action_listener(Editor::show_completions),
-        // build_action_listener(Editor::toggle_code_actions), todo!()
+        build_action_listener(Editor::toggle_code_actions),
         // build_action_listener(Editor::open_excerpts), todo!()
         build_action_listener(Editor::toggle_soft_wrap),
         build_action_listener(Editor::toggle_inlay_hints),
@@ -4142,13 +4142,21 @@ fn build_key_listeners(
         build_action_listener(Editor::restart_language_server),
         build_action_listener(Editor::show_character_palette),
         // build_action_listener(Editor::confirm_completion), todo!()
-        // build_action_listener(Editor::confirm_code_action), todo!()
+        build_action_listener(|editor, action, cx| {
+            editor
+                .confirm_code_action(action, cx)
+                .map(|task| task.detach_and_log_err(cx));
+        }),
         // build_action_listener(Editor::rename), todo!()
         // build_action_listener(Editor::confirm_rename), todo!()
         // build_action_listener(Editor::find_all_references), todo!()
         build_action_listener(Editor::next_copilot_suggestion),
         build_action_listener(Editor::previous_copilot_suggestion),
         build_action_listener(Editor::copilot_suggest),
+        build_action_listener(Editor::context_menu_first),
+        build_action_listener(Editor::context_menu_prev),
+        build_action_listener(Editor::context_menu_next),
+        build_action_listener(Editor::context_menu_last),
         build_key_listener(
             move |editor, key_down: &KeyDownEvent, dispatch_context, phase, cx| {
                 if phase == DispatchPhase::Bubble {