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