#[cfg(feature = "ratatui-crossterm")]
use ratatui::crossterm;
use crate::{Input, InputRequest, StateChanged};
use crossterm::event::{
Event as CrosstermEvent, KeyCode, KeyEvent, KeyEventKind, KeyModifiers,
};
use crossterm::{
cursor::MoveTo,
queue,
style::{Attribute as CAttribute, Print, SetAttribute},
};
use std::io::{Result, Write};
pub fn to_input_request(evt: &CrosstermEvent) -> Option<InputRequest> {
use InputRequest::*;
use KeyCode::*;
match evt {
CrosstermEvent::Key(KeyEvent {
code,
modifiers,
kind,
state: _,
}) if *kind == KeyEventKind::Press || *kind == KeyEventKind::Repeat => {
match (*code, *modifiers) {
(Backspace, KeyModifiers::NONE) | (Char('h'), KeyModifiers::CONTROL) => {
Some(DeletePrevChar)
}
(Delete, KeyModifiers::NONE) => Some(DeleteNextChar),
(Tab, KeyModifiers::NONE) => None,
(Left, KeyModifiers::NONE) | (Char('b'), KeyModifiers::CONTROL) => {
Some(GoToPrevChar)
}
(Left, KeyModifiers::CONTROL) | (Char('b'), KeyModifiers::META) => {
Some(GoToPrevWord)
}
(Right, KeyModifiers::NONE) | (Char('f'), KeyModifiers::CONTROL) => {
Some(GoToNextChar)
}
(Right, KeyModifiers::CONTROL) | (Char('f'), KeyModifiers::META) => {
Some(GoToNextWord)
}
(Char('u'), KeyModifiers::CONTROL) => Some(DeleteLine),
(Char('w'), KeyModifiers::CONTROL)
| (Char('d'), KeyModifiers::META)
| (Backspace, KeyModifiers::META)
| (Backspace, KeyModifiers::ALT) => Some(DeletePrevWord),
(Delete, KeyModifiers::CONTROL) => Some(DeleteNextWord),
(Char('k'), KeyModifiers::CONTROL) => Some(DeleteTillEnd),
(Char('y'), KeyModifiers::CONTROL) => Some(Yank),
(Char('a'), KeyModifiers::CONTROL) | (Home, KeyModifiers::NONE) => {
Some(GoToStart)
}
(Char('e'), KeyModifiers::CONTROL) | (End, KeyModifiers::NONE) => {
Some(GoToEnd)
}
(Char(c), KeyModifiers::NONE) => Some(InsertChar(c)),
(Char(c), KeyModifiers::SHIFT) => Some(InsertChar(c)),
(Char(c), modifiers)
if modifiers == KeyModifiers::CONTROL | KeyModifiers::ALT =>
{
Some(InsertChar(c))
}
(_, _) => None,
}
}
_ => None,
}
}
pub fn write<W: Write>(
stdout: &mut W,
value: &str,
cursor: usize,
(x, y): (u16, u16),
width: u16,
) -> Result<()> {
queue!(stdout, MoveTo(x, y), SetAttribute(CAttribute::NoReverse))?;
let val_width = width.max(1) as usize - 1;
let len = value.chars().count();
let start = (len.max(val_width) - val_width).min(cursor);
let mut chars = value.chars().skip(start);
let mut i = start;
while i < cursor {
i += 1;
let c = chars.next().unwrap_or(' ');
queue!(stdout, Print(c))?;
}
i += 1;
let c = chars.next().unwrap_or(' ');
queue!(
stdout,
SetAttribute(CAttribute::Reverse),
Print(c),
SetAttribute(CAttribute::NoReverse)
)?;
while i <= start + val_width {
i += 1;
let c = chars.next().unwrap_or(' ');
queue!(stdout, Print(c))?;
}
Ok(())
}
pub trait EventHandler {
fn handle_event(&mut self, evt: &CrosstermEvent) -> Option<StateChanged>;
}
impl EventHandler for Input {
fn handle_event(&mut self, evt: &CrosstermEvent) -> Option<StateChanged> {
to_input_request(evt).and_then(|req| self.handle(req))
}
}
#[cfg(test)]
mod tests;