271 lines
6.9 KiB
Rust
Raw Normal View History

2024-01-08 18:45:38 +02:00
#![cfg_attr(target_os = "yggdrasil", feature(yggdrasil_os, rustc_private))]
use std::{
2024-12-21 00:28:28 +02:00
fmt, fs::File, io::{self, stdin, stdout, IsTerminal, Read, Stdin, Stdout, Write}, os::fd::{AsRawFd, RawFd}
2024-01-08 18:45:38 +02:00
};
2024-10-31 13:14:07 +02:00
pub use self::{input::ReadChar, sys::RawMode};
2024-01-08 18:45:38 +02:00
#[cfg(target_os = "yggdrasil")]
mod yggdrasil;
#[cfg(target_os = "yggdrasil")]
use yggdrasil as sys;
#[cfg(unix)]
mod unix;
#[cfg(unix)]
use unix as sys;
mod input;
pub use input::{InputError, TermKey};
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("I/O error: {0}")]
IoError(#[from] io::Error),
#[error("Input error: {0}")]
InputError(#[from] InputError),
}
pub trait RawTerminal {
fn raw_enter_alternate_mode(&mut self) -> io::Result<()>;
fn raw_leave_alternate_mode(&mut self) -> io::Result<()>;
fn raw_clear_all(&mut self) -> io::Result<()>;
fn raw_clear_line(&mut self, what: u32) -> io::Result<()>;
fn raw_move_cursor(&mut self, row: usize, column: usize) -> io::Result<()>;
fn raw_set_cursor_style(&mut self, style: CursorStyle) -> io::Result<()>;
fn raw_set_color(&mut self, fgbg: u32, color: Color) -> io::Result<()>;
fn raw_set_style(&mut self, what: u32) -> io::Result<()>;
fn raw_set_cursor_visible(&mut self, visible: bool) -> io::Result<()>;
2024-10-31 13:14:07 +02:00
fn raw_size(&self) -> io::Result<(usize, usize)>;
2024-01-08 18:45:38 +02:00
}
2024-12-21 00:28:28 +02:00
enum TermInput {
Stdin(Stdin),
File(File)
}
impl Read for TermInput {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
match self {
Self::Stdin(stdin) => stdin.read(buf),
Self::File(file) => file.read(buf)
}
}
}
impl AsRawFd for TermInput {
fn as_raw_fd(&self) -> RawFd {
match self {
Self::Stdin(stdin) => stdin.as_raw_fd(),
Self::File(file) => file.as_raw_fd(),
}
}
}
2024-01-08 18:45:38 +02:00
pub struct Term {
2024-12-21 00:28:28 +02:00
stdin: TermInput,
2024-01-08 18:45:38 +02:00
stdout: Stdout,
raw: RawMode,
}
#[derive(Debug, Clone, Copy)]
pub enum CursorStyle {
Default,
Line,
}
#[derive(Debug, Clone, Copy)]
#[repr(u32)]
pub enum Color {
Black = 0,
Red = 1,
Green = 2,
Yellow = 3,
Blue = 4,
Magenta = 5,
Cyan = 6,
White = 7,
Default = 9,
}
#[derive(Debug, Clone, Copy)]
pub enum Clear {
All,
LineToEnd,
}
impl RawTerminal for Stdout {
fn raw_enter_alternate_mode(&mut self) -> io::Result<()> {
self.write_all(b"\x1B[?1049h")
}
fn raw_leave_alternate_mode(&mut self) -> io::Result<()> {
self.write_all(b"\x1B[?1049l")
}
fn raw_clear_all(&mut self) -> io::Result<()> {
self.write_all(b"\x1B[2J")
}
fn raw_clear_line(&mut self, what: u32) -> io::Result<()> {
write!(self, "\x1B[{}K", what)
}
fn raw_move_cursor(&mut self, row: usize, column: usize) -> io::Result<()> {
write!(self, "\x1B[{};{}f", row + 1, column + 1)
}
fn raw_set_cursor_style(&mut self, style: CursorStyle) -> io::Result<()> {
// TODO yggdrasil support for cursor styles
#[cfg(not(target_os = "yggdrasil"))]
{
match style {
CursorStyle::Default => self.write_all(b"\x1B[0 q")?,
CursorStyle::Line => self.write_all(b"\x1B[6 q")?,
}
}
#[cfg(target_os = "yggdrasil")]
{
let _ = style;
}
Ok(())
}
fn raw_set_color(&mut self, fgbg: u32, color: Color) -> io::Result<()> {
write!(self, "\x1B[{}{}m", fgbg, color as u32)
}
fn raw_set_style(&mut self, what: u32) -> io::Result<()> {
write!(self, "\x1B[{}m", what)
}
fn raw_set_cursor_visible(&mut self, visible: bool) -> io::Result<()> {
if visible {
write!(self, "\x1B[?25h")
} else {
write!(self, "\x1B[?25l")
}
}
2024-10-31 13:14:07 +02:00
fn raw_size(&self) -> io::Result<(usize, usize)> {
unsafe { sys::terminal_size(self) }
}
2024-01-08 18:45:38 +02:00
}
impl Term {
2024-12-21 00:28:28 +02:00
pub fn stdin_is_tty() -> bool {
stdin().is_terminal()
2024-01-08 18:45:38 +02:00
}
pub fn input_fd(&self) -> RawFd {
self.stdin.as_raw_fd()
}
2024-01-08 18:45:38 +02:00
pub fn open() -> Result<Self, Error> {
let stdin = stdin();
2024-12-21 00:28:28 +02:00
let stdin = if stdin.is_terminal() {
TermInput::Stdin(stdin)
} else {
let file = File::open("/dev/tty")?;
TermInput::File(file)
};
2024-01-08 18:45:38 +02:00
let mut stdout = stdout();
// Set stdin to raw mode
let raw = unsafe { RawMode::enter(&stdin)? };
stdout.raw_enter_alternate_mode()?;
stdout.raw_clear_all()?;
stdout.raw_move_cursor(0, 0)?;
Ok(Self { stdin, stdout, raw })
}
pub fn set_cursor_position(&mut self, row: usize, column: usize) -> Result<(), Error> {
self.stdout
.raw_move_cursor(row, column)
.map_err(Error::from)
}
pub fn set_cursor_visible(&mut self, visible: bool) -> Result<(), Error> {
#[cfg(unix)]
{
self.stdout
.raw_set_cursor_visible(visible)
.map_err(Error::from)
}
#[cfg(target_os = "yggdrasil")]
{
2024-01-15 18:20:02 +02:00
let _ = visible;
2024-01-08 18:45:38 +02:00
Ok(())
}
}
pub fn set_cursor_style(&mut self, style: CursorStyle) -> Result<(), Error> {
self.stdout.raw_set_cursor_style(style).map_err(Error::from)
}
pub fn size(&self) -> Result<(usize, usize), Error> {
2024-10-31 13:14:07 +02:00
self.stdout.raw_size().map_err(Error::from)
2024-01-08 18:45:38 +02:00
}
pub fn set_foreground(&mut self, color: Color) -> Result<(), Error> {
self.stdout.raw_set_color(3, color).map_err(Error::from)
}
pub fn set_background(&mut self, color: Color) -> Result<(), Error> {
self.stdout.raw_set_color(4, color).map_err(Error::from)
}
pub fn set_bright(&mut self, bright: bool) -> Result<(), Error> {
if bright {
self.stdout.raw_set_style(2)
} else {
self.stdout.raw_set_style(22)
}
.map_err(Error::from)
}
pub fn reset_style(&mut self) -> Result<(), Error> {
self.stdout.raw_set_style(0).map_err(Error::from)
}
pub fn clear(&mut self, clear: Clear) -> Result<(), Error> {
match clear {
Clear::All => self.stdout.raw_clear_all(),
Clear::LineToEnd => self.stdout.raw_clear_line(0),
}
.map_err(Error::from)
}
pub fn read_key(&mut self) -> Result<TermKey, Error> {
let ch = self.stdin.read_char().map_err(Error::from)?;
if ch == '\x1B' {
return Ok(TermKey::Escape);
}
Ok(TermKey::Char(ch))
}
pub fn flush(&mut self) -> Result<(), Error> {
self.stdout.flush().map_err(Error::from)
}
}
2024-10-31 13:14:07 +02:00
impl AsRawFd for Term {
fn as_raw_fd(&self) -> RawFd {
self.input_fd()
}
}
2024-01-08 18:45:38 +02:00
impl fmt::Write for Term {
fn write_str(&mut self, s: &str) -> fmt::Result {
self.stdout.write_all(s.as_bytes()).map_err(|_| fmt::Error)
}
}
impl Drop for Term {
fn drop(&mut self) {
unsafe {
self.raw.leave(&self.stdin);
}
self.stdout.raw_leave_alternate_mode().ok();
}
}