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