display_map.rs

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