use ratatui::crossterm::event::KeyCode;
use std::time::{Duration, Instant};
pub const Y_MOVE: &str = "↑↓/jk";
pub const CTRL_Y_MOVE: &str = "ctrl-↑↓/jk";
pub const X_MOVE: &str = "←→/hl";
pub const CTRL_X_MOVE: &str = "ctrl-←→/hl";
pub fn is_down(c: KeyCode) -> bool {
matches!(c, KeyCode::Char('j') | KeyCode::Down)
}
pub fn is_up(c: KeyCode) -> bool {
matches!(c, KeyCode::Char('k') | KeyCode::Up)
}
pub fn is_open(c: KeyCode) -> bool {
matches!(c, KeyCode::Enter)
}
pub fn is_back(c: KeyCode) -> bool {
matches!(c, KeyCode::Esc)
}
pub fn is_left(c: KeyCode) -> bool {
matches!(c, KeyCode::Char('h') | KeyCode::Left)
}
pub fn is_right(c: KeyCode) -> bool {
matches!(c, KeyCode::Char('l') | KeyCode::Right)
}
const REPEAT: Duration = Duration::from_millis(100);
const RAMP: Duration = Duration::from_millis(500);
const MAX_DOUBLINGS: u32 = 3;
#[derive(Default)]
pub struct Accel {
key: Option<(KeyCode, bool)>,
last: Option<Instant>,
held_since: Option<Instant>,
}
impl Accel {
pub fn steps(&mut self, code: KeyCode, ctrl: bool) -> usize {
let now = Instant::now();
let repeat = self.key == Some((code, ctrl))
&& self.last.is_some_and(|t| now.duration_since(t) < REPEAT);
self.key = Some((code, ctrl));
self.last = Some(now);
if !repeat {
self.held_since = None;
return 1;
}
let since = *self.held_since.get_or_insert(now);
let ramps = (now.duration_since(since).as_millis() / RAMP.as_millis()) as u32;
1 << ramps.min(MAX_DOUBLINGS)
}
}
pub fn stepped(cur: usize, len: usize, down: bool, rows: usize) -> usize {
if down {
(cur + rows).min(len.saturating_sub(1))
} else {
cur.saturating_sub(rows)
}
}
pub fn norm_esc(code: KeyCode, ctrl: bool) -> KeyCode {
if ctrl && matches!(code, KeyCode::Char('[')) {
KeyCode::Esc
} else {
code
}
}
pub fn char_to_byte(s: &str, idx: usize) -> usize {
s.char_indices().nth(idx).map(|(b, _)| b).unwrap_or(s.len())
}