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