From 953125d3f381880f288afa2b59f9936e76d534fa Mon Sep 17 00:00:00 2001
From: Nathan Vegdahl <cessen@cessen.com>
Date: Thu, 5 Aug 2021 17:32:33 -0700
Subject: [PATCH] Fix around-word text-object selection. (#546)

* Fix around-word text-object selection.

* Text object around-word: select to the left if no whitespace on the right.

Also only select around when there's whitespace at all.

* Make select-word-around select all white space on a side.

* Update commented-out test case.

* Fix unused import warning from rebase.
---
 helix-core/src/textobject.rs | 61 +++++++++++++++++++++---------------
 1 file changed, 36 insertions(+), 25 deletions(-)

diff --git a/helix-core/src/textobject.rs b/helix-core/src/textobject.rs
index b59ff6fa..b965f6df 100644
--- a/helix-core/src/textobject.rs
+++ b/helix-core/src/textobject.rs
@@ -1,7 +1,7 @@
 use ropey::RopeSlice;
 
-use crate::chars::{categorize_char, CharCategory};
-use crate::graphemes::{next_grapheme_boundary, prev_grapheme_boundary};
+use crate::chars::{categorize_char, char_is_whitespace, CharCategory};
+use crate::graphemes::next_grapheme_boundary;
 use crate::movement::Direction;
 use crate::surround;
 use crate::Range;
@@ -73,19 +73,23 @@ pub fn textobject_word(
 
     match textobject {
         TextObject::Inside => Range::new(word_start, word_end),
-        TextObject::Around => Range::new(
-            match slice
-                .get_char(word_start.saturating_sub(1))
-                .map(categorize_char)
-            {
-                None | Some(CharCategory::Eol) => word_start,
-                _ => prev_grapheme_boundary(slice, word_start),
-            },
-            match slice.get_char(word_end).map(categorize_char) {
-                None | Some(CharCategory::Eol) => word_end,
-                _ => next_grapheme_boundary(slice, word_end),
-            },
-        ),
+        TextObject::Around => {
+            let whitespace_count_right = slice
+                .chars_at(word_end)
+                .take_while(|c| char_is_whitespace(*c))
+                .count();
+
+            if whitespace_count_right > 0 {
+                Range::new(word_start, word_end + whitespace_count_right)
+            } else {
+                let whitespace_count_left = {
+                    let mut iter = slice.chars_at(word_start);
+                    iter.reverse();
+                    iter.take_while(|c| char_is_whitespace(*c)).count()
+                };
+                Range::new(word_start - whitespace_count_left, word_end)
+            }
+        }
     }
 }
 
@@ -126,9 +130,9 @@ mod test {
                     (13, Inside, (10, 16)),
                     (10, Inside, (10, 16)),
                     (15, Inside, (10, 16)),
-                    (13, Around, (9, 17)),
-                    (10, Around, (9, 17)),
-                    (15, Around, (9, 17)),
+                    (13, Around, (10, 17)),
+                    (10, Around, (10, 17)),
+                    (15, Around, (10, 17)),
                 ],
             ),
             (
@@ -167,9 +171,9 @@ mod test {
                     (13, Inside, (10, 16)),
                     (10, Inside, (10, 16)),
                     (15, Inside, (10, 16)),
-                    (13, Around, (9, 17)),
-                    (10, Around, (9, 17)),
-                    (15, Around, (9, 17)),
+                    (13, Around, (10, 17)),
+                    (10, Around, (10, 17)),
+                    (15, Around, (10, 17)),
                 ],
             ),
             (
@@ -178,10 +182,9 @@ mod test {
                     (14, Inside, (14, 21)),
                     (20, Inside, (14, 21)),
                     (17, Inside, (14, 21)),
-                    (14, Around, (13, 22)),
-                    // FIXME: edge case
-                    // (20, Around, (14, 20)),
-                    (17, Around, (13, 22)),
+                    (14, Around, (14, 21)),
+                    (20, Around, (14, 21)),
+                    (17, Around, (14, 21)),
                 ],
             ),
             (
@@ -195,6 +198,14 @@ mod test {
                     (11, Around, (11, 11)),
                 ],
             ),
+            (
+                "cursor on word   with extra whitespace",
+                vec![(11, Inside, (10, 14)), (11, Around, (10, 17))],
+            ),
+            (
+                "cursor at end with extra   whitespace",
+                vec![(28, Inside, (27, 37)), (28, Around, (24, 37))],
+            ),
             (
                 "cursor at end of doc",
                 vec![(19, Inside, (17, 20)), (19, Around, (16, 20))],