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}