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//! ## Structure of the display map layers
  18//!
  19//! Each layer in the map (and the multibuffer itself to some extent) has a few
  20//! structures that are used to implement the public API available to the layer
  21//! above:
  22//! - a `Transform` type - this represents a region of text that the layer in
  23//!   question is "managing", that it transforms into a more "processed" text
  24//!   for the layer above. For example, the inlay map has an `enum Transform`
  25//!   that has two variants:
  26//!     - `Isomorphic`, representing a region of text that has no inlay hints (i.e.
  27//!       is passed through the map transparently)
  28//!     - `Inlay`, representing a location where an inlay hint is to be inserted.
  29//! - a `TransformSummary` type, which is usually a struct with two fields:
  30//!   [`input: TextSummary`][`TextSummary`] and [`output: TextSummary`][`TextSummary`]. Here,
  31//!   `input` corresponds to "text in the layer below", and `output` corresponds to the text
  32//!   exposed to the layer above. So in the inlay map case, a `Transform::Isomorphic`'s summary is
  33//!   just `input = output = summary`, where `summary` is the [`TextSummary`] stored in that
  34//!   variant. Conversely, a `Transform::Inlay` always has an empty `input` summary, because it's
  35//!   not "replacing" any text that exists on disk. The `output` is the summary of the inlay text
  36//!   to be injected. - Various newtype wrappers for co-ordinate spaces (e.g. [`WrapRow`]
  37//!   represents a row index, after soft-wrapping (and all lower layers)).
  38//! - A `Snapshot` type (e.g. [`InlaySnapshot`]) that captures the state of a layer at a specific
  39//!   point in time.
  40//! - various APIs which drill through the layers below to work with the underlying text. Notably:
  41//!   - `fn text_summary_for_offset()` returns a [`TextSummary`] for the range in the co-ordinate
  42//!     space that the map in question is responsible for.
  43//!   - `fn <A>_point_to_<B>_point()` converts a point in co-ordinate space `A` into co-ordinate
  44//!     space `B`.
  45//!   - A [`RowInfo`] iterator (e.g. [`InlayBufferRows`]) and a [`Chunk`] iterator
  46//!     (e.g. [`InlayChunks`])
  47//!   - A `sync` function (e.g. [`InlayMap::sync`]) that takes a snapshot and list of [`Edit<T>`]s,
  48//!     and returns a new snapshot and a list of transformed [`Edit<S>`]s. Note that the generic
  49//!     parameter on `Edit` changes, since these methods take in edits in the co-ordinate space of
  50//!     the lower layer, and return edits in their own co-ordinate space. The term "edit" is
  51//!     slightly misleading, since an [`Edit<T>`] doesn't tell you what changed - rather it can be
  52//!     thought of as a "region to invalidate". In theory, it would be correct to always use a
  53//!     single edit that covers the entire range. However, this would lead to lots of unnecessary
  54//!     recalculation.
  55//!
  56//! See the docs for the [`inlay_map`] module for a more in-depth explanation of how a single layer
  57//! works.
  58//!
  59//! [Editor]: crate::Editor
  60//! [EditorElement]: crate::element::EditorElement
  61//! [`TextSummary`]: multi_buffer::MBTextSummary
  62//! [`WrapRow`]: wrap_map::WrapRow
  63//! [`InlayBufferRows`]: inlay_map::InlayBufferRows
  64//! [`InlayChunks`]: inlay_map::InlayChunks
  65//! [`Edit<T>`]: text::Edit
  66//! [`Edit<S>`]: text::Edit
  67//! [`Chunk`]: language::Chunk
  68
  69#[macro_use]
  70mod dimensions;
  71
  72mod block_map;
  73mod crease_map;
  74mod custom_highlights;
  75mod fold_map;
  76mod inlay_map;
  77mod invisibles;
  78mod tab_map;
  79mod wrap_map;
  80
  81pub use crate::display_map::{fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap};
  82pub use block_map::{
  83    Block, BlockChunks as DisplayChunks, BlockContext, BlockId, BlockMap, BlockPlacement,
  84    BlockPoint, BlockProperties, BlockRows, BlockStyle, CompanionView, CompanionViewMut,
  85    CustomBlockId, EditorMargins, RenderBlock, StickyHeaderExcerpt,
  86};
  87pub use crease_map::*;
  88pub use fold_map::{
  89    ChunkRenderer, ChunkRendererContext, ChunkRendererId, Fold, FoldId, FoldPlaceholder, FoldPoint,
  90};
  91pub use inlay_map::{InlayOffset, InlayPoint};
  92pub use invisibles::{is_invisible, replacement};
  93pub use wrap_map::{WrapPoint, WrapRow, WrapSnapshot};
  94
  95use collections::{HashMap, HashSet, IndexSet, hash_map};
  96use gpui::{
  97    App, Context, Entity, EntityId, Font, HighlightStyle, LineLayout, Pixels, UnderlineStyle,
  98    WeakEntity,
  99};
 100use language::{Point, Subscription as BufferSubscription, language_settings::language_settings};
 101use multi_buffer::{
 102    Anchor, AnchorRangeExt, ExcerptId, MultiBuffer, MultiBufferOffset, MultiBufferOffsetUtf16,
 103    MultiBufferPoint, MultiBufferRow, MultiBufferSnapshot, RowInfo, ToOffset, ToPoint,
 104};
 105use project::project_settings::DiagnosticSeverity;
 106use project::{InlayId, lsp_store::LspFoldingRange, lsp_store::TokenType};
 107use serde::Deserialize;
 108use sum_tree::{Bias, TreeMap};
 109use text::{BufferId, LineIndent, Patch};
 110use ui::{SharedString, px};
 111use unicode_segmentation::UnicodeSegmentation;
 112use ztracing::instrument;
 113
 114use std::cell::RefCell;
 115use std::{
 116    any::TypeId,
 117    borrow::Cow,
 118    fmt::Debug,
 119    iter,
 120    num::NonZeroU32,
 121    ops::{self, Add, Bound, Range, Sub},
 122    sync::Arc,
 123};
 124
 125use crate::{
 126    EditorStyle, RowExt, hover_links::InlayHighlight, inlays::Inlay, movement::TextLayoutDetails,
 127};
 128use block_map::{BlockRow, BlockSnapshot};
 129use fold_map::FoldSnapshot;
 130use inlay_map::InlaySnapshot;
 131use tab_map::TabSnapshot;
 132use wrap_map::{WrapMap, WrapPatch};
 133
 134#[derive(Copy, Clone, Debug, PartialEq, Eq)]
 135pub enum FoldStatus {
 136    Folded,
 137    Foldable,
 138}
 139
 140/// Keys for tagging text highlights.
 141///
 142/// Note the order is important as it determines the priority of the highlights, lower means higher priority
 143#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
 144pub enum HighlightKey {
 145    // Note we want semantic tokens > colorized brackets
 146    // to allow language server highlights to work over brackets.
 147    ColorizeBracket(usize),
 148    SemanticToken,
 149    // below is sorted lexicographically, as there is no relevant ordering for these aside from coming after the above
 150    BufferSearchHighlights,
 151    ConsoleAnsiHighlight(usize),
 152    DebugStackFrameLine,
 153    DocumentHighlightRead,
 154    DocumentHighlightWrite,
 155    EditPredictionHighlight,
 156    Editor,
 157    HighlightOnYank,
 158    HighlightsTreeView(usize),
 159    HoverState,
 160    HoveredLinkState,
 161    InlineAssist,
 162    InputComposition,
 163    MatchingBracket,
 164    PendingInput,
 165    ProjectSearchView,
 166    Rename,
 167    SearchWithinRange,
 168    SelectedTextHighlight,
 169    SyntaxTreeView(usize),
 170    VimExchange,
 171}
 172
 173pub trait ToDisplayPoint {
 174    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint;
 175}
 176
 177type TextHighlights = TreeMap<HighlightKey, Arc<(HighlightStyle, Vec<Range<Anchor>>)>>;
 178type SemanticTokensHighlights =
 179    TreeMap<BufferId, (Arc<[SemanticTokenHighlight]>, Arc<HighlightStyleInterner>)>;
 180type InlayHighlights = TreeMap<HighlightKey, TreeMap<InlayId, (HighlightStyle, InlayHighlight)>>;
 181
 182#[derive(Debug)]
 183pub struct CompanionExcerptPatch {
 184    pub patch: Patch<MultiBufferPoint>,
 185    pub edited_range: Range<MultiBufferPoint>,
 186    pub source_excerpt_range: Range<MultiBufferPoint>,
 187    pub target_excerpt_range: Range<MultiBufferPoint>,
 188}
 189
 190pub type ConvertMultiBufferRows = fn(
 191    &HashMap<ExcerptId, ExcerptId>,
 192    &MultiBufferSnapshot,
 193    &MultiBufferSnapshot,
 194    (Bound<MultiBufferPoint>, Bound<MultiBufferPoint>),
 195) -> Vec<CompanionExcerptPatch>;
 196
 197/// Decides how text in a [`MultiBuffer`] should be displayed in a buffer, handling inlay hints,
 198/// folding, hard tabs, soft wrapping, custom blocks (like diagnostics), and highlighting.
 199///
 200/// See the [module level documentation](self) for more information.
 201pub struct DisplayMap {
 202    entity_id: EntityId,
 203    /// The buffer that we are displaying.
 204    buffer: Entity<MultiBuffer>,
 205    buffer_subscription: BufferSubscription<MultiBufferOffset>,
 206    /// Decides where the [`Inlay`]s should be displayed.
 207    inlay_map: InlayMap,
 208    /// Decides where the fold indicators should be and tracks parts of a source file that are currently folded.
 209    fold_map: FoldMap,
 210    /// Keeps track of hard tabs in a buffer.
 211    tab_map: TabMap,
 212    /// Handles soft wrapping.
 213    wrap_map: Entity<WrapMap>,
 214    /// Tracks custom blocks such as diagnostics that should be displayed within buffer.
 215    block_map: BlockMap,
 216    /// Regions of text that should be highlighted.
 217    text_highlights: TextHighlights,
 218    /// Regions of inlays that should be highlighted.
 219    inlay_highlights: InlayHighlights,
 220    /// The semantic tokens from the language server.
 221    pub semantic_token_highlights: SemanticTokensHighlights,
 222    /// A container for explicitly foldable ranges, which supersede indentation based fold range suggestions.
 223    crease_map: CreaseMap,
 224    pub(crate) fold_placeholder: FoldPlaceholder,
 225    pub clip_at_line_ends: bool,
 226    pub(crate) masked: bool,
 227    pub(crate) diagnostics_max_severity: DiagnosticSeverity,
 228    pub(crate) companion: Option<(WeakEntity<DisplayMap>, Entity<Companion>)>,
 229    lsp_folding_crease_ids: HashMap<BufferId, Vec<CreaseId>>,
 230}
 231
 232pub(crate) struct Companion {
 233    rhs_display_map_id: EntityId,
 234    rhs_buffer_to_lhs_buffer: HashMap<BufferId, BufferId>,
 235    lhs_buffer_to_rhs_buffer: HashMap<BufferId, BufferId>,
 236    rhs_excerpt_to_lhs_excerpt: HashMap<ExcerptId, ExcerptId>,
 237    lhs_excerpt_to_rhs_excerpt: HashMap<ExcerptId, ExcerptId>,
 238    rhs_rows_to_lhs_rows: ConvertMultiBufferRows,
 239    lhs_rows_to_rhs_rows: ConvertMultiBufferRows,
 240    rhs_custom_block_to_balancing_block: RefCell<HashMap<CustomBlockId, CustomBlockId>>,
 241    lhs_custom_block_to_balancing_block: RefCell<HashMap<CustomBlockId, CustomBlockId>>,
 242}
 243
 244impl Companion {
 245    pub(crate) fn new(
 246        rhs_display_map_id: EntityId,
 247        rhs_rows_to_lhs_rows: ConvertMultiBufferRows,
 248        lhs_rows_to_rhs_rows: ConvertMultiBufferRows,
 249    ) -> Self {
 250        Self {
 251            rhs_display_map_id,
 252            rhs_buffer_to_lhs_buffer: Default::default(),
 253            lhs_buffer_to_rhs_buffer: Default::default(),
 254            rhs_excerpt_to_lhs_excerpt: Default::default(),
 255            lhs_excerpt_to_rhs_excerpt: Default::default(),
 256            rhs_rows_to_lhs_rows,
 257            lhs_rows_to_rhs_rows,
 258            rhs_custom_block_to_balancing_block: Default::default(),
 259            lhs_custom_block_to_balancing_block: Default::default(),
 260        }
 261    }
 262
 263    pub(crate) fn is_rhs(&self, display_map_id: EntityId) -> bool {
 264        self.rhs_display_map_id == display_map_id
 265    }
 266
 267    pub(crate) fn custom_block_to_balancing_block(
 268        &self,
 269        display_map_id: EntityId,
 270    ) -> &RefCell<HashMap<CustomBlockId, CustomBlockId>> {
 271        if self.is_rhs(display_map_id) {
 272            &self.rhs_custom_block_to_balancing_block
 273        } else {
 274            &self.lhs_custom_block_to_balancing_block
 275        }
 276    }
 277
 278    pub(crate) fn convert_rows_to_companion(
 279        &self,
 280        display_map_id: EntityId,
 281        companion_snapshot: &MultiBufferSnapshot,
 282        our_snapshot: &MultiBufferSnapshot,
 283        bounds: (Bound<MultiBufferPoint>, Bound<MultiBufferPoint>),
 284    ) -> Vec<CompanionExcerptPatch> {
 285        let (excerpt_map, convert_fn) = if self.is_rhs(display_map_id) {
 286            (&self.rhs_excerpt_to_lhs_excerpt, self.rhs_rows_to_lhs_rows)
 287        } else {
 288            (&self.lhs_excerpt_to_rhs_excerpt, self.lhs_rows_to_rhs_rows)
 289        };
 290        convert_fn(excerpt_map, companion_snapshot, our_snapshot, bounds)
 291    }
 292
 293    pub(crate) fn convert_point_from_companion(
 294        &self,
 295        display_map_id: EntityId,
 296        our_snapshot: &MultiBufferSnapshot,
 297        companion_snapshot: &MultiBufferSnapshot,
 298        point: MultiBufferPoint,
 299    ) -> Range<MultiBufferPoint> {
 300        let (excerpt_map, convert_fn) = if self.is_rhs(display_map_id) {
 301            (&self.lhs_excerpt_to_rhs_excerpt, self.lhs_rows_to_rhs_rows)
 302        } else {
 303            (&self.rhs_excerpt_to_lhs_excerpt, self.rhs_rows_to_lhs_rows)
 304        };
 305
 306        let excerpt = convert_fn(
 307            excerpt_map,
 308            our_snapshot,
 309            companion_snapshot,
 310            (Bound::Included(point), Bound::Included(point)),
 311        )
 312        .into_iter()
 313        .next();
 314
 315        let Some(excerpt) = excerpt else {
 316            return Point::zero()..our_snapshot.max_point();
 317        };
 318        excerpt.patch.edit_for_old_position(point).new
 319    }
 320
 321    pub(crate) fn convert_point_to_companion(
 322        &self,
 323        display_map_id: EntityId,
 324        our_snapshot: &MultiBufferSnapshot,
 325        companion_snapshot: &MultiBufferSnapshot,
 326        point: MultiBufferPoint,
 327    ) -> Range<MultiBufferPoint> {
 328        let (excerpt_map, convert_fn) = if self.is_rhs(display_map_id) {
 329            (&self.rhs_excerpt_to_lhs_excerpt, self.rhs_rows_to_lhs_rows)
 330        } else {
 331            (&self.lhs_excerpt_to_rhs_excerpt, self.lhs_rows_to_rhs_rows)
 332        };
 333
 334        let excerpt = convert_fn(
 335            excerpt_map,
 336            companion_snapshot,
 337            our_snapshot,
 338            (Bound::Included(point), Bound::Included(point)),
 339        )
 340        .into_iter()
 341        .next();
 342
 343        let Some(excerpt) = excerpt else {
 344            return Point::zero()..companion_snapshot.max_point();
 345        };
 346        excerpt.patch.edit_for_old_position(point).new
 347    }
 348
 349    pub(crate) fn companion_excerpt_to_excerpt(
 350        &self,
 351        display_map_id: EntityId,
 352    ) -> &HashMap<ExcerptId, ExcerptId> {
 353        if self.is_rhs(display_map_id) {
 354            &self.lhs_excerpt_to_rhs_excerpt
 355        } else {
 356            &self.rhs_excerpt_to_lhs_excerpt
 357        }
 358    }
 359
 360    fn buffer_to_companion_buffer(&self, display_map_id: EntityId) -> &HashMap<BufferId, BufferId> {
 361        if self.is_rhs(display_map_id) {
 362            &self.rhs_buffer_to_lhs_buffer
 363        } else {
 364            &self.lhs_buffer_to_rhs_buffer
 365        }
 366    }
 367
 368    pub(crate) fn add_excerpt_mapping(&mut self, lhs_id: ExcerptId, rhs_id: ExcerptId) {
 369        self.lhs_excerpt_to_rhs_excerpt.insert(lhs_id, rhs_id);
 370        self.rhs_excerpt_to_lhs_excerpt.insert(rhs_id, lhs_id);
 371    }
 372
 373    pub(crate) fn remove_excerpt_mappings(
 374        &mut self,
 375        lhs_ids: impl IntoIterator<Item = ExcerptId>,
 376        rhs_ids: impl IntoIterator<Item = ExcerptId>,
 377    ) {
 378        for id in lhs_ids {
 379            self.lhs_excerpt_to_rhs_excerpt.remove(&id);
 380        }
 381        for id in rhs_ids {
 382            self.rhs_excerpt_to_lhs_excerpt.remove(&id);
 383        }
 384    }
 385
 386    pub(crate) fn lhs_to_rhs_buffer(&self, lhs_buffer_id: BufferId) -> Option<BufferId> {
 387        self.lhs_buffer_to_rhs_buffer.get(&lhs_buffer_id).copied()
 388    }
 389
 390    pub(crate) fn add_buffer_mapping(&mut self, lhs_buffer: BufferId, rhs_buffer: BufferId) {
 391        self.lhs_buffer_to_rhs_buffer.insert(lhs_buffer, rhs_buffer);
 392        self.rhs_buffer_to_lhs_buffer.insert(rhs_buffer, lhs_buffer);
 393    }
 394}
 395
 396#[derive(Default, Debug)]
 397pub struct HighlightStyleInterner {
 398    styles: IndexSet<HighlightStyle>,
 399}
 400
 401impl HighlightStyleInterner {
 402    pub(crate) fn intern(&mut self, style: HighlightStyle) -> HighlightStyleId {
 403        HighlightStyleId(self.styles.insert_full(style).0 as u32)
 404    }
 405}
 406
 407impl ops::Index<HighlightStyleId> for HighlightStyleInterner {
 408    type Output = HighlightStyle;
 409
 410    fn index(&self, index: HighlightStyleId) -> &Self::Output {
 411        &self.styles[index.0 as usize]
 412    }
 413}
 414
 415#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
 416pub struct HighlightStyleId(u32);
 417
 418/// A `SemanticToken`, but positioned to an offset in a buffer, and stylized.
 419#[derive(Debug, Clone)]
 420pub struct SemanticTokenHighlight {
 421    pub range: Range<Anchor>,
 422    pub style: HighlightStyleId,
 423    pub token_type: TokenType,
 424    pub token_modifiers: u32,
 425    pub server_id: lsp::LanguageServerId,
 426}
 427
 428impl DisplayMap {
 429    pub fn new(
 430        buffer: Entity<MultiBuffer>,
 431        font: Font,
 432        font_size: Pixels,
 433        wrap_width: Option<Pixels>,
 434        buffer_header_height: u32,
 435        excerpt_header_height: u32,
 436        fold_placeholder: FoldPlaceholder,
 437        diagnostics_max_severity: DiagnosticSeverity,
 438        cx: &mut Context<Self>,
 439    ) -> Self {
 440        let buffer_subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
 441
 442        let tab_size = Self::tab_size(&buffer, cx);
 443        let buffer_snapshot = buffer.read(cx).snapshot(cx);
 444        let crease_map = CreaseMap::new(&buffer_snapshot);
 445        let (inlay_map, snapshot) = InlayMap::new(buffer_snapshot);
 446        let (fold_map, snapshot) = FoldMap::new(snapshot);
 447        let (tab_map, snapshot) = TabMap::new(snapshot, tab_size);
 448        let (wrap_map, snapshot) = WrapMap::new(snapshot, font, font_size, wrap_width, cx);
 449        let block_map = BlockMap::new(snapshot, buffer_header_height, excerpt_header_height);
 450
 451        cx.observe(&wrap_map, |_, _, cx| cx.notify()).detach();
 452
 453        DisplayMap {
 454            entity_id: cx.entity_id(),
 455            buffer,
 456            buffer_subscription,
 457            fold_map,
 458            inlay_map,
 459            tab_map,
 460            wrap_map,
 461            block_map,
 462            crease_map,
 463            fold_placeholder,
 464            diagnostics_max_severity,
 465            text_highlights: Default::default(),
 466            inlay_highlights: Default::default(),
 467            semantic_token_highlights: TreeMap::default(),
 468            clip_at_line_ends: false,
 469            masked: false,
 470            companion: None,
 471            lsp_folding_crease_ids: HashMap::default(),
 472        }
 473    }
 474
 475    // TODO(split-diff) figure out how to free the LHS from having to build a block map before this is called
 476    pub(crate) fn set_companion(
 477        &mut self,
 478        companion: Option<(Entity<DisplayMap>, Entity<Companion>)>,
 479        cx: &mut Context<Self>,
 480    ) {
 481        let this = cx.weak_entity();
 482        // Reverting to no companion, recompute the block map to clear spacers
 483        // and balancing blocks.
 484        let Some((companion_display_map, companion)) = companion else {
 485            let Some((_, companion)) = self.companion.take() else {
 486                return;
 487            };
 488            let (snapshot, edits) = self.sync_through_wrap(cx);
 489            let edits = edits.compose([text::Edit {
 490                old: WrapRow(0)..snapshot.max_point().row(),
 491                new: WrapRow(0)..snapshot.max_point().row(),
 492            }]);
 493            self.block_map.write(snapshot, edits, None).remove(
 494                companion
 495                    .read(cx)
 496                    .lhs_custom_block_to_balancing_block
 497                    .borrow()
 498                    .values()
 499                    .copied()
 500                    .collect(),
 501            );
 502            return;
 503        };
 504        assert_eq!(self.entity_id, companion.read(cx).rhs_display_map_id);
 505
 506        // Note, throwing away the wrap edits because we're going to recompute the maximal range for the block map regardless
 507        let old_max_row = self.block_map.wrap_snapshot.borrow().max_point().row();
 508        let snapshot = {
 509            let edits = self.buffer_subscription.consume();
 510            let snapshot = self.buffer.read(cx).snapshot(cx);
 511            let tab_size = Self::tab_size(&self.buffer, cx);
 512            let (snapshot, edits) = self.inlay_map.sync(snapshot, edits.into_inner());
 513            let (mut writer, snapshot, edits) = self.fold_map.write(snapshot, edits);
 514            let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
 515            let (_snapshot, _edits) = self
 516                .wrap_map
 517                .update(cx, |wrap_map, cx| wrap_map.sync(snapshot, edits, cx));
 518
 519            let (snapshot, edits) =
 520                writer.unfold_intersecting([Anchor::min()..Anchor::max()], true);
 521            let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
 522            let (snapshot, _edits) = self
 523                .wrap_map
 524                .update(cx, |wrap_map, cx| wrap_map.sync(snapshot, edits, cx));
 525
 526            self.block_map
 527                .retain_blocks_raw(|block| !matches!(block.placement, BlockPlacement::Replace(_)));
 528            snapshot
 529        };
 530
 531        let (companion_wrap_snapshot, companion_wrap_edits) =
 532            companion_display_map.update(cx, |dm, cx| dm.sync_through_wrap(cx));
 533
 534        let edits = Patch::new(
 535            [text::Edit {
 536                old: WrapRow(0)..old_max_row,
 537                new: WrapRow(0)..snapshot.max_point().row(),
 538            }]
 539            .into_iter()
 540            .collect(),
 541        );
 542
 543        let reader = self.block_map.read(
 544            snapshot.clone(),
 545            edits.clone(),
 546            Some(CompanionView::new(
 547                self.entity_id,
 548                &companion_wrap_snapshot,
 549                &companion_wrap_edits,
 550                companion.read(cx),
 551            )),
 552        );
 553
 554        companion_display_map.update(cx, |companion_display_map, cx| {
 555            for my_buffer in self.folded_buffers() {
 556                let their_buffer = companion
 557                    .read(cx)
 558                    .rhs_buffer_to_lhs_buffer
 559                    .get(my_buffer)
 560                    .unwrap();
 561                companion_display_map
 562                    .block_map
 563                    .folded_buffers
 564                    .insert(*their_buffer);
 565            }
 566            for block in reader.blocks {
 567                let Some(their_block) = block_map::balancing_block(
 568                    &block.properties(),
 569                    snapshot.buffer(),
 570                    companion_wrap_snapshot.buffer(),
 571                    self.entity_id,
 572                    companion.read(cx),
 573                ) else {
 574                    continue;
 575                };
 576                let their_id = companion_display_map
 577                    .block_map
 578                    .insert_block_raw(their_block, companion_wrap_snapshot.buffer());
 579                companion.update(cx, |companion, _cx| {
 580                    companion
 581                        .custom_block_to_balancing_block(self.entity_id)
 582                        .borrow_mut()
 583                        .insert(block.id, their_id);
 584                });
 585            }
 586            companion_display_map.block_map.read(
 587                companion_wrap_snapshot,
 588                companion_wrap_edits,
 589                Some(CompanionView::new(
 590                    companion_display_map.entity_id,
 591                    &snapshot,
 592                    &edits,
 593                    companion.read(cx),
 594                )),
 595            );
 596            companion_display_map.companion = Some((this, companion.clone()));
 597        });
 598
 599        self.companion = Some((companion_display_map.downgrade(), companion));
 600    }
 601
 602    pub(crate) fn companion(&self) -> Option<&Entity<Companion>> {
 603        self.companion.as_ref().map(|(_, c)| c)
 604    }
 605
 606    pub(crate) fn companion_excerpt_to_my_excerpt(
 607        &self,
 608        their_id: ExcerptId,
 609        cx: &App,
 610    ) -> Option<ExcerptId> {
 611        let (_, companion) = self.companion.as_ref()?;
 612        let c = companion.read(cx);
 613        c.companion_excerpt_to_excerpt(self.entity_id)
 614            .get(&their_id)
 615            .copied()
 616    }
 617
 618    fn sync_through_wrap(&mut self, cx: &mut App) -> (WrapSnapshot, WrapPatch) {
 619        let tab_size = Self::tab_size(&self.buffer, cx);
 620        let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
 621        let edits = self.buffer_subscription.consume().into_inner();
 622
 623        let (snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits);
 624        let (snapshot, edits) = self.fold_map.read(snapshot, edits);
 625        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
 626        self.wrap_map
 627            .update(cx, |map, cx| map.sync(snapshot, edits, cx))
 628    }
 629
 630    fn with_synced_companion_mut<R>(
 631        display_map_id: EntityId,
 632        companion: &Option<(WeakEntity<DisplayMap>, Entity<Companion>)>,
 633        cx: &mut App,
 634        callback: impl FnOnce(Option<CompanionViewMut<'_>>, &mut App) -> R,
 635    ) -> R {
 636        let Some((companion_display_map, companion)) = companion else {
 637            return callback(None, cx);
 638        };
 639        let Some(companion_display_map) = companion_display_map.upgrade() else {
 640            return callback(None, cx);
 641        };
 642        companion_display_map.update(cx, |companion_display_map, cx| {
 643            let (companion_wrap_snapshot, companion_wrap_edits) =
 644                companion_display_map.sync_through_wrap(cx);
 645            companion_display_map
 646                .buffer
 647                .update(cx, |companion_multibuffer, cx| {
 648                    companion.update(cx, |companion, cx| {
 649                        let companion_view = CompanionViewMut::new(
 650                            display_map_id,
 651                            companion_display_map.entity_id,
 652                            &companion_wrap_snapshot,
 653                            &companion_wrap_edits,
 654                            companion_multibuffer,
 655                            companion,
 656                            &mut companion_display_map.block_map,
 657                        );
 658                        callback(Some(companion_view), cx)
 659                    })
 660                })
 661        })
 662    }
 663
 664    #[instrument(skip_all)]
 665    pub fn snapshot(&mut self, cx: &mut Context<Self>) -> DisplaySnapshot {
 666        let (self_wrap_snapshot, self_wrap_edits) = self.sync_through_wrap(cx);
 667        let companion_wrap_data = self.companion.as_ref().and_then(|(companion_dm, _)| {
 668            companion_dm
 669                .update(cx, |dm, cx| dm.sync_through_wrap(cx))
 670                .ok()
 671        });
 672        let companion_ref = self.companion.as_ref().map(|(_, c)| c.read(cx));
 673        let companion_view = companion_wrap_data.as_ref().zip(companion_ref).map(
 674            |((snapshot, edits), companion)| {
 675                CompanionView::new(self.entity_id, snapshot, edits, companion)
 676            },
 677        );
 678
 679        let block_snapshot = self
 680            .block_map
 681            .read(
 682                self_wrap_snapshot.clone(),
 683                self_wrap_edits.clone(),
 684                companion_view,
 685            )
 686            .snapshot;
 687
 688        if let Some((companion_dm, _)) = &self.companion {
 689            let _ = companion_dm.update(cx, |dm, _cx| {
 690                if let Some((companion_snapshot, companion_edits)) = companion_wrap_data {
 691                    let their_companion_ref = dm.companion.as_ref().map(|(_, c)| c.read(_cx));
 692                    dm.block_map.read(
 693                        companion_snapshot,
 694                        companion_edits,
 695                        their_companion_ref.map(|c| {
 696                            CompanionView::new(
 697                                dm.entity_id,
 698                                &self_wrap_snapshot,
 699                                &self_wrap_edits,
 700                                c,
 701                            )
 702                        }),
 703                    );
 704                }
 705            });
 706        }
 707
 708        let companion_display_snapshot = self.companion.as_ref().and_then(|(companion_dm, _)| {
 709            companion_dm
 710                .update(cx, |dm, cx| Arc::new(dm.snapshot_simple(cx)))
 711                .ok()
 712        });
 713
 714        DisplaySnapshot {
 715            display_map_id: self.entity_id,
 716            companion_display_snapshot,
 717            block_snapshot,
 718            diagnostics_max_severity: self.diagnostics_max_severity,
 719            crease_snapshot: self.crease_map.snapshot(),
 720            text_highlights: self.text_highlights.clone(),
 721            inlay_highlights: self.inlay_highlights.clone(),
 722            semantic_token_highlights: self.semantic_token_highlights.clone(),
 723            clip_at_line_ends: self.clip_at_line_ends,
 724            masked: self.masked,
 725            use_lsp_folding_ranges: !self.lsp_folding_crease_ids.is_empty(),
 726            fold_placeholder: self.fold_placeholder.clone(),
 727        }
 728    }
 729
 730    fn snapshot_simple(&mut self, cx: &mut Context<Self>) -> DisplaySnapshot {
 731        let (wrap_snapshot, wrap_edits) = self.sync_through_wrap(cx);
 732
 733        let block_snapshot = self
 734            .block_map
 735            .read(wrap_snapshot, wrap_edits, None)
 736            .snapshot;
 737
 738        DisplaySnapshot {
 739            display_map_id: self.entity_id,
 740            companion_display_snapshot: None,
 741            block_snapshot,
 742            diagnostics_max_severity: self.diagnostics_max_severity,
 743            crease_snapshot: self.crease_map.snapshot(),
 744            text_highlights: self.text_highlights.clone(),
 745            inlay_highlights: self.inlay_highlights.clone(),
 746            semantic_token_highlights: self.semantic_token_highlights.clone(),
 747            clip_at_line_ends: self.clip_at_line_ends,
 748            masked: self.masked,
 749            use_lsp_folding_ranges: !self.lsp_folding_crease_ids.is_empty(),
 750            fold_placeholder: self.fold_placeholder.clone(),
 751        }
 752    }
 753
 754    #[instrument(skip_all)]
 755    pub fn set_state(&mut self, other: &DisplaySnapshot, cx: &mut Context<Self>) {
 756        self.fold(
 757            other
 758                .folds_in_range(MultiBufferOffset(0)..other.buffer_snapshot().len())
 759                .map(|fold| {
 760                    Crease::simple(
 761                        fold.range.to_offset(other.buffer_snapshot()),
 762                        fold.placeholder.clone(),
 763                    )
 764                })
 765                .collect(),
 766            cx,
 767        );
 768    }
 769
 770    /// Creates folds for the given creases.
 771    #[instrument(skip_all)]
 772    pub fn fold<T: Clone + ToOffset>(&mut self, creases: Vec<Crease<T>>, cx: &mut Context<Self>) {
 773        if self.companion().is_some() {
 774            return;
 775        }
 776
 777        let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
 778        let edits = self.buffer_subscription.consume().into_inner();
 779        let tab_size = Self::tab_size(&self.buffer, cx);
 780
 781        let (snapshot, edits) = self.inlay_map.sync(buffer_snapshot.clone(), edits);
 782        let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
 783        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
 784        let (snapshot, edits) = self
 785            .wrap_map
 786            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 787        self.block_map.read(snapshot, edits, None);
 788
 789        let inline = creases.iter().filter_map(|crease| {
 790            if let Crease::Inline {
 791                range, placeholder, ..
 792            } = crease
 793            {
 794                Some((range.clone(), placeholder.clone()))
 795            } else {
 796                None
 797            }
 798        });
 799        let (snapshot, edits) = fold_map.fold(inline);
 800
 801        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
 802        let (snapshot, edits) = self
 803            .wrap_map
 804            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 805
 806        let blocks = creases
 807            .into_iter()
 808            .filter_map(|crease| {
 809                if let Crease::Block {
 810                    range,
 811                    block_height,
 812                    render_block,
 813                    block_style,
 814                    block_priority,
 815                    ..
 816                } = crease
 817                {
 818                    Some((
 819                        range,
 820                        render_block,
 821                        block_height,
 822                        block_style,
 823                        block_priority,
 824                    ))
 825                } else {
 826                    None
 827                }
 828            })
 829            .map(|(range, render, height, style, priority)| {
 830                let start = buffer_snapshot.anchor_before(range.start);
 831                let end = buffer_snapshot.anchor_after(range.end);
 832                BlockProperties {
 833                    placement: BlockPlacement::Replace(start..=end),
 834                    render,
 835                    height: Some(height),
 836                    style,
 837                    priority,
 838                }
 839            });
 840
 841        self.block_map.write(snapshot, edits, None).insert(blocks);
 842    }
 843
 844    /// Removes any folds with the given ranges.
 845    #[instrument(skip_all)]
 846    pub fn remove_folds_with_type<T: ToOffset>(
 847        &mut self,
 848        ranges: impl IntoIterator<Item = Range<T>>,
 849        type_id: TypeId,
 850        cx: &mut Context<Self>,
 851    ) {
 852        let snapshot = self.buffer.read(cx).snapshot(cx);
 853        let edits = self.buffer_subscription.consume().into_inner();
 854        let tab_size = Self::tab_size(&self.buffer, cx);
 855
 856        let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
 857        let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
 858        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
 859        let (snapshot, edits) = self
 860            .wrap_map
 861            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 862        self.block_map.read(snapshot, edits, None);
 863
 864        let (snapshot, edits) = fold_map.remove_folds(ranges, type_id);
 865        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
 866        let (self_new_wrap_snapshot, self_new_wrap_edits) = self
 867            .wrap_map
 868            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 869
 870        self.block_map
 871            .write(self_new_wrap_snapshot, self_new_wrap_edits, None);
 872    }
 873
 874    /// Removes any folds whose ranges intersect any of the given ranges.
 875    #[instrument(skip_all)]
 876    pub fn unfold_intersecting<T: ToOffset>(
 877        &mut self,
 878        ranges: impl IntoIterator<Item = Range<T>>,
 879        inclusive: bool,
 880        cx: &mut Context<Self>,
 881    ) -> WrapSnapshot {
 882        let snapshot = self.buffer.read(cx).snapshot(cx);
 883        let offset_ranges = ranges
 884            .into_iter()
 885            .map(|range| range.start.to_offset(&snapshot)..range.end.to_offset(&snapshot))
 886            .collect::<Vec<_>>();
 887        let edits = self.buffer_subscription.consume().into_inner();
 888        let tab_size = Self::tab_size(&self.buffer, cx);
 889
 890        let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
 891        let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
 892        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
 893        let (snapshot, edits) = self
 894            .wrap_map
 895            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 896        self.block_map.read(snapshot, edits, None);
 897
 898        let (snapshot, edits) =
 899            fold_map.unfold_intersecting(offset_ranges.iter().cloned(), inclusive);
 900        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
 901        let (self_new_wrap_snapshot, self_new_wrap_edits) = self
 902            .wrap_map
 903            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 904
 905        self.block_map
 906            .write(self_new_wrap_snapshot.clone(), self_new_wrap_edits, None)
 907            .remove_intersecting_replace_blocks(offset_ranges, inclusive);
 908
 909        self_new_wrap_snapshot
 910    }
 911
 912    #[instrument(skip_all)]
 913    pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
 914        let (self_wrap_snapshot, self_wrap_edits) = self.sync_through_wrap(cx);
 915        self.block_map
 916            .write(self_wrap_snapshot, self_wrap_edits, None)
 917            .disable_header_for_buffer(buffer_id);
 918    }
 919
 920    #[instrument(skip_all)]
 921    pub fn fold_buffers(
 922        &mut self,
 923        buffer_ids: impl IntoIterator<Item = language::BufferId>,
 924        cx: &mut App,
 925    ) {
 926        let buffer_ids: Vec<_> = buffer_ids.into_iter().collect();
 927
 928        let (self_wrap_snapshot, self_wrap_edits) = self.sync_through_wrap(cx);
 929
 930        Self::with_synced_companion_mut(
 931            self.entity_id,
 932            &self.companion,
 933            cx,
 934            |companion_view, cx| {
 935                self.block_map
 936                    .write(
 937                        self_wrap_snapshot.clone(),
 938                        self_wrap_edits.clone(),
 939                        companion_view,
 940                    )
 941                    .fold_buffers(buffer_ids.iter().copied(), self.buffer.read(cx), cx);
 942            },
 943        )
 944    }
 945
 946    #[instrument(skip_all)]
 947    pub fn unfold_buffers(
 948        &mut self,
 949        buffer_ids: impl IntoIterator<Item = language::BufferId>,
 950        cx: &mut Context<Self>,
 951    ) {
 952        let buffer_ids: Vec<_> = buffer_ids.into_iter().collect();
 953
 954        let (self_wrap_snapshot, self_wrap_edits) = self.sync_through_wrap(cx);
 955
 956        Self::with_synced_companion_mut(
 957            self.entity_id,
 958            &self.companion,
 959            cx,
 960            |companion_view, cx| {
 961                self.block_map
 962                    .write(
 963                        self_wrap_snapshot.clone(),
 964                        self_wrap_edits.clone(),
 965                        companion_view,
 966                    )
 967                    .unfold_buffers(buffer_ids.iter().copied(), self.buffer.read(cx), cx);
 968            },
 969        )
 970    }
 971
 972    #[instrument(skip_all)]
 973    pub(crate) fn is_buffer_folded(&self, buffer_id: language::BufferId) -> bool {
 974        self.block_map.folded_buffers.contains(&buffer_id)
 975    }
 976
 977    #[instrument(skip_all)]
 978    pub(crate) fn folded_buffers(&self) -> &HashSet<BufferId> {
 979        &self.block_map.folded_buffers
 980    }
 981
 982    #[instrument(skip_all)]
 983    pub fn insert_creases(
 984        &mut self,
 985        creases: impl IntoIterator<Item = Crease<Anchor>>,
 986        cx: &mut Context<Self>,
 987    ) -> Vec<CreaseId> {
 988        let snapshot = self.buffer.read(cx).snapshot(cx);
 989        self.crease_map.insert(creases, &snapshot)
 990    }
 991
 992    #[instrument(skip_all)]
 993    pub fn remove_creases(
 994        &mut self,
 995        crease_ids: impl IntoIterator<Item = CreaseId>,
 996        cx: &mut Context<Self>,
 997    ) -> Vec<(CreaseId, Range<Anchor>)> {
 998        let snapshot = self.buffer.read(cx).snapshot(cx);
 999        self.crease_map.remove(crease_ids, &snapshot)
1000    }
1001
1002    /// Replaces the LSP folding-range creases for a single buffer.
1003    /// Converts the supplied buffer-anchor ranges into multi-buffer creases
1004    /// by mapping them through the appropriate excerpts.
1005    pub(super) fn set_lsp_folding_ranges(
1006        &mut self,
1007        buffer_id: BufferId,
1008        ranges: Vec<LspFoldingRange>,
1009        cx: &mut Context<Self>,
1010    ) {
1011        let snapshot = self.buffer.read(cx).snapshot(cx);
1012
1013        let old_ids = self
1014            .lsp_folding_crease_ids
1015            .remove(&buffer_id)
1016            .unwrap_or_default();
1017        if !old_ids.is_empty() {
1018            self.crease_map.remove(old_ids, &snapshot);
1019        }
1020
1021        if ranges.is_empty() {
1022            return;
1023        }
1024
1025        let excerpt_ids = snapshot
1026            .excerpts()
1027            .filter(|(_, buf, _)| buf.remote_id() == buffer_id)
1028            .map(|(id, _, _)| id)
1029            .collect::<Vec<_>>();
1030
1031        let base_placeholder = self.fold_placeholder.clone();
1032        let creases = ranges.into_iter().filter_map(|folding_range| {
1033            let mb_range = excerpt_ids.iter().find_map(|&id| {
1034                snapshot.anchor_range_in_excerpt(id, folding_range.range.clone())
1035            })?;
1036            let placeholder = if let Some(collapsed_text) = folding_range.collapsed_text {
1037                FoldPlaceholder {
1038                    render: Arc::new({
1039                        let collapsed_text = collapsed_text.clone();
1040                        move |fold_id, _fold_range, cx: &mut gpui::App| {
1041                            use gpui::{Element as _, ParentElement as _};
1042                            FoldPlaceholder::fold_element(fold_id, cx)
1043                                .child(collapsed_text.clone())
1044                                .into_any()
1045                        }
1046                    }),
1047                    constrain_width: false,
1048                    merge_adjacent: base_placeholder.merge_adjacent,
1049                    type_tag: base_placeholder.type_tag,
1050                    collapsed_text: Some(collapsed_text),
1051                }
1052            } else {
1053                base_placeholder.clone()
1054            };
1055            Some(Crease::simple(mb_range, placeholder))
1056        });
1057
1058        let new_ids = self.crease_map.insert(creases, &snapshot);
1059        if !new_ids.is_empty() {
1060            self.lsp_folding_crease_ids.insert(buffer_id, new_ids);
1061        }
1062    }
1063
1064    /// Removes all LSP folding-range creases for a single buffer.
1065    pub(super) fn clear_lsp_folding_ranges(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
1066        if let hash_map::Entry::Occupied(entry) = self.lsp_folding_crease_ids.entry(buffer_id) {
1067            let old_ids = entry.remove();
1068            let snapshot = self.buffer.read(cx).snapshot(cx);
1069            self.crease_map.remove(old_ids, &snapshot);
1070        }
1071    }
1072
1073    /// Returns `true` when at least one buffer has LSP folding-range creases.
1074    pub(super) fn has_lsp_folding_ranges(&self) -> bool {
1075        !self.lsp_folding_crease_ids.is_empty()
1076    }
1077
1078    #[instrument(skip_all)]
1079    pub fn insert_blocks(
1080        &mut self,
1081        blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
1082        cx: &mut Context<Self>,
1083    ) -> Vec<CustomBlockId> {
1084        let (self_wrap_snapshot, self_wrap_edits) = self.sync_through_wrap(cx);
1085        Self::with_synced_companion_mut(
1086            self.entity_id,
1087            &self.companion,
1088            cx,
1089            |companion_view, _cx| {
1090                self.block_map
1091                    .write(
1092                        self_wrap_snapshot.clone(),
1093                        self_wrap_edits.clone(),
1094                        companion_view,
1095                    )
1096                    .insert(blocks)
1097            },
1098        )
1099    }
1100
1101    #[instrument(skip_all)]
1102    pub fn resize_blocks(&mut self, heights: HashMap<CustomBlockId, u32>, cx: &mut Context<Self>) {
1103        let (self_wrap_snapshot, self_wrap_edits) = self.sync_through_wrap(cx);
1104
1105        Self::with_synced_companion_mut(
1106            self.entity_id,
1107            &self.companion,
1108            cx,
1109            |companion_view, _cx| {
1110                self.block_map
1111                    .write(
1112                        self_wrap_snapshot.clone(),
1113                        self_wrap_edits.clone(),
1114                        companion_view,
1115                    )
1116                    .resize(heights);
1117            },
1118        )
1119    }
1120
1121    #[instrument(skip_all)]
1122    pub fn replace_blocks(&mut self, renderers: HashMap<CustomBlockId, RenderBlock>) {
1123        self.block_map.replace_blocks(renderers);
1124    }
1125
1126    #[instrument(skip_all)]
1127    pub fn remove_blocks(&mut self, ids: HashSet<CustomBlockId>, cx: &mut Context<Self>) {
1128        let (self_wrap_snapshot, self_wrap_edits) = self.sync_through_wrap(cx);
1129
1130        Self::with_synced_companion_mut(
1131            self.entity_id,
1132            &self.companion,
1133            cx,
1134            |companion_view, _cx| {
1135                self.block_map
1136                    .write(
1137                        self_wrap_snapshot.clone(),
1138                        self_wrap_edits.clone(),
1139                        companion_view,
1140                    )
1141                    .remove(ids);
1142            },
1143        )
1144    }
1145
1146    #[instrument(skip_all)]
1147    pub fn row_for_block(
1148        &mut self,
1149        block_id: CustomBlockId,
1150        cx: &mut Context<Self>,
1151    ) -> Option<DisplayRow> {
1152        let (self_wrap_snapshot, self_wrap_edits) = self.sync_through_wrap(cx);
1153
1154        let companion_wrap_data = self.companion.as_ref().and_then(|(companion_dm, _)| {
1155            companion_dm
1156                .update(cx, |dm, cx| dm.sync_through_wrap(cx))
1157                .ok()
1158        });
1159
1160        let companion_ref = self.companion.as_ref().map(|(_, c)| c.read(cx));
1161        let companion_view = companion_wrap_data.as_ref().zip(companion_ref).map(
1162            |((snapshot, edits), companion)| {
1163                CompanionView::new(self.entity_id, snapshot, edits, companion)
1164            },
1165        );
1166
1167        let block_map = self.block_map.read(
1168            self_wrap_snapshot.clone(),
1169            self_wrap_edits.clone(),
1170            companion_view,
1171        );
1172        let block_row = block_map.row_for_block(block_id)?;
1173
1174        if let Some((companion_dm, _)) = &self.companion {
1175            let _ = companion_dm.update(cx, |dm, cx| {
1176                if let Some((companion_snapshot, companion_edits)) = companion_wrap_data {
1177                    let their_companion_ref = dm.companion.as_ref().map(|(_, c)| c.read(cx));
1178                    dm.block_map.read(
1179                        companion_snapshot,
1180                        companion_edits,
1181                        their_companion_ref.map(|c| {
1182                            CompanionView::new(
1183                                dm.entity_id,
1184                                &self_wrap_snapshot,
1185                                &self_wrap_edits,
1186                                c,
1187                            )
1188                        }),
1189                    );
1190                }
1191            });
1192        }
1193
1194        Some(DisplayRow(block_row.0))
1195    }
1196
1197    #[instrument(skip_all)]
1198    pub fn highlight_text(
1199        &mut self,
1200        key: HighlightKey,
1201        ranges: Vec<Range<Anchor>>,
1202        style: HighlightStyle,
1203        merge: bool,
1204        cx: &App,
1205    ) {
1206        let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
1207        let to_insert = match self.text_highlights.remove(&key).filter(|_| merge) {
1208            Some(previous) => {
1209                let mut merged_ranges = previous.1.clone();
1210                for new_range in ranges {
1211                    let i = merged_ranges
1212                        .binary_search_by(|probe| {
1213                            probe.start.cmp(&new_range.start, &multi_buffer_snapshot)
1214                        })
1215                        .unwrap_or_else(|i| i);
1216                    merged_ranges.insert(i, new_range);
1217                }
1218                Arc::new((style, merged_ranges))
1219            }
1220            None => Arc::new((style, ranges)),
1221        };
1222        self.text_highlights.insert(key, to_insert);
1223    }
1224
1225    #[instrument(skip_all)]
1226    pub(crate) fn highlight_inlays(
1227        &mut self,
1228        key: HighlightKey,
1229        highlights: Vec<InlayHighlight>,
1230        style: HighlightStyle,
1231    ) {
1232        for highlight in highlights {
1233            let update = self.inlay_highlights.update(&key, |highlights| {
1234                highlights.insert(highlight.inlay, (style, highlight.clone()))
1235            });
1236            if update.is_none() {
1237                self.inlay_highlights.insert(
1238                    key,
1239                    TreeMap::from_ordered_entries([(highlight.inlay, (style, highlight))]),
1240                );
1241            }
1242        }
1243    }
1244
1245    #[instrument(skip_all)]
1246    pub fn text_highlights(&self, key: HighlightKey) -> Option<(HighlightStyle, &[Range<Anchor>])> {
1247        let highlights = self.text_highlights.get(&key)?;
1248        Some((highlights.0, &highlights.1))
1249    }
1250
1251    pub fn all_text_highlights(
1252        &self,
1253    ) -> impl Iterator<Item = (&HighlightKey, &Arc<(HighlightStyle, Vec<Range<Anchor>>)>)> {
1254        self.text_highlights.iter()
1255    }
1256
1257    pub fn all_semantic_token_highlights(
1258        &self,
1259    ) -> impl Iterator<
1260        Item = (
1261            &BufferId,
1262            &(Arc<[SemanticTokenHighlight]>, Arc<HighlightStyleInterner>),
1263        ),
1264    > {
1265        self.semantic_token_highlights.iter()
1266    }
1267
1268    pub fn clear_highlights(&mut self, key: HighlightKey) -> bool {
1269        let mut cleared = self.text_highlights.remove(&key).is_some();
1270        cleared |= self.inlay_highlights.remove(&key).is_some();
1271        cleared
1272    }
1273
1274    pub fn clear_highlights_with(&mut self, mut f: impl FnMut(&HighlightKey) -> bool) -> bool {
1275        let mut cleared = false;
1276        self.text_highlights.retain(|k, _| {
1277            let b = !f(k);
1278            cleared |= b;
1279            b
1280        });
1281        self.inlay_highlights.retain(|k, _| {
1282            let b = !f(k);
1283            cleared |= b;
1284            b
1285        });
1286        cleared
1287    }
1288
1289    pub fn set_font(&self, font: Font, font_size: Pixels, cx: &mut Context<Self>) -> bool {
1290        self.wrap_map
1291            .update(cx, |map, cx| map.set_font_with_size(font, font_size, cx))
1292    }
1293
1294    pub fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut Context<Self>) -> bool {
1295        self.wrap_map
1296            .update(cx, |map, cx| map.set_wrap_width(width, cx))
1297    }
1298
1299    #[instrument(skip_all)]
1300    pub fn update_fold_widths(
1301        &mut self,
1302        widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
1303        cx: &mut Context<Self>,
1304    ) -> bool {
1305        let snapshot = self.buffer.read(cx).snapshot(cx);
1306        let edits = self.buffer_subscription.consume().into_inner();
1307        let tab_size = Self::tab_size(&self.buffer, cx);
1308
1309        let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
1310        let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
1311        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
1312        let (snapshot, edits) = self
1313            .wrap_map
1314            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
1315        self.block_map.read(snapshot, edits, None);
1316
1317        let (snapshot, edits) = fold_map.update_fold_widths(widths);
1318        let widths_changed = !edits.is_empty();
1319        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
1320        let (self_new_wrap_snapshot, self_new_wrap_edits) = self
1321            .wrap_map
1322            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
1323
1324        self.block_map
1325            .read(self_new_wrap_snapshot, self_new_wrap_edits, None);
1326
1327        widths_changed
1328    }
1329
1330    pub(crate) fn current_inlays(&self) -> impl Iterator<Item = &Inlay> {
1331        self.inlay_map.current_inlays()
1332    }
1333
1334    #[instrument(skip_all)]
1335    pub(crate) fn splice_inlays(
1336        &mut self,
1337        to_remove: &[InlayId],
1338        to_insert: Vec<Inlay>,
1339        cx: &mut Context<Self>,
1340    ) {
1341        if to_remove.is_empty() && to_insert.is_empty() {
1342            return;
1343        }
1344        let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
1345        let edits = self.buffer_subscription.consume().into_inner();
1346        let tab_size = Self::tab_size(&self.buffer, cx);
1347
1348        let companion_wrap_data = self.companion.as_ref().and_then(|(companion_dm, _)| {
1349            companion_dm
1350                .update(cx, |dm, cx| dm.sync_through_wrap(cx))
1351                .ok()
1352        });
1353
1354        let (snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits);
1355        let (snapshot, edits) = self.fold_map.read(snapshot, edits);
1356        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
1357        let (snapshot, edits) = self
1358            .wrap_map
1359            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
1360
1361        {
1362            let companion_ref = self.companion.as_ref().map(|(_, c)| c.read(cx));
1363            let companion_view = companion_wrap_data.as_ref().zip(companion_ref).map(
1364                |((snapshot, edits), companion)| {
1365                    CompanionView::new(self.entity_id, snapshot, edits, companion)
1366                },
1367            );
1368            self.block_map.read(snapshot, edits, companion_view);
1369        }
1370
1371        let (snapshot, edits) = self.inlay_map.splice(to_remove, to_insert);
1372        let (snapshot, edits) = self.fold_map.read(snapshot, edits);
1373        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
1374        let (self_new_wrap_snapshot, self_new_wrap_edits) = self
1375            .wrap_map
1376            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
1377
1378        let (self_wrap_snapshot, self_wrap_edits) =
1379            (self_new_wrap_snapshot.clone(), self_new_wrap_edits.clone());
1380
1381        {
1382            let companion_ref = self.companion.as_ref().map(|(_, c)| c.read(cx));
1383            let companion_view = companion_wrap_data.as_ref().zip(companion_ref).map(
1384                |((snapshot, edits), companion)| {
1385                    CompanionView::new(self.entity_id, snapshot, edits, companion)
1386                },
1387            );
1388            self.block_map
1389                .read(self_new_wrap_snapshot, self_new_wrap_edits, companion_view);
1390        }
1391
1392        if let Some((companion_dm, _)) = &self.companion {
1393            let _ = companion_dm.update(cx, |dm, cx| {
1394                if let Some((companion_snapshot, companion_edits)) = companion_wrap_data {
1395                    let their_companion_ref = dm.companion.as_ref().map(|(_, c)| c.read(cx));
1396                    dm.block_map.read(
1397                        companion_snapshot,
1398                        companion_edits,
1399                        their_companion_ref.map(|c| {
1400                            CompanionView::new(
1401                                dm.entity_id,
1402                                &self_wrap_snapshot,
1403                                &self_wrap_edits,
1404                                c,
1405                            )
1406                        }),
1407                    );
1408                }
1409            });
1410        }
1411    }
1412
1413    #[instrument(skip_all)]
1414    fn tab_size(buffer: &Entity<MultiBuffer>, cx: &App) -> NonZeroU32 {
1415        let buffer = buffer.read(cx).as_singleton().map(|buffer| buffer.read(cx));
1416        let language = buffer
1417            .and_then(|buffer| buffer.language())
1418            .map(|l| l.name());
1419        let file = buffer.and_then(|buffer| buffer.file());
1420        language_settings(language, file, cx).tab_size
1421    }
1422
1423    #[cfg(test)]
1424    pub fn is_rewrapping(&self, cx: &gpui::App) -> bool {
1425        self.wrap_map.read(cx).is_rewrapping()
1426    }
1427
1428    pub fn invalidate_semantic_highlights(&mut self, buffer_id: BufferId) {
1429        self.semantic_token_highlights.remove(&buffer_id);
1430    }
1431}
1432
1433#[derive(Debug, Default)]
1434pub(crate) struct Highlights<'a> {
1435    pub text_highlights: Option<&'a TextHighlights>,
1436    pub inlay_highlights: Option<&'a InlayHighlights>,
1437    pub semantic_token_highlights: Option<&'a SemanticTokensHighlights>,
1438    pub styles: HighlightStyles,
1439}
1440
1441#[derive(Clone, Copy, Debug)]
1442pub struct EditPredictionStyles {
1443    pub insertion: HighlightStyle,
1444    pub whitespace: HighlightStyle,
1445}
1446
1447#[derive(Default, Debug, Clone, Copy)]
1448pub struct HighlightStyles {
1449    pub inlay_hint: Option<HighlightStyle>,
1450    pub edit_prediction: Option<EditPredictionStyles>,
1451}
1452
1453#[derive(Clone)]
1454pub enum ChunkReplacement {
1455    Renderer(ChunkRenderer),
1456    Str(SharedString),
1457}
1458
1459pub struct HighlightedChunk<'a> {
1460    pub text: &'a str,
1461    pub style: Option<HighlightStyle>,
1462    pub is_tab: bool,
1463    pub is_inlay: bool,
1464    pub replacement: Option<ChunkReplacement>,
1465}
1466
1467impl<'a> HighlightedChunk<'a> {
1468    #[instrument(skip_all)]
1469    fn highlight_invisibles(
1470        self,
1471        editor_style: &'a EditorStyle,
1472    ) -> impl Iterator<Item = Self> + 'a {
1473        let mut chars = self.text.chars().peekable();
1474        let mut text = self.text;
1475        let style = self.style;
1476        let is_tab = self.is_tab;
1477        let renderer = self.replacement;
1478        let is_inlay = self.is_inlay;
1479        iter::from_fn(move || {
1480            let mut prefix_len = 0;
1481            while let Some(&ch) = chars.peek() {
1482                if !is_invisible(ch) {
1483                    prefix_len += ch.len_utf8();
1484                    chars.next();
1485                    continue;
1486                }
1487                if prefix_len > 0 {
1488                    let (prefix, suffix) = text.split_at(prefix_len);
1489                    text = suffix;
1490                    return Some(HighlightedChunk {
1491                        text: prefix,
1492                        style,
1493                        is_tab,
1494                        is_inlay,
1495                        replacement: renderer.clone(),
1496                    });
1497                }
1498                chars.next();
1499                let (prefix, suffix) = text.split_at(ch.len_utf8());
1500                text = suffix;
1501                if let Some(replacement) = replacement(ch) {
1502                    let invisible_highlight = HighlightStyle {
1503                        background_color: Some(editor_style.status.hint_background),
1504                        underline: Some(UnderlineStyle {
1505                            color: Some(editor_style.status.hint),
1506                            thickness: px(1.),
1507                            wavy: false,
1508                        }),
1509                        ..Default::default()
1510                    };
1511                    let invisible_style = if let Some(style) = style {
1512                        style.highlight(invisible_highlight)
1513                    } else {
1514                        invisible_highlight
1515                    };
1516                    return Some(HighlightedChunk {
1517                        text: prefix,
1518                        style: Some(invisible_style),
1519                        is_tab: false,
1520                        is_inlay,
1521                        replacement: Some(ChunkReplacement::Str(replacement.into())),
1522                    });
1523                } else {
1524                    let invisible_highlight = HighlightStyle {
1525                        background_color: Some(editor_style.status.hint_background),
1526                        underline: Some(UnderlineStyle {
1527                            color: Some(editor_style.status.hint),
1528                            thickness: px(1.),
1529                            wavy: false,
1530                        }),
1531                        ..Default::default()
1532                    };
1533                    let invisible_style = if let Some(style) = style {
1534                        style.highlight(invisible_highlight)
1535                    } else {
1536                        invisible_highlight
1537                    };
1538
1539                    return Some(HighlightedChunk {
1540                        text: prefix,
1541                        style: Some(invisible_style),
1542                        is_tab: false,
1543                        is_inlay,
1544                        replacement: renderer.clone(),
1545                    });
1546                }
1547            }
1548
1549            if !text.is_empty() {
1550                let remainder = text;
1551                text = "";
1552                Some(HighlightedChunk {
1553                    text: remainder,
1554                    style,
1555                    is_tab,
1556                    is_inlay,
1557                    replacement: renderer.clone(),
1558                })
1559            } else {
1560                None
1561            }
1562        })
1563    }
1564}
1565
1566#[derive(Clone)]
1567pub struct DisplaySnapshot {
1568    pub display_map_id: EntityId,
1569    pub companion_display_snapshot: Option<Arc<DisplaySnapshot>>,
1570    pub crease_snapshot: CreaseSnapshot,
1571    block_snapshot: BlockSnapshot,
1572    text_highlights: TextHighlights,
1573    inlay_highlights: InlayHighlights,
1574    semantic_token_highlights: SemanticTokensHighlights,
1575    clip_at_line_ends: bool,
1576    masked: bool,
1577    diagnostics_max_severity: DiagnosticSeverity,
1578    pub(crate) fold_placeholder: FoldPlaceholder,
1579    /// When true, LSP folding ranges are used via the crease map and the
1580    /// indent-based fallback in `crease_for_buffer_row` is skipped.
1581    pub(crate) use_lsp_folding_ranges: bool,
1582}
1583
1584impl DisplaySnapshot {
1585    pub fn companion_snapshot(&self) -> Option<&DisplaySnapshot> {
1586        self.companion_display_snapshot.as_deref()
1587    }
1588
1589    pub fn wrap_snapshot(&self) -> &WrapSnapshot {
1590        &self.block_snapshot.wrap_snapshot
1591    }
1592    pub fn tab_snapshot(&self) -> &TabSnapshot {
1593        &self.block_snapshot.wrap_snapshot.tab_snapshot
1594    }
1595
1596    pub fn fold_snapshot(&self) -> &FoldSnapshot {
1597        &self.block_snapshot.wrap_snapshot.tab_snapshot.fold_snapshot
1598    }
1599
1600    pub fn inlay_snapshot(&self) -> &InlaySnapshot {
1601        &self
1602            .block_snapshot
1603            .wrap_snapshot
1604            .tab_snapshot
1605            .fold_snapshot
1606            .inlay_snapshot
1607    }
1608
1609    pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot {
1610        &self
1611            .block_snapshot
1612            .wrap_snapshot
1613            .tab_snapshot
1614            .fold_snapshot
1615            .inlay_snapshot
1616            .buffer
1617    }
1618
1619    #[cfg(test)]
1620    pub fn fold_count(&self) -> usize {
1621        self.fold_snapshot().fold_count()
1622    }
1623
1624    pub fn is_empty(&self) -> bool {
1625        self.buffer_snapshot().len() == MultiBufferOffset(0)
1626    }
1627
1628    /// Returns whether tree-sitter syntax highlighting should be used.
1629    /// Returns `false` if any buffer with semantic token highlights has the "full" mode setting,
1630    /// meaning LSP semantic tokens should replace tree-sitter highlighting.
1631    pub fn use_tree_sitter_for_syntax(&self, position: DisplayRow, cx: &App) -> bool {
1632        let position = DisplayPoint::new(position, 0);
1633        let Some((buffer_snapshot, ..)) = self.point_to_buffer_point(position.to_point(self))
1634        else {
1635            return false;
1636        };
1637        let settings = language_settings(
1638            buffer_snapshot.language().map(|l| l.name()),
1639            buffer_snapshot.file(),
1640            cx,
1641        );
1642        settings.semantic_tokens.use_tree_sitter()
1643    }
1644
1645    pub fn row_infos(&self, start_row: DisplayRow) -> impl Iterator<Item = RowInfo> + '_ {
1646        self.block_snapshot.row_infos(BlockRow(start_row.0))
1647    }
1648
1649    pub fn widest_line_number(&self) -> u32 {
1650        self.buffer_snapshot().widest_line_number()
1651    }
1652
1653    #[instrument(skip_all)]
1654    pub fn prev_line_boundary(&self, mut point: MultiBufferPoint) -> (Point, DisplayPoint) {
1655        loop {
1656            let mut inlay_point = self.inlay_snapshot().to_inlay_point(point);
1657            let mut fold_point = self.fold_snapshot().to_fold_point(inlay_point, Bias::Left);
1658            fold_point.0.column = 0;
1659            inlay_point = fold_point.to_inlay_point(self.fold_snapshot());
1660            point = self.inlay_snapshot().to_buffer_point(inlay_point);
1661
1662            let mut display_point = self.point_to_display_point(point, Bias::Left);
1663            *display_point.column_mut() = 0;
1664            let next_point = self.display_point_to_point(display_point, Bias::Left);
1665            if next_point == point {
1666                return (point, display_point);
1667            }
1668            point = next_point;
1669        }
1670    }
1671
1672    #[instrument(skip_all)]
1673    pub fn next_line_boundary(
1674        &self,
1675        mut point: MultiBufferPoint,
1676    ) -> (MultiBufferPoint, DisplayPoint) {
1677        let original_point = point;
1678        loop {
1679            let mut inlay_point = self.inlay_snapshot().to_inlay_point(point);
1680            let mut fold_point = self.fold_snapshot().to_fold_point(inlay_point, Bias::Right);
1681            fold_point.0.column = self.fold_snapshot().line_len(fold_point.row());
1682            inlay_point = fold_point.to_inlay_point(self.fold_snapshot());
1683            point = self.inlay_snapshot().to_buffer_point(inlay_point);
1684
1685            let mut display_point = self.point_to_display_point(point, Bias::Right);
1686            *display_point.column_mut() = self.line_len(display_point.row());
1687            let next_point = self.display_point_to_point(display_point, Bias::Right);
1688            if next_point == point || original_point == point || original_point == next_point {
1689                return (point, display_point);
1690            }
1691            point = next_point;
1692        }
1693    }
1694
1695    // used by line_mode selections and tries to match vim behavior
1696    pub fn expand_to_line(&self, range: Range<Point>) -> Range<Point> {
1697        let new_start = MultiBufferPoint::new(range.start.row, 0);
1698        let new_end = if range.end.column > 0 {
1699            MultiBufferPoint::new(
1700                range.end.row,
1701                self.buffer_snapshot()
1702                    .line_len(MultiBufferRow(range.end.row)),
1703            )
1704        } else {
1705            range.end
1706        };
1707
1708        new_start..new_end
1709    }
1710
1711    #[instrument(skip_all)]
1712    pub fn point_to_display_point(&self, point: MultiBufferPoint, bias: Bias) -> DisplayPoint {
1713        let inlay_point = self.inlay_snapshot().to_inlay_point(point);
1714        let fold_point = self.fold_snapshot().to_fold_point(inlay_point, bias);
1715        let tab_point = self.tab_snapshot().fold_point_to_tab_point(fold_point);
1716        let wrap_point = self.wrap_snapshot().tab_point_to_wrap_point(tab_point);
1717        let block_point = self.block_snapshot.to_block_point(wrap_point);
1718        DisplayPoint(block_point)
1719    }
1720
1721    pub fn display_point_to_point(&self, point: DisplayPoint, bias: Bias) -> Point {
1722        self.inlay_snapshot()
1723            .to_buffer_point(self.display_point_to_inlay_point(point, bias))
1724    }
1725
1726    pub fn display_point_to_inlay_offset(&self, point: DisplayPoint, bias: Bias) -> InlayOffset {
1727        self.inlay_snapshot()
1728            .to_offset(self.display_point_to_inlay_point(point, bias))
1729    }
1730
1731    pub fn anchor_to_inlay_offset(&self, anchor: Anchor) -> InlayOffset {
1732        self.inlay_snapshot()
1733            .to_inlay_offset(anchor.to_offset(self.buffer_snapshot()))
1734    }
1735
1736    pub fn display_point_to_anchor(&self, point: DisplayPoint, bias: Bias) -> Anchor {
1737        self.buffer_snapshot()
1738            .anchor_at(point.to_offset(self, bias), bias)
1739    }
1740
1741    #[instrument(skip_all)]
1742    fn display_point_to_inlay_point(&self, point: DisplayPoint, bias: Bias) -> InlayPoint {
1743        let block_point = point.0;
1744        let wrap_point = self.block_snapshot.to_wrap_point(block_point, bias);
1745        let tab_point = self.wrap_snapshot().to_tab_point(wrap_point);
1746        let fold_point = self
1747            .tab_snapshot()
1748            .tab_point_to_fold_point(tab_point, bias)
1749            .0;
1750        fold_point.to_inlay_point(self.fold_snapshot())
1751    }
1752
1753    #[instrument(skip_all)]
1754    pub fn display_point_to_fold_point(&self, point: DisplayPoint, bias: Bias) -> FoldPoint {
1755        let block_point = point.0;
1756        let wrap_point = self.block_snapshot.to_wrap_point(block_point, bias);
1757        let tab_point = self.wrap_snapshot().to_tab_point(wrap_point);
1758        self.tab_snapshot()
1759            .tab_point_to_fold_point(tab_point, bias)
1760            .0
1761    }
1762
1763    #[instrument(skip_all)]
1764    pub fn fold_point_to_display_point(&self, fold_point: FoldPoint) -> DisplayPoint {
1765        let tab_point = self.tab_snapshot().fold_point_to_tab_point(fold_point);
1766        let wrap_point = self.wrap_snapshot().tab_point_to_wrap_point(tab_point);
1767        let block_point = self.block_snapshot.to_block_point(wrap_point);
1768        DisplayPoint(block_point)
1769    }
1770
1771    pub fn max_point(&self) -> DisplayPoint {
1772        DisplayPoint(self.block_snapshot.max_point())
1773    }
1774
1775    /// Returns text chunks starting at the given display row until the end of the file
1776    #[instrument(skip_all)]
1777    pub fn text_chunks(&self, display_row: DisplayRow) -> impl Iterator<Item = &str> {
1778        self.block_snapshot
1779            .chunks(
1780                BlockRow(display_row.0)..BlockRow(self.max_point().row().next_row().0),
1781                false,
1782                self.masked,
1783                Highlights::default(),
1784            )
1785            .map(|h| h.text)
1786    }
1787
1788    /// Returns text chunks starting at the end of the given display row in reverse until the start of the file
1789    #[instrument(skip_all)]
1790    pub fn reverse_text_chunks(&self, display_row: DisplayRow) -> impl Iterator<Item = &str> {
1791        (0..=display_row.0).rev().flat_map(move |row| {
1792            self.block_snapshot
1793                .chunks(
1794                    BlockRow(row)..BlockRow(row + 1),
1795                    false,
1796                    self.masked,
1797                    Highlights::default(),
1798                )
1799                .map(|h| h.text)
1800                .collect::<Vec<_>>()
1801                .into_iter()
1802                .rev()
1803        })
1804    }
1805
1806    #[instrument(skip_all)]
1807    pub fn chunks(
1808        &self,
1809        display_rows: Range<DisplayRow>,
1810        language_aware: bool,
1811        highlight_styles: HighlightStyles,
1812    ) -> DisplayChunks<'_> {
1813        self.block_snapshot.chunks(
1814            BlockRow(display_rows.start.0)..BlockRow(display_rows.end.0),
1815            language_aware,
1816            self.masked,
1817            Highlights {
1818                text_highlights: Some(&self.text_highlights),
1819                inlay_highlights: Some(&self.inlay_highlights),
1820                semantic_token_highlights: Some(&self.semantic_token_highlights),
1821                styles: highlight_styles,
1822            },
1823        )
1824    }
1825
1826    #[instrument(skip_all)]
1827    pub fn highlighted_chunks<'a>(
1828        &'a self,
1829        display_rows: Range<DisplayRow>,
1830        language_aware: bool,
1831        editor_style: &'a EditorStyle,
1832    ) -> impl Iterator<Item = HighlightedChunk<'a>> {
1833        self.chunks(
1834            display_rows,
1835            language_aware,
1836            HighlightStyles {
1837                inlay_hint: Some(editor_style.inlay_hints_style),
1838                edit_prediction: Some(editor_style.edit_prediction_styles),
1839            },
1840        )
1841        .flat_map(|chunk| {
1842            let syntax_highlight_style = chunk
1843                .syntax_highlight_id
1844                .and_then(|id| id.style(&editor_style.syntax));
1845
1846            let chunk_highlight = chunk.highlight_style.map(|chunk_highlight| {
1847                HighlightStyle {
1848                    // For color inlays, blend the color with the editor background
1849                    // if the color has transparency (alpha < 1.0)
1850                    color: chunk_highlight.color.map(|color| {
1851                        if chunk.is_inlay && !color.is_opaque() {
1852                            editor_style.background.blend(color)
1853                        } else {
1854                            color
1855                        }
1856                    }),
1857                    ..chunk_highlight
1858                }
1859            });
1860
1861            let diagnostic_highlight = chunk
1862                .diagnostic_severity
1863                .filter(|severity| {
1864                    self.diagnostics_max_severity
1865                        .into_lsp()
1866                        .is_some_and(|max_severity| severity <= &max_severity)
1867                })
1868                .map(|severity| HighlightStyle {
1869                    fade_out: chunk
1870                        .is_unnecessary
1871                        .then_some(editor_style.unnecessary_code_fade),
1872                    underline: (chunk.underline
1873                        && editor_style.show_underlines
1874                        && !(chunk.is_unnecessary && severity > lsp::DiagnosticSeverity::WARNING))
1875                        .then(|| {
1876                            let diagnostic_color =
1877                                super::diagnostic_style(severity, &editor_style.status);
1878                            UnderlineStyle {
1879                                color: Some(diagnostic_color),
1880                                thickness: 1.0.into(),
1881                                wavy: true,
1882                            }
1883                        }),
1884                    ..Default::default()
1885                });
1886
1887            let style = [
1888                syntax_highlight_style,
1889                chunk_highlight,
1890                diagnostic_highlight,
1891            ]
1892            .into_iter()
1893            .flatten()
1894            .reduce(|acc, highlight| acc.highlight(highlight));
1895
1896            HighlightedChunk {
1897                text: chunk.text,
1898                style,
1899                is_tab: chunk.is_tab,
1900                is_inlay: chunk.is_inlay,
1901                replacement: chunk.renderer.map(ChunkReplacement::Renderer),
1902            }
1903            .highlight_invisibles(editor_style)
1904        })
1905    }
1906
1907    #[instrument(skip_all)]
1908    pub fn layout_row(
1909        &self,
1910        display_row: DisplayRow,
1911        TextLayoutDetails {
1912            text_system,
1913            editor_style,
1914            rem_size,
1915            scroll_anchor: _,
1916            visible_rows: _,
1917            vertical_scroll_margin: _,
1918        }: &TextLayoutDetails,
1919    ) -> Arc<LineLayout> {
1920        let mut runs = Vec::new();
1921        let mut line = String::new();
1922
1923        let range = display_row..display_row.next_row();
1924        for chunk in self.highlighted_chunks(range, false, editor_style) {
1925            line.push_str(chunk.text);
1926
1927            let text_style = if let Some(style) = chunk.style {
1928                Cow::Owned(editor_style.text.clone().highlight(style))
1929            } else {
1930                Cow::Borrowed(&editor_style.text)
1931            };
1932
1933            runs.push(text_style.to_run(chunk.text.len()))
1934        }
1935
1936        if line.ends_with('\n') {
1937            line.pop();
1938            if let Some(last_run) = runs.last_mut() {
1939                last_run.len -= 1;
1940                if last_run.len == 0 {
1941                    runs.pop();
1942                }
1943            }
1944        }
1945
1946        let font_size = editor_style.text.font_size.to_pixels(*rem_size);
1947        text_system.layout_line(&line, font_size, &runs, None)
1948    }
1949
1950    pub fn x_for_display_point(
1951        &self,
1952        display_point: DisplayPoint,
1953        text_layout_details: &TextLayoutDetails,
1954    ) -> Pixels {
1955        let line = self.layout_row(display_point.row(), text_layout_details);
1956        line.x_for_index(display_point.column() as usize)
1957    }
1958
1959    pub fn display_column_for_x(
1960        &self,
1961        display_row: DisplayRow,
1962        x: Pixels,
1963        details: &TextLayoutDetails,
1964    ) -> u32 {
1965        let layout_line = self.layout_row(display_row, details);
1966        layout_line.closest_index_for_x(x) as u32
1967    }
1968
1969    #[instrument(skip_all)]
1970    pub fn grapheme_at(&self, mut point: DisplayPoint) -> Option<SharedString> {
1971        point = DisplayPoint(self.block_snapshot.clip_point(point.0, Bias::Left));
1972        let chars = self
1973            .text_chunks(point.row())
1974            .flat_map(str::chars)
1975            .skip_while({
1976                let mut column = 0;
1977                move |char| {
1978                    let at_point = column >= point.column();
1979                    column += char.len_utf8() as u32;
1980                    !at_point
1981                }
1982            })
1983            .take_while({
1984                let mut prev = false;
1985                move |char| {
1986                    let now = char.is_ascii();
1987                    let end = char.is_ascii() && (char.is_ascii_whitespace() || prev);
1988                    prev = now;
1989                    !end
1990                }
1991            });
1992        chars.collect::<String>().graphemes(true).next().map(|s| {
1993            if let Some(invisible) = s.chars().next().filter(|&c| is_invisible(c)) {
1994                replacement(invisible).unwrap_or(s).to_owned().into()
1995            } else if s == "\n" {
1996                " ".into()
1997            } else {
1998                s.to_owned().into()
1999            }
2000        })
2001    }
2002
2003    pub fn buffer_chars_at(
2004        &self,
2005        mut offset: MultiBufferOffset,
2006    ) -> impl Iterator<Item = (char, MultiBufferOffset)> + '_ {
2007        self.buffer_snapshot().chars_at(offset).map(move |ch| {
2008            let ret = (ch, offset);
2009            offset += ch.len_utf8();
2010            ret
2011        })
2012    }
2013
2014    pub fn reverse_buffer_chars_at(
2015        &self,
2016        mut offset: MultiBufferOffset,
2017    ) -> impl Iterator<Item = (char, MultiBufferOffset)> + '_ {
2018        self.buffer_snapshot()
2019            .reversed_chars_at(offset)
2020            .map(move |ch| {
2021                offset -= ch.len_utf8();
2022                (ch, offset)
2023            })
2024    }
2025
2026    pub fn clip_point(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint {
2027        let mut clipped = self.block_snapshot.clip_point(point.0, bias);
2028        if self.clip_at_line_ends {
2029            clipped = self.clip_at_line_end(DisplayPoint(clipped)).0
2030        }
2031        DisplayPoint(clipped)
2032    }
2033
2034    pub fn clip_ignoring_line_ends(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint {
2035        DisplayPoint(self.block_snapshot.clip_point(point.0, bias))
2036    }
2037
2038    pub fn clip_at_line_end(&self, display_point: DisplayPoint) -> DisplayPoint {
2039        let mut point = self.display_point_to_point(display_point, Bias::Left);
2040
2041        if point.column != self.buffer_snapshot().line_len(MultiBufferRow(point.row)) {
2042            return display_point;
2043        }
2044        point.column = point.column.saturating_sub(1);
2045        point = self.buffer_snapshot().clip_point(point, Bias::Left);
2046        self.point_to_display_point(point, Bias::Left)
2047    }
2048
2049    pub fn folds_in_range<T>(&self, range: Range<T>) -> impl Iterator<Item = &Fold>
2050    where
2051        T: ToOffset,
2052    {
2053        self.fold_snapshot().folds_in_range(range)
2054    }
2055
2056    pub fn blocks_in_range(
2057        &self,
2058        rows: Range<DisplayRow>,
2059    ) -> impl Iterator<Item = (DisplayRow, &Block)> {
2060        self.block_snapshot
2061            .blocks_in_range(BlockRow(rows.start.0)..BlockRow(rows.end.0))
2062            .map(|(row, block)| (DisplayRow(row.0), block))
2063    }
2064
2065    pub fn sticky_header_excerpt(&self, row: f64) -> Option<StickyHeaderExcerpt<'_>> {
2066        self.block_snapshot.sticky_header_excerpt(row)
2067    }
2068
2069    pub fn block_for_id(&self, id: BlockId) -> Option<Block> {
2070        self.block_snapshot.block_for_id(id)
2071    }
2072
2073    pub fn intersects_fold<T: ToOffset>(&self, offset: T) -> bool {
2074        self.fold_snapshot().intersects_fold(offset)
2075    }
2076
2077    pub fn is_line_folded(&self, buffer_row: MultiBufferRow) -> bool {
2078        self.block_snapshot.is_line_replaced(buffer_row)
2079            || self.fold_snapshot().is_line_folded(buffer_row)
2080    }
2081
2082    pub fn is_block_line(&self, display_row: DisplayRow) -> bool {
2083        self.block_snapshot.is_block_line(BlockRow(display_row.0))
2084    }
2085
2086    pub fn is_folded_buffer_header(&self, display_row: DisplayRow) -> bool {
2087        self.block_snapshot
2088            .is_folded_buffer_header(BlockRow(display_row.0))
2089    }
2090
2091    pub fn soft_wrap_indent(&self, display_row: DisplayRow) -> Option<u32> {
2092        let wrap_row = self
2093            .block_snapshot
2094            .to_wrap_point(BlockPoint::new(BlockRow(display_row.0), 0), Bias::Left)
2095            .row();
2096        self.wrap_snapshot().soft_wrap_indent(wrap_row)
2097    }
2098
2099    pub fn text(&self) -> String {
2100        self.text_chunks(DisplayRow(0)).collect()
2101    }
2102
2103    pub fn line(&self, display_row: DisplayRow) -> String {
2104        let mut result = String::new();
2105        for chunk in self.text_chunks(display_row) {
2106            if let Some(ix) = chunk.find('\n') {
2107                result.push_str(&chunk[0..ix]);
2108                break;
2109            } else {
2110                result.push_str(chunk);
2111            }
2112        }
2113        result
2114    }
2115
2116    pub fn line_indent_for_buffer_row(&self, buffer_row: MultiBufferRow) -> LineIndent {
2117        self.buffer_snapshot().line_indent_for_row(buffer_row)
2118    }
2119
2120    pub fn line_len(&self, row: DisplayRow) -> u32 {
2121        self.block_snapshot.line_len(BlockRow(row.0))
2122    }
2123
2124    pub fn longest_row(&self) -> DisplayRow {
2125        DisplayRow(self.block_snapshot.longest_row().0)
2126    }
2127
2128    pub fn longest_row_in_range(&self, range: Range<DisplayRow>) -> DisplayRow {
2129        let block_range = BlockRow(range.start.0)..BlockRow(range.end.0);
2130        let longest_row = self.block_snapshot.longest_row_in_range(block_range);
2131        DisplayRow(longest_row.0)
2132    }
2133
2134    pub fn starts_indent(&self, buffer_row: MultiBufferRow) -> bool {
2135        let max_row = self.buffer_snapshot().max_row();
2136        if buffer_row >= max_row {
2137            return false;
2138        }
2139
2140        let line_indent = self.line_indent_for_buffer_row(buffer_row);
2141        if line_indent.is_line_blank() {
2142            return false;
2143        }
2144
2145        (buffer_row.0 + 1..=max_row.0)
2146            .find_map(|next_row| {
2147                let next_line_indent = self.line_indent_for_buffer_row(MultiBufferRow(next_row));
2148                if next_line_indent.raw_len() > line_indent.raw_len() {
2149                    Some(true)
2150                } else if !next_line_indent.is_line_blank() {
2151                    Some(false)
2152                } else {
2153                    None
2154                }
2155            })
2156            .unwrap_or(false)
2157    }
2158
2159    #[instrument(skip_all)]
2160    pub fn crease_for_buffer_row(&self, buffer_row: MultiBufferRow) -> Option<Crease<Point>> {
2161        let start =
2162            MultiBufferPoint::new(buffer_row.0, self.buffer_snapshot().line_len(buffer_row));
2163        if let Some(crease) = self
2164            .crease_snapshot
2165            .query_row(buffer_row, self.buffer_snapshot())
2166        {
2167            match crease {
2168                Crease::Inline {
2169                    range,
2170                    placeholder,
2171                    render_toggle,
2172                    render_trailer,
2173                    metadata,
2174                } => Some(Crease::Inline {
2175                    range: range.to_point(self.buffer_snapshot()),
2176                    placeholder: placeholder.clone(),
2177                    render_toggle: render_toggle.clone(),
2178                    render_trailer: render_trailer.clone(),
2179                    metadata: metadata.clone(),
2180                }),
2181                Crease::Block {
2182                    range,
2183                    block_height,
2184                    block_style,
2185                    render_block,
2186                    block_priority,
2187                    render_toggle,
2188                } => Some(Crease::Block {
2189                    range: range.to_point(self.buffer_snapshot()),
2190                    block_height: *block_height,
2191                    block_style: *block_style,
2192                    render_block: render_block.clone(),
2193                    block_priority: *block_priority,
2194                    render_toggle: render_toggle.clone(),
2195                }),
2196            }
2197        } else if !self.use_lsp_folding_ranges
2198            && self.starts_indent(MultiBufferRow(start.row))
2199            && !self.is_line_folded(MultiBufferRow(start.row))
2200        {
2201            let start_line_indent = self.line_indent_for_buffer_row(buffer_row);
2202            let max_point = self.buffer_snapshot().max_point();
2203            let mut end = None;
2204
2205            for row in (buffer_row.0 + 1)..=max_point.row {
2206                let line_indent = self.line_indent_for_buffer_row(MultiBufferRow(row));
2207                if !line_indent.is_line_blank()
2208                    && line_indent.raw_len() <= start_line_indent.raw_len()
2209                {
2210                    let prev_row = row - 1;
2211                    end = Some(Point::new(
2212                        prev_row,
2213                        self.buffer_snapshot().line_len(MultiBufferRow(prev_row)),
2214                    ));
2215                    break;
2216                }
2217            }
2218
2219            let mut row_before_line_breaks = end.unwrap_or(max_point);
2220            while row_before_line_breaks.row > start.row
2221                && self
2222                    .buffer_snapshot()
2223                    .is_line_blank(MultiBufferRow(row_before_line_breaks.row))
2224            {
2225                row_before_line_breaks.row -= 1;
2226            }
2227
2228            row_before_line_breaks = Point::new(
2229                row_before_line_breaks.row,
2230                self.buffer_snapshot()
2231                    .line_len(MultiBufferRow(row_before_line_breaks.row)),
2232            );
2233
2234            Some(Crease::Inline {
2235                range: start..row_before_line_breaks,
2236                placeholder: self.fold_placeholder.clone(),
2237                render_toggle: None,
2238                render_trailer: None,
2239                metadata: None,
2240            })
2241        } else {
2242            None
2243        }
2244    }
2245
2246    #[cfg(any(test, feature = "test-support"))]
2247    #[instrument(skip_all)]
2248    pub fn text_highlight_ranges(
2249        &self,
2250        key: HighlightKey,
2251    ) -> Option<Arc<(HighlightStyle, Vec<Range<Anchor>>)>> {
2252        self.text_highlights.get(&key).cloned()
2253    }
2254
2255    #[cfg(any(test, feature = "test-support"))]
2256    #[instrument(skip_all)]
2257    pub fn all_text_highlight_ranges(
2258        &self,
2259        f: impl Fn(&HighlightKey) -> bool,
2260    ) -> Vec<(gpui::Hsla, Range<Point>)> {
2261        use itertools::Itertools;
2262
2263        self.text_highlights
2264            .iter()
2265            .filter(|(key, _)| f(key))
2266            .map(|(_, value)| value.clone())
2267            .flat_map(|ranges| {
2268                ranges
2269                    .1
2270                    .iter()
2271                    .flat_map(|range| {
2272                        Some((ranges.0.color?, range.to_point(self.buffer_snapshot())))
2273                    })
2274                    .collect::<Vec<_>>()
2275            })
2276            .sorted_by_key(|(_, range)| range.start)
2277            .collect()
2278    }
2279
2280    #[allow(unused)]
2281    #[cfg(any(test, feature = "test-support"))]
2282    pub(crate) fn inlay_highlights(
2283        &self,
2284        key: HighlightKey,
2285    ) -> Option<&TreeMap<InlayId, (HighlightStyle, InlayHighlight)>> {
2286        self.inlay_highlights.get(&key)
2287    }
2288
2289    pub fn buffer_header_height(&self) -> u32 {
2290        self.block_snapshot.buffer_header_height
2291    }
2292
2293    pub fn excerpt_header_height(&self) -> u32 {
2294        self.block_snapshot.excerpt_header_height
2295    }
2296
2297    /// Given a `DisplayPoint`, returns another `DisplayPoint` corresponding to
2298    /// the start of the buffer row that is a given number of buffer rows away
2299    /// from the provided point.
2300    ///
2301    /// This moves by buffer rows instead of display rows, a distinction that is
2302    /// important when soft wrapping is enabled.
2303    #[instrument(skip_all)]
2304    pub fn start_of_relative_buffer_row(&self, point: DisplayPoint, times: isize) -> DisplayPoint {
2305        let start = self.display_point_to_fold_point(point, Bias::Left);
2306        let target = start.row() as isize + times;
2307        let new_row = (target.max(0) as u32).min(self.fold_snapshot().max_point().row());
2308
2309        self.clip_point(
2310            self.fold_point_to_display_point(
2311                self.fold_snapshot()
2312                    .clip_point(FoldPoint::new(new_row, 0), Bias::Right),
2313            ),
2314            Bias::Right,
2315        )
2316    }
2317}
2318
2319impl std::ops::Deref for DisplaySnapshot {
2320    type Target = BlockSnapshot;
2321
2322    fn deref(&self) -> &Self::Target {
2323        &self.block_snapshot
2324    }
2325}
2326
2327/// A zero-indexed point in a text buffer consisting of a row and column adjusted for inserted blocks.
2328#[derive(Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq)]
2329pub struct DisplayPoint(BlockPoint);
2330
2331impl Debug for DisplayPoint {
2332    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2333        f.write_fmt(format_args!(
2334            "DisplayPoint({}, {})",
2335            self.row().0,
2336            self.column()
2337        ))
2338    }
2339}
2340
2341impl Add for DisplayPoint {
2342    type Output = Self;
2343
2344    fn add(self, other: Self) -> Self::Output {
2345        DisplayPoint(BlockPoint(self.0.0 + other.0.0))
2346    }
2347}
2348
2349impl Sub for DisplayPoint {
2350    type Output = Self;
2351
2352    fn sub(self, other: Self) -> Self::Output {
2353        DisplayPoint(BlockPoint(self.0.0 - other.0.0))
2354    }
2355}
2356
2357#[derive(Debug, Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq, Deserialize, Hash)]
2358#[serde(transparent)]
2359pub struct DisplayRow(pub u32);
2360
2361impl DisplayRow {
2362    pub(crate) fn as_display_point(&self) -> DisplayPoint {
2363        DisplayPoint::new(*self, 0)
2364    }
2365}
2366
2367impl Add<DisplayRow> for DisplayRow {
2368    type Output = Self;
2369
2370    fn add(self, other: Self) -> Self::Output {
2371        DisplayRow(self.0 + other.0)
2372    }
2373}
2374
2375impl Add<u32> for DisplayRow {
2376    type Output = Self;
2377
2378    fn add(self, other: u32) -> Self::Output {
2379        DisplayRow(self.0 + other)
2380    }
2381}
2382
2383impl Sub<DisplayRow> for DisplayRow {
2384    type Output = Self;
2385
2386    fn sub(self, other: Self) -> Self::Output {
2387        DisplayRow(self.0 - other.0)
2388    }
2389}
2390
2391impl Sub<u32> for DisplayRow {
2392    type Output = Self;
2393
2394    fn sub(self, other: u32) -> Self::Output {
2395        DisplayRow(self.0 - other)
2396    }
2397}
2398
2399impl DisplayPoint {
2400    pub fn new(row: DisplayRow, column: u32) -> Self {
2401        Self(BlockPoint(Point::new(row.0, column)))
2402    }
2403
2404    pub fn zero() -> Self {
2405        Self::new(DisplayRow(0), 0)
2406    }
2407
2408    pub fn is_zero(&self) -> bool {
2409        self.0.is_zero()
2410    }
2411
2412    pub fn row(self) -> DisplayRow {
2413        DisplayRow(self.0.row)
2414    }
2415
2416    pub fn column(self) -> u32 {
2417        self.0.column
2418    }
2419
2420    pub fn row_mut(&mut self) -> &mut u32 {
2421        &mut self.0.row
2422    }
2423
2424    pub fn column_mut(&mut self) -> &mut u32 {
2425        &mut self.0.column
2426    }
2427
2428    pub fn to_point(self, map: &DisplaySnapshot) -> Point {
2429        map.display_point_to_point(self, Bias::Left)
2430    }
2431
2432    pub fn to_offset(self, map: &DisplaySnapshot, bias: Bias) -> MultiBufferOffset {
2433        let wrap_point = map.block_snapshot.to_wrap_point(self.0, bias);
2434        let tab_point = map.wrap_snapshot().to_tab_point(wrap_point);
2435        let fold_point = map
2436            .tab_snapshot()
2437            .tab_point_to_fold_point(tab_point, bias)
2438            .0;
2439        let inlay_point = fold_point.to_inlay_point(map.fold_snapshot());
2440        map.inlay_snapshot()
2441            .to_buffer_offset(map.inlay_snapshot().to_offset(inlay_point))
2442    }
2443}
2444
2445impl ToDisplayPoint for MultiBufferOffset {
2446    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
2447        map.point_to_display_point(self.to_point(map.buffer_snapshot()), Bias::Left)
2448    }
2449}
2450
2451impl ToDisplayPoint for MultiBufferOffsetUtf16 {
2452    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
2453        self.to_offset(map.buffer_snapshot()).to_display_point(map)
2454    }
2455}
2456
2457impl ToDisplayPoint for Point {
2458    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
2459        map.point_to_display_point(*self, Bias::Left)
2460    }
2461}
2462
2463impl ToDisplayPoint for Anchor {
2464    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
2465        self.to_point(map.buffer_snapshot()).to_display_point(map)
2466    }
2467}
2468
2469#[cfg(test)]
2470pub mod tests {
2471    use super::*;
2472    use crate::{
2473        movement,
2474        test::{marked_display_snapshot, test_font},
2475    };
2476    use Bias::*;
2477    use block_map::BlockPlacement;
2478    use gpui::{
2479        App, AppContext as _, BorrowAppContext, Element, Hsla, Rgba, div, font, observe, px,
2480    };
2481    use language::{
2482        Buffer, Diagnostic, DiagnosticEntry, DiagnosticSet, Language, LanguageConfig,
2483        LanguageMatcher,
2484    };
2485    use lsp::LanguageServerId;
2486
2487    use rand::{Rng, prelude::*};
2488    use settings::{SettingsContent, SettingsStore};
2489    use smol::stream::StreamExt;
2490    use std::{env, sync::Arc};
2491    use text::PointUtf16;
2492    use theme::{LoadThemes, SyntaxTheme};
2493    use unindent::Unindent as _;
2494    use util::test::{marked_text_ranges, sample_text};
2495
2496    #[gpui::test(iterations = 100)]
2497    async fn test_random_display_map(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
2498        cx.background_executor.set_block_on_ticks(0..=50);
2499        let operations = env::var("OPERATIONS")
2500            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2501            .unwrap_or(10);
2502
2503        let mut tab_size = rng.random_range(1..=4);
2504        let buffer_start_excerpt_header_height = rng.random_range(1..=5);
2505        let excerpt_header_height = rng.random_range(1..=5);
2506        let font_size = px(14.0);
2507        let max_wrap_width = 300.0;
2508        let mut wrap_width = if rng.random_bool(0.1) {
2509            None
2510        } else {
2511            Some(px(rng.random_range(0.0..=max_wrap_width)))
2512        };
2513
2514        log::info!("tab size: {}", tab_size);
2515        log::info!("wrap width: {:?}", wrap_width);
2516
2517        cx.update(|cx| {
2518            init_test(cx, |s| {
2519                s.project.all_languages.defaults.tab_size = NonZeroU32::new(tab_size)
2520            });
2521        });
2522
2523        let buffer = cx.update(|cx| {
2524            if rng.random() {
2525                let len = rng.random_range(0..10);
2526                let text = util::RandomCharIter::new(&mut rng)
2527                    .take(len)
2528                    .collect::<String>();
2529                MultiBuffer::build_simple(&text, cx)
2530            } else {
2531                MultiBuffer::build_random(&mut rng, cx)
2532            }
2533        });
2534
2535        let font = test_font();
2536        let map = cx.new(|cx| {
2537            DisplayMap::new(
2538                buffer.clone(),
2539                font,
2540                font_size,
2541                wrap_width,
2542                buffer_start_excerpt_header_height,
2543                excerpt_header_height,
2544                FoldPlaceholder::test(),
2545                DiagnosticSeverity::Warning,
2546                cx,
2547            )
2548        });
2549        let mut notifications = observe(&map, cx);
2550        let mut fold_count = 0;
2551        let mut blocks = Vec::new();
2552
2553        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
2554        log::info!("buffer text: {:?}", snapshot.buffer_snapshot().text());
2555        log::info!("fold text: {:?}", snapshot.fold_snapshot().text());
2556        log::info!("tab text: {:?}", snapshot.tab_snapshot().text());
2557        log::info!("wrap text: {:?}", snapshot.wrap_snapshot().text());
2558        log::info!("block text: {:?}", snapshot.block_snapshot.text());
2559        log::info!("display text: {:?}", snapshot.text());
2560
2561        for _i in 0..operations {
2562            match rng.random_range(0..100) {
2563                0..=19 => {
2564                    wrap_width = if rng.random_bool(0.2) {
2565                        None
2566                    } else {
2567                        Some(px(rng.random_range(0.0..=max_wrap_width)))
2568                    };
2569                    log::info!("setting wrap width to {:?}", wrap_width);
2570                    map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
2571                }
2572                20..=29 => {
2573                    let mut tab_sizes = vec![1, 2, 3, 4];
2574                    tab_sizes.remove((tab_size - 1) as usize);
2575                    tab_size = *tab_sizes.choose(&mut rng).unwrap();
2576                    log::info!("setting tab size to {:?}", tab_size);
2577                    cx.update(|cx| {
2578                        cx.update_global::<SettingsStore, _>(|store, cx| {
2579                            store.update_user_settings(cx, |s| {
2580                                s.project.all_languages.defaults.tab_size =
2581                                    NonZeroU32::new(tab_size);
2582                            });
2583                        });
2584                    });
2585                }
2586                30..=44 => {
2587                    map.update(cx, |map, cx| {
2588                        if rng.random() || blocks.is_empty() {
2589                            let snapshot = map.snapshot(cx);
2590                            let buffer = snapshot.buffer_snapshot();
2591                            let block_properties = (0..rng.random_range(1..=1))
2592                                .map(|_| {
2593                                    let position = buffer.anchor_after(buffer.clip_offset(
2594                                        rng.random_range(MultiBufferOffset(0)..=buffer.len()),
2595                                        Bias::Left,
2596                                    ));
2597
2598                                    let placement = if rng.random() {
2599                                        BlockPlacement::Above(position)
2600                                    } else {
2601                                        BlockPlacement::Below(position)
2602                                    };
2603                                    let height = rng.random_range(1..5);
2604                                    log::info!(
2605                                        "inserting block {:?} with height {}",
2606                                        placement.as_ref().map(|p| p.to_point(&buffer)),
2607                                        height
2608                                    );
2609                                    let priority = rng.random_range(1..100);
2610                                    BlockProperties {
2611                                        placement,
2612                                        style: BlockStyle::Fixed,
2613                                        height: Some(height),
2614                                        render: Arc::new(|_| div().into_any()),
2615                                        priority,
2616                                    }
2617                                })
2618                                .collect::<Vec<_>>();
2619                            blocks.extend(map.insert_blocks(block_properties, cx));
2620                        } else {
2621                            blocks.shuffle(&mut rng);
2622                            let remove_count = rng.random_range(1..=4.min(blocks.len()));
2623                            let block_ids_to_remove = (0..remove_count)
2624                                .map(|_| blocks.remove(rng.random_range(0..blocks.len())))
2625                                .collect();
2626                            log::info!("removing block ids {:?}", block_ids_to_remove);
2627                            map.remove_blocks(block_ids_to_remove, cx);
2628                        }
2629                    });
2630                }
2631                45..=79 => {
2632                    let mut ranges = Vec::new();
2633                    for _ in 0..rng.random_range(1..=3) {
2634                        buffer.read_with(cx, |buffer, cx| {
2635                            let buffer = buffer.read(cx);
2636                            let end = buffer.clip_offset(
2637                                rng.random_range(MultiBufferOffset(0)..=buffer.len()),
2638                                Right,
2639                            );
2640                            let start = buffer
2641                                .clip_offset(rng.random_range(MultiBufferOffset(0)..=end), Left);
2642                            ranges.push(start..end);
2643                        });
2644                    }
2645
2646                    if rng.random() && fold_count > 0 {
2647                        log::info!("unfolding ranges: {:?}", ranges);
2648                        map.update(cx, |map, cx| {
2649                            map.unfold_intersecting(ranges, true, cx);
2650                        });
2651                    } else {
2652                        log::info!("folding ranges: {:?}", ranges);
2653                        map.update(cx, |map, cx| {
2654                            map.fold(
2655                                ranges
2656                                    .into_iter()
2657                                    .map(|range| Crease::simple(range, FoldPlaceholder::test()))
2658                                    .collect(),
2659                                cx,
2660                            );
2661                        });
2662                    }
2663                }
2664                _ => {
2665                    buffer.update(cx, |buffer, cx| buffer.randomly_mutate(&mut rng, 5, cx));
2666                }
2667            }
2668
2669            if map.read_with(cx, |map, cx| map.is_rewrapping(cx)) {
2670                notifications.next().await.unwrap();
2671            }
2672
2673            let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
2674            fold_count = snapshot.fold_count();
2675            log::info!("buffer text: {:?}", snapshot.buffer_snapshot().text());
2676            log::info!("fold text: {:?}", snapshot.fold_snapshot().text());
2677            log::info!("tab text: {:?}", snapshot.tab_snapshot().text());
2678            log::info!("wrap text: {:?}", snapshot.wrap_snapshot().text());
2679            log::info!("block text: {:?}", snapshot.block_snapshot.text());
2680            log::info!("display text: {:?}", snapshot.text());
2681
2682            // Line boundaries
2683            let buffer = snapshot.buffer_snapshot();
2684            for _ in 0..5 {
2685                let row = rng.random_range(0..=buffer.max_point().row);
2686                let column = rng.random_range(0..=buffer.line_len(MultiBufferRow(row)));
2687                let point = buffer.clip_point(Point::new(row, column), Left);
2688
2689                let (prev_buffer_bound, prev_display_bound) = snapshot.prev_line_boundary(point);
2690                let (next_buffer_bound, next_display_bound) = snapshot.next_line_boundary(point);
2691
2692                assert!(prev_buffer_bound <= point);
2693                assert!(next_buffer_bound >= point);
2694                assert_eq!(prev_buffer_bound.column, 0);
2695                assert_eq!(prev_display_bound.column(), 0);
2696                if next_buffer_bound < buffer.max_point() {
2697                    assert_eq!(buffer.chars_at(next_buffer_bound).next(), Some('\n'));
2698                }
2699
2700                assert_eq!(
2701                    prev_display_bound,
2702                    prev_buffer_bound.to_display_point(&snapshot),
2703                    "row boundary before {:?}. reported buffer row boundary: {:?}",
2704                    point,
2705                    prev_buffer_bound
2706                );
2707                assert_eq!(
2708                    next_display_bound,
2709                    next_buffer_bound.to_display_point(&snapshot),
2710                    "display row boundary after {:?}. reported buffer row boundary: {:?}",
2711                    point,
2712                    next_buffer_bound
2713                );
2714                assert_eq!(
2715                    prev_buffer_bound,
2716                    prev_display_bound.to_point(&snapshot),
2717                    "row boundary before {:?}. reported display row boundary: {:?}",
2718                    point,
2719                    prev_display_bound
2720                );
2721                assert_eq!(
2722                    next_buffer_bound,
2723                    next_display_bound.to_point(&snapshot),
2724                    "row boundary after {:?}. reported display row boundary: {:?}",
2725                    point,
2726                    next_display_bound
2727                );
2728            }
2729
2730            // Movement
2731            let min_point = snapshot.clip_point(DisplayPoint::new(DisplayRow(0), 0), Left);
2732            let max_point = snapshot.clip_point(snapshot.max_point(), Right);
2733            for _ in 0..5 {
2734                let row = rng.random_range(0..=snapshot.max_point().row().0);
2735                let column = rng.random_range(0..=snapshot.line_len(DisplayRow(row)));
2736                let point = snapshot.clip_point(DisplayPoint::new(DisplayRow(row), column), Left);
2737
2738                log::info!("Moving from point {:?}", point);
2739
2740                let moved_right = movement::right(&snapshot, point);
2741                log::info!("Right {:?}", moved_right);
2742                if point < max_point {
2743                    assert!(moved_right > point);
2744                    if point.column() == snapshot.line_len(point.row())
2745                        || snapshot.soft_wrap_indent(point.row()).is_some()
2746                            && point.column() == snapshot.line_len(point.row()) - 1
2747                    {
2748                        assert!(moved_right.row() > point.row());
2749                    }
2750                } else {
2751                    assert_eq!(moved_right, point);
2752                }
2753
2754                let moved_left = movement::left(&snapshot, point);
2755                log::info!("Left {:?}", moved_left);
2756                if point > min_point {
2757                    assert!(moved_left < point);
2758                    if point.column() == 0 {
2759                        assert!(moved_left.row() < point.row());
2760                    }
2761                } else {
2762                    assert_eq!(moved_left, point);
2763                }
2764            }
2765        }
2766    }
2767
2768    #[gpui::test(retries = 5)]
2769    async fn test_soft_wraps(cx: &mut gpui::TestAppContext) {
2770        cx.background_executor
2771            .set_block_on_ticks(usize::MAX..=usize::MAX);
2772        cx.update(|cx| {
2773            init_test(cx, |_| {});
2774        });
2775
2776        let mut cx = crate::test::editor_test_context::EditorTestContext::new(cx).await;
2777        let editor = cx.editor.clone();
2778        let window = cx.window;
2779
2780        _ = cx.update_window(window, |_, window, cx| {
2781            let text_layout_details =
2782                editor.update(cx, |editor, cx| editor.text_layout_details(window, cx));
2783
2784            let font_size = px(12.0);
2785            let wrap_width = Some(px(96.));
2786
2787            let text = "one two three four five\nsix seven eight";
2788            let buffer = MultiBuffer::build_simple(text, cx);
2789            let map = cx.new(|cx| {
2790                DisplayMap::new(
2791                    buffer.clone(),
2792                    font("Helvetica"),
2793                    font_size,
2794                    wrap_width,
2795                    1,
2796                    1,
2797                    FoldPlaceholder::test(),
2798                    DiagnosticSeverity::Warning,
2799                    cx,
2800                )
2801            });
2802
2803            let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
2804            assert_eq!(
2805                snapshot.text_chunks(DisplayRow(0)).collect::<String>(),
2806                "one two \nthree four \nfive\nsix seven \neight"
2807            );
2808            assert_eq!(
2809                snapshot.clip_point(DisplayPoint::new(DisplayRow(0), 8), Bias::Left),
2810                DisplayPoint::new(DisplayRow(0), 7)
2811            );
2812            assert_eq!(
2813                snapshot.clip_point(DisplayPoint::new(DisplayRow(0), 8), Bias::Right),
2814                DisplayPoint::new(DisplayRow(1), 0)
2815            );
2816            assert_eq!(
2817                movement::right(&snapshot, DisplayPoint::new(DisplayRow(0), 7)),
2818                DisplayPoint::new(DisplayRow(1), 0)
2819            );
2820            assert_eq!(
2821                movement::left(&snapshot, DisplayPoint::new(DisplayRow(1), 0)),
2822                DisplayPoint::new(DisplayRow(0), 7)
2823            );
2824
2825            let x = snapshot
2826                .x_for_display_point(DisplayPoint::new(DisplayRow(1), 10), &text_layout_details);
2827            assert_eq!(
2828                movement::up(
2829                    &snapshot,
2830                    DisplayPoint::new(DisplayRow(1), 10),
2831                    language::SelectionGoal::None,
2832                    false,
2833                    &text_layout_details,
2834                ),
2835                (
2836                    DisplayPoint::new(DisplayRow(0), 7),
2837                    language::SelectionGoal::HorizontalPosition(f64::from(x))
2838                )
2839            );
2840            assert_eq!(
2841                movement::down(
2842                    &snapshot,
2843                    DisplayPoint::new(DisplayRow(0), 7),
2844                    language::SelectionGoal::HorizontalPosition(f64::from(x)),
2845                    false,
2846                    &text_layout_details
2847                ),
2848                (
2849                    DisplayPoint::new(DisplayRow(1), 10),
2850                    language::SelectionGoal::HorizontalPosition(f64::from(x))
2851                )
2852            );
2853            assert_eq!(
2854                movement::down(
2855                    &snapshot,
2856                    DisplayPoint::new(DisplayRow(1), 10),
2857                    language::SelectionGoal::HorizontalPosition(f64::from(x)),
2858                    false,
2859                    &text_layout_details
2860                ),
2861                (
2862                    DisplayPoint::new(DisplayRow(2), 4),
2863                    language::SelectionGoal::HorizontalPosition(f64::from(x))
2864                )
2865            );
2866
2867            let ix = MultiBufferOffset(snapshot.buffer_snapshot().text().find("seven").unwrap());
2868            buffer.update(cx, |buffer, cx| {
2869                buffer.edit([(ix..ix, "and ")], None, cx);
2870            });
2871
2872            let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
2873            assert_eq!(
2874                snapshot.text_chunks(DisplayRow(1)).collect::<String>(),
2875                "three four \nfive\nsix and \nseven eight"
2876            );
2877
2878            // Re-wrap on font size changes
2879            map.update(cx, |map, cx| {
2880                map.set_font(font("Helvetica"), font_size + Pixels::from(3.), cx)
2881            });
2882
2883            let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
2884            assert_eq!(
2885                snapshot.text_chunks(DisplayRow(1)).collect::<String>(),
2886                "three \nfour five\nsix and \nseven \neight"
2887            )
2888        });
2889    }
2890
2891    #[gpui::test]
2892    fn test_text_chunks(cx: &mut gpui::App) {
2893        init_test(cx, |_| {});
2894
2895        let text = sample_text(6, 6, 'a');
2896        let buffer = MultiBuffer::build_simple(&text, cx);
2897
2898        let font_size = px(14.0);
2899        let map = cx.new(|cx| {
2900            DisplayMap::new(
2901                buffer.clone(),
2902                font("Helvetica"),
2903                font_size,
2904                None,
2905                1,
2906                1,
2907                FoldPlaceholder::test(),
2908                DiagnosticSeverity::Warning,
2909                cx,
2910            )
2911        });
2912
2913        buffer.update(cx, |buffer, cx| {
2914            buffer.edit(
2915                vec![
2916                    (
2917                        MultiBufferPoint::new(1, 0)..MultiBufferPoint::new(1, 0),
2918                        "\t",
2919                    ),
2920                    (
2921                        MultiBufferPoint::new(1, 1)..MultiBufferPoint::new(1, 1),
2922                        "\t",
2923                    ),
2924                    (
2925                        MultiBufferPoint::new(2, 1)..MultiBufferPoint::new(2, 1),
2926                        "\t",
2927                    ),
2928                ],
2929                None,
2930                cx,
2931            )
2932        });
2933
2934        assert_eq!(
2935            map.update(cx, |map, cx| map.snapshot(cx))
2936                .text_chunks(DisplayRow(1))
2937                .collect::<String>()
2938                .lines()
2939                .next(),
2940            Some("    b   bbbbb")
2941        );
2942        assert_eq!(
2943            map.update(cx, |map, cx| map.snapshot(cx))
2944                .text_chunks(DisplayRow(2))
2945                .collect::<String>()
2946                .lines()
2947                .next(),
2948            Some("c   ccccc")
2949        );
2950    }
2951
2952    #[gpui::test]
2953    fn test_inlays_with_newlines_after_blocks(cx: &mut gpui::TestAppContext) {
2954        cx.update(|cx| init_test(cx, |_| {}));
2955
2956        let buffer = cx.new(|cx| Buffer::local("a", cx));
2957        let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
2958        let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
2959
2960        let font_size = px(14.0);
2961        let map = cx.new(|cx| {
2962            DisplayMap::new(
2963                buffer.clone(),
2964                font("Helvetica"),
2965                font_size,
2966                None,
2967                1,
2968                1,
2969                FoldPlaceholder::test(),
2970                DiagnosticSeverity::Warning,
2971                cx,
2972            )
2973        });
2974
2975        map.update(cx, |map, cx| {
2976            map.insert_blocks(
2977                [BlockProperties {
2978                    placement: BlockPlacement::Above(
2979                        buffer_snapshot.anchor_before(Point::new(0, 0)),
2980                    ),
2981                    height: Some(2),
2982                    style: BlockStyle::Sticky,
2983                    render: Arc::new(|_| div().into_any()),
2984                    priority: 0,
2985                }],
2986                cx,
2987            );
2988        });
2989        map.update(cx, |m, cx| assert_eq!(m.snapshot(cx).text(), "\n\na"));
2990
2991        map.update(cx, |map, cx| {
2992            map.splice_inlays(
2993                &[],
2994                vec![Inlay::edit_prediction(
2995                    0,
2996                    buffer_snapshot.anchor_after(MultiBufferOffset(0)),
2997                    "\n",
2998                )],
2999                cx,
3000            );
3001        });
3002        map.update(cx, |m, cx| assert_eq!(m.snapshot(cx).text(), "\n\n\na"));
3003
3004        // Regression test: updating the display map does not crash when a
3005        // block is immediately followed by a multi-line inlay.
3006        buffer.update(cx, |buffer, cx| {
3007            buffer.edit(
3008                [(MultiBufferOffset(1)..MultiBufferOffset(1), "b")],
3009                None,
3010                cx,
3011            );
3012        });
3013        map.update(cx, |m, cx| assert_eq!(m.snapshot(cx).text(), "\n\n\nab"));
3014    }
3015
3016    #[gpui::test]
3017    async fn test_chunks(cx: &mut gpui::TestAppContext) {
3018        let text = r#"
3019            fn outer() {}
3020
3021            mod module {
3022                fn inner() {}
3023            }"#
3024        .unindent();
3025
3026        let theme =
3027            SyntaxTheme::new_test(vec![("mod.body", Hsla::red()), ("fn.name", Hsla::blue())]);
3028        let language = Arc::new(
3029            Language::new(
3030                LanguageConfig {
3031                    name: "Test".into(),
3032                    matcher: LanguageMatcher {
3033                        path_suffixes: vec![".test".to_string()],
3034                        ..Default::default()
3035                    },
3036                    ..Default::default()
3037                },
3038                Some(tree_sitter_rust::LANGUAGE.into()),
3039            )
3040            .with_highlights_query(
3041                r#"
3042                (mod_item name: (identifier) body: _ @mod.body)
3043                (function_item name: (identifier) @fn.name)
3044                "#,
3045            )
3046            .unwrap(),
3047        );
3048        language.set_theme(&theme);
3049
3050        cx.update(|cx| {
3051            init_test(cx, |s| {
3052                s.project.all_languages.defaults.tab_size = Some(2.try_into().unwrap())
3053            })
3054        });
3055
3056        let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
3057        cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
3058        let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
3059
3060        let font_size = px(14.0);
3061
3062        let map = cx.new(|cx| {
3063            DisplayMap::new(
3064                buffer,
3065                font("Helvetica"),
3066                font_size,
3067                None,
3068                1,
3069                1,
3070                FoldPlaceholder::test(),
3071                DiagnosticSeverity::Warning,
3072                cx,
3073            )
3074        });
3075        assert_eq!(
3076            cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(5), &map, &theme, cx)),
3077            vec![
3078                ("fn ".to_string(), None),
3079                ("outer".to_string(), Some(Hsla::blue())),
3080                ("() {}\n\nmod module ".to_string(), None),
3081                ("{\n    fn ".to_string(), Some(Hsla::red())),
3082                ("inner".to_string(), Some(Hsla::blue())),
3083                ("() {}\n}".to_string(), Some(Hsla::red())),
3084            ]
3085        );
3086        assert_eq!(
3087            cx.update(|cx| syntax_chunks(DisplayRow(3)..DisplayRow(5), &map, &theme, cx)),
3088            vec![
3089                ("    fn ".to_string(), Some(Hsla::red())),
3090                ("inner".to_string(), Some(Hsla::blue())),
3091                ("() {}\n}".to_string(), Some(Hsla::red())),
3092            ]
3093        );
3094
3095        map.update(cx, |map, cx| {
3096            map.fold(
3097                vec![Crease::simple(
3098                    MultiBufferPoint::new(0, 6)..MultiBufferPoint::new(3, 2),
3099                    FoldPlaceholder::test(),
3100                )],
3101                cx,
3102            )
3103        });
3104        assert_eq!(
3105            cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(2), &map, &theme, cx)),
3106            vec![
3107                ("fn ".to_string(), None),
3108                ("out".to_string(), Some(Hsla::blue())),
3109                ("".to_string(), None),
3110                ("  fn ".to_string(), Some(Hsla::red())),
3111                ("inner".to_string(), Some(Hsla::blue())),
3112                ("() {}\n}".to_string(), Some(Hsla::red())),
3113            ]
3114        );
3115    }
3116
3117    #[gpui::test]
3118    async fn test_chunks_with_syntax_highlighting_across_blocks(cx: &mut gpui::TestAppContext) {
3119        cx.background_executor
3120            .set_block_on_ticks(usize::MAX..=usize::MAX);
3121
3122        let text = r#"
3123            const A: &str = "
3124                one
3125                two
3126                three
3127            ";
3128            const B: &str = "four";
3129        "#
3130        .unindent();
3131
3132        let theme = SyntaxTheme::new_test(vec![
3133            ("string", Hsla::red()),
3134            ("punctuation", Hsla::blue()),
3135            ("keyword", Hsla::green()),
3136        ]);
3137        let language = Arc::new(
3138            Language::new(
3139                LanguageConfig {
3140                    name: "Rust".into(),
3141                    ..Default::default()
3142                },
3143                Some(tree_sitter_rust::LANGUAGE.into()),
3144            )
3145            .with_highlights_query(
3146                r#"
3147                (string_literal) @string
3148                "const" @keyword
3149                [":" ";"] @punctuation
3150                "#,
3151            )
3152            .unwrap(),
3153        );
3154        language.set_theme(&theme);
3155
3156        cx.update(|cx| init_test(cx, |_| {}));
3157
3158        let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
3159        cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
3160        let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
3161        let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
3162
3163        let map = cx.new(|cx| {
3164            DisplayMap::new(
3165                buffer,
3166                font("Courier"),
3167                px(16.0),
3168                None,
3169                1,
3170                1,
3171                FoldPlaceholder::test(),
3172                DiagnosticSeverity::Warning,
3173                cx,
3174            )
3175        });
3176
3177        // Insert two blocks in the middle of a multi-line string literal.
3178        // The second block has zero height.
3179        map.update(cx, |map, cx| {
3180            map.insert_blocks(
3181                [
3182                    BlockProperties {
3183                        placement: BlockPlacement::Below(
3184                            buffer_snapshot.anchor_before(Point::new(1, 0)),
3185                        ),
3186                        height: Some(1),
3187                        style: BlockStyle::Sticky,
3188                        render: Arc::new(|_| div().into_any()),
3189                        priority: 0,
3190                    },
3191                    BlockProperties {
3192                        placement: BlockPlacement::Below(
3193                            buffer_snapshot.anchor_before(Point::new(2, 0)),
3194                        ),
3195                        height: None,
3196                        style: BlockStyle::Sticky,
3197                        render: Arc::new(|_| div().into_any()),
3198                        priority: 0,
3199                    },
3200                ],
3201                cx,
3202            )
3203        });
3204
3205        pretty_assertions::assert_eq!(
3206            cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(7), &map, &theme, cx)),
3207            [
3208                ("const".into(), Some(Hsla::green())),
3209                (" A".into(), None),
3210                (":".into(), Some(Hsla::blue())),
3211                (" &str = ".into(), None),
3212                ("\"\n    one\n".into(), Some(Hsla::red())),
3213                ("\n".into(), None),
3214                ("    two\n    three\n\"".into(), Some(Hsla::red())),
3215                (";".into(), Some(Hsla::blue())),
3216                ("\n".into(), None),
3217                ("const".into(), Some(Hsla::green())),
3218                (" B".into(), None),
3219                (":".into(), Some(Hsla::blue())),
3220                (" &str = ".into(), None),
3221                ("\"four\"".into(), Some(Hsla::red())),
3222                (";".into(), Some(Hsla::blue())),
3223                ("\n".into(), None),
3224            ]
3225        );
3226    }
3227
3228    #[gpui::test]
3229    async fn test_chunks_with_diagnostics_across_blocks(cx: &mut gpui::TestAppContext) {
3230        cx.background_executor
3231            .set_block_on_ticks(usize::MAX..=usize::MAX);
3232
3233        let text = r#"
3234            struct A {
3235                b: usize;
3236            }
3237            const c: usize = 1;
3238        "#
3239        .unindent();
3240
3241        cx.update(|cx| init_test(cx, |_| {}));
3242
3243        let buffer = cx.new(|cx| Buffer::local(text, cx));
3244
3245        buffer.update(cx, |buffer, cx| {
3246            buffer.update_diagnostics(
3247                LanguageServerId(0),
3248                DiagnosticSet::new(
3249                    [DiagnosticEntry {
3250                        range: PointUtf16::new(0, 0)..PointUtf16::new(2, 1),
3251                        diagnostic: Diagnostic {
3252                            severity: lsp::DiagnosticSeverity::ERROR,
3253                            group_id: 1,
3254                            message: "hi".into(),
3255                            ..Default::default()
3256                        },
3257                    }],
3258                    buffer,
3259                ),
3260                cx,
3261            )
3262        });
3263
3264        let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
3265        let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
3266
3267        let map = cx.new(|cx| {
3268            DisplayMap::new(
3269                buffer,
3270                font("Courier"),
3271                px(16.0),
3272                None,
3273                1,
3274                1,
3275                FoldPlaceholder::test(),
3276                DiagnosticSeverity::Warning,
3277                cx,
3278            )
3279        });
3280
3281        let black = gpui::black().to_rgb();
3282        let red = gpui::red().to_rgb();
3283
3284        // Insert a block in the middle of a multi-line diagnostic.
3285        map.update(cx, |map, cx| {
3286            map.highlight_text(
3287                HighlightKey::Editor,
3288                vec![
3289                    buffer_snapshot.anchor_before(Point::new(3, 9))
3290                        ..buffer_snapshot.anchor_after(Point::new(3, 14)),
3291                    buffer_snapshot.anchor_before(Point::new(3, 17))
3292                        ..buffer_snapshot.anchor_after(Point::new(3, 18)),
3293                ],
3294                red.into(),
3295                false,
3296                cx,
3297            );
3298            map.insert_blocks(
3299                [BlockProperties {
3300                    placement: BlockPlacement::Below(
3301                        buffer_snapshot.anchor_before(Point::new(1, 0)),
3302                    ),
3303                    height: Some(1),
3304                    style: BlockStyle::Sticky,
3305                    render: Arc::new(|_| div().into_any()),
3306                    priority: 0,
3307                }],
3308                cx,
3309            )
3310        });
3311
3312        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
3313        let mut chunks = Vec::<(String, Option<lsp::DiagnosticSeverity>, Rgba)>::new();
3314        for chunk in snapshot.chunks(DisplayRow(0)..DisplayRow(5), true, Default::default()) {
3315            let color = chunk
3316                .highlight_style
3317                .and_then(|style| style.color)
3318                .map_or(black, |color| color.to_rgb());
3319            if let Some((last_chunk, last_severity, last_color)) = chunks.last_mut()
3320                && *last_severity == chunk.diagnostic_severity
3321                && *last_color == color
3322            {
3323                last_chunk.push_str(chunk.text);
3324                continue;
3325            }
3326
3327            chunks.push((chunk.text.to_string(), chunk.diagnostic_severity, color));
3328        }
3329
3330        assert_eq!(
3331            chunks,
3332            [
3333                (
3334                    "struct A {\n    b: usize;\n".into(),
3335                    Some(lsp::DiagnosticSeverity::ERROR),
3336                    black
3337                ),
3338                ("\n".into(), None, black),
3339                ("}".into(), Some(lsp::DiagnosticSeverity::ERROR), black),
3340                ("\nconst c: ".into(), None, black),
3341                ("usize".into(), None, red),
3342                (" = ".into(), None, black),
3343                ("1".into(), None, red),
3344                (";\n".into(), None, black),
3345            ]
3346        );
3347    }
3348
3349    #[gpui::test]
3350    async fn test_point_translation_with_replace_blocks(cx: &mut gpui::TestAppContext) {
3351        cx.background_executor
3352            .set_block_on_ticks(usize::MAX..=usize::MAX);
3353
3354        cx.update(|cx| init_test(cx, |_| {}));
3355
3356        let buffer = cx.update(|cx| MultiBuffer::build_simple("abcde\nfghij\nklmno\npqrst", cx));
3357        let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
3358        let map = cx.new(|cx| {
3359            DisplayMap::new(
3360                buffer.clone(),
3361                font("Courier"),
3362                px(16.0),
3363                None,
3364                1,
3365                1,
3366                FoldPlaceholder::test(),
3367                DiagnosticSeverity::Warning,
3368                cx,
3369            )
3370        });
3371
3372        let snapshot = map.update(cx, |map, cx| {
3373            map.insert_blocks(
3374                [BlockProperties {
3375                    placement: BlockPlacement::Replace(
3376                        buffer_snapshot.anchor_before(Point::new(1, 2))
3377                            ..=buffer_snapshot.anchor_after(Point::new(2, 3)),
3378                    ),
3379                    height: Some(4),
3380                    style: BlockStyle::Fixed,
3381                    render: Arc::new(|_| div().into_any()),
3382                    priority: 0,
3383                }],
3384                cx,
3385            );
3386            map.snapshot(cx)
3387        });
3388
3389        assert_eq!(snapshot.text(), "abcde\n\n\n\n\npqrst");
3390
3391        let point_to_display_points = [
3392            (Point::new(1, 0), DisplayPoint::new(DisplayRow(1), 0)),
3393            (Point::new(2, 0), DisplayPoint::new(DisplayRow(1), 0)),
3394            (Point::new(3, 0), DisplayPoint::new(DisplayRow(5), 0)),
3395        ];
3396        for (buffer_point, display_point) in point_to_display_points {
3397            assert_eq!(
3398                snapshot.point_to_display_point(buffer_point, Bias::Left),
3399                display_point,
3400                "point_to_display_point({:?}, Bias::Left)",
3401                buffer_point
3402            );
3403            assert_eq!(
3404                snapshot.point_to_display_point(buffer_point, Bias::Right),
3405                display_point,
3406                "point_to_display_point({:?}, Bias::Right)",
3407                buffer_point
3408            );
3409        }
3410
3411        let display_points_to_points = [
3412            (
3413                DisplayPoint::new(DisplayRow(1), 0),
3414                Point::new(1, 0),
3415                Point::new(2, 5),
3416            ),
3417            (
3418                DisplayPoint::new(DisplayRow(2), 0),
3419                Point::new(1, 0),
3420                Point::new(2, 5),
3421            ),
3422            (
3423                DisplayPoint::new(DisplayRow(3), 0),
3424                Point::new(1, 0),
3425                Point::new(2, 5),
3426            ),
3427            (
3428                DisplayPoint::new(DisplayRow(4), 0),
3429                Point::new(1, 0),
3430                Point::new(2, 5),
3431            ),
3432            (
3433                DisplayPoint::new(DisplayRow(5), 0),
3434                Point::new(3, 0),
3435                Point::new(3, 0),
3436            ),
3437        ];
3438        for (display_point, left_buffer_point, right_buffer_point) in display_points_to_points {
3439            assert_eq!(
3440                snapshot.display_point_to_point(display_point, Bias::Left),
3441                left_buffer_point,
3442                "display_point_to_point({:?}, Bias::Left)",
3443                display_point
3444            );
3445            assert_eq!(
3446                snapshot.display_point_to_point(display_point, Bias::Right),
3447                right_buffer_point,
3448                "display_point_to_point({:?}, Bias::Right)",
3449                display_point
3450            );
3451        }
3452    }
3453
3454    #[gpui::test]
3455    async fn test_chunks_with_soft_wrapping(cx: &mut gpui::TestAppContext) {
3456        cx.background_executor
3457            .set_block_on_ticks(usize::MAX..=usize::MAX);
3458
3459        let text = r#"
3460            fn outer() {}
3461
3462            mod module {
3463                fn inner() {}
3464            }"#
3465        .unindent();
3466
3467        let theme =
3468            SyntaxTheme::new_test(vec![("mod.body", Hsla::red()), ("fn.name", Hsla::blue())]);
3469        let language = Arc::new(
3470            Language::new(
3471                LanguageConfig {
3472                    name: "Test".into(),
3473                    matcher: LanguageMatcher {
3474                        path_suffixes: vec![".test".to_string()],
3475                        ..Default::default()
3476                    },
3477                    ..Default::default()
3478                },
3479                Some(tree_sitter_rust::LANGUAGE.into()),
3480            )
3481            .with_highlights_query(
3482                r#"
3483                (mod_item name: (identifier) body: _ @mod.body)
3484                (function_item name: (identifier) @fn.name)
3485                "#,
3486            )
3487            .unwrap(),
3488        );
3489        language.set_theme(&theme);
3490
3491        cx.update(|cx| init_test(cx, |_| {}));
3492
3493        let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
3494        cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
3495        let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
3496
3497        let font_size = px(16.0);
3498
3499        let map = cx.new(|cx| {
3500            DisplayMap::new(
3501                buffer,
3502                font("Courier"),
3503                font_size,
3504                Some(px(40.0)),
3505                1,
3506                1,
3507                FoldPlaceholder::test(),
3508                DiagnosticSeverity::Warning,
3509                cx,
3510            )
3511        });
3512        assert_eq!(
3513            cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(5), &map, &theme, cx)),
3514            [
3515                ("fn \n".to_string(), None),
3516                ("oute".to_string(), Some(Hsla::blue())),
3517                ("\n".to_string(), None),
3518                ("r".to_string(), Some(Hsla::blue())),
3519                ("() \n{}\n\n".to_string(), None),
3520            ]
3521        );
3522        assert_eq!(
3523            cx.update(|cx| syntax_chunks(DisplayRow(3)..DisplayRow(5), &map, &theme, cx)),
3524            [("{}\n\n".to_string(), None)]
3525        );
3526
3527        map.update(cx, |map, cx| {
3528            map.fold(
3529                vec![Crease::simple(
3530                    MultiBufferPoint::new(0, 6)..MultiBufferPoint::new(3, 2),
3531                    FoldPlaceholder::test(),
3532                )],
3533                cx,
3534            )
3535        });
3536        assert_eq!(
3537            cx.update(|cx| syntax_chunks(DisplayRow(1)..DisplayRow(4), &map, &theme, cx)),
3538            [
3539                ("out".to_string(), Some(Hsla::blue())),
3540                ("\n".to_string(), None),
3541                ("  ".to_string(), Some(Hsla::red())),
3542                ("\n".to_string(), None),
3543                ("fn ".to_string(), Some(Hsla::red())),
3544                ("i".to_string(), Some(Hsla::blue())),
3545                ("\n".to_string(), None)
3546            ]
3547        );
3548    }
3549
3550    #[gpui::test]
3551    async fn test_chunks_with_text_highlights(cx: &mut gpui::TestAppContext) {
3552        cx.update(|cx| init_test(cx, |_| {}));
3553
3554        let theme =
3555            SyntaxTheme::new_test(vec![("operator", Hsla::red()), ("string", Hsla::green())]);
3556        let language = Arc::new(
3557            Language::new(
3558                LanguageConfig {
3559                    name: "Test".into(),
3560                    matcher: LanguageMatcher {
3561                        path_suffixes: vec![".test".to_string()],
3562                        ..Default::default()
3563                    },
3564                    ..Default::default()
3565                },
3566                Some(tree_sitter_rust::LANGUAGE.into()),
3567            )
3568            .with_highlights_query(
3569                r#"
3570                ":" @operator
3571                (string_literal) @string
3572                "#,
3573            )
3574            .unwrap(),
3575        );
3576        language.set_theme(&theme);
3577
3578        let (text, highlighted_ranges) = marked_text_ranges(r#"constˇ «a»«:» B = "c «d»""#, false);
3579
3580        let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
3581        cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
3582
3583        let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
3584        let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
3585
3586        let font_size = px(16.0);
3587        let map = cx.new(|cx| {
3588            DisplayMap::new(
3589                buffer,
3590                font("Courier"),
3591                font_size,
3592                None,
3593                1,
3594                1,
3595                FoldPlaceholder::test(),
3596                DiagnosticSeverity::Warning,
3597                cx,
3598            )
3599        });
3600
3601        let style = HighlightStyle {
3602            color: Some(Hsla::blue()),
3603            ..Default::default()
3604        };
3605
3606        map.update(cx, |map, cx| {
3607            map.highlight_text(
3608                HighlightKey::Editor,
3609                highlighted_ranges
3610                    .into_iter()
3611                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
3612                    .map(|range| {
3613                        buffer_snapshot.anchor_before(range.start)
3614                            ..buffer_snapshot.anchor_before(range.end)
3615                    })
3616                    .collect(),
3617                style,
3618                false,
3619                cx,
3620            );
3621        });
3622
3623        assert_eq!(
3624            cx.update(|cx| chunks(DisplayRow(0)..DisplayRow(10), &map, &theme, cx)),
3625            [
3626                ("const ".to_string(), None, None),
3627                ("a".to_string(), None, Some(Hsla::blue())),
3628                (":".to_string(), Some(Hsla::red()), Some(Hsla::blue())),
3629                (" B = ".to_string(), None, None),
3630                ("\"c ".to_string(), Some(Hsla::green()), None),
3631                ("d".to_string(), Some(Hsla::green()), Some(Hsla::blue())),
3632                ("\"".to_string(), Some(Hsla::green()), None),
3633            ]
3634        );
3635    }
3636
3637    #[gpui::test]
3638    fn test_clip_point(cx: &mut gpui::App) {
3639        init_test(cx, |_| {});
3640
3641        fn assert(text: &str, shift_right: bool, bias: Bias, cx: &mut gpui::App) {
3642            let (unmarked_snapshot, mut markers) = marked_display_snapshot(text, cx);
3643
3644            match bias {
3645                Bias::Left => {
3646                    if shift_right {
3647                        *markers[1].column_mut() += 1;
3648                    }
3649
3650                    assert_eq!(unmarked_snapshot.clip_point(markers[1], bias), markers[0])
3651                }
3652                Bias::Right => {
3653                    if shift_right {
3654                        *markers[0].column_mut() += 1;
3655                    }
3656
3657                    assert_eq!(unmarked_snapshot.clip_point(markers[0], bias), markers[1])
3658                }
3659            };
3660        }
3661
3662        use Bias::{Left, Right};
3663        assert("ˇˇα", false, Left, cx);
3664        assert("ˇˇα", true, Left, cx);
3665        assert("ˇˇα", false, Right, cx);
3666        assert("ˇαˇ", true, Right, cx);
3667        assert("ˇˇ✋", false, Left, cx);
3668        assert("ˇˇ✋", true, Left, cx);
3669        assert("ˇˇ✋", false, Right, cx);
3670        assert("ˇ✋ˇ", true, Right, cx);
3671        assert("ˇˇ🍐", false, Left, cx);
3672        assert("ˇˇ🍐", true, Left, cx);
3673        assert("ˇˇ🍐", false, Right, cx);
3674        assert("ˇ🍐ˇ", true, Right, cx);
3675        assert("ˇˇ\t", false, Left, cx);
3676        assert("ˇˇ\t", true, Left, cx);
3677        assert("ˇˇ\t", false, Right, cx);
3678        assert("ˇ\tˇ", true, Right, cx);
3679        assert(" ˇˇ\t", false, Left, cx);
3680        assert(" ˇˇ\t", true, Left, cx);
3681        assert(" ˇˇ\t", false, Right, cx);
3682        assert(" ˇ\tˇ", true, Right, cx);
3683        assert("   ˇˇ\t", false, Left, cx);
3684        assert("   ˇˇ\t", false, Right, cx);
3685    }
3686
3687    #[gpui::test]
3688    fn test_clip_at_line_ends(cx: &mut gpui::App) {
3689        init_test(cx, |_| {});
3690
3691        fn assert(text: &str, cx: &mut gpui::App) {
3692            let (mut unmarked_snapshot, markers) = marked_display_snapshot(text, cx);
3693            unmarked_snapshot.clip_at_line_ends = true;
3694            assert_eq!(
3695                unmarked_snapshot.clip_point(markers[1], Bias::Left),
3696                markers[0]
3697            );
3698        }
3699
3700        assert("ˇˇ", cx);
3701        assert("ˇaˇ", cx);
3702        assert("aˇbˇ", cx);
3703        assert("aˇαˇ", cx);
3704    }
3705
3706    #[gpui::test]
3707    fn test_creases(cx: &mut gpui::App) {
3708        init_test(cx, |_| {});
3709
3710        let text = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll";
3711        let buffer = MultiBuffer::build_simple(text, cx);
3712        let font_size = px(14.0);
3713        cx.new(|cx| {
3714            let mut map = DisplayMap::new(
3715                buffer.clone(),
3716                font("Helvetica"),
3717                font_size,
3718                None,
3719                1,
3720                1,
3721                FoldPlaceholder::test(),
3722                DiagnosticSeverity::Warning,
3723                cx,
3724            );
3725            let snapshot = map.buffer.read(cx).snapshot(cx);
3726            let range =
3727                snapshot.anchor_before(Point::new(2, 0))..snapshot.anchor_after(Point::new(3, 3));
3728
3729            map.crease_map.insert(
3730                [Crease::inline(
3731                    range,
3732                    FoldPlaceholder::test(),
3733                    |_row, _status, _toggle, _window, _cx| div(),
3734                    |_row, _status, _window, _cx| div(),
3735                )],
3736                &map.buffer.read(cx).snapshot(cx),
3737            );
3738
3739            map
3740        });
3741    }
3742
3743    #[gpui::test]
3744    fn test_tabs_with_multibyte_chars(cx: &mut gpui::App) {
3745        init_test(cx, |_| {});
3746
3747        let text = "\t\tα\nβ\t\n🏀β\t\tγ";
3748        let buffer = MultiBuffer::build_simple(text, cx);
3749        let font_size = px(14.0);
3750
3751        let map = cx.new(|cx| {
3752            DisplayMap::new(
3753                buffer.clone(),
3754                font("Helvetica"),
3755                font_size,
3756                None,
3757                1,
3758                1,
3759                FoldPlaceholder::test(),
3760                DiagnosticSeverity::Warning,
3761                cx,
3762            )
3763        });
3764        let map = map.update(cx, |map, cx| map.snapshot(cx));
3765        assert_eq!(map.text(), "✅       α\nβ   \n🏀β      γ");
3766        assert_eq!(
3767            map.text_chunks(DisplayRow(0)).collect::<String>(),
3768            "✅       α\nβ   \n🏀β      γ"
3769        );
3770        assert_eq!(
3771            map.text_chunks(DisplayRow(1)).collect::<String>(),
3772            "β   \n🏀β      γ"
3773        );
3774        assert_eq!(
3775            map.text_chunks(DisplayRow(2)).collect::<String>(),
3776            "🏀β      γ"
3777        );
3778
3779        let point = MultiBufferPoint::new(0, "\t\t".len() as u32);
3780        let display_point = DisplayPoint::new(DisplayRow(0), "".len() as u32);
3781        assert_eq!(point.to_display_point(&map), display_point);
3782        assert_eq!(display_point.to_point(&map), point);
3783
3784        let point = MultiBufferPoint::new(1, "β\t".len() as u32);
3785        let display_point = DisplayPoint::new(DisplayRow(1), "β   ".len() as u32);
3786        assert_eq!(point.to_display_point(&map), display_point);
3787        assert_eq!(display_point.to_point(&map), point,);
3788
3789        let point = MultiBufferPoint::new(2, "🏀β\t\t".len() as u32);
3790        let display_point = DisplayPoint::new(DisplayRow(2), "🏀β      ".len() as u32);
3791        assert_eq!(point.to_display_point(&map), display_point);
3792        assert_eq!(display_point.to_point(&map), point,);
3793
3794        // Display points inside of expanded tabs
3795        assert_eq!(
3796            DisplayPoint::new(DisplayRow(0), "".len() as u32).to_point(&map),
3797            MultiBufferPoint::new(0, "\t".len() as u32),
3798        );
3799        assert_eq!(
3800            DisplayPoint::new(DisplayRow(0), "".len() as u32).to_point(&map),
3801            MultiBufferPoint::new(0, "".len() as u32),
3802        );
3803
3804        // Clipping display points inside of multi-byte characters
3805        assert_eq!(
3806            map.clip_point(
3807                DisplayPoint::new(DisplayRow(0), "".len() as u32 - 1),
3808                Left
3809            ),
3810            DisplayPoint::new(DisplayRow(0), 0)
3811        );
3812        assert_eq!(
3813            map.clip_point(
3814                DisplayPoint::new(DisplayRow(0), "".len() as u32 - 1),
3815                Bias::Right
3816            ),
3817            DisplayPoint::new(DisplayRow(0), "".len() as u32)
3818        );
3819    }
3820
3821    #[gpui::test]
3822    fn test_max_point(cx: &mut gpui::App) {
3823        init_test(cx, |_| {});
3824
3825        let buffer = MultiBuffer::build_simple("aaa\n\t\tbbb", cx);
3826        let font_size = px(14.0);
3827        let map = cx.new(|cx| {
3828            DisplayMap::new(
3829                buffer.clone(),
3830                font("Helvetica"),
3831                font_size,
3832                None,
3833                1,
3834                1,
3835                FoldPlaceholder::test(),
3836                DiagnosticSeverity::Warning,
3837                cx,
3838            )
3839        });
3840        assert_eq!(
3841            map.update(cx, |map, cx| map.snapshot(cx)).max_point(),
3842            DisplayPoint::new(DisplayRow(1), 11)
3843        )
3844    }
3845
3846    fn syntax_chunks(
3847        rows: Range<DisplayRow>,
3848        map: &Entity<DisplayMap>,
3849        theme: &SyntaxTheme,
3850        cx: &mut App,
3851    ) -> Vec<(String, Option<Hsla>)> {
3852        chunks(rows, map, theme, cx)
3853            .into_iter()
3854            .map(|(text, color, _)| (text, color))
3855            .collect()
3856    }
3857
3858    fn chunks(
3859        rows: Range<DisplayRow>,
3860        map: &Entity<DisplayMap>,
3861        theme: &SyntaxTheme,
3862        cx: &mut App,
3863    ) -> Vec<(String, Option<Hsla>, Option<Hsla>)> {
3864        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
3865        let mut chunks: Vec<(String, Option<Hsla>, Option<Hsla>)> = Vec::new();
3866        for chunk in snapshot.chunks(rows, true, HighlightStyles::default()) {
3867            let syntax_color = chunk
3868                .syntax_highlight_id
3869                .and_then(|id| id.style(theme)?.color);
3870            let highlight_color = chunk.highlight_style.and_then(|style| style.color);
3871            if let Some((last_chunk, last_syntax_color, last_highlight_color)) = chunks.last_mut()
3872                && syntax_color == *last_syntax_color
3873                && highlight_color == *last_highlight_color
3874            {
3875                last_chunk.push_str(chunk.text);
3876                continue;
3877            }
3878            chunks.push((chunk.text.to_string(), syntax_color, highlight_color));
3879        }
3880        chunks
3881    }
3882
3883    fn init_test(cx: &mut App, f: impl Fn(&mut SettingsContent)) {
3884        let settings = SettingsStore::test(cx);
3885        cx.set_global(settings);
3886        crate::init(cx);
3887        theme::init(LoadThemes::JustBase, cx);
3888        cx.update_global::<SettingsStore, _>(|store, cx| {
3889            store.update_user_settings(cx, f);
3890        });
3891    }
3892}