model.rs

  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}