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