tons of various things but no major changes + rename

This commit is contained in:
2025-03-27 21:52:22 -04:00
parent 0a4e1154b3
commit 31ae40b96a
7 changed files with 227 additions and 153 deletions

View File

@@ -1,5 +1,5 @@
[package]
name = "neutrino"
name = "neutuino"
version = "0.1.0"
edition = "2024"

View File

@@ -14,9 +14,12 @@ pub fn rgb_color_code(red: u8, green: u8, blue: u8) -> String {
///
/// The title must be only in ASCII characters or **weird** things will happen
#[must_use]
pub fn set_window_title(title: [u8; 255]) -> String {
let title = String::from_utf8_lossy(&title);
format!("\x1b]0;{title}\x1b\x5c")
pub fn set_window_title<T: Into<String>>(title: T) -> Option<String> {
let title = title.into();
if title.len() > 255 {
return None;
}
Some(format!("\x1b]0;{title}\x1b\x5c"))
}
/// Moves the cursor up {num} characters
@@ -67,6 +70,16 @@ pub fn move_cursor_to_position(column: u16, line: u16) -> String {
format!("\x1b[{};{}H", line.saturating_add(1), column.saturating_add(1))
}
/// Sends input when terminal is in focus
pub const FOCUS_REPORTING_ENABLE: &str = "\x1b[?1004h";
/// Stops sending input when terminal is in focus
pub const FOCUS_REPORTING_DISABLE: &str = "\x1b[?1004l";
/// Makes all pasted text treated differently
pub const BRACKETED_PASTE_ENABLE: &str = "\x1b[?2004h";
/// Disables bracketed paste
pub const BRACKETED_PASTE_DISABLE: &str = "\x1b[?2004l";
/// Saves the current cursor position
pub const CURSOR_POSITION_SAVE: &str = "\x1b7";
/// Restores the saved cursor position

View File

@@ -2,59 +2,106 @@
//!
//! Very incomplete currently
use std::io::{self, Read};
use std::sync::mpsc;
use std::thread;
/// 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.
///
/// Taken from the Termion crate
pub struct AsyncReader {
recv: mpsc::Receiver<io::Result<u8>>,
}
// pub(crate) fn parse_event(
// buffer: &[u8],
// input_available: bool,
// ) -> io::Result<Option<InternalEvent>> {
// if buffer.is_empty() {
// return Ok(None);
// }
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 buffer[0] {
// b'\x1B' => {
// if buffer.len() == 1 {
// if input_available {
// // Possible Esc sequence
// Ok(None)
// } else {
// Ok(Some(InternalEvent::Event(Event::Key(KeyCode::Esc.into()))))
// }
// } else {
// match buffer[1] {
// b'O' => {
// if buffer.len() == 2 {
// Ok(None)
// } else {
// match buffer[2] {
// b'D' => {
// Ok(Some(InternalEvent::Event(Event::Key(KeyCode::Left.into()))))
// }
// b'C' => Ok(Some(InternalEvent::Event(Event::Key(
// KeyCode::Right.into(),
// )))),
// b'A' => {
// Ok(Some(InternalEvent::Event(Event::Key(KeyCode::Up.into()))))
// }
// b'B' => {
// Ok(Some(InternalEvent::Event(Event::Key(KeyCode::Down.into()))))
// }
// b'H' => {
// Ok(Some(InternalEvent::Event(Event::Key(KeyCode::Home.into()))))
// }
// b'F' => {
// Ok(Some(InternalEvent::Event(Event::Key(KeyCode::End.into()))))
// }
// // F1-F4
// val @ b'P'..=b'S' => Ok(Some(InternalEvent::Event(Event::Key(
// KeyCode::F(1 + val - b'P').into(),
// )))),
// _ => Err(could_not_parse_event_error()),
// }
// }
// }
// b'[' => parse_csi(buffer),
// b'\x1B' => Ok(Some(InternalEvent::Event(Event::Key(KeyCode::Esc.into())))),
// _ => parse_event(&buffer[1..], input_available).map(|event_option| {
// event_option.map(|event| {
// if let InternalEvent::Event(Event::Key(key_event)) = event {
// let mut alt_key_event = key_event;
// alt_key_event.modifiers |= KeyModifiers::ALT;
// InternalEvent::Event(Event::Key(alt_key_event))
// } else {
// event
// }
// })
// }),
// }
// }
// }
// b'\r' => Ok(Some(InternalEvent::Event(Event::Key(
// KeyCode::Enter.into(),
// )))),
// // Issue #371: \n = 0xA, which is also the keycode for Ctrl+J. The only reason we get
// // newlines as input is because the terminal converts \r into \n for us. When we
// // enter raw mode, we disable that, so \n no longer has any meaning - it's better to
// // use Ctrl+J. Waiting to handle it here means it gets picked up later
// b'\n' if !crate::terminal::sys::is_raw_mode_enabled() => Ok(Some(InternalEvent::Event(
// Event::Key(KeyCode::Enter.into()),
// ))),
// b'\t' => Ok(Some(InternalEvent::Event(Event::Key(KeyCode::Tab.into())))),
// b'\x7F' => Ok(Some(InternalEvent::Event(Event::Key(
// KeyCode::Backspace.into(),
// )))),
// c @ b'\x01'..=b'\x1A' => Ok(Some(InternalEvent::Event(Event::Key(KeyEvent::new(
// KeyCode::Char((c - 0x1 + b'a') as char),
// KeyModifiers::CONTROL,
// ))))),
// c @ b'\x1C'..=b'\x1F' => Ok(Some(InternalEvent::Event(Event::Key(KeyEvent::new(
// KeyCode::Char((c - 0x1C + b'4') as char),
// KeyModifiers::CONTROL,
// ))))),
// b'\0' => Ok(Some(InternalEvent::Event(Event::Key(KeyEvent::new(
// KeyCode::Char(' '),
// KeyModifiers::CONTROL,
// ))))),
// _ => parse_utf8_char(buffer).map(|maybe_char| {
// maybe_char
// .map(KeyCode::Char)
// .map(char_code_to_event)
// .map(Event::Key)
// .map(InternalEvent::Event)
// }),
// }
// }
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<R: Read + Send + 'static>(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 }
}
}

16
src/input_sequences.md Normal file
View File

@@ -0,0 +1,16 @@
* 0x1b = Escape
* 0x1b1b = Escape
* 0x1b41 = Up Arrow
* 0x1b42 = Down Arrow
* 0x1b43 = Right Arrow
* 0x1b44 = Left Arrow
* 0x1b46 = End
* 0x1b48 = Home
* 0x1b5b41 = Up Arrow
* 0x1b5b42 = Down Arrow
* 0x1b5b43 = Right Arrow
* 0x1b5b44 = Left Arrow
* 0x1b5b46 = End
* 0x1b5b48 = Home
* 0x20-7e = Char(n)

View File

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

View File

@@ -3,31 +3,26 @@ use std::io;
unsafe extern "C" {
fn ioctl(fd: c_int, request: c_ulong, argp: *mut u8) -> c_int;
fn tcgetattr(fd: c_int, termios_p: *mut Termios) -> c_int;
safe 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;
}
#[cfg(any(target_os = "linux", target_os = "redox"))]
const STDIN_FILENO: c_int = 0x0;
const STDOUT_FILENO: c_int = 0x1;
#[cfg(target_os = "linux")]
const TIOCGWINSZ: c_ulong = 0x5413;
#[cfg(target_os = "linux")]
const NCCS: usize = 0x20;
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
const TIOCGWINSZ: c_ulong = 0x40087468;
#[cfg(any(target_os = "linux", target_os = "redox"))]
const NCCS: usize = 32;
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
const NCCS: usize = 20;
const STDIN_FILENO: c_int = 0;
const STDOUT_FILENO: c_int = 1;
const ECHO: c_uint = 8;
const ICANON: c_uint = 2;
const ISIG: c_uint = 1;
#[cfg(target_os = "macos")]
const TIOCGWINSZ: c_ulong = 0x4008_7468;
#[cfg(target_os = "macos")]
const NCCS: usize = 0x14;
#[repr(C)]
#[derive(Debug, Clone, Copy)]
#[derive(Default, Debug, Clone, Copy)]
struct Winsize {
row: c_ushort,
col: c_ushort,
@@ -36,7 +31,7 @@ struct Winsize {
}
#[repr(C)]
#[derive(Debug, Clone)]
#[derive(Default, Debug, Clone)]
struct Termios {
iflag: c_uint,
oflag: c_uint,
@@ -45,6 +40,42 @@ struct Termios {
cc: [u8; NCCS],
}
/// This struct represents a raw terminal
///
/// This struct will automatically enable raw mode when it is created
/// and disable raw mode when it is destructed
///
/// This insures that you never exit with a terminal still in raw mode which is problematic for
/// users
pub struct RawTerminal {
orig_termios: Termios
}
impl RawTerminal {
/// This constructs a terminal, automatically making it raw
///
/// # Errors
///
/// If there is no stdin,
/// stdin is not a tty,
/// if it fails to change terminal settings
pub fn new() -> io::Result<Self> {
let mut orig_termios = Termios::default();
get_attributes(STDIN_FILENO, &mut orig_termios)?;
let mut termios = orig_termios.clone();
cfmakeraw(&raw mut termios);
set_attributes(STDIN_FILENO, &mut termios)?;
Ok(Self { orig_termios })
}
}
impl Drop for RawTerminal {
fn drop(&mut self) {
let mut termios = self.orig_termios.clone();
set_attributes(STDIN_FILENO, &mut termios).expect("Failed to disable terminal raw mode");
}
}
/// Enables ANSI support on Windows terminals
///
/// ANSI is on by default on *nix machines but still exists on them for simpler usage
@@ -72,50 +103,16 @@ pub fn enable_ansi() -> io::Result<()> {
/// if stdout isn't a TTY, or
/// if it fails to retrieve the terminal size
pub fn get_terminal_size() -> io::Result<(u16, u16)> {
let mut winsize = unsafe { std::mem::zeroed::<Winsize>() };
let mut winsize = Winsize::default();
let ioctl_result = unsafe { ioctl(STDOUT_FILENO, TIOCGWINSZ, (&raw mut winsize).cast::<u8>()) };
if ioctl_result == 0 {
Ok((winsize.col as u16, winsize.row as u16))
Ok((winsize.col, winsize.row))
} else {
Err(io::Error::last_os_error())
}
}
/// Enables raw mode
///
/// Disables input echoing, line feeding, etc.
///
/// # Errors
///
/// If there is no stdout,
/// if stdout isn't a TTY, or
/// if it fails to get or set terminal settings
pub fn enable_raw_mode() -> io::Result<()> {
let mut termios = unsafe { std::mem::zeroed::<Termios>() };
get_attributes(STDIN_FILENO, &mut termios)?;
termios.lflag &= !(ECHO | ISIG | ICANON);
set_attributes(STDIN_FILENO, &mut termios)?;
Ok(())
}
/// Disables raw mode
///
/// Enables input echoing, line feeding, etc.
///
/// # Errors
///
/// If there is no stdout,
/// if stdout isn't a TTY, or
/// if it fails to get or set terminal settings
pub fn disable_raw_mode() -> io::Result<()> {
let mut termios = unsafe { std::mem::zeroed::<Termios>() };
get_attributes(STDIN_FILENO, &mut termios)?;
termios.lflag |= ECHO | ISIG | ICANON;
set_attributes(STDIN_FILENO, &mut termios)?;
Ok(())
}
fn get_attributes(fd: c_int, termios: &mut Termios) -> io::Result<()> {
if unsafe { tcgetattr(fd, &raw mut *termios) } != 0 {
return Err(io::Error::last_os_error());

View File

@@ -26,6 +26,43 @@ struct ConsoleScreenBufferInfo {
_unused: [u16; 9],
}
/// This struct represents a raw terminal
///
/// This struct will automatically enable raw mode when it is created
/// and disable raw mode when it is destructed
///
/// This insures that you never exit with a terminal still in raw mode which is problematic for
/// users
pub struct RawTerminal;
impl RawTerminal {
/// This constructs a terminal, automatically making it raw
///
/// # Errors
///
/// If there is no stdin,
/// stdin is not a tty,
/// if it fails to change terminal settings
pub fn new() -> io::Result<Self> {
let handle = get_std_handle(STD_INPUT_HANDLE)?;
let mut mode = 0;
get_console_mode(handle, &mut mode)?;
mode &= !(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);
set_console_mode(handle, &mut mode)?;
Ok(Self)
}
}
impl Drop for RawTerminal {
fn drop(&mut self) {
let handle = get_std_handle(STD_INPUT_HANDLE).expect("Failed to disable terminal raw mode");
let mut mode = 0;
get_console_mode(handle, &mut mode).expect("Failed to disable terminal raw mode");
mode |= ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT;
set_console_mode(handle, &mut mode).expect("Failed to disable terminal raw mode");
}
}
/// Enables ANSI support on Windows terminals
///
/// ANSI is on by default on *nix machines but still exists on them for simpler usage
@@ -40,9 +77,9 @@ struct ConsoleScreenBufferInfo {
pub fn enable_ansi() -> io::Result<()> {
let handle = get_std_handle(STD_OUTPUT_HANDLE)?;
let mut mode = 0;
get_console_mode(handle, &raw mut mode)?;
get_console_mode(handle, &mut mode)?;
mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
set_console_mode(handle, &raw mut mode)?;
set_console_mode(handle, &mut mode)?;
Ok(())
}
@@ -66,42 +103,6 @@ pub fn get_terminal_size() -> io::Result<(u16, u16)> {
Err(io::Error::last_os_error())
}
/// Enables raw mode
///
/// Disables input echoing, line feeding, etc.
///
/// # Errors
///
/// If there is no stdout,
/// if stdout isn't a TTY, or
/// if it fails to get or set terminal settings
pub fn enable_raw_mode() -> io::Result<()> {
let handle = get_std_handle(STD_INPUT_HANDLE)?;
let mut mode = 0;
get_console_mode(handle, &raw mut mode)?;
mode &= !(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);
set_console_mode(handle, &raw mut mode)?;
Ok(())
}
/// Disables raw mode
///
/// Enables input echoing, line feeding, etc.
///
/// # Errors
///
/// If there is no stdout,
/// if stdout isn't a TTY, or
/// if it fails to get or set terminal settings
pub fn disable_raw_mode() -> io::Result<()> {
let handle = get_std_handle(STD_INPUT_HANDLE)?;
let mut mode = 0;
get_console_mode(handle, &raw mut mode)?;
mode |= ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT;
set_console_mode(handle, &raw mut mode)?;
Ok(())
}
fn get_std_handle(handle: u32) -> io::Result<usize> {
let handle = unsafe { GetStdHandle(handle) };
if handle == INVALID_HANDLE_VALUE {
@@ -111,7 +112,7 @@ fn get_std_handle(handle: u32) -> io::Result<usize> {
}
}
fn set_console_mode(handle: usize, mode: *mut u32) -> io::Result<()> {
fn set_console_mode(handle: usize, mode: &mut u32) -> io::Result<()> {
if unsafe { SetConsoleMode(handle, mode) == 0 } {
Err(io::Error::last_os_error())
} else {
@@ -119,7 +120,7 @@ fn set_console_mode(handle: usize, mode: *mut u32) -> io::Result<()> {
}
}
fn get_console_mode(handle: usize, mode: *mut u32) -> io::Result<()> {
fn get_console_mode(handle: usize, mode: &mut u32) -> io::Result<()> {
if unsafe { GetConsoleMode(handle, mode) == 0 } {
Err(io::Error::last_os_error())
} else {