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}