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
|
|
|
}
|
|
|
|
|
2024-03-19 23:11:03 +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();
|
|
|
|
}
|
|
|
|
}
|