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