mod.rs

  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}