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