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