display_map.rs

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