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