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