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