Cargo.lock 🔗
@@ -12570,6 +12570,7 @@ dependencies = [
"gpui",
"language",
"menu",
+ "notifications",
"pretty_assertions",
"project",
"rayon",
Korbin de Man and cameron created
Co-authored-by: cameron <cameron.studdstreet@gmail.com>
Cargo.lock | 1
crates/project_panel/Cargo.toml | 1
crates/project_panel/src/project_panel.rs | 115 +++++++++++++++++++++++++
3 files changed, 117 insertions(+)
@@ -12570,6 +12570,7 @@ dependencies = [
"gpui",
"language",
"menu",
+ "notifications",
"pretty_assertions",
"project",
"rayon",
@@ -45,6 +45,7 @@ workspace.workspace = true
language.workspace = true
zed_actions.workspace = true
telemetry.workspace = true
+notifications.workspace = true
[dev-dependencies]
client = { workspace = true, features = ["test-support"] }
@@ -29,6 +29,7 @@ use gpui::{
};
use language::DiagnosticSeverity;
use menu::{Confirm, SelectFirst, SelectLast, SelectNext, SelectPrevious};
+use notifications::status_toast::{StatusToast, ToastIcon};
use project::{
Entry, EntryKind, Fs, GitEntry, GitEntryRef, GitTraversal, Project, ProjectEntryId,
ProjectPath, Worktree, WorktreeId,
@@ -1140,6 +1141,12 @@ impl ProjectPanel {
"Copy Relative Path",
Box::new(zed_actions::workspace::CopyRelativePath),
)
+ .when(!is_dir && self.has_git_changes(entry_id), |menu| {
+ menu.separator().action(
+ "Restore File",
+ Box::new(git::RestoreFile { skip_prompt: false }),
+ )
+ })
.when(has_git_repo, |menu| {
menu.separator()
.action("View File History", Box::new(git::FileHistory))
@@ -1180,6 +1187,19 @@ impl ProjectPanel {
cx.notify();
}
+ fn has_git_changes(&self, entry_id: ProjectEntryId) -> bool {
+ for visible in &self.state.visible_entries {
+ if let Some(git_entry) = visible.entries.iter().find(|e| e.id == entry_id) {
+ let total_modified =
+ git_entry.git_summary.index.modified + git_entry.git_summary.worktree.modified;
+ let total_deleted =
+ git_entry.git_summary.index.deleted + git_entry.git_summary.worktree.deleted;
+ return total_modified > 0 || total_deleted > 0;
+ }
+ }
+ false
+ }
+
fn is_unfoldable(&self, entry: &Entry, worktree: &Worktree) -> bool {
if !entry.is_dir() || self.state.unfolded_dir_ids.contains(&entry.id) {
return false;
@@ -2041,6 +2061,100 @@ impl ProjectPanel {
self.remove(false, action.skip_prompt, window, cx);
}
+ fn restore_file(
+ &mut self,
+ action: &git::RestoreFile,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) {
+ maybe!({
+ let selection = self.state.selection?;
+ let project = self.project.read(cx);
+
+ let (_worktree, entry) = self.selected_sub_entry(cx)?;
+ if entry.is_dir() {
+ return None;
+ }
+
+ let project_path = project.path_for_entry(selection.entry_id, cx)?;
+
+ let git_store = project.git_store();
+ let (repository, repo_path) = git_store
+ .read(cx)
+ .repository_and_path_for_project_path(&project_path, cx)?;
+
+ let snapshot = repository.read(cx).snapshot();
+ let status = snapshot.status_for_path(&repo_path)?;
+ if !status.status.is_modified() && !status.status.is_deleted() {
+ return None;
+ }
+
+ let file_name = entry.path.file_name()?.to_string();
+
+ let answer = if !action.skip_prompt {
+ let prompt = format!("Discard changes to {}?", file_name);
+ Some(window.prompt(PromptLevel::Info, &prompt, None, &["Restore", "Cancel"], cx))
+ } else {
+ None
+ };
+
+ cx.spawn_in(window, async move |panel, cx| {
+ if let Some(answer) = answer
+ && answer.await != Ok(0)
+ {
+ return anyhow::Ok(());
+ }
+
+ let task = panel.update(cx, |_panel, cx| {
+ repository.update(cx, |repo, cx| {
+ repo.checkout_files("HEAD", vec![repo_path], cx)
+ })
+ })?;
+
+ if let Err(e) = task.await {
+ panel
+ .update(cx, |panel, cx| {
+ let message = format!("Failed to restore {}: {}", file_name, e);
+ let toast = StatusToast::new(message, cx, |this, _| {
+ this.icon(ToastIcon::new(IconName::XCircle).color(Color::Error))
+ .dismiss_button(true)
+ });
+ panel
+ .workspace
+ .update(cx, |workspace, cx| {
+ workspace.toggle_status_toast(toast, cx);
+ })
+ .ok();
+ })
+ .ok();
+ }
+
+ panel
+ .update(cx, |panel, cx| {
+ panel.project.update(cx, |project, cx| {
+ if let Some(buffer_id) = project
+ .buffer_store()
+ .read(cx)
+ .buffer_id_for_project_path(&project_path)
+ {
+ if let Some(buffer) = project.buffer_for_id(*buffer_id, cx) {
+ buffer.update(cx, |buffer, cx| {
+ let _ = buffer.reload(cx);
+ });
+ }
+ }
+ })
+ })
+ .ok();
+
+ anyhow::Ok(())
+ })
+ .detach_and_log_err(cx);
+
+ Some(())
+ });
+ }
+
fn remove(
&mut self,
trash: bool,
@@ -5631,6 +5745,7 @@ impl Render for ProjectPanel {
.on_action(cx.listener(Self::copy))
.on_action(cx.listener(Self::paste))
.on_action(cx.listener(Self::duplicate))
+ .on_action(cx.listener(Self::restore_file))
.when(!project.is_remote(), |el| {
el.on_action(cx.listener(Self::trash))
})