model.rs

  1use std::{
  2    path::{Path, PathBuf},
  3    sync::Arc,
  4};
  5
  6use anyhow::{bail, Result};
  7
  8use sqlez::{
  9    bindable::{Bind, Column},
 10    statement::Statement,
 11};
 12
 13#[derive(Debug, Clone, PartialEq, Eq)]
 14pub(crate) struct WorkspaceId(Vec<PathBuf>);
 15
 16impl WorkspaceId {
 17    pub fn paths(self) -> Vec<PathBuf> {
 18        self.0
 19    }
 20}
 21
 22impl<P: AsRef<Path>, T: IntoIterator<Item = P>> From<T> for WorkspaceId {
 23    fn from(iterator: T) -> Self {
 24        let mut roots = iterator
 25            .into_iter()
 26            .map(|p| p.as_ref().to_path_buf())
 27            .collect::<Vec<_>>();
 28        roots.sort();
 29        Self(roots)
 30    }
 31}
 32
 33impl Bind for &WorkspaceId {
 34    fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
 35        bincode::serialize(&self.0)
 36            .expect("Bincode serialization of paths should not fail")
 37            .bind(statement, start_index)
 38    }
 39}
 40
 41impl Column for WorkspaceId {
 42    fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
 43        let blob = statement.column_blob(start_index)?;
 44        Ok((WorkspaceId(bincode::deserialize(blob)?), start_index + 1))
 45    }
 46}
 47
 48#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)]
 49pub enum DockAnchor {
 50    #[default]
 51    Bottom,
 52    Right,
 53    Expanded,
 54}
 55
 56impl Bind for DockAnchor {
 57    fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result<i32> {
 58        match self {
 59            DockAnchor::Bottom => "Bottom",
 60            DockAnchor::Right => "Right",
 61            DockAnchor::Expanded => "Expanded",
 62        }
 63        .bind(statement, start_index)
 64    }
 65}
 66
 67impl Column for DockAnchor {
 68    fn column(statement: &mut Statement, start_index: i32) -> anyhow::Result<(Self, i32)> {
 69        String::column(statement, start_index).and_then(|(anchor_text, next_index)| {
 70            Ok((
 71                match anchor_text.as_ref() {
 72                    "Bottom" => DockAnchor::Bottom,
 73                    "Right" => DockAnchor::Right,
 74                    "Expanded" => DockAnchor::Expanded,
 75                    _ => bail!("Stored dock anchor is incorrect"),
 76                },
 77                next_index,
 78            ))
 79        })
 80    }
 81}
 82
 83pub(crate) type WorkspaceRow = (WorkspaceId, DockAnchor, bool);
 84
 85#[derive(Debug, PartialEq, Eq)]
 86pub struct SerializedWorkspace {
 87    pub dock_anchor: DockAnchor,
 88    pub dock_visible: bool,
 89    pub center_group: SerializedPaneGroup,
 90    pub dock_pane: SerializedPane,
 91}
 92
 93#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
 94pub enum Axis {
 95    #[default]
 96    Horizontal,
 97    Vertical,
 98}
 99
100impl Bind for Axis {
101    fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result<i32> {
102        match self {
103            Axis::Horizontal => "Horizontal",
104            Axis::Vertical => "Vertical",
105        }
106        .bind(statement, start_index)
107    }
108}
109
110impl Column for Axis {
111    fn column(statement: &mut Statement, start_index: i32) -> anyhow::Result<(Self, i32)> {
112        String::column(statement, start_index).and_then(|(axis_text, next_index)| {
113            Ok((
114                match axis_text.as_str() {
115                    "Horizontal" => Axis::Horizontal,
116                    "Vertical" => Axis::Vertical,
117                    _ => bail!("Stored serialized item kind is incorrect"),
118                },
119                next_index,
120            ))
121        })
122    }
123}
124
125#[derive(Debug, PartialEq, Eq, Clone)]
126pub enum SerializedPaneGroup {
127    Group {
128        axis: Axis,
129        children: Vec<SerializedPaneGroup>,
130    },
131    Pane(SerializedPane),
132}
133
134// Dock panes, and grouped panes combined?
135// AND we're collapsing PaneGroup::Pane
136// In the case where
137
138impl Default for SerializedPaneGroup {
139    fn default() -> Self {
140        Self::Group {
141            axis: Axis::Horizontal,
142            children: vec![Self::Pane(Default::default())],
143        }
144    }
145}
146
147#[derive(Debug, PartialEq, Eq, Default, Clone)]
148pub struct SerializedPane {
149    pub(crate) children: Vec<SerializedItem>,
150}
151
152impl SerializedPane {
153    pub fn new(children: Vec<SerializedItem>) -> Self {
154        SerializedPane { children }
155    }
156}
157
158pub type GroupId = i64;
159pub type PaneId = i64;
160pub type ItemId = usize;
161
162pub(crate) enum SerializedItemKind {
163    Editor,
164    Diagnostics,
165    ProjectSearch,
166    Terminal,
167}
168
169impl Bind for SerializedItemKind {
170    fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result<i32> {
171        match self {
172            SerializedItemKind::Editor => "Editor",
173            SerializedItemKind::Diagnostics => "Diagnostics",
174            SerializedItemKind::ProjectSearch => "ProjectSearch",
175            SerializedItemKind::Terminal => "Terminal",
176        }
177        .bind(statement, start_index)
178    }
179}
180
181impl Column for SerializedItemKind {
182    fn column(statement: &mut Statement, start_index: i32) -> anyhow::Result<(Self, i32)> {
183        String::column(statement, start_index).and_then(|(kind_text, next_index)| {
184            Ok((
185                match kind_text.as_ref() {
186                    "Editor" => SerializedItemKind::Editor,
187                    "Diagnostics" => SerializedItemKind::Diagnostics,
188                    "ProjectSearch" => SerializedItemKind::ProjectSearch,
189                    "Terminal" => SerializedItemKind::Terminal,
190                    _ => bail!("Stored serialized item kind is incorrect"),
191                },
192                next_index,
193            ))
194        })
195    }
196}
197
198#[derive(Debug, PartialEq, Eq, Clone)]
199pub enum SerializedItem {
200    Editor { item_id: usize, path: Arc<Path> },
201    Diagnostics { item_id: usize },
202    ProjectSearch { item_id: usize, query: String },
203    Terminal { item_id: usize },
204}
205
206impl SerializedItem {
207    pub fn item_id(&self) -> usize {
208        match self {
209            SerializedItem::Editor { item_id, .. } => *item_id,
210            SerializedItem::Diagnostics { item_id } => *item_id,
211            SerializedItem::ProjectSearch { item_id, .. } => *item_id,
212            SerializedItem::Terminal { item_id } => *item_id,
213        }
214    }
215
216    pub(crate) fn kind(&self) -> SerializedItemKind {
217        match self {
218            SerializedItem::Editor { .. } => SerializedItemKind::Editor,
219            SerializedItem::Diagnostics { .. } => SerializedItemKind::Diagnostics,
220            SerializedItem::ProjectSearch { .. } => SerializedItemKind::ProjectSearch,
221            SerializedItem::Terminal { .. } => SerializedItemKind::Terminal,
222        }
223    }
224}
225
226#[cfg(test)]
227mod tests {
228    use sqlez::connection::Connection;
229
230    use crate::model::DockAnchor;
231
232    use super::WorkspaceId;
233
234    #[test]
235    fn test_workspace_round_trips() {
236        let db = Connection::open_memory("workspace_id_round_trips");
237
238        db.exec(indoc::indoc! {"
239            CREATE TABLE workspace_id_test(
240                workspace_id BLOB,
241                dock_anchor TEXT
242            );"})
243            .unwrap();
244
245        let workspace_id: WorkspaceId = WorkspaceId::from(&["\test2", "\test1"]);
246
247        db.prepare("INSERT INTO workspace_id_test(workspace_id, dock_anchor) VALUES (?,?)")
248            .unwrap()
249            .with_bindings((&workspace_id, DockAnchor::Bottom))
250            .unwrap()
251            .exec()
252            .unwrap();
253
254        assert_eq!(
255            db.prepare("SELECT workspace_id, dock_anchor FROM workspace_id_test LIMIT 1")
256                .unwrap()
257                .row::<(WorkspaceId, DockAnchor)>()
258                .unwrap(),
259            (WorkspaceId::from(&["\test1", "\test2"]), DockAnchor::Bottom)
260        );
261    }
262}