1 use std::cmp;
2 use std::env;
3 use std::ffi::OsStr;
4 use std::fmt::Display;
5 use std::io;
6 use std::iter::once;
7 use std::mem;
8 use std::os::windows::ffi::OsStrExt;
9 use std::os::windows::io::AsRawHandle;
10 use std::slice;
11 use std::{char, mem::MaybeUninit};
12 
13 use encode_unicode::error::InvalidUtf16Tuple;
14 use encode_unicode::CharExt;
15 #[cfg(feature = "windows-console-colors")]
16 use regex::Regex;
17 use winapi::ctypes::c_void;
18 use winapi::shared::minwindef::DWORD;
19 use winapi::shared::minwindef::MAX_PATH;
20 use winapi::um::consoleapi::{GetConsoleMode, SetConsoleMode};
21 use winapi::um::consoleapi::{GetNumberOfConsoleInputEvents, ReadConsoleInputW};
22 use winapi::um::fileapi::FILE_NAME_INFO;
23 use winapi::um::handleapi::INVALID_HANDLE_VALUE;
24 use winapi::um::minwinbase::FileNameInfo;
25 use winapi::um::processenv::GetStdHandle;
26 use winapi::um::winbase::GetFileInformationByHandleEx;
27 use winapi::um::winbase::{STD_ERROR_HANDLE, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE};
28 use winapi::um::wincon::{
29     FillConsoleOutputAttribute, FillConsoleOutputCharacterA, GetConsoleCursorInfo,
30     GetConsoleScreenBufferInfo, SetConsoleCursorInfo, SetConsoleCursorPosition, SetConsoleTitleW,
31     CONSOLE_CURSOR_INFO, CONSOLE_SCREEN_BUFFER_INFO, COORD, INPUT_RECORD, KEY_EVENT,
32     KEY_EVENT_RECORD,
33 };
34 use winapi::um::winnt::{CHAR, HANDLE, INT, WCHAR};
35 #[cfg(feature = "windows-console-colors")]
36 use winapi_util::console::{Color, Console, Intense};
37 
38 use crate::common_term;
39 use crate::kb::Key;
40 use crate::term::{Term, TermTarget};
41 
42 #[cfg(feature = "windows-console-colors")]
43 lazy_static::lazy_static! {
44     static ref INTENSE_COLOR_RE: Regex = Regex::new(r"\x1b\[(3|4)8;5;(8|9|1[0-5])m").unwrap();
45     static ref NORMAL_COLOR_RE: Regex = Regex::new(r"\x1b\[(3|4)([0-7])m").unwrap();
46     static ref ATTR_RE: Regex = Regex::new(r"\x1b\[([1-8])m").unwrap();
47 }
48 
49 const ENABLE_VIRTUAL_TERMINAL_PROCESSING: u32 = 0x4;
50 pub const DEFAULT_WIDTH: u16 = 79;
51 
as_handle(term: &Term) -> HANDLE52 pub fn as_handle(term: &Term) -> HANDLE {
53     // convert between winapi::um::winnt::HANDLE and std::os::windows::raw::HANDLE
54     // which are both c_void. would be nice to find a better way to do this
55     term.as_raw_handle() as HANDLE
56 }
57 
is_a_terminal(out: &Term) -> bool58 pub fn is_a_terminal(out: &Term) -> bool {
59     let (fd, others) = match out.target() {
60         TermTarget::Stdout => (STD_OUTPUT_HANDLE, [STD_INPUT_HANDLE, STD_ERROR_HANDLE]),
61         TermTarget::Stderr => (STD_ERROR_HANDLE, [STD_INPUT_HANDLE, STD_OUTPUT_HANDLE]),
62     };
63 
64     if unsafe { console_on_any(&[fd]) } {
65         // False positives aren't possible. If we got a console then
66         // we definitely have a tty on stdin.
67         return true;
68     }
69 
70     // At this point, we *could* have a false negative. We can determine that
71     // this is true negative if we can detect the presence of a console on
72     // any of the other streams. If another stream has a console, then we know
73     // we're in a Windows console and can therefore trust the negative.
74     if unsafe { console_on_any(&others) } {
75         return false;
76     }
77 
78     msys_tty_on(out)
79 }
80 
is_a_color_terminal(out: &Term) -> bool81 pub fn is_a_color_terminal(out: &Term) -> bool {
82     if !is_a_terminal(out) {
83         return false;
84     }
85     if msys_tty_on(out) {
86         return match env::var("TERM") {
87             Ok(term) => term != "dumb",
88             Err(_) => true,
89         };
90     }
91     enable_ansi_on(out)
92 }
93 
enable_ansi_on(out: &Term) -> bool94 fn enable_ansi_on(out: &Term) -> bool {
95     unsafe {
96         let handle = as_handle(out);
97 
98         let mut dw_mode = 0;
99         if GetConsoleMode(handle, &mut dw_mode) == 0 {
100             return false;
101         }
102 
103         dw_mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
104         if SetConsoleMode(handle, dw_mode) == 0 {
105             return false;
106         }
107 
108         true
109     }
110 }
111 
console_on_any(fds: &[DWORD]) -> bool112 unsafe fn console_on_any(fds: &[DWORD]) -> bool {
113     for &fd in fds {
114         let mut out = 0;
115         let handle = GetStdHandle(fd);
116         if GetConsoleMode(handle, &mut out) != 0 {
117             return true;
118         }
119     }
120     false
121 }
122 
123 #[inline]
terminal_size(out: &Term) -> Option<(u16, u16)>124 pub fn terminal_size(out: &Term) -> Option<(u16, u16)> {
125     terminal_size::terminal_size_using_handle(out.as_raw_handle()).map(|x| ((x.1).0, (x.0).0))
126 }
127 
move_cursor_to(out: &Term, x: usize, y: usize) -> io::Result<()>128 pub fn move_cursor_to(out: &Term, x: usize, y: usize) -> io::Result<()> {
129     if out.is_msys_tty {
130         return common_term::move_cursor_to(out, x, y);
131     }
132     if let Some((hand, _)) = get_console_screen_buffer_info(as_handle(out)) {
133         unsafe {
134             SetConsoleCursorPosition(
135                 hand,
136                 COORD {
137                     X: x as i16,
138                     Y: y as i16,
139                 },
140             );
141         }
142     }
143     Ok(())
144 }
145 
move_cursor_up(out: &Term, n: usize) -> io::Result<()>146 pub fn move_cursor_up(out: &Term, n: usize) -> io::Result<()> {
147     if out.is_msys_tty {
148         return common_term::move_cursor_up(out, n);
149     }
150 
151     if let Some((_, csbi)) = get_console_screen_buffer_info(as_handle(out)) {
152         move_cursor_to(out, 0, csbi.dwCursorPosition.Y as usize - n)?;
153     }
154     Ok(())
155 }
156 
move_cursor_down(out: &Term, n: usize) -> io::Result<()>157 pub fn move_cursor_down(out: &Term, n: usize) -> io::Result<()> {
158     if out.is_msys_tty {
159         return common_term::move_cursor_down(out, n);
160     }
161 
162     if let Some((_, csbi)) = get_console_screen_buffer_info(as_handle(out)) {
163         move_cursor_to(out, 0, csbi.dwCursorPosition.Y as usize + n)?;
164     }
165     Ok(())
166 }
167 
move_cursor_left(out: &Term, n: usize) -> io::Result<()>168 pub fn move_cursor_left(out: &Term, n: usize) -> io::Result<()> {
169     if out.is_msys_tty {
170         return common_term::move_cursor_left(out, n);
171     }
172 
173     if let Some((_, csbi)) = get_console_screen_buffer_info(as_handle(out)) {
174         move_cursor_to(
175             out,
176             csbi.dwCursorPosition.X as usize - n,
177             csbi.dwCursorPosition.Y as usize,
178         )?;
179     }
180     Ok(())
181 }
182 
move_cursor_right(out: &Term, n: usize) -> io::Result<()>183 pub fn move_cursor_right(out: &Term, n: usize) -> io::Result<()> {
184     if out.is_msys_tty {
185         return common_term::move_cursor_right(out, n);
186     }
187 
188     if let Some((_, csbi)) = get_console_screen_buffer_info(as_handle(out)) {
189         move_cursor_to(
190             out,
191             csbi.dwCursorPosition.X as usize + n,
192             csbi.dwCursorPosition.Y as usize,
193         )?;
194     }
195     Ok(())
196 }
197 
clear_line(out: &Term) -> io::Result<()>198 pub fn clear_line(out: &Term) -> io::Result<()> {
199     if out.is_msys_tty {
200         return common_term::clear_line(out);
201     }
202     if let Some((hand, csbi)) = get_console_screen_buffer_info(as_handle(out)) {
203         unsafe {
204             let width = csbi.srWindow.Right - csbi.srWindow.Left;
205             let pos = COORD {
206                 X: 0,
207                 Y: csbi.dwCursorPosition.Y,
208             };
209             let mut written = 0;
210             FillConsoleOutputCharacterA(hand, b' ' as CHAR, width as DWORD, pos, &mut written);
211             FillConsoleOutputAttribute(hand, csbi.wAttributes, width as DWORD, pos, &mut written);
212             SetConsoleCursorPosition(hand, pos);
213         }
214     }
215     Ok(())
216 }
217 
clear_chars(out: &Term, n: usize) -> io::Result<()>218 pub fn clear_chars(out: &Term, n: usize) -> io::Result<()> {
219     if out.is_msys_tty {
220         return common_term::clear_chars(out, n);
221     }
222     if let Some((hand, csbi)) = get_console_screen_buffer_info(as_handle(out)) {
223         unsafe {
224             let width = cmp::min(csbi.dwCursorPosition.X, n as i16);
225             let pos = COORD {
226                 X: csbi.dwCursorPosition.X - width,
227                 Y: csbi.dwCursorPosition.Y,
228             };
229             let mut written = 0;
230             FillConsoleOutputCharacterA(hand, b' ' as CHAR, width as DWORD, pos, &mut written);
231             FillConsoleOutputAttribute(hand, csbi.wAttributes, width as DWORD, pos, &mut written);
232             SetConsoleCursorPosition(hand, pos);
233         }
234     }
235     Ok(())
236 }
237 
clear_screen(out: &Term) -> io::Result<()>238 pub fn clear_screen(out: &Term) -> io::Result<()> {
239     if out.is_msys_tty {
240         return common_term::clear_screen(out);
241     }
242     if let Some((hand, csbi)) = get_console_screen_buffer_info(as_handle(out)) {
243         unsafe {
244             let cells = csbi.dwSize.X as DWORD * csbi.dwSize.Y as DWORD; // as DWORD, or else this causes stack overflows.
245             let pos = COORD { X: 0, Y: 0 };
246             let mut written = 0;
247             FillConsoleOutputCharacterA(hand, b' ' as CHAR, cells, pos, &mut written); // cells as DWORD no longer needed.
248             FillConsoleOutputAttribute(hand, csbi.wAttributes, cells, pos, &mut written);
249             SetConsoleCursorPosition(hand, pos);
250         }
251     }
252     Ok(())
253 }
254 
clear_to_end_of_screen(out: &Term) -> io::Result<()>255 pub fn clear_to_end_of_screen(out: &Term) -> io::Result<()> {
256     if out.is_msys_tty {
257         return common_term::clear_to_end_of_screen(out);
258     }
259     if let Some((hand, csbi)) = get_console_screen_buffer_info(as_handle(out)) {
260         unsafe {
261             let bottom = csbi.srWindow.Right as DWORD * csbi.srWindow.Bottom as DWORD;
262             let cells =
263                 bottom - (csbi.dwCursorPosition.X as DWORD * csbi.dwCursorPosition.Y as DWORD); // as DWORD, or else this causes stack overflows.
264             let pos = COORD {
265                 X: 0,
266                 Y: csbi.dwCursorPosition.Y,
267             };
268             let mut written = 0;
269             FillConsoleOutputCharacterA(hand, b' ' as CHAR, cells, pos, &mut written); // cells as DWORD no longer needed.
270             FillConsoleOutputAttribute(hand, csbi.wAttributes, cells, pos, &mut written);
271             SetConsoleCursorPosition(hand, pos);
272         }
273     }
274     Ok(())
275 }
276 
show_cursor(out: &Term) -> io::Result<()>277 pub fn show_cursor(out: &Term) -> io::Result<()> {
278     if out.is_msys_tty {
279         return common_term::show_cursor(out);
280     }
281     if let Some((hand, mut cci)) = get_console_cursor_info(as_handle(out)) {
282         unsafe {
283             cci.bVisible = 1;
284             SetConsoleCursorInfo(hand, &cci);
285         }
286     }
287     Ok(())
288 }
289 
hide_cursor(out: &Term) -> io::Result<()>290 pub fn hide_cursor(out: &Term) -> io::Result<()> {
291     if out.is_msys_tty {
292         return common_term::hide_cursor(out);
293     }
294     if let Some((hand, mut cci)) = get_console_cursor_info(as_handle(out)) {
295         unsafe {
296             cci.bVisible = 0;
297             SetConsoleCursorInfo(hand, &cci);
298         }
299     }
300     Ok(())
301 }
302 
get_console_screen_buffer_info(hand: HANDLE) -> Option<(HANDLE, CONSOLE_SCREEN_BUFFER_INFO)>303 fn get_console_screen_buffer_info(hand: HANDLE) -> Option<(HANDLE, CONSOLE_SCREEN_BUFFER_INFO)> {
304     let mut csbi: CONSOLE_SCREEN_BUFFER_INFO = unsafe { mem::zeroed() };
305     match unsafe { GetConsoleScreenBufferInfo(hand, &mut csbi) } {
306         0 => None,
307         _ => Some((hand, csbi)),
308     }
309 }
310 
get_console_cursor_info(hand: HANDLE) -> Option<(HANDLE, CONSOLE_CURSOR_INFO)>311 fn get_console_cursor_info(hand: HANDLE) -> Option<(HANDLE, CONSOLE_CURSOR_INFO)> {
312     let mut cci: CONSOLE_CURSOR_INFO = unsafe { mem::zeroed() };
313     match unsafe { GetConsoleCursorInfo(hand, &mut cci) } {
314         0 => None,
315         _ => Some((hand, cci)),
316     }
317 }
318 
key_from_key_code(code: INT) -> Key319 pub fn key_from_key_code(code: INT) -> Key {
320     match code {
321         winapi::um::winuser::VK_LEFT => Key::ArrowLeft,
322         winapi::um::winuser::VK_RIGHT => Key::ArrowRight,
323         winapi::um::winuser::VK_UP => Key::ArrowUp,
324         winapi::um::winuser::VK_DOWN => Key::ArrowDown,
325         winapi::um::winuser::VK_RETURN => Key::Enter,
326         winapi::um::winuser::VK_ESCAPE => Key::Escape,
327         winapi::um::winuser::VK_BACK => Key::Backspace,
328         winapi::um::winuser::VK_TAB => Key::Tab,
329         winapi::um::winuser::VK_HOME => Key::Home,
330         winapi::um::winuser::VK_END => Key::End,
331         winapi::um::winuser::VK_DELETE => Key::Del,
332         _ => Key::Unknown,
333     }
334 }
335 
read_secure() -> io::Result<String>336 pub fn read_secure() -> io::Result<String> {
337     let mut rv = String::new();
338     loop {
339         match read_single_key()? {
340             Key::Enter => {
341                 break;
342             }
343             Key::Char('\x08') => {
344                 if !rv.is_empty() {
345                     let new_len = rv.len() - 1;
346                     rv.truncate(new_len);
347                 }
348             }
349             Key::Char(c) => {
350                 rv.push(c);
351             }
352             _ => {}
353         }
354     }
355     Ok(rv)
356 }
357 
read_single_key() -> io::Result<Key>358 pub fn read_single_key() -> io::Result<Key> {
359     let key_event = read_key_event()?;
360 
361     let unicode_char = unsafe { *key_event.uChar.UnicodeChar() };
362     if unicode_char == 0 {
363         Ok(key_from_key_code(key_event.wVirtualKeyCode as INT))
364     } else {
365         // This is a unicode character, in utf-16. Try to decode it by itself.
366         match char::from_utf16_tuple((unicode_char, None)) {
367             Ok(c) => {
368                 // Maintain backward compatibility. The previous implementation (_getwch()) would return
369                 // a special keycode for `Enter`, while ReadConsoleInputW() prefers to use '\r'.
370                 if c == '\r' {
371                     Ok(Key::Enter)
372                 } else if c == '\x08' {
373                     Ok(Key::Backspace)
374                 } else if c == '\x1B' {
375                     Ok(Key::Escape)
376                 } else {
377                     Ok(Key::Char(c))
378                 }
379             }
380             // This is part of a surrogate pair. Try to read the second half.
381             Err(InvalidUtf16Tuple::MissingSecond) => {
382                 // Confirm that there is a next character to read.
383                 if get_key_event_count()? == 0 {
384                     let message = format!(
385                         "Read invlid utf16 {}: {}",
386                         unicode_char,
387                         InvalidUtf16Tuple::MissingSecond
388                     );
389                     return Err(io::Error::new(io::ErrorKind::InvalidData, message));
390                 }
391 
392                 // Read the next character.
393                 let next_event = read_key_event()?;
394                 let next_surrogate = unsafe { *next_event.uChar.UnicodeChar() };
395 
396                 // Attempt to decode it.
397                 match char::from_utf16_tuple((unicode_char, Some(next_surrogate))) {
398                     Ok(c) => Ok(Key::Char(c)),
399 
400                     // Return an InvalidData error. This is the recommended value for UTF-related I/O errors.
401                     // (This error is given when reading a non-UTF8 file into a String, for example.)
402                     Err(e) => {
403                         let message = format!(
404                             "Read invalid surrogate pair ({}, {}): {}",
405                             unicode_char, next_surrogate, e
406                         );
407                         Err(io::Error::new(io::ErrorKind::InvalidData, message))
408                     }
409                 }
410             }
411 
412             // Return an InvalidData error. This is the recommended value for UTF-related I/O errors.
413             // (This error is given when reading a non-UTF8 file into a String, for example.)
414             Err(e) => {
415                 let message = format!("Read invalid utf16 {}: {}", unicode_char, e);
416                 Err(io::Error::new(io::ErrorKind::InvalidData, message))
417             }
418         }
419     }
420 }
421 
get_stdin_handle() -> io::Result<HANDLE>422 fn get_stdin_handle() -> io::Result<HANDLE> {
423     let handle = unsafe { GetStdHandle(STD_INPUT_HANDLE) };
424     if handle == INVALID_HANDLE_VALUE {
425         Err(io::Error::last_os_error())
426     } else {
427         Ok(handle)
428     }
429 }
430 
431 /// Get the number of pending events in the ReadConsoleInput queue. Note that while
432 /// these aren't necessarily key events, the only way that multiple events can be
433 /// put into the queue simultaneously is if a unicode character spanning multiple u16's
434 /// is read.
435 ///
436 /// Therefore, this is accurate as long as at least one KEY_EVENT has already been read.
get_key_event_count() -> io::Result<DWORD>437 fn get_key_event_count() -> io::Result<DWORD> {
438     let handle = get_stdin_handle()?;
439     let mut event_count: DWORD = unsafe { mem::zeroed() };
440 
441     let success = unsafe { GetNumberOfConsoleInputEvents(handle, &mut event_count) };
442     if success == 0 {
443         Err(io::Error::last_os_error())
444     } else {
445         Ok(event_count)
446     }
447 }
448 
read_key_event() -> io::Result<KEY_EVENT_RECORD>449 fn read_key_event() -> io::Result<KEY_EVENT_RECORD> {
450     let handle = get_stdin_handle()?;
451     let mut buffer: INPUT_RECORD = unsafe { mem::zeroed() };
452 
453     let mut events_read: DWORD = unsafe { mem::zeroed() };
454 
455     let mut key_event: KEY_EVENT_RECORD;
456     loop {
457         let success = unsafe { ReadConsoleInputW(handle, &mut buffer, 1, &mut events_read) };
458         if success == 0 {
459             return Err(io::Error::last_os_error());
460         }
461         if events_read == 0 {
462             return Err(io::Error::new(
463                 io::ErrorKind::Other,
464                 "ReadConsoleInput returned no events, instead of waiting for an event",
465             ));
466         }
467 
468         if events_read == 1 && buffer.EventType != KEY_EVENT {
469             // This isn't a key event; ignore it.
470             continue;
471         }
472 
473         key_event = unsafe { mem::transmute(buffer.Event) };
474 
475         if key_event.bKeyDown == 0 {
476             // This is a key being released; ignore it.
477             continue;
478         }
479 
480         return Ok(key_event);
481     }
482 }
483 
wants_emoji() -> bool484 pub fn wants_emoji() -> bool {
485     // If WT_SESSION is set, we can assume we're running in the nne
486     // Windows Terminal.  The correct way to detect this is not available
487     // yet.  See https://github.com/microsoft/terminal/issues/1040
488     env::var("WT_SESSION").is_ok()
489 }
490 
491 /// Returns true if there is an MSYS tty on the given handle.
msys_tty_on(term: &Term) -> bool492 pub fn msys_tty_on(term: &Term) -> bool {
493     let handle = term.as_raw_handle();
494     unsafe {
495         // Check whether the Windows 10 native pty is enabled
496         {
497             let mut out = MaybeUninit::uninit();
498             let res = GetConsoleMode(handle as *mut _, out.as_mut_ptr());
499             if res != 0 // If res is true then out was initialized.
500                 && (out.assume_init() & ENABLE_VIRTUAL_TERMINAL_PROCESSING)
501                     == ENABLE_VIRTUAL_TERMINAL_PROCESSING
502             {
503                 return true;
504             }
505         }
506 
507         let size = mem::size_of::<FILE_NAME_INFO>();
508         let mut name_info_bytes = vec![0u8; size + MAX_PATH * mem::size_of::<WCHAR>()];
509         let res = GetFileInformationByHandleEx(
510             handle as *mut _,
511             FileNameInfo,
512             &mut *name_info_bytes as *mut _ as *mut c_void,
513             name_info_bytes.len() as u32,
514         );
515         if res == 0 {
516             return false;
517         }
518         let name_info: &FILE_NAME_INFO = &*(name_info_bytes.as_ptr() as *const FILE_NAME_INFO);
519         let s = slice::from_raw_parts(
520             name_info.FileName.as_ptr(),
521             name_info.FileNameLength as usize / 2,
522         );
523         let name = String::from_utf16_lossy(s);
524         // This checks whether 'pty' exists in the file name, which indicates that
525         // a pseudo-terminal is attached. To mitigate against false positives
526         // (e.g., an actual file name that contains 'pty'), we also require that
527         // either the strings 'msys-' or 'cygwin-' are in the file name as well.)
528         let is_msys = name.contains("msys-") || name.contains("cygwin-");
529         let is_pty = name.contains("-pty");
530         is_msys && is_pty
531     }
532 }
533 
set_title<T: Display>(title: T)534 pub fn set_title<T: Display>(title: T) {
535     let buffer: Vec<u16> = OsStr::new(&format!("{}", title))
536         .encode_wide()
537         .chain(once(0))
538         .collect();
539     unsafe {
540         SetConsoleTitleW(buffer.as_ptr());
541     }
542 }
543 
544 #[cfg(feature = "windows-console-colors")]
console_colors(out: &Term, mut con: Console, bytes: &[u8]) -> io::Result<()>545 pub fn console_colors(out: &Term, mut con: Console, bytes: &[u8]) -> io::Result<()> {
546     use crate::ansi::AnsiCodeIterator;
547     use std::str::from_utf8;
548 
549     let s = from_utf8(bytes).expect("data to be printed is not an ansi string");
550     let mut iter = AnsiCodeIterator::new(s);
551 
552     while !iter.rest_slice().is_empty() {
553         if let Some((part, is_esc)) = iter.next() {
554             if !is_esc {
555                 out.write_through_common(part.as_bytes())?;
556             } else if part == "\x1b[0m" {
557                 con.reset()?;
558             } else if let Some(cap) = INTENSE_COLOR_RE.captures(part) {
559                 let color = get_color_from_ansi(cap.get(2).unwrap().as_str());
560 
561                 match cap.get(1).unwrap().as_str() {
562                     "3" => con.fg(Intense::Yes, color)?,
563                     "4" => con.bg(Intense::Yes, color)?,
564                     _ => unreachable!(),
565                 };
566             } else if let Some(cap) = NORMAL_COLOR_RE.captures(part) {
567                 let color = get_color_from_ansi(cap.get(2).unwrap().as_str());
568 
569                 match cap.get(1).unwrap().as_str() {
570                     "3" => con.fg(Intense::No, color)?,
571                     "4" => con.bg(Intense::No, color)?,
572                     _ => unreachable!(),
573                 };
574             } else if !ATTR_RE.is_match(part) {
575                 out.write_through_common(part.as_bytes())?;
576             }
577         }
578     }
579 
580     Ok(())
581 }
582 
583 #[cfg(feature = "windows-console-colors")]
get_color_from_ansi(ansi: &str) -> Color584 fn get_color_from_ansi(ansi: &str) -> Color {
585     match ansi {
586         "0" | "8" => Color::Black,
587         "1" | "9" => Color::Red,
588         "2" | "10" => Color::Green,
589         "3" | "11" => Color::Yellow,
590         "4" | "12" => Color::Blue,
591         "5" | "13" => Color::Magenta,
592         "6" | "14" => Color::Cyan,
593         "7" | "15" => Color::White,
594         _ => unreachable!(),
595     }
596 }
597