model.rs

  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}