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