display_map.rs

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