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