display_map.rs

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