Skip to repository content848 lines · 27.4 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T02:29:30.937Z 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
unstaged_diff.rs
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