block_map.rs

   1use super::wrap_map::{self, Edit as WrapEdit, Snapshot as WrapSnapshot, WrapPoint};
   2use buffer::{rope, Anchor, Bias, Edit, Point, Rope, ToOffset, ToPoint as _};
   3use gpui::{fonts::HighlightStyle, AppContext, ModelHandle};
   4use language::{Buffer, HighlightedChunk};
   5use parking_lot::Mutex;
   6use std::{
   7    cmp::{self, Ordering},
   8    collections::HashSet,
   9    iter,
  10    ops::Range,
  11    slice,
  12    sync::{
  13        atomic::{AtomicUsize, Ordering::SeqCst},
  14        Arc,
  15    },
  16};
  17use sum_tree::SumTree;
  18
  19pub struct BlockMap {
  20    buffer: ModelHandle<Buffer>,
  21    next_block_id: AtomicUsize,
  22    wrap_snapshot: Mutex<WrapSnapshot>,
  23    blocks: Vec<Arc<Block>>,
  24    transforms: Mutex<SumTree<Transform>>,
  25}
  26
  27pub struct BlockMapWriter<'a>(&'a mut BlockMap);
  28
  29pub struct BlockSnapshot {
  30    wrap_snapshot: WrapSnapshot,
  31    transforms: SumTree<Transform>,
  32}
  33
  34#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
  35pub struct BlockId(usize);
  36
  37#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
  38pub struct BlockPoint(pub super::Point);
  39
  40#[derive(Debug)]
  41struct Block {
  42    id: BlockId,
  43    position: Anchor,
  44    text: Rope,
  45    runs: Vec<(usize, HighlightStyle)>,
  46    disposition: BlockDisposition,
  47}
  48
  49#[derive(Clone)]
  50pub struct BlockProperties<P, T>
  51where
  52    P: Clone,
  53    T: Clone,
  54{
  55    position: P,
  56    text: T,
  57    runs: Vec<(usize, HighlightStyle)>,
  58    disposition: BlockDisposition,
  59}
  60
  61#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
  62enum BlockDisposition {
  63    Above,
  64    Below,
  65}
  66
  67#[derive(Clone, Debug)]
  68struct Transform {
  69    summary: TransformSummary,
  70    block: Option<Arc<Block>>,
  71}
  72
  73#[derive(Clone, Debug, Default)]
  74struct TransformSummary {
  75    input: Point,
  76    output: Point,
  77}
  78
  79pub struct HighlightedChunks<'a> {
  80    transforms: sum_tree::Cursor<'a, Transform, (BlockPoint, WrapPoint)>,
  81    input_chunks: wrap_map::HighlightedChunks<'a>,
  82    input_chunk: HighlightedChunk<'a>,
  83    block_chunks: Option<BlockChunks<'a>>,
  84    output_position: BlockPoint,
  85    max_output_position: BlockPoint,
  86}
  87
  88struct BlockChunks<'a> {
  89    chunks: rope::Chunks<'a>,
  90    runs: iter::Peekable<slice::Iter<'a, (usize, HighlightStyle)>>,
  91    chunk: Option<&'a str>,
  92    run_start: usize,
  93    offset: usize,
  94}
  95
  96impl BlockMap {
  97    pub fn new(buffer: ModelHandle<Buffer>, wrap_snapshot: WrapSnapshot) -> Self {
  98        Self {
  99            buffer,
 100            next_block_id: AtomicUsize::new(0),
 101            blocks: Vec::new(),
 102            transforms: Mutex::new(SumTree::from_item(
 103                Transform::isomorphic(wrap_snapshot.text_summary().lines),
 104                &(),
 105            )),
 106            wrap_snapshot: Mutex::new(wrap_snapshot),
 107        }
 108    }
 109
 110    pub fn read(
 111        &self,
 112        wrap_snapshot: WrapSnapshot,
 113        edits: Vec<WrapEdit>,
 114        cx: &AppContext,
 115    ) -> BlockSnapshot {
 116        self.sync(&wrap_snapshot, edits, cx);
 117        *self.wrap_snapshot.lock() = wrap_snapshot.clone();
 118        BlockSnapshot {
 119            wrap_snapshot,
 120            transforms: self.transforms.lock().clone(),
 121        }
 122    }
 123
 124    pub fn write(
 125        &mut self,
 126        wrap_snapshot: WrapSnapshot,
 127        edits: Vec<WrapEdit>,
 128        cx: &AppContext,
 129    ) -> BlockMapWriter {
 130        self.sync(&wrap_snapshot, edits, cx);
 131        *self.wrap_snapshot.lock() = wrap_snapshot;
 132        BlockMapWriter(self)
 133    }
 134
 135    pub fn sync(&self, wrap_snapshot: &WrapSnapshot, edits: Vec<WrapEdit>, cx: &AppContext) {
 136        if edits.is_empty() {
 137            return;
 138        }
 139
 140        let buffer = self.buffer.read(cx);
 141        let mut transforms = self.transforms.lock();
 142        let mut new_transforms = SumTree::new();
 143        let old_max_point = WrapPoint(transforms.summary().input);
 144        let new_max_point = wrap_snapshot.max_point();
 145        let mut cursor = transforms.cursor::<WrapPoint>();
 146        let mut last_block_ix = 0;
 147        let mut blocks_in_edit = Vec::new();
 148        let mut edits = edits.into_iter().peekable();
 149
 150        while let Some(edit) = edits.next() {
 151            // Preserve any old transforms that precede this edit.
 152            let old_start = WrapPoint::new(edit.old.start, 0);
 153            let new_start = WrapPoint::new(edit.new.start, 0);
 154            new_transforms.push_tree(cursor.slice(&old_start, Bias::Left, &()), &());
 155
 156            // Preserve any portion of an old transform that precedes this edit.
 157            let extent_before_edit = old_start.0 - cursor.start().0;
 158            if !extent_before_edit.is_zero() {
 159                push_isomorphic(&mut new_transforms, extent_before_edit);
 160            }
 161
 162            // Skip over any old transforms that intersect this edit.
 163            let mut old_end = WrapPoint::new(edit.old.end, 0);
 164            let mut new_end = WrapPoint::new(edit.new.end, 0);
 165            cursor.seek(&old_end, Bias::Left, &());
 166            cursor.next(&());
 167
 168            // Combine this edit with any subsequent edits that intersect the same transform.
 169            while let Some(next_edit) = edits.peek() {
 170                if next_edit.old.start <= cursor.start().row() {
 171                    old_end = WrapPoint::new(next_edit.old.end, 0);
 172                    new_end = WrapPoint::new(next_edit.new.end, 0);
 173                    cursor.seek(&old_end, Bias::Left, &());
 174                    cursor.next(&());
 175                    edits.next();
 176                } else {
 177                    break;
 178                }
 179            }
 180
 181            // Find the blocks within this edited region.
 182            let new_start = wrap_snapshot.to_point(new_start, Bias::Left);
 183            let start_anchor = buffer.anchor_before(new_start);
 184            let start_block_ix = match self.blocks[last_block_ix..].binary_search_by(|probe| {
 185                probe
 186                    .position
 187                    .cmp(&start_anchor, buffer)
 188                    .unwrap()
 189                    .then(Ordering::Greater)
 190            }) {
 191                Ok(ix) | Err(ix) => last_block_ix + ix,
 192            };
 193            let end_block_ix = if new_end.row() > wrap_snapshot.max_point().row() {
 194                self.blocks.len()
 195            } else {
 196                let new_end = wrap_snapshot.to_point(new_end, Bias::Left);
 197                let end_anchor = buffer.anchor_before(new_end);
 198                match self.blocks[start_block_ix..].binary_search_by(|probe| {
 199                    probe
 200                        .position
 201                        .cmp(&end_anchor, buffer)
 202                        .unwrap()
 203                        .then(Ordering::Greater)
 204                }) {
 205                    Ok(ix) | Err(ix) => start_block_ix + ix,
 206                }
 207            };
 208            last_block_ix = end_block_ix;
 209            blocks_in_edit.clear();
 210            blocks_in_edit.extend(
 211                self.blocks[start_block_ix..end_block_ix]
 212                    .iter()
 213                    .map(|block| {
 214                        let mut position = block.position.to_point(buffer);
 215                        match block.disposition {
 216                            BlockDisposition::Above => position.column = 0,
 217                            BlockDisposition::Below => {
 218                                position.column = buffer.line_len(position.row)
 219                            }
 220                        }
 221                        let position = wrap_snapshot.from_point(position, Bias::Left);
 222                        (position.row(), block)
 223                    }),
 224            );
 225            blocks_in_edit.sort_unstable_by_key(|(row, block)| (*row, block.disposition, block.id));
 226
 227            // For each of these blocks, insert a new isomorphic transform preceding the block,
 228            // and then insert the block itself.
 229            for (block_row, block) in blocks_in_edit.iter().copied() {
 230                let new_transforms_end = new_transforms.summary().input;
 231                if block.disposition.is_above() {
 232                    if block_row > new_transforms_end.row {
 233                        push_isomorphic(
 234                            &mut new_transforms,
 235                            Point::new(block_row, 0) - new_transforms_end,
 236                        )
 237                    }
 238                } else {
 239                    if block_row >= new_transforms_end.row {
 240                        push_isomorphic(
 241                            &mut new_transforms,
 242                            Point::new(block_row, wrap_snapshot.line_len(block_row))
 243                                - new_transforms_end,
 244                        );
 245                    }
 246                }
 247
 248                new_transforms.push(Transform::block(block.clone()), &());
 249            }
 250
 251            old_end = old_end.min(old_max_point);
 252            new_end = new_end.min(new_max_point);
 253
 254            // Insert an isomorphic transform after the final block.
 255            let extent_after_last_block = new_end.0 - new_transforms.summary().input;
 256            if !extent_after_last_block.is_zero() {
 257                push_isomorphic(&mut new_transforms, extent_after_last_block);
 258            }
 259
 260            // Preserve any portion of the old transform after this edit.
 261            let extent_after_edit = cursor.start().0 - old_end.0;
 262            if !extent_after_edit.is_zero() {
 263                push_isomorphic(&mut new_transforms, extent_after_edit);
 264            }
 265        }
 266
 267        new_transforms.push_tree(cursor.suffix(&()), &());
 268        debug_assert_eq!(new_transforms.summary().input, wrap_snapshot.max_point().0);
 269
 270        drop(cursor);
 271        *transforms = new_transforms;
 272    }
 273}
 274
 275fn push_isomorphic(tree: &mut SumTree<Transform>, extent: Point) {
 276    let mut extent = Some(extent);
 277    tree.update_last(
 278        |last_transform| {
 279            if last_transform.is_isomorphic() {
 280                let extent = extent.take().unwrap();
 281                last_transform.summary.input += &extent;
 282                last_transform.summary.output += &extent;
 283            }
 284        },
 285        &(),
 286    );
 287    if let Some(extent) = extent {
 288        tree.push(Transform::isomorphic(extent), &());
 289    }
 290}
 291
 292impl BlockPoint {
 293    fn new(row: u32, column: u32) -> Self {
 294        Self(Point::new(row, column))
 295    }
 296}
 297
 298impl std::ops::Deref for BlockPoint {
 299    type Target = Point;
 300
 301    fn deref(&self) -> &Self::Target {
 302        &self.0
 303    }
 304}
 305
 306impl std::ops::DerefMut for BlockPoint {
 307    fn deref_mut(&mut self) -> &mut Self::Target {
 308        &mut self.0
 309    }
 310}
 311
 312impl<'a> BlockMapWriter<'a> {
 313    pub fn insert<P, T>(
 314        &mut self,
 315        blocks: impl IntoIterator<Item = BlockProperties<P, T>>,
 316        cx: &AppContext,
 317    ) -> Vec<BlockId>
 318    where
 319        P: ToOffset + Clone,
 320        T: Into<Rope> + Clone,
 321    {
 322        let buffer = self.0.buffer.read(cx);
 323        let mut ids = Vec::new();
 324        let mut edits = Vec::<Edit<u32>>::new();
 325        let wrap_snapshot = &*self.0.wrap_snapshot.lock();
 326
 327        for block in blocks {
 328            let id = BlockId(self.0.next_block_id.fetch_add(1, SeqCst));
 329            ids.push(id);
 330
 331            let position = buffer.anchor_before(block.position);
 332            let point = position.to_point(buffer);
 333            let start_row = wrap_snapshot
 334                .from_point(Point::new(point.row, 0), Bias::Left)
 335                .row();
 336            let end_row = if point.row == buffer.max_point().row {
 337                wrap_snapshot.max_point().row() + 1
 338            } else {
 339                wrap_snapshot
 340                    .from_point(Point::new(point.row + 1, 0), Bias::Left)
 341                    .row()
 342            };
 343
 344            let block_ix = match self
 345                .0
 346                .blocks
 347                .binary_search_by(|probe| probe.position.cmp(&position, buffer).unwrap())
 348            {
 349                Ok(ix) | Err(ix) => ix,
 350            };
 351            let mut text = block.text.into();
 352            if block.disposition.is_above() {
 353                text.push("\n");
 354            } else {
 355                text.push_front("\n");
 356            }
 357
 358            self.0.blocks.insert(
 359                block_ix,
 360                Arc::new(Block {
 361                    id,
 362                    position,
 363                    text,
 364                    runs: block.runs,
 365                    disposition: block.disposition,
 366                }),
 367            );
 368
 369            if let Err(edit_ix) = edits.binary_search_by_key(&start_row, |edit| edit.old.start) {
 370                edits.insert(
 371                    edit_ix,
 372                    Edit {
 373                        old: start_row..end_row,
 374                        new: start_row..end_row,
 375                    },
 376                );
 377            }
 378        }
 379
 380        self.0.sync(wrap_snapshot, edits, cx);
 381        ids
 382    }
 383
 384    pub fn remove(&mut self, block_ids: HashSet<BlockId>, cx: &AppContext) {
 385        let buffer = self.0.buffer.read(cx);
 386        let wrap_snapshot = &*self.0.wrap_snapshot.lock();
 387        let mut edits = Vec::new();
 388        let mut last_block_buffer_row = None;
 389        self.0.blocks.retain(|block| {
 390            if block_ids.contains(&block.id) {
 391                let buffer_row = block.position.to_point(buffer).row;
 392                if last_block_buffer_row != Some(buffer_row) {
 393                    last_block_buffer_row = Some(buffer_row);
 394                    let start_row = wrap_snapshot
 395                        .from_point(Point::new(buffer_row, 0), Bias::Left)
 396                        .row();
 397                    let end_row = wrap_snapshot
 398                        .from_point(
 399                            Point::new(buffer_row, buffer.line_len(buffer_row)),
 400                            Bias::Left,
 401                        )
 402                        .row()
 403                        + 1;
 404                    edits.push(Edit {
 405                        old: start_row..end_row,
 406                        new: start_row..end_row,
 407                    })
 408                }
 409                false
 410            } else {
 411                true
 412            }
 413        });
 414        self.0.sync(wrap_snapshot, edits, cx);
 415    }
 416}
 417
 418impl BlockSnapshot {
 419    #[cfg(test)]
 420    fn text(&mut self) -> String {
 421        self.highlighted_chunks_for_rows(0..self.max_point().0.row + 1)
 422            .map(|chunk| chunk.text)
 423            .collect()
 424    }
 425
 426    pub fn highlighted_chunks_for_rows(&mut self, rows: Range<u32>) -> HighlightedChunks {
 427        let max_output_position = self.max_point().min(BlockPoint::new(rows.end, 0));
 428        let mut cursor = self.transforms.cursor::<(BlockPoint, WrapPoint)>();
 429        let output_position = BlockPoint::new(rows.start, 0);
 430        cursor.seek(&output_position, Bias::Right, &());
 431        let (input_start, output_start) = cursor.start();
 432        let row_overshoot = rows.start - output_start.0.row;
 433        let input_start_row = input_start.0.row + row_overshoot;
 434        let input_end_row = self.to_wrap_point(BlockPoint::new(rows.end, 0)).row();
 435        let input_chunks = self
 436            .wrap_snapshot
 437            .highlighted_chunks_for_rows(input_start_row..input_end_row);
 438        HighlightedChunks {
 439            input_chunks,
 440            input_chunk: Default::default(),
 441            block_chunks: None,
 442            transforms: cursor,
 443            output_position,
 444            max_output_position,
 445        }
 446    }
 447
 448    pub fn max_point(&self) -> BlockPoint {
 449        BlockPoint(self.transforms.summary().output)
 450    }
 451
 452    pub fn clip_point(&self, point: BlockPoint, bias: Bias) -> BlockPoint {
 453        let mut cursor = self.transforms.cursor::<(BlockPoint, WrapPoint)>();
 454        cursor.seek(&point, Bias::Right, &());
 455        if let Some(transform) = cursor.prev_item() {
 456            if transform.is_isomorphic() && point == cursor.start().0 {
 457                return point;
 458            }
 459        }
 460        if let Some(transform) = cursor.item() {
 461            if transform.is_isomorphic() {
 462                let (output_start, input_start) = cursor.start();
 463                let output_overshoot = point.0 - output_start.0;
 464                let input_point = self
 465                    .wrap_snapshot
 466                    .clip_point(WrapPoint(input_start.0 + output_overshoot), bias);
 467                let input_overshoot = input_point.0 - input_start.0;
 468                BlockPoint(output_start.0 + input_overshoot)
 469            } else {
 470                if bias == Bias::Left && cursor.start().1 .0 > Point::zero()
 471                    || cursor.end(&()).1 == self.wrap_snapshot.max_point()
 472                {
 473                    loop {
 474                        cursor.prev(&());
 475                        let transform = cursor.item().unwrap();
 476                        if transform.is_isomorphic() {
 477                            return BlockPoint(cursor.end(&()).0 .0);
 478                        }
 479                    }
 480                } else {
 481                    loop {
 482                        cursor.next(&());
 483                        let transform = cursor.item().unwrap();
 484                        if transform.is_isomorphic() {
 485                            return BlockPoint(cursor.start().0 .0);
 486                        }
 487                    }
 488                }
 489            }
 490        } else {
 491            self.max_point()
 492        }
 493    }
 494
 495    pub fn to_block_point(&self, wrap_point: WrapPoint) -> BlockPoint {
 496        let mut cursor = self.transforms.cursor::<(WrapPoint, BlockPoint)>();
 497        cursor.seek(&wrap_point, Bias::Right, &());
 498        while let Some(item) = cursor.item() {
 499            if item.is_isomorphic() {
 500                break;
 501            }
 502            cursor.next(&());
 503        }
 504        let (input_start, output_start) = cursor.start();
 505        let input_overshoot = wrap_point.0 - input_start.0;
 506        BlockPoint(output_start.0 + input_overshoot)
 507    }
 508
 509    pub fn to_wrap_point(&self, block_point: BlockPoint) -> WrapPoint {
 510        let mut cursor = self.transforms.cursor::<(BlockPoint, WrapPoint)>();
 511        cursor.seek(&block_point, Bias::Right, &());
 512        let (output_start, input_start) = cursor.start();
 513        let output_overshoot = block_point.0 - output_start.0;
 514        WrapPoint(input_start.0 + output_overshoot)
 515    }
 516}
 517
 518impl Transform {
 519    fn isomorphic(lines: Point) -> Self {
 520        Self {
 521            summary: TransformSummary {
 522                input: lines,
 523                output: lines,
 524            },
 525            block: None,
 526        }
 527    }
 528
 529    fn block(block: Arc<Block>) -> Self {
 530        Self {
 531            summary: TransformSummary {
 532                input: Default::default(),
 533                output: block.text.summary().lines,
 534            },
 535            block: Some(block),
 536        }
 537    }
 538
 539    fn is_isomorphic(&self) -> bool {
 540        self.block.is_none()
 541    }
 542}
 543
 544impl<'a> Iterator for HighlightedChunks<'a> {
 545    type Item = HighlightedChunk<'a>;
 546
 547    fn next(&mut self) -> Option<Self::Item> {
 548        if self.output_position >= self.max_output_position {
 549            return None;
 550        }
 551
 552        if let Some(block_chunks) = self.block_chunks.as_mut() {
 553            if let Some(block_chunk) = block_chunks.next() {
 554                self.output_position.0 += Point::from_str(block_chunk.text);
 555                return Some(block_chunk);
 556            } else {
 557                self.block_chunks.take();
 558            }
 559        }
 560
 561        let transform = self.transforms.item()?;
 562        if let Some(block) = transform.block.as_ref() {
 563            let block_start = self.transforms.start().0 .0;
 564            let block_end = self.transforms.end(&()).0 .0;
 565            let start_in_block = self.output_position.0 - block_start;
 566            let end_in_block = cmp::min(self.max_output_position.0, block_end) - block_start;
 567            self.transforms.next(&());
 568            let mut block_chunks = BlockChunks::new(block, start_in_block..end_in_block);
 569            if let Some(block_chunk) = block_chunks.next() {
 570                self.output_position.0 += Point::from_str(block_chunk.text);
 571                return Some(block_chunk);
 572            }
 573        }
 574
 575        if self.input_chunk.text.is_empty() {
 576            if let Some(input_chunk) = self.input_chunks.next() {
 577                self.input_chunk = input_chunk;
 578            }
 579        }
 580
 581        let transform_end = self.transforms.end(&()).0 .0;
 582        let (prefix_lines, prefix_bytes) = offset_for_point(
 583            self.input_chunk.text,
 584            transform_end - self.output_position.0,
 585        );
 586        self.output_position.0 += prefix_lines;
 587        let (prefix, suffix) = self.input_chunk.text.split_at(prefix_bytes);
 588        self.input_chunk.text = suffix;
 589        if self.output_position.0 == transform_end {
 590            self.transforms.next(&());
 591        }
 592
 593        Some(HighlightedChunk {
 594            text: prefix,
 595            ..self.input_chunk
 596        })
 597    }
 598}
 599
 600impl<'a> BlockChunks<'a> {
 601    fn new(block: &'a Block, point_range: Range<Point>) -> Self {
 602        let offset_range = block.text.point_to_offset(point_range.start)
 603            ..block.text.point_to_offset(point_range.end);
 604
 605        let mut runs = block.runs.iter().peekable();
 606        let mut run_start = 0;
 607        while let Some((run_len, _)) = runs.peek() {
 608            let run_end = run_start + run_len;
 609            if run_end <= offset_range.start {
 610                run_start = run_end;
 611                runs.next();
 612            } else {
 613                break;
 614            }
 615        }
 616
 617        Self {
 618            chunk: None,
 619            run_start,
 620            chunks: block.text.chunks_in_range(offset_range.clone()),
 621            runs,
 622            offset: offset_range.start,
 623        }
 624    }
 625}
 626
 627impl<'a> Iterator for BlockChunks<'a> {
 628    type Item = HighlightedChunk<'a>;
 629
 630    fn next(&mut self) -> Option<Self::Item> {
 631        if self.chunk.is_none() {
 632            self.chunk = self.chunks.next();
 633        }
 634
 635        let chunk = self.chunk?;
 636        let mut chunk_len = chunk.len();
 637        // let mut highlight_style = None;
 638        if let Some((run_len, _)) = self.runs.peek() {
 639            // highlight_style = Some(style.clone());
 640            let run_end_in_chunk = self.run_start + run_len - self.offset;
 641            if run_end_in_chunk <= chunk_len {
 642                chunk_len = run_end_in_chunk;
 643                self.run_start += run_len;
 644                self.runs.next();
 645            }
 646        }
 647
 648        self.offset += chunk_len;
 649        let (chunk, suffix) = chunk.split_at(chunk_len);
 650        self.chunk = if suffix.is_empty() {
 651            None
 652        } else {
 653            Some(suffix)
 654        };
 655
 656        Some(HighlightedChunk {
 657            text: chunk,
 658            highlight_id: Default::default(),
 659            diagnostic: None,
 660        })
 661    }
 662}
 663
 664impl sum_tree::Item for Transform {
 665    type Summary = TransformSummary;
 666
 667    fn summary(&self) -> Self::Summary {
 668        self.summary.clone()
 669    }
 670}
 671
 672impl sum_tree::Summary for TransformSummary {
 673    type Context = ();
 674
 675    fn add_summary(&mut self, summary: &Self, _: &()) {
 676        self.input += summary.input;
 677        self.output += summary.output;
 678    }
 679}
 680
 681impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapPoint {
 682    fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
 683        self.0 += summary.input;
 684    }
 685}
 686
 687impl<'a> sum_tree::Dimension<'a, TransformSummary> for BlockPoint {
 688    fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
 689        self.0 += summary.output;
 690    }
 691}
 692
 693impl BlockDisposition {
 694    fn is_above(&self) -> bool {
 695        matches!(self, BlockDisposition::Above)
 696    }
 697}
 698
 699// Count the number of bytes prior to a target point. If the string doesn't contain the target
 700// point, return its total extent. Otherwise return the target point itself.
 701fn offset_for_point(s: &str, target: Point) -> (Point, usize) {
 702    let mut point = Point::zero();
 703    let mut offset = 0;
 704    for (row, line) in s.split('\n').enumerate().take(target.row as usize + 1) {
 705        let row = row as u32;
 706        if row > 0 {
 707            offset += 1;
 708        }
 709        point.row = row;
 710        point.column = if row == target.row {
 711            cmp::min(line.len() as u32, target.column)
 712        } else {
 713            line.len() as u32
 714        };
 715        offset += point.column as usize;
 716    }
 717    (point, offset)
 718}
 719
 720#[cfg(test)]
 721mod tests {
 722    use super::*;
 723    use crate::display_map::{fold_map::FoldMap, tab_map::TabMap, wrap_map::WrapMap};
 724    use buffer::RandomCharIter;
 725    use language::Buffer;
 726    use rand::prelude::*;
 727    use std::env;
 728
 729    #[gpui::test]
 730    fn test_basic_blocks(cx: &mut gpui::MutableAppContext) {
 731        let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap();
 732        let font_id = cx
 733            .font_cache()
 734            .select_font(family_id, &Default::default())
 735            .unwrap();
 736
 737        let text = "aaa\nbbb\nccc\nddd";
 738
 739        let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
 740        let (fold_map, folds_snapshot) = FoldMap::new(buffer.clone(), cx);
 741        let (tab_map, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), 1);
 742        let (wrap_map, wraps_snapshot) = WrapMap::new(tabs_snapshot, font_id, 14.0, None, cx);
 743        let mut block_map = BlockMap::new(buffer.clone(), wraps_snapshot.clone());
 744
 745        let mut writer = block_map.write(wraps_snapshot.clone(), vec![], cx);
 746        writer.insert(
 747            vec![
 748                BlockProperties {
 749                    position: Point::new(1, 0),
 750                    text: "BLOCK 1",
 751                    disposition: BlockDisposition::Above,
 752                    runs: vec![],
 753                },
 754                BlockProperties {
 755                    position: Point::new(1, 2),
 756                    text: "BLOCK 2",
 757                    disposition: BlockDisposition::Above,
 758                    runs: vec![],
 759                },
 760                BlockProperties {
 761                    position: Point::new(3, 2),
 762                    text: "BLOCK 3",
 763                    disposition: BlockDisposition::Below,
 764                    runs: vec![],
 765                },
 766            ],
 767            cx,
 768        );
 769
 770        let mut snapshot = block_map.read(wraps_snapshot, vec![], cx);
 771        assert_eq!(
 772            snapshot.text(),
 773            "aaa\nBLOCK 1\nBLOCK 2\nbbb\nccc\nddd\nBLOCK 3"
 774        );
 775        assert_eq!(
 776            snapshot.to_block_point(WrapPoint::new(1, 0)),
 777            BlockPoint::new(3, 0)
 778        );
 779        assert_eq!(
 780            snapshot.clip_point(BlockPoint::new(1, 0), Bias::Left),
 781            BlockPoint::new(1, 0)
 782        );
 783        assert_eq!(
 784            snapshot.clip_point(BlockPoint::new(1, 0), Bias::Right),
 785            BlockPoint::new(1, 0)
 786        );
 787        assert_eq!(
 788            snapshot.clip_point(BlockPoint::new(1, 1), Bias::Left),
 789            BlockPoint::new(1, 0)
 790        );
 791        assert_eq!(
 792            snapshot.clip_point(BlockPoint::new(1, 1), Bias::Right),
 793            BlockPoint::new(3, 0)
 794        );
 795        assert_eq!(
 796            snapshot.clip_point(BlockPoint::new(3, 0), Bias::Left),
 797            BlockPoint::new(3, 0)
 798        );
 799        assert_eq!(
 800            snapshot.clip_point(BlockPoint::new(3, 0), Bias::Right),
 801            BlockPoint::new(3, 0)
 802        );
 803        assert_eq!(
 804            snapshot.clip_point(BlockPoint::new(5, 3), Bias::Left),
 805            BlockPoint::new(5, 3)
 806        );
 807        assert_eq!(
 808            snapshot.clip_point(BlockPoint::new(5, 3), Bias::Right),
 809            BlockPoint::new(5, 3)
 810        );
 811        assert_eq!(
 812            snapshot.clip_point(BlockPoint::new(6, 0), Bias::Left),
 813            BlockPoint::new(5, 3)
 814        );
 815        assert_eq!(
 816            snapshot.clip_point(BlockPoint::new(6, 0), Bias::Right),
 817            BlockPoint::new(5, 3)
 818        );
 819
 820        // Insert a line break, separating two block decorations into separate
 821        // lines.
 822        buffer.update(cx, |buffer, cx| {
 823            buffer.edit([Point::new(1, 1)..Point::new(1, 1)], "!!!\n", cx)
 824        });
 825
 826        let (folds_snapshot, fold_edits) = fold_map.read(cx);
 827        let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits);
 828        let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
 829            wrap_map.sync(tabs_snapshot, tab_edits, cx)
 830        });
 831        let mut snapshot = block_map.read(wraps_snapshot, wrap_edits, cx);
 832        assert_eq!(
 833            snapshot.text(),
 834            "aaa\nBLOCK 1\nb!!!\nBLOCK 2\nbb\nccc\nddd\nBLOCK 3"
 835        );
 836    }
 837
 838    #[gpui::test]
 839    fn test_blocks_on_wrapped_lines(cx: &mut gpui::MutableAppContext) {
 840        let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap();
 841        let font_id = cx
 842            .font_cache()
 843            .select_font(family_id, &Default::default())
 844            .unwrap();
 845
 846        let text = "one two three\nfour five six\nseven eight";
 847
 848        let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
 849        let (fold_map, folds_snapshot) = FoldMap::new(buffer.clone(), cx);
 850        let (tab_map, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), 1);
 851        let (wrap_map, wraps_snapshot) = WrapMap::new(tabs_snapshot, font_id, 14.0, Some(60.), cx);
 852        let mut block_map = BlockMap::new(buffer.clone(), wraps_snapshot.clone());
 853
 854        let mut writer = block_map.write(wraps_snapshot.clone(), vec![], cx);
 855        writer.insert(
 856            vec![
 857                BlockProperties {
 858                    position: Point::new(1, 12),
 859                    text: "BLOCK 1",
 860                    disposition: BlockDisposition::Above,
 861                    runs: vec![],
 862                },
 863                BlockProperties {
 864                    position: Point::new(1, 1),
 865                    text: "BLOCK 2",
 866                    disposition: BlockDisposition::Below,
 867                    runs: vec![],
 868                },
 869            ],
 870            cx,
 871        );
 872
 873        // Blocks with an 'above' disposition go above their corresponding buffer line.
 874        // Blocks with a 'below' disposition go below their corresponding buffer line.
 875        let mut snapshot = block_map.read(wraps_snapshot, vec![], cx);
 876        assert_eq!(
 877            snapshot.text(),
 878            "one two \nthree\nBLOCK 1\nfour five \nsix\nBLOCK 2\nseven \neight"
 879        );
 880    }
 881
 882    #[gpui::test(iterations = 100)]
 883    fn test_random_blocks(cx: &mut gpui::MutableAppContext, mut rng: StdRng) {
 884        let operations = env::var("OPERATIONS")
 885            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
 886            .unwrap_or(10);
 887
 888        let wrap_width = if rng.gen_bool(0.2) {
 889            None
 890        } else {
 891            Some(rng.gen_range(0.0..=100.0))
 892        };
 893        let tab_size = 1;
 894        let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap();
 895        let font_id = cx
 896            .font_cache()
 897            .select_font(family_id, &Default::default())
 898            .unwrap();
 899        let font_size = 14.0;
 900
 901        log::info!("Wrap width: {:?}", wrap_width);
 902
 903        let buffer = cx.add_model(|cx| {
 904            let len = rng.gen_range(0..10);
 905            let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
 906            log::info!("initial buffer text: {:?}", text);
 907            Buffer::new(0, text, cx)
 908        });
 909        let (fold_map, folds_snapshot) = FoldMap::new(buffer.clone(), cx);
 910        let (tab_map, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), tab_size);
 911        let (wrap_map, wraps_snapshot) =
 912            WrapMap::new(tabs_snapshot, font_id, font_size, wrap_width, cx);
 913        let mut block_map = BlockMap::new(buffer.clone(), wraps_snapshot);
 914        let mut expected_blocks = Vec::new();
 915
 916        for _ in 0..operations {
 917            match rng.gen_range(0..=100) {
 918                0..=19 => {
 919                    let wrap_width = if rng.gen_bool(0.2) {
 920                        None
 921                    } else {
 922                        Some(rng.gen_range(0.0..=100.0))
 923                    };
 924                    log::info!("Setting wrap width to {:?}", wrap_width);
 925                    wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
 926                }
 927                20..=39 => {
 928                    let block_count = rng.gen_range(1..=1);
 929                    let block_properties = (0..block_count)
 930                        .map(|_| {
 931                            let buffer = buffer.read(cx);
 932                            let position = buffer.anchor_before(rng.gen_range(0..=buffer.len()));
 933
 934                            let len = rng.gen_range(0..10);
 935                            let text = Rope::from(
 936                                RandomCharIter::new(&mut rng)
 937                                    .take(len)
 938                                    .collect::<String>()
 939                                    .as_str(),
 940                            );
 941                            let disposition = if rng.gen() {
 942                                BlockDisposition::Above
 943                            } else {
 944                                BlockDisposition::Below
 945                            };
 946                            log::info!(
 947                                "inserting block {:?} {:?} with text {:?}",
 948                                disposition,
 949                                position.to_point(buffer),
 950                                text.to_string()
 951                            );
 952                            BlockProperties {
 953                                position,
 954                                text,
 955                                runs: Vec::<(usize, HighlightStyle)>::new(),
 956                                disposition,
 957                            }
 958                        })
 959                        .collect::<Vec<_>>();
 960
 961                    let (folds_snapshot, fold_edits) = fold_map.read(cx);
 962                    let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits);
 963                    let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
 964                        wrap_map.sync(tabs_snapshot, tab_edits, cx)
 965                    });
 966                    let mut block_map = block_map.write(wraps_snapshot, wrap_edits, cx);
 967                    let block_ids = block_map.insert(block_properties.clone(), cx);
 968                    for (block_id, props) in block_ids.into_iter().zip(block_properties) {
 969                        expected_blocks.push((block_id, props));
 970                    }
 971                }
 972                40..=59 if !expected_blocks.is_empty() => {
 973                    let block_count = rng.gen_range(1..=4.min(expected_blocks.len()));
 974                    let block_ids_to_remove = (0..block_count)
 975                        .map(|_| {
 976                            expected_blocks
 977                                .remove(rng.gen_range(0..expected_blocks.len()))
 978                                .0
 979                        })
 980                        .collect();
 981
 982                    let (folds_snapshot, fold_edits) = fold_map.read(cx);
 983                    let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits);
 984                    let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
 985                        wrap_map.sync(tabs_snapshot, tab_edits, cx)
 986                    });
 987                    let mut block_map = block_map.write(wraps_snapshot, wrap_edits, cx);
 988                    block_map.remove(block_ids_to_remove, cx);
 989                }
 990                _ => {
 991                    buffer.update(cx, |buffer, _| {
 992                        buffer.randomly_edit(&mut rng, 1);
 993                        log::info!("buffer text: {:?}", buffer.text());
 994                    });
 995                }
 996            }
 997
 998            let (folds_snapshot, fold_edits) = fold_map.read(cx);
 999            let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits);
1000            let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
1001                wrap_map.sync(tabs_snapshot, tab_edits, cx)
1002            });
1003            let mut blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits, cx);
1004            assert_eq!(
1005                blocks_snapshot.transforms.summary().input,
1006                wraps_snapshot.max_point().0
1007            );
1008            log::info!("blocks text: {:?}", blocks_snapshot.text());
1009
1010            let buffer = buffer.read(cx);
1011            let mut sorted_blocks = expected_blocks
1012                .iter()
1013                .cloned()
1014                .map(|(id, block)| {
1015                    let mut position = block.position.to_point(buffer);
1016                    match block.disposition {
1017                        BlockDisposition::Above => {
1018                            position.column = 0;
1019                        }
1020                        BlockDisposition::Below => {
1021                            position.column = buffer.line_len(position.row);
1022                        }
1023                    };
1024                    let row = wraps_snapshot.from_point(position, Bias::Left).row();
1025                    (
1026                        id,
1027                        BlockProperties {
1028                            position: row,
1029                            text: block.text,
1030                            runs: block.runs,
1031                            disposition: block.disposition,
1032                        },
1033                    )
1034                })
1035                .collect::<Vec<_>>();
1036            sorted_blocks
1037                .sort_unstable_by_key(|(id, block)| (block.position, block.disposition, *id));
1038            let mut sorted_blocks = sorted_blocks.into_iter().peekable();
1039
1040            let mut expected_text = String::new();
1041            let input_text = wraps_snapshot.text();
1042            for (row, input_line) in input_text.split('\n').enumerate() {
1043                let row = row as u32;
1044                if row > 0 {
1045                    expected_text.push('\n');
1046                }
1047
1048                while let Some((_, block)) = sorted_blocks.peek() {
1049                    if block.position == row && block.disposition == BlockDisposition::Above {
1050                        expected_text.extend(block.text.chunks());
1051                        expected_text.push('\n');
1052                        sorted_blocks.next();
1053                    } else {
1054                        break;
1055                    }
1056                }
1057
1058                expected_text.push_str(input_line);
1059
1060                while let Some((_, block)) = sorted_blocks.peek() {
1061                    if block.position == row && block.disposition == BlockDisposition::Below {
1062                        expected_text.push('\n');
1063                        expected_text.extend(block.text.chunks());
1064                        sorted_blocks.next();
1065                    } else {
1066                        break;
1067                    }
1068                }
1069            }
1070
1071            assert_eq!(blocks_snapshot.text(), expected_text);
1072        }
1073    }
1074}