From 9e69ac889c7fac29a88f5e213826ce3be4a2895b Mon Sep 17 00:00:00 2001 From: Mayank Verma Date: Tue, 25 Nov 2025 03:15:12 +0530 Subject: [PATCH] editor: Fix copy file actions not working in remote environments (#43362) Closes #42500 Release Notes: - Fixed all three editor actions not working in remote environments - `editor: copy file name` - `editor: copy file location` - `editor: copy file name without extension` Here's the before/after: https://github.com/user-attachments/assets/bfb03e99-2e1a-47a2-bd26-280180154fe3 --- crates/collab/src/tests/editor_tests.rs | 287 +++++++++++++++++++++++- crates/editor/src/editor.rs | 27 ++- 2 files changed, 302 insertions(+), 12 deletions(-) diff --git a/crates/collab/src/tests/editor_tests.rs b/crates/collab/src/tests/editor_tests.rs index 33f07bfb388763875565bc9e37bda363f02600f0..fe20ab935c9fb2ffd2c18962953f9d62ca06fb16 100644 --- a/crates/collab/src/tests/editor_tests.rs +++ b/crates/collab/src/tests/editor_tests.rs @@ -7,8 +7,9 @@ use editor::{ DocumentColorsRenderMode, Editor, FETCH_COLORS_DEBOUNCE_TIMEOUT, MultiBufferOffset, RowInfo, SelectionEffects, actions::{ - ConfirmCodeAction, ConfirmCompletion, ConfirmRename, ContextMenuFirst, - ExpandMacroRecursively, MoveToEnd, Redo, Rename, SelectAll, ToggleCodeActions, Undo, + ConfirmCodeAction, ConfirmCompletion, ConfirmRename, ContextMenuFirst, CopyFileLocation, + CopyFileName, CopyFileNameWithoutExtension, ExpandMacroRecursively, MoveToEnd, Redo, + Rename, SelectAll, ToggleCodeActions, Undo, }, test::{ editor_test_context::{AssertionContextManager, EditorTestContext}, @@ -4269,6 +4270,288 @@ async fn test_client_can_query_lsp_ext(cx_a: &mut TestAppContext, cx_b: &mut Tes }); } +#[gpui::test] +async fn test_copy_file_name_without_extension( + cx_a: &mut TestAppContext, + cx_b: &mut TestAppContext, +) { + let mut server = TestServer::start(cx_a.executor()).await; + let client_a = server.create_client(cx_a, "user_a").await; + let client_b = server.create_client(cx_b, "user_b").await; + server + .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)]) + .await; + + cx_b.update(editor::init); + + client_a + .fs() + .insert_tree( + path!("/root"), + json!({ + "src": { + "main.rs": indoc! {" + fn main() { + println!(\"Hello, world!\"); + } + "}, + } + }), + ) + .await; + + let (project_a, worktree_id) = client_a.build_local_project(path!("/root"), cx_a).await; + let active_call_a = cx_a.read(ActiveCall::global); + let project_id = active_call_a + .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) + .await + .unwrap(); + + let project_b = client_b.join_remote_project(project_id, cx_b).await; + + let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a); + let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); + + let editor_a = workspace_a + .update_in(cx_a, |workspace, window, cx| { + workspace.open_path( + (worktree_id, rel_path("src/main.rs")), + None, + true, + window, + cx, + ) + }) + .await + .unwrap() + .downcast::() + .unwrap(); + + let editor_b = workspace_b + .update_in(cx_b, |workspace, window, cx| { + workspace.open_path( + (worktree_id, rel_path("src/main.rs")), + None, + true, + window, + cx, + ) + }) + .await + .unwrap() + .downcast::() + .unwrap(); + + cx_a.run_until_parked(); + cx_b.run_until_parked(); + + editor_a.update_in(cx_a, |editor, window, cx| { + editor.copy_file_name_without_extension(&CopyFileNameWithoutExtension, window, cx); + }); + + assert_eq!( + cx_a.read_from_clipboard().and_then(|item| item.text()), + Some("main".to_string()) + ); + + editor_b.update_in(cx_b, |editor, window, cx| { + editor.copy_file_name_without_extension(&CopyFileNameWithoutExtension, window, cx); + }); + + assert_eq!( + cx_b.read_from_clipboard().and_then(|item| item.text()), + Some("main".to_string()) + ); +} + +#[gpui::test] +async fn test_copy_file_name(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { + let mut server = TestServer::start(cx_a.executor()).await; + let client_a = server.create_client(cx_a, "user_a").await; + let client_b = server.create_client(cx_b, "user_b").await; + server + .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)]) + .await; + + cx_b.update(editor::init); + + client_a + .fs() + .insert_tree( + path!("/root"), + json!({ + "src": { + "main.rs": indoc! {" + fn main() { + println!(\"Hello, world!\"); + } + "}, + } + }), + ) + .await; + + let (project_a, worktree_id) = client_a.build_local_project(path!("/root"), cx_a).await; + let active_call_a = cx_a.read(ActiveCall::global); + let project_id = active_call_a + .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) + .await + .unwrap(); + + let project_b = client_b.join_remote_project(project_id, cx_b).await; + + let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a); + let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); + + let editor_a = workspace_a + .update_in(cx_a, |workspace, window, cx| { + workspace.open_path( + (worktree_id, rel_path("src/main.rs")), + None, + true, + window, + cx, + ) + }) + .await + .unwrap() + .downcast::() + .unwrap(); + + let editor_b = workspace_b + .update_in(cx_b, |workspace, window, cx| { + workspace.open_path( + (worktree_id, rel_path("src/main.rs")), + None, + true, + window, + cx, + ) + }) + .await + .unwrap() + .downcast::() + .unwrap(); + + cx_a.run_until_parked(); + cx_b.run_until_parked(); + + editor_a.update_in(cx_a, |editor, window, cx| { + editor.copy_file_name(&CopyFileName, window, cx); + }); + + assert_eq!( + cx_a.read_from_clipboard().and_then(|item| item.text()), + Some("main.rs".to_string()) + ); + + editor_b.update_in(cx_b, |editor, window, cx| { + editor.copy_file_name(&CopyFileName, window, cx); + }); + + assert_eq!( + cx_b.read_from_clipboard().and_then(|item| item.text()), + Some("main.rs".to_string()) + ); +} + +#[gpui::test] +async fn test_copy_file_location(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { + let mut server = TestServer::start(cx_a.executor()).await; + let client_a = server.create_client(cx_a, "user_a").await; + let client_b = server.create_client(cx_b, "user_b").await; + server + .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)]) + .await; + + cx_b.update(editor::init); + + client_a + .fs() + .insert_tree( + path!("/root"), + json!({ + "src": { + "main.rs": indoc! {" + fn main() { + println!(\"Hello, world!\"); + } + "}, + } + }), + ) + .await; + + let (project_a, worktree_id) = client_a.build_local_project(path!("/root"), cx_a).await; + let active_call_a = cx_a.read(ActiveCall::global); + let project_id = active_call_a + .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) + .await + .unwrap(); + + let project_b = client_b.join_remote_project(project_id, cx_b).await; + + let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a); + let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); + + let editor_a = workspace_a + .update_in(cx_a, |workspace, window, cx| { + workspace.open_path( + (worktree_id, rel_path("src/main.rs")), + None, + true, + window, + cx, + ) + }) + .await + .unwrap() + .downcast::() + .unwrap(); + + let editor_b = workspace_b + .update_in(cx_b, |workspace, window, cx| { + workspace.open_path( + (worktree_id, rel_path("src/main.rs")), + None, + true, + window, + cx, + ) + }) + .await + .unwrap() + .downcast::() + .unwrap(); + + cx_a.run_until_parked(); + cx_b.run_until_parked(); + + editor_a.update_in(cx_a, |editor, window, cx| { + editor.change_selections(Default::default(), window, cx, |s| { + s.select_ranges([MultiBufferOffset(16)..MultiBufferOffset(16)]); + }); + editor.copy_file_location(&CopyFileLocation, window, cx); + }); + + assert_eq!( + cx_a.read_from_clipboard().and_then(|item| item.text()), + Some(format!("{}:2", path!("src/main.rs"))) + ); + + editor_b.update_in(cx_b, |editor, window, cx| { + editor.change_selections(Default::default(), window, cx, |s| { + s.select_ranges([MultiBufferOffset(16)..MultiBufferOffset(16)]); + }); + editor.copy_file_location(&CopyFileLocation, window, cx); + }); + + assert_eq!( + cx_b.read_from_clipboard().and_then(|item| item.text()), + Some(format!("{}:2", path!("src/main.rs"))) + ); +} + #[track_caller] fn tab_undo_assert( cx_a: &mut EditorTestContext, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index adb24900bf144b9cdedfb432e296a9a9e27a51c7..08627f1bd64be6e62581014628c57306df43623e 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -20234,18 +20234,20 @@ impl Editor { _: &mut Window, cx: &mut Context, ) { - if let Some(file) = self.target_file(cx) - && let Some(file_stem) = file.path().file_stem() - { + if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| { + let file = buffer.read(cx).file()?; + file.path().file_stem() + }) { cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string())); } } pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context) { - if let Some(file) = self.target_file(cx) - && let Some(name) = file.path().file_name() - { - cx.write_to_clipboard(ClipboardItem::new_string(name.to_string())); + if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| { + let file = buffer.read(cx).file()?; + Some(file.file_name(cx)) + }) { + cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string())); } } @@ -20519,9 +20521,14 @@ impl Editor { .start .row + 1; - if let Some(file) = self.target_file(cx) { - let path = file.path().display(file.path_style(cx)); - cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}"))); + if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| { + let project = self.project()?.read(cx); + let file = buffer.read(cx).file()?; + let path = file.path().display(project.path_style(cx)); + + Some(format!("{path}:{selection}")) + }) { + cx.write_to_clipboard(ClipboardItem::new_string(file_location)); } }