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().is_empty() {
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
414                    .read_with(cx, |pane, _| pane.items_len() != 0)
415                    .log_err()?
416                {
417                    let pane = pane.upgrade()?;
418                    Some((
419                        Member::Pane(pane.clone()),
420                        active.then_some(pane),
421                        new_items,
422                    ))
423                } else {
424                    let pane = pane.upgrade()?;
425                    workspace
426                        .update_in(cx, |workspace, window, cx| {
427                            workspace.force_remove_pane(&pane, &None, window, cx)
428                        })
429                        .log_err()?;
430                    None
431                }
432            }
433        }
434    }
435}
436
437#[derive(Debug, PartialEq, Eq, Default, Clone)]
438pub struct SerializedPane {
439    pub(crate) active: bool,
440    pub(crate) children: Vec<SerializedItem>,
441    pub(crate) pinned_count: usize,
442}
443
444impl SerializedPane {
445    pub fn new(children: Vec<SerializedItem>, active: bool, pinned_count: usize) -> Self {
446        SerializedPane {
447            children,
448            active,
449            pinned_count,
450        }
451    }
452
453    pub async fn deserialize_to(
454        &self,
455        project: &Entity<Project>,
456        pane: &WeakEntity<Pane>,
457        workspace_id: WorkspaceId,
458        workspace: WeakEntity<Workspace>,
459        cx: &mut AsyncWindowContext,
460    ) -> Result<Vec<Option<Box<dyn ItemHandle>>>> {
461        let mut item_tasks = Vec::new();
462        let mut active_item_index = None;
463        let mut preview_item_index = None;
464        for (index, item) in self.children.iter().enumerate() {
465            let project = project.clone();
466            item_tasks.push(pane.update_in(cx, |_, window, cx| {
467                SerializableItemRegistry::deserialize(
468                    &item.kind,
469                    project,
470                    workspace.clone(),
471                    workspace_id,
472                    item.item_id,
473                    window,
474                    cx,
475                )
476            })?);
477            if item.active {
478                active_item_index = Some(index);
479            }
480            if item.preview {
481                preview_item_index = Some(index);
482            }
483        }
484
485        let mut items = Vec::new();
486        for item_handle in futures::future::join_all(item_tasks).await {
487            let item_handle = item_handle.log_err();
488            items.push(item_handle.clone());
489
490            if let Some(item_handle) = item_handle {
491                pane.update_in(cx, |pane, window, cx| {
492                    pane.add_item(item_handle.clone(), true, true, None, window, cx);
493                })?;
494            }
495        }
496
497        if let Some(active_item_index) = active_item_index {
498            pane.update_in(cx, |pane, window, cx| {
499                pane.activate_item(active_item_index, false, false, window, cx);
500            })?;
501        }
502
503        if let Some(preview_item_index) = preview_item_index {
504            pane.update(cx, |pane, cx| {
505                if let Some(item) = pane.item_for_index(preview_item_index) {
506                    pane.set_preview_item_id(Some(item.item_id()), cx);
507                }
508            })?;
509        }
510        pane.update(cx, |pane, _| {
511            pane.set_pinned_count(self.pinned_count.min(items.len()));
512        })?;
513
514        anyhow::Ok(items)
515    }
516}
517
518pub type GroupId = i64;
519pub type PaneId = i64;
520pub type ItemId = u64;
521
522#[derive(Debug, PartialEq, Eq, Clone)]
523pub struct SerializedItem {
524    pub kind: Arc<str>,
525    pub item_id: ItemId,
526    pub active: bool,
527    pub preview: bool,
528}
529
530impl SerializedItem {
531    pub fn new(kind: impl AsRef<str>, item_id: ItemId, active: bool, preview: bool) -> Self {
532        Self {
533            kind: Arc::from(kind.as_ref()),
534            item_id,
535            active,
536            preview,
537        }
538    }
539}
540
541#[cfg(test)]
542impl Default for SerializedItem {
543    fn default() -> Self {
544        SerializedItem {
545            kind: Arc::from("Terminal"),
546            item_id: 100000,
547            active: false,
548            preview: false,
549        }
550    }
551}
552
553impl StaticColumnCount for SerializedItem {
554    fn column_count() -> usize {
555        4
556    }
557}
558impl Bind for &SerializedItem {
559    fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
560        let next_index = statement.bind(&self.kind, start_index)?;
561        let next_index = statement.bind(&self.item_id, next_index)?;
562        let next_index = statement.bind(&self.active, next_index)?;
563        statement.bind(&self.preview, next_index)
564    }
565}
566
567impl Column for SerializedItem {
568    fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
569        let (kind, next_index) = Arc::<str>::column(statement, start_index)?;
570        let (item_id, next_index) = ItemId::column(statement, next_index)?;
571        let (active, next_index) = bool::column(statement, next_index)?;
572        let (preview, next_index) = bool::column(statement, next_index)?;
573        Ok((
574            SerializedItem {
575                kind,
576                item_id,
577                active,
578                preview,
579            },
580            next_index,
581        ))
582    }
583}
584
585#[cfg(test)]
586mod tests {
587    use super::*;
588
589    #[test]
590    fn test_serialize_local_paths() {
591        let paths = vec!["b", "a", "c"];
592        let serialized = SerializedWorkspaceLocation::from_local_paths(paths);
593
594        assert_eq!(
595            serialized,
596            SerializedWorkspaceLocation::Local(
597                LocalPaths::new(vec!["a", "b", "c"]),
598                LocalPathsOrder::new(vec![1, 0, 2])
599            )
600        );
601    }
602
603    #[test]
604    fn test_sorted_paths() {
605        let paths = vec!["b", "a", "c"];
606        let serialized = SerializedWorkspaceLocation::from_local_paths(paths);
607        assert_eq!(
608            serialized.sorted_paths(),
609            Arc::new(vec![
610                PathBuf::from("b"),
611                PathBuf::from("a"),
612                PathBuf::from("c"),
613            ])
614        );
615
616        let paths = Arc::new(vec![
617            PathBuf::from("a"),
618            PathBuf::from("b"),
619            PathBuf::from("c"),
620        ]);
621        let order = vec![2, 0, 1];
622        let serialized =
623            SerializedWorkspaceLocation::Local(LocalPaths(paths), LocalPathsOrder(order));
624        assert_eq!(
625            serialized.sorted_paths(),
626            Arc::new(vec![
627                PathBuf::from("b"),
628                PathBuf::from("c"),
629                PathBuf::from("a"),
630            ])
631        );
632
633        let paths = Arc::new(vec![
634            PathBuf::from("a"),
635            PathBuf::from("b"),
636            PathBuf::from("c"),
637        ]);
638        let order = vec![];
639        let serialized =
640            SerializedWorkspaceLocation::Local(LocalPaths(paths.clone()), LocalPathsOrder(order));
641        assert_eq!(serialized.sorted_paths(), paths);
642
643        let urls = ["/a", "/b", "/c"];
644        let serialized = SerializedWorkspaceLocation::Ssh(SerializedSshProject {
645            id: SshProjectId(0),
646            host: "host".to_string(),
647            port: Some(22),
648            paths: urls.iter().map(|s| s.to_string()).collect(),
649            user: Some("user".to_string()),
650        });
651        assert_eq!(
652            serialized.sorted_paths(),
653            Arc::new(
654                urls.iter()
655                    .map(|p| PathBuf::from(format!("user@host:22{}", p)))
656                    .collect()
657            )
658        );
659    }
660}