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