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