use std::path::{Path, PathBuf};use std::sync::Arc; use anyhow::Context as _;use collections::HashSet;use fuzzy::StringMatchCandidate;use git::repository::Worktree as GitWorktree;use gpui::{ Action, AnyElement, App, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, Modifiers, ModifiersChangedEvent, ParentElement, PromptLevel, Render, SharedString, Styled, Subscription, Task, TaskExt, WeakEntity, Window, actions, rems,};use picker::{Picker, PickerDelegate, PickerEditorPosition};use project::Project;use project::git_store::RepositoryEvent;use ui::{ Button, CommonAnimationExt as _, Divider, HighlightedLabel, IconButton, KeyBinding, ListItem, ListItemSpacing, Tooltip, prelude::*,};use util::ResultExt as _;use util::paths::PathExt;use workspace::{ ModalView, MultiWorkspace, Workspace, dock::DockPosition, notifications::DetachAndPromptErr,}; use crate::git_panel::show_error_toast;use zed_actions::{ CreateWorktree, NewWorktreeBranchTarget, OpenWorktreeInNewWindow, SwitchWorktree,}; actions!( worktree_picker, [ /// Deletes the selected git worktree. DeleteWorktree, /// Force deletes the selected git worktree. ForceDeleteWorktree ]); pub struct WorktreePicker { picker: Entity<Picker<WorktreePickerDelegate>>, focus_handle: FocusHandle, _subscriptions: Vec<Subscription>,} impl WorktreePicker { pub fn new( project: Entity<Project>, workspace: WeakEntity<Workspace>, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let focused_dock = workspace .upgrade() .and_then(|workspace| workspace.read(cx).focused_dock_position(window, cx)); Self::new_inner(project, workspace, focused_dock, false, window, cx) } pub fn new_modal( project: Entity<Project>, workspace: WeakEntity<Workspace>, focused_dock: Option<DockPosition>, window: &mut Window, cx: &mut Context<Self>, ) -> Self { Self::new_inner(project, workspace, focused_dock, true, window, cx) } fn new_inner( project: Entity<Project>, workspace: WeakEntity<Workspace>, focused_dock: Option<DockPosition>, show_footer: bool, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let project_ref = project.read(cx); let project_worktree_paths: HashSet<PathBuf> = project_ref .visible_worktrees(cx) .map(|wt| wt.read(cx).abs_path().to_path_buf()) .collect(); let has_multiple_repositories = project_ref.repositories(cx).len() > 1; let repository = project_ref.active_repository(cx); let current_branch_name = repository.as_ref().and_then(|repo| { repo.read(cx) .branch .as_ref() .map(|branch| branch.name().to_string()) }); let all_worktrees_request = repository .clone() .map(|repository| repository.update(cx, |repository, _| repository.worktrees())); let default_branch_request = repository.clone().map(|repository| { repository.update(cx, |repository, _| repository.default_branch(true)) }); let initial_matches = vec![WorktreeEntry::CreateFromCurrentBranch]; let delegate = WorktreePickerDelegate { matches: initial_matches, all_worktrees: Vec::new(), project_worktree_paths, selected_index: 0, project, workspace, focused_dock, current_branch_name, default_branch: None, has_multiple_repositories, focus_handle: cx.focus_handle(), show_footer, modifiers: Modifiers::default(), hovered_delete_index: None, deleting_worktree_paths: HashSet::default(), }; let picker = cx.new(|cx| { Picker::list(delegate, window, cx) .list_measure_all() .show_scrollbar(true) .modal(false) .max_height(Some(rems(20.).into())) }); let picker_focus_handle = picker.focus_handle(cx); picker.update(cx, |picker, _| { picker.delegate.focus_handle = picker_focus_handle; }); let mut subscriptions = Vec::new(); { let picker_handle = picker.downgrade(); cx.spawn_in(window, async move |_this, cx| { let all_worktrees: Vec<_> = match all_worktrees_request { Some(req) => match req.await { Ok(Ok(worktrees)) => { worktrees.into_iter().filter(|wt| !wt.is_bare).collect() } Ok(Err(err)) => { log::warn!("WorktreePicker: git worktree list failed: {err}"); return anyhow::Ok(()); } Err(_) => { log::warn!("WorktreePicker: worktree request was cancelled"); return anyhow::Ok(()); } }, None => Vec::new(), }; let default_branch = match default_branch_request { Some(req) => req.await.ok().and_then(Result::ok).flatten(), None => None, }; picker_handle.update_in(cx, |picker, window, cx| { picker.delegate.all_worktrees = all_worktrees; picker.delegate.default_branch = default_branch.and_then(|branch| RemoteBranchName::parse(&branch)); picker.refresh(window, cx); })?; anyhow::Ok(()) }) .detach_and_log_err(cx); } if let Some(repo) = &repository { let picker_entity = picker.downgrade(); subscriptions.push(cx.subscribe_in( repo, window, move |_this, repo, event: &RepositoryEvent, window, cx| { if matches!(event, RepositoryEvent::GitWorktreeListChanged) { let worktrees_request = repo.update(cx, |repo, _| repo.worktrees()); let picker = picker_entity.clone(); cx.spawn_in(window, async move |_, cx| { let all_worktrees: Vec<_> = worktrees_request .await?? .into_iter() .filter(|wt| !wt.is_bare) .collect(); picker.update_in(cx, |picker, window, cx| { picker.delegate.all_worktrees = all_worktrees; picker.refresh(window, cx); })?; anyhow::Ok(()) }) .detach_and_log_err(cx); } }, )); } subscriptions.push(cx.subscribe(&picker, |_, _, _, cx| { cx.emit(DismissEvent); })); Self { focus_handle: picker.focus_handle(cx), picker, _subscriptions: subscriptions, } } fn handle_modifiers_changed( &mut self, ev: &ModifiersChangedEvent, _: &mut Window, cx: &mut Context<Self>, ) { self.picker.update(cx, |picker, cx| { picker.delegate.modifiers = ev.modifiers; cx.notify(); }); }} impl Focusable for WorktreePicker { fn focus_handle(&self, _cx: &App) -> FocusHandle { self.focus_handle.clone() }} impl ModalView for WorktreePicker {}impl EventEmitter<DismissEvent> for WorktreePicker {} impl Render for WorktreePicker { fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { v_flex() .key_context("WorktreePicker") .w(rems(34.)) .elevation_3(cx) .child(self.picker.clone()) .on_modifiers_changed(cx.listener(Self::handle_modifiers_changed)) .on_mouse_down_out(cx.listener(|_, _, _, cx| { cx.emit(DismissEvent); })) .on_action(cx.listener(|this, _: &DeleteWorktree, window, cx| { this.picker.update(cx, |picker, cx| { let ix = picker.delegate.selected_index; picker.delegate.delete_worktree(ix, false, window, cx); }); })) .on_action(cx.listener(|this, _: &ForceDeleteWorktree, window, cx| { this.picker.update(cx, |picker, cx| { let ix = picker.delegate.selected_index; picker.delegate.delete_worktree(ix, true, window, cx); }); })) }} #[derive(Clone)]enum WorktreeEntry { CreateFromCurrentBranch, CreateFromDefaultBranch { default_branch: RemoteBranchName, }, Separator, Worktree { worktree: GitWorktree, positions: Vec<usize>, }, CreateNamed { name: String, from_branch: Option<RemoteBranchName>, disabled_reason: Option<String>, },} #[derive(Clone)]struct RemoteBranchName { remote_name: String, branch_name: String,} impl RemoteBranchName { fn parse(name: &str) -> Option<Self> { let name = name.strip_prefix("refs/remotes/").unwrap_or(name); let (remote_name, branch_name) = name.split_once('/')?; if remote_name.is_empty() || branch_name.is_empty() { return None; } Some(Self { remote_name: remote_name.to_string(), branch_name: branch_name.to_string(), }) } fn display_name(&self) -> String { format!("{}/{}", self.remote_name, self.branch_name) }} struct WorktreePickerDelegate { matches: Vec<WorktreeEntry>, all_worktrees: Vec<GitWorktree>, project_worktree_paths: HashSet<PathBuf>, selected_index: usize, project: Entity<Project>, workspace: WeakEntity<Workspace>, focused_dock: Option<DockPosition>, current_branch_name: Option<String>, default_branch: Option<RemoteBranchName>, has_multiple_repositories: bool, focus_handle: FocusHandle, show_footer: bool, modifiers: Modifiers, hovered_delete_index: Option<usize>, deleting_worktree_paths: HashSet<PathBuf>,} fn remove_worktree_command(path: &Path, force: bool) -> String { if force { format!("worktree remove --force {}", path.display()) } else { format!("worktree remove {}", path.display()) }} struct WorktreeRemoveForceDeletePrompt { required_error_substrings: &'static [&'static str], message: fn(&str) -> String,} impl WorktreeRemoveForceDeletePrompt { fn matches(&self, normalized_error_message: &str) -> bool { self.required_error_substrings .iter() .all(|substring| normalized_error_message.contains(substring)) }} const WORKTREE_REMOVE_FORCE_DELETE_PROMPTS: &[WorktreeRemoveForceDeletePrompt] = &[WorktreeRemoveForceDeletePrompt { required_error_substrings: &[ "contains modified or untracked files", "use --force to delete it", ], message: dirty_worktree_force_delete_prompt, }]; fn dirty_worktree_force_delete_prompt(display_name: &str) -> String { format!("Worktree \"{display_name}\" contains modified or untracked files. Force delete it?")} fn force_delete_prompt_for_worktree_remove_error( error: &anyhow::Error, display_name: &str,) -> Option<String> { let normalized_error_message = error.to_string().to_lowercase(); WORKTREE_REMOVE_FORCE_DELETE_PROMPTS .iter() .find(|prompt| prompt.matches(&normalized_error_message)) .map(|prompt| (prompt.message)(display_name))} struct DeleteWorktreeTooltip { picker: WeakEntity<Picker<WorktreePickerDelegate>>, focus_handle: FocusHandle, delete_index: usize, _subscription: Subscription,} impl DeleteWorktreeTooltip { fn new( picker: Entity<Picker<WorktreePickerDelegate>>, focus_handle: FocusHandle, delete_index: usize, cx: &mut Context<Self>, ) -> Self { let subscription = cx.observe(&picker, |_, _, cx| cx.notify()); Self { picker: picker.downgrade(), focus_handle, delete_index, _subscription: subscription, } }} impl Render for DeleteWorktreeTooltip { fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { let force_delete = self .picker .read_with(cx, |picker, _| { picker .delegate .is_force_delete_hovering_index(self.delete_index) }) .unwrap_or(false); if force_delete { Tooltip::for_action_in( "Force Delete Worktree", &ForceDeleteWorktree, &self.focus_handle, cx, ) .into_any_element() } else { Tooltip::with_meta_in( "Delete Worktree", Some(&DeleteWorktree), "Hold alt to force delete", &self.focus_handle, cx, ) .into_any_element() } }} impl WorktreePickerDelegate { fn build_fixed_entries(&self) -> Vec<WorktreeEntry> { let mut entries = Vec::new(); if self.has_multiple_repositories { entries.push(WorktreeEntry::CreateFromCurrentBranch); } else if let Some(ref default_branch) = self.default_branch { let is_different = self .current_branch_name .as_ref() .is_none_or(|current| current != &default_branch.branch_name); entries.push(WorktreeEntry::CreateFromDefaultBranch { default_branch: default_branch.clone(), }); if is_different { entries.push(WorktreeEntry::CreateFromCurrentBranch); } } else { entries.push(WorktreeEntry::CreateFromCurrentBranch); } entries } fn all_repo_worktrees(&self) -> &[GitWorktree] { &self.all_worktrees } fn creation_blocked_reason(&self, cx: &App) -> Option<SharedString> { let project = self.project.read(cx); if project.is_via_collab() { Some("Worktree creation is not supported in collaborative projects".into()) } else if project.repositories(cx).is_empty() { Some("Requires a Git repository in the project".into()) } else { None } } fn can_delete_worktree(&self, worktree: &GitWorktree) -> bool { !worktree.is_main && !self.project_worktree_paths.contains(&worktree.path) } fn is_force_delete_hovering_index(&self, index: usize) -> bool { self.modifiers.alt && self.hovered_delete_index == Some(index) } fn delete_worktree( &mut self, ix: usize, force: bool, window: &mut Window, cx: &mut Context<Picker<Self>>, ) { let Some(entry) = self.matches.get(ix) else { return; }; let WorktreeEntry::Worktree { worktree, .. } = entry else { return; }; if !self.can_delete_worktree(worktree) || self.deleting_worktree_paths.contains(&worktree.path) { return; } let repo = self.project.read(cx).active_repository(cx); let Some(repo) = repo else { return; }; let path = worktree.path.clone(); let display_name = worktree.directory_name( self.all_worktrees .iter() .find(|worktree| worktree.is_main) .map(|worktree| worktree.path.as_path()), ); let workspace = self.workspace.clone(); self.deleting_worktree_paths.insert(path.clone());Lines 1–500 of 1893. Load more
use std::path::{Path, PathBuf}; use std::sync::Arc; use anyhow::Context as _; use collections::HashSet; use fuzzy::StringMatchCandidate; use git::repository::Worktree as GitWorktree; use gpui::{ Action, AnyElement, App, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, Modifiers, ModifiersChangedEvent, ParentElement, PromptLevel, Render, SharedString, Styled, Subscription, Task, TaskExt, WeakEntity, Window, actions, rems, }; use picker::{Picker, PickerDelegate, PickerEditorPosition}; use project::Project; use project::git_store::RepositoryEvent; use ui::{ Button, CommonAnimationExt as _, Divider, HighlightedLabel, IconButton, KeyBinding, ListItem, ListItemSpacing, Tooltip, prelude::*, }; use util::ResultExt as _; use util::paths::PathExt; use workspace::{ ModalView, MultiWorkspace, Workspace, dock::DockPosition, notifications::DetachAndPromptErr, }; use crate::git_panel::show_error_toast; use zed_actions::{ CreateWorktree, NewWorktreeBranchTarget, OpenWorktreeInNewWindow, SwitchWorktree, }; actions!( worktree_picker, [ /// Deletes the selected git worktree. DeleteWorktree, /// Force deletes the selected git worktree. ForceDeleteWorktree ] ); pub struct WorktreePicker { picker: Entity<Picker<WorktreePickerDelegate>>, focus_handle: FocusHandle, _subscriptions: Vec<Subscription>, } impl WorktreePicker { pub fn new( project: Entity<Project>, workspace: WeakEntity<Workspace>, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let focused_dock = workspace .upgrade() .and_then(|workspace| workspace.read(cx).focused_dock_position(window, cx)); Self::new_inner(project, workspace, focused_dock, false, window, cx) } pub fn new_modal( project: Entity<Project>, workspace: WeakEntity<Workspace>, focused_dock: Option<DockPosition>, window: &mut Window, cx: &mut Context<Self>, ) -> Self { Self::new_inner(project, workspace, focused_dock, true, window, cx) } fn new_inner( project: Entity<Project>, workspace: WeakEntity<Workspace>, focused_dock: Option<DockPosition>, show_footer: bool, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let project_ref = project.read(cx); let project_worktree_paths: HashSet<PathBuf> = project_ref .visible_worktrees(cx) .map(|wt| wt.read(cx).abs_path().to_path_buf()) .collect(); let has_multiple_repositories = project_ref.repositories(cx).len() > 1; let repository = project_ref.active_repository(cx); let current_branch_name = repository.as_ref().and_then(|repo| { repo.read(cx) .branch .as_ref() .map(|branch| branch.name().to_string()) }); let all_worktrees_request = repository .clone() .map(|repository| repository.update(cx, |repository, _| repository.worktrees())); let default_branch_request = repository.clone().map(|repository| { repository.update(cx, |repository, _| repository.default_branch(true)) }); let initial_matches = vec![WorktreeEntry::CreateFromCurrentBranch]; let delegate = WorktreePickerDelegate { matches: initial_matches, all_worktrees: Vec::new(), project_worktree_paths, selected_index: 0, project, workspace, focused_dock, current_branch_name, default_branch: None, has_multiple_repositories, focus_handle: cx.focus_handle(), show_footer, modifiers: Modifiers::default(), hovered_delete_index: None, deleting_worktree_paths: HashSet::default(), }; let picker = cx.new(|cx| { Picker::list(delegate, window, cx) .list_measure_all() .show_scrollbar(true) .modal(false) .max_height(Some(rems(20.).into())) }); let picker_focus_handle = picker.focus_handle(cx); picker.update(cx, |picker, _| { picker.delegate.focus_handle = picker_focus_handle; }); let mut subscriptions = Vec::new(); { let picker_handle = picker.downgrade(); cx.spawn_in(window, async move |_this, cx| { let all_worktrees: Vec<_> = match all_worktrees_request { Some(req) => match req.await { Ok(Ok(worktrees)) => { worktrees.into_iter().filter(|wt| !wt.is_bare).collect() } Ok(Err(err)) => { log::warn!("WorktreePicker: git worktree list failed: {err}"); return anyhow::Ok(()); } Err(_) => { log::warn!("WorktreePicker: worktree request was cancelled"); return anyhow::Ok(()); } }, None => Vec::new(), }; let default_branch = match default_branch_request { Some(req) => req.await.ok().and_then(Result::ok).flatten(), None => None, }; picker_handle.update_in(cx, |picker, window, cx| { picker.delegate.all_worktrees = all_worktrees; picker.delegate.default_branch = default_branch.and_then(|branch| RemoteBranchName::parse(&branch)); picker.refresh(window, cx); })?; anyhow::Ok(()) }) .detach_and_log_err(cx); } if let Some(repo) = &repository { let picker_entity = picker.downgrade(); subscriptions.push(cx.subscribe_in( repo, window, move |_this, repo, event: &RepositoryEvent, window, cx| { if matches!(event, RepositoryEvent::GitWorktreeListChanged) { let worktrees_request = repo.update(cx, |repo, _| repo.worktrees()); let picker = picker_entity.clone(); cx.spawn_in(window, async move |_, cx| { let all_worktrees: Vec<_> = worktrees_request .await?? .into_iter() .filter(|wt| !wt.is_bare) .collect(); picker.update_in(cx, |picker, window, cx| { picker.delegate.all_worktrees = all_worktrees; picker.refresh(window, cx); })?; anyhow::Ok(()) }) .detach_and_log_err(cx); } }, )); } subscriptions.push(cx.subscribe(&picker, |_, _, _, cx| { cx.emit(DismissEvent); })); Self { focus_handle: picker.focus_handle(cx), picker, _subscriptions: subscriptions, } } fn handle_modifiers_changed( &mut self, ev: &ModifiersChangedEvent, _: &mut Window, cx: &mut Context<Self>, ) { self.picker.update(cx, |picker, cx| { picker.delegate.modifiers = ev.modifiers; cx.notify(); }); } } impl Focusable for WorktreePicker { fn focus_handle(&self, _cx: &App) -> FocusHandle { self.focus_handle.clone() } } impl ModalView for WorktreePicker {} impl EventEmitter<DismissEvent> for WorktreePicker {} impl Render for WorktreePicker { fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { v_flex() .key_context("WorktreePicker") .w(rems(34.)) .elevation_3(cx) .child(self.picker.clone()) .on_modifiers_changed(cx.listener(Self::handle_modifiers_changed)) .on_mouse_down_out(cx.listener(|_, _, _, cx| { cx.emit(DismissEvent); })) .on_action(cx.listener(|this, _: &DeleteWorktree, window, cx| { this.picker.update(cx, |picker, cx| { let ix = picker.delegate.selected_index; picker.delegate.delete_worktree(ix, false, window, cx); }); })) .on_action(cx.listener(|this, _: &ForceDeleteWorktree, window, cx| { this.picker.update(cx, |picker, cx| { let ix = picker.delegate.selected_index; picker.delegate.delete_worktree(ix, true, window, cx); }); })) } } #[derive(Clone)] enum WorktreeEntry { CreateFromCurrentBranch, CreateFromDefaultBranch { default_branch: RemoteBranchName, }, Separator, Worktree { worktree: GitWorktree, positions: Vec<usize>, }, CreateNamed { name: String, from_branch: Option<RemoteBranchName>, disabled_reason: Option<String>, }, } #[derive(Clone)] struct RemoteBranchName { remote_name: String, branch_name: String, } impl RemoteBranchName { fn parse(name: &str) -> Option<Self> { let name = name.strip_prefix("refs/remotes/").unwrap_or(name); let (remote_name, branch_name) = name.split_once('/')?; if remote_name.is_empty() || branch_name.is_empty() { return None; } Some(Self { remote_name: remote_name.to_string(), branch_name: branch_name.to_string(), }) } fn display_name(&self) -> String { format!("{}/{}", self.remote_name, self.branch_name) } } struct WorktreePickerDelegate { matches: Vec<WorktreeEntry>, all_worktrees: Vec<GitWorktree>, project_worktree_paths: HashSet<PathBuf>, selected_index: usize, project: Entity<Project>, workspace: WeakEntity<Workspace>, focused_dock: Option<DockPosition>, current_branch_name: Option<String>, default_branch: Option<RemoteBranchName>, has_multiple_repositories: bool, focus_handle: FocusHandle, show_footer: bool, modifiers: Modifiers, hovered_delete_index: Option<usize>, deleting_worktree_paths: HashSet<PathBuf>, } fn remove_worktree_command(path: &Path, force: bool) -> String { if force { format!("worktree remove --force {}", path.display()) } else { format!("worktree remove {}", path.display()) } } struct WorktreeRemoveForceDeletePrompt { required_error_substrings: &'static [&'static str], message: fn(&str) -> String, } impl WorktreeRemoveForceDeletePrompt { fn matches(&self, normalized_error_message: &str) -> bool { self.required_error_substrings .iter() .all(|substring| normalized_error_message.contains(substring)) } } const WORKTREE_REMOVE_FORCE_DELETE_PROMPTS: &[WorktreeRemoveForceDeletePrompt] = &[WorktreeRemoveForceDeletePrompt { required_error_substrings: &[ "contains modified or untracked files", "use --force to delete it", ], message: dirty_worktree_force_delete_prompt, }]; fn dirty_worktree_force_delete_prompt(display_name: &str) -> String { format!("Worktree \"{display_name}\" contains modified or untracked files. Force delete it?") } fn force_delete_prompt_for_worktree_remove_error( error: &anyhow::Error, display_name: &str, ) -> Option<String> { let normalized_error_message = error.to_string().to_lowercase(); WORKTREE_REMOVE_FORCE_DELETE_PROMPTS .iter() .find(|prompt| prompt.matches(&normalized_error_message)) .map(|prompt| (prompt.message)(display_name)) } struct DeleteWorktreeTooltip { picker: WeakEntity<Picker<WorktreePickerDelegate>>, focus_handle: FocusHandle, delete_index: usize, _subscription: Subscription, } impl DeleteWorktreeTooltip { fn new( picker: Entity<Picker<WorktreePickerDelegate>>, focus_handle: FocusHandle, delete_index: usize, cx: &mut Context<Self>, ) -> Self { let subscription = cx.observe(&picker, |_, _, cx| cx.notify()); Self { picker: picker.downgrade(), focus_handle, delete_index, _subscription: subscription, } } } impl Render for DeleteWorktreeTooltip { fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { let force_delete = self .picker .read_with(cx, |picker, _| { picker .delegate .is_force_delete_hovering_index(self.delete_index) }) .unwrap_or(false); if force_delete { Tooltip::for_action_in( "Force Delete Worktree", &ForceDeleteWorktree, &self.focus_handle, cx, ) .into_any_element() } else { Tooltip::with_meta_in( "Delete Worktree", Some(&DeleteWorktree), "Hold alt to force delete", &self.focus_handle, cx, ) .into_any_element() } } } impl WorktreePickerDelegate { fn build_fixed_entries(&self) -> Vec<WorktreeEntry> { let mut entries = Vec::new(); if self.has_multiple_repositories { entries.push(WorktreeEntry::CreateFromCurrentBranch); } else if let Some(ref default_branch) = self.default_branch { let is_different = self .current_branch_name .as_ref() .is_none_or(|current| current != &default_branch.branch_name); entries.push(WorktreeEntry::CreateFromDefaultBranch { default_branch: default_branch.clone(), }); if is_different { entries.push(WorktreeEntry::CreateFromCurrentBranch); } } else { entries.push(WorktreeEntry::CreateFromCurrentBranch); } entries } fn all_repo_worktrees(&self) -> &[GitWorktree] { &self.all_worktrees } fn creation_blocked_reason(&self, cx: &App) -> Option<SharedString> { let project = self.project.read(cx); if project.is_via_collab() { Some("Worktree creation is not supported in collaborative projects".into()) } else if project.repositories(cx).is_empty() { Some("Requires a Git repository in the project".into()) } else { None } } fn can_delete_worktree(&self, worktree: &GitWorktree) -> bool { !worktree.is_main && !self.project_worktree_paths.contains(&worktree.path) } fn is_force_delete_hovering_index(&self, index: usize) -> bool { self.modifiers.alt && self.hovered_delete_index == Some(index) } fn delete_worktree( &mut self, ix: usize, force: bool, window: &mut Window, cx: &mut Context<Picker<Self>>, ) { let Some(entry) = self.matches.get(ix) else { return; }; let WorktreeEntry::Worktree { worktree, .. } = entry else { return; }; if !self.can_delete_worktree(worktree) || self.deleting_worktree_paths.contains(&worktree.path) { return; } let repo = self.project.read(cx).active_repository(cx); let Some(repo) = repo else { return; }; let path = worktree.path.clone(); let display_name = worktree.directory_name( self.all_worktrees .iter() .find(|worktree| worktree.is_main) .map(|worktree| worktree.path.as_path()), ); let workspace = self.workspace.clone(); self.deleting_worktree_paths.insert(path.clone());