Finished the bulk of workspace serialization. Just items and wiring it all through.

Co-Authored-By: kay@zed.dev
This commit is contained in:
Mikayla Maki 2022-11-04 17:48:29 -07:00
parent 0186289420
commit f27a9d77d1
5 changed files with 185 additions and 119 deletions

View file

@ -63,7 +63,7 @@ impl Db {
.context("Getting dock pane") .context("Getting dock pane")
.log_err()?, .log_err()?,
center_group: self center_group: self
.get_center_group(&workspace_id) .get_center_pane_group(&workspace_id)
.context("Getting center group") .context("Getting center group")
.log_err()?, .log_err()?,
dock_anchor, dock_anchor,
@ -104,8 +104,8 @@ impl Db {
.exec()?; .exec()?;
// Save center pane group and dock pane // Save center pane group and dock pane
self.save_center_group(&workspace_id, &workspace.center_group)?; self.save_pane_group(&workspace_id, &workspace.center_group, None)?;
self.save_dock_pane(&workspace_id, &workspace.dock_pane)?; self.save_pane(&workspace_id, &workspace.dock_pane, None)?;
Ok(()) Ok(())
}) })
@ -152,8 +152,8 @@ mod tests {
}; };
#[test] #[test]
fn test_basic_functionality() { fn test_workspace_assignment() {
env_logger::init(); env_logger::try_init().ok();
let db = Db::open_in_memory("test_basic_functionality"); let db = Db::open_in_memory("test_basic_functionality");

View file

@ -6,63 +6,11 @@ use crate::{
model::{ItemId, PaneId, SerializedItem, SerializedItemKind, WorkspaceId}, model::{ItemId, PaneId, SerializedItem, SerializedItemKind, WorkspaceId},
Db, 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( pub(crate) const ITEM_MIGRATIONS: Migration = Migration::new(
"item", "item",
&[indoc! {" &[indoc! {"

View file

@ -5,7 +5,6 @@ use std::{
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use gpui::Axis;
use sqlez::{ use sqlez::{
bindable::{Bind, Column}, bindable::{Bind, Column},
statement::Statement, statement::Statement,
@ -91,22 +90,61 @@ pub struct SerializedWorkspace {
pub dock_pane: SerializedPane, pub dock_pane: SerializedPane,
} }
#[derive(Debug, PartialEq, Eq, Default)] #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct SerializedPaneGroup { pub enum Axis {
axis: Axis, #[default]
children: Vec<SerializedPaneGroup>, Horizontal,
Vertical,
} }
impl SerializedPaneGroup { impl Bind for Axis {
pub fn new() -> Self { fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result<i32> {
SerializedPaneGroup { 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,
))
})
}
}
#[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, 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 struct SerializedPane {
pub(crate) children: Vec<SerializedItem>, pub(crate) children: Vec<SerializedItem>,
} }
@ -142,9 +180,9 @@ impl Bind for SerializedItemKind {
impl Column for SerializedItemKind { impl Column for SerializedItemKind {
fn column(statement: &mut Statement, start_index: i32) -> anyhow::Result<(Self, i32)> { 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(( Ok((
match anchor_text.as_ref() { match kind_text.as_ref() {
"Editor" => SerializedItemKind::Editor, "Editor" => SerializedItemKind::Editor,
"Diagnostics" => SerializedItemKind::Diagnostics, "Diagnostics" => SerializedItemKind::Diagnostics,
"ProjectSearch" => SerializedItemKind::ProjectSearch, "ProjectSearch" => SerializedItemKind::ProjectSearch,
@ -157,7 +195,7 @@ impl Column for SerializedItemKind {
} }
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq, Clone)]
pub enum SerializedItem { pub enum SerializedItem {
Editor { item_id: usize, path: Arc<Path> }, Editor { item_id: usize, path: Arc<Path> },
Diagnostics { item_id: usize }, Diagnostics { item_id: usize },

View file

@ -1,9 +1,9 @@
use anyhow::{Context, Result}; use anyhow::{bail, Context, Result};
use indoc::indoc; use indoc::indoc;
use sqlez::migrations::Migration; use sqlez::migrations::Migration;
use util::unzip_option; use util::unzip_option;
use crate::model::{GroupId, PaneId, SerializedPane}; use crate::model::{Axis, GroupId, PaneId, SerializedPane};
use super::{ use super::{
model::{SerializedPaneGroup, WorkspaceId}, model::{SerializedPaneGroup, WorkspaceId},
@ -16,47 +16,107 @@ pub(crate) const PANE_MIGRATIONS: Migration = Migration::new(
CREATE TABLE pane_groups( CREATE TABLE pane_groups(
group_id INTEGER PRIMARY KEY, group_id INTEGER PRIMARY KEY,
workspace_id BLOB NOT NULL, 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' axis TEXT NOT NULL, -- Enum: 'Vertical' / 'Horizontal'
FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) ON DELETE CASCADE, 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; ) STRICT;
CREATE TABLE panes( CREATE TABLE panes(
pane_id INTEGER PRIMARY KEY, pane_id INTEGER PRIMARY KEY,
workspace_id BLOB NOT NULL, workspace_id BLOB NOT NULL,
group_id INTEGER, -- If null, this is a dock pane parent_group_id INTEGER, -- NULL, this is a dock pane
position INTEGER, -- If 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(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; ) STRICT;
"}], "}],
); );
impl Db { impl Db {
pub(crate) fn get_center_group( pub(crate) fn get_center_pane_group(
&self, &self,
_workspace_id: &WorkspaceId, workspace_id: &WorkspaceId,
) -> Result<SerializedPaneGroup> { ) -> 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, &self,
_workspace_id: &WorkspaceId, workspace_id: &WorkspaceId,
_center_pane_group: &SerializedPaneGroup, 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<()> { ) -> Result<()> {
// Delete the center pane group for this workspace and any of its children if parent.is_none() && !matches!(pane_group, SerializedPaneGroup::Group { .. }) {
// Generate new pane group IDs as we go through bail!("Pane groups must have a SerializedPaneGroup::Group at the root")
// insert them }
Ok(())
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> { pub(crate) fn get_dock_pane(&self, workspace_id: &WorkspaceId) -> Result<SerializedPane> {
let pane_id = self let pane_id = self
.prepare(indoc! {" .prepare(indoc! {"
SELECT pane_id FROM panes 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)? .with_bindings(workspace_id)?
.row::<PaneId>()?; .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( pub(crate) fn save_pane(
&self, &self,
workspace_id: &WorkspaceId, workspace_id: &WorkspaceId,
@ -82,7 +134,7 @@ impl Db {
let (parent_id, order) = unzip_option(parent); let (parent_id, order) = unzip_option(parent);
let pane_id = self 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))? .with_bindings((workspace_id, parent_id, order))?
.insert()? as PaneId; .insert()? as PaneId;
@ -101,18 +153,20 @@ mod tests {
fn default_workspace( fn default_workspace(
dock_pane: SerializedPane, dock_pane: SerializedPane,
center_group: SerializedPaneGroup, center_group: &SerializedPaneGroup,
) -> SerializedWorkspace { ) -> SerializedWorkspace {
SerializedWorkspace { SerializedWorkspace {
dock_anchor: crate::model::DockAnchor::Right, dock_anchor: crate::model::DockAnchor::Right,
dock_visible: false, dock_visible: false,
center_group, center_group: center_group.clone(),
dock_pane, dock_pane,
} }
} }
#[test] #[test]
fn test_basic_dock_pane() { fn test_basic_dock_pane() {
env_logger::try_init().ok();
let db = Db::open_in_memory("basic_dock_pane"); let db = Db::open_in_memory("basic_dock_pane");
let dock_pane = crate::model::SerializedPane { 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); db.save_workspace(&["/tmp"], None, &workspace);
@ -133,24 +187,50 @@ mod tests {
assert_eq!(workspace.dock_pane, new_workspace.dock_pane); assert_eq!(workspace.dock_pane, new_workspace.dock_pane);
} }
// #[test] #[test]
// fn test_dock_simple_split() { fn test_simple_split() {
// let db = Db::open_in_memory("simple_split"); env_logger::try_init().ok();
// let workspace = db.workspace_for_roots(&["/tmp"]); let db = Db::open_in_memory("simple_split");
// // Pane group -> Pane -> 10 , 20 // -----------------
// let center_pane = SerializedPaneGroup { // | 1,2 | 5,6 |
// axis: gpui::Axis::Horizontal, // | - - - | |
// children: vec![PaneGroupChild::Pane(SerializedPane { // | 3,4 | |
// items: vec![ItemId { item_id: 10 }, ItemId { item_id: 20 }], // -----------------
// })], 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, &center_pane); let workspace = default_workspace(Default::default(), &center_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);
// } }
} }

BIN
crates/db/test.db Normal file

Binary file not shown.