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