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