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