display_map.rs

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