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