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