Skip to repository content244 lines · 8.9 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T04:25:10.372Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
commit_context_menu.rs
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