1mod anchor;
2#[cfg(test)]
3mod multi_buffer_tests;
4mod path_key;
5mod transaction;
6
7use crate::anchor::ExcerptAnchor;
8
9use self::transaction::History;
10
11pub use anchor::{Anchor, AnchorRangeExt};
12
13use anchor::AnchorSeekTarget;
14use anyhow::{Result, anyhow};
15use buffer_diff::{
16 BufferDiff, BufferDiffEvent, BufferDiffSnapshot, DiffChanged, DiffHunkSecondaryStatus,
17 DiffHunkStatus, DiffHunkStatusKind,
18};
19use clock::ReplicaId;
20use collections::{BTreeMap, Bound, HashMap, HashSet};
21use gpui::{App, Context, Entity, EventEmitter};
22use itertools::Itertools;
23use language::{
24 AutoindentMode, BracketMatch, Buffer, BufferChunks, BufferRow, BufferSnapshot, Capability,
25 CharClassifier, CharKind, CharScopeContext, Chunk, CursorShape, DiagnosticEntryRef, File,
26 IndentGuideSettings, IndentSize, Language, LanguageScope, OffsetRangeExt, OffsetUtf16, Outline,
27 OutlineItem, Point, PointUtf16, Selection, TextDimension, TextObject, ToOffset as _,
28 ToPoint as _, TransactionId, TreeSitterOptions, Unclipped,
29 language_settings::{LanguageSettings, language_settings},
30};
31
32#[cfg(any(test, feature = "test-support"))]
33use gpui::AppContext as _;
34
35use rope::DimensionPair;
36use smallvec::SmallVec;
37use smol::future::yield_now;
38use std::{
39 any::type_name,
40 borrow::Cow,
41 cell::{Cell, OnceCell, Ref, RefCell},
42 cmp::{self, Ordering},
43 fmt,
44 future::Future,
45 io,
46 iter::{self, FromIterator},
47 mem,
48 ops::{self, Add, AddAssign, ControlFlow, Range, RangeBounds, Sub, SubAssign},
49 rc::Rc,
50 str,
51 sync::{Arc, OnceLock},
52 time::Duration,
53};
54use sum_tree::{Bias, Cursor, Dimension, Dimensions, SumTree, TreeMap};
55use text::{
56 BufferId, Edit, LineIndent, TextSummary,
57 locator::Locator,
58 subscription::{Subscription, Topic},
59};
60use theme::SyntaxTheme;
61use ztracing::instrument;
62
63pub use self::path_key::{PathExcerptInsertResult, PathKey};
64
65pub static EXCERPT_CONTEXT_LINES: OnceLock<fn(&App) -> u32> = OnceLock::new();
66
67pub fn excerpt_context_lines(cx: &App) -> u32 {
68 EXCERPT_CONTEXT_LINES.get().map(|f| f(cx)).unwrap_or(2)
69}
70
71/// One or more [`Buffers`](Buffer) being edited in a single view.
72///
73/// See <https://zed.dev/features#multi-buffers>
74pub struct MultiBuffer {
75 /// A snapshot of the [`Excerpt`]s in the MultiBuffer.
76 /// Use [`MultiBuffer::snapshot`] to get a up-to-date snapshot.
77 snapshot: RefCell<MultiBufferSnapshot>,
78 /// Contains the state of the buffers being edited
79 buffers: BTreeMap<BufferId, BufferState>,
80 /// Mapping from buffer IDs to their diff states
81 diffs: HashMap<BufferId, DiffState>,
82 subscriptions: Topic<MultiBufferOffset>,
83 /// If true, the multi-buffer only contains a single [`Buffer`] and a single [`Excerpt`]
84 singleton: bool,
85 /// The history of the multi-buffer.
86 history: History,
87 /// The explicit title of the multi-buffer.
88 /// If `None`, it will be derived from the underlying path or content.
89 title: Option<String>,
90 /// The writing capability of the multi-buffer.
91 capability: Capability,
92 buffer_changed_since_sync: Rc<Cell<bool>>,
93}
94
95#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
96pub struct PathKeyIndex(u64);
97
98pub struct ExcerptInfo {
99 path_key: PathKey,
100 buffer_id: BufferId,
101 range: ExcerptRange<text::Anchor>,
102}
103
104#[derive(Clone, Debug, PartialEq, Eq)]
105pub enum Event {
106 BufferUpdated {
107 buffer: Entity<Buffer>,
108 path_key: PathKey,
109 ranges: Vec<Range<text::Anchor>>,
110 },
111 BuffersRemoved {
112 removed_buffer_ids: Vec<BufferId>,
113 },
114 BuffersEdited {
115 buffer_ids: Vec<BufferId>,
116 },
117 DiffHunksToggled,
118 Edited {
119 edited_buffer: Option<Entity<Buffer>>,
120 },
121 TransactionUndone {
122 transaction_id: TransactionId,
123 },
124 Reloaded,
125 LanguageChanged(BufferId, bool),
126 Reparsed(BufferId),
127 Saved,
128 FileHandleChanged,
129 DirtyChanged,
130 DiagnosticsUpdated,
131 BufferDiffChanged,
132}
133
134/// A diff hunk, representing a range of consequent lines in a multibuffer.
135#[derive(Debug, Clone, PartialEq, Eq)]
136pub struct MultiBufferDiffHunk {
137 /// The row range in the multibuffer where this diff hunk appears.
138 pub row_range: Range<MultiBufferRow>,
139 path_key_index: PathKeyIndex,
140 /// The buffer ID that this hunk belongs to.
141 pub buffer_id: BufferId,
142 /// The range of the underlying buffer that this hunk corresponds to.
143 pub buffer_range: Range<text::Anchor>,
144 /// The range within the buffer's diff base that this hunk corresponds to.
145 pub diff_base_byte_range: Range<BufferOffset>,
146 /// The status of this hunk (added/modified/deleted and secondary status).
147 pub status: DiffHunkStatus,
148 /// The word diffs for this hunk.
149 pub word_diffs: Vec<Range<MultiBufferOffset>>,
150}
151
152impl MultiBufferDiffHunk {
153 pub fn status(&self) -> DiffHunkStatus {
154 self.status
155 }
156
157 pub fn is_created_file(&self) -> bool {
158 self.diff_base_byte_range == (BufferOffset(0)..BufferOffset(0))
159 && self.buffer_range.start.is_min()
160 && self.buffer_range.end.is_max()
161 }
162
163 pub fn multi_buffer_range(&self) -> Range<Anchor> {
164 let start = Anchor::in_buffer(self.path_key_index, self.buffer_range.start);
165 let end = Anchor::in_buffer(self.path_key_index, self.buffer_range.end);
166 start..end
167 }
168}
169
170pub type MultiBufferPoint = Point;
171type ExcerptOffset = ExcerptDimension<MultiBufferOffset>;
172type ExcerptPoint = ExcerptDimension<Point>;
173
174#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq, Hash, serde::Deserialize)]
175#[serde(transparent)]
176pub struct MultiBufferRow(pub u32);
177
178impl MultiBufferRow {
179 pub const MIN: Self = Self(0);
180 pub const MAX: Self = Self(u32::MAX);
181}
182
183impl ops::Add<usize> for MultiBufferRow {
184 type Output = Self;
185
186 fn add(self, rhs: usize) -> Self::Output {
187 MultiBufferRow(self.0 + rhs as u32)
188 }
189}
190
191pub trait MultiBufferDimension: 'static + Copy + Default + std::fmt::Debug {
192 type TextDimension: TextDimension;
193 fn from_summary(summary: &MBTextSummary) -> Self;
194
195 fn add_text_dim(&mut self, summary: &Self::TextDimension);
196
197 fn add_mb_text_summary(&mut self, summary: &MBTextSummary);
198}
199
200// todo(lw): MultiBufferPoint
201impl MultiBufferDimension for Point {
202 type TextDimension = Point;
203 fn from_summary(summary: &MBTextSummary) -> Self {
204 summary.lines
205 }
206
207 fn add_text_dim(&mut self, other: &Self::TextDimension) {
208 *self += *other;
209 }
210
211 fn add_mb_text_summary(&mut self, summary: &MBTextSummary) {
212 *self += summary.lines;
213 }
214}
215
216// todo(lw): MultiBufferPointUtf16
217impl MultiBufferDimension for PointUtf16 {
218 type TextDimension = PointUtf16;
219 fn from_summary(summary: &MBTextSummary) -> Self {
220 summary.lines_utf16()
221 }
222
223 fn add_text_dim(&mut self, other: &Self::TextDimension) {
224 *self += *other;
225 }
226
227 fn add_mb_text_summary(&mut self, summary: &MBTextSummary) {
228 *self += summary.lines_utf16();
229 }
230}
231
232#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq, Hash, serde::Deserialize)]
233pub struct MultiBufferOffset(pub usize);
234
235impl fmt::Display for MultiBufferOffset {
236 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
237 write!(f, "{}", self.0)
238 }
239}
240
241impl rand::distr::uniform::SampleUniform for MultiBufferOffset {
242 type Sampler = MultiBufferOffsetUniformSampler;
243}
244
245pub struct MultiBufferOffsetUniformSampler {
246 sampler: rand::distr::uniform::UniformUsize,
247}
248
249impl rand::distr::uniform::UniformSampler for MultiBufferOffsetUniformSampler {
250 type X = MultiBufferOffset;
251
252 fn new<B1, B2>(low_b: B1, high_b: B2) -> Result<Self, rand::distr::uniform::Error>
253 where
254 B1: rand::distr::uniform::SampleBorrow<Self::X> + Sized,
255 B2: rand::distr::uniform::SampleBorrow<Self::X> + Sized,
256 {
257 let low = *low_b.borrow();
258 let high = *high_b.borrow();
259 let sampler = rand::distr::uniform::UniformUsize::new(low.0, high.0);
260 sampler.map(|sampler| MultiBufferOffsetUniformSampler { sampler })
261 }
262
263 #[inline] // if the range is constant, this helps LLVM to do the
264 // calculations at compile-time.
265 fn new_inclusive<B1, B2>(low_b: B1, high_b: B2) -> Result<Self, rand::distr::uniform::Error>
266 where
267 B1: rand::distr::uniform::SampleBorrow<Self::X> + Sized,
268 B2: rand::distr::uniform::SampleBorrow<Self::X> + Sized,
269 {
270 let low = *low_b.borrow();
271 let high = *high_b.borrow();
272 let sampler = rand::distr::uniform::UniformUsize::new_inclusive(low.0, high.0);
273 sampler.map(|sampler| MultiBufferOffsetUniformSampler { sampler })
274 }
275
276 fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> Self::X {
277 MultiBufferOffset(self.sampler.sample(rng))
278 }
279}
280impl MultiBufferDimension for MultiBufferOffset {
281 type TextDimension = usize;
282 fn from_summary(summary: &MBTextSummary) -> Self {
283 summary.len
284 }
285
286 fn add_text_dim(&mut self, other: &Self::TextDimension) {
287 self.0 += *other;
288 }
289
290 fn add_mb_text_summary(&mut self, summary: &MBTextSummary) {
291 *self += summary.len;
292 }
293}
294impl MultiBufferDimension for MultiBufferOffsetUtf16 {
295 type TextDimension = OffsetUtf16;
296 fn from_summary(summary: &MBTextSummary) -> Self {
297 MultiBufferOffsetUtf16(summary.len_utf16)
298 }
299
300 fn add_text_dim(&mut self, other: &Self::TextDimension) {
301 self.0 += *other;
302 }
303
304 fn add_mb_text_summary(&mut self, summary: &MBTextSummary) {
305 self.0 += summary.len_utf16;
306 }
307}
308
309#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq, Hash, serde::Deserialize)]
310pub struct BufferOffset(pub usize);
311
312impl TextDimension for BufferOffset {
313 fn from_text_summary(summary: &TextSummary) -> Self {
314 BufferOffset(usize::from_text_summary(summary))
315 }
316 fn from_chunk(chunk: rope::ChunkSlice) -> Self {
317 BufferOffset(usize::from_chunk(chunk))
318 }
319 fn add_assign(&mut self, other: &Self) {
320 TextDimension::add_assign(&mut self.0, &other.0);
321 }
322}
323impl<'a> sum_tree::Dimension<'a, rope::ChunkSummary> for BufferOffset {
324 fn zero(cx: ()) -> Self {
325 BufferOffset(<usize as sum_tree::Dimension<'a, rope::ChunkSummary>>::zero(cx))
326 }
327
328 fn add_summary(&mut self, summary: &'a rope::ChunkSummary, cx: ()) {
329 usize::add_summary(&mut self.0, summary, cx);
330 }
331}
332
333impl Sub for BufferOffset {
334 type Output = usize;
335
336 fn sub(self, other: BufferOffset) -> Self::Output {
337 self.0 - other.0
338 }
339}
340
341impl AddAssign<DimensionPair<usize, Point>> for BufferOffset {
342 fn add_assign(&mut self, other: DimensionPair<usize, Point>) {
343 self.0 += other.key;
344 }
345}
346
347impl language::ToPoint for BufferOffset {
348 fn to_point(&self, snapshot: &text::BufferSnapshot) -> Point {
349 self.0.to_point(snapshot)
350 }
351}
352
353impl language::ToPointUtf16 for BufferOffset {
354 fn to_point_utf16(&self, snapshot: &text::BufferSnapshot) -> PointUtf16 {
355 self.0.to_point_utf16(snapshot)
356 }
357}
358
359impl language::ToOffset for BufferOffset {
360 fn to_offset(&self, snapshot: &text::BufferSnapshot) -> usize {
361 self.0.to_offset(snapshot)
362 }
363}
364
365impl language::ToOffsetUtf16 for BufferOffset {
366 fn to_offset_utf16(&self, snapshot: &text::BufferSnapshot) -> OffsetUtf16 {
367 self.0.to_offset_utf16(snapshot)
368 }
369}
370
371#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
372pub struct MultiBufferOffsetUtf16(pub OffsetUtf16);
373
374impl ops::Add<usize> for MultiBufferOffsetUtf16 {
375 type Output = MultiBufferOffsetUtf16;
376
377 fn add(self, rhs: usize) -> Self::Output {
378 MultiBufferOffsetUtf16(OffsetUtf16(self.0.0 + rhs))
379 }
380}
381
382impl ops::Add<OffsetUtf16> for MultiBufferOffsetUtf16 {
383 type Output = Self;
384
385 fn add(self, rhs: OffsetUtf16) -> Self::Output {
386 MultiBufferOffsetUtf16(self.0 + rhs)
387 }
388}
389
390impl AddAssign<OffsetUtf16> for MultiBufferOffsetUtf16 {
391 fn add_assign(&mut self, rhs: OffsetUtf16) {
392 self.0 += rhs;
393 }
394}
395
396impl AddAssign<usize> for MultiBufferOffsetUtf16 {
397 fn add_assign(&mut self, rhs: usize) {
398 self.0.0 += rhs;
399 }
400}
401
402impl Sub for MultiBufferOffsetUtf16 {
403 type Output = OffsetUtf16;
404
405 fn sub(self, other: MultiBufferOffsetUtf16) -> Self::Output {
406 self.0 - other.0
407 }
408}
409
410impl Sub<OffsetUtf16> for MultiBufferOffsetUtf16 {
411 type Output = MultiBufferOffsetUtf16;
412
413 fn sub(self, other: OffsetUtf16) -> Self::Output {
414 MultiBufferOffsetUtf16(self.0 - other)
415 }
416}
417
418#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
419pub struct BufferOffsetUtf16(pub OffsetUtf16);
420
421impl MultiBufferOffset {
422 const ZERO: Self = Self(0);
423 pub fn saturating_sub(self, other: MultiBufferOffset) -> usize {
424 self.0.saturating_sub(other.0)
425 }
426 pub fn saturating_sub_usize(self, other: usize) -> MultiBufferOffset {
427 MultiBufferOffset(self.0.saturating_sub(other))
428 }
429}
430
431impl ops::Sub for MultiBufferOffset {
432 type Output = usize;
433
434 fn sub(self, other: MultiBufferOffset) -> Self::Output {
435 self.0 - other.0
436 }
437}
438
439impl ops::Sub<usize> for MultiBufferOffset {
440 type Output = Self;
441
442 fn sub(self, other: usize) -> Self::Output {
443 MultiBufferOffset(self.0 - other)
444 }
445}
446
447impl ops::SubAssign<usize> for MultiBufferOffset {
448 fn sub_assign(&mut self, other: usize) {
449 self.0 -= other;
450 }
451}
452
453impl ops::Add<usize> for BufferOffset {
454 type Output = Self;
455
456 fn add(self, rhs: usize) -> Self::Output {
457 BufferOffset(self.0 + rhs)
458 }
459}
460
461impl ops::AddAssign<usize> for BufferOffset {
462 fn add_assign(&mut self, other: usize) {
463 self.0 += other;
464 }
465}
466
467impl ops::Add<usize> for MultiBufferOffset {
468 type Output = Self;
469
470 fn add(self, rhs: usize) -> Self::Output {
471 MultiBufferOffset(self.0 + rhs)
472 }
473}
474
475impl ops::AddAssign<usize> for MultiBufferOffset {
476 fn add_assign(&mut self, other: usize) {
477 self.0 += other;
478 }
479}
480
481impl ops::Add<isize> for MultiBufferOffset {
482 type Output = Self;
483
484 fn add(self, rhs: isize) -> Self::Output {
485 MultiBufferOffset((self.0 as isize + rhs) as usize)
486 }
487}
488
489impl ops::Add for MultiBufferOffset {
490 type Output = Self;
491
492 fn add(self, rhs: MultiBufferOffset) -> Self::Output {
493 MultiBufferOffset(self.0 + rhs.0)
494 }
495}
496
497impl ops::AddAssign<MultiBufferOffset> for MultiBufferOffset {
498 fn add_assign(&mut self, other: MultiBufferOffset) {
499 self.0 += other.0;
500 }
501}
502
503pub trait ToOffset: 'static + fmt::Debug {
504 fn to_offset(&self, snapshot: &MultiBufferSnapshot) -> MultiBufferOffset;
505 fn to_offset_utf16(&self, snapshot: &MultiBufferSnapshot) -> MultiBufferOffsetUtf16;
506}
507
508pub trait ToPoint: 'static + fmt::Debug {
509 fn to_point(&self, snapshot: &MultiBufferSnapshot) -> Point;
510 fn to_point_utf16(&self, snapshot: &MultiBufferSnapshot) -> PointUtf16;
511}
512
513struct BufferState {
514 buffer: Entity<Buffer>,
515 last_version: RefCell<clock::Global>,
516 last_non_text_state_update_count: Cell<usize>,
517 _subscriptions: [gpui::Subscription; 2],
518}
519
520struct DiffState {
521 diff: Entity<BufferDiff>,
522 main_buffer: Option<Entity<language::Buffer>>,
523 _subscription: gpui::Subscription,
524}
525
526impl DiffState {
527 fn snapshot(&self, cx: &App) -> DiffStateSnapshot {
528 DiffStateSnapshot {
529 diff: self.diff.read(cx).snapshot(cx),
530 main_buffer: self.main_buffer.as_ref().map(|b| b.read(cx).snapshot()),
531 }
532 }
533}
534
535#[derive(Clone)]
536struct DiffStateSnapshot {
537 diff: BufferDiffSnapshot,
538 main_buffer: Option<language::BufferSnapshot>,
539}
540
541impl std::ops::Deref for DiffStateSnapshot {
542 type Target = BufferDiffSnapshot;
543
544 fn deref(&self) -> &Self::Target {
545 &self.diff
546 }
547}
548
549impl DiffState {
550 fn new(diff: Entity<BufferDiff>, cx: &mut Context<MultiBuffer>) -> Self {
551 DiffState {
552 _subscription: cx.subscribe(&diff, |this, diff, event, cx| match event {
553 BufferDiffEvent::DiffChanged(DiffChanged {
554 changed_range,
555 base_text_changed_range: _,
556 extended_range,
557 }) => {
558 let use_extended = this.snapshot.borrow().use_extended_diff_range;
559 let range = if use_extended {
560 extended_range.clone()
561 } else {
562 changed_range.clone()
563 };
564 if let Some(range) = range {
565 this.buffer_diff_changed(diff, range, cx)
566 }
567 cx.emit(Event::BufferDiffChanged);
568 }
569 BufferDiffEvent::LanguageChanged => this.buffer_diff_language_changed(diff, cx),
570 _ => {}
571 }),
572 diff,
573 main_buffer: None,
574 }
575 }
576
577 fn new_inverted(
578 diff: Entity<BufferDiff>,
579 main_buffer: Entity<language::Buffer>,
580 cx: &mut Context<MultiBuffer>,
581 ) -> Self {
582 let weak_main_buffer = main_buffer.downgrade();
583 DiffState {
584 _subscription: cx.subscribe(&diff, {
585 move |this, diff, event, cx| {
586 let Some(main_buffer) = weak_main_buffer.upgrade() else {
587 return;
588 };
589 match event {
590 BufferDiffEvent::DiffChanged(DiffChanged {
591 changed_range: _,
592 base_text_changed_range,
593 extended_range: _,
594 }) => {
595 this.inverted_buffer_diff_changed(
596 diff,
597 main_buffer,
598 base_text_changed_range.clone(),
599 cx,
600 );
601 cx.emit(Event::BufferDiffChanged);
602 }
603 BufferDiffEvent::LanguageChanged => {
604 this.inverted_buffer_diff_language_changed(diff, main_buffer, cx)
605 }
606 _ => {}
607 }
608 }
609 }),
610 diff,
611 main_buffer: Some(main_buffer),
612 }
613 }
614}
615
616/// The contents of a [`MultiBuffer`] at a single point in time.
617#[derive(Clone, Default)]
618pub struct MultiBufferSnapshot {
619 excerpts: SumTree<Excerpt>,
620 path_keys_by_buffer: TreeMap<BufferId, PathKey>,
621 path_keys_by_index: TreeMap<PathKeyIndex, PathKey>,
622 diffs: TreeMap<BufferId, DiffStateSnapshot>,
623 diff_transforms: SumTree<DiffTransform>,
624 non_text_state_update_count: usize,
625 edit_count: usize,
626 is_dirty: bool,
627 has_deleted_file: bool,
628 has_conflict: bool,
629 has_inverted_diff: bool,
630 singleton: bool,
631 trailing_excerpt_update_count: usize,
632 all_diff_hunks_expanded: bool,
633 show_deleted_hunks: bool,
634 use_extended_diff_range: bool,
635 show_headers: bool,
636}
637
638#[derive(Debug, Clone)]
639enum DiffTransform {
640 BufferContent {
641 summary: MBTextSummary,
642 inserted_hunk_info: Option<DiffTransformHunkInfo>,
643 },
644 DeletedHunk {
645 summary: TextSummary,
646 buffer_id: BufferId,
647 hunk_info: DiffTransformHunkInfo,
648 base_text_byte_range: Range<usize>,
649 has_trailing_newline: bool,
650 },
651}
652
653#[derive(Clone, Copy, Debug)]
654struct DiffTransformHunkInfo {
655 buffer_id: BufferId,
656 hunk_start_anchor: text::Anchor,
657 hunk_secondary_status: DiffHunkSecondaryStatus,
658 is_logically_deleted: bool,
659 excerpt_end: ExcerptAnchor,
660}
661
662impl Eq for DiffTransformHunkInfo {}
663
664impl PartialEq for DiffTransformHunkInfo {
665 fn eq(&self, other: &DiffTransformHunkInfo) -> bool {
666 self.buffer_id == other.buffer_id && self.hunk_start_anchor == other.hunk_start_anchor
667 }
668}
669
670impl std::hash::Hash for DiffTransformHunkInfo {
671 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
672 self.buffer_id.hash(state);
673 self.hunk_start_anchor.hash(state);
674 }
675}
676
677#[derive(Clone)]
678pub struct ExcerptBoundaryInfo {
679 pub buffer: Arc<BufferSnapshot>,
680 pub buffer_id: BufferId,
681 pub range: ExcerptRange<text::Anchor>,
682 pub end_row: MultiBufferRow,
683}
684
685impl std::fmt::Debug for ExcerptBoundaryInfo {
686 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
687 f.debug_struct(type_name::<Self>())
688 .field("buffer_id", &self.buffer_id)
689 .field("path", &self.buffer.file().map(|f| f.path()))
690 .field("range", &self.range)
691 .finish()
692 }
693}
694
695/// A boundary between `Excerpt`s in a [`MultiBuffer`]
696#[derive(Debug)]
697pub struct ExcerptBoundary {
698 pub prev: Option<ExcerptBoundaryInfo>,
699 pub next: ExcerptBoundaryInfo,
700 /// The row in the `MultiBuffer` where the boundary is located
701 pub row: MultiBufferRow,
702}
703
704impl ExcerptBoundary {
705 pub fn starts_new_buffer(&self) -> bool {
706 match (self.prev.as_ref(), &self.next) {
707 (None, _) => true,
708 (Some(prev), next) => prev.buffer_id != next.buffer_id,
709 }
710 }
711}
712
713#[derive(Clone, Debug, PartialEq, Eq)]
714pub struct ExpandInfo {
715 pub direction: ExpandExcerptDirection,
716 pub excerpt_range: Range<text::Anchor>,
717}
718
719#[derive(Clone, Debug, Default, PartialEq, Eq)]
720pub struct RowInfo {
721 pub buffer_id: Option<BufferId>,
722 pub buffer_row: Option<u32>,
723 pub multibuffer_row: Option<MultiBufferRow>,
724 pub diff_status: Option<buffer_diff::DiffHunkStatus>,
725 pub expand_info: Option<ExpandInfo>,
726 pub wrapped_buffer_row: Option<u32>,
727}
728
729/// A slice into a [`Buffer`] that is being edited in a [`MultiBuffer`].
730#[derive(Clone)]
731pub(crate) struct Excerpt {
732 /// The location of the excerpt in the [`MultiBuffer`]
733 pub(crate) path_key: PathKey,
734 pub(crate) path_key_index: PathKeyIndex,
735 /// A snapshot of the buffer being excerpted
736 pub(crate) buffer: Arc<BufferSnapshot>,
737 /// The range of the buffer to be shown in the excerpt
738 pub(crate) range: ExcerptRange<text::Anchor>,
739
740 /// The last row in the excerpted slice of the buffer
741 pub(crate) max_buffer_row: BufferRow,
742 /// A summary of the text in the excerpt
743 pub(crate) text_summary: TextSummary,
744 pub(crate) has_trailing_newline: bool,
745}
746
747/// A public view into an `Excerpt` in a [`MultiBuffer`].
748///
749/// Contains methods for getting the [`Buffer`] of the excerpt,
750/// as well as mapping offsets to/from buffer and multibuffer coordinates.
751#[derive(Clone)]
752pub struct MultiBufferExcerpt<'a> {
753 excerpt: &'a Excerpt,
754 diff_transforms:
755 sum_tree::Cursor<'a, 'static, DiffTransform, DiffTransforms<MultiBufferOffset>>,
756 /// The offset in the multibuffer considering diff transforms.
757 offset: MultiBufferOffset,
758 /// The offset in the multibuffer without diff transforms.
759 excerpt_offset: ExcerptOffset,
760 buffer_offset: BufferOffset,
761}
762
763/// A range of text from a single [`Buffer`], to be shown as an `Excerpt`.
764/// These ranges are relative to the buffer itself
765#[derive(Clone, Debug, Eq, PartialEq, Hash)]
766pub struct ExcerptRange<T> {
767 /// The full range of text to be shown in the excerpt.
768 pub context: Range<T>,
769 /// The primary range of text to be highlighted in the excerpt.
770 /// In a multi-buffer search, this would be the text that matched the search
771 pub primary: Range<T>,
772}
773
774impl<T: Clone> ExcerptRange<T> {
775 pub fn new(context: Range<T>) -> Self {
776 Self {
777 context: context.clone(),
778 primary: context,
779 }
780 }
781}
782
783impl ExcerptRange<text::Anchor> {
784 pub fn contains(&self, t: &text::Anchor, snapshot: &BufferSnapshot) -> bool {
785 self.context.start.cmp(t, snapshot).is_le() && self.context.end.cmp(t, snapshot).is_ge()
786 }
787}
788
789#[derive(Clone, Debug)]
790pub struct ExcerptSummary {
791 path_key: PathKey,
792 max_anchor: Anchor,
793 widest_line_number: u32,
794 text: MBTextSummary,
795 count: usize,
796}
797
798impl ExcerptSummary {
799 pub fn min() -> Self {
800 ExcerptSummary {
801 path_key: PathKey::min(),
802 max_anchor: Anchor::Min,
803 widest_line_number: 0,
804 text: MBTextSummary::default(),
805 count: 0,
806 }
807 }
808
809 pub fn len(&self) -> ExcerptOffset {
810 ExcerptDimension(self.text.len)
811 }
812}
813
814#[derive(Debug, Clone)]
815pub struct DiffTransformSummary {
816 input: MBTextSummary,
817 output: MBTextSummary,
818}
819
820/// Summary of a string of text.
821#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
822pub struct MBTextSummary {
823 /// Length in bytes.
824 pub len: MultiBufferOffset,
825 /// Length in UTF-8.
826 pub chars: usize,
827 /// Length in UTF-16 code units
828 pub len_utf16: OffsetUtf16,
829 /// A point representing the number of lines and the length of the last line.
830 ///
831 /// In other words, it marks the point after the last byte in the text, (if
832 /// EOF was a character, this would be its position).
833 pub lines: Point,
834 /// How many `char`s are in the first line
835 pub first_line_chars: u32,
836 /// How many `char`s are in the last line
837 pub last_line_chars: u32,
838 /// How many UTF-16 code units are in the last line
839 pub last_line_len_utf16: u32,
840 /// The row idx of the longest row
841 pub longest_row: u32,
842 /// How many `char`s are in the longest row
843 pub longest_row_chars: u32,
844}
845
846impl From<TextSummary> for MBTextSummary {
847 fn from(summary: TextSummary) -> Self {
848 MBTextSummary {
849 len: MultiBufferOffset(summary.len),
850 chars: summary.chars,
851 len_utf16: summary.len_utf16,
852 lines: summary.lines,
853 first_line_chars: summary.first_line_chars,
854 last_line_chars: summary.last_line_chars,
855 last_line_len_utf16: summary.last_line_len_utf16,
856 longest_row: summary.longest_row,
857 longest_row_chars: summary.longest_row_chars,
858 }
859 }
860}
861
862impl From<MBTextSummary> for TextSummary {
863 fn from(summary: MBTextSummary) -> Self {
864 TextSummary {
865 len: summary.len.0,
866 chars: summary.chars,
867 len_utf16: summary.len_utf16,
868 lines: summary.lines,
869 first_line_chars: summary.first_line_chars,
870 last_line_chars: summary.last_line_chars,
871 last_line_len_utf16: summary.last_line_len_utf16,
872 longest_row: summary.longest_row,
873 longest_row_chars: summary.longest_row_chars,
874 }
875 }
876}
877
878impl From<&str> for MBTextSummary {
879 fn from(text: &str) -> Self {
880 MBTextSummary::from(TextSummary::from(text))
881 }
882}
883
884impl MultiBufferDimension for MBTextSummary {
885 type TextDimension = TextSummary;
886
887 fn from_summary(summary: &MBTextSummary) -> Self {
888 *summary
889 }
890
891 fn add_text_dim(&mut self, summary: &Self::TextDimension) {
892 *self += *summary;
893 }
894
895 fn add_mb_text_summary(&mut self, summary: &MBTextSummary) {
896 *self += *summary;
897 }
898}
899
900impl AddAssign for MBTextSummary {
901 fn add_assign(&mut self, other: MBTextSummary) {
902 let joined_chars = self.last_line_chars + other.first_line_chars;
903 if joined_chars > self.longest_row_chars {
904 self.longest_row = self.lines.row;
905 self.longest_row_chars = joined_chars;
906 }
907 if other.longest_row_chars > self.longest_row_chars {
908 self.longest_row = self.lines.row + other.longest_row;
909 self.longest_row_chars = other.longest_row_chars;
910 }
911
912 if self.lines.row == 0 {
913 self.first_line_chars += other.first_line_chars;
914 }
915
916 if other.lines.row == 0 {
917 self.last_line_chars += other.first_line_chars;
918 self.last_line_len_utf16 += other.last_line_len_utf16;
919 } else {
920 self.last_line_chars = other.last_line_chars;
921 self.last_line_len_utf16 = other.last_line_len_utf16;
922 }
923
924 self.chars += other.chars;
925 self.len += other.len;
926 self.len_utf16 += other.len_utf16;
927 self.lines += other.lines;
928 }
929}
930
931impl AddAssign<TextSummary> for MBTextSummary {
932 fn add_assign(&mut self, other: TextSummary) {
933 *self += MBTextSummary::from(other);
934 }
935}
936
937impl MBTextSummary {
938 pub fn lines_utf16(&self) -> PointUtf16 {
939 PointUtf16 {
940 row: self.lines.row,
941 column: self.last_line_len_utf16,
942 }
943 }
944}
945
946impl<K, V> MultiBufferDimension for DimensionPair<K, V>
947where
948 K: MultiBufferDimension,
949 V: MultiBufferDimension,
950{
951 type TextDimension = DimensionPair<K::TextDimension, V::TextDimension>;
952
953 fn from_summary(summary: &MBTextSummary) -> Self {
954 Self {
955 key: K::from_summary(summary),
956 value: Some(V::from_summary(summary)),
957 }
958 }
959
960 fn add_text_dim(&mut self, summary: &Self::TextDimension) {
961 self.key.add_text_dim(&summary.key);
962 if let Some(value) = &mut self.value {
963 if let Some(other_value) = summary.value.as_ref() {
964 value.add_text_dim(other_value);
965 }
966 }
967 }
968
969 fn add_mb_text_summary(&mut self, summary: &MBTextSummary) {
970 self.key.add_mb_text_summary(summary);
971 if let Some(value) = &mut self.value {
972 value.add_mb_text_summary(summary);
973 }
974 }
975}
976
977#[derive(Clone)]
978pub struct MultiBufferRows<'a> {
979 point: Point,
980 is_empty: bool,
981 is_singleton: bool,
982 cursor: MultiBufferCursor<'a, Point, Point>,
983}
984
985pub struct MultiBufferChunks<'a> {
986 excerpts: Cursor<'a, 'static, Excerpt, ExcerptOffset>,
987 diff_transforms:
988 Cursor<'a, 'static, DiffTransform, Dimensions<MultiBufferOffset, ExcerptOffset>>,
989 diffs: &'a TreeMap<BufferId, DiffStateSnapshot>,
990 diff_base_chunks: Option<(BufferId, BufferChunks<'a>)>,
991 buffer_chunk: Option<Chunk<'a>>,
992 range: Range<MultiBufferOffset>,
993 excerpt_offset_range: Range<ExcerptOffset>,
994 excerpt_chunks: Option<ExcerptChunks<'a>>,
995 language_aware: bool,
996}
997
998pub struct ReversedMultiBufferChunks<'a> {
999 cursor: MultiBufferCursor<'a, MultiBufferOffset, BufferOffset>,
1000 current_chunks: Option<rope::Chunks<'a>>,
1001 start: MultiBufferOffset,
1002 offset: MultiBufferOffset,
1003}
1004
1005pub struct MultiBufferBytes<'a> {
1006 range: Range<MultiBufferOffset>,
1007 cursor: MultiBufferCursor<'a, MultiBufferOffset, BufferOffset>,
1008 excerpt_bytes: Option<text::Bytes<'a>>,
1009 has_trailing_newline: bool,
1010 chunk: &'a [u8],
1011}
1012
1013pub struct ReversedMultiBufferBytes<'a> {
1014 range: Range<MultiBufferOffset>,
1015 chunks: ReversedMultiBufferChunks<'a>,
1016 chunk: &'a [u8],
1017}
1018
1019#[derive(Clone)]
1020struct DiffTransforms<MBD> {
1021 output_dimension: OutputDimension<MBD>,
1022 excerpt_dimension: ExcerptDimension<MBD>,
1023}
1024
1025impl<'a, MBD: MultiBufferDimension> Dimension<'a, DiffTransformSummary> for DiffTransforms<MBD> {
1026 fn zero(cx: <DiffTransformSummary as sum_tree::Summary>::Context<'_>) -> Self {
1027 Self {
1028 output_dimension: OutputDimension::zero(cx),
1029 excerpt_dimension: <ExcerptDimension<MBD> as Dimension<'a, DiffTransformSummary>>::zero(
1030 cx,
1031 ),
1032 }
1033 }
1034
1035 fn add_summary(
1036 &mut self,
1037 summary: &'a DiffTransformSummary,
1038 cx: <DiffTransformSummary as sum_tree::Summary>::Context<'_>,
1039 ) {
1040 self.output_dimension.add_summary(summary, cx);
1041 self.excerpt_dimension.add_summary(summary, cx);
1042 }
1043}
1044
1045#[derive(Clone)]
1046struct MultiBufferCursor<'a, MBD, BD> {
1047 excerpts: Cursor<'a, 'static, Excerpt, ExcerptDimension<MBD>>,
1048 diff_transforms: Cursor<'a, 'static, DiffTransform, DiffTransforms<MBD>>,
1049 diffs: &'a TreeMap<BufferId, DiffStateSnapshot>,
1050 cached_region: OnceCell<Option<MultiBufferRegion<'a, MBD, BD>>>,
1051}
1052
1053#[derive(Clone)]
1054struct MultiBufferRegion<'a, MBD, BD> {
1055 buffer: &'a BufferSnapshot,
1056 is_main_buffer: bool,
1057 diff_hunk_status: Option<DiffHunkStatus>,
1058 excerpt: &'a Excerpt,
1059 buffer_range: Range<BD>,
1060 range: Range<MBD>,
1061 has_trailing_newline: bool,
1062}
1063
1064struct ExcerptChunks<'a> {
1065 content_chunks: BufferChunks<'a>,
1066 end: ExcerptAnchor,
1067 has_footer: bool,
1068}
1069
1070#[derive(Debug)]
1071struct BufferEdit {
1072 range: Range<BufferOffset>,
1073 new_text: Arc<str>,
1074 is_insertion: bool,
1075 original_indent_column: Option<u32>,
1076}
1077
1078#[derive(Clone, Copy, Debug, PartialEq)]
1079enum DiffChangeKind {
1080 BufferEdited,
1081 DiffUpdated { base_changed: bool },
1082 ExpandOrCollapseHunks { expand: bool },
1083}
1084
1085#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1086pub enum ExpandExcerptDirection {
1087 Up,
1088 Down,
1089 UpAndDown,
1090}
1091
1092impl ExpandExcerptDirection {
1093 pub fn should_expand_up(&self) -> bool {
1094 match self {
1095 ExpandExcerptDirection::Up => true,
1096 ExpandExcerptDirection::Down => false,
1097 ExpandExcerptDirection::UpAndDown => true,
1098 }
1099 }
1100
1101 pub fn should_expand_down(&self) -> bool {
1102 match self {
1103 ExpandExcerptDirection::Up => false,
1104 ExpandExcerptDirection::Down => true,
1105 ExpandExcerptDirection::UpAndDown => true,
1106 }
1107 }
1108}
1109
1110#[derive(Clone, Debug, PartialEq)]
1111pub struct IndentGuide {
1112 pub buffer_id: BufferId,
1113 pub start_row: MultiBufferRow,
1114 pub end_row: MultiBufferRow,
1115 pub depth: u32,
1116 pub tab_size: u32,
1117 pub settings: IndentGuideSettings,
1118}
1119
1120impl IndentGuide {
1121 pub fn indent_level(&self) -> u32 {
1122 self.depth * self.tab_size
1123 }
1124}
1125
1126impl MultiBuffer {
1127 pub fn new(capability: Capability) -> Self {
1128 Self::new_(
1129 capability,
1130 MultiBufferSnapshot {
1131 show_headers: true,
1132 show_deleted_hunks: true,
1133 ..MultiBufferSnapshot::default()
1134 },
1135 )
1136 }
1137
1138 pub fn without_headers(capability: Capability) -> Self {
1139 Self::new_(
1140 capability,
1141 MultiBufferSnapshot {
1142 show_deleted_hunks: true,
1143 ..MultiBufferSnapshot::default()
1144 },
1145 )
1146 }
1147
1148 pub fn singleton(buffer: Entity<Buffer>, cx: &mut Context<Self>) -> Self {
1149 let mut this = Self::new_(
1150 buffer.read(cx).capability(),
1151 MultiBufferSnapshot {
1152 singleton: true,
1153 show_deleted_hunks: true,
1154 ..MultiBufferSnapshot::default()
1155 },
1156 );
1157 this.singleton = true;
1158 this.set_excerpts_for_path(
1159 PathKey::sorted(0),
1160 buffer.clone(),
1161 [Point::zero()..buffer.read(cx).max_point()],
1162 0,
1163 cx,
1164 );
1165 this
1166 }
1167
1168 #[inline]
1169 pub fn new_(capability: Capability, snapshot: MultiBufferSnapshot) -> Self {
1170 Self {
1171 snapshot: RefCell::new(snapshot),
1172 buffers: Default::default(),
1173 diffs: HashMap::default(),
1174 subscriptions: Topic::default(),
1175 singleton: false,
1176 capability,
1177 title: None,
1178 buffer_changed_since_sync: Default::default(),
1179 history: History::default(),
1180 }
1181 }
1182
1183 pub fn clone(&self, new_cx: &mut Context<Self>) -> Self {
1184 let mut buffers = BTreeMap::default();
1185 let buffer_changed_since_sync = Rc::new(Cell::new(false));
1186 for (buffer_id, buffer_state) in self.buffers.iter() {
1187 buffer_state.buffer.update(new_cx, |buffer, _| {
1188 buffer.record_changes(Rc::downgrade(&buffer_changed_since_sync));
1189 });
1190 buffers.insert(
1191 *buffer_id,
1192 BufferState {
1193 buffer: buffer_state.buffer.clone(),
1194 last_version: buffer_state.last_version.clone(),
1195 last_non_text_state_update_count: buffer_state
1196 .last_non_text_state_update_count
1197 .clone(),
1198 _subscriptions: [
1199 new_cx.observe(&buffer_state.buffer, |_, _, cx| cx.notify()),
1200 new_cx.subscribe(&buffer_state.buffer, Self::on_buffer_event),
1201 ],
1202 },
1203 );
1204 }
1205 let mut diff_bases = HashMap::default();
1206 for (buffer_id, diff) in self.diffs.iter() {
1207 diff_bases.insert(*buffer_id, DiffState::new(diff.diff.clone(), new_cx));
1208 }
1209 Self {
1210 snapshot: RefCell::new(self.snapshot.borrow().clone()),
1211 buffers,
1212 diffs: diff_bases,
1213 subscriptions: Default::default(),
1214 singleton: self.singleton,
1215 capability: self.capability,
1216 history: self.history.clone(),
1217 title: self.title.clone(),
1218 buffer_changed_since_sync,
1219 }
1220 }
1221
1222 pub fn set_group_interval(&mut self, group_interval: Duration) {
1223 self.history.set_group_interval(group_interval);
1224 }
1225
1226 pub fn with_title(mut self, title: String) -> Self {
1227 self.title = Some(title);
1228 self
1229 }
1230
1231 pub fn read_only(&self) -> bool {
1232 !self.capability.editable()
1233 }
1234
1235 pub fn capability(&self) -> Capability {
1236 self.capability
1237 }
1238
1239 /// Returns an up-to-date snapshot of the MultiBuffer.
1240 #[ztracing::instrument(skip_all)]
1241 pub fn snapshot(&self, cx: &App) -> MultiBufferSnapshot {
1242 self.sync(cx);
1243 self.snapshot.borrow().clone()
1244 }
1245
1246 pub fn read(&self, cx: &App) -> Ref<'_, MultiBufferSnapshot> {
1247 self.sync(cx);
1248 self.snapshot.borrow()
1249 }
1250
1251 pub fn as_singleton(&self) -> Option<Entity<Buffer>> {
1252 if self.singleton {
1253 Some(self.buffers.values().next().unwrap().buffer.clone())
1254 } else {
1255 None
1256 }
1257 }
1258
1259 pub fn is_singleton(&self) -> bool {
1260 self.singleton
1261 }
1262
1263 pub fn subscribe(&mut self) -> Subscription<MultiBufferOffset> {
1264 self.subscriptions.subscribe()
1265 }
1266
1267 pub fn is_dirty(&self, cx: &App) -> bool {
1268 self.read(cx).is_dirty()
1269 }
1270
1271 pub fn has_deleted_file(&self, cx: &App) -> bool {
1272 self.read(cx).has_deleted_file()
1273 }
1274
1275 pub fn has_conflict(&self, cx: &App) -> bool {
1276 self.read(cx).has_conflict()
1277 }
1278
1279 // The `is_empty` signature doesn't match what clippy expects
1280 #[allow(clippy::len_without_is_empty)]
1281 pub fn len(&self, cx: &App) -> MultiBufferOffset {
1282 self.read(cx).len()
1283 }
1284
1285 pub fn is_empty(&self) -> bool {
1286 self.buffers.is_empty()
1287 }
1288
1289 pub fn edit<I, S, T>(
1290 &mut self,
1291 edits: I,
1292 autoindent_mode: Option<AutoindentMode>,
1293 cx: &mut Context<Self>,
1294 ) where
1295 I: IntoIterator<Item = (Range<S>, T)>,
1296 S: ToOffset,
1297 T: Into<Arc<str>>,
1298 {
1299 self.edit_internal(edits, autoindent_mode, true, cx);
1300 }
1301
1302 pub fn edit_non_coalesce<I, S, T>(
1303 &mut self,
1304 edits: I,
1305 autoindent_mode: Option<AutoindentMode>,
1306 cx: &mut Context<Self>,
1307 ) where
1308 I: IntoIterator<Item = (Range<S>, T)>,
1309 S: ToOffset,
1310 T: Into<Arc<str>>,
1311 {
1312 self.edit_internal(edits, autoindent_mode, false, cx);
1313 }
1314
1315 fn edit_internal<I, S, T>(
1316 &mut self,
1317 edits: I,
1318 autoindent_mode: Option<AutoindentMode>,
1319 coalesce_adjacent: bool,
1320 cx: &mut Context<Self>,
1321 ) where
1322 I: IntoIterator<Item = (Range<S>, T)>,
1323 S: ToOffset,
1324 T: Into<Arc<str>>,
1325 {
1326 if self.read_only() || self.buffers.is_empty() {
1327 return;
1328 }
1329 self.sync_mut(cx);
1330 let edits = edits
1331 .into_iter()
1332 .map(|(range, new_text)| {
1333 let mut range = range.start.to_offset(self.snapshot.get_mut())
1334 ..range.end.to_offset(self.snapshot.get_mut());
1335 if range.start > range.end {
1336 mem::swap(&mut range.start, &mut range.end);
1337 }
1338 (range, new_text.into())
1339 })
1340 .collect::<Vec<_>>();
1341
1342 return edit_internal(self, edits, autoindent_mode, coalesce_adjacent, cx);
1343
1344 // Non-generic part of edit, hoisted out to avoid blowing up LLVM IR.
1345 fn edit_internal(
1346 this: &mut MultiBuffer,
1347 edits: Vec<(Range<MultiBufferOffset>, Arc<str>)>,
1348 mut autoindent_mode: Option<AutoindentMode>,
1349 coalesce_adjacent: bool,
1350 cx: &mut Context<MultiBuffer>,
1351 ) {
1352 let original_indent_columns = match &mut autoindent_mode {
1353 Some(AutoindentMode::Block {
1354 original_indent_columns,
1355 }) => mem::take(original_indent_columns),
1356 _ => Default::default(),
1357 };
1358
1359 let buffer_edits = MultiBuffer::convert_edits_to_buffer_edits(
1360 edits,
1361 this.snapshot.get_mut(),
1362 &original_indent_columns,
1363 );
1364
1365 let mut buffer_ids = Vec::with_capacity(buffer_edits.len());
1366 for (buffer_id, mut edits) in buffer_edits {
1367 buffer_ids.push(buffer_id);
1368 edits.sort_by_key(|edit| edit.range.start);
1369 this.buffers[&buffer_id].buffer.update(cx, |buffer, cx| {
1370 let mut edits = edits.into_iter().peekable();
1371 let mut insertions = Vec::new();
1372 let mut original_indent_columns = Vec::new();
1373 let mut deletions = Vec::new();
1374 let empty_str: Arc<str> = Arc::default();
1375 while let Some(BufferEdit {
1376 mut range,
1377 mut new_text,
1378 mut is_insertion,
1379 original_indent_column,
1380 }) = edits.next()
1381 {
1382 while let Some(BufferEdit {
1383 range: next_range,
1384 is_insertion: next_is_insertion,
1385 new_text: next_new_text,
1386 ..
1387 }) = edits.peek()
1388 {
1389 let should_coalesce = if coalesce_adjacent {
1390 range.end >= next_range.start
1391 } else {
1392 range.end > next_range.start
1393 };
1394
1395 if should_coalesce {
1396 range.end = cmp::max(next_range.end, range.end);
1397 is_insertion |= *next_is_insertion;
1398 new_text = format!("{new_text}{next_new_text}").into();
1399 edits.next();
1400 } else {
1401 break;
1402 }
1403 }
1404
1405 if is_insertion {
1406 original_indent_columns.push(original_indent_column);
1407 insertions.push((
1408 buffer.anchor_before(range.start)..buffer.anchor_before(range.end),
1409 new_text.clone(),
1410 ));
1411 } else if !range.is_empty() {
1412 deletions.push((
1413 buffer.anchor_before(range.start)..buffer.anchor_before(range.end),
1414 empty_str.clone(),
1415 ));
1416 }
1417 }
1418
1419 let deletion_autoindent_mode =
1420 if let Some(AutoindentMode::Block { .. }) = autoindent_mode {
1421 Some(AutoindentMode::Block {
1422 original_indent_columns: Default::default(),
1423 })
1424 } else {
1425 autoindent_mode.clone()
1426 };
1427 let insertion_autoindent_mode =
1428 if let Some(AutoindentMode::Block { .. }) = autoindent_mode {
1429 Some(AutoindentMode::Block {
1430 original_indent_columns,
1431 })
1432 } else {
1433 autoindent_mode.clone()
1434 };
1435
1436 if coalesce_adjacent {
1437 buffer.edit(deletions, deletion_autoindent_mode, cx);
1438 buffer.edit(insertions, insertion_autoindent_mode, cx);
1439 } else {
1440 buffer.edit_non_coalesce(deletions, deletion_autoindent_mode, cx);
1441 buffer.edit_non_coalesce(insertions, insertion_autoindent_mode, cx);
1442 }
1443 })
1444 }
1445
1446 cx.emit(Event::BuffersEdited { buffer_ids });
1447 }
1448 }
1449
1450 fn convert_edits_to_buffer_edits(
1451 edits: Vec<(Range<MultiBufferOffset>, Arc<str>)>,
1452 snapshot: &MultiBufferSnapshot,
1453 original_indent_columns: &[Option<u32>],
1454 ) -> HashMap<BufferId, Vec<BufferEdit>> {
1455 let mut buffer_edits: HashMap<BufferId, Vec<BufferEdit>> = Default::default();
1456 let mut cursor = snapshot.cursor::<MultiBufferOffset, BufferOffset>();
1457 for (ix, (range, new_text)) in edits.into_iter().enumerate() {
1458 let original_indent_column = original_indent_columns.get(ix).copied().flatten();
1459
1460 cursor.seek(&range.start);
1461 let mut start_region = cursor.region().expect("start offset out of bounds");
1462 if !start_region.is_main_buffer {
1463 cursor.next();
1464 if let Some(region) = cursor.region() {
1465 start_region = region;
1466 } else {
1467 continue;
1468 }
1469 }
1470
1471 if range.end < start_region.range.start {
1472 continue;
1473 }
1474
1475 let start_region = start_region.clone();
1476 if range.end > start_region.range.end {
1477 cursor.seek_forward(&range.end);
1478 }
1479 let mut end_region = cursor.region().expect("end offset out of bounds");
1480 if !end_region.is_main_buffer {
1481 cursor.prev();
1482 if let Some(region) = cursor.region() {
1483 end_region = region;
1484 } else {
1485 continue;
1486 }
1487 }
1488
1489 if range.start > end_region.range.end {
1490 continue;
1491 }
1492
1493 let start_overshoot = range.start.saturating_sub(start_region.range.start);
1494 let end_overshoot = range.end.saturating_sub(end_region.range.start);
1495 let buffer_start = (start_region.buffer_range.start + start_overshoot)
1496 .min(start_region.buffer_range.end);
1497 let buffer_end =
1498 (end_region.buffer_range.start + end_overshoot).min(end_region.buffer_range.end);
1499
1500 if start_region.excerpt == end_region.excerpt {
1501 if start_region.buffer.capability == Capability::ReadWrite
1502 && start_region.is_main_buffer
1503 {
1504 buffer_edits
1505 .entry(start_region.buffer.remote_id())
1506 .or_default()
1507 .push(BufferEdit {
1508 range: buffer_start..buffer_end,
1509 new_text,
1510 is_insertion: true,
1511 original_indent_column,
1512 });
1513 }
1514 } else {
1515 let start_excerpt_range = buffer_start..start_region.buffer_range.end;
1516 let end_excerpt_range = end_region.buffer_range.start..buffer_end;
1517 if start_region.buffer.capability == Capability::ReadWrite
1518 && start_region.is_main_buffer
1519 {
1520 buffer_edits
1521 .entry(start_region.buffer.remote_id())
1522 .or_default()
1523 .push(BufferEdit {
1524 range: start_excerpt_range,
1525 new_text: new_text.clone(),
1526 is_insertion: true,
1527 original_indent_column,
1528 });
1529 }
1530 if end_region.buffer.capability == Capability::ReadWrite
1531 && end_region.is_main_buffer
1532 {
1533 buffer_edits
1534 .entry(end_region.buffer.remote_id())
1535 .or_default()
1536 .push(BufferEdit {
1537 range: end_excerpt_range,
1538 new_text: new_text.clone(),
1539 is_insertion: false,
1540 original_indent_column,
1541 });
1542 }
1543 let end_region_excerpt = end_region.excerpt.clone();
1544
1545 cursor.seek(&range.start);
1546 cursor.next_excerpt();
1547 while let Some(region) = cursor.region() {
1548 if region.excerpt == &end_region_excerpt {
1549 break;
1550 }
1551 if region.buffer.capability == Capability::ReadWrite && region.is_main_buffer {
1552 buffer_edits
1553 .entry(region.buffer.remote_id())
1554 .or_default()
1555 .push(BufferEdit {
1556 range: region.buffer_range.clone(),
1557 new_text: new_text.clone(),
1558 is_insertion: false,
1559 original_indent_column,
1560 });
1561 }
1562 cursor.next_excerpt();
1563 }
1564 }
1565 }
1566 buffer_edits
1567 }
1568
1569 pub fn autoindent_ranges<I, S>(&mut self, ranges: I, cx: &mut Context<Self>)
1570 where
1571 I: IntoIterator<Item = Range<S>>,
1572 S: ToOffset,
1573 {
1574 if self.read_only() || self.buffers.is_empty() {
1575 return;
1576 }
1577 self.sync_mut(cx);
1578 let empty = Arc::<str>::from("");
1579 let edits = ranges
1580 .into_iter()
1581 .map(|range| {
1582 let mut range = range.start.to_offset(self.snapshot.get_mut())
1583 ..range.end.to_offset(&self.snapshot.get_mut());
1584 if range.start > range.end {
1585 mem::swap(&mut range.start, &mut range.end);
1586 }
1587 (range, empty.clone())
1588 })
1589 .collect::<Vec<_>>();
1590
1591 return autoindent_ranges_internal(self, edits, cx);
1592
1593 fn autoindent_ranges_internal(
1594 this: &mut MultiBuffer,
1595 edits: Vec<(Range<MultiBufferOffset>, Arc<str>)>,
1596 cx: &mut Context<MultiBuffer>,
1597 ) {
1598 let buffer_edits =
1599 MultiBuffer::convert_edits_to_buffer_edits(edits, this.snapshot.get_mut(), &[]);
1600
1601 let mut buffer_ids = Vec::new();
1602 for (buffer_id, mut edits) in buffer_edits {
1603 buffer_ids.push(buffer_id);
1604 edits.sort_unstable_by_key(|edit| edit.range.start);
1605
1606 let mut ranges: Vec<Range<BufferOffset>> = Vec::new();
1607 for edit in edits {
1608 if let Some(last_range) = ranges.last_mut()
1609 && edit.range.start <= last_range.end
1610 {
1611 last_range.end = last_range.end.max(edit.range.end);
1612 continue;
1613 }
1614 ranges.push(edit.range);
1615 }
1616
1617 this.buffers[&buffer_id].buffer.update(cx, |buffer, cx| {
1618 buffer.autoindent_ranges(ranges, cx);
1619 })
1620 }
1621
1622 cx.emit(Event::BuffersEdited { buffer_ids });
1623 }
1624 }
1625
1626 pub fn set_active_selections(
1627 &self,
1628 selections: &[Selection<Anchor>],
1629 line_mode: bool,
1630 cursor_shape: CursorShape,
1631 cx: &mut Context<Self>,
1632 ) {
1633 let mut selections_by_buffer: HashMap<BufferId, Vec<Selection<text::Anchor>>> =
1634 Default::default();
1635 let snapshot = self.read(cx);
1636 let mut cursor = snapshot.excerpts.cursor::<ExcerptSummary>(());
1637 for selection in selections {
1638 let start = selection.start.seek_target(&snapshot);
1639
1640 cursor.seek(&start, Bias::Left);
1641 while let Some(excerpt) = cursor.item() {
1642 // todo!() method?
1643 let excerpt_start =
1644 Anchor::in_buffer(excerpt.path_key_index, excerpt.range.context.end);
1645 if excerpt_start.cmp(&selection.end, &snapshot).is_gt() {
1646 break;
1647 }
1648 let start = text::Anchor::max(
1649 &excerpt.range.context.start,
1650 &selection.start.text_anchor().unwrap_or(text::Anchor::MIN),
1651 &excerpt.buffer,
1652 )
1653 .clone();
1654 let end = text::Anchor::min(
1655 &excerpt.range.context.end,
1656 &selection.end.text_anchor().unwrap_or(text::Anchor::MAX),
1657 &excerpt.buffer,
1658 )
1659 .clone();
1660 selections_by_buffer
1661 .entry(excerpt.buffer.remote_id())
1662 .or_default()
1663 .push(Selection {
1664 id: selection.id,
1665 start,
1666 end,
1667 reversed: selection.reversed,
1668 goal: selection.goal,
1669 });
1670
1671 cursor.next();
1672 }
1673 }
1674
1675 for (buffer_id, buffer_state) in self.buffers.iter() {
1676 if !selections_by_buffer.contains_key(buffer_id) {
1677 buffer_state
1678 .buffer
1679 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
1680 }
1681 }
1682
1683 for (buffer_id, selections) in selections_by_buffer {
1684 self.buffers[&buffer_id].buffer.update(cx, |buffer, cx| {
1685 buffer.set_active_selections(selections.into(), line_mode, cursor_shape, cx);
1686 });
1687 }
1688 }
1689
1690 pub fn remove_active_selections(&self, cx: &mut Context<Self>) {
1691 for buffer in self.buffers.values() {
1692 buffer
1693 .buffer
1694 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
1695 }
1696 }
1697
1698 #[instrument(skip_all)]
1699 fn merge_excerpt_ranges<'a>(
1700 expanded_ranges: impl IntoIterator<Item = &'a ExcerptRange<Point>> + 'a,
1701 ) -> (Vec<ExcerptRange<Point>>, Vec<usize>) {
1702 let mut merged_ranges: Vec<ExcerptRange<Point>> = Vec::new();
1703 let mut counts: Vec<usize> = Vec::new();
1704 for range in expanded_ranges {
1705 if let Some(last_range) = merged_ranges.last_mut() {
1706 assert!(
1707 last_range.context.start <= range.context.start,
1708 "ranges must be sorted: {last_range:?} <= {range:?}"
1709 );
1710 if last_range.context.end >= range.context.start
1711 || last_range.context.end.row + 1 == range.context.start.row
1712 {
1713 last_range.context.end = range.context.end.max(last_range.context.end);
1714 *counts.last_mut().unwrap() += 1;
1715 continue;
1716 }
1717 }
1718 merged_ranges.push(range.clone());
1719 counts.push(1);
1720 }
1721 (merged_ranges, counts)
1722 }
1723
1724 pub fn clear(&mut self, cx: &mut Context<Self>) {
1725 self.sync_mut(cx);
1726 let removed_buffer_ids = std::mem::take(&mut self.buffers).into_keys().collect();
1727 let MultiBufferSnapshot {
1728 excerpts,
1729 diffs: _,
1730 diff_transforms: _,
1731 non_text_state_update_count: _,
1732 edit_count: _,
1733 is_dirty,
1734 has_deleted_file,
1735 has_conflict,
1736 has_inverted_diff,
1737 singleton: _,
1738 trailing_excerpt_update_count,
1739 all_diff_hunks_expanded: _,
1740 show_deleted_hunks: _,
1741 use_extended_diff_range: _,
1742 show_headers: _,
1743 path_keys_by_buffer: _,
1744 path_keys_by_index: _,
1745 } = self.snapshot.get_mut();
1746 let start = ExcerptDimension(MultiBufferOffset::ZERO);
1747 let prev_len = ExcerptDimension(excerpts.summary().text.len);
1748 *excerpts = Default::default();
1749 *trailing_excerpt_update_count += 1;
1750 *is_dirty = false;
1751 *has_deleted_file = false;
1752 *has_conflict = false;
1753 *has_inverted_diff = false;
1754
1755 let edits = Self::sync_diff_transforms(
1756 self.snapshot.get_mut(),
1757 vec![Edit {
1758 old: start..prev_len,
1759 new: start..start,
1760 }],
1761 DiffChangeKind::BufferEdited,
1762 );
1763 if !edits.is_empty() {
1764 self.subscriptions.publish(edits);
1765 }
1766 cx.emit(Event::Edited {
1767 edited_buffer: None,
1768 });
1769 cx.emit(Event::BuffersRemoved { removed_buffer_ids });
1770 cx.notify();
1771 }
1772
1773 #[ztracing::instrument(skip_all)]
1774 pub fn excerpts_for_buffer(
1775 &self,
1776 buffer_id: BufferId,
1777 cx: &App,
1778 ) -> Vec<ExcerptRange<text::Anchor>> {
1779 let mut excerpts = Vec::new();
1780 let snapshot = self.read(cx);
1781 let Some(path_key) = snapshot.path_keys_by_buffer.get(&buffer_id).cloned() else {
1782 return excerpts;
1783 };
1784
1785 let mut cursor = snapshot.excerpts.cursor::<PathKey>(());
1786 cursor.seek_forward(&path_key, Bias::Left);
1787
1788 while let Some(item) = cursor.item()
1789 && item.path_key == path_key
1790 {
1791 excerpts.push(item.range.clone());
1792 cursor.next()
1793 }
1794 excerpts
1795 }
1796
1797 pub fn excerpt_ranges_for_buffer(&self, buffer_id: BufferId, cx: &App) -> Vec<Range<Point>> {
1798 let snapshot = self.read(cx);
1799 let mut excerpts = snapshot
1800 .excerpts
1801 .cursor::<Dimensions<PathKey, ExcerptPoint>>(());
1802 let mut diff_transforms = snapshot
1803 .diff_transforms
1804 .cursor::<Dimensions<ExcerptPoint, OutputDimension<Point>>>(());
1805 diff_transforms.next();
1806 let mut result = Vec::new();
1807 let Some(path_key) = snapshot.path_keys_by_buffer.get(&buffer_id) else {
1808 return result;
1809 };
1810
1811 while let Some(excerpt) = excerpts.item()
1812 && &excerpt.path_key == path_key
1813 {
1814 let excerpt_start = excerpts.start().1;
1815 let excerpt_end = excerpt_start + excerpt.text_summary.lines;
1816
1817 diff_transforms.seek_forward(&excerpt_start, Bias::Left);
1818 let overshoot = excerpt_start - diff_transforms.start().0;
1819 let start = diff_transforms.start().1 + overshoot;
1820
1821 diff_transforms.seek_forward(&excerpt_end, Bias::Right);
1822 let overshoot = excerpt_end - diff_transforms.start().0;
1823 let end = diff_transforms.start().1 + overshoot;
1824
1825 result.push(start.0..end.0);
1826 excerpts.next()
1827 }
1828 result
1829 }
1830
1831 // todo!() this seems bogus
1832 pub fn excerpt_buffer_ids(&self) -> Vec<BufferId> {
1833 self.snapshot
1834 .borrow()
1835 .excerpts
1836 .iter()
1837 .map(|entry| entry.buffer.remote_id())
1838 .collect()
1839 }
1840
1841 pub fn excerpt_containing(&self, position: impl ToOffset, cx: &App) -> Option<ExcerptInfo> {
1842 let snapshot = self.read(cx);
1843 let offset = position.to_offset(&snapshot);
1844
1845 let mut cursor = snapshot.cursor::<MultiBufferOffset, BufferOffset>();
1846 cursor.seek(&offset);
1847 let excerpt = cursor.excerpt().or(snapshot.excerpts.last())?;
1848 Some(ExcerptInfo {
1849 path_key: excerpt.path_key.clone(),
1850 buffer_id: excerpt.buffer.remote_id(),
1851 range: excerpt.range.clone(),
1852 })
1853 }
1854
1855 pub fn buffer_for_anchor(&self, anchor: Anchor, cx: &App) -> Option<Entity<Buffer>> {
1856 let buffer_id = match anchor {
1857 Anchor::Min => self.snapshot(cx).excerpts.first()?.buffer.remote_id(),
1858 Anchor::Max => self.snapshot(cx).excerpts.last()?.buffer.remote_id(),
1859 Anchor::Text { buffer_id, .. } => buffer_id,
1860 };
1861 self.buffer(buffer_id)
1862 }
1863
1864 // If point is at the end of the buffer, the last excerpt is returned
1865 pub fn point_to_buffer_offset<T: ToOffset>(
1866 &self,
1867 point: T,
1868 cx: &App,
1869 ) -> Option<(Entity<Buffer>, BufferOffset)> {
1870 let snapshot = self.read(cx);
1871 let (buffer, offset) = snapshot.point_to_buffer_offset(point)?;
1872 Some((
1873 self.buffers.get(&buffer.remote_id())?.buffer.clone(),
1874 offset,
1875 ))
1876 }
1877
1878 // If point is at the end of the buffer, the last excerpt is returned
1879 pub fn point_to_buffer_point<T: ToPoint>(
1880 &self,
1881 point: T,
1882 cx: &App,
1883 ) -> Option<(Entity<Buffer>, Point)> {
1884 let snapshot = self.read(cx);
1885 let (buffer, point) = snapshot.point_to_buffer_point(point.to_point(&snapshot))?;
1886 Some((self.buffers.get(&buffer.remote_id())?.buffer.clone(), point))
1887 }
1888
1889 pub fn buffer_point_to_anchor(
1890 &self,
1891 // todo(lw): We shouldn't need this?
1892 buffer: &Entity<Buffer>,
1893 point: Point,
1894 cx: &App,
1895 ) -> Option<Anchor> {
1896 let mut found = None;
1897 let snapshot = buffer.read(cx).snapshot();
1898 for range in self.excerpts_for_buffer(snapshot.remote_id(), cx) {
1899 let start = range.context.start.to_point(&snapshot);
1900 let end = range.context.end.to_point(&snapshot);
1901 if start <= point && point < end {
1902 found = Some(snapshot.clip_point(point, Bias::Left));
1903 break;
1904 }
1905 if point < start {
1906 found = Some(start);
1907 }
1908 if point > end {
1909 found = Some(end);
1910 }
1911 }
1912
1913 Some(Anchor::text(snapshot.anchor_after(found?)))
1914 }
1915
1916 pub fn buffer_anchor_to_anchor(
1917 &self,
1918 // todo(lw): We shouldn't need this?
1919 buffer: &Entity<Buffer>,
1920 anchor: text::Anchor,
1921 cx: &App,
1922 ) -> Option<Anchor> {
1923 let snapshot = buffer.read(cx).snapshot();
1924 for range in self.excerpts_for_buffer(snapshot.remote_id(), cx) {
1925 if range.contains(&anchor, &snapshot) {
1926 return Some(Anchor::text(anchor));
1927 }
1928 }
1929
1930 None
1931 }
1932
1933 pub fn wait_for_anchors<'a, Anchors: 'a + Iterator<Item = Anchor>>(
1934 &self,
1935 anchors: Anchors,
1936 cx: &mut Context<Self>,
1937 ) -> impl 'static + Future<Output = Result<()>> + use<Anchors> {
1938 let mut error = None;
1939 let mut futures = Vec::new();
1940 for anchor in anchors {
1941 if let Some(excerpt_anchor) = anchor.excerpt_anchor() {
1942 if let Some(buffer) = self.buffers.get(&excerpt_anchor.buffer_id) {
1943 buffer.buffer.update(cx, |buffer, _| {
1944 futures.push(buffer.wait_for_anchors([excerpt_anchor.text_anchor()]))
1945 });
1946 } else {
1947 error = Some(anyhow!(
1948 "buffer {:?} is not part of this multi-buffer",
1949 excerpt_anchor.buffer_id
1950 ));
1951 break;
1952 }
1953 }
1954 }
1955 async move {
1956 if let Some(error) = error {
1957 Err(error)?;
1958 }
1959 for future in futures {
1960 future.await?;
1961 }
1962 Ok(())
1963 }
1964 }
1965
1966 pub fn text_anchor_for_position<T: ToOffset>(
1967 &self,
1968 position: T,
1969 cx: &App,
1970 ) -> Option<(Entity<Buffer>, text::Anchor)> {
1971 let snapshot = self.read(cx);
1972 let anchor = snapshot.anchor_before(position).excerpt_anchor()?;
1973 let buffer = self.buffers.get(&anchor.buffer_id)?.buffer.clone();
1974 Some((buffer, anchor.text_anchor()))
1975 }
1976
1977 fn on_buffer_event(
1978 &mut self,
1979 buffer: Entity<Buffer>,
1980 event: &language::BufferEvent,
1981 cx: &mut Context<Self>,
1982 ) {
1983 use language::BufferEvent;
1984 let buffer_id = buffer.read(cx).remote_id();
1985 cx.emit(match event {
1986 BufferEvent::Edited => Event::Edited {
1987 edited_buffer: Some(buffer),
1988 },
1989 BufferEvent::DirtyChanged => Event::DirtyChanged,
1990 BufferEvent::Saved => Event::Saved,
1991 BufferEvent::FileHandleChanged => Event::FileHandleChanged,
1992 BufferEvent::Reloaded => Event::Reloaded,
1993 BufferEvent::LanguageChanged(has_language) => {
1994 Event::LanguageChanged(buffer_id, *has_language)
1995 }
1996 BufferEvent::Reparsed => Event::Reparsed(buffer_id),
1997 BufferEvent::DiagnosticsUpdated => Event::DiagnosticsUpdated,
1998 BufferEvent::CapabilityChanged => {
1999 self.capability = buffer.read(cx).capability();
2000 return;
2001 }
2002 BufferEvent::Operation { .. } | BufferEvent::ReloadNeeded => return,
2003 });
2004 }
2005
2006 fn buffer_diff_language_changed(&mut self, diff: Entity<BufferDiff>, cx: &mut Context<Self>) {
2007 let diff = diff.read(cx);
2008 let buffer_id = diff.buffer_id;
2009 let diff = DiffStateSnapshot {
2010 diff: diff.snapshot(cx),
2011 main_buffer: None,
2012 };
2013 self.snapshot.get_mut().diffs.insert(buffer_id, diff);
2014 }
2015
2016 fn inverted_buffer_diff_language_changed(
2017 &mut self,
2018 diff: Entity<BufferDiff>,
2019 main_buffer: Entity<language::Buffer>,
2020 cx: &mut Context<Self>,
2021 ) {
2022 let base_text_buffer_id = diff.read(cx).base_text_buffer().read(cx).remote_id();
2023 let main_buffer_snapshot = main_buffer.read(cx).snapshot();
2024 let diff = diff.read(cx);
2025 let diff = DiffStateSnapshot {
2026 diff: diff.snapshot(cx),
2027 main_buffer: Some(main_buffer_snapshot),
2028 };
2029 self.snapshot
2030 .get_mut()
2031 .diffs
2032 .insert(base_text_buffer_id, diff);
2033 }
2034
2035 fn buffer_diff_changed(
2036 &mut self,
2037 diff: Entity<BufferDiff>,
2038 range: Range<text::Anchor>,
2039 cx: &mut Context<Self>,
2040 ) {
2041 let Some(buffer) = self.buffer(diff.read(cx).buffer_id) else {
2042 return;
2043 };
2044 let snapshot = self.sync_mut(cx);
2045
2046 let diff = diff.read(cx);
2047 let buffer_id = diff.buffer_id;
2048
2049 let Some(path) = snapshot.path_for_buffer(buffer_id).cloned() else {
2050 return;
2051 };
2052 let new_diff = DiffStateSnapshot {
2053 diff: diff.snapshot(cx),
2054 main_buffer: None,
2055 };
2056 let base_text_changed = snapshot
2057 .diffs
2058 .get(&buffer_id)
2059 .is_none_or(|old_diff| !new_diff.base_texts_definitely_eq(old_diff));
2060 snapshot.diffs.insert_or_replace(buffer_id, new_diff);
2061
2062 let buffer = buffer.read(cx);
2063 let diff_change_range = range.to_offset(buffer);
2064
2065 let excerpt_edits = snapshot.excerpt_edits_for_diff_change(&path, diff_change_range);
2066 let edits = Self::sync_diff_transforms(
2067 snapshot,
2068 excerpt_edits,
2069 DiffChangeKind::DiffUpdated {
2070 base_changed: base_text_changed,
2071 },
2072 );
2073 if !edits.is_empty() {
2074 self.subscriptions.publish(edits);
2075 }
2076 cx.emit(Event::Edited {
2077 edited_buffer: None,
2078 });
2079 }
2080
2081 fn inverted_buffer_diff_changed(
2082 &mut self,
2083 diff: Entity<BufferDiff>,
2084 main_buffer: Entity<language::Buffer>,
2085 diff_change_range: Option<Range<usize>>,
2086 cx: &mut Context<Self>,
2087 ) {
2088 let snapshot = self.sync_mut(cx);
2089
2090 let base_text_buffer_id = diff.read(cx).base_text_buffer().read(cx).remote_id();
2091 let Some(path) = snapshot.path_for_buffer(base_text_buffer_id).cloned() else {
2092 return;
2093 };
2094
2095 let main_buffer_snapshot = main_buffer.read(cx).snapshot();
2096 let diff = diff.read(cx);
2097 let new_diff = DiffStateSnapshot {
2098 diff: diff.snapshot(cx),
2099 main_buffer: Some(main_buffer_snapshot),
2100 };
2101 snapshot
2102 .diffs
2103 .insert_or_replace(base_text_buffer_id, new_diff);
2104
2105 let Some(diff_change_range) = diff_change_range else {
2106 return;
2107 };
2108
2109 let excerpt_edits = snapshot.excerpt_edits_for_diff_change(&path, diff_change_range);
2110 let edits = Self::sync_diff_transforms(
2111 snapshot,
2112 excerpt_edits,
2113 DiffChangeKind::DiffUpdated {
2114 // We don't read this field for inverted diffs.
2115 base_changed: false,
2116 },
2117 );
2118 if !edits.is_empty() {
2119 self.subscriptions.publish(edits);
2120 }
2121 cx.emit(Event::Edited {
2122 edited_buffer: None,
2123 });
2124 }
2125
2126 pub fn all_buffers_iter(&self) -> impl Iterator<Item = Entity<Buffer>> {
2127 self.buffers.values().map(|state| state.buffer.clone())
2128 }
2129
2130 pub fn all_buffers(&self) -> HashSet<Entity<Buffer>> {
2131 self.all_buffers_iter().collect()
2132 }
2133
2134 pub fn all_buffer_ids_iter(&self) -> impl Iterator<Item = BufferId> {
2135 self.buffers.keys().copied()
2136 }
2137
2138 pub fn all_buffer_ids(&self) -> Vec<BufferId> {
2139 self.all_buffer_ids_iter().collect()
2140 }
2141
2142 pub fn buffer(&self, buffer_id: BufferId) -> Option<Entity<Buffer>> {
2143 self.buffers
2144 .get(&buffer_id)
2145 .map(|state| state.buffer.clone())
2146 }
2147
2148 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2149 self.point_to_buffer_offset(point, cx)
2150 .and_then(|(buffer, offset)| buffer.read(cx).language_at(offset))
2151 }
2152
2153 pub fn language_settings<'a>(&'a self, cx: &'a App) -> Cow<'a, LanguageSettings> {
2154 let buffer_id = self
2155 .snapshot
2156 .borrow()
2157 .excerpts
2158 .first()
2159 .map(|excerpt| excerpt.buffer.remote_id());
2160 buffer_id
2161 .and_then(|buffer_id| self.buffer(buffer_id))
2162 .map(|buffer| {
2163 let buffer = buffer.read(cx);
2164 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
2165 })
2166 .unwrap_or_else(move || self.language_settings_at(MultiBufferOffset::default(), cx))
2167 }
2168
2169 pub fn language_settings_at<'a, T: ToOffset>(
2170 &'a self,
2171 point: T,
2172 cx: &'a App,
2173 ) -> Cow<'a, LanguageSettings> {
2174 let mut language = None;
2175 let mut file = None;
2176 if let Some((buffer, offset)) = self.point_to_buffer_offset(point, cx) {
2177 let buffer = buffer.read(cx);
2178 language = buffer.language_at(offset);
2179 file = buffer.file();
2180 }
2181 language_settings(language.map(|l| l.name()), file, cx)
2182 }
2183
2184 pub fn for_each_buffer(&self, f: &mut dyn FnMut(&Entity<Buffer>)) {
2185 self.buffers.values().for_each(|state| f(&state.buffer))
2186 }
2187
2188 pub fn explicit_title(&self) -> Option<&str> {
2189 self.title.as_deref()
2190 }
2191
2192 pub fn title<'a>(&'a self, cx: &'a App) -> Cow<'a, str> {
2193 if let Some(title) = self.title.as_ref() {
2194 return title.into();
2195 }
2196
2197 if let Some(buffer) = self.as_singleton() {
2198 let buffer = buffer.read(cx);
2199
2200 if let Some(file) = buffer.file() {
2201 return file.file_name(cx).into();
2202 }
2203
2204 if let Some(title) = self.buffer_content_title(buffer) {
2205 return title;
2206 }
2207 };
2208
2209 "untitled".into()
2210 }
2211
2212 fn buffer_content_title(&self, buffer: &Buffer) -> Option<Cow<'_, str>> {
2213 let mut is_leading_whitespace = true;
2214 let mut count = 0;
2215 let mut prev_was_space = false;
2216 let mut title = String::new();
2217
2218 for ch in buffer.snapshot().chars() {
2219 if is_leading_whitespace && ch.is_whitespace() {
2220 continue;
2221 }
2222
2223 is_leading_whitespace = false;
2224
2225 if ch == '\n' || count >= 40 {
2226 break;
2227 }
2228
2229 if ch.is_whitespace() {
2230 if !prev_was_space {
2231 title.push(' ');
2232 count += 1;
2233 prev_was_space = true;
2234 }
2235 } else {
2236 title.push(ch);
2237 count += 1;
2238 prev_was_space = false;
2239 }
2240 }
2241
2242 let title = title.trim_end().to_string();
2243
2244 if title.is_empty() {
2245 return None;
2246 }
2247
2248 Some(title.into())
2249 }
2250
2251 pub fn set_title(&mut self, title: String, cx: &mut Context<Self>) {
2252 self.title = Some(title);
2253 cx.notify();
2254 }
2255
2256 /// Preserve preview tabs containing this multibuffer until additional edits occur.
2257 pub fn refresh_preview(&self, cx: &mut Context<Self>) {
2258 for buffer_state in self.buffers.values() {
2259 buffer_state
2260 .buffer
2261 .update(cx, |buffer, _cx| buffer.refresh_preview());
2262 }
2263 }
2264
2265 /// Whether we should preserve the preview status of a tab containing this multi-buffer.
2266 pub fn preserve_preview(&self, cx: &App) -> bool {
2267 self.buffers
2268 .values()
2269 .all(|state| state.buffer.read(cx).preserve_preview())
2270 }
2271
2272 #[cfg(any(test, feature = "test-support"))]
2273 pub fn is_parsing(&self, cx: &App) -> bool {
2274 self.as_singleton().unwrap().read(cx).is_parsing()
2275 }
2276
2277 pub fn add_diff(&mut self, diff: Entity<BufferDiff>, cx: &mut Context<Self>) {
2278 let buffer_id = diff.read(cx).buffer_id;
2279
2280 if let Some(existing_diff) = self.diff_for(buffer_id)
2281 && diff.entity_id() == existing_diff.entity_id()
2282 {
2283 return;
2284 }
2285
2286 self.buffer_diff_changed(
2287 diff.clone(),
2288 text::Anchor::min_max_range_for_buffer(buffer_id),
2289 cx,
2290 );
2291 self.diffs.insert(buffer_id, DiffState::new(diff, cx));
2292 }
2293
2294 pub fn add_inverted_diff(
2295 &mut self,
2296 diff: Entity<BufferDiff>,
2297 main_buffer: Entity<language::Buffer>,
2298 cx: &mut Context<Self>,
2299 ) {
2300 let snapshot = diff.read(cx).base_text(cx);
2301 let base_text_buffer_id = snapshot.remote_id();
2302 let diff_change_range = 0..snapshot.len();
2303 self.snapshot.get_mut().has_inverted_diff = true;
2304 self.inverted_buffer_diff_changed(
2305 diff.clone(),
2306 main_buffer.clone(),
2307 Some(diff_change_range),
2308 cx,
2309 );
2310 self.diffs.insert(
2311 base_text_buffer_id,
2312 DiffState::new_inverted(diff, main_buffer, cx),
2313 );
2314 }
2315
2316 pub fn diff_for(&self, buffer_id: BufferId) -> Option<Entity<BufferDiff>> {
2317 self.diffs.get(&buffer_id).map(|state| state.diff.clone())
2318 }
2319
2320 pub fn expand_diff_hunks(&mut self, ranges: Vec<Range<Anchor>>, cx: &mut Context<Self>) {
2321 self.expand_or_collapse_diff_hunks(ranges, true, cx);
2322 }
2323
2324 pub fn collapse_diff_hunks(&mut self, ranges: Vec<Range<Anchor>>, cx: &mut Context<Self>) {
2325 self.expand_or_collapse_diff_hunks(ranges, false, cx);
2326 }
2327
2328 pub fn set_all_diff_hunks_expanded(&mut self, cx: &mut Context<Self>) {
2329 self.snapshot.get_mut().all_diff_hunks_expanded = true;
2330 self.expand_or_collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], true, cx);
2331 }
2332
2333 pub fn all_diff_hunks_expanded(&self) -> bool {
2334 self.snapshot.borrow().all_diff_hunks_expanded
2335 }
2336
2337 pub fn set_all_diff_hunks_collapsed(&mut self, cx: &mut Context<Self>) {
2338 self.snapshot.get_mut().all_diff_hunks_expanded = false;
2339 self.expand_or_collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], false, cx);
2340 }
2341
2342 pub fn set_show_deleted_hunks(&mut self, show: bool, cx: &mut Context<Self>) {
2343 self.snapshot.get_mut().show_deleted_hunks = show;
2344
2345 self.sync_mut(cx);
2346
2347 let old_len = self.snapshot.borrow().len();
2348
2349 let ranges = std::iter::once((Point::zero()..Point::MAX, Anchor::Max));
2350 let _ = self.expand_or_collapse_diff_hunks_inner(ranges, true, cx);
2351
2352 let new_len = self.snapshot.borrow().len();
2353
2354 self.subscriptions.publish(vec![Edit {
2355 old: MultiBufferOffset(0)..old_len,
2356 new: MultiBufferOffset(0)..new_len,
2357 }]);
2358
2359 cx.emit(Event::DiffHunksToggled);
2360 cx.emit(Event::Edited {
2361 edited_buffer: None,
2362 });
2363 }
2364
2365 pub fn set_use_extended_diff_range(&mut self, use_extended: bool, _cx: &mut Context<Self>) {
2366 self.snapshot.get_mut().use_extended_diff_range = use_extended;
2367 }
2368
2369 pub fn has_multiple_hunks(&self, cx: &App) -> bool {
2370 self.read(cx)
2371 .diff_hunks_in_range(Anchor::min()..Anchor::max())
2372 .nth(1)
2373 .is_some()
2374 }
2375
2376 pub fn single_hunk_is_expanded(&self, range: Range<Anchor>, cx: &App) -> bool {
2377 let snapshot = self.read(cx);
2378 let mut cursor = snapshot.diff_transforms.cursor::<MultiBufferOffset>(());
2379 let offset_range = range.to_offset(&snapshot);
2380 cursor.seek(&offset_range.start, Bias::Left);
2381 while let Some(item) = cursor.item() {
2382 if *cursor.start() >= offset_range.end && *cursor.start() > offset_range.start {
2383 break;
2384 }
2385 if item.hunk_info().is_some() {
2386 return true;
2387 }
2388 cursor.next();
2389 }
2390 false
2391 }
2392
2393 pub fn has_expanded_diff_hunks_in_ranges(&self, ranges: &[Range<Anchor>], cx: &App) -> bool {
2394 let snapshot = self.read(cx);
2395 let mut cursor = snapshot.diff_transforms.cursor::<MultiBufferOffset>(());
2396 for range in ranges {
2397 let range = range.to_point(&snapshot);
2398 let start = snapshot.point_to_offset(Point::new(range.start.row, 0));
2399 let end = (snapshot.point_to_offset(Point::new(range.end.row + 1, 0)) + 1usize)
2400 .min(snapshot.len());
2401 cursor.seek(&start, Bias::Right);
2402 while let Some(item) = cursor.item() {
2403 if *cursor.start() >= end {
2404 break;
2405 }
2406 if item.hunk_info().is_some() {
2407 return true;
2408 }
2409 cursor.next();
2410 }
2411 }
2412 false
2413 }
2414
2415 pub fn expand_or_collapse_diff_hunks_inner(
2416 &mut self,
2417 // second element is the end of the last excerpt that should be considered for this range
2418 // (or Anchor::Max to opt out)
2419 ranges: impl IntoIterator<Item = (Range<Point>, Anchor)>,
2420 expand: bool,
2421 cx: &mut Context<Self>,
2422 ) -> Vec<Edit<MultiBufferOffset>> {
2423 if self.snapshot.borrow().all_diff_hunks_expanded && !expand {
2424 return Vec::new();
2425 }
2426 self.sync_mut(cx);
2427 let mut snapshot = self.snapshot.get_mut();
2428 let mut excerpt_edits = Vec::new();
2429 let mut last_hunk_row = None;
2430 for (range, excerpt_end) in ranges {
2431 for diff_hunk in snapshot.diff_hunks_in_range(range) {
2432 if diff_hunk
2433 .multi_buffer_range()
2434 .end
2435 .cmp(&excerpt_end, &snapshot)
2436 .is_gt()
2437 {
2438 continue;
2439 }
2440 if last_hunk_row.is_some_and(|row| row >= diff_hunk.row_range.start) {
2441 continue;
2442 }
2443 let range = diff_hunk.multi_buffer_range();
2444 let start = snapshot.excerpt_offset_for_anchor(&range.start);
2445 let end = snapshot.excerpt_offset_for_anchor(&range.end);
2446 last_hunk_row = Some(diff_hunk.row_range.start);
2447 excerpt_edits.push(text::Edit {
2448 old: start..end,
2449 new: start..end,
2450 });
2451 }
2452 }
2453
2454 Self::sync_diff_transforms(
2455 &mut snapshot,
2456 excerpt_edits,
2457 DiffChangeKind::ExpandOrCollapseHunks { expand },
2458 )
2459 }
2460
2461 pub fn expand_or_collapse_diff_hunks(
2462 &mut self,
2463 ranges: Vec<Range<Anchor>>,
2464 expand: bool,
2465 cx: &mut Context<Self>,
2466 ) {
2467 let snapshot = self.snapshot.borrow().clone();
2468 let ranges = ranges.iter().map(move |range| {
2469 let excerpt_end = snapshot
2470 .excerpt_at(range.end)
2471 .map(|excerpt| excerpt.end_anchor().into())
2472 .unwrap_or(Anchor::Max);
2473 let range = range.to_point(&snapshot);
2474 let mut peek_end = range.end;
2475 if range.end.row < snapshot.max_row().0 {
2476 peek_end = Point::new(range.end.row + 1, 0);
2477 };
2478 (range.start..peek_end, excerpt_end)
2479 });
2480 let edits = self.expand_or_collapse_diff_hunks_inner(ranges, expand, cx);
2481 if !edits.is_empty() {
2482 self.subscriptions.publish(edits);
2483 }
2484 cx.emit(Event::DiffHunksToggled);
2485 cx.emit(Event::Edited {
2486 edited_buffer: None,
2487 });
2488 }
2489
2490 #[ztracing::instrument(skip_all)]
2491 fn sync(&self, cx: &App) {
2492 let changed = self.buffer_changed_since_sync.replace(false);
2493 if !changed {
2494 return;
2495 }
2496 let edits = Self::sync_from_buffer_changes(
2497 &mut self.snapshot.borrow_mut(),
2498 &self.buffers,
2499 &self.diffs,
2500 cx,
2501 );
2502 if !edits.is_empty() {
2503 self.subscriptions.publish(edits);
2504 }
2505 }
2506
2507 fn sync_mut(&mut self, cx: &App) -> &mut MultiBufferSnapshot {
2508 let snapshot = self.snapshot.get_mut();
2509 let changed = self.buffer_changed_since_sync.replace(false);
2510 if !changed {
2511 return snapshot;
2512 }
2513 let edits = Self::sync_from_buffer_changes(snapshot, &self.buffers, &self.diffs, cx);
2514
2515 if !edits.is_empty() {
2516 self.subscriptions.publish(edits);
2517 }
2518
2519 snapshot
2520 }
2521
2522 fn sync_from_buffer_changes(
2523 snapshot: &mut MultiBufferSnapshot,
2524 buffers: &BTreeMap<BufferId, BufferState>,
2525 diffs: &HashMap<BufferId, DiffState>,
2526 cx: &App,
2527 ) -> Vec<Edit<MultiBufferOffset>> {
2528 let MultiBufferSnapshot {
2529 excerpts,
2530 diffs: buffer_diff,
2531 path_keys_by_buffer,
2532 path_keys_by_index,
2533 diff_transforms: _,
2534 non_text_state_update_count,
2535 edit_count,
2536 is_dirty,
2537 has_deleted_file,
2538 has_conflict,
2539 has_inverted_diff: _,
2540 singleton: _,
2541 trailing_excerpt_update_count: _,
2542 all_diff_hunks_expanded: _,
2543 show_deleted_hunks: _,
2544 use_extended_diff_range: _,
2545 show_headers: _,
2546 } = snapshot;
2547 *is_dirty = false;
2548 *has_deleted_file = false;
2549 *has_conflict = false;
2550
2551 if !diffs.is_empty() {
2552 let mut diffs_to_add = Vec::new();
2553 for (id, diff) in diffs {
2554 // For inverted diffs, we excerpt the diff base texts in the multibuffer
2555 // and use the diff hunk base text ranges to compute diff transforms.
2556 // Those base text ranges are usize, so make sure if the base text changed
2557 // we also update the diff snapshot so that we don't use stale offsets
2558 if buffer_diff.get(id).is_none_or(|existing_diff| {
2559 if existing_diff.main_buffer.is_none() {
2560 return false;
2561 }
2562 let base_text = diff.diff.read(cx).base_text_buffer().read(cx);
2563 base_text.remote_id() != existing_diff.base_text().remote_id()
2564 || base_text
2565 .version()
2566 .changed_since(existing_diff.base_text().version())
2567 }) {
2568 if diffs_to_add.capacity() == 0 {
2569 // we'd rather overallocate than reallocate as buffer diffs are quite big
2570 // meaning re-allocations will be fairly expensive
2571 diffs_to_add.reserve(diffs.len());
2572 }
2573 diffs_to_add.push((*id, diff.snapshot(cx)));
2574 }
2575 }
2576 buffer_diff.extend(diffs_to_add);
2577 }
2578
2579 let mut paths_to_edit = Vec::new();
2580 let mut non_text_state_updated = false;
2581 let mut edited = false;
2582 for buffer_state in buffers.values() {
2583 let buffer = buffer_state.buffer.read(cx);
2584 let version = buffer.version();
2585 let non_text_state_update_count = buffer.non_text_state_update_count();
2586
2587 let buffer_edited = version.changed_since(&buffer_state.last_version.borrow());
2588 let buffer_non_text_state_updated =
2589 non_text_state_update_count > buffer_state.last_non_text_state_update_count.get();
2590 if (buffer_edited || buffer_non_text_state_updated)
2591 && let Some(path_key) = path_keys_by_buffer.get(&buffer.remote_id())
2592 {
2593 *buffer_state.last_version.borrow_mut() = version;
2594 buffer_state
2595 .last_non_text_state_update_count
2596 .set(non_text_state_update_count);
2597 paths_to_edit.push((path_key.clone(), buffer_state.buffer.clone(), buffer_edited));
2598 }
2599
2600 edited |= buffer_edited;
2601 non_text_state_updated |= buffer_non_text_state_updated;
2602 *is_dirty |= buffer.is_dirty();
2603 *has_deleted_file |= buffer
2604 .file()
2605 .is_some_and(|file| file.disk_state().is_deleted());
2606 *has_conflict |= buffer.has_conflict();
2607 }
2608 if edited {
2609 *edit_count += 1;
2610 }
2611 if non_text_state_updated {
2612 *non_text_state_update_count += 1;
2613 }
2614
2615 paths_to_edit.sort_unstable_by_key(|(path, _, _)| path);
2616
2617 let mut edits = Vec::new();
2618 let mut new_excerpts = SumTree::default();
2619 let mut cursor = excerpts.cursor::<ExcerptSummary>(());
2620
2621 for (path, buffer, buffer_edited) in paths_to_edit {
2622 new_excerpts.append(cursor.slice(&path, Bias::Left), ());
2623 let old_excerpt = cursor.item().unwrap();
2624 let buffer = buffer.read(cx);
2625 let buffer_id = buffer.remote_id();
2626 let mut new_excerpt;
2627 if buffer_edited {
2628 edits.extend(
2629 buffer
2630 .edits_since_in_range::<usize>(
2631 old_excerpt.buffer.version(),
2632 old_excerpt.range.context.clone(),
2633 )
2634 .map(|edit| {
2635 let excerpt_old_start = cursor.start().1;
2636 let excerpt_new_start =
2637 ExcerptDimension(new_excerpts.summary().text.len);
2638 let old_start = excerpt_old_start + edit.old.start;
2639 let old_end = excerpt_old_start + edit.old.end;
2640 let new_start = excerpt_new_start + edit.new.start;
2641 let new_end = excerpt_new_start + edit.new.end;
2642 Edit {
2643 old: old_start..old_end,
2644 new: new_start..new_end,
2645 }
2646 }),
2647 );
2648
2649 new_excerpt = Excerpt::new(
2650 path,
2651 Arc::new(buffer.snapshot()),
2652 old_excerpt.range.clone(),
2653 old_excerpt.has_trailing_newline,
2654 );
2655 } else {
2656 new_excerpt = old_excerpt.clone();
2657 new_excerpt.buffer = Arc::new(buffer.snapshot());
2658 }
2659
2660 new_excerpts.push(new_excerpt, ());
2661 cursor.next();
2662 }
2663 new_excerpts.append(cursor.suffix(), ());
2664
2665 drop(cursor);
2666 *excerpts = new_excerpts;
2667
2668 Self::sync_diff_transforms(snapshot, edits, DiffChangeKind::BufferEdited)
2669 }
2670
2671 fn sync_diff_transforms(
2672 snapshot: &mut MultiBufferSnapshot,
2673 excerpt_edits: Vec<text::Edit<ExcerptOffset>>,
2674 change_kind: DiffChangeKind,
2675 ) -> Vec<Edit<MultiBufferOffset>> {
2676 if excerpt_edits.is_empty() {
2677 return vec![];
2678 }
2679
2680 let mut excerpts = snapshot.excerpts.cursor::<ExcerptOffset>(());
2681 let mut old_diff_transforms = snapshot
2682 .diff_transforms
2683 .cursor::<Dimensions<ExcerptOffset, MultiBufferOffset>>(());
2684 let mut new_diff_transforms = SumTree::default();
2685 let mut old_expanded_hunks = HashSet::default();
2686 let mut output_edits = Vec::new();
2687 let mut output_delta = 0_isize;
2688 let mut at_transform_boundary = true;
2689 let mut end_of_current_insert = None;
2690
2691 let mut excerpt_edits = excerpt_edits.into_iter().peekable();
2692 while let Some(edit) = excerpt_edits.next() {
2693 excerpts.seek_forward(&edit.new.start, Bias::Right);
2694 if excerpts.item().is_none() && *excerpts.start() == edit.new.start {
2695 excerpts.prev();
2696 }
2697
2698 // Keep any transforms that are before the edit.
2699 if at_transform_boundary {
2700 at_transform_boundary = false;
2701 let transforms_before_edit = old_diff_transforms.slice(&edit.old.start, Bias::Left);
2702 Self::append_diff_transforms(&mut new_diff_transforms, transforms_before_edit);
2703 if let Some(transform) = old_diff_transforms.item()
2704 && old_diff_transforms.end().0 == edit.old.start
2705 && old_diff_transforms.start().0 < edit.old.start
2706 {
2707 Self::push_diff_transform(&mut new_diff_transforms, transform.clone());
2708 old_diff_transforms.next();
2709 }
2710 }
2711
2712 // Compute the start of the edit in output coordinates.
2713 let edit_start_overshoot = edit.old.start - old_diff_transforms.start().0;
2714 let edit_old_start = old_diff_transforms.start().1 + edit_start_overshoot;
2715 let edit_new_start =
2716 MultiBufferOffset((edit_old_start.0 as isize + output_delta) as usize);
2717
2718 let changed_diff_hunks = Self::recompute_diff_transforms_for_edit(
2719 &edit,
2720 &mut excerpts,
2721 &mut old_diff_transforms,
2722 &mut new_diff_transforms,
2723 &mut end_of_current_insert,
2724 &mut old_expanded_hunks,
2725 snapshot,
2726 change_kind,
2727 );
2728
2729 // Compute the end of the edit in output coordinates.
2730 let edit_old_end_overshoot = edit.old.end - old_diff_transforms.start().0;
2731 let edit_new_end_overshoot = edit.new.end - new_diff_transforms.summary().excerpt_len();
2732 let edit_old_end = old_diff_transforms.start().1 + edit_old_end_overshoot;
2733 let edit_new_end = new_diff_transforms.summary().output.len + edit_new_end_overshoot;
2734 let output_edit = Edit {
2735 old: edit_old_start..edit_old_end,
2736 new: edit_new_start..edit_new_end,
2737 };
2738
2739 output_delta += (output_edit.new.end - output_edit.new.start) as isize;
2740 output_delta -= (output_edit.old.end - output_edit.old.start) as isize;
2741 if changed_diff_hunks || matches!(change_kind, DiffChangeKind::BufferEdited) {
2742 output_edits.push(output_edit);
2743 }
2744
2745 // If this is the last edit that intersects the current diff transform,
2746 // then recreate the content up to the end of this transform, to prepare
2747 // for reusing additional slices of the old transforms.
2748 if excerpt_edits
2749 .peek()
2750 .is_none_or(|next_edit| next_edit.old.start >= old_diff_transforms.end().0)
2751 {
2752 let keep_next_old_transform = (old_diff_transforms.start().0 >= edit.old.end)
2753 && match old_diff_transforms.item() {
2754 Some(DiffTransform::BufferContent {
2755 inserted_hunk_info: Some(hunk),
2756 ..
2757 }) => excerpts.item().is_some_and(|excerpt| {
2758 if let Some(diff) = snapshot.diffs.get(&excerpt.buffer_id)
2759 && diff.main_buffer.is_some()
2760 {
2761 return true;
2762 }
2763 hunk.hunk_start_anchor.is_valid(&excerpt.buffer)
2764 }),
2765 _ => true,
2766 };
2767
2768 let mut excerpt_offset = edit.new.end;
2769 if !keep_next_old_transform {
2770 excerpt_offset += old_diff_transforms.end().0 - edit.old.end;
2771 old_diff_transforms.next();
2772 }
2773
2774 old_expanded_hunks.clear();
2775 Self::push_buffer_content_transform(
2776 snapshot,
2777 &mut new_diff_transforms,
2778 excerpt_offset,
2779 end_of_current_insert,
2780 );
2781 at_transform_boundary = true;
2782 }
2783 }
2784
2785 // Keep any transforms that are after the last edit.
2786 Self::append_diff_transforms(&mut new_diff_transforms, old_diff_transforms.suffix());
2787
2788 // Ensure there's always at least one buffer content transform.
2789 if new_diff_transforms.is_empty() {
2790 new_diff_transforms.push(
2791 DiffTransform::BufferContent {
2792 summary: Default::default(),
2793 inserted_hunk_info: None,
2794 },
2795 (),
2796 );
2797 }
2798
2799 drop(old_diff_transforms);
2800 drop(excerpts);
2801 snapshot.diff_transforms = new_diff_transforms;
2802 snapshot.edit_count += 1;
2803
2804 #[cfg(any(test, feature = "test-support"))]
2805 snapshot.check_invariants();
2806 output_edits
2807 }
2808
2809 fn recompute_diff_transforms_for_edit(
2810 edit: &Edit<ExcerptOffset>,
2811 excerpts: &mut Cursor<Excerpt, ExcerptOffset>,
2812 old_diff_transforms: &mut Cursor<
2813 DiffTransform,
2814 Dimensions<ExcerptOffset, MultiBufferOffset>,
2815 >,
2816 new_diff_transforms: &mut SumTree<DiffTransform>,
2817 end_of_current_insert: &mut Option<(ExcerptOffset, DiffTransformHunkInfo)>,
2818 old_expanded_hunks: &mut HashSet<DiffTransformHunkInfo>,
2819 snapshot: &MultiBufferSnapshot,
2820 change_kind: DiffChangeKind,
2821 ) -> bool {
2822 log::trace!(
2823 "recomputing diff transform for edit {:?} => {:?}",
2824 edit.old.start..edit.old.end,
2825 edit.new.start..edit.new.end
2826 );
2827
2828 // Record which hunks were previously expanded.
2829 while let Some(item) = old_diff_transforms.item() {
2830 if let Some(hunk_info) = item.hunk_info() {
2831 log::trace!(
2832 "previously expanded hunk at {:?}",
2833 old_diff_transforms.start()
2834 );
2835 old_expanded_hunks.insert(hunk_info);
2836 }
2837 if old_diff_transforms.end().0 > edit.old.end {
2838 break;
2839 }
2840 old_diff_transforms.next();
2841 }
2842
2843 // Avoid querying diff hunks if there's no possibility of hunks being expanded.
2844 // For inverted diffs, hunks are always shown, so we can't skip this.
2845 let all_diff_hunks_expanded = snapshot.all_diff_hunks_expanded;
2846 if old_expanded_hunks.is_empty()
2847 && change_kind == DiffChangeKind::BufferEdited
2848 && !all_diff_hunks_expanded
2849 && !snapshot.has_inverted_diff
2850 {
2851 return false;
2852 }
2853
2854 // Visit each excerpt that intersects the edit.
2855 let mut did_expand_hunks = false;
2856 while let Some(excerpt) = excerpts.item() {
2857 // Recompute the expanded hunks in the portion of the excerpt that
2858 // intersects the edit.
2859 if let Some(diff) = snapshot.diffs.get(&excerpt.buffer_id) {
2860 let buffer = &excerpt.buffer;
2861 let excerpt_start = *excerpts.start();
2862 let excerpt_end = excerpt_start + excerpt.text_summary.len;
2863 let excerpt_buffer_start = excerpt.range.context.start.to_offset(buffer);
2864 let excerpt_buffer_end = excerpt_buffer_start + excerpt.text_summary.len;
2865 let edit_buffer_start =
2866 excerpt_buffer_start + edit.new.start.saturating_sub(excerpt_start);
2867 let edit_buffer_end =
2868 excerpt_buffer_start + edit.new.end.saturating_sub(excerpt_start);
2869 let edit_buffer_end = edit_buffer_end.min(excerpt_buffer_end);
2870
2871 if let Some(main_buffer) = &diff.main_buffer {
2872 for hunk in diff.hunks_intersecting_base_text_range(
2873 edit_buffer_start..edit_buffer_end,
2874 main_buffer,
2875 ) {
2876 did_expand_hunks = true;
2877 let hunk_buffer_range = hunk.diff_base_byte_range.clone();
2878 if hunk_buffer_range.start < excerpt_buffer_start {
2879 log::trace!("skipping hunk that starts before excerpt");
2880 continue;
2881 }
2882 hunk_buffer_range.end.to_point(&excerpt.buffer);
2883 let hunk_excerpt_start = excerpt_start
2884 + hunk_buffer_range.start.saturating_sub(excerpt_buffer_start);
2885 let hunk_excerpt_end = excerpt_end
2886 .min(excerpt_start + (hunk_buffer_range.end - excerpt_buffer_start));
2887 Self::push_buffer_content_transform(
2888 snapshot,
2889 new_diff_transforms,
2890 hunk_excerpt_start,
2891 *end_of_current_insert,
2892 );
2893 if !hunk_buffer_range.is_empty() {
2894 let hunk_info = DiffTransformHunkInfo {
2895 buffer_id: buffer.remote_id(),
2896 hunk_start_anchor: hunk.buffer_range.start,
2897 hunk_secondary_status: hunk.secondary_status,
2898 is_logically_deleted: true,
2899 };
2900 *end_of_current_insert =
2901 Some((hunk_excerpt_end.min(excerpt_end), hunk_info));
2902 }
2903 }
2904 } else {
2905 let edit_anchor_range = buffer.anchor_before(edit_buffer_start)
2906 ..buffer.anchor_after(edit_buffer_end);
2907 for hunk in diff.hunks_intersecting_range(edit_anchor_range, buffer) {
2908 if hunk.is_created_file() && !all_diff_hunks_expanded {
2909 continue;
2910 }
2911
2912 let hunk_buffer_range = hunk.buffer_range.to_offset(buffer);
2913 if hunk_buffer_range.start < excerpt_buffer_start {
2914 log::trace!("skipping hunk that starts before excerpt");
2915 continue;
2916 }
2917
2918 let hunk_info = DiffTransformHunkInfo {
2919 buffer_id: buffer.remote_id(),
2920 hunk_start_anchor: hunk.buffer_range.start,
2921 hunk_secondary_status: hunk.secondary_status,
2922 is_logically_deleted: false,
2923 };
2924
2925 let hunk_excerpt_start = excerpt_start
2926 + hunk_buffer_range.start.saturating_sub(excerpt_buffer_start);
2927 let hunk_excerpt_end = excerpt_end
2928 .min(excerpt_start + (hunk_buffer_range.end - excerpt_buffer_start));
2929
2930 Self::push_buffer_content_transform(
2931 snapshot,
2932 new_diff_transforms,
2933 hunk_excerpt_start,
2934 *end_of_current_insert,
2935 );
2936
2937 // For every existing hunk, determine if it was previously expanded
2938 // and if it should currently be expanded.
2939 let was_previously_expanded = old_expanded_hunks.contains(&hunk_info);
2940 let should_expand_hunk = match &change_kind {
2941 DiffChangeKind::DiffUpdated { base_changed: true } => {
2942 was_previously_expanded || all_diff_hunks_expanded
2943 }
2944 DiffChangeKind::ExpandOrCollapseHunks { expand } => {
2945 let intersects = hunk_buffer_range.is_empty()
2946 || hunk_buffer_range.end > edit_buffer_start;
2947 if *expand {
2948 intersects || was_previously_expanded || all_diff_hunks_expanded
2949 } else {
2950 !intersects
2951 && (was_previously_expanded || all_diff_hunks_expanded)
2952 }
2953 }
2954 _ => was_previously_expanded || all_diff_hunks_expanded,
2955 };
2956
2957 if should_expand_hunk {
2958 did_expand_hunks = true;
2959 log::trace!(
2960 "expanding hunk {:?}, excerpt:{:?}",
2961 hunk_excerpt_start..hunk_excerpt_end,
2962 excerpt.id
2963 );
2964
2965 if !hunk.diff_base_byte_range.is_empty()
2966 && hunk_buffer_range.start >= edit_buffer_start
2967 && hunk_buffer_range.start <= excerpt_buffer_end
2968 && snapshot.show_deleted_hunks
2969 {
2970 let base_text = diff.base_text();
2971 let mut text_cursor =
2972 base_text.as_rope().cursor(hunk.diff_base_byte_range.start);
2973 let mut base_text_summary = text_cursor
2974 .summary::<TextSummary>(hunk.diff_base_byte_range.end);
2975
2976 let mut has_trailing_newline = false;
2977 if base_text_summary.last_line_chars > 0 {
2978 base_text_summary += TextSummary::newline();
2979 has_trailing_newline = true;
2980 }
2981
2982 new_diff_transforms.push(
2983 DiffTransform::DeletedHunk {
2984 base_text_byte_range: hunk.diff_base_byte_range.clone(),
2985 summary: base_text_summary,
2986 buffer_id: excerpt.buffer.remote_id(),
2987 hunk_info,
2988 has_trailing_newline,
2989 },
2990 (),
2991 );
2992 }
2993
2994 if !hunk_buffer_range.is_empty() {
2995 *end_of_current_insert =
2996 Some((hunk_excerpt_end.min(excerpt_end), hunk_info));
2997 }
2998 }
2999 }
3000 }
3001 }
3002
3003 if excerpts.end() <= edit.new.end {
3004 excerpts.next();
3005 } else {
3006 break;
3007 }
3008 }
3009
3010 did_expand_hunks || !old_expanded_hunks.is_empty()
3011 }
3012
3013 fn append_diff_transforms(
3014 new_transforms: &mut SumTree<DiffTransform>,
3015 subtree: SumTree<DiffTransform>,
3016 ) {
3017 if let Some(DiffTransform::BufferContent {
3018 inserted_hunk_info,
3019 summary,
3020 }) = subtree.first()
3021 && Self::extend_last_buffer_content_transform(
3022 new_transforms,
3023 *inserted_hunk_info,
3024 *summary,
3025 )
3026 {
3027 let mut cursor = subtree.cursor::<()>(());
3028 cursor.next();
3029 cursor.next();
3030 new_transforms.append(cursor.suffix(), ());
3031 return;
3032 }
3033 new_transforms.append(subtree, ());
3034 }
3035
3036 fn push_diff_transform(new_transforms: &mut SumTree<DiffTransform>, transform: DiffTransform) {
3037 if let DiffTransform::BufferContent {
3038 inserted_hunk_info: inserted_hunk_anchor,
3039 summary,
3040 } = transform
3041 && Self::extend_last_buffer_content_transform(
3042 new_transforms,
3043 inserted_hunk_anchor,
3044 summary,
3045 )
3046 {
3047 return;
3048 }
3049 new_transforms.push(transform, ());
3050 }
3051
3052 fn push_buffer_content_transform(
3053 old_snapshot: &MultiBufferSnapshot,
3054 new_transforms: &mut SumTree<DiffTransform>,
3055 end_offset: ExcerptOffset,
3056 current_inserted_hunk: Option<(ExcerptOffset, DiffTransformHunkInfo)>,
3057 ) {
3058 let inserted_region = current_inserted_hunk.map(|(insertion_end_offset, hunk_info)| {
3059 (end_offset.min(insertion_end_offset), Some(hunk_info))
3060 });
3061 let unchanged_region = [(end_offset, None)];
3062
3063 for (end_offset, inserted_hunk_info) in inserted_region.into_iter().chain(unchanged_region)
3064 {
3065 let start_offset = new_transforms.summary().excerpt_len();
3066 if end_offset <= start_offset {
3067 continue;
3068 }
3069 let summary_to_add = old_snapshot
3070 .text_summary_for_excerpt_offset_range::<MBTextSummary>(start_offset..end_offset);
3071
3072 if !Self::extend_last_buffer_content_transform(
3073 new_transforms,
3074 inserted_hunk_info,
3075 summary_to_add,
3076 ) {
3077 new_transforms.push(
3078 DiffTransform::BufferContent {
3079 summary: summary_to_add,
3080 inserted_hunk_info,
3081 },
3082 (),
3083 )
3084 }
3085 }
3086 }
3087
3088 fn extend_last_buffer_content_transform(
3089 new_transforms: &mut SumTree<DiffTransform>,
3090 new_inserted_hunk_info: Option<DiffTransformHunkInfo>,
3091 summary_to_add: MBTextSummary,
3092 ) -> bool {
3093 let mut did_extend = false;
3094 new_transforms.update_last(
3095 |last_transform| {
3096 if let DiffTransform::BufferContent {
3097 summary,
3098 inserted_hunk_info: inserted_hunk_anchor,
3099 } = last_transform
3100 && *inserted_hunk_anchor == new_inserted_hunk_info
3101 {
3102 *summary += summary_to_add;
3103 did_extend = true;
3104 }
3105 },
3106 (),
3107 );
3108 did_extend
3109 }
3110
3111 pub fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
3112 let snapshot = self.snapshot(cx);
3113 let excerpt_end = snapshot
3114 .excerpt_at(range.end)
3115 .map(|excerpt| excerpt.end_anchor().into())
3116 .unwrap_or(Anchor::Max);
3117 let point_range = range.to_point(&snapshot);
3118 let expand = !self.single_hunk_is_expanded(range, cx);
3119 let edits =
3120 self.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_end)], expand, cx);
3121 if !edits.is_empty() {
3122 self.subscriptions.publish(edits);
3123 }
3124 cx.emit(Event::DiffHunksToggled);
3125 cx.emit(Event::Edited {
3126 edited_buffer: None,
3127 });
3128 }
3129}
3130
3131fn build_excerpt_ranges(
3132 ranges: impl IntoIterator<Item = Range<Point>>,
3133 context_line_count: u32,
3134 buffer_snapshot: &BufferSnapshot,
3135) -> Vec<ExcerptRange<Point>> {
3136 ranges
3137 .into_iter()
3138 .map(|range| {
3139 let start_row = range.start.row.saturating_sub(context_line_count);
3140 let start = Point::new(start_row, 0);
3141 let end_row = (range.end.row + context_line_count).min(buffer_snapshot.max_point().row);
3142 let end = Point::new(end_row, buffer_snapshot.line_len(end_row));
3143 ExcerptRange {
3144 context: start..end,
3145 primary: range,
3146 }
3147 })
3148 .collect()
3149}
3150
3151#[cfg(any(test, feature = "test-support"))]
3152impl MultiBuffer {
3153 pub fn build_simple(text: &str, cx: &mut gpui::App) -> Entity<Self> {
3154 let buffer = cx.new(|cx| Buffer::local(text, cx));
3155 cx.new(|cx| Self::singleton(buffer, cx))
3156 }
3157
3158 pub fn build_multi<const COUNT: usize>(
3159 excerpts: [(&str, Vec<Range<Point>>); COUNT],
3160 cx: &mut gpui::App,
3161 ) -> Entity<Self> {
3162 let multi = cx.new(|_| Self::new(Capability::ReadWrite));
3163 for (ix, (text, ranges)) in excerpts.into_iter().enumerate() {
3164 let buffer = cx.new(|cx| Buffer::local(text, cx));
3165 let snapshot = buffer.read(cx).snapshot();
3166 let excerpt_ranges = ranges
3167 .into_iter()
3168 .map(ExcerptRange::new)
3169 .collect::<Vec<_>>();
3170 multi.update(cx, |multi, cx| {
3171 multi.set_excerpt_ranges_for_path(
3172 PathKey::sorted(ix as u64),
3173 buffer,
3174 &snapshot,
3175 excerpt_ranges,
3176 cx,
3177 )
3178 });
3179 }
3180
3181 multi
3182 }
3183
3184 pub fn build_from_buffer(buffer: Entity<Buffer>, cx: &mut gpui::App) -> Entity<Self> {
3185 cx.new(|cx| Self::singleton(buffer, cx))
3186 }
3187
3188 pub fn build_random(rng: &mut impl rand::Rng, cx: &mut gpui::App) -> Entity<Self> {
3189 cx.new(|cx| {
3190 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
3191 let mutation_count = rng.random_range(1..=5);
3192 multibuffer.randomly_edit_excerpts(rng, mutation_count, cx);
3193 multibuffer
3194 })
3195 }
3196
3197 pub fn randomly_edit(
3198 &mut self,
3199 rng: &mut impl rand::Rng,
3200 edit_count: usize,
3201 cx: &mut Context<Self>,
3202 ) {
3203 use util::RandomCharIter;
3204
3205 let snapshot = self.read(cx);
3206 let mut edits: Vec<(Range<MultiBufferOffset>, Arc<str>)> = Vec::new();
3207 let mut last_end = None;
3208 for _ in 0..edit_count {
3209 if last_end.is_some_and(|last_end| last_end >= snapshot.len()) {
3210 break;
3211 }
3212
3213 let new_start = last_end.map_or(MultiBufferOffset::ZERO, |last_end| last_end + 1usize);
3214 let end =
3215 snapshot.clip_offset(rng.random_range(new_start..=snapshot.len()), Bias::Right);
3216 let start = snapshot.clip_offset(rng.random_range(new_start..=end), Bias::Right);
3217 last_end = Some(end);
3218
3219 let mut range = start..end;
3220 if rng.random_bool(0.2) {
3221 mem::swap(&mut range.start, &mut range.end);
3222 }
3223
3224 let new_text_len = rng.random_range(0..10);
3225 let new_text: String = RandomCharIter::new(&mut *rng).take(new_text_len).collect();
3226
3227 edits.push((range, new_text.into()));
3228 }
3229 log::info!("mutating multi-buffer with {:?}", edits);
3230 drop(snapshot);
3231
3232 self.edit(edits, None, cx);
3233 }
3234
3235 pub fn randomly_edit_excerpts(
3236 &mut self,
3237 rng: &mut impl rand::Rng,
3238 mutation_count: usize,
3239 cx: &mut Context<Self>,
3240 ) {
3241 use rand::prelude::*;
3242 use std::env;
3243 use util::RandomCharIter;
3244
3245 let max_excerpts = env::var("MAX_EXCERPTS")
3246 .map(|i| i.parse().expect("invalid `MAX_EXCERPTS` variable"))
3247 .unwrap_or(5);
3248
3249 let mut buffers = Vec::new();
3250 for _ in 0..mutation_count {
3251 if rng.random_bool(0.05) {
3252 log::info!("Clearing multi-buffer");
3253 self.clear(cx);
3254 continue;
3255 } else if rng.random_bool(0.1) && !self.excerpt_ids().is_empty() {
3256 let ids = self.excerpt_ids();
3257 let mut excerpts = HashSet::default();
3258 for _ in 0..rng.random_range(0..ids.len()) {
3259 excerpts.extend(ids.choose(rng).copied());
3260 }
3261
3262 let line_count = rng.random_range(0..5);
3263
3264 log::info!("Expanding excerpts {excerpts:?} by {line_count} lines");
3265
3266 self.expand_excerpts(
3267 excerpts.iter().cloned(),
3268 line_count,
3269 ExpandExcerptDirection::UpAndDown,
3270 cx,
3271 );
3272 continue;
3273 }
3274
3275 let excerpt_ids = self.excerpt_ids();
3276 if excerpt_ids.is_empty() || (rng.random() && excerpt_ids.len() < max_excerpts) {
3277 let buffer_handle = if rng.random() || self.buffers.is_empty() {
3278 let text = RandomCharIter::new(&mut *rng).take(10).collect::<String>();
3279 buffers.push(cx.new(|cx| Buffer::local(text, cx)));
3280 let buffer = buffers.last().unwrap().read(cx);
3281 log::info!(
3282 "Creating new buffer {} with text: {:?}",
3283 buffer.remote_id(),
3284 buffer.text()
3285 );
3286 buffers.last().unwrap().clone()
3287 } else {
3288 self.buffers.values().choose(rng).unwrap().buffer.clone()
3289 };
3290
3291 let buffer = buffer_handle.read(cx);
3292 let buffer_text = buffer.text();
3293 let ranges = (0..rng.random_range(0..5))
3294 .map(|_| {
3295 let end_ix =
3296 buffer.clip_offset(rng.random_range(0..=buffer.len()), Bias::Right);
3297 let start_ix = buffer.clip_offset(rng.random_range(0..=end_ix), Bias::Left);
3298 ExcerptRange::new(start_ix..end_ix)
3299 })
3300 .collect::<Vec<_>>();
3301 log::info!(
3302 "Inserting excerpts from buffer {} and ranges {:?}: {:?}",
3303 buffer_handle.read(cx).remote_id(),
3304 ranges.iter().map(|r| &r.context).collect::<Vec<_>>(),
3305 ranges
3306 .iter()
3307 .map(|r| &buffer_text[r.context.clone()])
3308 .collect::<Vec<_>>()
3309 );
3310
3311 let excerpt_id =
3312 self.insert_excerpts_after(ExcerptId::max(), buffer_handle, ranges, cx);
3313 log::info!("Inserted with ids: {:?}", excerpt_id);
3314 } else {
3315 let remove_count = rng.random_range(1..=excerpt_ids.len());
3316 let mut excerpts_to_remove = excerpt_ids
3317 .choose_multiple(rng, remove_count)
3318 .cloned()
3319 .collect::<Vec<_>>();
3320 let snapshot = self.snapshot.borrow();
3321 excerpts_to_remove.sort_unstable_by(|a, b| a.cmp(b, &snapshot));
3322 drop(snapshot);
3323 log::info!("Removing excerpts {:?}", excerpts_to_remove);
3324 self.remove_excerpts(excerpts_to_remove, cx);
3325 }
3326 }
3327 }
3328
3329 pub fn randomly_mutate(
3330 &mut self,
3331 rng: &mut impl rand::Rng,
3332 mutation_count: usize,
3333 cx: &mut Context<Self>,
3334 ) {
3335 use rand::prelude::*;
3336
3337 if rng.random_bool(0.7) || self.singleton {
3338 let buffer = self
3339 .buffers
3340 .values()
3341 .choose(rng)
3342 .map(|state| state.buffer.clone());
3343
3344 if let Some(buffer) = buffer {
3345 buffer.update(cx, |buffer, cx| {
3346 if rng.random() {
3347 buffer.randomly_edit(rng, mutation_count, cx);
3348 } else {
3349 buffer.randomly_undo_redo(rng, cx);
3350 }
3351 });
3352 } else {
3353 self.randomly_edit(rng, mutation_count, cx);
3354 }
3355 } else {
3356 self.randomly_edit_excerpts(rng, mutation_count, cx);
3357 }
3358
3359 self.check_invariants(cx);
3360 }
3361
3362 fn check_invariants(&self, cx: &App) {
3363 self.read(cx).check_invariants();
3364 }
3365}
3366
3367impl EventEmitter<Event> for MultiBuffer {}
3368
3369impl MultiBufferSnapshot {
3370 pub fn text(&self) -> String {
3371 self.chunks(MultiBufferOffset::ZERO..self.len(), false)
3372 .map(|chunk| chunk.text)
3373 .collect()
3374 }
3375
3376 pub fn reversed_chars_at<T: ToOffset>(&self, position: T) -> impl Iterator<Item = char> + '_ {
3377 self.reversed_chunks_in_range(MultiBufferOffset::ZERO..position.to_offset(self))
3378 .flat_map(|c| c.chars().rev())
3379 }
3380
3381 fn reversed_chunks_in_range(
3382 &self,
3383 range: Range<MultiBufferOffset>,
3384 ) -> ReversedMultiBufferChunks<'_> {
3385 let mut cursor = self.cursor::<MultiBufferOffset, BufferOffset>();
3386 cursor.seek(&range.end);
3387 let current_chunks = cursor.region().as_ref().map(|region| {
3388 let start_overshoot = range.start.saturating_sub(region.range.start);
3389 let end_overshoot = range.end - region.range.start;
3390 let end = (region.buffer_range.start + end_overshoot).min(region.buffer_range.end);
3391 let start = region.buffer_range.start + start_overshoot;
3392 region.buffer.reversed_chunks_in_range(start..end)
3393 });
3394 ReversedMultiBufferChunks {
3395 cursor,
3396 current_chunks,
3397 start: range.start,
3398 offset: range.end,
3399 }
3400 }
3401
3402 pub fn chars_at<T: ToOffset>(&self, position: T) -> impl Iterator<Item = char> + '_ {
3403 let offset = position.to_offset(self);
3404 self.text_for_range(offset..self.len())
3405 .flat_map(|chunk| chunk.chars())
3406 }
3407
3408 pub fn text_for_range<T: ToOffset>(&self, range: Range<T>) -> impl Iterator<Item = &str> + '_ {
3409 self.chunks(range, false).map(|chunk| chunk.text)
3410 }
3411
3412 pub fn is_line_blank(&self, row: MultiBufferRow) -> bool {
3413 self.text_for_range(Point::new(row.0, 0)..Point::new(row.0, self.line_len(row)))
3414 .all(|chunk| chunk.matches(|c: char| !c.is_whitespace()).next().is_none())
3415 }
3416
3417 pub fn contains_str_at<T>(&self, position: T, needle: &str) -> bool
3418 where
3419 T: ToOffset,
3420 {
3421 let position = position.to_offset(self);
3422 position == self.clip_offset(position, Bias::Left)
3423 && self
3424 .bytes_in_range(position..self.len())
3425 .flatten()
3426 .copied()
3427 .take(needle.len())
3428 .eq(needle.bytes())
3429 }
3430
3431 pub fn diff_hunks(&self) -> impl Iterator<Item = MultiBufferDiffHunk> + '_ {
3432 self.diff_hunks_in_range(Anchor::min()..Anchor::max())
3433 }
3434
3435 pub fn diff_hunks_in_range<T: ToPoint>(
3436 &self,
3437 range: Range<T>,
3438 ) -> impl Iterator<Item = MultiBufferDiffHunk> + '_ {
3439 let query_range = range.start.to_point(self)..range.end.to_point(self);
3440 self.lift_buffer_metadata(query_range.clone(), move |buffer, buffer_range| {
3441 let diff = self.diffs.get(&buffer.remote_id())?;
3442 let iter = if let Some(main_buffer) = &diff.main_buffer {
3443 let buffer_start = buffer.point_to_offset(buffer_range.start);
3444 let buffer_end = buffer.point_to_offset(buffer_range.end);
3445 itertools::Either::Left(
3446 diff.hunks_intersecting_base_text_range(buffer_start..buffer_end, main_buffer)
3447 .map(move |hunk| (hunk, buffer, true)),
3448 )
3449 } else {
3450 let buffer_start = buffer.anchor_before(buffer_range.start);
3451 let buffer_end = buffer.anchor_after(buffer_range.end);
3452 itertools::Either::Right(
3453 diff.hunks_intersecting_range(buffer_start..buffer_end, buffer)
3454 .map(move |hunk| (hunk, buffer, false)),
3455 )
3456 };
3457 Some(iter.filter_map(|(hunk, buffer, is_inverted)| {
3458 if hunk.is_created_file() && !self.all_diff_hunks_expanded {
3459 return None;
3460 }
3461 let range = if is_inverted {
3462 hunk.diff_base_byte_range.to_point(&buffer)
3463 } else {
3464 hunk.range.clone()
3465 };
3466 Some((range, (hunk, is_inverted)))
3467 }))
3468 })
3469 .filter_map(move |(range, (hunk, is_inverted), excerpt)| {
3470 if range.start != range.end && range.end == query_range.start && !hunk.range.is_empty()
3471 {
3472 return None;
3473 }
3474 let end_row = if range.end.column == 0 {
3475 range.end.row
3476 } else {
3477 range.end.row + 1
3478 };
3479
3480 let word_diffs =
3481 (!hunk.base_word_diffs.is_empty() || !hunk.buffer_word_diffs.is_empty())
3482 .then(|| {
3483 let mut word_diffs = Vec::new();
3484
3485 if self.show_deleted_hunks || is_inverted {
3486 let hunk_start_offset = if is_inverted {
3487 Anchor::in_buffer(
3488 excerpt.path_key_index,
3489 excerpt.buffer.anchor_after(hunk.diff_base_byte_range.start),
3490 )
3491 .to_offset(self)
3492 } else {
3493 Anchor::in_buffer(excerpt.path_key_index, hunk.buffer_range.start)
3494 .to_offset(self)
3495 };
3496
3497 word_diffs.extend(hunk.base_word_diffs.iter().map(|diff| {
3498 hunk_start_offset + diff.start..hunk_start_offset + diff.end
3499 }));
3500 }
3501
3502 if !is_inverted {
3503 word_diffs.extend(hunk.buffer_word_diffs.into_iter().map(|diff| {
3504 Anchor::range_in_buffer(excerpt.path_key_index, diff)
3505 .to_offset(self)
3506 }));
3507 }
3508 word_diffs
3509 })
3510 .unwrap_or_default();
3511
3512 let buffer_range = if is_inverted {
3513 excerpt.buffer.anchor_after(hunk.diff_base_byte_range.start)
3514 ..excerpt.buffer.anchor_before(hunk.diff_base_byte_range.end)
3515 } else {
3516 hunk.buffer_range.clone()
3517 };
3518 let status_kind = if hunk.buffer_range.start == hunk.buffer_range.end {
3519 DiffHunkStatusKind::Deleted
3520 } else if hunk.diff_base_byte_range.is_empty() {
3521 DiffHunkStatusKind::Added
3522 } else {
3523 DiffHunkStatusKind::Modified
3524 };
3525 Some(MultiBufferDiffHunk {
3526 row_range: MultiBufferRow(range.start.row)..MultiBufferRow(end_row),
3527 buffer_id: excerpt.buffer.remote_id(),
3528 buffer_range,
3529 word_diffs,
3530 diff_base_byte_range: BufferOffset(hunk.diff_base_byte_range.start)
3531 ..BufferOffset(hunk.diff_base_byte_range.end),
3532 status: DiffHunkStatus {
3533 kind: status_kind,
3534 secondary: hunk.secondary_status,
3535 },
3536 })
3537 })
3538 }
3539
3540 fn excerpts_for_range<T: ToOffset>(
3541 &self,
3542 range: Range<T>,
3543 ) -> impl Iterator<Item = &Excerpt> + '_ {
3544 let range = range.start.to_offset(self)..range.end.to_offset(self);
3545 let mut cursor = self.cursor::<MultiBufferOffset, BufferOffset>();
3546 cursor.seek(&range.start);
3547 std::iter::from_fn(move || {
3548 let region = cursor.region()?;
3549 if region.range.start > range.end
3550 || region.range.start == range.end && region.range.start > range.start
3551 {
3552 return None;
3553 }
3554 let excerpt = region.excerpt;
3555 cursor.next_excerpt_forwards();
3556 Some(excerpt)
3557 })
3558 }
3559
3560 // todo!() repeats ids? seems weird
3561 pub fn buffer_ids_for_range<T: ToOffset>(
3562 &self,
3563 range: Range<T>,
3564 ) -> impl Iterator<Item = BufferId> + '_ {
3565 self.excerpts_for_range(range)
3566 .map(|excerpt| excerpt.buffer.remote_id())
3567 }
3568
3569 /// Resolves the given [`text::Anchor`]s to [`crate::Anchor`]s if the anchor is within a visible excerpt.
3570 ///
3571 /// The passed in anchors must be ordered.
3572 pub fn text_anchors_to_visible_anchors(
3573 &self,
3574 anchors: impl IntoIterator<Item = text::Anchor>,
3575 ) -> Vec<Option<Anchor>> {
3576 let anchors = anchors.into_iter();
3577 let mut result = Vec::with_capacity(anchors.size_hint().0);
3578 let mut anchors = anchors.peekable();
3579 let mut cursor = self.excerpts.cursor::<ExcerptSummary>(());
3580 'anchors: while let Some(anchor) = anchors.peek() {
3581 let Some(buffer_id) = anchor.buffer_id else {
3582 anchors.next();
3583 result.push(None);
3584 continue 'anchors;
3585 };
3586 let mut same_buffer_anchors =
3587 anchors.peeking_take_while(|a| a.buffer_id.is_some_and(|b| buffer_id == b));
3588
3589 if let Some(path) = self.path_keys_by_buffer.get(&buffer_id) {
3590 let Some(mut next) = same_buffer_anchors.next() else {
3591 continue 'anchors;
3592 };
3593 cursor.seek_forward(path, Bias::Left);
3594 'excerpts: loop {
3595 let Some(excerpt) = cursor.item() else {
3596 break;
3597 };
3598 if &excerpt.path_key != path {
3599 break;
3600 }
3601
3602 loop {
3603 // anchor is before the first excerpt
3604 if excerpt
3605 .range
3606 .context
3607 .start
3608 .cmp(&next, &excerpt.buffer)
3609 .is_gt()
3610 {
3611 // so we skip it and try the next anchor
3612 result.push(None);
3613 match same_buffer_anchors.next() {
3614 Some(anchor) => next = anchor,
3615 None => continue 'anchors,
3616 }
3617 // anchor is within the excerpt
3618 } else if excerpt
3619 .range
3620 .context
3621 .end
3622 .cmp(&next, &excerpt.buffer)
3623 .is_ge()
3624 {
3625 // record it and all following anchors that are within
3626 result.push(Some(Anchor::in_buffer(excerpt.path_key_index, next)));
3627 result.extend(
3628 same_buffer_anchors
3629 .peeking_take_while(|a| {
3630 excerpt.range.context.end.cmp(a, &excerpt.buffer).is_ge()
3631 })
3632 .map(|a| Some(Anchor::in_buffer(excerpt.path_key_index, a))),
3633 );
3634 match same_buffer_anchors.next() {
3635 Some(anchor) => next = anchor,
3636 None => continue 'anchors,
3637 }
3638 // anchor is after the excerpt, try the next one
3639 } else {
3640 cursor.next();
3641 continue 'excerpts;
3642 }
3643 }
3644 }
3645 // account for `next`
3646 result.push(None);
3647 }
3648 result.extend(same_buffer_anchors.map(|_| None));
3649 }
3650
3651 result
3652 }
3653
3654 pub fn ranges_to_buffer_ranges<T: ToOffset>(
3655 &self,
3656 ranges: impl Iterator<Item = Range<T>>,
3657 ) -> impl Iterator<Item = (&BufferSnapshot, Range<BufferOffset>, ExcerptId)> {
3658 ranges.flat_map(|range| {
3659 self.range_to_buffer_ranges((Bound::Included(range.start), Bound::Included(range.end)))
3660 .into_iter()
3661 })
3662 }
3663
3664 pub fn range_to_buffer_ranges<R, T>(
3665 &self,
3666 range: R,
3667 ) -> Vec<(&BufferSnapshot, Range<BufferOffset>, ExcerptId)>
3668 where
3669 R: RangeBounds<T>,
3670 T: ToOffset,
3671 {
3672 self.range_to_buffer_ranges_with_context(range)
3673 .into_iter()
3674 .map(|(buffer, range, id, _context)| (buffer, range, id))
3675 .collect()
3676 }
3677
3678 pub fn range_to_buffer_ranges_with_context<R, T>(
3679 &self,
3680 range: R,
3681 ) -> Vec<(
3682 &BufferSnapshot,
3683 Range<BufferOffset>,
3684 ExcerptId,
3685 Range<text::Anchor>,
3686 )>
3687 where
3688 R: RangeBounds<T>,
3689 T: ToOffset,
3690 {
3691 let start = match range.start_bound() {
3692 Bound::Included(start) => start.to_offset(self),
3693 Bound::Excluded(_) => panic!("excluded start bound not supported"),
3694 Bound::Unbounded => MultiBufferOffset::ZERO,
3695 };
3696 let end_bound = match range.end_bound() {
3697 Bound::Included(end) => Bound::Included(end.to_offset(self)),
3698 Bound::Excluded(end) => Bound::Excluded(end.to_offset(self)),
3699 Bound::Unbounded => Bound::Unbounded,
3700 };
3701 let bounds = (Bound::Included(start), end_bound);
3702
3703 let mut cursor = self.cursor::<MultiBufferOffset, BufferOffset>();
3704 cursor.seek(&start);
3705
3706 let mut result: Vec<(
3707 &BufferSnapshot,
3708 Range<BufferOffset>,
3709 ExcerptId,
3710 Range<text::Anchor>,
3711 )> = Vec::new();
3712 while let Some(region) = cursor.region() {
3713 let dominated_by_end_bound = match end_bound {
3714 Bound::Included(end) => region.range.start > end,
3715 Bound::Excluded(end) => region.range.start >= end,
3716 Bound::Unbounded => false,
3717 };
3718 if dominated_by_end_bound {
3719 break;
3720 }
3721 if region.is_main_buffer {
3722 let start_overshoot = start.saturating_sub(region.range.start);
3723 let end_offset = match end_bound {
3724 Bound::Included(end) | Bound::Excluded(end) => end,
3725 Bound::Unbounded => region.range.end,
3726 };
3727 let end_overshoot = end_offset.saturating_sub(region.range.start);
3728 let start = region
3729 .buffer_range
3730 .end
3731 .min(region.buffer_range.start + start_overshoot);
3732 let end = region
3733 .buffer_range
3734 .end
3735 .min(region.buffer_range.start + end_overshoot);
3736 let context = region.excerpt.range.context.clone();
3737 if let Some(prev) = result.last_mut().filter(|(_, prev_range, excerpt_id, _)| {
3738 *excerpt_id == region.excerpt.id && prev_range.end == start
3739 }) {
3740 prev.1.end = end;
3741 } else {
3742 result.push((region.buffer, start..end, region.excerpt.id, context));
3743 }
3744 }
3745 cursor.next();
3746 }
3747
3748 if let Some(excerpt) = cursor.excerpt() {
3749 let dominated_by_prev_excerpt =
3750 result.last().is_some_and(|(_, _, id, _)| *id == excerpt.id);
3751 if !dominated_by_prev_excerpt && excerpt.text_summary.len == 0 {
3752 let excerpt_position = self.len();
3753 if bounds.contains(&excerpt_position) {
3754 let buffer_offset =
3755 BufferOffset(excerpt.range.context.start.to_offset(&excerpt.buffer));
3756 let context = excerpt.range.context.clone();
3757 result.push((
3758 &excerpt.buffer,
3759 buffer_offset..buffer_offset,
3760 excerpt.id,
3761 context,
3762 ));
3763 }
3764 }
3765 }
3766
3767 result
3768 }
3769
3770 pub fn range_to_buffer_ranges_with_deleted_hunks<T: ToOffset>(
3771 &self,
3772 range: Range<T>,
3773 ) -> impl Iterator<
3774 Item = (
3775 &BufferSnapshot,
3776 Range<BufferOffset>,
3777 ExcerptId,
3778 Option<Anchor>,
3779 ),
3780 > + '_ {
3781 let start = range.start.to_offset(self);
3782 let end = range.end.to_offset(self);
3783
3784 let mut cursor = self.cursor::<MultiBufferOffset, BufferOffset>();
3785 cursor.seek(&start);
3786
3787 std::iter::from_fn(move || {
3788 let region = cursor.region()?;
3789 if region.range.start > end {
3790 return None;
3791 }
3792 let start_overshoot = start.saturating_sub(region.range.start);
3793 let end_overshoot = end.saturating_sub(region.range.start);
3794 let start = region
3795 .buffer_range
3796 .end
3797 .min(region.buffer_range.start + start_overshoot);
3798 let end = region
3799 .buffer_range
3800 .end
3801 .min(region.buffer_range.start + end_overshoot);
3802
3803 let region_excerpt_id = region.excerpt.id;
3804 let deleted_hunk_anchor = if region.is_main_buffer {
3805 None
3806 } else {
3807 Some(self.anchor_before(region.range.start))
3808 };
3809 let result = (
3810 region.buffer,
3811 start..end,
3812 region_excerpt_id,
3813 deleted_hunk_anchor,
3814 );
3815 cursor.next();
3816 Some(result)
3817 })
3818 }
3819
3820 /// Retrieves buffer metadata for the given range, and converts it into multi-buffer
3821 /// coordinates.
3822 ///
3823 /// The given callback will be called for every excerpt intersecting the given range. It will
3824 /// be passed the excerpt's buffer and the buffer range that the input range intersects.
3825 /// The callback should return an iterator of metadata items from that buffer, each paired
3826 /// with a buffer range.
3827 ///
3828 /// The returned iterator yields each of these metadata items, paired with its range in
3829 /// multi-buffer coordinates.
3830 fn lift_buffer_metadata<'a, MBD, M, I>(
3831 &'a self,
3832 query_range: Range<MBD>,
3833 get_buffer_metadata: impl 'a + Fn(&'a BufferSnapshot, Range<MBD::TextDimension>) -> Option<I>,
3834 ) -> impl Iterator<Item = (Range<MBD>, M, &'a Excerpt)> + 'a
3835 where
3836 I: Iterator<Item = (Range<MBD::TextDimension>, M)> + 'a,
3837 MBD: MultiBufferDimension
3838 + Ord
3839 + Sub<Output = MBD::TextDimension>
3840 + ops::Add<MBD::TextDimension, Output = MBD>
3841 + ops::AddAssign<MBD::TextDimension>,
3842 MBD::TextDimension: Sub<Output = MBD::TextDimension>
3843 + ops::Add<Output = MBD::TextDimension>
3844 + AddAssign<MBD::TextDimension>
3845 + Ord,
3846 {
3847 let mut current_excerpt_metadata: Option<(Excerpt, I)> = None;
3848 let mut cursor = self.cursor::<MBD, MBD::TextDimension>();
3849
3850 // Find the excerpt and buffer offset where the given range ends.
3851 cursor.seek(&query_range.end);
3852 let mut range_end = None;
3853 while let Some(region) = cursor.region() {
3854 if region.is_main_buffer {
3855 let mut buffer_end = region.buffer_range.start;
3856 let overshoot = if query_range.end > region.range.start {
3857 query_range.end - region.range.start
3858 } else {
3859 <MBD::TextDimension>::default()
3860 };
3861 buffer_end = buffer_end + overshoot;
3862 range_end = Some((region.excerpt, buffer_end));
3863 break;
3864 }
3865 cursor.next();
3866 }
3867
3868 cursor.seek(&query_range.start);
3869
3870 if let Some(region) = cursor.region().filter(|region| !region.is_main_buffer)
3871 && region.range.start > MBD::default()
3872 {
3873 cursor.prev()
3874 } else if let Some(region) = cursor.region()
3875 && region.is_main_buffer
3876 && region.diff_hunk_status.is_some()
3877 {
3878 cursor.prev();
3879 if cursor.region().is_none_or(|region| region.is_main_buffer) {
3880 cursor.next();
3881 }
3882 }
3883
3884 iter::from_fn(move || {
3885 loop {
3886 let excerpt = cursor.excerpt()?;
3887
3888 // If we have already retrieved metadata for this excerpt, continue to use it.
3889 let metadata_iter = if let Some((_, metadata)) = current_excerpt_metadata
3890 .as_mut()
3891 .filter(|(excerpt_id, _)| *excerpt_id == excerpt.id)
3892 {
3893 Some(metadata)
3894 }
3895 // Otherwise, compute the intersection of the input range with the excerpt's range,
3896 // and retrieve the metadata for the resulting range.
3897 else {
3898 let region = cursor.region()?;
3899 let mut buffer_start;
3900 if region.is_main_buffer {
3901 buffer_start = region.buffer_range.start;
3902 if query_range.start > region.range.start {
3903 let overshoot = query_range.start - region.range.start;
3904 buffer_start = buffer_start + overshoot;
3905 }
3906 buffer_start = buffer_start.min(region.buffer_range.end);
3907 } else {
3908 buffer_start = cursor.main_buffer_position()?;
3909 };
3910 let mut buffer_end = excerpt
3911 .range
3912 .context
3913 .end
3914 .summary::<MBD::TextDimension>(&excerpt.buffer);
3915 if let Some((end_excerpt, end_buffer_offset)) = range_end
3916 && excerpt == end_excerpt
3917 {
3918 buffer_end = buffer_end.min(end_buffer_offset);
3919 }
3920
3921 get_buffer_metadata(&excerpt.buffer, buffer_start..buffer_end)
3922 .map(|iterator| &mut current_excerpt_metadata.insert((excerpt, iterator)).1)
3923 };
3924
3925 // Visit each metadata item.
3926 if let Some((metadata_buffer_range, metadata)) =
3927 metadata_iter.and_then(Iterator::next)
3928 {
3929 // Find the multibuffer regions that contain the start and end of
3930 // the metadata item's range.
3931 if metadata_buffer_range.start > <MBD::TextDimension>::default() {
3932 while let Some(region) = cursor.region() {
3933 if (region.is_main_buffer
3934 && (region.buffer_range.end >= metadata_buffer_range.start
3935 || cursor.is_at_end_of_excerpt()))
3936 || (!region.is_main_buffer
3937 && region.buffer_range.start == metadata_buffer_range.start)
3938 {
3939 break;
3940 }
3941 cursor.next();
3942 }
3943 }
3944 let start_region = cursor.region()?.clone();
3945 while let Some(region) = cursor.region() {
3946 if region.is_main_buffer
3947 && (region.buffer_range.end > metadata_buffer_range.end
3948 || cursor.is_at_end_of_excerpt())
3949 {
3950 break;
3951 }
3952 cursor.next();
3953 }
3954 let end_region = cursor.region();
3955
3956 // Convert the metadata item's range into multibuffer coordinates.
3957 let mut start_position = start_region.range.start;
3958 let region_buffer_start = start_region.buffer_range.start;
3959 if start_region.is_main_buffer
3960 && metadata_buffer_range.start > region_buffer_start
3961 {
3962 start_position =
3963 start_position + (metadata_buffer_range.start - region_buffer_start);
3964 start_position = start_position.min(start_region.range.end);
3965 }
3966
3967 let mut end_position = self.max_position();
3968 if let Some(end_region) = &end_region {
3969 end_position = end_region.range.start;
3970 debug_assert!(end_region.is_main_buffer);
3971 let region_buffer_start = end_region.buffer_range.start;
3972 if metadata_buffer_range.end > region_buffer_start {
3973 end_position =
3974 end_position + (metadata_buffer_range.end - region_buffer_start);
3975 }
3976 end_position = end_position.min(end_region.range.end);
3977 }
3978
3979 if start_position <= query_range.end && end_position >= query_range.start {
3980 return Some((start_position..end_position, metadata, excerpt));
3981 }
3982 }
3983 // When there are no more metadata items for this excerpt, move to the next excerpt.
3984 else {
3985 current_excerpt_metadata.take();
3986 if let Some((end_excerpt, _)) = range_end
3987 && excerpt == end_excerpt
3988 {
3989 return None;
3990 }
3991 cursor.next_excerpt_forwards();
3992 }
3993 }
3994 })
3995 }
3996
3997 pub fn diff_hunk_before<T: ToOffset>(&self, position: T) -> Option<MultiBufferRow> {
3998 let offset = position.to_offset(self);
3999
4000 let mut cursor = self
4001 .cursor::<DimensionPair<MultiBufferOffset, Point>, DimensionPair<BufferOffset, Point>>(
4002 );
4003 cursor.seek(&DimensionPair {
4004 key: offset,
4005 value: None,
4006 });
4007 cursor.seek_to_start_of_current_excerpt();
4008 let excerpt = cursor.excerpt()?;
4009
4010 let excerpt_start = excerpt.range.context.start.to_offset(&excerpt.buffer);
4011 let excerpt_end = excerpt.range.context.end.to_offset(&excerpt.buffer);
4012 let current_position = match self.anchor_before(offset) {
4013 Anchor::Min => 0,
4014 Anchor::Excerpt(excerpt_anchor) => {
4015 excerpt_anchor.text_anchor().to_offset(&excerpt.buffer)
4016 }
4017 Anchor::Max => unreachable!(),
4018 };
4019
4020 if let Some(diff) = self.diffs.get(&excerpt.buffer.remote_id()) {
4021 if let Some(main_buffer) = &diff.main_buffer {
4022 for hunk in diff
4023 .hunks_intersecting_base_text_range_rev(excerpt_start..excerpt_end, main_buffer)
4024 {
4025 if hunk.diff_base_byte_range.end >= current_position {
4026 continue;
4027 }
4028 let hunk_start = excerpt.buffer.anchor_after(hunk.diff_base_byte_range.start);
4029 let start =
4030 Anchor::in_buffer(excerpt.path_key_index, hunk_start).to_point(self);
4031 return Some(MultiBufferRow(start.row));
4032 }
4033 } else {
4034 let excerpt_end = excerpt
4035 .buffer
4036 .anchor_before(excerpt_end.min(current_position));
4037 for hunk in diff.hunks_intersecting_range_rev(
4038 excerpt.range.context.start..excerpt_end,
4039 &excerpt.buffer,
4040 ) {
4041 let hunk_end = hunk.buffer_range.end.to_offset(&excerpt.buffer);
4042 if hunk_end >= current_position {
4043 continue;
4044 }
4045 let start = Anchor::in_buffer(excerpt.path_key_index, hunk.buffer_range.start)
4046 .to_point(self);
4047 return Some(MultiBufferRow(start.row));
4048 }
4049 }
4050 }
4051
4052 loop {
4053 cursor.prev_excerpt();
4054 let excerpt = cursor.excerpt()?;
4055
4056 let Some(diff) = self.diffs.get(&excerpt.buffer.remote_id()) else {
4057 continue;
4058 };
4059 if let Some(main_buffer) = &diff.main_buffer {
4060 let Some(hunk) = diff
4061 .hunks_intersecting_base_text_range_rev(
4062 excerpt.range.context.to_offset(&excerpt.buffer),
4063 main_buffer,
4064 )
4065 .next()
4066 else {
4067 continue;
4068 };
4069 let hunk_start = excerpt.buffer.anchor_after(hunk.diff_base_byte_range.start);
4070 let start = Anchor::in_buffer(excerpt.path_key_index, hunk_start).to_point(self);
4071 return Some(MultiBufferRow(start.row));
4072 } else {
4073 let Some(hunk) = diff
4074 .hunks_intersecting_range_rev(excerpt.range.context.clone(), &excerpt.buffer)
4075 .next()
4076 else {
4077 continue;
4078 };
4079 let start = Anchor::in_buffer(excerpt.path_key_index, hunk.buffer_range.start)
4080 .to_point(self);
4081 return Some(MultiBufferRow(start.row));
4082 }
4083 }
4084 }
4085
4086 pub fn has_diff_hunks(&self) -> bool {
4087 self.diffs.values().any(|diff| !diff.is_empty())
4088 }
4089
4090 pub fn is_inside_word<T: ToOffset>(
4091 &self,
4092 position: T,
4093 scope_context: Option<CharScopeContext>,
4094 ) -> bool {
4095 let position = position.to_offset(self);
4096 let classifier = self
4097 .char_classifier_at(position)
4098 .scope_context(scope_context);
4099 let next_char_kind = self.chars_at(position).next().map(|c| classifier.kind(c));
4100 let prev_char_kind = self
4101 .reversed_chars_at(position)
4102 .next()
4103 .map(|c| classifier.kind(c));
4104 prev_char_kind.zip(next_char_kind) == Some((CharKind::Word, CharKind::Word))
4105 }
4106
4107 pub fn surrounding_word<T: ToOffset>(
4108 &self,
4109 start: T,
4110 scope_context: Option<CharScopeContext>,
4111 ) -> (Range<MultiBufferOffset>, Option<CharKind>) {
4112 let mut start = start.to_offset(self);
4113 let mut end = start;
4114 let mut next_chars = self.chars_at(start).peekable();
4115 let mut prev_chars = self.reversed_chars_at(start).peekable();
4116
4117 let classifier = self.char_classifier_at(start).scope_context(scope_context);
4118
4119 let word_kind = cmp::max(
4120 prev_chars.peek().copied().map(|c| classifier.kind(c)),
4121 next_chars.peek().copied().map(|c| classifier.kind(c)),
4122 );
4123
4124 for ch in prev_chars {
4125 if Some(classifier.kind(ch)) == word_kind && ch != '\n' {
4126 start -= ch.len_utf8();
4127 } else {
4128 break;
4129 }
4130 }
4131
4132 for ch in next_chars {
4133 if Some(classifier.kind(ch)) == word_kind && ch != '\n' {
4134 end += ch.len_utf8();
4135 } else {
4136 break;
4137 }
4138 }
4139
4140 (start..end, word_kind)
4141 }
4142
4143 pub fn char_kind_before<T: ToOffset>(
4144 &self,
4145 start: T,
4146 scope_context: Option<CharScopeContext>,
4147 ) -> Option<CharKind> {
4148 let start = start.to_offset(self);
4149 let classifier = self.char_classifier_at(start).scope_context(scope_context);
4150 self.reversed_chars_at(start)
4151 .next()
4152 .map(|ch| classifier.kind(ch))
4153 }
4154
4155 pub fn is_singleton(&self) -> bool {
4156 self.singleton
4157 }
4158
4159 pub fn as_singleton(&self) -> Option<(ExcerptId, BufferId, &BufferSnapshot)> {
4160 if self.singleton {
4161 self.excerpts
4162 .iter()
4163 .next()
4164 .map(|e| (e.id, e.buffer_id, &*e.buffer))
4165 } else {
4166 None
4167 }
4168 }
4169
4170 pub fn len(&self) -> MultiBufferOffset {
4171 self.diff_transforms.summary().output.len
4172 }
4173
4174 pub fn max_position<MBD: MultiBufferDimension>(&self) -> MBD {
4175 MBD::from_summary(&self.text_summary())
4176 }
4177
4178 pub fn is_empty(&self) -> bool {
4179 self.diff_transforms.summary().output.len == MultiBufferOffset(0)
4180 }
4181
4182 pub fn widest_line_number(&self) -> u32 {
4183 // widest_line_number is 0-based, so 1 is added to get the displayed line number.
4184 self.excerpts.summary().widest_line_number + 1
4185 }
4186
4187 pub fn bytes_in_range<T: ToOffset>(&self, range: Range<T>) -> MultiBufferBytes<'_> {
4188 let range = range.start.to_offset(self)..range.end.to_offset(self);
4189 let mut excerpts = self.cursor::<MultiBufferOffset, BufferOffset>();
4190 excerpts.seek(&range.start);
4191
4192 let mut chunk;
4193 let mut has_trailing_newline;
4194 let excerpt_bytes;
4195 if let Some(region) = excerpts.region() {
4196 let mut bytes = region.buffer.bytes_in_range(
4197 region.buffer_range.start + (range.start - region.range.start)
4198 ..(region.buffer_range.start + (range.end - region.range.start))
4199 .min(region.buffer_range.end),
4200 );
4201 chunk = bytes.next().unwrap_or(&[][..]);
4202 excerpt_bytes = Some(bytes);
4203 has_trailing_newline = region.has_trailing_newline && range.end >= region.range.end;
4204 if chunk.is_empty() && has_trailing_newline {
4205 chunk = b"\n";
4206 has_trailing_newline = false;
4207 }
4208 } else {
4209 chunk = &[][..];
4210 excerpt_bytes = None;
4211 has_trailing_newline = false;
4212 };
4213
4214 MultiBufferBytes {
4215 range,
4216 cursor: excerpts,
4217 excerpt_bytes,
4218 has_trailing_newline,
4219 chunk,
4220 }
4221 }
4222
4223 pub fn reversed_bytes_in_range<T: ToOffset>(
4224 &self,
4225 range: Range<T>,
4226 ) -> ReversedMultiBufferBytes<'_> {
4227 let range = range.start.to_offset(self)..range.end.to_offset(self);
4228 let mut chunks = self.reversed_chunks_in_range(range.clone());
4229 let chunk = chunks.next().map_or(&[][..], |c| c.as_bytes());
4230 ReversedMultiBufferBytes {
4231 range,
4232 chunks,
4233 chunk,
4234 }
4235 }
4236
4237 pub fn row_infos(&self, start_row: MultiBufferRow) -> MultiBufferRows<'_> {
4238 let mut cursor = self.cursor::<Point, Point>();
4239 cursor.seek(&Point::new(start_row.0, 0));
4240 let mut result = MultiBufferRows {
4241 point: Point::new(0, 0),
4242 is_empty: self.excerpts.is_empty(),
4243 is_singleton: self.is_singleton(),
4244 cursor,
4245 };
4246 result.seek(start_row);
4247 result
4248 }
4249
4250 pub fn chunks<T: ToOffset>(
4251 &self,
4252 range: Range<T>,
4253 language_aware: bool,
4254 ) -> MultiBufferChunks<'_> {
4255 let mut chunks = MultiBufferChunks {
4256 excerpt_offset_range: ExcerptDimension(MultiBufferOffset::ZERO)
4257 ..ExcerptDimension(MultiBufferOffset::ZERO),
4258 range: MultiBufferOffset::ZERO..MultiBufferOffset::ZERO,
4259 excerpts: self.excerpts.cursor(()),
4260 diff_transforms: self.diff_transforms.cursor(()),
4261 diffs: &self.diffs,
4262 diff_base_chunks: None,
4263 excerpt_chunks: None,
4264 buffer_chunk: None,
4265 language_aware,
4266 };
4267 let range = range.start.to_offset(self)..range.end.to_offset(self);
4268 chunks.seek(range);
4269 chunks
4270 }
4271
4272 pub fn clip_offset(&self, offset: MultiBufferOffset, bias: Bias) -> MultiBufferOffset {
4273 self.clip_dimension(offset, bias, text::BufferSnapshot::clip_offset)
4274 }
4275
4276 pub fn clip_point(&self, point: Point, bias: Bias) -> Point {
4277 self.clip_dimension(point, bias, text::BufferSnapshot::clip_point)
4278 }
4279
4280 pub fn clip_offset_utf16(
4281 &self,
4282 offset: MultiBufferOffsetUtf16,
4283 bias: Bias,
4284 ) -> MultiBufferOffsetUtf16 {
4285 self.clip_dimension(offset, bias, text::BufferSnapshot::clip_offset_utf16)
4286 }
4287
4288 pub fn clip_point_utf16(&self, point: Unclipped<PointUtf16>, bias: Bias) -> PointUtf16 {
4289 self.clip_dimension(point.0, bias, |buffer, point, bias| {
4290 buffer.clip_point_utf16(Unclipped(point), bias)
4291 })
4292 }
4293
4294 pub fn offset_to_point(&self, offset: MultiBufferOffset) -> Point {
4295 self.convert_dimension(offset, text::BufferSnapshot::offset_to_point)
4296 }
4297
4298 pub fn offset_to_point_utf16(&self, offset: MultiBufferOffset) -> PointUtf16 {
4299 self.convert_dimension(offset, text::BufferSnapshot::offset_to_point_utf16)
4300 }
4301
4302 pub fn point_to_point_utf16(&self, point: Point) -> PointUtf16 {
4303 self.convert_dimension(point, text::BufferSnapshot::point_to_point_utf16)
4304 }
4305
4306 pub fn point_utf16_to_point(&self, point: PointUtf16) -> Point {
4307 self.convert_dimension(point, text::BufferSnapshot::point_utf16_to_point)
4308 }
4309
4310 #[instrument(skip_all)]
4311 pub fn point_to_offset(&self, point: Point) -> MultiBufferOffset {
4312 self.convert_dimension(point, text::BufferSnapshot::point_to_offset)
4313 }
4314
4315 pub fn point_to_offset_utf16(&self, point: Point) -> MultiBufferOffsetUtf16 {
4316 self.convert_dimension(point, text::BufferSnapshot::point_to_offset_utf16)
4317 }
4318
4319 pub fn offset_utf16_to_offset(&self, offset: MultiBufferOffsetUtf16) -> MultiBufferOffset {
4320 self.convert_dimension(offset, text::BufferSnapshot::offset_utf16_to_offset)
4321 }
4322
4323 pub fn offset_to_offset_utf16(&self, offset: MultiBufferOffset) -> MultiBufferOffsetUtf16 {
4324 self.convert_dimension(offset, text::BufferSnapshot::offset_to_offset_utf16)
4325 }
4326
4327 pub fn point_utf16_to_offset(&self, point: PointUtf16) -> MultiBufferOffset {
4328 self.convert_dimension(point, text::BufferSnapshot::point_utf16_to_offset)
4329 }
4330
4331 pub fn point_utf16_to_offset_utf16(&self, point: PointUtf16) -> MultiBufferOffsetUtf16 {
4332 self.convert_dimension(point, text::BufferSnapshot::point_utf16_to_offset_utf16)
4333 }
4334
4335 fn clip_dimension<MBD, BD>(
4336 &self,
4337 position: MBD,
4338 bias: Bias,
4339 clip_buffer_position: fn(&text::BufferSnapshot, BD, Bias) -> BD,
4340 ) -> MBD
4341 where
4342 MBD: MultiBufferDimension + Ord + Sub + ops::AddAssign<<MBD as Sub>::Output>,
4343 BD: TextDimension + Sub<Output = <MBD as Sub>::Output> + AddAssign<<MBD as Sub>::Output>,
4344 {
4345 let mut cursor = self.cursor::<MBD, BD>();
4346 cursor.seek(&position);
4347 if let Some(region) = cursor.region() {
4348 if position >= region.range.end {
4349 return region.range.end;
4350 }
4351 let overshoot = position - region.range.start;
4352 let mut buffer_position = region.buffer_range.start;
4353 buffer_position += overshoot;
4354 let clipped_buffer_position =
4355 clip_buffer_position(region.buffer, buffer_position, bias);
4356 let mut position = region.range.start;
4357 position += clipped_buffer_position - region.buffer_range.start;
4358 position
4359 } else {
4360 self.max_position()
4361 }
4362 }
4363
4364 #[instrument(skip_all)]
4365 fn convert_dimension<MBR1, MBR2, BR1, BR2>(
4366 &self,
4367 key: MBR1,
4368 convert_buffer_dimension: fn(&text::BufferSnapshot, BR1) -> BR2,
4369 ) -> MBR2
4370 where
4371 MBR1: MultiBufferDimension + Ord + Sub + ops::AddAssign<<MBR1 as Sub>::Output>,
4372 BR1: TextDimension + Sub<Output = <MBR1 as Sub>::Output> + AddAssign<<MBR1 as Sub>::Output>,
4373 MBR2: MultiBufferDimension + Ord + Sub + ops::AddAssign<<MBR2 as Sub>::Output>,
4374 BR2: TextDimension + Sub<Output = <MBR2 as Sub>::Output> + AddAssign<<MBR2 as Sub>::Output>,
4375 {
4376 let mut cursor = self.cursor::<DimensionPair<MBR1, MBR2>, DimensionPair<BR1, BR2>>();
4377 cursor.seek(&DimensionPair { key, value: None });
4378 if let Some(region) = cursor.region() {
4379 if key >= region.range.end.key {
4380 return region.range.end.value.unwrap();
4381 }
4382 let start_key = region.range.start.key;
4383 let start_value = region.range.start.value.unwrap();
4384 let buffer_start_key = region.buffer_range.start.key;
4385 let buffer_start_value = region.buffer_range.start.value.unwrap();
4386 let mut buffer_key = buffer_start_key;
4387 buffer_key += key - start_key;
4388 let buffer_value = convert_buffer_dimension(region.buffer, buffer_key);
4389 let mut result = start_value;
4390 result += buffer_value - buffer_start_value;
4391 result
4392 } else {
4393 self.max_position()
4394 }
4395 }
4396
4397 pub fn point_to_buffer_offset<T: ToOffset>(
4398 &self,
4399 point: T,
4400 ) -> Option<(&BufferSnapshot, BufferOffset)> {
4401 let offset = point.to_offset(self);
4402 let mut cursor = self.cursor::<MultiBufferOffset, BufferOffset>();
4403 cursor.seek(&offset);
4404 let region = cursor.region()?;
4405 let overshoot = offset - region.range.start;
4406 let buffer_offset = region.buffer_range.start + overshoot;
4407 if buffer_offset == BufferOffset(region.buffer.len() + 1)
4408 && region.has_trailing_newline
4409 && !region.is_main_buffer
4410 {
4411 let main_buffer_position = cursor.main_buffer_position()?;
4412 let buffer_snapshot = &cursor.excerpt()?.buffer;
4413 return Some((buffer_snapshot, main_buffer_position));
4414 } else if buffer_offset > BufferOffset(region.buffer.len()) {
4415 return None;
4416 }
4417 Some((region.buffer, buffer_offset))
4418 }
4419
4420 pub fn point_to_buffer_point(&self, point: Point) -> Option<(&BufferSnapshot, Point)> {
4421 let mut cursor = self.cursor::<Point, Point>();
4422 cursor.seek(&point);
4423 let region = cursor.region()?;
4424 let overshoot = point - region.range.start;
4425 let buffer_point = region.buffer_range.start + overshoot;
4426 let excerpt = cursor.excerpt()?;
4427 if buffer_point == region.buffer.max_point() + Point::new(1, 0)
4428 && region.has_trailing_newline
4429 && !region.is_main_buffer
4430 {
4431 return Some((&excerpt.buffer, cursor.main_buffer_position()?));
4432 } else if buffer_point > region.buffer.max_point() {
4433 return None;
4434 }
4435 Some((region.buffer, buffer_point))
4436 }
4437
4438 pub fn suggested_indents(
4439 &self,
4440 rows: impl IntoIterator<Item = u32>,
4441 cx: &App,
4442 ) -> BTreeMap<MultiBufferRow, IndentSize> {
4443 let mut result = BTreeMap::new();
4444 self.suggested_indents_callback(
4445 rows,
4446 &mut |row, indent| {
4447 result.insert(row, indent);
4448 ControlFlow::Continue(())
4449 },
4450 cx,
4451 );
4452 result
4453 }
4454
4455 // move this to be a generator once those are a thing
4456 pub fn suggested_indents_callback(
4457 &self,
4458 rows: impl IntoIterator<Item = u32>,
4459 cb: &mut dyn FnMut(MultiBufferRow, IndentSize) -> ControlFlow<()>,
4460 cx: &App,
4461 ) {
4462 let mut rows_for_excerpt = Vec::new();
4463 let mut cursor = self.cursor::<Point, Point>();
4464 let mut rows = rows.into_iter().peekable();
4465 let mut prev_row = u32::MAX;
4466 let mut prev_language_indent_size = IndentSize::default();
4467
4468 while let Some(row) = rows.next() {
4469 cursor.seek(&Point::new(row, 0));
4470 let Some(region) = cursor.region() else {
4471 continue;
4472 };
4473
4474 // Retrieve the language and indent size once for each disjoint region being indented.
4475 let single_indent_size = if row.saturating_sub(1) == prev_row {
4476 prev_language_indent_size
4477 } else {
4478 region
4479 .buffer
4480 .language_indent_size_at(Point::new(row, 0), cx)
4481 };
4482 prev_language_indent_size = single_indent_size;
4483 prev_row = row;
4484
4485 let start_buffer_row = region.buffer_range.start.row;
4486 let start_multibuffer_row = region.range.start.row;
4487 let end_multibuffer_row = region.range.end.row;
4488
4489 rows_for_excerpt.push(row);
4490 while let Some(next_row) = rows.peek().copied() {
4491 if end_multibuffer_row > next_row {
4492 rows_for_excerpt.push(next_row);
4493 rows.next();
4494 } else {
4495 break;
4496 }
4497 }
4498
4499 let buffer_rows = rows_for_excerpt
4500 .drain(..)
4501 .map(|row| start_buffer_row + row - start_multibuffer_row);
4502 let buffer_indents = region
4503 .buffer
4504 .suggested_indents(buffer_rows, single_indent_size);
4505 for (row, indent) in buffer_indents {
4506 if cb(
4507 MultiBufferRow(start_multibuffer_row + row - start_buffer_row),
4508 indent,
4509 )
4510 .is_break()
4511 {
4512 return;
4513 }
4514 }
4515 }
4516 }
4517
4518 pub fn indent_size_for_line(&self, row: MultiBufferRow) -> IndentSize {
4519 if let Some((buffer, range)) = self.buffer_line_for_row(row) {
4520 let mut size = buffer.indent_size_for_line(range.start.row);
4521 size.len = size
4522 .len
4523 .min(range.end.column)
4524 .saturating_sub(range.start.column);
4525 size
4526 } else {
4527 IndentSize::spaces(0)
4528 }
4529 }
4530
4531 pub fn line_indent_for_row(&self, row: MultiBufferRow) -> LineIndent {
4532 if let Some((buffer, range)) = self.buffer_line_for_row(row) {
4533 LineIndent::from_iter(buffer.text_for_range(range).flat_map(|s| s.chars()))
4534 } else {
4535 LineIndent::spaces(0)
4536 }
4537 }
4538
4539 pub fn indent_and_comment_for_line(&self, row: MultiBufferRow, cx: &App) -> String {
4540 let mut indent = self.indent_size_for_line(row).chars().collect::<String>();
4541
4542 if self.language_settings(cx).extend_comment_on_newline
4543 && let Some(language_scope) = self.language_scope_at(Point::new(row.0, 0))
4544 {
4545 let delimiters = language_scope.line_comment_prefixes();
4546 for delimiter in delimiters {
4547 if *self
4548 .chars_at(Point::new(row.0, indent.len() as u32))
4549 .take(delimiter.chars().count())
4550 .collect::<String>()
4551 .as_str()
4552 == **delimiter
4553 {
4554 indent.push_str(delimiter);
4555 break;
4556 }
4557 }
4558 }
4559
4560 indent
4561 }
4562
4563 pub fn is_line_whitespace_upto<T>(&self, position: T) -> bool
4564 where
4565 T: ToOffset,
4566 {
4567 for char in self.reversed_chars_at(position) {
4568 if !char.is_whitespace() {
4569 return false;
4570 }
4571 if char == '\n' {
4572 return true;
4573 }
4574 }
4575 true
4576 }
4577
4578 pub fn prev_non_blank_row(&self, mut row: MultiBufferRow) -> Option<MultiBufferRow> {
4579 while row.0 > 0 {
4580 row.0 -= 1;
4581 if !self.is_line_blank(row) {
4582 return Some(row);
4583 }
4584 }
4585 None
4586 }
4587
4588 pub fn line_len(&self, row: MultiBufferRow) -> u32 {
4589 if let Some((_, range)) = self.buffer_line_for_row(row) {
4590 range.end.column - range.start.column
4591 } else {
4592 0
4593 }
4594 }
4595
4596 pub fn buffer_line_for_row(
4597 &self,
4598 row: MultiBufferRow,
4599 ) -> Option<(&BufferSnapshot, Range<Point>)> {
4600 let mut cursor = self.cursor::<Point, Point>();
4601 let point = Point::new(row.0, 0);
4602 cursor.seek(&point);
4603 let region = cursor.region()?;
4604 let overshoot = point.min(region.range.end) - region.range.start;
4605 let buffer_point = region.buffer_range.start + overshoot;
4606 if buffer_point.row > region.buffer_range.end.row {
4607 return None;
4608 }
4609 let line_start = Point::new(buffer_point.row, 0).max(region.buffer_range.start);
4610 let line_end = Point::new(buffer_point.row, region.buffer.line_len(buffer_point.row))
4611 .min(region.buffer_range.end);
4612 Some((region.buffer, line_start..line_end))
4613 }
4614
4615 pub fn max_point(&self) -> Point {
4616 self.text_summary().lines
4617 }
4618
4619 pub fn max_row(&self) -> MultiBufferRow {
4620 MultiBufferRow(self.text_summary().lines.row)
4621 }
4622
4623 pub fn text_summary(&self) -> MBTextSummary {
4624 self.diff_transforms.summary().output
4625 }
4626
4627 pub fn text_summary_for_range<MBD, O>(&self, range: Range<O>) -> MBD
4628 where
4629 MBD: MultiBufferDimension + AddAssign,
4630 O: ToOffset,
4631 {
4632 let range = range.start.to_offset(self)..range.end.to_offset(self);
4633 let mut cursor = self
4634 .diff_transforms
4635 .cursor::<Dimensions<MultiBufferOffset, ExcerptOffset>>(());
4636 cursor.seek(&range.start, Bias::Right);
4637
4638 let Some(first_transform) = cursor.item() else {
4639 return MBD::from_summary(&MBTextSummary::default());
4640 };
4641
4642 let diff_transform_start = cursor.start().0;
4643 let diff_transform_end = cursor.end().0;
4644 let diff_start = range.start;
4645 let start_overshoot = diff_start - diff_transform_start;
4646 let end_overshoot = std::cmp::min(range.end, diff_transform_end) - diff_transform_start;
4647
4648 let mut result = match first_transform {
4649 DiffTransform::BufferContent { .. } => {
4650 let excerpt_start = cursor.start().1 + start_overshoot;
4651 let excerpt_end = cursor.start().1 + end_overshoot;
4652 self.text_summary_for_excerpt_offset_range(excerpt_start..excerpt_end)
4653 }
4654 DiffTransform::DeletedHunk {
4655 buffer_id,
4656 base_text_byte_range,
4657 has_trailing_newline,
4658 ..
4659 } => {
4660 let buffer_start = base_text_byte_range.start + start_overshoot;
4661 let mut buffer_end = base_text_byte_range.start + end_overshoot;
4662 let Some(base_text) = self.diffs.get(buffer_id).map(|diff| diff.base_text()) else {
4663 panic!("{:?} is in non-existent deleted hunk", range.start)
4664 };
4665
4666 let include_trailing_newline =
4667 *has_trailing_newline && range.end >= diff_transform_end;
4668 if include_trailing_newline {
4669 buffer_end -= 1;
4670 }
4671
4672 let mut summary = base_text
4673 .text_summary_for_range::<MBD::TextDimension, _>(buffer_start..buffer_end);
4674
4675 if include_trailing_newline {
4676 summary.add_assign(&<MBD::TextDimension>::from_text_summary(
4677 &TextSummary::newline(),
4678 ))
4679 }
4680
4681 let mut result = MBD::default();
4682 result.add_text_dim(&summary);
4683 result
4684 }
4685 };
4686 if range.end < diff_transform_end {
4687 return result;
4688 }
4689
4690 cursor.next();
4691 result.add_mb_text_summary(
4692 &cursor
4693 .summary::<_, OutputDimension<_>>(&range.end, Bias::Right)
4694 .0,
4695 );
4696
4697 let Some(last_transform) = cursor.item() else {
4698 return result;
4699 };
4700
4701 let overshoot = range.end - cursor.start().0;
4702 let suffix = match last_transform {
4703 DiffTransform::BufferContent { .. } => {
4704 let end = cursor.start().1 + overshoot;
4705 self.text_summary_for_excerpt_offset_range::<MBD>(cursor.start().1..end)
4706 }
4707 DiffTransform::DeletedHunk {
4708 base_text_byte_range,
4709 buffer_id,
4710 has_trailing_newline,
4711 ..
4712 } => {
4713 let buffer_end = base_text_byte_range.start + overshoot;
4714 let Some(base_text) = self.diffs.get(buffer_id).map(|diff| diff.base_text()) else {
4715 panic!("{:?} is in non-existent deleted hunk", range.end)
4716 };
4717
4718 let mut suffix = base_text.text_summary_for_range::<MBD::TextDimension, _>(
4719 base_text_byte_range.start..buffer_end,
4720 );
4721 if *has_trailing_newline && buffer_end == base_text_byte_range.end + 1 {
4722 suffix.add_assign(&<MBD::TextDimension>::from_text_summary(
4723 &TextSummary::from("\n"),
4724 ))
4725 }
4726
4727 let mut result = MBD::default();
4728 result.add_text_dim(&suffix);
4729 result
4730 }
4731 };
4732
4733 result += suffix;
4734 result
4735 }
4736
4737 fn text_summary_for_excerpt_offset_range<MBD>(&self, mut range: Range<ExcerptOffset>) -> MBD
4738 where
4739 MBD: MultiBufferDimension + AddAssign,
4740 {
4741 let mut summary = MBD::default();
4742 let mut cursor = self.excerpts.cursor::<ExcerptOffset>(());
4743 cursor.seek(&range.start, Bias::Right);
4744 if let Some(excerpt) = cursor.item() {
4745 let mut end_before_newline = cursor.end();
4746 if excerpt.has_trailing_newline {
4747 end_before_newline -= 1;
4748 }
4749
4750 let excerpt_start = excerpt.range.context.start.to_offset(&excerpt.buffer);
4751 let start_in_excerpt = excerpt_start + (range.start - *cursor.start());
4752 let end_in_excerpt =
4753 excerpt_start + (cmp::min(end_before_newline, range.end) - *cursor.start());
4754 summary.add_text_dim(
4755 &excerpt
4756 .buffer
4757 .text_summary_for_range::<MBD::TextDimension, _>(
4758 start_in_excerpt..end_in_excerpt,
4759 ),
4760 );
4761
4762 if range.end > end_before_newline {
4763 summary.add_mb_text_summary(&MBTextSummary::from(TextSummary::newline()));
4764 }
4765
4766 cursor.next();
4767 }
4768
4769 if range.end > *cursor.start() {
4770 summary += cursor
4771 .summary::<_, ExcerptDimension<MBD>>(&range.end, Bias::Right)
4772 .0;
4773 if let Some(excerpt) = cursor.item() {
4774 range.end = cmp::max(*cursor.start(), range.end);
4775
4776 let excerpt_start = excerpt.range.context.start.to_offset(&excerpt.buffer);
4777 let end_in_excerpt = excerpt_start + (range.end - *cursor.start());
4778 summary.add_text_dim(
4779 &excerpt
4780 .buffer
4781 .text_summary_for_range::<MBD::TextDimension, _>(
4782 excerpt_start..end_in_excerpt,
4783 ),
4784 );
4785 }
4786 }
4787
4788 summary
4789 }
4790
4791 pub fn summary_for_anchor<MBD>(&self, anchor: &Anchor) -> MBD
4792 where
4793 MBD: MultiBufferDimension
4794 + Ord
4795 + Sub<Output = MBD::TextDimension>
4796 + Sub<MBD::TextDimension, Output = MBD>
4797 + AddAssign<MBD::TextDimension>
4798 + Add<MBD::TextDimension, Output = MBD>,
4799 MBD::TextDimension: Sub<Output = MBD::TextDimension> + Ord,
4800 {
4801 let target = anchor.seek_target(self);
4802 let anchor = match anchor {
4803 Anchor::Min => {
4804 return MBD::default();
4805 }
4806 Anchor::Excerpt(excerpt_anchor) => excerpt_anchor,
4807 Anchor::Max => {
4808 return MBD::from_summary(&self.text_summary());
4809 }
4810 };
4811
4812 let (start, _, item) = self
4813 .excerpts
4814 .find::<ExcerptSummary, _>((), &target, Bias::Left);
4815 let start = MBD::from_summary(&start.text);
4816
4817 let excerpt_start_position = ExcerptDimension(start);
4818 if self.diff_transforms.is_empty() {
4819 if let Some(excerpt) = item {
4820 if !excerpt.contains(anchor) {
4821 return excerpt_start_position.0;
4822 }
4823 let excerpt_buffer_start = excerpt
4824 .range
4825 .context
4826 .start
4827 .summary::<MBD::TextDimension>(&excerpt.buffer);
4828 let excerpt_buffer_end = excerpt
4829 .range
4830 .context
4831 .end
4832 .summary::<MBD::TextDimension>(&excerpt.buffer);
4833 let buffer_summary = anchor
4834 .text_anchor()
4835 .summary::<MBD::TextDimension>(&excerpt.buffer);
4836 let summary = cmp::min(excerpt_buffer_end, buffer_summary);
4837 let mut position = excerpt_start_position;
4838 if summary > excerpt_buffer_start {
4839 position += summary - excerpt_buffer_start;
4840 }
4841
4842 position.0
4843 } else {
4844 excerpt_start_position.0
4845 }
4846 } else {
4847 let mut diff_transforms_cursor = self
4848 .diff_transforms
4849 .cursor::<Dimensions<ExcerptDimension<MBD>, OutputDimension<MBD>>>(());
4850 diff_transforms_cursor.next();
4851
4852 if let Some(excerpt) = item {
4853 if !excerpt.contains(anchor) {
4854 return self.summary_for_excerpt_position_without_hunks(
4855 Bias::Left,
4856 excerpt_start_position,
4857 &mut diff_transforms_cursor,
4858 );
4859 }
4860 let excerpt_buffer_start = excerpt
4861 .range
4862 .context
4863 .start
4864 .summary::<MBD::TextDimension>(&excerpt.buffer);
4865 let excerpt_buffer_end = excerpt
4866 .range
4867 .context
4868 .end
4869 .summary::<MBD::TextDimension>(&excerpt.buffer);
4870 let buffer_summary = anchor
4871 .text_anchor()
4872 .summary::<MBD::TextDimension>(&excerpt.buffer);
4873 let summary = cmp::min(excerpt_buffer_end, buffer_summary);
4874 let mut position = excerpt_start_position;
4875 if summary > excerpt_buffer_start {
4876 position += summary - excerpt_buffer_start;
4877 }
4878
4879 if diff_transforms_cursor.start().0 < position {
4880 diff_transforms_cursor.seek_forward(&position, Bias::Left);
4881 }
4882 self.summary_for_anchor_with_excerpt_position(
4883 *anchor,
4884 position,
4885 &mut diff_transforms_cursor,
4886 &excerpt.buffer,
4887 )
4888 } else {
4889 diff_transforms_cursor.seek_forward(&excerpt_start_position, Bias::Left);
4890 self.summary_for_excerpt_position_without_hunks(
4891 Bias::Right,
4892 excerpt_start_position,
4893 &mut diff_transforms_cursor,
4894 )
4895 }
4896 }
4897 }
4898
4899 /// Maps an anchor's excerpt-space position to its output-space position by
4900 /// walking the diff transforms. The cursor is shared across consecutive
4901 /// calls, so it may already be partway through the transform list.
4902 fn summary_for_anchor_with_excerpt_position<MBD>(
4903 &self,
4904 anchor: ExcerptAnchor,
4905 excerpt_position: ExcerptDimension<MBD>,
4906 diff_transforms: &mut Cursor<
4907 DiffTransform,
4908 Dimensions<ExcerptDimension<MBD>, OutputDimension<MBD>>,
4909 >,
4910 excerpt_buffer: &text::BufferSnapshot,
4911 ) -> MBD
4912 where
4913 MBD: MultiBufferDimension + Ord + Sub + AddAssign<<MBD as Sub>::Output>,
4914 {
4915 loop {
4916 let transform_end_position = diff_transforms.end().0;
4917 let item = diff_transforms.item();
4918 let at_transform_end = transform_end_position == excerpt_position && item.is_some();
4919
4920 // A right-biased anchor at a transform boundary belongs to the
4921 // *next* transform, so advance past the current one.
4922 if anchor.bias == Bias::Right && at_transform_end {
4923 diff_transforms.next();
4924 continue;
4925 }
4926
4927 let mut position = diff_transforms.start().1;
4928 match item {
4929 Some(DiffTransform::DeletedHunk {
4930 buffer_id,
4931 base_text_byte_range,
4932 hunk_info,
4933 ..
4934 }) => {
4935 if let Some(diff_base_anchor) = anchor.diff_base_anchor
4936 && let Some(base_text) =
4937 self.diffs.get(buffer_id).map(|diff| diff.base_text())
4938 && diff_base_anchor.is_valid(&base_text)
4939 {
4940 // The anchor carries a diff-base position — resolve it
4941 // to a location inside the deleted hunk.
4942 let base_text_offset = diff_base_anchor.to_offset(base_text);
4943 if base_text_offset >= base_text_byte_range.start
4944 && base_text_offset <= base_text_byte_range.end
4945 {
4946 let position_in_hunk = base_text
4947 .text_summary_for_range::<MBD::TextDimension, _>(
4948 base_text_byte_range.start..base_text_offset,
4949 );
4950 position.0.add_text_dim(&position_in_hunk);
4951 } else if at_transform_end {
4952 // diff_base offset falls outside this hunk's range;
4953 // advance to see if the next transform is a better fit.
4954 diff_transforms.next();
4955 continue;
4956 }
4957 } else if at_transform_end
4958 && anchor
4959 .text_anchor()
4960 .cmp(&hunk_info.hunk_start_anchor, excerpt_buffer)
4961 .is_gt()
4962 {
4963 // The anchor has no (valid) diff-base position, so it
4964 // belongs in the buffer content, not in the deleted
4965 // hunk. However, after an edit deletes the text between
4966 // the hunk boundary and this anchor, both resolve to
4967 // the same excerpt_position—landing us here on the
4968 // DeletedHunk left behind by the shared cursor. Use the
4969 // CRDT ordering to detect that the anchor is strictly
4970 // *past* the hunk boundary and skip to the following
4971 // BufferContent.
4972 diff_transforms.next();
4973 continue;
4974 }
4975 }
4976 _ => {
4977 // On a BufferContent (or no transform). If the anchor
4978 // carries a diff_base_anchor it needs a DeletedHunk, so
4979 // advance to find one.
4980 if at_transform_end && anchor.diff_base_anchor.is_some() {
4981 diff_transforms.next();
4982 continue;
4983 }
4984 let overshoot = excerpt_position - diff_transforms.start().0;
4985 position += overshoot;
4986 }
4987 }
4988
4989 return position.0;
4990 }
4991 }
4992
4993 /// Like `resolve_summary_for_anchor` but optimized for min/max anchors.
4994 fn summary_for_excerpt_position_without_hunks<MBD>(
4995 &self,
4996 bias: Bias,
4997 excerpt_position: ExcerptDimension<MBD>,
4998 diff_transforms: &mut Cursor<
4999 DiffTransform,
5000 Dimensions<ExcerptDimension<MBD>, OutputDimension<MBD>>,
5001 >,
5002 ) -> MBD
5003 where
5004 MBD: MultiBufferDimension + Ord + Sub + AddAssign<<MBD as Sub>::Output>,
5005 {
5006 loop {
5007 let transform_end_position = diff_transforms.end().0;
5008 let item = diff_transforms.item();
5009 let at_transform_end = transform_end_position == excerpt_position && item.is_some();
5010
5011 // A right-biased anchor at a transform boundary belongs to the
5012 // *next* transform, so advance past the current one.
5013 if bias == Bias::Right && at_transform_end {
5014 diff_transforms.next();
5015 continue;
5016 }
5017
5018 let mut position = diff_transforms.start().1;
5019 if let Some(DiffTransform::BufferContent { .. }) | None = item {
5020 let overshoot = excerpt_position - diff_transforms.start().0;
5021 position += overshoot;
5022 }
5023
5024 return position.0;
5025 }
5026 }
5027
5028 fn excerpt_offset_for_anchor(&self, anchor: &Anchor) -> ExcerptOffset {
5029 let anchor = match anchor {
5030 Anchor::Min => return ExcerptOffset::default(),
5031 Anchor::Excerpt(excerpt_anchor) => excerpt_anchor,
5032 Anchor::Max => return self.excerpts.summary().len(),
5033 };
5034 let mut cursor = self.excerpts.cursor::<ExcerptSummary>(());
5035 let target = anchor.seek_target(self);
5036
5037 cursor.seek(&target, Bias::Left);
5038
5039 let mut position = cursor.start().len();
5040 if let Some(excerpt) = cursor.item()
5041 && excerpt.contains(anchor)
5042 {
5043 let excerpt_buffer_start = excerpt
5044 .buffer
5045 .offset_for_anchor(&excerpt.range.context.start);
5046 let excerpt_buffer_end = excerpt.buffer.offset_for_anchor(&excerpt.range.context.end);
5047 let buffer_position = cmp::min(
5048 excerpt_buffer_end,
5049 excerpt.buffer.offset_for_anchor(&anchor.text_anchor()),
5050 );
5051 if buffer_position > excerpt_buffer_start {
5052 position += buffer_position - excerpt_buffer_start;
5053 }
5054 }
5055 position
5056 }
5057
5058 pub fn summaries_for_anchors<'a, MBD, I>(&'a self, anchors: I) -> Vec<MBD>
5059 where
5060 MBD: MultiBufferDimension
5061 + Ord
5062 + Sub<Output = MBD::TextDimension>
5063 + AddAssign<MBD::TextDimension>,
5064 MBD::TextDimension: Sub<Output = MBD::TextDimension> + Ord,
5065 I: 'a + IntoIterator<Item = &'a Anchor>,
5066 {
5067 let mut anchors = anchors.into_iter().peekable();
5068 let mut cursor = self.excerpts.cursor::<ExcerptSummary>(());
5069 let mut diff_transforms_cursor = self
5070 .diff_transforms
5071 .cursor::<Dimensions<ExcerptDimension<MBD>, OutputDimension<MBD>>>(());
5072 diff_transforms_cursor.next();
5073
5074 let mut summaries = Vec::new();
5075 while let Some(anchor) = anchors.peek() {
5076 let target = anchor.seek_target(self);
5077 let excerpt_anchor = match anchor {
5078 Anchor::Min => {
5079 summaries.push(MBD::default());
5080 anchors.next();
5081 continue;
5082 }
5083 Anchor::Excerpt(excerpt_anchor) => excerpt_anchor,
5084 Anchor::Max => {
5085 summaries.push(MBD::from_summary(&self.text_summary()));
5086 anchors.next();
5087 continue;
5088 }
5089 };
5090
5091 cursor.seek_forward(&target, Bias::Left);
5092
5093 let excerpt_start_position = ExcerptDimension(MBD::from_summary(&cursor.start().text));
5094 if let Some(excerpt) = cursor.item() {
5095 if !excerpt.contains(&excerpt_anchor) {
5096 let position = self.summary_for_excerpt_position_without_hunks(
5097 Bias::Left,
5098 excerpt_start_position,
5099 &mut diff_transforms_cursor,
5100 );
5101 summaries.push(position);
5102 anchors.next();
5103 continue;
5104 }
5105 let excerpt_buffer_start = excerpt
5106 .range
5107 .context
5108 .start
5109 .summary::<MBD::TextDimension>(&excerpt.buffer);
5110 let excerpt_buffer_end = excerpt
5111 .range
5112 .context
5113 .end
5114 .summary::<MBD::TextDimension>(&excerpt.buffer);
5115 for (buffer_summary, excerpt_anchor) in excerpt
5116 .buffer
5117 .summaries_for_anchors_with_payload::<MBD::TextDimension, _, _>(
5118 std::iter::from_fn(|| {
5119 let excerpt_anchor = anchors.peek()?.excerpt_anchor()?;
5120 if !excerpt.contains(&excerpt_anchor) {
5121 return None;
5122 }
5123 Some((excerpt_anchor.text_anchor(), excerpt_anchor))
5124 }),
5125 )
5126 {
5127 let summary = cmp::min(excerpt_buffer_end, buffer_summary);
5128 let mut position = excerpt_start_position;
5129 if summary > excerpt_buffer_start {
5130 position += summary - excerpt_buffer_start;
5131 }
5132
5133 if diff_transforms_cursor.start().0 < position {
5134 diff_transforms_cursor.seek_forward(&position, Bias::Left);
5135 }
5136
5137 summaries.push(self.summary_for_anchor_with_excerpt_position(
5138 excerpt_anchor,
5139 position,
5140 &mut diff_transforms_cursor,
5141 &excerpt.buffer,
5142 ));
5143 }
5144 } else {
5145 diff_transforms_cursor.seek_forward(&excerpt_start_position, Bias::Left);
5146 let position = self.summary_for_excerpt_position_without_hunks(
5147 Bias::Right,
5148 excerpt_start_position,
5149 &mut diff_transforms_cursor,
5150 );
5151 summaries.push(position);
5152 anchors.next();
5153 }
5154 }
5155
5156 summaries
5157 }
5158
5159 pub fn dimensions_from_points<'a, MBD>(
5160 &'a self,
5161 points: impl 'a + IntoIterator<Item = Point>,
5162 ) -> impl 'a + Iterator<Item = MBD>
5163 where
5164 MBD: MultiBufferDimension + Sub + AddAssign<<MBD as Sub>::Output>,
5165 {
5166 let mut cursor = self.cursor::<DimensionPair<Point, MBD>, Point>();
5167 cursor.seek(&DimensionPair {
5168 key: Point::default(),
5169 value: None,
5170 });
5171 let mut points = points.into_iter();
5172 iter::from_fn(move || {
5173 let point = points.next()?;
5174
5175 cursor.seek_forward(&DimensionPair {
5176 key: point,
5177 value: None,
5178 });
5179
5180 if let Some(region) = cursor.region() {
5181 let overshoot = point - region.range.start.key;
5182 let buffer_point = region.buffer_range.start + overshoot;
5183 let mut position = region.range.start.value.unwrap();
5184 position.add_text_dim(
5185 ®ion
5186 .buffer
5187 .text_summary_for_range(region.buffer_range.start..buffer_point),
5188 );
5189 if point == region.range.end.key && region.has_trailing_newline {
5190 position.add_mb_text_summary(&MBTextSummary::from(TextSummary::newline()));
5191 }
5192 Some(position)
5193 } else {
5194 Some(MBD::from_summary(&self.text_summary()))
5195 }
5196 })
5197 }
5198
5199 pub fn refresh_anchors<'a, I>(&'a self, anchors: I) -> Vec<(usize, Anchor, bool)>
5200 where
5201 I: 'a + IntoIterator<Item = &'a Anchor>,
5202 {
5203 let mut anchors = anchors.into_iter().enumerate().peekable();
5204 let mut cursor = self.excerpts.cursor::<ExcerptSummary>(());
5205 cursor.next();
5206
5207 let mut result = Vec::new();
5208
5209 while let Some((_, anchor)) = anchors.peek() {
5210 let old_excerpt_id = anchor.excerpt_id;
5211
5212 // Find the location where this anchor's excerpt should be.
5213 let old_locator = self.excerpt_locator_for_id(old_excerpt_id);
5214 cursor.seek_forward(&Some(old_locator), Bias::Left);
5215
5216 let next_excerpt = cursor.item();
5217 let prev_excerpt = cursor.prev_item();
5218
5219 // Process all of the anchors for this excerpt.
5220 while let Some((anchor_ix, &anchor)) =
5221 anchors.next_if(|(_, anchor)| anchor.excerpt_id == old_excerpt_id)
5222 {
5223 let mut anchor = anchor;
5224
5225 // Leave min and max anchors unchanged if invalid or
5226 // if the old excerpt still exists at this location
5227 let mut kept_position = next_excerpt
5228 .is_some_and(|e| e.id == old_excerpt_id && e.contains(&anchor))
5229 || old_excerpt_id == ExcerptId::max()
5230 || old_excerpt_id == ExcerptId::min();
5231
5232 // If the old excerpt no longer exists at this location, then attempt to
5233 // find an equivalent position for this anchor in an adjacent excerpt.
5234 if !kept_position {
5235 for excerpt in [next_excerpt, prev_excerpt].iter().filter_map(|e| *e) {
5236 if excerpt.contains(&anchor) {
5237 anchor.excerpt_id = excerpt.id;
5238 kept_position = true;
5239 break;
5240 }
5241 }
5242 }
5243
5244 // If there's no adjacent excerpt that contains the anchor's position,
5245 // then report that the anchor has lost its position.
5246 if !kept_position {
5247 anchor = if let Some(excerpt) = next_excerpt {
5248 let mut text_anchor = excerpt
5249 .range
5250 .context
5251 .start
5252 .bias(anchor.text_anchor.bias, &excerpt.buffer);
5253 if text_anchor
5254 .cmp(&excerpt.range.context.end, &excerpt.buffer)
5255 .is_gt()
5256 {
5257 text_anchor = excerpt.range.context.end;
5258 }
5259 Anchor::text(excerpt.id, text_anchor)
5260 } else if let Some(excerpt) = prev_excerpt {
5261 let mut text_anchor = excerpt
5262 .range
5263 .context
5264 .end
5265 .bias(anchor.text_anchor.bias, &excerpt.buffer);
5266 if text_anchor
5267 .cmp(&excerpt.range.context.start, &excerpt.buffer)
5268 .is_lt()
5269 {
5270 text_anchor = excerpt.range.context.start;
5271 }
5272 Anchor::text(excerpt.id, text_anchor)
5273 } else if anchor.text_anchor.bias == Bias::Left {
5274 Anchor::min()
5275 } else {
5276 Anchor::max()
5277 };
5278 }
5279
5280 result.push((anchor_ix, anchor, kept_position));
5281 }
5282 }
5283 result.sort_unstable_by(|a, b| a.1.cmp(&b.1, self));
5284 result
5285 }
5286
5287 pub fn anchor_before<T: ToOffset>(&self, position: T) -> Anchor {
5288 self.anchor_at(position, Bias::Left)
5289 }
5290
5291 pub fn anchor_after<T: ToOffset>(&self, position: T) -> Anchor {
5292 self.anchor_at(position, Bias::Right)
5293 }
5294
5295 pub fn anchor_at<T: ToOffset>(&self, position: T, mut bias: Bias) -> Anchor {
5296 let offset = position.to_offset(self);
5297
5298 // Find the given position in the diff transforms. Determine the corresponding
5299 // offset in the excerpts, and whether the position is within a deleted hunk.
5300 let mut diff_transforms = self
5301 .diff_transforms
5302 .cursor::<Dimensions<MultiBufferOffset, ExcerptOffset>>(());
5303 diff_transforms.seek(&offset, Bias::Right);
5304
5305 if offset == diff_transforms.start().0
5306 && bias == Bias::Left
5307 && let Some(prev_item) = diff_transforms.prev_item()
5308 && let DiffTransform::DeletedHunk { .. } = prev_item
5309 {
5310 diff_transforms.prev();
5311 }
5312 let offset_in_transform = offset - diff_transforms.start().0;
5313 let mut excerpt_offset = diff_transforms.start().1;
5314 let mut diff_base_anchor = None;
5315 if let Some(DiffTransform::DeletedHunk {
5316 buffer_id,
5317 base_text_byte_range,
5318 has_trailing_newline,
5319 ..
5320 }) = diff_transforms.item()
5321 {
5322 let diff = self.diffs.get(buffer_id).expect("missing diff");
5323 if offset_in_transform > base_text_byte_range.len() {
5324 debug_assert!(*has_trailing_newline);
5325 bias = Bias::Right;
5326 } else {
5327 diff_base_anchor = Some(
5328 diff.base_text()
5329 .anchor_at(base_text_byte_range.start + offset_in_transform, bias),
5330 );
5331 bias = Bias::Left;
5332 }
5333 } else {
5334 excerpt_offset += MultiBufferOffset(offset_in_transform);
5335 };
5336
5337 let mut excerpts = self
5338 .excerpts
5339 .cursor::<Dimensions<ExcerptOffset, ExcerptSummary>>(());
5340 excerpts.seek(&excerpt_offset, Bias::Right);
5341 if excerpts.item().is_none() && excerpt_offset == excerpts.start().0 && bias == Bias::Left {
5342 excerpts.prev();
5343 }
5344 if let Some(excerpt) = excerpts.item() {
5345 let mut overshoot = excerpt_offset.saturating_sub(excerpts.start().0);
5346 if excerpt.has_trailing_newline && excerpt_offset == excerpts.end().0 {
5347 overshoot -= 1;
5348 bias = Bias::Right;
5349 }
5350
5351 let buffer_start = excerpt.range.context.start.to_offset(&excerpt.buffer);
5352 let text_anchor =
5353 excerpt.clip_anchor(excerpt.buffer.anchor_at(buffer_start + overshoot, bias));
5354 let anchor = ExcerptAnchor::in_buffer(excerpt.path_key_index, text_anchor);
5355 let anchor = match diff_base_anchor {
5356 Some(diff_base_anchor) => anchor.with_diff_base_anchor(diff_base_anchor),
5357 None => anchor,
5358 };
5359 anchor.into()
5360 } else if excerpt_offset == ExcerptDimension(MultiBufferOffset::ZERO) && bias == Bias::Left
5361 {
5362 Anchor::min()
5363 } else {
5364 Anchor::max()
5365 }
5366 }
5367
5368 /// Wraps the [`text::Anchor`] in a [`crate::Anchor`] if this multi-buffer is a singleton.
5369 pub fn as_singleton_anchor(&self, text_anchor: text::Anchor) -> Option<Anchor> {
5370 let (excerpt, buffer, _) = self.as_singleton()?;
5371 if text_anchor.buffer_id.is_none_or(|id| id == buffer) {
5372 Some(Anchor::in_buffer(excerpt, text_anchor))
5373 } else {
5374 None
5375 }
5376 }
5377
5378 /// Returns an anchor for the given excerpt and text anchor,
5379 /// Returns [`None`] if the excerpt_id is no longer valid or the text anchor range is out of excerpt's bounds.
5380 pub fn anchor_range_in_excerpt(
5381 &self,
5382 excerpt_id: ExcerptId,
5383 text_anchor: Range<text::Anchor>,
5384 ) -> Option<Range<Anchor>> {
5385 let excerpt = self.excerpt(self.latest_excerpt_id(excerpt_id))?;
5386
5387 Some(
5388 Self::anchor_in_excerpt_(excerpt, text_anchor.start)?
5389 ..Self::anchor_in_excerpt_(excerpt, text_anchor.end)?,
5390 )
5391 }
5392
5393 /// Returns an anchor for the given excerpt and text anchor,
5394 /// Returns [`None`] if the excerpt_id is no longer valid or the text anchor range is out of excerpt's bounds.
5395 pub fn anchor_in_excerpt(
5396 &self,
5397 excerpt_id: ExcerptId,
5398 text_anchor: text::Anchor,
5399 ) -> Option<Anchor> {
5400 let excerpt = self.excerpt(self.latest_excerpt_id(excerpt_id))?;
5401 Self::anchor_in_excerpt_(excerpt, text_anchor)
5402 }
5403
5404 /// Same as [`MultiBuffer::anchor_in_excerpt`], but more efficient than calling it multiple times.
5405 pub fn anchors_in_excerpt(
5406 &self,
5407 excerpt_id: ExcerptId,
5408 text_anchors: impl IntoIterator<Item = text::Anchor>,
5409 ) -> Option<impl Iterator<Item = Option<Anchor>>> {
5410 let excerpt = self.excerpt(self.latest_excerpt_id(excerpt_id))?;
5411 Some(
5412 text_anchors
5413 .into_iter()
5414 .map(|text_anchor| Self::anchor_in_excerpt_(excerpt, text_anchor)),
5415 )
5416 }
5417
5418 fn anchor_in_excerpt_(excerpt: &Excerpt, text_anchor: text::Anchor) -> Option<Anchor> {
5419 match text_anchor.buffer_id {
5420 Some(buffer_id) if buffer_id == excerpt.buffer.remote_id() => (),
5421 Some(_) => return None,
5422 None if text_anchor.is_max() || text_anchor.is_min() => {
5423 return Some(Anchor::in_buffer(excerpt.path_key_index, text_anchor));
5424 }
5425 None => return None,
5426 }
5427
5428 let context = &excerpt.range.context;
5429 if context.start.cmp(&text_anchor, &excerpt.buffer).is_gt()
5430 || context.end.cmp(&text_anchor, &excerpt.buffer).is_lt()
5431 {
5432 return None;
5433 }
5434
5435 Some(Anchor::in_buffer(excerpt.path_key_index, text_anchor))
5436 }
5437
5438 pub fn can_resolve(&self, anchor: &Anchor) -> bool {
5439 match anchor {
5440 // todo(lw): should be `!self.is_empty()`
5441 Anchor::Min | Anchor::Max => true,
5442 Anchor::Excerpt(excerpt_anchor) => {
5443 let Some(target) = excerpt_anchor.try_seek_target(self) else {
5444 return false;
5445 };
5446 let mut cursor = self.excerpts.cursor::<ExcerptSummary>(());
5447 cursor.seek(&target, Bias::Left);
5448 let Some(excerpt) = cursor.item() else {
5449 return false;
5450 };
5451 excerpt.buffer.can_resolve(&excerpt_anchor.text_anchor())
5452 }
5453 }
5454 }
5455
5456 pub fn excerpts(
5457 &self,
5458 ) -> impl Iterator<Item = (ExcerptId, &BufferSnapshot, ExcerptRange<text::Anchor>)> {
5459 self.excerpts
5460 .iter()
5461 .map(|excerpt| (excerpt.id, &*excerpt.buffer, excerpt.range.clone()))
5462 }
5463
5464 fn cursor<'a, MBD, BD>(&'a self) -> MultiBufferCursor<'a, MBD, BD>
5465 where
5466 MBD: MultiBufferDimension + Ord + Sub + ops::AddAssign<<MBD as Sub>::Output>,
5467 BD: TextDimension + AddAssign<<MBD as Sub>::Output>,
5468 {
5469 let excerpts = self.excerpts.cursor(());
5470 let diff_transforms = self.diff_transforms.cursor(());
5471 MultiBufferCursor {
5472 excerpts,
5473 diff_transforms,
5474 diffs: &self.diffs,
5475 cached_region: OnceCell::new(),
5476 }
5477 }
5478
5479 pub fn excerpt_before(&self, excerpt_id: ExcerptId) -> Option<MultiBufferExcerpt<'_>> {
5480 let start_locator = self.excerpt_locator_for_id(excerpt_id);
5481 let mut excerpts = self
5482 .excerpts
5483 .cursor::<Dimensions<Option<&Locator>, ExcerptOffset>>(());
5484 excerpts.seek(&Some(start_locator), Bias::Left);
5485 excerpts.prev();
5486
5487 let mut diff_transforms = self
5488 .diff_transforms
5489 .cursor::<DiffTransforms<MultiBufferOffset>>(());
5490 diff_transforms.seek(&excerpts.start().1, Bias::Left);
5491 if diff_transforms.end().excerpt_dimension < excerpts.start().1 {
5492 diff_transforms.next();
5493 }
5494
5495 let excerpt = excerpts.item()?;
5496 Some(MultiBufferExcerpt {
5497 excerpt,
5498 offset: diff_transforms.start().output_dimension.0,
5499 buffer_offset: BufferOffset(excerpt.range.context.start.to_offset(&excerpt.buffer)),
5500 excerpt_offset: excerpts.start().1,
5501 diff_transforms,
5502 })
5503 }
5504
5505 pub fn excerpt_boundaries_in_range<R, T>(
5506 &self,
5507 range: R,
5508 ) -> impl Iterator<Item = ExcerptBoundary> + '_
5509 where
5510 R: RangeBounds<T>,
5511 T: ToOffset,
5512 {
5513 let start_offset;
5514 let start = match range.start_bound() {
5515 Bound::Included(start) => {
5516 start_offset = start.to_offset(self);
5517 Bound::Included(start_offset)
5518 }
5519 Bound::Excluded(_) => {
5520 panic!("not supported")
5521 }
5522 Bound::Unbounded => {
5523 start_offset = MultiBufferOffset::ZERO;
5524 Bound::Unbounded
5525 }
5526 };
5527 let end = match range.end_bound() {
5528 Bound::Included(end) => Bound::Included(end.to_offset(self)),
5529 Bound::Excluded(end) => Bound::Excluded(end.to_offset(self)),
5530 Bound::Unbounded => Bound::Unbounded,
5531 };
5532 let bounds = (start, end);
5533 let mut cursor = self.cursor::<DimensionPair<MultiBufferOffset, Point>, BufferOffset>();
5534 cursor.seek(&DimensionPair {
5535 key: start_offset,
5536 value: None,
5537 });
5538
5539 if cursor
5540 .fetch_excerpt_with_range()
5541 .is_some_and(|(_, range)| bounds.contains(&range.start.key))
5542 {
5543 cursor.prev_excerpt();
5544 } else {
5545 cursor.seek_to_start_of_current_excerpt();
5546 }
5547 let mut prev_region = cursor
5548 .fetch_excerpt_with_range()
5549 .map(|(excerpt, _)| excerpt);
5550
5551 cursor.next_excerpt_forwards();
5552
5553 iter::from_fn(move || {
5554 loop {
5555 if self.singleton {
5556 return None;
5557 }
5558
5559 let (next_excerpt, next_range) = cursor.fetch_excerpt_with_range()?;
5560 cursor.next_excerpt_forwards();
5561 if !bounds.contains(&next_range.start.key) {
5562 prev_region = Some(next_excerpt);
5563 continue;
5564 }
5565
5566 let next_region_start = next_range.start.value.unwrap();
5567 let next_region_end = if let Some((_, range)) = cursor.fetch_excerpt_with_range() {
5568 range.start.value.unwrap()
5569 } else {
5570 self.max_point()
5571 };
5572
5573 let prev = prev_region.as_ref().map(|region| ExcerptBoundaryInfo {
5574 id: region.id,
5575 buffer: region.buffer.clone(),
5576 buffer_id: region.buffer_id,
5577 range: region.range.clone(),
5578 end_row: MultiBufferRow(next_region_start.row),
5579 });
5580
5581 let next = ExcerptBoundaryInfo {
5582 id: next_excerpt.id,
5583 buffer: next_excerpt.buffer.clone(),
5584 buffer_id: next_excerpt.buffer_id,
5585 range: next_excerpt.range.clone(),
5586 end_row: if next_excerpt.has_trailing_newline {
5587 MultiBufferRow(next_region_end.row - 1)
5588 } else {
5589 MultiBufferRow(next_region_end.row)
5590 },
5591 };
5592
5593 let row = MultiBufferRow(next_region_start.row);
5594
5595 prev_region = Some(next_excerpt);
5596
5597 return Some(ExcerptBoundary { row, prev, next });
5598 }
5599 })
5600 }
5601
5602 pub fn edit_count(&self) -> usize {
5603 self.edit_count
5604 }
5605
5606 pub fn non_text_state_update_count(&self) -> usize {
5607 self.non_text_state_update_count
5608 }
5609
5610 /// Returns the smallest enclosing bracket ranges containing the given range or
5611 /// None if no brackets contain range or the range is not contained in a single
5612 /// excerpt
5613 ///
5614 /// Can optionally pass a range_filter to filter the ranges of brackets to consider
5615 #[ztracing::instrument(skip_all)]
5616 pub fn innermost_enclosing_bracket_ranges<T: ToOffset>(
5617 &self,
5618 range: Range<T>,
5619 range_filter: Option<
5620 &dyn Fn(&BufferSnapshot, Range<BufferOffset>, Range<BufferOffset>) -> bool,
5621 >,
5622 ) -> Option<(Range<MultiBufferOffset>, Range<MultiBufferOffset>)> {
5623 let range = range.start.to_offset(self)..range.end.to_offset(self);
5624 let mut excerpt = self.excerpt_containing(range.clone())?;
5625 let buffer = excerpt.buffer();
5626 let excerpt_buffer_range = excerpt.buffer_range();
5627
5628 // Filter to ranges contained in the excerpt
5629 let range_filter = |open: Range<usize>, close: Range<usize>| -> bool {
5630 excerpt_buffer_range.contains(&BufferOffset(open.start))
5631 && excerpt_buffer_range.contains(&BufferOffset(close.end))
5632 && range_filter.is_none_or(|filter| {
5633 filter(
5634 buffer,
5635 BufferOffset(open.start)..BufferOffset(close.end),
5636 BufferOffset(close.start)..BufferOffset(close.end),
5637 )
5638 })
5639 };
5640
5641 let (open, close) = excerpt.buffer().innermost_enclosing_bracket_ranges(
5642 excerpt.map_range_to_buffer(range),
5643 Some(&range_filter),
5644 )?;
5645
5646 Some((
5647 excerpt.map_range_from_buffer(BufferOffset(open.start)..BufferOffset(open.end)),
5648 excerpt.map_range_from_buffer(BufferOffset(close.start)..BufferOffset(close.end)),
5649 ))
5650 }
5651
5652 /// Returns enclosing bracket ranges containing the given range or returns None if the range is
5653 /// not contained in a single excerpt
5654 pub fn enclosing_bracket_ranges<T: ToOffset>(
5655 &self,
5656 range: Range<T>,
5657 ) -> Option<impl Iterator<Item = (Range<MultiBufferOffset>, Range<MultiBufferOffset>)> + '_>
5658 {
5659 let range = range.start.to_offset(self)..range.end.to_offset(self);
5660 let mut excerpt = self.excerpt_containing(range.clone())?;
5661
5662 Some(
5663 excerpt
5664 .buffer()
5665 .enclosing_bracket_ranges(excerpt.map_range_to_buffer(range))
5666 .filter_map(move |pair| {
5667 let open_range =
5668 BufferOffset(pair.open_range.start)..BufferOffset(pair.open_range.end);
5669 let close_range =
5670 BufferOffset(pair.close_range.start)..BufferOffset(pair.close_range.end);
5671 if excerpt.contains_buffer_range(open_range.start..close_range.end) {
5672 Some((
5673 excerpt.map_range_from_buffer(open_range),
5674 excerpt.map_range_from_buffer(close_range),
5675 ))
5676 } else {
5677 None
5678 }
5679 }),
5680 )
5681 }
5682
5683 /// Returns enclosing bracket ranges containing the given range or returns None if the range is
5684 /// not contained in a single excerpt
5685 pub fn text_object_ranges<T: ToOffset>(
5686 &self,
5687 range: Range<T>,
5688 options: TreeSitterOptions,
5689 ) -> impl Iterator<Item = (Range<MultiBufferOffset>, TextObject)> + '_ {
5690 let range = range.start.to_offset(self)..range.end.to_offset(self);
5691 self.excerpt_containing(range.clone())
5692 .map(|mut excerpt| {
5693 excerpt
5694 .buffer()
5695 .text_object_ranges(excerpt.map_range_to_buffer(range), options)
5696 .filter_map(move |(range, text_object)| {
5697 let range = BufferOffset(range.start)..BufferOffset(range.end);
5698 if excerpt.contains_buffer_range(range.clone()) {
5699 Some((excerpt.map_range_from_buffer(range), text_object))
5700 } else {
5701 None
5702 }
5703 })
5704 })
5705 .into_iter()
5706 .flatten()
5707 }
5708
5709 /// Returns bracket range pairs overlapping the given `range` or returns None if the `range` is
5710 /// not contained in a single excerpt
5711 pub fn bracket_ranges<T: ToOffset>(
5712 &self,
5713 range: Range<T>,
5714 ) -> Option<impl Iterator<Item = (Range<MultiBufferOffset>, Range<MultiBufferOffset>)> + '_>
5715 {
5716 let range = range.start.to_offset(self)..range.end.to_offset(self);
5717 let mut excerpt = self.excerpt_containing(range.clone())?;
5718 Some(
5719 excerpt
5720 .buffer()
5721 .bracket_ranges(excerpt.map_range_to_buffer(range))
5722 .filter_map(move |pair| {
5723 let open_range =
5724 BufferOffset(pair.open_range.start)..BufferOffset(pair.open_range.end);
5725 let close_range =
5726 BufferOffset(pair.close_range.start)..BufferOffset(pair.close_range.end);
5727 excerpt
5728 .contains_buffer_range(open_range.start..close_range.end)
5729 .then(|| BracketMatch {
5730 open_range: excerpt.map_range_from_buffer(open_range),
5731 close_range: excerpt.map_range_from_buffer(close_range),
5732 color_index: pair.color_index,
5733 newline_only: pair.newline_only,
5734 syntax_layer_depth: pair.syntax_layer_depth,
5735 })
5736 })
5737 .map(BracketMatch::bracket_ranges),
5738 )
5739 }
5740
5741 pub fn redacted_ranges<'a, T: ToOffset>(
5742 &'a self,
5743 range: Range<T>,
5744 redaction_enabled: impl Fn(Option<&Arc<dyn File>>) -> bool + 'a,
5745 ) -> impl Iterator<Item = Range<MultiBufferOffset>> + 'a {
5746 let range = range.start.to_offset(self)..range.end.to_offset(self);
5747 self.lift_buffer_metadata(range, move |buffer, range| {
5748 if redaction_enabled(buffer.file()) {
5749 Some(buffer.redacted_ranges(range).map(|range| (range, ())))
5750 } else {
5751 None
5752 }
5753 })
5754 .map(|(range, _, _)| range)
5755 }
5756
5757 pub fn runnable_ranges(
5758 &self,
5759 range: Range<Anchor>,
5760 ) -> impl Iterator<Item = (Range<MultiBufferOffset>, language::RunnableRange)> + '_ {
5761 let range = range.start.to_offset(self)..range.end.to_offset(self);
5762 self.lift_buffer_metadata(range, move |buffer, range| {
5763 Some(
5764 buffer
5765 .runnable_ranges(range.clone())
5766 .filter(move |runnable| {
5767 runnable.run_range.start >= range.start
5768 && runnable.run_range.end < range.end
5769 })
5770 .map(|runnable| (runnable.run_range.clone(), runnable)),
5771 )
5772 })
5773 .map(|(run_range, runnable, _)| (run_range, runnable))
5774 }
5775
5776 pub fn line_indents(
5777 &self,
5778 start_row: MultiBufferRow,
5779 buffer_filter: impl Fn(&BufferSnapshot) -> bool,
5780 ) -> impl Iterator<Item = (MultiBufferRow, LineIndent, &BufferSnapshot)> {
5781 let max_point = self.max_point();
5782 let mut cursor = self.cursor::<Point, Point>();
5783 cursor.seek(&Point::new(start_row.0, 0));
5784 iter::from_fn(move || {
5785 let mut region = cursor.region()?;
5786 while !buffer_filter(®ion.excerpt.buffer) {
5787 cursor.next();
5788 region = cursor.region()?;
5789 }
5790 let region = cursor.region()?;
5791 let overshoot = start_row.0.saturating_sub(region.range.start.row);
5792 let buffer_start_row =
5793 (region.buffer_range.start.row + overshoot).min(region.buffer_range.end.row);
5794
5795 let buffer_end_row = if region.is_main_buffer
5796 && (region.has_trailing_newline || region.range.end == max_point)
5797 {
5798 region.buffer_range.end.row
5799 } else {
5800 region.buffer_range.end.row.saturating_sub(1)
5801 };
5802
5803 let line_indents = region
5804 .buffer
5805 .line_indents_in_row_range(buffer_start_row..buffer_end_row);
5806 let region_buffer_row = region.buffer_range.start.row;
5807 let region_row = region.range.start.row;
5808 let region_buffer = ®ion.excerpt.buffer;
5809 cursor.next();
5810 Some(line_indents.map(move |(buffer_row, indent)| {
5811 let row = region_row + (buffer_row - region_buffer_row);
5812 (MultiBufferRow(row), indent, region_buffer.as_ref())
5813 }))
5814 })
5815 .flatten()
5816 }
5817
5818 pub fn reversed_line_indents(
5819 &self,
5820 end_row: MultiBufferRow,
5821 buffer_filter: impl Fn(&BufferSnapshot) -> bool,
5822 ) -> impl Iterator<Item = (MultiBufferRow, LineIndent, &BufferSnapshot)> {
5823 let max_point = self.max_point();
5824 let mut cursor = self.cursor::<Point, Point>();
5825 cursor.seek(&Point::new(end_row.0, 0));
5826 iter::from_fn(move || {
5827 let mut region = cursor.region()?;
5828 while !buffer_filter(®ion.excerpt.buffer) {
5829 cursor.prev();
5830 region = cursor.region()?;
5831 }
5832 let region = cursor.region()?;
5833
5834 let buffer_start_row = region.buffer_range.start.row;
5835 let buffer_end_row = if region.is_main_buffer
5836 && (region.has_trailing_newline || region.range.end == max_point)
5837 {
5838 region.buffer_range.end.row + 1
5839 } else {
5840 region.buffer_range.end.row
5841 };
5842
5843 let overshoot = end_row.0 - region.range.start.row;
5844 let buffer_end_row =
5845 (region.buffer_range.start.row + overshoot + 1).min(buffer_end_row);
5846
5847 let line_indents = region
5848 .buffer
5849 .reversed_line_indents_in_row_range(buffer_start_row..buffer_end_row);
5850 let region_buffer_row = region.buffer_range.start.row;
5851 let region_row = region.range.start.row;
5852 let region_buffer = ®ion.excerpt.buffer;
5853 cursor.prev();
5854 Some(line_indents.map(move |(buffer_row, indent)| {
5855 let row = region_row + (buffer_row - region_buffer_row);
5856 (MultiBufferRow(row), indent, region_buffer.as_ref())
5857 }))
5858 })
5859 .flatten()
5860 }
5861
5862 pub async fn enclosing_indent(
5863 &self,
5864 mut target_row: MultiBufferRow,
5865 ) -> Option<(Range<MultiBufferRow>, LineIndent)> {
5866 let max_row = MultiBufferRow(self.max_point().row);
5867 if target_row >= max_row {
5868 return None;
5869 }
5870
5871 let mut target_indent = self.line_indent_for_row(target_row);
5872
5873 // If the current row is at the start of an indented block, we want to return this
5874 // block as the enclosing indent.
5875 if !target_indent.is_line_empty() && target_row < max_row {
5876 let next_line_indent = self.line_indent_for_row(MultiBufferRow(target_row.0 + 1));
5877 if !next_line_indent.is_line_empty()
5878 && target_indent.raw_len() < next_line_indent.raw_len()
5879 {
5880 target_indent = next_line_indent;
5881 target_row.0 += 1;
5882 }
5883 }
5884
5885 const SEARCH_ROW_LIMIT: u32 = 25000;
5886 const SEARCH_WHITESPACE_ROW_LIMIT: u32 = 2500;
5887 const YIELD_INTERVAL: u32 = 100;
5888
5889 let mut accessed_row_counter = 0;
5890
5891 // If there is a blank line at the current row, search for the next non indented lines
5892 if target_indent.is_line_empty() {
5893 let start = MultiBufferRow(target_row.0.saturating_sub(SEARCH_WHITESPACE_ROW_LIMIT));
5894 let end =
5895 MultiBufferRow((max_row.0 + 1).min(target_row.0 + SEARCH_WHITESPACE_ROW_LIMIT));
5896
5897 let mut non_empty_line_above = None;
5898 for (row, indent, _) in self.reversed_line_indents(target_row, |_| true) {
5899 if row < start {
5900 break;
5901 }
5902 accessed_row_counter += 1;
5903 if accessed_row_counter == YIELD_INTERVAL {
5904 accessed_row_counter = 0;
5905 yield_now().await;
5906 }
5907 if !indent.is_line_empty() {
5908 non_empty_line_above = Some((row, indent));
5909 break;
5910 }
5911 }
5912
5913 let mut non_empty_line_below = None;
5914 for (row, indent, _) in self.line_indents(target_row, |_| true) {
5915 if row > end {
5916 break;
5917 }
5918 accessed_row_counter += 1;
5919 if accessed_row_counter == YIELD_INTERVAL {
5920 accessed_row_counter = 0;
5921 yield_now().await;
5922 }
5923 if !indent.is_line_empty() {
5924 non_empty_line_below = Some((row, indent));
5925 break;
5926 }
5927 }
5928
5929 let (row, indent) = match (non_empty_line_above, non_empty_line_below) {
5930 (Some((above_row, above_indent)), Some((below_row, below_indent))) => {
5931 if above_indent.raw_len() >= below_indent.raw_len() {
5932 (above_row, above_indent)
5933 } else {
5934 (below_row, below_indent)
5935 }
5936 }
5937 (Some(above), None) => above,
5938 (None, Some(below)) => below,
5939 _ => return None,
5940 };
5941
5942 target_indent = indent;
5943 target_row = row;
5944 }
5945
5946 let start = MultiBufferRow(target_row.0.saturating_sub(SEARCH_ROW_LIMIT));
5947 let end = MultiBufferRow((max_row.0 + 1).min(target_row.0 + SEARCH_ROW_LIMIT));
5948
5949 let mut start_indent = None;
5950 for (row, indent, _) in self.reversed_line_indents(target_row, |_| true) {
5951 if row < start {
5952 break;
5953 }
5954 accessed_row_counter += 1;
5955 if accessed_row_counter == YIELD_INTERVAL {
5956 accessed_row_counter = 0;
5957 yield_now().await;
5958 }
5959 if !indent.is_line_empty() && indent.raw_len() < target_indent.raw_len() {
5960 start_indent = Some((row, indent));
5961 break;
5962 }
5963 }
5964 let (start_row, start_indent_size) = start_indent?;
5965
5966 let mut end_indent = (end, None);
5967 for (row, indent, _) in self.line_indents(target_row, |_| true) {
5968 if row > end {
5969 break;
5970 }
5971 accessed_row_counter += 1;
5972 if accessed_row_counter == YIELD_INTERVAL {
5973 accessed_row_counter = 0;
5974 yield_now().await;
5975 }
5976 if !indent.is_line_empty() && indent.raw_len() < target_indent.raw_len() {
5977 end_indent = (MultiBufferRow(row.0.saturating_sub(1)), Some(indent));
5978 break;
5979 }
5980 }
5981 let (end_row, end_indent_size) = end_indent;
5982
5983 let indent = if let Some(end_indent_size) = end_indent_size {
5984 if start_indent_size.raw_len() > end_indent_size.raw_len() {
5985 start_indent_size
5986 } else {
5987 end_indent_size
5988 }
5989 } else {
5990 start_indent_size
5991 };
5992
5993 Some((start_row..end_row, indent))
5994 }
5995
5996 pub fn indent_guides_in_range<T: ToPoint>(
5997 &self,
5998 range: Range<T>,
5999 ignore_disabled_for_language: bool,
6000 cx: &App,
6001 ) -> impl Iterator<Item = IndentGuide> {
6002 let range = range.start.to_point(self)..range.end.to_point(self);
6003 let start_row = MultiBufferRow(range.start.row);
6004 let end_row = MultiBufferRow(range.end.row);
6005
6006 let mut row_indents = self.line_indents(start_row, |buffer| {
6007 let settings =
6008 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx);
6009 settings.indent_guides.enabled || ignore_disabled_for_language
6010 });
6011
6012 let mut result = Vec::new();
6013 let mut indent_stack = SmallVec::<[IndentGuide; 8]>::new();
6014
6015 let mut prev_settings = None;
6016 while let Some((first_row, mut line_indent, buffer)) = row_indents.next() {
6017 if first_row > end_row {
6018 break;
6019 }
6020 let current_depth = indent_stack.len() as u32;
6021
6022 // Avoid retrieving the language settings repeatedly for every buffer row.
6023 if let Some((prev_buffer_id, _)) = &prev_settings
6024 && prev_buffer_id != &buffer.remote_id()
6025 {
6026 prev_settings.take();
6027 }
6028 let settings = &prev_settings
6029 .get_or_insert_with(|| {
6030 (
6031 buffer.remote_id(),
6032 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx),
6033 )
6034 })
6035 .1;
6036 let tab_size = settings.tab_size.get();
6037
6038 // When encountering empty, continue until found useful line indent
6039 // then add to the indent stack with the depth found
6040 let mut found_indent = false;
6041 let mut last_row = first_row;
6042 if line_indent.is_line_blank() {
6043 while !found_indent {
6044 let Some((target_row, new_line_indent, _)) = row_indents.next() else {
6045 break;
6046 };
6047 const TRAILING_ROW_SEARCH_LIMIT: u32 = 25;
6048 if target_row > MultiBufferRow(end_row.0 + TRAILING_ROW_SEARCH_LIMIT) {
6049 break;
6050 }
6051
6052 if new_line_indent.is_line_blank() {
6053 continue;
6054 }
6055 last_row = target_row.min(end_row);
6056 line_indent = new_line_indent;
6057 found_indent = true;
6058 break;
6059 }
6060 } else {
6061 found_indent = true
6062 }
6063
6064 let depth = if found_indent {
6065 line_indent.len(tab_size) / tab_size
6066 } else {
6067 0
6068 };
6069
6070 match depth.cmp(¤t_depth) {
6071 cmp::Ordering::Less => {
6072 for _ in 0..(current_depth - depth) {
6073 let mut indent = indent_stack.pop().unwrap();
6074 if last_row != first_row {
6075 // In this case, we landed on an empty row, had to seek forward,
6076 // and discovered that the indent we where on is ending.
6077 // This means that the last display row must
6078 // be on line that ends this indent range, so we
6079 // should display the range up to the first non-empty line
6080 indent.end_row = MultiBufferRow(first_row.0.saturating_sub(1));
6081 }
6082
6083 result.push(indent)
6084 }
6085 }
6086 cmp::Ordering::Greater => {
6087 for next_depth in current_depth..depth {
6088 indent_stack.push(IndentGuide {
6089 buffer_id: buffer.remote_id(),
6090 start_row: first_row,
6091 end_row: last_row,
6092 depth: next_depth,
6093 tab_size,
6094 settings: settings.indent_guides.clone(),
6095 });
6096 }
6097 }
6098 _ => {}
6099 }
6100
6101 for indent in indent_stack.iter_mut() {
6102 indent.end_row = last_row;
6103 }
6104 }
6105
6106 result.extend(indent_stack);
6107 result.into_iter()
6108 }
6109
6110 pub fn trailing_excerpt_update_count(&self) -> usize {
6111 self.trailing_excerpt_update_count
6112 }
6113
6114 pub fn file_at<T: ToOffset>(&self, point: T) -> Option<&Arc<dyn File>> {
6115 self.point_to_buffer_offset(point)
6116 .and_then(|(buffer, _)| buffer.file())
6117 }
6118
6119 pub fn language_at<T: ToOffset>(&self, offset: T) -> Option<&Arc<Language>> {
6120 self.point_to_buffer_offset(offset)
6121 .and_then(|(buffer, offset)| buffer.language_at(offset))
6122 }
6123
6124 fn language_settings<'a>(&'a self, cx: &'a App) -> Cow<'a, LanguageSettings> {
6125 self.excerpts
6126 .first()
6127 .map(|excerpt| &excerpt.buffer)
6128 .map(|buffer| {
6129 language_settings(
6130 buffer.language().map(|language| language.name()),
6131 buffer.file(),
6132 cx,
6133 )
6134 })
6135 .unwrap_or_else(move || self.language_settings_at(MultiBufferOffset::ZERO, cx))
6136 }
6137
6138 pub fn language_settings_at<'a, T: ToOffset>(
6139 &'a self,
6140 point: T,
6141 cx: &'a App,
6142 ) -> Cow<'a, LanguageSettings> {
6143 let mut language = None;
6144 let mut file = None;
6145 if let Some((buffer, offset)) = self.point_to_buffer_offset(point) {
6146 language = buffer.language_at(offset);
6147 file = buffer.file();
6148 }
6149 language_settings(language.map(|l| l.name()), file, cx)
6150 }
6151
6152 pub fn language_scope_at<T: ToOffset>(&self, point: T) -> Option<LanguageScope> {
6153 self.point_to_buffer_offset(point)
6154 .and_then(|(buffer, offset)| buffer.language_scope_at(offset))
6155 }
6156
6157 pub fn char_classifier_at<T: ToOffset>(&self, point: T) -> CharClassifier {
6158 self.point_to_buffer_offset(point)
6159 .map(|(buffer, offset)| buffer.char_classifier_at(offset))
6160 .unwrap_or_default()
6161 }
6162
6163 pub fn language_indent_size_at<T: ToOffset>(
6164 &self,
6165 position: T,
6166 cx: &App,
6167 ) -> Option<IndentSize> {
6168 let (buffer_snapshot, offset) = self.point_to_buffer_offset(position)?;
6169 Some(buffer_snapshot.language_indent_size_at(offset, cx))
6170 }
6171
6172 pub fn is_dirty(&self) -> bool {
6173 self.is_dirty
6174 }
6175
6176 pub fn has_deleted_file(&self) -> bool {
6177 self.has_deleted_file
6178 }
6179
6180 pub fn has_conflict(&self) -> bool {
6181 self.has_conflict
6182 }
6183
6184 pub fn has_diagnostics(&self) -> bool {
6185 self.excerpts
6186 .iter()
6187 .any(|excerpt| excerpt.buffer.has_diagnostics())
6188 }
6189
6190 pub fn diagnostic_group(
6191 &self,
6192 buffer_id: BufferId,
6193 group_id: usize,
6194 ) -> impl Iterator<Item = DiagnosticEntryRef<'_, Point>> + '_ {
6195 self.lift_buffer_metadata::<Point, _, _>(
6196 Point::zero()..self.max_point(),
6197 move |buffer, range| {
6198 if buffer.remote_id() != buffer_id {
6199 return None;
6200 };
6201 Some(
6202 buffer
6203 .diagnostics_in_range(range, false)
6204 .filter(move |diagnostic| diagnostic.diagnostic.group_id == group_id)
6205 .map(move |DiagnosticEntryRef { diagnostic, range }| (range, diagnostic)),
6206 )
6207 },
6208 )
6209 .map(|(range, diagnostic, _)| DiagnosticEntryRef { diagnostic, range })
6210 }
6211
6212 pub fn diagnostics_in_range<'a, MBD>(
6213 &'a self,
6214 range: Range<MBD>,
6215 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MBD>> + 'a
6216 where
6217 MBD::TextDimension: 'a
6218 + text::ToOffset
6219 + text::FromAnchor
6220 + Sub<Output = MBD::TextDimension>
6221 + fmt::Debug
6222 + ops::Add<Output = MBD::TextDimension>
6223 + ops::AddAssign
6224 + Ord,
6225 MBD: MultiBufferDimension
6226 + Ord
6227 + Sub<Output = MBD::TextDimension>
6228 + ops::Add<MBD::TextDimension, Output = MBD>
6229 + ops::AddAssign<MBD::TextDimension>
6230 + 'a,
6231 {
6232 self.lift_buffer_metadata::<MBD, _, _>(range, move |buffer, buffer_range| {
6233 Some(
6234 buffer
6235 .diagnostics_in_range(buffer_range.start..buffer_range.end, false)
6236 .map(|entry| (entry.range, entry.diagnostic)),
6237 )
6238 })
6239 .map(|(range, diagnostic, _)| DiagnosticEntryRef { diagnostic, range })
6240 }
6241
6242 pub fn diagnostics_with_buffer_ids_in_range<'a, MBD>(
6243 &'a self,
6244 range: Range<MBD>,
6245 ) -> impl Iterator<Item = (BufferId, DiagnosticEntryRef<'a, MBD>)> + 'a
6246 where
6247 MBD: MultiBufferDimension
6248 + Ord
6249 + Sub<Output = MBD::TextDimension>
6250 + ops::Add<MBD::TextDimension, Output = MBD>
6251 + ops::AddAssign<MBD::TextDimension>,
6252 MBD::TextDimension: Sub<Output = MBD::TextDimension>
6253 + ops::Add<Output = MBD::TextDimension>
6254 + text::ToOffset
6255 + text::FromAnchor
6256 + AddAssign<MBD::TextDimension>
6257 + Ord,
6258 {
6259 self.lift_buffer_metadata::<MBD, _, _>(range, move |buffer, buffer_range| {
6260 Some(
6261 buffer
6262 .diagnostics_in_range(buffer_range.start..buffer_range.end, false)
6263 .map(|entry| (entry.range, entry.diagnostic)),
6264 )
6265 })
6266 .map(|(range, diagnostic, b)| (b.buffer_id, DiagnosticEntryRef { diagnostic, range }))
6267 }
6268
6269 pub fn syntax_ancestor<T: ToOffset>(
6270 &self,
6271 range: Range<T>,
6272 ) -> Option<(tree_sitter::Node<'_>, Range<MultiBufferOffset>)> {
6273 let range = range.start.to_offset(self)..range.end.to_offset(self);
6274 let mut excerpt = self.excerpt_containing(range.clone())?;
6275 let node = excerpt
6276 .buffer()
6277 .syntax_ancestor(excerpt.map_range_to_buffer(range))?;
6278 let node_range = node.byte_range();
6279 let node_range = BufferOffset(node_range.start)..BufferOffset(node_range.end);
6280 if !excerpt.contains_buffer_range(node_range.clone()) {
6281 return None;
6282 };
6283 Some((node, excerpt.map_range_from_buffer(node_range)))
6284 }
6285
6286 pub fn outline(&self, theme: Option<&SyntaxTheme>) -> Option<Outline<Anchor>> {
6287 let (excerpt_id, _, buffer) = self.as_singleton()?;
6288 let outline = buffer.outline(theme);
6289 Some(Outline::new(
6290 outline
6291 .items
6292 .into_iter()
6293 .flat_map(|item| {
6294 Some(OutlineItem {
6295 depth: item.depth,
6296 range: self.anchor_range_in_excerpt(excerpt_id, item.range)?,
6297 source_range_for_text: self
6298 .anchor_range_in_excerpt(excerpt_id, item.source_range_for_text)?,
6299 text: item.text,
6300 highlight_ranges: item.highlight_ranges,
6301 name_ranges: item.name_ranges,
6302 body_range: item.body_range.and_then(|body_range| {
6303 self.anchor_range_in_excerpt(excerpt_id, body_range)
6304 }),
6305 annotation_range: item.annotation_range.and_then(|annotation_range| {
6306 self.anchor_range_in_excerpt(excerpt_id, annotation_range)
6307 }),
6308 })
6309 })
6310 .collect(),
6311 ))
6312 }
6313
6314 pub fn symbols_containing<T: ToOffset>(
6315 &self,
6316 offset: T,
6317 theme: Option<&SyntaxTheme>,
6318 ) -> Option<(BufferId, Vec<OutlineItem<Anchor>>)> {
6319 let anchor = self.anchor_before(offset);
6320 let excerpt @ &Excerpt {
6321 id: excerpt_id,
6322 buffer_id,
6323 ref buffer,
6324 ..
6325 } = self.excerpt(anchor.excerpt_id)?;
6326 if cfg!(debug_assertions) {
6327 match anchor.text_anchor.buffer_id {
6328 // we clearly are hitting this according to sentry, but in what situations can this occur?
6329 Some(anchor_buffer_id) => {
6330 assert_eq!(
6331 anchor_buffer_id, buffer_id,
6332 "anchor {anchor:?} does not match with resolved excerpt {excerpt:?}"
6333 )
6334 }
6335 None => assert!(anchor.is_max()),
6336 }
6337 };
6338 Some((
6339 buffer_id,
6340 buffer
6341 .symbols_containing(anchor.text_anchor, theme)
6342 .into_iter()
6343 .flat_map(|item| {
6344 Some(OutlineItem {
6345 depth: item.depth,
6346 source_range_for_text: Anchor::range_in_buffer(
6347 excerpt_id,
6348 item.source_range_for_text,
6349 ),
6350 range: Anchor::range_in_buffer(excerpt_id, item.range),
6351 text: item.text,
6352 highlight_ranges: item.highlight_ranges,
6353 name_ranges: item.name_ranges,
6354 body_range: item
6355 .body_range
6356 .map(|body_range| Anchor::range_in_buffer(excerpt_id, body_range)),
6357 annotation_range: item
6358 .annotation_range
6359 .map(|body_range| Anchor::range_in_buffer(excerpt_id, body_range)),
6360 })
6361 })
6362 .collect(),
6363 ))
6364 }
6365
6366 pub fn buffer_for_path(&self, path: &PathKey) -> Option<&BufferSnapshot> {
6367 todo!()
6368 }
6369
6370 pub fn path_for_buffer(&self, buffer_id: BufferId) -> Option<&PathKey> {
6371 self.path_keys_by_buffer.get(&buffer_id)
6372 }
6373
6374 pub fn buffer_for_id(&self, id: BufferId) -> Option<&BufferSnapshot> {
6375 self.buffer_for_path(self.path_keys_by_buffer.get(&id)?)
6376 }
6377
6378 fn try_path_for_anchor(&self, anchor: ExcerptAnchor) -> Option<PathKey> {
6379 self.path_keys_by_index.get(&anchor.path).cloned()
6380 }
6381
6382 fn path_for_anchor(&self, anchor: ExcerptAnchor) -> PathKey {
6383 self.try_path_for_anchor(anchor)
6384 .expect("invalid anchor: path was never added to multibuffer")
6385 }
6386
6387 pub fn range_for_excerpt(&self, excerpt_id: ExcerptId) -> Option<Range<Point>> {
6388 let mut cursor = self
6389 .excerpts
6390 .cursor::<Dimensions<Option<&Locator>, ExcerptPoint>>(());
6391 let locator = self.excerpt_locator_for_id(excerpt_id);
6392 let mut sought_exact = cursor.seek(&Some(locator), Bias::Left);
6393 if cursor.item().is_none() && excerpt_id == ExcerptId::max() {
6394 sought_exact = true;
6395 cursor.prev();
6396 } else if excerpt_id == ExcerptId::min() {
6397 sought_exact = true;
6398 }
6399 if sought_exact {
6400 let start = cursor.start().1;
6401 let end = cursor.end().1;
6402 let mut diff_transforms = self
6403 .diff_transforms
6404 .cursor::<Dimensions<ExcerptPoint, OutputDimension<Point>>>(());
6405 diff_transforms.seek(&start, Bias::Left);
6406 let overshoot = start - diff_transforms.start().0;
6407 let start = diff_transforms.start().1 + overshoot;
6408 diff_transforms.seek(&end, Bias::Right);
6409 let overshoot = end - diff_transforms.start().0;
6410 let end = diff_transforms.start().1 + overshoot;
6411 Some(start.0..end.0)
6412 } else {
6413 None
6414 }
6415 }
6416
6417 // todo!() sort out excerpt_containing etc.
6418 fn excerpt_at(&self, position: impl ToOffset) -> Option<&Excerpt> {
6419 todo!()
6420 }
6421
6422 /// Returns the excerpt containing range and its offset start within the multibuffer or none if `range` spans multiple excerpts
6423 pub fn excerpt_containing<T: ToOffset>(
6424 &self,
6425 range: Range<T>,
6426 ) -> Option<MultiBufferExcerpt<'_>> {
6427 let range = range.start.to_offset(self)..range.end.to_offset(self);
6428 let mut cursor = self.cursor::<MultiBufferOffset, BufferOffset>();
6429 cursor.seek(&range.start);
6430
6431 let start_excerpt = cursor.excerpt()?;
6432 if range.end != range.start {
6433 cursor.seek_forward(&range.end);
6434 if cursor.excerpt()?.id != start_excerpt.id {
6435 return None;
6436 }
6437 }
6438
6439 cursor.seek_to_start_of_current_excerpt();
6440 let region = cursor.region()?;
6441 let offset = region.range.start;
6442 let buffer_offset = start_excerpt.buffer_start_offset();
6443 let excerpt_offset = *cursor.excerpts.start();
6444 Some(MultiBufferExcerpt {
6445 diff_transforms: cursor.diff_transforms,
6446 excerpt: start_excerpt,
6447 offset,
6448 buffer_offset,
6449 excerpt_offset,
6450 })
6451 }
6452
6453 pub fn buffer_id_for_anchor(&self, anchor: Anchor) -> Option<BufferId> {
6454 match anchor {
6455 Anchor::Min => self
6456 .excerpts
6457 .first()
6458 .map(|excerpt| excerpt.buffer.remote_id()),
6459 Anchor::Excerpt(excerpt_anchor) => Some(excerpt_anchor.buffer_id),
6460 Anchor::Max => self
6461 .excerpts
6462 .last()
6463 .map(|excerpt| excerpt.buffer.remote_id()),
6464 }
6465 }
6466
6467 pub fn selections_in_range<'a>(
6468 &'a self,
6469 range: &'a Range<Anchor>,
6470 include_local: bool,
6471 ) -> impl 'a + Iterator<Item = (ReplicaId, bool, CursorShape, Selection<Anchor>)> {
6472 let mut cursor = self.excerpts.cursor::<ExcerptSummary>(());
6473 cursor.seek(&range.start.seek_target(self), Bias::Left);
6474 cursor
6475 .take_while(move |excerpt| {
6476 let excerpt_start =
6477 Anchor::in_buffer(excerpt.path_key_index, excerpt.range.context.start);
6478 excerpt_start.cmp(&range.end, self).is_le()
6479 })
6480 .flat_map(move |excerpt| {
6481 let mut query_range = excerpt.range.context.start..excerpt.range.context.end;
6482 if let Some(excerpt_anchor) = range.start.excerpt_anchor()
6483 && excerpt.contains(&excerpt_anchor)
6484 {
6485 query_range.start = excerpt_anchor.text_anchor();
6486 }
6487 if let Some(excerpt_anchor) = range.end.excerpt_anchor()
6488 && excerpt.contains(&excerpt_anchor)
6489 {
6490 query_range.end = excerpt_anchor.text_anchor();
6491 }
6492
6493 excerpt
6494 .buffer
6495 .selections_in_range(query_range, include_local)
6496 .flat_map(move |(replica_id, line_mode, cursor_shape, selections)| {
6497 selections.map(move |selection| {
6498 let mut start =
6499 Anchor::in_buffer(excerpt.path_key_index, selection.start);
6500 let mut end = Anchor::in_buffer(excerpt.path_key_index, selection.end);
6501 if range.start.cmp(&start, self).is_gt() {
6502 start = range.start;
6503 }
6504 if range.end.cmp(&end, self).is_lt() {
6505 end = range.end;
6506 }
6507
6508 (
6509 replica_id,
6510 line_mode,
6511 cursor_shape,
6512 Selection {
6513 id: selection.id,
6514 start,
6515 end,
6516 reversed: selection.reversed,
6517 goal: selection.goal,
6518 },
6519 )
6520 })
6521 })
6522 })
6523 }
6524
6525 pub fn show_headers(&self) -> bool {
6526 self.show_headers
6527 }
6528
6529 pub fn diff_for_buffer_id(&self, buffer_id: BufferId) -> Option<&BufferDiffSnapshot> {
6530 self.diffs.get(&buffer_id).map(|diff| &diff.diff)
6531 }
6532
6533 pub fn all_diff_hunks_expanded(&self) -> bool {
6534 self.all_diff_hunks_expanded
6535 }
6536
6537 /// Visually annotates a position or range with the `Debug` representation of a value. The
6538 /// callsite of this function is used as a key - previous annotations will be removed.
6539 #[cfg(debug_assertions)]
6540 #[track_caller]
6541 pub fn debug<V, R>(&self, ranges: &R, value: V)
6542 where
6543 R: debug::ToMultiBufferDebugRanges,
6544 V: std::fmt::Debug,
6545 {
6546 self.debug_with_key(std::panic::Location::caller(), ranges, value);
6547 }
6548
6549 /// Visually annotates a position or range with the `Debug` representation of a value. Previous
6550 /// debug annotations with the same key will be removed. The key is also used to determine the
6551 /// annotation's color.
6552 #[cfg(debug_assertions)]
6553 #[track_caller]
6554 pub fn debug_with_key<K, R, V>(&self, key: &K, ranges: &R, value: V)
6555 where
6556 K: std::hash::Hash + 'static,
6557 R: debug::ToMultiBufferDebugRanges,
6558 V: std::fmt::Debug,
6559 {
6560 let text_ranges = ranges
6561 .to_multi_buffer_debug_ranges(self)
6562 .into_iter()
6563 .flat_map(|range| {
6564 self.range_to_buffer_ranges(range.start..=range.end)
6565 .into_iter()
6566 .map(|(buffer, range, _excerpt_id)| {
6567 buffer.anchor_after(range.start)..buffer.anchor_before(range.end)
6568 })
6569 })
6570 .collect();
6571 text::debug::GlobalDebugRanges::with_locked(|debug_ranges| {
6572 debug_ranges.insert(key, text_ranges, format!("{value:?}").into())
6573 });
6574 }
6575
6576 fn excerpt_edits_for_diff_change(
6577 &self,
6578 path: &PathKey,
6579 diff_change_range: Range<usize>,
6580 ) -> Vec<Edit<ExcerptDimension<MultiBufferOffset>>> {
6581 let mut excerpt_edits = Vec::new();
6582 let mut cursor = self.excerpts.cursor::<ExcerptSummary>(());
6583 cursor.seek(path, Bias::Left);
6584 while let Some(excerpt) = cursor.item()
6585 && &excerpt.path_key == path
6586 {
6587 let excerpt_buffer_range = excerpt.range.context.to_offset(&excerpt.buffer);
6588 let excerpt_start = cursor.start().clone();
6589 let excerpt_len = excerpt.text_summary.len;
6590 cursor.next();
6591 if diff_change_range.end < excerpt_buffer_range.start
6592 || diff_change_range.start > excerpt_buffer_range.end
6593 {
6594 continue;
6595 }
6596 let diff_change_start_in_excerpt = diff_change_range
6597 .start
6598 .saturating_sub(excerpt_buffer_range.start);
6599 let diff_change_end_in_excerpt = diff_change_range
6600 .end
6601 .saturating_sub(excerpt_buffer_range.start);
6602 let edit_start = excerpt_start.len() + diff_change_start_in_excerpt.min(excerpt_len);
6603 let edit_end = excerpt_start.len() + diff_change_end_in_excerpt.min(excerpt_len);
6604 excerpt_edits.push(Edit {
6605 old: edit_start..edit_end,
6606 new: edit_start..edit_end,
6607 });
6608 }
6609 excerpt_edits
6610 }
6611
6612 fn excerpt_ranges_for_path(
6613 &self,
6614 path_key: &PathKey,
6615 ) -> impl Iterator<Item = ExcerptRange<text::Anchor>> {
6616 todo!()
6617 }
6618}
6619
6620#[cfg(any(test, feature = "test-support"))]
6621impl MultiBufferSnapshot {
6622 pub fn random_byte_range(
6623 &self,
6624 start_offset: MultiBufferOffset,
6625 rng: &mut impl rand::Rng,
6626 ) -> Range<MultiBufferOffset> {
6627 let end = self.clip_offset(rng.random_range(start_offset..=self.len()), Bias::Right);
6628 let start = self.clip_offset(rng.random_range(start_offset..=end), Bias::Right);
6629 start..end
6630 }
6631
6632 #[cfg(any(test, feature = "test-support"))]
6633 fn check_invariants(&self) {
6634 let excerpts = self.excerpts.items(());
6635 let excerpt_ids = self.excerpt_ids.items(());
6636
6637 for (ix, excerpt) in excerpts.iter().enumerate() {
6638 if ix == 0 {
6639 if excerpt.locator <= Locator::min() {
6640 panic!("invalid first excerpt locator {:?}", excerpt.locator);
6641 }
6642 } else if excerpt.locator <= excerpts[ix - 1].locator {
6643 panic!("excerpts are out-of-order: {:?}", excerpts);
6644 }
6645 }
6646
6647 for (ix, entry) in excerpt_ids.iter().enumerate() {
6648 if ix == 0 {
6649 if entry.id.cmp(&ExcerptId::min(), self).is_le() {
6650 panic!("invalid first excerpt id {:?}", entry.id);
6651 }
6652 } else if entry.id <= excerpt_ids[ix - 1].id {
6653 panic!("excerpt ids are out-of-order: {:?}", excerpt_ids);
6654 }
6655 }
6656
6657 if self.diff_transforms.summary().input != self.excerpts.summary().text {
6658 panic!(
6659 "incorrect input summary. expected {:?}, got {:?}. transforms: {:+?}",
6660 self.excerpts.summary().text,
6661 self.diff_transforms.summary().input,
6662 self.diff_transforms.items(()),
6663 );
6664 }
6665
6666 let mut prev_transform: Option<&DiffTransform> = None;
6667 for item in self.diff_transforms.iter() {
6668 if let DiffTransform::BufferContent {
6669 summary,
6670 inserted_hunk_info,
6671 } = item
6672 {
6673 if let Some(DiffTransform::BufferContent {
6674 inserted_hunk_info: prev_inserted_hunk_info,
6675 ..
6676 }) = prev_transform
6677 && *inserted_hunk_info == *prev_inserted_hunk_info
6678 {
6679 panic!(
6680 "multiple adjacent buffer content transforms with is_inserted_hunk = {inserted_hunk_info:?}. transforms: {:+?}",
6681 self.diff_transforms.items(())
6682 );
6683 }
6684 if summary.len == MultiBufferOffset(0) && !self.is_empty() {
6685 panic!("empty buffer content transform");
6686 }
6687 }
6688 prev_transform = Some(item);
6689 }
6690 }
6691}
6692
6693impl<'a, MBD, BD> MultiBufferCursor<'a, MBD, BD>
6694where
6695 MBD: MultiBufferDimension + Ord + Sub + ops::AddAssign<<MBD as Sub>::Output>,
6696 BD: TextDimension + AddAssign<<MBD as Sub>::Output>,
6697{
6698 #[instrument(skip_all)]
6699 fn seek(&mut self, position: &MBD) {
6700 let position = OutputDimension(*position);
6701 self.cached_region.take();
6702 self.diff_transforms.seek(&position, Bias::Right);
6703 if self.diff_transforms.item().is_none()
6704 && self.diff_transforms.start().output_dimension == position
6705 {
6706 self.diff_transforms.prev();
6707 }
6708
6709 let mut excerpt_position = self.diff_transforms.start().excerpt_dimension;
6710 if let Some(DiffTransform::BufferContent { .. }) = self.diff_transforms.item() {
6711 let overshoot = position - self.diff_transforms.start().output_dimension;
6712 excerpt_position += overshoot;
6713 }
6714
6715 self.excerpts.seek(&excerpt_position, Bias::Right);
6716 if self.excerpts.item().is_none() && excerpt_position == *self.excerpts.start() {
6717 self.excerpts.prev();
6718 }
6719 }
6720
6721 fn seek_forward(&mut self, position: &MBD) {
6722 let position = OutputDimension(*position);
6723 self.cached_region.take();
6724 self.diff_transforms.seek_forward(&position, Bias::Right);
6725 if self.diff_transforms.item().is_none()
6726 && self.diff_transforms.start().output_dimension == position
6727 {
6728 self.diff_transforms.prev();
6729 }
6730
6731 let overshoot = position - self.diff_transforms.start().output_dimension;
6732 let mut excerpt_position = self.diff_transforms.start().excerpt_dimension;
6733 if let Some(DiffTransform::BufferContent { .. }) = self.diff_transforms.item() {
6734 excerpt_position += overshoot;
6735 }
6736
6737 self.excerpts.seek_forward(&excerpt_position, Bias::Right);
6738 if self.excerpts.item().is_none() && excerpt_position == *self.excerpts.start() {
6739 self.excerpts.prev();
6740 }
6741 }
6742
6743 fn next_excerpt(&mut self) {
6744 self.excerpts.next();
6745 self.seek_to_start_of_current_excerpt();
6746 }
6747
6748 fn prev_excerpt(&mut self) {
6749 self.excerpts.prev();
6750 self.seek_to_start_of_current_excerpt();
6751 }
6752
6753 fn seek_to_start_of_current_excerpt(&mut self) {
6754 self.cached_region.take();
6755
6756 if self.diff_transforms.seek(self.excerpts.start(), Bias::Left)
6757 && self.diff_transforms.start().excerpt_dimension < *self.excerpts.start()
6758 && self.diff_transforms.next_item().is_some()
6759 {
6760 self.diff_transforms.next();
6761 }
6762 }
6763
6764 fn next_excerpt_forwards(&mut self) {
6765 self.excerpts.next();
6766 self.seek_to_start_of_current_excerpt_forward();
6767 }
6768
6769 fn seek_to_start_of_current_excerpt_forward(&mut self) {
6770 self.cached_region.take();
6771
6772 if self
6773 .diff_transforms
6774 .seek_forward(self.excerpts.start(), Bias::Left)
6775 && self.diff_transforms.start().excerpt_dimension < *self.excerpts.start()
6776 && self.diff_transforms.next_item().is_some()
6777 {
6778 self.diff_transforms.next();
6779 }
6780 }
6781
6782 fn next(&mut self) {
6783 self.cached_region.take();
6784 match self
6785 .diff_transforms
6786 .end()
6787 .excerpt_dimension
6788 .cmp(&self.excerpts.end())
6789 {
6790 cmp::Ordering::Less => self.diff_transforms.next(),
6791 cmp::Ordering::Greater => self.excerpts.next(),
6792 cmp::Ordering::Equal => {
6793 self.diff_transforms.next();
6794 if self.diff_transforms.end().excerpt_dimension > self.excerpts.end()
6795 || self.diff_transforms.item().is_none()
6796 {
6797 self.excerpts.next();
6798 } else if let Some(DiffTransform::DeletedHunk { hunk_info, .. }) =
6799 self.diff_transforms.item()
6800 && self
6801 .excerpts
6802 .item()
6803 .is_some_and(|excerpt| excerpt.end_anchor() != hunk_info.excerpt_end)
6804 {
6805 self.excerpts.next();
6806 }
6807 }
6808 }
6809 }
6810
6811 fn prev(&mut self) {
6812 self.cached_region.take();
6813 match self
6814 .diff_transforms
6815 .start()
6816 .excerpt_dimension
6817 .cmp(self.excerpts.start())
6818 {
6819 cmp::Ordering::Less => self.excerpts.prev(),
6820 cmp::Ordering::Greater => self.diff_transforms.prev(),
6821 cmp::Ordering::Equal => {
6822 self.diff_transforms.prev();
6823 if self.diff_transforms.start().excerpt_dimension < *self.excerpts.start()
6824 || self.diff_transforms.item().is_none()
6825 {
6826 self.excerpts.prev();
6827 }
6828 }
6829 }
6830 }
6831
6832 fn region(&self) -> Option<&MultiBufferRegion<'a, MBD, BD>> {
6833 self.cached_region
6834 .get_or_init(|| self.build_region())
6835 .as_ref()
6836 }
6837
6838 fn is_at_start_of_excerpt(&mut self) -> bool {
6839 if self.diff_transforms.start().excerpt_dimension > *self.excerpts.start() {
6840 return false;
6841 } else if self.diff_transforms.start().excerpt_dimension < *self.excerpts.start() {
6842 return true;
6843 }
6844
6845 self.diff_transforms.prev();
6846 let prev_transform = self.diff_transforms.item();
6847 self.diff_transforms.next();
6848
6849 prev_transform.is_none_or(|next_transform| {
6850 matches!(next_transform, DiffTransform::BufferContent { .. })
6851 })
6852 }
6853
6854 fn is_at_end_of_excerpt(&self) -> bool {
6855 if self.diff_transforms.end().excerpt_dimension < self.excerpts.end() {
6856 return false;
6857 } else if self.diff_transforms.end().excerpt_dimension > self.excerpts.end()
6858 || self.diff_transforms.item().is_none()
6859 {
6860 return true;
6861 }
6862
6863 let next_transform = self.diff_transforms.next_item();
6864 next_transform.is_none_or(|next_transform| match next_transform {
6865 DiffTransform::BufferContent { .. } => true,
6866 DiffTransform::DeletedHunk { hunk_info, .. } => self
6867 .excerpts
6868 .item()
6869 .is_some_and(|excerpt| excerpt.end_anchor() != hunk_info.excerpt_end),
6870 })
6871 }
6872
6873 fn main_buffer_position(&self) -> Option<BD> {
6874 let excerpt = self.excerpts.item()?;
6875 let buffer = &excerpt.buffer;
6876 let buffer_context_start = excerpt.range.context.start.summary::<BD>(buffer);
6877 let mut buffer_start = buffer_context_start;
6878 let overshoot = self.diff_transforms.end().excerpt_dimension - *self.excerpts.start();
6879 buffer_start += overshoot;
6880 Some(buffer_start)
6881 }
6882
6883 fn build_region(&self) -> Option<MultiBufferRegion<'a, MBD, BD>> {
6884 let excerpt = self.excerpts.item()?;
6885 match self.diff_transforms.item()? {
6886 DiffTransform::DeletedHunk {
6887 buffer_id,
6888 base_text_byte_range,
6889 has_trailing_newline,
6890 hunk_info,
6891 ..
6892 } => {
6893 let diff = self.diffs.get(buffer_id)?;
6894 let buffer = diff.base_text();
6895 let mut rope_cursor = buffer.as_rope().cursor(0);
6896 let buffer_start = rope_cursor.summary::<BD>(base_text_byte_range.start);
6897 let buffer_range_len = rope_cursor.summary::<BD>(base_text_byte_range.end);
6898 let mut buffer_end = buffer_start;
6899 TextDimension::add_assign(&mut buffer_end, &buffer_range_len);
6900 let start = self.diff_transforms.start().output_dimension.0;
6901 let end = self.diff_transforms.end().output_dimension.0;
6902 Some(MultiBufferRegion {
6903 buffer,
6904 excerpt,
6905 has_trailing_newline: *has_trailing_newline,
6906 is_main_buffer: false,
6907 diff_hunk_status: Some(DiffHunkStatus::deleted(
6908 hunk_info.hunk_secondary_status,
6909 )),
6910 buffer_range: buffer_start..buffer_end,
6911 range: start..end,
6912 })
6913 }
6914 DiffTransform::BufferContent {
6915 inserted_hunk_info, ..
6916 } => {
6917 let buffer = &excerpt.buffer;
6918 let buffer_context_start = excerpt.range.context.start.summary::<BD>(buffer);
6919
6920 let mut start = self.diff_transforms.start().output_dimension.0;
6921 let mut buffer_start = buffer_context_start;
6922 if self.diff_transforms.start().excerpt_dimension < *self.excerpts.start() {
6923 let overshoot =
6924 *self.excerpts.start() - self.diff_transforms.start().excerpt_dimension;
6925 start += overshoot;
6926 } else {
6927 let overshoot =
6928 self.diff_transforms.start().excerpt_dimension - *self.excerpts.start();
6929 buffer_start += overshoot;
6930 }
6931
6932 let mut end;
6933 let mut buffer_end;
6934 let has_trailing_newline;
6935 let transform_end = self.diff_transforms.end();
6936 if transform_end.excerpt_dimension < self.excerpts.end() {
6937 let overshoot = transform_end.excerpt_dimension - *self.excerpts.start();
6938 end = transform_end.output_dimension.0;
6939 buffer_end = buffer_context_start;
6940 buffer_end += overshoot;
6941 has_trailing_newline = false;
6942 } else {
6943 let overshoot =
6944 self.excerpts.end() - self.diff_transforms.start().excerpt_dimension;
6945 end = self.diff_transforms.start().output_dimension.0;
6946 end += overshoot;
6947 buffer_end = excerpt.range.context.end.summary::<BD>(buffer);
6948 has_trailing_newline = excerpt.has_trailing_newline;
6949 };
6950
6951 let diff_hunk_status = inserted_hunk_info.map(|info| {
6952 if info.is_logically_deleted {
6953 DiffHunkStatus::deleted(info.hunk_secondary_status)
6954 } else {
6955 DiffHunkStatus::added(info.hunk_secondary_status)
6956 }
6957 });
6958
6959 Some(MultiBufferRegion {
6960 buffer,
6961 excerpt,
6962 has_trailing_newline,
6963 is_main_buffer: true,
6964 diff_hunk_status,
6965 buffer_range: buffer_start..buffer_end,
6966 range: start..end,
6967 })
6968 }
6969 }
6970 }
6971
6972 fn fetch_excerpt_with_range(&self) -> Option<(&'a Excerpt, Range<MBD>)> {
6973 let excerpt = self.excerpts.item()?;
6974 match self.diff_transforms.item()? {
6975 &DiffTransform::DeletedHunk { .. } => {
6976 let start = self.diff_transforms.start().output_dimension.0;
6977 let end = self.diff_transforms.end().output_dimension.0;
6978 Some((excerpt, start..end))
6979 }
6980 DiffTransform::BufferContent { .. } => {
6981 let mut start = self.diff_transforms.start().output_dimension.0;
6982 if self.diff_transforms.start().excerpt_dimension < *self.excerpts.start() {
6983 let overshoot =
6984 *self.excerpts.start() - self.diff_transforms.start().excerpt_dimension;
6985 start += overshoot;
6986 }
6987
6988 let mut end;
6989 let transform_end = self.diff_transforms.end();
6990 if transform_end.excerpt_dimension < self.excerpts.end() {
6991 end = transform_end.output_dimension.0;
6992 } else {
6993 let overshoot =
6994 self.excerpts.end() - self.diff_transforms.start().excerpt_dimension;
6995 end = self.diff_transforms.start().output_dimension.0;
6996 end += overshoot;
6997 };
6998
6999 Some((excerpt, start..end))
7000 }
7001 }
7002 }
7003
7004 fn excerpt(&self) -> Option<&'a Excerpt> {
7005 self.excerpts.item()
7006 }
7007}
7008
7009impl Excerpt {
7010 fn new(
7011 path_key: PathKey,
7012 buffer: Arc<BufferSnapshot>,
7013 range: ExcerptRange<text::Anchor>,
7014 has_trailing_newline: bool,
7015 ) -> Self {
7016 Excerpt {
7017 path_key,
7018 max_buffer_row: range.context.end.to_point(&buffer).row,
7019 text_summary: buffer
7020 .text_summary_for_range::<TextSummary, _>(range.context.to_offset(&buffer)),
7021 buffer,
7022 range,
7023 has_trailing_newline,
7024 }
7025 }
7026
7027 fn chunks_in_range(&self, range: Range<usize>, language_aware: bool) -> ExcerptChunks<'_> {
7028 let content_start = self.range.context.start.to_offset(&self.buffer);
7029 let chunks_start = content_start + range.start;
7030 let chunks_end = content_start + cmp::min(range.end, self.text_summary.len);
7031
7032 let has_footer = self.has_trailing_newline
7033 && range.start <= self.text_summary.len
7034 && range.end > self.text_summary.len;
7035
7036 let content_chunks = self.buffer.chunks(chunks_start..chunks_end, language_aware);
7037
7038 ExcerptChunks {
7039 content_chunks,
7040 has_footer,
7041 end: self.end_anchor(),
7042 }
7043 }
7044
7045 fn seek_chunks(&self, excerpt_chunks: &mut ExcerptChunks, range: Range<usize>) {
7046 let content_start = self.range.context.start.to_offset(&self.buffer);
7047 let chunks_start = content_start + range.start;
7048 let chunks_end = content_start + cmp::min(range.end, self.text_summary.len);
7049 excerpt_chunks.content_chunks.seek(chunks_start..chunks_end);
7050 excerpt_chunks.has_footer = self.has_trailing_newline
7051 && range.start <= self.text_summary.len
7052 && range.end > self.text_summary.len;
7053 }
7054
7055 fn clip_anchor(&self, text_anchor: text::Anchor) -> text::Anchor {
7056 if text_anchor
7057 .cmp(&self.range.context.start, &self.buffer)
7058 .is_lt()
7059 {
7060 self.range.context.start
7061 } else if text_anchor
7062 .cmp(&self.range.context.end, &self.buffer)
7063 .is_gt()
7064 {
7065 self.range.context.end
7066 } else {
7067 text_anchor
7068 }
7069 }
7070
7071 pub(crate) fn contains(&self, anchor: &ExcerptAnchor) -> bool {
7072 self.path_key_index == anchor.path
7073 && self.buffer.remote_id() == anchor.buffer_id
7074 && self.range.contains(&anchor.text_anchor(), &self.buffer)
7075 }
7076
7077 /// The [`Excerpt`]'s start offset in its [`Buffer`]
7078 fn buffer_start_offset(&self) -> BufferOffset {
7079 BufferOffset(self.range.context.start.to_offset(&self.buffer))
7080 }
7081
7082 /// The [`Excerpt`]'s end offset in its [`Buffer`]
7083 fn buffer_end_offset(&self) -> BufferOffset {
7084 self.buffer_start_offset() + self.text_summary.len
7085 }
7086
7087 fn start_anchor(&self) -> ExcerptAnchor {
7088 todo!()
7089 }
7090
7091 fn end_anchor(&self) -> ExcerptAnchor {
7092 todo!()
7093 }
7094}
7095
7096impl PartialEq for Excerpt {
7097 fn eq(&self, other: &Self) -> bool {
7098 self.buffer.remote_id() == other.buffer.remote_id()
7099 && self.range.context == other.range.context
7100 }
7101}
7102
7103impl<'a> MultiBufferExcerpt<'a> {
7104 pub fn buffer_id(&self) -> BufferId {
7105 self.excerpt.buffer.remote_id()
7106 }
7107
7108 pub fn start_anchor(&self) -> Anchor {
7109 Anchor::in_buffer(
7110 self.excerpt.path_key_index,
7111 self.excerpt.range.context.start,
7112 )
7113 }
7114
7115 pub fn end_anchor(&self) -> Anchor {
7116 Anchor::in_buffer(self.excerpt.path_key_index, self.excerpt.range.context.end)
7117 }
7118
7119 pub fn buffer(&self) -> &'a BufferSnapshot {
7120 &self.excerpt.buffer
7121 }
7122
7123 pub fn buffer_range(&self) -> Range<BufferOffset> {
7124 self.buffer_offset
7125 ..BufferOffset(
7126 self.excerpt
7127 .range
7128 .context
7129 .end
7130 .to_offset(&self.excerpt.buffer.text),
7131 )
7132 }
7133
7134 pub fn start_offset(&self) -> MultiBufferOffset {
7135 self.offset
7136 }
7137
7138 /// Maps an offset within the [`MultiBuffer`] to an offset within the [`Buffer`]
7139 pub fn map_offset_to_buffer(&mut self, offset: MultiBufferOffset) -> BufferOffset {
7140 self.map_range_to_buffer(offset..offset).start
7141 }
7142
7143 /// Maps a range within the [`MultiBuffer`] to a range within the [`Buffer`]
7144 pub fn map_range_to_buffer(&mut self, range: Range<MultiBufferOffset>) -> Range<BufferOffset> {
7145 self.diff_transforms
7146 .seek(&OutputDimension(range.start), Bias::Right);
7147 let start = self.map_offset_to_buffer_internal(range.start);
7148 let end = if range.end > range.start {
7149 self.diff_transforms
7150 .seek_forward(&OutputDimension(range.end), Bias::Right);
7151 self.map_offset_to_buffer_internal(range.end)
7152 } else {
7153 start
7154 };
7155 start..end
7156 }
7157
7158 fn map_offset_to_buffer_internal(&self, offset: MultiBufferOffset) -> BufferOffset {
7159 let mut excerpt_offset = self.diff_transforms.start().excerpt_dimension;
7160 if let Some(DiffTransform::BufferContent { .. }) = self.diff_transforms.item() {
7161 excerpt_offset += offset - self.diff_transforms.start().output_dimension.0;
7162 };
7163 let offset_in_excerpt = excerpt_offset.saturating_sub(self.excerpt_offset);
7164 self.buffer_offset + offset_in_excerpt
7165 }
7166
7167 /// Map an offset within the [`Buffer`] to an offset within the [`MultiBuffer`]
7168 pub fn map_offset_from_buffer(&mut self, buffer_offset: BufferOffset) -> MultiBufferOffset {
7169 self.map_range_from_buffer(buffer_offset..buffer_offset)
7170 .start
7171 }
7172
7173 /// Map a range within the [`Buffer`] to a range within the [`MultiBuffer`]
7174 pub fn map_range_from_buffer(
7175 &mut self,
7176 buffer_range: Range<BufferOffset>,
7177 ) -> Range<MultiBufferOffset> {
7178 if buffer_range.start < self.buffer_offset {
7179 log::warn!(
7180 "Attempting to map a range from a buffer offset that starts before the current buffer offset"
7181 );
7182 return self.offset..self.offset;
7183 }
7184 let overshoot = buffer_range.start - self.buffer_offset;
7185 let excerpt_offset = self.excerpt_offset + overshoot;
7186 let excerpt_seek_dim = excerpt_offset;
7187 self.diff_transforms.seek(&excerpt_seek_dim, Bias::Right);
7188 if self.diff_transforms.start().excerpt_dimension > excerpt_offset {
7189 log::warn!(
7190 "Attempting to map a range from a buffer offset that starts before the current buffer offset"
7191 );
7192 return self.offset..self.offset;
7193 }
7194 let overshoot = excerpt_offset - self.diff_transforms.start().excerpt_dimension;
7195 let start = self.diff_transforms.start().output_dimension.0 + overshoot;
7196
7197 let end = if buffer_range.start < buffer_range.end {
7198 let overshoot = buffer_range.end - self.buffer_offset;
7199 let excerpt_offset = self.excerpt_offset + overshoot;
7200 let excerpt_seek_dim = excerpt_offset;
7201 self.diff_transforms
7202 .seek_forward(&excerpt_seek_dim, Bias::Right);
7203 let overshoot = excerpt_offset - self.diff_transforms.start().excerpt_dimension;
7204 // todo(lw): Clamp end to the excerpt boundaries
7205 self.diff_transforms.start().output_dimension.0 + overshoot
7206 } else {
7207 start
7208 };
7209
7210 start..end
7211 }
7212
7213 /// Returns true if the entirety of the given range is in the buffer's excerpt
7214 pub fn contains_buffer_range(&self, range: Range<BufferOffset>) -> bool {
7215 range.start >= self.excerpt.buffer_start_offset()
7216 && range.end <= self.excerpt.buffer_end_offset()
7217 }
7218
7219 /// Returns true if any part of the given range is in the buffer's excerpt
7220 pub fn contains_partial_buffer_range(&self, range: Range<BufferOffset>) -> bool {
7221 range.start <= self.excerpt.buffer_end_offset()
7222 && range.end >= self.excerpt.buffer_start_offset()
7223 }
7224
7225 pub fn max_buffer_row(&self) -> u32 {
7226 self.excerpt.max_buffer_row
7227 }
7228}
7229
7230impl fmt::Debug for Excerpt {
7231 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
7232 f.debug_struct("Excerpt")
7233 .field("path_key", &self.path_key)
7234 .field("range", &self.range)
7235 .field("text_summary", &self.text_summary)
7236 .field("has_trailing_newline", &self.has_trailing_newline)
7237 .finish()
7238 }
7239}
7240
7241impl sum_tree::Item for Excerpt {
7242 type Summary = ExcerptSummary;
7243
7244 fn summary(&self, _cx: ()) -> Self::Summary {
7245 let mut text = self.text_summary;
7246 if self.has_trailing_newline {
7247 text += TextSummary::from("\n");
7248 }
7249 ExcerptSummary {
7250 path_key: self.path_key,
7251 max_anchor: self.range.context.end,
7252 widest_line_number: self.max_buffer_row,
7253 text: text.into(),
7254 count: 1,
7255 }
7256 }
7257}
7258
7259impl DiffTransform {
7260 fn hunk_info(&self) -> Option<DiffTransformHunkInfo> {
7261 match self {
7262 DiffTransform::DeletedHunk { hunk_info, .. } => Some(*hunk_info),
7263 DiffTransform::BufferContent {
7264 inserted_hunk_info, ..
7265 } => *inserted_hunk_info,
7266 }
7267 }
7268}
7269
7270impl sum_tree::Item for DiffTransform {
7271 type Summary = DiffTransformSummary;
7272
7273 fn summary(&self, _: <Self::Summary as sum_tree::Summary>::Context<'_>) -> Self::Summary {
7274 match self {
7275 DiffTransform::BufferContent { summary, .. } => DiffTransformSummary {
7276 input: *summary,
7277 output: *summary,
7278 },
7279 &DiffTransform::DeletedHunk { summary, .. } => DiffTransformSummary {
7280 input: MBTextSummary::default(),
7281 output: summary.into(),
7282 },
7283 }
7284 }
7285}
7286
7287impl DiffTransformSummary {
7288 fn excerpt_len(&self) -> ExcerptOffset {
7289 ExcerptDimension(self.input.len)
7290 }
7291}
7292
7293impl sum_tree::ContextLessSummary for DiffTransformSummary {
7294 fn zero() -> Self {
7295 DiffTransformSummary {
7296 input: MBTextSummary::default(),
7297 output: MBTextSummary::default(),
7298 }
7299 }
7300
7301 fn add_summary(&mut self, other: &Self) {
7302 self.input += other.input;
7303 self.output += other.output;
7304 }
7305}
7306
7307impl sum_tree::Dimension<'_, ExcerptSummary> for PathKey {
7308 fn zero(_: <ExcerptSummary as sum_tree::Summary>::Context<'_>) -> Self {
7309 PathKey::min()
7310 }
7311
7312 fn add_summary(
7313 &mut self,
7314 summary: &'_ ExcerptSummary,
7315 _cx: <ExcerptSummary as sum_tree::Summary>::Context<'_>,
7316 ) {
7317 *self = summary.path_key.clone();
7318 }
7319}
7320
7321impl sum_tree::Dimension<'_, ExcerptSummary> for MultiBufferOffset {
7322 fn zero(_: <ExcerptSummary as sum_tree::Summary>::Context<'_>) -> Self {
7323 MultiBufferOffset::ZERO
7324 }
7325
7326 fn add_summary(
7327 &mut self,
7328 summary: &'_ ExcerptSummary,
7329 _cx: <ExcerptSummary as sum_tree::Summary>::Context<'_>,
7330 ) {
7331 self += summary.text.len
7332 }
7333}
7334
7335impl sum_tree::ContextLessSummary for ExcerptSummary {
7336 fn zero() -> Self {
7337 Self::min()
7338 }
7339
7340 fn add_summary(&mut self, summary: &Self) {
7341 debug_assert!(
7342 summary.path_key >= self.path_key,
7343 "Path keys must be in ascending order: {:?} > {:?}",
7344 summary.path_key,
7345 self.path_key
7346 );
7347
7348 self.path_key = summary.path_key.clone();
7349 self.max_anchor = summary.max_anchor;
7350 self.text += summary.text;
7351 self.widest_line_number = cmp::max(self.widest_line_number, summary.widest_line_number);
7352 self.count += summary.count;
7353 }
7354}
7355
7356impl sum_tree::SeekTarget<'_, ExcerptSummary, ExcerptSummary> for AnchorSeekTarget {
7357 fn cmp(
7358 &self,
7359 cursor_location: &ExcerptSummary,
7360 _cx: <ExcerptSummary as sum_tree::Summary>::Context<'_>,
7361 ) -> cmp::Ordering {
7362 match self {
7363 AnchorSeekTarget::Min => Ordering::Less,
7364 AnchorSeekTarget::Max => Ordering::Greater,
7365 AnchorSeekTarget::Excerpt {
7366 path_key,
7367 anchor,
7368 snapshot,
7369 } => {
7370 let path_comparison = Ord::cmp(path_key, &cursor_location.path_key);
7371 if path_comparison.is_ne() {
7372 path_comparison
7373 } else if let Some(snapshot) = snapshot {
7374 snapshot
7375 .remote_id()
7376 .cmp(&cursor_location.buffer_id)
7377 .then_with(|| {
7378 anchor
7379 .text_anchor()
7380 .cmp(&cursor_location.max_anchor, snapshot)
7381 })
7382 } else {
7383 // shouldn't happen because we expect this buffer not to have any excerpts
7384 // (otherwise snapshot would have been Some)
7385 Ordering::Equal
7386 }
7387 }
7388 }
7389 }
7390}
7391
7392impl sum_tree::SeekTarget<'_, ExcerptSummary, ExcerptSummary> for PathKey {
7393 fn cmp(
7394 &self,
7395 cursor_location: &ExcerptSummary,
7396 _cx: <ExcerptSummary as sum_tree::Summary>::Context<'_>,
7397 ) -> cmp::Ordering {
7398 todo!()
7399 }
7400}
7401
7402impl<'a, MBD> sum_tree::Dimension<'a, ExcerptSummary> for ExcerptDimension<MBD>
7403where
7404 MBD: MultiBufferDimension + Default,
7405{
7406 fn zero(_: ()) -> Self {
7407 ExcerptDimension(MBD::default())
7408 }
7409
7410 fn add_summary(&mut self, summary: &'a ExcerptSummary, _: ()) {
7411 MultiBufferDimension::add_mb_text_summary(&mut self.0, &summary.text)
7412 }
7413}
7414
7415#[derive(Copy, Clone, PartialOrd, Ord, Eq, PartialEq, Debug)]
7416struct OutputDimension<T>(T);
7417
7418impl<T: PartialEq> PartialEq<T> for OutputDimension<T> {
7419 fn eq(&self, other: &T) -> bool {
7420 self.0 == *other
7421 }
7422}
7423
7424impl<T: PartialOrd> PartialOrd<T> for OutputDimension<T> {
7425 fn partial_cmp(&self, other: &T) -> Option<cmp::Ordering> {
7426 self.0.partial_cmp(other)
7427 }
7428}
7429
7430impl<R, T, U> ops::Sub<OutputDimension<U>> for OutputDimension<T>
7431where
7432 T: ops::Sub<U, Output = R>,
7433{
7434 type Output = R;
7435
7436 fn sub(self, other: OutputDimension<U>) -> Self::Output {
7437 self.0 - other.0
7438 }
7439}
7440
7441impl<R, T, U> ops::Add<U> for OutputDimension<T>
7442where
7443 T: ops::Add<U, Output = R>,
7444{
7445 type Output = OutputDimension<R>;
7446
7447 fn add(self, other: U) -> Self::Output {
7448 OutputDimension(self.0 + other)
7449 }
7450}
7451
7452impl<T, U> AddAssign<U> for OutputDimension<T>
7453where
7454 T: AddAssign<U>,
7455{
7456 fn add_assign(&mut self, other: U) {
7457 self.0 += other;
7458 }
7459}
7460
7461impl<T, U> SubAssign<U> for OutputDimension<T>
7462where
7463 T: SubAssign<U>,
7464{
7465 fn sub_assign(&mut self, other: U) {
7466 self.0 -= other;
7467 }
7468}
7469
7470#[derive(Copy, Clone, PartialOrd, Ord, Eq, PartialEq, Debug, Default)]
7471struct ExcerptDimension<T>(T);
7472
7473impl<T: PartialEq> PartialEq<T> for ExcerptDimension<T> {
7474 fn eq(&self, other: &T) -> bool {
7475 self.0 == *other
7476 }
7477}
7478
7479impl<T: PartialOrd> PartialOrd<T> for ExcerptDimension<T> {
7480 fn partial_cmp(&self, other: &T) -> Option<cmp::Ordering> {
7481 self.0.partial_cmp(other)
7482 }
7483}
7484
7485impl ExcerptOffset {
7486 fn saturating_sub(self, other: ExcerptOffset) -> usize {
7487 self.0.saturating_sub(other.0)
7488 }
7489}
7490
7491impl<R, T, U> ops::Sub<ExcerptDimension<U>> for ExcerptDimension<T>
7492where
7493 T: ops::Sub<U, Output = R>,
7494{
7495 type Output = R;
7496
7497 fn sub(self, other: ExcerptDimension<U>) -> Self::Output {
7498 self.0 - other.0
7499 }
7500}
7501
7502impl<R, T, U> ops::Add<U> for ExcerptDimension<T>
7503where
7504 T: ops::Add<U, Output = R>,
7505{
7506 type Output = ExcerptDimension<R>;
7507
7508 fn add(self, other: U) -> Self::Output {
7509 ExcerptDimension(self.0 + other)
7510 }
7511}
7512
7513impl<T, U> AddAssign<U> for ExcerptDimension<T>
7514where
7515 T: AddAssign<U>,
7516{
7517 fn add_assign(&mut self, other: U) {
7518 self.0 += other;
7519 }
7520}
7521
7522impl<T, U> SubAssign<U> for ExcerptDimension<T>
7523where
7524 T: SubAssign<U>,
7525{
7526 fn sub_assign(&mut self, other: U) {
7527 self.0 -= other;
7528 }
7529}
7530
7531impl<'a> sum_tree::Dimension<'a, DiffTransformSummary> for MultiBufferOffset {
7532 fn zero(_: ()) -> Self {
7533 MultiBufferOffset::ZERO
7534 }
7535
7536 fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: ()) {
7537 *self += summary.output.len;
7538 }
7539}
7540
7541impl<MBD> sum_tree::SeekTarget<'_, DiffTransformSummary, DiffTransformSummary>
7542 for ExcerptDimension<MBD>
7543where
7544 MBD: MultiBufferDimension + Ord,
7545{
7546 fn cmp(&self, cursor_location: &DiffTransformSummary, _: ()) -> cmp::Ordering {
7547 Ord::cmp(&self.0, &MBD::from_summary(&cursor_location.input))
7548 }
7549}
7550
7551impl<'a, MBD> sum_tree::SeekTarget<'a, DiffTransformSummary, DiffTransforms<MBD>>
7552 for ExcerptDimension<MBD>
7553where
7554 MBD: MultiBufferDimension + Ord,
7555{
7556 fn cmp(&self, cursor_location: &DiffTransforms<MBD>, _: ()) -> cmp::Ordering {
7557 Ord::cmp(&self.0, &cursor_location.excerpt_dimension.0)
7558 }
7559}
7560
7561impl<'a, MBD: MultiBufferDimension> sum_tree::Dimension<'a, DiffTransformSummary>
7562 for ExcerptDimension<MBD>
7563{
7564 fn zero(_: ()) -> Self {
7565 ExcerptDimension(MBD::default())
7566 }
7567
7568 fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: ()) {
7569 self.0.add_mb_text_summary(&summary.input)
7570 }
7571}
7572
7573impl<'a, MBD> sum_tree::SeekTarget<'a, DiffTransformSummary, DiffTransforms<MBD>>
7574 for OutputDimension<MBD>
7575where
7576 MBD: MultiBufferDimension + Ord,
7577{
7578 fn cmp(&self, cursor_location: &DiffTransforms<MBD>, _: ()) -> cmp::Ordering {
7579 Ord::cmp(&self.0, &cursor_location.output_dimension.0)
7580 }
7581}
7582
7583impl<'a, MBD: MultiBufferDimension> sum_tree::Dimension<'a, DiffTransformSummary>
7584 for OutputDimension<MBD>
7585{
7586 fn zero(_: ()) -> Self {
7587 OutputDimension(MBD::default())
7588 }
7589
7590 fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: ()) {
7591 self.0.add_mb_text_summary(&summary.output)
7592 }
7593}
7594
7595impl MultiBufferRows<'_> {
7596 pub fn seek(&mut self, MultiBufferRow(row): MultiBufferRow) {
7597 self.point = Point::new(row, 0);
7598 self.cursor.seek(&self.point);
7599 }
7600}
7601
7602impl Iterator for MultiBufferRows<'_> {
7603 type Item = RowInfo;
7604
7605 fn next(&mut self) -> Option<Self::Item> {
7606 if self.is_empty && self.point.row == 0 {
7607 self.point += Point::new(1, 0);
7608 return Some(RowInfo {
7609 buffer_id: None,
7610 buffer_row: Some(0),
7611 multibuffer_row: Some(MultiBufferRow(0)),
7612 diff_status: None,
7613 expand_info: None,
7614 wrapped_buffer_row: None,
7615 });
7616 }
7617
7618 let mut region = self.cursor.region()?.clone();
7619 while self.point >= region.range.end {
7620 self.cursor.next();
7621 if let Some(next_region) = self.cursor.region() {
7622 region = next_region.clone();
7623 } else if self.point == self.cursor.diff_transforms.end().output_dimension.0 {
7624 let multibuffer_row = MultiBufferRow(self.point.row);
7625 let last_excerpt = self
7626 .cursor
7627 .excerpts
7628 .item()
7629 .or(self.cursor.excerpts.prev_item())?;
7630 let last_row = last_excerpt
7631 .range
7632 .context
7633 .end
7634 .to_point(&last_excerpt.buffer)
7635 .row;
7636
7637 let first_row = last_excerpt
7638 .range
7639 .context
7640 .start
7641 .to_point(&last_excerpt.buffer)
7642 .row;
7643
7644 let expand_info = if self.is_singleton {
7645 None
7646 } else {
7647 let needs_expand_up = first_row == last_row
7648 && last_row > 0
7649 && !region.diff_hunk_status.is_some_and(|d| d.is_deleted());
7650 let needs_expand_down = last_row < last_excerpt.buffer.max_point().row;
7651
7652 if needs_expand_up && needs_expand_down {
7653 Some(ExpandExcerptDirection::UpAndDown)
7654 } else if needs_expand_up {
7655 Some(ExpandExcerptDirection::Up)
7656 } else if needs_expand_down {
7657 Some(ExpandExcerptDirection::Down)
7658 } else {
7659 None
7660 }
7661 .map(|direction| ExpandInfo {
7662 direction,
7663 excerpt_id: last_excerpt.id,
7664 })
7665 };
7666 self.point += Point::new(1, 0);
7667 return Some(RowInfo {
7668 buffer_id: Some(last_excerpt.buffer_id),
7669 buffer_row: Some(last_row),
7670 multibuffer_row: Some(multibuffer_row),
7671 diff_status: None,
7672 wrapped_buffer_row: None,
7673 expand_info,
7674 });
7675 } else {
7676 return None;
7677 };
7678 }
7679
7680 let overshoot = self.point - region.range.start;
7681 let buffer_point = region.buffer_range.start + overshoot;
7682 let expand_info = if self.is_singleton {
7683 None
7684 } else {
7685 let needs_expand_up = self.point.row == region.range.start.row
7686 && self.cursor.is_at_start_of_excerpt()
7687 && buffer_point.row > 0;
7688 let needs_expand_down = (region.excerpt.has_trailing_newline
7689 && self.point.row + 1 == region.range.end.row
7690 || !region.excerpt.has_trailing_newline && self.point.row == region.range.end.row)
7691 && self.cursor.is_at_end_of_excerpt()
7692 && buffer_point.row < region.buffer.max_point().row;
7693
7694 if needs_expand_up && needs_expand_down {
7695 Some(ExpandExcerptDirection::UpAndDown)
7696 } else if needs_expand_up {
7697 Some(ExpandExcerptDirection::Up)
7698 } else if needs_expand_down {
7699 Some(ExpandExcerptDirection::Down)
7700 } else {
7701 None
7702 }
7703 .map(|direction| ExpandInfo {
7704 direction,
7705 excerpt_id: region.excerpt.id,
7706 })
7707 };
7708
7709 let result = Some(RowInfo {
7710 buffer_id: Some(region.buffer.remote_id()),
7711 buffer_row: Some(buffer_point.row),
7712 multibuffer_row: Some(MultiBufferRow(self.point.row)),
7713 diff_status: region
7714 .diff_hunk_status
7715 .filter(|_| self.point < region.range.end),
7716 expand_info,
7717 wrapped_buffer_row: None,
7718 });
7719 self.point += Point::new(1, 0);
7720 result
7721 }
7722}
7723
7724impl<'a> MultiBufferChunks<'a> {
7725 pub fn offset(&self) -> MultiBufferOffset {
7726 self.range.start
7727 }
7728
7729 pub fn seek(&mut self, range: Range<MultiBufferOffset>) {
7730 self.diff_transforms.seek(&range.end, Bias::Right);
7731 let mut excerpt_end = self.diff_transforms.start().1;
7732 if let Some(DiffTransform::BufferContent { .. }) = self.diff_transforms.item() {
7733 let overshoot = range.end - self.diff_transforms.start().0;
7734 excerpt_end += overshoot;
7735 }
7736
7737 self.diff_transforms.seek(&range.start, Bias::Right);
7738 let mut excerpt_start = self.diff_transforms.start().1;
7739 if let Some(DiffTransform::BufferContent { .. }) = self.diff_transforms.item() {
7740 let overshoot = range.start - self.diff_transforms.start().0;
7741 excerpt_start += overshoot;
7742 }
7743
7744 self.seek_to_excerpt_offset_range(excerpt_start..excerpt_end);
7745 self.buffer_chunk.take();
7746 self.range = range;
7747 }
7748
7749 fn seek_to_excerpt_offset_range(&mut self, new_range: Range<ExcerptOffset>) {
7750 self.excerpt_offset_range = new_range.clone();
7751 self.excerpts.seek(&new_range.start, Bias::Right);
7752 if let Some(excerpt) = self.excerpts.item() {
7753 let excerpt_start = *self.excerpts.start();
7754 if let Some(excerpt_chunks) = self
7755 .excerpt_chunks
7756 .as_mut()
7757 .filter(|chunks| excerpt.end_anchor() == chunks.end)
7758 {
7759 excerpt.seek_chunks(
7760 excerpt_chunks,
7761 (self.excerpt_offset_range.start - excerpt_start)
7762 ..(self.excerpt_offset_range.end - excerpt_start),
7763 );
7764 } else {
7765 self.excerpt_chunks = Some(excerpt.chunks_in_range(
7766 (self.excerpt_offset_range.start - excerpt_start)
7767 ..(self.excerpt_offset_range.end - excerpt_start),
7768 self.language_aware,
7769 ));
7770 }
7771 } else {
7772 self.excerpt_chunks = None;
7773 }
7774 }
7775
7776 #[ztracing::instrument(skip_all)]
7777 fn next_excerpt_chunk(&mut self) -> Option<Chunk<'a>> {
7778 loop {
7779 if self.excerpt_offset_range.is_empty() {
7780 return None;
7781 } else if let Some(chunk) = self.excerpt_chunks.as_mut()?.next() {
7782 self.excerpt_offset_range.start += chunk.text.len();
7783 return Some(chunk);
7784 } else {
7785 self.excerpts.next();
7786 let excerpt = self.excerpts.item()?;
7787 self.excerpt_chunks = Some(excerpt.chunks_in_range(
7788 0..(self.excerpt_offset_range.end - *self.excerpts.start()),
7789 self.language_aware,
7790 ));
7791 }
7792 }
7793 }
7794}
7795
7796impl<'a> Iterator for ReversedMultiBufferChunks<'a> {
7797 type Item = &'a str;
7798
7799 fn next(&mut self) -> Option<Self::Item> {
7800 let mut region = self.cursor.region()?;
7801 if self.offset == region.range.start {
7802 self.cursor.prev();
7803 region = self.cursor.region()?;
7804 let start_overshoot = self.start.saturating_sub(region.range.start);
7805 self.current_chunks = Some(region.buffer.reversed_chunks_in_range(
7806 region.buffer_range.start + start_overshoot..region.buffer_range.end,
7807 ));
7808 }
7809
7810 if self.offset == region.range.end && region.has_trailing_newline {
7811 self.offset -= 1;
7812 Some("\n")
7813 } else {
7814 let chunk = self.current_chunks.as_mut().unwrap().next()?;
7815 self.offset -= chunk.len();
7816 Some(chunk)
7817 }
7818 }
7819}
7820
7821impl<'a> Iterator for MultiBufferChunks<'a> {
7822 type Item = Chunk<'a>;
7823
7824 #[ztracing::instrument(skip_all)]
7825 fn next(&mut self) -> Option<Chunk<'a>> {
7826 if self.range.start >= self.range.end {
7827 return None;
7828 }
7829 if self.range.start == self.diff_transforms.end().0 {
7830 self.diff_transforms.next();
7831 }
7832
7833 let diff_transform_start = self.diff_transforms.start().0;
7834 let diff_transform_end = self.diff_transforms.end().0;
7835 debug_assert!(
7836 self.range.start < diff_transform_end,
7837 "{:?} < {:?} of ({1:?}..{2:?})",
7838 self.range.start,
7839 diff_transform_end,
7840 diff_transform_start
7841 );
7842
7843 let diff_transform = self.diff_transforms.item()?;
7844 match diff_transform {
7845 DiffTransform::BufferContent { .. } => {
7846 let chunk = if let Some(chunk) = &mut self.buffer_chunk {
7847 chunk
7848 } else {
7849 let chunk = self.next_excerpt_chunk().unwrap();
7850 self.buffer_chunk.insert(chunk)
7851 };
7852
7853 let chunk_end = self.range.start + chunk.text.len();
7854 let diff_transform_end = diff_transform_end.min(self.range.end);
7855
7856 if diff_transform_end < chunk_end {
7857 let split_idx = diff_transform_end - self.range.start;
7858 let (before, after) = chunk.text.split_at(split_idx);
7859 self.range.start = diff_transform_end;
7860 let mask = 1u128.unbounded_shl(split_idx as u32).wrapping_sub(1);
7861 let chars = chunk.chars & mask;
7862 let tabs = chunk.tabs & mask;
7863 let newlines = chunk.newlines & mask;
7864
7865 chunk.text = after;
7866 chunk.chars = chunk.chars >> split_idx;
7867 chunk.tabs = chunk.tabs >> split_idx;
7868 chunk.newlines = chunk.newlines >> split_idx;
7869
7870 Some(Chunk {
7871 text: before,
7872 chars,
7873 tabs,
7874 newlines,
7875 ..chunk.clone()
7876 })
7877 } else {
7878 self.range.start = chunk_end;
7879 self.buffer_chunk.take()
7880 }
7881 }
7882 DiffTransform::DeletedHunk {
7883 buffer_id,
7884 base_text_byte_range,
7885 has_trailing_newline,
7886 ..
7887 } => {
7888 let base_text_start =
7889 base_text_byte_range.start + (self.range.start - diff_transform_start);
7890 let base_text_end =
7891 base_text_byte_range.start + (self.range.end - diff_transform_start);
7892 let base_text_end = base_text_end.min(base_text_byte_range.end);
7893
7894 let mut chunks = if let Some((_, mut chunks)) = self
7895 .diff_base_chunks
7896 .take()
7897 .filter(|(id, _)| id == buffer_id)
7898 {
7899 if chunks.range().start != base_text_start || chunks.range().end < base_text_end
7900 {
7901 chunks.seek(base_text_start..base_text_end);
7902 }
7903 chunks
7904 } else {
7905 let base_buffer = &self.diffs.get(buffer_id)?.base_text();
7906 base_buffer.chunks(base_text_start..base_text_end, self.language_aware)
7907 };
7908
7909 let chunk = if let Some(chunk) = chunks.next() {
7910 self.range.start += chunk.text.len();
7911 self.diff_base_chunks = Some((*buffer_id, chunks));
7912 chunk
7913 } else {
7914 debug_assert!(has_trailing_newline);
7915 self.range.start += "\n".len();
7916 Chunk {
7917 text: "\n",
7918 chars: 1u128,
7919 newlines: 1u128,
7920 ..Default::default()
7921 }
7922 };
7923 Some(chunk)
7924 }
7925 }
7926 }
7927}
7928
7929impl MultiBufferBytes<'_> {
7930 fn consume(&mut self, len: usize) {
7931 self.range.start += len;
7932 self.chunk = &self.chunk[len..];
7933
7934 if !self.range.is_empty() && self.chunk.is_empty() {
7935 if let Some(chunk) = self.excerpt_bytes.as_mut().and_then(|bytes| bytes.next()) {
7936 self.chunk = chunk;
7937 } else if self.has_trailing_newline {
7938 self.has_trailing_newline = false;
7939 self.chunk = b"\n";
7940 } else {
7941 self.cursor.next();
7942 if let Some(region) = self.cursor.region() {
7943 let mut excerpt_bytes = region.buffer.bytes_in_range(
7944 region.buffer_range.start
7945 ..(region.buffer_range.start + (self.range.end - region.range.start))
7946 .min(region.buffer_range.end),
7947 );
7948 self.chunk = excerpt_bytes.next().unwrap_or(&[]);
7949 self.excerpt_bytes = Some(excerpt_bytes);
7950 self.has_trailing_newline =
7951 region.has_trailing_newline && self.range.end >= region.range.end;
7952 if self.chunk.is_empty() && self.has_trailing_newline {
7953 self.has_trailing_newline = false;
7954 self.chunk = b"\n";
7955 }
7956 }
7957 }
7958 }
7959 }
7960}
7961
7962impl<'a> Iterator for MultiBufferBytes<'a> {
7963 type Item = &'a [u8];
7964
7965 fn next(&mut self) -> Option<Self::Item> {
7966 let chunk = self.chunk;
7967 if chunk.is_empty() {
7968 None
7969 } else {
7970 self.consume(chunk.len());
7971 Some(chunk)
7972 }
7973 }
7974}
7975
7976impl io::Read for MultiBufferBytes<'_> {
7977 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
7978 let len = cmp::min(buf.len(), self.chunk.len());
7979 buf[..len].copy_from_slice(&self.chunk[..len]);
7980 if len > 0 {
7981 self.consume(len);
7982 }
7983 Ok(len)
7984 }
7985}
7986
7987impl io::Read for ReversedMultiBufferBytes<'_> {
7988 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
7989 let len = cmp::min(buf.len(), self.chunk.len());
7990 buf[..len].copy_from_slice(&self.chunk[..len]);
7991 buf[..len].reverse();
7992 if len > 0 {
7993 self.range.end -= len;
7994 self.chunk = &self.chunk[..self.chunk.len() - len];
7995 if !self.range.is_empty()
7996 && self.chunk.is_empty()
7997 && let Some(chunk) = self.chunks.next()
7998 {
7999 self.chunk = chunk.as_bytes();
8000 }
8001 }
8002 Ok(len)
8003 }
8004}
8005
8006impl<'a> Iterator for ExcerptChunks<'a> {
8007 type Item = Chunk<'a>;
8008
8009 fn next(&mut self) -> Option<Self::Item> {
8010 if let Some(chunk) = self.content_chunks.next() {
8011 return Some(chunk);
8012 }
8013
8014 if self.has_footer {
8015 let text = "\n";
8016 let chars = 0b1;
8017 let newlines = 0b1;
8018 self.has_footer = false;
8019 return Some(Chunk {
8020 text,
8021 chars,
8022 newlines,
8023 ..Default::default()
8024 });
8025 }
8026
8027 None
8028 }
8029}
8030
8031impl ToOffset for Point {
8032 fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> MultiBufferOffset {
8033 snapshot.point_to_offset(*self)
8034 }
8035 fn to_offset_utf16(&self, snapshot: &MultiBufferSnapshot) -> MultiBufferOffsetUtf16 {
8036 snapshot.point_to_offset_utf16(*self)
8037 }
8038}
8039
8040impl ToOffset for MultiBufferOffset {
8041 #[track_caller]
8042 fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> MultiBufferOffset {
8043 assert!(
8044 *self <= snapshot.len(),
8045 "offset {} is greater than the snapshot.len() {}",
8046 self.0,
8047 snapshot.len().0,
8048 );
8049 *self
8050 }
8051 fn to_offset_utf16(&self, snapshot: &MultiBufferSnapshot) -> MultiBufferOffsetUtf16 {
8052 snapshot.offset_to_offset_utf16(*self)
8053 }
8054}
8055
8056impl ToOffset for MultiBufferOffsetUtf16 {
8057 fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> MultiBufferOffset {
8058 snapshot.offset_utf16_to_offset(*self)
8059 }
8060
8061 fn to_offset_utf16(&self, _snapshot: &MultiBufferSnapshot) -> MultiBufferOffsetUtf16 {
8062 *self
8063 }
8064}
8065
8066impl ToOffset for PointUtf16 {
8067 fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> MultiBufferOffset {
8068 snapshot.point_utf16_to_offset(*self)
8069 }
8070 fn to_offset_utf16(&self, snapshot: &MultiBufferSnapshot) -> MultiBufferOffsetUtf16 {
8071 snapshot.point_utf16_to_offset_utf16(*self)
8072 }
8073}
8074
8075impl ToPoint for MultiBufferOffset {
8076 fn to_point<'a>(&self, snapshot: &MultiBufferSnapshot) -> Point {
8077 snapshot.offset_to_point(*self)
8078 }
8079 fn to_point_utf16<'a>(&self, snapshot: &MultiBufferSnapshot) -> PointUtf16 {
8080 snapshot.offset_to_point_utf16(*self)
8081 }
8082}
8083
8084impl ToPoint for Point {
8085 fn to_point<'a>(&self, _: &MultiBufferSnapshot) -> Point {
8086 *self
8087 }
8088 fn to_point_utf16<'a>(&self, snapshot: &MultiBufferSnapshot) -> PointUtf16 {
8089 snapshot.point_to_point_utf16(*self)
8090 }
8091}
8092
8093impl ToPoint for PointUtf16 {
8094 fn to_point<'a>(&self, snapshot: &MultiBufferSnapshot) -> Point {
8095 snapshot.point_utf16_to_point(*self)
8096 }
8097 fn to_point_utf16<'a>(&self, _: &MultiBufferSnapshot) -> PointUtf16 {
8098 *self
8099 }
8100}
8101
8102#[cfg(debug_assertions)]
8103pub mod debug {
8104 use super::*;
8105
8106 pub trait ToMultiBufferDebugRanges {
8107 fn to_multi_buffer_debug_ranges(
8108 &self,
8109 snapshot: &MultiBufferSnapshot,
8110 ) -> Vec<Range<MultiBufferOffset>>;
8111 }
8112
8113 impl<T: ToOffset> ToMultiBufferDebugRanges for T {
8114 fn to_multi_buffer_debug_ranges(
8115 &self,
8116 snapshot: &MultiBufferSnapshot,
8117 ) -> Vec<Range<MultiBufferOffset>> {
8118 [self.to_offset(snapshot)].to_multi_buffer_debug_ranges(snapshot)
8119 }
8120 }
8121
8122 impl<T: ToOffset> ToMultiBufferDebugRanges for Range<T> {
8123 fn to_multi_buffer_debug_ranges(
8124 &self,
8125 snapshot: &MultiBufferSnapshot,
8126 ) -> Vec<Range<MultiBufferOffset>> {
8127 [self.start.to_offset(snapshot)..self.end.to_offset(snapshot)]
8128 .to_multi_buffer_debug_ranges(snapshot)
8129 }
8130 }
8131
8132 impl<T: ToOffset> ToMultiBufferDebugRanges for Vec<T> {
8133 fn to_multi_buffer_debug_ranges(
8134 &self,
8135 snapshot: &MultiBufferSnapshot,
8136 ) -> Vec<Range<MultiBufferOffset>> {
8137 self.as_slice().to_multi_buffer_debug_ranges(snapshot)
8138 }
8139 }
8140
8141 impl<T: ToOffset> ToMultiBufferDebugRanges for Vec<Range<T>> {
8142 fn to_multi_buffer_debug_ranges(
8143 &self,
8144 snapshot: &MultiBufferSnapshot,
8145 ) -> Vec<Range<MultiBufferOffset>> {
8146 self.as_slice().to_multi_buffer_debug_ranges(snapshot)
8147 }
8148 }
8149
8150 impl<T: ToOffset> ToMultiBufferDebugRanges for [T] {
8151 fn to_multi_buffer_debug_ranges(
8152 &self,
8153 snapshot: &MultiBufferSnapshot,
8154 ) -> Vec<Range<MultiBufferOffset>> {
8155 self.iter()
8156 .map(|item| {
8157 let offset = item.to_offset(snapshot);
8158 offset..offset
8159 })
8160 .collect()
8161 }
8162 }
8163
8164 impl<T: ToOffset> ToMultiBufferDebugRanges for [Range<T>] {
8165 fn to_multi_buffer_debug_ranges(
8166 &self,
8167 snapshot: &MultiBufferSnapshot,
8168 ) -> Vec<Range<MultiBufferOffset>> {
8169 self.iter()
8170 .map(|range| range.start.to_offset(snapshot)..range.end.to_offset(snapshot))
8171 .collect()
8172 }
8173 }
8174}