display_map.rs

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