libpse/src/terminal.rs
2025-01-20 00:01:03 -05:00

125 lines
3.9 KiB
Rust

use crossterm::{cursor, event::{self, Event, KeyCode, KeyEvent, KeyModifiers}, execute, terminal};
use std::io::{self, Write};
use thiserror::Error;
use crate::{commands::Command, session::{self, Pse}};
#[derive(Debug, Error)]
pub enum InputHandleError {
#[error("UserExit")]
UserExit,
#[error("Sigterm")]
Sigterm,
#[error("Render failure: {0}")]
Write(io::Error),
#[error("Flush failure: {0}")]
Flush(io::Error),
#[error("Disabling the terminal's raw mode failed: {0}")]
EnableRaw(io::Error),
#[error("Enabling the terminal's raw mode failed: {0}")]
DisableRaw(io::Error),
#[error("key input failure: {0}")]
Key(KeyCode),
}
type InputResult<T> = Result<T, InputHandleError>;
trait SpecificKeybinds {
const TERM_ID_1: &str;
fn key_ctrl(&mut self, input_key: KeyEvent, keycode: KeyCode) -> InputResult<()>;
fn key_enter(&mut self) -> InputResult<()>;
fn key_backspace(&mut self) -> InputResult<()>;
fn key_arrow_right(&mut self) -> InputResult<()>;
fn key_arrow_left(&mut self) -> InputResult<()>;
}
impl SpecificKeybinds for Pse {
const TERM_ID_1: &str = "exit";
fn key_ctrl(&mut self, input_key: KeyEvent, keycode: KeyCode) -> InputResult<()> {
if input_key.modifiers.contains(KeyModifiers::CONTROL) {
match keycode {
KeyCode::Char('c') => Err(InputHandleError::Sigterm),
_ => Ok(())
}
} else {
self.term_render(keycode.to_string())
}
}
fn key_enter(&mut self) -> InputResult<()> {
if self.rt.input == Self::TERM_ID_1 { return Err(InputHandleError::UserExit) };
terminal::disable_raw_mode().map_err(InputHandleError::DisableRaw)?;
Command::new(&self.rt.input).exec(&mut self.history);
self.rt.input.clear();
Ok(())
}
fn key_backspace(&mut self) -> InputResult<()> {
if self.rt.input.pop().is_some() {
execute!(
io::stdout(),
cursor::MoveLeft(1),
terminal::Clear(terminal::ClearType::UntilNewLine)
).map_err(InputHandleError::Flush)
} else {
//the string is empty, do terminal beep
Ok(())
}
}
fn key_arrow_right(&mut self) -> InputResult<()> {
execute!(io::stdout(), cursor::MoveRight(1)).map_err(InputHandleError::Flush)
}
fn key_arrow_left(&mut self) -> InputResult<()> {
execute!(io::stdout(), cursor::MoveLeft(1)).map_err(InputHandleError::Flush)
}
}
pub trait TermProcessor {
fn term_render(&mut self, def_string: String) -> InputResult<()>;
fn term_input_handler(&mut self, input_key: KeyEvent) -> Option<()>;
fn term_input_mainthread(&mut self) -> io::Result<()>;
fn term_input_processor(&mut self) -> io::Result<()>;
}
impl TermProcessor for Pse {
fn term_render(&mut self, def_string: String) -> InputResult<()> {
self.rt.input.push_str(&def_string);
write!(io::stdout(), "{def_string}").map_err(InputHandleError::Write)?;
io::stdout().flush().map_err(InputHandleError::Flush)
}
fn term_input_handler(&mut self, input_key: KeyEvent) -> Option<()> {
let input_handle = match input_key.code {
KeyCode::Enter => self.key_enter(),
KeyCode::Backspace => self.key_backspace(),
KeyCode::Tab => todo!(),
KeyCode::Right => self.key_arrow_right(),
KeyCode::Left => self.key_arrow_left(),
KeyCode::Up => todo!(),
KeyCode::Down => todo!(),
keycode => self.key_ctrl(input_key, keycode)
};
input_handle.map_or_else(|inp_err| match inp_err {
InputHandleError::UserExit => None,
InputHandleError::Sigterm => self.term_render("^C".to_owned()).ok(),
input_err => session::shell_error_none(input_err)
}, Some)
}
fn term_input_mainthread(&mut self) -> io::Result<()> {
execute!(io::stdout(), event::EnableBracketedPaste)?;
loop {
terminal::enable_raw_mode()?;
if let Event::Key(event) = event::read()? {
if self.term_input_handler(event).is_none() { break Ok(()) }
}
}
}
fn term_input_processor(&mut self) -> io::Result<()> {
self.term_input_mainthread()?;
terminal::disable_raw_mode()?;
execute!(io::stdout(), event::DisableBracketedPaste)
}
}