editor: Fix crash when pasting after copy and trim in visual line mode (#46640)

Dino created

When using the `editor::actions::CopyAndTrim` action with a multi-line
selection in vim's Visual Line mode, pasting would crash Zed.

The bug occurred because trimming splits a selection into per-line
ranges, creating multiple `editor::ClipboardSelection` entries. However,
when `is_entire_line` was true (Visual Line mode), no newline separators
were added between these entries in the clipboard text. The paste code
then assumed separators existed and read past the end of the text.

The fix ensures newline separators are always added between trimmed line
ranges, regardless of whether the original selection was in line mode.

Closes #46616 

Release Notes:

- Fixed a crash when pasting after using `editor: copy and trim` in
vim's Visual Line mode

Change summary

crates/editor/src/editor.rs       |  5 +++--
crates/editor/src/editor_tests.rs | 19 +++++++++++++++++++
2 files changed, 22 insertions(+), 2 deletions(-)

Detailed changes

crates/editor/src/editor.rs ๐Ÿ”—

@@ -13272,13 +13272,14 @@ impl Editor {
                     trimmed_selections.push(start..end);
                 }
 
+                let is_multiline_trim = trimmed_selections.len() > 1;
                 for trimmed_range in trimmed_selections {
                     if is_first {
                         is_first = false;
-                    } else if !prev_selection_was_entire_line {
+                    } else if is_multiline_trim || !prev_selection_was_entire_line {
                         text += "\n";
                     }
-                    prev_selection_was_entire_line = is_entire_line;
+                    prev_selection_was_entire_line = is_entire_line && !is_multiline_trim;
                     let mut len = 0;
                     for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
                         text.push_str(chunk);

crates/editor/src/editor_tests.rs ๐Ÿ”—

@@ -7612,6 +7612,25 @@ if is_entire_line {
     );
 }
 
+#[gpui::test]
+async fn test_copy_trim_line_mode(cx: &mut TestAppContext) {
+    init_test(cx, |_| {});
+
+    let mut cx = EditorTestContext::new(cx).await;
+
+    cx.set_state(indoc! {"
+        ยซ    a
+            bห‡ยป
+    "});
+    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
+    cx.update_editor(|editor, window, cx| editor.copy_and_trim(&CopyAndTrim, window, cx));
+
+    assert_eq!(
+        cx.read_from_clipboard().and_then(|item| item.text()),
+        Some("a\nb\n".to_string())
+    );
+}
+
 #[gpui::test]
 async fn test_paste_multiline(cx: &mut TestAppContext) {
     init_test(cx, |_| {});