use anyhow::Context as _;use collections::HashSet;use fuzzy::StringMatchCandidate; use git::repository::Worktree as GitWorktree;use gpui::{ Action, App, AsyncWindowContext, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, Modifiers, ModifiersChangedEvent, ParentElement, Render, SharedString, Styled, Subscription, Task, WeakEntity, Window, actions, rems,};use picker::{Picker, PickerDelegate, PickerEditorPosition};use project::project_settings::ProjectSettings;use project::{ git_store::Repository, trusted_worktrees::{PathTrust, TrustedWorktrees},};use remote::{RemoteConnectionOptions, remote_client::ConnectionIdentifier};use remote_connection::{RemoteConnectionModal, connect};use settings::Settings;use std::{path::PathBuf, sync::Arc};use ui::{HighlightedLabel, KeyBinding, ListItem, ListItemSpacing, prelude::*};use util::ResultExt;use workspace::{ModalView, MultiWorkspace, Workspace, notifications::DetachAndPromptErr}; use crate::git_panel::show_error_toast; actions!( git, [ WorktreeFromDefault, WorktreeFromDefaultOnWindow, DeleteWorktree ]); pub fn open( workspace: &mut Workspace, _: &zed_actions::git::Worktree, window: &mut Window, cx: &mut Context<Workspace>,) { let repository = workspace.project().read(cx).active_repository(cx); let workspace_handle = workspace.weak_handle(); workspace.toggle_modal(window, cx, |window, cx| { WorktreeList::new(repository, workspace_handle, rems(34.), window, cx) })} pub fn create_embedded( repository: Option<Entity<Repository>>, workspace: WeakEntity<Workspace>, width: Rems, window: &mut Window, cx: &mut Context<WorktreeList>,) -> WorktreeList { WorktreeList::new_embedded(repository, workspace, width, window, cx)} pub struct WorktreeList { width: Rems, pub picker: Entity<Picker<WorktreeListDelegate>>, picker_focus_handle: FocusHandle, _subscription: Option<Subscription>, embedded: bool,} impl WorktreeList { fn new( repository: Option<Entity<Repository>>, workspace: WeakEntity<Workspace>, width: Rems, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let mut this = Self::new_inner(repository, workspace, width, false, window, cx); this._subscription = Some(cx.subscribe(&this.picker, |_, _, _, cx| { cx.emit(DismissEvent); })); this } fn new_inner( repository: Option<Entity<Repository>>, workspace: WeakEntity<Workspace>, width: Rems, embedded: bool, window: &mut Window, cx: &mut Context<Self>, ) -> Self { 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)) }); cx.spawn_in(window, async move |this, cx| { let all_worktrees: Vec<_> = all_worktrees_request .context("No active repository")? .await?? .into_iter() .filter(|worktree| worktree.ref_name.is_some()) // hide worktrees without a branch .collect(); let default_branch = default_branch_request .context("No active repository")? .await .map(Result::ok) .ok() .flatten() .flatten(); this.update_in(cx, |this, window, cx| { this.picker.update(cx, |picker, cx| { picker.delegate.all_worktrees = Some(all_worktrees); picker.delegate.default_branch = default_branch; picker.refresh(window, cx); }) })?; anyhow::Ok(()) }) .detach_and_log_err(cx); let delegate = WorktreeListDelegate::new(workspace, repository, window, cx); let picker = cx.new(|cx| { Picker::uniform_list(delegate, window, cx) .show_scrollbar(true) .modal(!embedded) }); let picker_focus_handle = picker.focus_handle(cx); picker.update(cx, |picker, _| { picker.delegate.focus_handle = picker_focus_handle.clone(); }); Self { picker, picker_focus_handle, width, _subscription: None, embedded, } } fn new_embedded( repository: Option<Entity<Repository>>, workspace: WeakEntity<Workspace>, width: Rems, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let mut this = Self::new_inner(repository, workspace, width, true, window, cx); this._subscription = Some(cx.subscribe(&this.picker, |_, _, _, cx| { cx.emit(DismissEvent); })); this } pub fn handle_modifiers_changed( &mut self, ev: &ModifiersChangedEvent, _: &mut Window, cx: &mut Context<Self>, ) { self.picker .update(cx, |picker, _| picker.delegate.modifiers = ev.modifiers) } pub fn handle_new_worktree( &mut self, replace_current_window: bool, window: &mut Window, cx: &mut Context<Self>, ) { self.picker.update(cx, |picker, cx| { let ix = picker.delegate.selected_index(); let Some(entry) = picker.delegate.matches.get(ix) else { return; }; let Some(default_branch) = picker.delegate.default_branch.clone() else { return; }; if !entry.is_new { return; } picker.delegate.create_worktree( entry.worktree.display_name(), replace_current_window, Some(default_branch.into()), window, cx, ); }) } 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 {} impl Focusable for WorktreeList { fn focus_handle(&self, _: &App) -> FocusHandle { self.picker_focus_handle.clone() }} impl Render for WorktreeList { fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { v_flex() .key_context("GitWorktreeSelector") .w(self.width) .on_modifiers_changed(cx.listener(Self::handle_modifiers_changed)) .on_action(cx.listener(|this, _: &WorktreeFromDefault, w, cx| { this.handle_new_worktree(false, w, cx) })) .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({ cx.listener(move |this, _, window, cx| { this.picker.update(cx, |this, cx| { this.cancel(&Default::default(), window, cx); }) }) }) }) }} #[derive(Debug, Clone)]struct WorktreeEntry { worktree: GitWorktree, positions: Vec<usize>, is_new: bool,} pub struct WorktreeListDelegate { matches: Vec<WorktreeEntry>, all_worktrees: Option<Vec<GitWorktree>>, workspace: WeakEntity<Workspace>, repo: Option<Entity<Repository>>, selected_index: usize, last_query: String, modifiers: Modifiers, focus_handle: FocusHandle, default_branch: Option<SharedString>,} impl WorktreeListDelegate { fn new( workspace: WeakEntity<Workspace>, repo: Option<Entity<Repository>>, _window: &mut Window, cx: &mut Context<WorktreeList>, ) -> Self { Self { matches: vec![], all_worktrees: None, workspace, selected_index: 0, repo, last_query: Default::default(), modifiers: Default::default(), focus_handle: cx.focus_handle(), default_branch: None, } } fn create_worktree( &self, worktree_branch: &str, replace_current_window: bool, commit: Option<String>, window: &mut Window, cx: &mut Context<Picker<Self>>, ) { let Some(repo) = self.repo.clone() else { return; }; let branch = worktree_branch.to_string(); let workspace = self.workspace.clone(); cx.spawn_in(window, async move |_, cx| { let (receiver, new_worktree_path) = repo.update(cx, |repo, cx| { let worktree_directory_setting = ProjectSettings::get_global(cx) .git .worktree_directory .clone(); let new_worktree_path = repo.path_for_new_linked_worktree(&branch, &worktree_directory_setting)?; let receiver = repo.create_worktree(branch.clone(), new_worktree_path.clone(), commit); anyhow::Ok((receiver, new_worktree_path)) })?; receiver.await??; workspace.update(cx, |workspace, cx| { if let Some(trusted_worktrees) = TrustedWorktrees::try_get_global(cx) { let repo_path = &repo.read(cx).snapshot().work_directory_abs_path; let project = workspace.project(); if let Some((parent_worktree, _)) = project.read(cx).find_worktree(repo_path, cx) { let worktree_store = project.read(cx).worktree_store(); trusted_worktrees.update(cx, |trusted_worktrees, cx| { if trusted_worktrees.can_trust( &worktree_store, parent_worktree.read(cx).id(), cx, ) { trusted_worktrees.trust( &worktree_store, HashSet::from_iter([PathTrust::AbsPath( new_worktree_path.clone(), )]), cx, ); } }); } } })?; let (connection_options, app_state, is_local) = workspace.update(cx, |workspace, cx| { let project = workspace.project().clone(); let connection_options = project.read(cx).remote_connection_options(cx); let app_state = workspace.app_state().clone(); let is_local = project.read(cx).is_local(); (connection_options, app_state, is_local) })?; if is_local { workspace .update_in(cx, |workspace, window, cx| { workspace.open_workspace_for_paths( replace_current_window, vec![new_worktree_path], window, cx, ) })? .await?; } else if let Some(connection_options) = connection_options { open_remote_worktree( connection_options, vec![new_worktree_path], app_state, workspace.clone(), replace_current_window, cx, ) .await?; } anyhow::Ok(()) }) .detach_and_prompt_err("Failed to create worktree", window, cx, |e, _, _| { let msg = e.to_string(); if msg.contains("git.worktree_directory") { Some(format!("Invalid git.worktree_directory setting: {}", e)) } else { Some(msg) } }); } fn open_worktree( &self, worktree_path: &PathBuf, replace_current_window: bool, window: &mut Window, cx: &mut Context<Picker<Self>>, ) { let workspace = self.workspace.clone(); let path = worktree_path.clone(); let Some((connection_options, app_state, is_local)) = workspace .update(cx, |workspace, cx| { let project = workspace.project().clone(); let connection_options = project.read(cx).remote_connection_options(cx); let app_state = workspace.app_state().clone(); let is_local = project.read(cx).is_local(); (connection_options, app_state, is_local) }) .log_err() else { return; }; if is_local { let open_task = workspace.update(cx, |workspace, cx| { workspace.open_workspace_for_paths(replace_current_window, vec![path], window, cx) }); cx.spawn(async move |_, _| { open_task?.await?; anyhow::Ok(()) }) .detach_and_prompt_err( "Failed to open worktree", window, cx, |e, _, _| Some(e.to_string()), ); } else if let Some(connection_options) = connection_options { cx.spawn_in(window, async move |_, cx| { open_remote_worktree( connection_options, vec![path], app_state, workspace, replace_current_window, cx, ) .await }) .detach_and_prompt_err( "Failed to open worktree", window, cx, |e, _, _| Some(e.to_string()), ); } cx.emit(DismissEvent); } fn base_branch<'a>(&'a self, cx: &'a mut Context<Picker<Self>>) -> Option<&'a str> { self.repo .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(); }Lines 1–500 of 900. Load more
use anyhow::Context as _; use collections::HashSet; use fuzzy::StringMatchCandidate; use git::repository::Worktree as GitWorktree; use gpui::{ Action, App, AsyncWindowContext, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, Modifiers, ModifiersChangedEvent, ParentElement, Render, SharedString, Styled, Subscription, Task, WeakEntity, Window, actions, rems, }; use picker::{Picker, PickerDelegate, PickerEditorPosition}; use project::project_settings::ProjectSettings; use project::{ git_store::Repository, trusted_worktrees::{PathTrust, TrustedWorktrees}, }; use remote::{RemoteConnectionOptions, remote_client::ConnectionIdentifier}; use remote_connection::{RemoteConnectionModal, connect}; use settings::Settings; use std::{path::PathBuf, sync::Arc}; use ui::{HighlightedLabel, KeyBinding, ListItem, ListItemSpacing, prelude::*}; use util::ResultExt; use workspace::{ModalView, MultiWorkspace, Workspace, notifications::DetachAndPromptErr}; use crate::git_panel::show_error_toast; actions!( git, [ WorktreeFromDefault, WorktreeFromDefaultOnWindow, DeleteWorktree ] ); pub fn open( workspace: &mut Workspace, _: &zed_actions::git::Worktree, window: &mut Window, cx: &mut Context<Workspace>, ) { let repository = workspace.project().read(cx).active_repository(cx); let workspace_handle = workspace.weak_handle(); workspace.toggle_modal(window, cx, |window, cx| { WorktreeList::new(repository, workspace_handle, rems(34.), window, cx) }) } pub fn create_embedded( repository: Option<Entity<Repository>>, workspace: WeakEntity<Workspace>, width: Rems, window: &mut Window, cx: &mut Context<WorktreeList>, ) -> WorktreeList { WorktreeList::new_embedded(repository, workspace, width, window, cx) } pub struct WorktreeList { width: Rems, pub picker: Entity<Picker<WorktreeListDelegate>>, picker_focus_handle: FocusHandle, _subscription: Option<Subscription>, embedded: bool, } impl WorktreeList { fn new( repository: Option<Entity<Repository>>, workspace: WeakEntity<Workspace>, width: Rems, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let mut this = Self::new_inner(repository, workspace, width, false, window, cx); this._subscription = Some(cx.subscribe(&this.picker, |_, _, _, cx| { cx.emit(DismissEvent); })); this } fn new_inner( repository: Option<Entity<Repository>>, workspace: WeakEntity<Workspace>, width: Rems, embedded: bool, window: &mut Window, cx: &mut Context<Self>, ) -> Self { 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)) }); cx.spawn_in(window, async move |this, cx| { let all_worktrees: Vec<_> = all_worktrees_request .context("No active repository")? .await?? .into_iter() .filter(|worktree| worktree.ref_name.is_some()) // hide worktrees without a branch .collect(); let default_branch = default_branch_request .context("No active repository")? .await .map(Result::ok) .ok() .flatten() .flatten(); this.update_in(cx, |this, window, cx| { this.picker.update(cx, |picker, cx| { picker.delegate.all_worktrees = Some(all_worktrees); picker.delegate.default_branch = default_branch; picker.refresh(window, cx); }) })?; anyhow::Ok(()) }) .detach_and_log_err(cx); let delegate = WorktreeListDelegate::new(workspace, repository, window, cx); let picker = cx.new(|cx| { Picker::uniform_list(delegate, window, cx) .show_scrollbar(true) .modal(!embedded) }); let picker_focus_handle = picker.focus_handle(cx); picker.update(cx, |picker, _| { picker.delegate.focus_handle = picker_focus_handle.clone(); }); Self { picker, picker_focus_handle, width, _subscription: None, embedded, } } fn new_embedded( repository: Option<Entity<Repository>>, workspace: WeakEntity<Workspace>, width: Rems, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let mut this = Self::new_inner(repository, workspace, width, true, window, cx); this._subscription = Some(cx.subscribe(&this.picker, |_, _, _, cx| { cx.emit(DismissEvent); })); this } pub fn handle_modifiers_changed( &mut self, ev: &ModifiersChangedEvent, _: &mut Window, cx: &mut Context<Self>, ) { self.picker .update(cx, |picker, _| picker.delegate.modifiers = ev.modifiers) } pub fn handle_new_worktree( &mut self, replace_current_window: bool, window: &mut Window, cx: &mut Context<Self>, ) { self.picker.update(cx, |picker, cx| { let ix = picker.delegate.selected_index(); let Some(entry) = picker.delegate.matches.get(ix) else { return; }; let Some(default_branch) = picker.delegate.default_branch.clone() else { return; }; if !entry.is_new { return; } picker.delegate.create_worktree( entry.worktree.display_name(), replace_current_window, Some(default_branch.into()), window, cx, ); }) } 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 {} impl Focusable for WorktreeList { fn focus_handle(&self, _: &App) -> FocusHandle { self.picker_focus_handle.clone() } } impl Render for WorktreeList { fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { v_flex() .key_context("GitWorktreeSelector") .w(self.width) .on_modifiers_changed(cx.listener(Self::handle_modifiers_changed)) .on_action(cx.listener(|this, _: &WorktreeFromDefault, w, cx| { this.handle_new_worktree(false, w, cx) })) .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({ cx.listener(move |this, _, window, cx| { this.picker.update(cx, |this, cx| { this.cancel(&Default::default(), window, cx); }) }) }) }) } } #[derive(Debug, Clone)] struct WorktreeEntry { worktree: GitWorktree, positions: Vec<usize>, is_new: bool, } pub struct WorktreeListDelegate { matches: Vec<WorktreeEntry>, all_worktrees: Option<Vec<GitWorktree>>, workspace: WeakEntity<Workspace>, repo: Option<Entity<Repository>>, selected_index: usize, last_query: String, modifiers: Modifiers, focus_handle: FocusHandle, default_branch: Option<SharedString>, } impl WorktreeListDelegate { fn new( workspace: WeakEntity<Workspace>, repo: Option<Entity<Repository>>, _window: &mut Window, cx: &mut Context<WorktreeList>, ) -> Self { Self { matches: vec![], all_worktrees: None, workspace, selected_index: 0, repo, last_query: Default::default(), modifiers: Default::default(), focus_handle: cx.focus_handle(), default_branch: None, } } fn create_worktree( &self, worktree_branch: &str, replace_current_window: bool, commit: Option<String>, window: &mut Window, cx: &mut Context<Picker<Self>>, ) { let Some(repo) = self.repo.clone() else { return; }; let branch = worktree_branch.to_string(); let workspace = self.workspace.clone(); cx.spawn_in(window, async move |_, cx| { let (receiver, new_worktree_path) = repo.update(cx, |repo, cx| { let worktree_directory_setting = ProjectSettings::get_global(cx) .git .worktree_directory .clone(); let new_worktree_path = repo.path_for_new_linked_worktree(&branch, &worktree_directory_setting)?; let receiver = repo.create_worktree(branch.clone(), new_worktree_path.clone(), commit); anyhow::Ok((receiver, new_worktree_path)) })?; receiver.await??; workspace.update(cx, |workspace, cx| { if let Some(trusted_worktrees) = TrustedWorktrees::try_get_global(cx) { let repo_path = &repo.read(cx).snapshot().work_directory_abs_path; let project = workspace.project(); if let Some((parent_worktree, _)) = project.read(cx).find_worktree(repo_path, cx) { let worktree_store = project.read(cx).worktree_store(); trusted_worktrees.update(cx, |trusted_worktrees, cx| { if trusted_worktrees.can_trust( &worktree_store, parent_worktree.read(cx).id(), cx, ) { trusted_worktrees.trust( &worktree_store, HashSet::from_iter([PathTrust::AbsPath( new_worktree_path.clone(), )]), cx, ); } }); } } })?; let (connection_options, app_state, is_local) = workspace.update(cx, |workspace, cx| { let project = workspace.project().clone(); let connection_options = project.read(cx).remote_connection_options(cx); let app_state = workspace.app_state().clone(); let is_local = project.read(cx).is_local(); (connection_options, app_state, is_local) })?; if is_local { workspace .update_in(cx, |workspace, window, cx| { workspace.open_workspace_for_paths( replace_current_window, vec![new_worktree_path], window, cx, ) })? .await?; } else if let Some(connection_options) = connection_options { open_remote_worktree( connection_options, vec![new_worktree_path], app_state, workspace.clone(), replace_current_window, cx, ) .await?; } anyhow::Ok(()) }) .detach_and_prompt_err("Failed to create worktree", window, cx, |e, _, _| { let msg = e.to_string(); if msg.contains("git.worktree_directory") { Some(format!("Invalid git.worktree_directory setting: {}", e)) } else { Some(msg) } }); } fn open_worktree( &self, worktree_path: &PathBuf, replace_current_window: bool, window: &mut Window, cx: &mut Context<Picker<Self>>, ) { let workspace = self.workspace.clone(); let path = worktree_path.clone(); let Some((connection_options, app_state, is_local)) = workspace .update(cx, |workspace, cx| { let project = workspace.project().clone(); let connection_options = project.read(cx).remote_connection_options(cx); let app_state = workspace.app_state().clone(); let is_local = project.read(cx).is_local(); (connection_options, app_state, is_local) }) .log_err() else { return; }; if is_local { let open_task = workspace.update(cx, |workspace, cx| { workspace.open_workspace_for_paths(replace_current_window, vec![path], window, cx) }); cx.spawn(async move |_, _| { open_task?.await?; anyhow::Ok(()) }) .detach_and_prompt_err( "Failed to open worktree", window, cx, |e, _, _| Some(e.to_string()), ); } else if let Some(connection_options) = connection_options { cx.spawn_in(window, async move |_, cx| { open_remote_worktree( connection_options, vec![path], app_state, workspace, replace_current_window, cx, ) .await }) .detach_and_prompt_err( "Failed to open worktree", window, cx, |e, _, _| Some(e.to_string()), ); } cx.emit(DismissEvent); } fn base_branch<'a>(&'a self, cx: &'a mut Context<Picker<Self>>) -> Option<&'a str> { self.repo .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(); }