Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T03:28:15.050Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

commit_context_menu.rs

244 lines · 8.9 KB · rust
1use crate::commit_view::CommitView;
2use git::Oid;
3use gpui::{Action, ClipboardItem, Entity, FocusHandle, SharedString, WeakEntity, Window, actions};
4use project::{GIT_COMMAND_TASK_TAG, git_store::Repository};
5
6use task::{TaskContext, TaskVariables, VariableName};
7use ui::{Color, ContextMenu, ContextMenuEntry, IconName, IconPosition, prelude::*};
8use workspace::Workspace;
9
10actions!(
11    git_graph,
12    [
13        /// Copies the SHA of the selected commit to the clipboard.
14        CopyCommitSha,
15        /// Copies a tag from the selected commit to the clipboard.
16        CopyCommitTag,
17        /// Opens the commit view for the selected commit.
18        OpenCommitView,
19    ]
20);
21
22const COMMIT_TAG_LIST_WIDTH_IN_REMS: Rems = rems(10.);
23const CUSTOM_GIT_COMMANDS_DOCS_SLUG: &str = "tasks#custom-git-commands";
24
25pub(crate) struct CommitContextMenuData {
26    pub(crate) sha: Oid,
27    pub(crate) tag_names: Vec<SharedString>,
28}
29
30#[derive(Clone, Copy, PartialEq, Eq)]
31pub(crate) enum CommitContextMenuSource {
32    GitGraph,
33    GitPanel,
34}
35
36pub(crate) fn commit_context_menu(
37    commit: CommitContextMenuData,
38    source: CommitContextMenuSource,
39    ref_name: Option<SharedString>,
40    focus_handle: FocusHandle,
41    repository: Option<WeakEntity<Repository>>,
42    workspace: WeakEntity<Workspace>,
43    window: &mut Window,
44    cx: &mut App,
45) -> Entity<ContextMenu> {
46    let sha = commit.sha;
47    let sha_short = sha.display_short();
48    let git_tasks = git_context_menu_tasks(
49        git_task_context(&repository, sha, ref_name.as_deref(), cx),
50        &workspace,
51        cx,
52    );
53    let header = match &ref_name {
54        Some(ref_name) => format!("Ref {ref_name}"),
55        None => format!("Commit {sha_short}"),
56    };
57
58    ContextMenu::build(window, cx, move |context_menu, _, _| {
59        context_menu
60            .context(focus_handle)
61            .header(header)
62            .entry("View Diff", Some(OpenCommitView.boxed_clone()), {
63                let repository = repository.clone();
64                let workspace = workspace.clone();
65                move |window, cx| {
66                    let Some(repository) = repository.clone() else {
67                        return;
68                    };
69                    CommitView::open(
70                        sha.to_string(),
71                        repository,
72                        workspace.clone(),
73                        None,
74                        None,
75                        window,
76                        cx,
77                    );
78                }
79            })
80            .entry(
81                "Copy SHA",
82                Some(CopyCommitSha.boxed_clone()),
83                move |_window, cx| {
84                    cx.write_to_clipboard(ClipboardItem::new_string(sha.to_string()));
85                },
86            )
87            .when_some(ref_name.clone(), |menu, ref_name| {
88                menu.entry("Copy Ref Name", None, move |_window, cx| {
89                    cx.write_to_clipboard(ClipboardItem::new_string(ref_name.to_string()));
90                })
91            })
92            .when(ref_name.is_none(), |menu| {
93                menu.map(|menu| {
94                    let tag_names = commit.tag_names.clone();
95                    let copy_tag_label = "Copy Tag";
96
97                    match tag_names.as_slice() {
98                        [] => menu.item(
99                            ContextMenuEntry::new(copy_tag_label)
100                                .action(CopyCommitTag.boxed_clone())
101                                .disabled(true),
102                        ),
103                        [tag_name] => {
104                            let tag_name = tag_name.clone();
105                            let label = format!("{copy_tag_label}: {tag_name}");
106                            menu.entry(
107                                label,
108                                Some(CopyCommitTag.boxed_clone()),
109                                move |_window, cx| {
110                                    cx.write_to_clipboard(ClipboardItem::new_string(
111                                        tag_name.to_string(),
112                                    ));
113                                },
114                            )
115                        }
116                        _ => menu.submenu(copy_tag_label, move |menu, _window, _cx| {
117                            let mut menu = menu.fixed_width(COMMIT_TAG_LIST_WIDTH_IN_REMS.into());
118
119                            for tag_name in tag_names.clone() {
120                                let tag_name_to_copy = tag_name.clone();
121                                menu = menu.entry(tag_name, None, move |_window, cx| {
122                                    cx.write_to_clipboard(ClipboardItem::new_string(
123                                        tag_name_to_copy.to_string(),
124                                    ));
125                                });
126                            }
127                            menu
128                        }),
129                    }
130                })
131            })
132            .when(source == CommitContextMenuSource::GitPanel, |menu| {
133                menu.entry("Show in Git Graph", None, move |window, cx| {
134                    window.dispatch_action(
135                        Box::new(crate::git_graph::OpenAtCommit {
136                            sha: sha.to_string(),
137                        }),
138                        cx,
139                    );
140                })
141            })
142            .map(|mut menu| {
143                menu = menu.separator().header("Custom Commands");
144
145                if git_tasks.is_empty() {
146                    return menu.item(
147                        ContextMenuEntry::new("Learn More")
148                            .icon(IconName::ArrowUpRight)
149                            .icon_color(Color::Muted)
150                            .icon_position(IconPosition::End)
151                            .handler(|_window, cx| {
152                                let docs_url =
153                                    release_channel::docs_url(CUSTOM_GIT_COMMANDS_DOCS_SLUG, cx);
154                                cx.open_url(&docs_url);
155                            }),
156                    );
157                }
158
159                for (task_source_kind, resolved_task) in git_tasks {
160                    let label = resolved_task.display_label().to_string();
161                    let workspace = workspace.clone();
162                    menu = menu.entry(label, None, move |window, cx| {
163                        workspace
164                            .update(cx, |workspace, cx| {
165                                workspace.schedule_resolved_task(
166                                    task_source_kind.clone(),
167                                    resolved_task.clone(),
168                                    false,
169                                    window,
170                                    cx,
171                                );
172                            })
173                            .ok();
174                    });
175                }
176
177                menu
178            })
179    })
180}
181
182fn git_task_context(
183    repository: &Option<WeakEntity<Repository>>,
184    commit_sha: git::Oid,
185    ref_name: Option<&str>,
186    cx: &App,
187) -> Option<TaskContext> {
188    let repository_path = repository
189        .as_ref()?
190        .upgrade()?
191        .read(cx)
192        .work_directory_abs_path
193        .to_path_buf();
194    let repository_name = repository_path
195        .file_name()
196        .and_then(|name| name.to_str())
197        .map(ToString::to_string);
198    let mut task_variables = TaskVariables::from_iter([
199        (VariableName::GitSha, commit_sha.to_string()),
200        (VariableName::GitShaShort, commit_sha.display_short()),
201        (
202            VariableName::GitRepositoryPath,
203            repository_path.to_string_lossy().into_owned(),
204        ),
205    ]);
206
207    if let Some(repository_name) = repository_name {
208        task_variables.insert(VariableName::GitRepositoryName, repository_name);
209    }
210    if let Some(ref_name) = ref_name {
211        task_variables.insert(VariableName::GitRef, ref_name.to_string());
212    }
213
214    Some(TaskContext {
215        cwd: Some(repository_path),
216        task_variables,
217        ..TaskContext::default()
218    })
219}
220
221fn git_context_menu_tasks(
222    task_context: Option<TaskContext>,
223    workspace: &WeakEntity<Workspace>,
224    cx: &App,
225) -> Vec<(project::TaskSourceKind, task::ResolvedTask)> {
226    let Some(task_context) = task_context else {
227        return Vec::new();
228    };
229    let Some(workspace) = workspace.upgrade() else {
230        return Vec::new();
231    };
232    let project = workspace.read(cx).project().clone();
233    let task_inventory = project.read_with(cx, |project, cx| {
234        project.task_store().read(cx).task_inventory().cloned()
235    });
236    let Some(task_inventory) = task_inventory else {
237        return Vec::new();
238    };
239
240    task_inventory
241        .read(cx)
242        .resolve_global_tasks_with_tag(GIT_COMMAND_TASK_TAG, &task_context)
243}
244
Served at tenant.openagents/omega Member data and write actions are omitted.