display_map.rs

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