1mod anchor;
2mod selection;
3
4pub use anchor::{Anchor, AnchorRangeExt};
5use anyhow::Result;
6use clock::ReplicaId;
7use collections::HashMap;
8use gpui::{AppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task};
9use language::{
10 Buffer, BufferChunks, BufferSnapshot, Chunk, DiagnosticEntry, Event, File, Language,
11 ToOffset as _, ToPoint as _, TransactionId,
12};
13pub use selection::SelectionSet;
14use std::{
15 cell::{Ref, RefCell},
16 cmp, io,
17 iter::Peekable,
18 ops::{Range, Sub},
19 sync::Arc,
20 time::SystemTime,
21};
22use sum_tree::{Bias, Cursor, SumTree};
23use text::{
24 locator::Locator,
25 rope::TextDimension,
26 subscription::{Subscription, Topic},
27 AnchorRangeExt as _, Edit, Point, PointUtf16, SelectionSetId, TextSummary,
28};
29use theme::SyntaxTheme;
30
31const NEWLINES: &'static [u8] = &[b'\n'; u8::MAX as usize];
32
33pub type ExcerptId = Locator;
34
35pub struct MultiBuffer {
36 snapshot: RefCell<MultiBufferSnapshot>,
37 buffers: HashMap<usize, BufferState>,
38 subscriptions: Topic,
39 selection_sets: HashMap<SelectionSetId, SelectionSet>,
40 singleton: bool,
41 replica_id: ReplicaId,
42}
43
44pub trait ToOffset: 'static {
45 fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize;
46}
47
48pub trait ToPoint: 'static {
49 fn to_point<'a>(&self, snapshot: &MultiBufferSnapshot) -> Point;
50}
51
52#[derive(Debug)]
53struct BufferState {
54 buffer: ModelHandle<Buffer>,
55 last_version: clock::Global,
56 last_parse_count: usize,
57 last_diagnostics_update_count: usize,
58 excerpts: Vec<ExcerptId>,
59}
60
61#[derive(Clone, Default)]
62pub struct MultiBufferSnapshot {
63 excerpts: SumTree<Excerpt>,
64}
65
66pub struct ExcerptProperties<'a, T> {
67 buffer: &'a ModelHandle<Buffer>,
68 range: Range<T>,
69 header_height: u8,
70}
71
72#[derive(Clone)]
73struct Excerpt {
74 id: ExcerptId,
75 buffer: BufferSnapshot,
76 range: Range<text::Anchor>,
77 text_summary: TextSummary,
78 header_height: u8,
79 has_trailing_newline: bool,
80}
81
82#[derive(Clone, Debug, Default)]
83struct ExcerptSummary {
84 excerpt_id: ExcerptId,
85 text: TextSummary,
86}
87
88pub struct MultiBufferChunks<'a> {
89 range: Range<usize>,
90 cursor: Cursor<'a, Excerpt, usize>,
91 header_height: u8,
92 has_trailing_newline: bool,
93 excerpt_chunks: Option<BufferChunks<'a>>,
94 theme: Option<&'a SyntaxTheme>,
95}
96
97pub struct MultiBufferBytes<'a> {
98 chunks: Peekable<MultiBufferChunks<'a>>,
99}
100
101impl MultiBuffer {
102 pub fn new(replica_id: ReplicaId) -> Self {
103 Self {
104 snapshot: Default::default(),
105 buffers: Default::default(),
106 subscriptions: Default::default(),
107 selection_sets: Default::default(),
108 singleton: false,
109 replica_id,
110 }
111 }
112
113 pub fn singleton(buffer: ModelHandle<Buffer>, cx: &mut ModelContext<Self>) -> Self {
114 let mut this = Self::new(buffer.read(cx).replica_id());
115 this.singleton = true;
116 this.push_excerpt(
117 ExcerptProperties {
118 buffer: &buffer,
119 range: text::Anchor::min()..text::Anchor::max(),
120 header_height: 0,
121 },
122 cx,
123 );
124 this
125 }
126
127 pub fn build_simple(text: &str, cx: &mut MutableAppContext) -> ModelHandle<Self> {
128 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
129 cx.add_model(|cx| Self::singleton(buffer, cx))
130 }
131
132 pub fn replica_id(&self) -> ReplicaId {
133 self.replica_id
134 }
135
136 pub fn snapshot(&self, cx: &AppContext) -> MultiBufferSnapshot {
137 self.sync(cx);
138 self.snapshot.borrow().clone()
139 }
140
141 pub fn read(&self, cx: &AppContext) -> Ref<MultiBufferSnapshot> {
142 self.sync(cx);
143 self.snapshot.borrow()
144 }
145
146 pub fn as_singleton(&self) -> Option<&ModelHandle<Buffer>> {
147 if self.buffers.len() == 1 {
148 return Some(&self.buffers.values().next().unwrap().buffer);
149 } else {
150 None
151 }
152 }
153
154 pub fn subscribe(&mut self) -> Subscription {
155 self.subscriptions.subscribe()
156 }
157
158 pub fn edit<I, S, T>(&mut self, ranges_iter: I, new_text: T, cx: &mut ModelContext<Self>)
159 where
160 I: IntoIterator<Item = Range<S>>,
161 S: ToOffset,
162 T: Into<String>,
163 {
164 self.edit_internal(ranges_iter, new_text, false, cx)
165 }
166
167 pub fn edit_with_autoindent<I, S, T>(
168 &mut self,
169 ranges_iter: I,
170 new_text: T,
171 cx: &mut ModelContext<Self>,
172 ) where
173 I: IntoIterator<Item = Range<S>>,
174 S: ToOffset,
175 T: Into<String>,
176 {
177 self.edit_internal(ranges_iter, new_text, true, cx)
178 }
179
180 pub fn edit_internal<I, S, T>(
181 &mut self,
182 ranges_iter: I,
183 new_text: T,
184 autoindent: bool,
185 cx: &mut ModelContext<Self>,
186 ) where
187 I: IntoIterator<Item = Range<S>>,
188 S: ToOffset,
189 T: Into<String>,
190 {
191 // TODO
192 let snapshot = self.read(cx);
193 let ranges_iter = ranges_iter
194 .into_iter()
195 .map(|range| range.start.to_offset(&snapshot)..range.end.to_offset(&snapshot));
196 self.as_singleton().unwrap().update(cx, |buffer, cx| {
197 if autoindent {
198 buffer.edit_with_autoindent(ranges_iter, new_text, cx);
199 } else {
200 buffer.edit(ranges_iter, new_text, cx);
201 }
202 });
203 }
204
205 pub fn start_transaction(&mut self, cx: &mut ModelContext<Self>) -> Option<TransactionId> {
206 // TODO
207 self.as_singleton()
208 .unwrap()
209 .update(cx, |buffer, _| buffer.start_transaction())
210 }
211
212 pub fn end_transaction(&mut self, cx: &mut ModelContext<Self>) -> Option<TransactionId> {
213 // TODO
214 self.as_singleton()
215 .unwrap()
216 .update(cx, |buffer, cx| buffer.end_transaction(cx))
217 }
218
219 pub fn undo(&mut self, cx: &mut ModelContext<Self>) -> Option<TransactionId> {
220 // TODO
221 self.as_singleton()
222 .unwrap()
223 .update(cx, |buffer, cx| buffer.undo(cx))
224 }
225
226 pub fn redo(&mut self, cx: &mut ModelContext<Self>) -> Option<TransactionId> {
227 // TODO
228 self.as_singleton()
229 .unwrap()
230 .update(cx, |buffer, cx| buffer.redo(cx))
231 }
232
233 pub fn push_excerpt<O>(
234 &mut self,
235 props: ExcerptProperties<O>,
236 cx: &mut ModelContext<Self>,
237 ) -> ExcerptId
238 where
239 O: text::ToOffset,
240 {
241 self.sync(cx);
242
243 let buffer = &props.buffer;
244 cx.subscribe(buffer, Self::on_buffer_event).detach();
245
246 let buffer = props.buffer.read(cx);
247 let range = buffer.anchor_before(&props.range.start)..buffer.anchor_after(&props.range.end);
248 let mut snapshot = self.snapshot.borrow_mut();
249 let prev_id = snapshot.excerpts.last().map(|e| &e.id);
250 let id = ExcerptId::between(prev_id.unwrap_or(&ExcerptId::min()), &ExcerptId::max());
251
252 let edit_start = snapshot.excerpts.summary().text.bytes;
253 let excerpt = Excerpt::new(
254 id.clone(),
255 buffer.snapshot(),
256 range,
257 props.header_height,
258 !self.singleton,
259 );
260 let edit = Edit {
261 old: edit_start..edit_start,
262 new: edit_start..edit_start + excerpt.text_summary.bytes,
263 };
264 snapshot.excerpts.push(excerpt, &());
265 self.buffers
266 .entry(props.buffer.id())
267 .or_insert_with(|| BufferState {
268 buffer: props.buffer.clone(),
269 last_version: buffer.version(),
270 last_parse_count: buffer.parse_count(),
271 last_diagnostics_update_count: buffer.diagnostics_update_count(),
272 excerpts: Default::default(),
273 })
274 .excerpts
275 .push(id.clone());
276
277 self.subscriptions.publish_mut([edit]);
278
279 id
280 }
281
282 fn on_buffer_event(
283 &mut self,
284 _: ModelHandle<Buffer>,
285 event: &Event,
286 cx: &mut ModelContext<Self>,
287 ) {
288 cx.emit(event.clone());
289 }
290
291 pub fn save(
292 &mut self,
293 cx: &mut ModelContext<Self>,
294 ) -> Result<Task<Result<(clock::Global, SystemTime)>>> {
295 self.as_singleton()
296 .unwrap()
297 .update(cx, |buffer, cx| buffer.save(cx))
298 }
299
300 pub fn language<'a>(&self, cx: &'a AppContext) -> Option<&'a Arc<Language>> {
301 self.as_singleton().unwrap().read(cx).language()
302 }
303
304 pub fn file<'a>(&self, cx: &'a AppContext) -> Option<&'a dyn File> {
305 self.as_singleton()
306 .and_then(|buffer| buffer.read(cx).file())
307 }
308
309 pub fn is_dirty(&self, cx: &AppContext) -> bool {
310 self.as_singleton().unwrap().read(cx).is_dirty()
311 }
312
313 pub fn has_conflict(&self, cx: &AppContext) -> bool {
314 self.as_singleton().unwrap().read(cx).has_conflict()
315 }
316
317 pub fn is_parsing(&self, cx: &AppContext) -> bool {
318 self.as_singleton().unwrap().read(cx).is_parsing()
319 }
320
321 fn sync(&self, cx: &AppContext) {
322 let mut snapshot = self.snapshot.borrow_mut();
323 let mut excerpts_to_edit = Vec::new();
324 for buffer_state in self.buffers.values() {
325 let buffer = buffer_state.buffer.read(cx);
326 let buffer_changed = buffer.version().gt(&buffer_state.last_version);
327 if buffer_changed
328 || buffer.parse_count() > buffer_state.last_parse_count
329 || buffer.diagnostics_update_count() > buffer_state.last_diagnostics_update_count
330 {
331 excerpts_to_edit.extend(
332 buffer_state
333 .excerpts
334 .iter()
335 .map(|excerpt_id| (excerpt_id, buffer_state, buffer_changed)),
336 );
337 }
338 }
339 excerpts_to_edit.sort_unstable_by_key(|(excerpt_id, _, _)| *excerpt_id);
340
341 let mut edits = Vec::new();
342 let mut new_excerpts = SumTree::new();
343 let mut cursor = snapshot.excerpts.cursor::<(Option<&ExcerptId>, usize)>();
344
345 for (id, buffer_state, buffer_changed) in excerpts_to_edit {
346 new_excerpts.push_tree(cursor.slice(&Some(id), Bias::Left, &()), &());
347 let old_excerpt = cursor.item().unwrap();
348 let buffer = buffer_state.buffer.read(cx);
349
350 let mut new_excerpt;
351 if buffer_changed {
352 edits.extend(
353 buffer
354 .edits_since_in_range::<usize>(
355 old_excerpt.buffer.version(),
356 old_excerpt.range.clone(),
357 )
358 .map(|mut edit| {
359 let excerpt_old_start =
360 cursor.start().1 + old_excerpt.header_height as usize;
361 let excerpt_new_start = new_excerpts.summary().text.bytes
362 + old_excerpt.header_height as usize;
363 edit.old.start += excerpt_old_start;
364 edit.old.end += excerpt_old_start;
365 edit.new.start += excerpt_new_start;
366 edit.new.end += excerpt_new_start;
367 edit
368 }),
369 );
370
371 new_excerpt = Excerpt::new(
372 id.clone(),
373 buffer.snapshot(),
374 old_excerpt.range.clone(),
375 old_excerpt.header_height,
376 !self.singleton,
377 );
378 } else {
379 new_excerpt = old_excerpt.clone();
380 new_excerpt.buffer = buffer.snapshot();
381 }
382
383 new_excerpts.push(new_excerpt, &());
384 cursor.next(&());
385 }
386 new_excerpts.push_tree(cursor.suffix(&()), &());
387
388 drop(cursor);
389 snapshot.excerpts = new_excerpts;
390
391 self.subscriptions.publish(edits);
392 }
393}
394
395#[cfg(any(test, feature = "test-support"))]
396impl MultiBuffer {
397 pub fn randomly_edit<R: rand::Rng>(
398 &mut self,
399 rng: &mut R,
400 count: usize,
401 cx: &mut ModelContext<Self>,
402 ) {
403 self.as_singleton()
404 .unwrap()
405 .update(cx, |buffer, cx| buffer.randomly_edit(rng, count, cx));
406 self.sync(cx);
407 }
408}
409
410impl Entity for MultiBuffer {
411 type Event = language::Event;
412}
413
414impl MultiBufferSnapshot {
415 pub fn text(&self) -> String {
416 self.chunks(0..self.len(), None)
417 .map(|chunk| chunk.text)
418 .collect()
419 }
420
421 pub fn reversed_chars_at<'a, T: ToOffset>(
422 &'a self,
423 position: T,
424 ) -> impl Iterator<Item = char> + 'a {
425 // TODO
426 let offset = position.to_offset(self);
427 self.as_singleton().unwrap().reversed_chars_at(offset)
428 }
429
430 pub fn chars_at<'a, T: ToOffset>(&'a self, position: T) -> impl Iterator<Item = char> + 'a {
431 let offset = position.to_offset(self);
432 self.text_for_range(offset..self.len())
433 .flat_map(|chunk| chunk.chars())
434 }
435
436 pub fn text_for_range<'a, T: ToOffset>(
437 &'a self,
438 range: Range<T>,
439 ) -> impl Iterator<Item = &'a str> {
440 self.chunks(range, None).map(|chunk| chunk.text)
441 }
442
443 pub fn is_line_blank(&self, row: u32) -> bool {
444 self.text_for_range(Point::new(row, 0)..Point::new(row, self.line_len(row)))
445 .all(|chunk| chunk.matches(|c: char| !c.is_whitespace()).next().is_none())
446 }
447
448 pub fn contains_str_at<T>(&self, position: T, needle: &str) -> bool
449 where
450 T: ToOffset,
451 {
452 let offset = position.to_offset(self);
453 self.as_singleton().unwrap().contains_str_at(offset, needle)
454 }
455
456 fn as_singleton(&self) -> Option<&BufferSnapshot> {
457 let mut excerpts = self.excerpts.iter();
458 let buffer = excerpts.next().map(|excerpt| &excerpt.buffer);
459 if excerpts.next().is_none() {
460 buffer
461 } else {
462 None
463 }
464 }
465
466 pub fn len(&self) -> usize {
467 self.excerpts.summary().text.bytes
468 }
469
470 pub fn clip_offset(&self, offset: usize, bias: Bias) -> usize {
471 let mut cursor = self.excerpts.cursor::<usize>();
472 cursor.seek(&offset, Bias::Right, &());
473 if let Some(excerpt) = cursor.item() {
474 let start_after_header = *cursor.start() + excerpt.header_height as usize;
475 if offset < start_after_header {
476 *cursor.start()
477 } else {
478 let excerpt_start = excerpt.range.start.to_offset(&excerpt.buffer);
479 let buffer_offset = excerpt
480 .buffer
481 .clip_offset(excerpt_start + (offset - start_after_header), bias);
482 let offset_in_excerpt = if buffer_offset > excerpt_start {
483 buffer_offset - excerpt_start
484 } else {
485 0
486 };
487 start_after_header + offset_in_excerpt
488 }
489 } else {
490 self.excerpts.summary().text.bytes
491 }
492 }
493
494 pub fn clip_point(&self, point: Point, bias: Bias) -> Point {
495 let mut cursor = self.excerpts.cursor::<Point>();
496 cursor.seek(&point, Bias::Right, &());
497 if let Some(excerpt) = cursor.item() {
498 let start_after_header = *cursor.start() + Point::new(excerpt.header_height as u32, 0);
499 if point < start_after_header {
500 *cursor.start()
501 } else {
502 let excerpt_start = excerpt.range.start.to_point(&excerpt.buffer);
503 let buffer_point = excerpt
504 .buffer
505 .clip_point(excerpt_start + (point - start_after_header), bias);
506 let point_in_excerpt = if buffer_point > excerpt_start {
507 buffer_point - excerpt_start
508 } else {
509 Point::zero()
510 };
511 start_after_header + point_in_excerpt
512 }
513 } else {
514 self.excerpts.summary().text.lines
515 }
516 }
517
518 pub fn clip_point_utf16(&self, point: PointUtf16, bias: Bias) -> PointUtf16 {
519 let mut cursor = self.excerpts.cursor::<PointUtf16>();
520 cursor.seek(&point, Bias::Right, &());
521 if let Some(excerpt) = cursor.item() {
522 let start_after_header =
523 *cursor.start() + PointUtf16::new(excerpt.header_height as u32, 0);
524 if point < start_after_header {
525 *cursor.start()
526 } else {
527 let excerpt_start = excerpt
528 .buffer
529 .offset_to_point_utf16(excerpt.range.start.to_offset(&excerpt.buffer));
530 let buffer_point = excerpt
531 .buffer
532 .clip_point_utf16(excerpt_start + (point - start_after_header), bias);
533 let point_in_excerpt = if buffer_point > excerpt_start {
534 buffer_point - excerpt_start
535 } else {
536 PointUtf16::new(0, 0)
537 };
538 start_after_header + point_in_excerpt
539 }
540 } else {
541 self.excerpts.summary().text.lines_utf16
542 }
543 }
544
545 pub fn bytes_in_range<'a, T: ToOffset>(&'a self, range: Range<T>) -> MultiBufferBytes<'a> {
546 MultiBufferBytes {
547 chunks: self.chunks(range, None).peekable(),
548 }
549 }
550
551 pub fn chunks<'a, T: ToOffset>(
552 &'a self,
553 range: Range<T>,
554 theme: Option<&'a SyntaxTheme>,
555 ) -> MultiBufferChunks<'a> {
556 let mut result = MultiBufferChunks {
557 range: 0..range.end.to_offset(self),
558 cursor: self.excerpts.cursor::<usize>(),
559 header_height: 0,
560 excerpt_chunks: None,
561 has_trailing_newline: false,
562 theme,
563 };
564 result.seek(range.start.to_offset(self));
565 result
566 }
567
568 pub fn offset_to_point(&self, offset: usize) -> Point {
569 let mut cursor = self.excerpts.cursor::<(usize, Point)>();
570 cursor.seek(&offset, Bias::Right, &());
571 if let Some(excerpt) = cursor.item() {
572 let (start_offset, start_point) = cursor.start();
573 let overshoot = offset - start_offset;
574 let header_height = excerpt.header_height as usize;
575 if overshoot < header_height {
576 *start_point
577 } else {
578 let excerpt_start_offset = excerpt.range.start.to_offset(&excerpt.buffer);
579 let excerpt_start_point = excerpt.range.start.to_point(&excerpt.buffer);
580 let buffer_point = excerpt
581 .buffer
582 .offset_to_point(excerpt_start_offset + (overshoot - header_height));
583 *start_point
584 + Point::new(header_height as u32, 0)
585 + (buffer_point - excerpt_start_point)
586 }
587 } else {
588 self.excerpts.summary().text.lines
589 }
590 }
591
592 pub fn point_to_offset(&self, point: Point) -> usize {
593 let mut cursor = self.excerpts.cursor::<(Point, usize)>();
594 cursor.seek(&point, Bias::Right, &());
595 if let Some(excerpt) = cursor.item() {
596 let (start_point, start_offset) = cursor.start();
597 let overshoot = point - start_point;
598 let header_height = Point::new(excerpt.header_height as u32, 0);
599 if overshoot < header_height {
600 *start_offset
601 } else {
602 let excerpt_start_offset = excerpt.range.start.to_offset(&excerpt.buffer);
603 let excerpt_start_point = excerpt.range.start.to_point(&excerpt.buffer);
604 let buffer_offset = excerpt
605 .buffer
606 .point_to_offset(excerpt_start_point + (overshoot - header_height));
607 *start_offset + excerpt.header_height as usize + buffer_offset
608 - excerpt_start_offset
609 }
610 } else {
611 self.excerpts.summary().text.bytes
612 }
613 }
614
615 pub fn point_utf16_to_offset(&self, point: PointUtf16) -> usize {
616 let mut cursor = self.excerpts.cursor::<(PointUtf16, usize)>();
617 cursor.seek(&point, Bias::Right, &());
618 if let Some(excerpt) = cursor.item() {
619 let (start_point, start_offset) = cursor.start();
620 let overshoot = point - start_point;
621 let header_height = PointUtf16::new(excerpt.header_height as u32, 0);
622 if overshoot < header_height {
623 *start_offset
624 } else {
625 let excerpt_start_offset = excerpt.range.start.to_offset(&excerpt.buffer);
626 let excerpt_start_point = excerpt
627 .buffer
628 .offset_to_point_utf16(excerpt.range.start.to_offset(&excerpt.buffer));
629 let buffer_offset = excerpt
630 .buffer
631 .point_utf16_to_offset(excerpt_start_point + (overshoot - header_height));
632 *start_offset
633 + excerpt.header_height as usize
634 + (buffer_offset - excerpt_start_offset)
635 }
636 } else {
637 self.excerpts.summary().text.bytes
638 }
639 }
640
641 pub fn indent_column_for_line(&self, row: u32) -> u32 {
642 if let Some((buffer, range)) = self.buffer_line_for_row(row) {
643 buffer
644 .indent_column_for_line(range.start.row)
645 .min(range.end.column)
646 .saturating_sub(range.start.column)
647 } else {
648 0
649 }
650 }
651
652 pub fn line_len(&self, row: u32) -> u32 {
653 if let Some((_, range)) = self.buffer_line_for_row(row) {
654 range.end.column - range.start.column
655 } else {
656 0
657 }
658 }
659
660 fn buffer_line_for_row(&self, row: u32) -> Option<(&BufferSnapshot, Range<Point>)> {
661 let mut cursor = self.excerpts.cursor::<Point>();
662 cursor.seek(&Point::new(row, 0), Bias::Right, &());
663 if let Some(excerpt) = cursor.item() {
664 let overshoot = row - cursor.start().row;
665 let header_height = excerpt.header_height as u32;
666 if overshoot >= header_height {
667 let excerpt_start = excerpt.range.start.to_point(&excerpt.buffer);
668 let excerpt_end = excerpt.range.end.to_point(&excerpt.buffer);
669 let buffer_row = excerpt_start.row + overshoot - header_height;
670 let line_start = Point::new(buffer_row, 0);
671 let line_end = Point::new(buffer_row, excerpt.buffer.line_len(buffer_row));
672 return Some((
673 &excerpt.buffer,
674 line_start.max(excerpt_start)..line_end.min(excerpt_end),
675 ));
676 }
677 }
678 None
679 }
680
681 pub fn max_point(&self) -> Point {
682 self.text_summary().lines
683 }
684
685 pub fn text_summary(&self) -> TextSummary {
686 self.excerpts.summary().text
687 }
688
689 pub fn text_summary_for_range<'a, D, O>(&'a self, range: Range<O>) -> D
690 where
691 D: TextDimension,
692 O: ToOffset,
693 {
694 let mut summary = D::default();
695 let mut range = range.start.to_offset(self)..range.end.to_offset(self);
696 let mut cursor = self.excerpts.cursor::<usize>();
697 cursor.seek(&range.start, Bias::Right, &());
698 if let Some(excerpt) = cursor.item() {
699 let start_after_header = cursor.start() + excerpt.header_height as usize;
700 if range.start < start_after_header {
701 let header_len = cmp::min(range.end, start_after_header) - range.start;
702 summary.add_assign(&D::from_text_summary(&TextSummary {
703 bytes: header_len,
704 lines: Point::new(header_len as u32, 0),
705 lines_utf16: PointUtf16::new(header_len as u32, 0),
706 first_line_chars: 0,
707 last_line_chars: 0,
708 longest_row: 0,
709 longest_row_chars: 0,
710 }));
711 range.start = start_after_header;
712 range.end = cmp::max(range.start, range.end);
713 }
714
715 let mut end_before_newline = cursor.end(&());
716 if excerpt.has_trailing_newline {
717 end_before_newline -= 1;
718 }
719
720 let excerpt_start = excerpt.range.start.to_offset(&excerpt.buffer);
721 let start_in_excerpt = excerpt_start + (range.start - start_after_header);
722 let end_in_excerpt =
723 excerpt_start + (cmp::min(end_before_newline, range.end) - start_after_header);
724 summary.add_assign(
725 &excerpt
726 .buffer
727 .text_summary_for_range(start_in_excerpt..end_in_excerpt),
728 );
729
730 if range.end > end_before_newline {
731 summary.add_assign(&D::from_text_summary(&TextSummary {
732 bytes: 1,
733 lines: Point::new(1 as u32, 0),
734 lines_utf16: PointUtf16::new(1 as u32, 0),
735 first_line_chars: 0,
736 last_line_chars: 0,
737 longest_row: 0,
738 longest_row_chars: 0,
739 }));
740 }
741
742 cursor.next(&());
743 }
744
745 if range.end > *cursor.start() {
746 summary.add_assign(&D::from_text_summary(&cursor.summary::<_, TextSummary>(
747 &range.end,
748 Bias::Right,
749 &(),
750 )));
751 if let Some(excerpt) = cursor.item() {
752 let start_after_header = cursor.start() + excerpt.header_height as usize;
753 let header_len =
754 cmp::min(range.end - cursor.start(), excerpt.header_height as usize);
755 summary.add_assign(&D::from_text_summary(&TextSummary {
756 bytes: header_len,
757 lines: Point::new(header_len as u32, 0),
758 lines_utf16: PointUtf16::new(header_len as u32, 0),
759 first_line_chars: 0,
760 last_line_chars: 0,
761 longest_row: 0,
762 longest_row_chars: 0,
763 }));
764 range.end = cmp::max(start_after_header, range.end);
765
766 let excerpt_start = excerpt.range.start.to_offset(&excerpt.buffer);
767 let end_in_excerpt = excerpt_start + (range.end - start_after_header);
768 summary.add_assign(
769 &excerpt
770 .buffer
771 .text_summary_for_range(excerpt_start..end_in_excerpt),
772 );
773 cursor.next(&());
774 }
775 }
776
777 summary
778 }
779
780 pub fn summary_for_anchor<D>(&self, anchor: &Anchor) -> D
781 where
782 D: TextDimension + Ord + Sub<D, Output = D>,
783 {
784 let mut cursor = self.excerpts.cursor::<ExcerptSummary>();
785 cursor.seek(&Some(&anchor.excerpt_id), Bias::Left, &());
786 if let Some(excerpt) = cursor.item() {
787 if excerpt.id == anchor.excerpt_id {
788 let mut excerpt_start = D::from_text_summary(&cursor.start().text);
789 excerpt_start.add_summary(&excerpt.header_summary(), &());
790 let excerpt_buffer_start = excerpt.range.start.summary::<D>(&excerpt.buffer);
791 let buffer_point = anchor.text_anchor.summary::<D>(&excerpt.buffer);
792 if buffer_point > excerpt_buffer_start {
793 excerpt_start.add_assign(&(buffer_point - excerpt_buffer_start));
794 }
795 return excerpt_start;
796 }
797 }
798 D::from_text_summary(&cursor.start().text)
799 }
800
801 pub fn summaries_for_anchors<'a, D, I>(&'a self, anchors: I) -> Vec<D>
802 where
803 D: TextDimension + Ord + Sub<D, Output = D>,
804 I: 'a + IntoIterator<Item = &'a Anchor>,
805 {
806 let mut anchors = anchors.into_iter().peekable();
807 let mut cursor = self.excerpts.cursor::<ExcerptSummary>();
808 let mut summaries = Vec::new();
809 while let Some(anchor) = anchors.peek() {
810 let excerpt_id = &anchor.excerpt_id;
811 cursor.seek(&Some(excerpt_id), Bias::Left, &());
812 if let Some(excerpt) = cursor.item() {
813 let excerpt_exists = excerpt.id == *excerpt_id;
814 let excerpt_anchors = std::iter::from_fn(|| {
815 let anchor = anchors.peek()?;
816 if anchor.excerpt_id == *excerpt_id {
817 Some(&anchors.next().unwrap().text_anchor)
818 } else {
819 None
820 }
821 });
822
823 if excerpt_exists {
824 let mut excerpt_start = D::from_text_summary(&cursor.start().text);
825 excerpt_start.add_summary(&excerpt.header_summary(), &());
826 let excerpt_buffer_start = excerpt.range.start.summary::<D>(&excerpt.buffer);
827 summaries.extend(
828 excerpt
829 .buffer
830 .summaries_for_anchors::<D, _>(excerpt_anchors)
831 .map(move |summary| {
832 let mut excerpt_start = excerpt_start.clone();
833 let excerpt_buffer_start = excerpt_buffer_start.clone();
834 if summary > excerpt_buffer_start {
835 excerpt_start.add_assign(&(summary - excerpt_buffer_start));
836 }
837 excerpt_start
838 }),
839 );
840 } else {
841 excerpt_anchors.for_each(drop);
842 }
843 } else {
844 break;
845 }
846 }
847
848 summaries
849 }
850
851 pub fn anchor_before<T: ToOffset>(&self, position: T) -> Anchor {
852 self.anchor_at(position, Bias::Left)
853 }
854
855 pub fn anchor_after<T: ToOffset>(&self, position: T) -> Anchor {
856 self.anchor_at(position, Bias::Right)
857 }
858
859 pub fn anchor_at<T: ToOffset>(&self, position: T, bias: Bias) -> Anchor {
860 let offset = position.to_offset(self);
861 let mut cursor = self.excerpts.cursor::<(usize, Option<&ExcerptId>)>();
862 cursor.seek(&offset, bias, &());
863 if let Some(excerpt) = cursor.item() {
864 let overshoot =
865 (offset - cursor.start().0).saturating_sub(excerpt.header_height as usize);
866 let buffer_start = excerpt.range.start.to_offset(&excerpt.buffer);
867 Anchor {
868 excerpt_id: excerpt.id.clone(),
869 text_anchor: excerpt.buffer.anchor_at(buffer_start + overshoot, bias),
870 }
871 } else if offset == 0 && bias == Bias::Left {
872 Anchor::min()
873 } else {
874 Anchor::max()
875 }
876 }
877
878 pub fn parse_count(&self) -> usize {
879 self.as_singleton().unwrap().parse_count()
880 }
881
882 pub fn enclosing_bracket_ranges<T: ToOffset>(
883 &self,
884 range: Range<T>,
885 ) -> Option<(Range<usize>, Range<usize>)> {
886 let range = range.start.to_offset(self)..range.end.to_offset(self);
887 self.as_singleton().unwrap().enclosing_bracket_ranges(range)
888 }
889
890 pub fn diagnostics_update_count(&self) -> usize {
891 self.as_singleton().unwrap().diagnostics_update_count()
892 }
893
894 pub fn language(&self) -> Option<&Arc<Language>> {
895 self.as_singleton().unwrap().language()
896 }
897
898 pub fn diagnostic_group<'a, O>(
899 &'a self,
900 group_id: usize,
901 ) -> impl Iterator<Item = DiagnosticEntry<O>> + 'a
902 where
903 O: text::FromAnchor + 'a,
904 {
905 self.as_singleton().unwrap().diagnostic_group(group_id)
906 }
907
908 pub fn diagnostics_in_range<'a, T, O>(
909 &'a self,
910 range: Range<T>,
911 ) -> impl Iterator<Item = DiagnosticEntry<O>> + 'a
912 where
913 T: 'a + ToOffset,
914 O: 'a + text::FromAnchor,
915 {
916 let range = range.start.to_offset(self)..range.end.to_offset(self);
917 self.as_singleton().unwrap().diagnostics_in_range(range)
918 }
919
920 pub fn range_for_syntax_ancestor<T: ToOffset>(&self, range: Range<T>) -> Option<Range<usize>> {
921 let range = range.start.to_offset(self)..range.end.to_offset(self);
922 self.as_singleton()
923 .unwrap()
924 .range_for_syntax_ancestor(range)
925 }
926
927 fn buffer_snapshot_for_excerpt<'a>(
928 &'a self,
929 excerpt_id: &'a ExcerptId,
930 ) -> Option<&'a BufferSnapshot> {
931 let mut cursor = self.excerpts.cursor::<Option<&ExcerptId>>();
932 cursor.seek(&Some(excerpt_id), Bias::Left, &());
933 if let Some(excerpt) = cursor.item() {
934 if excerpt.id == *excerpt_id {
935 return Some(&excerpt.buffer);
936 }
937 }
938 None
939 }
940}
941
942impl Excerpt {
943 fn new(
944 id: ExcerptId,
945 buffer: BufferSnapshot,
946 range: Range<text::Anchor>,
947 header_height: u8,
948 has_trailing_newline: bool,
949 ) -> Self {
950 let mut text_summary =
951 buffer.text_summary_for_range::<TextSummary, _>(range.to_offset(&buffer));
952 if header_height > 0 {
953 text_summary.first_line_chars = 0;
954 text_summary.lines.row += header_height as u32;
955 text_summary.lines_utf16.row += header_height as u32;
956 text_summary.bytes += header_height as usize;
957 text_summary.longest_row += header_height as u32;
958 }
959 if has_trailing_newline {
960 text_summary.last_line_chars = 0;
961 text_summary.lines.row += 1;
962 text_summary.lines.column = 0;
963 text_summary.lines_utf16.row += 1;
964 text_summary.lines_utf16.column = 0;
965 text_summary.bytes += 1;
966 }
967
968 Excerpt {
969 id,
970 buffer,
971 range,
972 text_summary,
973 header_height,
974 has_trailing_newline,
975 }
976 }
977
978 fn header_summary(&self) -> TextSummary {
979 TextSummary {
980 bytes: self.header_height as usize,
981 lines: Point::new(self.header_height as u32, 0),
982 lines_utf16: PointUtf16::new(self.header_height as u32, 0),
983 first_line_chars: 0,
984 last_line_chars: 0,
985 longest_row: 0,
986 longest_row_chars: 0,
987 }
988 }
989}
990
991impl sum_tree::Item for Excerpt {
992 type Summary = ExcerptSummary;
993
994 fn summary(&self) -> Self::Summary {
995 ExcerptSummary {
996 excerpt_id: self.id.clone(),
997 text: self.text_summary.clone(),
998 }
999 }
1000}
1001
1002impl sum_tree::Summary for ExcerptSummary {
1003 type Context = ();
1004
1005 fn add_summary(&mut self, summary: &Self, _: &()) {
1006 debug_assert!(summary.excerpt_id > self.excerpt_id);
1007 self.excerpt_id = summary.excerpt_id.clone();
1008 self.text.add_summary(&summary.text, &());
1009 }
1010}
1011
1012impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for TextSummary {
1013 fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
1014 *self += &summary.text;
1015 }
1016}
1017
1018impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for usize {
1019 fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
1020 *self += summary.text.bytes;
1021 }
1022}
1023
1024impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, ExcerptSummary> for usize {
1025 fn cmp(&self, cursor_location: &ExcerptSummary, _: &()) -> cmp::Ordering {
1026 Ord::cmp(self, &cursor_location.text.bytes)
1027 }
1028}
1029
1030impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, ExcerptSummary> for Option<&'a ExcerptId> {
1031 fn cmp(&self, cursor_location: &ExcerptSummary, _: &()) -> cmp::Ordering {
1032 Ord::cmp(self, &Some(&cursor_location.excerpt_id))
1033 }
1034}
1035
1036impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Point {
1037 fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
1038 *self += summary.text.lines;
1039 }
1040}
1041
1042impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for PointUtf16 {
1043 fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
1044 *self += summary.text.lines_utf16
1045 }
1046}
1047
1048impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Option<&'a ExcerptId> {
1049 fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
1050 *self = Some(&summary.excerpt_id);
1051 }
1052}
1053
1054impl<'a> MultiBufferChunks<'a> {
1055 pub fn offset(&self) -> usize {
1056 self.range.start
1057 }
1058
1059 pub fn seek(&mut self, offset: usize) {
1060 self.range.start = offset;
1061 self.cursor.seek_forward(&offset, Bias::Right, &());
1062 self.header_height = 0;
1063 self.excerpt_chunks = None;
1064 if let Some(excerpt) = self.cursor.item() {
1065 let buffer_range = excerpt.range.to_offset(&excerpt.buffer);
1066 self.header_height = excerpt.header_height;
1067 self.has_trailing_newline = excerpt.has_trailing_newline;
1068
1069 let buffer_start;
1070 let start_overshoot = self.range.start - self.cursor.start();
1071 if start_overshoot < excerpt.header_height as usize {
1072 self.header_height -= start_overshoot as u8;
1073 buffer_start = buffer_range.start;
1074 } else {
1075 buffer_start =
1076 buffer_range.start + start_overshoot - excerpt.header_height as usize;
1077 self.header_height = 0;
1078 }
1079
1080 let buffer_end;
1081 let end_overshoot = self.range.end - self.cursor.start();
1082 if end_overshoot < excerpt.header_height as usize {
1083 self.header_height -= excerpt.header_height - end_overshoot as u8;
1084 buffer_end = buffer_start;
1085 } else {
1086 buffer_end = cmp::min(
1087 buffer_range.end,
1088 buffer_range.start + end_overshoot - excerpt.header_height as usize,
1089 );
1090 }
1091
1092 self.excerpt_chunks = Some(excerpt.buffer.chunks(buffer_start..buffer_end, self.theme));
1093 }
1094 }
1095}
1096
1097impl<'a> Iterator for MultiBufferChunks<'a> {
1098 type Item = Chunk<'a>;
1099
1100 fn next(&mut self) -> Option<Self::Item> {
1101 loop {
1102 if self.header_height > 0 {
1103 let chunk = Chunk {
1104 text: unsafe {
1105 std::str::from_utf8_unchecked(&NEWLINES[..self.header_height as usize])
1106 },
1107 ..Default::default()
1108 };
1109 self.range.start += self.header_height as usize;
1110 self.header_height = 0;
1111 return Some(chunk);
1112 }
1113
1114 if let Some(excerpt_chunks) = self.excerpt_chunks.as_mut() {
1115 if let Some(chunk) = excerpt_chunks.next() {
1116 self.range.start += chunk.text.len();
1117 return Some(chunk);
1118 }
1119 self.excerpt_chunks.take();
1120 if self.has_trailing_newline && self.cursor.end(&()) <= self.range.end {
1121 self.range.start += 1;
1122 return Some(Chunk {
1123 text: "\n",
1124 ..Default::default()
1125 });
1126 }
1127 }
1128
1129 self.cursor.next(&());
1130 if *self.cursor.start() >= self.range.end {
1131 return None;
1132 }
1133
1134 let excerpt = self.cursor.item()?;
1135 let buffer_range = excerpt.range.to_offset(&excerpt.buffer);
1136
1137 let buffer_end = cmp::min(
1138 buffer_range.end,
1139 buffer_range.start + self.range.end
1140 - excerpt.header_height as usize
1141 - self.cursor.start(),
1142 );
1143
1144 self.header_height = excerpt.header_height;
1145 self.has_trailing_newline = excerpt.has_trailing_newline;
1146 self.excerpt_chunks = Some(
1147 excerpt
1148 .buffer
1149 .chunks(buffer_range.start..buffer_end, self.theme),
1150 );
1151 }
1152 }
1153}
1154
1155impl<'a> Iterator for MultiBufferBytes<'a> {
1156 type Item = &'a [u8];
1157
1158 fn next(&mut self) -> Option<Self::Item> {
1159 self.chunks.next().map(|chunk| chunk.text.as_bytes())
1160 }
1161}
1162
1163impl<'a> io::Read for MultiBufferBytes<'a> {
1164 fn read(&mut self, _: &mut [u8]) -> io::Result<usize> {
1165 todo!()
1166 }
1167}
1168
1169impl ToOffset for Point {
1170 fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize {
1171 snapshot.point_to_offset(*self)
1172 }
1173}
1174
1175impl ToOffset for PointUtf16 {
1176 fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize {
1177 snapshot.point_utf16_to_offset(*self)
1178 }
1179}
1180
1181impl ToOffset for usize {
1182 fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize {
1183 assert!(*self <= snapshot.len(), "offset is out of range");
1184 *self
1185 }
1186}
1187
1188impl ToPoint for usize {
1189 fn to_point<'a>(&self, snapshot: &MultiBufferSnapshot) -> Point {
1190 snapshot.offset_to_point(*self)
1191 }
1192}
1193
1194impl ToPoint for Point {
1195 fn to_point<'a>(&self, _: &MultiBufferSnapshot) -> Point {
1196 *self
1197 }
1198}
1199
1200#[cfg(test)]
1201mod tests {
1202 use super::*;
1203 use gpui::MutableAppContext;
1204 use language::Buffer;
1205 use rand::prelude::*;
1206 use std::env;
1207 use text::{Point, RandomCharIter};
1208 use util::test::sample_text;
1209
1210 #[gpui::test]
1211 fn test_singleton_multibuffer(cx: &mut MutableAppContext) {
1212 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6, 'a'), cx));
1213 let multibuffer = cx.add_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
1214 assert_eq!(
1215 multibuffer.read(cx).snapshot(cx).text(),
1216 buffer.read(cx).text()
1217 );
1218
1219 buffer.update(cx, |buffer, cx| buffer.edit([1..3], "XXX", cx));
1220 assert_eq!(
1221 multibuffer.read(cx).snapshot(cx).text(),
1222 buffer.read(cx).text()
1223 );
1224 }
1225
1226 #[gpui::test]
1227 fn test_excerpt_buffer(cx: &mut MutableAppContext) {
1228 let buffer_1 = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6, 'a'), cx));
1229 let buffer_2 = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6, 'g'), cx));
1230
1231 let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
1232
1233 let subscription = multibuffer.update(cx, |multibuffer, cx| {
1234 let subscription = multibuffer.subscribe();
1235 multibuffer.push_excerpt(
1236 ExcerptProperties {
1237 buffer: &buffer_1,
1238 range: Point::new(1, 2)..Point::new(2, 5),
1239 header_height: 2,
1240 },
1241 cx,
1242 );
1243 assert_eq!(
1244 subscription.consume().into_inner(),
1245 [Edit {
1246 old: 0..0,
1247 new: 0..13
1248 }]
1249 );
1250
1251 multibuffer.push_excerpt(
1252 ExcerptProperties {
1253 buffer: &buffer_1,
1254 range: Point::new(3, 3)..Point::new(4, 4),
1255 header_height: 1,
1256 },
1257 cx,
1258 );
1259 multibuffer.push_excerpt(
1260 ExcerptProperties {
1261 buffer: &buffer_2,
1262 range: Point::new(3, 1)..Point::new(3, 3),
1263 header_height: 3,
1264 },
1265 cx,
1266 );
1267 assert_eq!(
1268 subscription.consume().into_inner(),
1269 [Edit {
1270 old: 13..13,
1271 new: 13..29
1272 }]
1273 );
1274
1275 subscription
1276 });
1277
1278 assert_eq!(
1279 multibuffer.read(cx).snapshot(cx).text(),
1280 concat!(
1281 "\n", // Preserve newlines
1282 "\n", //
1283 "bbbb\n", //
1284 "ccccc\n", //
1285 "\n", //
1286 "ddd\n", //
1287 "eeee\n", //
1288 "\n", //
1289 "\n", //
1290 "\n", //
1291 "jj\n" //
1292 )
1293 );
1294
1295 buffer_1.update(cx, |buffer, cx| {
1296 buffer.edit(
1297 [
1298 Point::new(0, 0)..Point::new(0, 0),
1299 Point::new(2, 1)..Point::new(2, 3),
1300 ],
1301 "\n",
1302 cx,
1303 );
1304 });
1305
1306 assert_eq!(
1307 multibuffer.read(cx).snapshot(cx).text(),
1308 concat!(
1309 "\n", // Preserve newlines
1310 "\n", //
1311 "bbbb\n", //
1312 "c\n", //
1313 "cc\n", //
1314 "\n", //
1315 "ddd\n", //
1316 "eeee\n", //
1317 "\n", //
1318 "\n", //
1319 "\n", //
1320 "jj\n" //
1321 )
1322 );
1323
1324 assert_eq!(
1325 subscription.consume().into_inner(),
1326 [Edit {
1327 old: 8..10,
1328 new: 8..9
1329 }]
1330 );
1331 }
1332
1333 #[gpui::test(iterations = 100)]
1334 fn test_random_excerpts(cx: &mut MutableAppContext, mut rng: StdRng) {
1335 let operations = env::var("OPERATIONS")
1336 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
1337 .unwrap_or(10);
1338
1339 let mut buffers: Vec<ModelHandle<Buffer>> = Vec::new();
1340 let list = cx.add_model(|_| MultiBuffer::new(0));
1341 let mut excerpt_ids = Vec::new();
1342 let mut expected_excerpts = Vec::new();
1343 let mut old_versions = Vec::new();
1344
1345 for _ in 0..operations {
1346 match rng.gen_range(0..100) {
1347 0..=19 if !buffers.is_empty() => {
1348 let buffer = buffers.choose(&mut rng).unwrap();
1349 buffer.update(cx, |buf, cx| buf.randomly_edit(&mut rng, 1, cx));
1350 }
1351 _ => {
1352 let buffer_handle = if buffers.is_empty() || rng.gen_bool(0.4) {
1353 let base_text = RandomCharIter::new(&mut rng).take(10).collect::<String>();
1354 buffers.push(cx.add_model(|cx| Buffer::new(0, base_text, cx)));
1355 buffers.last().unwrap()
1356 } else {
1357 buffers.choose(&mut rng).unwrap()
1358 };
1359
1360 let buffer = buffer_handle.read(cx);
1361 let end_ix = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Bias::Right);
1362 let start_ix = buffer.clip_offset(rng.gen_range(0..=end_ix), Bias::Left);
1363 let header_height = rng.gen_range(0..=5);
1364 let anchor_range = buffer.anchor_before(start_ix)..buffer.anchor_after(end_ix);
1365 log::info!(
1366 "Pushing excerpt wih header {}, buffer {}: {:?}[{:?}] = {:?}",
1367 header_height,
1368 buffer_handle.id(),
1369 buffer.text(),
1370 start_ix..end_ix,
1371 &buffer.text()[start_ix..end_ix]
1372 );
1373
1374 let excerpt_id = list.update(cx, |list, cx| {
1375 list.push_excerpt(
1376 ExcerptProperties {
1377 buffer: &buffer_handle,
1378 range: start_ix..end_ix,
1379 header_height,
1380 },
1381 cx,
1382 )
1383 });
1384 excerpt_ids.push(excerpt_id);
1385 expected_excerpts.push((buffer_handle.clone(), anchor_range, header_height));
1386 }
1387 }
1388
1389 if rng.gen_bool(0.3) {
1390 list.update(cx, |list, cx| {
1391 old_versions.push((list.snapshot(cx), list.subscribe()));
1392 })
1393 }
1394
1395 let snapshot = list.read(cx).snapshot(cx);
1396
1397 let mut excerpt_starts = Vec::new();
1398 let mut expected_text = String::new();
1399 for (buffer, range, header_height) in &expected_excerpts {
1400 let buffer = buffer.read(cx);
1401 let buffer_range = range.to_offset(buffer);
1402
1403 for _ in 0..*header_height {
1404 expected_text.push('\n');
1405 }
1406
1407 excerpt_starts.push(TextSummary::from(expected_text.as_str()));
1408 expected_text.extend(buffer.text_for_range(buffer_range.clone()));
1409 expected_text.push('\n');
1410 }
1411
1412 assert_eq!(snapshot.text(), expected_text);
1413
1414 let mut excerpt_starts = excerpt_starts.into_iter();
1415 for (buffer, range, _) in &expected_excerpts {
1416 let buffer_id = buffer.id();
1417 let buffer = buffer.read(cx);
1418 let buffer_range = range.to_offset(buffer);
1419 let buffer_start_point = buffer.offset_to_point(buffer_range.start);
1420 let buffer_start_point_utf16 =
1421 buffer.text_summary_for_range::<PointUtf16, _>(0..buffer_range.start);
1422
1423 let excerpt_start = excerpt_starts.next().unwrap();
1424 let mut offset = excerpt_start.bytes;
1425 let mut buffer_offset = buffer_range.start;
1426 let mut point = excerpt_start.lines;
1427 let mut buffer_point = buffer_start_point;
1428 let mut point_utf16 = excerpt_start.lines_utf16;
1429 let mut buffer_point_utf16 = buffer_start_point_utf16;
1430 for byte in buffer.bytes_in_range(buffer_range.clone()).flatten() {
1431 let left_offset = snapshot.clip_offset(offset, Bias::Left);
1432 let right_offset = snapshot.clip_offset(offset, Bias::Right);
1433 let buffer_left_offset = buffer.clip_offset(buffer_offset, Bias::Left);
1434 let buffer_right_offset = buffer.clip_offset(buffer_offset, Bias::Right);
1435 assert_eq!(
1436 left_offset,
1437 excerpt_start.bytes + (buffer_left_offset - buffer_range.start),
1438 "clip_offset({:?}, Left). buffer: {:?}, buffer offset: {:?}",
1439 offset,
1440 buffer_id,
1441 buffer_offset,
1442 );
1443 assert_eq!(
1444 right_offset,
1445 excerpt_start.bytes + (buffer_right_offset - buffer_range.start),
1446 "clip_offset({:?}, Right). buffer: {:?}, buffer offset: {:?}",
1447 offset,
1448 buffer_id,
1449 buffer_offset,
1450 );
1451
1452 let left_point = snapshot.clip_point(point, Bias::Left);
1453 let right_point = snapshot.clip_point(point, Bias::Right);
1454 let buffer_left_point = buffer.clip_point(buffer_point, Bias::Left);
1455 let buffer_right_point = buffer.clip_point(buffer_point, Bias::Right);
1456 assert_eq!(
1457 left_point,
1458 excerpt_start.lines + (buffer_left_point - buffer_start_point),
1459 "clip_point({:?}, Left). buffer: {:?}, buffer point: {:?}",
1460 point,
1461 buffer_id,
1462 buffer_point,
1463 );
1464 assert_eq!(
1465 right_point,
1466 excerpt_start.lines + (buffer_right_point - buffer_start_point),
1467 "clip_point({:?}, Right). buffer: {:?}, buffer point: {:?}",
1468 point,
1469 buffer_id,
1470 buffer_point,
1471 );
1472
1473 let left_point_utf16 = snapshot.clip_point_utf16(point_utf16, Bias::Left);
1474 let right_point_utf16 = snapshot.clip_point_utf16(point_utf16, Bias::Right);
1475 let buffer_left_point_utf16 =
1476 buffer.clip_point_utf16(buffer_point_utf16, Bias::Left);
1477 let buffer_right_point_utf16 =
1478 buffer.clip_point_utf16(buffer_point_utf16, Bias::Right);
1479 assert_eq!(
1480 left_point_utf16,
1481 excerpt_start.lines_utf16
1482 + (buffer_left_point_utf16 - buffer_start_point_utf16),
1483 "clip_point_utf16({:?}, Left). buffer: {:?}, buffer point_utf16: {:?}",
1484 point_utf16,
1485 buffer_id,
1486 buffer_point_utf16,
1487 );
1488 assert_eq!(
1489 right_point_utf16,
1490 excerpt_start.lines_utf16
1491 + (buffer_right_point_utf16 - buffer_start_point_utf16),
1492 "clip_point_utf16({:?}, Right). buffer: {:?}, buffer point_utf16: {:?}",
1493 point_utf16,
1494 buffer_id,
1495 buffer_point_utf16,
1496 );
1497
1498 assert_eq!(
1499 snapshot.point_to_offset(left_point),
1500 left_offset,
1501 "point_to_offset({:?})",
1502 left_point,
1503 );
1504 assert_eq!(
1505 snapshot.offset_to_point(left_offset),
1506 left_point,
1507 "offset_to_point({:?})",
1508 left_offset,
1509 );
1510
1511 offset += 1;
1512 buffer_offset += 1;
1513 if *byte == b'\n' {
1514 point += Point::new(1, 0);
1515 point_utf16 += PointUtf16::new(1, 0);
1516 buffer_point += Point::new(1, 0);
1517 buffer_point_utf16 += PointUtf16::new(1, 0);
1518 } else {
1519 point += Point::new(0, 1);
1520 point_utf16 += PointUtf16::new(0, 1);
1521 buffer_point += Point::new(0, 1);
1522 buffer_point_utf16 += PointUtf16::new(0, 1);
1523 }
1524 }
1525 }
1526
1527 for (row, line) in expected_text.split('\n').enumerate() {
1528 assert_eq!(
1529 snapshot.line_len(row as u32),
1530 line.len() as u32,
1531 "line_len({}).",
1532 row
1533 );
1534 }
1535
1536 for _ in 0..10 {
1537 let end_ix = snapshot.clip_offset(rng.gen_range(0..=snapshot.len()), Bias::Right);
1538 let start_ix = snapshot.clip_offset(rng.gen_range(0..=end_ix), Bias::Left);
1539
1540 assert_eq!(
1541 snapshot
1542 .text_for_range(start_ix..end_ix)
1543 .collect::<String>(),
1544 &expected_text[start_ix..end_ix],
1545 "incorrect text for range {:?}",
1546 start_ix..end_ix
1547 );
1548
1549 let expected_summary = TextSummary::from(&expected_text[start_ix..end_ix]);
1550 assert_eq!(
1551 snapshot.text_summary_for_range::<TextSummary, _>(start_ix..end_ix),
1552 expected_summary,
1553 "incorrect summary for range {:?}",
1554 start_ix..end_ix
1555 );
1556 }
1557 }
1558
1559 let snapshot = list.read(cx).snapshot(cx);
1560 for (old_snapshot, subscription) in old_versions {
1561 let edits = subscription.consume().into_inner();
1562
1563 log::info!(
1564 "applying edits since old text: {:?}: {:?}",
1565 old_snapshot.text(),
1566 edits,
1567 );
1568
1569 let mut text = old_snapshot.text();
1570 for edit in edits {
1571 let new_text: String = snapshot.text_for_range(edit.new.clone()).collect();
1572 text.replace_range(edit.new.start..edit.new.start + edit.old.len(), &new_text);
1573 }
1574 assert_eq!(text.to_string(), snapshot.text());
1575 }
1576 }
1577}