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