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