display_map.rs

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