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