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