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