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, BlockId, BlockMap,
33 BlockPlacement, 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 block_map::BlockPlacement;
1184 use gpui::{div, font, observe, px, AppContext, BorrowAppContext, Context, Element, Hsla};
1185 use language::{
1186 language_settings::{AllLanguageSettings, AllLanguageSettingsContent},
1187 Buffer, Language, LanguageConfig, LanguageMatcher,
1188 };
1189 use project::Project;
1190 use rand::{prelude::*, Rng};
1191 use settings::SettingsStore;
1192 use smol::stream::StreamExt;
1193 use std::{env, sync::Arc};
1194 use theme::{LoadThemes, SyntaxTheme};
1195 use util::test::{marked_text_ranges, sample_text};
1196 use Bias::*;
1197
1198 #[gpui::test(iterations = 100)]
1199 async fn test_random_display_map(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
1200 cx.background_executor.set_block_on_ticks(0..=50);
1201 let operations = env::var("OPERATIONS")
1202 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
1203 .unwrap_or(10);
1204
1205 let mut tab_size = rng.gen_range(1..=4);
1206 let buffer_start_excerpt_header_height = rng.gen_range(1..=5);
1207 let excerpt_header_height = rng.gen_range(1..=5);
1208 let font_size = px(14.0);
1209 let max_wrap_width = 300.0;
1210 let mut wrap_width = if rng.gen_bool(0.1) {
1211 None
1212 } else {
1213 Some(px(rng.gen_range(0.0..=max_wrap_width)))
1214 };
1215
1216 log::info!("tab size: {}", tab_size);
1217 log::info!("wrap width: {:?}", wrap_width);
1218
1219 cx.update(|cx| {
1220 init_test(cx, |s| s.defaults.tab_size = NonZeroU32::new(tab_size));
1221 });
1222
1223 let buffer = cx.update(|cx| {
1224 if rng.gen() {
1225 let len = rng.gen_range(0..10);
1226 let text = util::RandomCharIter::new(&mut rng)
1227 .take(len)
1228 .collect::<String>();
1229 MultiBuffer::build_simple(&text, cx)
1230 } else {
1231 MultiBuffer::build_random(&mut rng, cx)
1232 }
1233 });
1234
1235 let map = cx.new_model(|cx| {
1236 DisplayMap::new(
1237 buffer.clone(),
1238 font("Helvetica"),
1239 font_size,
1240 wrap_width,
1241 true,
1242 buffer_start_excerpt_header_height,
1243 excerpt_header_height,
1244 0,
1245 FoldPlaceholder::test(),
1246 cx,
1247 )
1248 });
1249 let mut notifications = observe(&map, cx);
1250 let mut fold_count = 0;
1251 let mut blocks = Vec::new();
1252
1253 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1254 log::info!("buffer text: {:?}", snapshot.buffer_snapshot.text());
1255 log::info!("fold text: {:?}", snapshot.fold_snapshot.text());
1256 log::info!("char text: {:?}", snapshot.char_snapshot.text());
1257 log::info!("wrap text: {:?}", snapshot.wrap_snapshot.text());
1258 log::info!("block text: {:?}", snapshot.block_snapshot.text());
1259 log::info!("display text: {:?}", snapshot.text());
1260
1261 for _i in 0..operations {
1262 match rng.gen_range(0..100) {
1263 0..=19 => {
1264 wrap_width = if rng.gen_bool(0.2) {
1265 None
1266 } else {
1267 Some(px(rng.gen_range(0.0..=max_wrap_width)))
1268 };
1269 log::info!("setting wrap width to {:?}", wrap_width);
1270 map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
1271 }
1272 20..=29 => {
1273 let mut tab_sizes = vec![1, 2, 3, 4];
1274 tab_sizes.remove((tab_size - 1) as usize);
1275 tab_size = *tab_sizes.choose(&mut rng).unwrap();
1276 log::info!("setting tab size to {:?}", tab_size);
1277 cx.update(|cx| {
1278 cx.update_global::<SettingsStore, _>(|store, cx| {
1279 store.update_user_settings::<AllLanguageSettings>(cx, |s| {
1280 s.defaults.tab_size = NonZeroU32::new(tab_size);
1281 });
1282 });
1283 });
1284 }
1285 30..=44 => {
1286 map.update(cx, |map, cx| {
1287 if rng.gen() || blocks.is_empty() {
1288 let buffer = map.snapshot(cx).buffer_snapshot;
1289 let block_properties = (0..rng.gen_range(1..=1))
1290 .map(|_| {
1291 let position =
1292 buffer.anchor_after(buffer.clip_offset(
1293 rng.gen_range(0..=buffer.len()),
1294 Bias::Left,
1295 ));
1296
1297 let placement = if rng.gen() {
1298 BlockPlacement::Above(position)
1299 } else {
1300 BlockPlacement::Below(position)
1301 };
1302 let height = rng.gen_range(1..5);
1303 log::info!(
1304 "inserting block {:?} with height {}",
1305 placement.as_ref().map(|p| p.to_point(&buffer)),
1306 height
1307 );
1308 let priority = rng.gen_range(1..100);
1309 BlockProperties {
1310 placement,
1311 style: BlockStyle::Fixed,
1312 height,
1313 render: Box::new(|_| div().into_any()),
1314 priority,
1315 }
1316 })
1317 .collect::<Vec<_>>();
1318 blocks.extend(map.insert_blocks(block_properties, cx));
1319 } else {
1320 blocks.shuffle(&mut rng);
1321 let remove_count = rng.gen_range(1..=4.min(blocks.len()));
1322 let block_ids_to_remove = (0..remove_count)
1323 .map(|_| blocks.remove(rng.gen_range(0..blocks.len())))
1324 .collect();
1325 log::info!("removing block ids {:?}", block_ids_to_remove);
1326 map.remove_blocks(block_ids_to_remove, cx);
1327 }
1328 });
1329 }
1330 45..=79 => {
1331 let mut ranges = Vec::new();
1332 for _ in 0..rng.gen_range(1..=3) {
1333 buffer.read_with(cx, |buffer, cx| {
1334 let buffer = buffer.read(cx);
1335 let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right);
1336 let start = buffer.clip_offset(rng.gen_range(0..=end), Left);
1337 ranges.push(start..end);
1338 });
1339 }
1340
1341 if rng.gen() && fold_count > 0 {
1342 log::info!("unfolding ranges: {:?}", ranges);
1343 map.update(cx, |map, cx| {
1344 map.unfold(ranges, true, cx);
1345 });
1346 } else {
1347 log::info!("folding ranges: {:?}", ranges);
1348 map.update(cx, |map, cx| {
1349 map.fold(
1350 ranges
1351 .into_iter()
1352 .map(|range| (range, FoldPlaceholder::test())),
1353 cx,
1354 );
1355 });
1356 }
1357 }
1358 _ => {
1359 buffer.update(cx, |buffer, cx| buffer.randomly_mutate(&mut rng, 5, cx));
1360 }
1361 }
1362
1363 if map.read_with(cx, |map, cx| map.is_rewrapping(cx)) {
1364 notifications.next().await.unwrap();
1365 }
1366
1367 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1368 fold_count = snapshot.fold_count();
1369 log::info!("buffer text: {:?}", snapshot.buffer_snapshot.text());
1370 log::info!("fold text: {:?}", snapshot.fold_snapshot.text());
1371 log::info!("char text: {:?}", snapshot.char_snapshot.text());
1372 log::info!("wrap text: {:?}", snapshot.wrap_snapshot.text());
1373 log::info!("block text: {:?}", snapshot.block_snapshot.text());
1374 log::info!("display text: {:?}", snapshot.text());
1375
1376 // Line boundaries
1377 let buffer = &snapshot.buffer_snapshot;
1378 for _ in 0..5 {
1379 let row = rng.gen_range(0..=buffer.max_point().row);
1380 let column = rng.gen_range(0..=buffer.line_len(MultiBufferRow(row)));
1381 let point = buffer.clip_point(Point::new(row, column), Left);
1382
1383 let (prev_buffer_bound, prev_display_bound) = snapshot.prev_line_boundary(point);
1384 let (next_buffer_bound, next_display_bound) = snapshot.next_line_boundary(point);
1385
1386 assert!(prev_buffer_bound <= point);
1387 assert!(next_buffer_bound >= point);
1388 assert_eq!(prev_buffer_bound.column, 0);
1389 assert_eq!(prev_display_bound.column(), 0);
1390 if next_buffer_bound < buffer.max_point() {
1391 assert_eq!(buffer.chars_at(next_buffer_bound).next(), Some('\n'));
1392 }
1393
1394 assert_eq!(
1395 prev_display_bound,
1396 prev_buffer_bound.to_display_point(&snapshot),
1397 "row boundary before {:?}. reported buffer row boundary: {:?}",
1398 point,
1399 prev_buffer_bound
1400 );
1401 assert_eq!(
1402 next_display_bound,
1403 next_buffer_bound.to_display_point(&snapshot),
1404 "display row boundary after {:?}. reported buffer row boundary: {:?}",
1405 point,
1406 next_buffer_bound
1407 );
1408 assert_eq!(
1409 prev_buffer_bound,
1410 prev_display_bound.to_point(&snapshot),
1411 "row boundary before {:?}. reported display row boundary: {:?}",
1412 point,
1413 prev_display_bound
1414 );
1415 assert_eq!(
1416 next_buffer_bound,
1417 next_display_bound.to_point(&snapshot),
1418 "row boundary after {:?}. reported display row boundary: {:?}",
1419 point,
1420 next_display_bound
1421 );
1422 }
1423
1424 // Movement
1425 let min_point = snapshot.clip_point(DisplayPoint::new(DisplayRow(0), 0), Left);
1426 let max_point = snapshot.clip_point(snapshot.max_point(), Right);
1427 for _ in 0..5 {
1428 let row = rng.gen_range(0..=snapshot.max_point().row().0);
1429 let column = rng.gen_range(0..=snapshot.line_len(DisplayRow(row)));
1430 let point = snapshot.clip_point(DisplayPoint::new(DisplayRow(row), column), Left);
1431
1432 log::info!("Moving from point {:?}", point);
1433
1434 let moved_right = movement::right(&snapshot, point);
1435 log::info!("Right {:?}", moved_right);
1436 if point < max_point {
1437 assert!(moved_right > point);
1438 if point.column() == snapshot.line_len(point.row())
1439 || snapshot.soft_wrap_indent(point.row()).is_some()
1440 && point.column() == snapshot.line_len(point.row()) - 1
1441 {
1442 assert!(moved_right.row() > point.row());
1443 }
1444 } else {
1445 assert_eq!(moved_right, point);
1446 }
1447
1448 let moved_left = movement::left(&snapshot, point);
1449 log::info!("Left {:?}", moved_left);
1450 if point > min_point {
1451 assert!(moved_left < point);
1452 if point.column() == 0 {
1453 assert!(moved_left.row() < point.row());
1454 }
1455 } else {
1456 assert_eq!(moved_left, point);
1457 }
1458 }
1459 }
1460 }
1461
1462 #[cfg(target_os = "macos")]
1463 #[gpui::test(retries = 5)]
1464 async fn test_soft_wraps(cx: &mut gpui::TestAppContext) {
1465 cx.background_executor
1466 .set_block_on_ticks(usize::MAX..=usize::MAX);
1467 cx.update(|cx| {
1468 init_test(cx, |_| {});
1469 });
1470
1471 let mut cx = crate::test::editor_test_context::EditorTestContext::new(cx).await;
1472 let editor = cx.editor.clone();
1473 let window = cx.window;
1474
1475 _ = cx.update_window(window, |_, cx| {
1476 let text_layout_details =
1477 editor.update(cx, |editor, cx| editor.text_layout_details(cx));
1478
1479 let font_size = px(12.0);
1480 let wrap_width = Some(px(64.));
1481
1482 let text = "one two three four five\nsix seven eight";
1483 let buffer = MultiBuffer::build_simple(text, cx);
1484 let map = cx.new_model(|cx| {
1485 DisplayMap::new(
1486 buffer.clone(),
1487 font("Helvetica"),
1488 font_size,
1489 wrap_width,
1490 true,
1491 1,
1492 1,
1493 0,
1494 FoldPlaceholder::test(),
1495 cx,
1496 )
1497 });
1498
1499 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1500 assert_eq!(
1501 snapshot.text_chunks(DisplayRow(0)).collect::<String>(),
1502 "one two \nthree four \nfive\nsix seven \neight"
1503 );
1504 assert_eq!(
1505 snapshot.clip_point(DisplayPoint::new(DisplayRow(0), 8), Bias::Left),
1506 DisplayPoint::new(DisplayRow(0), 7)
1507 );
1508 assert_eq!(
1509 snapshot.clip_point(DisplayPoint::new(DisplayRow(0), 8), Bias::Right),
1510 DisplayPoint::new(DisplayRow(1), 0)
1511 );
1512 assert_eq!(
1513 movement::right(&snapshot, DisplayPoint::new(DisplayRow(0), 7)),
1514 DisplayPoint::new(DisplayRow(1), 0)
1515 );
1516 assert_eq!(
1517 movement::left(&snapshot, DisplayPoint::new(DisplayRow(1), 0)),
1518 DisplayPoint::new(DisplayRow(0), 7)
1519 );
1520
1521 let x = snapshot
1522 .x_for_display_point(DisplayPoint::new(DisplayRow(1), 10), &text_layout_details);
1523 assert_eq!(
1524 movement::up(
1525 &snapshot,
1526 DisplayPoint::new(DisplayRow(1), 10),
1527 language::SelectionGoal::None,
1528 false,
1529 &text_layout_details,
1530 ),
1531 (
1532 DisplayPoint::new(DisplayRow(0), 7),
1533 language::SelectionGoal::HorizontalPosition(x.0)
1534 )
1535 );
1536 assert_eq!(
1537 movement::down(
1538 &snapshot,
1539 DisplayPoint::new(DisplayRow(0), 7),
1540 language::SelectionGoal::HorizontalPosition(x.0),
1541 false,
1542 &text_layout_details
1543 ),
1544 (
1545 DisplayPoint::new(DisplayRow(1), 10),
1546 language::SelectionGoal::HorizontalPosition(x.0)
1547 )
1548 );
1549 assert_eq!(
1550 movement::down(
1551 &snapshot,
1552 DisplayPoint::new(DisplayRow(1), 10),
1553 language::SelectionGoal::HorizontalPosition(x.0),
1554 false,
1555 &text_layout_details
1556 ),
1557 (
1558 DisplayPoint::new(DisplayRow(2), 4),
1559 language::SelectionGoal::HorizontalPosition(x.0)
1560 )
1561 );
1562
1563 let ix = snapshot.buffer_snapshot.text().find("seven").unwrap();
1564 buffer.update(cx, |buffer, cx| {
1565 buffer.edit([(ix..ix, "and ")], None, cx);
1566 });
1567
1568 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1569 assert_eq!(
1570 snapshot.text_chunks(DisplayRow(1)).collect::<String>(),
1571 "three four \nfive\nsix and \nseven eight"
1572 );
1573
1574 // Re-wrap on font size changes
1575 map.update(cx, |map, cx| {
1576 map.set_font(font("Helvetica"), px(font_size.0 + 3.), cx)
1577 });
1578
1579 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1580 assert_eq!(
1581 snapshot.text_chunks(DisplayRow(1)).collect::<String>(),
1582 "three \nfour five\nsix and \nseven \neight"
1583 )
1584 });
1585 }
1586
1587 #[gpui::test]
1588 fn test_text_chunks(cx: &mut gpui::AppContext) {
1589 init_test(cx, |_| {});
1590
1591 let text = sample_text(6, 6, 'a');
1592 let buffer = MultiBuffer::build_simple(&text, cx);
1593
1594 let font_size = px(14.0);
1595 let map = cx.new_model(|cx| {
1596 DisplayMap::new(
1597 buffer.clone(),
1598 font("Helvetica"),
1599 font_size,
1600 None,
1601 true,
1602 1,
1603 1,
1604 0,
1605 FoldPlaceholder::test(),
1606 cx,
1607 )
1608 });
1609
1610 buffer.update(cx, |buffer, cx| {
1611 buffer.edit(
1612 vec![
1613 (
1614 MultiBufferPoint::new(1, 0)..MultiBufferPoint::new(1, 0),
1615 "\t",
1616 ),
1617 (
1618 MultiBufferPoint::new(1, 1)..MultiBufferPoint::new(1, 1),
1619 "\t",
1620 ),
1621 (
1622 MultiBufferPoint::new(2, 1)..MultiBufferPoint::new(2, 1),
1623 "\t",
1624 ),
1625 ],
1626 None,
1627 cx,
1628 )
1629 });
1630
1631 assert_eq!(
1632 map.update(cx, |map, cx| map.snapshot(cx))
1633 .text_chunks(DisplayRow(1))
1634 .collect::<String>()
1635 .lines()
1636 .next(),
1637 Some(" b bbbbb")
1638 );
1639 assert_eq!(
1640 map.update(cx, |map, cx| map.snapshot(cx))
1641 .text_chunks(DisplayRow(2))
1642 .collect::<String>()
1643 .lines()
1644 .next(),
1645 Some("c ccccc")
1646 );
1647 }
1648
1649 #[gpui::test]
1650 async fn test_chunks(cx: &mut gpui::TestAppContext) {
1651 use unindent::Unindent as _;
1652
1653 let text = r#"
1654 fn outer() {}
1655
1656 mod module {
1657 fn inner() {}
1658 }"#
1659 .unindent();
1660
1661 let theme =
1662 SyntaxTheme::new_test(vec![("mod.body", Hsla::red()), ("fn.name", Hsla::blue())]);
1663 let language = Arc::new(
1664 Language::new(
1665 LanguageConfig {
1666 name: "Test".into(),
1667 matcher: LanguageMatcher {
1668 path_suffixes: vec![".test".to_string()],
1669 ..Default::default()
1670 },
1671 ..Default::default()
1672 },
1673 Some(tree_sitter_rust::LANGUAGE.into()),
1674 )
1675 .with_highlights_query(
1676 r#"
1677 (mod_item name: (identifier) body: _ @mod.body)
1678 (function_item name: (identifier) @fn.name)
1679 "#,
1680 )
1681 .unwrap(),
1682 );
1683 language.set_theme(&theme);
1684
1685 cx.update(|cx| init_test(cx, |s| s.defaults.tab_size = Some(2.try_into().unwrap())));
1686
1687 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
1688 cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
1689 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
1690
1691 let font_size = px(14.0);
1692
1693 let map = cx.new_model(|cx| {
1694 DisplayMap::new(
1695 buffer,
1696 font("Helvetica"),
1697 font_size,
1698 None,
1699 true,
1700 1,
1701 1,
1702 1,
1703 FoldPlaceholder::test(),
1704 cx,
1705 )
1706 });
1707 assert_eq!(
1708 cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(5), &map, &theme, cx)),
1709 vec![
1710 ("fn ".to_string(), None),
1711 ("outer".to_string(), Some(Hsla::blue())),
1712 ("() {}\n\nmod module ".to_string(), None),
1713 ("{\n fn ".to_string(), Some(Hsla::red())),
1714 ("inner".to_string(), Some(Hsla::blue())),
1715 ("() {}\n}".to_string(), Some(Hsla::red())),
1716 ]
1717 );
1718 assert_eq!(
1719 cx.update(|cx| syntax_chunks(DisplayRow(3)..DisplayRow(5), &map, &theme, cx)),
1720 vec![
1721 (" fn ".to_string(), Some(Hsla::red())),
1722 ("inner".to_string(), Some(Hsla::blue())),
1723 ("() {}\n}".to_string(), Some(Hsla::red())),
1724 ]
1725 );
1726
1727 map.update(cx, |map, cx| {
1728 map.fold(
1729 vec![(
1730 MultiBufferPoint::new(0, 6)..MultiBufferPoint::new(3, 2),
1731 FoldPlaceholder::test(),
1732 )],
1733 cx,
1734 )
1735 });
1736 assert_eq!(
1737 cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(2), &map, &theme, cx)),
1738 vec![
1739 ("fn ".to_string(), None),
1740 ("out".to_string(), Some(Hsla::blue())),
1741 ("⋯".to_string(), None),
1742 (" fn ".to_string(), Some(Hsla::red())),
1743 ("inner".to_string(), Some(Hsla::blue())),
1744 ("() {}\n}".to_string(), Some(Hsla::red())),
1745 ]
1746 );
1747 }
1748
1749 // todo(linux) fails due to pixel differences in text rendering
1750 #[cfg(target_os = "macos")]
1751 #[gpui::test]
1752 async fn test_chunks_with_soft_wrapping(cx: &mut gpui::TestAppContext) {
1753 use unindent::Unindent as _;
1754
1755 cx.background_executor
1756 .set_block_on_ticks(usize::MAX..=usize::MAX);
1757
1758 let text = r#"
1759 fn outer() {}
1760
1761 mod module {
1762 fn inner() {}
1763 }"#
1764 .unindent();
1765
1766 let theme =
1767 SyntaxTheme::new_test(vec![("mod.body", Hsla::red()), ("fn.name", Hsla::blue())]);
1768 let language = Arc::new(
1769 Language::new(
1770 LanguageConfig {
1771 name: "Test".into(),
1772 matcher: LanguageMatcher {
1773 path_suffixes: vec![".test".to_string()],
1774 ..Default::default()
1775 },
1776 ..Default::default()
1777 },
1778 Some(tree_sitter_rust::LANGUAGE.into()),
1779 )
1780 .with_highlights_query(
1781 r#"
1782 (mod_item name: (identifier) body: _ @mod.body)
1783 (function_item name: (identifier) @fn.name)
1784 "#,
1785 )
1786 .unwrap(),
1787 );
1788 language.set_theme(&theme);
1789
1790 cx.update(|cx| init_test(cx, |_| {}));
1791
1792 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
1793 cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
1794 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
1795
1796 let font_size = px(16.0);
1797
1798 let map = cx.new_model(|cx| {
1799 DisplayMap::new(
1800 buffer,
1801 font("Courier"),
1802 font_size,
1803 Some(px(40.0)),
1804 true,
1805 1,
1806 1,
1807 0,
1808 FoldPlaceholder::test(),
1809 cx,
1810 )
1811 });
1812 assert_eq!(
1813 cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(5), &map, &theme, cx)),
1814 [
1815 ("fn \n".to_string(), None),
1816 ("oute\nr".to_string(), Some(Hsla::blue())),
1817 ("() \n{}\n\n".to_string(), None),
1818 ]
1819 );
1820 assert_eq!(
1821 cx.update(|cx| syntax_chunks(DisplayRow(3)..DisplayRow(5), &map, &theme, cx)),
1822 [("{}\n\n".to_string(), None)]
1823 );
1824
1825 map.update(cx, |map, cx| {
1826 map.fold(
1827 vec![(
1828 MultiBufferPoint::new(0, 6)..MultiBufferPoint::new(3, 2),
1829 FoldPlaceholder::test(),
1830 )],
1831 cx,
1832 )
1833 });
1834 assert_eq!(
1835 cx.update(|cx| syntax_chunks(DisplayRow(1)..DisplayRow(4), &map, &theme, cx)),
1836 [
1837 ("out".to_string(), Some(Hsla::blue())),
1838 ("⋯\n".to_string(), None),
1839 (" \nfn ".to_string(), Some(Hsla::red())),
1840 ("i\n".to_string(), Some(Hsla::blue()))
1841 ]
1842 );
1843 }
1844
1845 #[gpui::test]
1846 async fn test_chunks_with_text_highlights(cx: &mut gpui::TestAppContext) {
1847 cx.update(|cx| init_test(cx, |_| {}));
1848
1849 let theme =
1850 SyntaxTheme::new_test(vec![("operator", Hsla::red()), ("string", Hsla::green())]);
1851 let language = Arc::new(
1852 Language::new(
1853 LanguageConfig {
1854 name: "Test".into(),
1855 matcher: LanguageMatcher {
1856 path_suffixes: vec![".test".to_string()],
1857 ..Default::default()
1858 },
1859 ..Default::default()
1860 },
1861 Some(tree_sitter_rust::LANGUAGE.into()),
1862 )
1863 .with_highlights_query(
1864 r#"
1865 ":" @operator
1866 (string_literal) @string
1867 "#,
1868 )
1869 .unwrap(),
1870 );
1871 language.set_theme(&theme);
1872
1873 let (text, highlighted_ranges) = marked_text_ranges(r#"constˇ «a»: B = "c «d»""#, false);
1874
1875 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
1876 cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
1877
1878 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
1879 let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
1880
1881 let font_size = px(16.0);
1882 let map = cx.new_model(|cx| {
1883 DisplayMap::new(
1884 buffer,
1885 font("Courier"),
1886 font_size,
1887 None,
1888 true,
1889 1,
1890 1,
1891 1,
1892 FoldPlaceholder::test(),
1893 cx,
1894 )
1895 });
1896
1897 enum MyType {}
1898
1899 let style = HighlightStyle {
1900 color: Some(Hsla::blue()),
1901 ..Default::default()
1902 };
1903
1904 map.update(cx, |map, _cx| {
1905 map.highlight_text(
1906 TypeId::of::<MyType>(),
1907 highlighted_ranges
1908 .into_iter()
1909 .map(|range| {
1910 buffer_snapshot.anchor_before(range.start)
1911 ..buffer_snapshot.anchor_before(range.end)
1912 })
1913 .collect(),
1914 style,
1915 );
1916 });
1917
1918 assert_eq!(
1919 cx.update(|cx| chunks(DisplayRow(0)..DisplayRow(10), &map, &theme, cx)),
1920 [
1921 ("const ".to_string(), None, None),
1922 ("a".to_string(), None, Some(Hsla::blue())),
1923 (":".to_string(), Some(Hsla::red()), None),
1924 (" B = ".to_string(), None, None),
1925 ("\"c ".to_string(), Some(Hsla::green()), None),
1926 ("d".to_string(), Some(Hsla::green()), Some(Hsla::blue())),
1927 ("\"".to_string(), Some(Hsla::green()), None),
1928 ]
1929 );
1930 }
1931
1932 #[gpui::test]
1933 fn test_clip_point(cx: &mut gpui::AppContext) {
1934 init_test(cx, |_| {});
1935
1936 fn assert(text: &str, shift_right: bool, bias: Bias, cx: &mut gpui::AppContext) {
1937 let (unmarked_snapshot, mut markers) = marked_display_snapshot(text, cx);
1938
1939 match bias {
1940 Bias::Left => {
1941 if shift_right {
1942 *markers[1].column_mut() += 1;
1943 }
1944
1945 assert_eq!(unmarked_snapshot.clip_point(markers[1], bias), markers[0])
1946 }
1947 Bias::Right => {
1948 if shift_right {
1949 *markers[0].column_mut() += 1;
1950 }
1951
1952 assert_eq!(unmarked_snapshot.clip_point(markers[0], bias), markers[1])
1953 }
1954 };
1955 }
1956
1957 use Bias::{Left, Right};
1958 assert("ˇˇα", false, Left, cx);
1959 assert("ˇˇα", true, Left, cx);
1960 assert("ˇˇα", false, Right, cx);
1961 assert("ˇαˇ", true, Right, cx);
1962 assert("ˇˇ✋", false, Left, cx);
1963 assert("ˇˇ✋", true, Left, cx);
1964 assert("ˇˇ✋", false, Right, cx);
1965 assert("ˇ✋ˇ", true, Right, cx);
1966 assert("ˇˇ🍐", false, Left, cx);
1967 assert("ˇˇ🍐", true, Left, cx);
1968 assert("ˇˇ🍐", false, Right, cx);
1969 assert("ˇ🍐ˇ", true, Right, cx);
1970 assert("ˇˇ\t", false, Left, cx);
1971 assert("ˇˇ\t", true, Left, cx);
1972 assert("ˇˇ\t", false, Right, cx);
1973 assert("ˇ\tˇ", true, Right, cx);
1974 assert(" ˇˇ\t", false, Left, cx);
1975 assert(" ˇˇ\t", true, Left, cx);
1976 assert(" ˇˇ\t", false, Right, cx);
1977 assert(" ˇ\tˇ", true, Right, cx);
1978 assert(" ˇˇ\t", false, Left, cx);
1979 assert(" ˇˇ\t", false, Right, cx);
1980 }
1981
1982 #[gpui::test]
1983 fn test_clip_at_line_ends(cx: &mut gpui::AppContext) {
1984 init_test(cx, |_| {});
1985
1986 fn assert(text: &str, cx: &mut gpui::AppContext) {
1987 let (mut unmarked_snapshot, markers) = marked_display_snapshot(text, cx);
1988 unmarked_snapshot.clip_at_line_ends = true;
1989 assert_eq!(
1990 unmarked_snapshot.clip_point(markers[1], Bias::Left),
1991 markers[0]
1992 );
1993 }
1994
1995 assert("ˇˇ", cx);
1996 assert("ˇaˇ", cx);
1997 assert("aˇbˇ", cx);
1998 assert("aˇαˇ", cx);
1999 }
2000
2001 #[gpui::test]
2002 fn test_creases(cx: &mut gpui::AppContext) {
2003 init_test(cx, |_| {});
2004
2005 let text = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll";
2006 let buffer = MultiBuffer::build_simple(text, cx);
2007 let font_size = px(14.0);
2008 cx.new_model(|cx| {
2009 let mut map = DisplayMap::new(
2010 buffer.clone(),
2011 font("Helvetica"),
2012 font_size,
2013 None,
2014 true,
2015 1,
2016 1,
2017 0,
2018 FoldPlaceholder::test(),
2019 cx,
2020 );
2021 let snapshot = map.buffer.read(cx).snapshot(cx);
2022 let range =
2023 snapshot.anchor_before(Point::new(2, 0))..snapshot.anchor_after(Point::new(3, 3));
2024
2025 map.crease_map.insert(
2026 [Crease::new(
2027 range,
2028 FoldPlaceholder::test(),
2029 |_row, _status, _toggle, _cx| div(),
2030 |_row, _status, _cx| div(),
2031 )],
2032 &map.buffer.read(cx).snapshot(cx),
2033 );
2034
2035 map
2036 });
2037 }
2038
2039 #[gpui::test]
2040 fn test_tabs_with_multibyte_chars(cx: &mut gpui::AppContext) {
2041 init_test(cx, |_| {});
2042
2043 let text = "✅\t\tα\nβ\t\n🏀β\t\tγ";
2044 let buffer = MultiBuffer::build_simple(text, cx);
2045 let font_size = px(14.0);
2046
2047 let map = cx.new_model(|cx| {
2048 DisplayMap::new(
2049 buffer.clone(),
2050 font("Helvetica"),
2051 font_size,
2052 None,
2053 true,
2054 1,
2055 1,
2056 0,
2057 FoldPlaceholder::test(),
2058 cx,
2059 )
2060 });
2061 let map = map.update(cx, |map, cx| map.snapshot(cx));
2062 assert_eq!(map.text(), "✅ α\nβ \n🏀β γ");
2063 assert_eq!(
2064 map.text_chunks(DisplayRow(0)).collect::<String>(),
2065 "✅ α\nβ \n🏀β γ"
2066 );
2067 assert_eq!(
2068 map.text_chunks(DisplayRow(1)).collect::<String>(),
2069 "β \n🏀β γ"
2070 );
2071 assert_eq!(
2072 map.text_chunks(DisplayRow(2)).collect::<String>(),
2073 "🏀β γ"
2074 );
2075
2076 let point = MultiBufferPoint::new(0, "✅\t\t".len() as u32);
2077 let display_point = DisplayPoint::new(DisplayRow(0), "✅ ".len() as u32);
2078 assert_eq!(point.to_display_point(&map), display_point);
2079 assert_eq!(display_point.to_point(&map), point);
2080
2081 let point = MultiBufferPoint::new(1, "β\t".len() as u32);
2082 let display_point = DisplayPoint::new(DisplayRow(1), "β ".len() as u32);
2083 assert_eq!(point.to_display_point(&map), display_point);
2084 assert_eq!(display_point.to_point(&map), point,);
2085
2086 let point = MultiBufferPoint::new(2, "🏀β\t\t".len() as u32);
2087 let display_point = DisplayPoint::new(DisplayRow(2), "🏀β ".len() as u32);
2088 assert_eq!(point.to_display_point(&map), display_point);
2089 assert_eq!(display_point.to_point(&map), point,);
2090
2091 // Display points inside of expanded tabs
2092 assert_eq!(
2093 DisplayPoint::new(DisplayRow(0), "✅ ".len() as u32).to_point(&map),
2094 MultiBufferPoint::new(0, "✅\t".len() as u32),
2095 );
2096 assert_eq!(
2097 DisplayPoint::new(DisplayRow(0), "✅ ".len() as u32).to_point(&map),
2098 MultiBufferPoint::new(0, "✅".len() as u32),
2099 );
2100
2101 // Clipping display points inside of multi-byte characters
2102 assert_eq!(
2103 map.clip_point(
2104 DisplayPoint::new(DisplayRow(0), "✅".len() as u32 - 1),
2105 Left
2106 ),
2107 DisplayPoint::new(DisplayRow(0), 0)
2108 );
2109 assert_eq!(
2110 map.clip_point(
2111 DisplayPoint::new(DisplayRow(0), "✅".len() as u32 - 1),
2112 Bias::Right
2113 ),
2114 DisplayPoint::new(DisplayRow(0), "✅".len() as u32)
2115 );
2116 }
2117
2118 #[gpui::test]
2119 fn test_max_point(cx: &mut gpui::AppContext) {
2120 init_test(cx, |_| {});
2121
2122 let buffer = MultiBuffer::build_simple("aaa\n\t\tbbb", cx);
2123 let font_size = px(14.0);
2124 let map = cx.new_model(|cx| {
2125 DisplayMap::new(
2126 buffer.clone(),
2127 font("Helvetica"),
2128 font_size,
2129 None,
2130 true,
2131 1,
2132 1,
2133 0,
2134 FoldPlaceholder::test(),
2135 cx,
2136 )
2137 });
2138 assert_eq!(
2139 map.update(cx, |map, cx| map.snapshot(cx)).max_point(),
2140 DisplayPoint::new(DisplayRow(1), 11)
2141 )
2142 }
2143
2144 fn syntax_chunks(
2145 rows: Range<DisplayRow>,
2146 map: &Model<DisplayMap>,
2147 theme: &SyntaxTheme,
2148 cx: &mut AppContext,
2149 ) -> Vec<(String, Option<Hsla>)> {
2150 chunks(rows, map, theme, cx)
2151 .into_iter()
2152 .map(|(text, color, _)| (text, color))
2153 .collect()
2154 }
2155
2156 fn chunks(
2157 rows: Range<DisplayRow>,
2158 map: &Model<DisplayMap>,
2159 theme: &SyntaxTheme,
2160 cx: &mut AppContext,
2161 ) -> Vec<(String, Option<Hsla>, Option<Hsla>)> {
2162 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
2163 let mut chunks: Vec<(String, Option<Hsla>, Option<Hsla>)> = Vec::new();
2164 for chunk in snapshot.chunks(rows, true, HighlightStyles::default()) {
2165 let syntax_color = chunk
2166 .syntax_highlight_id
2167 .and_then(|id| id.style(theme)?.color);
2168 let highlight_color = chunk.highlight_style.and_then(|style| style.color);
2169 if let Some((last_chunk, last_syntax_color, last_highlight_color)) = chunks.last_mut() {
2170 if syntax_color == *last_syntax_color && highlight_color == *last_highlight_color {
2171 last_chunk.push_str(chunk.text);
2172 continue;
2173 }
2174 }
2175 chunks.push((chunk.text.to_string(), syntax_color, highlight_color));
2176 }
2177 chunks
2178 }
2179
2180 fn init_test(cx: &mut AppContext, f: impl Fn(&mut AllLanguageSettingsContent)) {
2181 let settings = SettingsStore::test(cx);
2182 cx.set_global(settings);
2183 language::init(cx);
2184 crate::init(cx);
2185 Project::init_settings(cx);
2186 theme::init(LoadThemes::JustBase, cx);
2187 cx.update_global::<SettingsStore, _>(|store, cx| {
2188 store.update_user_settings::<AllLanguageSettings>(cx, f);
2189 });
2190 }
2191}