display_map.rs

  1mod fold_map;
  2mod tab_map;
  3mod wrap_map;
  4
  5use buffer::{Anchor, Buffer, Point, ToOffset, ToPoint};
  6use fold_map::{FoldMap, ToFoldPoint as _};
  7use gpui::{fonts::FontId, Entity, ModelContext, ModelHandle};
  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: buffer::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 buffer::{History, Language, LanguageConfig, RandomCharIter, SelectionGoal, SyntaxTheme};
362    use gpui::{color::Color, MutableAppContext};
363    use rand::{prelude::StdRng, Rng};
364    use std::{env, sync::Arc};
365    use Bias::*;
366
367    #[gpui::test(iterations = 100)]
368    async fn test_random(mut cx: gpui::TestAppContext, mut rng: StdRng) {
369        cx.foreground().set_block_on_ticks(0..=50);
370        cx.foreground().forbid_parking();
371        let operations = env::var("OPERATIONS")
372            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
373            .unwrap_or(10);
374
375        let font_cache = cx.font_cache().clone();
376        let tab_size = rng.gen_range(1..=4);
377        let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
378        let font_id = font_cache
379            .select_font(family_id, &Default::default())
380            .unwrap();
381        let font_size = 14.0;
382        let max_wrap_width = 300.0;
383        let mut wrap_width = if rng.gen_bool(0.1) {
384            None
385        } else {
386            Some(rng.gen_range(0.0..=max_wrap_width))
387        };
388
389        log::info!("tab size: {}", tab_size);
390        log::info!("wrap width: {:?}", wrap_width);
391
392        let buffer = cx.add_model(|cx| {
393            let len = rng.gen_range(0..10);
394            let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
395            Buffer::new(0, text, cx)
396        });
397
398        let map = cx.add_model(|cx| {
399            DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, wrap_width, cx)
400        });
401        let (_observer, notifications) = Observer::new(&map, &mut cx);
402        let mut fold_count = 0;
403
404        for _i in 0..operations {
405            match rng.gen_range(0..100) {
406                0..=19 => {
407                    wrap_width = if rng.gen_bool(0.2) {
408                        None
409                    } else {
410                        Some(rng.gen_range(0.0..=max_wrap_width))
411                    };
412                    log::info!("setting wrap width to {:?}", wrap_width);
413                    map.update(&mut cx, |map, cx| map.set_wrap_width(wrap_width, cx));
414                }
415                20..=80 => {
416                    let mut ranges = Vec::new();
417                    for _ in 0..rng.gen_range(1..=3) {
418                        buffer.read_with(&cx, |buffer, _| {
419                            let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right);
420                            let start = buffer.clip_offset(rng.gen_range(0..=end), Left);
421                            ranges.push(start..end);
422                        });
423                    }
424
425                    if rng.gen() && fold_count > 0 {
426                        log::info!("unfolding ranges: {:?}", ranges);
427                        map.update(&mut cx, |map, cx| {
428                            map.unfold(ranges, cx);
429                        });
430                    } else {
431                        log::info!("folding ranges: {:?}", ranges);
432                        map.update(&mut cx, |map, cx| {
433                            map.fold(ranges, cx);
434                        });
435                    }
436                }
437                _ => {
438                    buffer.update(&mut cx, |buffer, cx| buffer.randomly_mutate(&mut rng, cx));
439                }
440            }
441
442            if map.read_with(&cx, |map, cx| map.is_rewrapping(cx)) {
443                notifications.recv().await.unwrap();
444            }
445
446            let snapshot = map.update(&mut cx, |map, cx| map.snapshot(cx));
447            fold_count = snapshot.fold_count();
448            log::info!("buffer text: {:?}", buffer.read_with(&cx, |b, _| b.text()));
449            log::info!("display text: {:?}", snapshot.text());
450
451            // Line boundaries
452            for _ in 0..5 {
453                let row = rng.gen_range(0..=snapshot.max_point().row());
454                let column = rng.gen_range(0..=snapshot.line_len(row));
455                let point = snapshot.clip_point(DisplayPoint::new(row, column), Left);
456
457                let (prev_display_bound, prev_buffer_bound) = snapshot.prev_row_boundary(point);
458                let (next_display_bound, next_buffer_bound) = snapshot.next_row_boundary(point);
459
460                assert!(prev_display_bound <= point);
461                assert!(next_display_bound >= point);
462                assert_eq!(prev_buffer_bound.column, 0);
463                assert_eq!(prev_display_bound.column(), 0);
464                if next_display_bound < snapshot.max_point() {
465                    assert_eq!(
466                        buffer
467                            .read_with(&cx, |buffer, _| buffer.chars_at(next_buffer_bound).next()),
468                        Some('\n')
469                    )
470                }
471
472                assert_eq!(
473                    prev_display_bound,
474                    prev_buffer_bound.to_display_point(&snapshot, Left),
475                    "row boundary before {:?}. reported buffer row boundary: {:?}",
476                    point,
477                    prev_buffer_bound
478                );
479                assert_eq!(
480                    next_display_bound,
481                    next_buffer_bound.to_display_point(&snapshot, Right),
482                    "display row boundary after {:?}. reported buffer row boundary: {:?}",
483                    point,
484                    next_buffer_bound
485                );
486                assert_eq!(
487                    prev_buffer_bound,
488                    prev_display_bound.to_buffer_point(&snapshot, Left),
489                    "row boundary before {:?}. reported display row boundary: {:?}",
490                    point,
491                    prev_display_bound
492                );
493                assert_eq!(
494                    next_buffer_bound,
495                    next_display_bound.to_buffer_point(&snapshot, Right),
496                    "row boundary after {:?}. reported display row boundary: {:?}",
497                    point,
498                    next_display_bound
499                );
500            }
501
502            // Movement
503            for _ in 0..5 {
504                let row = rng.gen_range(0..=snapshot.max_point().row());
505                let column = rng.gen_range(0..=snapshot.line_len(row));
506                let point = snapshot.clip_point(DisplayPoint::new(row, column), Left);
507
508                log::info!("Moving from point {:?}", point);
509
510                let moved_right = movement::right(&snapshot, point).unwrap();
511                log::info!("Right {:?}", moved_right);
512                if point < snapshot.max_point() {
513                    assert!(moved_right > point);
514                    if point.column() == snapshot.line_len(point.row())
515                        || snapshot.soft_wrap_indent(point.row()).is_some()
516                            && point.column() == snapshot.line_len(point.row()) - 1
517                    {
518                        assert!(moved_right.row() > point.row());
519                    }
520                } else {
521                    assert_eq!(moved_right, point);
522                }
523
524                let moved_left = movement::left(&snapshot, point).unwrap();
525                log::info!("Left {:?}", moved_left);
526                if !point.is_zero() {
527                    assert!(moved_left < point);
528                    if point.column() == 0 {
529                        assert!(moved_left.row() < point.row());
530                    }
531                } else {
532                    assert!(moved_left.is_zero());
533                }
534            }
535        }
536    }
537
538    #[gpui::test]
539    fn test_soft_wraps(cx: &mut MutableAppContext) {
540        cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
541        cx.foreground().forbid_parking();
542
543        let font_cache = cx.font_cache();
544
545        let tab_size = 4;
546        let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
547        let font_id = font_cache
548            .select_font(family_id, &Default::default())
549            .unwrap();
550        let font_size = 12.0;
551        let wrap_width = Some(64.);
552
553        let text = "one two three four five\nsix seven eight";
554        let buffer = cx.add_model(|cx| Buffer::new(0, text.to_string(), cx));
555        let map = cx.add_model(|cx| {
556            DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, wrap_width, cx)
557        });
558
559        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
560        assert_eq!(
561            snapshot.chunks_at(0).collect::<String>(),
562            "one two \nthree four \nfive\nsix seven \neight"
563        );
564        assert_eq!(
565            snapshot.clip_point(DisplayPoint::new(0, 8), Bias::Left),
566            DisplayPoint::new(0, 7)
567        );
568        assert_eq!(
569            snapshot.clip_point(DisplayPoint::new(0, 8), Bias::Right),
570            DisplayPoint::new(1, 0)
571        );
572        assert_eq!(
573            movement::right(&snapshot, DisplayPoint::new(0, 7)).unwrap(),
574            DisplayPoint::new(1, 0)
575        );
576        assert_eq!(
577            movement::left(&snapshot, DisplayPoint::new(1, 0)).unwrap(),
578            DisplayPoint::new(0, 7)
579        );
580        assert_eq!(
581            movement::up(&snapshot, DisplayPoint::new(1, 10), SelectionGoal::None).unwrap(),
582            (DisplayPoint::new(0, 7), SelectionGoal::Column(10))
583        );
584        assert_eq!(
585            movement::down(
586                &snapshot,
587                DisplayPoint::new(0, 7),
588                SelectionGoal::Column(10)
589            )
590            .unwrap(),
591            (DisplayPoint::new(1, 10), SelectionGoal::Column(10))
592        );
593        assert_eq!(
594            movement::down(
595                &snapshot,
596                DisplayPoint::new(1, 10),
597                SelectionGoal::Column(10)
598            )
599            .unwrap(),
600            (DisplayPoint::new(2, 4), SelectionGoal::Column(10))
601        );
602
603        buffer.update(cx, |buffer, cx| {
604            let ix = buffer.text().find("seven").unwrap();
605            buffer.edit(vec![ix..ix], "and ", cx);
606        });
607
608        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
609        assert_eq!(
610            snapshot.chunks_at(1).collect::<String>(),
611            "three four \nfive\nsix and \nseven eight"
612        );
613
614        // Re-wrap on font size changes
615        map.update(cx, |map, cx| map.set_font(font_id, font_size + 3., cx));
616
617        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
618        assert_eq!(
619            snapshot.chunks_at(1).collect::<String>(),
620            "three \nfour five\nsix and \nseven \neight"
621        )
622    }
623
624    #[gpui::test]
625    fn test_chunks_at(cx: &mut gpui::MutableAppContext) {
626        let text = sample_text(6, 6);
627        let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
628        let tab_size = 4;
629        let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap();
630        let font_id = cx
631            .font_cache()
632            .select_font(family_id, &Default::default())
633            .unwrap();
634        let font_size = 14.0;
635        let map = cx.add_model(|cx| {
636            DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, None, cx)
637        });
638        buffer.update(cx, |buffer, cx| {
639            buffer.edit(
640                vec![
641                    Point::new(1, 0)..Point::new(1, 0),
642                    Point::new(1, 1)..Point::new(1, 1),
643                    Point::new(2, 1)..Point::new(2, 1),
644                ],
645                "\t",
646                cx,
647            )
648        });
649
650        assert_eq!(
651            map.update(cx, |map, cx| map.snapshot(cx))
652                .chunks_at(1)
653                .collect::<String>()
654                .lines()
655                .next(),
656            Some("    b   bbbbb")
657        );
658        assert_eq!(
659            map.update(cx, |map, cx| map.snapshot(cx))
660                .chunks_at(2)
661                .collect::<String>()
662                .lines()
663                .next(),
664            Some("c   ccccc")
665        );
666    }
667
668    #[gpui::test]
669    async fn test_highlighted_chunks_at(mut cx: gpui::TestAppContext) {
670        use unindent::Unindent as _;
671
672        let grammar = tree_sitter_rust::language();
673        let text = r#"
674            fn outer() {}
675
676            mod module {
677                fn inner() {}
678            }"#
679        .unindent();
680        let highlight_query = tree_sitter::Query::new(
681            grammar,
682            r#"
683            (mod_item name: (identifier) body: _ @mod.body)
684            (function_item name: (identifier) @fn.name)"#,
685        )
686        .unwrap();
687        let theme = SyntaxTheme::new(vec![
688            ("mod.body".to_string(), Color::from_u32(0xff0000ff).into()),
689            ("fn.name".to_string(), Color::from_u32(0x00ff00ff).into()),
690        ]);
691        let lang = Arc::new(Language {
692            config: LanguageConfig {
693                name: "Test".to_string(),
694                path_suffixes: vec![".test".to_string()],
695                ..Default::default()
696            },
697            grammar: grammar.clone(),
698            highlight_query,
699            brackets_query: tree_sitter::Query::new(grammar, "").unwrap(),
700            highlight_map: Default::default(),
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 grammar = tree_sitter_rust::language();
762        let text = r#"
763            fn outer() {}
764
765            mod module {
766                fn inner() {}
767            }"#
768        .unindent();
769        let highlight_query = tree_sitter::Query::new(
770            grammar,
771            r#"
772            (mod_item name: (identifier) body: _ @mod.body)
773            (function_item name: (identifier) @fn.name)"#,
774        )
775        .unwrap();
776        let theme = SyntaxTheme::new(vec![
777            ("mod.body".to_string(), Color::from_u32(0xff0000ff).into()),
778            ("fn.name".to_string(), Color::from_u32(0x00ff00ff).into()),
779        ]);
780        let lang = Arc::new(Language {
781            config: LanguageConfig {
782                name: "Test".to_string(),
783                path_suffixes: vec![".test".to_string()],
784                ..Default::default()
785            },
786            grammar: grammar.clone(),
787            highlight_query,
788            brackets_query: tree_sitter::Query::new(grammar, "").unwrap(),
789            highlight_map: Default::default(),
790        });
791        lang.set_theme(&theme);
792
793        let buffer = cx.add_model(|cx| {
794            Buffer::from_history(0, History::new(text.into()), None, Some(lang), cx)
795        });
796        buffer.condition(&cx, |buf, _| !buf.is_parsing()).await;
797
798        let font_cache = cx.font_cache();
799
800        let tab_size = 4;
801        let family_id = font_cache.load_family(&["Courier"]).unwrap();
802        let font_id = font_cache
803            .select_font(family_id, &Default::default())
804            .unwrap();
805        let font_size = 16.0;
806
807        let map = cx
808            .add_model(|cx| DisplayMap::new(buffer, tab_size, font_id, font_size, Some(40.0), cx));
809        assert_eq!(
810            cx.update(|cx| highlighted_chunks(0..5, &map, &theme, cx)),
811            [
812                ("fn \n".to_string(), None),
813                ("oute\nr".to_string(), Some("fn.name")),
814                ("() \n{}\n\n".to_string(), None),
815            ]
816        );
817        assert_eq!(
818            cx.update(|cx| highlighted_chunks(3..5, &map, &theme, cx)),
819            [("{}\n\n".to_string(), None)]
820        );
821
822        map.update(&mut cx, |map, cx| {
823            map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], cx)
824        });
825        assert_eq!(
826            cx.update(|cx| highlighted_chunks(1..4, &map, &theme, cx)),
827            [
828                ("out".to_string(), Some("fn.name")),
829                ("\n".to_string(), None),
830                ("  \nfn ".to_string(), Some("mod.body")),
831                ("i\n".to_string(), Some("fn.name"))
832            ]
833        );
834    }
835
836    #[gpui::test]
837    fn test_clip_point(cx: &mut gpui::MutableAppContext) {
838        use Bias::{Left, Right};
839
840        let text = "\n'a', 'α',\t'✋',\t'❎', '🍐'\n";
841        let display_text = "\n'a', 'α',   '✋',    '❎', '🍐'\n";
842        let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
843
844        let tab_size = 4;
845        let font_cache = cx.font_cache();
846        let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
847        let font_id = font_cache
848            .select_font(family_id, &Default::default())
849            .unwrap();
850        let font_size = 14.0;
851        let map = cx.add_model(|cx| {
852            DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, None, cx)
853        });
854        let map = map.update(cx, |map, cx| map.snapshot(cx));
855
856        assert_eq!(map.text(), display_text);
857        for (input_column, bias, output_column) in vec![
858            ("'a', '".len(), Left, "'a', '".len()),
859            ("'a', '".len() + 1, Left, "'a', '".len()),
860            ("'a', '".len() + 1, Right, "'a', 'α".len()),
861            ("'a', 'α', ".len(), Left, "'a', 'α',".len()),
862            ("'a', 'α', ".len(), Right, "'a', 'α',   ".len()),
863            ("'a', 'α',   '".len() + 1, Left, "'a', 'α',   '".len()),
864            ("'a', 'α',   '".len() + 1, Right, "'a', 'α',   '✋".len()),
865            ("'a', 'α',   '✋',".len(), Right, "'a', 'α',   '✋',".len()),
866            ("'a', 'α',   '✋', ".len(), Left, "'a', 'α',   '✋',".len()),
867            (
868                "'a', 'α',   '✋', ".len(),
869                Right,
870                "'a', 'α',   '✋',    ".len(),
871            ),
872        ] {
873            assert_eq!(
874                map.clip_point(DisplayPoint::new(1, input_column as u32), bias),
875                DisplayPoint::new(1, output_column as u32),
876                "clip_point(({}, {}))",
877                1,
878                input_column,
879            );
880        }
881    }
882
883    #[gpui::test]
884    fn test_tabs_with_multibyte_chars(cx: &mut gpui::MutableAppContext) {
885        let text = "\t\tα\nβ\t\n🏀β\t\tγ";
886        let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
887        let tab_size = 4;
888        let font_cache = cx.font_cache();
889        let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
890        let font_id = font_cache
891            .select_font(family_id, &Default::default())
892            .unwrap();
893        let font_size = 14.0;
894
895        let map = cx.add_model(|cx| {
896            DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, None, cx)
897        });
898        let map = map.update(cx, |map, cx| map.snapshot(cx));
899        assert_eq!(map.text(), "✅       α\nβ   \n🏀β      γ");
900        assert_eq!(
901            map.chunks_at(0).collect::<String>(),
902            "✅       α\nβ   \n🏀β      γ"
903        );
904        assert_eq!(map.chunks_at(1).collect::<String>(), "β   \n🏀β      γ");
905        assert_eq!(map.chunks_at(2).collect::<String>(), "🏀β      γ");
906
907        let point = Point::new(0, "\t\t".len() as u32);
908        let display_point = DisplayPoint::new(0, "".len() as u32);
909        assert_eq!(point.to_display_point(&map, Left), display_point);
910        assert_eq!(display_point.to_buffer_point(&map, Left), point,);
911
912        let point = Point::new(1, "β\t".len() as u32);
913        let display_point = DisplayPoint::new(1, "β   ".len() as u32);
914        assert_eq!(point.to_display_point(&map, Left), display_point);
915        assert_eq!(display_point.to_buffer_point(&map, Left), point,);
916
917        let point = Point::new(2, "🏀β\t\t".len() as u32);
918        let display_point = DisplayPoint::new(2, "🏀β      ".len() as u32);
919        assert_eq!(point.to_display_point(&map, Left), display_point);
920        assert_eq!(display_point.to_buffer_point(&map, Left), point,);
921
922        // Display points inside of expanded tabs
923        assert_eq!(
924            DisplayPoint::new(0, "".len() as u32).to_buffer_point(&map, Right),
925            Point::new(0, "\t\t".len() as u32),
926        );
927        assert_eq!(
928            DisplayPoint::new(0, "".len() as u32).to_buffer_point(&map, Left),
929            Point::new(0, "\t".len() as u32),
930        );
931        assert_eq!(
932            DisplayPoint::new(0, "".len() as u32).to_buffer_point(&map, Right),
933            Point::new(0, "\t".len() as u32),
934        );
935        assert_eq!(
936            DisplayPoint::new(0, "".len() as u32).to_buffer_point(&map, Left),
937            Point::new(0, "".len() as u32),
938        );
939
940        // Clipping display points inside of multi-byte characters
941        assert_eq!(
942            map.clip_point(DisplayPoint::new(0, "".len() as u32 - 1), Left),
943            DisplayPoint::new(0, 0)
944        );
945        assert_eq!(
946            map.clip_point(DisplayPoint::new(0, "".len() as u32 - 1), Bias::Right),
947            DisplayPoint::new(0, "".len() as u32)
948        );
949    }
950
951    #[gpui::test]
952    fn test_max_point(cx: &mut gpui::MutableAppContext) {
953        let buffer = cx.add_model(|cx| Buffer::new(0, "aaa\n\t\tbbb", cx));
954        let tab_size = 4;
955        let font_cache = cx.font_cache();
956        let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
957        let font_id = font_cache
958            .select_font(family_id, &Default::default())
959            .unwrap();
960        let font_size = 14.0;
961        let map = cx.add_model(|cx| {
962            DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, None, cx)
963        });
964        assert_eq!(
965            map.update(cx, |map, cx| map.snapshot(cx)).max_point(),
966            DisplayPoint::new(1, 11)
967        )
968    }
969
970    fn highlighted_chunks<'a>(
971        rows: Range<u32>,
972        map: &ModelHandle<DisplayMap>,
973        theme: &'a SyntaxTheme,
974        cx: &mut MutableAppContext,
975    ) -> Vec<(String, Option<&'a str>)> {
976        let mut snapshot = map.update(cx, |map, cx| map.snapshot(cx));
977        let mut chunks: Vec<(String, Option<&str>)> = Vec::new();
978        for (chunk, style_id) in snapshot.highlighted_chunks_for_rows(rows) {
979            let style_name = theme.highlight_name(style_id);
980            if let Some((last_chunk, last_style_name)) = chunks.last_mut() {
981                if style_name == *last_style_name {
982                    last_chunk.push_str(chunk);
983                } else {
984                    chunks.push((chunk.to_string(), style_name));
985                }
986            } else {
987                chunks.push((chunk.to_string(), style_name));
988            }
989        }
990        chunks
991    }
992}