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    MultiBufferOffset, 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            ranges.sort_by(|l, r| l.context.start.cmp(&r.context.start));
268
269            self.set_excerpt_ranges_for_path(
270                path.clone(),
271                buffer,
272                buffer_snapshot,
273                dbg!(ranges),
274                cx,
275            );
276        }
277    }
278
279    /// Sets excerpts, returns `true` if at least one new excerpt was added.
280    pub(crate) fn set_merged_excerpt_ranges_for_path<T>(
281        &mut self,
282        path: PathKey,
283        buffer: Entity<Buffer>,
284        buffer_snapshot: &BufferSnapshot,
285        new: Vec<ExcerptRange<T>>,
286        cx: &mut Context<Self>,
287    ) -> (bool, PathKeyIndex)
288    where
289        T: language::ToOffset,
290    {
291        let anchor_ranges = new
292            .into_iter()
293            .map(|r| ExcerptRange {
294                context: buffer_snapshot.anchor_before(r.context.start)
295                    ..buffer_snapshot.anchor_after(r.context.end),
296                primary: buffer_snapshot.anchor_before(r.primary.start)
297                    ..buffer_snapshot.anchor_after(r.primary.end),
298            })
299            .collect::<Vec<_>>();
300        self.update_path_excerpts(path, buffer, buffer_snapshot, &anchor_ranges, cx)
301    }
302
303    fn get_or_create_path_key_index(&mut self, path_key: &PathKey) -> PathKeyIndex {
304        let mut snapshot = self.snapshot.borrow_mut();
305        let existing = snapshot
306            .path_keys_by_index
307            .iter()
308            // todo!() perf? (but ExcerptIdMapping was doing this)
309            .find(|(_, existing_path)| existing_path == &path_key)
310            .map(|(index, _)| *index);
311
312        if let Some(existing) = existing {
313            return existing;
314        }
315
316        let index = snapshot
317            .path_keys_by_index
318            .last()
319            .map(|(index, _)| PathKeyIndex(index.0 + 1))
320            .unwrap_or(PathKeyIndex(0));
321        snapshot.path_keys_by_index.insert(index, path_key.clone());
322        index
323    }
324
325    // todo!() re-instate nonshrinking version for project diff / diagnostics
326    pub fn update_path_excerpts<'a>(
327        &mut self,
328        path_key: PathKey,
329        buffer: Entity<Buffer>,
330        buffer_snapshot: &BufferSnapshot,
331        to_insert: &Vec<ExcerptRange<text::Anchor>>,
332        cx: &mut Context<Self>,
333    ) -> (bool, PathKeyIndex) {
334        dbg!(&path_key, &to_insert);
335        let path_key_index = self.get_or_create_path_key_index(&path_key);
336        if let Some(old_path_key) = self
337            .snapshot(cx)
338            .path_for_buffer(buffer_snapshot.remote_id())
339            && old_path_key != &path_key
340        {
341            self.remove_excerpts(old_path_key.clone(), cx);
342        }
343
344        if to_insert.len() == 0 {
345            self.remove_excerpts(path_key.clone(), cx);
346
347            return (false, path_key_index);
348        }
349        assert_eq!(self.history.transaction_depth(), 0);
350        self.sync_mut(cx);
351
352        let buffer_id = buffer_snapshot.remote_id();
353
354        let mut snapshot = self.snapshot.get_mut();
355        let mut cursor = snapshot
356            .excerpts
357            .cursor::<Dimensions<PathKey, ExcerptOffset>>(());
358        let mut new_excerpts = SumTree::new(());
359
360        let new_ranges = to_insert.clone();
361        let mut to_insert = to_insert.iter().peekable();
362        let mut patch = Patch::empty();
363        let mut added_new_excerpt = false;
364
365        new_excerpts.append(cursor.slice(&path_key, Bias::Left), ());
366
367        // handle the case where the path key used to be associated
368        // with a different buffer by removing its excerpts.
369        if let Some(excerpt) = cursor.item()
370            && excerpt.path_key == path_key
371            && excerpt.buffer_id != buffer_id
372        {
373            let before = cursor.position.1;
374            cursor.seek_forward(&path_key, Bias::Right);
375            let after = cursor.position.1;
376            patch.push(Edit {
377                old: before..after,
378                new: new_excerpts.summary().len()..new_excerpts.summary().len(),
379            });
380        }
381
382        while let Some(excerpt) = cursor.item()
383            && excerpt.path_key == path_key
384        {
385            assert_eq!(excerpt.buffer_id, buffer_id);
386            let Some(next_excerpt) = to_insert.peek() else {
387                break;
388            };
389            if &excerpt.range == *next_excerpt {
390                let before = new_excerpts.summary().len();
391                new_excerpts.update_last(
392                    |prev_excerpt| {
393                        if !prev_excerpt.has_trailing_newline {
394                            prev_excerpt.has_trailing_newline = true;
395                            patch.push(Edit {
396                                old: cursor.position.1..cursor.position.1,
397                                new: before..before + MultiBufferOffset(1),
398                            });
399                        }
400                    },
401                    (),
402                );
403                new_excerpts.push(excerpt.clone(), ());
404                to_insert.next();
405                cursor.next();
406                continue;
407            }
408
409            if excerpt
410                .range
411                .context
412                .start
413                .cmp(&next_excerpt.context.start, &buffer_snapshot)
414                .is_le()
415            {
416                // remove old excerpt
417                let before = cursor.position.1;
418                cursor.next();
419                let after = cursor.position.1;
420                patch.push(Edit {
421                    old: before..after,
422                    new: new_excerpts.summary().len()..new_excerpts.summary().len(),
423                });
424            } else {
425                // insert new excerpt
426                let next_excerpt = to_insert.next().unwrap();
427                added_new_excerpt = true;
428                let before = new_excerpts.summary().len();
429                new_excerpts.update_last(
430                    |prev_excerpt| {
431                        dbg!("NORMAL INSERT");
432                        prev_excerpt.has_trailing_newline = true;
433                    },
434                    (),
435                );
436                new_excerpts.push(
437                    Excerpt::new(
438                        path_key.clone(),
439                        path_key_index,
440                        &buffer_snapshot,
441                        next_excerpt.clone(),
442                        false,
443                    ),
444                    (),
445                );
446                let after = new_excerpts.summary().len();
447                patch.push_maybe_empty(Edit {
448                    old: cursor.position.1..cursor.position.1,
449                    new: before..after,
450                });
451            }
452        }
453
454        // remove any further trailing excerpts
455        let mut before = cursor.position.1;
456        cursor.seek_forward(&path_key, Bias::Right);
457        let after = cursor.position.1;
458        // if we removed the previous last excerpt, remove the trailing newline from the new last excerpt
459        if cursor.item().is_none() && to_insert.peek().is_none() {
460            new_excerpts.update_last(
461                |excerpt| {
462                    if excerpt.has_trailing_newline {
463                        before.0.0 = before
464                            .0
465                            .0
466                            .checked_sub(1)
467                            .expect("should have preceding excerpt");
468                        excerpt.has_trailing_newline = false;
469                    }
470                },
471                (),
472            );
473        }
474        patch.push(Edit {
475            old: before..after,
476            new: new_excerpts.summary().len()..new_excerpts.summary().len(),
477        });
478
479        while let Some(next_excerpt) = to_insert.next() {
480            added_new_excerpt = true;
481            let before = new_excerpts.summary().len();
482            new_excerpts.update_last(
483                |prev_excerpt| {
484                    dbg!("TRAILING INSERT");
485                    prev_excerpt.has_trailing_newline = true;
486                },
487                (),
488            );
489            new_excerpts.push(
490                Excerpt::new(
491                    path_key.clone(),
492                    path_key_index,
493                    &buffer_snapshot,
494                    next_excerpt.clone(),
495                    false,
496                ),
497                (),
498            );
499            let after = new_excerpts.summary().len();
500            patch.push_maybe_empty(Edit {
501                old: cursor.position.1..cursor.position.1,
502                new: before..after,
503            });
504        }
505
506        let suffix_start = cursor.position.1;
507        let suffix = cursor.suffix();
508        let changed_trailing_excerpt = suffix.is_empty();
509        if !suffix.is_empty() {
510            let before = new_excerpts.summary().len();
511            new_excerpts.update_last(
512                |prev_excerpt| {
513                    if !prev_excerpt.has_trailing_newline {
514                        dbg!("BEFORE SUFFIX");
515                        prev_excerpt.has_trailing_newline = true;
516                        patch.push(dbg!(Edit {
517                            old: suffix_start..suffix_start,
518                            new: before..before + MultiBufferOffset(1),
519                        }));
520                    }
521                },
522                (),
523            );
524        }
525        dbg!(&patch);
526        new_excerpts.append(suffix, ());
527        drop(cursor);
528
529        snapshot.excerpts = new_excerpts;
530        snapshot.buffers.insert(
531            buffer_id,
532            BufferStateSnapshot {
533                path_key: path_key.clone(),
534                buffer_snapshot: buffer_snapshot.clone(),
535            },
536        );
537
538        self.buffers.entry(buffer_id).or_insert_with(|| {
539            self.buffer_changed_since_sync.replace(true);
540            buffer.update(cx, |buffer, _| {
541                buffer.record_changes(Rc::downgrade(&self.buffer_changed_since_sync));
542            });
543            BufferState {
544                _subscriptions: [
545                    cx.observe(&buffer, |_, _, cx| cx.notify()),
546                    cx.subscribe(&buffer, Self::on_buffer_event),
547                ],
548                buffer: buffer.clone(),
549            }
550        });
551
552        if changed_trailing_excerpt {
553            snapshot.trailing_excerpt_update_count += 1;
554        }
555
556        let edits = Self::sync_diff_transforms(
557            &mut snapshot,
558            patch.into_inner(),
559            DiffChangeKind::BufferEdited,
560        );
561        if !edits.is_empty() {
562            self.subscriptions.publish(edits);
563        }
564
565        cx.emit(Event::Edited {
566            edited_buffer: None,
567        });
568        cx.emit(Event::BufferUpdated {
569            buffer,
570            path_key: path_key.clone(),
571            ranges: new_ranges,
572        });
573        cx.notify();
574
575        (added_new_excerpt, path_key_index)
576    }
577
578    pub fn remove_excerpts_for_buffer(&mut self, buffer: BufferId, cx: &mut Context<Self>) {
579        let snapshot = self.sync_mut(cx);
580        let Some(path) = snapshot.path_for_buffer(buffer).cloned() else {
581            return;
582        };
583        self.remove_excerpts(path, cx);
584    }
585
586    pub fn remove_excerpts(&mut self, path: PathKey, cx: &mut Context<Self>) {
587        assert_eq!(self.history.transaction_depth(), 0);
588        self.sync_mut(cx);
589
590        let mut snapshot = self.snapshot.get_mut();
591        let mut cursor = snapshot
592            .excerpts
593            .cursor::<Dimensions<PathKey, ExcerptOffset>>(());
594        let mut new_excerpts = SumTree::new(());
595        new_excerpts.append(cursor.slice(&path, Bias::Left), ());
596        let mut edit_start = cursor.position.1;
597        let mut buffer_id = None;
598        if let Some(excerpt) = cursor.item()
599            && excerpt.path_key == path
600        {
601            buffer_id = Some(excerpt.buffer_id);
602        }
603        cursor.seek(&path, Bias::Right);
604        let edit_end = cursor.position.1;
605        let suffix = cursor.suffix();
606        let changed_trailing_excerpt = suffix.is_empty();
607        new_excerpts.append(suffix, ());
608
609        if let Some(buffer_id) = buffer_id {
610            snapshot.buffers.remove(&buffer_id);
611            self.buffers.remove(&buffer_id);
612            cx.emit(Event::BuffersRemoved {
613                removed_buffer_ids: vec![buffer_id],
614            })
615        }
616        drop(cursor);
617        dbg!("REMOVING");
618        if changed_trailing_excerpt {
619            snapshot.trailing_excerpt_update_count += 1;
620            new_excerpts.update_last(
621                |excerpt| {
622                    if excerpt.has_trailing_newline {
623                        excerpt.has_trailing_newline = false;
624                        edit_start.0.0 = edit_start
625                            .0
626                            .0
627                            .checked_sub(1)
628                            .expect("should have at least one excerpt");
629                    }
630                },
631                (),
632            )
633        }
634
635        let edit = Edit {
636            old: edit_start..edit_end,
637            new: edit_start..edit_start,
638        };
639        snapshot.excerpts = new_excerpts;
640
641        let edits =
642            Self::sync_diff_transforms(&mut snapshot, vec![edit], DiffChangeKind::BufferEdited);
643        if !edits.is_empty() {
644            self.subscriptions.publish(edits);
645        }
646
647        cx.emit(Event::Edited {
648            edited_buffer: None,
649        });
650        cx.notify();
651    }
652}