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}