Fix popin for project panel by pre-resolving keybindings in terms of the project panel

Mikayla created

Change summary

crates/project_panel/src/project_panel.rs | 114 +++++++++++-------------
crates/ui/src/components/context_menu.rs  |  44 +++++++++
2 files changed, 96 insertions(+), 62 deletions(-)

Detailed changes

crates/project_panel/src/project_panel.rs 🔗

@@ -381,67 +381,59 @@ impl ProjectPanel {
             let is_local = project.is_local();
             let is_read_only = project.is_read_only();
 
-            let context_menu = ContextMenu::build(cx, |mut menu, cx| {
-                if is_read_only {
-                    menu = menu.action("Copy Relative Path", Box::new(CopyRelativePath));
-                    if is_dir {
-                        menu = menu.action("Search Inside", Box::new(NewSearchInDirectory))
-                    }
-
-                    return menu;
-                }
-
-                if is_local {
-                    menu = menu.action(
-                        "Add Folder to Project",
-                        Box::new(workspace::AddFolderToProject),
-                    );
-                    if is_root {
-                        menu = menu.entry(
-                            "Remove from Project",
-                            None,
-                            cx.handler_for(&this, move |this, cx| {
-                                this.project.update(cx, |project, cx| {
-                                    project.remove_worktree(worktree_id, cx)
-                                });
-                            }),
-                        );
-                    }
-                }
-
-                menu = menu
-                    .action("New File", Box::new(NewFile))
-                    .action("New Folder", Box::new(NewDirectory))
-                    .separator()
-                    .action("Cut", Box::new(Cut))
-                    .action("Copy", Box::new(Copy));
-
-                if let Some(clipboard_entry) = self.clipboard_entry {
-                    if clipboard_entry.worktree_id() == worktree_id {
-                        menu = menu.action("Paste", Box::new(Paste));
-                    }
-                }
-
-                menu = menu
-                    .separator()
-                    .action("Copy Path", Box::new(CopyPath))
-                    .action("Copy Relative Path", Box::new(CopyRelativePath))
-                    .separator()
-                    .action("Reveal in Finder", Box::new(RevealInFinder));
-
-                if is_dir {
-                    menu = menu
-                        .action("Open in Terminal", Box::new(OpenInTerminal))
-                        .action("Search Inside", Box::new(NewSearchInDirectory))
-                }
-
-                menu = menu.separator().action("Rename", Box::new(Rename));
-
-                if !is_root {
-                    menu = menu.action("Delete", Box::new(Delete));
-                }
-
-                menu
+            let context_menu = ContextMenu::build(cx, |menu, cx| {
+                menu.context(self.focus_handle.clone()).then_if_else(
+                    is_read_only,
+                    |menu| {
+                        menu.action("Copy Relative Path", Box::new(CopyRelativePath))
+                            .then_if(is_dir, |menu| {
+                                menu.action("Search Inside", Box::new(NewSearchInDirectory))
+                            })
+                    },
+                    |menu| {
+                        menu.then_if(is_local, |menu| {
+                            menu.action(
+                                "Add Folder to Project",
+                                Box::new(workspace::AddFolderToProject),
+                            )
+                            .then_if(is_root, |menu| {
+                                menu.entry(
+                                    "Remove from Project",
+                                    None,
+                                    cx.handler_for(&this, move |this, cx| {
+                                        this.project.update(cx, |project, cx| {
+                                            project.remove_worktree(worktree_id, cx)
+                                        });
+                                    }),
+                                )
+                            })
+                        })
+                        .action("New File", Box::new(NewFile))
+                        .action("New Folder", Box::new(NewDirectory))
+                        .separator()
+                        .action("Cut", Box::new(Cut))
+                        .action("Copy", Box::new(Copy))
+                        .if_some(self.clipboard_entry, |menu, entry| {
+                            menu.then_if(entry.worktree_id() == worktree_id, |menu| {
+                                menu.action("Paste", Box::new(Paste))
+                            })
+                        })
+                        .separator()
+                        .action("Copy Path", Box::new(CopyPath))
+                        .action("Copy Relative Path", Box::new(CopyRelativePath))
+                        .separator()
+                        .action("Reveal in Finder", Box::new(RevealInFinder))
+                        .then_if(is_dir, |menu| {
+                            menu
+                                .action("Open in Terminal", Box::new(OpenInTerminal))
+                                .action("Search Inside", Box::new(NewSearchInDirectory))
+                        })
+                        .separator().action("Rename", Box::new(Rename))
+                        .then_if(!is_root, |menu| {
+                            menu.action("Delete", Box::new(Delete))
+                        })
+                    },
+                )
             });
 
             cx.focus_view(&context_menu);

crates/ui/src/components/context_menu.rs 🔗

@@ -27,6 +27,7 @@ enum ContextMenuItem {
 pub struct ContextMenu {
     items: Vec<ContextMenuItem>,
     focus_handle: FocusHandle,
+    action_context: Option<FocusHandle>,
     selected_index: Option<usize>,
     delayed: bool,
     clicked: bool,
@@ -56,6 +57,7 @@ impl ContextMenu {
                 Self {
                     items: Default::default(),
                     focus_handle,
+                    action_context: None,
                     selected_index: None,
                     delayed: false,
                     clicked: false,
@@ -66,6 +68,39 @@ impl ContextMenu {
         })
     }
 
+    pub fn if_some<T>(self, condition: Option<T>, f: impl FnOnce(Self, T) -> Self) -> Self {
+        if let Some(t) = condition {
+            f(self, t)
+        } else {
+            self
+        }
+    }
+
+    pub fn then_if_else(self, condition: bool, then: impl FnOnce(Self) -> Self, otherwise: impl FnOnce(Self) -> Self) -> Self {
+        if condition {
+            then(self)
+        } else {
+            otherwise(self)
+        }
+    }
+
+    pub fn then_if(self, condition: bool, f: impl FnOnce(Self) -> Self) -> Self {
+        if condition {
+            f(self)
+        } else {
+            self
+        }
+    }
+
+    pub fn map(self, f: impl FnOnce(Self) -> Self) -> Self {
+        f(self)
+    }
+
+    pub fn context(mut self, focus: FocusHandle) -> Self {
+        self.action_context = Some(focus);
+        self
+    }
+
     pub fn header(mut self, title: impl Into<SharedString>) -> Self {
         self.items.push(ContextMenuItem::Header(title.into()));
         self
@@ -305,7 +340,14 @@ impl Render for ContextMenu {
                                         .child(label_element)
                                         .debug_selector(|| format!("MENU_ITEM-{}", label))
                                         .children(action.as_ref().and_then(|action| {
-                                            KeyBinding::for_action(&**action, cx)
+                                            self.action_context
+                                                .as_ref()
+                                                .map(|focus| {
+                                                    KeyBinding::for_action_in(&**action, focus, cx)
+                                                })
+                                                .unwrap_or_else(|| {
+                                                    KeyBinding::for_action(&**action, cx)
+                                                })
                                                 .map(|binding| div().ml_1().child(binding))
                                         })),
                                 )