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