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