Add a basic context menu to the project panel

This commit is contained in:
Max Brunsfeld 2023-11-22 15:19:10 -08:00
parent 75891e83f3
commit bcf449d3fe
2 changed files with 98 additions and 75 deletions

View file

@ -8,10 +8,11 @@ use file_associations::FileAssociations;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use gpui::{ use gpui::{
actions, div, px, uniform_list, Action, AppContext, AssetSource, AsyncWindowContext, actions, div, overlay, px, uniform_list, Action, AppContext, AssetSource, AsyncWindowContext,
ClipboardItem, Div, EventEmitter, FocusHandle, Focusable, FocusableView, InteractiveElement, ClipboardItem, Div, EventEmitter, FocusHandle, Focusable, FocusableView, InteractiveElement,
Model, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Render, Stateful, Styled, Model, MouseButton, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Render,
Task, UniformListScrollHandle, View, ViewContext, VisualContext as _, WeakView, WindowContext, Stateful, Styled, Subscription, Task, UniformListScrollHandle, View, ViewContext,
VisualContext as _, WeakView, WindowContext,
}; };
use menu::{Confirm, SelectNext, SelectPrev}; use menu::{Confirm, SelectNext, SelectPrev};
use project::{ use project::{
@ -29,7 +30,7 @@ use std::{
sync::Arc, sync::Arc,
}; };
use theme::ActiveTheme as _; use theme::ActiveTheme as _;
use ui::{v_stack, IconElement, Label, ListItem}; use ui::{v_stack, ContextMenu, IconElement, Label, ListItem};
use unicase::UniCase; use unicase::UniCase;
use util::{maybe, ResultExt, TryFutureExt}; use util::{maybe, ResultExt, TryFutureExt};
use workspace::{ use workspace::{
@ -49,6 +50,7 @@ pub struct ProjectPanel {
last_worktree_root_id: Option<ProjectEntryId>, last_worktree_root_id: Option<ProjectEntryId>,
expanded_dir_ids: HashMap<WorktreeId, Vec<ProjectEntryId>>, expanded_dir_ids: HashMap<WorktreeId, Vec<ProjectEntryId>>,
selection: Option<Selection>, selection: Option<Selection>,
context_menu: Option<(View<ContextMenu>, Point<Pixels>, Subscription)>,
edit_state: Option<EditState>, edit_state: Option<EditState>,
filename_editor: View<Editor>, filename_editor: View<Editor>,
clipboard_entry: Option<ClipboardEntry>, clipboard_entry: Option<ClipboardEntry>,
@ -231,6 +233,7 @@ impl ProjectPanel {
expanded_dir_ids: Default::default(), expanded_dir_ids: Default::default(),
selection: None, selection: None,
edit_state: None, edit_state: None,
context_menu: None,
filename_editor, filename_editor,
clipboard_entry: None, clipboard_entry: None,
// context_menu: cx.add_view(|cx| ContextMenu::new(view_id, cx)), // context_menu: cx.add_view(|cx| ContextMenu::new(view_id, cx)),
@ -366,80 +369,92 @@ impl ProjectPanel {
fn deploy_context_menu( fn deploy_context_menu(
&mut self, &mut self,
_position: Point<Pixels>, position: Point<Pixels>,
_entry_id: ProjectEntryId, entry_id: ProjectEntryId,
_cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
// todo!() let this = cx.view().clone();
// let project = self.project.read(cx); let project = self.project.read(cx);
// let worktree_id = if let Some(id) = project.worktree_id_for_entry(entry_id, cx) { let worktree_id = if let Some(id) = project.worktree_id_for_entry(entry_id, cx) {
// id id
// } else { } else {
// return; return;
// }; };
// self.selection = Some(Selection { self.selection = Some(Selection {
// worktree_id, worktree_id,
// entry_id, entry_id,
// }); });
// let mut menu_entries = Vec::new(); if let Some((worktree, entry)) = self.selected_entry(cx) {
// if let Some((worktree, entry)) = self.selected_entry(cx) { let is_root = Some(entry) == worktree.root_entry();
// let is_root = Some(entry) == worktree.root_entry(); let is_dir = entry.is_dir();
// if !project.is_remote() { let worktree_id = worktree.id();
// menu_entries.push(ContextMenuItem::action( let is_local = project.is_local();
// "Add Folder to Project",
// workspace::AddFolderToProject,
// ));
// if is_root {
// let project = self.project.clone();
// menu_entries.push(ContextMenuItem::handler("Remove from Project", move |cx| {
// project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
// }));
// }
// }
// menu_entries.push(ContextMenuItem::action("New File", NewFile));
// menu_entries.push(ContextMenuItem::action("New Folder", NewDirectory));
// menu_entries.push(ContextMenuItem::Separator);
// menu_entries.push(ContextMenuItem::action("Cut", Cut));
// menu_entries.push(ContextMenuItem::action("Copy", Copy));
// if let Some(clipboard_entry) = self.clipboard_entry {
// if clipboard_entry.worktree_id() == worktree.id() {
// menu_entries.push(ContextMenuItem::action("Paste", Paste));
// }
// }
// menu_entries.push(ContextMenuItem::Separator);
// menu_entries.push(ContextMenuItem::action("Copy Path", CopyPath));
// menu_entries.push(ContextMenuItem::action(
// "Copy Relative Path",
// CopyRelativePath,
// ));
// if entry.is_dir() { let context_menu = ContextMenu::build(cx, |mut menu, cx| {
// menu_entries.push(ContextMenuItem::Separator); if is_local {
// } menu = menu.action(
// menu_entries.push(ContextMenuItem::action("Reveal in Finder", RevealInFinder)); "Add Folder to Project",
// if entry.is_dir() { Box::new(workspace::AddFolderToProject),
// menu_entries.push(ContextMenuItem::action("Open in Terminal", OpenInTerminal)); );
// menu_entries.push(ContextMenuItem::action( if is_root {
// "Search Inside", menu = menu.entry(
// NewSearchInDirectory, "Remove from Project",
// )); cx.listener_for(&this, move |this, _, cx| {
// } this.project.update(cx, |project, cx| {
project.remove_worktree(worktree_id, cx)
});
}),
);
}
}
// menu_entries.push(ContextMenuItem::Separator); menu = menu
// menu_entries.push(ContextMenuItem::action("Rename", Rename)); .action("New File", Box::new(NewFile))
// if !is_root { .action("New Folder", Box::new(NewDirectory))
// menu_entries.push(ContextMenuItem::action("Delete", Delete)); .separator()
// } .action("Cut", Box::new(Cut))
// } .action("Copy", Box::new(Copy));
// // self.context_menu.update(cx, |menu, cx| { if let Some(clipboard_entry) = self.clipboard_entry {
// // menu.show(position, AnchorCorner::TopLeft, menu_entries, cx); if clipboard_entry.worktree_id() == worktree_id {
// // }); menu = menu.action("Paste", Box::new(Paste));
}
}
// cx.notify(); 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
});
cx.focus_view(&context_menu);
let subscription = cx.on_blur(&context_menu.focus_handle(cx), |this, cx| {
this.context_menu.take();
cx.notify();
});
self.context_menu = Some((context_menu, position, subscription));
}
cx.notify();
} }
fn expand_selected_entry(&mut self, _: &ExpandSelectedEntry, cx: &mut ViewContext<Self>) { fn expand_selected_entry(&mut self, _: &ExpandSelectedEntry, cx: &mut ViewContext<Self>) {
@ -1379,6 +1394,9 @@ impl ProjectPanel {
.ml_1(), .ml_1(),
) )
.on_click(cx.listener(move |this, event: &gpui::ClickEvent, cx| { .on_click(cx.listener(move |this, event: &gpui::ClickEvent, cx| {
if event.down.button == MouseButton::Right {
return;
}
if !show_editor { if !show_editor {
if kind.is_dir() { if kind.is_dir() {
this.toggle_expanded(entry_id, cx); this.toggle_expanded(entry_id, cx);
@ -1415,6 +1433,7 @@ impl Render for ProjectPanel {
div() div()
.id("project-panel") .id("project-panel")
.size_full() .size_full()
.relative()
.key_context("ProjectPanel") .key_context("ProjectPanel")
.on_action(cx.listener(Self::select_next)) .on_action(cx.listener(Self::select_next))
.on_action(cx.listener(Self::select_prev)) .on_action(cx.listener(Self::select_prev))
@ -1458,6 +1477,12 @@ impl Render for ProjectPanel {
.size_full() .size_full()
.track_scroll(self.list.clone()), .track_scroll(self.list.clone()),
) )
.children(self.context_menu.as_ref().map(|(menu, position, _)| {
overlay()
.position(*position)
.anchor(gpui::AnchorCorner::BottomLeft)
.child(menu.clone())
}))
} else { } else {
v_stack() v_stack()
.id("empty-project_panel") .id("empty-project_panel")

View file

@ -1,13 +1,10 @@
use std::cell::RefCell; use crate::{prelude::*, v_stack, Label, List, ListItem, ListSeparator, ListSubHeader};
use std::rc::Rc;
use crate::{prelude::*, v_stack, Label, List};
use crate::{ListItem, ListSeparator, ListSubHeader};
use gpui::{ use gpui::{
overlay, px, Action, AnchorCorner, AnyElement, AppContext, Bounds, ClickEvent, DismissEvent, overlay, px, Action, AnchorCorner, AnyElement, AppContext, Bounds, ClickEvent, DismissEvent,
DispatchPhase, Div, EventEmitter, FocusHandle, FocusableView, IntoElement, LayoutId, DispatchPhase, Div, EventEmitter, FocusHandle, FocusableView, IntoElement, LayoutId,
ManagedView, MouseButton, MouseDownEvent, Pixels, Point, Render, View, VisualContext, ManagedView, MouseButton, MouseDownEvent, Pixels, Point, Render, View, VisualContext,
}; };
use std::{cell::RefCell, rc::Rc};
pub enum ContextMenuItem { pub enum ContextMenuItem {
Separator, Separator,
@ -177,6 +174,7 @@ pub struct MenuHandleState<M> {
child_element: Option<AnyElement>, child_element: Option<AnyElement>,
menu_element: Option<AnyElement>, menu_element: Option<AnyElement>,
} }
impl<M: ManagedView> Element for MenuHandle<M> { impl<M: ManagedView> Element for MenuHandle<M> {
type State = MenuHandleState<M>; type State = MenuHandleState<M>;