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 Ok((point, SelectionGoal::Column(goal_column)))
44}
45
46pub fn down(
47 map: &DisplayMapSnapshot,
48 mut point: DisplayPoint,
49 goal: SelectionGoal,
50) -> Result<(DisplayPoint, SelectionGoal)> {
51 let max_point = map.max_point();
52 let goal_column = if let SelectionGoal::Column(column) = goal {
53 column
54 } else {
55 map.column_to_chars(point.row(), point.column())
56 };
57
58 if point.row() < max_point.row() {
59 *point.row_mut() += 1;
60 *point.column_mut() = map.column_from_chars(point.row(), goal_column);
61 } else {
62 point = max_point;
63 }
64
65 Ok((point, SelectionGoal::Column(goal_column)))
66}
67
68pub fn line_beginning(
69 map: &DisplayMapSnapshot,
70 point: DisplayPoint,
71 toggle_indent: bool,
72) -> Result<DisplayPoint> {
73 let (indent, is_blank) = map.line_indent(point.row());
74 if toggle_indent && !is_blank && point.column() != indent {
75 Ok(DisplayPoint::new(point.row(), indent))
76 } else {
77 Ok(DisplayPoint::new(point.row(), 0))
78 }
79}
80
81pub fn line_end(map: &DisplayMapSnapshot, point: DisplayPoint) -> Result<DisplayPoint> {
82 Ok(DisplayPoint::new(point.row(), map.line_len(point.row())))
83}
84
85pub fn prev_word_boundary(map: &DisplayMapSnapshot, point: DisplayPoint) -> Result<DisplayPoint> {
86 if point.column() == 0 {
87 if point.row() == 0 {
88 Ok(DisplayPoint::new(0, 0))
89 } else {
90 let row = point.row() - 1;
91 Ok(DisplayPoint::new(row, map.line_len(row)))
92 }
93 } else {
94 let mut boundary = DisplayPoint::new(point.row(), 0);
95 let mut column = 0;
96 let mut prev_c = None;
97 for c in map.chars_at(boundary) {
98 if column >= point.column() {
99 break;
100 }
101
102 if prev_c.is_none() || char_kind(prev_c.unwrap()) != char_kind(c) {
103 *boundary.column_mut() = column;
104 }
105
106 prev_c = Some(c);
107 column += c.len_utf8() as u32;
108 }
109 Ok(boundary)
110 }
111}
112
113pub fn next_word_boundary(
114 map: &DisplayMapSnapshot,
115 mut point: DisplayPoint,
116) -> Result<DisplayPoint> {
117 let mut prev_c = None;
118 for c in map.chars_at(point) {
119 if prev_c.is_some() && (c == '\n' || char_kind(prev_c.unwrap()) != char_kind(c)) {
120 break;
121 }
122
123 if c == '\n' {
124 *point.row_mut() += 1;
125 *point.column_mut() = 0;
126 } else {
127 *point.column_mut() += c.len_utf8() as u32;
128 }
129 prev_c = Some(c);
130 }
131 Ok(point)
132}
133
134#[derive(Copy, Clone, Eq, PartialEq)]
135enum CharKind {
136 Newline,
137 Whitespace,
138 Punctuation,
139 Word,
140}
141
142fn char_kind(c: char) -> CharKind {
143 if c == '\n' {
144 CharKind::Newline
145 } else if c.is_whitespace() {
146 CharKind::Whitespace
147 } else if c.is_alphanumeric() || c == '_' {
148 CharKind::Word
149 } else {
150 CharKind::Punctuation
151 }
152}