Keep all hunks expanded in proposed change editor (#18598)

Also, fix visual bug when pressing escape with a non-empty selection in
a deleted text block.

Release Notes:

- N/A

Co-authored-by: Antonio <antonio@zed.dev>
This commit is contained in:
Max Brunsfeld 2024-10-01 12:58:12 -06:00 committed by GitHub
parent 9b148f3dcc
commit 7dcb0de28c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 259 additions and 190 deletions

View file

@ -3059,7 +3059,7 @@ impl Editor {
} }
pub fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) { pub fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
if self.clear_clicked_diff_hunks(cx) { if self.clear_expanded_diff_hunks(cx) {
cx.notify(); cx.notify();
return; return;
} }

View file

@ -32,6 +32,7 @@ pub(super) struct ExpandedHunks {
pub(crate) hunks: Vec<ExpandedHunk>, pub(crate) hunks: Vec<ExpandedHunk>,
diff_base: HashMap<BufferId, DiffBaseBuffer>, diff_base: HashMap<BufferId, DiffBaseBuffer>,
hunk_update_tasks: HashMap<Option<BufferId>, Task<()>>, hunk_update_tasks: HashMap<Option<BufferId>, Task<()>>,
expand_all: bool,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -72,6 +73,10 @@ impl ExpandedHunks {
} }
impl Editor { impl Editor {
pub fn set_expand_all_diff_hunks(&mut self) {
self.expanded_hunks.expand_all = true;
}
pub(super) fn toggle_hovered_hunk( pub(super) fn toggle_hovered_hunk(
&mut self, &mut self,
hovered_hunk: &HoveredHunk, hovered_hunk: &HoveredHunk,
@ -133,6 +138,10 @@ impl Editor {
hunks_to_toggle: Vec<MultiBufferDiffHunk>, hunks_to_toggle: Vec<MultiBufferDiffHunk>,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
if self.expanded_hunks.expand_all {
return;
}
let previous_toggle_task = self.expanded_hunks.hunk_update_tasks.remove(&None); let previous_toggle_task = self.expanded_hunks.hunk_update_tasks.remove(&None);
let new_toggle_task = cx.spawn(move |editor, mut cx| async move { let new_toggle_task = cx.spawn(move |editor, mut cx| async move {
if let Some(task) = previous_toggle_task { if let Some(task) = previous_toggle_task {
@ -426,62 +435,64 @@ impl Editor {
.child( .child(
h_flex() h_flex()
.gap_1() .gap_1()
.child( .when(!is_branch_buffer, |row| {
IconButton::new("next-hunk", IconName::ArrowDown) row.child(
.shape(IconButtonShape::Square) IconButton::new("next-hunk", IconName::ArrowDown)
.icon_size(IconSize::Small) .shape(IconButtonShape::Square)
.tooltip({ .icon_size(IconSize::Small)
let focus_handle = editor.focus_handle(cx); .tooltip({
move |cx| { let focus_handle = editor.focus_handle(cx);
Tooltip::for_action_in( move |cx| {
"Next Hunk", Tooltip::for_action_in(
&GoToHunk, "Next Hunk",
&focus_handle, &GoToHunk,
cx, &focus_handle,
)
}
})
.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, cx,
); )
}); }
} })
}), .on_click({
) let editor = editor.clone();
.child( let hunk = hunk.clone();
IconButton::new("prev-hunk", IconName::ArrowUp) move |_event, cx| {
.shape(IconButtonShape::Square) editor.update(cx, |editor, cx| {
.icon_size(IconSize::Small) editor.go_to_subsequent_hunk(
.tooltip({ hunk.multi_buffer_range.end,
let focus_handle = editor.focus_handle(cx); cx,
move |cx| { );
Tooltip::for_action_in( });
"Previous Hunk", }
&GoToPrevHunk, }),
&focus_handle, )
cx, .child(
) IconButton::new("prev-hunk", IconName::ArrowUp)
} .shape(IconButtonShape::Square)
}) .icon_size(IconSize::Small)
.on_click({ .tooltip({
let editor = editor.clone(); let focus_handle = editor.focus_handle(cx);
let hunk = hunk.clone(); move |cx| {
move |_event, cx| { Tooltip::for_action_in(
editor.update(cx, |editor, cx| { "Previous Hunk",
editor.go_to_preceding_hunk( &GoToPrevHunk,
hunk.multi_buffer_range.start, &focus_handle,
cx, 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( .child(
IconButton::new("discard", IconName::Undo) IconButton::new("discard", IconName::Undo)
.shape(IconButtonShape::Square) .shape(IconButtonShape::Square)
@ -527,99 +538,115 @@ impl Editor {
} }
}), }),
) )
.when(is_branch_buffer, |this| { .map(|this| {
this.child( if is_branch_buffer {
IconButton::new("apply", IconName::Check) this.child(
.shape(IconButtonShape::Square) IconButton::new("apply", IconName::Check)
.icon_size(IconSize::Small) .shape(IconButtonShape::Square)
.tooltip({ .icon_size(IconSize::Small)
let focus_handle = editor.focus_handle(cx); .tooltip({
move |cx| { let focus_handle =
Tooltip::for_action_in( editor.focus_handle(cx);
"Apply Hunk", move |cx| {
&ApplyDiffHunk, Tooltip::for_action_in(
&focus_handle, "Apply Hunk",
cx, &ApplyDiffHunk,
) &focus_handle,
}
})
.on_click({
let editor = editor.clone();
let hunk = hunk.clone();
move |_event, cx| {
editor.update(cx, |editor, cx| {
editor.apply_changes_in_range(
hunk.multi_buffer_range.clone(),
cx, cx,
); )
}); }
} })
}), .on_click({
) let editor = editor.clone();
}) let hunk = hunk.clone();
.child({ move |_event, cx| {
let focus = editor.focus_handle(cx); editor.update(cx, |editor, cx| {
PopoverMenu::new("hunk-controls-dropdown") editor.apply_changes_in_range(
.trigger( hunk.multi_buffer_range
IconButton::new( .clone(),
"toggle_editor_selections_icon", cx,
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) } else {
.with_handle(hunk_controls_menu_handle) this.child({
.menu(move |cx| { let focus = editor.focus_handle(cx);
let focus = focus.clone(); PopoverMenu::new("hunk-controls-dropdown")
let menu = .trigger(
ContextMenu::build(cx, move |menu, _| { IconButton::new(
menu.context(focus.clone()).action( "toggle_editor_selections_icon",
"Discard All", IconName::EllipsisVertical,
RevertFile.boxed_clone(),
) )
}); .shape(IconButtonShape::Square)
Some(menu) .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",
RevertFile
.boxed_clone(),
)
},
);
Some(menu)
})
}) })
}
}), }),
) )
.child( .when(!is_branch_buffer, |div| {
IconButton::new("collapse", IconName::Close) div.child(
.shape(IconButtonShape::Square) IconButton::new("collapse", IconName::Close)
.icon_size(IconSize::Small) .shape(IconButtonShape::Square)
.tooltip({ .icon_size(IconSize::Small)
let focus_handle = editor.focus_handle(cx); .tooltip({
move |cx| { let focus_handle = editor.focus_handle(cx);
Tooltip::for_action_in( move |cx| {
"Collapse Hunk", Tooltip::for_action_in(
&ToggleHunkDiff, "Collapse Hunk",
&focus_handle, &ToggleHunkDiff,
cx, &focus_handle,
) cx,
} )
}) }
.on_click({ })
let editor = editor.clone(); .on_click({
let hunk = hunk.clone(); let editor = editor.clone();
move |_event, cx| { let hunk = hunk.clone();
editor.update(cx, |editor, cx| { move |_event, cx| {
editor.toggle_hovered_hunk(&hunk, cx); editor.update(cx, |editor, cx| {
}); editor.toggle_hovered_hunk(&hunk, cx);
} });
}), }
), }),
)
}),
) )
.into_any_element() .into_any_element()
} }
@ -694,7 +721,10 @@ impl Editor {
} }
} }
pub(super) fn clear_clicked_diff_hunks(&mut self, cx: &mut ViewContext<'_, Editor>) -> bool { pub(super) fn clear_expanded_diff_hunks(&mut self, cx: &mut ViewContext<'_, Editor>) -> bool {
if self.expanded_hunks.expand_all {
return false;
}
self.expanded_hunks.hunk_update_tasks.clear(); self.expanded_hunks.hunk_update_tasks.clear();
self.clear_row_highlights::<DiffRowHighlight>(); self.clear_row_highlights::<DiffRowHighlight>();
let to_remove = self let to_remove = self
@ -798,33 +828,43 @@ impl Editor {
status, status,
} => { } => {
let hunk_display_range = display_row_range; let hunk_display_range = display_row_range;
if expanded_hunk_display_range.start if expanded_hunk_display_range.start
> hunk_display_range.end > hunk_display_range.end
{ {
recalculated_hunks.next(); recalculated_hunks.next();
continue; if editor.expanded_hunks.expand_all {
} else if expanded_hunk_display_range.end
< hunk_display_range.start
{
break;
} else {
if !expanded_hunk.folded
&& expanded_hunk_display_range == hunk_display_range
&& expanded_hunk.status == hunk_status(buffer_hunk)
&& expanded_hunk.diff_base_byte_range
== buffer_hunk.diff_base_byte_range
{
recalculated_hunks.next();
retain = true;
} else {
hunks_to_reexpand.push(HoveredHunk { hunks_to_reexpand.push(HoveredHunk {
status, status,
multi_buffer_range, multi_buffer_range,
diff_base_byte_range, diff_base_byte_range,
}); });
} }
continue;
}
if expanded_hunk_display_range.end
< hunk_display_range.start
{
break; break;
} }
if !expanded_hunk.folded
&& expanded_hunk_display_range == hunk_display_range
&& expanded_hunk.status == hunk_status(buffer_hunk)
&& expanded_hunk.diff_base_byte_range
== buffer_hunk.diff_base_byte_range
{
recalculated_hunks.next();
retain = true;
} else {
hunks_to_reexpand.push(HoveredHunk {
status,
multi_buffer_range,
diff_base_byte_range,
});
}
break;
} }
} }
} }
@ -836,6 +876,26 @@ impl Editor {
retain retain
}); });
if editor.expanded_hunks.expand_all {
for hunk in recalculated_hunks {
match diff_hunk_to_display(&hunk, &snapshot) {
DisplayDiffHunk::Folded { .. } => {}
DisplayDiffHunk::Unfolded {
diff_base_byte_range,
multi_buffer_range,
status,
..
} => {
hunks_to_reexpand.push(HoveredHunk {
status,
multi_buffer_range,
diff_base_byte_range,
});
}
}
}
}
editor.remove_highlighted_rows::<DiffRowHighlight>(highlights_to_remove, cx); editor.remove_highlighted_rows::<DiffRowHighlight>(highlights_to_remove, cx);
editor.remove_blocks(blocks_to_remove, None, cx); editor.remove_blocks(blocks_to_remove, None, cx);
@ -1000,13 +1060,15 @@ fn editor_with_deleted_text(
editor.scroll_manager.set_forbid_vertical_scroll(true); editor.scroll_manager.set_forbid_vertical_scroll(true);
editor.set_read_only(true); editor.set_read_only(true);
editor.set_show_inline_completions(Some(false), cx); editor.set_show_inline_completions(Some(false), cx);
editor.highlight_rows::<DiffRowHighlight>(
enum DeletedBlockRowHighlight {}
editor.highlight_rows::<DeletedBlockRowHighlight>(
Anchor::min()..Anchor::max(), Anchor::min()..Anchor::max(),
deleted_color, deleted_color,
false, false,
cx, cx,
); );
editor.set_current_line_highlight(Some(CurrentLineHighlight::None)); editor.set_current_line_highlight(Some(CurrentLineHighlight::None)); //
editor editor
._subscriptions ._subscriptions
.extend([cx.on_blur(&editor.focus_handle, |editor, cx| { .extend([cx.on_blur(&editor.focus_handle, |editor, cx| {
@ -1015,37 +1077,41 @@ fn editor_with_deleted_text(
}); });
})]); })]);
let parent_editor_for_reverts = parent_editor.clone();
let original_multi_buffer_range = hunk.multi_buffer_range.clone(); let original_multi_buffer_range = hunk.multi_buffer_range.clone();
let diff_base_range = hunk.diff_base_byte_range.clone(); let diff_base_range = hunk.diff_base_byte_range.clone();
editor editor
.register_action::<RevertSelectedHunks>(move |_, cx| { .register_action::<RevertSelectedHunks>({
parent_editor_for_reverts let parent_editor = parent_editor.clone();
.update(cx, |editor, cx| { move |_, cx| {
let Some((buffer, original_text)) = parent_editor
editor.buffer().update(cx, |buffer, cx| { .update(cx, |editor, cx| {
let (_, buffer, _) = buffer let Some((buffer, original_text)) =
.excerpt_containing(original_multi_buffer_range.start, cx)?; editor.buffer().update(cx, |buffer, cx| {
let original_text = let (_, buffer, _) = buffer.excerpt_containing(
buffer.read(cx).diff_base()?.slice(diff_base_range.clone()); original_multi_buffer_range.start,
Some((buffer, Arc::from(original_text.to_string()))) cx,
}) )?;
else { let original_text =
return; buffer.read(cx).diff_base()?.slice(diff_base_range.clone());
}; Some((buffer, Arc::from(original_text.to_string())))
buffer.update(cx, |buffer, cx| { })
buffer.edit( else {
Some(( return;
original_multi_buffer_range.start.text_anchor };
..original_multi_buffer_range.end.text_anchor, buffer.update(cx, |buffer, cx| {
original_text, buffer.edit(
)), Some((
None, original_multi_buffer_range.start.text_anchor
cx, ..original_multi_buffer_range.end.text_anchor,
) original_text,
}); )),
}) None,
.ok(); cx,
)
});
})
.ok();
}
}) })
.detach(); .detach();
let hunk = hunk.clone(); let hunk = hunk.clone();

View file

@ -63,8 +63,11 @@ impl ProposedChangesEditor {
let (recalculate_diffs_tx, mut recalculate_diffs_rx) = mpsc::unbounded(); let (recalculate_diffs_tx, mut recalculate_diffs_rx) = mpsc::unbounded();
Self { Self {
editor: cx editor: cx.new_view(|cx| {
.new_view(|cx| Editor::for_multibuffer(multibuffer.clone(), project, true, cx)), let mut editor = Editor::for_multibuffer(multibuffer.clone(), project, true, cx);
editor.set_expand_all_diff_hunks();
editor
}),
recalculate_diffs_tx, recalculate_diffs_tx,
_recalculate_diffs_task: cx.spawn(|_, mut cx| async move { _recalculate_diffs_task: cx.spawn(|_, mut cx| async move {
let mut buffers_to_diff = HashSet::default(); let mut buffers_to_diff = HashSet::default();