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::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<Range<text::Anchor>>,
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 placeholder = self.fold_placeholder.clone();
1369 let creases = ranges.into_iter().filter_map(|range| {
1370 let mb_range = excerpt_ids
1371 .iter()
1372 .find_map(|&id| snapshot.anchor_range_in_excerpt(id, range.clone()))?;
1373 Some(Crease::simple(mb_range, placeholder.clone()))
1374 });
1375
1376 let new_ids = self.crease_map.insert(creases, &snapshot);
1377 if !new_ids.is_empty() {
1378 self.lsp_folding_crease_ids.insert(buffer_id, new_ids);
1379 }
1380 }
1381
1382 /// Removes all LSP folding-range creases for a single buffer.
1383 pub(super) fn clear_lsp_folding_ranges(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
1384 if let hash_map::Entry::Occupied(entry) = self.lsp_folding_crease_ids.entry(buffer_id) {
1385 let old_ids = entry.remove();
1386 let snapshot = self.buffer.read(cx).snapshot(cx);
1387 self.crease_map.remove(old_ids, &snapshot);
1388 }
1389 }
1390
1391 /// Returns `true` when at least one buffer has LSP folding-range creases.
1392 pub(super) fn has_lsp_folding_ranges(&self) -> bool {
1393 !self.lsp_folding_crease_ids.is_empty()
1394 }
1395
1396 #[instrument(skip_all)]
1397 pub fn insert_blocks(
1398 &mut self,
1399 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
1400 cx: &mut Context<Self>,
1401 ) -> Vec<CustomBlockId> {
1402 let (self_wrap_snapshot, self_wrap_edits) = self.sync_through_wrap(cx);
1403
1404 let companion_wrap_data = self.companion.as_ref().and_then(|(companion_dm, _)| {
1405 companion_dm
1406 .update(cx, |dm, cx| dm.sync_through_wrap(cx))
1407 .ok()
1408 });
1409
1410 let result = if let Some((companion_dm, companion)) = self.companion.as_ref()
1411 && let Some((snapshot, edits)) = companion_wrap_data.as_ref()
1412 {
1413 companion_dm
1414 .update(cx, |dm, cx| {
1415 companion.update(cx, |companion, _| {
1416 self.block_map
1417 .write(
1418 self_wrap_snapshot.clone(),
1419 self_wrap_edits.clone(),
1420 Some(CompanionViewMut::new(
1421 self.entity_id,
1422 snapshot,
1423 edits,
1424 companion,
1425 &mut dm.block_map,
1426 )),
1427 )
1428 .insert(blocks)
1429 })
1430 })
1431 .ok()
1432 .expect("success inserting blocks with companion")
1433 } else {
1434 self.block_map
1435 .write(self_wrap_snapshot.clone(), self_wrap_edits.clone(), None)
1436 .insert(blocks)
1437 };
1438
1439 if let Some((companion_dm, _)) = &self.companion {
1440 let _ = companion_dm.update(cx, |dm, cx| {
1441 if let Some((companion_snapshot, companion_edits)) = companion_wrap_data {
1442 let their_companion_ref = dm.companion.as_ref().map(|(_, c)| c.read(cx));
1443 dm.block_map.read(
1444 companion_snapshot,
1445 companion_edits,
1446 their_companion_ref.map(|c| {
1447 CompanionView::new(
1448 dm.entity_id,
1449 &self_wrap_snapshot,
1450 &self_wrap_edits,
1451 c,
1452 )
1453 }),
1454 );
1455 }
1456 });
1457 }
1458
1459 result
1460 }
1461
1462 #[instrument(skip_all)]
1463 pub fn resize_blocks(&mut self, heights: HashMap<CustomBlockId, u32>, cx: &mut Context<Self>) {
1464 let (self_wrap_snapshot, self_wrap_edits) = self.sync_through_wrap(cx);
1465
1466 let companion_wrap_data = self.companion.as_ref().and_then(|(companion_dm, _)| {
1467 companion_dm
1468 .update(cx, |dm, cx| dm.sync_through_wrap(cx))
1469 .ok()
1470 });
1471
1472 if let Some((companion_dm, companion)) = self.companion.as_ref()
1473 && let Some((snapshot, edits)) = companion_wrap_data.as_ref()
1474 {
1475 companion_dm
1476 .update(cx, |dm, cx| {
1477 companion.update(cx, |companion, _| {
1478 self.block_map
1479 .write(
1480 self_wrap_snapshot.clone(),
1481 self_wrap_edits.clone(),
1482 Some(CompanionViewMut::new(
1483 self.entity_id,
1484 snapshot,
1485 edits,
1486 companion,
1487 &mut dm.block_map,
1488 )),
1489 )
1490 .resize(heights);
1491 })
1492 })
1493 .ok();
1494 } else {
1495 self.block_map
1496 .write(self_wrap_snapshot.clone(), self_wrap_edits.clone(), None)
1497 .resize(heights);
1498 }
1499
1500 if let Some((companion_dm, _)) = &self.companion {
1501 let _ = companion_dm.update(cx, |dm, cx| {
1502 if let Some((companion_snapshot, companion_edits)) = companion_wrap_data {
1503 let their_companion_ref = dm.companion.as_ref().map(|(_, c)| c.read(cx));
1504 dm.block_map.read(
1505 companion_snapshot,
1506 companion_edits,
1507 their_companion_ref.map(|c| {
1508 CompanionView::new(
1509 dm.entity_id,
1510 &self_wrap_snapshot,
1511 &self_wrap_edits,
1512 c,
1513 )
1514 }),
1515 );
1516 }
1517 });
1518 }
1519 }
1520
1521 #[instrument(skip_all)]
1522 pub fn replace_blocks(&mut self, renderers: HashMap<CustomBlockId, RenderBlock>) {
1523 self.block_map.replace_blocks(renderers);
1524 }
1525
1526 #[instrument(skip_all)]
1527 pub fn remove_blocks(&mut self, ids: HashSet<CustomBlockId>, cx: &mut Context<Self>) {
1528 let (self_wrap_snapshot, self_wrap_edits) = self.sync_through_wrap(cx);
1529
1530 let companion_wrap_data = self.companion.as_ref().and_then(|(companion_dm, _)| {
1531 companion_dm
1532 .update(cx, |dm, cx| dm.sync_through_wrap(cx))
1533 .ok()
1534 });
1535
1536 if let Some((companion_dm, companion)) = self.companion.as_ref()
1537 && let Some((snapshot, edits)) = companion_wrap_data.as_ref()
1538 {
1539 companion_dm
1540 .update(cx, |dm, cx| {
1541 companion.update(cx, |companion, _| {
1542 self.block_map
1543 .write(
1544 self_wrap_snapshot.clone(),
1545 self_wrap_edits.clone(),
1546 Some(CompanionViewMut::new(
1547 self.entity_id,
1548 snapshot,
1549 edits,
1550 companion,
1551 &mut dm.block_map,
1552 )),
1553 )
1554 .remove(ids);
1555 })
1556 })
1557 .ok();
1558 } else {
1559 self.block_map
1560 .write(self_wrap_snapshot.clone(), self_wrap_edits.clone(), None)
1561 .remove(ids);
1562 }
1563
1564 if let Some((companion_dm, _)) = &self.companion {
1565 let _ = companion_dm.update(cx, |dm, cx| {
1566 if let Some((companion_snapshot, companion_edits)) = companion_wrap_data {
1567 let their_companion_ref = dm.companion.as_ref().map(|(_, c)| c.read(cx));
1568 dm.block_map.read(
1569 companion_snapshot,
1570 companion_edits,
1571 their_companion_ref.map(|c| {
1572 CompanionView::new(
1573 dm.entity_id,
1574 &self_wrap_snapshot,
1575 &self_wrap_edits,
1576 c,
1577 )
1578 }),
1579 );
1580 }
1581 });
1582 }
1583 }
1584
1585 #[instrument(skip_all)]
1586 pub fn row_for_block(
1587 &mut self,
1588 block_id: CustomBlockId,
1589 cx: &mut Context<Self>,
1590 ) -> Option<DisplayRow> {
1591 let (self_wrap_snapshot, self_wrap_edits) = self.sync_through_wrap(cx);
1592
1593 let companion_wrap_data = self.companion.as_ref().and_then(|(companion_dm, _)| {
1594 companion_dm
1595 .update(cx, |dm, cx| dm.sync_through_wrap(cx))
1596 .ok()
1597 });
1598
1599 let companion_ref = self.companion.as_ref().map(|(_, c)| c.read(cx));
1600 let companion_view = companion_wrap_data.as_ref().zip(companion_ref).map(
1601 |((snapshot, edits), companion)| {
1602 CompanionView::new(self.entity_id, snapshot, edits, companion)
1603 },
1604 );
1605
1606 let block_map = self.block_map.read(
1607 self_wrap_snapshot.clone(),
1608 self_wrap_edits.clone(),
1609 companion_view,
1610 );
1611 let block_row = block_map.row_for_block(block_id)?;
1612
1613 if let Some((companion_dm, _)) = &self.companion {
1614 let _ = companion_dm.update(cx, |dm, cx| {
1615 if let Some((companion_snapshot, companion_edits)) = companion_wrap_data {
1616 let their_companion_ref = dm.companion.as_ref().map(|(_, c)| c.read(cx));
1617 dm.block_map.read(
1618 companion_snapshot,
1619 companion_edits,
1620 their_companion_ref.map(|c| {
1621 CompanionView::new(
1622 dm.entity_id,
1623 &self_wrap_snapshot,
1624 &self_wrap_edits,
1625 c,
1626 )
1627 }),
1628 );
1629 }
1630 });
1631 }
1632
1633 Some(DisplayRow(block_row.0))
1634 }
1635
1636 #[instrument(skip_all)]
1637 pub fn highlight_text(
1638 &mut self,
1639 key: HighlightKey,
1640 ranges: Vec<Range<Anchor>>,
1641 style: HighlightStyle,
1642 merge: bool,
1643 cx: &App,
1644 ) {
1645 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
1646 let to_insert = match self.text_highlights.remove(&key).filter(|_| merge) {
1647 Some(previous) => {
1648 let mut merged_ranges = previous.1.clone();
1649 for new_range in ranges {
1650 let i = merged_ranges
1651 .binary_search_by(|probe| {
1652 probe.start.cmp(&new_range.start, &multi_buffer_snapshot)
1653 })
1654 .unwrap_or_else(|i| i);
1655 merged_ranges.insert(i, new_range);
1656 }
1657 Arc::new((style, merged_ranges))
1658 }
1659 None => Arc::new((style, ranges)),
1660 };
1661 self.text_highlights.insert(key, to_insert);
1662 }
1663
1664 #[instrument(skip_all)]
1665 pub(crate) fn highlight_inlays(
1666 &mut self,
1667 key: HighlightKey,
1668 highlights: Vec<InlayHighlight>,
1669 style: HighlightStyle,
1670 ) {
1671 for highlight in highlights {
1672 let update = self.inlay_highlights.update(&key, |highlights| {
1673 highlights.insert(highlight.inlay, (style, highlight.clone()))
1674 });
1675 if update.is_none() {
1676 self.inlay_highlights.insert(
1677 key,
1678 TreeMap::from_ordered_entries([(highlight.inlay, (style, highlight))]),
1679 );
1680 }
1681 }
1682 }
1683
1684 #[instrument(skip_all)]
1685 pub fn text_highlights(&self, key: HighlightKey) -> Option<(HighlightStyle, &[Range<Anchor>])> {
1686 let highlights = self.text_highlights.get(&key)?;
1687 Some((highlights.0, &highlights.1))
1688 }
1689
1690 pub fn all_text_highlights(
1691 &self,
1692 ) -> impl Iterator<Item = (&HighlightKey, &Arc<(HighlightStyle, Vec<Range<Anchor>>)>)> {
1693 self.text_highlights.iter()
1694 }
1695
1696 pub fn all_semantic_token_highlights(
1697 &self,
1698 ) -> impl Iterator<
1699 Item = (
1700 &BufferId,
1701 &(Arc<[SemanticTokenHighlight]>, Arc<HighlightStyleInterner>),
1702 ),
1703 > {
1704 self.semantic_token_highlights.iter()
1705 }
1706
1707 pub fn clear_highlights(&mut self, key: HighlightKey) -> bool {
1708 let mut cleared = self.text_highlights.remove(&key).is_some();
1709 cleared |= self.inlay_highlights.remove(&key).is_some();
1710 cleared
1711 }
1712
1713 pub fn clear_highlights_with(&mut self, mut f: impl FnMut(&HighlightKey) -> bool) -> bool {
1714 let mut cleared = false;
1715 self.text_highlights.retain(|k, _| {
1716 let b = !f(k);
1717 cleared |= b;
1718 b
1719 });
1720 self.inlay_highlights.retain(|k, _| {
1721 let b = !f(k);
1722 cleared |= b;
1723 b
1724 });
1725 cleared
1726 }
1727
1728 pub fn set_font(&self, font: Font, font_size: Pixels, cx: &mut Context<Self>) -> bool {
1729 self.wrap_map
1730 .update(cx, |map, cx| map.set_font_with_size(font, font_size, cx))
1731 }
1732
1733 pub fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut Context<Self>) -> bool {
1734 self.wrap_map
1735 .update(cx, |map, cx| map.set_wrap_width(width, cx))
1736 }
1737
1738 #[instrument(skip_all)]
1739 pub fn update_fold_widths(
1740 &mut self,
1741 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
1742 cx: &mut Context<Self>,
1743 ) -> bool {
1744 let snapshot = self.buffer.read(cx).snapshot(cx);
1745 let edits = self.buffer_subscription.consume().into_inner();
1746 let tab_size = Self::tab_size(&self.buffer, cx);
1747
1748 let companion_wrap_data = self.companion.as_ref().and_then(|(companion_dm, _)| {
1749 companion_dm
1750 .update(cx, |dm, cx| dm.sync_through_wrap(cx))
1751 .ok()
1752 });
1753
1754 let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
1755 let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
1756 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
1757 let (snapshot, edits) = self
1758 .wrap_map
1759 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
1760
1761 {
1762 let companion_ref = self.companion.as_ref().map(|(_, c)| c.read(cx));
1763 let companion_view = companion_wrap_data.as_ref().zip(companion_ref).map(
1764 |((snapshot, edits), companion)| {
1765 CompanionView::new(self.entity_id, snapshot, edits, companion)
1766 },
1767 );
1768 self.block_map.read(snapshot, edits, companion_view);
1769 }
1770
1771 let (snapshot, edits) = fold_map.update_fold_widths(widths);
1772 let widths_changed = !edits.is_empty();
1773 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
1774 let (self_new_wrap_snapshot, self_new_wrap_edits) = self
1775 .wrap_map
1776 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
1777
1778 let (self_wrap_snapshot, self_wrap_edits) =
1779 (self_new_wrap_snapshot.clone(), self_new_wrap_edits.clone());
1780
1781 {
1782 let companion_ref = self.companion.as_ref().map(|(_, c)| c.read(cx));
1783 let companion_view = companion_wrap_data.as_ref().zip(companion_ref).map(
1784 |((snapshot, edits), companion)| {
1785 CompanionView::new(self.entity_id, snapshot, edits, companion)
1786 },
1787 );
1788 self.block_map
1789 .read(self_new_wrap_snapshot, self_new_wrap_edits, companion_view);
1790 }
1791
1792 if let Some((companion_dm, _)) = &self.companion {
1793 let _ = companion_dm.update(cx, |dm, cx| {
1794 if let Some((companion_snapshot, companion_edits)) = companion_wrap_data {
1795 let their_companion_ref = dm.companion.as_ref().map(|(_, c)| c.read(cx));
1796 dm.block_map.read(
1797 companion_snapshot,
1798 companion_edits,
1799 their_companion_ref.map(|c| {
1800 CompanionView::new(
1801 dm.entity_id,
1802 &self_wrap_snapshot,
1803 &self_wrap_edits,
1804 c,
1805 )
1806 }),
1807 );
1808 }
1809 });
1810 }
1811
1812 widths_changed
1813 }
1814
1815 pub(crate) fn current_inlays(&self) -> impl Iterator<Item = &Inlay> {
1816 self.inlay_map.current_inlays()
1817 }
1818
1819 #[instrument(skip_all)]
1820 pub(crate) fn splice_inlays(
1821 &mut self,
1822 to_remove: &[InlayId],
1823 to_insert: Vec<Inlay>,
1824 cx: &mut Context<Self>,
1825 ) {
1826 if to_remove.is_empty() && to_insert.is_empty() {
1827 return;
1828 }
1829 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
1830 let edits = self.buffer_subscription.consume().into_inner();
1831 let tab_size = Self::tab_size(&self.buffer, cx);
1832
1833 let companion_wrap_data = self.companion.as_ref().and_then(|(companion_dm, _)| {
1834 companion_dm
1835 .update(cx, |dm, cx| dm.sync_through_wrap(cx))
1836 .ok()
1837 });
1838
1839 let (snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits);
1840 let (snapshot, edits) = self.fold_map.read(snapshot, edits);
1841 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
1842 let (snapshot, edits) = self
1843 .wrap_map
1844 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
1845
1846 {
1847 let companion_ref = self.companion.as_ref().map(|(_, c)| c.read(cx));
1848 let companion_view = companion_wrap_data.as_ref().zip(companion_ref).map(
1849 |((snapshot, edits), companion)| {
1850 CompanionView::new(self.entity_id, snapshot, edits, companion)
1851 },
1852 );
1853 self.block_map.read(snapshot, edits, companion_view);
1854 }
1855
1856 let (snapshot, edits) = self.inlay_map.splice(to_remove, to_insert);
1857 let (snapshot, edits) = self.fold_map.read(snapshot, edits);
1858 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
1859 let (self_new_wrap_snapshot, self_new_wrap_edits) = self
1860 .wrap_map
1861 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
1862
1863 let (self_wrap_snapshot, self_wrap_edits) =
1864 (self_new_wrap_snapshot.clone(), self_new_wrap_edits.clone());
1865
1866 {
1867 let companion_ref = self.companion.as_ref().map(|(_, c)| c.read(cx));
1868 let companion_view = companion_wrap_data.as_ref().zip(companion_ref).map(
1869 |((snapshot, edits), companion)| {
1870 CompanionView::new(self.entity_id, snapshot, edits, companion)
1871 },
1872 );
1873 self.block_map
1874 .read(self_new_wrap_snapshot, self_new_wrap_edits, companion_view);
1875 }
1876
1877 if let Some((companion_dm, _)) = &self.companion {
1878 let _ = companion_dm.update(cx, |dm, cx| {
1879 if let Some((companion_snapshot, companion_edits)) = companion_wrap_data {
1880 let their_companion_ref = dm.companion.as_ref().map(|(_, c)| c.read(cx));
1881 dm.block_map.read(
1882 companion_snapshot,
1883 companion_edits,
1884 their_companion_ref.map(|c| {
1885 CompanionView::new(
1886 dm.entity_id,
1887 &self_wrap_snapshot,
1888 &self_wrap_edits,
1889 c,
1890 )
1891 }),
1892 );
1893 }
1894 });
1895 }
1896 }
1897
1898 #[instrument(skip_all)]
1899 fn tab_size(buffer: &Entity<MultiBuffer>, cx: &App) -> NonZeroU32 {
1900 let buffer = buffer.read(cx).as_singleton().map(|buffer| buffer.read(cx));
1901 let language = buffer
1902 .and_then(|buffer| buffer.language())
1903 .map(|l| l.name());
1904 let file = buffer.and_then(|buffer| buffer.file());
1905 language_settings(language, file, cx).tab_size
1906 }
1907
1908 #[cfg(test)]
1909 pub fn is_rewrapping(&self, cx: &gpui::App) -> bool {
1910 self.wrap_map.read(cx).is_rewrapping()
1911 }
1912
1913 pub fn invalidate_semantic_highlights(&mut self, buffer_id: BufferId) {
1914 self.semantic_token_highlights.remove(&buffer_id);
1915 }
1916}
1917
1918#[derive(Debug, Default)]
1919pub(crate) struct Highlights<'a> {
1920 pub text_highlights: Option<&'a TextHighlights>,
1921 pub inlay_highlights: Option<&'a InlayHighlights>,
1922 pub semantic_token_highlights: Option<&'a SemanticTokensHighlights>,
1923 pub styles: HighlightStyles,
1924}
1925
1926#[derive(Clone, Copy, Debug)]
1927pub struct EditPredictionStyles {
1928 pub insertion: HighlightStyle,
1929 pub whitespace: HighlightStyle,
1930}
1931
1932#[derive(Default, Debug, Clone, Copy)]
1933pub struct HighlightStyles {
1934 pub inlay_hint: Option<HighlightStyle>,
1935 pub edit_prediction: Option<EditPredictionStyles>,
1936}
1937
1938#[derive(Clone)]
1939pub enum ChunkReplacement {
1940 Renderer(ChunkRenderer),
1941 Str(SharedString),
1942}
1943
1944pub struct HighlightedChunk<'a> {
1945 pub text: &'a str,
1946 pub style: Option<HighlightStyle>,
1947 pub is_tab: bool,
1948 pub is_inlay: bool,
1949 pub replacement: Option<ChunkReplacement>,
1950}
1951
1952impl<'a> HighlightedChunk<'a> {
1953 #[instrument(skip_all)]
1954 fn highlight_invisibles(
1955 self,
1956 editor_style: &'a EditorStyle,
1957 ) -> impl Iterator<Item = Self> + 'a {
1958 let mut chars = self.text.chars().peekable();
1959 let mut text = self.text;
1960 let style = self.style;
1961 let is_tab = self.is_tab;
1962 let renderer = self.replacement;
1963 let is_inlay = self.is_inlay;
1964 iter::from_fn(move || {
1965 let mut prefix_len = 0;
1966 while let Some(&ch) = chars.peek() {
1967 if !is_invisible(ch) {
1968 prefix_len += ch.len_utf8();
1969 chars.next();
1970 continue;
1971 }
1972 if prefix_len > 0 {
1973 let (prefix, suffix) = text.split_at(prefix_len);
1974 text = suffix;
1975 return Some(HighlightedChunk {
1976 text: prefix,
1977 style,
1978 is_tab,
1979 is_inlay,
1980 replacement: renderer.clone(),
1981 });
1982 }
1983 chars.next();
1984 let (prefix, suffix) = text.split_at(ch.len_utf8());
1985 text = suffix;
1986 if let Some(replacement) = replacement(ch) {
1987 let invisible_highlight = HighlightStyle {
1988 background_color: Some(editor_style.status.hint_background),
1989 underline: Some(UnderlineStyle {
1990 color: Some(editor_style.status.hint),
1991 thickness: px(1.),
1992 wavy: false,
1993 }),
1994 ..Default::default()
1995 };
1996 let invisible_style = if let Some(style) = style {
1997 style.highlight(invisible_highlight)
1998 } else {
1999 invisible_highlight
2000 };
2001 return Some(HighlightedChunk {
2002 text: prefix,
2003 style: Some(invisible_style),
2004 is_tab: false,
2005 is_inlay,
2006 replacement: Some(ChunkReplacement::Str(replacement.into())),
2007 });
2008 } else {
2009 let invisible_highlight = HighlightStyle {
2010 background_color: Some(editor_style.status.hint_background),
2011 underline: Some(UnderlineStyle {
2012 color: Some(editor_style.status.hint),
2013 thickness: px(1.),
2014 wavy: false,
2015 }),
2016 ..Default::default()
2017 };
2018 let invisible_style = if let Some(style) = style {
2019 style.highlight(invisible_highlight)
2020 } else {
2021 invisible_highlight
2022 };
2023
2024 return Some(HighlightedChunk {
2025 text: prefix,
2026 style: Some(invisible_style),
2027 is_tab: false,
2028 is_inlay,
2029 replacement: renderer.clone(),
2030 });
2031 }
2032 }
2033
2034 if !text.is_empty() {
2035 let remainder = text;
2036 text = "";
2037 Some(HighlightedChunk {
2038 text: remainder,
2039 style,
2040 is_tab,
2041 is_inlay,
2042 replacement: renderer.clone(),
2043 })
2044 } else {
2045 None
2046 }
2047 })
2048 }
2049}
2050
2051#[derive(Clone)]
2052pub struct DisplaySnapshot {
2053 pub display_map_id: EntityId,
2054 pub companion_display_snapshot: Option<Arc<DisplaySnapshot>>,
2055 pub crease_snapshot: CreaseSnapshot,
2056 block_snapshot: BlockSnapshot,
2057 text_highlights: TextHighlights,
2058 inlay_highlights: InlayHighlights,
2059 semantic_token_highlights: SemanticTokensHighlights,
2060 clip_at_line_ends: bool,
2061 masked: bool,
2062 diagnostics_max_severity: DiagnosticSeverity,
2063 pub(crate) fold_placeholder: FoldPlaceholder,
2064 /// When true, LSP folding ranges are used via the crease map and the
2065 /// indent-based fallback in `crease_for_buffer_row` is skipped.
2066 pub(crate) use_lsp_folding_ranges: bool,
2067}
2068
2069impl DisplaySnapshot {
2070 pub fn companion_snapshot(&self) -> Option<&DisplaySnapshot> {
2071 self.companion_display_snapshot.as_deref()
2072 }
2073
2074 pub fn wrap_snapshot(&self) -> &WrapSnapshot {
2075 &self.block_snapshot.wrap_snapshot
2076 }
2077 pub fn tab_snapshot(&self) -> &TabSnapshot {
2078 &self.block_snapshot.wrap_snapshot.tab_snapshot
2079 }
2080
2081 pub fn fold_snapshot(&self) -> &FoldSnapshot {
2082 &self.block_snapshot.wrap_snapshot.tab_snapshot.fold_snapshot
2083 }
2084
2085 pub fn inlay_snapshot(&self) -> &InlaySnapshot {
2086 &self
2087 .block_snapshot
2088 .wrap_snapshot
2089 .tab_snapshot
2090 .fold_snapshot
2091 .inlay_snapshot
2092 }
2093
2094 pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot {
2095 &self
2096 .block_snapshot
2097 .wrap_snapshot
2098 .tab_snapshot
2099 .fold_snapshot
2100 .inlay_snapshot
2101 .buffer
2102 }
2103
2104 #[cfg(test)]
2105 pub fn fold_count(&self) -> usize {
2106 self.fold_snapshot().fold_count()
2107 }
2108
2109 pub fn is_empty(&self) -> bool {
2110 self.buffer_snapshot().len() == MultiBufferOffset(0)
2111 }
2112
2113 /// Returns whether tree-sitter syntax highlighting should be used.
2114 /// Returns `false` if any buffer with semantic token highlights has the "full" mode setting,
2115 /// meaning LSP semantic tokens should replace tree-sitter highlighting.
2116 pub fn use_tree_sitter_for_syntax(&self, position: DisplayRow, cx: &App) -> bool {
2117 let position = DisplayPoint::new(position, 0);
2118 let Some((buffer_snapshot, ..)) = self.point_to_buffer_point(position.to_point(self))
2119 else {
2120 return false;
2121 };
2122 let settings = language_settings(
2123 buffer_snapshot.language().map(|l| l.name()),
2124 buffer_snapshot.file(),
2125 cx,
2126 );
2127 settings.semantic_tokens.use_tree_sitter()
2128 }
2129
2130 pub fn row_infos(&self, start_row: DisplayRow) -> impl Iterator<Item = RowInfo> + '_ {
2131 self.block_snapshot.row_infos(BlockRow(start_row.0))
2132 }
2133
2134 pub fn widest_line_number(&self) -> u32 {
2135 self.buffer_snapshot().widest_line_number()
2136 }
2137
2138 #[instrument(skip_all)]
2139 pub fn prev_line_boundary(&self, mut point: MultiBufferPoint) -> (Point, DisplayPoint) {
2140 loop {
2141 let mut inlay_point = self.inlay_snapshot().to_inlay_point(point);
2142 let mut fold_point = self.fold_snapshot().to_fold_point(inlay_point, Bias::Left);
2143 fold_point.0.column = 0;
2144 inlay_point = fold_point.to_inlay_point(self.fold_snapshot());
2145 point = self.inlay_snapshot().to_buffer_point(inlay_point);
2146
2147 let mut display_point = self.point_to_display_point(point, Bias::Left);
2148 *display_point.column_mut() = 0;
2149 let next_point = self.display_point_to_point(display_point, Bias::Left);
2150 if next_point == point {
2151 return (point, display_point);
2152 }
2153 point = next_point;
2154 }
2155 }
2156
2157 #[instrument(skip_all)]
2158 pub fn next_line_boundary(
2159 &self,
2160 mut point: MultiBufferPoint,
2161 ) -> (MultiBufferPoint, DisplayPoint) {
2162 let original_point = point;
2163 loop {
2164 let mut inlay_point = self.inlay_snapshot().to_inlay_point(point);
2165 let mut fold_point = self.fold_snapshot().to_fold_point(inlay_point, Bias::Right);
2166 fold_point.0.column = self.fold_snapshot().line_len(fold_point.row());
2167 inlay_point = fold_point.to_inlay_point(self.fold_snapshot());
2168 point = self.inlay_snapshot().to_buffer_point(inlay_point);
2169
2170 let mut display_point = self.point_to_display_point(point, Bias::Right);
2171 *display_point.column_mut() = self.line_len(display_point.row());
2172 let next_point = self.display_point_to_point(display_point, Bias::Right);
2173 if next_point == point || original_point == point || original_point == next_point {
2174 return (point, display_point);
2175 }
2176 point = next_point;
2177 }
2178 }
2179
2180 // used by line_mode selections and tries to match vim behavior
2181 pub fn expand_to_line(&self, range: Range<Point>) -> Range<Point> {
2182 let new_start = MultiBufferPoint::new(range.start.row, 0);
2183 let new_end = if range.end.column > 0 {
2184 MultiBufferPoint::new(
2185 range.end.row,
2186 self.buffer_snapshot()
2187 .line_len(MultiBufferRow(range.end.row)),
2188 )
2189 } else {
2190 range.end
2191 };
2192
2193 new_start..new_end
2194 }
2195
2196 #[instrument(skip_all)]
2197 pub fn point_to_display_point(&self, point: MultiBufferPoint, bias: Bias) -> DisplayPoint {
2198 let inlay_point = self.inlay_snapshot().to_inlay_point(point);
2199 let fold_point = self.fold_snapshot().to_fold_point(inlay_point, bias);
2200 let tab_point = self.tab_snapshot().fold_point_to_tab_point(fold_point);
2201 let wrap_point = self.wrap_snapshot().tab_point_to_wrap_point(tab_point);
2202 let block_point = self.block_snapshot.to_block_point(wrap_point);
2203 DisplayPoint(block_point)
2204 }
2205
2206 pub fn display_point_to_point(&self, point: DisplayPoint, bias: Bias) -> Point {
2207 self.inlay_snapshot()
2208 .to_buffer_point(self.display_point_to_inlay_point(point, bias))
2209 }
2210
2211 pub fn display_point_to_inlay_offset(&self, point: DisplayPoint, bias: Bias) -> InlayOffset {
2212 self.inlay_snapshot()
2213 .to_offset(self.display_point_to_inlay_point(point, bias))
2214 }
2215
2216 pub fn anchor_to_inlay_offset(&self, anchor: Anchor) -> InlayOffset {
2217 self.inlay_snapshot()
2218 .to_inlay_offset(anchor.to_offset(self.buffer_snapshot()))
2219 }
2220
2221 pub fn display_point_to_anchor(&self, point: DisplayPoint, bias: Bias) -> Anchor {
2222 self.buffer_snapshot()
2223 .anchor_at(point.to_offset(self, bias), bias)
2224 }
2225
2226 #[instrument(skip_all)]
2227 fn display_point_to_inlay_point(&self, point: DisplayPoint, bias: Bias) -> InlayPoint {
2228 let block_point = point.0;
2229 let wrap_point = self.block_snapshot.to_wrap_point(block_point, bias);
2230 let tab_point = self.wrap_snapshot().to_tab_point(wrap_point);
2231 let fold_point = self
2232 .tab_snapshot()
2233 .tab_point_to_fold_point(tab_point, bias)
2234 .0;
2235 fold_point.to_inlay_point(self.fold_snapshot())
2236 }
2237
2238 #[instrument(skip_all)]
2239 pub fn display_point_to_fold_point(&self, point: DisplayPoint, bias: Bias) -> FoldPoint {
2240 let block_point = point.0;
2241 let wrap_point = self.block_snapshot.to_wrap_point(block_point, bias);
2242 let tab_point = self.wrap_snapshot().to_tab_point(wrap_point);
2243 self.tab_snapshot()
2244 .tab_point_to_fold_point(tab_point, bias)
2245 .0
2246 }
2247
2248 #[instrument(skip_all)]
2249 pub fn fold_point_to_display_point(&self, fold_point: FoldPoint) -> DisplayPoint {
2250 let tab_point = self.tab_snapshot().fold_point_to_tab_point(fold_point);
2251 let wrap_point = self.wrap_snapshot().tab_point_to_wrap_point(tab_point);
2252 let block_point = self.block_snapshot.to_block_point(wrap_point);
2253 DisplayPoint(block_point)
2254 }
2255
2256 pub fn max_point(&self) -> DisplayPoint {
2257 DisplayPoint(self.block_snapshot.max_point())
2258 }
2259
2260 /// Returns text chunks starting at the given display row until the end of the file
2261 #[instrument(skip_all)]
2262 pub fn text_chunks(&self, display_row: DisplayRow) -> impl Iterator<Item = &str> {
2263 self.block_snapshot
2264 .chunks(
2265 BlockRow(display_row.0)..BlockRow(self.max_point().row().next_row().0),
2266 false,
2267 self.masked,
2268 Highlights::default(),
2269 )
2270 .map(|h| h.text)
2271 }
2272
2273 /// Returns text chunks starting at the end of the given display row in reverse until the start of the file
2274 #[instrument(skip_all)]
2275 pub fn reverse_text_chunks(&self, display_row: DisplayRow) -> impl Iterator<Item = &str> {
2276 (0..=display_row.0).rev().flat_map(move |row| {
2277 self.block_snapshot
2278 .chunks(
2279 BlockRow(row)..BlockRow(row + 1),
2280 false,
2281 self.masked,
2282 Highlights::default(),
2283 )
2284 .map(|h| h.text)
2285 .collect::<Vec<_>>()
2286 .into_iter()
2287 .rev()
2288 })
2289 }
2290
2291 #[instrument(skip_all)]
2292 pub fn chunks(
2293 &self,
2294 display_rows: Range<DisplayRow>,
2295 language_aware: bool,
2296 highlight_styles: HighlightStyles,
2297 ) -> DisplayChunks<'_> {
2298 self.block_snapshot.chunks(
2299 BlockRow(display_rows.start.0)..BlockRow(display_rows.end.0),
2300 language_aware,
2301 self.masked,
2302 Highlights {
2303 text_highlights: Some(&self.text_highlights),
2304 inlay_highlights: Some(&self.inlay_highlights),
2305 semantic_token_highlights: Some(&self.semantic_token_highlights),
2306 styles: highlight_styles,
2307 },
2308 )
2309 }
2310
2311 #[instrument(skip_all)]
2312 pub fn highlighted_chunks<'a>(
2313 &'a self,
2314 display_rows: Range<DisplayRow>,
2315 language_aware: bool,
2316 editor_style: &'a EditorStyle,
2317 ) -> impl Iterator<Item = HighlightedChunk<'a>> {
2318 self.chunks(
2319 display_rows,
2320 language_aware,
2321 HighlightStyles {
2322 inlay_hint: Some(editor_style.inlay_hints_style),
2323 edit_prediction: Some(editor_style.edit_prediction_styles),
2324 },
2325 )
2326 .flat_map(|chunk| {
2327 let syntax_highlight_style = chunk
2328 .syntax_highlight_id
2329 .and_then(|id| id.style(&editor_style.syntax));
2330
2331 let chunk_highlight = chunk.highlight_style.map(|chunk_highlight| {
2332 HighlightStyle {
2333 // For color inlays, blend the color with the editor background
2334 // if the color has transparency (alpha < 1.0)
2335 color: chunk_highlight.color.map(|color| {
2336 if chunk.is_inlay && !color.is_opaque() {
2337 editor_style.background.blend(color)
2338 } else {
2339 color
2340 }
2341 }),
2342 ..chunk_highlight
2343 }
2344 });
2345
2346 let diagnostic_highlight = chunk
2347 .diagnostic_severity
2348 .filter(|severity| {
2349 self.diagnostics_max_severity
2350 .into_lsp()
2351 .is_some_and(|max_severity| severity <= &max_severity)
2352 })
2353 .map(|severity| HighlightStyle {
2354 fade_out: chunk
2355 .is_unnecessary
2356 .then_some(editor_style.unnecessary_code_fade),
2357 underline: (chunk.underline
2358 && editor_style.show_underlines
2359 && !(chunk.is_unnecessary && severity > lsp::DiagnosticSeverity::WARNING))
2360 .then(|| {
2361 let diagnostic_color =
2362 super::diagnostic_style(severity, &editor_style.status);
2363 UnderlineStyle {
2364 color: Some(diagnostic_color),
2365 thickness: 1.0.into(),
2366 wavy: true,
2367 }
2368 }),
2369 ..Default::default()
2370 });
2371
2372 let style = [
2373 syntax_highlight_style,
2374 chunk_highlight,
2375 diagnostic_highlight,
2376 ]
2377 .into_iter()
2378 .flatten()
2379 .reduce(|acc, highlight| acc.highlight(highlight));
2380
2381 HighlightedChunk {
2382 text: chunk.text,
2383 style,
2384 is_tab: chunk.is_tab,
2385 is_inlay: chunk.is_inlay,
2386 replacement: chunk.renderer.map(ChunkReplacement::Renderer),
2387 }
2388 .highlight_invisibles(editor_style)
2389 })
2390 }
2391
2392 #[instrument(skip_all)]
2393 pub fn layout_row(
2394 &self,
2395 display_row: DisplayRow,
2396 TextLayoutDetails {
2397 text_system,
2398 editor_style,
2399 rem_size,
2400 scroll_anchor: _,
2401 visible_rows: _,
2402 vertical_scroll_margin: _,
2403 }: &TextLayoutDetails,
2404 ) -> Arc<LineLayout> {
2405 let mut runs = Vec::new();
2406 let mut line = String::new();
2407
2408 let range = display_row..display_row.next_row();
2409 for chunk in self.highlighted_chunks(range, false, editor_style) {
2410 line.push_str(chunk.text);
2411
2412 let text_style = if let Some(style) = chunk.style {
2413 Cow::Owned(editor_style.text.clone().highlight(style))
2414 } else {
2415 Cow::Borrowed(&editor_style.text)
2416 };
2417
2418 runs.push(text_style.to_run(chunk.text.len()))
2419 }
2420
2421 if line.ends_with('\n') {
2422 line.pop();
2423 if let Some(last_run) = runs.last_mut() {
2424 last_run.len -= 1;
2425 if last_run.len == 0 {
2426 runs.pop();
2427 }
2428 }
2429 }
2430
2431 let font_size = editor_style.text.font_size.to_pixels(*rem_size);
2432 text_system.layout_line(&line, font_size, &runs, None)
2433 }
2434
2435 pub fn x_for_display_point(
2436 &self,
2437 display_point: DisplayPoint,
2438 text_layout_details: &TextLayoutDetails,
2439 ) -> Pixels {
2440 let line = self.layout_row(display_point.row(), text_layout_details);
2441 line.x_for_index(display_point.column() as usize)
2442 }
2443
2444 pub fn display_column_for_x(
2445 &self,
2446 display_row: DisplayRow,
2447 x: Pixels,
2448 details: &TextLayoutDetails,
2449 ) -> u32 {
2450 let layout_line = self.layout_row(display_row, details);
2451 layout_line.closest_index_for_x(x) as u32
2452 }
2453
2454 #[instrument(skip_all)]
2455 pub fn grapheme_at(&self, mut point: DisplayPoint) -> Option<SharedString> {
2456 point = DisplayPoint(self.block_snapshot.clip_point(point.0, Bias::Left));
2457 let chars = self
2458 .text_chunks(point.row())
2459 .flat_map(str::chars)
2460 .skip_while({
2461 let mut column = 0;
2462 move |char| {
2463 let at_point = column >= point.column();
2464 column += char.len_utf8() as u32;
2465 !at_point
2466 }
2467 })
2468 .take_while({
2469 let mut prev = false;
2470 move |char| {
2471 let now = char.is_ascii();
2472 let end = char.is_ascii() && (char.is_ascii_whitespace() || prev);
2473 prev = now;
2474 !end
2475 }
2476 });
2477 chars.collect::<String>().graphemes(true).next().map(|s| {
2478 if let Some(invisible) = s.chars().next().filter(|&c| is_invisible(c)) {
2479 replacement(invisible).unwrap_or(s).to_owned().into()
2480 } else if s == "\n" {
2481 " ".into()
2482 } else {
2483 s.to_owned().into()
2484 }
2485 })
2486 }
2487
2488 pub fn buffer_chars_at(
2489 &self,
2490 mut offset: MultiBufferOffset,
2491 ) -> impl Iterator<Item = (char, MultiBufferOffset)> + '_ {
2492 self.buffer_snapshot().chars_at(offset).map(move |ch| {
2493 let ret = (ch, offset);
2494 offset += ch.len_utf8();
2495 ret
2496 })
2497 }
2498
2499 pub fn reverse_buffer_chars_at(
2500 &self,
2501 mut offset: MultiBufferOffset,
2502 ) -> impl Iterator<Item = (char, MultiBufferOffset)> + '_ {
2503 self.buffer_snapshot()
2504 .reversed_chars_at(offset)
2505 .map(move |ch| {
2506 offset -= ch.len_utf8();
2507 (ch, offset)
2508 })
2509 }
2510
2511 pub fn clip_point(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint {
2512 let mut clipped = self.block_snapshot.clip_point(point.0, bias);
2513 if self.clip_at_line_ends {
2514 clipped = self.clip_at_line_end(DisplayPoint(clipped)).0
2515 }
2516 DisplayPoint(clipped)
2517 }
2518
2519 pub fn clip_ignoring_line_ends(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint {
2520 DisplayPoint(self.block_snapshot.clip_point(point.0, bias))
2521 }
2522
2523 pub fn clip_at_line_end(&self, display_point: DisplayPoint) -> DisplayPoint {
2524 let mut point = self.display_point_to_point(display_point, Bias::Left);
2525
2526 if point.column != self.buffer_snapshot().line_len(MultiBufferRow(point.row)) {
2527 return display_point;
2528 }
2529 point.column = point.column.saturating_sub(1);
2530 point = self.buffer_snapshot().clip_point(point, Bias::Left);
2531 self.point_to_display_point(point, Bias::Left)
2532 }
2533
2534 pub fn folds_in_range<T>(&self, range: Range<T>) -> impl Iterator<Item = &Fold>
2535 where
2536 T: ToOffset,
2537 {
2538 self.fold_snapshot().folds_in_range(range)
2539 }
2540
2541 pub fn blocks_in_range(
2542 &self,
2543 rows: Range<DisplayRow>,
2544 ) -> impl Iterator<Item = (DisplayRow, &Block)> {
2545 self.block_snapshot
2546 .blocks_in_range(BlockRow(rows.start.0)..BlockRow(rows.end.0))
2547 .map(|(row, block)| (DisplayRow(row.0), block))
2548 }
2549
2550 pub fn sticky_header_excerpt(&self, row: f64) -> Option<StickyHeaderExcerpt<'_>> {
2551 self.block_snapshot.sticky_header_excerpt(row)
2552 }
2553
2554 pub fn block_for_id(&self, id: BlockId) -> Option<Block> {
2555 self.block_snapshot.block_for_id(id)
2556 }
2557
2558 pub fn intersects_fold<T: ToOffset>(&self, offset: T) -> bool {
2559 self.fold_snapshot().intersects_fold(offset)
2560 }
2561
2562 pub fn is_line_folded(&self, buffer_row: MultiBufferRow) -> bool {
2563 self.block_snapshot.is_line_replaced(buffer_row)
2564 || self.fold_snapshot().is_line_folded(buffer_row)
2565 }
2566
2567 pub fn is_block_line(&self, display_row: DisplayRow) -> bool {
2568 self.block_snapshot.is_block_line(BlockRow(display_row.0))
2569 }
2570
2571 pub fn is_folded_buffer_header(&self, display_row: DisplayRow) -> bool {
2572 self.block_snapshot
2573 .is_folded_buffer_header(BlockRow(display_row.0))
2574 }
2575
2576 pub fn soft_wrap_indent(&self, display_row: DisplayRow) -> Option<u32> {
2577 let wrap_row = self
2578 .block_snapshot
2579 .to_wrap_point(BlockPoint::new(BlockRow(display_row.0), 0), Bias::Left)
2580 .row();
2581 self.wrap_snapshot().soft_wrap_indent(wrap_row)
2582 }
2583
2584 pub fn text(&self) -> String {
2585 self.text_chunks(DisplayRow(0)).collect()
2586 }
2587
2588 pub fn line(&self, display_row: DisplayRow) -> String {
2589 let mut result = String::new();
2590 for chunk in self.text_chunks(display_row) {
2591 if let Some(ix) = chunk.find('\n') {
2592 result.push_str(&chunk[0..ix]);
2593 break;
2594 } else {
2595 result.push_str(chunk);
2596 }
2597 }
2598 result
2599 }
2600
2601 pub fn line_indent_for_buffer_row(&self, buffer_row: MultiBufferRow) -> LineIndent {
2602 self.buffer_snapshot().line_indent_for_row(buffer_row)
2603 }
2604
2605 pub fn line_len(&self, row: DisplayRow) -> u32 {
2606 self.block_snapshot.line_len(BlockRow(row.0))
2607 }
2608
2609 pub fn longest_row(&self) -> DisplayRow {
2610 DisplayRow(self.block_snapshot.longest_row().0)
2611 }
2612
2613 pub fn longest_row_in_range(&self, range: Range<DisplayRow>) -> DisplayRow {
2614 let block_range = BlockRow(range.start.0)..BlockRow(range.end.0);
2615 let longest_row = self.block_snapshot.longest_row_in_range(block_range);
2616 DisplayRow(longest_row.0)
2617 }
2618
2619 pub fn starts_indent(&self, buffer_row: MultiBufferRow) -> bool {
2620 let max_row = self.buffer_snapshot().max_row();
2621 if buffer_row >= max_row {
2622 return false;
2623 }
2624
2625 let line_indent = self.line_indent_for_buffer_row(buffer_row);
2626 if line_indent.is_line_blank() {
2627 return false;
2628 }
2629
2630 (buffer_row.0 + 1..=max_row.0)
2631 .find_map(|next_row| {
2632 let next_line_indent = self.line_indent_for_buffer_row(MultiBufferRow(next_row));
2633 if next_line_indent.raw_len() > line_indent.raw_len() {
2634 Some(true)
2635 } else if !next_line_indent.is_line_blank() {
2636 Some(false)
2637 } else {
2638 None
2639 }
2640 })
2641 .unwrap_or(false)
2642 }
2643
2644 #[instrument(skip_all)]
2645 pub fn crease_for_buffer_row(&self, buffer_row: MultiBufferRow) -> Option<Crease<Point>> {
2646 let start =
2647 MultiBufferPoint::new(buffer_row.0, self.buffer_snapshot().line_len(buffer_row));
2648 if let Some(crease) = self
2649 .crease_snapshot
2650 .query_row(buffer_row, self.buffer_snapshot())
2651 {
2652 match crease {
2653 Crease::Inline {
2654 range,
2655 placeholder,
2656 render_toggle,
2657 render_trailer,
2658 metadata,
2659 } => Some(Crease::Inline {
2660 range: range.to_point(self.buffer_snapshot()),
2661 placeholder: placeholder.clone(),
2662 render_toggle: render_toggle.clone(),
2663 render_trailer: render_trailer.clone(),
2664 metadata: metadata.clone(),
2665 }),
2666 Crease::Block {
2667 range,
2668 block_height,
2669 block_style,
2670 render_block,
2671 block_priority,
2672 render_toggle,
2673 } => Some(Crease::Block {
2674 range: range.to_point(self.buffer_snapshot()),
2675 block_height: *block_height,
2676 block_style: *block_style,
2677 render_block: render_block.clone(),
2678 block_priority: *block_priority,
2679 render_toggle: render_toggle.clone(),
2680 }),
2681 }
2682 } else if !self.use_lsp_folding_ranges
2683 && self.starts_indent(MultiBufferRow(start.row))
2684 && !self.is_line_folded(MultiBufferRow(start.row))
2685 {
2686 let start_line_indent = self.line_indent_for_buffer_row(buffer_row);
2687 let max_point = self.buffer_snapshot().max_point();
2688 let mut end = None;
2689
2690 for row in (buffer_row.0 + 1)..=max_point.row {
2691 let line_indent = self.line_indent_for_buffer_row(MultiBufferRow(row));
2692 if !line_indent.is_line_blank()
2693 && line_indent.raw_len() <= start_line_indent.raw_len()
2694 {
2695 let prev_row = row - 1;
2696 end = Some(Point::new(
2697 prev_row,
2698 self.buffer_snapshot().line_len(MultiBufferRow(prev_row)),
2699 ));
2700 break;
2701 }
2702 }
2703
2704 let mut row_before_line_breaks = end.unwrap_or(max_point);
2705 while row_before_line_breaks.row > start.row
2706 && self
2707 .buffer_snapshot()
2708 .is_line_blank(MultiBufferRow(row_before_line_breaks.row))
2709 {
2710 row_before_line_breaks.row -= 1;
2711 }
2712
2713 row_before_line_breaks = Point::new(
2714 row_before_line_breaks.row,
2715 self.buffer_snapshot()
2716 .line_len(MultiBufferRow(row_before_line_breaks.row)),
2717 );
2718
2719 Some(Crease::Inline {
2720 range: start..row_before_line_breaks,
2721 placeholder: self.fold_placeholder.clone(),
2722 render_toggle: None,
2723 render_trailer: None,
2724 metadata: None,
2725 })
2726 } else {
2727 None
2728 }
2729 }
2730
2731 #[cfg(any(test, feature = "test-support"))]
2732 #[instrument(skip_all)]
2733 pub fn text_highlight_ranges(
2734 &self,
2735 key: HighlightKey,
2736 ) -> Option<Arc<(HighlightStyle, Vec<Range<Anchor>>)>> {
2737 self.text_highlights.get(&key).cloned()
2738 }
2739
2740 #[cfg(any(test, feature = "test-support"))]
2741 #[instrument(skip_all)]
2742 pub fn all_text_highlight_ranges(
2743 &self,
2744 f: impl Fn(&HighlightKey) -> bool,
2745 ) -> Vec<(gpui::Hsla, Range<Point>)> {
2746 use itertools::Itertools;
2747
2748 self.text_highlights
2749 .iter()
2750 .filter(|(key, _)| f(key))
2751 .map(|(_, value)| value.clone())
2752 .flat_map(|ranges| {
2753 ranges
2754 .1
2755 .iter()
2756 .flat_map(|range| {
2757 Some((ranges.0.color?, range.to_point(self.buffer_snapshot())))
2758 })
2759 .collect::<Vec<_>>()
2760 })
2761 .sorted_by_key(|(_, range)| range.start)
2762 .collect()
2763 }
2764
2765 #[allow(unused)]
2766 #[cfg(any(test, feature = "test-support"))]
2767 pub(crate) fn inlay_highlights(
2768 &self,
2769 key: HighlightKey,
2770 ) -> Option<&TreeMap<InlayId, (HighlightStyle, InlayHighlight)>> {
2771 self.inlay_highlights.get(&key)
2772 }
2773
2774 pub fn buffer_header_height(&self) -> u32 {
2775 self.block_snapshot.buffer_header_height
2776 }
2777
2778 pub fn excerpt_header_height(&self) -> u32 {
2779 self.block_snapshot.excerpt_header_height
2780 }
2781
2782 /// Given a `DisplayPoint`, returns another `DisplayPoint` corresponding to
2783 /// the start of the buffer row that is a given number of buffer rows away
2784 /// from the provided point.
2785 ///
2786 /// This moves by buffer rows instead of display rows, a distinction that is
2787 /// important when soft wrapping is enabled.
2788 #[instrument(skip_all)]
2789 pub fn start_of_relative_buffer_row(&self, point: DisplayPoint, times: isize) -> DisplayPoint {
2790 let start = self.display_point_to_fold_point(point, Bias::Left);
2791 let target = start.row() as isize + times;
2792 let new_row = (target.max(0) as u32).min(self.fold_snapshot().max_point().row());
2793
2794 self.clip_point(
2795 self.fold_point_to_display_point(
2796 self.fold_snapshot()
2797 .clip_point(FoldPoint::new(new_row, 0), Bias::Right),
2798 ),
2799 Bias::Right,
2800 )
2801 }
2802}
2803
2804impl std::ops::Deref for DisplaySnapshot {
2805 type Target = BlockSnapshot;
2806
2807 fn deref(&self) -> &Self::Target {
2808 &self.block_snapshot
2809 }
2810}
2811
2812/// A zero-indexed point in a text buffer consisting of a row and column adjusted for inserted blocks.
2813#[derive(Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq)]
2814pub struct DisplayPoint(BlockPoint);
2815
2816impl Debug for DisplayPoint {
2817 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2818 f.write_fmt(format_args!(
2819 "DisplayPoint({}, {})",
2820 self.row().0,
2821 self.column()
2822 ))
2823 }
2824}
2825
2826impl Add for DisplayPoint {
2827 type Output = Self;
2828
2829 fn add(self, other: Self) -> Self::Output {
2830 DisplayPoint(BlockPoint(self.0.0 + other.0.0))
2831 }
2832}
2833
2834impl Sub for DisplayPoint {
2835 type Output = Self;
2836
2837 fn sub(self, other: Self) -> Self::Output {
2838 DisplayPoint(BlockPoint(self.0.0 - other.0.0))
2839 }
2840}
2841
2842#[derive(Debug, Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq, Deserialize, Hash)]
2843#[serde(transparent)]
2844pub struct DisplayRow(pub u32);
2845
2846impl DisplayRow {
2847 pub(crate) fn as_display_point(&self) -> DisplayPoint {
2848 DisplayPoint::new(*self, 0)
2849 }
2850}
2851
2852impl Add<DisplayRow> for DisplayRow {
2853 type Output = Self;
2854
2855 fn add(self, other: Self) -> Self::Output {
2856 DisplayRow(self.0 + other.0)
2857 }
2858}
2859
2860impl Add<u32> for DisplayRow {
2861 type Output = Self;
2862
2863 fn add(self, other: u32) -> Self::Output {
2864 DisplayRow(self.0 + other)
2865 }
2866}
2867
2868impl Sub<DisplayRow> for DisplayRow {
2869 type Output = Self;
2870
2871 fn sub(self, other: Self) -> Self::Output {
2872 DisplayRow(self.0 - other.0)
2873 }
2874}
2875
2876impl Sub<u32> for DisplayRow {
2877 type Output = Self;
2878
2879 fn sub(self, other: u32) -> Self::Output {
2880 DisplayRow(self.0 - other)
2881 }
2882}
2883
2884impl DisplayPoint {
2885 pub fn new(row: DisplayRow, column: u32) -> Self {
2886 Self(BlockPoint(Point::new(row.0, column)))
2887 }
2888
2889 pub fn zero() -> Self {
2890 Self::new(DisplayRow(0), 0)
2891 }
2892
2893 pub fn is_zero(&self) -> bool {
2894 self.0.is_zero()
2895 }
2896
2897 pub fn row(self) -> DisplayRow {
2898 DisplayRow(self.0.row)
2899 }
2900
2901 pub fn column(self) -> u32 {
2902 self.0.column
2903 }
2904
2905 pub fn row_mut(&mut self) -> &mut u32 {
2906 &mut self.0.row
2907 }
2908
2909 pub fn column_mut(&mut self) -> &mut u32 {
2910 &mut self.0.column
2911 }
2912
2913 pub fn to_point(self, map: &DisplaySnapshot) -> Point {
2914 map.display_point_to_point(self, Bias::Left)
2915 }
2916
2917 pub fn to_offset(self, map: &DisplaySnapshot, bias: Bias) -> MultiBufferOffset {
2918 let wrap_point = map.block_snapshot.to_wrap_point(self.0, bias);
2919 let tab_point = map.wrap_snapshot().to_tab_point(wrap_point);
2920 let fold_point = map
2921 .tab_snapshot()
2922 .tab_point_to_fold_point(tab_point, bias)
2923 .0;
2924 let inlay_point = fold_point.to_inlay_point(map.fold_snapshot());
2925 map.inlay_snapshot()
2926 .to_buffer_offset(map.inlay_snapshot().to_offset(inlay_point))
2927 }
2928}
2929
2930impl ToDisplayPoint for MultiBufferOffset {
2931 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
2932 map.point_to_display_point(self.to_point(map.buffer_snapshot()), Bias::Left)
2933 }
2934}
2935
2936impl ToDisplayPoint for MultiBufferOffsetUtf16 {
2937 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
2938 self.to_offset(map.buffer_snapshot()).to_display_point(map)
2939 }
2940}
2941
2942impl ToDisplayPoint for Point {
2943 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
2944 map.point_to_display_point(*self, Bias::Left)
2945 }
2946}
2947
2948impl ToDisplayPoint for Anchor {
2949 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
2950 self.to_point(map.buffer_snapshot()).to_display_point(map)
2951 }
2952}
2953
2954#[cfg(test)]
2955pub mod tests {
2956 use super::*;
2957 use crate::{
2958 movement,
2959 test::{marked_display_snapshot, test_font},
2960 };
2961 use Bias::*;
2962 use block_map::BlockPlacement;
2963 use gpui::{
2964 App, AppContext as _, BorrowAppContext, Element, Hsla, Rgba, div, font, observe, px,
2965 };
2966 use language::{
2967 Buffer, Diagnostic, DiagnosticEntry, DiagnosticSet, Language, LanguageConfig,
2968 LanguageMatcher,
2969 };
2970 use lsp::LanguageServerId;
2971
2972 use rand::{Rng, prelude::*};
2973 use settings::{SettingsContent, SettingsStore};
2974 use smol::stream::StreamExt;
2975 use std::{env, sync::Arc};
2976 use text::PointUtf16;
2977 use theme::{LoadThemes, SyntaxTheme};
2978 use unindent::Unindent as _;
2979 use util::test::{marked_text_ranges, sample_text};
2980
2981 #[gpui::test(iterations = 100)]
2982 async fn test_random_display_map(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
2983 cx.background_executor.set_block_on_ticks(0..=50);
2984 let operations = env::var("OPERATIONS")
2985 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2986 .unwrap_or(10);
2987
2988 let mut tab_size = rng.random_range(1..=4);
2989 let buffer_start_excerpt_header_height = rng.random_range(1..=5);
2990 let excerpt_header_height = rng.random_range(1..=5);
2991 let font_size = px(14.0);
2992 let max_wrap_width = 300.0;
2993 let mut wrap_width = if rng.random_bool(0.1) {
2994 None
2995 } else {
2996 Some(px(rng.random_range(0.0..=max_wrap_width)))
2997 };
2998
2999 log::info!("tab size: {}", tab_size);
3000 log::info!("wrap width: {:?}", wrap_width);
3001
3002 cx.update(|cx| {
3003 init_test(cx, |s| {
3004 s.project.all_languages.defaults.tab_size = NonZeroU32::new(tab_size)
3005 });
3006 });
3007
3008 let buffer = cx.update(|cx| {
3009 if rng.random() {
3010 let len = rng.random_range(0..10);
3011 let text = util::RandomCharIter::new(&mut rng)
3012 .take(len)
3013 .collect::<String>();
3014 MultiBuffer::build_simple(&text, cx)
3015 } else {
3016 MultiBuffer::build_random(&mut rng, cx)
3017 }
3018 });
3019
3020 let font = test_font();
3021 let map = cx.new(|cx| {
3022 DisplayMap::new(
3023 buffer.clone(),
3024 font,
3025 font_size,
3026 wrap_width,
3027 buffer_start_excerpt_header_height,
3028 excerpt_header_height,
3029 FoldPlaceholder::test(),
3030 DiagnosticSeverity::Warning,
3031 cx,
3032 )
3033 });
3034 let mut notifications = observe(&map, cx);
3035 let mut fold_count = 0;
3036 let mut blocks = Vec::new();
3037
3038 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
3039 log::info!("buffer text: {:?}", snapshot.buffer_snapshot().text());
3040 log::info!("fold text: {:?}", snapshot.fold_snapshot().text());
3041 log::info!("tab text: {:?}", snapshot.tab_snapshot().text());
3042 log::info!("wrap text: {:?}", snapshot.wrap_snapshot().text());
3043 log::info!("block text: {:?}", snapshot.block_snapshot.text());
3044 log::info!("display text: {:?}", snapshot.text());
3045
3046 for _i in 0..operations {
3047 match rng.random_range(0..100) {
3048 0..=19 => {
3049 wrap_width = if rng.random_bool(0.2) {
3050 None
3051 } else {
3052 Some(px(rng.random_range(0.0..=max_wrap_width)))
3053 };
3054 log::info!("setting wrap width to {:?}", wrap_width);
3055 map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
3056 }
3057 20..=29 => {
3058 let mut tab_sizes = vec![1, 2, 3, 4];
3059 tab_sizes.remove((tab_size - 1) as usize);
3060 tab_size = *tab_sizes.choose(&mut rng).unwrap();
3061 log::info!("setting tab size to {:?}", tab_size);
3062 cx.update(|cx| {
3063 cx.update_global::<SettingsStore, _>(|store, cx| {
3064 store.update_user_settings(cx, |s| {
3065 s.project.all_languages.defaults.tab_size =
3066 NonZeroU32::new(tab_size);
3067 });
3068 });
3069 });
3070 }
3071 30..=44 => {
3072 map.update(cx, |map, cx| {
3073 if rng.random() || blocks.is_empty() {
3074 let snapshot = map.snapshot(cx);
3075 let buffer = snapshot.buffer_snapshot();
3076 let block_properties = (0..rng.random_range(1..=1))
3077 .map(|_| {
3078 let position = buffer.anchor_after(buffer.clip_offset(
3079 rng.random_range(MultiBufferOffset(0)..=buffer.len()),
3080 Bias::Left,
3081 ));
3082
3083 let placement = if rng.random() {
3084 BlockPlacement::Above(position)
3085 } else {
3086 BlockPlacement::Below(position)
3087 };
3088 let height = rng.random_range(1..5);
3089 log::info!(
3090 "inserting block {:?} with height {}",
3091 placement.as_ref().map(|p| p.to_point(&buffer)),
3092 height
3093 );
3094 let priority = rng.random_range(1..100);
3095 BlockProperties {
3096 placement,
3097 style: BlockStyle::Fixed,
3098 height: Some(height),
3099 render: Arc::new(|_| div().into_any()),
3100 priority,
3101 }
3102 })
3103 .collect::<Vec<_>>();
3104 blocks.extend(map.insert_blocks(block_properties, cx));
3105 } else {
3106 blocks.shuffle(&mut rng);
3107 let remove_count = rng.random_range(1..=4.min(blocks.len()));
3108 let block_ids_to_remove = (0..remove_count)
3109 .map(|_| blocks.remove(rng.random_range(0..blocks.len())))
3110 .collect();
3111 log::info!("removing block ids {:?}", block_ids_to_remove);
3112 map.remove_blocks(block_ids_to_remove, cx);
3113 }
3114 });
3115 }
3116 45..=79 => {
3117 let mut ranges = Vec::new();
3118 for _ in 0..rng.random_range(1..=3) {
3119 buffer.read_with(cx, |buffer, cx| {
3120 let buffer = buffer.read(cx);
3121 let end = buffer.clip_offset(
3122 rng.random_range(MultiBufferOffset(0)..=buffer.len()),
3123 Right,
3124 );
3125 let start = buffer
3126 .clip_offset(rng.random_range(MultiBufferOffset(0)..=end), Left);
3127 ranges.push(start..end);
3128 });
3129 }
3130
3131 if rng.random() && fold_count > 0 {
3132 log::info!("unfolding ranges: {:?}", ranges);
3133 map.update(cx, |map, cx| {
3134 map.unfold_intersecting(ranges, true, cx);
3135 });
3136 } else {
3137 log::info!("folding ranges: {:?}", ranges);
3138 map.update(cx, |map, cx| {
3139 map.fold(
3140 ranges
3141 .into_iter()
3142 .map(|range| Crease::simple(range, FoldPlaceholder::test()))
3143 .collect(),
3144 cx,
3145 );
3146 });
3147 }
3148 }
3149 _ => {
3150 buffer.update(cx, |buffer, cx| buffer.randomly_mutate(&mut rng, 5, cx));
3151 }
3152 }
3153
3154 if map.read_with(cx, |map, cx| map.is_rewrapping(cx)) {
3155 notifications.next().await.unwrap();
3156 }
3157
3158 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
3159 fold_count = snapshot.fold_count();
3160 log::info!("buffer text: {:?}", snapshot.buffer_snapshot().text());
3161 log::info!("fold text: {:?}", snapshot.fold_snapshot().text());
3162 log::info!("tab text: {:?}", snapshot.tab_snapshot().text());
3163 log::info!("wrap text: {:?}", snapshot.wrap_snapshot().text());
3164 log::info!("block text: {:?}", snapshot.block_snapshot.text());
3165 log::info!("display text: {:?}", snapshot.text());
3166
3167 // Line boundaries
3168 let buffer = snapshot.buffer_snapshot();
3169 for _ in 0..5 {
3170 let row = rng.random_range(0..=buffer.max_point().row);
3171 let column = rng.random_range(0..=buffer.line_len(MultiBufferRow(row)));
3172 let point = buffer.clip_point(Point::new(row, column), Left);
3173
3174 let (prev_buffer_bound, prev_display_bound) = snapshot.prev_line_boundary(point);
3175 let (next_buffer_bound, next_display_bound) = snapshot.next_line_boundary(point);
3176
3177 assert!(prev_buffer_bound <= point);
3178 assert!(next_buffer_bound >= point);
3179 assert_eq!(prev_buffer_bound.column, 0);
3180 assert_eq!(prev_display_bound.column(), 0);
3181 if next_buffer_bound < buffer.max_point() {
3182 assert_eq!(buffer.chars_at(next_buffer_bound).next(), Some('\n'));
3183 }
3184
3185 assert_eq!(
3186 prev_display_bound,
3187 prev_buffer_bound.to_display_point(&snapshot),
3188 "row boundary before {:?}. reported buffer row boundary: {:?}",
3189 point,
3190 prev_buffer_bound
3191 );
3192 assert_eq!(
3193 next_display_bound,
3194 next_buffer_bound.to_display_point(&snapshot),
3195 "display row boundary after {:?}. reported buffer row boundary: {:?}",
3196 point,
3197 next_buffer_bound
3198 );
3199 assert_eq!(
3200 prev_buffer_bound,
3201 prev_display_bound.to_point(&snapshot),
3202 "row boundary before {:?}. reported display row boundary: {:?}",
3203 point,
3204 prev_display_bound
3205 );
3206 assert_eq!(
3207 next_buffer_bound,
3208 next_display_bound.to_point(&snapshot),
3209 "row boundary after {:?}. reported display row boundary: {:?}",
3210 point,
3211 next_display_bound
3212 );
3213 }
3214
3215 // Movement
3216 let min_point = snapshot.clip_point(DisplayPoint::new(DisplayRow(0), 0), Left);
3217 let max_point = snapshot.clip_point(snapshot.max_point(), Right);
3218 for _ in 0..5 {
3219 let row = rng.random_range(0..=snapshot.max_point().row().0);
3220 let column = rng.random_range(0..=snapshot.line_len(DisplayRow(row)));
3221 let point = snapshot.clip_point(DisplayPoint::new(DisplayRow(row), column), Left);
3222
3223 log::info!("Moving from point {:?}", point);
3224
3225 let moved_right = movement::right(&snapshot, point);
3226 log::info!("Right {:?}", moved_right);
3227 if point < max_point {
3228 assert!(moved_right > point);
3229 if point.column() == snapshot.line_len(point.row())
3230 || snapshot.soft_wrap_indent(point.row()).is_some()
3231 && point.column() == snapshot.line_len(point.row()) - 1
3232 {
3233 assert!(moved_right.row() > point.row());
3234 }
3235 } else {
3236 assert_eq!(moved_right, point);
3237 }
3238
3239 let moved_left = movement::left(&snapshot, point);
3240 log::info!("Left {:?}", moved_left);
3241 if point > min_point {
3242 assert!(moved_left < point);
3243 if point.column() == 0 {
3244 assert!(moved_left.row() < point.row());
3245 }
3246 } else {
3247 assert_eq!(moved_left, point);
3248 }
3249 }
3250 }
3251 }
3252
3253 #[gpui::test(retries = 5)]
3254 async fn test_soft_wraps(cx: &mut gpui::TestAppContext) {
3255 cx.background_executor
3256 .set_block_on_ticks(usize::MAX..=usize::MAX);
3257 cx.update(|cx| {
3258 init_test(cx, |_| {});
3259 });
3260
3261 let mut cx = crate::test::editor_test_context::EditorTestContext::new(cx).await;
3262 let editor = cx.editor.clone();
3263 let window = cx.window;
3264
3265 _ = cx.update_window(window, |_, window, cx| {
3266 let text_layout_details =
3267 editor.update(cx, |editor, cx| editor.text_layout_details(window, cx));
3268
3269 let font_size = px(12.0);
3270 let wrap_width = Some(px(96.));
3271
3272 let text = "one two three four five\nsix seven eight";
3273 let buffer = MultiBuffer::build_simple(text, cx);
3274 let map = cx.new(|cx| {
3275 DisplayMap::new(
3276 buffer.clone(),
3277 font("Helvetica"),
3278 font_size,
3279 wrap_width,
3280 1,
3281 1,
3282 FoldPlaceholder::test(),
3283 DiagnosticSeverity::Warning,
3284 cx,
3285 )
3286 });
3287
3288 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
3289 assert_eq!(
3290 snapshot.text_chunks(DisplayRow(0)).collect::<String>(),
3291 "one two \nthree four \nfive\nsix seven \neight"
3292 );
3293 assert_eq!(
3294 snapshot.clip_point(DisplayPoint::new(DisplayRow(0), 8), Bias::Left),
3295 DisplayPoint::new(DisplayRow(0), 7)
3296 );
3297 assert_eq!(
3298 snapshot.clip_point(DisplayPoint::new(DisplayRow(0), 8), Bias::Right),
3299 DisplayPoint::new(DisplayRow(1), 0)
3300 );
3301 assert_eq!(
3302 movement::right(&snapshot, DisplayPoint::new(DisplayRow(0), 7)),
3303 DisplayPoint::new(DisplayRow(1), 0)
3304 );
3305 assert_eq!(
3306 movement::left(&snapshot, DisplayPoint::new(DisplayRow(1), 0)),
3307 DisplayPoint::new(DisplayRow(0), 7)
3308 );
3309
3310 let x = snapshot
3311 .x_for_display_point(DisplayPoint::new(DisplayRow(1), 10), &text_layout_details);
3312 assert_eq!(
3313 movement::up(
3314 &snapshot,
3315 DisplayPoint::new(DisplayRow(1), 10),
3316 language::SelectionGoal::None,
3317 false,
3318 &text_layout_details,
3319 ),
3320 (
3321 DisplayPoint::new(DisplayRow(0), 7),
3322 language::SelectionGoal::HorizontalPosition(f64::from(x))
3323 )
3324 );
3325 assert_eq!(
3326 movement::down(
3327 &snapshot,
3328 DisplayPoint::new(DisplayRow(0), 7),
3329 language::SelectionGoal::HorizontalPosition(f64::from(x)),
3330 false,
3331 &text_layout_details
3332 ),
3333 (
3334 DisplayPoint::new(DisplayRow(1), 10),
3335 language::SelectionGoal::HorizontalPosition(f64::from(x))
3336 )
3337 );
3338 assert_eq!(
3339 movement::down(
3340 &snapshot,
3341 DisplayPoint::new(DisplayRow(1), 10),
3342 language::SelectionGoal::HorizontalPosition(f64::from(x)),
3343 false,
3344 &text_layout_details
3345 ),
3346 (
3347 DisplayPoint::new(DisplayRow(2), 4),
3348 language::SelectionGoal::HorizontalPosition(f64::from(x))
3349 )
3350 );
3351
3352 let ix = MultiBufferOffset(snapshot.buffer_snapshot().text().find("seven").unwrap());
3353 buffer.update(cx, |buffer, cx| {
3354 buffer.edit([(ix..ix, "and ")], None, cx);
3355 });
3356
3357 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
3358 assert_eq!(
3359 snapshot.text_chunks(DisplayRow(1)).collect::<String>(),
3360 "three four \nfive\nsix and \nseven eight"
3361 );
3362
3363 // Re-wrap on font size changes
3364 map.update(cx, |map, cx| {
3365 map.set_font(font("Helvetica"), font_size + Pixels::from(3.), cx)
3366 });
3367
3368 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
3369 assert_eq!(
3370 snapshot.text_chunks(DisplayRow(1)).collect::<String>(),
3371 "three \nfour five\nsix and \nseven \neight"
3372 )
3373 });
3374 }
3375
3376 #[gpui::test]
3377 fn test_text_chunks(cx: &mut gpui::App) {
3378 init_test(cx, |_| {});
3379
3380 let text = sample_text(6, 6, 'a');
3381 let buffer = MultiBuffer::build_simple(&text, cx);
3382
3383 let font_size = px(14.0);
3384 let map = cx.new(|cx| {
3385 DisplayMap::new(
3386 buffer.clone(),
3387 font("Helvetica"),
3388 font_size,
3389 None,
3390 1,
3391 1,
3392 FoldPlaceholder::test(),
3393 DiagnosticSeverity::Warning,
3394 cx,
3395 )
3396 });
3397
3398 buffer.update(cx, |buffer, cx| {
3399 buffer.edit(
3400 vec![
3401 (
3402 MultiBufferPoint::new(1, 0)..MultiBufferPoint::new(1, 0),
3403 "\t",
3404 ),
3405 (
3406 MultiBufferPoint::new(1, 1)..MultiBufferPoint::new(1, 1),
3407 "\t",
3408 ),
3409 (
3410 MultiBufferPoint::new(2, 1)..MultiBufferPoint::new(2, 1),
3411 "\t",
3412 ),
3413 ],
3414 None,
3415 cx,
3416 )
3417 });
3418
3419 assert_eq!(
3420 map.update(cx, |map, cx| map.snapshot(cx))
3421 .text_chunks(DisplayRow(1))
3422 .collect::<String>()
3423 .lines()
3424 .next(),
3425 Some(" b bbbbb")
3426 );
3427 assert_eq!(
3428 map.update(cx, |map, cx| map.snapshot(cx))
3429 .text_chunks(DisplayRow(2))
3430 .collect::<String>()
3431 .lines()
3432 .next(),
3433 Some("c ccccc")
3434 );
3435 }
3436
3437 #[gpui::test]
3438 fn test_inlays_with_newlines_after_blocks(cx: &mut gpui::TestAppContext) {
3439 cx.update(|cx| init_test(cx, |_| {}));
3440
3441 let buffer = cx.new(|cx| Buffer::local("a", cx));
3442 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
3443 let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
3444
3445 let font_size = px(14.0);
3446 let map = cx.new(|cx| {
3447 DisplayMap::new(
3448 buffer.clone(),
3449 font("Helvetica"),
3450 font_size,
3451 None,
3452 1,
3453 1,
3454 FoldPlaceholder::test(),
3455 DiagnosticSeverity::Warning,
3456 cx,
3457 )
3458 });
3459
3460 map.update(cx, |map, cx| {
3461 map.insert_blocks(
3462 [BlockProperties {
3463 placement: BlockPlacement::Above(
3464 buffer_snapshot.anchor_before(Point::new(0, 0)),
3465 ),
3466 height: Some(2),
3467 style: BlockStyle::Sticky,
3468 render: Arc::new(|_| div().into_any()),
3469 priority: 0,
3470 }],
3471 cx,
3472 );
3473 });
3474 map.update(cx, |m, cx| assert_eq!(m.snapshot(cx).text(), "\n\na"));
3475
3476 map.update(cx, |map, cx| {
3477 map.splice_inlays(
3478 &[],
3479 vec![Inlay::edit_prediction(
3480 0,
3481 buffer_snapshot.anchor_after(MultiBufferOffset(0)),
3482 "\n",
3483 )],
3484 cx,
3485 );
3486 });
3487 map.update(cx, |m, cx| assert_eq!(m.snapshot(cx).text(), "\n\n\na"));
3488
3489 // Regression test: updating the display map does not crash when a
3490 // block is immediately followed by a multi-line inlay.
3491 buffer.update(cx, |buffer, cx| {
3492 buffer.edit(
3493 [(MultiBufferOffset(1)..MultiBufferOffset(1), "b")],
3494 None,
3495 cx,
3496 );
3497 });
3498 map.update(cx, |m, cx| assert_eq!(m.snapshot(cx).text(), "\n\n\nab"));
3499 }
3500
3501 #[gpui::test]
3502 async fn test_chunks(cx: &mut gpui::TestAppContext) {
3503 let text = r#"
3504 fn outer() {}
3505
3506 mod module {
3507 fn inner() {}
3508 }"#
3509 .unindent();
3510
3511 let theme =
3512 SyntaxTheme::new_test(vec![("mod.body", Hsla::red()), ("fn.name", Hsla::blue())]);
3513 let language = Arc::new(
3514 Language::new(
3515 LanguageConfig {
3516 name: "Test".into(),
3517 matcher: LanguageMatcher {
3518 path_suffixes: vec![".test".to_string()],
3519 ..Default::default()
3520 },
3521 ..Default::default()
3522 },
3523 Some(tree_sitter_rust::LANGUAGE.into()),
3524 )
3525 .with_highlights_query(
3526 r#"
3527 (mod_item name: (identifier) body: _ @mod.body)
3528 (function_item name: (identifier) @fn.name)
3529 "#,
3530 )
3531 .unwrap(),
3532 );
3533 language.set_theme(&theme);
3534
3535 cx.update(|cx| {
3536 init_test(cx, |s| {
3537 s.project.all_languages.defaults.tab_size = Some(2.try_into().unwrap())
3538 })
3539 });
3540
3541 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
3542 cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
3543 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
3544
3545 let font_size = px(14.0);
3546
3547 let map = cx.new(|cx| {
3548 DisplayMap::new(
3549 buffer,
3550 font("Helvetica"),
3551 font_size,
3552 None,
3553 1,
3554 1,
3555 FoldPlaceholder::test(),
3556 DiagnosticSeverity::Warning,
3557 cx,
3558 )
3559 });
3560 assert_eq!(
3561 cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(5), &map, &theme, cx)),
3562 vec![
3563 ("fn ".to_string(), None),
3564 ("outer".to_string(), Some(Hsla::blue())),
3565 ("() {}\n\nmod module ".to_string(), None),
3566 ("{\n fn ".to_string(), Some(Hsla::red())),
3567 ("inner".to_string(), Some(Hsla::blue())),
3568 ("() {}\n}".to_string(), Some(Hsla::red())),
3569 ]
3570 );
3571 assert_eq!(
3572 cx.update(|cx| syntax_chunks(DisplayRow(3)..DisplayRow(5), &map, &theme, cx)),
3573 vec![
3574 (" fn ".to_string(), Some(Hsla::red())),
3575 ("inner".to_string(), Some(Hsla::blue())),
3576 ("() {}\n}".to_string(), Some(Hsla::red())),
3577 ]
3578 );
3579
3580 map.update(cx, |map, cx| {
3581 map.fold(
3582 vec![Crease::simple(
3583 MultiBufferPoint::new(0, 6)..MultiBufferPoint::new(3, 2),
3584 FoldPlaceholder::test(),
3585 )],
3586 cx,
3587 )
3588 });
3589 assert_eq!(
3590 cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(2), &map, &theme, cx)),
3591 vec![
3592 ("fn ".to_string(), None),
3593 ("out".to_string(), Some(Hsla::blue())),
3594 ("⋯".to_string(), None),
3595 (" fn ".to_string(), Some(Hsla::red())),
3596 ("inner".to_string(), Some(Hsla::blue())),
3597 ("() {}\n}".to_string(), Some(Hsla::red())),
3598 ]
3599 );
3600 }
3601
3602 #[gpui::test]
3603 async fn test_chunks_with_syntax_highlighting_across_blocks(cx: &mut gpui::TestAppContext) {
3604 cx.background_executor
3605 .set_block_on_ticks(usize::MAX..=usize::MAX);
3606
3607 let text = r#"
3608 const A: &str = "
3609 one
3610 two
3611 three
3612 ";
3613 const B: &str = "four";
3614 "#
3615 .unindent();
3616
3617 let theme = SyntaxTheme::new_test(vec![
3618 ("string", Hsla::red()),
3619 ("punctuation", Hsla::blue()),
3620 ("keyword", Hsla::green()),
3621 ]);
3622 let language = Arc::new(
3623 Language::new(
3624 LanguageConfig {
3625 name: "Rust".into(),
3626 ..Default::default()
3627 },
3628 Some(tree_sitter_rust::LANGUAGE.into()),
3629 )
3630 .with_highlights_query(
3631 r#"
3632 (string_literal) @string
3633 "const" @keyword
3634 [":" ";"] @punctuation
3635 "#,
3636 )
3637 .unwrap(),
3638 );
3639 language.set_theme(&theme);
3640
3641 cx.update(|cx| init_test(cx, |_| {}));
3642
3643 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
3644 cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
3645 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
3646 let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
3647
3648 let map = cx.new(|cx| {
3649 DisplayMap::new(
3650 buffer,
3651 font("Courier"),
3652 px(16.0),
3653 None,
3654 1,
3655 1,
3656 FoldPlaceholder::test(),
3657 DiagnosticSeverity::Warning,
3658 cx,
3659 )
3660 });
3661
3662 // Insert two blocks in the middle of a multi-line string literal.
3663 // The second block has zero height.
3664 map.update(cx, |map, cx| {
3665 map.insert_blocks(
3666 [
3667 BlockProperties {
3668 placement: BlockPlacement::Below(
3669 buffer_snapshot.anchor_before(Point::new(1, 0)),
3670 ),
3671 height: Some(1),
3672 style: BlockStyle::Sticky,
3673 render: Arc::new(|_| div().into_any()),
3674 priority: 0,
3675 },
3676 BlockProperties {
3677 placement: BlockPlacement::Below(
3678 buffer_snapshot.anchor_before(Point::new(2, 0)),
3679 ),
3680 height: None,
3681 style: BlockStyle::Sticky,
3682 render: Arc::new(|_| div().into_any()),
3683 priority: 0,
3684 },
3685 ],
3686 cx,
3687 )
3688 });
3689
3690 pretty_assertions::assert_eq!(
3691 cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(7), &map, &theme, cx)),
3692 [
3693 ("const".into(), Some(Hsla::green())),
3694 (" A".into(), None),
3695 (":".into(), Some(Hsla::blue())),
3696 (" &str = ".into(), None),
3697 ("\"\n one\n".into(), Some(Hsla::red())),
3698 ("\n".into(), None),
3699 (" two\n three\n\"".into(), Some(Hsla::red())),
3700 (";".into(), Some(Hsla::blue())),
3701 ("\n".into(), None),
3702 ("const".into(), Some(Hsla::green())),
3703 (" B".into(), None),
3704 (":".into(), Some(Hsla::blue())),
3705 (" &str = ".into(), None),
3706 ("\"four\"".into(), Some(Hsla::red())),
3707 (";".into(), Some(Hsla::blue())),
3708 ("\n".into(), None),
3709 ]
3710 );
3711 }
3712
3713 #[gpui::test]
3714 async fn test_chunks_with_diagnostics_across_blocks(cx: &mut gpui::TestAppContext) {
3715 cx.background_executor
3716 .set_block_on_ticks(usize::MAX..=usize::MAX);
3717
3718 let text = r#"
3719 struct A {
3720 b: usize;
3721 }
3722 const c: usize = 1;
3723 "#
3724 .unindent();
3725
3726 cx.update(|cx| init_test(cx, |_| {}));
3727
3728 let buffer = cx.new(|cx| Buffer::local(text, cx));
3729
3730 buffer.update(cx, |buffer, cx| {
3731 buffer.update_diagnostics(
3732 LanguageServerId(0),
3733 DiagnosticSet::new(
3734 [DiagnosticEntry {
3735 range: PointUtf16::new(0, 0)..PointUtf16::new(2, 1),
3736 diagnostic: Diagnostic {
3737 severity: lsp::DiagnosticSeverity::ERROR,
3738 group_id: 1,
3739 message: "hi".into(),
3740 ..Default::default()
3741 },
3742 }],
3743 buffer,
3744 ),
3745 cx,
3746 )
3747 });
3748
3749 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
3750 let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
3751
3752 let map = cx.new(|cx| {
3753 DisplayMap::new(
3754 buffer,
3755 font("Courier"),
3756 px(16.0),
3757 None,
3758 1,
3759 1,
3760 FoldPlaceholder::test(),
3761 DiagnosticSeverity::Warning,
3762 cx,
3763 )
3764 });
3765
3766 let black = gpui::black().to_rgb();
3767 let red = gpui::red().to_rgb();
3768
3769 // Insert a block in the middle of a multi-line diagnostic.
3770 map.update(cx, |map, cx| {
3771 map.highlight_text(
3772 HighlightKey::Editor,
3773 vec![
3774 buffer_snapshot.anchor_before(Point::new(3, 9))
3775 ..buffer_snapshot.anchor_after(Point::new(3, 14)),
3776 buffer_snapshot.anchor_before(Point::new(3, 17))
3777 ..buffer_snapshot.anchor_after(Point::new(3, 18)),
3778 ],
3779 red.into(),
3780 false,
3781 cx,
3782 );
3783 map.insert_blocks(
3784 [BlockProperties {
3785 placement: BlockPlacement::Below(
3786 buffer_snapshot.anchor_before(Point::new(1, 0)),
3787 ),
3788 height: Some(1),
3789 style: BlockStyle::Sticky,
3790 render: Arc::new(|_| div().into_any()),
3791 priority: 0,
3792 }],
3793 cx,
3794 )
3795 });
3796
3797 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
3798 let mut chunks = Vec::<(String, Option<lsp::DiagnosticSeverity>, Rgba)>::new();
3799 for chunk in snapshot.chunks(DisplayRow(0)..DisplayRow(5), true, Default::default()) {
3800 let color = chunk
3801 .highlight_style
3802 .and_then(|style| style.color)
3803 .map_or(black, |color| color.to_rgb());
3804 if let Some((last_chunk, last_severity, last_color)) = chunks.last_mut()
3805 && *last_severity == chunk.diagnostic_severity
3806 && *last_color == color
3807 {
3808 last_chunk.push_str(chunk.text);
3809 continue;
3810 }
3811
3812 chunks.push((chunk.text.to_string(), chunk.diagnostic_severity, color));
3813 }
3814
3815 assert_eq!(
3816 chunks,
3817 [
3818 (
3819 "struct A {\n b: usize;\n".into(),
3820 Some(lsp::DiagnosticSeverity::ERROR),
3821 black
3822 ),
3823 ("\n".into(), None, black),
3824 ("}".into(), Some(lsp::DiagnosticSeverity::ERROR), black),
3825 ("\nconst c: ".into(), None, black),
3826 ("usize".into(), None, red),
3827 (" = ".into(), None, black),
3828 ("1".into(), None, red),
3829 (";\n".into(), None, black),
3830 ]
3831 );
3832 }
3833
3834 #[gpui::test]
3835 async fn test_point_translation_with_replace_blocks(cx: &mut gpui::TestAppContext) {
3836 cx.background_executor
3837 .set_block_on_ticks(usize::MAX..=usize::MAX);
3838
3839 cx.update(|cx| init_test(cx, |_| {}));
3840
3841 let buffer = cx.update(|cx| MultiBuffer::build_simple("abcde\nfghij\nklmno\npqrst", cx));
3842 let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
3843 let map = cx.new(|cx| {
3844 DisplayMap::new(
3845 buffer.clone(),
3846 font("Courier"),
3847 px(16.0),
3848 None,
3849 1,
3850 1,
3851 FoldPlaceholder::test(),
3852 DiagnosticSeverity::Warning,
3853 cx,
3854 )
3855 });
3856
3857 let snapshot = map.update(cx, |map, cx| {
3858 map.insert_blocks(
3859 [BlockProperties {
3860 placement: BlockPlacement::Replace(
3861 buffer_snapshot.anchor_before(Point::new(1, 2))
3862 ..=buffer_snapshot.anchor_after(Point::new(2, 3)),
3863 ),
3864 height: Some(4),
3865 style: BlockStyle::Fixed,
3866 render: Arc::new(|_| div().into_any()),
3867 priority: 0,
3868 }],
3869 cx,
3870 );
3871 map.snapshot(cx)
3872 });
3873
3874 assert_eq!(snapshot.text(), "abcde\n\n\n\n\npqrst");
3875
3876 let point_to_display_points = [
3877 (Point::new(1, 0), DisplayPoint::new(DisplayRow(1), 0)),
3878 (Point::new(2, 0), DisplayPoint::new(DisplayRow(1), 0)),
3879 (Point::new(3, 0), DisplayPoint::new(DisplayRow(5), 0)),
3880 ];
3881 for (buffer_point, display_point) in point_to_display_points {
3882 assert_eq!(
3883 snapshot.point_to_display_point(buffer_point, Bias::Left),
3884 display_point,
3885 "point_to_display_point({:?}, Bias::Left)",
3886 buffer_point
3887 );
3888 assert_eq!(
3889 snapshot.point_to_display_point(buffer_point, Bias::Right),
3890 display_point,
3891 "point_to_display_point({:?}, Bias::Right)",
3892 buffer_point
3893 );
3894 }
3895
3896 let display_points_to_points = [
3897 (
3898 DisplayPoint::new(DisplayRow(1), 0),
3899 Point::new(1, 0),
3900 Point::new(2, 5),
3901 ),
3902 (
3903 DisplayPoint::new(DisplayRow(2), 0),
3904 Point::new(1, 0),
3905 Point::new(2, 5),
3906 ),
3907 (
3908 DisplayPoint::new(DisplayRow(3), 0),
3909 Point::new(1, 0),
3910 Point::new(2, 5),
3911 ),
3912 (
3913 DisplayPoint::new(DisplayRow(4), 0),
3914 Point::new(1, 0),
3915 Point::new(2, 5),
3916 ),
3917 (
3918 DisplayPoint::new(DisplayRow(5), 0),
3919 Point::new(3, 0),
3920 Point::new(3, 0),
3921 ),
3922 ];
3923 for (display_point, left_buffer_point, right_buffer_point) in display_points_to_points {
3924 assert_eq!(
3925 snapshot.display_point_to_point(display_point, Bias::Left),
3926 left_buffer_point,
3927 "display_point_to_point({:?}, Bias::Left)",
3928 display_point
3929 );
3930 assert_eq!(
3931 snapshot.display_point_to_point(display_point, Bias::Right),
3932 right_buffer_point,
3933 "display_point_to_point({:?}, Bias::Right)",
3934 display_point
3935 );
3936 }
3937 }
3938
3939 #[gpui::test]
3940 async fn test_chunks_with_soft_wrapping(cx: &mut gpui::TestAppContext) {
3941 cx.background_executor
3942 .set_block_on_ticks(usize::MAX..=usize::MAX);
3943
3944 let text = r#"
3945 fn outer() {}
3946
3947 mod module {
3948 fn inner() {}
3949 }"#
3950 .unindent();
3951
3952 let theme =
3953 SyntaxTheme::new_test(vec![("mod.body", Hsla::red()), ("fn.name", Hsla::blue())]);
3954 let language = Arc::new(
3955 Language::new(
3956 LanguageConfig {
3957 name: "Test".into(),
3958 matcher: LanguageMatcher {
3959 path_suffixes: vec![".test".to_string()],
3960 ..Default::default()
3961 },
3962 ..Default::default()
3963 },
3964 Some(tree_sitter_rust::LANGUAGE.into()),
3965 )
3966 .with_highlights_query(
3967 r#"
3968 (mod_item name: (identifier) body: _ @mod.body)
3969 (function_item name: (identifier) @fn.name)
3970 "#,
3971 )
3972 .unwrap(),
3973 );
3974 language.set_theme(&theme);
3975
3976 cx.update(|cx| init_test(cx, |_| {}));
3977
3978 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
3979 cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
3980 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
3981
3982 let font_size = px(16.0);
3983
3984 let map = cx.new(|cx| {
3985 DisplayMap::new(
3986 buffer,
3987 font("Courier"),
3988 font_size,
3989 Some(px(40.0)),
3990 1,
3991 1,
3992 FoldPlaceholder::test(),
3993 DiagnosticSeverity::Warning,
3994 cx,
3995 )
3996 });
3997 assert_eq!(
3998 cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(5), &map, &theme, cx)),
3999 [
4000 ("fn \n".to_string(), None),
4001 ("oute".to_string(), Some(Hsla::blue())),
4002 ("\n".to_string(), None),
4003 ("r".to_string(), Some(Hsla::blue())),
4004 ("() \n{}\n\n".to_string(), None),
4005 ]
4006 );
4007 assert_eq!(
4008 cx.update(|cx| syntax_chunks(DisplayRow(3)..DisplayRow(5), &map, &theme, cx)),
4009 [("{}\n\n".to_string(), None)]
4010 );
4011
4012 map.update(cx, |map, cx| {
4013 map.fold(
4014 vec![Crease::simple(
4015 MultiBufferPoint::new(0, 6)..MultiBufferPoint::new(3, 2),
4016 FoldPlaceholder::test(),
4017 )],
4018 cx,
4019 )
4020 });
4021 assert_eq!(
4022 cx.update(|cx| syntax_chunks(DisplayRow(1)..DisplayRow(4), &map, &theme, cx)),
4023 [
4024 ("out".to_string(), Some(Hsla::blue())),
4025 ("⋯\n".to_string(), None),
4026 (" ".to_string(), Some(Hsla::red())),
4027 ("\n".to_string(), None),
4028 ("fn ".to_string(), Some(Hsla::red())),
4029 ("i".to_string(), Some(Hsla::blue())),
4030 ("\n".to_string(), None)
4031 ]
4032 );
4033 }
4034
4035 #[gpui::test]
4036 async fn test_chunks_with_text_highlights(cx: &mut gpui::TestAppContext) {
4037 cx.update(|cx| init_test(cx, |_| {}));
4038
4039 let theme =
4040 SyntaxTheme::new_test(vec![("operator", Hsla::red()), ("string", Hsla::green())]);
4041 let language = Arc::new(
4042 Language::new(
4043 LanguageConfig {
4044 name: "Test".into(),
4045 matcher: LanguageMatcher {
4046 path_suffixes: vec![".test".to_string()],
4047 ..Default::default()
4048 },
4049 ..Default::default()
4050 },
4051 Some(tree_sitter_rust::LANGUAGE.into()),
4052 )
4053 .with_highlights_query(
4054 r#"
4055 ":" @operator
4056 (string_literal) @string
4057 "#,
4058 )
4059 .unwrap(),
4060 );
4061 language.set_theme(&theme);
4062
4063 let (text, highlighted_ranges) = marked_text_ranges(r#"constˇ «a»«:» B = "c «d»""#, false);
4064
4065 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
4066 cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
4067
4068 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
4069 let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
4070
4071 let font_size = px(16.0);
4072 let map = cx.new(|cx| {
4073 DisplayMap::new(
4074 buffer,
4075 font("Courier"),
4076 font_size,
4077 None,
4078 1,
4079 1,
4080 FoldPlaceholder::test(),
4081 DiagnosticSeverity::Warning,
4082 cx,
4083 )
4084 });
4085
4086 let style = HighlightStyle {
4087 color: Some(Hsla::blue()),
4088 ..Default::default()
4089 };
4090
4091 map.update(cx, |map, cx| {
4092 map.highlight_text(
4093 HighlightKey::Editor,
4094 highlighted_ranges
4095 .into_iter()
4096 .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
4097 .map(|range| {
4098 buffer_snapshot.anchor_before(range.start)
4099 ..buffer_snapshot.anchor_before(range.end)
4100 })
4101 .collect(),
4102 style,
4103 false,
4104 cx,
4105 );
4106 });
4107
4108 assert_eq!(
4109 cx.update(|cx| chunks(DisplayRow(0)..DisplayRow(10), &map, &theme, cx)),
4110 [
4111 ("const ".to_string(), None, None),
4112 ("a".to_string(), None, Some(Hsla::blue())),
4113 (":".to_string(), Some(Hsla::red()), Some(Hsla::blue())),
4114 (" B = ".to_string(), None, None),
4115 ("\"c ".to_string(), Some(Hsla::green()), None),
4116 ("d".to_string(), Some(Hsla::green()), Some(Hsla::blue())),
4117 ("\"".to_string(), Some(Hsla::green()), None),
4118 ]
4119 );
4120 }
4121
4122 #[gpui::test]
4123 fn test_clip_point(cx: &mut gpui::App) {
4124 init_test(cx, |_| {});
4125
4126 fn assert(text: &str, shift_right: bool, bias: Bias, cx: &mut gpui::App) {
4127 let (unmarked_snapshot, mut markers) = marked_display_snapshot(text, cx);
4128
4129 match bias {
4130 Bias::Left => {
4131 if shift_right {
4132 *markers[1].column_mut() += 1;
4133 }
4134
4135 assert_eq!(unmarked_snapshot.clip_point(markers[1], bias), markers[0])
4136 }
4137 Bias::Right => {
4138 if shift_right {
4139 *markers[0].column_mut() += 1;
4140 }
4141
4142 assert_eq!(unmarked_snapshot.clip_point(markers[0], bias), markers[1])
4143 }
4144 };
4145 }
4146
4147 use Bias::{Left, Right};
4148 assert("ˇˇα", false, Left, cx);
4149 assert("ˇˇα", true, Left, cx);
4150 assert("ˇˇα", false, Right, cx);
4151 assert("ˇαˇ", true, Right, cx);
4152 assert("ˇˇ✋", false, Left, cx);
4153 assert("ˇˇ✋", true, Left, cx);
4154 assert("ˇˇ✋", false, Right, cx);
4155 assert("ˇ✋ˇ", true, Right, cx);
4156 assert("ˇˇ🍐", false, Left, cx);
4157 assert("ˇˇ🍐", true, Left, cx);
4158 assert("ˇˇ🍐", false, Right, cx);
4159 assert("ˇ🍐ˇ", true, Right, cx);
4160 assert("ˇˇ\t", false, Left, cx);
4161 assert("ˇˇ\t", true, Left, cx);
4162 assert("ˇˇ\t", false, Right, cx);
4163 assert("ˇ\tˇ", true, Right, cx);
4164 assert(" ˇˇ\t", false, Left, cx);
4165 assert(" ˇˇ\t", true, Left, cx);
4166 assert(" ˇˇ\t", false, Right, cx);
4167 assert(" ˇ\tˇ", true, Right, cx);
4168 assert(" ˇˇ\t", false, Left, cx);
4169 assert(" ˇˇ\t", false, Right, cx);
4170 }
4171
4172 #[gpui::test]
4173 fn test_clip_at_line_ends(cx: &mut gpui::App) {
4174 init_test(cx, |_| {});
4175
4176 fn assert(text: &str, cx: &mut gpui::App) {
4177 let (mut unmarked_snapshot, markers) = marked_display_snapshot(text, cx);
4178 unmarked_snapshot.clip_at_line_ends = true;
4179 assert_eq!(
4180 unmarked_snapshot.clip_point(markers[1], Bias::Left),
4181 markers[0]
4182 );
4183 }
4184
4185 assert("ˇˇ", cx);
4186 assert("ˇaˇ", cx);
4187 assert("aˇbˇ", cx);
4188 assert("aˇαˇ", cx);
4189 }
4190
4191 #[gpui::test]
4192 fn test_creases(cx: &mut gpui::App) {
4193 init_test(cx, |_| {});
4194
4195 let text = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll";
4196 let buffer = MultiBuffer::build_simple(text, cx);
4197 let font_size = px(14.0);
4198 cx.new(|cx| {
4199 let mut map = DisplayMap::new(
4200 buffer.clone(),
4201 font("Helvetica"),
4202 font_size,
4203 None,
4204 1,
4205 1,
4206 FoldPlaceholder::test(),
4207 DiagnosticSeverity::Warning,
4208 cx,
4209 );
4210 let snapshot = map.buffer.read(cx).snapshot(cx);
4211 let range =
4212 snapshot.anchor_before(Point::new(2, 0))..snapshot.anchor_after(Point::new(3, 3));
4213
4214 map.crease_map.insert(
4215 [Crease::inline(
4216 range,
4217 FoldPlaceholder::test(),
4218 |_row, _status, _toggle, _window, _cx| div(),
4219 |_row, _status, _window, _cx| div(),
4220 )],
4221 &map.buffer.read(cx).snapshot(cx),
4222 );
4223
4224 map
4225 });
4226 }
4227
4228 #[gpui::test]
4229 fn test_tabs_with_multibyte_chars(cx: &mut gpui::App) {
4230 init_test(cx, |_| {});
4231
4232 let text = "✅\t\tα\nβ\t\n🏀β\t\tγ";
4233 let buffer = MultiBuffer::build_simple(text, cx);
4234 let font_size = px(14.0);
4235
4236 let map = cx.new(|cx| {
4237 DisplayMap::new(
4238 buffer.clone(),
4239 font("Helvetica"),
4240 font_size,
4241 None,
4242 1,
4243 1,
4244 FoldPlaceholder::test(),
4245 DiagnosticSeverity::Warning,
4246 cx,
4247 )
4248 });
4249 let map = map.update(cx, |map, cx| map.snapshot(cx));
4250 assert_eq!(map.text(), "✅ α\nβ \n🏀β γ");
4251 assert_eq!(
4252 map.text_chunks(DisplayRow(0)).collect::<String>(),
4253 "✅ α\nβ \n🏀β γ"
4254 );
4255 assert_eq!(
4256 map.text_chunks(DisplayRow(1)).collect::<String>(),
4257 "β \n🏀β γ"
4258 );
4259 assert_eq!(
4260 map.text_chunks(DisplayRow(2)).collect::<String>(),
4261 "🏀β γ"
4262 );
4263
4264 let point = MultiBufferPoint::new(0, "✅\t\t".len() as u32);
4265 let display_point = DisplayPoint::new(DisplayRow(0), "✅ ".len() as u32);
4266 assert_eq!(point.to_display_point(&map), display_point);
4267 assert_eq!(display_point.to_point(&map), point);
4268
4269 let point = MultiBufferPoint::new(1, "β\t".len() as u32);
4270 let display_point = DisplayPoint::new(DisplayRow(1), "β ".len() as u32);
4271 assert_eq!(point.to_display_point(&map), display_point);
4272 assert_eq!(display_point.to_point(&map), point,);
4273
4274 let point = MultiBufferPoint::new(2, "🏀β\t\t".len() as u32);
4275 let display_point = DisplayPoint::new(DisplayRow(2), "🏀β ".len() as u32);
4276 assert_eq!(point.to_display_point(&map), display_point);
4277 assert_eq!(display_point.to_point(&map), point,);
4278
4279 // Display points inside of expanded tabs
4280 assert_eq!(
4281 DisplayPoint::new(DisplayRow(0), "✅ ".len() as u32).to_point(&map),
4282 MultiBufferPoint::new(0, "✅\t".len() as u32),
4283 );
4284 assert_eq!(
4285 DisplayPoint::new(DisplayRow(0), "✅ ".len() as u32).to_point(&map),
4286 MultiBufferPoint::new(0, "✅".len() as u32),
4287 );
4288
4289 // Clipping display points inside of multi-byte characters
4290 assert_eq!(
4291 map.clip_point(
4292 DisplayPoint::new(DisplayRow(0), "✅".len() as u32 - 1),
4293 Left
4294 ),
4295 DisplayPoint::new(DisplayRow(0), 0)
4296 );
4297 assert_eq!(
4298 map.clip_point(
4299 DisplayPoint::new(DisplayRow(0), "✅".len() as u32 - 1),
4300 Bias::Right
4301 ),
4302 DisplayPoint::new(DisplayRow(0), "✅".len() as u32)
4303 );
4304 }
4305
4306 #[gpui::test]
4307 fn test_max_point(cx: &mut gpui::App) {
4308 init_test(cx, |_| {});
4309
4310 let buffer = MultiBuffer::build_simple("aaa\n\t\tbbb", cx);
4311 let font_size = px(14.0);
4312 let map = cx.new(|cx| {
4313 DisplayMap::new(
4314 buffer.clone(),
4315 font("Helvetica"),
4316 font_size,
4317 None,
4318 1,
4319 1,
4320 FoldPlaceholder::test(),
4321 DiagnosticSeverity::Warning,
4322 cx,
4323 )
4324 });
4325 assert_eq!(
4326 map.update(cx, |map, cx| map.snapshot(cx)).max_point(),
4327 DisplayPoint::new(DisplayRow(1), 11)
4328 )
4329 }
4330
4331 fn syntax_chunks(
4332 rows: Range<DisplayRow>,
4333 map: &Entity<DisplayMap>,
4334 theme: &SyntaxTheme,
4335 cx: &mut App,
4336 ) -> Vec<(String, Option<Hsla>)> {
4337 chunks(rows, map, theme, cx)
4338 .into_iter()
4339 .map(|(text, color, _)| (text, color))
4340 .collect()
4341 }
4342
4343 fn chunks(
4344 rows: Range<DisplayRow>,
4345 map: &Entity<DisplayMap>,
4346 theme: &SyntaxTheme,
4347 cx: &mut App,
4348 ) -> Vec<(String, Option<Hsla>, Option<Hsla>)> {
4349 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
4350 let mut chunks: Vec<(String, Option<Hsla>, Option<Hsla>)> = Vec::new();
4351 for chunk in snapshot.chunks(rows, true, HighlightStyles::default()) {
4352 let syntax_color = chunk
4353 .syntax_highlight_id
4354 .and_then(|id| id.style(theme)?.color);
4355 let highlight_color = chunk.highlight_style.and_then(|style| style.color);
4356 if let Some((last_chunk, last_syntax_color, last_highlight_color)) = chunks.last_mut()
4357 && syntax_color == *last_syntax_color
4358 && highlight_color == *last_highlight_color
4359 {
4360 last_chunk.push_str(chunk.text);
4361 continue;
4362 }
4363 chunks.push((chunk.text.to_string(), syntax_color, highlight_color));
4364 }
4365 chunks
4366 }
4367
4368 fn init_test(cx: &mut App, f: impl Fn(&mut SettingsContent)) {
4369 let settings = SettingsStore::test(cx);
4370 cx.set_global(settings);
4371 crate::init(cx);
4372 theme::init(LoadThemes::JustBase, cx);
4373 cx.update_global::<SettingsStore, _>(|store, cx| {
4374 store.update_user_settings(cx, f);
4375 });
4376 }
4377}