model.rs

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