1mod anchor;
2
3pub use anchor::{Anchor, AnchorRangeExt};
4use anyhow::Result;
5use clock::ReplicaId;
6use collections::{HashMap, HashSet};
7use gpui::{AppContext, ElementBox, Entity, ModelContext, ModelHandle, Task};
8use language::{
9 Buffer, BufferChunks, BufferSnapshot, Chunk, DiagnosticEntry, Event, File, Language, Selection,
10 ToOffset as _, ToPoint as _, TransactionId,
11};
12use std::{
13 cell::{Ref, RefCell},
14 cmp, fmt, io,
15 iter::{self, FromIterator},
16 ops::{Range, Sub},
17 str,
18 sync::Arc,
19 time::{Duration, Instant},
20};
21use sum_tree::{Bias, Cursor, SumTree};
22use text::{
23 locator::Locator,
24 rope::TextDimension,
25 subscription::{Subscription, Topic},
26 AnchorRangeExt as _, Edit, Point, PointUtf16, TextSummary,
27};
28use theme::SyntaxTheme;
29use util::post_inc;
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 singleton: bool,
40 replica_id: ReplicaId,
41 history: History,
42}
43
44struct History {
45 next_transaction_id: usize,
46 undo_stack: Vec<Transaction>,
47 redo_stack: Vec<Transaction>,
48 transaction_depth: usize,
49 group_interval: Duration,
50}
51
52struct Transaction {
53 id: usize,
54 buffer_transactions: HashSet<(usize, text::TransactionId)>,
55 first_edit_at: Instant,
56 last_edit_at: Instant,
57}
58
59pub trait ToOffset: 'static + fmt::Debug {
60 fn to_offset(&self, snapshot: &MultiBufferSnapshot) -> usize;
61}
62
63pub trait ToPoint: 'static + fmt::Debug {
64 fn to_point(&self, snapshot: &MultiBufferSnapshot) -> Point;
65}
66
67pub trait FromAnchor: 'static {
68 fn from_anchor(anchor: &Anchor, snapshot: &MultiBufferSnapshot) -> Self;
69}
70
71#[derive(Debug)]
72struct BufferState {
73 buffer: ModelHandle<Buffer>,
74 last_version: clock::Global,
75 last_parse_count: usize,
76 last_diagnostics_update_count: usize,
77 excerpts: Vec<ExcerptId>,
78}
79
80#[derive(Clone, Default)]
81pub struct MultiBufferSnapshot {
82 excerpts: SumTree<Excerpt>,
83 parse_count: usize,
84 diagnostics_update_count: usize,
85 is_dirty: bool,
86 has_conflict: bool,
87}
88
89pub type RenderHeaderFn = Arc<dyn 'static + Send + Sync + Fn(&AppContext) -> ElementBox>;
90
91pub struct ExcerptProperties<'a, T> {
92 pub buffer: &'a ModelHandle<Buffer>,
93 pub range: Range<T>,
94 pub header_height: u8,
95 pub render_header: Option<RenderHeaderFn>,
96}
97
98#[derive(Clone)]
99struct Excerpt {
100 id: ExcerptId,
101 buffer_id: usize,
102 buffer: BufferSnapshot,
103 range: Range<text::Anchor>,
104 render_header: Option<RenderHeaderFn>,
105 text_summary: TextSummary,
106 header_height: u8,
107 has_trailing_newline: bool,
108}
109
110#[derive(Clone, Debug, Default)]
111struct ExcerptSummary {
112 excerpt_id: ExcerptId,
113 text: TextSummary,
114}
115
116pub struct MultiBufferRows<'a> {
117 buffer_row_range: Range<u32>,
118 excerpts: Cursor<'a, Excerpt, Point>,
119}
120
121pub struct MultiBufferChunks<'a> {
122 range: Range<usize>,
123 excerpts: Cursor<'a, Excerpt, usize>,
124 excerpt_chunks: Option<ExcerptChunks<'a>>,
125 theme: Option<&'a SyntaxTheme>,
126}
127
128pub struct MultiBufferBytes<'a> {
129 range: Range<usize>,
130 excerpts: Cursor<'a, Excerpt, usize>,
131 excerpt_bytes: Option<ExcerptBytes<'a>>,
132 chunk: &'a [u8],
133}
134
135struct ExcerptChunks<'a> {
136 content_chunks: BufferChunks<'a>,
137 footer_height: usize,
138}
139
140struct ExcerptBytes<'a> {
141 content_bytes: language::rope::Bytes<'a>,
142 footer_height: usize,
143}
144
145impl MultiBuffer {
146 pub fn new(replica_id: ReplicaId) -> Self {
147 Self {
148 snapshot: Default::default(),
149 buffers: Default::default(),
150 subscriptions: Default::default(),
151 singleton: false,
152 replica_id,
153 history: History {
154 next_transaction_id: Default::default(),
155 undo_stack: Default::default(),
156 redo_stack: Default::default(),
157 transaction_depth: 0,
158 group_interval: Duration::from_millis(300),
159 },
160 }
161 }
162
163 pub fn singleton(buffer: ModelHandle<Buffer>, cx: &mut ModelContext<Self>) -> Self {
164 let mut this = Self::new(buffer.read(cx).replica_id());
165 this.singleton = true;
166 this.push_excerpt(
167 ExcerptProperties {
168 buffer: &buffer,
169 range: text::Anchor::min()..text::Anchor::max(),
170 header_height: 0,
171 render_header: None,
172 },
173 cx,
174 );
175 this
176 }
177
178 #[cfg(any(test, feature = "test-support"))]
179 pub fn build_simple(text: &str, cx: &mut gpui::MutableAppContext) -> ModelHandle<Self> {
180 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
181 cx.add_model(|cx| Self::singleton(buffer, cx))
182 }
183
184 #[cfg(any(test, feature = "test-support"))]
185 pub fn build_random(
186 mut rng: &mut impl rand::Rng,
187 cx: &mut gpui::MutableAppContext,
188 ) -> ModelHandle<Self> {
189 use rand::prelude::*;
190 use std::env;
191 use text::RandomCharIter;
192
193 let max_excerpts = env::var("MAX_EXCERPTS")
194 .map(|i| i.parse().expect("invalid `MAX_EXCERPTS` variable"))
195 .unwrap_or(5);
196 let excerpts = rng.gen_range(1..=max_excerpts);
197
198 cx.add_model(|cx| {
199 let mut multibuffer = MultiBuffer::new(0);
200 let mut buffers = Vec::new();
201 for _ in 0..excerpts {
202 let buffer_handle = if rng.gen() || buffers.is_empty() {
203 let text = RandomCharIter::new(&mut rng).take(10).collect::<String>();
204 buffers.push(cx.add_model(|cx| Buffer::new(0, text, cx)));
205 let buffer = buffers.last().unwrap();
206 log::info!(
207 "Creating new buffer {} with text: {:?}",
208 buffer.id(),
209 buffer.read(cx).text()
210 );
211 buffers.last().unwrap()
212 } else {
213 buffers.choose(rng).unwrap()
214 };
215
216 let buffer = buffer_handle.read(cx);
217 let end_ix = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Bias::Right);
218 let start_ix = buffer.clip_offset(rng.gen_range(0..=end_ix), Bias::Left);
219 let header_height = rng.gen_range(0..=5);
220 log::info!(
221 "Inserting excerpt from buffer {} with header height {} and range {:?}: {:?}",
222 buffer_handle.id(),
223 header_height,
224 start_ix..end_ix,
225 &buffer.text()[start_ix..end_ix]
226 );
227
228 multibuffer.push_excerpt(
229 ExcerptProperties {
230 buffer: buffer_handle,
231 range: start_ix..end_ix,
232 header_height,
233 render_header: None,
234 },
235 cx,
236 );
237 }
238 multibuffer
239 })
240 }
241
242 pub fn replica_id(&self) -> ReplicaId {
243 self.replica_id
244 }
245
246 pub fn snapshot(&self, cx: &AppContext) -> MultiBufferSnapshot {
247 self.sync(cx);
248 self.snapshot.borrow().clone()
249 }
250
251 pub fn read(&self, cx: &AppContext) -> Ref<MultiBufferSnapshot> {
252 self.sync(cx);
253 self.snapshot.borrow()
254 }
255
256 pub fn as_singleton(&self) -> Option<&ModelHandle<Buffer>> {
257 if self.singleton {
258 return Some(&self.buffers.values().next().unwrap().buffer);
259 } else {
260 None
261 }
262 }
263
264 pub fn subscribe(&mut self) -> Subscription {
265 self.subscriptions.subscribe()
266 }
267
268 pub fn edit<I, S, T>(&mut self, ranges: I, new_text: T, cx: &mut ModelContext<Self>)
269 where
270 I: IntoIterator<Item = Range<S>>,
271 S: ToOffset,
272 T: Into<String>,
273 {
274 self.edit_internal(ranges, new_text, false, cx)
275 }
276
277 pub fn edit_with_autoindent<I, S, T>(
278 &mut self,
279 ranges: I,
280 new_text: T,
281 cx: &mut ModelContext<Self>,
282 ) where
283 I: IntoIterator<Item = Range<S>>,
284 S: ToOffset,
285 T: Into<String>,
286 {
287 self.edit_internal(ranges, new_text, true, cx)
288 }
289
290 pub fn edit_internal<I, S, T>(
291 &mut self,
292 ranges_iter: I,
293 new_text: T,
294 autoindent: bool,
295 cx: &mut ModelContext<Self>,
296 ) where
297 I: IntoIterator<Item = Range<S>>,
298 S: ToOffset,
299 T: Into<String>,
300 {
301 if let Some(buffer) = self.as_singleton() {
302 let snapshot = self.read(cx);
303 let ranges = ranges_iter
304 .into_iter()
305 .map(|range| range.start.to_offset(&snapshot)..range.end.to_offset(&snapshot));
306 return buffer.update(cx, |buffer, cx| {
307 if autoindent {
308 buffer.edit_with_autoindent(ranges, new_text, cx)
309 } else {
310 buffer.edit(ranges, new_text, cx)
311 }
312 });
313 }
314
315 let snapshot = self.read(cx);
316 let mut buffer_edits: HashMap<usize, Vec<(Range<usize>, bool)>> = Default::default();
317 let mut cursor = snapshot.excerpts.cursor::<usize>();
318 for range in ranges_iter {
319 let start = range.start.to_offset(&snapshot);
320 let end = range.end.to_offset(&snapshot);
321 cursor.seek(&start, Bias::Right, &());
322 if cursor.item().is_none() && start == *cursor.start() {
323 cursor.prev(&());
324 }
325 let start_excerpt = cursor.item().expect("start offset out of bounds");
326 let start_overshoot = start - cursor.start();
327 let buffer_start =
328 start_excerpt.range.start.to_offset(&start_excerpt.buffer) + start_overshoot;
329
330 cursor.seek(&end, Bias::Right, &());
331 if cursor.item().is_none() && end == *cursor.start() {
332 cursor.prev(&());
333 }
334 let end_excerpt = cursor.item().expect("end offset out of bounds");
335 let end_overshoot = end - cursor.start();
336 let buffer_end = end_excerpt.range.start.to_offset(&end_excerpt.buffer) + end_overshoot;
337
338 if start_excerpt.id == end_excerpt.id {
339 buffer_edits
340 .entry(start_excerpt.buffer_id)
341 .or_insert(Vec::new())
342 .push((buffer_start..buffer_end, true));
343 } else {
344 let start_excerpt_range =
345 buffer_start..start_excerpt.range.end.to_offset(&start_excerpt.buffer);
346 let end_excerpt_range =
347 end_excerpt.range.start.to_offset(&end_excerpt.buffer)..buffer_end;
348 buffer_edits
349 .entry(start_excerpt.buffer_id)
350 .or_insert(Vec::new())
351 .push((start_excerpt_range, true));
352 buffer_edits
353 .entry(end_excerpt.buffer_id)
354 .or_insert(Vec::new())
355 .push((end_excerpt_range, false));
356
357 cursor.seek(&start, Bias::Right, &());
358 cursor.next(&());
359 while let Some(excerpt) = cursor.item() {
360 if excerpt.id == end_excerpt.id {
361 break;
362 }
363 buffer_edits
364 .entry(excerpt.buffer_id)
365 .or_insert(Vec::new())
366 .push((excerpt.range.to_offset(&excerpt.buffer), false));
367 cursor.next(&());
368 }
369 }
370 }
371
372 let new_text = new_text.into();
373 for (buffer_id, mut edits) in buffer_edits {
374 edits.sort_unstable_by_key(|(range, _)| range.start);
375 self.buffers[&buffer_id].buffer.update(cx, |buffer, cx| {
376 let mut edits = edits.into_iter().peekable();
377 let mut insertions = Vec::new();
378 let mut deletions = Vec::new();
379 while let Some((mut range, mut is_insertion)) = edits.next() {
380 while let Some((next_range, next_is_insertion)) = edits.peek() {
381 if range.end >= next_range.start {
382 range.end = cmp::max(next_range.end, range.end);
383 is_insertion |= *next_is_insertion;
384 edits.next();
385 } else {
386 break;
387 }
388 }
389
390 if is_insertion {
391 insertions.push(
392 buffer.anchor_before(range.start)..buffer.anchor_before(range.end),
393 );
394 } else if !range.is_empty() {
395 deletions.push(
396 buffer.anchor_before(range.start)..buffer.anchor_before(range.end),
397 );
398 }
399 }
400
401 if autoindent {
402 buffer.edit_with_autoindent(deletions, "", cx);
403 buffer.edit_with_autoindent(insertions, new_text.clone(), cx);
404 } else {
405 buffer.edit(deletions, "", cx);
406 buffer.edit(insertions, new_text.clone(), cx);
407 }
408 })
409 }
410 }
411
412 pub fn start_transaction(&mut self, cx: &mut ModelContext<Self>) -> Option<TransactionId> {
413 self.start_transaction_at(Instant::now(), cx)
414 }
415
416 pub(crate) fn start_transaction_at(
417 &mut self,
418 now: Instant,
419 cx: &mut ModelContext<Self>,
420 ) -> Option<TransactionId> {
421 if let Some(buffer) = self.as_singleton() {
422 return buffer.update(cx, |buffer, _| buffer.start_transaction_at(now));
423 }
424
425 for BufferState { buffer, .. } in self.buffers.values() {
426 buffer.update(cx, |buffer, _| buffer.start_transaction_at(now));
427 }
428 self.history.start_transaction(now)
429 }
430
431 pub fn end_transaction(&mut self, cx: &mut ModelContext<Self>) -> Option<TransactionId> {
432 self.end_transaction_at(Instant::now(), cx)
433 }
434
435 pub(crate) fn end_transaction_at(
436 &mut self,
437 now: Instant,
438 cx: &mut ModelContext<Self>,
439 ) -> Option<TransactionId> {
440 if let Some(buffer) = self.as_singleton() {
441 return buffer.update(cx, |buffer, cx| buffer.end_transaction_at(now, cx));
442 }
443
444 let mut buffer_transactions = HashSet::default();
445 for BufferState { buffer, .. } in self.buffers.values() {
446 if let Some(transaction_id) =
447 buffer.update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
448 {
449 buffer_transactions.insert((buffer.id(), transaction_id));
450 }
451 }
452
453 if self.history.end_transaction(now, buffer_transactions) {
454 let transaction_id = self.history.group().unwrap();
455 Some(transaction_id)
456 } else {
457 None
458 }
459 }
460
461 pub fn set_active_selections(
462 &mut self,
463 selections: &[Selection<Anchor>],
464 cx: &mut ModelContext<Self>,
465 ) {
466 let mut selections_by_buffer: HashMap<usize, Vec<Selection<text::Anchor>>> =
467 Default::default();
468 let snapshot = self.read(cx);
469 let mut cursor = snapshot.excerpts.cursor::<Option<&ExcerptId>>();
470 for selection in selections {
471 cursor.seek(&Some(&selection.start.excerpt_id), Bias::Left, &());
472 while let Some(excerpt) = cursor.item() {
473 if excerpt.id > selection.end.excerpt_id {
474 break;
475 }
476
477 let mut start = excerpt.range.start.clone();
478 let mut end = excerpt.range.end.clone();
479 if excerpt.id == selection.start.excerpt_id {
480 start = selection.start.text_anchor.clone();
481 }
482 if excerpt.id == selection.end.excerpt_id {
483 end = selection.end.text_anchor.clone();
484 }
485 selections_by_buffer
486 .entry(excerpt.buffer_id)
487 .or_default()
488 .push(Selection {
489 id: selection.id,
490 start,
491 end,
492 reversed: selection.reversed,
493 goal: selection.goal,
494 });
495
496 cursor.next(&());
497 }
498 }
499
500 for (buffer_id, mut selections) in selections_by_buffer {
501 self.buffers[&buffer_id].buffer.update(cx, |buffer, cx| {
502 selections.sort_unstable_by(|a, b| a.start.cmp(&b.start, buffer).unwrap());
503 let mut selections = selections.into_iter().peekable();
504 let merged_selections = Arc::from_iter(iter::from_fn(|| {
505 let mut selection = selections.next()?;
506 while let Some(next_selection) = selections.peek() {
507 if selection
508 .end
509 .cmp(&next_selection.start, buffer)
510 .unwrap()
511 .is_ge()
512 {
513 let next_selection = selections.next().unwrap();
514 if next_selection
515 .end
516 .cmp(&selection.end, buffer)
517 .unwrap()
518 .is_ge()
519 {
520 selection.end = next_selection.end;
521 }
522 } else {
523 break;
524 }
525 }
526 Some(selection)
527 }));
528 buffer.set_active_selections(merged_selections, cx);
529 });
530 }
531 }
532
533 pub fn remove_active_selections(&mut self, cx: &mut ModelContext<Self>) {
534 for buffer in self.buffers.values() {
535 buffer
536 .buffer
537 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
538 }
539 }
540
541 pub fn undo(&mut self, cx: &mut ModelContext<Self>) -> Option<TransactionId> {
542 if let Some(buffer) = self.as_singleton() {
543 return buffer.update(cx, |buffer, cx| buffer.undo(cx));
544 }
545
546 while let Some(transaction) = self.history.pop_undo() {
547 let mut undone = false;
548 for (buffer_id, buffer_transaction_id) in &transaction.buffer_transactions {
549 if let Some(BufferState { buffer, .. }) = self.buffers.get(&buffer_id) {
550 undone |= buffer.update(cx, |buf, cx| {
551 buf.undo_transaction(*buffer_transaction_id, cx)
552 });
553 }
554 }
555
556 if undone {
557 return Some(transaction.id);
558 }
559 }
560
561 None
562 }
563
564 pub fn redo(&mut self, cx: &mut ModelContext<Self>) -> Option<TransactionId> {
565 if let Some(buffer) = self.as_singleton() {
566 return buffer.update(cx, |buffer, cx| buffer.redo(cx));
567 }
568
569 while let Some(transaction) = self.history.pop_redo() {
570 let mut redone = false;
571 for (buffer_id, buffer_transaction_id) in &transaction.buffer_transactions {
572 if let Some(BufferState { buffer, .. }) = self.buffers.get(&buffer_id) {
573 redone |= buffer.update(cx, |buf, cx| {
574 buf.redo_transaction(*buffer_transaction_id, cx)
575 });
576 }
577 }
578
579 if redone {
580 return Some(transaction.id);
581 }
582 }
583
584 None
585 }
586
587 pub fn push_excerpt<O>(
588 &mut self,
589 props: ExcerptProperties<O>,
590 cx: &mut ModelContext<Self>,
591 ) -> ExcerptId
592 where
593 O: text::ToOffset,
594 {
595 assert_eq!(self.history.transaction_depth, 0);
596 self.sync(cx);
597
598 let buffer = props.buffer.clone();
599 cx.observe(&buffer, |_, _, cx| cx.notify()).detach();
600 cx.subscribe(&buffer, Self::on_buffer_event).detach();
601
602 let buffer_snapshot = buffer.read(cx).snapshot();
603 let range = buffer_snapshot.anchor_before(&props.range.start)
604 ..buffer_snapshot.anchor_after(&props.range.end);
605 let last_version = buffer_snapshot.version().clone();
606 let last_parse_count = buffer_snapshot.parse_count();
607 let last_diagnostics_update_count = buffer_snapshot.diagnostics_update_count();
608
609 let mut snapshot = self.snapshot.borrow_mut();
610 let mut prev_id = None;
611 let edit_start = snapshot.excerpts.summary().text.bytes;
612 snapshot.excerpts.update_last(
613 |excerpt| {
614 excerpt.has_trailing_newline = true;
615 prev_id = Some(excerpt.id.clone());
616 },
617 &(),
618 );
619
620 let id = ExcerptId::between(&prev_id.unwrap_or(ExcerptId::min()), &ExcerptId::max());
621 let excerpt = Excerpt::new(
622 id.clone(),
623 buffer.id(),
624 buffer_snapshot,
625 range,
626 props.header_height,
627 props.render_header,
628 false,
629 );
630 snapshot.excerpts.push(excerpt, &());
631 self.buffers
632 .entry(props.buffer.id())
633 .or_insert_with(|| BufferState {
634 buffer,
635 last_version,
636 last_parse_count,
637 last_diagnostics_update_count,
638 excerpts: Default::default(),
639 })
640 .excerpts
641 .push(id.clone());
642 self.subscriptions.publish_mut([Edit {
643 old: edit_start..edit_start,
644 new: edit_start..snapshot.excerpts.summary().text.bytes,
645 }]);
646
647 cx.notify();
648
649 id
650 }
651
652 fn on_buffer_event(
653 &mut self,
654 _: ModelHandle<Buffer>,
655 event: &Event,
656 cx: &mut ModelContext<Self>,
657 ) {
658 cx.emit(event.clone());
659 }
660
661 pub fn save(&mut self, cx: &mut ModelContext<Self>) -> Result<Task<Result<()>>> {
662 let mut save_tasks = Vec::new();
663 for BufferState { buffer, .. } in self.buffers.values() {
664 save_tasks.push(buffer.update(cx, |buffer, cx| buffer.save(cx))?);
665 }
666
667 Ok(cx.spawn(|_, _| async move {
668 for save in save_tasks {
669 save.await?;
670 }
671 Ok(())
672 }))
673 }
674
675 pub fn language<'a>(&self, cx: &'a AppContext) -> Option<&'a Arc<Language>> {
676 self.buffers
677 .values()
678 .next()
679 .and_then(|state| state.buffer.read(cx).language())
680 }
681
682 pub fn file<'a>(&self, cx: &'a AppContext) -> Option<&'a dyn File> {
683 self.as_singleton()?.read(cx).file()
684 }
685
686 #[cfg(test)]
687 pub fn is_parsing(&self, cx: &AppContext) -> bool {
688 self.as_singleton().unwrap().read(cx).is_parsing()
689 }
690
691 fn sync(&self, cx: &AppContext) {
692 let mut snapshot = self.snapshot.borrow_mut();
693 let mut excerpts_to_edit = Vec::new();
694 let mut reparsed = false;
695 let mut diagnostics_updated = false;
696 let mut is_dirty = false;
697 let mut has_conflict = false;
698 for buffer_state in self.buffers.values() {
699 let buffer = buffer_state.buffer.read(cx);
700 let buffer_edited = buffer.version().gt(&buffer_state.last_version);
701 let buffer_reparsed = buffer.parse_count() > buffer_state.last_parse_count;
702 let buffer_diagnostics_updated =
703 buffer.diagnostics_update_count() > buffer_state.last_diagnostics_update_count;
704 if buffer_edited || buffer_reparsed || buffer_diagnostics_updated {
705 excerpts_to_edit.extend(
706 buffer_state
707 .excerpts
708 .iter()
709 .map(|excerpt_id| (excerpt_id, buffer_state, buffer_edited)),
710 );
711 }
712
713 reparsed |= buffer_reparsed;
714 diagnostics_updated |= buffer_diagnostics_updated;
715 is_dirty |= buffer.is_dirty();
716 has_conflict |= buffer.has_conflict();
717 }
718 if reparsed {
719 snapshot.parse_count += 1;
720 }
721 if diagnostics_updated {
722 snapshot.diagnostics_update_count += 1;
723 }
724 snapshot.is_dirty = is_dirty;
725 snapshot.has_conflict = has_conflict;
726
727 excerpts_to_edit.sort_unstable_by_key(|(excerpt_id, _, _)| *excerpt_id);
728
729 let mut edits = Vec::new();
730 let mut new_excerpts = SumTree::new();
731 let mut cursor = snapshot.excerpts.cursor::<(Option<&ExcerptId>, usize)>();
732
733 for (id, buffer_state, buffer_edited) in excerpts_to_edit {
734 new_excerpts.push_tree(cursor.slice(&Some(id), Bias::Left, &()), &());
735 let old_excerpt = cursor.item().unwrap();
736 let buffer = buffer_state.buffer.read(cx);
737
738 let mut new_excerpt;
739 if buffer_edited {
740 edits.extend(
741 buffer
742 .edits_since_in_range::<usize>(
743 old_excerpt.buffer.version(),
744 old_excerpt.range.clone(),
745 )
746 .map(|mut edit| {
747 let excerpt_old_start = cursor.start().1;
748 let excerpt_new_start = new_excerpts.summary().text.bytes;
749 edit.old.start += excerpt_old_start;
750 edit.old.end += excerpt_old_start;
751 edit.new.start += excerpt_new_start;
752 edit.new.end += excerpt_new_start;
753 edit
754 }),
755 );
756
757 new_excerpt = Excerpt::new(
758 id.clone(),
759 buffer_state.buffer.id(),
760 buffer.snapshot(),
761 old_excerpt.range.clone(),
762 old_excerpt.header_height,
763 old_excerpt.render_header.clone(),
764 old_excerpt.has_trailing_newline,
765 );
766 } else {
767 new_excerpt = old_excerpt.clone();
768 new_excerpt.buffer = buffer.snapshot();
769 }
770
771 new_excerpts.push(new_excerpt, &());
772 cursor.next(&());
773 }
774 new_excerpts.push_tree(cursor.suffix(&()), &());
775
776 drop(cursor);
777 snapshot.excerpts = new_excerpts;
778
779 self.subscriptions.publish(edits);
780 }
781}
782
783#[cfg(any(test, feature = "test-support"))]
784impl MultiBuffer {
785 pub fn randomly_edit(
786 &mut self,
787 rng: &mut impl rand::Rng,
788 count: usize,
789 cx: &mut ModelContext<Self>,
790 ) {
791 use text::RandomCharIter;
792
793 let snapshot = self.read(cx);
794 let mut old_ranges: Vec<Range<usize>> = Vec::new();
795 for _ in 0..count {
796 let last_end = old_ranges.last().map_or(0, |last_range| last_range.end + 1);
797 if last_end > snapshot.len() {
798 break;
799 }
800 let end_ix = snapshot.clip_offset(rng.gen_range(0..=last_end), Bias::Right);
801 let start_ix = snapshot.clip_offset(rng.gen_range(0..=end_ix), Bias::Left);
802 old_ranges.push(start_ix..end_ix);
803 }
804 let new_text_len = rng.gen_range(0..10);
805 let new_text: String = RandomCharIter::new(&mut *rng).take(new_text_len).collect();
806 log::info!("mutating multi-buffer at {:?}: {:?}", old_ranges, new_text);
807 drop(snapshot);
808
809 self.edit(old_ranges.iter().cloned(), new_text.as_str(), cx);
810 }
811}
812
813impl Entity for MultiBuffer {
814 type Event = language::Event;
815}
816
817impl MultiBufferSnapshot {
818 pub fn text(&self) -> String {
819 self.chunks(0..self.len(), None)
820 .map(|chunk| chunk.text)
821 .collect()
822 }
823
824 pub fn excerpt_headers_in_range<'a>(
825 &'a self,
826 range: Range<u32>,
827 ) -> impl 'a + Iterator<Item = (u32, u8, RenderHeaderFn)> {
828 let mut cursor = self.excerpts.cursor::<Point>();
829 cursor.seek(&Point::new(range.start, 0), Bias::Right, &());
830
831 if cursor.item().is_some() && range.start > cursor.start().row {
832 cursor.next(&());
833 }
834
835 iter::from_fn(move || {
836 while let Some(excerpt) = cursor.item() {
837 if cursor.start().row >= range.end {
838 break;
839 }
840
841 if let Some(render) = excerpt.render_header.clone() {
842 let start = cursor.start().row;
843 cursor.next(&());
844 return Some((start, excerpt.header_height, render));
845 } else {
846 cursor.next(&());
847 }
848 }
849 None
850 })
851 }
852
853 pub fn reversed_chars_at<'a, T: ToOffset>(
854 &'a self,
855 position: T,
856 ) -> impl Iterator<Item = char> + 'a {
857 let mut offset = position.to_offset(self);
858 let mut cursor = self.excerpts.cursor::<usize>();
859 cursor.seek(&offset, Bias::Left, &());
860 let mut excerpt_chunks = cursor.item().map(|excerpt| {
861 let end_before_footer = cursor.start() + excerpt.text_summary.bytes;
862 let start = excerpt.range.start.to_offset(&excerpt.buffer);
863 let end = start + (cmp::min(offset, end_before_footer) - cursor.start());
864 excerpt.buffer.reversed_chunks_in_range(start..end)
865 });
866 iter::from_fn(move || {
867 if offset == *cursor.start() {
868 cursor.prev(&());
869 let excerpt = cursor.item()?;
870 excerpt_chunks = Some(
871 excerpt
872 .buffer
873 .reversed_chunks_in_range(excerpt.range.clone()),
874 );
875 }
876
877 let excerpt = cursor.item().unwrap();
878 if offset == cursor.end(&()) && excerpt.has_trailing_newline {
879 offset -= 1;
880 Some("\n")
881 } else {
882 let chunk = excerpt_chunks.as_mut().unwrap().next().unwrap();
883 offset -= chunk.len();
884 Some(chunk)
885 }
886 })
887 .flat_map(|c| c.chars().rev())
888 }
889
890 pub fn chars_at<'a, T: ToOffset>(&'a self, position: T) -> impl Iterator<Item = char> + 'a {
891 let offset = position.to_offset(self);
892 self.text_for_range(offset..self.len())
893 .flat_map(|chunk| chunk.chars())
894 }
895
896 pub fn text_for_range<'a, T: ToOffset>(
897 &'a self,
898 range: Range<T>,
899 ) -> impl Iterator<Item = &'a str> {
900 self.chunks(range, None).map(|chunk| chunk.text)
901 }
902
903 pub fn is_line_blank(&self, row: u32) -> bool {
904 self.text_for_range(Point::new(row, 0)..Point::new(row, self.line_len(row)))
905 .all(|chunk| chunk.matches(|c: char| !c.is_whitespace()).next().is_none())
906 }
907
908 pub fn contains_str_at<T>(&self, position: T, needle: &str) -> bool
909 where
910 T: ToOffset,
911 {
912 let position = position.to_offset(self);
913 position == self.clip_offset(position, Bias::Left)
914 && self
915 .bytes_in_range(position..self.len())
916 .flatten()
917 .copied()
918 .take(needle.len())
919 .eq(needle.bytes())
920 }
921
922 fn as_singleton(&self) -> Option<&BufferSnapshot> {
923 let mut excerpts = self.excerpts.iter();
924 let buffer = excerpts.next().map(|excerpt| &excerpt.buffer);
925 if excerpts.next().is_none() {
926 buffer
927 } else {
928 None
929 }
930 }
931
932 pub fn len(&self) -> usize {
933 self.excerpts.summary().text.bytes
934 }
935
936 pub fn clip_offset(&self, offset: usize, bias: Bias) -> usize {
937 let mut cursor = self.excerpts.cursor::<usize>();
938 cursor.seek(&offset, Bias::Right, &());
939 let overshoot = if let Some(excerpt) = cursor.item() {
940 let excerpt_start = excerpt.range.start.to_offset(&excerpt.buffer);
941 let buffer_offset = excerpt
942 .buffer
943 .clip_offset(excerpt_start + (offset - cursor.start()), bias);
944 buffer_offset.saturating_sub(excerpt_start)
945 } else {
946 0
947 };
948 cursor.start() + overshoot
949 }
950
951 pub fn clip_point(&self, point: Point, bias: Bias) -> Point {
952 let mut cursor = self.excerpts.cursor::<Point>();
953 cursor.seek(&point, Bias::Right, &());
954 let overshoot = if let Some(excerpt) = cursor.item() {
955 let excerpt_start = excerpt.range.start.to_point(&excerpt.buffer);
956 let buffer_point = excerpt
957 .buffer
958 .clip_point(excerpt_start + (point - cursor.start()), bias);
959 buffer_point.saturating_sub(excerpt_start)
960 } else {
961 Point::zero()
962 };
963 *cursor.start() + overshoot
964 }
965
966 pub fn clip_point_utf16(&self, point: PointUtf16, bias: Bias) -> PointUtf16 {
967 let mut cursor = self.excerpts.cursor::<PointUtf16>();
968 cursor.seek(&point, Bias::Right, &());
969 let overshoot = if let Some(excerpt) = cursor.item() {
970 let excerpt_start = excerpt
971 .buffer
972 .offset_to_point_utf16(excerpt.range.start.to_offset(&excerpt.buffer));
973 let buffer_point = excerpt
974 .buffer
975 .clip_point_utf16(excerpt_start + (point - cursor.start()), bias);
976 buffer_point.saturating_sub(excerpt_start)
977 } else {
978 PointUtf16::zero()
979 };
980 *cursor.start() + overshoot
981 }
982
983 pub fn bytes_in_range<'a, T: ToOffset>(&'a self, range: Range<T>) -> MultiBufferBytes<'a> {
984 let range = range.start.to_offset(self)..range.end.to_offset(self);
985 let mut excerpts = self.excerpts.cursor::<usize>();
986 excerpts.seek(&range.start, Bias::Right, &());
987
988 let mut chunk = &[][..];
989 let excerpt_bytes = if let Some(excerpt) = excerpts.item() {
990 let mut excerpt_bytes = excerpt
991 .bytes_in_range(range.start - excerpts.start()..range.end - excerpts.start());
992 chunk = excerpt_bytes.next().unwrap_or(&[][..]);
993 Some(excerpt_bytes)
994 } else {
995 None
996 };
997
998 MultiBufferBytes {
999 range,
1000 excerpts,
1001 excerpt_bytes,
1002 chunk,
1003 }
1004 }
1005
1006 pub fn buffer_rows<'a>(&'a self, start_row: u32) -> MultiBufferRows<'a> {
1007 let mut result = MultiBufferRows {
1008 buffer_row_range: 0..0,
1009 excerpts: self.excerpts.cursor(),
1010 };
1011 result.seek(start_row);
1012 result
1013 }
1014
1015 pub fn chunks<'a, T: ToOffset>(
1016 &'a self,
1017 range: Range<T>,
1018 theme: Option<&'a SyntaxTheme>,
1019 ) -> MultiBufferChunks<'a> {
1020 let range = range.start.to_offset(self)..range.end.to_offset(self);
1021 let mut chunks = MultiBufferChunks {
1022 range: range.clone(),
1023 excerpts: self.excerpts.cursor(),
1024 excerpt_chunks: None,
1025 theme,
1026 };
1027 chunks.seek(range.start);
1028 chunks
1029 }
1030
1031 pub fn offset_to_point(&self, offset: usize) -> Point {
1032 let mut cursor = self.excerpts.cursor::<(usize, Point)>();
1033 cursor.seek(&offset, Bias::Right, &());
1034 if let Some(excerpt) = cursor.item() {
1035 let (start_offset, start_point) = cursor.start();
1036 let overshoot = offset - start_offset;
1037 let excerpt_start_offset = excerpt.range.start.to_offset(&excerpt.buffer);
1038 let excerpt_start_point = excerpt.range.start.to_point(&excerpt.buffer);
1039 let buffer_point = excerpt
1040 .buffer
1041 .offset_to_point(excerpt_start_offset + overshoot);
1042 *start_point + (buffer_point - excerpt_start_point)
1043 } else {
1044 self.excerpts.summary().text.lines
1045 }
1046 }
1047
1048 pub fn point_to_offset(&self, point: Point) -> usize {
1049 let mut cursor = self.excerpts.cursor::<(Point, usize)>();
1050 cursor.seek(&point, Bias::Right, &());
1051 if let Some(excerpt) = cursor.item() {
1052 let (start_point, start_offset) = cursor.start();
1053 let overshoot = point - start_point;
1054 let excerpt_start_offset = excerpt.range.start.to_offset(&excerpt.buffer);
1055 let excerpt_start_point = excerpt.range.start.to_point(&excerpt.buffer);
1056 let buffer_offset = excerpt
1057 .buffer
1058 .point_to_offset(excerpt_start_point + overshoot);
1059 *start_offset + buffer_offset - excerpt_start_offset
1060 } else {
1061 self.excerpts.summary().text.bytes
1062 }
1063 }
1064
1065 pub fn point_utf16_to_offset(&self, point: PointUtf16) -> usize {
1066 let mut cursor = self.excerpts.cursor::<(PointUtf16, usize)>();
1067 cursor.seek(&point, Bias::Right, &());
1068 if let Some(excerpt) = cursor.item() {
1069 let (start_point, start_offset) = cursor.start();
1070 let overshoot = point - start_point;
1071 let excerpt_start_offset = excerpt.range.start.to_offset(&excerpt.buffer);
1072 let excerpt_start_point = excerpt
1073 .buffer
1074 .offset_to_point_utf16(excerpt.range.start.to_offset(&excerpt.buffer));
1075 let buffer_offset = excerpt
1076 .buffer
1077 .point_utf16_to_offset(excerpt_start_point + overshoot);
1078 *start_offset + (buffer_offset - excerpt_start_offset)
1079 } else {
1080 self.excerpts.summary().text.bytes
1081 }
1082 }
1083
1084 pub fn indent_column_for_line(&self, row: u32) -> u32 {
1085 if let Some((buffer, range)) = self.buffer_line_for_row(row) {
1086 buffer
1087 .indent_column_for_line(range.start.row)
1088 .min(range.end.column)
1089 .saturating_sub(range.start.column)
1090 } else {
1091 0
1092 }
1093 }
1094
1095 pub fn line_len(&self, row: u32) -> u32 {
1096 if let Some((_, range)) = self.buffer_line_for_row(row) {
1097 range.end.column - range.start.column
1098 } else {
1099 0
1100 }
1101 }
1102
1103 fn buffer_line_for_row(&self, row: u32) -> Option<(&BufferSnapshot, Range<Point>)> {
1104 let mut cursor = self.excerpts.cursor::<Point>();
1105 cursor.seek(&Point::new(row, 0), Bias::Right, &());
1106 if let Some(excerpt) = cursor.item() {
1107 let overshoot = row - cursor.start().row;
1108 let excerpt_start = excerpt.range.start.to_point(&excerpt.buffer);
1109 let excerpt_end = excerpt.range.end.to_point(&excerpt.buffer);
1110 let buffer_row = excerpt_start.row + overshoot;
1111 let line_start = Point::new(buffer_row, 0);
1112 let line_end = Point::new(buffer_row, excerpt.buffer.line_len(buffer_row));
1113 return Some((
1114 &excerpt.buffer,
1115 line_start.max(excerpt_start)..line_end.min(excerpt_end),
1116 ));
1117 }
1118 None
1119 }
1120
1121 pub fn max_point(&self) -> Point {
1122 self.text_summary().lines
1123 }
1124
1125 pub fn text_summary(&self) -> TextSummary {
1126 self.excerpts.summary().text
1127 }
1128
1129 pub fn text_summary_for_range<'a, D, O>(&'a self, range: Range<O>) -> D
1130 where
1131 D: TextDimension,
1132 O: ToOffset,
1133 {
1134 let mut summary = D::default();
1135 let mut range = range.start.to_offset(self)..range.end.to_offset(self);
1136 let mut cursor = self.excerpts.cursor::<usize>();
1137 cursor.seek(&range.start, Bias::Right, &());
1138 if let Some(excerpt) = cursor.item() {
1139 let mut end_before_newline = cursor.end(&());
1140 if excerpt.has_trailing_newline {
1141 end_before_newline -= 1;
1142 }
1143
1144 let excerpt_start = excerpt.range.start.to_offset(&excerpt.buffer);
1145 let start_in_excerpt = excerpt_start + (range.start - cursor.start());
1146 let end_in_excerpt =
1147 excerpt_start + (cmp::min(end_before_newline, range.end) - cursor.start());
1148 summary.add_assign(
1149 &excerpt
1150 .buffer
1151 .text_summary_for_range(start_in_excerpt..end_in_excerpt),
1152 );
1153
1154 if range.end > end_before_newline {
1155 summary.add_assign(&D::from_text_summary(&TextSummary {
1156 bytes: 1,
1157 lines: Point::new(1 as u32, 0),
1158 lines_utf16: PointUtf16::new(1 as u32, 0),
1159 first_line_chars: 0,
1160 last_line_chars: 0,
1161 longest_row: 0,
1162 longest_row_chars: 0,
1163 }));
1164 }
1165
1166 cursor.next(&());
1167 }
1168
1169 if range.end > *cursor.start() {
1170 summary.add_assign(&D::from_text_summary(&cursor.summary::<_, TextSummary>(
1171 &range.end,
1172 Bias::Right,
1173 &(),
1174 )));
1175 if let Some(excerpt) = cursor.item() {
1176 range.end = cmp::max(*cursor.start(), range.end);
1177
1178 let excerpt_start = excerpt.range.start.to_offset(&excerpt.buffer);
1179 let end_in_excerpt = excerpt_start + (range.end - cursor.start());
1180 summary.add_assign(
1181 &excerpt
1182 .buffer
1183 .text_summary_for_range(excerpt_start..end_in_excerpt),
1184 );
1185 }
1186 }
1187
1188 summary
1189 }
1190
1191 pub fn summary_for_anchor<D>(&self, anchor: &Anchor) -> D
1192 where
1193 D: TextDimension + Ord + Sub<D, Output = D>,
1194 {
1195 let mut cursor = self.excerpts.cursor::<ExcerptSummary>();
1196 cursor.seek(&Some(&anchor.excerpt_id), Bias::Left, &());
1197 if cursor.item().is_none() {
1198 cursor.next(&());
1199 }
1200
1201 let mut position = D::from_text_summary(&cursor.start().text);
1202 if let Some(excerpt) = cursor.item() {
1203 if excerpt.id == anchor.excerpt_id {
1204 let excerpt_buffer_start = excerpt.range.start.summary::<D>(&excerpt.buffer);
1205 let buffer_position = anchor.text_anchor.summary::<D>(&excerpt.buffer);
1206 if buffer_position > excerpt_buffer_start {
1207 position.add_assign(&(buffer_position - excerpt_buffer_start));
1208 }
1209 }
1210 }
1211 position
1212 }
1213
1214 pub fn summaries_for_anchors<'a, D, I>(&'a self, anchors: I) -> Vec<D>
1215 where
1216 D: TextDimension + Ord + Sub<D, Output = D>,
1217 I: 'a + IntoIterator<Item = &'a Anchor>,
1218 {
1219 let mut anchors = anchors.into_iter().peekable();
1220 let mut cursor = self.excerpts.cursor::<ExcerptSummary>();
1221 let mut summaries = Vec::new();
1222 while let Some(anchor) = anchors.peek() {
1223 let excerpt_id = &anchor.excerpt_id;
1224 let excerpt_anchors = iter::from_fn(|| {
1225 let anchor = anchors.peek()?;
1226 if anchor.excerpt_id == *excerpt_id {
1227 Some(&anchors.next().unwrap().text_anchor)
1228 } else {
1229 None
1230 }
1231 });
1232
1233 cursor.seek_forward(&Some(excerpt_id), Bias::Left, &());
1234 if cursor.item().is_none() {
1235 cursor.next(&());
1236 }
1237
1238 let position = D::from_text_summary(&cursor.start().text);
1239 if let Some(excerpt) = cursor.item() {
1240 if excerpt.id == *excerpt_id {
1241 let excerpt_buffer_start = excerpt.range.start.summary::<D>(&excerpt.buffer);
1242 summaries.extend(
1243 excerpt
1244 .buffer
1245 .summaries_for_anchors::<D, _>(excerpt_anchors)
1246 .map(move |summary| {
1247 let mut position = position.clone();
1248 let excerpt_buffer_start = excerpt_buffer_start.clone();
1249 if summary > excerpt_buffer_start {
1250 position.add_assign(&(summary - excerpt_buffer_start));
1251 }
1252 position
1253 }),
1254 );
1255 continue;
1256 }
1257 }
1258
1259 summaries.extend(excerpt_anchors.map(|_| position.clone()));
1260 }
1261
1262 summaries
1263 }
1264
1265 pub fn anchor_before<T: ToOffset>(&self, position: T) -> Anchor {
1266 self.anchor_at(position, Bias::Left)
1267 }
1268
1269 pub fn anchor_after<T: ToOffset>(&self, position: T) -> Anchor {
1270 self.anchor_at(position, Bias::Right)
1271 }
1272
1273 pub fn anchor_at<T: ToOffset>(&self, position: T, mut bias: Bias) -> Anchor {
1274 let offset = position.to_offset(self);
1275 let mut cursor = self.excerpts.cursor::<(usize, Option<&ExcerptId>)>();
1276 cursor.seek(&offset, Bias::Right, &());
1277 if cursor.item().is_none() && offset == cursor.start().0 && bias == Bias::Left {
1278 cursor.prev(&());
1279 }
1280 if let Some(excerpt) = cursor.item() {
1281 let mut overshoot = offset.saturating_sub(cursor.start().0);
1282 if excerpt.has_trailing_newline && offset == cursor.end(&()).0 {
1283 overshoot -= 1;
1284 bias = Bias::Right;
1285 }
1286
1287 let buffer_start = excerpt.range.start.to_offset(&excerpt.buffer);
1288 let text_anchor =
1289 excerpt.clip_anchor(excerpt.buffer.anchor_at(buffer_start + overshoot, bias));
1290 Anchor {
1291 excerpt_id: excerpt.id.clone(),
1292 text_anchor,
1293 }
1294 } else if offset == 0 && bias == Bias::Left {
1295 Anchor::min()
1296 } else {
1297 Anchor::max()
1298 }
1299 }
1300
1301 pub fn anchor_in_excerpt(&self, excerpt_id: ExcerptId, text_anchor: text::Anchor) -> Anchor {
1302 let mut cursor = self.excerpts.cursor::<Option<&ExcerptId>>();
1303 cursor.seek(&Some(&excerpt_id), Bias::Left, &());
1304 if let Some(excerpt) = cursor.item() {
1305 if excerpt.id == excerpt_id {
1306 let text_anchor = excerpt.clip_anchor(text_anchor);
1307 drop(cursor);
1308 return Anchor {
1309 excerpt_id,
1310 text_anchor,
1311 };
1312 }
1313 }
1314 panic!("excerpt not found");
1315 }
1316
1317 pub fn parse_count(&self) -> usize {
1318 self.parse_count
1319 }
1320
1321 pub fn enclosing_bracket_ranges<T: ToOffset>(
1322 &self,
1323 range: Range<T>,
1324 ) -> Option<(Range<usize>, Range<usize>)> {
1325 let range = range.start.to_offset(self)..range.end.to_offset(self);
1326
1327 let mut cursor = self.excerpts.cursor::<usize>();
1328 cursor.seek(&range.start, Bias::Right, &());
1329 let start_excerpt = cursor.item();
1330
1331 cursor.seek(&range.end, Bias::Right, &());
1332 let end_excerpt = cursor.item();
1333
1334 start_excerpt
1335 .zip(end_excerpt)
1336 .and_then(|(start_excerpt, end_excerpt)| {
1337 if start_excerpt.id != end_excerpt.id {
1338 return None;
1339 }
1340
1341 let excerpt_buffer_start =
1342 start_excerpt.range.start.to_offset(&start_excerpt.buffer);
1343 let excerpt_buffer_end = excerpt_buffer_start + start_excerpt.text_summary.bytes;
1344
1345 let start_in_buffer =
1346 excerpt_buffer_start + range.start.saturating_sub(*cursor.start());
1347 let end_in_buffer =
1348 excerpt_buffer_start + range.end.saturating_sub(*cursor.start());
1349 let (mut start_bracket_range, mut end_bracket_range) = start_excerpt
1350 .buffer
1351 .enclosing_bracket_ranges(start_in_buffer..end_in_buffer)?;
1352
1353 if start_bracket_range.start >= excerpt_buffer_start
1354 && end_bracket_range.end < excerpt_buffer_end
1355 {
1356 start_bracket_range.start =
1357 cursor.start() + (start_bracket_range.start - excerpt_buffer_start);
1358 start_bracket_range.end =
1359 cursor.start() + (start_bracket_range.end - excerpt_buffer_start);
1360 end_bracket_range.start =
1361 cursor.start() + (end_bracket_range.start - excerpt_buffer_start);
1362 end_bracket_range.end =
1363 cursor.start() + (end_bracket_range.end - excerpt_buffer_start);
1364 Some((start_bracket_range, end_bracket_range))
1365 } else {
1366 None
1367 }
1368 })
1369 }
1370
1371 pub fn diagnostics_update_count(&self) -> usize {
1372 self.diagnostics_update_count
1373 }
1374
1375 pub fn language(&self) -> Option<&Arc<Language>> {
1376 self.excerpts
1377 .iter()
1378 .next()
1379 .and_then(|excerpt| excerpt.buffer.language())
1380 }
1381
1382 pub fn is_dirty(&self) -> bool {
1383 self.is_dirty
1384 }
1385
1386 pub fn has_conflict(&self) -> bool {
1387 self.has_conflict
1388 }
1389
1390 pub fn diagnostic_group<'a, O>(
1391 &'a self,
1392 group_id: usize,
1393 ) -> impl Iterator<Item = DiagnosticEntry<O>> + 'a
1394 where
1395 O: text::FromAnchor + 'a,
1396 {
1397 self.as_singleton()
1398 .into_iter()
1399 .flat_map(move |buffer| buffer.diagnostic_group(group_id))
1400 }
1401
1402 pub fn diagnostics_in_range<'a, T, O>(
1403 &'a self,
1404 range: Range<T>,
1405 ) -> impl Iterator<Item = DiagnosticEntry<O>> + 'a
1406 where
1407 T: 'a + ToOffset,
1408 O: 'a + text::FromAnchor,
1409 {
1410 self.as_singleton().into_iter().flat_map(move |buffer| {
1411 buffer.diagnostics_in_range(range.start.to_offset(self)..range.end.to_offset(self))
1412 })
1413 }
1414
1415 pub fn range_for_syntax_ancestor<T: ToOffset>(&self, range: Range<T>) -> Option<Range<usize>> {
1416 let range = range.start.to_offset(self)..range.end.to_offset(self);
1417
1418 let mut cursor = self.excerpts.cursor::<usize>();
1419 cursor.seek(&range.start, Bias::Right, &());
1420 let start_excerpt = cursor.item();
1421
1422 cursor.seek(&range.end, Bias::Right, &());
1423 let end_excerpt = cursor.item();
1424
1425 start_excerpt
1426 .zip(end_excerpt)
1427 .and_then(|(start_excerpt, end_excerpt)| {
1428 if start_excerpt.id != end_excerpt.id {
1429 return None;
1430 }
1431
1432 let excerpt_buffer_start =
1433 start_excerpt.range.start.to_offset(&start_excerpt.buffer);
1434 let excerpt_buffer_end = excerpt_buffer_start + start_excerpt.text_summary.bytes;
1435
1436 let start_in_buffer =
1437 excerpt_buffer_start + range.start.saturating_sub(*cursor.start());
1438 let end_in_buffer =
1439 excerpt_buffer_start + range.end.saturating_sub(*cursor.start());
1440 let mut ancestor_buffer_range = start_excerpt
1441 .buffer
1442 .range_for_syntax_ancestor(start_in_buffer..end_in_buffer)?;
1443 ancestor_buffer_range.start =
1444 cmp::max(ancestor_buffer_range.start, excerpt_buffer_start);
1445 ancestor_buffer_range.end = cmp::min(ancestor_buffer_range.end, excerpt_buffer_end);
1446
1447 let start = cursor.start() + (ancestor_buffer_range.start - excerpt_buffer_start);
1448 let end = cursor.start() + (ancestor_buffer_range.end - excerpt_buffer_start);
1449 Some(start..end)
1450 })
1451 }
1452
1453 fn buffer_snapshot_for_excerpt<'a>(
1454 &'a self,
1455 excerpt_id: &'a ExcerptId,
1456 ) -> Option<&'a BufferSnapshot> {
1457 let mut cursor = self.excerpts.cursor::<Option<&ExcerptId>>();
1458 cursor.seek(&Some(excerpt_id), Bias::Left, &());
1459 if let Some(excerpt) = cursor.item() {
1460 if excerpt.id == *excerpt_id {
1461 return Some(&excerpt.buffer);
1462 }
1463 }
1464 None
1465 }
1466
1467 pub fn remote_selections_in_range<'a>(
1468 &'a self,
1469 range: &'a Range<Anchor>,
1470 ) -> impl 'a + Iterator<Item = (ReplicaId, Selection<Anchor>)> {
1471 let mut cursor = self.excerpts.cursor::<Option<&ExcerptId>>();
1472 cursor.seek(&Some(&range.start.excerpt_id), Bias::Left, &());
1473 cursor
1474 .take_while(move |excerpt| excerpt.id <= range.end.excerpt_id)
1475 .flat_map(move |excerpt| {
1476 let mut query_range = excerpt.range.start.clone()..excerpt.range.end.clone();
1477 if excerpt.id == range.start.excerpt_id {
1478 query_range.start = range.start.text_anchor.clone();
1479 }
1480 if excerpt.id == range.end.excerpt_id {
1481 query_range.end = range.end.text_anchor.clone();
1482 }
1483
1484 excerpt
1485 .buffer
1486 .remote_selections_in_range(query_range)
1487 .flat_map(move |(replica_id, selections)| {
1488 selections.map(move |selection| {
1489 let mut start = Anchor {
1490 excerpt_id: excerpt.id.clone(),
1491 text_anchor: selection.start.clone(),
1492 };
1493 let mut end = Anchor {
1494 excerpt_id: excerpt.id.clone(),
1495 text_anchor: selection.end.clone(),
1496 };
1497 if range.start.cmp(&start, self).unwrap().is_gt() {
1498 start = range.start.clone();
1499 }
1500 if range.end.cmp(&end, self).unwrap().is_lt() {
1501 end = range.end.clone();
1502 }
1503
1504 (
1505 replica_id,
1506 Selection {
1507 id: selection.id,
1508 start,
1509 end,
1510 reversed: selection.reversed,
1511 goal: selection.goal,
1512 },
1513 )
1514 })
1515 })
1516 })
1517 }
1518}
1519
1520impl History {
1521 fn start_transaction(&mut self, now: Instant) -> Option<TransactionId> {
1522 self.transaction_depth += 1;
1523 if self.transaction_depth == 1 {
1524 let id = post_inc(&mut self.next_transaction_id);
1525 self.undo_stack.push(Transaction {
1526 id,
1527 buffer_transactions: Default::default(),
1528 first_edit_at: now,
1529 last_edit_at: now,
1530 });
1531 Some(id)
1532 } else {
1533 None
1534 }
1535 }
1536
1537 fn end_transaction(
1538 &mut self,
1539 now: Instant,
1540 buffer_transactions: HashSet<(usize, TransactionId)>,
1541 ) -> bool {
1542 assert_ne!(self.transaction_depth, 0);
1543 self.transaction_depth -= 1;
1544 if self.transaction_depth == 0 {
1545 if buffer_transactions.is_empty() {
1546 self.undo_stack.pop();
1547 false
1548 } else {
1549 let transaction = self.undo_stack.last_mut().unwrap();
1550 transaction.last_edit_at = now;
1551 transaction.buffer_transactions.extend(buffer_transactions);
1552 true
1553 }
1554 } else {
1555 false
1556 }
1557 }
1558
1559 fn pop_undo(&mut self) -> Option<&Transaction> {
1560 assert_eq!(self.transaction_depth, 0);
1561 if let Some(transaction) = self.undo_stack.pop() {
1562 self.redo_stack.push(transaction);
1563 self.redo_stack.last()
1564 } else {
1565 None
1566 }
1567 }
1568
1569 fn pop_redo(&mut self) -> Option<&Transaction> {
1570 assert_eq!(self.transaction_depth, 0);
1571 if let Some(transaction) = self.redo_stack.pop() {
1572 self.undo_stack.push(transaction);
1573 self.undo_stack.last()
1574 } else {
1575 None
1576 }
1577 }
1578
1579 fn group(&mut self) -> Option<TransactionId> {
1580 let mut new_len = self.undo_stack.len();
1581 let mut transactions = self.undo_stack.iter_mut();
1582
1583 if let Some(mut transaction) = transactions.next_back() {
1584 while let Some(prev_transaction) = transactions.next_back() {
1585 if transaction.first_edit_at - prev_transaction.last_edit_at <= self.group_interval
1586 {
1587 transaction = prev_transaction;
1588 new_len -= 1;
1589 } else {
1590 break;
1591 }
1592 }
1593 }
1594
1595 let (transactions_to_keep, transactions_to_merge) = self.undo_stack.split_at_mut(new_len);
1596 if let Some(last_transaction) = transactions_to_keep.last_mut() {
1597 if let Some(transaction) = transactions_to_merge.last() {
1598 last_transaction.last_edit_at = transaction.last_edit_at;
1599 }
1600 }
1601
1602 self.undo_stack.truncate(new_len);
1603 self.undo_stack.last().map(|t| t.id)
1604 }
1605}
1606
1607impl Excerpt {
1608 fn new(
1609 id: ExcerptId,
1610 buffer_id: usize,
1611 buffer: BufferSnapshot,
1612 range: Range<text::Anchor>,
1613 header_height: u8,
1614 render_header: Option<RenderHeaderFn>,
1615 has_trailing_newline: bool,
1616 ) -> Self {
1617 Excerpt {
1618 id,
1619 text_summary: buffer.text_summary_for_range::<TextSummary, _>(range.to_offset(&buffer)),
1620 buffer_id,
1621 buffer,
1622 range,
1623 header_height,
1624 render_header,
1625 has_trailing_newline,
1626 }
1627 }
1628
1629 fn chunks_in_range<'a>(
1630 &'a self,
1631 range: Range<usize>,
1632 theme: Option<&'a SyntaxTheme>,
1633 ) -> ExcerptChunks<'a> {
1634 let content_start = self.range.start.to_offset(&self.buffer);
1635 let chunks_start = content_start + range.start;
1636 let chunks_end = content_start + cmp::min(range.end, self.text_summary.bytes);
1637
1638 let footer_height = if self.has_trailing_newline
1639 && range.start <= self.text_summary.bytes
1640 && range.end > self.text_summary.bytes
1641 {
1642 1
1643 } else {
1644 0
1645 };
1646
1647 let content_chunks = self.buffer.chunks(chunks_start..chunks_end, theme);
1648
1649 ExcerptChunks {
1650 content_chunks,
1651 footer_height,
1652 }
1653 }
1654
1655 fn bytes_in_range(&self, range: Range<usize>) -> ExcerptBytes {
1656 let content_start = self.range.start.to_offset(&self.buffer);
1657 let bytes_start = content_start + range.start;
1658 let bytes_end = content_start + cmp::min(range.end, self.text_summary.bytes);
1659 let footer_height = if self.has_trailing_newline
1660 && range.start <= self.text_summary.bytes
1661 && range.end > self.text_summary.bytes
1662 {
1663 1
1664 } else {
1665 0
1666 };
1667 let content_bytes = self.buffer.bytes_in_range(bytes_start..bytes_end);
1668
1669 ExcerptBytes {
1670 content_bytes,
1671 footer_height,
1672 }
1673 }
1674
1675 fn clip_anchor(&self, text_anchor: text::Anchor) -> text::Anchor {
1676 if text_anchor
1677 .cmp(&self.range.start, &self.buffer)
1678 .unwrap()
1679 .is_lt()
1680 {
1681 self.range.start.clone()
1682 } else if text_anchor
1683 .cmp(&self.range.end, &self.buffer)
1684 .unwrap()
1685 .is_gt()
1686 {
1687 self.range.end.clone()
1688 } else {
1689 text_anchor
1690 }
1691 }
1692}
1693
1694impl fmt::Debug for Excerpt {
1695 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1696 f.debug_struct("Excerpt")
1697 .field("id", &self.id)
1698 .field("buffer_id", &self.buffer_id)
1699 .field("range", &self.range)
1700 .field("text_summary", &self.text_summary)
1701 .field("has_trailing_newline", &self.has_trailing_newline)
1702 .finish()
1703 }
1704}
1705
1706impl sum_tree::Item for Excerpt {
1707 type Summary = ExcerptSummary;
1708
1709 fn summary(&self) -> Self::Summary {
1710 let mut text = self.text_summary.clone();
1711 if self.has_trailing_newline {
1712 text += TextSummary::from("\n");
1713 }
1714 ExcerptSummary {
1715 excerpt_id: self.id.clone(),
1716 text,
1717 }
1718 }
1719}
1720
1721impl sum_tree::Summary for ExcerptSummary {
1722 type Context = ();
1723
1724 fn add_summary(&mut self, summary: &Self, _: &()) {
1725 debug_assert!(summary.excerpt_id > self.excerpt_id);
1726 self.excerpt_id = summary.excerpt_id.clone();
1727 self.text.add_summary(&summary.text, &());
1728 }
1729}
1730
1731impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for TextSummary {
1732 fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
1733 *self += &summary.text;
1734 }
1735}
1736
1737impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for usize {
1738 fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
1739 *self += summary.text.bytes;
1740 }
1741}
1742
1743impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, ExcerptSummary> for usize {
1744 fn cmp(&self, cursor_location: &ExcerptSummary, _: &()) -> cmp::Ordering {
1745 Ord::cmp(self, &cursor_location.text.bytes)
1746 }
1747}
1748
1749impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, ExcerptSummary> for Option<&'a ExcerptId> {
1750 fn cmp(&self, cursor_location: &ExcerptSummary, _: &()) -> cmp::Ordering {
1751 Ord::cmp(self, &Some(&cursor_location.excerpt_id))
1752 }
1753}
1754
1755impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Point {
1756 fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
1757 *self += summary.text.lines;
1758 }
1759}
1760
1761impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for PointUtf16 {
1762 fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
1763 *self += summary.text.lines_utf16
1764 }
1765}
1766
1767impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Option<&'a ExcerptId> {
1768 fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
1769 *self = Some(&summary.excerpt_id);
1770 }
1771}
1772
1773impl<'a> MultiBufferRows<'a> {
1774 pub fn seek(&mut self, row: u32) {
1775 self.buffer_row_range = 0..0;
1776
1777 self.excerpts
1778 .seek_forward(&Point::new(row, 0), Bias::Right, &());
1779 if self.excerpts.item().is_none() {
1780 self.excerpts.prev(&());
1781
1782 if self.excerpts.item().is_none() && row == 0 {
1783 self.buffer_row_range = 0..1;
1784 return;
1785 }
1786 }
1787
1788 if let Some(excerpt) = self.excerpts.item() {
1789 let overshoot = row - self.excerpts.start().row;
1790 let excerpt_start = excerpt.range.start.to_point(&excerpt.buffer).row;
1791 self.buffer_row_range.start = excerpt_start + overshoot;
1792 self.buffer_row_range.end = excerpt_start + excerpt.text_summary.lines.row + 1;
1793 }
1794 }
1795}
1796
1797impl<'a> Iterator for MultiBufferRows<'a> {
1798 type Item = Option<u32>;
1799
1800 fn next(&mut self) -> Option<Self::Item> {
1801 loop {
1802 if !self.buffer_row_range.is_empty() {
1803 let row = Some(self.buffer_row_range.start);
1804 self.buffer_row_range.start += 1;
1805 return Some(row);
1806 }
1807 self.excerpts.item()?;
1808 self.excerpts.next(&());
1809 let excerpt = self.excerpts.item()?;
1810 self.buffer_row_range.start = excerpt.range.start.to_point(&excerpt.buffer).row;
1811 self.buffer_row_range.end =
1812 self.buffer_row_range.start + excerpt.text_summary.lines.row + 1;
1813 }
1814 }
1815}
1816
1817impl<'a> MultiBufferChunks<'a> {
1818 pub fn offset(&self) -> usize {
1819 self.range.start
1820 }
1821
1822 pub fn seek(&mut self, offset: usize) {
1823 self.range.start = offset;
1824 self.excerpts.seek(&offset, Bias::Right, &());
1825 if let Some(excerpt) = self.excerpts.item() {
1826 self.excerpt_chunks = Some(excerpt.chunks_in_range(
1827 self.range.start - self.excerpts.start()..self.range.end - self.excerpts.start(),
1828 self.theme,
1829 ));
1830 } else {
1831 self.excerpt_chunks = None;
1832 }
1833 }
1834}
1835
1836impl<'a> Iterator for MultiBufferChunks<'a> {
1837 type Item = Chunk<'a>;
1838
1839 fn next(&mut self) -> Option<Self::Item> {
1840 if self.range.is_empty() {
1841 None
1842 } else if let Some(chunk) = self.excerpt_chunks.as_mut()?.next() {
1843 self.range.start += chunk.text.len();
1844 Some(chunk)
1845 } else {
1846 self.excerpts.next(&());
1847 let excerpt = self.excerpts.item()?;
1848 self.excerpt_chunks = Some(
1849 excerpt.chunks_in_range(0..self.range.end - self.excerpts.start(), self.theme),
1850 );
1851 self.next()
1852 }
1853 }
1854}
1855
1856impl<'a> MultiBufferBytes<'a> {
1857 fn consume(&mut self, len: usize) {
1858 self.range.start += len;
1859 self.chunk = &self.chunk[len..];
1860
1861 if !self.range.is_empty() && self.chunk.is_empty() {
1862 if let Some(chunk) = self.excerpt_bytes.as_mut().and_then(|bytes| bytes.next()) {
1863 self.chunk = chunk;
1864 } else {
1865 self.excerpts.next(&());
1866 if let Some(excerpt) = self.excerpts.item() {
1867 let mut excerpt_bytes =
1868 excerpt.bytes_in_range(0..self.range.end - self.excerpts.start());
1869 self.chunk = excerpt_bytes.next().unwrap();
1870 self.excerpt_bytes = Some(excerpt_bytes);
1871 }
1872 }
1873 }
1874 }
1875}
1876
1877impl<'a> Iterator for MultiBufferBytes<'a> {
1878 type Item = &'a [u8];
1879
1880 fn next(&mut self) -> Option<Self::Item> {
1881 let chunk = self.chunk;
1882 if chunk.is_empty() {
1883 None
1884 } else {
1885 self.consume(chunk.len());
1886 Some(chunk)
1887 }
1888 }
1889}
1890
1891impl<'a> io::Read for MultiBufferBytes<'a> {
1892 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
1893 let len = cmp::min(buf.len(), self.chunk.len());
1894 buf[..len].copy_from_slice(&self.chunk[..len]);
1895 if len > 0 {
1896 self.consume(len);
1897 }
1898 Ok(len)
1899 }
1900}
1901
1902impl<'a> Iterator for ExcerptBytes<'a> {
1903 type Item = &'a [u8];
1904
1905 fn next(&mut self) -> Option<Self::Item> {
1906 if let Some(chunk) = self.content_bytes.next() {
1907 if !chunk.is_empty() {
1908 return Some(chunk);
1909 }
1910 }
1911
1912 if self.footer_height > 0 {
1913 let result = &NEWLINES[..self.footer_height];
1914 self.footer_height = 0;
1915 return Some(result);
1916 }
1917
1918 None
1919 }
1920}
1921
1922impl<'a> Iterator for ExcerptChunks<'a> {
1923 type Item = Chunk<'a>;
1924
1925 fn next(&mut self) -> Option<Self::Item> {
1926 if let Some(chunk) = self.content_chunks.next() {
1927 return Some(chunk);
1928 }
1929
1930 if self.footer_height > 0 {
1931 let text = unsafe { str::from_utf8_unchecked(&NEWLINES[..self.footer_height]) };
1932 self.footer_height = 0;
1933 return Some(Chunk {
1934 text,
1935 ..Default::default()
1936 });
1937 }
1938
1939 None
1940 }
1941}
1942
1943impl ToOffset for Point {
1944 fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize {
1945 snapshot.point_to_offset(*self)
1946 }
1947}
1948
1949impl ToOffset for PointUtf16 {
1950 fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize {
1951 snapshot.point_utf16_to_offset(*self)
1952 }
1953}
1954
1955impl ToOffset for usize {
1956 fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize {
1957 assert!(*self <= snapshot.len(), "offset is out of range");
1958 *self
1959 }
1960}
1961
1962impl ToPoint for usize {
1963 fn to_point<'a>(&self, snapshot: &MultiBufferSnapshot) -> Point {
1964 snapshot.offset_to_point(*self)
1965 }
1966}
1967
1968impl ToPoint for Point {
1969 fn to_point<'a>(&self, _: &MultiBufferSnapshot) -> Point {
1970 *self
1971 }
1972}
1973
1974#[cfg(test)]
1975mod tests {
1976 use super::*;
1977 use gpui::{elements::Empty, Element, MutableAppContext};
1978 use language::{Buffer, Rope};
1979 use rand::prelude::*;
1980 use std::env;
1981 use text::{Point, RandomCharIter};
1982 use util::test::sample_text;
1983
1984 #[gpui::test]
1985 fn test_singleton_multibuffer(cx: &mut MutableAppContext) {
1986 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6, 'a'), cx));
1987 let multibuffer = cx.add_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
1988
1989 let snapshot = multibuffer.read(cx).snapshot(cx);
1990 assert_eq!(snapshot.text(), buffer.read(cx).text());
1991
1992 assert_eq!(
1993 snapshot.buffer_rows(0).collect::<Vec<_>>(),
1994 (0..buffer.read(cx).row_count())
1995 .map(Some)
1996 .collect::<Vec<_>>()
1997 );
1998
1999 buffer.update(cx, |buffer, cx| buffer.edit([1..3], "XXX\n", cx));
2000 let snapshot = multibuffer.read(cx).snapshot(cx);
2001
2002 assert_eq!(snapshot.text(), buffer.read(cx).text());
2003 assert_eq!(
2004 snapshot.buffer_rows(0).collect::<Vec<_>>(),
2005 (0..buffer.read(cx).row_count())
2006 .map(Some)
2007 .collect::<Vec<_>>()
2008 );
2009 }
2010
2011 #[gpui::test]
2012 fn test_excerpt_buffer(cx: &mut MutableAppContext) {
2013 let buffer_1 = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6, 'a'), cx));
2014 let buffer_2 = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6, 'g'), cx));
2015 let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
2016
2017 let subscription = multibuffer.update(cx, |multibuffer, cx| {
2018 let subscription = multibuffer.subscribe();
2019 multibuffer.push_excerpt(
2020 ExcerptProperties {
2021 buffer: &buffer_1,
2022 range: Point::new(1, 2)..Point::new(2, 5),
2023 header_height: 2,
2024 render_header: Some(Arc::new(|_| Empty::new().named("header 1"))),
2025 },
2026 cx,
2027 );
2028 assert_eq!(
2029 subscription.consume().into_inner(),
2030 [Edit {
2031 old: 0..0,
2032 new: 0..10
2033 }]
2034 );
2035
2036 multibuffer.push_excerpt(
2037 ExcerptProperties {
2038 buffer: &buffer_1,
2039 range: Point::new(3, 3)..Point::new(4, 4),
2040 header_height: 1,
2041 render_header: Some(Arc::new(|_| Empty::new().named("header 2"))),
2042 },
2043 cx,
2044 );
2045 multibuffer.push_excerpt(
2046 ExcerptProperties {
2047 buffer: &buffer_2,
2048 range: Point::new(3, 1)..Point::new(3, 3),
2049 header_height: 3,
2050 render_header: Some(Arc::new(|_| Empty::new().named("header 3"))),
2051 },
2052 cx,
2053 );
2054 assert_eq!(
2055 subscription.consume().into_inner(),
2056 [Edit {
2057 old: 10..10,
2058 new: 10..22
2059 }]
2060 );
2061
2062 subscription
2063 });
2064
2065 let snapshot = multibuffer.read(cx).snapshot(cx);
2066 assert_eq!(
2067 snapshot.text(),
2068 concat!(
2069 "bbbb\n", // Preserve newlines
2070 "ccccc\n", //
2071 "ddd\n", //
2072 "eeee\n", //
2073 "jj" //
2074 )
2075 );
2076 assert_eq!(
2077 snapshot.buffer_rows(0).collect::<Vec<_>>(),
2078 [Some(1), Some(2), Some(3), Some(4), Some(3)]
2079 );
2080 assert_eq!(
2081 snapshot.buffer_rows(2).collect::<Vec<_>>(),
2082 [Some(3), Some(4), Some(3)]
2083 );
2084 assert_eq!(snapshot.buffer_rows(4).collect::<Vec<_>>(), [Some(3)]);
2085 assert_eq!(snapshot.buffer_rows(5).collect::<Vec<_>>(), []);
2086
2087 {
2088 let snapshot = multibuffer.read(cx).read(cx);
2089 assert_eq!(
2090 snapshot
2091 .excerpt_headers_in_range(0..snapshot.max_point().row + 1)
2092 .map(|(start_row, header_height, render)| (
2093 start_row,
2094 header_height,
2095 render(cx).name().unwrap().to_string()
2096 ))
2097 .collect::<Vec<_>>(),
2098 &[
2099 (0, 2, "header 1".into()),
2100 (2, 1, "header 2".into()),
2101 (4, 3, "header 3".into())
2102 ]
2103 );
2104
2105 assert_eq!(
2106 snapshot
2107 .excerpt_headers_in_range(1..4)
2108 .map(|(start_row, header_height, render)| (
2109 start_row,
2110 header_height,
2111 render(cx).name().unwrap().to_string()
2112 ))
2113 .collect::<Vec<_>>(),
2114 &[(2, 1, "header 2".into())]
2115 );
2116
2117 assert_eq!(
2118 snapshot
2119 .excerpt_headers_in_range(2..5)
2120 .map(|(start_row, header_height, render)| (
2121 start_row,
2122 header_height,
2123 render(cx).name().unwrap().to_string()
2124 ))
2125 .collect::<Vec<_>>(),
2126 &[(2, 1, "header 2".into()), (4, 3, "header 3".into())]
2127 );
2128 }
2129
2130 buffer_1.update(cx, |buffer, cx| {
2131 buffer.edit(
2132 [
2133 Point::new(0, 0)..Point::new(0, 0),
2134 Point::new(2, 1)..Point::new(2, 3),
2135 ],
2136 "\n",
2137 cx,
2138 );
2139 });
2140
2141 assert_eq!(
2142 multibuffer.read(cx).snapshot(cx).text(),
2143 concat!(
2144 "bbbb\n", // Preserve newlines
2145 "c\n", //
2146 "cc\n", //
2147 "ddd\n", //
2148 "eeee\n", //
2149 "jj" //
2150 )
2151 );
2152
2153 assert_eq!(
2154 subscription.consume().into_inner(),
2155 [Edit {
2156 old: 6..8,
2157 new: 6..7
2158 }]
2159 );
2160
2161 // bbbb\nc\ncc\nddd\neeee\njj
2162 let multibuffer = multibuffer.read(cx).snapshot(cx);
2163 assert_eq!(
2164 multibuffer.clip_point(Point::new(0, 5), Bias::Left),
2165 Point::new(0, 4)
2166 );
2167 assert_eq!(
2168 multibuffer.clip_point(Point::new(0, 5), Bias::Right),
2169 Point::new(0, 4)
2170 );
2171 assert_eq!(
2172 multibuffer.clip_point(Point::new(5, 1), Bias::Right),
2173 Point::new(5, 1)
2174 );
2175 assert_eq!(
2176 multibuffer.clip_point(Point::new(5, 2), Bias::Right),
2177 Point::new(5, 2)
2178 );
2179 assert_eq!(
2180 multibuffer.clip_point(Point::new(5, 3), Bias::Right),
2181 Point::new(5, 2)
2182 );
2183 }
2184
2185 #[gpui::test]
2186 fn test_empty_excerpt_buffer(cx: &mut MutableAppContext) {
2187 let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
2188
2189 let snapshot = multibuffer.read(cx).snapshot(cx);
2190 assert_eq!(snapshot.text(), "");
2191 assert_eq!(snapshot.buffer_rows(0).collect::<Vec<_>>(), &[Some(0)]);
2192 assert_eq!(snapshot.buffer_rows(1).collect::<Vec<_>>(), &[]);
2193 }
2194
2195 #[gpui::test]
2196 fn test_singleton_multibuffer_anchors(cx: &mut MutableAppContext) {
2197 let buffer = cx.add_model(|cx| Buffer::new(0, "abcd", cx));
2198 let multibuffer = cx.add_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
2199 let old_snapshot = multibuffer.read(cx).snapshot(cx);
2200 buffer.update(cx, |buffer, cx| {
2201 buffer.edit([0..0], "X", cx);
2202 buffer.edit([5..5], "Y", cx);
2203 });
2204 let new_snapshot = multibuffer.read(cx).snapshot(cx);
2205
2206 assert_eq!(old_snapshot.text(), "abcd");
2207 assert_eq!(new_snapshot.text(), "XabcdY");
2208
2209 assert_eq!(old_snapshot.anchor_before(0).to_offset(&new_snapshot), 0);
2210 assert_eq!(old_snapshot.anchor_after(0).to_offset(&new_snapshot), 1);
2211 assert_eq!(old_snapshot.anchor_before(4).to_offset(&new_snapshot), 5);
2212 assert_eq!(old_snapshot.anchor_after(4).to_offset(&new_snapshot), 6);
2213 }
2214
2215 #[gpui::test]
2216 fn test_multibuffer_anchors(cx: &mut MutableAppContext) {
2217 let buffer_1 = cx.add_model(|cx| Buffer::new(0, "abcd", cx));
2218 let buffer_2 = cx.add_model(|cx| Buffer::new(0, "efghi", cx));
2219 let multibuffer = cx.add_model(|cx| {
2220 let mut multibuffer = MultiBuffer::new(0);
2221 multibuffer.push_excerpt(
2222 ExcerptProperties {
2223 buffer: &buffer_1,
2224 range: 0..4,
2225 header_height: 1,
2226 render_header: None,
2227 },
2228 cx,
2229 );
2230 multibuffer.push_excerpt(
2231 ExcerptProperties {
2232 buffer: &buffer_2,
2233 range: 0..5,
2234 header_height: 1,
2235 render_header: None,
2236 },
2237 cx,
2238 );
2239 multibuffer
2240 });
2241 let old_snapshot = multibuffer.read(cx).snapshot(cx);
2242
2243 assert_eq!(old_snapshot.anchor_before(0).to_offset(&old_snapshot), 0);
2244 assert_eq!(old_snapshot.anchor_after(0).to_offset(&old_snapshot), 0);
2245 assert_eq!(Anchor::min().to_offset(&old_snapshot), 0);
2246 assert_eq!(Anchor::min().to_offset(&old_snapshot), 0);
2247 assert_eq!(Anchor::max().to_offset(&old_snapshot), 10);
2248 assert_eq!(Anchor::max().to_offset(&old_snapshot), 10);
2249
2250 buffer_1.update(cx, |buffer, cx| {
2251 buffer.edit([0..0], "W", cx);
2252 buffer.edit([5..5], "X", cx);
2253 });
2254 buffer_2.update(cx, |buffer, cx| {
2255 buffer.edit([0..0], "Y", cx);
2256 buffer.edit([6..0], "Z", cx);
2257 });
2258 let new_snapshot = multibuffer.read(cx).snapshot(cx);
2259
2260 assert_eq!(old_snapshot.text(), "abcd\nefghi");
2261 assert_eq!(new_snapshot.text(), "WabcdX\nYefghiZ");
2262
2263 assert_eq!(old_snapshot.anchor_before(0).to_offset(&new_snapshot), 0);
2264 assert_eq!(old_snapshot.anchor_after(0).to_offset(&new_snapshot), 1);
2265 assert_eq!(old_snapshot.anchor_before(1).to_offset(&new_snapshot), 2);
2266 assert_eq!(old_snapshot.anchor_after(1).to_offset(&new_snapshot), 2);
2267 assert_eq!(old_snapshot.anchor_before(2).to_offset(&new_snapshot), 3);
2268 assert_eq!(old_snapshot.anchor_after(2).to_offset(&new_snapshot), 3);
2269 assert_eq!(old_snapshot.anchor_before(5).to_offset(&new_snapshot), 7);
2270 assert_eq!(old_snapshot.anchor_after(5).to_offset(&new_snapshot), 8);
2271 assert_eq!(old_snapshot.anchor_before(10).to_offset(&new_snapshot), 13);
2272 assert_eq!(old_snapshot.anchor_after(10).to_offset(&new_snapshot), 14);
2273 }
2274
2275 #[gpui::test(iterations = 100)]
2276 fn test_random_excerpts(cx: &mut MutableAppContext, mut rng: StdRng) {
2277 let operations = env::var("OPERATIONS")
2278 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2279 .unwrap_or(10);
2280
2281 let mut buffers: Vec<ModelHandle<Buffer>> = Vec::new();
2282 let list = cx.add_model(|_| MultiBuffer::new(0));
2283 let mut excerpt_ids = Vec::new();
2284 let mut expected_excerpts = Vec::new();
2285 let mut old_versions = Vec::new();
2286
2287 for _ in 0..operations {
2288 match rng.gen_range(0..100) {
2289 0..=19 if !buffers.is_empty() => {
2290 let buffer = buffers.choose(&mut rng).unwrap();
2291 buffer.update(cx, |buf, cx| buf.randomly_edit(&mut rng, 5, cx));
2292 }
2293 _ => {
2294 let buffer_handle = if buffers.is_empty() || rng.gen_bool(0.4) {
2295 let base_text = RandomCharIter::new(&mut rng).take(10).collect::<String>();
2296 buffers.push(cx.add_model(|cx| Buffer::new(0, base_text, cx)));
2297 buffers.last().unwrap()
2298 } else {
2299 buffers.choose(&mut rng).unwrap()
2300 };
2301
2302 let buffer = buffer_handle.read(cx);
2303 let end_ix = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Bias::Right);
2304 let start_ix = buffer.clip_offset(rng.gen_range(0..=end_ix), Bias::Left);
2305 let header_height = rng.gen_range(0..=5);
2306 let anchor_range = buffer.anchor_before(start_ix)..buffer.anchor_after(end_ix);
2307 log::info!(
2308 "Pushing excerpt wih header {}, buffer {}: {:?}[{:?}] = {:?}",
2309 header_height,
2310 buffer_handle.id(),
2311 buffer.text(),
2312 start_ix..end_ix,
2313 &buffer.text()[start_ix..end_ix]
2314 );
2315
2316 let excerpt_id = list.update(cx, |list, cx| {
2317 list.push_excerpt(
2318 ExcerptProperties {
2319 buffer: &buffer_handle,
2320 range: start_ix..end_ix,
2321 header_height,
2322 render_header: None,
2323 },
2324 cx,
2325 )
2326 });
2327 excerpt_ids.push(excerpt_id);
2328 expected_excerpts.push((buffer_handle.clone(), anchor_range, header_height));
2329 }
2330 }
2331
2332 if rng.gen_bool(0.3) {
2333 list.update(cx, |list, cx| {
2334 old_versions.push((list.snapshot(cx), list.subscribe()));
2335 })
2336 }
2337
2338 let snapshot = list.read(cx).snapshot(cx);
2339
2340 let mut excerpt_starts = Vec::new();
2341 let mut expected_text = String::new();
2342 let mut expected_buffer_rows = Vec::new();
2343 for (buffer, range, _) in &expected_excerpts {
2344 let buffer = buffer.read(cx);
2345 let buffer_range = range.to_offset(buffer);
2346
2347 excerpt_starts.push(TextSummary::from(expected_text.as_str()));
2348 expected_text.extend(buffer.text_for_range(buffer_range.clone()));
2349 expected_text.push('\n');
2350
2351 let buffer_row_range = buffer.offset_to_point(buffer_range.start).row
2352 ..=buffer.offset_to_point(buffer_range.end).row;
2353 for row in buffer_row_range {
2354 expected_buffer_rows.push(Some(row));
2355 }
2356 }
2357 // Remove final trailing newline.
2358 if !expected_excerpts.is_empty() {
2359 expected_text.pop();
2360 }
2361
2362 assert_eq!(snapshot.text(), expected_text);
2363 log::info!("MultiBuffer text: {:?}", expected_text);
2364
2365 assert_eq!(
2366 snapshot.buffer_rows(0).collect::<Vec<_>>(),
2367 expected_buffer_rows,
2368 );
2369
2370 for _ in 0..5 {
2371 let start_row = rng.gen_range(0..=expected_buffer_rows.len());
2372 assert_eq!(
2373 snapshot.buffer_rows(start_row as u32).collect::<Vec<_>>(),
2374 &expected_buffer_rows[start_row..],
2375 "buffer_rows({})",
2376 start_row
2377 );
2378 }
2379
2380 let mut excerpt_starts = excerpt_starts.into_iter();
2381 for (buffer, range, _) in &expected_excerpts {
2382 let buffer_id = buffer.id();
2383 let buffer = buffer.read(cx);
2384 let buffer_range = range.to_offset(buffer);
2385 let buffer_start_point = buffer.offset_to_point(buffer_range.start);
2386 let buffer_start_point_utf16 =
2387 buffer.text_summary_for_range::<PointUtf16, _>(0..buffer_range.start);
2388
2389 let excerpt_start = excerpt_starts.next().unwrap();
2390 let mut offset = excerpt_start.bytes;
2391 let mut buffer_offset = buffer_range.start;
2392 let mut point = excerpt_start.lines;
2393 let mut buffer_point = buffer_start_point;
2394 let mut point_utf16 = excerpt_start.lines_utf16;
2395 let mut buffer_point_utf16 = buffer_start_point_utf16;
2396 for ch in buffer
2397 .snapshot()
2398 .chunks(buffer_range.clone(), None)
2399 .flat_map(|c| c.text.chars())
2400 {
2401 for _ in 0..ch.len_utf8() {
2402 let left_offset = snapshot.clip_offset(offset, Bias::Left);
2403 let right_offset = snapshot.clip_offset(offset, Bias::Right);
2404 let buffer_left_offset = buffer.clip_offset(buffer_offset, Bias::Left);
2405 let buffer_right_offset = buffer.clip_offset(buffer_offset, Bias::Right);
2406 assert_eq!(
2407 left_offset,
2408 excerpt_start.bytes + (buffer_left_offset - buffer_range.start),
2409 "clip_offset({:?}, Left). buffer: {:?}, buffer offset: {:?}",
2410 offset,
2411 buffer_id,
2412 buffer_offset,
2413 );
2414 assert_eq!(
2415 right_offset,
2416 excerpt_start.bytes + (buffer_right_offset - buffer_range.start),
2417 "clip_offset({:?}, Right). buffer: {:?}, buffer offset: {:?}",
2418 offset,
2419 buffer_id,
2420 buffer_offset,
2421 );
2422
2423 let left_point = snapshot.clip_point(point, Bias::Left);
2424 let right_point = snapshot.clip_point(point, Bias::Right);
2425 let buffer_left_point = buffer.clip_point(buffer_point, Bias::Left);
2426 let buffer_right_point = buffer.clip_point(buffer_point, Bias::Right);
2427 assert_eq!(
2428 left_point,
2429 excerpt_start.lines + (buffer_left_point - buffer_start_point),
2430 "clip_point({:?}, Left). buffer: {:?}, buffer point: {:?}",
2431 point,
2432 buffer_id,
2433 buffer_point,
2434 );
2435 assert_eq!(
2436 right_point,
2437 excerpt_start.lines + (buffer_right_point - buffer_start_point),
2438 "clip_point({:?}, Right). buffer: {:?}, buffer point: {:?}",
2439 point,
2440 buffer_id,
2441 buffer_point,
2442 );
2443
2444 assert_eq!(
2445 snapshot.point_to_offset(left_point),
2446 left_offset,
2447 "point_to_offset({:?})",
2448 left_point,
2449 );
2450 assert_eq!(
2451 snapshot.offset_to_point(left_offset),
2452 left_point,
2453 "offset_to_point({:?})",
2454 left_offset,
2455 );
2456
2457 offset += 1;
2458 buffer_offset += 1;
2459 if ch == '\n' {
2460 point += Point::new(1, 0);
2461 buffer_point += Point::new(1, 0);
2462 } else {
2463 point += Point::new(0, 1);
2464 buffer_point += Point::new(0, 1);
2465 }
2466 }
2467
2468 for _ in 0..ch.len_utf16() {
2469 let left_point_utf16 = snapshot.clip_point_utf16(point_utf16, Bias::Left);
2470 let right_point_utf16 = snapshot.clip_point_utf16(point_utf16, Bias::Right);
2471 let buffer_left_point_utf16 =
2472 buffer.clip_point_utf16(buffer_point_utf16, Bias::Left);
2473 let buffer_right_point_utf16 =
2474 buffer.clip_point_utf16(buffer_point_utf16, Bias::Right);
2475 assert_eq!(
2476 left_point_utf16,
2477 excerpt_start.lines_utf16
2478 + (buffer_left_point_utf16 - buffer_start_point_utf16),
2479 "clip_point_utf16({:?}, Left). buffer: {:?}, buffer point_utf16: {:?}",
2480 point_utf16,
2481 buffer_id,
2482 buffer_point_utf16,
2483 );
2484 assert_eq!(
2485 right_point_utf16,
2486 excerpt_start.lines_utf16
2487 + (buffer_right_point_utf16 - buffer_start_point_utf16),
2488 "clip_point_utf16({:?}, Right). buffer: {:?}, buffer point_utf16: {:?}",
2489 point_utf16,
2490 buffer_id,
2491 buffer_point_utf16,
2492 );
2493
2494 if ch == '\n' {
2495 point_utf16 += PointUtf16::new(1, 0);
2496 buffer_point_utf16 += PointUtf16::new(1, 0);
2497 } else {
2498 point_utf16 += PointUtf16::new(0, 1);
2499 buffer_point_utf16 += PointUtf16::new(0, 1);
2500 }
2501 }
2502 }
2503 }
2504
2505 for (row, line) in expected_text.split('\n').enumerate() {
2506 assert_eq!(
2507 snapshot.line_len(row as u32),
2508 line.len() as u32,
2509 "line_len({}).",
2510 row
2511 );
2512 }
2513
2514 let text_rope = Rope::from(expected_text.as_str());
2515 for _ in 0..10 {
2516 let end_ix = text_rope.clip_offset(rng.gen_range(0..=text_rope.len()), Bias::Right);
2517 let start_ix = text_rope.clip_offset(rng.gen_range(0..=end_ix), Bias::Left);
2518
2519 assert_eq!(
2520 snapshot
2521 .text_for_range(start_ix..end_ix)
2522 .collect::<String>(),
2523 &expected_text[start_ix..end_ix],
2524 "incorrect text for range {:?}",
2525 start_ix..end_ix
2526 );
2527
2528 let expected_summary = TextSummary::from(&expected_text[start_ix..end_ix]);
2529 assert_eq!(
2530 snapshot.text_summary_for_range::<TextSummary, _>(start_ix..end_ix),
2531 expected_summary,
2532 "incorrect summary for range {:?}",
2533 start_ix..end_ix
2534 );
2535 }
2536
2537 for _ in 0..10 {
2538 let end_ix = text_rope.clip_offset(rng.gen_range(0..=text_rope.len()), Bias::Right);
2539 assert_eq!(
2540 snapshot.reversed_chars_at(end_ix).collect::<String>(),
2541 expected_text[..end_ix].chars().rev().collect::<String>(),
2542 );
2543 }
2544
2545 for _ in 0..10 {
2546 let end_ix = rng.gen_range(0..=text_rope.len());
2547 let start_ix = rng.gen_range(0..=end_ix);
2548 assert_eq!(
2549 snapshot
2550 .bytes_in_range(start_ix..end_ix)
2551 .flatten()
2552 .copied()
2553 .collect::<Vec<_>>(),
2554 expected_text.as_bytes()[start_ix..end_ix].to_vec(),
2555 "bytes_in_range({:?})",
2556 start_ix..end_ix,
2557 );
2558 }
2559 }
2560
2561 let snapshot = list.read(cx).snapshot(cx);
2562 for (old_snapshot, subscription) in old_versions {
2563 let edits = subscription.consume().into_inner();
2564
2565 log::info!(
2566 "applying subscription edits to old text: {:?}: {:?}",
2567 old_snapshot.text(),
2568 edits,
2569 );
2570
2571 let mut text = old_snapshot.text();
2572 for edit in edits {
2573 let new_text: String = snapshot.text_for_range(edit.new.clone()).collect();
2574 text.replace_range(edit.new.start..edit.new.start + edit.old.len(), &new_text);
2575 }
2576 assert_eq!(text.to_string(), snapshot.text());
2577 }
2578 }
2579
2580 #[gpui::test]
2581 fn test_history(cx: &mut MutableAppContext) {
2582 let buffer_1 = cx.add_model(|cx| Buffer::new(0, "1234", cx));
2583 let buffer_2 = cx.add_model(|cx| Buffer::new(0, "5678", cx));
2584 let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
2585 let group_interval = multibuffer.read(cx).history.group_interval;
2586 multibuffer.update(cx, |multibuffer, cx| {
2587 multibuffer.push_excerpt(
2588 ExcerptProperties {
2589 buffer: &buffer_1,
2590 range: 0..buffer_1.read(cx).len(),
2591 header_height: 0,
2592 render_header: None,
2593 },
2594 cx,
2595 );
2596 multibuffer.push_excerpt(
2597 ExcerptProperties {
2598 buffer: &buffer_2,
2599 range: 0..buffer_2.read(cx).len(),
2600 header_height: 0,
2601 render_header: None,
2602 },
2603 cx,
2604 );
2605 });
2606
2607 let mut now = Instant::now();
2608
2609 multibuffer.update(cx, |multibuffer, cx| {
2610 multibuffer.start_transaction_at(now, cx);
2611 multibuffer.edit(
2612 [
2613 Point::new(0, 0)..Point::new(0, 0),
2614 Point::new(1, 0)..Point::new(1, 0),
2615 ],
2616 "A",
2617 cx,
2618 );
2619 multibuffer.edit(
2620 [
2621 Point::new(0, 1)..Point::new(0, 1),
2622 Point::new(1, 1)..Point::new(1, 1),
2623 ],
2624 "B",
2625 cx,
2626 );
2627 multibuffer.end_transaction_at(now, cx);
2628 assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
2629
2630 now += 2 * group_interval;
2631 multibuffer.start_transaction_at(now, cx);
2632 multibuffer.edit([2..2], "C", cx);
2633 multibuffer.end_transaction_at(now, cx);
2634 assert_eq!(multibuffer.read(cx).text(), "ABC1234\nAB5678");
2635
2636 multibuffer.undo(cx);
2637 assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
2638
2639 multibuffer.undo(cx);
2640 assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
2641
2642 multibuffer.redo(cx);
2643 assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
2644
2645 multibuffer.redo(cx);
2646 assert_eq!(multibuffer.read(cx).text(), "ABC1234\nAB5678");
2647
2648 buffer_1.update(cx, |buffer_1, cx| buffer_1.undo(cx));
2649 assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
2650
2651 multibuffer.undo(cx);
2652 assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
2653
2654 multibuffer.redo(cx);
2655 assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
2656
2657 multibuffer.redo(cx);
2658 assert_eq!(multibuffer.read(cx).text(), "ABC1234\nAB5678");
2659
2660 multibuffer.undo(cx);
2661 assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
2662
2663 buffer_1.update(cx, |buffer_1, cx| buffer_1.redo(cx));
2664 assert_eq!(multibuffer.read(cx).text(), "ABC1234\nAB5678");
2665
2666 multibuffer.undo(cx);
2667 assert_eq!(multibuffer.read(cx).text(), "C1234\n5678");
2668 });
2669 }
2670}