Finished the bulk of workspace serialization. Just items and wiring it all through.
Co-Authored-By: kay@zed.dev
This commit is contained in:
parent
0186289420
commit
f27a9d77d1
5 changed files with 185 additions and 119 deletions
|
@ -63,7 +63,7 @@ impl Db {
|
|||
.context("Getting dock pane")
|
||||
.log_err()?,
|
||||
center_group: self
|
||||
.get_center_group(&workspace_id)
|
||||
.get_center_pane_group(&workspace_id)
|
||||
.context("Getting center group")
|
||||
.log_err()?,
|
||||
dock_anchor,
|
||||
|
@ -104,8 +104,8 @@ impl Db {
|
|||
.exec()?;
|
||||
|
||||
// Save center pane group and dock pane
|
||||
self.save_center_group(&workspace_id, &workspace.center_group)?;
|
||||
self.save_dock_pane(&workspace_id, &workspace.dock_pane)?;
|
||||
self.save_pane_group(&workspace_id, &workspace.center_group, None)?;
|
||||
self.save_pane(&workspace_id, &workspace.dock_pane, None)?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
|
@ -152,8 +152,8 @@ mod tests {
|
|||
};
|
||||
|
||||
#[test]
|
||||
fn test_basic_functionality() {
|
||||
env_logger::init();
|
||||
fn test_workspace_assignment() {
|
||||
env_logger::try_init().ok();
|
||||
|
||||
let db = Db::open_in_memory("test_basic_functionality");
|
||||
|
||||
|
|
|
@ -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 {
|
||||
#[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,
|
||||
))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum SerializedPaneGroup {
|
||||
Group {
|
||||
axis: Axis,
|
||||
children: Vec<SerializedPaneGroup>,
|
||||
},
|
||||
Pane(SerializedPane),
|
||||
}
|
||||
|
||||
impl SerializedPaneGroup {
|
||||
pub fn new() -> Self {
|
||||
SerializedPaneGroup {
|
||||
// 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
|
||||
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");
|
||||
#[test]
|
||||
fn test_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 {
|
||||
// axis: gpui::Axis::Horizontal,
|
||||
// children: vec![PaneGroupChild::Pane(SerializedPane {
|
||||
// items: vec![ItemId { item_id: 10 }, ItemId { item_id: 20 }],
|
||||
// })],
|
||||
// };
|
||||
// -----------------
|
||||
// | 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);
|
||||
}
|
||||
}
|
||||
|
|
BIN
crates/db/test.db
Normal file
BIN
crates/db/test.db
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue