From 12bc8907d926ca719ae182ac3aba0ba7e226f9d7 Mon Sep 17 00:00:00 2001 From: vipex <101529155+vipexv@users.noreply.github.com> Date: Sat, 12 Jul 2025 00:23:04 +0200 Subject: [PATCH] Recall empty, unsaved buffers on app load (#33475) Closes #33342 This PR implements serialization of pinned tabs regardless of their state (empty, untitled, etc.) The root cause was that empty untitled tabs were being skipped during serialization but their pinned state was still being persisted, leading to a mismatch between the stored pinned count and actual restorable tabs, this issue lead to a crash which was patched by @JosephTLyons, but this PR aims to be a proper fix. **Note**: I'm still evaluating the best approach for this fix. Currently exploring whether it's necessary to store the pinned state in the database schema or if there's a simpler solution that doesn't require schema changes. --- **Edit from Joseph** We ended up going with altering our recall logic, where we always restore all editors, even those that are new, empty, and unsaved. This prevents the crash that #33335 patched because we are no longer skipping the restoration of pinned editors that have no text and haven't been saved, throwing off the count dealing with the number of pinned items. This solution is rather simple, but I think it's fine. We simply just restore everything the same, no conditional dropping of anything. This is also consistent with VS Code, which also restores all editors, regardless of whether or not a new, unsaved buffers have content or not. https://github.com/zed-industries/zed/tree/alt-solution-for-%2333342 Release Notes: - N/A --------- Co-authored-by: Joseph T. Lyons --- crates/editor/src/items.rs | 48 +++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 4e6e471f48..ca635a2132 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -1226,7 +1226,20 @@ impl SerializableItem for Editor { abs_path: None, contents: None, .. - } => Task::ready(Err(anyhow!("No path or contents found for buffer"))), + } => window.spawn(cx, async move |cx| { + let buffer = project + .update(cx, |project, cx| project.create_buffer(cx))? + .await?; + + cx.update(|window, cx| { + cx.new(|cx| { + let mut editor = Editor::for_buffer(buffer, Some(project), window, cx); + + editor.read_metadata_from_db(item_id, workspace_id, window, cx); + editor + }) + }) + }), } } @@ -2098,5 +2111,38 @@ mod tests { assert!(editor.has_conflict(cx)); // The editor should have a conflict }); } + + // Test case 5: Deserialize with no path, no content, no language, and no old mtime (new, empty, unsaved buffer) + { + let project = Project::test(fs.clone(), [path!("/file.rs").as_ref()], cx).await; + let (workspace, cx) = + cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx)); + + let workspace_id = workspace::WORKSPACE_DB.next_id().await.unwrap(); + + let item_id = 10000 as ItemId; + let serialized_editor = SerializedEditor { + abs_path: None, + contents: None, + language: None, + mtime: None, + }; + + DB.save_serialized_editor(item_id, workspace_id, serialized_editor) + .await + .unwrap(); + + let deserialized = + deserialize_editor(item_id, workspace_id, workspace, project, cx).await; + + deserialized.update(cx, |editor, cx| { + assert_eq!(editor.text(cx), ""); + assert!(!editor.is_dirty(cx)); + assert!(!editor.has_conflict(cx)); + + let buffer = editor.buffer().read(cx).as_singleton().unwrap().read(cx); + assert!(buffer.file().is_none()); + }); + } } }