264 lines
9.1 KiB
Rust
264 lines
9.1 KiB
Rust
//! Low-level wincon-styling
|
|
|
|
use std::os::windows::io::AsHandle;
|
|
use std::os::windows::io::AsRawHandle;
|
|
|
|
type StdioColorResult = std::io::Result<(anstyle::AnsiColor, anstyle::AnsiColor)>;
|
|
type StdioColorInnerResult = Result<(anstyle::AnsiColor, anstyle::AnsiColor), inner::IoError>;
|
|
|
|
/// Cached [`get_colors`] call for [`std::io::stdout`]
|
|
pub fn stdout_initial_colors() -> StdioColorResult {
|
|
static INITIAL: std::sync::OnceLock<StdioColorInnerResult> = std::sync::OnceLock::new();
|
|
INITIAL
|
|
.get_or_init(|| get_colors_(&std::io::stdout()))
|
|
.clone()
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
/// Cached [`get_colors`] call for [`std::io::stderr`]
|
|
pub fn stderr_initial_colors() -> StdioColorResult {
|
|
static INITIAL: std::sync::OnceLock<StdioColorInnerResult> = std::sync::OnceLock::new();
|
|
INITIAL
|
|
.get_or_init(|| get_colors_(&std::io::stderr()))
|
|
.clone()
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
/// Apply colors to future writes
|
|
///
|
|
/// **Note:** Make sure any buffers are first flushed or else these colors will apply
|
|
pub fn set_colors<S: AsHandle>(
|
|
stream: &mut S,
|
|
fg: anstyle::AnsiColor,
|
|
bg: anstyle::AnsiColor,
|
|
) -> std::io::Result<()> {
|
|
set_colors_(stream, fg, bg).map_err(Into::into)
|
|
}
|
|
|
|
fn set_colors_<S: AsHandle>(
|
|
stream: &mut S,
|
|
fg: anstyle::AnsiColor,
|
|
bg: anstyle::AnsiColor,
|
|
) -> Result<(), inner::IoError> {
|
|
let handle = stream.as_handle();
|
|
let handle = handle.as_raw_handle();
|
|
let attributes = inner::set_colors(fg, bg);
|
|
inner::set_console_text_attributes(handle, attributes)
|
|
}
|
|
|
|
/// Get the colors currently active on the console
|
|
pub fn get_colors<S: AsHandle>(stream: &S) -> StdioColorResult {
|
|
get_colors_(stream).map_err(Into::into)
|
|
}
|
|
|
|
fn get_colors_<S: AsHandle>(stream: &S) -> StdioColorInnerResult {
|
|
let handle = stream.as_handle();
|
|
let handle = handle.as_raw_handle();
|
|
let info = inner::get_screen_buffer_info(handle)?;
|
|
let (fg, bg) = inner::get_colors(&info);
|
|
Ok((fg, bg))
|
|
}
|
|
|
|
pub(crate) fn write_colored<S: AsHandle + std::io::Write>(
|
|
stream: &mut S,
|
|
fg: Option<anstyle::AnsiColor>,
|
|
bg: Option<anstyle::AnsiColor>,
|
|
data: &[u8],
|
|
initial: StdioColorResult,
|
|
) -> std::io::Result<usize> {
|
|
let (initial_fg, initial_bg) = initial?;
|
|
let non_default = fg.is_some() || bg.is_some();
|
|
|
|
if non_default {
|
|
let fg = fg.unwrap_or(initial_fg);
|
|
let bg = bg.unwrap_or(initial_bg);
|
|
// Ensure everything is written with the last set of colors before applying the next set
|
|
stream.flush()?;
|
|
set_colors(stream, fg, bg)?;
|
|
}
|
|
let written = stream.write(data)?;
|
|
if non_default {
|
|
// Ensure everything is written with the last set of colors before applying the next set
|
|
stream.flush()?;
|
|
set_colors(stream, initial_fg, initial_bg)?;
|
|
}
|
|
Ok(written)
|
|
}
|
|
|
|
mod inner {
|
|
use windows_sys::Win32::System::Console::CONSOLE_CHARACTER_ATTRIBUTES;
|
|
use windows_sys::Win32::System::Console::CONSOLE_SCREEN_BUFFER_INFO;
|
|
use windows_sys::Win32::System::Console::FOREGROUND_BLUE;
|
|
use windows_sys::Win32::System::Console::FOREGROUND_GREEN;
|
|
use windows_sys::Win32::System::Console::FOREGROUND_INTENSITY;
|
|
use windows_sys::Win32::System::Console::FOREGROUND_RED;
|
|
|
|
use std::os::windows::io::RawHandle;
|
|
|
|
const FOREGROUND_CYAN: CONSOLE_CHARACTER_ATTRIBUTES = FOREGROUND_BLUE | FOREGROUND_GREEN;
|
|
const FOREGROUND_MAGENTA: CONSOLE_CHARACTER_ATTRIBUTES = FOREGROUND_BLUE | FOREGROUND_RED;
|
|
const FOREGROUND_YELLOW: CONSOLE_CHARACTER_ATTRIBUTES = FOREGROUND_GREEN | FOREGROUND_RED;
|
|
const FOREGROUND_WHITE: CONSOLE_CHARACTER_ATTRIBUTES =
|
|
FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED;
|
|
|
|
#[derive(Copy, Clone, Debug)]
|
|
pub(crate) enum IoError {
|
|
BrokenPipe,
|
|
RawOs(i32),
|
|
}
|
|
|
|
impl From<IoError> for std::io::Error {
|
|
fn from(io: IoError) -> Self {
|
|
match io {
|
|
IoError::BrokenPipe => {
|
|
std::io::Error::new(std::io::ErrorKind::BrokenPipe, "console is detached")
|
|
}
|
|
IoError::RawOs(code) => std::io::Error::from_raw_os_error(code),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl IoError {
|
|
fn last_os_error() -> Self {
|
|
Self::RawOs(std::io::Error::last_os_error().raw_os_error().unwrap())
|
|
}
|
|
}
|
|
|
|
pub(crate) fn get_screen_buffer_info(
|
|
handle: RawHandle,
|
|
) -> Result<CONSOLE_SCREEN_BUFFER_INFO, IoError> {
|
|
unsafe {
|
|
let handle = std::mem::transmute(handle);
|
|
if handle == 0 {
|
|
return Err(IoError::BrokenPipe);
|
|
}
|
|
|
|
let mut info: CONSOLE_SCREEN_BUFFER_INFO = std::mem::zeroed();
|
|
if windows_sys::Win32::System::Console::GetConsoleScreenBufferInfo(handle, &mut info)
|
|
!= 0
|
|
{
|
|
Ok(info)
|
|
} else {
|
|
Err(IoError::last_os_error())
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn set_console_text_attributes(
|
|
handle: RawHandle,
|
|
attributes: CONSOLE_CHARACTER_ATTRIBUTES,
|
|
) -> Result<(), IoError> {
|
|
unsafe {
|
|
let handle = std::mem::transmute(handle);
|
|
if handle == 0 {
|
|
return Err(IoError::BrokenPipe);
|
|
}
|
|
|
|
if windows_sys::Win32::System::Console::SetConsoleTextAttribute(handle, attributes) != 0
|
|
{
|
|
Ok(())
|
|
} else {
|
|
Err(IoError::last_os_error())
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn get_colors(
|
|
info: &CONSOLE_SCREEN_BUFFER_INFO,
|
|
) -> (anstyle::AnsiColor, anstyle::AnsiColor) {
|
|
let attributes = info.wAttributes;
|
|
let bg = from_nibble(attributes >> 4);
|
|
let fg = from_nibble(attributes);
|
|
(fg, bg)
|
|
}
|
|
|
|
pub(crate) fn set_colors(
|
|
fg: anstyle::AnsiColor,
|
|
bg: anstyle::AnsiColor,
|
|
) -> CONSOLE_CHARACTER_ATTRIBUTES {
|
|
to_nibble(bg) << 4 | to_nibble(fg)
|
|
}
|
|
|
|
fn from_nibble(color: CONSOLE_CHARACTER_ATTRIBUTES) -> anstyle::AnsiColor {
|
|
if color & FOREGROUND_WHITE == FOREGROUND_WHITE {
|
|
// 3 bits high
|
|
anstyle::AnsiColor::White
|
|
} else if color & FOREGROUND_CYAN == FOREGROUND_CYAN {
|
|
// 2 bits high
|
|
anstyle::AnsiColor::Cyan
|
|
} else if color & FOREGROUND_YELLOW == FOREGROUND_YELLOW {
|
|
// 2 bits high
|
|
anstyle::AnsiColor::Yellow
|
|
} else if color & FOREGROUND_MAGENTA == FOREGROUND_MAGENTA {
|
|
// 2 bits high
|
|
anstyle::AnsiColor::Magenta
|
|
} else if color & FOREGROUND_RED == FOREGROUND_RED {
|
|
// 1 bit high
|
|
anstyle::AnsiColor::Red
|
|
} else if color & FOREGROUND_GREEN == FOREGROUND_GREEN {
|
|
// 1 bit high
|
|
anstyle::AnsiColor::Green
|
|
} else if color & FOREGROUND_BLUE == FOREGROUND_BLUE {
|
|
// 1 bit high
|
|
anstyle::AnsiColor::Blue
|
|
} else {
|
|
// 0 bits high
|
|
anstyle::AnsiColor::Black
|
|
}
|
|
.bright(color & FOREGROUND_INTENSITY == FOREGROUND_INTENSITY)
|
|
}
|
|
|
|
fn to_nibble(color: anstyle::AnsiColor) -> CONSOLE_CHARACTER_ATTRIBUTES {
|
|
let mut attributes = 0;
|
|
attributes |= match color.bright(false) {
|
|
anstyle::AnsiColor::Black => 0,
|
|
anstyle::AnsiColor::Red => FOREGROUND_RED,
|
|
anstyle::AnsiColor::Green => FOREGROUND_GREEN,
|
|
anstyle::AnsiColor::Yellow => FOREGROUND_YELLOW,
|
|
anstyle::AnsiColor::Blue => FOREGROUND_BLUE,
|
|
anstyle::AnsiColor::Magenta => FOREGROUND_MAGENTA,
|
|
anstyle::AnsiColor::Cyan => FOREGROUND_CYAN,
|
|
anstyle::AnsiColor::White => FOREGROUND_WHITE,
|
|
anstyle::AnsiColor::BrightBlack
|
|
| anstyle::AnsiColor::BrightRed
|
|
| anstyle::AnsiColor::BrightGreen
|
|
| anstyle::AnsiColor::BrightYellow
|
|
| anstyle::AnsiColor::BrightBlue
|
|
| anstyle::AnsiColor::BrightMagenta
|
|
| anstyle::AnsiColor::BrightCyan
|
|
| anstyle::AnsiColor::BrightWhite => unreachable!("brights were toggled off"),
|
|
};
|
|
if color.is_bright() {
|
|
attributes |= FOREGROUND_INTENSITY;
|
|
}
|
|
attributes
|
|
}
|
|
|
|
#[test]
|
|
fn to_from_nibble() {
|
|
const COLORS: [anstyle::AnsiColor; 16] = [
|
|
anstyle::AnsiColor::Black,
|
|
anstyle::AnsiColor::Red,
|
|
anstyle::AnsiColor::Green,
|
|
anstyle::AnsiColor::Yellow,
|
|
anstyle::AnsiColor::Blue,
|
|
anstyle::AnsiColor::Magenta,
|
|
anstyle::AnsiColor::Cyan,
|
|
anstyle::AnsiColor::White,
|
|
anstyle::AnsiColor::BrightBlack,
|
|
anstyle::AnsiColor::BrightRed,
|
|
anstyle::AnsiColor::BrightGreen,
|
|
anstyle::AnsiColor::BrightYellow,
|
|
anstyle::AnsiColor::BrightBlue,
|
|
anstyle::AnsiColor::BrightMagenta,
|
|
anstyle::AnsiColor::BrightCyan,
|
|
anstyle::AnsiColor::BrightWhite,
|
|
];
|
|
for expected in COLORS {
|
|
let nibble = to_nibble(expected);
|
|
let actual = from_nibble(nibble);
|
|
assert_eq!(expected, actual, "Intermediate: {}", nibble);
|
|
}
|
|
}
|
|
}
|