mod.rs

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