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