1use super::{SerializedAxis, SerializedWindowBounds};
2use crate::{
3 item::ItemHandle, Member, Pane, PaneAxis, SerializableItemRegistry, Workspace, WorkspaceId,
4};
5use anyhow::{Context, Result};
6use async_recursion::async_recursion;
7use client::DevServerProjectId;
8use db::sqlez::{
9 bindable::{Bind, Column, StaticColumnCount},
10 statement::Statement,
11};
12use gpui::{AsyncWindowContext, Model, View, WeakView};
13use project::Project;
14use serde::{Deserialize, Serialize};
15use std::{
16 path::{Path, PathBuf},
17 sync::Arc,
18};
19use ui::SharedString;
20use util::ResultExt;
21use uuid::Uuid;
22
23#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
24pub struct SerializedDevServerProject {
25 pub id: DevServerProjectId,
26 pub dev_server_name: String,
27 pub paths: Vec<SharedString>,
28}
29
30#[derive(Debug, PartialEq, Clone)]
31pub struct LocalPaths(Arc<Vec<PathBuf>>);
32
33impl LocalPaths {
34 pub fn new<P: AsRef<Path>>(paths: impl IntoIterator<Item = P>) -> Self {
35 let mut paths: Vec<PathBuf> = paths
36 .into_iter()
37 .map(|p| p.as_ref().to_path_buf())
38 .collect();
39 // Ensure all future `zed workspace1 workspace2` and `zed workspace2 workspace1` calls are using the same workspace.
40 // The actual workspace order is stored in the `LocalPathsOrder` struct.
41 paths.sort();
42 Self(Arc::new(paths))
43 }
44
45 pub fn paths(&self) -> &Arc<Vec<PathBuf>> {
46 &self.0
47 }
48}
49
50impl StaticColumnCount for LocalPaths {}
51impl Bind for &LocalPaths {
52 fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
53 statement.bind(&bincode::serialize(&self.0)?, start_index)
54 }
55}
56
57impl Column for LocalPaths {
58 fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
59 let path_blob = statement.column_blob(start_index)?;
60 let paths: Arc<Vec<PathBuf>> = if path_blob.is_empty() {
61 Default::default()
62 } else {
63 bincode::deserialize(path_blob).context("Bincode deserialization of paths failed")?
64 };
65
66 Ok((Self(paths), start_index + 1))
67 }
68}
69
70#[derive(Debug, PartialEq, Clone)]
71pub struct LocalPathsOrder(Vec<usize>);
72
73impl LocalPathsOrder {
74 pub fn new(order: impl IntoIterator<Item = usize>) -> Self {
75 Self(order.into_iter().collect())
76 }
77
78 pub fn order(&self) -> &[usize] {
79 self.0.as_slice()
80 }
81
82 pub fn default_for_paths(paths: &LocalPaths) -> Self {
83 Self::new(0..paths.0.len())
84 }
85}
86
87impl StaticColumnCount for LocalPathsOrder {}
88impl Bind for &LocalPathsOrder {
89 fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
90 statement.bind(&bincode::serialize(&self.0)?, start_index)
91 }
92}
93
94impl Column for LocalPathsOrder {
95 fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
96 let order_blob = statement.column_blob(start_index)?;
97 let order = if order_blob.is_empty() {
98 Vec::new()
99 } else {
100 bincode::deserialize(order_blob).context("deserializing workspace root order")?
101 };
102
103 Ok((Self(order), start_index + 1))
104 }
105}
106
107impl From<SerializedDevServerProject> for SerializedWorkspaceLocation {
108 fn from(dev_server_project: SerializedDevServerProject) -> Self {
109 Self::DevServer(dev_server_project)
110 }
111}
112
113impl StaticColumnCount for SerializedDevServerProject {}
114impl Bind for &SerializedDevServerProject {
115 fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
116 let next_index = statement.bind(&self.id.0, start_index)?;
117 let next_index = statement.bind(&self.dev_server_name, next_index)?;
118 let paths = serde_json::to_string(&self.paths)?;
119 statement.bind(&paths, next_index)
120 }
121}
122
123impl Column for SerializedDevServerProject {
124 fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
125 let id = statement.column_int64(start_index)?;
126 let dev_server_name = statement.column_text(start_index + 1)?.to_string();
127 let paths = statement.column_text(start_index + 2)?.to_string();
128 let paths: Vec<SharedString> = if paths.starts_with('[') {
129 serde_json::from_str(&paths).context("JSON deserialization of paths failed")?
130 } else {
131 vec![paths.into()]
132 };
133
134 Ok((
135 Self {
136 id: DevServerProjectId(id as u64),
137 dev_server_name,
138 paths,
139 },
140 start_index + 3,
141 ))
142 }
143}
144
145#[derive(Debug, PartialEq, Clone)]
146pub enum SerializedWorkspaceLocation {
147 Local(LocalPaths, LocalPathsOrder),
148 DevServer(SerializedDevServerProject),
149}
150
151impl SerializedWorkspaceLocation {
152 /// Create a new `SerializedWorkspaceLocation` from a list of local paths.
153 ///
154 /// The paths will be sorted and the order will be stored in the `LocalPathsOrder` struct.
155 ///
156 /// # Examples
157 ///
158 /// ```
159 /// use std::path::Path;
160 /// use zed_workspace::SerializedWorkspaceLocation;
161 ///
162 /// let location = SerializedWorkspaceLocation::from_local_paths(vec![
163 /// Path::new("path/to/workspace1"),
164 /// Path::new("path/to/workspace2"),
165 /// ]);
166 /// assert_eq!(location, SerializedWorkspaceLocation::Local(
167 /// LocalPaths::new(vec![
168 /// Path::new("path/to/workspace1"),
169 /// Path::new("path/to/workspace2"),
170 /// ]),
171 /// LocalPathsOrder::new(vec![0, 1]),
172 /// ));
173 /// ```
174 ///
175 /// ```
176 /// use std::path::Path;
177 /// use zed_workspace::SerializedWorkspaceLocation;
178 ///
179 /// let location = SerializedWorkspaceLocation::from_local_paths(vec![
180 /// Path::new("path/to/workspace2"),
181 /// Path::new("path/to/workspace1"),
182 /// ]);
183 ///
184 /// assert_eq!(location, SerializedWorkspaceLocation::Local(
185 /// LocalPaths::new(vec![
186 /// Path::new("path/to/workspace1"),
187 /// Path::new("path/to/workspace2"),
188 /// ]),
189 /// LocalPathsOrder::new(vec![1, 0]),
190 /// ));
191 /// ```
192 pub fn from_local_paths<P: AsRef<Path>>(paths: impl IntoIterator<Item = P>) -> Self {
193 let mut indexed_paths: Vec<_> = paths
194 .into_iter()
195 .map(|p| p.as_ref().to_path_buf())
196 .enumerate()
197 .collect();
198
199 indexed_paths.sort_by(|(_, a), (_, b)| a.cmp(b));
200
201 let sorted_paths: Vec<_> = indexed_paths.iter().map(|(_, path)| path.clone()).collect();
202 let order: Vec<_> = indexed_paths.iter().map(|(index, _)| *index).collect();
203
204 Self::Local(LocalPaths::new(sorted_paths), LocalPathsOrder::new(order))
205 }
206}
207
208#[derive(Debug, PartialEq, Clone)]
209pub(crate) struct SerializedWorkspace {
210 pub(crate) id: WorkspaceId,
211 pub(crate) location: SerializedWorkspaceLocation,
212 pub(crate) center_group: SerializedPaneGroup,
213 pub(crate) window_bounds: Option<SerializedWindowBounds>,
214 pub(crate) centered_layout: bool,
215 pub(crate) display: Option<Uuid>,
216 pub(crate) docks: DockStructure,
217}
218
219#[derive(Debug, PartialEq, Clone, Default)]
220pub struct DockStructure {
221 pub(crate) left: DockData,
222 pub(crate) right: DockData,
223 pub(crate) bottom: DockData,
224}
225
226impl Column for DockStructure {
227 fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
228 let (left, next_index) = DockData::column(statement, start_index)?;
229 let (right, next_index) = DockData::column(statement, next_index)?;
230 let (bottom, next_index) = DockData::column(statement, next_index)?;
231 Ok((
232 DockStructure {
233 left,
234 right,
235 bottom,
236 },
237 next_index,
238 ))
239 }
240}
241
242impl Bind for DockStructure {
243 fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
244 let next_index = statement.bind(&self.left, start_index)?;
245 let next_index = statement.bind(&self.right, next_index)?;
246 statement.bind(&self.bottom, next_index)
247 }
248}
249
250#[derive(Debug, PartialEq, Clone, Default)]
251pub struct DockData {
252 pub(crate) visible: bool,
253 pub(crate) active_panel: Option<String>,
254 pub(crate) zoom: bool,
255}
256
257impl Column for DockData {
258 fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
259 let (visible, next_index) = Option::<bool>::column(statement, start_index)?;
260 let (active_panel, next_index) = Option::<String>::column(statement, next_index)?;
261 let (zoom, next_index) = Option::<bool>::column(statement, next_index)?;
262 Ok((
263 DockData {
264 visible: visible.unwrap_or(false),
265 active_panel,
266 zoom: zoom.unwrap_or(false),
267 },
268 next_index,
269 ))
270 }
271}
272
273impl Bind for DockData {
274 fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
275 let next_index = statement.bind(&self.visible, start_index)?;
276 let next_index = statement.bind(&self.active_panel, next_index)?;
277 statement.bind(&self.zoom, next_index)
278 }
279}
280
281#[derive(Debug, PartialEq, Clone)]
282pub(crate) enum SerializedPaneGroup {
283 Group {
284 axis: SerializedAxis,
285 flexes: Option<Vec<f32>>,
286 children: Vec<SerializedPaneGroup>,
287 },
288 Pane(SerializedPane),
289}
290
291#[cfg(test)]
292impl Default for SerializedPaneGroup {
293 fn default() -> Self {
294 Self::Pane(SerializedPane {
295 children: vec![SerializedItem::default()],
296 active: false,
297 })
298 }
299}
300
301impl SerializedPaneGroup {
302 #[async_recursion(?Send)]
303 pub(crate) async fn deserialize(
304 self,
305 project: &Model<Project>,
306 workspace_id: WorkspaceId,
307 workspace: WeakView<Workspace>,
308 cx: &mut AsyncWindowContext,
309 ) -> Option<(Member, Option<View<Pane>>, Vec<Option<Box<dyn ItemHandle>>>)> {
310 match self {
311 SerializedPaneGroup::Group {
312 axis,
313 children,
314 flexes,
315 } => {
316 let mut current_active_pane = None;
317 let mut members = Vec::new();
318 let mut items = Vec::new();
319 for child in children {
320 if let Some((new_member, active_pane, new_items)) = child
321 .deserialize(project, workspace_id, workspace.clone(), cx)
322 .await
323 {
324 members.push(new_member);
325 items.extend(new_items);
326 current_active_pane = current_active_pane.or(active_pane);
327 }
328 }
329
330 if members.is_empty() {
331 return None;
332 }
333
334 if members.len() == 1 {
335 return Some((members.remove(0), current_active_pane, items));
336 }
337
338 Some((
339 Member::Axis(PaneAxis::load(axis.0, members, flexes)),
340 current_active_pane,
341 items,
342 ))
343 }
344 SerializedPaneGroup::Pane(serialized_pane) => {
345 let pane = workspace
346 .update(cx, |workspace, cx| workspace.add_pane(cx).downgrade())
347 .log_err()?;
348 let active = serialized_pane.active;
349 let new_items = serialized_pane
350 .deserialize_to(project, &pane, workspace_id, workspace.clone(), cx)
351 .await
352 .log_err()?;
353
354 if pane.update(cx, |pane, _| pane.items_len() != 0).log_err()? {
355 let pane = pane.upgrade()?;
356 Some((Member::Pane(pane.clone()), active.then(|| pane), new_items))
357 } else {
358 let pane = pane.upgrade()?;
359 workspace
360 .update(cx, |workspace, cx| workspace.force_remove_pane(&pane, cx))
361 .log_err()?;
362 None
363 }
364 }
365 }
366 }
367}
368
369#[derive(Debug, PartialEq, Eq, Default, Clone)]
370pub struct SerializedPane {
371 pub(crate) active: bool,
372 pub(crate) children: Vec<SerializedItem>,
373}
374
375impl SerializedPane {
376 pub fn new(children: Vec<SerializedItem>, active: bool) -> Self {
377 SerializedPane { children, active }
378 }
379
380 pub async fn deserialize_to(
381 &self,
382 project: &Model<Project>,
383 pane: &WeakView<Pane>,
384 workspace_id: WorkspaceId,
385 workspace: WeakView<Workspace>,
386 cx: &mut AsyncWindowContext,
387 ) -> Result<Vec<Option<Box<dyn ItemHandle>>>> {
388 let mut item_tasks = Vec::new();
389 let mut active_item_index = None;
390 let mut preview_item_index = None;
391 for (index, item) in self.children.iter().enumerate() {
392 let project = project.clone();
393 item_tasks.push(pane.update(cx, |_, cx| {
394 SerializableItemRegistry::deserialize(
395 &item.kind,
396 project,
397 workspace.clone(),
398 workspace_id,
399 item.item_id,
400 cx,
401 )
402 })?);
403 if item.active {
404 active_item_index = Some(index);
405 }
406 if item.preview {
407 preview_item_index = Some(index);
408 }
409 }
410
411 let mut items = Vec::new();
412 for item_handle in futures::future::join_all(item_tasks).await {
413 let item_handle = item_handle.log_err();
414 items.push(item_handle.clone());
415
416 if let Some(item_handle) = item_handle {
417 pane.update(cx, |pane, cx| {
418 pane.add_item(item_handle.clone(), true, true, None, cx);
419 })?;
420 }
421 }
422
423 if let Some(active_item_index) = active_item_index {
424 pane.update(cx, |pane, cx| {
425 pane.activate_item(active_item_index, false, false, cx);
426 })?;
427 }
428
429 if let Some(preview_item_index) = preview_item_index {
430 pane.update(cx, |pane, cx| {
431 if let Some(item) = pane.item_for_index(preview_item_index) {
432 pane.set_preview_item_id(Some(item.item_id()), cx);
433 }
434 })?;
435 }
436
437 anyhow::Ok(items)
438 }
439}
440
441pub type GroupId = i64;
442pub type PaneId = i64;
443pub type ItemId = u64;
444
445#[derive(Debug, PartialEq, Eq, Clone)]
446pub struct SerializedItem {
447 pub kind: Arc<str>,
448 pub item_id: ItemId,
449 pub active: bool,
450 pub preview: bool,
451}
452
453impl SerializedItem {
454 pub fn new(kind: impl AsRef<str>, item_id: ItemId, active: bool, preview: bool) -> Self {
455 Self {
456 kind: Arc::from(kind.as_ref()),
457 item_id,
458 active,
459 preview,
460 }
461 }
462}
463
464#[cfg(test)]
465impl Default for SerializedItem {
466 fn default() -> Self {
467 SerializedItem {
468 kind: Arc::from("Terminal"),
469 item_id: 100000,
470 active: false,
471 preview: false,
472 }
473 }
474}
475
476impl StaticColumnCount for SerializedItem {
477 fn column_count() -> usize {
478 4
479 }
480}
481impl Bind for &SerializedItem {
482 fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
483 let next_index = statement.bind(&self.kind, start_index)?;
484 let next_index = statement.bind(&self.item_id, next_index)?;
485 let next_index = statement.bind(&self.active, next_index)?;
486 statement.bind(&self.preview, next_index)
487 }
488}
489
490impl Column for SerializedItem {
491 fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
492 let (kind, next_index) = Arc::<str>::column(statement, start_index)?;
493 let (item_id, next_index) = ItemId::column(statement, next_index)?;
494 let (active, next_index) = bool::column(statement, next_index)?;
495 let (preview, next_index) = bool::column(statement, next_index)?;
496 Ok((
497 SerializedItem {
498 kind,
499 item_id,
500 active,
501 preview,
502 },
503 next_index,
504 ))
505 }
506}
507
508#[cfg(test)]
509mod tests {
510 use super::*;
511
512 #[test]
513 fn test_serialize_local_paths() {
514 let paths = vec!["b", "a", "c"];
515 let serialized = SerializedWorkspaceLocation::from_local_paths(paths);
516
517 assert_eq!(
518 serialized,
519 SerializedWorkspaceLocation::Local(
520 LocalPaths::new(vec!["a", "b", "c"]),
521 LocalPathsOrder::new(vec![1, 0, 2])
522 )
523 );
524 }
525}