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
//! Abstractions for reading input from the console using Rustyline.
use crate::prelude::*;
use rustyline::DefaultEditor;
use rustyline::error::ReadlineError;
use std::io;
/// Read stdin with Rustyline, allowing for line editing and history.
pub fn new_reader(prompt: &str) -> Result<Box<dyn Read>> {
let editor = DefaultEditor::new().expect("Failed to create Rustyline editor");
Ok(Box::new(RustyLineReader::new(editor, prompt)))
}
/// An adapter that wraps a Rustyline editor and implements `std::io::Read`.
struct RustyLineReader {
editor: DefaultEditor,
prompt: String,
buffer: Vec<u8>,
}
impl RustyLineReader {
fn new(editor: DefaultEditor, prompt: &str) -> Self {
Self { editor, prompt: prompt.to_string(), buffer: Vec::new() }
}
}
impl Read for RustyLineReader {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
// If our internal buffer is empty, we need to block and fetch the next line
if self.buffer.is_empty() {
match self.editor.readline(&self.prompt) {
Ok(line) => {
// Add the line to history (optional)
let _ = self.editor.add_history_entry(line.as_str()).unwrap();
// Convert the line to bytes and append a newline character
// so it behaves like standard console input
self.buffer = format!("{line}\n").into_bytes();
}
Err(ReadlineError::Eof) => {
// EOF (Ctrl-D) means we return 0 bytes read, signaling the end of the stream
return Ok(0);
}
Err(ReadlineError::Interrupted) => {
// Handle Ctrl-C as an Interrupted IO error
return Err(io::Error::new(io::ErrorKind::Interrupted, "Ctrl-C"));
}
Err(err) => {
// Wrap any other rustyline errors into an IO error
return Err(io::Error::other(err));
}
}
}
// Consume bytes from our internal buffer into the provided output buffer
let amt = std::cmp::min(buf.len(), self.buffer.len());
buf[..amt].copy_from_slice(&self.buffer[..amt]);
// Remove the consumed bytes from our internal buffer
self.buffer.drain(..amt);
Ok(amt)
}
}