Skip to repository content

tenant.openagents/omega

No repository description is available.

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

unstaged_diff.rs

848 lines · 27.4 KB · rust
1use crate::{
2    diff_multibuffer::DiffMultibuffer,
3    git_panel::{GitPanel, GitPanelAddon, GitStatusEntry},
4};
5use anyhow::{Context as _, Result};
6use buffer_diff::DiffHunkStatus;
7use editor::{
8    DiffHunkDelegate, Editor, EditorEvent, ResolvedDiffHunks, SplittableEditor,
9    actions::{GoToHunk, GoToPreviousHunk},
10};
11use git::{StageAll, StageAndNext};
12use gpui::{
13    Action, AnyElement, App, Context, Entity, EventEmitter, FocusHandle, Focusable, Render,
14    SharedString, Subscription, Task, WeakEntity,
15};
16use language::Capability;
17use project::{
18    Project, ProjectPath,
19    git_store::diff_buffer_list::{DiffBase, DiffBufferList},
20    project_settings::ProjectSettings,
21};
22use settings::Settings;
23use std::{
24    any::{Any, TypeId},
25    ops::Range,
26    sync::Arc,
27};
28use ui::{DiffStat, Divider, Icon, Tooltip, Window, prelude::*};
29use util::ResultExt as _;
30use workspace::{
31    ItemNavHistory, SerializableItem, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView,
32    Workspace,
33    item::{Item, ItemEvent, ItemHandle, SaveOptions, TabContentParams},
34    searchable::SearchableItemHandle,
35};
36
37pub(crate) struct UnstagedDiffDelegate;
38
39impl DiffHunkDelegate for UnstagedDiffDelegate {
40    fn toggle(
41        &self,
42        hunks: Vec<ResolvedDiffHunks>,
43        editor: &mut Editor,
44        window: &mut Window,
45        cx: &mut Context<Editor>,
46    ) {
47        self.stage_or_unstage(true, hunks, editor, window, cx);
48    }
49
50    fn stage_or_unstage(
51        &self,
52        stage: bool,
53        hunks: Vec<ResolvedDiffHunks>,
54        editor: &mut Editor,
55        _window: &mut Window,
56        cx: &mut Context<Editor>,
57    ) {
58        if !stage {
59            return;
60        }
61        let Some(project) = editor.project().cloned() else {
62            return;
63        };
64        for hunks in hunks {
65            let Some(buffer) = hunks.buffer else {
66                continue;
67            };
68            let worktree_ranges = hunks
69                .hunks
70                .into_iter()
71                .map(|hunk| hunk.buffer_range)
72                .collect::<Vec<_>>();
73            if worktree_ranges.is_empty() {
74                continue;
75            }
76            project
77                .update(cx, |project, cx| {
78                    project.stage_hunks(buffer, hunks.diff, worktree_ranges, cx)
79                })
80                .log_err();
81        }
82    }
83
84    fn restore(
85        &self,
86        hunks: Vec<ResolvedDiffHunks>,
87        editor: &mut Editor,
88        window: &mut Window,
89        cx: &mut Context<Editor>,
90    ) {
91        if hunks.is_empty() || editor.read_only(cx) {
92            return;
93        }
94        editor.transact(window, cx, |editor, window, cx| {
95            editor.restore_diff_hunks(hunks, cx);
96            let selections = editor
97                .selections
98                .all::<editor::MultiBufferOffset>(&editor.display_snapshot(cx));
99            editor.change_selections(
100                editor::SelectionEffects::no_scroll(),
101                window,
102                cx,
103                |selections_state| {
104                    selections_state.select(selections);
105                },
106            );
107        });
108    }
109
110    fn render_hunk_controls(
111        &self,
112        row: u32,
113        status: &DiffHunkStatus,
114        hunk_range: Range<editor::Anchor>,
115        is_created_file: bool,
116        line_height: Pixels,
117        editor: &Entity<Editor>,
118        _window: &mut Window,
119        cx: &mut App,
120    ) -> AnyElement {
121        if !ProjectSettings::get_global(cx)
122            .git
123            .show_stage_restore_buttons
124        {
125            return gpui::Empty.into_any_element();
126        }
127        let hunk_range_for_restore = hunk_range.clone();
128        let hunk_range = hunk_range.start..hunk_range.start;
129        h_flex()
130            .h(line_height)
131            .mr_1()
132            .gap_1()
133            .px_0p5()
134            .pb_1()
135            .border_x_1()
136            .border_b_1()
137            .border_color(cx.theme().colors().border_variant)
138            .rounded_b_lg()
139            .bg(cx.theme().colors().editor_background)
140            .block_mouse_except_scroll()
141            .shadow_md()
142            .child(
143                Button::new(("stage", row as u64), "Stage")
144                    .alpha(if status.is_pending() { 0.66 } else { 1.0 })
145                    .tooltip(Tooltip::text("Stage Hunk"))
146                    .on_click({
147                        let editor = editor.clone();
148                        move |_event, window, cx| {
149                            editor.update(cx, |editor, cx| {
150                                editor.stage_or_unstage_diff_hunks(
151                                    true,
152                                    vec![hunk_range.clone()],
153                                    window,
154                                    cx,
155                                );
156                            });
157                        }
158                    }),
159            )
160            .child(
161                Button::new(("restore", row as u64), "Restore")
162                    .tooltip(Tooltip::text("Restore Hunk"))
163                    .on_click({
164                        let editor = editor.clone();
165                        let hunk_range = hunk_range_for_restore;
166                        move |_event, window, cx| {
167                            editor.update(cx, |editor, cx| {
168                                let snapshot = editor.buffer().read(cx).snapshot(cx);
169                                let hunks: Vec<_> = editor
170                                    .diff_hunks_in_ranges(
171                                        std::slice::from_ref(&hunk_range),
172                                        &snapshot,
173                                    )
174                                    .collect();
175                                if !hunks.is_empty() {
176                                    editor.apply_restore(hunks, window, cx);
177                                }
178                            });
179                        }
180                    })
181                    .disabled(is_created_file),
182            )
183            .into_any_element()
184    }
185
186    fn render_hunk_as_staged(&self, _status: &DiffHunkStatus, _cx: &App) -> bool {
187        false
188    }
189}
190
191pub struct UnstagedDiff {
192    diff: Entity<DiffMultibuffer>,
193    project: Entity<Project>,
194    workspace: WeakEntity<Workspace>,
195    _diff_event_subscription: Subscription,
196}
197
198impl UnstagedDiff {
199    pub(crate) fn register(workspace: &mut Workspace, cx: &mut Context<Workspace>) {
200        let _ = workspace;
201        workspace::register_serializable_item::<Self>(cx);
202    }
203
204    pub fn deploy_at(
205        workspace: &mut Workspace,
206        entry: Option<GitStatusEntry>,
207        window: &mut Window,
208        cx: &mut Context<Workspace>,
209    ) {
210        telemetry::event!(
211            "Git Unstaged Diff Opened",
212            source = if entry.is_some() {
213                "Git Panel"
214            } else {
215                "Action"
216            }
217        );
218        let intended_repo = workspace.project().read(cx).active_repository(cx);
219        let existing = workspace.items_of_type::<Self>(cx).next();
220        let unstaged_diff = if let Some(existing) = existing {
221            workspace.activate_item(&existing, true, true, window, cx);
222            existing
223        } else {
224            let workspace_handle = cx.entity();
225            let unstaged_diff =
226                cx.new(|cx| Self::new(workspace.project().clone(), workspace_handle, window, cx));
227            workspace.add_item_to_active_pane(
228                Box::new(unstaged_diff.clone()),
229                None,
230                true,
231                window,
232                cx,
233            );
234            unstaged_diff
235        };
236
237        if let Some(intended) = &intended_repo {
238            let needs_switch = unstaged_diff
239                .read(cx)
240                .diff
241                .read(cx)
242                .repo(cx)
243                .map_or(true, |current| current.entity_id() != intended.entity_id());
244            if needs_switch {
245                unstaged_diff.update(cx, |unstaged_diff, cx| {
246                    unstaged_diff.diff.update(cx, |diff, cx| {
247                        diff.set_repo(Some(intended.clone()), cx);
248                    });
249                });
250            }
251        }
252
253        if let Some(entry) = entry {
254            unstaged_diff.update(cx, |unstaged_diff, cx| {
255                unstaged_diff.move_to_entry(entry, window, cx);
256            });
257        }
258    }
259
260    pub(crate) fn move_to_entry(
261        &mut self,
262        entry: GitStatusEntry,
263        window: &mut Window,
264        cx: &mut Context<Self>,
265    ) {
266        self.diff
267            .update(cx, |diff, cx| diff.move_to_entry(entry, window, cx));
268    }
269
270    pub(crate) fn new(
271        project: Entity<Project>,
272        workspace: Entity<Workspace>,
273        window: &mut Window,
274        cx: &mut Context<Self>,
275    ) -> Self {
276        let branch_diff =
277            cx.new(|cx| DiffBufferList::new(DiffBase::Index, project.clone(), window, cx));
278        let workspace_handle = workspace.downgrade();
279        let diff = cx.new(|cx| {
280            DiffMultibuffer::new(
281                branch_diff,
282                Capability::ReadWrite,
283                "No unstaged changes",
284                move |editor, cx| {
285                    editor.set_diff_hunk_delegate(Some(Arc::new(UnstagedDiffDelegate)), cx);
286                    editor.rhs_editor().update(cx, |rhs_editor, _cx| {
287                        rhs_editor.set_read_only(false);
288                        rhs_editor.register_addon(GitPanelAddon {
289                            workspace: workspace_handle,
290                        });
291                    });
292                },
293                project.clone(),
294                workspace.clone(),
295                window,
296                cx,
297            )
298        });
299        Self::from_diff(diff, project, workspace, cx)
300    }
301
302    pub(crate) fn from_diff(
303        diff: Entity<DiffMultibuffer>,
304        project: Entity<Project>,
305        workspace: Entity<Workspace>,
306        cx: &mut Context<Self>,
307    ) -> Self {
308        let diff_event_subscription = cx.subscribe(&diff, |_, _, event: &EditorEvent, cx| {
309            cx.emit(event.clone())
310        });
311
312        Self {
313            diff,
314            project,
315            workspace: workspace.downgrade(),
316            _diff_event_subscription: diff_event_subscription,
317        }
318    }
319
320    fn button_states(&self, cx: &App) -> ButtonStates {
321        let diff = self.diff.read(cx);
322        let editor = diff.editor().read(cx).rhs_editor().clone();
323        let editor = editor.read(cx);
324        let snapshot = diff.multibuffer().read(cx).snapshot(cx);
325        let prev_next = snapshot.diff_hunks().nth(1).is_some();
326        let (selection, ranges) = diff.selected_ranges(cx);
327        let stage = editor
328            .diff_hunks_in_ranges(&ranges, &snapshot)
329            .next()
330            .is_some();
331        let restore = editor
332            .diff_hunks_in_ranges(&ranges, &snapshot)
333            .any(|h| !h.is_created_file());
334        let mut stage_all = false;
335        self.workspace
336            .read_with(cx, |workspace, cx| {
337                if let Some(git_panel) = workspace.panel::<GitPanel>(cx) {
338                    stage_all = git_panel.read(cx).can_stage_all();
339                }
340            })
341            .ok();
342        let restore_all = snapshot.diff_hunks().any(|h| !h.is_created_file());
343
344        ButtonStates {
345            stage,
346            restore,
347            restore_all,
348            prev_next,
349            selection,
350            stage_all,
351        }
352    }
353
354    fn stage_selected_unstaged_hunks(
355        &mut self,
356        move_to_next: bool,
357        window: &mut Window,
358        cx: &mut Context<Self>,
359    ) {
360        self.diff.update(cx, |diff, cx| {
361            diff.stage_or_unstage_selected_hunks(true, move_to_next, window, cx)
362        });
363    }
364
365    fn restore_selected_unstaged_hunks(
366        &mut self,
367        move_to_next: bool,
368        window: &mut Window,
369        cx: &mut Context<Self>,
370    ) {
371        self.diff.update(cx, |diff, cx| {
372            diff.restore_selected_hunks(move_to_next, window, cx)
373        });
374    }
375}
376
377struct ButtonStates {
378    stage: bool,
379    restore: bool,
380    restore_all: bool,
381    prev_next: bool,
382    selection: bool,
383    stage_all: bool,
384}
385
386impl EventEmitter<EditorEvent> for UnstagedDiff {}
387
388impl Focusable for UnstagedDiff {
389    fn focus_handle(&self, cx: &App) -> FocusHandle {
390        self.diff.read(cx).focus_handle(cx)
391    }
392}
393
394impl Item for UnstagedDiff {
395    type Event = EditorEvent;
396
397    fn tab_icon(&self, _window: &Window, _cx: &App) -> Option<Icon> {
398        Some(Icon::new(IconName::GitBranch).color(Color::Muted))
399    }
400
401    fn to_item_events(event: &EditorEvent, f: &mut dyn FnMut(ItemEvent)) {
402        Editor::to_item_events(event, f)
403    }
404
405    fn deactivated(&mut self, window: &mut Window, cx: &mut Context<Self>) {
406        self.diff
407            .update(cx, |diff, cx| diff.deactivated(window, cx));
408    }
409
410    fn navigate(
411        &mut self,
412        data: Arc<dyn Any + Send>,
413        window: &mut Window,
414        cx: &mut Context<Self>,
415    ) -> bool {
416        self.diff
417            .update(cx, |diff, cx| diff.navigate(data, window, cx))
418    }
419
420    fn tab_tooltip_text(&self, _: &App) -> Option<SharedString> {
421        Some("Unstaged Changes".into())
422    }
423
424    fn tab_content(&self, params: TabContentParams, _window: &Window, _cx: &App) -> AnyElement {
425        Label::new(self.tab_content_text(0, _cx))
426            .color(if params.selected {
427                Color::Default
428            } else {
429                Color::Muted
430            })
431            .into_any_element()
432    }
433
434    fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
435        "Unstaged Changes".into()
436    }
437
438    fn telemetry_event_text(&self) -> Option<&'static str> {
439        Some("Git Unstaged Diff Opened")
440    }
441
442    fn as_searchable(&self, _: &Entity<Self>, cx: &App) -> Option<Box<dyn SearchableItemHandle>> {
443        Some(Box::new(self.diff.read(cx).editor().clone()))
444    }
445
446    fn for_each_project_item(
447        &self,
448        cx: &App,
449        f: &mut dyn FnMut(gpui::EntityId, &dyn project::ProjectItem),
450    ) {
451        self.diff.read(cx).for_each_project_item(cx, f);
452    }
453
454    fn set_nav_history(
455        &mut self,
456        nav_history: ItemNavHistory,
457        _: &mut Window,
458        cx: &mut Context<Self>,
459    ) {
460        self.diff
461            .update(cx, |diff, cx| diff.set_nav_history(nav_history, cx));
462    }
463
464    fn can_split(&self) -> bool {
465        true
466    }
467
468    fn clone_on_split(
469        &self,
470        _workspace_id: Option<workspace::WorkspaceId>,
471        window: &mut Window,
472        cx: &mut Context<Self>,
473    ) -> Task<Option<Entity<Self>>>
474    where
475        Self: Sized,
476    {
477        let Some(workspace) = self.workspace.upgrade() else {
478            return Task::ready(None);
479        };
480        let project = self.project.clone();
481        Task::ready(Some(cx.new(|cx| Self::new(project, workspace, window, cx))))
482    }
483
484    fn is_dirty(&self, cx: &App) -> bool {
485        self.diff.read(cx).is_dirty(cx)
486    }
487
488    fn has_conflict(&self, cx: &App) -> bool {
489        self.diff.read(cx).has_conflict(cx)
490    }
491
492    fn can_save(&self, _cx: &App) -> bool {
493        true
494    }
495
496    fn save(
497        &mut self,
498        options: SaveOptions,
499        project: Entity<Project>,
500        window: &mut Window,
501        cx: &mut Context<Self>,
502    ) -> Task<Result<()>> {
503        self.diff
504            .update(cx, |diff, cx| diff.save(options, project, window, cx))
505    }
506
507    fn save_as(
508        &mut self,
509        _: Entity<Project>,
510        _: ProjectPath,
511        _: &mut Window,
512        _: &mut Context<Self>,
513    ) -> Task<Result<()>> {
514        unreachable!()
515    }
516
517    fn reload(
518        &mut self,
519        project: Entity<Project>,
520        window: &mut Window,
521        cx: &mut Context<Self>,
522    ) -> Task<Result<()>> {
523        self.diff
524            .update(cx, |diff, cx| diff.reload(project, window, cx))
525    }
526
527    fn act_as_type<'a>(
528        &'a self,
529        type_id: TypeId,
530        self_handle: &'a Entity<Self>,
531        cx: &'a App,
532    ) -> Option<gpui::AnyEntity> {
533        if type_id == TypeId::of::<Self>() {
534            Some(self_handle.clone().into())
535        } else if type_id == TypeId::of::<DiffMultibuffer>() {
536            Some(self.diff.clone().into())
537        } else if type_id == TypeId::of::<Editor>() {
538            Some(
539                self.diff
540                    .read(cx)
541                    .editor()
542                    .read(cx)
543                    .rhs_editor()
544                    .clone()
545                    .into(),
546            )
547        } else if type_id == TypeId::of::<SplittableEditor>() {
548            Some(self.diff.read(cx).editor().clone().into())
549        } else if type_id == TypeId::of::<DiffBufferList>() {
550            Some(self.diff.read(cx).branch_diff().clone().into())
551        } else {
552            None
553        }
554    }
555
556    fn added_to_workspace(
557        &mut self,
558        workspace: &mut Workspace,
559        window: &mut Window,
560        cx: &mut Context<Self>,
561    ) {
562        self.diff.update(cx, |diff, cx| {
563            diff.added_to_workspace(workspace, window, cx)
564        });
565    }
566}
567
568impl SerializableItem for UnstagedDiff {
569    fn serialized_item_kind() -> &'static str {
570        "UnstagedDiff"
571    }
572
573    fn cleanup(
574        _: workspace::WorkspaceId,
575        _: Vec<workspace::ItemId>,
576        _: &mut Window,
577        _: &mut App,
578    ) -> Task<Result<()>> {
579        Task::ready(Ok(()))
580    }
581
582    fn deserialize(
583        project: Entity<Project>,
584        workspace: WeakEntity<Workspace>,
585        _: workspace::WorkspaceId,
586        _: workspace::ItemId,
587        window: &mut Window,
588        cx: &mut App,
589    ) -> Task<Result<Entity<Self>>> {
590        window.spawn(cx, async move |cx| {
591            let workspace = workspace.upgrade().context("workspace gone")?;
592            cx.update(|window, cx| Ok(cx.new(|cx| Self::new(project, workspace, window, cx))))?
593        })
594    }
595
596    fn serialize(
597        &mut self,
598        _: &mut Workspace,
599        _: workspace::ItemId,
600        _: bool,
601        _: &mut Window,
602        _: &mut Context<Self>,
603    ) -> Option<Task<Result<()>>> {
604        Some(Task::ready(Ok(())))
605    }
606
607    fn should_serialize(&self, _: &Self::Event) -> bool {
608        false
609    }
610}
611
612impl Render for UnstagedDiff {
613    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
614        self.diff.clone()
615    }
616}
617
618pub struct UnstagedDiffToolbar {
619    unstaged_diff: Option<WeakEntity<UnstagedDiff>>,
620    workspace: WeakEntity<Workspace>,
621}
622
623impl UnstagedDiffToolbar {
624    pub fn new(workspace: &Workspace, _: &mut Context<Self>) -> Self {
625        Self {
626            unstaged_diff: None,
627            workspace: workspace.weak_handle(),
628        }
629    }
630
631    fn unstaged_diff(&self, _: &App) -> Option<Entity<UnstagedDiff>> {
632        self.unstaged_diff.as_ref()?.upgrade()
633    }
634
635    fn dispatch_action(&self, action: &dyn Action, window: &mut Window, cx: &mut Context<Self>) {
636        if let Some(unstaged_diff) = self.unstaged_diff(cx) {
637            unstaged_diff.focus_handle(cx).focus(window, cx);
638        }
639        let action = action.boxed_clone();
640        cx.defer(move |cx| {
641            cx.dispatch_action(action.as_ref());
642        })
643    }
644
645    fn stage_selected_unstaged_hunks(
646        &mut self,
647        move_to_next: bool,
648        window: &mut Window,
649        cx: &mut Context<Self>,
650    ) {
651        let Some(unstaged_diff) = self.unstaged_diff(cx) else {
652            return;
653        };
654        unstaged_diff.update(cx, |unstaged_diff, cx| {
655            unstaged_diff.stage_selected_unstaged_hunks(move_to_next, window, cx);
656        });
657    }
658
659    fn restore_selected_unstaged_hunks(
660        &mut self,
661        move_to_next: bool,
662        window: &mut Window,
663        cx: &mut Context<Self>,
664    ) {
665        let Some(unstaged_diff) = self.unstaged_diff(cx) else {
666            return;
667        };
668        unstaged_diff.update(cx, |unstaged_diff, cx| {
669            unstaged_diff.restore_selected_unstaged_hunks(move_to_next, window, cx);
670        });
671    }
672
673    fn stage_all(&mut self, window: &mut Window, cx: &mut Context<Self>) {
674        self.workspace
675            .update(cx, |workspace, cx| {
676                let Some(panel) = workspace.panel::<GitPanel>(cx) else {
677                    return;
678                };
679                panel.update(cx, |panel, cx| {
680                    panel.stage_all(&Default::default(), window, cx);
681                });
682            })
683            .ok();
684    }
685
686    fn restore_all(&mut self, window: &mut Window, cx: &mut Context<Self>) {
687        let Some(unstaged_diff) = self.unstaged_diff(cx) else {
688            return;
689        };
690        let diff = unstaged_diff.read(cx).diff.read(cx);
691        let editor = diff.editor().read(cx).rhs_editor().clone();
692        let snapshot = diff.multibuffer().read(cx).snapshot(cx);
693        let hunks: Vec<_> = snapshot
694            .diff_hunks()
695            .filter(|h| !h.is_created_file())
696            .collect();
697        if !hunks.is_empty() {
698            editor.update(cx, |editor, cx| {
699                editor.apply_restore(hunks, window, cx);
700            });
701        }
702    }
703}
704
705impl EventEmitter<ToolbarItemEvent> for UnstagedDiffToolbar {}
706
707impl ToolbarItemView for UnstagedDiffToolbar {
708    fn set_active_pane_item(
709        &mut self,
710        active_pane_item: Option<&dyn ItemHandle>,
711        _: &mut Window,
712        cx: &mut Context<Self>,
713    ) -> ToolbarItemLocation {
714        self.unstaged_diff = active_pane_item
715            .and_then(|item| item.act_as::<UnstagedDiff>(cx))
716            .map(|entity| entity.downgrade());
717        if self.unstaged_diff.is_some() {
718            ToolbarItemLocation::PrimaryRight
719        } else {
720            ToolbarItemLocation::Hidden
721        }
722    }
723
724    fn pane_focus_update(
725        &mut self,
726        _pane_focused: bool,
727        _window: &mut Window,
728        _cx: &mut Context<Self>,
729    ) {
730    }
731}
732
733impl Render for UnstagedDiffToolbar {
734    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
735        let Some(unstaged_diff) = self.unstaged_diff(cx) else {
736            return div();
737        };
738        let focus_handle = unstaged_diff.focus_handle(cx);
739        let button_states = unstaged_diff.read(cx).button_states(cx);
740
741        let diff = unstaged_diff.read(cx).diff.read(cx);
742        let (additions, deletions) = diff.calculate_changed_lines(cx);
743        let is_multibuffer_empty = diff.multibuffer().read(cx).is_empty();
744
745        h_flex()
746            .my_neg_1()
747            .py_1()
748            .gap_1p5()
749            .flex_wrap()
750            .justify_between()
751            .when(!is_multibuffer_empty, |this| {
752                this.child(DiffStat::new(
753                    "unstaged-diff-stat",
754                    additions as usize,
755                    deletions as usize,
756                ))
757                .child(Divider::vertical().ml_1())
758            })
759            // n.b. the only reason these arrows are here is because we don't
760            // support "undo" for staging so we need a way to go back.
761            .child(
762                h_group_sm()
763                    .child(
764                        IconButton::new("up", IconName::ArrowUp)
765                            .icon_size(IconSize::Small)
766                            .disabled(!button_states.prev_next)
767                            .tooltip(Tooltip::for_action_title_in(
768                                "Go to Previous Hunk",
769                                &GoToPreviousHunk,
770                                &focus_handle,
771                            ))
772                            .on_click(cx.listener(|this, _, window, cx| {
773                                this.dispatch_action(&GoToPreviousHunk, window, cx)
774                            })),
775                    )
776                    .child(
777                        IconButton::new("down", IconName::ArrowDown)
778                            .icon_size(IconSize::Small)
779                            .disabled(!button_states.prev_next)
780                            .tooltip(Tooltip::for_action_title_in(
781                                "Go to Next Hunk",
782                                &GoToHunk,
783                                &focus_handle,
784                            ))
785                            .on_click(cx.listener(|this, _, window, cx| {
786                                this.dispatch_action(&GoToHunk, window, cx)
787                            })),
788                    ),
789            )
790            .child(Divider::vertical())
791            .child(
792                h_group_sm()
793                    .when(button_states.selection, |this| {
794                        this.child(
795                            Button::new("stage", "Stage")
796                                .disabled(!button_states.stage)
797                                .tooltip(Tooltip::text("Stage Selected Hunks"))
798                                .on_click(cx.listener(|this, _, window, cx| {
799                                    this.stage_selected_unstaged_hunks(false, window, cx)
800                                })),
801                        )
802                    })
803                    .when(!button_states.selection, |this| {
804                        this.child(
805                            Button::new("stage", "Stage")
806                                .disabled(!button_states.stage)
807                                .tooltip(Tooltip::for_action_title_in(
808                                    "Stage and Go to Next Hunk",
809                                    &StageAndNext,
810                                    &focus_handle,
811                                ))
812                                .on_click(cx.listener(|this, _, window, cx| {
813                                    this.stage_selected_unstaged_hunks(true, window, cx)
814                                })),
815                        )
816                    })
817                    .child(
818                        Button::new("restore", "Restore")
819                            .disabled(!button_states.restore)
820                            .tooltip(Tooltip::text("Restore Selected Hunks"))
821                            .on_click(cx.listener(|this, _, window, cx| {
822                                this.restore_selected_unstaged_hunks(false, window, cx)
823                            })),
824                    ),
825            )
826            .child(Divider::vertical())
827            .child(
828                Button::new("stage-all", "Stage All")
829                    .width(rems_from_px(80.))
830                    .disabled(!button_states.stage_all)
831                    .tooltip(Tooltip::for_action_title_in(
832                        "Stage All Changes",
833                        &StageAll,
834                        &focus_handle,
835                    ))
836                    .on_click(cx.listener(|this, _, window, cx| this.stage_all(window, cx))),
837            )
838            .child(Divider::vertical())
839            .child(
840                Button::new("restore-all", "Restore All")
841                    .width(rems_from_px(80.))
842                    .disabled(!button_states.restore_all)
843                    .tooltip(Tooltip::text("Restore All Changes"))
844                    .on_click(cx.listener(|this, _, window, cx| this.restore_all(window, cx))),
845            )
846    }
847}
848
Served at tenant.openagents/omega Member data and write actions are omitted.