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