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(&buffer_snapshot, None, cx);
1674 settings.semantic_tokens.use_tree_sitter()
1675 }
1676
1677 pub fn row_infos(&self, start_row: DisplayRow) -> impl Iterator<Item = RowInfo> + '_ {
1678 self.block_snapshot.row_infos(BlockRow(start_row.0))
1679 }
1680
1681 pub fn widest_line_number(&self) -> u32 {
1682 self.buffer_snapshot().widest_line_number()
1683 }
1684
1685 #[instrument(skip_all)]
1686 pub fn prev_line_boundary(&self, mut point: MultiBufferPoint) -> (Point, DisplayPoint) {
1687 loop {
1688 let mut inlay_point = self.inlay_snapshot().to_inlay_point(point);
1689 let mut fold_point = self.fold_snapshot().to_fold_point(inlay_point, Bias::Left);
1690 fold_point.0.column = 0;
1691 inlay_point = fold_point.to_inlay_point(self.fold_snapshot());
1692 point = self.inlay_snapshot().to_buffer_point(inlay_point);
1693
1694 let mut display_point = self.point_to_display_point(point, Bias::Left);
1695 *display_point.column_mut() = 0;
1696 let next_point = self.display_point_to_point(display_point, Bias::Left);
1697 if next_point == point {
1698 return (point, display_point);
1699 }
1700 point = next_point;
1701 }
1702 }
1703
1704 #[instrument(skip_all)]
1705 pub fn next_line_boundary(
1706 &self,
1707 mut point: MultiBufferPoint,
1708 ) -> (MultiBufferPoint, DisplayPoint) {
1709 let original_point = point;
1710 loop {
1711 let mut inlay_point = self.inlay_snapshot().to_inlay_point(point);
1712 let mut fold_point = self.fold_snapshot().to_fold_point(inlay_point, Bias::Right);
1713 fold_point.0.column = self.fold_snapshot().line_len(fold_point.row());
1714 inlay_point = fold_point.to_inlay_point(self.fold_snapshot());
1715 point = self.inlay_snapshot().to_buffer_point(inlay_point);
1716
1717 let mut display_point = self.point_to_display_point(point, Bias::Right);
1718 *display_point.column_mut() = self.line_len(display_point.row());
1719 let next_point = self.display_point_to_point(display_point, Bias::Right);
1720 if next_point == point || original_point == point || original_point == next_point {
1721 return (point, display_point);
1722 }
1723 point = next_point;
1724 }
1725 }
1726
1727 // used by line_mode selections and tries to match vim behavior
1728 pub fn expand_to_line(&self, range: Range<Point>) -> Range<Point> {
1729 let new_start = MultiBufferPoint::new(range.start.row, 0);
1730 let new_end = if range.end.column > 0 {
1731 MultiBufferPoint::new(
1732 range.end.row,
1733 self.buffer_snapshot()
1734 .line_len(MultiBufferRow(range.end.row)),
1735 )
1736 } else {
1737 range.end
1738 };
1739
1740 new_start..new_end
1741 }
1742
1743 #[instrument(skip_all)]
1744 pub fn point_to_display_point(&self, point: MultiBufferPoint, bias: Bias) -> DisplayPoint {
1745 let inlay_point = self.inlay_snapshot().to_inlay_point(point);
1746 let fold_point = self.fold_snapshot().to_fold_point(inlay_point, bias);
1747 let tab_point = self.tab_snapshot().fold_point_to_tab_point(fold_point);
1748 let wrap_point = self.wrap_snapshot().tab_point_to_wrap_point(tab_point);
1749 let block_point = self.block_snapshot.to_block_point(wrap_point);
1750 DisplayPoint(block_point)
1751 }
1752
1753 /// Converts a buffer offset range into one or more `DisplayPoint` ranges
1754 /// that cover only actual buffer text, excluding any inlay hint text that
1755 /// falls within the range.
1756 pub fn isomorphic_display_point_ranges_for_buffer_range(
1757 &self,
1758 range: Range<MultiBufferOffset>,
1759 ) -> SmallVec<[Range<DisplayPoint>; 1]> {
1760 let inlay_snapshot = self.inlay_snapshot();
1761 inlay_snapshot
1762 .buffer_offset_to_inlay_ranges(range)
1763 .map(|inlay_range| {
1764 let inlay_point_to_display_point = |inlay_point: InlayPoint, bias: Bias| {
1765 let fold_point = self.fold_snapshot().to_fold_point(inlay_point, bias);
1766 let tab_point = self.tab_snapshot().fold_point_to_tab_point(fold_point);
1767 let wrap_point = self.wrap_snapshot().tab_point_to_wrap_point(tab_point);
1768 let block_point = self.block_snapshot.to_block_point(wrap_point);
1769 DisplayPoint(block_point)
1770 };
1771
1772 let start = inlay_point_to_display_point(
1773 inlay_snapshot.to_point(inlay_range.start),
1774 Bias::Left,
1775 );
1776 let end = inlay_point_to_display_point(
1777 inlay_snapshot.to_point(inlay_range.end),
1778 Bias::Left,
1779 );
1780 start..end
1781 })
1782 .collect()
1783 }
1784
1785 pub fn display_point_to_point(&self, point: DisplayPoint, bias: Bias) -> Point {
1786 self.inlay_snapshot()
1787 .to_buffer_point(self.display_point_to_inlay_point(point, bias))
1788 }
1789
1790 pub fn display_point_to_inlay_offset(&self, point: DisplayPoint, bias: Bias) -> InlayOffset {
1791 self.inlay_snapshot()
1792 .to_offset(self.display_point_to_inlay_point(point, bias))
1793 }
1794
1795 pub fn anchor_to_inlay_offset(&self, anchor: Anchor) -> InlayOffset {
1796 self.inlay_snapshot()
1797 .to_inlay_offset(anchor.to_offset(self.buffer_snapshot()))
1798 }
1799
1800 pub fn display_point_to_anchor(&self, point: DisplayPoint, bias: Bias) -> Anchor {
1801 self.buffer_snapshot()
1802 .anchor_at(point.to_offset(self, bias), bias)
1803 }
1804
1805 #[instrument(skip_all)]
1806 fn display_point_to_inlay_point(&self, point: DisplayPoint, bias: Bias) -> InlayPoint {
1807 let block_point = point.0;
1808 let wrap_point = self.block_snapshot.to_wrap_point(block_point, bias);
1809 let tab_point = self.wrap_snapshot().to_tab_point(wrap_point);
1810 let fold_point = self
1811 .tab_snapshot()
1812 .tab_point_to_fold_point(tab_point, bias)
1813 .0;
1814 fold_point.to_inlay_point(self.fold_snapshot())
1815 }
1816
1817 #[instrument(skip_all)]
1818 pub fn display_point_to_fold_point(&self, point: DisplayPoint, bias: Bias) -> FoldPoint {
1819 let block_point = point.0;
1820 let wrap_point = self.block_snapshot.to_wrap_point(block_point, bias);
1821 let tab_point = self.wrap_snapshot().to_tab_point(wrap_point);
1822 self.tab_snapshot()
1823 .tab_point_to_fold_point(tab_point, bias)
1824 .0
1825 }
1826
1827 #[instrument(skip_all)]
1828 pub fn fold_point_to_display_point(&self, fold_point: FoldPoint) -> DisplayPoint {
1829 let tab_point = self.tab_snapshot().fold_point_to_tab_point(fold_point);
1830 let wrap_point = self.wrap_snapshot().tab_point_to_wrap_point(tab_point);
1831 let block_point = self.block_snapshot.to_block_point(wrap_point);
1832 DisplayPoint(block_point)
1833 }
1834
1835 pub fn max_point(&self) -> DisplayPoint {
1836 DisplayPoint(self.block_snapshot.max_point())
1837 }
1838
1839 /// Returns text chunks starting at the given display row until the end of the file
1840 #[instrument(skip_all)]
1841 pub fn text_chunks(&self, display_row: DisplayRow) -> impl Iterator<Item = &str> {
1842 self.block_snapshot
1843 .chunks(
1844 BlockRow(display_row.0)..BlockRow(self.max_point().row().next_row().0),
1845 false,
1846 self.masked,
1847 Highlights::default(),
1848 )
1849 .map(|h| h.text)
1850 }
1851
1852 /// Returns text chunks starting at the end of the given display row in reverse until the start of the file
1853 #[instrument(skip_all)]
1854 pub fn reverse_text_chunks(&self, display_row: DisplayRow) -> impl Iterator<Item = &str> {
1855 (0..=display_row.0).rev().flat_map(move |row| {
1856 self.block_snapshot
1857 .chunks(
1858 BlockRow(row)..BlockRow(row + 1),
1859 false,
1860 self.masked,
1861 Highlights::default(),
1862 )
1863 .map(|h| h.text)
1864 .collect::<Vec<_>>()
1865 .into_iter()
1866 .rev()
1867 })
1868 }
1869
1870 #[instrument(skip_all)]
1871 pub fn chunks(
1872 &self,
1873 display_rows: Range<DisplayRow>,
1874 language_aware: bool,
1875 highlight_styles: HighlightStyles,
1876 ) -> DisplayChunks<'_> {
1877 self.block_snapshot.chunks(
1878 BlockRow(display_rows.start.0)..BlockRow(display_rows.end.0),
1879 language_aware,
1880 self.masked,
1881 Highlights {
1882 text_highlights: Some(&self.text_highlights),
1883 inlay_highlights: Some(&self.inlay_highlights),
1884 semantic_token_highlights: Some(&self.semantic_token_highlights),
1885 styles: highlight_styles,
1886 },
1887 )
1888 }
1889
1890 #[instrument(skip_all)]
1891 pub fn highlighted_chunks<'a>(
1892 &'a self,
1893 display_rows: Range<DisplayRow>,
1894 language_aware: bool,
1895 editor_style: &'a EditorStyle,
1896 ) -> impl Iterator<Item = HighlightedChunk<'a>> {
1897 self.chunks(
1898 display_rows,
1899 language_aware,
1900 HighlightStyles {
1901 inlay_hint: Some(editor_style.inlay_hints_style),
1902 edit_prediction: Some(editor_style.edit_prediction_styles),
1903 },
1904 )
1905 .flat_map(|chunk| {
1906 let syntax_highlight_style = chunk
1907 .syntax_highlight_id
1908 .and_then(|id| id.style(&editor_style.syntax));
1909
1910 let chunk_highlight = chunk.highlight_style.map(|chunk_highlight| {
1911 HighlightStyle {
1912 // For color inlays, blend the color with the editor background
1913 // if the color has transparency (alpha < 1.0)
1914 color: chunk_highlight.color.map(|color| {
1915 if chunk.is_inlay && !color.is_opaque() {
1916 editor_style.background.blend(color)
1917 } else {
1918 color
1919 }
1920 }),
1921 underline: chunk_highlight
1922 .underline
1923 .filter(|_| editor_style.show_underlines),
1924 ..chunk_highlight
1925 }
1926 });
1927
1928 let diagnostic_highlight = chunk
1929 .diagnostic_severity
1930 .filter(|severity| {
1931 self.diagnostics_max_severity
1932 .into_lsp()
1933 .is_some_and(|max_severity| severity <= &max_severity)
1934 })
1935 .map(|severity| HighlightStyle {
1936 fade_out: chunk
1937 .is_unnecessary
1938 .then_some(editor_style.unnecessary_code_fade),
1939 underline: (chunk.underline
1940 && editor_style.show_underlines
1941 && !(chunk.is_unnecessary && severity > lsp::DiagnosticSeverity::WARNING))
1942 .then(|| {
1943 let diagnostic_color =
1944 super::diagnostic_style(severity, &editor_style.status);
1945 UnderlineStyle {
1946 color: Some(diagnostic_color),
1947 thickness: 1.0.into(),
1948 wavy: true,
1949 }
1950 }),
1951 ..Default::default()
1952 });
1953
1954 let style = [
1955 syntax_highlight_style,
1956 chunk_highlight,
1957 diagnostic_highlight,
1958 ]
1959 .into_iter()
1960 .flatten()
1961 .reduce(|acc, highlight| acc.highlight(highlight));
1962
1963 HighlightedChunk {
1964 text: chunk.text,
1965 style,
1966 is_tab: chunk.is_tab,
1967 is_inlay: chunk.is_inlay,
1968 replacement: chunk.renderer.map(ChunkReplacement::Renderer),
1969 }
1970 .highlight_invisibles(editor_style)
1971 })
1972 }
1973
1974 /// Returns combined highlight styles (tree-sitter syntax + semantic tokens)
1975 /// for a byte range within the specified buffer.
1976 /// Returned ranges are 0-based relative to `buffer_range.start`.
1977 pub(super) fn combined_highlights(
1978 &self,
1979 multibuffer_range: Range<MultiBufferOffset>,
1980 syntax_theme: &theme::SyntaxTheme,
1981 ) -> Vec<(Range<usize>, HighlightStyle)> {
1982 let multibuffer = self.buffer_snapshot();
1983
1984 let chunks = custom_highlights::CustomHighlightsChunks::new(
1985 multibuffer_range,
1986 true,
1987 None,
1988 Some(&self.semantic_token_highlights),
1989 multibuffer,
1990 );
1991
1992 let mut highlights = Vec::new();
1993 let mut offset = 0usize;
1994 for chunk in chunks {
1995 let chunk_len = chunk.text.len();
1996 if chunk_len == 0 {
1997 continue;
1998 }
1999
2000 let syntax_style = chunk
2001 .syntax_highlight_id
2002 .and_then(|id| id.style(syntax_theme));
2003 let overlay_style = chunk.highlight_style;
2004
2005 let combined = match (syntax_style, overlay_style) {
2006 (Some(syntax), Some(overlay)) => Some(syntax.highlight(overlay)),
2007 (some @ Some(_), None) | (None, some @ Some(_)) => some,
2008 (None, None) => None,
2009 };
2010
2011 if let Some(style) = combined {
2012 highlights.push((offset..offset + chunk_len, style));
2013 }
2014 offset += chunk_len;
2015 }
2016 highlights
2017 }
2018
2019 #[instrument(skip_all)]
2020 pub fn layout_row(
2021 &self,
2022 display_row: DisplayRow,
2023 TextLayoutDetails {
2024 text_system,
2025 editor_style,
2026 rem_size,
2027 scroll_anchor: _,
2028 visible_rows: _,
2029 vertical_scroll_margin: _,
2030 }: &TextLayoutDetails,
2031 ) -> Arc<LineLayout> {
2032 let mut runs = Vec::new();
2033 let mut line = String::new();
2034
2035 let range = display_row..display_row.next_row();
2036 for chunk in self.highlighted_chunks(range, false, editor_style) {
2037 line.push_str(chunk.text);
2038
2039 let text_style = if let Some(style) = chunk.style {
2040 Cow::Owned(editor_style.text.clone().highlight(style))
2041 } else {
2042 Cow::Borrowed(&editor_style.text)
2043 };
2044
2045 runs.push(text_style.to_run(chunk.text.len()))
2046 }
2047
2048 if line.ends_with('\n') {
2049 line.pop();
2050 if let Some(last_run) = runs.last_mut() {
2051 last_run.len -= 1;
2052 if last_run.len == 0 {
2053 runs.pop();
2054 }
2055 }
2056 }
2057
2058 let font_size = editor_style.text.font_size.to_pixels(*rem_size);
2059 text_system.layout_line(&line, font_size, &runs, None)
2060 }
2061
2062 pub fn x_for_display_point(
2063 &self,
2064 display_point: DisplayPoint,
2065 text_layout_details: &TextLayoutDetails,
2066 ) -> Pixels {
2067 let line = self.layout_row(display_point.row(), text_layout_details);
2068 line.x_for_index(display_point.column() as usize)
2069 }
2070
2071 pub fn display_column_for_x(
2072 &self,
2073 display_row: DisplayRow,
2074 x: Pixels,
2075 details: &TextLayoutDetails,
2076 ) -> u32 {
2077 let layout_line = self.layout_row(display_row, details);
2078 layout_line.closest_index_for_x(x) as u32
2079 }
2080
2081 #[instrument(skip_all)]
2082 pub fn grapheme_at(&self, mut point: DisplayPoint) -> Option<SharedString> {
2083 point = DisplayPoint(self.block_snapshot.clip_point(point.0, Bias::Left));
2084 let chars = self
2085 .text_chunks(point.row())
2086 .flat_map(str::chars)
2087 .skip_while({
2088 let mut column = 0;
2089 move |char| {
2090 let at_point = column >= point.column();
2091 column += char.len_utf8() as u32;
2092 !at_point
2093 }
2094 })
2095 .take_while({
2096 let mut prev = false;
2097 move |char| {
2098 let now = char.is_ascii();
2099 let end = char.is_ascii() && (char.is_ascii_whitespace() || prev);
2100 prev = now;
2101 !end
2102 }
2103 });
2104 chars.collect::<String>().graphemes(true).next().map(|s| {
2105 if let Some(invisible) = s.chars().next().filter(|&c| is_invisible(c)) {
2106 replacement(invisible).unwrap_or(s).to_owned().into()
2107 } else if s == "\n" {
2108 " ".into()
2109 } else {
2110 s.to_owned().into()
2111 }
2112 })
2113 }
2114
2115 pub fn buffer_chars_at(
2116 &self,
2117 mut offset: MultiBufferOffset,
2118 ) -> impl Iterator<Item = (char, MultiBufferOffset)> + '_ {
2119 self.buffer_snapshot().chars_at(offset).map(move |ch| {
2120 let ret = (ch, offset);
2121 offset += ch.len_utf8();
2122 ret
2123 })
2124 }
2125
2126 pub fn reverse_buffer_chars_at(
2127 &self,
2128 mut offset: MultiBufferOffset,
2129 ) -> impl Iterator<Item = (char, MultiBufferOffset)> + '_ {
2130 self.buffer_snapshot()
2131 .reversed_chars_at(offset)
2132 .map(move |ch| {
2133 offset -= ch.len_utf8();
2134 (ch, offset)
2135 })
2136 }
2137
2138 pub fn clip_point(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint {
2139 let mut clipped = self.block_snapshot.clip_point(point.0, bias);
2140 if self.clip_at_line_ends {
2141 clipped = self.clip_at_line_end(DisplayPoint(clipped)).0
2142 }
2143 DisplayPoint(clipped)
2144 }
2145
2146 pub fn clip_ignoring_line_ends(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint {
2147 DisplayPoint(self.block_snapshot.clip_point(point.0, bias))
2148 }
2149
2150 pub fn clip_at_line_end(&self, display_point: DisplayPoint) -> DisplayPoint {
2151 let mut point = self.display_point_to_point(display_point, Bias::Left);
2152
2153 if point.column != self.buffer_snapshot().line_len(MultiBufferRow(point.row)) {
2154 return display_point;
2155 }
2156 point.column = point.column.saturating_sub(1);
2157 point = self.buffer_snapshot().clip_point(point, Bias::Left);
2158 self.point_to_display_point(point, Bias::Left)
2159 }
2160
2161 pub fn folds_in_range<T>(&self, range: Range<T>) -> impl Iterator<Item = &Fold>
2162 where
2163 T: ToOffset,
2164 {
2165 self.fold_snapshot().folds_in_range(range)
2166 }
2167
2168 pub fn blocks_in_range(
2169 &self,
2170 rows: Range<DisplayRow>,
2171 ) -> impl Iterator<Item = (DisplayRow, &Block)> {
2172 self.block_snapshot
2173 .blocks_in_range(BlockRow(rows.start.0)..BlockRow(rows.end.0))
2174 .map(|(row, block)| (DisplayRow(row.0), block))
2175 }
2176
2177 pub fn sticky_header_excerpt(&self, row: f64) -> Option<StickyHeaderExcerpt<'_>> {
2178 self.block_snapshot.sticky_header_excerpt(row)
2179 }
2180
2181 pub fn block_for_id(&self, id: BlockId) -> Option<Block> {
2182 self.block_snapshot.block_for_id(id)
2183 }
2184
2185 pub fn intersects_fold<T: ToOffset>(&self, offset: T) -> bool {
2186 self.fold_snapshot().intersects_fold(offset)
2187 }
2188
2189 pub fn is_line_folded(&self, buffer_row: MultiBufferRow) -> bool {
2190 self.block_snapshot.is_line_replaced(buffer_row)
2191 || self.fold_snapshot().is_line_folded(buffer_row)
2192 }
2193
2194 pub fn is_block_line(&self, display_row: DisplayRow) -> bool {
2195 self.block_snapshot.is_block_line(BlockRow(display_row.0))
2196 }
2197
2198 pub fn is_folded_buffer_header(&self, display_row: DisplayRow) -> bool {
2199 self.block_snapshot
2200 .is_folded_buffer_header(BlockRow(display_row.0))
2201 }
2202
2203 pub fn soft_wrap_indent(&self, display_row: DisplayRow) -> Option<u32> {
2204 let wrap_row = self
2205 .block_snapshot
2206 .to_wrap_point(BlockPoint::new(BlockRow(display_row.0), 0), Bias::Left)
2207 .row();
2208 self.wrap_snapshot().soft_wrap_indent(wrap_row)
2209 }
2210
2211 pub fn text(&self) -> String {
2212 self.text_chunks(DisplayRow(0)).collect()
2213 }
2214
2215 pub fn line(&self, display_row: DisplayRow) -> String {
2216 let mut result = String::new();
2217 for chunk in self.text_chunks(display_row) {
2218 if let Some(ix) = chunk.find('\n') {
2219 result.push_str(&chunk[0..ix]);
2220 break;
2221 } else {
2222 result.push_str(chunk);
2223 }
2224 }
2225 result
2226 }
2227
2228 pub fn line_indent_for_buffer_row(&self, buffer_row: MultiBufferRow) -> LineIndent {
2229 self.buffer_snapshot().line_indent_for_row(buffer_row)
2230 }
2231
2232 pub fn line_len(&self, row: DisplayRow) -> u32 {
2233 self.block_snapshot.line_len(BlockRow(row.0))
2234 }
2235
2236 pub fn longest_row(&self) -> DisplayRow {
2237 DisplayRow(self.block_snapshot.longest_row().0)
2238 }
2239
2240 pub fn longest_row_in_range(&self, range: Range<DisplayRow>) -> DisplayRow {
2241 let block_range = BlockRow(range.start.0)..BlockRow(range.end.0);
2242 let longest_row = self.block_snapshot.longest_row_in_range(block_range);
2243 DisplayRow(longest_row.0)
2244 }
2245
2246 pub fn starts_indent(&self, buffer_row: MultiBufferRow) -> bool {
2247 let max_row = self.buffer_snapshot().max_row();
2248 if buffer_row >= max_row {
2249 return false;
2250 }
2251
2252 let line_indent = self.line_indent_for_buffer_row(buffer_row);
2253 if line_indent.is_line_blank() {
2254 return false;
2255 }
2256
2257 (buffer_row.0 + 1..=max_row.0)
2258 .find_map(|next_row| {
2259 let next_line_indent = self.line_indent_for_buffer_row(MultiBufferRow(next_row));
2260 if next_line_indent.raw_len() > line_indent.raw_len() {
2261 Some(true)
2262 } else if !next_line_indent.is_line_blank() {
2263 Some(false)
2264 } else {
2265 None
2266 }
2267 })
2268 .unwrap_or(false)
2269 }
2270
2271 /// Returns the indent length of `row` if it starts with a closing bracket.
2272 fn closing_bracket_indent_len(&self, row: u32) -> Option<u32> {
2273 let snapshot = self.buffer_snapshot();
2274 let indent_len = self
2275 .line_indent_for_buffer_row(MultiBufferRow(row))
2276 .raw_len();
2277 let content_start = Point::new(row, indent_len);
2278 let line_text: String = snapshot
2279 .chars_at(content_start)
2280 .take_while(|ch| *ch != '\n')
2281 .collect();
2282
2283 let scope = snapshot.language_scope_at(Point::new(row, 0))?;
2284 if scope
2285 .brackets()
2286 .any(|(pair, _)| line_text.starts_with(&pair.end))
2287 {
2288 return Some(indent_len);
2289 }
2290
2291 None
2292 }
2293
2294 #[instrument(skip_all)]
2295 pub fn crease_for_buffer_row(&self, buffer_row: MultiBufferRow) -> Option<Crease<Point>> {
2296 let start =
2297 MultiBufferPoint::new(buffer_row.0, self.buffer_snapshot().line_len(buffer_row));
2298 if let Some(crease) = self
2299 .crease_snapshot
2300 .query_row(buffer_row, self.buffer_snapshot())
2301 {
2302 match crease {
2303 Crease::Inline {
2304 range,
2305 placeholder,
2306 render_toggle,
2307 render_trailer,
2308 metadata,
2309 } => Some(Crease::Inline {
2310 range: range.to_point(self.buffer_snapshot()),
2311 placeholder: placeholder.clone(),
2312 render_toggle: render_toggle.clone(),
2313 render_trailer: render_trailer.clone(),
2314 metadata: metadata.clone(),
2315 }),
2316 Crease::Block {
2317 range,
2318 block_height,
2319 block_style,
2320 render_block,
2321 block_priority,
2322 render_toggle,
2323 } => Some(Crease::Block {
2324 range: range.to_point(self.buffer_snapshot()),
2325 block_height: *block_height,
2326 block_style: *block_style,
2327 render_block: render_block.clone(),
2328 block_priority: *block_priority,
2329 render_toggle: render_toggle.clone(),
2330 }),
2331 }
2332 } else if !self.use_lsp_folding_ranges
2333 && self.starts_indent(MultiBufferRow(start.row))
2334 && !self.is_line_folded(MultiBufferRow(start.row))
2335 {
2336 let start_line_indent = self.line_indent_for_buffer_row(buffer_row);
2337 let max_point = self.buffer_snapshot().max_point();
2338 let mut closing_row = None;
2339
2340 for row in (buffer_row.0 + 1)..=max_point.row {
2341 let line_indent = self.line_indent_for_buffer_row(MultiBufferRow(row));
2342 if !line_indent.is_line_blank()
2343 && line_indent.raw_len() <= start_line_indent.raw_len()
2344 {
2345 if self
2346 .buffer_snapshot()
2347 .language_scope_at(Point::new(row, 0))
2348 .is_some_and(|scope| {
2349 matches!(
2350 scope.override_name(),
2351 Some("string") | Some("comment") | Some("comment.inclusive")
2352 )
2353 })
2354 {
2355 continue;
2356 }
2357
2358 closing_row = Some(row);
2359 break;
2360 }
2361 }
2362
2363 let last_non_blank_row = |from_row: u32| -> Point {
2364 let mut row = from_row;
2365 while row > start.row && self.buffer_snapshot().is_line_blank(MultiBufferRow(row)) {
2366 row -= 1;
2367 }
2368 Point::new(row, self.buffer_snapshot().line_len(MultiBufferRow(row)))
2369 };
2370
2371 let end = if let Some(row) = closing_row {
2372 if let Some(indent_len) = self.closing_bracket_indent_len(row) {
2373 // Include newline and whitespace before closing delimiter,
2374 // so it appears on the same display line as the fold placeholder
2375 Point::new(row, indent_len)
2376 } else {
2377 last_non_blank_row(row - 1)
2378 }
2379 } else {
2380 last_non_blank_row(max_point.row)
2381 };
2382
2383 Some(Crease::Inline {
2384 range: start..end,
2385 placeholder: self.fold_placeholder.clone(),
2386 render_toggle: None,
2387 render_trailer: None,
2388 metadata: None,
2389 })
2390 } else {
2391 None
2392 }
2393 }
2394
2395 #[cfg(any(test, feature = "test-support"))]
2396 #[instrument(skip_all)]
2397 pub fn text_highlight_ranges(
2398 &self,
2399 key: HighlightKey,
2400 ) -> Option<Arc<(HighlightStyle, Vec<Range<Anchor>>)>> {
2401 self.text_highlights.get(&key).cloned()
2402 }
2403
2404 #[cfg(any(test, feature = "test-support"))]
2405 #[instrument(skip_all)]
2406 pub fn all_text_highlight_ranges(
2407 &self,
2408 f: &dyn Fn(&HighlightKey) -> bool,
2409 ) -> Vec<(gpui::Hsla, Range<Point>)> {
2410 use itertools::Itertools;
2411
2412 self.text_highlights
2413 .iter()
2414 .filter(|(key, _)| f(key))
2415 .map(|(_, value)| value.clone())
2416 .flat_map(|ranges| {
2417 ranges
2418 .1
2419 .iter()
2420 .flat_map(|range| {
2421 Some((ranges.0.color?, range.to_point(self.buffer_snapshot())))
2422 })
2423 .collect::<Vec<_>>()
2424 })
2425 .sorted_by_key(|(_, range)| range.start)
2426 .collect()
2427 }
2428
2429 #[allow(unused)]
2430 #[cfg(any(test, feature = "test-support"))]
2431 pub(crate) fn inlay_highlights(
2432 &self,
2433 key: HighlightKey,
2434 ) -> Option<&TreeMap<InlayId, (HighlightStyle, InlayHighlight)>> {
2435 self.inlay_highlights.get(&key)
2436 }
2437
2438 pub fn buffer_header_height(&self) -> u32 {
2439 self.block_snapshot.buffer_header_height
2440 }
2441
2442 pub fn excerpt_header_height(&self) -> u32 {
2443 self.block_snapshot.excerpt_header_height
2444 }
2445
2446 /// Given a `DisplayPoint`, returns another `DisplayPoint` corresponding to
2447 /// the start of the buffer row that is a given number of buffer rows away
2448 /// from the provided point.
2449 ///
2450 /// This moves by buffer rows instead of display rows, a distinction that is
2451 /// important when soft wrapping is enabled.
2452 #[instrument(skip_all)]
2453 pub fn start_of_relative_buffer_row(&self, point: DisplayPoint, times: isize) -> DisplayPoint {
2454 let start = self.display_point_to_fold_point(point, Bias::Left);
2455 let target = start.row() as isize + times;
2456 let new_row = (target.max(0) as u32).min(self.fold_snapshot().max_point().row());
2457
2458 self.clip_point(
2459 self.fold_point_to_display_point(
2460 self.fold_snapshot()
2461 .clip_point(FoldPoint::new(new_row, 0), Bias::Right),
2462 ),
2463 Bias::Right,
2464 )
2465 }
2466}
2467
2468impl std::ops::Deref for DisplaySnapshot {
2469 type Target = BlockSnapshot;
2470
2471 fn deref(&self) -> &Self::Target {
2472 &self.block_snapshot
2473 }
2474}
2475
2476/// A zero-indexed point in a text buffer consisting of a row and column adjusted for inserted blocks.
2477#[derive(Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq)]
2478pub struct DisplayPoint(BlockPoint);
2479
2480impl Debug for DisplayPoint {
2481 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2482 f.write_fmt(format_args!(
2483 "DisplayPoint({}, {})",
2484 self.row().0,
2485 self.column()
2486 ))
2487 }
2488}
2489
2490impl Add for DisplayPoint {
2491 type Output = Self;
2492
2493 fn add(self, other: Self) -> Self::Output {
2494 DisplayPoint(BlockPoint(self.0.0 + other.0.0))
2495 }
2496}
2497
2498impl Sub for DisplayPoint {
2499 type Output = Self;
2500
2501 fn sub(self, other: Self) -> Self::Output {
2502 DisplayPoint(BlockPoint(self.0.0 - other.0.0))
2503 }
2504}
2505
2506#[derive(Debug, Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq, Deserialize, Hash)]
2507#[serde(transparent)]
2508pub struct DisplayRow(pub u32);
2509
2510impl DisplayRow {
2511 pub(crate) fn as_display_point(&self) -> DisplayPoint {
2512 DisplayPoint::new(*self, 0)
2513 }
2514}
2515
2516impl Add<DisplayRow> for DisplayRow {
2517 type Output = Self;
2518
2519 fn add(self, other: Self) -> Self::Output {
2520 DisplayRow(self.0 + other.0)
2521 }
2522}
2523
2524impl Add<u32> for DisplayRow {
2525 type Output = Self;
2526
2527 fn add(self, other: u32) -> Self::Output {
2528 DisplayRow(self.0 + other)
2529 }
2530}
2531
2532impl Sub<DisplayRow> for DisplayRow {
2533 type Output = Self;
2534
2535 fn sub(self, other: Self) -> Self::Output {
2536 DisplayRow(self.0 - other.0)
2537 }
2538}
2539
2540impl Sub<u32> for DisplayRow {
2541 type Output = Self;
2542
2543 fn sub(self, other: u32) -> Self::Output {
2544 DisplayRow(self.0 - other)
2545 }
2546}
2547
2548impl DisplayPoint {
2549 pub fn new(row: DisplayRow, column: u32) -> Self {
2550 Self(BlockPoint(Point::new(row.0, column)))
2551 }
2552
2553 pub fn zero() -> Self {
2554 Self::new(DisplayRow(0), 0)
2555 }
2556
2557 pub fn is_zero(&self) -> bool {
2558 self.0.is_zero()
2559 }
2560
2561 pub fn row(self) -> DisplayRow {
2562 DisplayRow(self.0.row)
2563 }
2564
2565 pub fn column(self) -> u32 {
2566 self.0.column
2567 }
2568
2569 pub fn row_mut(&mut self) -> &mut u32 {
2570 &mut self.0.row
2571 }
2572
2573 pub fn column_mut(&mut self) -> &mut u32 {
2574 &mut self.0.column
2575 }
2576
2577 pub fn to_point(self, map: &DisplaySnapshot) -> Point {
2578 map.display_point_to_point(self, Bias::Left)
2579 }
2580
2581 pub fn to_offset(self, map: &DisplaySnapshot, bias: Bias) -> MultiBufferOffset {
2582 let wrap_point = map.block_snapshot.to_wrap_point(self.0, bias);
2583 let tab_point = map.wrap_snapshot().to_tab_point(wrap_point);
2584 let fold_point = map
2585 .tab_snapshot()
2586 .tab_point_to_fold_point(tab_point, bias)
2587 .0;
2588 let inlay_point = fold_point.to_inlay_point(map.fold_snapshot());
2589 map.inlay_snapshot()
2590 .to_buffer_offset(map.inlay_snapshot().to_offset(inlay_point))
2591 }
2592}
2593
2594impl ToDisplayPoint for MultiBufferOffset {
2595 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
2596 map.point_to_display_point(self.to_point(map.buffer_snapshot()), Bias::Left)
2597 }
2598}
2599
2600impl ToDisplayPoint for MultiBufferOffsetUtf16 {
2601 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
2602 self.to_offset(map.buffer_snapshot()).to_display_point(map)
2603 }
2604}
2605
2606impl ToDisplayPoint for Point {
2607 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
2608 map.point_to_display_point(*self, Bias::Left)
2609 }
2610}
2611
2612impl ToDisplayPoint for Anchor {
2613 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
2614 self.to_point(map.buffer_snapshot()).to_display_point(map)
2615 }
2616}
2617
2618#[cfg(test)]
2619pub mod tests {
2620 use super::*;
2621 use crate::{
2622 movement,
2623 test::{marked_display_snapshot, test_font},
2624 };
2625 use Bias::*;
2626 use block_map::BlockPlacement;
2627 use gpui::{
2628 App, AppContext as _, BorrowAppContext, Element, Hsla, Rgba, div, font, observe, px,
2629 };
2630 use language::{
2631 Buffer, Diagnostic, DiagnosticEntry, DiagnosticSet, Language, LanguageConfig,
2632 LanguageMatcher,
2633 };
2634 use lsp::LanguageServerId;
2635
2636 use rand::{Rng, prelude::*};
2637 use settings::{SettingsContent, SettingsStore};
2638 use smol::stream::StreamExt;
2639 use std::{env, sync::Arc};
2640 use text::PointUtf16;
2641 use theme::{LoadThemes, SyntaxTheme};
2642 use unindent::Unindent as _;
2643 use util::test::{marked_text_ranges, sample_text};
2644
2645 #[gpui::test(iterations = 100)]
2646 async fn test_random_display_map(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
2647 cx.background_executor.set_block_on_ticks(0..=50);
2648 let operations = env::var("OPERATIONS")
2649 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2650 .unwrap_or(10);
2651
2652 let mut tab_size = rng.random_range(1..=4);
2653 let buffer_start_excerpt_header_height = rng.random_range(1..=5);
2654 let excerpt_header_height = rng.random_range(1..=5);
2655 let font_size = px(14.0);
2656 let max_wrap_width = 300.0;
2657 let mut wrap_width = if rng.random_bool(0.1) {
2658 None
2659 } else {
2660 Some(px(rng.random_range(0.0..=max_wrap_width)))
2661 };
2662
2663 log::info!("tab size: {}", tab_size);
2664 log::info!("wrap width: {:?}", wrap_width);
2665
2666 cx.update(|cx| {
2667 init_test(cx, &|s| {
2668 s.project.all_languages.defaults.tab_size = NonZeroU32::new(tab_size)
2669 });
2670 });
2671
2672 let buffer = cx.update(|cx| {
2673 if rng.random() {
2674 let len = rng.random_range(0..10);
2675 let text = util::RandomCharIter::new(&mut rng)
2676 .take(len)
2677 .collect::<String>();
2678 MultiBuffer::build_simple(&text, cx)
2679 } else {
2680 MultiBuffer::build_random(&mut rng, cx)
2681 }
2682 });
2683
2684 let font = test_font();
2685 let map = cx.new(|cx| {
2686 DisplayMap::new(
2687 buffer.clone(),
2688 font,
2689 font_size,
2690 wrap_width,
2691 buffer_start_excerpt_header_height,
2692 excerpt_header_height,
2693 FoldPlaceholder::test(),
2694 DiagnosticSeverity::Warning,
2695 cx,
2696 )
2697 });
2698 let mut notifications = observe(&map, cx);
2699 let mut fold_count = 0;
2700 let mut blocks = Vec::new();
2701
2702 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
2703 log::info!("buffer text: {:?}", snapshot.buffer_snapshot().text());
2704 log::info!("fold text: {:?}", snapshot.fold_snapshot().text());
2705 log::info!("tab text: {:?}", snapshot.tab_snapshot().text());
2706 log::info!("wrap text: {:?}", snapshot.wrap_snapshot().text());
2707 log::info!("block text: {:?}", snapshot.block_snapshot.text());
2708 log::info!("display text: {:?}", snapshot.text());
2709
2710 for _i in 0..operations {
2711 match rng.random_range(0..100) {
2712 0..=19 => {
2713 wrap_width = if rng.random_bool(0.2) {
2714 None
2715 } else {
2716 Some(px(rng.random_range(0.0..=max_wrap_width)))
2717 };
2718 log::info!("setting wrap width to {:?}", wrap_width);
2719 map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
2720 }
2721 20..=29 => {
2722 let mut tab_sizes = vec![1, 2, 3, 4];
2723 tab_sizes.remove((tab_size - 1) as usize);
2724 tab_size = *tab_sizes.choose(&mut rng).unwrap();
2725 log::info!("setting tab size to {:?}", tab_size);
2726 cx.update(|cx| {
2727 cx.update_global::<SettingsStore, _>(|store, cx| {
2728 store.update_user_settings(cx, |s| {
2729 s.project.all_languages.defaults.tab_size =
2730 NonZeroU32::new(tab_size);
2731 });
2732 });
2733 });
2734 }
2735 30..=44 => {
2736 map.update(cx, |map, cx| {
2737 if rng.random() || blocks.is_empty() {
2738 let snapshot = map.snapshot(cx);
2739 let buffer = snapshot.buffer_snapshot();
2740 let block_properties = (0..rng.random_range(1..=1))
2741 .map(|_| {
2742 let position = buffer.anchor_after(buffer.clip_offset(
2743 rng.random_range(MultiBufferOffset(0)..=buffer.len()),
2744 Bias::Left,
2745 ));
2746
2747 let placement = if rng.random() {
2748 BlockPlacement::Above(position)
2749 } else {
2750 BlockPlacement::Below(position)
2751 };
2752 let height = rng.random_range(1..5);
2753 log::info!(
2754 "inserting block {:?} with height {}",
2755 placement.as_ref().map(|p| p.to_point(&buffer)),
2756 height
2757 );
2758 let priority = rng.random_range(1..100);
2759 BlockProperties {
2760 placement,
2761 style: BlockStyle::Fixed,
2762 height: Some(height),
2763 render: Arc::new(|_| div().into_any()),
2764 priority,
2765 }
2766 })
2767 .collect::<Vec<_>>();
2768 blocks.extend(map.insert_blocks(block_properties, cx));
2769 } else {
2770 blocks.shuffle(&mut rng);
2771 let remove_count = rng.random_range(1..=4.min(blocks.len()));
2772 let block_ids_to_remove = (0..remove_count)
2773 .map(|_| blocks.remove(rng.random_range(0..blocks.len())))
2774 .collect();
2775 log::info!("removing block ids {:?}", block_ids_to_remove);
2776 map.remove_blocks(block_ids_to_remove, cx);
2777 }
2778 });
2779 }
2780 45..=79 => {
2781 let mut ranges = Vec::new();
2782 for _ in 0..rng.random_range(1..=3) {
2783 buffer.read_with(cx, |buffer, cx| {
2784 let buffer = buffer.read(cx);
2785 let end = buffer.clip_offset(
2786 rng.random_range(MultiBufferOffset(0)..=buffer.len()),
2787 Right,
2788 );
2789 let start = buffer
2790 .clip_offset(rng.random_range(MultiBufferOffset(0)..=end), Left);
2791 ranges.push(start..end);
2792 });
2793 }
2794
2795 if rng.random() && fold_count > 0 {
2796 log::info!("unfolding ranges: {:?}", ranges);
2797 map.update(cx, |map, cx| {
2798 map.unfold_intersecting(ranges, true, cx);
2799 });
2800 } else {
2801 log::info!("folding ranges: {:?}", ranges);
2802 map.update(cx, |map, cx| {
2803 map.fold(
2804 ranges
2805 .into_iter()
2806 .map(|range| Crease::simple(range, FoldPlaceholder::test()))
2807 .collect(),
2808 cx,
2809 );
2810 });
2811 }
2812 }
2813 _ => {
2814 buffer.update(cx, |buffer, cx| buffer.randomly_mutate(&mut rng, 5, cx));
2815 }
2816 }
2817
2818 if map.read_with(cx, |map, cx| map.is_rewrapping(cx)) {
2819 notifications.next().await.unwrap();
2820 }
2821
2822 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
2823 fold_count = snapshot.fold_count();
2824 log::info!("buffer text: {:?}", snapshot.buffer_snapshot().text());
2825 log::info!("fold text: {:?}", snapshot.fold_snapshot().text());
2826 log::info!("tab text: {:?}", snapshot.tab_snapshot().text());
2827 log::info!("wrap text: {:?}", snapshot.wrap_snapshot().text());
2828 log::info!("block text: {:?}", snapshot.block_snapshot.text());
2829 log::info!("display text: {:?}", snapshot.text());
2830
2831 // Line boundaries
2832 let buffer = snapshot.buffer_snapshot();
2833 for _ in 0..5 {
2834 let row = rng.random_range(0..=buffer.max_point().row);
2835 let column = rng.random_range(0..=buffer.line_len(MultiBufferRow(row)));
2836 let point = buffer.clip_point(Point::new(row, column), Left);
2837
2838 let (prev_buffer_bound, prev_display_bound) = snapshot.prev_line_boundary(point);
2839 let (next_buffer_bound, next_display_bound) = snapshot.next_line_boundary(point);
2840
2841 assert!(prev_buffer_bound <= point);
2842 assert!(next_buffer_bound >= point);
2843 assert_eq!(prev_buffer_bound.column, 0);
2844 assert_eq!(prev_display_bound.column(), 0);
2845 if next_buffer_bound < buffer.max_point() {
2846 assert_eq!(buffer.chars_at(next_buffer_bound).next(), Some('\n'));
2847 }
2848
2849 assert_eq!(
2850 prev_display_bound,
2851 prev_buffer_bound.to_display_point(&snapshot),
2852 "row boundary before {:?}. reported buffer row boundary: {:?}",
2853 point,
2854 prev_buffer_bound
2855 );
2856 assert_eq!(
2857 next_display_bound,
2858 next_buffer_bound.to_display_point(&snapshot),
2859 "display row boundary after {:?}. reported buffer row boundary: {:?}",
2860 point,
2861 next_buffer_bound
2862 );
2863 assert_eq!(
2864 prev_buffer_bound,
2865 prev_display_bound.to_point(&snapshot),
2866 "row boundary before {:?}. reported display row boundary: {:?}",
2867 point,
2868 prev_display_bound
2869 );
2870 assert_eq!(
2871 next_buffer_bound,
2872 next_display_bound.to_point(&snapshot),
2873 "row boundary after {:?}. reported display row boundary: {:?}",
2874 point,
2875 next_display_bound
2876 );
2877 }
2878
2879 // Movement
2880 let min_point = snapshot.clip_point(DisplayPoint::new(DisplayRow(0), 0), Left);
2881 let max_point = snapshot.clip_point(snapshot.max_point(), Right);
2882 for _ in 0..5 {
2883 let row = rng.random_range(0..=snapshot.max_point().row().0);
2884 let column = rng.random_range(0..=snapshot.line_len(DisplayRow(row)));
2885 let point = snapshot.clip_point(DisplayPoint::new(DisplayRow(row), column), Left);
2886
2887 log::info!("Moving from point {:?}", point);
2888
2889 let moved_right = movement::right(&snapshot, point);
2890 log::info!("Right {:?}", moved_right);
2891 if point < max_point {
2892 assert!(moved_right > point);
2893 if point.column() == snapshot.line_len(point.row())
2894 || snapshot.soft_wrap_indent(point.row()).is_some()
2895 && point.column() == snapshot.line_len(point.row()) - 1
2896 {
2897 assert!(moved_right.row() > point.row());
2898 }
2899 } else {
2900 assert_eq!(moved_right, point);
2901 }
2902
2903 let moved_left = movement::left(&snapshot, point);
2904 log::info!("Left {:?}", moved_left);
2905 if point > min_point {
2906 assert!(moved_left < point);
2907 if point.column() == 0 {
2908 assert!(moved_left.row() < point.row());
2909 }
2910 } else {
2911 assert_eq!(moved_left, point);
2912 }
2913 }
2914 }
2915 }
2916
2917 #[gpui::test(retries = 5)]
2918 async fn test_soft_wraps(cx: &mut gpui::TestAppContext) {
2919 cx.background_executor
2920 .set_block_on_ticks(usize::MAX..=usize::MAX);
2921 cx.update(|cx| {
2922 init_test(cx, &|_| {});
2923 });
2924
2925 let mut cx = crate::test::editor_test_context::EditorTestContext::new(cx).await;
2926 let editor = cx.editor.clone();
2927 let window = cx.window;
2928
2929 _ = cx.update_window(window, |_, window, cx| {
2930 let text_layout_details =
2931 editor.update(cx, |editor, cx| editor.text_layout_details(window, cx));
2932
2933 let font_size = px(12.0);
2934 let wrap_width = Some(px(96.));
2935
2936 let text = "one two three four five\nsix seven eight";
2937 let buffer = MultiBuffer::build_simple(text, cx);
2938 let map = cx.new(|cx| {
2939 DisplayMap::new(
2940 buffer.clone(),
2941 font("Helvetica"),
2942 font_size,
2943 wrap_width,
2944 1,
2945 1,
2946 FoldPlaceholder::test(),
2947 DiagnosticSeverity::Warning,
2948 cx,
2949 )
2950 });
2951
2952 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
2953 assert_eq!(
2954 snapshot.text_chunks(DisplayRow(0)).collect::<String>(),
2955 "one two \nthree four \nfive\nsix seven \neight"
2956 );
2957 assert_eq!(
2958 snapshot.clip_point(DisplayPoint::new(DisplayRow(0), 8), Bias::Left),
2959 DisplayPoint::new(DisplayRow(0), 7)
2960 );
2961 assert_eq!(
2962 snapshot.clip_point(DisplayPoint::new(DisplayRow(0), 8), Bias::Right),
2963 DisplayPoint::new(DisplayRow(1), 0)
2964 );
2965 assert_eq!(
2966 movement::right(&snapshot, DisplayPoint::new(DisplayRow(0), 7)),
2967 DisplayPoint::new(DisplayRow(1), 0)
2968 );
2969 assert_eq!(
2970 movement::left(&snapshot, DisplayPoint::new(DisplayRow(1), 0)),
2971 DisplayPoint::new(DisplayRow(0), 7)
2972 );
2973
2974 let x = snapshot
2975 .x_for_display_point(DisplayPoint::new(DisplayRow(1), 10), &text_layout_details);
2976 assert_eq!(
2977 movement::up(
2978 &snapshot,
2979 DisplayPoint::new(DisplayRow(1), 10),
2980 language::SelectionGoal::None,
2981 false,
2982 &text_layout_details,
2983 ),
2984 (
2985 DisplayPoint::new(DisplayRow(0), 7),
2986 language::SelectionGoal::HorizontalPosition(f64::from(x))
2987 )
2988 );
2989 assert_eq!(
2990 movement::down(
2991 &snapshot,
2992 DisplayPoint::new(DisplayRow(0), 7),
2993 language::SelectionGoal::HorizontalPosition(f64::from(x)),
2994 false,
2995 &text_layout_details
2996 ),
2997 (
2998 DisplayPoint::new(DisplayRow(1), 10),
2999 language::SelectionGoal::HorizontalPosition(f64::from(x))
3000 )
3001 );
3002 assert_eq!(
3003 movement::down(
3004 &snapshot,
3005 DisplayPoint::new(DisplayRow(1), 10),
3006 language::SelectionGoal::HorizontalPosition(f64::from(x)),
3007 false,
3008 &text_layout_details
3009 ),
3010 (
3011 DisplayPoint::new(DisplayRow(2), 4),
3012 language::SelectionGoal::HorizontalPosition(f64::from(x))
3013 )
3014 );
3015
3016 let ix = MultiBufferOffset(snapshot.buffer_snapshot().text().find("seven").unwrap());
3017 buffer.update(cx, |buffer, cx| {
3018 buffer.edit([(ix..ix, "and ")], None, cx);
3019 });
3020
3021 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
3022 assert_eq!(
3023 snapshot.text_chunks(DisplayRow(1)).collect::<String>(),
3024 "three four \nfive\nsix and \nseven eight"
3025 );
3026
3027 // Re-wrap on font size changes
3028 map.update(cx, |map, cx| {
3029 map.set_font(font("Helvetica"), font_size + Pixels::from(3.), cx)
3030 });
3031
3032 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
3033 assert_eq!(
3034 snapshot.text_chunks(DisplayRow(1)).collect::<String>(),
3035 "three \nfour five\nsix and \nseven \neight"
3036 )
3037 });
3038 }
3039
3040 #[gpui::test]
3041 fn test_text_chunks(cx: &mut gpui::App) {
3042 init_test(cx, &|_| {});
3043
3044 let text = sample_text(6, 6, 'a');
3045 let buffer = MultiBuffer::build_simple(&text, cx);
3046
3047 let font_size = px(14.0);
3048 let map = cx.new(|cx| {
3049 DisplayMap::new(
3050 buffer.clone(),
3051 font("Helvetica"),
3052 font_size,
3053 None,
3054 1,
3055 1,
3056 FoldPlaceholder::test(),
3057 DiagnosticSeverity::Warning,
3058 cx,
3059 )
3060 });
3061
3062 buffer.update(cx, |buffer, cx| {
3063 buffer.edit(
3064 vec![
3065 (
3066 MultiBufferPoint::new(1, 0)..MultiBufferPoint::new(1, 0),
3067 "\t",
3068 ),
3069 (
3070 MultiBufferPoint::new(1, 1)..MultiBufferPoint::new(1, 1),
3071 "\t",
3072 ),
3073 (
3074 MultiBufferPoint::new(2, 1)..MultiBufferPoint::new(2, 1),
3075 "\t",
3076 ),
3077 ],
3078 None,
3079 cx,
3080 )
3081 });
3082
3083 assert_eq!(
3084 map.update(cx, |map, cx| map.snapshot(cx))
3085 .text_chunks(DisplayRow(1))
3086 .collect::<String>()
3087 .lines()
3088 .next(),
3089 Some(" b bbbbb")
3090 );
3091 assert_eq!(
3092 map.update(cx, |map, cx| map.snapshot(cx))
3093 .text_chunks(DisplayRow(2))
3094 .collect::<String>()
3095 .lines()
3096 .next(),
3097 Some("c ccccc")
3098 );
3099 }
3100
3101 #[gpui::test]
3102 fn test_inlays_with_newlines_after_blocks(cx: &mut gpui::TestAppContext) {
3103 cx.update(|cx| init_test(cx, &|_| {}));
3104
3105 let buffer = cx.new(|cx| Buffer::local("a", cx));
3106 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
3107 let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
3108
3109 let font_size = px(14.0);
3110 let map = cx.new(|cx| {
3111 DisplayMap::new(
3112 buffer.clone(),
3113 font("Helvetica"),
3114 font_size,
3115 None,
3116 1,
3117 1,
3118 FoldPlaceholder::test(),
3119 DiagnosticSeverity::Warning,
3120 cx,
3121 )
3122 });
3123
3124 map.update(cx, |map, cx| {
3125 map.insert_blocks(
3126 [BlockProperties {
3127 placement: BlockPlacement::Above(
3128 buffer_snapshot.anchor_before(Point::new(0, 0)),
3129 ),
3130 height: Some(2),
3131 style: BlockStyle::Sticky,
3132 render: Arc::new(|_| div().into_any()),
3133 priority: 0,
3134 }],
3135 cx,
3136 );
3137 });
3138 map.update(cx, |m, cx| assert_eq!(m.snapshot(cx).text(), "\n\na"));
3139
3140 map.update(cx, |map, cx| {
3141 map.splice_inlays(
3142 &[],
3143 vec![Inlay::edit_prediction(
3144 0,
3145 buffer_snapshot.anchor_after(MultiBufferOffset(0)),
3146 "\n",
3147 )],
3148 cx,
3149 );
3150 });
3151 map.update(cx, |m, cx| assert_eq!(m.snapshot(cx).text(), "\n\n\na"));
3152
3153 // Regression test: updating the display map does not crash when a
3154 // block is immediately followed by a multi-line inlay.
3155 buffer.update(cx, |buffer, cx| {
3156 buffer.edit(
3157 [(MultiBufferOffset(1)..MultiBufferOffset(1), "b")],
3158 None,
3159 cx,
3160 );
3161 });
3162 map.update(cx, |m, cx| assert_eq!(m.snapshot(cx).text(), "\n\n\nab"));
3163 }
3164
3165 #[gpui::test]
3166 async fn test_chunks(cx: &mut gpui::TestAppContext) {
3167 let text = r#"
3168 fn outer() {}
3169
3170 mod module {
3171 fn inner() {}
3172 }"#
3173 .unindent();
3174
3175 let theme =
3176 SyntaxTheme::new_test(vec![("mod.body", Hsla::red()), ("fn.name", Hsla::blue())]);
3177 let language = Arc::new(
3178 Language::new(
3179 LanguageConfig {
3180 name: "Test".into(),
3181 matcher: LanguageMatcher {
3182 path_suffixes: vec![".test".to_string()],
3183 ..Default::default()
3184 },
3185 ..Default::default()
3186 },
3187 Some(tree_sitter_rust::LANGUAGE.into()),
3188 )
3189 .with_highlights_query(
3190 r#"
3191 (mod_item name: (identifier) body: _ @mod.body)
3192 (function_item name: (identifier) @fn.name)
3193 "#,
3194 )
3195 .unwrap(),
3196 );
3197 language.set_theme(&theme);
3198
3199 cx.update(|cx| {
3200 init_test(cx, &|s| {
3201 s.project.all_languages.defaults.tab_size = Some(2.try_into().unwrap())
3202 })
3203 });
3204
3205 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
3206 cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
3207 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
3208
3209 let font_size = px(14.0);
3210
3211 let map = cx.new(|cx| {
3212 DisplayMap::new(
3213 buffer,
3214 font("Helvetica"),
3215 font_size,
3216 None,
3217 1,
3218 1,
3219 FoldPlaceholder::test(),
3220 DiagnosticSeverity::Warning,
3221 cx,
3222 )
3223 });
3224 assert_eq!(
3225 cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(5), &map, &theme, cx)),
3226 vec![
3227 ("fn ".to_string(), None),
3228 ("outer".to_string(), Some(Hsla::blue())),
3229 ("() {}\n\nmod module ".to_string(), None),
3230 ("{\n fn ".to_string(), Some(Hsla::red())),
3231 ("inner".to_string(), Some(Hsla::blue())),
3232 ("() {}\n}".to_string(), Some(Hsla::red())),
3233 ]
3234 );
3235 assert_eq!(
3236 cx.update(|cx| syntax_chunks(DisplayRow(3)..DisplayRow(5), &map, &theme, cx)),
3237 vec![
3238 (" fn ".to_string(), Some(Hsla::red())),
3239 ("inner".to_string(), Some(Hsla::blue())),
3240 ("() {}\n}".to_string(), Some(Hsla::red())),
3241 ]
3242 );
3243
3244 map.update(cx, |map, cx| {
3245 map.fold(
3246 vec![Crease::simple(
3247 MultiBufferPoint::new(0, 6)..MultiBufferPoint::new(3, 2),
3248 FoldPlaceholder::test(),
3249 )],
3250 cx,
3251 )
3252 });
3253 assert_eq!(
3254 cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(2), &map, &theme, cx)),
3255 vec![
3256 ("fn ".to_string(), None),
3257 ("out".to_string(), Some(Hsla::blue())),
3258 ("⋯".to_string(), None),
3259 (" fn ".to_string(), Some(Hsla::red())),
3260 ("inner".to_string(), Some(Hsla::blue())),
3261 ("() {}\n}".to_string(), Some(Hsla::red())),
3262 ]
3263 );
3264 }
3265
3266 #[gpui::test]
3267 async fn test_chunks_with_syntax_highlighting_across_blocks(cx: &mut gpui::TestAppContext) {
3268 cx.background_executor
3269 .set_block_on_ticks(usize::MAX..=usize::MAX);
3270
3271 let text = r#"
3272 const A: &str = "
3273 one
3274 two
3275 three
3276 ";
3277 const B: &str = "four";
3278 "#
3279 .unindent();
3280
3281 let theme = SyntaxTheme::new_test(vec![
3282 ("string", Hsla::red()),
3283 ("punctuation", Hsla::blue()),
3284 ("keyword", Hsla::green()),
3285 ]);
3286 let language = Arc::new(
3287 Language::new(
3288 LanguageConfig {
3289 name: "Rust".into(),
3290 ..Default::default()
3291 },
3292 Some(tree_sitter_rust::LANGUAGE.into()),
3293 )
3294 .with_highlights_query(
3295 r#"
3296 (string_literal) @string
3297 "const" @keyword
3298 [":" ";"] @punctuation
3299 "#,
3300 )
3301 .unwrap(),
3302 );
3303 language.set_theme(&theme);
3304
3305 cx.update(|cx| init_test(cx, &|_| {}));
3306
3307 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
3308 cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
3309 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
3310 let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
3311
3312 let map = cx.new(|cx| {
3313 DisplayMap::new(
3314 buffer,
3315 font("Courier"),
3316 px(16.0),
3317 None,
3318 1,
3319 1,
3320 FoldPlaceholder::test(),
3321 DiagnosticSeverity::Warning,
3322 cx,
3323 )
3324 });
3325
3326 // Insert two blocks in the middle of a multi-line string literal.
3327 // The second block has zero height.
3328 map.update(cx, |map, cx| {
3329 map.insert_blocks(
3330 [
3331 BlockProperties {
3332 placement: BlockPlacement::Below(
3333 buffer_snapshot.anchor_before(Point::new(1, 0)),
3334 ),
3335 height: Some(1),
3336 style: BlockStyle::Sticky,
3337 render: Arc::new(|_| div().into_any()),
3338 priority: 0,
3339 },
3340 BlockProperties {
3341 placement: BlockPlacement::Below(
3342 buffer_snapshot.anchor_before(Point::new(2, 0)),
3343 ),
3344 height: None,
3345 style: BlockStyle::Sticky,
3346 render: Arc::new(|_| div().into_any()),
3347 priority: 0,
3348 },
3349 ],
3350 cx,
3351 )
3352 });
3353
3354 pretty_assertions::assert_eq!(
3355 cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(7), &map, &theme, cx)),
3356 [
3357 ("const".into(), Some(Hsla::green())),
3358 (" A".into(), None),
3359 (":".into(), Some(Hsla::blue())),
3360 (" &str = ".into(), None),
3361 ("\"\n one\n".into(), Some(Hsla::red())),
3362 ("\n".into(), None),
3363 (" two\n three\n\"".into(), Some(Hsla::red())),
3364 (";".into(), Some(Hsla::blue())),
3365 ("\n".into(), None),
3366 ("const".into(), Some(Hsla::green())),
3367 (" B".into(), None),
3368 (":".into(), Some(Hsla::blue())),
3369 (" &str = ".into(), None),
3370 ("\"four\"".into(), Some(Hsla::red())),
3371 (";".into(), Some(Hsla::blue())),
3372 ("\n".into(), None),
3373 ]
3374 );
3375 }
3376
3377 #[gpui::test]
3378 async fn test_chunks_with_diagnostics_across_blocks(cx: &mut gpui::TestAppContext) {
3379 cx.background_executor
3380 .set_block_on_ticks(usize::MAX..=usize::MAX);
3381
3382 let text = r#"
3383 struct A {
3384 b: usize;
3385 }
3386 const c: usize = 1;
3387 "#
3388 .unindent();
3389
3390 cx.update(|cx| init_test(cx, &|_| {}));
3391
3392 let buffer = cx.new(|cx| Buffer::local(text, cx));
3393
3394 buffer.update(cx, |buffer, cx| {
3395 buffer.update_diagnostics(
3396 LanguageServerId(0),
3397 DiagnosticSet::new(
3398 [DiagnosticEntry {
3399 range: PointUtf16::new(0, 0)..PointUtf16::new(2, 1),
3400 diagnostic: Diagnostic {
3401 severity: lsp::DiagnosticSeverity::ERROR,
3402 group_id: 1,
3403 message: "hi".into(),
3404 ..Default::default()
3405 },
3406 }],
3407 buffer,
3408 ),
3409 cx,
3410 )
3411 });
3412
3413 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
3414 let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
3415
3416 let map = cx.new(|cx| {
3417 DisplayMap::new(
3418 buffer,
3419 font("Courier"),
3420 px(16.0),
3421 None,
3422 1,
3423 1,
3424 FoldPlaceholder::test(),
3425 DiagnosticSeverity::Warning,
3426 cx,
3427 )
3428 });
3429
3430 let black = gpui::black().to_rgb();
3431 let red = gpui::red().to_rgb();
3432
3433 // Insert a block in the middle of a multi-line diagnostic.
3434 map.update(cx, |map, cx| {
3435 map.highlight_text(
3436 HighlightKey::Editor,
3437 vec![
3438 buffer_snapshot.anchor_before(Point::new(3, 9))
3439 ..buffer_snapshot.anchor_after(Point::new(3, 14)),
3440 buffer_snapshot.anchor_before(Point::new(3, 17))
3441 ..buffer_snapshot.anchor_after(Point::new(3, 18)),
3442 ],
3443 red.into(),
3444 false,
3445 cx,
3446 );
3447 map.insert_blocks(
3448 [BlockProperties {
3449 placement: BlockPlacement::Below(
3450 buffer_snapshot.anchor_before(Point::new(1, 0)),
3451 ),
3452 height: Some(1),
3453 style: BlockStyle::Sticky,
3454 render: Arc::new(|_| div().into_any()),
3455 priority: 0,
3456 }],
3457 cx,
3458 )
3459 });
3460
3461 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
3462 let mut chunks = Vec::<(String, Option<lsp::DiagnosticSeverity>, Rgba)>::new();
3463 for chunk in snapshot.chunks(DisplayRow(0)..DisplayRow(5), true, Default::default()) {
3464 let color = chunk
3465 .highlight_style
3466 .and_then(|style| style.color)
3467 .map_or(black, |color| color.to_rgb());
3468 if let Some((last_chunk, last_severity, last_color)) = chunks.last_mut()
3469 && *last_severity == chunk.diagnostic_severity
3470 && *last_color == color
3471 {
3472 last_chunk.push_str(chunk.text);
3473 continue;
3474 }
3475
3476 chunks.push((chunk.text.to_string(), chunk.diagnostic_severity, color));
3477 }
3478
3479 assert_eq!(
3480 chunks,
3481 [
3482 (
3483 "struct A {\n b: usize;\n".into(),
3484 Some(lsp::DiagnosticSeverity::ERROR),
3485 black
3486 ),
3487 ("\n".into(), None, black),
3488 ("}".into(), Some(lsp::DiagnosticSeverity::ERROR), black),
3489 ("\nconst c: ".into(), None, black),
3490 ("usize".into(), None, red),
3491 (" = ".into(), None, black),
3492 ("1".into(), None, red),
3493 (";\n".into(), None, black),
3494 ]
3495 );
3496 }
3497
3498 #[gpui::test]
3499 async fn test_point_translation_with_replace_blocks(cx: &mut gpui::TestAppContext) {
3500 cx.background_executor
3501 .set_block_on_ticks(usize::MAX..=usize::MAX);
3502
3503 cx.update(|cx| init_test(cx, &|_| {}));
3504
3505 let buffer = cx.update(|cx| MultiBuffer::build_simple("abcde\nfghij\nklmno\npqrst", cx));
3506 let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
3507 let map = cx.new(|cx| {
3508 DisplayMap::new(
3509 buffer.clone(),
3510 font("Courier"),
3511 px(16.0),
3512 None,
3513 1,
3514 1,
3515 FoldPlaceholder::test(),
3516 DiagnosticSeverity::Warning,
3517 cx,
3518 )
3519 });
3520
3521 let snapshot = map.update(cx, |map, cx| {
3522 map.insert_blocks(
3523 [BlockProperties {
3524 placement: BlockPlacement::Replace(
3525 buffer_snapshot.anchor_before(Point::new(1, 2))
3526 ..=buffer_snapshot.anchor_after(Point::new(2, 3)),
3527 ),
3528 height: Some(4),
3529 style: BlockStyle::Fixed,
3530 render: Arc::new(|_| div().into_any()),
3531 priority: 0,
3532 }],
3533 cx,
3534 );
3535 map.snapshot(cx)
3536 });
3537
3538 assert_eq!(snapshot.text(), "abcde\n\n\n\n\npqrst");
3539
3540 let point_to_display_points = [
3541 (Point::new(1, 0), DisplayPoint::new(DisplayRow(1), 0)),
3542 (Point::new(2, 0), DisplayPoint::new(DisplayRow(1), 0)),
3543 (Point::new(3, 0), DisplayPoint::new(DisplayRow(5), 0)),
3544 ];
3545 for (buffer_point, display_point) in point_to_display_points {
3546 assert_eq!(
3547 snapshot.point_to_display_point(buffer_point, Bias::Left),
3548 display_point,
3549 "point_to_display_point({:?}, Bias::Left)",
3550 buffer_point
3551 );
3552 assert_eq!(
3553 snapshot.point_to_display_point(buffer_point, Bias::Right),
3554 display_point,
3555 "point_to_display_point({:?}, Bias::Right)",
3556 buffer_point
3557 );
3558 }
3559
3560 let display_points_to_points = [
3561 (
3562 DisplayPoint::new(DisplayRow(1), 0),
3563 Point::new(1, 0),
3564 Point::new(2, 5),
3565 ),
3566 (
3567 DisplayPoint::new(DisplayRow(2), 0),
3568 Point::new(1, 0),
3569 Point::new(2, 5),
3570 ),
3571 (
3572 DisplayPoint::new(DisplayRow(3), 0),
3573 Point::new(1, 0),
3574 Point::new(2, 5),
3575 ),
3576 (
3577 DisplayPoint::new(DisplayRow(4), 0),
3578 Point::new(1, 0),
3579 Point::new(2, 5),
3580 ),
3581 (
3582 DisplayPoint::new(DisplayRow(5), 0),
3583 Point::new(3, 0),
3584 Point::new(3, 0),
3585 ),
3586 ];
3587 for (display_point, left_buffer_point, right_buffer_point) in display_points_to_points {
3588 assert_eq!(
3589 snapshot.display_point_to_point(display_point, Bias::Left),
3590 left_buffer_point,
3591 "display_point_to_point({:?}, Bias::Left)",
3592 display_point
3593 );
3594 assert_eq!(
3595 snapshot.display_point_to_point(display_point, Bias::Right),
3596 right_buffer_point,
3597 "display_point_to_point({:?}, Bias::Right)",
3598 display_point
3599 );
3600 }
3601 }
3602
3603 #[gpui::test]
3604 async fn test_chunks_with_soft_wrapping(cx: &mut gpui::TestAppContext) {
3605 cx.background_executor
3606 .set_block_on_ticks(usize::MAX..=usize::MAX);
3607
3608 let text = r#"
3609 fn outer() {}
3610
3611 mod module {
3612 fn inner() {}
3613 }"#
3614 .unindent();
3615
3616 let theme =
3617 SyntaxTheme::new_test(vec![("mod.body", Hsla::red()), ("fn.name", Hsla::blue())]);
3618 let language = Arc::new(
3619 Language::new(
3620 LanguageConfig {
3621 name: "Test".into(),
3622 matcher: LanguageMatcher {
3623 path_suffixes: vec![".test".to_string()],
3624 ..Default::default()
3625 },
3626 ..Default::default()
3627 },
3628 Some(tree_sitter_rust::LANGUAGE.into()),
3629 )
3630 .with_highlights_query(
3631 r#"
3632 (mod_item name: (identifier) body: _ @mod.body)
3633 (function_item name: (identifier) @fn.name)
3634 "#,
3635 )
3636 .unwrap(),
3637 );
3638 language.set_theme(&theme);
3639
3640 cx.update(|cx| init_test(cx, &|_| {}));
3641
3642 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
3643 cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
3644 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
3645
3646 let font_size = px(16.0);
3647
3648 let map = cx.new(|cx| {
3649 DisplayMap::new(
3650 buffer,
3651 font("Courier"),
3652 font_size,
3653 Some(px(40.0)),
3654 1,
3655 1,
3656 FoldPlaceholder::test(),
3657 DiagnosticSeverity::Warning,
3658 cx,
3659 )
3660 });
3661 assert_eq!(
3662 cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(5), &map, &theme, cx)),
3663 [
3664 ("fn \n".to_string(), None),
3665 ("oute".to_string(), Some(Hsla::blue())),
3666 ("\n".to_string(), None),
3667 ("r".to_string(), Some(Hsla::blue())),
3668 ("() \n{}\n\n".to_string(), None),
3669 ]
3670 );
3671 assert_eq!(
3672 cx.update(|cx| syntax_chunks(DisplayRow(3)..DisplayRow(5), &map, &theme, cx)),
3673 [("{}\n\n".to_string(), None)]
3674 );
3675
3676 map.update(cx, |map, cx| {
3677 map.fold(
3678 vec![Crease::simple(
3679 MultiBufferPoint::new(0, 6)..MultiBufferPoint::new(3, 2),
3680 FoldPlaceholder::test(),
3681 )],
3682 cx,
3683 )
3684 });
3685 assert_eq!(
3686 cx.update(|cx| syntax_chunks(DisplayRow(1)..DisplayRow(4), &map, &theme, cx)),
3687 [
3688 ("out".to_string(), Some(Hsla::blue())),
3689 ("⋯\n".to_string(), None),
3690 (" ".to_string(), Some(Hsla::red())),
3691 ("\n".to_string(), None),
3692 ("fn ".to_string(), Some(Hsla::red())),
3693 ("i".to_string(), Some(Hsla::blue())),
3694 ("\n".to_string(), None)
3695 ]
3696 );
3697 }
3698
3699 #[gpui::test]
3700 async fn test_chunks_with_text_highlights(cx: &mut gpui::TestAppContext) {
3701 cx.update(|cx| init_test(cx, &|_| {}));
3702
3703 let theme =
3704 SyntaxTheme::new_test(vec![("operator", Hsla::red()), ("string", Hsla::green())]);
3705 let language = Arc::new(
3706 Language::new(
3707 LanguageConfig {
3708 name: "Test".into(),
3709 matcher: LanguageMatcher {
3710 path_suffixes: vec![".test".to_string()],
3711 ..Default::default()
3712 },
3713 ..Default::default()
3714 },
3715 Some(tree_sitter_rust::LANGUAGE.into()),
3716 )
3717 .with_highlights_query(
3718 r#"
3719 ":" @operator
3720 (string_literal) @string
3721 "#,
3722 )
3723 .unwrap(),
3724 );
3725 language.set_theme(&theme);
3726
3727 let (text, highlighted_ranges) = marked_text_ranges(r#"constˇ «a»«:» B = "c «d»""#, false);
3728
3729 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
3730 cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
3731
3732 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
3733 let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
3734
3735 let font_size = px(16.0);
3736 let map = cx.new(|cx| {
3737 DisplayMap::new(
3738 buffer,
3739 font("Courier"),
3740 font_size,
3741 None,
3742 1,
3743 1,
3744 FoldPlaceholder::test(),
3745 DiagnosticSeverity::Warning,
3746 cx,
3747 )
3748 });
3749
3750 let style = HighlightStyle {
3751 color: Some(Hsla::blue()),
3752 ..Default::default()
3753 };
3754
3755 map.update(cx, |map, cx| {
3756 map.highlight_text(
3757 HighlightKey::Editor,
3758 highlighted_ranges
3759 .into_iter()
3760 .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
3761 .map(|range| {
3762 buffer_snapshot.anchor_before(range.start)
3763 ..buffer_snapshot.anchor_before(range.end)
3764 })
3765 .collect(),
3766 style,
3767 false,
3768 cx,
3769 );
3770 });
3771
3772 assert_eq!(
3773 cx.update(|cx| chunks(DisplayRow(0)..DisplayRow(10), &map, &theme, cx)),
3774 [
3775 ("const ".to_string(), None, None),
3776 ("a".to_string(), None, Some(Hsla::blue())),
3777 (":".to_string(), Some(Hsla::red()), Some(Hsla::blue())),
3778 (" B = ".to_string(), None, None),
3779 ("\"c ".to_string(), Some(Hsla::green()), None),
3780 ("d".to_string(), Some(Hsla::green()), Some(Hsla::blue())),
3781 ("\"".to_string(), Some(Hsla::green()), None),
3782 ]
3783 );
3784 }
3785
3786 #[gpui::test]
3787 fn test_clip_point(cx: &mut gpui::App) {
3788 init_test(cx, &|_| {});
3789
3790 fn assert(text: &str, shift_right: bool, bias: Bias, cx: &mut gpui::App) {
3791 let (unmarked_snapshot, mut markers) = marked_display_snapshot(text, cx);
3792
3793 match bias {
3794 Bias::Left => {
3795 if shift_right {
3796 *markers[1].column_mut() += 1;
3797 }
3798
3799 assert_eq!(unmarked_snapshot.clip_point(markers[1], bias), markers[0])
3800 }
3801 Bias::Right => {
3802 if shift_right {
3803 *markers[0].column_mut() += 1;
3804 }
3805
3806 assert_eq!(unmarked_snapshot.clip_point(markers[0], bias), markers[1])
3807 }
3808 };
3809 }
3810
3811 use Bias::{Left, Right};
3812 assert("ˇˇα", false, Left, cx);
3813 assert("ˇˇα", true, Left, cx);
3814 assert("ˇˇα", false, Right, cx);
3815 assert("ˇαˇ", true, Right, cx);
3816 assert("ˇˇ✋", false, Left, cx);
3817 assert("ˇˇ✋", true, Left, cx);
3818 assert("ˇˇ✋", false, Right, cx);
3819 assert("ˇ✋ˇ", true, Right, cx);
3820 assert("ˇˇ🍐", false, Left, cx);
3821 assert("ˇˇ🍐", true, Left, cx);
3822 assert("ˇˇ🍐", false, Right, cx);
3823 assert("ˇ🍐ˇ", true, Right, cx);
3824 assert("ˇˇ\t", false, Left, cx);
3825 assert("ˇˇ\t", true, Left, cx);
3826 assert("ˇˇ\t", false, Right, cx);
3827 assert("ˇ\tˇ", true, Right, cx);
3828 assert(" ˇˇ\t", false, Left, cx);
3829 assert(" ˇˇ\t", true, Left, cx);
3830 assert(" ˇˇ\t", false, Right, cx);
3831 assert(" ˇ\tˇ", true, Right, cx);
3832 assert(" ˇˇ\t", false, Left, cx);
3833 assert(" ˇˇ\t", false, Right, cx);
3834 }
3835
3836 #[gpui::test]
3837 fn test_clip_at_line_ends(cx: &mut gpui::App) {
3838 init_test(cx, &|_| {});
3839
3840 fn assert(text: &str, cx: &mut gpui::App) {
3841 let (mut unmarked_snapshot, markers) = marked_display_snapshot(text, cx);
3842 unmarked_snapshot.clip_at_line_ends = true;
3843 assert_eq!(
3844 unmarked_snapshot.clip_point(markers[1], Bias::Left),
3845 markers[0]
3846 );
3847 }
3848
3849 assert("ˇˇ", cx);
3850 assert("ˇaˇ", cx);
3851 assert("aˇbˇ", cx);
3852 assert("aˇαˇ", cx);
3853 }
3854
3855 #[gpui::test]
3856 fn test_creases(cx: &mut gpui::App) {
3857 init_test(cx, &|_| {});
3858
3859 let text = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll";
3860 let buffer = MultiBuffer::build_simple(text, cx);
3861 let font_size = px(14.0);
3862 cx.new(|cx| {
3863 let mut map = DisplayMap::new(
3864 buffer.clone(),
3865 font("Helvetica"),
3866 font_size,
3867 None,
3868 1,
3869 1,
3870 FoldPlaceholder::test(),
3871 DiagnosticSeverity::Warning,
3872 cx,
3873 );
3874 let snapshot = map.buffer.read(cx).snapshot(cx);
3875 let range =
3876 snapshot.anchor_before(Point::new(2, 0))..snapshot.anchor_after(Point::new(3, 3));
3877
3878 map.crease_map.insert(
3879 [Crease::inline(
3880 range,
3881 FoldPlaceholder::test(),
3882 |_row, _status, _toggle, _window, _cx| div(),
3883 |_row, _status, _window, _cx| div(),
3884 )],
3885 &map.buffer.read(cx).snapshot(cx),
3886 );
3887
3888 map
3889 });
3890 }
3891
3892 #[gpui::test]
3893 fn test_tabs_with_multibyte_chars(cx: &mut gpui::App) {
3894 init_test(cx, &|_| {});
3895
3896 let text = "✅\t\tα\nβ\t\n🏀β\t\tγ";
3897 let buffer = MultiBuffer::build_simple(text, cx);
3898 let font_size = px(14.0);
3899
3900 let map = cx.new(|cx| {
3901 DisplayMap::new(
3902 buffer.clone(),
3903 font("Helvetica"),
3904 font_size,
3905 None,
3906 1,
3907 1,
3908 FoldPlaceholder::test(),
3909 DiagnosticSeverity::Warning,
3910 cx,
3911 )
3912 });
3913 let map = map.update(cx, |map, cx| map.snapshot(cx));
3914 assert_eq!(map.text(), "✅ α\nβ \n🏀β γ");
3915 assert_eq!(
3916 map.text_chunks(DisplayRow(0)).collect::<String>(),
3917 "✅ α\nβ \n🏀β γ"
3918 );
3919 assert_eq!(
3920 map.text_chunks(DisplayRow(1)).collect::<String>(),
3921 "β \n🏀β γ"
3922 );
3923 assert_eq!(
3924 map.text_chunks(DisplayRow(2)).collect::<String>(),
3925 "🏀β γ"
3926 );
3927
3928 let point = MultiBufferPoint::new(0, "✅\t\t".len() as u32);
3929 let display_point = DisplayPoint::new(DisplayRow(0), "✅ ".len() as u32);
3930 assert_eq!(point.to_display_point(&map), display_point);
3931 assert_eq!(display_point.to_point(&map), point);
3932
3933 let point = MultiBufferPoint::new(1, "β\t".len() as u32);
3934 let display_point = DisplayPoint::new(DisplayRow(1), "β ".len() as u32);
3935 assert_eq!(point.to_display_point(&map), display_point);
3936 assert_eq!(display_point.to_point(&map), point,);
3937
3938 let point = MultiBufferPoint::new(2, "🏀β\t\t".len() as u32);
3939 let display_point = DisplayPoint::new(DisplayRow(2), "🏀β ".len() as u32);
3940 assert_eq!(point.to_display_point(&map), display_point);
3941 assert_eq!(display_point.to_point(&map), point,);
3942
3943 // Display points inside of expanded tabs
3944 assert_eq!(
3945 DisplayPoint::new(DisplayRow(0), "✅ ".len() as u32).to_point(&map),
3946 MultiBufferPoint::new(0, "✅\t".len() as u32),
3947 );
3948 assert_eq!(
3949 DisplayPoint::new(DisplayRow(0), "✅ ".len() as u32).to_point(&map),
3950 MultiBufferPoint::new(0, "✅".len() as u32),
3951 );
3952
3953 // Clipping display points inside of multi-byte characters
3954 assert_eq!(
3955 map.clip_point(
3956 DisplayPoint::new(DisplayRow(0), "✅".len() as u32 - 1),
3957 Left
3958 ),
3959 DisplayPoint::new(DisplayRow(0), 0)
3960 );
3961 assert_eq!(
3962 map.clip_point(
3963 DisplayPoint::new(DisplayRow(0), "✅".len() as u32 - 1),
3964 Bias::Right
3965 ),
3966 DisplayPoint::new(DisplayRow(0), "✅".len() as u32)
3967 );
3968 }
3969
3970 #[gpui::test]
3971 fn test_max_point(cx: &mut gpui::App) {
3972 init_test(cx, &|_| {});
3973
3974 let buffer = MultiBuffer::build_simple("aaa\n\t\tbbb", cx);
3975 let font_size = px(14.0);
3976 let map = cx.new(|cx| {
3977 DisplayMap::new(
3978 buffer.clone(),
3979 font("Helvetica"),
3980 font_size,
3981 None,
3982 1,
3983 1,
3984 FoldPlaceholder::test(),
3985 DiagnosticSeverity::Warning,
3986 cx,
3987 )
3988 });
3989 assert_eq!(
3990 map.update(cx, |map, cx| map.snapshot(cx)).max_point(),
3991 DisplayPoint::new(DisplayRow(1), 11)
3992 )
3993 }
3994
3995 fn syntax_chunks(
3996 rows: Range<DisplayRow>,
3997 map: &Entity<DisplayMap>,
3998 theme: &SyntaxTheme,
3999 cx: &mut App,
4000 ) -> Vec<(String, Option<Hsla>)> {
4001 chunks(rows, map, theme, cx)
4002 .into_iter()
4003 .map(|(text, color, _)| (text, color))
4004 .collect()
4005 }
4006
4007 fn chunks(
4008 rows: Range<DisplayRow>,
4009 map: &Entity<DisplayMap>,
4010 theme: &SyntaxTheme,
4011 cx: &mut App,
4012 ) -> Vec<(String, Option<Hsla>, Option<Hsla>)> {
4013 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
4014 let mut chunks: Vec<(String, Option<Hsla>, Option<Hsla>)> = Vec::new();
4015 for chunk in snapshot.chunks(rows, true, HighlightStyles::default()) {
4016 let syntax_color = chunk
4017 .syntax_highlight_id
4018 .and_then(|id| id.style(theme)?.color);
4019 let highlight_color = chunk.highlight_style.and_then(|style| style.color);
4020 if let Some((last_chunk, last_syntax_color, last_highlight_color)) = chunks.last_mut()
4021 && syntax_color == *last_syntax_color
4022 && highlight_color == *last_highlight_color
4023 {
4024 last_chunk.push_str(chunk.text);
4025 continue;
4026 }
4027 chunks.push((chunk.text.to_string(), syntax_color, highlight_color));
4028 }
4029 chunks
4030 }
4031
4032 fn init_test(cx: &mut App, f: &dyn Fn(&mut SettingsContent)) {
4033 let settings = SettingsStore::test(cx);
4034 cx.set_global(settings);
4035 crate::init(cx);
4036 theme::init(LoadThemes::JustBase, cx);
4037 cx.update_global::<SettingsStore, _>(|store, cx| {
4038 store.update_user_settings(cx, f);
4039 });
4040 }
4041
4042 #[gpui::test]
4043 fn test_isomorphic_display_point_ranges_for_buffer_range(cx: &mut gpui::TestAppContext) {
4044 cx.update(|cx| init_test(cx, &|_| {}));
4045
4046 let buffer = cx.new(|cx| Buffer::local("let x = 5;\n", cx));
4047 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
4048 let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
4049
4050 let font_size = px(14.0);
4051 let map = cx.new(|cx| {
4052 DisplayMap::new(
4053 buffer.clone(),
4054 font("Helvetica"),
4055 font_size,
4056 None,
4057 1,
4058 1,
4059 FoldPlaceholder::test(),
4060 DiagnosticSeverity::Warning,
4061 cx,
4062 )
4063 });
4064
4065 // Without inlays, a buffer range maps to a single display range.
4066 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
4067 let ranges = snapshot.isomorphic_display_point_ranges_for_buffer_range(
4068 MultiBufferOffset(4)..MultiBufferOffset(9),
4069 );
4070 assert_eq!(ranges.len(), 1);
4071 // "x = 5" is columns 4..9 with no inlays shifting anything.
4072 assert_eq!(ranges[0].start, DisplayPoint::new(DisplayRow(0), 4));
4073 assert_eq!(ranges[0].end, DisplayPoint::new(DisplayRow(0), 9));
4074
4075 // Insert a 4-char inlay hint ": i32" at buffer offset 5 (after "x").
4076 map.update(cx, |map, cx| {
4077 map.splice_inlays(
4078 &[],
4079 vec![Inlay::mock_hint(
4080 0,
4081 buffer_snapshot.anchor_after(MultiBufferOffset(5)),
4082 ": i32",
4083 )],
4084 cx,
4085 );
4086 });
4087 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
4088 assert_eq!(snapshot.text(), "let x: i32 = 5;\n");
4089
4090 // A buffer range [4..9] ("x = 5") now spans across the inlay.
4091 // It should be split into two display ranges that skip the inlay text.
4092 let ranges = snapshot.isomorphic_display_point_ranges_for_buffer_range(
4093 MultiBufferOffset(4)..MultiBufferOffset(9),
4094 );
4095 assert_eq!(
4096 ranges.len(),
4097 2,
4098 "expected the range to be split around the inlay, got: {:?}",
4099 ranges,
4100 );
4101 // First sub-range: buffer [4, 5) → "x" at display columns 4..5
4102 assert_eq!(ranges[0].start, DisplayPoint::new(DisplayRow(0), 4));
4103 assert_eq!(ranges[0].end, DisplayPoint::new(DisplayRow(0), 5));
4104 // Second sub-range: buffer [5, 9) → " = 5" at display columns 10..14
4105 // (shifted right by the 5-char ": i32" inlay)
4106 assert_eq!(ranges[1].start, DisplayPoint::new(DisplayRow(0), 10));
4107 assert_eq!(ranges[1].end, DisplayPoint::new(DisplayRow(0), 14));
4108
4109 // A range entirely before the inlay is not split.
4110 let ranges = snapshot.isomorphic_display_point_ranges_for_buffer_range(
4111 MultiBufferOffset(0)..MultiBufferOffset(5),
4112 );
4113 assert_eq!(ranges.len(), 1);
4114 assert_eq!(ranges[0].start, DisplayPoint::new(DisplayRow(0), 0));
4115 assert_eq!(ranges[0].end, DisplayPoint::new(DisplayRow(0), 5));
4116
4117 // A range entirely after the inlay is not split.
4118 let ranges = snapshot.isomorphic_display_point_ranges_for_buffer_range(
4119 MultiBufferOffset(5)..MultiBufferOffset(9),
4120 );
4121 assert_eq!(ranges.len(), 1);
4122 assert_eq!(ranges[0].start, DisplayPoint::new(DisplayRow(0), 10));
4123 assert_eq!(ranges[0].end, DisplayPoint::new(DisplayRow(0), 14));
4124 }
4125
4126 #[test]
4127 fn test_highlight_invisibles_preserves_compound_emojis() {
4128 let editor_style = EditorStyle::default();
4129
4130 let pilot_emoji = "🧑\u{200d}✈\u{fe0f}";
4131 let chunk = HighlightedChunk {
4132 text: pilot_emoji,
4133 style: None,
4134 is_tab: false,
4135 is_inlay: false,
4136 replacement: None,
4137 };
4138
4139 let chunks: Vec<_> = chunk
4140 .highlight_invisibles(&editor_style)
4141 .map(|chunk| chunk.text.to_string())
4142 .collect();
4143
4144 assert_eq!(
4145 chunks.concat(),
4146 pilot_emoji,
4147 "all text bytes must be preserved"
4148 );
4149 assert_eq!(
4150 chunks.len(),
4151 1,
4152 "compound emoji should not be split into multiple chunks, got: {:?}",
4153 chunks,
4154 );
4155 }
4156}