display_map.rs

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