Skip to repository content4382 lines · 163.9 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T03:31:28.401Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
display_map.rs
1//! This module defines where the text should be displayed in an [`Editor`][Editor].
2//!
3//! Not literally though - rendering, layout and all that jazz is a responsibility of [`EditorElement`][EditorElement].
4//! Instead, [`DisplayMap`] decides where Inlays/Inlay hints are displayed, when
5//! to apply a soft wrap, where to add fold indicators, whether there are any tabs in the buffer that
6//! we display as spaces and where to display custom blocks (like diagnostics).
7//! Seems like a lot? That's because it is. [`DisplayMap`] is conceptually made up
8//! of several smaller structures that form a hierarchy (starting at the bottom):
9//! - [`InlayMap`] that decides where the [`Inlay`]s should be displayed.
10//! - [`FoldMap`] that decides where the fold indicators should be; it also tracks parts of a source file that are currently folded.
11//! - [`TabMap`] that keeps track of hard tabs in a buffer.
12//! - [`WrapMap`] that handles soft wrapping.
13//! - [`BlockMap`] that tracks custom blocks such as diagnostics that should be displayed within buffer.
14//! - [`DisplayMap`] that adds background highlights to the regions of text.
15//! Each one of those builds on top of preceding map.
16//!
17//! ## Structure of the display map layers
18//!
19//! Each layer in the map (and the multibuffer itself to some extent) has a few
20//! structures that are used to implement the public API available to the layer
21//! above:
22//! - a `Transform` type - this represents a region of text that the layer in
23//! question is "managing", that it transforms into a more "processed" text
24//! for the layer above. For example, the inlay map has an `enum Transform`
25//! that has two variants:
26//! - `Isomorphic`, representing a region of text that has no inlay hints (i.e.
27//! is passed through the map transparently)
28//! - `Inlay`, representing a location where an inlay hint is to be inserted.
29//! - a `TransformSummary` type, which is usually a struct with two fields:
30//! [`input: TextSummary`][`TextSummary`] and [`output: TextSummary`][`TextSummary`]. Here,
31//! `input` corresponds to "text in the layer below", and `output` corresponds to the text
32//! exposed to the layer above. So in the inlay map case, a `Transform::Isomorphic`'s summary is
33//! just `input = output = summary`, where `summary` is the [`TextSummary`] stored in that
34//! variant. Conversely, a `Transform::Inlay` always has an empty `input` summary, because it's
35//! not "replacing" any text that exists on disk. The `output` is the summary of the inlay text
36//! to be injected. - Various newtype wrappers for co-ordinate spaces (e.g. [`WrapRow`]
37//! represents a row index, after soft-wrapping (and all lower layers)).
38//! - A `Snapshot` type (e.g. [`InlaySnapshot`]) that captures the state of a layer at a specific
39//! point in time.
40//! - various APIs which drill through the layers below to work with the underlying text. Notably:
41//! - `fn text_summary_for_offset()` returns a [`TextSummary`] for the range in the co-ordinate
42//! space that the map in question is responsible for.
43//! - `fn <A>_point_to_<B>_point()` converts a point in co-ordinate space `A` into co-ordinate
44//! space `B`.
45//! - A [`RowInfo`] iterator (e.g. [`InlayBufferRows`]) and a [`Chunk`] iterator
46//! (e.g. [`InlayChunks`])
47//! - A `sync` function (e.g. [`InlayMap::sync`]) that takes a snapshot and list of [`Edit<T>`]s,
48//! and returns a new snapshot and a list of transformed [`Edit<S>`]s. Note that the generic
49//! parameter on `Edit` changes, since these methods take in edits in the co-ordinate space of
50//! the lower layer, and return edits in their own co-ordinate space. The term "edit" is
51//! slightly misleading, since an [`Edit<T>`] doesn't tell you what changed - rather it can be
52//! thought of as a "region to invalidate". In theory, it would be correct to always use a
53//! single edit that covers the entire range. However, this would lead to lots of unnecessary
54//! recalculation.
55//!
56//! See the docs for the [`inlay_map`] module for a more in-depth explanation of how a single layer
57//! works.
58//!
59//! [Editor]: crate::Editor
60//! [EditorElement]: crate::element::EditorElement
61//! [`TextSummary`]: multi_buffer::MBTextSummary
62//! [`WrapRow`]: wrap_map::WrapRow
63//! [`InlayBufferRows`]: inlay_map::InlayBufferRows
64//! [`InlayChunks`]: inlay_map::InlayChunks
65//! [`Edit<T>`]: text::Edit
66//! [`Edit<S>`]: text::Edit
67//! [`Chunk`]: language::Chunk
68
69#[macro_use]
70mod dimensions;
71
72mod block_map;
73mod crease_map;
74mod custom_highlights;
75mod fold_map;
76mod inlay_map;
77mod invisibles;
78mod tab_map;
79mod wrap_map;
80
81pub use crate::display_map::{fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap};
82pub use block_map::{
83 Block, BlockChunks as DisplayChunks, BlockContext, BlockId, BlockMap, BlockPlacement,
84 BlockPoint, BlockProperties, BlockRows, BlockStyle, CompanionView, CompanionViewMut,
85 CustomBlockId, EditorMargins, RenderBlock, StickyHeaderExcerpt,
86};
87pub use crease_map::*;
88pub use fold_map::{
89 ChunkRenderer, ChunkRendererContext, ChunkRendererId, Fold, FoldId, FoldPlaceholder, FoldPoint,
90};
91pub use inlay_map::{InlayOffset, InlayPoint};
92pub use invisibles::{is_invisible, replacement};
93pub use wrap_map::{WrapPoint, WrapRow, WrapSnapshot};
94
95use collections::{HashMap, HashSet, IndexSet};
96use gpui::{
97 App, Context, Entity, EntityId, Font, HighlightStyle, 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