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