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