1use super::{SerializedAxis, SerializedWindowBounds};
2use crate::{
3 item::ItemHandle, Member, Pane, PaneAxis, SerializableItemRegistry, Workspace, WorkspaceId,
4};
5use anyhow::{Context, Result};
6use async_recursion::async_recursion;
7use client::DevServerProjectId;
8use db::sqlez::{
9 bindable::{Bind, Column, StaticColumnCount},
10 statement::Statement,
11};
12use gpui::{AsyncWindowContext, Model, View, WeakView};
13use project::Project;
14use remote::ssh_session::SshProjectId;
15use serde::{Deserialize, Serialize};
16use std::{
17 path::{Path, PathBuf},
18 sync::Arc,
19};
20use ui::SharedString;
21use util::ResultExt;
22use uuid::Uuid;
23
24#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
25pub struct SerializedSshProject {
26 pub id: SshProjectId,
27 pub host: String,
28 pub port: Option<u16>,
29 pub path: String,
30 pub user: Option<String>,
31}
32
33impl SerializedSshProject {
34 pub fn ssh_url(&self) -> String {
35 let mut result = String::from("ssh://");
36 if let Some(user) = &self.user {
37 result.push_str(user);
38 result.push('@');
39 }
40 result.push_str(&self.host);
41 if let Some(port) = &self.port {
42 result.push(':');
43 result.push_str(&port.to_string());
44 }
45 result.push_str(&self.path);
46 result
47 }
48}
49
50impl StaticColumnCount for SerializedSshProject {
51 fn column_count() -> usize {
52 5
53 }
54}
55
56impl Bind for &SerializedSshProject {
57 fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
58 let next_index = statement.bind(&self.id.0, start_index)?;
59 let next_index = statement.bind(&self.host, next_index)?;
60 let next_index = statement.bind(&self.port, next_index)?;
61 let next_index = statement.bind(&self.path, next_index)?;
62 statement.bind(&self.user, next_index)
63 }
64}
65
66impl Column for SerializedSshProject {
67 fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
68 let id = statement.column_int64(start_index)?;
69 let host = statement.column_text(start_index + 1)?.to_string();
70 let (port, _) = Option::<u16>::column(statement, start_index + 2)?;
71 let path = statement.column_text(start_index + 3)?.to_string();
72 let (user, _) = Option::<String>::column(statement, start_index + 4)?;
73
74 Ok((
75 Self {
76 id: SshProjectId(id as u64),
77 host,
78 port,
79 path,
80 user,
81 },
82 start_index + 5,
83 ))
84 }
85}
86
87#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
88pub struct SerializedDevServerProject {
89 pub id: DevServerProjectId,
90 pub dev_server_name: String,
91 pub paths: Vec<SharedString>,
92}
93
94#[derive(Debug, PartialEq, Clone)]
95pub struct LocalPaths(Arc<Vec<PathBuf>>);
96
97impl LocalPaths {
98 pub fn new<P: AsRef<Path>>(paths: impl IntoIterator<Item = P>) -> Self {
99 let mut paths: Vec<PathBuf> = paths
100 .into_iter()
101 .map(|p| p.as_ref().to_path_buf())
102 .collect();
103 // Ensure all future `zed workspace1 workspace2` and `zed workspace2 workspace1` calls are using the same workspace.
104 // The actual workspace order is stored in the `LocalPathsOrder` struct.
105 paths.sort();
106 Self(Arc::new(paths))
107 }
108
109 pub fn paths(&self) -> &Arc<Vec<PathBuf>> {
110 &self.0
111 }
112}
113
114impl StaticColumnCount for LocalPaths {}
115impl Bind for &LocalPaths {
116 fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
117 statement.bind(&bincode::serialize(&self.0)?, start_index)
118 }
119}
120
121impl Column for LocalPaths {
122 fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
123 let path_blob = statement.column_blob(start_index)?;
124 let paths: Arc<Vec<PathBuf>> = if path_blob.is_empty() {
125 Default::default()
126 } else {
127 bincode::deserialize(path_blob).context("Bincode deserialization of paths failed")?
128 };
129
130 Ok((Self(paths), start_index + 1))
131 }
132}
133
134#[derive(Debug, PartialEq, Clone)]
135pub struct LocalPathsOrder(Vec<usize>);
136
137impl LocalPathsOrder {
138 pub fn new(order: impl IntoIterator<Item = usize>) -> Self {
139 Self(order.into_iter().collect())
140 }
141
142 pub fn order(&self) -> &[usize] {
143 self.0.as_slice()
144 }
145
146 pub fn default_for_paths(paths: &LocalPaths) -> Self {
147 Self::new(0..paths.0.len())
148 }
149}
150
151impl StaticColumnCount for LocalPathsOrder {}
152impl Bind for &LocalPathsOrder {
153 fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
154 statement.bind(&bincode::serialize(&self.0)?, start_index)
155 }
156}
157
158impl Column for LocalPathsOrder {
159 fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
160 let order_blob = statement.column_blob(start_index)?;
161 let order = if order_blob.is_empty() {
162 Vec::new()
163 } else {
164 bincode::deserialize(order_blob).context("deserializing workspace root order")?
165 };
166
167 Ok((Self(order), start_index + 1))
168 }
169}
170
171impl From<SerializedDevServerProject> for SerializedWorkspaceLocation {
172 fn from(dev_server_project: SerializedDevServerProject) -> Self {
173 Self::DevServer(dev_server_project)
174 }
175}
176
177impl StaticColumnCount for SerializedDevServerProject {}
178impl Bind for &SerializedDevServerProject {
179 fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
180 let next_index = statement.bind(&self.id.0, start_index)?;
181 let next_index = statement.bind(&self.dev_server_name, next_index)?;
182 let paths = serde_json::to_string(&self.paths)?;
183 statement.bind(&paths, next_index)
184 }
185}
186
187impl Column for SerializedDevServerProject {
188 fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
189 let id = statement.column_int64(start_index)?;
190 let dev_server_name = statement.column_text(start_index + 1)?.to_string();
191 let paths = statement.column_text(start_index + 2)?.to_string();
192 let paths: Vec<SharedString> = if paths.starts_with('[') {
193 serde_json::from_str(&paths).context("JSON deserialization of paths failed")?
194 } else {
195 vec![paths.into()]
196 };
197
198 Ok((
199 Self {
200 id: DevServerProjectId(id as u64),
201 dev_server_name,
202 paths,
203 },
204 start_index + 3,
205 ))
206 }
207}
208
209#[derive(Debug, PartialEq, Clone)]
210pub enum SerializedWorkspaceLocation {
211 Local(LocalPaths, LocalPathsOrder),
212 Ssh(SerializedSshProject),
213 DevServer(SerializedDevServerProject),
214}
215
216impl SerializedWorkspaceLocation {
217 /// Create a new `SerializedWorkspaceLocation` from a list of local paths.
218 ///
219 /// The paths will be sorted and the order will be stored in the `LocalPathsOrder` struct.
220 ///
221 /// # Examples
222 ///
223 /// ```
224 /// use std::path::Path;
225 /// use zed_workspace::SerializedWorkspaceLocation;
226 ///
227 /// let location = SerializedWorkspaceLocation::from_local_paths(vec![
228 /// Path::new("path/to/workspace1"),
229 /// Path::new("path/to/workspace2"),
230 /// ]);
231 /// assert_eq!(location, SerializedWorkspaceLocation::Local(
232 /// LocalPaths::new(vec![
233 /// Path::new("path/to/workspace1"),
234 /// Path::new("path/to/workspace2"),
235 /// ]),
236 /// LocalPathsOrder::new(vec![0, 1]),
237 /// ));
238 /// ```
239 ///
240 /// ```
241 /// use std::path::Path;
242 /// use zed_workspace::SerializedWorkspaceLocation;
243 ///
244 /// let location = SerializedWorkspaceLocation::from_local_paths(vec![
245 /// Path::new("path/to/workspace2"),
246 /// Path::new("path/to/workspace1"),
247 /// ]);
248 ///
249 /// assert_eq!(location, SerializedWorkspaceLocation::Local(
250 /// LocalPaths::new(vec![
251 /// Path::new("path/to/workspace1"),
252 /// Path::new("path/to/workspace2"),
253 /// ]),
254 /// LocalPathsOrder::new(vec![1, 0]),
255 /// ));
256 /// ```
257 pub fn from_local_paths<P: AsRef<Path>>(paths: impl IntoIterator<Item = P>) -> Self {
258 let mut indexed_paths: Vec<_> = paths
259 .into_iter()
260 .map(|p| p.as_ref().to_path_buf())
261 .enumerate()
262 .collect();
263
264 indexed_paths.sort_by(|(_, a), (_, b)| a.cmp(b));
265
266 let sorted_paths: Vec<_> = indexed_paths.iter().map(|(_, path)| path.clone()).collect();
267 let order: Vec<_> = indexed_paths.iter().map(|(index, _)| *index).collect();
268
269 Self::Local(LocalPaths::new(sorted_paths), LocalPathsOrder::new(order))
270 }
271}
272
273#[derive(Debug, PartialEq, Clone)]
274pub(crate) struct SerializedWorkspace {
275 pub(crate) id: WorkspaceId,
276 pub(crate) location: SerializedWorkspaceLocation,
277 pub(crate) center_group: SerializedPaneGroup,
278 pub(crate) window_bounds: Option<SerializedWindowBounds>,
279 pub(crate) centered_layout: bool,
280 pub(crate) display: Option<Uuid>,
281 pub(crate) docks: DockStructure,
282 pub(crate) session_id: Option<String>,
283 pub(crate) window_id: Option<u64>,
284}
285
286#[derive(Debug, PartialEq, Clone, Default)]
287pub struct DockStructure {
288 pub(crate) left: DockData,
289 pub(crate) right: DockData,
290 pub(crate) bottom: DockData,
291}
292
293impl Column for DockStructure {
294 fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
295 let (left, next_index) = DockData::column(statement, start_index)?;
296 let (right, next_index) = DockData::column(statement, next_index)?;
297 let (bottom, next_index) = DockData::column(statement, next_index)?;
298 Ok((
299 DockStructure {
300 left,
301 right,
302 bottom,
303 },
304 next_index,
305 ))
306 }
307}
308
309impl Bind for DockStructure {
310 fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
311 let next_index = statement.bind(&self.left, start_index)?;
312 let next_index = statement.bind(&self.right, next_index)?;
313 statement.bind(&self.bottom, next_index)
314 }
315}
316
317#[derive(Debug, PartialEq, Clone, Default)]
318pub struct DockData {
319 pub(crate) visible: bool,
320 pub(crate) active_panel: Option<String>,
321 pub(crate) zoom: bool,
322}
323
324impl Column for DockData {
325 fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
326 let (visible, next_index) = Option::<bool>::column(statement, start_index)?;
327 let (active_panel, next_index) = Option::<String>::column(statement, next_index)?;
328 let (zoom, next_index) = Option::<bool>::column(statement, next_index)?;
329 Ok((
330 DockData {
331 visible: visible.unwrap_or(false),
332 active_panel,
333 zoom: zoom.unwrap_or(false),
334 },
335 next_index,
336 ))
337 }
338}
339
340impl Bind for DockData {
341 fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
342 let next_index = statement.bind(&self.visible, start_index)?;
343 let next_index = statement.bind(&self.active_panel, next_index)?;
344 statement.bind(&self.zoom, next_index)
345 }
346}
347
348#[derive(Debug, PartialEq, Clone)]
349pub(crate) enum SerializedPaneGroup {
350 Group {
351 axis: SerializedAxis,
352 flexes: Option<Vec<f32>>,
353 children: Vec<SerializedPaneGroup>,
354 },
355 Pane(SerializedPane),
356}
357
358#[cfg(test)]
359impl Default for SerializedPaneGroup {
360 fn default() -> Self {
361 Self::Pane(SerializedPane {
362 children: vec![SerializedItem::default()],
363 active: false,
364 pinned_count: 0,
365 })
366 }
367}
368
369impl SerializedPaneGroup {
370 #[async_recursion(?Send)]
371 pub(crate) async fn deserialize(
372 self,
373 project: &Model<Project>,
374 workspace_id: WorkspaceId,
375 workspace: WeakView<Workspace>,
376 cx: &mut AsyncWindowContext,
377 ) -> Option<(Member, Option<View<Pane>>, Vec<Option<Box<dyn ItemHandle>>>)> {
378 match self {
379 SerializedPaneGroup::Group {
380 axis,
381 children,
382 flexes,
383 } => {
384 let mut current_active_pane = None;
385 let mut members = Vec::new();
386 let mut items = Vec::new();
387 for child in children {
388 if let Some((new_member, active_pane, new_items)) = child
389 .deserialize(project, workspace_id, workspace.clone(), cx)
390 .await
391 {
392 members.push(new_member);
393 items.extend(new_items);
394 current_active_pane = current_active_pane.or(active_pane);
395 }
396 }
397
398 if members.is_empty() {
399 return None;
400 }
401
402 if members.len() == 1 {
403 return Some((members.remove(0), current_active_pane, items));
404 }
405
406 Some((
407 Member::Axis(PaneAxis::load(axis.0, members, flexes)),
408 current_active_pane,
409 items,
410 ))
411 }
412 SerializedPaneGroup::Pane(serialized_pane) => {
413 let pane = workspace
414 .update(cx, |workspace, cx| workspace.add_pane(cx).downgrade())
415 .log_err()?;
416 let active = serialized_pane.active;
417 let new_items = serialized_pane
418 .deserialize_to(project, &pane, workspace_id, workspace.clone(), cx)
419 .await
420 .log_err()?;
421
422 if pane.update(cx, |pane, _| pane.items_len() != 0).log_err()? {
423 let pane = pane.upgrade()?;
424 Some((
425 Member::Pane(pane.clone()),
426 active.then_some(pane),
427 new_items,
428 ))
429 } else {
430 let pane = pane.upgrade()?;
431 workspace
432 .update(cx, |workspace, cx| {
433 workspace.force_remove_pane(&pane, &None, cx)
434 })
435 .log_err()?;
436 None
437 }
438 }
439 }
440 }
441}
442
443#[derive(Debug, PartialEq, Eq, Default, Clone)]
444pub struct SerializedPane {
445 pub(crate) active: bool,
446 pub(crate) children: Vec<SerializedItem>,
447 pub(crate) pinned_count: usize,
448}
449
450impl SerializedPane {
451 pub fn new(children: Vec<SerializedItem>, active: bool, pinned_count: usize) -> Self {
452 SerializedPane {
453 children,
454 active,
455 pinned_count,
456 }
457 }
458
459 pub async fn deserialize_to(
460 &self,
461 project: &Model<Project>,
462 pane: &WeakView<Pane>,
463 workspace_id: WorkspaceId,
464 workspace: WeakView<Workspace>,
465 cx: &mut AsyncWindowContext,
466 ) -> Result<Vec<Option<Box<dyn ItemHandle>>>> {
467 let mut item_tasks = Vec::new();
468 let mut active_item_index = None;
469 let mut preview_item_index = None;
470 for (index, item) in self.children.iter().enumerate() {
471 let project = project.clone();
472 item_tasks.push(pane.update(cx, |_, cx| {
473 SerializableItemRegistry::deserialize(
474 &item.kind,
475 project,
476 workspace.clone(),
477 workspace_id,
478 item.item_id,
479 cx,
480 )
481 })?);
482 if item.active {
483 active_item_index = Some(index);
484 }
485 if item.preview {
486 preview_item_index = Some(index);
487 }
488 }
489
490 let mut items = Vec::new();
491 for item_handle in futures::future::join_all(item_tasks).await {
492 let item_handle = item_handle.log_err();
493 items.push(item_handle.clone());
494
495 if let Some(item_handle) = item_handle {
496 pane.update(cx, |pane, cx| {
497 pane.add_item(item_handle.clone(), true, true, None, cx);
498 })?;
499 }
500 }
501
502 if let Some(active_item_index) = active_item_index {
503 pane.update(cx, |pane, cx| {
504 pane.activate_item(active_item_index, false, false, cx);
505 })?;
506 }
507
508 if let Some(preview_item_index) = preview_item_index {
509 pane.update(cx, |pane, cx| {
510 if let Some(item) = pane.item_for_index(preview_item_index) {
511 pane.set_preview_item_id(Some(item.item_id()), cx);
512 }
513 })?;
514 }
515 pane.update(cx, |pane, _| {
516 pane.set_pinned_count(self.pinned_count);
517 })?;
518
519 anyhow::Ok(items)
520 }
521}
522
523pub type GroupId = i64;
524pub type PaneId = i64;
525pub type ItemId = u64;
526
527#[derive(Debug, PartialEq, Eq, Clone)]
528pub struct SerializedItem {
529 pub kind: Arc<str>,
530 pub item_id: ItemId,
531 pub active: bool,
532 pub preview: bool,
533}
534
535impl SerializedItem {
536 pub fn new(kind: impl AsRef<str>, item_id: ItemId, active: bool, preview: bool) -> Self {
537 Self {
538 kind: Arc::from(kind.as_ref()),
539 item_id,
540 active,
541 preview,
542 }
543 }
544}
545
546#[cfg(test)]
547impl Default for SerializedItem {
548 fn default() -> Self {
549 SerializedItem {
550 kind: Arc::from("Terminal"),
551 item_id: 100000,
552 active: false,
553 preview: false,
554 }
555 }
556}
557
558impl StaticColumnCount for SerializedItem {
559 fn column_count() -> usize {
560 4
561 }
562}
563impl Bind for &SerializedItem {
564 fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
565 let next_index = statement.bind(&self.kind, start_index)?;
566 let next_index = statement.bind(&self.item_id, next_index)?;
567 let next_index = statement.bind(&self.active, next_index)?;
568 statement.bind(&self.preview, next_index)
569 }
570}
571
572impl Column for SerializedItem {
573 fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
574 let (kind, next_index) = Arc::<str>::column(statement, start_index)?;
575 let (item_id, next_index) = ItemId::column(statement, next_index)?;
576 let (active, next_index) = bool::column(statement, next_index)?;
577 let (preview, next_index) = bool::column(statement, next_index)?;
578 Ok((
579 SerializedItem {
580 kind,
581 item_id,
582 active,
583 preview,
584 },
585 next_index,
586 ))
587 }
588}
589
590#[cfg(test)]
591mod tests {
592 use super::*;
593
594 #[test]
595 fn test_serialize_local_paths() {
596 let paths = vec!["b", "a", "c"];
597 let serialized = SerializedWorkspaceLocation::from_local_paths(paths);
598
599 assert_eq!(
600 serialized,
601 SerializedWorkspaceLocation::Local(
602 LocalPaths::new(vec!["a", "b", "c"]),
603 LocalPathsOrder::new(vec![1, 0, 2])
604 )
605 );
606 }
607}