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