From dc6f7fd577829c9bae891becf97ed765f5e2b613 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Fri, 17 Feb 2023 18:32:04 -0800 Subject: [PATCH 1/5] pull toggle button into its own file --- crates/workspace/src/dock.rs | 121 ++---------------- .../workspace/src/dock/toggle_dock_button.rs | 112 ++++++++++++++++ crates/workspace/src/pane.rs | 2 +- 3 files changed, 123 insertions(+), 112 deletions(-) create mode 100644 crates/workspace/src/dock/toggle_dock_button.rs diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 1702c6e52112d4a993aed2b94fe6881f57550237..bda82959112e71c6449fec18013e3459ff8c4b96 100644 --- a/crates/workspace/src/dock.rs +++ b/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); @@ -376,108 +377,6 @@ impl Dock { } } -pub struct ToggleDockButton { - workspace: WeakViewHandle, -} - -impl ToggleDockButton { - pub fn new(workspace: ViewHandle, cx: &mut ViewContext) -> 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::().theme.clone(); - - let button = MouseEventHandler::::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::( - 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::( - 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, - ) { - //Not applicable - } -} - #[cfg(test)] mod tests { use std::{ @@ -485,7 +384,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; diff --git a/crates/workspace/src/dock/toggle_dock_button.rs b/crates/workspace/src/dock/toggle_dock_button.rs new file mode 100644 index 0000000000000000000000000000000000000000..cafbea7db37c6fdccd0e3534bd2c4a2757aef2f0 --- /dev/null +++ b/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, +} + +impl ToggleDockButton { + pub fn new(workspace: ViewHandle, cx: &mut ViewContext) -> 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::().theme.clone(); + + let button = MouseEventHandler::::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::( + 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::( + 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, + ) { + //Not applicable + } +} diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index ccd2dd38e116feb735523c60aad2a21c11281540..8e51a54178cceca5cbc9dbae70ba06be8007e3a1 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1432,7 +1432,7 @@ impl View for Pane { enum TabBarEventHandler {} stack.add_child( MouseEventHandler::::new(0, cx, |_, _| { - Flex::row() + Empty::new() .contained() .with_style(theme.workspace.tab_bar.container) .boxed() From 04df00b221c69614eccaa9e559dc6ed4976a8cbb Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Sat, 18 Feb 2023 13:10:01 -0800 Subject: [PATCH 2/5] Iterate over keymap then dispatch path when matching keybindings to make precedence more intuitive Rename action which adds the active tab to the dock to be more intuitive Add action which moves the active tab out of the dock and bind it to the same keybinding --- assets/keymaps/default.json | 9 ++++--- crates/gpui/src/keymap_matcher.rs | 33 +++++++++++++----------- crates/workspace/src/dock.rs | 42 +++++++++++++++++++++++++++++-- 3 files changed, 63 insertions(+), 21 deletions(-) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index ed0cf894849601bcebcb0117689de2e69e27259d..e8f055cb7d740fc1ce15e88d496dce60a4b0ea6b 100644 --- a/assets/keymaps/default.json +++ b/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" } }, { diff --git a/crates/gpui/src/keymap_matcher.rs b/crates/gpui/src/keymap_matcher.rs index edcc458658eeff81358171ecbdf9a6cf324056e4..9a702a220c839a9a4ac8e5ebd02c340e02bf4b8f 100644 --- a/crates/gpui/src/keymap_matcher.rs +++ b/crates/gpui/src/keymap_matcher.rs @@ -75,29 +75,32 @@ 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() { - // Don't require pending view entry if there are no pending keystrokes - 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 previous_view_context != self.contexts[i] { + // Find the bindings which map the pending keystrokes and current context + // Iterate over the bindings in precedence order before the dispatch path so that + // users have more control over precedence rules + for binding in self.keymap.bindings().iter().rev() { + 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) { continue; } - } - // Find the bindings which map the pending keystrokes and current context - for binding in self.keymap.bindings().iter().rev() { + // 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 previous_view_context != self.contexts[i] { + continue; + } + } + match binding.match_keys_and_context(&self.pending_keystrokes, &self.contexts[i..]) { BindingMatchResult::Complete(action) => { - matched_bindings.push((view_id, action)) + matched_bindings.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; } _ => {} diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index bda82959112e71c6449fec18013e3459ff8c4b96..057658c3b59a2a35584268ceea544594e55686e6 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -30,7 +30,8 @@ actions!( AnchorDockRight, AnchorDockBottom, ExpandDock, - MoveActiveItemToDock, + AddTabToDock, + RemoveTabFromDock, ] ); impl_internal_actions!(dock, [MoveDock, AddDefaultItemToDock]); @@ -55,7 +56,8 @@ pub fn init(cx: &mut MutableAppContext) { }, ); cx.add_action( - |workspace: &mut Workspace, _: &MoveActiveItemToDock, cx: &mut ViewContext| { + |workspace: &mut Workspace, _: &AddTabToDock, cx: &mut ViewContext| { + eprintln!("Add tab to dock"); if let Some(active_item) = workspace.active_item(cx) { let item_id = active_item.id(); @@ -67,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| { + 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(), From 3fb6e31b9294bc1fc01f33d88d3b7da4bff8b484 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Sat, 18 Feb 2023 13:42:28 -0800 Subject: [PATCH 3/5] revert for loop change and store matched actions in a sorted set instead --- crates/gpui/src/keymap_matcher.rs | 40 +++++++++++++++++-------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/crates/gpui/src/keymap_matcher.rs b/crates/gpui/src/keymap_matcher.rs index 9a702a220c839a9a4ac8e5ebd02c340e02bf4b8f..93f5d0934afa436e37e2d45f1dbe3f29262660eb 100644 --- a/crates/gpui/src/keymap_matcher.rs +++ b/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; @@ -66,7 +66,10 @@ impl KeymapMatcher { mut dispatch_path: Vec<(usize, KeymapContext)>, ) -> MatchResult { let mut any_pending = false; - let mut matched_bindings: Vec<(usize, Box)> = Vec::new(); + // Collect matched bindings into an ordered list using the position in the bindings + // list as the precedence + let mut matched_bindings: BTreeMap)>> = + Default::default(); let first_keystroke = self.pending_keystrokes.is_empty(); self.pending_keystrokes.push(keystroke.clone()); @@ -76,27 +79,28 @@ impl KeymapMatcher { .extend(dispatch_path.iter_mut().map(|e| std::mem::take(&mut e.1))); // Find the bindings which map the pending keystrokes and current context - // Iterate over the bindings in precedence order before the dispatch path so that - // users have more control over precedence rules - for binding in self.keymap.bindings().iter().rev() { - 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) { - continue; - } + 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) { + 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 previous_view_context != self.contexts[i] { - 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 previous_view_context != self.contexts[i] { + continue; } + } + 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 @@ -113,7 +117,7 @@ impl KeymapMatcher { } if !matched_bindings.is_empty() { - MatchResult::Matches(matched_bindings) + MatchResult::Matches(matched_bindings.into_values().flatten().collect()) } else if any_pending { MatchResult::Pending } else { From 159d3ab00c49a3984f02d5008dfeb99548ba3188 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Sat, 18 Feb 2023 13:49:08 -0800 Subject: [PATCH 4/5] Add comment explaining push_keystroke --- crates/gpui/src/keymap_matcher.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/crates/gpui/src/keymap_matcher.rs b/crates/gpui/src/keymap_matcher.rs index 93f5d0934afa436e37e2d45f1dbe3f29262660eb..26da099a9fc5c67e9aa195b5b2412a25cb34161f 100644 --- a/crates/gpui/src/keymap_matcher.rs +++ b/crates/gpui/src/keymap_matcher.rs @@ -60,6 +60,16 @@ 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, @@ -117,6 +127,8 @@ impl KeymapMatcher { } if !matched_bindings.is_empty() { + // 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 From 0981244797e3ed03d26a5db0c50698ab60009146 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Sat, 18 Feb 2023 13:53:13 -0800 Subject: [PATCH 5/5] further tweak comment --- crates/gpui/src/keymap_matcher.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/gpui/src/keymap_matcher.rs b/crates/gpui/src/keymap_matcher.rs index 26da099a9fc5c67e9aa195b5b2412a25cb34161f..cfc26d6869e3babec69e891bb86e03b86212d799 100644 --- a/crates/gpui/src/keymap_matcher.rs +++ b/crates/gpui/src/keymap_matcher.rs @@ -76,8 +76,10 @@ impl KeymapMatcher { mut dispatch_path: Vec<(usize, KeymapContext)>, ) -> MatchResult { let mut any_pending = false; - // Collect matched bindings into an ordered list using the position in the bindings - // list as the precedence + // 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)>> = Default::default();