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