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