@@ -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 {
@@ -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;
@@ -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
+ }
+}