use crate::{Input, InputRequest, StateChanged};
use ratatui::crossterm::event::{
Event as CrosstermEvent, KeyCode, KeyEvent, KeyEventKind, KeyModifiers,
};
use ratatui::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 => 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) => Some(DeletePrevWord),
(Delete, KeyModifiers::CONTROL) => Some(DeleteNextWord),
(Char('k'), KeyModifiers::CONTROL) => Some(DeleteTillEnd),
(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)),
(_, _) => 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 {
use ratatui::crossterm::event::{KeyEventKind, KeyEventState};
use super::*;
#[test]
fn handle_tab() {
let evt = CrosstermEvent::Key(KeyEvent {
code: KeyCode::Tab,
modifiers: KeyModifiers::NONE,
kind: KeyEventKind::Press,
state: KeyEventState::NONE,
});
let req = to_input_request(&evt);
assert!(req.is_none());
}
}