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