1mod block_map;
2mod fold_map;
3mod tab_map;
4mod wrap_map;
5
6use crate::{Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint};
7pub use block_map::{BlockMap, BlockPoint};
8use collections::{HashMap, HashSet};
9use fold_map::FoldMap;
10use gpui::{
11 color::Color,
12 fonts::{FontId, HighlightStyle},
13 Entity, ModelContext, ModelHandle,
14};
15use language::{OffsetUtf16, Point, Subscription as BufferSubscription};
16use settings::Settings;
17use std::{any::TypeId, fmt::Debug, num::NonZeroU32, ops::Range, sync::Arc};
18use sum_tree::{Bias, TreeMap};
19use tab_map::TabMap;
20use wrap_map::WrapMap;
21
22pub use block_map::{
23 BlockBufferRows as DisplayBufferRows, BlockChunks as DisplayChunks, BlockContext,
24 BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock, TransformBlock,
25};
26
27use self::tab_map::TabSnapshot;
28
29#[derive(Copy, Clone, Debug, PartialEq, Eq)]
30pub enum FoldStatus {
31 Folded,
32 Foldable,
33}
34
35pub trait ToDisplayPoint {
36 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint;
37}
38
39type TextHighlights = TreeMap<Option<TypeId>, Arc<(HighlightStyle, Vec<Range<Anchor>>)>>;
40
41pub struct DisplayMap {
42 buffer: ModelHandle<MultiBuffer>,
43 buffer_subscription: BufferSubscription,
44 fold_map: FoldMap,
45 tab_map: TabMap,
46 wrap_map: ModelHandle<WrapMap>,
47 block_map: BlockMap,
48 text_highlights: TextHighlights,
49 pub clip_at_line_ends: bool,
50}
51
52impl Entity for DisplayMap {
53 type Event = ();
54}
55
56impl DisplayMap {
57 pub fn new(
58 buffer: ModelHandle<MultiBuffer>,
59 font_id: FontId,
60 font_size: f32,
61 wrap_width: Option<f32>,
62 buffer_header_height: u8,
63 excerpt_header_height: u8,
64 cx: &mut ModelContext<Self>,
65 ) -> Self {
66 let buffer_subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
67
68 let tab_size = Self::tab_size(&buffer, cx);
69 let (fold_map, snapshot) = FoldMap::new(buffer.read(cx).snapshot(cx));
70 let (tab_map, snapshot) = TabMap::new(snapshot, tab_size);
71 let (wrap_map, snapshot) = WrapMap::new(snapshot, font_id, font_size, wrap_width, cx);
72 let block_map = BlockMap::new(snapshot, buffer_header_height, excerpt_header_height);
73 cx.observe(&wrap_map, |_, _, cx| cx.notify()).detach();
74 DisplayMap {
75 buffer,
76 buffer_subscription,
77 fold_map,
78 tab_map,
79 wrap_map,
80 block_map,
81 text_highlights: Default::default(),
82 clip_at_line_ends: false,
83 }
84 }
85
86 pub fn snapshot(&self, cx: &mut ModelContext<Self>) -> DisplaySnapshot {
87 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
88 let edits = self.buffer_subscription.consume().into_inner();
89 let (folds_snapshot, edits) = self.fold_map.read(buffer_snapshot, edits);
90
91 let tab_size = Self::tab_size(&self.buffer, cx);
92 let (tabs_snapshot, edits) = self.tab_map.sync(folds_snapshot.clone(), edits, tab_size);
93 let (wraps_snapshot, edits) = self
94 .wrap_map
95 .update(cx, |map, cx| map.sync(tabs_snapshot.clone(), edits, cx));
96 let blocks_snapshot = self.block_map.read(wraps_snapshot.clone(), edits);
97
98 DisplaySnapshot {
99 buffer_snapshot: self.buffer.read(cx).snapshot(cx),
100 folds_snapshot,
101 tabs_snapshot,
102 wraps_snapshot,
103 blocks_snapshot,
104 text_highlights: self.text_highlights.clone(),
105 clip_at_line_ends: self.clip_at_line_ends,
106 }
107 }
108
109 pub fn set_state(&mut self, other: &DisplaySnapshot, cx: &mut ModelContext<Self>) {
110 self.fold(
111 other
112 .folds_in_range(0..other.buffer_snapshot.len())
113 .map(|fold| fold.to_offset(&other.buffer_snapshot)),
114 cx,
115 );
116 }
117
118 pub fn fold<T: ToOffset>(
119 &mut self,
120 ranges: impl IntoIterator<Item = Range<T>>,
121 cx: &mut ModelContext<Self>,
122 ) {
123 let snapshot = self.buffer.read(cx).snapshot(cx);
124 let edits = self.buffer_subscription.consume().into_inner();
125 let tab_size = Self::tab_size(&self.buffer, cx);
126 let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
127 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
128 let (snapshot, edits) = self
129 .wrap_map
130 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
131 self.block_map.read(snapshot, edits);
132 let (snapshot, edits) = fold_map.fold(ranges);
133 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
134 let (snapshot, edits) = self
135 .wrap_map
136 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
137 self.block_map.read(snapshot, edits);
138 }
139
140 pub fn unfold<T: ToOffset>(
141 &mut self,
142 ranges: impl IntoIterator<Item = Range<T>>,
143 inclusive: bool,
144 cx: &mut ModelContext<Self>,
145 ) {
146 let snapshot = self.buffer.read(cx).snapshot(cx);
147 let edits = self.buffer_subscription.consume().into_inner();
148 let tab_size = Self::tab_size(&self.buffer, cx);
149 let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
150 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
151 let (snapshot, edits) = self
152 .wrap_map
153 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
154 self.block_map.read(snapshot, edits);
155 let (snapshot, edits) = fold_map.unfold(ranges, inclusive);
156 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
157 let (snapshot, edits) = self
158 .wrap_map
159 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
160 self.block_map.read(snapshot, edits);
161 }
162
163 pub fn insert_blocks(
164 &mut self,
165 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
166 cx: &mut ModelContext<Self>,
167 ) -> Vec<BlockId> {
168 let snapshot = self.buffer.read(cx).snapshot(cx);
169 let edits = self.buffer_subscription.consume().into_inner();
170 let tab_size = Self::tab_size(&self.buffer, cx);
171 let (snapshot, edits) = self.fold_map.read(snapshot, edits);
172 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
173 let (snapshot, edits) = self
174 .wrap_map
175 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
176 let mut block_map = self.block_map.write(snapshot, edits);
177 block_map.insert(blocks)
178 }
179
180 pub fn replace_blocks(&mut self, styles: HashMap<BlockId, RenderBlock>) {
181 self.block_map.replace(styles);
182 }
183
184 pub fn remove_blocks(&mut self, ids: HashSet<BlockId>, cx: &mut ModelContext<Self>) {
185 let snapshot = self.buffer.read(cx).snapshot(cx);
186 let edits = self.buffer_subscription.consume().into_inner();
187 let tab_size = Self::tab_size(&self.buffer, cx);
188 let (snapshot, edits) = self.fold_map.read(snapshot, edits);
189 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
190 let (snapshot, edits) = self
191 .wrap_map
192 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
193 let mut block_map = self.block_map.write(snapshot, edits);
194 block_map.remove(ids);
195 }
196
197 pub fn highlight_text(
198 &mut self,
199 type_id: TypeId,
200 ranges: Vec<Range<Anchor>>,
201 style: HighlightStyle,
202 ) {
203 self.text_highlights
204 .insert(Some(type_id), Arc::new((style, ranges)));
205 }
206
207 pub fn text_highlights(&self, type_id: TypeId) -> Option<(HighlightStyle, &[Range<Anchor>])> {
208 let highlights = self.text_highlights.get(&Some(type_id))?;
209 Some((highlights.0, &highlights.1))
210 }
211
212 pub fn clear_text_highlights(
213 &mut self,
214 type_id: TypeId,
215 ) -> Option<Arc<(HighlightStyle, Vec<Range<Anchor>>)>> {
216 self.text_highlights.remove(&Some(type_id))
217 }
218
219 pub fn set_font(&self, font_id: FontId, font_size: f32, cx: &mut ModelContext<Self>) -> bool {
220 self.wrap_map
221 .update(cx, |map, cx| map.set_font(font_id, font_size, cx))
222 }
223
224 pub fn set_fold_ellipses_color(&mut self, color: Color) -> bool {
225 self.fold_map.set_ellipses_color(color)
226 }
227
228 pub fn set_wrap_width(&self, width: Option<f32>, cx: &mut ModelContext<Self>) -> bool {
229 self.wrap_map
230 .update(cx, |map, cx| map.set_wrap_width(width, cx))
231 }
232
233 fn tab_size(buffer: &ModelHandle<MultiBuffer>, cx: &mut ModelContext<Self>) -> NonZeroU32 {
234 let language_name = buffer
235 .read(cx)
236 .as_singleton()
237 .and_then(|buffer| buffer.read(cx).language())
238 .map(|language| language.name());
239
240 cx.global::<Settings>().tab_size(language_name.as_deref())
241 }
242
243 #[cfg(test)]
244 pub fn is_rewrapping(&self, cx: &gpui::AppContext) -> bool {
245 self.wrap_map.read(cx).is_rewrapping()
246 }
247}
248
249pub struct DisplaySnapshot {
250 pub buffer_snapshot: MultiBufferSnapshot,
251 folds_snapshot: fold_map::FoldSnapshot,
252 tabs_snapshot: tab_map::TabSnapshot,
253 wraps_snapshot: wrap_map::WrapSnapshot,
254 blocks_snapshot: block_map::BlockSnapshot,
255 text_highlights: TextHighlights,
256 clip_at_line_ends: bool,
257}
258
259impl DisplaySnapshot {
260 #[cfg(test)]
261 pub fn fold_count(&self) -> usize {
262 self.folds_snapshot.fold_count()
263 }
264
265 pub fn is_empty(&self) -> bool {
266 self.buffer_snapshot.len() == 0
267 }
268
269 pub fn buffer_rows(&self, start_row: u32) -> DisplayBufferRows {
270 self.blocks_snapshot.buffer_rows(start_row)
271 }
272
273 pub fn max_buffer_row(&self) -> u32 {
274 self.buffer_snapshot.max_buffer_row()
275 }
276
277 pub fn prev_line_boundary(&self, mut point: Point) -> (Point, DisplayPoint) {
278 loop {
279 let mut fold_point = self.folds_snapshot.to_fold_point(point, Bias::Left);
280 *fold_point.column_mut() = 0;
281 point = fold_point.to_buffer_point(&self.folds_snapshot);
282
283 let mut display_point = self.point_to_display_point(point, Bias::Left);
284 *display_point.column_mut() = 0;
285 let next_point = self.display_point_to_point(display_point, Bias::Left);
286 if next_point == point {
287 return (point, display_point);
288 }
289 point = next_point;
290 }
291 }
292
293 pub fn next_line_boundary(&self, mut point: Point) -> (Point, DisplayPoint) {
294 loop {
295 let mut fold_point = self.folds_snapshot.to_fold_point(point, Bias::Right);
296 *fold_point.column_mut() = self.folds_snapshot.line_len(fold_point.row());
297 point = fold_point.to_buffer_point(&self.folds_snapshot);
298
299 let mut display_point = self.point_to_display_point(point, Bias::Right);
300 *display_point.column_mut() = self.line_len(display_point.row());
301 let next_point = self.display_point_to_point(display_point, Bias::Right);
302 if next_point == point {
303 return (point, display_point);
304 }
305 point = next_point;
306 }
307 }
308
309 pub fn expand_to_line(&self, range: Range<Point>) -> Range<Point> {
310 let mut new_start = self.prev_line_boundary(range.start).0;
311 let mut new_end = self.next_line_boundary(range.end).0;
312
313 if new_start.row == range.start.row && new_end.row == range.end.row {
314 if new_end.row < self.buffer_snapshot.max_point().row {
315 new_end.row += 1;
316 new_end.column = 0;
317 } else if new_start.row > 0 {
318 new_start.row -= 1;
319 new_start.column = self.buffer_snapshot.line_len(new_start.row);
320 }
321 }
322
323 new_start..new_end
324 }
325
326 fn point_to_display_point(&self, point: Point, bias: Bias) -> DisplayPoint {
327 let fold_point = self.folds_snapshot.to_fold_point(point, bias);
328 let tab_point = self.tabs_snapshot.to_tab_point(fold_point);
329 let wrap_point = self.wraps_snapshot.tab_point_to_wrap_point(tab_point);
330 let block_point = self.blocks_snapshot.to_block_point(wrap_point);
331 DisplayPoint(block_point)
332 }
333
334 fn display_point_to_point(&self, point: DisplayPoint, bias: Bias) -> Point {
335 let block_point = point.0;
336 let wrap_point = self.blocks_snapshot.to_wrap_point(block_point);
337 let tab_point = self.wraps_snapshot.to_tab_point(wrap_point);
338 let fold_point = self.tabs_snapshot.to_fold_point(tab_point, bias).0;
339 fold_point.to_buffer_point(&self.folds_snapshot)
340 }
341
342 pub fn max_point(&self) -> DisplayPoint {
343 DisplayPoint(self.blocks_snapshot.max_point())
344 }
345
346 /// Returns text chunks starting at the given display row until the end of the file
347 pub fn text_chunks(&self, display_row: u32) -> impl Iterator<Item = &str> {
348 self.blocks_snapshot
349 .chunks(display_row..self.max_point().row() + 1, false, None)
350 .map(|h| h.text)
351 }
352
353 /// Returns text chunks starting at the end of the given display row in reverse until the start of the file
354 pub fn reverse_text_chunks(&self, display_row: u32) -> impl Iterator<Item = &str> {
355 (0..=display_row).into_iter().rev().flat_map(|row| {
356 self.blocks_snapshot
357 .chunks(row..row + 1, false, None)
358 .map(|h| h.text)
359 .collect::<Vec<_>>()
360 .into_iter()
361 .rev()
362 })
363 }
364
365 pub fn chunks(&self, display_rows: Range<u32>, language_aware: bool) -> DisplayChunks<'_> {
366 self.blocks_snapshot
367 .chunks(display_rows, language_aware, Some(&self.text_highlights))
368 }
369
370 pub fn chars_at(
371 &self,
372 mut point: DisplayPoint,
373 ) -> impl Iterator<Item = (char, DisplayPoint)> + '_ {
374 point = DisplayPoint(self.blocks_snapshot.clip_point(point.0, Bias::Left));
375 self.text_chunks(point.row())
376 .flat_map(str::chars)
377 .skip_while({
378 let mut column = 0;
379 move |char| {
380 let at_point = column >= point.column();
381 column += char.len_utf8() as u32;
382 !at_point
383 }
384 })
385 .map(move |ch| {
386 let result = (ch, point);
387 if ch == '\n' {
388 *point.row_mut() += 1;
389 *point.column_mut() = 0;
390 } else {
391 *point.column_mut() += ch.len_utf8() as u32;
392 }
393 result
394 })
395 }
396
397 pub fn reverse_chars_at(
398 &self,
399 mut point: DisplayPoint,
400 ) -> impl Iterator<Item = (char, DisplayPoint)> + '_ {
401 point = DisplayPoint(self.blocks_snapshot.clip_point(point.0, Bias::Left));
402 self.reverse_text_chunks(point.row())
403 .flat_map(|chunk| chunk.chars().rev())
404 .skip_while({
405 let mut column = self.line_len(point.row());
406 if self.max_point().row() > point.row() {
407 column += 1;
408 }
409
410 move |char| {
411 let at_point = column <= point.column();
412 column = column.saturating_sub(char.len_utf8() as u32);
413 !at_point
414 }
415 })
416 .map(move |ch| {
417 if ch == '\n' {
418 *point.row_mut() -= 1;
419 *point.column_mut() = self.line_len(point.row());
420 } else {
421 *point.column_mut() = point.column().saturating_sub(ch.len_utf8() as u32);
422 }
423 (ch, point)
424 })
425 }
426
427 /// Returns an iterator of the start positions of the occurances of `target` in the `self` after `from`
428 /// Stops if `condition` returns false for any of the character position pairs observed.
429 pub fn find_while<'a>(
430 &'a self,
431 from: DisplayPoint,
432 target: &str,
433 condition: impl FnMut(char, DisplayPoint) -> bool + 'a,
434 ) -> impl Iterator<Item = DisplayPoint> + 'a {
435 Self::find_internal(self.chars_at(from), target.chars().collect(), condition)
436 }
437
438 /// Returns an iterator of the end positions of the occurances of `target` in the `self` before `from`
439 /// Stops if `condition` returns false for any of the character position pairs observed.
440 pub fn reverse_find_while<'a>(
441 &'a self,
442 from: DisplayPoint,
443 target: &str,
444 condition: impl FnMut(char, DisplayPoint) -> bool + 'a,
445 ) -> impl Iterator<Item = DisplayPoint> + 'a {
446 Self::find_internal(
447 self.reverse_chars_at(from),
448 target.chars().rev().collect(),
449 condition,
450 )
451 }
452
453 fn find_internal<'a>(
454 iterator: impl Iterator<Item = (char, DisplayPoint)> + 'a,
455 target: Vec<char>,
456 mut condition: impl FnMut(char, DisplayPoint) -> bool + 'a,
457 ) -> impl Iterator<Item = DisplayPoint> + 'a {
458 // List of partial matches with the index of the last seen character in target and the starting point of the match
459 let mut partial_matches: Vec<(usize, DisplayPoint)> = Vec::new();
460 iterator
461 .take_while(move |(ch, point)| condition(*ch, *point))
462 .filter_map(move |(ch, point)| {
463 if Some(&ch) == target.get(0) {
464 partial_matches.push((0, point));
465 }
466
467 let mut found = None;
468 // Keep partial matches that have the correct next character
469 partial_matches.retain_mut(|(match_position, match_start)| {
470 if target.get(*match_position) == Some(&ch) {
471 *match_position += 1;
472 if *match_position == target.len() {
473 found = Some(match_start.clone());
474 // This match is completed. No need to keep tracking it
475 false
476 } else {
477 true
478 }
479 } else {
480 false
481 }
482 });
483
484 found
485 })
486 }
487
488 pub fn column_to_chars(&self, display_row: u32, target: u32) -> u32 {
489 let mut count = 0;
490 let mut column = 0;
491 for (c, _) in self.chars_at(DisplayPoint::new(display_row, 0)) {
492 if column >= target {
493 break;
494 }
495 count += 1;
496 column += c.len_utf8() as u32;
497 }
498 count
499 }
500
501 pub fn column_from_chars(&self, display_row: u32, char_count: u32) -> u32 {
502 let mut column = 0;
503
504 for (count, (c, _)) in self.chars_at(DisplayPoint::new(display_row, 0)).enumerate() {
505 if c == '\n' || count >= char_count as usize {
506 break;
507 }
508 column += c.len_utf8() as u32;
509 }
510
511 column
512 }
513
514 pub fn clip_point(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint {
515 let mut clipped = self.blocks_snapshot.clip_point(point.0, bias);
516 if self.clip_at_line_ends {
517 clipped = self.clip_at_line_end(DisplayPoint(clipped)).0
518 }
519 DisplayPoint(clipped)
520 }
521
522 pub fn clip_at_line_end(&self, point: DisplayPoint) -> DisplayPoint {
523 let mut point = point.0;
524 if point.column == self.line_len(point.row) {
525 point.column = point.column.saturating_sub(1);
526 point = self.blocks_snapshot.clip_point(point, Bias::Left);
527 }
528 DisplayPoint(point)
529 }
530
531 pub fn folds_in_range<T>(&self, range: Range<T>) -> impl Iterator<Item = &Range<Anchor>>
532 where
533 T: ToOffset,
534 {
535 self.folds_snapshot.folds_in_range(range)
536 }
537
538 pub fn blocks_in_range(
539 &self,
540 rows: Range<u32>,
541 ) -> impl Iterator<Item = (u32, &TransformBlock)> {
542 self.blocks_snapshot.blocks_in_range(rows)
543 }
544
545 pub fn intersects_fold<T: ToOffset>(&self, offset: T) -> bool {
546 self.folds_snapshot.intersects_fold(offset)
547 }
548
549 pub fn is_line_folded(&self, buffer_row: u32) -> bool {
550 self.folds_snapshot.is_line_folded(buffer_row)
551 }
552
553 pub fn is_block_line(&self, display_row: u32) -> bool {
554 self.blocks_snapshot.is_block_line(display_row)
555 }
556
557 pub fn soft_wrap_indent(&self, display_row: u32) -> Option<u32> {
558 let wrap_row = self
559 .blocks_snapshot
560 .to_wrap_point(BlockPoint::new(display_row, 0))
561 .row();
562 self.wraps_snapshot.soft_wrap_indent(wrap_row)
563 }
564
565 pub fn text(&self) -> String {
566 self.text_chunks(0).collect()
567 }
568
569 pub fn line(&self, display_row: u32) -> String {
570 let mut result = String::new();
571 for chunk in self.text_chunks(display_row) {
572 if let Some(ix) = chunk.find('\n') {
573 result.push_str(&chunk[0..ix]);
574 break;
575 } else {
576 result.push_str(chunk);
577 }
578 }
579 result
580 }
581
582 pub fn line_indent(&self, display_row: u32) -> (u32, bool) {
583 let mut indent = 0;
584 let mut is_blank = true;
585 for (c, _) in self.chars_at(DisplayPoint::new(display_row, 0)) {
586 if c == ' ' {
587 indent += 1;
588 } else {
589 is_blank = c == '\n';
590 break;
591 }
592 }
593 (indent, is_blank)
594 }
595
596 pub fn line_indent_for_buffer_row(&self, buffer_row: u32) -> (u32, bool) {
597 let (buffer, range) = self
598 .buffer_snapshot
599 .buffer_line_for_row(buffer_row)
600 .unwrap();
601 let chars = buffer.chars_at(Point::new(range.start.row, 0));
602
603 let mut is_blank = false;
604 let indent_size = TabSnapshot::expand_tabs(
605 chars.take_while(|c| {
606 if *c == ' ' || *c == '\t' {
607 true
608 } else {
609 if *c == '\n' {
610 is_blank = true;
611 }
612 false
613 }
614 }),
615 buffer.line_len(buffer_row) as usize, // Never collapse
616 self.tabs_snapshot.tab_size,
617 );
618
619 (indent_size as u32, is_blank)
620 }
621
622 pub fn line_len(&self, row: u32) -> u32 {
623 self.blocks_snapshot.line_len(row)
624 }
625
626 pub fn longest_row(&self) -> u32 {
627 self.blocks_snapshot.longest_row()
628 }
629
630 pub fn fold_for_line(self: &Self, buffer_row: u32) -> Option<FoldStatus> {
631 if self.is_line_folded(buffer_row) {
632 Some(FoldStatus::Folded)
633 } else if self.is_foldable(buffer_row) {
634 Some(FoldStatus::Foldable)
635 } else {
636 None
637 }
638 }
639
640 pub fn is_foldable(self: &Self, buffer_row: u32) -> bool {
641 let max_row = self.buffer_snapshot.max_buffer_row();
642 if buffer_row >= max_row {
643 return false;
644 }
645
646 let (indent_size, is_blank) = self.line_indent_for_buffer_row(buffer_row);
647 if is_blank {
648 return false;
649 }
650
651 for next_row in (buffer_row + 1)..=max_row {
652 let (next_indent_size, next_line_is_blank) = self.line_indent_for_buffer_row(next_row);
653 if next_indent_size > indent_size {
654 return true;
655 } else if !next_line_is_blank {
656 break;
657 }
658 }
659
660 false
661 }
662
663 pub fn foldable_range(self: &Self, buffer_row: u32) -> Option<Range<Point>> {
664 let start = Point::new(buffer_row, self.buffer_snapshot.line_len(buffer_row));
665 if self.is_foldable(start.row) && !self.is_line_folded(start.row) {
666 let (start_indent, _) = self.line_indent_for_buffer_row(buffer_row);
667 let max_point = self.buffer_snapshot.max_point();
668 let mut end = None;
669
670 for row in (buffer_row + 1)..=max_point.row {
671 let (indent, is_blank) = self.line_indent_for_buffer_row(row);
672 if !is_blank && indent <= start_indent {
673 let prev_row = row - 1;
674 end = Some(Point::new(
675 prev_row,
676 self.buffer_snapshot.line_len(prev_row),
677 ));
678 break;
679 }
680 }
681 let end = end.unwrap_or(max_point);
682 Some(start..end)
683 } else {
684 None
685 }
686 }
687
688 #[cfg(any(test, feature = "test-support"))]
689 pub fn highlight_ranges<Tag: ?Sized + 'static>(
690 &self,
691 ) -> Option<Arc<(HighlightStyle, Vec<Range<Anchor>>)>> {
692 let type_id = TypeId::of::<Tag>();
693 self.text_highlights.get(&Some(type_id)).cloned()
694 }
695}
696
697#[derive(Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq)]
698pub struct DisplayPoint(BlockPoint);
699
700impl Debug for DisplayPoint {
701 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
702 f.write_fmt(format_args!(
703 "DisplayPoint({}, {})",
704 self.row(),
705 self.column()
706 ))
707 }
708}
709
710impl DisplayPoint {
711 pub fn new(row: u32, column: u32) -> Self {
712 Self(BlockPoint(Point::new(row, column)))
713 }
714
715 pub fn zero() -> Self {
716 Self::new(0, 0)
717 }
718
719 pub fn is_zero(&self) -> bool {
720 self.0.is_zero()
721 }
722
723 pub fn row(self) -> u32 {
724 self.0.row
725 }
726
727 pub fn column(self) -> u32 {
728 self.0.column
729 }
730
731 pub fn row_mut(&mut self) -> &mut u32 {
732 &mut self.0.row
733 }
734
735 pub fn column_mut(&mut self) -> &mut u32 {
736 &mut self.0.column
737 }
738
739 pub fn to_point(self, map: &DisplaySnapshot) -> Point {
740 map.display_point_to_point(self, Bias::Left)
741 }
742
743 pub fn to_offset(self, map: &DisplaySnapshot, bias: Bias) -> usize {
744 let unblocked_point = map.blocks_snapshot.to_wrap_point(self.0);
745 let unwrapped_point = map.wraps_snapshot.to_tab_point(unblocked_point);
746 let unexpanded_point = map.tabs_snapshot.to_fold_point(unwrapped_point, bias).0;
747 unexpanded_point.to_buffer_offset(&map.folds_snapshot)
748 }
749}
750
751impl ToDisplayPoint for usize {
752 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
753 map.point_to_display_point(self.to_point(&map.buffer_snapshot), Bias::Left)
754 }
755}
756
757impl ToDisplayPoint for OffsetUtf16 {
758 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
759 self.to_offset(&map.buffer_snapshot).to_display_point(map)
760 }
761}
762
763impl ToDisplayPoint for Point {
764 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
765 map.point_to_display_point(*self, Bias::Left)
766 }
767}
768
769impl ToDisplayPoint for Anchor {
770 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
771 self.to_point(&map.buffer_snapshot).to_display_point(map)
772 }
773}
774
775pub fn next_rows(display_row: u32, display_map: &DisplaySnapshot) -> impl Iterator<Item = u32> {
776 let max_row = display_map.max_point().row();
777 let start_row = display_row + 1;
778 let mut current = None;
779 std::iter::from_fn(move || {
780 if current == None {
781 current = Some(start_row);
782 } else {
783 current = Some(current.unwrap() + 1)
784 }
785 if current.unwrap() > max_row {
786 None
787 } else {
788 current
789 }
790 })
791}
792
793#[cfg(test)]
794pub mod tests {
795 use super::*;
796 use crate::{movement, test::marked_display_snapshot};
797 use gpui::{color::Color, elements::*, test::observe, MutableAppContext};
798 use language::{Buffer, Language, LanguageConfig, SelectionGoal};
799 use rand::{prelude::*, Rng};
800 use smol::stream::StreamExt;
801 use std::{env, sync::Arc};
802 use theme::SyntaxTheme;
803 use util::test::{marked_text_offsets, marked_text_ranges, sample_text};
804 use Bias::*;
805
806 #[gpui::test(iterations = 100)]
807 async fn test_random_display_map(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
808 cx.foreground().set_block_on_ticks(0..=50);
809 cx.foreground().forbid_parking();
810 let operations = env::var("OPERATIONS")
811 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
812 .unwrap_or(10);
813
814 let font_cache = cx.font_cache().clone();
815 let mut tab_size = rng.gen_range(1..=4);
816 let buffer_start_excerpt_header_height = rng.gen_range(1..=5);
817 let excerpt_header_height = rng.gen_range(1..=5);
818 let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
819 let font_id = font_cache
820 .select_font(family_id, &Default::default())
821 .unwrap();
822 let font_size = 14.0;
823 let max_wrap_width = 300.0;
824 let mut wrap_width = if rng.gen_bool(0.1) {
825 None
826 } else {
827 Some(rng.gen_range(0.0..=max_wrap_width))
828 };
829
830 log::info!("tab size: {}", tab_size);
831 log::info!("wrap width: {:?}", wrap_width);
832
833 cx.update(|cx| {
834 let mut settings = Settings::test(cx);
835 settings.editor_overrides.tab_size = NonZeroU32::new(tab_size);
836 cx.set_global(settings)
837 });
838
839 let buffer = cx.update(|cx| {
840 if rng.gen() {
841 let len = rng.gen_range(0..10);
842 let text = util::RandomCharIter::new(&mut rng)
843 .take(len)
844 .collect::<String>();
845 MultiBuffer::build_simple(&text, cx)
846 } else {
847 MultiBuffer::build_random(&mut rng, cx)
848 }
849 });
850
851 let map = cx.add_model(|cx| {
852 DisplayMap::new(
853 buffer.clone(),
854 font_id,
855 font_size,
856 wrap_width,
857 buffer_start_excerpt_header_height,
858 excerpt_header_height,
859 cx,
860 )
861 });
862 let mut notifications = observe(&map, cx);
863 let mut fold_count = 0;
864 let mut blocks = Vec::new();
865
866 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
867 log::info!("buffer text: {:?}", snapshot.buffer_snapshot.text());
868 log::info!("fold text: {:?}", snapshot.folds_snapshot.text());
869 log::info!("tab text: {:?}", snapshot.tabs_snapshot.text());
870 log::info!("wrap text: {:?}", snapshot.wraps_snapshot.text());
871 log::info!("block text: {:?}", snapshot.blocks_snapshot.text());
872 log::info!("display text: {:?}", snapshot.text());
873
874 for _i in 0..operations {
875 match rng.gen_range(0..100) {
876 0..=19 => {
877 wrap_width = if rng.gen_bool(0.2) {
878 None
879 } else {
880 Some(rng.gen_range(0.0..=max_wrap_width))
881 };
882 log::info!("setting wrap width to {:?}", wrap_width);
883 map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
884 }
885 20..=29 => {
886 let mut tab_sizes = vec![1, 2, 3, 4];
887 tab_sizes.remove((tab_size - 1) as usize);
888 tab_size = *tab_sizes.choose(&mut rng).unwrap();
889 log::info!("setting tab size to {:?}", tab_size);
890 cx.update(|cx| {
891 let mut settings = Settings::test(cx);
892 settings.editor_overrides.tab_size = NonZeroU32::new(tab_size);
893 cx.set_global(settings)
894 });
895 }
896 30..=44 => {
897 map.update(cx, |map, cx| {
898 if rng.gen() || blocks.is_empty() {
899 let buffer = map.snapshot(cx).buffer_snapshot;
900 let block_properties = (0..rng.gen_range(1..=1))
901 .map(|_| {
902 let position =
903 buffer.anchor_after(buffer.clip_offset(
904 rng.gen_range(0..=buffer.len()),
905 Bias::Left,
906 ));
907
908 let disposition = if rng.gen() {
909 BlockDisposition::Above
910 } else {
911 BlockDisposition::Below
912 };
913 let height = rng.gen_range(1..5);
914 log::info!(
915 "inserting block {:?} {:?} with height {}",
916 disposition,
917 position.to_point(&buffer),
918 height
919 );
920 BlockProperties {
921 style: BlockStyle::Fixed,
922 position,
923 height,
924 disposition,
925 render: Arc::new(|_| Empty::new().boxed()),
926 }
927 })
928 .collect::<Vec<_>>();
929 blocks.extend(map.insert_blocks(block_properties, cx));
930 } else {
931 blocks.shuffle(&mut rng);
932 let remove_count = rng.gen_range(1..=4.min(blocks.len()));
933 let block_ids_to_remove = (0..remove_count)
934 .map(|_| blocks.remove(rng.gen_range(0..blocks.len())))
935 .collect();
936 log::info!("removing block ids {:?}", block_ids_to_remove);
937 map.remove_blocks(block_ids_to_remove, cx);
938 }
939 });
940 }
941 45..=79 => {
942 let mut ranges = Vec::new();
943 for _ in 0..rng.gen_range(1..=3) {
944 buffer.read_with(cx, |buffer, cx| {
945 let buffer = buffer.read(cx);
946 let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right);
947 let start = buffer.clip_offset(rng.gen_range(0..=end), Left);
948 ranges.push(start..end);
949 });
950 }
951
952 if rng.gen() && fold_count > 0 {
953 log::info!("unfolding ranges: {:?}", ranges);
954 map.update(cx, |map, cx| {
955 map.unfold(ranges, true, cx);
956 });
957 } else {
958 log::info!("folding ranges: {:?}", ranges);
959 map.update(cx, |map, cx| {
960 map.fold(ranges, cx);
961 });
962 }
963 }
964 _ => {
965 buffer.update(cx, |buffer, cx| buffer.randomly_mutate(&mut rng, 5, cx));
966 }
967 }
968
969 if map.read_with(cx, |map, cx| map.is_rewrapping(cx)) {
970 notifications.next().await.unwrap();
971 }
972
973 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
974 fold_count = snapshot.fold_count();
975 log::info!("buffer text: {:?}", snapshot.buffer_snapshot.text());
976 log::info!("fold text: {:?}", snapshot.folds_snapshot.text());
977 log::info!("tab text: {:?}", snapshot.tabs_snapshot.text());
978 log::info!("wrap text: {:?}", snapshot.wraps_snapshot.text());
979 log::info!("block text: {:?}", snapshot.blocks_snapshot.text());
980 log::info!("display text: {:?}", snapshot.text());
981
982 // Line boundaries
983 let buffer = &snapshot.buffer_snapshot;
984 for _ in 0..5 {
985 let row = rng.gen_range(0..=buffer.max_point().row);
986 let column = rng.gen_range(0..=buffer.line_len(row));
987 let point = buffer.clip_point(Point::new(row, column), Left);
988
989 let (prev_buffer_bound, prev_display_bound) = snapshot.prev_line_boundary(point);
990 let (next_buffer_bound, next_display_bound) = snapshot.next_line_boundary(point);
991
992 assert!(prev_buffer_bound <= point);
993 assert!(next_buffer_bound >= point);
994 assert_eq!(prev_buffer_bound.column, 0);
995 assert_eq!(prev_display_bound.column(), 0);
996 if next_buffer_bound < buffer.max_point() {
997 assert_eq!(buffer.chars_at(next_buffer_bound).next(), Some('\n'));
998 }
999
1000 assert_eq!(
1001 prev_display_bound,
1002 prev_buffer_bound.to_display_point(&snapshot),
1003 "row boundary before {:?}. reported buffer row boundary: {:?}",
1004 point,
1005 prev_buffer_bound
1006 );
1007 assert_eq!(
1008 next_display_bound,
1009 next_buffer_bound.to_display_point(&snapshot),
1010 "display row boundary after {:?}. reported buffer row boundary: {:?}",
1011 point,
1012 next_buffer_bound
1013 );
1014 assert_eq!(
1015 prev_buffer_bound,
1016 prev_display_bound.to_point(&snapshot),
1017 "row boundary before {:?}. reported display row boundary: {:?}",
1018 point,
1019 prev_display_bound
1020 );
1021 assert_eq!(
1022 next_buffer_bound,
1023 next_display_bound.to_point(&snapshot),
1024 "row boundary after {:?}. reported display row boundary: {:?}",
1025 point,
1026 next_display_bound
1027 );
1028 }
1029
1030 // Movement
1031 let min_point = snapshot.clip_point(DisplayPoint::new(0, 0), Left);
1032 let max_point = snapshot.clip_point(snapshot.max_point(), Right);
1033 for _ in 0..5 {
1034 let row = rng.gen_range(0..=snapshot.max_point().row());
1035 let column = rng.gen_range(0..=snapshot.line_len(row));
1036 let point = snapshot.clip_point(DisplayPoint::new(row, column), Left);
1037
1038 log::info!("Moving from point {:?}", point);
1039
1040 let moved_right = movement::right(&snapshot, point);
1041 log::info!("Right {:?}", moved_right);
1042 if point < max_point {
1043 assert!(moved_right > point);
1044 if point.column() == snapshot.line_len(point.row())
1045 || snapshot.soft_wrap_indent(point.row()).is_some()
1046 && point.column() == snapshot.line_len(point.row()) - 1
1047 {
1048 assert!(moved_right.row() > point.row());
1049 }
1050 } else {
1051 assert_eq!(moved_right, point);
1052 }
1053
1054 let moved_left = movement::left(&snapshot, point);
1055 log::info!("Left {:?}", moved_left);
1056 if point > min_point {
1057 assert!(moved_left < point);
1058 if point.column() == 0 {
1059 assert!(moved_left.row() < point.row());
1060 }
1061 } else {
1062 assert_eq!(moved_left, point);
1063 }
1064 }
1065 }
1066 }
1067
1068 #[gpui::test(retries = 5)]
1069 fn test_soft_wraps(cx: &mut MutableAppContext) {
1070 cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
1071 cx.foreground().forbid_parking();
1072
1073 let font_cache = cx.font_cache();
1074
1075 let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
1076 let font_id = font_cache
1077 .select_font(family_id, &Default::default())
1078 .unwrap();
1079 let font_size = 12.0;
1080 let wrap_width = Some(64.);
1081 cx.set_global(Settings::test(cx));
1082
1083 let text = "one two three four five\nsix seven eight";
1084 let buffer = MultiBuffer::build_simple(text, cx);
1085 let map = cx.add_model(|cx| {
1086 DisplayMap::new(buffer.clone(), font_id, font_size, wrap_width, 1, 1, cx)
1087 });
1088
1089 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1090 assert_eq!(
1091 snapshot.text_chunks(0).collect::<String>(),
1092 "one two \nthree four \nfive\nsix seven \neight"
1093 );
1094 assert_eq!(
1095 snapshot.clip_point(DisplayPoint::new(0, 8), Bias::Left),
1096 DisplayPoint::new(0, 7)
1097 );
1098 assert_eq!(
1099 snapshot.clip_point(DisplayPoint::new(0, 8), Bias::Right),
1100 DisplayPoint::new(1, 0)
1101 );
1102 assert_eq!(
1103 movement::right(&snapshot, DisplayPoint::new(0, 7)),
1104 DisplayPoint::new(1, 0)
1105 );
1106 assert_eq!(
1107 movement::left(&snapshot, DisplayPoint::new(1, 0)),
1108 DisplayPoint::new(0, 7)
1109 );
1110 assert_eq!(
1111 movement::up(
1112 &snapshot,
1113 DisplayPoint::new(1, 10),
1114 SelectionGoal::None,
1115 false
1116 ),
1117 (DisplayPoint::new(0, 7), SelectionGoal::Column(10))
1118 );
1119 assert_eq!(
1120 movement::down(
1121 &snapshot,
1122 DisplayPoint::new(0, 7),
1123 SelectionGoal::Column(10),
1124 false
1125 ),
1126 (DisplayPoint::new(1, 10), SelectionGoal::Column(10))
1127 );
1128 assert_eq!(
1129 movement::down(
1130 &snapshot,
1131 DisplayPoint::new(1, 10),
1132 SelectionGoal::Column(10),
1133 false
1134 ),
1135 (DisplayPoint::new(2, 4), SelectionGoal::Column(10))
1136 );
1137
1138 let ix = snapshot.buffer_snapshot.text().find("seven").unwrap();
1139 buffer.update(cx, |buffer, cx| {
1140 buffer.edit([(ix..ix, "and ")], None, cx);
1141 });
1142
1143 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1144 assert_eq!(
1145 snapshot.text_chunks(1).collect::<String>(),
1146 "three four \nfive\nsix and \nseven eight"
1147 );
1148
1149 // Re-wrap on font size changes
1150 map.update(cx, |map, cx| map.set_font(font_id, font_size + 3., cx));
1151
1152 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1153 assert_eq!(
1154 snapshot.text_chunks(1).collect::<String>(),
1155 "three \nfour five\nsix and \nseven \neight"
1156 )
1157 }
1158
1159 #[gpui::test]
1160 fn test_text_chunks(cx: &mut gpui::MutableAppContext) {
1161 cx.set_global(Settings::test(cx));
1162 let text = sample_text(6, 6, 'a');
1163 let buffer = MultiBuffer::build_simple(&text, cx);
1164 let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap();
1165 let font_id = cx
1166 .font_cache()
1167 .select_font(family_id, &Default::default())
1168 .unwrap();
1169 let font_size = 14.0;
1170 let map =
1171 cx.add_model(|cx| DisplayMap::new(buffer.clone(), font_id, font_size, None, 1, 1, cx));
1172 buffer.update(cx, |buffer, cx| {
1173 buffer.edit(
1174 vec![
1175 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1176 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1177 (Point::new(2, 1)..Point::new(2, 1), "\t"),
1178 ],
1179 None,
1180 cx,
1181 )
1182 });
1183
1184 assert_eq!(
1185 map.update(cx, |map, cx| map.snapshot(cx))
1186 .text_chunks(1)
1187 .collect::<String>()
1188 .lines()
1189 .next(),
1190 Some(" b bbbbb")
1191 );
1192 assert_eq!(
1193 map.update(cx, |map, cx| map.snapshot(cx))
1194 .text_chunks(2)
1195 .collect::<String>()
1196 .lines()
1197 .next(),
1198 Some("c ccccc")
1199 );
1200 }
1201
1202 #[gpui::test]
1203 async fn test_chunks(cx: &mut gpui::TestAppContext) {
1204 use unindent::Unindent as _;
1205
1206 let text = r#"
1207 fn outer() {}
1208
1209 mod module {
1210 fn inner() {}
1211 }"#
1212 .unindent();
1213
1214 let theme = SyntaxTheme::new(vec![
1215 ("mod.body".to_string(), Color::red().into()),
1216 ("fn.name".to_string(), Color::blue().into()),
1217 ]);
1218 let language = Arc::new(
1219 Language::new(
1220 LanguageConfig {
1221 name: "Test".into(),
1222 path_suffixes: vec![".test".to_string()],
1223 ..Default::default()
1224 },
1225 Some(tree_sitter_rust::language()),
1226 )
1227 .with_highlights_query(
1228 r#"
1229 (mod_item name: (identifier) body: _ @mod.body)
1230 (function_item name: (identifier) @fn.name)
1231 "#,
1232 )
1233 .unwrap(),
1234 );
1235 language.set_theme(&theme);
1236 cx.update(|cx| {
1237 let mut settings = Settings::test(cx);
1238 settings.editor_defaults.tab_size = Some(2.try_into().unwrap());
1239 cx.set_global(settings);
1240 });
1241
1242 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
1243 buffer.condition(cx, |buf, _| !buf.is_parsing()).await;
1244 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
1245
1246 let font_cache = cx.font_cache();
1247 let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
1248 let font_id = font_cache
1249 .select_font(family_id, &Default::default())
1250 .unwrap();
1251 let font_size = 14.0;
1252
1253 let map = cx.add_model(|cx| DisplayMap::new(buffer, font_id, font_size, None, 1, 1, cx));
1254 assert_eq!(
1255 cx.update(|cx| syntax_chunks(0..5, &map, &theme, cx)),
1256 vec![
1257 ("fn ".to_string(), None),
1258 ("outer".to_string(), Some(Color::blue())),
1259 ("() {}\n\nmod module ".to_string(), None),
1260 ("{\n fn ".to_string(), Some(Color::red())),
1261 ("inner".to_string(), Some(Color::blue())),
1262 ("() {}\n}".to_string(), Some(Color::red())),
1263 ]
1264 );
1265 assert_eq!(
1266 cx.update(|cx| syntax_chunks(3..5, &map, &theme, cx)),
1267 vec![
1268 (" fn ".to_string(), Some(Color::red())),
1269 ("inner".to_string(), Some(Color::blue())),
1270 ("() {}\n}".to_string(), Some(Color::red())),
1271 ]
1272 );
1273
1274 map.update(cx, |map, cx| {
1275 map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], cx)
1276 });
1277 assert_eq!(
1278 cx.update(|cx| syntax_chunks(0..2, &map, &theme, cx)),
1279 vec![
1280 ("fn ".to_string(), None),
1281 ("out".to_string(), Some(Color::blue())),
1282 ("⋯".to_string(), None),
1283 (" fn ".to_string(), Some(Color::red())),
1284 ("inner".to_string(), Some(Color::blue())),
1285 ("() {}\n}".to_string(), Some(Color::red())),
1286 ]
1287 );
1288 }
1289
1290 #[gpui::test]
1291 async fn test_chunks_with_soft_wrapping(cx: &mut gpui::TestAppContext) {
1292 use unindent::Unindent as _;
1293
1294 cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
1295
1296 let text = r#"
1297 fn outer() {}
1298
1299 mod module {
1300 fn inner() {}
1301 }"#
1302 .unindent();
1303
1304 let theme = SyntaxTheme::new(vec![
1305 ("mod.body".to_string(), Color::red().into()),
1306 ("fn.name".to_string(), Color::blue().into()),
1307 ]);
1308 let language = Arc::new(
1309 Language::new(
1310 LanguageConfig {
1311 name: "Test".into(),
1312 path_suffixes: vec![".test".to_string()],
1313 ..Default::default()
1314 },
1315 Some(tree_sitter_rust::language()),
1316 )
1317 .with_highlights_query(
1318 r#"
1319 (mod_item name: (identifier) body: _ @mod.body)
1320 (function_item name: (identifier) @fn.name)
1321 "#,
1322 )
1323 .unwrap(),
1324 );
1325 language.set_theme(&theme);
1326
1327 cx.update(|cx| cx.set_global(Settings::test(cx)));
1328
1329 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
1330 buffer.condition(cx, |buf, _| !buf.is_parsing()).await;
1331 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
1332
1333 let font_cache = cx.font_cache();
1334
1335 let family_id = font_cache.load_family(&["Courier"]).unwrap();
1336 let font_id = font_cache
1337 .select_font(family_id, &Default::default())
1338 .unwrap();
1339 let font_size = 16.0;
1340
1341 let map =
1342 cx.add_model(|cx| DisplayMap::new(buffer, font_id, font_size, Some(40.0), 1, 1, cx));
1343 assert_eq!(
1344 cx.update(|cx| syntax_chunks(0..5, &map, &theme, cx)),
1345 [
1346 ("fn \n".to_string(), None),
1347 ("oute\nr".to_string(), Some(Color::blue())),
1348 ("() \n{}\n\n".to_string(), None),
1349 ]
1350 );
1351 assert_eq!(
1352 cx.update(|cx| syntax_chunks(3..5, &map, &theme, cx)),
1353 [("{}\n\n".to_string(), None)]
1354 );
1355
1356 map.update(cx, |map, cx| {
1357 map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], cx)
1358 });
1359 assert_eq!(
1360 cx.update(|cx| syntax_chunks(1..4, &map, &theme, cx)),
1361 [
1362 ("out".to_string(), Some(Color::blue())),
1363 ("⋯\n".to_string(), None),
1364 (" \nfn ".to_string(), Some(Color::red())),
1365 ("i\n".to_string(), Some(Color::blue()))
1366 ]
1367 );
1368 }
1369
1370 #[gpui::test]
1371 async fn test_chunks_with_text_highlights(cx: &mut gpui::TestAppContext) {
1372 cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
1373
1374 cx.update(|cx| cx.set_global(Settings::test(cx)));
1375 let theme = SyntaxTheme::new(vec![
1376 ("operator".to_string(), Color::red().into()),
1377 ("string".to_string(), Color::green().into()),
1378 ]);
1379 let language = Arc::new(
1380 Language::new(
1381 LanguageConfig {
1382 name: "Test".into(),
1383 path_suffixes: vec![".test".to_string()],
1384 ..Default::default()
1385 },
1386 Some(tree_sitter_rust::language()),
1387 )
1388 .with_highlights_query(
1389 r#"
1390 ":" @operator
1391 (string_literal) @string
1392 "#,
1393 )
1394 .unwrap(),
1395 );
1396 language.set_theme(&theme);
1397
1398 let (text, highlighted_ranges) = marked_text_ranges(r#"constˇ «a»: B = "c «d»""#, false);
1399
1400 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
1401 buffer.condition(cx, |buf, _| !buf.is_parsing()).await;
1402
1403 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
1404 let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
1405
1406 let font_cache = cx.font_cache();
1407 let family_id = font_cache.load_family(&["Courier"]).unwrap();
1408 let font_id = font_cache
1409 .select_font(family_id, &Default::default())
1410 .unwrap();
1411 let font_size = 16.0;
1412 let map = cx.add_model(|cx| DisplayMap::new(buffer, font_id, font_size, None, 1, 1, cx));
1413
1414 enum MyType {}
1415
1416 let style = HighlightStyle {
1417 color: Some(Color::blue()),
1418 ..Default::default()
1419 };
1420
1421 map.update(cx, |map, _cx| {
1422 map.highlight_text(
1423 TypeId::of::<MyType>(),
1424 highlighted_ranges
1425 .into_iter()
1426 .map(|range| {
1427 buffer_snapshot.anchor_before(range.start)
1428 ..buffer_snapshot.anchor_before(range.end)
1429 })
1430 .collect(),
1431 style,
1432 );
1433 });
1434
1435 assert_eq!(
1436 cx.update(|cx| chunks(0..10, &map, &theme, cx)),
1437 [
1438 ("const ".to_string(), None, None),
1439 ("a".to_string(), None, Some(Color::blue())),
1440 (":".to_string(), Some(Color::red()), None),
1441 (" B = ".to_string(), None, None),
1442 ("\"c ".to_string(), Some(Color::green()), None),
1443 ("d".to_string(), Some(Color::green()), Some(Color::blue())),
1444 ("\"".to_string(), Some(Color::green()), None),
1445 ]
1446 );
1447 }
1448
1449 #[gpui::test]
1450 fn test_clip_point(cx: &mut gpui::MutableAppContext) {
1451 cx.set_global(Settings::test(cx));
1452 fn assert(text: &str, shift_right: bool, bias: Bias, cx: &mut gpui::MutableAppContext) {
1453 let (unmarked_snapshot, mut markers) = marked_display_snapshot(text, cx);
1454
1455 match bias {
1456 Bias::Left => {
1457 if shift_right {
1458 *markers[1].column_mut() += 1;
1459 }
1460
1461 assert_eq!(unmarked_snapshot.clip_point(markers[1], bias), markers[0])
1462 }
1463 Bias::Right => {
1464 if shift_right {
1465 *markers[0].column_mut() += 1;
1466 }
1467
1468 assert_eq!(unmarked_snapshot.clip_point(markers[0], bias), markers[1])
1469 }
1470 };
1471 }
1472
1473 use Bias::{Left, Right};
1474 assert("ˇˇα", false, Left, cx);
1475 assert("ˇˇα", true, Left, cx);
1476 assert("ˇˇα", false, Right, cx);
1477 assert("ˇαˇ", true, Right, cx);
1478 assert("ˇˇ✋", false, Left, cx);
1479 assert("ˇˇ✋", true, Left, cx);
1480 assert("ˇˇ✋", false, Right, cx);
1481 assert("ˇ✋ˇ", true, Right, cx);
1482 assert("ˇˇ🍐", false, Left, cx);
1483 assert("ˇˇ🍐", true, Left, cx);
1484 assert("ˇˇ🍐", false, Right, cx);
1485 assert("ˇ🍐ˇ", true, Right, cx);
1486 assert("ˇˇ\t", false, Left, cx);
1487 assert("ˇˇ\t", true, Left, cx);
1488 assert("ˇˇ\t", false, Right, cx);
1489 assert("ˇ\tˇ", true, Right, cx);
1490 assert(" ˇˇ\t", false, Left, cx);
1491 assert(" ˇˇ\t", true, Left, cx);
1492 assert(" ˇˇ\t", false, Right, cx);
1493 assert(" ˇ\tˇ", true, Right, cx);
1494 assert(" ˇˇ\t", false, Left, cx);
1495 assert(" ˇˇ\t", false, Right, cx);
1496 }
1497
1498 #[gpui::test]
1499 fn test_clip_at_line_ends(cx: &mut gpui::MutableAppContext) {
1500 cx.set_global(Settings::test(cx));
1501
1502 fn assert(text: &str, cx: &mut gpui::MutableAppContext) {
1503 let (mut unmarked_snapshot, markers) = marked_display_snapshot(text, cx);
1504 unmarked_snapshot.clip_at_line_ends = true;
1505 assert_eq!(
1506 unmarked_snapshot.clip_point(markers[1], Bias::Left),
1507 markers[0]
1508 );
1509 }
1510
1511 assert("ˇˇ", cx);
1512 assert("ˇaˇ", cx);
1513 assert("aˇbˇ", cx);
1514 assert("aˇαˇ", cx);
1515 }
1516
1517 #[gpui::test]
1518 fn test_tabs_with_multibyte_chars(cx: &mut gpui::MutableAppContext) {
1519 cx.set_global(Settings::test(cx));
1520 let text = "✅\t\tα\nβ\t\n🏀β\t\tγ";
1521 let buffer = MultiBuffer::build_simple(text, cx);
1522 let font_cache = cx.font_cache();
1523 let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
1524 let font_id = font_cache
1525 .select_font(family_id, &Default::default())
1526 .unwrap();
1527 let font_size = 14.0;
1528
1529 let map =
1530 cx.add_model(|cx| DisplayMap::new(buffer.clone(), font_id, font_size, None, 1, 1, cx));
1531 let map = map.update(cx, |map, cx| map.snapshot(cx));
1532 assert_eq!(map.text(), "✅ α\nβ \n🏀β γ");
1533 assert_eq!(
1534 map.text_chunks(0).collect::<String>(),
1535 "✅ α\nβ \n🏀β γ"
1536 );
1537 assert_eq!(map.text_chunks(1).collect::<String>(), "β \n🏀β γ");
1538 assert_eq!(map.text_chunks(2).collect::<String>(), "🏀β γ");
1539
1540 let point = Point::new(0, "✅\t\t".len() as u32);
1541 let display_point = DisplayPoint::new(0, "✅ ".len() as u32);
1542 assert_eq!(point.to_display_point(&map), display_point);
1543 assert_eq!(display_point.to_point(&map), point);
1544
1545 let point = Point::new(1, "β\t".len() as u32);
1546 let display_point = DisplayPoint::new(1, "β ".len() as u32);
1547 assert_eq!(point.to_display_point(&map), display_point);
1548 assert_eq!(display_point.to_point(&map), point,);
1549
1550 let point = Point::new(2, "🏀β\t\t".len() as u32);
1551 let display_point = DisplayPoint::new(2, "🏀β ".len() as u32);
1552 assert_eq!(point.to_display_point(&map), display_point);
1553 assert_eq!(display_point.to_point(&map), point,);
1554
1555 // Display points inside of expanded tabs
1556 assert_eq!(
1557 DisplayPoint::new(0, "✅ ".len() as u32).to_point(&map),
1558 Point::new(0, "✅\t".len() as u32),
1559 );
1560 assert_eq!(
1561 DisplayPoint::new(0, "✅ ".len() as u32).to_point(&map),
1562 Point::new(0, "✅".len() as u32),
1563 );
1564
1565 // Clipping display points inside of multi-byte characters
1566 assert_eq!(
1567 map.clip_point(DisplayPoint::new(0, "✅".len() as u32 - 1), Left),
1568 DisplayPoint::new(0, 0)
1569 );
1570 assert_eq!(
1571 map.clip_point(DisplayPoint::new(0, "✅".len() as u32 - 1), Bias::Right),
1572 DisplayPoint::new(0, "✅".len() as u32)
1573 );
1574 }
1575
1576 #[gpui::test]
1577 fn test_max_point(cx: &mut gpui::MutableAppContext) {
1578 cx.set_global(Settings::test(cx));
1579 let buffer = MultiBuffer::build_simple("aaa\n\t\tbbb", cx);
1580 let font_cache = cx.font_cache();
1581 let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
1582 let font_id = font_cache
1583 .select_font(family_id, &Default::default())
1584 .unwrap();
1585 let font_size = 14.0;
1586 let map =
1587 cx.add_model(|cx| DisplayMap::new(buffer.clone(), font_id, font_size, None, 1, 1, cx));
1588 assert_eq!(
1589 map.update(cx, |map, cx| map.snapshot(cx)).max_point(),
1590 DisplayPoint::new(1, 11)
1591 )
1592 }
1593
1594 #[test]
1595 fn test_find_internal() {
1596 assert("This is a ˇtest of find internal", "test");
1597 assert("Some text ˇaˇaˇaa with repeated characters", "aa");
1598
1599 fn assert(marked_text: &str, target: &str) {
1600 let (text, expected_offsets) = marked_text_offsets(marked_text);
1601
1602 let chars = text
1603 .chars()
1604 .enumerate()
1605 .map(|(index, ch)| (ch, DisplayPoint::new(0, index as u32)));
1606 let target = target.chars();
1607
1608 assert_eq!(
1609 expected_offsets
1610 .into_iter()
1611 .map(|offset| offset as u32)
1612 .collect::<Vec<_>>(),
1613 DisplaySnapshot::find_internal(chars, target.collect(), |_, _| true)
1614 .map(|point| point.column())
1615 .collect::<Vec<_>>()
1616 )
1617 }
1618 }
1619
1620 fn syntax_chunks<'a>(
1621 rows: Range<u32>,
1622 map: &ModelHandle<DisplayMap>,
1623 theme: &'a SyntaxTheme,
1624 cx: &mut MutableAppContext,
1625 ) -> Vec<(String, Option<Color>)> {
1626 chunks(rows, map, theme, cx)
1627 .into_iter()
1628 .map(|(text, color, _)| (text, color))
1629 .collect()
1630 }
1631
1632 fn chunks<'a>(
1633 rows: Range<u32>,
1634 map: &ModelHandle<DisplayMap>,
1635 theme: &'a SyntaxTheme,
1636 cx: &mut MutableAppContext,
1637 ) -> Vec<(String, Option<Color>, Option<Color>)> {
1638 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1639 let mut chunks: Vec<(String, Option<Color>, Option<Color>)> = Vec::new();
1640 for chunk in snapshot.chunks(rows, true) {
1641 let syntax_color = chunk
1642 .syntax_highlight_id
1643 .and_then(|id| id.style(theme)?.color);
1644 let highlight_color = chunk.highlight_style.and_then(|style| style.color);
1645 if let Some((last_chunk, last_syntax_color, last_highlight_color)) = chunks.last_mut() {
1646 if syntax_color == *last_syntax_color && highlight_color == *last_highlight_color {
1647 last_chunk.push_str(chunk.text);
1648 continue;
1649 }
1650 }
1651 chunks.push((chunk.text.to_string(), syntax_color, highlight_color));
1652 }
1653 chunks
1654 }
1655}