@@ -15,7 +15,7 @@ use workspace::{ModalView, Workspace, pane};
use crate::branch_picker::{self, BranchList, DeleteBranch, FilterRemotes};
use crate::stash_picker::{self, DropStashItem, ShowStashItem, StashList};
use crate::worktree_picker::{
- self, WorktreeFromDefault, WorktreeFromDefaultOnWindow, WorktreeList,
+ self, DeleteWorktree, WorktreeFromDefault, WorktreeFromDefaultOnWindow, WorktreeList,
};
actions!(
@@ -408,6 +408,19 @@ impl GitPicker {
}
}
+ fn handle_worktree_delete(
+ &mut self,
+ _: &DeleteWorktree,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) {
+ if let Some(worktree_list) = &self.worktree_list {
+ worktree_list.update(cx, |list, cx| {
+ list.handle_delete(&DeleteWorktree, window, cx);
+ });
+ }
+ }
+
fn handle_drop_stash(
&mut self,
_: &DropStashItem,
@@ -524,6 +537,7 @@ impl Render for GitPicker {
.when(self.tab == GitPickerTab::Worktrees, |el| {
el.on_action(cx.listener(Self::handle_worktree_from_default))
.on_action(cx.listener(Self::handle_worktree_from_default_on_window))
+ .on_action(cx.listener(Self::handle_worktree_delete))
})
.when(self.tab == GitPickerTab::Stash, |el| {
el.on_action(cx.listener(Self::handle_drop_stash))
@@ -22,7 +22,16 @@ use ui::{HighlightedLabel, KeyBinding, ListItem, ListItemSpacing, prelude::*};
use util::ResultExt;
use workspace::{ModalView, MultiWorkspace, Workspace, notifications::DetachAndPromptErr};
-actions!(git, [WorktreeFromDefault, WorktreeFromDefaultOnWindow]);
+use crate::git_panel::show_error_toast;
+
+actions!(
+ git,
+ [
+ WorktreeFromDefault,
+ WorktreeFromDefaultOnWindow,
+ DeleteWorktree
+ ]
+);
pub fn open(
workspace: &mut Workspace,
@@ -181,6 +190,19 @@ impl WorktreeList {
);
})
}
+
+ pub fn handle_delete(
+ &mut self,
+ _: &DeleteWorktree,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) {
+ self.picker.update(cx, |picker, cx| {
+ picker
+ .delegate
+ .delete_at(picker.delegate.selected_index, window, cx)
+ })
+ }
}
impl ModalView for WorktreeList {}
impl EventEmitter<DismissEvent> for WorktreeList {}
@@ -203,6 +225,9 @@ impl Render for WorktreeList {
.on_action(cx.listener(|this, _: &WorktreeFromDefaultOnWindow, w, cx| {
this.handle_new_worktree(true, w, cx)
}))
+ .on_action(cx.listener(|this, _: &DeleteWorktree, window, cx| {
+ this.handle_delete(&DeleteWorktree, window, cx)
+ }))
.child(self.picker.clone())
.when(!self.embedded, |el| {
el.on_mouse_down_out({
@@ -420,6 +445,57 @@ impl WorktreeListDelegate {
.as_ref()
.and_then(|repo| repo.read(cx).branch.as_ref().map(|b| b.name()))
}
+
+ fn delete_at(&self, idx: usize, window: &mut Window, cx: &mut Context<Picker<Self>>) {
+ let Some(entry) = self.matches.get(idx).cloned() else {
+ return;
+ };
+ if entry.is_new {
+ return;
+ }
+ let Some(repo) = self.repo.clone() else {
+ return;
+ };
+ let workspace = self.workspace.clone();
+ let path = entry.worktree.path;
+
+ cx.spawn_in(window, async move |picker, cx| {
+ let result = repo
+ .update(cx, |repo, _| repo.remove_worktree(path.clone(), false))
+ .await?;
+
+ if let Err(e) = result {
+ log::error!("Failed to remove worktree: {}", e);
+ if let Some(workspace) = workspace.upgrade() {
+ cx.update(|_window, cx| {
+ show_error_toast(
+ workspace,
+ format!("worktree remove {}", path.display()),
+ e,
+ cx,
+ )
+ })?;
+ }
+ return Ok(());
+ }
+
+ picker.update_in(cx, |picker, _, cx| {
+ picker.delegate.matches.retain(|e| e.worktree.path != path);
+ if let Some(all_worktrees) = &mut picker.delegate.all_worktrees {
+ all_worktrees.retain(|w| w.path != path);
+ }
+ if picker.delegate.matches.is_empty() {
+ picker.delegate.selected_index = 0;
+ } else if picker.delegate.selected_index >= picker.delegate.matches.len() {
+ picker.delegate.selected_index = picker.delegate.matches.len() - 1;
+ }
+ cx.notify();
+ })?;
+
+ anyhow::Ok(())
+ })
+ .detach();
+ }
}
async fn open_remote_worktree(
@@ -778,6 +854,16 @@ impl PickerDelegate for WorktreeListDelegate {
} else {
Some(
footer_container
+ .child(
+ Button::new("delete-worktree", "Delete")
+ .key_binding(
+ KeyBinding::for_action_in(&DeleteWorktree, &focus_handle, cx)
+ .map(|kb| kb.size(rems_from_px(12.))),
+ )
+ .on_click(|_, window, cx| {
+ window.dispatch_action(DeleteWorktree.boxed_clone(), cx)
+ }),
+ )
.child(
Button::new("open-in-new-window", "Open in New Window")
.key_binding(
@@ -5732,7 +5732,7 @@ impl Repository {
pub fn remove_worktree(&mut self, path: PathBuf, force: bool) -> oneshot::Receiver<Result<()>> {
self.send_job(
- Some("git worktree remove".into()),
+ Some(format!("git worktree remove: {}", path.display()).into()),
move |repo, _cx| async move {
match repo {
RepositoryState::Local(LocalRepositoryState { backend, .. }) => {