chore: checkpoint before Python removal

This commit is contained in:
2026-03-26 22:33:59 +00:00
parent 683cec9307
commit e568ddf82a
29972 changed files with 11269302 additions and 2 deletions

View File

@@ -0,0 +1,452 @@
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;
#[allow(clippy::upper_case_acronyms)]
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(crate) 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(crate) 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(crate) 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(crate) 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(crate) 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,
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(crate) 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(crate) 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(crate) 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(crate) 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(crate) 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(crate) 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(crate) 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(crate) 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');
}
}

623
vendor/console/src/windows_term/mod.rs vendored Normal file
View File

@@ -0,0 +1,623 @@
use std::cmp;
use std::env;
use std::ffi::OsStr;
use std::fmt::Display;
use std::io;
use std::iter::once;
use std::mem;
use std::os::raw::c_void;
use std::os::windows::ffi::OsStrExt;
use std::os::windows::io::AsRawHandle;
use std::{char, mem::MaybeUninit};
use encode_unicode::error::Utf16TupleError;
use encode_unicode::CharExt;
use windows_sys::Win32::Foundation::{HANDLE, INVALID_HANDLE_VALUE, MAX_PATH};
use windows_sys::Win32::Storage::FileSystem::{FileNameInfo, GetFileInformationByHandleEx};
use windows_sys::Win32::System::Console::CONSOLE_MODE;
use windows_sys::Win32::System::Console::{
FillConsoleOutputAttribute, FillConsoleOutputCharacterA, GetConsoleCursorInfo, GetConsoleMode,
GetConsoleScreenBufferInfo, GetNumberOfConsoleInputEvents, GetStdHandle, ReadConsoleInputW,
SetConsoleCursorInfo, SetConsoleCursorPosition, SetConsoleMode, SetConsoleTitleW,
CONSOLE_CURSOR_INFO, CONSOLE_SCREEN_BUFFER_INFO, COORD, ENABLE_PROCESSED_INPUT,
ENABLE_VIRTUAL_TERMINAL_PROCESSING, INPUT_RECORD, INPUT_RECORD_0, KEY_EVENT, KEY_EVENT_RECORD,
STD_ERROR_HANDLE, STD_HANDLE, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE,
};
use windows_sys::Win32::UI::Input::KeyboardAndMouse::VIRTUAL_KEY;
use crate::common_term;
use crate::kb::Key;
use crate::term::{Term, TermTarget};
#[cfg(feature = "windows-console-colors")]
mod colors;
#[cfg(feature = "windows-console-colors")]
pub(crate) use self::colors::*;
pub(crate) const DEFAULT_WIDTH: u16 = 79;
pub(crate) fn as_handle(term: &Term) -> HANDLE {
// convert between windows_sys::Win32::Foundation::HANDLE and std::os::windows::raw::HANDLE
term.as_raw_handle() as HANDLE
}
pub(crate) fn is_a_terminal(out: &Term) -> bool {
let (fd, others) = match out.target() {
TermTarget::Stdout => (STD_OUTPUT_HANDLE, [STD_INPUT_HANDLE, STD_ERROR_HANDLE]),
TermTarget::Stderr => (STD_ERROR_HANDLE, [STD_INPUT_HANDLE, STD_OUTPUT_HANDLE]),
};
if unsafe { console_on_any(&[fd]) } {
// False positives aren't possible. If we got a console then
// we definitely have a tty on stdin.
return true;
}
// At this point, we *could* have a false negative. We can determine that
// this is true negative if we can detect the presence of a console on
// any of the other streams. If another stream has a console, then we know
// we're in a Windows console and can therefore trust the negative.
if unsafe { console_on_any(&others) } {
return false;
}
msys_tty_on(out)
}
pub(crate) fn is_a_color_terminal(out: &Term) -> bool {
if !is_a_terminal(out) {
return false;
}
if msys_tty_on(out) {
return match env::var("TERM") {
Ok(term) => term != "dumb",
Err(_) => true,
};
}
enable_ansi_on(out)
}
/// Enables or disables the `mode` flag on the given `HANDLE` and yields the previous mode.
fn set_console_mode(handle: HANDLE, mode: CONSOLE_MODE, enable: bool) -> Option<CONSOLE_MODE> {
unsafe {
let mut dw_mode = 0;
if GetConsoleMode(handle, &mut dw_mode) == 0 {
return None;
}
let new_dw_mode = match enable {
true => dw_mode | mode,
false => dw_mode & !mode,
};
if SetConsoleMode(handle, new_dw_mode) == 0 {
return None;
}
Some(dw_mode)
}
}
struct ConsoleModeGuard {
handle: HANDLE,
restore_mode: CONSOLE_MODE,
}
impl ConsoleModeGuard {
fn set(handle: HANDLE, mode: CONSOLE_MODE, enable: bool) -> Option<Self> {
Some(ConsoleModeGuard {
handle,
restore_mode: set_console_mode(handle, mode, enable)?,
})
}
}
impl Drop for ConsoleModeGuard {
fn drop(&mut self) {
unsafe {
SetConsoleMode(self.handle, self.restore_mode);
}
}
}
fn enable_ansi_on(out: &Term) -> bool {
set_console_mode(
out.as_raw_handle(),
ENABLE_VIRTUAL_TERMINAL_PROCESSING,
true,
)
.is_some()
}
unsafe fn console_on_any(fds: &[STD_HANDLE]) -> bool {
for &fd in fds {
let mut out = 0;
let handle = GetStdHandle(fd);
if GetConsoleMode(handle, &mut out) != 0 {
return true;
}
}
false
}
pub(crate) fn terminal_size(out: &Term) -> Option<(u16, u16)> {
use windows_sys::Win32::System::Console::SMALL_RECT;
// convert between windows_sys::Win32::Foundation::HANDLE and std::os::windows::raw::HANDLE
let handle = out.as_raw_handle();
let hand = handle as windows_sys::Win32::Foundation::HANDLE;
if hand == INVALID_HANDLE_VALUE {
return None;
}
let zc = COORD { X: 0, Y: 0 };
let mut csbi = CONSOLE_SCREEN_BUFFER_INFO {
dwSize: zc,
dwCursorPosition: zc,
wAttributes: 0,
srWindow: SMALL_RECT {
Left: 0,
Top: 0,
Right: 0,
Bottom: 0,
},
dwMaximumWindowSize: zc,
};
if unsafe { GetConsoleScreenBufferInfo(hand, &mut csbi) } == 0 {
return None;
}
let rows = (csbi.srWindow.Bottom - csbi.srWindow.Top + 1) as u16;
let columns = (csbi.srWindow.Right - csbi.srWindow.Left + 1) as u16;
Some((rows, columns))
}
pub(crate) fn move_cursor_to(out: &Term, x: usize, y: usize) -> io::Result<()> {
if out.is_msys_tty {
return common_term::move_cursor_to(out, x, y);
}
if let Some((hand, _)) = get_console_screen_buffer_info(as_handle(out)) {
unsafe {
SetConsoleCursorPosition(
hand,
COORD {
X: x as i16,
Y: y as i16,
},
);
}
}
Ok(())
}
pub(crate) fn move_cursor_up(out: &Term, n: usize) -> io::Result<()> {
if out.is_msys_tty {
return common_term::move_cursor_up(out, n);
}
if let Some((_, csbi)) = get_console_screen_buffer_info(as_handle(out)) {
move_cursor_to(out, 0, csbi.dwCursorPosition.Y as usize - n)?;
}
Ok(())
}
pub(crate) fn move_cursor_down(out: &Term, n: usize) -> io::Result<()> {
if out.is_msys_tty {
return common_term::move_cursor_down(out, n);
}
if let Some((_, csbi)) = get_console_screen_buffer_info(as_handle(out)) {
move_cursor_to(out, 0, csbi.dwCursorPosition.Y as usize + n)?;
}
Ok(())
}
pub(crate) fn move_cursor_left(out: &Term, n: usize) -> io::Result<()> {
if out.is_msys_tty {
return common_term::move_cursor_left(out, n);
}
if let Some((_, csbi)) = get_console_screen_buffer_info(as_handle(out)) {
move_cursor_to(
out,
csbi.dwCursorPosition.X as usize - n,
csbi.dwCursorPosition.Y as usize,
)?;
}
Ok(())
}
pub(crate) fn move_cursor_right(out: &Term, n: usize) -> io::Result<()> {
if out.is_msys_tty {
return common_term::move_cursor_right(out, n);
}
if let Some((_, csbi)) = get_console_screen_buffer_info(as_handle(out)) {
move_cursor_to(
out,
csbi.dwCursorPosition.X as usize + n,
csbi.dwCursorPosition.Y as usize,
)?;
}
Ok(())
}
pub(crate) fn clear_line(out: &Term) -> io::Result<()> {
if out.is_msys_tty {
return common_term::clear_line(out);
}
if let Some((hand, csbi)) = get_console_screen_buffer_info(as_handle(out)) {
unsafe {
let width = csbi.srWindow.Right - csbi.srWindow.Left;
let pos = COORD {
X: 0,
Y: csbi.dwCursorPosition.Y,
};
let mut written = 0;
FillConsoleOutputCharacterA(hand, b' ' as i8, width as u32, pos, &mut written);
FillConsoleOutputAttribute(hand, csbi.wAttributes, width as u32, pos, &mut written);
SetConsoleCursorPosition(hand, pos);
}
}
Ok(())
}
pub(crate) fn clear_chars(out: &Term, n: usize) -> io::Result<()> {
if out.is_msys_tty {
return common_term::clear_chars(out, n);
}
if let Some((hand, csbi)) = get_console_screen_buffer_info(as_handle(out)) {
unsafe {
let width = cmp::min(csbi.dwCursorPosition.X, n as i16);
let pos = COORD {
X: csbi.dwCursorPosition.X - width,
Y: csbi.dwCursorPosition.Y,
};
let mut written = 0;
FillConsoleOutputCharacterA(hand, b' ' as i8, width as u32, pos, &mut written);
FillConsoleOutputAttribute(hand, csbi.wAttributes, width as u32, pos, &mut written);
SetConsoleCursorPosition(hand, pos);
}
}
Ok(())
}
pub(crate) fn clear_screen(out: &Term) -> io::Result<()> {
if out.is_msys_tty {
return common_term::clear_screen(out);
}
if let Some((hand, csbi)) = get_console_screen_buffer_info(as_handle(out)) {
unsafe {
let cells = csbi.dwSize.X as u32 * csbi.dwSize.Y as u32; // as u32, or else this causes stack overflows.
let pos = COORD { X: 0, Y: 0 };
let mut written = 0;
FillConsoleOutputCharacterA(hand, b' ' as i8, cells, pos, &mut written); // cells as u32 no longer needed.
FillConsoleOutputAttribute(hand, csbi.wAttributes, cells, pos, &mut written);
SetConsoleCursorPosition(hand, pos);
}
}
Ok(())
}
pub(crate) fn clear_to_end_of_screen(out: &Term) -> io::Result<()> {
if out.is_msys_tty {
return common_term::clear_to_end_of_screen(out);
}
if let Some((hand, csbi)) = get_console_screen_buffer_info(as_handle(out)) {
unsafe {
let bottom = csbi.srWindow.Right as u32 * csbi.srWindow.Bottom as u32;
let cells = bottom - (csbi.dwCursorPosition.X as u32 * csbi.dwCursorPosition.Y as u32); // as u32, or else this causes stack overflows.
let pos = COORD {
X: 0,
Y: csbi.dwCursorPosition.Y,
};
let mut written = 0;
FillConsoleOutputCharacterA(hand, b' ' as i8, cells, pos, &mut written); // cells as u32 no longer needed.
FillConsoleOutputAttribute(hand, csbi.wAttributes, cells, pos, &mut written);
SetConsoleCursorPosition(hand, pos);
}
}
Ok(())
}
pub(crate) fn show_cursor(out: &Term) -> io::Result<()> {
if out.is_msys_tty {
return common_term::show_cursor(out);
}
if let Some((hand, mut cci)) = get_console_cursor_info(as_handle(out)) {
unsafe {
cci.bVisible = 1;
SetConsoleCursorInfo(hand, &cci);
}
}
Ok(())
}
pub(crate) fn hide_cursor(out: &Term) -> io::Result<()> {
if out.is_msys_tty {
return common_term::hide_cursor(out);
}
if let Some((hand, mut cci)) = get_console_cursor_info(as_handle(out)) {
unsafe {
cci.bVisible = 0;
SetConsoleCursorInfo(hand, &cci);
}
}
Ok(())
}
fn get_console_screen_buffer_info(hand: HANDLE) -> Option<(HANDLE, CONSOLE_SCREEN_BUFFER_INFO)> {
let mut csbi: CONSOLE_SCREEN_BUFFER_INFO = unsafe { mem::zeroed() };
match unsafe { GetConsoleScreenBufferInfo(hand, &mut csbi) } {
0 => None,
_ => Some((hand, csbi)),
}
}
fn get_console_cursor_info(hand: HANDLE) -> Option<(HANDLE, CONSOLE_CURSOR_INFO)> {
let mut cci: CONSOLE_CURSOR_INFO = unsafe { mem::zeroed() };
match unsafe { GetConsoleCursorInfo(hand, &mut cci) } {
0 => None,
_ => Some((hand, cci)),
}
}
pub(crate) fn key_from_key_code(code: VIRTUAL_KEY) -> Key {
use windows_sys::Win32::UI::Input::KeyboardAndMouse;
match code {
KeyboardAndMouse::VK_LEFT => Key::ArrowLeft,
KeyboardAndMouse::VK_RIGHT => Key::ArrowRight,
KeyboardAndMouse::VK_UP => Key::ArrowUp,
KeyboardAndMouse::VK_DOWN => Key::ArrowDown,
KeyboardAndMouse::VK_RETURN => Key::Enter,
KeyboardAndMouse::VK_ESCAPE => Key::Escape,
KeyboardAndMouse::VK_BACK => Key::Backspace,
KeyboardAndMouse::VK_TAB => Key::Tab,
KeyboardAndMouse::VK_HOME => Key::Home,
KeyboardAndMouse::VK_END => Key::End,
KeyboardAndMouse::VK_DELETE => Key::Del,
KeyboardAndMouse::VK_SHIFT => Key::Shift,
KeyboardAndMouse::VK_MENU => Key::Alt,
_ => Key::Unknown,
}
}
pub(crate) fn read_secure() -> io::Result<String> {
let mut rv = String::new();
loop {
match read_single_key(false)? {
Key::Enter => {
break;
}
Key::Char('\x08') => {
if !rv.is_empty() {
let new_len = rv.len() - 1;
rv.truncate(new_len);
}
}
Key::Char(c) => {
rv.push(c);
}
_ => {}
}
}
Ok(rv)
}
pub(crate) fn read_single_key(ctrlc_key: bool) -> io::Result<Key> {
let key_event = {
let _guard = ctrlc_key.then(|| {
ConsoleModeGuard::set(
unsafe { GetStdHandle(STD_INPUT_HANDLE) },
ENABLE_PROCESSED_INPUT,
false,
)
});
read_key_event()?
};
let unicode_char = unsafe { key_event.uChar.UnicodeChar };
if unicode_char == 0 {
Ok(key_from_key_code(key_event.wVirtualKeyCode))
} else {
// This is a unicode character, in utf-16. Try to decode it by itself.
match char::from_utf16_tuple((unicode_char, None)) {
Ok(c) => {
// Maintain backward compatibility. The previous implementation (_getwch()) would return
// a special keycode for `Enter`, while ReadConsoleInputW() prefers to use '\r'.
if c == '\r' {
Ok(Key::Enter)
} else if c == '\t' {
Ok(Key::Tab)
} else if c == '\x08' {
Ok(Key::Backspace)
} else if c == '\x1B' {
Ok(Key::Escape)
} else if c == '\x03' && ctrlc_key {
Ok(Key::CtrlC)
} else {
Ok(Key::Char(c))
}
}
// This is part of a surrogate pair. Try to read the second half.
Err(Utf16TupleError::MissingSecond) => {
// Confirm that there is a next character to read.
if get_key_event_count()? == 0 {
let message = format!(
"Read invalid utf16 {}: {}",
unicode_char,
Utf16TupleError::MissingSecond
);
return Err(io::Error::new(io::ErrorKind::InvalidData, message));
}
// Read the next character.
let next_event = read_key_event()?;
let next_surrogate = unsafe { next_event.uChar.UnicodeChar };
// Attempt to decode it.
match char::from_utf16_tuple((unicode_char, Some(next_surrogate))) {
Ok(c) => Ok(Key::Char(c)),
// Return an InvalidData error. This is the recommended value for UTF-related I/O errors.
// (This error is given when reading a non-UTF8 file into a String, for example.)
Err(e) => {
let message = format!(
"Read invalid surrogate pair ({}, {}): {}",
unicode_char, next_surrogate, e
);
Err(io::Error::new(io::ErrorKind::InvalidData, message))
}
}
}
// Return an InvalidData error. This is the recommended value for UTF-related I/O errors.
// (This error is given when reading a non-UTF8 file into a String, for example.)
Err(e) => {
let message = format!("Read invalid utf16 {}: {}", unicode_char, e);
Err(io::Error::new(io::ErrorKind::InvalidData, message))
}
}
}
}
fn get_stdin_handle() -> io::Result<HANDLE> {
let handle = unsafe { GetStdHandle(STD_INPUT_HANDLE) };
if handle == INVALID_HANDLE_VALUE {
Err(io::Error::last_os_error())
} else {
Ok(handle)
}
}
/// Get the number of pending events in the ReadConsoleInput queue. Note that while
/// these aren't necessarily key events, the only way that multiple events can be
/// put into the queue simultaneously is if a unicode character spanning multiple u16's
/// is read.
///
/// Therefore, this is accurate as long as at least one KEY_EVENT has already been read.
fn get_key_event_count() -> io::Result<u32> {
let handle = get_stdin_handle()?;
let mut event_count: u32 = unsafe { mem::zeroed() };
let success = unsafe { GetNumberOfConsoleInputEvents(handle, &mut event_count) };
if success == 0 {
Err(io::Error::last_os_error())
} else {
Ok(event_count)
}
}
fn read_key_event() -> io::Result<KEY_EVENT_RECORD> {
let handle = get_stdin_handle()?;
let mut buffer: INPUT_RECORD = unsafe { mem::zeroed() };
let mut events_read: u32 = unsafe { mem::zeroed() };
let mut key_event: KEY_EVENT_RECORD;
loop {
let success = unsafe { ReadConsoleInputW(handle, &mut buffer, 1, &mut events_read) };
if success == 0 {
return Err(io::Error::last_os_error());
}
if events_read == 0 {
return Err(io::Error::new(
io::ErrorKind::Other,
"ReadConsoleInput returned no events, instead of waiting for an event",
));
}
if events_read == 1 && buffer.EventType != KEY_EVENT as u16 {
// This isn't a key event; ignore it.
continue;
}
key_event = unsafe { mem::transmute::<INPUT_RECORD_0, KEY_EVENT_RECORD>(buffer.Event) };
if key_event.bKeyDown == 0 {
// This is a key being released; ignore it.
continue;
}
return Ok(key_event);
}
}
pub(crate) fn wants_emoji() -> bool {
// If WT_SESSION is set, we can assume we're running in the nne
// Windows Terminal. The correct way to detect this is not available
// yet. See https://github.com/microsoft/terminal/issues/1040
env::var("WT_SESSION").is_ok()
}
/// Returns true if there is an MSYS tty on the given handle.
pub(crate) fn msys_tty_on(term: &Term) -> bool {
let handle = term.as_raw_handle();
unsafe {
// Check whether the Windows 10 native pty is enabled
{
let mut out = MaybeUninit::uninit();
let res = GetConsoleMode(handle as HANDLE, out.as_mut_ptr());
if res != 0 // If res is true then out was initialized.
&& (out.assume_init() & ENABLE_VIRTUAL_TERMINAL_PROCESSING)
== ENABLE_VIRTUAL_TERMINAL_PROCESSING
{
return true;
}
}
/// Mirrors windows_sys::Win32::Storage::FileSystem::FILE_NAME_INFO, giving
/// it a fixed length that we can stack allocate
#[repr(C)]
#[allow(non_snake_case)]
struct FILE_NAME_INFO {
FileNameLength: u32,
FileName: [u16; MAX_PATH as usize],
}
let mut name_info = FILE_NAME_INFO {
FileNameLength: 0,
FileName: [0; MAX_PATH as usize],
};
let res = GetFileInformationByHandleEx(
handle as HANDLE,
FileNameInfo,
&mut name_info as *mut _ as *mut c_void,
std::mem::size_of::<FILE_NAME_INFO>() as u32,
);
if res == 0 {
return false;
}
// Use `get` because `FileNameLength` can be out of range.
let s = match name_info
.FileName
.get(..name_info.FileNameLength as usize / 2)
{
Some(s) => s,
None => return false,
};
let name = String::from_utf16_lossy(s);
// This checks whether 'pty' exists in the file name, which indicates that
// a pseudo-terminal is attached. To mitigate against false positives
// (e.g., an actual file name that contains 'pty'), we also require that
// either the strings 'msys-' or 'cygwin-' are in the file name as well.)
let is_msys = name.contains("msys-") || name.contains("cygwin-");
let is_pty = name.contains("-pty");
is_msys && is_pty
}
}
pub(crate) fn set_title<T: Display>(title: T) {
let buffer: Vec<u16> = OsStr::new(&format!("{}", title))
.encode_wide()
.chain(once(0))
.collect();
unsafe {
SetConsoleTitleW(buffer.as_ptr());
}
}