From 64708527e7a994401076b367f67eebc5280c13a3 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 26 Nov 2024 10:19:13 -0800 Subject: [PATCH] Revert "Styling for Apply/Discard buttons (#21017)" This reverts commit 884748038e9c99b83b943d4550dd3cf515563071. --- assets/keymaps/default-linux.json | 2 +- assets/keymaps/default-macos.json | 2 +- crates/editor/src/actions.rs | 2 +- crates/editor/src/editor.rs | 64 +-- crates/editor/src/editor_tests.rs | 2 +- crates/editor/src/element.rs | 23 +- crates/editor/src/hunk_diff.rs | 479 +++++++++---------- crates/editor/src/proposed_changes_editor.rs | 118 +---- crates/zed/src/zed.rs | 8 +- 9 files changed, 291 insertions(+), 409 deletions(-) diff --git a/assets/keymaps/default-linux.json b/assets/keymaps/default-linux.json index 9ba416c210..2eedc1c839 100644 --- a/assets/keymaps/default-linux.json +++ b/assets/keymaps/default-linux.json @@ -522,7 +522,7 @@ { "context": "ProposedChangesEditor", "bindings": { - "ctrl-shift-y": "editor::ApplySelectedDiffHunks", + "ctrl-shift-y": "editor::ApplyDiffHunk", "ctrl-alt-a": "editor::ApplyAllDiffHunks" } }, diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json index a4eae2af52..ddbbdd3faf 100644 --- a/assets/keymaps/default-macos.json +++ b/assets/keymaps/default-macos.json @@ -562,7 +562,7 @@ { "context": "ProposedChangesEditor", "bindings": { - "cmd-shift-y": "editor::ApplySelectedDiffHunks", + "cmd-shift-y": "editor::ApplyDiffHunk", "cmd-shift-a": "editor::ApplyAllDiffHunks" } }, diff --git a/crates/editor/src/actions.rs b/crates/editor/src/actions.rs index 719a35a009..5b11b18bc2 100644 --- a/crates/editor/src/actions.rs +++ b/crates/editor/src/actions.rs @@ -209,7 +209,7 @@ gpui::actions!( AddSelectionAbove, AddSelectionBelow, ApplyAllDiffHunks, - ApplySelectedDiffHunks, + ApplyDiffHunk, Backspace, Cancel, CancelLanguageServerWork, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index eeaaeb5c2b..78f0aab5a5 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -99,8 +99,7 @@ use language::{ use language::{point_to_lsp, BufferRow, CharClassifier, Runnable, RunnableRange}; use linked_editing_ranges::refresh_linked_ranges; pub use proposed_changes_editor::{ - ProposedChangeLocation, ProposedChangesEditor, ProposedChangesToolbar, - ProposedChangesToolbarControls, + ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar, }; use similar::{ChangeTag, TextDiff}; use std::iter::Peekable; @@ -161,7 +160,7 @@ use theme::{ }; use ui::{ h_flex, prelude::*, ButtonSize, ButtonStyle, Disclosure, IconButton, IconName, IconSize, - ListItem, Popover, Tooltip, + ListItem, Popover, PopoverMenuHandle, Tooltip, }; use util::{defer, maybe, post_inc, RangeExt, ResultExt, TryFutureExt}; use workspace::item::{ItemHandle, PreviewTabsSettings}; @@ -591,6 +590,7 @@ pub struct Editor { nav_history: Option, context_menu: RwLock>, mouse_context_menu: Option, + hunk_controls_menu_handle: PopoverMenuHandle, completion_tasks: Vec<(CompletionId, Task>)>, signature_help_state: SignatureHelpState, auto_signature_help: Option, @@ -2112,6 +2112,7 @@ impl Editor { nav_history: None, context_menu: RwLock::new(None), mouse_context_menu: None, + hunk_controls_menu_handle: PopoverMenuHandle::default(), completion_tasks: Default::default(), signature_help_state: SignatureHelpState::default(), auto_signature_help: None, @@ -13557,24 +13558,20 @@ fn test_wrap_with_prefix() { ); } -fn is_hunk_selected(hunk: &MultiBufferDiffHunk, selections: &[Selection]) -> bool { - let mut buffer_rows_for_selections = selections.iter().map(|selection| { - let start = MultiBufferRow(selection.start.row); - let end = MultiBufferRow(selection.end.row); - start..end - }); - - buffer_rows_for_selections.any(|range| does_selection_touch_hunk(&range, hunk)) -} - fn hunks_for_selections( multi_buffer_snapshot: &MultiBufferSnapshot, selections: &[Selection], ) -> Vec { let buffer_rows_for_selections = selections.iter().map(|selection| { - let start = MultiBufferRow(selection.start.to_point(multi_buffer_snapshot).row); - let end = MultiBufferRow(selection.end.to_point(multi_buffer_snapshot).row); - start..end + let head = selection.head(); + let tail = selection.tail(); + let start = MultiBufferRow(tail.to_point(multi_buffer_snapshot).row); + let end = MultiBufferRow(head.to_point(multi_buffer_snapshot).row); + if start > end { + end..start + } else { + start..end + } }); hunks_for_rows(buffer_rows_for_selections, multi_buffer_snapshot) @@ -13591,8 +13588,19 @@ pub fn hunks_for_rows( let query_rows = selected_multi_buffer_rows.start..selected_multi_buffer_rows.end.next_row(); for hunk in multi_buffer_snapshot.git_diff_hunks_in_range(query_rows.clone()) { - let related_to_selection = - does_selection_touch_hunk(&selected_multi_buffer_rows, &hunk); + // Deleted hunk is an empty row range, no caret can be placed there and Zed allows to revert it + // when the caret is just above or just below the deleted hunk. + let allow_adjacent = hunk_status(&hunk) == DiffHunkStatus::Removed; + let related_to_selection = if allow_adjacent { + hunk.row_range.overlaps(&query_rows) + || hunk.row_range.start == query_rows.end + || hunk.row_range.end == query_rows.start + } else { + // `selected_multi_buffer_rows` are inclusive (e.g. [2..2] means 2nd row is selected) + // `hunk.row_range` is exclusive (e.g. [2..3] means 2nd row is selected) + hunk.row_range.overlaps(&selected_multi_buffer_rows) + || selected_multi_buffer_rows.end == hunk.row_range.start + }; if related_to_selection { if !processed_buffer_rows .entry(hunk.buffer_id) @@ -13609,26 +13617,6 @@ pub fn hunks_for_rows( hunks } -fn does_selection_touch_hunk( - selected_multi_buffer_rows: &Range, - hunk: &MultiBufferDiffHunk, -) -> bool { - let query_rows = selected_multi_buffer_rows.start..selected_multi_buffer_rows.end.next_row(); - // Deleted hunk is an empty row range, no caret can be placed there and Zed allows to revert it - // when the caret is just above or just below the deleted hunk. - let allow_adjacent = hunk_status(hunk) == DiffHunkStatus::Removed; - if allow_adjacent { - hunk.row_range.overlaps(&query_rows) - || hunk.row_range.start == query_rows.end - || hunk.row_range.end == query_rows.start - } else { - // `selected_multi_buffer_rows` are inclusive (e.g. [2..2] means 2nd row is selected) - // `hunk.row_range` is exclusive (e.g. [2..3] means 2nd row is selected) - hunk.row_range.overlaps(selected_multi_buffer_rows) - || selected_multi_buffer_rows.end == hunk.row_range.start - } -} - pub trait CollaborationHub { fn collaborators<'a>(&self, cx: &'a AppContext) -> &'a HashMap; fn user_participant_indices<'a>( diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 397d5e46d4..669134ef10 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -12552,7 +12552,7 @@ async fn test_edits_around_expanded_insertion_hunks( executor.run_until_parked(); cx.assert_diff_hunks( r#" - - use some::mod1; + use some::mod1; - use some::mod2; - - const A: u32 = 42; diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 19c1f3bf39..7f4bc3fb77 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -2509,7 +2509,6 @@ impl EditorElement { element, available_space: size(AvailableSpace::MinContent, element_size.height.into()), style: BlockStyle::Fixed, - is_zero_height: block.height() == 0, }); } for (row, block) in non_fixed_blocks { @@ -2556,7 +2555,6 @@ impl EditorElement { element, available_space: size(width.into(), element_size.height.into()), style, - is_zero_height: block.height() == 0, }); } @@ -2604,7 +2602,6 @@ impl EditorElement { element, available_space: size(width, element_size.height.into()), style, - is_zero_height: block.height() == 0, }); } } @@ -3950,23 +3947,8 @@ impl EditorElement { } fn paint_blocks(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) { - cx.paint_layer(layout.text_hitbox.bounds, |cx| { - layout.blocks.retain_mut(|block| { - if !block.is_zero_height { - block.element.paint(cx); - } - - block.is_zero_height - }); - }); - - // Paint all the zero-height blocks in a higher layer (if there were any remaining to paint). - if !layout.blocks.is_empty() { - cx.paint_layer(layout.text_hitbox.bounds, |cx| { - for mut block in layout.blocks.drain(..) { - block.element.paint(cx); - } - }); + for mut block in layout.blocks.drain(..) { + block.element.paint(cx); } } @@ -6029,7 +6011,6 @@ struct BlockLayout { element: AnyElement, available_space: Size, style: BlockStyle, - is_zero_height: bool, } fn layout_line( diff --git a/crates/editor/src/hunk_diff.rs b/crates/editor/src/hunk_diff.rs index 5c6d5ff7a3..27bb8ac557 100644 --- a/crates/editor/src/hunk_diff.rs +++ b/crates/editor/src/hunk_diff.rs @@ -1,8 +1,6 @@ use collections::{hash_map, HashMap, HashSet}; use git::diff::DiffHunkStatus; -use gpui::{ - AppContext, ClickEvent, CursorStyle, FocusableView, Hsla, Model, MouseButton, Task, View, -}; +use gpui::{Action, AnchorCorner, AppContext, CursorStyle, Hsla, Model, MouseButton, Task, View}; use language::{Buffer, BufferId, Point}; use multi_buffer::{ Anchor, AnchorRangeExt, ExcerptRange, MultiBuffer, MultiBufferDiffHunk, MultiBufferRow, @@ -11,18 +9,17 @@ use multi_buffer::{ use std::{ops::Range, sync::Arc}; use text::OffsetRangeExt; use ui::{ - prelude::*, ActiveTheme, IconButtonShape, InteractiveElement, IntoElement, KeyBinding, - ParentElement, Styled, TintColor, Tooltip, ViewContext, VisualContext, + prelude::*, ActiveTheme, ContextMenu, IconButtonShape, InteractiveElement, IntoElement, + ParentElement, PopoverMenu, Styled, Tooltip, ViewContext, VisualContext, }; use util::RangeExt; use workspace::Item; use crate::{ - editor_settings::CurrentLineHighlight, hunk_status, hunks_for_selections, is_hunk_selected, - ApplyAllDiffHunks, ApplySelectedDiffHunks, BlockPlacement, BlockProperties, BlockStyle, - CustomBlockId, DiffRowHighlight, DisplayRow, DisplaySnapshot, Editor, EditorElement, - ExpandAllHunkDiffs, GoToHunk, GoToPrevHunk, RevertSelectedHunks, ToDisplayPoint, - ToggleHunkDiff, + editor_settings::CurrentLineHighlight, hunk_status, hunks_for_selections, ApplyAllDiffHunks, + ApplyDiffHunk, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, DiffRowHighlight, + DisplayRow, DisplaySnapshot, Editor, EditorElement, ExpandAllHunkDiffs, GoToHunk, GoToPrevHunk, + RevertFile, RevertSelectedHunks, ToDisplayPoint, ToggleHunkDiff, }; #[derive(Debug, Clone)] @@ -60,6 +57,7 @@ pub enum DisplayDiffHunk { Folded { display_row: DisplayRow, }, + Unfolded { diff_base_byte_range: Range, display_row_range: Range, @@ -373,35 +371,26 @@ impl Editor { pub(crate) fn apply_selected_diff_hunks( &mut self, - _: &ApplySelectedDiffHunks, + _: &ApplyDiffHunk, cx: &mut ViewContext, ) { let snapshot = self.buffer.read(cx).snapshot(cx); let hunks = hunks_for_selections(&snapshot, &self.selections.disjoint_anchors()); - + let mut ranges_by_buffer = HashMap::default(); self.transact(cx, |editor, cx| { - if hunks.is_empty() { - // If there are no selected hunks, e.g. because we're using the keybinding with nothing selected, apply the first hunk. - if let Some(first_hunk) = editor.expanded_hunks.hunks.first() { - editor.apply_diff_hunks_in_range(first_hunk.hunk_range.clone(), cx); + for hunk in hunks { + if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) { + ranges_by_buffer + .entry(buffer.clone()) + .or_insert_with(Vec::new) + .push(hunk.buffer_range.to_offset(buffer.read(cx))); } - } else { - let mut ranges_by_buffer = HashMap::default(); + } - for hunk in hunks { - if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) { - ranges_by_buffer - .entry(buffer.clone()) - .or_insert_with(Vec::new) - .push(hunk.buffer_range.to_offset(buffer.read(cx))); - } - } - - for (buffer, ranges) in ranges_by_buffer { - buffer.update(cx, |buffer, cx| { - buffer.merge_into_base(ranges, cx); - }); - } + for (buffer, ranges) in ranges_by_buffer { + buffer.update(cx, |buffer, cx| { + buffer.merge_into_base(ranges, cx); + }); } }); @@ -423,238 +412,246 @@ impl Editor { buffer.read(cx).diff_base_buffer().is_some() }); + let border_color = cx.theme().colors().border_variant; + let bg_color = cx.theme().colors().editor_background; + let gutter_color = match hunk.status { + DiffHunkStatus::Added => cx.theme().status().created, + DiffHunkStatus::Modified => cx.theme().status().modified, + DiffHunkStatus::Removed => cx.theme().status().deleted, + }; + BlockProperties { placement: BlockPlacement::Above(hunk.multi_buffer_range.start), - height: 0, + height: 1, style: BlockStyle::Sticky, - priority: 1, + priority: 0, render: Arc::new({ let editor = cx.view().clone(); let hunk = hunk.clone(); move |cx| { - let is_hunk_selected = editor.update(&mut **cx, |editor, cx| { - let snapshot = editor.buffer.read(cx).snapshot(cx); - let selections = &editor.selections.all::(cx); - - if editor.focus_handle(cx).is_focused(cx) && !selections.is_empty() { - if let Some(hunk) = to_diff_hunk(&hunk, &snapshot) { - is_hunk_selected(&hunk, selections) - } else { - false - } - } else { - // If we have no cursor, or aren't focused, then default to the first hunk - // because that's what the keyboard shortcuts do. - editor - .expanded_hunks - .hunks - .first() - .map(|first_hunk| first_hunk.hunk_range == hunk.multi_buffer_range) - .unwrap_or(false) - } - }); - - let focus_handle = editor.focus_handle(cx); - - let handle_discard_click = { - let editor = editor.clone(); - let hunk = hunk.clone(); - move |_event: &ClickEvent, cx: &mut WindowContext| { - let multi_buffer = editor.read(cx).buffer().clone(); - let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx); - let mut revert_changes = HashMap::default(); - if let Some(hunk) = - crate::hunk_diff::to_diff_hunk(&hunk, &multi_buffer_snapshot) - { - Editor::prepare_revert_change( - &mut revert_changes, - &multi_buffer, - &hunk, - cx, - ); - } - if !revert_changes.is_empty() { - editor.update(cx, |editor, cx| editor.revert(revert_changes, cx)); - } - } - }; - - let handle_apply_click = { - let editor = editor.clone(); - let hunk = hunk.clone(); - move |_event: &ClickEvent, cx: &mut WindowContext| { - editor.update(cx, |editor, cx| { - editor - .apply_diff_hunks_in_range(hunk.multi_buffer_range.clone(), cx); - }); - } - }; - - let discard_key_binding = - KeyBinding::for_action_in(&RevertSelectedHunks, &focus_handle, cx); - - let discard_tooltip = { - let focus_handle = editor.focus_handle(cx); - move |cx: &mut WindowContext| { - Tooltip::for_action_in( - "Discard Hunk", - &RevertSelectedHunks, - &focus_handle, - cx, - ) - } - }; + let hunk_controls_menu_handle = + editor.read(cx).hunk_controls_menu_handle.clone(); h_flex() .id(cx.block_id) - .pr_5() + .block_mouse_down() + .h(cx.line_height()) .w_full() - .justify_end() + .border_t_1() + .border_color(border_color) + .bg(bg_color) + .child( + div() + .id("gutter-strip") + .w(EditorElement::diff_hunk_strip_width(cx.line_height())) + .h_full() + .bg(gutter_color) + .cursor(CursorStyle::PointingHand) + .on_click({ + let editor = editor.clone(); + let hunk = hunk.clone(); + move |_event, cx| { + editor.update(cx, |editor, cx| { + editor.toggle_hovered_hunk(&hunk, cx); + }); + } + }), + ) .child( h_flex() - .h(cx.line_height()) - .gap_1() - .px_1() - .pb_1() - .border_x_1() - .border_b_1() - .border_color(cx.theme().colors().border_variant) - .rounded_b_lg() - .bg(cx.theme().colors().editor_background) - .shadow(smallvec::smallvec![gpui::BoxShadow { - color: gpui::hsla(0.0, 0.0, 0.0, 0.1), - blur_radius: px(1.0), - spread_radius: px(1.0), - offset: gpui::point(px(0.), px(1.0)), - }]) - .when(!is_branch_buffer, |row| { - row.child( - IconButton::new("next-hunk", IconName::ArrowDown) - .shape(IconButtonShape::Square) - .icon_size(IconSize::Small) - .tooltip({ - let focus_handle = editor.focus_handle(cx); - move |cx| { - Tooltip::for_action_in( - "Next Hunk", - &GoToHunk, - &focus_handle.clone(), - cx, - ) - } - }) - .on_click({ - let editor = editor.clone(); - let hunk = hunk.clone(); - move |_event, cx| { - editor.update(cx, |editor, cx| { - editor.go_to_subsequent_hunk( - hunk.multi_buffer_range.end, - cx, - ); - }); - } - }), - ) - .child( - IconButton::new("prev-hunk", IconName::ArrowUp) - .shape(IconButtonShape::Square) - .icon_size(IconSize::Small) - .tooltip({ - let focus_handle = editor.focus_handle(cx); - move |cx| { - Tooltip::for_action_in( - "Previous Hunk", - &GoToPrevHunk, - &focus_handle, - cx, - ) - } - }) - .on_click({ - let editor = editor.clone(); - let hunk = hunk.clone(); - move |_event, cx| { - editor.update(cx, |editor, cx| { - editor.go_to_preceding_hunk( - hunk.multi_buffer_range.start, - cx, - ); - }); - } - }), - ) - }) - .child(if is_branch_buffer { - if is_hunk_selected { - Button::new("discard", "Discard") - .style(ButtonStyle::Tinted(TintColor::Negative)) - .label_size(LabelSize::Small) - .key_binding(discard_key_binding) - .on_click(handle_discard_click.clone()) - .into_any_element() - } else { - IconButton::new("discard", IconName::Close) - .style(ButtonStyle::Tinted(TintColor::Negative)) - .shape(IconButtonShape::Square) - .icon_size(IconSize::XSmall) - .tooltip(discard_tooltip.clone()) - .on_click(handle_discard_click.clone()) - .into_any_element() - } - } else { - if is_hunk_selected { - Button::new("undo", "Undo") - .style(ButtonStyle::Tinted(TintColor::Negative)) - .label_size(LabelSize::Small) - .key_binding(discard_key_binding) - .on_click(handle_discard_click.clone()) - .into_any_element() - } else { - IconButton::new("undo", IconName::Undo) - .shape(IconButtonShape::Square) - .icon_size(IconSize::Small) - .tooltip(discard_tooltip.clone()) - .on_click(handle_discard_click.clone()) - .into_any_element() - } - }) - .when(is_branch_buffer, |this| { - this.child({ - let button = Button::new("apply", "Apply") - .style(ButtonStyle::Tinted(TintColor::Positive)) - .label_size(LabelSize::Small) - .key_binding(KeyBinding::for_action_in( - &ApplySelectedDiffHunks, - &focus_handle, - cx, - )) - .on_click(handle_apply_click.clone()) - .into_any_element(); - if is_hunk_selected { - button - } else { - IconButton::new("apply", IconName::Check) - .style(ButtonStyle::Tinted(TintColor::Positive)) + .px_6() + .size_full() + .justify_end() + .child( + h_flex() + .gap_1() + .when(!is_branch_buffer, |row| { + row.child( + IconButton::new("next-hunk", IconName::ArrowDown) + .shape(IconButtonShape::Square) + .icon_size(IconSize::Small) + .tooltip({ + let focus_handle = editor.focus_handle(cx); + move |cx| { + Tooltip::for_action_in( + "Next Hunk", + &GoToHunk, + &focus_handle, + cx, + ) + } + }) + .on_click({ + let editor = editor.clone(); + let hunk = hunk.clone(); + move |_event, cx| { + editor.update(cx, |editor, cx| { + editor.go_to_subsequent_hunk( + hunk.multi_buffer_range.end, + cx, + ); + }); + } + }), + ) + .child( + IconButton::new("prev-hunk", IconName::ArrowUp) + .shape(IconButtonShape::Square) + .icon_size(IconSize::Small) + .tooltip({ + let focus_handle = editor.focus_handle(cx); + move |cx| { + Tooltip::for_action_in( + "Previous Hunk", + &GoToPrevHunk, + &focus_handle, + cx, + ) + } + }) + .on_click({ + let editor = editor.clone(); + let hunk = hunk.clone(); + move |_event, cx| { + editor.update(cx, |editor, cx| { + editor.go_to_preceding_hunk( + hunk.multi_buffer_range.start, + cx, + ); + }); + } + }), + ) + }) + .child( + IconButton::new("discard", IconName::Undo) .shape(IconButtonShape::Square) - .icon_size(IconSize::XSmall) + .icon_size(IconSize::Small) .tooltip({ let focus_handle = editor.focus_handle(cx); move |cx| { Tooltip::for_action_in( - "Apply Hunk", - &ApplySelectedDiffHunks, + "Discard Hunk", + &RevertSelectedHunks, &focus_handle, cx, ) } }) - .on_click(handle_apply_click.clone()) - .into_any_element() - } - }) - }) + .on_click({ + let editor = editor.clone(); + let hunk = hunk.clone(); + move |_event, cx| { + let multi_buffer = + editor.read(cx).buffer().clone(); + let multi_buffer_snapshot = + multi_buffer.read(cx).snapshot(cx); + let mut revert_changes = HashMap::default(); + if let Some(hunk) = + crate::hunk_diff::to_diff_hunk( + &hunk, + &multi_buffer_snapshot, + ) + { + Editor::prepare_revert_change( + &mut revert_changes, + &multi_buffer, + &hunk, + cx, + ); + } + if !revert_changes.is_empty() { + editor.update(cx, |editor, cx| { + editor.revert(revert_changes, cx) + }); + } + } + }), + ) + .map(|this| { + if is_branch_buffer { + this.child( + IconButton::new("apply", IconName::Check) + .shape(IconButtonShape::Square) + .icon_size(IconSize::Small) + .tooltip({ + let focus_handle = + editor.focus_handle(cx); + move |cx| { + Tooltip::for_action_in( + "Apply Hunk", + &ApplyDiffHunk, + &focus_handle, + cx, + ) + } + }) + .on_click({ + let editor = editor.clone(); + let hunk = hunk.clone(); + move |_event, cx| { + editor.update(cx, |editor, cx| { + editor + .apply_diff_hunks_in_range( + hunk.multi_buffer_range + .clone(), + cx, + ); + }); + } + }), + ) + } else { + this.child({ + let focus = editor.focus_handle(cx); + PopoverMenu::new("hunk-controls-dropdown") + .trigger( + IconButton::new( + "toggle_editor_selections_icon", + IconName::EllipsisVertical, + ) + .shape(IconButtonShape::Square) + .icon_size(IconSize::Small) + .style(ButtonStyle::Subtle) + .selected( + hunk_controls_menu_handle + .is_deployed(), + ) + .when( + !hunk_controls_menu_handle + .is_deployed(), + |this| { + this.tooltip(|cx| { + Tooltip::text( + "Hunk Controls", + cx, + ) + }) + }, + ), + ) + .anchor(AnchorCorner::TopRight) + .with_handle(hunk_controls_menu_handle) + .menu(move |cx| { + let focus = focus.clone(); + let menu = ContextMenu::build( + cx, + move |menu, _| { + menu.context(focus.clone()) + .action( + "Discard All Hunks", + RevertFile + .boxed_clone(), + ) + }, + ); + Some(menu) + }) + }) + } + }), + ) .when(!is_branch_buffer, |div| { div.child( IconButton::new("collapse", IconName::Close) @@ -710,7 +707,7 @@ impl Editor { placement: BlockPlacement::Above(hunk.multi_buffer_range.start), height, style: BlockStyle::Flex, - priority: 1, + priority: 0, render: Arc::new(move |cx| { let width = EditorElement::diff_hunk_strip_width(cx.line_height()); let gutter_dimensions = editor.read(cx.context).gutter_dimensions; diff --git a/crates/editor/src/proposed_changes_editor.rs b/crates/editor/src/proposed_changes_editor.rs index 3a9509eb39..ac97fe18da 100644 --- a/crates/editor/src/proposed_changes_editor.rs +++ b/crates/editor/src/proposed_changes_editor.rs @@ -5,11 +5,10 @@ use gpui::{AppContext, EventEmitter, FocusableView, Model, Render, Subscription, use language::{Buffer, BufferEvent, Capability}; use multi_buffer::{ExcerptRange, MultiBuffer}; use project::Project; -use settings::Settings; use smol::stream::StreamExt; use std::{any::TypeId, ops::Range, rc::Rc, time::Duration}; use text::ToOffset; -use ui::{prelude::*, KeyBinding}; +use ui::{prelude::*, ButtonLike, KeyBinding}; use workspace::{ searchable::SearchableItemHandle, Item, ItemHandle as _, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, @@ -35,11 +34,7 @@ struct BufferEntry { _subscription: Subscription, } -pub struct ProposedChangesToolbarControls { - current_editor: Option>, -} - -pub struct ProposedChangesToolbar { +pub struct ProposedChangesEditorToolbar { current_editor: Option>, } @@ -233,10 +228,6 @@ impl ProposedChangesEditor { _ => (), } } - - fn all_changes_accepted(&self) -> bool { - false // In the future, we plan to compute this based on the current state of patches. - } } impl Render for ProposedChangesEditor { @@ -260,11 +251,7 @@ impl Item for ProposedChangesEditor { type Event = EditorEvent; fn tab_icon(&self, _cx: &ui::WindowContext) -> Option { - if self.all_changes_accepted() { - Some(Icon::new(IconName::Check).color(Color::Success)) - } else { - Some(Icon::new(IconName::ZedAssistant)) - } + Some(Icon::new(IconName::Diff)) } fn tab_content_text(&self, _cx: &WindowContext) -> Option { @@ -330,7 +317,7 @@ impl Item for ProposedChangesEditor { } } -impl ProposedChangesToolbarControls { +impl ProposedChangesEditorToolbar { pub fn new() -> Self { Self { current_editor: None, @@ -346,97 +333,28 @@ impl ProposedChangesToolbarControls { } } -impl Render for ProposedChangesToolbarControls { +impl Render for ProposedChangesEditorToolbar { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - if let Some(editor) = &self.current_editor { - let focus_handle = editor.focus_handle(cx); - let action = &ApplyAllDiffHunks; - let keybinding = KeyBinding::for_action_in(action, &focus_handle, cx); + let button_like = ButtonLike::new("apply-changes").child(Label::new("Apply All")); - let editor = editor.read(cx); + match &self.current_editor { + Some(editor) => { + let focus_handle = editor.focus_handle(cx); + let keybinding = KeyBinding::for_action_in(&ApplyAllDiffHunks, &focus_handle, cx) + .map(|binding| binding.into_any_element()); - let apply_all_button = if editor.all_changes_accepted() { - None - } else { - Some( - Button::new("apply-changes", "Apply All") - .style(ButtonStyle::Filled) - .key_binding(keybinding) - .on_click(move |_event, cx| focus_handle.dispatch_action(action, cx)), - ) - }; - - h_flex() - .gap_1() - .children([apply_all_button].into_iter().flatten()) - .into_any_element() - } else { - gpui::Empty.into_any_element() + button_like.children(keybinding).on_click({ + move |_event, cx| focus_handle.dispatch_action(&ApplyAllDiffHunks, cx) + }) + } + None => button_like.disabled(true), } } } -impl EventEmitter for ProposedChangesToolbarControls {} +impl EventEmitter for ProposedChangesEditorToolbar {} -impl ToolbarItemView for ProposedChangesToolbarControls { - fn set_active_pane_item( - &mut self, - active_pane_item: Option<&dyn workspace::ItemHandle>, - _cx: &mut ViewContext, - ) -> workspace::ToolbarItemLocation { - self.current_editor = - active_pane_item.and_then(|item| item.downcast::()); - self.get_toolbar_item_location() - } -} - -impl ProposedChangesToolbar { - pub fn new() -> Self { - Self { - current_editor: None, - } - } - - fn get_toolbar_item_location(&self) -> ToolbarItemLocation { - if self.current_editor.is_some() { - ToolbarItemLocation::PrimaryLeft - } else { - ToolbarItemLocation::Hidden - } - } -} - -impl Render for ProposedChangesToolbar { - fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - if let Some(editor) = &self.current_editor { - let editor = editor.read(cx); - let all_changes_accepted = editor.all_changes_accepted(); - let icon = if all_changes_accepted { - Icon::new(IconName::Check).color(Color::Success) - } else { - Icon::new(IconName::ZedAssistant) - }; - - h_flex() - .gap_2p5() - .font(theme::ThemeSettings::get_global(cx).buffer_font.clone()) - .child(icon.size(IconSize::Small)) - .child( - Label::new(editor.title.clone()) - .color(Color::Muted) - .single_line() - .strikethrough(all_changes_accepted), - ) - .into_any_element() - } else { - gpui::Empty.into_any_element() - } - } -} - -impl EventEmitter for ProposedChangesToolbar {} - -impl ToolbarItemView for ProposedChangesToolbar { +impl ToolbarItemView for ProposedChangesEditorToolbar { fn set_active_pane_item( &mut self, active_pane_item: Option<&dyn workspace::ItemHandle>, diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index f5c0259b1a..4e3d05d2fb 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -17,8 +17,8 @@ use breadcrumbs::Breadcrumbs; use client::{zed_urls, ZED_URL_SCHEME}; use collections::VecDeque; use command_palette_hooks::CommandPaletteFilter; +use editor::ProposedChangesEditorToolbar; use editor::{scroll::Autoscroll, Editor, MultiBuffer}; -use editor::{ProposedChangesToolbar, ProposedChangesToolbarControls}; use feature_flags::FeatureFlagAppExt; use futures::{channel::mpsc, select_biased, StreamExt}; use gpui::{ @@ -644,10 +644,8 @@ fn initialize_pane(workspace: &Workspace, pane: &View, cx: &mut ViewContex let buffer_search_bar = cx.new_view(search::BufferSearchBar::new); toolbar.add_item(buffer_search_bar.clone(), cx); - let proposed_changes_bar = cx.new_view(|_| ProposedChangesToolbar::new()); - toolbar.add_item(proposed_changes_bar, cx); - let proposed_changes_controls = cx.new_view(|_| ProposedChangesToolbarControls::new()); - toolbar.add_item(proposed_changes_controls, cx); + let proposed_change_bar = cx.new_view(|_| ProposedChangesEditorToolbar::new()); + toolbar.add_item(proposed_change_bar, cx); let quick_action_bar = cx.new_view(|cx| QuickActionBar::new(buffer_search_bar, workspace, cx)); toolbar.add_item(quick_action_bar, cx);