243 lines · 8.7 KB · Rust
RawDownload
use crate::commit_view::CommitView;use git::Oid;use gpui::{Action, ClipboardItem, Entity, FocusHandle, SharedString, WeakEntity, Window, actions};use project::{GIT_COMMAND_TASK_TAG, git_store::Repository}; use task::{TaskContext, TaskVariables, VariableName};use ui::{Color, ContextMenu, ContextMenuEntry, IconName, IconPosition, prelude::*};use workspace::Workspace; actions!(    git_graph,    [        /// Copies the SHA of the selected commit to the clipboard.        CopyCommitSha,        /// Copies a tag from the selected commit to the clipboard.        CopyCommitTag,        /// Opens the commit view for the selected commit.        OpenCommitView,    ]); const COMMIT_TAG_LIST_WIDTH_IN_REMS: Rems = rems(10.);const CUSTOM_GIT_COMMANDS_DOCS_SLUG: &str = "tasks#custom-git-commands"; pub(crate) struct CommitContextMenuData {    pub(crate) sha: Oid,    pub(crate) tag_names: Vec<SharedString>,} #[derive(Clone, Copy, PartialEq, Eq)]pub(crate) enum CommitContextMenuSource {    GitGraph,    GitPanel,} pub(crate) fn commit_context_menu(    commit: CommitContextMenuData,    source: CommitContextMenuSource,    ref_name: Option<SharedString>,    focus_handle: FocusHandle,    repository: Option<WeakEntity<Repository>>,    workspace: WeakEntity<Workspace>,    window: &mut Window,    cx: &mut App,) -> Entity<ContextMenu> {    let sha = commit.sha;    let sha_short = sha.display_short();    let git_tasks = git_context_menu_tasks(        git_task_context(&repository, sha, ref_name.as_deref(), cx),        &workspace,        cx,    );    let header = match &ref_name {        Some(ref_name) => format!("Ref {ref_name}"),        None => format!("Commit {sha_short}"),    };     ContextMenu::build(window, cx, move |context_menu, _, _| {        context_menu            .context(focus_handle)            .header(header)            .entry("View Diff", Some(OpenCommitView.boxed_clone()), {                let repository = repository.clone();                let workspace = workspace.clone();                move |window, cx| {                    let Some(repository) = repository.clone() else {                        return;                    };                    CommitView::open(                        sha.to_string(),                        repository,                        workspace.clone(),                        None,                        None,                        window,                        cx,                    );                }            })            .entry(                "Copy SHA",                Some(CopyCommitSha.boxed_clone()),                move |_window, cx| {                    cx.write_to_clipboard(ClipboardItem::new_string(sha.to_string()));                },            )            .when_some(ref_name.clone(), |menu, ref_name| {                menu.entry("Copy Ref Name", None, move |_window, cx| {                    cx.write_to_clipboard(ClipboardItem::new_string(ref_name.to_string()));                })            })            .when(ref_name.is_none(), |menu| {                menu.map(|menu| {                    let tag_names = commit.tag_names.clone();                    let copy_tag_label = "Copy Tag";                     match tag_names.as_slice() {                        [] => menu.item(                            ContextMenuEntry::new(copy_tag_label)                                .action(CopyCommitTag.boxed_clone())                                .disabled(true),                        ),                        [tag_name] => {                            let tag_name = tag_name.clone();                            let label = format!("{copy_tag_label}: {tag_name}");                            menu.entry(                                label,                                Some(CopyCommitTag.boxed_clone()),                                move |_window, cx| {                                    cx.write_to_clipboard(ClipboardItem::new_string(                                        tag_name.to_string(),                                    ));                                },                            )                        }                        _ => menu.submenu(copy_tag_label, move |menu, _window, _cx| {                            let mut menu = menu.fixed_width(COMMIT_TAG_LIST_WIDTH_IN_REMS.into());                             for tag_name in tag_names.clone() {                                let tag_name_to_copy = tag_name.clone();                                menu = menu.entry(tag_name, None, move |_window, cx| {                                    cx.write_to_clipboard(ClipboardItem::new_string(                                        tag_name_to_copy.to_string(),                                    ));                                });                            }                            menu                        }),                    }                })            })            .when(source == CommitContextMenuSource::GitPanel, |menu| {                menu.entry("Show in Git Graph", None, move |window, cx| {                    window.dispatch_action(                        Box::new(crate::git_graph::OpenAtCommit {                            sha: sha.to_string(),                        }),                        cx,                    );                })            })            .map(|mut menu| {                menu = menu.separator().header("Custom Commands");                 if git_tasks.is_empty() {                    return menu.item(                        ContextMenuEntry::new("Learn More")                            .icon(IconName::ArrowUpRight)                            .icon_color(Color::Muted)                            .icon_position(IconPosition::End)                            .handler(|_window, cx| {                                let docs_url =                                    release_channel::docs_url(CUSTOM_GIT_COMMANDS_DOCS_SLUG, cx);                                cx.open_url(&docs_url);                            }),                    );                }                 for (task_source_kind, resolved_task) in git_tasks {                    let label = resolved_task.display_label().to_string();                    let workspace = workspace.clone();                    menu = menu.entry(label, None, move |window, cx| {                        workspace                            .update(cx, |workspace, cx| {                                workspace.schedule_resolved_task(                                    task_source_kind.clone(),                                    resolved_task.clone(),                                    false,                                    window,                                    cx,                                );                            })                            .ok();                    });                }                 menu            })    })} fn git_task_context(    repository: &Option<WeakEntity<Repository>>,    commit_sha: git::Oid,    ref_name: Option<&str>,    cx: &App,) -> Option<TaskContext> {    let repository_path = repository        .as_ref()?        .upgrade()?        .read(cx)        .work_directory_abs_path        .to_path_buf();    let repository_name = repository_path        .file_name()        .and_then(|name| name.to_str())        .map(ToString::to_string);    let mut task_variables = TaskVariables::from_iter([        (VariableName::GitSha, commit_sha.to_string()),        (VariableName::GitShaShort, commit_sha.display_short()),        (            VariableName::GitRepositoryPath,            repository_path.to_string_lossy().into_owned(),        ),    ]);     if let Some(repository_name) = repository_name {        task_variables.insert(VariableName::GitRepositoryName, repository_name);    }    if let Some(ref_name) = ref_name {        task_variables.insert(VariableName::GitRef, ref_name.to_string());    }     Some(TaskContext {        cwd: Some(repository_path),        task_variables,        ..TaskContext::default()    })} fn git_context_menu_tasks(    task_context: Option<TaskContext>,    workspace: &WeakEntity<Workspace>,    cx: &App,) -> Vec<(project::TaskSourceKind, task::ResolvedTask)> {    let Some(task_context) = task_context else {        return Vec::new();    };    let Some(workspace) = workspace.upgrade() else {        return Vec::new();    };    let project = workspace.read(cx).project().clone();    let task_inventory = project.read_with(cx, |project, cx| {        project.task_store().read(cx).task_inventory().cloned()    });    let Some(task_inventory) = task_inventory else {        return Vec::new();    };     task_inventory        .read(cx)        .resolve_global_tasks_with_tag(GIT_COMMAND_TASK_TAG, &task_context)}