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 is_block_line(&self, display_row: u32) -> bool {
 306        self.blocks_snapshot.is_block_line(display_row)
 307    }
 308
 309    pub fn soft_wrap_indent(&self, display_row: u32) -> Option<u32> {
 310        let wrap_row = self
 311            .blocks_snapshot
 312            .to_wrap_point(BlockPoint::new(display_row, 0))
 313            .row();
 314        self.wraps_snapshot.soft_wrap_indent(wrap_row)
 315    }
 316
 317    pub fn text(&self) -> String {
 318        self.text_chunks(0).collect()
 319    }
 320
 321    pub fn line(&self, display_row: u32) -> String {
 322        let mut result = String::new();
 323        for chunk in self.text_chunks(display_row) {
 324            if let Some(ix) = chunk.find('\n') {
 325                result.push_str(&chunk[0..ix]);
 326                break;
 327            } else {
 328                result.push_str(chunk);
 329            }
 330        }
 331        result
 332    }
 333
 334    pub fn line_indent(&self, display_row: u32) -> (u32, bool) {
 335        let mut indent = 0;
 336        let mut is_blank = true;
 337        for c in self.chars_at(DisplayPoint::new(display_row, 0)) {
 338            if c == ' ' {
 339                indent += 1;
 340            } else {
 341                is_blank = c == '\n';
 342                break;
 343            }
 344        }
 345        (indent, is_blank)
 346    }
 347
 348    pub fn line_len(&self, row: u32) -> u32 {
 349        self.blocks_snapshot.line_len(row)
 350    }
 351
 352    pub fn longest_row(&self) -> u32 {
 353        self.blocks_snapshot.longest_row()
 354    }
 355}
 356
 357#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
 358pub struct DisplayPoint(BlockPoint);
 359
 360impl DisplayPoint {
 361    pub fn new(row: u32, column: u32) -> Self {
 362        Self(BlockPoint(Point::new(row, column)))
 363    }
 364
 365    pub fn zero() -> Self {
 366        Self::new(0, 0)
 367    }
 368
 369    #[cfg(test)]
 370    pub fn is_zero(&self) -> bool {
 371        self.0.is_zero()
 372    }
 373
 374    pub fn row(self) -> u32 {
 375        self.0.row
 376    }
 377
 378    pub fn column(self) -> u32 {
 379        self.0.column
 380    }
 381
 382    pub fn row_mut(&mut self) -> &mut u32 {
 383        &mut self.0.row
 384    }
 385
 386    pub fn column_mut(&mut self) -> &mut u32 {
 387        &mut self.0.column
 388    }
 389
 390    pub fn to_point(self, map: &DisplayMapSnapshot) -> Point {
 391        map.display_point_to_point(self, Bias::Left)
 392    }
 393
 394    pub fn to_offset(self, map: &DisplayMapSnapshot, bias: Bias) -> usize {
 395        let unblocked_point = map.blocks_snapshot.to_wrap_point(self.0);
 396        let unwrapped_point = map.wraps_snapshot.to_tab_point(unblocked_point);
 397        let unexpanded_point = map.tabs_snapshot.to_fold_point(unwrapped_point, bias).0;
 398        unexpanded_point.to_buffer_offset(&map.folds_snapshot)
 399    }
 400}
 401
 402impl ToDisplayPoint for Point {
 403    fn to_display_point(&self, map: &DisplayMapSnapshot) -> DisplayPoint {
 404        map.point_to_display_point(*self, Bias::Left)
 405    }
 406}
 407
 408impl ToDisplayPoint for Anchor {
 409    fn to_display_point(&self, map: &DisplayMapSnapshot) -> DisplayPoint {
 410        self.to_point(&map.buffer_snapshot).to_display_point(map)
 411    }
 412}
 413
 414#[cfg(test)]
 415mod tests {
 416    use super::*;
 417    use crate::{movement, test::*};
 418    use gpui::{color::Color, MutableAppContext};
 419    use language::{Language, LanguageConfig, RandomCharIter, SelectionGoal};
 420    use rand::{prelude::StdRng, Rng};
 421    use std::{env, sync::Arc};
 422    use theme::SyntaxTheme;
 423    use Bias::*;
 424
 425    #[gpui::test(iterations = 100)]
 426    async fn test_random(mut cx: gpui::TestAppContext, mut rng: StdRng) {
 427        cx.foreground().set_block_on_ticks(0..=50);
 428        cx.foreground().forbid_parking();
 429        let operations = env::var("OPERATIONS")
 430            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
 431            .unwrap_or(10);
 432
 433        let font_cache = cx.font_cache().clone();
 434        let tab_size = rng.gen_range(1..=4);
 435        let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
 436        let font_id = font_cache
 437            .select_font(family_id, &Default::default())
 438            .unwrap();
 439        let font_size = 14.0;
 440        let max_wrap_width = 300.0;
 441        let mut wrap_width = if rng.gen_bool(0.1) {
 442            None
 443        } else {
 444            Some(rng.gen_range(0.0..=max_wrap_width))
 445        };
 446
 447        log::info!("tab size: {}", tab_size);
 448        log::info!("wrap width: {:?}", wrap_width);
 449
 450        let buffer = cx.add_model(|cx| {
 451            let len = rng.gen_range(0..10);
 452            let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
 453            Buffer::new(0, text, cx)
 454        });
 455
 456        let map = cx.add_model(|cx| {
 457            DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, wrap_width, cx)
 458        });
 459        let (_observer, notifications) = Observer::new(&map, &mut cx);
 460        let mut fold_count = 0;
 461
 462        for _i in 0..operations {
 463            match rng.gen_range(0..100) {
 464                0..=19 => {
 465                    wrap_width = if rng.gen_bool(0.2) {
 466                        None
 467                    } else {
 468                        Some(rng.gen_range(0.0..=max_wrap_width))
 469                    };
 470                    log::info!("setting wrap width to {:?}", wrap_width);
 471                    map.update(&mut cx, |map, cx| map.set_wrap_width(wrap_width, cx));
 472                }
 473                20..=80 => {
 474                    let mut ranges = Vec::new();
 475                    for _ in 0..rng.gen_range(1..=3) {
 476                        buffer.read_with(&cx, |buffer, _| {
 477                            let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right);
 478                            let start = buffer.clip_offset(rng.gen_range(0..=end), Left);
 479                            ranges.push(start..end);
 480                        });
 481                    }
 482
 483                    if rng.gen() && fold_count > 0 {
 484                        log::info!("unfolding ranges: {:?}", ranges);
 485                        map.update(&mut cx, |map, cx| {
 486                            map.unfold(ranges, cx);
 487                        });
 488                    } else {
 489                        log::info!("folding ranges: {:?}", ranges);
 490                        map.update(&mut cx, |map, cx| {
 491                            map.fold(ranges, cx);
 492                        });
 493                    }
 494                }
 495                _ => {
 496                    buffer.update(&mut cx, |buffer, _| buffer.randomly_edit(&mut rng, 5));
 497                }
 498            }
 499
 500            if map.read_with(&cx, |map, cx| map.is_rewrapping(cx)) {
 501                notifications.recv().await.unwrap();
 502            }
 503
 504            let snapshot = map.update(&mut cx, |map, cx| map.snapshot(cx));
 505            fold_count = snapshot.fold_count();
 506            log::info!("buffer text: {:?}", buffer.read_with(&cx, |b, _| b.text()));
 507            log::info!("display text: {:?}", snapshot.text());
 508
 509            // Line boundaries
 510            for _ in 0..5 {
 511                let row = rng.gen_range(0..=snapshot.max_point().row());
 512                let column = rng.gen_range(0..=snapshot.line_len(row));
 513                let point = snapshot.clip_point(DisplayPoint::new(row, column), Left);
 514
 515                let (prev_display_bound, prev_buffer_bound) = snapshot.prev_row_boundary(point);
 516                let (next_display_bound, next_buffer_bound) = snapshot.next_row_boundary(point);
 517
 518                assert!(prev_display_bound <= point);
 519                assert!(next_display_bound >= point);
 520                assert_eq!(prev_buffer_bound.column, 0);
 521                assert_eq!(prev_display_bound.column(), 0);
 522                if next_display_bound < snapshot.max_point() {
 523                    assert_eq!(
 524                        buffer
 525                            .read_with(&cx, |buffer, _| buffer.chars_at(next_buffer_bound).next()),
 526                        Some('\n')
 527                    )
 528                }
 529
 530                assert_eq!(
 531                    prev_display_bound,
 532                    prev_buffer_bound.to_display_point(&snapshot),
 533                    "row boundary before {:?}. reported buffer row boundary: {:?}",
 534                    point,
 535                    prev_buffer_bound
 536                );
 537                assert_eq!(
 538                    next_display_bound,
 539                    next_buffer_bound.to_display_point(&snapshot),
 540                    "display row boundary after {:?}. reported buffer row boundary: {:?}",
 541                    point,
 542                    next_buffer_bound
 543                );
 544                assert_eq!(
 545                    prev_buffer_bound,
 546                    prev_display_bound.to_point(&snapshot),
 547                    "row boundary before {:?}. reported display row boundary: {:?}",
 548                    point,
 549                    prev_display_bound
 550                );
 551                assert_eq!(
 552                    next_buffer_bound,
 553                    next_display_bound.to_point(&snapshot),
 554                    "row boundary after {:?}. reported display row boundary: {:?}",
 555                    point,
 556                    next_display_bound
 557                );
 558            }
 559
 560            // Movement
 561            for _ in 0..5 {
 562                let row = rng.gen_range(0..=snapshot.max_point().row());
 563                let column = rng.gen_range(0..=snapshot.line_len(row));
 564                let point = snapshot.clip_point(DisplayPoint::new(row, column), Left);
 565
 566                log::info!("Moving from point {:?}", point);
 567
 568                let moved_right = movement::right(&snapshot, point).unwrap();
 569                log::info!("Right {:?}", moved_right);
 570                if point < snapshot.max_point() {
 571                    assert!(moved_right > point);
 572                    if point.column() == snapshot.line_len(point.row())
 573                        || snapshot.soft_wrap_indent(point.row()).is_some()
 574                            && point.column() == snapshot.line_len(point.row()) - 1
 575                    {
 576                        assert!(moved_right.row() > point.row());
 577                    }
 578                } else {
 579                    assert_eq!(moved_right, point);
 580                }
 581
 582                let moved_left = movement::left(&snapshot, point).unwrap();
 583                log::info!("Left {:?}", moved_left);
 584                if !point.is_zero() {
 585                    assert!(moved_left < point);
 586                    if point.column() == 0 {
 587                        assert!(moved_left.row() < point.row());
 588                    }
 589                } else {
 590                    assert!(moved_left.is_zero());
 591                }
 592            }
 593        }
 594    }
 595
 596    #[gpui::test]
 597    fn test_soft_wraps(cx: &mut MutableAppContext) {
 598        cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
 599        cx.foreground().forbid_parking();
 600
 601        let font_cache = cx.font_cache();
 602
 603        let tab_size = 4;
 604        let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
 605        let font_id = font_cache
 606            .select_font(family_id, &Default::default())
 607            .unwrap();
 608        let font_size = 12.0;
 609        let wrap_width = Some(64.);
 610
 611        let text = "one two three four five\nsix seven eight";
 612        let buffer = cx.add_model(|cx| Buffer::new(0, text.to_string(), cx));
 613        let map = cx.add_model(|cx| {
 614            DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, wrap_width, cx)
 615        });
 616
 617        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
 618        assert_eq!(
 619            snapshot.text_chunks(0).collect::<String>(),
 620            "one two \nthree four \nfive\nsix seven \neight"
 621        );
 622        assert_eq!(
 623            snapshot.clip_point(DisplayPoint::new(0, 8), Bias::Left),
 624            DisplayPoint::new(0, 7)
 625        );
 626        assert_eq!(
 627            snapshot.clip_point(DisplayPoint::new(0, 8), Bias::Right),
 628            DisplayPoint::new(1, 0)
 629        );
 630        assert_eq!(
 631            movement::right(&snapshot, DisplayPoint::new(0, 7)).unwrap(),
 632            DisplayPoint::new(1, 0)
 633        );
 634        assert_eq!(
 635            movement::left(&snapshot, DisplayPoint::new(1, 0)).unwrap(),
 636            DisplayPoint::new(0, 7)
 637        );
 638        assert_eq!(
 639            movement::up(&snapshot, DisplayPoint::new(1, 10), SelectionGoal::None).unwrap(),
 640            (DisplayPoint::new(0, 7), SelectionGoal::Column(10))
 641        );
 642        assert_eq!(
 643            movement::down(
 644                &snapshot,
 645                DisplayPoint::new(0, 7),
 646                SelectionGoal::Column(10)
 647            )
 648            .unwrap(),
 649            (DisplayPoint::new(1, 10), SelectionGoal::Column(10))
 650        );
 651        assert_eq!(
 652            movement::down(
 653                &snapshot,
 654                DisplayPoint::new(1, 10),
 655                SelectionGoal::Column(10)
 656            )
 657            .unwrap(),
 658            (DisplayPoint::new(2, 4), SelectionGoal::Column(10))
 659        );
 660
 661        buffer.update(cx, |buffer, cx| {
 662            let ix = buffer.text().find("seven").unwrap();
 663            buffer.edit(vec![ix..ix], "and ", cx);
 664        });
 665
 666        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
 667        assert_eq!(
 668            snapshot.text_chunks(1).collect::<String>(),
 669            "three four \nfive\nsix and \nseven eight"
 670        );
 671
 672        // Re-wrap on font size changes
 673        map.update(cx, |map, cx| map.set_font(font_id, font_size + 3., cx));
 674
 675        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
 676        assert_eq!(
 677            snapshot.text_chunks(1).collect::<String>(),
 678            "three \nfour five\nsix and \nseven \neight"
 679        )
 680    }
 681
 682    #[gpui::test]
 683    fn test_text_chunks(cx: &mut gpui::MutableAppContext) {
 684        let text = sample_text(6, 6);
 685        let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
 686        let tab_size = 4;
 687        let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap();
 688        let font_id = cx
 689            .font_cache()
 690            .select_font(family_id, &Default::default())
 691            .unwrap();
 692        let font_size = 14.0;
 693        let map = cx.add_model(|cx| {
 694            DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, None, cx)
 695        });
 696        buffer.update(cx, |buffer, cx| {
 697            buffer.edit(
 698                vec![
 699                    Point::new(1, 0)..Point::new(1, 0),
 700                    Point::new(1, 1)..Point::new(1, 1),
 701                    Point::new(2, 1)..Point::new(2, 1),
 702                ],
 703                "\t",
 704                cx,
 705            )
 706        });
 707
 708        assert_eq!(
 709            map.update(cx, |map, cx| map.snapshot(cx))
 710                .text_chunks(1)
 711                .collect::<String>()
 712                .lines()
 713                .next(),
 714            Some("    b   bbbbb")
 715        );
 716        assert_eq!(
 717            map.update(cx, |map, cx| map.snapshot(cx))
 718                .text_chunks(2)
 719                .collect::<String>()
 720                .lines()
 721                .next(),
 722            Some("c   ccccc")
 723        );
 724    }
 725
 726    #[gpui::test]
 727    async fn test_chunks(mut cx: gpui::TestAppContext) {
 728        use unindent::Unindent as _;
 729
 730        let text = r#"
 731            fn outer() {}
 732
 733            mod module {
 734                fn inner() {}
 735            }"#
 736        .unindent();
 737
 738        let theme = SyntaxTheme::new(vec![
 739            ("mod.body".to_string(), Color::from_u32(0xff0000ff).into()),
 740            ("fn.name".to_string(), Color::from_u32(0x00ff00ff).into()),
 741        ]);
 742        let lang = Arc::new(
 743            Language::new(
 744                LanguageConfig {
 745                    name: "Test".to_string(),
 746                    path_suffixes: vec![".test".to_string()],
 747                    ..Default::default()
 748                },
 749                tree_sitter_rust::language(),
 750            )
 751            .with_highlights_query(
 752                r#"
 753                (mod_item name: (identifier) body: _ @mod.body)
 754                (function_item name: (identifier) @fn.name)
 755                "#,
 756            )
 757            .unwrap(),
 758        );
 759        lang.set_theme(&theme);
 760
 761        let buffer =
 762            cx.add_model(|cx| Buffer::new(0, text, cx).with_language(Some(lang), None, cx));
 763        buffer.condition(&cx, |buf, _| !buf.is_parsing()).await;
 764
 765        let tab_size = 2;
 766        let font_cache = cx.font_cache();
 767        let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
 768        let font_id = font_cache
 769            .select_font(family_id, &Default::default())
 770            .unwrap();
 771        let font_size = 14.0;
 772
 773        let map =
 774            cx.add_model(|cx| DisplayMap::new(buffer, tab_size, font_id, font_size, None, cx));
 775        assert_eq!(
 776            cx.update(|cx| chunks(0..5, &map, &theme, cx)),
 777            vec![
 778                ("fn ".to_string(), None),
 779                ("outer".to_string(), Some("fn.name")),
 780                ("() {}\n\nmod module ".to_string(), None),
 781                ("{\n    fn ".to_string(), Some("mod.body")),
 782                ("inner".to_string(), Some("fn.name")),
 783                ("() {}\n}".to_string(), Some("mod.body")),
 784            ]
 785        );
 786        assert_eq!(
 787            cx.update(|cx| chunks(3..5, &map, &theme, cx)),
 788            vec![
 789                ("    fn ".to_string(), Some("mod.body")),
 790                ("inner".to_string(), Some("fn.name")),
 791                ("() {}\n}".to_string(), Some("mod.body")),
 792            ]
 793        );
 794
 795        map.update(&mut cx, |map, cx| {
 796            map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], cx)
 797        });
 798        assert_eq!(
 799            cx.update(|cx| chunks(0..2, &map, &theme, cx)),
 800            vec![
 801                ("fn ".to_string(), None),
 802                ("out".to_string(), Some("fn.name")),
 803                ("".to_string(), None),
 804                ("  fn ".to_string(), Some("mod.body")),
 805                ("inner".to_string(), Some("fn.name")),
 806                ("() {}\n}".to_string(), Some("mod.body")),
 807            ]
 808        );
 809    }
 810
 811    #[gpui::test]
 812    async fn test_chunks_with_soft_wrapping(mut cx: gpui::TestAppContext) {
 813        use unindent::Unindent as _;
 814
 815        cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
 816
 817        let text = r#"
 818            fn outer() {}
 819
 820            mod module {
 821                fn inner() {}
 822            }"#
 823        .unindent();
 824
 825        let theme = SyntaxTheme::new(vec![
 826            ("mod.body".to_string(), Color::from_u32(0xff0000ff).into()),
 827            ("fn.name".to_string(), Color::from_u32(0x00ff00ff).into()),
 828        ]);
 829        let lang = Arc::new(
 830            Language::new(
 831                LanguageConfig {
 832                    name: "Test".to_string(),
 833                    path_suffixes: vec![".test".to_string()],
 834                    ..Default::default()
 835                },
 836                tree_sitter_rust::language(),
 837            )
 838            .with_highlights_query(
 839                r#"
 840                (mod_item name: (identifier) body: _ @mod.body)
 841                (function_item name: (identifier) @fn.name)
 842                "#,
 843            )
 844            .unwrap(),
 845        );
 846        lang.set_theme(&theme);
 847
 848        let buffer =
 849            cx.add_model(|cx| Buffer::new(0, text, cx).with_language(Some(lang), None, cx));
 850        buffer.condition(&cx, |buf, _| !buf.is_parsing()).await;
 851
 852        let font_cache = cx.font_cache();
 853
 854        let tab_size = 4;
 855        let family_id = font_cache.load_family(&["Courier"]).unwrap();
 856        let font_id = font_cache
 857            .select_font(family_id, &Default::default())
 858            .unwrap();
 859        let font_size = 16.0;
 860
 861        let map = cx
 862            .add_model(|cx| DisplayMap::new(buffer, tab_size, font_id, font_size, Some(40.0), cx));
 863        assert_eq!(
 864            cx.update(|cx| chunks(0..5, &map, &theme, cx)),
 865            [
 866                ("fn \n".to_string(), None),
 867                ("oute\nr".to_string(), Some("fn.name")),
 868                ("() \n{}\n\n".to_string(), None),
 869            ]
 870        );
 871        assert_eq!(
 872            cx.update(|cx| chunks(3..5, &map, &theme, cx)),
 873            [("{}\n\n".to_string(), None)]
 874        );
 875
 876        map.update(&mut cx, |map, cx| {
 877            map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], cx)
 878        });
 879        assert_eq!(
 880            cx.update(|cx| chunks(1..4, &map, &theme, cx)),
 881            [
 882                ("out".to_string(), Some("fn.name")),
 883                ("\n".to_string(), None),
 884                ("  \nfn ".to_string(), Some("mod.body")),
 885                ("i\n".to_string(), Some("fn.name"))
 886            ]
 887        );
 888    }
 889
 890    #[gpui::test]
 891    fn test_clip_point(cx: &mut gpui::MutableAppContext) {
 892        use Bias::{Left, Right};
 893
 894        let text = "\n'a', 'α',\t'✋',\t'❎', '🍐'\n";
 895        let display_text = "\n'a', 'α',   '✋',    '❎', '🍐'\n";
 896        let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
 897
 898        let tab_size = 4;
 899        let font_cache = cx.font_cache();
 900        let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
 901        let font_id = font_cache
 902            .select_font(family_id, &Default::default())
 903            .unwrap();
 904        let font_size = 14.0;
 905        let map = cx.add_model(|cx| {
 906            DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, None, cx)
 907        });
 908        let map = map.update(cx, |map, cx| map.snapshot(cx));
 909
 910        assert_eq!(map.text(), display_text);
 911        for (input_column, bias, output_column) in vec![
 912            ("'a', '".len(), Left, "'a', '".len()),
 913            ("'a', '".len() + 1, Left, "'a', '".len()),
 914            ("'a', '".len() + 1, Right, "'a', 'α".len()),
 915            ("'a', 'α', ".len(), Left, "'a', 'α',".len()),
 916            ("'a', 'α', ".len(), Right, "'a', 'α',   ".len()),
 917            ("'a', 'α',   '".len() + 1, Left, "'a', 'α',   '".len()),
 918            ("'a', 'α',   '".len() + 1, Right, "'a', 'α',   '✋".len()),
 919            ("'a', 'α',   '✋',".len(), Right, "'a', 'α',   '✋',".len()),
 920            ("'a', 'α',   '✋', ".len(), Left, "'a', 'α',   '✋',".len()),
 921            (
 922                "'a', 'α',   '✋', ".len(),
 923                Right,
 924                "'a', 'α',   '✋',    ".len(),
 925            ),
 926        ] {
 927            assert_eq!(
 928                map.clip_point(DisplayPoint::new(1, input_column as u32), bias),
 929                DisplayPoint::new(1, output_column as u32),
 930                "clip_point(({}, {}))",
 931                1,
 932                input_column,
 933            );
 934        }
 935    }
 936
 937    #[gpui::test]
 938    fn test_tabs_with_multibyte_chars(cx: &mut gpui::MutableAppContext) {
 939        let text = "\t\tα\nβ\t\n🏀β\t\tγ";
 940        let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
 941        let tab_size = 4;
 942        let font_cache = cx.font_cache();
 943        let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
 944        let font_id = font_cache
 945            .select_font(family_id, &Default::default())
 946            .unwrap();
 947        let font_size = 14.0;
 948
 949        let map = cx.add_model(|cx| {
 950            DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, None, cx)
 951        });
 952        let map = map.update(cx, |map, cx| map.snapshot(cx));
 953        assert_eq!(map.text(), "✅       α\nβ   \n🏀β      γ");
 954        assert_eq!(
 955            map.text_chunks(0).collect::<String>(),
 956            "✅       α\nβ   \n🏀β      γ"
 957        );
 958        assert_eq!(map.text_chunks(1).collect::<String>(), "β   \n🏀β      γ");
 959        assert_eq!(map.text_chunks(2).collect::<String>(), "🏀β      γ");
 960
 961        let point = Point::new(0, "\t\t".len() as u32);
 962        let display_point = DisplayPoint::new(0, "".len() as u32);
 963        assert_eq!(point.to_display_point(&map), display_point);
 964        assert_eq!(display_point.to_point(&map), point);
 965
 966        let point = Point::new(1, "β\t".len() as u32);
 967        let display_point = DisplayPoint::new(1, "β   ".len() as u32);
 968        assert_eq!(point.to_display_point(&map), display_point);
 969        assert_eq!(display_point.to_point(&map), point,);
 970
 971        let point = Point::new(2, "🏀β\t\t".len() as u32);
 972        let display_point = DisplayPoint::new(2, "🏀β      ".len() as u32);
 973        assert_eq!(point.to_display_point(&map), display_point);
 974        assert_eq!(display_point.to_point(&map), point,);
 975
 976        // Display points inside of expanded tabs
 977        assert_eq!(
 978            DisplayPoint::new(0, "".len() as u32).to_point(&map),
 979            Point::new(0, "\t".len() as u32),
 980        );
 981        assert_eq!(
 982            DisplayPoint::new(0, "".len() as u32).to_point(&map),
 983            Point::new(0, "".len() as u32),
 984        );
 985
 986        // Clipping display points inside of multi-byte characters
 987        assert_eq!(
 988            map.clip_point(DisplayPoint::new(0, "".len() as u32 - 1), Left),
 989            DisplayPoint::new(0, 0)
 990        );
 991        assert_eq!(
 992            map.clip_point(DisplayPoint::new(0, "".len() as u32 - 1), Bias::Right),
 993            DisplayPoint::new(0, "".len() as u32)
 994        );
 995    }
 996
 997    #[gpui::test]
 998    fn test_max_point(cx: &mut gpui::MutableAppContext) {
 999        let buffer = cx.add_model(|cx| Buffer::new(0, "aaa\n\t\tbbb", cx));
1000        let tab_size = 4;
1001        let font_cache = cx.font_cache();
1002        let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
1003        let font_id = font_cache
1004            .select_font(family_id, &Default::default())
1005            .unwrap();
1006        let font_size = 14.0;
1007        let map = cx.add_model(|cx| {
1008            DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, None, cx)
1009        });
1010        assert_eq!(
1011            map.update(cx, |map, cx| map.snapshot(cx)).max_point(),
1012            DisplayPoint::new(1, 11)
1013        )
1014    }
1015
1016    fn chunks<'a>(
1017        rows: Range<u32>,
1018        map: &ModelHandle<DisplayMap>,
1019        theme: &'a SyntaxTheme,
1020        cx: &mut MutableAppContext,
1021    ) -> Vec<(String, Option<&'a str>)> {
1022        let mut snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1023        let mut chunks: Vec<(String, Option<&str>)> = Vec::new();
1024        for chunk in snapshot.chunks(rows) {
1025            let style_name = chunk.highlight_id.name(theme);
1026            if let Some((last_chunk, last_style_name)) = chunks.last_mut() {
1027                if style_name == *last_style_name {
1028                    last_chunk.push_str(chunk.text);
1029                } else {
1030                    chunks.push((chunk.text.to_string(), style_name));
1031                }
1032            } else {
1033                chunks.push((chunk.text.to_string(), style_name));
1034            }
1035        }
1036        chunks
1037    }
1038}