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