1use std::{
2 path::{Path, PathBuf},
3 sync::Arc,
4};
5
6use anyhow::{bail, Result};
7
8use gpui::Axis;
9use settings::DockAnchor;
10use sqlez::{
11 bindable::{Bind, Column},
12 statement::Statement,
13};
14
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub(crate) struct WorkspaceId(Vec<PathBuf>);
17
18impl WorkspaceId {
19 pub fn paths(self) -> Vec<PathBuf> {
20 self.0
21 }
22}
23
24impl<P: AsRef<Path>, T: IntoIterator<Item = P>> From<T> for WorkspaceId {
25 fn from(iterator: T) -> Self {
26 let mut roots = iterator
27 .into_iter()
28 .map(|p| p.as_ref().to_path_buf())
29 .collect::<Vec<_>>();
30 roots.sort();
31 Self(roots)
32 }
33}
34
35impl Bind for &WorkspaceId {
36 fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
37 bincode::serialize(&self.0)
38 .expect("Bincode serialization of paths should not fail")
39 .bind(statement, start_index)
40 }
41}
42
43impl Column for WorkspaceId {
44 fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
45 let blob = statement.column_blob(start_index)?;
46 Ok((WorkspaceId(bincode::deserialize(blob)?), start_index + 1))
47 }
48}
49
50#[derive(Debug, PartialEq, Eq)]
51pub struct SerializedWorkspace {
52 pub dock_anchor: DockAnchor,
53 pub dock_visible: bool,
54 pub center_group: SerializedPaneGroup,
55 pub dock_pane: SerializedPane,
56}
57
58#[derive(Debug, PartialEq, Eq, Clone)]
59pub enum SerializedPaneGroup {
60 Group {
61 axis: Axis,
62 children: Vec<SerializedPaneGroup>,
63 },
64 Pane(SerializedPane),
65}
66
67impl Default for SerializedPaneGroup {
68 fn default() -> Self {
69 Self::Group {
70 axis: Axis::Horizontal,
71 children: vec![Self::Pane(Default::default())],
72 }
73 }
74}
75
76#[derive(Debug, PartialEq, Eq, Default, Clone)]
77pub struct SerializedPane {
78 pub(crate) children: Vec<SerializedItem>,
79}
80
81impl SerializedPane {
82 pub fn new(children: Vec<SerializedItem>) -> Self {
83 SerializedPane { children }
84 }
85}
86
87pub type GroupId = i64;
88pub type PaneId = i64;
89pub type ItemId = usize;
90
91pub(crate) enum SerializedItemKind {
92 Editor,
93 Diagnostics,
94 ProjectSearch,
95 Terminal,
96}
97
98impl Bind for SerializedItemKind {
99 fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result<i32> {
100 match self {
101 SerializedItemKind::Editor => "Editor",
102 SerializedItemKind::Diagnostics => "Diagnostics",
103 SerializedItemKind::ProjectSearch => "ProjectSearch",
104 SerializedItemKind::Terminal => "Terminal",
105 }
106 .bind(statement, start_index)
107 }
108}
109
110impl Column for SerializedItemKind {
111 fn column(statement: &mut Statement, start_index: i32) -> anyhow::Result<(Self, i32)> {
112 String::column(statement, start_index).and_then(|(kind_text, next_index)| {
113 Ok((
114 match kind_text.as_ref() {
115 "Editor" => SerializedItemKind::Editor,
116 "Diagnostics" => SerializedItemKind::Diagnostics,
117 "ProjectSearch" => SerializedItemKind::ProjectSearch,
118 "Terminal" => SerializedItemKind::Terminal,
119 _ => bail!("Stored serialized item kind is incorrect"),
120 },
121 next_index,
122 ))
123 })
124 }
125}
126
127#[derive(Debug, PartialEq, Eq, Clone)]
128pub enum SerializedItem {
129 Editor { item_id: usize, path: Arc<Path> },
130 Diagnostics { item_id: usize },
131 ProjectSearch { item_id: usize, query: String },
132 Terminal { item_id: usize },
133}
134
135impl SerializedItem {
136 pub fn item_id(&self) -> usize {
137 match self {
138 SerializedItem::Editor { item_id, .. } => *item_id,
139 SerializedItem::Diagnostics { item_id } => *item_id,
140 SerializedItem::ProjectSearch { item_id, .. } => *item_id,
141 SerializedItem::Terminal { item_id } => *item_id,
142 }
143 }
144
145 pub(crate) fn kind(&self) -> SerializedItemKind {
146 match self {
147 SerializedItem::Editor { .. } => SerializedItemKind::Editor,
148 SerializedItem::Diagnostics { .. } => SerializedItemKind::Diagnostics,
149 SerializedItem::ProjectSearch { .. } => SerializedItemKind::ProjectSearch,
150 SerializedItem::Terminal { .. } => SerializedItemKind::Terminal,
151 }
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use sqlez::connection::Connection;
158
159 use crate::persistence::model::DockAnchor;
160
161 use super::WorkspaceId;
162
163 #[test]
164 fn test_workspace_round_trips() {
165 let db = Connection::open_memory("workspace_id_round_trips");
166
167 db.exec(indoc::indoc! {"
168 CREATE TABLE workspace_id_test(
169 workspace_id BLOB,
170 dock_anchor TEXT
171 );"})
172 .unwrap()()
173 .unwrap();
174
175 let workspace_id: WorkspaceId = WorkspaceId::from(&["\test2", "\test1"]);
176
177 db.exec_bound("INSERT INTO workspace_id_test(workspace_id, dock_anchor) VALUES (?,?)")
178 .unwrap()((&workspace_id, DockAnchor::Bottom))
179 .unwrap();
180
181 assert_eq!(
182 db.select_row("SELECT workspace_id, dock_anchor FROM workspace_id_test LIMIT 1")
183 .unwrap()()
184 .unwrap(),
185 Some((WorkspaceId::from(&["\test1", "\test2"]), DockAnchor::Bottom))
186 );
187 }
188}