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