Fix moving to previous word across a wrap boundary

Nathan Sobo created

I'm just going to the end of the soft-wrapped line, mirroring the behavior with hard wraps. It's maybe not perfectly technically correct but that behavior would require us to consider word boundaries outside of the current line, which doesn't seem worth the complexity.

Change summary

zed/src/editor.rs                      |  2 +-
zed/src/editor/display_map.rs          |  7 +++----
zed/src/editor/display_map/wrap_map.rs | 13 ++++++++-----
zed/src/editor/movement.rs             | 11 +++++++++--
4 files changed, 21 insertions(+), 12 deletions(-)

Detailed changes

zed/src/editor.rs 🔗

@@ -3423,7 +3423,7 @@ mod tests {
             view.move_to_previous_word_boundary(&(), cx);
             assert_eq!(
                 view.selection_ranges(cx),
-                &[DisplayPoint::new(1, 14)..DisplayPoint::new(1, 14)]
+                &[DisplayPoint::new(1, 15)..DisplayPoint::new(1, 15)]
             );
         });
     }

zed/src/editor/display_map.rs 🔗

@@ -213,9 +213,8 @@ impl DisplayMapSnapshot {
         self.folds_snapshot.is_line_folded(row)
     }
 
-    #[cfg(test)]
-    pub fn is_line_wrapped(&self, display_row: u32) -> bool {
-        self.wraps_snapshot.is_line_wrapped(display_row)
+    pub fn soft_wrap_indent(&self, display_row: u32) -> Option<u32> {
+        self.wraps_snapshot.soft_wrap_indent(display_row)
     }
 
     pub fn text(&self) -> String {
@@ -492,7 +491,7 @@ mod tests {
                 if point < snapshot.max_point() {
                     assert!(moved_right > point);
                     if point.column() == snapshot.line_len(point.row())
-                        || snapshot.is_line_wrapped(point.row())
+                        || snapshot.soft_wrap_indent(point.row()).is_some()
                             && point.column() == snapshot.line_len(point.row()) - 1
                     {
                         assert!(moved_right.row() > point.row());

zed/src/editor/display_map/wrap_map.rs 🔗

@@ -511,13 +511,16 @@ impl Snapshot {
         len as u32
     }
 
-    #[cfg(test)]
-    pub fn is_line_wrapped(&self, row: u32) -> bool {
+    pub fn soft_wrap_indent(&self, row: u32) -> Option<u32> {
         let mut cursor = self.transforms.cursor::<_, ()>();
         cursor.seek(&WrapPoint::new(row + 1, 0), Bias::Right, &());
-        cursor
-            .item()
-            .map_or(false, |transform| !transform.is_isomorphic())
+        cursor.item().and_then(|transform| {
+            if transform.is_isomorphic() {
+                None
+            } else {
+                Some(transform.summary.output.lines.column)
+            }
+        })
     }
 
     pub fn longest_row(&self) -> u32 {

zed/src/editor/movement.rs 🔗

@@ -102,12 +102,19 @@ pub fn line_end(map: &DisplayMapSnapshot, point: DisplayPoint) -> Result<Display
 }
 
 pub fn prev_word_boundary(map: &DisplayMapSnapshot, point: DisplayPoint) -> Result<DisplayPoint> {
-    if point.column() == 0 {
+    let mut line_start = 0;
+    if point.row() > 0 {
+        if let Some(indent) = map.soft_wrap_indent(point.row() - 1) {
+            line_start = indent;
+        }
+    }
+
+    if point.column() == line_start {
         if point.row() == 0 {
             Ok(DisplayPoint::new(0, 0))
         } else {
             let row = point.row() - 1;
-            Ok(DisplayPoint::new(row, map.line_len(row)))
+            Ok(map.clip_point(DisplayPoint::new(row, map.line_len(row)), Bias::Left))
         }
     } else {
         let mut boundary = DisplayPoint::new(point.row(), 0);