use std::io::Write;
use crate::Command;
#[derive(Debug, Clone, Copy)]
pub struct EnterAlternateScreen;
impl Command for EnterAlternateScreen {
fn write_ansi(&self, w: &mut impl Write) -> std::io::Result<()> {
w.write_all(b"\x1b[?1049h")
}
}
#[derive(Debug, Clone, Copy)]
pub struct LeaveAlternateScreen;
impl Command for LeaveAlternateScreen {
fn write_ansi(&self, w: &mut impl Write) -> std::io::Result<()> {
w.write_all(b"\x1b[?1049l")
}
}
#[derive(Debug, Clone, Copy)]
pub struct Clear(pub ClearType);
impl Command for Clear {
fn write_ansi(&self, w: &mut impl Write) -> std::io::Result<()> {
match self.0 {
ClearType::All => w.write_all(b"\x1b[2J"),
ClearType::Purge => w.write_all(b"\x1b[3J"),
ClearType::FromCursorDown => w.write_all(b"\x1b[J"),
ClearType::FromCursorUp => w.write_all(b"\x1b[1J"),
ClearType::CurrentLine => w.write_all(b"\x1b[2K"),
ClearType::UntilNewLine => w.write_all(b"\x1b[K"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ClearType {
All,
Purge,
FromCursorDown,
FromCursorUp,
CurrentLine,
UntilNewLine,
}
#[cfg(unix)]
mod unix {
use std::io;
use std::sync::Mutex;
static ORIGINAL_TERMIOS: Mutex<Option<libc::termios>> = Mutex::new(None);
pub fn enable_raw_mode() -> io::Result<()> {
let mut original = ORIGINAL_TERMIOS.lock().unwrap();
if original.is_some() {
return Ok(()); }
unsafe {
let mut termios: libc::termios = std::mem::zeroed();
if libc::tcgetattr(libc::STDIN_FILENO, &mut termios) != 0 {
return Err(io::Error::last_os_error());
}
let saved = termios;
termios.c_iflag &= !(libc::IGNBRK
| libc::BRKINT
| libc::PARMRK
| libc::ISTRIP
| libc::INLCR
| libc::IGNCR
| libc::ICRNL
| libc::IXON);
termios.c_oflag &= !libc::OPOST;
termios.c_lflag &=
!(libc::ECHO | libc::ECHONL | libc::ICANON | libc::ISIG | libc::IEXTEN);
termios.c_cflag &= !(libc::CSIZE | libc::PARENB);
termios.c_cflag |= libc::CS8;
termios.c_cc[libc::VMIN] = 1;
termios.c_cc[libc::VTIME] = 0;
if libc::tcsetattr(libc::STDIN_FILENO, libc::TCSANOW, &termios) != 0 {
return Err(io::Error::last_os_error());
}
*original = Some(saved);
}
Ok(())
}
pub fn disable_raw_mode() -> io::Result<()> {
let mut original = ORIGINAL_TERMIOS.lock().unwrap();
if let Some(termios) = original.take() {
unsafe {
if libc::tcsetattr(libc::STDIN_FILENO, libc::TCSANOW, &termios) != 0 {
return Err(io::Error::last_os_error());
}
}
}
Ok(())
}
pub fn size() -> io::Result<(u16, u16)> {
unsafe {
let mut ws: libc::winsize = std::mem::zeroed();
if libc::ioctl(libc::STDOUT_FILENO, libc::TIOCGWINSZ, &mut ws) != 0 {
return Err(io::Error::last_os_error());
}
Ok((ws.ws_col, ws.ws_row))
}
}
}
#[cfg(target_arch = "wasm32")]
mod wasm {
use std::io;
pub fn enable_raw_mode() -> io::Result<()> {
Ok(())
}
pub fn disable_raw_mode() -> io::Result<()> {
Ok(())
}
pub fn size() -> io::Result<(u16, u16)> {
#[cfg(target_os = "wasi")]
{
query_cursor_position()
}
#[cfg(not(target_os = "wasi"))]
{
Err(io::Error::new(
io::ErrorKind::Unsupported,
"terminal size detection requires WASI",
))
}
}
#[cfg(target_os = "wasi")]
fn query_cursor_position() -> io::Result<(u16, u16)> {
use std::io::{Read, Write};
use std::os::fd::AsRawFd;
let mut stdout = io::stdout().lock();
stdout.write_all(b"\x1b7\x1b[9999;9999H\x1b[6n\x1b8")?;
stdout.flush()?;
let fd = io::stdin().as_raw_fd();
unsafe {
let mut pfd = libc::pollfd {
fd,
events: libc::POLLIN,
revents: 0,
};
if libc::poll(&mut pfd, 1, 1000) <= 0 {
return Err(io::Error::new(
io::ErrorKind::TimedOut,
"terminal did not respond to CPR query",
));
}
}
let mut buf = [0u8; 32];
let mut len = 0;
let mut stdin = io::stdin().lock();
while len < buf.len() {
match stdin.read(&mut buf[len..len + 1]) {
Ok(1) => {
len += 1;
if buf[len - 1] == b'R' {
break;
}
}
_ => break,
}
}
let csi = buf[..len]
.windows(2)
.position(|w| w == b"\x1b[")
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "no CSI in CPR response"))?;
let inner = &buf[csi + 2..len];
let r = inner
.iter()
.position(|&b| b == b'R')
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "no R in CPR response"))?;
let params = std::str::from_utf8(&inner[..r])
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "invalid CPR response"))?;
let mut parts = params.split(';');
let rows: u16 = parts
.next()
.and_then(|s| s.parse().ok())
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "bad CPR rows"))?;
let cols: u16 = parts
.next()
.and_then(|s| s.parse().ok())
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "bad CPR cols"))?;
Ok((cols, rows))
}
}
pub fn enable_raw_mode() -> std::io::Result<()> {
#[cfg(unix)]
{
unix::enable_raw_mode()
}
#[cfg(target_arch = "wasm32")]
{
wasm::enable_raw_mode()
}
}
pub fn disable_raw_mode() -> std::io::Result<()> {
#[cfg(unix)]
{
unix::disable_raw_mode()
}
#[cfg(target_arch = "wasm32")]
{
wasm::disable_raw_mode()
}
}
pub fn size() -> std::io::Result<(u16, u16)> {
#[cfg(unix)]
{
unix::size()
}
#[cfg(target_arch = "wasm32")]
{
wasm::size()
}
}