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