@@ -6,63 +6,11 @@ use crate::{
model::{ItemId, PaneId, SerializedItem, SerializedItemKind, WorkspaceId},
Db,
};
-// use collections::HashSet;
-// use rusqlite::{named_params, params, types::FromSql};
-
-// use crate::workspace::WorkspaceId;
-
-// use super::Db;
-
-// /// Current design makes the cut at the item level,
-// /// - Maybe A little more bottom up, serialize 'Terminals' and 'Editors' directly, and then make a seperate
-// /// - items table, with a kind, and an integer that acts as a key to one of these other tables
-// /// This column is a foreign key to ONE OF: editors, terminals, searches
-// /// -
-
-// // (workspace_id, item_id)
-// // kind -> ::Editor::
-
-// // ->
-// // At the workspace level
-// // -> (Workspace_ID, item_id)
-// // -> One shot, big query, load everything up:
-
-// // -> SerializedWorkspace::deserialize(tx, itemKey)
-// // -> SerializedEditor::deserialize(tx, itemKey)
-
-// // ->
-// // -> Workspace::new(SerializedWorkspace)
-// // -> Editor::new(serialized_workspace[???]serializedEditor)
-
-// // //Pros: Keeps sql out of every body elese, makes changing it easier (e.g. for loading from a network or RocksDB)
-// // //Cons: DB has to know the internals of the entire rest of the app
-
-// // Workspace
-// // Worktree roots
-// // Pane groups
-// // Dock
-// // Items
-// // Sidebars
-
-// // Things I'm doing: finding about nullability for foreign keys
-// pub(crate) const ITEMS_M_1: &str = "
-// CREATE TABLE project_searches(
-// workspace_id INTEGER,
-// item_id INTEGER,
-// query TEXT,
-// PRIMARY KEY (workspace_id, item_id)
-// FOREIGN KEY(workspace_id) REFERENCES workspace_ids(workspace_id)
-// ) STRICT;
-
-// CREATE TABLE editors(
-// workspace_id INTEGER,
-// item_id INTEGER,
-// path BLOB NOT NULL,
-// PRIMARY KEY (workspace_id, item_id)
-// FOREIGN KEY(workspace_id) REFERENCES workspace_ids(workspace_id)
-// ) STRICT;
-// ";
+// 1) Move all of this into Workspace crate
+// 2) Deserialize items fully
+// 3) Typed prepares (including how you expect to pull data out)
+// 4) Investigate Tree column impls
pub(crate) const ITEM_MIGRATIONS: Migration = Migration::new(
"item",
&[indoc! {"
@@ -5,7 +5,6 @@ use std::{
use anyhow::{bail, Result};
-use gpui::Axis;
use sqlez::{
bindable::{Bind, Column},
statement::Statement,
@@ -91,22 +90,61 @@ pub struct SerializedWorkspace {
pub dock_pane: SerializedPane,
}
-#[derive(Debug, PartialEq, Eq, Default)]
-pub struct SerializedPaneGroup {
- axis: Axis,
- children: Vec<SerializedPaneGroup>,
+#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
+pub enum Axis {
+ #[default]
+ Horizontal,
+ Vertical,
+}
+
+impl Bind for Axis {
+ fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result<i32> {
+ match self {
+ Axis::Horizontal => "Horizontal",
+ Axis::Vertical => "Vertical",
+ }
+ .bind(statement, start_index)
+ }
+}
+
+impl Column for Axis {
+ fn column(statement: &mut Statement, start_index: i32) -> anyhow::Result<(Self, i32)> {
+ String::column(statement, start_index).and_then(|(axis_text, next_index)| {
+ Ok((
+ match axis_text.as_str() {
+ "Horizontal" => Axis::Horizontal,
+ "Vertical" => Axis::Vertical,
+ _ => bail!("Stored serialized item kind is incorrect"),
+ },
+ next_index,
+ ))
+ })
+ }
}
-impl SerializedPaneGroup {
- pub fn new() -> Self {
- SerializedPaneGroup {
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub enum SerializedPaneGroup {
+ Group {
+ axis: Axis,
+ children: Vec<SerializedPaneGroup>,
+ },
+ Pane(SerializedPane),
+}
+
+// Dock panes, and grouped panes combined?
+// AND we're collapsing PaneGroup::Pane
+// In the case where
+
+impl Default for SerializedPaneGroup {
+ fn default() -> Self {
+ Self::Group {
axis: Axis::Horizontal,
- children: Vec::new(),
+ children: vec![Self::Pane(Default::default())],
}
}
}
-#[derive(Debug, PartialEq, Eq, Default)]
+#[derive(Debug, PartialEq, Eq, Default, Clone)]
pub struct SerializedPane {
pub(crate) children: Vec<SerializedItem>,
}
@@ -142,9 +180,9 @@ impl Bind for SerializedItemKind {
impl Column for SerializedItemKind {
fn column(statement: &mut Statement, start_index: i32) -> anyhow::Result<(Self, i32)> {
- String::column(statement, start_index).and_then(|(anchor_text, next_index)| {
+ String::column(statement, start_index).and_then(|(kind_text, next_index)| {
Ok((
- match anchor_text.as_ref() {
+ match kind_text.as_ref() {
"Editor" => SerializedItemKind::Editor,
"Diagnostics" => SerializedItemKind::Diagnostics,
"ProjectSearch" => SerializedItemKind::ProjectSearch,
@@ -157,7 +195,7 @@ impl Column for SerializedItemKind {
}
}
-#[derive(Debug, PartialEq, Eq)]
+#[derive(Debug, PartialEq, Eq, Clone)]
pub enum SerializedItem {
Editor { item_id: usize, path: Arc<Path> },
Diagnostics { item_id: usize },
@@ -1,9 +1,9 @@
-use anyhow::{Context, Result};
+use anyhow::{bail, Context, Result};
use indoc::indoc;
use sqlez::migrations::Migration;
use util::unzip_option;
-use crate::model::{GroupId, PaneId, SerializedPane};
+use crate::model::{Axis, GroupId, PaneId, SerializedPane};
use super::{
model::{SerializedPaneGroup, WorkspaceId},
@@ -16,47 +16,107 @@ pub(crate) const PANE_MIGRATIONS: Migration = Migration::new(
CREATE TABLE pane_groups(
group_id INTEGER PRIMARY KEY,
workspace_id BLOB NOT NULL,
- parent_group INTEGER, -- NULL indicates that this is a root node
+ parent_group_id INTEGER, -- NULL indicates that this is a root node
+ position INTEGER, -- NULL indicates that this is a root node
axis TEXT NOT NULL, -- Enum: 'Vertical' / 'Horizontal'
FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) ON DELETE CASCADE,
- FOREIGN KEY(parent_group) REFERENCES pane_groups(group_id) ON DELETE CASCADE
+ FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE
) STRICT;
CREATE TABLE panes(
pane_id INTEGER PRIMARY KEY,
workspace_id BLOB NOT NULL,
- group_id INTEGER, -- If null, this is a dock pane
- position INTEGER, -- If null, this is a dock pane
+ parent_group_id INTEGER, -- NULL, this is a dock pane
+ position INTEGER, -- NULL, this is a dock pane
FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) ON DELETE CASCADE,
- FOREIGN KEY(group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE
+ FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE
) STRICT;
"}],
);
impl Db {
- pub(crate) fn get_center_group(
+ pub(crate) fn get_center_pane_group(
&self,
- _workspace_id: &WorkspaceId,
+ workspace_id: &WorkspaceId,
) -> Result<SerializedPaneGroup> {
- Ok(SerializedPaneGroup::new())
+ self.get_pane_group_children(workspace_id, None)?
+ .into_iter()
+ .next()
+ .context("No center pane group")
}
- pub(crate) fn save_center_group(
+ fn get_pane_group_children(
&self,
- _workspace_id: &WorkspaceId,
- _center_pane_group: &SerializedPaneGroup,
+ workspace_id: &WorkspaceId,
+ group_id: Option<GroupId>,
+ ) -> Result<Vec<SerializedPaneGroup>> {
+ let children = self
+ .prepare(indoc! {"
+ SELECT group_id, axis, pane_id
+ FROM (SELECT group_id, axis, NULL as pane_id, position, parent_group_id, workspace_id
+ FROM pane_groups
+ UNION
+ SELECT NULL, NULL, pane_id, position, parent_group_id, workspace_id
+ FROM panes
+ -- Remove the dock panes from the union
+ WHERE parent_group_id IS NOT NULL and position IS NOT NULL)
+ WHERE parent_group_id IS ? AND workspace_id = ?
+ ORDER BY position
+ "})?
+ .with_bindings((group_id, workspace_id))?
+ .rows::<(Option<GroupId>, Option<Axis>, Option<PaneId>)>()?;
+
+ children
+ .into_iter()
+ .map(|(group_id, axis, pane_id)| {
+ if let Some((group_id, axis)) = group_id.zip(axis) {
+ Ok(SerializedPaneGroup::Group {
+ axis,
+ children: self.get_pane_group_children(workspace_id, Some(group_id))?,
+ })
+ } else if let Some(pane_id) = pane_id {
+ Ok(SerializedPaneGroup::Pane(SerializedPane {
+ children: self.get_items(pane_id)?,
+ }))
+ } else {
+ bail!("Pane Group Child was neither a pane group or a pane");
+ }
+ })
+ .collect::<Result<_>>()
+ }
+
+ pub(crate) fn save_pane_group(
+ &self,
+ workspace_id: &WorkspaceId,
+ pane_group: &SerializedPaneGroup,
+ parent: Option<(GroupId, usize)>,
) -> Result<()> {
- // Delete the center pane group for this workspace and any of its children
- // Generate new pane group IDs as we go through
- // insert them
- Ok(())
+ if parent.is_none() && !matches!(pane_group, SerializedPaneGroup::Group { .. }) {
+ bail!("Pane groups must have a SerializedPaneGroup::Group at the root")
+ }
+
+ let (parent_id, position) = unzip_option(parent);
+
+ match pane_group {
+ SerializedPaneGroup::Group { axis, children } => {
+ let parent_id = self.prepare("INSERT INTO pane_groups(workspace_id, parent_group_id, position, axis) VALUES (?, ?, ?, ?)")?
+ .with_bindings((workspace_id, parent_id, position, *axis))?
+ .insert()? as GroupId;
+
+ for (position, group) in children.iter().enumerate() {
+ self.save_pane_group(workspace_id, group, Some((parent_id, position)))?
+ }
+ Ok(())
+ }
+ SerializedPaneGroup::Pane(pane) => self.save_pane(workspace_id, pane, parent),
+ }
}
pub(crate) fn get_dock_pane(&self, workspace_id: &WorkspaceId) -> Result<SerializedPane> {
let pane_id = self
.prepare(indoc! {"
SELECT pane_id FROM panes
- WHERE workspace_id = ? AND group_id IS NULL AND position IS NULL"})?
+ WHERE workspace_id = ? AND parent_group_id IS NULL AND position IS NULL"})?
.with_bindings(workspace_id)?
.row::<PaneId>()?;
@@ -65,14 +125,6 @@ impl Db {
))
}
- pub(crate) fn save_dock_pane(
- &self,
- workspace: &WorkspaceId,
- dock_pane: &SerializedPane,
- ) -> Result<()> {
- self.save_pane(workspace, &dock_pane, None)
- }
-
pub(crate) fn save_pane(
&self,
workspace_id: &WorkspaceId,
@@ -82,7 +134,7 @@ impl Db {
let (parent_id, order) = unzip_option(parent);
let pane_id = self
- .prepare("INSERT INTO panes(workspace_id, group_id, position) VALUES (?, ?, ?)")?
+ .prepare("INSERT INTO panes(workspace_id, parent_group_id, position) VALUES (?, ?, ?)")?
.with_bindings((workspace_id, parent_id, order))?
.insert()? as PaneId;
@@ -101,18 +153,20 @@ mod tests {
fn default_workspace(
dock_pane: SerializedPane,
- center_group: SerializedPaneGroup,
+ center_group: &SerializedPaneGroup,
) -> SerializedWorkspace {
SerializedWorkspace {
dock_anchor: crate::model::DockAnchor::Right,
dock_visible: false,
- center_group,
+ center_group: center_group.clone(),
dock_pane,
}
}
#[test]
fn test_basic_dock_pane() {
+ env_logger::try_init().ok();
+
let db = Db::open_in_memory("basic_dock_pane");
let dock_pane = crate::model::SerializedPane {
@@ -124,7 +178,7 @@ mod tests {
],
};
- let workspace = default_workspace(dock_pane, SerializedPaneGroup::new());
+ let workspace = default_workspace(dock_pane, &Default::default());
db.save_workspace(&["/tmp"], None, &workspace);
@@ -133,24 +187,50 @@ mod tests {
assert_eq!(workspace.dock_pane, new_workspace.dock_pane);
}
- // #[test]
- // fn test_dock_simple_split() {
- // let db = Db::open_in_memory("simple_split");
-
- // let workspace = db.workspace_for_roots(&["/tmp"]);
-
- // // Pane group -> Pane -> 10 , 20
- // let center_pane = SerializedPaneGroup {
- // axis: gpui::Axis::Horizontal,
- // children: vec![PaneGroupChild::Pane(SerializedPane {
- // items: vec![ItemId { item_id: 10 }, ItemId { item_id: 20 }],
- // })],
- // };
+ #[test]
+ fn test_simple_split() {
+ env_logger::try_init().ok();
+
+ let db = Db::open_in_memory("simple_split");
+
+ // -----------------
+ // | 1,2 | 5,6 |
+ // | - - - | |
+ // | 3,4 | |
+ // -----------------
+ let center_pane = SerializedPaneGroup::Group {
+ axis: crate::model::Axis::Horizontal,
+ children: vec![
+ SerializedPaneGroup::Group {
+ axis: crate::model::Axis::Vertical,
+ children: vec![
+ SerializedPaneGroup::Pane(SerializedPane {
+ children: vec![
+ SerializedItem::Terminal { item_id: 1 },
+ SerializedItem::Terminal { item_id: 2 },
+ ],
+ }),
+ SerializedPaneGroup::Pane(SerializedPane {
+ children: vec![
+ SerializedItem::Terminal { item_id: 4 },
+ SerializedItem::Terminal { item_id: 3 },
+ ],
+ }),
+ ],
+ },
+ SerializedPaneGroup::Pane(SerializedPane {
+ children: vec![
+ SerializedItem::Terminal { item_id: 5 },
+ SerializedItem::Terminal { item_id: 6 },
+ ],
+ }),
+ ],
+ };
- // db.save_pane_splits(&workspace.workspace_id, ¢er_pane);
+ let workspace = default_workspace(Default::default(), ¢er_pane);
- // // let new_workspace = db.workspace_for_roots(&["/tmp"]);
+ db.save_workspace(&["/tmp"], None, &workspace);
- // // assert_eq!(new_workspace.center_group, center_pane);
- // }
+ assert_eq!(workspace.center_group, center_pane);
+ }
}