use crate::{ branch_picker, diff_multibuffer::DiffMultibuffer, project_diff::{ self, CompareWithBranch, DeployBranchDiff, ReviewDiff, render_send_review_to_agent_button, },};use agent_settings::AgentSettings;use anyhow::{Context as _, Result, anyhow};use editor::{ Addon, Editor, EditorEvent, RestoreOnlyDiffHunkDelegate, SplittableEditor, actions::SendReviewToAgent,};use git::{repository::DiffType, status::FileStatus};use gpui::{ Action, AnyElement, App, AppContext as _, Entity, EventEmitter, FocusHandle, Focusable, Render, SharedString, Subscription, Task, WeakEntity,};use language::{BufferId, Capability};use project::{ Project, ProjectPath, git_store::{ Repository, diff_buffer_list::{self, DiffBase}, },};use settings::Settings;use std::{ any::{Any, TypeId}, sync::Arc,};use ui::{DiffStat, Divider, PopoverMenu, Tooltip, prelude::*};use workspace::{ ItemHandle, ItemNavHistory, SerializableItem, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, item::{Item, ItemEvent, SaveOptions, TabContentParams}, notifications::NotifyTaskExt, searchable::SearchableItemHandle,};use zed_actions::agent::ReviewBranchDiff; /// The workspace item for a branch (merge-base) diff: "Changes since {branch}"./// It wraps a single [`DiffMultibuffer`] over [`DiffBase::Merge`] and delegates/// the [`Item`] surface to it. The merge base can be changed in place via the/// [`BranchDiffToolbar`]'s branch picker, which reloads without reconfiguring/// the editor (the merge styling is identical for every base ref).pub struct BranchDiff { diff: Entity<DiffMultibuffer>, project: Entity<Project>, workspace: WeakEntity<Workspace>, _diff_event_subscription: Subscription,} struct BranchDiffAddon { branch_diff: Entity<diff_buffer_list::DiffBufferList>,} impl Addon for BranchDiffAddon { fn to_any(&self) -> &dyn std::any::Any { self } fn override_status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> { self.branch_diff .read(cx) .status_for_buffer_id(buffer_id, cx) }} impl BranchDiff { pub(crate) fn register(workspace: &mut Workspace, cx: &mut Context<Workspace>) { workspace.register_action(Self::deploy_branch_diff); workspace.register_action(Self::compare_with_branch); workspace::register_serializable_item::<Self>(cx); } fn deploy_branch_diff( workspace: &mut Workspace, _: &DeployBranchDiff, 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 | DiffBase::Index | DiffBase::Staged => 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).repo(cx).map_or(true, |current| { current.read(cx).id != intended_repo.read(cx).id }); if needs_switch { existing.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); } #[cfg(any(test, feature = "test-support"))] pub 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 base_ref = main_branch .await?? .context("Could not determine default branch")?; cx.update(|window, cx| { cx.new(|cx| { Self::new_with_base_ref(project, workspace, base_ref, Some(repo), window, cx) }) }) }) } pub(crate) 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| { cx.update(|window, cx| { cx.new(|cx| { Self::new_with_base_ref(project, workspace, base_ref, Some(repo), window, cx) }) }) }) } pub(crate) fn new_with_base_ref( project: Entity<Project>, workspace: Entity<Workspace>, base_ref: SharedString, repo: Option<Entity<Repository>>, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let branch_diff = cx.new(|cx| { let mut branch_diff = diff_buffer_list::DiffBufferList::new( DiffBase::Merge { base_ref }, project.clone(), window, cx, ); if repo.is_some() { branch_diff.set_repo(repo, cx); } branch_diff }); let branch_diff_for_addon = branch_diff.clone(); let diff = cx.new(|cx| { DiffMultibuffer::new( branch_diff, Capability::ReadWrite, "No changes", move |editor, cx| { editor.set_diff_hunk_delegate(Some(Arc::new(RestoreOnlyDiffHunkDelegate)), cx); editor.rhs_editor().update(cx, move |rhs_editor, _cx| { rhs_editor.set_read_only(false); rhs_editor.register_addon(BranchDiffAddon { branch_diff: branch_diff_for_addon, }); }); }, 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 diff_event_subscription = cx.subscribe(&diff, |_, _, event: &EditorEvent, cx| { cx.emit(event.clone()) }); Self { diff, project, workspace: workspace.downgrade(), _diff_event_subscription: diff_event_subscription, } } pub(crate) 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, cx)); } fn set_merge_base(&mut self, base_ref: SharedString, cx: &mut Context<Self>) { self.diff.update(cx, |diff, cx| { diff.branch_diff().update(cx, |branch_diff, cx| { branch_diff.set_diff_base(DiffBase::Merge { base_ref }, cx); }); }); } fn review_diff(&mut self, _: &ReviewDiff, window: &mut Window, cx: &mut Context<Self>) { let DiffBase::Merge { base_ref } = self.diff_base(cx).clone() else { return; }; let Some(repo) = self.repo(cx) 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); } #[cfg(any(test, feature = "test-support"))] pub fn editor(&self, cx: &App) -> Entity<SplittableEditor> { self.diff.read(cx).editor().clone() }} impl EventEmitter<EditorEvent> for BranchDiff {} impl Focusable for BranchDiff { fn focus_handle(&self, cx: &App) -> FocusHandle { self.diff.read(cx).focus_handle(cx) }} impl Item for BranchDiff { 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 { match self.diff_base(cx) { DiffBase::Merge { base_ref } => format!("Changes since {}", base_ref).into(), DiffBase::Head | DiffBase::Index | DiffBase::Staged => "Changes".into(), } } fn telemetry_event_text(&self) -> Option<&'static str> { Some("Branch 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,Lines 1–500 of 1199. Load more
use crate::{ branch_picker, diff_multibuffer::DiffMultibuffer, project_diff::{ self, CompareWithBranch, DeployBranchDiff, ReviewDiff, render_send_review_to_agent_button, }, }; use agent_settings::AgentSettings; use anyhow::{Context as _, Result, anyhow}; use editor::{ Addon, Editor, EditorEvent, RestoreOnlyDiffHunkDelegate, SplittableEditor, actions::SendReviewToAgent, }; use git::{repository::DiffType, status::FileStatus}; use gpui::{ Action, AnyElement, App, AppContext as _, Entity, EventEmitter, FocusHandle, Focusable, Render, SharedString, Subscription, Task, WeakEntity, }; use language::{BufferId, Capability}; use project::{ Project, ProjectPath, git_store::{ Repository, diff_buffer_list::{self, DiffBase}, }, }; use settings::Settings; use std::{ any::{Any, TypeId}, sync::Arc, }; use ui::{DiffStat, Divider, PopoverMenu, Tooltip, prelude::*}; use workspace::{ ItemHandle, ItemNavHistory, SerializableItem, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, item::{Item, ItemEvent, SaveOptions, TabContentParams}, notifications::NotifyTaskExt, searchable::SearchableItemHandle, }; use zed_actions::agent::ReviewBranchDiff; /// The workspace item for a branch (merge-base) diff: "Changes since {branch}". /// It wraps a single [`DiffMultibuffer`] over [`DiffBase::Merge`] and delegates /// the [`Item`] surface to it. The merge base can be changed in place via the /// [`BranchDiffToolbar`]'s branch picker, which reloads without reconfiguring /// the editor (the merge styling is identical for every base ref). pub struct BranchDiff { diff: Entity<DiffMultibuffer>, project: Entity<Project>, workspace: WeakEntity<Workspace>, _diff_event_subscription: Subscription, } struct BranchDiffAddon { branch_diff: Entity<diff_buffer_list::DiffBufferList>, } impl Addon for BranchDiffAddon { fn to_any(&self) -> &dyn std::any::Any { self } fn override_status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> { self.branch_diff .read(cx) .status_for_buffer_id(buffer_id, cx) } } impl BranchDiff { pub(crate) fn register(workspace: &mut Workspace, cx: &mut Context<Workspace>) { workspace.register_action(Self::deploy_branch_diff); workspace.register_action(Self::compare_with_branch); workspace::register_serializable_item::<Self>(cx); } fn deploy_branch_diff( workspace: &mut Workspace, _: &DeployBranchDiff, 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 | DiffBase::Index | DiffBase::Staged => 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).repo(cx).map_or(true, |current| { current.read(cx).id != intended_repo.read(cx).id }); if needs_switch { existing.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); } #[cfg(any(test, feature = "test-support"))] pub 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 base_ref = main_branch .await?? .context("Could not determine default branch")?; cx.update(|window, cx| { cx.new(|cx| { Self::new_with_base_ref(project, workspace, base_ref, Some(repo), window, cx) }) }) }) } pub(crate) 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| { cx.update(|window, cx| { cx.new(|cx| { Self::new_with_base_ref(project, workspace, base_ref, Some(repo), window, cx) }) }) }) } pub(crate) fn new_with_base_ref( project: Entity<Project>, workspace: Entity<Workspace>, base_ref: SharedString, repo: Option<Entity<Repository>>, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let branch_diff = cx.new(|cx| { let mut branch_diff = diff_buffer_list::DiffBufferList::new( DiffBase::Merge { base_ref }, project.clone(), window, cx, ); if repo.is_some() { branch_diff.set_repo(repo, cx); } branch_diff }); let branch_diff_for_addon = branch_diff.clone(); let diff = cx.new(|cx| { DiffMultibuffer::new( branch_diff, Capability::ReadWrite, "No changes", move |editor, cx| { editor.set_diff_hunk_delegate(Some(Arc::new(RestoreOnlyDiffHunkDelegate)), cx); editor.rhs_editor().update(cx, move |rhs_editor, _cx| { rhs_editor.set_read_only(false); rhs_editor.register_addon(BranchDiffAddon { branch_diff: branch_diff_for_addon, }); }); }, 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 diff_event_subscription = cx.subscribe(&diff, |_, _, event: &EditorEvent, cx| { cx.emit(event.clone()) }); Self { diff, project, workspace: workspace.downgrade(), _diff_event_subscription: diff_event_subscription, } } pub(crate) 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, cx)); } fn set_merge_base(&mut self, base_ref: SharedString, cx: &mut Context<Self>) { self.diff.update(cx, |diff, cx| { diff.branch_diff().update(cx, |branch_diff, cx| { branch_diff.set_diff_base(DiffBase::Merge { base_ref }, cx); }); }); } fn review_diff(&mut self, _: &ReviewDiff, window: &mut Window, cx: &mut Context<Self>) { let DiffBase::Merge { base_ref } = self.diff_base(cx).clone() else { return; }; let Some(repo) = self.repo(cx) 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); } #[cfg(any(test, feature = "test-support"))] pub fn editor(&self, cx: &App) -> Entity<SplittableEditor> { self.diff.read(cx).editor().clone() } } impl EventEmitter<EditorEvent> for BranchDiff {} impl Focusable for BranchDiff { fn focus_handle(&self, cx: &App) -> FocusHandle { self.diff.read(cx).focus_handle(cx) } } impl Item for BranchDiff { 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 { match self.diff_base(cx) { DiffBase::Merge { base_ref } => format!("Changes since {}", base_ref).into(), DiffBase::Head | DiffBase::Index | DiffBase::Staged => "Changes".into(), } } fn telemetry_event_text(&self) -> Option<&'static str> { Some("Branch 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,