WIP

K Simmons created

Change summary

Cargo.lock                        |   1 
crates/db/Cargo.toml              |   1 
crates/db/src/db.rs               |   1 
crates/db/src/items.rs            |  62 ++++++++++++++
crates/db/src/pane.rs             | 134 ++++++++++++++++++++++++++++++++
crates/db/src/workspace.rs        | 137 +++++++++++++++++++++++---------
crates/gpui/src/presenter.rs      |   3 
crates/workspace/Cargo.toml       |   1 
crates/workspace/src/dock.rs      |   6 +
crates/workspace/src/workspace.rs |   1 
10 files changed, 302 insertions(+), 45 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -7617,6 +7617,7 @@ dependencies = [
  "client",
  "collections",
  "context_menu",
+ "db",
  "drag_and_drop",
  "fs",
  "futures 0.3.24",

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"

crates/db/src/db.rs 🔗

@@ -1,6 +1,7 @@
 mod items;
 mod kvp;
 mod migrations;
+mod pane;
 mod workspace;
 
 use std::fs;

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<Arc<Path>>,
+    query: Option<String>,
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub enum SerializedItem {
+    Editor { item_id: usize, path: Arc<Path> },
+    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) {}
+}

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<PaneGroupChild>,
+}
+
+struct PaneGroupChildRow {
+    child_pane_id: Option<usize>,
+    child_group_id: Option<usize>,
+    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<ItemId>,
+}
+
+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<Item = PaneGroupChildRow> {
+        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) {}
+}

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<SerializedPane>,
+}
+
 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<Path>]) -> WorkspaceId {
+    pub fn workspace_for_worktree_roots(
+        &self,
+        worktree_roots: &[Arc<Path>],
+    ) -> 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<Path>]) -> Option<WorkspaceId> {
+        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<Arc<Path>>)> {
         // 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<Path> {
+        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<Path> {
-            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));

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,
 }

crates/workspace/Cargo.toml 🔗

@@ -18,6 +18,7 @@ test-support = [
 ]
 
 [dependencies]
+db = { path = "../db" }
 call = { path = "../call" }
 client = { path = "../client" }
 collections = { path = "../collections" }

crates/workspace/src/dock.rs 🔗

@@ -137,7 +137,11 @@ pub struct Dock {
 }
 
 impl Dock {
-    pub fn new(cx: &mut ViewContext<Workspace>, default_item_factory: DefaultItemFactory) -> Self {
+    pub fn new(
+        serialized_pane: SerializedPane,
+        default_item_factory: DefaultItemFactory,
+        cx: &mut ViewContext<Workspace>,
+    ) -> Self {
         let anchor = cx.global::<Settings>().default_dock_anchor;
         let pane = cx.add_view(|cx| Pane::new(Some(anchor), cx));
         pane.update(cx, |pane, cx| {

crates/workspace/src/workspace.rs 🔗

@@ -1110,6 +1110,7 @@ enum FollowerItem {
 
 impl Workspace {
     pub fn new(
+        serialized_workspace: SerializedWorkspace,
         project: ModelHandle<Project>,
         dock_default_factory: DefaultItemFactory,
         cx: &mut ViewContext<Self>,