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