display_map.rs

   1mod block_map;
   2mod fold_map;
   3mod patch;
   4mod tab_map;
   5mod wrap_map;
   6
   7use block_map::{BlockId, BlockMap, BlockPoint, BlockProperties};
   8use buffer::Rope;
   9use fold_map::{FoldMap, ToFoldPoint as _};
  10use gpui::{fonts::FontId, Entity, ModelContext, ModelHandle};
  11use language::{Anchor, Buffer, Point, ToOffset, ToPoint};
  12use std::{collections::HashSet, ops::Range};
  13use sum_tree::Bias;
  14use tab_map::TabMap;
  15use wrap_map::WrapMap;
  16
  17pub use block_map::HighlightedChunks;
  18pub use wrap_map::BufferRows;
  19
  20pub trait ToDisplayPoint {
  21    fn to_display_point(&self, map: &DisplayMapSnapshot, bias: Bias) -> DisplayPoint;
  22}
  23
  24pub struct DisplayMap {
  25    buffer: ModelHandle<Buffer>,
  26    fold_map: FoldMap,
  27    tab_map: TabMap,
  28    wrap_map: ModelHandle<WrapMap>,
  29    block_map: BlockMap,
  30}
  31
  32impl Entity for DisplayMap {
  33    type Event = ();
  34}
  35
  36impl DisplayMap {
  37    pub fn new(
  38        buffer: ModelHandle<Buffer>,
  39        tab_size: usize,
  40        font_id: FontId,
  41        font_size: f32,
  42        wrap_width: Option<f32>,
  43        cx: &mut ModelContext<Self>,
  44    ) -> Self {
  45        let (fold_map, snapshot) = FoldMap::new(buffer.clone(), cx);
  46        let (tab_map, snapshot) = TabMap::new(snapshot, tab_size);
  47        let (wrap_map, snapshot) = WrapMap::new(snapshot, font_id, font_size, wrap_width, cx);
  48        let block_map = BlockMap::new(buffer.clone(), snapshot);
  49        cx.observe(&wrap_map, |_, _, cx| cx.notify()).detach();
  50        DisplayMap {
  51            buffer,
  52            fold_map,
  53            tab_map,
  54            wrap_map,
  55            block_map,
  56        }
  57    }
  58
  59    pub fn snapshot(&self, cx: &mut ModelContext<Self>) -> DisplayMapSnapshot {
  60        let (folds_snapshot, edits) = self.fold_map.read(cx);
  61        let (tabs_snapshot, edits) = self.tab_map.sync(folds_snapshot.clone(), edits);
  62        let (wraps_snapshot, edits) = self
  63            .wrap_map
  64            .update(cx, |map, cx| map.sync(tabs_snapshot.clone(), edits, cx));
  65        let blocks_snapshot = self.block_map.read(wraps_snapshot.clone(), edits, cx);
  66
  67        DisplayMapSnapshot {
  68            buffer_snapshot: self.buffer.read(cx).snapshot(),
  69            folds_snapshot,
  70            tabs_snapshot,
  71            wraps_snapshot,
  72            blocks_snapshot,
  73        }
  74    }
  75
  76    pub fn fold<T: ToOffset>(
  77        &mut self,
  78        ranges: impl IntoIterator<Item = Range<T>>,
  79        cx: &mut ModelContext<Self>,
  80    ) {
  81        let (mut fold_map, snapshot, edits) = self.fold_map.write(cx);
  82        let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
  83        let (snapshot, edits) = self
  84            .wrap_map
  85            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
  86        self.block_map.sync(&snapshot, edits, cx);
  87        let (snapshot, edits) = fold_map.fold(ranges, cx);
  88        let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
  89        let (snapshot, edits) = self
  90            .wrap_map
  91            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
  92        self.block_map.sync(&snapshot, edits, cx);
  93    }
  94
  95    pub fn unfold<T: ToOffset>(
  96        &mut self,
  97        ranges: impl IntoIterator<Item = Range<T>>,
  98        cx: &mut ModelContext<Self>,
  99    ) {
 100        let (mut fold_map, snapshot, edits) = self.fold_map.write(cx);
 101        let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
 102        let (snapshot, edits) = self
 103            .wrap_map
 104            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 105        self.block_map.sync(&snapshot, edits, cx);
 106        let (snapshot, edits) = fold_map.unfold(ranges, cx);
 107        let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
 108        let (snapshot, edits) = self
 109            .wrap_map
 110            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 111        self.block_map.sync(&snapshot, edits, cx);
 112    }
 113
 114    pub fn insert_blocks<P, T>(
 115        &mut self,
 116        blocks: impl IntoIterator<Item = BlockProperties<P, T>>,
 117        cx: &mut ModelContext<Self>,
 118    ) -> Vec<BlockId>
 119    where
 120        P: ToOffset + Clone,
 121        T: Into<Rope> + Clone,
 122    {
 123        let (snapshot, edits) = self.fold_map.read(cx);
 124        let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
 125        let (snapshot, edits) = self
 126            .wrap_map
 127            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 128        let mut block_map = self.block_map.write(snapshot, edits, cx);
 129        block_map.insert(blocks, cx)
 130    }
 131
 132    pub fn remove_blocks(&mut self, ids: HashSet<BlockId>, cx: &mut ModelContext<Self>) {
 133        let (snapshot, edits) = self.fold_map.read(cx);
 134        let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
 135        let (snapshot, edits) = self
 136            .wrap_map
 137            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 138        let mut block_map = self.block_map.write(snapshot, edits, cx);
 139        block_map.remove(ids, cx);
 140    }
 141
 142    pub fn set_font(&self, font_id: FontId, font_size: f32, cx: &mut ModelContext<Self>) {
 143        self.wrap_map
 144            .update(cx, |map, cx| map.set_font(font_id, font_size, cx));
 145    }
 146
 147    pub fn set_wrap_width(&self, width: Option<f32>, cx: &mut ModelContext<Self>) -> bool {
 148        self.wrap_map
 149            .update(cx, |map, cx| map.set_wrap_width(width, cx))
 150    }
 151
 152    #[cfg(test)]
 153    pub fn is_rewrapping(&self, cx: &gpui::AppContext) -> bool {
 154        self.wrap_map.read(cx).is_rewrapping()
 155    }
 156}
 157
 158pub struct DisplayMapSnapshot {
 159    buffer_snapshot: language::Snapshot,
 160    folds_snapshot: fold_map::Snapshot,
 161    tabs_snapshot: tab_map::Snapshot,
 162    wraps_snapshot: wrap_map::Snapshot,
 163    blocks_snapshot: block_map::BlockSnapshot,
 164}
 165
 166impl DisplayMapSnapshot {
 167    #[cfg(test)]
 168    pub fn fold_count(&self) -> usize {
 169        self.folds_snapshot.fold_count()
 170    }
 171
 172    pub fn is_empty(&self) -> bool {
 173        self.buffer_snapshot.len() == 0
 174    }
 175
 176    pub fn buffer_rows(&self, start_row: u32) -> BufferRows {
 177        self.wraps_snapshot.buffer_rows(start_row)
 178    }
 179
 180    pub fn buffer_row_count(&self) -> u32 {
 181        self.buffer_snapshot.max_point().row + 1
 182    }
 183
 184    pub fn prev_row_boundary(&self, mut display_point: DisplayPoint) -> (DisplayPoint, Point) {
 185        loop {
 186            *display_point.column_mut() = 0;
 187            let mut point = display_point.to_buffer_point(self, Bias::Left);
 188            point.column = 0;
 189            let next_display_point = point.to_display_point(self, Bias::Left);
 190            if next_display_point == display_point {
 191                return (display_point, point);
 192            }
 193            display_point = next_display_point;
 194        }
 195    }
 196
 197    pub fn next_row_boundary(&self, mut display_point: DisplayPoint) -> (DisplayPoint, Point) {
 198        loop {
 199            *display_point.column_mut() = self.line_len(display_point.row());
 200            let mut point = display_point.to_buffer_point(self, Bias::Right);
 201            point.column = self.buffer_snapshot.line_len(point.row);
 202            let next_display_point = point.to_display_point(self, Bias::Right);
 203            if next_display_point == display_point {
 204                return (display_point, point);
 205            }
 206            display_point = next_display_point;
 207        }
 208    }
 209
 210    pub fn max_point(&self) -> DisplayPoint {
 211        DisplayPoint(self.blocks_snapshot.max_point())
 212    }
 213
 214    pub fn chunks_at(&self, display_row: u32) -> wrap_map::Chunks {
 215        self.wraps_snapshot.chunks_at(display_row)
 216    }
 217
 218    pub fn highlighted_chunks_for_rows(
 219        &mut self,
 220        display_rows: Range<u32>,
 221    ) -> block_map::HighlightedChunks {
 222        self.blocks_snapshot
 223            .highlighted_chunks_for_rows(display_rows)
 224    }
 225
 226    pub fn chars_at<'a>(&'a self, point: DisplayPoint) -> impl Iterator<Item = char> + 'a {
 227        let mut column = 0;
 228        let mut chars = self.chunks_at(point.row()).flat_map(str::chars);
 229        while column < point.column() {
 230            if let Some(c) = chars.next() {
 231                column += c.len_utf8() as u32;
 232            } else {
 233                break;
 234            }
 235        }
 236        chars
 237    }
 238
 239    pub fn column_to_chars(&self, display_row: u32, target: u32) -> u32 {
 240        let mut count = 0;
 241        let mut column = 0;
 242        for c in self.chars_at(DisplayPoint::new(display_row, 0)) {
 243            if column >= target {
 244                break;
 245            }
 246            count += 1;
 247            column += c.len_utf8() as u32;
 248        }
 249        count
 250    }
 251
 252    pub fn column_from_chars(&self, display_row: u32, char_count: u32) -> u32 {
 253        let mut count = 0;
 254        let mut column = 0;
 255        for c in self.chars_at(DisplayPoint::new(display_row, 0)) {
 256            if c == '\n' || count >= char_count {
 257                break;
 258            }
 259            count += 1;
 260            column += c.len_utf8() as u32;
 261        }
 262        column
 263    }
 264
 265    pub fn clip_point(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint {
 266        DisplayPoint(self.blocks_snapshot.clip_point(point.0, bias))
 267    }
 268
 269    pub fn folds_in_range<'a, T>(
 270        &'a self,
 271        range: Range<T>,
 272    ) -> impl Iterator<Item = &'a Range<Anchor>>
 273    where
 274        T: ToOffset,
 275    {
 276        self.folds_snapshot.folds_in_range(range)
 277    }
 278
 279    pub fn intersects_fold<T: ToOffset>(&self, offset: T) -> bool {
 280        self.folds_snapshot.intersects_fold(offset)
 281    }
 282
 283    pub fn is_line_folded(&self, display_row: u32) -> bool {
 284        let block_point = BlockPoint(Point::new(display_row, 0));
 285        let wrap_point = self.blocks_snapshot.to_wrap_point(block_point);
 286        let tab_point = self.wraps_snapshot.to_tab_point(wrap_point);
 287        self.folds_snapshot.is_line_folded(tab_point.row())
 288    }
 289
 290    pub fn soft_wrap_indent(&self, display_row: u32) -> Option<u32> {
 291        self.wraps_snapshot.soft_wrap_indent(display_row)
 292    }
 293
 294    pub fn text(&self) -> String {
 295        self.chunks_at(0).collect()
 296    }
 297
 298    pub fn line(&self, display_row: u32) -> String {
 299        let mut result = String::new();
 300        for chunk in self.chunks_at(display_row) {
 301            if let Some(ix) = chunk.find('\n') {
 302                result.push_str(&chunk[0..ix]);
 303                break;
 304            } else {
 305                result.push_str(chunk);
 306            }
 307        }
 308        result
 309    }
 310
 311    pub fn line_indent(&self, display_row: u32) -> (u32, bool) {
 312        let mut indent = 0;
 313        let mut is_blank = true;
 314        for c in self.chars_at(DisplayPoint::new(display_row, 0)) {
 315            if c == ' ' {
 316                indent += 1;
 317            } else {
 318                is_blank = c == '\n';
 319                break;
 320            }
 321        }
 322        (indent, is_blank)
 323    }
 324
 325    pub fn line_len(&self, row: u32) -> u32 {
 326        self.wraps_snapshot.line_len(row)
 327    }
 328
 329    pub fn longest_row(&self) -> u32 {
 330        self.wraps_snapshot.longest_row()
 331    }
 332
 333    pub fn anchor_before(&self, point: DisplayPoint, bias: Bias) -> Anchor {
 334        self.buffer_snapshot
 335            .anchor_before(point.to_buffer_point(self, bias))
 336    }
 337
 338    pub fn anchor_after(&self, point: DisplayPoint, bias: Bias) -> Anchor {
 339        self.buffer_snapshot
 340            .anchor_after(point.to_buffer_point(self, bias))
 341    }
 342}
 343
 344#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
 345pub struct DisplayPoint(BlockPoint);
 346
 347impl DisplayPoint {
 348    pub fn new(row: u32, column: u32) -> Self {
 349        Self(BlockPoint(Point::new(row, column)))
 350    }
 351
 352    pub fn zero() -> Self {
 353        Self::new(0, 0)
 354    }
 355
 356    #[cfg(test)]
 357    pub fn is_zero(&self) -> bool {
 358        self.0.is_zero()
 359    }
 360
 361    pub fn row(self) -> u32 {
 362        self.0.row
 363    }
 364
 365    pub fn column(self) -> u32 {
 366        self.0.column
 367    }
 368
 369    pub fn row_mut(&mut self) -> &mut u32 {
 370        &mut self.0.row
 371    }
 372
 373    pub fn column_mut(&mut self) -> &mut u32 {
 374        &mut self.0.column
 375    }
 376
 377    pub fn to_buffer_point(self, map: &DisplayMapSnapshot, bias: Bias) -> Point {
 378        let unblocked_point = map.blocks_snapshot.to_wrap_point(self.0);
 379        let unwrapped_point = map.wraps_snapshot.to_tab_point(unblocked_point);
 380        let unexpanded_point = map.tabs_snapshot.to_fold_point(unwrapped_point, bias).0;
 381        unexpanded_point.to_buffer_point(&map.folds_snapshot)
 382    }
 383
 384    pub fn to_buffer_offset(self, map: &DisplayMapSnapshot, bias: Bias) -> usize {
 385        let unblocked_point = map.blocks_snapshot.to_wrap_point(self.0);
 386        let unwrapped_point = map.wraps_snapshot.to_tab_point(unblocked_point);
 387        let unexpanded_point = map.tabs_snapshot.to_fold_point(unwrapped_point, bias).0;
 388        unexpanded_point.to_buffer_offset(&map.folds_snapshot)
 389    }
 390}
 391
 392impl ToDisplayPoint for Point {
 393    fn to_display_point(&self, map: &DisplayMapSnapshot, bias: Bias) -> DisplayPoint {
 394        let fold_point = self.to_fold_point(&map.folds_snapshot, bias);
 395        let tab_point = map.tabs_snapshot.to_tab_point(fold_point);
 396        let wrap_point = map.wraps_snapshot.to_wrap_point(tab_point);
 397        let block_point = map.blocks_snapshot.to_block_point(wrap_point);
 398        DisplayPoint(block_point)
 399    }
 400}
 401
 402impl ToDisplayPoint for Anchor {
 403    fn to_display_point(&self, map: &DisplayMapSnapshot, bias: Bias) -> DisplayPoint {
 404        self.to_point(&map.buffer_snapshot)
 405            .to_display_point(map, bias)
 406    }
 407}
 408
 409#[cfg(test)]
 410mod tests {
 411    use super::*;
 412    use crate::{movement, test::*};
 413    use gpui::{color::Color, MutableAppContext};
 414    use language::{Language, LanguageConfig, RandomCharIter, SelectionGoal};
 415    use rand::{prelude::StdRng, Rng};
 416    use std::{env, sync::Arc};
 417    use theme::SyntaxTheme;
 418    use Bias::*;
 419
 420    #[gpui::test(iterations = 100)]
 421    async fn test_random(mut cx: gpui::TestAppContext, mut rng: StdRng) {
 422        cx.foreground().set_block_on_ticks(0..=50);
 423        cx.foreground().forbid_parking();
 424        let operations = env::var("OPERATIONS")
 425            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
 426            .unwrap_or(10);
 427
 428        let font_cache = cx.font_cache().clone();
 429        let tab_size = rng.gen_range(1..=4);
 430        let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
 431        let font_id = font_cache
 432            .select_font(family_id, &Default::default())
 433            .unwrap();
 434        let font_size = 14.0;
 435        let max_wrap_width = 300.0;
 436        let mut wrap_width = if rng.gen_bool(0.1) {
 437            None
 438        } else {
 439            Some(rng.gen_range(0.0..=max_wrap_width))
 440        };
 441
 442        log::info!("tab size: {}", tab_size);
 443        log::info!("wrap width: {:?}", wrap_width);
 444
 445        let buffer = cx.add_model(|cx| {
 446            let len = rng.gen_range(0..10);
 447            let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
 448            Buffer::new(0, text, cx)
 449        });
 450
 451        let map = cx.add_model(|cx| {
 452            DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, wrap_width, cx)
 453        });
 454        let (_observer, notifications) = Observer::new(&map, &mut cx);
 455        let mut fold_count = 0;
 456
 457        for _i in 0..operations {
 458            match rng.gen_range(0..100) {
 459                0..=19 => {
 460                    wrap_width = if rng.gen_bool(0.2) {
 461                        None
 462                    } else {
 463                        Some(rng.gen_range(0.0..=max_wrap_width))
 464                    };
 465                    log::info!("setting wrap width to {:?}", wrap_width);
 466                    map.update(&mut cx, |map, cx| map.set_wrap_width(wrap_width, cx));
 467                }
 468                20..=80 => {
 469                    let mut ranges = Vec::new();
 470                    for _ in 0..rng.gen_range(1..=3) {
 471                        buffer.read_with(&cx, |buffer, _| {
 472                            let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right);
 473                            let start = buffer.clip_offset(rng.gen_range(0..=end), Left);
 474                            ranges.push(start..end);
 475                        });
 476                    }
 477
 478                    if rng.gen() && fold_count > 0 {
 479                        log::info!("unfolding ranges: {:?}", ranges);
 480                        map.update(&mut cx, |map, cx| {
 481                            map.unfold(ranges, cx);
 482                        });
 483                    } else {
 484                        log::info!("folding ranges: {:?}", ranges);
 485                        map.update(&mut cx, |map, cx| {
 486                            map.fold(ranges, cx);
 487                        });
 488                    }
 489                }
 490                _ => {
 491                    buffer.update(&mut cx, |buffer, _| buffer.randomly_edit(&mut rng, 5));
 492                }
 493            }
 494
 495            if map.read_with(&cx, |map, cx| map.is_rewrapping(cx)) {
 496                notifications.recv().await.unwrap();
 497            }
 498
 499            let snapshot = map.update(&mut cx, |map, cx| map.snapshot(cx));
 500            fold_count = snapshot.fold_count();
 501            log::info!("buffer text: {:?}", buffer.read_with(&cx, |b, _| b.text()));
 502            log::info!("display text: {:?}", snapshot.text());
 503
 504            // Line boundaries
 505            for _ in 0..5 {
 506                let row = rng.gen_range(0..=snapshot.max_point().row());
 507                let column = rng.gen_range(0..=snapshot.line_len(row));
 508                let point = snapshot.clip_point(DisplayPoint::new(row, column), Left);
 509
 510                let (prev_display_bound, prev_buffer_bound) = snapshot.prev_row_boundary(point);
 511                let (next_display_bound, next_buffer_bound) = snapshot.next_row_boundary(point);
 512
 513                assert!(prev_display_bound <= point);
 514                assert!(next_display_bound >= point);
 515                assert_eq!(prev_buffer_bound.column, 0);
 516                assert_eq!(prev_display_bound.column(), 0);
 517                if next_display_bound < snapshot.max_point() {
 518                    assert_eq!(
 519                        buffer
 520                            .read_with(&cx, |buffer, _| buffer.chars_at(next_buffer_bound).next()),
 521                        Some('\n')
 522                    )
 523                }
 524
 525                assert_eq!(
 526                    prev_display_bound,
 527                    prev_buffer_bound.to_display_point(&snapshot, Left),
 528                    "row boundary before {:?}. reported buffer row boundary: {:?}",
 529                    point,
 530                    prev_buffer_bound
 531                );
 532                assert_eq!(
 533                    next_display_bound,
 534                    next_buffer_bound.to_display_point(&snapshot, Right),
 535                    "display row boundary after {:?}. reported buffer row boundary: {:?}",
 536                    point,
 537                    next_buffer_bound
 538                );
 539                assert_eq!(
 540                    prev_buffer_bound,
 541                    prev_display_bound.to_buffer_point(&snapshot, Left),
 542                    "row boundary before {:?}. reported display row boundary: {:?}",
 543                    point,
 544                    prev_display_bound
 545                );
 546                assert_eq!(
 547                    next_buffer_bound,
 548                    next_display_bound.to_buffer_point(&snapshot, Right),
 549                    "row boundary after {:?}. reported display row boundary: {:?}",
 550                    point,
 551                    next_display_bound
 552                );
 553            }
 554
 555            // Movement
 556            for _ in 0..5 {
 557                let row = rng.gen_range(0..=snapshot.max_point().row());
 558                let column = rng.gen_range(0..=snapshot.line_len(row));
 559                let point = snapshot.clip_point(DisplayPoint::new(row, column), Left);
 560
 561                log::info!("Moving from point {:?}", point);
 562
 563                let moved_right = movement::right(&snapshot, point).unwrap();
 564                log::info!("Right {:?}", moved_right);
 565                if point < snapshot.max_point() {
 566                    assert!(moved_right > point);
 567                    if point.column() == snapshot.line_len(point.row())
 568                        || snapshot.soft_wrap_indent(point.row()).is_some()
 569                            && point.column() == snapshot.line_len(point.row()) - 1
 570                    {
 571                        assert!(moved_right.row() > point.row());
 572                    }
 573                } else {
 574                    assert_eq!(moved_right, point);
 575                }
 576
 577                let moved_left = movement::left(&snapshot, point).unwrap();
 578                log::info!("Left {:?}", moved_left);
 579                if !point.is_zero() {
 580                    assert!(moved_left < point);
 581                    if point.column() == 0 {
 582                        assert!(moved_left.row() < point.row());
 583                    }
 584                } else {
 585                    assert!(moved_left.is_zero());
 586                }
 587            }
 588        }
 589    }
 590
 591    #[gpui::test]
 592    fn test_soft_wraps(cx: &mut MutableAppContext) {
 593        cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
 594        cx.foreground().forbid_parking();
 595
 596        let font_cache = cx.font_cache();
 597
 598        let tab_size = 4;
 599        let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
 600        let font_id = font_cache
 601            .select_font(family_id, &Default::default())
 602            .unwrap();
 603        let font_size = 12.0;
 604        let wrap_width = Some(64.);
 605
 606        let text = "one two three four five\nsix seven eight";
 607        let buffer = cx.add_model(|cx| Buffer::new(0, text.to_string(), cx));
 608        let map = cx.add_model(|cx| {
 609            DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, wrap_width, cx)
 610        });
 611
 612        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
 613        assert_eq!(
 614            snapshot.chunks_at(0).collect::<String>(),
 615            "one two \nthree four \nfive\nsix seven \neight"
 616        );
 617        assert_eq!(
 618            snapshot.clip_point(DisplayPoint::new(0, 8), Bias::Left),
 619            DisplayPoint::new(0, 7)
 620        );
 621        assert_eq!(
 622            snapshot.clip_point(DisplayPoint::new(0, 8), Bias::Right),
 623            DisplayPoint::new(1, 0)
 624        );
 625        assert_eq!(
 626            movement::right(&snapshot, DisplayPoint::new(0, 7)).unwrap(),
 627            DisplayPoint::new(1, 0)
 628        );
 629        assert_eq!(
 630            movement::left(&snapshot, DisplayPoint::new(1, 0)).unwrap(),
 631            DisplayPoint::new(0, 7)
 632        );
 633        assert_eq!(
 634            movement::up(&snapshot, DisplayPoint::new(1, 10), SelectionGoal::None).unwrap(),
 635            (DisplayPoint::new(0, 7), SelectionGoal::Column(10))
 636        );
 637        assert_eq!(
 638            movement::down(
 639                &snapshot,
 640                DisplayPoint::new(0, 7),
 641                SelectionGoal::Column(10)
 642            )
 643            .unwrap(),
 644            (DisplayPoint::new(1, 10), SelectionGoal::Column(10))
 645        );
 646        assert_eq!(
 647            movement::down(
 648                &snapshot,
 649                DisplayPoint::new(1, 10),
 650                SelectionGoal::Column(10)
 651            )
 652            .unwrap(),
 653            (DisplayPoint::new(2, 4), SelectionGoal::Column(10))
 654        );
 655
 656        buffer.update(cx, |buffer, cx| {
 657            let ix = buffer.text().find("seven").unwrap();
 658            buffer.edit(vec![ix..ix], "and ", cx);
 659        });
 660
 661        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
 662        assert_eq!(
 663            snapshot.chunks_at(1).collect::<String>(),
 664            "three four \nfive\nsix and \nseven eight"
 665        );
 666
 667        // Re-wrap on font size changes
 668        map.update(cx, |map, cx| map.set_font(font_id, font_size + 3., cx));
 669
 670        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
 671        assert_eq!(
 672            snapshot.chunks_at(1).collect::<String>(),
 673            "three \nfour five\nsix and \nseven \neight"
 674        )
 675    }
 676
 677    #[gpui::test]
 678    fn test_chunks_at(cx: &mut gpui::MutableAppContext) {
 679        let text = sample_text(6, 6);
 680        let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
 681        let tab_size = 4;
 682        let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap();
 683        let font_id = cx
 684            .font_cache()
 685            .select_font(family_id, &Default::default())
 686            .unwrap();
 687        let font_size = 14.0;
 688        let map = cx.add_model(|cx| {
 689            DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, None, cx)
 690        });
 691        buffer.update(cx, |buffer, cx| {
 692            buffer.edit(
 693                vec![
 694                    Point::new(1, 0)..Point::new(1, 0),
 695                    Point::new(1, 1)..Point::new(1, 1),
 696                    Point::new(2, 1)..Point::new(2, 1),
 697                ],
 698                "\t",
 699                cx,
 700            )
 701        });
 702
 703        assert_eq!(
 704            map.update(cx, |map, cx| map.snapshot(cx))
 705                .chunks_at(1)
 706                .collect::<String>()
 707                .lines()
 708                .next(),
 709            Some("    b   bbbbb")
 710        );
 711        assert_eq!(
 712            map.update(cx, |map, cx| map.snapshot(cx))
 713                .chunks_at(2)
 714                .collect::<String>()
 715                .lines()
 716                .next(),
 717            Some("c   ccccc")
 718        );
 719    }
 720
 721    #[gpui::test]
 722    async fn test_highlighted_chunks_at(mut cx: gpui::TestAppContext) {
 723        use unindent::Unindent as _;
 724
 725        let text = r#"
 726            fn outer() {}
 727
 728            mod module {
 729                fn inner() {}
 730            }"#
 731        .unindent();
 732
 733        let theme = SyntaxTheme::new(vec![
 734            ("mod.body".to_string(), Color::from_u32(0xff0000ff).into()),
 735            ("fn.name".to_string(), Color::from_u32(0x00ff00ff).into()),
 736        ]);
 737        let lang = Arc::new(
 738            Language::new(
 739                LanguageConfig {
 740                    name: "Test".to_string(),
 741                    path_suffixes: vec![".test".to_string()],
 742                    ..Default::default()
 743                },
 744                tree_sitter_rust::language(),
 745            )
 746            .with_highlights_query(
 747                r#"
 748                (mod_item name: (identifier) body: _ @mod.body)
 749                (function_item name: (identifier) @fn.name)
 750                "#,
 751            )
 752            .unwrap(),
 753        );
 754        lang.set_theme(&theme);
 755
 756        let buffer =
 757            cx.add_model(|cx| Buffer::new(0, text, cx).with_language(Some(lang), None, cx));
 758        buffer.condition(&cx, |buf, _| !buf.is_parsing()).await;
 759
 760        let tab_size = 2;
 761        let font_cache = cx.font_cache();
 762        let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
 763        let font_id = font_cache
 764            .select_font(family_id, &Default::default())
 765            .unwrap();
 766        let font_size = 14.0;
 767
 768        let map =
 769            cx.add_model(|cx| DisplayMap::new(buffer, tab_size, font_id, font_size, None, cx));
 770        assert_eq!(
 771            cx.update(|cx| highlighted_chunks(0..5, &map, &theme, cx)),
 772            vec![
 773                ("fn ".to_string(), None),
 774                ("outer".to_string(), Some("fn.name")),
 775                ("() {}\n\nmod module ".to_string(), None),
 776                ("{\n    fn ".to_string(), Some("mod.body")),
 777                ("inner".to_string(), Some("fn.name")),
 778                ("() {}\n}".to_string(), Some("mod.body")),
 779            ]
 780        );
 781        assert_eq!(
 782            cx.update(|cx| highlighted_chunks(3..5, &map, &theme, cx)),
 783            vec![
 784                ("    fn ".to_string(), Some("mod.body")),
 785                ("inner".to_string(), Some("fn.name")),
 786                ("() {}\n}".to_string(), Some("mod.body")),
 787            ]
 788        );
 789
 790        map.update(&mut cx, |map, cx| {
 791            map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], cx)
 792        });
 793        assert_eq!(
 794            cx.update(|cx| highlighted_chunks(0..2, &map, &theme, cx)),
 795            vec![
 796                ("fn ".to_string(), None),
 797                ("out".to_string(), Some("fn.name")),
 798                ("".to_string(), None),
 799                ("  fn ".to_string(), Some("mod.body")),
 800                ("inner".to_string(), Some("fn.name")),
 801                ("() {}\n}".to_string(), Some("mod.body")),
 802            ]
 803        );
 804    }
 805
 806    #[gpui::test]
 807    async fn test_highlighted_chunks_with_soft_wrapping(mut cx: gpui::TestAppContext) {
 808        use unindent::Unindent as _;
 809
 810        cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
 811
 812        let text = r#"
 813            fn outer() {}
 814
 815            mod module {
 816                fn inner() {}
 817            }"#
 818        .unindent();
 819
 820        let theme = SyntaxTheme::new(vec![
 821            ("mod.body".to_string(), Color::from_u32(0xff0000ff).into()),
 822            ("fn.name".to_string(), Color::from_u32(0x00ff00ff).into()),
 823        ]);
 824        let lang = Arc::new(
 825            Language::new(
 826                LanguageConfig {
 827                    name: "Test".to_string(),
 828                    path_suffixes: vec![".test".to_string()],
 829                    ..Default::default()
 830                },
 831                tree_sitter_rust::language(),
 832            )
 833            .with_highlights_query(
 834                r#"
 835                (mod_item name: (identifier) body: _ @mod.body)
 836                (function_item name: (identifier) @fn.name)
 837                "#,
 838            )
 839            .unwrap(),
 840        );
 841        lang.set_theme(&theme);
 842
 843        let buffer =
 844            cx.add_model(|cx| Buffer::new(0, text, cx).with_language(Some(lang), None, cx));
 845        buffer.condition(&cx, |buf, _| !buf.is_parsing()).await;
 846
 847        let font_cache = cx.font_cache();
 848
 849        let tab_size = 4;
 850        let family_id = font_cache.load_family(&["Courier"]).unwrap();
 851        let font_id = font_cache
 852            .select_font(family_id, &Default::default())
 853            .unwrap();
 854        let font_size = 16.0;
 855
 856        let map = cx
 857            .add_model(|cx| DisplayMap::new(buffer, tab_size, font_id, font_size, Some(40.0), cx));
 858        assert_eq!(
 859            cx.update(|cx| highlighted_chunks(0..5, &map, &theme, cx)),
 860            [
 861                ("fn \n".to_string(), None),
 862                ("oute\nr".to_string(), Some("fn.name")),
 863                ("() \n{}\n\n".to_string(), None),
 864            ]
 865        );
 866        assert_eq!(
 867            cx.update(|cx| highlighted_chunks(3..5, &map, &theme, cx)),
 868            [("{}\n\n".to_string(), None)]
 869        );
 870
 871        map.update(&mut cx, |map, cx| {
 872            map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], cx)
 873        });
 874        assert_eq!(
 875            cx.update(|cx| highlighted_chunks(1..4, &map, &theme, cx)),
 876            [
 877                ("out".to_string(), Some("fn.name")),
 878                ("\n".to_string(), None),
 879                ("  \nfn ".to_string(), Some("mod.body")),
 880                ("i\n".to_string(), Some("fn.name"))
 881            ]
 882        );
 883    }
 884
 885    #[gpui::test]
 886    fn test_clip_point(cx: &mut gpui::MutableAppContext) {
 887        use Bias::{Left, Right};
 888
 889        let text = "\n'a', 'α',\t'✋',\t'❎', '🍐'\n";
 890        let display_text = "\n'a', 'α',   '✋',    '❎', '🍐'\n";
 891        let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
 892
 893        let tab_size = 4;
 894        let font_cache = cx.font_cache();
 895        let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
 896        let font_id = font_cache
 897            .select_font(family_id, &Default::default())
 898            .unwrap();
 899        let font_size = 14.0;
 900        let map = cx.add_model(|cx| {
 901            DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, None, cx)
 902        });
 903        let map = map.update(cx, |map, cx| map.snapshot(cx));
 904
 905        assert_eq!(map.text(), display_text);
 906        for (input_column, bias, output_column) in vec![
 907            ("'a', '".len(), Left, "'a', '".len()),
 908            ("'a', '".len() + 1, Left, "'a', '".len()),
 909            ("'a', '".len() + 1, Right, "'a', 'α".len()),
 910            ("'a', 'α', ".len(), Left, "'a', 'α',".len()),
 911            ("'a', 'α', ".len(), Right, "'a', 'α',   ".len()),
 912            ("'a', 'α',   '".len() + 1, Left, "'a', 'α',   '".len()),
 913            ("'a', 'α',   '".len() + 1, Right, "'a', 'α',   '✋".len()),
 914            ("'a', 'α',   '✋',".len(), Right, "'a', 'α',   '✋',".len()),
 915            ("'a', 'α',   '✋', ".len(), Left, "'a', 'α',   '✋',".len()),
 916            (
 917                "'a', 'α',   '✋', ".len(),
 918                Right,
 919                "'a', 'α',   '✋',    ".len(),
 920            ),
 921        ] {
 922            assert_eq!(
 923                map.clip_point(DisplayPoint::new(1, input_column as u32), bias),
 924                DisplayPoint::new(1, output_column as u32),
 925                "clip_point(({}, {}))",
 926                1,
 927                input_column,
 928            );
 929        }
 930    }
 931
 932    #[gpui::test]
 933    fn test_tabs_with_multibyte_chars(cx: &mut gpui::MutableAppContext) {
 934        let text = "\t\tα\nβ\t\n🏀β\t\tγ";
 935        let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
 936        let tab_size = 4;
 937        let font_cache = cx.font_cache();
 938        let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
 939        let font_id = font_cache
 940            .select_font(family_id, &Default::default())
 941            .unwrap();
 942        let font_size = 14.0;
 943
 944        let map = cx.add_model(|cx| {
 945            DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, None, cx)
 946        });
 947        let map = map.update(cx, |map, cx| map.snapshot(cx));
 948        assert_eq!(map.text(), "✅       α\nβ   \n🏀β      γ");
 949        assert_eq!(
 950            map.chunks_at(0).collect::<String>(),
 951            "✅       α\nβ   \n🏀β      γ"
 952        );
 953        assert_eq!(map.chunks_at(1).collect::<String>(), "β   \n🏀β      γ");
 954        assert_eq!(map.chunks_at(2).collect::<String>(), "🏀β      γ");
 955
 956        let point = Point::new(0, "\t\t".len() as u32);
 957        let display_point = DisplayPoint::new(0, "".len() as u32);
 958        assert_eq!(point.to_display_point(&map, Left), display_point);
 959        assert_eq!(display_point.to_buffer_point(&map, Left), point,);
 960
 961        let point = Point::new(1, "β\t".len() as u32);
 962        let display_point = DisplayPoint::new(1, "β   ".len() as u32);
 963        assert_eq!(point.to_display_point(&map, Left), display_point);
 964        assert_eq!(display_point.to_buffer_point(&map, Left), point,);
 965
 966        let point = Point::new(2, "🏀β\t\t".len() as u32);
 967        let display_point = DisplayPoint::new(2, "🏀β      ".len() as u32);
 968        assert_eq!(point.to_display_point(&map, Left), display_point);
 969        assert_eq!(display_point.to_buffer_point(&map, Left), point,);
 970
 971        // Display points inside of expanded tabs
 972        assert_eq!(
 973            DisplayPoint::new(0, "".len() as u32).to_buffer_point(&map, Right),
 974            Point::new(0, "\t\t".len() as u32),
 975        );
 976        assert_eq!(
 977            DisplayPoint::new(0, "".len() as u32).to_buffer_point(&map, Left),
 978            Point::new(0, "\t".len() as u32),
 979        );
 980        assert_eq!(
 981            DisplayPoint::new(0, "".len() as u32).to_buffer_point(&map, Right),
 982            Point::new(0, "\t".len() as u32),
 983        );
 984        assert_eq!(
 985            DisplayPoint::new(0, "".len() as u32).to_buffer_point(&map, Left),
 986            Point::new(0, "".len() as u32),
 987        );
 988
 989        // Clipping display points inside of multi-byte characters
 990        assert_eq!(
 991            map.clip_point(DisplayPoint::new(0, "".len() as u32 - 1), Left),
 992            DisplayPoint::new(0, 0)
 993        );
 994        assert_eq!(
 995            map.clip_point(DisplayPoint::new(0, "".len() as u32 - 1), Bias::Right),
 996            DisplayPoint::new(0, "".len() as u32)
 997        );
 998    }
 999
1000    #[gpui::test]
1001    fn test_max_point(cx: &mut gpui::MutableAppContext) {
1002        let buffer = cx.add_model(|cx| Buffer::new(0, "aaa\n\t\tbbb", cx));
1003        let tab_size = 4;
1004        let font_cache = cx.font_cache();
1005        let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
1006        let font_id = font_cache
1007            .select_font(family_id, &Default::default())
1008            .unwrap();
1009        let font_size = 14.0;
1010        let map = cx.add_model(|cx| {
1011            DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, None, cx)
1012        });
1013        assert_eq!(
1014            map.update(cx, |map, cx| map.snapshot(cx)).max_point(),
1015            DisplayPoint::new(1, 11)
1016        )
1017    }
1018
1019    fn highlighted_chunks<'a>(
1020        rows: Range<u32>,
1021        map: &ModelHandle<DisplayMap>,
1022        theme: &'a SyntaxTheme,
1023        cx: &mut MutableAppContext,
1024    ) -> Vec<(String, Option<&'a str>)> {
1025        let mut snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1026        let mut chunks: Vec<(String, Option<&str>)> = Vec::new();
1027        for chunk in snapshot.highlighted_chunks_for_rows(rows) {
1028            let style_name = chunk.highlight_id.name(theme);
1029            if let Some((last_chunk, last_style_name)) = chunks.last_mut() {
1030                if style_name == *last_style_name {
1031                    last_chunk.push_str(chunk.text);
1032                } else {
1033                    chunks.push((chunk.text.to_string(), style_name));
1034                }
1035            } else {
1036                chunks.push((chunk.text.to_string(), style_name));
1037            }
1038        }
1039        chunks
1040    }
1041}