From 0ee6ca1767bf45e1921a4c085e5b7cbcfe3620e0 Mon Sep 17 00:00:00 2001 From: Dino Date: Fri, 24 Oct 2025 15:27:34 +0100 Subject: [PATCH] project: Fix inability to open file after save as (#41012) Update `project::buffer_store::BufferStore.save_buffer_as` in order to correctly update the `path_to_buffer_id` hash map, ensuring that the currently open file's path is dissociated from the buffer's id, to prevent the new buffer from being open when trying to open the original file. Closes #29783 Release Notes: - Fixed issue where using `workspace: save as` would prevent users from opening the original file from which the new file was created --------- Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> --- crates/project/src/buffer_store.rs | 9 +++- crates/project/src/project_tests.rs | 67 +++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/crates/project/src/buffer_store.rs b/crates/project/src/buffer_store.rs index 442cd35dc1b171a1510439f5314d3f543293350f..b9249d36e2ca8da6b17f342a8db9f3dcca113515 100644 --- a/crates/project/src/buffer_store.rs +++ b/crates/project/src/buffer_store.rs @@ -909,7 +909,14 @@ impl BufferStore { }; cx.spawn(async move |this, cx| { task.await?; - this.update(cx, |_, cx| { + this.update(cx, |this, cx| { + old_file.clone().and_then(|file| { + this.path_to_buffer_id.remove(&ProjectPath { + worktree_id: file.worktree_id(cx), + path: file.path().clone(), + }) + }); + cx.emit(BufferStoreEvent::BufferChangedFilePath { buffer, old_file }); }) }) diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 859fe02cfa70d035a347c62ba7cbe93f250b674a..e3714cddf15d7623ab32403ea9cdd889c27abedc 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -4251,6 +4251,73 @@ async fn test_save_as(cx: &mut gpui::TestAppContext) { assert_eq!(opened_buffer, buffer); } +#[gpui::test] +async fn test_save_as_existing_file(cx: &mut gpui::TestAppContext) { + init_test(cx); + + let fs = FakeFs::new(cx.executor()); + let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await; + + fs.insert_tree( + path!("/dir"), + json!({ + "data_a.txt": "data about a" + }), + ) + .await; + + let buffer = project + .update(cx, |project, cx| { + project.open_local_buffer(path!("/dir/data_a.txt"), cx) + }) + .await + .unwrap(); + + buffer.update(cx, |buffer, cx| { + buffer.edit([(11..12, "b")], None, cx); + }); + + // Save buffer's contents as a new file and confirm that the buffer's now + // associated with `data_b.txt` instead of `data_a.txt`, confirming that the + // file associated with the buffer has now been updated to `data_b.txt` + project + .update(cx, |project, cx| { + let worktree_id = project.worktrees(cx).next().unwrap().read(cx).id(); + let new_path = ProjectPath { + worktree_id, + path: rel_path("data_b.txt").into(), + }; + + project.save_buffer_as(buffer.clone(), new_path, cx) + }) + .await + .unwrap(); + + buffer.update(cx, |buffer, cx| { + assert_eq!( + buffer.file().unwrap().full_path(cx), + Path::new("dir/data_b.txt") + ) + }); + + // Open the original `data_a.txt` file, confirming that its contents are + // unchanged and the resulting buffer's associated file is `data_a.txt`. + let original_buffer = project + .update(cx, |project, cx| { + project.open_local_buffer(path!("/dir/data_a.txt"), cx) + }) + .await + .unwrap(); + + original_buffer.update(cx, |buffer, cx| { + assert_eq!(buffer.text(), "data about a"); + assert_eq!( + buffer.file().unwrap().full_path(cx), + Path::new("dir/data_a.txt") + ) + }); +} + #[gpui::test(retries = 5)] async fn test_rescan_and_remote_updates(cx: &mut gpui::TestAppContext) { use worktree::WorktreeModelHandle as _;