mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-28 07:22:17 +03:00
Add support for activating a pane by direction
Contributes: zed-industries/community#476 Contributes: zed-industries/community#478
This commit is contained in:
parent
b13e86aba6
commit
e1379f0ef0
@ -563,6 +563,7 @@ pub struct Editor {
|
||||
inlay_hint_cache: InlayHintCache,
|
||||
next_inlay_id: usize,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
pixel_position_of_newest_cursor: Option<Vector2F>,
|
||||
}
|
||||
|
||||
pub struct EditorSnapshot {
|
||||
@ -1394,6 +1395,7 @@ impl Editor {
|
||||
copilot_state: Default::default(),
|
||||
inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
|
||||
gutter_hovered: false,
|
||||
pixel_position_of_newest_cursor: None,
|
||||
_subscriptions: vec![
|
||||
cx.observe(&buffer, Self::on_buffer_changed),
|
||||
cx.subscribe(&buffer, Self::on_buffer_event),
|
||||
|
@ -61,6 +61,7 @@ enum FoldMarkers {}
|
||||
struct SelectionLayout {
|
||||
head: DisplayPoint,
|
||||
cursor_shape: CursorShape,
|
||||
is_newest: bool,
|
||||
range: Range<DisplayPoint>,
|
||||
}
|
||||
|
||||
@ -70,6 +71,7 @@ impl SelectionLayout {
|
||||
line_mode: bool,
|
||||
cursor_shape: CursorShape,
|
||||
map: &DisplaySnapshot,
|
||||
is_newest: bool,
|
||||
) -> Self {
|
||||
if line_mode {
|
||||
let selection = selection.map(|p| p.to_point(&map.buffer_snapshot));
|
||||
@ -77,6 +79,7 @@ impl SelectionLayout {
|
||||
Self {
|
||||
head: selection.head().to_display_point(map),
|
||||
cursor_shape,
|
||||
is_newest,
|
||||
range: point_range.start.to_display_point(map)
|
||||
..point_range.end.to_display_point(map),
|
||||
}
|
||||
@ -85,6 +88,7 @@ impl SelectionLayout {
|
||||
Self {
|
||||
head: selection.head(),
|
||||
cursor_shape,
|
||||
is_newest,
|
||||
range: selection.range(),
|
||||
}
|
||||
}
|
||||
@ -864,6 +868,12 @@ impl EditorElement {
|
||||
let x = cursor_character_x - scroll_left;
|
||||
let y = cursor_position.row() as f32 * layout.position_map.line_height
|
||||
- scroll_top;
|
||||
if selection.is_newest {
|
||||
editor.pixel_position_of_newest_cursor = Some(vec2f(
|
||||
bounds.origin_x() + x + block_width / 2.,
|
||||
bounds.origin_y() + y + layout.position_map.line_height / 2.,
|
||||
));
|
||||
}
|
||||
cursors.push(Cursor {
|
||||
color: selection_style.cursor,
|
||||
block_width,
|
||||
@ -2108,6 +2118,7 @@ impl Element<Editor> for EditorElement {
|
||||
line_mode,
|
||||
cursor_shape,
|
||||
&snapshot.display_snapshot,
|
||||
false,
|
||||
));
|
||||
}
|
||||
selections.extend(remote_selections);
|
||||
@ -2117,6 +2128,7 @@ impl Element<Editor> for EditorElement {
|
||||
.selections
|
||||
.disjoint_in_range(start_anchor..end_anchor, cx);
|
||||
local_selections.extend(editor.selections.pending(cx));
|
||||
let newest = editor.selections.newest(cx);
|
||||
for selection in &local_selections {
|
||||
let is_empty = selection.start == selection.end;
|
||||
let selection_start = snapshot.prev_line_boundary(selection.start).1;
|
||||
@ -2139,11 +2151,13 @@ impl Element<Editor> for EditorElement {
|
||||
local_selections
|
||||
.into_iter()
|
||||
.map(|selection| {
|
||||
let is_newest = selection == newest;
|
||||
SelectionLayout::new(
|
||||
selection,
|
||||
editor.selections.line_mode,
|
||||
editor.cursor_shape,
|
||||
&snapshot.display_snapshot,
|
||||
is_newest,
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
|
@ -7,8 +7,10 @@ use anyhow::{Context, Result};
|
||||
use collections::HashSet;
|
||||
use futures::future::try_join_all;
|
||||
use gpui::{
|
||||
elements::*, geometry::vector::vec2f, AppContext, AsyncAppContext, Entity, ModelHandle,
|
||||
Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle,
|
||||
elements::*,
|
||||
geometry::vector::{vec2f, Vector2F},
|
||||
AppContext, AsyncAppContext, Entity, ModelHandle, Subscription, Task, View, ViewContext,
|
||||
ViewHandle, WeakViewHandle,
|
||||
};
|
||||
use language::{
|
||||
proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, OffsetRangeExt, Point,
|
||||
@ -750,6 +752,10 @@ impl Item for Editor {
|
||||
Some(Box::new(handle.clone()))
|
||||
}
|
||||
|
||||
fn pixel_position_of_cursor(&self) -> Option<Vector2F> {
|
||||
self.pixel_position_of_newest_cursor
|
||||
}
|
||||
|
||||
fn breadcrumb_location(&self) -> ToolbarItemLocation {
|
||||
ToolbarItemLocation::PrimaryLeft { flex: None }
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ use crate::{
|
||||
use crate::{AutosaveSetting, DelayedDebouncedEditAction, WorkspaceSettings};
|
||||
use anyhow::Result;
|
||||
use client::{proto, Client};
|
||||
use gpui::geometry::vector::Vector2F;
|
||||
use gpui::{
|
||||
fonts::HighlightStyle, AnyElement, AnyViewHandle, AppContext, ModelHandle, Task, View,
|
||||
ViewContext, ViewHandle, WeakViewHandle, WindowContext,
|
||||
@ -203,6 +204,9 @@ pub trait Item: View {
|
||||
fn show_toolbar(&self) -> bool {
|
||||
true
|
||||
}
|
||||
fn pixel_position_of_cursor(&self) -> Option<Vector2F> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ItemHandle: 'static + fmt::Debug {
|
||||
@ -271,6 +275,7 @@ pub trait ItemHandle: 'static + fmt::Debug {
|
||||
fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>>;
|
||||
fn serialized_item_kind(&self) -> Option<&'static str>;
|
||||
fn show_toolbar(&self, cx: &AppContext) -> bool;
|
||||
fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Vector2F>;
|
||||
}
|
||||
|
||||
pub trait WeakItemHandle {
|
||||
@ -615,6 +620,10 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
|
||||
fn show_toolbar(&self, cx: &AppContext) -> bool {
|
||||
self.read(cx).show_toolbar()
|
||||
}
|
||||
|
||||
fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Vector2F> {
|
||||
self.read(cx).pixel_position_of_cursor()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Box<dyn ItemHandle>> for AnyViewHandle {
|
||||
|
@ -542,6 +542,12 @@ impl Pane {
|
||||
self.items.get(self.active_item_index).cloned()
|
||||
}
|
||||
|
||||
pub fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Vector2F> {
|
||||
self.items
|
||||
.get(self.active_item_index)?
|
||||
.pixel_position_of_cursor(cx)
|
||||
}
|
||||
|
||||
pub fn item_for_entry(
|
||||
&self,
|
||||
entry_id: ProjectEntryId,
|
||||
|
@ -54,6 +54,20 @@ impl PaneGroup {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bounding_box_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<RectF> {
|
||||
match &self.root {
|
||||
Member::Pane(_) => None,
|
||||
Member::Axis(axis) => axis.bounding_box_for_pane(pane),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pane_at_pixel_position(&self, coordinate: Vector2F) -> Option<&ViewHandle<Pane>> {
|
||||
match &self.root {
|
||||
Member::Pane(pane) => Some(pane),
|
||||
Member::Axis(axis) => axis.pane_at_pixel_position(coordinate),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns:
|
||||
/// - Ok(true) if it found and removed a pane
|
||||
/// - Ok(false) if it found but did not remove the pane
|
||||
@ -309,15 +323,18 @@ pub(crate) struct PaneAxis {
|
||||
pub axis: Axis,
|
||||
pub members: Vec<Member>,
|
||||
pub flexes: Rc<RefCell<Vec<f32>>>,
|
||||
pub bounding_boxes: Rc<RefCell<Vec<Option<RectF>>>>,
|
||||
}
|
||||
|
||||
impl PaneAxis {
|
||||
pub fn new(axis: Axis, members: Vec<Member>) -> Self {
|
||||
let flexes = Rc::new(RefCell::new(vec![1.; members.len()]));
|
||||
let bounding_boxes = Rc::new(RefCell::new(vec![None; members.len()]));
|
||||
Self {
|
||||
axis,
|
||||
members,
|
||||
flexes,
|
||||
bounding_boxes,
|
||||
}
|
||||
}
|
||||
|
||||
@ -326,10 +343,12 @@ impl PaneAxis {
|
||||
debug_assert!(members.len() == flexes.len());
|
||||
|
||||
let flexes = Rc::new(RefCell::new(flexes));
|
||||
let bounding_boxes = Rc::new(RefCell::new(vec![None; members.len()]));
|
||||
Self {
|
||||
axis,
|
||||
members,
|
||||
flexes,
|
||||
bounding_boxes,
|
||||
}
|
||||
}
|
||||
|
||||
@ -409,6 +428,40 @@ impl PaneAxis {
|
||||
}
|
||||
}
|
||||
|
||||
fn bounding_box_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<RectF> {
|
||||
for (idx, member) in self.members.iter().enumerate() {
|
||||
match member {
|
||||
Member::Pane(found) => {
|
||||
if pane == found {
|
||||
return self.bounding_boxes.borrow()[idx];
|
||||
}
|
||||
}
|
||||
Member::Axis(axis) => {
|
||||
if let Some(rect) = axis.bounding_box_for_pane(pane) {
|
||||
return Some(rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn pane_at_pixel_position(&self, coordinate: Vector2F) -> Option<&ViewHandle<Pane>> {
|
||||
let bounding_boxes = self.bounding_boxes.borrow();
|
||||
|
||||
for (idx, member) in self.members.iter().enumerate() {
|
||||
if let Some(coordinates) = bounding_boxes[idx] {
|
||||
if coordinates.contains_point(coordinate) {
|
||||
return match member {
|
||||
Member::Pane(found) => Some(found),
|
||||
Member::Axis(axis) => axis.pane_at_pixel_position(coordinate),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn render(
|
||||
&self,
|
||||
project: &ModelHandle<Project>,
|
||||
@ -423,7 +476,12 @@ impl PaneAxis {
|
||||
) -> AnyElement<Workspace> {
|
||||
debug_assert!(self.members.len() == self.flexes.borrow().len());
|
||||
|
||||
let mut pane_axis = PaneAxisElement::new(self.axis, basis, self.flexes.clone());
|
||||
let mut pane_axis = PaneAxisElement::new(
|
||||
self.axis,
|
||||
basis,
|
||||
self.flexes.clone(),
|
||||
self.bounding_boxes.clone(),
|
||||
);
|
||||
let mut active_pane_ix = None;
|
||||
|
||||
let mut members = self.members.iter().enumerate().peekable();
|
||||
@ -546,14 +604,21 @@ mod element {
|
||||
active_pane_ix: Option<usize>,
|
||||
flexes: Rc<RefCell<Vec<f32>>>,
|
||||
children: Vec<AnyElement<Workspace>>,
|
||||
bounding_boxes: Rc<RefCell<Vec<Option<RectF>>>>,
|
||||
}
|
||||
|
||||
impl PaneAxisElement {
|
||||
pub fn new(axis: Axis, basis: usize, flexes: Rc<RefCell<Vec<f32>>>) -> Self {
|
||||
pub fn new(
|
||||
axis: Axis,
|
||||
basis: usize,
|
||||
flexes: Rc<RefCell<Vec<f32>>>,
|
||||
bounding_boxes: Rc<RefCell<Vec<Option<RectF>>>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
axis,
|
||||
basis,
|
||||
flexes,
|
||||
bounding_boxes,
|
||||
active_pane_ix: None,
|
||||
children: Default::default(),
|
||||
}
|
||||
@ -708,11 +773,16 @@ mod element {
|
||||
|
||||
let mut child_origin = bounds.origin();
|
||||
|
||||
let mut bounding_boxes = self.bounding_boxes.borrow_mut();
|
||||
bounding_boxes.clear();
|
||||
|
||||
let mut children_iter = self.children.iter_mut().enumerate().peekable();
|
||||
while let Some((ix, child)) = children_iter.next() {
|
||||
let child_start = child_origin.clone();
|
||||
child.paint(scene, child_origin, visible_bounds, view, cx);
|
||||
|
||||
bounding_boxes.push(Some(RectF::new(child_origin, child.size())));
|
||||
|
||||
match self.axis {
|
||||
Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0),
|
||||
Axis::Vertical => child_origin += vec2f(0.0, child.size().y()),
|
||||
|
@ -152,6 +152,9 @@ pub struct OpenPaths {
|
||||
#[derive(Clone, Deserialize, PartialEq)]
|
||||
pub struct ActivatePane(pub usize);
|
||||
|
||||
#[derive(Clone, Deserialize, PartialEq)]
|
||||
pub struct ActivatePaneInDirection(pub SplitDirection);
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Toast {
|
||||
id: usize,
|
||||
@ -197,7 +200,7 @@ impl Clone for Toast {
|
||||
}
|
||||
}
|
||||
|
||||
impl_actions!(workspace, [ActivatePane, Toast]);
|
||||
impl_actions!(workspace, [ActivatePane, ActivatePaneInDirection, Toast]);
|
||||
|
||||
pub type WorkspaceId = i64;
|
||||
|
||||
@ -262,6 +265,13 @@ pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
|
||||
cx.add_action(|workspace: &mut Workspace, _: &ActivateNextPane, cx| {
|
||||
workspace.activate_next_pane(cx)
|
||||
});
|
||||
|
||||
cx.add_action(
|
||||
|workspace: &mut Workspace, action: &ActivatePaneInDirection, cx| {
|
||||
workspace.activate_pane_in_direction(action.0, cx)
|
||||
},
|
||||
);
|
||||
|
||||
cx.add_action(|workspace: &mut Workspace, _: &ToggleLeftDock, cx| {
|
||||
workspace.toggle_dock(DockPosition::Left, cx);
|
||||
});
|
||||
@ -2054,6 +2064,40 @@ impl Workspace {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn activate_pane_in_direction(
|
||||
&mut self,
|
||||
direction: SplitDirection,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let bounding_box = match self.center.bounding_box_for_pane(&self.active_pane) {
|
||||
Some(coordinates) => coordinates,
|
||||
None => {
|
||||
return;
|
||||
}
|
||||
};
|
||||
let cursor = self.active_pane.read(cx).pixel_position_of_cursor(cx);
|
||||
let center = match cursor {
|
||||
Some(cursor) => cursor,
|
||||
None => bounding_box.center(),
|
||||
};
|
||||
|
||||
// currently there's a small gap between panes, so we can't just look "1px to the left"
|
||||
// instead of trying to calcuate this exactly, we assume it'll always be smaller than
|
||||
// "pane_gap" pixels (and that no-one uses panes smaller in any dimension than pane_gap).
|
||||
let pane_gap = 20.;
|
||||
|
||||
let target = match direction {
|
||||
SplitDirection::Left => vec2f(bounding_box.origin_x() - pane_gap, center.y()),
|
||||
SplitDirection::Right => vec2f(bounding_box.max_x() + pane_gap, center.y()),
|
||||
SplitDirection::Up => vec2f(center.x(), bounding_box.origin_y() - pane_gap),
|
||||
SplitDirection::Down => vec2f(center.x(), bounding_box.max_y() + pane_gap),
|
||||
};
|
||||
|
||||
if let Some(pane) = self.center.pane_at_pixel_position(target) {
|
||||
cx.focus(pane);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_pane_focused(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
|
||||
if self.active_pane != pane {
|
||||
self.active_pane = pane.clone();
|
||||
@ -3030,6 +3074,7 @@ impl Workspace {
|
||||
axis,
|
||||
members,
|
||||
flexes,
|
||||
bounding_boxes: _,
|
||||
}) => SerializedPaneGroup::Group {
|
||||
axis: *axis,
|
||||
children: members
|
||||
|
Loading…
Reference in New Issue
Block a user