mirror of
https://github.com/zed-industries/zed.git
synced 2026-05-27 00:08:42 +00:00
git_ui: Add ability to delete git worktrees from picker (#50015)
Adds the ability to delete a git worktree directly from the worktree picker, inspired by the existing branch delete functionality. (`cmd-shift-backspace` on macOS, `ctrl-shift-backspace` on Linux/Windows). Screenshot: <img width="1288" height="466" alt="Screenshot 2026-02-24 at 16 01 05" src="https://github.com/user-attachments/assets/6ca5048d-63fa-4295-a578-358904bb92eb" /> Release Notes: - Added the ability to delete a git worktree from the worktree picker --------- Co-authored-by: Anthony Eid <anthony@zed.dev>
This commit is contained in:
parent
e7172681b2
commit
53fca25ba5
6 changed files with 106 additions and 3 deletions
|
|
@ -1312,6 +1312,7 @@
|
|||
"bindings": {
|
||||
"ctrl-shift-space": "git::WorktreeFromDefaultOnWindow",
|
||||
"ctrl-space": "git::WorktreeFromDefault",
|
||||
"ctrl-shift-backspace": "git::DeleteWorktree",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1417,6 +1417,7 @@
|
|||
"bindings": {
|
||||
"ctrl-shift-space": "git::WorktreeFromDefaultOnWindow",
|
||||
"ctrl-space": "git::WorktreeFromDefault",
|
||||
"cmd-shift-backspace": "git::DeleteWorktree",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1333,6 +1333,7 @@
|
|||
"bindings": {
|
||||
"ctrl-shift-space": "git::WorktreeFromDefaultOnWindow",
|
||||
"ctrl-space": "git::WorktreeFromDefault",
|
||||
"ctrl-shift-backspace": "git::DeleteWorktree",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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, .. }) => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue