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
 83#[derive(Debug, PartialEq, Eq)]
 84pub struct SerializedWorkspace {
 85    pub dock_anchor: DockAnchor,
 86    pub dock_visible: bool,
 87    pub center_group: SerializedPaneGroup,
 88    pub dock_pane: SerializedPane,
 89}
 90
 91#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
 92pub enum Axis {
 93    #[default]
 94    Horizontal,
 95    Vertical,
 96}
 97
 98impl Bind for Axis {
 99    fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result<i32> {
100        match self {
101            Axis::Horizontal => "Horizontal",
102            Axis::Vertical => "Vertical",
103        }
104        .bind(statement, start_index)
105    }
106}
107
108impl Column for Axis {
109    fn column(statement: &mut Statement, start_index: i32) -> anyhow::Result<(Self, i32)> {
110        String::column(statement, start_index).and_then(|(axis_text, next_index)| {
111            Ok((
112                match axis_text.as_str() {
113                    "Horizontal" => Axis::Horizontal,
114                    "Vertical" => Axis::Vertical,
115                    _ => bail!("Stored serialized item kind is incorrect"),
116                },
117                next_index,
118            ))
119        })
120    }
121}
122
123#[derive(Debug, PartialEq, Eq, Clone)]
124pub enum SerializedPaneGroup {
125    Group {
126        axis: Axis,
127        children: Vec<SerializedPaneGroup>,
128    },
129    Pane(SerializedPane),
130}
131
132// Dock panes, and grouped panes combined?
133// AND we're collapsing PaneGroup::Pane
134// In the case where
135
136impl Default for SerializedPaneGroup {
137    fn default() -> Self {
138        Self::Group {
139            axis: Axis::Horizontal,
140            children: vec![Self::Pane(Default::default())],
141        }
142    }
143}
144
145#[derive(Debug, PartialEq, Eq, Default, Clone)]
146pub struct SerializedPane {
147    pub(crate) children: Vec<SerializedItem>,
148}
149
150impl SerializedPane {
151    pub fn new(children: Vec<SerializedItem>) -> Self {
152        SerializedPane { children }
153    }
154}
155
156pub type GroupId = i64;
157pub type PaneId = i64;
158pub type ItemId = usize;
159
160pub(crate) enum SerializedItemKind {
161    Editor,
162    Diagnostics,
163    ProjectSearch,
164    Terminal,
165}
166
167impl Bind for SerializedItemKind {
168    fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result<i32> {
169        match self {
170            SerializedItemKind::Editor => "Editor",
171            SerializedItemKind::Diagnostics => "Diagnostics",
172            SerializedItemKind::ProjectSearch => "ProjectSearch",
173            SerializedItemKind::Terminal => "Terminal",
174        }
175        .bind(statement, start_index)
176    }
177}
178
179impl Column for SerializedItemKind {
180    fn column(statement: &mut Statement, start_index: i32) -> anyhow::Result<(Self, i32)> {
181        String::column(statement, start_index).and_then(|(kind_text, next_index)| {
182            Ok((
183                match kind_text.as_ref() {
184                    "Editor" => SerializedItemKind::Editor,
185                    "Diagnostics" => SerializedItemKind::Diagnostics,
186                    "ProjectSearch" => SerializedItemKind::ProjectSearch,
187                    "Terminal" => SerializedItemKind::Terminal,
188                    _ => bail!("Stored serialized item kind is incorrect"),
189                },
190                next_index,
191            ))
192        })
193    }
194}
195
196#[derive(Debug, PartialEq, Eq, Clone)]
197pub enum SerializedItem {
198    Editor { item_id: usize, path: Arc<Path> },
199    Diagnostics { item_id: usize },
200    ProjectSearch { item_id: usize, query: String },
201    Terminal { item_id: usize },
202}
203
204impl SerializedItem {
205    pub fn item_id(&self) -> usize {
206        match self {
207            SerializedItem::Editor { item_id, .. } => *item_id,
208            SerializedItem::Diagnostics { item_id } => *item_id,
209            SerializedItem::ProjectSearch { item_id, .. } => *item_id,
210            SerializedItem::Terminal { item_id } => *item_id,
211        }
212    }
213
214    pub(crate) fn kind(&self) -> SerializedItemKind {
215        match self {
216            SerializedItem::Editor { .. } => SerializedItemKind::Editor,
217            SerializedItem::Diagnostics { .. } => SerializedItemKind::Diagnostics,
218            SerializedItem::ProjectSearch { .. } => SerializedItemKind::ProjectSearch,
219            SerializedItem::Terminal { .. } => SerializedItemKind::Terminal,
220        }
221    }
222}
223
224#[cfg(test)]
225mod tests {
226    use sqlez::connection::Connection;
227
228    use crate::model::DockAnchor;
229
230    use super::WorkspaceId;
231
232    #[test]
233    fn test_workspace_round_trips() {
234        let db = Connection::open_memory("workspace_id_round_trips");
235
236        db.exec(indoc::indoc! {"
237            CREATE TABLE workspace_id_test(
238                workspace_id BLOB,
239                dock_anchor TEXT
240            );"})
241            .unwrap()()
242        .unwrap();
243
244        let workspace_id: WorkspaceId = WorkspaceId::from(&["\test2", "\test1"]);
245
246        db.exec_bound("INSERT INTO workspace_id_test(workspace_id, dock_anchor) VALUES (?,?)")
247            .unwrap()((&workspace_id, DockAnchor::Bottom))
248        .unwrap();
249
250        assert_eq!(
251            db.select_row("SELECT workspace_id, dock_anchor FROM workspace_id_test LIMIT 1")
252                .unwrap()()
253            .unwrap(),
254            Some((WorkspaceId::from(&["\test1", "\test2"]), DockAnchor::Bottom))
255        );
256    }
257}