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