Merge remote-tracking branch 'origin/main' into zmd

This commit is contained in:
Nathan Sobo 2023-05-24 11:04:07 -06:00
commit 747322a02d
107 changed files with 5048 additions and 3991 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,125 +0,0 @@
use super::{icon_for_dock_anchor, Dock, FocusDock, HideDock};
use crate::{handle_dropped_item, StatusItemView, Workspace};
use gpui::{
elements::{Empty, MouseEventHandler, Svg},
platform::CursorStyle,
platform::MouseButton,
AnyElement, Element, Entity, View, ViewContext, ViewHandle, WeakViewHandle,
};
pub struct ToggleDockButton {
workspace: WeakViewHandle<Workspace>,
}
impl ToggleDockButton {
pub fn new(workspace: ViewHandle<Workspace>, cx: &mut ViewContext<Self>) -> Self {
// When dock moves, redraw so that the icon and toggle status matches.
cx.subscribe(&workspace, |_, _, _, cx| cx.notify()).detach();
Self {
workspace: workspace.downgrade(),
}
}
}
impl Entity for ToggleDockButton {
type Event = ();
}
impl View for ToggleDockButton {
fn ui_name() -> &'static str {
"Dock Toggle"
}
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> AnyElement<Self> {
let workspace = self.workspace.upgrade(cx);
if workspace.is_none() {
return Empty::new().into_any();
}
let workspace = workspace.unwrap();
let dock_position = workspace.read(cx).dock.position;
let dock_pane = workspace.read(cx).dock_pane().clone();
let theme = theme::current(cx).clone();
let button = MouseEventHandler::<Self, _>::new(0, cx, {
let theme = theme.clone();
move |state, _| {
let style = theme
.workspace
.status_bar
.sidebar_buttons
.item
.style_for(state, dock_position.is_visible());
Svg::new(icon_for_dock_anchor(dock_position.anchor()))
.with_color(style.icon_color)
.constrained()
.with_width(style.icon_size)
.with_height(style.icon_size)
.contained()
.with_style(style.container)
}
})
.with_cursor_style(CursorStyle::PointingHand)
.on_up(MouseButton::Left, move |event, this, cx| {
let drop_index = dock_pane.read(cx).items_len() + 1;
handle_dropped_item(
event,
this.workspace.clone(),
&dock_pane.downgrade(),
drop_index,
false,
None,
cx,
);
});
if dock_position.is_visible() {
button
.on_click(MouseButton::Left, |_, this, cx| {
if let Some(workspace) = this.workspace.upgrade(cx) {
workspace.update(cx, |workspace, cx| {
Dock::hide_dock(workspace, &Default::default(), cx)
})
}
})
.with_tooltip::<Self>(
0,
"Hide Dock".into(),
Some(Box::new(HideDock)),
theme.tooltip.clone(),
cx,
)
} else {
button
.on_click(MouseButton::Left, |_, this, cx| {
if let Some(workspace) = this.workspace.upgrade(cx) {
workspace.update(cx, |workspace, cx| {
Dock::focus_dock(workspace, &Default::default(), cx)
})
}
})
.with_tooltip::<Self>(
0,
"Focus Dock".into(),
Some(Box::new(FocusDock)),
theme.tooltip.clone(),
cx,
)
}
.into_any()
}
}
impl StatusItemView for ToggleDockButton {
fn set_active_pane_item(
&mut self,
_active_pane_item: Option<&dyn crate::ItemHandle>,
_cx: &mut ViewContext<Self>,
) {
//Not applicable
}
}

View file

@ -437,7 +437,7 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
for item_event in T::to_item_events(event).into_iter() {
match item_event {
ItemEvent::CloseItem => {
Pane::close_item_by_id(workspace, pane, item.id(), cx)
pane.update(cx, |pane, cx| pane.close_item_by_id(item.id(), cx))
.detach_and_log_err(cx);
return;
}
@ -769,7 +769,7 @@ impl<T: FollowableItem> FollowableItemHandle for ViewHandle<T> {
#[cfg(test)]
pub(crate) mod test {
use super::{Item, ItemEvent};
use crate::{sidebar::SidebarItem, ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId};
use crate::{ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId};
use gpui::{
elements::Empty, AnyElement, AppContext, Element, Entity, ModelHandle, Task, View,
ViewContext, ViewHandle, WeakViewHandle,
@ -1062,6 +1062,4 @@ pub(crate) mod test {
Task::Ready(Some(anyhow::Ok(view)))
}
}
impl SidebarItem for TestItem {}
}

File diff suppressed because it is too large Load diff

View file

@ -12,6 +12,7 @@ use gpui::{
use project::ProjectEntryId;
pub fn dragged_item_receiver<Tag, D, F>(
pane: &Pane,
region_id: usize,
drop_index: usize,
allow_same_pane: bool,
@ -24,22 +25,24 @@ where
D: Element<Pane>,
F: FnOnce(&mut MouseState, &mut ViewContext<Pane>) -> D,
{
MouseEventHandler::<Tag, _>::above(region_id, cx, |state, cx| {
let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
let drag_position = if (pane.can_drop)(drag_and_drop, cx) {
drag_and_drop
.currently_dragged::<DraggedItem>(cx.window_id())
.map(|(drag_position, _)| drag_position)
.or_else(|| {
drag_and_drop
.currently_dragged::<ProjectEntryId>(cx.window_id())
.map(|(drag_position, _)| drag_position)
})
} else {
None
};
let mut handler = MouseEventHandler::<Tag, _>::above(region_id, cx, |state, cx| {
// Observing hovered will cause a render when the mouse enters regardless
// of if mouse position was accessed before
let drag_position = if state.hovered() {
cx.global::<DragAndDrop<Workspace>>()
.currently_dragged::<DraggedItem>(cx.window_id())
.map(|(drag_position, _)| drag_position)
.or_else(|| {
cx.global::<DragAndDrop<Workspace>>()
.currently_dragged::<ProjectEntryId>(cx.window_id())
.map(|(drag_position, _)| drag_position)
})
} else {
None
};
let drag_position = if state.hovered() { drag_position } else { None };
Stack::new()
.with_child(render_child(state, cx))
.with_children(drag_position.map(|drag_position| {
@ -64,38 +67,44 @@ where
}
})
}))
})
.on_up(MouseButton::Left, {
move |event, pane, cx| {
let workspace = pane.workspace.clone();
let pane = cx.weak_handle();
handle_dropped_item(
event,
workspace,
&pane,
drop_index,
allow_same_pane,
split_margin,
cx,
);
cx.notify();
}
})
.on_move(|_, _, cx| {
let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
});
if drag_and_drop
.currently_dragged::<DraggedItem>(cx.window_id())
.is_some()
|| drag_and_drop
.currently_dragged::<ProjectEntryId>(cx.window_id())
.is_some()
{
cx.notify();
} else {
cx.propagate_event();
}
})
if drag_position.is_some() {
handler = handler
.on_up(MouseButton::Left, {
move |event, pane, cx| {
let workspace = pane.workspace.clone();
let pane = cx.weak_handle();
handle_dropped_item(
event,
workspace,
&pane,
drop_index,
allow_same_pane,
split_margin,
cx,
);
cx.notify();
}
})
.on_move(|_, _, cx| {
let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
if drag_and_drop
.currently_dragged::<DraggedItem>(cx.window_id())
.is_some()
|| drag_and_drop
.currently_dragged::<ProjectEntryId>(cx.window_id())
.is_some()
{
cx.notify();
} else {
cx.propagate_event();
}
})
}
handler
}
pub fn handle_dropped_item<V: View>(
@ -115,7 +124,7 @@ pub fn handle_dropped_item<V: View>(
let action = if let Some((_, dragged_item)) =
drag_and_drop.currently_dragged::<DraggedItem>(cx.window_id())
{
Action::Move(dragged_item.pane.clone(), dragged_item.item.id())
Action::Move(dragged_item.pane.clone(), dragged_item.handle.id())
} else if let Some((_, project_entry)) =
drag_and_drop.currently_dragged::<ProjectEntryId>(cx.window_id())
{

View file

@ -7,7 +7,7 @@ use gpui::{
elements::*,
geometry::{rect::RectF, vector::Vector2F},
platform::{CursorStyle, MouseButton},
Axis, Border, ModelHandle, ViewContext, ViewHandle,
AnyViewHandle, Axis, Border, ModelHandle, ViewContext, ViewHandle,
};
use project::Project;
use serde::Deserialize;
@ -71,6 +71,7 @@ impl PaneGroup {
follower_states: &FollowerStatesByLeader,
active_call: Option<&ModelHandle<ActiveCall>>,
active_pane: &ViewHandle<Pane>,
zoomed: Option<&AnyViewHandle>,
app_state: &Arc<AppState>,
cx: &mut ViewContext<Workspace>,
) -> AnyElement<Workspace> {
@ -80,6 +81,7 @@ impl PaneGroup {
follower_states,
active_call,
active_pane,
zoomed,
app_state,
cx,
)
@ -134,6 +136,7 @@ impl Member {
follower_states: &FollowerStatesByLeader,
active_call: Option<&ModelHandle<ActiveCall>>,
active_pane: &ViewHandle<Pane>,
zoomed: Option<&AnyViewHandle>,
app_state: &Arc<AppState>,
cx: &mut ViewContext<Workspace>,
) -> AnyElement<Workspace> {
@ -141,6 +144,12 @@ impl Member {
match self {
Member::Pane(pane) => {
let pane_element = if Some(&**pane) == zoomed {
Empty::new().into_any()
} else {
ChildView::new(pane, cx).into_any()
};
let leader = follower_states
.iter()
.find_map(|(leader_id, follower_states)| {
@ -257,7 +266,7 @@ impl Member {
};
Stack::new()
.with_child(ChildView::new(pane, cx).contained().with_border(border))
.with_child(pane_element.contained().with_border(border))
.with_children(leader_status_box)
.into_any()
}
@ -267,6 +276,7 @@ impl Member {
follower_states,
active_call,
active_pane,
zoomed,
app_state,
cx,
),
@ -371,6 +381,7 @@ impl PaneAxis {
follower_state: &FollowerStatesByLeader,
active_call: Option<&ModelHandle<ActiveCall>>,
active_pane: &ViewHandle<Pane>,
zoomed: Option<&AnyViewHandle>,
app_state: &Arc<AppState>,
cx: &mut ViewContext<Workspace>,
) -> AnyElement<Workspace> {
@ -388,6 +399,7 @@ impl PaneAxis {
follower_state,
active_call,
active_pane,
zoomed,
app_state,
cx,
);

View file

@ -11,7 +11,6 @@ use gpui::{platform::WindowBounds, Axis};
use util::{unzip_option, ResultExt};
use uuid::Uuid;
use crate::dock::DockPosition;
use crate::WorkspaceId;
use model::{
@ -19,15 +18,17 @@ use model::{
WorkspaceLocation,
};
use self::model::DockStructure;
define_connection! {
// Current schema shape using pseudo-rust syntax:
//
// workspaces(
// workspace_id: usize, // Primary key for workspaces
// workspace_location: Bincode<Vec<PathBuf>>,
// dock_visible: bool,
// dock_anchor: DockAnchor, // 'Bottom' / 'Right' / 'Expanded'
// dock_pane: Option<usize>, // PaneId
// dock_visible: bool, // Deprecated
// dock_anchor: DockAnchor, // Deprecated
// dock_pane: Option<usize>, // Deprecated
// left_sidebar_open: boolean,
// timestamp: String, // UTC YYYY-MM-DD HH:MM:SS
// window_state: String, // WindowBounds Discriminant
@ -71,10 +72,10 @@ define_connection! {
CREATE TABLE workspaces(
workspace_id INTEGER PRIMARY KEY,
workspace_location BLOB UNIQUE,
dock_visible INTEGER, // Boolean
dock_anchor TEXT, // Enum: 'Bottom' / 'Right' / 'Expanded'
dock_pane INTEGER, // NULL indicates that we don't have a dock pane yet
left_sidebar_open INTEGER, //Boolean
dock_visible INTEGER, // Deprecated. Preserving so users can downgrade Zed.
dock_anchor TEXT, // Deprecated. Preserving so users can downgrade Zed.
dock_pane INTEGER, // Deprecated. Preserving so users can downgrade Zed.
left_sidebar_open INTEGER, // Boolean
timestamp TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY(dock_pane) REFERENCES panes(pane_id)
) STRICT;
@ -131,6 +132,36 @@ define_connection! {
ALTER TABLE workspaces ADD COLUMN window_width REAL;
ALTER TABLE workspaces ADD COLUMN window_height REAL;
ALTER TABLE workspaces ADD COLUMN display BLOB;
),
// Drop foreign key constraint from workspaces.dock_pane to panes table.
sql!(
CREATE TABLE workspaces_2(
workspace_id INTEGER PRIMARY KEY,
workspace_location BLOB UNIQUE,
dock_visible INTEGER, // Deprecated. Preserving so users can downgrade Zed.
dock_anchor TEXT, // Deprecated. Preserving so users can downgrade Zed.
dock_pane INTEGER, // Deprecated. Preserving so users can downgrade Zed.
left_sidebar_open INTEGER, // Boolean
timestamp TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL,
window_state TEXT,
window_x REAL,
window_y REAL,
window_width REAL,
window_height REAL,
display BLOB
) STRICT;
INSERT INTO workspaces_2 SELECT * FROM workspaces;
DROP TABLE workspaces;
ALTER TABLE workspaces_2 RENAME TO workspaces;
),
// Add panels related information
sql!(
ALTER TABLE workspaces ADD COLUMN left_dock_visible INTEGER; //bool
ALTER TABLE workspaces ADD COLUMN left_dock_active_panel TEXT;
ALTER TABLE workspaces ADD COLUMN right_dock_visible INTEGER; //bool
ALTER TABLE workspaces ADD COLUMN right_dock_active_panel TEXT;
ALTER TABLE workspaces ADD COLUMN bottom_dock_visible INTEGER; //bool
ALTER TABLE workspaces ADD COLUMN bottom_dock_active_panel TEXT;
)];
}
@ -146,27 +177,29 @@ impl WorkspaceDb {
// Note that we re-assign the workspace_id here in case it's empty
// and we've grabbed the most recent workspace
let (workspace_id, workspace_location, left_sidebar_open, dock_position, bounds, display): (
let (workspace_id, workspace_location, bounds, display, docks): (
WorkspaceId,
WorkspaceLocation,
bool,
DockPosition,
Option<WindowBounds>,
Option<Uuid>,
DockStructure,
) = self
.select_row_bound(sql! {
SELECT
workspace_id,
workspace_location,
left_sidebar_open,
dock_visible,
dock_anchor,
window_state,
window_x,
window_y,
window_width,
window_height,
display
display,
left_dock_visible,
left_dock_active_panel,
right_dock_visible,
right_dock_active_panel,
bottom_dock_visible,
bottom_dock_active_panel
FROM workspaces
WHERE workspace_location = ?
})
@ -178,18 +211,13 @@ impl WorkspaceDb {
Some(SerializedWorkspace {
id: workspace_id,
location: workspace_location.clone(),
dock_pane: self
.get_dock_pane(workspace_id)
.context("Getting dock pane")
.log_err()?,
center_group: self
.get_center_pane_group(workspace_id)
.context("Getting center group")
.log_err()?,
dock_position,
left_sidebar_open,
bounds,
display,
docks,
})
}
@ -200,7 +228,6 @@ impl WorkspaceDb {
conn.with_savepoint("update_worktrees", || {
// Clear out panes and pane_groups
conn.exec_bound(sql!(
UPDATE workspaces SET dock_pane = NULL WHERE workspace_id = ?1;
DELETE FROM pane_groups WHERE workspace_id = ?1;
DELETE FROM panes WHERE workspace_id = ?1;))?(workspace.id)
.expect("Clearing old panes");
@ -215,42 +242,32 @@ impl WorkspaceDb {
INSERT INTO workspaces(
workspace_id,
workspace_location,
left_sidebar_open,
dock_visible,
dock_anchor,
left_dock_visible,
left_dock_active_panel,
right_dock_visible,
right_dock_active_panel,
bottom_dock_visible,
bottom_dock_active_panel,
timestamp
)
VALUES (?1, ?2, ?3, ?4, ?5, CURRENT_TIMESTAMP)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, CURRENT_TIMESTAMP)
ON CONFLICT DO
UPDATE SET
workspace_location = ?2,
left_sidebar_open = ?3,
dock_visible = ?4,
dock_anchor = ?5,
left_dock_visible = ?3,
left_dock_active_panel = ?4,
right_dock_visible = ?5,
right_dock_active_panel = ?6,
bottom_dock_visible = ?7,
bottom_dock_active_panel = ?8,
timestamp = CURRENT_TIMESTAMP
))?((
workspace.id,
&workspace.location,
workspace.left_sidebar_open,
workspace.dock_position,
))
))?((workspace.id, &workspace.location, workspace.docks))
.context("Updating workspace")?;
// Save center pane group and dock pane
// Save center pane group
Self::save_pane_group(conn, workspace.id, &workspace.center_group, None)
.context("save pane group in save workspace")?;
let dock_id = Self::save_pane(conn, workspace.id, &workspace.dock_pane, None, true)
.context("save pane in save workspace")?;
// Complete workspace initialization
conn.exec_bound(sql!(
UPDATE workspaces
SET dock_pane = ?
WHERE workspace_id = ?
))?((dock_id, workspace.id))
.context("Finishing initialization with dock pane")?;
Ok(())
})
.log_err();
@ -402,32 +419,17 @@ impl WorkspaceDb {
Ok(())
}
SerializedPaneGroup::Pane(pane) => {
Self::save_pane(conn, workspace_id, &pane, parent, false)?;
Self::save_pane(conn, workspace_id, &pane, parent)?;
Ok(())
}
}
}
fn get_dock_pane(&self, workspace_id: WorkspaceId) -> Result<SerializedPane> {
let (pane_id, active) = self.select_row_bound(sql!(
SELECT pane_id, active
FROM panes
WHERE pane_id = (SELECT dock_pane FROM workspaces WHERE workspace_id = ?)
))?(workspace_id)?
.context("No dock pane for workspace")?;
Ok(SerializedPane::new(
self.get_items(pane_id).context("Reading items")?,
active,
))
}
fn save_pane(
conn: &Connection,
workspace_id: WorkspaceId,
pane: &SerializedPane,
parent: Option<(GroupId, usize)>, // None indicates BOTH dock pane AND center_pane
dock: bool,
parent: Option<(GroupId, usize)>,
) -> Result<PaneId> {
let pane_id = conn.select_row_bound::<_, i64>(sql!(
INSERT INTO panes(workspace_id, active)
@ -436,13 +438,11 @@ impl WorkspaceDb {
))?((workspace_id, pane.active))?
.ok_or_else(|| anyhow!("Could not retrieve inserted pane_id"))?;
if !dock {
let (parent_id, order) = unzip_option(parent);
conn.exec_bound(sql!(
INSERT INTO center_panes(pane_id, parent_group_id, position)
VALUES (?, ?, ?)
))?((pane_id, parent_id, order))?;
}
let (parent_id, order) = unzip_option(parent);
conn.exec_bound(sql!(
INSERT INTO center_panes(pane_id, parent_group_id, position)
VALUES (?, ?, ?)
))?((pane_id, parent_id, order))?;
Self::save_items(conn, workspace_id, pane_id, &pane.children).context("Saving items")?;
@ -498,9 +498,7 @@ impl WorkspaceDb {
#[cfg(test)]
mod tests {
use super::*;
use crate::DockAnchor;
use db::open_test_db;
use std::sync::Arc;
#[gpui::test]
async fn test_next_id_stability() {
@ -575,23 +573,19 @@ mod tests {
let mut workspace_1 = SerializedWorkspace {
id: 1,
location: (["/tmp", "/tmp2"]).into(),
dock_position: crate::dock::DockPosition::Shown(DockAnchor::Bottom),
center_group: Default::default(),
dock_pane: Default::default(),
left_sidebar_open: true,
bounds: Default::default(),
display: Default::default(),
docks: Default::default(),
};
let mut workspace_2 = SerializedWorkspace {
let workspace_2 = SerializedWorkspace {
id: 2,
location: (["/tmp"]).into(),
dock_position: crate::dock::DockPosition::Hidden(DockAnchor::Expanded),
center_group: Default::default(),
dock_pane: Default::default(),
left_sidebar_open: false,
bounds: Default::default(),
display: Default::default(),
docks: Default::default(),
};
db.save_workspace(workspace_1.clone()).await;
@ -615,12 +609,6 @@ mod tests {
workspace_1.location = (["/tmp", "/tmp3"]).into();
db.save_workspace(workspace_1.clone()).await;
db.save_workspace(workspace_1).await;
workspace_2.dock_pane.children.push(SerializedItem {
kind: Arc::from("Test"),
item_id: 10,
active: true,
});
db.save_workspace(workspace_2).await;
let test_text_2 = db
@ -644,16 +632,6 @@ mod tests {
let db = WorkspaceDb(open_test_db("test_full_workspace_serialization").await);
let dock_pane = crate::persistence::model::SerializedPane {
children: vec![
SerializedItem::new("Terminal", 1, false),
SerializedItem::new("Terminal", 2, false),
SerializedItem::new("Terminal", 3, true),
SerializedItem::new("Terminal", 4, false),
],
active: false,
};
// -----------------
// | 1,2 | 5,6 |
// | - - - | |
@ -694,12 +672,10 @@ mod tests {
let workspace = SerializedWorkspace {
id: 5,
location: (["/tmp", "/tmp2"]).into(),
dock_position: DockPosition::Shown(DockAnchor::Bottom),
center_group,
dock_pane,
left_sidebar_open: true,
bounds: Default::default(),
display: Default::default(),
docks: Default::default(),
};
db.save_workspace(workspace.clone()).await;
@ -724,23 +700,19 @@ mod tests {
let workspace_1 = SerializedWorkspace {
id: 1,
location: (["/tmp", "/tmp2"]).into(),
dock_position: crate::dock::DockPosition::Shown(DockAnchor::Bottom),
center_group: Default::default(),
dock_pane: Default::default(),
left_sidebar_open: true,
bounds: Default::default(),
display: Default::default(),
docks: Default::default(),
};
let mut workspace_2 = SerializedWorkspace {
id: 2,
location: (["/tmp"]).into(),
dock_position: crate::dock::DockPosition::Hidden(DockAnchor::Expanded),
center_group: Default::default(),
dock_pane: Default::default(),
left_sidebar_open: false,
bounds: Default::default(),
display: Default::default(),
docks: Default::default(),
};
db.save_workspace(workspace_1.clone()).await;
@ -773,12 +745,10 @@ mod tests {
let mut workspace_3 = SerializedWorkspace {
id: 3,
location: (&["/tmp", "/tmp2"]).into(),
dock_position: DockPosition::Shown(DockAnchor::Right),
center_group: Default::default(),
dock_pane: Default::default(),
left_sidebar_open: false,
bounds: Default::default(),
display: Default::default(),
docks: Default::default(),
};
db.save_workspace(workspace_3.clone()).await;
@ -798,52 +768,23 @@ mod tests {
);
}
use crate::dock::DockPosition;
use crate::persistence::model::SerializedWorkspace;
use crate::persistence::model::{SerializedItem, SerializedPane, SerializedPaneGroup};
fn default_workspace<P: AsRef<Path>>(
workspace_id: &[P],
dock_pane: SerializedPane,
center_group: &SerializedPaneGroup,
) -> SerializedWorkspace {
SerializedWorkspace {
id: 4,
location: workspace_id.into(),
dock_position: crate::dock::DockPosition::Hidden(DockAnchor::Right),
center_group: center_group.clone(),
dock_pane,
left_sidebar_open: true,
bounds: Default::default(),
display: Default::default(),
docks: Default::default(),
}
}
#[gpui::test]
async fn test_basic_dock_pane() {
env_logger::try_init().ok();
let db = WorkspaceDb(open_test_db("basic_dock_pane").await);
let dock_pane = crate::persistence::model::SerializedPane::new(
vec![
SerializedItem::new("Terminal", 1, false),
SerializedItem::new("Terminal", 4, false),
SerializedItem::new("Terminal", 2, false),
SerializedItem::new("Terminal", 3, true),
],
false,
);
let workspace = default_workspace(&["/tmp"], dock_pane, &Default::default());
db.save_workspace(workspace.clone()).await;
let new_workspace = db.workspace_for_roots(&["/tmp"]).unwrap();
assert_eq!(workspace.dock_pane, new_workspace.dock_pane);
}
#[gpui::test]
async fn test_simple_split() {
env_logger::try_init().ok();
@ -887,7 +828,7 @@ mod tests {
],
};
let workspace = default_workspace(&["/tmp"], Default::default(), &center_pane);
let workspace = default_workspace(&["/tmp"], &center_pane);
db.save_workspace(workspace.clone()).await;
@ -936,7 +877,7 @@ mod tests {
let id = &["/tmp"];
let mut workspace = default_workspace(id, Default::default(), &center_pane);
let mut workspace = default_workspace(id, &center_pane);
db.save_workspace(workspace.clone()).await;

View file

@ -1,7 +1,4 @@
use crate::{
dock::DockPosition, item::ItemHandle, DockAnchor, ItemDeserializers, Member, Pane, PaneAxis,
Workspace, WorkspaceId,
};
use crate::{item::ItemHandle, ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId};
use anyhow::{anyhow, Context, Result};
use async_recursion::async_recursion;
use db::sqlez::{
@ -62,12 +59,68 @@ impl Column for WorkspaceLocation {
pub struct SerializedWorkspace {
pub id: WorkspaceId,
pub location: WorkspaceLocation,
pub dock_position: DockPosition,
pub center_group: SerializedPaneGroup,
pub dock_pane: SerializedPane,
pub left_sidebar_open: bool,
pub bounds: Option<WindowBounds>,
pub display: Option<Uuid>,
pub docks: DockStructure,
}
#[derive(Debug, PartialEq, Clone, Default)]
pub struct DockStructure {
pub(crate) left: DockData,
pub(crate) right: DockData,
pub(crate) bottom: DockData,
}
impl Column for DockStructure {
fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
let (left, next_index) = DockData::column(statement, start_index)?;
let (right, next_index) = DockData::column(statement, next_index)?;
let (bottom, next_index) = DockData::column(statement, next_index)?;
Ok((
DockStructure {
left,
right,
bottom,
},
next_index,
))
}
}
impl Bind for DockStructure {
fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
let next_index = statement.bind(&self.left, start_index)?;
let next_index = statement.bind(&self.right, next_index)?;
statement.bind(&self.bottom, next_index)
}
}
#[derive(Debug, PartialEq, Clone, Default)]
pub struct DockData {
pub(crate) visible: bool,
pub(crate) active_panel: Option<String>,
}
impl Column for DockData {
fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
let (visible, next_index) = Option::<bool>::column(statement, start_index)?;
let (active_panel, next_index) = Option::<String>::column(statement, next_index)?;
Ok((
DockData {
visible: visible.unwrap_or(false),
active_panel,
},
next_index,
))
}
}
impl Bind for DockData {
fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
let next_index = statement.bind(&self.visible, start_index)?;
statement.bind(&self.active_panel, next_index)
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
@ -266,9 +319,9 @@ impl StaticColumnCount for SerializedItem {
}
impl Bind for &SerializedItem {
fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
let next_index = statement.bind(self.kind.clone(), start_index)?;
let next_index = statement.bind(self.item_id, next_index)?;
statement.bind(self.active, next_index)
let next_index = statement.bind(&self.kind, start_index)?;
let next_index = statement.bind(&self.item_id, next_index)?;
statement.bind(&self.active, next_index)
}
}
@ -287,64 +340,3 @@ impl Column for SerializedItem {
))
}
}
impl StaticColumnCount for DockPosition {
fn column_count() -> usize {
2
}
}
impl Bind for DockPosition {
fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
let next_index = statement.bind(self.is_visible(), start_index)?;
statement.bind(self.anchor(), next_index)
}
}
impl Column for DockPosition {
fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
let (visible, next_index) = bool::column(statement, start_index)?;
let (dock_anchor, next_index) = DockAnchor::column(statement, next_index)?;
let position = if visible {
DockPosition::Shown(dock_anchor)
} else {
DockPosition::Hidden(dock_anchor)
};
Ok((position, next_index))
}
}
#[cfg(test)]
mod tests {
use super::WorkspaceLocation;
use crate::DockAnchor;
use db::sqlez::connection::Connection;
#[test]
fn test_workspace_round_trips() {
let db = Connection::open_memory(Some("workspace_id_round_trips"));
db.exec(indoc::indoc! {"
CREATE TABLE workspace_id_test(
workspace_id INTEGER,
dock_anchor TEXT
);"})
.unwrap()()
.unwrap();
let workspace_id: WorkspaceLocation = WorkspaceLocation::from(&["\test2", "\test1"]);
db.exec_bound("INSERT INTO workspace_id_test(workspace_id, dock_anchor) VALUES (?,?)")
.unwrap()((&workspace_id, DockAnchor::Bottom))
.unwrap();
assert_eq!(
db.select_row("SELECT workspace_id, dock_anchor FROM workspace_id_test LIMIT 1")
.unwrap()()
.unwrap(),
Some((
WorkspaceLocation::from(&["\test1", "\test2"]),
DockAnchor::Bottom
))
);
}
}

View file

@ -1,321 +0,0 @@
use crate::{StatusItemView, Workspace};
use gpui::{
elements::*, impl_actions, platform::CursorStyle, platform::MouseButton, AnyViewHandle,
AppContext, Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext,
};
use serde::Deserialize;
use std::rc::Rc;
pub trait SidebarItem: View {
fn should_activate_item_on_event(&self, _: &Self::Event, _: &AppContext) -> bool {
false
}
fn should_show_badge(&self, _: &AppContext) -> bool {
false
}
fn contains_focused_view(&self, _: &AppContext) -> bool {
false
}
}
pub trait SidebarItemHandle {
fn id(&self) -> usize;
fn should_show_badge(&self, cx: &WindowContext) -> bool;
fn is_focused(&self, cx: &WindowContext) -> bool;
fn as_any(&self) -> &AnyViewHandle;
}
impl<T> SidebarItemHandle for ViewHandle<T>
where
T: SidebarItem,
{
fn id(&self) -> usize {
self.id()
}
fn should_show_badge(&self, cx: &WindowContext) -> bool {
self.read(cx).should_show_badge(cx)
}
fn is_focused(&self, cx: &WindowContext) -> bool {
ViewHandle::is_focused(self, cx) || self.read(cx).contains_focused_view(cx)
}
fn as_any(&self) -> &AnyViewHandle {
self
}
}
impl From<&dyn SidebarItemHandle> for AnyViewHandle {
fn from(val: &dyn SidebarItemHandle) -> Self {
val.as_any().clone()
}
}
pub struct Sidebar {
sidebar_side: SidebarSide,
items: Vec<Item>,
is_open: bool,
active_item_ix: usize,
}
#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
pub enum SidebarSide {
Left,
Right,
}
impl SidebarSide {
fn to_resizable_side(self) -> Side {
match self {
Self::Left => Side::Right,
Self::Right => Side::Left,
}
}
}
struct Item {
icon_path: &'static str,
tooltip: String,
view: Rc<dyn SidebarItemHandle>,
_subscriptions: [Subscription; 2],
}
pub struct SidebarButtons {
sidebar: ViewHandle<Sidebar>,
workspace: WeakViewHandle<Workspace>,
}
#[derive(Clone, Debug, Deserialize, PartialEq)]
pub struct ToggleSidebarItem {
pub sidebar_side: SidebarSide,
pub item_index: usize,
}
impl_actions!(workspace, [ToggleSidebarItem]);
impl Sidebar {
pub fn new(sidebar_side: SidebarSide) -> Self {
Self {
sidebar_side,
items: Default::default(),
active_item_ix: 0,
is_open: false,
}
}
pub fn is_open(&self) -> bool {
self.is_open
}
pub fn active_item_ix(&self) -> usize {
self.active_item_ix
}
pub fn set_open(&mut self, open: bool, cx: &mut ViewContext<Self>) {
if open != self.is_open {
self.is_open = open;
cx.notify();
}
}
pub fn toggle_open(&mut self, cx: &mut ViewContext<Self>) {
if self.is_open {}
self.is_open = !self.is_open;
cx.notify();
}
pub fn add_item<T: SidebarItem>(
&mut self,
icon_path: &'static str,
tooltip: String,
view: ViewHandle<T>,
cx: &mut ViewContext<Self>,
) {
let subscriptions = [
cx.observe(&view, |_, _, cx| cx.notify()),
cx.subscribe(&view, |this, view, event, cx| {
if view.read(cx).should_activate_item_on_event(event, cx) {
if let Some(ix) = this
.items
.iter()
.position(|item| item.view.id() == view.id())
{
this.activate_item(ix, cx);
}
}
}),
];
self.items.push(Item {
icon_path,
tooltip,
view: Rc::new(view),
_subscriptions: subscriptions,
});
cx.notify()
}
pub fn activate_item(&mut self, item_ix: usize, cx: &mut ViewContext<Self>) {
self.active_item_ix = item_ix;
cx.notify();
}
pub fn toggle_item(&mut self, item_ix: usize, cx: &mut ViewContext<Self>) {
if self.active_item_ix == item_ix {
self.is_open = false;
} else {
self.active_item_ix = item_ix;
}
cx.notify();
}
pub fn active_item(&self) -> Option<&Rc<dyn SidebarItemHandle>> {
if self.is_open {
self.items.get(self.active_item_ix).map(|item| &item.view)
} else {
None
}
}
}
impl Entity for Sidebar {
type Event = ();
}
impl View for Sidebar {
fn ui_name() -> &'static str {
"Sidebar"
}
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
if let Some(active_item) = self.active_item() {
enum ResizeHandleTag {}
let style = &theme::current(cx).workspace.sidebar;
ChildView::new(active_item.as_any(), cx)
.contained()
.with_style(style.container)
.with_resize_handle::<ResizeHandleTag>(
self.sidebar_side as usize,
self.sidebar_side.to_resizable_side(),
4.,
style.initial_size,
cx,
)
.into_any()
} else {
Empty::new().into_any()
}
}
}
impl SidebarButtons {
pub fn new(
sidebar: ViewHandle<Sidebar>,
workspace: WeakViewHandle<Workspace>,
cx: &mut ViewContext<Self>,
) -> Self {
cx.observe(&sidebar, |_, _, cx| cx.notify()).detach();
Self { sidebar, workspace }
}
}
impl Entity for SidebarButtons {
type Event = ();
}
impl View for SidebarButtons {
fn ui_name() -> &'static str {
"SidebarToggleButton"
}
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
let theme = &theme::current(cx);
let tooltip_style = theme.tooltip.clone();
let theme = &theme.workspace.status_bar.sidebar_buttons;
let sidebar = self.sidebar.read(cx);
let item_style = theme.item.clone();
let badge_style = theme.badge;
let active_ix = sidebar.active_item_ix;
let is_open = sidebar.is_open;
let sidebar_side = sidebar.sidebar_side;
let group_style = match sidebar_side {
SidebarSide::Left => theme.group_left,
SidebarSide::Right => theme.group_right,
};
#[allow(clippy::needless_collect)]
let items = sidebar
.items
.iter()
.map(|item| (item.icon_path, item.tooltip.clone(), item.view.clone()))
.collect::<Vec<_>>();
Flex::row()
.with_children(items.into_iter().enumerate().map(
|(ix, (icon_path, tooltip, item_view))| {
let action = ToggleSidebarItem {
sidebar_side,
item_index: ix,
};
MouseEventHandler::<Self, _>::new(ix, cx, |state, cx| {
let is_active = is_open && ix == active_ix;
let style = item_style.style_for(state, is_active);
Stack::new()
.with_child(Svg::new(icon_path).with_color(style.icon_color))
.with_children(if !is_active && item_view.should_show_badge(cx) {
Some(
Empty::new()
.collapsed()
.contained()
.with_style(badge_style)
.aligned()
.bottom()
.right(),
)
} else {
None
})
.constrained()
.with_width(style.icon_size)
.with_height(style.icon_size)
.contained()
.with_style(style.container)
})
.with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, {
let action = action.clone();
move |_, this, cx| {
if let Some(workspace) = this.workspace.upgrade(cx) {
let action = action.clone();
cx.window_context().defer(move |cx| {
workspace.update(cx, |workspace, cx| {
workspace.toggle_sidebar_item(&action, cx)
});
});
}
}
})
.with_tooltip::<Self>(
ix,
tooltip,
Some(Box::new(action)),
tooltip_style.clone(),
cx,
)
},
))
.contained()
.with_style(group_style)
.into_any()
}
}
impl StatusItemView for SidebarButtons {
fn set_active_pane_item(
&mut self,
_: Option<&dyn crate::ItemHandle>,
_: &mut ViewContext<Self>,
) {
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,3 @@
use anyhow::bail;
use db::sqlez::{
bindable::{Bind, Column, StaticColumnCount},
statement::Statement,
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::Setting;
@ -13,17 +8,15 @@ pub struct WorkspaceSettings {
pub confirm_quit: bool,
pub show_call_status_icon: bool,
pub autosave: AutosaveSetting,
pub default_dock_anchor: DockAnchor,
pub git: GitSettings,
}
#[derive(Clone, Serialize, Deserialize, JsonSchema)]
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
pub struct WorkspaceSettingsContent {
pub active_pane_magnification: Option<f32>,
pub confirm_quit: Option<bool>,
pub show_call_status_icon: Option<bool>,
pub autosave: Option<AutosaveSetting>,
pub default_dock_anchor: Option<DockAnchor>,
pub git: Option<GitSettings>,
}
@ -36,15 +29,6 @@ pub enum AutosaveSetting {
OnWindowChange,
}
#[derive(PartialEq, Eq, Debug, Default, Copy, Clone, Hash, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum DockAnchor {
#[default]
Bottom,
Right,
Expanded,
}
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
pub struct GitSettings {
pub git_gutter: Option<GitGutterSetting>,
@ -59,35 +43,6 @@ pub enum GitGutterSetting {
Hide,
}
impl StaticColumnCount for DockAnchor {}
impl Bind for DockAnchor {
fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result<i32> {
match self {
DockAnchor::Bottom => "Bottom",
DockAnchor::Right => "Right",
DockAnchor::Expanded => "Expanded",
}
.bind(statement, start_index)
}
}
impl Column for DockAnchor {
fn column(statement: &mut Statement, start_index: i32) -> anyhow::Result<(Self, i32)> {
String::column(statement, start_index).and_then(|(anchor_text, next_index)| {
Ok((
match anchor_text.as_ref() {
"Bottom" => DockAnchor::Bottom,
"Right" => DockAnchor::Right,
"Expanded" => DockAnchor::Expanded,
_ => bail!("Stored dock anchor is incorrect"),
},
next_index,
))
})
}
}
impl Setting for WorkspaceSettings {
const KEY: Option<&'static str> = None;