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