display_map.rs

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