@@ -1,4 +1,7 @@
-use crate::git_panel::{GitPanel, GitPanelAddon, GitStatusEntry};
+use crate::{
+ git_panel::{GitPanel, GitPanelAddon, GitStatusEntry},
+ remote_button::{render_publish_button, render_push_button},
+};
use anyhow::Result;
use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus};
use collections::HashSet;
@@ -9,8 +12,9 @@ use editor::{
};
use futures::StreamExt;
use git::{
- repository::Branch, status::FileStatus, Commit, StageAll, StageAndNext, ToggleStaged,
- UnstageAll, UnstageAndNext,
+ repository::{Branch, Upstream, UpstreamTracking, UpstreamTrackingStatus},
+ status::FileStatus,
+ Commit, StageAll, StageAndNext, ToggleStaged, UnstageAll, UnstageAndNext,
};
use gpui::{
actions, Action, AnyElement, AnyView, App, AppContext as _, AsyncWindowContext, Entity,
@@ -1022,6 +1026,287 @@ impl Render for ProjectDiffToolbar {
}
}
+#[derive(IntoElement, IntoComponent)]
+#[component(scope = "Version Control")]
+pub struct ProjectDiffEmptyState {
+ pub no_repo: bool,
+ pub can_push_and_pull: bool,
+ pub focus_handle: Option<FocusHandle>,
+ pub current_branch: Option<Branch>,
+ // has_pending_commits: bool,
+ // ahead_of_remote: bool,
+ // no_git_repository: bool,
+}
+
+impl RenderOnce for ProjectDiffEmptyState {
+ fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
+ let status_against_remote = |ahead_by: usize, behind_by: usize| -> bool {
+ match self.current_branch {
+ Some(Branch {
+ upstream:
+ Some(Upstream {
+ tracking:
+ UpstreamTracking::Tracked(UpstreamTrackingStatus {
+ ahead, behind, ..
+ }),
+ ..
+ }),
+ ..
+ }) if (ahead > 0) == (ahead_by > 0) && (behind > 0) == (behind_by > 0) => true,
+ _ => false,
+ }
+ };
+
+ let change_count = |current_branch: &Branch| -> (usize, usize) {
+ match current_branch {
+ Branch {
+ upstream:
+ Some(Upstream {
+ tracking:
+ UpstreamTracking::Tracked(UpstreamTrackingStatus {
+ ahead, behind, ..
+ }),
+ ..
+ }),
+ ..
+ } => (*ahead as usize, *behind as usize),
+ _ => (0, 0),
+ }
+ };
+
+ let not_ahead_or_behind = status_against_remote(0, 0);
+ let ahead_of_remote = status_against_remote(1, 0);
+ let branch_not_on_remote = if let Some(branch) = self.current_branch.as_ref() {
+ branch.upstream.is_none()
+ } else {
+ false
+ };
+
+ let has_branch_container = |branch: &Branch| {
+ h_flex()
+ .max_w(px(420.))
+ .bg(cx.theme().colors().text.opacity(0.05))
+ .border_1()
+ .border_color(cx.theme().colors().border)
+ .rounded_sm()
+ .gap_8()
+ .px_6()
+ .py_4()
+ .map(|this| {
+ if ahead_of_remote {
+ let ahead_count = change_count(branch).0;
+ let ahead_string = format!("{} Commits Ahead", ahead_count);
+ this.child(
+ v_flex()
+ .child(Headline::new(ahead_string).size(HeadlineSize::Small))
+ .child(
+ Label::new(format!("Push your changes to {}", branch.name))
+ .color(Color::Muted),
+ ),
+ )
+ .child(div().child(render_push_button(
+ self.focus_handle,
+ "push".into(),
+ ahead_count as u32,
+ )))
+ } else if branch_not_on_remote {
+ this.child(
+ v_flex()
+ .child(Headline::new("Publish Branch").size(HeadlineSize::Small))
+ .child(
+ Label::new(format!("Create {} on remote", branch.name))
+ .color(Color::Muted),
+ ),
+ )
+ .child(
+ div().child(render_publish_button(self.focus_handle, "publish".into())),
+ )
+ } else {
+ this.child(Label::new("Remote status unknown").color(Color::Muted))
+ }
+ })
+ };
+
+ v_flex().size_full().items_center().justify_center().child(
+ v_flex()
+ .gap_1()
+ .when(self.no_repo, |this| {
+ // TODO: add git init
+ this.text_center()
+ .child(Label::new("No Repository").color(Color::Muted))
+ })
+ .map(|this| {
+ if not_ahead_or_behind && self.current_branch.is_some() {
+ this.text_center()
+ .child(Label::new("No Changes").color(Color::Muted))
+ } else {
+ this.when_some(self.current_branch.as_ref(), |this, branch| {
+ this.child(has_branch_container(&branch))
+ })
+ }
+ }),
+ )
+ }
+}
+
+// .when(self.can_push_and_pull, |this| {
+// let remote_button = crate::render_remote_button(
+// "project-diff-remote-button",
+// &branch,
+// self.focus_handle.clone(),
+// false,
+// );
+
+// match remote_button {
+// Some(button) => {
+// this.child(h_flex().justify_around().child(button))
+// }
+// None => this.child(
+// h_flex()
+// .justify_around()
+// .child(Label::new("Remote up to date")),
+// ),
+// }
+// }),
+//
+// // .map(|this| {
+// this.child(h_flex().justify_around().mt_1().child(
+// Button::new("project-diff-close-button", "Close").when_some(
+// self.focus_handle.clone(),
+// |this, focus_handle| {
+// this.key_binding(KeyBinding::for_action_in(
+// &CloseActiveItem::default(),
+// &focus_handle,
+// window,
+// cx,
+// ))
+// .on_click(move |_, window, cx| {
+// window.focus(&focus_handle);
+// window
+// .dispatch_action(Box::new(CloseActiveItem::default()), cx);
+// })
+// },
+// ),
+// ))
+// }),
+
+mod preview {
+ use git::repository::{
+ Branch, CommitSummary, Upstream, UpstreamTracking, UpstreamTrackingStatus,
+ };
+ use ui::prelude::*;
+
+ use super::ProjectDiffEmptyState;
+
+ // View this component preview using `workspace: open component-preview`
+ impl ComponentPreview for ProjectDiffEmptyState {
+ fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
+ let unknown_upstream: Option<UpstreamTracking> = None;
+ let ahead_of_upstream: Option<UpstreamTracking> = Some(
+ UpstreamTrackingStatus {
+ ahead: 2,
+ behind: 0,
+ }
+ .into(),
+ );
+
+ let not_ahead_or_behind_upstream: Option<UpstreamTracking> = Some(
+ UpstreamTrackingStatus {
+ ahead: 0,
+ behind: 0,
+ }
+ .into(),
+ );
+
+ fn branch(upstream: Option<UpstreamTracking>) -> Branch {
+ Branch {
+ is_head: true,
+ name: "some-branch".into(),
+ upstream: upstream.map(|tracking| Upstream {
+ ref_name: "origin/some-branch".into(),
+ tracking,
+ }),
+ most_recent_commit: Some(CommitSummary {
+ sha: "abc123".into(),
+ subject: "Modify stuff".into(),
+ commit_timestamp: 1710932954,
+ has_parent: true,
+ }),
+ }
+ }
+
+ let no_repo_state = ProjectDiffEmptyState {
+ no_repo: true,
+ can_push_and_pull: false,
+ focus_handle: None,
+ current_branch: None,
+ };
+
+ let no_changes_state = ProjectDiffEmptyState {
+ no_repo: false,
+ can_push_and_pull: true,
+ focus_handle: None,
+ current_branch: Some(branch(not_ahead_or_behind_upstream)),
+ };
+
+ let ahead_of_upstream_state = ProjectDiffEmptyState {
+ no_repo: false,
+ can_push_and_pull: true,
+ focus_handle: None,
+ current_branch: Some(branch(ahead_of_upstream)),
+ };
+
+ let unknown_upstream_state = ProjectDiffEmptyState {
+ no_repo: false,
+ can_push_and_pull: true,
+ focus_handle: None,
+ current_branch: Some(branch(unknown_upstream)),
+ };
+
+ let (width, height) = (px(480.), px(320.));
+
+ v_flex()
+ .gap_6()
+ .children(vec![example_group(vec![
+ single_example(
+ "No Repo",
+ div()
+ .w(width)
+ .h(height)
+ .child(no_repo_state)
+ .into_any_element(),
+ ),
+ single_example(
+ "No Changes",
+ div()
+ .w(width)
+ .h(height)
+ .child(no_changes_state)
+ .into_any_element(),
+ ),
+ single_example(
+ "Unknown Upstream",
+ div()
+ .w(width)
+ .h(height)
+ .child(unknown_upstream_state)
+ .into_any_element(),
+ ),
+ single_example(
+ "Ahead of Remote",
+ div()
+ .w(width)
+ .h(height)
+ .child(ahead_of_upstream_state)
+ .into_any_element(),
+ ),
+ ])
+ .vertical()])
+ .into_any_element()
+ }
+ }
+}
+
#[cfg(not(target_os = "windows"))]
#[cfg(test)]
mod tests {