display_map.rs

   1mod block_map;
   2mod fold_map;
   3mod tab_map;
   4mod wrap_map;
   5
   6use crate::{Anchor, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint};
   7use block_map::{BlockMap, BlockPoint};
   8use collections::{HashMap, HashSet};
   9use fold_map::FoldMap;
  10use gpui::{
  11    fonts::{FontId, HighlightStyle},
  12    Entity, ModelContext, ModelHandle,
  13};
  14use language::{Point, Subscription as BufferSubscription};
  15use std::{any::TypeId, fmt::Debug, ops::Range, sync::Arc};
  16use sum_tree::{Bias, TreeMap};
  17use tab_map::TabMap;
  18use wrap_map::WrapMap;
  19
  20pub use block_map::{
  21    BlockBufferRows as DisplayBufferRows, BlockChunks as DisplayChunks, BlockContext,
  22    BlockDisposition, BlockId, BlockProperties, RenderBlock, TransformBlock,
  23};
  24
  25pub trait ToDisplayPoint {
  26    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint;
  27}
  28
  29type TextHighlights = TreeMap<Option<TypeId>, Arc<(HighlightStyle, Vec<Range<Anchor>>)>>;
  30
  31pub struct DisplayMap {
  32    buffer: ModelHandle<MultiBuffer>,
  33    buffer_subscription: BufferSubscription,
  34    fold_map: FoldMap,
  35    tab_map: TabMap,
  36    wrap_map: ModelHandle<WrapMap>,
  37    block_map: BlockMap,
  38    text_highlights: TextHighlights,
  39    pub clip_at_line_ends: bool,
  40}
  41
  42impl Entity for DisplayMap {
  43    type Event = ();
  44}
  45
  46impl DisplayMap {
  47    pub fn new(
  48        buffer: ModelHandle<MultiBuffer>,
  49        // TODO - remove. read tab_size from settings inside
  50        tab_size: usize,
  51        font_id: FontId,
  52        font_size: f32,
  53        wrap_width: Option<f32>,
  54        buffer_header_height: u8,
  55        excerpt_header_height: u8,
  56        cx: &mut ModelContext<Self>,
  57    ) -> Self {
  58        let buffer_subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
  59        let (fold_map, snapshot) = FoldMap::new(buffer.read(cx).snapshot(cx));
  60        let (tab_map, snapshot) = TabMap::new(snapshot, tab_size);
  61        let (wrap_map, snapshot) = WrapMap::new(snapshot, font_id, font_size, wrap_width, cx);
  62        let block_map = BlockMap::new(snapshot, buffer_header_height, excerpt_header_height);
  63        cx.observe(&wrap_map, |_, _, cx| cx.notify()).detach();
  64        DisplayMap {
  65            buffer,
  66            buffer_subscription,
  67            fold_map,
  68            tab_map,
  69            wrap_map,
  70            block_map,
  71            text_highlights: Default::default(),
  72            clip_at_line_ends: false,
  73        }
  74    }
  75
  76    pub fn snapshot(&self, cx: &mut ModelContext<Self>) -> DisplaySnapshot {
  77        let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
  78        let edits = self.buffer_subscription.consume().into_inner();
  79        let (folds_snapshot, edits) = self.fold_map.read(buffer_snapshot, edits);
  80
  81        // TODO: Pull tabsize out of cx and pass it to sync
  82        let (tabs_snapshot, edits) = self.tab_map.sync(folds_snapshot.clone(), edits);
  83        let (wraps_snapshot, edits) = self
  84            .wrap_map
  85            .update(cx, |map, cx| map.sync(tabs_snapshot.clone(), edits, cx));
  86        let blocks_snapshot = self.block_map.read(wraps_snapshot.clone(), edits);
  87
  88        DisplaySnapshot {
  89            buffer_snapshot: self.buffer.read(cx).snapshot(cx),
  90            folds_snapshot,
  91            tabs_snapshot,
  92            wraps_snapshot,
  93            blocks_snapshot,
  94            text_highlights: self.text_highlights.clone(),
  95            clip_at_line_ends: self.clip_at_line_ends,
  96        }
  97    }
  98
  99    pub fn fold<T: ToOffset>(
 100        &mut self,
 101        ranges: impl IntoIterator<Item = Range<T>>,
 102        cx: &mut ModelContext<Self>,
 103    ) {
 104        let snapshot = self.buffer.read(cx).snapshot(cx);
 105        let edits = self.buffer_subscription.consume().into_inner();
 106        let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
 107        let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
 108        let (snapshot, edits) = self
 109            .wrap_map
 110            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 111        self.block_map.read(snapshot, edits);
 112        let (snapshot, edits) = fold_map.fold(ranges);
 113        let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
 114        let (snapshot, edits) = self
 115            .wrap_map
 116            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 117        self.block_map.read(snapshot, edits);
 118    }
 119
 120    pub fn unfold<T: ToOffset>(
 121        &mut self,
 122        ranges: impl IntoIterator<Item = Range<T>>,
 123        inclusive: bool,
 124        cx: &mut ModelContext<Self>,
 125    ) {
 126        let snapshot = self.buffer.read(cx).snapshot(cx);
 127        let edits = self.buffer_subscription.consume().into_inner();
 128        let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
 129        let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
 130        let (snapshot, edits) = self
 131            .wrap_map
 132            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 133        self.block_map.read(snapshot, edits);
 134        let (snapshot, edits) = fold_map.unfold(ranges, inclusive);
 135        let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
 136        let (snapshot, edits) = self
 137            .wrap_map
 138            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 139        self.block_map.read(snapshot, edits);
 140    }
 141
 142    pub fn insert_blocks(
 143        &mut self,
 144        blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
 145        cx: &mut ModelContext<Self>,
 146    ) -> Vec<BlockId> {
 147        let snapshot = self.buffer.read(cx).snapshot(cx);
 148        let edits = self.buffer_subscription.consume().into_inner();
 149        let (snapshot, edits) = self.fold_map.read(snapshot, edits);
 150        let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
 151        let (snapshot, edits) = self
 152            .wrap_map
 153            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 154        let mut block_map = self.block_map.write(snapshot, edits);
 155        block_map.insert(blocks)
 156    }
 157
 158    pub fn replace_blocks(&mut self, styles: HashMap<BlockId, RenderBlock>) {
 159        self.block_map.replace(styles);
 160    }
 161
 162    pub fn remove_blocks(&mut self, ids: HashSet<BlockId>, cx: &mut ModelContext<Self>) {
 163        let snapshot = self.buffer.read(cx).snapshot(cx);
 164        let edits = self.buffer_subscription.consume().into_inner();
 165        let (snapshot, edits) = self.fold_map.read(snapshot, edits);
 166        let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
 167        let (snapshot, edits) = self
 168            .wrap_map
 169            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 170        let mut block_map = self.block_map.write(snapshot, edits);
 171        block_map.remove(ids);
 172    }
 173
 174    pub fn highlight_text(
 175        &mut self,
 176        type_id: TypeId,
 177        ranges: Vec<Range<Anchor>>,
 178        style: HighlightStyle,
 179    ) {
 180        self.text_highlights
 181            .insert(Some(type_id), Arc::new((style, ranges)));
 182    }
 183
 184    pub fn clear_text_highlights(
 185        &mut self,
 186        type_id: TypeId,
 187    ) -> Option<Arc<(HighlightStyle, Vec<Range<Anchor>>)>> {
 188        self.text_highlights.remove(&Some(type_id))
 189    }
 190
 191    pub fn set_font(&self, font_id: FontId, font_size: f32, cx: &mut ModelContext<Self>) {
 192        self.wrap_map
 193            .update(cx, |map, cx| map.set_font(font_id, font_size, cx));
 194    }
 195
 196    pub fn set_wrap_width(&self, width: Option<f32>, cx: &mut ModelContext<Self>) -> bool {
 197        self.wrap_map
 198            .update(cx, |map, cx| map.set_wrap_width(width, cx))
 199    }
 200
 201    #[cfg(test)]
 202    pub fn is_rewrapping(&self, cx: &gpui::AppContext) -> bool {
 203        self.wrap_map.read(cx).is_rewrapping()
 204    }
 205}
 206
 207pub struct DisplaySnapshot {
 208    pub buffer_snapshot: MultiBufferSnapshot,
 209    folds_snapshot: fold_map::FoldSnapshot,
 210    tabs_snapshot: tab_map::TabSnapshot,
 211    wraps_snapshot: wrap_map::WrapSnapshot,
 212    blocks_snapshot: block_map::BlockSnapshot,
 213    text_highlights: TextHighlights,
 214    clip_at_line_ends: bool,
 215}
 216
 217impl DisplaySnapshot {
 218    #[cfg(test)]
 219    pub fn fold_count(&self) -> usize {
 220        self.folds_snapshot.fold_count()
 221    }
 222
 223    pub fn is_empty(&self) -> bool {
 224        self.buffer_snapshot.len() == 0
 225    }
 226
 227    pub fn buffer_rows<'a>(&'a self, start_row: u32) -> DisplayBufferRows<'a> {
 228        self.blocks_snapshot.buffer_rows(start_row)
 229    }
 230
 231    pub fn max_buffer_row(&self) -> u32 {
 232        self.buffer_snapshot.max_buffer_row()
 233    }
 234
 235    pub fn prev_line_boundary(&self, mut point: Point) -> (Point, DisplayPoint) {
 236        loop {
 237            let mut fold_point = self.folds_snapshot.to_fold_point(point, Bias::Left);
 238            *fold_point.column_mut() = 0;
 239            point = fold_point.to_buffer_point(&self.folds_snapshot);
 240
 241            let mut display_point = self.point_to_display_point(point, Bias::Left);
 242            *display_point.column_mut() = 0;
 243            let next_point = self.display_point_to_point(display_point, Bias::Left);
 244            if next_point == point {
 245                return (point, display_point);
 246            }
 247            point = next_point;
 248        }
 249    }
 250
 251    pub fn next_line_boundary(&self, mut point: Point) -> (Point, DisplayPoint) {
 252        loop {
 253            let mut fold_point = self.folds_snapshot.to_fold_point(point, Bias::Right);
 254            *fold_point.column_mut() = self.folds_snapshot.line_len(fold_point.row());
 255            point = fold_point.to_buffer_point(&self.folds_snapshot);
 256
 257            let mut display_point = self.point_to_display_point(point, Bias::Right);
 258            *display_point.column_mut() = self.line_len(display_point.row());
 259            let next_point = self.display_point_to_point(display_point, Bias::Right);
 260            if next_point == point {
 261                return (point, display_point);
 262            }
 263            point = next_point;
 264        }
 265    }
 266
 267    fn point_to_display_point(&self, point: Point, bias: Bias) -> DisplayPoint {
 268        let fold_point = self.folds_snapshot.to_fold_point(point, bias);
 269        let tab_point = self.tabs_snapshot.to_tab_point(fold_point);
 270        let wrap_point = self.wraps_snapshot.from_tab_point(tab_point);
 271        let block_point = self.blocks_snapshot.to_block_point(wrap_point);
 272        DisplayPoint(block_point)
 273    }
 274
 275    fn display_point_to_point(&self, point: DisplayPoint, bias: Bias) -> Point {
 276        let block_point = point.0;
 277        let wrap_point = self.blocks_snapshot.to_wrap_point(block_point);
 278        let tab_point = self.wraps_snapshot.to_tab_point(wrap_point);
 279        let fold_point = self.tabs_snapshot.to_fold_point(tab_point, bias).0;
 280        fold_point.to_buffer_point(&self.folds_snapshot)
 281    }
 282
 283    pub fn max_point(&self) -> DisplayPoint {
 284        DisplayPoint(self.blocks_snapshot.max_point())
 285    }
 286
 287    pub fn text_chunks(&self, display_row: u32) -> impl Iterator<Item = &str> {
 288        self.blocks_snapshot
 289            .chunks(display_row..self.max_point().row() + 1, false, None)
 290            .map(|h| h.text)
 291    }
 292
 293    pub fn chunks<'a>(
 294        &'a self,
 295        display_rows: Range<u32>,
 296        language_aware: bool,
 297    ) -> DisplayChunks<'a> {
 298        self.blocks_snapshot
 299            .chunks(display_rows, language_aware, Some(&self.text_highlights))
 300    }
 301
 302    pub fn chars_at<'a>(&'a self, point: DisplayPoint) -> impl Iterator<Item = char> + 'a {
 303        let mut column = 0;
 304        let mut chars = self.text_chunks(point.row()).flat_map(str::chars);
 305        while column < point.column() {
 306            if let Some(c) = chars.next() {
 307                column += c.len_utf8() as u32;
 308            } else {
 309                break;
 310            }
 311        }
 312        chars
 313    }
 314
 315    pub fn column_to_chars(&self, display_row: u32, target: u32) -> u32 {
 316        let mut count = 0;
 317        let mut column = 0;
 318        for c in self.chars_at(DisplayPoint::new(display_row, 0)) {
 319            if column >= target {
 320                break;
 321            }
 322            count += 1;
 323            column += c.len_utf8() as u32;
 324        }
 325        count
 326    }
 327
 328    pub fn column_from_chars(&self, display_row: u32, char_count: u32) -> u32 {
 329        let mut count = 0;
 330        let mut column = 0;
 331        for c in self.chars_at(DisplayPoint::new(display_row, 0)) {
 332            if c == '\n' || count >= char_count {
 333                break;
 334            }
 335            count += 1;
 336            column += c.len_utf8() as u32;
 337        }
 338        column
 339    }
 340
 341    pub fn clip_point(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint {
 342        let mut clipped = self.blocks_snapshot.clip_point(point.0, bias);
 343        if self.clip_at_line_ends && clipped.column == self.line_len(clipped.row) {
 344            clipped.column = clipped.column.saturating_sub(1);
 345            clipped = self.blocks_snapshot.clip_point(clipped, Bias::Left);
 346        }
 347        DisplayPoint(clipped)
 348    }
 349
 350    pub fn folds_in_range<'a, T>(
 351        &'a self,
 352        range: Range<T>,
 353    ) -> impl Iterator<Item = &'a Range<Anchor>>
 354    where
 355        T: ToOffset,
 356    {
 357        self.folds_snapshot.folds_in_range(range)
 358    }
 359
 360    pub fn blocks_in_range<'a>(
 361        &'a self,
 362        rows: Range<u32>,
 363    ) -> impl Iterator<Item = (u32, &'a TransformBlock)> {
 364        self.blocks_snapshot.blocks_in_range(rows)
 365    }
 366
 367    pub fn intersects_fold<T: ToOffset>(&self, offset: T) -> bool {
 368        self.folds_snapshot.intersects_fold(offset)
 369    }
 370
 371    pub fn is_line_folded(&self, display_row: u32) -> bool {
 372        let block_point = BlockPoint(Point::new(display_row, 0));
 373        let wrap_point = self.blocks_snapshot.to_wrap_point(block_point);
 374        let tab_point = self.wraps_snapshot.to_tab_point(wrap_point);
 375        self.folds_snapshot.is_line_folded(tab_point.row())
 376    }
 377
 378    pub fn is_block_line(&self, display_row: u32) -> bool {
 379        self.blocks_snapshot.is_block_line(display_row)
 380    }
 381
 382    pub fn soft_wrap_indent(&self, display_row: u32) -> Option<u32> {
 383        let wrap_row = self
 384            .blocks_snapshot
 385            .to_wrap_point(BlockPoint::new(display_row, 0))
 386            .row();
 387        self.wraps_snapshot.soft_wrap_indent(wrap_row)
 388    }
 389
 390    pub fn text(&self) -> String {
 391        self.text_chunks(0).collect()
 392    }
 393
 394    pub fn line(&self, display_row: u32) -> String {
 395        let mut result = String::new();
 396        for chunk in self.text_chunks(display_row) {
 397            if let Some(ix) = chunk.find('\n') {
 398                result.push_str(&chunk[0..ix]);
 399                break;
 400            } else {
 401                result.push_str(chunk);
 402            }
 403        }
 404        result
 405    }
 406
 407    pub fn line_indent(&self, display_row: u32) -> (u32, bool) {
 408        let mut indent = 0;
 409        let mut is_blank = true;
 410        for c in self.chars_at(DisplayPoint::new(display_row, 0)) {
 411            if c == ' ' {
 412                indent += 1;
 413            } else {
 414                is_blank = c == '\n';
 415                break;
 416            }
 417        }
 418        (indent, is_blank)
 419    }
 420
 421    pub fn line_len(&self, row: u32) -> u32 {
 422        self.blocks_snapshot.line_len(row)
 423    }
 424
 425    pub fn longest_row(&self) -> u32 {
 426        self.blocks_snapshot.longest_row()
 427    }
 428}
 429
 430#[derive(Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq)]
 431pub struct DisplayPoint(BlockPoint);
 432
 433impl Debug for DisplayPoint {
 434    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 435        f.write_fmt(format_args!(
 436            "DisplayPoint({}, {})",
 437            self.row(),
 438            self.column()
 439        ))
 440    }
 441}
 442
 443impl DisplayPoint {
 444    pub fn new(row: u32, column: u32) -> Self {
 445        Self(BlockPoint(Point::new(row, column)))
 446    }
 447
 448    pub fn zero() -> Self {
 449        Self::new(0, 0)
 450    }
 451
 452    pub fn is_zero(&self) -> bool {
 453        self.0.is_zero()
 454    }
 455
 456    pub fn row(self) -> u32 {
 457        self.0.row
 458    }
 459
 460    pub fn column(self) -> u32 {
 461        self.0.column
 462    }
 463
 464    pub fn row_mut(&mut self) -> &mut u32 {
 465        &mut self.0.row
 466    }
 467
 468    pub fn column_mut(&mut self) -> &mut u32 {
 469        &mut self.0.column
 470    }
 471
 472    pub fn to_point(self, map: &DisplaySnapshot) -> Point {
 473        map.display_point_to_point(self, Bias::Left)
 474    }
 475
 476    pub fn to_offset(self, map: &DisplaySnapshot, bias: Bias) -> usize {
 477        let unblocked_point = map.blocks_snapshot.to_wrap_point(self.0);
 478        let unwrapped_point = map.wraps_snapshot.to_tab_point(unblocked_point);
 479        let unexpanded_point = map.tabs_snapshot.to_fold_point(unwrapped_point, bias).0;
 480        unexpanded_point.to_buffer_offset(&map.folds_snapshot)
 481    }
 482}
 483
 484impl ToDisplayPoint for usize {
 485    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
 486        map.point_to_display_point(self.to_point(&map.buffer_snapshot), Bias::Left)
 487    }
 488}
 489
 490impl ToDisplayPoint for Point {
 491    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
 492        map.point_to_display_point(*self, Bias::Left)
 493    }
 494}
 495
 496impl ToDisplayPoint for Anchor {
 497    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
 498        self.to_point(&map.buffer_snapshot).to_display_point(map)
 499    }
 500}
 501
 502#[cfg(test)]
 503pub mod tests {
 504    use super::*;
 505    use crate::{movement, test::marked_display_snapshot};
 506    use gpui::{color::Color, elements::*, test::observe, MutableAppContext};
 507    use language::{Buffer, Language, LanguageConfig, RandomCharIter, SelectionGoal};
 508    use rand::{prelude::*, Rng};
 509    use smol::stream::StreamExt;
 510    use std::{env, sync::Arc};
 511    use theme::SyntaxTheme;
 512    use util::test::{marked_text_ranges, sample_text};
 513    use Bias::*;
 514
 515    #[gpui::test(iterations = 100)]
 516    async fn test_random_display_map(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
 517        cx.foreground().set_block_on_ticks(0..=50);
 518        cx.foreground().forbid_parking();
 519        let operations = env::var("OPERATIONS")
 520            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
 521            .unwrap_or(10);
 522
 523        let font_cache = cx.font_cache().clone();
 524        let tab_size = rng.gen_range(1..=4);
 525        let buffer_start_excerpt_header_height = rng.gen_range(1..=5);
 526        let excerpt_header_height = rng.gen_range(1..=5);
 527        let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
 528        let font_id = font_cache
 529            .select_font(family_id, &Default::default())
 530            .unwrap();
 531        let font_size = 14.0;
 532        let max_wrap_width = 300.0;
 533        let mut wrap_width = if rng.gen_bool(0.1) {
 534            None
 535        } else {
 536            Some(rng.gen_range(0.0..=max_wrap_width))
 537        };
 538
 539        log::info!("tab size: {}", tab_size);
 540        log::info!("wrap width: {:?}", wrap_width);
 541
 542        let buffer = cx.update(|cx| {
 543            if rng.gen() {
 544                let len = rng.gen_range(0..10);
 545                let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
 546                MultiBuffer::build_simple(&text, cx)
 547            } else {
 548                MultiBuffer::build_random(&mut rng, cx)
 549            }
 550        });
 551
 552        let map = cx.add_model(|cx| {
 553            DisplayMap::new(
 554                buffer.clone(),
 555                tab_size,
 556                font_id,
 557                font_size,
 558                wrap_width,
 559                buffer_start_excerpt_header_height,
 560                excerpt_header_height,
 561                cx,
 562            )
 563        });
 564        let mut notifications = observe(&map, cx);
 565        let mut fold_count = 0;
 566        let mut blocks = Vec::new();
 567
 568        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
 569        log::info!("buffer text: {:?}", snapshot.buffer_snapshot.text());
 570        log::info!("fold text: {:?}", snapshot.folds_snapshot.text());
 571        log::info!("tab text: {:?}", snapshot.tabs_snapshot.text());
 572        log::info!("wrap text: {:?}", snapshot.wraps_snapshot.text());
 573        log::info!("block text: {:?}", snapshot.blocks_snapshot.text());
 574        log::info!("display text: {:?}", snapshot.text());
 575
 576        for _i in 0..operations {
 577            match rng.gen_range(0..100) {
 578                0..=19 => {
 579                    wrap_width = if rng.gen_bool(0.2) {
 580                        None
 581                    } else {
 582                        Some(rng.gen_range(0.0..=max_wrap_width))
 583                    };
 584                    log::info!("setting wrap width to {:?}", wrap_width);
 585                    map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
 586                }
 587                20..=44 => {
 588                    map.update(cx, |map, cx| {
 589                        if rng.gen() || blocks.is_empty() {
 590                            let buffer = map.snapshot(cx).buffer_snapshot;
 591                            let block_properties = (0..rng.gen_range(1..=1))
 592                                .map(|_| {
 593                                    let position =
 594                                        buffer.anchor_after(buffer.clip_offset(
 595                                            rng.gen_range(0..=buffer.len()),
 596                                            Bias::Left,
 597                                        ));
 598
 599                                    let disposition = if rng.gen() {
 600                                        BlockDisposition::Above
 601                                    } else {
 602                                        BlockDisposition::Below
 603                                    };
 604                                    let height = rng.gen_range(1..5);
 605                                    log::info!(
 606                                        "inserting block {:?} {:?} with height {}",
 607                                        disposition,
 608                                        position.to_point(&buffer),
 609                                        height
 610                                    );
 611                                    BlockProperties {
 612                                        position,
 613                                        height,
 614                                        disposition,
 615                                        render: Arc::new(|_| Empty::new().boxed()),
 616                                    }
 617                                })
 618                                .collect::<Vec<_>>();
 619                            blocks.extend(map.insert_blocks(block_properties, cx));
 620                        } else {
 621                            blocks.shuffle(&mut rng);
 622                            let remove_count = rng.gen_range(1..=4.min(blocks.len()));
 623                            let block_ids_to_remove = (0..remove_count)
 624                                .map(|_| blocks.remove(rng.gen_range(0..blocks.len())))
 625                                .collect();
 626                            log::info!("removing block ids {:?}", block_ids_to_remove);
 627                            map.remove_blocks(block_ids_to_remove, cx);
 628                        }
 629                    });
 630                }
 631                45..=79 => {
 632                    let mut ranges = Vec::new();
 633                    for _ in 0..rng.gen_range(1..=3) {
 634                        buffer.read_with(cx, |buffer, cx| {
 635                            let buffer = buffer.read(cx);
 636                            let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right);
 637                            let start = buffer.clip_offset(rng.gen_range(0..=end), Left);
 638                            ranges.push(start..end);
 639                        });
 640                    }
 641
 642                    if rng.gen() && fold_count > 0 {
 643                        log::info!("unfolding ranges: {:?}", ranges);
 644                        map.update(cx, |map, cx| {
 645                            map.unfold(ranges, true, cx);
 646                        });
 647                    } else {
 648                        log::info!("folding ranges: {:?}", ranges);
 649                        map.update(cx, |map, cx| {
 650                            map.fold(ranges, cx);
 651                        });
 652                    }
 653                }
 654                _ => {
 655                    buffer.update(cx, |buffer, cx| buffer.randomly_edit(&mut rng, 5, cx));
 656                }
 657            }
 658
 659            if map.read_with(cx, |map, cx| map.is_rewrapping(cx)) {
 660                notifications.next().await.unwrap();
 661            }
 662
 663            let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
 664            fold_count = snapshot.fold_count();
 665            log::info!("buffer text: {:?}", snapshot.buffer_snapshot.text());
 666            log::info!("fold text: {:?}", snapshot.folds_snapshot.text());
 667            log::info!("tab text: {:?}", snapshot.tabs_snapshot.text());
 668            log::info!("wrap text: {:?}", snapshot.wraps_snapshot.text());
 669            log::info!("block text: {:?}", snapshot.blocks_snapshot.text());
 670            log::info!("display text: {:?}", snapshot.text());
 671
 672            // Line boundaries
 673            let buffer = &snapshot.buffer_snapshot;
 674            for _ in 0..5 {
 675                let row = rng.gen_range(0..=buffer.max_point().row);
 676                let column = rng.gen_range(0..=buffer.line_len(row));
 677                let point = buffer.clip_point(Point::new(row, column), Left);
 678
 679                let (prev_buffer_bound, prev_display_bound) = snapshot.prev_line_boundary(point);
 680                let (next_buffer_bound, next_display_bound) = snapshot.next_line_boundary(point);
 681
 682                assert!(prev_buffer_bound <= point);
 683                assert!(next_buffer_bound >= point);
 684                assert_eq!(prev_buffer_bound.column, 0);
 685                assert_eq!(prev_display_bound.column(), 0);
 686                if next_buffer_bound < buffer.max_point() {
 687                    assert_eq!(buffer.chars_at(next_buffer_bound).next(), Some('\n'));
 688                }
 689
 690                assert_eq!(
 691                    prev_display_bound,
 692                    prev_buffer_bound.to_display_point(&snapshot),
 693                    "row boundary before {:?}. reported buffer row boundary: {:?}",
 694                    point,
 695                    prev_buffer_bound
 696                );
 697                assert_eq!(
 698                    next_display_bound,
 699                    next_buffer_bound.to_display_point(&snapshot),
 700                    "display row boundary after {:?}. reported buffer row boundary: {:?}",
 701                    point,
 702                    next_buffer_bound
 703                );
 704                assert_eq!(
 705                    prev_buffer_bound,
 706                    prev_display_bound.to_point(&snapshot),
 707                    "row boundary before {:?}. reported display row boundary: {:?}",
 708                    point,
 709                    prev_display_bound
 710                );
 711                assert_eq!(
 712                    next_buffer_bound,
 713                    next_display_bound.to_point(&snapshot),
 714                    "row boundary after {:?}. reported display row boundary: {:?}",
 715                    point,
 716                    next_display_bound
 717                );
 718            }
 719
 720            // Movement
 721            let min_point = snapshot.clip_point(DisplayPoint::new(0, 0), Left);
 722            let max_point = snapshot.clip_point(snapshot.max_point(), Right);
 723            for _ in 0..5 {
 724                let row = rng.gen_range(0..=snapshot.max_point().row());
 725                let column = rng.gen_range(0..=snapshot.line_len(row));
 726                let point = snapshot.clip_point(DisplayPoint::new(row, column), Left);
 727
 728                log::info!("Moving from point {:?}", point);
 729
 730                let moved_right = movement::right(&snapshot, point);
 731                log::info!("Right {:?}", moved_right);
 732                if point < max_point {
 733                    assert!(moved_right > point);
 734                    if point.column() == snapshot.line_len(point.row())
 735                        || snapshot.soft_wrap_indent(point.row()).is_some()
 736                            && point.column() == snapshot.line_len(point.row()) - 1
 737                    {
 738                        assert!(moved_right.row() > point.row());
 739                    }
 740                } else {
 741                    assert_eq!(moved_right, point);
 742                }
 743
 744                let moved_left = movement::left(&snapshot, point);
 745                log::info!("Left {:?}", moved_left);
 746                if point > min_point {
 747                    assert!(moved_left < point);
 748                    if point.column() == 0 {
 749                        assert!(moved_left.row() < point.row());
 750                    }
 751                } else {
 752                    assert_eq!(moved_left, point);
 753                }
 754            }
 755        }
 756    }
 757
 758    #[gpui::test(retries = 5)]
 759    fn test_soft_wraps(cx: &mut MutableAppContext) {
 760        cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
 761        cx.foreground().forbid_parking();
 762
 763        let font_cache = cx.font_cache();
 764
 765        let tab_size = 4;
 766        let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
 767        let font_id = font_cache
 768            .select_font(family_id, &Default::default())
 769            .unwrap();
 770        let font_size = 12.0;
 771        let wrap_width = Some(64.);
 772
 773        let text = "one two three four five\nsix seven eight";
 774        let buffer = MultiBuffer::build_simple(text, cx);
 775        let map = cx.add_model(|cx| {
 776            DisplayMap::new(
 777                buffer.clone(),
 778                tab_size,
 779                font_id,
 780                font_size,
 781                wrap_width,
 782                1,
 783                1,
 784                cx,
 785            )
 786        });
 787
 788        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
 789        assert_eq!(
 790            snapshot.text_chunks(0).collect::<String>(),
 791            "one two \nthree four \nfive\nsix seven \neight"
 792        );
 793        assert_eq!(
 794            snapshot.clip_point(DisplayPoint::new(0, 8), Bias::Left),
 795            DisplayPoint::new(0, 7)
 796        );
 797        assert_eq!(
 798            snapshot.clip_point(DisplayPoint::new(0, 8), Bias::Right),
 799            DisplayPoint::new(1, 0)
 800        );
 801        assert_eq!(
 802            movement::right(&snapshot, DisplayPoint::new(0, 7)),
 803            DisplayPoint::new(1, 0)
 804        );
 805        assert_eq!(
 806            movement::left(&snapshot, DisplayPoint::new(1, 0)),
 807            DisplayPoint::new(0, 7)
 808        );
 809        assert_eq!(
 810            movement::up(&snapshot, DisplayPoint::new(1, 10), SelectionGoal::None),
 811            (DisplayPoint::new(0, 7), SelectionGoal::Column(10))
 812        );
 813        assert_eq!(
 814            movement::down(
 815                &snapshot,
 816                DisplayPoint::new(0, 7),
 817                SelectionGoal::Column(10)
 818            ),
 819            (DisplayPoint::new(1, 10), SelectionGoal::Column(10))
 820        );
 821        assert_eq!(
 822            movement::down(
 823                &snapshot,
 824                DisplayPoint::new(1, 10),
 825                SelectionGoal::Column(10)
 826            ),
 827            (DisplayPoint::new(2, 4), SelectionGoal::Column(10))
 828        );
 829
 830        let ix = snapshot.buffer_snapshot.text().find("seven").unwrap();
 831        buffer.update(cx, |buffer, cx| {
 832            buffer.edit(vec![ix..ix], "and ", cx);
 833        });
 834
 835        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
 836        assert_eq!(
 837            snapshot.text_chunks(1).collect::<String>(),
 838            "three four \nfive\nsix and \nseven eight"
 839        );
 840
 841        // Re-wrap on font size changes
 842        map.update(cx, |map, cx| map.set_font(font_id, font_size + 3., cx));
 843
 844        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
 845        assert_eq!(
 846            snapshot.text_chunks(1).collect::<String>(),
 847            "three \nfour five\nsix and \nseven \neight"
 848        )
 849    }
 850
 851    #[gpui::test]
 852    fn test_text_chunks(cx: &mut gpui::MutableAppContext) {
 853        let text = sample_text(6, 6, 'a');
 854        let buffer = MultiBuffer::build_simple(&text, cx);
 855        let tab_size = 4;
 856        let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap();
 857        let font_id = cx
 858            .font_cache()
 859            .select_font(family_id, &Default::default())
 860            .unwrap();
 861        let font_size = 14.0;
 862        let map = cx.add_model(|cx| {
 863            DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, None, 1, 1, cx)
 864        });
 865        buffer.update(cx, |buffer, cx| {
 866            buffer.edit(
 867                vec![
 868                    Point::new(1, 0)..Point::new(1, 0),
 869                    Point::new(1, 1)..Point::new(1, 1),
 870                    Point::new(2, 1)..Point::new(2, 1),
 871                ],
 872                "\t",
 873                cx,
 874            )
 875        });
 876
 877        assert_eq!(
 878            map.update(cx, |map, cx| map.snapshot(cx))
 879                .text_chunks(1)
 880                .collect::<String>()
 881                .lines()
 882                .next(),
 883            Some("    b   bbbbb")
 884        );
 885        assert_eq!(
 886            map.update(cx, |map, cx| map.snapshot(cx))
 887                .text_chunks(2)
 888                .collect::<String>()
 889                .lines()
 890                .next(),
 891            Some("c   ccccc")
 892        );
 893    }
 894
 895    #[gpui::test]
 896    async fn test_chunks(cx: &mut gpui::TestAppContext) {
 897        use unindent::Unindent as _;
 898
 899        let text = r#"
 900            fn outer() {}
 901
 902            mod module {
 903                fn inner() {}
 904            }"#
 905        .unindent();
 906
 907        let theme = SyntaxTheme::new(vec![
 908            ("mod.body".to_string(), Color::red().into()),
 909            ("fn.name".to_string(), Color::blue().into()),
 910        ]);
 911        let language = Arc::new(
 912            Language::new(
 913                LanguageConfig {
 914                    name: "Test".into(),
 915                    path_suffixes: vec![".test".to_string()],
 916                    ..Default::default()
 917                },
 918                Some(tree_sitter_rust::language()),
 919            )
 920            .with_highlights_query(
 921                r#"
 922                (mod_item name: (identifier) body: _ @mod.body)
 923                (function_item name: (identifier) @fn.name)
 924                "#,
 925            )
 926            .unwrap(),
 927        );
 928        language.set_theme(&theme);
 929
 930        let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
 931        buffer.condition(&cx, |buf, _| !buf.is_parsing()).await;
 932        let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
 933
 934        let tab_size = 2;
 935        let font_cache = cx.font_cache();
 936        let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
 937        let font_id = font_cache
 938            .select_font(family_id, &Default::default())
 939            .unwrap();
 940        let font_size = 14.0;
 941
 942        let map = cx
 943            .add_model(|cx| DisplayMap::new(buffer, tab_size, font_id, font_size, None, 1, 1, cx));
 944        assert_eq!(
 945            cx.update(|cx| syntax_chunks(0..5, &map, &theme, cx)),
 946            vec![
 947                ("fn ".to_string(), None),
 948                ("outer".to_string(), Some(Color::blue())),
 949                ("() {}\n\nmod module ".to_string(), None),
 950                ("{\n    fn ".to_string(), Some(Color::red())),
 951                ("inner".to_string(), Some(Color::blue())),
 952                ("() {}\n}".to_string(), Some(Color::red())),
 953            ]
 954        );
 955        assert_eq!(
 956            cx.update(|cx| syntax_chunks(3..5, &map, &theme, cx)),
 957            vec![
 958                ("    fn ".to_string(), Some(Color::red())),
 959                ("inner".to_string(), Some(Color::blue())),
 960                ("() {}\n}".to_string(), Some(Color::red())),
 961            ]
 962        );
 963
 964        map.update(cx, |map, cx| {
 965            map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], cx)
 966        });
 967        assert_eq!(
 968            cx.update(|cx| syntax_chunks(0..2, &map, &theme, cx)),
 969            vec![
 970                ("fn ".to_string(), None),
 971                ("out".to_string(), Some(Color::blue())),
 972                ("".to_string(), None),
 973                ("  fn ".to_string(), Some(Color::red())),
 974                ("inner".to_string(), Some(Color::blue())),
 975                ("() {}\n}".to_string(), Some(Color::red())),
 976            ]
 977        );
 978    }
 979
 980    #[gpui::test]
 981    async fn test_chunks_with_soft_wrapping(cx: &mut gpui::TestAppContext) {
 982        use unindent::Unindent as _;
 983
 984        cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
 985
 986        let text = r#"
 987            fn outer() {}
 988
 989            mod module {
 990                fn inner() {}
 991            }"#
 992        .unindent();
 993
 994        let theme = SyntaxTheme::new(vec![
 995            ("mod.body".to_string(), Color::red().into()),
 996            ("fn.name".to_string(), Color::blue().into()),
 997        ]);
 998        let language = Arc::new(
 999            Language::new(
1000                LanguageConfig {
1001                    name: "Test".into(),
1002                    path_suffixes: vec![".test".to_string()],
1003                    ..Default::default()
1004                },
1005                Some(tree_sitter_rust::language()),
1006            )
1007            .with_highlights_query(
1008                r#"
1009                (mod_item name: (identifier) body: _ @mod.body)
1010                (function_item name: (identifier) @fn.name)
1011                "#,
1012            )
1013            .unwrap(),
1014        );
1015        language.set_theme(&theme);
1016
1017        let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
1018        buffer.condition(&cx, |buf, _| !buf.is_parsing()).await;
1019        let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
1020
1021        let font_cache = cx.font_cache();
1022
1023        let tab_size = 4;
1024        let family_id = font_cache.load_family(&["Courier"]).unwrap();
1025        let font_id = font_cache
1026            .select_font(family_id, &Default::default())
1027            .unwrap();
1028        let font_size = 16.0;
1029
1030        let map = cx.add_model(|cx| {
1031            DisplayMap::new(buffer, tab_size, font_id, font_size, Some(40.0), 1, 1, cx)
1032        });
1033        assert_eq!(
1034            cx.update(|cx| syntax_chunks(0..5, &map, &theme, cx)),
1035            [
1036                ("fn \n".to_string(), None),
1037                ("oute\nr".to_string(), Some(Color::blue())),
1038                ("() \n{}\n\n".to_string(), None),
1039            ]
1040        );
1041        assert_eq!(
1042            cx.update(|cx| syntax_chunks(3..5, &map, &theme, cx)),
1043            [("{}\n\n".to_string(), None)]
1044        );
1045
1046        map.update(cx, |map, cx| {
1047            map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], cx)
1048        });
1049        assert_eq!(
1050            cx.update(|cx| syntax_chunks(1..4, &map, &theme, cx)),
1051            [
1052                ("out".to_string(), Some(Color::blue())),
1053                ("\n".to_string(), None),
1054                ("  \nfn ".to_string(), Some(Color::red())),
1055                ("i\n".to_string(), Some(Color::blue()))
1056            ]
1057        );
1058    }
1059
1060    #[gpui::test]
1061    async fn test_chunks_with_text_highlights(cx: &mut gpui::TestAppContext) {
1062        cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
1063
1064        let theme = SyntaxTheme::new(vec![
1065            ("operator".to_string(), Color::red().into()),
1066            ("string".to_string(), Color::green().into()),
1067        ]);
1068        let language = Arc::new(
1069            Language::new(
1070                LanguageConfig {
1071                    name: "Test".into(),
1072                    path_suffixes: vec![".test".to_string()],
1073                    ..Default::default()
1074                },
1075                Some(tree_sitter_rust::language()),
1076            )
1077            .with_highlights_query(
1078                r#"
1079                ":" @operator
1080                (string_literal) @string
1081                "#,
1082            )
1083            .unwrap(),
1084        );
1085        language.set_theme(&theme);
1086
1087        let (text, highlighted_ranges) = marked_text_ranges(r#"const[] [a]: B = "c [d]""#);
1088
1089        let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
1090        buffer.condition(&cx, |buf, _| !buf.is_parsing()).await;
1091
1092        let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
1093        let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
1094
1095        let font_cache = cx.font_cache();
1096        let tab_size = 4;
1097        let family_id = font_cache.load_family(&["Courier"]).unwrap();
1098        let font_id = font_cache
1099            .select_font(family_id, &Default::default())
1100            .unwrap();
1101        let font_size = 16.0;
1102        let map = cx
1103            .add_model(|cx| DisplayMap::new(buffer, tab_size, font_id, font_size, None, 1, 1, cx));
1104
1105        enum MyType {}
1106
1107        let style = HighlightStyle {
1108            color: Some(Color::blue()),
1109            ..Default::default()
1110        };
1111
1112        map.update(cx, |map, _cx| {
1113            map.highlight_text(
1114                TypeId::of::<MyType>(),
1115                highlighted_ranges
1116                    .into_iter()
1117                    .map(|range| {
1118                        buffer_snapshot.anchor_before(range.start)
1119                            ..buffer_snapshot.anchor_before(range.end)
1120                    })
1121                    .collect(),
1122                style,
1123            );
1124        });
1125
1126        assert_eq!(
1127            cx.update(|cx| chunks(0..10, &map, &theme, cx)),
1128            [
1129                ("const ".to_string(), None, None),
1130                ("a".to_string(), None, Some(Color::blue())),
1131                (":".to_string(), Some(Color::red()), None),
1132                (" B = ".to_string(), None, None),
1133                ("\"c ".to_string(), Some(Color::green()), None),
1134                ("d".to_string(), Some(Color::green()), Some(Color::blue())),
1135                ("\"".to_string(), Some(Color::green()), None),
1136            ]
1137        );
1138    }
1139
1140    #[gpui::test]
1141    fn test_clip_point(cx: &mut gpui::MutableAppContext) {
1142        fn assert(text: &str, shift_right: bool, bias: Bias, cx: &mut gpui::MutableAppContext) {
1143            let (unmarked_snapshot, mut markers) = marked_display_snapshot(text, cx);
1144
1145            match bias {
1146                Bias::Left => {
1147                    if shift_right {
1148                        *markers[1].column_mut() += 1;
1149                    }
1150
1151                    assert_eq!(unmarked_snapshot.clip_point(markers[1], bias), markers[0])
1152                }
1153                Bias::Right => {
1154                    if shift_right {
1155                        *markers[0].column_mut() += 1;
1156                    }
1157
1158                    assert_eq!(
1159                        unmarked_snapshot.clip_point(dbg!(markers[0]), bias),
1160                        markers[1]
1161                    )
1162                }
1163            };
1164        }
1165
1166        use Bias::{Left, Right};
1167        assert("||α", false, Left, cx);
1168        assert("||α", true, Left, cx);
1169        assert("||α", false, Right, cx);
1170        assert("|α|", true, Right, cx);
1171        assert("||✋", false, Left, cx);
1172        assert("||✋", true, Left, cx);
1173        assert("||✋", false, Right, cx);
1174        assert("|✋|", true, Right, cx);
1175        assert("||🍐", false, Left, cx);
1176        assert("||🍐", true, Left, cx);
1177        assert("||🍐", false, Right, cx);
1178        assert("|🍐|", true, Right, cx);
1179        assert("||\t", false, Left, cx);
1180        assert("||\t", true, Left, cx);
1181        assert("||\t", false, Right, cx);
1182        assert("|\t|", true, Right, cx);
1183        assert(" ||\t", false, Left, cx);
1184        assert(" ||\t", true, Left, cx);
1185        assert(" ||\t", false, Right, cx);
1186        assert(" |\t|", true, Right, cx);
1187        assert("   ||\t", false, Left, cx);
1188        assert("   ||\t", false, Right, cx);
1189    }
1190
1191    #[gpui::test]
1192    fn test_clip_at_line_ends(cx: &mut gpui::MutableAppContext) {
1193        fn assert(text: &str, cx: &mut gpui::MutableAppContext) {
1194            let (mut unmarked_snapshot, markers) = marked_display_snapshot(text, cx);
1195            unmarked_snapshot.clip_at_line_ends = true;
1196            assert_eq!(
1197                unmarked_snapshot.clip_point(markers[1], Bias::Left),
1198                markers[0]
1199            );
1200        }
1201
1202        assert("||", cx);
1203        assert("|a|", cx);
1204        assert("a|b|", cx);
1205        assert("a|α|", cx);
1206    }
1207
1208    #[gpui::test]
1209    fn test_tabs_with_multibyte_chars(cx: &mut gpui::MutableAppContext) {
1210        let text = "\t\tα\nβ\t\n🏀β\t\tγ";
1211        let buffer = MultiBuffer::build_simple(text, cx);
1212        let tab_size = 4;
1213        let font_cache = cx.font_cache();
1214        let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
1215        let font_id = font_cache
1216            .select_font(family_id, &Default::default())
1217            .unwrap();
1218        let font_size = 14.0;
1219
1220        let map = cx.add_model(|cx| {
1221            DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, None, 1, 1, cx)
1222        });
1223        let map = map.update(cx, |map, cx| map.snapshot(cx));
1224        assert_eq!(map.text(), "✅       α\nβ   \n🏀β      γ");
1225        assert_eq!(
1226            map.text_chunks(0).collect::<String>(),
1227            "✅       α\nβ   \n🏀β      γ"
1228        );
1229        assert_eq!(map.text_chunks(1).collect::<String>(), "β   \n🏀β      γ");
1230        assert_eq!(map.text_chunks(2).collect::<String>(), "🏀β      γ");
1231
1232        let point = Point::new(0, "\t\t".len() as u32);
1233        let display_point = DisplayPoint::new(0, "".len() as u32);
1234        assert_eq!(point.to_display_point(&map), display_point);
1235        assert_eq!(display_point.to_point(&map), point);
1236
1237        let point = Point::new(1, "β\t".len() as u32);
1238        let display_point = DisplayPoint::new(1, "β   ".len() as u32);
1239        assert_eq!(point.to_display_point(&map), display_point);
1240        assert_eq!(display_point.to_point(&map), point,);
1241
1242        let point = Point::new(2, "🏀β\t\t".len() as u32);
1243        let display_point = DisplayPoint::new(2, "🏀β      ".len() as u32);
1244        assert_eq!(point.to_display_point(&map), display_point);
1245        assert_eq!(display_point.to_point(&map), point,);
1246
1247        // Display points inside of expanded tabs
1248        assert_eq!(
1249            DisplayPoint::new(0, "".len() as u32).to_point(&map),
1250            Point::new(0, "\t".len() as u32),
1251        );
1252        assert_eq!(
1253            DisplayPoint::new(0, "".len() as u32).to_point(&map),
1254            Point::new(0, "".len() as u32),
1255        );
1256
1257        // Clipping display points inside of multi-byte characters
1258        assert_eq!(
1259            map.clip_point(DisplayPoint::new(0, "".len() as u32 - 1), Left),
1260            DisplayPoint::new(0, 0)
1261        );
1262        assert_eq!(
1263            map.clip_point(DisplayPoint::new(0, "".len() as u32 - 1), Bias::Right),
1264            DisplayPoint::new(0, "".len() as u32)
1265        );
1266    }
1267
1268    #[gpui::test]
1269    fn test_max_point(cx: &mut gpui::MutableAppContext) {
1270        let buffer = MultiBuffer::build_simple("aaa\n\t\tbbb", cx);
1271        let tab_size = 4;
1272        let font_cache = cx.font_cache();
1273        let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
1274        let font_id = font_cache
1275            .select_font(family_id, &Default::default())
1276            .unwrap();
1277        let font_size = 14.0;
1278        let map = cx.add_model(|cx| {
1279            DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, None, 1, 1, cx)
1280        });
1281        assert_eq!(
1282            map.update(cx, |map, cx| map.snapshot(cx)).max_point(),
1283            DisplayPoint::new(1, 11)
1284        )
1285    }
1286
1287    fn syntax_chunks<'a>(
1288        rows: Range<u32>,
1289        map: &ModelHandle<DisplayMap>,
1290        theme: &'a SyntaxTheme,
1291        cx: &mut MutableAppContext,
1292    ) -> Vec<(String, Option<Color>)> {
1293        chunks(rows, map, theme, cx)
1294            .into_iter()
1295            .map(|(text, color, _)| (text, color))
1296            .collect()
1297    }
1298
1299    fn chunks<'a>(
1300        rows: Range<u32>,
1301        map: &ModelHandle<DisplayMap>,
1302        theme: &'a SyntaxTheme,
1303        cx: &mut MutableAppContext,
1304    ) -> Vec<(String, Option<Color>, Option<Color>)> {
1305        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1306        let mut chunks: Vec<(String, Option<Color>, Option<Color>)> = Vec::new();
1307        for chunk in snapshot.chunks(rows, true) {
1308            let syntax_color = chunk
1309                .syntax_highlight_id
1310                .and_then(|id| id.style(theme)?.color);
1311            let highlight_color = chunk.highlight_style.and_then(|style| style.color);
1312            if let Some((last_chunk, last_syntax_color, last_highlight_color)) = chunks.last_mut() {
1313                if syntax_color == *last_syntax_color && highlight_color == *last_highlight_color {
1314                    last_chunk.push_str(chunk.text);
1315                    continue;
1316                }
1317            }
1318            chunks.push((chunk.text.to_string(), syntax_color, highlight_color));
1319        }
1320        chunks
1321    }
1322}