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