use crate::{Event, KeyKind, KeyModifiers, KeyWithModifiers, Result};
use crokey::KeyCombination;
use crossterm::event::{self, Event as CrosstermEvent, KeyCode, KeyEvent};
use std::collections::HashMap;
use std::time::Duration;
pub struct KeyboardHandler {
poll_rate: Duration,
keybinds: HashMap<KeyCombination, KeybindAction>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum KeybindAction {
Quit,
Save,
Copy,
Paste,
Cut,
Undo,
Redo,
SelectAll,
Find,
Replace,
New,
Open,
Custom(String),
}
impl KeyboardHandler {
pub fn new() -> Self {
Self {
poll_rate: Duration::from_millis(1),
keybinds: HashMap::new(),
}
}
pub fn with_common_keybinds() -> Self {
let mut handler = Self::new();
let _ = handler.add_keybind("ctrl-q", KeybindAction::Quit);
let _ = handler.add_keybind("ctrl-s", KeybindAction::Save);
let _ = handler.add_keybind("ctrl-x", KeybindAction::Cut);
let _ = handler.add_keybind("ctrl-v", KeybindAction::Paste);
let _ = handler.add_keybind("ctrl-z", KeybindAction::Undo);
let _ = handler.add_keybind("ctrl-y", KeybindAction::Redo);
let _ = handler.add_keybind("ctrl-a", KeybindAction::SelectAll);
let _ = handler.add_keybind("ctrl-f", KeybindAction::Find);
let _ = handler.add_keybind("ctrl-h", KeybindAction::Replace);
let _ = handler.add_keybind("ctrl-n", KeybindAction::New);
let _ = handler.add_keybind("ctrl-o", KeybindAction::Open);
handler
}
pub fn add_keybind(&mut self, key_combination: &str, action: KeybindAction) -> Result<()> {
let combination = key_combination.parse::<KeyCombination>().map_err(|e| {
crate::Error::InputError(format!(
"Invalid key combination '{}': {}",
key_combination, e
))
})?;
self.keybinds.insert(combination, action);
Ok(())
}
pub fn remove_keybind(&mut self, key_combination: &str) -> Result<bool> {
let combination = key_combination.parse::<KeyCombination>().map_err(|e| {
crate::Error::InputError(format!(
"Invalid key combination '{}': {}",
key_combination, e
))
})?;
Ok(self.keybinds.remove(&combination).is_some())
}
pub fn clear_keybinds(&mut self) {
self.keybinds.clear();
}
pub fn keybinds(&self) -> &HashMap<KeyCombination, KeybindAction> {
&self.keybinds
}
pub fn set_poll_rate(&mut self, milliseconds: u64) {
self.poll_rate = Duration::from_millis(milliseconds);
}
pub fn poll_rate(&self) -> Duration {
self.poll_rate
}
pub fn poll_with_keybinds(&self) -> Result<Option<Event>> {
if event::poll(self.poll_rate)? {
if let CrosstermEvent::Key(key) = event::read()? {
if let Some(action) = self.check_keybind_match(&key) {
return Ok(Some(Event::Keybind(action)));
}
return Ok(Some(self.convert_key_event(key.code)));
}
}
Ok(None)
}
pub fn poll(&self) -> Result<Option<Event>> {
if event::poll(self.poll_rate)? {
if let CrosstermEvent::Key(key) = event::read()? {
return Ok(Some(self.convert_key_event(key.code)));
}
}
Ok(None)
}
pub fn get_input(&self, timeout: Duration) -> Result<Event> {
if event::poll(timeout)? {
if let CrosstermEvent::Key(key) = event::read()? {
if let Some(action) = self.check_keybind_match(&key) {
return Ok(Event::Keybind(action));
}
return Ok(self.convert_key_event(key.code));
}
}
Ok(Event::Unknown)
}
pub fn wait_for_input(&self) -> Result<Event> {
loop {
if let CrosstermEvent::Key(key) = event::read()? {
if let Some(action) = self.check_keybind_match(&key) {
return Ok(Event::Keybind(action));
}
if let Some(e) = self.convert_key_event_with_modifiers(&key) {
return Ok(e);
}
return Ok(self.convert_key_event(key.code));
}
}
}
fn check_keybind_match(&self, key: &KeyEvent) -> Option<KeybindAction> {
let combination = self.crossterm_to_crokey(key)?;
self.keybinds.get(&combination).cloned()
}
fn crossterm_to_crokey(&self, key: &KeyEvent) -> Option<KeyCombination> {
use crossterm::event::KeyCode;
let key_str = match key.code {
KeyCode::Char(c) => {
let mut result = String::new();
if key
.modifiers
.contains(crossterm::event::KeyModifiers::CONTROL)
{
result.push_str("ctrl-");
}
if key.modifiers.contains(crossterm::event::KeyModifiers::ALT) {
result.push_str("alt-");
}
if key
.modifiers
.contains(crossterm::event::KeyModifiers::SHIFT)
{
result.push_str("shift-");
}
result.push(c);
result
}
KeyCode::Up => {
let mut result = String::new();
if key
.modifiers
.contains(crossterm::event::KeyModifiers::CONTROL)
{
result.push_str("ctrl-");
}
if key.modifiers.contains(crossterm::event::KeyModifiers::ALT) {
result.push_str("alt-");
}
if key
.modifiers
.contains(crossterm::event::KeyModifiers::SHIFT)
{
result.push_str("shift-");
}
result.push_str("up");
result
}
KeyCode::Down => {
let mut result = String::new();
if key
.modifiers
.contains(crossterm::event::KeyModifiers::CONTROL)
{
result.push_str("ctrl-");
}
if key.modifiers.contains(crossterm::event::KeyModifiers::ALT) {
result.push_str("alt-");
}
if key
.modifiers
.contains(crossterm::event::KeyModifiers::SHIFT)
{
result.push_str("shift-");
}
result.push_str("down");
result
}
KeyCode::Left => {
let mut result = String::new();
if key
.modifiers
.contains(crossterm::event::KeyModifiers::CONTROL)
{
result.push_str("ctrl-");
}
if key.modifiers.contains(crossterm::event::KeyModifiers::ALT) {
result.push_str("alt-");
}
if key
.modifiers
.contains(crossterm::event::KeyModifiers::SHIFT)
{
result.push_str("shift-");
}
result.push_str("left");
result
}
KeyCode::Right => {
let mut result = String::new();
if key
.modifiers
.contains(crossterm::event::KeyModifiers::CONTROL)
{
result.push_str("ctrl-");
}
if key.modifiers.contains(crossterm::event::KeyModifiers::ALT) {
result.push_str("alt-");
}
if key
.modifiers
.contains(crossterm::event::KeyModifiers::SHIFT)
{
result.push_str("shift-");
}
result.push_str("right");
result
}
KeyCode::Enter => {
let mut result = String::new();
if key
.modifiers
.contains(crossterm::event::KeyModifiers::CONTROL)
{
result.push_str("ctrl-");
}
if key.modifiers.contains(crossterm::event::KeyModifiers::ALT) {
result.push_str("alt-");
}
if key
.modifiers
.contains(crossterm::event::KeyModifiers::SHIFT)
{
result.push_str("shift-");
}
result.push_str("enter");
result
}
KeyCode::Esc => {
let mut result = String::new();
if key
.modifiers
.contains(crossterm::event::KeyModifiers::CONTROL)
{
result.push_str("ctrl-");
}
if key.modifiers.contains(crossterm::event::KeyModifiers::ALT) {
result.push_str("alt-");
}
if key
.modifiers
.contains(crossterm::event::KeyModifiers::SHIFT)
{
result.push_str("shift-");
}
result.push_str("esc");
result
}
KeyCode::F(n) => {
let mut result = String::new();
if key
.modifiers
.contains(crossterm::event::KeyModifiers::CONTROL)
{
result.push_str("ctrl-");
}
if key.modifiers.contains(crossterm::event::KeyModifiers::ALT) {
result.push_str("alt-");
}
if key
.modifiers
.contains(crossterm::event::KeyModifiers::SHIFT)
{
result.push_str("shift-");
}
result.push_str(&format!("f{}", n));
result
}
_ => return None,
};
crokey::parse(&key_str).ok()
}
fn convert_key_event(&self, key_code: KeyCode) -> Event {
match key_code {
KeyCode::Char(c) => Event::Character(c),
KeyCode::Tab => Event::Tab,
KeyCode::Up => Event::KeyUp,
KeyCode::Down => Event::KeyDown,
KeyCode::Left => Event::KeyLeft,
KeyCode::Right => Event::KeyRight,
KeyCode::Delete => Event::Delete,
KeyCode::Backspace => Event::Backspace,
KeyCode::Enter => Event::Enter,
KeyCode::F(n) => Event::FunctionKey(n),
KeyCode::Esc => Event::Escape,
_ => Event::Unknown,
}
}
fn convert_key_event_with_modifiers(&self, key_event: &KeyEvent) -> Option<Event> {
let mods = KeyModifiers {
shift: key_event
.modifiers
.contains(crossterm::event::KeyModifiers::SHIFT),
ctrl: key_event
.modifiers
.contains(crossterm::event::KeyModifiers::CONTROL),
alt: key_event
.modifiers
.contains(crossterm::event::KeyModifiers::ALT),
super_key: key_event
.modifiers
.contains(crossterm::event::KeyModifiers::SUPER),
};
let key = match key_event.code {
KeyCode::Char(c) => KeyKind::Char(c),
KeyCode::Tab => KeyKind::Tab,
KeyCode::Up => KeyKind::Up,
KeyCode::Down => KeyKind::Down,
KeyCode::Left => KeyKind::Left,
KeyCode::Right => KeyKind::Right,
KeyCode::Delete => KeyKind::Delete,
KeyCode::Backspace => KeyKind::Backspace,
KeyCode::Enter => KeyKind::Enter,
KeyCode::Esc => KeyKind::Escape,
KeyCode::F(n) => KeyKind::Function(n),
_ => return None,
};
Some(Event::KeyWithModifiers(KeyWithModifiers { key, mods }))
}
pub fn process_key_event(&self, key_event: KeyEvent) -> Event {
if let Some(action) = self.check_keybind_match(&key_event) {
return Event::Keybind(action);
}
if let Some(e) = self.convert_key_event_with_modifiers(&key_event) {
return e;
}
self.convert_key_event(key_event.code)
}
}
impl Default for KeyboardHandler {
fn default() -> Self {
Self::new()
}
}