display_map.rs

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