1use anyhow::Context as _;
2use collections::HashSet;
3use fuzzy::StringMatchCandidate;
4
5use git::repository::Worktree as GitWorktree;
6use gpui::{
7 Action, App, AsyncWindowContext, Context, DismissEvent, Entity, EventEmitter, FocusHandle,
8 Focusable, InteractiveElement, IntoElement, Modifiers, ModifiersChangedEvent, ParentElement,
9 PathPromptOptions, Render, SharedString, Styled, Subscription, Task, WeakEntity, Window,
10 actions, rems,
11};
12use picker::{Picker, PickerDelegate, PickerEditorPosition};
13use project::{
14 DirectoryLister,
15 git_store::Repository,
16 trusted_worktrees::{PathTrust, TrustedWorktrees},
17};
18use remote::{RemoteConnectionOptions, remote_client::ConnectionIdentifier};
19use remote_connection::{RemoteConnectionModal, connect};
20use std::{path::PathBuf, sync::Arc};
21use ui::{HighlightedLabel, KeyBinding, ListItem, ListItemSpacing, prelude::*};
22use util::ResultExt;
23use workspace::{ModalView, MultiWorkspace, Workspace, notifications::DetachAndPromptErr};
24
25actions!(git, [WorktreeFromDefault, WorktreeFromDefaultOnWindow]);
26
27pub fn open(
28 workspace: &mut Workspace,
29 _: &zed_actions::git::Worktree,
30 window: &mut Window,
31 cx: &mut Context<Workspace>,
32) {
33 let repository = workspace.project().read(cx).active_repository(cx);
34 let workspace_handle = workspace.weak_handle();
35 workspace.toggle_modal(window, cx, |window, cx| {
36 WorktreeList::new(repository, workspace_handle, rems(34.), window, cx)
37 })
38}
39
40pub fn create_embedded(
41 repository: Option<Entity<Repository>>,
42 workspace: WeakEntity<Workspace>,
43 width: Rems,
44 window: &mut Window,
45 cx: &mut Context<WorktreeList>,
46) -> WorktreeList {
47 WorktreeList::new_embedded(repository, workspace, width, window, cx)
48}
49
50pub struct WorktreeList {
51 width: Rems,
52 pub picker: Entity<Picker<WorktreeListDelegate>>,
53 picker_focus_handle: FocusHandle,
54 _subscription: Option<Subscription>,
55 embedded: bool,
56}
57
58impl WorktreeList {
59 fn new(
60 repository: Option<Entity<Repository>>,
61 workspace: WeakEntity<Workspace>,
62 width: Rems,
63 window: &mut Window,
64 cx: &mut Context<Self>,
65 ) -> Self {
66 let mut this = Self::new_inner(repository, workspace, width, false, window, cx);
67 this._subscription = Some(cx.subscribe(&this.picker, |_, _, _, cx| {
68 cx.emit(DismissEvent);
69 }));
70 this
71 }
72
73 fn new_inner(
74 repository: Option<Entity<Repository>>,
75 workspace: WeakEntity<Workspace>,
76 width: Rems,
77 embedded: bool,
78 window: &mut Window,
79 cx: &mut Context<Self>,
80 ) -> Self {
81 let all_worktrees_request = repository
82 .clone()
83 .map(|repository| repository.update(cx, |repository, _| repository.worktrees()));
84
85 let default_branch_request = repository.clone().map(|repository| {
86 repository.update(cx, |repository, _| repository.default_branch(false))
87 });
88
89 cx.spawn_in(window, async move |this, cx| {
90 let all_worktrees = all_worktrees_request
91 .context("No active repository")?
92 .await??;
93
94 let default_branch = default_branch_request
95 .context("No active repository")?
96 .await
97 .map(Result::ok)
98 .ok()
99 .flatten()
100 .flatten();
101
102 this.update_in(cx, |this, window, cx| {
103 this.picker.update(cx, |picker, cx| {
104 picker.delegate.all_worktrees = Some(all_worktrees);
105 picker.delegate.default_branch = default_branch;
106 picker.refresh(window, cx);
107 })
108 })?;
109
110 anyhow::Ok(())
111 })
112 .detach_and_log_err(cx);
113
114 let delegate = WorktreeListDelegate::new(workspace, repository, window, cx);
115 let picker = cx.new(|cx| {
116 Picker::uniform_list(delegate, window, cx)
117 .show_scrollbar(true)
118 .modal(!embedded)
119 });
120 let picker_focus_handle = picker.focus_handle(cx);
121 picker.update(cx, |picker, _| {
122 picker.delegate.focus_handle = picker_focus_handle.clone();
123 });
124
125 Self {
126 picker,
127 picker_focus_handle,
128 width,
129 _subscription: None,
130 embedded,
131 }
132 }
133
134 fn new_embedded(
135 repository: Option<Entity<Repository>>,
136 workspace: WeakEntity<Workspace>,
137 width: Rems,
138 window: &mut Window,
139 cx: &mut Context<Self>,
140 ) -> Self {
141 let mut this = Self::new_inner(repository, workspace, width, true, window, cx);
142 this._subscription = Some(cx.subscribe(&this.picker, |_, _, _, cx| {
143 cx.emit(DismissEvent);
144 }));
145 this
146 }
147
148 pub fn handle_modifiers_changed(
149 &mut self,
150 ev: &ModifiersChangedEvent,
151 _: &mut Window,
152 cx: &mut Context<Self>,
153 ) {
154 self.picker
155 .update(cx, |picker, _| picker.delegate.modifiers = ev.modifiers)
156 }
157
158 pub fn handle_new_worktree(
159 &mut self,
160 replace_current_window: bool,
161 window: &mut Window,
162 cx: &mut Context<Self>,
163 ) {
164 self.picker.update(cx, |picker, cx| {
165 let ix = picker.delegate.selected_index();
166 let Some(entry) = picker.delegate.matches.get(ix) else {
167 return;
168 };
169 let Some(default_branch) = picker.delegate.default_branch.clone() else {
170 return;
171 };
172 if !entry.is_new {
173 return;
174 }
175 picker.delegate.create_worktree(
176 entry.worktree.branch(),
177 replace_current_window,
178 Some(default_branch.into()),
179 window,
180 cx,
181 );
182 })
183 }
184}
185impl ModalView for WorktreeList {}
186impl EventEmitter<DismissEvent> for WorktreeList {}
187
188impl Focusable for WorktreeList {
189 fn focus_handle(&self, _: &App) -> FocusHandle {
190 self.picker_focus_handle.clone()
191 }
192}
193
194impl Render for WorktreeList {
195 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
196 v_flex()
197 .key_context("GitWorktreeSelector")
198 .w(self.width)
199 .on_modifiers_changed(cx.listener(Self::handle_modifiers_changed))
200 .on_action(cx.listener(|this, _: &WorktreeFromDefault, w, cx| {
201 this.handle_new_worktree(false, w, cx)
202 }))
203 .on_action(cx.listener(|this, _: &WorktreeFromDefaultOnWindow, w, cx| {
204 this.handle_new_worktree(true, w, cx)
205 }))
206 .child(self.picker.clone())
207 .when(!self.embedded, |el| {
208 el.on_mouse_down_out({
209 cx.listener(move |this, _, window, cx| {
210 this.picker.update(cx, |this, cx| {
211 this.cancel(&Default::default(), window, cx);
212 })
213 })
214 })
215 })
216 }
217}
218
219#[derive(Debug, Clone)]
220struct WorktreeEntry {
221 worktree: GitWorktree,
222 positions: Vec<usize>,
223 is_new: bool,
224}
225
226pub struct WorktreeListDelegate {
227 matches: Vec<WorktreeEntry>,
228 all_worktrees: Option<Vec<GitWorktree>>,
229 workspace: WeakEntity<Workspace>,
230 repo: Option<Entity<Repository>>,
231 selected_index: usize,
232 last_query: String,
233 modifiers: Modifiers,
234 focus_handle: FocusHandle,
235 default_branch: Option<SharedString>,
236}
237
238impl WorktreeListDelegate {
239 fn new(
240 workspace: WeakEntity<Workspace>,
241 repo: Option<Entity<Repository>>,
242 _window: &mut Window,
243 cx: &mut Context<WorktreeList>,
244 ) -> Self {
245 Self {
246 matches: vec![],
247 all_worktrees: None,
248 workspace,
249 selected_index: 0,
250 repo,
251 last_query: Default::default(),
252 modifiers: Default::default(),
253 focus_handle: cx.focus_handle(),
254 default_branch: None,
255 }
256 }
257
258 fn create_worktree(
259 &self,
260 worktree_branch: &str,
261 replace_current_window: bool,
262 commit: Option<String>,
263 window: &mut Window,
264 cx: &mut Context<Picker<Self>>,
265 ) {
266 let Some(repo) = self.repo.clone() else {
267 return;
268 };
269
270 let worktree_path = self
271 .workspace
272 .clone()
273 .update(cx, |this, cx| {
274 this.prompt_for_open_path(
275 PathPromptOptions {
276 files: false,
277 directories: true,
278 multiple: false,
279 prompt: Some("Select directory for new worktree".into()),
280 },
281 DirectoryLister::Project(this.project().clone()),
282 window,
283 cx,
284 )
285 })
286 .log_err();
287 let Some(worktree_path) = worktree_path else {
288 return;
289 };
290
291 let branch = worktree_branch.to_string();
292 let workspace = self.workspace.clone();
293 cx.spawn_in(window, async move |_, cx| {
294 let Some(paths) = worktree_path.await? else {
295 return anyhow::Ok(());
296 };
297 let path = paths.get(0).cloned().context("No path selected")?;
298
299 repo.update(cx, |repo, _| {
300 repo.create_worktree(branch.clone(), path.clone(), commit)
301 })
302 .await??;
303 let new_worktree_path = path.join(branch);
304
305 workspace.update(cx, |workspace, cx| {
306 if let Some(trusted_worktrees) = TrustedWorktrees::try_get_global(cx) {
307 let repo_path = &repo.read(cx).snapshot().work_directory_abs_path;
308 let project = workspace.project();
309 if let Some((parent_worktree, _)) =
310 project.read(cx).find_worktree(repo_path, cx)
311 {
312 let worktree_store = project.read(cx).worktree_store();
313 trusted_worktrees.update(cx, |trusted_worktrees, cx| {
314 if trusted_worktrees.can_trust(
315 &worktree_store,
316 parent_worktree.read(cx).id(),
317 cx,
318 ) {
319 trusted_worktrees.trust(
320 &worktree_store,
321 HashSet::from_iter([PathTrust::AbsPath(
322 new_worktree_path.clone(),
323 )]),
324 cx,
325 );
326 }
327 });
328 }
329 }
330 })?;
331
332 let (connection_options, app_state, is_local) =
333 workspace.update(cx, |workspace, cx| {
334 let project = workspace.project().clone();
335 let connection_options = project.read(cx).remote_connection_options(cx);
336 let app_state = workspace.app_state().clone();
337 let is_local = project.read(cx).is_local();
338 (connection_options, app_state, is_local)
339 })?;
340
341 if is_local {
342 workspace
343 .update_in(cx, |workspace, window, cx| {
344 workspace.open_workspace_for_paths(
345 replace_current_window,
346 vec![new_worktree_path],
347 window,
348 cx,
349 )
350 })?
351 .await?;
352 } else if let Some(connection_options) = connection_options {
353 open_remote_worktree(
354 connection_options,
355 vec![new_worktree_path],
356 app_state,
357 workspace.clone(),
358 replace_current_window,
359 cx,
360 )
361 .await?;
362 }
363
364 anyhow::Ok(())
365 })
366 .detach_and_prompt_err("Failed to create worktree", window, cx, |e, _, _| {
367 Some(e.to_string())
368 });
369 }
370
371 fn open_worktree(
372 &self,
373 worktree_path: &PathBuf,
374 replace_current_window: bool,
375 window: &mut Window,
376 cx: &mut Context<Picker<Self>>,
377 ) {
378 let workspace = self.workspace.clone();
379 let path = worktree_path.clone();
380
381 let Some((connection_options, app_state, is_local)) = workspace
382 .update(cx, |workspace, cx| {
383 let project = workspace.project().clone();
384 let connection_options = project.read(cx).remote_connection_options(cx);
385 let app_state = workspace.app_state().clone();
386 let is_local = project.read(cx).is_local();
387 (connection_options, app_state, is_local)
388 })
389 .log_err()
390 else {
391 return;
392 };
393
394 if is_local {
395 let open_task = workspace.update(cx, |workspace, cx| {
396 workspace.open_workspace_for_paths(replace_current_window, vec![path], window, cx)
397 });
398 cx.spawn(async move |_, _| {
399 open_task?.await?;
400 anyhow::Ok(())
401 })
402 .detach_and_prompt_err(
403 "Failed to open worktree",
404 window,
405 cx,
406 |e, _, _| Some(e.to_string()),
407 );
408 } else if let Some(connection_options) = connection_options {
409 cx.spawn_in(window, async move |_, cx| {
410 open_remote_worktree(
411 connection_options,
412 vec![path],
413 app_state,
414 workspace,
415 replace_current_window,
416 cx,
417 )
418 .await
419 })
420 .detach_and_prompt_err(
421 "Failed to open worktree",
422 window,
423 cx,
424 |e, _, _| Some(e.to_string()),
425 );
426 }
427
428 cx.emit(DismissEvent);
429 }
430
431 fn base_branch<'a>(&'a self, cx: &'a mut Context<Picker<Self>>) -> Option<&'a str> {
432 self.repo
433 .as_ref()
434 .and_then(|repo| repo.read(cx).branch.as_ref().map(|b| b.name()))
435 }
436}
437
438async fn open_remote_worktree(
439 connection_options: RemoteConnectionOptions,
440 paths: Vec<PathBuf>,
441 app_state: Arc<workspace::AppState>,
442 workspace: WeakEntity<Workspace>,
443 replace_current_window: bool,
444 cx: &mut AsyncWindowContext,
445) -> anyhow::Result<()> {
446 let workspace_window = cx
447 .window_handle()
448 .downcast::<MultiWorkspace>()
449 .ok_or_else(|| anyhow::anyhow!("Window is not a Workspace window"))?;
450
451 let connect_task = workspace.update_in(cx, |workspace, window, cx| {
452 workspace.toggle_modal(window, cx, |window, cx| {
453 RemoteConnectionModal::new(&connection_options, Vec::new(), window, cx)
454 });
455
456 let prompt = workspace
457 .active_modal::<RemoteConnectionModal>(cx)
458 .expect("Modal just created")
459 .read(cx)
460 .prompt
461 .clone();
462
463 connect(
464 ConnectionIdentifier::setup(),
465 connection_options.clone(),
466 prompt,
467 window,
468 cx,
469 )
470 .prompt_err("Failed to connect", window, cx, |_, _, _| None)
471 })?;
472
473 let session = connect_task.await;
474
475 workspace
476 .update_in(cx, |workspace, _window, cx| {
477 if let Some(prompt) = workspace.active_modal::<RemoteConnectionModal>(cx) {
478 prompt.update(cx, |prompt, cx| prompt.finished(cx))
479 }
480 })
481 .ok();
482
483 let Some(Some(session)) = session else {
484 return Ok(());
485 };
486
487 let new_project: Entity<project::Project> = cx.update(|_, cx| {
488 project::Project::remote(
489 session,
490 app_state.client.clone(),
491 app_state.node_runtime.clone(),
492 app_state.user_store.clone(),
493 app_state.languages.clone(),
494 app_state.fs.clone(),
495 true,
496 cx,
497 )
498 })?;
499
500 let window_to_use = if replace_current_window {
501 workspace_window
502 } else {
503 let workspace_position = cx
504 .update(|_, cx| {
505 workspace::remote_workspace_position_from_db(connection_options.clone(), &paths, cx)
506 })?
507 .await
508 .context("fetching workspace position from db")?;
509
510 let mut options =
511 cx.update(|_, cx| (app_state.build_window_options)(workspace_position.display, cx))?;
512 options.window_bounds = workspace_position.window_bounds;
513
514 cx.open_window(options, |window, cx| {
515 let workspace = cx.new(|cx| {
516 let mut workspace =
517 Workspace::new(None, new_project.clone(), app_state.clone(), window, cx);
518 workspace.centered_layout = workspace_position.centered_layout;
519 workspace
520 });
521 cx.new(|cx| MultiWorkspace::new(workspace, window, cx))
522 })?
523 };
524
525 workspace::open_remote_project_with_existing_connection(
526 connection_options,
527 new_project,
528 paths,
529 app_state,
530 window_to_use,
531 cx,
532 )
533 .await?;
534
535 Ok(())
536}
537
538impl PickerDelegate for WorktreeListDelegate {
539 type ListItem = ListItem;
540
541 fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
542 "Select worktree…".into()
543 }
544
545 fn editor_position(&self) -> PickerEditorPosition {
546 PickerEditorPosition::Start
547 }
548
549 fn match_count(&self) -> usize {
550 self.matches.len()
551 }
552
553 fn selected_index(&self) -> usize {
554 self.selected_index
555 }
556
557 fn set_selected_index(
558 &mut self,
559 ix: usize,
560 _window: &mut Window,
561 _: &mut Context<Picker<Self>>,
562 ) {
563 self.selected_index = ix;
564 }
565
566 fn update_matches(
567 &mut self,
568 query: String,
569 window: &mut Window,
570 cx: &mut Context<Picker<Self>>,
571 ) -> Task<()> {
572 let Some(all_worktrees) = self.all_worktrees.clone() else {
573 return Task::ready(());
574 };
575
576 cx.spawn_in(window, async move |picker, cx| {
577 let mut matches: Vec<WorktreeEntry> = if query.is_empty() {
578 all_worktrees
579 .into_iter()
580 .map(|worktree| WorktreeEntry {
581 worktree,
582 positions: Vec::new(),
583 is_new: false,
584 })
585 .collect()
586 } else {
587 let candidates = all_worktrees
588 .iter()
589 .enumerate()
590 .map(|(ix, worktree)| StringMatchCandidate::new(ix, worktree.branch()))
591 .collect::<Vec<StringMatchCandidate>>();
592 fuzzy::match_strings(
593 &candidates,
594 &query,
595 true,
596 true,
597 10000,
598 &Default::default(),
599 cx.background_executor().clone(),
600 )
601 .await
602 .into_iter()
603 .map(|candidate| WorktreeEntry {
604 worktree: all_worktrees[candidate.candidate_id].clone(),
605 positions: candidate.positions,
606 is_new: false,
607 })
608 .collect()
609 };
610 picker
611 .update(cx, |picker, _| {
612 if !query.is_empty()
613 && !matches
614 .first()
615 .is_some_and(|entry| entry.worktree.branch() == query)
616 {
617 let query = query.replace(' ', "-");
618 matches.push(WorktreeEntry {
619 worktree: GitWorktree {
620 path: Default::default(),
621 ref_name: format!("refs/heads/{query}").into(),
622 sha: Default::default(),
623 },
624 positions: Vec::new(),
625 is_new: true,
626 })
627 }
628 let delegate = &mut picker.delegate;
629 delegate.matches = matches;
630 if delegate.matches.is_empty() {
631 delegate.selected_index = 0;
632 } else {
633 delegate.selected_index =
634 core::cmp::min(delegate.selected_index, delegate.matches.len() - 1);
635 }
636 delegate.last_query = query;
637 })
638 .log_err();
639 })
640 }
641
642 fn confirm(&mut self, secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
643 let Some(entry) = self.matches.get(self.selected_index()) else {
644 return;
645 };
646 if entry.is_new {
647 self.create_worktree(&entry.worktree.branch(), secondary, None, window, cx);
648 } else {
649 self.open_worktree(&entry.worktree.path, secondary, window, cx);
650 }
651
652 cx.emit(DismissEvent);
653 }
654
655 fn dismissed(&mut self, _: &mut Window, cx: &mut Context<Picker<Self>>) {
656 cx.emit(DismissEvent);
657 }
658
659 fn render_match(
660 &self,
661 ix: usize,
662 selected: bool,
663 _window: &mut Window,
664 cx: &mut Context<Picker<Self>>,
665 ) -> Option<Self::ListItem> {
666 let entry = &self.matches.get(ix)?;
667 let path = entry.worktree.path.to_string_lossy().to_string();
668 let sha = entry
669 .worktree
670 .sha
671 .clone()
672 .chars()
673 .take(7)
674 .collect::<String>();
675
676 let (branch_name, sublabel) = if entry.is_new {
677 (
678 Label::new(format!("Create Worktree: \"{}\"…", entry.worktree.branch()))
679 .truncate()
680 .into_any_element(),
681 format!(
682 "based off {}",
683 self.base_branch(cx).unwrap_or("the current branch")
684 ),
685 )
686 } else {
687 let branch = entry.worktree.branch();
688 let branch_first_line = branch.lines().next().unwrap_or(branch);
689 let positions: Vec<_> = entry
690 .positions
691 .iter()
692 .copied()
693 .filter(|&pos| pos < branch_first_line.len())
694 .collect();
695
696 (
697 HighlightedLabel::new(branch_first_line.to_owned(), positions)
698 .truncate()
699 .into_any_element(),
700 path,
701 )
702 };
703
704 Some(
705 ListItem::new(format!("worktree-menu-{ix}"))
706 .inset(true)
707 .spacing(ListItemSpacing::Sparse)
708 .toggle_state(selected)
709 .child(
710 v_flex()
711 .w_full()
712 .child(
713 h_flex()
714 .gap_2()
715 .justify_between()
716 .overflow_x_hidden()
717 .child(branch_name)
718 .when(!entry.is_new, |this| {
719 this.child(
720 Label::new(sha)
721 .size(LabelSize::Small)
722 .color(Color::Muted)
723 .buffer_font(cx)
724 .into_element(),
725 )
726 }),
727 )
728 .child(
729 Label::new(sublabel)
730 .size(LabelSize::Small)
731 .color(Color::Muted)
732 .truncate()
733 .into_any_element(),
734 ),
735 ),
736 )
737 }
738
739 fn no_matches_text(&self, _window: &mut Window, _cx: &mut App) -> Option<SharedString> {
740 Some("No worktrees found".into())
741 }
742
743 fn render_footer(&self, _: &mut Window, cx: &mut Context<Picker<Self>>) -> Option<AnyElement> {
744 let focus_handle = self.focus_handle.clone();
745 let selected_entry = self.matches.get(self.selected_index);
746 let is_creating = selected_entry.is_some_and(|entry| entry.is_new);
747
748 let footer_container = h_flex()
749 .w_full()
750 .p_1p5()
751 .gap_0p5()
752 .justify_end()
753 .border_t_1()
754 .border_color(cx.theme().colors().border_variant);
755
756 if is_creating {
757 let from_default_button = self.default_branch.as_ref().map(|default_branch| {
758 Button::new(
759 "worktree-from-default",
760 format!("Create from: {default_branch}"),
761 )
762 .key_binding(
763 KeyBinding::for_action_in(&WorktreeFromDefault, &focus_handle, cx)
764 .map(|kb| kb.size(rems_from_px(12.))),
765 )
766 .on_click(|_, window, cx| {
767 window.dispatch_action(WorktreeFromDefault.boxed_clone(), cx)
768 })
769 });
770
771 let current_branch = self.base_branch(cx).unwrap_or("current branch");
772
773 Some(
774 footer_container
775 .when_some(from_default_button, |this, button| this.child(button))
776 .child(
777 Button::new(
778 "worktree-from-current",
779 format!("Create from: {current_branch}"),
780 )
781 .key_binding(
782 KeyBinding::for_action_in(&menu::Confirm, &focus_handle, cx)
783 .map(|kb| kb.size(rems_from_px(12.))),
784 )
785 .on_click(|_, window, cx| {
786 window.dispatch_action(menu::Confirm.boxed_clone(), cx)
787 }),
788 )
789 .into_any(),
790 )
791 } else {
792 Some(
793 footer_container
794 .child(
795 Button::new("open-in-new-window", "Open in New Window")
796 .key_binding(
797 KeyBinding::for_action_in(&menu::Confirm, &focus_handle, cx)
798 .map(|kb| kb.size(rems_from_px(12.))),
799 )
800 .on_click(|_, window, cx| {
801 window.dispatch_action(menu::Confirm.boxed_clone(), cx)
802 }),
803 )
804 .child(
805 Button::new("open-in-window", "Open")
806 .key_binding(
807 KeyBinding::for_action_in(
808 &menu::SecondaryConfirm,
809 &focus_handle,
810 cx,
811 )
812 .map(|kb| kb.size(rems_from_px(12.))),
813 )
814 .on_click(|_, window, cx| {
815 window.dispatch_action(menu::SecondaryConfirm.boxed_clone(), cx)
816 }),
817 )
818 .into_any(),
819 )
820 }
821 }
822}