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