model.rs

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