From 1cd3249ca9dfbeb7f8ee9f20256b4f01f55c9293 Mon Sep 17 00:00:00 2001 From: saberoueslati Date: Wed, 15 Apr 2026 08:40:22 +0100 Subject: [PATCH] editor: Fix stale session path for renamed files (#52539) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Context When a file is renamed via the project panel while open in an editor, Zed would restore the old (no longer existing) path on reload . On rename, `language::Buffer` emits `BufferEvent::FileHandleChanged`, which propagates to `multi_buffer::Event::FileHandleChanged` → `EditorEvent::TitleChanged`. However, `should_serialize()` only matched `Saved | DirtyChanged | BufferEdited`, so no re-serialization was triggered and the `editors` DB table retained the stale `abs_path`. The fix adds a dedicated `EditorEvent::FileHandleChanged` variant, emits it alongside `TitleChanged` when the buffer's file handle changes, and adds it to `should_serialize()`. Since `Editor::serialize()` already reads the current path from the buffer at call time, this naturally writes the new path to the DB. Closes #51629 ## How to Review Three files edited: - `editor.rs`: splits `FileHandleChanged` into its own arm and emits the new event variant - `items.rs`: adds `FileHandleChanged` to `should_serialize` - `items.rs` (test): `test_file_handle_changed_on_rename` does a full rename via `Project::rename_entry` and asserts the event fires and the buffer path updates ## Self-Review Checklist - [x] I've reviewed my own diff for quality, security, and reliability - [ ] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - Fixed renamed files being reopened with their old path after a restart **Important remark :** This pull request is a follow-up on the review of @SomeoneToIgnore on this pull request, https://github.com/zed-industries/zed/pull/51717, which was inadvertently closed because I mistakenly deleted my previous fork of the zed repo, sorry for any inconvenience caused by this Manual test video below : [Screencast from 2026-03-16 23-28-46.webm](https://github.com/user-attachments/assets/ff2e3259-ae26-4655-83b8-f693e84306d2) --- crates/editor/src/editor.rs | 11 +++-- crates/editor/src/items.rs | 80 ++++++++++++++++++++++++++++++++++++- 2 files changed, 87 insertions(+), 4 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 2878c8f3b88e4c9beee65dcddabe0b3d9fa1de8f..fbafb9a8bdae135230a2041def0de592cf0f52d0 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -24592,9 +24592,13 @@ impl Editor { } multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged), multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved), - multi_buffer::Event::FileHandleChanged - | multi_buffer::Event::Reloaded - | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged), + multi_buffer::Event::FileHandleChanged => { + cx.emit(EditorEvent::TitleChanged); + cx.emit(EditorEvent::FileHandleChanged); + } + multi_buffer::Event::Reloaded | multi_buffer::Event::BufferDiffChanged => { + cx.emit(EditorEvent::TitleChanged) + } multi_buffer::Event::DiagnosticsUpdated => { self.update_diagnostics_state(window, cx); } @@ -28461,6 +28465,7 @@ pub enum EditorEvent { DirtyChanged, Saved, TitleChanged, + FileHandleChanged, SelectionsChanged { local: bool, }, diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 9d062391a5c9ac0d8c0adcfa00a51269111d1e14..87f97ad8e4e60ecd42708d7cb11e8cb647508f82 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -1415,7 +1415,10 @@ impl SerializableItem for Editor { self.should_serialize_buffer() && matches!( event, - EditorEvent::Saved | EditorEvent::DirtyChanged | EditorEvent::BufferEdited + EditorEvent::Saved + | EditorEvent::DirtyChanged + | EditorEvent::BufferEdited + | EditorEvent::FileHandleChanged ) } } @@ -2396,6 +2399,81 @@ mod tests { } } + // Verify that renaming an open file emits EditorEvent::FileHandleChanged so that + // the workspace re-serializes the editor with the updated path. + #[gpui::test] + async fn test_file_handle_changed_on_rename(cx: &mut gpui::TestAppContext) { + use serde_json::json; + use std::cell::RefCell; + use std::rc::Rc; + use util::rel_path::rel_path; + + init_test(cx, |_| {}); + + let fs = FakeFs::new(cx.executor()); + fs.insert_tree(path!("/root"), json!({ "file.rs": "fn main() {}" })) + .await; + + let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await; + + let buffer = project + .update(cx, |project, cx| { + project.open_local_buffer(path!("/root/file.rs"), cx) + }) + .await + .unwrap(); + + let received_file_handle_changed = Rc::new(RefCell::new(false)); + let (editor, cx) = cx.add_window_view({ + let project = project.clone(); + let received_file_handle_changed = received_file_handle_changed.clone(); + move |window, cx| { + let mut editor = Editor::for_buffer(buffer, Some(project), window, cx); + editor.set_should_serialize(true, cx); + let entity = cx.entity(); + cx.subscribe_in(&entity, window, move |_, _, event: &EditorEvent, _, _| { + if matches!(event, EditorEvent::FileHandleChanged) { + *received_file_handle_changed.borrow_mut() = true; + } + }) + .detach(); + editor + } + }); + + cx.run_until_parked(); + + let (entry_id, worktree_id) = project.update(cx, |project, cx| { + let worktree = project.worktrees(cx).next().unwrap(); + let worktree = worktree.read(cx); + let entry = worktree.entry_for_path(rel_path("file.rs")).unwrap(); + (entry.id, worktree.id()) + }); + + project + .update(cx, |project, cx| { + project.rename_entry(entry_id, (worktree_id, rel_path("renamed.rs")).into(), cx) + }) + .await + .unwrap(); + + cx.run_until_parked(); + + assert!( + *received_file_handle_changed.borrow(), + "EditorEvent::FileHandleChanged must be emitted when the open file is renamed" + ); + + editor.update(cx, |editor, cx| { + let buffer = editor.buffer().read(cx).as_singleton().unwrap(); + let path = buffer.read(cx).file().unwrap().path(); + assert!( + path.as_std_path().ends_with("renamed.rs"), + "buffer path must reflect the renamed file, got {path:?}" + ); + }); + } + // Regression test for https://github.com/zed-industries/zed/issues/35947 // Verifies that deserializing a non-worktree editor does not add the item // to any pane as a side effect.