diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index e0ead12674..d87c0e13a9 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -169,7 +169,7 @@ impl DisplayMap { } pub struct DisplayMapSnapshot { - buffer_snapshot: language::Snapshot, + pub buffer_snapshot: language::Snapshot, folds_snapshot: fold_map::Snapshot, tabs_snapshot: tab_map::Snapshot, wraps_snapshot: wrap_map::Snapshot, diff --git a/crates/editor/src/movement.rs b/crates/editor/src/movement.rs index c6190fec29..083b828536 100644 --- a/crates/editor/src/movement.rs +++ b/crates/editor/src/movement.rs @@ -1,5 +1,7 @@ -use super::{Bias, DisplayMapSnapshot, DisplayPoint, SelectionGoal}; +use super::{Bias, DisplayMapSnapshot, DisplayPoint, SelectionGoal, ToDisplayPoint}; use anyhow::Result; +use buffer::ToPoint; +use std::{cmp, ops::Range}; pub fn left(map: &DisplayMapSnapshot, mut point: DisplayPoint) -> Result { if point.column() > 0 { @@ -113,10 +115,7 @@ pub fn line_end(map: &DisplayMapSnapshot, point: DisplayPoint) -> Result Result { +pub fn prev_word_boundary(map: &DisplayMapSnapshot, mut point: DisplayPoint) -> DisplayPoint { let mut line_start = 0; if point.row() > 0 { if let Some(indent) = map.soft_wrap_indent(point.row() - 1) { @@ -126,7 +125,7 @@ pub fn prev_word_boundary( if point.column() == line_start { if point.row() == 0 { - return Ok(DisplayPoint::new(0, 0)); + return DisplayPoint::new(0, 0); } else { let row = point.row() - 1; point = map.clip_point(DisplayPoint::new(row, map.line_len(row)), Bias::Left); @@ -152,13 +151,10 @@ pub fn prev_word_boundary( prev_char_kind = char_kind; column += c.len_utf8() as u32; } - Ok(boundary) + boundary } -pub fn next_word_boundary( - map: &DisplayMapSnapshot, - mut point: DisplayPoint, -) -> Result { +pub fn next_word_boundary(map: &DisplayMapSnapshot, mut point: DisplayPoint) -> DisplayPoint { let mut prev_char_kind = None; for c in map.chars_at(point) { let char_kind = char_kind(c); @@ -182,14 +178,46 @@ pub fn next_word_boundary( } prev_char_kind = Some(char_kind); } - Ok(point) + point } -#[derive(Copy, Clone, Eq, PartialEq)] +pub fn surrounding_word(map: &DisplayMapSnapshot, point: DisplayPoint) -> Range { + let mut start = map.clip_point(point, Bias::Left).to_offset(map, Bias::Left); + let mut end = start; + + let text = map.buffer_snapshot.text(); + let mut next_chars = text.chars_at(start).peekable(); + let mut prev_chars = text.reversed_chars_at(start).peekable(); + let word_kind = cmp::max( + prev_chars.peek().copied().map(char_kind), + next_chars.peek().copied().map(char_kind), + ); + + for ch in prev_chars { + if Some(char_kind(ch)) == word_kind { + start -= ch.len_utf8(); + } else { + break; + } + } + + for ch in next_chars { + if Some(char_kind(ch)) == word_kind { + end += ch.len_utf8(); + } else { + break; + } + } + + start.to_point(&map.buffer_snapshot).to_display_point(map) + ..end.to_point(&map.buffer_snapshot).to_display_point(map) +} + +#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord)] enum CharKind { Newline, - Whitespace, Punctuation, + Whitespace, Word, } @@ -225,45 +253,117 @@ mod tests { 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)); assert_eq!( - prev_word_boundary(&snapshot, DisplayPoint::new(0, 12)).unwrap(), + prev_word_boundary(&snapshot, DisplayPoint::new(0, 12)), DisplayPoint::new(0, 7) ); assert_eq!( - prev_word_boundary(&snapshot, DisplayPoint::new(0, 7)).unwrap(), + prev_word_boundary(&snapshot, DisplayPoint::new(0, 7)), DisplayPoint::new(0, 2) ); assert_eq!( - prev_word_boundary(&snapshot, DisplayPoint::new(0, 6)).unwrap(), + prev_word_boundary(&snapshot, DisplayPoint::new(0, 6)), DisplayPoint::new(0, 2) ); assert_eq!( - prev_word_boundary(&snapshot, DisplayPoint::new(0, 2)).unwrap(), + prev_word_boundary(&snapshot, DisplayPoint::new(0, 2)), DisplayPoint::new(0, 0) ); assert_eq!( - prev_word_boundary(&snapshot, DisplayPoint::new(0, 1)).unwrap(), + prev_word_boundary(&snapshot, DisplayPoint::new(0, 1)), DisplayPoint::new(0, 0) ); assert_eq!( - next_word_boundary(&snapshot, DisplayPoint::new(0, 0)).unwrap(), + next_word_boundary(&snapshot, DisplayPoint::new(0, 0)), DisplayPoint::new(0, 1) ); assert_eq!( - next_word_boundary(&snapshot, DisplayPoint::new(0, 1)).unwrap(), + next_word_boundary(&snapshot, DisplayPoint::new(0, 1)), DisplayPoint::new(0, 6) ); assert_eq!( - next_word_boundary(&snapshot, DisplayPoint::new(0, 2)).unwrap(), + next_word_boundary(&snapshot, DisplayPoint::new(0, 2)), DisplayPoint::new(0, 6) ); assert_eq!( - next_word_boundary(&snapshot, DisplayPoint::new(0, 6)).unwrap(), + next_word_boundary(&snapshot, DisplayPoint::new(0, 6)), DisplayPoint::new(0, 12) ); assert_eq!( - next_word_boundary(&snapshot, DisplayPoint::new(0, 7)).unwrap(), + next_word_boundary(&snapshot, DisplayPoint::new(0, 7)), DisplayPoint::new(0, 12) ); } + + #[gpui::test] + fn test_surrounding_word(cx: &mut gpui::MutableAppContext) { + let tab_size = 4; + let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap(); + let font_id = cx + .font_cache() + .select_font(family_id, &Default::default()) + .unwrap(); + let font_size = 14.0; + let buffer = cx.add_model(|cx| Buffer::new(0, "lorem ipsum dolor\n sit", 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)); + + assert_eq!( + surrounding_word(&snapshot, DisplayPoint::new(0, 0)), + DisplayPoint::new(0, 0)..DisplayPoint::new(0, 5) + ); + assert_eq!( + surrounding_word(&snapshot, DisplayPoint::new(0, 2)), + DisplayPoint::new(0, 0)..DisplayPoint::new(0, 5) + ); + assert_eq!( + surrounding_word(&snapshot, DisplayPoint::new(0, 5)), + DisplayPoint::new(0, 0)..DisplayPoint::new(0, 5) + ); + assert_eq!( + surrounding_word(&snapshot, DisplayPoint::new(0, 6)), + DisplayPoint::new(0, 6)..DisplayPoint::new(0, 11) + ); + assert_eq!( + surrounding_word(&snapshot, DisplayPoint::new(0, 7)), + DisplayPoint::new(0, 6)..DisplayPoint::new(0, 11) + ); + assert_eq!( + surrounding_word(&snapshot, DisplayPoint::new(0, 11)), + DisplayPoint::new(0, 6)..DisplayPoint::new(0, 11) + ); + assert_eq!( + surrounding_word(&snapshot, DisplayPoint::new(0, 13)), + DisplayPoint::new(0, 11)..DisplayPoint::new(0, 14) + ); + assert_eq!( + surrounding_word(&snapshot, DisplayPoint::new(0, 14)), + DisplayPoint::new(0, 14)..DisplayPoint::new(0, 19) + ); + assert_eq!( + surrounding_word(&snapshot, DisplayPoint::new(0, 17)), + DisplayPoint::new(0, 14)..DisplayPoint::new(0, 19) + ); + assert_eq!( + surrounding_word(&snapshot, DisplayPoint::new(0, 19)), + DisplayPoint::new(0, 14)..DisplayPoint::new(0, 19) + ); + assert_eq!( + surrounding_word(&snapshot, DisplayPoint::new(1, 0)), + DisplayPoint::new(1, 0)..DisplayPoint::new(1, 4) + ); + assert_eq!( + surrounding_word(&snapshot, DisplayPoint::new(1, 1)), + DisplayPoint::new(1, 0)..DisplayPoint::new(1, 4) + ); + assert_eq!( + surrounding_word(&snapshot, DisplayPoint::new(1, 6)), + DisplayPoint::new(1, 4)..DisplayPoint::new(1, 7) + ); + assert_eq!( + surrounding_word(&snapshot, DisplayPoint::new(1, 7)), + DisplayPoint::new(1, 4)..DisplayPoint::new(1, 7) + ); + } }