display_map.rs

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