path_key.rs

  1use std::{ops::Range, rc::Rc, sync::Arc};
  2
  3use gpui::{App, AppContext, Context, Entity};
  4use itertools::Itertools;
  5use language::{Buffer, BufferSnapshot};
  6use rope::Point;
  7use sum_tree::{Dimensions, SumTree};
  8use text::{Bias, BufferId, Edit, OffsetRangeExt, Patch};
  9use util::rel_path::RelPath;
 10use ztracing::instrument;
 11
 12use crate::{
 13    Anchor, BufferState, BufferStateSnapshot, DiffChangeKind, Event, Excerpt, ExcerptOffset,
 14    ExcerptRange, ExcerptSummary, ExpandExcerptDirection, MultiBuffer, MultiBufferOffset,
 15    PathKeyIndex, build_excerpt_ranges, remove_diff_state,
 16};
 17
 18#[derive(PartialEq, Eq, Ord, PartialOrd, Clone, Hash, Debug)]
 19pub struct PathKey {
 20    // Used by the derived PartialOrd & Ord
 21    pub sort_prefix: Option<u64>,
 22    pub path: Arc<RelPath>,
 23}
 24
 25impl PathKey {
 26    pub fn min() -> Self {
 27        Self {
 28            sort_prefix: None,
 29            path: RelPath::empty().into_arc(),
 30        }
 31    }
 32
 33    pub fn sorted(sort_prefix: u64) -> Self {
 34        Self {
 35            sort_prefix: Some(sort_prefix),
 36            path: RelPath::empty().into_arc(),
 37        }
 38    }
 39    pub fn with_sort_prefix(sort_prefix: u64, path: Arc<RelPath>) -> Self {
 40        Self {
 41            sort_prefix: Some(sort_prefix),
 42            path,
 43        }
 44    }
 45
 46    pub fn for_buffer(buffer: &Entity<Buffer>, cx: &App) -> Self {
 47        if let Some(file) = buffer.read(cx).file() {
 48            Self::with_sort_prefix(file.worktree_id(cx).to_proto(), file.path().clone())
 49        } else {
 50            Self {
 51                sort_prefix: None,
 52                path: RelPath::unix(&buffer.entity_id().to_string())
 53                    .unwrap()
 54                    .into_arc(),
 55            }
 56        }
 57    }
 58}
 59
 60impl MultiBuffer {
 61    pub fn buffer_for_path(&self, path: &PathKey, cx: &App) -> Option<Entity<Buffer>> {
 62        let snapshot = self.snapshot(cx);
 63        let excerpt = snapshot.excerpts_for_path(path).next()?;
 64        self.buffer(excerpt.context.start.buffer_id)
 65    }
 66
 67    pub fn location_for_path(&self, path: &PathKey, cx: &App) -> Option<Anchor> {
 68        let snapshot = self.snapshot(cx);
 69        let excerpt = snapshot.excerpts_for_path(path).next()?;
 70        let path_key_index = snapshot.path_key_index_for_buffer(excerpt.context.start.buffer_id)?;
 71        Some(Anchor::in_buffer(path_key_index, excerpt.context.start))
 72    }
 73
 74    pub fn set_excerpts_for_buffer(
 75        &mut self,
 76        buffer: Entity<Buffer>,
 77        ranges: impl IntoIterator<Item = Range<Point>>,
 78        context_line_count: u32,
 79        cx: &mut Context<Self>,
 80    ) -> bool {
 81        let path = PathKey::for_buffer(&buffer, cx);
 82        self.set_excerpts_for_path(path, buffer, ranges, context_line_count, cx)
 83    }
 84
 85    /// Sets excerpts, returns `true` if at least one new excerpt was added.
 86    ///
 87    /// Any existing excerpts for this buffer or this path will be replaced by the provided ranges.
 88    #[instrument(skip_all)]
 89    pub fn set_excerpts_for_path(
 90        &mut self,
 91        path: PathKey,
 92        buffer: Entity<Buffer>,
 93        ranges: impl IntoIterator<Item = Range<Point>>,
 94        context_line_count: u32,
 95        cx: &mut Context<Self>,
 96    ) -> bool {
 97        let buffer_snapshot = buffer.read(cx).snapshot();
 98        let ranges: Vec<_> = ranges.into_iter().collect();
 99        let excerpt_ranges = build_excerpt_ranges(ranges, context_line_count, &buffer_snapshot);
100
101        let merged = Self::merge_excerpt_ranges(&excerpt_ranges);
102        let (inserted, _path_key_index) =
103            let excerpt_insertion_result = self.set_merged_excerpt_ranges_for_path(path, buffer, &buffer_snapshot, merged, cx);
104        inserted
105    }
106
107    /// Like [`Self::set_excerpts_for_path`], but expands the provided ranges to cover any overlapping existing excerpts
108    /// for the same buffer and path.
109    ///
110    /// Existing excerpts that do not overlap any of the provided ranges are discarded.
111    pub fn update_excerpts_for_path(
112        &mut self,
113        path: PathKey,
114        buffer: Entity<Buffer>,
115        ranges: impl IntoIterator<Item = Range<Point>>,
116        context_line_count: u32,
117        cx: &mut Context<Self>,
118    ) -> bool {
119        let buffer_snapshot = buffer.read(cx).snapshot();
120        let ranges: Vec<_> = ranges.into_iter().collect();
121        let excerpt_ranges = build_excerpt_ranges(ranges, context_line_count, &buffer_snapshot);
122        let merged = self.merge_new_with_existing_excerpt_ranges(
123            &path,
124            &buffer_snapshot,
125            excerpt_ranges,
126            cx,
127        );
128
129        let (inserted, _path_key_index) =
130            self.set_merged_excerpt_ranges_for_path(path, buffer, &buffer_snapshot, merged, cx);
131        inserted
132    }
133
134    pub fn merge_new_with_existing_excerpt_ranges(
135        &self,
136        path: &PathKey,
137        buffer_snapshot: &BufferSnapshot,
138        mut excerpt_ranges: Vec<ExcerptRange<Point>>,
139        cx: &App,
140    ) -> Vec<ExcerptRange<Point>> {
141        let multibuffer_snapshot = self.snapshot(cx);
142
143        if multibuffer_snapshot.path_for_buffer(buffer_snapshot.remote_id()) == Some(path) {
144            excerpt_ranges.sort_by_key(|range| range.context.start);
145            let mut combined_ranges = Vec::new();
146            let mut new_ranges = excerpt_ranges.into_iter().peekable();
147            for existing_range in
148                multibuffer_snapshot.excerpts_for_buffer(buffer_snapshot.remote_id())
149            {
150                let existing_range = ExcerptRange {
151                    context: existing_range.context.to_point(buffer_snapshot),
152                    primary: existing_range.primary.to_point(buffer_snapshot),
153                };
154                while let Some(new_range) = new_ranges.peek()
155                    && new_range.context.end < existing_range.context.start
156                {
157                    combined_ranges.push(new_range.clone());
158                    new_ranges.next();
159                }
160
161                if let Some(new_range) = new_ranges.peek()
162                    && new_range.context.start <= existing_range.context.end
163                {
164                    combined_ranges.push(existing_range)
165                }
166            }
167            combined_ranges.extend(new_ranges);
168            excerpt_ranges = combined_ranges;
169        }
170
171        excerpt_ranges.sort_by_key(|range| range.context.start);
172        Self::merge_excerpt_ranges(&excerpt_ranges)
173    }
174
175    pub fn set_excerpt_ranges_for_path(
176        &mut self,
177        path: PathKey,
178        buffer: Entity<Buffer>,
179        buffer_snapshot: &BufferSnapshot,
180        excerpt_ranges: Vec<ExcerptRange<Point>>,
181        cx: &mut Context<Self>,
182    ) -> bool {
183        let merged = Self::merge_excerpt_ranges(&excerpt_ranges);
184        let (inserted, _path_key_index) =
185            self.set_merged_excerpt_ranges_for_path(path, buffer, buffer_snapshot, merged, cx);
186        inserted
187    }
188
189    pub fn set_anchored_excerpts_for_path(
190        &self,
191        path_key: PathKey,
192        buffer: Entity<Buffer>,
193        ranges: Vec<Range<text::Anchor>>,
194        context_line_count: u32,
195        cx: &Context<Self>,
196    ) -> impl Future<Output = Vec<Range<Anchor>>> + use<> {
197        let buffer_snapshot = buffer.read(cx).snapshot();
198        let multi_buffer = cx.weak_entity();
199        let mut app = cx.to_async();
200        async move {
201            let snapshot = buffer_snapshot.clone();
202            let (ranges, merged_excerpt_ranges) = app
203                .background_spawn(async move {
204                    let point_ranges = ranges.iter().map(|range| range.to_point(&snapshot));
205                    let excerpt_ranges =
206                        build_excerpt_ranges(point_ranges, context_line_count, &snapshot);
207                    let merged = Self::merge_excerpt_ranges(&excerpt_ranges);
208                    (ranges, merged)
209                })
210                .await;
211
212            multi_buffer
213                .update(&mut app, move |multi_buffer, cx| {
214                    let (_, path_key_index) = multi_buffer.set_merged_excerpt_ranges_for_path(
215                        path_key,
216                        buffer,
217                        &buffer_snapshot,
218                        merged_excerpt_ranges,
219                        cx,
220                    );
221                    ranges
222                        .into_iter()
223                        .map(|range| Anchor::range_in_buffer(path_key_index, range))
224                        .collect()
225                })
226                .ok()
227                .unwrap_or_default()
228        }
229    }
230
231    pub fn expand_excerpts(
232        &mut self,
233        anchors: impl IntoIterator<Item = Anchor>,
234        line_count: u32,
235        direction: ExpandExcerptDirection,
236        cx: &mut Context<Self>,
237    ) {
238        if line_count == 0 {
239            return;
240        }
241
242        let snapshot = self.snapshot(cx);
243        let mut sorted_anchors = anchors
244            .into_iter()
245            .filter_map(|anchor| anchor.excerpt_anchor())
246            .collect::<Vec<_>>();
247        if sorted_anchors.is_empty() {
248            return;
249        }
250        sorted_anchors.sort_by(|a, b| a.cmp(b, &snapshot));
251        let buffers = sorted_anchors.into_iter().chunk_by(|anchor| anchor.path);
252        let mut cursor = snapshot.excerpts.cursor::<ExcerptSummary>(());
253
254        for (path_index, excerpt_anchors) in &buffers {
255            let path = snapshot
256                .path_keys_by_index
257                .get(&path_index)
258                .expect("anchor from wrong multibuffer");
259
260            let mut excerpt_anchors = excerpt_anchors.peekable();
261            let mut ranges = Vec::new();
262
263            cursor.seek_forward(path, Bias::Left);
264            let Some((buffer, buffer_snapshot)) = cursor
265                .item()
266                .map(|excerpt| (excerpt.buffer(&self), excerpt.buffer_snapshot(&snapshot)))
267            else {
268                continue;
269            };
270
271            while let Some(excerpt) = cursor.item()
272                && &excerpt.path_key == path
273            {
274                let mut range = ExcerptRange {
275                    context: excerpt.range.context.to_point(buffer_snapshot),
276                    primary: excerpt.range.primary.to_point(buffer_snapshot),
277                };
278
279                let mut needs_expand = false;
280                while excerpt_anchors.peek().is_some_and(|anchor| {
281                    excerpt
282                        .range
283                        .contains(&anchor.text_anchor(), buffer_snapshot)
284                }) {
285                    needs_expand = true;
286                    excerpt_anchors.next();
287                }
288
289                if needs_expand {
290                    match direction {
291                        ExpandExcerptDirection::Up => {
292                            range.context.start.row =
293                                range.context.start.row.saturating_sub(line_count);
294                            range.context.start.column = 0;
295                        }
296                        ExpandExcerptDirection::Down => {
297                            range.context.end.row = (range.context.end.row + line_count)
298                                .min(excerpt.buffer_snapshot(&snapshot).max_point().row);
299                            range.context.end.column = excerpt
300                                .buffer_snapshot(&snapshot)
301                                .line_len(range.context.end.row);
302                        }
303                        ExpandExcerptDirection::UpAndDown => {
304                            range.context.start.row =
305                                range.context.start.row.saturating_sub(line_count);
306                            range.context.start.column = 0;
307                            range.context.end.row = (range.context.end.row + line_count)
308                                .min(excerpt.buffer_snapshot(&snapshot).max_point().row);
309                            range.context.end.column = excerpt
310                                .buffer_snapshot(&snapshot)
311                                .line_len(range.context.end.row);
312                        }
313                    }
314                }
315
316                ranges.push(range);
317                cursor.next();
318            }
319
320            ranges.sort_by(|l, r| l.context.start.cmp(&r.context.start));
321
322            self.set_excerpt_ranges_for_path(path.clone(), buffer, buffer_snapshot, ranges, cx);
323        }
324    }
325
326    /// Sets excerpts, returns `true` if at least one new excerpt was added.
327    pub(crate) fn set_merged_excerpt_ranges_for_path<T>(
328        &mut self,
329        path: PathKey,
330        buffer: Entity<Buffer>,
331        buffer_snapshot: &BufferSnapshot,
332        new: Vec<ExcerptRange<T>>,
333        cx: &mut Context<Self>,
334    ) -> (bool, PathKeyIndex)
335    where
336        T: language::ToOffset,
337    {
338        let anchor_ranges = new
339            .into_iter()
340            .map(|r| ExcerptRange {
341                context: buffer_snapshot.anchor_before(r.context.start)
342                    ..buffer_snapshot.anchor_after(r.context.end),
343                primary: buffer_snapshot.anchor_before(r.primary.start)
344                    ..buffer_snapshot.anchor_after(r.primary.end),
345            })
346            .collect::<Vec<_>>();
347        let inserted =
348            self.update_path_excerpts(path.clone(), buffer, buffer_snapshot, &anchor_ranges, cx);
349        let path_key_index = self.get_or_create_path_key_index(&path);
350        (inserted, path_key_index)
351    }
352
353    pub(crate) fn get_or_create_path_key_index(&mut self, path_key: &PathKey) -> PathKeyIndex {
354        let mut snapshot = self.snapshot.borrow_mut();
355
356        if let Some(&existing) = snapshot.indices_by_path_key.get(path_key) {
357            return existing;
358        }
359
360        let index = snapshot
361            .path_keys_by_index
362            .last()
363            .map(|(index, _)| PathKeyIndex(index.0 + 1))
364            .unwrap_or(PathKeyIndex(0));
365        snapshot.path_keys_by_index.insert(index, path_key.clone());
366        snapshot.indices_by_path_key.insert(path_key.clone(), index);
367        index
368    }
369
370    /// Expand each new merged range to encompass any overlapping existing
371    /// excerpt, then re-merge. This turns "partial overlap where the union
372    /// equals the existing range" into an exact match, avoiding unnecessary
373    /// remove+insert churn that floods the wrap map with edits.
374    fn expand_new_ranges_to_existing(
375        &self,
376        path: &PathKey,
377        buffer_snapshot: &BufferSnapshot,
378        mut new: Vec<ExcerptRange<Point>>,
379        counts: Vec<usize>,
380        cx: &App,
381    ) -> (Vec<ExcerptRange<Point>>, Vec<usize>) {
382        let existing = self.excerpts_by_path.get(path).cloned().unwrap_or_default();
383        if existing.is_empty() || new.is_empty() {
384            return (new, counts);
385        }
386
387        let snapshot = self.snapshot(cx);
388        let buffer_id = buffer_snapshot.remote_id();
389        let existing_ranges: Vec<Range<Point>> = existing
390            .iter()
391            .filter_map(|&id| {
392                let excerpt = snapshot.excerpt(id)?;
393                (excerpt.buffer_id == buffer_id)
394                    .then(|| excerpt.range.context.to_point(buffer_snapshot))
395            })
396            .collect();
397
398        let mut changed = false;
399        for new_range in &mut new {
400            for existing_range in &existing_ranges {
401                if new_range.context.start <= existing_range.end
402                    && new_range.context.end >= existing_range.start
403                {
404                    let expanded_start = new_range.context.start.min(existing_range.start);
405                    let expanded_end = new_range.context.end.max(existing_range.end);
406                    if expanded_start != new_range.context.start
407                        || expanded_end != new_range.context.end
408                    {
409                        new_range.context.start = expanded_start;
410                        new_range.context.end = expanded_end;
411                        changed = true;
412                    }
413                }
414            }
415        }
416
417        if !changed {
418            return (new, counts);
419        }
420
421        let mut result_ranges: Vec<ExcerptRange<Point>> = Vec::new();
422        let mut result_counts: Vec<usize> = Vec::new();
423        for (range, count) in new.into_iter().zip(counts) {
424            if let Some(last) = result_ranges.last_mut() {
425                if last.context.end >= range.context.start
426                    || last.context.end.row + 1 == range.context.start.row
427                {
428                    last.context.end = last.context.end.max(range.context.end);
429                    *result_counts.last_mut().unwrap() += count;
430                    continue;
431                }
432            }
433            result_ranges.push(range);
434            result_counts.push(count);
435        }
436
437        (result_ranges, result_counts)
438    }
439
440    fn update_path_excerpts(
441        &mut self,
442        path_key: PathKey,
443        buffer: Entity<Buffer>,
444        buffer_snapshot: &BufferSnapshot,
445        to_insert: &Vec<ExcerptRange<text::Anchor>>,
446        cx: &mut Context<Self>,
447    ) -> bool {
448        let path_key_index = self.get_or_create_path_key_index(&path_key);
449        if let Some(old_path_key) = self
450            .snapshot(cx)
451            .path_for_buffer(buffer_snapshot.remote_id())
452            && old_path_key != &path_key
453        {
454            self.remove_excerpts(old_path_key.clone(), cx);
455        }
456
457        if to_insert.len() == 0 {
458            self.remove_excerpts(path_key.clone(), cx);
459
460            return false;
461        }
462        assert_eq!(self.history.transaction_depth(), 0);
463        self.sync_mut(cx);
464
465        let buffer_id = buffer_snapshot.remote_id();
466
467        let mut snapshot = self.snapshot.get_mut();
468        let mut cursor = snapshot
469            .excerpts
470            .cursor::<Dimensions<PathKey, ExcerptOffset>>(());
471        let mut new_excerpts = SumTree::new(());
472
473        let new_ranges = to_insert.clone();
474        let mut to_insert = to_insert.iter().peekable();
475        let mut patch = Patch::empty();
476        let mut added_new_excerpt = false;
477
478        new_excerpts.append(cursor.slice(&path_key, Bias::Left), ());
479
480        // handle the case where the path key used to be associated
481        // with a different buffer by removing its excerpts.
482        if let Some(excerpt) = cursor.item()
483            && &excerpt.path_key == &path_key
484            && excerpt.buffer_id != buffer_id
485        {
486            let old_buffer_id = excerpt.buffer_id;
487            self.buffers.remove(&old_buffer_id);
488            snapshot.buffers.remove(&old_buffer_id);
489            remove_diff_state(&mut snapshot.diffs, old_buffer_id);
490            self.diffs.remove(&old_buffer_id);
491            let before = cursor.position.1;
492            cursor.seek_forward(&path_key, Bias::Right);
493            let after = cursor.position.1;
494            patch.push(Edit {
495                old: before..after,
496                new: new_excerpts.summary().len()..new_excerpts.summary().len(),
497            });
498            cx.emit(Event::BuffersRemoved {
499                removed_buffer_ids: vec![old_buffer_id],
500            });
501        }
502
503        while let Some(excerpt) = cursor.item()
504            && excerpt.path_key == path_key
505        {
506            assert_eq!(excerpt.buffer_id, buffer_id);
507            let Some(next_excerpt) = to_insert.peek() else {
508                break;
509            };
510            if &excerpt.range == *next_excerpt {
511                let before = new_excerpts.summary().len();
512                new_excerpts.update_last(
513                    |prev_excerpt| {
514                        if !prev_excerpt.has_trailing_newline {
515                            prev_excerpt.has_trailing_newline = true;
516                            patch.push(Edit {
517                                old: cursor.position.1..cursor.position.1,
518                                new: before..before + MultiBufferOffset(1),
519                            });
520                        }
521                    },
522                    (),
523                );
524                new_excerpts.push(excerpt.clone(), ());
525                to_insert.next();
526                cursor.next();
527                continue;
528            }
529
530            if excerpt
531                .range
532                .context
533                .start
534                .cmp(&next_excerpt.context.start, &buffer_snapshot)
535                .is_le()
536            {
537                // remove old excerpt
538                let before = cursor.position.1;
539                cursor.next();
540                let after = cursor.position.1;
541                patch.push(Edit {
542                    old: before..after,
543                    new: new_excerpts.summary().len()..new_excerpts.summary().len(),
544                });
545            } else {
546                // insert new excerpt
547                let next_excerpt = to_insert.next().unwrap();
548                added_new_excerpt = true;
549                let before = new_excerpts.summary().len();
550                new_excerpts.update_last(
551                    |prev_excerpt| {
552                        prev_excerpt.has_trailing_newline = true;
553                    },
554                    (),
555                );
556                new_excerpts.push(
557                    Excerpt::new(
558                        path_key.clone(),
559                        path_key_index,
560                        &buffer_snapshot,
561                        next_excerpt.clone(),
562                        false,
563                    ),
564                    (),
565                );
566                let after = new_excerpts.summary().len();
567                patch.push_maybe_empty(Edit {
568                    old: cursor.position.1..cursor.position.1,
569                    new: before..after,
570                });
571            }
572        }
573
574        // remove any further trailing excerpts
575        let mut before = cursor.position.1;
576        cursor.seek_forward(&path_key, Bias::Right);
577        let after = cursor.position.1;
578        // if we removed the previous last excerpt, remove the trailing newline from the new last excerpt
579        if cursor.item().is_none() && to_insert.peek().is_none() {
580            new_excerpts.update_last(
581                |excerpt| {
582                    if excerpt.has_trailing_newline {
583                        before.0.0 = before
584                            .0
585                            .0
586                            .checked_sub(1)
587                            .expect("should have preceding excerpt");
588                        excerpt.has_trailing_newline = false;
589                    }
590                },
591                (),
592            );
593        }
594        patch.push(Edit {
595            old: before..after,
596            new: new_excerpts.summary().len()..new_excerpts.summary().len(),
597        });
598
599        while let Some(next_excerpt) = to_insert.next() {
600            added_new_excerpt = true;
601            let before = new_excerpts.summary().len();
602            new_excerpts.update_last(
603                |prev_excerpt| {
604                    prev_excerpt.has_trailing_newline = true;
605                },
606                (),
607            );
608            new_excerpts.push(
609                Excerpt::new(
610                    path_key.clone(),
611                    path_key_index,
612                    &buffer_snapshot,
613                    next_excerpt.clone(),
614                    false,
615                ),
616                (),
617            );
618            let after = new_excerpts.summary().len();
619            patch.push_maybe_empty(Edit {
620                old: cursor.position.1..cursor.position.1,
621                new: before..after,
622            });
623        }
624
625        let suffix_start = cursor.position.1;
626        let suffix = cursor.suffix();
627        let changed_trailing_excerpt = suffix.is_empty();
628        if !suffix.is_empty() {
629            let before = new_excerpts.summary().len();
630            new_excerpts.update_last(
631                |prev_excerpt| {
632                    if !prev_excerpt.has_trailing_newline {
633                        prev_excerpt.has_trailing_newline = true;
634                        patch.push(Edit {
635                            old: suffix_start..suffix_start,
636                            new: before..before + MultiBufferOffset(1),
637                        });
638                    }
639                },
640                (),
641            );
642        }
643        new_excerpts.append(suffix, ());
644        drop(cursor);
645
646        snapshot.excerpts = new_excerpts;
647        snapshot.buffers.insert(
648            buffer_id,
649            BufferStateSnapshot {
650                path_key: path_key.clone(),
651                path_key_index,
652                buffer_snapshot: buffer_snapshot.clone(),
653            },
654        );
655
656        self.buffers.entry(buffer_id).or_insert_with(|| {
657            self.buffer_changed_since_sync.replace(true);
658            buffer.update(cx, |buffer, _| {
659                buffer.record_changes(Rc::downgrade(&self.buffer_changed_since_sync));
660            });
661            BufferState {
662                _subscriptions: [
663                    cx.observe(&buffer, |_, _, cx| cx.notify()),
664                    cx.subscribe(&buffer, Self::on_buffer_event),
665                ],
666                buffer: buffer.clone(),
667            }
668        });
669
670        if changed_trailing_excerpt {
671            snapshot.trailing_excerpt_update_count += 1;
672        }
673
674        let edits = Self::sync_diff_transforms(
675            &mut snapshot,
676            patch.into_inner(),
677            DiffChangeKind::BufferEdited,
678        );
679        if !edits.is_empty() {
680            self.subscriptions.publish(edits);
681        }
682
683        cx.emit(Event::Edited {
684            edited_buffer: None,
685            is_local: true,
686        });
687        cx.emit(Event::BufferRangesUpdated {
688            buffer,
689            path_key: path_key.clone(),
690            ranges: new_ranges,
691        });
692        cx.notify();
693
694        added_new_excerpt
695    }
696
697    pub fn remove_excerpts_for_buffer(&mut self, buffer: BufferId, cx: &mut Context<Self>) {
698        let snapshot = self.sync_mut(cx);
699        let Some(path) = snapshot.path_for_buffer(buffer).cloned() else {
700            return;
701        };
702        self.remove_excerpts(path, cx);
703    }
704
705    pub fn remove_excerpts(&mut self, path: PathKey, cx: &mut Context<Self>) {
706        assert_eq!(self.history.transaction_depth(), 0);
707        self.sync_mut(cx);
708
709        let mut snapshot = self.snapshot.get_mut();
710        let mut cursor = snapshot
711            .excerpts
712            .cursor::<Dimensions<PathKey, ExcerptOffset>>(());
713        let mut new_excerpts = SumTree::new(());
714        new_excerpts.append(cursor.slice(&path, Bias::Left), ());
715        let mut edit_start = cursor.position.1;
716        let mut buffer_id = None;
717        if let Some(excerpt) = cursor.item()
718            && excerpt.path_key == path
719        {
720            buffer_id = Some(excerpt.buffer_id);
721        }
722        cursor.seek(&path, Bias::Right);
723        let edit_end = cursor.position.1;
724        let suffix = cursor.suffix();
725        let changed_trailing_excerpt = suffix.is_empty();
726        new_excerpts.append(suffix, ());
727
728        if let Some(buffer_id) = buffer_id {
729            snapshot.buffers.remove(&buffer_id);
730            remove_diff_state(&mut snapshot.diffs, buffer_id);
731            self.buffers.remove(&buffer_id);
732            self.diffs.remove(&buffer_id);
733            cx.emit(Event::BuffersRemoved {
734                removed_buffer_ids: vec![buffer_id],
735            })
736        }
737        drop(cursor);
738        if changed_trailing_excerpt {
739            snapshot.trailing_excerpt_update_count += 1;
740            new_excerpts.update_last(
741                |excerpt| {
742                    if excerpt.has_trailing_newline {
743                        excerpt.has_trailing_newline = false;
744                        edit_start.0.0 = edit_start
745                            .0
746                            .0
747                            .checked_sub(1)
748                            .expect("should have at least one excerpt");
749                    }
750                },
751                (),
752            )
753        }
754
755        let edit = Edit {
756            old: edit_start..edit_end,
757            new: edit_start..edit_start,
758        };
759        snapshot.excerpts = new_excerpts;
760
761        let edits =
762            Self::sync_diff_transforms(&mut snapshot, vec![edit], DiffChangeKind::BufferEdited);
763        if !edits.is_empty() {
764            self.subscriptions.publish(edits);
765        }
766
767        cx.emit(Event::Edited {
768            edited_buffer: None,
769            is_local: true,
770        });
771        cx.notify();
772    }
773}