Make 'wrap selections in tag' work with line selection mode (#41030)

Lukas and Conrad Irwin created

The `wrap selections in tag` action currently did not take line_mode
into account, which means when selecting lines with `shift-v`, the
start/end tags would be inserted into the middle of the selection (where
the cursor sits)


https://github.com/user-attachments/assets/a1cbf3da-d52a-42e2-aecf-1a7b6d1dbb32

This PR fixes this behaviour by checking if the selection uses line_mode
and then adjusting start and end points accordingly.

NOTE: I looked into amending the test cases for this, but I am unsure
how to express line mode with range markers. I would appreciate some
guidance on this and then I am happy to add test cases.

After:


https://github.com/user-attachments/assets/a212c41f-b0db-4f50-866f-fced7bc677ca

Release Notes:

- Fixed `Editor: wrap selection in tags` when in vim visual line mode

---------

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

Change summary

crates/editor/src/editor.rs |  2 
crates/vim/src/test.rs      | 54 ++++++++++++++++++++++++++++++++++++--
2 files changed, 51 insertions(+), 5 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -10522,7 +10522,7 @@ impl Editor {
 
         for selection in self
             .selections
-            .all::<Point>(&self.display_snapshot(cx))
+            .all_adjusted(&self.display_snapshot(cx))
             .iter()
         {
             let Some(wrap_config) = snapshot

crates/vim/src/test.rs 🔗

@@ -2,19 +2,21 @@ mod neovim_backed_test_context;
 mod neovim_connection;
 mod vim_test_context;
 
-use std::time::Duration;
+use std::{sync::Arc, time::Duration};
 
 use collections::HashMap;
 use command_palette::CommandPalette;
 use editor::{
-    AnchorRangeExt, DisplayPoint, Editor, EditorMode, MultiBuffer, actions::DeleteLine,
-    code_context_menus::CodeContextMenu, display_map::DisplayRow,
+    AnchorRangeExt, DisplayPoint, Editor, EditorMode, MultiBuffer,
+    actions::{DeleteLine, WrapSelectionsInTag},
+    code_context_menus::CodeContextMenu,
+    display_map::DisplayRow,
     test::editor_test_context::EditorTestContext,
 };
 use futures::StreamExt;
 use gpui::{KeyBinding, Modifiers, MouseButton, TestAppContext, px};
 use itertools::Itertools;
-use language::Point;
+use language::{Language, LanguageConfig, Point};
 pub use neovim_backed_test_context::*;
 use settings::SettingsStore;
 use ui::Pixels;
@@ -2319,3 +2321,47 @@ async fn test_clipping_on_mode_change(cx: &mut gpui::TestAppContext) {
         Mode::Normal,
     );
 }
+
+#[gpui::test]
+async fn test_wrap_selections_in_tag_line_mode(cx: &mut gpui::TestAppContext) {
+    let mut cx = VimTestContext::new(cx, true).await;
+
+    let js_language = Arc::new(Language::new(
+        LanguageConfig {
+            name: "JavaScript".into(),
+            wrap_characters: Some(language::WrapCharactersConfig {
+                start_prefix: "<".into(),
+                start_suffix: ">".into(),
+                end_prefix: "</".into(),
+                end_suffix: ">".into(),
+            }),
+            ..LanguageConfig::default()
+        },
+        None,
+    ));
+
+    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
+
+    cx.set_state(
+        indoc! {
+        "
+        ˇaaaaa
+        bbbbb
+        "
+        },
+        Mode::Normal,
+    );
+
+    cx.simulate_keystrokes("shift-v j");
+    cx.dispatch_action(WrapSelectionsInTag);
+
+    cx.assert_state(
+        indoc! {
+            "
+            <ˇ>aaaaa
+            bbbbb</ˇ>
+            "
+        },
+        Mode::VisualLine,
+    );
+}