vim: Preserve trailing whitespace in inner text object selections (#24481)

5brian and Conrad Irwin created

Closes #24438

Changes: Adjusted loop to only trim whitespace between last newline and
closing marker, when using inner objects like `y/d/c i b`

| Start   | Fixed `vib`   | Previous `vib`   |
| ---------- | ---------- | ---------- |
|
![image](https://github.com/user-attachments/assets/3d64dd7d-ed3d-4a85-9f98-f2f83799a738)
|
![image](https://github.com/user-attachments/assets/841beb59-31b1-475e-93f0-f4deaf18939c)
|
![image](https://github.com/user-attachments/assets/736d4c6f-20e1-4563-9471-1e8195455df4)
|



Release Notes:

- vim: Preserve trailing whitespace in inner text object selections

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>

Change summary

crates/vim/src/object.rs                                  | 24 ++++++++
crates/vim/test_data/test_anybrackets_trailing_space.json | 11 ++++
2 files changed, 33 insertions(+), 2 deletions(-)

Detailed changes

crates/vim/src/object.rs 🔗

@@ -1339,14 +1339,20 @@ fn surrounding_markers(
             }
         }
 
+        let mut last_newline_end = None;
         for (ch, range) in movement::chars_before(map, closing.start) {
             if !ch.is_whitespace() {
                 break;
             }
-            if ch != '\n' {
-                closing.start = range.start
+            if ch == '\n' {
+                last_newline_end = Some(range.end);
+                break;
             }
         }
+        // Adjust closing.start to exclude whitespace after a newline, if present
+        if let Some(end) = last_newline_end {
+            closing.start = end;
+        }
     }
 
     let result = if around {
@@ -2254,6 +2260,20 @@ mod test {
         }
     }
 
+    #[gpui::test]
+    async fn test_anybrackets_trailing_space(cx: &mut gpui::TestAppContext) {
+        let mut cx = NeovimBackedTestContext::new(cx).await;
+
+        cx.set_shared_state("(trailingˇ whitespace          )")
+            .await;
+        cx.simulate_shared_keystrokes("v i b").await;
+        cx.shared_state().await.assert_matches();
+        cx.simulate_shared_keystrokes("escape y i b").await;
+        cx.shared_clipboard()
+            .await
+            .assert_eq("trailing whitespace          ");
+    }
+
     #[gpui::test]
     async fn test_tags(cx: &mut gpui::TestAppContext) {
         let mut cx = VimTestContext::new_html(cx).await;

crates/vim/test_data/test_anybrackets_trailing_space.json 🔗

@@ -0,0 +1,11 @@
+{"Put":{"state":"(trailingˇ whitespace          )"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"b"}
+{"Get":{"state":"(«trailing whitespace          ˇ»)","mode":"Visual"}}
+{"Key":"escape"}
+{"Key":"y"}
+{"Key":"i"}
+{"Key":"b"}
+{"Get":{"state":"(ˇtrailing whitespace          )","mode":"Normal"}}
+{"ReadRegister":{"name":"\"","value":"trailing whitespace          "}}