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