1mod block_map;
2mod fold_map;
3mod tab_map;
4mod wrap_map;
5
6use crate::{Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint};
7use block_map::{BlockMap, BlockPoint};
8use collections::{HashMap, HashSet};
9use fold_map::FoldMap;
10use gpui::{
11 fonts::{FontId, HighlightStyle},
12 Entity, ModelContext, ModelHandle,
13};
14use language::Subscription as BufferSubscription;
15use rope::{offset_utf16::OffsetUtf16, point::Point};
16use settings::Settings;
17use std::{any::TypeId, fmt::Debug, num::NonZeroU32, ops::Range, sync::Arc};
18use sum_tree::{Bias, TreeMap};
19use tab_map::TabMap;
20use wrap_map::WrapMap;
21
22pub use block_map::{
23 BlockBufferRows as DisplayBufferRows, BlockChunks as DisplayChunks, BlockContext,
24 BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock, TransformBlock,
25};
26
27pub trait ToDisplayPoint {
28 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint;
29}
30
31type TextHighlights = TreeMap<Option<TypeId>, Arc<(HighlightStyle, Vec<Range<Anchor>>)>>;
32
33pub struct DisplayMap {
34 buffer: ModelHandle<MultiBuffer>,
35 buffer_subscription: BufferSubscription,
36 fold_map: FoldMap,
37 tab_map: TabMap,
38 wrap_map: ModelHandle<WrapMap>,
39 block_map: BlockMap,
40 text_highlights: TextHighlights,
41 pub clip_at_line_ends: bool,
42}
43
44impl Entity for DisplayMap {
45 type Event = ();
46}
47
48impl DisplayMap {
49 pub fn new(
50 buffer: ModelHandle<MultiBuffer>,
51 font_id: FontId,
52 font_size: f32,
53 wrap_width: Option<f32>,
54 buffer_header_height: u8,
55 excerpt_header_height: u8,
56 cx: &mut ModelContext<Self>,
57 ) -> Self {
58 let buffer_subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
59
60 let tab_size = Self::tab_size(&buffer, cx);
61 let (fold_map, snapshot) = FoldMap::new(buffer.read(cx).snapshot(cx));
62 let (tab_map, snapshot) = TabMap::new(snapshot, tab_size);
63 let (wrap_map, snapshot) = WrapMap::new(snapshot, font_id, font_size, wrap_width, cx);
64 let block_map = BlockMap::new(snapshot, buffer_header_height, excerpt_header_height);
65 cx.observe(&wrap_map, |_, _, cx| cx.notify()).detach();
66 DisplayMap {
67 buffer,
68 buffer_subscription,
69 fold_map,
70 tab_map,
71 wrap_map,
72 block_map,
73 text_highlights: Default::default(),
74 clip_at_line_ends: false,
75 }
76 }
77
78 pub fn snapshot(&self, cx: &mut ModelContext<Self>) -> DisplaySnapshot {
79 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
80 let edits = self.buffer_subscription.consume().into_inner();
81 let (folds_snapshot, edits) = self.fold_map.read(buffer_snapshot, edits);
82
83 let tab_size = Self::tab_size(&self.buffer, cx);
84 let (tabs_snapshot, edits) = self.tab_map.sync(folds_snapshot.clone(), edits, tab_size);
85 let (wraps_snapshot, edits) = self
86 .wrap_map
87 .update(cx, |map, cx| map.sync(tabs_snapshot.clone(), edits, cx));
88 let blocks_snapshot = self.block_map.read(wraps_snapshot.clone(), edits);
89
90 DisplaySnapshot {
91 buffer_snapshot: self.buffer.read(cx).snapshot(cx),
92 folds_snapshot,
93 tabs_snapshot,
94 wraps_snapshot,
95 blocks_snapshot,
96 text_highlights: self.text_highlights.clone(),
97 clip_at_line_ends: self.clip_at_line_ends,
98 }
99 }
100
101 pub fn set_state(&mut self, other: &DisplaySnapshot, cx: &mut ModelContext<Self>) {
102 self.fold(
103 other
104 .folds_in_range(0..other.buffer_snapshot.len())
105 .map(|fold| fold.to_offset(&other.buffer_snapshot)),
106 cx,
107 );
108 }
109
110 pub fn fold<T: ToOffset>(
111 &mut self,
112 ranges: impl IntoIterator<Item = Range<T>>,
113 cx: &mut ModelContext<Self>,
114 ) {
115 let snapshot = self.buffer.read(cx).snapshot(cx);
116 let edits = self.buffer_subscription.consume().into_inner();
117 let tab_size = Self::tab_size(&self.buffer, cx);
118 let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
119 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
120 let (snapshot, edits) = self
121 .wrap_map
122 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
123 self.block_map.read(snapshot, edits);
124 let (snapshot, edits) = fold_map.fold(ranges);
125 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
126 let (snapshot, edits) = self
127 .wrap_map
128 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
129 self.block_map.read(snapshot, edits);
130 }
131
132 pub fn unfold<T: ToOffset>(
133 &mut self,
134 ranges: impl IntoIterator<Item = Range<T>>,
135 inclusive: bool,
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 (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
142 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
143 let (snapshot, edits) = self
144 .wrap_map
145 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
146 self.block_map.read(snapshot, edits);
147 let (snapshot, edits) = fold_map.unfold(ranges, inclusive);
148 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
149 let (snapshot, edits) = self
150 .wrap_map
151 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
152 self.block_map.read(snapshot, edits);
153 }
154
155 pub fn insert_blocks(
156 &mut self,
157 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
158 cx: &mut ModelContext<Self>,
159 ) -> Vec<BlockId> {
160 let snapshot = self.buffer.read(cx).snapshot(cx);
161 let edits = self.buffer_subscription.consume().into_inner();
162 let tab_size = Self::tab_size(&self.buffer, cx);
163 let (snapshot, edits) = self.fold_map.read(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 let mut block_map = self.block_map.write(snapshot, edits);
169 block_map.insert(blocks)
170 }
171
172 pub fn replace_blocks(&mut self, styles: HashMap<BlockId, RenderBlock>) {
173 self.block_map.replace(styles);
174 }
175
176 pub fn remove_blocks(&mut self, ids: HashSet<BlockId>, cx: &mut ModelContext<Self>) {
177 let snapshot = self.buffer.read(cx).snapshot(cx);
178 let edits = self.buffer_subscription.consume().into_inner();
179 let tab_size = Self::tab_size(&self.buffer, cx);
180 let (snapshot, edits) = self.fold_map.read(snapshot, edits);
181 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
182 let (snapshot, edits) = self
183 .wrap_map
184 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
185 let mut block_map = self.block_map.write(snapshot, edits);
186 block_map.remove(ids);
187 }
188
189 pub fn highlight_text(
190 &mut self,
191 type_id: TypeId,
192 ranges: Vec<Range<Anchor>>,
193 style: HighlightStyle,
194 ) {
195 self.text_highlights
196 .insert(Some(type_id), Arc::new((style, ranges)));
197 }
198
199 pub fn text_highlights(&self, type_id: TypeId) -> Option<(HighlightStyle, &[Range<Anchor>])> {
200 let highlights = self.text_highlights.get(&Some(type_id))?;
201 Some((highlights.0, &highlights.1))
202 }
203
204 pub fn clear_text_highlights(
205 &mut self,
206 type_id: TypeId,
207 ) -> Option<Arc<(HighlightStyle, Vec<Range<Anchor>>)>> {
208 self.text_highlights.remove(&Some(type_id))
209 }
210
211 pub fn set_font(&self, font_id: FontId, font_size: f32, cx: &mut ModelContext<Self>) -> bool {
212 self.wrap_map
213 .update(cx, |map, cx| map.set_font(font_id, font_size, cx))
214 }
215
216 pub fn set_wrap_width(&self, width: Option<f32>, cx: &mut ModelContext<Self>) -> bool {
217 self.wrap_map
218 .update(cx, |map, cx| map.set_wrap_width(width, cx))
219 }
220
221 fn tab_size(buffer: &ModelHandle<MultiBuffer>, cx: &mut ModelContext<Self>) -> NonZeroU32 {
222 let language_name = buffer
223 .read(cx)
224 .as_singleton()
225 .and_then(|buffer| buffer.read(cx).language())
226 .map(|language| language.name());
227
228 cx.global::<Settings>().tab_size(language_name.as_deref())
229 }
230
231 #[cfg(test)]
232 pub fn is_rewrapping(&self, cx: &gpui::AppContext) -> bool {
233 self.wrap_map.read(cx).is_rewrapping()
234 }
235}
236
237pub struct DisplaySnapshot {
238 pub buffer_snapshot: MultiBufferSnapshot,
239 folds_snapshot: fold_map::FoldSnapshot,
240 tabs_snapshot: tab_map::TabSnapshot,
241 wraps_snapshot: wrap_map::WrapSnapshot,
242 blocks_snapshot: block_map::BlockSnapshot,
243 text_highlights: TextHighlights,
244 clip_at_line_ends: bool,
245}
246
247impl DisplaySnapshot {
248 #[cfg(test)]
249 pub fn fold_count(&self) -> usize {
250 self.folds_snapshot.fold_count()
251 }
252
253 pub fn is_empty(&self) -> bool {
254 self.buffer_snapshot.len() == 0
255 }
256
257 pub fn buffer_rows(&self, start_row: u32) -> DisplayBufferRows {
258 self.blocks_snapshot.buffer_rows(start_row)
259 }
260
261 pub fn max_buffer_row(&self) -> u32 {
262 self.buffer_snapshot.max_buffer_row()
263 }
264
265 pub fn prev_line_boundary(&self, mut point: Point) -> (Point, DisplayPoint) {
266 loop {
267 let mut fold_point = self.folds_snapshot.to_fold_point(point, Bias::Left);
268 *fold_point.column_mut() = 0;
269 point = fold_point.to_buffer_point(&self.folds_snapshot);
270
271 let mut display_point = self.point_to_display_point(point, Bias::Left);
272 *display_point.column_mut() = 0;
273 let next_point = self.display_point_to_point(display_point, Bias::Left);
274 if next_point == point {
275 return (point, display_point);
276 }
277 point = next_point;
278 }
279 }
280
281 pub fn next_line_boundary(&self, mut point: Point) -> (Point, DisplayPoint) {
282 loop {
283 let mut fold_point = self.folds_snapshot.to_fold_point(point, Bias::Right);
284 *fold_point.column_mut() = self.folds_snapshot.line_len(fold_point.row());
285 point = fold_point.to_buffer_point(&self.folds_snapshot);
286
287 let mut display_point = self.point_to_display_point(point, Bias::Right);
288 *display_point.column_mut() = self.line_len(display_point.row());
289 let next_point = self.display_point_to_point(display_point, Bias::Right);
290 if next_point == point {
291 return (point, display_point);
292 }
293 point = next_point;
294 }
295 }
296
297 pub fn expand_to_line(&self, range: Range<Point>) -> Range<Point> {
298 let mut new_start = self.prev_line_boundary(range.start).0;
299 let mut new_end = self.next_line_boundary(range.end).0;
300
301 if new_start.row == range.start.row && new_end.row == range.end.row {
302 if new_end.row < self.buffer_snapshot.max_point().row {
303 new_end.row += 1;
304 new_end.column = 0;
305 } else if new_start.row > 0 {
306 new_start.row -= 1;
307 new_start.column = self.buffer_snapshot.line_len(new_start.row);
308 }
309 }
310
311 new_start..new_end
312 }
313
314 fn point_to_display_point(&self, point: Point, bias: Bias) -> DisplayPoint {
315 let fold_point = self.folds_snapshot.to_fold_point(point, bias);
316 let tab_point = self.tabs_snapshot.to_tab_point(fold_point);
317 let wrap_point = self.wraps_snapshot.tab_point_to_wrap_point(tab_point);
318 let block_point = self.blocks_snapshot.to_block_point(wrap_point);
319 DisplayPoint(block_point)
320 }
321
322 fn display_point_to_point(&self, point: DisplayPoint, bias: Bias) -> Point {
323 let block_point = point.0;
324 let wrap_point = self.blocks_snapshot.to_wrap_point(block_point);
325 let tab_point = self.wraps_snapshot.to_tab_point(wrap_point);
326 let fold_point = self.tabs_snapshot.to_fold_point(tab_point, bias).0;
327 fold_point.to_buffer_point(&self.folds_snapshot)
328 }
329
330 pub fn max_point(&self) -> DisplayPoint {
331 DisplayPoint(self.blocks_snapshot.max_point())
332 }
333
334 pub fn text_chunks(&self, display_row: u32) -> impl Iterator<Item = &str> {
335 self.blocks_snapshot
336 .chunks(display_row..self.max_point().row() + 1, false, None)
337 .map(|h| h.text)
338 }
339
340 pub fn chunks(&self, display_rows: Range<u32>, language_aware: bool) -> DisplayChunks<'_> {
341 self.blocks_snapshot
342 .chunks(display_rows, language_aware, Some(&self.text_highlights))
343 }
344
345 pub fn chars_at(&self, point: DisplayPoint) -> impl Iterator<Item = char> + '_ {
346 let mut column = 0;
347 let mut chars = self.text_chunks(point.row()).flat_map(str::chars);
348 while column < point.column() {
349 if let Some(c) = chars.next() {
350 column += c.len_utf8() as u32;
351 } else {
352 break;
353 }
354 }
355 chars
356 }
357
358 pub fn column_to_chars(&self, display_row: u32, target: u32) -> u32 {
359 let mut count = 0;
360 let mut column = 0;
361 for c in self.chars_at(DisplayPoint::new(display_row, 0)) {
362 if column >= target {
363 break;
364 }
365 count += 1;
366 column += c.len_utf8() as u32;
367 }
368 count
369 }
370
371 pub fn column_from_chars(&self, display_row: u32, char_count: u32) -> u32 {
372 let mut column = 0;
373
374 for (count, c) in self.chars_at(DisplayPoint::new(display_row, 0)).enumerate() {
375 if c == '\n' || count >= char_count as usize {
376 break;
377 }
378 column += c.len_utf8() as u32;
379 }
380
381 column
382 }
383
384 pub fn clip_point(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint {
385 let mut clipped = self.blocks_snapshot.clip_point(point.0, bias);
386 if self.clip_at_line_ends {
387 clipped = self.clip_at_line_end(DisplayPoint(clipped)).0
388 }
389 DisplayPoint(clipped)
390 }
391
392 pub fn clip_at_line_end(&self, point: DisplayPoint) -> DisplayPoint {
393 let mut point = point.0;
394 if point.column == self.line_len(point.row) {
395 point.column = point.column.saturating_sub(1);
396 point = self.blocks_snapshot.clip_point(point, Bias::Left);
397 }
398 DisplayPoint(point)
399 }
400
401 pub fn folds_in_range<T>(&self, range: Range<T>) -> impl Iterator<Item = &Range<Anchor>>
402 where
403 T: ToOffset,
404 {
405 self.folds_snapshot.folds_in_range(range)
406 }
407
408 pub fn blocks_in_range(
409 &self,
410 rows: Range<u32>,
411 ) -> impl Iterator<Item = (u32, &TransformBlock)> {
412 self.blocks_snapshot.blocks_in_range(rows)
413 }
414
415 pub fn intersects_fold<T: ToOffset>(&self, offset: T) -> bool {
416 self.folds_snapshot.intersects_fold(offset)
417 }
418
419 pub fn is_line_folded(&self, display_row: u32) -> bool {
420 let block_point = BlockPoint(Point::new(display_row, 0));
421 let wrap_point = self.blocks_snapshot.to_wrap_point(block_point);
422 let tab_point = self.wraps_snapshot.to_tab_point(wrap_point);
423 self.folds_snapshot.is_line_folded(tab_point.row())
424 }
425
426 pub fn is_block_line(&self, display_row: u32) -> bool {
427 self.blocks_snapshot.is_block_line(display_row)
428 }
429
430 pub fn soft_wrap_indent(&self, display_row: u32) -> Option<u32> {
431 let wrap_row = self
432 .blocks_snapshot
433 .to_wrap_point(BlockPoint::new(display_row, 0))
434 .row();
435 self.wraps_snapshot.soft_wrap_indent(wrap_row)
436 }
437
438 pub fn text(&self) -> String {
439 self.text_chunks(0).collect()
440 }
441
442 pub fn line(&self, display_row: u32) -> String {
443 let mut result = String::new();
444 for chunk in self.text_chunks(display_row) {
445 if let Some(ix) = chunk.find('\n') {
446 result.push_str(&chunk[0..ix]);
447 break;
448 } else {
449 result.push_str(chunk);
450 }
451 }
452 result
453 }
454
455 pub fn line_indent(&self, display_row: u32) -> (u32, bool) {
456 let mut indent = 0;
457 let mut is_blank = true;
458 for c in self.chars_at(DisplayPoint::new(display_row, 0)) {
459 if c == ' ' {
460 indent += 1;
461 } else {
462 is_blank = c == '\n';
463 break;
464 }
465 }
466 (indent, is_blank)
467 }
468
469 pub fn line_len(&self, row: u32) -> u32 {
470 self.blocks_snapshot.line_len(row)
471 }
472
473 pub fn longest_row(&self) -> u32 {
474 self.blocks_snapshot.longest_row()
475 }
476
477 #[cfg(any(test, feature = "test-support"))]
478 pub fn highlight_ranges<Tag: ?Sized + 'static>(
479 &self,
480 ) -> Option<Arc<(HighlightStyle, Vec<Range<Anchor>>)>> {
481 let type_id = TypeId::of::<Tag>();
482 self.text_highlights.get(&Some(type_id)).cloned()
483 }
484}
485
486#[derive(Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq)]
487pub struct DisplayPoint(BlockPoint);
488
489impl Debug for DisplayPoint {
490 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
491 f.write_fmt(format_args!(
492 "DisplayPoint({}, {})",
493 self.row(),
494 self.column()
495 ))
496 }
497}
498
499impl DisplayPoint {
500 pub fn new(row: u32, column: u32) -> Self {
501 Self(BlockPoint(Point::new(row, column)))
502 }
503
504 pub fn zero() -> Self {
505 Self::new(0, 0)
506 }
507
508 pub fn is_zero(&self) -> bool {
509 self.0.is_zero()
510 }
511
512 pub fn row(self) -> u32 {
513 self.0.row
514 }
515
516 pub fn column(self) -> u32 {
517 self.0.column
518 }
519
520 pub fn row_mut(&mut self) -> &mut u32 {
521 &mut self.0.row
522 }
523
524 pub fn column_mut(&mut self) -> &mut u32 {
525 &mut self.0.column
526 }
527
528 pub fn to_point(self, map: &DisplaySnapshot) -> Point {
529 map.display_point_to_point(self, Bias::Left)
530 }
531
532 pub fn to_offset(self, map: &DisplaySnapshot, bias: Bias) -> usize {
533 let unblocked_point = map.blocks_snapshot.to_wrap_point(self.0);
534 let unwrapped_point = map.wraps_snapshot.to_tab_point(unblocked_point);
535 let unexpanded_point = map.tabs_snapshot.to_fold_point(unwrapped_point, bias).0;
536 unexpanded_point.to_buffer_offset(&map.folds_snapshot)
537 }
538}
539
540impl ToDisplayPoint for usize {
541 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
542 map.point_to_display_point(self.to_point(&map.buffer_snapshot), Bias::Left)
543 }
544}
545
546impl ToDisplayPoint for OffsetUtf16 {
547 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
548 self.to_offset(&map.buffer_snapshot).to_display_point(map)
549 }
550}
551
552impl ToDisplayPoint for Point {
553 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
554 map.point_to_display_point(*self, Bias::Left)
555 }
556}
557
558impl ToDisplayPoint for Anchor {
559 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
560 self.to_point(&map.buffer_snapshot).to_display_point(map)
561 }
562}
563
564#[cfg(test)]
565pub mod tests {
566 use super::*;
567 use crate::{movement, test::marked_display_snapshot};
568 use gpui::{color::Color, elements::*, test::observe, MutableAppContext};
569 use language::{Buffer, Language, LanguageConfig, SelectionGoal};
570 use rand::{prelude::*, Rng};
571 use smol::stream::StreamExt;
572 use std::{env, sync::Arc};
573 use theme::SyntaxTheme;
574 use util::test::{marked_text_ranges, sample_text};
575 use Bias::*;
576
577 #[gpui::test(iterations = 100)]
578 async fn test_random_display_map(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
579 cx.foreground().set_block_on_ticks(0..=50);
580 cx.foreground().forbid_parking();
581 let operations = env::var("OPERATIONS")
582 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
583 .unwrap_or(10);
584
585 let font_cache = cx.font_cache().clone();
586 let mut tab_size = rng.gen_range(1..=4);
587 let buffer_start_excerpt_header_height = rng.gen_range(1..=5);
588 let excerpt_header_height = rng.gen_range(1..=5);
589 let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
590 let font_id = font_cache
591 .select_font(family_id, &Default::default())
592 .unwrap();
593 let font_size = 14.0;
594 let max_wrap_width = 300.0;
595 let mut wrap_width = if rng.gen_bool(0.1) {
596 None
597 } else {
598 Some(rng.gen_range(0.0..=max_wrap_width))
599 };
600
601 log::info!("tab size: {}", tab_size);
602 log::info!("wrap width: {:?}", wrap_width);
603
604 cx.update(|cx| {
605 let mut settings = Settings::test(cx);
606 settings.editor_overrides.tab_size = NonZeroU32::new(tab_size);
607 cx.set_global(settings)
608 });
609
610 let buffer = cx.update(|cx| {
611 if rng.gen() {
612 let len = rng.gen_range(0..10);
613 let text = util::RandomCharIter::new(&mut rng)
614 .take(len)
615 .collect::<String>();
616 MultiBuffer::build_simple(&text, cx)
617 } else {
618 MultiBuffer::build_random(&mut rng, cx)
619 }
620 });
621
622 let map = cx.add_model(|cx| {
623 DisplayMap::new(
624 buffer.clone(),
625 font_id,
626 font_size,
627 wrap_width,
628 buffer_start_excerpt_header_height,
629 excerpt_header_height,
630 cx,
631 )
632 });
633 let mut notifications = observe(&map, cx);
634 let mut fold_count = 0;
635 let mut blocks = Vec::new();
636
637 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
638 log::info!("buffer text: {:?}", snapshot.buffer_snapshot.text());
639 log::info!("fold text: {:?}", snapshot.folds_snapshot.text());
640 log::info!("tab text: {:?}", snapshot.tabs_snapshot.text());
641 log::info!("wrap text: {:?}", snapshot.wraps_snapshot.text());
642 log::info!("block text: {:?}", snapshot.blocks_snapshot.text());
643 log::info!("display text: {:?}", snapshot.text());
644
645 for _i in 0..operations {
646 match rng.gen_range(0..100) {
647 0..=19 => {
648 wrap_width = if rng.gen_bool(0.2) {
649 None
650 } else {
651 Some(rng.gen_range(0.0..=max_wrap_width))
652 };
653 log::info!("setting wrap width to {:?}", wrap_width);
654 map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
655 }
656 20..=29 => {
657 let mut tab_sizes = vec![1, 2, 3, 4];
658 tab_sizes.remove((tab_size - 1) as usize);
659 tab_size = *tab_sizes.choose(&mut rng).unwrap();
660 log::info!("setting tab size to {:?}", tab_size);
661 cx.update(|cx| {
662 let mut settings = Settings::test(cx);
663 settings.editor_overrides.tab_size = NonZeroU32::new(tab_size);
664 cx.set_global(settings)
665 });
666 }
667 30..=44 => {
668 map.update(cx, |map, cx| {
669 if rng.gen() || blocks.is_empty() {
670 let buffer = map.snapshot(cx).buffer_snapshot;
671 let block_properties = (0..rng.gen_range(1..=1))
672 .map(|_| {
673 let position =
674 buffer.anchor_after(buffer.clip_offset(
675 rng.gen_range(0..=buffer.len()),
676 Bias::Left,
677 ));
678
679 let disposition = if rng.gen() {
680 BlockDisposition::Above
681 } else {
682 BlockDisposition::Below
683 };
684 let height = rng.gen_range(1..5);
685 log::info!(
686 "inserting block {:?} {:?} with height {}",
687 disposition,
688 position.to_point(&buffer),
689 height
690 );
691 BlockProperties {
692 style: BlockStyle::Fixed,
693 position,
694 height,
695 disposition,
696 render: Arc::new(|_| Empty::new().boxed()),
697 }
698 })
699 .collect::<Vec<_>>();
700 blocks.extend(map.insert_blocks(block_properties, cx));
701 } else {
702 blocks.shuffle(&mut rng);
703 let remove_count = rng.gen_range(1..=4.min(blocks.len()));
704 let block_ids_to_remove = (0..remove_count)
705 .map(|_| blocks.remove(rng.gen_range(0..blocks.len())))
706 .collect();
707 log::info!("removing block ids {:?}", block_ids_to_remove);
708 map.remove_blocks(block_ids_to_remove, cx);
709 }
710 });
711 }
712 45..=79 => {
713 let mut ranges = Vec::new();
714 for _ in 0..rng.gen_range(1..=3) {
715 buffer.read_with(cx, |buffer, cx| {
716 let buffer = buffer.read(cx);
717 let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right);
718 let start = buffer.clip_offset(rng.gen_range(0..=end), Left);
719 ranges.push(start..end);
720 });
721 }
722
723 if rng.gen() && fold_count > 0 {
724 log::info!("unfolding ranges: {:?}", ranges);
725 map.update(cx, |map, cx| {
726 map.unfold(ranges, true, cx);
727 });
728 } else {
729 log::info!("folding ranges: {:?}", ranges);
730 map.update(cx, |map, cx| {
731 map.fold(ranges, cx);
732 });
733 }
734 }
735 _ => {
736 buffer.update(cx, |buffer, cx| buffer.randomly_mutate(&mut rng, 5, cx));
737 }
738 }
739
740 if map.read_with(cx, |map, cx| map.is_rewrapping(cx)) {
741 notifications.next().await.unwrap();
742 }
743
744 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
745 fold_count = snapshot.fold_count();
746 log::info!("buffer text: {:?}", snapshot.buffer_snapshot.text());
747 log::info!("fold text: {:?}", snapshot.folds_snapshot.text());
748 log::info!("tab text: {:?}", snapshot.tabs_snapshot.text());
749 log::info!("wrap text: {:?}", snapshot.wraps_snapshot.text());
750 log::info!("block text: {:?}", snapshot.blocks_snapshot.text());
751 log::info!("display text: {:?}", snapshot.text());
752
753 // Line boundaries
754 let buffer = &snapshot.buffer_snapshot;
755 for _ in 0..5 {
756 let row = rng.gen_range(0..=buffer.max_point().row);
757 let column = rng.gen_range(0..=buffer.line_len(row));
758 let point = buffer.clip_point(Point::new(row, column), Left);
759
760 let (prev_buffer_bound, prev_display_bound) = snapshot.prev_line_boundary(point);
761 let (next_buffer_bound, next_display_bound) = snapshot.next_line_boundary(point);
762
763 assert!(prev_buffer_bound <= point);
764 assert!(next_buffer_bound >= point);
765 assert_eq!(prev_buffer_bound.column, 0);
766 assert_eq!(prev_display_bound.column(), 0);
767 if next_buffer_bound < buffer.max_point() {
768 assert_eq!(buffer.chars_at(next_buffer_bound).next(), Some('\n'));
769 }
770
771 assert_eq!(
772 prev_display_bound,
773 prev_buffer_bound.to_display_point(&snapshot),
774 "row boundary before {:?}. reported buffer row boundary: {:?}",
775 point,
776 prev_buffer_bound
777 );
778 assert_eq!(
779 next_display_bound,
780 next_buffer_bound.to_display_point(&snapshot),
781 "display row boundary after {:?}. reported buffer row boundary: {:?}",
782 point,
783 next_buffer_bound
784 );
785 assert_eq!(
786 prev_buffer_bound,
787 prev_display_bound.to_point(&snapshot),
788 "row boundary before {:?}. reported display row boundary: {:?}",
789 point,
790 prev_display_bound
791 );
792 assert_eq!(
793 next_buffer_bound,
794 next_display_bound.to_point(&snapshot),
795 "row boundary after {:?}. reported display row boundary: {:?}",
796 point,
797 next_display_bound
798 );
799 }
800
801 // Movement
802 let min_point = snapshot.clip_point(DisplayPoint::new(0, 0), Left);
803 let max_point = snapshot.clip_point(snapshot.max_point(), Right);
804 for _ in 0..5 {
805 let row = rng.gen_range(0..=snapshot.max_point().row());
806 let column = rng.gen_range(0..=snapshot.line_len(row));
807 let point = snapshot.clip_point(DisplayPoint::new(row, column), Left);
808
809 log::info!("Moving from point {:?}", point);
810
811 let moved_right = movement::right(&snapshot, point);
812 log::info!("Right {:?}", moved_right);
813 if point < max_point {
814 assert!(moved_right > point);
815 if point.column() == snapshot.line_len(point.row())
816 || snapshot.soft_wrap_indent(point.row()).is_some()
817 && point.column() == snapshot.line_len(point.row()) - 1
818 {
819 assert!(moved_right.row() > point.row());
820 }
821 } else {
822 assert_eq!(moved_right, point);
823 }
824
825 let moved_left = movement::left(&snapshot, point);
826 log::info!("Left {:?}", moved_left);
827 if point > min_point {
828 assert!(moved_left < point);
829 if point.column() == 0 {
830 assert!(moved_left.row() < point.row());
831 }
832 } else {
833 assert_eq!(moved_left, point);
834 }
835 }
836 }
837 }
838
839 #[gpui::test(retries = 5)]
840 fn test_soft_wraps(cx: &mut MutableAppContext) {
841 cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
842 cx.foreground().forbid_parking();
843
844 let font_cache = cx.font_cache();
845
846 let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
847 let font_id = font_cache
848 .select_font(family_id, &Default::default())
849 .unwrap();
850 let font_size = 12.0;
851 let wrap_width = Some(64.);
852 cx.set_global(Settings::test(cx));
853
854 let text = "one two three four five\nsix seven eight";
855 let buffer = MultiBuffer::build_simple(text, cx);
856 let map = cx.add_model(|cx| {
857 DisplayMap::new(buffer.clone(), font_id, font_size, wrap_width, 1, 1, cx)
858 });
859
860 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
861 assert_eq!(
862 snapshot.text_chunks(0).collect::<String>(),
863 "one two \nthree four \nfive\nsix seven \neight"
864 );
865 assert_eq!(
866 snapshot.clip_point(DisplayPoint::new(0, 8), Bias::Left),
867 DisplayPoint::new(0, 7)
868 );
869 assert_eq!(
870 snapshot.clip_point(DisplayPoint::new(0, 8), Bias::Right),
871 DisplayPoint::new(1, 0)
872 );
873 assert_eq!(
874 movement::right(&snapshot, DisplayPoint::new(0, 7)),
875 DisplayPoint::new(1, 0)
876 );
877 assert_eq!(
878 movement::left(&snapshot, DisplayPoint::new(1, 0)),
879 DisplayPoint::new(0, 7)
880 );
881 assert_eq!(
882 movement::up(
883 &snapshot,
884 DisplayPoint::new(1, 10),
885 SelectionGoal::None,
886 false
887 ),
888 (DisplayPoint::new(0, 7), SelectionGoal::Column(10))
889 );
890 assert_eq!(
891 movement::down(
892 &snapshot,
893 DisplayPoint::new(0, 7),
894 SelectionGoal::Column(10),
895 false
896 ),
897 (DisplayPoint::new(1, 10), SelectionGoal::Column(10))
898 );
899 assert_eq!(
900 movement::down(
901 &snapshot,
902 DisplayPoint::new(1, 10),
903 SelectionGoal::Column(10),
904 false
905 ),
906 (DisplayPoint::new(2, 4), SelectionGoal::Column(10))
907 );
908
909 let ix = snapshot.buffer_snapshot.text().find("seven").unwrap();
910 buffer.update(cx, |buffer, cx| {
911 buffer.edit([(ix..ix, "and ")], None, cx);
912 });
913
914 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
915 assert_eq!(
916 snapshot.text_chunks(1).collect::<String>(),
917 "three four \nfive\nsix and \nseven eight"
918 );
919
920 // Re-wrap on font size changes
921 map.update(cx, |map, cx| map.set_font(font_id, font_size + 3., cx));
922
923 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
924 assert_eq!(
925 snapshot.text_chunks(1).collect::<String>(),
926 "three \nfour five\nsix and \nseven \neight"
927 )
928 }
929
930 #[gpui::test]
931 fn test_text_chunks(cx: &mut gpui::MutableAppContext) {
932 cx.set_global(Settings::test(cx));
933 let text = sample_text(6, 6, 'a');
934 let buffer = MultiBuffer::build_simple(&text, cx);
935 let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap();
936 let font_id = cx
937 .font_cache()
938 .select_font(family_id, &Default::default())
939 .unwrap();
940 let font_size = 14.0;
941 let map =
942 cx.add_model(|cx| DisplayMap::new(buffer.clone(), font_id, font_size, None, 1, 1, cx));
943 buffer.update(cx, |buffer, cx| {
944 buffer.edit(
945 vec![
946 (Point::new(1, 0)..Point::new(1, 0), "\t"),
947 (Point::new(1, 1)..Point::new(1, 1), "\t"),
948 (Point::new(2, 1)..Point::new(2, 1), "\t"),
949 ],
950 None,
951 cx,
952 )
953 });
954
955 assert_eq!(
956 map.update(cx, |map, cx| map.snapshot(cx))
957 .text_chunks(1)
958 .collect::<String>()
959 .lines()
960 .next(),
961 Some(" b bbbbb")
962 );
963 assert_eq!(
964 map.update(cx, |map, cx| map.snapshot(cx))
965 .text_chunks(2)
966 .collect::<String>()
967 .lines()
968 .next(),
969 Some("c ccccc")
970 );
971 }
972
973 #[gpui::test]
974 async fn test_chunks(cx: &mut gpui::TestAppContext) {
975 use unindent::Unindent as _;
976
977 let text = r#"
978 fn outer() {}
979
980 mod module {
981 fn inner() {}
982 }"#
983 .unindent();
984
985 let theme = SyntaxTheme::new(vec![
986 ("mod.body".to_string(), Color::red().into()),
987 ("fn.name".to_string(), Color::blue().into()),
988 ]);
989 let language = Arc::new(
990 Language::new(
991 LanguageConfig {
992 name: "Test".into(),
993 path_suffixes: vec![".test".to_string()],
994 ..Default::default()
995 },
996 Some(tree_sitter_rust::language()),
997 )
998 .with_highlights_query(
999 r#"
1000 (mod_item name: (identifier) body: _ @mod.body)
1001 (function_item name: (identifier) @fn.name)
1002 "#,
1003 )
1004 .unwrap(),
1005 );
1006 language.set_theme(&theme);
1007 cx.update(|cx| {
1008 let mut settings = Settings::test(cx);
1009 settings.editor_defaults.tab_size = Some(2.try_into().unwrap());
1010 cx.set_global(settings);
1011 });
1012
1013 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
1014 buffer.condition(cx, |buf, _| !buf.is_parsing()).await;
1015 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
1016
1017 let font_cache = cx.font_cache();
1018 let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
1019 let font_id = font_cache
1020 .select_font(family_id, &Default::default())
1021 .unwrap();
1022 let font_size = 14.0;
1023
1024 let map = cx.add_model(|cx| DisplayMap::new(buffer, font_id, font_size, None, 1, 1, cx));
1025 assert_eq!(
1026 cx.update(|cx| syntax_chunks(0..5, &map, &theme, cx)),
1027 vec![
1028 ("fn ".to_string(), None),
1029 ("outer".to_string(), Some(Color::blue())),
1030 ("() {}\n\nmod module ".to_string(), None),
1031 ("{\n fn ".to_string(), Some(Color::red())),
1032 ("inner".to_string(), Some(Color::blue())),
1033 ("() {}\n}".to_string(), Some(Color::red())),
1034 ]
1035 );
1036 assert_eq!(
1037 cx.update(|cx| syntax_chunks(3..5, &map, &theme, cx)),
1038 vec![
1039 (" fn ".to_string(), Some(Color::red())),
1040 ("inner".to_string(), Some(Color::blue())),
1041 ("() {}\n}".to_string(), Some(Color::red())),
1042 ]
1043 );
1044
1045 map.update(cx, |map, cx| {
1046 map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], cx)
1047 });
1048 assert_eq!(
1049 cx.update(|cx| syntax_chunks(0..2, &map, &theme, cx)),
1050 vec![
1051 ("fn ".to_string(), None),
1052 ("out".to_string(), Some(Color::blue())),
1053 ("ā¦".to_string(), None),
1054 (" fn ".to_string(), Some(Color::red())),
1055 ("inner".to_string(), Some(Color::blue())),
1056 ("() {}\n}".to_string(), Some(Color::red())),
1057 ]
1058 );
1059 }
1060
1061 #[gpui::test]
1062 async fn test_chunks_with_soft_wrapping(cx: &mut gpui::TestAppContext) {
1063 use unindent::Unindent as _;
1064
1065 cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
1066
1067 let text = r#"
1068 fn outer() {}
1069
1070 mod module {
1071 fn inner() {}
1072 }"#
1073 .unindent();
1074
1075 let theme = SyntaxTheme::new(vec![
1076 ("mod.body".to_string(), Color::red().into()),
1077 ("fn.name".to_string(), Color::blue().into()),
1078 ]);
1079 let language = Arc::new(
1080 Language::new(
1081 LanguageConfig {
1082 name: "Test".into(),
1083 path_suffixes: vec![".test".to_string()],
1084 ..Default::default()
1085 },
1086 Some(tree_sitter_rust::language()),
1087 )
1088 .with_highlights_query(
1089 r#"
1090 (mod_item name: (identifier) body: _ @mod.body)
1091 (function_item name: (identifier) @fn.name)
1092 "#,
1093 )
1094 .unwrap(),
1095 );
1096 language.set_theme(&theme);
1097
1098 cx.update(|cx| cx.set_global(Settings::test(cx)));
1099
1100 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
1101 buffer.condition(cx, |buf, _| !buf.is_parsing()).await;
1102 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
1103
1104 let font_cache = cx.font_cache();
1105
1106 let family_id = font_cache.load_family(&["Courier"]).unwrap();
1107 let font_id = font_cache
1108 .select_font(family_id, &Default::default())
1109 .unwrap();
1110 let font_size = 16.0;
1111
1112 let map =
1113 cx.add_model(|cx| DisplayMap::new(buffer, font_id, font_size, Some(40.0), 1, 1, cx));
1114 assert_eq!(
1115 cx.update(|cx| syntax_chunks(0..5, &map, &theme, cx)),
1116 [
1117 ("fn \n".to_string(), None),
1118 ("oute\nr".to_string(), Some(Color::blue())),
1119 ("() \n{}\n\n".to_string(), None),
1120 ]
1121 );
1122 assert_eq!(
1123 cx.update(|cx| syntax_chunks(3..5, &map, &theme, cx)),
1124 [("{}\n\n".to_string(), None)]
1125 );
1126
1127 map.update(cx, |map, cx| {
1128 map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], cx)
1129 });
1130 assert_eq!(
1131 cx.update(|cx| syntax_chunks(1..4, &map, &theme, cx)),
1132 [
1133 ("out".to_string(), Some(Color::blue())),
1134 ("ā¦\n".to_string(), None),
1135 (" \nfn ".to_string(), Some(Color::red())),
1136 ("i\n".to_string(), Some(Color::blue()))
1137 ]
1138 );
1139 }
1140
1141 #[gpui::test]
1142 async fn test_chunks_with_text_highlights(cx: &mut gpui::TestAppContext) {
1143 cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
1144
1145 cx.update(|cx| cx.set_global(Settings::test(cx)));
1146 let theme = SyntaxTheme::new(vec![
1147 ("operator".to_string(), Color::red().into()),
1148 ("string".to_string(), Color::green().into()),
1149 ]);
1150 let language = Arc::new(
1151 Language::new(
1152 LanguageConfig {
1153 name: "Test".into(),
1154 path_suffixes: vec![".test".to_string()],
1155 ..Default::default()
1156 },
1157 Some(tree_sitter_rust::language()),
1158 )
1159 .with_highlights_query(
1160 r#"
1161 ":" @operator
1162 (string_literal) @string
1163 "#,
1164 )
1165 .unwrap(),
1166 );
1167 language.set_theme(&theme);
1168
1169 let (text, highlighted_ranges) = marked_text_ranges(r#"constĖ Ā«aĀ»: B = "c Ā«dĀ»""#, false);
1170
1171 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
1172 buffer.condition(cx, |buf, _| !buf.is_parsing()).await;
1173
1174 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
1175 let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
1176
1177 let font_cache = cx.font_cache();
1178 let family_id = font_cache.load_family(&["Courier"]).unwrap();
1179 let font_id = font_cache
1180 .select_font(family_id, &Default::default())
1181 .unwrap();
1182 let font_size = 16.0;
1183 let map = cx.add_model(|cx| DisplayMap::new(buffer, font_id, font_size, None, 1, 1, cx));
1184
1185 enum MyType {}
1186
1187 let style = HighlightStyle {
1188 color: Some(Color::blue()),
1189 ..Default::default()
1190 };
1191
1192 map.update(cx, |map, _cx| {
1193 map.highlight_text(
1194 TypeId::of::<MyType>(),
1195 highlighted_ranges
1196 .into_iter()
1197 .map(|range| {
1198 buffer_snapshot.anchor_before(range.start)
1199 ..buffer_snapshot.anchor_before(range.end)
1200 })
1201 .collect(),
1202 style,
1203 );
1204 });
1205
1206 assert_eq!(
1207 cx.update(|cx| chunks(0..10, &map, &theme, cx)),
1208 [
1209 ("const ".to_string(), None, None),
1210 ("a".to_string(), None, Some(Color::blue())),
1211 (":".to_string(), Some(Color::red()), None),
1212 (" B = ".to_string(), None, None),
1213 ("\"c ".to_string(), Some(Color::green()), None),
1214 ("d".to_string(), Some(Color::green()), Some(Color::blue())),
1215 ("\"".to_string(), Some(Color::green()), None),
1216 ]
1217 );
1218 }
1219
1220 #[gpui::test]
1221 fn test_clip_point(cx: &mut gpui::MutableAppContext) {
1222 cx.set_global(Settings::test(cx));
1223 fn assert(text: &str, shift_right: bool, bias: Bias, cx: &mut gpui::MutableAppContext) {
1224 let (unmarked_snapshot, mut markers) = marked_display_snapshot(text, cx);
1225
1226 match bias {
1227 Bias::Left => {
1228 if shift_right {
1229 *markers[1].column_mut() += 1;
1230 }
1231
1232 assert_eq!(unmarked_snapshot.clip_point(markers[1], bias), markers[0])
1233 }
1234 Bias::Right => {
1235 if shift_right {
1236 *markers[0].column_mut() += 1;
1237 }
1238
1239 assert_eq!(unmarked_snapshot.clip_point(markers[0], bias), markers[1])
1240 }
1241 };
1242 }
1243
1244 use Bias::{Left, Right};
1245 assert("ĖĖα", false, Left, cx);
1246 assert("ĖĖα", true, Left, cx);
1247 assert("ĖĖα", false, Right, cx);
1248 assert("ĖαĖ", true, Right, cx);
1249 assert("ĖĖā", false, Left, cx);
1250 assert("ĖĖā", true, Left, cx);
1251 assert("ĖĖā", false, Right, cx);
1252 assert("ĖāĖ", true, Right, cx);
1253 assert("ĖĖš", false, Left, cx);
1254 assert("ĖĖš", true, Left, cx);
1255 assert("ĖĖš", false, Right, cx);
1256 assert("ĖšĖ", true, Right, cx);
1257 assert("ĖĖ\t", false, Left, cx);
1258 assert("ĖĖ\t", true, Left, cx);
1259 assert("ĖĖ\t", false, Right, cx);
1260 assert("Ė\tĖ", true, Right, cx);
1261 assert(" ĖĖ\t", false, Left, cx);
1262 assert(" ĖĖ\t", true, Left, cx);
1263 assert(" ĖĖ\t", false, Right, cx);
1264 assert(" Ė\tĖ", true, Right, cx);
1265 assert(" ĖĖ\t", false, Left, cx);
1266 assert(" ĖĖ\t", false, Right, cx);
1267 }
1268
1269 #[gpui::test]
1270 fn test_clip_at_line_ends(cx: &mut gpui::MutableAppContext) {
1271 cx.set_global(Settings::test(cx));
1272
1273 fn assert(text: &str, cx: &mut gpui::MutableAppContext) {
1274 let (mut unmarked_snapshot, markers) = marked_display_snapshot(text, cx);
1275 unmarked_snapshot.clip_at_line_ends = true;
1276 assert_eq!(
1277 unmarked_snapshot.clip_point(markers[1], Bias::Left),
1278 markers[0]
1279 );
1280 }
1281
1282 assert("ĖĖ", cx);
1283 assert("ĖaĖ", cx);
1284 assert("aĖbĖ", cx);
1285 assert("aĖαĖ", cx);
1286 }
1287
1288 #[gpui::test]
1289 fn test_tabs_with_multibyte_chars(cx: &mut gpui::MutableAppContext) {
1290 cx.set_global(Settings::test(cx));
1291 let text = "ā
\t\tα\nβ\t\nšĪ²\t\tγ";
1292 let buffer = MultiBuffer::build_simple(text, cx);
1293 let font_cache = cx.font_cache();
1294 let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
1295 let font_id = font_cache
1296 .select_font(family_id, &Default::default())
1297 .unwrap();
1298 let font_size = 14.0;
1299
1300 let map =
1301 cx.add_model(|cx| DisplayMap::new(buffer.clone(), font_id, font_size, None, 1, 1, cx));
1302 let map = map.update(cx, |map, cx| map.snapshot(cx));
1303 assert_eq!(map.text(), "ā
α\nβ \nšĪ² γ");
1304 assert_eq!(
1305 map.text_chunks(0).collect::<String>(),
1306 "ā
α\nβ \nšĪ² γ"
1307 );
1308 assert_eq!(map.text_chunks(1).collect::<String>(), "β \nšĪ² γ");
1309 assert_eq!(map.text_chunks(2).collect::<String>(), "šĪ² γ");
1310
1311 let point = Point::new(0, "ā
\t\t".len() as u32);
1312 let display_point = DisplayPoint::new(0, "ā
".len() as u32);
1313 assert_eq!(point.to_display_point(&map), display_point);
1314 assert_eq!(display_point.to_point(&map), point);
1315
1316 let point = Point::new(1, "β\t".len() as u32);
1317 let display_point = DisplayPoint::new(1, "β ".len() as u32);
1318 assert_eq!(point.to_display_point(&map), display_point);
1319 assert_eq!(display_point.to_point(&map), point,);
1320
1321 let point = Point::new(2, "šĪ²\t\t".len() as u32);
1322 let display_point = DisplayPoint::new(2, "šĪ² ".len() as u32);
1323 assert_eq!(point.to_display_point(&map), display_point);
1324 assert_eq!(display_point.to_point(&map), point,);
1325
1326 // Display points inside of expanded tabs
1327 assert_eq!(
1328 DisplayPoint::new(0, "ā
".len() as u32).to_point(&map),
1329 Point::new(0, "ā
\t".len() as u32),
1330 );
1331 assert_eq!(
1332 DisplayPoint::new(0, "ā
".len() as u32).to_point(&map),
1333 Point::new(0, "ā
".len() as u32),
1334 );
1335
1336 // Clipping display points inside of multi-byte characters
1337 assert_eq!(
1338 map.clip_point(DisplayPoint::new(0, "ā
".len() as u32 - 1), Left),
1339 DisplayPoint::new(0, 0)
1340 );
1341 assert_eq!(
1342 map.clip_point(DisplayPoint::new(0, "ā
".len() as u32 - 1), Bias::Right),
1343 DisplayPoint::new(0, "ā
".len() as u32)
1344 );
1345 }
1346
1347 #[gpui::test]
1348 fn test_max_point(cx: &mut gpui::MutableAppContext) {
1349 cx.set_global(Settings::test(cx));
1350 let buffer = MultiBuffer::build_simple("aaa\n\t\tbbb", cx);
1351 let font_cache = cx.font_cache();
1352 let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
1353 let font_id = font_cache
1354 .select_font(family_id, &Default::default())
1355 .unwrap();
1356 let font_size = 14.0;
1357 let map =
1358 cx.add_model(|cx| DisplayMap::new(buffer.clone(), font_id, font_size, None, 1, 1, cx));
1359 assert_eq!(
1360 map.update(cx, |map, cx| map.snapshot(cx)).max_point(),
1361 DisplayPoint::new(1, 11)
1362 )
1363 }
1364
1365 fn syntax_chunks<'a>(
1366 rows: Range<u32>,
1367 map: &ModelHandle<DisplayMap>,
1368 theme: &'a SyntaxTheme,
1369 cx: &mut MutableAppContext,
1370 ) -> Vec<(String, Option<Color>)> {
1371 chunks(rows, map, theme, cx)
1372 .into_iter()
1373 .map(|(text, color, _)| (text, color))
1374 .collect()
1375 }
1376
1377 fn chunks<'a>(
1378 rows: Range<u32>,
1379 map: &ModelHandle<DisplayMap>,
1380 theme: &'a SyntaxTheme,
1381 cx: &mut MutableAppContext,
1382 ) -> Vec<(String, Option<Color>, Option<Color>)> {
1383 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1384 let mut chunks: Vec<(String, Option<Color>, Option<Color>)> = Vec::new();
1385 for chunk in snapshot.chunks(rows, true) {
1386 let syntax_color = chunk
1387 .syntax_highlight_id
1388 .and_then(|id| id.style(theme)?.color);
1389 let highlight_color = chunk.highlight_style.and_then(|style| style.color);
1390 if let Some((last_chunk, last_syntax_color, last_highlight_color)) = chunks.last_mut() {
1391 if syntax_color == *last_syntax_color && highlight_color == *last_highlight_color {
1392 last_chunk.push_str(chunk.text);
1393 continue;
1394 }
1395 }
1396 chunks.push((chunk.text.to_string(), syntax_color, highlight_color));
1397 }
1398 chunks
1399 }
1400}