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//! [Editor]: crate::Editor
18//! [EditorElement]: crate::element::EditorElement
19
20mod block_map;
21mod crease_map;
22mod custom_highlights;
23mod fold_map;
24mod inlay_map;
25pub(crate) mod invisibles;
26mod tab_map;
27mod wrap_map;
28
29use crate::{
30 EditorStyle, RowExt, hover_links::InlayHighlight, inlays::Inlay, movement::TextLayoutDetails,
31};
32pub use block_map::{
33 Block, BlockChunks as DisplayChunks, BlockContext, BlockId, BlockMap, BlockPlacement,
34 BlockPoint, BlockProperties, BlockRows, BlockStyle, CustomBlockId, EditorMargins, RenderBlock,
35 StickyHeaderExcerpt,
36};
37use block_map::{BlockRow, BlockSnapshot};
38use collections::{HashMap, HashSet};
39pub use crease_map::*;
40use fold_map::FoldSnapshot;
41pub use fold_map::{
42 ChunkRenderer, ChunkRendererContext, ChunkRendererId, Fold, FoldId, FoldPlaceholder, FoldPoint,
43};
44use gpui::{App, Context, Entity, Font, HighlightStyle, LineLayout, Pixels, UnderlineStyle};
45use inlay_map::InlaySnapshot;
46pub use inlay_map::{InlayOffset, InlayPoint};
47pub use invisibles::{is_invisible, replacement};
48use language::{
49 OffsetUtf16, Point, Subscription as BufferSubscription, language_settings::language_settings,
50};
51use multi_buffer::{
52 Anchor, AnchorRangeExt, MultiBuffer, MultiBufferPoint, MultiBufferRow, MultiBufferSnapshot,
53 RowInfo, ToOffset, ToPoint,
54};
55use project::InlayId;
56use project::project_settings::DiagnosticSeverity;
57use serde::Deserialize;
58
59use std::{
60 any::TypeId,
61 borrow::Cow,
62 fmt::Debug,
63 iter,
64 num::NonZeroU32,
65 ops::{Add, Range, Sub},
66 sync::Arc,
67};
68use sum_tree::{Bias, TreeMap};
69use tab_map::TabSnapshot;
70use text::{BufferId, LineIndent};
71use ui::{SharedString, px};
72use unicode_segmentation::UnicodeSegmentation;
73use wrap_map::{WrapMap, WrapSnapshot};
74
75pub use crate::display_map::{fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap};
76
77#[derive(Copy, Clone, Debug, PartialEq, Eq)]
78pub enum FoldStatus {
79 Folded,
80 Foldable,
81}
82
83#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
84pub enum HighlightKey {
85 Type(TypeId),
86 TypePlus(TypeId, usize),
87}
88
89pub trait ToDisplayPoint {
90 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint;
91}
92
93type TextHighlights = TreeMap<HighlightKey, Arc<(HighlightStyle, Vec<Range<Anchor>>)>>;
94type InlayHighlights = TreeMap<TypeId, TreeMap<InlayId, (HighlightStyle, InlayHighlight)>>;
95
96/// Decides how text in a [`MultiBuffer`] should be displayed in a buffer, handling inlay hints,
97/// folding, hard tabs, soft wrapping, custom blocks (like diagnostics), and highlighting.
98///
99/// See the [module level documentation](self) for more information.
100pub struct DisplayMap {
101 /// The buffer that we are displaying.
102 buffer: Entity<MultiBuffer>,
103 buffer_subscription: BufferSubscription,
104 /// Decides where the [`Inlay`]s should be displayed.
105 inlay_map: InlayMap,
106 /// Decides where the fold indicators should be and tracks parts of a source file that are currently folded.
107 fold_map: FoldMap,
108 /// Keeps track of hard tabs in a buffer.
109 tab_map: TabMap,
110 /// Handles soft wrapping.
111 wrap_map: Entity<WrapMap>,
112 /// Tracks custom blocks such as diagnostics that should be displayed within buffer.
113 block_map: BlockMap,
114 /// Regions of text that should be highlighted.
115 text_highlights: TextHighlights,
116 /// Regions of inlays that should be highlighted.
117 inlay_highlights: InlayHighlights,
118 /// A container for explicitly foldable ranges, which supersede indentation based fold range suggestions.
119 crease_map: CreaseMap,
120 pub(crate) fold_placeholder: FoldPlaceholder,
121 pub clip_at_line_ends: bool,
122 pub(crate) masked: bool,
123 pub(crate) diagnostics_max_severity: DiagnosticSeverity,
124}
125
126impl DisplayMap {
127 pub fn new(
128 buffer: Entity<MultiBuffer>,
129 font: Font,
130 font_size: Pixels,
131 wrap_width: Option<Pixels>,
132 buffer_header_height: u32,
133 excerpt_header_height: u32,
134 fold_placeholder: FoldPlaceholder,
135 diagnostics_max_severity: DiagnosticSeverity,
136 cx: &mut Context<Self>,
137 ) -> Self {
138 let buffer_subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
139
140 let tab_size = Self::tab_size(&buffer, cx);
141 let buffer_snapshot = buffer.read(cx).snapshot(cx);
142 let crease_map = CreaseMap::new(&buffer_snapshot);
143 let (inlay_map, snapshot) = InlayMap::new(buffer_snapshot);
144 let (fold_map, snapshot) = FoldMap::new(snapshot);
145 let (tab_map, snapshot) = TabMap::new(snapshot, tab_size);
146 let (wrap_map, snapshot) = WrapMap::new(snapshot, font, font_size, wrap_width, cx);
147 let block_map = BlockMap::new(snapshot, buffer_header_height, excerpt_header_height);
148
149 cx.observe(&wrap_map, |_, _, cx| cx.notify()).detach();
150
151 DisplayMap {
152 buffer,
153 buffer_subscription,
154 fold_map,
155 inlay_map,
156 tab_map,
157 wrap_map,
158 block_map,
159 crease_map,
160 fold_placeholder,
161 diagnostics_max_severity,
162 text_highlights: Default::default(),
163 inlay_highlights: Default::default(),
164 clip_at_line_ends: false,
165 masked: false,
166 }
167 }
168
169 pub fn snapshot(&mut self, cx: &mut Context<Self>) -> DisplaySnapshot {
170 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
171 let edits = self.buffer_subscription.consume().into_inner();
172 let (inlay_snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits);
173 let (fold_snapshot, edits) = self.fold_map.read(inlay_snapshot, edits);
174 let tab_size = Self::tab_size(&self.buffer, cx);
175 let (tab_snapshot, edits) = self.tab_map.sync(fold_snapshot, edits, tab_size);
176 let (wrap_snapshot, edits) = self
177 .wrap_map
178 .update(cx, |map, cx| map.sync(tab_snapshot, edits, cx));
179 let block_snapshot = self.block_map.read(wrap_snapshot, edits).snapshot;
180
181 DisplaySnapshot {
182 block_snapshot,
183 diagnostics_max_severity: self.diagnostics_max_severity,
184 crease_snapshot: self.crease_map.snapshot(),
185 text_highlights: self.text_highlights.clone(),
186 inlay_highlights: self.inlay_highlights.clone(),
187 clip_at_line_ends: self.clip_at_line_ends,
188 masked: self.masked,
189 fold_placeholder: self.fold_placeholder.clone(),
190 }
191 }
192
193 pub fn set_state(&mut self, other: &DisplaySnapshot, cx: &mut Context<Self>) {
194 self.fold(
195 other
196 .folds_in_range(0..other.buffer_snapshot().len())
197 .map(|fold| {
198 Crease::simple(
199 fold.range.to_offset(other.buffer_snapshot()),
200 fold.placeholder.clone(),
201 )
202 })
203 .collect(),
204 cx,
205 );
206 }
207
208 /// Creates folds for the given creases.
209 pub fn fold<T: Clone + ToOffset>(&mut self, creases: Vec<Crease<T>>, cx: &mut Context<Self>) {
210 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
211 let edits = self.buffer_subscription.consume().into_inner();
212 let tab_size = Self::tab_size(&self.buffer, cx);
213 let (snapshot, edits) = self.inlay_map.sync(buffer_snapshot.clone(), edits);
214 let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
215 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
216 let (snapshot, edits) = self
217 .wrap_map
218 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
219 self.block_map.read(snapshot, edits);
220
221 let inline = creases.iter().filter_map(|crease| {
222 if let Crease::Inline {
223 range, placeholder, ..
224 } = crease
225 {
226 Some((range.clone(), placeholder.clone()))
227 } else {
228 None
229 }
230 });
231 let (snapshot, edits) = fold_map.fold(inline);
232
233 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
234 let (snapshot, edits) = self
235 .wrap_map
236 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
237 let mut block_map = self.block_map.write(snapshot, edits);
238 let blocks = creases.into_iter().filter_map(|crease| {
239 if let Crease::Block {
240 range,
241 block_height,
242 render_block,
243 block_style,
244 block_priority,
245 ..
246 } = crease
247 {
248 Some((
249 range,
250 render_block,
251 block_height,
252 block_style,
253 block_priority,
254 ))
255 } else {
256 None
257 }
258 });
259 block_map.insert(
260 blocks
261 .into_iter()
262 .map(|(range, render, height, style, priority)| {
263 let start = buffer_snapshot.anchor_before(range.start);
264 let end = buffer_snapshot.anchor_after(range.end);
265 BlockProperties {
266 placement: BlockPlacement::Replace(start..=end),
267 render,
268 height: Some(height),
269 style,
270 priority,
271 }
272 }),
273 );
274 }
275
276 /// Removes any folds with the given ranges.
277 pub fn remove_folds_with_type<T: ToOffset>(
278 &mut self,
279 ranges: impl IntoIterator<Item = Range<T>>,
280 type_id: TypeId,
281 cx: &mut Context<Self>,
282 ) {
283 let snapshot = self.buffer.read(cx).snapshot(cx);
284 let edits = self.buffer_subscription.consume().into_inner();
285 let tab_size = Self::tab_size(&self.buffer, cx);
286 let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
287 let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
288 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
289 let (snapshot, edits) = self
290 .wrap_map
291 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
292 self.block_map.read(snapshot, edits);
293 let (snapshot, edits) = fold_map.remove_folds(ranges, type_id);
294 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
295 let (snapshot, edits) = self
296 .wrap_map
297 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
298 self.block_map.write(snapshot, edits);
299 }
300
301 /// Removes any folds whose ranges intersect any of the given ranges.
302 pub fn unfold_intersecting<T: ToOffset>(
303 &mut self,
304 ranges: impl IntoIterator<Item = Range<T>>,
305 inclusive: bool,
306 cx: &mut Context<Self>,
307 ) {
308 let snapshot = self.buffer.read(cx).snapshot(cx);
309 let offset_ranges = ranges
310 .into_iter()
311 .map(|range| range.start.to_offset(&snapshot)..range.end.to_offset(&snapshot))
312 .collect::<Vec<_>>();
313 let edits = self.buffer_subscription.consume().into_inner();
314 let tab_size = Self::tab_size(&self.buffer, cx);
315 let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
316 let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
317 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
318 let (snapshot, edits) = self
319 .wrap_map
320 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
321 self.block_map.read(snapshot, edits);
322
323 let (snapshot, edits) =
324 fold_map.unfold_intersecting(offset_ranges.iter().cloned(), inclusive);
325 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
326 let (snapshot, edits) = self
327 .wrap_map
328 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
329 let mut block_map = self.block_map.write(snapshot, edits);
330 block_map.remove_intersecting_replace_blocks(offset_ranges, inclusive);
331 }
332
333 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
334 let snapshot = self.buffer.read(cx).snapshot(cx);
335 let edits = self.buffer_subscription.consume().into_inner();
336 let tab_size = Self::tab_size(&self.buffer, cx);
337 let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
338 let (snapshot, edits) = self.fold_map.read(snapshot, edits);
339 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
340 let (snapshot, edits) = self
341 .wrap_map
342 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
343 let mut block_map = self.block_map.write(snapshot, edits);
344 block_map.disable_header_for_buffer(buffer_id)
345 }
346
347 pub fn fold_buffers(
348 &mut self,
349 buffer_ids: impl IntoIterator<Item = language::BufferId>,
350 cx: &mut Context<Self>,
351 ) {
352 let snapshot = self.buffer.read(cx).snapshot(cx);
353 let edits = self.buffer_subscription.consume().into_inner();
354 let tab_size = Self::tab_size(&self.buffer, cx);
355 let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
356 let (snapshot, edits) = self.fold_map.read(snapshot, edits);
357 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
358 let (snapshot, edits) = self
359 .wrap_map
360 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
361 let mut block_map = self.block_map.write(snapshot, edits);
362 block_map.fold_buffers(buffer_ids, self.buffer.read(cx), cx)
363 }
364
365 pub fn unfold_buffers(
366 &mut self,
367 buffer_ids: impl IntoIterator<Item = language::BufferId>,
368 cx: &mut Context<Self>,
369 ) {
370 let snapshot = self.buffer.read(cx).snapshot(cx);
371 let edits = self.buffer_subscription.consume().into_inner();
372 let tab_size = Self::tab_size(&self.buffer, cx);
373 let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
374 let (snapshot, edits) = self.fold_map.read(snapshot, edits);
375 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
376 let (snapshot, edits) = self
377 .wrap_map
378 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
379 let mut block_map = self.block_map.write(snapshot, edits);
380 block_map.unfold_buffers(buffer_ids, self.buffer.read(cx), cx)
381 }
382
383 pub(crate) fn is_buffer_folded(&self, buffer_id: language::BufferId) -> bool {
384 self.block_map.folded_buffers.contains(&buffer_id)
385 }
386
387 pub(crate) fn folded_buffers(&self) -> &HashSet<BufferId> {
388 &self.block_map.folded_buffers
389 }
390
391 pub fn insert_creases(
392 &mut self,
393 creases: impl IntoIterator<Item = Crease<Anchor>>,
394 cx: &mut Context<Self>,
395 ) -> Vec<CreaseId> {
396 let snapshot = self.buffer.read(cx).snapshot(cx);
397 self.crease_map.insert(creases, &snapshot)
398 }
399
400 pub fn remove_creases(
401 &mut self,
402 crease_ids: impl IntoIterator<Item = CreaseId>,
403 cx: &mut Context<Self>,
404 ) -> Vec<(CreaseId, Range<Anchor>)> {
405 let snapshot = self.buffer.read(cx).snapshot(cx);
406 self.crease_map.remove(crease_ids, &snapshot)
407 }
408
409 pub fn insert_blocks(
410 &mut self,
411 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
412 cx: &mut Context<Self>,
413 ) -> Vec<CustomBlockId> {
414 let snapshot = self.buffer.read(cx).snapshot(cx);
415 let edits = self.buffer_subscription.consume().into_inner();
416 let tab_size = Self::tab_size(&self.buffer, cx);
417 let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
418 let (snapshot, edits) = self.fold_map.read(snapshot, edits);
419 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
420 let (snapshot, edits) = self
421 .wrap_map
422 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
423 let mut block_map = self.block_map.write(snapshot, edits);
424 block_map.insert(blocks)
425 }
426
427 pub fn resize_blocks(&mut self, heights: HashMap<CustomBlockId, u32>, cx: &mut Context<Self>) {
428 let snapshot = self.buffer.read(cx).snapshot(cx);
429 let edits = self.buffer_subscription.consume().into_inner();
430 let tab_size = Self::tab_size(&self.buffer, cx);
431 let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
432 let (snapshot, edits) = self.fold_map.read(snapshot, edits);
433 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
434 let (snapshot, edits) = self
435 .wrap_map
436 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
437 let mut block_map = self.block_map.write(snapshot, edits);
438 block_map.resize(heights);
439 }
440
441 pub fn replace_blocks(&mut self, renderers: HashMap<CustomBlockId, RenderBlock>) {
442 self.block_map.replace_blocks(renderers);
443 }
444
445 pub fn remove_blocks(&mut self, ids: HashSet<CustomBlockId>, cx: &mut Context<Self>) {
446 let snapshot = self.buffer.read(cx).snapshot(cx);
447 let edits = self.buffer_subscription.consume().into_inner();
448 let tab_size = Self::tab_size(&self.buffer, cx);
449 let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
450 let (snapshot, edits) = self.fold_map.read(snapshot, edits);
451 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
452 let (snapshot, edits) = self
453 .wrap_map
454 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
455 let mut block_map = self.block_map.write(snapshot, edits);
456 block_map.remove(ids);
457 }
458
459 pub fn row_for_block(
460 &mut self,
461 block_id: CustomBlockId,
462 cx: &mut Context<Self>,
463 ) -> Option<DisplayRow> {
464 let snapshot = self.buffer.read(cx).snapshot(cx);
465 let edits = self.buffer_subscription.consume().into_inner();
466 let tab_size = Self::tab_size(&self.buffer, cx);
467 let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
468 let (snapshot, edits) = self.fold_map.read(snapshot, edits);
469 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
470 let (snapshot, edits) = self
471 .wrap_map
472 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
473 let block_map = self.block_map.read(snapshot, edits);
474 let block_row = block_map.row_for_block(block_id)?;
475 Some(DisplayRow(block_row.0))
476 }
477
478 pub fn highlight_text(
479 &mut self,
480 key: HighlightKey,
481 ranges: Vec<Range<Anchor>>,
482 style: HighlightStyle,
483 ) {
484 self.text_highlights.insert(key, Arc::new((style, ranges)));
485 }
486
487 pub(crate) fn highlight_inlays(
488 &mut self,
489 type_id: TypeId,
490 highlights: Vec<InlayHighlight>,
491 style: HighlightStyle,
492 ) {
493 for highlight in highlights {
494 let update = self.inlay_highlights.update(&type_id, |highlights| {
495 highlights.insert(highlight.inlay, (style, highlight.clone()))
496 });
497 if update.is_none() {
498 self.inlay_highlights.insert(
499 type_id,
500 TreeMap::from_ordered_entries([(highlight.inlay, (style, highlight))]),
501 );
502 }
503 }
504 }
505
506 pub fn text_highlights(&self, type_id: TypeId) -> Option<(HighlightStyle, &[Range<Anchor>])> {
507 let highlights = self.text_highlights.get(&HighlightKey::Type(type_id))?;
508 Some((highlights.0, &highlights.1))
509 }
510
511 #[cfg(feature = "test-support")]
512 pub fn all_text_highlights(
513 &self,
514 ) -> impl Iterator<Item = &Arc<(HighlightStyle, Vec<Range<Anchor>>)>> {
515 self.text_highlights.values()
516 }
517
518 pub fn clear_highlights(&mut self, type_id: TypeId) -> bool {
519 let mut cleared = self
520 .text_highlights
521 .remove(&HighlightKey::Type(type_id))
522 .is_some();
523 cleared |= self.inlay_highlights.remove(&type_id).is_some();
524 cleared
525 }
526
527 pub fn set_font(&self, font: Font, font_size: Pixels, cx: &mut Context<Self>) -> bool {
528 self.wrap_map
529 .update(cx, |map, cx| map.set_font_with_size(font, font_size, cx))
530 }
531
532 pub fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut Context<Self>) -> bool {
533 self.wrap_map
534 .update(cx, |map, cx| map.set_wrap_width(width, cx))
535 }
536
537 pub fn update_fold_widths(
538 &mut self,
539 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
540 cx: &mut Context<Self>,
541 ) -> bool {
542 let snapshot = self.buffer.read(cx).snapshot(cx);
543 let edits = self.buffer_subscription.consume().into_inner();
544 let tab_size = Self::tab_size(&self.buffer, cx);
545 let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
546 let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
547 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
548 let (snapshot, edits) = self
549 .wrap_map
550 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
551 self.block_map.read(snapshot, edits);
552
553 let (snapshot, edits) = fold_map.update_fold_widths(widths);
554 let widths_changed = !edits.is_empty();
555 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
556 let (snapshot, edits) = self
557 .wrap_map
558 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
559 self.block_map.read(snapshot, edits);
560
561 widths_changed
562 }
563
564 pub(crate) fn current_inlays(&self) -> impl Iterator<Item = &Inlay> {
565 self.inlay_map.current_inlays()
566 }
567
568 pub(crate) fn splice_inlays(
569 &mut self,
570 to_remove: &[InlayId],
571 to_insert: Vec<Inlay>,
572 cx: &mut Context<Self>,
573 ) {
574 if to_remove.is_empty() && to_insert.is_empty() {
575 return;
576 }
577 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
578 let edits = self.buffer_subscription.consume().into_inner();
579 let (snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits);
580 let (snapshot, edits) = self.fold_map.read(snapshot, edits);
581 let tab_size = Self::tab_size(&self.buffer, cx);
582 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
583 let (snapshot, edits) = self
584 .wrap_map
585 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
586 self.block_map.read(snapshot, edits);
587
588 let (snapshot, edits) = self.inlay_map.splice(to_remove, to_insert);
589 let (snapshot, edits) = self.fold_map.read(snapshot, edits);
590 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
591 let (snapshot, edits) = self
592 .wrap_map
593 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
594 self.block_map.read(snapshot, edits);
595 }
596
597 fn tab_size(buffer: &Entity<MultiBuffer>, cx: &App) -> NonZeroU32 {
598 let buffer = buffer.read(cx).as_singleton().map(|buffer| buffer.read(cx));
599 let language = buffer
600 .and_then(|buffer| buffer.language())
601 .map(|l| l.name());
602 let file = buffer.and_then(|buffer| buffer.file());
603 language_settings(language, file, cx).tab_size
604 }
605
606 #[cfg(test)]
607 pub fn is_rewrapping(&self, cx: &gpui::App) -> bool {
608 self.wrap_map.read(cx).is_rewrapping()
609 }
610}
611
612#[derive(Debug, Default)]
613pub(crate) struct Highlights<'a> {
614 pub text_highlights: Option<&'a TextHighlights>,
615 pub inlay_highlights: Option<&'a InlayHighlights>,
616 pub styles: HighlightStyles,
617}
618
619#[derive(Clone, Copy, Debug)]
620pub struct EditPredictionStyles {
621 pub insertion: HighlightStyle,
622 pub whitespace: HighlightStyle,
623}
624
625#[derive(Default, Debug, Clone, Copy)]
626pub struct HighlightStyles {
627 pub inlay_hint: Option<HighlightStyle>,
628 pub edit_prediction: Option<EditPredictionStyles>,
629}
630
631#[derive(Clone)]
632pub enum ChunkReplacement {
633 Renderer(ChunkRenderer),
634 Str(SharedString),
635}
636
637pub struct HighlightedChunk<'a> {
638 pub text: &'a str,
639 pub style: Option<HighlightStyle>,
640 pub is_tab: bool,
641 pub is_inlay: bool,
642 pub replacement: Option<ChunkReplacement>,
643}
644
645impl<'a> HighlightedChunk<'a> {
646 fn highlight_invisibles(
647 self,
648 editor_style: &'a EditorStyle,
649 ) -> impl Iterator<Item = Self> + 'a {
650 let mut chars = self.text.chars().peekable();
651 let mut text = self.text;
652 let style = self.style;
653 let is_tab = self.is_tab;
654 let renderer = self.replacement;
655 let is_inlay = self.is_inlay;
656 iter::from_fn(move || {
657 let mut prefix_len = 0;
658 while let Some(&ch) = chars.peek() {
659 if !is_invisible(ch) {
660 prefix_len += ch.len_utf8();
661 chars.next();
662 continue;
663 }
664 if prefix_len > 0 {
665 let (prefix, suffix) = text.split_at(prefix_len);
666 text = suffix;
667 return Some(HighlightedChunk {
668 text: prefix,
669 style,
670 is_tab,
671 is_inlay,
672 replacement: renderer.clone(),
673 });
674 }
675 chars.next();
676 let (prefix, suffix) = text.split_at(ch.len_utf8());
677 text = suffix;
678 if let Some(replacement) = replacement(ch) {
679 let invisible_highlight = HighlightStyle {
680 background_color: Some(editor_style.status.hint_background),
681 underline: Some(UnderlineStyle {
682 color: Some(editor_style.status.hint),
683 thickness: px(1.),
684 wavy: false,
685 }),
686 ..Default::default()
687 };
688 let invisible_style = if let Some(style) = style {
689 style.highlight(invisible_highlight)
690 } else {
691 invisible_highlight
692 };
693 return Some(HighlightedChunk {
694 text: prefix,
695 style: Some(invisible_style),
696 is_tab: false,
697 is_inlay,
698 replacement: Some(ChunkReplacement::Str(replacement.into())),
699 });
700 } else {
701 let invisible_highlight = HighlightStyle {
702 background_color: Some(editor_style.status.hint_background),
703 underline: Some(UnderlineStyle {
704 color: Some(editor_style.status.hint),
705 thickness: px(1.),
706 wavy: false,
707 }),
708 ..Default::default()
709 };
710 let invisible_style = if let Some(style) = style {
711 style.highlight(invisible_highlight)
712 } else {
713 invisible_highlight
714 };
715
716 return Some(HighlightedChunk {
717 text: prefix,
718 style: Some(invisible_style),
719 is_tab: false,
720 is_inlay,
721 replacement: renderer.clone(),
722 });
723 }
724 }
725
726 if !text.is_empty() {
727 let remainder = text;
728 text = "";
729 Some(HighlightedChunk {
730 text: remainder,
731 style,
732 is_tab,
733 is_inlay,
734 replacement: renderer.clone(),
735 })
736 } else {
737 None
738 }
739 })
740 }
741}
742
743#[derive(Clone)]
744pub struct DisplaySnapshot {
745 pub crease_snapshot: CreaseSnapshot,
746 block_snapshot: BlockSnapshot,
747 text_highlights: TextHighlights,
748 inlay_highlights: InlayHighlights,
749 clip_at_line_ends: bool,
750 masked: bool,
751 diagnostics_max_severity: DiagnosticSeverity,
752 pub(crate) fold_placeholder: FoldPlaceholder,
753}
754impl DisplaySnapshot {
755 pub fn wrap_snapshot(&self) -> &WrapSnapshot {
756 &self.block_snapshot.wrap_snapshot
757 }
758 pub fn tab_snapshot(&self) -> &TabSnapshot {
759 &self.block_snapshot.wrap_snapshot.tab_snapshot
760 }
761
762 pub fn fold_snapshot(&self) -> &FoldSnapshot {
763 &self.block_snapshot.wrap_snapshot.tab_snapshot.fold_snapshot
764 }
765
766 pub fn inlay_snapshot(&self) -> &InlaySnapshot {
767 &self
768 .block_snapshot
769 .wrap_snapshot
770 .tab_snapshot
771 .fold_snapshot
772 .inlay_snapshot
773 }
774
775 pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot {
776 &self
777 .block_snapshot
778 .wrap_snapshot
779 .tab_snapshot
780 .fold_snapshot
781 .inlay_snapshot
782 .buffer
783 }
784
785 #[cfg(test)]
786 pub fn fold_count(&self) -> usize {
787 self.fold_snapshot().fold_count()
788 }
789
790 pub fn is_empty(&self) -> bool {
791 self.buffer_snapshot().len() == 0
792 }
793
794 pub fn row_infos(&self, start_row: DisplayRow) -> impl Iterator<Item = RowInfo> + '_ {
795 self.block_snapshot.row_infos(BlockRow(start_row.0))
796 }
797
798 pub fn widest_line_number(&self) -> u32 {
799 self.buffer_snapshot().widest_line_number()
800 }
801
802 pub fn prev_line_boundary(&self, mut point: MultiBufferPoint) -> (Point, DisplayPoint) {
803 loop {
804 let mut inlay_point = self.inlay_snapshot().to_inlay_point(point);
805 let mut fold_point = self.fold_snapshot().to_fold_point(inlay_point, Bias::Left);
806 fold_point.0.column = 0;
807 inlay_point = fold_point.to_inlay_point(self.fold_snapshot());
808 point = self.inlay_snapshot().to_buffer_point(inlay_point);
809
810 let mut display_point = self.point_to_display_point(point, Bias::Left);
811 *display_point.column_mut() = 0;
812 let next_point = self.display_point_to_point(display_point, Bias::Left);
813 if next_point == point {
814 return (point, display_point);
815 }
816 point = next_point;
817 }
818 }
819
820 pub fn next_line_boundary(
821 &self,
822 mut point: MultiBufferPoint,
823 ) -> (MultiBufferPoint, DisplayPoint) {
824 let original_point = point;
825 loop {
826 let mut inlay_point = self.inlay_snapshot().to_inlay_point(point);
827 let mut fold_point = self.fold_snapshot().to_fold_point(inlay_point, Bias::Right);
828 fold_point.0.column = self.fold_snapshot().line_len(fold_point.row());
829 inlay_point = fold_point.to_inlay_point(self.fold_snapshot());
830 point = self.inlay_snapshot().to_buffer_point(inlay_point);
831
832 let mut display_point = self.point_to_display_point(point, Bias::Right);
833 *display_point.column_mut() = self.line_len(display_point.row());
834 let next_point = self.display_point_to_point(display_point, Bias::Right);
835 if next_point == point || original_point == point || original_point == next_point {
836 return (point, display_point);
837 }
838 point = next_point;
839 }
840 }
841
842 // used by line_mode selections and tries to match vim behavior
843 pub fn expand_to_line(&self, range: Range<Point>) -> Range<Point> {
844 let new_start = MultiBufferPoint::new(range.start.row, 0);
845 let new_end = if range.end.column > 0 {
846 MultiBufferPoint::new(
847 range.end.row,
848 self.buffer_snapshot()
849 .line_len(MultiBufferRow(range.end.row)),
850 )
851 } else {
852 range.end
853 };
854
855 new_start..new_end
856 }
857
858 pub fn point_to_display_point(&self, point: MultiBufferPoint, bias: Bias) -> DisplayPoint {
859 let inlay_point = self.inlay_snapshot().to_inlay_point(point);
860 let fold_point = self.fold_snapshot().to_fold_point(inlay_point, bias);
861 let tab_point = self.tab_snapshot().to_tab_point(fold_point);
862 let wrap_point = self.wrap_snapshot().tab_point_to_wrap_point(tab_point);
863 let block_point = self.block_snapshot.to_block_point(wrap_point);
864 DisplayPoint(block_point)
865 }
866
867 pub fn display_point_to_point(&self, point: DisplayPoint, bias: Bias) -> Point {
868 self.inlay_snapshot()
869 .to_buffer_point(self.display_point_to_inlay_point(point, bias))
870 }
871
872 pub fn display_point_to_inlay_offset(&self, point: DisplayPoint, bias: Bias) -> InlayOffset {
873 self.inlay_snapshot()
874 .to_offset(self.display_point_to_inlay_point(point, bias))
875 }
876
877 pub fn anchor_to_inlay_offset(&self, anchor: Anchor) -> InlayOffset {
878 self.inlay_snapshot()
879 .to_inlay_offset(anchor.to_offset(self.buffer_snapshot()))
880 }
881
882 pub fn display_point_to_anchor(&self, point: DisplayPoint, bias: Bias) -> Anchor {
883 self.buffer_snapshot()
884 .anchor_at(point.to_offset(self, bias), bias)
885 }
886
887 fn display_point_to_inlay_point(&self, point: DisplayPoint, bias: Bias) -> InlayPoint {
888 let block_point = point.0;
889 let wrap_point = self.block_snapshot.to_wrap_point(block_point, bias);
890 let tab_point = self.wrap_snapshot().to_tab_point(wrap_point);
891 let fold_point = self.tab_snapshot().to_fold_point(tab_point, bias).0;
892 fold_point.to_inlay_point(self.fold_snapshot())
893 }
894
895 pub fn display_point_to_fold_point(&self, point: DisplayPoint, bias: Bias) -> FoldPoint {
896 let block_point = point.0;
897 let wrap_point = self.block_snapshot.to_wrap_point(block_point, bias);
898 let tab_point = self.wrap_snapshot().to_tab_point(wrap_point);
899 self.tab_snapshot().to_fold_point(tab_point, bias).0
900 }
901
902 pub fn fold_point_to_display_point(&self, fold_point: FoldPoint) -> DisplayPoint {
903 let tab_point = self.tab_snapshot().to_tab_point(fold_point);
904 let wrap_point = self.wrap_snapshot().tab_point_to_wrap_point(tab_point);
905 let block_point = self.block_snapshot.to_block_point(wrap_point);
906 DisplayPoint(block_point)
907 }
908
909 pub fn max_point(&self) -> DisplayPoint {
910 DisplayPoint(self.block_snapshot.max_point())
911 }
912
913 /// Returns text chunks starting at the given display row until the end of the file
914 pub fn text_chunks(&self, display_row: DisplayRow) -> impl Iterator<Item = &str> {
915 self.block_snapshot
916 .chunks(
917 display_row.0..self.max_point().row().next_row().0,
918 false,
919 self.masked,
920 Highlights::default(),
921 )
922 .map(|h| h.text)
923 }
924
925 /// Returns text chunks starting at the end of the given display row in reverse until the start of the file
926 pub fn reverse_text_chunks(&self, display_row: DisplayRow) -> impl Iterator<Item = &str> {
927 (0..=display_row.0).rev().flat_map(move |row| {
928 self.block_snapshot
929 .chunks(row..row + 1, false, self.masked, Highlights::default())
930 .map(|h| h.text)
931 .collect::<Vec<_>>()
932 .into_iter()
933 .rev()
934 })
935 }
936
937 pub fn chunks(
938 &self,
939 display_rows: Range<DisplayRow>,
940 language_aware: bool,
941 highlight_styles: HighlightStyles,
942 ) -> DisplayChunks<'_> {
943 self.block_snapshot.chunks(
944 display_rows.start.0..display_rows.end.0,
945 language_aware,
946 self.masked,
947 Highlights {
948 text_highlights: Some(&self.text_highlights),
949 inlay_highlights: Some(&self.inlay_highlights),
950 styles: highlight_styles,
951 },
952 )
953 }
954
955 pub fn highlighted_chunks<'a>(
956 &'a self,
957 display_rows: Range<DisplayRow>,
958 language_aware: bool,
959 editor_style: &'a EditorStyle,
960 ) -> impl Iterator<Item = HighlightedChunk<'a>> {
961 self.chunks(
962 display_rows,
963 language_aware,
964 HighlightStyles {
965 inlay_hint: Some(editor_style.inlay_hints_style),
966 edit_prediction: Some(editor_style.edit_prediction_styles),
967 },
968 )
969 .flat_map(|chunk| {
970 let highlight_style = chunk
971 .syntax_highlight_id
972 .and_then(|id| id.style(&editor_style.syntax));
973
974 let chunk_highlight = chunk.highlight_style.map(|chunk_highlight| {
975 HighlightStyle {
976 // For color inlays, blend the color with the editor background
977 // if the color has transparency (alpha < 1.0)
978 color: chunk_highlight.color.map(|color| {
979 if chunk.is_inlay && !color.is_opaque() {
980 editor_style.background.blend(color)
981 } else {
982 color
983 }
984 }),
985 ..chunk_highlight
986 }
987 });
988
989 let diagnostic_highlight = chunk
990 .diagnostic_severity
991 .filter(|severity| {
992 self.diagnostics_max_severity
993 .into_lsp()
994 .is_some_and(|max_severity| severity <= &max_severity)
995 })
996 .map(|severity| HighlightStyle {
997 fade_out: chunk
998 .is_unnecessary
999 .then_some(editor_style.unnecessary_code_fade),
1000 underline: (chunk.underline
1001 && editor_style.show_underlines
1002 && !(chunk.is_unnecessary && severity > lsp::DiagnosticSeverity::WARNING))
1003 .then(|| {
1004 let diagnostic_color =
1005 super::diagnostic_style(severity, &editor_style.status);
1006 UnderlineStyle {
1007 color: Some(diagnostic_color),
1008 thickness: 1.0.into(),
1009 wavy: true,
1010 }
1011 }),
1012 ..Default::default()
1013 });
1014
1015 let style = [highlight_style, chunk_highlight, diagnostic_highlight]
1016 .into_iter()
1017 .flatten()
1018 .reduce(|acc, highlight| acc.highlight(highlight));
1019
1020 HighlightedChunk {
1021 text: chunk.text,
1022 style,
1023 is_tab: chunk.is_tab,
1024 is_inlay: chunk.is_inlay,
1025 replacement: chunk.renderer.map(ChunkReplacement::Renderer),
1026 }
1027 .highlight_invisibles(editor_style)
1028 })
1029 }
1030
1031 pub fn layout_row(
1032 &self,
1033 display_row: DisplayRow,
1034 TextLayoutDetails {
1035 text_system,
1036 editor_style,
1037 rem_size,
1038 scroll_anchor: _,
1039 visible_rows: _,
1040 vertical_scroll_margin: _,
1041 }: &TextLayoutDetails,
1042 ) -> Arc<LineLayout> {
1043 let mut runs = Vec::new();
1044 let mut line = String::new();
1045
1046 let range = display_row..display_row.next_row();
1047 for chunk in self.highlighted_chunks(range, false, editor_style) {
1048 line.push_str(chunk.text);
1049
1050 let text_style = if let Some(style) = chunk.style {
1051 Cow::Owned(editor_style.text.clone().highlight(style))
1052 } else {
1053 Cow::Borrowed(&editor_style.text)
1054 };
1055
1056 runs.push(text_style.to_run(chunk.text.len()))
1057 }
1058
1059 if line.ends_with('\n') {
1060 line.pop();
1061 if let Some(last_run) = runs.last_mut() {
1062 last_run.len -= 1;
1063 if last_run.len == 0 {
1064 runs.pop();
1065 }
1066 }
1067 }
1068
1069 let font_size = editor_style.text.font_size.to_pixels(*rem_size);
1070 text_system.layout_line(&line, font_size, &runs, None)
1071 }
1072
1073 pub fn x_for_display_point(
1074 &self,
1075 display_point: DisplayPoint,
1076 text_layout_details: &TextLayoutDetails,
1077 ) -> Pixels {
1078 let line = self.layout_row(display_point.row(), text_layout_details);
1079 line.x_for_index(display_point.column() as usize)
1080 }
1081
1082 pub fn display_column_for_x(
1083 &self,
1084 display_row: DisplayRow,
1085 x: Pixels,
1086 details: &TextLayoutDetails,
1087 ) -> u32 {
1088 let layout_line = self.layout_row(display_row, details);
1089 layout_line.closest_index_for_x(x) as u32
1090 }
1091
1092 pub fn grapheme_at(&self, mut point: DisplayPoint) -> Option<SharedString> {
1093 point = DisplayPoint(self.block_snapshot.clip_point(point.0, Bias::Left));
1094 let chars = self
1095 .text_chunks(point.row())
1096 .flat_map(str::chars)
1097 .skip_while({
1098 let mut column = 0;
1099 move |char| {
1100 let at_point = column >= point.column();
1101 column += char.len_utf8() as u32;
1102 !at_point
1103 }
1104 })
1105 .take_while({
1106 let mut prev = false;
1107 move |char| {
1108 let now = char.is_ascii();
1109 let end = char.is_ascii() && (char.is_ascii_whitespace() || prev);
1110 prev = now;
1111 !end
1112 }
1113 });
1114 chars.collect::<String>().graphemes(true).next().map(|s| {
1115 if let Some(invisible) = s.chars().next().filter(|&c| is_invisible(c)) {
1116 replacement(invisible).unwrap_or(s).to_owned().into()
1117 } else if s == "\n" {
1118 " ".into()
1119 } else {
1120 s.to_owned().into()
1121 }
1122 })
1123 }
1124
1125 pub fn buffer_chars_at(&self, mut offset: usize) -> impl Iterator<Item = (char, usize)> + '_ {
1126 self.buffer_snapshot().chars_at(offset).map(move |ch| {
1127 let ret = (ch, offset);
1128 offset += ch.len_utf8();
1129 ret
1130 })
1131 }
1132
1133 pub fn reverse_buffer_chars_at(
1134 &self,
1135 mut offset: usize,
1136 ) -> impl Iterator<Item = (char, usize)> + '_ {
1137 self.buffer_snapshot()
1138 .reversed_chars_at(offset)
1139 .map(move |ch| {
1140 offset -= ch.len_utf8();
1141 (ch, offset)
1142 })
1143 }
1144
1145 pub fn clip_point(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint {
1146 let mut clipped = self.block_snapshot.clip_point(point.0, bias);
1147 if self.clip_at_line_ends {
1148 clipped = self.clip_at_line_end(DisplayPoint(clipped)).0
1149 }
1150 DisplayPoint(clipped)
1151 }
1152
1153 pub fn clip_ignoring_line_ends(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint {
1154 DisplayPoint(self.block_snapshot.clip_point(point.0, bias))
1155 }
1156
1157 pub fn clip_at_line_end(&self, display_point: DisplayPoint) -> DisplayPoint {
1158 let mut point = self.display_point_to_point(display_point, Bias::Left);
1159
1160 if point.column != self.buffer_snapshot().line_len(MultiBufferRow(point.row)) {
1161 return display_point;
1162 }
1163 point.column = point.column.saturating_sub(1);
1164 point = self.buffer_snapshot().clip_point(point, Bias::Left);
1165 self.point_to_display_point(point, Bias::Left)
1166 }
1167
1168 pub fn folds_in_range<T>(&self, range: Range<T>) -> impl Iterator<Item = &Fold>
1169 where
1170 T: ToOffset,
1171 {
1172 self.fold_snapshot().folds_in_range(range)
1173 }
1174
1175 pub fn blocks_in_range(
1176 &self,
1177 rows: Range<DisplayRow>,
1178 ) -> impl Iterator<Item = (DisplayRow, &Block)> {
1179 self.block_snapshot
1180 .blocks_in_range(rows.start.0..rows.end.0)
1181 .map(|(row, block)| (DisplayRow(row), block))
1182 }
1183
1184 pub fn sticky_header_excerpt(&self, row: f64) -> Option<StickyHeaderExcerpt<'_>> {
1185 self.block_snapshot.sticky_header_excerpt(row)
1186 }
1187
1188 pub fn block_for_id(&self, id: BlockId) -> Option<Block> {
1189 self.block_snapshot.block_for_id(id)
1190 }
1191
1192 pub fn intersects_fold<T: ToOffset>(&self, offset: T) -> bool {
1193 self.fold_snapshot().intersects_fold(offset)
1194 }
1195
1196 pub fn is_line_folded(&self, buffer_row: MultiBufferRow) -> bool {
1197 self.block_snapshot.is_line_replaced(buffer_row)
1198 || self.fold_snapshot().is_line_folded(buffer_row)
1199 }
1200
1201 pub fn is_block_line(&self, display_row: DisplayRow) -> bool {
1202 self.block_snapshot.is_block_line(BlockRow(display_row.0))
1203 }
1204
1205 pub fn is_folded_buffer_header(&self, display_row: DisplayRow) -> bool {
1206 self.block_snapshot
1207 .is_folded_buffer_header(BlockRow(display_row.0))
1208 }
1209
1210 pub fn soft_wrap_indent(&self, display_row: DisplayRow) -> Option<u32> {
1211 let wrap_row = self
1212 .block_snapshot
1213 .to_wrap_point(BlockPoint::new(display_row.0, 0), Bias::Left)
1214 .row();
1215 self.wrap_snapshot().soft_wrap_indent(wrap_row)
1216 }
1217
1218 pub fn text(&self) -> String {
1219 self.text_chunks(DisplayRow(0)).collect()
1220 }
1221
1222 pub fn line(&self, display_row: DisplayRow) -> String {
1223 let mut result = String::new();
1224 for chunk in self.text_chunks(display_row) {
1225 if let Some(ix) = chunk.find('\n') {
1226 result.push_str(&chunk[0..ix]);
1227 break;
1228 } else {
1229 result.push_str(chunk);
1230 }
1231 }
1232 result
1233 }
1234
1235 pub fn line_indent_for_buffer_row(&self, buffer_row: MultiBufferRow) -> LineIndent {
1236 self.buffer_snapshot().line_indent_for_row(buffer_row)
1237 }
1238
1239 pub fn line_len(&self, row: DisplayRow) -> u32 {
1240 self.block_snapshot.line_len(BlockRow(row.0))
1241 }
1242
1243 pub fn longest_row(&self) -> DisplayRow {
1244 DisplayRow(self.block_snapshot.longest_row())
1245 }
1246
1247 pub fn longest_row_in_range(&self, range: Range<DisplayRow>) -> DisplayRow {
1248 let block_range = BlockRow(range.start.0)..BlockRow(range.end.0);
1249 let longest_row = self.block_snapshot.longest_row_in_range(block_range);
1250 DisplayRow(longest_row.0)
1251 }
1252
1253 pub fn starts_indent(&self, buffer_row: MultiBufferRow) -> bool {
1254 let max_row = self.buffer_snapshot().max_row();
1255 if buffer_row >= max_row {
1256 return false;
1257 }
1258
1259 let line_indent = self.line_indent_for_buffer_row(buffer_row);
1260 if line_indent.is_line_blank() {
1261 return false;
1262 }
1263
1264 (buffer_row.0 + 1..=max_row.0)
1265 .find_map(|next_row| {
1266 let next_line_indent = self.line_indent_for_buffer_row(MultiBufferRow(next_row));
1267 if next_line_indent.raw_len() > line_indent.raw_len() {
1268 Some(true)
1269 } else if !next_line_indent.is_line_blank() {
1270 Some(false)
1271 } else {
1272 None
1273 }
1274 })
1275 .unwrap_or(false)
1276 }
1277
1278 pub fn crease_for_buffer_row(&self, buffer_row: MultiBufferRow) -> Option<Crease<Point>> {
1279 let start =
1280 MultiBufferPoint::new(buffer_row.0, self.buffer_snapshot().line_len(buffer_row));
1281 if let Some(crease) = self
1282 .crease_snapshot
1283 .query_row(buffer_row, self.buffer_snapshot())
1284 {
1285 match crease {
1286 Crease::Inline {
1287 range,
1288 placeholder,
1289 render_toggle,
1290 render_trailer,
1291 metadata,
1292 } => Some(Crease::Inline {
1293 range: range.to_point(self.buffer_snapshot()),
1294 placeholder: placeholder.clone(),
1295 render_toggle: render_toggle.clone(),
1296 render_trailer: render_trailer.clone(),
1297 metadata: metadata.clone(),
1298 }),
1299 Crease::Block {
1300 range,
1301 block_height,
1302 block_style,
1303 render_block,
1304 block_priority,
1305 render_toggle,
1306 } => Some(Crease::Block {
1307 range: range.to_point(self.buffer_snapshot()),
1308 block_height: *block_height,
1309 block_style: *block_style,
1310 render_block: render_block.clone(),
1311 block_priority: *block_priority,
1312 render_toggle: render_toggle.clone(),
1313 }),
1314 }
1315 } else if self.starts_indent(MultiBufferRow(start.row))
1316 && !self.is_line_folded(MultiBufferRow(start.row))
1317 {
1318 let start_line_indent = self.line_indent_for_buffer_row(buffer_row);
1319 let max_point = self.buffer_snapshot().max_point();
1320 let mut end = None;
1321
1322 for row in (buffer_row.0 + 1)..=max_point.row {
1323 let line_indent = self.line_indent_for_buffer_row(MultiBufferRow(row));
1324 if !line_indent.is_line_blank()
1325 && line_indent.raw_len() <= start_line_indent.raw_len()
1326 {
1327 let prev_row = row - 1;
1328 end = Some(Point::new(
1329 prev_row,
1330 self.buffer_snapshot().line_len(MultiBufferRow(prev_row)),
1331 ));
1332 break;
1333 }
1334 }
1335
1336 let mut row_before_line_breaks = end.unwrap_or(max_point);
1337 while row_before_line_breaks.row > start.row
1338 && self
1339 .buffer_snapshot()
1340 .is_line_blank(MultiBufferRow(row_before_line_breaks.row))
1341 {
1342 row_before_line_breaks.row -= 1;
1343 }
1344
1345 row_before_line_breaks = Point::new(
1346 row_before_line_breaks.row,
1347 self.buffer_snapshot()
1348 .line_len(MultiBufferRow(row_before_line_breaks.row)),
1349 );
1350
1351 Some(Crease::Inline {
1352 range: start..row_before_line_breaks,
1353 placeholder: self.fold_placeholder.clone(),
1354 render_toggle: None,
1355 render_trailer: None,
1356 metadata: None,
1357 })
1358 } else {
1359 None
1360 }
1361 }
1362
1363 #[cfg(any(test, feature = "test-support"))]
1364 pub fn text_highlight_ranges<Tag: ?Sized + 'static>(
1365 &self,
1366 ) -> Option<Arc<(HighlightStyle, Vec<Range<Anchor>>)>> {
1367 let type_id = TypeId::of::<Tag>();
1368 self.text_highlights
1369 .get(&HighlightKey::Type(type_id))
1370 .cloned()
1371 }
1372
1373 #[allow(unused)]
1374 #[cfg(any(test, feature = "test-support"))]
1375 pub(crate) fn inlay_highlights<Tag: ?Sized + 'static>(
1376 &self,
1377 ) -> Option<&TreeMap<InlayId, (HighlightStyle, InlayHighlight)>> {
1378 let type_id = TypeId::of::<Tag>();
1379 self.inlay_highlights.get(&type_id)
1380 }
1381
1382 pub fn buffer_header_height(&self) -> u32 {
1383 self.block_snapshot.buffer_header_height
1384 }
1385
1386 pub fn excerpt_header_height(&self) -> u32 {
1387 self.block_snapshot.excerpt_header_height
1388 }
1389
1390 /// Given a `DisplayPoint`, returns another `DisplayPoint` corresponding to
1391 /// the start of the buffer row that is a given number of buffer rows away
1392 /// from the provided point.
1393 ///
1394 /// This moves by buffer rows instead of display rows, a distinction that is
1395 /// important when soft wrapping is enabled.
1396 pub fn start_of_relative_buffer_row(&self, point: DisplayPoint, times: isize) -> DisplayPoint {
1397 let start = self.display_point_to_fold_point(point, Bias::Left);
1398 let target = start.row() as isize + times;
1399 let new_row = (target.max(0) as u32).min(self.fold_snapshot().max_point().row());
1400
1401 self.clip_point(
1402 self.fold_point_to_display_point(
1403 self.fold_snapshot()
1404 .clip_point(FoldPoint::new(new_row, 0), Bias::Right),
1405 ),
1406 Bias::Right,
1407 )
1408 }
1409}
1410
1411#[derive(Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq)]
1412pub struct DisplayPoint(BlockPoint);
1413
1414impl Debug for DisplayPoint {
1415 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1416 f.write_fmt(format_args!(
1417 "DisplayPoint({}, {})",
1418 self.row().0,
1419 self.column()
1420 ))
1421 }
1422}
1423
1424impl Add for DisplayPoint {
1425 type Output = Self;
1426
1427 fn add(self, other: Self) -> Self::Output {
1428 DisplayPoint(BlockPoint(self.0.0 + other.0.0))
1429 }
1430}
1431
1432impl Sub for DisplayPoint {
1433 type Output = Self;
1434
1435 fn sub(self, other: Self) -> Self::Output {
1436 DisplayPoint(BlockPoint(self.0.0 - other.0.0))
1437 }
1438}
1439
1440#[derive(Debug, Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq, Deserialize, Hash)]
1441#[serde(transparent)]
1442pub struct DisplayRow(pub u32);
1443
1444impl Add<DisplayRow> for DisplayRow {
1445 type Output = Self;
1446
1447 fn add(self, other: Self) -> Self::Output {
1448 DisplayRow(self.0 + other.0)
1449 }
1450}
1451
1452impl Add<u32> for DisplayRow {
1453 type Output = Self;
1454
1455 fn add(self, other: u32) -> Self::Output {
1456 DisplayRow(self.0 + other)
1457 }
1458}
1459
1460impl Sub<DisplayRow> for DisplayRow {
1461 type Output = Self;
1462
1463 fn sub(self, other: Self) -> Self::Output {
1464 DisplayRow(self.0 - other.0)
1465 }
1466}
1467
1468impl Sub<u32> for DisplayRow {
1469 type Output = Self;
1470
1471 fn sub(self, other: u32) -> Self::Output {
1472 DisplayRow(self.0 - other)
1473 }
1474}
1475
1476impl DisplayPoint {
1477 pub fn new(row: DisplayRow, column: u32) -> Self {
1478 Self(BlockPoint(Point::new(row.0, column)))
1479 }
1480
1481 pub fn zero() -> Self {
1482 Self::new(DisplayRow(0), 0)
1483 }
1484
1485 pub fn is_zero(&self) -> bool {
1486 self.0.is_zero()
1487 }
1488
1489 pub fn row(self) -> DisplayRow {
1490 DisplayRow(self.0.row)
1491 }
1492
1493 pub fn column(self) -> u32 {
1494 self.0.column
1495 }
1496
1497 pub fn row_mut(&mut self) -> &mut u32 {
1498 &mut self.0.row
1499 }
1500
1501 pub fn column_mut(&mut self) -> &mut u32 {
1502 &mut self.0.column
1503 }
1504
1505 pub fn to_point(self, map: &DisplaySnapshot) -> Point {
1506 map.display_point_to_point(self, Bias::Left)
1507 }
1508
1509 pub fn to_offset(self, map: &DisplaySnapshot, bias: Bias) -> usize {
1510 let wrap_point = map.block_snapshot.to_wrap_point(self.0, bias);
1511 let tab_point = map.wrap_snapshot().to_tab_point(wrap_point);
1512 let fold_point = map.tab_snapshot().to_fold_point(tab_point, bias).0;
1513 let inlay_point = fold_point.to_inlay_point(map.fold_snapshot());
1514 map.inlay_snapshot()
1515 .to_buffer_offset(map.inlay_snapshot().to_offset(inlay_point))
1516 }
1517}
1518
1519impl ToDisplayPoint for usize {
1520 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
1521 map.point_to_display_point(self.to_point(map.buffer_snapshot()), Bias::Left)
1522 }
1523}
1524
1525impl ToDisplayPoint for OffsetUtf16 {
1526 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
1527 self.to_offset(map.buffer_snapshot()).to_display_point(map)
1528 }
1529}
1530
1531impl ToDisplayPoint for Point {
1532 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
1533 map.point_to_display_point(*self, Bias::Left)
1534 }
1535}
1536
1537impl ToDisplayPoint for Anchor {
1538 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
1539 self.to_point(map.buffer_snapshot()).to_display_point(map)
1540 }
1541}
1542
1543#[cfg(test)]
1544pub mod tests {
1545 use super::*;
1546 use crate::{
1547 movement,
1548 test::{marked_display_snapshot, test_font},
1549 };
1550 use Bias::*;
1551 use block_map::BlockPlacement;
1552 use gpui::{
1553 App, AppContext as _, BorrowAppContext, Element, Hsla, Rgba, div, font, observe, px,
1554 };
1555 use language::{
1556 Buffer, Diagnostic, DiagnosticEntry, DiagnosticSet, Language, LanguageConfig,
1557 LanguageMatcher,
1558 };
1559 use lsp::LanguageServerId;
1560 use project::Project;
1561 use rand::{Rng, prelude::*};
1562 use settings::{SettingsContent, SettingsStore};
1563 use smol::stream::StreamExt;
1564 use std::{env, sync::Arc};
1565 use text::PointUtf16;
1566 use theme::{LoadThemes, SyntaxTheme};
1567 use unindent::Unindent as _;
1568 use util::test::{marked_text_ranges, sample_text};
1569
1570 #[gpui::test(iterations = 100)]
1571 async fn test_random_display_map(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
1572 cx.background_executor.set_block_on_ticks(0..=50);
1573 let operations = env::var("OPERATIONS")
1574 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
1575 .unwrap_or(10);
1576
1577 let mut tab_size = rng.random_range(1..=4);
1578 let buffer_start_excerpt_header_height = rng.random_range(1..=5);
1579 let excerpt_header_height = rng.random_range(1..=5);
1580 let font_size = px(14.0);
1581 let max_wrap_width = 300.0;
1582 let mut wrap_width = if rng.random_bool(0.1) {
1583 None
1584 } else {
1585 Some(px(rng.random_range(0.0..=max_wrap_width)))
1586 };
1587
1588 log::info!("tab size: {}", tab_size);
1589 log::info!("wrap width: {:?}", wrap_width);
1590
1591 cx.update(|cx| {
1592 init_test(cx, |s| {
1593 s.project.all_languages.defaults.tab_size = NonZeroU32::new(tab_size)
1594 });
1595 });
1596
1597 let buffer = cx.update(|cx| {
1598 if rng.random() {
1599 let len = rng.random_range(0..10);
1600 let text = util::RandomCharIter::new(&mut rng)
1601 .take(len)
1602 .collect::<String>();
1603 MultiBuffer::build_simple(&text, cx)
1604 } else {
1605 MultiBuffer::build_random(&mut rng, cx)
1606 }
1607 });
1608
1609 let font = test_font();
1610 let map = cx.new(|cx| {
1611 DisplayMap::new(
1612 buffer.clone(),
1613 font,
1614 font_size,
1615 wrap_width,
1616 buffer_start_excerpt_header_height,
1617 excerpt_header_height,
1618 FoldPlaceholder::test(),
1619 DiagnosticSeverity::Warning,
1620 cx,
1621 )
1622 });
1623 let mut notifications = observe(&map, cx);
1624 let mut fold_count = 0;
1625 let mut blocks = Vec::new();
1626
1627 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1628 log::info!("buffer text: {:?}", snapshot.buffer_snapshot().text());
1629 log::info!("fold text: {:?}", snapshot.fold_snapshot().text());
1630 log::info!("tab text: {:?}", snapshot.tab_snapshot().text());
1631 log::info!("wrap text: {:?}", snapshot.wrap_snapshot().text());
1632 log::info!("block text: {:?}", snapshot.block_snapshot.text());
1633 log::info!("display text: {:?}", snapshot.text());
1634
1635 for _i in 0..operations {
1636 match rng.random_range(0..100) {
1637 0..=19 => {
1638 wrap_width = if rng.random_bool(0.2) {
1639 None
1640 } else {
1641 Some(px(rng.random_range(0.0..=max_wrap_width)))
1642 };
1643 log::info!("setting wrap width to {:?}", wrap_width);
1644 map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
1645 }
1646 20..=29 => {
1647 let mut tab_sizes = vec![1, 2, 3, 4];
1648 tab_sizes.remove((tab_size - 1) as usize);
1649 tab_size = *tab_sizes.choose(&mut rng).unwrap();
1650 log::info!("setting tab size to {:?}", tab_size);
1651 cx.update(|cx| {
1652 cx.update_global::<SettingsStore, _>(|store, cx| {
1653 store.update_user_settings(cx, |s| {
1654 s.project.all_languages.defaults.tab_size =
1655 NonZeroU32::new(tab_size);
1656 });
1657 });
1658 });
1659 }
1660 30..=44 => {
1661 map.update(cx, |map, cx| {
1662 if rng.random() || blocks.is_empty() {
1663 let snapshot = map.snapshot(cx);
1664 let buffer = snapshot.buffer_snapshot();
1665 let block_properties = (0..rng.random_range(1..=1))
1666 .map(|_| {
1667 let position = buffer.anchor_after(buffer.clip_offset(
1668 rng.random_range(0..=buffer.len()),
1669 Bias::Left,
1670 ));
1671
1672 let placement = if rng.random() {
1673 BlockPlacement::Above(position)
1674 } else {
1675 BlockPlacement::Below(position)
1676 };
1677 let height = rng.random_range(1..5);
1678 log::info!(
1679 "inserting block {:?} with height {}",
1680 placement.as_ref().map(|p| p.to_point(&buffer)),
1681 height
1682 );
1683 let priority = rng.random_range(1..100);
1684 BlockProperties {
1685 placement,
1686 style: BlockStyle::Fixed,
1687 height: Some(height),
1688 render: Arc::new(|_| div().into_any()),
1689 priority,
1690 }
1691 })
1692 .collect::<Vec<_>>();
1693 blocks.extend(map.insert_blocks(block_properties, cx));
1694 } else {
1695 blocks.shuffle(&mut rng);
1696 let remove_count = rng.random_range(1..=4.min(blocks.len()));
1697 let block_ids_to_remove = (0..remove_count)
1698 .map(|_| blocks.remove(rng.random_range(0..blocks.len())))
1699 .collect();
1700 log::info!("removing block ids {:?}", block_ids_to_remove);
1701 map.remove_blocks(block_ids_to_remove, cx);
1702 }
1703 });
1704 }
1705 45..=79 => {
1706 let mut ranges = Vec::new();
1707 for _ in 0..rng.random_range(1..=3) {
1708 buffer.read_with(cx, |buffer, cx| {
1709 let buffer = buffer.read(cx);
1710 let end = buffer.clip_offset(rng.random_range(0..=buffer.len()), Right);
1711 let start = buffer.clip_offset(rng.random_range(0..=end), Left);
1712 ranges.push(start..end);
1713 });
1714 }
1715
1716 if rng.random() && fold_count > 0 {
1717 log::info!("unfolding ranges: {:?}", ranges);
1718 map.update(cx, |map, cx| {
1719 map.unfold_intersecting(ranges, true, cx);
1720 });
1721 } else {
1722 log::info!("folding ranges: {:?}", ranges);
1723 map.update(cx, |map, cx| {
1724 map.fold(
1725 ranges
1726 .into_iter()
1727 .map(|range| Crease::simple(range, FoldPlaceholder::test()))
1728 .collect(),
1729 cx,
1730 );
1731 });
1732 }
1733 }
1734 _ => {
1735 buffer.update(cx, |buffer, cx| buffer.randomly_mutate(&mut rng, 5, cx));
1736 }
1737 }
1738
1739 if map.read_with(cx, |map, cx| map.is_rewrapping(cx)) {
1740 notifications.next().await.unwrap();
1741 }
1742
1743 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1744 fold_count = snapshot.fold_count();
1745 log::info!("buffer text: {:?}", snapshot.buffer_snapshot().text());
1746 log::info!("fold text: {:?}", snapshot.fold_snapshot().text());
1747 log::info!("tab text: {:?}", snapshot.tab_snapshot().text());
1748 log::info!("wrap text: {:?}", snapshot.wrap_snapshot().text());
1749 log::info!("block text: {:?}", snapshot.block_snapshot.text());
1750 log::info!("display text: {:?}", snapshot.text());
1751
1752 // Line boundaries
1753 let buffer = snapshot.buffer_snapshot();
1754 for _ in 0..5 {
1755 let row = rng.random_range(0..=buffer.max_point().row);
1756 let column = rng.random_range(0..=buffer.line_len(MultiBufferRow(row)));
1757 let point = buffer.clip_point(Point::new(row, column), Left);
1758
1759 let (prev_buffer_bound, prev_display_bound) = snapshot.prev_line_boundary(point);
1760 let (next_buffer_bound, next_display_bound) = snapshot.next_line_boundary(point);
1761
1762 assert!(prev_buffer_bound <= point);
1763 assert!(next_buffer_bound >= point);
1764 assert_eq!(prev_buffer_bound.column, 0);
1765 assert_eq!(prev_display_bound.column(), 0);
1766 if next_buffer_bound < buffer.max_point() {
1767 assert_eq!(buffer.chars_at(next_buffer_bound).next(), Some('\n'));
1768 }
1769
1770 assert_eq!(
1771 prev_display_bound,
1772 prev_buffer_bound.to_display_point(&snapshot),
1773 "row boundary before {:?}. reported buffer row boundary: {:?}",
1774 point,
1775 prev_buffer_bound
1776 );
1777 assert_eq!(
1778 next_display_bound,
1779 next_buffer_bound.to_display_point(&snapshot),
1780 "display row boundary after {:?}. reported buffer row boundary: {:?}",
1781 point,
1782 next_buffer_bound
1783 );
1784 assert_eq!(
1785 prev_buffer_bound,
1786 prev_display_bound.to_point(&snapshot),
1787 "row boundary before {:?}. reported display row boundary: {:?}",
1788 point,
1789 prev_display_bound
1790 );
1791 assert_eq!(
1792 next_buffer_bound,
1793 next_display_bound.to_point(&snapshot),
1794 "row boundary after {:?}. reported display row boundary: {:?}",
1795 point,
1796 next_display_bound
1797 );
1798 }
1799
1800 // Movement
1801 let min_point = snapshot.clip_point(DisplayPoint::new(DisplayRow(0), 0), Left);
1802 let max_point = snapshot.clip_point(snapshot.max_point(), Right);
1803 for _ in 0..5 {
1804 let row = rng.random_range(0..=snapshot.max_point().row().0);
1805 let column = rng.random_range(0..=snapshot.line_len(DisplayRow(row)));
1806 let point = snapshot.clip_point(DisplayPoint::new(DisplayRow(row), column), Left);
1807
1808 log::info!("Moving from point {:?}", point);
1809
1810 let moved_right = movement::right(&snapshot, point);
1811 log::info!("Right {:?}", moved_right);
1812 if point < max_point {
1813 assert!(moved_right > point);
1814 if point.column() == snapshot.line_len(point.row())
1815 || snapshot.soft_wrap_indent(point.row()).is_some()
1816 && point.column() == snapshot.line_len(point.row()) - 1
1817 {
1818 assert!(moved_right.row() > point.row());
1819 }
1820 } else {
1821 assert_eq!(moved_right, point);
1822 }
1823
1824 let moved_left = movement::left(&snapshot, point);
1825 log::info!("Left {:?}", moved_left);
1826 if point > min_point {
1827 assert!(moved_left < point);
1828 if point.column() == 0 {
1829 assert!(moved_left.row() < point.row());
1830 }
1831 } else {
1832 assert_eq!(moved_left, point);
1833 }
1834 }
1835 }
1836 }
1837
1838 #[gpui::test(retries = 5)]
1839 async fn test_soft_wraps(cx: &mut gpui::TestAppContext) {
1840 cx.background_executor
1841 .set_block_on_ticks(usize::MAX..=usize::MAX);
1842 cx.update(|cx| {
1843 init_test(cx, |_| {});
1844 });
1845
1846 let mut cx = crate::test::editor_test_context::EditorTestContext::new(cx).await;
1847 let editor = cx.editor.clone();
1848 let window = cx.window;
1849
1850 _ = cx.update_window(window, |_, window, cx| {
1851 let text_layout_details =
1852 editor.update(cx, |editor, _cx| editor.text_layout_details(window));
1853
1854 let font_size = px(12.0);
1855 let wrap_width = Some(px(96.));
1856
1857 let text = "one two three four five\nsix seven eight";
1858 let buffer = MultiBuffer::build_simple(text, cx);
1859 let map = cx.new(|cx| {
1860 DisplayMap::new(
1861 buffer.clone(),
1862 font("Helvetica"),
1863 font_size,
1864 wrap_width,
1865 1,
1866 1,
1867 FoldPlaceholder::test(),
1868 DiagnosticSeverity::Warning,
1869 cx,
1870 )
1871 });
1872
1873 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1874 assert_eq!(
1875 snapshot.text_chunks(DisplayRow(0)).collect::<String>(),
1876 "one two \nthree four \nfive\nsix seven \neight"
1877 );
1878 assert_eq!(
1879 snapshot.clip_point(DisplayPoint::new(DisplayRow(0), 8), Bias::Left),
1880 DisplayPoint::new(DisplayRow(0), 7)
1881 );
1882 assert_eq!(
1883 snapshot.clip_point(DisplayPoint::new(DisplayRow(0), 8), Bias::Right),
1884 DisplayPoint::new(DisplayRow(1), 0)
1885 );
1886 assert_eq!(
1887 movement::right(&snapshot, DisplayPoint::new(DisplayRow(0), 7)),
1888 DisplayPoint::new(DisplayRow(1), 0)
1889 );
1890 assert_eq!(
1891 movement::left(&snapshot, DisplayPoint::new(DisplayRow(1), 0)),
1892 DisplayPoint::new(DisplayRow(0), 7)
1893 );
1894
1895 let x = snapshot
1896 .x_for_display_point(DisplayPoint::new(DisplayRow(1), 10), &text_layout_details);
1897 assert_eq!(
1898 movement::up(
1899 &snapshot,
1900 DisplayPoint::new(DisplayRow(1), 10),
1901 language::SelectionGoal::None,
1902 false,
1903 &text_layout_details,
1904 ),
1905 (
1906 DisplayPoint::new(DisplayRow(0), 7),
1907 language::SelectionGoal::HorizontalPosition(f64::from(x))
1908 )
1909 );
1910 assert_eq!(
1911 movement::down(
1912 &snapshot,
1913 DisplayPoint::new(DisplayRow(0), 7),
1914 language::SelectionGoal::HorizontalPosition(f64::from(x)),
1915 false,
1916 &text_layout_details
1917 ),
1918 (
1919 DisplayPoint::new(DisplayRow(1), 10),
1920 language::SelectionGoal::HorizontalPosition(f64::from(x))
1921 )
1922 );
1923 assert_eq!(
1924 movement::down(
1925 &snapshot,
1926 DisplayPoint::new(DisplayRow(1), 10),
1927 language::SelectionGoal::HorizontalPosition(f64::from(x)),
1928 false,
1929 &text_layout_details
1930 ),
1931 (
1932 DisplayPoint::new(DisplayRow(2), 4),
1933 language::SelectionGoal::HorizontalPosition(f64::from(x))
1934 )
1935 );
1936
1937 let ix = snapshot.buffer_snapshot().text().find("seven").unwrap();
1938 buffer.update(cx, |buffer, cx| {
1939 buffer.edit([(ix..ix, "and ")], None, cx);
1940 });
1941
1942 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1943 assert_eq!(
1944 snapshot.text_chunks(DisplayRow(1)).collect::<String>(),
1945 "three four \nfive\nsix and \nseven eight"
1946 );
1947
1948 // Re-wrap on font size changes
1949 map.update(cx, |map, cx| {
1950 map.set_font(font("Helvetica"), font_size + Pixels::from(3.), cx)
1951 });
1952
1953 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1954 assert_eq!(
1955 snapshot.text_chunks(DisplayRow(1)).collect::<String>(),
1956 "three \nfour five\nsix and \nseven \neight"
1957 )
1958 });
1959 }
1960
1961 #[gpui::test]
1962 fn test_text_chunks(cx: &mut gpui::App) {
1963 init_test(cx, |_| {});
1964
1965 let text = sample_text(6, 6, 'a');
1966 let buffer = MultiBuffer::build_simple(&text, cx);
1967
1968 let font_size = px(14.0);
1969 let map = cx.new(|cx| {
1970 DisplayMap::new(
1971 buffer.clone(),
1972 font("Helvetica"),
1973 font_size,
1974 None,
1975 1,
1976 1,
1977 FoldPlaceholder::test(),
1978 DiagnosticSeverity::Warning,
1979 cx,
1980 )
1981 });
1982
1983 buffer.update(cx, |buffer, cx| {
1984 buffer.edit(
1985 vec![
1986 (
1987 MultiBufferPoint::new(1, 0)..MultiBufferPoint::new(1, 0),
1988 "\t",
1989 ),
1990 (
1991 MultiBufferPoint::new(1, 1)..MultiBufferPoint::new(1, 1),
1992 "\t",
1993 ),
1994 (
1995 MultiBufferPoint::new(2, 1)..MultiBufferPoint::new(2, 1),
1996 "\t",
1997 ),
1998 ],
1999 None,
2000 cx,
2001 )
2002 });
2003
2004 assert_eq!(
2005 map.update(cx, |map, cx| map.snapshot(cx))
2006 .text_chunks(DisplayRow(1))
2007 .collect::<String>()
2008 .lines()
2009 .next(),
2010 Some(" b bbbbb")
2011 );
2012 assert_eq!(
2013 map.update(cx, |map, cx| map.snapshot(cx))
2014 .text_chunks(DisplayRow(2))
2015 .collect::<String>()
2016 .lines()
2017 .next(),
2018 Some("c ccccc")
2019 );
2020 }
2021
2022 #[gpui::test]
2023 fn test_inlays_with_newlines_after_blocks(cx: &mut gpui::TestAppContext) {
2024 cx.update(|cx| init_test(cx, |_| {}));
2025
2026 let buffer = cx.new(|cx| Buffer::local("a", cx));
2027 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
2028 let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
2029
2030 let font_size = px(14.0);
2031 let map = cx.new(|cx| {
2032 DisplayMap::new(
2033 buffer.clone(),
2034 font("Helvetica"),
2035 font_size,
2036 None,
2037 1,
2038 1,
2039 FoldPlaceholder::test(),
2040 DiagnosticSeverity::Warning,
2041 cx,
2042 )
2043 });
2044
2045 map.update(cx, |map, cx| {
2046 map.insert_blocks(
2047 [BlockProperties {
2048 placement: BlockPlacement::Above(
2049 buffer_snapshot.anchor_before(Point::new(0, 0)),
2050 ),
2051 height: Some(2),
2052 style: BlockStyle::Sticky,
2053 render: Arc::new(|_| div().into_any()),
2054 priority: 0,
2055 }],
2056 cx,
2057 );
2058 });
2059 map.update(cx, |m, cx| assert_eq!(m.snapshot(cx).text(), "\n\na"));
2060
2061 map.update(cx, |map, cx| {
2062 map.splice_inlays(
2063 &[],
2064 vec![Inlay::edit_prediction(
2065 0,
2066 buffer_snapshot.anchor_after(0),
2067 "\n",
2068 )],
2069 cx,
2070 );
2071 });
2072 map.update(cx, |m, cx| assert_eq!(m.snapshot(cx).text(), "\n\n\na"));
2073
2074 // Regression test: updating the display map does not crash when a
2075 // block is immediately followed by a multi-line inlay.
2076 buffer.update(cx, |buffer, cx| {
2077 buffer.edit([(1..1, "b")], None, cx);
2078 });
2079 map.update(cx, |m, cx| assert_eq!(m.snapshot(cx).text(), "\n\n\nab"));
2080 }
2081
2082 #[gpui::test]
2083 async fn test_chunks(cx: &mut gpui::TestAppContext) {
2084 let text = r#"
2085 fn outer() {}
2086
2087 mod module {
2088 fn inner() {}
2089 }"#
2090 .unindent();
2091
2092 let theme =
2093 SyntaxTheme::new_test(vec![("mod.body", Hsla::red()), ("fn.name", Hsla::blue())]);
2094 let language = Arc::new(
2095 Language::new(
2096 LanguageConfig {
2097 name: "Test".into(),
2098 matcher: LanguageMatcher {
2099 path_suffixes: vec![".test".to_string()],
2100 ..Default::default()
2101 },
2102 ..Default::default()
2103 },
2104 Some(tree_sitter_rust::LANGUAGE.into()),
2105 )
2106 .with_highlights_query(
2107 r#"
2108 (mod_item name: (identifier) body: _ @mod.body)
2109 (function_item name: (identifier) @fn.name)
2110 "#,
2111 )
2112 .unwrap(),
2113 );
2114 language.set_theme(&theme);
2115
2116 cx.update(|cx| {
2117 init_test(cx, |s| {
2118 s.project.all_languages.defaults.tab_size = Some(2.try_into().unwrap())
2119 })
2120 });
2121
2122 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
2123 cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
2124 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
2125
2126 let font_size = px(14.0);
2127
2128 let map = cx.new(|cx| {
2129 DisplayMap::new(
2130 buffer,
2131 font("Helvetica"),
2132 font_size,
2133 None,
2134 1,
2135 1,
2136 FoldPlaceholder::test(),
2137 DiagnosticSeverity::Warning,
2138 cx,
2139 )
2140 });
2141 assert_eq!(
2142 cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(5), &map, &theme, cx)),
2143 vec![
2144 ("fn ".to_string(), None),
2145 ("outer".to_string(), Some(Hsla::blue())),
2146 ("() {}\n\nmod module ".to_string(), None),
2147 ("{\n fn ".to_string(), Some(Hsla::red())),
2148 ("inner".to_string(), Some(Hsla::blue())),
2149 ("() {}\n}".to_string(), Some(Hsla::red())),
2150 ]
2151 );
2152 assert_eq!(
2153 cx.update(|cx| syntax_chunks(DisplayRow(3)..DisplayRow(5), &map, &theme, cx)),
2154 vec![
2155 (" fn ".to_string(), Some(Hsla::red())),
2156 ("inner".to_string(), Some(Hsla::blue())),
2157 ("() {}\n}".to_string(), Some(Hsla::red())),
2158 ]
2159 );
2160
2161 map.update(cx, |map, cx| {
2162 map.fold(
2163 vec![Crease::simple(
2164 MultiBufferPoint::new(0, 6)..MultiBufferPoint::new(3, 2),
2165 FoldPlaceholder::test(),
2166 )],
2167 cx,
2168 )
2169 });
2170 assert_eq!(
2171 cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(2), &map, &theme, cx)),
2172 vec![
2173 ("fn ".to_string(), None),
2174 ("out".to_string(), Some(Hsla::blue())),
2175 ("⋯".to_string(), None),
2176 (" fn ".to_string(), Some(Hsla::red())),
2177 ("inner".to_string(), Some(Hsla::blue())),
2178 ("() {}\n}".to_string(), Some(Hsla::red())),
2179 ]
2180 );
2181 }
2182
2183 #[gpui::test]
2184 async fn test_chunks_with_syntax_highlighting_across_blocks(cx: &mut gpui::TestAppContext) {
2185 cx.background_executor
2186 .set_block_on_ticks(usize::MAX..=usize::MAX);
2187
2188 let text = r#"
2189 const A: &str = "
2190 one
2191 two
2192 three
2193 ";
2194 const B: &str = "four";
2195 "#
2196 .unindent();
2197
2198 let theme = SyntaxTheme::new_test(vec![
2199 ("string", Hsla::red()),
2200 ("punctuation", Hsla::blue()),
2201 ("keyword", Hsla::green()),
2202 ]);
2203 let language = Arc::new(
2204 Language::new(
2205 LanguageConfig {
2206 name: "Rust".into(),
2207 ..Default::default()
2208 },
2209 Some(tree_sitter_rust::LANGUAGE.into()),
2210 )
2211 .with_highlights_query(
2212 r#"
2213 (string_literal) @string
2214 "const" @keyword
2215 [":" ";"] @punctuation
2216 "#,
2217 )
2218 .unwrap(),
2219 );
2220 language.set_theme(&theme);
2221
2222 cx.update(|cx| init_test(cx, |_| {}));
2223
2224 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
2225 cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
2226 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
2227 let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
2228
2229 let map = cx.new(|cx| {
2230 DisplayMap::new(
2231 buffer,
2232 font("Courier"),
2233 px(16.0),
2234 None,
2235 1,
2236 1,
2237 FoldPlaceholder::test(),
2238 DiagnosticSeverity::Warning,
2239 cx,
2240 )
2241 });
2242
2243 // Insert two blocks in the middle of a multi-line string literal.
2244 // The second block has zero height.
2245 map.update(cx, |map, cx| {
2246 map.insert_blocks(
2247 [
2248 BlockProperties {
2249 placement: BlockPlacement::Below(
2250 buffer_snapshot.anchor_before(Point::new(1, 0)),
2251 ),
2252 height: Some(1),
2253 style: BlockStyle::Sticky,
2254 render: Arc::new(|_| div().into_any()),
2255 priority: 0,
2256 },
2257 BlockProperties {
2258 placement: BlockPlacement::Below(
2259 buffer_snapshot.anchor_before(Point::new(2, 0)),
2260 ),
2261 height: None,
2262 style: BlockStyle::Sticky,
2263 render: Arc::new(|_| div().into_any()),
2264 priority: 0,
2265 },
2266 ],
2267 cx,
2268 )
2269 });
2270
2271 pretty_assertions::assert_eq!(
2272 cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(7), &map, &theme, cx)),
2273 [
2274 ("const".into(), Some(Hsla::green())),
2275 (" A".into(), None),
2276 (":".into(), Some(Hsla::blue())),
2277 (" &str = ".into(), None),
2278 ("\"\n one\n".into(), Some(Hsla::red())),
2279 ("\n".into(), None),
2280 (" two\n three\n\"".into(), Some(Hsla::red())),
2281 (";".into(), Some(Hsla::blue())),
2282 ("\n".into(), None),
2283 ("const".into(), Some(Hsla::green())),
2284 (" B".into(), None),
2285 (":".into(), Some(Hsla::blue())),
2286 (" &str = ".into(), None),
2287 ("\"four\"".into(), Some(Hsla::red())),
2288 (";".into(), Some(Hsla::blue())),
2289 ("\n".into(), None),
2290 ]
2291 );
2292 }
2293
2294 #[gpui::test]
2295 async fn test_chunks_with_diagnostics_across_blocks(cx: &mut gpui::TestAppContext) {
2296 cx.background_executor
2297 .set_block_on_ticks(usize::MAX..=usize::MAX);
2298
2299 let text = r#"
2300 struct A {
2301 b: usize;
2302 }
2303 const c: usize = 1;
2304 "#
2305 .unindent();
2306
2307 cx.update(|cx| init_test(cx, |_| {}));
2308
2309 let buffer = cx.new(|cx| Buffer::local(text, cx));
2310
2311 buffer.update(cx, |buffer, cx| {
2312 buffer.update_diagnostics(
2313 LanguageServerId(0),
2314 DiagnosticSet::new(
2315 [DiagnosticEntry {
2316 range: PointUtf16::new(0, 0)..PointUtf16::new(2, 1),
2317 diagnostic: Diagnostic {
2318 severity: lsp::DiagnosticSeverity::ERROR,
2319 group_id: 1,
2320 message: "hi".into(),
2321 ..Default::default()
2322 },
2323 }],
2324 buffer,
2325 ),
2326 cx,
2327 )
2328 });
2329
2330 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
2331 let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
2332
2333 let map = cx.new(|cx| {
2334 DisplayMap::new(
2335 buffer,
2336 font("Courier"),
2337 px(16.0),
2338 None,
2339 1,
2340 1,
2341 FoldPlaceholder::test(),
2342 DiagnosticSeverity::Warning,
2343 cx,
2344 )
2345 });
2346
2347 let black = gpui::black().to_rgb();
2348 let red = gpui::red().to_rgb();
2349
2350 // Insert a block in the middle of a multi-line diagnostic.
2351 map.update(cx, |map, cx| {
2352 map.highlight_text(
2353 HighlightKey::Type(TypeId::of::<usize>()),
2354 vec![
2355 buffer_snapshot.anchor_before(Point::new(3, 9))
2356 ..buffer_snapshot.anchor_after(Point::new(3, 14)),
2357 buffer_snapshot.anchor_before(Point::new(3, 17))
2358 ..buffer_snapshot.anchor_after(Point::new(3, 18)),
2359 ],
2360 red.into(),
2361 );
2362 map.insert_blocks(
2363 [BlockProperties {
2364 placement: BlockPlacement::Below(
2365 buffer_snapshot.anchor_before(Point::new(1, 0)),
2366 ),
2367 height: Some(1),
2368 style: BlockStyle::Sticky,
2369 render: Arc::new(|_| div().into_any()),
2370 priority: 0,
2371 }],
2372 cx,
2373 )
2374 });
2375
2376 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
2377 let mut chunks = Vec::<(String, Option<lsp::DiagnosticSeverity>, Rgba)>::new();
2378 for chunk in snapshot.chunks(DisplayRow(0)..DisplayRow(5), true, Default::default()) {
2379 let color = chunk
2380 .highlight_style
2381 .and_then(|style| style.color)
2382 .map_or(black, |color| color.to_rgb());
2383 if let Some((last_chunk, last_severity, last_color)) = chunks.last_mut()
2384 && *last_severity == chunk.diagnostic_severity
2385 && *last_color == color
2386 {
2387 last_chunk.push_str(chunk.text);
2388 continue;
2389 }
2390
2391 chunks.push((chunk.text.to_string(), chunk.diagnostic_severity, color));
2392 }
2393
2394 assert_eq!(
2395 chunks,
2396 [
2397 (
2398 "struct A {\n b: usize;\n".into(),
2399 Some(lsp::DiagnosticSeverity::ERROR),
2400 black
2401 ),
2402 ("\n".into(), None, black),
2403 ("}".into(), Some(lsp::DiagnosticSeverity::ERROR), black),
2404 ("\nconst c: ".into(), None, black),
2405 ("usize".into(), None, red),
2406 (" = ".into(), None, black),
2407 ("1".into(), None, red),
2408 (";\n".into(), None, black),
2409 ]
2410 );
2411 }
2412
2413 #[gpui::test]
2414 async fn test_point_translation_with_replace_blocks(cx: &mut gpui::TestAppContext) {
2415 cx.background_executor
2416 .set_block_on_ticks(usize::MAX..=usize::MAX);
2417
2418 cx.update(|cx| init_test(cx, |_| {}));
2419
2420 let buffer = cx.update(|cx| MultiBuffer::build_simple("abcde\nfghij\nklmno\npqrst", cx));
2421 let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
2422 let map = cx.new(|cx| {
2423 DisplayMap::new(
2424 buffer.clone(),
2425 font("Courier"),
2426 px(16.0),
2427 None,
2428 1,
2429 1,
2430 FoldPlaceholder::test(),
2431 DiagnosticSeverity::Warning,
2432 cx,
2433 )
2434 });
2435
2436 let snapshot = map.update(cx, |map, cx| {
2437 map.insert_blocks(
2438 [BlockProperties {
2439 placement: BlockPlacement::Replace(
2440 buffer_snapshot.anchor_before(Point::new(1, 2))
2441 ..=buffer_snapshot.anchor_after(Point::new(2, 3)),
2442 ),
2443 height: Some(4),
2444 style: BlockStyle::Fixed,
2445 render: Arc::new(|_| div().into_any()),
2446 priority: 0,
2447 }],
2448 cx,
2449 );
2450 map.snapshot(cx)
2451 });
2452
2453 assert_eq!(snapshot.text(), "abcde\n\n\n\n\npqrst");
2454
2455 let point_to_display_points = [
2456 (Point::new(1, 0), DisplayPoint::new(DisplayRow(1), 0)),
2457 (Point::new(2, 0), DisplayPoint::new(DisplayRow(1), 0)),
2458 (Point::new(3, 0), DisplayPoint::new(DisplayRow(5), 0)),
2459 ];
2460 for (buffer_point, display_point) in point_to_display_points {
2461 assert_eq!(
2462 snapshot.point_to_display_point(buffer_point, Bias::Left),
2463 display_point,
2464 "point_to_display_point({:?}, Bias::Left)",
2465 buffer_point
2466 );
2467 assert_eq!(
2468 snapshot.point_to_display_point(buffer_point, Bias::Right),
2469 display_point,
2470 "point_to_display_point({:?}, Bias::Right)",
2471 buffer_point
2472 );
2473 }
2474
2475 let display_points_to_points = [
2476 (
2477 DisplayPoint::new(DisplayRow(1), 0),
2478 Point::new(1, 0),
2479 Point::new(2, 5),
2480 ),
2481 (
2482 DisplayPoint::new(DisplayRow(2), 0),
2483 Point::new(1, 0),
2484 Point::new(2, 5),
2485 ),
2486 (
2487 DisplayPoint::new(DisplayRow(3), 0),
2488 Point::new(1, 0),
2489 Point::new(2, 5),
2490 ),
2491 (
2492 DisplayPoint::new(DisplayRow(4), 0),
2493 Point::new(1, 0),
2494 Point::new(2, 5),
2495 ),
2496 (
2497 DisplayPoint::new(DisplayRow(5), 0),
2498 Point::new(3, 0),
2499 Point::new(3, 0),
2500 ),
2501 ];
2502 for (display_point, left_buffer_point, right_buffer_point) in display_points_to_points {
2503 assert_eq!(
2504 snapshot.display_point_to_point(display_point, Bias::Left),
2505 left_buffer_point,
2506 "display_point_to_point({:?}, Bias::Left)",
2507 display_point
2508 );
2509 assert_eq!(
2510 snapshot.display_point_to_point(display_point, Bias::Right),
2511 right_buffer_point,
2512 "display_point_to_point({:?}, Bias::Right)",
2513 display_point
2514 );
2515 }
2516 }
2517
2518 #[gpui::test]
2519 async fn test_chunks_with_soft_wrapping(cx: &mut gpui::TestAppContext) {
2520 cx.background_executor
2521 .set_block_on_ticks(usize::MAX..=usize::MAX);
2522
2523 let text = r#"
2524 fn outer() {}
2525
2526 mod module {
2527 fn inner() {}
2528 }"#
2529 .unindent();
2530
2531 let theme =
2532 SyntaxTheme::new_test(vec![("mod.body", Hsla::red()), ("fn.name", Hsla::blue())]);
2533 let language = Arc::new(
2534 Language::new(
2535 LanguageConfig {
2536 name: "Test".into(),
2537 matcher: LanguageMatcher {
2538 path_suffixes: vec![".test".to_string()],
2539 ..Default::default()
2540 },
2541 ..Default::default()
2542 },
2543 Some(tree_sitter_rust::LANGUAGE.into()),
2544 )
2545 .with_highlights_query(
2546 r#"
2547 (mod_item name: (identifier) body: _ @mod.body)
2548 (function_item name: (identifier) @fn.name)
2549 "#,
2550 )
2551 .unwrap(),
2552 );
2553 language.set_theme(&theme);
2554
2555 cx.update(|cx| init_test(cx, |_| {}));
2556
2557 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
2558 cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
2559 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
2560
2561 let font_size = px(16.0);
2562
2563 let map = cx.new(|cx| {
2564 DisplayMap::new(
2565 buffer,
2566 font("Courier"),
2567 font_size,
2568 Some(px(40.0)),
2569 1,
2570 1,
2571 FoldPlaceholder::test(),
2572 DiagnosticSeverity::Warning,
2573 cx,
2574 )
2575 });
2576 assert_eq!(
2577 cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(5), &map, &theme, cx)),
2578 [
2579 ("fn \n".to_string(), None),
2580 ("oute".to_string(), Some(Hsla::blue())),
2581 ("\n".to_string(), None),
2582 ("r".to_string(), Some(Hsla::blue())),
2583 ("() \n{}\n\n".to_string(), None),
2584 ]
2585 );
2586 assert_eq!(
2587 cx.update(|cx| syntax_chunks(DisplayRow(3)..DisplayRow(5), &map, &theme, cx)),
2588 [("{}\n\n".to_string(), None)]
2589 );
2590
2591 map.update(cx, |map, cx| {
2592 map.fold(
2593 vec![Crease::simple(
2594 MultiBufferPoint::new(0, 6)..MultiBufferPoint::new(3, 2),
2595 FoldPlaceholder::test(),
2596 )],
2597 cx,
2598 )
2599 });
2600 assert_eq!(
2601 cx.update(|cx| syntax_chunks(DisplayRow(1)..DisplayRow(4), &map, &theme, cx)),
2602 [
2603 ("out".to_string(), Some(Hsla::blue())),
2604 ("⋯\n".to_string(), None),
2605 (" ".to_string(), Some(Hsla::red())),
2606 ("\n".to_string(), None),
2607 ("fn ".to_string(), Some(Hsla::red())),
2608 ("i".to_string(), Some(Hsla::blue())),
2609 ("\n".to_string(), None)
2610 ]
2611 );
2612 }
2613
2614 #[gpui::test]
2615 async fn test_chunks_with_text_highlights(cx: &mut gpui::TestAppContext) {
2616 cx.update(|cx| init_test(cx, |_| {}));
2617
2618 let theme =
2619 SyntaxTheme::new_test(vec![("operator", Hsla::red()), ("string", Hsla::green())]);
2620 let language = Arc::new(
2621 Language::new(
2622 LanguageConfig {
2623 name: "Test".into(),
2624 matcher: LanguageMatcher {
2625 path_suffixes: vec![".test".to_string()],
2626 ..Default::default()
2627 },
2628 ..Default::default()
2629 },
2630 Some(tree_sitter_rust::LANGUAGE.into()),
2631 )
2632 .with_highlights_query(
2633 r#"
2634 ":" @operator
2635 (string_literal) @string
2636 "#,
2637 )
2638 .unwrap(),
2639 );
2640 language.set_theme(&theme);
2641
2642 let (text, highlighted_ranges) = marked_text_ranges(r#"constˇ «a»«:» B = "c «d»""#, false);
2643
2644 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
2645 cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
2646
2647 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
2648 let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
2649
2650 let font_size = px(16.0);
2651 let map = cx.new(|cx| {
2652 DisplayMap::new(
2653 buffer,
2654 font("Courier"),
2655 font_size,
2656 None,
2657 1,
2658 1,
2659 FoldPlaceholder::test(),
2660 DiagnosticSeverity::Warning,
2661 cx,
2662 )
2663 });
2664
2665 enum MyType {}
2666
2667 let style = HighlightStyle {
2668 color: Some(Hsla::blue()),
2669 ..Default::default()
2670 };
2671
2672 map.update(cx, |map, _cx| {
2673 map.highlight_text(
2674 HighlightKey::Type(TypeId::of::<MyType>()),
2675 highlighted_ranges
2676 .into_iter()
2677 .map(|range| {
2678 buffer_snapshot.anchor_before(range.start)
2679 ..buffer_snapshot.anchor_before(range.end)
2680 })
2681 .collect(),
2682 style,
2683 );
2684 });
2685
2686 assert_eq!(
2687 cx.update(|cx| chunks(DisplayRow(0)..DisplayRow(10), &map, &theme, cx)),
2688 [
2689 ("const ".to_string(), None, None),
2690 ("a".to_string(), None, Some(Hsla::blue())),
2691 (":".to_string(), Some(Hsla::red()), Some(Hsla::blue())),
2692 (" B = ".to_string(), None, None),
2693 ("\"c ".to_string(), Some(Hsla::green()), None),
2694 ("d".to_string(), Some(Hsla::green()), Some(Hsla::blue())),
2695 ("\"".to_string(), Some(Hsla::green()), None),
2696 ]
2697 );
2698 }
2699
2700 #[gpui::test]
2701 fn test_clip_point(cx: &mut gpui::App) {
2702 init_test(cx, |_| {});
2703
2704 fn assert(text: &str, shift_right: bool, bias: Bias, cx: &mut gpui::App) {
2705 let (unmarked_snapshot, mut markers) = marked_display_snapshot(text, cx);
2706
2707 match bias {
2708 Bias::Left => {
2709 if shift_right {
2710 *markers[1].column_mut() += 1;
2711 }
2712
2713 assert_eq!(unmarked_snapshot.clip_point(markers[1], bias), markers[0])
2714 }
2715 Bias::Right => {
2716 if shift_right {
2717 *markers[0].column_mut() += 1;
2718 }
2719
2720 assert_eq!(unmarked_snapshot.clip_point(markers[0], bias), markers[1])
2721 }
2722 };
2723 }
2724
2725 use Bias::{Left, Right};
2726 assert("ˇˇα", false, Left, cx);
2727 assert("ˇˇα", true, Left, cx);
2728 assert("ˇˇα", false, Right, cx);
2729 assert("ˇαˇ", true, Right, cx);
2730 assert("ˇˇ✋", false, Left, cx);
2731 assert("ˇˇ✋", true, Left, cx);
2732 assert("ˇˇ✋", false, Right, cx);
2733 assert("ˇ✋ˇ", true, Right, cx);
2734 assert("ˇˇ🍐", false, Left, cx);
2735 assert("ˇˇ🍐", true, Left, cx);
2736 assert("ˇˇ🍐", false, Right, cx);
2737 assert("ˇ🍐ˇ", true, Right, cx);
2738 assert("ˇˇ\t", false, Left, cx);
2739 assert("ˇˇ\t", true, Left, cx);
2740 assert("ˇˇ\t", false, Right, cx);
2741 assert("ˇ\tˇ", true, Right, cx);
2742 assert(" ˇˇ\t", false, Left, cx);
2743 assert(" ˇˇ\t", true, Left, cx);
2744 assert(" ˇˇ\t", false, Right, cx);
2745 assert(" ˇ\tˇ", true, Right, cx);
2746 assert(" ˇˇ\t", false, Left, cx);
2747 assert(" ˇˇ\t", false, Right, cx);
2748 }
2749
2750 #[gpui::test]
2751 fn test_clip_at_line_ends(cx: &mut gpui::App) {
2752 init_test(cx, |_| {});
2753
2754 fn assert(text: &str, cx: &mut gpui::App) {
2755 let (mut unmarked_snapshot, markers) = marked_display_snapshot(text, cx);
2756 unmarked_snapshot.clip_at_line_ends = true;
2757 assert_eq!(
2758 unmarked_snapshot.clip_point(markers[1], Bias::Left),
2759 markers[0]
2760 );
2761 }
2762
2763 assert("ˇˇ", cx);
2764 assert("ˇaˇ", cx);
2765 assert("aˇbˇ", cx);
2766 assert("aˇαˇ", cx);
2767 }
2768
2769 #[gpui::test]
2770 fn test_creases(cx: &mut gpui::App) {
2771 init_test(cx, |_| {});
2772
2773 let text = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll";
2774 let buffer = MultiBuffer::build_simple(text, cx);
2775 let font_size = px(14.0);
2776 cx.new(|cx| {
2777 let mut map = DisplayMap::new(
2778 buffer.clone(),
2779 font("Helvetica"),
2780 font_size,
2781 None,
2782 1,
2783 1,
2784 FoldPlaceholder::test(),
2785 DiagnosticSeverity::Warning,
2786 cx,
2787 );
2788 let snapshot = map.buffer.read(cx).snapshot(cx);
2789 let range =
2790 snapshot.anchor_before(Point::new(2, 0))..snapshot.anchor_after(Point::new(3, 3));
2791
2792 map.crease_map.insert(
2793 [Crease::inline(
2794 range,
2795 FoldPlaceholder::test(),
2796 |_row, _status, _toggle, _window, _cx| div(),
2797 |_row, _status, _window, _cx| div(),
2798 )],
2799 &map.buffer.read(cx).snapshot(cx),
2800 );
2801
2802 map
2803 });
2804 }
2805
2806 #[gpui::test]
2807 fn test_tabs_with_multibyte_chars(cx: &mut gpui::App) {
2808 init_test(cx, |_| {});
2809
2810 let text = "✅\t\tα\nβ\t\n🏀β\t\tγ";
2811 let buffer = MultiBuffer::build_simple(text, cx);
2812 let font_size = px(14.0);
2813
2814 let map = cx.new(|cx| {
2815 DisplayMap::new(
2816 buffer.clone(),
2817 font("Helvetica"),
2818 font_size,
2819 None,
2820 1,
2821 1,
2822 FoldPlaceholder::test(),
2823 DiagnosticSeverity::Warning,
2824 cx,
2825 )
2826 });
2827 let map = map.update(cx, |map, cx| map.snapshot(cx));
2828 assert_eq!(map.text(), "✅ α\nβ \n🏀β γ");
2829 assert_eq!(
2830 map.text_chunks(DisplayRow(0)).collect::<String>(),
2831 "✅ α\nβ \n🏀β γ"
2832 );
2833 assert_eq!(
2834 map.text_chunks(DisplayRow(1)).collect::<String>(),
2835 "β \n🏀β γ"
2836 );
2837 assert_eq!(
2838 map.text_chunks(DisplayRow(2)).collect::<String>(),
2839 "🏀β γ"
2840 );
2841
2842 let point = MultiBufferPoint::new(0, "✅\t\t".len() as u32);
2843 let display_point = DisplayPoint::new(DisplayRow(0), "✅ ".len() as u32);
2844 assert_eq!(point.to_display_point(&map), display_point);
2845 assert_eq!(display_point.to_point(&map), point);
2846
2847 let point = MultiBufferPoint::new(1, "β\t".len() as u32);
2848 let display_point = DisplayPoint::new(DisplayRow(1), "β ".len() as u32);
2849 assert_eq!(point.to_display_point(&map), display_point);
2850 assert_eq!(display_point.to_point(&map), point,);
2851
2852 let point = MultiBufferPoint::new(2, "🏀β\t\t".len() as u32);
2853 let display_point = DisplayPoint::new(DisplayRow(2), "🏀β ".len() as u32);
2854 assert_eq!(point.to_display_point(&map), display_point);
2855 assert_eq!(display_point.to_point(&map), point,);
2856
2857 // Display points inside of expanded tabs
2858 assert_eq!(
2859 DisplayPoint::new(DisplayRow(0), "✅ ".len() as u32).to_point(&map),
2860 MultiBufferPoint::new(0, "✅\t".len() as u32),
2861 );
2862 assert_eq!(
2863 DisplayPoint::new(DisplayRow(0), "✅ ".len() as u32).to_point(&map),
2864 MultiBufferPoint::new(0, "✅".len() as u32),
2865 );
2866
2867 // Clipping display points inside of multi-byte characters
2868 assert_eq!(
2869 map.clip_point(
2870 DisplayPoint::new(DisplayRow(0), "✅".len() as u32 - 1),
2871 Left
2872 ),
2873 DisplayPoint::new(DisplayRow(0), 0)
2874 );
2875 assert_eq!(
2876 map.clip_point(
2877 DisplayPoint::new(DisplayRow(0), "✅".len() as u32 - 1),
2878 Bias::Right
2879 ),
2880 DisplayPoint::new(DisplayRow(0), "✅".len() as u32)
2881 );
2882 }
2883
2884 #[gpui::test]
2885 fn test_max_point(cx: &mut gpui::App) {
2886 init_test(cx, |_| {});
2887
2888 let buffer = MultiBuffer::build_simple("aaa\n\t\tbbb", cx);
2889 let font_size = px(14.0);
2890 let map = cx.new(|cx| {
2891 DisplayMap::new(
2892 buffer.clone(),
2893 font("Helvetica"),
2894 font_size,
2895 None,
2896 1,
2897 1,
2898 FoldPlaceholder::test(),
2899 DiagnosticSeverity::Warning,
2900 cx,
2901 )
2902 });
2903 assert_eq!(
2904 map.update(cx, |map, cx| map.snapshot(cx)).max_point(),
2905 DisplayPoint::new(DisplayRow(1), 11)
2906 )
2907 }
2908
2909 fn syntax_chunks(
2910 rows: Range<DisplayRow>,
2911 map: &Entity<DisplayMap>,
2912 theme: &SyntaxTheme,
2913 cx: &mut App,
2914 ) -> Vec<(String, Option<Hsla>)> {
2915 chunks(rows, map, theme, cx)
2916 .into_iter()
2917 .map(|(text, color, _)| (text, color))
2918 .collect()
2919 }
2920
2921 fn chunks(
2922 rows: Range<DisplayRow>,
2923 map: &Entity<DisplayMap>,
2924 theme: &SyntaxTheme,
2925 cx: &mut App,
2926 ) -> Vec<(String, Option<Hsla>, Option<Hsla>)> {
2927 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
2928 let mut chunks: Vec<(String, Option<Hsla>, Option<Hsla>)> = Vec::new();
2929 for chunk in snapshot.chunks(rows, true, HighlightStyles::default()) {
2930 let syntax_color = chunk
2931 .syntax_highlight_id
2932 .and_then(|id| id.style(theme)?.color);
2933 let highlight_color = chunk.highlight_style.and_then(|style| style.color);
2934 if let Some((last_chunk, last_syntax_color, last_highlight_color)) = chunks.last_mut()
2935 && syntax_color == *last_syntax_color
2936 && highlight_color == *last_highlight_color
2937 {
2938 last_chunk.push_str(chunk.text);
2939 continue;
2940 }
2941 chunks.push((chunk.text.to_string(), syntax_color, highlight_color));
2942 }
2943 chunks
2944 }
2945
2946 fn init_test(cx: &mut App, f: impl Fn(&mut SettingsContent)) {
2947 let settings = SettingsStore::test(cx);
2948 cx.set_global(settings);
2949 workspace::init_settings(cx);
2950 language::init(cx);
2951 crate::init(cx);
2952 Project::init_settings(cx);
2953 theme::init(LoadThemes::JustBase, cx);
2954 cx.update_global::<SettingsStore, _>(|store, cx| {
2955 store.update_user_settings(cx, f);
2956 });
2957 }
2958}