feat: track checkout identity for pinned diff editability
This commit is contained in:
+156
-30
@@ -2588,7 +2588,7 @@ impl Editor {
|
||||
self.diff_display_options = (view_mode, split_axis);
|
||||
}
|
||||
|
||||
pub(crate) fn queue_working_refreshes(&mut self) {
|
||||
pub(crate) fn queue_checkout_refreshes(&mut self, follow_working: bool) {
|
||||
for index in 0..self.tab_order.len() {
|
||||
let tab = self.tab_order[index];
|
||||
let owner = if tab == self.tab_id {
|
||||
@@ -2599,7 +2599,7 @@ impl Editor {
|
||||
if let Some(owner) = owner {
|
||||
self.with_pane(owner, |editor| {
|
||||
if let Some(diff) = &mut editor.diff {
|
||||
diff.queue_working_refresh();
|
||||
diff.queue_checkout_refresh(follow_working);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -2638,7 +2638,19 @@ impl Editor {
|
||||
{
|
||||
Some(buffers)
|
||||
}
|
||||
Operation::RefreshWorking { buffers, .. } => Some(buffers),
|
||||
Operation::RefreshCheckout {
|
||||
follow_working: true,
|
||||
buffers,
|
||||
..
|
||||
} if self
|
||||
.diff
|
||||
.as_ref()
|
||||
.or(self.parked_diff.as_ref())
|
||||
.and_then(|diff| diff.after.as_ref())
|
||||
.is_some_and(|after| after.target == RevisionTarget::Working) =>
|
||||
{
|
||||
Some(buffers)
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
if let Some(buffers) = buffers {
|
||||
@@ -2694,6 +2706,25 @@ impl Editor {
|
||||
return self.with_pane(owner, |editor| editor.apply_diff_result(id, result));
|
||||
}
|
||||
self.outstanding_diff_request = None;
|
||||
if self.diff.as_ref().is_some_and(DiffState::refresh_pending)
|
||||
&& !matches!(&result, Ok(Outcome::CheckoutRefresh(None)))
|
||||
{
|
||||
self.finish_completion();
|
||||
self.snippet = None;
|
||||
self.clear_hover();
|
||||
if self.active.view.mode == Mode::Insert {
|
||||
self.finish_insert();
|
||||
self.leave_insert();
|
||||
}
|
||||
}
|
||||
let preserved_view = (self.diff.as_ref().is_some_and(|diff| {
|
||||
diff.refresh_pending()
|
||||
&& diff
|
||||
.after
|
||||
.as_ref()
|
||||
.is_some_and(|after| after.target != RevisionTarget::Working)
|
||||
}) && !matches!(&result, Ok(Outcome::CheckoutRefresh(None))))
|
||||
.then(|| self.active.view.inherit());
|
||||
let Some(diff) = &mut self.diff else {
|
||||
return false;
|
||||
};
|
||||
@@ -2701,15 +2732,10 @@ impl Editor {
|
||||
return false;
|
||||
}
|
||||
let refresh = diff.refresh_pending();
|
||||
let previous = diff.comparison_id;
|
||||
if !diff.apply_result(id, result) {
|
||||
return false;
|
||||
}
|
||||
let replaced = previous != diff.comparison_id;
|
||||
if refresh {
|
||||
if !replaced {
|
||||
return true;
|
||||
}
|
||||
self.pending_lsp_navigation = None;
|
||||
self.diff_bound = None;
|
||||
} else if let Some(files) = &mut diff.files {
|
||||
@@ -2719,7 +2745,34 @@ impl Editor {
|
||||
self.diff.as_ref().and_then(|diff| diff.comparison_id) != Some(role.0)
|
||||
});
|
||||
self.bind_diff_buffer();
|
||||
let pinned_refresh = preserved_view.is_some();
|
||||
if self.active.diff_snapshot.is_some()
|
||||
&& let Some(mut view) = preserved_view
|
||||
{
|
||||
view.clamp(&self.active.lines);
|
||||
self.active.view = view;
|
||||
}
|
||||
let reviewed = self.diff.as_mut().and_then(|diff| {
|
||||
(refresh
|
||||
&& diff
|
||||
.after
|
||||
.as_ref()
|
||||
.is_some_and(|after| after.target != RevisionTarget::Working))
|
||||
.then(|| std::mem::take(&mut diff.reviewed_files))
|
||||
});
|
||||
self.refresh_live_diff();
|
||||
if let Some(reviewed) = reviewed {
|
||||
self.diff
|
||||
.as_mut()
|
||||
.expect("comparison remains open")
|
||||
.reviewed_files = reviewed;
|
||||
}
|
||||
if pinned_refresh {
|
||||
self.diff
|
||||
.as_mut()
|
||||
.expect("comparison remains open")
|
||||
.follow_caret = false;
|
||||
}
|
||||
if refresh || obsolete_snapshot {
|
||||
self.prune_diff_snapshots();
|
||||
}
|
||||
@@ -2976,8 +3029,15 @@ impl Editor {
|
||||
diff.focus
|
||||
};
|
||||
let key = (comparison_id, diff.file, focus);
|
||||
let writable = focus == Focus::After && diff.after_editable();
|
||||
if self.diff_bound.is_some_and(|(id, file, side, buffer)| {
|
||||
(id, file, side) == key && buffer == self.active.id
|
||||
(id, file, side) == key
|
||||
&& buffer == self.active.id
|
||||
&& if writable {
|
||||
self.active.diff_snapshot.is_none() && diff.live_buffer == Some(self.active.id)
|
||||
} else {
|
||||
self.active.diff_snapshot == Some(key)
|
||||
}
|
||||
}) {
|
||||
return;
|
||||
}
|
||||
@@ -2996,7 +3056,6 @@ impl Editor {
|
||||
diff.corresponding_row(from, focus, target.active.row)
|
||||
.map(|row| (row, column))
|
||||
});
|
||||
let writable = focus == Focus::After && diff.after_editable();
|
||||
// Binding a comparison side is not a user departure from its palette.
|
||||
let palette = self.palette.take();
|
||||
if writable {
|
||||
@@ -3139,7 +3198,7 @@ impl Editor {
|
||||
}
|
||||
});
|
||||
if let Some(buffer) = buffer
|
||||
&& diff.working_after_available()
|
||||
&& diff.live_after_available()
|
||||
&& diff.live_revision != Some(buffer.text_revision)
|
||||
{
|
||||
diff.refresh_after(&buffer.lines, buffer.text_revision);
|
||||
@@ -3833,10 +3892,6 @@ impl Editor {
|
||||
return Err("Definition target is binary or non-UTF-8".into());
|
||||
}
|
||||
let writable = focus == Focus::After
|
||||
&& diff
|
||||
.after
|
||||
.as_ref()
|
||||
.is_some_and(|revision| revision.target == RevisionTarget::Working)
|
||||
&& comparison.editable_paths.contains(&file.path)
|
||||
&& file.status != crate::diff::FileStatus::Removed
|
||||
&& file
|
||||
@@ -3872,13 +3927,7 @@ impl Editor {
|
||||
let file = &comparison.files[file_index];
|
||||
let live = if writable {
|
||||
Some(target)
|
||||
} else if focus == Focus::Before
|
||||
&& diff
|
||||
.after
|
||||
.as_ref()
|
||||
.is_some_and(|revision| revision.target == RevisionTarget::Working)
|
||||
&& comparison.editable_paths.contains(&file.path)
|
||||
{
|
||||
} else if focus == Focus::Before && comparison.editable_paths.contains(&file.path) {
|
||||
let identity = path_identity(&comparison.root.join(&file.path)).ok();
|
||||
std::iter::once(&self.active)
|
||||
.chain(self.parked.values())
|
||||
@@ -12780,14 +12829,14 @@ mod tests {
|
||||
let visible_tab = editor.tab_id();
|
||||
let visible_pane = editor.pane_id();
|
||||
let cursor = editor.cursor();
|
||||
editor.queue_working_refreshes();
|
||||
editor.queue_checkout_refreshes(true);
|
||||
let (tab, request, operation) = editor.take_next_diff_request().unwrap();
|
||||
assert_eq!(tab, hidden_tab);
|
||||
assert!(
|
||||
matches!(operation, Operation::RefreshWorking { comparison_id, .. }
|
||||
matches!(operation, Operation::RefreshCheckout { comparison_id, .. }
|
||||
if comparison_id == old_comparison)
|
||||
);
|
||||
assert!(!editor.apply_diff_result(request, Ok(Outcome::WorkingRefresh(None))));
|
||||
assert!(!editor.apply_diff_result(request, Ok(Outcome::CheckoutRefresh(None))));
|
||||
assert_eq!(
|
||||
editor.tab_diff(hidden_tab).unwrap().comparison_id,
|
||||
Some(old_comparison)
|
||||
@@ -12797,12 +12846,12 @@ mod tests {
|
||||
assert_eq!(editor.buffer_id(), old_snapshot);
|
||||
assert_eq!(editor.cursor(), cursor);
|
||||
|
||||
editor.queue_working_refreshes();
|
||||
editor.queue_checkout_refreshes(true);
|
||||
let (_, request, _) = editor.take_next_diff_request().unwrap();
|
||||
let replacement = editor.with_pane(owner, |editor| {
|
||||
editor.diff.as_mut().unwrap().comparison.take().unwrap()
|
||||
});
|
||||
assert!(editor.apply_diff_result(request, Ok(Outcome::WorkingRefresh(Some(replacement)))));
|
||||
assert!(editor.apply_diff_result(request, Ok(Outcome::CheckoutRefresh(Some(replacement)))));
|
||||
assert_eq!(
|
||||
editor.tab_diff(hidden_tab).unwrap().comparison_id,
|
||||
Some(request)
|
||||
@@ -12816,7 +12865,7 @@ mod tests {
|
||||
.apply(vec![Effect::Insert("rejected".into())])
|
||||
.is_err()
|
||||
);
|
||||
assert!(!editor.apply_diff_result(request, Ok(Outcome::WorkingRefresh(None))));
|
||||
assert!(!editor.apply_diff_result(request, Ok(Outcome::CheckoutRefresh(None))));
|
||||
|
||||
editor.with_pane(owner, |editor| {
|
||||
assert_ne!(editor.buffer_id(), old_snapshot);
|
||||
@@ -12846,7 +12895,7 @@ mod tests {
|
||||
let mut target = lazy_lsp_target(&mut editor);
|
||||
let old_comparison = editor.diff().unwrap().comparison_id.unwrap();
|
||||
let virtual_root = editor.diff().unwrap().snapshot_roots[0].clone().unwrap();
|
||||
editor.queue_working_refreshes();
|
||||
editor.queue_checkout_refreshes(true);
|
||||
let (_, refresh, _) = editor.take_next_diff_request().unwrap();
|
||||
let loaded_before = target.before.clone();
|
||||
let loaded_after = target.after.clone();
|
||||
@@ -12878,7 +12927,7 @@ mod tests {
|
||||
let changed = request_kind != "noop_navigation";
|
||||
let outcome = if changed { Some(replacement) } else { None };
|
||||
assert_eq!(
|
||||
editor.apply_diff_result(refresh, Ok(Outcome::WorkingRefresh(outcome))),
|
||||
editor.apply_diff_result(refresh, Ok(Outcome::CheckoutRefresh(outcome))),
|
||||
changed
|
||||
);
|
||||
if changed {
|
||||
@@ -13832,6 +13881,83 @@ mod tests {
|
||||
assert!(editor.dirty());
|
||||
editor.handle(Input::Char('u'));
|
||||
assert_eq!(editor.lines(), ["beta"]);
|
||||
for refresh_error in [false, true] {
|
||||
let path = directory.path("file.txt");
|
||||
fs::write(&path, "original").unwrap();
|
||||
let mut editor = Editor::open(Some(path.clone()), vec![]).unwrap();
|
||||
historical_diff(
|
||||
&mut editor,
|
||||
&directory.0,
|
||||
vec!["before".into()],
|
||||
vec!["original".into()],
|
||||
);
|
||||
let endpoints = (
|
||||
editor.diff().unwrap().before.clone(),
|
||||
editor.diff().unwrap().after.clone(),
|
||||
);
|
||||
editor.queue_checkout_refreshes(false);
|
||||
let (_, request, _) = editor.take_next_diff_request().unwrap();
|
||||
let mut promoted = editor.diff.as_mut().unwrap().comparison.take().unwrap();
|
||||
promoted.editable_paths.insert("file.txt".into());
|
||||
editor.apply_diff_result(request, Ok(Outcome::CheckoutRefresh(Some(promoted))));
|
||||
editor.diff.as_mut().unwrap().focus = Focus::After;
|
||||
editor.bind_diff_buffer();
|
||||
assert!(editor.diff_editing());
|
||||
let live = editor.buffer_id();
|
||||
type_text(&mut editor, "ix");
|
||||
assert_eq!(editor.mode(), Mode::Insert);
|
||||
let comparison_id = editor.diff().unwrap().comparison_id;
|
||||
editor.queue_checkout_refreshes(false);
|
||||
let (_, request, _) = editor.take_next_diff_request().unwrap();
|
||||
if refresh_error {
|
||||
assert!(editor.apply_diff_result(request, Err("checkout unavailable".into())));
|
||||
assert_eq!(editor.diff().unwrap().comparison_id, comparison_id);
|
||||
} else {
|
||||
let mut demoted = editor.diff.as_mut().unwrap().comparison.take().unwrap();
|
||||
demoted.editable_paths.clear();
|
||||
demoted.files[0].after = vec!["original".into()];
|
||||
demoted.files[0].after_eof = false;
|
||||
demoted.files[0].hunks = crate::diff::line_hunks(
|
||||
&demoted.files[0].before,
|
||||
false,
|
||||
&demoted.files[0].after,
|
||||
false,
|
||||
);
|
||||
assert!(
|
||||
editor.apply_diff_result(request, Ok(Outcome::CheckoutRefresh(Some(demoted))),)
|
||||
);
|
||||
}
|
||||
assert!(!editor.diff_editable());
|
||||
assert_eq!(editor.lines(), ["original"]);
|
||||
assert_eq!(editor.buffer(live).unwrap().lines, ["xoriginal"]);
|
||||
assert!(editor.buffer(live).unwrap().dirty());
|
||||
assert_eq!(editor.buffer(live).unwrap().view.mode, Mode::Normal);
|
||||
assert!(
|
||||
editor
|
||||
.apply(vec![Effect::Insert("blocked".into())])
|
||||
.is_err()
|
||||
);
|
||||
editor.execute("w");
|
||||
assert_eq!(fs::read_to_string(&path).unwrap(), "original");
|
||||
editor.queue_checkout_refreshes(false);
|
||||
let (_, request, _) = editor.take_next_diff_request().unwrap();
|
||||
let mut promoted = editor.diff.as_mut().unwrap().comparison.take().unwrap();
|
||||
promoted.editable_paths.insert("file.txt".into());
|
||||
editor.apply_diff_result(request, Ok(Outcome::CheckoutRefresh(Some(promoted))));
|
||||
assert!(editor.diff_editing());
|
||||
assert!(editor.diff().unwrap().error.is_none());
|
||||
assert_eq!(editor.buffer_id(), live);
|
||||
assert_eq!(editor.lines(), ["xoriginal"]);
|
||||
assert_eq!(
|
||||
(
|
||||
editor.diff().unwrap().before.clone(),
|
||||
editor.diff().unwrap().after.clone(),
|
||||
),
|
||||
endpoints,
|
||||
);
|
||||
editor.handle(Input::Char('u'));
|
||||
assert_eq!(editor.lines(), ["original"]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user