Merge pull request #174 from zed-industries/tweak-word-boundaries

Adjust word-boundary motion, treating whitespace more like other editors
This commit is contained in:
Antonio Scandurra 2021-09-23 09:09:49 +02:00 committed by GitHub
commit a1ca507498
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 133 additions and 92 deletions

View file

@ -1493,8 +1493,26 @@ impl Editor {
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
self.start_transaction(cx); self.start_transaction(cx);
self.select_to_previous_word_boundary(&SelectToPreviousWordBoundary, cx); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
self.backspace(&Backspace, 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); self.end_transaction(cx);
} }
@ -1545,8 +1563,26 @@ impl Editor {
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
self.start_transaction(cx); self.start_transaction(cx);
self.select_to_next_word_boundary(&SelectToNextWordBoundary, cx); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
self.delete(&Delete, 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); 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.update(cx, |view, cx| {
view.move_to_previous_word_boundary(&MoveToPreviousWordBoundary, cx); view.move_to_previous_word_boundary(&MoveToPreviousWordBoundary, cx);
assert_eq!( assert_eq!(
view.selection_ranges(cx), view.selection_ranges(cx),
&[ &[
DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0), 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!( assert_eq!(
view.selection_ranges(cx), 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), 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.update(cx, |view, cx| {
view.move_to_next_word_boundary(&MoveToNextWordBoundary, cx); view.move_to_next_word_boundary(&MoveToNextWordBoundary, cx);
assert_eq!( assert_eq!(
view.selection_ranges(cx), view.selection_ranges(cx),
&[ &[
DisplayPoint::new(0, 9)..DisplayPoint::new(0, 9), 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), view.selection_ranges(cx),
&[ &[
DisplayPoint::new(0, 10)..DisplayPoint::new(0, 9), 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), view.selection_ranges(cx),
&[ &[
DisplayPoint::new(0, 10)..DisplayPoint::new(0, 7), 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), view.selection_ranges(cx),
&[ &[
DisplayPoint::new(0, 10)..DisplayPoint::new(0, 9), DisplayPoint::new(0, 10)..DisplayPoint::new(0, 9),
DisplayPoint::new(2, 3)..DisplayPoint::new(2, 2), DisplayPoint::new(2, 4)..DisplayPoint::new(2, 3),
]
);
});
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),
] ]
); );
}); });
@ -3418,11 +3402,52 @@ mod tests {
view.move_to_previous_word_boundary(&MoveToPreviousWordBoundary, cx); view.move_to_previous_word_boundary(&MoveToPreviousWordBoundary, cx);
assert_eq!( assert_eq!(
view.selection_ranges(cx), 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] #[gpui::test]
fn test_backspace(cx: &mut gpui::MutableAppContext) { fn test_backspace(cx: &mut gpui::MutableAppContext) {
let buffer = cx.add_model(|cx| { let buffer = cx.add_model(|cx| {

View file

@ -101,7 +101,10 @@ pub fn line_end(map: &DisplayMapSnapshot, point: DisplayPoint) -> Result<Display
Ok(map.clip_point(line_end, Bias::Left)) 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; let mut line_start = 0;
if point.row() > 0 { if point.row() > 0 {
if let Some(indent) = map.soft_wrap_indent(point.row() - 1) { 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.column() == line_start {
if point.row() == 0 { if point.row() == 0 {
Ok(DisplayPoint::new(0, 0)) return Ok(DisplayPoint::new(0, 0));
} else { } else {
let row = point.row() - 1; 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( pub fn next_word_boundary(
map: &DisplayMapSnapshot, map: &DisplayMapSnapshot,
mut point: DisplayPoint, mut point: DisplayPoint,
) -> Result<DisplayPoint> { ) -> Result<DisplayPoint> {
let mut prev_c = None; let mut prev_char_kind = None;
for c in map.chars_at(point) { for c in map.chars_at(point) {
if prev_c.is_some() && (c == '\n' || char_kind(prev_c.unwrap()) != char_kind(c)) { let char_kind = char_kind(c);
break; 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' { if c == '\n' {
@ -152,7 +168,7 @@ pub fn next_word_boundary(
} else { } else {
*point.column_mut() += c.len_utf8() as u32; *point.column_mut() += c.len_utf8() as u32;
} }
prev_c = Some(c); prev_char_kind = Some(char_kind);
} }
Ok(point) Ok(point)
} }
@ -192,7 +208,7 @@ mod tests {
.unwrap(); .unwrap();
let font_size = 14.0; 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 = let display_map =
cx.add_model(|cx| DisplayMap::new(buffer, tab_size, font_id, font_size, None, cx)); 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)); let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx));
@ -202,7 +218,7 @@ mod tests {
); );
assert_eq!( assert_eq!(
prev_word_boundary(&snapshot, DisplayPoint::new(0, 7)).unwrap(), prev_word_boundary(&snapshot, DisplayPoint::new(0, 7)).unwrap(),
DisplayPoint::new(0, 6) DisplayPoint::new(0, 2)
); );
assert_eq!( assert_eq!(
prev_word_boundary(&snapshot, DisplayPoint::new(0, 6)).unwrap(), prev_word_boundary(&snapshot, DisplayPoint::new(0, 6)).unwrap(),
@ -210,7 +226,7 @@ mod tests {
); );
assert_eq!( assert_eq!(
prev_word_boundary(&snapshot, DisplayPoint::new(0, 2)).unwrap(), prev_word_boundary(&snapshot, DisplayPoint::new(0, 2)).unwrap(),
DisplayPoint::new(0, 1) DisplayPoint::new(0, 0)
); );
assert_eq!( assert_eq!(
prev_word_boundary(&snapshot, DisplayPoint::new(0, 1)).unwrap(), prev_word_boundary(&snapshot, DisplayPoint::new(0, 1)).unwrap(),
@ -223,7 +239,7 @@ mod tests {
); );
assert_eq!( assert_eq!(
next_word_boundary(&snapshot, DisplayPoint::new(0, 1)).unwrap(), next_word_boundary(&snapshot, DisplayPoint::new(0, 1)).unwrap(),
DisplayPoint::new(0, 2) DisplayPoint::new(0, 6)
); );
assert_eq!( assert_eq!(
next_word_boundary(&snapshot, DisplayPoint::new(0, 2)).unwrap(), next_word_boundary(&snapshot, DisplayPoint::new(0, 2)).unwrap(),
@ -231,7 +247,7 @@ mod tests {
); );
assert_eq!( assert_eq!(
next_word_boundary(&snapshot, DisplayPoint::new(0, 6)).unwrap(), next_word_boundary(&snapshot, DisplayPoint::new(0, 6)).unwrap(),
DisplayPoint::new(0, 7) DisplayPoint::new(0, 12)
); );
assert_eq!( assert_eq!(
next_word_boundary(&snapshot, DisplayPoint::new(0, 7)).unwrap(), next_word_boundary(&snapshot, DisplayPoint::new(0, 7)).unwrap(),