persistence.rs

  1#![allow(dead_code)]
  2
  3pub mod model;
  4
  5use std::path::Path;
  6
  7use anyhow::{anyhow, bail, Context, Result};
  8use db::{connection, sql_method};
  9use gpui::Axis;
 10use indoc::indoc;
 11
 12use db::sqlez::domain::Domain;
 13use util::{iife, unzip_option, ResultExt};
 14
 15use crate::dock::DockPosition;
 16use crate::WorkspaceId;
 17
 18use super::Workspace;
 19
 20use model::{
 21    GroupId, PaneId, SerializedItem, SerializedPane, SerializedPaneGroup, SerializedWorkspace,
 22    WorkspaceLocation,
 23};
 24
 25connection!(DB: WorkspaceDb<Workspace>);
 26
 27impl Domain for Workspace {
 28    fn name() -> &'static str {
 29        "workspace"
 30    }
 31
 32    fn migrations() -> &'static [&'static str] {
 33        &[indoc! {"
 34            CREATE TABLE workspaces(
 35                workspace_id INTEGER PRIMARY KEY,
 36                workspace_location BLOB UNIQUE,
 37                dock_visible INTEGER, -- Boolean
 38                dock_anchor TEXT, -- Enum: 'Bottom' / 'Right' / 'Expanded'
 39                dock_pane INTEGER, -- NULL indicates that we don't have a dock pane yet
 40                timestamp TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL,
 41                FOREIGN KEY(dock_pane) REFERENCES panes(pane_id)
 42            ) STRICT;
 43            
 44            CREATE TABLE pane_groups(
 45                group_id INTEGER PRIMARY KEY,
 46                workspace_id INTEGER NOT NULL,
 47                parent_group_id INTEGER, -- NULL indicates that this is a root node
 48                position INTEGER, -- NULL indicates that this is a root node
 49                axis TEXT NOT NULL, -- Enum:  'Vertical' / 'Horizontal'
 50                FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) 
 51                    ON DELETE CASCADE 
 52                    ON UPDATE CASCADE,
 53                FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE
 54            ) STRICT;
 55            
 56            CREATE TABLE panes(
 57                pane_id INTEGER PRIMARY KEY,
 58                workspace_id INTEGER NOT NULL,
 59                active INTEGER NOT NULL, -- Boolean
 60                FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) 
 61                    ON DELETE CASCADE 
 62                    ON UPDATE CASCADE
 63            ) STRICT;
 64            
 65            CREATE TABLE center_panes(
 66                pane_id INTEGER PRIMARY KEY,
 67                parent_group_id INTEGER, -- NULL means that this is a root pane
 68                position INTEGER, -- NULL means that this is a root pane
 69                FOREIGN KEY(pane_id) REFERENCES panes(pane_id) 
 70                    ON DELETE CASCADE,
 71                FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE
 72            ) STRICT;
 73                            
 74            CREATE TABLE items(
 75                item_id INTEGER NOT NULL, -- This is the item's view id, so this is not unique
 76                workspace_id INTEGER NOT NULL,
 77                pane_id INTEGER NOT NULL,
 78                kind TEXT NOT NULL,
 79                position INTEGER NOT NULL,
 80                FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
 81                    ON DELETE CASCADE
 82                    ON UPDATE CASCADE,
 83                FOREIGN KEY(pane_id) REFERENCES panes(pane_id)
 84                    ON DELETE CASCADE,
 85                PRIMARY KEY(item_id, workspace_id)
 86            ) STRICT;
 87        "}]
 88    }
 89}
 90
 91impl WorkspaceDb {
 92    /// Returns a serialized workspace for the given worktree_roots. If the passed array
 93    /// is empty, the most recent workspace is returned instead. If no workspace for the
 94    /// passed roots is stored, returns none.
 95    pub fn workspace_for_roots<P: AsRef<Path>>(
 96        &self,
 97        worktree_roots: &[P],
 98    ) -> Option<SerializedWorkspace> {
 99        let workspace_location: WorkspaceLocation = worktree_roots.into();
100
101        // Note that we re-assign the workspace_id here in case it's empty
102        // and we've grabbed the most recent workspace
103        let (workspace_id, workspace_location, dock_position): (
104            WorkspaceId,
105            WorkspaceLocation,
106            DockPosition,
107        ) = iife!({
108            if worktree_roots.len() == 0 {
109                self.select_row(indoc! {"
110                    SELECT workspace_id, workspace_location, dock_visible, dock_anchor
111                    FROM workspaces 
112                    ORDER BY timestamp DESC LIMIT 1"})?()?
113            } else {
114                self.select_row_bound(indoc! {"
115                    SELECT workspace_id, workspace_location, dock_visible, dock_anchor
116                    FROM workspaces 
117                    WHERE workspace_location = ?"})?(&workspace_location)?
118            }
119            .context("No workspaces found")
120        })
121        .warn_on_err()
122        .flatten()?;
123
124        Some(SerializedWorkspace {
125            id: workspace_id,
126            location: workspace_location.clone(),
127            dock_pane: self
128                .get_dock_pane(workspace_id)
129                .context("Getting dock pane")
130                .log_err()?,
131            center_group: self
132                .get_center_pane_group(workspace_id)
133                .context("Getting center group")
134                .log_err()?,
135            dock_position,
136        })
137    }
138
139    /// Saves a workspace using the worktree roots. Will garbage collect any workspaces
140    /// that used this workspace previously
141    pub fn save_workspace(&self, workspace: &SerializedWorkspace) {
142        self.with_savepoint("update_worktrees", || {
143            // Clear out panes and pane_groups
144            self.exec_bound(indoc! {"
145                UPDATE workspaces SET dock_pane = NULL WHERE workspace_id = ?1;
146                DELETE FROM pane_groups WHERE workspace_id = ?1;
147                DELETE FROM panes WHERE workspace_id = ?1;"})?(workspace.id)
148            .context("Clearing old panes")?;
149
150            self.exec_bound(indoc! {"
151                DELETE FROM workspaces WHERE workspace_location = ? AND workspace_id != ?
152                "})?((&workspace.location, workspace.id))
153            .context("clearing out old locations")?;
154            
155            // Update or insert
156            self.exec_bound(indoc! {
157                "INSERT INTO
158                    workspaces(workspace_id, workspace_location, dock_visible, dock_anchor, timestamp) 
159                 VALUES 
160                    (?1, ?2, ?3, ?4, CURRENT_TIMESTAMP)
161                 ON CONFLICT DO UPDATE SET
162                    workspace_location = ?2, dock_visible = ?3, dock_anchor = ?4, timestamp = CURRENT_TIMESTAMP"
163            })?((workspace.id, &workspace.location, workspace.dock_position))
164            .context("Updating workspace")?;
165
166            // Save center pane group and dock pane
167            self.save_pane_group(workspace.id, &workspace.center_group, None)
168                .context("save pane group in save workspace")?;
169
170            let dock_id = self
171                .save_pane(workspace.id, &workspace.dock_pane, None, true)
172                .context("save pane in save workspace")?;
173
174            // Complete workspace initialization
175            self.exec_bound(indoc! {"
176                UPDATE workspaces
177                SET dock_pane = ?
178                WHERE workspace_id = ?"})?((dock_id, workspace.id))
179            .context("Finishing initialization with dock pane")?;
180
181            Ok(())
182        })
183        .with_context(|| {
184            format!(
185                "Update workspace with roots {:?} and id {:?} failed.",
186                workspace.location.paths(),
187                workspace.id
188            )
189        })
190        .log_err();
191    }
192
193    sql_method! {
194        next_id() -> Result<Option<WorkspaceId>>:
195            "INSERT INTO workspaces DEFAULT VALUES RETURNING workspace_id"
196    }
197
198    /// Returns the previous workspace ids sorted by last modified along with their opened worktree roots
199    pub fn recent_workspaces(&self, limit: usize) -> Vec<(WorkspaceId, WorkspaceLocation)> {
200        iife!({
201            // TODO, upgrade anyhow: https://docs.rs/anyhow/1.0.66/anyhow/fn.Ok.html
202            Ok::<_, anyhow::Error>(
203                self.select_bound::<usize, (WorkspaceId, WorkspaceLocation)>(
204                    "SELECT workspace_id, workspace_location FROM workspaces ORDER BY timestamp DESC LIMIT ?",
205                )?(limit)?
206                .into_iter()
207                .collect::<Vec<(WorkspaceId, WorkspaceLocation)>>(),
208            )
209        })
210        .log_err()
211        .unwrap_or_default()
212    }
213
214    fn get_center_pane_group(&self, workspace_id: WorkspaceId) -> Result<SerializedPaneGroup> {
215        self.get_pane_group(workspace_id, None)?
216            .into_iter()
217            .next()
218            .context("No center pane group")
219    }
220
221    fn get_pane_group(
222        &self,
223        workspace_id: WorkspaceId,
224        group_id: Option<GroupId>,
225    ) -> Result<Vec<SerializedPaneGroup>> {
226        type GroupKey = (Option<GroupId>, WorkspaceId);
227        type GroupOrPane = (Option<GroupId>, Option<Axis>, Option<PaneId>, Option<bool>);
228        self.select_bound::<GroupKey, GroupOrPane>(indoc! {"
229            SELECT group_id, axis, pane_id, active
230                FROM (SELECT 
231                        group_id,
232                        axis,
233                        NULL as pane_id,
234                        NULL as active,
235                        position,
236                        parent_group_id,
237                        workspace_id
238                      FROM pane_groups
239                     UNION
240                      SELECT 
241                        NULL,
242                        NULL,  
243                        center_panes.pane_id,
244                        panes.active as active,
245                        position,
246                        parent_group_id,
247                        panes.workspace_id as workspace_id
248                      FROM center_panes
249                      JOIN panes ON center_panes.pane_id = panes.pane_id) 
250            WHERE parent_group_id IS ? AND workspace_id = ?
251            ORDER BY position
252            "})?((group_id, workspace_id))?
253        .into_iter()
254        .map(|(group_id, axis, pane_id, active)| {
255            if let Some((group_id, axis)) = group_id.zip(axis) {
256                Ok(SerializedPaneGroup::Group {
257                    axis,
258                    children: self.get_pane_group(workspace_id, Some(group_id))?,
259                })
260            } else if let Some((pane_id, active)) = pane_id.zip(active) {
261                Ok(SerializedPaneGroup::Pane(SerializedPane::new(
262                    self.get_items(pane_id)?,
263                    active,
264                )))
265            } else {
266                bail!("Pane Group Child was neither a pane group or a pane");
267            }
268        })
269        // Filter out panes and pane groups which don't have any children or items
270        .filter(|pane_group| match pane_group {
271            Ok(SerializedPaneGroup::Group { children, .. }) => !children.is_empty(),
272            Ok(SerializedPaneGroup::Pane(pane)) => !pane.children.is_empty(),
273            _ => true,
274        })
275        .collect::<Result<_>>()
276    }
277
278    fn save_pane_group(
279        &self,
280        workspace_id: WorkspaceId,
281        pane_group: &SerializedPaneGroup,
282        parent: Option<(GroupId, usize)>,
283    ) -> Result<()> {
284        match pane_group {
285            SerializedPaneGroup::Group { axis, children } => {
286                let (parent_id, position) = unzip_option(parent);
287
288                let group_id = self.select_row_bound::<_, i64>(indoc! {"
289                        INSERT INTO pane_groups(workspace_id, parent_group_id, position, axis) 
290                        VALUES (?, ?, ?, ?) 
291                        RETURNING group_id"})?((
292                    workspace_id,
293                    parent_id,
294                    position,
295                    *axis,
296                ))?
297                .ok_or_else(|| anyhow!("Couldn't retrieve group_id from inserted pane_group"))?;
298
299                for (position, group) in children.iter().enumerate() {
300                    self.save_pane_group(workspace_id, group, Some((group_id, position)))?
301                }
302
303                Ok(())
304            }
305            SerializedPaneGroup::Pane(pane) => {
306                self.save_pane(workspace_id, &pane, parent, false)?;
307                Ok(())
308            }
309        }
310    }
311
312    fn get_dock_pane(&self, workspace_id: WorkspaceId) -> Result<SerializedPane> {
313        let (pane_id, active) = self.select_row_bound(indoc! {"
314            SELECT pane_id, active
315            FROM panes
316            WHERE pane_id = (SELECT dock_pane FROM workspaces WHERE workspace_id = ?)"})?(
317            workspace_id,
318        )?
319        .context("No dock pane for workspace")?;
320
321        Ok(SerializedPane::new(
322            self.get_items(pane_id).context("Reading items")?,
323            active,
324        ))
325    }
326
327    fn save_pane(
328        &self,
329        workspace_id: WorkspaceId,
330        pane: &SerializedPane,
331        parent: Option<(GroupId, usize)>, // None indicates BOTH dock pane AND center_pane
332        dock: bool,
333    ) -> Result<PaneId> {
334        let pane_id = self.select_row_bound::<_, i64>(indoc! {"
335            INSERT INTO panes(workspace_id, active) 
336            VALUES (?, ?) 
337            RETURNING pane_id"})?((workspace_id, pane.active))?
338        .ok_or_else(|| anyhow!("Could not retrieve inserted pane_id"))?;
339
340        if !dock {
341            let (parent_id, order) = unzip_option(parent);
342            self.exec_bound(indoc! {"
343                INSERT INTO center_panes(pane_id, parent_group_id, position)
344                VALUES (?, ?, ?)"})?((pane_id, parent_id, order))?;
345        }
346
347        self.save_items(workspace_id, pane_id, &pane.children)
348            .context("Saving items")?;
349
350        Ok(pane_id)
351    }
352
353    fn get_items(&self, pane_id: PaneId) -> Result<Vec<SerializedItem>> {
354        Ok(self.select_bound(indoc! {"
355            SELECT kind, item_id FROM items
356            WHERE pane_id = ?
357            ORDER BY position"})?(pane_id)?)
358    }
359
360    fn save_items(
361        &self,
362        workspace_id: WorkspaceId,
363        pane_id: PaneId,
364        items: &[SerializedItem],
365    ) -> Result<()> {
366        let mut insert = self.exec_bound(
367            "INSERT INTO items(workspace_id, pane_id, position, kind, item_id) VALUES (?, ?, ?, ?, ?)",
368        ).context("Preparing insertion")?;
369        for (position, item) in items.iter().enumerate() {
370            insert((workspace_id, pane_id, position, item))?;
371        }
372
373        Ok(())
374    }
375}
376
377#[cfg(test)]
378mod tests {
379
380    use std::sync::Arc;
381
382    use db::open_memory_db;
383    use settings::DockAnchor;
384
385    use super::*;
386
387    #[test]
388    fn test_next_id_stability() {
389        env_logger::try_init().ok();
390
391        let db = WorkspaceDb(open_memory_db(Some("test_workspace_id_stability")));
392
393        db.migrate(
394            "test_table",
395            &["CREATE TABLE test_table(
396                text TEXT,
397                workspace_id INTEGER,
398                FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
399                    ON DELETE CASCADE
400            ) STRICT;"],
401        )
402        .unwrap();
403        
404        let id = db.next_id().unwrap();
405    
406        db.exec_bound("INSERT INTO test_table(text, workspace_id) VALUES (?, ?)")
407            .unwrap()(("test-text-1", id))
408        .unwrap();
409
410        let test_text_1 = db
411            .select_row_bound::<_, String>("SELECT text FROM test_table WHERE workspace_id = ?")
412            .unwrap()(1)
413        .unwrap()
414        .unwrap();
415        assert_eq!(test_text_1, "test-text-1");
416    }
417    
418    #[test]
419    fn test_workspace_id_stability() {
420        env_logger::try_init().ok();
421
422        let db = WorkspaceDb(open_memory_db(Some("test_workspace_id_stability")));
423
424        db.migrate(
425            "test_table",
426            &["CREATE TABLE test_table(
427                text TEXT,
428                workspace_id INTEGER,
429                FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
430                    ON DELETE CASCADE
431            ) STRICT;"],
432        )
433        .unwrap();
434
435        let mut workspace_1 = SerializedWorkspace {
436            id: 1,
437            location: (["/tmp", "/tmp2"]).into(),
438            dock_position: crate::dock::DockPosition::Shown(DockAnchor::Bottom),
439            center_group: Default::default(),
440            dock_pane: Default::default(),
441        };
442
443        let mut workspace_2 = SerializedWorkspace {
444            id: 2,
445            location: (["/tmp"]).into(),
446            dock_position: crate::dock::DockPosition::Hidden(DockAnchor::Expanded),
447            center_group: Default::default(),
448            dock_pane: Default::default(),
449        };
450
451        db.save_workspace(&workspace_1);
452
453        db.exec_bound("INSERT INTO test_table(text, workspace_id) VALUES (?, ?)")
454            .unwrap()(("test-text-1", 1))
455        .unwrap();
456
457        db.save_workspace(&workspace_2);
458
459        db.exec_bound("INSERT INTO test_table(text, workspace_id) VALUES (?, ?)")
460            .unwrap()(("test-text-2", 2))
461        .unwrap();
462
463        workspace_1.location = (["/tmp", "/tmp3"]).into();
464        db.save_workspace(&workspace_1);
465        db.save_workspace(&workspace_1);
466
467        workspace_2.dock_pane.children.push(SerializedItem {
468            kind: Arc::from("Test"),
469            item_id: 10,
470        });
471        db.save_workspace(&workspace_2);
472
473        let test_text_2 = db
474            .select_row_bound::<_, String>("SELECT text FROM test_table WHERE workspace_id = ?")
475            .unwrap()(2)
476        .unwrap()
477        .unwrap();
478        assert_eq!(test_text_2, "test-text-2");
479
480        let test_text_1 = db
481            .select_row_bound::<_, String>("SELECT text FROM test_table WHERE workspace_id = ?")
482            .unwrap()(1)
483        .unwrap()
484        .unwrap();
485        assert_eq!(test_text_1, "test-text-1");
486    }
487
488    #[test]
489    fn test_full_workspace_serialization() {
490        env_logger::try_init().ok();
491
492        let db = WorkspaceDb(open_memory_db(Some("test_full_workspace_serialization")));
493
494        let dock_pane = crate::persistence::model::SerializedPane {
495            children: vec![
496                SerializedItem::new("Terminal", 1),
497                SerializedItem::new("Terminal", 2),
498                SerializedItem::new("Terminal", 3),
499                SerializedItem::new("Terminal", 4),
500            ],
501            active: false,
502        };
503
504        //  -----------------
505        //  | 1,2   | 5,6   |
506        //  | - - - |       |
507        //  | 3,4   |       |
508        //  -----------------
509        let center_group = SerializedPaneGroup::Group {
510            axis: gpui::Axis::Horizontal,
511            children: vec![
512                SerializedPaneGroup::Group {
513                    axis: gpui::Axis::Vertical,
514                    children: vec![
515                        SerializedPaneGroup::Pane(SerializedPane::new(
516                            vec![
517                                SerializedItem::new("Terminal", 5),
518                                SerializedItem::new("Terminal", 6),
519                            ],
520                            false,
521                        )),
522                        SerializedPaneGroup::Pane(SerializedPane::new(
523                            vec![
524                                SerializedItem::new("Terminal", 7),
525                                SerializedItem::new("Terminal", 8),
526                            ],
527                            false,
528                        )),
529                    ],
530                },
531                SerializedPaneGroup::Pane(SerializedPane::new(
532                    vec![
533                        SerializedItem::new("Terminal", 9),
534                        SerializedItem::new("Terminal", 10),
535                    ],
536                    false,
537                )),
538            ],
539        };
540
541        let workspace = SerializedWorkspace {
542            id: 5,
543            location: (["/tmp", "/tmp2"]).into(),
544            dock_position: DockPosition::Shown(DockAnchor::Bottom),
545            center_group,
546            dock_pane,
547        };
548
549        db.save_workspace(&workspace);
550        let round_trip_workspace = db.workspace_for_roots(&["/tmp2", "/tmp"]);
551
552        assert_eq!(workspace, round_trip_workspace.unwrap());
553
554        // Test guaranteed duplicate IDs
555        db.save_workspace(&workspace);
556        db.save_workspace(&workspace);
557
558        let round_trip_workspace = db.workspace_for_roots(&["/tmp", "/tmp2"]);
559        assert_eq!(workspace, round_trip_workspace.unwrap());
560    }
561
562    #[test]
563    fn test_workspace_assignment() {
564        env_logger::try_init().ok();
565
566        let db = WorkspaceDb(open_memory_db(Some("test_basic_functionality")));
567
568        let workspace_1 = SerializedWorkspace {
569            id: 1,
570            location: (["/tmp", "/tmp2"]).into(),
571            dock_position: crate::dock::DockPosition::Shown(DockAnchor::Bottom),
572            center_group: Default::default(),
573            dock_pane: Default::default(),
574        };
575
576        let mut workspace_2 = SerializedWorkspace {
577            id: 2,
578            location: (["/tmp"]).into(),
579            dock_position: crate::dock::DockPosition::Hidden(DockAnchor::Expanded),
580            center_group: Default::default(),
581            dock_pane: Default::default(),
582        };
583
584        db.save_workspace(&workspace_1);
585        db.save_workspace(&workspace_2);
586
587        // Test that paths are treated as a set
588        assert_eq!(
589            db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(),
590            workspace_1
591        );
592        assert_eq!(
593            db.workspace_for_roots(&["/tmp2", "/tmp"]).unwrap(),
594            workspace_1
595        );
596
597        // Make sure that other keys work
598        assert_eq!(db.workspace_for_roots(&["/tmp"]).unwrap(), workspace_2);
599        assert_eq!(db.workspace_for_roots(&["/tmp3", "/tmp2", "/tmp4"]), None);
600
601        // Test 'mutate' case of updating a pre-existing id
602        workspace_2.location = (["/tmp", "/tmp2"]).into();
603
604        db.save_workspace(&workspace_2);
605        assert_eq!(
606            db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(),
607            workspace_2
608        );
609
610        // Test other mechanism for mutating
611        let mut workspace_3 = SerializedWorkspace {
612            id: 3,
613            location: (&["/tmp", "/tmp2"]).into(),
614            dock_position: DockPosition::Shown(DockAnchor::Right),
615            center_group: Default::default(),
616            dock_pane: Default::default(),
617        };
618
619        db.save_workspace(&workspace_3);
620        assert_eq!(
621            db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(),
622            workspace_3
623        );
624
625        // Make sure that updating paths differently also works
626        workspace_3.location = (["/tmp3", "/tmp4", "/tmp2"]).into();
627        db.save_workspace(&workspace_3);
628        assert_eq!(db.workspace_for_roots(&["/tmp2", "tmp"]), None);
629        assert_eq!(
630            db.workspace_for_roots(&["/tmp2", "/tmp3", "/tmp4"])
631                .unwrap(),
632            workspace_3
633        );
634    }
635
636    use crate::dock::DockPosition;
637    use crate::persistence::model::SerializedWorkspace;
638    use crate::persistence::model::{SerializedItem, SerializedPane, SerializedPaneGroup};
639
640    fn default_workspace<P: AsRef<Path>>(
641        workspace_id: &[P],
642        dock_pane: SerializedPane,
643        center_group: &SerializedPaneGroup,
644    ) -> SerializedWorkspace {
645        SerializedWorkspace {
646            id: 4,
647            location: workspace_id.into(),
648            dock_position: crate::dock::DockPosition::Hidden(DockAnchor::Right),
649            center_group: center_group.clone(),
650            dock_pane,
651        }
652    }
653
654    #[test]
655    fn test_basic_dock_pane() {
656        env_logger::try_init().ok();
657
658        let db = WorkspaceDb(open_memory_db(Some("basic_dock_pane")));
659
660        let dock_pane = crate::persistence::model::SerializedPane::new(
661            vec![
662                SerializedItem::new("Terminal", 1),
663                SerializedItem::new("Terminal", 4),
664                SerializedItem::new("Terminal", 2),
665                SerializedItem::new("Terminal", 3),
666            ],
667            false,
668        );
669
670        let workspace = default_workspace(&["/tmp"], dock_pane, &Default::default());
671
672        db.save_workspace(&workspace);
673
674        let new_workspace = db.workspace_for_roots(&["/tmp"]).unwrap();
675
676        assert_eq!(workspace.dock_pane, new_workspace.dock_pane);
677    }
678
679    #[test]
680    fn test_simple_split() {
681        env_logger::try_init().ok();
682
683        let db = WorkspaceDb(open_memory_db(Some("simple_split")));
684
685        //  -----------------
686        //  | 1,2   | 5,6   |
687        //  | - - - |       |
688        //  | 3,4   |       |
689        //  -----------------
690        let center_pane = SerializedPaneGroup::Group {
691            axis: gpui::Axis::Horizontal,
692            children: vec![
693                SerializedPaneGroup::Group {
694                    axis: gpui::Axis::Vertical,
695                    children: vec![
696                        SerializedPaneGroup::Pane(SerializedPane::new(
697                            vec![
698                                SerializedItem::new("Terminal", 1),
699                                SerializedItem::new("Terminal", 2),
700                            ],
701                            false,
702                        )),
703                        SerializedPaneGroup::Pane(SerializedPane::new(
704                            vec![
705                                SerializedItem::new("Terminal", 4),
706                                SerializedItem::new("Terminal", 3),
707                            ],
708                            true,
709                        )),
710                    ],
711                },
712                SerializedPaneGroup::Pane(SerializedPane::new(
713                    vec![
714                        SerializedItem::new("Terminal", 5),
715                        SerializedItem::new("Terminal", 6),
716                    ],
717                    false,
718                )),
719            ],
720        };
721
722        let workspace = default_workspace(&["/tmp"], Default::default(), &center_pane);
723
724        db.save_workspace(&workspace);
725
726        let new_workspace = db.workspace_for_roots(&["/tmp"]).unwrap();
727
728        assert_eq!(workspace.center_group, new_workspace.center_group);
729    }
730
731    #[test]
732    fn test_cleanup_panes() {
733        env_logger::try_init().ok();
734
735        let db = WorkspaceDb(open_memory_db(Some("test_cleanup_panes")));
736
737        let center_pane = SerializedPaneGroup::Group {
738            axis: gpui::Axis::Horizontal,
739            children: vec![
740                SerializedPaneGroup::Group {
741                    axis: gpui::Axis::Vertical,
742                    children: vec![
743                        SerializedPaneGroup::Pane(SerializedPane::new(
744                            vec![
745                                SerializedItem::new("Terminal", 1),
746                                SerializedItem::new("Terminal", 2),
747                            ],
748                            false,
749                        )),
750                        SerializedPaneGroup::Pane(SerializedPane::new(
751                            vec![
752                                SerializedItem::new("Terminal", 4),
753                                SerializedItem::new("Terminal", 3),
754                            ],
755                            true,
756                        )),
757                    ],
758                },
759                SerializedPaneGroup::Pane(SerializedPane::new(
760                    vec![
761                        SerializedItem::new("Terminal", 5),
762                        SerializedItem::new("Terminal", 6),
763                    ],
764                    false,
765                )),
766            ],
767        };
768
769        let id = &["/tmp"];
770
771        let mut workspace = default_workspace(id, Default::default(), &center_pane);
772
773        db.save_workspace(&workspace);
774
775        workspace.center_group = SerializedPaneGroup::Group {
776            axis: gpui::Axis::Vertical,
777            children: vec![
778                SerializedPaneGroup::Pane(SerializedPane::new(
779                    vec![
780                        SerializedItem::new("Terminal", 1),
781                        SerializedItem::new("Terminal", 2),
782                    ],
783                    false,
784                )),
785                SerializedPaneGroup::Pane(SerializedPane::new(
786                    vec![
787                        SerializedItem::new("Terminal", 4),
788                        SerializedItem::new("Terminal", 3),
789                    ],
790                    true,
791                )),
792            ],
793        };
794
795        db.save_workspace(&workspace);
796
797        let new_workspace = db.workspace_for_roots(id).unwrap();
798
799        assert_eq!(workspace.center_group, new_workspace.center_group);
800    }
801}