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