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