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