Merge branch 'main' into vim-search

This commit is contained in:
Conrad Irwin 2023-07-17 09:20:43 -06:00
commit f887a17ffe
111 changed files with 5714 additions and 1553 deletions

View file

@ -27,7 +27,7 @@ use std::{
};
use theme::Theme;
#[derive(Eq, PartialEq, Hash)]
#[derive(Eq, PartialEq, Hash, Debug)]
pub enum ItemEvent {
CloseItem,
UpdateTab,

View file

@ -2316,6 +2316,7 @@ mod tests {
cx.set_global(SettingsStore::test(cx));
theme::init((), cx);
crate::init_settings(cx);
Project::init_settings(cx);
});
}

View file

@ -1,6 +1,8 @@
use std::sync::Arc;
use std::{cell::RefCell, rc::Rc, sync::Arc};
use crate::{AppState, FollowerStatesByLeader, Pane, Workspace, WorkspaceSettings};
use crate::{
pane_group::element::PaneAxisElement, AppState, FollowerStatesByLeader, Pane, Workspace,
};
use anyhow::{anyhow, Result};
use call::{ActiveCall, ParticipantLocation};
use gpui::{
@ -13,7 +15,11 @@ use project::Project;
use serde::Deserialize;
use theme::Theme;
#[derive(Clone, Debug, Eq, PartialEq)]
const HANDLE_HITBOX_SIZE: f32 = 4.0;
const HORIZONTAL_MIN_SIZE: f32 = 80.;
const VERTICAL_MIN_SIZE: f32 = 100.;
#[derive(Clone, Debug, PartialEq)]
pub struct PaneGroup {
pub(crate) root: Member,
}
@ -77,6 +83,7 @@ impl PaneGroup {
) -> AnyElement<Workspace> {
self.root.render(
project,
0,
theme,
follower_states,
active_call,
@ -94,7 +101,7 @@ impl PaneGroup {
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[derive(Clone, Debug, PartialEq)]
pub(crate) enum Member {
Axis(PaneAxis),
Pane(ViewHandle<Pane>),
@ -119,7 +126,7 @@ impl Member {
Down | Right => vec![Member::Pane(old_pane), Member::Pane(new_pane)],
};
Member::Axis(PaneAxis { axis, members })
Member::Axis(PaneAxis::new(axis, members))
}
fn contains(&self, needle: &ViewHandle<Pane>) -> bool {
@ -132,6 +139,7 @@ impl Member {
pub fn render(
&self,
project: &ModelHandle<Project>,
basis: usize,
theme: &Theme,
follower_states: &FollowerStatesByLeader,
active_call: Option<&ModelHandle<ActiveCall>>,
@ -272,6 +280,7 @@ impl Member {
}
Member::Axis(axis) => axis.render(
project,
basis + 1,
theme,
follower_states,
active_call,
@ -295,13 +304,35 @@ impl Member {
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct PaneAxis {
pub axis: Axis,
pub members: Vec<Member>,
pub flexes: Rc<RefCell<Vec<f32>>>,
}
impl PaneAxis {
pub fn new(axis: Axis, members: Vec<Member>) -> Self {
let flexes = Rc::new(RefCell::new(vec![1.; members.len()]));
Self {
axis,
members,
flexes,
}
}
pub fn load(axis: Axis, members: Vec<Member>, flexes: Option<Vec<f32>>) -> Self {
let flexes = flexes.unwrap_or_else(|| vec![1.; members.len()]);
debug_assert!(members.len() == flexes.len());
let flexes = Rc::new(RefCell::new(flexes));
Self {
axis,
members,
flexes,
}
}
fn split(
&mut self,
old_pane: &ViewHandle<Pane>,
@ -323,6 +354,7 @@ impl PaneAxis {
}
self.members.insert(idx, Member::Pane(new_pane.clone()));
*self.flexes.borrow_mut() = vec![1.; self.members.len()];
} else {
*member =
Member::new_axis(old_pane.clone(), new_pane.clone(), direction);
@ -362,10 +394,13 @@ impl PaneAxis {
if found_pane {
if let Some(idx) = remove_member {
self.members.remove(idx);
*self.flexes.borrow_mut() = vec![1.; self.members.len()];
}
if self.members.len() == 1 {
Ok(self.members.pop())
let result = self.members.pop();
*self.flexes.borrow_mut() = vec![1.; self.members.len()];
Ok(result)
} else {
Ok(None)
}
@ -377,6 +412,7 @@ impl PaneAxis {
fn render(
&self,
project: &ModelHandle<Project>,
basis: usize,
theme: &Theme,
follower_state: &FollowerStatesByLeader,
active_call: Option<&ModelHandle<ActiveCall>>,
@ -385,40 +421,50 @@ impl PaneAxis {
app_state: &Arc<AppState>,
cx: &mut ViewContext<Workspace>,
) -> AnyElement<Workspace> {
let last_member_ix = self.members.len() - 1;
Flex::new(self.axis)
.with_children(self.members.iter().enumerate().map(|(ix, member)| {
let mut flex = 1.0;
if member.contains(active_pane) {
flex = settings::get::<WorkspaceSettings>(cx).active_pane_magnification;
debug_assert!(self.members.len() == self.flexes.borrow().len());
let mut pane_axis = PaneAxisElement::new(self.axis, basis, self.flexes.clone());
let mut active_pane_ix = None;
let mut members = self.members.iter().enumerate().peekable();
while let Some((ix, member)) = members.next() {
let last = members.peek().is_none();
if member.contains(active_pane) {
active_pane_ix = Some(ix);
}
let mut member = member.render(
project,
(basis + ix) * 10,
theme,
follower_state,
active_call,
active_pane,
zoomed,
app_state,
cx,
);
if !last {
let mut border = theme.workspace.pane_divider;
border.left = false;
border.right = false;
border.top = false;
border.bottom = false;
match self.axis {
Axis::Vertical => border.bottom = true,
Axis::Horizontal => border.right = true,
}
let mut member = member.render(
project,
theme,
follower_state,
active_call,
active_pane,
zoomed,
app_state,
cx,
);
if ix < last_member_ix {
let mut border = theme.workspace.pane_divider;
border.left = false;
border.right = false;
border.top = false;
border.bottom = false;
match self.axis {
Axis::Vertical => border.bottom = true,
Axis::Horizontal => border.right = true,
}
member = member.contained().with_border(border).into_any();
}
member = member.contained().with_border(border).into_any();
}
FlexItem::new(member).flex(flex, true)
}))
.into_any()
pane_axis = pane_axis.with_child(member.into_any());
}
pane_axis.set_active_pane(active_pane_ix);
pane_axis.into_any()
}
}
@ -474,3 +520,336 @@ impl SplitDirection {
}
}
}
mod element {
use std::{cell::RefCell, ops::Range, rc::Rc};
use gpui::{
geometry::{
rect::RectF,
vector::{vec2f, Vector2F},
},
json::{self, ToJson},
platform::{CursorStyle, MouseButton},
AnyElement, Axis, CursorRegion, Element, LayoutContext, MouseRegion, RectFExt,
SceneBuilder, SizeConstraint, Vector2FExt, ViewContext,
};
use crate::{
pane_group::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE},
Workspace, WorkspaceSettings,
};
pub struct PaneAxisElement {
axis: Axis,
basis: usize,
active_pane_ix: Option<usize>,
flexes: Rc<RefCell<Vec<f32>>>,
children: Vec<AnyElement<Workspace>>,
}
impl PaneAxisElement {
pub fn new(axis: Axis, basis: usize, flexes: Rc<RefCell<Vec<f32>>>) -> Self {
Self {
axis,
basis,
flexes,
active_pane_ix: None,
children: Default::default(),
}
}
pub fn set_active_pane(&mut self, active_pane_ix: Option<usize>) {
self.active_pane_ix = active_pane_ix;
}
fn layout_children(
&mut self,
active_pane_magnification: f32,
constraint: SizeConstraint,
remaining_space: &mut f32,
remaining_flex: &mut f32,
cross_axis_max: &mut f32,
view: &mut Workspace,
cx: &mut LayoutContext<Workspace>,
) {
let flexes = self.flexes.borrow();
let cross_axis = self.axis.invert();
for (ix, child) in self.children.iter_mut().enumerate() {
let flex = if active_pane_magnification != 1. {
if let Some(active_pane_ix) = self.active_pane_ix {
if ix == active_pane_ix {
active_pane_magnification
} else {
1.
}
} else {
1.
}
} else {
flexes[ix]
};
let child_size = if *remaining_flex == 0.0 {
*remaining_space
} else {
let space_per_flex = *remaining_space / *remaining_flex;
space_per_flex * flex
};
let child_constraint = match self.axis {
Axis::Horizontal => SizeConstraint::new(
vec2f(child_size, constraint.min.y()),
vec2f(child_size, constraint.max.y()),
),
Axis::Vertical => SizeConstraint::new(
vec2f(constraint.min.x(), child_size),
vec2f(constraint.max.x(), child_size),
),
};
let child_size = child.layout(child_constraint, view, cx);
*remaining_space -= child_size.along(self.axis);
*remaining_flex -= flex;
*cross_axis_max = cross_axis_max.max(child_size.along(cross_axis));
}
}
}
impl Extend<AnyElement<Workspace>> for PaneAxisElement {
fn extend<T: IntoIterator<Item = AnyElement<Workspace>>>(&mut self, children: T) {
self.children.extend(children);
}
}
impl Element<Workspace> for PaneAxisElement {
type LayoutState = f32;
type PaintState = ();
fn layout(
&mut self,
constraint: SizeConstraint,
view: &mut Workspace,
cx: &mut LayoutContext<Workspace>,
) -> (Vector2F, Self::LayoutState) {
debug_assert!(self.children.len() == self.flexes.borrow().len());
let active_pane_magnification =
settings::get::<WorkspaceSettings>(cx).active_pane_magnification;
let mut remaining_flex = 0.;
if active_pane_magnification != 1. {
let active_pane_flex = self
.active_pane_ix
.map(|_| active_pane_magnification)
.unwrap_or(1.);
remaining_flex += self.children.len() as f32 - 1. + active_pane_flex;
} else {
for flex in self.flexes.borrow().iter() {
remaining_flex += flex;
}
}
let mut cross_axis_max: f32 = 0.0;
let mut remaining_space = constraint.max_along(self.axis);
if remaining_space.is_infinite() {
panic!("flex contains flexible children but has an infinite constraint along the flex axis");
}
self.layout_children(
active_pane_magnification,
constraint,
&mut remaining_space,
&mut remaining_flex,
&mut cross_axis_max,
view,
cx,
);
let mut size = match self.axis {
Axis::Horizontal => vec2f(constraint.max.x() - remaining_space, cross_axis_max),
Axis::Vertical => vec2f(cross_axis_max, constraint.max.y() - remaining_space),
};
if constraint.min.x().is_finite() {
size.set_x(size.x().max(constraint.min.x()));
}
if constraint.min.y().is_finite() {
size.set_y(size.y().max(constraint.min.y()));
}
if size.x() > constraint.max.x() {
size.set_x(constraint.max.x());
}
if size.y() > constraint.max.y() {
size.set_y(constraint.max.y());
}
(size, remaining_space)
}
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
remaining_space: &mut Self::LayoutState,
view: &mut Workspace,
cx: &mut ViewContext<Workspace>,
) -> Self::PaintState {
let can_resize = settings::get::<WorkspaceSettings>(cx).active_pane_magnification == 1.;
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
let overflowing = *remaining_space < 0.;
if overflowing {
scene.push_layer(Some(visible_bounds));
}
let mut child_origin = bounds.origin();
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);
match self.axis {
Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0),
Axis::Vertical => child_origin += vec2f(0.0, child.size().y()),
}
if let Some(Some((next_ix, next_child))) = can_resize.then(|| children_iter.peek())
{
scene.push_stacking_context(None, None);
let handle_origin = match self.axis {
Axis::Horizontal => child_origin - vec2f(HANDLE_HITBOX_SIZE / 2., 0.0),
Axis::Vertical => child_origin - vec2f(0.0, HANDLE_HITBOX_SIZE / 2.),
};
let handle_bounds = match self.axis {
Axis::Horizontal => RectF::new(
handle_origin,
vec2f(HANDLE_HITBOX_SIZE, visible_bounds.height()),
),
Axis::Vertical => RectF::new(
handle_origin,
vec2f(visible_bounds.width(), HANDLE_HITBOX_SIZE),
),
};
let style = match self.axis {
Axis::Horizontal => CursorStyle::ResizeLeftRight,
Axis::Vertical => CursorStyle::ResizeUpDown,
};
scene.push_cursor_region(CursorRegion {
bounds: handle_bounds,
style,
});
let axis = self.axis;
let child_size = child.size();
let next_child_size = next_child.size();
let drag_bounds = visible_bounds.clone();
let flexes = self.flexes.clone();
let current_flex = flexes.borrow()[ix];
let next_ix = *next_ix;
let next_flex = flexes.borrow()[next_ix];
enum ResizeHandle {}
let mut mouse_region = MouseRegion::new::<ResizeHandle>(
cx.view_id(),
self.basis + ix,
handle_bounds,
);
mouse_region = mouse_region.on_drag(
MouseButton::Left,
move |drag, workspace: &mut Workspace, cx| {
let min_size = match axis {
Axis::Horizontal => HORIZONTAL_MIN_SIZE,
Axis::Vertical => VERTICAL_MIN_SIZE,
};
// Don't allow resizing to less than the minimum size, if elements are already too small
if min_size - 1. > child_size.along(axis)
|| min_size - 1. > next_child_size.along(axis)
{
return;
}
let mut current_target_size = (drag.position - child_start).along(axis);
let proposed_current_pixel_change =
current_target_size - child_size.along(axis);
if proposed_current_pixel_change < 0. {
current_target_size = f32::max(current_target_size, min_size);
} else if proposed_current_pixel_change > 0. {
// TODO: cascade this change to other children if current item is at min size
let next_target_size = f32::max(
next_child_size.along(axis) - proposed_current_pixel_change,
min_size,
);
current_target_size = f32::min(
current_target_size,
child_size.along(axis) + next_child_size.along(axis)
- next_target_size,
);
}
let current_pixel_change = current_target_size - child_size.along(axis);
let flex_change = current_pixel_change / drag_bounds.length_along(axis);
let current_target_flex = current_flex + flex_change;
let next_target_flex = next_flex - flex_change;
let mut borrow = flexes.borrow_mut();
*borrow.get_mut(ix).unwrap() = current_target_flex;
*borrow.get_mut(next_ix).unwrap() = next_target_flex;
workspace.schedule_serialize(cx);
cx.notify();
},
);
scene.push_mouse_region(mouse_region);
scene.pop_stacking_context();
}
}
if overflowing {
scene.pop_layer();
}
}
fn rect_for_text_range(
&self,
range_utf16: Range<usize>,
_: RectF,
_: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
view: &Workspace,
cx: &ViewContext<Workspace>,
) -> Option<RectF> {
self.children
.iter()
.find_map(|child| child.rect_for_text_range(range_utf16.clone(), view, cx))
}
fn debug(
&self,
bounds: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
view: &Workspace,
cx: &ViewContext<Workspace>,
) -> json::Value {
serde_json::json!({
"type": "PaneAxis",
"bounds": bounds.to_json(),
"axis": self.axis.to_json(),
"flexes": *self.flexes.borrow(),
"children": self.children.iter().map(|child| child.debug(view, cx)).collect::<Vec<json::Value>>()
})
}
}
}

View file

@ -45,6 +45,7 @@ define_connection! {
// parent_group_id: Option<usize>, // None indicates that this is the root node
// position: Optiopn<usize>, // None indicates that this is the root node
// axis: Option<Axis>, // 'Vertical', 'Horizontal'
// flexes: Option<Vec<f32>>, // A JSON array of floats
// )
//
// panes(
@ -168,7 +169,12 @@ define_connection! {
ALTER TABLE workspaces ADD COLUMN left_dock_zoom INTEGER; //bool
ALTER TABLE workspaces ADD COLUMN right_dock_zoom INTEGER; //bool
ALTER TABLE workspaces ADD COLUMN bottom_dock_zoom INTEGER; //bool
)];
),
// Add pane group flex data
sql!(
ALTER TABLE pane_groups ADD COLUMN flexes TEXT;
)
];
}
impl WorkspaceDb {
@ -359,38 +365,51 @@ impl WorkspaceDb {
group_id: Option<GroupId>,
) -> Result<Vec<SerializedPaneGroup>> {
type GroupKey = (Option<GroupId>, WorkspaceId);
type GroupOrPane = (Option<GroupId>, Option<Axis>, Option<PaneId>, Option<bool>);
type GroupOrPane = (
Option<GroupId>,
Option<Axis>,
Option<PaneId>,
Option<bool>,
Option<String>,
);
self.select_bound::<GroupKey, GroupOrPane>(sql!(
SELECT group_id, axis, pane_id, active
SELECT group_id, axis, pane_id, active, flexes
FROM (SELECT
group_id,
axis,
NULL as pane_id,
NULL as active,
position,
parent_group_id,
workspace_id
FROM pane_groups
group_id,
axis,
NULL as pane_id,
NULL as active,
position,
parent_group_id,
workspace_id,
flexes
FROM pane_groups
UNION
SELECT
NULL,
NULL,
center_panes.pane_id,
panes.active as active,
position,
parent_group_id,
panes.workspace_id as workspace_id
FROM center_panes
JOIN panes ON center_panes.pane_id = panes.pane_id)
SELECT
NULL,
NULL,
center_panes.pane_id,
panes.active as active,
position,
parent_group_id,
panes.workspace_id as workspace_id,
NULL
FROM center_panes
JOIN panes ON center_panes.pane_id = panes.pane_id)
WHERE parent_group_id IS ? AND workspace_id = ?
ORDER BY position
))?((group_id, workspace_id))?
.into_iter()
.map(|(group_id, axis, pane_id, active)| {
.map(|(group_id, axis, pane_id, active, flexes)| {
if let Some((group_id, axis)) = group_id.zip(axis) {
let flexes = flexes
.map(|flexes| serde_json::from_str::<Vec<f32>>(&flexes))
.transpose()?;
Ok(SerializedPaneGroup::Group {
axis,
children: self.get_pane_group(workspace_id, Some(group_id))?,
flexes,
})
} else if let Some((pane_id, active)) = pane_id.zip(active) {
Ok(SerializedPaneGroup::Pane(SerializedPane::new(
@ -417,14 +436,34 @@ impl WorkspaceDb {
parent: Option<(GroupId, usize)>,
) -> Result<()> {
match pane_group {
SerializedPaneGroup::Group { axis, children } => {
SerializedPaneGroup::Group {
axis,
children,
flexes,
} => {
let (parent_id, position) = unzip_option(parent);
let flex_string = flexes
.as_ref()
.map(|flexes| serde_json::json!(flexes).to_string());
let group_id = conn.select_row_bound::<_, i64>(sql!(
INSERT INTO pane_groups(workspace_id, parent_group_id, position, axis)
VALUES (?, ?, ?, ?)
INSERT INTO pane_groups(
workspace_id,
parent_group_id,
position,
axis,
flexes
)
VALUES (?, ?, ?, ?, ?)
RETURNING group_id
))?((workspace_id, parent_id, position, *axis))?
))?((
workspace_id,
parent_id,
position,
*axis,
flex_string,
))?
.ok_or_else(|| anyhow!("Couldn't retrieve group_id from inserted pane_group"))?;
for (position, group) in children.iter().enumerate() {
@ -641,6 +680,14 @@ mod tests {
assert_eq!(test_text_1, "test-text-1");
}
fn group(axis: gpui::Axis, children: Vec<SerializedPaneGroup>) -> SerializedPaneGroup {
SerializedPaneGroup::Group {
axis,
flexes: None,
children,
}
}
#[gpui::test]
async fn test_full_workspace_serialization() {
env_logger::try_init().ok();
@ -652,12 +699,12 @@ mod tests {
// | - - - | |
// | 3,4 | |
// -----------------
let center_group = SerializedPaneGroup::Group {
axis: gpui::Axis::Horizontal,
children: vec![
SerializedPaneGroup::Group {
axis: gpui::Axis::Vertical,
children: vec![
let center_group = group(
gpui::Axis::Horizontal,
vec![
group(
gpui::Axis::Vertical,
vec![
SerializedPaneGroup::Pane(SerializedPane::new(
vec![
SerializedItem::new("Terminal", 5, false),
@ -673,7 +720,7 @@ mod tests {
false,
)),
],
},
),
SerializedPaneGroup::Pane(SerializedPane::new(
vec![
SerializedItem::new("Terminal", 9, false),
@ -682,7 +729,7 @@ mod tests {
false,
)),
],
};
);
let workspace = SerializedWorkspace {
id: 5,
@ -811,12 +858,12 @@ mod tests {
// | - - - | |
// | 3,4 | |
// -----------------
let center_pane = SerializedPaneGroup::Group {
axis: gpui::Axis::Horizontal,
children: vec![
SerializedPaneGroup::Group {
axis: gpui::Axis::Vertical,
children: vec![
let center_pane = group(
gpui::Axis::Horizontal,
vec![
group(
gpui::Axis::Vertical,
vec![
SerializedPaneGroup::Pane(SerializedPane::new(
vec![
SerializedItem::new("Terminal", 1, false),
@ -832,7 +879,7 @@ mod tests {
true,
)),
],
},
),
SerializedPaneGroup::Pane(SerializedPane::new(
vec![
SerializedItem::new("Terminal", 5, true),
@ -841,7 +888,7 @@ mod tests {
false,
)),
],
};
);
let workspace = default_workspace(&["/tmp"], &center_pane);
@ -858,12 +905,12 @@ mod tests {
let db = WorkspaceDb(open_test_db("test_cleanup_panes").await);
let center_pane = SerializedPaneGroup::Group {
axis: gpui::Axis::Horizontal,
children: vec![
SerializedPaneGroup::Group {
axis: gpui::Axis::Vertical,
children: vec![
let center_pane = group(
gpui::Axis::Horizontal,
vec![
group(
gpui::Axis::Vertical,
vec![
SerializedPaneGroup::Pane(SerializedPane::new(
vec![
SerializedItem::new("Terminal", 1, false),
@ -879,7 +926,7 @@ mod tests {
true,
)),
],
},
),
SerializedPaneGroup::Pane(SerializedPane::new(
vec![
SerializedItem::new("Terminal", 5, false),
@ -888,7 +935,7 @@ mod tests {
false,
)),
],
};
);
let id = &["/tmp"];
@ -896,9 +943,9 @@ mod tests {
db.save_workspace(workspace.clone()).await;
workspace.center_group = SerializedPaneGroup::Group {
axis: gpui::Axis::Vertical,
children: vec![
workspace.center_group = group(
gpui::Axis::Vertical,
vec![
SerializedPaneGroup::Pane(SerializedPane::new(
vec![
SerializedItem::new("Terminal", 1, false),
@ -914,7 +961,7 @@ mod tests {
true,
)),
],
};
);
db.save_workspace(workspace.clone()).await;

View file

@ -127,10 +127,11 @@ impl Bind for DockData {
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
#[derive(Debug, PartialEq, Clone)]
pub enum SerializedPaneGroup {
Group {
axis: Axis,
flexes: Option<Vec<f32>>,
children: Vec<SerializedPaneGroup>,
},
Pane(SerializedPane),
@ -149,7 +150,7 @@ impl Default for SerializedPaneGroup {
impl SerializedPaneGroup {
#[async_recursion(?Send)]
pub(crate) async fn deserialize(
&self,
self,
project: &ModelHandle<Project>,
workspace_id: WorkspaceId,
workspace: &WeakViewHandle<Workspace>,
@ -160,7 +161,11 @@ impl SerializedPaneGroup {
Vec<Option<Box<dyn ItemHandle>>>,
)> {
match self {
SerializedPaneGroup::Group { axis, children } => {
SerializedPaneGroup::Group {
axis,
children,
flexes,
} => {
let mut current_active_pane = None;
let mut members = Vec::new();
let mut items = Vec::new();
@ -184,10 +189,7 @@ impl SerializedPaneGroup {
}
Some((
Member::Axis(PaneAxis {
axis: *axis,
members,
}),
Member::Axis(PaneAxis::load(axis, members, flexes)),
current_active_pane,
items,
))

View file

@ -37,7 +37,11 @@ pub trait SearchableItem: Item {
regex: true,
}
}
fn to_search_event(event: &Self::Event) -> Option<SearchEvent>;
fn to_search_event(
&mut self,
event: &Self::Event,
cx: &mut ViewContext<Self>,
) -> Option<SearchEvent>;
fn clear_matches(&mut self, cx: &mut ViewContext<Self>);
fn update_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>);
fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String;
@ -47,6 +51,7 @@ pub trait SearchableItem: Item {
matches: Vec<Self::Match>,
cx: &mut ViewContext<Self>,
);
fn select_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>);
fn match_index_for_direction(
&mut self,
matches: &Vec<Self::Match>,
@ -97,6 +102,7 @@ pub trait SearchableItemHandle: ItemHandle {
matches: &Vec<Box<dyn Any + Send>>,
cx: &mut WindowContext,
);
fn select_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut WindowContext);
fn match_index_for_direction(
&self,
matches: &Vec<Box<dyn Any + Send>>,
@ -135,8 +141,9 @@ impl<T: SearchableItem> SearchableItemHandle for ViewHandle<T> {
cx: &mut WindowContext,
handler: Box<dyn Fn(SearchEvent, &mut WindowContext)>,
) -> Subscription {
cx.subscribe(self, move |_, event, cx| {
if let Some(search_event) = T::to_search_event(event) {
cx.subscribe(self, move |handle, event, cx| {
let search_event = handle.update(cx, |handle, cx| handle.to_search_event(event, cx));
if let Some(search_event) = search_event {
handler(search_event, cx)
}
})
@ -161,6 +168,12 @@ impl<T: SearchableItem> SearchableItemHandle for ViewHandle<T> {
let matches = downcast_matches(matches);
self.update(cx, |this, cx| this.activate_match(index, matches, cx));
}
fn select_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut WindowContext) {
let matches = downcast_matches(matches);
self.update(cx, |this, cx| this.select_matches(matches, cx));
}
fn match_index_for_direction(
&self,
matches: &Vec<Box<dyn Any + Send>>,

View file

@ -1,8 +1,4 @@
pub mod dock;
/// NOTE: Focus only 'takes' after an update has flushed_effects.
///
/// This may cause issues when you're trying to write tests that use workspace focus to add items at
/// specific locations.
pub mod item;
pub mod notifications;
pub mod pane;
@ -508,6 +504,7 @@ pub struct Workspace {
subscriptions: Vec<Subscription>,
_apply_leader_updates: Task<Result<()>>,
_observe_current_user: Task<Result<()>>,
_schedule_serialize: Option<Task<()>>,
pane_history_timestamp: Arc<AtomicUsize>,
}
@ -722,6 +719,7 @@ impl Workspace {
app_state,
_observe_current_user,
_apply_leader_updates,
_schedule_serialize: None,
leader_updates_tx,
subscriptions,
pane_history_timestamp,
@ -1823,6 +1821,13 @@ impl Workspace {
.update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
}
pub fn split_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
let new_pane = self.split_pane(self.active_pane.clone(), SplitDirection::Right, cx);
new_pane.update(cx, move |new_pane, cx| {
new_pane.add_item(item, true, true, None, cx)
})
}
pub fn open_abs_path(
&mut self,
abs_path: PathBuf,
@ -1853,6 +1858,21 @@ impl Workspace {
})
}
pub fn split_abs_path(
&mut self,
abs_path: PathBuf,
visible: bool,
cx: &mut ViewContext<Self>,
) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
let project_path_task =
Workspace::project_path_for_path(self.project.clone(), &abs_path, visible, cx);
cx.spawn(|this, mut cx| async move {
let (_, path) = project_path_task.await?;
this.update(&mut cx, |this, cx| this.split_path(path, cx))?
.await
})
}
pub fn open_path(
&mut self,
path: impl Into<ProjectPath>,
@ -1878,6 +1898,38 @@ impl Workspace {
})
}
pub fn split_path(
&mut self,
path: impl Into<ProjectPath>,
cx: &mut ViewContext<Self>,
) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
let pane = self.last_active_center_pane.clone().unwrap_or_else(|| {
self.panes
.first()
.expect("There must be an active pane")
.downgrade()
});
if let Member::Pane(center_pane) = &self.center.root {
if center_pane.read(cx).items_len() == 0 {
return self.open_path(path, Some(pane), true, cx);
}
}
let task = self.load_path(path.into(), cx);
cx.spawn(|this, mut cx| async move {
let (project_entry_id, build_item) = task.await?;
this.update(&mut cx, move |this, cx| -> Option<_> {
let pane = pane.upgrade(cx)?;
let new_pane = this.split_pane(pane, SplitDirection::Right, cx);
new_pane.update(cx, |new_pane, cx| {
Some(new_pane.open_item(project_entry_id, true, cx, build_item))
})
})
.map(|option| option.ok_or_else(|| anyhow!("pane was dropped")))?
})
}
pub(crate) fn load_path(
&mut self,
path: ProjectPath,
@ -1928,6 +1980,30 @@ impl Workspace {
item
}
pub fn split_project_item<T>(
&mut self,
project_item: ModelHandle<T::Item>,
cx: &mut ViewContext<Self>,
) -> ViewHandle<T>
where
T: ProjectItem,
{
use project::Item as _;
let entry_id = project_item.read(cx).entry_id(cx);
if let Some(item) = entry_id
.and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
.and_then(|item| item.downcast())
{
self.activate_item(&item, cx);
return item;
}
let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
self.split_item(Box::new(item.clone()), cx);
item
}
pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) {
self.active_pane.update(cx, |pane, cx| {
@ -1955,7 +2031,7 @@ impl Workspace {
if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
cx.focus(&pane);
} else {
self.split_pane(self.active_pane.clone(), SplitDirection::Right, cx);
self.split_and_clone(self.active_pane.clone(), SplitDirection::Right, cx);
}
}
@ -2008,7 +2084,7 @@ impl Workspace {
match event {
pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx),
pane::Event::Split(direction) => {
self.split_pane(pane, *direction, cx);
self.split_and_clone(pane, *direction, cx);
}
pane::Event::Remove => self.remove_pane(pane, cx),
pane::Event::ActivateItem { local } => {
@ -2059,6 +2135,20 @@ impl Workspace {
}
pub fn split_pane(
&mut self,
pane_to_split: ViewHandle<Pane>,
split_direction: SplitDirection,
cx: &mut ViewContext<Self>,
) -> ViewHandle<Pane> {
let new_pane = self.add_pane(cx);
self.center
.split(&pane_to_split, &new_pane, split_direction)
.unwrap();
cx.notify();
new_pane
}
pub fn split_and_clone(
&mut self,
pane: ViewHandle<Pane>,
direction: SplitDirection,
@ -2897,6 +2987,14 @@ impl Workspace {
cx.notify();
}
fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {
self._schedule_serialize = Some(cx.spawn(|this, cx| async move {
cx.background().timer(Duration::from_millis(100)).await;
this.read_with(&cx, |this, cx| this.serialize_workspace(cx))
.ok();
}));
}
fn serialize_workspace(&self, cx: &ViewContext<Self>) {
fn serialize_pane_handle(
pane_handle: &ViewHandle<Pane>,
@ -2927,12 +3025,17 @@ impl Workspace {
cx: &AppContext,
) -> SerializedPaneGroup {
match pane_group {
Member::Axis(PaneAxis { axis, members }) => SerializedPaneGroup::Group {
Member::Axis(PaneAxis {
axis,
members,
flexes,
}) => SerializedPaneGroup::Group {
axis: *axis,
children: members
.iter()
.map(|member| build_serialized_pane_group(member, cx))
.collect::<Vec<_>>(),
flexes: Some(flexes.borrow().clone()),
},
Member::Pane(pane_handle) => {
SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx))
@ -3399,27 +3502,11 @@ fn notify_if_database_failed(workspace: &WeakViewHandle<Workspace>, cx: &mut Asy
if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
workspace.show_notification_once(0, cx, |cx| {
cx.add_view(|_| {
MessageNotification::new("Failed to load any database file.")
MessageNotification::new("Failed to load the database file.")
.with_click_message("Click to let us know about this error")
.on_click(|cx| cx.platform().open_url(REPORT_ISSUE_URL))
})
});
} else {
let backup_path = (*db::BACKUP_DB_PATH).read();
if let Some(backup_path) = backup_path.clone() {
workspace.show_notification_once(1, cx, move |cx| {
cx.add_view(move |_| {
MessageNotification::new(format!(
"Database file was corrupted. Old database backed up to {}",
backup_path.display()
))
.with_click_message("Click to show old database in finder")
.on_click(move |cx| {
cx.platform().open_url(&backup_path.to_string_lossy())
})
})
});
}
}
})
.log_err();
@ -4235,7 +4322,7 @@ mod tests {
});
workspace
.split_pane(left_pane.clone(), SplitDirection::Right, cx)
.split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
.unwrap();
left_pane