multi_buffer.rs

   1use crate::buffer::{self, Buffer, Chunk, ToOffset as _, ToPoint as _};
   2use collections::HashMap;
   3use gpui::{AppContext, Entity, ModelContext, ModelHandle};
   4use parking_lot::Mutex;
   5use smallvec::{smallvec, SmallVec};
   6use std::{cmp, iter, ops::Range};
   7use sum_tree::{Bias, Cursor, SumTree};
   8use text::{
   9    rope::TextDimension,
  10    subscription::{Subscription, Topic},
  11    Anchor, AnchorRangeExt, Edit, Point, PointUtf16, TextSummary,
  12};
  13use theme::SyntaxTheme;
  14
  15const NEWLINES: &'static [u8] = &[b'\n'; u8::MAX as usize];
  16
  17pub type ExcerptId = Location;
  18
  19#[derive(Default)]
  20pub struct MultiBuffer {
  21    snapshot: Mutex<MultiBufferSnapshot>,
  22    buffers: HashMap<usize, BufferState>,
  23    subscriptions: Topic,
  24}
  25
  26pub trait ToOffset {
  27    fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize;
  28}
  29
  30pub trait ToPoint {
  31    fn to_point<'a>(&self, snapshot: &MultiBufferSnapshot) -> Point;
  32}
  33
  34#[derive(Debug)]
  35struct BufferState {
  36    buffer: ModelHandle<Buffer>,
  37    last_sync: clock::Global,
  38    excerpts: Vec<ExcerptId>,
  39}
  40
  41#[derive(Clone, Default)]
  42pub struct MultiBufferSnapshot {
  43    excerpts: SumTree<Excerpt>,
  44}
  45
  46pub struct ExcerptProperties<'a, T> {
  47    buffer: &'a ModelHandle<Buffer>,
  48    range: Range<T>,
  49    header_height: u8,
  50}
  51
  52#[derive(Clone)]
  53struct Excerpt {
  54    id: ExcerptId,
  55    buffer: buffer::BufferSnapshot,
  56    range: Range<Anchor>,
  57    text_summary: TextSummary,
  58    header_height: u8,
  59}
  60
  61#[derive(Clone, Debug, Default)]
  62struct ExcerptSummary {
  63    excerpt_id: ExcerptId,
  64    text: TextSummary,
  65}
  66
  67#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
  68pub struct Location(SmallVec<[u8; 4]>);
  69
  70pub struct Chunks<'a> {
  71    range: Range<usize>,
  72    cursor: Cursor<'a, Excerpt, usize>,
  73    header_height: u8,
  74    excerpt_chunks: Option<buffer::BufferChunks<'a>>,
  75    theme: Option<&'a SyntaxTheme>,
  76}
  77
  78impl MultiBuffer {
  79    pub fn new() -> Self {
  80        Self::default()
  81    }
  82
  83    pub fn snapshot(&self, cx: &AppContext) -> MultiBufferSnapshot {
  84        self.sync(cx);
  85        self.snapshot.lock().clone()
  86    }
  87
  88    pub fn subscribe(&mut self) -> Subscription {
  89        self.subscriptions.subscribe()
  90    }
  91
  92    pub fn push<O>(&mut self, props: ExcerptProperties<O>, cx: &mut ModelContext<Self>) -> ExcerptId
  93    where
  94        O: text::ToOffset,
  95    {
  96        self.sync(cx);
  97
  98        let buffer = props.buffer.read(cx);
  99        let range = buffer.anchor_before(props.range.start)..buffer.anchor_after(props.range.end);
 100        let mut snapshot = self.snapshot.lock();
 101        let prev_id = snapshot.excerpts.last().map(|e| &e.id);
 102        let id = ExcerptId::between(prev_id.unwrap_or(&ExcerptId::min()), &ExcerptId::max());
 103
 104        let edit_start = snapshot.excerpts.summary().text.bytes;
 105        let excerpt = Excerpt::new(id.clone(), buffer.snapshot(), range, props.header_height);
 106        let edit = Edit {
 107            old: edit_start..edit_start,
 108            new: edit_start..edit_start + excerpt.text_summary.bytes,
 109        };
 110        snapshot.excerpts.push(excerpt, &());
 111        self.buffers
 112            .entry(props.buffer.id())
 113            .or_insert_with(|| BufferState {
 114                buffer: props.buffer.clone(),
 115                last_sync: buffer.version(),
 116                excerpts: Default::default(),
 117            })
 118            .excerpts
 119            .push(id.clone());
 120
 121        self.subscriptions.publish_mut([edit]);
 122
 123        id
 124    }
 125
 126    fn sync(&self, cx: &AppContext) {
 127        let mut snapshot = self.snapshot.lock();
 128        let mut excerpts_to_edit = Vec::new();
 129        for buffer_state in self.buffers.values() {
 130            if buffer_state
 131                .buffer
 132                .read(cx)
 133                .version()
 134                .gt(&buffer_state.last_sync)
 135            {
 136                excerpts_to_edit.extend(
 137                    buffer_state
 138                        .excerpts
 139                        .iter()
 140                        .map(|excerpt_id| (excerpt_id, buffer_state)),
 141                );
 142            }
 143        }
 144        excerpts_to_edit.sort_unstable_by_key(|(excerpt_id, _)| *excerpt_id);
 145
 146        let mut edits = Vec::new();
 147        let mut new_excerpts = SumTree::new();
 148        let mut cursor = snapshot.excerpts.cursor::<(ExcerptId, usize)>();
 149
 150        for (id, buffer_state) in excerpts_to_edit {
 151            new_excerpts.push_tree(cursor.slice(id, Bias::Left, &()), &());
 152            let old_excerpt = cursor.item().unwrap();
 153            let buffer = buffer_state.buffer.read(cx);
 154
 155            edits.extend(
 156                buffer
 157                    .edits_since_in_range::<usize>(
 158                        old_excerpt.buffer.version(),
 159                        old_excerpt.range.clone(),
 160                    )
 161                    .map(|mut edit| {
 162                        let excerpt_old_start =
 163                            cursor.start().1 + old_excerpt.header_height as usize;
 164                        let excerpt_new_start =
 165                            new_excerpts.summary().text.bytes + old_excerpt.header_height as usize;
 166                        edit.old.start += excerpt_old_start;
 167                        edit.old.end += excerpt_old_start;
 168                        edit.new.start += excerpt_new_start;
 169                        edit.new.end += excerpt_new_start;
 170                        edit
 171                    }),
 172            );
 173
 174            new_excerpts.push(
 175                Excerpt::new(
 176                    id.clone(),
 177                    buffer.snapshot(),
 178                    old_excerpt.range.clone(),
 179                    old_excerpt.header_height,
 180                ),
 181                &(),
 182            );
 183
 184            cursor.next(&());
 185        }
 186        new_excerpts.push_tree(cursor.suffix(&()), &());
 187
 188        drop(cursor);
 189        snapshot.excerpts = new_excerpts;
 190
 191        self.subscriptions.publish(edits);
 192    }
 193}
 194
 195impl Entity for MultiBuffer {
 196    type Event = ();
 197}
 198
 199impl MultiBufferSnapshot {
 200    pub fn text(&self) -> String {
 201        self.chunks(0..self.len(), None)
 202            .map(|chunk| chunk.text)
 203            .collect()
 204    }
 205
 206    pub fn text_for_range<'a, T: ToOffset>(
 207        &'a self,
 208        range: Range<T>,
 209    ) -> impl Iterator<Item = &'a str> {
 210        self.chunks(range, None).map(|chunk| chunk.text)
 211    }
 212
 213    pub fn len(&self) -> usize {
 214        self.excerpts.summary().text.bytes
 215    }
 216
 217    pub fn clip_offset(&self, offset: usize, bias: Bias) -> usize {
 218        let mut cursor = self.excerpts.cursor::<usize>();
 219        cursor.seek(&offset, Bias::Right, &());
 220        if let Some(excerpt) = cursor.item() {
 221            let start_after_header = *cursor.start() + excerpt.header_height as usize;
 222            if offset < start_after_header {
 223                *cursor.start()
 224            } else {
 225                let excerpt_start = excerpt.range.start.to_offset(&excerpt.buffer);
 226                let buffer_offset = excerpt
 227                    .buffer
 228                    .clip_offset(excerpt_start + (offset - start_after_header), bias);
 229                let offset_in_excerpt = if buffer_offset > excerpt_start {
 230                    buffer_offset - excerpt_start
 231                } else {
 232                    0
 233                };
 234                start_after_header + offset_in_excerpt
 235            }
 236        } else {
 237            self.excerpts.summary().text.bytes
 238        }
 239    }
 240
 241    pub fn clip_point(&self, point: Point, bias: Bias) -> Point {
 242        let mut cursor = self.excerpts.cursor::<Point>();
 243        cursor.seek(&point, Bias::Right, &());
 244        if let Some(excerpt) = cursor.item() {
 245            let start_after_header = *cursor.start() + Point::new(excerpt.header_height as u32, 0);
 246            if point < start_after_header {
 247                *cursor.start()
 248            } else {
 249                let excerpt_start = excerpt.range.start.to_point(&excerpt.buffer);
 250                let buffer_point = excerpt
 251                    .buffer
 252                    .clip_point(excerpt_start + (point - start_after_header), bias);
 253                let point_in_excerpt = if buffer_point > excerpt_start {
 254                    buffer_point - excerpt_start
 255                } else {
 256                    Point::zero()
 257                };
 258                start_after_header + point_in_excerpt
 259            }
 260        } else {
 261            self.excerpts.summary().text.lines
 262        }
 263    }
 264
 265    pub fn clip_point_utf16(&self, point: PointUtf16, bias: Bias) -> PointUtf16 {
 266        let mut cursor = self.excerpts.cursor::<PointUtf16>();
 267        cursor.seek(&point, Bias::Right, &());
 268        if let Some(excerpt) = cursor.item() {
 269            let start_after_header =
 270                *cursor.start() + PointUtf16::new(excerpt.header_height as u32, 0);
 271            if point < start_after_header {
 272                *cursor.start()
 273            } else {
 274                let excerpt_start = excerpt
 275                    .buffer
 276                    .offset_to_point_utf16(excerpt.range.start.to_offset(&excerpt.buffer));
 277                let buffer_point = excerpt
 278                    .buffer
 279                    .clip_point_utf16(excerpt_start + (point - start_after_header), bias);
 280                let point_in_excerpt = if buffer_point > excerpt_start {
 281                    buffer_point - excerpt_start
 282                } else {
 283                    PointUtf16::new(0, 0)
 284                };
 285                start_after_header + point_in_excerpt
 286            }
 287        } else {
 288            self.excerpts.summary().text.lines_utf16
 289        }
 290    }
 291
 292    pub fn chunks<'a, T: ToOffset>(
 293        &'a self,
 294        range: Range<T>,
 295        theme: Option<&'a SyntaxTheme>,
 296    ) -> Chunks<'a> {
 297        let range = range.start.to_offset(self)..range.end.to_offset(self);
 298        let mut cursor = self.excerpts.cursor::<usize>();
 299        cursor.seek(&range.start, Bias::Right, &());
 300
 301        let mut header_height: u8 = 0;
 302        let excerpt_chunks = cursor.item().map(|excerpt| {
 303            let buffer_range = excerpt.range.to_offset(&excerpt.buffer);
 304            header_height = excerpt.header_height;
 305
 306            let buffer_start;
 307            let start_overshoot = range.start - cursor.start();
 308            if start_overshoot < excerpt.header_height as usize {
 309                header_height -= start_overshoot as u8;
 310                buffer_start = buffer_range.start;
 311            } else {
 312                buffer_start =
 313                    buffer_range.start + start_overshoot - excerpt.header_height as usize;
 314                header_height = 0;
 315            }
 316
 317            let buffer_end;
 318            let end_overshoot = range.end - cursor.start();
 319            if end_overshoot < excerpt.header_height as usize {
 320                header_height -= excerpt.header_height - end_overshoot as u8;
 321                buffer_end = buffer_start;
 322            } else {
 323                buffer_end = cmp::min(
 324                    buffer_range.end,
 325                    buffer_range.start + end_overshoot - excerpt.header_height as usize,
 326                );
 327            }
 328
 329            excerpt.buffer.chunks(buffer_start..buffer_end, theme)
 330        });
 331
 332        Chunks {
 333            range,
 334            cursor,
 335            header_height,
 336            excerpt_chunks,
 337            theme,
 338        }
 339    }
 340
 341    pub fn offset_to_point(&self, offset: usize) -> Point {
 342        let mut cursor = self.excerpts.cursor::<(usize, Point)>();
 343        cursor.seek(&offset, Bias::Right, &());
 344        if let Some(excerpt) = cursor.item() {
 345            let (start_offset, start_point) = cursor.start();
 346            let overshoot = offset - start_offset;
 347            let header_height = excerpt.header_height as usize;
 348            if overshoot < header_height {
 349                *start_point
 350            } else {
 351                let excerpt_start_offset = excerpt.range.start.to_offset(&excerpt.buffer);
 352                let excerpt_start_point = excerpt.range.start.to_point(&excerpt.buffer);
 353                let buffer_point = excerpt
 354                    .buffer
 355                    .offset_to_point(excerpt_start_offset + (overshoot - header_height));
 356                *start_point
 357                    + Point::new(header_height as u32, 0)
 358                    + (buffer_point - excerpt_start_point)
 359            }
 360        } else {
 361            self.excerpts.summary().text.lines
 362        }
 363    }
 364
 365    pub fn point_to_offset(&self, point: Point) -> usize {
 366        let mut cursor = self.excerpts.cursor::<(Point, usize)>();
 367        cursor.seek(&point, Bias::Right, &());
 368        if let Some(excerpt) = cursor.item() {
 369            let (start_point, start_offset) = cursor.start();
 370            let overshoot = point - start_point;
 371            let header_height = Point::new(excerpt.header_height as u32, 0);
 372            if overshoot < header_height {
 373                *start_offset
 374            } else {
 375                let excerpt_start_offset = excerpt.range.start.to_offset(&excerpt.buffer);
 376                let excerpt_start_point = excerpt.range.start.to_point(&excerpt.buffer);
 377                let buffer_offset = excerpt
 378                    .buffer
 379                    .point_to_offset(excerpt_start_point + (overshoot - header_height));
 380                *start_offset + excerpt.header_height as usize + buffer_offset
 381                    - excerpt_start_offset
 382            }
 383        } else {
 384            self.excerpts.summary().text.bytes
 385        }
 386    }
 387
 388    pub fn point_utf16_to_offset(&self, point: PointUtf16) -> usize {
 389        let mut cursor = self.excerpts.cursor::<(PointUtf16, usize)>();
 390        cursor.seek(&point, Bias::Right, &());
 391        if let Some(excerpt) = cursor.item() {
 392            let (start_point, start_offset) = cursor.start();
 393            let overshoot = point - start_point;
 394            let header_height = PointUtf16::new(excerpt.header_height as u32, 0);
 395            if overshoot < header_height {
 396                *start_offset
 397            } else {
 398                let excerpt_start_offset = excerpt.range.start.to_offset(&excerpt.buffer);
 399                let excerpt_start_point = excerpt
 400                    .buffer
 401                    .offset_to_point_utf16(excerpt.range.start.to_offset(&excerpt.buffer));
 402                let buffer_offset = excerpt
 403                    .buffer
 404                    .point_utf16_to_offset(excerpt_start_point + (overshoot - header_height));
 405                *start_offset
 406                    + excerpt.header_height as usize
 407                    + (buffer_offset - excerpt_start_offset)
 408            }
 409        } else {
 410            self.excerpts.summary().text.bytes
 411        }
 412    }
 413
 414    pub fn line_len(&self, row: u32) -> u32 {
 415        let mut cursor = self.excerpts.cursor::<Point>();
 416        cursor.seek(&Point::new(row, 0), Bias::Right, &());
 417        if let Some(excerpt) = cursor.item() {
 418            let overshoot = row - cursor.start().row;
 419            let header_height = excerpt.header_height as u32;
 420            if overshoot < header_height {
 421                0
 422            } else {
 423                let excerpt_start = excerpt.range.start.to_point(&excerpt.buffer);
 424                let excerpt_end = excerpt.range.end.to_point(&excerpt.buffer);
 425                let buffer_row = excerpt_start.row + overshoot - header_height;
 426                let mut len = excerpt.buffer.line_len(buffer_row);
 427                if buffer_row == excerpt_end.row {
 428                    len = excerpt_end.column;
 429                }
 430                if buffer_row == excerpt_start.row {
 431                    len -= excerpt_start.column
 432                }
 433                len
 434            }
 435        } else {
 436            0
 437        }
 438    }
 439
 440    pub fn max_point(&self) -> Point {
 441        self.text_summary().lines
 442    }
 443
 444    pub fn text_summary(&self) -> TextSummary {
 445        self.excerpts.summary().text
 446    }
 447
 448    pub fn text_summary_for_range<'a, D, O>(&'a self, range: Range<O>) -> D
 449    where
 450        D: TextDimension,
 451        O: ToOffset,
 452    {
 453        let mut summary = D::default();
 454        let mut range = range.start.to_offset(self)..range.end.to_offset(self);
 455        let mut cursor = self.excerpts.cursor::<usize>();
 456        cursor.seek(&range.start, Bias::Right, &());
 457        if let Some(excerpt) = cursor.item() {
 458            let start_after_header = cursor.start() + excerpt.header_height as usize;
 459            if range.start < start_after_header {
 460                let header_len = cmp::min(range.end, start_after_header) - range.start;
 461                summary.add_assign(&D::from_text_summary(&TextSummary {
 462                    bytes: header_len,
 463                    lines: Point::new(header_len as u32, 0),
 464                    lines_utf16: PointUtf16::new(header_len as u32, 0),
 465                    first_line_chars: 0,
 466                    last_line_chars: 0,
 467                    longest_row: 0,
 468                    longest_row_chars: 0,
 469                }));
 470                range.start = start_after_header;
 471                range.end = cmp::max(range.start, range.end);
 472            }
 473
 474            let end_before_newline = cursor.end(&()) - 1;
 475            let excerpt_start = excerpt.range.start.to_offset(&excerpt.buffer);
 476            let start_in_excerpt = excerpt_start + (range.start - start_after_header);
 477            let end_in_excerpt =
 478                excerpt_start + (cmp::min(end_before_newline, range.end) - start_after_header);
 479            summary.add_assign(
 480                &excerpt
 481                    .buffer
 482                    .text_summary_for_range(start_in_excerpt..end_in_excerpt),
 483            );
 484
 485            if range.end > end_before_newline {
 486                summary.add_assign(&D::from_text_summary(&TextSummary {
 487                    bytes: 1,
 488                    lines: Point::new(1 as u32, 0),
 489                    lines_utf16: PointUtf16::new(1 as u32, 0),
 490                    first_line_chars: 0,
 491                    last_line_chars: 0,
 492                    longest_row: 0,
 493                    longest_row_chars: 0,
 494                }));
 495            }
 496
 497            cursor.next(&());
 498        }
 499
 500        if range.end > *cursor.start() {
 501            summary.add_assign(&D::from_text_summary(&cursor.summary::<_, TextSummary>(
 502                &range.end,
 503                Bias::Right,
 504                &(),
 505            )));
 506            if let Some(excerpt) = cursor.item() {
 507                let start_after_header = cursor.start() + excerpt.header_height as usize;
 508                let header_len =
 509                    cmp::min(range.end - cursor.start(), excerpt.header_height as usize);
 510                summary.add_assign(&D::from_text_summary(&TextSummary {
 511                    bytes: header_len,
 512                    lines: Point::new(header_len as u32, 0),
 513                    lines_utf16: PointUtf16::new(header_len as u32, 0),
 514                    first_line_chars: 0,
 515                    last_line_chars: 0,
 516                    longest_row: 0,
 517                    longest_row_chars: 0,
 518                }));
 519                range.end = cmp::max(start_after_header, range.end);
 520
 521                let excerpt_start = excerpt.range.start.to_offset(&excerpt.buffer);
 522                let end_in_excerpt = excerpt_start + (range.end - start_after_header);
 523                summary.add_assign(
 524                    &excerpt
 525                        .buffer
 526                        .text_summary_for_range(excerpt_start..end_in_excerpt),
 527                );
 528                cursor.next(&());
 529            }
 530        }
 531
 532        summary
 533    }
 534}
 535
 536impl Excerpt {
 537    fn new(
 538        id: ExcerptId,
 539        buffer: buffer::BufferSnapshot,
 540        range: Range<Anchor>,
 541        header_height: u8,
 542    ) -> Self {
 543        let mut text_summary =
 544            buffer.text_summary_for_range::<TextSummary, _>(range.to_offset(&buffer));
 545        if header_height > 0 {
 546            text_summary.first_line_chars = 0;
 547            text_summary.lines.row += header_height as u32;
 548            text_summary.lines_utf16.row += header_height as u32;
 549            text_summary.bytes += header_height as usize;
 550            text_summary.longest_row += header_height as u32;
 551        }
 552        text_summary.last_line_chars = 0;
 553        text_summary.lines.row += 1;
 554        text_summary.lines.column = 0;
 555        text_summary.lines_utf16.row += 1;
 556        text_summary.lines_utf16.column = 0;
 557        text_summary.bytes += 1;
 558
 559        Excerpt {
 560            id,
 561            buffer,
 562            range,
 563            text_summary,
 564            header_height,
 565        }
 566    }
 567}
 568
 569impl sum_tree::Item for Excerpt {
 570    type Summary = ExcerptSummary;
 571
 572    fn summary(&self) -> Self::Summary {
 573        ExcerptSummary {
 574            excerpt_id: self.id.clone(),
 575            text: self.text_summary.clone(),
 576        }
 577    }
 578}
 579
 580impl sum_tree::Summary for ExcerptSummary {
 581    type Context = ();
 582
 583    fn add_summary(&mut self, summary: &Self, _: &()) {
 584        debug_assert!(summary.excerpt_id > self.excerpt_id);
 585        self.excerpt_id = summary.excerpt_id.clone();
 586        self.text.add_summary(&summary.text, &());
 587    }
 588}
 589
 590impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for TextSummary {
 591    fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
 592        *self += &summary.text;
 593    }
 594}
 595
 596impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for usize {
 597    fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
 598        *self += summary.text.bytes;
 599    }
 600}
 601
 602impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Point {
 603    fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
 604        *self += summary.text.lines;
 605    }
 606}
 607
 608impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for PointUtf16 {
 609    fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
 610        *self += summary.text.lines_utf16
 611    }
 612}
 613
 614impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Location {
 615    fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
 616        debug_assert!(summary.excerpt_id > *self);
 617        *self = summary.excerpt_id.clone();
 618    }
 619}
 620
 621impl<'a> Iterator for Chunks<'a> {
 622    type Item = Chunk<'a>;
 623
 624    fn next(&mut self) -> Option<Self::Item> {
 625        loop {
 626            if self.header_height > 0 {
 627                let chunk = Chunk {
 628                    text: unsafe {
 629                        std::str::from_utf8_unchecked(&NEWLINES[..self.header_height as usize])
 630                    },
 631                    ..Default::default()
 632                };
 633                self.header_height = 0;
 634                return Some(chunk);
 635            }
 636
 637            if let Some(excerpt_chunks) = self.excerpt_chunks.as_mut() {
 638                if let Some(chunk) = excerpt_chunks.next() {
 639                    return Some(chunk);
 640                }
 641                self.excerpt_chunks.take();
 642                if self.cursor.end(&()) <= self.range.end {
 643                    return Some(Chunk {
 644                        text: "\n",
 645                        ..Default::default()
 646                    });
 647                }
 648            }
 649
 650            self.cursor.next(&());
 651            if *self.cursor.start() >= self.range.end {
 652                return None;
 653            }
 654
 655            let excerpt = self.cursor.item()?;
 656            let buffer_range = excerpt.range.to_offset(&excerpt.buffer);
 657
 658            let buffer_end = cmp::min(
 659                buffer_range.end,
 660                buffer_range.start + self.range.end
 661                    - excerpt.header_height as usize
 662                    - self.cursor.start(),
 663            );
 664
 665            self.header_height = excerpt.header_height;
 666            self.excerpt_chunks = Some(
 667                excerpt
 668                    .buffer
 669                    .chunks(buffer_range.start..buffer_end, self.theme),
 670            );
 671        }
 672    }
 673}
 674
 675impl ToOffset for Point {
 676    fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize {
 677        snapshot.point_to_offset(*self)
 678    }
 679}
 680
 681impl ToOffset for PointUtf16 {
 682    fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize {
 683        snapshot.point_utf16_to_offset(*self)
 684    }
 685}
 686
 687impl ToOffset for usize {
 688    fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize {
 689        assert!(*self <= snapshot.len(), "offset is out of range");
 690        *self
 691    }
 692}
 693
 694impl ToPoint for usize {
 695    fn to_point<'a>(&self, snapshot: &MultiBufferSnapshot) -> Point {
 696        snapshot.offset_to_point(*self)
 697    }
 698}
 699
 700impl ToPoint for Point {
 701    fn to_point<'a>(&self, _: &MultiBufferSnapshot) -> Point {
 702        *self
 703    }
 704}
 705
 706impl Default for Location {
 707    fn default() -> Self {
 708        Self::min()
 709    }
 710}
 711
 712impl Location {
 713    pub fn min() -> Self {
 714        Self(smallvec![u8::MIN])
 715    }
 716
 717    pub fn max() -> Self {
 718        Self(smallvec![u8::MAX])
 719    }
 720
 721    pub fn between(lhs: &Self, rhs: &Self) -> Self {
 722        let lhs = lhs.0.iter().copied().chain(iter::repeat(u8::MIN));
 723        let rhs = rhs.0.iter().copied().chain(iter::repeat(u8::MAX));
 724        let mut location = SmallVec::new();
 725        for (lhs, rhs) in lhs.zip(rhs) {
 726            let mid = lhs + (rhs.saturating_sub(lhs)) / 2;
 727            location.push(mid);
 728            if mid > lhs {
 729                break;
 730            }
 731        }
 732        Self(location)
 733    }
 734}
 735
 736#[cfg(test)]
 737mod tests {
 738    use super::*;
 739    use crate::buffer::Buffer;
 740    use gpui::MutableAppContext;
 741    use rand::prelude::*;
 742    use std::{env, mem};
 743    use text::{Point, RandomCharIter};
 744    use util::test::sample_text;
 745
 746    #[gpui::test]
 747    fn test_excerpt_buffer(cx: &mut MutableAppContext) {
 748        let buffer_1 = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6, 'a'), cx));
 749        let buffer_2 = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6, 'g'), cx));
 750
 751        let list = cx.add_model(|_| MultiBuffer::new());
 752
 753        let subscription = list.update(cx, |list, cx| {
 754            let subscription = list.subscribe();
 755            list.push(
 756                ExcerptProperties {
 757                    buffer: &buffer_1,
 758                    range: Point::new(1, 2)..Point::new(2, 5),
 759                    header_height: 2,
 760                },
 761                cx,
 762            );
 763            assert_eq!(
 764                subscription.consume().into_inner(),
 765                [Edit {
 766                    old: 0..0,
 767                    new: 0..13
 768                }]
 769            );
 770
 771            list.push(
 772                ExcerptProperties {
 773                    buffer: &buffer_1,
 774                    range: Point::new(3, 3)..Point::new(4, 4),
 775                    header_height: 1,
 776                },
 777                cx,
 778            );
 779            list.push(
 780                ExcerptProperties {
 781                    buffer: &buffer_2,
 782                    range: Point::new(3, 1)..Point::new(3, 3),
 783                    header_height: 3,
 784                },
 785                cx,
 786            );
 787            assert_eq!(
 788                subscription.consume().into_inner(),
 789                [Edit {
 790                    old: 13..13,
 791                    new: 13..29
 792                }]
 793            );
 794
 795            subscription
 796        });
 797
 798        assert_eq!(
 799            list.read(cx).snapshot(cx).text(),
 800            concat!(
 801                "\n",      // Preserve newlines
 802                "\n",      //
 803                "bbbb\n",  //
 804                "ccccc\n", //
 805                "\n",      //
 806                "ddd\n",   //
 807                "eeee\n",  //
 808                "\n",      //
 809                "\n",      //
 810                "\n",      //
 811                "jj\n"     //
 812            )
 813        );
 814
 815        buffer_1.update(cx, |buffer, cx| {
 816            buffer.edit(
 817                [
 818                    Point::new(0, 0)..Point::new(0, 0),
 819                    Point::new(2, 1)..Point::new(2, 3),
 820                ],
 821                "\n",
 822                cx,
 823            );
 824        });
 825
 826        assert_eq!(
 827            list.read(cx).snapshot(cx).text(),
 828            concat!(
 829                "\n",     // Preserve newlines
 830                "\n",     //
 831                "bbbb\n", //
 832                "c\n",    //
 833                "cc\n",   //
 834                "\n",     //
 835                "ddd\n",  //
 836                "eeee\n", //
 837                "\n",     //
 838                "\n",     //
 839                "\n",     //
 840                "jj\n"    //
 841            )
 842        );
 843
 844        assert_eq!(
 845            subscription.consume().into_inner(),
 846            [Edit {
 847                old: 8..10,
 848                new: 8..9
 849            }]
 850        );
 851    }
 852
 853    #[gpui::test(iterations = 100)]
 854    fn test_random_excerpts(cx: &mut MutableAppContext, mut rng: StdRng) {
 855        let operations = env::var("OPERATIONS")
 856            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
 857            .unwrap_or(10);
 858
 859        let mut buffers: Vec<ModelHandle<Buffer>> = Vec::new();
 860        let list = cx.add_model(|_| MultiBuffer::new());
 861        let mut excerpt_ids = Vec::new();
 862        let mut expected_excerpts = Vec::new();
 863        let mut old_versions = Vec::new();
 864
 865        for _ in 0..operations {
 866            match rng.gen_range(0..100) {
 867                0..=19 if !buffers.is_empty() => {
 868                    let buffer = buffers.choose(&mut rng).unwrap();
 869                    buffer.update(cx, |buf, cx| buf.randomly_edit(&mut rng, 1, cx));
 870                }
 871                _ => {
 872                    let buffer_handle = if buffers.is_empty() || rng.gen_bool(0.4) {
 873                        let base_text = RandomCharIter::new(&mut rng).take(10).collect::<String>();
 874                        buffers.push(cx.add_model(|cx| Buffer::new(0, base_text, cx)));
 875                        buffers.last().unwrap()
 876                    } else {
 877                        buffers.choose(&mut rng).unwrap()
 878                    };
 879
 880                    let buffer = buffer_handle.read(cx);
 881                    let end_ix = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Bias::Right);
 882                    let start_ix = buffer.clip_offset(rng.gen_range(0..=end_ix), Bias::Left);
 883                    let header_height = rng.gen_range(0..=5);
 884                    let anchor_range = buffer.anchor_before(start_ix)..buffer.anchor_after(end_ix);
 885                    log::info!(
 886                        "Pushing excerpt wih header {}, buffer {}: {:?}[{:?}] = {:?}",
 887                        header_height,
 888                        buffer_handle.id(),
 889                        buffer.text(),
 890                        start_ix..end_ix,
 891                        &buffer.text()[start_ix..end_ix]
 892                    );
 893
 894                    let excerpt_id = list.update(cx, |list, cx| {
 895                        list.push(
 896                            ExcerptProperties {
 897                                buffer: &buffer_handle,
 898                                range: start_ix..end_ix,
 899                                header_height,
 900                            },
 901                            cx,
 902                        )
 903                    });
 904                    excerpt_ids.push(excerpt_id);
 905                    expected_excerpts.push((buffer_handle.clone(), anchor_range, header_height));
 906                }
 907            }
 908
 909            if rng.gen_bool(0.3) {
 910                list.update(cx, |list, cx| {
 911                    old_versions.push((list.snapshot(cx), list.subscribe()));
 912                })
 913            }
 914
 915            let snapshot = list.read(cx).snapshot(cx);
 916
 917            let mut excerpt_starts = Vec::new();
 918            let mut expected_text = String::new();
 919            for (buffer, range, header_height) in &expected_excerpts {
 920                let buffer = buffer.read(cx);
 921                let buffer_range = range.to_offset(buffer);
 922
 923                for _ in 0..*header_height {
 924                    expected_text.push('\n');
 925                }
 926
 927                excerpt_starts.push(TextSummary::from(expected_text.as_str()));
 928                expected_text.extend(buffer.text_for_range(buffer_range.clone()));
 929                expected_text.push('\n');
 930            }
 931
 932            assert_eq!(snapshot.text(), expected_text);
 933
 934            let mut excerpt_starts = excerpt_starts.into_iter();
 935            for (buffer, range, _) in &expected_excerpts {
 936                let buffer_id = buffer.id();
 937                let buffer = buffer.read(cx);
 938                let buffer_range = range.to_offset(buffer);
 939                let buffer_start_point = buffer.offset_to_point(buffer_range.start);
 940                let buffer_start_point_utf16 =
 941                    buffer.text_summary_for_range::<PointUtf16, _>(0..buffer_range.start);
 942
 943                let excerpt_start = excerpt_starts.next().unwrap();
 944                let mut offset = excerpt_start.bytes;
 945                let mut buffer_offset = buffer_range.start;
 946                let mut point = excerpt_start.lines;
 947                let mut buffer_point = buffer_start_point;
 948                let mut point_utf16 = excerpt_start.lines_utf16;
 949                let mut buffer_point_utf16 = buffer_start_point_utf16;
 950                for byte in buffer.bytes_in_range(buffer_range.clone()).flatten() {
 951                    let left_offset = snapshot.clip_offset(offset, Bias::Left);
 952                    let right_offset = snapshot.clip_offset(offset, Bias::Right);
 953                    let buffer_left_offset = buffer.clip_offset(buffer_offset, Bias::Left);
 954                    let buffer_right_offset = buffer.clip_offset(buffer_offset, Bias::Right);
 955                    assert_eq!(
 956                        left_offset,
 957                        excerpt_start.bytes + (buffer_left_offset - buffer_range.start),
 958                        "clip_offset({:?}, Left). buffer: {:?}, buffer offset: {:?}",
 959                        offset,
 960                        buffer_id,
 961                        buffer_offset,
 962                    );
 963                    assert_eq!(
 964                        right_offset,
 965                        excerpt_start.bytes + (buffer_right_offset - buffer_range.start),
 966                        "clip_offset({:?}, Right). buffer: {:?}, buffer offset: {:?}",
 967                        offset,
 968                        buffer_id,
 969                        buffer_offset,
 970                    );
 971
 972                    let left_point = snapshot.clip_point(point, Bias::Left);
 973                    let right_point = snapshot.clip_point(point, Bias::Right);
 974                    let buffer_left_point = buffer.clip_point(buffer_point, Bias::Left);
 975                    let buffer_right_point = buffer.clip_point(buffer_point, Bias::Right);
 976                    assert_eq!(
 977                        left_point,
 978                        excerpt_start.lines + (buffer_left_point - buffer_start_point),
 979                        "clip_point({:?}, Left). buffer: {:?}, buffer point: {:?}",
 980                        point,
 981                        buffer_id,
 982                        buffer_point,
 983                    );
 984                    assert_eq!(
 985                        right_point,
 986                        excerpt_start.lines + (buffer_right_point - buffer_start_point),
 987                        "clip_point({:?}, Right). buffer: {:?}, buffer point: {:?}",
 988                        point,
 989                        buffer_id,
 990                        buffer_point,
 991                    );
 992
 993                    let left_point_utf16 = snapshot.clip_point_utf16(point_utf16, Bias::Left);
 994                    let right_point_utf16 = snapshot.clip_point_utf16(point_utf16, Bias::Right);
 995                    let buffer_left_point_utf16 =
 996                        buffer.clip_point_utf16(buffer_point_utf16, Bias::Left);
 997                    let buffer_right_point_utf16 =
 998                        buffer.clip_point_utf16(buffer_point_utf16, Bias::Right);
 999                    assert_eq!(
1000                        left_point_utf16,
1001                        excerpt_start.lines_utf16
1002                            + (buffer_left_point_utf16 - buffer_start_point_utf16),
1003                        "clip_point_utf16({:?}, Left). buffer: {:?}, buffer point_utf16: {:?}",
1004                        point_utf16,
1005                        buffer_id,
1006                        buffer_point_utf16,
1007                    );
1008                    assert_eq!(
1009                        right_point_utf16,
1010                        excerpt_start.lines_utf16
1011                            + (buffer_right_point_utf16 - buffer_start_point_utf16),
1012                        "clip_point_utf16({:?}, Right). buffer: {:?}, buffer point_utf16: {:?}",
1013                        point_utf16,
1014                        buffer_id,
1015                        buffer_point_utf16,
1016                    );
1017
1018                    assert_eq!(
1019                        snapshot.point_to_offset(left_point),
1020                        left_offset,
1021                        "point_to_offset({:?})",
1022                        left_point,
1023                    );
1024                    assert_eq!(
1025                        snapshot.offset_to_point(left_offset),
1026                        left_point,
1027                        "offset_to_point({:?})",
1028                        left_offset,
1029                    );
1030
1031                    offset += 1;
1032                    buffer_offset += 1;
1033                    if *byte == b'\n' {
1034                        point += Point::new(1, 0);
1035                        point_utf16 += PointUtf16::new(1, 0);
1036                        buffer_point += Point::new(1, 0);
1037                        buffer_point_utf16 += PointUtf16::new(1, 0);
1038                    } else {
1039                        point += Point::new(0, 1);
1040                        point_utf16 += PointUtf16::new(0, 1);
1041                        buffer_point += Point::new(0, 1);
1042                        buffer_point_utf16 += PointUtf16::new(0, 1);
1043                    }
1044                }
1045            }
1046
1047            for (row, line) in expected_text.split('\n').enumerate() {
1048                assert_eq!(
1049                    snapshot.line_len(row as u32),
1050                    line.len() as u32,
1051                    "line_len({}).",
1052                    row
1053                );
1054            }
1055
1056            for _ in 0..10 {
1057                let end_ix = snapshot.clip_offset(rng.gen_range(0..=snapshot.len()), Bias::Right);
1058                let start_ix = snapshot.clip_offset(rng.gen_range(0..=end_ix), Bias::Left);
1059
1060                assert_eq!(
1061                    snapshot
1062                        .text_for_range(start_ix..end_ix)
1063                        .collect::<String>(),
1064                    &expected_text[start_ix..end_ix],
1065                    "incorrect text for range {:?}",
1066                    start_ix..end_ix
1067                );
1068
1069                let expected_summary = TextSummary::from(&expected_text[start_ix..end_ix]);
1070                assert_eq!(
1071                    snapshot.text_summary_for_range::<TextSummary, _>(start_ix..end_ix),
1072                    expected_summary,
1073                    "incorrect summary for range {:?}",
1074                    start_ix..end_ix
1075                );
1076            }
1077        }
1078
1079        let snapshot = list.read(cx).snapshot(cx);
1080        for (old_snapshot, subscription) in old_versions {
1081            let edits = subscription.consume().into_inner();
1082
1083            log::info!(
1084                "applying edits since old text: {:?}: {:?}",
1085                old_snapshot.text(),
1086                edits,
1087            );
1088
1089            let mut text = old_snapshot.text();
1090            for edit in edits {
1091                let new_text: String = snapshot.text_for_range(edit.new.clone()).collect();
1092                text.replace_range(edit.new.start..edit.new.start + edit.old.len(), &new_text);
1093            }
1094            assert_eq!(text.to_string(), snapshot.text());
1095        }
1096    }
1097
1098    #[gpui::test(iterations = 100)]
1099    fn test_location(mut rng: StdRng) {
1100        let mut lhs = Default::default();
1101        let mut rhs = Default::default();
1102        while lhs == rhs {
1103            lhs = Location(
1104                (0..rng.gen_range(1..=5))
1105                    .map(|_| rng.gen_range(0..=100))
1106                    .collect(),
1107            );
1108            rhs = Location(
1109                (0..rng.gen_range(1..=5))
1110                    .map(|_| rng.gen_range(0..=100))
1111                    .collect(),
1112            );
1113        }
1114
1115        if lhs > rhs {
1116            mem::swap(&mut lhs, &mut rhs);
1117        }
1118
1119        let middle = Location::between(&lhs, &rhs);
1120        assert!(middle > lhs);
1121        assert!(middle < rhs);
1122        for ix in 0..middle.0.len() - 1 {
1123            assert!(
1124                middle.0[ix] == *lhs.0.get(ix).unwrap_or(&0)
1125                    || middle.0[ix] == *rhs.0.get(ix).unwrap_or(&0)
1126            );
1127        }
1128    }
1129}