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