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