feat: follow forward working-checkout changes in diffs
This commit is contained in:
+262
-12
@@ -2542,8 +2542,13 @@ impl Editor {
|
||||
self.diff.as_ref().or(self.parked_diff.as_ref())
|
||||
}
|
||||
|
||||
pub(crate) fn diff_root(&mut self) -> PathBuf {
|
||||
let owner = self.diff_owner.unwrap_or(self.pane_id);
|
||||
pub(crate) fn diff_root(&mut self, tab: usize) -> PathBuf {
|
||||
let owner = if tab == self.tab_id {
|
||||
self.diff_owner.unwrap_or(self.pane_id)
|
||||
} else {
|
||||
let tab = &self.tabs[&tab];
|
||||
tab.diff_owner.unwrap_or(tab.pane_id)
|
||||
};
|
||||
self.with_pane(owner, |editor| {
|
||||
if let Some(root) = &editor.active.snapshot_root {
|
||||
return root.clone();
|
||||
@@ -2575,15 +2580,60 @@ impl Editor {
|
||||
self.diff_display_options = (view_mode, split_axis);
|
||||
}
|
||||
|
||||
pub(crate) fn queue_working_refreshes(&mut self) {
|
||||
for index in 0..self.tab_order.len() {
|
||||
let tab = self.tab_order[index];
|
||||
let owner = if tab == self.tab_id {
|
||||
self.diff_owner
|
||||
} else {
|
||||
self.tabs.get(&tab).and_then(|tab| tab.diff_owner)
|
||||
};
|
||||
if let Some(owner) = owner {
|
||||
self.with_pane(owner, |editor| {
|
||||
if let Some(diff) = &mut editor.diff {
|
||||
diff.queue_working_refresh();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn take_next_diff_request(&mut self) -> Option<(usize, u64, Operation)> {
|
||||
let tab = std::iter::once(self.tab_id)
|
||||
.chain(
|
||||
self.tab_order
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|tab| *tab != self.tab_id),
|
||||
)
|
||||
.find(|tab| self.tab_diff(*tab).is_some_and(DiffState::request_ready))?;
|
||||
let owner = if tab == self.tab_id {
|
||||
self.diff_owner
|
||||
} else {
|
||||
self.tabs.get(&tab)?.diff_owner
|
||||
}?;
|
||||
self.with_pane(owner, |editor| {
|
||||
let (id, operation) = editor.take_diff_request()?;
|
||||
Some((tab, id, operation))
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn take_diff_request(&mut self) -> Option<(u64, Operation)> {
|
||||
let mut operation = self
|
||||
.diff
|
||||
.as_mut()
|
||||
.or(self.parked_diff.as_mut())?
|
||||
.take_request()?;
|
||||
if let Operation::Compare { after, buffers, .. } = &mut operation
|
||||
&& after.target == RevisionTarget::Working
|
||||
{
|
||||
let buffers = match &mut operation {
|
||||
Operation::Compare { after, buffers, .. }
|
||||
if after.target == RevisionTarget::Working =>
|
||||
{
|
||||
Some(buffers)
|
||||
}
|
||||
Operation::RefreshWorking { buffers, .. } => Some(buffers),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(buffers) = buffers {
|
||||
*buffers = std::iter::once(&self.active)
|
||||
.chain(self.parked.values())
|
||||
.filter(|buffer| buffer.dirty())
|
||||
@@ -2639,15 +2689,32 @@ impl Editor {
|
||||
let Some(diff) = &mut self.diff else {
|
||||
return false;
|
||||
};
|
||||
if diff.request_queued() {
|
||||
if diff.request_queued() && !diff.refresh_pending() {
|
||||
return false;
|
||||
}
|
||||
diff.apply_result(id, result);
|
||||
if let Some(files) = &mut diff.files {
|
||||
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 {
|
||||
files.set_view(self.file_explorer_view);
|
||||
}
|
||||
let obsolete_snapshot = self.active.diff_snapshot.is_some_and(|role| {
|
||||
self.diff.as_ref().and_then(|diff| diff.comparison_id) != Some(role.0)
|
||||
});
|
||||
self.bind_diff_buffer();
|
||||
self.refresh_live_diff();
|
||||
if refresh || obsolete_snapshot {
|
||||
self.prune_diff_snapshots();
|
||||
}
|
||||
let completed_navigation = self
|
||||
.pending_lsp_navigation
|
||||
.as_ref()
|
||||
@@ -2792,6 +2859,17 @@ impl Editor {
|
||||
.is_some_and(|bound| bound.3 != self.active.id)
|
||||
{
|
||||
if let Some(role) = self.active.diff_snapshot {
|
||||
if self
|
||||
.diff
|
||||
.as_ref()
|
||||
.is_none_or(|diff| diff.comparison_id != Some(role.0))
|
||||
{
|
||||
self.diff_bound = None;
|
||||
self.diff = None;
|
||||
self.diff_owner = None;
|
||||
self.outstanding_diff_request = None;
|
||||
return;
|
||||
}
|
||||
self.diff_bound = Some((role.0, role.1, role.2, self.active.id));
|
||||
if let Some(diff) = &mut self.diff {
|
||||
if diff.file != role.1
|
||||
@@ -2821,9 +2899,18 @@ impl Editor {
|
||||
{
|
||||
self.activate(buffer);
|
||||
}
|
||||
self.prune_diff_snapshots();
|
||||
self.diff_bound = None;
|
||||
self.diff_return_buffer = None;
|
||||
}
|
||||
|
||||
fn prune_diff_snapshots(&mut self) {
|
||||
self.parked.retain(|id, buffer| {
|
||||
buffer.diff_snapshot.is_none()
|
||||
|| self.panes.values().any(|pane| pane.buffer_id == *id)
|
||||
|| self
|
||||
.panes
|
||||
.iter()
|
||||
.any(|(pane_id, pane)| *pane_id != self.pane_id && pane.buffer_id == *id)
|
||||
|| buffer.diff_snapshot.is_some_and(|role| {
|
||||
self.tabs.values().any(|tab| {
|
||||
tab.diff()
|
||||
@@ -2835,8 +2922,6 @@ impl Editor {
|
||||
pane.views
|
||||
.retain(|id, _| *id == self.active.id || self.parked.contains_key(id));
|
||||
}
|
||||
self.diff_bound = None;
|
||||
self.diff_return_buffer = None;
|
||||
}
|
||||
|
||||
fn bind_diff_buffer(&mut self) {
|
||||
@@ -12638,6 +12723,171 @@ mod tests {
|
||||
assert!(editor.diff_interacting());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn working_refresh_routes_hidden_owners_and_keeps_obsolete_shared_snapshots_readonly() {
|
||||
let directory = Directory::new();
|
||||
fs::write(directory.path("file.txt"), "working").unwrap();
|
||||
let mut editor = editor();
|
||||
working_diff(&mut editor, &directory.0, &["file.txt"]);
|
||||
editor.diff.as_mut().unwrap().focus = Focus::Before;
|
||||
editor.bind_diff_buffer();
|
||||
let old_snapshot = editor.buffer_id();
|
||||
let old_comparison = editor.diff().unwrap().comparison_id.unwrap();
|
||||
let owner = editor.pane_id();
|
||||
let hidden_tab = editor.tab_id();
|
||||
editor.execute("tab.new");
|
||||
let visible_tab = editor.tab_id();
|
||||
let visible_pane = editor.pane_id();
|
||||
let cursor = editor.cursor();
|
||||
editor.queue_working_refreshes();
|
||||
let (tab, request, operation) = editor.take_next_diff_request().unwrap();
|
||||
assert_eq!(tab, hidden_tab);
|
||||
assert!(
|
||||
matches!(operation, Operation::RefreshWorking { comparison_id, .. }
|
||||
if comparison_id == old_comparison)
|
||||
);
|
||||
assert!(!editor.apply_diff_result(request, Ok(Outcome::WorkingRefresh(None))));
|
||||
assert_eq!(
|
||||
editor.tab_diff(hidden_tab).unwrap().comparison_id,
|
||||
Some(old_comparison)
|
||||
);
|
||||
assert_eq!(editor.tab_id(), visible_tab);
|
||||
assert_eq!(editor.pane_id(), visible_pane);
|
||||
assert_eq!(editor.buffer_id(), old_snapshot);
|
||||
assert_eq!(editor.cursor(), cursor);
|
||||
|
||||
editor.queue_working_refreshes();
|
||||
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_eq!(
|
||||
editor.tab_diff(hidden_tab).unwrap().comparison_id,
|
||||
Some(request)
|
||||
);
|
||||
assert_eq!(editor.tab_id(), visible_tab);
|
||||
assert_eq!(editor.buffer_id(), old_snapshot);
|
||||
assert_eq!(editor.cursor(), cursor);
|
||||
assert!(editor.active.diff_snapshot.is_some());
|
||||
assert!(
|
||||
editor
|
||||
.apply(vec![Effect::Insert("rejected".into())])
|
||||
.is_err()
|
||||
);
|
||||
assert!(!editor.apply_diff_result(request, Ok(Outcome::WorkingRefresh(None))));
|
||||
|
||||
editor.with_pane(owner, |editor| {
|
||||
assert_ne!(editor.buffer_id(), old_snapshot);
|
||||
editor.switch_buffer(old_snapshot);
|
||||
editor.leave_diff_on_navigation();
|
||||
assert!(editor.diff().is_none());
|
||||
assert!(editor.active.diff_snapshot.is_some());
|
||||
assert!(editor.mutation_blocked());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completed_working_refresh_serializes_lazy_selection_and_invalidates_pending_navigation() {
|
||||
for request_kind in [
|
||||
"selection",
|
||||
"after_selection",
|
||||
"navigation",
|
||||
"noop_navigation",
|
||||
] {
|
||||
let directory = Directory::new();
|
||||
fs::write(directory.path("file.txt"), "working").unwrap();
|
||||
let mut editor = editor();
|
||||
working_diff(&mut editor, &directory.0, &["file.txt"]);
|
||||
editor.diff.as_mut().unwrap().focus = Focus::Before;
|
||||
editor.bind_diff_buffer();
|
||||
let old_snapshot = editor.buffer_id();
|
||||
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();
|
||||
let (_, refresh, _) = editor.take_next_diff_request().unwrap();
|
||||
let loaded_before = target.before.clone();
|
||||
let loaded_after = target.after.clone();
|
||||
target.loaded = false;
|
||||
let replacement = crate::diff::Comparison {
|
||||
root: directory.0.clone(),
|
||||
before: "base".into(),
|
||||
after: "ahead".into(),
|
||||
editable_paths: Default::default(),
|
||||
files: vec![target],
|
||||
};
|
||||
let selection = request_kind.ends_with("selection");
|
||||
let focus = if request_kind == "after_selection" {
|
||||
Focus::After
|
||||
} else {
|
||||
Focus::Before
|
||||
};
|
||||
if selection {
|
||||
assert!(editor.diff.as_mut().unwrap().select_file_index(1));
|
||||
editor.diff.as_mut().unwrap().focus = focus;
|
||||
} else {
|
||||
editor
|
||||
.navigate_lsp_to(virtual_root.join("nested/old.txt"), lsp_point(1, 4), None)
|
||||
.unwrap();
|
||||
assert!(editor.pending_lsp_navigation.is_some());
|
||||
assert_eq!(editor.diff().unwrap().file, 0);
|
||||
}
|
||||
assert!(editor.take_next_diff_request().is_none());
|
||||
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))),
|
||||
changed
|
||||
);
|
||||
if changed {
|
||||
assert!(editor.pending_lsp_navigation.is_none());
|
||||
assert_eq!(editor.diff().unwrap().comparison_id, Some(refresh));
|
||||
assert_eq!(editor.diff().unwrap().file, 0);
|
||||
assert_eq!(editor.diff().unwrap().focus, focus);
|
||||
} else {
|
||||
assert!(editor.pending_lsp_navigation.is_some());
|
||||
assert_eq!(editor.diff().unwrap().comparison_id, Some(old_comparison));
|
||||
}
|
||||
let (_, load, operation) = editor.take_next_diff_request().unwrap();
|
||||
assert!(
|
||||
matches!(operation, Operation::LoadFile { comparison_id, file_index }
|
||||
if comparison_id == if changed { refresh } else { old_comparison }
|
||||
&& file_index == if changed { 0 } else { 1 })
|
||||
);
|
||||
if !changed {
|
||||
assert_eq!(
|
||||
editor.pending_lsp_navigation.as_ref().unwrap().request,
|
||||
Some(load)
|
||||
);
|
||||
}
|
||||
if changed {
|
||||
let file = crate::diff::ChangedFile {
|
||||
path: "nested/new.txt".into(),
|
||||
old_path: Some("nested/old.txt".into()),
|
||||
status: crate::diff::FileStatus::Renamed,
|
||||
loaded: true,
|
||||
hunks: crate::diff::line_hunks(&loaded_before, false, &loaded_after, false),
|
||||
before: loaded_before,
|
||||
after: loaded_after,
|
||||
before_eof: false,
|
||||
after_eof: false,
|
||||
notice: None,
|
||||
};
|
||||
assert!(editor.apply_diff_result(
|
||||
load,
|
||||
Ok(Outcome::File {
|
||||
comparison_id: refresh,
|
||||
file_index: 0,
|
||||
file,
|
||||
})
|
||||
));
|
||||
assert_eq!(editor.diff().unwrap().focus, focus);
|
||||
assert!(!editor.parked.contains_key(&old_snapshot));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn lsp_point(row: usize, column: usize) -> Utf16Range {
|
||||
Utf16Range {
|
||||
start: (row, column),
|
||||
@@ -13010,7 +13260,7 @@ mod tests {
|
||||
editor.interaction_document_root(editor.buffer_id()),
|
||||
directory.0.as_path()
|
||||
);
|
||||
assert_eq!(editor.diff_root(), directory.0);
|
||||
assert_eq!(editor.diff_root(editor.tab_id()), directory.0);
|
||||
editor.handle(Input::Char('\t'));
|
||||
assert_eq!(editor.buffer_id(), live_id);
|
||||
assert!(editor.diff_editing());
|
||||
|
||||
Reference in New Issue
Block a user