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