display_map.rs

  1mod fold_map;
  2mod tab_map;
  3mod wrap_map;
  4
  5use fold_map::{FoldMap, ToFoldPoint as _};
  6use gpui::{fonts::FontId, Entity, ModelContext, ModelHandle};
  7use language::{Anchor, Buffer, Point, ToOffset, ToPoint};
  8use std::ops::Range;
  9use sum_tree::Bias;
 10use tab_map::TabMap;
 11use wrap_map::WrapMap;
 12pub use wrap_map::{BufferRows, HighlightedChunks};
 13
 14pub trait ToDisplayPoint {
 15    fn to_display_point(&self, map: &DisplayMapSnapshot, bias: Bias) -> DisplayPoint;
 16}
 17
 18pub struct DisplayMap {
 19    buffer: ModelHandle<Buffer>,
 20    fold_map: FoldMap,
 21    tab_map: TabMap,
 22    wrap_map: ModelHandle<WrapMap>,
 23}
 24
 25impl Entity for DisplayMap {
 26    type Event = ();
 27}
 28
 29impl DisplayMap {
 30    pub fn new(
 31        buffer: ModelHandle<Buffer>,
 32        tab_size: usize,
 33        font_id: FontId,
 34        font_size: f32,
 35        wrap_width: Option<f32>,
 36        cx: &mut ModelContext<Self>,
 37    ) -> Self {
 38        let (fold_map, snapshot) = FoldMap::new(buffer.clone(), cx);
 39        let (tab_map, snapshot) = TabMap::new(snapshot, tab_size);
 40        let wrap_map =
 41            cx.add_model(|cx| WrapMap::new(snapshot, font_id, font_size, wrap_width, cx));
 42        cx.observe(&wrap_map, |_, _, cx| cx.notify()).detach();
 43        DisplayMap {
 44            buffer,
 45            fold_map,
 46            tab_map,
 47            wrap_map,
 48        }
 49    }
 50
 51    pub fn snapshot(&self, cx: &mut ModelContext<Self>) -> DisplayMapSnapshot {
 52        let (folds_snapshot, edits) = self.fold_map.read(cx);
 53        let (tabs_snapshot, edits) = self.tab_map.sync(folds_snapshot.clone(), edits);
 54        let wraps_snapshot = self
 55            .wrap_map
 56            .update(cx, |map, cx| map.sync(tabs_snapshot.clone(), edits, cx));
 57        DisplayMapSnapshot {
 58            buffer_snapshot: self.buffer.read(cx).snapshot(),
 59            folds_snapshot,
 60            tabs_snapshot,
 61            wraps_snapshot,
 62        }
 63    }
 64
 65    pub fn fold<T: ToOffset>(
 66        &mut self,
 67        ranges: impl IntoIterator<Item = Range<T>>,
 68        cx: &mut ModelContext<Self>,
 69    ) {
 70        let (mut fold_map, snapshot, edits) = self.fold_map.write(cx);
 71        let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
 72        self.wrap_map
 73            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 74        let (snapshot, edits) = fold_map.fold(ranges, cx);
 75        let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
 76        self.wrap_map
 77            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 78    }
 79
 80    pub fn unfold<T: ToOffset>(
 81        &mut self,
 82        ranges: impl IntoIterator<Item = Range<T>>,
 83        cx: &mut ModelContext<Self>,
 84    ) {
 85        let (mut fold_map, snapshot, edits) = self.fold_map.write(cx);
 86        let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
 87        self.wrap_map
 88            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 89        let (snapshot, edits) = fold_map.unfold(ranges, cx);
 90        let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
 91        self.wrap_map
 92            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 93    }
 94
 95    pub fn set_font(&self, font_id: FontId, font_size: f32, cx: &mut ModelContext<Self>) {
 96        self.wrap_map
 97            .update(cx, |map, cx| map.set_font(font_id, font_size, cx));
 98    }
 99
100    pub fn set_wrap_width(&self, width: Option<f32>, cx: &mut ModelContext<Self>) -> bool {
101        self.wrap_map
102            .update(cx, |map, cx| map.set_wrap_width(width, cx))
103    }
104
105    #[cfg(test)]
106    pub fn is_rewrapping(&self, cx: &gpui::AppContext) -> bool {
107        self.wrap_map.read(cx).is_rewrapping()
108    }
109}
110
111pub struct DisplayMapSnapshot {
112    buffer_snapshot: language::Snapshot,
113    folds_snapshot: fold_map::Snapshot,
114    tabs_snapshot: tab_map::Snapshot,
115    wraps_snapshot: wrap_map::Snapshot,
116}
117
118impl DisplayMapSnapshot {
119    #[cfg(test)]
120    pub fn fold_count(&self) -> usize {
121        self.folds_snapshot.fold_count()
122    }
123
124    pub fn is_empty(&self) -> bool {
125        self.buffer_snapshot.len() == 0
126    }
127
128    pub fn buffer_rows(&self, start_row: u32) -> BufferRows {
129        self.wraps_snapshot.buffer_rows(start_row)
130    }
131
132    pub fn buffer_row_count(&self) -> u32 {
133        self.buffer_snapshot.max_point().row + 1
134    }
135
136    pub fn prev_row_boundary(&self, mut display_point: DisplayPoint) -> (DisplayPoint, Point) {
137        loop {
138            *display_point.column_mut() = 0;
139            let mut point = display_point.to_buffer_point(self, Bias::Left);
140            point.column = 0;
141            let next_display_point = point.to_display_point(self, Bias::Left);
142            if next_display_point == display_point {
143                return (display_point, point);
144            }
145            display_point = next_display_point;
146        }
147    }
148
149    pub fn next_row_boundary(&self, mut display_point: DisplayPoint) -> (DisplayPoint, Point) {
150        loop {
151            *display_point.column_mut() = self.line_len(display_point.row());
152            let mut point = display_point.to_buffer_point(self, Bias::Right);
153            point.column = self.buffer_snapshot.line_len(point.row);
154            let next_display_point = point.to_display_point(self, Bias::Right);
155            if next_display_point == display_point {
156                return (display_point, point);
157            }
158            display_point = next_display_point;
159        }
160    }
161
162    pub fn max_point(&self) -> DisplayPoint {
163        DisplayPoint(self.wraps_snapshot.max_point())
164    }
165
166    pub fn chunks_at(&self, display_row: u32) -> wrap_map::Chunks {
167        self.wraps_snapshot.chunks_at(display_row)
168    }
169
170    pub fn highlighted_chunks_for_rows(
171        &mut self,
172        display_rows: Range<u32>,
173    ) -> wrap_map::HighlightedChunks {
174        self.wraps_snapshot
175            .highlighted_chunks_for_rows(display_rows)
176    }
177
178    pub fn chars_at<'a>(&'a self, point: DisplayPoint) -> impl Iterator<Item = char> + 'a {
179        let mut column = 0;
180        let mut chars = self.chunks_at(point.row()).flat_map(str::chars);
181        while column < point.column() {
182            if let Some(c) = chars.next() {
183                column += c.len_utf8() as u32;
184            } else {
185                break;
186            }
187        }
188        chars
189    }
190
191    pub fn column_to_chars(&self, display_row: u32, target: u32) -> u32 {
192        let mut count = 0;
193        let mut column = 0;
194        for c in self.chars_at(DisplayPoint::new(display_row, 0)) {
195            if column >= target {
196                break;
197            }
198            count += 1;
199            column += c.len_utf8() as u32;
200        }
201        count
202    }
203
204    pub fn column_from_chars(&self, display_row: u32, char_count: u32) -> u32 {
205        let mut count = 0;
206        let mut column = 0;
207        for c in self.chars_at(DisplayPoint::new(display_row, 0)) {
208            if c == '\n' || count >= char_count {
209                break;
210            }
211            count += 1;
212            column += c.len_utf8() as u32;
213        }
214        column
215    }
216
217    pub fn clip_point(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint {
218        DisplayPoint(self.wraps_snapshot.clip_point(point.0, bias))
219    }
220
221    pub fn folds_in_range<'a, T>(
222        &'a self,
223        range: Range<T>,
224    ) -> impl Iterator<Item = &'a Range<Anchor>>
225    where
226        T: ToOffset,
227    {
228        self.folds_snapshot.folds_in_range(range)
229    }
230
231    pub fn intersects_fold<T: ToOffset>(&self, offset: T) -> bool {
232        self.folds_snapshot.intersects_fold(offset)
233    }
234
235    pub fn is_line_folded(&self, display_row: u32) -> bool {
236        let wrap_point = DisplayPoint::new(display_row, 0).0;
237        let row = self.wraps_snapshot.to_tab_point(wrap_point).row();
238        self.folds_snapshot.is_line_folded(row)
239    }
240
241    pub fn soft_wrap_indent(&self, display_row: u32) -> Option<u32> {
242        self.wraps_snapshot.soft_wrap_indent(display_row)
243    }
244
245    pub fn text(&self) -> String {
246        self.chunks_at(0).collect()
247    }
248
249    pub fn line(&self, display_row: u32) -> String {
250        let mut result = String::new();
251        for chunk in self.chunks_at(display_row) {
252            if let Some(ix) = chunk.find('\n') {
253                result.push_str(&chunk[0..ix]);
254                break;
255            } else {
256                result.push_str(chunk);
257            }
258        }
259        result
260    }
261
262    pub fn line_indent(&self, display_row: u32) -> (u32, bool) {
263        let mut indent = 0;
264        let mut is_blank = true;
265        for c in self.chars_at(DisplayPoint::new(display_row, 0)) {
266            if c == ' ' {
267                indent += 1;
268            } else {
269                is_blank = c == '\n';
270                break;
271            }
272        }
273        (indent, is_blank)
274    }
275
276    pub fn line_len(&self, row: u32) -> u32 {
277        self.wraps_snapshot.line_len(row)
278    }
279
280    pub fn longest_row(&self) -> u32 {
281        self.wraps_snapshot.longest_row()
282    }
283
284    pub fn anchor_before(&self, point: DisplayPoint, bias: Bias) -> Anchor {
285        self.buffer_snapshot
286            .anchor_before(point.to_buffer_point(self, bias))
287    }
288
289    pub fn anchor_after(&self, point: DisplayPoint, bias: Bias) -> Anchor {
290        self.buffer_snapshot
291            .anchor_after(point.to_buffer_point(self, bias))
292    }
293}
294
295#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
296pub struct DisplayPoint(wrap_map::WrapPoint);
297
298impl DisplayPoint {
299    pub fn new(row: u32, column: u32) -> Self {
300        Self(wrap_map::WrapPoint::new(row, column))
301    }
302
303    pub fn zero() -> Self {
304        Self::new(0, 0)
305    }
306
307    #[cfg(test)]
308    pub fn is_zero(&self) -> bool {
309        self.0.is_zero()
310    }
311
312    pub fn row(self) -> u32 {
313        self.0.row()
314    }
315
316    pub fn column(self) -> u32 {
317        self.0.column()
318    }
319
320    pub fn row_mut(&mut self) -> &mut u32 {
321        self.0.row_mut()
322    }
323
324    pub fn column_mut(&mut self) -> &mut u32 {
325        self.0.column_mut()
326    }
327
328    pub fn to_buffer_point(self, map: &DisplayMapSnapshot, bias: Bias) -> Point {
329        let unwrapped_point = map.wraps_snapshot.to_tab_point(self.0);
330        let unexpanded_point = map.tabs_snapshot.to_fold_point(unwrapped_point, bias).0;
331        unexpanded_point.to_buffer_point(&map.folds_snapshot)
332    }
333
334    pub fn to_buffer_offset(self, map: &DisplayMapSnapshot, bias: Bias) -> usize {
335        let unwrapped_point = map.wraps_snapshot.to_tab_point(self.0);
336        let unexpanded_point = map.tabs_snapshot.to_fold_point(unwrapped_point, bias).0;
337        unexpanded_point.to_buffer_offset(&map.folds_snapshot)
338    }
339}
340
341impl ToDisplayPoint for Point {
342    fn to_display_point(&self, map: &DisplayMapSnapshot, bias: Bias) -> DisplayPoint {
343        let fold_point = self.to_fold_point(&map.folds_snapshot, bias);
344        let tab_point = map.tabs_snapshot.to_tab_point(fold_point);
345        let wrap_point = map.wraps_snapshot.to_wrap_point(tab_point);
346        DisplayPoint(wrap_point)
347    }
348}
349
350impl ToDisplayPoint for Anchor {
351    fn to_display_point(&self, map: &DisplayMapSnapshot, bias: Bias) -> DisplayPoint {
352        self.to_point(&map.buffer_snapshot)
353            .to_display_point(map, bias)
354    }
355}
356
357#[cfg(test)]
358mod tests {
359    use super::*;
360    use crate::{movement, test::*};
361    use gpui::{color::Color, MutableAppContext};
362    use language::{History, Language, LanguageConfig, RandomCharIter, SelectionGoal};
363    use rand::{prelude::StdRng, Rng};
364    use std::{env, sync::Arc};
365    use theme::SyntaxTheme;
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, _| buffer.randomly_edit(&mut rng, 5));
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 text = r#"
674            fn outer() {}
675
676            mod module {
677                fn inner() {}
678            }"#
679        .unindent();
680
681        let theme = SyntaxTheme::new(vec![
682            ("mod.body".to_string(), Color::from_u32(0xff0000ff).into()),
683            ("fn.name".to_string(), Color::from_u32(0x00ff00ff).into()),
684        ]);
685        let lang = Arc::new(
686            Language::new(
687                LanguageConfig {
688                    name: "Test".to_string(),
689                    path_suffixes: vec![".test".to_string()],
690                    ..Default::default()
691                },
692                tree_sitter_rust::language(),
693            )
694            .with_highlights_query(
695                r#"
696                (mod_item name: (identifier) body: _ @mod.body)
697                (function_item name: (identifier) @fn.name)
698                "#,
699            )
700            .unwrap(),
701        );
702        lang.set_theme(&theme);
703
704        let buffer = cx.add_model(|cx| {
705            Buffer::from_history(0, History::new(text.into()), None, Some(lang), cx)
706        });
707        buffer.condition(&cx, |buf, _| !buf.is_parsing()).await;
708
709        let tab_size = 2;
710        let font_cache = cx.font_cache();
711        let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
712        let font_id = font_cache
713            .select_font(family_id, &Default::default())
714            .unwrap();
715        let font_size = 14.0;
716
717        let map =
718            cx.add_model(|cx| DisplayMap::new(buffer, tab_size, font_id, font_size, None, cx));
719        assert_eq!(
720            cx.update(|cx| highlighted_chunks(0..5, &map, &theme, cx)),
721            vec![
722                ("fn ".to_string(), None),
723                ("outer".to_string(), Some("fn.name")),
724                ("() {}\n\nmod module ".to_string(), None),
725                ("{\n    fn ".to_string(), Some("mod.body")),
726                ("inner".to_string(), Some("fn.name")),
727                ("() {}\n}".to_string(), Some("mod.body")),
728            ]
729        );
730        assert_eq!(
731            cx.update(|cx| highlighted_chunks(3..5, &map, &theme, cx)),
732            vec![
733                ("    fn ".to_string(), Some("mod.body")),
734                ("inner".to_string(), Some("fn.name")),
735                ("() {}\n}".to_string(), Some("mod.body")),
736            ]
737        );
738
739        map.update(&mut cx, |map, cx| {
740            map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], cx)
741        });
742        assert_eq!(
743            cx.update(|cx| highlighted_chunks(0..2, &map, &theme, cx)),
744            vec![
745                ("fn ".to_string(), None),
746                ("out".to_string(), Some("fn.name")),
747                ("".to_string(), None),
748                ("  fn ".to_string(), Some("mod.body")),
749                ("inner".to_string(), Some("fn.name")),
750                ("() {}\n}".to_string(), Some("mod.body")),
751            ]
752        );
753    }
754
755    #[gpui::test]
756    async fn test_highlighted_chunks_with_soft_wrapping(mut cx: gpui::TestAppContext) {
757        use unindent::Unindent as _;
758
759        cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
760
761        let text = r#"
762            fn outer() {}
763
764            mod module {
765                fn inner() {}
766            }"#
767        .unindent();
768
769        let theme = SyntaxTheme::new(vec![
770            ("mod.body".to_string(), Color::from_u32(0xff0000ff).into()),
771            ("fn.name".to_string(), Color::from_u32(0x00ff00ff).into()),
772        ]);
773        let lang = Arc::new(
774            Language::new(
775                LanguageConfig {
776                    name: "Test".to_string(),
777                    path_suffixes: vec![".test".to_string()],
778                    ..Default::default()
779                },
780                tree_sitter_rust::language(),
781            )
782            .with_highlights_query(
783                r#"
784                (mod_item name: (identifier) body: _ @mod.body)
785                (function_item name: (identifier) @fn.name)
786                "#,
787            )
788            .unwrap(),
789        );
790        lang.set_theme(&theme);
791
792        let buffer = cx.add_model(|cx| {
793            Buffer::from_history(0, History::new(text.into()), None, Some(lang), cx)
794        });
795        buffer.condition(&cx, |buf, _| !buf.is_parsing()).await;
796
797        let font_cache = cx.font_cache();
798
799        let tab_size = 4;
800        let family_id = font_cache.load_family(&["Courier"]).unwrap();
801        let font_id = font_cache
802            .select_font(family_id, &Default::default())
803            .unwrap();
804        let font_size = 16.0;
805
806        let map = cx
807            .add_model(|cx| DisplayMap::new(buffer, tab_size, font_id, font_size, Some(40.0), cx));
808        assert_eq!(
809            cx.update(|cx| highlighted_chunks(0..5, &map, &theme, cx)),
810            [
811                ("fn \n".to_string(), None),
812                ("oute\nr".to_string(), Some("fn.name")),
813                ("() \n{}\n\n".to_string(), None),
814            ]
815        );
816        assert_eq!(
817            cx.update(|cx| highlighted_chunks(3..5, &map, &theme, cx)),
818            [("{}\n\n".to_string(), None)]
819        );
820
821        map.update(&mut cx, |map, cx| {
822            map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], cx)
823        });
824        assert_eq!(
825            cx.update(|cx| highlighted_chunks(1..4, &map, &theme, cx)),
826            [
827                ("out".to_string(), Some("fn.name")),
828                ("\n".to_string(), None),
829                ("  \nfn ".to_string(), Some("mod.body")),
830                ("i\n".to_string(), Some("fn.name"))
831            ]
832        );
833    }
834
835    #[gpui::test]
836    fn test_clip_point(cx: &mut gpui::MutableAppContext) {
837        use Bias::{Left, Right};
838
839        let text = "\n'a', 'α',\t'✋',\t'❎', '🍐'\n";
840        let display_text = "\n'a', 'α',   '✋',    '❎', '🍐'\n";
841        let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
842
843        let tab_size = 4;
844        let font_cache = cx.font_cache();
845        let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
846        let font_id = font_cache
847            .select_font(family_id, &Default::default())
848            .unwrap();
849        let font_size = 14.0;
850        let map = cx.add_model(|cx| {
851            DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, None, cx)
852        });
853        let map = map.update(cx, |map, cx| map.snapshot(cx));
854
855        assert_eq!(map.text(), display_text);
856        for (input_column, bias, output_column) in vec![
857            ("'a', '".len(), Left, "'a', '".len()),
858            ("'a', '".len() + 1, Left, "'a', '".len()),
859            ("'a', '".len() + 1, Right, "'a', 'α".len()),
860            ("'a', 'α', ".len(), Left, "'a', 'α',".len()),
861            ("'a', 'α', ".len(), Right, "'a', 'α',   ".len()),
862            ("'a', 'α',   '".len() + 1, Left, "'a', 'α',   '".len()),
863            ("'a', 'α',   '".len() + 1, Right, "'a', 'α',   '✋".len()),
864            ("'a', 'α',   '✋',".len(), Right, "'a', 'α',   '✋',".len()),
865            ("'a', 'α',   '✋', ".len(), Left, "'a', 'α',   '✋',".len()),
866            (
867                "'a', 'α',   '✋', ".len(),
868                Right,
869                "'a', 'α',   '✋',    ".len(),
870            ),
871        ] {
872            assert_eq!(
873                map.clip_point(DisplayPoint::new(1, input_column as u32), bias),
874                DisplayPoint::new(1, output_column as u32),
875                "clip_point(({}, {}))",
876                1,
877                input_column,
878            );
879        }
880    }
881
882    #[gpui::test]
883    fn test_tabs_with_multibyte_chars(cx: &mut gpui::MutableAppContext) {
884        let text = "\t\tα\nβ\t\n🏀β\t\tγ";
885        let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
886        let tab_size = 4;
887        let font_cache = cx.font_cache();
888        let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
889        let font_id = font_cache
890            .select_font(family_id, &Default::default())
891            .unwrap();
892        let font_size = 14.0;
893
894        let map = cx.add_model(|cx| {
895            DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, None, cx)
896        });
897        let map = map.update(cx, |map, cx| map.snapshot(cx));
898        assert_eq!(map.text(), "✅       α\nβ   \n🏀β      γ");
899        assert_eq!(
900            map.chunks_at(0).collect::<String>(),
901            "✅       α\nβ   \n🏀β      γ"
902        );
903        assert_eq!(map.chunks_at(1).collect::<String>(), "β   \n🏀β      γ");
904        assert_eq!(map.chunks_at(2).collect::<String>(), "🏀β      γ");
905
906        let point = Point::new(0, "\t\t".len() as u32);
907        let display_point = DisplayPoint::new(0, "".len() as u32);
908        assert_eq!(point.to_display_point(&map, Left), display_point);
909        assert_eq!(display_point.to_buffer_point(&map, Left), point,);
910
911        let point = Point::new(1, "β\t".len() as u32);
912        let display_point = DisplayPoint::new(1, "β   ".len() as u32);
913        assert_eq!(point.to_display_point(&map, Left), display_point);
914        assert_eq!(display_point.to_buffer_point(&map, Left), point,);
915
916        let point = Point::new(2, "🏀β\t\t".len() as u32);
917        let display_point = DisplayPoint::new(2, "🏀β      ".len() as u32);
918        assert_eq!(point.to_display_point(&map, Left), display_point);
919        assert_eq!(display_point.to_buffer_point(&map, Left), point,);
920
921        // Display points inside of expanded tabs
922        assert_eq!(
923            DisplayPoint::new(0, "".len() as u32).to_buffer_point(&map, Right),
924            Point::new(0, "\t\t".len() as u32),
925        );
926        assert_eq!(
927            DisplayPoint::new(0, "".len() as u32).to_buffer_point(&map, Left),
928            Point::new(0, "\t".len() as u32),
929        );
930        assert_eq!(
931            DisplayPoint::new(0, "".len() as u32).to_buffer_point(&map, Right),
932            Point::new(0, "\t".len() as u32),
933        );
934        assert_eq!(
935            DisplayPoint::new(0, "".len() as u32).to_buffer_point(&map, Left),
936            Point::new(0, "".len() as u32),
937        );
938
939        // Clipping display points inside of multi-byte characters
940        assert_eq!(
941            map.clip_point(DisplayPoint::new(0, "".len() as u32 - 1), Left),
942            DisplayPoint::new(0, 0)
943        );
944        assert_eq!(
945            map.clip_point(DisplayPoint::new(0, "".len() as u32 - 1), Bias::Right),
946            DisplayPoint::new(0, "".len() as u32)
947        );
948    }
949
950    #[gpui::test]
951    fn test_max_point(cx: &mut gpui::MutableAppContext) {
952        let buffer = cx.add_model(|cx| Buffer::new(0, "aaa\n\t\tbbb", cx));
953        let tab_size = 4;
954        let font_cache = cx.font_cache();
955        let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
956        let font_id = font_cache
957            .select_font(family_id, &Default::default())
958            .unwrap();
959        let font_size = 14.0;
960        let map = cx.add_model(|cx| {
961            DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, None, cx)
962        });
963        assert_eq!(
964            map.update(cx, |map, cx| map.snapshot(cx)).max_point(),
965            DisplayPoint::new(1, 11)
966        )
967    }
968
969    fn highlighted_chunks<'a>(
970        rows: Range<u32>,
971        map: &ModelHandle<DisplayMap>,
972        theme: &'a SyntaxTheme,
973        cx: &mut MutableAppContext,
974    ) -> Vec<(String, Option<&'a str>)> {
975        let mut snapshot = map.update(cx, |map, cx| map.snapshot(cx));
976        let mut chunks: Vec<(String, Option<&str>)> = Vec::new();
977        for (chunk, style_id) in snapshot.highlighted_chunks_for_rows(rows) {
978            let style_name = style_id.name(theme);
979            if let Some((last_chunk, last_style_name)) = chunks.last_mut() {
980                if style_name == *last_style_name {
981                    last_chunk.push_str(chunk);
982                } else {
983                    chunks.push((chunk.to_string(), style_name));
984                }
985            } else {
986                chunks.push((chunk.to_string(), style_name));
987            }
988        }
989        chunks
990    }
991}