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