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::{OffsetUtf16, 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(&self, start_row: u32) -> DisplayBufferRows {
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.tab_point_to_wrap_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 /// Returns text chunks starting at the given display row until the end of the file
334 pub fn text_chunks(&self, display_row: u32) -> impl Iterator<Item = &str> {
335 self.blocks_snapshot
336 .chunks(display_row..self.max_point().row() + 1, false, None)
337 .map(|h| h.text)
338 }
339
340 /// Returns text chunks starting at the end of the given display row in reverse until the start of the file
341 pub fn reverse_text_chunks(&self, display_row: u32) -> impl Iterator<Item = &str> {
342 (0..=display_row).into_iter().rev().flat_map(|row| {
343 self.blocks_snapshot
344 .chunks(row..row + 1, false, None)
345 .map(|h| h.text)
346 .collect::<Vec<_>>()
347 .into_iter()
348 .rev()
349 })
350 }
351
352 pub fn chunks(&self, display_rows: Range<u32>, language_aware: bool) -> DisplayChunks<'_> {
353 self.blocks_snapshot
354 .chunks(display_rows, language_aware, Some(&self.text_highlights))
355 }
356
357 pub fn chars_at(
358 &self,
359 mut point: DisplayPoint,
360 ) -> impl Iterator<Item = (char, DisplayPoint)> + '_ {
361 point = DisplayPoint(self.blocks_snapshot.clip_point(point.0, Bias::Left));
362 self.text_chunks(point.row())
363 .flat_map(str::chars)
364 .skip_while({
365 let mut column = 0;
366 move |char| {
367 let at_point = column >= point.column();
368 column += char.len_utf8() as u32;
369 !at_point
370 }
371 })
372 .map(move |ch| {
373 let result = (ch, point);
374 if ch == '\n' {
375 *point.row_mut() += 1;
376 *point.column_mut() = 0;
377 } else {
378 *point.column_mut() += ch.len_utf8() as u32;
379 }
380 result
381 })
382 }
383
384 pub fn reverse_chars_at(
385 &self,
386 mut point: DisplayPoint,
387 ) -> impl Iterator<Item = (char, DisplayPoint)> + '_ {
388 point = DisplayPoint(self.blocks_snapshot.clip_point(point.0, Bias::Left));
389 self.reverse_text_chunks(point.row())
390 .flat_map(|chunk| chunk.chars().rev())
391 .skip_while({
392 let mut column = self.line_len(point.row());
393 if self.max_point().row() > point.row() {
394 column += 1;
395 }
396
397 move |char| {
398 let at_point = column <= point.column();
399 column = column.saturating_sub(char.len_utf8() as u32);
400 !at_point
401 }
402 })
403 .map(move |ch| {
404 if ch == '\n' {
405 *point.row_mut() -= 1;
406 *point.column_mut() = self.line_len(point.row());
407 } else {
408 *point.column_mut() = point.column().saturating_sub(ch.len_utf8() as u32);
409 }
410 (ch, point)
411 })
412 }
413
414 /// Returns an iterator of the start positions of the occurances of `target` in the `self` after `from`
415 /// Stops if `condition` returns false for any of the character position pairs observed.
416 pub fn find_while<'a>(
417 &'a self,
418 from: DisplayPoint,
419 target: &str,
420 condition: impl FnMut(char, DisplayPoint) -> bool + 'a,
421 ) -> impl Iterator<Item = DisplayPoint> + 'a {
422 Self::find_internal(self.chars_at(from), target.chars().collect(), condition)
423 }
424
425 /// Returns an iterator of the end positions of the occurances of `target` in the `self` before `from`
426 /// Stops if `condition` returns false for any of the character position pairs observed.
427 pub fn reverse_find_while<'a>(
428 &'a self,
429 from: DisplayPoint,
430 target: &str,
431 condition: impl FnMut(char, DisplayPoint) -> bool + 'a,
432 ) -> impl Iterator<Item = DisplayPoint> + 'a {
433 Self::find_internal(
434 self.reverse_chars_at(from),
435 target.chars().rev().collect(),
436 condition,
437 )
438 }
439
440 fn find_internal<'a>(
441 iterator: impl Iterator<Item = (char, DisplayPoint)> + 'a,
442 target: Vec<char>,
443 mut condition: impl FnMut(char, DisplayPoint) -> bool + 'a,
444 ) -> impl Iterator<Item = DisplayPoint> + 'a {
445 // List of partial matches with the index of the last seen character in target and the starting point of the match
446 let mut partial_matches: Vec<(usize, DisplayPoint)> = Vec::new();
447 iterator
448 .take_while(move |(ch, point)| condition(*ch, *point))
449 .filter_map(move |(ch, point)| {
450 if Some(&ch) == target.get(0) {
451 partial_matches.push((0, point));
452 }
453
454 let mut found = None;
455 // Keep partial matches that have the correct next character
456 partial_matches.retain_mut(|(match_position, match_start)| {
457 if target.get(*match_position) == Some(&ch) {
458 *match_position += 1;
459 if *match_position == target.len() {
460 found = Some(match_start.clone());
461 // This match is completed. No need to keep tracking it
462 false
463 } else {
464 true
465 }
466 } else {
467 false
468 }
469 });
470
471 found
472 })
473 }
474
475 pub fn column_to_chars(&self, display_row: u32, target: u32) -> u32 {
476 let mut count = 0;
477 let mut column = 0;
478 for (c, _) in self.chars_at(DisplayPoint::new(display_row, 0)) {
479 if column >= target {
480 break;
481 }
482 count += 1;
483 column += c.len_utf8() as u32;
484 }
485 count
486 }
487
488 pub fn column_from_chars(&self, display_row: u32, char_count: u32) -> u32 {
489 let mut column = 0;
490
491 for (count, (c, _)) in self.chars_at(DisplayPoint::new(display_row, 0)).enumerate() {
492 if c == '\n' || count >= char_count as usize {
493 break;
494 }
495 column += c.len_utf8() as u32;
496 }
497
498 column
499 }
500
501 pub fn clip_point(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint {
502 let mut clipped = self.blocks_snapshot.clip_point(point.0, bias);
503 if self.clip_at_line_ends {
504 clipped = self.clip_at_line_end(DisplayPoint(clipped)).0
505 }
506 DisplayPoint(clipped)
507 }
508
509 pub fn clip_at_line_end(&self, point: DisplayPoint) -> DisplayPoint {
510 let mut point = point.0;
511 if point.column == self.line_len(point.row) {
512 point.column = point.column.saturating_sub(1);
513 point = self.blocks_snapshot.clip_point(point, Bias::Left);
514 }
515 DisplayPoint(point)
516 }
517
518 pub fn folds_in_range<T>(&self, range: Range<T>) -> impl Iterator<Item = &Range<Anchor>>
519 where
520 T: ToOffset,
521 {
522 self.folds_snapshot.folds_in_range(range)
523 }
524
525 pub fn blocks_in_range(
526 &self,
527 rows: Range<u32>,
528 ) -> impl Iterator<Item = (u32, &TransformBlock)> {
529 self.blocks_snapshot.blocks_in_range(rows)
530 }
531
532 pub fn intersects_fold<T: ToOffset>(&self, offset: T) -> bool {
533 self.folds_snapshot.intersects_fold(offset)
534 }
535
536 pub fn is_line_folded(&self, display_row: u32) -> bool {
537 let block_point = BlockPoint(Point::new(display_row, 0));
538 let wrap_point = self.blocks_snapshot.to_wrap_point(block_point);
539 let tab_point = self.wraps_snapshot.to_tab_point(wrap_point);
540 self.folds_snapshot.is_line_folded(tab_point.row())
541 }
542
543 pub fn is_block_line(&self, display_row: u32) -> bool {
544 self.blocks_snapshot.is_block_line(display_row)
545 }
546
547 pub fn soft_wrap_indent(&self, display_row: u32) -> Option<u32> {
548 let wrap_row = self
549 .blocks_snapshot
550 .to_wrap_point(BlockPoint::new(display_row, 0))
551 .row();
552 self.wraps_snapshot.soft_wrap_indent(wrap_row)
553 }
554
555 pub fn text(&self) -> String {
556 self.text_chunks(0).collect()
557 }
558
559 pub fn line(&self, display_row: u32) -> String {
560 let mut result = String::new();
561 for chunk in self.text_chunks(display_row) {
562 if let Some(ix) = chunk.find('\n') {
563 result.push_str(&chunk[0..ix]);
564 break;
565 } else {
566 result.push_str(chunk);
567 }
568 }
569 result
570 }
571
572 pub fn line_indent(&self, display_row: u32) -> (u32, bool) {
573 let mut indent = 0;
574 let mut is_blank = true;
575 for (c, _) in self.chars_at(DisplayPoint::new(display_row, 0)) {
576 if c == ' ' {
577 indent += 1;
578 } else {
579 is_blank = c == '\n';
580 break;
581 }
582 }
583 (indent, is_blank)
584 }
585
586 pub fn line_len(&self, row: u32) -> u32 {
587 self.blocks_snapshot.line_len(row)
588 }
589
590 pub fn longest_row(&self) -> u32 {
591 self.blocks_snapshot.longest_row()
592 }
593
594 #[cfg(any(test, feature = "test-support"))]
595 pub fn highlight_ranges<Tag: ?Sized + 'static>(
596 &self,
597 ) -> Option<Arc<(HighlightStyle, Vec<Range<Anchor>>)>> {
598 let type_id = TypeId::of::<Tag>();
599 self.text_highlights.get(&Some(type_id)).cloned()
600 }
601}
602
603#[derive(Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq)]
604pub struct DisplayPoint(BlockPoint);
605
606impl Debug for DisplayPoint {
607 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
608 f.write_fmt(format_args!(
609 "DisplayPoint({}, {})",
610 self.row(),
611 self.column()
612 ))
613 }
614}
615
616impl DisplayPoint {
617 pub fn new(row: u32, column: u32) -> Self {
618 Self(BlockPoint(Point::new(row, column)))
619 }
620
621 pub fn zero() -> Self {
622 Self::new(0, 0)
623 }
624
625 pub fn is_zero(&self) -> bool {
626 self.0.is_zero()
627 }
628
629 pub fn row(self) -> u32 {
630 self.0.row
631 }
632
633 pub fn column(self) -> u32 {
634 self.0.column
635 }
636
637 pub fn row_mut(&mut self) -> &mut u32 {
638 &mut self.0.row
639 }
640
641 pub fn column_mut(&mut self) -> &mut u32 {
642 &mut self.0.column
643 }
644
645 pub fn to_point(self, map: &DisplaySnapshot) -> Point {
646 map.display_point_to_point(self, Bias::Left)
647 }
648
649 pub fn to_offset(self, map: &DisplaySnapshot, bias: Bias) -> usize {
650 let unblocked_point = map.blocks_snapshot.to_wrap_point(self.0);
651 let unwrapped_point = map.wraps_snapshot.to_tab_point(unblocked_point);
652 let unexpanded_point = map.tabs_snapshot.to_fold_point(unwrapped_point, bias).0;
653 unexpanded_point.to_buffer_offset(&map.folds_snapshot)
654 }
655}
656
657impl ToDisplayPoint for usize {
658 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
659 map.point_to_display_point(self.to_point(&map.buffer_snapshot), Bias::Left)
660 }
661}
662
663impl ToDisplayPoint for OffsetUtf16 {
664 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
665 self.to_offset(&map.buffer_snapshot).to_display_point(map)
666 }
667}
668
669impl ToDisplayPoint for Point {
670 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
671 map.point_to_display_point(*self, Bias::Left)
672 }
673}
674
675impl ToDisplayPoint for Anchor {
676 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
677 self.to_point(&map.buffer_snapshot).to_display_point(map)
678 }
679}
680
681#[cfg(test)]
682pub mod tests {
683 use super::*;
684 use crate::{movement, test::marked_display_snapshot};
685 use gpui::{color::Color, elements::*, test::observe, MutableAppContext};
686 use language::{Buffer, Language, LanguageConfig, SelectionGoal};
687 use rand::{prelude::*, Rng};
688 use smol::stream::StreamExt;
689 use std::{env, sync::Arc};
690 use theme::SyntaxTheme;
691 use util::test::{marked_text_offsets, marked_text_ranges, sample_text};
692 use Bias::*;
693
694 #[gpui::test(iterations = 100)]
695 async fn test_random_display_map(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
696 cx.foreground().set_block_on_ticks(0..=50);
697 cx.foreground().forbid_parking();
698 let operations = env::var("OPERATIONS")
699 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
700 .unwrap_or(10);
701
702 let font_cache = cx.font_cache().clone();
703 let mut tab_size = rng.gen_range(1..=4);
704 let buffer_start_excerpt_header_height = rng.gen_range(1..=5);
705 let excerpt_header_height = rng.gen_range(1..=5);
706 let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
707 let font_id = font_cache
708 .select_font(family_id, &Default::default())
709 .unwrap();
710 let font_size = 14.0;
711 let max_wrap_width = 300.0;
712 let mut wrap_width = if rng.gen_bool(0.1) {
713 None
714 } else {
715 Some(rng.gen_range(0.0..=max_wrap_width))
716 };
717
718 log::info!("tab size: {}", tab_size);
719 log::info!("wrap width: {:?}", wrap_width);
720
721 cx.update(|cx| {
722 let mut settings = Settings::test(cx);
723 settings.editor_overrides.tab_size = NonZeroU32::new(tab_size);
724 cx.set_global(settings)
725 });
726
727 let buffer = cx.update(|cx| {
728 if rng.gen() {
729 let len = rng.gen_range(0..10);
730 let text = util::RandomCharIter::new(&mut rng)
731 .take(len)
732 .collect::<String>();
733 MultiBuffer::build_simple(&text, cx)
734 } else {
735 MultiBuffer::build_random(&mut rng, cx)
736 }
737 });
738
739 let map = cx.add_model(|cx| {
740 DisplayMap::new(
741 buffer.clone(),
742 font_id,
743 font_size,
744 wrap_width,
745 buffer_start_excerpt_header_height,
746 excerpt_header_height,
747 cx,
748 )
749 });
750 let mut notifications = observe(&map, cx);
751 let mut fold_count = 0;
752 let mut blocks = Vec::new();
753
754 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
755 log::info!("buffer text: {:?}", snapshot.buffer_snapshot.text());
756 log::info!("fold text: {:?}", snapshot.folds_snapshot.text());
757 log::info!("tab text: {:?}", snapshot.tabs_snapshot.text());
758 log::info!("wrap text: {:?}", snapshot.wraps_snapshot.text());
759 log::info!("block text: {:?}", snapshot.blocks_snapshot.text());
760 log::info!("display text: {:?}", snapshot.text());
761
762 for _i in 0..operations {
763 match rng.gen_range(0..100) {
764 0..=19 => {
765 wrap_width = if rng.gen_bool(0.2) {
766 None
767 } else {
768 Some(rng.gen_range(0.0..=max_wrap_width))
769 };
770 log::info!("setting wrap width to {:?}", wrap_width);
771 map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
772 }
773 20..=29 => {
774 let mut tab_sizes = vec![1, 2, 3, 4];
775 tab_sizes.remove((tab_size - 1) as usize);
776 tab_size = *tab_sizes.choose(&mut rng).unwrap();
777 log::info!("setting tab size to {:?}", tab_size);
778 cx.update(|cx| {
779 let mut settings = Settings::test(cx);
780 settings.editor_overrides.tab_size = NonZeroU32::new(tab_size);
781 cx.set_global(settings)
782 });
783 }
784 30..=44 => {
785 map.update(cx, |map, cx| {
786 if rng.gen() || blocks.is_empty() {
787 let buffer = map.snapshot(cx).buffer_snapshot;
788 let block_properties = (0..rng.gen_range(1..=1))
789 .map(|_| {
790 let position =
791 buffer.anchor_after(buffer.clip_offset(
792 rng.gen_range(0..=buffer.len()),
793 Bias::Left,
794 ));
795
796 let disposition = if rng.gen() {
797 BlockDisposition::Above
798 } else {
799 BlockDisposition::Below
800 };
801 let height = rng.gen_range(1..5);
802 log::info!(
803 "inserting block {:?} {:?} with height {}",
804 disposition,
805 position.to_point(&buffer),
806 height
807 );
808 BlockProperties {
809 style: BlockStyle::Fixed,
810 position,
811 height,
812 disposition,
813 render: Arc::new(|_| Empty::new().boxed()),
814 }
815 })
816 .collect::<Vec<_>>();
817 blocks.extend(map.insert_blocks(block_properties, cx));
818 } else {
819 blocks.shuffle(&mut rng);
820 let remove_count = rng.gen_range(1..=4.min(blocks.len()));
821 let block_ids_to_remove = (0..remove_count)
822 .map(|_| blocks.remove(rng.gen_range(0..blocks.len())))
823 .collect();
824 log::info!("removing block ids {:?}", block_ids_to_remove);
825 map.remove_blocks(block_ids_to_remove, cx);
826 }
827 });
828 }
829 45..=79 => {
830 let mut ranges = Vec::new();
831 for _ in 0..rng.gen_range(1..=3) {
832 buffer.read_with(cx, |buffer, cx| {
833 let buffer = buffer.read(cx);
834 let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right);
835 let start = buffer.clip_offset(rng.gen_range(0..=end), Left);
836 ranges.push(start..end);
837 });
838 }
839
840 if rng.gen() && fold_count > 0 {
841 log::info!("unfolding ranges: {:?}", ranges);
842 map.update(cx, |map, cx| {
843 map.unfold(ranges, true, cx);
844 });
845 } else {
846 log::info!("folding ranges: {:?}", ranges);
847 map.update(cx, |map, cx| {
848 map.fold(ranges, cx);
849 });
850 }
851 }
852 _ => {
853 buffer.update(cx, |buffer, cx| buffer.randomly_mutate(&mut rng, 5, cx));
854 }
855 }
856
857 if map.read_with(cx, |map, cx| map.is_rewrapping(cx)) {
858 notifications.next().await.unwrap();
859 }
860
861 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
862 fold_count = snapshot.fold_count();
863 log::info!("buffer text: {:?}", snapshot.buffer_snapshot.text());
864 log::info!("fold text: {:?}", snapshot.folds_snapshot.text());
865 log::info!("tab text: {:?}", snapshot.tabs_snapshot.text());
866 log::info!("wrap text: {:?}", snapshot.wraps_snapshot.text());
867 log::info!("block text: {:?}", snapshot.blocks_snapshot.text());
868 log::info!("display text: {:?}", snapshot.text());
869
870 // Line boundaries
871 let buffer = &snapshot.buffer_snapshot;
872 for _ in 0..5 {
873 let row = rng.gen_range(0..=buffer.max_point().row);
874 let column = rng.gen_range(0..=buffer.line_len(row));
875 let point = buffer.clip_point(Point::new(row, column), Left);
876
877 let (prev_buffer_bound, prev_display_bound) = snapshot.prev_line_boundary(point);
878 let (next_buffer_bound, next_display_bound) = snapshot.next_line_boundary(point);
879
880 assert!(prev_buffer_bound <= point);
881 assert!(next_buffer_bound >= point);
882 assert_eq!(prev_buffer_bound.column, 0);
883 assert_eq!(prev_display_bound.column(), 0);
884 if next_buffer_bound < buffer.max_point() {
885 assert_eq!(buffer.chars_at(next_buffer_bound).next(), Some('\n'));
886 }
887
888 assert_eq!(
889 prev_display_bound,
890 prev_buffer_bound.to_display_point(&snapshot),
891 "row boundary before {:?}. reported buffer row boundary: {:?}",
892 point,
893 prev_buffer_bound
894 );
895 assert_eq!(
896 next_display_bound,
897 next_buffer_bound.to_display_point(&snapshot),
898 "display row boundary after {:?}. reported buffer row boundary: {:?}",
899 point,
900 next_buffer_bound
901 );
902 assert_eq!(
903 prev_buffer_bound,
904 prev_display_bound.to_point(&snapshot),
905 "row boundary before {:?}. reported display row boundary: {:?}",
906 point,
907 prev_display_bound
908 );
909 assert_eq!(
910 next_buffer_bound,
911 next_display_bound.to_point(&snapshot),
912 "row boundary after {:?}. reported display row boundary: {:?}",
913 point,
914 next_display_bound
915 );
916 }
917
918 // Movement
919 let min_point = snapshot.clip_point(DisplayPoint::new(0, 0), Left);
920 let max_point = snapshot.clip_point(snapshot.max_point(), Right);
921 for _ in 0..5 {
922 let row = rng.gen_range(0..=snapshot.max_point().row());
923 let column = rng.gen_range(0..=snapshot.line_len(row));
924 let point = snapshot.clip_point(DisplayPoint::new(row, column), Left);
925
926 log::info!("Moving from point {:?}", point);
927
928 let moved_right = movement::right(&snapshot, point);
929 log::info!("Right {:?}", moved_right);
930 if point < max_point {
931 assert!(moved_right > point);
932 if point.column() == snapshot.line_len(point.row())
933 || snapshot.soft_wrap_indent(point.row()).is_some()
934 && point.column() == snapshot.line_len(point.row()) - 1
935 {
936 assert!(moved_right.row() > point.row());
937 }
938 } else {
939 assert_eq!(moved_right, point);
940 }
941
942 let moved_left = movement::left(&snapshot, point);
943 log::info!("Left {:?}", moved_left);
944 if point > min_point {
945 assert!(moved_left < point);
946 if point.column() == 0 {
947 assert!(moved_left.row() < point.row());
948 }
949 } else {
950 assert_eq!(moved_left, point);
951 }
952 }
953 }
954 }
955
956 #[gpui::test(retries = 5)]
957 fn test_soft_wraps(cx: &mut MutableAppContext) {
958 cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
959 cx.foreground().forbid_parking();
960
961 let font_cache = cx.font_cache();
962
963 let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
964 let font_id = font_cache
965 .select_font(family_id, &Default::default())
966 .unwrap();
967 let font_size = 12.0;
968 let wrap_width = Some(64.);
969 cx.set_global(Settings::test(cx));
970
971 let text = "one two three four five\nsix seven eight";
972 let buffer = MultiBuffer::build_simple(text, cx);
973 let map = cx.add_model(|cx| {
974 DisplayMap::new(buffer.clone(), font_id, font_size, wrap_width, 1, 1, cx)
975 });
976
977 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
978 assert_eq!(
979 snapshot.text_chunks(0).collect::<String>(),
980 "one two \nthree four \nfive\nsix seven \neight"
981 );
982 assert_eq!(
983 snapshot.clip_point(DisplayPoint::new(0, 8), Bias::Left),
984 DisplayPoint::new(0, 7)
985 );
986 assert_eq!(
987 snapshot.clip_point(DisplayPoint::new(0, 8), Bias::Right),
988 DisplayPoint::new(1, 0)
989 );
990 assert_eq!(
991 movement::right(&snapshot, DisplayPoint::new(0, 7)),
992 DisplayPoint::new(1, 0)
993 );
994 assert_eq!(
995 movement::left(&snapshot, DisplayPoint::new(1, 0)),
996 DisplayPoint::new(0, 7)
997 );
998 assert_eq!(
999 movement::up(
1000 &snapshot,
1001 DisplayPoint::new(1, 10),
1002 SelectionGoal::None,
1003 false
1004 ),
1005 (DisplayPoint::new(0, 7), SelectionGoal::Column(10))
1006 );
1007 assert_eq!(
1008 movement::down(
1009 &snapshot,
1010 DisplayPoint::new(0, 7),
1011 SelectionGoal::Column(10),
1012 false
1013 ),
1014 (DisplayPoint::new(1, 10), SelectionGoal::Column(10))
1015 );
1016 assert_eq!(
1017 movement::down(
1018 &snapshot,
1019 DisplayPoint::new(1, 10),
1020 SelectionGoal::Column(10),
1021 false
1022 ),
1023 (DisplayPoint::new(2, 4), SelectionGoal::Column(10))
1024 );
1025
1026 let ix = snapshot.buffer_snapshot.text().find("seven").unwrap();
1027 buffer.update(cx, |buffer, cx| {
1028 buffer.edit([(ix..ix, "and ")], None, cx);
1029 });
1030
1031 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1032 assert_eq!(
1033 snapshot.text_chunks(1).collect::<String>(),
1034 "three four \nfive\nsix and \nseven eight"
1035 );
1036
1037 // Re-wrap on font size changes
1038 map.update(cx, |map, cx| map.set_font(font_id, font_size + 3., cx));
1039
1040 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1041 assert_eq!(
1042 snapshot.text_chunks(1).collect::<String>(),
1043 "three \nfour five\nsix and \nseven \neight"
1044 )
1045 }
1046
1047 #[gpui::test]
1048 fn test_text_chunks(cx: &mut gpui::MutableAppContext) {
1049 cx.set_global(Settings::test(cx));
1050 let text = sample_text(6, 6, 'a');
1051 let buffer = MultiBuffer::build_simple(&text, cx);
1052 let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap();
1053 let font_id = cx
1054 .font_cache()
1055 .select_font(family_id, &Default::default())
1056 .unwrap();
1057 let font_size = 14.0;
1058 let map =
1059 cx.add_model(|cx| DisplayMap::new(buffer.clone(), font_id, font_size, None, 1, 1, cx));
1060 buffer.update(cx, |buffer, cx| {
1061 buffer.edit(
1062 vec![
1063 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1064 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1065 (Point::new(2, 1)..Point::new(2, 1), "\t"),
1066 ],
1067 None,
1068 cx,
1069 )
1070 });
1071
1072 assert_eq!(
1073 map.update(cx, |map, cx| map.snapshot(cx))
1074 .text_chunks(1)
1075 .collect::<String>()
1076 .lines()
1077 .next(),
1078 Some(" b bbbbb")
1079 );
1080 assert_eq!(
1081 map.update(cx, |map, cx| map.snapshot(cx))
1082 .text_chunks(2)
1083 .collect::<String>()
1084 .lines()
1085 .next(),
1086 Some("c ccccc")
1087 );
1088 }
1089
1090 #[gpui::test]
1091 async fn test_chunks(cx: &mut gpui::TestAppContext) {
1092 use unindent::Unindent as _;
1093
1094 let text = r#"
1095 fn outer() {}
1096
1097 mod module {
1098 fn inner() {}
1099 }"#
1100 .unindent();
1101
1102 let theme = SyntaxTheme::new(vec![
1103 ("mod.body".to_string(), Color::red().into()),
1104 ("fn.name".to_string(), Color::blue().into()),
1105 ]);
1106 let language = Arc::new(
1107 Language::new(
1108 LanguageConfig {
1109 name: "Test".into(),
1110 path_suffixes: vec![".test".to_string()],
1111 ..Default::default()
1112 },
1113 Some(tree_sitter_rust::language()),
1114 )
1115 .with_highlights_query(
1116 r#"
1117 (mod_item name: (identifier) body: _ @mod.body)
1118 (function_item name: (identifier) @fn.name)
1119 "#,
1120 )
1121 .unwrap(),
1122 );
1123 language.set_theme(&theme);
1124 cx.update(|cx| {
1125 let mut settings = Settings::test(cx);
1126 settings.editor_defaults.tab_size = Some(2.try_into().unwrap());
1127 cx.set_global(settings);
1128 });
1129
1130 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
1131 buffer.condition(cx, |buf, _| !buf.is_parsing()).await;
1132 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
1133
1134 let font_cache = cx.font_cache();
1135 let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
1136 let font_id = font_cache
1137 .select_font(family_id, &Default::default())
1138 .unwrap();
1139 let font_size = 14.0;
1140
1141 let map = cx.add_model(|cx| DisplayMap::new(buffer, font_id, font_size, None, 1, 1, cx));
1142 assert_eq!(
1143 cx.update(|cx| syntax_chunks(0..5, &map, &theme, cx)),
1144 vec![
1145 ("fn ".to_string(), None),
1146 ("outer".to_string(), Some(Color::blue())),
1147 ("() {}\n\nmod module ".to_string(), None),
1148 ("{\n fn ".to_string(), Some(Color::red())),
1149 ("inner".to_string(), Some(Color::blue())),
1150 ("() {}\n}".to_string(), Some(Color::red())),
1151 ]
1152 );
1153 assert_eq!(
1154 cx.update(|cx| syntax_chunks(3..5, &map, &theme, cx)),
1155 vec![
1156 (" fn ".to_string(), Some(Color::red())),
1157 ("inner".to_string(), Some(Color::blue())),
1158 ("() {}\n}".to_string(), Some(Color::red())),
1159 ]
1160 );
1161
1162 map.update(cx, |map, cx| {
1163 map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], cx)
1164 });
1165 assert_eq!(
1166 cx.update(|cx| syntax_chunks(0..2, &map, &theme, cx)),
1167 vec![
1168 ("fn ".to_string(), None),
1169 ("out".to_string(), Some(Color::blue())),
1170 ("…".to_string(), None),
1171 (" fn ".to_string(), Some(Color::red())),
1172 ("inner".to_string(), Some(Color::blue())),
1173 ("() {}\n}".to_string(), Some(Color::red())),
1174 ]
1175 );
1176 }
1177
1178 #[gpui::test]
1179 async fn test_chunks_with_soft_wrapping(cx: &mut gpui::TestAppContext) {
1180 use unindent::Unindent as _;
1181
1182 cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
1183
1184 let text = r#"
1185 fn outer() {}
1186
1187 mod module {
1188 fn inner() {}
1189 }"#
1190 .unindent();
1191
1192 let theme = SyntaxTheme::new(vec![
1193 ("mod.body".to_string(), Color::red().into()),
1194 ("fn.name".to_string(), Color::blue().into()),
1195 ]);
1196 let language = Arc::new(
1197 Language::new(
1198 LanguageConfig {
1199 name: "Test".into(),
1200 path_suffixes: vec![".test".to_string()],
1201 ..Default::default()
1202 },
1203 Some(tree_sitter_rust::language()),
1204 )
1205 .with_highlights_query(
1206 r#"
1207 (mod_item name: (identifier) body: _ @mod.body)
1208 (function_item name: (identifier) @fn.name)
1209 "#,
1210 )
1211 .unwrap(),
1212 );
1213 language.set_theme(&theme);
1214
1215 cx.update(|cx| cx.set_global(Settings::test(cx)));
1216
1217 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
1218 buffer.condition(cx, |buf, _| !buf.is_parsing()).await;
1219 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
1220
1221 let font_cache = cx.font_cache();
1222
1223 let family_id = font_cache.load_family(&["Courier"]).unwrap();
1224 let font_id = font_cache
1225 .select_font(family_id, &Default::default())
1226 .unwrap();
1227 let font_size = 16.0;
1228
1229 let map =
1230 cx.add_model(|cx| DisplayMap::new(buffer, font_id, font_size, Some(40.0), 1, 1, cx));
1231 assert_eq!(
1232 cx.update(|cx| syntax_chunks(0..5, &map, &theme, cx)),
1233 [
1234 ("fn \n".to_string(), None),
1235 ("oute\nr".to_string(), Some(Color::blue())),
1236 ("() \n{}\n\n".to_string(), None),
1237 ]
1238 );
1239 assert_eq!(
1240 cx.update(|cx| syntax_chunks(3..5, &map, &theme, cx)),
1241 [("{}\n\n".to_string(), None)]
1242 );
1243
1244 map.update(cx, |map, cx| {
1245 map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], cx)
1246 });
1247 assert_eq!(
1248 cx.update(|cx| syntax_chunks(1..4, &map, &theme, cx)),
1249 [
1250 ("out".to_string(), Some(Color::blue())),
1251 ("…\n".to_string(), None),
1252 (" \nfn ".to_string(), Some(Color::red())),
1253 ("i\n".to_string(), Some(Color::blue()))
1254 ]
1255 );
1256 }
1257
1258 #[gpui::test]
1259 async fn test_chunks_with_text_highlights(cx: &mut gpui::TestAppContext) {
1260 cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
1261
1262 cx.update(|cx| cx.set_global(Settings::test(cx)));
1263 let theme = SyntaxTheme::new(vec![
1264 ("operator".to_string(), Color::red().into()),
1265 ("string".to_string(), Color::green().into()),
1266 ]);
1267 let language = Arc::new(
1268 Language::new(
1269 LanguageConfig {
1270 name: "Test".into(),
1271 path_suffixes: vec![".test".to_string()],
1272 ..Default::default()
1273 },
1274 Some(tree_sitter_rust::language()),
1275 )
1276 .with_highlights_query(
1277 r#"
1278 ":" @operator
1279 (string_literal) @string
1280 "#,
1281 )
1282 .unwrap(),
1283 );
1284 language.set_theme(&theme);
1285
1286 let (text, highlighted_ranges) = marked_text_ranges(r#"constˇ «a»: B = "c «d»""#, false);
1287
1288 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
1289 buffer.condition(cx, |buf, _| !buf.is_parsing()).await;
1290
1291 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
1292 let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
1293
1294 let font_cache = cx.font_cache();
1295 let family_id = font_cache.load_family(&["Courier"]).unwrap();
1296 let font_id = font_cache
1297 .select_font(family_id, &Default::default())
1298 .unwrap();
1299 let font_size = 16.0;
1300 let map = cx.add_model(|cx| DisplayMap::new(buffer, font_id, font_size, None, 1, 1, cx));
1301
1302 enum MyType {}
1303
1304 let style = HighlightStyle {
1305 color: Some(Color::blue()),
1306 ..Default::default()
1307 };
1308
1309 map.update(cx, |map, _cx| {
1310 map.highlight_text(
1311 TypeId::of::<MyType>(),
1312 highlighted_ranges
1313 .into_iter()
1314 .map(|range| {
1315 buffer_snapshot.anchor_before(range.start)
1316 ..buffer_snapshot.anchor_before(range.end)
1317 })
1318 .collect(),
1319 style,
1320 );
1321 });
1322
1323 assert_eq!(
1324 cx.update(|cx| chunks(0..10, &map, &theme, cx)),
1325 [
1326 ("const ".to_string(), None, None),
1327 ("a".to_string(), None, Some(Color::blue())),
1328 (":".to_string(), Some(Color::red()), None),
1329 (" B = ".to_string(), None, None),
1330 ("\"c ".to_string(), Some(Color::green()), None),
1331 ("d".to_string(), Some(Color::green()), Some(Color::blue())),
1332 ("\"".to_string(), Some(Color::green()), None),
1333 ]
1334 );
1335 }
1336
1337 #[gpui::test]
1338 fn test_clip_point(cx: &mut gpui::MutableAppContext) {
1339 cx.set_global(Settings::test(cx));
1340 fn assert(text: &str, shift_right: bool, bias: Bias, cx: &mut gpui::MutableAppContext) {
1341 let (unmarked_snapshot, mut markers) = marked_display_snapshot(text, cx);
1342
1343 match bias {
1344 Bias::Left => {
1345 if shift_right {
1346 *markers[1].column_mut() += 1;
1347 }
1348
1349 assert_eq!(unmarked_snapshot.clip_point(markers[1], bias), markers[0])
1350 }
1351 Bias::Right => {
1352 if shift_right {
1353 *markers[0].column_mut() += 1;
1354 }
1355
1356 assert_eq!(unmarked_snapshot.clip_point(markers[0], bias), markers[1])
1357 }
1358 };
1359 }
1360
1361 use Bias::{Left, Right};
1362 assert("ˇˇα", false, Left, cx);
1363 assert("ˇˇα", true, Left, cx);
1364 assert("ˇˇα", false, Right, cx);
1365 assert("ˇαˇ", true, Right, cx);
1366 assert("ˇˇ✋", false, Left, cx);
1367 assert("ˇˇ✋", true, Left, cx);
1368 assert("ˇˇ✋", false, Right, cx);
1369 assert("ˇ✋ˇ", true, Right, cx);
1370 assert("ˇˇ🍐", false, Left, cx);
1371 assert("ˇˇ🍐", true, Left, cx);
1372 assert("ˇˇ🍐", false, Right, cx);
1373 assert("ˇ🍐ˇ", true, Right, cx);
1374 assert("ˇˇ\t", false, Left, cx);
1375 assert("ˇˇ\t", true, Left, cx);
1376 assert("ˇˇ\t", false, Right, cx);
1377 assert("ˇ\tˇ", true, Right, cx);
1378 assert(" ˇˇ\t", false, Left, cx);
1379 assert(" ˇˇ\t", true, Left, cx);
1380 assert(" ˇˇ\t", false, Right, cx);
1381 assert(" ˇ\tˇ", true, Right, cx);
1382 assert(" ˇˇ\t", false, Left, cx);
1383 assert(" ˇˇ\t", false, Right, cx);
1384 }
1385
1386 #[gpui::test]
1387 fn test_clip_at_line_ends(cx: &mut gpui::MutableAppContext) {
1388 cx.set_global(Settings::test(cx));
1389
1390 fn assert(text: &str, cx: &mut gpui::MutableAppContext) {
1391 let (mut unmarked_snapshot, markers) = marked_display_snapshot(text, cx);
1392 unmarked_snapshot.clip_at_line_ends = true;
1393 assert_eq!(
1394 unmarked_snapshot.clip_point(markers[1], Bias::Left),
1395 markers[0]
1396 );
1397 }
1398
1399 assert("ˇˇ", cx);
1400 assert("ˇaˇ", cx);
1401 assert("aˇbˇ", cx);
1402 assert("aˇαˇ", cx);
1403 }
1404
1405 #[gpui::test]
1406 fn test_tabs_with_multibyte_chars(cx: &mut gpui::MutableAppContext) {
1407 cx.set_global(Settings::test(cx));
1408 let text = "✅\t\tα\nβ\t\n🏀β\t\tγ";
1409 let buffer = MultiBuffer::build_simple(text, cx);
1410 let font_cache = cx.font_cache();
1411 let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
1412 let font_id = font_cache
1413 .select_font(family_id, &Default::default())
1414 .unwrap();
1415 let font_size = 14.0;
1416
1417 let map =
1418 cx.add_model(|cx| DisplayMap::new(buffer.clone(), font_id, font_size, None, 1, 1, cx));
1419 let map = map.update(cx, |map, cx| map.snapshot(cx));
1420 assert_eq!(map.text(), "✅ α\nβ \n🏀β γ");
1421 assert_eq!(
1422 map.text_chunks(0).collect::<String>(),
1423 "✅ α\nβ \n🏀β γ"
1424 );
1425 assert_eq!(map.text_chunks(1).collect::<String>(), "β \n🏀β γ");
1426 assert_eq!(map.text_chunks(2).collect::<String>(), "🏀β γ");
1427
1428 let point = Point::new(0, "✅\t\t".len() as u32);
1429 let display_point = DisplayPoint::new(0, "✅ ".len() as u32);
1430 assert_eq!(point.to_display_point(&map), display_point);
1431 assert_eq!(display_point.to_point(&map), point);
1432
1433 let point = Point::new(1, "β\t".len() as u32);
1434 let display_point = DisplayPoint::new(1, "β ".len() as u32);
1435 assert_eq!(point.to_display_point(&map), display_point);
1436 assert_eq!(display_point.to_point(&map), point,);
1437
1438 let point = Point::new(2, "🏀β\t\t".len() as u32);
1439 let display_point = DisplayPoint::new(2, "🏀β ".len() as u32);
1440 assert_eq!(point.to_display_point(&map), display_point);
1441 assert_eq!(display_point.to_point(&map), point,);
1442
1443 // Display points inside of expanded tabs
1444 assert_eq!(
1445 DisplayPoint::new(0, "✅ ".len() as u32).to_point(&map),
1446 Point::new(0, "✅\t".len() as u32),
1447 );
1448 assert_eq!(
1449 DisplayPoint::new(0, "✅ ".len() as u32).to_point(&map),
1450 Point::new(0, "✅".len() as u32),
1451 );
1452
1453 // Clipping display points inside of multi-byte characters
1454 assert_eq!(
1455 map.clip_point(DisplayPoint::new(0, "✅".len() as u32 - 1), Left),
1456 DisplayPoint::new(0, 0)
1457 );
1458 assert_eq!(
1459 map.clip_point(DisplayPoint::new(0, "✅".len() as u32 - 1), Bias::Right),
1460 DisplayPoint::new(0, "✅".len() as u32)
1461 );
1462 }
1463
1464 #[gpui::test]
1465 fn test_max_point(cx: &mut gpui::MutableAppContext) {
1466 cx.set_global(Settings::test(cx));
1467 let buffer = MultiBuffer::build_simple("aaa\n\t\tbbb", cx);
1468 let font_cache = cx.font_cache();
1469 let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
1470 let font_id = font_cache
1471 .select_font(family_id, &Default::default())
1472 .unwrap();
1473 let font_size = 14.0;
1474 let map =
1475 cx.add_model(|cx| DisplayMap::new(buffer.clone(), font_id, font_size, None, 1, 1, cx));
1476 assert_eq!(
1477 map.update(cx, |map, cx| map.snapshot(cx)).max_point(),
1478 DisplayPoint::new(1, 11)
1479 )
1480 }
1481
1482 #[test]
1483 fn test_find_internal() {
1484 assert("This is a ˇtest of find internal", "test");
1485 assert("Some text ˇaˇaˇaa with repeated characters", "aa");
1486
1487 fn assert(marked_text: &str, target: &str) {
1488 let (text, expected_offsets) = marked_text_offsets(marked_text);
1489
1490 let chars = text
1491 .chars()
1492 .enumerate()
1493 .map(|(index, ch)| (ch, DisplayPoint::new(0, index as u32)));
1494 let target = target.chars();
1495
1496 assert_eq!(
1497 expected_offsets
1498 .into_iter()
1499 .map(|offset| offset as u32)
1500 .collect::<Vec<_>>(),
1501 DisplaySnapshot::find_internal(chars, target.collect(), |_, _| true)
1502 .map(|point| point.column())
1503 .collect::<Vec<_>>()
1504 )
1505 }
1506 }
1507
1508 fn syntax_chunks<'a>(
1509 rows: Range<u32>,
1510 map: &ModelHandle<DisplayMap>,
1511 theme: &'a SyntaxTheme,
1512 cx: &mut MutableAppContext,
1513 ) -> Vec<(String, Option<Color>)> {
1514 chunks(rows, map, theme, cx)
1515 .into_iter()
1516 .map(|(text, color, _)| (text, color))
1517 .collect()
1518 }
1519
1520 fn chunks<'a>(
1521 rows: Range<u32>,
1522 map: &ModelHandle<DisplayMap>,
1523 theme: &'a SyntaxTheme,
1524 cx: &mut MutableAppContext,
1525 ) -> Vec<(String, Option<Color>, Option<Color>)> {
1526 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1527 let mut chunks: Vec<(String, Option<Color>, Option<Color>)> = Vec::new();
1528 for chunk in snapshot.chunks(rows, true) {
1529 let syntax_color = chunk
1530 .syntax_highlight_id
1531 .and_then(|id| id.style(theme)?.color);
1532 let highlight_color = chunk.highlight_style.and_then(|style| style.color);
1533 if let Some((last_chunk, last_syntax_color, last_highlight_color)) = chunks.last_mut() {
1534 if syntax_color == *last_syntax_color && highlight_color == *last_highlight_color {
1535 last_chunk.push_str(chunk.text);
1536 continue;
1537 }
1538 }
1539 chunks.push((chunk.text.to_string(), syntax_color, highlight_color));
1540 }
1541 chunks
1542 }
1543}