1mod fold_map;
2
3use super::{buffer, Anchor, Bias, Buffer, Edit, Point, ToOffset, ToPoint};
4pub use fold_map::BufferRows;
5use fold_map::{FoldMap, FoldMapSnapshot};
6use gpui::{AppContext, ModelHandle};
7use std::ops::Range;
8
9pub struct DisplayMap {
10 buffer: ModelHandle<Buffer>,
11 fold_map: FoldMap,
12 tab_size: usize,
13}
14
15impl DisplayMap {
16 pub fn new(buffer: ModelHandle<Buffer>, tab_size: usize, ctx: &AppContext) -> Self {
17 DisplayMap {
18 buffer: buffer.clone(),
19 fold_map: FoldMap::new(buffer, ctx),
20 tab_size,
21 }
22 }
23
24 pub fn snapshot(&self, ctx: &AppContext) -> DisplayMapSnapshot {
25 DisplayMapSnapshot {
26 folds_snapshot: self.fold_map.snapshot(ctx),
27 tab_size: self.tab_size,
28 }
29 }
30
31 pub fn folds_in_range<'a, T>(
32 &'a self,
33 range: Range<T>,
34 app: &'a AppContext,
35 ) -> impl Iterator<Item = &'a Range<Anchor>>
36 where
37 T: ToOffset,
38 {
39 self.fold_map.folds_in_range(range, app)
40 }
41
42 pub fn fold<T: ToOffset>(
43 &mut self,
44 ranges: impl IntoIterator<Item = Range<T>>,
45 ctx: &AppContext,
46 ) {
47 self.fold_map.fold(ranges, ctx)
48 }
49
50 pub fn unfold<T: ToOffset>(
51 &mut self,
52 ranges: impl IntoIterator<Item = Range<T>>,
53 ctx: &AppContext,
54 ) {
55 self.fold_map.unfold(ranges, ctx)
56 }
57
58 pub fn is_line_folded(&self, display_row: u32, ctx: &AppContext) -> bool {
59 self.fold_map.is_line_folded(display_row, ctx)
60 }
61
62 pub fn text(&self, ctx: &AppContext) -> String {
63 self.snapshot(ctx)
64 .chunks_at(DisplayPoint::zero(), ctx)
65 .collect()
66 }
67
68 pub fn line(&self, display_row: u32, ctx: &AppContext) -> String {
69 let mut result = String::new();
70 for chunk in self
71 .snapshot(ctx)
72 .chunks_at(DisplayPoint::new(display_row, 0), ctx)
73 {
74 if let Some(ix) = chunk.find('\n') {
75 result.push_str(&chunk[0..ix]);
76 break;
77 } else {
78 result.push_str(chunk);
79 }
80 }
81 result
82 }
83
84 pub fn line_indent(&self, display_row: u32, ctx: &AppContext) -> (u32, bool) {
85 let mut indent = 0;
86 let mut is_blank = true;
87 for c in self
88 .snapshot(ctx)
89 .chars_at(DisplayPoint::new(display_row, 0), ctx)
90 {
91 if c == ' ' {
92 indent += 1;
93 } else {
94 is_blank = c == '\n';
95 break;
96 }
97 }
98 (indent, is_blank)
99 }
100
101 pub fn line_len(&self, row: u32, ctx: &AppContext) -> u32 {
102 DisplayPoint::new(row, self.fold_map.line_len(row, ctx))
103 .expand_tabs(self, ctx)
104 .column()
105 }
106
107 // TODO - make this delegate to the DisplayMapSnapshot
108 pub fn max_point(&self, ctx: &AppContext) -> DisplayPoint {
109 self.fold_map.max_point(ctx).expand_tabs(self, ctx)
110 }
111
112 pub fn rightmost_row(&self, ctx: &AppContext) -> u32 {
113 self.fold_map.rightmost_row(ctx)
114 }
115
116 pub fn anchor_before(&self, point: DisplayPoint, bias: Bias, app: &AppContext) -> Anchor {
117 self.buffer
118 .read(app)
119 .anchor_before(point.to_buffer_point(self, bias, app))
120 }
121
122 pub fn anchor_after(&self, point: DisplayPoint, bias: Bias, app: &AppContext) -> Anchor {
123 self.buffer
124 .read(app)
125 .anchor_after(point.to_buffer_point(self, bias, app))
126 }
127}
128
129pub struct DisplayMapSnapshot {
130 folds_snapshot: FoldMapSnapshot,
131 tab_size: usize,
132}
133
134impl DisplayMapSnapshot {
135 pub fn buffer_rows(&self, start_row: u32) -> BufferRows {
136 self.folds_snapshot.buffer_rows(start_row)
137 }
138
139 pub fn chunks_at<'a>(&'a self, point: DisplayPoint, app: &'a AppContext) -> Chunks<'a> {
140 let (point, expanded_char_column, to_next_stop) =
141 self.collapse_tabs(point, Bias::Left, app);
142 let fold_chunks = self
143 .folds_snapshot
144 .chunks_at(self.folds_snapshot.to_display_offset(point, app), app);
145 Chunks {
146 fold_chunks,
147 column: expanded_char_column,
148 tab_size: self.tab_size,
149 chunk: &SPACES[0..to_next_stop],
150 skip_leading_tab: to_next_stop > 0,
151 }
152 }
153
154 pub fn chars_at<'a>(
155 &'a self,
156 point: DisplayPoint,
157 app: &'a AppContext,
158 ) -> impl Iterator<Item = char> + 'a {
159 self.chunks_at(point, app).flat_map(str::chars)
160 }
161
162 pub fn column_to_chars(&self, display_row: u32, target: u32, ctx: &AppContext) -> u32 {
163 let mut count = 0;
164 let mut column = 0;
165 for c in self.chars_at(DisplayPoint::new(display_row, 0), ctx) {
166 if column >= target {
167 break;
168 }
169 count += 1;
170 column += c.len_utf8() as u32;
171 }
172 count
173 }
174
175 pub fn column_from_chars(&self, display_row: u32, char_count: u32, ctx: &AppContext) -> u32 {
176 let mut count = 0;
177 let mut column = 0;
178 for c in self.chars_at(DisplayPoint::new(display_row, 0), ctx) {
179 if c == '\n' || count >= char_count {
180 break;
181 }
182 count += 1;
183 column += c.len_utf8() as u32;
184 }
185 column
186 }
187
188 pub fn clip_point(&self, point: DisplayPoint, bias: Bias, ctx: &AppContext) -> DisplayPoint {
189 self.expand_tabs(
190 self.folds_snapshot
191 .clip_point(self.collapse_tabs(point, bias, ctx).0, bias, ctx),
192 ctx,
193 )
194 }
195
196 fn expand_tabs(&self, mut point: DisplayPoint, ctx: &AppContext) -> DisplayPoint {
197 let chars = self
198 .folds_snapshot
199 .chars_at(DisplayPoint(Point::new(point.row(), 0)), ctx);
200 let expanded = expand_tabs(chars, point.column() as usize, self.tab_size);
201 *point.column_mut() = expanded as u32;
202 point
203 }
204
205 fn collapse_tabs(
206 &self,
207 mut point: DisplayPoint,
208 bias: Bias,
209 ctx: &AppContext,
210 ) -> (DisplayPoint, usize, usize) {
211 let chars = self
212 .folds_snapshot
213 .chars_at(DisplayPoint(Point::new(point.row(), 0)), ctx);
214 let expanded = point.column() as usize;
215 let (collapsed, expanded_char_column, to_next_stop) =
216 collapse_tabs(chars, expanded, bias, self.tab_size);
217 *point.column_mut() = collapsed as u32;
218 (point, expanded_char_column, to_next_stop)
219 }
220}
221
222#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
223pub struct DisplayPoint(Point);
224
225impl DisplayPoint {
226 pub fn new(row: u32, column: u32) -> Self {
227 Self(Point::new(row, column))
228 }
229
230 pub fn zero() -> Self {
231 Self::new(0, 0)
232 }
233
234 pub fn row(self) -> u32 {
235 self.0.row
236 }
237
238 pub fn column(self) -> u32 {
239 self.0.column
240 }
241
242 pub fn row_mut(&mut self) -> &mut u32 {
243 &mut self.0.row
244 }
245
246 pub fn column_mut(&mut self) -> &mut u32 {
247 &mut self.0.column
248 }
249
250 pub fn to_buffer_point(self, map: &DisplayMap, bias: Bias, ctx: &AppContext) -> Point {
251 map.fold_map
252 .to_buffer_point(self.collapse_tabs(map, bias, ctx), ctx)
253 }
254
255 pub fn to_buffer_offset(self, map: &DisplayMap, bias: Bias, ctx: &AppContext) -> usize {
256 map.fold_map
257 .to_buffer_offset(self.collapse_tabs(&map, bias, ctx), ctx)
258 }
259
260 fn expand_tabs(self, map: &DisplayMap, ctx: &AppContext) -> Self {
261 map.snapshot(ctx).expand_tabs(self, ctx)
262 }
263
264 fn collapse_tabs(self, map: &DisplayMap, bias: Bias, ctx: &AppContext) -> Self {
265 map.snapshot(ctx).collapse_tabs(self, bias, ctx).0
266 }
267}
268
269impl Point {
270 pub fn to_display_point(self, map: &DisplayMap, ctx: &AppContext) -> DisplayPoint {
271 let mut display_point = map.fold_map.to_display_point(self, ctx);
272 let snapshot = map.fold_map.snapshot(ctx);
273 let chars = snapshot.chars_at(DisplayPoint::new(display_point.row(), 0), ctx);
274 *display_point.column_mut() =
275 expand_tabs(chars, display_point.column() as usize, map.tab_size) as u32;
276 display_point
277 }
278}
279
280impl Anchor {
281 pub fn to_display_point(&self, map: &DisplayMap, app: &AppContext) -> DisplayPoint {
282 self.to_point(map.buffer.read(app))
283 .to_display_point(map, app)
284 }
285}
286
287// Handles a tab width <= 16
288const SPACES: &'static str = " ";
289
290pub struct Chunks<'a> {
291 fold_chunks: fold_map::Chunks<'a>,
292 chunk: &'a str,
293 column: usize,
294 tab_size: usize,
295 skip_leading_tab: bool,
296}
297
298impl<'a> Iterator for Chunks<'a> {
299 type Item = &'a str;
300
301 fn next(&mut self) -> Option<Self::Item> {
302 if self.chunk.is_empty() {
303 if let Some(chunk) = self.fold_chunks.next() {
304 self.chunk = chunk;
305 if self.skip_leading_tab {
306 self.chunk = &self.chunk[1..];
307 self.skip_leading_tab = false;
308 }
309 } else {
310 return None;
311 }
312 }
313
314 for (ix, c) in self.chunk.char_indices() {
315 match c {
316 '\t' => {
317 if ix > 0 {
318 let (prefix, suffix) = self.chunk.split_at(ix);
319 self.chunk = suffix;
320 return Some(prefix);
321 } else {
322 self.chunk = &self.chunk[1..];
323 let len = self.tab_size - self.column % self.tab_size;
324 self.column += len;
325 return Some(&SPACES[0..len]);
326 }
327 }
328 '\n' => self.column = 0,
329 _ => self.column += 1,
330 }
331 }
332
333 let result = Some(self.chunk);
334 self.chunk = "";
335 result
336 }
337}
338
339pub fn expand_tabs(chars: impl Iterator<Item = char>, column: usize, tab_size: usize) -> usize {
340 let mut expanded_chars = 0;
341 let mut expanded_bytes = 0;
342 let mut collapsed_bytes = 0;
343 for c in chars {
344 if collapsed_bytes == column {
345 break;
346 }
347 if c == '\t' {
348 let tab_len = tab_size - expanded_chars % tab_size;
349 expanded_bytes += tab_len;
350 expanded_chars += tab_len;
351 } else {
352 expanded_bytes += c.len_utf8();
353 expanded_chars += 1;
354 }
355 collapsed_bytes += c.len_utf8();
356 }
357 expanded_bytes
358}
359
360pub fn collapse_tabs(
361 mut chars: impl Iterator<Item = char>,
362 column: usize,
363 bias: Bias,
364 tab_size: usize,
365) -> (usize, usize, usize) {
366 let mut expanded_bytes = 0;
367 let mut expanded_chars = 0;
368 let mut collapsed_bytes = 0;
369 while let Some(c) = chars.next() {
370 if expanded_bytes >= column {
371 break;
372 }
373
374 if c == '\t' {
375 let tab_len = tab_size - (expanded_chars % tab_size);
376 expanded_chars += tab_len;
377 expanded_bytes += tab_len;
378 if expanded_bytes > column {
379 expanded_chars -= expanded_bytes - column;
380 return match bias {
381 Bias::Left => (collapsed_bytes, expanded_chars, expanded_bytes - column),
382 Bias::Right => (collapsed_bytes + 1, expanded_chars, 0),
383 };
384 }
385 } else {
386 expanded_chars += 1;
387 expanded_bytes += c.len_utf8();
388 }
389
390 if expanded_bytes > column && matches!(bias, Bias::Left) {
391 expanded_chars -= 1;
392 break;
393 }
394
395 collapsed_bytes += c.len_utf8();
396 }
397 (collapsed_bytes, expanded_chars, 0)
398}
399
400#[cfg(test)]
401mod tests {
402 use super::*;
403 use crate::test::*;
404
405 #[gpui::test]
406 fn test_chunks_at(app: &mut gpui::MutableAppContext) {
407 let text = sample_text(6, 6);
408 let buffer = app.add_model(|ctx| Buffer::new(0, text, ctx));
409 let map = DisplayMap::new(buffer.clone(), 4, app.as_ref());
410 buffer
411 .update(app, |buffer, ctx| {
412 buffer.edit(
413 vec![
414 Point::new(1, 0)..Point::new(1, 0),
415 Point::new(1, 1)..Point::new(1, 1),
416 Point::new(2, 1)..Point::new(2, 1),
417 ],
418 "\t",
419 Some(ctx),
420 )
421 })
422 .unwrap();
423
424 assert_eq!(
425 &map.snapshot(app.as_ref())
426 .chunks_at(DisplayPoint::new(1, 0), app.as_ref())
427 .collect::<String>()[0..10],
428 " b bb"
429 );
430 assert_eq!(
431 &map.snapshot(app.as_ref())
432 .chunks_at(DisplayPoint::new(1, 2), app.as_ref())
433 .collect::<String>()[0..10],
434 " b bbbb"
435 );
436 assert_eq!(
437 &map.snapshot(app.as_ref())
438 .chunks_at(DisplayPoint::new(1, 6), app.as_ref())
439 .collect::<String>()[0..13],
440 " bbbbb\nc c"
441 );
442 }
443
444 #[gpui::test]
445 fn test_clip_point(app: &mut gpui::MutableAppContext) {
446 use Bias::{Left, Right};
447
448 let text = "\n'a', 'α',\t'✋',\t'❎', '🍐'\n";
449 let display_text = "\n'a', 'α', '✋', '❎', '🍐'\n";
450 let buffer = app.add_model(|ctx| Buffer::new(0, text, ctx));
451 let ctx = app.as_ref();
452 let map = DisplayMap::new(buffer.clone(), 4, ctx);
453 assert_eq!(map.text(ctx), display_text);
454
455 let map = map.snapshot(ctx);
456 for (input_column, bias, output_column) in vec![
457 ("'a', '".len(), Left, "'a', '".len()),
458 ("'a', '".len() + 1, Left, "'a', '".len()),
459 ("'a', '".len() + 1, Right, "'a', 'α".len()),
460 ("'a', 'α', ".len(), Left, "'a', 'α',".len()),
461 ("'a', 'α', ".len(), Right, "'a', 'α', ".len()),
462 ("'a', 'α', '".len() + 1, Left, "'a', 'α', '".len()),
463 ("'a', 'α', '".len() + 1, Right, "'a', 'α', '✋".len()),
464 ("'a', 'α', '✋',".len(), Right, "'a', 'α', '✋',".len()),
465 ("'a', 'α', '✋', ".len(), Left, "'a', 'α', '✋',".len()),
466 (
467 "'a', 'α', '✋', ".len(),
468 Right,
469 "'a', 'α', '✋', ".len(),
470 ),
471 ] {
472 assert_eq!(
473 map.clip_point(DisplayPoint::new(1, input_column as u32), bias, ctx),
474 DisplayPoint::new(1, output_column as u32),
475 "clip_point(({}, {}))",
476 1,
477 input_column,
478 );
479 }
480 }
481
482 #[test]
483 fn test_expand_tabs() {
484 assert_eq!(expand_tabs("\t".chars(), 0, 4), 0);
485 assert_eq!(expand_tabs("\t".chars(), 1, 4), 4);
486 assert_eq!(expand_tabs("\ta".chars(), 2, 4), 5);
487 }
488
489 #[gpui::test]
490 fn test_tabs_with_multibyte_chars(app: &mut gpui::MutableAppContext) {
491 let text = "✅\t\tα\nβ\t\n🏀β\t\tγ";
492 let buffer = app.add_model(|ctx| Buffer::new(0, text, ctx));
493 let ctx = app.as_ref();
494 let map = DisplayMap::new(buffer.clone(), 4, ctx);
495 assert_eq!(map.text(ctx), "✅ α\nβ \n🏀β γ");
496
497 let point = Point::new(0, "✅\t\t".len() as u32);
498 let display_point = DisplayPoint::new(0, "✅ ".len() as u32);
499 assert_eq!(point.to_display_point(&map, ctx), display_point);
500 assert_eq!(display_point.to_buffer_point(&map, Bias::Left, ctx), point,);
501
502 let point = Point::new(1, "β\t".len() as u32);
503 let display_point = DisplayPoint::new(1, "β ".len() as u32);
504 assert_eq!(point.to_display_point(&map, ctx), display_point);
505 assert_eq!(display_point.to_buffer_point(&map, Bias::Left, ctx), point,);
506
507 let point = Point::new(2, "🏀β\t\t".len() as u32);
508 let display_point = DisplayPoint::new(2, "🏀β ".len() as u32);
509 assert_eq!(point.to_display_point(&map, ctx), display_point);
510 assert_eq!(display_point.to_buffer_point(&map, Bias::Left, ctx), point,);
511
512 // Display points inside of expanded tabs
513 assert_eq!(
514 DisplayPoint::new(0, "✅ ".len() as u32).to_buffer_point(&map, Bias::Right, ctx),
515 Point::new(0, "✅\t\t".len() as u32),
516 );
517 assert_eq!(
518 DisplayPoint::new(0, "✅ ".len() as u32).to_buffer_point(&map, Bias::Left, ctx),
519 Point::new(0, "✅\t".len() as u32),
520 );
521 assert_eq!(
522 map.snapshot(ctx)
523 .chunks_at(DisplayPoint::new(0, "✅ ".len() as u32), ctx)
524 .collect::<String>(),
525 " α\nβ \n🏀β γ"
526 );
527 assert_eq!(
528 DisplayPoint::new(0, "✅ ".len() as u32).to_buffer_point(&map, Bias::Right, ctx),
529 Point::new(0, "✅\t".len() as u32),
530 );
531 assert_eq!(
532 DisplayPoint::new(0, "✅ ".len() as u32).to_buffer_point(&map, Bias::Left, ctx),
533 Point::new(0, "✅".len() as u32),
534 );
535 assert_eq!(
536 map.snapshot(ctx)
537 .chunks_at(DisplayPoint::new(0, "✅ ".len() as u32), ctx)
538 .collect::<String>(),
539 " α\nβ \n🏀β γ"
540 );
541
542 // Clipping display points inside of multi-byte characters
543 assert_eq!(
544 map.snapshot(ctx).clip_point(
545 DisplayPoint::new(0, "✅".len() as u32 - 1),
546 Bias::Left,
547 ctx
548 ),
549 DisplayPoint::new(0, 0)
550 );
551 assert_eq!(
552 map.snapshot(ctx).clip_point(
553 DisplayPoint::new(0, "✅".len() as u32 - 1),
554 Bias::Right,
555 ctx
556 ),
557 DisplayPoint::new(0, "✅".len() as u32)
558 );
559 }
560
561 #[gpui::test]
562 fn test_max_point(app: &mut gpui::MutableAppContext) {
563 let buffer = app.add_model(|ctx| Buffer::new(0, "aaa\n\t\tbbb", ctx));
564 let map = DisplayMap::new(buffer.clone(), 4, app.as_ref());
565 assert_eq!(map.max_point(app.as_ref()), DisplayPoint::new(1, 11))
566 }
567}