movement.rs

  1use super::{Bias, DisplayMapSnapshot, DisplayPoint, SelectionGoal};
  2use anyhow::Result;
  3
  4pub fn left(map: &DisplayMapSnapshot, mut point: DisplayPoint) -> Result<DisplayPoint> {
  5    if point.column() > 0 {
  6        *point.column_mut() -= 1;
  7    } else if point.row() > 0 {
  8        *point.row_mut() -= 1;
  9        *point.column_mut() = map.line_len(point.row());
 10    }
 11    Ok(map.clip_point(point, Bias::Left))
 12}
 13
 14pub fn right(map: &DisplayMapSnapshot, mut point: DisplayPoint) -> Result<DisplayPoint> {
 15    let max_column = map.line_len(point.row());
 16    if point.column() < max_column {
 17        *point.column_mut() += 1;
 18    } else if point.row() < map.max_point().row() {
 19        *point.row_mut() += 1;
 20        *point.column_mut() = 0;
 21    }
 22    Ok(map.clip_point(point, Bias::Right))
 23}
 24
 25pub fn up(
 26    map: &DisplayMapSnapshot,
 27    mut point: DisplayPoint,
 28    goal: SelectionGoal,
 29) -> Result<(DisplayPoint, SelectionGoal)> {
 30    let goal_column = if let SelectionGoal::Column(column) = goal {
 31        column
 32    } else {
 33        map.column_to_chars(point.row(), point.column())
 34    };
 35
 36    if point.row() > 0 {
 37        *point.row_mut() -= 1;
 38        *point.column_mut() = map.column_from_chars(point.row(), goal_column);
 39    } else {
 40        point = DisplayPoint::new(0, 0);
 41    }
 42
 43    let clip_bias = if point.column() == map.line_len(point.row()) {
 44        Bias::Left
 45    } else {
 46        Bias::Right
 47    };
 48
 49    Ok((
 50        map.clip_point(point, clip_bias),
 51        SelectionGoal::Column(goal_column),
 52    ))
 53}
 54
 55pub fn down(
 56    map: &DisplayMapSnapshot,
 57    mut point: DisplayPoint,
 58    goal: SelectionGoal,
 59) -> Result<(DisplayPoint, SelectionGoal)> {
 60    let max_point = map.max_point();
 61    let goal_column = if let SelectionGoal::Column(column) = goal {
 62        column
 63    } else {
 64        map.column_to_chars(point.row(), point.column())
 65    };
 66
 67    if point.row() < max_point.row() {
 68        *point.row_mut() += 1;
 69        *point.column_mut() = map.column_from_chars(point.row(), goal_column);
 70    } else {
 71        point = max_point;
 72    }
 73
 74    let clip_bias = if point.column() == map.line_len(point.row()) {
 75        Bias::Left
 76    } else {
 77        Bias::Right
 78    };
 79
 80    Ok((
 81        map.clip_point(point, clip_bias),
 82        SelectionGoal::Column(goal_column),
 83    ))
 84}
 85
 86pub fn line_beginning(
 87    map: &DisplayMapSnapshot,
 88    point: DisplayPoint,
 89    toggle_indent: bool,
 90) -> Result<DisplayPoint> {
 91    let (indent, is_blank) = map.line_indent(point.row());
 92    if toggle_indent && !is_blank && point.column() != indent {
 93        Ok(DisplayPoint::new(point.row(), indent))
 94    } else {
 95        Ok(DisplayPoint::new(point.row(), 0))
 96    }
 97}
 98
 99pub fn line_end(map: &DisplayMapSnapshot, point: DisplayPoint) -> Result<DisplayPoint> {
100    let line_end = DisplayPoint::new(point.row(), map.line_len(point.row()));
101    Ok(map.clip_point(line_end, Bias::Left))
102}
103
104pub fn prev_word_boundary(
105    map: &DisplayMapSnapshot,
106    mut point: DisplayPoint,
107) -> Result<DisplayPoint> {
108    let mut line_start = 0;
109    if point.row() > 0 {
110        if let Some(indent) = map.soft_wrap_indent(point.row() - 1) {
111            line_start = indent;
112        }
113    }
114
115    if point.column() == line_start {
116        if point.row() == 0 {
117            return Ok(DisplayPoint::new(0, 0));
118        } else {
119            let row = point.row() - 1;
120            point = map.clip_point(DisplayPoint::new(row, map.line_len(row)), Bias::Left);
121        }
122    }
123
124    let mut boundary = DisplayPoint::new(point.row(), 0);
125    let mut column = 0;
126    let mut prev_char_kind = CharKind::Newline;
127    for c in map.chars_at(DisplayPoint::new(point.row(), 0)) {
128        if column >= point.column() {
129            break;
130        }
131
132        let char_kind = char_kind(c);
133        if char_kind != prev_char_kind
134            && char_kind != CharKind::Whitespace
135            && char_kind != CharKind::Newline
136        {
137            *boundary.column_mut() = column;
138        }
139
140        prev_char_kind = char_kind;
141        column += c.len_utf8() as u32;
142    }
143    Ok(boundary)
144}
145
146pub fn next_word_boundary(
147    map: &DisplayMapSnapshot,
148    mut point: DisplayPoint,
149) -> Result<DisplayPoint> {
150    let mut prev_char_kind = None;
151    for c in map.chars_at(point) {
152        let char_kind = char_kind(c);
153        if let Some(prev_char_kind) = prev_char_kind {
154            if c == '\n' {
155                break;
156            }
157            if prev_char_kind != char_kind
158                && prev_char_kind != CharKind::Whitespace
159                && prev_char_kind != CharKind::Newline
160            {
161                break;
162            }
163        }
164
165        if c == '\n' {
166            *point.row_mut() += 1;
167            *point.column_mut() = 0;
168        } else {
169            *point.column_mut() += c.len_utf8() as u32;
170        }
171        prev_char_kind = Some(char_kind);
172    }
173    Ok(point)
174}
175
176#[derive(Copy, Clone, Eq, PartialEq)]
177enum CharKind {
178    Newline,
179    Whitespace,
180    Punctuation,
181    Word,
182}
183
184fn char_kind(c: char) -> CharKind {
185    if c == '\n' {
186        CharKind::Newline
187    } else if c.is_whitespace() {
188        CharKind::Whitespace
189    } else if c.is_alphanumeric() || c == '_' {
190        CharKind::Word
191    } else {
192        CharKind::Punctuation
193    }
194}
195
196#[cfg(test)]
197mod tests {
198    use super::*;
199    use crate::{display_map::DisplayMap, Buffer};
200
201    #[gpui::test]
202    fn test_prev_next_word_boundary_multibyte(cx: &mut gpui::MutableAppContext) {
203        let tab_size = 4;
204        let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap();
205        let font_id = cx
206            .font_cache()
207            .select_font(family_id, &Default::default())
208            .unwrap();
209        let font_size = 14.0;
210
211        let buffer = cx.add_model(|cx| Buffer::new(0, "a bcΔ defγ hi—jk", cx));
212        let display_map =
213            cx.add_model(|cx| DisplayMap::new(buffer, tab_size, font_id, font_size, None, cx));
214        let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx));
215        assert_eq!(
216            prev_word_boundary(&snapshot, DisplayPoint::new(0, 12)).unwrap(),
217            DisplayPoint::new(0, 7)
218        );
219        assert_eq!(
220            prev_word_boundary(&snapshot, DisplayPoint::new(0, 7)).unwrap(),
221            DisplayPoint::new(0, 2)
222        );
223        assert_eq!(
224            prev_word_boundary(&snapshot, DisplayPoint::new(0, 6)).unwrap(),
225            DisplayPoint::new(0, 2)
226        );
227        assert_eq!(
228            prev_word_boundary(&snapshot, DisplayPoint::new(0, 2)).unwrap(),
229            DisplayPoint::new(0, 0)
230        );
231        assert_eq!(
232            prev_word_boundary(&snapshot, DisplayPoint::new(0, 1)).unwrap(),
233            DisplayPoint::new(0, 0)
234        );
235
236        assert_eq!(
237            next_word_boundary(&snapshot, DisplayPoint::new(0, 0)).unwrap(),
238            DisplayPoint::new(0, 1)
239        );
240        assert_eq!(
241            next_word_boundary(&snapshot, DisplayPoint::new(0, 1)).unwrap(),
242            DisplayPoint::new(0, 6)
243        );
244        assert_eq!(
245            next_word_boundary(&snapshot, DisplayPoint::new(0, 2)).unwrap(),
246            DisplayPoint::new(0, 6)
247        );
248        assert_eq!(
249            next_word_boundary(&snapshot, DisplayPoint::new(0, 6)).unwrap(),
250            DisplayPoint::new(0, 12)
251        );
252        assert_eq!(
253            next_word_boundary(&snapshot, DisplayPoint::new(0, 7)).unwrap(),
254            DisplayPoint::new(0, 12)
255        );
256    }
257}