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