1
2use gpui::Axis;
3
4use rusqlite::{OptionalExtension, Connection};
5use serde::{Deserialize, Serialize};
6use serde_rusqlite::{from_row, to_params_named};
7
8use crate::{items::ItemId, workspace::WorkspaceId};
9
10use super::Db;
11
12pub(crate) const PANE_M_1: &str = "
13CREATE TABLE dock_panes(
14 dock_pane_id INTEGER PRIMARY KEY,
15 workspace_id INTEGER NOT NULL,
16 anchor_position TEXT NOT NULL, -- Enum: 'Bottom' / 'Right' / 'Expanded'
17 visible INTEGER NOT NULL, -- Boolean
18 FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) ON DELETE CASCADE
19) STRICT;
20
21CREATE TABLE pane_groups(
22 group_id INTEGER PRIMARY KEY,
23 workspace_id INTEGER NOT NULL,
24 parent_group INTEGER, -- NULL indicates that this is a root node
25 axis TEXT NOT NULL, -- Enum: 'Vertical' / 'Horizontal'
26 FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) ON DELETE CASCADE,
27 FOREIGN KEY(parent_group) REFERENCES pane_groups(group_id) ON DELETE CASCADE
28) STRICT;
29
30CREATE TABLE grouped_panes(
31 pane_id INTEGER PRIMARY KEY,
32 workspace_id INTEGER NOT NULL,
33 group_id INTEGER NOT NULL,
34 idx INTEGER NOT NULL,
35 FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) ON DELETE CASCADE,
36 FOREIGN KEY(group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE
37) STRICT;
38
39CREATE TABLE items(
40 item_id INTEGER PRIMARY KEY,
41 workspace_id INTEGER NOT NULL,
42 kind TEXT NOT NULL,
43 FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) ON DELETE CASCADE
44) STRICT;
45
46CREATE TABLE group_items(
47 workspace_id INTEGER NOT NULL,
48 pane_id INTEGER NOT NULL,
49 item_id INTEGER NOT NULL,
50 idx INTEGER NOT NULL,
51 PRIMARY KEY (workspace_id, pane_id, item_id)
52 FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) ON DELETE CASCADE,
53 FOREIGN KEY(pane_id) REFERENCES grouped_panes(pane_id) ON DELETE CASCADE,
54 FOREIGN KEY(item_id) REFERENCES items(item_id) ON DELETE CASCADE
55) STRICT;
56
57CREATE TABLE dock_items(
58 workspace_id INTEGER NOT NULL,
59 dock_pane_id INTEGER NOT NULL,
60 item_id INTEGER NOT NULL,
61 idx INTEGER NOT NULL,
62 PRIMARY KEY (workspace_id, dock_pane_id, item_id)
63 FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) ON DELETE CASCADE,
64 FOREIGN KEY(dock_pane_id) REFERENCES dock_panes(dock_pane_id) ON DELETE CASCADE,
65 FOREIGN KEY(item_id) REFERENCES items(item_id)ON DELETE CASCADE
66) STRICT;
67";
68
69// We have an many-branched, unbalanced tree with three types:
70// Pane Groups
71// Panes
72// Items
73
74// The root is always a Pane Group
75// Pane Groups can have 0 (or more) Panes and/or Pane Groups as children
76// Panes can have 0 or more items as children
77// Panes can be their own root
78// Items cannot have children
79// References pointing down is hard (SQL doesn't like arrays)
80// References pointing up is easy (1-1 item / parent relationship) but is harder to query
81//
82
83#[derive(Debug, PartialEq, Eq, Copy, Clone)]
84pub struct PaneId {
85 workspace_id: WorkspaceId,
86 pane_id: usize,
87}
88
89#[derive(Debug, PartialEq, Eq, Copy, Clone)]
90pub struct PaneGroupId {
91 workspace_id: WorkspaceId,
92 group_id: usize,
93}
94
95impl PaneGroupId {
96 pub fn root(workspace_id: WorkspaceId) -> Self {
97 Self {
98 workspace_id,
99 group_id: 0,
100 }
101 }
102}
103
104#[derive(Debug, PartialEq, Eq)]
105pub struct SerializedPaneGroup {
106 group_id: PaneGroupId,
107 axis: Axis,
108 children: Vec<PaneGroupChild>,
109}
110
111impl SerializedPaneGroup {
112 pub fn empty_root(workspace_id: WorkspaceId) -> Self {
113 Self {
114 group_id: PaneGroupId::root(workspace_id),
115 axis: Default::default(),
116 children: Default::default(),
117 }
118 }
119}
120
121struct PaneGroupChildRow {
122 child_pane_id: Option<usize>,
123 child_group_id: Option<usize>,
124 index: usize,
125}
126
127#[derive(Debug, PartialEq, Eq)]
128pub enum PaneGroupChild {
129 Pane(SerializedPane),
130 Group(SerializedPaneGroup),
131}
132
133#[derive(Debug, PartialEq, Eq)]
134pub struct SerializedPane {
135 pane_id: PaneId,
136 children: Vec<ItemId>,
137}
138
139
140//********* CURRENTLY IN USE TYPES: *********
141
142
143#[derive(Default, Debug, PartialEq, Eq, Deserialize, Serialize)]
144pub enum DockAnchor {
145 #[default]
146 Bottom,
147 Right,
148 Expanded,
149}
150
151#[derive(Default, Debug, PartialEq, Eq, Deserialize, Serialize)]
152pub struct SerializedDockPane {
153 pub anchor_position: DockAnchor,
154 pub visible: bool,
155}
156
157impl SerializedDockPane {
158 pub fn to_row(&self, workspace: WorkspaceId) -> DockRow {
159 DockRow { workspace_id: workspace, anchor_position: self.anchor_position, visible: self.visible }
160 }
161}
162
163#[derive(Default, Debug, PartialEq, Eq, Deserialize, Serialize)]
164pub(crate) struct DockRow {
165 workspace_id: WorkspaceId,
166 anchor_position: DockAnchor,
167 visible: bool,
168}
169
170impl DockRow {
171 pub fn to_pane(&self) -> SerializedDockPane {
172 SerializedDockPane { anchor_position: self.anchor_position, visible: self.visible }
173 }
174}
175
176impl Db {
177 pub fn get_pane_group(&self, pane_group_id: PaneGroupId) -> SerializedPaneGroup {
178 let axis = self.get_pane_group_axis(pane_group_id);
179 let mut children: Vec<(usize, PaneGroupChild)> = Vec::new();
180 for child_row in self.get_pane_group_children(pane_group_id) {
181 if let Some(child_pane_id) = child_row.child_pane_id {
182 children.push((
183 child_row.index,
184 PaneGroupChild::Pane(self.get_pane(PaneId {
185 workspace_id: pane_group_id.workspace_id,
186 pane_id: child_pane_id,
187 })),
188 ));
189 } else if let Some(child_group_id) = child_row.child_group_id {
190 children.push((
191 child_row.index,
192 PaneGroupChild::Group(self.get_pane_group(PaneGroupId {
193 workspace_id: pane_group_id.workspace_id,
194 group_id: child_group_id,
195 })),
196 ));
197 }
198 }
199 children.sort_by_key(|(index, _)| *index);
200
201 SerializedPaneGroup {
202 group_id: pane_group_id,
203 axis,
204 children: children.into_iter().map(|(_, child)| child).collect(),
205 }
206 }
207
208 fn get_pane_group_children(
209 &self,
210 _pane_group_id: PaneGroupId,
211 ) -> impl Iterator<Item = PaneGroupChildRow> {
212 Vec::new().into_iter()
213 }
214
215 fn get_pane_group_axis(&self, _pane_group_id: PaneGroupId) -> Axis {
216 unimplemented!();
217 }
218
219 pub fn save_pane_splits(&self, _center_pane_group: SerializedPaneGroup) {
220 // Delete the center pane group for this workspace and any of its children
221 // Generate new pane group IDs as we go through
222 // insert them
223 // Items garbage collect themselves when dropped
224 }
225
226 pub(crate) fn get_pane(&self, _pane_id: PaneId) -> SerializedPane {
227 unimplemented!();
228 }
229
230 pub fn get_dock_pane(&self, workspace: WorkspaceId) -> Option<SerializedDockPane> {
231 fn logic(conn: &Connection, workspace: WorkspaceId) -> anyhow::Result<Option<SerializedDockPane>> {
232
233 let mut stmt = conn.prepare("SELECT workspace_id, anchor_position, visible FROM dock_panes WHERE workspace_id = ?")?;
234
235 let dock_panes = stmt.query_row([workspace.raw_id()], |row_ref| from_row::<DockRow>).optional();
236
237 let mut dock_panes_iter = stmt.query_and_then([workspace.raw_id()], from_row::<DockRow>)?;
238 let dock_pane = dock_panes_iter
239 .next()
240 .and_then(|dock_row|
241 dock_row
242 .ok()
243 .map(|dock_row| dock_row.to_pane()));
244
245 Ok(dock_pane)
246 }
247
248 self.real()
249 .map(|db| {
250 let lock = db.connection.lock();
251
252 match logic(&lock, workspace) {
253 Ok(dock_pane) => dock_pane,
254 Err(err) => {
255 log::error!("Failed to get the dock pane: {}", err);
256 None
257 },
258 }
259 })
260 .unwrap_or(None)
261
262 }
263
264 pub fn save_dock_pane(&self, workspace: WorkspaceId, dock_pane: SerializedDockPane) {
265 to_params_named(dock_pane.to_row(workspace))
266 .map_err(|err| {
267 log::error!("Failed to parse params for the dock row: {}", err);
268 err
269 })
270 .ok()
271 .zip(self.real())
272 .map(|(params, db)| {
273 // TODO: overwrite old dock panes if need be
274 let query = "INSERT INTO dock_panes (workspace_id, anchor_position, visible) VALUES (:workspace_id, :anchor_position, :visible);";
275
276 db.connection
277 .lock()
278 .execute(query, params.to_slice().as_slice())
279 .map(|_| ()) // Eat the return value
280 .unwrap_or_else(|err| {
281 log::error!("Failed to insert new dock pane into DB: {}", err);
282 })
283 });
284 }
285}
286
287#[cfg(test)]
288mod tests {
289
290 use crate::Db;
291
292 use super::{DockAnchor, SerializedDockPane};
293
294 #[test]
295 fn test_basic_dock_pane() {
296 let db = Db::open_in_memory();
297
298 let workspace = db.workspace_for_roots(&["/tmp"]);
299
300 let dock_pane = SerializedDockPane {
301 workspace_id: workspace.workspace_id,
302 anchor_position: DockAnchor::Expanded,
303 visible: true,
304 };
305
306 db.save_dock_pane(&dock_pane);
307
308 let new_workspace = db.workspace_for_roots(&["/tmp"]);
309
310 assert_eq!(new_workspace.dock_pane.unwrap(), dock_pane);
311 }
312}