From d6bd3661e2ddbc0b38e4acf04bd29682d2c4feba Mon Sep 17 00:00:00 2001 From: Pedro Paulo Magno <34977611+ppfmagno@users.noreply.github.com> Date: Thu, 22 Jan 2026 13:35:59 -0300 Subject: [PATCH] workspace: Persist dock state for empty workspaces (#45966) Closes #4568 Release Notes: - Fixed project-less workspaces not persisting their dock state Co-authored-by: Kirill Bulatov --- crates/workspace/src/persistence.rs | 19 ++++++++++ crates/workspace/src/persistence/model.rs | 5 ++- crates/workspace/src/workspace.rs | 46 ++++++++++++++++++++++- 3 files changed, 67 insertions(+), 3 deletions(-) diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index adb6d7a95cb62dc211fbbd68eb3a02de644f6b30..c79fd2f3d2d48e54ed9f64832a28d2a3563dc710 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -281,6 +281,25 @@ impl From for WindowBounds { } } +const DEFAULT_DOCK_STATE_KEY: &str = "default_dock_state"; + +pub fn read_default_dock_state() -> Option { + let json_str = KEY_VALUE_STORE + .read_kvp(DEFAULT_DOCK_STATE_KEY) + .log_err() + .flatten()?; + + serde_json::from_str::(&json_str).ok() +} + +pub async fn write_default_dock_state(docks: DockStructure) -> anyhow::Result<()> { + let json_str = serde_json::to_string(&docks)?; + KEY_VALUE_STORE + .write_kvp(DEFAULT_DOCK_STATE_KEY.to_string(), json_str) + .await?; + Ok(()) +} + #[derive(Debug)] pub struct Breakpoint { pub position: u32, diff --git a/crates/workspace/src/persistence/model.rs b/crates/workspace/src/persistence/model.rs index 08a3adf9ebd7fa49a5f8fb86eec65c66deb00421..417896c584a1906f5d2f712a864cb2807c69af0a 100644 --- a/crates/workspace/src/persistence/model.rs +++ b/crates/workspace/src/persistence/model.rs @@ -15,6 +15,7 @@ use gpui::{AsyncWindowContext, Entity, WeakEntity}; use language::{Toolchain, ToolchainScope}; use project::{Project, debugger::breakpoint_store::SourceBreakpoint}; use remote::RemoteConnectionOptions; +use serde::{Deserialize, Serialize}; use std::{ collections::BTreeMap, path::{Path, PathBuf}, @@ -64,7 +65,7 @@ pub(crate) struct SerializedWorkspace { pub(crate) window_id: Option, } -#[derive(Debug, PartialEq, Clone, Default)] +#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize)] pub struct DockStructure { pub(crate) left: DockData, pub(crate) right: DockData, @@ -114,7 +115,7 @@ impl Bind for DockStructure { } } -#[derive(Debug, PartialEq, Clone, Default)] +#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize)] pub struct DockData { pub(crate) visible: bool, pub(crate) active_panel: Option, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 5e418071a500947554e1b8e4b00b75205193d73a..001cc349492b4dc272d52fa73eafa58bf73ae4ac 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1823,6 +1823,15 @@ impl Workspace { }; notify_if_database_failed(window, cx); + // Check if this is an empty workspace (no paths to open) + // An empty workspace is one where project_paths is empty + let is_empty_workspace = project_paths.is_empty(); + // Check if serialized workspace has paths before it's moved + let serialized_workspace_has_paths = serialized_workspace + .as_ref() + .map(|ws| !ws.paths.is_empty()) + .unwrap_or(false); + let opened_items = window .update(cx, |_workspace, window, cx| { open_items(serialized_workspace, project_paths, window, cx) @@ -1830,6 +1839,32 @@ impl Workspace { .await .unwrap_or_default(); + // Restore default dock state for empty workspaces + // Only restore if: + // 1. This is an empty workspace (no paths), AND + // 2. The serialized workspace either doesn't exist or has no paths + if is_empty_workspace && !serialized_workspace_has_paths { + if let Some(default_docks) = persistence::read_default_dock_state() { + window + .update(cx, |workspace, window, cx| { + for (dock, serialized_dock) in [ + (&mut workspace.right_dock, default_docks.right), + (&mut workspace.left_dock, default_docks.left), + (&mut workspace.bottom_dock, default_docks.bottom), + ] + .iter_mut() + { + dock.update(cx, |dock, cx| { + dock.serialized_dock = Some(serialized_dock.clone()); + dock.restore_state(window, cx); + }); + } + cx.notify(); + }) + .log_err(); + } + } + window .update(cx, |workspace, window, cx| { window.activate_window(); @@ -5858,6 +5893,8 @@ impl Workspace { WorkspaceLocation::DetachFromSession => { let window_bounds = SerializedWindowBounds(window.window_bounds()); let display = window.display(cx).and_then(|d| d.uuid().ok()); + // Save dock state for empty local workspaces + let docks = build_serialized_docks(self, window, cx); window.spawn(cx, async move |_| { persistence::DB .set_window_open_status( @@ -5871,9 +5908,16 @@ impl Workspace { .set_session_id(database_id, None) .await .log_err(); + persistence::write_default_dock_state(docks).await.log_err(); + }) + } + WorkspaceLocation::None => { + // Save dock state for empty non-local workspaces + let docks = build_serialized_docks(self, window, cx); + window.spawn(cx, async move |_| { + persistence::write_default_dock_state(docks).await.log_err(); }) } - WorkspaceLocation::None => Task::ready(()), } }