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