Many Changes

This commit is contained in:
2025-07-08 13:18:52 -04:00
parent d446e8f31b
commit 0c0d5f15ef
7 changed files with 202 additions and 294 deletions

View File

@@ -38,7 +38,7 @@ fn main() -> io::Result<()> {
COLORS_BG[next(counter)]
));
// q to quit
if input == Event::Key(Key::Char('q'), KeyType::Press, KeyModifiers::none()) {
if input == Event::Key(Key::Char('q'), KeyType::Press, KeyMods::NONE) {
break;
}
counter = next(counter);

View File

@@ -12,6 +12,21 @@ pub use crate::unix::enable_ansi;
#[cfg(windows)]
pub use crate::windows::enable_ansi;
fn alt_screen(bool: bool) -> std::io::Result<()> {
if bool {
print!("{ALT_SCREEN_ENTER}");
} else {
print!("{ALT_SCREEN_EXIT}");
}
io::stdout().flush()?;
Ok(())
}
/// Creates a handler for the alt screen
pub fn alt_screen_handler() -> io::Result<crate::Handler> {
crate::Handler::new(&alt_screen)
}
/// Sets the terminal to an arbitrary 12-bit/truecolor color in the foreground when printed
#[must_use]
pub fn rgb_color_code_fg(red: u8, green: u8, blue: u8) -> String {
@@ -86,6 +101,11 @@ pub fn move_cursor_to_position(column: u16, line: u16) -> String {
)
}
// /// Enables mouse input
// pub const ENABLE_MOUSE: &str = "\x1b[?1000h\x1b[?1002h\x1b[?1015h\x1b[?1006h";
// /// Disables mouse input
// pub const DISABLE_MOUSE: &str = "\x1b[?1006l\x1b[?1015l\x1b[?1002l\x1b[?1000l"";
/// Saves the current cursor position
pub const CURSOR_POSITION_SAVE: &str = "\x1b7";
/// Restores the saved cursor position
@@ -256,75 +276,3 @@ pub const COLORS: [(&str, &str); 9] = [
(COLOR_WHITE_FG, COLOR_WHITE_BG),
(COLOR_DEFAULT_FG, COLOR_DEFAULT_BG),
];
/// Struct that prints `ALT_SCREEN_ENTER` on construction
/// and `ALT_SCREEN_EXIT` on destruction
///
/// Prefered over function as it prints `ALT_SCREEN_EXIT` on panic
pub struct AltScreenHandler {
enabled: bool,
}
impl AltScreenHandler {
/// Creates a new instance and sets the terminal into the alternate screen
///
/// # Errors
///
/// If it fails to print or flush the output
pub fn new() -> io::Result<Self> {
print!("{ALT_SCREEN_ENTER}");
io::stdout().flush()?;
Ok(Self { enabled: true })
}
/// Enables raw mode
///
/// # Errors
///
/// Never errors if the alt screen is already enabled
///
/// If it fails to print or flush the output
pub fn enable(&mut self) -> io::Result<()> {
self.set(true)
}
/// Disables raw mode
///
/// # Errors
///
/// Never errors if the alt screen is already disabled
///
/// If it fails to print or flush the output
pub fn disable(&mut self) -> io::Result<()> {
self.set(false)
}
/// Sets raw mode
///
/// # Errors
///
/// Never errors if the alt screen is in the same state as the boolean
///
/// If it fails to print or flush the output
pub fn set(&mut self, alt: bool) -> io::Result<()> {
if self.enabled == alt {
return Ok(());
}
if alt {
print!("{ALT_SCREEN_ENTER}");
} else {
print!("{ALT_SCREEN_EXIT}");
}
io::stdout().flush()?;
self.enabled = alt;
Ok(())
}
/// Gets if the alt screen is enabled
#[must_use]
pub fn get(&self) -> bool {
self.enabled
}
}
impl Drop for AltScreenHandler {
fn drop(&mut self) {
self.disable().expect("Failed to disable alternate screen");
}
}

View File

@@ -10,80 +10,16 @@ pub use crate::unix::{disable_raw_mode, enable_raw_mode, get_terminal_size};
#[cfg(windows)]
pub use crate::windows::{disable_raw_mode, enable_raw_mode, get_terminal_size};
/// Struct that calls `enable_raw_mode` on construction
/// and `disable_raw_mode` on destruction
///
/// Prefered over function as it calls `disable_raw_mode` on panic
pub struct RawModeHandler {
enabled: bool,
}
impl RawModeHandler {
/// Creates a new instance and sets the terminal to raw mode
///
/// # Errors
///
/// If there is no stdin,
/// stdin is not a tty,
/// or it fails to change terminal settings
pub fn new() -> io::Result<Self> {
fn raw_mode(bool: bool) -> std::io::Result<()> {
if bool {
enable_raw_mode()?;
Ok(Self { enabled: true })
}
/// Enables raw mode
///
/// # Errors
///
/// Never errors if raw mode is already enabled
///
/// If there is no stdin,
/// stdin is not a tty,
/// or it fails to change terminal settings
pub fn enable(&mut self) -> io::Result<()> {
self.set(true)
}
/// Disables raw mode
///
/// # Errors
///
/// Never errors if raw mode is already disabled
///
/// If there is no stdin,
/// stdin is not a tty,
/// or it fails to change terminal settings
pub fn disable(&mut self) -> io::Result<()> {
self.set(false)
}
/// Sets raw mode
///
/// # Errors
///
/// Never errors if raw mode is in the same state as the boolean
///
/// If there is no stdin,
/// stdin is not a tty,
/// or it fails to change terminal settings
pub fn set(&mut self, raw: bool) -> io::Result<()> {
if self.enabled == raw {
return Ok(());
}
if raw {
enable_raw_mode()?;
} else {
disable_raw_mode()?;
}
self.enabled = raw;
Ok(())
}
/// Gets if raw mode is enabled
#[must_use]
pub fn get(&self) -> bool {
self.enabled
} else {
disable_raw_mode()?;
}
Ok(())
}
impl Drop for RawModeHandler {
fn drop(&mut self) {
self.disable().expect("Failed to disable terminal raw mode");
}
/// Creates a handler for raw mode
pub fn raw_mode_handler() -> io::Result<crate::Handler> {
crate::Handler::new(&raw_mode)
}

View File

@@ -12,7 +12,7 @@
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum Event {
/// An event that happens upon a key being pressed
Key(Key, KeyType, KeyModifiers),
Key(Key, KeyType, KeyMods),
/// An event that happens upon focus to the terminal window being gained
FocusGained,
/// An event that happens upon focus to the terminal window being lost
@@ -52,54 +52,47 @@ pub enum Key {
Char(char),
/// The Escape key
Escape,
/// A null byte sent to the terminal
///
/// Can mean several different things
Null,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)]
#[allow(clippy::struct_excessive_bools)]
pub struct KeyModifiers {
pub struct KeyMods {
pub shift: bool,
pub alt: bool,
pub ctrl: bool,
pub meta: bool,
}
impl KeyModifiers {
impl KeyMods {
pub const NONE: Self = Self {
shift: false,
alt: false,
ctrl: false,
meta: false,
};
pub const SHIFT: Self = Self::NONE.shift(true);
pub const ALT: Self = Self::NONE.alt(true);
pub const CTRL: Self = Self::NONE.ctrl(true);
pub const META: Self = Self::NONE.meta(true);
#[must_use]
pub const fn none() -> Self {
Self {
shift: false,
alt: false,
ctrl: false,
meta: false,
}
pub const fn shift(mut self, on: bool) -> Self {
self.shift = on;
self
}
#[must_use]
pub const fn shift(self) -> Self {
let mut value = self;
value.shift = true;
value
pub const fn alt(mut self, on: bool) -> Self {
self.alt = on;
self
}
#[must_use]
pub const fn alt(self) -> Self {
let mut value = self;
value.alt = true;
value
pub const fn ctrl(mut self, on: bool) -> Self {
self.ctrl = on;
self
}
#[must_use]
pub const fn ctrl(self) -> Self {
let mut value = self;
value.ctrl = true;
value
}
#[must_use]
pub const fn meta(self) -> Self {
let mut value = self;
value.meta = true;
value
pub const fn meta(mut self, on: bool) -> Self {
self.meta = on;
self
}
}
@@ -113,6 +106,10 @@ pub enum KeyType {
Release,
}
pub fn press_key(key: Key, key_mods: KeyMods) -> Event {
Event::Key(key, KeyType::Press, key_mods)
}
#[cfg(unix)]
pub use crate::unix::poll_input;

View File

@@ -40,6 +40,8 @@
//!
//! \* Do not have full support for advanced input
use std::io;
#[cfg(unix)]
mod unix;
@@ -56,3 +58,64 @@ pub mod prelude {
pub use crate::control::*;
pub use crate::input::*;
}
/// Struct that calls `func(true)` on construction
/// and `func(false)` on destruction
pub struct Handler {
enabled: bool,
func: &'static dyn Fn(bool) -> io::Result<()>,
}
impl Handler {
/// Creates a new instance and turns it on
///
/// # Errors
///
/// If the function errors
pub fn new(func: &'static dyn Fn(bool) -> io::Result<()>) -> io::Result<Self> {
let mut handler = Self {
enabled: true,
func,
};
handler.set(true)?;
return Ok(handler);
}
/// Calls `func(true)`
///
/// # Errors
///
/// If the function errors
pub fn enable(&mut self) -> io::Result<()> {
self.set(true)
}
/// Calls `func(false)`
///
/// # Errors
///
/// If the function errors
pub fn disable(&mut self) -> io::Result<()> {
self.set(false)
}
/// Calls `func(set)`
///
/// # Errors
///
/// If the function errors
pub fn set(&mut self, set: bool) -> io::Result<()> {
if self.enabled != set {
(self.func)(set)?;
}
Ok(())
}
/// Gets if it is enabled
#[must_use]
pub fn get(&self) -> bool {
self.enabled
}
}
impl Drop for Handler {
fn drop(&mut self) {
self.disable().expect("Failed to disable terminal raw mode");
}
}

View File

@@ -2,7 +2,7 @@ use std::ffi::{c_int, c_uint, c_ulong, c_ushort};
use std::io;
use std::sync::LazyLock;
use crate::input::{Event, Key, KeyModifiers, KeyType};
use crate::input::{Event, Key, KeyMods, KeyType, press_key};
use std::ffi::{c_short, c_void};
use std::time::Duration;
@@ -133,6 +133,41 @@ pub fn get_terminal_size() -> io::Result<(u16, u16)> {
}
}
// Some of this input code has been modified from [termion](https://github.com/redox-os/termion)
/// Attempts to fetch input from stdin
///
/// # Errors
/// If the timeout has expired or
/// there was an error getting the data
pub fn poll_input(timeout: Duration) -> io::Result<Event> {
let mut fds = [PollFD {
fd: STDIN_FILENO,
events: POLLIN,
revents: 0,
}];
let result = unsafe {
#[allow(clippy::cast_possible_truncation)]
poll(
fds.as_mut_ptr(),
fds.len() as c_ulong,
timeout.as_millis() as c_int,
)
};
let mut read_iter = ReadIterator::new(STDIN_FILENO);
let timed_out: io::Error = io::ErrorKind::TimedOut.into();
match result {
1.. => {
let item = read_iter.next().ok_or(timed_out)??;
try_parse_event(item, &mut read_iter)
}
0 => Err(timed_out),
_ => Err(io::Error::last_os_error()),
}
}
unsafe extern "C" {
fn poll(fds: *mut PollFD, nfds: c_ulong, timeout: c_int) -> c_int;
fn read(fd: c_int, buf: *mut c_void, count: c_ulong) -> c_short;
@@ -171,87 +206,25 @@ impl Iterator for ReadIterator {
}
}
/// Attempts to fetch input from stdin
///
/// # Errors
/// If the timeout has expired or
/// there was an error getting the data
pub fn poll_input(timeout: Duration) -> io::Result<Event> {
let mut fds = [PollFD {
fd: STDIN_FILENO,
events: POLLIN,
revents: 0,
}];
let result = unsafe {
#[allow(clippy::cast_possible_truncation)]
poll(
fds.as_mut_ptr(),
fds.len() as c_ulong,
timeout.as_millis() as c_int,
)
};
let mut read_iter = ReadIterator::new(STDIN_FILENO);
let timed_out: io::Error = io::ErrorKind::TimedOut.into();
match result {
1.. => {
let item = read_iter.next().ok_or(timed_out)??;
try_parse_event(item, &mut read_iter)
}
0 => Err(timed_out),
_ => Err(io::Error::last_os_error()),
}
}
fn try_parse_event<I>(item: u8, iter: &mut I) -> io::Result<Event>
where
I: Iterator<Item = io::Result<u8>>,
{
match item {
b'\x1b' => try_parse_ansi_sequence(iter),
b'\r' => Ok(Event::Key(
Key::Char('\n'),
KeyType::Press,
KeyModifiers::none(),
)),
b'\n' => Ok(Event::Key(
Key::Char('j'),
KeyType::Press,
KeyModifiers::none().ctrl(),
)),
b'\t' => Ok(Event::Key(
Key::Char('\t'),
KeyType::Press,
KeyModifiers::none(),
)),
b'\x7f' => Ok(Event::Key(
Key::Backspace,
KeyType::Press,
KeyModifiers::none(),
)),
b'\0' => Ok(Event::Key(Key::Null, KeyType::Press, KeyModifiers::none())),
c @ b'\x01'..=b'\x1a' => Ok(Event::Key(
Key::Char((c + 96) as char),
KeyType::Press,
KeyModifiers::none().ctrl(),
)),
c @ b'\x1c'..=b'\x1f' => Ok(Event::Key(
Key::Char((c + 24) as char),
KeyType::Press,
KeyModifiers::none().ctrl(),
)),
b'\r' => Ok(press_key(Key::Char('\r'), KeyMods::NONE)),
b'\n' => Ok(press_key(Key::Char('j'), KeyMods::CTRL)),
b'\t' => Ok(press_key(Key::Char('\t'), KeyMods::NONE)),
b'\x7f' => Ok(press_key(Key::Backspace, KeyMods::NONE)),
b'\0' => Ok(press_key(Key::Char(' '), KeyMods::CTRL)),
c @ b'\x01'..=b'\x1a' => Ok(press_key(Key::Char((c + 96) as char), KeyMods::CTRL)),
c @ b'\x1c'..=b'\x1f' => Ok(press_key(Key::Char((c + 24) as char), KeyMods::CTRL)),
c => {
let character = parse_utf8_char(c, iter)?;
Ok(Event::Key(
Key::Char(parse_utf8_char(c, iter)?),
Key::Char(character),
KeyType::Press,
KeyModifiers {
shift: character.is_uppercase(),
alt: false,
ctrl: false,
meta: false,
},
KeyMods::NONE.shift(character.is_uppercase()),
))
}
}
@@ -280,11 +253,7 @@ where
let error = io::Error::other("Could not parse event");
match iter.next() {
Some(Ok(b'O')) => match iter.next() {
Some(Ok(val @ b'P'..=b's')) => Ok(Event::Key(
Key::F(1 + val - b'P'),
KeyType::Press,
KeyModifiers::none(),
)),
Some(Ok(val @ b'P'..=b's')) => Ok(press_key(Key::F(1 + val - b'P'), KeyMods::NONE)),
_ => Err(error),
},
Some(Ok(b'[')) => try_parse_csi_sequence(iter).ok_or(error),
@@ -298,24 +267,16 @@ where
{
match iter.next() {
Some(Ok(b'[')) => match iter.next() {
Some(Ok(val @ b'A'..=b'E')) => Some(Event::Key(
Key::F(1 + val - b'A'),
KeyType::Press,
KeyModifiers::none(),
)),
Some(Ok(val @ b'A'..=b'E')) => Some(press_key(Key::F(1 + val - b'A'), KeyMods::NONE)),
_ => None,
},
Some(Ok(b'D')) => Some(Event::Key(Key::Left, KeyType::Press, KeyModifiers::none())),
Some(Ok(b'C')) => Some(Event::Key(Key::Right, KeyType::Press, KeyModifiers::none())),
Some(Ok(b'A')) => Some(Event::Key(Key::Up, KeyType::Press, KeyModifiers::none())),
Some(Ok(b'B')) => Some(Event::Key(Key::Down, KeyType::Press, KeyModifiers::none())),
Some(Ok(b'H')) => Some(Event::Key(Key::Home, KeyType::Press, KeyModifiers::none())),
Some(Ok(b'F')) => Some(Event::Key(Key::End, KeyType::Press, KeyModifiers::none())),
Some(Ok(b'Z')) => Some(Event::Key(
Key::Tab,
KeyType::Press,
KeyModifiers::none().shift(),
)),
Some(Ok(b'D')) => Some(press_key(Key::Left, KeyMods::NONE)),
Some(Ok(b'C')) => Some(press_key(Key::Right, KeyMods::NONE)),
Some(Ok(b'A')) => Some(press_key(Key::Up, KeyMods::NONE)),
Some(Ok(b'B')) => Some(press_key(Key::Down, KeyMods::NONE)),
Some(Ok(b'H')) => Some(press_key(Key::Home, KeyMods::NONE)),
Some(Ok(b'F')) => Some(press_key(Key::End, KeyMods::NONE)),
Some(Ok(b'Z')) => Some(press_key(Key::Tab, KeyMods::SHIFT)),
_ => None,
}
}

View File

@@ -1,4 +1,4 @@
use crate::input::{Event, Key, KeyModifiers, KeyType};
use crate::input::{Event, Key, KeyMods, KeyType, press_key};
use std::os::windows::raw::HANDLE;
use std::{io, mem, time::Duration};
@@ -16,6 +16,7 @@ unsafe extern "system" {
const STD_INPUT_HANDLE: i32 = -10;
const STD_OUTPUT_HANDLE: i32 = -11;
const ENABLE_VIRTUAL_TERMINAL_PROCESSING: u32 = 4;
const ENABLE_VIRTUAL_TERMINAL_INPUT: u32 = 0x200;
const ENABLE_ECHO_INPUT: u32 = 4;
const ENABLE_LINE_INPUT: u32 = 2;
const ENABLE_PROCESSED_INPUT: u32 = 1;
@@ -112,6 +113,12 @@ pub fn enable_ansi() -> io::Result<()> {
get_console_mode(handle, &mut mode)?;
mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
set_console_mode(handle, mode)?;
let handle = get_stdin_handle()?;
let mut mode = 0;
get_console_mode(handle, &mut mode)?;
mode &= !ENABLE_VIRTUAL_TERMINAL_INPUT;
set_console_mode(handle, mode)?;
Ok(())
}
@@ -237,40 +244,36 @@ fn parse_key_event(event: &KeyEventRecord) -> Event {
let shift = event.control_key_state & 0x0010 != 0; // SHIFT_PRESSED
match event.virtual_key_code {
0x08 => Event::Key(Key::Backspace, KeyType::Press, KeyModifiers::none()),
0x08 => press_key(Key::Backspace, KeyMods::NONE),
0x09 => {
if shift {
Event::Key(Key::Tab, KeyType::Press, KeyModifiers::none().shift())
press_key(Key::Tab, KeyMods::SHIFT)
} else {
Event::Key(Key::Tab, KeyType::Press, KeyModifiers::none())
press_key(Key::Tab, KeyMods::NONE)
}
}
0x0D => Event::Key(Key::Char('\n'), KeyType::Press, KeyModifiers::none()),
0x1B => Event::Key(Key::Escape, KeyType::Press, KeyModifiers::none()),
0x21 => Event::Key(Key::PageUp, KeyType::Press, KeyModifiers::none()),
0x22 => Event::Key(Key::PageDown, KeyType::Press, KeyModifiers::none()),
0x23 => Event::Key(Key::End, KeyType::Press, KeyModifiers::none()),
0x24 => Event::Key(Key::Home, KeyType::Press, KeyModifiers::none()),
0x25 => Event::Key(Key::Left, KeyType::Press, KeyModifiers::none()),
0x26 => Event::Key(Key::Up, KeyType::Press, KeyModifiers::none()),
0x27 => Event::Key(Key::Right, KeyType::Press, KeyModifiers::none()),
0x28 => Event::Key(Key::Down, KeyType::Press, KeyModifiers::none()),
0x2D => Event::Key(Key::Insert, KeyType::Press, KeyModifiers::none()),
0x2E => Event::Key(Key::Delete, KeyType::Press, KeyModifiers::none()),
0x0D => press_key(Key::Char('\n'), KeyMods::NONE),
0x1B => press_key(Key::Escape, KeyMods::NONE),
0x21 => press_key(Key::PageUp, KeyMods::NONE),
0x22 => press_key(Key::PageDown, KeyMods::NONE),
0x23 => press_key(Key::End, KeyMods::NONE),
0x24 => press_key(Key::Home, KeyMods::NONE),
0x25 => press_key(Key::Left, KeyMods::NONE),
0x26 => press_key(Key::Up, KeyMods::NONE),
0x27 => press_key(Key::Right, KeyMods::NONE),
0x28 => press_key(Key::Down, KeyMods::NONE),
0x2D => press_key(Key::Insert, KeyMods::NONE),
0x2E => press_key(Key::Delete, KeyMods::NONE),
// I don't think anybody is going to try to press F256 clippy
#[allow(clippy::cast_possible_truncation)]
0x70..=0x87 => Event::Key(
Key::F((event.virtual_key_code - 0x6F) as u8),
KeyType::Press,
KeyModifiers::none(),
), // F1-F24
0x70..=0x87 => press_key(Key::F(event.virtual_key_code - 0x6F) as u8), KeyMods::NONE),
_ => {
let num = u32::from(unsafe { event.u_char.unicode_char });
let c = char::from_u32(num).unwrap_or(' ');
if ctrl && c.is_ascii_alphabetic() {
Event::Key(Key::Char(c), KeyType::Press, KeyModifiers::none().ctrl())
press_key(Key::Char(c), KeyMods::CTRL)
} else {
Event::Key(Key::Char(c), KeyType::Press, KeyModifiers::none())
press_key(Key::Char(c), KeyMods::NONE)
}
}
}