Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions crates/fs/src/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1362,7 +1362,8 @@ impl Fs for RealFs {
let trashed_entry = self
.trash
.lock()
.remove(trash_id)
.get(trash_id)
.cloned()
.ok_or(TrashRestoreError::AlreadyRestored)?;

let restored_item_path = trashed_entry.original_parent.join(&trashed_entry.name);
Expand All @@ -1375,7 +1376,9 @@ impl Fs for RealFs {
tx.send(res)
})
.expect("The OS can spawn a threads");

rx.await.expect("Restore all never panics")?;
self.trash.lock().remove(trash_id);
Ok(restored_item_path)
}
}
Expand Down Expand Up @@ -3346,7 +3349,7 @@ impl Fs for FakeFs {
async fn restore(&self, trash_id: TrashId) -> Result<PathBuf, TrashRestoreError> {
let mut state = self.state.lock();

let Some((trashed_entry, fake_entry)) = state.trash.lock().remove(trash_id) else {
let Some((trashed_entry, fake_entry)) = state.trash.lock().get(trash_id).cloned() else {
return Err(TrashRestoreError::AlreadyRestored);
};

Expand All @@ -3366,6 +3369,7 @@ impl Fs for FakeFs {

match result {
Ok(_) => {
state.trash.lock().remove(trash_id);
state.emit_event([(path.clone(), Some(PathEventKind::Created))]);
Ok(path)
}
Expand Down
27 changes: 27 additions & 0 deletions crates/fs/tests/integration/fs_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1126,3 +1126,30 @@ async fn test_realfs_watch_stress_reports_missed_paths(
missed_paths.len()
);
}

#[gpui::test]
async fn restore_can_be_retried_after_collision(cx: &mut TestAppContext) {
let fs = FakeFs::new(cx.background_executor.clone());
let path = path!("/root/a.txt");
let remove_options = RemoveOptions::default();
fs.insert_tree(path!("/root"), json!({ "a.txt": "original"}))
.await;

// We'll first trash the `a.txt` file so we can hold onto its `TrashId`,
// allowing us to later attempt restoring it again, ensuring that it didn't
// get removed from the trash state, even if restoring failed.
let trash_id = fs.trash(path.as_ref(), remove_options).await.unwrap();

fs.insert_file(path, "conflicting".into()).await;
let err = fs.restore(trash_id).await.unwrap_err();
assert!(matches!(err, TrashRestoreError::Collision { .. }));

fs.remove_file(path.as_ref(), remove_options).await.unwrap();
let restored_path = fs.restore(trash_id).await.unwrap();
assert_eq!(fs.load(restored_path.as_path()).await.unwrap(), "original");

assert!(matches!(
fs.restore(trash_id).await.unwrap_err(),
TrashRestoreError::AlreadyRestored
));
}
24 changes: 13 additions & 11 deletions crates/project_panel/src/project_panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4822,18 +4822,20 @@ impl ProjectPanel {
}
// update selection
if let Some(entry_id) = last_succeed {
project_panel.update_in(cx, |project_panel, window, cx| {
project_panel.selection = Some(SelectedEntry {
worktree_id,
entry_id,
});
// if only one entry was dragged and it was disambiguated, open the rename editor
if item_count == 1 && disambiguation_range.is_some() {
project_panel.rename_impl(disambiguation_range, window, cx);
}
project_panel
.update_in(cx, |project_panel, window, cx| {
project_panel.selection = Some(SelectedEntry {
worktree_id,
entry_id,
});
// if only one entry was dragged and it was disambiguated, open the rename editor
if item_count == 1 && disambiguation_range.is_some() {
project_panel.rename_impl(disambiguation_range, window, cx);
}

project_panel.undo_manager.record(changes)
})??;
project_panel.undo_manager.record(changes)
})?
.log_err();
}

std::result::Result::Ok::<(), anyhow::Error>(())
Expand Down
Loading