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 if self
2324 .buffer_snapshot()
2325 .language_scope_at(Point::new(row, 0))
2326 .is_some_and(|scope| {
2327 matches!(
2328 scope.override_name(),
2329 Some("string") | Some("comment") | Some("comment.inclusive")
2330 )
2331 })
2332 {
2333 continue;
2334 }
2335
2336 let prev_row = row - 1;
2337 end = Some(Point::new(
2338 prev_row,
2339 self.buffer_snapshot().line_len(MultiBufferRow(prev_row)),
2340 ));
2341 break;
2342 }
2343 }
2344
2345 let mut row_before_line_breaks = end.unwrap_or(max_point);
2346 while row_before_line_breaks.row > start.row
2347 && self
2348 .buffer_snapshot()
2349 .is_line_blank(MultiBufferRow(row_before_line_breaks.row))
2350 {
2351 row_before_line_breaks.row -= 1;
2352 }
2353
2354 row_before_line_breaks = Point::new(
2355 row_before_line_breaks.row,
2356 self.buffer_snapshot()
2357 .line_len(MultiBufferRow(row_before_line_breaks.row)),
2358 );
2359
2360 Some(Crease::Inline {
2361 range: start..row_before_line_breaks,
2362 placeholder: self.fold_placeholder.clone(),
2363 render_toggle: None,
2364 render_trailer: None,
2365 metadata: None,
2366 })
2367 } else {
2368 None
2369 }
2370 }
2371
2372 #[cfg(any(test, feature = "test-support"))]
2373 #[instrument(skip_all)]
2374 pub fn text_highlight_ranges(
2375 &self,
2376 key: HighlightKey,
2377 ) -> Option<Arc<(HighlightStyle, Vec<Range<Anchor>>)>> {
2378 self.text_highlights.get(&key).cloned()
2379 }
2380
2381 #[cfg(any(test, feature = "test-support"))]
2382 #[instrument(skip_all)]
2383 pub fn all_text_highlight_ranges(
2384 &self,
2385 f: &dyn Fn(&HighlightKey) -> bool,
2386 ) -> Vec<(gpui::Hsla, Range<Point>)> {
2387 use itertools::Itertools;
2388
2389 self.text_highlights
2390 .iter()
2391 .filter(|(key, _)| f(key))
2392 .map(|(_, value)| value.clone())
2393 .flat_map(|ranges| {
2394 ranges
2395 .1
2396 .iter()
2397 .flat_map(|range| {
2398 Some((ranges.0.color?, range.to_point(self.buffer_snapshot())))
2399 })
2400 .collect::<Vec<_>>()
2401 })
2402 .sorted_by_key(|(_, range)| range.start)
2403 .collect()
2404 }
2405
2406 #[allow(unused)]
2407 #[cfg(any(test, feature = "test-support"))]
2408 pub(crate) fn inlay_highlights(
2409 &self,
2410 key: HighlightKey,
2411 ) -> Option<&TreeMap<InlayId, (HighlightStyle, InlayHighlight)>> {
2412 self.inlay_highlights.get(&key)
2413 }
2414
2415 pub fn buffer_header_height(&self) -> u32 {
2416 self.block_snapshot.buffer_header_height
2417 }
2418
2419 pub fn excerpt_header_height(&self) -> u32 {
2420 self.block_snapshot.excerpt_header_height
2421 }
2422
2423 /// Given a `DisplayPoint`, returns another `DisplayPoint` corresponding to
2424 /// the start of the buffer row that is a given number of buffer rows away
2425 /// from the provided point.
2426 ///
2427 /// This moves by buffer rows instead of display rows, a distinction that is
2428 /// important when soft wrapping is enabled.
2429 #[instrument(skip_all)]
2430 pub fn start_of_relative_buffer_row(&self, point: DisplayPoint, times: isize) -> DisplayPoint {
2431 let start = self.display_point_to_fold_point(point, Bias::Left);
2432 let target = start.row() as isize + times;
2433 let new_row = (target.max(0) as u32).min(self.fold_snapshot().max_point().row());
2434
2435 self.clip_point(
2436 self.fold_point_to_display_point(
2437 self.fold_snapshot()
2438 .clip_point(FoldPoint::new(new_row, 0), Bias::Right),
2439 ),
2440 Bias::Right,
2441 )
2442 }
2443}
2444
2445impl std::ops::Deref for DisplaySnapshot {
2446 type Target = BlockSnapshot;
2447
2448 fn deref(&self) -> &Self::Target {
2449 &self.block_snapshot
2450 }
2451}
2452
2453/// A zero-indexed point in a text buffer consisting of a row and column adjusted for inserted blocks.
2454#[derive(Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq)]
2455pub struct DisplayPoint(BlockPoint);
2456
2457impl Debug for DisplayPoint {
2458 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2459 f.write_fmt(format_args!(
2460 "DisplayPoint({}, {})",
2461 self.row().0,
2462 self.column()
2463 ))
2464 }
2465}
2466
2467impl Add for DisplayPoint {
2468 type Output = Self;
2469
2470 fn add(self, other: Self) -> Self::Output {
2471 DisplayPoint(BlockPoint(self.0.0 + other.0.0))
2472 }
2473}
2474
2475impl Sub for DisplayPoint {
2476 type Output = Self;
2477
2478 fn sub(self, other: Self) -> Self::Output {
2479 DisplayPoint(BlockPoint(self.0.0 - other.0.0))
2480 }
2481}
2482
2483#[derive(Debug, Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq, Deserialize, Hash)]
2484#[serde(transparent)]
2485pub struct DisplayRow(pub u32);
2486
2487impl DisplayRow {
2488 pub(crate) fn as_display_point(&self) -> DisplayPoint {
2489 DisplayPoint::new(*self, 0)
2490 }
2491}
2492
2493impl Add<DisplayRow> for DisplayRow {
2494 type Output = Self;
2495
2496 fn add(self, other: Self) -> Self::Output {
2497 DisplayRow(self.0 + other.0)
2498 }
2499}
2500
2501impl Add<u32> for DisplayRow {
2502 type Output = Self;
2503
2504 fn add(self, other: u32) -> Self::Output {
2505 DisplayRow(self.0 + other)
2506 }
2507}
2508
2509impl Sub<DisplayRow> for DisplayRow {
2510 type Output = Self;
2511
2512 fn sub(self, other: Self) -> Self::Output {
2513 DisplayRow(self.0 - other.0)
2514 }
2515}
2516
2517impl Sub<u32> for DisplayRow {
2518 type Output = Self;
2519
2520 fn sub(self, other: u32) -> Self::Output {
2521 DisplayRow(self.0 - other)
2522 }
2523}
2524
2525impl DisplayPoint {
2526 pub fn new(row: DisplayRow, column: u32) -> Self {
2527 Self(BlockPoint(Point::new(row.0, column)))
2528 }
2529
2530 pub fn zero() -> Self {
2531 Self::new(DisplayRow(0), 0)
2532 }
2533
2534 pub fn is_zero(&self) -> bool {
2535 self.0.is_zero()
2536 }
2537
2538 pub fn row(self) -> DisplayRow {
2539 DisplayRow(self.0.row)
2540 }
2541
2542 pub fn column(self) -> u32 {
2543 self.0.column
2544 }
2545
2546 pub fn row_mut(&mut self) -> &mut u32 {
2547 &mut self.0.row
2548 }
2549
2550 pub fn column_mut(&mut self) -> &mut u32 {
2551 &mut self.0.column
2552 }
2553
2554 pub fn to_point(self, map: &DisplaySnapshot) -> Point {
2555 map.display_point_to_point(self, Bias::Left)
2556 }
2557
2558 pub fn to_offset(self, map: &DisplaySnapshot, bias: Bias) -> MultiBufferOffset {
2559 let wrap_point = map.block_snapshot.to_wrap_point(self.0, bias);
2560 let tab_point = map.wrap_snapshot().to_tab_point(wrap_point);
2561 let fold_point = map
2562 .tab_snapshot()
2563 .tab_point_to_fold_point(tab_point, bias)
2564 .0;
2565 let inlay_point = fold_point.to_inlay_point(map.fold_snapshot());
2566 map.inlay_snapshot()
2567 .to_buffer_offset(map.inlay_snapshot().to_offset(inlay_point))
2568 }
2569}
2570
2571impl ToDisplayPoint for MultiBufferOffset {
2572 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
2573 map.point_to_display_point(self.to_point(map.buffer_snapshot()), Bias::Left)
2574 }
2575}
2576
2577impl ToDisplayPoint for MultiBufferOffsetUtf16 {
2578 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
2579 self.to_offset(map.buffer_snapshot()).to_display_point(map)
2580 }
2581}
2582
2583impl ToDisplayPoint for Point {
2584 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
2585 map.point_to_display_point(*self, Bias::Left)
2586 }
2587}
2588
2589impl ToDisplayPoint for Anchor {
2590 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
2591 self.to_point(map.buffer_snapshot()).to_display_point(map)
2592 }
2593}
2594
2595#[cfg(test)]
2596pub mod tests {
2597 use super::*;
2598 use crate::{
2599 movement,
2600 test::{marked_display_snapshot, test_font},
2601 };
2602 use Bias::*;
2603 use block_map::BlockPlacement;
2604 use gpui::{
2605 App, AppContext as _, BorrowAppContext, Element, Hsla, Rgba, div, font, observe, px,
2606 };
2607 use language::{
2608 Buffer, Diagnostic, DiagnosticEntry, DiagnosticSet, Language, LanguageConfig,
2609 LanguageMatcher,
2610 };
2611 use lsp::LanguageServerId;
2612
2613 use rand::{Rng, prelude::*};
2614 use settings::{SettingsContent, SettingsStore};
2615 use smol::stream::StreamExt;
2616 use std::{env, sync::Arc};
2617 use text::PointUtf16;
2618 use theme::{LoadThemes, SyntaxTheme};
2619 use unindent::Unindent as _;
2620 use util::test::{marked_text_ranges, sample_text};
2621
2622 #[gpui::test(iterations = 100)]
2623 async fn test_random_display_map(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
2624 cx.background_executor.set_block_on_ticks(0..=50);
2625 let operations = env::var("OPERATIONS")
2626 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2627 .unwrap_or(10);
2628
2629 let mut tab_size = rng.random_range(1..=4);
2630 let buffer_start_excerpt_header_height = rng.random_range(1..=5);
2631 let excerpt_header_height = rng.random_range(1..=5);
2632 let font_size = px(14.0);
2633 let max_wrap_width = 300.0;
2634 let mut wrap_width = if rng.random_bool(0.1) {
2635 None
2636 } else {
2637 Some(px(rng.random_range(0.0..=max_wrap_width)))
2638 };
2639
2640 log::info!("tab size: {}", tab_size);
2641 log::info!("wrap width: {:?}", wrap_width);
2642
2643 cx.update(|cx| {
2644 init_test(cx, &|s| {
2645 s.project.all_languages.defaults.tab_size = NonZeroU32::new(tab_size)
2646 });
2647 });
2648
2649 let buffer = cx.update(|cx| {
2650 if rng.random() {
2651 let len = rng.random_range(0..10);
2652 let text = util::RandomCharIter::new(&mut rng)
2653 .take(len)
2654 .collect::<String>();
2655 MultiBuffer::build_simple(&text, cx)
2656 } else {
2657 MultiBuffer::build_random(&mut rng, cx)
2658 }
2659 });
2660
2661 let font = test_font();
2662 let map = cx.new(|cx| {
2663 DisplayMap::new(
2664 buffer.clone(),
2665 font,
2666 font_size,
2667 wrap_width,
2668 buffer_start_excerpt_header_height,
2669 excerpt_header_height,
2670 FoldPlaceholder::test(),
2671 DiagnosticSeverity::Warning,
2672 cx,
2673 )
2674 });
2675 let mut notifications = observe(&map, cx);
2676 let mut fold_count = 0;
2677 let mut blocks = Vec::new();
2678
2679 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
2680 log::info!("buffer text: {:?}", snapshot.buffer_snapshot().text());
2681 log::info!("fold text: {:?}", snapshot.fold_snapshot().text());
2682 log::info!("tab text: {:?}", snapshot.tab_snapshot().text());
2683 log::info!("wrap text: {:?}", snapshot.wrap_snapshot().text());
2684 log::info!("block text: {:?}", snapshot.block_snapshot.text());
2685 log::info!("display text: {:?}", snapshot.text());
2686
2687 for _i in 0..operations {
2688 match rng.random_range(0..100) {
2689 0..=19 => {
2690 wrap_width = if rng.random_bool(0.2) {
2691 None
2692 } else {
2693 Some(px(rng.random_range(0.0..=max_wrap_width)))
2694 };
2695 log::info!("setting wrap width to {:?}", wrap_width);
2696 map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
2697 }
2698 20..=29 => {
2699 let mut tab_sizes = vec![1, 2, 3, 4];
2700 tab_sizes.remove((tab_size - 1) as usize);
2701 tab_size = *tab_sizes.choose(&mut rng).unwrap();
2702 log::info!("setting tab size to {:?}", tab_size);
2703 cx.update(|cx| {
2704 cx.update_global::<SettingsStore, _>(|store, cx| {
2705 store.update_user_settings(cx, |s| {
2706 s.project.all_languages.defaults.tab_size =
2707 NonZeroU32::new(tab_size);
2708 });
2709 });
2710 });
2711 }
2712 30..=44 => {
2713 map.update(cx, |map, cx| {
2714 if rng.random() || blocks.is_empty() {
2715 let snapshot = map.snapshot(cx);
2716 let buffer = snapshot.buffer_snapshot();
2717 let block_properties = (0..rng.random_range(1..=1))
2718 .map(|_| {
2719 let position = buffer.anchor_after(buffer.clip_offset(
2720 rng.random_range(MultiBufferOffset(0)..=buffer.len()),
2721 Bias::Left,
2722 ));
2723
2724 let placement = if rng.random() {
2725 BlockPlacement::Above(position)
2726 } else {
2727 BlockPlacement::Below(position)
2728 };
2729 let height = rng.random_range(1..5);
2730 log::info!(
2731 "inserting block {:?} with height {}",
2732 placement.as_ref().map(|p| p.to_point(&buffer)),
2733 height
2734 );
2735 let priority = rng.random_range(1..100);
2736 BlockProperties {
2737 placement,
2738 style: BlockStyle::Fixed,
2739 height: Some(height),
2740 render: Arc::new(|_| div().into_any()),
2741 priority,
2742 }
2743 })
2744 .collect::<Vec<_>>();
2745 blocks.extend(map.insert_blocks(block_properties, cx));
2746 } else {
2747 blocks.shuffle(&mut rng);
2748 let remove_count = rng.random_range(1..=4.min(blocks.len()));
2749 let block_ids_to_remove = (0..remove_count)
2750 .map(|_| blocks.remove(rng.random_range(0..blocks.len())))
2751 .collect();
2752 log::info!("removing block ids {:?}", block_ids_to_remove);
2753 map.remove_blocks(block_ids_to_remove, cx);
2754 }
2755 });
2756 }
2757 45..=79 => {
2758 let mut ranges = Vec::new();
2759 for _ in 0..rng.random_range(1..=3) {
2760 buffer.read_with(cx, |buffer, cx| {
2761 let buffer = buffer.read(cx);
2762 let end = buffer.clip_offset(
2763 rng.random_range(MultiBufferOffset(0)..=buffer.len()),
2764 Right,
2765 );
2766 let start = buffer
2767 .clip_offset(rng.random_range(MultiBufferOffset(0)..=end), Left);
2768 ranges.push(start..end);
2769 });
2770 }
2771
2772 if rng.random() && fold_count > 0 {
2773 log::info!("unfolding ranges: {:?}", ranges);
2774 map.update(cx, |map, cx| {
2775 map.unfold_intersecting(ranges, true, cx);
2776 });
2777 } else {
2778 log::info!("folding ranges: {:?}", ranges);
2779 map.update(cx, |map, cx| {
2780 map.fold(
2781 ranges
2782 .into_iter()
2783 .map(|range| Crease::simple(range, FoldPlaceholder::test()))
2784 .collect(),
2785 cx,
2786 );
2787 });
2788 }
2789 }
2790 _ => {
2791 buffer.update(cx, |buffer, cx| buffer.randomly_mutate(&mut rng, 5, cx));
2792 }
2793 }
2794
2795 if map.read_with(cx, |map, cx| map.is_rewrapping(cx)) {
2796 notifications.next().await.unwrap();
2797 }
2798
2799 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
2800 fold_count = snapshot.fold_count();
2801 log::info!("buffer text: {:?}", snapshot.buffer_snapshot().text());
2802 log::info!("fold text: {:?}", snapshot.fold_snapshot().text());
2803 log::info!("tab text: {:?}", snapshot.tab_snapshot().text());
2804 log::info!("wrap text: {:?}", snapshot.wrap_snapshot().text());
2805 log::info!("block text: {:?}", snapshot.block_snapshot.text());
2806 log::info!("display text: {:?}", snapshot.text());
2807
2808 // Line boundaries
2809 let buffer = snapshot.buffer_snapshot();
2810 for _ in 0..5 {
2811 let row = rng.random_range(0..=buffer.max_point().row);
2812 let column = rng.random_range(0..=buffer.line_len(MultiBufferRow(row)));
2813 let point = buffer.clip_point(Point::new(row, column), Left);
2814
2815 let (prev_buffer_bound, prev_display_bound) = snapshot.prev_line_boundary(point);
2816 let (next_buffer_bound, next_display_bound) = snapshot.next_line_boundary(point);
2817
2818 assert!(prev_buffer_bound <= point);
2819 assert!(next_buffer_bound >= point);
2820 assert_eq!(prev_buffer_bound.column, 0);
2821 assert_eq!(prev_display_bound.column(), 0);
2822 if next_buffer_bound < buffer.max_point() {
2823 assert_eq!(buffer.chars_at(next_buffer_bound).next(), Some('\n'));
2824 }
2825
2826 assert_eq!(
2827 prev_display_bound,
2828 prev_buffer_bound.to_display_point(&snapshot),
2829 "row boundary before {:?}. reported buffer row boundary: {:?}",
2830 point,
2831 prev_buffer_bound
2832 );
2833 assert_eq!(
2834 next_display_bound,
2835 next_buffer_bound.to_display_point(&snapshot),
2836 "display row boundary after {:?}. reported buffer row boundary: {:?}",
2837 point,
2838 next_buffer_bound
2839 );
2840 assert_eq!(
2841 prev_buffer_bound,
2842 prev_display_bound.to_point(&snapshot),
2843 "row boundary before {:?}. reported display row boundary: {:?}",
2844 point,
2845 prev_display_bound
2846 );
2847 assert_eq!(
2848 next_buffer_bound,
2849 next_display_bound.to_point(&snapshot),
2850 "row boundary after {:?}. reported display row boundary: {:?}",
2851 point,
2852 next_display_bound
2853 );
2854 }
2855
2856 // Movement
2857 let min_point = snapshot.clip_point(DisplayPoint::new(DisplayRow(0), 0), Left);
2858 let max_point = snapshot.clip_point(snapshot.max_point(), Right);
2859 for _ in 0..5 {
2860 let row = rng.random_range(0..=snapshot.max_point().row().0);
2861 let column = rng.random_range(0..=snapshot.line_len(DisplayRow(row)));
2862 let point = snapshot.clip_point(DisplayPoint::new(DisplayRow(row), column), Left);
2863
2864 log::info!("Moving from point {:?}", point);
2865
2866 let moved_right = movement::right(&snapshot, point);
2867 log::info!("Right {:?}", moved_right);
2868 if point < max_point {
2869 assert!(moved_right > point);
2870 if point.column() == snapshot.line_len(point.row())
2871 || snapshot.soft_wrap_indent(point.row()).is_some()
2872 && point.column() == snapshot.line_len(point.row()) - 1
2873 {
2874 assert!(moved_right.row() > point.row());
2875 }
2876 } else {
2877 assert_eq!(moved_right, point);
2878 }
2879
2880 let moved_left = movement::left(&snapshot, point);
2881 log::info!("Left {:?}", moved_left);
2882 if point > min_point {
2883 assert!(moved_left < point);
2884 if point.column() == 0 {
2885 assert!(moved_left.row() < point.row());
2886 }
2887 } else {
2888 assert_eq!(moved_left, point);
2889 }
2890 }
2891 }
2892 }
2893
2894 #[gpui::test(retries = 5)]
2895 async fn test_soft_wraps(cx: &mut gpui::TestAppContext) {
2896 cx.background_executor
2897 .set_block_on_ticks(usize::MAX..=usize::MAX);
2898 cx.update(|cx| {
2899 init_test(cx, &|_| {});
2900 });
2901
2902 let mut cx = crate::test::editor_test_context::EditorTestContext::new(cx).await;
2903 let editor = cx.editor.clone();
2904 let window = cx.window;
2905
2906 _ = cx.update_window(window, |_, window, cx| {
2907 let text_layout_details =
2908 editor.update(cx, |editor, cx| editor.text_layout_details(window, cx));
2909
2910 let font_size = px(12.0);
2911 let wrap_width = Some(px(96.));
2912
2913 let text = "one two three four five\nsix seven eight";
2914 let buffer = MultiBuffer::build_simple(text, cx);
2915 let map = cx.new(|cx| {
2916 DisplayMap::new(
2917 buffer.clone(),
2918 font("Helvetica"),
2919 font_size,
2920 wrap_width,
2921 1,
2922 1,
2923 FoldPlaceholder::test(),
2924 DiagnosticSeverity::Warning,
2925 cx,
2926 )
2927 });
2928
2929 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
2930 assert_eq!(
2931 snapshot.text_chunks(DisplayRow(0)).collect::<String>(),
2932 "one two \nthree four \nfive\nsix seven \neight"
2933 );
2934 assert_eq!(
2935 snapshot.clip_point(DisplayPoint::new(DisplayRow(0), 8), Bias::Left),
2936 DisplayPoint::new(DisplayRow(0), 7)
2937 );
2938 assert_eq!(
2939 snapshot.clip_point(DisplayPoint::new(DisplayRow(0), 8), Bias::Right),
2940 DisplayPoint::new(DisplayRow(1), 0)
2941 );
2942 assert_eq!(
2943 movement::right(&snapshot, DisplayPoint::new(DisplayRow(0), 7)),
2944 DisplayPoint::new(DisplayRow(1), 0)
2945 );
2946 assert_eq!(
2947 movement::left(&snapshot, DisplayPoint::new(DisplayRow(1), 0)),
2948 DisplayPoint::new(DisplayRow(0), 7)
2949 );
2950
2951 let x = snapshot
2952 .x_for_display_point(DisplayPoint::new(DisplayRow(1), 10), &text_layout_details);
2953 assert_eq!(
2954 movement::up(
2955 &snapshot,
2956 DisplayPoint::new(DisplayRow(1), 10),
2957 language::SelectionGoal::None,
2958 false,
2959 &text_layout_details,
2960 ),
2961 (
2962 DisplayPoint::new(DisplayRow(0), 7),
2963 language::SelectionGoal::HorizontalPosition(f64::from(x))
2964 )
2965 );
2966 assert_eq!(
2967 movement::down(
2968 &snapshot,
2969 DisplayPoint::new(DisplayRow(0), 7),
2970 language::SelectionGoal::HorizontalPosition(f64::from(x)),
2971 false,
2972 &text_layout_details
2973 ),
2974 (
2975 DisplayPoint::new(DisplayRow(1), 10),
2976 language::SelectionGoal::HorizontalPosition(f64::from(x))
2977 )
2978 );
2979 assert_eq!(
2980 movement::down(
2981 &snapshot,
2982 DisplayPoint::new(DisplayRow(1), 10),
2983 language::SelectionGoal::HorizontalPosition(f64::from(x)),
2984 false,
2985 &text_layout_details
2986 ),
2987 (
2988 DisplayPoint::new(DisplayRow(2), 4),
2989 language::SelectionGoal::HorizontalPosition(f64::from(x))
2990 )
2991 );
2992
2993 let ix = MultiBufferOffset(snapshot.buffer_snapshot().text().find("seven").unwrap());
2994 buffer.update(cx, |buffer, cx| {
2995 buffer.edit([(ix..ix, "and ")], None, cx);
2996 });
2997
2998 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
2999 assert_eq!(
3000 snapshot.text_chunks(DisplayRow(1)).collect::<String>(),
3001 "three four \nfive\nsix and \nseven eight"
3002 );
3003
3004 // Re-wrap on font size changes
3005 map.update(cx, |map, cx| {
3006 map.set_font(font("Helvetica"), font_size + Pixels::from(3.), cx)
3007 });
3008
3009 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
3010 assert_eq!(
3011 snapshot.text_chunks(DisplayRow(1)).collect::<String>(),
3012 "three \nfour five\nsix and \nseven \neight"
3013 )
3014 });
3015 }
3016
3017 #[gpui::test]
3018 fn test_text_chunks(cx: &mut gpui::App) {
3019 init_test(cx, &|_| {});
3020
3021 let text = sample_text(6, 6, 'a');
3022 let buffer = MultiBuffer::build_simple(&text, cx);
3023
3024 let font_size = px(14.0);
3025 let map = cx.new(|cx| {
3026 DisplayMap::new(
3027 buffer.clone(),
3028 font("Helvetica"),
3029 font_size,
3030 None,
3031 1,
3032 1,
3033 FoldPlaceholder::test(),
3034 DiagnosticSeverity::Warning,
3035 cx,
3036 )
3037 });
3038
3039 buffer.update(cx, |buffer, cx| {
3040 buffer.edit(
3041 vec![
3042 (
3043 MultiBufferPoint::new(1, 0)..MultiBufferPoint::new(1, 0),
3044 "\t",
3045 ),
3046 (
3047 MultiBufferPoint::new(1, 1)..MultiBufferPoint::new(1, 1),
3048 "\t",
3049 ),
3050 (
3051 MultiBufferPoint::new(2, 1)..MultiBufferPoint::new(2, 1),
3052 "\t",
3053 ),
3054 ],
3055 None,
3056 cx,
3057 )
3058 });
3059
3060 assert_eq!(
3061 map.update(cx, |map, cx| map.snapshot(cx))
3062 .text_chunks(DisplayRow(1))
3063 .collect::<String>()
3064 .lines()
3065 .next(),
3066 Some(" b bbbbb")
3067 );
3068 assert_eq!(
3069 map.update(cx, |map, cx| map.snapshot(cx))
3070 .text_chunks(DisplayRow(2))
3071 .collect::<String>()
3072 .lines()
3073 .next(),
3074 Some("c ccccc")
3075 );
3076 }
3077
3078 #[gpui::test]
3079 fn test_inlays_with_newlines_after_blocks(cx: &mut gpui::TestAppContext) {
3080 cx.update(|cx| init_test(cx, &|_| {}));
3081
3082 let buffer = cx.new(|cx| Buffer::local("a", cx));
3083 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
3084 let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
3085
3086 let font_size = px(14.0);
3087 let map = cx.new(|cx| {
3088 DisplayMap::new(
3089 buffer.clone(),
3090 font("Helvetica"),
3091 font_size,
3092 None,
3093 1,
3094 1,
3095 FoldPlaceholder::test(),
3096 DiagnosticSeverity::Warning,
3097 cx,
3098 )
3099 });
3100
3101 map.update(cx, |map, cx| {
3102 map.insert_blocks(
3103 [BlockProperties {
3104 placement: BlockPlacement::Above(
3105 buffer_snapshot.anchor_before(Point::new(0, 0)),
3106 ),
3107 height: Some(2),
3108 style: BlockStyle::Sticky,
3109 render: Arc::new(|_| div().into_any()),
3110 priority: 0,
3111 }],
3112 cx,
3113 );
3114 });
3115 map.update(cx, |m, cx| assert_eq!(m.snapshot(cx).text(), "\n\na"));
3116
3117 map.update(cx, |map, cx| {
3118 map.splice_inlays(
3119 &[],
3120 vec![Inlay::edit_prediction(
3121 0,
3122 buffer_snapshot.anchor_after(MultiBufferOffset(0)),
3123 "\n",
3124 )],
3125 cx,
3126 );
3127 });
3128 map.update(cx, |m, cx| assert_eq!(m.snapshot(cx).text(), "\n\n\na"));
3129
3130 // Regression test: updating the display map does not crash when a
3131 // block is immediately followed by a multi-line inlay.
3132 buffer.update(cx, |buffer, cx| {
3133 buffer.edit(
3134 [(MultiBufferOffset(1)..MultiBufferOffset(1), "b")],
3135 None,
3136 cx,
3137 );
3138 });
3139 map.update(cx, |m, cx| assert_eq!(m.snapshot(cx).text(), "\n\n\nab"));
3140 }
3141
3142 #[gpui::test]
3143 async fn test_chunks(cx: &mut gpui::TestAppContext) {
3144 let text = r#"
3145 fn outer() {}
3146
3147 mod module {
3148 fn inner() {}
3149 }"#
3150 .unindent();
3151
3152 let theme =
3153 SyntaxTheme::new_test(vec![("mod.body", Hsla::red()), ("fn.name", Hsla::blue())]);
3154 let language = Arc::new(
3155 Language::new(
3156 LanguageConfig {
3157 name: "Test".into(),
3158 matcher: LanguageMatcher {
3159 path_suffixes: vec![".test".to_string()],
3160 ..Default::default()
3161 },
3162 ..Default::default()
3163 },
3164 Some(tree_sitter_rust::LANGUAGE.into()),
3165 )
3166 .with_highlights_query(
3167 r#"
3168 (mod_item name: (identifier) body: _ @mod.body)
3169 (function_item name: (identifier) @fn.name)
3170 "#,
3171 )
3172 .unwrap(),
3173 );
3174 language.set_theme(&theme);
3175
3176 cx.update(|cx| {
3177 init_test(cx, &|s| {
3178 s.project.all_languages.defaults.tab_size = Some(2.try_into().unwrap())
3179 })
3180 });
3181
3182 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
3183 cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
3184 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
3185
3186 let font_size = px(14.0);
3187
3188 let map = cx.new(|cx| {
3189 DisplayMap::new(
3190 buffer,
3191 font("Helvetica"),
3192 font_size,
3193 None,
3194 1,
3195 1,
3196 FoldPlaceholder::test(),
3197 DiagnosticSeverity::Warning,
3198 cx,
3199 )
3200 });
3201 assert_eq!(
3202 cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(5), &map, &theme, cx)),
3203 vec![
3204 ("fn ".to_string(), None),
3205 ("outer".to_string(), Some(Hsla::blue())),
3206 ("() {}\n\nmod module ".to_string(), None),
3207 ("{\n fn ".to_string(), Some(Hsla::red())),
3208 ("inner".to_string(), Some(Hsla::blue())),
3209 ("() {}\n}".to_string(), Some(Hsla::red())),
3210 ]
3211 );
3212 assert_eq!(
3213 cx.update(|cx| syntax_chunks(DisplayRow(3)..DisplayRow(5), &map, &theme, cx)),
3214 vec![
3215 (" fn ".to_string(), Some(Hsla::red())),
3216 ("inner".to_string(), Some(Hsla::blue())),
3217 ("() {}\n}".to_string(), Some(Hsla::red())),
3218 ]
3219 );
3220
3221 map.update(cx, |map, cx| {
3222 map.fold(
3223 vec![Crease::simple(
3224 MultiBufferPoint::new(0, 6)..MultiBufferPoint::new(3, 2),
3225 FoldPlaceholder::test(),
3226 )],
3227 cx,
3228 )
3229 });
3230 assert_eq!(
3231 cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(2), &map, &theme, cx)),
3232 vec![
3233 ("fn ".to_string(), None),
3234 ("out".to_string(), Some(Hsla::blue())),
3235 ("⋯".to_string(), None),
3236 (" fn ".to_string(), Some(Hsla::red())),
3237 ("inner".to_string(), Some(Hsla::blue())),
3238 ("() {}\n}".to_string(), Some(Hsla::red())),
3239 ]
3240 );
3241 }
3242
3243 #[gpui::test]
3244 async fn test_chunks_with_syntax_highlighting_across_blocks(cx: &mut gpui::TestAppContext) {
3245 cx.background_executor
3246 .set_block_on_ticks(usize::MAX..=usize::MAX);
3247
3248 let text = r#"
3249 const A: &str = "
3250 one
3251 two
3252 three
3253 ";
3254 const B: &str = "four";
3255 "#
3256 .unindent();
3257
3258 let theme = SyntaxTheme::new_test(vec![
3259 ("string", Hsla::red()),
3260 ("punctuation", Hsla::blue()),
3261 ("keyword", Hsla::green()),
3262 ]);
3263 let language = Arc::new(
3264 Language::new(
3265 LanguageConfig {
3266 name: "Rust".into(),
3267 ..Default::default()
3268 },
3269 Some(tree_sitter_rust::LANGUAGE.into()),
3270 )
3271 .with_highlights_query(
3272 r#"
3273 (string_literal) @string
3274 "const" @keyword
3275 [":" ";"] @punctuation
3276 "#,
3277 )
3278 .unwrap(),
3279 );
3280 language.set_theme(&theme);
3281
3282 cx.update(|cx| init_test(cx, &|_| {}));
3283
3284 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
3285 cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
3286 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
3287 let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
3288
3289 let map = cx.new(|cx| {
3290 DisplayMap::new(
3291 buffer,
3292 font("Courier"),
3293 px(16.0),
3294 None,
3295 1,
3296 1,
3297 FoldPlaceholder::test(),
3298 DiagnosticSeverity::Warning,
3299 cx,
3300 )
3301 });
3302
3303 // Insert two blocks in the middle of a multi-line string literal.
3304 // The second block has zero height.
3305 map.update(cx, |map, cx| {
3306 map.insert_blocks(
3307 [
3308 BlockProperties {
3309 placement: BlockPlacement::Below(
3310 buffer_snapshot.anchor_before(Point::new(1, 0)),
3311 ),
3312 height: Some(1),
3313 style: BlockStyle::Sticky,
3314 render: Arc::new(|_| div().into_any()),
3315 priority: 0,
3316 },
3317 BlockProperties {
3318 placement: BlockPlacement::Below(
3319 buffer_snapshot.anchor_before(Point::new(2, 0)),
3320 ),
3321 height: None,
3322 style: BlockStyle::Sticky,
3323 render: Arc::new(|_| div().into_any()),
3324 priority: 0,
3325 },
3326 ],
3327 cx,
3328 )
3329 });
3330
3331 pretty_assertions::assert_eq!(
3332 cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(7), &map, &theme, cx)),
3333 [
3334 ("const".into(), Some(Hsla::green())),
3335 (" A".into(), None),
3336 (":".into(), Some(Hsla::blue())),
3337 (" &str = ".into(), None),
3338 ("\"\n one\n".into(), Some(Hsla::red())),
3339 ("\n".into(), None),
3340 (" two\n three\n\"".into(), Some(Hsla::red())),
3341 (";".into(), Some(Hsla::blue())),
3342 ("\n".into(), None),
3343 ("const".into(), Some(Hsla::green())),
3344 (" B".into(), None),
3345 (":".into(), Some(Hsla::blue())),
3346 (" &str = ".into(), None),
3347 ("\"four\"".into(), Some(Hsla::red())),
3348 (";".into(), Some(Hsla::blue())),
3349 ("\n".into(), None),
3350 ]
3351 );
3352 }
3353
3354 #[gpui::test]
3355 async fn test_chunks_with_diagnostics_across_blocks(cx: &mut gpui::TestAppContext) {
3356 cx.background_executor
3357 .set_block_on_ticks(usize::MAX..=usize::MAX);
3358
3359 let text = r#"
3360 struct A {
3361 b: usize;
3362 }
3363 const c: usize = 1;
3364 "#
3365 .unindent();
3366
3367 cx.update(|cx| init_test(cx, &|_| {}));
3368
3369 let buffer = cx.new(|cx| Buffer::local(text, cx));
3370
3371 buffer.update(cx, |buffer, cx| {
3372 buffer.update_diagnostics(
3373 LanguageServerId(0),
3374 DiagnosticSet::new(
3375 [DiagnosticEntry {
3376 range: PointUtf16::new(0, 0)..PointUtf16::new(2, 1),
3377 diagnostic: Diagnostic {
3378 severity: lsp::DiagnosticSeverity::ERROR,
3379 group_id: 1,
3380 message: "hi".into(),
3381 ..Default::default()
3382 },
3383 }],
3384 buffer,
3385 ),
3386 cx,
3387 )
3388 });
3389
3390 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
3391 let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
3392
3393 let map = cx.new(|cx| {
3394 DisplayMap::new(
3395 buffer,
3396 font("Courier"),
3397 px(16.0),
3398 None,
3399 1,
3400 1,
3401 FoldPlaceholder::test(),
3402 DiagnosticSeverity::Warning,
3403 cx,
3404 )
3405 });
3406
3407 let black = gpui::black().to_rgb();
3408 let red = gpui::red().to_rgb();
3409
3410 // Insert a block in the middle of a multi-line diagnostic.
3411 map.update(cx, |map, cx| {
3412 map.highlight_text(
3413 HighlightKey::Editor,
3414 vec![
3415 buffer_snapshot.anchor_before(Point::new(3, 9))
3416 ..buffer_snapshot.anchor_after(Point::new(3, 14)),
3417 buffer_snapshot.anchor_before(Point::new(3, 17))
3418 ..buffer_snapshot.anchor_after(Point::new(3, 18)),
3419 ],
3420 red.into(),
3421 false,
3422 cx,
3423 );
3424 map.insert_blocks(
3425 [BlockProperties {
3426 placement: BlockPlacement::Below(
3427 buffer_snapshot.anchor_before(Point::new(1, 0)),
3428 ),
3429 height: Some(1),
3430 style: BlockStyle::Sticky,
3431 render: Arc::new(|_| div().into_any()),
3432 priority: 0,
3433 }],
3434 cx,
3435 )
3436 });
3437
3438 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
3439 let mut chunks = Vec::<(String, Option<lsp::DiagnosticSeverity>, Rgba)>::new();
3440 for chunk in snapshot.chunks(DisplayRow(0)..DisplayRow(5), true, Default::default()) {
3441 let color = chunk
3442 .highlight_style
3443 .and_then(|style| style.color)
3444 .map_or(black, |color| color.to_rgb());
3445 if let Some((last_chunk, last_severity, last_color)) = chunks.last_mut()
3446 && *last_severity == chunk.diagnostic_severity
3447 && *last_color == color
3448 {
3449 last_chunk.push_str(chunk.text);
3450 continue;
3451 }
3452
3453 chunks.push((chunk.text.to_string(), chunk.diagnostic_severity, color));
3454 }
3455
3456 assert_eq!(
3457 chunks,
3458 [
3459 (
3460 "struct A {\n b: usize;\n".into(),
3461 Some(lsp::DiagnosticSeverity::ERROR),
3462 black
3463 ),
3464 ("\n".into(), None, black),
3465 ("}".into(), Some(lsp::DiagnosticSeverity::ERROR), black),
3466 ("\nconst c: ".into(), None, black),
3467 ("usize".into(), None, red),
3468 (" = ".into(), None, black),
3469 ("1".into(), None, red),
3470 (";\n".into(), None, black),
3471 ]
3472 );
3473 }
3474
3475 #[gpui::test]
3476 async fn test_point_translation_with_replace_blocks(cx: &mut gpui::TestAppContext) {
3477 cx.background_executor
3478 .set_block_on_ticks(usize::MAX..=usize::MAX);
3479
3480 cx.update(|cx| init_test(cx, &|_| {}));
3481
3482 let buffer = cx.update(|cx| MultiBuffer::build_simple("abcde\nfghij\nklmno\npqrst", cx));
3483 let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
3484 let map = cx.new(|cx| {
3485 DisplayMap::new(
3486 buffer.clone(),
3487 font("Courier"),
3488 px(16.0),
3489 None,
3490 1,
3491 1,
3492 FoldPlaceholder::test(),
3493 DiagnosticSeverity::Warning,
3494 cx,
3495 )
3496 });
3497
3498 let snapshot = map.update(cx, |map, cx| {
3499 map.insert_blocks(
3500 [BlockProperties {
3501 placement: BlockPlacement::Replace(
3502 buffer_snapshot.anchor_before(Point::new(1, 2))
3503 ..=buffer_snapshot.anchor_after(Point::new(2, 3)),
3504 ),
3505 height: Some(4),
3506 style: BlockStyle::Fixed,
3507 render: Arc::new(|_| div().into_any()),
3508 priority: 0,
3509 }],
3510 cx,
3511 );
3512 map.snapshot(cx)
3513 });
3514
3515 assert_eq!(snapshot.text(), "abcde\n\n\n\n\npqrst");
3516
3517 let point_to_display_points = [
3518 (Point::new(1, 0), DisplayPoint::new(DisplayRow(1), 0)),
3519 (Point::new(2, 0), DisplayPoint::new(DisplayRow(1), 0)),
3520 (Point::new(3, 0), DisplayPoint::new(DisplayRow(5), 0)),
3521 ];
3522 for (buffer_point, display_point) in point_to_display_points {
3523 assert_eq!(
3524 snapshot.point_to_display_point(buffer_point, Bias::Left),
3525 display_point,
3526 "point_to_display_point({:?}, Bias::Left)",
3527 buffer_point
3528 );
3529 assert_eq!(
3530 snapshot.point_to_display_point(buffer_point, Bias::Right),
3531 display_point,
3532 "point_to_display_point({:?}, Bias::Right)",
3533 buffer_point
3534 );
3535 }
3536
3537 let display_points_to_points = [
3538 (
3539 DisplayPoint::new(DisplayRow(1), 0),
3540 Point::new(1, 0),
3541 Point::new(2, 5),
3542 ),
3543 (
3544 DisplayPoint::new(DisplayRow(2), 0),
3545 Point::new(1, 0),
3546 Point::new(2, 5),
3547 ),
3548 (
3549 DisplayPoint::new(DisplayRow(3), 0),
3550 Point::new(1, 0),
3551 Point::new(2, 5),
3552 ),
3553 (
3554 DisplayPoint::new(DisplayRow(4), 0),
3555 Point::new(1, 0),
3556 Point::new(2, 5),
3557 ),
3558 (
3559 DisplayPoint::new(DisplayRow(5), 0),
3560 Point::new(3, 0),
3561 Point::new(3, 0),
3562 ),
3563 ];
3564 for (display_point, left_buffer_point, right_buffer_point) in display_points_to_points {
3565 assert_eq!(
3566 snapshot.display_point_to_point(display_point, Bias::Left),
3567 left_buffer_point,
3568 "display_point_to_point({:?}, Bias::Left)",
3569 display_point
3570 );
3571 assert_eq!(
3572 snapshot.display_point_to_point(display_point, Bias::Right),
3573 right_buffer_point,
3574 "display_point_to_point({:?}, Bias::Right)",
3575 display_point
3576 );
3577 }
3578 }
3579
3580 #[gpui::test]
3581 async fn test_chunks_with_soft_wrapping(cx: &mut gpui::TestAppContext) {
3582 cx.background_executor
3583 .set_block_on_ticks(usize::MAX..=usize::MAX);
3584
3585 let text = r#"
3586 fn outer() {}
3587
3588 mod module {
3589 fn inner() {}
3590 }"#
3591 .unindent();
3592
3593 let theme =
3594 SyntaxTheme::new_test(vec![("mod.body", Hsla::red()), ("fn.name", Hsla::blue())]);
3595 let language = Arc::new(
3596 Language::new(
3597 LanguageConfig {
3598 name: "Test".into(),
3599 matcher: LanguageMatcher {
3600 path_suffixes: vec![".test".to_string()],
3601 ..Default::default()
3602 },
3603 ..Default::default()
3604 },
3605 Some(tree_sitter_rust::LANGUAGE.into()),
3606 )
3607 .with_highlights_query(
3608 r#"
3609 (mod_item name: (identifier) body: _ @mod.body)
3610 (function_item name: (identifier) @fn.name)
3611 "#,
3612 )
3613 .unwrap(),
3614 );
3615 language.set_theme(&theme);
3616
3617 cx.update(|cx| init_test(cx, &|_| {}));
3618
3619 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
3620 cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
3621 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
3622
3623 let font_size = px(16.0);
3624
3625 let map = cx.new(|cx| {
3626 DisplayMap::new(
3627 buffer,
3628 font("Courier"),
3629 font_size,
3630 Some(px(40.0)),
3631 1,
3632 1,
3633 FoldPlaceholder::test(),
3634 DiagnosticSeverity::Warning,
3635 cx,
3636 )
3637 });
3638 assert_eq!(
3639 cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(5), &map, &theme, cx)),
3640 [
3641 ("fn \n".to_string(), None),
3642 ("oute".to_string(), Some(Hsla::blue())),
3643 ("\n".to_string(), None),
3644 ("r".to_string(), Some(Hsla::blue())),
3645 ("() \n{}\n\n".to_string(), None),
3646 ]
3647 );
3648 assert_eq!(
3649 cx.update(|cx| syntax_chunks(DisplayRow(3)..DisplayRow(5), &map, &theme, cx)),
3650 [("{}\n\n".to_string(), None)]
3651 );
3652
3653 map.update(cx, |map, cx| {
3654 map.fold(
3655 vec![Crease::simple(
3656 MultiBufferPoint::new(0, 6)..MultiBufferPoint::new(3, 2),
3657 FoldPlaceholder::test(),
3658 )],
3659 cx,
3660 )
3661 });
3662 assert_eq!(
3663 cx.update(|cx| syntax_chunks(DisplayRow(1)..DisplayRow(4), &map, &theme, cx)),
3664 [
3665 ("out".to_string(), Some(Hsla::blue())),
3666 ("⋯\n".to_string(), None),
3667 (" ".to_string(), Some(Hsla::red())),
3668 ("\n".to_string(), None),
3669 ("fn ".to_string(), Some(Hsla::red())),
3670 ("i".to_string(), Some(Hsla::blue())),
3671 ("\n".to_string(), None)
3672 ]
3673 );
3674 }
3675
3676 #[gpui::test]
3677 async fn test_chunks_with_text_highlights(cx: &mut gpui::TestAppContext) {
3678 cx.update(|cx| init_test(cx, &|_| {}));
3679
3680 let theme =
3681 SyntaxTheme::new_test(vec![("operator", Hsla::red()), ("string", Hsla::green())]);
3682 let language = Arc::new(
3683 Language::new(
3684 LanguageConfig {
3685 name: "Test".into(),
3686 matcher: LanguageMatcher {
3687 path_suffixes: vec![".test".to_string()],
3688 ..Default::default()
3689 },
3690 ..Default::default()
3691 },
3692 Some(tree_sitter_rust::LANGUAGE.into()),
3693 )
3694 .with_highlights_query(
3695 r#"
3696 ":" @operator
3697 (string_literal) @string
3698 "#,
3699 )
3700 .unwrap(),
3701 );
3702 language.set_theme(&theme);
3703
3704 let (text, highlighted_ranges) = marked_text_ranges(r#"constˇ «a»«:» B = "c «d»""#, false);
3705
3706 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
3707 cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
3708
3709 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
3710 let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
3711
3712 let font_size = px(16.0);
3713 let map = cx.new(|cx| {
3714 DisplayMap::new(
3715 buffer,
3716 font("Courier"),
3717 font_size,
3718 None,
3719 1,
3720 1,
3721 FoldPlaceholder::test(),
3722 DiagnosticSeverity::Warning,
3723 cx,
3724 )
3725 });
3726
3727 let style = HighlightStyle {
3728 color: Some(Hsla::blue()),
3729 ..Default::default()
3730 };
3731
3732 map.update(cx, |map, cx| {
3733 map.highlight_text(
3734 HighlightKey::Editor,
3735 highlighted_ranges
3736 .into_iter()
3737 .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
3738 .map(|range| {
3739 buffer_snapshot.anchor_before(range.start)
3740 ..buffer_snapshot.anchor_before(range.end)
3741 })
3742 .collect(),
3743 style,
3744 false,
3745 cx,
3746 );
3747 });
3748
3749 assert_eq!(
3750 cx.update(|cx| chunks(DisplayRow(0)..DisplayRow(10), &map, &theme, cx)),
3751 [
3752 ("const ".to_string(), None, None),
3753 ("a".to_string(), None, Some(Hsla::blue())),
3754 (":".to_string(), Some(Hsla::red()), Some(Hsla::blue())),
3755 (" B = ".to_string(), None, None),
3756 ("\"c ".to_string(), Some(Hsla::green()), None),
3757 ("d".to_string(), Some(Hsla::green()), Some(Hsla::blue())),
3758 ("\"".to_string(), Some(Hsla::green()), None),
3759 ]
3760 );
3761 }
3762
3763 #[gpui::test]
3764 fn test_clip_point(cx: &mut gpui::App) {
3765 init_test(cx, &|_| {});
3766
3767 fn assert(text: &str, shift_right: bool, bias: Bias, cx: &mut gpui::App) {
3768 let (unmarked_snapshot, mut markers) = marked_display_snapshot(text, cx);
3769
3770 match bias {
3771 Bias::Left => {
3772 if shift_right {
3773 *markers[1].column_mut() += 1;
3774 }
3775
3776 assert_eq!(unmarked_snapshot.clip_point(markers[1], bias), markers[0])
3777 }
3778 Bias::Right => {
3779 if shift_right {
3780 *markers[0].column_mut() += 1;
3781 }
3782
3783 assert_eq!(unmarked_snapshot.clip_point(markers[0], bias), markers[1])
3784 }
3785 };
3786 }
3787
3788 use Bias::{Left, Right};
3789 assert("ˇˇα", false, Left, cx);
3790 assert("ˇˇα", true, Left, cx);
3791 assert("ˇˇα", false, Right, cx);
3792 assert("ˇαˇ", true, Right, cx);
3793 assert("ˇˇ✋", false, Left, cx);
3794 assert("ˇˇ✋", true, Left, cx);
3795 assert("ˇˇ✋", false, Right, cx);
3796 assert("ˇ✋ˇ", true, Right, cx);
3797 assert("ˇˇ🍐", false, Left, cx);
3798 assert("ˇˇ🍐", true, Left, cx);
3799 assert("ˇˇ🍐", false, Right, cx);
3800 assert("ˇ🍐ˇ", true, Right, cx);
3801 assert("ˇˇ\t", false, Left, cx);
3802 assert("ˇˇ\t", true, Left, cx);
3803 assert("ˇˇ\t", false, Right, cx);
3804 assert("ˇ\tˇ", true, Right, cx);
3805 assert(" ˇˇ\t", false, Left, cx);
3806 assert(" ˇˇ\t", true, Left, cx);
3807 assert(" ˇˇ\t", false, Right, cx);
3808 assert(" ˇ\tˇ", true, Right, cx);
3809 assert(" ˇˇ\t", false, Left, cx);
3810 assert(" ˇˇ\t", false, Right, cx);
3811 }
3812
3813 #[gpui::test]
3814 fn test_clip_at_line_ends(cx: &mut gpui::App) {
3815 init_test(cx, &|_| {});
3816
3817 fn assert(text: &str, cx: &mut gpui::App) {
3818 let (mut unmarked_snapshot, markers) = marked_display_snapshot(text, cx);
3819 unmarked_snapshot.clip_at_line_ends = true;
3820 assert_eq!(
3821 unmarked_snapshot.clip_point(markers[1], Bias::Left),
3822 markers[0]
3823 );
3824 }
3825
3826 assert("ˇˇ", cx);
3827 assert("ˇaˇ", cx);
3828 assert("aˇbˇ", cx);
3829 assert("aˇαˇ", cx);
3830 }
3831
3832 #[gpui::test]
3833 fn test_creases(cx: &mut gpui::App) {
3834 init_test(cx, &|_| {});
3835
3836 let text = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll";
3837 let buffer = MultiBuffer::build_simple(text, cx);
3838 let font_size = px(14.0);
3839 cx.new(|cx| {
3840 let mut map = DisplayMap::new(
3841 buffer.clone(),
3842 font("Helvetica"),
3843 font_size,
3844 None,
3845 1,
3846 1,
3847 FoldPlaceholder::test(),
3848 DiagnosticSeverity::Warning,
3849 cx,
3850 );
3851 let snapshot = map.buffer.read(cx).snapshot(cx);
3852 let range =
3853 snapshot.anchor_before(Point::new(2, 0))..snapshot.anchor_after(Point::new(3, 3));
3854
3855 map.crease_map.insert(
3856 [Crease::inline(
3857 range,
3858 FoldPlaceholder::test(),
3859 |_row, _status, _toggle, _window, _cx| div(),
3860 |_row, _status, _window, _cx| div(),
3861 )],
3862 &map.buffer.read(cx).snapshot(cx),
3863 );
3864
3865 map
3866 });
3867 }
3868
3869 #[gpui::test]
3870 fn test_tabs_with_multibyte_chars(cx: &mut gpui::App) {
3871 init_test(cx, &|_| {});
3872
3873 let text = "✅\t\tα\nβ\t\n🏀β\t\tγ";
3874 let buffer = MultiBuffer::build_simple(text, cx);
3875 let font_size = px(14.0);
3876
3877 let map = cx.new(|cx| {
3878 DisplayMap::new(
3879 buffer.clone(),
3880 font("Helvetica"),
3881 font_size,
3882 None,
3883 1,
3884 1,
3885 FoldPlaceholder::test(),
3886 DiagnosticSeverity::Warning,
3887 cx,
3888 )
3889 });
3890 let map = map.update(cx, |map, cx| map.snapshot(cx));
3891 assert_eq!(map.text(), "✅ α\nβ \n🏀β γ");
3892 assert_eq!(
3893 map.text_chunks(DisplayRow(0)).collect::<String>(),
3894 "✅ α\nβ \n🏀β γ"
3895 );
3896 assert_eq!(
3897 map.text_chunks(DisplayRow(1)).collect::<String>(),
3898 "β \n🏀β γ"
3899 );
3900 assert_eq!(
3901 map.text_chunks(DisplayRow(2)).collect::<String>(),
3902 "🏀β γ"
3903 );
3904
3905 let point = MultiBufferPoint::new(0, "✅\t\t".len() as u32);
3906 let display_point = DisplayPoint::new(DisplayRow(0), "✅ ".len() as u32);
3907 assert_eq!(point.to_display_point(&map), display_point);
3908 assert_eq!(display_point.to_point(&map), point);
3909
3910 let point = MultiBufferPoint::new(1, "β\t".len() as u32);
3911 let display_point = DisplayPoint::new(DisplayRow(1), "β ".len() as u32);
3912 assert_eq!(point.to_display_point(&map), display_point);
3913 assert_eq!(display_point.to_point(&map), point,);
3914
3915 let point = MultiBufferPoint::new(2, "🏀β\t\t".len() as u32);
3916 let display_point = DisplayPoint::new(DisplayRow(2), "🏀β ".len() as u32);
3917 assert_eq!(point.to_display_point(&map), display_point);
3918 assert_eq!(display_point.to_point(&map), point,);
3919
3920 // Display points inside of expanded tabs
3921 assert_eq!(
3922 DisplayPoint::new(DisplayRow(0), "✅ ".len() as u32).to_point(&map),
3923 MultiBufferPoint::new(0, "✅\t".len() as u32),
3924 );
3925 assert_eq!(
3926 DisplayPoint::new(DisplayRow(0), "✅ ".len() as u32).to_point(&map),
3927 MultiBufferPoint::new(0, "✅".len() as u32),
3928 );
3929
3930 // Clipping display points inside of multi-byte characters
3931 assert_eq!(
3932 map.clip_point(
3933 DisplayPoint::new(DisplayRow(0), "✅".len() as u32 - 1),
3934 Left
3935 ),
3936 DisplayPoint::new(DisplayRow(0), 0)
3937 );
3938 assert_eq!(
3939 map.clip_point(
3940 DisplayPoint::new(DisplayRow(0), "✅".len() as u32 - 1),
3941 Bias::Right
3942 ),
3943 DisplayPoint::new(DisplayRow(0), "✅".len() as u32)
3944 );
3945 }
3946
3947 #[gpui::test]
3948 fn test_max_point(cx: &mut gpui::App) {
3949 init_test(cx, &|_| {});
3950
3951 let buffer = MultiBuffer::build_simple("aaa\n\t\tbbb", cx);
3952 let font_size = px(14.0);
3953 let map = cx.new(|cx| {
3954 DisplayMap::new(
3955 buffer.clone(),
3956 font("Helvetica"),
3957 font_size,
3958 None,
3959 1,
3960 1,
3961 FoldPlaceholder::test(),
3962 DiagnosticSeverity::Warning,
3963 cx,
3964 )
3965 });
3966 assert_eq!(
3967 map.update(cx, |map, cx| map.snapshot(cx)).max_point(),
3968 DisplayPoint::new(DisplayRow(1), 11)
3969 )
3970 }
3971
3972 fn syntax_chunks(
3973 rows: Range<DisplayRow>,
3974 map: &Entity<DisplayMap>,
3975 theme: &SyntaxTheme,
3976 cx: &mut App,
3977 ) -> Vec<(String, Option<Hsla>)> {
3978 chunks(rows, map, theme, cx)
3979 .into_iter()
3980 .map(|(text, color, _)| (text, color))
3981 .collect()
3982 }
3983
3984 fn chunks(
3985 rows: Range<DisplayRow>,
3986 map: &Entity<DisplayMap>,
3987 theme: &SyntaxTheme,
3988 cx: &mut App,
3989 ) -> Vec<(String, Option<Hsla>, Option<Hsla>)> {
3990 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
3991 let mut chunks: Vec<(String, Option<Hsla>, Option<Hsla>)> = Vec::new();
3992 for chunk in snapshot.chunks(rows, true, HighlightStyles::default()) {
3993 let syntax_color = chunk
3994 .syntax_highlight_id
3995 .and_then(|id| id.style(theme)?.color);
3996 let highlight_color = chunk.highlight_style.and_then(|style| style.color);
3997 if let Some((last_chunk, last_syntax_color, last_highlight_color)) = chunks.last_mut()
3998 && syntax_color == *last_syntax_color
3999 && highlight_color == *last_highlight_color
4000 {
4001 last_chunk.push_str(chunk.text);
4002 continue;
4003 }
4004 chunks.push((chunk.text.to_string(), syntax_color, highlight_color));
4005 }
4006 chunks
4007 }
4008
4009 fn init_test(cx: &mut App, f: &dyn Fn(&mut SettingsContent)) {
4010 let settings = SettingsStore::test(cx);
4011 cx.set_global(settings);
4012 crate::init(cx);
4013 theme::init(LoadThemes::JustBase, cx);
4014 cx.update_global::<SettingsStore, _>(|store, cx| {
4015 store.update_user_settings(cx, f);
4016 });
4017 }
4018
4019 #[gpui::test]
4020 fn test_isomorphic_display_point_ranges_for_buffer_range(cx: &mut gpui::TestAppContext) {
4021 cx.update(|cx| init_test(cx, &|_| {}));
4022
4023 let buffer = cx.new(|cx| Buffer::local("let x = 5;\n", cx));
4024 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
4025 let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
4026
4027 let font_size = px(14.0);
4028 let map = cx.new(|cx| {
4029 DisplayMap::new(
4030 buffer.clone(),
4031 font("Helvetica"),
4032 font_size,
4033 None,
4034 1,
4035 1,
4036 FoldPlaceholder::test(),
4037 DiagnosticSeverity::Warning,
4038 cx,
4039 )
4040 });
4041
4042 // Without inlays, a buffer range maps to a single display range.
4043 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
4044 let ranges = snapshot.isomorphic_display_point_ranges_for_buffer_range(
4045 MultiBufferOffset(4)..MultiBufferOffset(9),
4046 );
4047 assert_eq!(ranges.len(), 1);
4048 // "x = 5" is columns 4..9 with no inlays shifting anything.
4049 assert_eq!(ranges[0].start, DisplayPoint::new(DisplayRow(0), 4));
4050 assert_eq!(ranges[0].end, DisplayPoint::new(DisplayRow(0), 9));
4051
4052 // Insert a 4-char inlay hint ": i32" at buffer offset 5 (after "x").
4053 map.update(cx, |map, cx| {
4054 map.splice_inlays(
4055 &[],
4056 vec![Inlay::mock_hint(
4057 0,
4058 buffer_snapshot.anchor_after(MultiBufferOffset(5)),
4059 ": i32",
4060 )],
4061 cx,
4062 );
4063 });
4064 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
4065 assert_eq!(snapshot.text(), "let x: i32 = 5;\n");
4066
4067 // A buffer range [4..9] ("x = 5") now spans across the inlay.
4068 // It should be split into two display ranges that skip the inlay text.
4069 let ranges = snapshot.isomorphic_display_point_ranges_for_buffer_range(
4070 MultiBufferOffset(4)..MultiBufferOffset(9),
4071 );
4072 assert_eq!(
4073 ranges.len(),
4074 2,
4075 "expected the range to be split around the inlay, got: {:?}",
4076 ranges,
4077 );
4078 // First sub-range: buffer [4, 5) → "x" at display columns 4..5
4079 assert_eq!(ranges[0].start, DisplayPoint::new(DisplayRow(0), 4));
4080 assert_eq!(ranges[0].end, DisplayPoint::new(DisplayRow(0), 5));
4081 // Second sub-range: buffer [5, 9) → " = 5" at display columns 10..14
4082 // (shifted right by the 5-char ": i32" inlay)
4083 assert_eq!(ranges[1].start, DisplayPoint::new(DisplayRow(0), 10));
4084 assert_eq!(ranges[1].end, DisplayPoint::new(DisplayRow(0), 14));
4085
4086 // A range entirely before the inlay is not split.
4087 let ranges = snapshot.isomorphic_display_point_ranges_for_buffer_range(
4088 MultiBufferOffset(0)..MultiBufferOffset(5),
4089 );
4090 assert_eq!(ranges.len(), 1);
4091 assert_eq!(ranges[0].start, DisplayPoint::new(DisplayRow(0), 0));
4092 assert_eq!(ranges[0].end, DisplayPoint::new(DisplayRow(0), 5));
4093
4094 // A range entirely after the inlay is not split.
4095 let ranges = snapshot.isomorphic_display_point_ranges_for_buffer_range(
4096 MultiBufferOffset(5)..MultiBufferOffset(9),
4097 );
4098 assert_eq!(ranges.len(), 1);
4099 assert_eq!(ranges[0].start, DisplayPoint::new(DisplayRow(0), 10));
4100 assert_eq!(ranges[0].end, DisplayPoint::new(DisplayRow(0), 14));
4101 }
4102
4103 #[test]
4104 fn test_highlight_invisibles_preserves_compound_emojis() {
4105 let editor_style = EditorStyle::default();
4106
4107 let pilot_emoji = "🧑\u{200d}✈\u{fe0f}";
4108 let chunk = HighlightedChunk {
4109 text: pilot_emoji,
4110 style: None,
4111 is_tab: false,
4112 is_inlay: false,
4113 replacement: None,
4114 };
4115
4116 let chunks: Vec<_> = chunk
4117 .highlight_invisibles(&editor_style)
4118 .map(|chunk| chunk.text.to_string())
4119 .collect();
4120
4121 assert_eq!(
4122 chunks.concat(),
4123 pilot_emoji,
4124 "all text bytes must be preserved"
4125 );
4126 assert_eq!(
4127 chunks.len(),
4128 1,
4129 "compound emoji should not be split into multiple chunks, got: {:?}",
4130 chunks,
4131 );
4132 }
4133}