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 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}