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