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