use super::{Bias, DisplayMap, DisplayPoint, SelectionGoal}; use anyhow::Result; use gpui::AppContext; pub fn left(map: &DisplayMap, mut point: DisplayPoint, cx: &AppContext) -> Result { if point.column() > 0 { *point.column_mut() -= 1; } else if point.row() > 0 { *point.row_mut() -= 1; *point.column_mut() = map.line_len(point.row(), cx); } Ok(map.snapshot(cx).clip_point(point, Bias::Left)) } pub fn right(map: &DisplayMap, mut point: DisplayPoint, cx: &AppContext) -> Result { let max_column = map.line_len(point.row(), cx); if point.column() < max_column { *point.column_mut() += 1; } else if point.row() < map.max_point(cx).row() { *point.row_mut() += 1; *point.column_mut() = 0; } Ok(map.snapshot(cx).clip_point(point, Bias::Right)) } pub fn up( map: &DisplayMap, mut point: DisplayPoint, goal: SelectionGoal, cx: &AppContext, ) -> Result<(DisplayPoint, SelectionGoal)> { let map = map.snapshot(cx); let goal_column = if let SelectionGoal::Column(column) = goal { column } else { map.column_to_chars(point.row(), point.column()) }; if point.row() > 0 { *point.row_mut() -= 1; *point.column_mut() = map.column_from_chars(point.row(), goal_column); } else { point = DisplayPoint::new(0, 0); } Ok((point, SelectionGoal::Column(goal_column))) } pub fn down( map: &DisplayMap, mut point: DisplayPoint, goal: SelectionGoal, cx: &AppContext, ) -> Result<(DisplayPoint, SelectionGoal)> { let max_point = map.max_point(cx); let map = map.snapshot(cx); let goal_column = if let SelectionGoal::Column(column) = goal { column } else { map.column_to_chars(point.row(), point.column()) }; if point.row() < max_point.row() { *point.row_mut() += 1; *point.column_mut() = map.column_from_chars(point.row(), goal_column); } else { point = max_point; } Ok((point, SelectionGoal::Column(goal_column))) } pub fn line_beginning( map: &DisplayMap, point: DisplayPoint, toggle_indent: bool, cx: &AppContext, ) -> Result { let (indent, is_blank) = map.line_indent(point.row(), cx); if toggle_indent && !is_blank && point.column() != indent { Ok(DisplayPoint::new(point.row(), indent)) } else { Ok(DisplayPoint::new(point.row(), 0)) } } pub fn line_end(map: &DisplayMap, point: DisplayPoint, cx: &AppContext) -> Result { Ok(DisplayPoint::new( point.row(), map.line_len(point.row(), cx), )) } pub fn prev_word_boundary( map: &DisplayMap, point: DisplayPoint, cx: &AppContext, ) -> Result { if point.column() == 0 { if point.row() == 0 { Ok(DisplayPoint::new(0, 0)) } else { let row = point.row() - 1; Ok(DisplayPoint::new(row, map.line_len(row, cx))) } } else { let mut boundary = DisplayPoint::new(point.row(), 0); let mut column = 0; let mut prev_c = None; for c in map.snapshot(cx).chars_at(boundary) { 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) } } pub fn next_word_boundary( map: &DisplayMap, mut point: DisplayPoint, cx: &AppContext, ) -> Result { let mut prev_c = None; for c in map.snapshot(cx).chars_at(point) { if prev_c.is_some() && (c == '\n' || char_kind(prev_c.unwrap()) != char_kind(c)) { break; } if c == '\n' { *point.row_mut() += 1; *point.column_mut() = 0; } else { *point.column_mut() += c.len_utf8() as u32; } prev_c = Some(c); } Ok(point) } #[derive(Copy, Clone, Eq, PartialEq)] enum CharKind { Newline, Whitespace, Punctuation, Word, } fn char_kind(c: char) -> CharKind { if c == '\n' { CharKind::Newline } else if c.is_whitespace() { CharKind::Whitespace } else if c.is_alphanumeric() || c == '_' { CharKind::Word } else { CharKind::Punctuation } }