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