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