mirror of
https://github.com/Xyverle/neutuino.git
synced 2026-06-26 22:23:14 -04:00
Fix release-mode segfault on *nix + add *nix alt support
This commit is contained in:
44
README.md
44
README.md
@@ -1,5 +1,5 @@
|
||||
# Neutuino
|
||||
Zero dependency cross-platform pure-rust simple TUI library
|
||||
Zero dependency pure-rust simple TUI library
|
||||
|
||||
Supported OSes: Windows 10+, MacOS (untested), and Linux
|
||||
|
||||
@@ -9,10 +9,48 @@ This project is still highly work in progress and it will be a decent while unti
|
||||
- [x] Output (Unix)
|
||||
- [x] Output (Windows)
|
||||
- [x] Input (Unix) (Appears to work, more testing needed)
|
||||
- [x] Input (Windows) (Appears to work, more testing needed)
|
||||
- [ ] Input (Kitty)
|
||||
- [ ] Input (Windows) (WIP)
|
||||
- [ ] Advanced Input (Kitty-like)
|
||||
- [ ] Advanced Input (Windows)
|
||||
- [ ] Events (Focus reporting, Bracketed-paste) (Unix)
|
||||
- [ ] Events (Focus reporting, Bracketed-paste) (Windows)
|
||||
- [ ] Mouse input (Unix)
|
||||
- [ ] Mouse input (Windows)
|
||||
- [ ] Feature completeness / API cleanup
|
||||
|
||||
## Stability
|
||||
This library is unstable, for now every release should be considered breaking
|
||||
|
||||
## Support
|
||||
This library generally attempts to have as much functionality as it can but sadly many terminal
|
||||
emulators are heavily limited, there are a few protocols I have decided not to support
|
||||
(i.e. [Kitty graphics protocol](https://sw.kovidgoyal.net/kitty/graphics-protocol/))
|
||||
|
||||
In general, this library will work best with terminals that support
|
||||
[Kitty comprehensive keyboard handling](https://sw.kovidgoyal.net/kitty/keyboard-protocol/)
|
||||
|
||||
### Protocol Support
|
||||
This is a list of terminal protocols and whether they will be supported, they still might not
|
||||
work as this library is work in progress but eventually will be
|
||||
|
||||
Just because a protocol is listed as not planned doesn't mean it definitely won't be added, but
|
||||
it is most likely not without good reason
|
||||
- Standard Windows terminals (Full support planned)\*
|
||||
- WinPTY (Windows psuedo-terminals) (Full support planned)
|
||||
- Standard \*nix terminals (Full support planned)\*
|
||||
- OSC 52 system clipboard (Full support planned)
|
||||
- Kitty comprehensive keyboard handling (Full support planned)
|
||||
- Kitty colored and styled underlines (Full support planned)
|
||||
- Other Kitty protocols (there are a lot of them) (Not planned)
|
||||
|
||||
\* Do not have full support for advanced input
|
||||
|
||||
## Structure
|
||||
This library has multiple APIs
|
||||
|
||||
Inside the `cli` module there are utilities for making something similar to Cargo or Git
|
||||
|
||||
And inside the `tui` module there are utilities for making something similar to Neovim or Emacs
|
||||
|
||||
And inside the rest of the library there are lower-level APIs in case you want to abstract over
|
||||
this library yourself or if you need more precise control over the terminal
|
||||
|
||||
@@ -14,7 +14,7 @@ fn main() -> io::Result<()> {
|
||||
let all_styles = format!("{STYLE_BOLD}{STYLE_ITALIC}{STYLE_UNDERLINE}");
|
||||
|
||||
enable_ansi()?;
|
||||
let _raw_terminal = RawModeHandler::new()?;
|
||||
enable_raw_mode()?;
|
||||
|
||||
println!("q to quit{}", move_cursor_to_column(0));
|
||||
let next = |x: usize| (x + 1) % COLORS_FG.len();
|
||||
@@ -43,5 +43,6 @@ fn main() -> io::Result<()> {
|
||||
}
|
||||
counter = next(counter);
|
||||
}
|
||||
disable_raw_mode()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
17
src/ansi.rs
17
src/ansi.rs
@@ -4,29 +4,12 @@
|
||||
//!
|
||||
//! For these to always work on Windows you need to run the `enable_ansi` function inside this module
|
||||
|
||||
use std::io::{self, Write};
|
||||
|
||||
#[cfg(unix)]
|
||||
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 {
|
||||
|
||||
@@ -2,24 +2,8 @@
|
||||
//!
|
||||
//! These are built to work on Windows, Linux, and MacOS
|
||||
|
||||
use std::io;
|
||||
|
||||
#[cfg(unix)]
|
||||
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};
|
||||
|
||||
fn raw_mode(bool: bool) -> std::io::Result<()> {
|
||||
if bool {
|
||||
enable_raw_mode()?;
|
||||
} else {
|
||||
disable_raw_mode()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Creates a handler for raw mode
|
||||
pub fn raw_mode_handler() -> io::Result<crate::Handler> {
|
||||
crate::Handler::new(&raw_mode)
|
||||
}
|
||||
|
||||
103
src/lib.rs
103
src/lib.rs
@@ -1,47 +1,7 @@
|
||||
#![warn(clippy::all, clippy::pedantic)]
|
||||
// this lint has way too many false positives
|
||||
#![allow(clippy::doc_markdown)]
|
||||
//! This crate is a simple and minimal TUI library that supports the following OSes:
|
||||
//! - Windows 10+
|
||||
//! - MacOS (currently untested)
|
||||
//! - Linux
|
||||
//!
|
||||
//! ## Roadmap
|
||||
//! - [x] Output (Unix)
|
||||
//! - [x] Output (Windows)
|
||||
//! - [x] Input (Unix) (Appears to work, more testing needed)
|
||||
//! - [ ] Input (Windows) (WIP)
|
||||
//! - [ ] Advanced Input (Kitty-like)
|
||||
//! - [ ] Advanced Input (Windows)
|
||||
//! - [ ] Events (Focus reporting, Bracketed-paste) (Unix)
|
||||
//! - [ ] Events (Focus reporting, Bracketed-paste) (Windows)
|
||||
//! - [ ] Mouse input (Unix)
|
||||
//! - [ ] Mouse input (Windows)
|
||||
//! - [ ] Feature completeness / API cleanup
|
||||
//!
|
||||
//! ## Support
|
||||
//! This library generally attempts to have as much functionality as it can but sadly many terminal
|
||||
//! emulators are heavily limited, there are a few protocols I have decided not to support but for
|
||||
//! the most part this holds true
|
||||
//!
|
||||
//! ### Protocol Support
|
||||
//! This is a list of terminal protocols and whether they will be supported, they still might not
|
||||
//! work as this library is work in progress but eventually will be
|
||||
//!
|
||||
//! Just because a protocol is listed as not planned doesn't mean it definetly won't be added but
|
||||
//! it is most likely not without good reason
|
||||
//! - Standard Windows terminals (Full support planned)\*
|
||||
//! - WinPTY (Windows psuedo-terminals) (Full support planned)
|
||||
//! - Standard \*nix terminals (Full support planned)\*
|
||||
//! - OSC 52 system clipboard (Full support planned)
|
||||
//! - Kitty comprehensive keyboard handling (Full support planned)
|
||||
//! - Kitty colored and styled underlines (Full support planned)
|
||||
//! - Other Kitty protocols (there are a lot of them) (Not planned)
|
||||
//!
|
||||
//! \* Do not have full support for advanced input
|
||||
|
||||
use std::io;
|
||||
|
||||
#![doc = include_str!("../README.md")]
|
||||
#[cfg(unix)]
|
||||
mod unix;
|
||||
|
||||
@@ -58,64 +18,3 @@ 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");
|
||||
}
|
||||
}
|
||||
|
||||
56
src/unix.rs
56
src/unix.rs
@@ -10,7 +10,7 @@ unsafe extern "C" {
|
||||
fn ioctl(fd: c_int, request: c_ulong, argp: *mut u8) -> c_int;
|
||||
fn cfmakeraw(termios: *mut Termios);
|
||||
fn tcgetattr(fd: c_int, termios: *mut Termios) -> c_int;
|
||||
fn tcsetattr(fd: c_int, optional_actions: c_int, termios: *mut Termios) -> c_int;
|
||||
fn tcsetattr(fd: c_int, optional_actions: c_int, termios: *const Termios) -> c_int;
|
||||
}
|
||||
|
||||
const STDIN_FILENO: c_int = 0;
|
||||
@@ -27,6 +27,8 @@ const TIOCGWINSZ: c_ulong = 0x4008_7468;
|
||||
#[cfg(target_os = "macos")]
|
||||
const NCCS: usize = 0x14;
|
||||
|
||||
const ERROR_MAGIC: i32 = 68905;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Default, Debug, Clone, Copy)]
|
||||
struct Winsize {
|
||||
@@ -44,6 +46,10 @@ struct Termios {
|
||||
cflag: c_uint,
|
||||
lflag: c_uint,
|
||||
cc: [u8; NCCS],
|
||||
#[cfg(target_os = "linux")]
|
||||
ispeed: c_ulong,
|
||||
#[cfg(target_os = "linux")]
|
||||
ospeed: c_ulong,
|
||||
}
|
||||
|
||||
fn get_attributes(fd: c_int, termios: &mut Termios) -> io::Result<()> {
|
||||
@@ -60,10 +66,13 @@ fn set_attributes(fd: c_int, termios: &mut Termios) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
static TERMIOS: LazyLock<Option<Termios>> = LazyLock::new(|| {
|
||||
let mut orig_termios = Termios::default();
|
||||
get_attributes(STDIN_FILENO, &mut orig_termios).ok()?;
|
||||
Some(orig_termios)
|
||||
static TERMIOS: LazyLock<Result<Termios, i32>> = LazyLock::new(|| {
|
||||
let mut orig_termios = unsafe { std::mem::zeroed() };
|
||||
let attributes = get_attributes(STDIN_FILENO, &mut orig_termios);
|
||||
match attributes {
|
||||
Ok(()) => Ok(orig_termios),
|
||||
Err(e) => Err(e.raw_os_error().unwrap_or(ERROR_MAGIC)),
|
||||
}
|
||||
});
|
||||
|
||||
/// Enables raw mode, which disables line buffering, input echoing, and output canonicalization
|
||||
@@ -74,7 +83,12 @@ static TERMIOS: LazyLock<Option<Termios>> = LazyLock::new(|| {
|
||||
/// stdin is not a tty,
|
||||
/// or it fails to change terminal settings
|
||||
pub fn enable_raw_mode() -> io::Result<()> {
|
||||
let mut termios = (*TERMIOS).ok_or(io::Error::other("Failed to get terminal properties"))?;
|
||||
let mut termios = (*TERMIOS).map_err(|e| {
|
||||
if e == ERROR_MAGIC {
|
||||
return io::Error::other("Failed to get terminal properties");
|
||||
}
|
||||
io::Error::from_raw_os_error(e)
|
||||
})?;
|
||||
unsafe {
|
||||
cfmakeraw(&mut termios);
|
||||
}
|
||||
@@ -90,7 +104,12 @@ pub fn enable_raw_mode() -> io::Result<()> {
|
||||
/// stdin is not a tty,
|
||||
/// or it fails to change terminal settings
|
||||
pub fn disable_raw_mode() -> io::Result<()> {
|
||||
let mut termios = (*TERMIOS).ok_or(io::Error::other("Failed to get terminal properties"))?;
|
||||
let mut termios = (*TERMIOS).map_err(|e| {
|
||||
if e == ERROR_MAGIC {
|
||||
return io::Error::other("Failed to get terminal properties");
|
||||
}
|
||||
io::Error::from_raw_os_error(e)
|
||||
})?;
|
||||
set_attributes(STDIN_FILENO, &mut termios)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -257,6 +276,29 @@ where
|
||||
_ => Err(error),
|
||||
},
|
||||
Some(Ok(b'[')) => try_parse_csi_sequence(iter).ok_or(error),
|
||||
Some(Ok(c)) => match c {
|
||||
b'\r' => Ok(press_key(Key::Char('\r'), KeyMods::ALT)),
|
||||
b'\n' => Ok(press_key(Key::Char('j'), KeyMods::CTRL.alt(true))),
|
||||
b'\t' => Ok(press_key(Key::Char('\t'), KeyMods::ALT)),
|
||||
b'\x7f' => Ok(press_key(Key::Backspace, KeyMods::ALT)),
|
||||
b'\0' => Ok(press_key(Key::Char(' '), KeyMods::CTRL.alt(true))),
|
||||
c @ b'\x01'..=b'\x1a' => Ok(press_key(
|
||||
Key::Char((c + 96) as char),
|
||||
KeyMods::CTRL.alt(true),
|
||||
)),
|
||||
c @ b'\x1c'..=b'\x1f' => Ok(press_key(
|
||||
Key::Char((c + 24) as char),
|
||||
KeyMods::CTRL.alt(true),
|
||||
)),
|
||||
c => {
|
||||
let character = parse_utf8_char(c, iter)?;
|
||||
Ok(Event::Key(
|
||||
Key::Char(character),
|
||||
KeyType::Press,
|
||||
KeyMods::NONE.shift(character.is_uppercase()).alt(true),
|
||||
))
|
||||
}
|
||||
},
|
||||
_ => Err(error),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user