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