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. Load more
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(