Merge pull request #1467 from zed-industries/new-button

Mikayla Maki created

Add discoverable 'New' button

Change summary

assets/keymaps/default.json             |  4 
crates/context_menu/src/context_menu.rs |  7 ++
crates/search/src/project_search.rs     |  8 ++
crates/terminal/src/terminal_view.rs    | 10 ++-
crates/theme/src/theme.rs               |  2 
crates/workspace/src/pane.rs            | 68 +++++++++++++++++++++++---
crates/workspace/src/workspace.rs       |  2 
crates/zed/src/menus.rs                 |  2 
styles/src/styleTree/contextMenu.ts     |  2 
9 files changed, 87 insertions(+), 18 deletions(-)

Detailed changes

assets/keymaps/default.json 🔗

@@ -31,7 +31,7 @@
             "cmd-n": "workspace::NewFile",
             "cmd-shift-n": "workspace::NewWindow",
             "cmd-o": "workspace::Open",
-            "ctrl-`": "terminal::Deploy"
+            "ctrl-`": "workspace::NewTerminal"
         }
     },
     {
@@ -300,7 +300,7 @@
                 8
             ],
             "cmd-b": "workspace::ToggleLeftSidebar",
-            "cmd-shift-f": "project_search::Deploy",
+            "cmd-shift-f": "workspace::NewSearch",
             "cmd-k cmd-t": "theme_selector::Toggle",
             "cmd-k cmd-s": "zed::OpenKeymap",
             "cmd-t": "project_symbols::Toggle",

crates/context_menu/src/context_menu.rs 🔗

@@ -22,6 +22,7 @@ pub fn init(cx: &mut MutableAppContext) {
     cx.add_action(ContextMenu::cancel);
 }
 
+//
 pub enum ContextMenuItem {
     Item {
         label: String,
@@ -258,6 +259,7 @@ impl ContextMenu {
                                 let style = style
                                     .item
                                     .style_for(Default::default(), Some(ix) == self.selected_index);
+
                                 Label::new(label.to_string(), style.label.clone())
                                     .contained()
                                     .with_style(style.container)
@@ -319,9 +321,12 @@ impl ContextMenu {
                             MouseEventHandler::new::<MenuItem, _, _>(ix, cx, |state, _| {
                                 let style =
                                     style.item.style_for(state, Some(ix) == self.selected_index);
+
                                 Flex::row()
                                     .with_child(
-                                        Label::new(label.to_string(), style.label.clone()).boxed(),
+                                        Label::new(label.to_string(), style.label.clone())
+                                            .contained()
+                                            .boxed(),
                                     )
                                     .with_child({
                                         KeystrokeLabel::new(

crates/search/src/project_search.rs 🔗

@@ -24,7 +24,7 @@ use workspace::{
     Item, ItemHandle, ItemNavHistory, Pane, ToolbarItemLocation, ToolbarItemView, Workspace,
 };
 
-actions!(project_search, [Deploy, SearchInNew, ToggleFocus]);
+actions!(project_search, [SearchInNew, ToggleFocus]);
 
 #[derive(Default)]
 struct ActiveSearches(HashMap<WeakModelHandle<Project>, WeakViewHandle<ProjectSearchView>>);
@@ -431,7 +431,11 @@ impl ProjectSearchView {
 
     // Re-activate the most recently activated search or the most recent if it has been closed.
     // If no search exists in the workspace, create a new one.
-    fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext<Workspace>) {
+    fn deploy(
+        workspace: &mut Workspace,
+        _: &workspace::NewSearch,
+        cx: &mut ViewContext<Workspace>,
+    ) {
         // Clean up entries for dropped projects
         cx.update_global(|state: &mut ActiveSearches, cx| {
             state.0.retain(|project, _| project.is_upgradable(cx))

crates/terminal/src/terminal_view.rs 🔗

@@ -5,17 +5,17 @@ use gpui::{
     actions, elements::*, AnyViewHandle, AppContext, Entity, ModelHandle, View, ViewContext,
     ViewHandle,
 };
+use workspace::{Item, Workspace};
 
 use crate::TerminalSize;
 use project::{LocalWorktree, Project, ProjectPath};
 use settings::{Settings, WorkingDirectory};
 use smallvec::SmallVec;
 use std::path::{Path, PathBuf};
-use workspace::{Item, Workspace};
 
 use crate::connected_el::TerminalEl;
 
-actions!(terminal, [Deploy, DeployModal]);
+actions!(terminal, [DeployModal]);
 
 //Make terminal view an enum, that can give you views for the error and non-error states
 //Take away all the result unwrapping in the current TerminalView by making it 'infallible'
@@ -59,7 +59,11 @@ impl Entity for ErrorView {
 
 impl TerminalView {
     ///Create a new Terminal in the current working directory or the user's home directory
-    pub fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext<Workspace>) {
+    pub fn deploy(
+        workspace: &mut Workspace,
+        _: &workspace::NewTerminal,
+        cx: &mut ViewContext<Workspace>,
+    ) {
         let strategy = cx
             .global::<Settings>()
             .terminal_overrides

crates/theme/src/theme.rs 🔗

@@ -267,6 +267,8 @@ pub struct ContextMenuItem {
     pub container: ContainerStyle,
     pub label: TextStyle,
     pub keystroke: ContainedText,
+    pub icon_width: f32,
+    pub icon_spacing: f32,
 }
 
 #[derive(Debug, Deserialize, Default)]

crates/workspace/src/pane.rs 🔗

@@ -1,5 +1,5 @@
 use super::{ItemHandle, SplitDirection};
-use crate::{toolbar::Toolbar, Item, WeakItemHandle, Workspace};
+use crate::{toolbar::Toolbar, Item, NewFile, NewSearch, NewTerminal, WeakItemHandle, Workspace};
 use anyhow::Result;
 use collections::{HashMap, HashSet, VecDeque};
 use context_menu::{ContextMenu, ContextMenuItem};
@@ -65,8 +65,13 @@ pub struct DeploySplitMenu {
     position: Vector2F,
 }
 
+#[derive(Clone, PartialEq)]
+pub struct DeployNewMenu {
+    position: Vector2F,
+}
+
 impl_actions!(pane, [GoBack, GoForward, ActivateItem]);
-impl_internal_actions!(pane, [CloseItem, DeploySplitMenu]);
+impl_internal_actions!(pane, [CloseItem, DeploySplitMenu, DeployNewMenu]);
 
 const MAX_NAVIGATION_HISTORY_LEN: usize = 1024;
 
@@ -98,6 +103,7 @@ pub fn init(cx: &mut MutableAppContext) {
     cx.add_action(|pane: &mut Pane, _: &SplitRight, cx| pane.split(SplitDirection::Right, cx));
     cx.add_action(|pane: &mut Pane, _: &SplitDown, cx| pane.split(SplitDirection::Down, cx));
     cx.add_action(Pane::deploy_split_menu);
+    cx.add_action(Pane::deploy_new_menu);
     cx.add_action(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
         Pane::reopen_closed_item(workspace, cx).detach();
     });
@@ -141,7 +147,7 @@ pub struct Pane {
     autoscroll: bool,
     nav_history: Rc<RefCell<NavHistory>>,
     toolbar: ViewHandle<Toolbar>,
-    split_menu: ViewHandle<ContextMenu>,
+    context_menu: ViewHandle<ContextMenu>,
 }
 
 pub struct ItemNavHistory {
@@ -182,7 +188,7 @@ pub struct NavigationEntry {
 impl Pane {
     pub fn new(cx: &mut ViewContext<Self>) -> Self {
         let handle = cx.weak_handle();
-        let split_menu = cx.add_view(|cx| ContextMenu::new(cx));
+        let context_menu = cx.add_view(|cx| ContextMenu::new(cx));
         Self {
             items: Vec::new(),
             is_active: true,
@@ -197,7 +203,7 @@ impl Pane {
                 pane: handle.clone(),
             })),
             toolbar: cx.add_view(|_| Toolbar::new(handle)),
-            split_menu,
+            context_menu,
         }
     }
 
@@ -831,7 +837,7 @@ impl Pane {
     }
 
     fn deploy_split_menu(&mut self, action: &DeploySplitMenu, cx: &mut ViewContext<Self>) {
-        self.split_menu.update(cx, |menu, cx| {
+        self.context_menu.update(cx, |menu, cx| {
             menu.show(
                 action.position,
                 vec![
@@ -845,6 +851,20 @@ impl Pane {
         });
     }
 
+    fn deploy_new_menu(&mut self, action: &DeployNewMenu, cx: &mut ViewContext<Self>) {
+        self.context_menu.update(cx, |menu, cx| {
+            menu.show(
+                action.position,
+                vec![
+                    ContextMenuItem::item("New File", NewFile),
+                    ContextMenuItem::item("New Terminal", NewTerminal),
+                    ContextMenuItem::item("New Search", NewSearch),
+                ],
+                cx,
+            );
+        });
+    }
+
     pub fn toolbar(&self) -> &ViewHandle<Toolbar> {
         &self.toolbar
     }
@@ -1083,10 +1103,40 @@ impl View for Pane {
                                 .with_child(self.render_tabs(cx).flex(1., true).named("tabs"));
 
                             if self.is_active {
-                                tab_row.add_child(
+                                tab_row.add_children([
                                     MouseEventHandler::new::<SplitIcon, _, _>(
                                         0,
                                         cx,
+                                        |mouse_state, cx| {
+                                            let theme =
+                                                &cx.global::<Settings>().theme.workspace.tab_bar;
+                                            let style =
+                                                theme.pane_button.style_for(mouse_state, false);
+                                            Svg::new("icons/plus_12.svg")
+                                                .with_color(style.color)
+                                                .constrained()
+                                                .with_width(style.icon_width)
+                                                .aligned()
+                                                .contained()
+                                                .with_style(style.container)
+                                                .constrained()
+                                                .with_width(style.button_width)
+                                                .with_height(style.button_width)
+                                                .aligned()
+                                                .boxed()
+                                        },
+                                    )
+                                    .with_cursor_style(CursorStyle::PointingHand)
+                                    .on_down(
+                                        MouseButton::Left,
+                                        |MouseButtonEvent { position, .. }, cx| {
+                                            cx.dispatch_action(DeployNewMenu { position });
+                                        },
+                                    )
+                                    .boxed(),
+                                    MouseEventHandler::new::<SplitIcon, _, _>(
+                                        1,
+                                        cx,
                                         |mouse_state, cx| {
                                             let theme =
                                                 &cx.global::<Settings>().theme.workspace.tab_bar;
@@ -1114,7 +1164,7 @@ impl View for Pane {
                                         },
                                     )
                                     .boxed(),
-                                )
+                                ])
                             }
 
                             tab_row
@@ -1155,7 +1205,7 @@ impl View for Pane {
                 })
                 .boxed(),
             )
-            .with_child(ChildView::new(&self.split_menu).boxed())
+            .with_child(ChildView::new(&self.context_menu).boxed())
             .named("pane")
     }
 

crates/zed/src/menus.rs 🔗

@@ -136,7 +136,7 @@ pub fn menus() -> Vec<Menu<'static>> {
                 },
                 MenuItem::Action {
                     name: "Find In Project",
-                    action: Box::new(search::project_search::Deploy),
+                    action: Box::new(workspace::NewSearch),
                 },
                 MenuItem::Separator,
                 MenuItem::Action {

styles/src/styleTree/contextMenu.ts 🔗

@@ -16,6 +16,8 @@ export default function contextMenu(theme: Theme) {
     border: border(theme, "primary"),
     keystrokeMargin: 30,
     item: {
+      iconSpacing: 8,
+      iconWidth: 14,
       padding: { left: 4, right: 4, top: 2, bottom: 2 },
       cornerRadius: 6,
       label: text(theme, "sans", "primary", { size: "sm" }),