model.rs

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