Detailed changes
@@ -306,6 +306,10 @@ impl Companion {
}
}
+ pub(crate) fn lhs_to_rhs_buffer(&self, lhs_buffer_id: BufferId) -> Option<BufferId> {
+ self.lhs_buffer_to_rhs_buffer.get(&lhs_buffer_id).copied()
+ }
+
pub(crate) fn add_buffer_mapping(&mut self, lhs_buffer: BufferId, rhs_buffer: BufferId) {
self.lhs_buffer_to_rhs_buffer.insert(lhs_buffer, rhs_buffer);
self.rhs_buffer_to_lhs_buffer.insert(rhs_buffer, lhs_buffer);
@@ -1176,6 +1176,7 @@ pub struct Editor {
offset_content: bool,
disable_expand_excerpt_buttons: bool,
delegate_expand_excerpts: bool,
+ delegate_stage_and_restore: bool,
show_line_numbers: Option<bool>,
use_relative_line_numbers: Option<bool>,
show_git_diff_gutter: Option<bool>,
@@ -2374,6 +2375,7 @@ impl Editor {
use_relative_line_numbers: None,
disable_expand_excerpt_buttons: !full_mode,
delegate_expand_excerpts: false,
+ delegate_stage_and_restore: false,
show_git_diff_gutter: None,
show_code_actions: None,
show_runnables: None,
@@ -11418,12 +11420,25 @@ impl Editor {
window: &mut Window,
cx: &mut Context<Editor>,
) {
+ if self.delegate_stage_and_restore {
+ let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
+ if !hunks.is_empty() {
+ cx.emit(EditorEvent::RestoreRequested { hunks });
+ }
+ return;
+ }
+ let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
+ self.transact(window, cx, |editor, window, cx| {
+ editor.restore_diff_hunks(hunks, cx);
+ editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
+ selections.refresh()
+ });
+ });
+ }
+
+ pub(crate) fn restore_diff_hunks(&self, hunks: Vec<MultiBufferDiffHunk>, cx: &mut App) {
let mut revert_changes = HashMap::default();
- let chunk_by = self
- .snapshot(window, cx)
- .hunks_for_ranges(ranges)
- .into_iter()
- .chunk_by(|hunk| hunk.buffer_id);
+ let chunk_by = hunks.into_iter().chunk_by(|hunk| hunk.buffer_id);
for (buffer_id, hunks) in &chunk_by {
let hunks = hunks.collect::<Vec<_>>();
for hunk in &hunks {
@@ -11431,10 +11446,21 @@ impl Editor {
}
self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
}
- drop(chunk_by);
if !revert_changes.is_empty() {
- self.transact(window, cx, |editor, window, cx| {
- editor.restore(revert_changes, window, cx);
+ self.buffer().update(cx, |multi_buffer, cx| {
+ for (buffer_id, changes) in revert_changes {
+ if let Some(buffer) = multi_buffer.buffer(buffer_id) {
+ buffer.update(cx, |buffer, cx| {
+ buffer.edit(
+ changes
+ .into_iter()
+ .map(|(range, text)| (range, text.to_string())),
+ None,
+ cx,
+ );
+ });
+ }
+ }
});
}
}
@@ -20344,6 +20370,14 @@ impl Editor {
ranges: Vec<Range<Anchor>>,
cx: &mut Context<Self>,
) {
+ if self.delegate_stage_and_restore {
+ let snapshot = self.buffer.read(cx).snapshot(cx);
+ let hunks: Vec<_> = self.diff_hunks_in_ranges(&ranges, &snapshot).collect();
+ if !hunks.is_empty() {
+ cx.emit(EditorEvent::StageOrUnstageRequested { stage, hunks });
+ }
+ return;
+ }
let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
cx.spawn(async move |this, cx| {
task.await?;
@@ -20439,7 +20473,7 @@ impl Editor {
}
}
- fn do_stage_or_unstage(
+ pub(crate) fn do_stage_or_unstage(
&self,
stage: bool,
buffer_id: BufferId,
@@ -21111,6 +21145,10 @@ impl Editor {
self.delegate_expand_excerpts = delegate;
}
+ pub fn set_delegate_stage_and_restore(&mut self, delegate: bool) {
+ self.delegate_stage_and_restore = delegate;
+ }
+
pub fn set_on_local_selections_changed(
&mut self,
callback: Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
@@ -27360,6 +27398,13 @@ pub enum EditorEvent {
lines: u32,
direction: ExpandExcerptDirection,
},
+ StageOrUnstageRequested {
+ stage: bool,
+ hunks: Vec<MultiBufferDiffHunk>,
+ },
+ RestoreRequested {
+ hunks: Vec<MultiBufferDiffHunk>,
+ },
BufferEdited,
Edited {
transaction_id: clock::Lamport,
@@ -10716,24 +10716,25 @@ impl Element for EditorElement {
let mode = snapshot.mode.clone();
- let (diff_hunk_controls, diff_hunk_control_bounds) = if is_read_only {
- (vec![], vec![])
- } else {
- self.layout_diff_hunk_controls(
- start_row..end_row,
- &row_infos,
- &text_hitbox,
- newest_selection_head,
- line_height,
- right_margin,
- scroll_pixel_position,
- &display_hunks,
- &highlighted_rows,
- self.editor.clone(),
- window,
- cx,
- )
- };
+ let (diff_hunk_controls, diff_hunk_control_bounds) =
+ if is_read_only && !self.editor.read(cx).delegate_stage_and_restore {
+ (vec![], vec![])
+ } else {
+ self.layout_diff_hunk_controls(
+ start_row..end_row,
+ &row_infos,
+ &text_hitbox,
+ newest_selection_head,
+ line_height,
+ right_margin,
+ scroll_pixel_position,
+ &display_hunks,
+ &highlighted_rows,
+ self.editor.clone(),
+ window,
+ cx,
+ )
+ };
let position_map = Rc::new(PositionMap {
size: bounds.size,
@@ -4,10 +4,11 @@ use buffer_diff::{BufferDiff, BufferDiffSnapshot};
use collections::HashMap;
use feature_flags::{FeatureFlag, FeatureFlagAppExt as _};
use gpui::{Action, AppContext as _, Entity, EventEmitter, Focusable, Subscription, WeakEntity};
+use itertools::Itertools;
use language::{Buffer, Capability};
use multi_buffer::{
- Anchor, ExcerptId, ExcerptRange, ExpandExcerptDirection, MultiBuffer, MultiBufferPoint,
- MultiBufferSnapshot, PathKey,
+ Anchor, ExcerptId, ExcerptRange, ExpandExcerptDirection, MultiBuffer, MultiBufferDiffHunk,
+ MultiBufferPoint, MultiBufferSnapshot, PathKey,
};
use project::Project;
use rope::Point;
@@ -60,6 +61,34 @@ pub(crate) fn convert_rhs_rows_to_lhs(
)
}
+fn translate_lhs_hunks_to_rhs(
+ lhs_hunks: &[MultiBufferDiffHunk],
+ splittable: &SplittableEditor,
+ cx: &App,
+) -> Vec<MultiBufferDiffHunk> {
+ let rhs_display_map = splittable.rhs_editor.read(cx).display_map.read(cx);
+ let Some(companion) = rhs_display_map.companion() else {
+ return vec![];
+ };
+ let companion = companion.read(cx);
+ let rhs_snapshot = splittable.rhs_multibuffer.read(cx).snapshot(cx);
+ let rhs_hunks: Vec<MultiBufferDiffHunk> = rhs_snapshot.diff_hunks().collect();
+
+ let mut translated = Vec::new();
+ for lhs_hunk in lhs_hunks {
+ let Some(rhs_buffer_id) = companion.lhs_to_rhs_buffer(lhs_hunk.buffer_id) else {
+ continue;
+ };
+ if let Some(rhs_hunk) = rhs_hunks.iter().find(|rhs_hunk| {
+ rhs_hunk.buffer_id == rhs_buffer_id
+ && rhs_hunk.diff_base_byte_range == lhs_hunk.diff_base_byte_range
+ }) {
+ translated.push(rhs_hunk.clone());
+ }
+ }
+ translated
+}
+
fn patches_for_range<F>(
excerpt_map: &HashMap<ExcerptId, ExcerptId>,
source_snapshot: &MultiBufferSnapshot,
@@ -371,6 +400,7 @@ impl SplittableEditor {
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_delegate_stage_and_restore(true);
editor.set_show_vertical_scrollbar(false, cx);
editor.set_minimap_visibility(crate::MinimapVisibility::Disabled, window, cx);
editor
@@ -396,6 +426,30 @@ impl SplittableEditor {
this.expand_excerpts(rhs_ids.into_iter(), *lines, *direction, cx);
}
}
+ EditorEvent::StageOrUnstageRequested { stage, hunks } => {
+ if this.lhs.is_some() {
+ let translated = translate_lhs_hunks_to_rhs(hunks, this, cx);
+ if !translated.is_empty() {
+ let stage = *stage;
+ this.rhs_editor.update(cx, |editor, cx| {
+ let chunk_by = translated.into_iter().chunk_by(|h| h.buffer_id);
+ for (buffer_id, hunks) in &chunk_by {
+ editor.do_stage_or_unstage(stage, buffer_id, hunks, cx);
+ }
+ });
+ }
+ }
+ }
+ EditorEvent::RestoreRequested { hunks } => {
+ if this.lhs.is_some() {
+ let translated = translate_lhs_hunks_to_rhs(hunks, this, cx);
+ if !translated.is_empty() {
+ this.rhs_editor.update(cx, |editor, cx| {
+ editor.restore_diff_hunks(translated, cx);
+ });
+ }
+ }
+ }
EditorEvent::SelectionsChanged { .. } => {
if let Some(lhs) = &mut this.lhs {
lhs.has_latest_selection = true;