diff --git a/zed/src/editor.rs b/zed/src/editor.rs index 4a5958267e..dbc1cf71a0 100644 --- a/zed/src/editor.rs +++ b/zed/src/editor.rs @@ -1493,8 +1493,26 @@ impl Editor { cx: &mut ViewContext<Self>, ) { self.start_transaction(cx); - self.select_to_previous_word_boundary(&SelectToPreviousWordBoundary, cx); - self.backspace(&Backspace, cx); + let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + let mut selections = self.selections(cx.as_ref()).to_vec(); + { + let buffer = self.buffer.read(cx); + for selection in &mut selections { + let range = selection.point_range(buffer); + if range.start == range.end { + let head = selection.head().to_display_point(&display_map, Bias::Left); + let cursor = display_map.anchor_before( + movement::prev_word_boundary(&display_map, head).unwrap(), + Bias::Right, + ); + selection.set_head(&buffer, cursor); + selection.goal = SelectionGoal::None; + } + } + } + + self.update_selections(selections, true, cx); + self.insert(&Insert(String::new()), cx); self.end_transaction(cx); } @@ -1545,8 +1563,26 @@ impl Editor { cx: &mut ViewContext<Self>, ) { self.start_transaction(cx); - self.select_to_next_word_boundary(&SelectToNextWordBoundary, cx); - self.delete(&Delete, cx); + let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + let mut selections = self.selections(cx.as_ref()).to_vec(); + { + let buffer = self.buffer.read(cx); + for selection in &mut selections { + let range = selection.point_range(buffer); + if range.start == range.end { + let head = selection.head().to_display_point(&display_map, Bias::Left); + let cursor = display_map.anchor_before( + movement::next_word_boundary(&display_map, head).unwrap(), + Bias::Right, + ); + selection.set_head(&buffer, cursor); + selection.goal = SelectionGoal::None; + } + } + } + + self.update_selections(selections, true, cx); + self.insert(&Insert(String::new()), cx); self.end_transaction(cx); } @@ -3226,24 +3262,13 @@ mod tests { ); }); - view.update(cx, |view, cx| { - view.move_to_previous_word_boundary(&MoveToPreviousWordBoundary, cx); - assert_eq!( - view.selection_ranges(cx), - &[ - DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3), - DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0), - ] - ); - }); - view.update(cx, |view, cx| { view.move_to_previous_word_boundary(&MoveToPreviousWordBoundary, cx); assert_eq!( view.selection_ranges(cx), &[ DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0), - DisplayPoint::new(0, 24)..DisplayPoint::new(0, 24), + DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0), ] ); }); @@ -3275,30 +3300,19 @@ mod tests { assert_eq!( view.selection_ranges(cx), &[ - DisplayPoint::new(0, 4)..DisplayPoint::new(0, 4), + DisplayPoint::new(0, 7)..DisplayPoint::new(0, 7), DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0), ] ); }); - view.update(cx, |view, cx| { - view.move_to_next_word_boundary(&MoveToNextWordBoundary, cx); - assert_eq!( - view.selection_ranges(cx), - &[ - DisplayPoint::new(0, 7)..DisplayPoint::new(0, 7), - DisplayPoint::new(2, 0)..DisplayPoint::new(2, 0), - ] - ); - }); - view.update(cx, |view, cx| { view.move_to_next_word_boundary(&MoveToNextWordBoundary, cx); assert_eq!( view.selection_ranges(cx), &[ DisplayPoint::new(0, 9)..DisplayPoint::new(0, 9), - DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2), + DisplayPoint::new(2, 3)..DisplayPoint::new(2, 3), ] ); }); @@ -3310,7 +3324,7 @@ mod tests { view.selection_ranges(cx), &[ DisplayPoint::new(0, 10)..DisplayPoint::new(0, 9), - DisplayPoint::new(2, 3)..DisplayPoint::new(2, 2), + DisplayPoint::new(2, 4)..DisplayPoint::new(2, 3), ] ); }); @@ -3321,7 +3335,7 @@ mod tests { view.selection_ranges(cx), &[ DisplayPoint::new(0, 10)..DisplayPoint::new(0, 7), - DisplayPoint::new(2, 3)..DisplayPoint::new(2, 0), + DisplayPoint::new(2, 4)..DisplayPoint::new(2, 2), ] ); }); @@ -3332,37 +3346,7 @@ mod tests { view.selection_ranges(cx), &[ DisplayPoint::new(0, 10)..DisplayPoint::new(0, 9), - DisplayPoint::new(2, 3)..DisplayPoint::new(2, 2), - ] - ); - }); - - view.update(cx, |view, cx| { - view.delete_to_next_word_boundary(&DeleteToNextWordBoundary, cx); - assert_eq!( - view.display_text(cx), - "use std::s::{foo, bar}\n\n {az.qux()}" - ); - assert_eq!( - view.selection_ranges(cx), - &[ - DisplayPoint::new(0, 10)..DisplayPoint::new(0, 10), - DisplayPoint::new(2, 3)..DisplayPoint::new(2, 3), - ] - ); - }); - - view.update(cx, |view, cx| { - view.delete_to_previous_word_boundary(&DeleteToPreviousWordBoundary, cx); - assert_eq!( - view.display_text(cx), - "use std::::{foo, bar}\n\n az.qux()}" - ); - assert_eq!( - view.selection_ranges(cx), - &[ - DisplayPoint::new(0, 9)..DisplayPoint::new(0, 9), - DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2), + DisplayPoint::new(2, 4)..DisplayPoint::new(2, 3), ] ); }); @@ -3418,11 +3402,52 @@ mod tests { view.move_to_previous_word_boundary(&MoveToPreviousWordBoundary, cx); assert_eq!( view.selection_ranges(cx), - &[DisplayPoint::new(1, 15)..DisplayPoint::new(1, 15)] + &[DisplayPoint::new(1, 14)..DisplayPoint::new(1, 14)] ); }); } + #[gpui::test] + fn test_delete_to_word_boundary(cx: &mut gpui::MutableAppContext) { + let buffer = cx.add_model(|cx| Buffer::new(0, "one two three four", cx)); + let settings = settings::test(&cx).1; + let (_, view) = cx.add_window(Default::default(), |cx| { + build_editor(buffer.clone(), settings, cx) + }); + + view.update(cx, |view, cx| { + view.select_display_ranges( + &[ + // an empty selection - the preceding word fragment is deleted + DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2), + // characters selected - they are deleted + DisplayPoint::new(0, 9)..DisplayPoint::new(0, 12), + ], + cx, + ) + .unwrap(); + view.delete_to_previous_word_boundary(&DeleteToPreviousWordBoundary, cx); + }); + + assert_eq!(buffer.read(cx).text(), "e two te four"); + + view.update(cx, |view, cx| { + view.select_display_ranges( + &[ + // an empty selection - the following word fragment is deleted + DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3), + // characters selected - they are deleted + DisplayPoint::new(0, 9)..DisplayPoint::new(0, 10), + ], + cx, + ) + .unwrap(); + view.delete_to_next_word_boundary(&DeleteToNextWordBoundary, cx); + }); + + assert_eq!(buffer.read(cx).text(), "e t te our"); + } + #[gpui::test] fn test_backspace(cx: &mut gpui::MutableAppContext) { let buffer = cx.add_model(|cx| { diff --git a/zed/src/editor/movement.rs b/zed/src/editor/movement.rs index 8f5bc6f20a..d86aa9ca53 100644 --- a/zed/src/editor/movement.rs +++ b/zed/src/editor/movement.rs @@ -101,7 +101,10 @@ pub fn line_end(map: &DisplayMapSnapshot, point: DisplayPoint) -> Result<Display Ok(map.clip_point(line_end, Bias::Left)) } -pub fn prev_word_boundary(map: &DisplayMapSnapshot, point: DisplayPoint) -> Result<DisplayPoint> { +pub fn prev_word_boundary( + map: &DisplayMapSnapshot, + mut point: DisplayPoint, +) -> Result<DisplayPoint> { let mut line_start = 0; if point.row() > 0 { if let Some(indent) = map.soft_wrap_indent(point.row() - 1) { @@ -111,39 +114,52 @@ pub fn prev_word_boundary(map: &DisplayMapSnapshot, point: DisplayPoint) -> Resu if point.column() == line_start { if point.row() == 0 { - Ok(DisplayPoint::new(0, 0)) + return Ok(DisplayPoint::new(0, 0)); } else { let row = point.row() - 1; - Ok(map.clip_point(DisplayPoint::new(row, map.line_len(row)), Bias::Left)) + point = map.clip_point(DisplayPoint::new(row, map.line_len(row)), Bias::Left); } - } else { - let mut boundary = DisplayPoint::new(point.row(), 0); - let mut column = 0; - let mut prev_c = None; - for c in map.chars_at(DisplayPoint::new(point.row(), 0)) { - if column >= point.column() { - break; - } - - if prev_c.is_none() || char_kind(prev_c.unwrap()) != char_kind(c) { - *boundary.column_mut() = column; - } - - prev_c = Some(c); - column += c.len_utf8() as u32; - } - Ok(boundary) } + + let mut boundary = DisplayPoint::new(point.row(), 0); + let mut column = 0; + let mut prev_char_kind = CharKind::Newline; + for c in map.chars_at(DisplayPoint::new(point.row(), 0)) { + if column >= point.column() { + break; + } + + let char_kind = char_kind(c); + if char_kind != prev_char_kind + && char_kind != CharKind::Whitespace + && char_kind != CharKind::Newline + { + *boundary.column_mut() = column; + } + + prev_char_kind = char_kind; + column += c.len_utf8() as u32; + } + Ok(boundary) } pub fn next_word_boundary( map: &DisplayMapSnapshot, mut point: DisplayPoint, ) -> Result<DisplayPoint> { - let mut prev_c = None; + let mut prev_char_kind = None; for c in map.chars_at(point) { - if prev_c.is_some() && (c == '\n' || char_kind(prev_c.unwrap()) != char_kind(c)) { - break; + let char_kind = char_kind(c); + if let Some(prev_char_kind) = prev_char_kind { + if c == '\n' { + break; + } + if prev_char_kind != char_kind + && prev_char_kind != CharKind::Whitespace + && prev_char_kind != CharKind::Newline + { + break; + } } if c == '\n' { @@ -152,7 +168,7 @@ pub fn next_word_boundary( } else { *point.column_mut() += c.len_utf8() as u32; } - prev_c = Some(c); + prev_char_kind = Some(char_kind); } Ok(point) } @@ -192,7 +208,7 @@ mod tests { .unwrap(); let font_size = 14.0; - let buffer = cx.add_model(|cx| Buffer::new(0, "a bcΔ defγ", cx)); + let buffer = cx.add_model(|cx| Buffer::new(0, "a bcΔ defγ hi—jk", cx)); let display_map = cx.add_model(|cx| DisplayMap::new(buffer, tab_size, font_id, font_size, None, cx)); let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx)); @@ -202,7 +218,7 @@ mod tests { ); assert_eq!( prev_word_boundary(&snapshot, DisplayPoint::new(0, 7)).unwrap(), - DisplayPoint::new(0, 6) + DisplayPoint::new(0, 2) ); assert_eq!( prev_word_boundary(&snapshot, DisplayPoint::new(0, 6)).unwrap(), @@ -210,7 +226,7 @@ mod tests { ); assert_eq!( prev_word_boundary(&snapshot, DisplayPoint::new(0, 2)).unwrap(), - DisplayPoint::new(0, 1) + DisplayPoint::new(0, 0) ); assert_eq!( prev_word_boundary(&snapshot, DisplayPoint::new(0, 1)).unwrap(), @@ -223,7 +239,7 @@ mod tests { ); assert_eq!( next_word_boundary(&snapshot, DisplayPoint::new(0, 1)).unwrap(), - DisplayPoint::new(0, 2) + DisplayPoint::new(0, 6) ); assert_eq!( next_word_boundary(&snapshot, DisplayPoint::new(0, 2)).unwrap(), @@ -231,7 +247,7 @@ mod tests { ); assert_eq!( next_word_boundary(&snapshot, DisplayPoint::new(0, 6)).unwrap(), - DisplayPoint::new(0, 7) + DisplayPoint::new(0, 12) ); assert_eq!( next_word_boundary(&snapshot, DisplayPoint::new(0, 7)).unwrap(),