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 query!{
376 fn update_timestamp(workspace_id: WorkspaceId) -> Result<()> {
377 UPDATE workspaces
378 SET timestamp = CURRENT_TIMESTAMP
379 WHERE workspace_id = ?
380 }
381 }
382
383}
384
385#[cfg(test)]
386mod tests {
387
388 use std::sync::Arc;
389
390 use db::open_test_db;
391 use settings::DockAnchor;
392
393 use super::*;
394
395 #[gpui::test]
396 async fn test_next_id_stability() {
397 env_logger::try_init().ok();
398
399 let db = WorkspaceDb(open_test_db("test_next_id_stability").await);
400
401 db.write(|conn| {
402 conn.migrate(
403 "test_table",
404 &[sql!(
405 CREATE TABLE test_table(
406 text TEXT,
407 workspace_id INTEGER,
408 FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
409 ON DELETE CASCADE
410 ) STRICT;
411 )],
412 )
413 .unwrap();
414 })
415 .await;
416
417 let id = db.next_id().await.unwrap();
418 // Assert the empty row got inserted
419 assert_eq!(
420 Some(id),
421 db.select_row_bound::<WorkspaceId, WorkspaceId>(sql!(
422 SELECT workspace_id FROM workspaces WHERE workspace_id = ?
423 ))
424 .unwrap()(id)
425 .unwrap()
426 );
427
428 db.write(move |conn| {
429 conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?)))
430 .unwrap()(("test-text-1", id))
431 .unwrap()
432 })
433 .await;
434
435 let test_text_1 = db
436 .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?))
437 .unwrap()(1)
438 .unwrap()
439 .unwrap();
440 assert_eq!(test_text_1, "test-text-1");
441 }
442
443 #[gpui::test]
444 async fn test_workspace_id_stability() {
445 env_logger::try_init().ok();
446
447 let db = WorkspaceDb(open_test_db("test_workspace_id_stability").await);
448
449 db.write(|conn| {
450 conn.migrate(
451 "test_table",
452 &[sql!(
453 CREATE TABLE test_table(
454 text TEXT,
455 workspace_id INTEGER,
456 FOREIGN KEY(workspace_id)
457 REFERENCES workspaces(workspace_id)
458 ON DELETE CASCADE
459 ) STRICT;)],
460 )
461 })
462 .await
463 .unwrap();
464
465 let mut workspace_1 = SerializedWorkspace {
466 id: 1,
467 location: (["/tmp", "/tmp2"]).into(),
468 dock_position: crate::dock::DockPosition::Shown(DockAnchor::Bottom),
469 center_group: Default::default(),
470 dock_pane: Default::default(),
471 left_sidebar_open: true
472 };
473
474 let mut workspace_2 = SerializedWorkspace {
475 id: 2,
476 location: (["/tmp"]).into(),
477 dock_position: crate::dock::DockPosition::Hidden(DockAnchor::Expanded),
478 center_group: Default::default(),
479 dock_pane: Default::default(),
480 left_sidebar_open: false
481 };
482
483 db.save_workspace(workspace_1.clone()).await;
484
485 db.write(|conn| {
486 conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?)))
487 .unwrap()(("test-text-1", 1))
488 .unwrap();
489 })
490 .await;
491
492 db.save_workspace(workspace_2.clone()).await;
493
494 db.write(|conn| {
495 conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?)))
496 .unwrap()(("test-text-2", 2))
497 .unwrap();
498 })
499 .await;
500
501 workspace_1.location = (["/tmp", "/tmp3"]).into();
502 db.save_workspace(workspace_1.clone()).await;
503 db.save_workspace(workspace_1).await;
504
505 workspace_2.dock_pane.children.push(SerializedItem {
506 kind: Arc::from("Test"),
507 item_id: 10,
508 active: true,
509 });
510 db.save_workspace(workspace_2).await;
511
512 let test_text_2 = db
513 .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?))
514 .unwrap()(2)
515 .unwrap()
516 .unwrap();
517 assert_eq!(test_text_2, "test-text-2");
518
519 let test_text_1 = db
520 .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?))
521 .unwrap()(1)
522 .unwrap()
523 .unwrap();
524 assert_eq!(test_text_1, "test-text-1");
525 }
526
527 #[gpui::test]
528 async fn test_full_workspace_serialization() {
529 env_logger::try_init().ok();
530
531 let db = WorkspaceDb(open_test_db("test_full_workspace_serialization").await);
532
533 let dock_pane = crate::persistence::model::SerializedPane {
534 children: vec![
535 SerializedItem::new("Terminal", 1, false),
536 SerializedItem::new("Terminal", 2, false),
537 SerializedItem::new("Terminal", 3, true),
538 SerializedItem::new("Terminal", 4, false),
539 ],
540 active: false,
541 };
542
543 // -----------------
544 // | 1,2 | 5,6 |
545 // | - - - | |
546 // | 3,4 | |
547 // -----------------
548 let center_group = SerializedPaneGroup::Group {
549 axis: gpui::Axis::Horizontal,
550 children: vec![
551 SerializedPaneGroup::Group {
552 axis: gpui::Axis::Vertical,
553 children: vec![
554 SerializedPaneGroup::Pane(SerializedPane::new(
555 vec![
556 SerializedItem::new("Terminal", 5, false),
557 SerializedItem::new("Terminal", 6, true),
558 ],
559 false,
560 )),
561 SerializedPaneGroup::Pane(SerializedPane::new(
562 vec![
563 SerializedItem::new("Terminal", 7, true),
564 SerializedItem::new("Terminal", 8, false),
565 ],
566 false,
567 )),
568 ],
569 },
570 SerializedPaneGroup::Pane(SerializedPane::new(
571 vec![
572 SerializedItem::new("Terminal", 9, false),
573 SerializedItem::new("Terminal", 10, true),
574 ],
575 false,
576 )),
577 ],
578 };
579
580 let workspace = SerializedWorkspace {
581 id: 5,
582 location: (["/tmp", "/tmp2"]).into(),
583 dock_position: DockPosition::Shown(DockAnchor::Bottom),
584 center_group,
585 dock_pane,
586 left_sidebar_open: true
587 };
588
589 db.save_workspace(workspace.clone()).await;
590 let round_trip_workspace = db.workspace_for_roots(&["/tmp2", "/tmp"]);
591
592 assert_eq!(workspace, round_trip_workspace.unwrap());
593
594 // Test guaranteed duplicate IDs
595 db.save_workspace(workspace.clone()).await;
596 db.save_workspace(workspace.clone()).await;
597
598 let round_trip_workspace = db.workspace_for_roots(&["/tmp", "/tmp2"]);
599 assert_eq!(workspace, round_trip_workspace.unwrap());
600 }
601
602 #[gpui::test]
603 async fn test_workspace_assignment() {
604 env_logger::try_init().ok();
605
606 let db = WorkspaceDb(open_test_db("test_basic_functionality").await);
607
608 let workspace_1 = SerializedWorkspace {
609 id: 1,
610 location: (["/tmp", "/tmp2"]).into(),
611 dock_position: crate::dock::DockPosition::Shown(DockAnchor::Bottom),
612 center_group: Default::default(),
613 dock_pane: Default::default(),
614 left_sidebar_open: true,
615 };
616
617 let mut workspace_2 = SerializedWorkspace {
618 id: 2,
619 location: (["/tmp"]).into(),
620 dock_position: crate::dock::DockPosition::Hidden(DockAnchor::Expanded),
621 center_group: Default::default(),
622 dock_pane: Default::default(),
623 left_sidebar_open: false,
624 };
625
626 db.save_workspace(workspace_1.clone()).await;
627 db.save_workspace(workspace_2.clone()).await;
628
629 // Test that paths are treated as a set
630 assert_eq!(
631 db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(),
632 workspace_1
633 );
634 assert_eq!(
635 db.workspace_for_roots(&["/tmp2", "/tmp"]).unwrap(),
636 workspace_1
637 );
638
639 // Make sure that other keys work
640 assert_eq!(db.workspace_for_roots(&["/tmp"]).unwrap(), workspace_2);
641 assert_eq!(db.workspace_for_roots(&["/tmp3", "/tmp2", "/tmp4"]), None);
642
643 // Test 'mutate' case of updating a pre-existing id
644 workspace_2.location = (["/tmp", "/tmp2"]).into();
645
646 db.save_workspace(workspace_2.clone()).await;
647 assert_eq!(
648 db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(),
649 workspace_2
650 );
651
652 // Test other mechanism for mutating
653 let mut workspace_3 = SerializedWorkspace {
654 id: 3,
655 location: (&["/tmp", "/tmp2"]).into(),
656 dock_position: DockPosition::Shown(DockAnchor::Right),
657 center_group: Default::default(),
658 dock_pane: Default::default(),
659 left_sidebar_open: false
660 };
661
662 db.save_workspace(workspace_3.clone()).await;
663 assert_eq!(
664 db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(),
665 workspace_3
666 );
667
668 // Make sure that updating paths differently also works
669 workspace_3.location = (["/tmp3", "/tmp4", "/tmp2"]).into();
670 db.save_workspace(workspace_3.clone()).await;
671 assert_eq!(db.workspace_for_roots(&["/tmp2", "tmp"]), None);
672 assert_eq!(
673 db.workspace_for_roots(&["/tmp2", "/tmp3", "/tmp4"])
674 .unwrap(),
675 workspace_3
676 );
677 }
678
679 use crate::dock::DockPosition;
680 use crate::persistence::model::SerializedWorkspace;
681 use crate::persistence::model::{SerializedItem, SerializedPane, SerializedPaneGroup};
682
683 fn default_workspace<P: AsRef<Path>>(
684 workspace_id: &[P],
685 dock_pane: SerializedPane,
686 center_group: &SerializedPaneGroup,
687 ) -> SerializedWorkspace {
688 SerializedWorkspace {
689 id: 4,
690 location: workspace_id.into(),
691 dock_position: crate::dock::DockPosition::Hidden(DockAnchor::Right),
692 center_group: center_group.clone(),
693 dock_pane,
694 left_sidebar_open: true
695 }
696 }
697
698 #[gpui::test]
699 async fn test_basic_dock_pane() {
700 env_logger::try_init().ok();
701
702 let db = WorkspaceDb(open_test_db("basic_dock_pane").await);
703
704 let dock_pane = crate::persistence::model::SerializedPane::new(
705 vec![
706 SerializedItem::new("Terminal", 1, false),
707 SerializedItem::new("Terminal", 4, false),
708 SerializedItem::new("Terminal", 2, false),
709 SerializedItem::new("Terminal", 3, true),
710 ],
711 false,
712 );
713
714 let workspace = default_workspace(&["/tmp"], dock_pane, &Default::default());
715
716 db.save_workspace(workspace.clone()).await;
717
718 let new_workspace = db.workspace_for_roots(&["/tmp"]).unwrap();
719
720 assert_eq!(workspace.dock_pane, new_workspace.dock_pane);
721 }
722
723 #[gpui::test]
724 async fn test_simple_split() {
725 env_logger::try_init().ok();
726
727 let db = WorkspaceDb(open_test_db("simple_split").await);
728
729 // -----------------
730 // | 1,2 | 5,6 |
731 // | - - - | |
732 // | 3,4 | |
733 // -----------------
734 let center_pane = SerializedPaneGroup::Group {
735 axis: gpui::Axis::Horizontal,
736 children: vec![
737 SerializedPaneGroup::Group {
738 axis: gpui::Axis::Vertical,
739 children: vec![
740 SerializedPaneGroup::Pane(SerializedPane::new(
741 vec![
742 SerializedItem::new("Terminal", 1, false),
743 SerializedItem::new("Terminal", 2, true),
744 ],
745 false,
746 )),
747 SerializedPaneGroup::Pane(SerializedPane::new(
748 vec![
749 SerializedItem::new("Terminal", 4, false),
750 SerializedItem::new("Terminal", 3, true),
751 ],
752 true,
753 )),
754 ],
755 },
756 SerializedPaneGroup::Pane(SerializedPane::new(
757 vec![
758 SerializedItem::new("Terminal", 5, true),
759 SerializedItem::new("Terminal", 6, false),
760 ],
761 false,
762 )),
763 ],
764 };
765
766 let workspace = default_workspace(&["/tmp"], Default::default(), ¢er_pane);
767
768 db.save_workspace(workspace.clone()).await;
769
770 let new_workspace = db.workspace_for_roots(&["/tmp"]).unwrap();
771
772 assert_eq!(workspace.center_group, new_workspace.center_group);
773 }
774
775 #[gpui::test]
776 async fn test_cleanup_panes() {
777 env_logger::try_init().ok();
778
779 let db = WorkspaceDb(open_test_db("test_cleanup_panes").await);
780
781 let center_pane = SerializedPaneGroup::Group {
782 axis: gpui::Axis::Horizontal,
783 children: vec![
784 SerializedPaneGroup::Group {
785 axis: gpui::Axis::Vertical,
786 children: vec![
787 SerializedPaneGroup::Pane(SerializedPane::new(
788 vec![
789 SerializedItem::new("Terminal", 1, false),
790 SerializedItem::new("Terminal", 2, true),
791 ],
792 false,
793 )),
794 SerializedPaneGroup::Pane(SerializedPane::new(
795 vec![
796 SerializedItem::new("Terminal", 4, false),
797 SerializedItem::new("Terminal", 3, true),
798 ],
799 true,
800 )),
801 ],
802 },
803 SerializedPaneGroup::Pane(SerializedPane::new(
804 vec![
805 SerializedItem::new("Terminal", 5, false),
806 SerializedItem::new("Terminal", 6, true),
807 ],
808 false,
809 )),
810 ],
811 };
812
813 let id = &["/tmp"];
814
815 let mut workspace = default_workspace(id, Default::default(), ¢er_pane);
816
817 db.save_workspace(workspace.clone()).await;
818
819 workspace.center_group = SerializedPaneGroup::Group {
820 axis: gpui::Axis::Vertical,
821 children: vec![
822 SerializedPaneGroup::Pane(SerializedPane::new(
823 vec![
824 SerializedItem::new("Terminal", 1, false),
825 SerializedItem::new("Terminal", 2, true),
826 ],
827 false,
828 )),
829 SerializedPaneGroup::Pane(SerializedPane::new(
830 vec![
831 SerializedItem::new("Terminal", 4, true),
832 SerializedItem::new("Terminal", 3, false),
833 ],
834 true,
835 )),
836 ],
837 };
838
839 db.save_workspace(workspace.clone()).await;
840
841 let new_workspace = db.workspace_for_roots(id).unwrap();
842
843 assert_eq!(workspace.center_group, new_workspace.center_group);
844 }
845}