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