1#![allow(dead_code)]
2
3pub mod model;
4
5use std::path::Path;
6
7use anyhow::{anyhow, bail, Context, Result};
8use db2::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql};
9use gpui2::WindowBounds;
10
11use util::{unzip_option, ResultExt};
12use uuid::Uuid;
13
14use crate::{Axis, WorkspaceId};
15
16use model::{
17 GroupId, PaneId, SerializedItem, SerializedPane, SerializedPaneGroup, SerializedWorkspace,
18 WorkspaceLocation,
19};
20
21use self::model::DockStructure;
22
23define_connection! {
24 // Current schema shape using pseudo-rust syntax:
25 //
26 // workspaces(
27 // workspace_id: usize, // Primary key for workspaces
28 // workspace_location: Bincode<Vec<PathBuf>>,
29 // dock_visible: bool, // Deprecated
30 // dock_anchor: DockAnchor, // Deprecated
31 // dock_pane: Option<usize>, // Deprecated
32 // left_sidebar_open: boolean,
33 // timestamp: String, // UTC YYYY-MM-DD HH:MM:SS
34 // window_state: String, // WindowBounds Discriminant
35 // window_x: Option<f32>, // WindowBounds::Fixed RectF x
36 // window_y: Option<f32>, // WindowBounds::Fixed RectF y
37 // window_width: Option<f32>, // WindowBounds::Fixed RectF width
38 // window_height: Option<f32>, // WindowBounds::Fixed RectF height
39 // display: Option<Uuid>, // Display id
40 // )
41 //
42 // pane_groups(
43 // group_id: usize, // Primary key for pane_groups
44 // workspace_id: usize, // References workspaces table
45 // parent_group_id: Option<usize>, // None indicates that this is the root node
46 // position: Optiopn<usize>, // None indicates that this is the root node
47 // axis: Option<Axis>, // 'Vertical', 'Horizontal'
48 // flexes: Option<Vec<f32>>, // A JSON array of floats
49 // )
50 //
51 // panes(
52 // pane_id: usize, // Primary key for panes
53 // workspace_id: usize, // References workspaces table
54 // active: bool,
55 // )
56 //
57 // center_panes(
58 // pane_id: usize, // Primary key for center_panes
59 // parent_group_id: Option<usize>, // References pane_groups. If none, this is the root
60 // position: Option<usize>, // None indicates this is the root
61 // )
62 //
63 // CREATE TABLE items(
64 // item_id: usize, // This is the item's view id, so this is not unique
65 // workspace_id: usize, // References workspaces table
66 // pane_id: usize, // References panes table
67 // kind: String, // Indicates which view this connects to. This is the key in the item_deserializers global
68 // position: usize, // Position of the item in the parent pane. This is equivalent to panes' position column
69 // active: bool, // Indicates if this item is the active one in the pane
70 // )
71 pub static ref DB: WorkspaceDb<()> =
72 &[sql!(
73 CREATE TABLE workspaces(
74 workspace_id INTEGER PRIMARY KEY,
75 workspace_location BLOB UNIQUE,
76 dock_visible INTEGER, // Deprecated. Preserving so users can downgrade Zed.
77 dock_anchor TEXT, // Deprecated. Preserving so users can downgrade Zed.
78 dock_pane INTEGER, // Deprecated. Preserving so users can downgrade Zed.
79 left_sidebar_open INTEGER, // Boolean
80 timestamp TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL,
81 FOREIGN KEY(dock_pane) REFERENCES panes(pane_id)
82 ) STRICT;
83
84 CREATE TABLE pane_groups(
85 group_id INTEGER PRIMARY KEY,
86 workspace_id INTEGER NOT NULL,
87 parent_group_id INTEGER, // NULL indicates that this is a root node
88 position INTEGER, // NULL indicates that this is a root node
89 axis TEXT NOT NULL, // Enum: 'Vertical' / 'Horizontal'
90 FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
91 ON DELETE CASCADE
92 ON UPDATE CASCADE,
93 FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE
94 ) STRICT;
95
96 CREATE TABLE panes(
97 pane_id INTEGER PRIMARY KEY,
98 workspace_id INTEGER NOT NULL,
99 active INTEGER NOT NULL, // Boolean
100 FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
101 ON DELETE CASCADE
102 ON UPDATE CASCADE
103 ) STRICT;
104
105 CREATE TABLE center_panes(
106 pane_id INTEGER PRIMARY KEY,
107 parent_group_id INTEGER, // NULL means that this is a root pane
108 position INTEGER, // NULL means that this is a root pane
109 FOREIGN KEY(pane_id) REFERENCES panes(pane_id)
110 ON DELETE CASCADE,
111 FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE
112 ) STRICT;
113
114 CREATE TABLE items(
115 item_id INTEGER NOT NULL, // This is the item's view id, so this is not unique
116 workspace_id INTEGER NOT NULL,
117 pane_id INTEGER NOT NULL,
118 kind TEXT NOT NULL,
119 position INTEGER NOT NULL,
120 active INTEGER NOT NULL,
121 FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
122 ON DELETE CASCADE
123 ON UPDATE CASCADE,
124 FOREIGN KEY(pane_id) REFERENCES panes(pane_id)
125 ON DELETE CASCADE,
126 PRIMARY KEY(item_id, workspace_id)
127 ) STRICT;
128 ),
129 sql!(
130 ALTER TABLE workspaces ADD COLUMN window_state TEXT;
131 ALTER TABLE workspaces ADD COLUMN window_x REAL;
132 ALTER TABLE workspaces ADD COLUMN window_y REAL;
133 ALTER TABLE workspaces ADD COLUMN window_width REAL;
134 ALTER TABLE workspaces ADD COLUMN window_height REAL;
135 ALTER TABLE workspaces ADD COLUMN display BLOB;
136 ),
137 // Drop foreign key constraint from workspaces.dock_pane to panes table.
138 sql!(
139 CREATE TABLE workspaces_2(
140 workspace_id INTEGER PRIMARY KEY,
141 workspace_location BLOB UNIQUE,
142 dock_visible INTEGER, // Deprecated. Preserving so users can downgrade Zed.
143 dock_anchor TEXT, // Deprecated. Preserving so users can downgrade Zed.
144 dock_pane INTEGER, // Deprecated. Preserving so users can downgrade Zed.
145 left_sidebar_open INTEGER, // Boolean
146 timestamp TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL,
147 window_state TEXT,
148 window_x REAL,
149 window_y REAL,
150 window_width REAL,
151 window_height REAL,
152 display BLOB
153 ) STRICT;
154 INSERT INTO workspaces_2 SELECT * FROM workspaces;
155 DROP TABLE workspaces;
156 ALTER TABLE workspaces_2 RENAME TO workspaces;
157 ),
158 // Add panels related information
159 sql!(
160 ALTER TABLE workspaces ADD COLUMN left_dock_visible INTEGER; //bool
161 ALTER TABLE workspaces ADD COLUMN left_dock_active_panel TEXT;
162 ALTER TABLE workspaces ADD COLUMN right_dock_visible INTEGER; //bool
163 ALTER TABLE workspaces ADD COLUMN right_dock_active_panel TEXT;
164 ALTER TABLE workspaces ADD COLUMN bottom_dock_visible INTEGER; //bool
165 ALTER TABLE workspaces ADD COLUMN bottom_dock_active_panel TEXT;
166 ),
167 // Add panel zoom persistence
168 sql!(
169 ALTER TABLE workspaces ADD COLUMN left_dock_zoom INTEGER; //bool
170 ALTER TABLE workspaces ADD COLUMN right_dock_zoom INTEGER; //bool
171 ALTER TABLE workspaces ADD COLUMN bottom_dock_zoom INTEGER; //bool
172 ),
173 // Add pane group flex data
174 sql!(
175 ALTER TABLE pane_groups ADD COLUMN flexes TEXT;
176 )
177 ];
178}
179
180impl WorkspaceDb {
181 /// Returns a serialized workspace for the given worktree_roots. If the passed array
182 /// is empty, the most recent workspace is returned instead. If no workspace for the
183 /// passed roots is stored, returns none.
184 pub fn workspace_for_roots<P: AsRef<Path>>(
185 &self,
186 worktree_roots: &[P],
187 ) -> Option<SerializedWorkspace> {
188 let workspace_location: WorkspaceLocation = worktree_roots.into();
189
190 // Note that we re-assign the workspace_id here in case it's empty
191 // and we've grabbed the most recent workspace
192 let (workspace_id, workspace_location, bounds, display, docks): (
193 WorkspaceId,
194 WorkspaceLocation,
195 Option<WindowBounds>,
196 Option<Uuid>,
197 DockStructure,
198 ) = self
199 .select_row_bound(sql! {
200 SELECT
201 workspace_id,
202 workspace_location,
203 window_state,
204 window_x,
205 window_y,
206 window_width,
207 window_height,
208 display,
209 left_dock_visible,
210 left_dock_active_panel,
211 left_dock_zoom,
212 right_dock_visible,
213 right_dock_active_panel,
214 right_dock_zoom,
215 bottom_dock_visible,
216 bottom_dock_active_panel,
217 bottom_dock_zoom
218 FROM workspaces
219 WHERE workspace_location = ?
220 })
221 .and_then(|mut prepared_statement| (prepared_statement)(&workspace_location))
222 .context("No workspaces found")
223 .warn_on_err()
224 .flatten()?;
225
226 Some(SerializedWorkspace {
227 id: workspace_id,
228 location: workspace_location.clone(),
229 center_group: self
230 .get_center_pane_group(workspace_id)
231 .context("Getting center group")
232 .log_err()?,
233 bounds,
234 display,
235 docks,
236 })
237 }
238
239 /// Saves a workspace using the worktree roots. Will garbage collect any workspaces
240 /// that used this workspace previously
241 pub async fn save_workspace(&self, workspace: SerializedWorkspace) {
242 self.write(move |conn| {
243 conn.with_savepoint("update_worktrees", || {
244 // Clear out panes and pane_groups
245 conn.exec_bound(sql!(
246 DELETE FROM pane_groups WHERE workspace_id = ?1;
247 DELETE FROM panes WHERE workspace_id = ?1;))?(workspace.id)
248 .expect("Clearing old panes");
249
250 conn.exec_bound(sql!(
251 DELETE FROM workspaces WHERE workspace_location = ? AND workspace_id != ?
252 ))?((&workspace.location, workspace.id.clone()))
253 .context("clearing out old locations")?;
254
255 // Upsert
256 conn.exec_bound(sql!(
257 INSERT INTO workspaces(
258 workspace_id,
259 workspace_location,
260 left_dock_visible,
261 left_dock_active_panel,
262 left_dock_zoom,
263 right_dock_visible,
264 right_dock_active_panel,
265 right_dock_zoom,
266 bottom_dock_visible,
267 bottom_dock_active_panel,
268 bottom_dock_zoom,
269 timestamp
270 )
271 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, CURRENT_TIMESTAMP)
272 ON CONFLICT DO
273 UPDATE SET
274 workspace_location = ?2,
275 left_dock_visible = ?3,
276 left_dock_active_panel = ?4,
277 left_dock_zoom = ?5,
278 right_dock_visible = ?6,
279 right_dock_active_panel = ?7,
280 right_dock_zoom = ?8,
281 bottom_dock_visible = ?9,
282 bottom_dock_active_panel = ?10,
283 bottom_dock_zoom = ?11,
284 timestamp = CURRENT_TIMESTAMP
285 ))?((workspace.id, &workspace.location, workspace.docks))
286 .context("Updating workspace")?;
287
288 // Save center pane group
289 Self::save_pane_group(conn, workspace.id, &workspace.center_group, None)
290 .context("save pane group in save workspace")?;
291
292 Ok(())
293 })
294 .log_err();
295 })
296 .await;
297 }
298
299 query! {
300 pub async fn next_id() -> Result<WorkspaceId> {
301 INSERT INTO workspaces DEFAULT VALUES RETURNING workspace_id
302 }
303 }
304
305 query! {
306 fn recent_workspaces() -> Result<Vec<(WorkspaceId, WorkspaceLocation)>> {
307 SELECT workspace_id, workspace_location
308 FROM workspaces
309 WHERE workspace_location IS NOT NULL
310 ORDER BY timestamp DESC
311 }
312 }
313
314 query! {
315 async fn delete_stale_workspace(id: WorkspaceId) -> Result<()> {
316 DELETE FROM workspaces
317 WHERE workspace_id IS ?
318 }
319 }
320
321 // Returns the recent locations which are still valid on disk and deletes ones which no longer
322 // exist.
323 pub async fn recent_workspaces_on_disk(&self) -> Result<Vec<(WorkspaceId, WorkspaceLocation)>> {
324 let mut result = Vec::new();
325 let mut delete_tasks = Vec::new();
326 for (id, location) in self.recent_workspaces()? {
327 if location.paths().iter().all(|path| path.exists())
328 && location.paths().iter().any(|path| path.is_dir())
329 {
330 result.push((id, location));
331 } else {
332 delete_tasks.push(self.delete_stale_workspace(id));
333 }
334 }
335
336 futures::future::join_all(delete_tasks).await;
337 Ok(result)
338 }
339
340 pub async fn last_workspace(&self) -> Result<Option<WorkspaceLocation>> {
341 Ok(self
342 .recent_workspaces_on_disk()
343 .await?
344 .into_iter()
345 .next()
346 .map(|(_, location)| location))
347 }
348
349 fn get_center_pane_group(&self, workspace_id: WorkspaceId) -> Result<SerializedPaneGroup> {
350 Ok(self
351 .get_pane_group(workspace_id, None)?
352 .into_iter()
353 .next()
354 .unwrap_or_else(|| {
355 SerializedPaneGroup::Pane(SerializedPane {
356 active: true,
357 children: vec![],
358 })
359 }))
360 }
361
362 fn get_pane_group(
363 &self,
364 workspace_id: WorkspaceId,
365 group_id: Option<GroupId>,
366 ) -> Result<Vec<SerializedPaneGroup>> {
367 type GroupKey = (Option<GroupId>, WorkspaceId);
368 type GroupOrPane = (
369 Option<GroupId>,
370 Option<Axis>,
371 Option<PaneId>,
372 Option<bool>,
373 Option<String>,
374 );
375 self.select_bound::<GroupKey, GroupOrPane>(sql!(
376 SELECT group_id, axis, pane_id, active, flexes
377 FROM (SELECT
378 group_id,
379 axis,
380 NULL as pane_id,
381 NULL as active,
382 position,
383 parent_group_id,
384 workspace_id,
385 flexes
386 FROM pane_groups
387 UNION
388 SELECT
389 NULL,
390 NULL,
391 center_panes.pane_id,
392 panes.active as active,
393 position,
394 parent_group_id,
395 panes.workspace_id as workspace_id,
396 NULL
397 FROM center_panes
398 JOIN panes ON center_panes.pane_id = panes.pane_id)
399 WHERE parent_group_id IS ? AND workspace_id = ?
400 ORDER BY position
401 ))?((group_id, workspace_id))?
402 .into_iter()
403 .map(|(group_id, axis, pane_id, active, flexes)| {
404 if let Some((group_id, axis)) = group_id.zip(axis) {
405 let flexes = flexes
406 .map(|flexes| serde_json::from_str::<Vec<f32>>(&flexes))
407 .transpose()?;
408
409 Ok(SerializedPaneGroup::Group {
410 axis,
411 children: self.get_pane_group(workspace_id, Some(group_id))?,
412 flexes,
413 })
414 } else if let Some((pane_id, active)) = pane_id.zip(active) {
415 Ok(SerializedPaneGroup::Pane(SerializedPane::new(
416 self.get_items(pane_id)?,
417 active,
418 )))
419 } else {
420 bail!("Pane Group Child was neither a pane group or a pane");
421 }
422 })
423 // Filter out panes and pane groups which don't have any children or items
424 .filter(|pane_group| match pane_group {
425 Ok(SerializedPaneGroup::Group { children, .. }) => !children.is_empty(),
426 Ok(SerializedPaneGroup::Pane(pane)) => !pane.children.is_empty(),
427 _ => true,
428 })
429 .collect::<Result<_>>()
430 }
431
432 fn save_pane_group(
433 conn: &Connection,
434 workspace_id: WorkspaceId,
435 pane_group: &SerializedPaneGroup,
436 parent: Option<(GroupId, usize)>,
437 ) -> Result<()> {
438 match pane_group {
439 SerializedPaneGroup::Group {
440 axis,
441 children,
442 flexes,
443 } => {
444 let (parent_id, position) = unzip_option(parent);
445
446 let flex_string = flexes
447 .as_ref()
448 .map(|flexes| serde_json::json!(flexes).to_string());
449
450 let group_id = conn.select_row_bound::<_, i64>(sql!(
451 INSERT INTO pane_groups(
452 workspace_id,
453 parent_group_id,
454 position,
455 axis,
456 flexes
457 )
458 VALUES (?, ?, ?, ?, ?)
459 RETURNING group_id
460 ))?((
461 workspace_id,
462 parent_id,
463 position,
464 *axis,
465 flex_string,
466 ))?
467 .ok_or_else(|| anyhow!("Couldn't retrieve group_id from inserted pane_group"))?;
468
469 for (position, group) in children.iter().enumerate() {
470 Self::save_pane_group(conn, workspace_id, group, Some((group_id, position)))?
471 }
472
473 Ok(())
474 }
475 SerializedPaneGroup::Pane(pane) => {
476 Self::save_pane(conn, workspace_id, &pane, parent)?;
477 Ok(())
478 }
479 }
480 }
481
482 fn save_pane(
483 conn: &Connection,
484 workspace_id: WorkspaceId,
485 pane: &SerializedPane,
486 parent: Option<(GroupId, usize)>,
487 ) -> Result<PaneId> {
488 let pane_id = conn.select_row_bound::<_, i64>(sql!(
489 INSERT INTO panes(workspace_id, active)
490 VALUES (?, ?)
491 RETURNING pane_id
492 ))?((workspace_id, pane.active))?
493 .ok_or_else(|| anyhow!("Could not retrieve inserted pane_id"))?;
494
495 let (parent_id, order) = unzip_option(parent);
496 conn.exec_bound(sql!(
497 INSERT INTO center_panes(pane_id, parent_group_id, position)
498 VALUES (?, ?, ?)
499 ))?((pane_id, parent_id, order))?;
500
501 Self::save_items(conn, workspace_id, pane_id, &pane.children).context("Saving items")?;
502
503 Ok(pane_id)
504 }
505
506 fn get_items(&self, pane_id: PaneId) -> Result<Vec<SerializedItem>> {
507 Ok(self.select_bound(sql!(
508 SELECT kind, item_id, active FROM items
509 WHERE pane_id = ?
510 ORDER BY position
511 ))?(pane_id)?)
512 }
513
514 fn save_items(
515 conn: &Connection,
516 workspace_id: WorkspaceId,
517 pane_id: PaneId,
518 items: &[SerializedItem],
519 ) -> Result<()> {
520 let mut insert = conn.exec_bound(sql!(
521 INSERT INTO items(workspace_id, pane_id, position, kind, item_id, active) VALUES (?, ?, ?, ?, ?, ?)
522 )).context("Preparing insertion")?;
523 for (position, item) in items.iter().enumerate() {
524 insert((workspace_id, pane_id, position, item))?;
525 }
526
527 Ok(())
528 }
529
530 query! {
531 pub async fn update_timestamp(workspace_id: WorkspaceId) -> Result<()> {
532 UPDATE workspaces
533 SET timestamp = CURRENT_TIMESTAMP
534 WHERE workspace_id = ?
535 }
536 }
537
538 query! {
539 pub async fn set_window_bounds(workspace_id: WorkspaceId, bounds: WindowBounds, display: Uuid) -> Result<()> {
540 UPDATE workspaces
541 SET window_state = ?2,
542 window_x = ?3,
543 window_y = ?4,
544 window_width = ?5,
545 window_height = ?6,
546 display = ?7
547 WHERE workspace_id = ?1
548 }
549 }
550}
551
552// todo!()
553// #[cfg(test)]
554// mod tests {
555// use super::*;
556// use db::open_test_db;
557
558// #[gpui::test]
559// async fn test_next_id_stability() {
560// env_logger::try_init().ok();
561
562// let db = WorkspaceDb(open_test_db("test_next_id_stability").await);
563
564// db.write(|conn| {
565// conn.migrate(
566// "test_table",
567// &[sql!(
568// CREATE TABLE test_table(
569// text TEXT,
570// workspace_id INTEGER,
571// FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
572// ON DELETE CASCADE
573// ) STRICT;
574// )],
575// )
576// .unwrap();
577// })
578// .await;
579
580// let id = db.next_id().await.unwrap();
581// // Assert the empty row got inserted
582// assert_eq!(
583// Some(id),
584// db.select_row_bound::<WorkspaceId, WorkspaceId>(sql!(
585// SELECT workspace_id FROM workspaces WHERE workspace_id = ?
586// ))
587// .unwrap()(id)
588// .unwrap()
589// );
590
591// db.write(move |conn| {
592// conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?)))
593// .unwrap()(("test-text-1", id))
594// .unwrap()
595// })
596// .await;
597
598// let test_text_1 = db
599// .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?))
600// .unwrap()(1)
601// .unwrap()
602// .unwrap();
603// assert_eq!(test_text_1, "test-text-1");
604// }
605
606// #[gpui::test]
607// async fn test_workspace_id_stability() {
608// env_logger::try_init().ok();
609
610// let db = WorkspaceDb(open_test_db("test_workspace_id_stability").await);
611
612// db.write(|conn| {
613// conn.migrate(
614// "test_table",
615// &[sql!(
616// CREATE TABLE test_table(
617// text TEXT,
618// workspace_id INTEGER,
619// FOREIGN KEY(workspace_id)
620// REFERENCES workspaces(workspace_id)
621// ON DELETE CASCADE
622// ) STRICT;)],
623// )
624// })
625// .await
626// .unwrap();
627
628// let mut workspace_1 = SerializedWorkspace {
629// id: 1,
630// location: (["/tmp", "/tmp2"]).into(),
631// center_group: Default::default(),
632// bounds: Default::default(),
633// display: Default::default(),
634// docks: Default::default(),
635// };
636
637// let workspace_2 = SerializedWorkspace {
638// id: 2,
639// location: (["/tmp"]).into(),
640// center_group: Default::default(),
641// bounds: Default::default(),
642// display: Default::default(),
643// docks: Default::default(),
644// };
645
646// db.save_workspace(workspace_1.clone()).await;
647
648// db.write(|conn| {
649// conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?)))
650// .unwrap()(("test-text-1", 1))
651// .unwrap();
652// })
653// .await;
654
655// db.save_workspace(workspace_2.clone()).await;
656
657// db.write(|conn| {
658// conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?)))
659// .unwrap()(("test-text-2", 2))
660// .unwrap();
661// })
662// .await;
663
664// workspace_1.location = (["/tmp", "/tmp3"]).into();
665// db.save_workspace(workspace_1.clone()).await;
666// db.save_workspace(workspace_1).await;
667// db.save_workspace(workspace_2).await;
668
669// let test_text_2 = db
670// .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?))
671// .unwrap()(2)
672// .unwrap()
673// .unwrap();
674// assert_eq!(test_text_2, "test-text-2");
675
676// let test_text_1 = db
677// .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?))
678// .unwrap()(1)
679// .unwrap()
680// .unwrap();
681// assert_eq!(test_text_1, "test-text-1");
682// }
683
684// fn group(axis: gpui::Axis, children: Vec<SerializedPaneGroup>) -> SerializedPaneGroup {
685// SerializedPaneGroup::Group {
686// axis,
687// flexes: None,
688// children,
689// }
690// }
691
692// #[gpui::test]
693// async fn test_full_workspace_serialization() {
694// env_logger::try_init().ok();
695
696// let db = WorkspaceDb(open_test_db("test_full_workspace_serialization").await);
697
698// // -----------------
699// // | 1,2 | 5,6 |
700// // | - - - | |
701// // | 3,4 | |
702// // -----------------
703// let center_group = group(
704// gpui::Axis::Horizontal,
705// vec![
706// group(
707// gpui::Axis::Vertical,
708// vec![
709// SerializedPaneGroup::Pane(SerializedPane::new(
710// vec![
711// SerializedItem::new("Terminal", 5, false),
712// SerializedItem::new("Terminal", 6, true),
713// ],
714// false,
715// )),
716// SerializedPaneGroup::Pane(SerializedPane::new(
717// vec![
718// SerializedItem::new("Terminal", 7, true),
719// SerializedItem::new("Terminal", 8, false),
720// ],
721// false,
722// )),
723// ],
724// ),
725// SerializedPaneGroup::Pane(SerializedPane::new(
726// vec![
727// SerializedItem::new("Terminal", 9, false),
728// SerializedItem::new("Terminal", 10, true),
729// ],
730// false,
731// )),
732// ],
733// );
734
735// let workspace = SerializedWorkspace {
736// id: 5,
737// location: (["/tmp", "/tmp2"]).into(),
738// center_group,
739// bounds: Default::default(),
740// display: Default::default(),
741// docks: Default::default(),
742// };
743
744// db.save_workspace(workspace.clone()).await;
745// let round_trip_workspace = db.workspace_for_roots(&["/tmp2", "/tmp"]);
746
747// assert_eq!(workspace, round_trip_workspace.unwrap());
748
749// // Test guaranteed duplicate IDs
750// db.save_workspace(workspace.clone()).await;
751// db.save_workspace(workspace.clone()).await;
752
753// let round_trip_workspace = db.workspace_for_roots(&["/tmp", "/tmp2"]);
754// assert_eq!(workspace, round_trip_workspace.unwrap());
755// }
756
757// #[gpui::test]
758// async fn test_workspace_assignment() {
759// env_logger::try_init().ok();
760
761// let db = WorkspaceDb(open_test_db("test_basic_functionality").await);
762
763// let workspace_1 = SerializedWorkspace {
764// id: 1,
765// location: (["/tmp", "/tmp2"]).into(),
766// center_group: Default::default(),
767// bounds: Default::default(),
768// display: Default::default(),
769// docks: Default::default(),
770// };
771
772// let mut workspace_2 = SerializedWorkspace {
773// id: 2,
774// location: (["/tmp"]).into(),
775// center_group: Default::default(),
776// bounds: Default::default(),
777// display: Default::default(),
778// docks: Default::default(),
779// };
780
781// db.save_workspace(workspace_1.clone()).await;
782// db.save_workspace(workspace_2.clone()).await;
783
784// // Test that paths are treated as a set
785// assert_eq!(
786// db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(),
787// workspace_1
788// );
789// assert_eq!(
790// db.workspace_for_roots(&["/tmp2", "/tmp"]).unwrap(),
791// workspace_1
792// );
793
794// // Make sure that other keys work
795// assert_eq!(db.workspace_for_roots(&["/tmp"]).unwrap(), workspace_2);
796// assert_eq!(db.workspace_for_roots(&["/tmp3", "/tmp2", "/tmp4"]), None);
797
798// // Test 'mutate' case of updating a pre-existing id
799// workspace_2.location = (["/tmp", "/tmp2"]).into();
800
801// db.save_workspace(workspace_2.clone()).await;
802// assert_eq!(
803// db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(),
804// workspace_2
805// );
806
807// // Test other mechanism for mutating
808// let mut workspace_3 = SerializedWorkspace {
809// id: 3,
810// location: (&["/tmp", "/tmp2"]).into(),
811// center_group: Default::default(),
812// bounds: Default::default(),
813// display: Default::default(),
814// docks: Default::default(),
815// };
816
817// db.save_workspace(workspace_3.clone()).await;
818// assert_eq!(
819// db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(),
820// workspace_3
821// );
822
823// // Make sure that updating paths differently also works
824// workspace_3.location = (["/tmp3", "/tmp4", "/tmp2"]).into();
825// db.save_workspace(workspace_3.clone()).await;
826// assert_eq!(db.workspace_for_roots(&["/tmp2", "tmp"]), None);
827// assert_eq!(
828// db.workspace_for_roots(&["/tmp2", "/tmp3", "/tmp4"])
829// .unwrap(),
830// workspace_3
831// );
832// }
833
834// use crate::persistence::model::SerializedWorkspace;
835// use crate::persistence::model::{SerializedItem, SerializedPane, SerializedPaneGroup};
836
837// fn default_workspace<P: AsRef<Path>>(
838// workspace_id: &[P],
839// center_group: &SerializedPaneGroup,
840// ) -> SerializedWorkspace {
841// SerializedWorkspace {
842// id: 4,
843// location: workspace_id.into(),
844// center_group: center_group.clone(),
845// bounds: Default::default(),
846// display: Default::default(),
847// docks: Default::default(),
848// }
849// }
850
851// #[gpui::test]
852// async fn test_simple_split() {
853// env_logger::try_init().ok();
854
855// let db = WorkspaceDb(open_test_db("simple_split").await);
856
857// // -----------------
858// // | 1,2 | 5,6 |
859// // | - - - | |
860// // | 3,4 | |
861// // -----------------
862// let center_pane = group(
863// gpui::Axis::Horizontal,
864// vec![
865// group(
866// gpui::Axis::Vertical,
867// vec![
868// SerializedPaneGroup::Pane(SerializedPane::new(
869// vec![
870// SerializedItem::new("Terminal", 1, false),
871// SerializedItem::new("Terminal", 2, true),
872// ],
873// false,
874// )),
875// SerializedPaneGroup::Pane(SerializedPane::new(
876// vec![
877// SerializedItem::new("Terminal", 4, false),
878// SerializedItem::new("Terminal", 3, true),
879// ],
880// true,
881// )),
882// ],
883// ),
884// SerializedPaneGroup::Pane(SerializedPane::new(
885// vec![
886// SerializedItem::new("Terminal", 5, true),
887// SerializedItem::new("Terminal", 6, false),
888// ],
889// false,
890// )),
891// ],
892// );
893
894// let workspace = default_workspace(&["/tmp"], ¢er_pane);
895
896// db.save_workspace(workspace.clone()).await;
897
898// let new_workspace = db.workspace_for_roots(&["/tmp"]).unwrap();
899
900// assert_eq!(workspace.center_group, new_workspace.center_group);
901// }
902
903// #[gpui::test]
904// async fn test_cleanup_panes() {
905// env_logger::try_init().ok();
906
907// let db = WorkspaceDb(open_test_db("test_cleanup_panes").await);
908
909// let center_pane = group(
910// gpui::Axis::Horizontal,
911// vec![
912// group(
913// gpui::Axis::Vertical,
914// vec![
915// SerializedPaneGroup::Pane(SerializedPane::new(
916// vec![
917// SerializedItem::new("Terminal", 1, false),
918// SerializedItem::new("Terminal", 2, true),
919// ],
920// false,
921// )),
922// SerializedPaneGroup::Pane(SerializedPane::new(
923// vec![
924// SerializedItem::new("Terminal", 4, false),
925// SerializedItem::new("Terminal", 3, true),
926// ],
927// true,
928// )),
929// ],
930// ),
931// SerializedPaneGroup::Pane(SerializedPane::new(
932// vec![
933// SerializedItem::new("Terminal", 5, false),
934// SerializedItem::new("Terminal", 6, true),
935// ],
936// false,
937// )),
938// ],
939// );
940
941// let id = &["/tmp"];
942
943// let mut workspace = default_workspace(id, ¢er_pane);
944
945// db.save_workspace(workspace.clone()).await;
946
947// workspace.center_group = group(
948// gpui::Axis::Vertical,
949// vec![
950// SerializedPaneGroup::Pane(SerializedPane::new(
951// vec![
952// SerializedItem::new("Terminal", 1, false),
953// SerializedItem::new("Terminal", 2, true),
954// ],
955// false,
956// )),
957// SerializedPaneGroup::Pane(SerializedPane::new(
958// vec![
959// SerializedItem::new("Terminal", 4, true),
960// SerializedItem::new("Terminal", 3, false),
961// ],
962// true,
963// )),
964// ],
965// );
966
967// db.save_workspace(workspace.clone()).await;
968
969// let new_workspace = db.workspace_for_roots(id).unwrap();
970
971// assert_eq!(workspace.center_group, new_workspace.center_group);
972// }
973// }