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