Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T02:32:16.027Z 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

git_picker.rs

552 lines · 18.2 KB · rust
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
Served at tenant.openagents/omega Member data and write actions are omitted.