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