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