use crate::{ conflict_view::ConflictAddon, git_panel::{GitPanel, GitPanelAddon, GitStatusEntry}, git_panel_settings::GitPanelSettings, resolve_active_repository,};use agent_settings::AgentSettings;use anyhow::{Context as _, Result, anyhow};use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus};use collections::{HashMap, HashSet};use editor::{ Addon, Editor, EditorEvent, EditorSettings, SelectionEffects, SplittableEditor, actions::{GoToHunk, GoToPreviousHunk, SendReviewToAgent}, multibuffer_context_lines, scroll::Autoscroll,};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::{ Project, ProjectPath, git_store::{ Repository, branch_diff::{self, BranchDiffEvent, DiffBase}, },};use settings::{Settings, SettingsStore};use smol::future::yield_now;use std::any::{Any, TypeId};use std::sync::Arc;use theme::ActiveTheme;use ui::{DiffStat, Divider, KeyBinding, 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, ]); pub struct ProjectDiff { project: Entity<Project>, multibuffer: Entity<MultiBuffer>, branch_diff: Entity<branch_diff::BranchDiff>, editor: Entity<SplittableEditor>, buffer_diff_subscriptions: HashMap<Arc<RelPath>, (Entity<BufferDiff>, Subscription)>, workspace: WeakEntity<Workspace>, focus_handle: FocusHandle, pending_scroll: Option<PathKey>, review_comment_count: usize, _task: Task<Result<()>>, _subscription: Subscription,} #[derive(Clone, Copy, Debug, PartialEq, Eq)]pub enum RefreshReason { DiffChanged, StatusesChanged, EditorSaved,} 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(|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 existing = workspace .items_of_type::<Self>(cx) .find(|item| matches!(item.read(cx).diff_base(cx), DiffBase::Merge { .. })); if let Some(existing) = existing { workspace.activate_item(&existing, true, true, window, 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_default_branch(project, workspace.clone(), 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: base_ref.to_string().into(), } .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 = resolve_active_repository(workspace, 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); }) }) } 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| { branch_diff::BranchDiff::new( DiffBase::Merge { base_ref: main_branch, }, project.clone(), window, cx, ) })?; 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) } fn new_impl( branch_diff: Entity<branch_diff::BranchDiff>, project: Entity<Project>, workspace: Entity<Workspace>, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let focus_handle = cx.focus_handle(); let multibuffer = cx.new(|cx| { let mut multibuffer = MultiBuffer::new(Capability::ReadWrite); multibuffer.set_all_diff_hunks_expanded(cx); multibuffer }); let editor = cx.new(|cx| { let diff_display_editor = SplittableEditor::new( EditorSettings::get_global(cx).diff_view_style, multibuffer.clone(), project.clone(), workspace.clone(), window, cx, ); match branch_diff.read(cx).diff_base() { DiffBase::Head => {} DiffBase::Merge { .. } => diff_display_editor.set_render_diff_hunk_controls( Arc::new(|_, _, _, _, _, _, _, _| gpui::Empty.into_any_element()), cx, ), } diff_display_editor.rhs_editor().update(cx, |editor, cx| { editor.disable_diagnostics(cx); editor.set_show_diff_review_button(true, cx); match branch_diff.read(cx).diff_base() { DiffBase::Head => { editor.register_addon(GitPanelAddon { workspace: workspace.downgrade(), }); } DiffBase::Merge { .. } => { editor.register_addon(BranchDiffAddon { branch_diff: branch_diff.clone(), }); editor.start_temporary_diff_override(); } } }); diff_display_editor }); let editor_subscription = cx.subscribe_in(&editor, window, Self::handle_editor_event); let primary_editor = editor.read(cx).rhs_editor().clone(); let review_comment_subscription = cx.subscribe(&primary_editor, |this, _editor, event: &EditorEvent, cx| { if let EditorEvent::ReviewCommentsChanged { total_count } = event { this.review_comment_count = *total_count; cx.notify(); } }); let branch_diff_subscription = cx.subscribe_in( &branch_diff, window, move |this, _git_store, event, window, cx| match event { BranchDiffEvent::FileListChanged => { this._task = window.spawn(cx, { let this = cx.weak_entity(); async |cx| Self::refresh(this, RefreshReason::StatusesChanged, cx).await }) } }, ); let mut was_sort_by_path = GitPanelSettings::get_global(cx).sort_by_path; let mut was_collapse_untracked_diff = GitPanelSettings::get_global(cx).collapse_untracked_diff; cx.observe_global_in::<SettingsStore>(window, move |this, window, cx| { let is_sort_by_path = GitPanelSettings::get_global(cx).sort_by_path; let is_collapse_untracked_diff = GitPanelSettings::get_global(cx).collapse_untracked_diff; if is_sort_by_path != was_sort_by_path || is_collapse_untracked_diff != was_collapse_untracked_diff { this._task = { window.spawn(cx, { let this = cx.weak_entity(); async |cx| Self::refresh(this, RefreshReason::StatusesChanged, cx).await }) } } was_sort_by_path = is_sort_by_path; was_collapse_untracked_diff = is_collapse_untracked_diff; }) .detach(); let task = window.spawn(cx, { let this = cx.weak_entity(); async |cx| Self::refresh(this, RefreshReason::StatusesChanged, cx).await }); Self { project, workspace: workspace.downgrade(), branch_diff, focus_handle, editor, multibuffer, buffer_diff_subscriptions: Default::default(), pending_scroll: None, review_comment_count: 0, _task: task, _subscription: Subscription::join( branch_diff_subscription, Subscription::join(editor_subscription, review_comment_subscription), ), } } pub fn diff_base<'a>(&'a self, cx: &'a App) -> &'a DiffBase { self.branch_diff.read(cx).diff_base() } pub fn move_to_entry( &mut self, entry: GitStatusEntry, window: &mut Window, cx: &mut Context<Self>, ) { let Some(git_repo) = self.branch_diff.read(cx).repo() else { return; }; let repo = git_repo.read(cx); let sort_prefix = sort_prefix(repo, &entry.repo_path, entry.status, cx); let path_key = PathKey::with_sort_prefix(sort_prefix, entry.repo_path.as_ref().clone()); self.move_to_path(path_key, window, cx) } pub fn move_to_project_path( &mut self, project_path: &ProjectPath, window: &mut Window, cx: &mut Context<Self>, ) { let Some(git_repo) = self.branch_diff.read(cx).repo() else { return; }; let Some(repo_path) = git_repo .read(cx) .project_path_to_repo_path(project_path, cx) else { return; }; let status = git_repo .read(cx) .status_for_path(&repo_path) .map(|entry| entry.status) .unwrap_or(FileStatus::Untracked); let sort_prefix = sort_prefix(&git_repo.read(cx), &repo_path, status, cx); let path_key = PathKey::with_sort_prefix(sort_prefix, repo_path.as_ref().clone()); self.move_to_path(path_key, window, cx) }Lines 1–500 of 2810. Load more
use crate::{ conflict_view::ConflictAddon, git_panel::{GitPanel, GitPanelAddon, GitStatusEntry}, git_panel_settings::GitPanelSettings, resolve_active_repository, }; use agent_settings::AgentSettings; use anyhow::{Context as _, Result, anyhow}; use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus}; use collections::{HashMap, HashSet}; use editor::{ Addon, Editor, EditorEvent, EditorSettings, SelectionEffects, SplittableEditor, actions::{GoToHunk, GoToPreviousHunk, SendReviewToAgent}, multibuffer_context_lines, scroll::Autoscroll, }; 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::{ Project, ProjectPath, git_store::{ Repository, branch_diff::{self, BranchDiffEvent, DiffBase}, }, }; use settings::{Settings, SettingsStore}; use smol::future::yield_now; use std::any::{Any, TypeId}; use std::sync::Arc; use theme::ActiveTheme; use ui::{DiffStat, Divider, KeyBinding, 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, ] ); pub struct ProjectDiff { project: Entity<Project>, multibuffer: Entity<MultiBuffer>, branch_diff: Entity<branch_diff::BranchDiff>, editor: Entity<SplittableEditor>, buffer_diff_subscriptions: HashMap<Arc<RelPath>, (Entity<BufferDiff>, Subscription)>, workspace: WeakEntity<Workspace>, focus_handle: FocusHandle, pending_scroll: Option<PathKey>, review_comment_count: usize, _task: Task<Result<()>>, _subscription: Subscription, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum RefreshReason { DiffChanged, StatusesChanged, EditorSaved, } 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(|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 existing = workspace .items_of_type::<Self>(cx) .find(|item| matches!(item.read(cx).diff_base(cx), DiffBase::Merge { .. })); if let Some(existing) = existing { workspace.activate_item(&existing, true, true, window, 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_default_branch(project, workspace.clone(), 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: base_ref.to_string().into(), } .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 = resolve_active_repository(workspace, 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); }) }) } 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| { branch_diff::BranchDiff::new( DiffBase::Merge { base_ref: main_branch, }, project.clone(), window, cx, ) })?; 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) } fn new_impl( branch_diff: Entity<branch_diff::BranchDiff>, project: Entity<Project>, workspace: Entity<Workspace>, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let focus_handle = cx.focus_handle(); let multibuffer = cx.new(|cx| { let mut multibuffer = MultiBuffer::new(Capability::ReadWrite); multibuffer.set_all_diff_hunks_expanded(cx); multibuffer }); let editor = cx.new(|cx| { let diff_display_editor = SplittableEditor::new( EditorSettings::get_global(cx).diff_view_style, multibuffer.clone(), project.clone(), workspace.clone(), window, cx, ); match branch_diff.read(cx).diff_base() { DiffBase::Head => {} DiffBase::Merge { .. } => diff_display_editor.set_render_diff_hunk_controls( Arc::new(|_, _, _, _, _, _, _, _| gpui::Empty.into_any_element()), cx, ), } diff_display_editor.rhs_editor().update(cx, |editor, cx| { editor.disable_diagnostics(cx); editor.set_show_diff_review_button(true, cx); match branch_diff.read(cx).diff_base() { DiffBase::Head => { editor.register_addon(GitPanelAddon { workspace: workspace.downgrade(), }); } DiffBase::Merge { .. } => { editor.register_addon(BranchDiffAddon { branch_diff: branch_diff.clone(), }); editor.start_temporary_diff_override(); } } }); diff_display_editor }); let editor_subscription = cx.subscribe_in(&editor, window, Self::handle_editor_event); let primary_editor = editor.read(cx).rhs_editor().clone(); let review_comment_subscription = cx.subscribe(&primary_editor, |this, _editor, event: &EditorEvent, cx| { if let EditorEvent::ReviewCommentsChanged { total_count } = event { this.review_comment_count = *total_count; cx.notify(); } }); let branch_diff_subscription = cx.subscribe_in( &branch_diff, window, move |this, _git_store, event, window, cx| match event { BranchDiffEvent::FileListChanged => { this._task = window.spawn(cx, { let this = cx.weak_entity(); async |cx| Self::refresh(this, RefreshReason::StatusesChanged, cx).await }) } }, ); let mut was_sort_by_path = GitPanelSettings::get_global(cx).sort_by_path; let mut was_collapse_untracked_diff = GitPanelSettings::get_global(cx).collapse_untracked_diff; cx.observe_global_in::<SettingsStore>(window, move |this, window, cx| { let is_sort_by_path = GitPanelSettings::get_global(cx).sort_by_path; let is_collapse_untracked_diff = GitPanelSettings::get_global(cx).collapse_untracked_diff; if is_sort_by_path != was_sort_by_path || is_collapse_untracked_diff != was_collapse_untracked_diff { this._task = { window.spawn(cx, { let this = cx.weak_entity(); async |cx| Self::refresh(this, RefreshReason::StatusesChanged, cx).await }) } } was_sort_by_path = is_sort_by_path; was_collapse_untracked_diff = is_collapse_untracked_diff; }) .detach(); let task = window.spawn(cx, { let this = cx.weak_entity(); async |cx| Self::refresh(this, RefreshReason::StatusesChanged, cx).await }); Self { project, workspace: workspace.downgrade(), branch_diff, focus_handle, editor, multibuffer, buffer_diff_subscriptions: Default::default(), pending_scroll: None, review_comment_count: 0, _task: task, _subscription: Subscription::join( branch_diff_subscription, Subscription::join(editor_subscription, review_comment_subscription), ), } } pub fn diff_base<'a>(&'a self, cx: &'a App) -> &'a DiffBase { self.branch_diff.read(cx).diff_base() } pub fn move_to_entry( &mut self, entry: GitStatusEntry, window: &mut Window, cx: &mut Context<Self>, ) { let Some(git_repo) = self.branch_diff.read(cx).repo() else { return; }; let repo = git_repo.read(cx); let sort_prefix = sort_prefix(repo, &entry.repo_path, entry.status, cx); let path_key = PathKey::with_sort_prefix(sort_prefix, entry.repo_path.as_ref().clone()); self.move_to_path(path_key, window, cx) } pub fn move_to_project_path( &mut self, project_path: &ProjectPath, window: &mut Window, cx: &mut Context<Self>, ) { let Some(git_repo) = self.branch_diff.read(cx).repo() else { return; }; let Some(repo_path) = git_repo .read(cx) .project_path_to_repo_path(project_path, cx) else { return; }; let status = git_repo .read(cx) .status_for_path(&repo_path) .map(|entry| entry.status) .unwrap_or(FileStatus::Untracked); let sort_prefix = sort_prefix(&git_repo.read(cx), &repo_path, status, cx); let path_key = PathKey::with_sort_prefix(sort_prefix, repo_path.as_ref().clone()); self.move_to_path(path_key, window, cx) }