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    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                false,
1773                self.masked,
1774                Highlights::default(),
1775            )
1776            .map(|h| h.text)
1777    }
1778
1779    /// Returns text chunks starting at the end of the given display row in reverse until the start of the file
1780    #[instrument(skip_all)]
1781    pub fn reverse_text_chunks(&self, display_row: DisplayRow) -> impl Iterator<Item = &str> {
1782        (0..=display_row.0).rev().flat_map(move |row| {
1783            self.block_snapshot
1784                .chunks(
1785                    BlockRow(row)..BlockRow(row + 1),
1786                    false,
1787                    self.masked,
1788                    Highlights::default(),
1789                )
1790                .map(|h| h.text)
1791                .collect::<Vec<_>>()
1792                .into_iter()
1793                .rev()
1794        })
1795    }
1796
1797    #[instrument(skip_all)]
1798    pub fn chunks(
1799        &self,
1800        display_rows: Range<DisplayRow>,
1801        language_aware: bool,
1802        highlight_styles: HighlightStyles,
1803    ) -> DisplayChunks<'_> {
1804        self.block_snapshot.chunks(
1805            BlockRow(display_rows.start.0)..BlockRow(display_rows.end.0),
1806            language_aware,
1807            self.masked,
1808            Highlights {
1809                text_highlights: Some(&self.text_highlights),
1810                inlay_highlights: Some(&self.inlay_highlights),
1811                semantic_token_highlights: Some(&self.semantic_token_highlights),
1812                styles: highlight_styles,
1813            },
1814        )
1815    }
1816
1817    #[instrument(skip_all)]
1818    pub fn highlighted_chunks<'a>(
1819        &'a self,
1820        display_rows: Range<DisplayRow>,
1821        language_aware: bool,
1822        editor_style: &'a EditorStyle,
1823    ) -> impl Iterator<Item = HighlightedChunk<'a>> {
1824        self.chunks(
1825            display_rows,
1826            language_aware,
1827            HighlightStyles {
1828                inlay_hint: Some(editor_style.inlay_hints_style),
1829                edit_prediction: Some(editor_style.edit_prediction_styles),
1830            },
1831        )
1832        .flat_map(|chunk| {
1833            let syntax_highlight_style = chunk
1834                .syntax_highlight_id
1835                .and_then(|id| editor_style.syntax.get(id).cloned());
1836
1837            let chunk_highlight = chunk.highlight_style.map(|chunk_highlight| {
1838                HighlightStyle {
1839                    // For color inlays, blend the color with the editor background
1840                    // if the color has transparency (alpha < 1.0)
1841                    color: chunk_highlight.color.map(|color| {
1842                        if chunk.is_inlay && !color.is_opaque() {
1843                            editor_style.background.blend(color)
1844                        } else {
1845                            color
1846                        }
1847                    }),
1848                    underline: chunk_highlight
1849                        .underline
1850                        .filter(|_| editor_style.show_underlines),
1851                    ..chunk_highlight
1852                }
1853            });
1854
1855            let diagnostic_highlight = chunk
1856                .diagnostic_severity
1857                .filter(|severity| {
1858                    self.diagnostics_max_severity
1859                        .into_lsp()
1860                        .is_some_and(|max_severity| severity <= &max_severity)
1861                })
1862                .map(|severity| HighlightStyle {
1863                    fade_out: chunk
1864                        .is_unnecessary
1865                        .then_some(editor_style.unnecessary_code_fade),
1866                    underline: (chunk.underline
1867                        && editor_style.show_underlines
1868                        && !(chunk.is_unnecessary && severity > lsp::DiagnosticSeverity::WARNING))
1869                        .then(|| {
1870                            let diagnostic_color =
1871                                super::diagnostic_style(severity, &editor_style.status);
1872                            UnderlineStyle {
1873                                color: Some(diagnostic_color),
1874                                thickness: 1.0.into(),
1875                                wavy: true,
1876                            }
1877                        }),
1878                    ..Default::default()
1879                });
1880
1881            let style = [
1882                syntax_highlight_style,
1883                chunk_highlight,
1884                diagnostic_highlight,
1885            ]
1886            .into_iter()
1887            .flatten()
1888            .reduce(|acc, highlight| acc.highlight(highlight));
1889
1890            HighlightedChunk {
1891                text: chunk.text,
1892                style,
1893                is_tab: chunk.is_tab,
1894                is_inlay: chunk.is_inlay,
1895                replacement: chunk.renderer.map(ChunkReplacement::Renderer),
1896            }
1897            .highlight_invisibles(editor_style)
1898        })
1899    }
1900
1901    /// Returns combined highlight styles (tree-sitter syntax + semantic tokens)
1902    /// for a byte range within the specified buffer.
1903    /// Returned ranges are 0-based relative to `buffer_range.start`.
1904    pub(super) fn combined_highlights(
1905        &self,
1906        multibuffer_range: Range<MultiBufferOffset>,
1907        syntax_theme: &theme::SyntaxTheme,
1908    ) -> Vec<(Range<usize>, HighlightStyle)> {
1909        let multibuffer = self.buffer_snapshot();
1910
1911        let chunks = custom_highlights::CustomHighlightsChunks::new(
1912            multibuffer_range,
1913            true,
1914            None,
1915            Some(&self.semantic_token_highlights),
1916            multibuffer,
1917        );
1918
1919        let mut highlights = Vec::new();
1920        let mut offset = 0usize;
1921        for chunk in chunks {
1922            let chunk_len = chunk.text.len();
1923            if chunk_len == 0 {
1924                continue;
1925            }
1926
1927            let syntax_style = chunk
1928                .syntax_highlight_id
1929                .and_then(|id| syntax_theme.get(id).cloned());
1930
1931            let overlay_style = chunk.highlight_style;
1932
1933            let combined = match (syntax_style, overlay_style) {
1934                (Some(syntax), Some(overlay)) => Some(syntax.highlight(overlay)),
1935                (some @ Some(_), None) | (None, some @ Some(_)) => some,
1936                (None, None) => None,
1937            };
1938
1939            if let Some(style) = combined {
1940                highlights.push((offset..offset + chunk_len, style));
1941            }
1942            offset += chunk_len;
1943        }
1944        highlights
1945    }
1946
1947    #[instrument(skip_all)]
1948    pub fn layout_row(
1949        &self,
1950        display_row: DisplayRow,
1951        TextLayoutDetails {
1952            text_system,
1953            editor_style,
1954            rem_size,
1955            scroll_anchor: _,
1956            visible_rows: _,
1957            vertical_scroll_margin: _,
1958        }: &TextLayoutDetails,
1959    ) -> Arc<LineLayout> {
1960        let mut runs = Vec::new();
1961        let mut line = String::new();
1962
1963        let range = display_row..display_row.next_row();
1964        for chunk in self.highlighted_chunks(range, false, editor_style) {
1965            line.push_str(chunk.text);
1966
1967            let text_style = if let Some(style) = chunk.style {
1968                Cow::Owned(editor_style.text.clone().highlight(style))
1969            } else {
1970                Cow::Borrowed(&editor_style.text)
1971            };
1972
1973            runs.push(text_style.to_run(chunk.text.len()))
1974        }
1975
1976        if line.ends_with('\n') {
1977            line.pop();
1978            if let Some(last_run) = runs.last_mut() {
1979                last_run.len -= 1;
1980                if last_run.len == 0 {
1981                    runs.pop();
1982                }
1983            }
1984        }
1985
1986        let font_size = editor_style.text.font_size.to_pixels(*rem_size);
1987        text_system.layout_line(&line, font_size, &runs, None)
1988    }
1989
1990    pub fn x_for_display_point(
1991        &self,
1992        display_point: DisplayPoint,
1993        text_layout_details: &TextLayoutDetails,
1994    ) -> Pixels {
1995        let line = self.layout_row(display_point.row(), text_layout_details);
1996        line.x_for_index(display_point.column() as usize)
1997    }
1998
1999    pub fn display_column_for_x(
2000        &self,
2001        display_row: DisplayRow,
2002        x: Pixels,
2003        details: &TextLayoutDetails,
2004    ) -> u32 {
2005        let layout_line = self.layout_row(display_row, details);
2006        layout_line.closest_index_for_x(x) as u32
2007    }
2008
2009    #[instrument(skip_all)]
2010    pub fn grapheme_at(&self, mut point: DisplayPoint) -> Option<SharedString> {
2011        point = DisplayPoint(self.block_snapshot.clip_point(point.0, Bias::Left));
2012        let chars = self
2013            .text_chunks(point.row())
2014            .flat_map(str::chars)
2015            .skip_while({
2016                let mut column = 0;
2017                move |char| {
2018                    let at_point = column >= point.column();
2019                    column += char.len_utf8() as u32;
2020                    !at_point
2021                }
2022            })
2023            .take_while({
2024                let mut prev = false;
2025                move |char| {
2026                    let now = char.is_ascii();
2027                    let end = char.is_ascii() && (char.is_ascii_whitespace() || prev);
2028                    prev = now;
2029                    !end
2030                }
2031            });
2032        chars.collect::<String>().graphemes(true).next().map(|s| {
2033            if let Some(invisible) = s.chars().next().filter(|&c| is_invisible(c)) {
2034                replacement(invisible).unwrap_or(s).to_owned().into()
2035            } else if s == "\n" {
2036                " ".into()
2037            } else {
2038                s.to_owned().into()
2039            }
2040        })
2041    }
2042
2043    pub fn buffer_chars_at(
2044        &self,
2045        mut offset: MultiBufferOffset,
2046    ) -> impl Iterator<Item = (char, MultiBufferOffset)> + '_ {
2047        self.buffer_snapshot().chars_at(offset).map(move |ch| {
2048            let ret = (ch, offset);
2049            offset += ch.len_utf8();
2050            ret
2051        })
2052    }
2053
2054    pub fn reverse_buffer_chars_at(
2055        &self,
2056        mut offset: MultiBufferOffset,
2057    ) -> impl Iterator<Item = (char, MultiBufferOffset)> + '_ {
2058        self.buffer_snapshot()
2059            .reversed_chars_at(offset)
2060            .map(move |ch| {
2061                offset -= ch.len_utf8();
2062                (ch, offset)
2063            })
2064    }
2065
2066    pub fn clip_point(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint {
2067        let mut clipped = self.block_snapshot.clip_point(point.0, bias);
2068        if self.clip_at_line_ends {
2069            clipped = self.clip_at_line_end(DisplayPoint(clipped)).0
2070        }
2071        DisplayPoint(clipped)
2072    }
2073
2074    pub fn clip_ignoring_line_ends(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint {
2075        DisplayPoint(self.block_snapshot.clip_point(point.0, bias))
2076    }
2077
2078    pub fn clip_at_line_end(&self, display_point: DisplayPoint) -> DisplayPoint {
2079        let mut point = self.display_point_to_point(display_point, Bias::Left);
2080
2081        if point.column != self.buffer_snapshot().line_len(MultiBufferRow(point.row)) {
2082            return display_point;
2083        }
2084        point.column = point.column.saturating_sub(1);
2085        point = self.buffer_snapshot().clip_point(point, Bias::Left);
2086        self.point_to_display_point(point, Bias::Left)
2087    }
2088
2089    pub fn folds_in_range<T>(&self, range: Range<T>) -> impl Iterator<Item = &Fold>
2090    where
2091        T: ToOffset,
2092    {
2093        self.fold_snapshot().folds_in_range(range)
2094    }
2095
2096    pub fn blocks_in_range(
2097        &self,
2098        rows: Range<DisplayRow>,
2099    ) -> impl Iterator<Item = (DisplayRow, &Block)> {
2100        self.block_snapshot
2101            .blocks_in_range(BlockRow(rows.start.0)..BlockRow(rows.end.0))
2102            .map(|(row, block)| (DisplayRow(row.0), block))
2103    }
2104
2105    pub fn sticky_header_excerpt(&self, row: f64) -> Option<StickyHeaderExcerpt<'_>> {
2106        self.block_snapshot.sticky_header_excerpt(row)
2107    }
2108
2109    pub fn block_for_id(&self, id: BlockId) -> Option<Block> {
2110        self.block_snapshot.block_for_id(id)
2111    }
2112
2113    pub fn intersects_fold<T: ToOffset>(&self, offset: T) -> bool {
2114        self.fold_snapshot().intersects_fold(offset)
2115    }
2116
2117    pub fn is_line_folded(&self, buffer_row: MultiBufferRow) -> bool {
2118        self.block_snapshot.is_line_replaced(buffer_row)
2119            || self.fold_snapshot().is_line_folded(buffer_row)
2120    }
2121
2122    pub fn is_block_line(&self, display_row: DisplayRow) -> bool {
2123        self.block_snapshot.is_block_line(BlockRow(display_row.0))
2124    }
2125
2126    pub fn is_folded_buffer_header(&self, display_row: DisplayRow) -> bool {
2127        self.block_snapshot
2128            .is_folded_buffer_header(BlockRow(display_row.0))
2129    }
2130
2131    pub fn soft_wrap_indent(&self, display_row: DisplayRow) -> Option<u32> {
2132        let wrap_row = self
2133            .block_snapshot
2134            .to_wrap_point(BlockPoint::new(BlockRow(display_row.0), 0), Bias::Left)
2135            .row();
2136        self.wrap_snapshot().soft_wrap_indent(wrap_row)
2137    }
2138
2139    pub fn text(&self) -> String {
2140        self.text_chunks(DisplayRow(0)).collect()
2141    }
2142
2143    pub fn line(&self, display_row: DisplayRow) -> String {
2144        let mut result = String::new();
2145        for chunk in self.text_chunks(display_row) {
2146            if let Some(ix) = chunk.find('\n') {
2147                result.push_str(&chunk[0..ix]);
2148                break;
2149            } else {
2150                result.push_str(chunk);
2151            }
2152        }
2153        result
2154    }
2155
2156    pub fn line_indent_for_buffer_row(&self, buffer_row: MultiBufferRow) -> LineIndent {
2157        self.buffer_snapshot().line_indent_for_row(buffer_row)
2158    }
2159
2160    pub fn line_len(&self, row: DisplayRow) -> u32 {
2161        self.block_snapshot.line_len(BlockRow(row.0))
2162    }
2163
2164    pub fn longest_row(&self) -> DisplayRow {
2165        DisplayRow(self.block_snapshot.longest_row().0)
2166    }
2167
2168    pub fn longest_row_in_range(&self, range: Range<DisplayRow>) -> DisplayRow {
2169        let block_range = BlockRow(range.start.0)..BlockRow(range.end.0);
2170        let longest_row = self.block_snapshot.longest_row_in_range(block_range);
2171        DisplayRow(longest_row.0)
2172    }
2173
2174    pub fn starts_indent(&self, buffer_row: MultiBufferRow) -> bool {
2175        let max_row = self.buffer_snapshot().max_row();
2176        if buffer_row >= max_row {
2177            return false;
2178        }
2179
2180        let line_indent = self.line_indent_for_buffer_row(buffer_row);
2181        if line_indent.is_line_blank() {
2182            return false;
2183        }
2184
2185        (buffer_row.0 + 1..=max_row.0)
2186            .find_map(|next_row| {
2187                let next_line_indent = self.line_indent_for_buffer_row(MultiBufferRow(next_row));
2188                if next_line_indent.raw_len() > line_indent.raw_len() {
2189                    Some(true)
2190                } else if !next_line_indent.is_line_blank() {
2191                    Some(false)
2192                } else {
2193                    None
2194                }
2195            })
2196            .unwrap_or(false)
2197    }
2198
2199    /// Returns the indent length of `row` if it starts with a closing bracket.
2200    fn closing_bracket_indent_len(&self, row: u32) -> Option<u32> {
2201        let snapshot = self.buffer_snapshot();
2202        let indent_len = self
2203            .line_indent_for_buffer_row(MultiBufferRow(row))
2204            .raw_len();
2205        let content_start = Point::new(row, indent_len);
2206        let line_text: String = snapshot
2207            .chars_at(content_start)
2208            .take_while(|ch| *ch != '\n')
2209            .collect();
2210
2211        let scope = snapshot.language_scope_at(Point::new(row, 0))?;
2212        if scope
2213            .brackets()
2214            .any(|(pair, _)| line_text.starts_with(&pair.end))
2215        {
2216            return Some(indent_len);
2217        }
2218
2219        None
2220    }
2221
2222    #[instrument(skip_all)]
2223    pub fn crease_for_buffer_row(&self, buffer_row: MultiBufferRow) -> Option<Crease<Point>> {
2224        let start =
2225            MultiBufferPoint::new(buffer_row.0, self.buffer_snapshot().line_len(buffer_row));
2226        if let Some(crease) = self
2227            .crease_snapshot
2228            .query_row(buffer_row, self.buffer_snapshot())
2229        {
2230            match crease {
2231                Crease::Inline {
2232                    range,
2233                    placeholder,
2234                    render_toggle,
2235                    render_trailer,
2236                    metadata,
2237                } => Some(Crease::Inline {
2238                    range: range.to_point(self.buffer_snapshot()),
2239                    placeholder: placeholder.clone(),
2240                    render_toggle: render_toggle.clone(),
2241                    render_trailer: render_trailer.clone(),
2242                    metadata: metadata.clone(),
2243                }),
2244                Crease::Block {
2245                    range,
2246                    block_height,
2247                    block_style,
2248                    render_block,
2249                    block_priority,
2250                    render_toggle,
2251                } => Some(Crease::Block {
2252                    range: range.to_point(self.buffer_snapshot()),
2253                    block_height: *block_height,
2254                    block_style: *block_style,
2255                    render_block: render_block.clone(),
2256                    block_priority: *block_priority,
2257                    render_toggle: render_toggle.clone(),
2258                }),
2259            }
2260        } else if !self.use_lsp_folding_ranges
2261            && self.starts_indent(MultiBufferRow(start.row))
2262            && !self.is_line_folded(MultiBufferRow(start.row))
2263        {
2264            let start_line_indent = self.line_indent_for_buffer_row(buffer_row);
2265            let max_point = self.buffer_snapshot().max_point();
2266            let mut closing_row = None;
2267
2268            for row in (buffer_row.0 + 1)..=max_point.row {
2269                let line_indent = self.line_indent_for_buffer_row(MultiBufferRow(row));
2270                if !line_indent.is_line_blank()
2271                    && line_indent.raw_len() <= start_line_indent.raw_len()
2272                {
2273                    if self
2274                        .buffer_snapshot()
2275                        .language_scope_at(Point::new(row, 0))
2276                        .is_some_and(|scope| {
2277                            matches!(
2278                                scope.override_name(),
2279                                Some("string") | Some("comment") | Some("comment.inclusive")
2280                            )
2281                        })
2282                    {
2283                        continue;
2284                    }
2285
2286                    closing_row = Some(row);
2287                    break;
2288                }
2289            }
2290
2291            let last_non_blank_row = |from_row: u32| -> Point {
2292                let mut row = from_row;
2293                while row > start.row && self.buffer_snapshot().is_line_blank(MultiBufferRow(row)) {
2294                    row -= 1;
2295                }
2296                Point::new(row, self.buffer_snapshot().line_len(MultiBufferRow(row)))
2297            };
2298
2299            let end = if let Some(row) = closing_row {
2300                if let Some(indent_len) = self.closing_bracket_indent_len(row) {
2301                    // Include newline and whitespace before closing delimiter,
2302                    // so it appears on the same display line as the fold placeholder
2303                    Point::new(row, indent_len)
2304                } else {
2305                    last_non_blank_row(row - 1)
2306                }
2307            } else {
2308                last_non_blank_row(max_point.row)
2309            };
2310
2311            Some(Crease::Inline {
2312                range: start..end,
2313                placeholder: self.fold_placeholder.clone(),
2314                render_toggle: None,
2315                render_trailer: None,
2316                metadata: None,
2317            })
2318        } else {
2319            None
2320        }
2321    }
2322
2323    #[cfg(any(test, feature = "test-support"))]
2324    #[instrument(skip_all)]
2325    pub fn text_highlight_ranges(
2326        &self,
2327        key: HighlightKey,
2328    ) -> Option<Arc<(HighlightStyle, Vec<Range<Anchor>>)>> {
2329        self.text_highlights.get(&key).cloned()
2330    }
2331
2332    #[cfg(any(test, feature = "test-support"))]
2333    #[instrument(skip_all)]
2334    pub fn all_text_highlight_ranges(
2335        &self,
2336        f: &dyn Fn(&HighlightKey) -> bool,
2337    ) -> Vec<(gpui::Hsla, Range<Point>)> {
2338        use itertools::Itertools;
2339
2340        self.text_highlights
2341            .iter()
2342            .filter(|(key, _)| f(key))
2343            .map(|(_, value)| value.clone())
2344            .flat_map(|ranges| {
2345                ranges
2346                    .1
2347                    .iter()
2348                    .flat_map(|range| {
2349                        Some((ranges.0.color?, range.to_point(self.buffer_snapshot())))
2350                    })
2351                    .collect::<Vec<_>>()
2352            })
2353            .sorted_by_key(|(_, range)| range.start)
2354            .collect()
2355    }
2356
2357    #[allow(unused)]
2358    #[cfg(any(test, feature = "test-support"))]
2359    pub(crate) fn inlay_highlights(
2360        &self,
2361        key: HighlightKey,
2362    ) -> Option<&TreeMap<InlayId, (HighlightStyle, InlayHighlight)>> {
2363        self.inlay_highlights.get(&key)
2364    }
2365
2366    pub fn buffer_header_height(&self) -> u32 {
2367        self.block_snapshot.buffer_header_height
2368    }
2369
2370    pub fn excerpt_header_height(&self) -> u32 {
2371        self.block_snapshot.excerpt_header_height
2372    }
2373
2374    /// Given a `DisplayPoint`, returns another `DisplayPoint` corresponding to
2375    /// the start of the buffer row that is a given number of buffer rows away
2376    /// from the provided point.
2377    ///
2378    /// This moves by buffer rows instead of display rows, a distinction that is
2379    /// important when soft wrapping is enabled.
2380    #[instrument(skip_all)]
2381    pub fn start_of_relative_buffer_row(&self, point: DisplayPoint, times: isize) -> DisplayPoint {
2382        let start = self.display_point_to_fold_point(point, Bias::Left);
2383        let target = start.row() as isize + times;
2384        let new_row = (target.max(0) as u32).min(self.fold_snapshot().max_point().row());
2385
2386        self.clip_point(
2387            self.fold_point_to_display_point(
2388                self.fold_snapshot()
2389                    .clip_point(FoldPoint::new(new_row, 0), Bias::Right),
2390            ),
2391            Bias::Right,
2392        )
2393    }
2394}
2395
2396impl std::ops::Deref for DisplaySnapshot {
2397    type Target = BlockSnapshot;
2398
2399    fn deref(&self) -> &Self::Target {
2400        &self.block_snapshot
2401    }
2402}
2403
2404/// A zero-indexed point in a text buffer consisting of a row and column adjusted for inserted blocks.
2405#[derive(Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq)]
2406pub struct DisplayPoint(BlockPoint);
2407
2408impl Debug for DisplayPoint {
2409    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2410        f.write_fmt(format_args!(
2411            "DisplayPoint({}, {})",
2412            self.row().0,
2413            self.column()
2414        ))
2415    }
2416}
2417
2418impl Add for DisplayPoint {
2419    type Output = Self;
2420
2421    fn add(self, other: Self) -> Self::Output {
2422        DisplayPoint(BlockPoint(self.0.0 + other.0.0))
2423    }
2424}
2425
2426impl Sub for DisplayPoint {
2427    type Output = Self;
2428
2429    fn sub(self, other: Self) -> Self::Output {
2430        DisplayPoint(BlockPoint(self.0.0 - other.0.0))
2431    }
2432}
2433
2434#[derive(Debug, Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq, Deserialize, Hash)]
2435#[serde(transparent)]
2436pub struct DisplayRow(pub u32);
2437
2438impl DisplayRow {
2439    pub(crate) fn as_display_point(&self) -> DisplayPoint {
2440        DisplayPoint::new(*self, 0)
2441    }
2442}
2443
2444impl Add<DisplayRow> for DisplayRow {
2445    type Output = Self;
2446
2447    fn add(self, other: Self) -> Self::Output {
2448        DisplayRow(self.0 + other.0)
2449    }
2450}
2451
2452impl Add<u32> for DisplayRow {
2453    type Output = Self;
2454
2455    fn add(self, other: u32) -> Self::Output {
2456        DisplayRow(self.0 + other)
2457    }
2458}
2459
2460impl Sub<DisplayRow> for DisplayRow {
2461    type Output = Self;
2462
2463    fn sub(self, other: Self) -> Self::Output {
2464        DisplayRow(self.0 - other.0)
2465    }
2466}
2467
2468impl Sub<u32> for DisplayRow {
2469    type Output = Self;
2470
2471    fn sub(self, other: u32) -> Self::Output {
2472        DisplayRow(self.0 - other)
2473    }
2474}
2475
2476impl DisplayPoint {
2477    pub fn new(row: DisplayRow, column: u32) -> Self {
2478        Self(BlockPoint(Point::new(row.0, column)))
2479    }
2480
2481    pub fn zero() -> Self {
2482        Self::new(DisplayRow(0), 0)
2483    }
2484
2485    pub fn is_zero(&self) -> bool {
2486        self.0.is_zero()
2487    }
2488
2489    pub fn row(self) -> DisplayRow {
2490        DisplayRow(self.0.row)
2491    }
2492
2493    pub fn column(self) -> u32 {
2494        self.0.column
2495    }
2496
2497    pub fn row_mut(&mut self) -> &mut u32 {
2498        &mut self.0.row
2499    }
2500
2501    pub fn column_mut(&mut self) -> &mut u32 {
2502        &mut self.0.column
2503    }
2504
2505    pub fn to_point(self, map: &DisplaySnapshot) -> Point {
2506        map.display_point_to_point(self, Bias::Left)
2507    }
2508
2509    pub fn to_offset(self, map: &DisplaySnapshot, bias: Bias) -> MultiBufferOffset {
2510        let wrap_point = map.block_snapshot.to_wrap_point(self.0, bias);
2511        let tab_point = map.wrap_snapshot().to_tab_point(wrap_point);
2512        let fold_point = map
2513            .tab_snapshot()
2514            .tab_point_to_fold_point(tab_point, bias)
2515            .0;
2516        let inlay_point = fold_point.to_inlay_point(map.fold_snapshot());
2517        map.inlay_snapshot()
2518            .to_buffer_offset(map.inlay_snapshot().to_offset(inlay_point))
2519    }
2520}
2521
2522impl ToDisplayPoint for MultiBufferOffset {
2523    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
2524        map.point_to_display_point(self.to_point(map.buffer_snapshot()), Bias::Left)
2525    }
2526}
2527
2528impl ToDisplayPoint for MultiBufferOffsetUtf16 {
2529    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
2530        self.to_offset(map.buffer_snapshot()).to_display_point(map)
2531    }
2532}
2533
2534impl ToDisplayPoint for Point {
2535    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
2536        map.point_to_display_point(*self, Bias::Left)
2537    }
2538}
2539
2540impl ToDisplayPoint for Anchor {
2541    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
2542        self.to_point(map.buffer_snapshot()).to_display_point(map)
2543    }
2544}
2545
2546#[cfg(test)]
2547pub mod tests {
2548    use super::*;
2549    use crate::{
2550        movement,
2551        test::{marked_display_snapshot, test_font},
2552    };
2553    use Bias::*;
2554    use block_map::BlockPlacement;
2555    use gpui::{
2556        App, AppContext as _, BorrowAppContext, Element, Hsla, Rgba, div, font, observe, px,
2557    };
2558    use language::{
2559        Buffer, Diagnostic, DiagnosticEntry, DiagnosticSet, Language, LanguageConfig,
2560        LanguageMatcher,
2561    };
2562    use lsp::LanguageServerId;
2563
2564    use rand::{Rng, prelude::*};
2565    use settings::{SettingsContent, SettingsStore};
2566    use smol::stream::StreamExt;
2567    use std::{env, sync::Arc};
2568    use text::PointUtf16;
2569    use theme::{LoadThemes, SyntaxTheme};
2570    use unindent::Unindent as _;
2571    use util::test::{marked_text_ranges, sample_text};
2572
2573    #[gpui::test(iterations = 100)]
2574    async fn test_random_display_map(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
2575        cx.background_executor.set_block_on_ticks(0..=50);
2576        let operations = env::var("OPERATIONS")
2577            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2578            .unwrap_or(10);
2579
2580        let mut tab_size = rng.random_range(1..=4);
2581        let buffer_start_excerpt_header_height = rng.random_range(1..=5);
2582        let excerpt_header_height = rng.random_range(1..=5);
2583        let font_size = px(14.0);
2584        let max_wrap_width = 300.0;
2585        let mut wrap_width = if rng.random_bool(0.1) {
2586            None
2587        } else {
2588            Some(px(rng.random_range(0.0..=max_wrap_width)))
2589        };
2590
2591        log::info!("tab size: {}", tab_size);
2592        log::info!("wrap width: {:?}", wrap_width);
2593
2594        cx.update(|cx| {
2595            init_test(cx, &|s| {
2596                s.project.all_languages.defaults.tab_size = NonZeroU32::new(tab_size)
2597            });
2598        });
2599
2600        let buffer = cx.update(|cx| {
2601            if rng.random() {
2602                let len = rng.random_range(0..10);
2603                let text = util::RandomCharIter::new(&mut rng)
2604                    .take(len)
2605                    .collect::<String>();
2606                MultiBuffer::build_simple(&text, cx)
2607            } else {
2608                MultiBuffer::build_random(&mut rng, cx)
2609            }
2610        });
2611
2612        let font = test_font();
2613        let map = cx.new(|cx| {
2614            DisplayMap::new(
2615                buffer.clone(),
2616                font,
2617                font_size,
2618                wrap_width,
2619                buffer_start_excerpt_header_height,
2620                excerpt_header_height,
2621                FoldPlaceholder::test(),
2622                DiagnosticSeverity::Warning,
2623                cx,
2624            )
2625        });
2626        let mut notifications = observe(&map, cx);
2627        let mut fold_count = 0;
2628        let mut blocks = Vec::new();
2629
2630        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
2631        log::info!("buffer text: {:?}", snapshot.buffer_snapshot().text());
2632        log::info!("fold text: {:?}", snapshot.fold_snapshot().text());
2633        log::info!("tab text: {:?}", snapshot.tab_snapshot().text());
2634        log::info!("wrap text: {:?}", snapshot.wrap_snapshot().text());
2635        log::info!("block text: {:?}", snapshot.block_snapshot.text());
2636        log::info!("display text: {:?}", snapshot.text());
2637
2638        for _i in 0..operations {
2639            match rng.random_range(0..100) {
2640                0..=19 => {
2641                    wrap_width = if rng.random_bool(0.2) {
2642                        None
2643                    } else {
2644                        Some(px(rng.random_range(0.0..=max_wrap_width)))
2645                    };
2646                    log::info!("setting wrap width to {:?}", wrap_width);
2647                    map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
2648                }
2649                20..=29 => {
2650                    let mut tab_sizes = vec![1, 2, 3, 4];
2651                    tab_sizes.remove((tab_size - 1) as usize);
2652                    tab_size = *tab_sizes.choose(&mut rng).unwrap();
2653                    log::info!("setting tab size to {:?}", tab_size);
2654                    cx.update(|cx| {
2655                        cx.update_global::<SettingsStore, _>(|store, cx| {
2656                            store.update_user_settings(cx, |s| {
2657                                s.project.all_languages.defaults.tab_size =
2658                                    NonZeroU32::new(tab_size);
2659                            });
2660                        });
2661                    });
2662                }
2663                30..=44 => {
2664                    map.update(cx, |map, cx| {
2665                        if rng.random() || blocks.is_empty() {
2666                            let snapshot = map.snapshot(cx);
2667                            let buffer = snapshot.buffer_snapshot();
2668                            let block_properties = (0..rng.random_range(1..=1))
2669                                .map(|_| {
2670                                    let position = buffer.anchor_after(buffer.clip_offset(
2671                                        rng.random_range(MultiBufferOffset(0)..=buffer.len()),
2672                                        Bias::Left,
2673                                    ));
2674
2675                                    let placement = if rng.random() {
2676                                        BlockPlacement::Above(position)
2677                                    } else {
2678                                        BlockPlacement::Below(position)
2679                                    };
2680                                    let height = rng.random_range(1..5);
2681                                    log::info!(
2682                                        "inserting block {:?} with height {}",
2683                                        placement.as_ref().map(|p| p.to_point(&buffer)),
2684                                        height
2685                                    );
2686                                    let priority = rng.random_range(1..100);
2687                                    BlockProperties {
2688                                        placement,
2689                                        style: BlockStyle::Fixed,
2690                                        height: Some(height),
2691                                        render: Arc::new(|_| div().into_any()),
2692                                        priority,
2693                                    }
2694                                })
2695                                .collect::<Vec<_>>();
2696                            blocks.extend(map.insert_blocks(block_properties, cx));
2697                        } else {
2698                            blocks.shuffle(&mut rng);
2699                            let remove_count = rng.random_range(1..=4.min(blocks.len()));
2700                            let block_ids_to_remove = (0..remove_count)
2701                                .map(|_| blocks.remove(rng.random_range(0..blocks.len())))
2702                                .collect();
2703                            log::info!("removing block ids {:?}", block_ids_to_remove);
2704                            map.remove_blocks(block_ids_to_remove, cx);
2705                        }
2706                    });
2707                }
2708                45..=79 => {
2709                    let mut ranges = Vec::new();
2710                    for _ in 0..rng.random_range(1..=3) {
2711                        buffer.read_with(cx, |buffer, cx| {
2712                            let buffer = buffer.read(cx);
2713                            let end = buffer.clip_offset(
2714                                rng.random_range(MultiBufferOffset(0)..=buffer.len()),
2715                                Right,
2716                            );
2717                            let start = buffer
2718                                .clip_offset(rng.random_range(MultiBufferOffset(0)..=end), Left);
2719                            ranges.push(start..end);
2720                        });
2721                    }
2722
2723                    if rng.random() && fold_count > 0 {
2724                        log::info!("unfolding ranges: {:?}", ranges);
2725                        map.update(cx, |map, cx| {
2726                            map.unfold_intersecting(ranges, true, cx);
2727                        });
2728                    } else {
2729                        log::info!("folding ranges: {:?}", ranges);
2730                        map.update(cx, |map, cx| {
2731                            map.fold(
2732                                ranges
2733                                    .into_iter()
2734                                    .map(|range| Crease::simple(range, FoldPlaceholder::test()))
2735                                    .collect(),
2736                                cx,
2737                            );
2738                        });
2739                    }
2740                }
2741                _ => {
2742                    buffer.update(cx, |buffer, cx| buffer.randomly_mutate(&mut rng, 5, cx));
2743                }
2744            }
2745
2746            if map.read_with(cx, |map, cx| map.is_rewrapping(cx)) {
2747                notifications.next().await.unwrap();
2748            }
2749
2750            let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
2751            fold_count = snapshot.fold_count();
2752            log::info!("buffer text: {:?}", snapshot.buffer_snapshot().text());
2753            log::info!("fold text: {:?}", snapshot.fold_snapshot().text());
2754            log::info!("tab text: {:?}", snapshot.tab_snapshot().text());
2755            log::info!("wrap text: {:?}", snapshot.wrap_snapshot().text());
2756            log::info!("block text: {:?}", snapshot.block_snapshot.text());
2757            log::info!("display text: {:?}", snapshot.text());
2758
2759            // Line boundaries
2760            let buffer = snapshot.buffer_snapshot();
2761            for _ in 0..5 {
2762                let row = rng.random_range(0..=buffer.max_point().row);
2763                let column = rng.random_range(0..=buffer.line_len(MultiBufferRow(row)));
2764                let point = buffer.clip_point(Point::new(row, column), Left);
2765
2766                let (prev_buffer_bound, prev_display_bound) = snapshot.prev_line_boundary(point);
2767                let (next_buffer_bound, next_display_bound) = snapshot.next_line_boundary(point);
2768
2769                assert!(prev_buffer_bound <= point);
2770                assert!(next_buffer_bound >= point);
2771                assert_eq!(prev_buffer_bound.column, 0);
2772                assert_eq!(prev_display_bound.column(), 0);
2773                if next_buffer_bound < buffer.max_point() {
2774                    assert_eq!(buffer.chars_at(next_buffer_bound).next(), Some('\n'));
2775                }
2776
2777                assert_eq!(
2778                    prev_display_bound,
2779                    prev_buffer_bound.to_display_point(&snapshot),
2780                    "row boundary before {:?}. reported buffer row boundary: {:?}",
2781                    point,
2782                    prev_buffer_bound
2783                );
2784                assert_eq!(
2785                    next_display_bound,
2786                    next_buffer_bound.to_display_point(&snapshot),
2787                    "display row boundary after {:?}. reported buffer row boundary: {:?}",
2788                    point,
2789                    next_buffer_bound
2790                );
2791                assert_eq!(
2792                    prev_buffer_bound,
2793                    prev_display_bound.to_point(&snapshot),
2794                    "row boundary before {:?}. reported display row boundary: {:?}",
2795                    point,
2796                    prev_display_bound
2797                );
2798                assert_eq!(
2799                    next_buffer_bound,
2800                    next_display_bound.to_point(&snapshot),
2801                    "row boundary after {:?}. reported display row boundary: {:?}",
2802                    point,
2803                    next_display_bound
2804                );
2805            }
2806
2807            // Movement
2808            let min_point = snapshot.clip_point(DisplayPoint::new(DisplayRow(0), 0), Left);
2809            let max_point = snapshot.clip_point(snapshot.max_point(), Right);
2810            for _ in 0..5 {
2811                let row = rng.random_range(0..=snapshot.max_point().row().0);
2812                let column = rng.random_range(0..=snapshot.line_len(DisplayRow(row)));
2813                let point = snapshot.clip_point(DisplayPoint::new(DisplayRow(row), column), Left);
2814
2815                log::info!("Moving from point {:?}", point);
2816
2817                let moved_right = movement::right(&snapshot, point);
2818                log::info!("Right {:?}", moved_right);
2819                if point < max_point {
2820                    assert!(moved_right > point);
2821                    if point.column() == snapshot.line_len(point.row())
2822                        || snapshot.soft_wrap_indent(point.row()).is_some()
2823                            && point.column() == snapshot.line_len(point.row()) - 1
2824                    {
2825                        assert!(moved_right.row() > point.row());
2826                    }
2827                } else {
2828                    assert_eq!(moved_right, point);
2829                }
2830
2831                let moved_left = movement::left(&snapshot, point);
2832                log::info!("Left {:?}", moved_left);
2833                if point > min_point {
2834                    assert!(moved_left < point);
2835                    if point.column() == 0 {
2836                        assert!(moved_left.row() < point.row());
2837                    }
2838                } else {
2839                    assert_eq!(moved_left, point);
2840                }
2841            }
2842        }
2843    }
2844
2845    #[gpui::test(retries = 5)]
2846    async fn test_soft_wraps(cx: &mut gpui::TestAppContext) {
2847        cx.background_executor
2848            .set_block_on_ticks(usize::MAX..=usize::MAX);
2849        cx.update(|cx| {
2850            init_test(cx, &|_| {});
2851        });
2852
2853        let mut cx = crate::test::editor_test_context::EditorTestContext::new(cx).await;
2854        let editor = cx.editor.clone();
2855        let window = cx.window;
2856
2857        _ = cx.update_window(window, |_, window, cx| {
2858            let text_layout_details =
2859                editor.update(cx, |editor, cx| editor.text_layout_details(window, cx));
2860
2861            let font_size = px(12.0);
2862            let wrap_width = Some(px(96.));
2863
2864            let text = "one two three four five\nsix seven eight";
2865            let buffer = MultiBuffer::build_simple(text, cx);
2866            let map = cx.new(|cx| {
2867                DisplayMap::new(
2868                    buffer.clone(),
2869                    font("Helvetica"),
2870                    font_size,
2871                    wrap_width,
2872                    1,
2873                    1,
2874                    FoldPlaceholder::test(),
2875                    DiagnosticSeverity::Warning,
2876                    cx,
2877                )
2878            });
2879
2880            let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
2881            assert_eq!(
2882                snapshot.text_chunks(DisplayRow(0)).collect::<String>(),
2883                "one two \nthree four \nfive\nsix seven \neight"
2884            );
2885            assert_eq!(
2886                snapshot.clip_point(DisplayPoint::new(DisplayRow(0), 8), Bias::Left),
2887                DisplayPoint::new(DisplayRow(0), 7)
2888            );
2889            assert_eq!(
2890                snapshot.clip_point(DisplayPoint::new(DisplayRow(0), 8), Bias::Right),
2891                DisplayPoint::new(DisplayRow(1), 0)
2892            );
2893            assert_eq!(
2894                movement::right(&snapshot, DisplayPoint::new(DisplayRow(0), 7)),
2895                DisplayPoint::new(DisplayRow(1), 0)
2896            );
2897            assert_eq!(
2898                movement::left(&snapshot, DisplayPoint::new(DisplayRow(1), 0)),
2899                DisplayPoint::new(DisplayRow(0), 7)
2900            );
2901
2902            let x = snapshot
2903                .x_for_display_point(DisplayPoint::new(DisplayRow(1), 10), &text_layout_details);
2904            assert_eq!(
2905                movement::up(
2906                    &snapshot,
2907                    DisplayPoint::new(DisplayRow(1), 10),
2908                    language::SelectionGoal::None,
2909                    false,
2910                    &text_layout_details,
2911                ),
2912                (
2913                    DisplayPoint::new(DisplayRow(0), 7),
2914                    language::SelectionGoal::HorizontalPosition(f64::from(x))
2915                )
2916            );
2917            assert_eq!(
2918                movement::down(
2919                    &snapshot,
2920                    DisplayPoint::new(DisplayRow(0), 7),
2921                    language::SelectionGoal::HorizontalPosition(f64::from(x)),
2922                    false,
2923                    &text_layout_details
2924                ),
2925                (
2926                    DisplayPoint::new(DisplayRow(1), 10),
2927                    language::SelectionGoal::HorizontalPosition(f64::from(x))
2928                )
2929            );
2930            assert_eq!(
2931                movement::down(
2932                    &snapshot,
2933                    DisplayPoint::new(DisplayRow(1), 10),
2934                    language::SelectionGoal::HorizontalPosition(f64::from(x)),
2935                    false,
2936                    &text_layout_details
2937                ),
2938                (
2939                    DisplayPoint::new(DisplayRow(2), 4),
2940                    language::SelectionGoal::HorizontalPosition(f64::from(x))
2941                )
2942            );
2943
2944            let ix = MultiBufferOffset(snapshot.buffer_snapshot().text().find("seven").unwrap());
2945            buffer.update(cx, |buffer, cx| {
2946                buffer.edit([(ix..ix, "and ")], None, cx);
2947            });
2948
2949            let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
2950            assert_eq!(
2951                snapshot.text_chunks(DisplayRow(1)).collect::<String>(),
2952                "three four \nfive\nsix and \nseven eight"
2953            );
2954
2955            // Re-wrap on font size changes
2956            map.update(cx, |map, cx| {
2957                map.set_font(font("Helvetica"), font_size + Pixels::from(3.), cx)
2958            });
2959
2960            let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
2961            assert_eq!(
2962                snapshot.text_chunks(DisplayRow(1)).collect::<String>(),
2963                "three \nfour five\nsix and \nseven \neight"
2964            )
2965        });
2966    }
2967
2968    #[gpui::test]
2969    fn test_text_chunks(cx: &mut gpui::App) {
2970        init_test(cx, &|_| {});
2971
2972        let text = sample_text(6, 6, 'a');
2973        let buffer = MultiBuffer::build_simple(&text, cx);
2974
2975        let font_size = px(14.0);
2976        let map = cx.new(|cx| {
2977            DisplayMap::new(
2978                buffer.clone(),
2979                font("Helvetica"),
2980                font_size,
2981                None,
2982                1,
2983                1,
2984                FoldPlaceholder::test(),
2985                DiagnosticSeverity::Warning,
2986                cx,
2987            )
2988        });
2989
2990        buffer.update(cx, |buffer, cx| {
2991            buffer.edit(
2992                vec![
2993                    (
2994                        MultiBufferPoint::new(1, 0)..MultiBufferPoint::new(1, 0),
2995                        "\t",
2996                    ),
2997                    (
2998                        MultiBufferPoint::new(1, 1)..MultiBufferPoint::new(1, 1),
2999                        "\t",
3000                    ),
3001                    (
3002                        MultiBufferPoint::new(2, 1)..MultiBufferPoint::new(2, 1),
3003                        "\t",
3004                    ),
3005                ],
3006                None,
3007                cx,
3008            )
3009        });
3010
3011        assert_eq!(
3012            map.update(cx, |map, cx| map.snapshot(cx))
3013                .text_chunks(DisplayRow(1))
3014                .collect::<String>()
3015                .lines()
3016                .next(),
3017            Some("    b   bbbbb")
3018        );
3019        assert_eq!(
3020            map.update(cx, |map, cx| map.snapshot(cx))
3021                .text_chunks(DisplayRow(2))
3022                .collect::<String>()
3023                .lines()
3024                .next(),
3025            Some("c   ccccc")
3026        );
3027    }
3028
3029    #[gpui::test]
3030    fn test_inlays_with_newlines_after_blocks(cx: &mut gpui::TestAppContext) {
3031        cx.update(|cx| init_test(cx, &|_| {}));
3032
3033        let buffer = cx.new(|cx| Buffer::local("a", cx));
3034        let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
3035        let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
3036
3037        let font_size = px(14.0);
3038        let map = cx.new(|cx| {
3039            DisplayMap::new(
3040                buffer.clone(),
3041                font("Helvetica"),
3042                font_size,
3043                None,
3044                1,
3045                1,
3046                FoldPlaceholder::test(),
3047                DiagnosticSeverity::Warning,
3048                cx,
3049            )
3050        });
3051
3052        map.update(cx, |map, cx| {
3053            map.insert_blocks(
3054                [BlockProperties {
3055                    placement: BlockPlacement::Above(
3056                        buffer_snapshot.anchor_before(Point::new(0, 0)),
3057                    ),
3058                    height: Some(2),
3059                    style: BlockStyle::Sticky,
3060                    render: Arc::new(|_| div().into_any()),
3061                    priority: 0,
3062                }],
3063                cx,
3064            );
3065        });
3066        map.update(cx, |m, cx| assert_eq!(m.snapshot(cx).text(), "\n\na"));
3067
3068        map.update(cx, |map, cx| {
3069            map.splice_inlays(
3070                &[],
3071                vec![Inlay::edit_prediction(
3072                    0,
3073                    buffer_snapshot.anchor_after(MultiBufferOffset(0)),
3074                    "\n",
3075                )],
3076                cx,
3077            );
3078        });
3079        map.update(cx, |m, cx| assert_eq!(m.snapshot(cx).text(), "\n\n\na"));
3080
3081        // Regression test: updating the display map does not crash when a
3082        // block is immediately followed by a multi-line inlay.
3083        buffer.update(cx, |buffer, cx| {
3084            buffer.edit(
3085                [(MultiBufferOffset(1)..MultiBufferOffset(1), "b")],
3086                None,
3087                cx,
3088            );
3089        });
3090        map.update(cx, |m, cx| assert_eq!(m.snapshot(cx).text(), "\n\n\nab"));
3091    }
3092
3093    #[gpui::test]
3094    async fn test_chunks(cx: &mut gpui::TestAppContext) {
3095        let text = r#"
3096            fn outer() {}
3097
3098            mod module {
3099                fn inner() {}
3100            }"#
3101        .unindent();
3102
3103        let theme =
3104            SyntaxTheme::new_test(vec![("mod.body", Hsla::red()), ("fn.name", Hsla::blue())]);
3105        let language = Arc::new(
3106            Language::new(
3107                LanguageConfig {
3108                    name: "Test".into(),
3109                    matcher: LanguageMatcher {
3110                        path_suffixes: vec![".test".to_string()],
3111                        ..Default::default()
3112                    },
3113                    ..Default::default()
3114                },
3115                Some(tree_sitter_rust::LANGUAGE.into()),
3116            )
3117            .with_highlights_query(
3118                r#"
3119                (mod_item name: (identifier) body: _ @mod.body)
3120                (function_item name: (identifier) @fn.name)
3121                "#,
3122            )
3123            .unwrap(),
3124        );
3125        language.set_theme(&theme);
3126
3127        cx.update(|cx| {
3128            init_test(cx, &|s| {
3129                s.project.all_languages.defaults.tab_size = Some(2.try_into().unwrap())
3130            })
3131        });
3132
3133        let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
3134        cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
3135        let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
3136
3137        let font_size = px(14.0);
3138
3139        let map = cx.new(|cx| {
3140            DisplayMap::new(
3141                buffer,
3142                font("Helvetica"),
3143                font_size,
3144                None,
3145                1,
3146                1,
3147                FoldPlaceholder::test(),
3148                DiagnosticSeverity::Warning,
3149                cx,
3150            )
3151        });
3152        assert_eq!(
3153            cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(5), &map, &theme, cx)),
3154            vec![
3155                ("fn ".to_string(), None),
3156                ("outer".to_string(), Some(Hsla::blue())),
3157                ("() {}\n\nmod module ".to_string(), None),
3158                ("{\n    fn ".to_string(), Some(Hsla::red())),
3159                ("inner".to_string(), Some(Hsla::blue())),
3160                ("() {}\n}".to_string(), Some(Hsla::red())),
3161            ]
3162        );
3163        assert_eq!(
3164            cx.update(|cx| syntax_chunks(DisplayRow(3)..DisplayRow(5), &map, &theme, cx)),
3165            vec![
3166                ("    fn ".to_string(), Some(Hsla::red())),
3167                ("inner".to_string(), Some(Hsla::blue())),
3168                ("() {}\n}".to_string(), Some(Hsla::red())),
3169            ]
3170        );
3171
3172        map.update(cx, |map, cx| {
3173            map.fold(
3174                vec![Crease::simple(
3175                    MultiBufferPoint::new(0, 6)..MultiBufferPoint::new(3, 2),
3176                    FoldPlaceholder::test(),
3177                )],
3178                cx,
3179            )
3180        });
3181        assert_eq!(
3182            cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(2), &map, &theme, cx)),
3183            vec![
3184                ("fn ".to_string(), None),
3185                ("out".to_string(), Some(Hsla::blue())),
3186                ("".to_string(), None),
3187                ("  fn ".to_string(), Some(Hsla::red())),
3188                ("inner".to_string(), Some(Hsla::blue())),
3189                ("() {}\n}".to_string(), Some(Hsla::red())),
3190            ]
3191        );
3192    }
3193
3194    #[gpui::test]
3195    async fn test_chunks_with_syntax_highlighting_across_blocks(cx: &mut gpui::TestAppContext) {
3196        cx.background_executor
3197            .set_block_on_ticks(usize::MAX..=usize::MAX);
3198
3199        let text = r#"
3200            const A: &str = "
3201                one
3202                two
3203                three
3204            ";
3205            const B: &str = "four";
3206        "#
3207        .unindent();
3208
3209        let theme = SyntaxTheme::new_test(vec![
3210            ("string", Hsla::red()),
3211            ("punctuation", Hsla::blue()),
3212            ("keyword", Hsla::green()),
3213        ]);
3214        let language = Arc::new(
3215            Language::new(
3216                LanguageConfig {
3217                    name: "Rust".into(),
3218                    ..Default::default()
3219                },
3220                Some(tree_sitter_rust::LANGUAGE.into()),
3221            )
3222            .with_highlights_query(
3223                r#"
3224                (string_literal) @string
3225                "const" @keyword
3226                [":" ";"] @punctuation
3227                "#,
3228            )
3229            .unwrap(),
3230        );
3231        language.set_theme(&theme);
3232
3233        cx.update(|cx| init_test(cx, &|_| {}));
3234
3235        let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
3236        cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
3237        let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
3238        let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
3239
3240        let map = cx.new(|cx| {
3241            DisplayMap::new(
3242                buffer,
3243                font("Courier"),
3244                px(16.0),
3245                None,
3246                1,
3247                1,
3248                FoldPlaceholder::test(),
3249                DiagnosticSeverity::Warning,
3250                cx,
3251            )
3252        });
3253
3254        // Insert two blocks in the middle of a multi-line string literal.
3255        // The second block has zero height.
3256        map.update(cx, |map, cx| {
3257            map.insert_blocks(
3258                [
3259                    BlockProperties {
3260                        placement: BlockPlacement::Below(
3261                            buffer_snapshot.anchor_before(Point::new(1, 0)),
3262                        ),
3263                        height: Some(1),
3264                        style: BlockStyle::Sticky,
3265                        render: Arc::new(|_| div().into_any()),
3266                        priority: 0,
3267                    },
3268                    BlockProperties {
3269                        placement: BlockPlacement::Below(
3270                            buffer_snapshot.anchor_before(Point::new(2, 0)),
3271                        ),
3272                        height: None,
3273                        style: BlockStyle::Sticky,
3274                        render: Arc::new(|_| div().into_any()),
3275                        priority: 0,
3276                    },
3277                ],
3278                cx,
3279            )
3280        });
3281
3282        pretty_assertions::assert_eq!(
3283            cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(7), &map, &theme, cx)),
3284            [
3285                ("const".into(), Some(Hsla::green())),
3286                (" A".into(), None),
3287                (":".into(), Some(Hsla::blue())),
3288                (" &str = ".into(), None),
3289                ("\"\n    one\n".into(), Some(Hsla::red())),
3290                ("\n".into(), None),
3291                ("    two\n    three\n\"".into(), Some(Hsla::red())),
3292                (";".into(), Some(Hsla::blue())),
3293                ("\n".into(), None),
3294                ("const".into(), Some(Hsla::green())),
3295                (" B".into(), None),
3296                (":".into(), Some(Hsla::blue())),
3297                (" &str = ".into(), None),
3298                ("\"four\"".into(), Some(Hsla::red())),
3299                (";".into(), Some(Hsla::blue())),
3300                ("\n".into(), None),
3301            ]
3302        );
3303    }
3304
3305    #[gpui::test]
3306    async fn test_chunks_with_diagnostics_across_blocks(cx: &mut gpui::TestAppContext) {
3307        cx.background_executor
3308            .set_block_on_ticks(usize::MAX..=usize::MAX);
3309
3310        let text = r#"
3311            struct A {
3312                b: usize;
3313            }
3314            const c: usize = 1;
3315        "#
3316        .unindent();
3317
3318        cx.update(|cx| init_test(cx, &|_| {}));
3319
3320        let buffer = cx.new(|cx| Buffer::local(text, cx));
3321
3322        buffer.update(cx, |buffer, cx| {
3323            buffer.update_diagnostics(
3324                LanguageServerId(0),
3325                DiagnosticSet::new(
3326                    [DiagnosticEntry {
3327                        range: PointUtf16::new(0, 0)..PointUtf16::new(2, 1),
3328                        diagnostic: Diagnostic {
3329                            severity: lsp::DiagnosticSeverity::ERROR,
3330                            group_id: 1,
3331                            message: "hi".into(),
3332                            ..Default::default()
3333                        },
3334                    }],
3335                    buffer,
3336                ),
3337                cx,
3338            )
3339        });
3340
3341        let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
3342        let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
3343
3344        let map = cx.new(|cx| {
3345            DisplayMap::new(
3346                buffer,
3347                font("Courier"),
3348                px(16.0),
3349                None,
3350                1,
3351                1,
3352                FoldPlaceholder::test(),
3353                DiagnosticSeverity::Warning,
3354                cx,
3355            )
3356        });
3357
3358        let black = gpui::black().to_rgb();
3359        let red = gpui::red().to_rgb();
3360
3361        // Insert a block in the middle of a multi-line diagnostic.
3362        map.update(cx, |map, cx| {
3363            map.highlight_text(
3364                HighlightKey::Editor,
3365                vec![
3366                    buffer_snapshot.anchor_before(Point::new(3, 9))
3367                        ..buffer_snapshot.anchor_after(Point::new(3, 14)),
3368                    buffer_snapshot.anchor_before(Point::new(3, 17))
3369                        ..buffer_snapshot.anchor_after(Point::new(3, 18)),
3370                ],
3371                red.into(),
3372                false,
3373                cx,
3374            );
3375            map.insert_blocks(
3376                [BlockProperties {
3377                    placement: BlockPlacement::Below(
3378                        buffer_snapshot.anchor_before(Point::new(1, 0)),
3379                    ),
3380                    height: Some(1),
3381                    style: BlockStyle::Sticky,
3382                    render: Arc::new(|_| div().into_any()),
3383                    priority: 0,
3384                }],
3385                cx,
3386            )
3387        });
3388
3389        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
3390        let mut chunks = Vec::<(String, Option<lsp::DiagnosticSeverity>, Rgba)>::new();
3391        for chunk in snapshot.chunks(DisplayRow(0)..DisplayRow(5), true, Default::default()) {
3392            let color = chunk
3393                .highlight_style
3394                .and_then(|style| style.color)
3395                .map_or(black, |color| color.to_rgb());
3396            if let Some((last_chunk, last_severity, last_color)) = chunks.last_mut()
3397                && *last_severity == chunk.diagnostic_severity
3398                && *last_color == color
3399            {
3400                last_chunk.push_str(chunk.text);
3401                continue;
3402            }
3403
3404            chunks.push((chunk.text.to_string(), chunk.diagnostic_severity, color));
3405        }
3406
3407        assert_eq!(
3408            chunks,
3409            [
3410                (
3411                    "struct A {\n    b: usize;\n".into(),
3412                    Some(lsp::DiagnosticSeverity::ERROR),
3413                    black
3414                ),
3415                ("\n".into(), None, black),
3416                ("}".into(), Some(lsp::DiagnosticSeverity::ERROR), black),
3417                ("\nconst c: ".into(), None, black),
3418                ("usize".into(), None, red),
3419                (" = ".into(), None, black),
3420                ("1".into(), None, red),
3421                (";\n".into(), None, black),
3422            ]
3423        );
3424    }
3425
3426    #[gpui::test]
3427    async fn test_point_translation_with_replace_blocks(cx: &mut gpui::TestAppContext) {
3428        cx.background_executor
3429            .set_block_on_ticks(usize::MAX..=usize::MAX);
3430
3431        cx.update(|cx| init_test(cx, &|_| {}));
3432
3433        let buffer = cx.update(|cx| MultiBuffer::build_simple("abcde\nfghij\nklmno\npqrst", cx));
3434        let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
3435        let map = cx.new(|cx| {
3436            DisplayMap::new(
3437                buffer.clone(),
3438                font("Courier"),
3439                px(16.0),
3440                None,
3441                1,
3442                1,
3443                FoldPlaceholder::test(),
3444                DiagnosticSeverity::Warning,
3445                cx,
3446            )
3447        });
3448
3449        let snapshot = map.update(cx, |map, cx| {
3450            map.insert_blocks(
3451                [BlockProperties {
3452                    placement: BlockPlacement::Replace(
3453                        buffer_snapshot.anchor_before(Point::new(1, 2))
3454                            ..=buffer_snapshot.anchor_after(Point::new(2, 3)),
3455                    ),
3456                    height: Some(4),
3457                    style: BlockStyle::Fixed,
3458                    render: Arc::new(|_| div().into_any()),
3459                    priority: 0,
3460                }],
3461                cx,
3462            );
3463            map.snapshot(cx)
3464        });
3465
3466        assert_eq!(snapshot.text(), "abcde\n\n\n\n\npqrst");
3467
3468        let point_to_display_points = [
3469            (Point::new(1, 0), DisplayPoint::new(DisplayRow(1), 0)),
3470            (Point::new(2, 0), DisplayPoint::new(DisplayRow(1), 0)),
3471            (Point::new(3, 0), DisplayPoint::new(DisplayRow(5), 0)),
3472        ];
3473        for (buffer_point, display_point) in point_to_display_points {
3474            assert_eq!(
3475                snapshot.point_to_display_point(buffer_point, Bias::Left),
3476                display_point,
3477                "point_to_display_point({:?}, Bias::Left)",
3478                buffer_point
3479            );
3480            assert_eq!(
3481                snapshot.point_to_display_point(buffer_point, Bias::Right),
3482                display_point,
3483                "point_to_display_point({:?}, Bias::Right)",
3484                buffer_point
3485            );
3486        }
3487
3488        let display_points_to_points = [
3489            (
3490                DisplayPoint::new(DisplayRow(1), 0),
3491                Point::new(1, 0),
3492                Point::new(2, 5),
3493            ),
3494            (
3495                DisplayPoint::new(DisplayRow(2), 0),
3496                Point::new(1, 0),
3497                Point::new(2, 5),
3498            ),
3499            (
3500                DisplayPoint::new(DisplayRow(3), 0),
3501                Point::new(1, 0),
3502                Point::new(2, 5),
3503            ),
3504            (
3505                DisplayPoint::new(DisplayRow(4), 0),
3506                Point::new(1, 0),
3507                Point::new(2, 5),
3508            ),
3509            (
3510                DisplayPoint::new(DisplayRow(5), 0),
3511                Point::new(3, 0),
3512                Point::new(3, 0),
3513            ),
3514        ];
3515        for (display_point, left_buffer_point, right_buffer_point) in display_points_to_points {
3516            assert_eq!(
3517                snapshot.display_point_to_point(display_point, Bias::Left),
3518                left_buffer_point,
3519                "display_point_to_point({:?}, Bias::Left)",
3520                display_point
3521            );
3522            assert_eq!(
3523                snapshot.display_point_to_point(display_point, Bias::Right),
3524                right_buffer_point,
3525                "display_point_to_point({:?}, Bias::Right)",
3526                display_point
3527            );
3528        }
3529    }
3530
3531    #[gpui::test]
3532    async fn test_chunks_with_soft_wrapping(cx: &mut gpui::TestAppContext) {
3533        cx.background_executor
3534            .set_block_on_ticks(usize::MAX..=usize::MAX);
3535
3536        let text = r#"
3537            fn outer() {}
3538
3539            mod module {
3540                fn inner() {}
3541            }"#
3542        .unindent();
3543
3544        let theme =
3545            SyntaxTheme::new_test(vec![("mod.body", Hsla::red()), ("fn.name", Hsla::blue())]);
3546        let language = Arc::new(
3547            Language::new(
3548                LanguageConfig {
3549                    name: "Test".into(),
3550                    matcher: LanguageMatcher {
3551                        path_suffixes: vec![".test".to_string()],
3552                        ..Default::default()
3553                    },
3554                    ..Default::default()
3555                },
3556                Some(tree_sitter_rust::LANGUAGE.into()),
3557            )
3558            .with_highlights_query(
3559                r#"
3560                (mod_item name: (identifier) body: _ @mod.body)
3561                (function_item name: (identifier) @fn.name)
3562                "#,
3563            )
3564            .unwrap(),
3565        );
3566        language.set_theme(&theme);
3567
3568        cx.update(|cx| init_test(cx, &|_| {}));
3569
3570        let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
3571        cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
3572        let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
3573
3574        let font_size = px(16.0);
3575
3576        let map = cx.new(|cx| {
3577            DisplayMap::new(
3578                buffer,
3579                font("Courier"),
3580                font_size,
3581                Some(px(40.0)),
3582                1,
3583                1,
3584                FoldPlaceholder::test(),
3585                DiagnosticSeverity::Warning,
3586                cx,
3587            )
3588        });
3589        assert_eq!(
3590            cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(5), &map, &theme, cx)),
3591            [
3592                ("fn \n".to_string(), None),
3593                ("oute".to_string(), Some(Hsla::blue())),
3594                ("\n".to_string(), None),
3595                ("r".to_string(), Some(Hsla::blue())),
3596                ("() \n{}\n\n".to_string(), None),
3597            ]
3598        );
3599        assert_eq!(
3600            cx.update(|cx| syntax_chunks(DisplayRow(3)..DisplayRow(5), &map, &theme, cx)),
3601            [("{}\n\n".to_string(), None)]
3602        );
3603
3604        map.update(cx, |map, cx| {
3605            map.fold(
3606                vec![Crease::simple(
3607                    MultiBufferPoint::new(0, 6)..MultiBufferPoint::new(3, 2),
3608                    FoldPlaceholder::test(),
3609                )],
3610                cx,
3611            )
3612        });
3613        assert_eq!(
3614            cx.update(|cx| syntax_chunks(DisplayRow(1)..DisplayRow(4), &map, &theme, cx)),
3615            [
3616                ("out".to_string(), Some(Hsla::blue())),
3617                ("\n".to_string(), None),
3618                ("  ".to_string(), Some(Hsla::red())),
3619                ("\n".to_string(), None),
3620                ("fn ".to_string(), Some(Hsla::red())),
3621                ("i".to_string(), Some(Hsla::blue())),
3622                ("\n".to_string(), None)
3623            ]
3624        );
3625    }
3626
3627    #[gpui::test]
3628    async fn test_chunks_with_text_highlights(cx: &mut gpui::TestAppContext) {
3629        cx.update(|cx| init_test(cx, &|_| {}));
3630
3631        let theme =
3632            SyntaxTheme::new_test(vec![("operator", Hsla::red()), ("string", Hsla::green())]);
3633        let language = Arc::new(
3634            Language::new(
3635                LanguageConfig {
3636                    name: "Test".into(),
3637                    matcher: LanguageMatcher {
3638                        path_suffixes: vec![".test".to_string()],
3639                        ..Default::default()
3640                    },
3641                    ..Default::default()
3642                },
3643                Some(tree_sitter_rust::LANGUAGE.into()),
3644            )
3645            .with_highlights_query(
3646                r#"
3647                ":" @operator
3648                (string_literal) @string
3649                "#,
3650            )
3651            .unwrap(),
3652        );
3653        language.set_theme(&theme);
3654
3655        let (text, highlighted_ranges) = marked_text_ranges(r#"constˇ «a»«:» B = "c «d»""#, false);
3656
3657        let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
3658        cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
3659
3660        let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
3661        let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
3662
3663        let font_size = px(16.0);
3664        let map = cx.new(|cx| {
3665            DisplayMap::new(
3666                buffer,
3667                font("Courier"),
3668                font_size,
3669                None,
3670                1,
3671                1,
3672                FoldPlaceholder::test(),
3673                DiagnosticSeverity::Warning,
3674                cx,
3675            )
3676        });
3677
3678        let style = HighlightStyle {
3679            color: Some(Hsla::blue()),
3680            ..Default::default()
3681        };
3682
3683        map.update(cx, |map, cx| {
3684            map.highlight_text(
3685                HighlightKey::Editor,
3686                highlighted_ranges
3687                    .into_iter()
3688                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
3689                    .map(|range| {
3690                        buffer_snapshot.anchor_before(range.start)
3691                            ..buffer_snapshot.anchor_before(range.end)
3692                    })
3693                    .collect(),
3694                style,
3695                false,
3696                cx,
3697            );
3698        });
3699
3700        assert_eq!(
3701            cx.update(|cx| chunks(DisplayRow(0)..DisplayRow(10), &map, &theme, cx)),
3702            [
3703                ("const ".to_string(), None, None),
3704                ("a".to_string(), None, Some(Hsla::blue())),
3705                (":".to_string(), Some(Hsla::red()), Some(Hsla::blue())),
3706                (" B = ".to_string(), None, None),
3707                ("\"c ".to_string(), Some(Hsla::green()), None),
3708                ("d".to_string(), Some(Hsla::green()), Some(Hsla::blue())),
3709                ("\"".to_string(), Some(Hsla::green()), None),
3710            ]
3711        );
3712    }
3713
3714    #[gpui::test]
3715    fn test_clip_point(cx: &mut gpui::App) {
3716        init_test(cx, &|_| {});
3717
3718        fn assert(text: &str, shift_right: bool, bias: Bias, cx: &mut gpui::App) {
3719            let (unmarked_snapshot, mut markers) = marked_display_snapshot(text, cx);
3720
3721            match bias {
3722                Bias::Left => {
3723                    if shift_right {
3724                        *markers[1].column_mut() += 1;
3725                    }
3726
3727                    assert_eq!(unmarked_snapshot.clip_point(markers[1], bias), markers[0])
3728                }
3729                Bias::Right => {
3730                    if shift_right {
3731                        *markers[0].column_mut() += 1;
3732                    }
3733
3734                    assert_eq!(unmarked_snapshot.clip_point(markers[0], bias), markers[1])
3735                }
3736            };
3737        }
3738
3739        use Bias::{Left, Right};
3740        assert("ˇˇα", false, Left, cx);
3741        assert("ˇˇα", true, Left, cx);
3742        assert("ˇˇα", false, Right, cx);
3743        assert("ˇαˇ", true, Right, cx);
3744        assert("ˇˇ✋", false, Left, cx);
3745        assert("ˇˇ✋", true, Left, cx);
3746        assert("ˇˇ✋", false, Right, cx);
3747        assert("ˇ✋ˇ", true, Right, cx);
3748        assert("ˇˇ🍐", false, Left, cx);
3749        assert("ˇˇ🍐", true, Left, cx);
3750        assert("ˇˇ🍐", false, Right, cx);
3751        assert("ˇ🍐ˇ", true, Right, cx);
3752        assert("ˇˇ\t", false, Left, cx);
3753        assert("ˇˇ\t", true, Left, cx);
3754        assert("ˇˇ\t", false, Right, cx);
3755        assert("ˇ\tˇ", true, Right, cx);
3756        assert(" ˇˇ\t", false, Left, cx);
3757        assert(" ˇˇ\t", true, Left, cx);
3758        assert(" ˇˇ\t", false, Right, cx);
3759        assert(" ˇ\tˇ", true, Right, cx);
3760        assert("   ˇˇ\t", false, Left, cx);
3761        assert("   ˇˇ\t", false, Right, cx);
3762    }
3763
3764    #[gpui::test]
3765    fn test_clip_at_line_ends(cx: &mut gpui::App) {
3766        init_test(cx, &|_| {});
3767
3768        fn assert(text: &str, cx: &mut gpui::App) {
3769            let (mut unmarked_snapshot, markers) = marked_display_snapshot(text, cx);
3770            unmarked_snapshot.clip_at_line_ends = true;
3771            assert_eq!(
3772                unmarked_snapshot.clip_point(markers[1], Bias::Left),
3773                markers[0]
3774            );
3775        }
3776
3777        assert("ˇˇ", cx);
3778        assert("ˇaˇ", cx);
3779        assert("aˇbˇ", cx);
3780        assert("aˇαˇ", cx);
3781    }
3782
3783    #[gpui::test]
3784    fn test_creases(cx: &mut gpui::App) {
3785        init_test(cx, &|_| {});
3786
3787        let text = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll";
3788        let buffer = MultiBuffer::build_simple(text, cx);
3789        let font_size = px(14.0);
3790        cx.new(|cx| {
3791            let mut map = DisplayMap::new(
3792                buffer.clone(),
3793                font("Helvetica"),
3794                font_size,
3795                None,
3796                1,
3797                1,
3798                FoldPlaceholder::test(),
3799                DiagnosticSeverity::Warning,
3800                cx,
3801            );
3802            let snapshot = map.buffer.read(cx).snapshot(cx);
3803            let range =
3804                snapshot.anchor_before(Point::new(2, 0))..snapshot.anchor_after(Point::new(3, 3));
3805
3806            map.crease_map.insert(
3807                [Crease::inline(
3808                    range,
3809                    FoldPlaceholder::test(),
3810                    |_row, _status, _toggle, _window, _cx| div(),
3811                    |_row, _status, _window, _cx| div(),
3812                )],
3813                &map.buffer.read(cx).snapshot(cx),
3814            );
3815
3816            map
3817        });
3818    }
3819
3820    #[gpui::test]
3821    fn test_tabs_with_multibyte_chars(cx: &mut gpui::App) {
3822        init_test(cx, &|_| {});
3823
3824        let text = "\t\tα\nβ\t\n🏀β\t\tγ";
3825        let buffer = MultiBuffer::build_simple(text, cx);
3826        let font_size = px(14.0);
3827
3828        let map = cx.new(|cx| {
3829            DisplayMap::new(
3830                buffer.clone(),
3831                font("Helvetica"),
3832                font_size,
3833                None,
3834                1,
3835                1,
3836                FoldPlaceholder::test(),
3837                DiagnosticSeverity::Warning,
3838                cx,
3839            )
3840        });
3841        let map = map.update(cx, |map, cx| map.snapshot(cx));
3842        assert_eq!(map.text(), "✅       α\nβ   \n🏀β      γ");
3843        assert_eq!(
3844            map.text_chunks(DisplayRow(0)).collect::<String>(),
3845            "✅       α\nβ   \n🏀β      γ"
3846        );
3847        assert_eq!(
3848            map.text_chunks(DisplayRow(1)).collect::<String>(),
3849            "β   \n🏀β      γ"
3850        );
3851        assert_eq!(
3852            map.text_chunks(DisplayRow(2)).collect::<String>(),
3853            "🏀β      γ"
3854        );
3855
3856        let point = MultiBufferPoint::new(0, "\t\t".len() as u32);
3857        let display_point = DisplayPoint::new(DisplayRow(0), "".len() as u32);
3858        assert_eq!(point.to_display_point(&map), display_point);
3859        assert_eq!(display_point.to_point(&map), point);
3860
3861        let point = MultiBufferPoint::new(1, "β\t".len() as u32);
3862        let display_point = DisplayPoint::new(DisplayRow(1), "β   ".len() as u32);
3863        assert_eq!(point.to_display_point(&map), display_point);
3864        assert_eq!(display_point.to_point(&map), point,);
3865
3866        let point = MultiBufferPoint::new(2, "🏀β\t\t".len() as u32);
3867        let display_point = DisplayPoint::new(DisplayRow(2), "🏀β      ".len() as u32);
3868        assert_eq!(point.to_display_point(&map), display_point);
3869        assert_eq!(display_point.to_point(&map), point,);
3870
3871        // Display points inside of expanded tabs
3872        assert_eq!(
3873            DisplayPoint::new(DisplayRow(0), "".len() as u32).to_point(&map),
3874            MultiBufferPoint::new(0, "\t".len() as u32),
3875        );
3876        assert_eq!(
3877            DisplayPoint::new(DisplayRow(0), "".len() as u32).to_point(&map),
3878            MultiBufferPoint::new(0, "".len() as u32),
3879        );
3880
3881        // Clipping display points inside of multi-byte characters
3882        assert_eq!(
3883            map.clip_point(
3884                DisplayPoint::new(DisplayRow(0), "".len() as u32 - 1),
3885                Left
3886            ),
3887            DisplayPoint::new(DisplayRow(0), 0)
3888        );
3889        assert_eq!(
3890            map.clip_point(
3891                DisplayPoint::new(DisplayRow(0), "".len() as u32 - 1),
3892                Bias::Right
3893            ),
3894            DisplayPoint::new(DisplayRow(0), "".len() as u32)
3895        );
3896    }
3897
3898    #[gpui::test]
3899    fn test_max_point(cx: &mut gpui::App) {
3900        init_test(cx, &|_| {});
3901
3902        let buffer = MultiBuffer::build_simple("aaa\n\t\tbbb", cx);
3903        let font_size = px(14.0);
3904        let map = cx.new(|cx| {
3905            DisplayMap::new(
3906                buffer.clone(),
3907                font("Helvetica"),
3908                font_size,
3909                None,
3910                1,
3911                1,
3912                FoldPlaceholder::test(),
3913                DiagnosticSeverity::Warning,
3914                cx,
3915            )
3916        });
3917        assert_eq!(
3918            map.update(cx, |map, cx| map.snapshot(cx)).max_point(),
3919            DisplayPoint::new(DisplayRow(1), 11)
3920        )
3921    }
3922
3923    fn syntax_chunks(
3924        rows: Range<DisplayRow>,
3925        map: &Entity<DisplayMap>,
3926        theme: &SyntaxTheme,
3927        cx: &mut App,
3928    ) -> Vec<(String, Option<Hsla>)> {
3929        chunks(rows, map, theme, cx)
3930            .into_iter()
3931            .map(|(text, color, _)| (text, color))
3932            .collect()
3933    }
3934
3935    fn chunks(
3936        rows: Range<DisplayRow>,
3937        map: &Entity<DisplayMap>,
3938        theme: &SyntaxTheme,
3939        cx: &mut App,
3940    ) -> Vec<(String, Option<Hsla>, Option<Hsla>)> {
3941        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
3942        let mut chunks: Vec<(String, Option<Hsla>, Option<Hsla>)> = Vec::new();
3943        for chunk in snapshot.chunks(rows, true, HighlightStyles::default()) {
3944            let syntax_color = chunk
3945                .syntax_highlight_id
3946                .and_then(|id| theme.get(id)?.color);
3947
3948            let highlight_color = chunk.highlight_style.and_then(|style| style.color);
3949            if let Some((last_chunk, last_syntax_color, last_highlight_color)) = chunks.last_mut()
3950                && syntax_color == *last_syntax_color
3951                && highlight_color == *last_highlight_color
3952            {
3953                last_chunk.push_str(chunk.text);
3954                continue;
3955            }
3956            chunks.push((chunk.text.to_string(), syntax_color, highlight_color));
3957        }
3958        chunks
3959    }
3960
3961    fn init_test(cx: &mut App, f: &dyn Fn(&mut SettingsContent)) {
3962        let settings = SettingsStore::test(cx);
3963        cx.set_global(settings);
3964        crate::init(cx);
3965        theme_settings::init(LoadThemes::JustBase, cx);
3966        cx.update_global::<SettingsStore, _>(|store, cx| {
3967            store.update_user_settings(cx, f);
3968        });
3969    }
3970
3971    #[gpui::test]
3972    fn test_isomorphic_display_point_ranges_for_buffer_range(cx: &mut gpui::TestAppContext) {
3973        cx.update(|cx| init_test(cx, &|_| {}));
3974
3975        let buffer = cx.new(|cx| Buffer::local("let x = 5;\n", cx));
3976        let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
3977        let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
3978
3979        let font_size = px(14.0);
3980        let map = cx.new(|cx| {
3981            DisplayMap::new(
3982                buffer.clone(),
3983                font("Helvetica"),
3984                font_size,
3985                None,
3986                1,
3987                1,
3988                FoldPlaceholder::test(),
3989                DiagnosticSeverity::Warning,
3990                cx,
3991            )
3992        });
3993
3994        // Without inlays, a buffer range maps to a single display range.
3995        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
3996        let ranges = snapshot.isomorphic_display_point_ranges_for_buffer_range(
3997            MultiBufferOffset(4)..MultiBufferOffset(9),
3998        );
3999        assert_eq!(ranges.len(), 1);
4000        // "x = 5" is columns 4..9 with no inlays shifting anything.
4001        assert_eq!(ranges[0].start, DisplayPoint::new(DisplayRow(0), 4));
4002        assert_eq!(ranges[0].end, DisplayPoint::new(DisplayRow(0), 9));
4003
4004        // Insert a 4-char inlay hint ": i32" at buffer offset 5 (after "x").
4005        map.update(cx, |map, cx| {
4006            map.splice_inlays(
4007                &[],
4008                vec![Inlay::mock_hint(
4009                    0,
4010                    buffer_snapshot.anchor_after(MultiBufferOffset(5)),
4011                    ": i32",
4012                )],
4013                cx,
4014            );
4015        });
4016        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
4017        assert_eq!(snapshot.text(), "let x: i32 = 5;\n");
4018
4019        // A buffer range [4..9] ("x = 5") now spans across the inlay.
4020        // It should be split into two display ranges that skip the inlay text.
4021        let ranges = snapshot.isomorphic_display_point_ranges_for_buffer_range(
4022            MultiBufferOffset(4)..MultiBufferOffset(9),
4023        );
4024        assert_eq!(
4025            ranges.len(),
4026            2,
4027            "expected the range to be split around the inlay, got: {:?}",
4028            ranges,
4029        );
4030        // First sub-range: buffer [4, 5) → "x" at display columns 4..5
4031        assert_eq!(ranges[0].start, DisplayPoint::new(DisplayRow(0), 4));
4032        assert_eq!(ranges[0].end, DisplayPoint::new(DisplayRow(0), 5));
4033        // Second sub-range: buffer [5, 9) → " = 5" at display columns 10..14
4034        // (shifted right by the 5-char ": i32" inlay)
4035        assert_eq!(ranges[1].start, DisplayPoint::new(DisplayRow(0), 10));
4036        assert_eq!(ranges[1].end, DisplayPoint::new(DisplayRow(0), 14));
4037
4038        // A range entirely before the inlay is not split.
4039        let ranges = snapshot.isomorphic_display_point_ranges_for_buffer_range(
4040            MultiBufferOffset(0)..MultiBufferOffset(5),
4041        );
4042        assert_eq!(ranges.len(), 1);
4043        assert_eq!(ranges[0].start, DisplayPoint::new(DisplayRow(0), 0));
4044        assert_eq!(ranges[0].end, DisplayPoint::new(DisplayRow(0), 5));
4045
4046        // A range entirely after the inlay is not split.
4047        let ranges = snapshot.isomorphic_display_point_ranges_for_buffer_range(
4048            MultiBufferOffset(5)..MultiBufferOffset(9),
4049        );
4050        assert_eq!(ranges.len(), 1);
4051        assert_eq!(ranges[0].start, DisplayPoint::new(DisplayRow(0), 10));
4052        assert_eq!(ranges[0].end, DisplayPoint::new(DisplayRow(0), 14));
4053    }
4054
4055    #[test]
4056    fn test_highlight_invisibles_preserves_compound_emojis() {
4057        let editor_style = EditorStyle::default();
4058
4059        let pilot_emoji = "🧑\u{200d}\u{fe0f}";
4060        let chunk = HighlightedChunk {
4061            text: pilot_emoji,
4062            style: None,
4063            is_tab: false,
4064            is_inlay: false,
4065            replacement: None,
4066        };
4067
4068        let chunks: Vec<_> = chunk
4069            .highlight_invisibles(&editor_style)
4070            .map(|chunk| chunk.text.to_string())
4071            .collect();
4072
4073        assert_eq!(
4074            chunks.concat(),
4075            pilot_emoji,
4076            "all text bytes must be preserved"
4077        );
4078        assert_eq!(
4079            chunks.len(),
4080            1,
4081            "compound emoji should not be split into multiple chunks, got: {:?}",
4082            chunks,
4083        );
4084    }
4085
4086    /// Regression test: Creating a DisplayMap when the MultiBuffer has pending
4087    /// unsynced changes should not cause a desync between the subscription edits
4088    /// and the InlayMap's buffer state.
4089    ///
4090    /// The bug occurred because:
4091    /// 1. DisplayMap::new created a subscription first
4092    /// 2. Then called snapshot() which synced and published edits
4093    /// 3. InlayMap was created with the post-sync snapshot
4094    /// 4. But the subscription captured the sync edits, leading to double-application
4095    #[gpui::test]
4096    fn test_display_map_subscription_ordering(cx: &mut gpui::App) {
4097        init_test(cx, &|_| {});
4098
4099        // Create a buffer with some initial text
4100        let buffer = cx.new(|cx| Buffer::local("initial", cx));
4101        let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
4102
4103        // Edit the buffer. This sets buffer_changed_since_sync = true.
4104        // Importantly, do NOT call multibuffer.snapshot() yet.
4105        buffer.update(cx, |buffer, cx| {
4106            buffer.edit([(0..0, "prefix ")], None, cx);
4107        });
4108
4109        // Create the DisplayMap. In the buggy code, this would:
4110        // 1. Create subscription (empty)
4111        // 2. Call snapshot() which syncs and publishes edits E1
4112        // 3. Create InlayMap with post-E1 snapshot
4113        // 4. Subscription now has E1, but InlayMap is already at post-E1 state
4114        let map = cx.new(|cx| {
4115            DisplayMap::new(
4116                multibuffer.clone(),
4117                font("Helvetica"),
4118                px(14.0),
4119                None,
4120                1,
4121                1,
4122                FoldPlaceholder::test(),
4123                DiagnosticSeverity::Warning,
4124                cx,
4125            )
4126        });
4127
4128        // Verify initial state is correct
4129        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
4130        assert_eq!(snapshot.text(), "prefix initial");
4131
4132        // Make another edit
4133        buffer.update(cx, |buffer, cx| {
4134            buffer.edit([(7..7, "more ")], None, cx);
4135        });
4136
4137        // This would crash in the buggy code because:
4138        // - InlayMap expects edits from V1 to V2
4139        // - But subscription has E1 ∘ E2 (from V0 to V2)
4140        // - The calculation `buffer_edit.new.end + (cursor.end().0 - buffer_edit.old.end)`
4141        //   would produce an offset exceeding the buffer length
4142        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
4143        assert_eq!(snapshot.text(), "prefix more initial");
4144    }
4145}