pane.rs

  1
  2use gpui::Axis;
  3
  4use rusqlite::{OptionalExtension, Connection};
  5use serde::{Deserialize, Serialize};
  6use serde_rusqlite::{from_row, to_params_named};
  7
  8use crate::{items::ItemId, workspace::WorkspaceId};
  9
 10use super::Db;
 11
 12pub(crate) const PANE_M_1: &str = "
 13CREATE TABLE dock_panes(
 14    dock_pane_id INTEGER PRIMARY KEY,
 15    workspace_id INTEGER NOT NULL,
 16    anchor_position TEXT NOT NULL, -- Enum: 'Bottom' / 'Right' / 'Expanded'
 17    visible INTEGER NOT NULL, -- Boolean
 18    FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) ON DELETE CASCADE
 19) STRICT;
 20
 21CREATE TABLE pane_groups(
 22    group_id INTEGER PRIMARY KEY,
 23    workspace_id INTEGER NOT NULL,
 24    parent_group INTEGER, -- NULL indicates that this is a root node
 25    axis TEXT NOT NULL, -- Enum:  'Vertical' / 'Horizontal'
 26    FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) ON DELETE CASCADE,
 27    FOREIGN KEY(parent_group) REFERENCES pane_groups(group_id) ON DELETE CASCADE
 28) STRICT;
 29
 30CREATE TABLE grouped_panes(
 31    pane_id INTEGER PRIMARY KEY,
 32    workspace_id INTEGER NOT NULL,
 33    group_id INTEGER NOT NULL,
 34    idx INTEGER NOT NULL,
 35    FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) ON DELETE CASCADE,
 36    FOREIGN KEY(group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE
 37) STRICT;
 38
 39CREATE TABLE items(
 40    item_id INTEGER PRIMARY KEY,
 41    workspace_id INTEGER NOT NULL,
 42    kind TEXT NOT NULL,
 43    FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) ON DELETE CASCADE
 44) STRICT;
 45
 46CREATE TABLE group_items(
 47    workspace_id INTEGER NOT NULL,
 48    pane_id INTEGER NOT NULL,
 49    item_id INTEGER NOT NULL,
 50    idx INTEGER NOT NULL,
 51    PRIMARY KEY (workspace_id, pane_id, item_id)
 52    FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) ON DELETE CASCADE,
 53    FOREIGN KEY(pane_id) REFERENCES grouped_panes(pane_id) ON DELETE CASCADE,
 54    FOREIGN KEY(item_id) REFERENCES items(item_id) ON DELETE CASCADE
 55) STRICT;
 56
 57CREATE TABLE dock_items(
 58    workspace_id INTEGER NOT NULL,
 59    dock_pane_id INTEGER NOT NULL,
 60    item_id INTEGER NOT NULL,
 61    idx INTEGER NOT NULL,
 62    PRIMARY KEY (workspace_id, dock_pane_id, item_id)
 63    FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) ON DELETE CASCADE,
 64    FOREIGN KEY(dock_pane_id) REFERENCES dock_panes(dock_pane_id) ON DELETE CASCADE,
 65    FOREIGN KEY(item_id) REFERENCES items(item_id)ON DELETE CASCADE
 66) STRICT;
 67";
 68
 69// We have an many-branched, unbalanced tree with three types:
 70// Pane Groups
 71// Panes
 72// Items
 73
 74// The root is always a Pane Group
 75// Pane Groups can have 0 (or more) Panes and/or Pane Groups as children
 76// Panes can have 0 or more items as children
 77// Panes can be their own root
 78// Items cannot have children
 79// References pointing down is hard (SQL doesn't like arrays)
 80// References pointing up is easy (1-1 item / parent relationship) but is harder to query
 81//
 82
 83#[derive(Debug, PartialEq, Eq, Copy, Clone)]
 84pub struct PaneId {
 85    workspace_id: WorkspaceId,
 86    pane_id: usize,
 87}
 88
 89#[derive(Debug, PartialEq, Eq, Copy, Clone)]
 90pub struct PaneGroupId {
 91    workspace_id: WorkspaceId,
 92    group_id: usize,
 93}
 94
 95impl PaneGroupId {
 96    pub fn root(workspace_id: WorkspaceId) -> Self {
 97        Self {
 98            workspace_id,
 99            group_id: 0,
100        }
101    }
102}
103
104#[derive(Debug, PartialEq, Eq)]
105pub struct SerializedPaneGroup {
106    group_id: PaneGroupId,
107    axis: Axis,
108    children: Vec<PaneGroupChild>,
109}
110
111impl SerializedPaneGroup {
112    pub fn empty_root(workspace_id: WorkspaceId) -> Self {
113        Self {
114            group_id: PaneGroupId::root(workspace_id),
115            axis: Default::default(),
116            children: Default::default(),
117        }
118    }
119}
120
121struct PaneGroupChildRow {
122    child_pane_id: Option<usize>,
123    child_group_id: Option<usize>,
124    index: usize,
125}
126
127#[derive(Debug, PartialEq, Eq)]
128pub enum PaneGroupChild {
129    Pane(SerializedPane),
130    Group(SerializedPaneGroup),
131}
132
133#[derive(Debug, PartialEq, Eq)]
134pub struct SerializedPane {
135    pane_id: PaneId,
136    children: Vec<ItemId>,
137}
138
139
140//********* CURRENTLY IN USE TYPES: *********
141
142
143#[derive(Default, Debug, PartialEq, Eq, Deserialize, Serialize)]
144pub enum DockAnchor {
145    #[default]
146    Bottom,
147    Right,
148    Expanded,
149}
150
151#[derive(Default, Debug, PartialEq, Eq, Deserialize, Serialize)]
152pub struct SerializedDockPane {
153    pub anchor_position: DockAnchor,
154    pub visible: bool,
155}
156
157impl SerializedDockPane {
158    pub fn to_row(&self, workspace: WorkspaceId) -> DockRow {
159        DockRow { workspace_id: workspace, anchor_position: self.anchor_position, visible: self.visible }
160    }
161}
162
163#[derive(Default, Debug, PartialEq, Eq, Deserialize, Serialize)]
164pub(crate) struct DockRow {
165    workspace_id: WorkspaceId,
166    anchor_position: DockAnchor,
167    visible: bool,
168}
169
170impl DockRow {
171    pub fn to_pane(&self) -> SerializedDockPane {
172        SerializedDockPane { anchor_position: self.anchor_position, visible: self.visible }
173    }
174}
175
176impl Db {
177    pub fn get_pane_group(&self, pane_group_id: PaneGroupId) -> SerializedPaneGroup {
178        let axis = self.get_pane_group_axis(pane_group_id);
179        let mut children: Vec<(usize, PaneGroupChild)> = Vec::new();
180        for child_row in self.get_pane_group_children(pane_group_id) {
181            if let Some(child_pane_id) = child_row.child_pane_id {
182                children.push((
183                    child_row.index,
184                    PaneGroupChild::Pane(self.get_pane(PaneId {
185                        workspace_id: pane_group_id.workspace_id,
186                        pane_id: child_pane_id,
187                    })),
188                ));
189            } else if let Some(child_group_id) = child_row.child_group_id {
190                children.push((
191                    child_row.index,
192                    PaneGroupChild::Group(self.get_pane_group(PaneGroupId {
193                        workspace_id: pane_group_id.workspace_id,
194                        group_id: child_group_id,
195                    })),
196                ));
197            }
198        }
199        children.sort_by_key(|(index, _)| *index);
200
201        SerializedPaneGroup {
202            group_id: pane_group_id,
203            axis,
204            children: children.into_iter().map(|(_, child)| child).collect(),
205        }
206    }
207
208    fn get_pane_group_children(
209        &self,
210        _pane_group_id: PaneGroupId,
211    ) -> impl Iterator<Item = PaneGroupChildRow> {
212        Vec::new().into_iter()
213    }
214
215    fn get_pane_group_axis(&self, _pane_group_id: PaneGroupId) -> Axis {
216        unimplemented!();
217    }
218
219    pub fn save_pane_splits(&self, _center_pane_group: SerializedPaneGroup) {
220        // Delete the center pane group for this workspace and any of its children
221        // Generate new pane group IDs as we go through
222        // insert them
223        // Items garbage collect themselves when dropped
224    }
225
226    pub(crate) fn get_pane(&self, _pane_id: PaneId) -> SerializedPane {
227        unimplemented!();
228    }
229
230    pub fn get_dock_pane(&self, workspace: WorkspaceId) -> Option<SerializedDockPane> {
231        fn logic(conn: &Connection, workspace: WorkspaceId) -> anyhow::Result<Option<SerializedDockPane>> {
232
233            let mut stmt = conn.prepare("SELECT workspace_id, anchor_position, visible FROM dock_panes WHERE workspace_id = ?")?;
234            
235            let dock_panes = stmt.query_row([workspace.raw_id()], |row_ref| from_row::<DockRow>).optional();
236            
237            let mut dock_panes_iter = stmt.query_and_then([workspace.raw_id()], from_row::<DockRow>)?;
238            let dock_pane = dock_panes_iter
239                    .next()
240                    .and_then(|dock_row|
241                        dock_row
242                            .ok()
243                            .map(|dock_row| dock_row.to_pane()));
244            
245            Ok(dock_pane)
246        }
247
248        self.real()
249            .map(|db| {
250                let lock = db.connection.lock();
251                
252                match logic(&lock, workspace) {
253                    Ok(dock_pane) => dock_pane,
254                    Err(err) => {
255                        log::error!("Failed to get the dock pane: {}", err);
256                        None
257                    },
258                }
259            })
260            .unwrap_or(None)
261            
262    }
263
264    pub fn save_dock_pane(&self, workspace: WorkspaceId, dock_pane: SerializedDockPane) {
265        to_params_named(dock_pane.to_row(workspace))
266            .map_err(|err| {
267                log::error!("Failed to parse params for the dock row: {}", err);
268                err
269            })
270            .ok()
271            .zip(self.real())
272            .map(|(params, db)| {
273                // TODO: overwrite old dock panes if need be
274                let query = "INSERT INTO dock_panes (workspace_id, anchor_position, visible) VALUES (:workspace_id, :anchor_position, :visible);";
275                
276                db.connection
277                    .lock()
278                    .execute(query, params.to_slice().as_slice())
279                    .map(|_| ()) // Eat the return value
280                    .unwrap_or_else(|err| {
281                        log::error!("Failed to insert new dock pane into DB: {}", err);
282                    })
283            });
284    }
285}
286
287#[cfg(test)]
288mod tests {
289
290    use crate::Db;
291
292    use super::{DockAnchor, SerializedDockPane};
293
294    #[test]
295    fn test_basic_dock_pane() {
296        let db = Db::open_in_memory();
297
298        let workspace = db.workspace_for_roots(&["/tmp"]);
299
300        let dock_pane = SerializedDockPane {
301            workspace_id: workspace.workspace_id,
302            anchor_position: DockAnchor::Expanded,
303            visible: true,
304        };
305
306        db.save_dock_pane(&dock_pane);
307
308        let new_workspace = db.workspace_for_roots(&["/tmp"]);
309
310        assert_eq!(new_workspace.dock_pane.unwrap(), dock_pane);
311    }
312}