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