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 active 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(sql!(
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(sql!(
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 async fn save_workspace(&self, workspace: SerializedWorkspace) {
142 self.write(move |conn| {
143 conn.with_savepoint("update_worktrees", || {
144 // Clear out panes and pane_groups
145 conn.exec_bound(sql!(
146 UPDATE workspaces SET dock_pane = NULL WHERE workspace_id = ?1;
147 DELETE FROM pane_groups WHERE workspace_id = ?1;
148 DELETE FROM panes WHERE workspace_id = ?1;))?(workspace.id)
149 .context("Clearing old panes")?;
150
151 conn.exec_bound(sql!(
152 DELETE FROM workspaces WHERE workspace_location = ? AND workspace_id != ?
153 ))?((&workspace.location, workspace.id.clone()))
154 .context("clearing out old locations")?;
155
156 // Upsert
157 conn.exec_bound(sql!(
158 INSERT INTO workspaces(
159 workspace_id,
160 workspace_location,
161 dock_visible,
162 dock_anchor,
163 timestamp
164 )
165 VALUES (?1, ?2, ?3, ?4, CURRENT_TIMESTAMP)
166 ON CONFLICT DO
167 UPDATE SET
168 workspace_location = ?2,
169 dock_visible = ?3,
170 dock_anchor = ?4,
171 timestamp = CURRENT_TIMESTAMP
172 ))?((workspace.id, &workspace.location, workspace.dock_position))
173 .context("Updating workspace")?;
174
175 // Save center pane group and dock pane
176 Self::save_pane_group(conn, workspace.id, &workspace.center_group, None)
177 .context("save pane group in save workspace")?;
178
179 let dock_id = Self::save_pane(conn, workspace.id, &workspace.dock_pane, None, true)
180 .context("save pane in save workspace")?;
181
182 // Complete workspace initialization
183 conn.exec_bound(sql!(
184 UPDATE workspaces
185 SET dock_pane = ?
186 WHERE workspace_id = ?
187 ))?((dock_id, workspace.id))
188 .context("Finishing initialization with dock pane")?;
189
190 Ok(())
191 })
192 .log_err();
193 })
194 .await;
195 }
196
197 query! {
198 pub async fn next_id() -> Result<WorkspaceId> {
199 INSERT INTO workspaces DEFAULT VALUES RETURNING workspace_id
200 }
201 }
202
203 query! {
204 pub fn recent_workspaces(limit: usize) -> Result<Vec<(WorkspaceId, WorkspaceLocation)>> {
205 SELECT workspace_id, workspace_location
206 FROM workspaces
207 ORDER BY timestamp DESC
208 LIMIT ?
209 }
210 }
211
212 fn get_center_pane_group(&self, workspace_id: WorkspaceId) -> Result<SerializedPaneGroup> {
213 self.get_pane_group(workspace_id, None)?
214 .into_iter()
215 .next()
216 .context("No center pane group")
217 }
218
219 fn get_pane_group(
220 &self,
221 workspace_id: WorkspaceId,
222 group_id: Option<GroupId>,
223 ) -> Result<Vec<SerializedPaneGroup>> {
224 type GroupKey = (Option<GroupId>, WorkspaceId);
225 type GroupOrPane = (Option<GroupId>, Option<Axis>, Option<PaneId>, Option<bool>);
226 self.select_bound::<GroupKey, GroupOrPane>(sql!(
227 SELECT group_id, axis, pane_id, active
228 FROM (SELECT
229 group_id,
230 axis,
231 NULL as pane_id,
232 NULL as active,
233 position,
234 parent_group_id,
235 workspace_id
236 FROM pane_groups
237 UNION
238 SELECT
239 NULL,
240 NULL,
241 center_panes.pane_id,
242 panes.active as active,
243 position,
244 parent_group_id,
245 panes.workspace_id as workspace_id
246 FROM center_panes
247 JOIN panes ON center_panes.pane_id = panes.pane_id)
248 WHERE parent_group_id IS ? AND workspace_id = ?
249 ORDER BY position
250 ))?((group_id, workspace_id))?
251 .into_iter()
252 .map(|(group_id, axis, pane_id, active)| {
253 if let Some((group_id, axis)) = group_id.zip(axis) {
254 Ok(SerializedPaneGroup::Group {
255 axis,
256 children: self.get_pane_group(workspace_id, Some(group_id))?,
257 })
258 } else if let Some((pane_id, active)) = pane_id.zip(active) {
259 Ok(SerializedPaneGroup::Pane(SerializedPane::new(
260 self.get_items(pane_id)?,
261 active,
262 )))
263 } else {
264 bail!("Pane Group Child was neither a pane group or a pane");
265 }
266 })
267 // Filter out panes and pane groups which don't have any children or items
268 .filter(|pane_group| match pane_group {
269 Ok(SerializedPaneGroup::Group { children, .. }) => !children.is_empty(),
270 Ok(SerializedPaneGroup::Pane(pane)) => !pane.children.is_empty(),
271 _ => true,
272 })
273 .collect::<Result<_>>()
274 }
275
276 fn save_pane_group(
277 conn: &Connection,
278 workspace_id: WorkspaceId,
279 pane_group: &SerializedPaneGroup,
280 parent: Option<(GroupId, usize)>,
281 ) -> Result<()> {
282 match pane_group {
283 SerializedPaneGroup::Group { axis, children } => {
284 let (parent_id, position) = unzip_option(parent);
285
286 let group_id = conn.select_row_bound::<_, i64>(sql!(
287 INSERT INTO pane_groups(workspace_id, parent_group_id, position, axis)
288 VALUES (?, ?, ?, ?)
289 RETURNING group_id
290 ))?((
291 workspace_id,
292 parent_id,
293 position,
294 *axis,
295 ))?
296 .ok_or_else(|| anyhow!("Couldn't retrieve group_id from inserted pane_group"))?;
297
298 for (position, group) in children.iter().enumerate() {
299 Self::save_pane_group(conn, workspace_id, group, Some((group_id, position)))?
300 }
301
302 Ok(())
303 }
304 SerializedPaneGroup::Pane(pane) => {
305 Self::save_pane(conn, workspace_id, &pane, parent, false)?;
306 Ok(())
307 }
308 }
309 }
310
311 fn get_dock_pane(&self, workspace_id: WorkspaceId) -> Result<SerializedPane> {
312 let (pane_id, active) = self.select_row_bound(sql!(
313 SELECT pane_id, active
314 FROM panes
315 WHERE pane_id = (SELECT dock_pane FROM workspaces WHERE workspace_id = ?)
316 ))?(
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 conn: &Connection,
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 = conn.select_row_bound::<_, i64>(sql!(
335 INSERT INTO panes(workspace_id, active)
336 VALUES (?, ?)
337 RETURNING pane_id
338 ))?((workspace_id, pane.active))?
339 .ok_or_else(|| anyhow!("Could not retrieve inserted pane_id"))?;
340
341 if !dock {
342 let (parent_id, order) = unzip_option(parent);
343 conn.exec_bound(sql!(
344 INSERT INTO center_panes(pane_id, parent_group_id, position)
345 VALUES (?, ?, ?)
346 ))?((pane_id, parent_id, order))?;
347 }
348
349 Self::save_items(conn, workspace_id, pane_id, &pane.children).context("Saving items")?;
350
351 Ok(pane_id)
352 }
353
354 fn get_items(&self, pane_id: PaneId) -> Result<Vec<SerializedItem>> {
355 Ok(self.select_bound(sql!(
356 SELECT kind, item_id, active FROM items
357 WHERE pane_id = ?
358 ORDER BY position
359 ))?(pane_id)?)
360 }
361
362 fn save_items(
363 conn: &Connection,
364 workspace_id: WorkspaceId,
365 pane_id: PaneId,
366 items: &[SerializedItem],
367 ) -> Result<()> {
368 let mut insert = conn.exec_bound(sql!(
369 INSERT INTO items(workspace_id, pane_id, position, kind, item_id, active) VALUES (?, ?, ?, ?, ?, ?)
370 )).context("Preparing insertion")?;
371 for (position, item) in items.iter().enumerate() {
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 active: true,
501 });
502 db.save_workspace(workspace_2).await;
503
504 let test_text_2 = db
505 .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?))
506 .unwrap()(2)
507 .unwrap()
508 .unwrap();
509 assert_eq!(test_text_2, "test-text-2");
510
511 let test_text_1 = db
512 .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?))
513 .unwrap()(1)
514 .unwrap()
515 .unwrap();
516 assert_eq!(test_text_1, "test-text-1");
517 }
518
519 #[gpui::test]
520 async fn test_full_workspace_serialization() {
521 env_logger::try_init().ok();
522
523 let db = WorkspaceDb(open_test_db("test_full_workspace_serialization").await);
524
525 let dock_pane = crate::persistence::model::SerializedPane {
526 children: vec![
527 SerializedItem::new("Terminal", 1, false),
528 SerializedItem::new("Terminal", 2, false),
529 SerializedItem::new("Terminal", 3, true),
530 SerializedItem::new("Terminal", 4, false),
531 ],
532 active: false,
533 };
534
535 // -----------------
536 // | 1,2 | 5,6 |
537 // | - - - | |
538 // | 3,4 | |
539 // -----------------
540 let center_group = SerializedPaneGroup::Group {
541 axis: gpui::Axis::Horizontal,
542 children: vec![
543 SerializedPaneGroup::Group {
544 axis: gpui::Axis::Vertical,
545 children: vec![
546 SerializedPaneGroup::Pane(SerializedPane::new(
547 vec![
548 SerializedItem::new("Terminal", 5, false),
549 SerializedItem::new("Terminal", 6, true),
550 ],
551 false,
552 )),
553 SerializedPaneGroup::Pane(SerializedPane::new(
554 vec![
555 SerializedItem::new("Terminal", 7, true),
556 SerializedItem::new("Terminal", 8, false),
557 ],
558 false,
559 )),
560 ],
561 },
562 SerializedPaneGroup::Pane(SerializedPane::new(
563 vec![
564 SerializedItem::new("Terminal", 9, false),
565 SerializedItem::new("Terminal", 10, true),
566 ],
567 false,
568 )),
569 ],
570 };
571
572 let workspace = SerializedWorkspace {
573 id: 5,
574 location: (["/tmp", "/tmp2"]).into(),
575 dock_position: DockPosition::Shown(DockAnchor::Bottom),
576 center_group,
577 dock_pane,
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 };
606
607 let mut workspace_2 = SerializedWorkspace {
608 id: 2,
609 location: (["/tmp"]).into(),
610 dock_position: crate::dock::DockPosition::Hidden(DockAnchor::Expanded),
611 center_group: Default::default(),
612 dock_pane: Default::default(),
613 };
614
615 db.save_workspace(workspace_1.clone()).await;
616 db.save_workspace(workspace_2.clone()).await;
617
618 // Test that paths are treated as a set
619 assert_eq!(
620 db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(),
621 workspace_1
622 );
623 assert_eq!(
624 db.workspace_for_roots(&["/tmp2", "/tmp"]).unwrap(),
625 workspace_1
626 );
627
628 // Make sure that other keys work
629 assert_eq!(db.workspace_for_roots(&["/tmp"]).unwrap(), workspace_2);
630 assert_eq!(db.workspace_for_roots(&["/tmp3", "/tmp2", "/tmp4"]), None);
631
632 // Test 'mutate' case of updating a pre-existing id
633 workspace_2.location = (["/tmp", "/tmp2"]).into();
634
635 db.save_workspace(workspace_2.clone()).await;
636 assert_eq!(
637 db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(),
638 workspace_2
639 );
640
641 // Test other mechanism for mutating
642 let mut workspace_3 = SerializedWorkspace {
643 id: 3,
644 location: (&["/tmp", "/tmp2"]).into(),
645 dock_position: DockPosition::Shown(DockAnchor::Right),
646 center_group: Default::default(),
647 dock_pane: Default::default(),
648 };
649
650 db.save_workspace(workspace_3.clone()).await;
651 assert_eq!(
652 db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(),
653 workspace_3
654 );
655
656 // Make sure that updating paths differently also works
657 workspace_3.location = (["/tmp3", "/tmp4", "/tmp2"]).into();
658 db.save_workspace(workspace_3.clone()).await;
659 assert_eq!(db.workspace_for_roots(&["/tmp2", "tmp"]), None);
660 assert_eq!(
661 db.workspace_for_roots(&["/tmp2", "/tmp3", "/tmp4"])
662 .unwrap(),
663 workspace_3
664 );
665 }
666
667 use crate::dock::DockPosition;
668 use crate::persistence::model::SerializedWorkspace;
669 use crate::persistence::model::{SerializedItem, SerializedPane, SerializedPaneGroup};
670
671 fn default_workspace<P: AsRef<Path>>(
672 workspace_id: &[P],
673 dock_pane: SerializedPane,
674 center_group: &SerializedPaneGroup,
675 ) -> SerializedWorkspace {
676 SerializedWorkspace {
677 id: 4,
678 location: workspace_id.into(),
679 dock_position: crate::dock::DockPosition::Hidden(DockAnchor::Right),
680 center_group: center_group.clone(),
681 dock_pane,
682 }
683 }
684
685 #[gpui::test]
686 async fn test_basic_dock_pane() {
687 env_logger::try_init().ok();
688
689 let db = WorkspaceDb(open_test_db("basic_dock_pane").await);
690
691 let dock_pane = crate::persistence::model::SerializedPane::new(
692 vec![
693 SerializedItem::new("Terminal", 1, false),
694 SerializedItem::new("Terminal", 4, false),
695 SerializedItem::new("Terminal", 2, false),
696 SerializedItem::new("Terminal", 3, true),
697 ],
698 false,
699 );
700
701 let workspace = default_workspace(&["/tmp"], dock_pane, &Default::default());
702
703 db.save_workspace(workspace.clone()).await;
704
705 let new_workspace = db.workspace_for_roots(&["/tmp"]).unwrap();
706
707 assert_eq!(workspace.dock_pane, new_workspace.dock_pane);
708 }
709
710 #[gpui::test]
711 async fn test_simple_split() {
712 env_logger::try_init().ok();
713
714 let db = WorkspaceDb(open_test_db("simple_split").await);
715
716 // -----------------
717 // | 1,2 | 5,6 |
718 // | - - - | |
719 // | 3,4 | |
720 // -----------------
721 let center_pane = SerializedPaneGroup::Group {
722 axis: gpui::Axis::Horizontal,
723 children: vec![
724 SerializedPaneGroup::Group {
725 axis: gpui::Axis::Vertical,
726 children: vec![
727 SerializedPaneGroup::Pane(SerializedPane::new(
728 vec![
729 SerializedItem::new("Terminal", 1, false),
730 SerializedItem::new("Terminal", 2, true),
731 ],
732 false,
733 )),
734 SerializedPaneGroup::Pane(SerializedPane::new(
735 vec![
736 SerializedItem::new("Terminal", 4, false),
737 SerializedItem::new("Terminal", 3, true),
738 ],
739 true,
740 )),
741 ],
742 },
743 SerializedPaneGroup::Pane(SerializedPane::new(
744 vec![
745 SerializedItem::new("Terminal", 5, true),
746 SerializedItem::new("Terminal", 6, false),
747 ],
748 false,
749 )),
750 ],
751 };
752
753 let workspace = default_workspace(&["/tmp"], Default::default(), ¢er_pane);
754
755 db.save_workspace(workspace.clone()).await;
756
757 let new_workspace = db.workspace_for_roots(&["/tmp"]).unwrap();
758
759 assert_eq!(workspace.center_group, new_workspace.center_group);
760 }
761
762 #[gpui::test]
763 async fn test_cleanup_panes() {
764 env_logger::try_init().ok();
765
766 let db = WorkspaceDb(open_test_db("test_cleanup_panes").await);
767
768 let center_pane = SerializedPaneGroup::Group {
769 axis: gpui::Axis::Horizontal,
770 children: vec![
771 SerializedPaneGroup::Group {
772 axis: gpui::Axis::Vertical,
773 children: vec![
774 SerializedPaneGroup::Pane(SerializedPane::new(
775 vec![
776 SerializedItem::new("Terminal", 1, false),
777 SerializedItem::new("Terminal", 2, true),
778 ],
779 false,
780 )),
781 SerializedPaneGroup::Pane(SerializedPane::new(
782 vec![
783 SerializedItem::new("Terminal", 4, false),
784 SerializedItem::new("Terminal", 3, true),
785 ],
786 true,
787 )),
788 ],
789 },
790 SerializedPaneGroup::Pane(SerializedPane::new(
791 vec![
792 SerializedItem::new("Terminal", 5, false),
793 SerializedItem::new("Terminal", 6, true),
794 ],
795 false,
796 )),
797 ],
798 };
799
800 let id = &["/tmp"];
801
802 let mut workspace = default_workspace(id, Default::default(), ¢er_pane);
803
804 db.save_workspace(workspace.clone()).await;
805
806 workspace.center_group = SerializedPaneGroup::Group {
807 axis: gpui::Axis::Vertical,
808 children: vec![
809 SerializedPaneGroup::Pane(SerializedPane::new(
810 vec![
811 SerializedItem::new("Terminal", 1, false),
812 SerializedItem::new("Terminal", 2, true),
813 ],
814 false,
815 )),
816 SerializedPaneGroup::Pane(SerializedPane::new(
817 vec![
818 SerializedItem::new("Terminal", 4, true),
819 SerializedItem::new("Terminal", 3, false),
820 ],
821 true,
822 )),
823 ],
824 };
825
826 db.save_workspace(workspace.clone()).await;
827
828 let new_workspace = db.workspace_for_roots(id).unwrap();
829
830 assert_eq!(workspace.center_group, new_workspace.center_group);
831 }
832}