diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index dc113db68e33dc527e6b8d2cb66f644bcd83b661..4a8aab364db3ec37f7f089b8dc3df4ca1114ee28 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -11,6 +11,7 @@ use std::{ 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, @@ -27,6 +28,7 @@ use project::WorktreeId; use remote::{ DockerConnectionOptions, RemoteConnectionOptions, SshConnectionOptions, WslConnectionOptions, }; +use serde::{Deserialize, Serialize}; use sqlez::{ bindable::{Bind, Column, StaticColumnCount}, statement::Statement, @@ -163,6 +165,124 @@ impl Column for SerializedWindowBounds { } } +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 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 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, @@ -1381,24 +1501,6 @@ impl WorkspaceDb { } } - pub(crate) fn last_window( - &self, - ) -> anyhow::Result<(Option, Option)> { - let mut prepared_query = - self.select::<(Option, Option)>(sql!( - SELECT - display, - window_state, window_x, window_y, window_width, window_height - FROM workspaces - WHERE paths - IS NOT NULL - ORDER BY timestamp DESC - LIMIT 1 - ))?; - let result = prepared_query()?; - Ok(result.into_iter().next().unwrap_or((None, None))) - } - query! { pub async fn delete_workspace_by_id(id: WorkspaceId) -> Result<()> { DELETE FROM workspaces diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 00412cfb75fce58b19a697e283f77c5a57ebb683..5947cb1703b37017bf75bfe412603332b8d5016a 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1508,6 +1508,15 @@ impl Workspace { && let Ok(display_uuid) = display.uuid() { let window_bounds = window.inner_window_bounds(); + let has_paths = !this.root_paths(cx).is_empty(); + if !has_paths { + cx.background_executor() + .spawn(persistence::write_default_window_bounds( + window_bounds, + display_uuid, + )) + .detach_and_log_err(cx); + } if let Some(database_id) = workspace_id { cx.background_executor() .spawn(DB.set_window_open_status( @@ -1516,6 +1525,13 @@ impl Workspace { display_uuid, )) .detach_and_log_err(cx); + } else { + cx.background_executor() + .spawn(persistence::write_default_window_bounds( + window_bounds, + display_uuid, + )) + .detach_and_log_err(cx); } } this.bounds_save_task_queued.take(); @@ -1724,6 +1740,7 @@ impl Workspace { window } else { let window_bounds_override = window_bounds_env_override(); + let is_empty_workspace = project_paths.is_empty(); let (window_bounds, display) = if let Some(bounds) = window_bounds_override { (Some(WindowBounds::Windowed(bounds)), None) @@ -1736,6 +1753,13 @@ impl Workspace { } else { (None, None) } + } else if is_empty_workspace { + // Empty workspace - try to restore the last known no-project window bounds + if let Some((display, bounds)) = persistence::read_default_window_bounds() { + (Some(bounds), Some(display)) + } else { + (None, None) + } } else { // New window - let GPUI's default_bounds() handle cascading (None, None) @@ -8820,14 +8844,13 @@ pub fn remote_workspace_position_from_db( } else { let restorable_bounds = serialized_workspace .as_ref() - .and_then(|workspace| Some((workspace.display?, workspace.window_bounds?))) - .or_else(|| { - let (display, window_bounds) = DB.last_window().log_err()?; - Some((display?, window_bounds?)) - }); + .and_then(|workspace| { + Some((workspace.display?, workspace.window_bounds.map(|b| b.0)?)) + }) + .or_else(|| persistence::read_default_window_bounds()); - if let Some((serialized_display, serialized_status)) = restorable_bounds { - (Some(serialized_status.0), Some(serialized_display)) + if let Some((serialized_display, serialized_bounds)) = restorable_bounds { + (Some(serialized_bounds), Some(serialized_display)) } else { (None, None) }