From 418009669c154d487ccf9c9856512225eca69e56 Mon Sep 17 00:00:00 2001 From: Xyverle Date: Sat, 15 Mar 2025 06:35:53 -0400 Subject: [PATCH] clean up & reorganize repo + fix clippy warnings --- src/ansi.rs | 5 +- src/input.rs | 211 +++++++-------------------------------- src/input/async.rs | 78 --------------- src/lib.rs | 1 + src/{os/mod.rs => os.rs} | 10 +- src/os/unix.rs.new | 98 ------------------ src/{os => }/unix.rs | 0 src/{os => }/windows.rs | 19 ++-- 8 files changed, 55 insertions(+), 367 deletions(-) delete mode 100644 src/input/async.rs rename src/{os/mod.rs => os.rs} (89%) delete mode 100644 src/os/unix.rs.new rename src/{os => }/unix.rs (100%) rename src/{os => }/windows.rs (86%) diff --git a/src/ansi.rs b/src/ansi.rs index c8ba7b1..785ecb6 100644 --- a/src/ansi.rs +++ b/src/ansi.rs @@ -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"); } diff --git a/src/input.rs b/src/input.rs index 7369aed..ea6b78c 100644 --- a/src/input.rs +++ b/src/input.rs @@ -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>, } -#[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 { + 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(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 { - 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 } } } - diff --git a/src/input/async.rs b/src/input/async.rs deleted file mode 100644 index 6a0af87..0000000 --- a/src/input/async.rs +++ /dev/null @@ -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>, - -} - -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 { - - 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 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 } - } diff --git a/src/lib.rs b/src/lib.rs index 9cc2b10..ba5c869 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ #![warn(clippy::all, clippy::pedantic)] pub mod ansi; +pub mod input; pub mod os; diff --git a/src/os/mod.rs b/src/os.rs similarity index 89% rename from src/os/mod.rs rename to src/os.rs index 94ebd17..ddd139f 100644 --- a/src/os/mod.rs +++ b/src/os.rs @@ -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; diff --git a/src/os/unix.rs.new b/src/os/unix.rs.new deleted file mode 100644 index dd2387a..0000000 --- a/src/os/unix.rs.new +++ /dev/null @@ -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 { - 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::() }; - 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(()) -} diff --git a/src/os/unix.rs b/src/unix.rs similarity index 100% rename from src/os/unix.rs rename to src/unix.rs diff --git a/src/os/windows.rs b/src/windows.rs similarity index 86% rename from src/os/windows.rs rename to src/windows.rs index c51c702..0749723 100644 --- a/src/os/windows.rs +++ b/src/windows.rs @@ -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 { 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) } }