use std::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, IntoElement, ParentElement, 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, 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, [DeleteWorktree]); 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(false)) }); 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_name: None, has_multiple_repositories, focus_handle: cx.focus_handle(), show_footer, }; 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_name = default_branch.map(|branch| branch.to_string()); 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, } }} 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_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, window, cx); }); })) }} #[derive(Clone)]enum WorktreeEntry { CreateFromCurrentBranch, CreateFromDefaultBranch { default_branch_name: String, }, Separator, Worktree { worktree: GitWorktree, positions: Vec<usize>, }, CreateNamed { name: String, from_branch: Option<String>, disabled_reason: Option<String>, },} 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_name: Option<String>, has_multiple_repositories: bool, focus_handle: FocusHandle, show_footer: bool,} impl WorktreePickerDelegate { fn build_fixed_entries(&self) -> Vec<WorktreeEntry> { let mut entries = Vec::new(); entries.push(WorktreeEntry::CreateFromCurrentBranch); if !self.has_multiple_repositories { if let Some(ref default_branch) = self.default_branch_name { let is_different = self .current_branch_name .as_ref() .is_none_or(|current| current != default_branch); if is_different { entries.push(WorktreeEntry::CreateFromDefaultBranch { default_branch_name: default_branch.clone(), }); } } } 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 delete_worktree(&self, ix: usize, 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) { return; } let repo = self.project.read(cx).active_repository(cx); let Some(repo) = repo else { return; }; let path = worktree.path.clone(); let workspace = self.workspace.clone(); cx.spawn_in(window, async move |picker, cx| { let result = repo .update(cx, |repo, _| repo.remove_worktree(path.clone(), false)) .await?; if let Err(error) = result { log::error!("Failed to remove worktree: {}", error); if let Some(workspace) = workspace.upgrade() { cx.update(|_window, cx| { show_error_toast( workspace, format!("worktree remove {}", path.display()), error, cx, ) })?; } return Ok(()); } picker.update_in(cx, |picker, _window, cx| { picker.delegate.matches.retain(|e| { !matches!(e, WorktreeEntry::Worktree { worktree, .. } if worktree.path == path) }); picker.delegate.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_and_log_err(cx); } fn sync_selected_index(&mut self, has_query: bool) { if !has_query { return; } if let Some(index) = self .matches .iter() .position(|entry| matches!(entry, WorktreeEntry::Worktree { .. })) { self.selected_index = index; } else if let Some(index) = self .matches .iter() .position(|entry| matches!(entry, WorktreeEntry::CreateNamed { .. })) { self.selected_index = index; } else { self.selected_index = 0; } }} impl PickerDelegate for WorktreePickerDelegate { type ListItem = AnyElement; fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> { "Select a worktree…".into() } fn editor_position(&self) -> PickerEditorPosition { PickerEditorPosition::Start } fn match_count(&self) -> usize { self.matches.len() } fn selected_index(&self) -> usize { self.selected_index } fn set_selected_index( &mut self, ix: usize, _window: &mut Window, _cx: &mut Context<Picker<Self>>, ) { self.selected_index = ix; } fn can_select(&self, ix: usize, _window: &mut Window, _cx: &mut Context<Picker<Self>>) -> bool { !matches!(self.matches.get(ix), Some(WorktreeEntry::Separator)) } fn update_matches( &mut self, query: String, window: &mut Window, cx: &mut Context<Picker<Self>>, ) -> Task<()> { let repo_worktrees = self.all_repo_worktrees().to_vec(); let normalized_query = query.replace(' ', "-"); let main_worktree_path = self .all_worktrees .iter() .find(|wt| wt.is_main) .map(|wt| wt.path.clone()); let has_named_worktree = self.all_worktrees.iter().any(|worktree| { worktree.directory_name(main_worktree_path.as_deref()) == normalized_query }); let create_named_disabled_reason: Option<String> = if self.has_multiple_repositories { Some("Cannot create a named worktree in a project with multiple repositories".into()) } else if has_named_worktree { Some("A worktree with this name already exists".into()) } else { None }; let show_default_branch_create = !self.has_multiple_repositories && self.default_branch_name.as_ref().is_some_and(|default| { self.current_branch_name .as_ref() .is_none_or(|current| current != default) }); let default_branch_name = self.default_branch_name.clone(); if query.is_empty() { let mut matches = self.build_fixed_entries(); if !repo_worktrees.is_empty() { let main_worktree_path = repo_worktrees .iter() .find(|wt| wt.is_main) .map(|wt| wt.path.clone()); let mut sorted = repo_worktrees; let project_paths = &self.project_worktree_paths; sorted.sort_by(|a, b| { let a_is_current = project_paths.contains(&a.path); let b_is_current = project_paths.contains(&b.path); b_is_current.cmp(&a_is_current).then_with(|| { a.directory_name(main_worktree_path.as_deref()) .cmp(&b.directory_name(main_worktree_path.as_deref())) }) }); matches.push(WorktreeEntry::Separator); for worktree in sorted { matches.push(WorktreeEntry::Worktree { worktree, positions: Vec::new(), }); } } self.matches = matches; self.sync_selected_index(false); return Task::ready(()); } let main_worktree_path = repo_worktrees .iter() .find(|wt| wt.is_main) .map(|wt| wt.path.clone()); let candidates: Vec<_> = repo_worktrees .iter() .enumerate() .map(|(ix, worktree)| { StringMatchCandidate::new( ix, &worktree.directory_name(main_worktree_path.as_deref()), ) }) .collect(); Lines 1–500 of 1135. Load more
use std::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, IntoElement, ParentElement, 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, 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, [DeleteWorktree]); 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(false)) }); 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_name: None, has_multiple_repositories, focus_handle: cx.focus_handle(), show_footer, }; 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_name = default_branch.map(|branch| branch.to_string()); 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, } } } 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_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, window, cx); }); })) } } #[derive(Clone)] enum WorktreeEntry { CreateFromCurrentBranch, CreateFromDefaultBranch { default_branch_name: String, }, Separator, Worktree { worktree: GitWorktree, positions: Vec<usize>, }, CreateNamed { name: String, from_branch: Option<String>, disabled_reason: Option<String>, }, } 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_name: Option<String>, has_multiple_repositories: bool, focus_handle: FocusHandle, show_footer: bool, } impl WorktreePickerDelegate { fn build_fixed_entries(&self) -> Vec<WorktreeEntry> { let mut entries = Vec::new(); entries.push(WorktreeEntry::CreateFromCurrentBranch); if !self.has_multiple_repositories { if let Some(ref default_branch) = self.default_branch_name { let is_different = self .current_branch_name .as_ref() .is_none_or(|current| current != default_branch); if is_different { entries.push(WorktreeEntry::CreateFromDefaultBranch { default_branch_name: default_branch.clone(), }); } } } 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 delete_worktree(&self, ix: usize, 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) { return; } let repo = self.project.read(cx).active_repository(cx); let Some(repo) = repo else { return; }; let path = worktree.path.clone(); let workspace = self.workspace.clone(); cx.spawn_in(window, async move |picker, cx| { let result = repo .update(cx, |repo, _| repo.remove_worktree(path.clone(), false)) .await?; if let Err(error) = result { log::error!("Failed to remove worktree: {}", error); if let Some(workspace) = workspace.upgrade() { cx.update(|_window, cx| { show_error_toast( workspace, format!("worktree remove {}", path.display()), error, cx, ) })?; } return Ok(()); } picker.update_in(cx, |picker, _window, cx| { picker.delegate.matches.retain(|e| { !matches!(e, WorktreeEntry::Worktree { worktree, .. } if worktree.path == path) }); picker.delegate.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_and_log_err(cx); } fn sync_selected_index(&mut self, has_query: bool) { if !has_query { return; } if let Some(index) = self .matches .iter() .position(|entry| matches!(entry, WorktreeEntry::Worktree { .. })) { self.selected_index = index; } else if let Some(index) = self .matches .iter() .position(|entry| matches!(entry, WorktreeEntry::CreateNamed { .. })) { self.selected_index = index; } else { self.selected_index = 0; } } } impl PickerDelegate for WorktreePickerDelegate { type ListItem = AnyElement; fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> { "Select a worktree…".into() } fn editor_position(&self) -> PickerEditorPosition { PickerEditorPosition::Start } fn match_count(&self) -> usize { self.matches.len() } fn selected_index(&self) -> usize { self.selected_index } fn set_selected_index( &mut self, ix: usize, _window: &mut Window, _cx: &mut Context<Picker<Self>>, ) { self.selected_index = ix; } fn can_select(&self, ix: usize, _window: &mut Window, _cx: &mut Context<Picker<Self>>) -> bool { !matches!(self.matches.get(ix), Some(WorktreeEntry::Separator)) } fn update_matches( &mut self, query: String, window: &mut Window, cx: &mut Context<Picker<Self>>, ) -> Task<()> { let repo_worktrees = self.all_repo_worktrees().to_vec(); let normalized_query = query.replace(' ', "-"); let main_worktree_path = self .all_worktrees .iter() .find(|wt| wt.is_main) .map(|wt| wt.path.clone()); let has_named_worktree = self.all_worktrees.iter().any(|worktree| { worktree.directory_name(main_worktree_path.as_deref()) == normalized_query }); let create_named_disabled_reason: Option<String> = if self.has_multiple_repositories { Some("Cannot create a named worktree in a project with multiple repositories".into()) } else if has_named_worktree { Some("A worktree with this name already exists".into()) } else { None }; let show_default_branch_create = !self.has_multiple_repositories && self.default_branch_name.as_ref().is_some_and(|default| { self.current_branch_name .as_ref() .is_none_or(|current| current != default) }); let default_branch_name = self.default_branch_name.clone(); if query.is_empty() { let mut matches = self.build_fixed_entries(); if !repo_worktrees.is_empty() { let main_worktree_path = repo_worktrees .iter() .find(|wt| wt.is_main) .map(|wt| wt.path.clone()); let mut sorted = repo_worktrees; let project_paths = &self.project_worktree_paths; sorted.sort_by(|a, b| { let a_is_current = project_paths.contains(&a.path); let b_is_current = project_paths.contains(&b.path); b_is_current.cmp(&a_is_current).then_with(|| { a.directory_name(main_worktree_path.as_deref()) .cmp(&b.directory_name(main_worktree_path.as_deref())) }) }); matches.push(WorktreeEntry::Separator); for worktree in sorted { matches.push(WorktreeEntry::Worktree { worktree, positions: Vec::new(), }); } } self.matches = matches; self.sync_selected_index(false); return Task::ready(()); } let main_worktree_path = repo_worktrees .iter() .find(|wt| wt.is_main) .map(|wt| wt.path.clone()); let candidates: Vec<_> = repo_worktrees .iter() .enumerate() .map(|(ix, worktree)| { StringMatchCandidate::new( ix, &worktree.directory_name(main_worktree_path.as_deref()), ) }) .collect();