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