clean up & reorganize repo + fix clippy warnings

This commit is contained in:
2025-03-15 06:35:53 -04:00
parent 3e0e08d672
commit 418009669c
8 changed files with 55 additions and 367 deletions

View File

@@ -181,6 +181,9 @@ pub fn exit_alternate_screen() {
}
pub fn set_window_title(title: &str) {
assert!(title.len() <= 255, "Title length longer than maximum of 255");
assert!(
title.len() <= 255,
"Title length longer than maximum of 255"
);
println!("\x1b]0;{title}\x1b\x5c");
}

View File

@@ -1,191 +1,54 @@
use std::mem;
use std::os::raw::c_int;
use std::io::{self, Read};
use std::sync::mpsc;
use std::thread;
#[derive(Debug)]
pub enum Key {
String(String),
Char(char),
Enter,
Escape,
Backspace,
Tab,
ShiftTab,
Delete,
Home,
End,
PageUp,
PageDown,
ArrowUp,
ArrowDown,
ArrowLeft,
ArrowRight,
Ctrl(char),
/// An asynchronous reader.
///
/// This acts as any other stream, with the exception that reading from it won't block. Instead,
/// the buffer will only be partially updated based on how much the internal buffer holds.
pub struct AsyncReader {
recv: mpsc::Receiver<io::Result<u8>>,
}
#[cfg(unix)]
pub mod platform {
use super::*;
use std::fs::File;
use std::io::Read;
use std::os::fd::AsRawFd;
unsafe extern "C" {
fn tcgetattr(fd: c_int, termios_p: *mut Termios) -> c_int;
fn tcsetattr(fd: c_int, optional_actions: c_int, termios_p: *const Termios) -> c_int;
fn fcntl(fd: c_int, cmd: c_int, arg: c_int) -> c_int;
}
const TCSAFLUSH: c_int = 2;
const ICANON: c_int = 0o0002;
const ECHO: c_int = 0o0010;
const O_NONBLOCK: c_int = 0x800;
#[repr(C)]
#[derive(Clone)]
struct Termios {
c_iflag: u32,
c_oflag: u32,
c_cflag: u32,
c_lflag: u32,
c_cc: [u8; 32],
}
pub struct RawInput {
stdin: File,
original_termios: Termios,
}
impl RawInput {
pub fn new() -> Self {
let stdin = File::open("/dev/tty").unwrap();
let fd = stdin.as_raw_fd();
let mut termios: Termios = unsafe { mem::zeroed() };
unsafe {
tcgetattr(fd, &mut termios);
let mut new_termios = termios.clone();
new_termios.c_lflag &= !(ICANON | ECHO) as u32;
tcsetattr(fd, TCSAFLUSH, &new_termios);
fcntl(fd, 4, O_NONBLOCK);
impl Read for AsyncReader {
/// Read from the byte stream.
///
/// This will never block, but try to drain the event queue until empty. If the total number of
/// bytes written is lower than the buffer's length, the event queue is empty or that the event
/// stream halted.
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let mut total = 0;
loop {
if total >= buf.len() {
break;
}
Self {
stdin,
original_termios: termios,
match self.recv.try_recv() {
Ok(Ok(b)) => {
buf[total] = b;
total += 1;
}
Ok(Err(e)) => return Err(e),
Err(_) => break,
}
}
pub fn read_key(&mut self) -> [u8; 6] {
let mut buf = [0; 6];
_ = self.stdin.read(&mut buf);
buf
}
}
impl Drop for RawInput {
fn drop(&mut self) {
unsafe { tcsetattr(self.stdin.as_raw_fd(), TCSAFLUSH, &self.original_termios) };
}
Ok(total)
}
}
#[cfg(windows)]
pub mod platform {
use super::*;
use std::ptr;
impl AsyncReader {
pub fn new<R: Read + Send + 'static>(reader: R) -> Self {
let (send, recv) = mpsc::channel();
extern "system" {
fn GetStdHandle(nStdHandle: c_int) -> isize;
fn SetConsoleMode(hConsoleHandle: isize, dwMode: u32) -> i32;
fn GetConsoleMode(hConsoleHandle: isize, lpMode: *mut u32) -> i32;
fn ReadConsoleInputW(hConsoleInput: isize, lpBuffer: *mut InputRecord, nLength: u32, lpNumberOfEventsRead: *mut u32) -> i32;
fn PeekConsoleInputW(hConsoleInput: isize, lpBuffer: *mut InputRecord, nLength: u32, lpNumberOfEventsRead: *mut u32) -> i32;
}
const STD_INPUT_HANDLE: c_int = -10;
const ENABLE_PROCESSED_INPUT: u32 = 0x0001;
const ENABLE_LINE_INPUT: u32 = 0x0002;
const ENABLE_ECHO_INPUT: u32 = 0x0004;
#[repr(C)]
struct InputRecord {
event_type: c_ushort,
event: KeyEventRecord,
}
#[repr(C)]
struct KeyEventRecord {
key_down: i32,
repeat_count: c_ushort,
virtual_key_code: c_ushort,
virtual_scan_code: c_ushort,
unicode_char: u16,
control_key_state: u32,
}
pub struct RawInput {
handle: isize,
original_mode: u32,
}
impl RawInput {
pub fn new() -> Self {
unsafe {
let handle = GetStdHandle(STD_INPUT_HANDLE);
let mut mode = 0;
GetConsoleMode(handle, &mut mode);
SetConsoleMode(handle, mode & !(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT));
Self {
handle,
original_mode: mode,
thread::spawn(move || {
for i in reader.bytes() {
if send.send(i).is_err() {
return;
}
}
}
});
pub fn read_key(&mut self) -> Option<Key> {
let mut records: [InputRecord; 1] = unsafe { mem::zeroed() };
let mut num_read = 0;
unsafe {
PeekConsoleInputW(self.handle, records.as_mut_ptr(), 1, &mut num_read);
if num_read == 0 {
return None;
}
ReadConsoleInputW(self.handle, records.as_mut_ptr(), 1, &mut num_read);
if records[0].event.key_down == 0 {
return None;
}
match records[0].event.virtual_key_code {
0x1B => Some(Key::Escape),
0x0D => Some(Key::Enter),
0x08 => Some(Key::Backspace),
0x09 => Some(Key::Tab),
0x2F => Some(Key::ShiftTab),
0x2E => Some(Key::Delete),
0x24 => Some(Key::Home),
0x23 => Some(Key::End),
0x21 => Some(Key::PageUp),
0x22 => Some(Key::PageDown),
0x26 => Some(Key::ArrowUp),
0x28 => Some(Key::ArrowDown),
0x25 => Some(Key::ArrowLeft),
0x27 => Some(Key::ArrowRight),
c if c >= 1 && c <= 26 => Some(Key::Ctrl((b'a' + (c - 1) as u8) as char)),
c if records[0].event.unicode_char != 0 => Some(Key::Char(records[0].event.unicode_char as u8 as char)),
_ => None,
}
}
}
}
impl Drop for RawInput {
fn drop(&mut self) {
unsafe { SetConsoleMode(self.handle, self.original_mode) };
}
Self { recv }
}
}

View File

@@ -1,78 +0,0 @@
/// An asynchronous reader.
///
/// This acts as any other stream, with the exception that reading from it won't block. Instead,
/// the buffer will only be partially updated based on how much the internal buffer holds.
pub struct AsyncReader {
recv: mpsc::Receiver<io::Result<u8>>,
}
impl Read for AsyncReader {
/// Read from the byte stream.
///
/// This will never block, but try to drain the event queue until empty. If the total number of
/// bytes written is lower than the buffer's length, the event queue is empty or that the event
/// stream halted.
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let mut total = 0;
loop {
if total >= buf.len() {
break;
}
match self.recv.try_recv() {
Ok(Ok(b)) => {
buf[total] = b;
total += 1;
}
Ok(Err(e)) => return Err(e),
Err(_) => break,
}
}
Ok(total)
}
}
impl AsyncReader {
pub fn<R: Read> new(reader: R) -> Self {
let (send, recv) = mpsc::channel();
thread::spawn(move || {
for i in reader.bytes() {
if send.send(i).is_err() {
return;
}
}
});
Self { recv: recv }
}

View File

@@ -1,4 +1,5 @@
#![warn(clippy::all, clippy::pedantic)]
pub mod ansi;
pub mod input;
pub mod os;

View File

@@ -1,14 +1,12 @@
#![allow(unused_imports)]
#[cfg(unix)]
mod unix;
#[cfg(windows)]
mod windows;
#[path = "unix.rs"] mod unix;
#[cfg(unix)]
use unix as os;
#[cfg(windows)]
#[path = "windows.rs"] mod windows;
#[cfg(windows)]
use windows as os;

View File

@@ -1,98 +0,0 @@
use std::ffi::{c_int, c_uint, c_ulong, c_ushort};
use std::io;
use std::mem;
unsafe extern "C" {
fn isatty(fd: c_int) -> c_int;
fn ioctl(fd: c_int, request: c_ulong, argp: *mut u8) -> c_int;
fn tcgetattr(fd: c_int, termios_p: *mut Termios) -> c_int;
fn tcsetattr(fd: c_int, optional_actions: c_int, termios: *mut Termios) -> c_int;
fn cfmakeraw(termios: *mut Termios);
}
#[cfg(target_os = "linux")]
const TIOCGWINSZ: c_ulong = 0x5413;
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
const TIOCGWINSZ: c_ulong = 0x40087468;
#[cfg(target_os = "linux")]
const NCCS: usize = 32;
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
const NCCS: usize = 20;
#[repr(C)]
#[derive(Debug, Clone, Copy)]
struct Winsize {
row: c_ushort,
col: c_ushort,
xpixel: c_ushort,
ypixel: c_ushort,
}
#[repr(C)]
#[derive(Debug, Clone)]
pub struct Termios {
iflag: c_uint,
oflag: c_uint,
cflag: c_uint,
lflag: c_uint,
cc: [u8; NCCS],
}
#[derive(Debug, Clone)]
pub struct RawTerminal {
input: Termios,
output: Termios,
}
impl RawTerminal {
pub fn new() -> io::Result<RawTerminal> {
let mut term: RawTerminal = unsafe { mem::zeroed() };
if unsafe { tcgetattr(0, &raw mut term.input) } != 0 {
return Err(io::Error::last_os_error())
}
if unsafe { tcgetattr(1, &raw mut term.output) } != 0 {
return Err(io::Error::last_os_error())
}
let orig_term = term.clone();
unsafe { cfmakeraw(&raw mut term.input) };
unsafe { cfmakeraw(&raw mut term.output) };
if unsafe { tcsetattr(0, 0, &raw mut term.input) } != 0 {
return Err(io::Error::last_os_error())
}
if unsafe { tcsetattr(1, 0, &raw mut term.output) } != 0 {
return Err(io::Error::last_os_error())
}
Ok(orig_term)
}
}
impl Drop for RawTerminal {
fn drop(&mut self) {
unsafe { tcsetattr(0, 0, &raw mut self.input) };
unsafe { tcsetattr(1, 0, &raw mut self.input) };
}
}
pub fn istty() -> bool {
unsafe { isatty(1) != 0 }
}
pub fn get_terminal_size() -> io::Result<(c_ushort, c_ushort)> {
let mut winsize = unsafe { std::mem::zeroed::<Winsize>() };
let ioctl_result = unsafe { ioctl(1, TIOCGWINSZ, &raw mut winsize as *mut u8) };
if ioctl_result == 0 {
Ok((winsize.col, winsize.row))
} else {
Err(io::Error::last_os_error())
}
}
pub fn enable_ansi() -> io::Result<()> {
// ANSI is on by default on unix platforms
// This is here for compatibility with the windows version of this API
Ok(())
}

View File

@@ -1,10 +1,9 @@
#![allow(non_snake_case)]
use std::ffi::{c_int, c_uint, c_ulong, c_ushort};
use std::io;
const STD_INPUT_HANDLE: u32 = 0xFFFFFFF6;
const STD_OUTPUT_HANDLE: u32 = 0xFFFFFFF5;
const STD_INPUT_HANDLE: u32 = 0xFFFF_FFF6;
const STD_OUTPUT_HANDLE: u32 = 0xFFFF_FFF5;
const ENABLE_VIRTUAL_TERMINAL_PROCESSING: u32 = 4;
const ENABLE_ECHO_INPUT: u32 = 4;
const ENABLE_LINE_INPUT: u32 = 2;
@@ -37,12 +36,12 @@ struct ConsoleScreenBufferInfo {
dwMaximumWindowSizeY: u16,
}
pub fn is_terminal() -> bool {
#[must_use] pub fn is_terminal() -> bool {
let handle = get_std_handle(STD_OUTPUT_HANDLE);
match handle {
Ok(handle) => {
let mut dwMode = 0;
return unsafe { GetConsoleMode(handle, &mut dwMode) != 0 };
unsafe { GetConsoleMode(handle, &mut dwMode) != 0 }
}
_ => false,
}
@@ -52,8 +51,8 @@ pub fn get_terminal_size() -> io::Result<(u16, u16)> {
let handle = get_std_handle(STD_OUTPUT_HANDLE)?;
let mut csbi = ConsoleScreenBufferInfo::default();
if unsafe { GetConsoleScreenBufferInfo(handle, &mut csbi) != 0 } {
let width = csbi.dwSizeX as u16;
let height = csbi.dwSizeY as u16;
let width = csbi.dwSizeX;
let height = csbi.dwSizeY;
return Ok((width, height));
}
Err(io::Error::last_os_error())
@@ -88,10 +87,10 @@ pub fn disable_raw_mode() -> io::Result<()> {
fn get_std_handle(handle: u32) -> io::Result<usize> {
let handle = unsafe { GetStdHandle(handle) };
if handle != INVALID_HANDLE_VALUE {
return Ok(handle);
if handle == INVALID_HANDLE_VALUE {
Err(io::Error::last_os_error())
} else {
return Err(io::Error::last_os_error());
Ok(handle)
}
}