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