pane.rs

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