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