vim: Make `vaf` include const for arrow functions in JS/TS/TSX (#45327)

Ben Kunkle created

Closes #24264

Release Notes:

- N/A *or* Added/Fixed/Improved ...

Change summary

crates/editor/src/test/editor_lsp_test_context.rs |  86 +++
crates/language/src/buffer_tests.rs               |  98 ++++
crates/language/src/syntax_map.rs                 |  61 ++
crates/languages/src/javascript/textobjects.scm   |  38 +
crates/languages/src/tsx/textobjects.scm          |  38 +
crates/languages/src/typescript/textobjects.scm   |  39 +
crates/vim/src/object.rs                          | 386 +++++++++++++++++
crates/vim/src/visual.rs                          |  16 
8 files changed, 742 insertions(+), 20 deletions(-)

Detailed changes

crates/editor/src/test/editor_lsp_test_context.rs 🔗

@@ -205,6 +205,49 @@ impl EditorLspTestContext {
                 (_ "{" "}" @end) @indent
                 (_ "(" ")" @end) @indent
                 "#})),
+            text_objects: Some(Cow::from(indoc! {r#"
+                (function_declaration
+                    body: (_
+                        "{"
+                        (_)* @function.inside
+                        "}")) @function.around
+
+                (method_definition
+                    body: (_
+                        "{"
+                        (_)* @function.inside
+                        "}")) @function.around
+
+                ; Arrow function in variable declaration - capture the full declaration
+                ([
+                    (lexical_declaration
+                        (variable_declarator
+                            value: (arrow_function
+                                body: (statement_block
+                                    "{"
+                                    (_)* @function.inside
+                                    "}"))))
+                    (variable_declaration
+                        (variable_declarator
+                            value: (arrow_function
+                                body: (statement_block
+                                    "{"
+                                    (_)* @function.inside
+                                    "}"))))
+                ]) @function.around
+
+                ([
+                    (lexical_declaration
+                        (variable_declarator
+                            value: (arrow_function)))
+                    (variable_declaration
+                        (variable_declarator
+                            value: (arrow_function)))
+                ]) @function.around
+
+                ; Catch-all for arrow functions in other contexts (callbacks, etc.)
+                ((arrow_function) @function.around (#not-has-parent? @function.around variable_declarator))
+                "#})),
             ..Default::default()
         })
         .expect("Could not parse queries");
@@ -276,6 +319,49 @@ impl EditorLspTestContext {
                   (jsx_opening_element) @start
                   (jsx_closing_element)? @end) @indent
                 "#})),
+            text_objects: Some(Cow::from(indoc! {r#"
+                (function_declaration
+                    body: (_
+                        "{"
+                        (_)* @function.inside
+                        "}")) @function.around
+
+                (method_definition
+                    body: (_
+                        "{"
+                        (_)* @function.inside
+                        "}")) @function.around
+
+                ; Arrow function in variable declaration - capture the full declaration
+                ([
+                    (lexical_declaration
+                        (variable_declarator
+                            value: (arrow_function
+                                body: (statement_block
+                                    "{"
+                                    (_)* @function.inside
+                                    "}"))))
+                    (variable_declaration
+                        (variable_declarator
+                            value: (arrow_function
+                                body: (statement_block
+                                    "{"
+                                    (_)* @function.inside
+                                    "}"))))
+                ]) @function.around
+
+                ([
+                    (lexical_declaration
+                        (variable_declarator
+                            value: (arrow_function)))
+                    (variable_declaration
+                        (variable_declarator
+                            value: (arrow_function)))
+                ]) @function.around
+
+                ; Catch-all for arrow functions in other contexts (callbacks, etc.)
+                ((arrow_function) @function.around (#not-has-parent? @function.around variable_declarator))
+                "#})),
             ..Default::default()
         })
         .expect("Could not parse queries");

crates/language/src/buffer_tests.rs 🔗

@@ -1141,6 +1141,104 @@ fn test_text_objects(cx: &mut App) {
     )
 }
 
+#[gpui::test]
+fn test_text_objects_with_has_parent_predicate(cx: &mut App) {
+    use std::borrow::Cow;
+
+    // Create a language with a custom text_objects query that uses #has-parent?
+    // This query only matches closure_expression when it's inside a call_expression
+    let language = Language::new(
+        LanguageConfig {
+            name: "Rust".into(),
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
+            ..Default::default()
+        },
+        Some(tree_sitter_rust::LANGUAGE.into()),
+    )
+    .with_queries(LanguageQueries {
+        text_objects: Some(Cow::from(indoc! {r#"
+            ; Only match closures that are arguments to function calls
+            (closure_expression) @function.around
+              (#has-parent? @function.around arguments)
+        "#})),
+        ..Default::default()
+    })
+    .expect("Could not parse queries");
+
+    let (text, ranges) = marked_text_ranges(
+        indoc! {r#"
+            fn main() {
+                let standalone = |x| x + 1;
+                let result = foo(|y| y * ˇ2);
+            }"#
+        },
+        false,
+    );
+
+    let buffer = cx.new(|cx| Buffer::local(text.clone(), cx).with_language(Arc::new(language), cx));
+    let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
+
+    let matches = snapshot
+        .text_object_ranges(ranges[0].clone(), TreeSitterOptions::default())
+        .map(|(range, text_object)| (&text[range], text_object))
+        .collect::<Vec<_>>();
+
+    // Should only match the closure inside foo(), not the standalone closure
+    assert_eq!(matches, &[("|y| y * 2", TextObject::AroundFunction),]);
+}
+
+#[gpui::test]
+fn test_text_objects_with_not_has_parent_predicate(cx: &mut App) {
+    use std::borrow::Cow;
+
+    // Create a language with a custom text_objects query that uses #not-has-parent?
+    // This query only matches closure_expression when it's NOT inside a call_expression
+    let language = Language::new(
+        LanguageConfig {
+            name: "Rust".into(),
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
+            ..Default::default()
+        },
+        Some(tree_sitter_rust::LANGUAGE.into()),
+    )
+    .with_queries(LanguageQueries {
+        text_objects: Some(Cow::from(indoc! {r#"
+            ; Only match closures that are NOT arguments to function calls
+            (closure_expression) @function.around
+              (#not-has-parent? @function.around arguments)
+        "#})),
+        ..Default::default()
+    })
+    .expect("Could not parse queries");
+
+    let (text, ranges) = marked_text_ranges(
+        indoc! {r#"
+            fn main() {
+                let standalone = |x| x +ˇ 1;
+                let result = foo(|y| y * 2);
+            }"#
+        },
+        false,
+    );
+
+    let buffer = cx.new(|cx| Buffer::local(text.clone(), cx).with_language(Arc::new(language), cx));
+    let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
+
+    let matches = snapshot
+        .text_object_ranges(ranges[0].clone(), TreeSitterOptions::default())
+        .map(|(range, text_object)| (&text[range], text_object))
+        .collect::<Vec<_>>();
+
+    // Should only match the standalone closure, not the one inside foo()
+    assert_eq!(matches, &[("|x| x + 1", TextObject::AroundFunction),]);
+}
+
 #[gpui::test]
 fn test_enclosing_bracket_ranges(cx: &mut App) {
     #[track_caller]

crates/language/src/syntax_map.rs 🔗

@@ -19,7 +19,10 @@ use std::{
 use streaming_iterator::StreamingIterator;
 use sum_tree::{Bias, Dimensions, SeekTarget, SumTree};
 use text::{Anchor, BufferSnapshot, OffsetRangeExt, Point, Rope, ToOffset, ToPoint};
-use tree_sitter::{Node, Query, QueryCapture, QueryCaptures, QueryCursor, QueryMatches, Tree};
+use tree_sitter::{
+    Node, Query, QueryCapture, QueryCaptures, QueryCursor, QueryMatch, QueryMatches,
+    QueryPredicateArg, Tree,
+};
 
 pub const MAX_BYTES_TO_QUERY: usize = 16 * 1024;
 
@@ -82,6 +85,7 @@ struct SyntaxMapMatchesLayer<'a> {
     next_captures: Vec<QueryCapture<'a>>,
     has_next: bool,
     matches: QueryMatches<'a, 'a, TextProvider<'a>, &'a [u8]>,
+    query: &'a Query,
     grammar_index: usize,
     _query_cursor: QueryCursorHandle,
 }
@@ -1163,6 +1167,7 @@ impl<'a> SyntaxMapMatches<'a> {
                 depth: layer.depth,
                 grammar_index,
                 matches,
+                query,
                 next_pattern_index: 0,
                 next_captures: Vec::new(),
                 has_next: false,
@@ -1260,13 +1265,20 @@ impl SyntaxMapCapturesLayer<'_> {
 
 impl SyntaxMapMatchesLayer<'_> {
     fn advance(&mut self) {
-        if let Some(mat) = self.matches.next() {
-            self.next_captures.clear();
-            self.next_captures.extend_from_slice(mat.captures);
-            self.next_pattern_index = mat.pattern_index;
-            self.has_next = true;
-        } else {
-            self.has_next = false;
+        loop {
+            if let Some(mat) = self.matches.next() {
+                if !satisfies_custom_predicates(self.query, mat) {
+                    continue;
+                }
+                self.next_captures.clear();
+                self.next_captures.extend_from_slice(mat.captures);
+                self.next_pattern_index = mat.pattern_index;
+                self.has_next = true;
+                return;
+            } else {
+                self.has_next = false;
+                return;
+            }
         }
     }
 
@@ -1295,6 +1307,39 @@ impl<'a> Iterator for SyntaxMapCaptures<'a> {
     }
 }
 
+fn satisfies_custom_predicates(query: &Query, mat: &QueryMatch) -> bool {
+    for predicate in query.general_predicates(mat.pattern_index) {
+        let satisfied = match predicate.operator.as_ref() {
+            "has-parent?" => has_parent(&predicate.args, mat),
+            "not-has-parent?" => !has_parent(&predicate.args, mat),
+            _ => true,
+        };
+        if !satisfied {
+            return false;
+        }
+    }
+    true
+}
+
+fn has_parent(args: &[QueryPredicateArg], mat: &QueryMatch) -> bool {
+    let (
+        Some(QueryPredicateArg::Capture(capture_ix)),
+        Some(QueryPredicateArg::String(parent_kind)),
+    ) = (args.first(), args.get(1))
+    else {
+        return false;
+    };
+
+    let Some(capture) = mat.captures.iter().find(|c| c.index == *capture_ix) else {
+        return false;
+    };
+
+    capture
+        .node
+        .parent()
+        .is_some_and(|p| p.kind() == parent_kind.as_ref())
+}
+
 fn join_ranges(
     a: impl Iterator<Item = Range<usize>>,
     b: impl Iterator<Item = Range<usize>>,

crates/languages/src/javascript/textobjects.scm 🔗

@@ -18,13 +18,47 @@
         (_)* @function.inside
         "}")) @function.around
 
-(arrow_function
+((arrow_function
     body: (statement_block
         "{"
         (_)* @function.inside
         "}")) @function.around
+ (#not-has-parent? @function.around variable_declarator))
 
-(arrow_function) @function.around
+; Arrow function in variable declaration - capture the full declaration
+([
+    (lexical_declaration
+        (variable_declarator
+            value: (arrow_function
+                body: (statement_block
+                    "{"
+                    (_)* @function.inside
+                    "}"))))
+    (variable_declaration
+        (variable_declarator
+            value: (arrow_function
+                body: (statement_block
+                    "{"
+                    (_)* @function.inside
+                    "}"))))
+]) @function.around
+
+; Arrow function in variable declaration (captures body for expression-bodied arrows)
+([
+    (lexical_declaration
+        (variable_declarator
+            value: (arrow_function
+                body: (_) @function.inside)))
+    (variable_declaration
+        (variable_declarator
+            value: (arrow_function
+                body: (_) @function.inside)))
+]) @function.around
+
+; Catch-all for arrow functions in other contexts (callbacks, etc.)
+((arrow_function
+    body: (_) @function.inside) @function.around
+ (#not-has-parent? @function.around variable_declarator))
 
 (generator_function
     body: (_

crates/languages/src/tsx/textobjects.scm 🔗

@@ -18,13 +18,47 @@
         (_)* @function.inside
         "}")) @function.around
 
-(arrow_function
+((arrow_function
     body: (statement_block
         "{"
         (_)* @function.inside
         "}")) @function.around
+ (#not-has-parent? @function.around variable_declarator))
 
-(arrow_function) @function.around
+; Arrow function in variable declaration - capture the full declaration
+([
+    (lexical_declaration
+        (variable_declarator
+            value: (arrow_function
+                body: (statement_block
+                    "{"
+                    (_)* @function.inside
+                    "}"))))
+    (variable_declaration
+        (variable_declarator
+            value: (arrow_function
+                body: (statement_block
+                    "{"
+                    (_)* @function.inside
+                    "}"))))
+]) @function.around
+
+; Arrow function in variable declaration (expression body fallback)
+([
+    (lexical_declaration
+        (variable_declarator
+            value: (arrow_function
+                body: (_) @function.inside)))
+    (variable_declaration
+        (variable_declarator
+            value: (arrow_function
+                body: (_) @function.inside)))
+]) @function.around
+
+; Catch-all for arrow functions in other contexts (callbacks, etc.)
+((arrow_function
+    body: (_) @function.inside) @function.around
+ (#not-has-parent? @function.around variable_declarator))
 (function_signature) @function.around
 
 (generator_function

crates/languages/src/typescript/textobjects.scm 🔗

@@ -18,13 +18,48 @@
         (_)* @function.inside
         "}")) @function.around
 
-(arrow_function
+((arrow_function
     body: (statement_block
         "{"
         (_)* @function.inside
         "}")) @function.around
+ (#not-has-parent? @function.around variable_declarator))
 
-(arrow_function) @function.around
+; Arrow function in variable declaration - capture the full declaration
+([
+    (lexical_declaration
+        (variable_declarator
+            value: (arrow_function
+                body: (statement_block
+                    "{"
+                    (_)* @function.inside
+                    "}"))))
+    (variable_declaration
+        (variable_declarator
+            value: (arrow_function
+                body: (statement_block
+                    "{"
+                    (_)* @function.inside
+                    "}"))))
+]) @function.around
+
+; Arrow function in variable declaration - capture body as @function.inside
+; (for statement blocks, the more specific pattern above captures just the contents)
+([
+    (lexical_declaration
+        (variable_declarator
+            value: (arrow_function
+                body: (_) @function.inside)))
+    (variable_declaration
+        (variable_declarator
+            value: (arrow_function
+                body: (_) @function.inside)))
+]) @function.around
+
+; Catch-all for arrow functions in other contexts (callbacks, etc.)
+((arrow_function
+    body: (_) @function.inside) @function.around
+ (#not-has-parent? @function.around variable_declarator))
 (function_signature) @function.around
 
 (generator_function

crates/vim/src/object.rs 🔗

@@ -3407,4 +3407,390 @@ mod test {
             .assert_eq("    ˇf = (x: unknown) => {");
         cx.shared_clipboard().await.assert_eq("const ");
     }
+
+    #[gpui::test]
+    async fn test_arrow_function_text_object(cx: &mut gpui::TestAppContext) {
+        let mut cx = VimTestContext::new_typescript(cx).await;
+
+        cx.set_state(
+            indoc! {"
+                const foo = () => {
+                    return ˇ1;
+                };
+            "},
+            Mode::Normal,
+        );
+        cx.simulate_keystrokes("v a f");
+        cx.assert_state(
+            indoc! {"
+                «const foo = () => {
+                    return 1;
+                };ˇ»
+            "},
+            Mode::VisualLine,
+        );
+
+        cx.set_state(
+            indoc! {"
+                arr.map(() => {
+                    return ˇ1;
+                });
+            "},
+            Mode::Normal,
+        );
+        cx.simulate_keystrokes("v a f");
+        cx.assert_state(
+            indoc! {"
+                arr.map(«() => {
+                    return 1;
+                }ˇ»);
+            "},
+            Mode::VisualLine,
+        );
+
+        cx.set_state(
+            indoc! {"
+                const foo = () => {
+                    return ˇ1;
+                };
+            "},
+            Mode::Normal,
+        );
+        cx.simulate_keystrokes("v i f");
+        cx.assert_state(
+            indoc! {"
+                const foo = () => {
+                    «return 1;ˇ»
+                };
+            "},
+            Mode::Visual,
+        );
+
+        cx.set_state(
+            indoc! {"
+                (() => {
+                    console.log(ˇ1);
+                })();
+            "},
+            Mode::Normal,
+        );
+        cx.simulate_keystrokes("v a f");
+        cx.assert_state(
+            indoc! {"
+                («() => {
+                    console.log(1);
+                }ˇ»)();
+            "},
+            Mode::VisualLine,
+        );
+
+        cx.set_state(
+            indoc! {"
+                const foo = () => {
+                    return ˇ1;
+                };
+                export { foo };
+            "},
+            Mode::Normal,
+        );
+        cx.simulate_keystrokes("v a f");
+        cx.assert_state(
+            indoc! {"
+                «const foo = () => {
+                    return 1;
+                };ˇ»
+                export { foo };
+            "},
+            Mode::VisualLine,
+        );
+
+        cx.set_state(
+            indoc! {"
+                let bar = () => {
+                    return ˇ2;
+                };
+            "},
+            Mode::Normal,
+        );
+        cx.simulate_keystrokes("v a f");
+        cx.assert_state(
+            indoc! {"
+                «let bar = () => {
+                    return 2;
+                };ˇ»
+            "},
+            Mode::VisualLine,
+        );
+
+        cx.set_state(
+            indoc! {"
+                var baz = () => {
+                    return ˇ3;
+                };
+            "},
+            Mode::Normal,
+        );
+        cx.simulate_keystrokes("v a f");
+        cx.assert_state(
+            indoc! {"
+                «var baz = () => {
+                    return 3;
+                };ˇ»
+            "},
+            Mode::VisualLine,
+        );
+
+        cx.set_state(
+            indoc! {"
+                const add = (a, b) => a + ˇb;
+            "},
+            Mode::Normal,
+        );
+        cx.simulate_keystrokes("v a f");
+        cx.assert_state(
+            indoc! {"
+                «const add = (a, b) => a + b;ˇ»
+            "},
+            Mode::VisualLine,
+        );
+
+        cx.set_state(
+            indoc! {"
+                const add = ˇ(a, b) => a + b;
+            "},
+            Mode::Normal,
+        );
+        cx.simulate_keystrokes("v a f");
+        cx.assert_state(
+            indoc! {"
+                «const add = (a, b) => a + b;ˇ»
+            "},
+            Mode::VisualLine,
+        );
+
+        cx.set_state(
+            indoc! {"
+                const add = (a, b) => a + bˇ;
+            "},
+            Mode::Normal,
+        );
+        cx.simulate_keystrokes("v a f");
+        cx.assert_state(
+            indoc! {"
+                «const add = (a, b) => a + b;ˇ»
+            "},
+            Mode::VisualLine,
+        );
+
+        cx.set_state(
+            indoc! {"
+                const add = (a, b) =ˇ> a + b;
+            "},
+            Mode::Normal,
+        );
+        cx.simulate_keystrokes("v a f");
+        cx.assert_state(
+            indoc! {"
+                «const add = (a, b) => a + b;ˇ»
+            "},
+            Mode::VisualLine,
+        );
+    }
+
+    #[gpui::test]
+    async fn test_arrow_function_in_jsx(cx: &mut gpui::TestAppContext) {
+        let mut cx = VimTestContext::new_tsx(cx).await;
+
+        cx.set_state(
+            indoc! {r#"
+                export const MyComponent = () => {
+                  return (
+                    <div>
+                      <div onClick={() => {
+                        alert("Hello world!");
+                        console.log(ˇ"clicked");
+                      }}>Hello world!</div>
+                    </div>
+                  );
+                };
+            "#},
+            Mode::Normal,
+        );
+        cx.simulate_keystrokes("v a f");
+        cx.assert_state(
+            indoc! {r#"
+                export const MyComponent = () => {
+                  return (
+                    <div>
+                      <div onClick={«() => {
+                        alert("Hello world!");
+                        console.log("clicked");
+                      }ˇ»}>Hello world!</div>
+                    </div>
+                  );
+                };
+            "#},
+            Mode::VisualLine,
+        );
+
+        cx.set_state(
+            indoc! {r#"
+                export const MyComponent = () => {
+                  return (
+                    <div>
+                      <div onClick={() => console.log("clickˇed")}>Hello world!</div>
+                    </div>
+                  );
+                };
+            "#},
+            Mode::Normal,
+        );
+        cx.simulate_keystrokes("v a f");
+        cx.assert_state(
+            indoc! {r#"
+                export const MyComponent = () => {
+                  return (
+                    <div>
+                      <div onClick={«() => console.log("clicked")ˇ»}>Hello world!</div>
+                    </div>
+                  );
+                };
+            "#},
+            Mode::VisualLine,
+        );
+
+        cx.set_state(
+            indoc! {r#"
+                export const MyComponent = () => {
+                  return (
+                    <div>
+                      <div onClick={ˇ() => console.log("clicked")}>Hello world!</div>
+                    </div>
+                  );
+                };
+            "#},
+            Mode::Normal,
+        );
+        cx.simulate_keystrokes("v a f");
+        cx.assert_state(
+            indoc! {r#"
+                export const MyComponent = () => {
+                  return (
+                    <div>
+                      <div onClick={«() => console.log("clicked")ˇ»}>Hello world!</div>
+                    </div>
+                  );
+                };
+            "#},
+            Mode::VisualLine,
+        );
+
+        cx.set_state(
+            indoc! {r#"
+                export const MyComponent = () => {
+                  return (
+                    <div>
+                      <div onClick={() => console.log("clicked"ˇ)}>Hello world!</div>
+                    </div>
+                  );
+                };
+            "#},
+            Mode::Normal,
+        );
+        cx.simulate_keystrokes("v a f");
+        cx.assert_state(
+            indoc! {r#"
+                export const MyComponent = () => {
+                  return (
+                    <div>
+                      <div onClick={«() => console.log("clicked")ˇ»}>Hello world!</div>
+                    </div>
+                  );
+                };
+            "#},
+            Mode::VisualLine,
+        );
+
+        cx.set_state(
+            indoc! {r#"
+                export const MyComponent = () => {
+                  return (
+                    <div>
+                      <div onClick={() =ˇ> console.log("clicked")}>Hello world!</div>
+                    </div>
+                  );
+                };
+            "#},
+            Mode::Normal,
+        );
+        cx.simulate_keystrokes("v a f");
+        cx.assert_state(
+            indoc! {r#"
+                export const MyComponent = () => {
+                  return (
+                    <div>
+                      <div onClick={«() => console.log("clicked")ˇ»}>Hello world!</div>
+                    </div>
+                  );
+                };
+            "#},
+            Mode::VisualLine,
+        );
+
+        cx.set_state(
+            indoc! {r#"
+                export const MyComponent = () => {
+                  return (
+                    <div>
+                      <div onClick={() => {
+                        console.log("cliˇcked");
+                      }}>Hello world!</div>
+                    </div>
+                  );
+                };
+            "#},
+            Mode::Normal,
+        );
+        cx.simulate_keystrokes("v a f");
+        cx.assert_state(
+            indoc! {r#"
+                export const MyComponent = () => {
+                  return (
+                    <div>
+                      <div onClick={«() => {
+                        console.log("clicked");
+                      }ˇ»}>Hello world!</div>
+                    </div>
+                  );
+                };
+            "#},
+            Mode::VisualLine,
+        );
+
+        cx.set_state(
+            indoc! {r#"
+                export const MyComponent = () => {
+                  return (
+                    <div>
+                      <div onClick={() => fˇoo()}>Hello world!</div>
+                    </div>
+                  );
+                };
+            "#},
+            Mode::Normal,
+        );
+        cx.simulate_keystrokes("v a f");
+        cx.assert_state(
+            indoc! {r#"
+                export const MyComponent = () => {
+                  return (
+                    <div>
+                      <div onClick={«() => foo()ˇ»}>Hello world!</div>
+                    </div>
+                  );
+                };
+            "#},
+            Mode::VisualLine,
+        );
+    }
 }

crates/vim/src/visual.rs 🔗

@@ -522,12 +522,16 @@ impl Vim {
                                             selection.start = original_point.to_display_point(map)
                                         }
                                     } else {
-                                        selection.end = movement::saturating_right(
-                                            map,
-                                            original_point.to_display_point(map),
-                                        );
-                                        if original_point.column > 0 {
-                                            selection.reversed = true
+                                        let original_display_point =
+                                            original_point.to_display_point(map);
+                                        if selection.end <= original_display_point {
+                                            selection.end = movement::saturating_right(
+                                                map,
+                                                original_display_point,
+                                            );
+                                            if original_point.column > 0 {
+                                                selection.reversed = true
+                                            }
                                         }
                                     }
                                 }