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}