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