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