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