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