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