Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T01:32:31.929Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

display_map.rs

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