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