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