3054 lines · 104.4 KB · Rust
RawDownload
use crate::{    branch_picker, conflict_view,    git_panel::{GitPanel, GitPanelAddon, GitStatusEntry},    git_panel_settings::GitPanelSettings,};use agent_settings::AgentSettings;use anyhow::{Context as _, Result, anyhow};use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus};use collections::HashMap;use editor::{    Addon, Editor, EditorEvent, EditorSettings, SelectionEffects, SplittableEditor,    actions::{GoToHunk, GoToPreviousHunk, SendReviewToAgent},    multibuffer_context_lines,    scroll::Autoscroll,};use futures_lite::future::yield_now;use git::repository::DiffType; use git::{    Commit, StageAll, StageAndNext, ToggleStaged, UnstageAll, UnstageAndNext, repository::RepoPath,    status::FileStatus,};use gpui::{    Action, AnyElement, App, AppContext as _, AsyncWindowContext, Entity, EventEmitter,    FocusHandle, Focusable, Render, Subscription, Task, WeakEntity, actions,};use language::{Anchor, Buffer, BufferId, Capability, OffsetRangeExt};use multi_buffer::{MultiBuffer, PathKey};use project::{    ConflictSet, Project, ProjectPath,    git_store::{        Repository,        branch_diff::{self, BranchDiffEvent, DiffBase},    },};use settings::{Settings, SettingsStore};use std::any::{Any, TypeId};use std::collections::BTreeMap;use std::sync::Arc;use theme::ActiveTheme;use ui::{    CommonAnimationExt as _, DiffStat, Divider, KeyBinding, PopoverMenu, Tooltip, prelude::*,    vertical_divider,};use util::{ResultExt as _, rel_path::RelPath};use workspace::{    CloseActiveItem, ItemNavHistory, SerializableItem, ToolbarItemEvent, ToolbarItemLocation,    ToolbarItemView, Workspace,    item::{Item, ItemEvent, ItemHandle, SaveOptions, TabContentParams},    notifications::NotifyTaskExt,    searchable::SearchableItemHandle,};use zed_actions::agent::ReviewBranchDiff;use ztracing::instrument; actions!(    git,    [        /// Shows the diff between the working directory and the index.        Diff,        /// Adds files to the git staging area.        Add,        /// Shows the diff between the working directory and your default        /// branch (typically main or master).        BranchDiff,        /// Opens a new agent thread with the branch diff for review.        ReviewDiff,        LeaderAndFollower,        /// Compare with a specific branch        CompareWithBranch,    ]); struct BufferSubscriptions {    _diff: Entity<BufferDiff>,    _diff_subscription: Subscription,    _conflict_set: Entity<ConflictSet>,    _conflict_set_subscription: Subscription,} pub struct ProjectDiff {    project: Entity<Project>,    multibuffer: Entity<MultiBuffer>,    branch_diff: Entity<branch_diff::BranchDiff>,    editor: Entity<SplittableEditor>,    buffer_subscriptions: HashMap<Arc<RelPath>, BufferSubscriptions>,    workspace: WeakEntity<Workspace>,    focus_handle: FocusHandle,    pending_scroll: Option<PathKey>,    review_comment_count: usize,    _task: Task<Result<()>>,    _subscription: Subscription,} const CONFLICT_SORT_PREFIX: u64 = 1;const TRACKED_SORT_PREFIX: u64 = 2;const NEW_SORT_PREFIX: u64 = 3; impl ProjectDiff {    pub(crate) fn register(workspace: &mut Workspace, cx: &mut Context<Workspace>) {        workspace.register_action(Self::deploy);        workspace.register_action(Self::deploy_branch_diff);        workspace.register_action(Self::compare_with_branch);        workspace.register_action(|workspace, _: &Add, window, cx| {            Self::deploy(workspace, &Diff, window, cx);        });        workspace::register_serializable_item::<ProjectDiff>(cx);    }     fn deploy(        workspace: &mut Workspace,        _: &Diff,        window: &mut Window,        cx: &mut Context<Workspace>,    ) {        Self::deploy_at(workspace, None, window, cx)    }     fn deploy_branch_diff(        workspace: &mut Workspace,        _: &BranchDiff,        window: &mut Window,        cx: &mut Context<Workspace>,    ) {        telemetry::event!("Git Branch Diff Opened");        let project = workspace.project().clone();        let Some(intended_repo) = project.read(cx).active_repository(cx) else {            let workspace = cx.entity().downgrade();            window                .spawn(cx, async |_cx| {                    let result: Result<()> = Err(anyhow!("No active repository"));                    result                })                .detach_and_notify_err(workspace, window, cx);            return;        };         let default_branch = intended_repo.update(cx, |repo, _| repo.default_branch(true));        let workspace = cx.entity();        let workspace_weak = workspace.downgrade();        window            .spawn(cx, async move |cx| {                let base_ref = default_branch                    .await??                    .context("Could not determine default branch")?;                 workspace.update_in(cx, |workspace, window, cx| {                    Self::deploy_branch_diff_with_base_ref(                        workspace,                        project,                        intended_repo,                        base_ref,                        window,                        cx,                    );                })?;                 anyhow::Ok(())            })            .detach_and_notify_err(workspace_weak, window, cx);    }     fn compare_with_branch(        workspace: &mut Workspace,        _: &CompareWithBranch,        window: &mut Window,        cx: &mut Context<Workspace>,    ) {        let project = workspace.project().clone();        let Some(repository) = project.read(cx).active_repository(cx) else {            let workspace = cx.entity().downgrade();            window                .spawn(cx, async |_cx| {                    let result: Result<()> = Err(anyhow!("No active repository"));                    result                })                .detach_and_notify_err(workspace, window, cx);            return;        };        let selected_branch = workspace.active_item_as::<Self>(cx).and_then(|item| {            match item.read(cx).diff_base(cx) {                DiffBase::Merge { base_ref } => Some(base_ref.clone()),                DiffBase::Head => None,            }        });        let workspace_handle = workspace.weak_handle();        let on_select = Arc::new({            let repository = repository.clone();            let workspace = workspace_handle.clone();            move |branch: git::repository::Branch, window: &mut Window, cx: &mut App| {                let base_ref: SharedString = branch.name().to_owned().into();                workspace                    .update(cx, |workspace, cx| {                        Self::deploy_branch_diff_with_base_ref(                            workspace,                            project.clone(),                            repository.clone(),                            base_ref,                            window,                            cx,                        );                    })                    .ok();            }        });         workspace.toggle_modal(window, cx, |window, cx| {            branch_picker::select_modal(                workspace_handle,                Some(repository),                selected_branch,                on_select,                window,                cx,            )        });    }     fn deploy_branch_diff_with_base_ref(        workspace: &mut Workspace,        project: Entity<Project>,        intended_repo: Entity<Repository>,        base_ref: SharedString,        window: &mut Window,        cx: &mut Context<Workspace>,    ) {        let existing = workspace.items_of_type::<Self>(cx).find(|item| {            let item = item.read(cx);            matches!(                item.diff_base(cx),                DiffBase::Merge { base_ref: existing_base_ref } if existing_base_ref == &base_ref            )        });        if let Some(existing) = existing {            workspace.activate_item(&existing, true, true, window, cx);             let needs_switch = existing                .read(cx)                .branch_diff                .read(cx)                .repo()                .map_or(true, |current| {                    current.read(cx).id != intended_repo.read(cx).id                });             if needs_switch {                existing.update(cx, |project_diff, cx| {                    project_diff.branch_diff.update(cx, |branch_diff, cx| {                        branch_diff.set_repo(Some(intended_repo), cx);                    });                });            }             return;        }         let workspace = cx.entity();        let workspace_weak = workspace.downgrade();        window            .spawn(cx, async move |cx| {                let this = cx                    .update(|window, cx| {                        Self::new_with_branch_base(                            project,                            workspace.clone(),                            base_ref,                            intended_repo,                            window,                            cx,                        )                    })?                    .await?;                workspace                    .update_in(cx, |workspace, window, cx| {                        workspace.add_item_to_active_pane(Box::new(this), None, true, window, cx);                    })                    .ok();                anyhow::Ok(())            })            .detach_and_notify_err(workspace_weak, window, cx);    }     fn review_diff(&mut self, _: &ReviewDiff, window: &mut Window, cx: &mut Context<Self>) {        let diff_base = self.diff_base(cx).clone();        let DiffBase::Merge { base_ref } = diff_base else {            return;        };         let Some(repo) = self.branch_diff.read(cx).repo().cloned() else {            return;        };         let diff_receiver = repo.update(cx, |repo, cx| {            repo.diff(                DiffType::MergeBase {                    base_ref: base_ref.clone(),                },                cx,            )        });         let workspace = self.workspace.clone();         window            .spawn(cx, {                let workspace = workspace.clone();                async move |cx| {                    let diff_text = diff_receiver.await??;                     if let Some(workspace) = workspace.upgrade() {                        workspace.update_in(cx, |_workspace, window, cx| {                            window.dispatch_action(                                ReviewBranchDiff {                                    diff_text: diff_text.into(),                                    base_ref,                                }                                .boxed_clone(),                                cx,                            );                        })?;                    }                     anyhow::Ok(())                }            })            .detach_and_notify_err(workspace, window, cx);    }     pub fn deploy_at(        workspace: &mut Workspace,        entry: Option<GitStatusEntry>,        window: &mut Window,        cx: &mut Context<Workspace>,    ) {        telemetry::event!(            "Git Diff Opened",            source = if entry.is_some() {                "Git Panel"            } else {                "Action"            }        );        let intended_repo = workspace.project().read(cx).active_repository(cx);         let existing = workspace            .items_of_type::<Self>(cx)            .find(|item| matches!(item.read(cx).diff_base(cx), DiffBase::Head));        let project_diff = if let Some(existing) = existing {            existing.update(cx, |project_diff, cx| {                project_diff.move_to_beginning(window, cx);            });             workspace.activate_item(&existing, true, true, window, cx);            existing        } else {            let workspace_handle = cx.entity();            let project_diff =                cx.new(|cx| Self::new(workspace.project().clone(), workspace_handle, window, cx));            workspace.add_item_to_active_pane(                Box::new(project_diff.clone()),                None,                true,                window,                cx,            );            project_diff        };         if let Some(intended) = &intended_repo {            let needs_switch = project_diff                .read(cx)                .branch_diff                .read(cx)                .repo()                .map_or(true, |current| current.read(cx).id != intended.read(cx).id);            if needs_switch {                project_diff.update(cx, |project_diff, cx| {                    project_diff.branch_diff.update(cx, |branch_diff, cx| {                        branch_diff.set_repo(Some(intended.clone()), cx);                    });                });            }        }         if let Some(entry) = entry {            project_diff.update(cx, |project_diff, cx| {                project_diff.move_to_entry(entry, window, cx);            })        }    }     pub fn deploy_at_project_path(        workspace: &mut Workspace,        project_path: ProjectPath,        window: &mut Window,        cx: &mut Context<Workspace>,    ) {        telemetry::event!("Git Diff Opened", source = "Agent Panel");        let existing = workspace            .items_of_type::<Self>(cx)            .find(|item| matches!(item.read(cx).diff_base(cx), DiffBase::Head));        let project_diff = if let Some(existing) = existing {            workspace.activate_item(&existing, true, true, window, cx);            existing        } else {            let workspace_handle = cx.entity();            let project_diff =                cx.new(|cx| Self::new(workspace.project().clone(), workspace_handle, window, cx));            workspace.add_item_to_active_pane(                Box::new(project_diff.clone()),                None,                true,                window,                cx,            );            project_diff        };        project_diff.update(cx, |project_diff, cx| {            project_diff.move_to_project_path(&project_path, window, cx);        });    }     pub fn autoscroll(&self, cx: &mut Context<Self>) {        self.editor.update(cx, |editor, cx| {            editor.rhs_editor().update(cx, |editor, cx| {                editor.request_autoscroll(Autoscroll::fit(), cx);            })        })    }     #[cfg(test)]    #[allow(dead_code)]    fn new_with_default_branch(        project: Entity<Project>,        workspace: Entity<Workspace>,        window: &mut Window,        cx: &mut App,    ) -> Task<Result<Entity<Self>>> {        let Some(repo) = project.read(cx).git_store().read(cx).active_repository() else {            return Task::ready(Err(anyhow!("No active repository")));        };        let main_branch = repo.update(cx, |repo, _| repo.default_branch(true));        window.spawn(cx, async move |cx| {            let main_branch = main_branch                .await??                .context("Could not determine default branch")?;             let branch_diff = cx.new_window_entity(|window, cx| {                let mut branch_diff = branch_diff::BranchDiff::new(                    DiffBase::Merge {                        base_ref: main_branch,                    },                    project.clone(),                    window,                    cx,                );                branch_diff.set_repo(Some(repo.clone()), cx);                branch_diff            })?;            cx.new_window_entity(|window, cx| {                Self::new_impl(branch_diff, project, workspace, window, cx)            })        })    }     fn new_with_branch_base(        project: Entity<Project>,        workspace: Entity<Workspace>,        base_ref: SharedString,        repo: Entity<Repository>,        window: &mut Window,        cx: &mut App,    ) -> Task<Result<Entity<Self>>> {        window.spawn(cx, async move |cx| {            let branch_diff = cx.new_window_entity(|window, cx| {                let mut branch_diff = branch_diff::BranchDiff::new(                    DiffBase::Merge { base_ref },                    project.clone(),                    window,                    cx,                );                branch_diff.set_repo(Some(repo.clone()), cx);                branch_diff            })?;            cx.new_window_entity(|window, cx| {                Self::new_impl(branch_diff, project, workspace, window, cx)            })        })    }     fn new(        project: Entity<Project>,        workspace: Entity<Workspace>,        window: &mut Window,        cx: &mut Context<Self>,    ) -> Self {        let branch_diff =            cx.new(|cx| branch_diff::BranchDiff::new(DiffBase::Head, project.clone(), window, cx));        Self::new_impl(branch_diff, project, workspace, window, cx)    }

Lines 1–500 of 3054.