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