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