Add ability to navigate to/from docks via keybindings (#7141)
This adds the ability to navigate to/from docks (Terminal, Project, Collaboration, Assistant) via keybindings. When using the `ActivatePaneInDirection` keybinding from the left/bottom/right dock, we check whether the movement is towards the center panel. If it is, we focus the last active pane. Fixes https://github.com/zed-industries/zed/issues/6833 and it came up in a few other tickes/discussions. Release Notes: - Added ability to navigate to docks and back to the editor using the `workspace::ActivatePaneInDirection` action (by default bound to `Ctrl-w [hjkl]` in Vim mode). ([#6833](https://github.com/zed-industries/zed/issues/6833)). ## Drawback There's this weird behavior: if you start Zed and no files are opened, you focus terminal, go left (project panel), then back to right to terminal, the terminal isn't focused. Even though we focus it in the code. Maybe this is a bug in the current focus handling code? ## Demo https://github.com/zed-industries/zed/assets/1185253/5d56db40-36aa-4758-a3bc-7a0de20ce5d7 --------- Co-authored-by: Piotr <piotr@zed.dev>
This commit is contained in:
parent
6c93c4bd35
commit
e65a76f0ec
3 changed files with 127 additions and 20 deletions
|
@ -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"]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -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<Self>) -> 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Self>,
|
||||
) {
|
||||
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>| 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<Pane>> {
|
||||
cx: &WindowContext,
|
||||
) -> Option<View<Pane>> {
|
||||
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<Self>,
|
||||
) {
|
||||
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<Pane>, cx: &mut ViewContext<Self>) {
|
||||
|
@ -3488,6 +3565,11 @@ fn open_items(
|
|||
})
|
||||
}
|
||||
|
||||
enum ActivateInDirectionTarget {
|
||||
Pane(View<Pane>),
|
||||
Dock(View<Dock>),
|
||||
}
|
||||
|
||||
fn notify_if_database_failed(workspace: WindowHandle<Workspace>, 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";
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue