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