2046 lines · 66.6 KB · Rust
RawDownload
use crate::{    diff_multibuffer::DiffMultibuffer,    git_panel::{GitPanel, GitPanelAddon, GitStatusEntry},    staged_diff::StagedDiff,    unstaged_diff::UnstagedDiff,};use anyhow::{Context as _, Result};use buffer_diff::DiffHunkSecondaryStatus;use editor::{    Editor, EditorEvent, SplittableEditor, UncommittedDiffHunkDelegate,    actions::{GoToHunk, GoToPreviousHunk, SendReviewToAgent},};use git::{Commit, StageAll, StageAndNext, ToggleStaged, UnstageAll, UnstageAndNext};use gpui::{    Action, AnyElement, App, AppContext as _, Entity, EventEmitter, FocusHandle, Focusable, Render,    Subscription, Task, WeakEntity, actions,};use language::Capability;use multi_buffer::MultiBuffer;use project::{    Project, ProjectPath,    git_store::{        Repository,        diff_buffer_list::{self, DiffBase},    },};use schemars::JsonSchema;use serde::Deserialize;use std::any::{Any, TypeId};use std::sync::Arc;use ui::{DiffStat, Divider, Tooltip, prelude::*};use workspace::{    ItemNavHistory, SerializableItem, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView,    Workspace,    item::{Item, ItemEvent, ItemHandle, SaveOptions, TabContentParams},    searchable::SearchableItemHandle,};use zed_actions::git as git_actions; actions!(    git,    [        /// Shows the diff between the working directory and the index.        Diff,        /// Adds files to the git staging area.        Add,        /// Opens a new agent thread with the branch diff for review.        ReviewDiff,        LeaderAndFollower,        /// Compare with a specific branch        CompareWithBranch,    ]); /// Shows the diff between the working directory and your default/// branch (typically main or master).#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)]#[action(namespace = git, name = "BranchDiff")]pub(crate) struct DeployBranchDiff; pub struct ProjectDiff {    project: Entity<Project>,    workspace: WeakEntity<Workspace>,    diff: Entity<DiffMultibuffer>,    _diff_observation: Subscription,} impl ProjectDiff {    pub(crate) fn register(workspace: &mut Workspace, cx: &mut Context<Workspace>) {        workspace.register_action(Self::deploy);        workspace.register_action(            |workspace, _: &git_actions::ViewUncommittedChanges, window, cx| {                Self::deploy_at(workspace, None, window, cx);            },        );        workspace.register_action(            |workspace, _: &git_actions::ViewUnstagedChanges, window, cx| {                UnstagedDiff::deploy_at(workspace, None, window, cx);            },        );        workspace.register_action(            |workspace, _: &git_actions::ViewStagedChanges, window, cx| {                StagedDiff::deploy_at(workspace, None, window, cx);            },        );        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)    }     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).next();        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)                .repo(cx)                .map_or(true, |current| current.read(cx).id != intended.read(cx).id);            if needs_switch {                project_diff.update(cx, |project_diff, cx| {                    project_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).next();        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.diff.update(cx, |diff, cx| diff.autoscroll(cx));    }     fn new(        project: Entity<Project>,        workspace: Entity<Workspace>,        window: &mut Window,        cx: &mut Context<Self>,    ) -> Self {        let branch_diff = cx.new(|cx| {            diff_buffer_list::DiffBufferList::new(DiffBase::Head, project.clone(), window, cx)        });        Self::new_impl(branch_diff, project, workspace, window, cx)    }     fn new_impl(        branch_diff: Entity<diff_buffer_list::DiffBufferList>,        project: Entity<Project>,        workspace: Entity<Workspace>,        window: &mut Window,        cx: &mut Context<Self>,    ) -> Self {        let workspace_handle = workspace.downgrade();        let diff = cx.new(|cx| {            DiffMultibuffer::new(                branch_diff,                Capability::ReadWrite,                "No uncommitted changes",                move |editor, cx| {                    editor.set_diff_hunk_delegate(Some(Arc::new(UncommittedDiffHunkDelegate)), cx);                    editor.rhs_editor().update(cx, |rhs_editor, _cx| {                        rhs_editor.set_read_only(false);                        rhs_editor.register_addon(GitPanelAddon {                            workspace: workspace_handle,                        });                    });                },                project.clone(),                workspace.clone(),                window,                cx,            )        });        Self::from_diff(diff, project, workspace, cx)    }     fn from_diff(        diff: Entity<DiffMultibuffer>,        project: Entity<Project>,        workspace: Entity<Workspace>,        cx: &mut Context<Self>,    ) -> Self {        let observation = cx.observe(&diff, |_, _, cx| cx.notify());        Self {            project,            workspace: workspace.downgrade(),            diff,            _diff_observation: observation,        }    }     pub fn diff_base<'a>(&'a self, cx: &'a App) -> &'a DiffBase {        self.diff.read(cx).diff_base(cx)    }     pub(crate) fn repo(&self, cx: &App) -> Option<Entity<Repository>> {        self.diff.read(cx).repo(cx)    }     pub(crate) fn set_repo(&mut self, repo: Option<Entity<Repository>>, cx: &mut Context<Self>) {        self.diff            .update(cx, |diff, cx| diff.set_repo(repo.clone(), cx));    }     pub fn move_to_entry(        &mut self,        entry: GitStatusEntry,        window: &mut Window,        cx: &mut Context<Self>,    ) {        self.diff            .update(cx, |diff, cx| diff.move_to_entry(entry, window, cx));    }     pub fn move_to_project_path(        &mut self,        project_path: &ProjectPath,        window: &mut Window,        cx: &mut Context<Self>,    ) {        self.diff.update(cx, |diff, cx| {            diff.move_to_project_path(project_path, window, cx)        });    }     fn move_to_beginning(&mut self, window: &mut Window, cx: &mut Context<Self>) {        self.diff            .update(cx, |diff, cx| diff.move_to_beginning(window, cx));    }     pub fn calculate_changed_lines(&self, cx: &App) -> (u32, u32) {        self.diff.read(cx).calculate_changed_lines(cx)    }     /// Returns the total count of review comments across all hunks/files.    pub fn total_review_comment_count(&self, cx: &App) -> usize {        self.diff.read(cx).total_review_comment_count()    }     /// Returns the splittable editor of the currently-shown diff view.    pub fn editor(&self, cx: &App) -> Entity<SplittableEditor> {        self.diff.read(cx).editor().clone()    }     /// Returns the multibuffer of the currently-shown diff view.    pub fn multibuffer(&self, cx: &App) -> Entity<MultiBuffer> {        self.diff.read(cx).multibuffer().clone()    }     fn button_states(&self, cx: &App) -> ButtonStates {        let diff = self.diff.read(cx);        let editor = diff.editor().read(cx).rhs_editor().clone();        let editor = editor.read(cx);        let snapshot = diff.multibuffer().read(cx).snapshot(cx);        let prev_next = snapshot.diff_hunks().nth(1).is_some();        let (selection, ranges) = diff.selected_ranges(cx);        let mut has_staged_hunks = false;        let mut has_unstaged_hunks = false;        for hunk in editor.diff_hunks_in_ranges(&ranges, &snapshot) {            match hunk.status.secondary {                DiffHunkSecondaryStatus::HasSecondaryHunk                | DiffHunkSecondaryStatus::SecondaryHunkAdditionPending => {                    has_unstaged_hunks = true;                }                DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk => {                    has_staged_hunks = true;                    has_unstaged_hunks = true;                }                DiffHunkSecondaryStatus::NoSecondaryHunk                | DiffHunkSecondaryStatus::SecondaryHunkRemovalPending => {                    has_staged_hunks = true;                }            }        }        let mut stage_all = false;        let mut unstage_all = false;        self.workspace            .read_with(cx, |workspace, cx| {                if let Some(git_panel) = workspace.panel::<GitPanel>(cx) {                    let git_panel = git_panel.read(cx);                    stage_all = git_panel.can_stage_all();                    unstage_all = git_panel.can_unstage_all();                }            })            .ok();         ButtonStates {            stage: has_unstaged_hunks,            unstage: has_staged_hunks,            prev_next,            selection,            stage_all,            unstage_all,        }    }     #[cfg(any(test, feature = "test-support"))]    pub fn excerpt_paths(&self, cx: &App) -> Vec<std::sync::Arc<util::rel_path::RelPath>> {        self.diff.read(cx).excerpt_paths(cx)    }     #[cfg(any(test, feature = "test-support"))]    pub fn excerpt_file_paths(&self, cx: &App) -> Vec<String> {        self.diff.read(cx).excerpt_file_paths(cx)    }} struct ButtonStates {    stage: bool,    unstage: bool,    prev_next: bool,    selection: bool,    stage_all: bool,    unstage_all: bool,} impl EventEmitter<EditorEvent> for ProjectDiff {} impl Focusable for ProjectDiff {    fn focus_handle(&self, cx: &App) -> FocusHandle {        self.diff.read(cx).focus_handle(cx)    }} impl Item for ProjectDiff {    type Event = EditorEvent;     fn tab_icon(&self, _window: &Window, _cx: &App) -> Option<Icon> {        Some(Icon::new(IconName::GitBranch).color(Color::Muted))    }     fn to_item_events(event: &EditorEvent, f: &mut dyn FnMut(ItemEvent)) {        Editor::to_item_events(event, f)    }     fn deactivated(&mut self, window: &mut Window, cx: &mut Context<Self>) {        self.diff            .update(cx, |diff, cx| diff.deactivated(window, cx));    }     fn navigate(        &mut self,        data: Arc<dyn Any + Send>,        window: &mut Window,        cx: &mut Context<Self>,    ) -> bool {        self.diff            .update(cx, |diff, cx| diff.navigate(data, window, cx))    }     fn tab_tooltip_text(&self, cx: &App) -> Option<SharedString> {        Some(self.tab_content_text(0, cx))    }     fn tab_content(&self, params: TabContentParams, _window: &Window, cx: &App) -> AnyElement {        Label::new(self.tab_content_text(0, cx))            .color(if params.selected {                Color::Default            } else {                Color::Muted            })            .into_any_element()    }     fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {        "Uncommitted Changes".into()    }     fn telemetry_event_text(&self) -> Option<&'static str> {        Some("Project Diff Opened")    }     fn as_searchable(&self, _: &Entity<Self>, cx: &App) -> Option<Box<dyn SearchableItemHandle>> {        Some(Box::new(self.diff.read(cx).editor().clone()))    }     fn for_each_project_item(        &self,        cx: &App,        f: &mut dyn FnMut(gpui::EntityId, &dyn project::ProjectItem),    ) {        self.diff.read(cx).for_each_project_item(cx, f)    }     fn active_project_path(&self, cx: &App) -> Option<ProjectPath> {        self.diff.read(cx).active_project_path(cx)    }     fn set_nav_history(        &mut self,        nav_history: ItemNavHistory,        _: &mut Window,        cx: &mut Context<Self>,    ) {        self.diff            .update(cx, |diff, cx| diff.set_nav_history(nav_history, cx));    }     fn can_split(&self) -> bool {        true    }     fn clone_on_split(        &self,        _workspace_id: Option<workspace::WorkspaceId>,        window: &mut Window,        cx: &mut Context<Self>,    ) -> Task<Option<Entity<Self>>>    where        Self: Sized,    {        let Some(workspace) = self.workspace.upgrade() else {            return Task::ready(None);        };        Task::ready(Some(cx.new(|cx| {            ProjectDiff::new(self.project.clone(), workspace, window, cx)        })))    }     fn is_dirty(&self, cx: &App) -> bool {        self.diff.read(cx).is_dirty(cx)    }     fn has_conflict(&self, cx: &App) -> bool {        self.diff.read(cx).has_conflict(cx)    }     fn can_save(&self, _: &App) -> bool {        true    }     fn save(        &mut self,        options: SaveOptions,        project: Entity<Project>,        window: &mut Window,        cx: &mut Context<Self>,    ) -> Task<Result<()>> {        self.diff            .update(cx, |diff, cx| diff.save(options, project, window, cx))    }     fn save_as(

Lines 1–500 of 2046.