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