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