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