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