1use std::io::{self, Write};
7use std::thread;
8use std::time::Duration;
9
10pub fn stdin_has_pending_input() -> bool {
12 #[cfg(windows)]
13 {
14 stdin_has_pending_input_windows()
15 }
16 #[cfg(unix)]
17 {
18 stdin_has_pending_input_unix()
19 }
20 #[cfg(not(any(windows, unix)))]
21 {
22 false
23 }
24}
25
26pub fn read_available_stdin_bytes() -> Vec<u8> {
28 #[cfg(windows)]
29 {
30 read_available_stdin_bytes_windows()
31 }
32 #[cfg(unix)]
33 {
34 read_available_stdin_bytes_unix()
35 }
36 #[cfg(not(any(windows, unix)))]
37 {
38 Vec::new()
39 }
40}
41
42pub fn drain_available_stdin_lines() -> Vec<String> {
44 let bytes = read_available_stdin_bytes();
45 if bytes.is_empty() {
46 return Vec::new();
47 }
48
49 let text = String::from_utf8_lossy(&bytes);
50 let mut lines: Vec<String> = text.split('\n').map(|line| line.trim_end_matches('\r').to_string()).collect();
51
52 if lines.last().map(|line| line.is_empty()).unwrap_or(false) {
53 lines.pop();
54 }
55
56 lines.into_iter().filter(|line| !line.is_empty()).collect()
57}
58
59pub fn read_pastable_line(prompt: &str) -> io::Result<String> {
64 print!("{} › ", prompt);
65 io::stdout().flush()?;
66
67 let mut lines = Vec::new();
68 let stdin = io::stdin();
69
70 let mut first = String::new();
71 stdin.read_line(&mut first)?;
72 let first = first.trim_end_matches(['\r', '\n']).to_string();
73 if !first.is_empty() {
74 lines.push(first);
75 }
76
77 for _ in 0..8 {
79 thread::sleep(Duration::from_millis(25));
80 if !stdin_has_pending_input() {
81 thread::sleep(Duration::from_millis(40));
83 if !stdin_has_pending_input() {
84 break;
85 }
86 }
87
88 let mut line = String::new();
89 if stdin.read_line(&mut line)? == 0 {
90 break;
91 }
92 let trimmed = line.trim_end_matches(['\r', '\n']).to_string();
93 if trimmed.is_empty() {
94 break;
95 }
96 lines.push(trimmed);
97 }
98
99 for extra in drain_available_stdin_lines() {
101 lines.push(extra);
102 }
103
104 Ok(lines.join("\n"))
105}
106
107#[cfg(windows)]
108fn stdin_has_pending_input_windows() -> bool {
109 use winapi::shared::minwindef::{DWORD, FALSE, TRUE};
110 use winapi::um::consoleapi::{GetNumberOfConsoleInputEvents, PeekConsoleInputA};
111 use winapi::um::handleapi::INVALID_HANDLE_VALUE;
112 use winapi::um::namedpipeapi::PeekNamedPipe;
113 use winapi::um::processenv::GetStdHandle;
114 use winapi::um::winbase::STD_INPUT_HANDLE;
115 use winapi::um::wincontypes::{INPUT_RECORD, KEY_EVENT};
116 use winapi::um::winnt::HANDLE;
117
118 unsafe {
119 let handle: HANDLE = GetStdHandle(STD_INPUT_HANDLE);
120 if handle.is_null() || handle == INVALID_HANDLE_VALUE {
121 return false;
122 }
123
124 let mut available: DWORD = 0;
126 if PeekNamedPipe(handle, std::ptr::null_mut(), 0, std::ptr::null_mut(), &mut available, std::ptr::null_mut()) != FALSE && available > 0 {
127 return true;
128 }
129
130 let mut events: DWORD = 0;
132 if GetNumberOfConsoleInputEvents(handle, &mut events) == FALSE || events == 0 {
133 return false;
134 }
135
136 let mut records = vec![std::mem::zeroed::<INPUT_RECORD>(); events as usize];
137 let mut read: DWORD = 0;
138 if PeekConsoleInputA(handle, records.as_mut_ptr(), events, &mut read) == FALSE {
139 return false;
140 }
141
142 for record in records.iter().take(read as usize) {
143 if record.EventType == KEY_EVENT {
144 let key = record.Event.KeyEvent();
145 if key.bKeyDown == TRUE {
146 let unicode = *key.uChar.UnicodeChar();
147 let ascii = *key.uChar.AsciiChar() as u8;
148 if unicode != 0 || ascii != 0 {
149 return true;
150 }
151 }
152 }
153 }
154
155 false
156 }
157}
158
159#[cfg(windows)]
160fn read_available_stdin_bytes_windows() -> Vec<u8> {
161 use std::ptr;
162 use winapi::shared::minwindef::{DWORD, FALSE};
163 use winapi::um::fileapi::ReadFile;
164 use winapi::um::handleapi::INVALID_HANDLE_VALUE;
165 use winapi::um::namedpipeapi::PeekNamedPipe;
166 use winapi::um::processenv::GetStdHandle;
167 use winapi::um::winbase::STD_INPUT_HANDLE;
168
169 unsafe {
170 let handle = GetStdHandle(STD_INPUT_HANDLE);
171 if handle.is_null() || handle == INVALID_HANDLE_VALUE {
172 return Vec::new();
173 }
174
175 let mut available: DWORD = 0;
176 let peeked = PeekNamedPipe(handle, ptr::null_mut(), 0, ptr::null_mut(), &mut available, ptr::null_mut());
177 if peeked == FALSE || available == 0 {
178 return Vec::new();
179 }
180
181 let mut buf = vec![0u8; available as usize];
182 let mut read: DWORD = 0;
183 let ok = ReadFile(handle, buf.as_mut_ptr() as *mut _, available, &mut read, ptr::null_mut());
184 if ok == FALSE {
185 return Vec::new();
186 }
187 buf.truncate(read as usize);
188 buf
189 }
190}
191
192#[cfg(unix)]
193fn stdin_has_pending_input_unix() -> bool {
194 use nix::poll::{PollFd, PollFlags, poll};
195 use std::os::fd::AsFd;
196
197 let stdin = std::io::stdin();
198 let mut fds = [PollFd::new(stdin.as_fd(), PollFlags::POLLIN)];
199 matches!(poll(&mut fds, 0u16), Ok(n) if n > 0)
200}
201
202#[cfg(unix)]
203fn read_available_stdin_bytes_unix() -> Vec<u8> {
204 use nix::poll::{PollFd, PollFlags, poll};
205 use std::io::Read;
206 use std::os::fd::{AsRawFd, BorrowedFd};
207
208 let stdin = std::io::stdin();
209 if !stdin_has_pending_input_unix() {
210 return Vec::new();
211 }
212
213 let mut out = Vec::new();
214 let mut chunk = [0u8; 4096];
215 let mut stdin_lock = stdin.lock();
216 loop {
217 let mut fds = [PollFd::new(unsafe { BorrowedFd::borrow_raw(stdin_lock.as_raw_fd()) }, PollFlags::POLLIN)];
218 match poll(&mut fds, 0u16) {
219 Ok(n) if n > 0 => match stdin_lock.read(&mut chunk) {
220 Ok(0) => break,
221 Ok(n) => out.extend_from_slice(&chunk[..n]),
222 Err(_) => break,
223 },
224 _ => break,
225 }
226 if out.len() > 64 * 1024 {
227 break;
228 }
229 }
230 out
231}