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