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