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