1use std::{
2 path::{Path, PathBuf},
3 sync::Arc,
4};
5
6use anyhow::Result;
7
8use async_recursion::async_recursion;
9use gpui::{AsyncAppContext, Axis, ModelHandle, Task, ViewHandle};
10
11use db::sqlez::{
12 bindable::{Bind, Column},
13 statement::Statement,
14};
15use project::Project;
16use settings::DockAnchor;
17use util::ResultExt;
18
19use crate::{dock::DockPosition, ItemDeserializers, Member, Pane, PaneAxis, Workspace};
20
21#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct WorkspaceId(Arc<Vec<PathBuf>>);
23
24impl WorkspaceId {
25 pub fn paths(&self) -> Arc<Vec<PathBuf>> {
26 self.0.clone()
27 }
28}
29
30impl<P: AsRef<Path>, T: IntoIterator<Item = P>> From<T> for WorkspaceId {
31 fn from(iterator: T) -> Self {
32 let mut roots = iterator
33 .into_iter()
34 .map(|p| p.as_ref().to_path_buf())
35 .collect::<Vec<_>>();
36 roots.sort();
37 Self(Arc::new(roots))
38 }
39}
40
41impl Bind for &WorkspaceId {
42 fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
43 bincode::serialize(&self.0)
44 .expect("Bincode serialization of paths should not fail")
45 .bind(statement, start_index)
46 }
47}
48
49impl Column for WorkspaceId {
50 fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
51 let blob = statement.column_blob(start_index)?;
52 Ok((WorkspaceId(bincode::deserialize(blob)?), start_index + 1))
53 }
54}
55
56#[derive(Debug, PartialEq, Eq)]
57pub struct SerializedWorkspace {
58 pub workspace_id: WorkspaceId,
59 pub dock_position: DockPosition,
60 pub center_group: SerializedPaneGroup,
61 pub dock_pane: SerializedPane,
62}
63
64#[derive(Debug, PartialEq, Eq, Clone)]
65pub enum SerializedPaneGroup {
66 Group {
67 axis: Axis,
68 children: Vec<SerializedPaneGroup>,
69 },
70 Pane(SerializedPane),
71}
72
73impl Default for SerializedPaneGroup {
74 fn default() -> Self {
75 Self::Pane(SerializedPane {
76 children: Vec::new(),
77 active: false,
78 })
79 }
80}
81
82impl SerializedPaneGroup {
83 #[async_recursion(?Send)]
84 pub(crate) async fn deserialize(
85 &self,
86 project: &ModelHandle<Project>,
87 workspace_id: &WorkspaceId,
88 workspace: &ViewHandle<Workspace>,
89 cx: &mut AsyncAppContext,
90 ) -> (Member, Option<ViewHandle<Pane>>) {
91 match self {
92 SerializedPaneGroup::Group { axis, children } => {
93 let mut current_active_pane = None;
94 let mut members = Vec::new();
95 for child in children {
96 let (new_member, active_pane) = child
97 .deserialize(project, workspace_id, workspace, cx)
98 .await;
99 members.push(new_member);
100
101 current_active_pane = current_active_pane.or(active_pane);
102 }
103 (
104 Member::Axis(PaneAxis {
105 axis: *axis,
106 members,
107 }),
108 current_active_pane,
109 )
110 }
111 SerializedPaneGroup::Pane(serialized_pane) => {
112 let pane = workspace.update(cx, |workspace, cx| workspace.add_pane(cx));
113 let active = serialized_pane.active;
114 serialized_pane
115 .deserialize_to(project, &pane, workspace_id, workspace, cx)
116 .await;
117
118 (Member::Pane(pane.clone()), active.then(|| pane))
119 }
120 }
121 }
122}
123
124#[derive(Debug, PartialEq, Eq, Default, Clone)]
125pub struct SerializedPane {
126 pub(crate) active: bool,
127 pub(crate) children: Vec<SerializedItem>,
128}
129
130impl SerializedPane {
131 pub fn new(children: Vec<SerializedItem>, active: bool) -> Self {
132 SerializedPane { children, active }
133 }
134
135 pub async fn deserialize_to(
136 &self,
137 project: &ModelHandle<Project>,
138 pane_handle: &ViewHandle<Pane>,
139 workspace_id: &WorkspaceId,
140 workspace: &ViewHandle<Workspace>,
141 cx: &mut AsyncAppContext,
142 ) {
143 for item in self.children.iter() {
144 let project = project.clone();
145 let workspace_id = workspace_id.clone();
146 let item_handle = pane_handle
147 .update(cx, |_, cx| {
148 if let Some(deserializer) = cx.global::<ItemDeserializers>().get(&item.kind) {
149 deserializer(
150 project,
151 workspace.downgrade(),
152 workspace_id,
153 item.item_id,
154 cx,
155 )
156 } else {
157 Task::ready(Err(anyhow::anyhow!(
158 "Deserializer does not exist for item kind: {}",
159 item.kind
160 )))
161 }
162 })
163 .await
164 .log_err();
165
166 if let Some(item_handle) = item_handle {
167 workspace.update(cx, |workspace, cx| {
168 Pane::add_item(workspace, &pane_handle, item_handle, false, false, None, cx);
169 })
170 }
171 }
172 }
173}
174
175pub type GroupId = i64;
176pub type PaneId = i64;
177pub type ItemId = usize;
178
179#[derive(Debug, PartialEq, Eq, Clone)]
180pub struct SerializedItem {
181 pub kind: Arc<str>,
182 pub item_id: ItemId,
183}
184
185impl SerializedItem {
186 pub fn new(kind: impl AsRef<str>, item_id: ItemId) -> Self {
187 Self {
188 kind: Arc::from(kind.as_ref()),
189 item_id,
190 }
191 }
192}
193
194impl Bind for &SerializedItem {
195 fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
196 let next_index = statement.bind(self.kind.clone(), start_index)?;
197 statement.bind(self.item_id, next_index)
198 }
199}
200
201impl Column for SerializedItem {
202 fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
203 let (kind, next_index) = Arc::<str>::column(statement, start_index)?;
204 let (item_id, next_index) = ItemId::column(statement, next_index)?;
205 Ok((SerializedItem { kind, item_id }, next_index))
206 }
207}
208
209impl Bind for DockPosition {
210 fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
211 let next_index = statement.bind(self.is_visible(), start_index)?;
212 statement.bind(self.anchor(), next_index)
213 }
214}
215
216impl Column for DockPosition {
217 fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
218 let (visible, next_index) = bool::column(statement, start_index)?;
219 let (dock_anchor, next_index) = DockAnchor::column(statement, next_index)?;
220 let position = if visible {
221 DockPosition::Shown(dock_anchor)
222 } else {
223 DockPosition::Hidden(dock_anchor)
224 };
225 Ok((position, next_index))
226 }
227}
228
229#[cfg(test)]
230mod tests {
231 use db::sqlez::connection::Connection;
232 use settings::DockAnchor;
233
234 use super::WorkspaceId;
235
236 #[test]
237 fn test_workspace_round_trips() {
238 let db = Connection::open_memory(Some("workspace_id_round_trips"));
239
240 db.exec(indoc::indoc! {"
241 CREATE TABLE workspace_id_test(
242 workspace_id BLOB,
243 dock_anchor TEXT
244 );"})
245 .unwrap()()
246 .unwrap();
247
248 let workspace_id: WorkspaceId = WorkspaceId::from(&["\test2", "\test1"]);
249
250 db.exec_bound("INSERT INTO workspace_id_test(workspace_id, dock_anchor) VALUES (?,?)")
251 .unwrap()((&workspace_id, DockAnchor::Bottom))
252 .unwrap();
253
254 assert_eq!(
255 db.select_row("SELECT workspace_id, dock_anchor FROM workspace_id_test LIMIT 1")
256 .unwrap()()
257 .unwrap(),
258 Some((WorkspaceId::from(&["\test1", "\test2"]), DockAnchor::Bottom))
259 );
260 }
261}