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