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::{AppContext, ModelHandle};
  8use postage::prelude::Stream;
  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: WrapMap,
 19}
 20
 21impl DisplayMap {
 22    pub fn new(
 23        buffer: ModelHandle<Buffer>,
 24        settings: Settings,
 25        wrap_width: Option<f32>,
 26        cx: &AppContext,
 27    ) -> Self {
 28        let (fold_map, snapshot) = FoldMap::new(buffer.clone(), cx);
 29        let (tab_map, snapshot) = TabMap::new(snapshot, settings.tab_size);
 30        let wrap_map = WrapMap::new(snapshot, settings, wrap_width, cx);
 31        DisplayMap {
 32            buffer,
 33            fold_map,
 34            tab_map,
 35            wrap_map,
 36        }
 37    }
 38
 39    pub fn snapshot(&self, cx: &AppContext) -> DisplayMapSnapshot {
 40        let (folds_snapshot, edits) = self.fold_map.read(cx);
 41        let (tabs_snapshot, edits) = self.tab_map.sync(folds_snapshot.clone(), edits);
 42        let wraps_snapshot = self.wrap_map.sync(tabs_snapshot.clone(), edits, cx);
 43        DisplayMapSnapshot {
 44            buffer_snapshot: self.buffer.read(cx).snapshot(),
 45            folds_snapshot,
 46            tabs_snapshot,
 47            wraps_snapshot,
 48        }
 49    }
 50
 51    pub fn fold<T: ToOffset>(
 52        &mut self,
 53        ranges: impl IntoIterator<Item = Range<T>>,
 54        cx: &AppContext,
 55    ) {
 56        let (mut fold_map, snapshot, edits) = self.fold_map.write(cx);
 57        let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
 58        self.wrap_map.sync(snapshot, edits, cx);
 59        let (snapshot, edits) = fold_map.fold(ranges, cx);
 60        let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
 61        self.wrap_map.sync(snapshot, edits, cx);
 62    }
 63
 64    pub fn unfold<T: ToOffset>(
 65        &mut self,
 66        ranges: impl IntoIterator<Item = Range<T>>,
 67        cx: &AppContext,
 68    ) {
 69        let (mut fold_map, snapshot, edits) = self.fold_map.write(cx);
 70        let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
 71        self.wrap_map.sync(snapshot, edits, cx);
 72        let (snapshot, edits) = fold_map.unfold(ranges, cx);
 73        let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
 74        self.wrap_map.sync(snapshot, edits, cx);
 75    }
 76
 77    pub fn set_wrap_width(&self, width: Option<f32>) {
 78        self.wrap_map.set_wrap_width(width);
 79    }
 80
 81    pub fn notifications(&self) -> impl Stream<Item = ()> {
 82        self.wrap_map.notifications()
 83    }
 84}
 85
 86pub struct DisplayMapSnapshot {
 87    buffer_snapshot: buffer::Snapshot,
 88    folds_snapshot: fold_map::Snapshot,
 89    tabs_snapshot: tab_map::Snapshot,
 90    wraps_snapshot: wrap_map::Snapshot,
 91}
 92
 93impl DisplayMapSnapshot {
 94    pub fn buffer_rows(&self, start_row: u32) -> BufferRows {
 95        self.wraps_snapshot.buffer_rows(start_row)
 96    }
 97
 98    pub fn max_point(&self) -> DisplayPoint {
 99        DisplayPoint(self.wraps_snapshot.max_point())
100    }
101
102    pub fn chunks_at(&self, point: DisplayPoint) -> wrap_map::Chunks {
103        self.wraps_snapshot.chunks_at(point.0)
104    }
105
106    pub fn highlighted_chunks_for_rows(&mut self, rows: Range<u32>) -> wrap_map::HighlightedChunks {
107        self.wraps_snapshot.highlighted_chunks_for_rows(rows)
108    }
109
110    pub fn chars_at<'a>(&'a self, point: DisplayPoint) -> impl Iterator<Item = char> + 'a {
111        self.chunks_at(point).flat_map(str::chars)
112    }
113
114    pub fn column_to_chars(&self, display_row: u32, target: u32) -> u32 {
115        let mut count = 0;
116        let mut column = 0;
117        for c in self.chars_at(DisplayPoint::new(display_row, 0)) {
118            if column >= target {
119                break;
120            }
121            count += 1;
122            column += c.len_utf8() as u32;
123        }
124        count
125    }
126
127    pub fn column_from_chars(&self, display_row: u32, char_count: u32) -> u32 {
128        let mut count = 0;
129        let mut column = 0;
130        for c in self.chars_at(DisplayPoint::new(display_row, 0)) {
131            if c == '\n' || count >= char_count {
132                break;
133            }
134            count += 1;
135            column += c.len_utf8() as u32;
136        }
137        column
138    }
139
140    pub fn clip_point(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint {
141        DisplayPoint(self.wraps_snapshot.clip_point(point.0, bias))
142    }
143
144    pub fn folds_in_range<'a, T>(
145        &'a self,
146        range: Range<T>,
147    ) -> impl Iterator<Item = &'a Range<Anchor>>
148    where
149        T: ToOffset,
150    {
151        self.folds_snapshot.folds_in_range(range)
152    }
153
154    pub fn intersects_fold<T: ToOffset>(&self, offset: T) -> bool {
155        self.folds_snapshot.intersects_fold(offset)
156    }
157
158    pub fn is_line_folded(&self, display_row: u32) -> bool {
159        let wrap_point = DisplayPoint::new(display_row, 0).0;
160        let row = self.wraps_snapshot.to_input_point(wrap_point).row();
161        self.folds_snapshot.is_line_folded(row)
162    }
163
164    pub fn text(&self) -> String {
165        self.chunks_at(DisplayPoint::zero()).collect()
166    }
167
168    pub fn line(&self, display_row: u32) -> String {
169        let mut result = String::new();
170        for chunk in self.chunks_at(DisplayPoint::new(display_row, 0)) {
171            if let Some(ix) = chunk.find('\n') {
172                result.push_str(&chunk[0..ix]);
173                break;
174            } else {
175                result.push_str(chunk);
176            }
177        }
178        result
179    }
180
181    pub fn line_indent(&self, display_row: u32) -> (u32, bool) {
182        let mut indent = 0;
183        let mut is_blank = true;
184        for c in self.chars_at(DisplayPoint::new(display_row, 0)) {
185            if c == ' ' {
186                indent += 1;
187            } else {
188                is_blank = c == '\n';
189                break;
190            }
191        }
192        (indent, is_blank)
193    }
194
195    pub fn line_len(&self, row: u32) -> u32 {
196        self.wraps_snapshot.line_len(row)
197    }
198
199    pub fn longest_row(&self) -> u32 {
200        self.wraps_snapshot.longest_row()
201    }
202
203    pub fn anchor_before(&self, point: DisplayPoint, bias: Bias) -> Anchor {
204        self.buffer_snapshot
205            .anchor_before(point.to_buffer_point(self, bias))
206    }
207
208    pub fn anchor_after(&self, point: DisplayPoint, bias: Bias) -> Anchor {
209        self.buffer_snapshot
210            .anchor_after(point.to_buffer_point(self, bias))
211    }
212}
213
214#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
215pub struct DisplayPoint(wrap_map::OutputPoint);
216
217impl DisplayPoint {
218    pub fn new(row: u32, column: u32) -> Self {
219        Self(wrap_map::OutputPoint::new(row, column))
220    }
221
222    pub fn zero() -> Self {
223        Self::new(0, 0)
224    }
225
226    pub fn row(self) -> u32 {
227        self.0.row()
228    }
229
230    pub fn column(self) -> u32 {
231        self.0.column()
232    }
233
234    pub fn row_mut(&mut self) -> &mut u32 {
235        self.0.row_mut()
236    }
237
238    pub fn column_mut(&mut self) -> &mut u32 {
239        self.0.column_mut()
240    }
241
242    pub fn to_buffer_point(self, map: &DisplayMapSnapshot, bias: Bias) -> Point {
243        let unwrapped_point = map.wraps_snapshot.to_input_point(self.0);
244        let unexpanded_point = map.tabs_snapshot.to_input_point(unwrapped_point, bias).0;
245        map.folds_snapshot.to_input_point(unexpanded_point)
246    }
247
248    pub fn to_buffer_offset(self, map: &DisplayMapSnapshot, bias: Bias) -> usize {
249        let unwrapped_point = map.wraps_snapshot.to_input_point(self.0);
250        let unexpanded_point = map.tabs_snapshot.to_input_point(unwrapped_point, bias).0;
251        map.folds_snapshot.to_input_offset(unexpanded_point)
252    }
253}
254
255impl Point {
256    pub fn to_display_point(self, map: &DisplayMapSnapshot) -> DisplayPoint {
257        let folded_point = map.folds_snapshot.to_output_point(self);
258        let expanded_point = map.tabs_snapshot.to_output_point(folded_point);
259        let wrapped_point = map.wraps_snapshot.to_output_point(expanded_point);
260        DisplayPoint(wrapped_point)
261    }
262}
263
264impl Anchor {
265    pub fn to_display_point(&self, map: &DisplayMapSnapshot) -> DisplayPoint {
266        self.to_point(&map.buffer_snapshot).to_display_point(map)
267    }
268}
269
270#[cfg(test)]
271mod tests {
272    use super::*;
273    use crate::{
274        language::{Language, LanguageConfig},
275        settings::Theme,
276        test::*,
277        util::RandomCharIter,
278    };
279    use buffer::History;
280    use rand::prelude::*;
281    use std::{env, sync::Arc};
282
283    #[gpui::test]
284    async fn test_random(mut cx: gpui::TestAppContext) {
285        cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
286        let iterations = env::var("ITERATIONS")
287            .map(|i| i.parse().expect("invalid `ITERATIONS` variable"))
288            .unwrap_or(100);
289        let operations = env::var("OPERATIONS")
290            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
291            .unwrap_or(10);
292        let seed_range = if let Ok(seed) = env::var("SEED") {
293            let seed = seed.parse().expect("invalid `SEED` variable");
294            seed..seed + 1
295        } else {
296            0..iterations
297        };
298        let font_cache = cx.font_cache();
299
300        for seed in seed_range {
301            dbg!(seed);
302            let mut rng = StdRng::seed_from_u64(seed);
303            let settings = Settings {
304                buffer_font_family: font_cache.load_family(&["Helvetica"]).unwrap(),
305                ui_font_family: font_cache.load_family(&["Helvetica"]).unwrap(),
306                buffer_font_size: 12.0,
307                ui_font_size: 12.0,
308                tab_size: rng.gen_range(1..=4),
309                theme: Arc::new(Theme::default()),
310            };
311
312            let buffer = cx.add_model(|cx| {
313                let len = rng.gen_range(0..10);
314                let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
315                log::info!("Initial buffer text: {:?}", text);
316                Buffer::new(0, text, cx)
317            });
318            let wrap_width = Some(rng.gen_range(20.0..=100.0));
319            let map = cx.read(|cx| DisplayMap::new(buffer.clone(), settings, wrap_width, cx));
320
321            for _op_ix in 0..operations {
322                buffer.update(&mut cx, |buffer, cx| buffer.randomly_mutate(&mut rng, cx));
323                let snapshot = cx.read(|cx| map.snapshot(cx));
324                let expected_buffer_rows = (0..=snapshot.max_point().row())
325                    .map(|display_row| {
326                        DisplayPoint::new(display_row, 0)
327                            .to_buffer_point(&snapshot, Bias::Left)
328                            .row
329                    })
330                    .collect::<Vec<_>>();
331                for start_display_row in 0..expected_buffer_rows.len() {
332                    assert_eq!(
333                        snapshot
334                            .buffer_rows(start_display_row as u32)
335                            .collect::<Vec<_>>(),
336                        &expected_buffer_rows[start_display_row..],
337                        "invalid buffer_rows({}..)",
338                        start_display_row
339                    );
340                }
341            }
342        }
343    }
344
345    #[gpui::test]
346    async fn test_soft_wraps(mut cx: gpui::TestAppContext) {
347        cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
348
349        let font_cache = cx.font_cache();
350
351        let settings = Settings {
352            buffer_font_family: font_cache.load_family(&["Helvetica"]).unwrap(),
353            ui_font_family: font_cache.load_family(&["Helvetica"]).unwrap(),
354            buffer_font_size: 12.0,
355            ui_font_size: 12.0,
356            tab_size: 4,
357            theme: Arc::new(Theme::default()),
358        };
359        let wrap_width = Some(64.);
360
361        let text = "one two three four five\nsix seven eight";
362        let buffer = cx.add_model(|cx| Buffer::new(0, text.to_string(), cx));
363        let map = cx.read(|cx| DisplayMap::new(buffer.clone(), settings, wrap_width, cx));
364
365        let snapshot = cx.read(|cx| map.snapshot(cx));
366        assert_eq!(
367            snapshot
368                .chunks_at(DisplayPoint::new(0, 3))
369                .collect::<String>(),
370            " two \nthree four \nfive\nsix seven \neight"
371        );
372
373        buffer.update(&mut cx, |buffer, cx| {
374            let ix = buffer.text().find("seven").unwrap();
375            buffer.edit(vec![ix..ix], "and ", cx);
376        });
377
378        let snapshot = cx.read(|cx| map.snapshot(cx));
379        assert_eq!(
380            snapshot
381                .chunks_at(DisplayPoint::new(1, 0))
382                .collect::<String>(),
383            "three four \nfive\nsix and \nseven eight"
384        );
385    }
386
387    #[gpui::test]
388    fn test_chunks_at(cx: &mut gpui::MutableAppContext) {
389        let text = sample_text(6, 6);
390        let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
391        let map = DisplayMap::new(
392            buffer.clone(),
393            Settings::new(cx.font_cache()).unwrap().with_tab_size(4),
394            None,
395            cx.as_ref(),
396        );
397        buffer.update(cx, |buffer, cx| {
398            buffer.edit(
399                vec![
400                    Point::new(1, 0)..Point::new(1, 0),
401                    Point::new(1, 1)..Point::new(1, 1),
402                    Point::new(2, 1)..Point::new(2, 1),
403                ],
404                "\t",
405                cx,
406            )
407        });
408
409        assert_eq!(
410            &map.snapshot(cx.as_ref())
411                .chunks_at(DisplayPoint::new(1, 0))
412                .collect::<String>()[0..10],
413            "    b   bb"
414        );
415        assert_eq!(
416            &map.snapshot(cx.as_ref())
417                .chunks_at(DisplayPoint::new(1, 2))
418                .collect::<String>()[0..10],
419            "  b   bbbb"
420        );
421        assert_eq!(
422            &map.snapshot(cx.as_ref())
423                .chunks_at(DisplayPoint::new(1, 6))
424                .collect::<String>()[0..13],
425            "  bbbbb\nc   c"
426        );
427    }
428
429    #[gpui::test]
430    async fn test_highlighted_chunks_at(mut cx: gpui::TestAppContext) {
431        use unindent::Unindent as _;
432
433        let grammar = tree_sitter_rust::language();
434        let text = r#"
435            fn outer() {}
436
437            mod module {
438                fn inner() {}
439            }"#
440        .unindent();
441        let highlight_query = tree_sitter::Query::new(
442            grammar,
443            r#"
444            (mod_item name: (identifier) body: _ @mod.body)
445            (function_item name: (identifier) @fn.name)"#,
446        )
447        .unwrap();
448        let theme = Theme::parse(
449            r#"
450            [syntax]
451            "mod.body" = 0xff0000
452            "fn.name" = 0x00ff00"#,
453        )
454        .unwrap();
455        let lang = Arc::new(Language {
456            config: LanguageConfig {
457                name: "Test".to_string(),
458                path_suffixes: vec![".test".to_string()],
459                ..Default::default()
460            },
461            grammar: grammar.clone(),
462            highlight_query,
463            brackets_query: tree_sitter::Query::new(grammar, "").unwrap(),
464            theme_mapping: Default::default(),
465        });
466        lang.set_theme(&theme);
467
468        let buffer = cx.add_model(|cx| {
469            Buffer::from_history(0, History::new(text.into()), None, Some(lang), cx)
470        });
471        buffer.condition(&cx, |buf, _| !buf.is_parsing()).await;
472
473        let mut map = cx.read(|cx| {
474            DisplayMap::new(
475                buffer,
476                Settings::new(cx.font_cache()).unwrap().with_tab_size(2),
477                None,
478                cx,
479            )
480        });
481        assert_eq!(
482            cx.read(|cx| highlighted_chunks(0..5, &map, &theme, cx)),
483            vec![
484                ("fn ".to_string(), None),
485                ("outer".to_string(), Some("fn.name")),
486                ("() {}\n\nmod module ".to_string(), None),
487                ("{\n    fn ".to_string(), Some("mod.body")),
488                ("inner".to_string(), Some("fn.name")),
489                ("() {}\n}".to_string(), Some("mod.body")),
490            ]
491        );
492        assert_eq!(
493            cx.read(|cx| highlighted_chunks(3..5, &map, &theme, cx)),
494            vec![
495                ("    fn ".to_string(), Some("mod.body")),
496                ("inner".to_string(), Some("fn.name")),
497                ("() {}\n}".to_string(), Some("mod.body")),
498            ]
499        );
500
501        cx.read(|cx| map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], cx));
502        assert_eq!(
503            cx.read(|cx| highlighted_chunks(0..2, &map, &theme, cx)),
504            vec![
505                ("fn ".to_string(), None),
506                ("out".to_string(), Some("fn.name")),
507                ("".to_string(), None),
508                ("  fn ".to_string(), Some("mod.body")),
509                ("inner".to_string(), Some("fn.name")),
510                ("() {}\n}".to_string(), Some("mod.body")),
511            ]
512        );
513
514        fn highlighted_chunks<'a>(
515            rows: Range<u32>,
516            map: &DisplayMap,
517            theme: &'a Theme,
518            cx: &AppContext,
519        ) -> Vec<(String, Option<&'a str>)> {
520            let mut chunks: Vec<(String, Option<&str>)> = Vec::new();
521            for (chunk, style_id) in map.snapshot(cx).highlighted_chunks_for_rows(rows) {
522                let style_name = theme.syntax_style_name(style_id);
523                if let Some((last_chunk, last_style_name)) = chunks.last_mut() {
524                    if style_name == *last_style_name {
525                        last_chunk.push_str(chunk);
526                    } else {
527                        chunks.push((chunk.to_string(), style_name));
528                    }
529                } else {
530                    chunks.push((chunk.to_string(), style_name));
531                }
532            }
533            chunks
534        }
535    }
536
537    #[gpui::test]
538    fn test_clip_point(cx: &mut gpui::MutableAppContext) {
539        use Bias::{Left, Right};
540
541        let text = "\n'a', 'α',\t'✋',\t'❎', '🍐'\n";
542        let display_text = "\n'a', 'α',   '✋',    '❎', '🍐'\n";
543        let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
544        let cx = cx.as_ref();
545        let map = DisplayMap::new(
546            buffer.clone(),
547            Settings::new(cx.font_cache()).unwrap().with_tab_size(4),
548            None,
549            cx,
550        );
551        let map = map.snapshot(cx);
552
553        assert_eq!(map.text(), display_text);
554        for (input_column, bias, output_column) in vec![
555            ("'a', '".len(), Left, "'a', '".len()),
556            ("'a', '".len() + 1, Left, "'a', '".len()),
557            ("'a', '".len() + 1, Right, "'a', 'α".len()),
558            ("'a', 'α', ".len(), Left, "'a', 'α',".len()),
559            ("'a', 'α', ".len(), Right, "'a', 'α',   ".len()),
560            ("'a', 'α',   '".len() + 1, Left, "'a', 'α',   '".len()),
561            ("'a', 'α',   '".len() + 1, Right, "'a', 'α',   '✋".len()),
562            ("'a', 'α',   '✋',".len(), Right, "'a', 'α',   '✋',".len()),
563            ("'a', 'α',   '✋', ".len(), Left, "'a', 'α',   '✋',".len()),
564            (
565                "'a', 'α',   '✋', ".len(),
566                Right,
567                "'a', 'α',   '✋',    ".len(),
568            ),
569        ] {
570            assert_eq!(
571                map.clip_point(DisplayPoint::new(1, input_column as u32), bias),
572                DisplayPoint::new(1, output_column as u32),
573                "clip_point(({}, {}))",
574                1,
575                input_column,
576            );
577        }
578    }
579
580    #[gpui::test]
581    fn test_tabs_with_multibyte_chars(cx: &mut gpui::MutableAppContext) {
582        let text = "\t\tα\nβ\t\n🏀β\t\tγ";
583        let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
584        let cx = cx.as_ref();
585        let map = DisplayMap::new(
586            buffer.clone(),
587            Settings::new(cx.font_cache()).unwrap().with_tab_size(4),
588            None,
589            cx,
590        );
591        let map = map.snapshot(cx);
592        assert_eq!(map.text(), "✅       α\nβ   \n🏀β      γ");
593
594        let point = Point::new(0, "\t\t".len() as u32);
595        let display_point = DisplayPoint::new(0, "".len() as u32);
596        assert_eq!(point.to_display_point(&map), display_point);
597        assert_eq!(display_point.to_buffer_point(&map, Bias::Left), point,);
598
599        let point = Point::new(1, "β\t".len() as u32);
600        let display_point = DisplayPoint::new(1, "β   ".len() as u32);
601        assert_eq!(point.to_display_point(&map), display_point);
602        assert_eq!(display_point.to_buffer_point(&map, Bias::Left), point,);
603
604        let point = Point::new(2, "🏀β\t\t".len() as u32);
605        let display_point = DisplayPoint::new(2, "🏀β      ".len() as u32);
606        assert_eq!(point.to_display_point(&map), display_point);
607        assert_eq!(display_point.to_buffer_point(&map, Bias::Left), point,);
608
609        // Display points inside of expanded tabs
610        assert_eq!(
611            DisplayPoint::new(0, "".len() as u32).to_buffer_point(&map, Bias::Right),
612            Point::new(0, "\t\t".len() as u32),
613        );
614        assert_eq!(
615            DisplayPoint::new(0, "".len() as u32).to_buffer_point(&map, Bias::Left),
616            Point::new(0, "\t".len() as u32),
617        );
618        assert_eq!(
619            map.chunks_at(DisplayPoint::new(0, "".len() as u32))
620                .collect::<String>(),
621            " α\nβ   \n🏀β      γ"
622        );
623        assert_eq!(
624            DisplayPoint::new(0, "".len() as u32).to_buffer_point(&map, Bias::Right),
625            Point::new(0, "\t".len() as u32),
626        );
627        assert_eq!(
628            DisplayPoint::new(0, "".len() as u32).to_buffer_point(&map, Bias::Left),
629            Point::new(0, "".len() as u32),
630        );
631        assert_eq!(
632            map.chunks_at(DisplayPoint::new(0, "".len() as u32))
633                .collect::<String>(),
634            "      α\nβ   \n🏀β      γ"
635        );
636
637        // Clipping display points inside of multi-byte characters
638        assert_eq!(
639            map.clip_point(DisplayPoint::new(0, "".len() as u32 - 1), Bias::Left),
640            DisplayPoint::new(0, 0)
641        );
642        assert_eq!(
643            map.clip_point(DisplayPoint::new(0, "".len() as u32 - 1), Bias::Right),
644            DisplayPoint::new(0, "".len() as u32)
645        );
646    }
647
648    #[gpui::test]
649    fn test_max_point(cx: &mut gpui::MutableAppContext) {
650        let buffer = cx.add_model(|cx| Buffer::new(0, "aaa\n\t\tbbb", cx));
651        let map = DisplayMap::new(
652            buffer.clone(),
653            Settings::new(cx.font_cache()).unwrap().with_tab_size(4),
654            None,
655            cx.as_ref(),
656        );
657        assert_eq!(
658            map.snapshot(cx.as_ref()).max_point(),
659            DisplayPoint::new(1, 11)
660        )
661    }
662}