-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathfzf_err_handling.rs
108 lines (99 loc) · 4.1 KB
/
fzf_err_handling.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
//! # A version of the `fzf` clone with better error handling
//!
//! Read lines from `stdin` in a streaming fashion and populate the picker, imitating the basic
//! functionality of [fzf](https://github.com/junegunn/fzf).
//!
//! Unlike the `fzf` example, this example forwards IO errors to the picker thread and tells it to
//! disconnect.
use std::{
io::{self, IsTerminal},
process::exit,
sync::mpsc::channel,
thread::spawn,
};
use nucleo_picker::{
event::{Event, StdinEventSender},
render::StrRenderer,
Picker,
};
/// The custom error type for our application. We could just use an `io::Error` directly (and also
/// benefit from free error propogation), but for demonstration purposes we also add more context
/// to the IO error with a custom wrapper type.
enum AppError {
Key(io::Error),
Stdin(io::Error),
}
fn main() -> io::Result<()> {
let mut picker = Picker::new(StrRenderer);
// initialize stderr and check that we can actually write to the screen
let mut stderr = io::stderr().lock();
if !stderr.is_terminal() {
eprintln!("Failed to start picker: STDERR not interactive!");
exit(1);
}
// create a new channel. The `sender` end is used to send `Event`s to the picker, and the
// `receiver` end is passed directly to the picker so that it can receive the corresponding
// events
let (sender, receiver) = channel();
// spawn a stdin watcher to read keyboard events and send them to the channel
let stdin_watcher = StdinEventSender::with_default_keybindings(sender.clone());
spawn(move || match stdin_watcher.watch() {
Ok(()) => {
// this path occurs when the picker quits and the receiver is dropped so there
// is no more work to be done
}
Err(io_err) => {
// we received an IO error while trying to read keyboard events, so we recover the
// inner channel and send an `Abort` event to tell the picker to quit immediately
//
// if we do not send the `Abort` event, or any other event which causes the picker to
// quit (such as a `Quit` event), the picker will hang until the thread reading from
// standard input completes, which could be a very long time
let inner = stdin_watcher.into_sender();
// if this fails, the picker already quit
let _ = inner.send(Event::Abort(AppError::Key(io_err)));
return;
}
});
// spawn a reader to read lines from standard input
let injector = picker.injector();
spawn(move || {
let stdin = io::stdin();
if !stdin.is_terminal() {
for line in stdin.lines() {
match line {
// add the line to the match list
Ok(s) => injector.push(s),
Err(io_err) => {
// if we encounter an IO error, we send the corresponding error
// to the picker so that it can abort and propogate the error
//
// it would also be fine to return but not send an abort event
// since the picker will remain interactive with the items it has
// already received.
let _ = sender.send(Event::Abort(AppError::Stdin(io_err)));
return;
}
}
}
}
});
match picker.pick_with_io(receiver, &mut stderr) {
Ok(Some(item)) => {
println!("{item}");
Ok(())
}
Ok(None) => exit(1),
Err(e) => {
// the 'factor' convenience method splits the error into a
// `Result<A, PickError<Infallible>>`; so we just need to handle our application error.
match e.factor()? {
AppError::Key(io_err) => eprintln!("IO error during keyboard input: {io_err}"),
AppError::Stdin(io_err) => {
eprintln!("IO error when reading from standard input: {io_err}")
}
}
exit(1);
}
}
}