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            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    pub fn update_path_excerpts(
371        &mut self,
372        path_key: PathKey,
373        buffer: Entity<Buffer>,
374        buffer_snapshot: &BufferSnapshot,
375        to_insert: &Vec<ExcerptRange<text::Anchor>>,
376        cx: &mut Context<Self>,
377    ) -> bool {
378        let path_key_index = self.get_or_create_path_key_index(&path_key);
379        if let Some(old_path_key) = self
380            .snapshot(cx)
381            .path_for_buffer(buffer_snapshot.remote_id())
382            && old_path_key != &path_key
383        {
384            self.remove_excerpts(old_path_key.clone(), cx);
385        }
386
387        if to_insert.len() == 0 {
388            self.remove_excerpts(path_key.clone(), cx);
389
390            return false;
391        }
392        assert_eq!(self.history.transaction_depth(), 0);
393        self.sync_mut(cx);
394
395        let buffer_id = buffer_snapshot.remote_id();
396
397        let mut snapshot = self.snapshot.get_mut();
398        let mut cursor = snapshot
399            .excerpts
400            .cursor::<Dimensions<PathKey, ExcerptOffset>>(());
401        let mut new_excerpts = SumTree::new(());
402
403        let new_ranges = to_insert.clone();
404        let mut to_insert = to_insert.iter().peekable();
405        let mut patch = Patch::empty();
406        let mut added_new_excerpt = false;
407
408        new_excerpts.append(cursor.slice(&path_key, Bias::Left), ());
409
410        // handle the case where the path key used to be associated
411        // with a different buffer by removing its excerpts.
412        if let Some(excerpt) = cursor.item()
413            && &excerpt.path_key == &path_key
414            && excerpt.buffer_id != buffer_id
415        {
416            let old_buffer_id = excerpt.buffer_id;
417            self.buffers.remove(&old_buffer_id);
418            snapshot.buffers.remove(&old_buffer_id);
419            remove_diff_state(&mut snapshot.diffs, old_buffer_id);
420            self.diffs.remove(&old_buffer_id);
421            let before = cursor.position.1;
422            cursor.seek_forward(&path_key, Bias::Right);
423            let after = cursor.position.1;
424            patch.push(Edit {
425                old: before..after,
426                new: new_excerpts.summary().len()..new_excerpts.summary().len(),
427            });
428            cx.emit(Event::BuffersRemoved {
429                removed_buffer_ids: vec![old_buffer_id],
430            });
431        }
432
433        while let Some(excerpt) = cursor.item()
434            && excerpt.path_key == path_key
435        {
436            assert_eq!(excerpt.buffer_id, buffer_id);
437            let Some(next_excerpt) = to_insert.peek() else {
438                break;
439            };
440            if &excerpt.range == *next_excerpt {
441                let before = new_excerpts.summary().len();
442                new_excerpts.update_last(
443                    |prev_excerpt| {
444                        if !prev_excerpt.has_trailing_newline {
445                            prev_excerpt.has_trailing_newline = true;
446                            patch.push(Edit {
447                                old: cursor.position.1..cursor.position.1,
448                                new: before..before + MultiBufferOffset(1),
449                            });
450                        }
451                    },
452                    (),
453                );
454                new_excerpts.push(excerpt.clone(), ());
455                to_insert.next();
456                cursor.next();
457                continue;
458            }
459
460            if excerpt
461                .range
462                .context
463                .start
464                .cmp(&next_excerpt.context.start, &buffer_snapshot)
465                .is_le()
466            {
467                // remove old excerpt
468                let before = cursor.position.1;
469                cursor.next();
470                let after = cursor.position.1;
471                patch.push(Edit {
472                    old: before..after,
473                    new: new_excerpts.summary().len()..new_excerpts.summary().len(),
474                });
475            } else {
476                // insert new excerpt
477                let next_excerpt = to_insert.next().unwrap();
478                added_new_excerpt = true;
479                let before = new_excerpts.summary().len();
480                new_excerpts.update_last(
481                    |prev_excerpt| {
482                        prev_excerpt.has_trailing_newline = true;
483                    },
484                    (),
485                );
486                new_excerpts.push(
487                    Excerpt::new(
488                        path_key.clone(),
489                        path_key_index,
490                        &buffer_snapshot,
491                        next_excerpt.clone(),
492                        false,
493                    ),
494                    (),
495                );
496                let after = new_excerpts.summary().len();
497                patch.push_maybe_empty(Edit {
498                    old: cursor.position.1..cursor.position.1,
499                    new: before..after,
500                });
501            }
502        }
503
504        // remove any further trailing excerpts
505        let mut before = cursor.position.1;
506        cursor.seek_forward(&path_key, Bias::Right);
507        let after = cursor.position.1;
508        // if we removed the previous last excerpt, remove the trailing newline from the new last excerpt
509        if cursor.item().is_none() && to_insert.peek().is_none() {
510            new_excerpts.update_last(
511                |excerpt| {
512                    if excerpt.has_trailing_newline {
513                        before.0.0 = before
514                            .0
515                            .0
516                            .checked_sub(1)
517                            .expect("should have preceding excerpt");
518                        excerpt.has_trailing_newline = false;
519                    }
520                },
521                (),
522            );
523        }
524        patch.push(Edit {
525            old: before..after,
526            new: new_excerpts.summary().len()..new_excerpts.summary().len(),
527        });
528
529        while let Some(next_excerpt) = to_insert.next() {
530            added_new_excerpt = true;
531            let before = new_excerpts.summary().len();
532            new_excerpts.update_last(
533                |prev_excerpt| {
534                    prev_excerpt.has_trailing_newline = true;
535                },
536                (),
537            );
538            new_excerpts.push(
539                Excerpt::new(
540                    path_key.clone(),
541                    path_key_index,
542                    &buffer_snapshot,
543                    next_excerpt.clone(),
544                    false,
545                ),
546                (),
547            );
548            let after = new_excerpts.summary().len();
549            patch.push_maybe_empty(Edit {
550                old: cursor.position.1..cursor.position.1,
551                new: before..after,
552            });
553        }
554
555        let suffix_start = cursor.position.1;
556        let suffix = cursor.suffix();
557        let changed_trailing_excerpt = suffix.is_empty();
558        if !suffix.is_empty() {
559            let before = new_excerpts.summary().len();
560            new_excerpts.update_last(
561                |prev_excerpt| {
562                    if !prev_excerpt.has_trailing_newline {
563                        prev_excerpt.has_trailing_newline = true;
564                        patch.push(Edit {
565                            old: suffix_start..suffix_start,
566                            new: before..before + MultiBufferOffset(1),
567                        });
568                    }
569                },
570                (),
571            );
572        }
573        new_excerpts.append(suffix, ());
574        drop(cursor);
575
576        snapshot.excerpts = new_excerpts;
577        snapshot.buffers.insert(
578            buffer_id,
579            BufferStateSnapshot {
580                path_key: path_key.clone(),
581                path_key_index,
582                buffer_snapshot: buffer_snapshot.clone(),
583            },
584        );
585
586        self.buffers.entry(buffer_id).or_insert_with(|| {
587            self.buffer_changed_since_sync.replace(true);
588            buffer.update(cx, |buffer, _| {
589                buffer.record_changes(Rc::downgrade(&self.buffer_changed_since_sync));
590            });
591            BufferState {
592                _subscriptions: [
593                    cx.observe(&buffer, |_, _, cx| cx.notify()),
594                    cx.subscribe(&buffer, Self::on_buffer_event),
595                ],
596                buffer: buffer.clone(),
597            }
598        });
599
600        if changed_trailing_excerpt {
601            snapshot.trailing_excerpt_update_count += 1;
602        }
603
604        let edits = Self::sync_diff_transforms(
605            &mut snapshot,
606            patch.into_inner(),
607            DiffChangeKind::BufferEdited,
608        );
609        if !edits.is_empty() {
610            self.subscriptions.publish(edits);
611        }
612
613        cx.emit(Event::Edited {
614            edited_buffer: None,
615            is_local: true,
616        });
617        cx.emit(Event::BufferRangesUpdated {
618            buffer,
619            path_key: path_key.clone(),
620            ranges: new_ranges,
621        });
622        cx.notify();
623
624        added_new_excerpt
625    }
626
627    pub fn remove_excerpts_for_buffer(&mut self, buffer: BufferId, cx: &mut Context<Self>) {
628        let snapshot = self.sync_mut(cx);
629        let Some(path) = snapshot.path_for_buffer(buffer).cloned() else {
630            return;
631        };
632        self.remove_excerpts(path, cx);
633    }
634
635    pub fn remove_excerpts(&mut self, path: PathKey, cx: &mut Context<Self>) {
636        assert_eq!(self.history.transaction_depth(), 0);
637        self.sync_mut(cx);
638
639        let mut snapshot = self.snapshot.get_mut();
640        let mut cursor = snapshot
641            .excerpts
642            .cursor::<Dimensions<PathKey, ExcerptOffset>>(());
643        let mut new_excerpts = SumTree::new(());
644        new_excerpts.append(cursor.slice(&path, Bias::Left), ());
645        let mut edit_start = cursor.position.1;
646        let mut buffer_id = None;
647        if let Some(excerpt) = cursor.item()
648            && excerpt.path_key == path
649        {
650            buffer_id = Some(excerpt.buffer_id);
651        }
652        cursor.seek(&path, Bias::Right);
653        let edit_end = cursor.position.1;
654        let suffix = cursor.suffix();
655        let changed_trailing_excerpt = suffix.is_empty();
656        new_excerpts.append(suffix, ());
657
658        if let Some(buffer_id) = buffer_id {
659            snapshot.buffers.remove(&buffer_id);
660            remove_diff_state(&mut snapshot.diffs, buffer_id);
661            self.buffers.remove(&buffer_id);
662            self.diffs.remove(&buffer_id);
663            cx.emit(Event::BuffersRemoved {
664                removed_buffer_ids: vec![buffer_id],
665            })
666        }
667        drop(cursor);
668        if changed_trailing_excerpt {
669            snapshot.trailing_excerpt_update_count += 1;
670            new_excerpts.update_last(
671                |excerpt| {
672                    if excerpt.has_trailing_newline {
673                        excerpt.has_trailing_newline = false;
674                        edit_start.0.0 = edit_start
675                            .0
676                            .0
677                            .checked_sub(1)
678                            .expect("should have at least one excerpt");
679                    }
680                },
681                (),
682            )
683        }
684
685        let edit = Edit {
686            old: edit_start..edit_end,
687            new: edit_start..edit_start,
688        };
689        snapshot.excerpts = new_excerpts;
690
691        let edits =
692            Self::sync_diff_transforms(&mut snapshot, vec![edit], DiffChangeKind::BufferEdited);
693        if !edits.is_empty() {
694            self.subscriptions.publish(edits);
695        }
696
697        cx.emit(Event::Edited {
698            edited_buffer: None,
699            is_local: true,
700        });
701        cx.notify();
702    }
703}