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