git: Move diff hunk operations onto buffer diffs (#63556)

# Objective

Keep Git-specific diff-base knowledge in `GitStore`, where Git
operations are defined.

Related to FR-135.

## Solution

Give each `BufferDiff` its supported hunk operations. Keep editor hooks
limited to rendering custom controls.

## Self-Review Checklist:

- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content adheres to Zed's UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

---

Release Notes:

- N/A
This commit is contained in:
Ben Kunkle authored and GitHub committed 2026-09-01 15:52:33 +00:00
1 parent 99f85efa71
commit aecaacdb44
21 files changed
+501 -603

No files matched your search

+7 -14
View File
@@ -89,13 +89,10 @@ impl Diff {
let language = buffer.read(cx).language().cloned();
let language_registry = buffer.read(cx).language_registry();
let buffer_diff = cx.new(|cx| {
BufferDiff::new_unchanged(
&buffer_text_snapshot,
language,
language_registry,
buffer_diff::DiffBaseKind::Custom,
cx,
)
let mut diff =
BufferDiff::new_unchanged(&buffer_text_snapshot, language, language_registry, cx);
diff.set_operations(Arc::new(buffer_diff::RestoreDiffOperations));
diff
});
let multibuffer = cx.new(|cx| {
@@ -396,13 +393,9 @@ async fn build_buffer_diff(
let base_text = base_text_exists.then(|| old_text);
let diff = cx.new(|cx| {
BufferDiff::new(
&buffer,
language,
language_registry,
buffer_diff::DiffBaseKind::Custom,
cx,
)
let mut diff = BufferDiff::new(&buffer, language, language_registry, cx);
diff.set_operations(Arc::new(buffer_diff::RestoreDiffOperations));
diff
});
diff.update(cx, |diff, cx| {
diff.set_base_text(base_text, buffer.text, cx)
+3 -7
View File
@@ -160,13 +160,9 @@ impl ActionLog {
let language = buffer.read(cx).language().cloned();
let language_registry = buffer.read(cx).language_registry();
let diff = cx.new(|cx| {
BufferDiff::new(
&text_snapshot,
language,
language_registry,
buffer_diff::DiffBaseKind::Custom,
cx,
)
let mut diff = BufferDiff::new(&text_snapshot, language, language_registry, cx);
diff.set_operations(Arc::new(buffer_diff::RestoreDiffOperations));
diff
});
let (diff_update_tx, diff_update_rx) = mpsc::unbounded();
let diff_base;
+12 -31
View File
@@ -6,8 +6,8 @@ use anyhow::Result;
use buffer_diff::DiffHunkStatus;
use collections::{HashMap, HashSet};
use editor::{
DiffHunkDelegate, Direction, Editor, EditorEvent, EditorSettings, MultiBuffer,
MultiBufferSnapshot, ResolvedDiffHunks, SelectionEffects, SplittableEditor, ToPoint,
DiffHunkRenderer, Direction, Editor, EditorEvent, EditorSettings, MultiBuffer,
MultiBufferSnapshot, SelectionEffects, SplittableEditor, ToPoint,
actions::{GoToHunk, GoToPreviousHunk},
multibuffer_context_lines,
scroll::Autoscroll,
@@ -101,7 +101,7 @@ impl AgentDiffPane {
cx,
);
diff_display_editor
.set_diff_hunk_delegate(Some(agent_diff_delegate(&thread, workspace.clone())), cx);
.set_diff_hunk_renderer(Some(agent_diff_renderer(&thread, workspace.clone())), cx);
diff_display_editor.update_editors(cx, |editor, _cx| {
editor.register_addon(AgentDiffAddon);
});
@@ -734,41 +734,22 @@ impl Render for AgentDiffPane {
}
}
struct AgentDiffDelegate {
struct AgentDiffHunkRenderer {
thread: Entity<AcpThread>,
workspace: WeakEntity<Workspace>,
}
fn agent_diff_delegate(
fn agent_diff_renderer(
thread: &Entity<AcpThread>,
workspace: WeakEntity<Workspace>,
) -> Arc<dyn DiffHunkDelegate> {
Arc::new(AgentDiffDelegate {
) -> Arc<dyn DiffHunkRenderer> {
Arc::new(AgentDiffHunkRenderer {
thread: thread.clone(),
workspace,
})
}
impl DiffHunkDelegate for AgentDiffDelegate {
fn toggle(
&self,
_hunks: Vec<ResolvedDiffHunks>,
_editor: &mut Editor,
_window: &mut Window,
_cx: &mut Context<Editor>,
) {
}
fn stage_or_unstage(
&self,
_stage: bool,
_hunks: Vec<ResolvedDiffHunks>,
_editor: &mut Editor,
_window: &mut Window,
_cx: &mut Context<Editor>,
) {
}
impl DiffHunkRenderer for AgentDiffHunkRenderer {
fn render_hunk_controls(
&self,
row: u32,
@@ -1579,7 +1560,7 @@ impl AgentDiff {
for (editor, _) in self.reviewing_editors.drain() {
editor
.update(cx, |editor, cx| {
editor.set_diff_hunk_delegate(None, cx);
editor.set_diff_hunk_renderer(None, cx);
editor.unregister_addon::<EditorAgentDiffAddon>();
})
.ok();
@@ -1628,8 +1609,8 @@ impl AgentDiff {
if previous_state.is_none() {
editor.update(cx, |editor, cx| {
editor.set_diff_hunk_delegate(
Some(agent_diff_delegate(&thread, workspace.clone())),
editor.set_diff_hunk_renderer(
Some(agent_diff_renderer(&thread, workspace.clone())),
cx,
);
editor.set_expand_all_diff_hunks(cx);
@@ -1678,7 +1659,7 @@ impl AgentDiff {
if in_workspace {
editor
.update(cx, |editor, cx| {
editor.set_diff_hunk_delegate(None, cx);
editor.set_diff_hunk_renderer(None, cx);
editor.unregister_addon::<EditorAgentDiffAddon>();
})
.ok();
+2 -2
View File
@@ -6,7 +6,7 @@ use agent_client_protocol::schema::v1 as acp;
use agent_settings::AgentSettings;
use collections::{HashMap, HashSet};
use editor::{
Editor, EditorEvent, EditorMode, MinimapVisibility, RestoreOnlyUnstagedDiffHunkDelegate,
Editor, EditorEvent, EditorMode, HiddenUnstagedDiffHunkRenderer, MinimapVisibility,
SizingBehavior,
};
use gpui::{
@@ -685,7 +685,7 @@ fn create_editor_diff(
editor.set_show_code_actions(false, cx);
editor.set_show_git_diff_gutter(false, cx);
editor.set_expand_all_diff_hunks(cx);
editor.set_diff_hunk_delegate(Some(Arc::new(RestoreOnlyUnstagedDiffHunkDelegate)), cx);
editor.set_diff_hunk_renderer(Some(Arc::new(HiddenUnstagedDiffHunkRenderer)), cx);
editor.set_text_style_refinement(diff_editor_text_style_refinement(cx));
editor
})
+67 -34
View File
@@ -25,23 +25,63 @@ pub struct BufferDiff {
diff_snapshot: Option<BufferDiffSnapshot>,
secondary_diff: Option<Entity<BufferDiff>>,
buffer_snapshot: text::BufferSnapshot,
base_kind: DiffBaseKind,
operations: Option<Arc<dyn DiffOperations>>,
}
/// Where this diff's base text came from. Only diffs whose base is HEAD
/// support staging and restoring hunks; a diff against any other base (e.g.
/// the merge base with another branch) would rewrite committed work.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DiffBaseKind {
/// The buffer's committed (HEAD) content.
Head,
/// The buffer's index (staged) content.
Index,
/// An arbitrary blob, such as the merge base with another branch.
Oid,
/// Arbitrary caller-provided text, such as an agent's original text,
/// the clipboard, or another file.
Custom,
pub trait DiffOperations {
fn supports_staging(&self) -> bool;
fn supports_unstaging(&self) -> bool;
fn supports_restore(&self) -> bool;
fn stage(
&self,
_diff: Entity<BufferDiff>,
_buffer: Option<Entity<language::Buffer>>,
_buffer_ranges: Vec<Range<Anchor>>,
_cx: &mut App,
) {
}
fn unstage(
&self,
_diff: Entity<BufferDiff>,
_buffer: Option<Entity<language::Buffer>>,
_buffer_ranges: Vec<Range<Anchor>>,
_cx: &mut App,
) {
}
}
pub struct RestoreDiffOperations;
impl DiffOperations for RestoreDiffOperations {
fn supports_staging(&self) -> bool {
false
}
fn supports_unstaging(&self) -> bool {
false
}
fn supports_restore(&self) -> bool {
true
}
}
#[cfg(any(test, feature = "test-support"))]
struct TestDiffOperations;
#[cfg(any(test, feature = "test-support"))]
impl DiffOperations for TestDiffOperations {
fn supports_staging(&self) -> bool {
true
}
fn supports_unstaging(&self) -> bool {
true
}
fn supports_restore(&self) -> bool {
true
}
}
#[derive(Clone)]
@@ -1570,7 +1610,6 @@ impl BufferDiff {
buffer: &text::BufferSnapshot,
language: Option<Arc<Language>>,
language_registry: Option<Arc<LanguageRegistry>>,
base_kind: DiffBaseKind,
cx: &mut App,
) -> Self {
let base_text = cx.new(|cx| {
@@ -1589,14 +1628,13 @@ impl BufferDiff {
diff_snapshot: None,
buffer_snapshot: buffer.clone(),
secondary_diff: None,
base_kind,
operations: None,
}
}
pub fn new_with_base_text_buffer(
buffer: &text::BufferSnapshot,
base_text_buffer: Entity<language::Buffer>,
base_kind: DiffBaseKind,
_cx: &mut App,
) -> Self {
BufferDiff {
@@ -1605,7 +1643,7 @@ impl BufferDiff {
diff_snapshot: None,
buffer_snapshot: buffer.clone(),
secondary_diff: None,
base_kind,
operations: None,
}
}
@@ -1613,7 +1651,6 @@ impl BufferDiff {
buffer: &text::BufferSnapshot,
language: Option<Arc<Language>>,
language_registry: Option<Arc<LanguageRegistry>>,
base_kind: DiffBaseKind,
cx: &mut Context<Self>,
) -> Self {
let base_text = buffer.text();
@@ -1644,7 +1681,7 @@ impl BufferDiff {
diff_snapshot: Some(diff_snapshot),
buffer_snapshot: buffer.clone(),
secondary_diff: None,
base_kind,
operations: None,
}
}
@@ -1654,7 +1691,8 @@ impl BufferDiff {
buffer: &text::BufferSnapshot,
cx: &mut Context<Self>,
) -> Self {
let mut this = BufferDiff::new(buffer, None, None, DiffBaseKind::Head, cx);
let mut this = BufferDiff::new(buffer, None, None, cx);
this.set_operations(Arc::new(TestDiffOperations));
let mut base_text = base_text.to_owned();
text::LineEnding::normalize(&mut base_text);
let base_text_buffer = cx.new(|cx| {
@@ -1678,14 +1716,12 @@ impl BufferDiff {
self.secondary_diff = Some(diff);
}
pub fn base_kind(&self) -> DiffBaseKind {
self.base_kind
pub fn set_operations(&mut self, operations: Arc<dyn DiffOperations>) {
self.operations = Some(operations);
}
/// Whether hunks in this diff can be staged or restored: true only when
/// the diff's base is HEAD.
pub fn is_stageable(&self) -> bool {
self.base_kind == DiffBaseKind::Head
pub fn operations(&self) -> Option<Arc<dyn DiffOperations>> {
self.operations.clone()
}
pub fn secondary_diff(&self) -> Option<Entity<BufferDiff>> {
@@ -2482,8 +2518,7 @@ mod tests {
],
);
diff = cx
.update(|cx| BufferDiff::new(&buffer, None, None, DiffBaseKind::Head, cx).snapshot(cx));
diff = cx.update(|cx| BufferDiff::new(&buffer, None, None, cx).snapshot(cx));
assert_hunks::<&str, _>(
diff.hunks_intersecting_range(
Anchor::min_max_range_for_buffer(buffer.remote_id()),
@@ -3188,8 +3223,7 @@ mod tests {
let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text_1);
let empty_diff = cx
.update(|cx| BufferDiff::new(&buffer, None, None, DiffBaseKind::Head, cx).snapshot(cx));
let empty_diff = cx.update(|cx| BufferDiff::new(&buffer, None, None, cx).snapshot(cx));
let diff_1 = BufferDiffSnapshot::new_sync(&buffer, base_text.clone(), cx);
let DiffChanged {
changed_range,
@@ -4347,8 +4381,7 @@ mod tests {
);
let buffer_snapshot = buffer.snapshot();
let diff =
cx.new(|cx| BufferDiff::new(&buffer_snapshot, None, None, DiffBaseKind::Head, cx));
let diff = cx.new(|cx| BufferDiff::new(&buffer_snapshot, None, None, cx));
diff.update(cx, |diff, cx| {
diff.set_base_text(Some(Arc::from(base_text_crlf)), buffer_snapshot.clone(), cx)
})
@@ -605,7 +605,6 @@ impl RatePredictionsModal {
&predicted_buffer_snapshot.text,
predicted_buffer_snapshot.language().cloned(),
predicted_buffer.read(cx).language_registry(),
buffer_diff::DiffBaseKind::Custom,
cx,
)
});
@@ -712,7 +711,6 @@ impl RatePredictionsModal {
&expected_buffer_snapshot.text,
expected_buffer_snapshot.language().cloned(),
expected_buffer.read(cx).language_registry(),
buffer_diff::DiffBaseKind::Custom,
cx,
)
});
+6 -5
View File
@@ -108,9 +108,8 @@ pub use element::{
};
pub use git::blame::{BlameRenderer, GitBlame};
pub use git::{
DiffHunkDelegate, ResolvedDiffHunk, ResolvedDiffHunks, RestoreOnlyDiffHunkDelegate,
RestoreOnlyUnstagedDiffHunkDelegate, UncommittedDiffHunkDelegate, render_diff_hunk_controls,
set_blame_renderer,
DefaultDiffHunkRenderer, DiffHunkRenderer, HiddenDiffHunkRenderer,
HiddenUnstagedDiffHunkRenderer, render_diff_hunk_controls, set_blame_renderer,
};
pub(crate) use git::{DiffHunkKey, StoredReviewComment};
use git::{DiffReviewDragState, DiffReviewOverlay, InlineBlamePopover};
@@ -1158,7 +1157,8 @@ pub struct Editor {
registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
language_detection_task: Task<()>,
load_diff_task: Option<Shared<Task<()>>>,
diff_hunk_delegate: Option<Arc<dyn DiffHunkDelegate>>,
diff_hunk_renderer: Option<Arc<dyn DiffHunkRenderer>>,
diff_hunk_action_target: Option<WeakEntity<Editor>>,
selection_mark_mode: bool,
toggle_fold_multiple_buffers: Task<()>,
_scroll_cursor_center_top_bottom_task: Task<()>,
@@ -2507,7 +2507,8 @@ impl Editor {
serialize_folds: Task::ready(()),
text_style_refinement: None,
load_diff_task: None,
diff_hunk_delegate: None,
diff_hunk_renderer: None,
diff_hunk_action_target: None,
minimap: None,
change_list: ChangeList::new(),
mode,
+1 -1
View File
@@ -28119,7 +28119,7 @@ async fn test_merge_base_diff_hunks_are_read_only(cx: &mut TestAppContext) {
.read(cx)
.diff_for(buffer_id)
.expect("buffer should have a display diff");
assert!(!diff.read(cx).is_stageable());
assert!(diff.read(cx).operations().is_none());
});
editor.update_in(cx, |editor, window, cx| {
editor.select_all(&SelectAll, window, cx);
+4 -4
View File
@@ -4664,7 +4664,7 @@ impl EditorElement {
window: &mut Window,
cx: &mut App,
) -> (Vec<AnyElement>, Vec<(DisplayRow, Bounds<Pixels>)>) {
let diff_hunk_delegate = editor.read(cx).diff_hunk_delegate();
let diff_hunk_renderer = editor.read(cx).diff_hunk_renderer();
let hovered_diff_hunk_row = editor.read(cx).hovered_diff_hunk_row;
let sticky_top = text_hitbox.bounds.top() + sticky_header_height;
@@ -4736,7 +4736,7 @@ impl EditorElement {
sticky_top.min(max_y)
};
let mut element = diff_hunk_delegate.render_hunk_controls(
let mut element = diff_hunk_renderer.render_hunk_controls(
display_row_range.start.0,
status,
multi_buffer_range.clone(),
@@ -6609,7 +6609,7 @@ impl EditorElement {
let unstaged = !self
.editor
.read(cx)
.diff_hunk_delegate()
.diff_hunk_renderer()
.render_hunk_as_staged(&status, cx);
let unstaged_hollow = matches!(
ProjectSettings::get_global(cx).git.hunk_style,
@@ -9398,7 +9398,7 @@ impl Element for EditorElement {
};
let (diff_hunk_controls, diff_hunk_control_bounds) =
if is_read_only && self.editor.read(cx).diff_hunk_delegate.is_none() {
if is_read_only && self.editor.read(cx).diff_hunk_renderer.is_none() {
(vec![], vec![])
} else {
self.layout_diff_hunk_controls(
+244 -256
View File
@@ -9,65 +9,20 @@ use buffer_diff::{BufferDiff, DiffHunkStatus, DiffHunkStatusKind};
use project::git_store::Repository;
#[derive(Clone)]
pub struct ResolvedDiffHunk {
pub buffer_range: Range<text::Anchor>,
pub diff_base_byte_range: Range<usize>,
pub status: DiffHunkStatus,
struct ResolvedDiffHunk {
buffer_range: Range<text::Anchor>,
diff_base_byte_range: Range<usize>,
status: DiffHunkStatus,
}
#[derive(Clone)]
pub struct ResolvedDiffHunks {
pub diff: Entity<BufferDiff>,
pub buffer_id: BufferId,
pub buffer: Option<Entity<Buffer>>,
pub hunks: Vec<ResolvedDiffHunk>,
struct ResolvedDiffHunks {
diff: Entity<BufferDiff>,
buffer: Option<Entity<Buffer>>,
hunks: Vec<ResolvedDiffHunk>,
}
pub trait DiffHunkDelegate {
fn toggle(
&self,
hunks: Vec<ResolvedDiffHunks>,
editor: &mut Editor,
window: &mut Window,
cx: &mut Context<Editor>,
);
fn stage_or_unstage(
&self,
stage: bool,
hunks: Vec<ResolvedDiffHunks>,
editor: &mut Editor,
window: &mut Window,
cx: &mut Context<Editor>,
);
fn restore(
&self,
hunks: Vec<ResolvedDiffHunks>,
editor: &mut Editor,
window: &mut Window,
cx: &mut Context<Editor>,
) {
if hunks.is_empty() || editor.read_only(cx) {
return;
}
self.stage_or_unstage(false, hunks.clone(), editor, window, cx);
editor.transact(window, cx, |editor, window, cx| {
editor.restore_diff_hunks(hunks, cx);
let selections = editor
.selections
.all::<MultiBufferOffset>(&editor.display_snapshot(cx));
editor.change_selections(
SelectionEffects::no_scroll(),
window,
cx,
|selections_state| {
selections_state.select(selections);
},
);
});
}
pub trait DiffHunkRenderer {
fn render_hunk_controls(
&self,
row: u32,
@@ -85,63 +40,9 @@ pub trait DiffHunkDelegate {
}
}
pub struct UncommittedDiffHunkDelegate;
impl DiffHunkDelegate for UncommittedDiffHunkDelegate {
fn toggle(
&self,
hunks: Vec<ResolvedDiffHunks>,
editor: &mut Editor,
window: &mut Window,
cx: &mut Context<Editor>,
) {
let stage = hunks
.iter()
.flat_map(|hunks| hunks.hunks.iter())
.any(|hunk| hunk.status.has_secondary_hunk());
self.stage_or_unstage(stage, hunks, editor, window, cx);
}
fn stage_or_unstage(
&self,
stage: bool,
hunks: Vec<ResolvedDiffHunks>,
editor: &mut Editor,
_window: &mut Window,
cx: &mut Context<Editor>,
) {
let Some(project) = editor.project() else {
return;
};
for hunks in hunks {
let Some(buffer) = hunks.buffer else {
continue;
};
let ranges = hunks
.hunks
.into_iter()
.map(|hunk| hunk.buffer_range)
.collect::<Vec<_>>();
if ranges.is_empty() {
continue;
}
let secondary_diff = hunks.diff.read(cx).secondary_diff();
project
.update(cx, |project, cx| {
if stage {
let Some(secondary_diff) = secondary_diff else {
return Err(anyhow::anyhow!("diff has no unstaged secondary"));
};
project.stage_hunks(buffer, secondary_diff, ranges, cx)
} else {
project.unstage_uncommitted_hunks(buffer, hunks.diff, ranges, cx)
}
})
.log_err();
}
}
pub struct DefaultDiffHunkRenderer;
impl DiffHunkRenderer for DefaultDiffHunkRenderer {
fn render_hunk_controls(
&self,
row: u32,
@@ -166,37 +67,9 @@ impl DiffHunkDelegate for UncommittedDiffHunkDelegate {
}
}
pub struct RestoreOnlyDiffHunkDelegate;
impl DiffHunkDelegate for RestoreOnlyDiffHunkDelegate {
fn toggle(
&self,
_hunks: Vec<ResolvedDiffHunks>,
_editor: &mut Editor,
_window: &mut Window,
_cx: &mut Context<Editor>,
) {
}
fn stage_or_unstage(
&self,
_stage: bool,
_hunks: Vec<ResolvedDiffHunks>,
_editor: &mut Editor,
_window: &mut Window,
_cx: &mut Context<Editor>,
) {
}
fn restore(
&self,
_hunks: Vec<ResolvedDiffHunks>,
_editor: &mut Editor,
_window: &mut Window,
_cx: &mut Context<Editor>,
) {
}
pub struct HiddenDiffHunkRenderer;
impl DiffHunkRenderer for HiddenDiffHunkRenderer {
fn render_hunk_controls(
&self,
_row: u32,
@@ -212,28 +85,9 @@ impl DiffHunkDelegate for RestoreOnlyDiffHunkDelegate {
}
}
pub struct RestoreOnlyUnstagedDiffHunkDelegate;
impl DiffHunkDelegate for RestoreOnlyUnstagedDiffHunkDelegate {
fn toggle(
&self,
_hunks: Vec<ResolvedDiffHunks>,
_editor: &mut Editor,
_window: &mut Window,
_cx: &mut Context<Editor>,
) {
}
fn stage_or_unstage(
&self,
_stage: bool,
_hunks: Vec<ResolvedDiffHunks>,
_editor: &mut Editor,
_window: &mut Window,
_cx: &mut Context<Editor>,
) {
}
pub struct HiddenUnstagedDiffHunkRenderer;
impl DiffHunkRenderer for HiddenUnstagedDiffHunkRenderer {
fn render_hunk_controls(
&self,
_row: u32,
@@ -465,7 +319,6 @@ impl Editor {
if !resolved_hunks.is_empty() {
resolved.push(ResolvedDiffHunks {
diff,
buffer_id: main_buffer_id,
buffer,
hunks: resolved_hunks,
});
@@ -475,24 +328,24 @@ impl Editor {
resolved
}
pub fn diff_hunk_delegate(&self) -> Arc<dyn DiffHunkDelegate> {
self.diff_hunk_delegate
pub fn diff_hunk_renderer(&self) -> Arc<dyn DiffHunkRenderer> {
self.diff_hunk_renderer
.clone()
.unwrap_or_else(|| Arc::new(UncommittedDiffHunkDelegate))
.unwrap_or_else(|| Arc::new(DefaultDiffHunkRenderer))
}
pub fn set_diff_hunk_delegate(
pub fn set_diff_hunk_renderer(
&mut self,
delegate: Option<Arc<dyn DiffHunkDelegate>>,
renderer: Option<Arc<dyn DiffHunkRenderer>>,
cx: &mut Context<Self>,
) {
let had_delegate = self.diff_hunk_delegate.is_some();
let has_delegate = delegate.is_some();
self.diff_hunk_delegate = delegate;
let had_renderer = self.diff_hunk_renderer.is_some();
let has_renderer = renderer.is_some();
self.diff_hunk_renderer = renderer;
if !had_delegate && has_delegate {
if !had_renderer && has_renderer {
self.load_diff_task.take();
} else if had_delegate && !has_delegate {
} else if had_renderer && !has_renderer {
self.buffer.update(cx, |buffer, cx| {
buffer.set_all_diff_hunks_collapsed(cx);
});
@@ -512,6 +365,10 @@ impl Editor {
cx.notify();
}
pub(crate) fn set_diff_hunk_action_target(&mut self, target: Option<WeakEntity<Editor>>) {
self.diff_hunk_action_target = target;
}
pub fn git_blame_inline_enabled(&self) -> bool {
self.git_blame_inline_enabled
}
@@ -1062,7 +919,7 @@ impl Editor {
);
}
pub fn restore_diff_hunks(&mut self, hunks: Vec<ResolvedDiffHunks>, cx: &mut Context<Self>) {
fn restore_diff_hunks(&mut self, hunks: Vec<ResolvedDiffHunks>, cx: &mut Context<Self>) {
let mut revert_changes = Vec::new();
for hunks in hunks {
let Some(buffer) = hunks.buffer else {
@@ -1774,36 +1631,104 @@ impl Editor {
pub fn apply_toggle(
&mut self,
hunks: Vec<MultiBufferDiffHunk>,
window: &mut Window,
_window: &mut Window,
cx: &mut Context<Self>,
) {
let mut hunks = self.resolve_diff_hunks(hunks, cx);
if self.diff_hunk_delegate.is_none() {
hunks.retain(|hunks| hunks.diff.read(cx).is_stageable());
}
let hunks = self.resolve_diff_hunks(hunks, cx);
if hunks.is_empty() {
return;
}
let delegate = self.diff_hunk_delegate();
delegate.toggle(hunks, self, window, cx);
if let Some(target) = self.diff_hunk_action_target.clone() {
target
.update(cx, |editor, cx| {
editor.toggle_resolved_diff_hunks(hunks, cx);
})
.log_err();
return;
}
self.toggle_resolved_diff_hunks(hunks, cx);
}
fn toggle_resolved_diff_hunks(
&mut self,
hunks: Vec<ResolvedDiffHunks>,
cx: &mut Context<Self>,
) {
for hunks in hunks {
let Some(operations) = hunks.diff.read(cx).operations() else {
continue;
};
let stage = if !operations.supports_unstaging() {
true
} else if !operations.supports_staging() {
false
} else {
hunks
.hunks
.iter()
.any(|hunk| hunk.status.has_secondary_hunk())
};
let ranges = hunks
.hunks
.into_iter()
.map(|hunk| hunk.buffer_range)
.collect();
if stage {
operations.stage(hunks.diff, hunks.buffer, ranges, cx);
} else {
operations.unstage(hunks.diff, hunks.buffer, ranges, cx);
}
}
}
pub fn apply_stage_or_unstage(
&mut self,
stage: bool,
hunks: Vec<MultiBufferDiffHunk>,
window: &mut Window,
_window: &mut Window,
cx: &mut Context<Self>,
) {
let mut hunks = self.resolve_diff_hunks(hunks, cx);
if self.diff_hunk_delegate.is_none() {
hunks.retain(|hunks| hunks.diff.read(cx).is_stageable());
}
let hunks = self.resolve_diff_hunks(hunks, cx);
if hunks.is_empty() {
return;
}
let delegate = self.diff_hunk_delegate();
delegate.stage_or_unstage(stage, hunks, self, window, cx);
if let Some(target) = self.diff_hunk_action_target.clone() {
target
.update(cx, |editor, cx| {
editor.stage_or_unstage_resolved_diff_hunks(stage, hunks, cx);
})
.log_err();
return;
}
self.stage_or_unstage_resolved_diff_hunks(stage, hunks, cx);
}
fn stage_or_unstage_resolved_diff_hunks(
&mut self,
stage: bool,
hunks: Vec<ResolvedDiffHunks>,
cx: &mut Context<Self>,
) {
for hunks in hunks {
let Some(operations) = hunks.diff.read(cx).operations() else {
continue;
};
if (stage && !operations.supports_staging())
|| (!stage && !operations.supports_unstaging())
{
continue;
}
let ranges = hunks
.hunks
.into_iter()
.map(|hunk| hunk.buffer_range)
.collect();
if stage {
operations.stage(hunks.diff, hunks.buffer, ranges, cx);
} else {
operations.unstage(hunks.diff, hunks.buffer, ranges, cx);
}
}
}
pub fn apply_restore(
@@ -1812,15 +1737,65 @@ impl Editor {
window: &mut Window,
cx: &mut Context<Self>,
) {
let mut hunks = self.resolve_diff_hunks(hunks, cx);
if self.diff_hunk_delegate.is_none() {
hunks.retain(|hunks| hunks.diff.read(cx).is_stageable());
}
let hunks = self.resolve_diff_hunks(hunks, cx);
if hunks.is_empty() {
return;
}
let delegate = self.diff_hunk_delegate();
delegate.restore(hunks, self, window, cx);
if let Some(target) = self.diff_hunk_action_target.clone() {
target
.update(cx, |editor, cx| {
editor.restore_resolved_diff_hunks(hunks, window, cx);
})
.log_err();
return;
}
self.restore_resolved_diff_hunks(hunks, window, cx);
}
fn restore_resolved_diff_hunks(
&mut self,
hunks: Vec<ResolvedDiffHunks>,
window: &mut Window,
cx: &mut Context<Self>,
) {
if self.read_only(cx) {
return;
}
let mut restorable_hunks = Vec::new();
for hunks in hunks {
let Some(operations) = hunks.diff.read(cx).operations() else {
continue;
};
if !operations.supports_restore() {
continue;
}
if operations.supports_unstaging() {
let ranges = hunks
.hunks
.iter()
.map(|hunk| hunk.buffer_range.clone())
.collect();
operations.unstage(hunks.diff.clone(), hunks.buffer.clone(), ranges, cx);
}
restorable_hunks.push(hunks);
}
if restorable_hunks.is_empty() {
return;
}
self.transact(window, cx, |editor, window, cx| {
editor.restore_diff_hunks(restorable_hunks, cx);
let selections = editor
.selections
.all::<MultiBufferOffset>(&editor.display_snapshot(cx));
editor.change_selections(
SelectionEffects::no_scroll(),
window,
cx,
|selections_state| {
selections_state.select(selections);
},
);
});
}
pub(super) fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
@@ -3063,15 +3038,23 @@ pub fn render_diff_hunk_controls(
_window: &mut Window,
cx: &mut App,
) -> AnyElement {
let stageable = hunk_range
let operations = hunk_range
.start
.buffer_id()
.and_then(|buffer_id| editor.read(cx).buffer().read(cx).diff_for(buffer_id))
.is_some_and(|diff| diff.read(cx).is_stageable());
let show_stage_restore = stageable
&& ProjectSettings::get_global(cx)
.git
.show_stage_restore_buttons;
.and_then(|diff| diff.read(cx).operations());
let show_stage_restore = ProjectSettings::get_global(cx)
.git
.show_stage_restore_buttons;
let supports_staging = operations
.as_ref()
.is_some_and(|operations| operations.supports_staging());
let supports_unstaging = operations
.as_ref()
.is_some_and(|operations| operations.supports_unstaging());
let supports_restore = operations
.as_ref()
.is_some_and(|operations| operations.supports_restore());
h_flex()
.h(line_height)
@@ -3087,64 +3070,69 @@ pub fn render_diff_hunk_controls(
.gap_1()
.block_mouse_except_scroll()
.shadow_md()
.when(show_stage_restore, |el| {
el.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,
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],
window,
.when(
show_stage_restore
&& ((status.has_secondary_hunk() && supports_staging)
|| (!status.has_secondary_hunk() && supports_unstaging)),
|el| {
el.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,
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,
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],
window,
)
}
})
.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],
window,
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,
cx,
);
});
}
})
})
})
.when(show_stage_restore, |el| {
)
}
})
.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],
window,
cx,
);
});
}
})
})
},
)
.when(show_stage_restore && supports_restore, |el| {
el.child(
Button::new(("restore", row as u64), "Restore")
.tooltip({
@@ -3269,7 +3257,7 @@ impl Editor {
let buffer = self.buffer.clone();
cx.spawn(async move |_, cx| {
let diffs = future::join_all(tasks).await;
if editor.read_with(cx, |editor, _cx| editor.diff_hunk_delegate.is_some()) {
if editor.read_with(cx, |editor, _cx| editor.diff_hunk_renderer.is_some()) {
return;
}
+20 -78
View File
@@ -22,14 +22,12 @@ use rope::Point;
use settings::{DiffViewStyle, SeedQuerySetting, Settings, SettingsStore, update_settings_file};
use text::{Bias, BufferId, OffsetRangeExt as _, Patch, ToPoint as _};
use ui::{Toggleable as _, Tooltip, prelude::*, render_modifiers};
use util::ResultExt as _;
use crate::{
display_map::CompanionExcerptPatch,
element::SplitSide,
split_editor_view::{SplitEditorState, SplitEditorView},
};
use ui::{Toggleable as _, Tooltip, prelude::*, render_modifiers};
use workspace::{
ActivatePaneLeft, ActivatePaneRight, Item, ToolbarItemLocation, Workspace,
item::{ItemBufferKind, ItemEvent, SaveOptions, TabContentParams},
@@ -37,8 +35,8 @@ use workspace::{
};
use crate::{
Autoscroll, DiffHunkDelegate, Editor, EditorEvent, EditorSettings, ResolvedDiffHunks,
ToggleSoftWrap, UncommittedDiffHunkDelegate,
Autoscroll, DefaultDiffHunkRenderer, DiffHunkRenderer, Editor, EditorEvent, EditorSettings,
ToggleSoftWrap,
actions::{DisableBreakpoint, EditLogBreakpoint, EnableBreakpoint, ToggleBreakpoint},
display_map::Companion,
};
@@ -151,63 +149,11 @@ fn translate_lhs_selections_to_rhs(
translated
}
struct SplitLhsDiffHunkDelegate {
struct SplitLhsDiffHunkRenderer {
splittable: WeakEntity<SplittableEditor>,
}
impl DiffHunkDelegate for SplitLhsDiffHunkDelegate {
fn toggle(
&self,
hunks: Vec<ResolvedDiffHunks>,
_editor: &mut Editor,
window: &mut Window,
cx: &mut Context<Editor>,
) {
self.splittable
.update(cx, |splittable, cx| {
splittable.rhs_editor.update(cx, |editor, cx| {
let delegate = editor.diff_hunk_delegate();
delegate.toggle(hunks, editor, window, cx);
});
})
.log_err();
}
fn stage_or_unstage(
&self,
stage: bool,
hunks: Vec<ResolvedDiffHunks>,
_editor: &mut Editor,
window: &mut Window,
cx: &mut Context<Editor>,
) {
self.splittable
.update(cx, |splittable, cx| {
splittable.rhs_editor.update(cx, |editor, cx| {
let delegate = editor.diff_hunk_delegate();
delegate.stage_or_unstage(stage, hunks, editor, window, cx);
});
})
.log_err();
}
fn restore(
&self,
hunks: Vec<ResolvedDiffHunks>,
_editor: &mut Editor,
window: &mut Window,
cx: &mut Context<Editor>,
) {
self.splittable
.update(cx, |splittable, cx| {
splittable.rhs_editor.update(cx, |editor, cx| {
let delegate = editor.diff_hunk_delegate();
delegate.restore(hunks, editor, window, cx);
});
})
.log_err();
}
impl DiffHunkRenderer for SplitLhsDiffHunkRenderer {
fn render_hunk_controls(
&self,
row: u32,
@@ -222,8 +168,8 @@ impl DiffHunkDelegate for SplitLhsDiffHunkDelegate {
let Some(splittable) = self.splittable.upgrade() else {
return gpui::Empty.into_any_element();
};
let delegate = splittable.read(cx).rhs_editor.read(cx).diff_hunk_delegate();
delegate.render_hunk_controls(
let renderer = splittable.read(cx).rhs_editor.read(cx).diff_hunk_renderer();
renderer.render_hunk_controls(
row,
status,
hunk_range,
@@ -239,8 +185,8 @@ impl DiffHunkDelegate for SplitLhsDiffHunkDelegate {
let Some(splittable) = self.splittable.upgrade() else {
return false;
};
let delegate = splittable.read(cx).rhs_editor.read(cx).diff_hunk_delegate();
delegate.render_hunk_as_staged(status, cx)
let renderer = splittable.read(cx).rhs_editor.read(cx).diff_hunk_renderer();
renderer.render_hunk_as_staged(status, cx)
}
}
@@ -636,13 +582,13 @@ impl SplittableEditor {
self.lhs.is_some()
}
pub fn set_diff_hunk_delegate(
pub fn set_diff_hunk_renderer(
&self,
delegate: Option<Arc<dyn DiffHunkDelegate>>,
renderer: Option<Arc<dyn DiffHunkRenderer>>,
cx: &mut Context<Self>,
) {
self.rhs_editor.update(cx, |editor, cx| {
editor.set_diff_hunk_delegate(delegate, cx);
editor.set_diff_hunk_renderer(renderer, cx);
});
}
@@ -683,7 +629,7 @@ impl SplittableEditor {
editor.disable_inline_diagnostics();
editor.disable_mouse_wheel_zoom();
editor.set_minimap_visibility(crate::MinimapVisibility::Disabled, window, cx);
editor.set_diff_hunk_delegate(Some(Arc::new(UncommittedDiffHunkDelegate)), cx);
editor.set_diff_hunk_renderer(Some(Arc::new(DefaultDiffHunkRenderer)), cx);
editor
});
// TODO(split-diff) we might want to tag editor events with whether they came from rhs/lhs
@@ -782,15 +728,19 @@ impl SplittableEditor {
});
let splittable = cx.weak_entity();
let rhs_editor = self.rhs_editor.downgrade();
let lhs_editor = cx.new(|cx| {
let mut editor =
Editor::for_multibuffer(lhs_multibuffer.clone(), Some(project.clone()), window, cx);
editor.set_number_deleted_lines(true, cx);
editor.set_delegate_expand_excerpts(true);
editor.set_diff_hunk_delegate(
Some(Arc::new(SplitLhsDiffHunkDelegate { splittable })),
editor.set_diff_hunk_renderer(
Some(Arc::new(SplitLhsDiffHunkRenderer {
splittable: splittable.clone(),
})),
cx,
);
editor.set_diff_hunk_action_target(Some(rhs_editor));
editor.set_delegate_open_excerpts(true);
editor.set_show_vertical_scrollbar(false, cx);
editor.disable_lsp_data();
@@ -3894,15 +3844,7 @@ mod tests {
.unindent();
let buffer2 = cx.new(|cx| Buffer::local(current_text.to_string(), cx));
let diff2 = cx.new(|cx| {
BufferDiff::new(
&buffer2.read(cx).text_snapshot(),
None,
None,
buffer_diff::DiffBaseKind::Custom,
cx,
)
});
let diff2 = cx.new(|cx| BufferDiff::new(&buffer2.read(cx).text_snapshot(), None, None, cx));
editor.update(cx, |editor, cx| {
let path1 = PathKey::sorted(0);
+2 -2
View File
@@ -9,7 +9,7 @@ use crate::{
use agent_settings::AgentSettings;
use anyhow::{Context as _, Result, anyhow};
use editor::{
Addon, Editor, EditorEvent, RestoreOnlyDiffHunkDelegate, SplittableEditor,
Addon, Editor, EditorEvent, HiddenDiffHunkRenderer, SplittableEditor,
actions::SendReviewToAgent,
};
use git::{repository::DiffType, status::FileStatus};
@@ -338,7 +338,7 @@ impl BranchDiff {
Capability::ReadWrite,
"No changes",
move |editor, cx| {
editor.set_diff_hunk_delegate(Some(Arc::new(RestoreOnlyDiffHunkDelegate)), cx);
editor.set_diff_hunk_renderer(Some(Arc::new(HiddenDiffHunkRenderer)), cx);
editor.rhs_editor().update(cx, move |rhs_editor, _cx| {
rhs_editor.set_read_only(false);
rhs_editor.register_addon(BranchDiffAddon {
+5 -13
View File
@@ -2,7 +2,7 @@ use anyhow::{Context as _, Result};
use buffer_diff::BufferDiff;
use collections::HashMap;
use editor::{
Addon, Editor, EditorEvent, EditorSettings, MultiBuffer, RestoreOnlyDiffHunkDelegate,
Addon, Editor, EditorEvent, EditorSettings, HiddenDiffHunkRenderer, MultiBuffer,
SplittableEditor, hover_markdown_style, multibuffer_context_lines,
};
use futures_lite::future::yield_now;
@@ -326,7 +326,7 @@ impl CommitView {
window,
cx,
);
editor.set_diff_hunk_delegate(Some(Arc::new(RestoreOnlyDiffHunkDelegate)), cx);
editor.set_diff_hunk_renderer(Some(Arc::new(HiddenDiffHunkRenderer)), cx);
editor.rhs_editor().update(cx, |editor, cx| {
editor.set_show_bookmarks(false, cx);
@@ -417,7 +417,6 @@ impl CommitView {
&snapshot,
snapshot.language().cloned(),
Some(language_registry.clone()),
buffer_diff::DiffBaseKind::Oid,
cx,
)
})
@@ -1159,15 +1158,8 @@ async fn build_buffer_diff(
let language = cx.update(|_, cx| buffer.read(cx).language().cloned())?;
let buffer = cx.update(|_, cx| buffer.read(cx).snapshot())?;
let diff = cx.new(|cx| {
BufferDiff::new(
&buffer.text,
language,
Some(language_registry.clone()),
buffer_diff::DiffBaseKind::Oid,
cx,
)
});
let diff =
cx.new(|cx| BufferDiff::new(&buffer.text, language, Some(language_registry.clone()), cx));
diff.update(cx, |diff, cx| {
diff.set_base_text(
@@ -1346,7 +1338,7 @@ impl Item for CommitView {
window,
cx,
);
editor.set_diff_hunk_delegate(Some(Arc::new(RestoreOnlyDiffHunkDelegate)), cx);
editor.set_diff_hunk_renderer(Some(Arc::new(HiddenDiffHunkRenderer)), cx);
editor.rhs_editor().update(cx, |editor, cx| {
editor.set_show_bookmarks(false, cx);
editor.set_show_breakpoints(false, cx);
+2 -3
View File
@@ -1,8 +1,7 @@
use anyhow::Result;
use buffer_diff::BufferDiff;
use editor::{
Editor, EditorEvent, MultiBuffer, RestoreOnlyUnstagedDiffHunkDelegate,
multibuffer_context_lines,
Editor, EditorEvent, HiddenUnstagedDiffHunkRenderer, MultiBuffer, multibuffer_context_lines,
};
use git_ui_core::file_diff_view::build_buffer_diff;
use gpui::{
@@ -199,7 +198,7 @@ impl MultiDiffView {
let editor = cx.new(|cx| {
let mut editor =
Editor::for_multibuffer(multibuffer, Some(project.clone()), window, cx);
editor.set_diff_hunk_delegate(Some(Arc::new(RestoreOnlyUnstagedDiffHunkDelegate)), cx);
editor.set_diff_hunk_renderer(Some(Arc::new(HiddenUnstagedDiffHunkRenderer)), cx);
editor.disable_diagnostics(cx);
editor.set_expand_all_diff_hunks(cx);
editor
+2 -2
View File
@@ -8,7 +8,7 @@ use crate::{
use anyhow::{Context as _, Result};
use buffer_diff::DiffHunkSecondaryStatus;
use editor::{
Editor, EditorEvent, SplittableEditor, UncommittedDiffHunkDelegate,
DefaultDiffHunkRenderer, Editor, EditorEvent, SplittableEditor,
actions::{GoToHunk, GoToPreviousHunk, SendReviewToAgent},
};
use git::{Commit, StageAll, StageAndNext, ToggleStaged, UnstageAll, UnstageAndNext};
@@ -243,7 +243,7 @@ impl ProjectDiff {
Capability::ReadWrite,
"No uncommitted changes",
move |editor, cx| {
editor.set_diff_hunk_delegate(Some(Arc::new(UncommittedDiffHunkDelegate)), cx);
editor.set_diff_hunk_renderer(Some(Arc::new(DefaultDiffHunkRenderer)), cx);
editor.rhs_editor().update(cx, |rhs_editor, _cx| {
rhs_editor.set_read_only(false);
rhs_editor.register_addon(GitPanelAddon {
+4 -46
View File
@@ -5,7 +5,7 @@ use crate::{
use anyhow::{Context as _, Result};
use buffer_diff::DiffHunkStatus;
use editor::{
DiffHunkDelegate, Editor, EditorEvent, ResolvedDiffHunks, SplittableEditor,
DiffHunkRenderer, Editor, EditorEvent, SplittableEditor,
actions::{GoToHunk, GoToPreviousHunk},
};
use git::{Commit, UnstageAll, UnstageAndNext};
@@ -26,7 +26,6 @@ use std::{
sync::Arc,
};
use ui::{DiffStat, Divider, Icon, Tooltip, Window, prelude::*};
use util::ResultExt as _;
use workspace::{
ItemNavHistory, SerializableItem, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView,
Workspace,
@@ -34,50 +33,9 @@ use workspace::{
searchable::SearchableItemHandle,
};
pub(crate) struct StagedDiffDelegate;
impl DiffHunkDelegate for StagedDiffDelegate {
fn toggle(
&self,
hunks: Vec<ResolvedDiffHunks>,
editor: &mut Editor,
window: &mut Window,
cx: &mut Context<Editor>,
) {
self.stage_or_unstage(false, hunks, editor, window, cx);
}
fn stage_or_unstage(
&self,
stage: bool,
hunks: Vec<ResolvedDiffHunks>,
editor: &mut Editor,
_window: &mut Window,
cx: &mut Context<Editor>,
) {
if stage {
return;
}
let Some(project) = editor.project().cloned() else {
return;
};
for hunks in hunks {
let index_ranges = hunks
.hunks
.into_iter()
.map(|hunk| hunk.buffer_range)
.collect::<Vec<_>>();
if index_ranges.is_empty() {
continue;
}
project
.update(cx, |project, cx| {
project.unstage_staged_hunks(hunks.diff, index_ranges, cx)
})
.log_err();
}
}
pub(crate) struct StagedDiffHunkRenderer;
impl DiffHunkRenderer for StagedDiffHunkRenderer {
fn render_hunk_controls(
&self,
row: u32,
@@ -228,7 +186,7 @@ impl StagedDiff {
Capability::ReadOnly,
"No staged changes",
move |editor, cx| {
editor.set_diff_hunk_delegate(Some(Arc::new(StagedDiffDelegate)), cx);
editor.set_diff_hunk_renderer(Some(Arc::new(StagedDiffHunkRenderer)), cx);
editor.rhs_editor().update(cx, |rhs_editor, _cx| {
rhs_editor.set_read_only(true);
rhs_editor.register_addon(GitPanelAddon {
+6 -6
View File
@@ -3,7 +3,7 @@
use anyhow::Result;
use buffer_diff::BufferDiff;
use editor::{
Editor, EditorEvent, EditorSettings, MultiBuffer, RestoreOnlyUnstagedDiffHunkDelegate,
Editor, EditorEvent, EditorSettings, HiddenUnstagedDiffHunkRenderer, MultiBuffer,
SplittableEditor, ToPoint, actions::DiffClipboardWithSelectionData,
};
use futures::{FutureExt, select_biased};
@@ -117,12 +117,13 @@ impl TextDiffView {
cx,
);
let diff_buffer = cx.new(|cx| {
BufferDiff::new_with_base_text_buffer(
let mut diff = BufferDiff::new_with_base_text_buffer(
&source_buffer_snapshot.text,
clipboard_buffer.clone(),
buffer_diff::DiffBaseKind::Custom,
cx,
)
);
diff.set_operations(Arc::new(buffer_diff::RestoreDiffOperations));
diff
});
let task = window.spawn(cx, async move |cx| {
@@ -185,8 +186,7 @@ impl TextDiffView {
window,
cx,
);
splittable
.set_diff_hunk_delegate(Some(Arc::new(RestoreOnlyUnstagedDiffHunkDelegate)), cx);
splittable.set_diff_hunk_renderer(Some(Arc::new(HiddenUnstagedDiffHunkRenderer)), cx);
splittable
});
+4 -75
View File
@@ -5,7 +5,7 @@ use crate::{
use anyhow::{Context as _, Result};
use buffer_diff::DiffHunkStatus;
use editor::{
DiffHunkDelegate, Editor, EditorEvent, ResolvedDiffHunks, SplittableEditor,
DiffHunkRenderer, Editor, EditorEvent, SplittableEditor,
actions::{GoToHunk, GoToPreviousHunk},
};
use git::{StageAll, StageAndNext};
@@ -26,7 +26,6 @@ use std::{
sync::Arc,
};
use ui::{DiffStat, Divider, Icon, Tooltip, Window, prelude::*};
use util::ResultExt as _;
use workspace::{
ItemNavHistory, SerializableItem, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView,
Workspace,
@@ -34,79 +33,9 @@ use workspace::{
searchable::SearchableItemHandle,
};
pub(crate) struct UnstagedDiffDelegate;
impl DiffHunkDelegate for UnstagedDiffDelegate {
fn toggle(
&self,
hunks: Vec<ResolvedDiffHunks>,
editor: &mut Editor,
window: &mut Window,
cx: &mut Context<Editor>,
) {
self.stage_or_unstage(true, hunks, editor, window, cx);
}
fn stage_or_unstage(
&self,
stage: bool,
hunks: Vec<ResolvedDiffHunks>,
editor: &mut Editor,
_window: &mut Window,
cx: &mut Context<Editor>,
) {
if !stage {
return;
}
let Some(project) = editor.project().cloned() else {
return;
};
for hunks in hunks {
let Some(buffer) = hunks.buffer else {
continue;
};
let worktree_ranges = hunks
.hunks
.into_iter()
.map(|hunk| hunk.buffer_range)
.collect::<Vec<_>>();
if worktree_ranges.is_empty() {
continue;
}
project
.update(cx, |project, cx| {
project.stage_hunks(buffer, hunks.diff, worktree_ranges, cx)
})
.log_err();
}
}
fn restore(
&self,
hunks: Vec<ResolvedDiffHunks>,
editor: &mut Editor,
window: &mut Window,
cx: &mut Context<Editor>,
) {
if hunks.is_empty() || editor.read_only(cx) {
return;
}
editor.transact(window, cx, |editor, window, cx| {
editor.restore_diff_hunks(hunks, cx);
let selections = editor
.selections
.all::<editor::MultiBufferOffset>(&editor.display_snapshot(cx));
editor.change_selections(
editor::SelectionEffects::no_scroll(),
window,
cx,
|selections_state| {
selections_state.select(selections);
},
);
});
}
pub(crate) struct UnstagedDiffHunkRenderer;
impl DiffHunkRenderer for UnstagedDiffHunkRenderer {
fn render_hunk_controls(
&self,
row: u32,
@@ -282,7 +211,7 @@ impl UnstagedDiff {
Capability::ReadWrite,
"No unstaged changes",
move |editor, cx| {
editor.set_diff_hunk_delegate(Some(Arc::new(UnstagedDiffDelegate)), cx);
editor.set_diff_hunk_renderer(Some(Arc::new(UnstagedDiffHunkRenderer)), cx);
editor.rhs_editor().update(cx, |rhs_editor, _cx| {
rhs_editor.set_read_only(false);
rhs_editor.register_addon(GitPanelAddon {
+6 -6
View File
@@ -3,7 +3,7 @@
use anyhow::Result;
use buffer_diff::BufferDiff;
use editor::{
Editor, EditorEvent, EditorSettings, MultiBuffer, RestoreOnlyUnstagedDiffHunkDelegate,
Editor, EditorEvent, EditorSettings, HiddenUnstagedDiffHunkRenderer, MultiBuffer,
SplittableEditor,
};
use futures::{FutureExt, select_biased};
@@ -124,8 +124,7 @@ impl FileDiffView {
window,
cx,
);
splittable
.set_diff_hunk_delegate(Some(Arc::new(RestoreOnlyUnstagedDiffHunkDelegate)), cx);
splittable.set_diff_hunk_renderer(Some(Arc::new(HiddenUnstagedDiffHunkRenderer)), cx);
splittable
});
@@ -214,13 +213,14 @@ pub async fn build_buffer_diff(
let language_registry = new_buffer.read_with(cx, |buffer, _| buffer.language_registry());
let diff = cx.new(|cx| {
BufferDiff::new(
let mut diff = BufferDiff::new(
&new_buffer_snapshot.text,
new_buffer_snapshot.language().cloned(),
language_registry,
buffer_diff::DiffBaseKind::Custom,
cx,
)
);
diff.set_operations(Arc::new(buffer_diff::RestoreDiffOperations));
diff
});
diff.update(cx, |diff, cx| {
+101 -16
View File
@@ -5,7 +5,7 @@ pub mod job_debug_queue;
pub mod pending_op;
use crate::{
ProjectEnvironment, ProjectItem, ProjectPath,
Project, ProjectEnvironment, ProjectItem, ProjectPath,
buffer_store::{BufferStore, BufferStoreEvent},
project_settings::ProjectSettings,
trusted_worktrees::{
@@ -15,7 +15,9 @@ use crate::{
};
use anyhow::{Context as _, Result, anyhow, bail};
use askpass::{AskPassDelegate, EncryptedPassword, IKnowWhatIAmDoingAndIHaveReadTheDocs};
use buffer_diff::{BufferDiff, DiffHunk, DiffHunkSecondaryStatus, PendingHunk, PendingSense};
use buffer_diff::{
BufferDiff, DiffHunk, DiffHunkSecondaryStatus, DiffOperations, PendingHunk, PendingSense,
};
use client::ProjectId;
use collections::HashMap;
pub use conflict_set::{ConflictRegion, ConflictSet, ConflictSetSnapshot, ConflictSetUpdate};
@@ -98,6 +100,7 @@ use zeroize::Zeroize;
pub struct GitStore {
state: GitStoreState,
project: Option<WeakEntity<Project>>,
buffer_store: Entity<BufferStore>,
worktree_store: Entity<WorktreeStore>,
repositories: HashMap<RepositoryId, Entity<Repository>>,
@@ -310,6 +313,68 @@ enum DiffKind {
SinceOid(Option<git::Oid>),
}
struct GitDiffOperations {
project: WeakEntity<Project>,
kind: DiffKind,
}
impl DiffOperations for GitDiffOperations {
fn supports_staging(&self) -> bool {
matches!(self.kind, DiffKind::Unstaged | DiffKind::Uncommitted)
}
fn supports_unstaging(&self) -> bool {
matches!(self.kind, DiffKind::Staged | DiffKind::Uncommitted)
}
fn supports_restore(&self) -> bool {
matches!(self.kind, DiffKind::Unstaged | DiffKind::Uncommitted)
}
fn stage(
&self,
diff: Entity<BufferDiff>,
buffer: Option<Entity<Buffer>>,
buffer_ranges: Vec<Range<Anchor>>,
cx: &mut App,
) {
let result = self.project.update(cx, |project, cx| match self.kind {
DiffKind::Unstaged => {
let buffer = buffer.context("unstaged diff has no worktree buffer")?;
project.stage_hunks(buffer, diff, buffer_ranges, cx)
}
DiffKind::Uncommitted => {
let buffer = buffer.context("uncommitted diff has no worktree buffer")?;
let secondary_diff = diff
.read(cx)
.secondary_diff()
.context("diff has no unstaged secondary")?;
project.stage_hunks(buffer, secondary_diff, buffer_ranges, cx)
}
_ => Ok(()),
});
result.and_then(|result| result).log_err();
}
fn unstage(
&self,
diff: Entity<BufferDiff>,
buffer: Option<Entity<Buffer>>,
buffer_ranges: Vec<Range<Anchor>>,
cx: &mut App,
) {
let result = self.project.update(cx, |project, cx| match self.kind {
DiffKind::Staged => project.unstage_staged_hunks(diff, buffer_ranges, cx),
DiffKind::Uncommitted => {
let buffer = buffer.context("uncommitted diff has no worktree buffer")?;
project.unstage_uncommitted_hunks(buffer, diff, buffer_ranges, cx)
}
_ => Ok(()),
});
result.and_then(|result| result).log_err();
}
}
struct IndexTextFile {
path: Arc<RelPath>,
full_path: PathBuf,
@@ -942,6 +1007,7 @@ impl GitStore {
let diff_base_setting = ProjectSettings::get_global(cx).git.diff_base;
GitStore {
state,
project: None,
buffer_store,
worktree_store,
repositories: HashMap::default(),
@@ -958,6 +1024,10 @@ impl GitStore {
}
}
pub(crate) fn set_project(&mut self, project: WeakEntity<Project>) {
self.project = Some(project);
}
pub fn init(client: &AnyProtoClient) {
client.add_entity_request_handler(Self::handle_get_remotes);
client.add_entity_request_handler(Self::handle_get_branches);
@@ -1628,7 +1698,6 @@ impl GitStore {
&buffer_snapshot,
buffer_snapshot.language().cloned(),
language_registry,
buffer_diff::DiffBaseKind::Oid,
cx,
)
});
@@ -1801,10 +1870,11 @@ impl GitStore {
this.loading_diffs.remove(&(buffer_id, kind));
let git_store = cx.weak_entity();
let project = this.project.clone();
let diff_state = this
.diffs
.entry(buffer_id)
.or_insert_with(|| cx.new(|cx| BufferGitState::new(git_store, cx)));
.or_insert_with(|| cx.new(|cx| BufferGitState::new(git_store.clone(), cx)));
let existing_unstaged_diff = diff_state.read(cx).unstaged_diff();
@@ -1820,12 +1890,15 @@ impl GitStore {
diff_state.get_or_create_index_text_buffer(index_text_file.clone(), cx)
});
cx.new(|cx| {
BufferDiff::new_with_base_text_buffer(
let mut diff = BufferDiff::new_with_base_text_buffer(
&text_snapshot,
base_text_buffer,
buffer_diff::DiffBaseKind::Index,
cx,
)
);
if let Some(project) = project.clone() {
diff.set_operations(Arc::new(GitDiffOperations { project, kind }));
}
diff
})
}
DiffKind::Staged => {
@@ -1848,12 +1921,15 @@ impl GitStore {
let index_text_snapshot = index_text_buffer.read(cx).text_snapshot();
staged_index_text_buffer = Some(index_text_buffer);
cx.new(|cx| {
BufferDiff::new_with_base_text_buffer(
let mut diff = BufferDiff::new_with_base_text_buffer(
&index_text_snapshot,
base_text_buffer,
buffer_diff::DiffBaseKind::Head,
cx,
)
);
if let Some(project) = project.clone() {
diff.set_operations(Arc::new(GitDiffOperations { project, kind }));
}
diff
})
}
DiffKind::Uncommitted => {
@@ -1861,12 +1937,15 @@ impl GitStore {
diff_state.get_or_create_head_text_buffer(cx)
});
cx.new(|cx| {
BufferDiff::new_with_base_text_buffer(
let mut diff = BufferDiff::new_with_base_text_buffer(
&text_snapshot,
base_text_buffer,
buffer_diff::DiffBaseKind::Head,
cx,
)
);
if let Some(project) = project.clone() {
diff.set_operations(Arc::new(GitDiffOperations { project, kind }));
}
diff
})
}
DiffKind::SinceOid(_) => {
@@ -1901,12 +1980,18 @@ impl GitStore {
let base_text_buffer =
diff_state.get_or_create_index_text_buffer(index_text_file, cx);
let unstaged_diff = cx.new(|cx| {
BufferDiff::new_with_base_text_buffer(
let mut diff = BufferDiff::new_with_base_text_buffer(
&text_snapshot,
base_text_buffer,
buffer_diff::DiffBaseKind::Index,
cx,
)
);
if let Some(project) = project.clone() {
diff.set_operations(Arc::new(GitDiffOperations {
project,
kind: DiffKind::Unstaged,
}));
}
diff
});
diff_state.unstaged_diff = Some(unstaged_diff.downgrade());
unstaged_diff
+3
View File
@@ -1305,6 +1305,7 @@ impl Project {
cx,
)
});
git_store.update(cx, |git_store, _| git_store.set_project(weak_self.clone()));
let task_store = cx.new(|cx| {
TaskStore::local(
@@ -1550,6 +1551,7 @@ impl Project {
cx,
)
});
git_store.update(cx, |git_store, _| git_store.set_project(weak_self.clone()));
let task_store = cx.new(|cx| {
TaskStore::remote(
@@ -1857,6 +1859,7 @@ impl Project {
let snippets = SnippetProvider::new(fs.clone(), BTreeSet::from_iter([]), cx);
let weak_self = cx.weak_entity();
git_store.update(cx, |git_store, _| git_store.set_project(weak_self.clone()));
let context_server_store = cx.new(|cx| {
ContextServerStore::local(worktree_store.clone(), Some(weak_self), false, cx)
});