1mod fold_map;
2mod tab_map;
3mod wrap_map;
4
5use super::{buffer, Anchor, Bias, Buffer, Point, Settings, ToOffset, ToPoint};
6use fold_map::FoldMap;
7use gpui::{Entity, ModelContext, ModelHandle};
8use postage::watch;
9use std::ops::Range;
10use tab_map::TabMap;
11pub use wrap_map::BufferRows;
12use wrap_map::WrapMap;
13
14pub struct DisplayMap {
15 buffer: ModelHandle<Buffer>,
16 fold_map: FoldMap,
17 tab_map: TabMap,
18 wrap_map: ModelHandle<WrapMap>,
19}
20
21impl Entity for DisplayMap {
22 type Event = ();
23}
24
25impl DisplayMap {
26 pub fn new(
27 buffer: ModelHandle<Buffer>,
28 settings: watch::Receiver<Settings>,
29 wrap_width: Option<f32>,
30 cx: &mut ModelContext<Self>,
31 ) -> Self {
32 let (fold_map, snapshot) = FoldMap::new(buffer.clone(), cx);
33 let (tab_map, snapshot) = TabMap::new(snapshot, settings.borrow().tab_size);
34 let wrap_map = cx.add_model(|cx| WrapMap::new(snapshot, settings, wrap_width, cx));
35 cx.observe(&wrap_map, |_, _, cx| cx.notify()).detach();
36 DisplayMap {
37 buffer,
38 fold_map,
39 tab_map,
40 wrap_map,
41 }
42 }
43
44 pub fn snapshot(&self, cx: &mut ModelContext<Self>) -> DisplayMapSnapshot {
45 let (folds_snapshot, edits) = self.fold_map.read(cx);
46 let (tabs_snapshot, edits) = self.tab_map.sync(folds_snapshot.clone(), edits);
47 let wraps_snapshot = self
48 .wrap_map
49 .update(cx, |map, cx| map.sync(tabs_snapshot.clone(), edits, cx));
50 DisplayMapSnapshot {
51 buffer_snapshot: self.buffer.read(cx).snapshot(),
52 folds_snapshot,
53 tabs_snapshot,
54 wraps_snapshot,
55 }
56 }
57
58 pub fn fold<T: ToOffset>(
59 &mut self,
60 ranges: impl IntoIterator<Item = Range<T>>,
61 cx: &mut ModelContext<Self>,
62 ) {
63 let (mut fold_map, snapshot, edits) = self.fold_map.write(cx);
64 let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
65 self.wrap_map
66 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
67 let (snapshot, edits) = fold_map.fold(ranges, cx);
68 let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
69 self.wrap_map
70 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
71 }
72
73 pub fn unfold<T: ToOffset>(
74 &mut self,
75 ranges: impl IntoIterator<Item = Range<T>>,
76 cx: &mut ModelContext<Self>,
77 ) {
78 let (mut fold_map, snapshot, edits) = self.fold_map.write(cx);
79 let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
80 self.wrap_map
81 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
82 let (snapshot, edits) = fold_map.unfold(ranges, cx);
83 let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
84 self.wrap_map
85 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
86 }
87
88 pub fn set_wrap_width(&self, width: Option<f32>, cx: &mut ModelContext<Self>) -> bool {
89 self.wrap_map
90 .update(cx, |map, cx| map.set_wrap_width(width, cx))
91 }
92
93 #[cfg(test)]
94 pub fn is_rewrapping(&self, cx: &gpui::AppContext) -> bool {
95 self.wrap_map.read(cx).is_rewrapping()
96 }
97}
98
99pub struct DisplayMapSnapshot {
100 buffer_snapshot: buffer::Snapshot,
101 folds_snapshot: fold_map::Snapshot,
102 tabs_snapshot: tab_map::Snapshot,
103 wraps_snapshot: wrap_map::Snapshot,
104}
105
106impl DisplayMapSnapshot {
107 #[cfg(test)]
108 pub fn fold_count(&self) -> usize {
109 self.folds_snapshot.fold_count()
110 }
111
112 pub fn is_empty(&self) -> bool {
113 self.buffer_snapshot.len() == 0
114 }
115
116 pub fn buffer_rows(&self, start_row: u32) -> BufferRows {
117 self.wraps_snapshot.buffer_rows(start_row)
118 }
119
120 pub fn buffer_row_count(&self) -> u32 {
121 self.buffer_snapshot.max_point().row + 1
122 }
123
124 pub fn prev_row_boundary(&self, mut display_point: DisplayPoint) -> (DisplayPoint, Point) {
125 loop {
126 *display_point.column_mut() = 0;
127 let mut point = display_point.to_buffer_point(self, Bias::Left);
128 point.column = 0;
129 let next_display_point = point.to_display_point(self, Bias::Left);
130 if next_display_point == display_point {
131 return (display_point, point);
132 }
133 display_point = next_display_point;
134 }
135 }
136
137 pub fn next_row_boundary(&self, mut display_point: DisplayPoint) -> (DisplayPoint, Point) {
138 loop {
139 *display_point.column_mut() = self.line_len(display_point.row());
140 let mut point = display_point.to_buffer_point(self, Bias::Right);
141 point.column = self.buffer_snapshot.line_len(point.row);
142 let next_display_point = point.to_display_point(self, Bias::Right);
143 if next_display_point == display_point {
144 return (display_point, point);
145 }
146 display_point = next_display_point;
147 }
148 }
149
150 pub fn max_point(&self) -> DisplayPoint {
151 DisplayPoint(self.wraps_snapshot.max_point())
152 }
153
154 pub fn chunks_at(&self, display_row: u32) -> wrap_map::Chunks {
155 self.wraps_snapshot.chunks_at(display_row)
156 }
157
158 pub fn highlighted_chunks_for_rows(
159 &mut self,
160 display_rows: Range<u32>,
161 ) -> wrap_map::HighlightedChunks {
162 self.wraps_snapshot
163 .highlighted_chunks_for_rows(display_rows)
164 }
165
166 pub fn chars_at<'a>(&'a self, point: DisplayPoint) -> impl Iterator<Item = char> + 'a {
167 let mut column = 0;
168 let mut chars = self.chunks_at(point.row()).flat_map(str::chars);
169 while column < point.column() {
170 if let Some(c) = chars.next() {
171 column += c.len_utf8() as u32;
172 } else {
173 break;
174 }
175 }
176 chars
177 }
178
179 pub fn column_to_chars(&self, display_row: u32, target: u32) -> u32 {
180 let mut count = 0;
181 let mut column = 0;
182 for c in self.chars_at(DisplayPoint::new(display_row, 0)) {
183 if column >= target {
184 break;
185 }
186 count += 1;
187 column += c.len_utf8() as u32;
188 }
189 count
190 }
191
192 pub fn column_from_chars(&self, display_row: u32, char_count: u32) -> u32 {
193 let mut count = 0;
194 let mut column = 0;
195 for c in self.chars_at(DisplayPoint::new(display_row, 0)) {
196 if c == '\n' || count >= char_count {
197 break;
198 }
199 count += 1;
200 column += c.len_utf8() as u32;
201 }
202 column
203 }
204
205 pub fn clip_point(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint {
206 DisplayPoint(self.wraps_snapshot.clip_point(point.0, bias))
207 }
208
209 pub fn folds_in_range<'a, T>(
210 &'a self,
211 range: Range<T>,
212 ) -> impl Iterator<Item = &'a Range<Anchor>>
213 where
214 T: ToOffset,
215 {
216 self.folds_snapshot.folds_in_range(range)
217 }
218
219 pub fn intersects_fold<T: ToOffset>(&self, offset: T) -> bool {
220 self.folds_snapshot.intersects_fold(offset)
221 }
222
223 pub fn is_line_folded(&self, display_row: u32) -> bool {
224 let wrap_point = DisplayPoint::new(display_row, 0).0;
225 let row = self.wraps_snapshot.to_tab_point(wrap_point).row();
226 self.folds_snapshot.is_line_folded(row)
227 }
228
229 pub fn soft_wrap_indent(&self, display_row: u32) -> Option<u32> {
230 self.wraps_snapshot.soft_wrap_indent(display_row)
231 }
232
233 pub fn text(&self) -> String {
234 self.chunks_at(0).collect()
235 }
236
237 pub fn line(&self, display_row: u32) -> String {
238 let mut result = String::new();
239 for chunk in self.chunks_at(display_row) {
240 if let Some(ix) = chunk.find('\n') {
241 result.push_str(&chunk[0..ix]);
242 break;
243 } else {
244 result.push_str(chunk);
245 }
246 }
247 result
248 }
249
250 pub fn line_indent(&self, display_row: u32) -> (u32, bool) {
251 let mut indent = 0;
252 let mut is_blank = true;
253 for c in self.chars_at(DisplayPoint::new(display_row, 0)) {
254 if c == ' ' {
255 indent += 1;
256 } else {
257 is_blank = c == '\n';
258 break;
259 }
260 }
261 (indent, is_blank)
262 }
263
264 pub fn line_len(&self, row: u32) -> u32 {
265 self.wraps_snapshot.line_len(row)
266 }
267
268 pub fn longest_row(&self) -> u32 {
269 self.wraps_snapshot.longest_row()
270 }
271
272 pub fn anchor_before(&self, point: DisplayPoint, bias: Bias) -> Anchor {
273 self.buffer_snapshot
274 .anchor_before(point.to_buffer_point(self, bias))
275 }
276
277 pub fn anchor_after(&self, point: DisplayPoint, bias: Bias) -> Anchor {
278 self.buffer_snapshot
279 .anchor_after(point.to_buffer_point(self, bias))
280 }
281}
282
283#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
284pub struct DisplayPoint(wrap_map::WrapPoint);
285
286impl DisplayPoint {
287 pub fn new(row: u32, column: u32) -> Self {
288 Self(wrap_map::WrapPoint::new(row, column))
289 }
290
291 pub fn zero() -> Self {
292 Self::new(0, 0)
293 }
294
295 #[cfg(test)]
296 pub fn is_zero(&self) -> bool {
297 self.0.is_zero()
298 }
299
300 pub fn row(self) -> u32 {
301 self.0.row()
302 }
303
304 pub fn column(self) -> u32 {
305 self.0.column()
306 }
307
308 pub fn row_mut(&mut self) -> &mut u32 {
309 self.0.row_mut()
310 }
311
312 pub fn column_mut(&mut self) -> &mut u32 {
313 self.0.column_mut()
314 }
315
316 pub fn to_buffer_point(self, map: &DisplayMapSnapshot, bias: Bias) -> Point {
317 let unwrapped_point = map.wraps_snapshot.to_tab_point(self.0);
318 let unexpanded_point = map.tabs_snapshot.to_fold_point(unwrapped_point, bias).0;
319 unexpanded_point.to_buffer_point(&map.folds_snapshot)
320 }
321
322 pub fn to_buffer_offset(self, map: &DisplayMapSnapshot, bias: Bias) -> usize {
323 let unwrapped_point = map.wraps_snapshot.to_tab_point(self.0);
324 let unexpanded_point = map.tabs_snapshot.to_fold_point(unwrapped_point, bias).0;
325 unexpanded_point.to_buffer_offset(&map.folds_snapshot)
326 }
327}
328
329impl Point {
330 pub fn to_display_point(self, map: &DisplayMapSnapshot, bias: Bias) -> DisplayPoint {
331 let fold_point = self.to_fold_point(&map.folds_snapshot, bias);
332 let tab_point = map.tabs_snapshot.to_tab_point(fold_point);
333 let wrap_point = map.wraps_snapshot.to_wrap_point(tab_point);
334 DisplayPoint(wrap_point)
335 }
336}
337
338impl Anchor {
339 pub fn to_display_point(&self, map: &DisplayMapSnapshot, bias: Bias) -> DisplayPoint {
340 self.to_point(&map.buffer_snapshot)
341 .to_display_point(map, bias)
342 }
343}
344
345#[cfg(test)]
346mod tests {
347 use super::*;
348 use crate::{
349 editor::movement,
350 language::{Language, LanguageConfig},
351 test::*,
352 theme::SyntaxTheme,
353 util::RandomCharIter,
354 };
355 use buffer::{History, SelectionGoal};
356 use gpui::{color::Color, MutableAppContext};
357 use rand::{prelude::StdRng, Rng};
358 use std::{env, sync::Arc};
359 use Bias::*;
360
361 #[gpui::test(iterations = 100)]
362 async fn test_random(mut cx: gpui::TestAppContext, mut rng: StdRng) {
363 cx.foreground().set_block_on_ticks(0..=50);
364 cx.foreground().forbid_parking();
365 let operations = env::var("OPERATIONS")
366 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
367 .unwrap_or(10);
368
369 let font_cache = cx.font_cache().clone();
370 let settings = Settings {
371 tab_size: rng.gen_range(1..=4),
372 buffer_font_family: font_cache.load_family(&["Helvetica"]).unwrap(),
373 buffer_font_size: 14.0,
374 ..cx.read(Settings::test)
375 };
376 let max_wrap_width = 300.0;
377 let mut wrap_width = if rng.gen_bool(0.1) {
378 None
379 } else {
380 Some(rng.gen_range(0.0..=max_wrap_width))
381 };
382
383 log::info!("tab size: {}", settings.tab_size);
384 log::info!("wrap width: {:?}", wrap_width);
385
386 let buffer = cx.add_model(|cx| {
387 let len = rng.gen_range(0..10);
388 let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
389 Buffer::new(0, text, cx)
390 });
391 let settings = watch::channel_with(settings).1;
392
393 let map = cx.add_model(|cx| DisplayMap::new(buffer.clone(), settings, wrap_width, cx));
394 let (_observer, notifications) = Observer::new(&map, &mut cx);
395 let mut fold_count = 0;
396
397 for _i in 0..operations {
398 match rng.gen_range(0..100) {
399 0..=19 => {
400 wrap_width = if rng.gen_bool(0.2) {
401 None
402 } else {
403 Some(rng.gen_range(0.0..=max_wrap_width))
404 };
405 log::info!("setting wrap width to {:?}", wrap_width);
406 map.update(&mut cx, |map, cx| map.set_wrap_width(wrap_width, cx));
407 }
408 20..=80 => {
409 let mut ranges = Vec::new();
410 for _ in 0..rng.gen_range(1..=3) {
411 buffer.read_with(&cx, |buffer, _| {
412 let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right);
413 let start = buffer.clip_offset(rng.gen_range(0..=end), Left);
414 ranges.push(start..end);
415 });
416 }
417
418 if rng.gen() && fold_count > 0 {
419 log::info!("unfolding ranges: {:?}", ranges);
420 map.update(&mut cx, |map, cx| {
421 map.unfold(ranges, cx);
422 });
423 } else {
424 log::info!("folding ranges: {:?}", ranges);
425 map.update(&mut cx, |map, cx| {
426 map.fold(ranges, cx);
427 });
428 }
429 }
430 _ => {
431 buffer.update(&mut cx, |buffer, cx| buffer.randomly_mutate(&mut rng, cx));
432 }
433 }
434
435 if map.read_with(&cx, |map, cx| map.is_rewrapping(cx)) {
436 notifications.recv().await.unwrap();
437 }
438
439 let snapshot = map.update(&mut cx, |map, cx| map.snapshot(cx));
440 fold_count = snapshot.fold_count();
441 log::info!("buffer text: {:?}", buffer.read_with(&cx, |b, _| b.text()));
442 log::info!("display text: {:?}", snapshot.text());
443
444 // Line boundaries
445 for _ in 0..5 {
446 let row = rng.gen_range(0..=snapshot.max_point().row());
447 let column = rng.gen_range(0..=snapshot.line_len(row));
448 let point = snapshot.clip_point(DisplayPoint::new(row, column), Left);
449
450 let (prev_display_bound, prev_buffer_bound) = snapshot.prev_row_boundary(point);
451 let (next_display_bound, next_buffer_bound) = snapshot.next_row_boundary(point);
452
453 assert!(prev_display_bound <= point);
454 assert!(next_display_bound >= point);
455 assert_eq!(prev_buffer_bound.column, 0);
456 assert_eq!(prev_display_bound.column(), 0);
457 if next_display_bound < snapshot.max_point() {
458 assert_eq!(
459 buffer
460 .read_with(&cx, |buffer, _| buffer.chars_at(next_buffer_bound).next()),
461 Some('\n')
462 )
463 }
464
465 assert_eq!(
466 prev_display_bound,
467 prev_buffer_bound.to_display_point(&snapshot, Left),
468 "row boundary before {:?}. reported buffer row boundary: {:?}",
469 point,
470 prev_buffer_bound
471 );
472 assert_eq!(
473 next_display_bound,
474 next_buffer_bound.to_display_point(&snapshot, Right),
475 "display row boundary after {:?}. reported buffer row boundary: {:?}",
476 point,
477 next_buffer_bound
478 );
479 assert_eq!(
480 prev_buffer_bound,
481 prev_display_bound.to_buffer_point(&snapshot, Left),
482 "row boundary before {:?}. reported display row boundary: {:?}",
483 point,
484 prev_display_bound
485 );
486 assert_eq!(
487 next_buffer_bound,
488 next_display_bound.to_buffer_point(&snapshot, Right),
489 "row boundary after {:?}. reported display row boundary: {:?}",
490 point,
491 next_display_bound
492 );
493 }
494
495 // Movement
496 for _ in 0..5 {
497 let row = rng.gen_range(0..=snapshot.max_point().row());
498 let column = rng.gen_range(0..=snapshot.line_len(row));
499 let point = snapshot.clip_point(DisplayPoint::new(row, column), Left);
500
501 log::info!("Moving from point {:?}", point);
502
503 let moved_right = movement::right(&snapshot, point).unwrap();
504 log::info!("Right {:?}", moved_right);
505 if point < snapshot.max_point() {
506 assert!(moved_right > point);
507 if point.column() == snapshot.line_len(point.row())
508 || snapshot.soft_wrap_indent(point.row()).is_some()
509 && point.column() == snapshot.line_len(point.row()) - 1
510 {
511 assert!(moved_right.row() > point.row());
512 }
513 } else {
514 assert_eq!(moved_right, point);
515 }
516
517 let moved_left = movement::left(&snapshot, point).unwrap();
518 log::info!("Left {:?}", moved_left);
519 if !point.is_zero() {
520 assert!(moved_left < point);
521 if point.column() == 0 {
522 assert!(moved_left.row() < point.row());
523 }
524 } else {
525 assert!(moved_left.is_zero());
526 }
527 }
528 }
529 }
530
531 #[gpui::test]
532 async fn test_soft_wraps(mut cx: gpui::TestAppContext) {
533 cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
534 cx.foreground().forbid_parking();
535
536 let font_cache = cx.font_cache();
537
538 let settings = Settings {
539 buffer_font_family: font_cache.load_family(&["Helvetica"]).unwrap(),
540 buffer_font_size: 12.0,
541 tab_size: 4,
542 ..cx.read(Settings::test)
543 };
544 let wrap_width = Some(64.);
545
546 let text = "one two three four five\nsix seven eight";
547 let buffer = cx.add_model(|cx| Buffer::new(0, text.to_string(), cx));
548 let settings = watch::channel_with(settings).1;
549 let map = cx.add_model(|cx| DisplayMap::new(buffer.clone(), settings, wrap_width, cx));
550
551 let snapshot = map.update(&mut cx, |map, cx| map.snapshot(cx));
552 assert_eq!(
553 snapshot.chunks_at(0).collect::<String>(),
554 "one two \nthree four \nfive\nsix seven \neight"
555 );
556 assert_eq!(
557 snapshot.clip_point(DisplayPoint::new(0, 8), Bias::Left),
558 DisplayPoint::new(0, 7)
559 );
560 assert_eq!(
561 snapshot.clip_point(DisplayPoint::new(0, 8), Bias::Right),
562 DisplayPoint::new(1, 0)
563 );
564 assert_eq!(
565 movement::right(&snapshot, DisplayPoint::new(0, 7)).unwrap(),
566 DisplayPoint::new(1, 0)
567 );
568 assert_eq!(
569 movement::left(&snapshot, DisplayPoint::new(1, 0)).unwrap(),
570 DisplayPoint::new(0, 7)
571 );
572 assert_eq!(
573 movement::up(&snapshot, DisplayPoint::new(1, 10), SelectionGoal::None).unwrap(),
574 (DisplayPoint::new(0, 7), SelectionGoal::Column(10))
575 );
576 assert_eq!(
577 movement::down(
578 &snapshot,
579 DisplayPoint::new(0, 7),
580 SelectionGoal::Column(10)
581 )
582 .unwrap(),
583 (DisplayPoint::new(1, 10), SelectionGoal::Column(10))
584 );
585 assert_eq!(
586 movement::down(
587 &snapshot,
588 DisplayPoint::new(1, 10),
589 SelectionGoal::Column(10)
590 )
591 .unwrap(),
592 (DisplayPoint::new(2, 4), SelectionGoal::Column(10))
593 );
594
595 buffer.update(&mut cx, |buffer, cx| {
596 let ix = buffer.text().find("seven").unwrap();
597 buffer.edit(vec![ix..ix], "and ", cx);
598 });
599
600 let snapshot = map.update(&mut cx, |map, cx| map.snapshot(cx));
601 assert_eq!(
602 snapshot.chunks_at(1).collect::<String>(),
603 "three four \nfive\nsix and \nseven eight"
604 );
605 }
606
607 #[gpui::test]
608 fn test_chunks_at(cx: &mut gpui::MutableAppContext) {
609 let text = sample_text(6, 6);
610 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
611 let map = cx.add_model(|cx| {
612 DisplayMap::new(
613 buffer.clone(),
614 Settings {
615 tab_size: 4,
616 ..Settings::test(cx)
617 },
618 None,
619 cx,
620 )
621 });
622 buffer.update(cx, |buffer, cx| {
623 buffer.edit(
624 vec![
625 Point::new(1, 0)..Point::new(1, 0),
626 Point::new(1, 1)..Point::new(1, 1),
627 Point::new(2, 1)..Point::new(2, 1),
628 ],
629 "\t",
630 cx,
631 )
632 });
633
634 assert_eq!(
635 map.update(cx, |map, cx| map.snapshot(cx))
636 .chunks_at(1)
637 .collect::<String>()
638 .lines()
639 .next(),
640 Some(" b bbbbb")
641 );
642 assert_eq!(
643 map.update(cx, |map, cx| map.snapshot(cx))
644 .chunks_at(2)
645 .collect::<String>()
646 .lines()
647 .next(),
648 Some("c ccccc")
649 );
650 }
651
652 #[gpui::test]
653 async fn test_highlighted_chunks_at(mut cx: gpui::TestAppContext) {
654 use unindent::Unindent as _;
655
656 let grammar = tree_sitter_rust::language();
657 let text = r#"
658 fn outer() {}
659
660 mod module {
661 fn inner() {}
662 }"#
663 .unindent();
664 let highlight_query = tree_sitter::Query::new(
665 grammar,
666 r#"
667 (mod_item name: (identifier) body: _ @mod.body)
668 (function_item name: (identifier) @fn.name)"#,
669 )
670 .unwrap();
671 let theme = SyntaxTheme::new(vec![
672 ("mod.body".to_string(), Color::from_u32(0xff0000ff).into()),
673 ("fn.name".to_string(), Color::from_u32(0x00ff00ff).into()),
674 ]);
675 let lang = Arc::new(Language {
676 config: LanguageConfig {
677 name: "Test".to_string(),
678 path_suffixes: vec![".test".to_string()],
679 ..Default::default()
680 },
681 grammar: grammar.clone(),
682 highlight_query,
683 brackets_query: tree_sitter::Query::new(grammar, "").unwrap(),
684 highlight_map: Default::default(),
685 });
686 lang.set_theme(&theme);
687
688 let buffer = cx.add_model(|cx| {
689 Buffer::from_history(0, History::new(text.into()), None, Some(lang), cx)
690 });
691 buffer.condition(&cx, |buf, _| !buf.is_parsing()).await;
692
693 let map = cx.add_model(|cx| {
694 DisplayMap::new(
695 buffer,
696 Settings {
697 tab_size: 2,
698 ..Settings::test(cx)
699 },
700 None,
701 cx,
702 )
703 });
704 assert_eq!(
705 cx.update(|cx| highlighted_chunks(0..5, &map, &theme, cx)),
706 vec![
707 ("fn ".to_string(), None),
708 ("outer".to_string(), Some("fn.name")),
709 ("() {}\n\nmod module ".to_string(), None),
710 ("{\n fn ".to_string(), Some("mod.body")),
711 ("inner".to_string(), Some("fn.name")),
712 ("() {}\n}".to_string(), Some("mod.body")),
713 ]
714 );
715 assert_eq!(
716 cx.update(|cx| highlighted_chunks(3..5, &map, &theme, cx)),
717 vec![
718 (" fn ".to_string(), Some("mod.body")),
719 ("inner".to_string(), Some("fn.name")),
720 ("() {}\n}".to_string(), Some("mod.body")),
721 ]
722 );
723
724 map.update(&mut cx, |map, cx| {
725 map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], cx)
726 });
727 assert_eq!(
728 cx.update(|cx| highlighted_chunks(0..2, &map, &theme, cx)),
729 vec![
730 ("fn ".to_string(), None),
731 ("out".to_string(), Some("fn.name")),
732 ("…".to_string(), None),
733 (" fn ".to_string(), Some("mod.body")),
734 ("inner".to_string(), Some("fn.name")),
735 ("() {}\n}".to_string(), Some("mod.body")),
736 ]
737 );
738 }
739
740 #[gpui::test]
741 async fn test_highlighted_chunks_with_soft_wrapping(mut cx: gpui::TestAppContext) {
742 use unindent::Unindent as _;
743
744 cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
745
746 let grammar = tree_sitter_rust::language();
747 let text = r#"
748 fn outer() {}
749
750 mod module {
751 fn inner() {}
752 }"#
753 .unindent();
754 let highlight_query = tree_sitter::Query::new(
755 grammar,
756 r#"
757 (mod_item name: (identifier) body: _ @mod.body)
758 (function_item name: (identifier) @fn.name)"#,
759 )
760 .unwrap();
761 let theme = SyntaxTheme::new(vec![
762 ("mod.body".to_string(), Color::from_u32(0xff0000ff).into()),
763 ("fn.name".to_string(), Color::from_u32(0x00ff00ff).into()),
764 ]);
765 let lang = Arc::new(Language {
766 config: LanguageConfig {
767 name: "Test".to_string(),
768 path_suffixes: vec![".test".to_string()],
769 ..Default::default()
770 },
771 grammar: grammar.clone(),
772 highlight_query,
773 brackets_query: tree_sitter::Query::new(grammar, "").unwrap(),
774 highlight_map: Default::default(),
775 });
776 lang.set_theme(&theme);
777
778 let buffer = cx.add_model(|cx| {
779 Buffer::from_history(0, History::new(text.into()), None, Some(lang), cx)
780 });
781 buffer.condition(&cx, |buf, _| !buf.is_parsing()).await;
782
783 let font_cache = cx.font_cache();
784 let settings = Settings {
785 tab_size: 4,
786 buffer_font_family: font_cache.load_family(&["Courier"]).unwrap(),
787 buffer_font_size: 16.0,
788 ..cx.read(Settings::test)
789 };
790 let map = cx.add_model(|cx| DisplayMap::new(buffer, settings, Some(40.0), cx));
791 assert_eq!(
792 cx.update(|cx| highlighted_chunks(0..5, &map, &theme, cx)),
793 [
794 ("fn \n".to_string(), None),
795 ("oute\nr".to_string(), Some("fn.name")),
796 ("() \n{}\n\n".to_string(), None),
797 ]
798 );
799 assert_eq!(
800 cx.update(|cx| highlighted_chunks(3..5, &map, &theme, cx)),
801 [("{}\n\n".to_string(), None)]
802 );
803
804 map.update(&mut cx, |map, cx| {
805 map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], cx)
806 });
807 assert_eq!(
808 cx.update(|cx| highlighted_chunks(1..4, &map, &theme, cx)),
809 [
810 ("out".to_string(), Some("fn.name")),
811 ("…\n".to_string(), None),
812 (" \nfn ".to_string(), Some("mod.body")),
813 ("i\n".to_string(), Some("fn.name"))
814 ]
815 );
816 }
817
818 #[gpui::test]
819 fn test_clip_point(cx: &mut gpui::MutableAppContext) {
820 use Bias::{Left, Right};
821
822 let text = "\n'a', 'α',\t'✋',\t'❎', '🍐'\n";
823 let display_text = "\n'a', 'α', '✋', '❎', '🍐'\n";
824 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
825 let map = cx.add_model(|cx| {
826 DisplayMap::new(
827 buffer.clone(),
828 Settings {
829 tab_size: 4,
830 ..Settings::test(cx)
831 },
832 None,
833 cx,
834 )
835 });
836 let map = map.update(cx, |map, cx| map.snapshot(cx));
837
838 assert_eq!(map.text(), display_text);
839 for (input_column, bias, output_column) in vec![
840 ("'a', '".len(), Left, "'a', '".len()),
841 ("'a', '".len() + 1, Left, "'a', '".len()),
842 ("'a', '".len() + 1, Right, "'a', 'α".len()),
843 ("'a', 'α', ".len(), Left, "'a', 'α',".len()),
844 ("'a', 'α', ".len(), Right, "'a', 'α', ".len()),
845 ("'a', 'α', '".len() + 1, Left, "'a', 'α', '".len()),
846 ("'a', 'α', '".len() + 1, Right, "'a', 'α', '✋".len()),
847 ("'a', 'α', '✋',".len(), Right, "'a', 'α', '✋',".len()),
848 ("'a', 'α', '✋', ".len(), Left, "'a', 'α', '✋',".len()),
849 (
850 "'a', 'α', '✋', ".len(),
851 Right,
852 "'a', 'α', '✋', ".len(),
853 ),
854 ] {
855 assert_eq!(
856 map.clip_point(DisplayPoint::new(1, input_column as u32), bias),
857 DisplayPoint::new(1, output_column as u32),
858 "clip_point(({}, {}))",
859 1,
860 input_column,
861 );
862 }
863 }
864
865 #[gpui::test]
866 fn test_tabs_with_multibyte_chars(cx: &mut gpui::MutableAppContext) {
867 let text = "✅\t\tα\nβ\t\n🏀β\t\tγ";
868 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
869 let map = cx.add_model(|cx| {
870 DisplayMap::new(
871 buffer.clone(),
872 Settings {
873 tab_size: 4,
874 ..Settings::test(cx)
875 },
876 None,
877 cx,
878 )
879 });
880 let map = map.update(cx, |map, cx| map.snapshot(cx));
881 assert_eq!(map.text(), "✅ α\nβ \n🏀β γ");
882 assert_eq!(
883 map.chunks_at(0).collect::<String>(),
884 "✅ α\nβ \n🏀β γ"
885 );
886 assert_eq!(map.chunks_at(1).collect::<String>(), "β \n🏀β γ");
887 assert_eq!(map.chunks_at(2).collect::<String>(), "🏀β γ");
888
889 let point = Point::new(0, "✅\t\t".len() as u32);
890 let display_point = DisplayPoint::new(0, "✅ ".len() as u32);
891 assert_eq!(point.to_display_point(&map, Left), display_point);
892 assert_eq!(display_point.to_buffer_point(&map, Left), point,);
893
894 let point = Point::new(1, "β\t".len() as u32);
895 let display_point = DisplayPoint::new(1, "β ".len() as u32);
896 assert_eq!(point.to_display_point(&map, Left), display_point);
897 assert_eq!(display_point.to_buffer_point(&map, Left), point,);
898
899 let point = Point::new(2, "🏀β\t\t".len() as u32);
900 let display_point = DisplayPoint::new(2, "🏀β ".len() as u32);
901 assert_eq!(point.to_display_point(&map, Left), display_point);
902 assert_eq!(display_point.to_buffer_point(&map, Left), point,);
903
904 // Display points inside of expanded tabs
905 assert_eq!(
906 DisplayPoint::new(0, "✅ ".len() as u32).to_buffer_point(&map, Right),
907 Point::new(0, "✅\t\t".len() as u32),
908 );
909 assert_eq!(
910 DisplayPoint::new(0, "✅ ".len() as u32).to_buffer_point(&map, Left),
911 Point::new(0, "✅\t".len() as u32),
912 );
913 assert_eq!(
914 DisplayPoint::new(0, "✅ ".len() as u32).to_buffer_point(&map, Right),
915 Point::new(0, "✅\t".len() as u32),
916 );
917 assert_eq!(
918 DisplayPoint::new(0, "✅ ".len() as u32).to_buffer_point(&map, Left),
919 Point::new(0, "✅".len() as u32),
920 );
921
922 // Clipping display points inside of multi-byte characters
923 assert_eq!(
924 map.clip_point(DisplayPoint::new(0, "✅".len() as u32 - 1), Left),
925 DisplayPoint::new(0, 0)
926 );
927 assert_eq!(
928 map.clip_point(DisplayPoint::new(0, "✅".len() as u32 - 1), Bias::Right),
929 DisplayPoint::new(0, "✅".len() as u32)
930 );
931 }
932
933 #[gpui::test]
934 fn test_max_point(cx: &mut gpui::MutableAppContext) {
935 let buffer = cx.add_model(|cx| Buffer::new(0, "aaa\n\t\tbbb", cx));
936 let map = cx.add_model(|cx| {
937 DisplayMap::new(
938 buffer.clone(),
939 Settings {
940 tab_size: 4,
941 ..Settings::test(cx)
942 },
943 None,
944 cx,
945 )
946 });
947 assert_eq!(
948 map.update(cx, |map, cx| map.snapshot(cx)).max_point(),
949 DisplayPoint::new(1, 11)
950 )
951 }
952
953 fn highlighted_chunks<'a>(
954 rows: Range<u32>,
955 map: &ModelHandle<DisplayMap>,
956 theme: &'a SyntaxTheme,
957 cx: &mut MutableAppContext,
958 ) -> Vec<(String, Option<&'a str>)> {
959 let mut snapshot = map.update(cx, |map, cx| map.snapshot(cx));
960 let mut chunks: Vec<(String, Option<&str>)> = Vec::new();
961 for (chunk, style_id) in snapshot.highlighted_chunks_for_rows(rows) {
962 let style_name = theme.highlight_name(style_id);
963 if let Some((last_chunk, last_style_name)) = chunks.last_mut() {
964 if style_name == *last_style_name {
965 last_chunk.push_str(chunk);
966 } else {
967 chunks.push((chunk.to_string(), style_name));
968 }
969 } else {
970 chunks.push((chunk.to_string(), style_name));
971 }
972 }
973 chunks
974 }
975}