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