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