display_map.rs

   1//! This module defines where the text should be displayed in an [`Editor`][Editor].
   2//!
   3//! Not literally though - rendering, layout and all that jazz is a responsibility of [`EditorElement`][EditorElement].
   4//! Instead, [`DisplayMap`] decides where Inlays/Inlay hints are displayed, when
   5//! to apply a soft wrap, where to add fold indicators, whether there are any tabs in the buffer that
   6//! we display as spaces and where to display custom blocks (like diagnostics).
   7//! Seems like a lot? That's because it is. [`DisplayMap`] is conceptually made up
   8//! of several smaller structures that form a hierarchy (starting at the bottom):
   9//! - [`InlayMap`] that decides where the [`Inlay`]s should be displayed.
  10//! - [`FoldMap`] that decides where the fold indicators should be; it also tracks parts of a source file that are currently folded.
  11//! - [`TabMap`] that keeps track of hard tabs in a buffer.
  12//! - [`WrapMap`] that handles soft wrapping.
  13//! - [`BlockMap`] that tracks custom blocks such as diagnostics that should be displayed within buffer.
  14//! - [`DisplayMap`] that adds background highlights to the regions of text.
  15//! Each one of those builds on top of preceding map.
  16//!
  17//! [Editor]: crate::Editor
  18//! [EditorElement]: crate::element::EditorElement
  19
  20mod block_map;
  21mod flap_map;
  22mod fold_map;
  23mod inlay_map;
  24mod tab_map;
  25mod wrap_map;
  26
  27use crate::{
  28    hover_links::InlayHighlight, movement::TextLayoutDetails, EditorStyle, InlayId, RowExt,
  29};
  30pub use block_map::{
  31    BlockBufferRows, BlockChunks as DisplayChunks, BlockContext, BlockDisposition, BlockId,
  32    BlockMap, BlockPoint, BlockProperties, BlockStyle, RenderBlock, TransformBlock,
  33};
  34use block_map::{BlockRow, BlockSnapshot};
  35use collections::{HashMap, HashSet};
  36pub use flap_map::*;
  37pub use fold_map::{Fold, FoldId, FoldPlaceholder, FoldPoint};
  38use fold_map::{FoldMap, FoldSnapshot};
  39use gpui::{
  40    AnyElement, Font, HighlightStyle, LineLayout, Model, ModelContext, Pixels, UnderlineStyle,
  41};
  42pub(crate) use inlay_map::Inlay;
  43use inlay_map::{InlayMap, InlaySnapshot};
  44pub use inlay_map::{InlayOffset, InlayPoint};
  45use language::{
  46    language_settings::language_settings, ChunkRenderer, OffsetUtf16, Point,
  47    Subscription as BufferSubscription,
  48};
  49use lsp::DiagnosticSeverity;
  50use multi_buffer::{
  51    Anchor, AnchorRangeExt, MultiBuffer, MultiBufferPoint, MultiBufferRow, MultiBufferSnapshot,
  52    ToOffset, ToPoint,
  53};
  54use serde::Deserialize;
  55use std::ops::Add;
  56use std::{any::TypeId, borrow::Cow, fmt::Debug, num::NonZeroU32, ops::Range, sync::Arc};
  57use sum_tree::{Bias, TreeMap};
  58use tab_map::{TabMap, TabSnapshot};
  59use text::LineIndent;
  60use ui::WindowContext;
  61use wrap_map::{WrapMap, WrapSnapshot};
  62
  63#[derive(Copy, Clone, Debug, PartialEq, Eq)]
  64pub enum FoldStatus {
  65    Folded,
  66    Foldable,
  67}
  68
  69pub type RenderFoldToggle = Arc<dyn Fn(FoldStatus, &mut WindowContext) -> AnyElement>;
  70
  71const UNNECESSARY_CODE_FADE: f32 = 0.3;
  72
  73pub trait ToDisplayPoint {
  74    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint;
  75}
  76
  77type TextHighlights = TreeMap<Option<TypeId>, Arc<(HighlightStyle, Vec<Range<Anchor>>)>>;
  78type InlayHighlights = TreeMap<TypeId, TreeMap<InlayId, (HighlightStyle, InlayHighlight)>>;
  79
  80/// Decides how text in a [`MultiBuffer`] should be displayed in a buffer, handling inlay hints,
  81/// folding, hard tabs, soft wrapping, custom blocks (like diagnostics), and highlighting.
  82///
  83/// See the [module level documentation](self) for more information.
  84pub struct DisplayMap {
  85    /// The buffer that we are displaying.
  86    buffer: Model<MultiBuffer>,
  87    buffer_subscription: BufferSubscription,
  88    /// Decides where the [`Inlay`]s should be displayed.
  89    inlay_map: InlayMap,
  90    /// Decides where the fold indicators should be and tracks parts of a source file that are currently folded.
  91    fold_map: FoldMap,
  92    /// Keeps track of hard tabs in a buffer.
  93    tab_map: TabMap,
  94    /// Handles soft wrapping.
  95    wrap_map: Model<WrapMap>,
  96    /// Tracks custom blocks such as diagnostics that should be displayed within buffer.
  97    block_map: BlockMap,
  98    /// Regions of text that should be highlighted.
  99    text_highlights: TextHighlights,
 100    /// Regions of inlays that should be highlighted.
 101    inlay_highlights: InlayHighlights,
 102    /// A container for explicitly foldable ranges, which supersede indentation based fold range suggestions.
 103    flap_map: FlapMap,
 104    fold_placeholder: FoldPlaceholder,
 105    pub clip_at_line_ends: bool,
 106}
 107
 108impl DisplayMap {
 109    #[allow(clippy::too_many_arguments)]
 110    pub fn new(
 111        buffer: Model<MultiBuffer>,
 112        font: Font,
 113        font_size: Pixels,
 114        wrap_width: Option<Pixels>,
 115        show_excerpt_controls: bool,
 116        buffer_header_height: u8,
 117        excerpt_header_height: u8,
 118        excerpt_footer_height: u8,
 119        fold_placeholder: FoldPlaceholder,
 120        cx: &mut ModelContext<Self>,
 121    ) -> Self {
 122        let buffer_subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
 123
 124        let tab_size = Self::tab_size(&buffer, cx);
 125        let (inlay_map, snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx));
 126        let (fold_map, snapshot) = FoldMap::new(snapshot);
 127        let (tab_map, snapshot) = TabMap::new(snapshot, tab_size);
 128        let (wrap_map, snapshot) = WrapMap::new(snapshot, font, font_size, wrap_width, cx);
 129        let block_map = BlockMap::new(
 130            snapshot,
 131            show_excerpt_controls,
 132            buffer_header_height,
 133            excerpt_header_height,
 134            excerpt_footer_height,
 135        );
 136        let flap_map = FlapMap::default();
 137
 138        cx.observe(&wrap_map, |_, _, cx| cx.notify()).detach();
 139
 140        DisplayMap {
 141            buffer,
 142            buffer_subscription,
 143            fold_map,
 144            inlay_map,
 145            tab_map,
 146            wrap_map,
 147            block_map,
 148            flap_map,
 149            fold_placeholder,
 150            text_highlights: Default::default(),
 151            inlay_highlights: Default::default(),
 152            clip_at_line_ends: false,
 153        }
 154    }
 155
 156    pub fn snapshot(&mut self, cx: &mut ModelContext<Self>) -> DisplaySnapshot {
 157        let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
 158        let edits = self.buffer_subscription.consume().into_inner();
 159        let (inlay_snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits);
 160        let (fold_snapshot, edits) = self.fold_map.read(inlay_snapshot.clone(), edits);
 161        let tab_size = Self::tab_size(&self.buffer, cx);
 162        let (tab_snapshot, edits) = self.tab_map.sync(fold_snapshot.clone(), edits, tab_size);
 163        let (wrap_snapshot, edits) = self
 164            .wrap_map
 165            .update(cx, |map, cx| map.sync(tab_snapshot.clone(), edits, cx));
 166        let block_snapshot = self.block_map.read(wrap_snapshot.clone(), edits);
 167
 168        DisplaySnapshot {
 169            buffer_snapshot: self.buffer.read(cx).snapshot(cx),
 170            fold_snapshot,
 171            inlay_snapshot,
 172            tab_snapshot,
 173            wrap_snapshot,
 174            block_snapshot,
 175            flap_snapshot: self.flap_map.snapshot(),
 176            text_highlights: self.text_highlights.clone(),
 177            inlay_highlights: self.inlay_highlights.clone(),
 178            clip_at_line_ends: self.clip_at_line_ends,
 179            fold_placeholder: self.fold_placeholder.clone(),
 180        }
 181    }
 182
 183    pub fn set_state(&mut self, other: &DisplaySnapshot, cx: &mut ModelContext<Self>) {
 184        self.fold(
 185            other
 186                .folds_in_range(0..other.buffer_snapshot.len())
 187                .map(|fold| {
 188                    (
 189                        fold.range.to_offset(&other.buffer_snapshot),
 190                        fold.placeholder.clone(),
 191                    )
 192                }),
 193            cx,
 194        );
 195    }
 196
 197    pub fn fold<T: ToOffset>(
 198        &mut self,
 199        ranges: impl IntoIterator<Item = (Range<T>, FoldPlaceholder)>,
 200        cx: &mut ModelContext<Self>,
 201    ) {
 202        let snapshot = self.buffer.read(cx).snapshot(cx);
 203        let edits = self.buffer_subscription.consume().into_inner();
 204        let tab_size = Self::tab_size(&self.buffer, cx);
 205        let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
 206        let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
 207        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
 208        let (snapshot, edits) = self
 209            .wrap_map
 210            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 211        self.block_map.read(snapshot, edits);
 212        let (snapshot, edits) = fold_map.fold(ranges);
 213        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
 214        let (snapshot, edits) = self
 215            .wrap_map
 216            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 217        self.block_map.read(snapshot, edits);
 218    }
 219
 220    pub fn unfold<T: ToOffset>(
 221        &mut self,
 222        ranges: impl IntoIterator<Item = Range<T>>,
 223        inclusive: bool,
 224        cx: &mut ModelContext<Self>,
 225    ) {
 226        let snapshot = self.buffer.read(cx).snapshot(cx);
 227        let edits = self.buffer_subscription.consume().into_inner();
 228        let tab_size = Self::tab_size(&self.buffer, cx);
 229        let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
 230        let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
 231        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
 232        let (snapshot, edits) = self
 233            .wrap_map
 234            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 235        self.block_map.read(snapshot, edits);
 236        let (snapshot, edits) = fold_map.unfold(ranges, inclusive);
 237        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
 238        let (snapshot, edits) = self
 239            .wrap_map
 240            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 241        self.block_map.read(snapshot, edits);
 242    }
 243
 244    pub fn insert_flaps(
 245        &mut self,
 246        flaps: impl IntoIterator<Item = Flap>,
 247        cx: &mut ModelContext<Self>,
 248    ) -> Vec<FlapId> {
 249        let snapshot = self.buffer.read(cx).snapshot(cx);
 250        self.flap_map.insert(flaps, &snapshot)
 251    }
 252
 253    pub fn remove_flaps(
 254        &mut self,
 255        flap_ids: impl IntoIterator<Item = FlapId>,
 256        cx: &mut ModelContext<Self>,
 257    ) {
 258        let snapshot = self.buffer.read(cx).snapshot(cx);
 259        self.flap_map.remove(flap_ids, &snapshot)
 260    }
 261
 262    pub fn insert_blocks(
 263        &mut self,
 264        blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
 265        cx: &mut ModelContext<Self>,
 266    ) -> Vec<BlockId> {
 267        let snapshot = self.buffer.read(cx).snapshot(cx);
 268        let edits = self.buffer_subscription.consume().into_inner();
 269        let tab_size = Self::tab_size(&self.buffer, cx);
 270        let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
 271        let (snapshot, edits) = self.fold_map.read(snapshot, edits);
 272        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
 273        let (snapshot, edits) = self
 274            .wrap_map
 275            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 276        let mut block_map = self.block_map.write(snapshot, edits);
 277        block_map.insert(blocks)
 278    }
 279
 280    pub fn replace_blocks(&mut self, styles: HashMap<BlockId, RenderBlock>) {
 281        self.block_map.replace(styles);
 282    }
 283
 284    pub fn remove_blocks(&mut self, ids: HashSet<BlockId>, cx: &mut ModelContext<Self>) {
 285        let snapshot = self.buffer.read(cx).snapshot(cx);
 286        let edits = self.buffer_subscription.consume().into_inner();
 287        let tab_size = Self::tab_size(&self.buffer, cx);
 288        let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
 289        let (snapshot, edits) = self.fold_map.read(snapshot, edits);
 290        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
 291        let (snapshot, edits) = self
 292            .wrap_map
 293            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 294        let mut block_map = self.block_map.write(snapshot, edits);
 295        block_map.remove(ids);
 296    }
 297
 298    pub fn highlight_text(
 299        &mut self,
 300        type_id: TypeId,
 301        ranges: Vec<Range<Anchor>>,
 302        style: HighlightStyle,
 303    ) {
 304        self.text_highlights
 305            .insert(Some(type_id), Arc::new((style, ranges)));
 306    }
 307
 308    pub(crate) fn highlight_inlays(
 309        &mut self,
 310        type_id: TypeId,
 311        highlights: Vec<InlayHighlight>,
 312        style: HighlightStyle,
 313    ) {
 314        for highlight in highlights {
 315            let update = self.inlay_highlights.update(&type_id, |highlights| {
 316                highlights.insert(highlight.inlay, (style, highlight.clone()))
 317            });
 318            if update.is_none() {
 319                self.inlay_highlights.insert(
 320                    type_id,
 321                    TreeMap::from_ordered_entries([(highlight.inlay, (style, highlight))]),
 322                );
 323            }
 324        }
 325    }
 326
 327    pub fn text_highlights(&self, type_id: TypeId) -> Option<(HighlightStyle, &[Range<Anchor>])> {
 328        let highlights = self.text_highlights.get(&Some(type_id))?;
 329        Some((highlights.0, &highlights.1))
 330    }
 331    pub fn clear_highlights(&mut self, type_id: TypeId) -> bool {
 332        let mut cleared = self.text_highlights.remove(&Some(type_id)).is_some();
 333        cleared |= self.inlay_highlights.remove(&type_id).is_some();
 334        cleared
 335    }
 336
 337    pub fn set_font(&self, font: Font, font_size: Pixels, cx: &mut ModelContext<Self>) -> bool {
 338        self.wrap_map
 339            .update(cx, |map, cx| map.set_font_with_size(font, font_size, cx))
 340    }
 341
 342    pub fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut ModelContext<Self>) -> bool {
 343        self.wrap_map
 344            .update(cx, |map, cx| map.set_wrap_width(width, cx))
 345    }
 346
 347    pub(crate) fn current_inlays(&self) -> impl Iterator<Item = &Inlay> {
 348        self.inlay_map.current_inlays()
 349    }
 350
 351    pub(crate) fn splice_inlays(
 352        &mut self,
 353        to_remove: Vec<InlayId>,
 354        to_insert: Vec<Inlay>,
 355        cx: &mut ModelContext<Self>,
 356    ) {
 357        if to_remove.is_empty() && to_insert.is_empty() {
 358            return;
 359        }
 360        let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
 361        let edits = self.buffer_subscription.consume().into_inner();
 362        let (snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits);
 363        let (snapshot, edits) = self.fold_map.read(snapshot, edits);
 364        let tab_size = Self::tab_size(&self.buffer, cx);
 365        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
 366        let (snapshot, edits) = self
 367            .wrap_map
 368            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 369        self.block_map.read(snapshot, edits);
 370
 371        let (snapshot, edits) = self.inlay_map.splice(to_remove, to_insert);
 372        let (snapshot, edits) = self.fold_map.read(snapshot, edits);
 373        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
 374        let (snapshot, edits) = self
 375            .wrap_map
 376            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 377        self.block_map.read(snapshot, edits);
 378    }
 379
 380    fn tab_size(buffer: &Model<MultiBuffer>, cx: &mut ModelContext<Self>) -> NonZeroU32 {
 381        let language = buffer
 382            .read(cx)
 383            .as_singleton()
 384            .and_then(|buffer| buffer.read(cx).language());
 385        language_settings(language, None, cx).tab_size
 386    }
 387
 388    #[cfg(test)]
 389    pub fn is_rewrapping(&self, cx: &gpui::AppContext) -> bool {
 390        self.wrap_map.read(cx).is_rewrapping()
 391    }
 392
 393    pub fn show_excerpt_controls(&self) -> bool {
 394        self.block_map.show_excerpt_controls()
 395    }
 396}
 397
 398#[derive(Debug, Default)]
 399pub(crate) struct Highlights<'a> {
 400    pub text_highlights: Option<&'a TextHighlights>,
 401    pub inlay_highlights: Option<&'a InlayHighlights>,
 402    pub styles: HighlightStyles,
 403}
 404
 405#[derive(Default, Debug, Clone, Copy)]
 406pub struct HighlightStyles {
 407    pub inlay_hint: Option<HighlightStyle>,
 408    pub suggestion: Option<HighlightStyle>,
 409}
 410
 411pub struct HighlightedChunk<'a> {
 412    pub text: &'a str,
 413    pub style: Option<HighlightStyle>,
 414    pub is_tab: bool,
 415    pub renderer: Option<ChunkRenderer>,
 416}
 417
 418#[derive(Clone)]
 419pub struct DisplaySnapshot {
 420    pub buffer_snapshot: MultiBufferSnapshot,
 421    pub fold_snapshot: FoldSnapshot,
 422    pub flap_snapshot: FlapSnapshot,
 423    inlay_snapshot: InlaySnapshot,
 424    tab_snapshot: TabSnapshot,
 425    wrap_snapshot: WrapSnapshot,
 426    block_snapshot: BlockSnapshot,
 427    text_highlights: TextHighlights,
 428    inlay_highlights: InlayHighlights,
 429    clip_at_line_ends: bool,
 430    pub(crate) fold_placeholder: FoldPlaceholder,
 431}
 432
 433impl DisplaySnapshot {
 434    #[cfg(test)]
 435    pub fn fold_count(&self) -> usize {
 436        self.fold_snapshot.fold_count()
 437    }
 438
 439    pub fn is_empty(&self) -> bool {
 440        self.buffer_snapshot.len() == 0
 441    }
 442
 443    pub fn buffer_rows(
 444        &self,
 445        start_row: DisplayRow,
 446    ) -> impl Iterator<Item = Option<MultiBufferRow>> + '_ {
 447        self.block_snapshot
 448            .buffer_rows(BlockRow(start_row.0))
 449            .map(|row| row.map(|row| MultiBufferRow(row.0)))
 450    }
 451
 452    pub fn max_buffer_row(&self) -> MultiBufferRow {
 453        self.buffer_snapshot.max_buffer_row()
 454    }
 455
 456    pub fn prev_line_boundary(&self, mut point: MultiBufferPoint) -> (Point, DisplayPoint) {
 457        loop {
 458            let mut inlay_point = self.inlay_snapshot.to_inlay_point(point);
 459            let mut fold_point = self.fold_snapshot.to_fold_point(inlay_point, Bias::Left);
 460            fold_point.0.column = 0;
 461            inlay_point = fold_point.to_inlay_point(&self.fold_snapshot);
 462            point = self.inlay_snapshot.to_buffer_point(inlay_point);
 463
 464            let mut display_point = self.point_to_display_point(point, Bias::Left);
 465            *display_point.column_mut() = 0;
 466            let next_point = self.display_point_to_point(display_point, Bias::Left);
 467            if next_point == point {
 468                return (point, display_point);
 469            }
 470            point = next_point;
 471        }
 472    }
 473
 474    pub fn next_line_boundary(&self, mut point: MultiBufferPoint) -> (Point, DisplayPoint) {
 475        loop {
 476            let mut inlay_point = self.inlay_snapshot.to_inlay_point(point);
 477            let mut fold_point = self.fold_snapshot.to_fold_point(inlay_point, Bias::Right);
 478            fold_point.0.column = self.fold_snapshot.line_len(fold_point.row());
 479            inlay_point = fold_point.to_inlay_point(&self.fold_snapshot);
 480            point = self.inlay_snapshot.to_buffer_point(inlay_point);
 481
 482            let mut display_point = self.point_to_display_point(point, Bias::Right);
 483            *display_point.column_mut() = self.line_len(display_point.row());
 484            let next_point = self.display_point_to_point(display_point, Bias::Right);
 485            if next_point == point {
 486                return (point, display_point);
 487            }
 488            point = next_point;
 489        }
 490    }
 491
 492    // used by line_mode selections and tries to match vim behaviour
 493    pub fn expand_to_line(&self, range: Range<Point>) -> Range<Point> {
 494        let new_start = if range.start.row == 0 {
 495            MultiBufferPoint::new(0, 0)
 496        } else if range.start.row == self.max_buffer_row().0
 497            || (range.end.column > 0 && range.end.row == self.max_buffer_row().0)
 498        {
 499            MultiBufferPoint::new(
 500                range.start.row - 1,
 501                self.buffer_snapshot
 502                    .line_len(MultiBufferRow(range.start.row - 1)),
 503            )
 504        } else {
 505            self.prev_line_boundary(range.start).0
 506        };
 507
 508        let new_end = if range.end.column == 0 {
 509            range.end
 510        } else if range.end.row < self.max_buffer_row().0 {
 511            self.buffer_snapshot
 512                .clip_point(MultiBufferPoint::new(range.end.row + 1, 0), Bias::Left)
 513        } else {
 514            self.buffer_snapshot.max_point()
 515        };
 516
 517        new_start..new_end
 518    }
 519
 520    fn point_to_display_point(&self, point: MultiBufferPoint, bias: Bias) -> DisplayPoint {
 521        let inlay_point = self.inlay_snapshot.to_inlay_point(point);
 522        let fold_point = self.fold_snapshot.to_fold_point(inlay_point, bias);
 523        let tab_point = self.tab_snapshot.to_tab_point(fold_point);
 524        let wrap_point = self.wrap_snapshot.tab_point_to_wrap_point(tab_point);
 525        let block_point = self.block_snapshot.to_block_point(wrap_point);
 526        DisplayPoint(block_point)
 527    }
 528
 529    fn display_point_to_point(&self, point: DisplayPoint, bias: Bias) -> Point {
 530        self.inlay_snapshot
 531            .to_buffer_point(self.display_point_to_inlay_point(point, bias))
 532    }
 533
 534    pub fn display_point_to_inlay_offset(&self, point: DisplayPoint, bias: Bias) -> InlayOffset {
 535        self.inlay_snapshot
 536            .to_offset(self.display_point_to_inlay_point(point, bias))
 537    }
 538
 539    pub fn anchor_to_inlay_offset(&self, anchor: Anchor) -> InlayOffset {
 540        self.inlay_snapshot
 541            .to_inlay_offset(anchor.to_offset(&self.buffer_snapshot))
 542    }
 543
 544    pub fn display_point_to_anchor(&self, point: DisplayPoint, bias: Bias) -> Anchor {
 545        self.buffer_snapshot
 546            .anchor_at(point.to_offset(&self, bias), bias)
 547    }
 548
 549    fn display_point_to_inlay_point(&self, point: DisplayPoint, bias: Bias) -> InlayPoint {
 550        let block_point = point.0;
 551        let wrap_point = self.block_snapshot.to_wrap_point(block_point);
 552        let tab_point = self.wrap_snapshot.to_tab_point(wrap_point);
 553        let fold_point = self.tab_snapshot.to_fold_point(tab_point, bias).0;
 554        fold_point.to_inlay_point(&self.fold_snapshot)
 555    }
 556
 557    pub fn display_point_to_fold_point(&self, point: DisplayPoint, bias: Bias) -> FoldPoint {
 558        let block_point = point.0;
 559        let wrap_point = self.block_snapshot.to_wrap_point(block_point);
 560        let tab_point = self.wrap_snapshot.to_tab_point(wrap_point);
 561        self.tab_snapshot.to_fold_point(tab_point, bias).0
 562    }
 563
 564    pub fn fold_point_to_display_point(&self, fold_point: FoldPoint) -> DisplayPoint {
 565        let tab_point = self.tab_snapshot.to_tab_point(fold_point);
 566        let wrap_point = self.wrap_snapshot.tab_point_to_wrap_point(tab_point);
 567        let block_point = self.block_snapshot.to_block_point(wrap_point);
 568        DisplayPoint(block_point)
 569    }
 570
 571    pub fn max_point(&self) -> DisplayPoint {
 572        DisplayPoint(self.block_snapshot.max_point())
 573    }
 574
 575    /// Returns text chunks starting at the given display row until the end of the file
 576    pub fn text_chunks(&self, display_row: DisplayRow) -> impl Iterator<Item = &str> {
 577        self.block_snapshot
 578            .chunks(
 579                display_row.0..self.max_point().row().next_row().0,
 580                false,
 581                Highlights::default(),
 582            )
 583            .map(|h| h.text)
 584    }
 585
 586    /// Returns text chunks starting at the end of the given display row in reverse until the start of the file
 587    pub fn reverse_text_chunks(&self, display_row: DisplayRow) -> impl Iterator<Item = &str> {
 588        (0..=display_row.0).rev().flat_map(|row| {
 589            self.block_snapshot
 590                .chunks(row..row + 1, false, Highlights::default())
 591                .map(|h| h.text)
 592                .collect::<Vec<_>>()
 593                .into_iter()
 594                .rev()
 595        })
 596    }
 597
 598    pub fn chunks(
 599        &self,
 600        display_rows: Range<DisplayRow>,
 601        language_aware: bool,
 602        highlight_styles: HighlightStyles,
 603    ) -> DisplayChunks<'_> {
 604        self.block_snapshot.chunks(
 605            display_rows.start.0..display_rows.end.0,
 606            language_aware,
 607            Highlights {
 608                text_highlights: Some(&self.text_highlights),
 609                inlay_highlights: Some(&self.inlay_highlights),
 610                styles: highlight_styles,
 611            },
 612        )
 613    }
 614
 615    pub fn highlighted_chunks<'a>(
 616        &'a self,
 617        display_rows: Range<DisplayRow>,
 618        language_aware: bool,
 619        editor_style: &'a EditorStyle,
 620    ) -> impl Iterator<Item = HighlightedChunk<'a>> {
 621        self.chunks(
 622            display_rows,
 623            language_aware,
 624            HighlightStyles {
 625                inlay_hint: Some(editor_style.inlay_hints_style),
 626                suggestion: Some(editor_style.suggestions_style),
 627            },
 628        )
 629        .map(|chunk| {
 630            let mut highlight_style = chunk
 631                .syntax_highlight_id
 632                .and_then(|id| id.style(&editor_style.syntax));
 633
 634            if let Some(chunk_highlight) = chunk.highlight_style {
 635                if let Some(highlight_style) = highlight_style.as_mut() {
 636                    highlight_style.highlight(chunk_highlight);
 637                } else {
 638                    highlight_style = Some(chunk_highlight);
 639                }
 640            }
 641
 642            let mut diagnostic_highlight = HighlightStyle::default();
 643
 644            if chunk.is_unnecessary {
 645                diagnostic_highlight.fade_out = Some(UNNECESSARY_CODE_FADE);
 646            }
 647
 648            if let Some(severity) = chunk.diagnostic_severity {
 649                // Omit underlines for HINT/INFO diagnostics on 'unnecessary' code.
 650                if severity <= DiagnosticSeverity::WARNING || !chunk.is_unnecessary {
 651                    let diagnostic_color =
 652                        super::diagnostic_style(severity, true, &editor_style.status);
 653                    diagnostic_highlight.underline = Some(UnderlineStyle {
 654                        color: Some(diagnostic_color),
 655                        thickness: 1.0.into(),
 656                        wavy: true,
 657                    });
 658                }
 659            }
 660
 661            if let Some(highlight_style) = highlight_style.as_mut() {
 662                highlight_style.highlight(diagnostic_highlight);
 663            } else {
 664                highlight_style = Some(diagnostic_highlight);
 665            }
 666
 667            HighlightedChunk {
 668                text: chunk.text,
 669                style: highlight_style,
 670                is_tab: chunk.is_tab,
 671                renderer: chunk.renderer,
 672            }
 673        })
 674    }
 675
 676    pub fn layout_row(
 677        &self,
 678        display_row: DisplayRow,
 679        TextLayoutDetails {
 680            text_system,
 681            editor_style,
 682            rem_size,
 683            scroll_anchor: _,
 684            visible_rows: _,
 685            vertical_scroll_margin: _,
 686        }: &TextLayoutDetails,
 687    ) -> Arc<LineLayout> {
 688        let mut runs = Vec::new();
 689        let mut line = String::new();
 690
 691        let range = display_row..display_row.next_row();
 692        for chunk in self.highlighted_chunks(range, false, &editor_style) {
 693            line.push_str(chunk.text);
 694
 695            let text_style = if let Some(style) = chunk.style {
 696                Cow::Owned(editor_style.text.clone().highlight(style))
 697            } else {
 698                Cow::Borrowed(&editor_style.text)
 699            };
 700
 701            runs.push(text_style.to_run(chunk.text.len()))
 702        }
 703
 704        if line.ends_with('\n') {
 705            line.pop();
 706            if let Some(last_run) = runs.last_mut() {
 707                last_run.len -= 1;
 708                if last_run.len == 0 {
 709                    runs.pop();
 710                }
 711            }
 712        }
 713
 714        let font_size = editor_style.text.font_size.to_pixels(*rem_size);
 715        text_system
 716            .layout_line(&line, font_size, &runs)
 717            .expect("we expect the font to be loaded because it's rendered by the editor")
 718    }
 719
 720    pub fn x_for_display_point(
 721        &self,
 722        display_point: DisplayPoint,
 723        text_layout_details: &TextLayoutDetails,
 724    ) -> Pixels {
 725        let line = self.layout_row(display_point.row(), text_layout_details);
 726        line.x_for_index(display_point.column() as usize)
 727    }
 728
 729    pub fn display_column_for_x(
 730        &self,
 731        display_row: DisplayRow,
 732        x: Pixels,
 733        details: &TextLayoutDetails,
 734    ) -> u32 {
 735        let layout_line = self.layout_row(display_row, details);
 736        layout_line.closest_index_for_x(x) as u32
 737    }
 738
 739    pub fn display_chars_at(
 740        &self,
 741        mut point: DisplayPoint,
 742    ) -> impl Iterator<Item = (char, DisplayPoint)> + '_ {
 743        point = DisplayPoint(self.block_snapshot.clip_point(point.0, Bias::Left));
 744        self.text_chunks(point.row())
 745            .flat_map(str::chars)
 746            .skip_while({
 747                let mut column = 0;
 748                move |char| {
 749                    let at_point = column >= point.column();
 750                    column += char.len_utf8() as u32;
 751                    !at_point
 752                }
 753            })
 754            .map(move |ch| {
 755                let result = (ch, point);
 756                if ch == '\n' {
 757                    *point.row_mut() += 1;
 758                    *point.column_mut() = 0;
 759                } else {
 760                    *point.column_mut() += ch.len_utf8() as u32;
 761                }
 762                result
 763            })
 764    }
 765
 766    pub fn buffer_chars_at(&self, mut offset: usize) -> impl Iterator<Item = (char, usize)> + '_ {
 767        self.buffer_snapshot.chars_at(offset).map(move |ch| {
 768            let ret = (ch, offset);
 769            offset += ch.len_utf8();
 770            ret
 771        })
 772    }
 773
 774    pub fn reverse_buffer_chars_at(
 775        &self,
 776        mut offset: usize,
 777    ) -> impl Iterator<Item = (char, usize)> + '_ {
 778        self.buffer_snapshot
 779            .reversed_chars_at(offset)
 780            .map(move |ch| {
 781                offset -= ch.len_utf8();
 782                (ch, offset)
 783            })
 784    }
 785
 786    pub fn clip_point(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint {
 787        let mut clipped = self.block_snapshot.clip_point(point.0, bias);
 788        if self.clip_at_line_ends {
 789            clipped = self.clip_at_line_end(DisplayPoint(clipped)).0
 790        }
 791        DisplayPoint(clipped)
 792    }
 793
 794    pub fn clip_ignoring_line_ends(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint {
 795        DisplayPoint(self.block_snapshot.clip_point(point.0, bias))
 796    }
 797
 798    pub fn clip_at_line_end(&self, point: DisplayPoint) -> DisplayPoint {
 799        let mut point = point.0;
 800        if point.column == self.line_len(DisplayRow(point.row)) {
 801            point.column = point.column.saturating_sub(1);
 802            point = self.block_snapshot.clip_point(point, Bias::Left);
 803        }
 804        DisplayPoint(point)
 805    }
 806
 807    pub fn folds_in_range<T>(&self, range: Range<T>) -> impl Iterator<Item = &Fold>
 808    where
 809        T: ToOffset,
 810    {
 811        self.fold_snapshot.folds_in_range(range)
 812    }
 813
 814    pub fn blocks_in_range(
 815        &self,
 816        rows: Range<DisplayRow>,
 817    ) -> impl Iterator<Item = (DisplayRow, &TransformBlock)> {
 818        self.block_snapshot
 819            .blocks_in_range(rows.start.0..rows.end.0)
 820            .map(|(row, block)| (DisplayRow(row), block))
 821    }
 822
 823    pub fn intersects_fold<T: ToOffset>(&self, offset: T) -> bool {
 824        self.fold_snapshot.intersects_fold(offset)
 825    }
 826
 827    pub fn is_line_folded(&self, buffer_row: MultiBufferRow) -> bool {
 828        self.fold_snapshot.is_line_folded(buffer_row)
 829    }
 830
 831    pub fn is_block_line(&self, display_row: DisplayRow) -> bool {
 832        self.block_snapshot.is_block_line(BlockRow(display_row.0))
 833    }
 834
 835    pub fn soft_wrap_indent(&self, display_row: DisplayRow) -> Option<u32> {
 836        let wrap_row = self
 837            .block_snapshot
 838            .to_wrap_point(BlockPoint::new(display_row.0, 0))
 839            .row();
 840        self.wrap_snapshot.soft_wrap_indent(wrap_row)
 841    }
 842
 843    pub fn text(&self) -> String {
 844        self.text_chunks(DisplayRow(0)).collect()
 845    }
 846
 847    pub fn line(&self, display_row: DisplayRow) -> String {
 848        let mut result = String::new();
 849        for chunk in self.text_chunks(display_row) {
 850            if let Some(ix) = chunk.find('\n') {
 851                result.push_str(&chunk[0..ix]);
 852                break;
 853            } else {
 854                result.push_str(chunk);
 855            }
 856        }
 857        result
 858    }
 859
 860    pub fn line_indent_for_buffer_row(&self, buffer_row: MultiBufferRow) -> LineIndent {
 861        let (buffer, range) = self
 862            .buffer_snapshot
 863            .buffer_line_for_row(buffer_row)
 864            .unwrap();
 865
 866        buffer.line_indent_for_row(range.start.row)
 867    }
 868
 869    pub fn line_len(&self, row: DisplayRow) -> u32 {
 870        self.block_snapshot.line_len(BlockRow(row.0))
 871    }
 872
 873    pub fn longest_row(&self) -> DisplayRow {
 874        DisplayRow(self.block_snapshot.longest_row())
 875    }
 876
 877    pub fn starts_indent(&self, buffer_row: MultiBufferRow) -> bool {
 878        let max_row = self.buffer_snapshot.max_buffer_row();
 879        if buffer_row >= max_row {
 880            return false;
 881        }
 882
 883        let line_indent = self.line_indent_for_buffer_row(buffer_row);
 884        if line_indent.is_line_blank() {
 885            return false;
 886        }
 887
 888        for next_row in (buffer_row.0 + 1)..=max_row.0 {
 889            let next_line_indent = self.line_indent_for_buffer_row(MultiBufferRow(next_row));
 890            if next_line_indent.raw_len() > line_indent.raw_len() {
 891                return true;
 892            } else if !next_line_indent.is_line_blank() {
 893                break;
 894            }
 895        }
 896
 897        false
 898    }
 899
 900    pub fn foldable_range(
 901        &self,
 902        buffer_row: MultiBufferRow,
 903    ) -> Option<(Range<Point>, FoldPlaceholder)> {
 904        let start = MultiBufferPoint::new(buffer_row.0, self.buffer_snapshot.line_len(buffer_row));
 905        if let Some(flap) = self
 906            .flap_snapshot
 907            .query_row(buffer_row, &self.buffer_snapshot)
 908        {
 909            Some((
 910                flap.range.to_point(&self.buffer_snapshot),
 911                flap.placeholder.clone(),
 912            ))
 913        } else if self.starts_indent(MultiBufferRow(start.row))
 914            && !self.is_line_folded(MultiBufferRow(start.row))
 915        {
 916            let start_line_indent = self.line_indent_for_buffer_row(buffer_row);
 917            let max_point = self.buffer_snapshot.max_point();
 918            let mut end = None;
 919
 920            for row in (buffer_row.0 + 1)..=max_point.row {
 921                let line_indent = self.line_indent_for_buffer_row(MultiBufferRow(row));
 922                if !line_indent.is_line_blank()
 923                    && line_indent.raw_len() <= start_line_indent.raw_len()
 924                {
 925                    let prev_row = row - 1;
 926                    end = Some(Point::new(
 927                        prev_row,
 928                        self.buffer_snapshot.line_len(MultiBufferRow(prev_row)),
 929                    ));
 930                    break;
 931                }
 932            }
 933            let end = end.unwrap_or(max_point);
 934            Some((start..end, self.fold_placeholder.clone()))
 935        } else {
 936            None
 937        }
 938    }
 939
 940    #[cfg(any(test, feature = "test-support"))]
 941    pub fn text_highlight_ranges<Tag: ?Sized + 'static>(
 942        &self,
 943    ) -> Option<Arc<(HighlightStyle, Vec<Range<Anchor>>)>> {
 944        let type_id = TypeId::of::<Tag>();
 945        self.text_highlights.get(&Some(type_id)).cloned()
 946    }
 947
 948    #[allow(unused)]
 949    #[cfg(any(test, feature = "test-support"))]
 950    pub(crate) fn inlay_highlights<Tag: ?Sized + 'static>(
 951        &self,
 952    ) -> Option<&TreeMap<InlayId, (HighlightStyle, InlayHighlight)>> {
 953        let type_id = TypeId::of::<Tag>();
 954        self.inlay_highlights.get(&type_id)
 955    }
 956}
 957
 958#[derive(Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq)]
 959pub struct DisplayPoint(BlockPoint);
 960
 961impl Debug for DisplayPoint {
 962    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 963        f.write_fmt(format_args!(
 964            "DisplayPoint({}, {})",
 965            self.row().0,
 966            self.column()
 967        ))
 968    }
 969}
 970
 971#[derive(Debug, Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq, Deserialize, Hash)]
 972#[serde(transparent)]
 973pub struct DisplayRow(pub u32);
 974
 975impl Add for DisplayRow {
 976    type Output = Self;
 977
 978    fn add(self, other: Self) -> Self::Output {
 979        DisplayRow(self.0 + other.0)
 980    }
 981}
 982
 983impl DisplayPoint {
 984    pub fn new(row: DisplayRow, column: u32) -> Self {
 985        Self(BlockPoint(Point::new(row.0, column)))
 986    }
 987
 988    pub fn zero() -> Self {
 989        Self::new(DisplayRow(0), 0)
 990    }
 991
 992    pub fn is_zero(&self) -> bool {
 993        self.0.is_zero()
 994    }
 995
 996    pub fn row(self) -> DisplayRow {
 997        DisplayRow(self.0.row)
 998    }
 999
1000    pub fn column(self) -> u32 {
1001        self.0.column
1002    }
1003
1004    pub fn row_mut(&mut self) -> &mut u32 {
1005        &mut self.0.row
1006    }
1007
1008    pub fn column_mut(&mut self) -> &mut u32 {
1009        &mut self.0.column
1010    }
1011
1012    pub fn to_point(self, map: &DisplaySnapshot) -> Point {
1013        map.display_point_to_point(self, Bias::Left)
1014    }
1015
1016    pub fn to_offset(self, map: &DisplaySnapshot, bias: Bias) -> usize {
1017        let wrap_point = map.block_snapshot.to_wrap_point(self.0);
1018        let tab_point = map.wrap_snapshot.to_tab_point(wrap_point);
1019        let fold_point = map.tab_snapshot.to_fold_point(tab_point, bias).0;
1020        let inlay_point = fold_point.to_inlay_point(&map.fold_snapshot);
1021        map.inlay_snapshot
1022            .to_buffer_offset(map.inlay_snapshot.to_offset(inlay_point))
1023    }
1024}
1025
1026impl ToDisplayPoint for usize {
1027    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
1028        map.point_to_display_point(self.to_point(&map.buffer_snapshot), Bias::Left)
1029    }
1030}
1031
1032impl ToDisplayPoint for OffsetUtf16 {
1033    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
1034        self.to_offset(&map.buffer_snapshot).to_display_point(map)
1035    }
1036}
1037
1038impl ToDisplayPoint for Point {
1039    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
1040        map.point_to_display_point(*self, Bias::Left)
1041    }
1042}
1043
1044impl ToDisplayPoint for Anchor {
1045    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
1046        self.to_point(&map.buffer_snapshot).to_display_point(map)
1047    }
1048}
1049
1050#[cfg(test)]
1051pub mod tests {
1052    use super::*;
1053    use crate::{
1054        movement,
1055        test::{editor_test_context::EditorTestContext, marked_display_snapshot},
1056    };
1057    use gpui::{div, font, observe, px, AppContext, BorrowAppContext, Context, Element, Hsla};
1058    use language::{
1059        language_settings::{AllLanguageSettings, AllLanguageSettingsContent},
1060        Buffer, Language, LanguageConfig, LanguageMatcher, SelectionGoal,
1061    };
1062    use project::Project;
1063    use rand::{prelude::*, Rng};
1064    use settings::SettingsStore;
1065    use smol::stream::StreamExt;
1066    use std::{env, sync::Arc};
1067    use theme::{LoadThemes, SyntaxTheme};
1068    use util::test::{marked_text_ranges, sample_text};
1069    use Bias::*;
1070
1071    #[gpui::test(iterations = 100)]
1072    async fn test_random_display_map(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
1073        cx.background_executor.set_block_on_ticks(0..=50);
1074        let operations = env::var("OPERATIONS")
1075            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
1076            .unwrap_or(10);
1077
1078        let mut tab_size = rng.gen_range(1..=4);
1079        let buffer_start_excerpt_header_height = rng.gen_range(1..=5);
1080        let excerpt_header_height = rng.gen_range(1..=5);
1081        let font_size = px(14.0);
1082        let max_wrap_width = 300.0;
1083        let mut wrap_width = if rng.gen_bool(0.1) {
1084            None
1085        } else {
1086            Some(px(rng.gen_range(0.0..=max_wrap_width)))
1087        };
1088
1089        log::info!("tab size: {}", tab_size);
1090        log::info!("wrap width: {:?}", wrap_width);
1091
1092        cx.update(|cx| {
1093            init_test(cx, |s| s.defaults.tab_size = NonZeroU32::new(tab_size));
1094        });
1095
1096        let buffer = cx.update(|cx| {
1097            if rng.gen() {
1098                let len = rng.gen_range(0..10);
1099                let text = util::RandomCharIter::new(&mut rng)
1100                    .take(len)
1101                    .collect::<String>();
1102                MultiBuffer::build_simple(&text, cx)
1103            } else {
1104                MultiBuffer::build_random(&mut rng, cx)
1105            }
1106        });
1107
1108        let map = cx.new_model(|cx| {
1109            DisplayMap::new(
1110                buffer.clone(),
1111                font("Helvetica"),
1112                font_size,
1113                wrap_width,
1114                true,
1115                buffer_start_excerpt_header_height,
1116                excerpt_header_height,
1117                0,
1118                FoldPlaceholder::test(),
1119                cx,
1120            )
1121        });
1122        let mut notifications = observe(&map, cx);
1123        let mut fold_count = 0;
1124        let mut blocks = Vec::new();
1125
1126        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1127        log::info!("buffer text: {:?}", snapshot.buffer_snapshot.text());
1128        log::info!("fold text: {:?}", snapshot.fold_snapshot.text());
1129        log::info!("tab text: {:?}", snapshot.tab_snapshot.text());
1130        log::info!("wrap text: {:?}", snapshot.wrap_snapshot.text());
1131        log::info!("block text: {:?}", snapshot.block_snapshot.text());
1132        log::info!("display text: {:?}", snapshot.text());
1133
1134        for _i in 0..operations {
1135            match rng.gen_range(0..100) {
1136                0..=19 => {
1137                    wrap_width = if rng.gen_bool(0.2) {
1138                        None
1139                    } else {
1140                        Some(px(rng.gen_range(0.0..=max_wrap_width)))
1141                    };
1142                    log::info!("setting wrap width to {:?}", wrap_width);
1143                    map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
1144                }
1145                20..=29 => {
1146                    let mut tab_sizes = vec![1, 2, 3, 4];
1147                    tab_sizes.remove((tab_size - 1) as usize);
1148                    tab_size = *tab_sizes.choose(&mut rng).unwrap();
1149                    log::info!("setting tab size to {:?}", tab_size);
1150                    cx.update(|cx| {
1151                        cx.update_global::<SettingsStore, _>(|store, cx| {
1152                            store.update_user_settings::<AllLanguageSettings>(cx, |s| {
1153                                s.defaults.tab_size = NonZeroU32::new(tab_size);
1154                            });
1155                        });
1156                    });
1157                }
1158                30..=44 => {
1159                    map.update(cx, |map, cx| {
1160                        if rng.gen() || blocks.is_empty() {
1161                            let buffer = map.snapshot(cx).buffer_snapshot;
1162                            let block_properties = (0..rng.gen_range(1..=1))
1163                                .map(|_| {
1164                                    let position =
1165                                        buffer.anchor_after(buffer.clip_offset(
1166                                            rng.gen_range(0..=buffer.len()),
1167                                            Bias::Left,
1168                                        ));
1169
1170                                    let disposition = if rng.gen() {
1171                                        BlockDisposition::Above
1172                                    } else {
1173                                        BlockDisposition::Below
1174                                    };
1175                                    let height = rng.gen_range(1..5);
1176                                    log::info!(
1177                                        "inserting block {:?} {:?} with height {}",
1178                                        disposition,
1179                                        position.to_point(&buffer),
1180                                        height
1181                                    );
1182                                    BlockProperties {
1183                                        style: BlockStyle::Fixed,
1184                                        position,
1185                                        height,
1186                                        disposition,
1187                                        render: Box::new(|_| div().into_any()),
1188                                    }
1189                                })
1190                                .collect::<Vec<_>>();
1191                            blocks.extend(map.insert_blocks(block_properties, cx));
1192                        } else {
1193                            blocks.shuffle(&mut rng);
1194                            let remove_count = rng.gen_range(1..=4.min(blocks.len()));
1195                            let block_ids_to_remove = (0..remove_count)
1196                                .map(|_| blocks.remove(rng.gen_range(0..blocks.len())))
1197                                .collect();
1198                            log::info!("removing block ids {:?}", block_ids_to_remove);
1199                            map.remove_blocks(block_ids_to_remove, cx);
1200                        }
1201                    });
1202                }
1203                45..=79 => {
1204                    let mut ranges = Vec::new();
1205                    for _ in 0..rng.gen_range(1..=3) {
1206                        buffer.read_with(cx, |buffer, cx| {
1207                            let buffer = buffer.read(cx);
1208                            let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right);
1209                            let start = buffer.clip_offset(rng.gen_range(0..=end), Left);
1210                            ranges.push(start..end);
1211                        });
1212                    }
1213
1214                    if rng.gen() && fold_count > 0 {
1215                        log::info!("unfolding ranges: {:?}", ranges);
1216                        map.update(cx, |map, cx| {
1217                            map.unfold(ranges, true, cx);
1218                        });
1219                    } else {
1220                        log::info!("folding ranges: {:?}", ranges);
1221                        map.update(cx, |map, cx| {
1222                            map.fold(
1223                                ranges
1224                                    .into_iter()
1225                                    .map(|range| (range, FoldPlaceholder::test())),
1226                                cx,
1227                            );
1228                        });
1229                    }
1230                }
1231                _ => {
1232                    buffer.update(cx, |buffer, cx| buffer.randomly_mutate(&mut rng, 5, cx));
1233                }
1234            }
1235
1236            if map.read_with(cx, |map, cx| map.is_rewrapping(cx)) {
1237                notifications.next().await.unwrap();
1238            }
1239
1240            let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1241            fold_count = snapshot.fold_count();
1242            log::info!("buffer text: {:?}", snapshot.buffer_snapshot.text());
1243            log::info!("fold text: {:?}", snapshot.fold_snapshot.text());
1244            log::info!("tab text: {:?}", snapshot.tab_snapshot.text());
1245            log::info!("wrap text: {:?}", snapshot.wrap_snapshot.text());
1246            log::info!("block text: {:?}", snapshot.block_snapshot.text());
1247            log::info!("display text: {:?}", snapshot.text());
1248
1249            // Line boundaries
1250            let buffer = &snapshot.buffer_snapshot;
1251            for _ in 0..5 {
1252                let row = rng.gen_range(0..=buffer.max_point().row);
1253                let column = rng.gen_range(0..=buffer.line_len(MultiBufferRow(row)));
1254                let point = buffer.clip_point(Point::new(row, column), Left);
1255
1256                let (prev_buffer_bound, prev_display_bound) = snapshot.prev_line_boundary(point);
1257                let (next_buffer_bound, next_display_bound) = snapshot.next_line_boundary(point);
1258
1259                assert!(prev_buffer_bound <= point);
1260                assert!(next_buffer_bound >= point);
1261                assert_eq!(prev_buffer_bound.column, 0);
1262                assert_eq!(prev_display_bound.column(), 0);
1263                if next_buffer_bound < buffer.max_point() {
1264                    assert_eq!(buffer.chars_at(next_buffer_bound).next(), Some('\n'));
1265                }
1266
1267                assert_eq!(
1268                    prev_display_bound,
1269                    prev_buffer_bound.to_display_point(&snapshot),
1270                    "row boundary before {:?}. reported buffer row boundary: {:?}",
1271                    point,
1272                    prev_buffer_bound
1273                );
1274                assert_eq!(
1275                    next_display_bound,
1276                    next_buffer_bound.to_display_point(&snapshot),
1277                    "display row boundary after {:?}. reported buffer row boundary: {:?}",
1278                    point,
1279                    next_buffer_bound
1280                );
1281                assert_eq!(
1282                    prev_buffer_bound,
1283                    prev_display_bound.to_point(&snapshot),
1284                    "row boundary before {:?}. reported display row boundary: {:?}",
1285                    point,
1286                    prev_display_bound
1287                );
1288                assert_eq!(
1289                    next_buffer_bound,
1290                    next_display_bound.to_point(&snapshot),
1291                    "row boundary after {:?}. reported display row boundary: {:?}",
1292                    point,
1293                    next_display_bound
1294                );
1295            }
1296
1297            // Movement
1298            let min_point = snapshot.clip_point(DisplayPoint::new(DisplayRow(0), 0), Left);
1299            let max_point = snapshot.clip_point(snapshot.max_point(), Right);
1300            for _ in 0..5 {
1301                let row = rng.gen_range(0..=snapshot.max_point().row().0);
1302                let column = rng.gen_range(0..=snapshot.line_len(DisplayRow(row)));
1303                let point = snapshot.clip_point(DisplayPoint::new(DisplayRow(row), column), Left);
1304
1305                log::info!("Moving from point {:?}", point);
1306
1307                let moved_right = movement::right(&snapshot, point);
1308                log::info!("Right {:?}", moved_right);
1309                if point < max_point {
1310                    assert!(moved_right > point);
1311                    if point.column() == snapshot.line_len(point.row())
1312                        || snapshot.soft_wrap_indent(point.row()).is_some()
1313                            && point.column() == snapshot.line_len(point.row()) - 1
1314                    {
1315                        assert!(moved_right.row() > point.row());
1316                    }
1317                } else {
1318                    assert_eq!(moved_right, point);
1319                }
1320
1321                let moved_left = movement::left(&snapshot, point);
1322                log::info!("Left {:?}", moved_left);
1323                if point > min_point {
1324                    assert!(moved_left < point);
1325                    if point.column() == 0 {
1326                        assert!(moved_left.row() < point.row());
1327                    }
1328                } else {
1329                    assert_eq!(moved_left, point);
1330                }
1331            }
1332        }
1333    }
1334
1335    #[gpui::test(retries = 5)]
1336    async fn test_soft_wraps(cx: &mut gpui::TestAppContext) {
1337        cx.background_executor
1338            .set_block_on_ticks(usize::MAX..=usize::MAX);
1339        cx.update(|cx| {
1340            init_test(cx, |_| {});
1341        });
1342
1343        let mut cx = EditorTestContext::new(cx).await;
1344        let editor = cx.editor.clone();
1345        let window = cx.window;
1346
1347        _ = cx.update_window(window, |_, cx| {
1348            let text_layout_details =
1349                editor.update(cx, |editor, cx| editor.text_layout_details(cx));
1350
1351            let font_size = px(12.0);
1352            let wrap_width = Some(px(64.));
1353
1354            let text = "one two three four five\nsix seven eight";
1355            let buffer = MultiBuffer::build_simple(text, cx);
1356            let map = cx.new_model(|cx| {
1357                DisplayMap::new(
1358                    buffer.clone(),
1359                    font("Helvetica"),
1360                    font_size,
1361                    wrap_width,
1362                    true,
1363                    1,
1364                    1,
1365                    0,
1366                    FoldPlaceholder::test(),
1367                    cx,
1368                )
1369            });
1370
1371            let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1372            assert_eq!(
1373                snapshot.text_chunks(DisplayRow(0)).collect::<String>(),
1374                "one two \nthree four \nfive\nsix seven \neight"
1375            );
1376            assert_eq!(
1377                snapshot.clip_point(DisplayPoint::new(DisplayRow(0), 8), Bias::Left),
1378                DisplayPoint::new(DisplayRow(0), 7)
1379            );
1380            assert_eq!(
1381                snapshot.clip_point(DisplayPoint::new(DisplayRow(0), 8), Bias::Right),
1382                DisplayPoint::new(DisplayRow(1), 0)
1383            );
1384            assert_eq!(
1385                movement::right(&snapshot, DisplayPoint::new(DisplayRow(0), 7)),
1386                DisplayPoint::new(DisplayRow(1), 0)
1387            );
1388            assert_eq!(
1389                movement::left(&snapshot, DisplayPoint::new(DisplayRow(1), 0)),
1390                DisplayPoint::new(DisplayRow(0), 7)
1391            );
1392
1393            let x = snapshot
1394                .x_for_display_point(DisplayPoint::new(DisplayRow(1), 10), &text_layout_details);
1395            assert_eq!(
1396                movement::up(
1397                    &snapshot,
1398                    DisplayPoint::new(DisplayRow(1), 10),
1399                    SelectionGoal::None,
1400                    false,
1401                    &text_layout_details,
1402                ),
1403                (
1404                    DisplayPoint::new(DisplayRow(0), 7),
1405                    SelectionGoal::HorizontalPosition(x.0)
1406                )
1407            );
1408            assert_eq!(
1409                movement::down(
1410                    &snapshot,
1411                    DisplayPoint::new(DisplayRow(0), 7),
1412                    SelectionGoal::HorizontalPosition(x.0),
1413                    false,
1414                    &text_layout_details
1415                ),
1416                (
1417                    DisplayPoint::new(DisplayRow(1), 10),
1418                    SelectionGoal::HorizontalPosition(x.0)
1419                )
1420            );
1421            assert_eq!(
1422                movement::down(
1423                    &snapshot,
1424                    DisplayPoint::new(DisplayRow(1), 10),
1425                    SelectionGoal::HorizontalPosition(x.0),
1426                    false,
1427                    &text_layout_details
1428                ),
1429                (
1430                    DisplayPoint::new(DisplayRow(2), 4),
1431                    SelectionGoal::HorizontalPosition(x.0)
1432                )
1433            );
1434
1435            let ix = snapshot.buffer_snapshot.text().find("seven").unwrap();
1436            buffer.update(cx, |buffer, cx| {
1437                buffer.edit([(ix..ix, "and ")], None, cx);
1438            });
1439
1440            let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1441            assert_eq!(
1442                snapshot.text_chunks(DisplayRow(1)).collect::<String>(),
1443                "three four \nfive\nsix and \nseven eight"
1444            );
1445
1446            // Re-wrap on font size changes
1447            map.update(cx, |map, cx| {
1448                map.set_font(font("Helvetica"), px(font_size.0 + 3.), cx)
1449            });
1450
1451            let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1452            assert_eq!(
1453                snapshot.text_chunks(DisplayRow(1)).collect::<String>(),
1454                "three \nfour five\nsix and \nseven \neight"
1455            )
1456        });
1457    }
1458
1459    #[gpui::test]
1460    fn test_text_chunks(cx: &mut gpui::AppContext) {
1461        init_test(cx, |_| {});
1462
1463        let text = sample_text(6, 6, 'a');
1464        let buffer = MultiBuffer::build_simple(&text, cx);
1465
1466        let font_size = px(14.0);
1467        let map = cx.new_model(|cx| {
1468            DisplayMap::new(
1469                buffer.clone(),
1470                font("Helvetica"),
1471                font_size,
1472                None,
1473                true,
1474                1,
1475                1,
1476                0,
1477                FoldPlaceholder::test(),
1478                cx,
1479            )
1480        });
1481
1482        buffer.update(cx, |buffer, cx| {
1483            buffer.edit(
1484                vec![
1485                    (
1486                        MultiBufferPoint::new(1, 0)..MultiBufferPoint::new(1, 0),
1487                        "\t",
1488                    ),
1489                    (
1490                        MultiBufferPoint::new(1, 1)..MultiBufferPoint::new(1, 1),
1491                        "\t",
1492                    ),
1493                    (
1494                        MultiBufferPoint::new(2, 1)..MultiBufferPoint::new(2, 1),
1495                        "\t",
1496                    ),
1497                ],
1498                None,
1499                cx,
1500            )
1501        });
1502
1503        assert_eq!(
1504            map.update(cx, |map, cx| map.snapshot(cx))
1505                .text_chunks(DisplayRow(1))
1506                .collect::<String>()
1507                .lines()
1508                .next(),
1509            Some("    b   bbbbb")
1510        );
1511        assert_eq!(
1512            map.update(cx, |map, cx| map.snapshot(cx))
1513                .text_chunks(DisplayRow(2))
1514                .collect::<String>()
1515                .lines()
1516                .next(),
1517            Some("c   ccccc")
1518        );
1519    }
1520
1521    #[gpui::test]
1522    async fn test_chunks(cx: &mut gpui::TestAppContext) {
1523        use unindent::Unindent as _;
1524
1525        let text = r#"
1526            fn outer() {}
1527
1528            mod module {
1529                fn inner() {}
1530            }"#
1531        .unindent();
1532
1533        let theme =
1534            SyntaxTheme::new_test(vec![("mod.body", Hsla::red()), ("fn.name", Hsla::blue())]);
1535        let language = Arc::new(
1536            Language::new(
1537                LanguageConfig {
1538                    name: "Test".into(),
1539                    matcher: LanguageMatcher {
1540                        path_suffixes: vec![".test".to_string()],
1541                        ..Default::default()
1542                    },
1543                    ..Default::default()
1544                },
1545                Some(tree_sitter_rust::language()),
1546            )
1547            .with_highlights_query(
1548                r#"
1549                (mod_item name: (identifier) body: _ @mod.body)
1550                (function_item name: (identifier) @fn.name)
1551                "#,
1552            )
1553            .unwrap(),
1554        );
1555        language.set_theme(&theme);
1556
1557        cx.update(|cx| init_test(cx, |s| s.defaults.tab_size = Some(2.try_into().unwrap())));
1558
1559        let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
1560        cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
1561        let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
1562
1563        let font_size = px(14.0);
1564
1565        let map = cx.new_model(|cx| {
1566            DisplayMap::new(
1567                buffer,
1568                font("Helvetica"),
1569                font_size,
1570                None,
1571                true,
1572                1,
1573                1,
1574                1,
1575                FoldPlaceholder::test(),
1576                cx,
1577            )
1578        });
1579        assert_eq!(
1580            cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(5), &map, &theme, cx)),
1581            vec![
1582                ("fn ".to_string(), None),
1583                ("outer".to_string(), Some(Hsla::blue())),
1584                ("() {}\n\nmod module ".to_string(), None),
1585                ("{\n    fn ".to_string(), Some(Hsla::red())),
1586                ("inner".to_string(), Some(Hsla::blue())),
1587                ("() {}\n}".to_string(), Some(Hsla::red())),
1588            ]
1589        );
1590        assert_eq!(
1591            cx.update(|cx| syntax_chunks(DisplayRow(3)..DisplayRow(5), &map, &theme, cx)),
1592            vec![
1593                ("    fn ".to_string(), Some(Hsla::red())),
1594                ("inner".to_string(), Some(Hsla::blue())),
1595                ("() {}\n}".to_string(), Some(Hsla::red())),
1596            ]
1597        );
1598
1599        map.update(cx, |map, cx| {
1600            map.fold(
1601                vec![(
1602                    MultiBufferPoint::new(0, 6)..MultiBufferPoint::new(3, 2),
1603                    FoldPlaceholder::test(),
1604                )],
1605                cx,
1606            )
1607        });
1608        assert_eq!(
1609            cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(2), &map, &theme, cx)),
1610            vec![
1611                ("fn ".to_string(), None),
1612                ("out".to_string(), Some(Hsla::blue())),
1613                ("".to_string(), None),
1614                ("  fn ".to_string(), Some(Hsla::red())),
1615                ("inner".to_string(), Some(Hsla::blue())),
1616                ("() {}\n}".to_string(), Some(Hsla::red())),
1617            ]
1618        );
1619    }
1620
1621    #[gpui::test]
1622    async fn test_chunks_with_soft_wrapping(cx: &mut gpui::TestAppContext) {
1623        use unindent::Unindent as _;
1624
1625        cx.background_executor
1626            .set_block_on_ticks(usize::MAX..=usize::MAX);
1627
1628        let text = r#"
1629            fn outer() {}
1630
1631            mod module {
1632                fn inner() {}
1633            }"#
1634        .unindent();
1635
1636        let theme =
1637            SyntaxTheme::new_test(vec![("mod.body", Hsla::red()), ("fn.name", Hsla::blue())]);
1638        let language = Arc::new(
1639            Language::new(
1640                LanguageConfig {
1641                    name: "Test".into(),
1642                    matcher: LanguageMatcher {
1643                        path_suffixes: vec![".test".to_string()],
1644                        ..Default::default()
1645                    },
1646                    ..Default::default()
1647                },
1648                Some(tree_sitter_rust::language()),
1649            )
1650            .with_highlights_query(
1651                r#"
1652                (mod_item name: (identifier) body: _ @mod.body)
1653                (function_item name: (identifier) @fn.name)
1654                "#,
1655            )
1656            .unwrap(),
1657        );
1658        language.set_theme(&theme);
1659
1660        cx.update(|cx| init_test(cx, |_| {}));
1661
1662        let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
1663        cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
1664        let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
1665
1666        let font_size = px(16.0);
1667
1668        let map = cx.new_model(|cx| {
1669            DisplayMap::new(
1670                buffer,
1671                font("Courier"),
1672                font_size,
1673                Some(px(40.0)),
1674                true,
1675                1,
1676                1,
1677                0,
1678                FoldPlaceholder::test(),
1679                cx,
1680            )
1681        });
1682        assert_eq!(
1683            cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(5), &map, &theme, cx)),
1684            [
1685                ("fn \n".to_string(), None),
1686                ("oute\nr".to_string(), Some(Hsla::blue())),
1687                ("() \n{}\n\n".to_string(), None),
1688            ]
1689        );
1690        assert_eq!(
1691            cx.update(|cx| syntax_chunks(DisplayRow(3)..DisplayRow(5), &map, &theme, cx)),
1692            [("{}\n\n".to_string(), None)]
1693        );
1694
1695        map.update(cx, |map, cx| {
1696            map.fold(
1697                vec![(
1698                    MultiBufferPoint::new(0, 6)..MultiBufferPoint::new(3, 2),
1699                    FoldPlaceholder::test(),
1700                )],
1701                cx,
1702            )
1703        });
1704        assert_eq!(
1705            cx.update(|cx| syntax_chunks(DisplayRow(1)..DisplayRow(4), &map, &theme, cx)),
1706            [
1707                ("out".to_string(), Some(Hsla::blue())),
1708                ("\n".to_string(), None),
1709                ("  \nfn ".to_string(), Some(Hsla::red())),
1710                ("i\n".to_string(), Some(Hsla::blue()))
1711            ]
1712        );
1713    }
1714
1715    #[gpui::test]
1716    async fn test_chunks_with_text_highlights(cx: &mut gpui::TestAppContext) {
1717        cx.update(|cx| init_test(cx, |_| {}));
1718
1719        let theme =
1720            SyntaxTheme::new_test(vec![("operator", Hsla::red()), ("string", Hsla::green())]);
1721        let language = Arc::new(
1722            Language::new(
1723                LanguageConfig {
1724                    name: "Test".into(),
1725                    matcher: LanguageMatcher {
1726                        path_suffixes: vec![".test".to_string()],
1727                        ..Default::default()
1728                    },
1729                    ..Default::default()
1730                },
1731                Some(tree_sitter_rust::language()),
1732            )
1733            .with_highlights_query(
1734                r#"
1735                ":" @operator
1736                (string_literal) @string
1737                "#,
1738            )
1739            .unwrap(),
1740        );
1741        language.set_theme(&theme);
1742
1743        let (text, highlighted_ranges) = marked_text_ranges(r#"constˇ «a»: B = "c «d»""#, false);
1744
1745        let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
1746        cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
1747
1748        let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
1749        let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
1750
1751        let font_size = px(16.0);
1752        let map = cx.new_model(|cx| {
1753            DisplayMap::new(
1754                buffer,
1755                font("Courier"),
1756                font_size,
1757                None,
1758                true,
1759                1,
1760                1,
1761                1,
1762                FoldPlaceholder::test(),
1763                cx,
1764            )
1765        });
1766
1767        enum MyType {}
1768
1769        let style = HighlightStyle {
1770            color: Some(Hsla::blue()),
1771            ..Default::default()
1772        };
1773
1774        map.update(cx, |map, _cx| {
1775            map.highlight_text(
1776                TypeId::of::<MyType>(),
1777                highlighted_ranges
1778                    .into_iter()
1779                    .map(|range| {
1780                        buffer_snapshot.anchor_before(range.start)
1781                            ..buffer_snapshot.anchor_before(range.end)
1782                    })
1783                    .collect(),
1784                style,
1785            );
1786        });
1787
1788        assert_eq!(
1789            cx.update(|cx| chunks(DisplayRow(0)..DisplayRow(10), &map, &theme, cx)),
1790            [
1791                ("const ".to_string(), None, None),
1792                ("a".to_string(), None, Some(Hsla::blue())),
1793                (":".to_string(), Some(Hsla::red()), None),
1794                (" B = ".to_string(), None, None),
1795                ("\"c ".to_string(), Some(Hsla::green()), None),
1796                ("d".to_string(), Some(Hsla::green()), Some(Hsla::blue())),
1797                ("\"".to_string(), Some(Hsla::green()), None),
1798            ]
1799        );
1800    }
1801
1802    #[gpui::test]
1803    fn test_clip_point(cx: &mut gpui::AppContext) {
1804        init_test(cx, |_| {});
1805
1806        fn assert(text: &str, shift_right: bool, bias: Bias, cx: &mut gpui::AppContext) {
1807            let (unmarked_snapshot, mut markers) = marked_display_snapshot(text, cx);
1808
1809            match bias {
1810                Bias::Left => {
1811                    if shift_right {
1812                        *markers[1].column_mut() += 1;
1813                    }
1814
1815                    assert_eq!(unmarked_snapshot.clip_point(markers[1], bias), markers[0])
1816                }
1817                Bias::Right => {
1818                    if shift_right {
1819                        *markers[0].column_mut() += 1;
1820                    }
1821
1822                    assert_eq!(unmarked_snapshot.clip_point(markers[0], bias), markers[1])
1823                }
1824            };
1825        }
1826
1827        use Bias::{Left, Right};
1828        assert("ˇˇα", false, Left, cx);
1829        assert("ˇˇα", true, Left, cx);
1830        assert("ˇˇα", false, Right, cx);
1831        assert("ˇαˇ", true, Right, cx);
1832        assert("ˇˇ✋", false, Left, cx);
1833        assert("ˇˇ✋", true, Left, cx);
1834        assert("ˇˇ✋", false, Right, cx);
1835        assert("ˇ✋ˇ", true, Right, cx);
1836        assert("ˇˇ🍐", false, Left, cx);
1837        assert("ˇˇ🍐", true, Left, cx);
1838        assert("ˇˇ🍐", false, Right, cx);
1839        assert("ˇ🍐ˇ", true, Right, cx);
1840        assert("ˇˇ\t", false, Left, cx);
1841        assert("ˇˇ\t", true, Left, cx);
1842        assert("ˇˇ\t", false, Right, cx);
1843        assert("ˇ\tˇ", true, Right, cx);
1844        assert(" ˇˇ\t", false, Left, cx);
1845        assert(" ˇˇ\t", true, Left, cx);
1846        assert(" ˇˇ\t", false, Right, cx);
1847        assert(" ˇ\tˇ", true, Right, cx);
1848        assert("   ˇˇ\t", false, Left, cx);
1849        assert("   ˇˇ\t", false, Right, cx);
1850    }
1851
1852    #[gpui::test]
1853    fn test_clip_at_line_ends(cx: &mut gpui::AppContext) {
1854        init_test(cx, |_| {});
1855
1856        fn assert(text: &str, cx: &mut gpui::AppContext) {
1857            let (mut unmarked_snapshot, markers) = marked_display_snapshot(text, cx);
1858            unmarked_snapshot.clip_at_line_ends = true;
1859            assert_eq!(
1860                unmarked_snapshot.clip_point(markers[1], Bias::Left),
1861                markers[0]
1862            );
1863        }
1864
1865        assert("ˇˇ", cx);
1866        assert("ˇaˇ", cx);
1867        assert("aˇbˇ", cx);
1868        assert("aˇαˇ", cx);
1869    }
1870
1871    #[gpui::test]
1872    fn test_flaps(cx: &mut gpui::AppContext) {
1873        init_test(cx, |_| {});
1874
1875        let text = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll";
1876        let buffer = MultiBuffer::build_simple(text, cx);
1877        let font_size = px(14.0);
1878        cx.new_model(|cx| {
1879            let mut map = DisplayMap::new(
1880                buffer.clone(),
1881                font("Helvetica"),
1882                font_size,
1883                None,
1884                true,
1885                1,
1886                1,
1887                0,
1888                FoldPlaceholder::test(),
1889                cx,
1890            );
1891            let snapshot = map.buffer.read(cx).snapshot(cx);
1892            let range =
1893                snapshot.anchor_before(Point::new(2, 0))..snapshot.anchor_after(Point::new(3, 3));
1894
1895            map.flap_map.insert(
1896                [Flap::new(
1897                    range,
1898                    FoldPlaceholder::test(),
1899                    |_row, _status, _toggle, _cx| div(),
1900                    |_row, _status, _cx| div(),
1901                )],
1902                &map.buffer.read(cx).snapshot(cx),
1903            );
1904
1905            map
1906        });
1907    }
1908
1909    #[gpui::test]
1910    fn test_tabs_with_multibyte_chars(cx: &mut gpui::AppContext) {
1911        init_test(cx, |_| {});
1912
1913        let text = "\t\tα\nβ\t\n🏀β\t\tγ";
1914        let buffer = MultiBuffer::build_simple(text, cx);
1915        let font_size = px(14.0);
1916
1917        let map = cx.new_model(|cx| {
1918            DisplayMap::new(
1919                buffer.clone(),
1920                font("Helvetica"),
1921                font_size,
1922                None,
1923                true,
1924                1,
1925                1,
1926                0,
1927                FoldPlaceholder::test(),
1928                cx,
1929            )
1930        });
1931        let map = map.update(cx, |map, cx| map.snapshot(cx));
1932        assert_eq!(map.text(), "✅       α\nβ   \n🏀β      γ");
1933        assert_eq!(
1934            map.text_chunks(DisplayRow(0)).collect::<String>(),
1935            "✅       α\nβ   \n🏀β      γ"
1936        );
1937        assert_eq!(
1938            map.text_chunks(DisplayRow(1)).collect::<String>(),
1939            "β   \n🏀β      γ"
1940        );
1941        assert_eq!(
1942            map.text_chunks(DisplayRow(2)).collect::<String>(),
1943            "🏀β      γ"
1944        );
1945
1946        let point = MultiBufferPoint::new(0, "\t\t".len() as u32);
1947        let display_point = DisplayPoint::new(DisplayRow(0), "".len() as u32);
1948        assert_eq!(point.to_display_point(&map), display_point);
1949        assert_eq!(display_point.to_point(&map), point);
1950
1951        let point = MultiBufferPoint::new(1, "β\t".len() as u32);
1952        let display_point = DisplayPoint::new(DisplayRow(1), "β   ".len() as u32);
1953        assert_eq!(point.to_display_point(&map), display_point);
1954        assert_eq!(display_point.to_point(&map), point,);
1955
1956        let point = MultiBufferPoint::new(2, "🏀β\t\t".len() as u32);
1957        let display_point = DisplayPoint::new(DisplayRow(2), "🏀β      ".len() as u32);
1958        assert_eq!(point.to_display_point(&map), display_point);
1959        assert_eq!(display_point.to_point(&map), point,);
1960
1961        // Display points inside of expanded tabs
1962        assert_eq!(
1963            DisplayPoint::new(DisplayRow(0), "".len() as u32).to_point(&map),
1964            MultiBufferPoint::new(0, "\t".len() as u32),
1965        );
1966        assert_eq!(
1967            DisplayPoint::new(DisplayRow(0), "".len() as u32).to_point(&map),
1968            MultiBufferPoint::new(0, "".len() as u32),
1969        );
1970
1971        // Clipping display points inside of multi-byte characters
1972        assert_eq!(
1973            map.clip_point(
1974                DisplayPoint::new(DisplayRow(0), "".len() as u32 - 1),
1975                Left
1976            ),
1977            DisplayPoint::new(DisplayRow(0), 0)
1978        );
1979        assert_eq!(
1980            map.clip_point(
1981                DisplayPoint::new(DisplayRow(0), "".len() as u32 - 1),
1982                Bias::Right
1983            ),
1984            DisplayPoint::new(DisplayRow(0), "".len() as u32)
1985        );
1986    }
1987
1988    #[gpui::test]
1989    fn test_max_point(cx: &mut gpui::AppContext) {
1990        init_test(cx, |_| {});
1991
1992        let buffer = MultiBuffer::build_simple("aaa\n\t\tbbb", cx);
1993        let font_size = px(14.0);
1994        let map = cx.new_model(|cx| {
1995            DisplayMap::new(
1996                buffer.clone(),
1997                font("Helvetica"),
1998                font_size,
1999                None,
2000                true,
2001                1,
2002                1,
2003                0,
2004                FoldPlaceholder::test(),
2005                cx,
2006            )
2007        });
2008        assert_eq!(
2009            map.update(cx, |map, cx| map.snapshot(cx)).max_point(),
2010            DisplayPoint::new(DisplayRow(1), 11)
2011        )
2012    }
2013
2014    fn syntax_chunks(
2015        rows: Range<DisplayRow>,
2016        map: &Model<DisplayMap>,
2017        theme: &SyntaxTheme,
2018        cx: &mut AppContext,
2019    ) -> Vec<(String, Option<Hsla>)> {
2020        chunks(rows, map, theme, cx)
2021            .into_iter()
2022            .map(|(text, color, _)| (text, color))
2023            .collect()
2024    }
2025
2026    fn chunks(
2027        rows: Range<DisplayRow>,
2028        map: &Model<DisplayMap>,
2029        theme: &SyntaxTheme,
2030        cx: &mut AppContext,
2031    ) -> Vec<(String, Option<Hsla>, Option<Hsla>)> {
2032        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
2033        let mut chunks: Vec<(String, Option<Hsla>, Option<Hsla>)> = Vec::new();
2034        for chunk in snapshot.chunks(rows, true, HighlightStyles::default()) {
2035            let syntax_color = chunk
2036                .syntax_highlight_id
2037                .and_then(|id| id.style(theme)?.color);
2038            let highlight_color = chunk.highlight_style.and_then(|style| style.color);
2039            if let Some((last_chunk, last_syntax_color, last_highlight_color)) = chunks.last_mut() {
2040                if syntax_color == *last_syntax_color && highlight_color == *last_highlight_color {
2041                    last_chunk.push_str(chunk.text);
2042                    continue;
2043                }
2044            }
2045            chunks.push((chunk.text.to_string(), syntax_color, highlight_color));
2046        }
2047        chunks
2048    }
2049
2050    fn init_test(cx: &mut AppContext, f: impl Fn(&mut AllLanguageSettingsContent)) {
2051        let settings = SettingsStore::test(cx);
2052        cx.set_global(settings);
2053        language::init(cx);
2054        crate::init(cx);
2055        Project::init_settings(cx);
2056        theme::init(LoadThemes::JustBase, cx);
2057        cx.update_global::<SettingsStore, _>(|store, cx| {
2058            store.update_user_settings::<AllLanguageSettings>(cx, f);
2059        });
2060    }
2061}