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