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