pub mod model; use std::{ borrow::Cow, collections::BTreeMap, path::{Path, PathBuf}, str::FromStr, sync::Arc,}; use anyhow::{Context as _, Result, bail};use collections::{HashMap, HashSet, IndexSet};use db::{ kvp::KEY_VALUE_STORE, query, sqlez::{connection::Connection, domain::Domain}, sqlez_macros::sql,};use gpui::{Axis, Bounds, Task, WindowBounds, WindowId, point, size};use project::{ debugger::breakpoint_store::{BreakpointState, SourceBreakpoint}, trusted_worktrees::{DbTrustedPaths, RemoteHostLocation},}; use language::{LanguageName, Toolchain, ToolchainScope};use remote::{ DockerConnectionOptions, RemoteConnectionOptions, SshConnectionOptions, WslConnectionOptions,};use serde::{Deserialize, Serialize};use sqlez::{ bindable::{Bind, Column, StaticColumnCount}, statement::Statement, thread_safe_connection::ThreadSafeConnection,}; use ui::{App, SharedString, px};use util::{ResultExt, maybe, rel_path::RelPath};use uuid::Uuid; use crate::{ WorkspaceId, path_list::{PathList, SerializedPathList}, persistence::model::RemoteConnectionKind,}; use model::{ GroupId, ItemId, PaneId, RemoteConnectionId, SerializedItem, SerializedPane, SerializedPaneGroup, SerializedWorkspace,}; use self::model::{DockStructure, SerializedWorkspaceLocation}; // https://www.sqlite.org/limits.html// > <..> the maximum value of a host parameter number is SQLITE_MAX_VARIABLE_NUMBER,// > which defaults to <..> 32766 for SQLite versions after 3.32.0.const MAX_QUERY_PLACEHOLDERS: usize = 32000; #[derive(Copy, Clone, Debug, PartialEq)]pub(crate) struct SerializedAxis(pub(crate) gpui::Axis);impl sqlez::bindable::StaticColumnCount for SerializedAxis {}impl sqlez::bindable::Bind for SerializedAxis { fn bind( &self, statement: &sqlez::statement::Statement, start_index: i32, ) -> anyhow::Result<i32> { match self.0 { gpui::Axis::Horizontal => "Horizontal", gpui::Axis::Vertical => "Vertical", } .bind(statement, start_index) }} impl sqlez::bindable::Column for SerializedAxis { fn column( statement: &mut sqlez::statement::Statement, start_index: i32, ) -> anyhow::Result<(Self, i32)> { String::column(statement, start_index).and_then(|(axis_text, next_index)| { Ok(( match axis_text.as_str() { "Horizontal" => Self(Axis::Horizontal), "Vertical" => Self(Axis::Vertical), _ => anyhow::bail!("Stored serialized item kind is incorrect"), }, next_index, )) }) }} #[derive(Copy, Clone, Debug, PartialEq, Default)]pub(crate) struct SerializedWindowBounds(pub(crate) WindowBounds); impl StaticColumnCount for SerializedWindowBounds { fn column_count() -> usize { 5 }} impl Bind for SerializedWindowBounds { fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> { match self.0 { WindowBounds::Windowed(bounds) => { let next_index = statement.bind(&"Windowed", start_index)?; statement.bind( &( SerializedPixels(bounds.origin.x), SerializedPixels(bounds.origin.y), SerializedPixels(bounds.size.width), SerializedPixels(bounds.size.height), ), next_index, ) } WindowBounds::Maximized(bounds) => { let next_index = statement.bind(&"Maximized", start_index)?; statement.bind( &( SerializedPixels(bounds.origin.x), SerializedPixels(bounds.origin.y), SerializedPixels(bounds.size.width), SerializedPixels(bounds.size.height), ), next_index, ) } WindowBounds::Fullscreen(bounds) => { let next_index = statement.bind(&"FullScreen", start_index)?; statement.bind( &( SerializedPixels(bounds.origin.x), SerializedPixels(bounds.origin.y), SerializedPixels(bounds.size.width), SerializedPixels(bounds.size.height), ), next_index, ) } } }} impl Column for SerializedWindowBounds { fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { let (window_state, next_index) = String::column(statement, start_index)?; let ((x, y, width, height), _): ((i32, i32, i32, i32), _) = Column::column(statement, next_index)?; let bounds = Bounds { origin: point(px(x as f32), px(y as f32)), size: size(px(width as f32), px(height as f32)), }; let status = match window_state.as_str() { "Windowed" | "Fixed" => SerializedWindowBounds(WindowBounds::Windowed(bounds)), "Maximized" => SerializedWindowBounds(WindowBounds::Maximized(bounds)), "FullScreen" => SerializedWindowBounds(WindowBounds::Fullscreen(bounds)), _ => bail!("Window State did not have a valid string"), }; Ok((status, next_index + 4)) }} const DEFAULT_WINDOW_BOUNDS_KEY: &str = "default_window_bounds"; pub fn read_default_window_bounds() -> Option<(Uuid, WindowBounds)> { let json_str = KEY_VALUE_STORE .read_kvp(DEFAULT_WINDOW_BOUNDS_KEY) .log_err() .flatten()?; let (display_uuid, persisted) = serde_json::from_str::<(Uuid, WindowBoundsJson)>(&json_str).ok()?; Some((display_uuid, persisted.into()))} pub async fn write_default_window_bounds( bounds: WindowBounds, display_uuid: Uuid,) -> anyhow::Result<()> { let persisted = WindowBoundsJson::from(bounds); let json_str = serde_json::to_string(&(display_uuid, persisted))?; KEY_VALUE_STORE .write_kvp(DEFAULT_WINDOW_BOUNDS_KEY.to_string(), json_str) .await?; Ok(())} #[derive(Serialize, Deserialize)]pub enum WindowBoundsJson { Windowed { x: i32, y: i32, width: i32, height: i32, }, Maximized { x: i32, y: i32, width: i32, height: i32, }, Fullscreen { x: i32, y: i32, width: i32, height: i32, },} impl From<WindowBounds> for WindowBoundsJson { fn from(b: WindowBounds) -> Self { match b { WindowBounds::Windowed(bounds) => { let origin = bounds.origin; let size = bounds.size; WindowBoundsJson::Windowed { x: f32::from(origin.x).round() as i32, y: f32::from(origin.y).round() as i32, width: f32::from(size.width).round() as i32, height: f32::from(size.height).round() as i32, } } WindowBounds::Maximized(bounds) => { let origin = bounds.origin; let size = bounds.size; WindowBoundsJson::Maximized { x: f32::from(origin.x).round() as i32, y: f32::from(origin.y).round() as i32, width: f32::from(size.width).round() as i32, height: f32::from(size.height).round() as i32, } } WindowBounds::Fullscreen(bounds) => { let origin = bounds.origin; let size = bounds.size; WindowBoundsJson::Fullscreen { x: f32::from(origin.x).round() as i32, y: f32::from(origin.y).round() as i32, width: f32::from(size.width).round() as i32, height: f32::from(size.height).round() as i32, } } } }} impl From<WindowBoundsJson> for WindowBounds { fn from(n: WindowBoundsJson) -> Self { match n { WindowBoundsJson::Windowed { x, y, width, height, } => WindowBounds::Windowed(Bounds { origin: point(px(x as f32), px(y as f32)), size: size(px(width as f32), px(height as f32)), }), WindowBoundsJson::Maximized { x, y, width, height, } => WindowBounds::Maximized(Bounds { origin: point(px(x as f32), px(y as f32)), size: size(px(width as f32), px(height as f32)), }), WindowBoundsJson::Fullscreen { x, y, width, height, } => WindowBounds::Fullscreen(Bounds { origin: point(px(x as f32), px(y as f32)), size: size(px(width as f32), px(height as f32)), }), } }} #[derive(Debug)]pub struct Breakpoint { pub position: u32, pub message: Option<Arc<str>>, pub condition: Option<Arc<str>>, pub hit_condition: Option<Arc<str>>, pub state: BreakpointState,} /// Wrapper for DB type of a breakpointstruct BreakpointStateWrapper<'a>(Cow<'a, BreakpointState>); impl From<BreakpointState> for BreakpointStateWrapper<'static> { fn from(kind: BreakpointState) -> Self { BreakpointStateWrapper(Cow::Owned(kind)) }} impl StaticColumnCount for BreakpointStateWrapper<'_> { fn column_count() -> usize { 1 }} impl Bind for BreakpointStateWrapper<'_> { fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result<i32> { statement.bind(&self.0.to_int(), start_index) }} impl Column for BreakpointStateWrapper<'_> { fn column(statement: &mut Statement, start_index: i32) -> anyhow::Result<(Self, i32)> { let state = statement.column_int(start_index)?; match state { 0 => Ok((BreakpointState::Enabled.into(), start_index + 1)), 1 => Ok((BreakpointState::Disabled.into(), start_index + 1)), _ => anyhow::bail!("Invalid BreakpointState discriminant {state}"), } }} impl sqlez::bindable::StaticColumnCount for Breakpoint { fn column_count() -> usize { // Position, log message, condition message, and hit condition message 4 + BreakpointStateWrapper::column_count() }} impl sqlez::bindable::Bind for Breakpoint { fn bind( &self, statement: &sqlez::statement::Statement, start_index: i32, ) -> anyhow::Result<i32> { let next_index = statement.bind(&self.position, start_index)?; let next_index = statement.bind(&self.message, next_index)?; let next_index = statement.bind(&self.condition, next_index)?; let next_index = statement.bind(&self.hit_condition, next_index)?; statement.bind( &BreakpointStateWrapper(Cow::Borrowed(&self.state)), next_index, ) }} impl Column for Breakpoint { fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { let position = statement .column_int(start_index) .with_context(|| format!("Failed to read BreakPoint at index {start_index}"))? as u32; let (message, next_index) = Option::<String>::column(statement, start_index + 1)?; let (condition, next_index) = Option::<String>::column(statement, next_index)?; let (hit_condition, next_index) = Option::<String>::column(statement, next_index)?; let (state, next_index) = BreakpointStateWrapper::column(statement, next_index)?; Ok(( Breakpoint { position, message: message.map(Arc::from), condition: condition.map(Arc::from), hit_condition: hit_condition.map(Arc::from), state: state.0.into_owned(), }, next_index, )) }} #[derive(Clone, Debug, PartialEq)]struct SerializedPixels(gpui::Pixels);impl sqlez::bindable::StaticColumnCount for SerializedPixels {} impl sqlez::bindable::Bind for SerializedPixels { fn bind( &self, statement: &sqlez::statement::Statement, start_index: i32, ) -> anyhow::Result<i32> { let this: i32 = u32::from(self.0) as _; this.bind(statement, start_index) }} pub struct WorkspaceDb(ThreadSafeConnection); impl Domain for WorkspaceDb { const NAME: &str = stringify!(WorkspaceDb); const MIGRATIONS: &[&str] = &[ sql!( CREATE TABLE workspaces( workspace_id INTEGER PRIMARY KEY, workspace_location BLOB UNIQUE, dock_visible INTEGER, // Deprecated. Preserving so users can downgrade Zed. dock_anchor TEXT, // Deprecated. Preserving so users can downgrade Zed. dock_pane INTEGER, // Deprecated. Preserving so users can downgrade Zed. left_sidebar_open INTEGER, // Boolean timestamp TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL, FOREIGN KEY(dock_pane) REFERENCES panes(pane_id) ) STRICT; CREATE TABLE pane_groups( group_id INTEGER PRIMARY KEY, workspace_id INTEGER NOT NULL, parent_group_id INTEGER, // NULL indicates that this is a root node position INTEGER, // NULL indicates that this is a root node axis TEXT NOT NULL, // Enum: 'Vertical' / 'Horizontal' FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) ON DELETE CASCADE ON UPDATE CASCADE, FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE ) STRICT; CREATE TABLE panes( pane_id INTEGER PRIMARY KEY, workspace_id INTEGER NOT NULL, active INTEGER NOT NULL, // Boolean FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) ON DELETE CASCADE ON UPDATE CASCADE ) STRICT; CREATE TABLE center_panes( pane_id INTEGER PRIMARY KEY, parent_group_id INTEGER, // NULL means that this is a root pane position INTEGER, // NULL means that this is a root pane FOREIGN KEY(pane_id) REFERENCES panes(pane_id) ON DELETE CASCADE, FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE ) STRICT; CREATE TABLE items( item_id INTEGER NOT NULL, // This is the item's view id, so this is not unique workspace_id INTEGER NOT NULL, pane_id INTEGER NOT NULL, kind TEXT NOT NULL, position INTEGER NOT NULL, active INTEGER NOT NULL, FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) ON DELETE CASCADE ON UPDATE CASCADE, FOREIGN KEY(pane_id) REFERENCES panes(pane_id) ON DELETE CASCADE, PRIMARY KEY(item_id, workspace_id) ) STRICT; ), sql!( ALTER TABLE workspaces ADD COLUMN window_state TEXT; ALTER TABLE workspaces ADD COLUMN window_x REAL; ALTER TABLE workspaces ADD COLUMN window_y REAL; ALTER TABLE workspaces ADD COLUMN window_width REAL; ALTER TABLE workspaces ADD COLUMN window_height REAL; ALTER TABLE workspaces ADD COLUMN display BLOB; ), // Drop foreign key constraint from workspaces.dock_pane to panes table. sql!( CREATE TABLE workspaces_2( workspace_id INTEGER PRIMARY KEY, workspace_location BLOB UNIQUE, dock_visible INTEGER, // Deprecated. Preserving so users can downgrade Zed. dock_anchor TEXT, // Deprecated. Preserving so users can downgrade Zed. dock_pane INTEGER, // Deprecated. Preserving so users can downgrade Zed. left_sidebar_open INTEGER, // Boolean timestamp TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL, window_state TEXT, window_x REAL, window_y REAL, window_width REAL, window_height REAL, display BLOB ) STRICT; INSERT INTO workspaces_2 SELECT * FROM workspaces; DROP TABLE workspaces; ALTER TABLE workspaces_2 RENAME TO workspaces; ), // Add panels related information sql!( ALTER TABLE workspaces ADD COLUMN left_dock_visible INTEGER; //bool ALTER TABLE workspaces ADD COLUMN left_dock_active_panel TEXT; ALTER TABLE workspaces ADD COLUMN right_dock_visible INTEGER; //bool ALTER TABLE workspaces ADD COLUMN right_dock_active_panel TEXT; ALTER TABLE workspaces ADD COLUMN bottom_dock_visible INTEGER; //bool ALTER TABLE workspaces ADD COLUMN bottom_dock_active_panel TEXT; ), // Add panel zoom persistence sql!( ALTER TABLE workspaces ADD COLUMN left_dock_zoom INTEGER; //bool ALTER TABLE workspaces ADD COLUMN right_dock_zoom INTEGER; //bool ALTER TABLE workspaces ADD COLUMN bottom_dock_zoom INTEGER; //bool ), // Add pane group flex data sql!( ALTER TABLE pane_groups ADD COLUMN flexes TEXT; ), // Add fullscreen field to workspaceLines 1–500 of 3370. Load more
pub mod model; use std::{ borrow::Cow, collections::BTreeMap, path::{Path, PathBuf}, str::FromStr, sync::Arc, }; use anyhow::{Context as _, Result, bail}; use collections::{HashMap, HashSet, IndexSet}; use db::{ kvp::KEY_VALUE_STORE, query, sqlez::{connection::Connection, domain::Domain}, sqlez_macros::sql, }; use gpui::{Axis, Bounds, Task, WindowBounds, WindowId, point, size}; use project::{ debugger::breakpoint_store::{BreakpointState, SourceBreakpoint}, trusted_worktrees::{DbTrustedPaths, RemoteHostLocation}, }; use language::{LanguageName, Toolchain, ToolchainScope}; use remote::{ DockerConnectionOptions, RemoteConnectionOptions, SshConnectionOptions, WslConnectionOptions, }; use serde::{Deserialize, Serialize}; use sqlez::{ bindable::{Bind, Column, StaticColumnCount}, statement::Statement, thread_safe_connection::ThreadSafeConnection, }; use ui::{App, SharedString, px}; use util::{ResultExt, maybe, rel_path::RelPath}; use uuid::Uuid; use crate::{ WorkspaceId, path_list::{PathList, SerializedPathList}, persistence::model::RemoteConnectionKind, }; use model::{ GroupId, ItemId, PaneId, RemoteConnectionId, SerializedItem, SerializedPane, SerializedPaneGroup, SerializedWorkspace, }; use self::model::{DockStructure, SerializedWorkspaceLocation}; // https://www.sqlite.org/limits.html // > <..> the maximum value of a host parameter number is SQLITE_MAX_VARIABLE_NUMBER, // > which defaults to <..> 32766 for SQLite versions after 3.32.0. const MAX_QUERY_PLACEHOLDERS: usize = 32000; #[derive(Copy, Clone, Debug, PartialEq)] pub(crate) struct SerializedAxis(pub(crate) gpui::Axis); impl sqlez::bindable::StaticColumnCount for SerializedAxis {} impl sqlez::bindable::Bind for SerializedAxis { fn bind( &self, statement: &sqlez::statement::Statement, start_index: i32, ) -> anyhow::Result<i32> { match self.0 { gpui::Axis::Horizontal => "Horizontal", gpui::Axis::Vertical => "Vertical", } .bind(statement, start_index) } } impl sqlez::bindable::Column for SerializedAxis { fn column( statement: &mut sqlez::statement::Statement, start_index: i32, ) -> anyhow::Result<(Self, i32)> { String::column(statement, start_index).and_then(|(axis_text, next_index)| { Ok(( match axis_text.as_str() { "Horizontal" => Self(Axis::Horizontal), "Vertical" => Self(Axis::Vertical), _ => anyhow::bail!("Stored serialized item kind is incorrect"), }, next_index, )) }) } } #[derive(Copy, Clone, Debug, PartialEq, Default)] pub(crate) struct SerializedWindowBounds(pub(crate) WindowBounds); impl StaticColumnCount for SerializedWindowBounds { fn column_count() -> usize { 5 } } impl Bind for SerializedWindowBounds { fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> { match self.0 { WindowBounds::Windowed(bounds) => { let next_index = statement.bind(&"Windowed", start_index)?; statement.bind( &( SerializedPixels(bounds.origin.x), SerializedPixels(bounds.origin.y), SerializedPixels(bounds.size.width), SerializedPixels(bounds.size.height), ), next_index, ) } WindowBounds::Maximized(bounds) => { let next_index = statement.bind(&"Maximized", start_index)?; statement.bind( &( SerializedPixels(bounds.origin.x), SerializedPixels(bounds.origin.y), SerializedPixels(bounds.size.width), SerializedPixels(bounds.size.height), ), next_index, ) } WindowBounds::Fullscreen(bounds) => { let next_index = statement.bind(&"FullScreen", start_index)?; statement.bind( &( SerializedPixels(bounds.origin.x), SerializedPixels(bounds.origin.y), SerializedPixels(bounds.size.width), SerializedPixels(bounds.size.height), ), next_index, ) } } } } impl Column for SerializedWindowBounds { fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { let (window_state, next_index) = String::column(statement, start_index)?; let ((x, y, width, height), _): ((i32, i32, i32, i32), _) = Column::column(statement, next_index)?; let bounds = Bounds { origin: point(px(x as f32), px(y as f32)), size: size(px(width as f32), px(height as f32)), }; let status = match window_state.as_str() { "Windowed" | "Fixed" => SerializedWindowBounds(WindowBounds::Windowed(bounds)), "Maximized" => SerializedWindowBounds(WindowBounds::Maximized(bounds)), "FullScreen" => SerializedWindowBounds(WindowBounds::Fullscreen(bounds)), _ => bail!("Window State did not have a valid string"), }; Ok((status, next_index + 4)) } } const DEFAULT_WINDOW_BOUNDS_KEY: &str = "default_window_bounds"; pub fn read_default_window_bounds() -> Option<(Uuid, WindowBounds)> { let json_str = KEY_VALUE_STORE .read_kvp(DEFAULT_WINDOW_BOUNDS_KEY) .log_err() .flatten()?; let (display_uuid, persisted) = serde_json::from_str::<(Uuid, WindowBoundsJson)>(&json_str).ok()?; Some((display_uuid, persisted.into())) } pub async fn write_default_window_bounds( bounds: WindowBounds, display_uuid: Uuid, ) -> anyhow::Result<()> { let persisted = WindowBoundsJson::from(bounds); let json_str = serde_json::to_string(&(display_uuid, persisted))?; KEY_VALUE_STORE .write_kvp(DEFAULT_WINDOW_BOUNDS_KEY.to_string(), json_str) .await?; Ok(()) } #[derive(Serialize, Deserialize)] pub enum WindowBoundsJson { Windowed { x: i32, y: i32, width: i32, height: i32, }, Maximized { x: i32, y: i32, width: i32, height: i32, }, Fullscreen { x: i32, y: i32, width: i32, height: i32, }, } impl From<WindowBounds> for WindowBoundsJson { fn from(b: WindowBounds) -> Self { match b { WindowBounds::Windowed(bounds) => { let origin = bounds.origin; let size = bounds.size; WindowBoundsJson::Windowed { x: f32::from(origin.x).round() as i32, y: f32::from(origin.y).round() as i32, width: f32::from(size.width).round() as i32, height: f32::from(size.height).round() as i32, } } WindowBounds::Maximized(bounds) => { let origin = bounds.origin; let size = bounds.size; WindowBoundsJson::Maximized { x: f32::from(origin.x).round() as i32, y: f32::from(origin.y).round() as i32, width: f32::from(size.width).round() as i32, height: f32::from(size.height).round() as i32, } } WindowBounds::Fullscreen(bounds) => { let origin = bounds.origin; let size = bounds.size; WindowBoundsJson::Fullscreen { x: f32::from(origin.x).round() as i32, y: f32::from(origin.y).round() as i32, width: f32::from(size.width).round() as i32, height: f32::from(size.height).round() as i32, } } } } } impl From<WindowBoundsJson> for WindowBounds { fn from(n: WindowBoundsJson) -> Self { match n { WindowBoundsJson::Windowed { x, y, width, height, } => WindowBounds::Windowed(Bounds { origin: point(px(x as f32), px(y as f32)), size: size(px(width as f32), px(height as f32)), }), WindowBoundsJson::Maximized { x, y, width, height, } => WindowBounds::Maximized(Bounds { origin: point(px(x as f32), px(y as f32)), size: size(px(width as f32), px(height as f32)), }), WindowBoundsJson::Fullscreen { x, y, width, height, } => WindowBounds::Fullscreen(Bounds { origin: point(px(x as f32), px(y as f32)), size: size(px(width as f32), px(height as f32)), }), } } } #[derive(Debug)] pub struct Breakpoint { pub position: u32, pub message: Option<Arc<str>>, pub condition: Option<Arc<str>>, pub hit_condition: Option<Arc<str>>, pub state: BreakpointState, } /// Wrapper for DB type of a breakpoint struct BreakpointStateWrapper<'a>(Cow<'a, BreakpointState>); impl From<BreakpointState> for BreakpointStateWrapper<'static> { fn from(kind: BreakpointState) -> Self { BreakpointStateWrapper(Cow::Owned(kind)) } } impl StaticColumnCount for BreakpointStateWrapper<'_> { fn column_count() -> usize { 1 } } impl Bind for BreakpointStateWrapper<'_> { fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result<i32> { statement.bind(&self.0.to_int(), start_index) } } impl Column for BreakpointStateWrapper<'_> { fn column(statement: &mut Statement, start_index: i32) -> anyhow::Result<(Self, i32)> { let state = statement.column_int(start_index)?; match state { 0 => Ok((BreakpointState::Enabled.into(), start_index + 1)), 1 => Ok((BreakpointState::Disabled.into(), start_index + 1)), _ => anyhow::bail!("Invalid BreakpointState discriminant {state}"), } } } impl sqlez::bindable::StaticColumnCount for Breakpoint { fn column_count() -> usize { // Position, log message, condition message, and hit condition message 4 + BreakpointStateWrapper::column_count() } } impl sqlez::bindable::Bind for Breakpoint { fn bind( &self, statement: &sqlez::statement::Statement, start_index: i32, ) -> anyhow::Result<i32> { let next_index = statement.bind(&self.position, start_index)?; let next_index = statement.bind(&self.message, next_index)?; let next_index = statement.bind(&self.condition, next_index)?; let next_index = statement.bind(&self.hit_condition, next_index)?; statement.bind( &BreakpointStateWrapper(Cow::Borrowed(&self.state)), next_index, ) } } impl Column for Breakpoint { fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { let position = statement .column_int(start_index) .with_context(|| format!("Failed to read BreakPoint at index {start_index}"))? as u32; let (message, next_index) = Option::<String>::column(statement, start_index + 1)?; let (condition, next_index) = Option::<String>::column(statement, next_index)?; let (hit_condition, next_index) = Option::<String>::column(statement, next_index)?; let (state, next_index) = BreakpointStateWrapper::column(statement, next_index)?; Ok(( Breakpoint { position, message: message.map(Arc::from), condition: condition.map(Arc::from), hit_condition: hit_condition.map(Arc::from), state: state.0.into_owned(), }, next_index, )) } } #[derive(Clone, Debug, PartialEq)] struct SerializedPixels(gpui::Pixels); impl sqlez::bindable::StaticColumnCount for SerializedPixels {} impl sqlez::bindable::Bind for SerializedPixels { fn bind( &self, statement: &sqlez::statement::Statement, start_index: i32, ) -> anyhow::Result<i32> { let this: i32 = u32::from(self.0) as _; this.bind(statement, start_index) } } pub struct WorkspaceDb(ThreadSafeConnection); impl Domain for WorkspaceDb { const NAME: &str = stringify!(WorkspaceDb); const MIGRATIONS: &[&str] = &[ sql!( CREATE TABLE workspaces( workspace_id INTEGER PRIMARY KEY, workspace_location BLOB UNIQUE, dock_visible INTEGER, // Deprecated. Preserving so users can downgrade Zed. dock_anchor TEXT, // Deprecated. Preserving so users can downgrade Zed. dock_pane INTEGER, // Deprecated. Preserving so users can downgrade Zed. left_sidebar_open INTEGER, // Boolean timestamp TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL, FOREIGN KEY(dock_pane) REFERENCES panes(pane_id) ) STRICT; CREATE TABLE pane_groups( group_id INTEGER PRIMARY KEY, workspace_id INTEGER NOT NULL, parent_group_id INTEGER, // NULL indicates that this is a root node position INTEGER, // NULL indicates that this is a root node axis TEXT NOT NULL, // Enum: 'Vertical' / 'Horizontal' FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) ON DELETE CASCADE ON UPDATE CASCADE, FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE ) STRICT; CREATE TABLE panes( pane_id INTEGER PRIMARY KEY, workspace_id INTEGER NOT NULL, active INTEGER NOT NULL, // Boolean FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) ON DELETE CASCADE ON UPDATE CASCADE ) STRICT; CREATE TABLE center_panes( pane_id INTEGER PRIMARY KEY, parent_group_id INTEGER, // NULL means that this is a root pane position INTEGER, // NULL means that this is a root pane FOREIGN KEY(pane_id) REFERENCES panes(pane_id) ON DELETE CASCADE, FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE ) STRICT; CREATE TABLE items( item_id INTEGER NOT NULL, // This is the item's view id, so this is not unique workspace_id INTEGER NOT NULL, pane_id INTEGER NOT NULL, kind TEXT NOT NULL, position INTEGER NOT NULL, active INTEGER NOT NULL, FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) ON DELETE CASCADE ON UPDATE CASCADE, FOREIGN KEY(pane_id) REFERENCES panes(pane_id) ON DELETE CASCADE, PRIMARY KEY(item_id, workspace_id) ) STRICT; ), sql!( ALTER TABLE workspaces ADD COLUMN window_state TEXT; ALTER TABLE workspaces ADD COLUMN window_x REAL; ALTER TABLE workspaces ADD COLUMN window_y REAL; ALTER TABLE workspaces ADD COLUMN window_width REAL; ALTER TABLE workspaces ADD COLUMN window_height REAL; ALTER TABLE workspaces ADD COLUMN display BLOB; ), // Drop foreign key constraint from workspaces.dock_pane to panes table. sql!( CREATE TABLE workspaces_2( workspace_id INTEGER PRIMARY KEY, workspace_location BLOB UNIQUE, dock_visible INTEGER, // Deprecated. Preserving so users can downgrade Zed. dock_anchor TEXT, // Deprecated. Preserving so users can downgrade Zed. dock_pane INTEGER, // Deprecated. Preserving so users can downgrade Zed. left_sidebar_open INTEGER, // Boolean timestamp TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL, window_state TEXT, window_x REAL, window_y REAL, window_width REAL, window_height REAL, display BLOB ) STRICT; INSERT INTO workspaces_2 SELECT * FROM workspaces; DROP TABLE workspaces; ALTER TABLE workspaces_2 RENAME TO workspaces; ), // Add panels related information sql!( ALTER TABLE workspaces ADD COLUMN left_dock_visible INTEGER; //bool ALTER TABLE workspaces ADD COLUMN left_dock_active_panel TEXT; ALTER TABLE workspaces ADD COLUMN right_dock_visible INTEGER; //bool ALTER TABLE workspaces ADD COLUMN right_dock_active_panel TEXT; ALTER TABLE workspaces ADD COLUMN bottom_dock_visible INTEGER; //bool ALTER TABLE workspaces ADD COLUMN bottom_dock_active_panel TEXT; ), // Add panel zoom persistence sql!( ALTER TABLE workspaces ADD COLUMN left_dock_zoom INTEGER; //bool ALTER TABLE workspaces ADD COLUMN right_dock_zoom INTEGER; //bool ALTER TABLE workspaces ADD COLUMN bottom_dock_zoom INTEGER; //bool ), // Add pane group flex data sql!( ALTER TABLE pane_groups ADD COLUMN flexes TEXT; ), // Add fullscreen field to workspace