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