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