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.load_family(&["Helvetica"]).unwrap();
 819        let font_id = font_cache
 820            .select_font(family_id, &Default::default())
 821            .unwrap();
 822        let font_size = 14.0;
 823        let max_wrap_width = 300.0;
 824        let mut wrap_width = if rng.gen_bool(0.1) {
 825            None
 826        } else {
 827            Some(rng.gen_range(0.0..=max_wrap_width))
 828        };
 829
 830        log::info!("tab size: {}", tab_size);
 831        log::info!("wrap width: {:?}", wrap_width);
 832
 833        cx.update(|cx| {
 834            let mut settings = Settings::test(cx);
 835            settings.editor_overrides.tab_size = NonZeroU32::new(tab_size);
 836            cx.set_global(settings)
 837        });
 838
 839        let buffer = cx.update(|cx| {
 840            if rng.gen() {
 841                let len = rng.gen_range(0..10);
 842                let text = util::RandomCharIter::new(&mut rng)
 843                    .take(len)
 844                    .collect::<String>();
 845                MultiBuffer::build_simple(&text, cx)
 846            } else {
 847                MultiBuffer::build_random(&mut rng, cx)
 848            }
 849        });
 850
 851        let map = cx.add_model(|cx| {
 852            DisplayMap::new(
 853                buffer.clone(),
 854                font_id,
 855                font_size,
 856                wrap_width,
 857                buffer_start_excerpt_header_height,
 858                excerpt_header_height,
 859                cx,
 860            )
 861        });
 862        let mut notifications = observe(&map, cx);
 863        let mut fold_count = 0;
 864        let mut blocks = Vec::new();
 865
 866        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
 867        log::info!("buffer text: {:?}", snapshot.buffer_snapshot.text());
 868        log::info!("fold text: {:?}", snapshot.folds_snapshot.text());
 869        log::info!("tab text: {:?}", snapshot.tabs_snapshot.text());
 870        log::info!("wrap text: {:?}", snapshot.wraps_snapshot.text());
 871        log::info!("block text: {:?}", snapshot.blocks_snapshot.text());
 872        log::info!("display text: {:?}", snapshot.text());
 873
 874        for _i in 0..operations {
 875            match rng.gen_range(0..100) {
 876                0..=19 => {
 877                    wrap_width = if rng.gen_bool(0.2) {
 878                        None
 879                    } else {
 880                        Some(rng.gen_range(0.0..=max_wrap_width))
 881                    };
 882                    log::info!("setting wrap width to {:?}", wrap_width);
 883                    map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
 884                }
 885                20..=29 => {
 886                    let mut tab_sizes = vec![1, 2, 3, 4];
 887                    tab_sizes.remove((tab_size - 1) as usize);
 888                    tab_size = *tab_sizes.choose(&mut rng).unwrap();
 889                    log::info!("setting tab size to {:?}", tab_size);
 890                    cx.update(|cx| {
 891                        let mut settings = Settings::test(cx);
 892                        settings.editor_overrides.tab_size = NonZeroU32::new(tab_size);
 893                        cx.set_global(settings)
 894                    });
 895                }
 896                30..=44 => {
 897                    map.update(cx, |map, cx| {
 898                        if rng.gen() || blocks.is_empty() {
 899                            let buffer = map.snapshot(cx).buffer_snapshot;
 900                            let block_properties = (0..rng.gen_range(1..=1))
 901                                .map(|_| {
 902                                    let position =
 903                                        buffer.anchor_after(buffer.clip_offset(
 904                                            rng.gen_range(0..=buffer.len()),
 905                                            Bias::Left,
 906                                        ));
 907
 908                                    let disposition = if rng.gen() {
 909                                        BlockDisposition::Above
 910                                    } else {
 911                                        BlockDisposition::Below
 912                                    };
 913                                    let height = rng.gen_range(1..5);
 914                                    log::info!(
 915                                        "inserting block {:?} {:?} with height {}",
 916                                        disposition,
 917                                        position.to_point(&buffer),
 918                                        height
 919                                    );
 920                                    BlockProperties {
 921                                        style: BlockStyle::Fixed,
 922                                        position,
 923                                        height,
 924                                        disposition,
 925                                        render: Arc::new(|_| Empty::new().boxed()),
 926                                    }
 927                                })
 928                                .collect::<Vec<_>>();
 929                            blocks.extend(map.insert_blocks(block_properties, cx));
 930                        } else {
 931                            blocks.shuffle(&mut rng);
 932                            let remove_count = rng.gen_range(1..=4.min(blocks.len()));
 933                            let block_ids_to_remove = (0..remove_count)
 934                                .map(|_| blocks.remove(rng.gen_range(0..blocks.len())))
 935                                .collect();
 936                            log::info!("removing block ids {:?}", block_ids_to_remove);
 937                            map.remove_blocks(block_ids_to_remove, cx);
 938                        }
 939                    });
 940                }
 941                45..=79 => {
 942                    let mut ranges = Vec::new();
 943                    for _ in 0..rng.gen_range(1..=3) {
 944                        buffer.read_with(cx, |buffer, cx| {
 945                            let buffer = buffer.read(cx);
 946                            let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right);
 947                            let start = buffer.clip_offset(rng.gen_range(0..=end), Left);
 948                            ranges.push(start..end);
 949                        });
 950                    }
 951
 952                    if rng.gen() && fold_count > 0 {
 953                        log::info!("unfolding ranges: {:?}", ranges);
 954                        map.update(cx, |map, cx| {
 955                            map.unfold(ranges, true, cx);
 956                        });
 957                    } else {
 958                        log::info!("folding ranges: {:?}", ranges);
 959                        map.update(cx, |map, cx| {
 960                            map.fold(ranges, cx);
 961                        });
 962                    }
 963                }
 964                _ => {
 965                    buffer.update(cx, |buffer, cx| buffer.randomly_mutate(&mut rng, 5, cx));
 966                }
 967            }
 968
 969            if map.read_with(cx, |map, cx| map.is_rewrapping(cx)) {
 970                notifications.next().await.unwrap();
 971            }
 972
 973            let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
 974            fold_count = snapshot.fold_count();
 975            log::info!("buffer text: {:?}", snapshot.buffer_snapshot.text());
 976            log::info!("fold text: {:?}", snapshot.folds_snapshot.text());
 977            log::info!("tab text: {:?}", snapshot.tabs_snapshot.text());
 978            log::info!("wrap text: {:?}", snapshot.wraps_snapshot.text());
 979            log::info!("block text: {:?}", snapshot.blocks_snapshot.text());
 980            log::info!("display text: {:?}", snapshot.text());
 981
 982            // Line boundaries
 983            let buffer = &snapshot.buffer_snapshot;
 984            for _ in 0..5 {
 985                let row = rng.gen_range(0..=buffer.max_point().row);
 986                let column = rng.gen_range(0..=buffer.line_len(row));
 987                let point = buffer.clip_point(Point::new(row, column), Left);
 988
 989                let (prev_buffer_bound, prev_display_bound) = snapshot.prev_line_boundary(point);
 990                let (next_buffer_bound, next_display_bound) = snapshot.next_line_boundary(point);
 991
 992                assert!(prev_buffer_bound <= point);
 993                assert!(next_buffer_bound >= point);
 994                assert_eq!(prev_buffer_bound.column, 0);
 995                assert_eq!(prev_display_bound.column(), 0);
 996                if next_buffer_bound < buffer.max_point() {
 997                    assert_eq!(buffer.chars_at(next_buffer_bound).next(), Some('\n'));
 998                }
 999
1000                assert_eq!(
1001                    prev_display_bound,
1002                    prev_buffer_bound.to_display_point(&snapshot),
1003                    "row boundary before {:?}. reported buffer row boundary: {:?}",
1004                    point,
1005                    prev_buffer_bound
1006                );
1007                assert_eq!(
1008                    next_display_bound,
1009                    next_buffer_bound.to_display_point(&snapshot),
1010                    "display row boundary after {:?}. reported buffer row boundary: {:?}",
1011                    point,
1012                    next_buffer_bound
1013                );
1014                assert_eq!(
1015                    prev_buffer_bound,
1016                    prev_display_bound.to_point(&snapshot),
1017                    "row boundary before {:?}. reported display row boundary: {:?}",
1018                    point,
1019                    prev_display_bound
1020                );
1021                assert_eq!(
1022                    next_buffer_bound,
1023                    next_display_bound.to_point(&snapshot),
1024                    "row boundary after {:?}. reported display row boundary: {:?}",
1025                    point,
1026                    next_display_bound
1027                );
1028            }
1029
1030            // Movement
1031            let min_point = snapshot.clip_point(DisplayPoint::new(0, 0), Left);
1032            let max_point = snapshot.clip_point(snapshot.max_point(), Right);
1033            for _ in 0..5 {
1034                let row = rng.gen_range(0..=snapshot.max_point().row());
1035                let column = rng.gen_range(0..=snapshot.line_len(row));
1036                let point = snapshot.clip_point(DisplayPoint::new(row, column), Left);
1037
1038                log::info!("Moving from point {:?}", point);
1039
1040                let moved_right = movement::right(&snapshot, point);
1041                log::info!("Right {:?}", moved_right);
1042                if point < max_point {
1043                    assert!(moved_right > point);
1044                    if point.column() == snapshot.line_len(point.row())
1045                        || snapshot.soft_wrap_indent(point.row()).is_some()
1046                            && point.column() == snapshot.line_len(point.row()) - 1
1047                    {
1048                        assert!(moved_right.row() > point.row());
1049                    }
1050                } else {
1051                    assert_eq!(moved_right, point);
1052                }
1053
1054                let moved_left = movement::left(&snapshot, point);
1055                log::info!("Left {:?}", moved_left);
1056                if point > min_point {
1057                    assert!(moved_left < point);
1058                    if point.column() == 0 {
1059                        assert!(moved_left.row() < point.row());
1060                    }
1061                } else {
1062                    assert_eq!(moved_left, point);
1063                }
1064            }
1065        }
1066    }
1067
1068    #[gpui::test(retries = 5)]
1069    fn test_soft_wraps(cx: &mut MutableAppContext) {
1070        cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
1071        cx.foreground().forbid_parking();
1072
1073        let font_cache = cx.font_cache();
1074
1075        let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
1076        let font_id = font_cache
1077            .select_font(family_id, &Default::default())
1078            .unwrap();
1079        let font_size = 12.0;
1080        let wrap_width = Some(64.);
1081        cx.set_global(Settings::test(cx));
1082
1083        let text = "one two three four five\nsix seven eight";
1084        let buffer = MultiBuffer::build_simple(text, cx);
1085        let map = cx.add_model(|cx| {
1086            DisplayMap::new(buffer.clone(), font_id, font_size, wrap_width, 1, 1, cx)
1087        });
1088
1089        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1090        assert_eq!(
1091            snapshot.text_chunks(0).collect::<String>(),
1092            "one two \nthree four \nfive\nsix seven \neight"
1093        );
1094        assert_eq!(
1095            snapshot.clip_point(DisplayPoint::new(0, 8), Bias::Left),
1096            DisplayPoint::new(0, 7)
1097        );
1098        assert_eq!(
1099            snapshot.clip_point(DisplayPoint::new(0, 8), Bias::Right),
1100            DisplayPoint::new(1, 0)
1101        );
1102        assert_eq!(
1103            movement::right(&snapshot, DisplayPoint::new(0, 7)),
1104            DisplayPoint::new(1, 0)
1105        );
1106        assert_eq!(
1107            movement::left(&snapshot, DisplayPoint::new(1, 0)),
1108            DisplayPoint::new(0, 7)
1109        );
1110        assert_eq!(
1111            movement::up(
1112                &snapshot,
1113                DisplayPoint::new(1, 10),
1114                SelectionGoal::None,
1115                false
1116            ),
1117            (DisplayPoint::new(0, 7), SelectionGoal::Column(10))
1118        );
1119        assert_eq!(
1120            movement::down(
1121                &snapshot,
1122                DisplayPoint::new(0, 7),
1123                SelectionGoal::Column(10),
1124                false
1125            ),
1126            (DisplayPoint::new(1, 10), SelectionGoal::Column(10))
1127        );
1128        assert_eq!(
1129            movement::down(
1130                &snapshot,
1131                DisplayPoint::new(1, 10),
1132                SelectionGoal::Column(10),
1133                false
1134            ),
1135            (DisplayPoint::new(2, 4), SelectionGoal::Column(10))
1136        );
1137
1138        let ix = snapshot.buffer_snapshot.text().find("seven").unwrap();
1139        buffer.update(cx, |buffer, cx| {
1140            buffer.edit([(ix..ix, "and ")], None, cx);
1141        });
1142
1143        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1144        assert_eq!(
1145            snapshot.text_chunks(1).collect::<String>(),
1146            "three four \nfive\nsix and \nseven eight"
1147        );
1148
1149        // Re-wrap on font size changes
1150        map.update(cx, |map, cx| map.set_font(font_id, font_size + 3., cx));
1151
1152        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1153        assert_eq!(
1154            snapshot.text_chunks(1).collect::<String>(),
1155            "three \nfour five\nsix and \nseven \neight"
1156        )
1157    }
1158
1159    #[gpui::test]
1160    fn test_text_chunks(cx: &mut gpui::MutableAppContext) {
1161        cx.set_global(Settings::test(cx));
1162        let text = sample_text(6, 6, 'a');
1163        let buffer = MultiBuffer::build_simple(&text, cx);
1164        let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap();
1165        let font_id = cx
1166            .font_cache()
1167            .select_font(family_id, &Default::default())
1168            .unwrap();
1169        let font_size = 14.0;
1170        let map =
1171            cx.add_model(|cx| DisplayMap::new(buffer.clone(), font_id, font_size, None, 1, 1, cx));
1172        buffer.update(cx, |buffer, cx| {
1173            buffer.edit(
1174                vec![
1175                    (Point::new(1, 0)..Point::new(1, 0), "\t"),
1176                    (Point::new(1, 1)..Point::new(1, 1), "\t"),
1177                    (Point::new(2, 1)..Point::new(2, 1), "\t"),
1178                ],
1179                None,
1180                cx,
1181            )
1182        });
1183
1184        assert_eq!(
1185            map.update(cx, |map, cx| map.snapshot(cx))
1186                .text_chunks(1)
1187                .collect::<String>()
1188                .lines()
1189                .next(),
1190            Some("    b   bbbbb")
1191        );
1192        assert_eq!(
1193            map.update(cx, |map, cx| map.snapshot(cx))
1194                .text_chunks(2)
1195                .collect::<String>()
1196                .lines()
1197                .next(),
1198            Some("c   ccccc")
1199        );
1200    }
1201
1202    #[gpui::test]
1203    async fn test_chunks(cx: &mut gpui::TestAppContext) {
1204        use unindent::Unindent as _;
1205
1206        let text = r#"
1207            fn outer() {}
1208
1209            mod module {
1210                fn inner() {}
1211            }"#
1212        .unindent();
1213
1214        let theme = SyntaxTheme::new(vec![
1215            ("mod.body".to_string(), Color::red().into()),
1216            ("fn.name".to_string(), Color::blue().into()),
1217        ]);
1218        let language = Arc::new(
1219            Language::new(
1220                LanguageConfig {
1221                    name: "Test".into(),
1222                    path_suffixes: vec![".test".to_string()],
1223                    ..Default::default()
1224                },
1225                Some(tree_sitter_rust::language()),
1226            )
1227            .with_highlights_query(
1228                r#"
1229                (mod_item name: (identifier) body: _ @mod.body)
1230                (function_item name: (identifier) @fn.name)
1231                "#,
1232            )
1233            .unwrap(),
1234        );
1235        language.set_theme(&theme);
1236        cx.update(|cx| {
1237            let mut settings = Settings::test(cx);
1238            settings.editor_defaults.tab_size = Some(2.try_into().unwrap());
1239            cx.set_global(settings);
1240        });
1241
1242        let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
1243        buffer.condition(cx, |buf, _| !buf.is_parsing()).await;
1244        let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
1245
1246        let font_cache = cx.font_cache();
1247        let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
1248        let font_id = font_cache
1249            .select_font(family_id, &Default::default())
1250            .unwrap();
1251        let font_size = 14.0;
1252
1253        let map = cx.add_model(|cx| DisplayMap::new(buffer, font_id, font_size, None, 1, 1, cx));
1254        assert_eq!(
1255            cx.update(|cx| syntax_chunks(0..5, &map, &theme, cx)),
1256            vec![
1257                ("fn ".to_string(), None),
1258                ("outer".to_string(), Some(Color::blue())),
1259                ("() {}\n\nmod module ".to_string(), None),
1260                ("{\n    fn ".to_string(), Some(Color::red())),
1261                ("inner".to_string(), Some(Color::blue())),
1262                ("() {}\n}".to_string(), Some(Color::red())),
1263            ]
1264        );
1265        assert_eq!(
1266            cx.update(|cx| syntax_chunks(3..5, &map, &theme, cx)),
1267            vec![
1268                ("    fn ".to_string(), Some(Color::red())),
1269                ("inner".to_string(), Some(Color::blue())),
1270                ("() {}\n}".to_string(), Some(Color::red())),
1271            ]
1272        );
1273
1274        map.update(cx, |map, cx| {
1275            map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], cx)
1276        });
1277        assert_eq!(
1278            cx.update(|cx| syntax_chunks(0..2, &map, &theme, cx)),
1279            vec![
1280                ("fn ".to_string(), None),
1281                ("out".to_string(), Some(Color::blue())),
1282                ("".to_string(), None),
1283                ("  fn ".to_string(), Some(Color::red())),
1284                ("inner".to_string(), Some(Color::blue())),
1285                ("() {}\n}".to_string(), Some(Color::red())),
1286            ]
1287        );
1288    }
1289
1290    #[gpui::test]
1291    async fn test_chunks_with_soft_wrapping(cx: &mut gpui::TestAppContext) {
1292        use unindent::Unindent as _;
1293
1294        cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
1295
1296        let text = r#"
1297            fn outer() {}
1298
1299            mod module {
1300                fn inner() {}
1301            }"#
1302        .unindent();
1303
1304        let theme = SyntaxTheme::new(vec![
1305            ("mod.body".to_string(), Color::red().into()),
1306            ("fn.name".to_string(), Color::blue().into()),
1307        ]);
1308        let language = Arc::new(
1309            Language::new(
1310                LanguageConfig {
1311                    name: "Test".into(),
1312                    path_suffixes: vec![".test".to_string()],
1313                    ..Default::default()
1314                },
1315                Some(tree_sitter_rust::language()),
1316            )
1317            .with_highlights_query(
1318                r#"
1319                (mod_item name: (identifier) body: _ @mod.body)
1320                (function_item name: (identifier) @fn.name)
1321                "#,
1322            )
1323            .unwrap(),
1324        );
1325        language.set_theme(&theme);
1326
1327        cx.update(|cx| cx.set_global(Settings::test(cx)));
1328
1329        let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
1330        buffer.condition(cx, |buf, _| !buf.is_parsing()).await;
1331        let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
1332
1333        let font_cache = cx.font_cache();
1334
1335        let family_id = font_cache.load_family(&["Courier"]).unwrap();
1336        let font_id = font_cache
1337            .select_font(family_id, &Default::default())
1338            .unwrap();
1339        let font_size = 16.0;
1340
1341        let map =
1342            cx.add_model(|cx| DisplayMap::new(buffer, font_id, font_size, Some(40.0), 1, 1, cx));
1343        assert_eq!(
1344            cx.update(|cx| syntax_chunks(0..5, &map, &theme, cx)),
1345            [
1346                ("fn \n".to_string(), None),
1347                ("oute\nr".to_string(), Some(Color::blue())),
1348                ("() \n{}\n\n".to_string(), None),
1349            ]
1350        );
1351        assert_eq!(
1352            cx.update(|cx| syntax_chunks(3..5, &map, &theme, cx)),
1353            [("{}\n\n".to_string(), None)]
1354        );
1355
1356        map.update(cx, |map, cx| {
1357            map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], cx)
1358        });
1359        assert_eq!(
1360            cx.update(|cx| syntax_chunks(1..4, &map, &theme, cx)),
1361            [
1362                ("out".to_string(), Some(Color::blue())),
1363                ("\n".to_string(), None),
1364                ("  \nfn ".to_string(), Some(Color::red())),
1365                ("i\n".to_string(), Some(Color::blue()))
1366            ]
1367        );
1368    }
1369
1370    #[gpui::test]
1371    async fn test_chunks_with_text_highlights(cx: &mut gpui::TestAppContext) {
1372        cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
1373
1374        cx.update(|cx| cx.set_global(Settings::test(cx)));
1375        let theme = SyntaxTheme::new(vec![
1376            ("operator".to_string(), Color::red().into()),
1377            ("string".to_string(), Color::green().into()),
1378        ]);
1379        let language = Arc::new(
1380            Language::new(
1381                LanguageConfig {
1382                    name: "Test".into(),
1383                    path_suffixes: vec![".test".to_string()],
1384                    ..Default::default()
1385                },
1386                Some(tree_sitter_rust::language()),
1387            )
1388            .with_highlights_query(
1389                r#"
1390                ":" @operator
1391                (string_literal) @string
1392                "#,
1393            )
1394            .unwrap(),
1395        );
1396        language.set_theme(&theme);
1397
1398        let (text, highlighted_ranges) = marked_text_ranges(r#"constˇ «a»: B = "c «d»""#, false);
1399
1400        let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
1401        buffer.condition(cx, |buf, _| !buf.is_parsing()).await;
1402
1403        let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
1404        let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
1405
1406        let font_cache = cx.font_cache();
1407        let family_id = font_cache.load_family(&["Courier"]).unwrap();
1408        let font_id = font_cache
1409            .select_font(family_id, &Default::default())
1410            .unwrap();
1411        let font_size = 16.0;
1412        let map = cx.add_model(|cx| DisplayMap::new(buffer, font_id, font_size, None, 1, 1, cx));
1413
1414        enum MyType {}
1415
1416        let style = HighlightStyle {
1417            color: Some(Color::blue()),
1418            ..Default::default()
1419        };
1420
1421        map.update(cx, |map, _cx| {
1422            map.highlight_text(
1423                TypeId::of::<MyType>(),
1424                highlighted_ranges
1425                    .into_iter()
1426                    .map(|range| {
1427                        buffer_snapshot.anchor_before(range.start)
1428                            ..buffer_snapshot.anchor_before(range.end)
1429                    })
1430                    .collect(),
1431                style,
1432            );
1433        });
1434
1435        assert_eq!(
1436            cx.update(|cx| chunks(0..10, &map, &theme, cx)),
1437            [
1438                ("const ".to_string(), None, None),
1439                ("a".to_string(), None, Some(Color::blue())),
1440                (":".to_string(), Some(Color::red()), None),
1441                (" B = ".to_string(), None, None),
1442                ("\"c ".to_string(), Some(Color::green()), None),
1443                ("d".to_string(), Some(Color::green()), Some(Color::blue())),
1444                ("\"".to_string(), Some(Color::green()), None),
1445            ]
1446        );
1447    }
1448
1449    #[gpui::test]
1450    fn test_clip_point(cx: &mut gpui::MutableAppContext) {
1451        cx.set_global(Settings::test(cx));
1452        fn assert(text: &str, shift_right: bool, bias: Bias, cx: &mut gpui::MutableAppContext) {
1453            let (unmarked_snapshot, mut markers) = marked_display_snapshot(text, cx);
1454
1455            match bias {
1456                Bias::Left => {
1457                    if shift_right {
1458                        *markers[1].column_mut() += 1;
1459                    }
1460
1461                    assert_eq!(unmarked_snapshot.clip_point(markers[1], bias), markers[0])
1462                }
1463                Bias::Right => {
1464                    if shift_right {
1465                        *markers[0].column_mut() += 1;
1466                    }
1467
1468                    assert_eq!(unmarked_snapshot.clip_point(markers[0], bias), markers[1])
1469                }
1470            };
1471        }
1472
1473        use Bias::{Left, Right};
1474        assert("ˇˇα", false, Left, cx);
1475        assert("ˇˇα", true, Left, cx);
1476        assert("ˇˇα", false, Right, cx);
1477        assert("ˇαˇ", true, Right, cx);
1478        assert("ˇˇ✋", false, Left, cx);
1479        assert("ˇˇ✋", true, Left, cx);
1480        assert("ˇˇ✋", false, Right, cx);
1481        assert("ˇ✋ˇ", true, Right, cx);
1482        assert("ˇˇ🍐", false, Left, cx);
1483        assert("ˇˇ🍐", true, Left, cx);
1484        assert("ˇˇ🍐", false, Right, cx);
1485        assert("ˇ🍐ˇ", true, Right, cx);
1486        assert("ˇˇ\t", false, Left, cx);
1487        assert("ˇˇ\t", true, Left, cx);
1488        assert("ˇˇ\t", false, Right, cx);
1489        assert("ˇ\tˇ", true, Right, cx);
1490        assert(" ˇˇ\t", false, Left, cx);
1491        assert(" ˇˇ\t", true, Left, cx);
1492        assert(" ˇˇ\t", false, Right, cx);
1493        assert(" ˇ\tˇ", true, Right, cx);
1494        assert("   ˇˇ\t", false, Left, cx);
1495        assert("   ˇˇ\t", false, Right, cx);
1496    }
1497
1498    #[gpui::test]
1499    fn test_clip_at_line_ends(cx: &mut gpui::MutableAppContext) {
1500        cx.set_global(Settings::test(cx));
1501
1502        fn assert(text: &str, cx: &mut gpui::MutableAppContext) {
1503            let (mut unmarked_snapshot, markers) = marked_display_snapshot(text, cx);
1504            unmarked_snapshot.clip_at_line_ends = true;
1505            assert_eq!(
1506                unmarked_snapshot.clip_point(markers[1], Bias::Left),
1507                markers[0]
1508            );
1509        }
1510
1511        assert("ˇˇ", cx);
1512        assert("ˇaˇ", cx);
1513        assert("aˇbˇ", cx);
1514        assert("aˇαˇ", cx);
1515    }
1516
1517    #[gpui::test]
1518    fn test_tabs_with_multibyte_chars(cx: &mut gpui::MutableAppContext) {
1519        cx.set_global(Settings::test(cx));
1520        let text = "\t\tα\nβ\t\n🏀β\t\tγ";
1521        let buffer = MultiBuffer::build_simple(text, cx);
1522        let font_cache = cx.font_cache();
1523        let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
1524        let font_id = font_cache
1525            .select_font(family_id, &Default::default())
1526            .unwrap();
1527        let font_size = 14.0;
1528
1529        let map =
1530            cx.add_model(|cx| DisplayMap::new(buffer.clone(), font_id, font_size, None, 1, 1, cx));
1531        let map = map.update(cx, |map, cx| map.snapshot(cx));
1532        assert_eq!(map.text(), "✅       α\nβ   \n🏀β      γ");
1533        assert_eq!(
1534            map.text_chunks(0).collect::<String>(),
1535            "✅       α\nβ   \n🏀β      γ"
1536        );
1537        assert_eq!(map.text_chunks(1).collect::<String>(), "β   \n🏀β      γ");
1538        assert_eq!(map.text_chunks(2).collect::<String>(), "🏀β      γ");
1539
1540        let point = Point::new(0, "\t\t".len() as u32);
1541        let display_point = DisplayPoint::new(0, "".len() as u32);
1542        assert_eq!(point.to_display_point(&map), display_point);
1543        assert_eq!(display_point.to_point(&map), point);
1544
1545        let point = Point::new(1, "β\t".len() as u32);
1546        let display_point = DisplayPoint::new(1, "β   ".len() as u32);
1547        assert_eq!(point.to_display_point(&map), display_point);
1548        assert_eq!(display_point.to_point(&map), point,);
1549
1550        let point = Point::new(2, "🏀β\t\t".len() as u32);
1551        let display_point = DisplayPoint::new(2, "🏀β      ".len() as u32);
1552        assert_eq!(point.to_display_point(&map), display_point);
1553        assert_eq!(display_point.to_point(&map), point,);
1554
1555        // Display points inside of expanded tabs
1556        assert_eq!(
1557            DisplayPoint::new(0, "".len() as u32).to_point(&map),
1558            Point::new(0, "\t".len() as u32),
1559        );
1560        assert_eq!(
1561            DisplayPoint::new(0, "".len() as u32).to_point(&map),
1562            Point::new(0, "".len() as u32),
1563        );
1564
1565        // Clipping display points inside of multi-byte characters
1566        assert_eq!(
1567            map.clip_point(DisplayPoint::new(0, "".len() as u32 - 1), Left),
1568            DisplayPoint::new(0, 0)
1569        );
1570        assert_eq!(
1571            map.clip_point(DisplayPoint::new(0, "".len() as u32 - 1), Bias::Right),
1572            DisplayPoint::new(0, "".len() as u32)
1573        );
1574    }
1575
1576    #[gpui::test]
1577    fn test_max_point(cx: &mut gpui::MutableAppContext) {
1578        cx.set_global(Settings::test(cx));
1579        let buffer = MultiBuffer::build_simple("aaa\n\t\tbbb", cx);
1580        let font_cache = cx.font_cache();
1581        let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
1582        let font_id = font_cache
1583            .select_font(family_id, &Default::default())
1584            .unwrap();
1585        let font_size = 14.0;
1586        let map =
1587            cx.add_model(|cx| DisplayMap::new(buffer.clone(), font_id, font_size, None, 1, 1, cx));
1588        assert_eq!(
1589            map.update(cx, |map, cx| map.snapshot(cx)).max_point(),
1590            DisplayPoint::new(1, 11)
1591        )
1592    }
1593
1594    #[test]
1595    fn test_find_internal() {
1596        assert("This is a ˇtest of find internal", "test");
1597        assert("Some text ˇaˇaˇaa with repeated characters", "aa");
1598
1599        fn assert(marked_text: &str, target: &str) {
1600            let (text, expected_offsets) = marked_text_offsets(marked_text);
1601
1602            let chars = text
1603                .chars()
1604                .enumerate()
1605                .map(|(index, ch)| (ch, DisplayPoint::new(0, index as u32)));
1606            let target = target.chars();
1607
1608            assert_eq!(
1609                expected_offsets
1610                    .into_iter()
1611                    .map(|offset| offset as u32)
1612                    .collect::<Vec<_>>(),
1613                DisplaySnapshot::find_internal(chars, target.collect(), |_, _| true)
1614                    .map(|point| point.column())
1615                    .collect::<Vec<_>>()
1616            )
1617        }
1618    }
1619
1620    fn syntax_chunks<'a>(
1621        rows: Range<u32>,
1622        map: &ModelHandle<DisplayMap>,
1623        theme: &'a SyntaxTheme,
1624        cx: &mut MutableAppContext,
1625    ) -> Vec<(String, Option<Color>)> {
1626        chunks(rows, map, theme, cx)
1627            .into_iter()
1628            .map(|(text, color, _)| (text, color))
1629            .collect()
1630    }
1631
1632    fn chunks<'a>(
1633        rows: Range<u32>,
1634        map: &ModelHandle<DisplayMap>,
1635        theme: &'a SyntaxTheme,
1636        cx: &mut MutableAppContext,
1637    ) -> Vec<(String, Option<Color>, Option<Color>)> {
1638        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1639        let mut chunks: Vec<(String, Option<Color>, Option<Color>)> = Vec::new();
1640        for chunk in snapshot.chunks(rows, true) {
1641            let syntax_color = chunk
1642                .syntax_highlight_id
1643                .and_then(|id| id.style(theme)?.color);
1644            let highlight_color = chunk.highlight_style.and_then(|style| style.color);
1645            if let Some((last_chunk, last_syntax_color, last_highlight_color)) = chunks.last_mut() {
1646                if syntax_color == *last_syntax_color && highlight_color == *last_highlight_color {
1647                    last_chunk.push_str(chunk.text);
1648                    continue;
1649                }
1650            }
1651            chunks.push((chunk.text.to_string(), syntax_color, highlight_color));
1652        }
1653        chunks
1654    }
1655}