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