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