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