display_map.rs

  1mod fold_map;
  2mod tab_map;
  3mod wrap_map;
  4
  5use super::{buffer, Anchor, Bias, Buffer, Point, ToOffset, ToPoint};
  6use fold_map::FoldMap;
  7use gpui::{fonts::FontId, Entity, ModelContext, ModelHandle};
  8use std::ops::Range;
  9use tab_map::TabMap;
 10use wrap_map::WrapMap;
 11pub use wrap_map::{BufferRows, HighlightedChunks};
 12
 13pub struct DisplayMap {
 14    buffer: ModelHandle<Buffer>,
 15    fold_map: FoldMap,
 16    tab_map: TabMap,
 17    wrap_map: ModelHandle<WrapMap>,
 18}
 19
 20impl Entity for DisplayMap {
 21    type Event = ();
 22}
 23
 24impl DisplayMap {
 25    pub fn new(
 26        buffer: ModelHandle<Buffer>,
 27        tab_size: usize,
 28        font_id: FontId,
 29        font_size: f32,
 30        wrap_width: Option<f32>,
 31        cx: &mut ModelContext<Self>,
 32    ) -> Self {
 33        let (fold_map, snapshot) = FoldMap::new(buffer.clone(), cx);
 34        let (tab_map, snapshot) = TabMap::new(snapshot, tab_size);
 35        let wrap_map =
 36            cx.add_model(|cx| WrapMap::new(snapshot, font_id, font_size, wrap_width, cx));
 37        cx.observe(&wrap_map, |_, _, cx| cx.notify()).detach();
 38        DisplayMap {
 39            buffer,
 40            fold_map,
 41            tab_map,
 42            wrap_map,
 43        }
 44    }
 45
 46    pub fn snapshot(&self, cx: &mut ModelContext<Self>) -> DisplayMapSnapshot {
 47        let (folds_snapshot, edits) = self.fold_map.read(cx);
 48        let (tabs_snapshot, edits) = self.tab_map.sync(folds_snapshot.clone(), edits);
 49        let wraps_snapshot = self
 50            .wrap_map
 51            .update(cx, |map, cx| map.sync(tabs_snapshot.clone(), edits, cx));
 52        DisplayMapSnapshot {
 53            buffer_snapshot: self.buffer.read(cx).snapshot(),
 54            folds_snapshot,
 55            tabs_snapshot,
 56            wraps_snapshot,
 57        }
 58    }
 59
 60    pub fn fold<T: ToOffset>(
 61        &mut self,
 62        ranges: impl IntoIterator<Item = Range<T>>,
 63        cx: &mut ModelContext<Self>,
 64    ) {
 65        let (mut fold_map, snapshot, edits) = self.fold_map.write(cx);
 66        let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
 67        self.wrap_map
 68            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 69        let (snapshot, edits) = fold_map.fold(ranges, cx);
 70        let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
 71        self.wrap_map
 72            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 73    }
 74
 75    pub fn unfold<T: ToOffset>(
 76        &mut self,
 77        ranges: impl IntoIterator<Item = Range<T>>,
 78        cx: &mut ModelContext<Self>,
 79    ) {
 80        let (mut fold_map, snapshot, edits) = self.fold_map.write(cx);
 81        let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
 82        self.wrap_map
 83            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 84        let (snapshot, edits) = fold_map.unfold(ranges, cx);
 85        let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
 86        self.wrap_map
 87            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 88    }
 89
 90    pub fn set_font(&self, font_id: FontId, font_size: f32, cx: &mut ModelContext<Self>) {
 91        self.wrap_map
 92            .update(cx, |map, cx| map.set_font(font_id, font_size, cx));
 93    }
 94
 95    pub fn set_wrap_width(&self, width: Option<f32>, cx: &mut ModelContext<Self>) -> bool {
 96        self.wrap_map
 97            .update(cx, |map, cx| map.set_wrap_width(width, cx))
 98    }
 99
100    #[cfg(test)]
101    pub fn is_rewrapping(&self, cx: &gpui::AppContext) -> bool {
102        self.wrap_map.read(cx).is_rewrapping()
103    }
104}
105
106pub struct DisplayMapSnapshot {
107    buffer_snapshot: buffer::Snapshot,
108    folds_snapshot: fold_map::Snapshot,
109    tabs_snapshot: tab_map::Snapshot,
110    wraps_snapshot: wrap_map::Snapshot,
111}
112
113impl DisplayMapSnapshot {
114    #[cfg(test)]
115    pub fn fold_count(&self) -> usize {
116        self.folds_snapshot.fold_count()
117    }
118
119    pub fn is_empty(&self) -> bool {
120        self.buffer_snapshot.len() == 0
121    }
122
123    pub fn buffer_rows(&self, start_row: u32) -> BufferRows {
124        self.wraps_snapshot.buffer_rows(start_row)
125    }
126
127    pub fn buffer_row_count(&self) -> u32 {
128        self.buffer_snapshot.max_point().row + 1
129    }
130
131    pub fn prev_row_boundary(&self, mut display_point: DisplayPoint) -> (DisplayPoint, Point) {
132        loop {
133            *display_point.column_mut() = 0;
134            let mut point = display_point.to_buffer_point(self, Bias::Left);
135            point.column = 0;
136            let next_display_point = point.to_display_point(self, Bias::Left);
137            if next_display_point == display_point {
138                return (display_point, point);
139            }
140            display_point = next_display_point;
141        }
142    }
143
144    pub fn next_row_boundary(&self, mut display_point: DisplayPoint) -> (DisplayPoint, Point) {
145        loop {
146            *display_point.column_mut() = self.line_len(display_point.row());
147            let mut point = display_point.to_buffer_point(self, Bias::Right);
148            point.column = self.buffer_snapshot.line_len(point.row);
149            let next_display_point = point.to_display_point(self, Bias::Right);
150            if next_display_point == display_point {
151                return (display_point, point);
152            }
153            display_point = next_display_point;
154        }
155    }
156
157    pub fn max_point(&self) -> DisplayPoint {
158        DisplayPoint(self.wraps_snapshot.max_point())
159    }
160
161    pub fn chunks_at(&self, display_row: u32) -> wrap_map::Chunks {
162        self.wraps_snapshot.chunks_at(display_row)
163    }
164
165    pub fn highlighted_chunks_for_rows(
166        &mut self,
167        display_rows: Range<u32>,
168    ) -> wrap_map::HighlightedChunks {
169        self.wraps_snapshot
170            .highlighted_chunks_for_rows(display_rows)
171    }
172
173    pub fn chars_at<'a>(&'a self, point: DisplayPoint) -> impl Iterator<Item = char> + 'a {
174        let mut column = 0;
175        let mut chars = self.chunks_at(point.row()).flat_map(str::chars);
176        while column < point.column() {
177            if let Some(c) = chars.next() {
178                column += c.len_utf8() as u32;
179            } else {
180                break;
181            }
182        }
183        chars
184    }
185
186    pub fn column_to_chars(&self, display_row: u32, target: u32) -> u32 {
187        let mut count = 0;
188        let mut column = 0;
189        for c in self.chars_at(DisplayPoint::new(display_row, 0)) {
190            if column >= target {
191                break;
192            }
193            count += 1;
194            column += c.len_utf8() as u32;
195        }
196        count
197    }
198
199    pub fn column_from_chars(&self, display_row: u32, char_count: u32) -> u32 {
200        let mut count = 0;
201        let mut column = 0;
202        for c in self.chars_at(DisplayPoint::new(display_row, 0)) {
203            if c == '\n' || count >= char_count {
204                break;
205            }
206            count += 1;
207            column += c.len_utf8() as u32;
208        }
209        column
210    }
211
212    pub fn clip_point(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint {
213        DisplayPoint(self.wraps_snapshot.clip_point(point.0, bias))
214    }
215
216    pub fn folds_in_range<'a, T>(
217        &'a self,
218        range: Range<T>,
219    ) -> impl Iterator<Item = &'a Range<Anchor>>
220    where
221        T: ToOffset,
222    {
223        self.folds_snapshot.folds_in_range(range)
224    }
225
226    pub fn intersects_fold<T: ToOffset>(&self, offset: T) -> bool {
227        self.folds_snapshot.intersects_fold(offset)
228    }
229
230    pub fn is_line_folded(&self, display_row: u32) -> bool {
231        let wrap_point = DisplayPoint::new(display_row, 0).0;
232        let row = self.wraps_snapshot.to_tab_point(wrap_point).row();
233        self.folds_snapshot.is_line_folded(row)
234    }
235
236    pub fn soft_wrap_indent(&self, display_row: u32) -> Option<u32> {
237        self.wraps_snapshot.soft_wrap_indent(display_row)
238    }
239
240    pub fn text(&self) -> String {
241        self.chunks_at(0).collect()
242    }
243
244    pub fn line(&self, display_row: u32) -> String {
245        let mut result = String::new();
246        for chunk in self.chunks_at(display_row) {
247            if let Some(ix) = chunk.find('\n') {
248                result.push_str(&chunk[0..ix]);
249                break;
250            } else {
251                result.push_str(chunk);
252            }
253        }
254        result
255    }
256
257    pub fn line_indent(&self, display_row: u32) -> (u32, bool) {
258        let mut indent = 0;
259        let mut is_blank = true;
260        for c in self.chars_at(DisplayPoint::new(display_row, 0)) {
261            if c == ' ' {
262                indent += 1;
263            } else {
264                is_blank = c == '\n';
265                break;
266            }
267        }
268        (indent, is_blank)
269    }
270
271    pub fn line_len(&self, row: u32) -> u32 {
272        self.wraps_snapshot.line_len(row)
273    }
274
275    pub fn longest_row(&self) -> u32 {
276        self.wraps_snapshot.longest_row()
277    }
278
279    pub fn anchor_before(&self, point: DisplayPoint, bias: Bias) -> Anchor {
280        self.buffer_snapshot
281            .anchor_before(point.to_buffer_point(self, bias))
282    }
283
284    pub fn anchor_after(&self, point: DisplayPoint, bias: Bias) -> Anchor {
285        self.buffer_snapshot
286            .anchor_after(point.to_buffer_point(self, bias))
287    }
288}
289
290#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
291pub struct DisplayPoint(wrap_map::WrapPoint);
292
293impl DisplayPoint {
294    pub fn new(row: u32, column: u32) -> Self {
295        Self(wrap_map::WrapPoint::new(row, column))
296    }
297
298    pub fn zero() -> Self {
299        Self::new(0, 0)
300    }
301
302    #[cfg(test)]
303    pub fn is_zero(&self) -> bool {
304        self.0.is_zero()
305    }
306
307    pub fn row(self) -> u32 {
308        self.0.row()
309    }
310
311    pub fn column(self) -> u32 {
312        self.0.column()
313    }
314
315    pub fn row_mut(&mut self) -> &mut u32 {
316        self.0.row_mut()
317    }
318
319    pub fn column_mut(&mut self) -> &mut u32 {
320        self.0.column_mut()
321    }
322
323    pub fn to_buffer_point(self, map: &DisplayMapSnapshot, bias: Bias) -> Point {
324        let unwrapped_point = map.wraps_snapshot.to_tab_point(self.0);
325        let unexpanded_point = map.tabs_snapshot.to_fold_point(unwrapped_point, bias).0;
326        unexpanded_point.to_buffer_point(&map.folds_snapshot)
327    }
328
329    pub fn to_buffer_offset(self, map: &DisplayMapSnapshot, bias: Bias) -> usize {
330        let unwrapped_point = map.wraps_snapshot.to_tab_point(self.0);
331        let unexpanded_point = map.tabs_snapshot.to_fold_point(unwrapped_point, bias).0;
332        unexpanded_point.to_buffer_offset(&map.folds_snapshot)
333    }
334}
335
336impl Point {
337    pub fn to_display_point(self, map: &DisplayMapSnapshot, bias: Bias) -> DisplayPoint {
338        let fold_point = self.to_fold_point(&map.folds_snapshot, bias);
339        let tab_point = map.tabs_snapshot.to_tab_point(fold_point);
340        let wrap_point = map.wraps_snapshot.to_wrap_point(tab_point);
341        DisplayPoint(wrap_point)
342    }
343}
344
345impl Anchor {
346    pub fn to_display_point(&self, map: &DisplayMapSnapshot, bias: Bias) -> DisplayPoint {
347        self.to_point(&map.buffer_snapshot)
348            .to_display_point(map, bias)
349    }
350}
351
352#[cfg(test)]
353mod tests {
354    use super::*;
355    use crate::{
356        editor::movement,
357        language::{Language, LanguageConfig},
358        test::*,
359        theme::SyntaxTheme,
360        util::RandomCharIter,
361    };
362    use buffer::{History, SelectionGoal};
363    use gpui::{color::Color, MutableAppContext};
364    use rand::{prelude::StdRng, Rng};
365    use std::{env, sync::Arc};
366    use Bias::*;
367
368    #[gpui::test(iterations = 100)]
369    async fn test_random(mut cx: gpui::TestAppContext, mut rng: StdRng) {
370        cx.foreground().set_block_on_ticks(0..=50);
371        cx.foreground().forbid_parking();
372        let operations = env::var("OPERATIONS")
373            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
374            .unwrap_or(10);
375
376        let font_cache = cx.font_cache().clone();
377        let tab_size = rng.gen_range(1..=4);
378        let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
379        let font_id = font_cache
380            .select_font(family_id, &Default::default())
381            .unwrap();
382        let font_size = 14.0;
383        let max_wrap_width = 300.0;
384        let mut wrap_width = if rng.gen_bool(0.1) {
385            None
386        } else {
387            Some(rng.gen_range(0.0..=max_wrap_width))
388        };
389
390        log::info!("tab size: {}", tab_size);
391        log::info!("wrap width: {:?}", wrap_width);
392
393        let buffer = cx.add_model(|cx| {
394            let len = rng.gen_range(0..10);
395            let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
396            Buffer::new(0, text, cx)
397        });
398
399        let map = cx.add_model(|cx| {
400            DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, wrap_width, cx)
401        });
402        let (_observer, notifications) = Observer::new(&map, &mut cx);
403        let mut fold_count = 0;
404
405        for _i in 0..operations {
406            match rng.gen_range(0..100) {
407                0..=19 => {
408                    wrap_width = if rng.gen_bool(0.2) {
409                        None
410                    } else {
411                        Some(rng.gen_range(0.0..=max_wrap_width))
412                    };
413                    log::info!("setting wrap width to {:?}", wrap_width);
414                    map.update(&mut cx, |map, cx| map.set_wrap_width(wrap_width, cx));
415                }
416                20..=80 => {
417                    let mut ranges = Vec::new();
418                    for _ in 0..rng.gen_range(1..=3) {
419                        buffer.read_with(&cx, |buffer, _| {
420                            let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right);
421                            let start = buffer.clip_offset(rng.gen_range(0..=end), Left);
422                            ranges.push(start..end);
423                        });
424                    }
425
426                    if rng.gen() && fold_count > 0 {
427                        log::info!("unfolding ranges: {:?}", ranges);
428                        map.update(&mut cx, |map, cx| {
429                            map.unfold(ranges, cx);
430                        });
431                    } else {
432                        log::info!("folding ranges: {:?}", ranges);
433                        map.update(&mut cx, |map, cx| {
434                            map.fold(ranges, cx);
435                        });
436                    }
437                }
438                _ => {
439                    buffer.update(&mut cx, |buffer, cx| buffer.randomly_mutate(&mut rng, cx));
440                }
441            }
442
443            if map.read_with(&cx, |map, cx| map.is_rewrapping(cx)) {
444                notifications.recv().await.unwrap();
445            }
446
447            let snapshot = map.update(&mut cx, |map, cx| map.snapshot(cx));
448            fold_count = snapshot.fold_count();
449            log::info!("buffer text: {:?}", buffer.read_with(&cx, |b, _| b.text()));
450            log::info!("display text: {:?}", snapshot.text());
451
452            // Line boundaries
453            for _ in 0..5 {
454                let row = rng.gen_range(0..=snapshot.max_point().row());
455                let column = rng.gen_range(0..=snapshot.line_len(row));
456                let point = snapshot.clip_point(DisplayPoint::new(row, column), Left);
457
458                let (prev_display_bound, prev_buffer_bound) = snapshot.prev_row_boundary(point);
459                let (next_display_bound, next_buffer_bound) = snapshot.next_row_boundary(point);
460
461                assert!(prev_display_bound <= point);
462                assert!(next_display_bound >= point);
463                assert_eq!(prev_buffer_bound.column, 0);
464                assert_eq!(prev_display_bound.column(), 0);
465                if next_display_bound < snapshot.max_point() {
466                    assert_eq!(
467                        buffer
468                            .read_with(&cx, |buffer, _| buffer.chars_at(next_buffer_bound).next()),
469                        Some('\n')
470                    )
471                }
472
473                assert_eq!(
474                    prev_display_bound,
475                    prev_buffer_bound.to_display_point(&snapshot, Left),
476                    "row boundary before {:?}. reported buffer row boundary: {:?}",
477                    point,
478                    prev_buffer_bound
479                );
480                assert_eq!(
481                    next_display_bound,
482                    next_buffer_bound.to_display_point(&snapshot, Right),
483                    "display row boundary after {:?}. reported buffer row boundary: {:?}",
484                    point,
485                    next_buffer_bound
486                );
487                assert_eq!(
488                    prev_buffer_bound,
489                    prev_display_bound.to_buffer_point(&snapshot, Left),
490                    "row boundary before {:?}. reported display row boundary: {:?}",
491                    point,
492                    prev_display_bound
493                );
494                assert_eq!(
495                    next_buffer_bound,
496                    next_display_bound.to_buffer_point(&snapshot, Right),
497                    "row boundary after {:?}. reported display row boundary: {:?}",
498                    point,
499                    next_display_bound
500                );
501            }
502
503            // Movement
504            for _ in 0..5 {
505                let row = rng.gen_range(0..=snapshot.max_point().row());
506                let column = rng.gen_range(0..=snapshot.line_len(row));
507                let point = snapshot.clip_point(DisplayPoint::new(row, column), Left);
508
509                log::info!("Moving from point {:?}", point);
510
511                let moved_right = movement::right(&snapshot, point).unwrap();
512                log::info!("Right {:?}", moved_right);
513                if point < snapshot.max_point() {
514                    assert!(moved_right > point);
515                    if point.column() == snapshot.line_len(point.row())
516                        || snapshot.soft_wrap_indent(point.row()).is_some()
517                            && point.column() == snapshot.line_len(point.row()) - 1
518                    {
519                        assert!(moved_right.row() > point.row());
520                    }
521                } else {
522                    assert_eq!(moved_right, point);
523                }
524
525                let moved_left = movement::left(&snapshot, point).unwrap();
526                log::info!("Left {:?}", moved_left);
527                if !point.is_zero() {
528                    assert!(moved_left < point);
529                    if point.column() == 0 {
530                        assert!(moved_left.row() < point.row());
531                    }
532                } else {
533                    assert!(moved_left.is_zero());
534                }
535            }
536        }
537    }
538
539    #[gpui::test]
540    fn test_soft_wraps(cx: &mut MutableAppContext) {
541        cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
542        cx.foreground().forbid_parking();
543
544        let font_cache = cx.font_cache();
545
546        let tab_size = 4;
547        let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
548        let font_id = font_cache
549            .select_font(family_id, &Default::default())
550            .unwrap();
551        let font_size = 12.0;
552        let wrap_width = Some(64.);
553
554        let text = "one two three four five\nsix seven eight";
555        let buffer = cx.add_model(|cx| Buffer::new(0, text.to_string(), cx));
556        let map = cx.add_model(|cx| {
557            DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, wrap_width, cx)
558        });
559
560        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
561        assert_eq!(
562            snapshot.chunks_at(0).collect::<String>(),
563            "one two \nthree four \nfive\nsix seven \neight"
564        );
565        assert_eq!(
566            snapshot.clip_point(DisplayPoint::new(0, 8), Bias::Left),
567            DisplayPoint::new(0, 7)
568        );
569        assert_eq!(
570            snapshot.clip_point(DisplayPoint::new(0, 8), Bias::Right),
571            DisplayPoint::new(1, 0)
572        );
573        assert_eq!(
574            movement::right(&snapshot, DisplayPoint::new(0, 7)).unwrap(),
575            DisplayPoint::new(1, 0)
576        );
577        assert_eq!(
578            movement::left(&snapshot, DisplayPoint::new(1, 0)).unwrap(),
579            DisplayPoint::new(0, 7)
580        );
581        assert_eq!(
582            movement::up(&snapshot, DisplayPoint::new(1, 10), SelectionGoal::None).unwrap(),
583            (DisplayPoint::new(0, 7), SelectionGoal::Column(10))
584        );
585        assert_eq!(
586            movement::down(
587                &snapshot,
588                DisplayPoint::new(0, 7),
589                SelectionGoal::Column(10)
590            )
591            .unwrap(),
592            (DisplayPoint::new(1, 10), SelectionGoal::Column(10))
593        );
594        assert_eq!(
595            movement::down(
596                &snapshot,
597                DisplayPoint::new(1, 10),
598                SelectionGoal::Column(10)
599            )
600            .unwrap(),
601            (DisplayPoint::new(2, 4), SelectionGoal::Column(10))
602        );
603
604        buffer.update(cx, |buffer, cx| {
605            let ix = buffer.text().find("seven").unwrap();
606            buffer.edit(vec![ix..ix], "and ", cx);
607        });
608
609        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
610        assert_eq!(
611            snapshot.chunks_at(1).collect::<String>(),
612            "three four \nfive\nsix and \nseven eight"
613        );
614
615        // Re-wrap on font size changes
616        map.update(cx, |map, cx| map.set_font(font_id, font_size + 3., cx));
617
618        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
619        assert_eq!(
620            snapshot.chunks_at(1).collect::<String>(),
621            "three \nfour five\nsix and \nseven \neight"
622        )
623    }
624
625    #[gpui::test]
626    fn test_chunks_at(cx: &mut gpui::MutableAppContext) {
627        let text = sample_text(6, 6);
628        let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
629        let tab_size = 4;
630        let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap();
631        let font_id = cx
632            .font_cache()
633            .select_font(family_id, &Default::default())
634            .unwrap();
635        let font_size = 14.0;
636        let map = cx.add_model(|cx| {
637            DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, None, cx)
638        });
639        buffer.update(cx, |buffer, cx| {
640            buffer.edit(
641                vec![
642                    Point::new(1, 0)..Point::new(1, 0),
643                    Point::new(1, 1)..Point::new(1, 1),
644                    Point::new(2, 1)..Point::new(2, 1),
645                ],
646                "\t",
647                cx,
648            )
649        });
650
651        assert_eq!(
652            map.update(cx, |map, cx| map.snapshot(cx))
653                .chunks_at(1)
654                .collect::<String>()
655                .lines()
656                .next(),
657            Some("    b   bbbbb")
658        );
659        assert_eq!(
660            map.update(cx, |map, cx| map.snapshot(cx))
661                .chunks_at(2)
662                .collect::<String>()
663                .lines()
664                .next(),
665            Some("c   ccccc")
666        );
667    }
668
669    #[gpui::test]
670    async fn test_highlighted_chunks_at(mut cx: gpui::TestAppContext) {
671        use unindent::Unindent as _;
672
673        let grammar = tree_sitter_rust::language();
674        let text = r#"
675            fn outer() {}
676
677            mod module {
678                fn inner() {}
679            }"#
680        .unindent();
681        let highlight_query = tree_sitter::Query::new(
682            grammar,
683            r#"
684            (mod_item name: (identifier) body: _ @mod.body)
685            (function_item name: (identifier) @fn.name)"#,
686        )
687        .unwrap();
688        let theme = SyntaxTheme::new(vec![
689            ("mod.body".to_string(), Color::from_u32(0xff0000ff).into()),
690            ("fn.name".to_string(), Color::from_u32(0x00ff00ff).into()),
691        ]);
692        let lang = Arc::new(Language {
693            config: LanguageConfig {
694                name: "Test".to_string(),
695                path_suffixes: vec![".test".to_string()],
696                ..Default::default()
697            },
698            grammar: grammar.clone(),
699            highlight_query,
700            brackets_query: tree_sitter::Query::new(grammar, "").unwrap(),
701            highlight_map: Default::default(),
702        });
703        lang.set_theme(&theme);
704
705        let buffer = cx.add_model(|cx| {
706            Buffer::from_history(0, History::new(text.into()), None, Some(lang), cx)
707        });
708        buffer.condition(&cx, |buf, _| !buf.is_parsing()).await;
709
710        let tab_size = 2;
711        let font_cache = cx.font_cache();
712        let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
713        let font_id = font_cache
714            .select_font(family_id, &Default::default())
715            .unwrap();
716        let font_size = 14.0;
717
718        let map =
719            cx.add_model(|cx| DisplayMap::new(buffer, tab_size, font_id, font_size, None, cx));
720        assert_eq!(
721            cx.update(|cx| highlighted_chunks(0..5, &map, &theme, cx)),
722            vec![
723                ("fn ".to_string(), None),
724                ("outer".to_string(), Some("fn.name")),
725                ("() {}\n\nmod module ".to_string(), None),
726                ("{\n    fn ".to_string(), Some("mod.body")),
727                ("inner".to_string(), Some("fn.name")),
728                ("() {}\n}".to_string(), Some("mod.body")),
729            ]
730        );
731        assert_eq!(
732            cx.update(|cx| highlighted_chunks(3..5, &map, &theme, cx)),
733            vec![
734                ("    fn ".to_string(), Some("mod.body")),
735                ("inner".to_string(), Some("fn.name")),
736                ("() {}\n}".to_string(), Some("mod.body")),
737            ]
738        );
739
740        map.update(&mut cx, |map, cx| {
741            map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], cx)
742        });
743        assert_eq!(
744            cx.update(|cx| highlighted_chunks(0..2, &map, &theme, cx)),
745            vec![
746                ("fn ".to_string(), None),
747                ("out".to_string(), Some("fn.name")),
748                ("".to_string(), None),
749                ("  fn ".to_string(), Some("mod.body")),
750                ("inner".to_string(), Some("fn.name")),
751                ("() {}\n}".to_string(), Some("mod.body")),
752            ]
753        );
754    }
755
756    #[gpui::test]
757    async fn test_highlighted_chunks_with_soft_wrapping(mut cx: gpui::TestAppContext) {
758        use unindent::Unindent as _;
759
760        cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
761
762        let grammar = tree_sitter_rust::language();
763        let text = r#"
764            fn outer() {}
765
766            mod module {
767                fn inner() {}
768            }"#
769        .unindent();
770        let highlight_query = tree_sitter::Query::new(
771            grammar,
772            r#"
773            (mod_item name: (identifier) body: _ @mod.body)
774            (function_item name: (identifier) @fn.name)"#,
775        )
776        .unwrap();
777        let theme = SyntaxTheme::new(vec![
778            ("mod.body".to_string(), Color::from_u32(0xff0000ff).into()),
779            ("fn.name".to_string(), Color::from_u32(0x00ff00ff).into()),
780        ]);
781        let lang = Arc::new(Language {
782            config: LanguageConfig {
783                name: "Test".to_string(),
784                path_suffixes: vec![".test".to_string()],
785                ..Default::default()
786            },
787            grammar: grammar.clone(),
788            highlight_query,
789            brackets_query: tree_sitter::Query::new(grammar, "").unwrap(),
790            highlight_map: Default::default(),
791        });
792        lang.set_theme(&theme);
793
794        let buffer = cx.add_model(|cx| {
795            Buffer::from_history(0, History::new(text.into()), None, Some(lang), cx)
796        });
797        buffer.condition(&cx, |buf, _| !buf.is_parsing()).await;
798
799        let font_cache = cx.font_cache();
800
801        let tab_size = 4;
802        let family_id = font_cache.load_family(&["Courier"]).unwrap();
803        let font_id = font_cache
804            .select_font(family_id, &Default::default())
805            .unwrap();
806        let font_size = 16.0;
807
808        let map = cx
809            .add_model(|cx| DisplayMap::new(buffer, tab_size, font_id, font_size, Some(40.0), cx));
810        assert_eq!(
811            cx.update(|cx| highlighted_chunks(0..5, &map, &theme, cx)),
812            [
813                ("fn \n".to_string(), None),
814                ("oute\nr".to_string(), Some("fn.name")),
815                ("() \n{}\n\n".to_string(), None),
816            ]
817        );
818        assert_eq!(
819            cx.update(|cx| highlighted_chunks(3..5, &map, &theme, cx)),
820            [("{}\n\n".to_string(), None)]
821        );
822
823        map.update(&mut cx, |map, cx| {
824            map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], cx)
825        });
826        assert_eq!(
827            cx.update(|cx| highlighted_chunks(1..4, &map, &theme, cx)),
828            [
829                ("out".to_string(), Some("fn.name")),
830                ("\n".to_string(), None),
831                ("  \nfn ".to_string(), Some("mod.body")),
832                ("i\n".to_string(), Some("fn.name"))
833            ]
834        );
835    }
836
837    #[gpui::test]
838    fn test_clip_point(cx: &mut gpui::MutableAppContext) {
839        use Bias::{Left, Right};
840
841        let text = "\n'a', 'α',\t'✋',\t'❎', '🍐'\n";
842        let display_text = "\n'a', 'α',   '✋',    '❎', '🍐'\n";
843        let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
844
845        let tab_size = 4;
846        let font_cache = cx.font_cache();
847        let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
848        let font_id = font_cache
849            .select_font(family_id, &Default::default())
850            .unwrap();
851        let font_size = 14.0;
852        let map = cx.add_model(|cx| {
853            DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, None, cx)
854        });
855        let map = map.update(cx, |map, cx| map.snapshot(cx));
856
857        assert_eq!(map.text(), display_text);
858        for (input_column, bias, output_column) in vec![
859            ("'a', '".len(), Left, "'a', '".len()),
860            ("'a', '".len() + 1, Left, "'a', '".len()),
861            ("'a', '".len() + 1, Right, "'a', 'α".len()),
862            ("'a', 'α', ".len(), Left, "'a', 'α',".len()),
863            ("'a', 'α', ".len(), Right, "'a', 'α',   ".len()),
864            ("'a', 'α',   '".len() + 1, Left, "'a', 'α',   '".len()),
865            ("'a', 'α',   '".len() + 1, Right, "'a', 'α',   '✋".len()),
866            ("'a', 'α',   '✋',".len(), Right, "'a', 'α',   '✋',".len()),
867            ("'a', 'α',   '✋', ".len(), Left, "'a', 'α',   '✋',".len()),
868            (
869                "'a', 'α',   '✋', ".len(),
870                Right,
871                "'a', 'α',   '✋',    ".len(),
872            ),
873        ] {
874            assert_eq!(
875                map.clip_point(DisplayPoint::new(1, input_column as u32), bias),
876                DisplayPoint::new(1, output_column as u32),
877                "clip_point(({}, {}))",
878                1,
879                input_column,
880            );
881        }
882    }
883
884    #[gpui::test]
885    fn test_tabs_with_multibyte_chars(cx: &mut gpui::MutableAppContext) {
886        let text = "\t\tα\nβ\t\n🏀β\t\tγ";
887        let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
888        let tab_size = 4;
889        let font_cache = cx.font_cache();
890        let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
891        let font_id = font_cache
892            .select_font(family_id, &Default::default())
893            .unwrap();
894        let font_size = 14.0;
895
896        let map = cx.add_model(|cx| {
897            DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, None, cx)
898        });
899        let map = map.update(cx, |map, cx| map.snapshot(cx));
900        assert_eq!(map.text(), "✅       α\nβ   \n🏀β      γ");
901        assert_eq!(
902            map.chunks_at(0).collect::<String>(),
903            "✅       α\nβ   \n🏀β      γ"
904        );
905        assert_eq!(map.chunks_at(1).collect::<String>(), "β   \n🏀β      γ");
906        assert_eq!(map.chunks_at(2).collect::<String>(), "🏀β      γ");
907
908        let point = Point::new(0, "\t\t".len() as u32);
909        let display_point = DisplayPoint::new(0, "".len() as u32);
910        assert_eq!(point.to_display_point(&map, Left), display_point);
911        assert_eq!(display_point.to_buffer_point(&map, Left), point,);
912
913        let point = Point::new(1, "β\t".len() as u32);
914        let display_point = DisplayPoint::new(1, "β   ".len() as u32);
915        assert_eq!(point.to_display_point(&map, Left), display_point);
916        assert_eq!(display_point.to_buffer_point(&map, Left), point,);
917
918        let point = Point::new(2, "🏀β\t\t".len() as u32);
919        let display_point = DisplayPoint::new(2, "🏀β      ".len() as u32);
920        assert_eq!(point.to_display_point(&map, Left), display_point);
921        assert_eq!(display_point.to_buffer_point(&map, Left), point,);
922
923        // Display points inside of expanded tabs
924        assert_eq!(
925            DisplayPoint::new(0, "".len() as u32).to_buffer_point(&map, Right),
926            Point::new(0, "\t\t".len() as u32),
927        );
928        assert_eq!(
929            DisplayPoint::new(0, "".len() as u32).to_buffer_point(&map, Left),
930            Point::new(0, "\t".len() as u32),
931        );
932        assert_eq!(
933            DisplayPoint::new(0, "".len() as u32).to_buffer_point(&map, Right),
934            Point::new(0, "\t".len() as u32),
935        );
936        assert_eq!(
937            DisplayPoint::new(0, "".len() as u32).to_buffer_point(&map, Left),
938            Point::new(0, "".len() as u32),
939        );
940
941        // Clipping display points inside of multi-byte characters
942        assert_eq!(
943            map.clip_point(DisplayPoint::new(0, "".len() as u32 - 1), Left),
944            DisplayPoint::new(0, 0)
945        );
946        assert_eq!(
947            map.clip_point(DisplayPoint::new(0, "".len() as u32 - 1), Bias::Right),
948            DisplayPoint::new(0, "".len() as u32)
949        );
950    }
951
952    #[gpui::test]
953    fn test_max_point(cx: &mut gpui::MutableAppContext) {
954        let buffer = cx.add_model(|cx| Buffer::new(0, "aaa\n\t\tbbb", cx));
955        let tab_size = 4;
956        let font_cache = cx.font_cache();
957        let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
958        let font_id = font_cache
959            .select_font(family_id, &Default::default())
960            .unwrap();
961        let font_size = 14.0;
962        let map = cx.add_model(|cx| {
963            DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, None, cx)
964        });
965        assert_eq!(
966            map.update(cx, |map, cx| map.snapshot(cx)).max_point(),
967            DisplayPoint::new(1, 11)
968        )
969    }
970
971    fn highlighted_chunks<'a>(
972        rows: Range<u32>,
973        map: &ModelHandle<DisplayMap>,
974        theme: &'a SyntaxTheme,
975        cx: &mut MutableAppContext,
976    ) -> Vec<(String, Option<&'a str>)> {
977        let mut snapshot = map.update(cx, |map, cx| map.snapshot(cx));
978        let mut chunks: Vec<(String, Option<&str>)> = Vec::new();
979        for (chunk, style_id) in snapshot.highlighted_chunks_for_rows(rows) {
980            let style_name = theme.highlight_name(style_id);
981            if let Some((last_chunk, last_style_name)) = chunks.last_mut() {
982                if style_name == *last_style_name {
983                    last_chunk.push_str(chunk);
984                } else {
985                    chunks.push((chunk.to_string(), style_name));
986                }
987            } else {
988                chunks.push((chunk.to_string(), style_name));
989            }
990        }
991        chunks
992    }
993}