model.rs

  1use super::{SerializedAxis, SerializedWindowBounds};
  2use crate::{
  3    Member, Pane, PaneAxis, SerializableItemRegistry, Workspace, WorkspaceId, item::ItemHandle,
  4    multi_workspace::SerializedProjectGroupState, path_list::PathList,
  5};
  6use anyhow::{Context, Result};
  7use async_recursion::async_recursion;
  8use collections::IndexSet;
  9use db::sqlez::{
 10    bindable::{Bind, Column, StaticColumnCount},
 11    statement::Statement,
 12};
 13use gpui::{AsyncWindowContext, Entity, WeakEntity, WindowId};
 14
 15use language::{Toolchain, ToolchainScope};
 16use project::{
 17    Project, ProjectGroupKey, bookmark_store::SerializedBookmark,
 18    debugger::breakpoint_store::SourceBreakpoint,
 19};
 20use remote::RemoteConnectionOptions;
 21use serde::{Deserialize, Serialize};
 22use std::{
 23    collections::BTreeMap,
 24    path::{Path, PathBuf},
 25    sync::Arc,
 26};
 27use util::{ResultExt, path_list::SerializedPathList};
 28use uuid::Uuid;
 29
 30#[derive(
 31    Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, serde::Serialize, serde::Deserialize,
 32)]
 33pub(crate) struct RemoteConnectionId(pub u64);
 34
 35#[derive(Debug, PartialEq, Eq, Clone, Copy)]
 36pub(crate) enum RemoteConnectionKind {
 37    Ssh,
 38    Wsl,
 39    Docker,
 40}
 41
 42#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
 43pub enum SerializedWorkspaceLocation {
 44    Local,
 45    Remote(RemoteConnectionOptions),
 46}
 47
 48impl SerializedWorkspaceLocation {
 49    /// Get sorted paths
 50    pub fn sorted_paths(&self) -> Arc<Vec<PathBuf>> {
 51        unimplemented!()
 52    }
 53}
 54
 55/// A workspace entry from a previous session, containing all the info needed
 56/// to restore it including which window it belonged to (for MultiWorkspace grouping).
 57#[derive(Debug, PartialEq, Clone)]
 58pub struct SessionWorkspace {
 59    pub workspace_id: WorkspaceId,
 60    pub location: SerializedWorkspaceLocation,
 61    pub paths: PathList,
 62    pub window_id: Option<WindowId>,
 63}
 64
 65#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
 66pub struct SerializedProjectGroup {
 67    pub path_list: SerializedPathList,
 68    pub(crate) location: SerializedWorkspaceLocation,
 69    #[serde(default = "default_expanded")]
 70    pub expanded: bool,
 71}
 72
 73fn default_expanded() -> bool {
 74    true
 75}
 76
 77impl SerializedProjectGroup {
 78    pub fn from_group(key: &ProjectGroupKey, expanded: bool) -> Self {
 79        Self {
 80            path_list: key.path_list().serialize(),
 81            location: match key.host() {
 82                Some(host) => SerializedWorkspaceLocation::Remote(host),
 83                None => SerializedWorkspaceLocation::Local,
 84            },
 85            expanded,
 86        }
 87    }
 88
 89    pub fn into_restored_state(self) -> SerializedProjectGroupState {
 90        let path_list = PathList::deserialize(&self.path_list);
 91        let host = match self.location {
 92            SerializedWorkspaceLocation::Local => None,
 93            SerializedWorkspaceLocation::Remote(opts) => Some(opts),
 94        };
 95        SerializedProjectGroupState {
 96            key: ProjectGroupKey::new(host, path_list),
 97            expanded: self.expanded,
 98        }
 99    }
100}
101
102impl From<SerializedProjectGroup> for ProjectGroupKey {
103    fn from(value: SerializedProjectGroup) -> Self {
104        value.into_restored_state().key
105    }
106}
107
108/// Per-window state for a MultiWorkspace, persisted to KVP.
109#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
110pub struct MultiWorkspaceState {
111    pub active_workspace_id: Option<WorkspaceId>,
112    pub sidebar_open: bool,
113    #[serde(alias = "project_group_keys")]
114    pub project_groups: Vec<SerializedProjectGroup>,
115    #[serde(default)]
116    pub sidebar_state: Option<String>,
117}
118
119/// The serialized state of a single MultiWorkspace window from a previous session:
120/// the active workspace to restore plus window-level state (project group keys,
121/// sidebar).
122#[derive(Debug, Clone)]
123pub struct SerializedMultiWorkspace {
124    pub active_workspace: SessionWorkspace,
125    pub state: MultiWorkspaceState,
126}
127
128#[derive(Debug, PartialEq, Clone)]
129pub(crate) struct SerializedWorkspace {
130    pub(crate) id: WorkspaceId,
131    pub(crate) location: SerializedWorkspaceLocation,
132    pub(crate) paths: PathList,
133    pub(crate) center_group: SerializedPaneGroup,
134    pub(crate) window_bounds: Option<SerializedWindowBounds>,
135    pub(crate) centered_layout: bool,
136    pub(crate) display: Option<Uuid>,
137    pub(crate) docks: DockStructure,
138    pub(crate) session_id: Option<String>,
139    pub(crate) bookmarks: BTreeMap<Arc<Path>, Vec<SerializedBookmark>>,
140    pub(crate) breakpoints: BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
141    pub(crate) user_toolchains: BTreeMap<ToolchainScope, IndexSet<Toolchain>>,
142    pub(crate) window_id: Option<u64>,
143}
144
145#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize)]
146pub struct DockStructure {
147    pub left: DockData,
148    pub right: DockData,
149    pub bottom: DockData,
150}
151
152impl RemoteConnectionKind {
153    pub(crate) fn serialize(&self) -> &'static str {
154        match self {
155            RemoteConnectionKind::Ssh => "ssh",
156            RemoteConnectionKind::Wsl => "wsl",
157            RemoteConnectionKind::Docker => "docker",
158        }
159    }
160
161    pub(crate) fn deserialize(text: &str) -> Option<Self> {
162        match text {
163            "ssh" => Some(Self::Ssh),
164            "wsl" => Some(Self::Wsl),
165            "docker" => Some(Self::Docker),
166            _ => None,
167        }
168    }
169}
170
171impl Column for DockStructure {
172    fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
173        let (left, next_index) = DockData::column(statement, start_index)?;
174        let (right, next_index) = DockData::column(statement, next_index)?;
175        let (bottom, next_index) = DockData::column(statement, next_index)?;
176        Ok((
177            DockStructure {
178                left,
179                right,
180                bottom,
181            },
182            next_index,
183        ))
184    }
185}
186
187impl Bind for DockStructure {
188    fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
189        let next_index = statement.bind(&self.left, start_index)?;
190        let next_index = statement.bind(&self.right, next_index)?;
191        statement.bind(&self.bottom, next_index)
192    }
193}
194
195#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize)]
196pub struct DockData {
197    pub visible: bool,
198    pub active_panel: Option<String>,
199    pub zoom: bool,
200}
201
202impl Column for DockData {
203    fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
204        let (visible, next_index) = Option::<bool>::column(statement, start_index)?;
205        let (active_panel, next_index) = Option::<String>::column(statement, next_index)?;
206        let (zoom, next_index) = Option::<bool>::column(statement, next_index)?;
207        Ok((
208            DockData {
209                visible: visible.unwrap_or(false),
210                active_panel,
211                zoom: zoom.unwrap_or(false),
212            },
213            next_index,
214        ))
215    }
216}
217
218impl Bind for DockData {
219    fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
220        let next_index = statement.bind(&self.visible, start_index)?;
221        let next_index = statement.bind(&self.active_panel, next_index)?;
222        statement.bind(&self.zoom, next_index)
223    }
224}
225
226#[derive(Debug, PartialEq, Clone)]
227pub(crate) enum SerializedPaneGroup {
228    Group {
229        axis: SerializedAxis,
230        flexes: Option<Vec<f32>>,
231        children: Vec<SerializedPaneGroup>,
232    },
233    Pane(SerializedPane),
234}
235
236#[cfg(test)]
237impl Default for SerializedPaneGroup {
238    fn default() -> Self {
239        Self::Pane(SerializedPane {
240            children: vec![SerializedItem::default()],
241            active: false,
242            pinned_count: 0,
243        })
244    }
245}
246
247impl SerializedPaneGroup {
248    #[async_recursion(?Send)]
249    pub(crate) async fn deserialize(
250        self,
251        project: &Entity<Project>,
252        workspace_id: WorkspaceId,
253        workspace: WeakEntity<Workspace>,
254        cx: &mut AsyncWindowContext,
255    ) -> Option<(
256        Member,
257        Option<Entity<Pane>>,
258        Vec<Option<Box<dyn ItemHandle>>>,
259    )> {
260        match self {
261            SerializedPaneGroup::Group {
262                axis,
263                children,
264                flexes,
265            } => {
266                let mut current_active_pane = None;
267                let mut members = Vec::new();
268                let mut items = Vec::new();
269                for child in children {
270                    if let Some((new_member, active_pane, new_items)) = child
271                        .deserialize(project, workspace_id, workspace.clone(), cx)
272                        .await
273                    {
274                        members.push(new_member);
275                        items.extend(new_items);
276                        current_active_pane = current_active_pane.or(active_pane);
277                    }
278                }
279
280                if members.is_empty() {
281                    return None;
282                }
283
284                if members.len() == 1 {
285                    return Some((members.remove(0), current_active_pane, items));
286                }
287
288                Some((
289                    Member::Axis(PaneAxis::load(axis.0, members, flexes)),
290                    current_active_pane,
291                    items,
292                ))
293            }
294            SerializedPaneGroup::Pane(serialized_pane) => {
295                let pane = workspace
296                    .update_in(cx, |workspace, window, cx| {
297                        workspace.add_pane(window, cx).downgrade()
298                    })
299                    .log_err()?;
300                let active = serialized_pane.active;
301                let new_items = serialized_pane
302                    .deserialize_to(project, &pane, workspace_id, workspace.clone(), cx)
303                    .await
304                    .context("Could not deserialize pane)")
305                    .log_err()?;
306
307                if pane
308                    .read_with(cx, |pane, _| pane.items_len() != 0)
309                    .log_err()?
310                {
311                    let pane = pane.upgrade()?;
312                    Some((
313                        Member::Pane(pane.clone()),
314                        active.then_some(pane),
315                        new_items,
316                    ))
317                } else {
318                    let pane = pane.upgrade()?;
319                    workspace
320                        .update_in(cx, |workspace, window, cx| {
321                            workspace.force_remove_pane(&pane, &None, window, cx)
322                        })
323                        .log_err()?;
324                    None
325                }
326            }
327        }
328    }
329}
330
331#[derive(Debug, PartialEq, Eq, Default, Clone)]
332pub struct SerializedPane {
333    pub(crate) active: bool,
334    pub(crate) children: Vec<SerializedItem>,
335    pub(crate) pinned_count: usize,
336}
337
338impl SerializedPane {
339    pub fn new(children: Vec<SerializedItem>, active: bool, pinned_count: usize) -> Self {
340        SerializedPane {
341            children,
342            active,
343            pinned_count,
344        }
345    }
346
347    pub async fn deserialize_to(
348        &self,
349        project: &Entity<Project>,
350        pane: &WeakEntity<Pane>,
351        workspace_id: WorkspaceId,
352        workspace: WeakEntity<Workspace>,
353        cx: &mut AsyncWindowContext,
354    ) -> Result<Vec<Option<Box<dyn ItemHandle>>>> {
355        let mut item_tasks = Vec::new();
356        let mut active_item_index = None;
357        let mut preview_item_index = None;
358        for (index, item) in self.children.iter().enumerate() {
359            let project = project.clone();
360            item_tasks.push(pane.update_in(cx, |_, window, cx| {
361                SerializableItemRegistry::deserialize(
362                    &item.kind,
363                    project,
364                    workspace.clone(),
365                    workspace_id,
366                    item.item_id,
367                    window,
368                    cx,
369                )
370            })?);
371            if item.active {
372                active_item_index = Some(index);
373            }
374            if item.preview {
375                preview_item_index = Some(index);
376            }
377        }
378
379        let mut items = Vec::new();
380        for item_handle in futures::future::join_all(item_tasks).await {
381            let item_handle = item_handle.log_err();
382            items.push(item_handle.clone());
383
384            if let Some(item_handle) = item_handle {
385                pane.update_in(cx, |pane, window, cx| {
386                    pane.add_item(item_handle.clone(), true, true, None, window, cx);
387                })?;
388            }
389        }
390
391        if let Some(active_item_index) = active_item_index {
392            pane.update_in(cx, |pane, window, cx| {
393                pane.activate_item(active_item_index, false, false, window, cx);
394            })?;
395        }
396
397        if let Some(preview_item_index) = preview_item_index {
398            pane.update(cx, |pane, cx| {
399                if let Some(item) = pane.item_for_index(preview_item_index) {
400                    pane.set_preview_item_id(Some(item.item_id()), cx);
401                }
402            })?;
403        }
404        pane.update(cx, |pane, _| {
405            pane.set_pinned_count(self.pinned_count.min(items.len()));
406        })?;
407
408        anyhow::Ok(items)
409    }
410}
411
412pub type GroupId = i64;
413pub type PaneId = i64;
414pub type ItemId = u64;
415
416#[derive(Debug, PartialEq, Eq, Clone)]
417pub struct SerializedItem {
418    pub kind: Arc<str>,
419    pub item_id: ItemId,
420    pub active: bool,
421    pub preview: bool,
422}
423
424impl SerializedItem {
425    pub fn new(kind: impl AsRef<str>, item_id: ItemId, active: bool, preview: bool) -> Self {
426        Self {
427            kind: Arc::from(kind.as_ref()),
428            item_id,
429            active,
430            preview,
431        }
432    }
433}
434
435#[cfg(test)]
436impl Default for SerializedItem {
437    fn default() -> Self {
438        SerializedItem {
439            kind: Arc::from("Terminal"),
440            item_id: 100000,
441            active: false,
442            preview: false,
443        }
444    }
445}
446
447impl StaticColumnCount for SerializedItem {
448    fn column_count() -> usize {
449        4
450    }
451}
452impl Bind for &SerializedItem {
453    fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
454        let next_index = statement.bind(&self.kind, start_index)?;
455        let next_index = statement.bind(&self.item_id, next_index)?;
456        let next_index = statement.bind(&self.active, next_index)?;
457        statement.bind(&self.preview, next_index)
458    }
459}
460
461impl Column for SerializedItem {
462    fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
463        let (kind, next_index) = Arc::<str>::column(statement, start_index)?;
464        let (item_id, next_index) = ItemId::column(statement, next_index)?;
465        let (active, next_index) = bool::column(statement, next_index)?;
466        let (preview, next_index) = bool::column(statement, next_index)?;
467        Ok((
468            SerializedItem {
469                kind,
470                item_id,
471                active,
472                preview,
473            },
474            next_index,
475        ))
476    }
477}