1use std::{borrow::Cow, sync::Arc};
2
3use crossbeam_channel::unbounded;
4use skim::{prelude::SkimOptionsBuilder, Skim, SkimItem, SkimOptions};
5
6pub trait SkimPick {
7 fn pick<T: ToString + Send + Sync + 'static>(
8 self,
9 items: impl IntoIterator<Item = T>,
10 ) -> Option<T>;
11}
12impl SkimPick for SkimOptions<'_> {
13 fn pick<T: ToString + Send + Sync + 'static>(
14 self,
15 items: impl IntoIterator<Item = T>,
16 ) -> Option<T> {
17 pick_with_options(items, self)
18 }
19}
20impl SkimPick for SkimOptionsBuilder<'_> {
21 fn pick<T: ToString + Send + Sync + 'static>(
22 mut self,
23 items: impl IntoIterator<Item = T>,
24 ) -> Option<T> {
25 pick_with_options(items, self.build().ok()?)
26 }
27}
28
29pub fn pick_with_options<T: ToString + Send + Sync + 'static>(
30 items: impl IntoIterator<Item = T>,
31 options: SkimOptions,
32) -> Option<T> {
33 let (tx, rx) = unbounded();
34 for item in items {
35 let item: Arc<dyn SkimItem> = Arc::new(Item(Some(item)));
36 tx.send(item).ok()?
37 }
38 drop(tx);
39
40 let choice = Skim::run_with(&options, Some(rx))?;
41 let mut choice = choice.selected_items.into_iter().next()?;
42 let item = Arc::get_mut(&mut choice)?
43 .as_any_mut()
44 .downcast_mut::<Item<_>>()?;
45 item.0.take()
46}
47
48pub fn pick<T: ToString + Send + Sync + 'static>(items: impl IntoIterator<Item = T>) -> Option<T> {
49 let config = SkimOptions::default();
50 pick_with_options(items, config)
51}
52
53struct Item<T>(Option<T>);
54impl<T: ToString + Send + Sync + 'static> SkimItem for Item<T> {
55 fn text(&self) -> Cow<str> {
56 if let Some(ref v) = self.0 {
57 Cow::Owned(v.to_string())
58 } else {
59 Cow::Borrowed("")
60 }
61 }
62}