display_map.rs

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