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