Skip to repository content552 lines · 18.2 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T03:29:33.867Z 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
git_picker.rs
1use std::fmt::Display;
2
3use gpui::{
4 App, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement,
5 KeyContext, ModifiersChangedEvent, MouseButton, ParentElement, Rems, Render, Styled,
6 Subscription, WeakEntity, Window, actions, rems,
7};
8use project::git_store::Repository;
9use ui::{
10 FluentBuilder, ToggleButtonGroup, ToggleButtonGroupStyle, ToggleButtonSimple, Tooltip,
11 prelude::*,
12};
13use workspace::{ModalView, Workspace, pane};
14
15use crate::branch_picker::{
16 self, BranchList, CycleBranchFilter, DeleteBranch, ForceDeleteBranch, ShowAllBranches,
17 ShowLocalBranches, ShowRemoteBranches,
18};
19use crate::stash_picker::{self, DropStashItem, ShowStashItem, StashList};
20
21actions!(git_picker, [ActivateBranchesTab, ActivateStashTab,]);
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
24pub enum GitPickerTab {
25 Branches,
26 Stashes,
27}
28
29impl Display for GitPickerTab {
30 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31 let label = match self {
32 GitPickerTab::Branches => "Branches",
33 GitPickerTab::Stashes => "Stashes",
34 };
35 write!(f, "{}", label)
36 }
37}
38
39pub struct GitPicker {
40 tab: GitPickerTab,
41 workspace: WeakEntity<Workspace>,
42 repository: Option<Entity<Repository>>,
43 width: Rems,
44 branch_list: Option<Entity<BranchList>>,
45 stash_list: Option<Entity<StashList>>,
46 _subscriptions: Vec<Subscription>,
47 popover_style: bool,
48}
49
50impl GitPicker {
51 pub fn new(
52 workspace: WeakEntity<Workspace>,
53 repository: Option<Entity<Repository>>,
54 initial_tab: GitPickerTab,
55 width: Rems,
56 window: &mut Window,
57 cx: &mut Context<Self>,
58 ) -> Self {
59 Self::new_internal(workspace, repository, initial_tab, width, false, window, cx)
60 }
61
62 fn new_internal(
63 workspace: WeakEntity<Workspace>,
64 repository: Option<Entity<Repository>>,
65 initial_tab: GitPickerTab,
66 width: Rems,
67 popover_style: bool,
68 window: &mut Window,
69 cx: &mut Context<Self>,
70 ) -> Self {
71 let mut this = Self {
72 tab: initial_tab,
73 workspace,
74 repository,
75 width,
76 branch_list: None,
77 stash_list: None,
78 _subscriptions: Vec::new(),
79 popover_style,
80 };
81
82 this.ensure_active_picker(window, cx);
83 this
84 }
85
86 fn ensure_active_picker(&mut self, window: &mut Window, cx: &mut Context<Self>) {
87 match self.tab {
88 GitPickerTab::Branches => {
89 self.ensure_branch_list(window, cx);
90 }
91 GitPickerTab::Stashes => {
92 self.ensure_stash_list(window, cx);
93 }
94 }
95 }
96
97 fn ensure_branch_list(
98 &mut self,
99 window: &mut Window,
100 cx: &mut Context<Self>,
101 ) -> Entity<BranchList> {
102 if self.branch_list.is_none() {
103 let show_footer = !self.popover_style;
104 let branch_list = cx.new(|cx| {
105 branch_picker::create_embedded(
106 self.workspace.clone(),
107 self.repository.clone(),
108 self.width,
109 show_footer,
110 window,
111 cx,
112 )
113 });
114
115 let subscription = cx.subscribe(&branch_list, |this, _, _: &DismissEvent, cx| {
116 if this.tab == GitPickerTab::Branches {
117 cx.emit(DismissEvent);
118 }
119 });
120
121 self._subscriptions.push(subscription);
122 self.branch_list = Some(branch_list);
123 }
124 self.branch_list.clone().unwrap()
125 }
126
127 fn ensure_stash_list(
128 &mut self,
129 window: &mut Window,
130 cx: &mut Context<Self>,
131 ) -> Entity<StashList> {
132 if self.stash_list.is_none() {
133 let show_footer = !self.popover_style;
134 let stash_list = cx.new(|cx| {
135 stash_picker::create_embedded(
136 self.repository.clone(),
137 self.workspace.clone(),
138 self.width,
139 show_footer,
140 window,
141 cx,
142 )
143 });
144
145 let subscription = cx.subscribe(&stash_list, |this, _, _: &DismissEvent, cx| {
146 if this.tab == GitPickerTab::Stashes {
147 cx.emit(DismissEvent);
148 }
149 });
150
151 self._subscriptions.push(subscription);
152 self.stash_list = Some(stash_list);
153 }
154 self.stash_list.clone().unwrap()
155 }
156
157 fn activate_next_tab(&mut self, window: &mut Window, cx: &mut Context<Self>) {
158 self.tab = match self.tab {
159 GitPickerTab::Branches => GitPickerTab::Stashes,
160 GitPickerTab::Stashes => GitPickerTab::Branches,
161 };
162 self.ensure_active_picker(window, cx);
163 self.focus_active_picker(window, cx);
164 cx.notify();
165 }
166
167 fn activate_previous_tab(&mut self, window: &mut Window, cx: &mut Context<Self>) {
168 self.tab = match self.tab {
169 GitPickerTab::Branches => GitPickerTab::Stashes,
170 GitPickerTab::Stashes => GitPickerTab::Branches,
171 };
172 self.ensure_active_picker(window, cx);
173 self.focus_active_picker(window, cx);
174 cx.notify();
175 }
176
177 fn focus_active_picker(&self, window: &mut Window, cx: &mut App) {
178 match self.tab {
179 GitPickerTab::Branches => {
180 if let Some(branch_list) = &self.branch_list {
181 branch_list.focus_handle(cx).focus(window, cx);
182 }
183 }
184 GitPickerTab::Stashes => {
185 if let Some(stash_list) = &self.stash_list {
186 stash_list.focus_handle(cx).focus(window, cx);
187 }
188 }
189 }
190 }
191
192 fn render_tab_bar(&self, cx: &mut Context<Self>) -> impl IntoElement {
193 let focus_handle = self.focus_handle(cx);
194 let branches_focus_handle = focus_handle.clone();
195 let stash_focus_handle = focus_handle;
196
197 h_flex().p_2().pb_0p5().w_full().child(
198 ToggleButtonGroup::single_row(
199 "git-picker-tabs",
200 [
201 ToggleButtonSimple::new(
202 GitPickerTab::Branches.to_string(),
203 cx.listener(|this, _, window, cx| {
204 this.tab = GitPickerTab::Branches;
205 this.ensure_active_picker(window, cx);
206 this.focus_active_picker(window, cx);
207 cx.notify();
208 }),
209 )
210 .tooltip(move |_, cx| {
211 Tooltip::for_action_in(
212 "Toggle Branch Picker",
213 &ActivateBranchesTab,
214 &branches_focus_handle,
215 cx,
216 )
217 }),
218 ToggleButtonSimple::new(
219 GitPickerTab::Stashes.to_string(),
220 cx.listener(|this, _, window, cx| {
221 this.tab = GitPickerTab::Stashes;
222 this.ensure_active_picker(window, cx);
223 this.focus_active_picker(window, cx);
224 cx.notify();
225 }),
226 )
227 .tooltip(move |_, cx| {
228 Tooltip::for_action_in(
229 "Toggle Stash Picker",
230 &ActivateStashTab,
231 &stash_focus_handle,
232 cx,
233 )
234 }),
235 ],
236 )
237 .label_size(LabelSize::Default)
238 .style(ToggleButtonGroupStyle::Outlined)
239 .auto_width()
240 .selected_index(match self.tab {
241 GitPickerTab::Branches => 0,
242 GitPickerTab::Stashes => 1,
243 }),
244 )
245 }
246
247 fn render_active_picker(
248 &mut self,
249 window: &mut Window,
250 cx: &mut Context<Self>,
251 ) -> impl IntoElement {
252 match self.tab {
253 GitPickerTab::Branches => {
254 let branch_list = self.ensure_branch_list(window, cx);
255 branch_list.into_any_element()
256 }
257 GitPickerTab::Stashes => {
258 let stash_list = self.ensure_stash_list(window, cx);
259 stash_list.into_any_element()
260 }
261 }
262 }
263
264 fn handle_modifiers_changed(
265 &mut self,
266 ev: &ModifiersChangedEvent,
267 window: &mut Window,
268 cx: &mut Context<Self>,
269 ) {
270 match self.tab {
271 GitPickerTab::Branches => {
272 if let Some(branch_list) = &self.branch_list {
273 branch_list.update(cx, |list, cx| {
274 list.handle_modifiers_changed(ev, window, cx);
275 });
276 }
277 }
278 GitPickerTab::Stashes => {
279 if let Some(stash_list) = &self.stash_list {
280 stash_list.update(cx, |list, cx| {
281 list.handle_modifiers_changed(ev, window, cx);
282 });
283 }
284 }
285 }
286 }
287
288 fn handle_delete_branch(
289 &mut self,
290 _: &DeleteBranch,
291 window: &mut Window,
292 cx: &mut Context<Self>,
293 ) {
294 if let Some(branch_list) = &self.branch_list {
295 branch_list.update(cx, |list, cx| {
296 list.handle_delete(&DeleteBranch, window, cx);
297 });
298 }
299 }
300
301 fn handle_force_delete_branch(
302 &mut self,
303 _: &ForceDeleteBranch,
304 window: &mut Window,
305 cx: &mut Context<Self>,
306 ) {
307 if let Some(branch_list) = &self.branch_list {
308 branch_list.update(cx, |list, cx| {
309 list.handle_force_delete(&ForceDeleteBranch, window, cx);
310 });
311 }
312 }
313
314 fn set_active_branch_filter(
315 &mut self,
316 branch_filter: branch_picker::BranchFilter,
317 window: &mut Window,
318 cx: &mut Context<Self>,
319 ) {
320 if let Some(branch_list) = &self.branch_list {
321 branch_list.update(cx, |list, cx| {
322 list.set_branch_filter(branch_filter, window, cx);
323 });
324 }
325 }
326
327 fn handle_show_all_branches(
328 &mut self,
329 _: &ShowAllBranches,
330 window: &mut Window,
331 cx: &mut Context<Self>,
332 ) {
333 self.set_active_branch_filter(branch_picker::BranchFilter::All, window, cx);
334 }
335
336 fn handle_show_local_branches(
337 &mut self,
338 _: &ShowLocalBranches,
339 window: &mut Window,
340 cx: &mut Context<Self>,
341 ) {
342 self.set_active_branch_filter(branch_picker::BranchFilter::Local, window, cx);
343 }
344
345 fn handle_show_remote_branches(
346 &mut self,
347 _: &ShowRemoteBranches,
348 window: &mut Window,
349 cx: &mut Context<Self>,
350 ) {
351 self.set_active_branch_filter(branch_picker::BranchFilter::Remote, window, cx);
352 }
353
354 fn handle_cycle_branch_filter(
355 &mut self,
356 _: &CycleBranchFilter,
357 window: &mut Window,
358 cx: &mut Context<Self>,
359 ) {
360 if let Some(branch_list) = &self.branch_list {
361 branch_list.update(cx, |list, cx| {
362 list.cycle_branch_filter(window, cx);
363 });
364 }
365 }
366
367 fn handle_drop_stash(
368 &mut self,
369 _: &DropStashItem,
370 window: &mut Window,
371 cx: &mut Context<Self>,
372 ) {
373 if let Some(stash_list) = &self.stash_list {
374 stash_list.update(cx, |list, cx| {
375 list.handle_drop_stash(&DropStashItem, window, cx);
376 });
377 }
378 }
379
380 fn handle_show_stash(
381 &mut self,
382 _: &ShowStashItem,
383 window: &mut Window,
384 cx: &mut Context<Self>,
385 ) {
386 if let Some(stash_list) = &self.stash_list {
387 stash_list.update(cx, |list, cx| {
388 list.handle_show_stash(&ShowStashItem, window, cx);
389 });
390 }
391 }
392}
393
394impl ModalView for GitPicker {}
395impl EventEmitter<DismissEvent> for GitPicker {}
396
397impl Focusable for GitPicker {
398 fn focus_handle(&self, cx: &App) -> FocusHandle {
399 match self.tab {
400 GitPickerTab::Branches => {
401 if let Some(branch_list) = &self.branch_list {
402 return branch_list.focus_handle(cx);
403 }
404 }
405 GitPickerTab::Stashes => {
406 if let Some(stash_list) = &self.stash_list {
407 return stash_list.focus_handle(cx);
408 }
409 }
410 }
411 cx.focus_handle()
412 }
413}
414
415impl Render for GitPicker {
416 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
417 v_flex()
418 .occlude()
419 .w(self.width)
420 .elevation_3(cx)
421 .overflow_hidden()
422 .when(self.popover_style, |el| {
423 el.on_mouse_down_out(cx.listener(|this, _, _, cx| {
424 if this
425 .branch_list
426 .as_ref()
427 .is_some_and(|branch_list| branch_list.read(cx).branch_filter_menu_open(cx))
428 {
429 return;
430 }
431 cx.emit(DismissEvent);
432 }))
433 })
434 .key_context({
435 let mut key_context = KeyContext::new_with_defaults();
436 key_context.add("Pane");
437 key_context.add("GitPicker");
438 match self.tab {
439 GitPickerTab::Branches => key_context.add("GitBranchSelector"),
440 GitPickerTab::Stashes => key_context.add("StashList"),
441 }
442 key_context
443 })
444 .on_mouse_down(MouseButton::Left, |_, _, cx| {
445 cx.stop_propagation();
446 })
447 .on_action(cx.listener(|_, _: &menu::Cancel, _, cx| {
448 cx.emit(DismissEvent);
449 }))
450 .on_action(cx.listener(|this, _: &pane::ActivateNextItem, window, cx| {
451 this.activate_next_tab(window, cx);
452 }))
453 .on_action(
454 cx.listener(|this, _: &pane::ActivatePreviousItem, window, cx| {
455 this.activate_previous_tab(window, cx);
456 }),
457 )
458 .on_action(cx.listener(|this, _: &ActivateBranchesTab, window, cx| {
459 this.tab = GitPickerTab::Branches;
460 this.ensure_active_picker(window, cx);
461 this.focus_active_picker(window, cx);
462 cx.notify();
463 }))
464 .on_action(cx.listener(|this, _: &ActivateStashTab, window, cx| {
465 this.tab = GitPickerTab::Stashes;
466 this.ensure_active_picker(window, cx);
467 this.focus_active_picker(window, cx);
468 cx.notify();
469 }))
470 .on_modifiers_changed(cx.listener(Self::handle_modifiers_changed))
471 .when(self.tab == GitPickerTab::Branches, |el| {
472 el.on_action(cx.listener(Self::handle_delete_branch))
473 .on_action(cx.listener(Self::handle_force_delete_branch))
474 .on_action(cx.listener(Self::handle_show_all_branches))
475 .on_action(cx.listener(Self::handle_show_local_branches))
476 .on_action(cx.listener(Self::handle_show_remote_branches))
477 .on_action(cx.listener(Self::handle_cycle_branch_filter))
478 })
479 .when(self.tab == GitPickerTab::Stashes, |el| {
480 el.on_action(cx.listener(Self::handle_drop_stash))
481 .on_action(cx.listener(Self::handle_show_stash))
482 })
483 .child(self.render_tab_bar(cx))
484 .child(self.render_active_picker(window, cx))
485 }
486}
487
488pub fn open_branches(
489 workspace: &mut Workspace,
490 _: &zed_actions::git::Branch,
491 window: &mut Window,
492 cx: &mut Context<Workspace>,
493) {
494 open_with_tab(workspace, GitPickerTab::Branches, window, cx);
495}
496
497pub fn open_stash(
498 workspace: &mut Workspace,
499 _: &zed_actions::git::ViewStash,
500 window: &mut Window,
501 cx: &mut Context<Workspace>,
502) {
503 open_with_tab(workspace, GitPickerTab::Stashes, window, cx);
504}
505
506fn open_with_tab(
507 workspace: &mut Workspace,
508 tab: GitPickerTab,
509 window: &mut Window,
510 cx: &mut Context<Workspace>,
511) {
512 let workspace_handle = workspace.weak_handle();
513 let repository = workspace.project().read(cx).active_repository(cx);
514
515 workspace.toggle_modal(window, cx, |window, cx| {
516 GitPicker::new(workspace_handle, repository, tab, rems(34.), window, cx)
517 })
518}
519
520pub fn popover(
521 workspace: WeakEntity<Workspace>,
522 repository: Option<Entity<Repository>>,
523 initial_tab: GitPickerTab,
524 width: Rems,
525 window: &mut Window,
526 cx: &mut App,
527) -> Entity<GitPicker> {
528 cx.new(|cx| {
529 let picker =
530 GitPicker::new_internal(workspace, repository, initial_tab, width, true, window, cx);
531 picker.focus_handle(cx).focus(window, cx);
532 picker
533 })
534}
535
536pub fn register(workspace: &mut Workspace) {
537 workspace.register_action(|workspace, _: &zed_actions::git::Branch, window, cx| {
538 open_with_tab(workspace, GitPickerTab::Branches, window, cx);
539 });
540 workspace.register_action(|workspace, _: &zed_actions::git::Switch, window, cx| {
541 open_with_tab(workspace, GitPickerTab::Branches, window, cx);
542 });
543 workspace.register_action(
544 |workspace, _: &zed_actions::git::CheckoutBranch, window, cx| {
545 open_with_tab(workspace, GitPickerTab::Branches, window, cx);
546 },
547 );
548 workspace.register_action(|workspace, _: &zed_actions::git::ViewStash, window, cx| {
549 open_with_tab(workspace, GitPickerTab::Stashes, window, cx);
550 });
551}
552