Zed ZippyAutofix79ce75c8 months agoHistory
422 lines · 18.8 KB · Rust
RawDownload
use crate::commit_tooltip::CommitAvatar;use crate::git_status_icon;use git::status::FileStatus;use git::{    BuildCommitPermalinkParams, GitHostingProviderRegistry, GitRemote, ParsedGitRemote,    parse_git_remote_url, repository::RepoPath,};use gpui::{    AnyElement, App, ClickEvent, FontWeight, Hsla, InteractiveElement, IntoElement, ParentElement,    SharedString, Styled, Window, px,};use project::git_store::Repository;use std::sync::Arc;use time::{OffsetDateTime, UtcOffset};use ui::{    ButtonLike, ButtonStyle, CopyButton, Icon, IconButton, IconName, IconSize, Label, LabelSize,    Tooltip, prelude::*,}; pub struct CommitDetailsSidebarData {    pub sha: SharedString,    pub author_name: SharedString,    pub author_email: SharedString,    pub commit_timestamp: i64,    pub subject: SharedString,    pub body: SharedString,    pub ref_names: Vec<SharedString>,    pub accent_color: Hsla,} impl CommitDetailsSidebarData {    pub fn new(        sha: SharedString,        author_name: SharedString,        author_email: SharedString,        commit_timestamp: i64,        subject: SharedString,        body: SharedString,    ) -> Self {        Self {            sha,            author_name,            author_email,            commit_timestamp,            subject,            body,            ref_names: Vec::new(),            accent_color: gpui::hsla(0.0, 0.0, 0.5, 1.0),        }    }     pub fn with_ref_names(mut self, ref_names: Vec<SharedString>) -> Self {        self.ref_names = ref_names;        self    }     pub fn with_accent_color(mut self, accent_color: Hsla) -> Self {        self.accent_color = accent_color;        self    }} pub struct CommitDetailsSidebar {    data: CommitDetailsSidebarData,    remote: Option<GitRemote>,    changed_files: Vec<(RepoPath, FileStatus)>,    on_close: Option<Box<dyn Fn(&mut Window, &mut App) + 'static>>,    on_file_click: Option<Arc<dyn Fn(&RepoPath, &ClickEvent, &mut Window, &mut App) + 'static>>,} impl CommitDetailsSidebar {    pub fn new(data: CommitDetailsSidebarData) -> Self {        Self {            data,            remote: None,            changed_files: Vec::new(),            on_close: None,            on_file_click: None,        }    }     pub fn remote(mut self, remote: Option<GitRemote>) -> Self {        self.remote = remote;        self    }     pub fn changed_files(mut self, files: Vec<(RepoPath, FileStatus)>) -> Self {        self.changed_files = files;        self    }     pub fn on_close(mut self, callback: impl Fn(&mut Window, &mut App) + 'static) -> Self {        self.on_close = Some(Box::new(callback));        self    }     pub fn on_file_click(        mut self,        callback: impl Fn(&RepoPath, &ClickEvent, &mut Window, &mut App) + 'static,    ) -> Self {        self.on_file_click = Some(Arc::new(callback));        self    }     pub fn render(self, window: &mut Window, cx: &mut App) -> AnyElement {        let full_sha = self.data.sha.clone();         let date_string = OffsetDateTime::from_unix_timestamp(self.data.commit_timestamp)            .ok()            .map(|datetime| {                let local_offset = UtcOffset::current_local_offset().unwrap_or(UtcOffset::UTC);                let local_datetime = datetime.to_offset(local_offset);                let format =                    time::format_description::parse("[month repr:short] [day], [year]").ok();                format                    .and_then(|f| local_datetime.format(&f).ok())                    .unwrap_or_default()            })            .unwrap_or_default();         let author_email_for_avatar = if self.data.author_email.is_empty() {            None        } else {            Some(self.data.author_email.clone())        };        let avatar = CommitAvatar::new(&full_sha, author_email_for_avatar, self.remote.as_ref());         let avatar_element = v_flex()            .w(px(32.))            .h(px(32.))            .border_1()            .border_color(cx.theme().colors().border)            .rounded_full()            .justify_center()            .items_center()            .child(                avatar                    .avatar(window, cx)                    .map(|a| a.size(px(32.)).into_any_element())                    .unwrap_or_else(|| {                        Icon::new(IconName::Person)                            .color(Color::Muted)                            .size(IconSize::XLarge)                            .into_any_element()                    }),            );         let changed_files_count = self.changed_files.len();        let author_name = self.data.author_name.clone();        let author_email = self.data.author_email.clone();        let subject = self.data.subject.clone();        let body = self.data.body.clone();        let ref_names = self.data.ref_names.clone();        let accent_color = self.data.accent_color;        let remote = self.remote.clone();        let on_close = self.on_close;        let on_file_click = self.on_file_click;        let changed_files = self.changed_files;         v_flex()            .w(px(300.))            .h_full()            .border_l_1()            .border_color(cx.theme().colors().border)            .bg(cx.theme().colors().surface_background)            // Commit Details            .child(                div()                    .id("commit-details")                    .w_full()                    .child(                        v_flex()                            .p_3()                            .gap_3()                            .child(                                h_flex()                                    .w_full()                                    .px_1()                                    .items_start()                                    .justify_between()                                    .child(avatar_element)                                    .when_some(on_close, |this, on_close| {                                        this.child(                                            IconButton::new("close-detail", IconName::Close)                                                .icon_size(IconSize::Small)                                                .on_click(move |_, window, cx| {                                                    on_close(window, cx);                                                }),                                        )                                    }),                            )                            .child(                                v_flex()                                    .px_1()                                    .gap_0p5()                                    .child(                                        Label::new(author_name.clone())                                            .weight(FontWeight::SEMIBOLD),                                    )                                    .child(                                        Label::new(date_string)                                            .color(Color::Muted)                                            .size(LabelSize::Small),                                    ),                            )                            .child(                                v_flex()                                    .gap_1p5()                                    .child(                                        h_flex()                                            .gap_1()                                            .child(                                                div()                                                    .w_5()                                                    .h_5()                                                    .flex()                                                    .items_center()                                                    .justify_center()                                                    .child(                                                        Icon::new(IconName::Person)                                                            .size(IconSize::Small)                                                            .color(Color::Muted),                                                    ),                                            )                                            .child(                                                div()                                                    .id("author-name")                                                    .child(                                                        Label::new(author_name)                                                            .size(LabelSize::Small)                                                            .color(Color::Muted),                                                    )                                                    .when(!author_email.is_empty(), |this| {                                                        this.tooltip(Tooltip::text(author_email))                                                    }),                                            ),                                    )                                    .child({                                        let short_sha: SharedString =                                            full_sha.chars().take(7).collect::<String>().into();                                        h_flex()                                            .gap_1()                                            .child(                                                CopyButton::new("copy-sha", full_sha.to_string())                                                    .tooltip_label("Copy SHA"),                                            )                                            .child(                                                Label::new(short_sha)                                                    .size(LabelSize::Small)                                                    .color(Color::Muted),                                            )                                    })                                    .when_some(remote, |this, remote| {                                        let provider_name = remote.host.name();                                        let icon = match provider_name.as_str() {                                            "GitHub" => IconName::Github,                                            _ => IconName::Link,                                        };                                        let parsed_remote = ParsedGitRemote {                                            owner: remote.owner.as_ref().into(),                                            repo: remote.repo.as_ref().into(),                                        };                                        let params = BuildCommitPermalinkParams {                                            sha: full_sha.as_ref(),                                        };                                        let url = remote                                            .host                                            .build_commit_permalink(&parsed_remote, params)                                            .to_string();                                        this.child(                                            h_flex().child(                                                ButtonLike::new("view-on-provider")                                                    .style(ButtonStyle::Subtle)                                                    .child(                                                        h_flex()                                                            .gap_1()                                                            .child(                                                                Icon::new(icon)                                                                    .size(IconSize::Small)                                                                    .color(Color::Muted),                                                            )                                                            .child(                                                                Label::new(format!(                                                                    "View on {}",                                                                    provider_name                                                                ))                                                                .size(LabelSize::Small)                                                                .color(Color::Muted),                                                            ),                                                    )                                                    .on_click(move |_, _, cx| {                                                        cx.open_url(&url);                                                    }),                                            ),                                        )                                    }),                            )                            .when(!ref_names.is_empty(), |this| {                                this.child(h_flex().pt_1().gap_1().flex_wrap().children(                                    ref_names.iter().map(|name| {                                        render_badge(name, accent_color).into_any_element()                                    }),                                ))                            })                            .child(                                v_flex()                                    .px_1()                                    .gap_2()                                    .child(Label::new(subject).weight(FontWeight::MEDIUM))                                    .when(!body.is_empty(), |this| {                                        this.child(                                            Label::new(body)                                                .size(LabelSize::Small)                                                .color(Color::Muted),                                        )                                    }),                            ),                    )                    .h_1_2()                    .overflow_y_scroll(),            )            // Changes list            .child(                div()                    .border_t_1()                    .border_color(cx.theme().colors().border)                    .id("changed-files")                    .w_full()                    .child(                        v_flex()                            .p_3()                            .gap_2()                            .child(                                Label::new(format!("{} Changed Files", changed_files_count))                                    .size(LabelSize::Small)                                    .color(Color::Muted),                            )                            .when(!changed_files.is_empty(), |this| {                                this.child(v_flex().gap_1().children(changed_files.iter().map(                                    |(path, status)| {                                        let file_name: String = path                                            .file_name()                                            .map(|n| n.to_string())                                            .unwrap_or_default();                                        let dir_path: String = path                                            .parent()                                            .map(|p| p.as_unix_str().to_string())                                            .unwrap_or_default();                                         let on_file_click = on_file_click.clone();                                        let path_for_click = path.clone();                                         h_flex()                                            .id(SharedString::from(path.as_unix_str().to_string()))                                            .gap_1()                                            .overflow_hidden()                                            .child(git_status_icon(*status))                                            .child(                                                Label::new(file_name)                                                    .size(LabelSize::Small)                                                    .single_line(),                                            )                                            .when(!dir_path.is_empty(), |this| {                                                this.child(                                                    Label::new(dir_path)                                                        .size(LabelSize::Small)                                                        .color(Color::Muted)                                                        .single_line(),                                                )                                            })                                            .when_some(on_file_click, |this, callback| {                                                this.cursor_pointer().on_click(                                                    move |event, window, cx| {                                                        callback(                                                            &path_for_click,                                                            event,                                                            window,                                                            cx,                                                        );                                                    },                                                )                                            })                                    },                                )))                            }),                    )                    .h_1_2()                    .overflow_y_scroll(),            )            .into_any_element()    }} fn render_badge(name: &SharedString, accent_color: Hsla) -> impl IntoElement {    div()        .px_1p5()        .py_0p5()        .flex()        .items_center()        .justify_center()        .rounded_md()        .bg(accent_color.opacity(0.18))        .border_1()        .border_color(accent_color.opacity(0.55))        .child(            Label::new(name.clone())                .size(LabelSize::Small)                .color(Color::Default)                .single_line(),        )} pub fn get_remote_from_repository(repository: &Repository, cx: &mut App) -> Option<GitRemote> {    let remote_url = repository.default_remote_url()?;    let provider_registry = GitHostingProviderRegistry::default_global(cx);    let (provider, parsed) = parse_git_remote_url(provider_registry, &remote_url)?;    Some(GitRemote {        host: provider,        owner: parsed.owner.into(),        repo: parsed.repo.into(),    })}