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