Allow reviewing of agent changes without Git (#27668)

Release Notes:

- N/A
This commit is contained in:
Antonio Scandurra 2025-03-28 19:58:53 +01:00 committed by GitHub
parent 8a307e7b89
commit 94ed0b7767
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 2271 additions and 1095 deletions

View file

@ -185,8 +185,8 @@ use theme::{
ThemeColors, ThemeSettings,
};
use ui::{
h_flex, prelude::*, ButtonSize, ButtonStyle, Disclosure, IconButton, IconName, IconSize, Key,
Tooltip,
h_flex, prelude::*, ButtonSize, ButtonStyle, Disclosure, IconButton, IconButtonShape, IconName,
IconSize, Key, Tooltip,
};
use util::{maybe, post_inc, RangeExt, ResultExt, TryFutureExt};
use workspace::{
@ -220,6 +220,18 @@ pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
pub(crate) const MIN_LINE_NUMBER_DIGITS: u32 = 4;
pub type RenderDiffHunkControlsFn = Arc<
dyn Fn(
u32,
&DiffHunkStatus,
Range<Anchor>,
bool,
Pixels,
&Entity<Editor>,
&mut App,
) -> AnyElement,
>;
const COLUMNAR_SELECTION_MODIFIERS: Modifiers = Modifiers {
alt: true,
shift: true,
@ -752,6 +764,7 @@ pub struct Editor {
show_git_blame_inline_delay_task: Option<Task<()>>,
git_blame_inline_tooltip: Option<WeakEntity<crate::commit_tooltip::CommitTooltip>>,
git_blame_inline_enabled: bool,
render_diff_hunk_controls: RenderDiffHunkControlsFn,
serialize_dirty_buffers: bool,
show_selection_menu: Option<bool>,
blame: Option<Entity<GitBlame>>,
@ -1527,6 +1540,7 @@ impl Editor {
show_git_blame_inline_delay_task: None,
git_blame_inline_tooltip: None,
git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
serialize_dirty_buffers: ProjectSettings::get_global(cx)
.session
.restore_unsaved_buffers,
@ -8399,7 +8413,7 @@ impl Editor {
self.restore_hunks_in_ranges(selections, window, cx);
}
fn restore_hunks_in_ranges(
pub fn restore_hunks_in_ranges(
&mut self,
ranges: Vec<Range<Point>>,
window: &mut Window,
@ -12623,7 +12637,7 @@ impl Editor {
);
}
fn go_to_hunk_before_or_after_position(
pub fn go_to_hunk_before_or_after_position(
&mut self,
snapshot: &EditorSnapshot,
position: Point,
@ -14786,6 +14800,15 @@ impl Editor {
self.stage_or_unstage_diff_hunks(stage, ranges, cx);
}
pub fn set_render_diff_hunk_controls(
&mut self,
render_diff_hunk_controls: RenderDiffHunkControlsFn,
cx: &mut Context<Self>,
) {
self.render_diff_hunk_controls = render_diff_hunk_controls;
cx.notify();
}
pub fn stage_and_next(
&mut self,
_: &::git::StageAndNext,
@ -19913,3 +19936,187 @@ impl From<Background> for LineHighlight {
}
}
}
fn render_diff_hunk_controls(
row: u32,
status: &DiffHunkStatus,
hunk_range: Range<Anchor>,
is_created_file: bool,
line_height: Pixels,
editor: &Entity<Editor>,
cx: &mut App,
) -> AnyElement {
h_flex()
.h(line_height)
.mr_1()
.gap_1()
.px_0p5()
.pb_1()
.border_x_1()
.border_b_1()
.border_color(cx.theme().colors().border_variant)
.rounded_b_lg()
.bg(cx.theme().colors().editor_background)
.gap_1()
.occlude()
.shadow_md()
.child(if status.has_secondary_hunk() {
Button::new(("stage", row as u64), "Stage")
.alpha(if status.is_pending() { 0.66 } else { 1.0 })
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |window, cx| {
Tooltip::for_action_in(
"Stage Hunk",
&::git::ToggleStaged,
&focus_handle,
window,
cx,
)
}
})
.on_click({
let editor = editor.clone();
move |_event, _window, cx| {
editor.update(cx, |editor, cx| {
editor.stage_or_unstage_diff_hunks(
true,
vec![hunk_range.start..hunk_range.start],
cx,
);
});
}
})
} else {
Button::new(("unstage", row as u64), "Unstage")
.alpha(if status.is_pending() { 0.66 } else { 1.0 })
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |window, cx| {
Tooltip::for_action_in(
"Unstage Hunk",
&::git::ToggleStaged,
&focus_handle,
window,
cx,
)
}
})
.on_click({
let editor = editor.clone();
move |_event, _window, cx| {
editor.update(cx, |editor, cx| {
editor.stage_or_unstage_diff_hunks(
false,
vec![hunk_range.start..hunk_range.start],
cx,
);
});
}
})
})
.child(
Button::new("restore", "Restore")
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |window, cx| {
Tooltip::for_action_in(
"Restore Hunk",
&::git::Restore,
&focus_handle,
window,
cx,
)
}
})
.on_click({
let editor = editor.clone();
move |_event, window, cx| {
editor.update(cx, |editor, cx| {
let snapshot = editor.snapshot(window, cx);
let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
editor.restore_hunks_in_ranges(vec![point..point], window, cx);
});
}
})
.disabled(is_created_file),
)
.when(
!editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
|el| {
el.child(
IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
// .disabled(!has_multiple_hunks)
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |window, cx| {
Tooltip::for_action_in(
"Next Hunk",
&GoToHunk,
&focus_handle,
window,
cx,
)
}
})
.on_click({
let editor = editor.clone();
move |_event, window, cx| {
editor.update(cx, |editor, cx| {
let snapshot = editor.snapshot(window, cx);
let position =
hunk_range.end.to_point(&snapshot.buffer_snapshot);
editor.go_to_hunk_before_or_after_position(
&snapshot,
position,
Direction::Next,
window,
cx,
);
editor.expand_selected_diff_hunks(cx);
});
}
}),
)
.child(
IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
// .disabled(!has_multiple_hunks)
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |window, cx| {
Tooltip::for_action_in(
"Previous Hunk",
&GoToPreviousHunk,
&focus_handle,
window,
cx,
)
}
})
.on_click({
let editor = editor.clone();
move |_event, window, cx| {
editor.update(cx, |editor, cx| {
let snapshot = editor.snapshot(window, cx);
let point =
hunk_range.start.to_point(&snapshot.buffer_snapshot);
editor.go_to_hunk_before_or_after_position(
&snapshot,
point,
Direction::Prev,
window,
cx,
);
editor.expand_selected_diff_hunks(cx);
});
}
}),
)
},
)
.into_any_element()
}

View file

@ -18,13 +18,13 @@ use crate::{
scroll::scroll_amount::ScrollAmount,
BlockId, ChunkReplacement, ContextMenuPlacement, CursorShape, CustomBlockId, DisplayDiffHunk,
DisplayPoint, DisplayRow, DocumentHighlightRead, DocumentHighlightWrite, EditDisplayMode,
Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle, FocusedBlock, GoToHunk,
GoToPreviousHunk, GutterDimensions, HalfPageDown, HalfPageUp, HandleInput, HoveredCursor,
InlayHintRefreshReason, InlineCompletion, JumpData, LineDown, LineHighlight, LineUp,
OpenExcerpts, PageDown, PageUp, Point, RowExt, RowRangeExt, SelectPhase, SelectedTextHighlight,
Selection, SoftWrap, StickyHeaderExcerpt, ToPoint, ToggleFold, COLUMNAR_SELECTION_MODIFIERS,
CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN,
MIN_LINE_NUMBER_DIGITS, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle, FocusedBlock,
GutterDimensions, HalfPageDown, HalfPageUp, HandleInput, HoveredCursor, InlayHintRefreshReason,
InlineCompletion, JumpData, LineDown, LineHighlight, LineUp, OpenExcerpts, PageDown, PageUp,
Point, RowExt, RowRangeExt, SelectPhase, SelectedTextHighlight, Selection, SoftWrap,
StickyHeaderExcerpt, ToPoint, ToggleFold, COLUMNAR_SELECTION_MODIFIERS, CURSORS_VISIBLE_FOR,
FILE_HEADER_HEIGHT, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN, MIN_LINE_NUMBER_DIGITS,
MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
};
use buffer_diff::{DiffHunkStatus, DiffHunkStatusKind};
use client::ParticipantIndex;
@ -43,7 +43,6 @@ use gpui::{
ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveElement, Style, Styled,
Subscription, TextRun, TextStyleRefinement, Window,
};
use inline_completion::Direction;
use itertools::Itertools;
use language::{
language_settings::{
@ -76,10 +75,7 @@ use std::{
use sum_tree::Bias;
use text::BufferId;
use theme::{ActiveTheme, Appearance, BufferLineHeight, PlayerColor};
use ui::{
h_flex, prelude::*, ButtonLike, ContextMenu, IconButtonShape, KeyBinding, Tooltip,
POPOVER_Y_PADDING,
};
use ui::{h_flex, prelude::*, ButtonLike, ContextMenu, KeyBinding, Tooltip, POPOVER_Y_PADDING};
use unicode_segmentation::UnicodeSegmentation;
use util::{debug_panic, RangeExt, ResultExt};
use workspace::{item::Item, notifications::NotifyTaskExt};
@ -3919,6 +3915,7 @@ impl EditorElement {
window: &mut Window,
cx: &mut App,
) -> Vec<AnyElement> {
let render_diff_hunk_controls = editor.read(cx).render_diff_hunk_controls.clone();
let point_for_position = position_map.point_for_position(window.mouse_position());
let mut controls = vec![];
@ -3961,7 +3958,7 @@ impl EditorElement {
+ text_hitbox.bounds.top()
- scroll_pixel_position.y;
let mut element = diff_hunk_controls(
let mut element = render_diff_hunk_controls(
display_row_range.start.0,
status,
multi_buffer_range.clone(),
@ -8882,187 +8879,3 @@ mod tests {
.collect()
}
}
fn diff_hunk_controls(
row: u32,
status: &DiffHunkStatus,
hunk_range: Range<Anchor>,
is_created_file: bool,
line_height: Pixels,
editor: &Entity<Editor>,
cx: &mut App,
) -> AnyElement {
h_flex()
.h(line_height)
.mr_1()
.gap_1()
.px_0p5()
.pb_1()
.border_x_1()
.border_b_1()
.border_color(cx.theme().colors().border_variant)
.rounded_b_lg()
.bg(cx.theme().colors().editor_background)
.gap_1()
.occlude()
.shadow_md()
.child(if status.has_secondary_hunk() {
Button::new(("stage", row as u64), "Stage")
.alpha(if status.is_pending() { 0.66 } else { 1.0 })
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |window, cx| {
Tooltip::for_action_in(
"Stage Hunk",
&::git::ToggleStaged,
&focus_handle,
window,
cx,
)
}
})
.on_click({
let editor = editor.clone();
move |_event, _window, cx| {
editor.update(cx, |editor, cx| {
editor.stage_or_unstage_diff_hunks(
true,
vec![hunk_range.start..hunk_range.start],
cx,
);
});
}
})
} else {
Button::new(("unstage", row as u64), "Unstage")
.alpha(if status.is_pending() { 0.66 } else { 1.0 })
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |window, cx| {
Tooltip::for_action_in(
"Unstage Hunk",
&::git::ToggleStaged,
&focus_handle,
window,
cx,
)
}
})
.on_click({
let editor = editor.clone();
move |_event, _window, cx| {
editor.update(cx, |editor, cx| {
editor.stage_or_unstage_diff_hunks(
false,
vec![hunk_range.start..hunk_range.start],
cx,
);
});
}
})
})
.child(
Button::new("restore", "Restore")
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |window, cx| {
Tooltip::for_action_in(
"Restore Hunk",
&::git::Restore,
&focus_handle,
window,
cx,
)
}
})
.on_click({
let editor = editor.clone();
move |_event, window, cx| {
editor.update(cx, |editor, cx| {
let snapshot = editor.snapshot(window, cx);
let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
editor.restore_hunks_in_ranges(vec![point..point], window, cx);
});
}
})
.disabled(is_created_file),
)
.when(
!editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
|el| {
el.child(
IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
// .disabled(!has_multiple_hunks)
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |window, cx| {
Tooltip::for_action_in(
"Next Hunk",
&GoToHunk,
&focus_handle,
window,
cx,
)
}
})
.on_click({
let editor = editor.clone();
move |_event, window, cx| {
editor.update(cx, |editor, cx| {
let snapshot = editor.snapshot(window, cx);
let position =
hunk_range.end.to_point(&snapshot.buffer_snapshot);
editor.go_to_hunk_before_or_after_position(
&snapshot,
position,
Direction::Next,
window,
cx,
);
editor.expand_selected_diff_hunks(cx);
});
}
}),
)
.child(
IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
// .disabled(!has_multiple_hunks)
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |window, cx| {
Tooltip::for_action_in(
"Previous Hunk",
&GoToPreviousHunk,
&focus_handle,
window,
cx,
)
}
})
.on_click({
let editor = editor.clone();
move |_event, window, cx| {
editor.update(cx, |editor, cx| {
let snapshot = editor.snapshot(window, cx);
let point =
hunk_range.start.to_point(&snapshot.buffer_snapshot);
editor.go_to_hunk_before_or_after_position(
&snapshot,
point,
Direction::Prev,
window,
cx,
);
editor.expand_selected_diff_hunks(cx);
});
}
}),
)
},
)
.into_any_element()
}