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