@@ -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,
},
@@ -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.