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