display_map.rs

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