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