@@ -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<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,
@@ -1381,24 +1501,6 @@ impl WorkspaceDb {
}
}
- pub(crate) fn last_window(
- &self,
- ) -> anyhow::Result<(Option<Uuid>, Option<SerializedWindowBounds>)> {
- let mut prepared_query =
- self.select::<(Option<Uuid>, Option<SerializedWindowBounds>)>(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
@@ -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)
}