display_map.rs

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