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