diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 102e92511d..eb8bdb33ed 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -502,5 +502,18 @@ "enter": "vim::SearchSubmit", "escape": "buffer_search::Dismiss" } + }, + { + "context": "Dock", + "bindings": { + "ctrl-w h": ["workspace::ActivatePaneInDirection", "Left"], + "ctrl-w l": ["workspace::ActivatePaneInDirection", "Right"], + "ctrl-w k": ["workspace::ActivatePaneInDirection", "Up"], + "ctrl-w j": ["workspace::ActivatePaneInDirection", "Down"], + "ctrl-w ctrl-h": ["workspace::ActivatePaneInDirection", "Left"], + "ctrl-w ctrl-l": ["workspace::ActivatePaneInDirection", "Right"], + "ctrl-w ctrl-k": ["workspace::ActivatePaneInDirection", "Up"], + "ctrl-w ctrl-j": ["workspace::ActivatePaneInDirection", "Down"] + } } ] diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 021383f73c..ffab5249e2 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -3,8 +3,9 @@ use crate::DraggedDock; use crate::{status_bar::StatusItemView, Workspace}; use gpui::{ div, px, Action, AnchorCorner, AnyView, AppContext, Axis, ClickEvent, Entity, EntityId, - EventEmitter, FocusHandle, FocusableView, IntoElement, MouseButton, ParentElement, Render, - SharedString, Styled, Subscription, View, ViewContext, VisualContext, WeakView, WindowContext, + EventEmitter, FocusHandle, FocusableView, IntoElement, KeyContext, MouseButton, ParentElement, + Render, SharedString, Styled, Subscription, View, ViewContext, VisualContext, WeakView, + WindowContext, }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -534,10 +535,18 @@ impl Dock { DockPosition::Right => crate::ToggleRightDock.boxed_clone(), } } + + fn dispatch_context() -> KeyContext { + let mut dispatch_context = KeyContext::default(); + dispatch_context.add("Dock"); + + dispatch_context + } } impl Render for Dock { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { + let dispatch_context = Self::dispatch_context(); if let Some(entry) = self.visible_entry() { let size = entry.panel.size(cx); @@ -588,6 +597,7 @@ impl Render for Dock { } div() + .key_context(dispatch_context) .track_focus(&self.focus_handle) .flex() .bg(cx.theme().colors().panel_background) @@ -612,7 +622,9 @@ impl Render for Dock { ) .child(handle) } else { - div().track_focus(&self.focus_handle) + div() + .key_context(dispatch_context) + .track_focus(&self.focus_handle) } } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 177d7384b6..19d203716b 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2075,30 +2075,93 @@ impl Workspace { direction: SplitDirection, cx: &mut WindowContext, ) { - if let Some(pane) = self.find_pane_in_direction(direction, cx) { - cx.focus_view(pane); + use ActivateInDirectionTarget as Target; + enum Origin { + LeftDock, + RightDock, + BottomDock, + Center, } - } - pub fn swap_pane_in_direction( - &mut self, - direction: SplitDirection, - cx: &mut ViewContext, - ) { - if let Some(to) = self - .find_pane_in_direction(direction, cx) - .map(|pane| pane.clone()) - { - self.center.swap(&self.active_pane.clone(), &to); - cx.notify(); + let origin: Origin = [ + (&self.left_dock, Origin::LeftDock), + (&self.right_dock, Origin::RightDock), + (&self.bottom_dock, Origin::BottomDock), + ] + .into_iter() + .find_map(|(dock, origin)| { + if dock.focus_handle(cx).contains_focused(cx) && dock.read(cx).is_open() { + Some(origin) + } else { + None + } + }) + .unwrap_or(Origin::Center); + + let get_last_active_pane = || { + self.last_active_center_pane.as_ref().and_then(|p| { + let p = p.upgrade()?; + (p.read(cx).items_len() != 0).then_some(p) + }) + }; + + let try_dock = + |dock: &View| dock.read(cx).is_open().then(|| Target::Dock(dock.clone())); + + let target = match (origin, direction) { + // We're in the center, so we first try to go to a different pane, + // otherwise try to go to a dock. + (Origin::Center, direction) => { + if let Some(pane) = self.find_pane_in_direction(direction, cx) { + Some(Target::Pane(pane)) + } else { + match direction { + SplitDirection::Up => None, + SplitDirection::Down => try_dock(&self.bottom_dock), + SplitDirection::Left => try_dock(&self.left_dock), + SplitDirection::Right => try_dock(&self.right_dock), + } + } + } + + (Origin::LeftDock, SplitDirection::Right) => { + if let Some(last_active_pane) = get_last_active_pane() { + Some(Target::Pane(last_active_pane)) + } else { + try_dock(&self.bottom_dock).or_else(|| try_dock(&self.right_dock)) + } + } + + (Origin::LeftDock, SplitDirection::Down) + | (Origin::RightDock, SplitDirection::Down) => try_dock(&self.bottom_dock), + + (Origin::BottomDock, SplitDirection::Up) => get_last_active_pane().map(Target::Pane), + (Origin::BottomDock, SplitDirection::Left) => try_dock(&self.left_dock), + (Origin::BottomDock, SplitDirection::Right) => try_dock(&self.right_dock), + + (Origin::RightDock, SplitDirection::Left) => { + if let Some(last_active_pane) = get_last_active_pane() { + Some(Target::Pane(last_active_pane)) + } else { + try_dock(&self.bottom_dock).or_else(|| try_dock(&self.left_dock)) + } + } + + _ => None, + }; + + match target { + Some(ActivateInDirectionTarget::Pane(pane)) => cx.focus_view(&pane), + Some(ActivateInDirectionTarget::Dock(dock)) => cx.focus_view(&dock), + None => {} } } fn find_pane_in_direction( &mut self, direction: SplitDirection, - cx: &AppContext, - ) -> Option<&View> { + cx: &WindowContext, + ) -> Option> { let Some(bounding_box) = self.center.bounding_box_for_pane(&self.active_pane) else { return None; }; @@ -2124,7 +2187,21 @@ impl Workspace { Point::new(center.x, bounding_box.bottom() + distance_to_next.into()) } }; - self.center.pane_at_pixel_position(target) + self.center.pane_at_pixel_position(target).cloned() + } + + pub fn swap_pane_in_direction( + &mut self, + direction: SplitDirection, + cx: &mut ViewContext, + ) { + if let Some(to) = self + .find_pane_in_direction(direction, cx) + .map(|pane| pane.clone()) + { + self.center.swap(&self.active_pane.clone(), &to); + cx.notify(); + } } fn handle_pane_focused(&mut self, pane: View, cx: &mut ViewContext) { @@ -3488,6 +3565,11 @@ fn open_items( }) } +enum ActivateInDirectionTarget { + Pane(View), + Dock(View), +} + fn notify_if_database_failed(workspace: WindowHandle, cx: &mut AsyncAppContext) { const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/zed/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";