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
819 .load_family(&["Helvetica"], &Default::default())
820 .unwrap();
821 let font_id = font_cache
822 .select_font(family_id, &Default::default())
823 .unwrap();
824 let font_size = 14.0;
825 let max_wrap_width = 300.0;
826 let mut wrap_width = if rng.gen_bool(0.1) {
827 None
828 } else {
829 Some(rng.gen_range(0.0..=max_wrap_width))
830 };
831
832 log::info!("tab size: {}", tab_size);
833 log::info!("wrap width: {:?}", wrap_width);
834
835 cx.update(|cx| {
836 let mut settings = Settings::test(cx);
837 settings.editor_overrides.tab_size = NonZeroU32::new(tab_size);
838 cx.set_global(settings)
839 });
840
841 let buffer = cx.update(|cx| {
842 if rng.gen() {
843 let len = rng.gen_range(0..10);
844 let text = util::RandomCharIter::new(&mut rng)
845 .take(len)
846 .collect::<String>();
847 MultiBuffer::build_simple(&text, cx)
848 } else {
849 MultiBuffer::build_random(&mut rng, cx)
850 }
851 });
852
853 let map = cx.add_model(|cx| {
854 DisplayMap::new(
855 buffer.clone(),
856 font_id,
857 font_size,
858 wrap_width,
859 buffer_start_excerpt_header_height,
860 excerpt_header_height,
861 cx,
862 )
863 });
864 let mut notifications = observe(&map, cx);
865 let mut fold_count = 0;
866 let mut blocks = Vec::new();
867
868 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
869 log::info!("buffer text: {:?}", snapshot.buffer_snapshot.text());
870 log::info!("fold text: {:?}", snapshot.folds_snapshot.text());
871 log::info!("tab text: {:?}", snapshot.tabs_snapshot.text());
872 log::info!("wrap text: {:?}", snapshot.wraps_snapshot.text());
873 log::info!("block text: {:?}", snapshot.blocks_snapshot.text());
874 log::info!("display text: {:?}", snapshot.text());
875
876 for _i in 0..operations {
877 match rng.gen_range(0..100) {
878 0..=19 => {
879 wrap_width = if rng.gen_bool(0.2) {
880 None
881 } else {
882 Some(rng.gen_range(0.0..=max_wrap_width))
883 };
884 log::info!("setting wrap width to {:?}", wrap_width);
885 map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
886 }
887 20..=29 => {
888 let mut tab_sizes = vec![1, 2, 3, 4];
889 tab_sizes.remove((tab_size - 1) as usize);
890 tab_size = *tab_sizes.choose(&mut rng).unwrap();
891 log::info!("setting tab size to {:?}", tab_size);
892 cx.update(|cx| {
893 let mut settings = Settings::test(cx);
894 settings.editor_overrides.tab_size = NonZeroU32::new(tab_size);
895 cx.set_global(settings)
896 });
897 }
898 30..=44 => {
899 map.update(cx, |map, cx| {
900 if rng.gen() || blocks.is_empty() {
901 let buffer = map.snapshot(cx).buffer_snapshot;
902 let block_properties = (0..rng.gen_range(1..=1))
903 .map(|_| {
904 let position =
905 buffer.anchor_after(buffer.clip_offset(
906 rng.gen_range(0..=buffer.len()),
907 Bias::Left,
908 ));
909
910 let disposition = if rng.gen() {
911 BlockDisposition::Above
912 } else {
913 BlockDisposition::Below
914 };
915 let height = rng.gen_range(1..5);
916 log::info!(
917 "inserting block {:?} {:?} with height {}",
918 disposition,
919 position.to_point(&buffer),
920 height
921 );
922 BlockProperties {
923 style: BlockStyle::Fixed,
924 position,
925 height,
926 disposition,
927 render: Arc::new(|_| Empty::new().boxed()),
928 }
929 })
930 .collect::<Vec<_>>();
931 blocks.extend(map.insert_blocks(block_properties, cx));
932 } else {
933 blocks.shuffle(&mut rng);
934 let remove_count = rng.gen_range(1..=4.min(blocks.len()));
935 let block_ids_to_remove = (0..remove_count)
936 .map(|_| blocks.remove(rng.gen_range(0..blocks.len())))
937 .collect();
938 log::info!("removing block ids {:?}", block_ids_to_remove);
939 map.remove_blocks(block_ids_to_remove, cx);
940 }
941 });
942 }
943 45..=79 => {
944 let mut ranges = Vec::new();
945 for _ in 0..rng.gen_range(1..=3) {
946 buffer.read_with(cx, |buffer, cx| {
947 let buffer = buffer.read(cx);
948 let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right);
949 let start = buffer.clip_offset(rng.gen_range(0..=end), Left);
950 ranges.push(start..end);
951 });
952 }
953
954 if rng.gen() && fold_count > 0 {
955 log::info!("unfolding ranges: {:?}", ranges);
956 map.update(cx, |map, cx| {
957 map.unfold(ranges, true, cx);
958 });
959 } else {
960 log::info!("folding ranges: {:?}", ranges);
961 map.update(cx, |map, cx| {
962 map.fold(ranges, cx);
963 });
964 }
965 }
966 _ => {
967 buffer.update(cx, |buffer, cx| buffer.randomly_mutate(&mut rng, 5, cx));
968 }
969 }
970
971 if map.read_with(cx, |map, cx| map.is_rewrapping(cx)) {
972 notifications.next().await.unwrap();
973 }
974
975 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
976 fold_count = snapshot.fold_count();
977 log::info!("buffer text: {:?}", snapshot.buffer_snapshot.text());
978 log::info!("fold text: {:?}", snapshot.folds_snapshot.text());
979 log::info!("tab text: {:?}", snapshot.tabs_snapshot.text());
980 log::info!("wrap text: {:?}", snapshot.wraps_snapshot.text());
981 log::info!("block text: {:?}", snapshot.blocks_snapshot.text());
982 log::info!("display text: {:?}", snapshot.text());
983
984 // Line boundaries
985 let buffer = &snapshot.buffer_snapshot;
986 for _ in 0..5 {
987 let row = rng.gen_range(0..=buffer.max_point().row);
988 let column = rng.gen_range(0..=buffer.line_len(row));
989 let point = buffer.clip_point(Point::new(row, column), Left);
990
991 let (prev_buffer_bound, prev_display_bound) = snapshot.prev_line_boundary(point);
992 let (next_buffer_bound, next_display_bound) = snapshot.next_line_boundary(point);
993
994 assert!(prev_buffer_bound <= point);
995 assert!(next_buffer_bound >= point);
996 assert_eq!(prev_buffer_bound.column, 0);
997 assert_eq!(prev_display_bound.column(), 0);
998 if next_buffer_bound < buffer.max_point() {
999 assert_eq!(buffer.chars_at(next_buffer_bound).next(), Some('\n'));
1000 }
1001
1002 assert_eq!(
1003 prev_display_bound,
1004 prev_buffer_bound.to_display_point(&snapshot),
1005 "row boundary before {:?}. reported buffer row boundary: {:?}",
1006 point,
1007 prev_buffer_bound
1008 );
1009 assert_eq!(
1010 next_display_bound,
1011 next_buffer_bound.to_display_point(&snapshot),
1012 "display row boundary after {:?}. reported buffer row boundary: {:?}",
1013 point,
1014 next_buffer_bound
1015 );
1016 assert_eq!(
1017 prev_buffer_bound,
1018 prev_display_bound.to_point(&snapshot),
1019 "row boundary before {:?}. reported display row boundary: {:?}",
1020 point,
1021 prev_display_bound
1022 );
1023 assert_eq!(
1024 next_buffer_bound,
1025 next_display_bound.to_point(&snapshot),
1026 "row boundary after {:?}. reported display row boundary: {:?}",
1027 point,
1028 next_display_bound
1029 );
1030 }
1031
1032 // Movement
1033 let min_point = snapshot.clip_point(DisplayPoint::new(0, 0), Left);
1034 let max_point = snapshot.clip_point(snapshot.max_point(), Right);
1035 for _ in 0..5 {
1036 let row = rng.gen_range(0..=snapshot.max_point().row());
1037 let column = rng.gen_range(0..=snapshot.line_len(row));
1038 let point = snapshot.clip_point(DisplayPoint::new(row, column), Left);
1039
1040 log::info!("Moving from point {:?}", point);
1041
1042 let moved_right = movement::right(&snapshot, point);
1043 log::info!("Right {:?}", moved_right);
1044 if point < max_point {
1045 assert!(moved_right > point);
1046 if point.column() == snapshot.line_len(point.row())
1047 || snapshot.soft_wrap_indent(point.row()).is_some()
1048 && point.column() == snapshot.line_len(point.row()) - 1
1049 {
1050 assert!(moved_right.row() > point.row());
1051 }
1052 } else {
1053 assert_eq!(moved_right, point);
1054 }
1055
1056 let moved_left = movement::left(&snapshot, point);
1057 log::info!("Left {:?}", moved_left);
1058 if point > min_point {
1059 assert!(moved_left < point);
1060 if point.column() == 0 {
1061 assert!(moved_left.row() < point.row());
1062 }
1063 } else {
1064 assert_eq!(moved_left, point);
1065 }
1066 }
1067 }
1068 }
1069
1070 #[gpui::test(retries = 5)]
1071 fn test_soft_wraps(cx: &mut MutableAppContext) {
1072 cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
1073 cx.foreground().forbid_parking();
1074
1075 let font_cache = cx.font_cache();
1076
1077 let family_id = font_cache
1078 .load_family(&["Helvetica"], &Default::default())
1079 .unwrap();
1080 let font_id = font_cache
1081 .select_font(family_id, &Default::default())
1082 .unwrap();
1083 let font_size = 12.0;
1084 let wrap_width = Some(64.);
1085 cx.set_global(Settings::test(cx));
1086
1087 let text = "one two three four five\nsix seven eight";
1088 let buffer = MultiBuffer::build_simple(text, cx);
1089 let map = cx.add_model(|cx| {
1090 DisplayMap::new(buffer.clone(), font_id, font_size, wrap_width, 1, 1, cx)
1091 });
1092
1093 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1094 assert_eq!(
1095 snapshot.text_chunks(0).collect::<String>(),
1096 "one two \nthree four \nfive\nsix seven \neight"
1097 );
1098 assert_eq!(
1099 snapshot.clip_point(DisplayPoint::new(0, 8), Bias::Left),
1100 DisplayPoint::new(0, 7)
1101 );
1102 assert_eq!(
1103 snapshot.clip_point(DisplayPoint::new(0, 8), Bias::Right),
1104 DisplayPoint::new(1, 0)
1105 );
1106 assert_eq!(
1107 movement::right(&snapshot, DisplayPoint::new(0, 7)),
1108 DisplayPoint::new(1, 0)
1109 );
1110 assert_eq!(
1111 movement::left(&snapshot, DisplayPoint::new(1, 0)),
1112 DisplayPoint::new(0, 7)
1113 );
1114 assert_eq!(
1115 movement::up(
1116 &snapshot,
1117 DisplayPoint::new(1, 10),
1118 SelectionGoal::None,
1119 false
1120 ),
1121 (DisplayPoint::new(0, 7), SelectionGoal::Column(10))
1122 );
1123 assert_eq!(
1124 movement::down(
1125 &snapshot,
1126 DisplayPoint::new(0, 7),
1127 SelectionGoal::Column(10),
1128 false
1129 ),
1130 (DisplayPoint::new(1, 10), SelectionGoal::Column(10))
1131 );
1132 assert_eq!(
1133 movement::down(
1134 &snapshot,
1135 DisplayPoint::new(1, 10),
1136 SelectionGoal::Column(10),
1137 false
1138 ),
1139 (DisplayPoint::new(2, 4), SelectionGoal::Column(10))
1140 );
1141
1142 let ix = snapshot.buffer_snapshot.text().find("seven").unwrap();
1143 buffer.update(cx, |buffer, cx| {
1144 buffer.edit([(ix..ix, "and ")], None, cx);
1145 });
1146
1147 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1148 assert_eq!(
1149 snapshot.text_chunks(1).collect::<String>(),
1150 "three four \nfive\nsix and \nseven eight"
1151 );
1152
1153 // Re-wrap on font size changes
1154 map.update(cx, |map, cx| map.set_font(font_id, font_size + 3., cx));
1155
1156 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1157 assert_eq!(
1158 snapshot.text_chunks(1).collect::<String>(),
1159 "three \nfour five\nsix and \nseven \neight"
1160 )
1161 }
1162
1163 #[gpui::test]
1164 fn test_text_chunks(cx: &mut gpui::MutableAppContext) {
1165 cx.set_global(Settings::test(cx));
1166 let text = sample_text(6, 6, 'a');
1167 let buffer = MultiBuffer::build_simple(&text, cx);
1168 let family_id = cx
1169 .font_cache()
1170 .load_family(&["Helvetica"], &Default::default())
1171 .unwrap();
1172 let font_id = cx
1173 .font_cache()
1174 .select_font(family_id, &Default::default())
1175 .unwrap();
1176 let font_size = 14.0;
1177 let map =
1178 cx.add_model(|cx| DisplayMap::new(buffer.clone(), font_id, font_size, None, 1, 1, cx));
1179 buffer.update(cx, |buffer, cx| {
1180 buffer.edit(
1181 vec![
1182 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1183 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1184 (Point::new(2, 1)..Point::new(2, 1), "\t"),
1185 ],
1186 None,
1187 cx,
1188 )
1189 });
1190
1191 assert_eq!(
1192 map.update(cx, |map, cx| map.snapshot(cx))
1193 .text_chunks(1)
1194 .collect::<String>()
1195 .lines()
1196 .next(),
1197 Some(" b bbbbb")
1198 );
1199 assert_eq!(
1200 map.update(cx, |map, cx| map.snapshot(cx))
1201 .text_chunks(2)
1202 .collect::<String>()
1203 .lines()
1204 .next(),
1205 Some("c ccccc")
1206 );
1207 }
1208
1209 #[gpui::test]
1210 async fn test_chunks(cx: &mut gpui::TestAppContext) {
1211 use unindent::Unindent as _;
1212
1213 let text = r#"
1214 fn outer() {}
1215
1216 mod module {
1217 fn inner() {}
1218 }"#
1219 .unindent();
1220
1221 let theme = SyntaxTheme::new(vec![
1222 ("mod.body".to_string(), Color::red().into()),
1223 ("fn.name".to_string(), Color::blue().into()),
1224 ]);
1225 let language = Arc::new(
1226 Language::new(
1227 LanguageConfig {
1228 name: "Test".into(),
1229 path_suffixes: vec![".test".to_string()],
1230 ..Default::default()
1231 },
1232 Some(tree_sitter_rust::language()),
1233 )
1234 .with_highlights_query(
1235 r#"
1236 (mod_item name: (identifier) body: _ @mod.body)
1237 (function_item name: (identifier) @fn.name)
1238 "#,
1239 )
1240 .unwrap(),
1241 );
1242 language.set_theme(&theme);
1243 cx.update(|cx| {
1244 let mut settings = Settings::test(cx);
1245 settings.editor_defaults.tab_size = Some(2.try_into().unwrap());
1246 cx.set_global(settings);
1247 });
1248
1249 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
1250 buffer.condition(cx, |buf, _| !buf.is_parsing()).await;
1251 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
1252
1253 let font_cache = cx.font_cache();
1254 let family_id = font_cache
1255 .load_family(&["Helvetica"], &Default::default())
1256 .unwrap();
1257 let font_id = font_cache
1258 .select_font(family_id, &Default::default())
1259 .unwrap();
1260 let font_size = 14.0;
1261
1262 let map = cx.add_model(|cx| DisplayMap::new(buffer, font_id, font_size, None, 1, 1, cx));
1263 assert_eq!(
1264 cx.update(|cx| syntax_chunks(0..5, &map, &theme, cx)),
1265 vec![
1266 ("fn ".to_string(), None),
1267 ("outer".to_string(), Some(Color::blue())),
1268 ("() {}\n\nmod module ".to_string(), None),
1269 ("{\n fn ".to_string(), Some(Color::red())),
1270 ("inner".to_string(), Some(Color::blue())),
1271 ("() {}\n}".to_string(), Some(Color::red())),
1272 ]
1273 );
1274 assert_eq!(
1275 cx.update(|cx| syntax_chunks(3..5, &map, &theme, cx)),
1276 vec![
1277 (" fn ".to_string(), Some(Color::red())),
1278 ("inner".to_string(), Some(Color::blue())),
1279 ("() {}\n}".to_string(), Some(Color::red())),
1280 ]
1281 );
1282
1283 map.update(cx, |map, cx| {
1284 map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], cx)
1285 });
1286 assert_eq!(
1287 cx.update(|cx| syntax_chunks(0..2, &map, &theme, cx)),
1288 vec![
1289 ("fn ".to_string(), None),
1290 ("out".to_string(), Some(Color::blue())),
1291 ("β―".to_string(), None),
1292 (" fn ".to_string(), Some(Color::red())),
1293 ("inner".to_string(), Some(Color::blue())),
1294 ("() {}\n}".to_string(), Some(Color::red())),
1295 ]
1296 );
1297 }
1298
1299 #[gpui::test]
1300 async fn test_chunks_with_soft_wrapping(cx: &mut gpui::TestAppContext) {
1301 use unindent::Unindent as _;
1302
1303 cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
1304
1305 let text = r#"
1306 fn outer() {}
1307
1308 mod module {
1309 fn inner() {}
1310 }"#
1311 .unindent();
1312
1313 let theme = SyntaxTheme::new(vec![
1314 ("mod.body".to_string(), Color::red().into()),
1315 ("fn.name".to_string(), Color::blue().into()),
1316 ]);
1317 let language = Arc::new(
1318 Language::new(
1319 LanguageConfig {
1320 name: "Test".into(),
1321 path_suffixes: vec![".test".to_string()],
1322 ..Default::default()
1323 },
1324 Some(tree_sitter_rust::language()),
1325 )
1326 .with_highlights_query(
1327 r#"
1328 (mod_item name: (identifier) body: _ @mod.body)
1329 (function_item name: (identifier) @fn.name)
1330 "#,
1331 )
1332 .unwrap(),
1333 );
1334 language.set_theme(&theme);
1335
1336 cx.update(|cx| cx.set_global(Settings::test(cx)));
1337
1338 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
1339 buffer.condition(cx, |buf, _| !buf.is_parsing()).await;
1340 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
1341
1342 let font_cache = cx.font_cache();
1343
1344 let family_id = font_cache
1345 .load_family(&["Courier"], &Default::default())
1346 .unwrap();
1347 let font_id = font_cache
1348 .select_font(family_id, &Default::default())
1349 .unwrap();
1350 let font_size = 16.0;
1351
1352 let map =
1353 cx.add_model(|cx| DisplayMap::new(buffer, font_id, font_size, Some(40.0), 1, 1, cx));
1354 assert_eq!(
1355 cx.update(|cx| syntax_chunks(0..5, &map, &theme, cx)),
1356 [
1357 ("fn \n".to_string(), None),
1358 ("oute\nr".to_string(), Some(Color::blue())),
1359 ("() \n{}\n\n".to_string(), None),
1360 ]
1361 );
1362 assert_eq!(
1363 cx.update(|cx| syntax_chunks(3..5, &map, &theme, cx)),
1364 [("{}\n\n".to_string(), None)]
1365 );
1366
1367 map.update(cx, |map, cx| {
1368 map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], cx)
1369 });
1370 assert_eq!(
1371 cx.update(|cx| syntax_chunks(1..4, &map, &theme, cx)),
1372 [
1373 ("out".to_string(), Some(Color::blue())),
1374 ("β―\n".to_string(), None),
1375 (" \nfn ".to_string(), Some(Color::red())),
1376 ("i\n".to_string(), Some(Color::blue()))
1377 ]
1378 );
1379 }
1380
1381 #[gpui::test]
1382 async fn test_chunks_with_text_highlights(cx: &mut gpui::TestAppContext) {
1383 cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
1384
1385 cx.update(|cx| cx.set_global(Settings::test(cx)));
1386 let theme = SyntaxTheme::new(vec![
1387 ("operator".to_string(), Color::red().into()),
1388 ("string".to_string(), Color::green().into()),
1389 ]);
1390 let language = Arc::new(
1391 Language::new(
1392 LanguageConfig {
1393 name: "Test".into(),
1394 path_suffixes: vec![".test".to_string()],
1395 ..Default::default()
1396 },
1397 Some(tree_sitter_rust::language()),
1398 )
1399 .with_highlights_query(
1400 r#"
1401 ":" @operator
1402 (string_literal) @string
1403 "#,
1404 )
1405 .unwrap(),
1406 );
1407 language.set_theme(&theme);
1408
1409 let (text, highlighted_ranges) = marked_text_ranges(r#"constΛ Β«aΒ»: B = "c Β«dΒ»""#, false);
1410
1411 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
1412 buffer.condition(cx, |buf, _| !buf.is_parsing()).await;
1413
1414 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
1415 let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
1416
1417 let font_cache = cx.font_cache();
1418 let family_id = font_cache
1419 .load_family(&["Courier"], &Default::default())
1420 .unwrap();
1421 let font_id = font_cache
1422 .select_font(family_id, &Default::default())
1423 .unwrap();
1424 let font_size = 16.0;
1425 let map = cx.add_model(|cx| DisplayMap::new(buffer, font_id, font_size, None, 1, 1, cx));
1426
1427 enum MyType {}
1428
1429 let style = HighlightStyle {
1430 color: Some(Color::blue()),
1431 ..Default::default()
1432 };
1433
1434 map.update(cx, |map, _cx| {
1435 map.highlight_text(
1436 TypeId::of::<MyType>(),
1437 highlighted_ranges
1438 .into_iter()
1439 .map(|range| {
1440 buffer_snapshot.anchor_before(range.start)
1441 ..buffer_snapshot.anchor_before(range.end)
1442 })
1443 .collect(),
1444 style,
1445 );
1446 });
1447
1448 assert_eq!(
1449 cx.update(|cx| chunks(0..10, &map, &theme, cx)),
1450 [
1451 ("const ".to_string(), None, None),
1452 ("a".to_string(), None, Some(Color::blue())),
1453 (":".to_string(), Some(Color::red()), None),
1454 (" B = ".to_string(), None, None),
1455 ("\"c ".to_string(), Some(Color::green()), None),
1456 ("d".to_string(), Some(Color::green()), Some(Color::blue())),
1457 ("\"".to_string(), Some(Color::green()), None),
1458 ]
1459 );
1460 }
1461
1462 #[gpui::test]
1463 fn test_clip_point(cx: &mut gpui::MutableAppContext) {
1464 cx.set_global(Settings::test(cx));
1465 fn assert(text: &str, shift_right: bool, bias: Bias, cx: &mut gpui::MutableAppContext) {
1466 let (unmarked_snapshot, mut markers) = marked_display_snapshot(text, cx);
1467
1468 match bias {
1469 Bias::Left => {
1470 if shift_right {
1471 *markers[1].column_mut() += 1;
1472 }
1473
1474 assert_eq!(unmarked_snapshot.clip_point(markers[1], bias), markers[0])
1475 }
1476 Bias::Right => {
1477 if shift_right {
1478 *markers[0].column_mut() += 1;
1479 }
1480
1481 assert_eq!(unmarked_snapshot.clip_point(markers[0], bias), markers[1])
1482 }
1483 };
1484 }
1485
1486 use Bias::{Left, Right};
1487 assert("ΛΛΞ±", false, Left, cx);
1488 assert("ΛΛΞ±", true, Left, cx);
1489 assert("ΛΛΞ±", false, Right, cx);
1490 assert("ΛΞ±Λ", true, Right, cx);
1491 assert("ΛΛβ", false, Left, cx);
1492 assert("ΛΛβ", true, Left, cx);
1493 assert("ΛΛβ", false, Right, cx);
1494 assert("ΛβΛ", true, Right, cx);
1495 assert("ΛΛπ", false, Left, cx);
1496 assert("ΛΛπ", true, Left, cx);
1497 assert("ΛΛπ", false, Right, cx);
1498 assert("ΛπΛ", true, Right, cx);
1499 assert("ΛΛ\t", false, Left, cx);
1500 assert("ΛΛ\t", true, Left, cx);
1501 assert("ΛΛ\t", false, Right, cx);
1502 assert("Λ\tΛ", true, Right, cx);
1503 assert(" ΛΛ\t", false, Left, cx);
1504 assert(" ΛΛ\t", true, Left, cx);
1505 assert(" ΛΛ\t", false, Right, cx);
1506 assert(" Λ\tΛ", true, Right, cx);
1507 assert(" ΛΛ\t", false, Left, cx);
1508 assert(" ΛΛ\t", false, Right, cx);
1509 }
1510
1511 #[gpui::test]
1512 fn test_clip_at_line_ends(cx: &mut gpui::MutableAppContext) {
1513 cx.set_global(Settings::test(cx));
1514
1515 fn assert(text: &str, cx: &mut gpui::MutableAppContext) {
1516 let (mut unmarked_snapshot, markers) = marked_display_snapshot(text, cx);
1517 unmarked_snapshot.clip_at_line_ends = true;
1518 assert_eq!(
1519 unmarked_snapshot.clip_point(markers[1], Bias::Left),
1520 markers[0]
1521 );
1522 }
1523
1524 assert("ΛΛ", cx);
1525 assert("ΛaΛ", cx);
1526 assert("aΛbΛ", cx);
1527 assert("aΛΞ±Λ", cx);
1528 }
1529
1530 #[gpui::test]
1531 fn test_tabs_with_multibyte_chars(cx: &mut gpui::MutableAppContext) {
1532 cx.set_global(Settings::test(cx));
1533 let text = "β
\t\tΞ±\nΞ²\t\nπΞ²\t\tΞ³";
1534 let buffer = MultiBuffer::build_simple(text, cx);
1535 let font_cache = cx.font_cache();
1536 let family_id = font_cache
1537 .load_family(&["Helvetica"], &Default::default())
1538 .unwrap();
1539 let font_id = font_cache
1540 .select_font(family_id, &Default::default())
1541 .unwrap();
1542 let font_size = 14.0;
1543
1544 let map =
1545 cx.add_model(|cx| DisplayMap::new(buffer.clone(), font_id, font_size, None, 1, 1, cx));
1546 let map = map.update(cx, |map, cx| map.snapshot(cx));
1547 assert_eq!(map.text(), "β
Ξ±\nΞ² \nπΞ² Ξ³");
1548 assert_eq!(
1549 map.text_chunks(0).collect::<String>(),
1550 "β
Ξ±\nΞ² \nπΞ² Ξ³"
1551 );
1552 assert_eq!(map.text_chunks(1).collect::<String>(), "Ξ² \nπΞ² Ξ³");
1553 assert_eq!(map.text_chunks(2).collect::<String>(), "πΞ² Ξ³");
1554
1555 let point = Point::new(0, "β
\t\t".len() as u32);
1556 let display_point = DisplayPoint::new(0, "β
".len() as u32);
1557 assert_eq!(point.to_display_point(&map), display_point);
1558 assert_eq!(display_point.to_point(&map), point);
1559
1560 let point = Point::new(1, "Ξ²\t".len() as u32);
1561 let display_point = DisplayPoint::new(1, "Ξ² ".len() as u32);
1562 assert_eq!(point.to_display_point(&map), display_point);
1563 assert_eq!(display_point.to_point(&map), point,);
1564
1565 let point = Point::new(2, "πΞ²\t\t".len() as u32);
1566 let display_point = DisplayPoint::new(2, "πΞ² ".len() as u32);
1567 assert_eq!(point.to_display_point(&map), display_point);
1568 assert_eq!(display_point.to_point(&map), point,);
1569
1570 // Display points inside of expanded tabs
1571 assert_eq!(
1572 DisplayPoint::new(0, "β
".len() as u32).to_point(&map),
1573 Point::new(0, "β
\t".len() as u32),
1574 );
1575 assert_eq!(
1576 DisplayPoint::new(0, "β
".len() as u32).to_point(&map),
1577 Point::new(0, "β
".len() as u32),
1578 );
1579
1580 // Clipping display points inside of multi-byte characters
1581 assert_eq!(
1582 map.clip_point(DisplayPoint::new(0, "β
".len() as u32 - 1), Left),
1583 DisplayPoint::new(0, 0)
1584 );
1585 assert_eq!(
1586 map.clip_point(DisplayPoint::new(0, "β
".len() as u32 - 1), Bias::Right),
1587 DisplayPoint::new(0, "β
".len() as u32)
1588 );
1589 }
1590
1591 #[gpui::test]
1592 fn test_max_point(cx: &mut gpui::MutableAppContext) {
1593 cx.set_global(Settings::test(cx));
1594 let buffer = MultiBuffer::build_simple("aaa\n\t\tbbb", cx);
1595 let font_cache = cx.font_cache();
1596 let family_id = font_cache
1597 .load_family(&["Helvetica"], &Default::default())
1598 .unwrap();
1599 let font_id = font_cache
1600 .select_font(family_id, &Default::default())
1601 .unwrap();
1602 let font_size = 14.0;
1603 let map =
1604 cx.add_model(|cx| DisplayMap::new(buffer.clone(), font_id, font_size, None, 1, 1, cx));
1605 assert_eq!(
1606 map.update(cx, |map, cx| map.snapshot(cx)).max_point(),
1607 DisplayPoint::new(1, 11)
1608 )
1609 }
1610
1611 #[test]
1612 fn test_find_internal() {
1613 assert("This is a Λtest of find internal", "test");
1614 assert("Some text ΛaΛaΛaa with repeated characters", "aa");
1615
1616 fn assert(marked_text: &str, target: &str) {
1617 let (text, expected_offsets) = marked_text_offsets(marked_text);
1618
1619 let chars = text
1620 .chars()
1621 .enumerate()
1622 .map(|(index, ch)| (ch, DisplayPoint::new(0, index as u32)));
1623 let target = target.chars();
1624
1625 assert_eq!(
1626 expected_offsets
1627 .into_iter()
1628 .map(|offset| offset as u32)
1629 .collect::<Vec<_>>(),
1630 DisplaySnapshot::find_internal(chars, target.collect(), |_, _| true)
1631 .map(|point| point.column())
1632 .collect::<Vec<_>>()
1633 )
1634 }
1635 }
1636
1637 fn syntax_chunks<'a>(
1638 rows: Range<u32>,
1639 map: &ModelHandle<DisplayMap>,
1640 theme: &'a SyntaxTheme,
1641 cx: &mut MutableAppContext,
1642 ) -> Vec<(String, Option<Color>)> {
1643 chunks(rows, map, theme, cx)
1644 .into_iter()
1645 .map(|(text, color, _)| (text, color))
1646 .collect()
1647 }
1648
1649 fn chunks<'a>(
1650 rows: Range<u32>,
1651 map: &ModelHandle<DisplayMap>,
1652 theme: &'a SyntaxTheme,
1653 cx: &mut MutableAppContext,
1654 ) -> Vec<(String, Option<Color>, Option<Color>)> {
1655 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1656 let mut chunks: Vec<(String, Option<Color>, Option<Color>)> = Vec::new();
1657 for chunk in snapshot.chunks(rows, true) {
1658 let syntax_color = chunk
1659 .syntax_highlight_id
1660 .and_then(|id| id.style(theme)?.color);
1661 let highlight_color = chunk.highlight_style.and_then(|style| style.color);
1662 if let Some((last_chunk, last_syntax_color, last_highlight_color)) = chunks.last_mut() {
1663 if syntax_color == *last_syntax_color && highlight_color == *last_highlight_color {
1664 last_chunk.push_str(chunk.text);
1665 continue;
1666 }
1667 }
1668 chunks.push((chunk.text.to_string(), syntax_color, highlight_color));
1669 }
1670 chunks
1671 }
1672}