3148 lines · 109.8 KB · Rust
RawDownload
pub(super) mod blame; use super::*;use ::git::{Restore, blame::BlameEntry, commit::ParsedCommitMessage, status::FileStatus};use buffer_diff::{BufferDiff, DiffHunkStatus, DiffHunkStatusKind}; #[derive(Clone)]pub struct ResolvedDiffHunk {    pub buffer_range: Range<text::Anchor>,    pub diff_base_byte_range: Range<usize>,    pub status: DiffHunkStatus,} #[derive(Clone)]pub struct ResolvedDiffHunks {    pub diff: Entity<BufferDiff>,    pub buffer_id: BufferId,    pub buffer: Option<Entity<Buffer>>,    pub hunks: Vec<ResolvedDiffHunk>,} pub trait DiffHunkDelegate {    fn toggle(        &self,        hunks: Vec<ResolvedDiffHunks>,        editor: &mut Editor,        window: &mut Window,        cx: &mut Context<Editor>,    );     fn stage_or_unstage(        &self,        stage: bool,        hunks: Vec<ResolvedDiffHunks>,        editor: &mut Editor,        window: &mut Window,        cx: &mut Context<Editor>,    );     fn restore(        &self,        hunks: Vec<ResolvedDiffHunks>,        editor: &mut Editor,        window: &mut Window,        cx: &mut Context<Editor>,    ) {        if hunks.is_empty() || editor.read_only(cx) {            return;        }        self.stage_or_unstage(false, hunks.clone(), editor, window, cx);        editor.transact(window, cx, |editor, window, cx| {            editor.restore_diff_hunks(hunks, cx);            let selections = editor                .selections                .all::<MultiBufferOffset>(&editor.display_snapshot(cx));            editor.change_selections(                SelectionEffects::no_scroll(),                window,                cx,                |selections_state| {                    selections_state.select(selections);                },            );        });    }     fn render_hunk_controls(        &self,        row: u32,        status: &DiffHunkStatus,        hunk_range: Range<Anchor>,        is_created_file: bool,        line_height: Pixels,        editor: &Entity<Editor>,        window: &mut Window,        cx: &mut App,    ) -> AnyElement;     fn render_hunk_as_staged(&self, status: &DiffHunkStatus, _cx: &App) -> bool {        !status.has_secondary_hunk()    }} pub struct UncommittedDiffHunkDelegate; impl DiffHunkDelegate for UncommittedDiffHunkDelegate {    fn toggle(        &self,        hunks: Vec<ResolvedDiffHunks>,        editor: &mut Editor,        window: &mut Window,        cx: &mut Context<Editor>,    ) {        let stage = hunks            .iter()            .flat_map(|hunks| hunks.hunks.iter())            .any(|hunk| hunk.status.has_secondary_hunk());        self.stage_or_unstage(stage, hunks, editor, window, cx);    }     fn stage_or_unstage(        &self,        stage: bool,        hunks: Vec<ResolvedDiffHunks>,        editor: &mut Editor,        _window: &mut Window,        cx: &mut Context<Editor>,    ) {        let Some(project) = editor.project() else {            return;        };        for hunks in hunks {            let Some(buffer) = hunks.buffer else {                continue;            };            let ranges = hunks                .hunks                .into_iter()                .map(|hunk| hunk.buffer_range)                .collect::<Vec<_>>();            if ranges.is_empty() {                continue;            }            let secondary_diff = hunks.diff.read(cx).secondary_diff();            project                .update(cx, |project, cx| {                    if stage {                        let Some(secondary_diff) = secondary_diff else {                            return Err(anyhow::anyhow!("diff has no unstaged secondary"));                        };                        project.stage_hunks(buffer, secondary_diff, ranges, cx)                    } else {                        project.unstage_uncommitted_hunks(buffer, hunks.diff, ranges, cx)                    }                })                .log_err();        }    }     fn render_hunk_controls(        &self,        row: u32,        status: &DiffHunkStatus,        hunk_range: Range<Anchor>,        is_created_file: bool,        line_height: Pixels,        editor: &Entity<Editor>,        window: &mut Window,        cx: &mut App,    ) -> AnyElement {        render_diff_hunk_controls(            row,            status,            hunk_range,            is_created_file,            line_height,            editor,            window,            cx,        )    }} pub struct RestoreOnlyDiffHunkDelegate; impl DiffHunkDelegate for RestoreOnlyDiffHunkDelegate {    fn toggle(        &self,        _hunks: Vec<ResolvedDiffHunks>,        _editor: &mut Editor,        _window: &mut Window,        _cx: &mut Context<Editor>,    ) {    }     fn stage_or_unstage(        &self,        _stage: bool,        _hunks: Vec<ResolvedDiffHunks>,        _editor: &mut Editor,        _window: &mut Window,        _cx: &mut Context<Editor>,    ) {    }     fn render_hunk_controls(        &self,        _row: u32,        _status: &DiffHunkStatus,        _hunk_range: Range<Anchor>,        _is_created_file: bool,        _line_height: Pixels,        _editor: &Entity<Editor>,        _window: &mut Window,        _cx: &mut App,    ) -> AnyElement {        gpui::Empty.into_any_element()    }} pub struct RestoreOnlyUnstagedDiffHunkDelegate; impl DiffHunkDelegate for RestoreOnlyUnstagedDiffHunkDelegate {    fn toggle(        &self,        _hunks: Vec<ResolvedDiffHunks>,        _editor: &mut Editor,        _window: &mut Window,        _cx: &mut Context<Editor>,    ) {    }     fn stage_or_unstage(        &self,        _stage: bool,        _hunks: Vec<ResolvedDiffHunks>,        _editor: &mut Editor,        _window: &mut Window,        _cx: &mut Context<Editor>,    ) {    }     fn render_hunk_controls(        &self,        _row: u32,        _status: &DiffHunkStatus,        _hunk_range: Range<Anchor>,        _is_created_file: bool,        _line_height: Pixels,        _editor: &Entity<Editor>,        _window: &mut Window,        _cx: &mut App,    ) -> AnyElement {        gpui::Empty.into_any_element()    }     fn render_hunk_as_staged(&self, _status: &DiffHunkStatus, _cx: &App) -> bool {        false    }} #[derive(Debug, Clone, PartialEq, Eq)]pub(super) enum DisplayDiffHunk {    Folded {        display_row: DisplayRow,    },    Unfolded {        is_created_file: bool,        diff_base_byte_range: Range<usize>,        display_row_range: Range<DisplayRow>,        multi_buffer_range: Range<Anchor>,        status: DiffHunkStatus,        word_diffs: Vec<Range<MultiBufferOffset>>,    },} #[derive(Clone)]pub(super) struct InlineBlamePopoverState {    pub(super) scroll_handle: ScrollHandle,    pub(super) commit_message: Option<ParsedCommitMessage>,    pub(super) markdown: Entity<Markdown>,} pub(super) struct InlineBlamePopover {    pub(super) position: gpui::Point<Pixels>,    pub(super) hide_task: Option<Task<()>>,    pub(super) popover_bounds: Option<Bounds<Pixels>>,    pub(super) popover_state: InlineBlamePopoverState,    pub(super) keyboard_grace: bool,} /// Represents a diff review button indicator that shows up when hovering over lines in the gutter/// in diff view mode.#[derive(Clone, Copy, Debug, PartialEq, Eq)]pub(super) struct PhantomDiffReviewIndicator {    /// The starting anchor of the selection (or the only row if not dragging).    pub(super) start: Anchor,    /// The ending anchor of the selection. Equal to start_anchor for single-line selection.    pub(super) end: Anchor,    /// There's a small debounce between hovering over the line and showing the indicator.    /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.    pub(super) is_active: bool,} #[derive(Clone, Debug)]pub(super) struct DiffReviewDragState {    start_anchor: Anchor,    current_anchor: Anchor,} /// Identifies a specific hunk in the diff buffer./// Used as a key to group comments by their location.#[derive(Clone, Debug)]pub(super) struct DiffHunkKey {    /// The file path (relative to worktree) this hunk belongs to.    pub(super) file_path: Arc<util::rel_path::RelPath>,    /// An anchor at the start of the hunk. This tracks position as the buffer changes.    pub(super) hunk_start_anchor: Anchor,} /// A review comment stored locally before being sent to the Agent panel.#[derive(Clone)]pub(super) struct StoredReviewComment {    /// Unique identifier for this comment (for edit/delete operations).    pub(super) id: usize,    /// The comment text entered by the user.    pub(super) comment: String,    /// Anchors for the code range being reviewed.    pub(super) range: Range<Anchor>,    /// Whether this comment is currently being edited inline.    pub(super) is_editing: bool,} /// Represents an active diff review overlay that appears when clicking the "Add Review" button.pub(super) struct DiffReviewOverlay {    pub(super) anchor_range: Range<Anchor>,    /// The block ID for the overlay.    pub(super) block_id: CustomBlockId,    /// The editor entity for the review input.    pub(super) prompt_editor: Entity<Editor>,    /// The hunk key this overlay belongs to.    pub(super) hunk_key: DiffHunkKey,    /// Whether the comments section is expanded.    pub(super) comments_expanded: bool,    /// Editors for comments currently being edited inline.    /// Key: comment ID, Value: Editor entity for inline editing.    pub(super) inline_edit_editors: HashMap<usize, Entity<Editor>>,    /// Subscriptions for inline edit editors' action handlers.    /// Key: comment ID, Value: Subscription keeping the Newline action handler alive.    pub(super) inline_edit_subscriptions: HashMap<usize, Subscription>,    /// The current user's avatar URI for display in comment rows.    pub(super) user_avatar_uri: Option<SharedUri>,    /// Subscription to keep the action handler alive.    _subscription: Subscription,} impl DiffReviewDragState {    pub(super) fn row_range(        &self,        snapshot: &DisplaySnapshot,    ) -> std::ops::RangeInclusive<DisplayRow> {        let start = self.start_anchor.to_display_point(snapshot).row();        let current = self.current_anchor.to_display_point(snapshot).row();         (start..=current).sorted()    }} impl StoredReviewComment {    fn new(id: usize, comment: String, anchor_range: Range<Anchor>) -> Self {        Self {            id,            comment,            range: anchor_range,            is_editing: false,        }    }} impl Editor {    pub fn diff_hunks_in_ranges<'a>(        &'a self,        ranges: &'a [Range<Anchor>],        buffer: &'a MultiBufferSnapshot,    ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {        ranges.iter().flat_map(move |range| {            let end_excerpt = buffer.excerpt_containing(range.end..range.end);            let range = range.to_point(buffer);            let mut peek_end = range.end;            if range.end.row < buffer.max_row().0 {                peek_end = Point::new(range.end.row + 1, 0);            }            buffer                .diff_hunks_in_range(range.start..peek_end)                .filter(move |hunk| {                    if let Some((_, excerpt_range)) = &end_excerpt                        && let Some(end_anchor) =                            buffer.anchor_in_excerpt(excerpt_range.context.end)                        && let Some(hunk_end_anchor) =                            buffer.anchor_in_excerpt(hunk.excerpt_range.context.end)                        && hunk_end_anchor.cmp(&end_anchor, buffer).is_gt()                    {                        false                    } else {                        true                    }                })        })    }     fn resolve_diff_hunks(        &self,        hunks: Vec<MultiBufferDiffHunk>,        cx: &App,    ) -> Vec<ResolvedDiffHunks> {        let multibuffer = self.buffer().read(cx);        let chunk_by = hunks.into_iter().chunk_by(|hunk| hunk.buffer_id);        let mut resolved = Vec::new();         for (source_buffer_id, hunks) in &chunk_by {            let Some(diff) = multibuffer.diff_for(source_buffer_id) else {                continue;            };            let diff_snapshot = diff.read(cx).snapshot(cx);            let main_buffer_id = diff_snapshot.buffer_id();            let buffer = multibuffer.buffer(main_buffer_id).or_else(|| {                self.project                    .as_ref()                    .and_then(|project| project.read(cx).buffer_for_id(main_buffer_id, cx))            });            let mut resolved_hunks = Vec::new();             for hunk in hunks {                if hunk.buffer_id == main_buffer_id {                    resolved_hunks.push(ResolvedDiffHunk {                        buffer_range: hunk.buffer_range,                        diff_base_byte_range: hunk.diff_base_byte_range.start.0                            ..hunk.diff_base_byte_range.end.0,                        status: hunk.status,                    });                } else {                    let diff_base_byte_range =                        hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0;                    let Some(hunk) = diff_snapshot                        .hunks_intersecting_base_text_range(                            diff_base_byte_range.clone(),                            diff_snapshot.buffer_snapshot(),                        )                        .find(|hunk| hunk.diff_base_byte_range == diff_base_byte_range)                    else {                        continue;                    };                    let kind = if hunk.buffer_range.start == hunk.buffer_range.end {                        DiffHunkStatusKind::Deleted                    } else if hunk.diff_base_byte_range.is_empty() {                        DiffHunkStatusKind::Added                    } else {                        DiffHunkStatusKind::Modified                    };                    resolved_hunks.push(ResolvedDiffHunk {                        buffer_range: hunk.buffer_range,                        diff_base_byte_range: hunk.diff_base_byte_range,                        status: DiffHunkStatus {                            kind,                            secondary: hunk.secondary_status,                        },                    });                }            }             if !resolved_hunks.is_empty() {                resolved.push(ResolvedDiffHunks {                    diff,                    buffer_id: main_buffer_id,                    buffer,                    hunks: resolved_hunks,                });            }        }         resolved    }     pub fn diff_hunk_delegate(&self) -> Arc<dyn DiffHunkDelegate> {        self.diff_hunk_delegate            .clone()            .unwrap_or_else(|| Arc::new(UncommittedDiffHunkDelegate))    }     pub fn set_diff_hunk_delegate(        &mut self,        delegate: Option<Arc<dyn DiffHunkDelegate>>,        cx: &mut Context<Self>,    ) {        let had_delegate = self.diff_hunk_delegate.is_some();        let has_delegate = delegate.is_some();        self.diff_hunk_delegate = delegate;         if !had_delegate && has_delegate {            self.load_diff_task.take();        } else if had_delegate && !has_delegate {            self.buffer.update(cx, |buffer, cx| {                buffer.set_all_diff_hunks_collapsed(cx);            });             if let Some(project) = self.project.clone() {                self.load_diff_task = Some(                    update_uncommitted_diff_for_buffer(                        cx.entity(),                        &project,                        self.buffer.read(cx).all_buffers(),                        self.buffer.clone(),                        cx,                    )                    .shared(),                );            }        }         cx.notify();

Lines 1–500 of 3148.