diff --git a/Cargo.lock b/Cargo.lock index e04624d686..b381331ef1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7617,6 +7617,7 @@ dependencies = [ "client", "collections", "context_menu", + "db", "drag_and_drop", "fs", "futures 0.3.24", diff --git a/crates/db/Cargo.toml b/crates/db/Cargo.toml index e0b932003e..10f0858a52 100644 --- a/crates/db/Cargo.toml +++ b/crates/db/Cargo.toml @@ -12,6 +12,7 @@ test-support = [] [dependencies] collections = { path = "../collections" } +gpui = { path = "../gpui" } anyhow = "1.0.57" async-trait = "0.1" lazy_static = "1.4.0" diff --git a/crates/db/src/db.rs b/crates/db/src/db.rs index 2b4b7cf9c3..bf3cd64508 100644 --- a/crates/db/src/db.rs +++ b/crates/db/src/db.rs @@ -1,6 +1,7 @@ mod items; mod kvp; mod migrations; +mod pane; mod workspace; use std::fs; diff --git a/crates/db/src/items.rs b/crates/db/src/items.rs index 7454f24331..1b633fdc47 100644 --- a/crates/db/src/items.rs +++ b/crates/db/src/items.rs @@ -1,8 +1,17 @@ -use std::{ffi::OsStr, fmt::Display, hash::Hash, os::unix::prelude::OsStrExt, path::PathBuf}; +use std::{ + ffi::OsStr, + fmt::Display, + hash::Hash, + os::unix::prelude::OsStrExt, + path::{Path, PathBuf}, + sync::Arc, +}; use anyhow::Result; use collections::HashSet; -use rusqlite::{named_params, params}; +use rusqlite::{named_params, params, types::FromSql}; + +use crate::workspace::WorkspaceId; use super::Db; @@ -62,3 +71,52 @@ CREATE TABLE editors( FOREIGN KEY(workspace_id) REFERENCES workspace_ids(workspace_id) ) STRICT; "; + +#[derive(Debug, PartialEq, Eq)] +pub struct ItemId { + workspace_id: usize, + item_id: usize, +} + +enum SerializedItemKind { + Editor, + Diagnostics, + ProjectSearch, + Terminal, +} + +struct SerializedItemRow { + kind: SerializedItemKind, + item_id: usize, + path: Option>, + query: Option, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum SerializedItem { + Editor { item_id: usize, path: Arc }, + Diagnostics { item_id: usize }, + ProjectSearch { item_id: usize, query: String }, + Terminal { item_id: usize }, +} + +impl SerializedItem { + pub fn item_id(&self) -> usize { + match self { + SerializedItem::Editor { item_id, .. } => *item_id, + SerializedItem::Diagnostics { item_id } => *item_id, + SerializedItem::ProjectSearch { item_id, .. } => *item_id, + SerializedItem::Terminal { item_id } => *item_id, + } + } +} + +impl Db { + pub fn get_item(&self, item_id: ItemId) -> SerializedItem { + unimplemented!() + } + + pub fn save_item(&self, workspace_id: WorkspaceId, item: &SerializedItem) {} + + pub fn close_item(&self, item_id: ItemId) {} +} diff --git a/crates/db/src/pane.rs b/crates/db/src/pane.rs new file mode 100644 index 0000000000..98feb36abf --- /dev/null +++ b/crates/db/src/pane.rs @@ -0,0 +1,134 @@ +use gpui::Axis; + +use crate::{items::ItemId, workspace::WorkspaceId}; + +use super::Db; + +pub(crate) const PANE_M_1: &str = " +CREATE TABLE pane_groups( + workspace_id INTEGER, + group_id INTEGER, + axis STRING NOT NULL, -- 'Vertical' / 'Horizontal' + PRIMARY KEY (workspace_id, group_id) +) STRICT; + +CREATE TABLE pane_group_children( + workspace_id INTEGER, + group_id INTEGER, + child_pane_id INTEGER, -- Nullable + child_group_id INTEGER, -- Nullable + index INTEGER, + PRIMARY KEY (workspace_id, group_id) +) STRICT; + +CREATE TABLE pane_items( + workspace_id INTEGER, + pane_id INTEGER, + item_id INTEGER, -- Array + index INTEGER, + KEY (workspace_id, pane_id) +) STRICT; +"; + +#[derive(Debug, PartialEq, Eq)] +pub struct PaneId { + workspace_id: WorkspaceId, + pane_id: usize, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct PaneGroupId { + workspace_id: WorkspaceId, + group_id: usize, +} + +impl PaneGroupId { + pub(crate) fn root(workspace_id: WorkspaceId) -> Self { + Self { + workspace_id, + group_id: 0, + } + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct SerializedPaneGroup { + group_id: PaneGroupId, + axis: Axis, + children: Vec, +} + +struct PaneGroupChildRow { + child_pane_id: Option, + child_group_id: Option, + index: usize, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum PaneGroupChild { + Pane(SerializedPane), + Group(SerializedPaneGroup), +} + +#[derive(Debug, PartialEq, Eq)] +pub struct SerializedPane { + pane_id: PaneId, + children: Vec, +} + +impl Db { + pub(crate) fn get_pane_group(&self, pane_group_id: PaneGroupId) -> SerializedPaneGroup { + let axis = self.get_pane_group_axis(pane_group_id); + let mut children: Vec<(usize, PaneGroupChild)> = Vec::new(); + for child_row in self.get_pane_group_children(pane_group_id) { + if let Some(child_pane_id) = child_row.child_pane_id { + children.push(( + child_row.index, + PaneGroupChild::Pane(self.get_pane(PaneId { + workspace_id: pane_group_id.workspace_id, + pane_id: child_pane_id, + })), + )); + } else if let Some(child_group_id) = child_row.child_group_id { + children.push(( + child_row.index, + PaneGroupChild::Group(self.get_pane_group(PaneGroupId { + workspace_id: pane_group_id.workspace_id, + group_id: child_group_id, + })), + )); + } + } + children.sort_by_key(|(index, _)| index); + + SerializedPaneGroup { + group_id: pane_group_id, + axis, + children: children.into_iter().map(|(_, child)| child).collect(), + } + } + + pub fn get_pane_group_children( + &self, + pane_group_id: PaneGroupId, + ) -> impl Iterator { + unimplemented!() + } + + pub fn get_pane_group_axis(&self, pane_group_id: PaneGroupId) -> Axis { + unimplemented!(); + } + + pub fn save_center_pane_group(&self, center_pane_group: SerializedPaneGroup) { + // Delete the center pane group for this workspace and any of its children + // Generate new pane group IDs as we go through + // insert them + // Items garbage collect themselves when dropped + } + + pub(crate) fn get_pane(&self, pane_id: PaneId) -> SerializedPane { + unimplemented!(); + } + + pub fn save_pane(&self, pane: SerializedPane) {} +} diff --git a/crates/db/src/workspace.rs b/crates/db/src/workspace.rs index 8ece0d5b78..e342391b71 100644 --- a/crates/db/src/workspace.rs +++ b/crates/db/src/workspace.rs @@ -1,5 +1,7 @@ use std::{path::Path, sync::Arc}; +use crate::pane::{PaneGroupId, PaneId, SerializedPane, SerializedPaneGroup}; + use super::Db; pub(crate) const WORKSPACE_M_1: &str = " @@ -17,28 +19,6 @@ CREATE TABLE worktree_roots( workspace_id INTEGER NOT NULL, FOREIGN KEY(workspace_id) REFERENCES workspace_ids(workspace_id) ) STRICT; - -CREATE TABLE pane_groups( - workspace_id INTEGER, - group_id INTEGER, - split_direction STRING, -- 'Vertical' / 'Horizontal' / - PRIMARY KEY (workspace_id, group_id) -) STRICT; - -CREATE TABLE pane_group_children( - workspace_id INTEGER, - group_id INTEGER, - child_pane_id INTEGER, -- Nullable - child_group_id INTEGER, -- Nullable - PRIMARY KEY (workspace_id, group_id) -) STRICT; - -CREATE TABLE pane_items( - workspace_id INTEGER, - pane_id INTEGER, - item_id INTEGER, -- Array - PRIMARY KEY (workspace_id, pane_id) -) STRICT; "; // Zed stores items with ids which are a combination of a view id during a given run and a workspace id. This @@ -52,18 +32,65 @@ CREATE TABLE pane_items( // Case 4: Starting Zed with multiple project folders // > Zed ~/projects/Zed ~/projects/Zed.dev -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Copy, Clone)] pub struct WorkspaceId(usize); +struct WorkspaceRow { + pub workspace_id: WorkspaceId, + pub center_group_id: PaneGroupId, + pub dock_pane_id: PaneId, +} + +pub struct SerializedWorkspace { + pub workspace_id: WorkspaceId, + pub center_group: SerializedPaneGroup, + pub dock_pane: Option, +} + impl Db { /// Finds or creates a workspace id for the given set of worktree roots. If the passed worktree roots is empty, return the /// the last workspace id - pub fn workspace_id(&self, worktree_roots: &[Arc]) -> WorkspaceId { + pub fn workspace_for_worktree_roots( + &self, + worktree_roots: &[Arc], + ) -> SerializedWorkspace { // Find the workspace id which is uniquely identified by this set of paths return it if found - // Otherwise: - // Find the max workspace_id and increment it as our new workspace id - // Store in the worktrees table the mapping from this new id to the set of worktree roots - unimplemented!(); + if let Some(workspace_id) = self.workspace_id(worktree_roots) { + let workspace_row = self.get_workspace_row(workspace_id); + let center_group = self.get_pane_group(workspace_row.center_group_id); + let dock_pane = self.get_pane(workspace_row.dock_pane_id); + + SerializedWorkspace { + workspace_id, + center_group, + dock_pane: Some(dock_pane), + } + } else { + let workspace_id = self.get_next_workspace_id(); + let center_group = SerializedPaneGroup { + group_id: PaneGroupId::root(workspace_id), + axis: Default::default(), + children: Default::default(), + }; + + SerializedWorkspace { + workspace_id, + center_group, + dock_pane: None, + } + } + } + + fn get_next_workspace_id(&self) -> WorkspaceId { + unimplemented!() + } + + fn workspace_id(&self, worktree_roots: &[Arc]) -> Option { + unimplemented!() + } + + fn get_workspace_row(&self, workspace_id: WorkspaceId) -> WorkspaceRow { + unimplemented!() } /// Updates the open paths for the given workspace id. Will garbage collect items from @@ -80,16 +107,12 @@ impl Db { unimplemented!(); } - /// Returns the previous workspace ids sorted by last modified + /// Returns the previous workspace ids sorted by last modified along with their opened worktree roots pub fn recent_workspaces(&self) -> Vec<(WorkspaceId, Vec>)> { // Return all the workspace ids and their associated paths ordered by the access timestamp //ORDER BY timestamps unimplemented!(); } - - pub fn center_pane(&self, workspace: WorkspaceId) -> SerializedPaneGroup {} - - pub fn dock_pane(&self, workspace: WorkspaceId) -> SerializedPane {} } #[cfg(test)] @@ -104,6 +127,42 @@ mod tests { use super::WorkspaceId; + fn arc_path(path: &'static str) -> Arc { + PathBuf::from(path).into() + } + + fn test_detect_workspace_id() { + let data = &[ + (WorkspaceId(1), vec![arc_path("/tmp")]), + (WorkspaceId(2), vec![arc_path("/tmp"), arc_path("/tmp2")]), + ( + WorkspaceId(3), + vec![arc_path("/tmp"), arc_path("/tmp2"), arc_path("/tmp3")], + ), + ]; + + let db = Db::open_in_memory(); + + for (workspace_id, entries) in data { + db.update_worktree_roots(workspace_id, entries); //?? + } + + assert_eq!(None, db.workspace_id(&[arc_path("/tmp2")])); + assert_eq!( + None, + db.workspace_id(&[arc_path("/tmp2"), arc_path("/tmp3")]) + ); + assert_eq!(Some(WorkspaceId(1)), db.workspace_id(&[arc_path("/tmp")])); + assert_eq!( + Some(WorkspaceId(2)), + db.workspace_id(&[arc_path("/tmp"), arc_path("/tmp2")]) + ); + assert_eq!( + Some(WorkspaceId(3)), + db.workspace_id(&[arc_path("/tmp"), arc_path("/tmp2"), arc_path("/tmp3")]) + ); + } + fn test_tricky_overlapping_updates() { // DB state: // (/tree) -> ID: 1 @@ -117,10 +176,6 @@ mod tests { // (/tree2, /tree3) -> ID: 2 // Get rid of 3 for garbage collection - fn arc_path(path: &'static str) -> Arc { - PathBuf::from(path).into() - } - let data = &[ (WorkspaceId(1), vec![arc_path("/tmp")]), (WorkspaceId(2), vec![arc_path("/tmp"), arc_path("/tmp2")]), @@ -131,18 +186,18 @@ mod tests { for (workspace_id, entries) in data { db.update_worktree_roots(workspace_id, entries); //?? - assert_eq!(&db.workspace_id(&[]), workspace_id) + assert_eq!(&db.workspace_id(&[]), &Some(*workspace_id)) } for (workspace_id, entries) in data { - assert_eq!(&db.workspace_id(entries.as_slice()), workspace_id); + assert_eq!(&db.workspace_id(entries.as_slice()), &Some(*workspace_id)); } db.update_worktree_roots(&WorkspaceId(2), &[arc_path("/tmp2")]); // todo!(); // make sure that 3 got garbage collected - assert_eq!(db.workspace_id(&[arc_path("/tmp2")]), WorkspaceId(2)); - assert_eq!(db.workspace_id(&[arc_path("/tmp")]), WorkspaceId(1)); + assert_eq!(db.workspace_id(&[arc_path("/tmp2")]), Some(WorkspaceId(2))); + assert_eq!(db.workspace_id(&[arc_path("/tmp")]), Some(WorkspaceId(1))); let recent_workspaces = db.recent_workspaces(); assert_eq!(recent_workspaces.get(0).unwrap().0, WorkspaceId(2)); diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index d15051ef12..27cd2a1347 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -863,8 +863,9 @@ pub struct DebugContext<'a> { pub app: &'a AppContext, } -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub enum Axis { + #[default] Horizontal, Vertical, } diff --git a/crates/workspace/Cargo.toml b/crates/workspace/Cargo.toml index 2db4ef2d3d..c481792f7c 100644 --- a/crates/workspace/Cargo.toml +++ b/crates/workspace/Cargo.toml @@ -18,6 +18,7 @@ test-support = [ ] [dependencies] +db = { path = "../db" } call = { path = "../call" } client = { path = "../client" } collections = { path = "../collections" } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index b17a7ea22e..fa8f182a31 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -137,7 +137,11 @@ pub struct Dock { } impl Dock { - pub fn new(cx: &mut ViewContext, default_item_factory: DefaultItemFactory) -> Self { + pub fn new( + serialized_pane: SerializedPane, + default_item_factory: DefaultItemFactory, + cx: &mut ViewContext, + ) -> Self { let anchor = cx.global::().default_dock_anchor; let pane = cx.add_view(|cx| Pane::new(Some(anchor), cx)); pane.update(cx, |pane, cx| { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 7082b61949..86eff8fb79 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1110,6 +1110,7 @@ enum FollowerItem { impl Workspace { pub fn new( + serialized_workspace: SerializedWorkspace, project: ModelHandle, dock_default_factory: DefaultItemFactory, cx: &mut ViewContext,