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