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