Skip to main content

nucleo_picker/
event.rs

1//! # Extended event handling
2//!
3//! This module defines the core [`Event`] type handled by a [`Picker`](crate::Picker), which
4//! defines an interactive update to the picker state.
5//!
6//! By default, the interactive picker launched by [`Picker::pick`](crate::Picker::pick) watches
7//! for terminal events (such as key presses) and maps them to [`Event`]s. The process of reading
8//! events is encapsulated in the [`EventSource`] trait, which you can implement yourself and pass
9//! directly to the picker using the [`Picker::pick_with_io`](crate::Picker::pick_with_io).
10//!
11//! Jump to:
12//! - The [`EventSource`] trait.
13//! - The [`StdinReader`], for automatically reading events from standard input, with customizable
14//!   keybindings.
15//! - The [`StdinEventSender`] to read events from standard input and send them through a
16//!   [mpsc channel](std::sync::mpsc::channel).
17//! - The [default keybindings](keybind_default), which are also useful to provide fallbacks for
18//!   keybind customization
19//!
20//! For somewhat comprehensive examples, see the [extended fzf
21//! example](https://github.com/autobib/nucleo-picker/blob/master/examples/fzf_err_handling.rs) or
22//! the [restart
23//! example](https://github.com/autobib/nucleo-picker/blob/master/examples/restart.rs).
24
25mod bind;
26
27use std::{
28    convert::Infallible,
29    io,
30    marker::PhantomData,
31    sync::mpsc::{Receiver, RecvTimeoutError, Sender},
32    time::Duration,
33};
34
35use crossterm::event::{KeyEvent, poll, read};
36
37use self::bind::convert_crossterm_event;
38
39pub use self::bind::{keybind_default, keybind_no_multi};
40pub use crate::{match_list::MatchListEvent, observer::Observer, prompt::PromptEvent};
41
42/// An event which controls the picker behaviour.
43///
44/// The type parameter `A` is the application-defined error which can be used to propagate
45/// application errors to the thread where the picker is running.
46///
47/// Most events are explained directly in the enum variant documentation. A few special cases
48/// require a bit more detail: [redraw](#redraw),
49/// [application-defined abort](#application-defined-abort), and [restart](#restart)
50///
51/// ## Redraw
52/// In most cases, it is not necessary to manually send an [`Event::Redraw`] since the default
53/// behaviour of the picker is to automatically redraw on each frame if the state of the screen
54/// would change when handling an event, or when the item list is updated internally.
55///
56/// There is no `Resize` variant since the screen size is automatically checked immediately before
57/// drawing to the screen. If you are generating your own events, propagate a screen resize as a
58/// [`Event::Redraw`], which will force a redraw to respect the new screen size.
59///
60/// ## Application-defined abort
61/// The abort event is a special event used to propagate errors from the application to the picker.
62/// When the picker receives an abort event, it immediately terminates and passes the abort event
63/// onwards inside the [`PickError::Aborted`](crate::error::PickError::Aborted) error variant.
64///
65/// By default, the associated type parameter is `!`, which means that [`Event::Abort`] cannot be
66/// constructed in ordinary circumstances. In order to generate [`Event::Abort`], you must use the
67/// [`Picker::pick_with_io`](crate::Picker::pick_with_io) method and pass an appropriate
68/// [`EventSource`] which generates your desired errors.
69///
70/// The provided [`EventSource`] implementations, namely [`StdinReader`] and
71/// [`mpsc::Receiver`](std::sync::mpsc::Receiver), are both generic over the same type parameter
72/// `A` so you can construct this variant with a custom error type if desired.
73///
74/// ## Restart
75/// The [`Event::Restart`] is used to restart the picker while it is still running. After a
76/// restart, all previously created [`Injector`]s become invalidated and the match list is
77/// cleared on the next frame. Therefore to receive a valid [`Injector`], the caller must
78/// watch for new injectors using the [`Observer`] returned by
79/// [`Picker::injector_observer`](crate::Picker::injector_observer`).
80///
81/// When the [`Event::Restart`] is processed by the picker, it will clear the item list and
82/// immediately update the observer with the new [`Injector`]. If the send fails because
83/// there is no receiver, the picker will fail with
84/// [`PickError::Disconnected`](crate::error::PickError::Disconnected). The picker will overwrite any
85/// previously pushed [`Injector`] when pushing the updated one to the channel. In particular,
86/// the [`Injector`] in the channel (if any) is always the most up-to-date.
87///
88/// It is possible that no [`Injector`] will be sent if the picker exits or disconnects
89/// before the event is processed.
90///
91/// For a detailed implementation example, see the [restart
92/// example](https://github.com/autobib/nucleo-picker/blob/master/examples/restart.rs).
93///
94/// [`Injector`]: crate::Injector
95#[non_exhaustive]
96pub enum Event<A = Infallible> {
97    /// Modify the prompt.
98    Prompt(PromptEvent),
99    /// Modify the list of matches.
100    MatchList(MatchListEvent),
101    /// Add or remove the highlighted item from the selection list.
102    // ToggleSelection,
103    /// Quit the picker (no selection).
104    Quit,
105    /// Quit the picker (no selection) if the prompt is empty.
106    QuitPromptEmpty,
107    /// Abort the picker (error) at user request.
108    UserInterrupt,
109    /// Abort the picker (error) for another reason.
110    Abort(A),
111    /// Redraw the screen.
112    Redraw,
113    /// Quit the picker by selecting either the queued selections or the highlighted item if no
114    /// selections are queued.
115    Select,
116    /// Restart the picker, invalidating all existing injectors.
117    Restart,
118}
119
120/// The result of waiting for an update from an [`EventSource`] with a timeout.
121///
122/// This is quite similar to the standard library
123/// [`mpsc::RecvTimeoutError`](std::sync::mpsc::RecvTimeoutError), but also permitting an
124/// [`io::Error`] which may result from reading from standard input.
125pub enum RecvError {
126    /// No event was received because we timed out.
127    Timeout,
128    /// The source is disconnected and there are no more messages.
129    Disconnected,
130    /// An IO error occurred while trying to read an event.
131    IO(io::Error),
132}
133
134impl From<io::Error> for RecvError {
135    fn from(err: io::Error) -> Self {
136        Self::IO(err)
137    }
138}
139
140impl From<RecvTimeoutError> for RecvError {
141    fn from(value: RecvTimeoutError) -> Self {
142        match value {
143            RecvTimeoutError::Timeout => Self::Timeout,
144            RecvTimeoutError::Disconnected => Self::Disconnected,
145        }
146    }
147}
148
149/// An abstraction over sources of [`Event`]s which drive a [`Picker`](crate::Picker).
150///
151/// Usually, you do not need to implement this trait yourself and can instead use one of the
152/// provided implementations:
153///
154/// - An implementation for [`StdinReader`], which reads key events interactively from standard
155///   input and supports custom key bindings.
156/// - An implementation for the [`Receiver`] end of a [`sync::mpsc`](std::sync::mpsc) channel.
157///
158/// The [`Receiver`] implementation means, in most cases, you can simply run an event driver in a
159/// separate thread and pass the receiver to the [`Picker`](crate::Picker). This might also be
160/// useful when co-existing with other parts of the application which might themselves generate
161/// events which are relevant for a picker. Also see the [`StdinEventSender`] struct.
162///
163/// ## Debouncing
164/// The picker automatically debounces incoming events, so you do not need to handle this yourself.
165/// However, since there are limitations to the commutativity of events, if the event stream is
166/// very overactive, the picker may still lag.
167///
168/// ## Associated `AbortErr` type
169/// The associated `AbortErr` type defines the application-specific error type which may be
170/// propagated directly to the picker. This is the same type as present in
171/// [`PickError::Aborted`](crate::error::PickError) as well as [`Event::Abort`].
172///
173/// If you do not need to construct this variant at all, you should set `AbortErr = !` so that
174/// you do not need to match on the corresponding [`PickError`](crate::error::PickError) variant.
175///
176/// The provided implementations for [`StdinReader`] and [`Receiver`] are both generic over a type
177/// parameter `A` which defaults to `A = !`. This type parameter is used as `AbortErr` in the
178/// provided [`EventSource`] implementation.
179///
180/// ## Implementation example
181/// Here is an example implementation for a `crossbeam::channel::Receiver`. This is identical to
182/// the implementation for [`mpsc::Receiver`](std::sync::mpsc::Receiver).
183/// ```
184/// use std::time::Duration;
185///
186/// use crossbeam::channel::{Receiver, RecvTimeoutError};
187/// use nucleo_picker::event::{Event, EventSource, RecvError};
188///
189/// struct EventReceiver<A> {
190///     inner: Receiver<Event<A>>
191/// }
192///
193/// impl<A> EventSource for EventReceiver<A> {
194///     type AbortErr = A;
195///
196///     fn recv_timeout(&mut self, duration: Duration) -> Result<Event<A>, RecvError> {
197///         self.inner.recv_timeout(duration).map_err(|err| match err {
198///             RecvTimeoutError::Timeout => RecvError::Timeout,
199///             RecvTimeoutError::Disconnected => RecvError::Disconnected,
200///         })
201///     }
202/// }
203/// ```
204///
205/// ## Usage example
206/// This is a partial usage example illustrating how to use a [`Receiver`]
207///
208/// In order to complete this example, one should also call
209/// [`Picker::pick_with_io`](crate::Picker::pick_with_io) using the
210/// receiver end of the channel.
211///
212/// For the full version of this example with these additional components, visit the [example on
213/// GitHub](https://github.com/autobib/nucleo-picker/blob/master/examples/fzf_err_handling.rs)
214/// ```
215/// use std::{
216///     io::{self, BufRead},
217///     sync::mpsc::channel,
218///     thread::spawn,
219/// };
220///
221/// use nucleo_picker::{
222///     event::{Event, StdinEventSender},
223///     render::StrRenderer,
224///     Picker,
225/// };
226///
227///
228/// // initialize a mpsc channel; we use see the 'sender' end to communicate with the picker
229/// let (sender, receiver) = channel();
230///
231/// let mut picker = Picker::new(StrRenderer);
232///
233/// // spawn a stdin watcher to read keyboard events and send them to the channel
234/// let stdin_watcher = StdinEventSender::with_default_keybindings(sender.clone());
235/// spawn(move || match stdin_watcher.watch() {
236///     Ok(()) => {
237///         // this path occurs when the picker quits and the receiver is dropped so there
238///         // is no more work to be done
239///     }
240///     Err(io_err) => {
241///         // we received an IO error while trying to read keyboard events, so we recover the
242///         // inner channel and send an `Abort` event to tell the picker to quit immediately
243///         //
244///         // if we do not send the `Abort` event, or any other event which causes the picker to
245///         // quit (such as a `Quit` event), the picker will hang until the thread reading from
246///         // standard input completes, which could be a very long time
247///         let inner = stdin_watcher.into_sender();
248///         // if this fails, the picker already quit
249///         let _ = inner.send(Event::Abort(io_err));
250///         return;
251///     }
252/// });
253///
254/// // read input from standard input
255/// let injector = picker.injector();
256/// spawn(move || {
257///     // in practice, one should also check that `stdin` is not interactive using `IsTerminal`.
258///     let stdin = io::stdin();
259///     for line in stdin.lines() {
260///         match line {
261///             Ok(s) => injector.push(s),
262///             Err(io_err) => {
263///                 // if we encounter an IO error, we send the corresponding error
264///                 // to the picker so that it can abort and propagate the error
265///                 //
266///                 // here, it is also safe to simply ignore the IO error since the picker will
267///                 // remain interactive with the items it has already received.
268///                 let _ = sender.send(Event::Abort(io_err));
269///                 return;
270///             }
271///         }
272///     }
273/// });
274/// ```
275pub trait EventSource {
276    /// The application-defined abort error propagated to the picker.
277    type AbortErr;
278
279    /// Receive a new event, timing out after the provided duration.
280    ///
281    /// If the receiver times out, the implementation should return a [`RecvError::Timeout`].
282    /// If the receiver cannot receive any more events, the implementation should return a
283    /// [`RecvError::Disconnected`]. Otherwise, return one of the other variants.
284    fn recv_timeout(&mut self, duration: Duration) -> Result<Event<Self::AbortErr>, RecvError>;
285}
286
287impl<A> EventSource for Receiver<Event<A>> {
288    type AbortErr = A;
289
290    fn recv_timeout(&mut self, duration: Duration) -> Result<Event<A>, RecvError> {
291        Self::recv_timeout(self, duration).map_err(From::from)
292    }
293}
294
295/// An [`EventSource`] implementation which reads events from [`io::Stdin`] and maps key
296/// events to events using a keybind closure.
297///
298/// The default implementation uses the [`keybind_default`] function for keybindings.
299///
300/// ## Customizing keybindings
301///
302/// The default keybindings are documented
303/// [here](https://github.com/autobib/nucleo-picker/blob/master/USAGE.md#keyboard-shortcuts). When
304/// modifying keybindings, if you are targeting Windows as a platform, you probably want to check
305/// for [`KeyEventKind::Press`](crossterm::event::KeyEventKind::Press) or you may get duplicated
306/// events.
307///
308/// ## Example
309///
310/// Use the [`keybind_default`] function to simplify your implementation of keybindings:
311/// ```
312/// use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
313/// use nucleo_picker::event::{keybind_default, Event, StdinReader};
314///
315/// /// Keybindings which use the default keybindings, but instead of interrupting on `ctrl + c`,
316/// /// instead performs a normal quit action. Generic over all possible `Event` type parameters
317/// /// for flexibility.
318/// fn keybind_no_interrupt<A>(key_event: KeyEvent) -> Option<Event<A>> {
319///     match key_event {
320///         KeyEvent {
321///             kind: KeyEventKind::Press,
322///             modifiers: KeyModifiers::CONTROL,
323///             code: KeyCode::Char('c'),
324///             ..
325///         } => Some(Event::Quit),
326///         e => keybind_default(e),
327///     }
328/// }
329/// ```
330pub struct StdinReader<A = Infallible, F = fn(KeyEvent) -> Option<Event<A>>> {
331    keybind: F,
332    _abort: PhantomData<A>,
333}
334
335impl<A> Default for StdinReader<A> {
336    fn default() -> Self {
337        Self::new(keybind_default)
338    }
339}
340
341impl<A, F: FnMut(KeyEvent) -> Option<Event<A>>> StdinReader<A, F> {
342    /// Create a new [`StdinReader`] with keybindings provided by the given closure.
343    pub fn new(keybind: F) -> Self {
344        Self {
345            keybind,
346            _abort: PhantomData,
347        }
348    }
349}
350
351impl<A, F: FnMut(KeyEvent) -> Option<Event<A>>> EventSource for StdinReader<A, F> {
352    type AbortErr = A;
353
354    fn recv_timeout(&mut self, duration: Duration) -> Result<Event<A>, RecvError> {
355        if poll(duration)?
356            && let Some(event) = convert_crossterm_event(read()?, &mut self.keybind)
357        {
358            return Ok(event);
359        };
360        Err(RecvError::Timeout)
361    }
362}
363
364/// A wrapper for a [`Sender`] which reads events from standard input and sends them to the
365/// channel.
366///
367/// The internal implementation is identical to the [`StdinReader`] struct, but instead of
368/// generating the events directly, sends them to the channel.
369pub struct StdinEventSender<A = Infallible, F = fn(KeyEvent) -> Option<Event<A>>> {
370    sender: Sender<Event<A>>,
371    keybind: F,
372}
373
374impl<A> StdinEventSender<A> {
375    /// Initialize a new [`StdinEventSender`] with default keybindings in the provided channel.
376    pub fn with_default_keybindings(sender: Sender<Event<A>>) -> Self {
377        Self {
378            sender,
379            keybind: keybind_default,
380        }
381    }
382}
383
384impl<A, F: Fn(KeyEvent) -> Option<Event<A>>> StdinEventSender<A, F> {
385    /// Watch for events until either the receiver is dropped (in which case `Ok(())` is returned),
386    /// or there is an IO error while reading from standard input. This method will block the
387    /// current thread until the channel disconnects or a read fails.
388    ///
389    /// This method is only compatible with keybindings which do not mutate internal state. For a
390    /// version which permits mutation, see [`watch_mut`](Self::watch_mut).
391    pub fn watch(&self) -> io::Result<()> {
392        loop {
393            if let Some(event) = convert_crossterm_event(read()?, &self.keybind)
394                && self.sender.send(event).is_err()
395            {
396                return Ok(());
397            }
398        }
399    }
400}
401
402impl<A, F: FnMut(KeyEvent) -> Option<Event<A>>> StdinEventSender<A, F> {
403    /// Initialize a new [`StdinEventSender`] with the given keybindings in the provided channel.
404    pub fn new(sender: Sender<Event<A>>, keybind: F) -> Self {
405        Self { sender, keybind }
406    }
407
408    /// Convert into the inner [`Sender<Event>`] to send further events when finished.
409    pub fn into_sender(self) -> Sender<Event<A>> {
410        self.sender
411    }
412
413    /// Watch for events until either the receiver is dropped (in which case `Ok(())` is returned),
414    /// or there is an IO error while reading from standard input. This method will block the
415    /// current thread until the channel disconnects or a read fails.
416    ///
417    /// If the mutable self reference is inconvenient and your keybindings do not mutate internal
418    /// state, use [`watch`](Self::watch).
419    pub fn watch_mut(&mut self) -> io::Result<()> {
420        loop {
421            if let Some(event) = convert_crossterm_event(read()?, &mut self.keybind)
422                && self.sender.send(event).is_err()
423            {
424                return Ok(());
425            }
426        }
427    }
428}