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}