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 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}