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