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