From 697a39c2511469e49e8af1974618d552410b1c38 Mon Sep 17 00:00:00 2001 From: Smit Barmase Date: Thu, 21 Aug 2025 20:19:17 +0530 Subject: [PATCH] Fix issue where renaming a file would not update imports in related files if they are not open (#36681) Closes #34445 Now we open a multi-buffer consisting of buffers that have updated, renamed file imports. Only local is handled, for now. Release Notes: - Fixed an issue where renaming a file would not update imports in related files if they are not already open. --- crates/editor/src/editor.rs | 58 ++++++++++++++++++++++++++++-- crates/project/src/buffer_store.rs | 11 +++++- crates/project/src/lsp_store.rs | 18 ++++++---- crates/project/src/project.rs | 11 ++++-- 4 files changed, 87 insertions(+), 11 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 05ee295360..2af8e6c0e4 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1900,6 +1900,60 @@ impl Editor { editor.update_lsp_data(false, Some(*buffer_id), window, cx); } } + + project::Event::EntryRenamed(transaction) => { + let Some(workspace) = editor.workspace() else { + return; + }; + let Some(active_editor) = workspace.read(cx).active_item_as::(cx) + else { + return; + }; + if active_editor.entity_id() == cx.entity_id() { + let edited_buffers_already_open = { + let other_editors: Vec> = workspace + .read(cx) + .panes() + .iter() + .flat_map(|pane| pane.read(cx).items_of_type::()) + .filter(|editor| editor.entity_id() != cx.entity_id()) + .collect(); + + transaction.0.keys().all(|buffer| { + other_editors.iter().any(|editor| { + let multi_buffer = editor.read(cx).buffer(); + multi_buffer.read(cx).is_singleton() + && multi_buffer.read(cx).as_singleton().map_or( + false, + |singleton| { + singleton.entity_id() == buffer.entity_id() + }, + ) + }) + }) + }; + + if !edited_buffers_already_open { + let workspace = workspace.downgrade(); + let transaction = transaction.clone(); + cx.defer_in(window, move |_, window, cx| { + cx.spawn_in(window, async move |editor, cx| { + Self::open_project_transaction( + &editor, + workspace, + transaction, + "Rename".to_string(), + cx, + ) + .await + .ok() + }) + .detach(); + }); + } + } + } + _ => {} }, )); @@ -6282,7 +6336,7 @@ impl Editor { } pub async fn open_project_transaction( - this: &WeakEntity, + editor: &WeakEntity, workspace: WeakEntity, transaction: ProjectTransaction, title: String, @@ -6300,7 +6354,7 @@ impl Editor { if let Some((buffer, transaction)) = entries.first() { if entries.len() == 1 { - let excerpt = this.update(cx, |editor, cx| { + let excerpt = editor.update(cx, |editor, cx| { editor .buffer() .read(cx) diff --git a/crates/project/src/buffer_store.rs b/crates/project/src/buffer_store.rs index a171b193d0..295bad6e59 100644 --- a/crates/project/src/buffer_store.rs +++ b/crates/project/src/buffer_store.rs @@ -88,9 +88,18 @@ pub enum BufferStoreEvent { }, } -#[derive(Default, Debug)] +#[derive(Default, Debug, Clone)] pub struct ProjectTransaction(pub HashMap, language::Transaction>); +impl PartialEq for ProjectTransaction { + fn eq(&self, other: &Self) -> bool { + self.0.len() == other.0.len() + && self.0.iter().all(|(buffer, transaction)| { + other.0.get(buffer).is_some_and(|t| t.id == transaction.id) + }) + } +} + impl EventEmitter for BufferStore {} impl RemoteBufferStore { diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 072f4396c1..709bd10358 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -8762,7 +8762,7 @@ impl LspStore { (root_path.join(&old_path), root_path.join(&new_path)) }; - Self::will_rename_entry( + let _transaction = Self::will_rename_entry( this.downgrade(), worktree_id, &old_abs_path, @@ -9224,7 +9224,7 @@ impl LspStore { new_path: &Path, is_dir: bool, cx: AsyncApp, - ) -> Task<()> { + ) -> Task { let old_uri = lsp::Url::from_file_path(old_path).ok().map(String::from); let new_uri = lsp::Url::from_file_path(new_path).ok().map(String::from); cx.spawn(async move |cx| { @@ -9257,7 +9257,7 @@ impl LspStore { .log_err() .flatten()?; - LocalLspStore::deserialize_workspace_edit( + let transaction = LocalLspStore::deserialize_workspace_edit( this.upgrade()?, edit, false, @@ -9265,8 +9265,8 @@ impl LspStore { cx, ) .await - .ok(); - Some(()) + .ok()?; + Some(transaction) } }); tasks.push(apply_edit); @@ -9276,11 +9276,17 @@ impl LspStore { }) .ok() .flatten(); + let mut merged_transaction = ProjectTransaction::default(); for task in tasks { // Await on tasks sequentially so that the order of application of edits is deterministic // (at least with regards to the order of registration of language servers) - task.await; + if let Some(transaction) = task.await { + for (buffer, buffer_transaction) in transaction.0 { + merged_transaction.0.insert(buffer, buffer_transaction); + } + } } + merged_transaction }) } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index ee4bfcb8cc..9fd4eed641 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -327,6 +327,7 @@ pub enum Event { RevealInProjectPanel(ProjectEntryId), SnippetEdit(BufferId, Vec<(lsp::Range, Snippet)>), ExpandedAllForEntry(WorktreeId, ProjectEntryId), + EntryRenamed(ProjectTransaction), AgentLocationChanged, } @@ -2119,7 +2120,7 @@ impl Project { let is_root_entry = self.entry_is_worktree_root(entry_id, cx); let lsp_store = self.lsp_store().downgrade(); - cx.spawn(async move |_, cx| { + cx.spawn(async move |project, cx| { let (old_abs_path, new_abs_path) = { let root_path = worktree.read_with(cx, |this, _| this.abs_path())?; let new_abs_path = if is_root_entry { @@ -2129,7 +2130,7 @@ impl Project { }; (root_path.join(&old_path), new_abs_path) }; - LspStore::will_rename_entry( + let transaction = LspStore::will_rename_entry( lsp_store.clone(), worktree_id, &old_abs_path, @@ -2145,6 +2146,12 @@ impl Project { })? .await?; + project + .update(cx, |_, cx| { + cx.emit(Event::EntryRenamed(transaction)); + }) + .ok(); + lsp_store .read_with(cx, |this, _| { this.did_rename_entry(worktree_id, &old_abs_path, &new_abs_path, is_dir);