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