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