summaryrefslogtreecommitdiffstats
path: root/vendor/console/src/windows_term/colors.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/console/src/windows_term/colors.rs')
-rw-r--r--vendor/console/src/windows_term/colors.rs451
1 files changed, 451 insertions, 0 deletions
diff --git a/vendor/console/src/windows_term/colors.rs b/vendor/console/src/windows_term/colors.rs
new file mode 100644
index 000000000..dc8209ded
--- /dev/null
+++ b/vendor/console/src/windows_term/colors.rs
@@ -0,0 +1,451 @@
+use std::io;
+use std::mem;
+use std::os::windows::io::AsRawHandle;
+use std::str::Bytes;
+
+use windows_sys::Win32::Foundation::HANDLE;
+use windows_sys::Win32::System::Console::{
+ GetConsoleScreenBufferInfo, SetConsoleTextAttribute, CONSOLE_SCREEN_BUFFER_INFO,
+ FOREGROUND_BLUE as FG_BLUE, FOREGROUND_GREEN as FG_GREEN, FOREGROUND_INTENSITY as FG_INTENSITY,
+ FOREGROUND_RED as FG_RED,
+};
+
+use crate::Term;
+
+type WORD = u16;
+
+const FG_CYAN: WORD = FG_BLUE | FG_GREEN;
+const FG_MAGENTA: WORD = FG_BLUE | FG_RED;
+const FG_YELLOW: WORD = FG_GREEN | FG_RED;
+const FG_WHITE: WORD = FG_BLUE | FG_GREEN | FG_RED;
+
+/// Query the given handle for information about the console's screen buffer.
+///
+/// The given handle should represent a console. Otherwise, an error is
+/// returned.
+///
+/// This corresponds to calling [`GetConsoleScreenBufferInfo`].
+///
+/// [`GetConsoleScreenBufferInfo`]: https://docs.microsoft.com/en-us/windows/console/getconsolescreenbufferinfo
+pub fn screen_buffer_info(h: HANDLE) -> io::Result<ScreenBufferInfo> {
+ unsafe {
+ let mut info: CONSOLE_SCREEN_BUFFER_INFO = mem::zeroed();
+ let rc = GetConsoleScreenBufferInfo(h, &mut info);
+ if rc == 0 {
+ return Err(io::Error::last_os_error());
+ }
+ Ok(ScreenBufferInfo(info))
+ }
+}
+
+/// Set the text attributes of the console represented by the given handle.
+///
+/// This corresponds to calling [`SetConsoleTextAttribute`].
+///
+/// [`SetConsoleTextAttribute`]: https://docs.microsoft.com/en-us/windows/console/setconsoletextattribute
+pub fn set_text_attributes(h: HANDLE, attributes: u16) -> io::Result<()> {
+ if unsafe { SetConsoleTextAttribute(h, attributes) } == 0 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(())
+ }
+}
+
+/// Represents console screen buffer information such as size, cursor position
+/// and styling attributes.
+///
+/// This wraps a [`CONSOLE_SCREEN_BUFFER_INFO`].
+///
+/// [`CONSOLE_SCREEN_BUFFER_INFO`]: https://docs.microsoft.com/en-us/windows/console/console-screen-buffer-info-str
+#[derive(Clone)]
+pub struct ScreenBufferInfo(CONSOLE_SCREEN_BUFFER_INFO);
+
+impl ScreenBufferInfo {
+ /// Returns the character attributes associated with this console.
+ ///
+ /// This corresponds to `wAttributes`.
+ ///
+ /// See [`char info`] for more details.
+ ///
+ /// [`char info`]: https://docs.microsoft.com/en-us/windows/console/char-info-str
+ pub fn attributes(&self) -> u16 {
+ self.0.wAttributes
+ }
+}
+
+/// A Windows console.
+///
+/// This represents a very limited set of functionality available to a Windows
+/// console. In particular, it can only change text attributes such as color
+/// and intensity. This may grow over time. If you need more routines, please
+/// file an issue and/or PR.
+///
+/// There is no way to "write" to this console. Simply write to
+/// stdout or stderr instead, while interleaving instructions to the console
+/// to change text attributes.
+///
+/// A common pitfall when using a console is to forget to flush writes to
+/// stdout before setting new text attributes.
+#[derive(Debug)]
+pub struct Console {
+ kind: HandleKind,
+ start_attr: TextAttributes,
+ cur_attr: TextAttributes,
+}
+
+#[derive(Clone, Copy, Debug)]
+enum HandleKind {
+ Stdout,
+ Stderr,
+}
+
+impl HandleKind {
+ fn handle(&self) -> HANDLE {
+ match *self {
+ HandleKind::Stdout => io::stdout().as_raw_handle() as HANDLE,
+ HandleKind::Stderr => io::stderr().as_raw_handle() as HANDLE,
+ }
+ }
+}
+
+impl Console {
+ /// Get a console for a standard I/O stream.
+ fn create_for_stream(kind: HandleKind) -> io::Result<Console> {
+ let h = kind.handle();
+ let info = screen_buffer_info(h)?;
+ let attr = TextAttributes::from_word(info.attributes());
+ Ok(Console {
+ kind: kind,
+ start_attr: attr,
+ cur_attr: attr,
+ })
+ }
+
+ /// Create a new Console to stdout.
+ ///
+ /// If there was a problem creating the console, then an error is returned.
+ pub fn stdout() -> io::Result<Console> {
+ Self::create_for_stream(HandleKind::Stdout)
+ }
+
+ /// Create a new Console to stderr.
+ ///
+ /// If there was a problem creating the console, then an error is returned.
+ pub fn stderr() -> io::Result<Console> {
+ Self::create_for_stream(HandleKind::Stderr)
+ }
+
+ /// Applies the current text attributes.
+ fn set(&mut self) -> io::Result<()> {
+ set_text_attributes(self.kind.handle(), self.cur_attr.to_word())
+ }
+
+ /// Apply the given intensity and color attributes to the console
+ /// foreground.
+ ///
+ /// If there was a problem setting attributes on the console, then an error
+ /// is returned.
+ pub fn fg(&mut self, intense: Intense, color: Color) -> io::Result<()> {
+ self.cur_attr.fg_color = color;
+ self.cur_attr.fg_intense = intense;
+ self.set()
+ }
+
+ /// Apply the given intensity and color attributes to the console
+ /// background.
+ ///
+ /// If there was a problem setting attributes on the console, then an error
+ /// is returned.
+ pub fn bg(&mut self, intense: Intense, color: Color) -> io::Result<()> {
+ self.cur_attr.bg_color = color;
+ self.cur_attr.bg_intense = intense;
+ self.set()
+ }
+
+ /// Reset the console text attributes to their original settings.
+ ///
+ /// The original settings correspond to the text attributes on the console
+ /// when this `Console` value was created.
+ ///
+ /// If there was a problem setting attributes on the console, then an error
+ /// is returned.
+ pub fn reset(&mut self) -> io::Result<()> {
+ self.cur_attr = self.start_attr;
+ self.set()
+ }
+}
+
+/// A representation of text attributes for the Windows console.
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+struct TextAttributes {
+ fg_color: Color,
+ fg_intense: Intense,
+ bg_color: Color,
+ bg_intense: Intense,
+}
+
+impl TextAttributes {
+ fn to_word(&self) -> WORD {
+ let mut w = 0;
+ w |= self.fg_color.to_fg();
+ w |= self.fg_intense.to_fg();
+ w |= self.bg_color.to_bg();
+ w |= self.bg_intense.to_bg();
+ w
+ }
+
+ fn from_word(word: WORD) -> TextAttributes {
+ TextAttributes {
+ fg_color: Color::from_fg(word),
+ fg_intense: Intense::from_fg(word),
+ bg_color: Color::from_bg(word),
+ bg_intense: Intense::from_bg(word),
+ }
+ }
+}
+
+/// Whether to use intense colors or not.
+#[allow(missing_docs)]
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum Intense {
+ Yes,
+ No,
+}
+
+impl Intense {
+ fn to_bg(&self) -> WORD {
+ self.to_fg() << 4
+ }
+
+ fn from_bg(word: WORD) -> Intense {
+ Intense::from_fg(word >> 4)
+ }
+
+ fn to_fg(&self) -> WORD {
+ match *self {
+ Intense::No => 0,
+ Intense::Yes => FG_INTENSITY,
+ }
+ }
+
+ fn from_fg(word: WORD) -> Intense {
+ if word & FG_INTENSITY > 0 {
+ Intense::Yes
+ } else {
+ Intense::No
+ }
+ }
+}
+
+/// The set of available colors for use with a Windows console.
+#[allow(missing_docs)]
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum Color {
+ Black,
+ Blue,
+ Green,
+ Red,
+ Cyan,
+ Magenta,
+ Yellow,
+ White,
+}
+
+impl Color {
+ fn to_bg(&self) -> WORD {
+ self.to_fg() << 4
+ }
+
+ fn from_bg(word: WORD) -> Color {
+ Color::from_fg(word >> 4)
+ }
+
+ fn to_fg(&self) -> WORD {
+ match *self {
+ Color::Black => 0,
+ Color::Blue => FG_BLUE,
+ Color::Green => FG_GREEN,
+ Color::Red => FG_RED,
+ Color::Cyan => FG_CYAN,
+ Color::Magenta => FG_MAGENTA,
+ Color::Yellow => FG_YELLOW,
+ Color::White => FG_WHITE,
+ }
+ }
+
+ fn from_fg(word: WORD) -> Color {
+ match word & 0b111 {
+ FG_BLUE => Color::Blue,
+ FG_GREEN => Color::Green,
+ FG_RED => Color::Red,
+ FG_CYAN => Color::Cyan,
+ FG_MAGENTA => Color::Magenta,
+ FG_YELLOW => Color::Yellow,
+ FG_WHITE => Color::White,
+ _ => Color::Black,
+ }
+ }
+}
+
+pub fn console_colors(out: &Term, mut con: Console, bytes: &[u8]) -> io::Result<()> {
+ use crate::ansi::AnsiCodeIterator;
+ use std::str::from_utf8;
+
+ let s = from_utf8(bytes).expect("data to be printed is not an ansi string");
+ let mut iter = AnsiCodeIterator::new(s);
+
+ while !iter.rest_slice().is_empty() {
+ if let Some((part, is_esc)) = iter.next() {
+ if !is_esc {
+ out.write_through_common(part.as_bytes())?;
+ } else if part == "\x1b[0m" {
+ con.reset()?;
+ } else if let Some((intense, color, fg_bg)) = driver(parse_color, part) {
+ match fg_bg {
+ FgBg::Foreground => con.fg(intense, color),
+ FgBg::Background => con.bg(intense, color),
+ }?;
+ } else if driver(parse_attr, part).is_none() {
+ out.write_through_common(part.as_bytes())?;
+ }
+ }
+ }
+
+ Ok(())
+}
+
+#[derive(Debug, PartialEq, Eq)]
+enum FgBg {
+ Foreground,
+ Background,
+}
+
+impl FgBg {
+ fn new(byte: u8) -> Option<Self> {
+ match byte {
+ b'3' => Some(Self::Foreground),
+ b'4' => Some(Self::Background),
+ _ => None,
+ }
+ }
+}
+
+fn driver<Out>(parse: fn(Bytes<'_>) -> Option<Out>, part: &str) -> Option<Out> {
+ let mut bytes = part.bytes();
+
+ loop {
+ while bytes.next()? != b'\x1b' {}
+
+ if let ret @ Some(_) = (parse)(bytes.clone()) {
+ return ret;
+ }
+ }
+}
+
+// `driver(parse_color, s)` parses the equivalent of the regex
+// \x1b\[(3|4)8;5;(8|9|1[0-5])m
+// for intense or
+// \x1b\[(3|4)([0-7])m
+// for normal
+fn parse_color(mut bytes: Bytes<'_>) -> Option<(Intense, Color, FgBg)> {
+ parse_prefix(&mut bytes)?;
+
+ let fg_bg = FgBg::new(bytes.next()?)?;
+ let (intense, color) = match bytes.next()? {
+ b @ b'0'..=b'7' => (Intense::No, normal_color_ansi_from_byte(b)?),
+ b'8' => {
+ if &[bytes.next()?, bytes.next()?, bytes.next()?] != b";5;" {
+ return None;
+ }
+ (Intense::Yes, parse_intense_color_ansi(&mut bytes)?)
+ }
+ _ => return None,
+ };
+
+ parse_suffix(&mut bytes)?;
+ Some((intense, color, fg_bg))
+}
+
+// `driver(parse_attr, s)` parses the equivalent of the regex
+// \x1b\[([1-8])m
+fn parse_attr(mut bytes: Bytes<'_>) -> Option<u8> {
+ parse_prefix(&mut bytes)?;
+ let attr = match bytes.next()? {
+ attr @ b'1'..=b'8' => attr,
+ _ => return None,
+ };
+ parse_suffix(&mut bytes)?;
+ Some(attr)
+}
+
+fn parse_prefix(bytes: &mut Bytes<'_>) -> Option<()> {
+ if bytes.next()? == b'[' {
+ Some(())
+ } else {
+ None
+ }
+}
+
+fn parse_intense_color_ansi(bytes: &mut Bytes<'_>) -> Option<Color> {
+ let color = match bytes.next()? {
+ b'8' => Color::Black,
+ b'9' => Color::Red,
+ b'1' => match bytes.next()? {
+ b'0' => Color::Green,
+ b'1' => Color::Yellow,
+ b'2' => Color::Blue,
+ b'3' => Color::Magenta,
+ b'4' => Color::Cyan,
+ b'5' => Color::White,
+ _ => return None,
+ },
+ _ => return None,
+ };
+ Some(color)
+}
+
+fn normal_color_ansi_from_byte(b: u8) -> Option<Color> {
+ let color = match b {
+ b'0' => Color::Black,
+ b'1' => Color::Red,
+ b'2' => Color::Green,
+ b'3' => Color::Yellow,
+ b'4' => Color::Blue,
+ b'5' => Color::Magenta,
+ b'6' => Color::Cyan,
+ b'7' => Color::White,
+ _ => return None,
+ };
+ Some(color)
+}
+
+fn parse_suffix(bytes: &mut Bytes<'_>) -> Option<()> {
+ if bytes.next()? == b'm' {
+ Some(())
+ } else {
+ None
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn color_parsing() {
+ let intense_color = "leading bytes \x1b[38;5;10m trailing bytes";
+ let parsed = driver(parse_color, intense_color).unwrap();
+ assert_eq!(parsed, (Intense::Yes, Color::Green, FgBg::Foreground));
+
+ let normal_color = "leading bytes \x1b[40m trailing bytes";
+ let parsed = driver(parse_color, normal_color).unwrap();
+ assert_eq!(parsed, (Intense::No, Color::Black, FgBg::Background));
+ }
+
+ #[test]
+ fn attr_parsing() {
+ let attr = "leading bytes \x1b[1m trailing bytes";
+ let parsed = driver(parse_attr, attr).unwrap();
+ assert_eq!(parsed, b'1');
+ }
+}