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