1mod block_map;
2mod fold_map;
3mod suggestion_map;
4mod tab_map;
5mod wrap_map;
6
7use crate::{Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint};
8pub use block_map::{BlockMap, BlockPoint};
9use collections::{HashMap, HashSet};
10use fold_map::{FoldMap, FoldOffset};
11use gpui::{
12 color::Color,
13 fonts::{FontId, HighlightStyle},
14 Entity, ModelContext, ModelHandle,
15};
16use language::{
17 language_settings::language_settings, OffsetUtf16, Point, Subscription as BufferSubscription,
18};
19use std::{any::TypeId, fmt::Debug, num::NonZeroU32, ops::Range, sync::Arc};
20pub use suggestion_map::Suggestion;
21use suggestion_map::SuggestionMap;
22use sum_tree::{Bias, TreeMap};
23use tab_map::TabMap;
24use wrap_map::WrapMap;
25
26pub use block_map::{
27 BlockBufferRows as DisplayBufferRows, BlockChunks as DisplayChunks, BlockContext,
28 BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock, TransformBlock,
29};
30
31#[derive(Copy, Clone, Debug, PartialEq, Eq)]
32pub enum FoldStatus {
33 Folded,
34 Foldable,
35}
36
37pub trait ToDisplayPoint {
38 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint;
39}
40
41type TextHighlights = TreeMap<Option<TypeId>, Arc<(HighlightStyle, Vec<Range<Anchor>>)>>;
42
43pub struct DisplayMap {
44 buffer: ModelHandle<MultiBuffer>,
45 buffer_subscription: BufferSubscription,
46 fold_map: FoldMap,
47 suggestion_map: SuggestionMap,
48 tab_map: TabMap,
49 wrap_map: ModelHandle<WrapMap>,
50 block_map: BlockMap,
51 text_highlights: TextHighlights,
52 pub clip_at_line_ends: bool,
53}
54
55impl Entity for DisplayMap {
56 type Event = ();
57}
58
59impl DisplayMap {
60 pub fn new(
61 buffer: ModelHandle<MultiBuffer>,
62 font_id: FontId,
63 font_size: f32,
64 wrap_width: Option<f32>,
65 buffer_header_height: u8,
66 excerpt_header_height: u8,
67 cx: &mut ModelContext<Self>,
68 ) -> Self {
69 let buffer_subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
70
71 let tab_size = Self::tab_size(&buffer, cx);
72 let (fold_map, snapshot) = FoldMap::new(buffer.read(cx).snapshot(cx));
73 let (suggestion_map, snapshot) = SuggestionMap::new(snapshot);
74 let (tab_map, snapshot) = TabMap::new(snapshot, tab_size);
75 let (wrap_map, snapshot) = WrapMap::new(snapshot, font_id, font_size, wrap_width, cx);
76 let block_map = BlockMap::new(snapshot, buffer_header_height, excerpt_header_height);
77 cx.observe(&wrap_map, |_, _, cx| cx.notify()).detach();
78 DisplayMap {
79 buffer,
80 buffer_subscription,
81 fold_map,
82 suggestion_map,
83 tab_map,
84 wrap_map,
85 block_map,
86 text_highlights: Default::default(),
87 clip_at_line_ends: false,
88 }
89 }
90
91 pub fn snapshot(&self, cx: &mut ModelContext<Self>) -> DisplaySnapshot {
92 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
93 let edits = self.buffer_subscription.consume().into_inner();
94 let (fold_snapshot, edits) = self.fold_map.read(buffer_snapshot, edits);
95 let (suggestion_snapshot, edits) = self.suggestion_map.sync(fold_snapshot.clone(), edits);
96
97 let tab_size = Self::tab_size(&self.buffer, cx);
98 let (tab_snapshot, edits) = self
99 .tab_map
100 .sync(suggestion_snapshot.clone(), edits, tab_size);
101 let (wrap_snapshot, edits) = self
102 .wrap_map
103 .update(cx, |map, cx| map.sync(tab_snapshot.clone(), edits, cx));
104 let block_snapshot = self.block_map.read(wrap_snapshot.clone(), edits);
105
106 DisplaySnapshot {
107 buffer_snapshot: self.buffer.read(cx).snapshot(cx),
108 fold_snapshot,
109 suggestion_snapshot,
110 tab_snapshot,
111 wrap_snapshot,
112 block_snapshot,
113 text_highlights: self.text_highlights.clone(),
114 clip_at_line_ends: self.clip_at_line_ends,
115 }
116 }
117
118 pub fn set_state(&mut self, other: &DisplaySnapshot, cx: &mut ModelContext<Self>) {
119 self.fold(
120 other
121 .folds_in_range(0..other.buffer_snapshot.len())
122 .map(|fold| fold.to_offset(&other.buffer_snapshot)),
123 cx,
124 );
125 }
126
127 pub fn fold<T: ToOffset>(
128 &mut self,
129 ranges: impl IntoIterator<Item = Range<T>>,
130 cx: &mut ModelContext<Self>,
131 ) {
132 let snapshot = self.buffer.read(cx).snapshot(cx);
133 let edits = self.buffer_subscription.consume().into_inner();
134 let tab_size = Self::tab_size(&self.buffer, cx);
135 let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
136 let (snapshot, edits) = self.suggestion_map.sync(snapshot, edits);
137 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
138 let (snapshot, edits) = self
139 .wrap_map
140 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
141 self.block_map.read(snapshot, edits);
142 let (snapshot, edits) = fold_map.fold(ranges);
143 let (snapshot, edits) = self.suggestion_map.sync(snapshot, edits);
144 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
145 let (snapshot, edits) = self
146 .wrap_map
147 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
148 self.block_map.read(snapshot, edits);
149 }
150
151 pub fn unfold<T: ToOffset>(
152 &mut self,
153 ranges: impl IntoIterator<Item = Range<T>>,
154 inclusive: bool,
155 cx: &mut ModelContext<Self>,
156 ) {
157 let snapshot = self.buffer.read(cx).snapshot(cx);
158 let edits = self.buffer_subscription.consume().into_inner();
159 let tab_size = Self::tab_size(&self.buffer, cx);
160 let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
161 let (snapshot, edits) = self.suggestion_map.sync(snapshot, edits);
162 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
163 let (snapshot, edits) = self
164 .wrap_map
165 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
166 self.block_map.read(snapshot, edits);
167 let (snapshot, edits) = fold_map.unfold(ranges, inclusive);
168 let (snapshot, edits) = self.suggestion_map.sync(snapshot, edits);
169 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
170 let (snapshot, edits) = self
171 .wrap_map
172 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
173 self.block_map.read(snapshot, edits);
174 }
175
176 pub fn insert_blocks(
177 &mut self,
178 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
179 cx: &mut ModelContext<Self>,
180 ) -> Vec<BlockId> {
181 let snapshot = self.buffer.read(cx).snapshot(cx);
182 let edits = self.buffer_subscription.consume().into_inner();
183 let tab_size = Self::tab_size(&self.buffer, cx);
184 let (snapshot, edits) = self.fold_map.read(snapshot, edits);
185 let (snapshot, edits) = self.suggestion_map.sync(snapshot, edits);
186 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
187 let (snapshot, edits) = self
188 .wrap_map
189 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
190 let mut block_map = self.block_map.write(snapshot, edits);
191 block_map.insert(blocks)
192 }
193
194 pub fn replace_blocks(&mut self, styles: HashMap<BlockId, RenderBlock>) {
195 self.block_map.replace(styles);
196 }
197
198 pub fn remove_blocks(&mut self, ids: HashSet<BlockId>, cx: &mut ModelContext<Self>) {
199 let snapshot = self.buffer.read(cx).snapshot(cx);
200 let edits = self.buffer_subscription.consume().into_inner();
201 let tab_size = Self::tab_size(&self.buffer, cx);
202 let (snapshot, edits) = self.fold_map.read(snapshot, edits);
203 let (snapshot, edits) = self.suggestion_map.sync(snapshot, edits);
204 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
205 let (snapshot, edits) = self
206 .wrap_map
207 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
208 let mut block_map = self.block_map.write(snapshot, edits);
209 block_map.remove(ids);
210 }
211
212 pub fn highlight_text(
213 &mut self,
214 type_id: TypeId,
215 ranges: Vec<Range<Anchor>>,
216 style: HighlightStyle,
217 ) {
218 self.text_highlights
219 .insert(Some(type_id), Arc::new((style, ranges)));
220 }
221
222 pub fn text_highlights(&self, type_id: TypeId) -> Option<(HighlightStyle, &[Range<Anchor>])> {
223 let highlights = self.text_highlights.get(&Some(type_id))?;
224 Some((highlights.0, &highlights.1))
225 }
226
227 pub fn clear_text_highlights(
228 &mut self,
229 type_id: TypeId,
230 ) -> Option<Arc<(HighlightStyle, Vec<Range<Anchor>>)>> {
231 self.text_highlights.remove(&Some(type_id))
232 }
233
234 pub fn has_suggestion(&self) -> bool {
235 self.suggestion_map.has_suggestion()
236 }
237
238 pub fn replace_suggestion<T>(
239 &self,
240 new_suggestion: Option<Suggestion<T>>,
241 cx: &mut ModelContext<Self>,
242 ) -> Option<Suggestion<FoldOffset>>
243 where
244 T: ToPoint,
245 {
246 let snapshot = self.buffer.read(cx).snapshot(cx);
247 let edits = self.buffer_subscription.consume().into_inner();
248 let tab_size = Self::tab_size(&self.buffer, cx);
249 let (snapshot, edits) = self.fold_map.read(snapshot, edits);
250 let (snapshot, edits, old_suggestion) =
251 self.suggestion_map.replace(new_suggestion, snapshot, edits);
252 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
253 let (snapshot, edits) = self
254 .wrap_map
255 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
256 self.block_map.read(snapshot, edits);
257 old_suggestion
258 }
259
260 pub fn set_font(&self, font_id: FontId, font_size: f32, cx: &mut ModelContext<Self>) -> bool {
261 self.wrap_map
262 .update(cx, |map, cx| map.set_font(font_id, font_size, cx))
263 }
264
265 pub fn set_fold_ellipses_color(&mut self, color: Color) -> bool {
266 self.fold_map.set_ellipses_color(color)
267 }
268
269 pub fn set_wrap_width(&self, width: Option<f32>, cx: &mut ModelContext<Self>) -> bool {
270 self.wrap_map
271 .update(cx, |map, cx| map.set_wrap_width(width, cx))
272 }
273
274 fn tab_size(buffer: &ModelHandle<MultiBuffer>, cx: &mut ModelContext<Self>) -> NonZeroU32 {
275 let language = buffer
276 .read(cx)
277 .as_singleton()
278 .and_then(|buffer| buffer.read(cx).language());
279 language_settings(language.as_deref(), None, cx).tab_size
280 }
281
282 #[cfg(test)]
283 pub fn is_rewrapping(&self, cx: &gpui::AppContext) -> bool {
284 self.wrap_map.read(cx).is_rewrapping()
285 }
286}
287
288pub struct DisplaySnapshot {
289 pub buffer_snapshot: MultiBufferSnapshot,
290 fold_snapshot: fold_map::FoldSnapshot,
291 suggestion_snapshot: suggestion_map::SuggestionSnapshot,
292 tab_snapshot: tab_map::TabSnapshot,
293 wrap_snapshot: wrap_map::WrapSnapshot,
294 block_snapshot: block_map::BlockSnapshot,
295 text_highlights: TextHighlights,
296 clip_at_line_ends: bool,
297}
298
299impl DisplaySnapshot {
300 #[cfg(test)]
301 pub fn fold_count(&self) -> usize {
302 self.fold_snapshot.fold_count()
303 }
304
305 pub fn is_empty(&self) -> bool {
306 self.buffer_snapshot.len() == 0
307 }
308
309 pub fn buffer_rows(&self, start_row: u32) -> DisplayBufferRows {
310 self.block_snapshot.buffer_rows(start_row)
311 }
312
313 pub fn max_buffer_row(&self) -> u32 {
314 self.buffer_snapshot.max_buffer_row()
315 }
316
317 pub fn prev_line_boundary(&self, mut point: Point) -> (Point, DisplayPoint) {
318 loop {
319 let mut fold_point = self.fold_snapshot.to_fold_point(point, Bias::Left);
320 *fold_point.column_mut() = 0;
321 point = fold_point.to_buffer_point(&self.fold_snapshot);
322
323 let mut display_point = self.point_to_display_point(point, Bias::Left);
324 *display_point.column_mut() = 0;
325 let next_point = self.display_point_to_point(display_point, Bias::Left);
326 if next_point == point {
327 return (point, display_point);
328 }
329 point = next_point;
330 }
331 }
332
333 pub fn next_line_boundary(&self, mut point: Point) -> (Point, DisplayPoint) {
334 loop {
335 let mut fold_point = self.fold_snapshot.to_fold_point(point, Bias::Right);
336 *fold_point.column_mut() = self.fold_snapshot.line_len(fold_point.row());
337 point = fold_point.to_buffer_point(&self.fold_snapshot);
338
339 let mut display_point = self.point_to_display_point(point, Bias::Right);
340 *display_point.column_mut() = self.line_len(display_point.row());
341 let next_point = self.display_point_to_point(display_point, Bias::Right);
342 if next_point == point {
343 return (point, display_point);
344 }
345 point = next_point;
346 }
347 }
348
349 pub fn expand_to_line(&self, range: Range<Point>) -> Range<Point> {
350 let mut new_start = self.prev_line_boundary(range.start).0;
351 let mut new_end = self.next_line_boundary(range.end).0;
352
353 if new_start.row == range.start.row && new_end.row == range.end.row {
354 if new_end.row < self.buffer_snapshot.max_point().row {
355 new_end.row += 1;
356 new_end.column = 0;
357 } else if new_start.row > 0 {
358 new_start.row -= 1;
359 new_start.column = self.buffer_snapshot.line_len(new_start.row);
360 }
361 }
362
363 new_start..new_end
364 }
365
366 fn point_to_display_point(&self, point: Point, bias: Bias) -> DisplayPoint {
367 let fold_point = self.fold_snapshot.to_fold_point(point, bias);
368 let suggestion_point = self.suggestion_snapshot.to_suggestion_point(fold_point);
369 let tab_point = self.tab_snapshot.to_tab_point(suggestion_point);
370 let wrap_point = self.wrap_snapshot.tab_point_to_wrap_point(tab_point);
371 let block_point = self.block_snapshot.to_block_point(wrap_point);
372 DisplayPoint(block_point)
373 }
374
375 fn display_point_to_point(&self, point: DisplayPoint, bias: Bias) -> Point {
376 let block_point = point.0;
377 let wrap_point = self.block_snapshot.to_wrap_point(block_point);
378 let tab_point = self.wrap_snapshot.to_tab_point(wrap_point);
379 let suggestion_point = self.tab_snapshot.to_suggestion_point(tab_point, bias).0;
380 let fold_point = self.suggestion_snapshot.to_fold_point(suggestion_point);
381 fold_point.to_buffer_point(&self.fold_snapshot)
382 }
383
384 pub fn max_point(&self) -> DisplayPoint {
385 DisplayPoint(self.block_snapshot.max_point())
386 }
387
388 /// Returns text chunks starting at the given display row until the end of the file
389 pub fn text_chunks(&self, display_row: u32) -> impl Iterator<Item = &str> {
390 self.block_snapshot
391 .chunks(display_row..self.max_point().row() + 1, false, None, None)
392 .map(|h| h.text)
393 }
394
395 /// Returns text chunks starting at the end of the given display row in reverse until the start of the file
396 pub fn reverse_text_chunks(&self, display_row: u32) -> impl Iterator<Item = &str> {
397 (0..=display_row).into_iter().rev().flat_map(|row| {
398 self.block_snapshot
399 .chunks(row..row + 1, false, None, None)
400 .map(|h| h.text)
401 .collect::<Vec<_>>()
402 .into_iter()
403 .rev()
404 })
405 }
406
407 pub fn chunks(
408 &self,
409 display_rows: Range<u32>,
410 language_aware: bool,
411 suggestion_highlight: Option<HighlightStyle>,
412 ) -> DisplayChunks<'_> {
413 self.block_snapshot.chunks(
414 display_rows,
415 language_aware,
416 Some(&self.text_highlights),
417 suggestion_highlight,
418 )
419 }
420
421 pub fn chars_at(
422 &self,
423 mut point: DisplayPoint,
424 ) -> impl Iterator<Item = (char, DisplayPoint)> + '_ {
425 point = DisplayPoint(self.block_snapshot.clip_point(point.0, Bias::Left));
426 self.text_chunks(point.row())
427 .flat_map(str::chars)
428 .skip_while({
429 let mut column = 0;
430 move |char| {
431 let at_point = column >= point.column();
432 column += char.len_utf8() as u32;
433 !at_point
434 }
435 })
436 .map(move |ch| {
437 let result = (ch, point);
438 if ch == '\n' {
439 *point.row_mut() += 1;
440 *point.column_mut() = 0;
441 } else {
442 *point.column_mut() += ch.len_utf8() as u32;
443 }
444 result
445 })
446 }
447
448 pub fn reverse_chars_at(
449 &self,
450 mut point: DisplayPoint,
451 ) -> impl Iterator<Item = (char, DisplayPoint)> + '_ {
452 point = DisplayPoint(self.block_snapshot.clip_point(point.0, Bias::Left));
453 self.reverse_text_chunks(point.row())
454 .flat_map(|chunk| chunk.chars().rev())
455 .skip_while({
456 let mut column = self.line_len(point.row());
457 if self.max_point().row() > point.row() {
458 column += 1;
459 }
460
461 move |char| {
462 let at_point = column <= point.column();
463 column = column.saturating_sub(char.len_utf8() as u32);
464 !at_point
465 }
466 })
467 .map(move |ch| {
468 if ch == '\n' {
469 *point.row_mut() -= 1;
470 *point.column_mut() = self.line_len(point.row());
471 } else {
472 *point.column_mut() = point.column().saturating_sub(ch.len_utf8() as u32);
473 }
474 (ch, point)
475 })
476 }
477
478 /// Returns an iterator of the start positions of the occurrences of `target` in the `self` after `from`
479 /// Stops if `condition` returns false for any of the character position pairs observed.
480 pub fn find_while<'a>(
481 &'a self,
482 from: DisplayPoint,
483 target: &str,
484 condition: impl FnMut(char, DisplayPoint) -> bool + 'a,
485 ) -> impl Iterator<Item = DisplayPoint> + 'a {
486 Self::find_internal(self.chars_at(from), target.chars().collect(), condition)
487 }
488
489 /// Returns an iterator of the end positions of the occurrences of `target` in the `self` before `from`
490 /// Stops if `condition` returns false for any of the character position pairs observed.
491 pub fn reverse_find_while<'a>(
492 &'a self,
493 from: DisplayPoint,
494 target: &str,
495 condition: impl FnMut(char, DisplayPoint) -> bool + 'a,
496 ) -> impl Iterator<Item = DisplayPoint> + 'a {
497 Self::find_internal(
498 self.reverse_chars_at(from),
499 target.chars().rev().collect(),
500 condition,
501 )
502 }
503
504 fn find_internal<'a>(
505 iterator: impl Iterator<Item = (char, DisplayPoint)> + 'a,
506 target: Vec<char>,
507 mut condition: impl FnMut(char, DisplayPoint) -> bool + 'a,
508 ) -> impl Iterator<Item = DisplayPoint> + 'a {
509 // List of partial matches with the index of the last seen character in target and the starting point of the match
510 let mut partial_matches: Vec<(usize, DisplayPoint)> = Vec::new();
511 iterator
512 .take_while(move |(ch, point)| condition(*ch, *point))
513 .filter_map(move |(ch, point)| {
514 if Some(&ch) == target.get(0) {
515 partial_matches.push((0, point));
516 }
517
518 let mut found = None;
519 // Keep partial matches that have the correct next character
520 partial_matches.retain_mut(|(match_position, match_start)| {
521 if target.get(*match_position) == Some(&ch) {
522 *match_position += 1;
523 if *match_position == target.len() {
524 found = Some(match_start.clone());
525 // This match is completed. No need to keep tracking it
526 false
527 } else {
528 true
529 }
530 } else {
531 false
532 }
533 });
534
535 found
536 })
537 }
538
539 pub fn column_to_chars(&self, display_row: u32, target: u32) -> u32 {
540 let mut count = 0;
541 let mut column = 0;
542 for (c, _) in self.chars_at(DisplayPoint::new(display_row, 0)) {
543 if column >= target {
544 break;
545 }
546 count += 1;
547 column += c.len_utf8() as u32;
548 }
549 count
550 }
551
552 pub fn column_from_chars(&self, display_row: u32, char_count: u32) -> u32 {
553 let mut column = 0;
554
555 for (count, (c, _)) in self.chars_at(DisplayPoint::new(display_row, 0)).enumerate() {
556 if c == '\n' || count >= char_count as usize {
557 break;
558 }
559 column += c.len_utf8() as u32;
560 }
561
562 column
563 }
564
565 pub fn clip_point(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint {
566 let mut clipped = self.block_snapshot.clip_point(point.0, bias);
567 if self.clip_at_line_ends {
568 clipped = self.clip_at_line_end(DisplayPoint(clipped)).0
569 }
570 DisplayPoint(clipped)
571 }
572
573 pub fn clip_at_line_end(&self, point: DisplayPoint) -> DisplayPoint {
574 let mut point = point.0;
575 if point.column == self.line_len(point.row) {
576 point.column = point.column.saturating_sub(1);
577 point = self.block_snapshot.clip_point(point, Bias::Left);
578 }
579 DisplayPoint(point)
580 }
581
582 pub fn folds_in_range<T>(&self, range: Range<T>) -> impl Iterator<Item = &Range<Anchor>>
583 where
584 T: ToOffset,
585 {
586 self.fold_snapshot.folds_in_range(range)
587 }
588
589 pub fn blocks_in_range(
590 &self,
591 rows: Range<u32>,
592 ) -> impl Iterator<Item = (u32, &TransformBlock)> {
593 self.block_snapshot.blocks_in_range(rows)
594 }
595
596 pub fn intersects_fold<T: ToOffset>(&self, offset: T) -> bool {
597 self.fold_snapshot.intersects_fold(offset)
598 }
599
600 pub fn is_line_folded(&self, buffer_row: u32) -> bool {
601 self.fold_snapshot.is_line_folded(buffer_row)
602 }
603
604 pub fn is_block_line(&self, display_row: u32) -> bool {
605 self.block_snapshot.is_block_line(display_row)
606 }
607
608 pub fn soft_wrap_indent(&self, display_row: u32) -> Option<u32> {
609 let wrap_row = self
610 .block_snapshot
611 .to_wrap_point(BlockPoint::new(display_row, 0))
612 .row();
613 self.wrap_snapshot.soft_wrap_indent(wrap_row)
614 }
615
616 pub fn text(&self) -> String {
617 self.text_chunks(0).collect()
618 }
619
620 pub fn line(&self, display_row: u32) -> String {
621 let mut result = String::new();
622 for chunk in self.text_chunks(display_row) {
623 if let Some(ix) = chunk.find('\n') {
624 result.push_str(&chunk[0..ix]);
625 break;
626 } else {
627 result.push_str(chunk);
628 }
629 }
630 result
631 }
632
633 pub fn line_indent(&self, display_row: u32) -> (u32, bool) {
634 let mut indent = 0;
635 let mut is_blank = true;
636 for (c, _) in self.chars_at(DisplayPoint::new(display_row, 0)) {
637 if c == ' ' {
638 indent += 1;
639 } else {
640 is_blank = c == '\n';
641 break;
642 }
643 }
644 (indent, is_blank)
645 }
646
647 pub fn line_indent_for_buffer_row(&self, buffer_row: u32) -> (u32, bool) {
648 let (buffer, range) = self
649 .buffer_snapshot
650 .buffer_line_for_row(buffer_row)
651 .unwrap();
652
653 let mut indent_size = 0;
654 let mut is_blank = false;
655 for c in buffer.chars_at(Point::new(range.start.row, 0)) {
656 if c == ' ' || c == '\t' {
657 indent_size += 1;
658 } else {
659 if c == '\n' {
660 is_blank = true;
661 }
662 break;
663 }
664 }
665
666 (indent_size, is_blank)
667 }
668
669 pub fn line_len(&self, row: u32) -> u32 {
670 self.block_snapshot.line_len(row)
671 }
672
673 pub fn longest_row(&self) -> u32 {
674 self.block_snapshot.longest_row()
675 }
676
677 pub fn fold_for_line(self: &Self, buffer_row: u32) -> Option<FoldStatus> {
678 if self.is_line_folded(buffer_row) {
679 Some(FoldStatus::Folded)
680 } else if self.is_foldable(buffer_row) {
681 Some(FoldStatus::Foldable)
682 } else {
683 None
684 }
685 }
686
687 pub fn is_foldable(self: &Self, buffer_row: u32) -> bool {
688 let max_row = self.buffer_snapshot.max_buffer_row();
689 if buffer_row >= max_row {
690 return false;
691 }
692
693 let (indent_size, is_blank) = self.line_indent_for_buffer_row(buffer_row);
694 if is_blank {
695 return false;
696 }
697
698 for next_row in (buffer_row + 1)..=max_row {
699 let (next_indent_size, next_line_is_blank) = self.line_indent_for_buffer_row(next_row);
700 if next_indent_size > indent_size {
701 return true;
702 } else if !next_line_is_blank {
703 break;
704 }
705 }
706
707 false
708 }
709
710 pub fn foldable_range(self: &Self, buffer_row: u32) -> Option<Range<Point>> {
711 let start = Point::new(buffer_row, self.buffer_snapshot.line_len(buffer_row));
712 if self.is_foldable(start.row) && !self.is_line_folded(start.row) {
713 let (start_indent, _) = self.line_indent_for_buffer_row(buffer_row);
714 let max_point = self.buffer_snapshot.max_point();
715 let mut end = None;
716
717 for row in (buffer_row + 1)..=max_point.row {
718 let (indent, is_blank) = self.line_indent_for_buffer_row(row);
719 if !is_blank && indent <= start_indent {
720 let prev_row = row - 1;
721 end = Some(Point::new(
722 prev_row,
723 self.buffer_snapshot.line_len(prev_row),
724 ));
725 break;
726 }
727 }
728 let end = end.unwrap_or(max_point);
729 Some(start..end)
730 } else {
731 None
732 }
733 }
734
735 #[cfg(any(test, feature = "test-support"))]
736 pub fn highlight_ranges<Tag: ?Sized + 'static>(
737 &self,
738 ) -> Option<Arc<(HighlightStyle, Vec<Range<Anchor>>)>> {
739 let type_id = TypeId::of::<Tag>();
740 self.text_highlights.get(&Some(type_id)).cloned()
741 }
742}
743
744#[derive(Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq)]
745pub struct DisplayPoint(BlockPoint);
746
747impl Debug for DisplayPoint {
748 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
749 f.write_fmt(format_args!(
750 "DisplayPoint({}, {})",
751 self.row(),
752 self.column()
753 ))
754 }
755}
756
757impl DisplayPoint {
758 pub fn new(row: u32, column: u32) -> Self {
759 Self(BlockPoint(Point::new(row, column)))
760 }
761
762 pub fn zero() -> Self {
763 Self::new(0, 0)
764 }
765
766 pub fn is_zero(&self) -> bool {
767 self.0.is_zero()
768 }
769
770 pub fn row(self) -> u32 {
771 self.0.row
772 }
773
774 pub fn column(self) -> u32 {
775 self.0.column
776 }
777
778 pub fn row_mut(&mut self) -> &mut u32 {
779 &mut self.0.row
780 }
781
782 pub fn column_mut(&mut self) -> &mut u32 {
783 &mut self.0.column
784 }
785
786 pub fn to_point(self, map: &DisplaySnapshot) -> Point {
787 map.display_point_to_point(self, Bias::Left)
788 }
789
790 pub fn to_offset(self, map: &DisplaySnapshot, bias: Bias) -> usize {
791 let wrap_point = map.block_snapshot.to_wrap_point(self.0);
792 let tab_point = map.wrap_snapshot.to_tab_point(wrap_point);
793 let suggestion_point = map.tab_snapshot.to_suggestion_point(tab_point, bias).0;
794 let fold_point = map.suggestion_snapshot.to_fold_point(suggestion_point);
795 fold_point.to_buffer_offset(&map.fold_snapshot)
796 }
797}
798
799impl ToDisplayPoint for usize {
800 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
801 map.point_to_display_point(self.to_point(&map.buffer_snapshot), Bias::Left)
802 }
803}
804
805impl ToDisplayPoint for OffsetUtf16 {
806 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
807 self.to_offset(&map.buffer_snapshot).to_display_point(map)
808 }
809}
810
811impl ToDisplayPoint for Point {
812 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
813 map.point_to_display_point(*self, Bias::Left)
814 }
815}
816
817impl ToDisplayPoint for Anchor {
818 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
819 self.to_point(&map.buffer_snapshot).to_display_point(map)
820 }
821}
822
823pub fn next_rows(display_row: u32, display_map: &DisplaySnapshot) -> impl Iterator<Item = u32> {
824 let max_row = display_map.max_point().row();
825 let start_row = display_row + 1;
826 let mut current = None;
827 std::iter::from_fn(move || {
828 if current == None {
829 current = Some(start_row);
830 } else {
831 current = Some(current.unwrap() + 1)
832 }
833 if current.unwrap() > max_row {
834 None
835 } else {
836 current
837 }
838 })
839}
840
841#[cfg(test)]
842pub mod tests {
843 use super::*;
844 use crate::{movement, test::marked_display_snapshot};
845 use gpui::{color::Color, elements::*, test::observe, AppContext};
846 use language::{
847 language_settings::{AllLanguageSettings, AllLanguageSettingsContent},
848 Buffer, Language, LanguageConfig, SelectionGoal,
849 };
850 use rand::{prelude::*, Rng};
851 use settings::SettingsStore;
852 use smol::stream::StreamExt;
853 use std::{env, sync::Arc};
854 use theme::SyntaxTheme;
855 use util::test::{marked_text_offsets, marked_text_ranges, sample_text};
856 use Bias::*;
857
858 #[gpui::test(iterations = 100)]
859 async fn test_random_display_map(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
860 cx.foreground().set_block_on_ticks(0..=50);
861 cx.foreground().forbid_parking();
862 let operations = env::var("OPERATIONS")
863 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
864 .unwrap_or(10);
865
866 let font_cache = cx.font_cache().clone();
867 let mut tab_size = rng.gen_range(1..=4);
868 let buffer_start_excerpt_header_height = rng.gen_range(1..=5);
869 let excerpt_header_height = rng.gen_range(1..=5);
870 let family_id = font_cache
871 .load_family(&["Helvetica"], &Default::default())
872 .unwrap();
873 let font_id = font_cache
874 .select_font(family_id, &Default::default())
875 .unwrap();
876 let font_size = 14.0;
877 let max_wrap_width = 300.0;
878 let mut wrap_width = if rng.gen_bool(0.1) {
879 None
880 } else {
881 Some(rng.gen_range(0.0..=max_wrap_width))
882 };
883
884 log::info!("tab size: {}", tab_size);
885 log::info!("wrap width: {:?}", wrap_width);
886
887 cx.update(|cx| {
888 init_test(cx, |s| s.defaults.tab_size = NonZeroU32::new(tab_size));
889 });
890
891 let buffer = cx.update(|cx| {
892 if rng.gen() {
893 let len = rng.gen_range(0..10);
894 let text = util::RandomCharIter::new(&mut rng)
895 .take(len)
896 .collect::<String>();
897 MultiBuffer::build_simple(&text, cx)
898 } else {
899 MultiBuffer::build_random(&mut rng, cx)
900 }
901 });
902
903 let map = cx.add_model(|cx| {
904 DisplayMap::new(
905 buffer.clone(),
906 font_id,
907 font_size,
908 wrap_width,
909 buffer_start_excerpt_header_height,
910 excerpt_header_height,
911 cx,
912 )
913 });
914 let mut notifications = observe(&map, cx);
915 let mut fold_count = 0;
916 let mut blocks = Vec::new();
917
918 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
919 log::info!("buffer text: {:?}", snapshot.buffer_snapshot.text());
920 log::info!("fold text: {:?}", snapshot.fold_snapshot.text());
921 log::info!("tab text: {:?}", snapshot.tab_snapshot.text());
922 log::info!("wrap text: {:?}", snapshot.wrap_snapshot.text());
923 log::info!("block text: {:?}", snapshot.block_snapshot.text());
924 log::info!("display text: {:?}", snapshot.text());
925
926 for _i in 0..operations {
927 match rng.gen_range(0..100) {
928 0..=19 => {
929 wrap_width = if rng.gen_bool(0.2) {
930 None
931 } else {
932 Some(rng.gen_range(0.0..=max_wrap_width))
933 };
934 log::info!("setting wrap width to {:?}", wrap_width);
935 map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
936 }
937 20..=29 => {
938 let mut tab_sizes = vec![1, 2, 3, 4];
939 tab_sizes.remove((tab_size - 1) as usize);
940 tab_size = *tab_sizes.choose(&mut rng).unwrap();
941 log::info!("setting tab size to {:?}", tab_size);
942 cx.update(|cx| {
943 cx.update_global::<SettingsStore, _, _>(|store, cx| {
944 store.update_user_settings::<AllLanguageSettings>(cx, |s| {
945 s.defaults.tab_size = NonZeroU32::new(tab_size);
946 });
947 });
948 });
949 }
950 30..=44 => {
951 map.update(cx, |map, cx| {
952 if rng.gen() || blocks.is_empty() {
953 let buffer = map.snapshot(cx).buffer_snapshot;
954 let block_properties = (0..rng.gen_range(1..=1))
955 .map(|_| {
956 let position =
957 buffer.anchor_after(buffer.clip_offset(
958 rng.gen_range(0..=buffer.len()),
959 Bias::Left,
960 ));
961
962 let disposition = if rng.gen() {
963 BlockDisposition::Above
964 } else {
965 BlockDisposition::Below
966 };
967 let height = rng.gen_range(1..5);
968 log::info!(
969 "inserting block {:?} {:?} with height {}",
970 disposition,
971 position.to_point(&buffer),
972 height
973 );
974 BlockProperties {
975 style: BlockStyle::Fixed,
976 position,
977 height,
978 disposition,
979 render: Arc::new(|_| Empty::new().into_any()),
980 }
981 })
982 .collect::<Vec<_>>();
983 blocks.extend(map.insert_blocks(block_properties, cx));
984 } else {
985 blocks.shuffle(&mut rng);
986 let remove_count = rng.gen_range(1..=4.min(blocks.len()));
987 let block_ids_to_remove = (0..remove_count)
988 .map(|_| blocks.remove(rng.gen_range(0..blocks.len())))
989 .collect();
990 log::info!("removing block ids {:?}", block_ids_to_remove);
991 map.remove_blocks(block_ids_to_remove, cx);
992 }
993 });
994 }
995 45..=79 => {
996 let mut ranges = Vec::new();
997 for _ in 0..rng.gen_range(1..=3) {
998 buffer.read_with(cx, |buffer, cx| {
999 let buffer = buffer.read(cx);
1000 let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right);
1001 let start = buffer.clip_offset(rng.gen_range(0..=end), Left);
1002 ranges.push(start..end);
1003 });
1004 }
1005
1006 if rng.gen() && fold_count > 0 {
1007 log::info!("unfolding ranges: {:?}", ranges);
1008 map.update(cx, |map, cx| {
1009 map.unfold(ranges, true, cx);
1010 });
1011 } else {
1012 log::info!("folding ranges: {:?}", ranges);
1013 map.update(cx, |map, cx| {
1014 map.fold(ranges, cx);
1015 });
1016 }
1017 }
1018 _ => {
1019 buffer.update(cx, |buffer, cx| buffer.randomly_mutate(&mut rng, 5, cx));
1020 }
1021 }
1022
1023 if map.read_with(cx, |map, cx| map.is_rewrapping(cx)) {
1024 notifications.next().await.unwrap();
1025 }
1026
1027 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1028 fold_count = snapshot.fold_count();
1029 log::info!("buffer text: {:?}", snapshot.buffer_snapshot.text());
1030 log::info!("fold text: {:?}", snapshot.fold_snapshot.text());
1031 log::info!("tab text: {:?}", snapshot.tab_snapshot.text());
1032 log::info!("wrap text: {:?}", snapshot.wrap_snapshot.text());
1033 log::info!("block text: {:?}", snapshot.block_snapshot.text());
1034 log::info!("display text: {:?}", snapshot.text());
1035
1036 // Line boundaries
1037 let buffer = &snapshot.buffer_snapshot;
1038 for _ in 0..5 {
1039 let row = rng.gen_range(0..=buffer.max_point().row);
1040 let column = rng.gen_range(0..=buffer.line_len(row));
1041 let point = buffer.clip_point(Point::new(row, column), Left);
1042
1043 let (prev_buffer_bound, prev_display_bound) = snapshot.prev_line_boundary(point);
1044 let (next_buffer_bound, next_display_bound) = snapshot.next_line_boundary(point);
1045
1046 assert!(prev_buffer_bound <= point);
1047 assert!(next_buffer_bound >= point);
1048 assert_eq!(prev_buffer_bound.column, 0);
1049 assert_eq!(prev_display_bound.column(), 0);
1050 if next_buffer_bound < buffer.max_point() {
1051 assert_eq!(buffer.chars_at(next_buffer_bound).next(), Some('\n'));
1052 }
1053
1054 assert_eq!(
1055 prev_display_bound,
1056 prev_buffer_bound.to_display_point(&snapshot),
1057 "row boundary before {:?}. reported buffer row boundary: {:?}",
1058 point,
1059 prev_buffer_bound
1060 );
1061 assert_eq!(
1062 next_display_bound,
1063 next_buffer_bound.to_display_point(&snapshot),
1064 "display row boundary after {:?}. reported buffer row boundary: {:?}",
1065 point,
1066 next_buffer_bound
1067 );
1068 assert_eq!(
1069 prev_buffer_bound,
1070 prev_display_bound.to_point(&snapshot),
1071 "row boundary before {:?}. reported display row boundary: {:?}",
1072 point,
1073 prev_display_bound
1074 );
1075 assert_eq!(
1076 next_buffer_bound,
1077 next_display_bound.to_point(&snapshot),
1078 "row boundary after {:?}. reported display row boundary: {:?}",
1079 point,
1080 next_display_bound
1081 );
1082 }
1083
1084 // Movement
1085 let min_point = snapshot.clip_point(DisplayPoint::new(0, 0), Left);
1086 let max_point = snapshot.clip_point(snapshot.max_point(), Right);
1087 for _ in 0..5 {
1088 let row = rng.gen_range(0..=snapshot.max_point().row());
1089 let column = rng.gen_range(0..=snapshot.line_len(row));
1090 let point = snapshot.clip_point(DisplayPoint::new(row, column), Left);
1091
1092 log::info!("Moving from point {:?}", point);
1093
1094 let moved_right = movement::right(&snapshot, point);
1095 log::info!("Right {:?}", moved_right);
1096 if point < max_point {
1097 assert!(moved_right > point);
1098 if point.column() == snapshot.line_len(point.row())
1099 || snapshot.soft_wrap_indent(point.row()).is_some()
1100 && point.column() == snapshot.line_len(point.row()) - 1
1101 {
1102 assert!(moved_right.row() > point.row());
1103 }
1104 } else {
1105 assert_eq!(moved_right, point);
1106 }
1107
1108 let moved_left = movement::left(&snapshot, point);
1109 log::info!("Left {:?}", moved_left);
1110 if point > min_point {
1111 assert!(moved_left < point);
1112 if point.column() == 0 {
1113 assert!(moved_left.row() < point.row());
1114 }
1115 } else {
1116 assert_eq!(moved_left, point);
1117 }
1118 }
1119 }
1120 }
1121
1122 #[gpui::test(retries = 5)]
1123 fn test_soft_wraps(cx: &mut AppContext) {
1124 cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
1125 init_test(cx, |_| {});
1126
1127 let font_cache = cx.font_cache();
1128
1129 let family_id = font_cache
1130 .load_family(&["Helvetica"], &Default::default())
1131 .unwrap();
1132 let font_id = font_cache
1133 .select_font(family_id, &Default::default())
1134 .unwrap();
1135 let font_size = 12.0;
1136 let wrap_width = Some(64.);
1137
1138 let text = "one two three four five\nsix seven eight";
1139 let buffer = MultiBuffer::build_simple(text, cx);
1140 let map = cx.add_model(|cx| {
1141 DisplayMap::new(buffer.clone(), font_id, font_size, wrap_width, 1, 1, cx)
1142 });
1143
1144 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1145 assert_eq!(
1146 snapshot.text_chunks(0).collect::<String>(),
1147 "one two \nthree four \nfive\nsix seven \neight"
1148 );
1149 assert_eq!(
1150 snapshot.clip_point(DisplayPoint::new(0, 8), Bias::Left),
1151 DisplayPoint::new(0, 7)
1152 );
1153 assert_eq!(
1154 snapshot.clip_point(DisplayPoint::new(0, 8), Bias::Right),
1155 DisplayPoint::new(1, 0)
1156 );
1157 assert_eq!(
1158 movement::right(&snapshot, DisplayPoint::new(0, 7)),
1159 DisplayPoint::new(1, 0)
1160 );
1161 assert_eq!(
1162 movement::left(&snapshot, DisplayPoint::new(1, 0)),
1163 DisplayPoint::new(0, 7)
1164 );
1165 assert_eq!(
1166 movement::up(
1167 &snapshot,
1168 DisplayPoint::new(1, 10),
1169 SelectionGoal::None,
1170 false
1171 ),
1172 (DisplayPoint::new(0, 7), SelectionGoal::Column(10))
1173 );
1174 assert_eq!(
1175 movement::down(
1176 &snapshot,
1177 DisplayPoint::new(0, 7),
1178 SelectionGoal::Column(10),
1179 false
1180 ),
1181 (DisplayPoint::new(1, 10), SelectionGoal::Column(10))
1182 );
1183 assert_eq!(
1184 movement::down(
1185 &snapshot,
1186 DisplayPoint::new(1, 10),
1187 SelectionGoal::Column(10),
1188 false
1189 ),
1190 (DisplayPoint::new(2, 4), SelectionGoal::Column(10))
1191 );
1192
1193 let ix = snapshot.buffer_snapshot.text().find("seven").unwrap();
1194 buffer.update(cx, |buffer, cx| {
1195 buffer.edit([(ix..ix, "and ")], None, cx);
1196 });
1197
1198 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1199 assert_eq!(
1200 snapshot.text_chunks(1).collect::<String>(),
1201 "three four \nfive\nsix and \nseven eight"
1202 );
1203
1204 // Re-wrap on font size changes
1205 map.update(cx, |map, cx| map.set_font(font_id, font_size + 3., cx));
1206
1207 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1208 assert_eq!(
1209 snapshot.text_chunks(1).collect::<String>(),
1210 "three \nfour five\nsix and \nseven \neight"
1211 )
1212 }
1213
1214 #[gpui::test]
1215 fn test_text_chunks(cx: &mut gpui::AppContext) {
1216 init_test(cx, |_| {});
1217
1218 let text = sample_text(6, 6, 'a');
1219 let buffer = MultiBuffer::build_simple(&text, cx);
1220 let family_id = cx
1221 .font_cache()
1222 .load_family(&["Helvetica"], &Default::default())
1223 .unwrap();
1224 let font_id = cx
1225 .font_cache()
1226 .select_font(family_id, &Default::default())
1227 .unwrap();
1228 let font_size = 14.0;
1229 let map =
1230 cx.add_model(|cx| DisplayMap::new(buffer.clone(), font_id, font_size, None, 1, 1, cx));
1231
1232 buffer.update(cx, |buffer, cx| {
1233 buffer.edit(
1234 vec![
1235 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1236 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1237 (Point::new(2, 1)..Point::new(2, 1), "\t"),
1238 ],
1239 None,
1240 cx,
1241 )
1242 });
1243
1244 assert_eq!(
1245 map.update(cx, |map, cx| map.snapshot(cx))
1246 .text_chunks(1)
1247 .collect::<String>()
1248 .lines()
1249 .next(),
1250 Some(" b bbbbb")
1251 );
1252 assert_eq!(
1253 map.update(cx, |map, cx| map.snapshot(cx))
1254 .text_chunks(2)
1255 .collect::<String>()
1256 .lines()
1257 .next(),
1258 Some("c ccccc")
1259 );
1260 }
1261
1262 #[gpui::test]
1263 async fn test_chunks(cx: &mut gpui::TestAppContext) {
1264 use unindent::Unindent as _;
1265
1266 let text = r#"
1267 fn outer() {}
1268
1269 mod module {
1270 fn inner() {}
1271 }"#
1272 .unindent();
1273
1274 let theme = SyntaxTheme::new(vec![
1275 ("mod.body".to_string(), Color::red().into()),
1276 ("fn.name".to_string(), Color::blue().into()),
1277 ]);
1278 let language = Arc::new(
1279 Language::new(
1280 LanguageConfig {
1281 name: "Test".into(),
1282 path_suffixes: vec![".test".to_string()],
1283 ..Default::default()
1284 },
1285 Some(tree_sitter_rust::language()),
1286 )
1287 .with_highlights_query(
1288 r#"
1289 (mod_item name: (identifier) body: _ @mod.body)
1290 (function_item name: (identifier) @fn.name)
1291 "#,
1292 )
1293 .unwrap(),
1294 );
1295 language.set_theme(&theme);
1296
1297 cx.update(|cx| init_test(cx, |s| s.defaults.tab_size = Some(2.try_into().unwrap())));
1298
1299 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
1300 buffer.condition(cx, |buf, _| !buf.is_parsing()).await;
1301 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
1302
1303 let font_cache = cx.font_cache();
1304 let family_id = font_cache
1305 .load_family(&["Helvetica"], &Default::default())
1306 .unwrap();
1307 let font_id = font_cache
1308 .select_font(family_id, &Default::default())
1309 .unwrap();
1310 let font_size = 14.0;
1311
1312 let map = cx.add_model(|cx| DisplayMap::new(buffer, font_id, font_size, None, 1, 1, cx));
1313 assert_eq!(
1314 cx.update(|cx| syntax_chunks(0..5, &map, &theme, cx)),
1315 vec![
1316 ("fn ".to_string(), None),
1317 ("outer".to_string(), Some(Color::blue())),
1318 ("() {}\n\nmod module ".to_string(), None),
1319 ("{\n fn ".to_string(), Some(Color::red())),
1320 ("inner".to_string(), Some(Color::blue())),
1321 ("() {}\n}".to_string(), Some(Color::red())),
1322 ]
1323 );
1324 assert_eq!(
1325 cx.update(|cx| syntax_chunks(3..5, &map, &theme, cx)),
1326 vec![
1327 (" fn ".to_string(), Some(Color::red())),
1328 ("inner".to_string(), Some(Color::blue())),
1329 ("() {}\n}".to_string(), Some(Color::red())),
1330 ]
1331 );
1332
1333 map.update(cx, |map, cx| {
1334 map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], cx)
1335 });
1336 assert_eq!(
1337 cx.update(|cx| syntax_chunks(0..2, &map, &theme, cx)),
1338 vec![
1339 ("fn ".to_string(), None),
1340 ("out".to_string(), Some(Color::blue())),
1341 ("β―".to_string(), None),
1342 (" fn ".to_string(), Some(Color::red())),
1343 ("inner".to_string(), Some(Color::blue())),
1344 ("() {}\n}".to_string(), Some(Color::red())),
1345 ]
1346 );
1347 }
1348
1349 #[gpui::test]
1350 async fn test_chunks_with_soft_wrapping(cx: &mut gpui::TestAppContext) {
1351 use unindent::Unindent as _;
1352
1353 cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
1354
1355 let text = r#"
1356 fn outer() {}
1357
1358 mod module {
1359 fn inner() {}
1360 }"#
1361 .unindent();
1362
1363 let theme = SyntaxTheme::new(vec![
1364 ("mod.body".to_string(), Color::red().into()),
1365 ("fn.name".to_string(), Color::blue().into()),
1366 ]);
1367 let language = Arc::new(
1368 Language::new(
1369 LanguageConfig {
1370 name: "Test".into(),
1371 path_suffixes: vec![".test".to_string()],
1372 ..Default::default()
1373 },
1374 Some(tree_sitter_rust::language()),
1375 )
1376 .with_highlights_query(
1377 r#"
1378 (mod_item name: (identifier) body: _ @mod.body)
1379 (function_item name: (identifier) @fn.name)
1380 "#,
1381 )
1382 .unwrap(),
1383 );
1384 language.set_theme(&theme);
1385
1386 cx.update(|cx| init_test(cx, |_| {}));
1387
1388 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
1389 buffer.condition(cx, |buf, _| !buf.is_parsing()).await;
1390 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
1391
1392 let font_cache = cx.font_cache();
1393
1394 let family_id = font_cache
1395 .load_family(&["Courier"], &Default::default())
1396 .unwrap();
1397 let font_id = font_cache
1398 .select_font(family_id, &Default::default())
1399 .unwrap();
1400 let font_size = 16.0;
1401
1402 let map =
1403 cx.add_model(|cx| DisplayMap::new(buffer, font_id, font_size, Some(40.0), 1, 1, cx));
1404 assert_eq!(
1405 cx.update(|cx| syntax_chunks(0..5, &map, &theme, cx)),
1406 [
1407 ("fn \n".to_string(), None),
1408 ("oute\nr".to_string(), Some(Color::blue())),
1409 ("() \n{}\n\n".to_string(), None),
1410 ]
1411 );
1412 assert_eq!(
1413 cx.update(|cx| syntax_chunks(3..5, &map, &theme, cx)),
1414 [("{}\n\n".to_string(), None)]
1415 );
1416
1417 map.update(cx, |map, cx| {
1418 map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], cx)
1419 });
1420 assert_eq!(
1421 cx.update(|cx| syntax_chunks(1..4, &map, &theme, cx)),
1422 [
1423 ("out".to_string(), Some(Color::blue())),
1424 ("β―\n".to_string(), None),
1425 (" \nfn ".to_string(), Some(Color::red())),
1426 ("i\n".to_string(), Some(Color::blue()))
1427 ]
1428 );
1429 }
1430
1431 #[gpui::test]
1432 async fn test_chunks_with_text_highlights(cx: &mut gpui::TestAppContext) {
1433 cx.update(|cx| init_test(cx, |_| {}));
1434
1435 let theme = SyntaxTheme::new(vec![
1436 ("operator".to_string(), Color::red().into()),
1437 ("string".to_string(), Color::green().into()),
1438 ]);
1439 let language = Arc::new(
1440 Language::new(
1441 LanguageConfig {
1442 name: "Test".into(),
1443 path_suffixes: vec![".test".to_string()],
1444 ..Default::default()
1445 },
1446 Some(tree_sitter_rust::language()),
1447 )
1448 .with_highlights_query(
1449 r#"
1450 ":" @operator
1451 (string_literal) @string
1452 "#,
1453 )
1454 .unwrap(),
1455 );
1456 language.set_theme(&theme);
1457
1458 let (text, highlighted_ranges) = marked_text_ranges(r#"constΛ Β«aΒ»: B = "c Β«dΒ»""#, false);
1459
1460 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
1461 buffer.condition(cx, |buf, _| !buf.is_parsing()).await;
1462
1463 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
1464 let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
1465
1466 let font_cache = cx.font_cache();
1467 let family_id = font_cache
1468 .load_family(&["Courier"], &Default::default())
1469 .unwrap();
1470 let font_id = font_cache
1471 .select_font(family_id, &Default::default())
1472 .unwrap();
1473 let font_size = 16.0;
1474 let map = cx.add_model(|cx| DisplayMap::new(buffer, font_id, font_size, None, 1, 1, cx));
1475
1476 enum MyType {}
1477
1478 let style = HighlightStyle {
1479 color: Some(Color::blue()),
1480 ..Default::default()
1481 };
1482
1483 map.update(cx, |map, _cx| {
1484 map.highlight_text(
1485 TypeId::of::<MyType>(),
1486 highlighted_ranges
1487 .into_iter()
1488 .map(|range| {
1489 buffer_snapshot.anchor_before(range.start)
1490 ..buffer_snapshot.anchor_before(range.end)
1491 })
1492 .collect(),
1493 style,
1494 );
1495 });
1496
1497 assert_eq!(
1498 cx.update(|cx| chunks(0..10, &map, &theme, cx)),
1499 [
1500 ("const ".to_string(), None, None),
1501 ("a".to_string(), None, Some(Color::blue())),
1502 (":".to_string(), Some(Color::red()), None),
1503 (" B = ".to_string(), None, None),
1504 ("\"c ".to_string(), Some(Color::green()), None),
1505 ("d".to_string(), Some(Color::green()), Some(Color::blue())),
1506 ("\"".to_string(), Some(Color::green()), None),
1507 ]
1508 );
1509 }
1510
1511 #[gpui::test]
1512 fn test_clip_point(cx: &mut gpui::AppContext) {
1513 init_test(cx, |_| {});
1514
1515 fn assert(text: &str, shift_right: bool, bias: Bias, cx: &mut gpui::AppContext) {
1516 let (unmarked_snapshot, mut markers) = marked_display_snapshot(text, cx);
1517
1518 match bias {
1519 Bias::Left => {
1520 if shift_right {
1521 *markers[1].column_mut() += 1;
1522 }
1523
1524 assert_eq!(unmarked_snapshot.clip_point(markers[1], bias), markers[0])
1525 }
1526 Bias::Right => {
1527 if shift_right {
1528 *markers[0].column_mut() += 1;
1529 }
1530
1531 assert_eq!(unmarked_snapshot.clip_point(markers[0], bias), markers[1])
1532 }
1533 };
1534 }
1535
1536 use Bias::{Left, Right};
1537 assert("ΛΛΞ±", false, Left, cx);
1538 assert("ΛΛΞ±", true, Left, cx);
1539 assert("ΛΛΞ±", false, Right, cx);
1540 assert("ΛΞ±Λ", true, Right, cx);
1541 assert("ΛΛβ", false, Left, cx);
1542 assert("ΛΛβ", true, Left, cx);
1543 assert("ΛΛβ", false, Right, cx);
1544 assert("ΛβΛ", true, Right, cx);
1545 assert("ΛΛπ", false, Left, cx);
1546 assert("ΛΛπ", true, Left, cx);
1547 assert("ΛΛπ", false, Right, cx);
1548 assert("ΛπΛ", true, Right, cx);
1549 assert("ΛΛ\t", false, Left, cx);
1550 assert("ΛΛ\t", true, Left, cx);
1551 assert("ΛΛ\t", false, Right, cx);
1552 assert("Λ\tΛ", true, Right, cx);
1553 assert(" ΛΛ\t", false, Left, cx);
1554 assert(" ΛΛ\t", true, Left, cx);
1555 assert(" ΛΛ\t", false, Right, cx);
1556 assert(" Λ\tΛ", true, Right, cx);
1557 assert(" ΛΛ\t", false, Left, cx);
1558 assert(" ΛΛ\t", false, Right, cx);
1559 }
1560
1561 #[gpui::test]
1562 fn test_clip_at_line_ends(cx: &mut gpui::AppContext) {
1563 init_test(cx, |_| {});
1564
1565 fn assert(text: &str, cx: &mut gpui::AppContext) {
1566 let (mut unmarked_snapshot, markers) = marked_display_snapshot(text, cx);
1567 unmarked_snapshot.clip_at_line_ends = true;
1568 assert_eq!(
1569 unmarked_snapshot.clip_point(markers[1], Bias::Left),
1570 markers[0]
1571 );
1572 }
1573
1574 assert("ΛΛ", cx);
1575 assert("ΛaΛ", cx);
1576 assert("aΛbΛ", cx);
1577 assert("aΛΞ±Λ", cx);
1578 }
1579
1580 #[gpui::test]
1581 fn test_tabs_with_multibyte_chars(cx: &mut gpui::AppContext) {
1582 init_test(cx, |_| {});
1583
1584 let text = "β
\t\tΞ±\nΞ²\t\nπΞ²\t\tΞ³";
1585 let buffer = MultiBuffer::build_simple(text, cx);
1586 let font_cache = cx.font_cache();
1587 let family_id = font_cache
1588 .load_family(&["Helvetica"], &Default::default())
1589 .unwrap();
1590 let font_id = font_cache
1591 .select_font(family_id, &Default::default())
1592 .unwrap();
1593 let font_size = 14.0;
1594
1595 let map =
1596 cx.add_model(|cx| DisplayMap::new(buffer.clone(), font_id, font_size, None, 1, 1, cx));
1597 let map = map.update(cx, |map, cx| map.snapshot(cx));
1598 assert_eq!(map.text(), "β
Ξ±\nΞ² \nπΞ² Ξ³");
1599 assert_eq!(
1600 map.text_chunks(0).collect::<String>(),
1601 "β
Ξ±\nΞ² \nπΞ² Ξ³"
1602 );
1603 assert_eq!(map.text_chunks(1).collect::<String>(), "Ξ² \nπΞ² Ξ³");
1604 assert_eq!(map.text_chunks(2).collect::<String>(), "πΞ² Ξ³");
1605
1606 let point = Point::new(0, "β
\t\t".len() as u32);
1607 let display_point = DisplayPoint::new(0, "β
".len() as u32);
1608 assert_eq!(point.to_display_point(&map), display_point);
1609 assert_eq!(display_point.to_point(&map), point);
1610
1611 let point = Point::new(1, "Ξ²\t".len() as u32);
1612 let display_point = DisplayPoint::new(1, "Ξ² ".len() as u32);
1613 assert_eq!(point.to_display_point(&map), display_point);
1614 assert_eq!(display_point.to_point(&map), point,);
1615
1616 let point = Point::new(2, "πΞ²\t\t".len() as u32);
1617 let display_point = DisplayPoint::new(2, "πΞ² ".len() as u32);
1618 assert_eq!(point.to_display_point(&map), display_point);
1619 assert_eq!(display_point.to_point(&map), point,);
1620
1621 // Display points inside of expanded tabs
1622 assert_eq!(
1623 DisplayPoint::new(0, "β
".len() as u32).to_point(&map),
1624 Point::new(0, "β
\t".len() as u32),
1625 );
1626 assert_eq!(
1627 DisplayPoint::new(0, "β
".len() as u32).to_point(&map),
1628 Point::new(0, "β
".len() as u32),
1629 );
1630
1631 // Clipping display points inside of multi-byte characters
1632 assert_eq!(
1633 map.clip_point(DisplayPoint::new(0, "β
".len() as u32 - 1), Left),
1634 DisplayPoint::new(0, 0)
1635 );
1636 assert_eq!(
1637 map.clip_point(DisplayPoint::new(0, "β
".len() as u32 - 1), Bias::Right),
1638 DisplayPoint::new(0, "β
".len() as u32)
1639 );
1640 }
1641
1642 #[gpui::test]
1643 fn test_max_point(cx: &mut gpui::AppContext) {
1644 init_test(cx, |_| {});
1645
1646 let buffer = MultiBuffer::build_simple("aaa\n\t\tbbb", cx);
1647 let font_cache = cx.font_cache();
1648 let family_id = font_cache
1649 .load_family(&["Helvetica"], &Default::default())
1650 .unwrap();
1651 let font_id = font_cache
1652 .select_font(family_id, &Default::default())
1653 .unwrap();
1654 let font_size = 14.0;
1655 let map =
1656 cx.add_model(|cx| DisplayMap::new(buffer.clone(), font_id, font_size, None, 1, 1, cx));
1657 assert_eq!(
1658 map.update(cx, |map, cx| map.snapshot(cx)).max_point(),
1659 DisplayPoint::new(1, 11)
1660 )
1661 }
1662
1663 #[test]
1664 fn test_find_internal() {
1665 assert("This is a Λtest of find internal", "test");
1666 assert("Some text ΛaΛaΛaa with repeated characters", "aa");
1667
1668 fn assert(marked_text: &str, target: &str) {
1669 let (text, expected_offsets) = marked_text_offsets(marked_text);
1670
1671 let chars = text
1672 .chars()
1673 .enumerate()
1674 .map(|(index, ch)| (ch, DisplayPoint::new(0, index as u32)));
1675 let target = target.chars();
1676
1677 assert_eq!(
1678 expected_offsets
1679 .into_iter()
1680 .map(|offset| offset as u32)
1681 .collect::<Vec<_>>(),
1682 DisplaySnapshot::find_internal(chars, target.collect(), |_, _| true)
1683 .map(|point| point.column())
1684 .collect::<Vec<_>>()
1685 )
1686 }
1687 }
1688
1689 fn syntax_chunks<'a>(
1690 rows: Range<u32>,
1691 map: &ModelHandle<DisplayMap>,
1692 theme: &'a SyntaxTheme,
1693 cx: &mut AppContext,
1694 ) -> Vec<(String, Option<Color>)> {
1695 chunks(rows, map, theme, cx)
1696 .into_iter()
1697 .map(|(text, color, _)| (text, color))
1698 .collect()
1699 }
1700
1701 fn chunks<'a>(
1702 rows: Range<u32>,
1703 map: &ModelHandle<DisplayMap>,
1704 theme: &'a SyntaxTheme,
1705 cx: &mut AppContext,
1706 ) -> Vec<(String, Option<Color>, Option<Color>)> {
1707 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1708 let mut chunks: Vec<(String, Option<Color>, Option<Color>)> = Vec::new();
1709 for chunk in snapshot.chunks(rows, true, None) {
1710 let syntax_color = chunk
1711 .syntax_highlight_id
1712 .and_then(|id| id.style(theme)?.color);
1713 let highlight_color = chunk.highlight_style.and_then(|style| style.color);
1714 if let Some((last_chunk, last_syntax_color, last_highlight_color)) = chunks.last_mut() {
1715 if syntax_color == *last_syntax_color && highlight_color == *last_highlight_color {
1716 last_chunk.push_str(chunk.text);
1717 continue;
1718 }
1719 }
1720 chunks.push((chunk.text.to_string(), syntax_color, highlight_color));
1721 }
1722 chunks
1723 }
1724
1725 fn init_test(cx: &mut AppContext, f: impl Fn(&mut AllLanguageSettingsContent)) {
1726 cx.foreground().forbid_parking();
1727 cx.set_global(SettingsStore::test(cx));
1728 language::init(cx);
1729 cx.update_global::<SettingsStore, _, _>(|store, cx| {
1730 store.update_user_settings::<AllLanguageSettings>(cx, f);
1731 });
1732 }
1733}