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",
|
"enter": "vim::SearchSubmit",
|
||||||
"escape": "buffer_search::Dismiss"
|
"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 crate::{status_bar::StatusItemView, Workspace};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, px, Action, AnchorCorner, AnyView, AppContext, Axis, ClickEvent, Entity, EntityId,
|
div, px, Action, AnchorCorner, AnyView, AppContext, Axis, ClickEvent, Entity, EntityId,
|
||||||
EventEmitter, FocusHandle, FocusableView, IntoElement, MouseButton, ParentElement, Render,
|
EventEmitter, FocusHandle, FocusableView, IntoElement, KeyContext, MouseButton, ParentElement,
|
||||||
SharedString, Styled, Subscription, View, ViewContext, VisualContext, WeakView, WindowContext,
|
Render, SharedString, Styled, Subscription, View, ViewContext, VisualContext, WeakView,
|
||||||
|
WindowContext,
|
||||||
};
|
};
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -534,10 +535,18 @@ impl Dock {
|
||||||
DockPosition::Right => crate::ToggleRightDock.boxed_clone(),
|
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 {
|
impl Render for Dock {
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
|
let dispatch_context = Self::dispatch_context();
|
||||||
if let Some(entry) = self.visible_entry() {
|
if let Some(entry) = self.visible_entry() {
|
||||||
let size = entry.panel.size(cx);
|
let size = entry.panel.size(cx);
|
||||||
|
|
||||||
|
@ -588,6 +597,7 @@ impl Render for Dock {
|
||||||
}
|
}
|
||||||
|
|
||||||
div()
|
div()
|
||||||
|
.key_context(dispatch_context)
|
||||||
.track_focus(&self.focus_handle)
|
.track_focus(&self.focus_handle)
|
||||||
.flex()
|
.flex()
|
||||||
.bg(cx.theme().colors().panel_background)
|
.bg(cx.theme().colors().panel_background)
|
||||||
|
@ -612,7 +622,9 @@ impl Render for Dock {
|
||||||
)
|
)
|
||||||
.child(handle)
|
.child(handle)
|
||||||
} else {
|
} 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,
|
direction: SplitDirection,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) {
|
) {
|
||||||
|
use ActivateInDirectionTarget as Target;
|
||||||
|
enum Origin {
|
||||||
|
LeftDock,
|
||||||
|
RightDock,
|
||||||
|
BottomDock,
|
||||||
|
Center,
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
if let Some(pane) = self.find_pane_in_direction(direction, cx) {
|
||||||
cx.focus_view(pane);
|
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),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn swap_pane_in_direction(
|
(Origin::LeftDock, SplitDirection::Right) => {
|
||||||
&mut self,
|
if let Some(last_active_pane) = get_last_active_pane() {
|
||||||
direction: SplitDirection,
|
Some(Target::Pane(last_active_pane))
|
||||||
cx: &mut ViewContext<Self>,
|
} else {
|
||||||
) {
|
try_dock(&self.bottom_dock).or_else(|| try_dock(&self.right_dock))
|
||||||
if let Some(to) = self
|
}
|
||||||
.find_pane_in_direction(direction, cx)
|
}
|
||||||
.map(|pane| pane.clone())
|
|
||||||
{
|
(Origin::LeftDock, SplitDirection::Down)
|
||||||
self.center.swap(&self.active_pane.clone(), &to);
|
| (Origin::RightDock, SplitDirection::Down) => try_dock(&self.bottom_dock),
|
||||||
cx.notify();
|
|
||||||
|
(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(
|
fn find_pane_in_direction(
|
||||||
&mut self,
|
&mut self,
|
||||||
direction: SplitDirection,
|
direction: SplitDirection,
|
||||||
cx: &AppContext,
|
cx: &WindowContext,
|
||||||
) -> Option<&View<Pane>> {
|
) -> Option<View<Pane>> {
|
||||||
let Some(bounding_box) = self.center.bounding_box_for_pane(&self.active_pane) else {
|
let Some(bounding_box) = self.center.bounding_box_for_pane(&self.active_pane) else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
@ -2124,7 +2187,21 @@ impl Workspace {
|
||||||
Point::new(center.x, bounding_box.bottom() + distance_to_next.into())
|
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>) {
|
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) {
|
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";
|
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