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