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