Merge pull request #2188 from zed-industries/dont-open-project-items-in-dock

Kay Simmons created

Dont open project items in dock

Change summary

assets/keymaps/default.json                     |   9 
crates/gpui/src/keymap_matcher.rs               |  41 +++-
crates/workspace/src/dock.rs                    | 163 +++++-------------
crates/workspace/src/dock/toggle_dock_button.rs | 112 +++++++++++++
crates/workspace/src/pane.rs                    |   2 
5 files changed, 199 insertions(+), 128 deletions(-)

Detailed changes

assets/keymaps/default.json 🔗

@@ -450,15 +450,16 @@
         }
     },
     {
-        "context": "Dock",
+        "context": "Pane",
         "bindings": {
-            "shift-escape": "dock::HideDock"
+            "cmd-escape": "dock::AddTabToDock"
         }
     },
     {
-        "context": "Pane",
+        "context": "Dock",
         "bindings": {
-            "cmd-escape": "dock::MoveActiveItemToDock"
+            "shift-escape": "dock::HideDock",
+            "cmd-escape": "dock::RemoveTabFromDock"
         }
     },
     {

crates/gpui/src/keymap_matcher.rs 🔗

@@ -5,7 +5,7 @@ mod keystroke;
 
 use std::{any::TypeId, fmt::Debug};
 
-use collections::HashMap;
+use collections::{BTreeMap, HashMap};
 use smallvec::SmallVec;
 
 use crate::Action;
@@ -60,13 +60,28 @@ impl KeymapMatcher {
         !self.pending_keystrokes.is_empty()
     }
 
+    /// Pushes a keystroke onto the matcher.
+    /// The result of the new keystroke is returned:
+    ///     MatchResult::None =>
+    ///         No match is valid for this key given any pending keystrokes.
+    ///     MatchResult::Pending =>
+    ///         There exist bindings which are still waiting for more keys.
+    ///     MatchResult::Complete(matches) =>
+    ///         1 or more bindings have recieved the necessary key presses.
+    ///         The order of the matched actions is by order in the keymap file first and
+    ///         position of the matching view second.
     pub fn push_keystroke(
         &mut self,
         keystroke: Keystroke,
         mut dispatch_path: Vec<(usize, KeymapContext)>,
     ) -> MatchResult {
         let mut any_pending = false;
-        let mut matched_bindings: Vec<(usize, Box<dyn Action>)> = Vec::new();
+        // Collect matched bindings into an ordered list using the position in the matching binding first,
+        // and then the order the binding matched in the view tree second.
+        // The key is the reverse position of the binding in the bindings list so that later bindings
+        // match before earlier ones in the user's config
+        let mut matched_bindings: BTreeMap<usize, Vec<(usize, Box<dyn Action>)>> =
+            Default::default();
 
         let first_keystroke = self.pending_keystrokes.is_empty();
         self.pending_keystrokes.push(keystroke.clone());
@@ -75,29 +90,33 @@ impl KeymapMatcher {
         self.contexts
             .extend(dispatch_path.iter_mut().map(|e| std::mem::take(&mut e.1)));
 
-        for (i, (view_id, _)) in dispatch_path.into_iter().enumerate() {
+        // Find the bindings which map the pending keystrokes and current context
+        for (i, (view_id, _)) in dispatch_path.iter().enumerate() {
             // Don't require pending view entry if there are no pending keystrokes
-            if !first_keystroke && !self.pending_views.contains_key(&view_id) {
+            if !first_keystroke && !self.pending_views.contains_key(view_id) {
                 continue;
             }
 
             // If there is a previous view context, invalidate that view if it
             // has changed
-            if let Some(previous_view_context) = self.pending_views.remove(&view_id) {
+            if let Some(previous_view_context) = self.pending_views.remove(view_id) {
                 if previous_view_context != self.contexts[i] {
                     continue;
                 }
             }
 
-            // Find the bindings which map the pending keystrokes and current context
-            for binding in self.keymap.bindings().iter().rev() {
+            for (order, binding) in self.keymap.bindings().iter().rev().enumerate() {
                 match binding.match_keys_and_context(&self.pending_keystrokes, &self.contexts[i..])
                 {
                     BindingMatchResult::Complete(action) => {
-                        matched_bindings.push((view_id, action))
+                        matched_bindings
+                            .entry(order)
+                            .or_default()
+                            .push((*view_id, action));
                     }
                     BindingMatchResult::Partial => {
-                        self.pending_views.insert(view_id, self.contexts[i].clone());
+                        self.pending_views
+                            .insert(*view_id, self.contexts[i].clone());
                         any_pending = true;
                     }
                     _ => {}
@@ -110,7 +129,9 @@ impl KeymapMatcher {
         }
 
         if !matched_bindings.is_empty() {
-            MatchResult::Matches(matched_bindings)
+            // Collect the sorted matched bindings into the final vec for ease of use
+            // Matched bindings are in order by precedence
+            MatchResult::Matches(matched_bindings.into_values().flatten().collect())
         } else if any_pending {
             MatchResult::Pending
         } else {

crates/workspace/src/dock.rs 🔗

@@ -1,19 +1,20 @@
+mod toggle_dock_button;
+
+use serde::Deserialize;
+
 use collections::HashMap;
 use gpui::{
     actions,
-    elements::{ChildView, Container, Empty, MouseEventHandler, ParentElement, Side, Stack, Svg},
+    elements::{ChildView, Container, Empty, MouseEventHandler, ParentElement, Side, Stack},
     geometry::vector::Vector2F,
-    impl_internal_actions, Border, CursorStyle, Element, ElementBox, Entity, MouseButton,
-    MutableAppContext, RenderContext, SizeConstraint, View, ViewContext, ViewHandle,
-    WeakViewHandle,
+    impl_internal_actions, Border, CursorStyle, Element, ElementBox, MouseButton,
+    MutableAppContext, RenderContext, SizeConstraint, ViewContext, ViewHandle,
 };
-use serde::Deserialize;
 use settings::{DockAnchor, Settings};
 use theme::Theme;
 
-use crate::{
-    handle_dropped_item, sidebar::SidebarSide, ItemHandle, Pane, StatusItemView, Workspace,
-};
+use crate::{sidebar::SidebarSide, ItemHandle, Pane, Workspace};
+pub use toggle_dock_button::ToggleDockButton;
 
 #[derive(PartialEq, Clone, Deserialize)]
 pub struct MoveDock(pub DockAnchor);
@@ -29,7 +30,8 @@ actions!(
         AnchorDockRight,
         AnchorDockBottom,
         ExpandDock,
-        MoveActiveItemToDock,
+        AddTabToDock,
+        RemoveTabFromDock,
     ]
 );
 impl_internal_actions!(dock, [MoveDock, AddDefaultItemToDock]);
@@ -54,7 +56,8 @@ pub fn init(cx: &mut MutableAppContext) {
         },
     );
     cx.add_action(
-        |workspace: &mut Workspace, _: &MoveActiveItemToDock, cx: &mut ViewContext<Workspace>| {
+        |workspace: &mut Workspace, _: &AddTabToDock, cx: &mut ViewContext<Workspace>| {
+            eprintln!("Add tab to dock");
             if let Some(active_item) = workspace.active_item(cx) {
                 let item_id = active_item.id();
 
@@ -66,6 +69,42 @@ pub fn init(cx: &mut MutableAppContext) {
 
                 let destination_index = to.read(cx).items_len() + 1;
 
+                Pane::move_item(
+                    workspace,
+                    from.clone(),
+                    to.clone(),
+                    item_id,
+                    destination_index,
+                    cx,
+                );
+            }
+        },
+    );
+    cx.add_action(
+        |workspace: &mut Workspace, _: &RemoveTabFromDock, cx: &mut ViewContext<Workspace>| {
+            eprintln!("Removing tab from dock");
+            if let Some(active_item) = workspace.active_item(cx) {
+                let item_id = active_item.id();
+
+                let from = workspace.dock_pane();
+                let to = workspace
+                    .last_active_center_pane
+                    .as_ref()
+                    .and_then(|pane| pane.upgrade(cx))
+                    .unwrap_or_else(|| {
+                        workspace
+                            .panes
+                            .first()
+                            .expect("There must be a pane")
+                            .clone()
+                    });
+
+                if from.id() == to.id() {
+                    return;
+                }
+
+                let destination_index = to.read(cx).items_len() + 1;
+
                 Pane::move_item(
                     workspace,
                     from.clone(),
@@ -376,108 +415,6 @@ impl Dock {
     }
 }
 
-pub struct ToggleDockButton {
-    workspace: WeakViewHandle<Workspace>,
-}
-
-impl ToggleDockButton {
-    pub fn new(workspace: ViewHandle<Workspace>, cx: &mut ViewContext<Self>) -> Self {
-        // When dock moves, redraw so that the icon and toggle status matches.
-        cx.subscribe(&workspace, |_, _, _, cx| cx.notify()).detach();
-
-        Self {
-            workspace: workspace.downgrade(),
-        }
-    }
-}
-
-impl Entity for ToggleDockButton {
-    type Event = ();
-}
-
-impl View for ToggleDockButton {
-    fn ui_name() -> &'static str {
-        "Dock Toggle"
-    }
-
-    fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox {
-        let workspace = self.workspace.upgrade(cx);
-
-        if workspace.is_none() {
-            return Empty::new().boxed();
-        }
-
-        let workspace = workspace.unwrap();
-        let dock_position = workspace.read(cx).dock.position;
-
-        let theme = cx.global::<Settings>().theme.clone();
-
-        let button = MouseEventHandler::<Self>::new(0, cx, {
-            let theme = theme.clone();
-            move |state, _| {
-                let style = theme
-                    .workspace
-                    .status_bar
-                    .sidebar_buttons
-                    .item
-                    .style_for(state, dock_position.is_visible());
-
-                Svg::new(icon_for_dock_anchor(dock_position.anchor()))
-                    .with_color(style.icon_color)
-                    .constrained()
-                    .with_width(style.icon_size)
-                    .with_height(style.icon_size)
-                    .contained()
-                    .with_style(style.container)
-                    .boxed()
-            }
-        })
-        .with_cursor_style(CursorStyle::PointingHand)
-        .on_up(MouseButton::Left, move |event, cx| {
-            let dock_pane = workspace.read(cx.app).dock_pane();
-            let drop_index = dock_pane.read(cx.app).items_len() + 1;
-            handle_dropped_item(event, &dock_pane.downgrade(), drop_index, false, None, cx);
-        });
-
-        if dock_position.is_visible() {
-            button
-                .on_click(MouseButton::Left, |_, cx| {
-                    cx.dispatch_action(HideDock);
-                })
-                .with_tooltip::<Self, _>(
-                    0,
-                    "Hide Dock".into(),
-                    Some(Box::new(HideDock)),
-                    theme.tooltip.clone(),
-                    cx,
-                )
-        } else {
-            button
-                .on_click(MouseButton::Left, |_, cx| {
-                    cx.dispatch_action(FocusDock);
-                })
-                .with_tooltip::<Self, _>(
-                    0,
-                    "Focus Dock".into(),
-                    Some(Box::new(FocusDock)),
-                    theme.tooltip.clone(),
-                    cx,
-                )
-        }
-        .boxed()
-    }
-}
-
-impl StatusItemView for ToggleDockButton {
-    fn set_active_pane_item(
-        &mut self,
-        _active_pane_item: Option<&dyn crate::ItemHandle>,
-        _cx: &mut ViewContext<Self>,
-    ) {
-        //Not applicable
-    }
-}
-
 #[cfg(test)]
 mod tests {
     use std::{
@@ -485,7 +422,7 @@ mod tests {
         path::PathBuf,
     };
 
-    use gpui::{AppContext, TestAppContext, UpdateView, ViewContext};
+    use gpui::{AppContext, TestAppContext, UpdateView, View, ViewContext};
     use project::{FakeFs, Project};
     use settings::Settings;
 

crates/workspace/src/dock/toggle_dock_button.rs 🔗

@@ -0,0 +1,112 @@
+use gpui::{
+    elements::{Empty, MouseEventHandler, Svg},
+    CursorStyle, Element, ElementBox, Entity, MouseButton, View, ViewContext, ViewHandle,
+    WeakViewHandle,
+};
+use settings::Settings;
+
+use crate::{handle_dropped_item, StatusItemView, Workspace};
+
+use super::{icon_for_dock_anchor, FocusDock, HideDock};
+
+pub struct ToggleDockButton {
+    workspace: WeakViewHandle<Workspace>,
+}
+
+impl ToggleDockButton {
+    pub fn new(workspace: ViewHandle<Workspace>, cx: &mut ViewContext<Self>) -> Self {
+        // When dock moves, redraw so that the icon and toggle status matches.
+        cx.subscribe(&workspace, |_, _, _, cx| cx.notify()).detach();
+
+        Self {
+            workspace: workspace.downgrade(),
+        }
+    }
+}
+
+impl Entity for ToggleDockButton {
+    type Event = ();
+}
+
+impl View for ToggleDockButton {
+    fn ui_name() -> &'static str {
+        "Dock Toggle"
+    }
+
+    fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox {
+        let workspace = self.workspace.upgrade(cx);
+
+        if workspace.is_none() {
+            return Empty::new().boxed();
+        }
+
+        let workspace = workspace.unwrap();
+        let dock_position = workspace.read(cx).dock.position;
+
+        let theme = cx.global::<Settings>().theme.clone();
+
+        let button = MouseEventHandler::<Self>::new(0, cx, {
+            let theme = theme.clone();
+            move |state, _| {
+                let style = theme
+                    .workspace
+                    .status_bar
+                    .sidebar_buttons
+                    .item
+                    .style_for(state, dock_position.is_visible());
+
+                Svg::new(icon_for_dock_anchor(dock_position.anchor()))
+                    .with_color(style.icon_color)
+                    .constrained()
+                    .with_width(style.icon_size)
+                    .with_height(style.icon_size)
+                    .contained()
+                    .with_style(style.container)
+                    .boxed()
+            }
+        })
+        .with_cursor_style(CursorStyle::PointingHand)
+        .on_up(MouseButton::Left, move |event, cx| {
+            let dock_pane = workspace.read(cx.app).dock_pane();
+            let drop_index = dock_pane.read(cx.app).items_len() + 1;
+            handle_dropped_item(event, &dock_pane.downgrade(), drop_index, false, None, cx);
+        });
+
+        if dock_position.is_visible() {
+            button
+                .on_click(MouseButton::Left, |_, cx| {
+                    cx.dispatch_action(HideDock);
+                })
+                .with_tooltip::<Self, _>(
+                    0,
+                    "Hide Dock".into(),
+                    Some(Box::new(HideDock)),
+                    theme.tooltip.clone(),
+                    cx,
+                )
+        } else {
+            button
+                .on_click(MouseButton::Left, |_, cx| {
+                    cx.dispatch_action(FocusDock);
+                })
+                .with_tooltip::<Self, _>(
+                    0,
+                    "Focus Dock".into(),
+                    Some(Box::new(FocusDock)),
+                    theme.tooltip.clone(),
+                    cx,
+                )
+        }
+        .boxed()
+    }
+}
+
+impl StatusItemView for ToggleDockButton {
+    fn set_active_pane_item(
+        &mut self,
+        _active_pane_item: Option<&dyn crate::ItemHandle>,
+        _cx: &mut ViewContext<Self>,
+    ) {
+        //Not applicable
+    }
+}

crates/workspace/src/pane.rs 🔗

@@ -1432,7 +1432,7 @@ impl View for Pane {
                                 enum TabBarEventHandler {}
                                 stack.add_child(
                                     MouseEventHandler::<TabBarEventHandler>::new(0, cx, |_, _| {
-                                        Flex::row()
+                                        Empty::new()
                                             .contained()
                                             .with_style(theme.workspace.tab_bar.container)
                                             .boxed()