From acfc71a42304a19d1c0b5753d3513c0ec0fa1547 Mon Sep 17 00:00:00 2001 From: Kavi Bidlack <104799865+kbidlack@users.noreply.github.com> Date: Wed, 14 Jan 2026 02:27:50 -0500 Subject: [PATCH] editor: Fix clipboard line numbers when copying from multibuffer (#46743) Release Notes: - When copying and pasting from a multibuffer view (e.g. into the agent panel), the line numbers from the selection corresponded to the line numbers in the multibuffer view (incorrect), instead of the actual line numbers from the file itself. - I also handled the case in which the selection spans multiple excerpts (different files in the multibuffer) by just returning None for that case, but maybe it should instead be split into two selections? Left is before (bugged, should be lines 8:16, instead it is 38:46), right is after (fixed).
--- crates/editor/src/editor.rs | 10 +++++- crates/editor/src/editor_tests.rs | 59 +++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index d8434ffa005569d903fba8b44b95e05bfe47c917..19c6ca50b364531602b328e76fd00d7f76e1d75f 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1644,7 +1644,15 @@ impl ClipboardSelection { project.absolute_path(&project_path, cx) }); - let line_range = file_path.as_ref().map(|_| range.start.row..=range.end.row); + let line_range = file_path.as_ref().and_then(|_| { + let (_, start_point, start_excerpt_id) = buffer.point_to_buffer_point(range.start)?; + let (_, end_point, end_excerpt_id) = buffer.point_to_buffer_point(range.end)?; + if start_excerpt_id == end_excerpt_id { + Some(start_point.row..=end_point.row) + } else { + None + } + }); Self { len, diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 0d0b1435325bca20f2dc330ad56190d9cad08c9b..7dc8a20a10191f716eb41f3a7686d1d3eff22871 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -7631,6 +7631,65 @@ async fn test_copy_trim_line_mode(cx: &mut TestAppContext) { ); } +#[gpui::test] +async fn test_clipboard_line_numbers_from_multibuffer(cx: &mut TestAppContext) { + init_test(cx, |_| {}); + + let fs = FakeFs::new(cx.executor()); + fs.insert_file( + path!("/file.txt"), + "first line\nsecond line\nthird line\nfourth line\nfifth line\n".into(), + ) + .await; + + let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await; + + let buffer = project + .update(cx, |project, cx| { + project.open_local_buffer(path!("/file.txt"), cx) + }) + .await + .unwrap(); + + let multibuffer = cx.new(|cx| { + let mut multibuffer = MultiBuffer::new(ReadWrite); + multibuffer.push_excerpts( + buffer.clone(), + [ExcerptRange::new(Point::new(2, 0)..Point::new(5, 0))], + cx, + ); + multibuffer + }); + + let (editor, cx) = cx.add_window_view(|window, cx| { + build_editor_with_project(project.clone(), multibuffer, window, cx) + }); + + editor.update_in(cx, |editor, window, cx| { + assert_eq!(editor.text(cx), "third line\nfourth line\nfifth line\n"); + + editor.select_all(&SelectAll, window, cx); + editor.copy(&Copy, window, cx); + }); + + let clipboard_selections: Option> = cx + .read_from_clipboard() + .and_then(|item| item.entries().first().cloned()) + .and_then(|entry| match entry { + gpui::ClipboardEntry::String(text) => text.metadata_json(), + _ => None, + }); + + let selections = clipboard_selections.expect("should have clipboard selections"); + assert_eq!(selections.len(), 1); + let selection = &selections[0]; + assert_eq!( + selection.line_range, + Some(2..=5), + "line range should be from original file (rows 2-5), not multibuffer rows (0-2)" + ); +} + #[gpui::test] async fn test_paste_multiline(cx: &mut TestAppContext) { init_test(cx, |_| {});