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