clash_tui/src/keybindings.rs
yly 4d79ba5884 Implement Clash TUI with iocraft
Fullscreen terminal UI for Clash proxy manager with proxy selector,
connections viewer, configurable keybindings, and TOML config support.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-18 02:09:51 +08:00

109 lines
3.7 KiB
Rust

use anyhow::{anyhow, Result};
use iocraft::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
#[derive(Clone, Copy, Debug)]
pub struct KeyBinding {
pub code: KeyCode,
pub modifiers: KeyModifiers,
}
impl KeyBinding {
pub fn parse(s: &str) -> Result<Self> {
let s = s.trim();
let (modifiers, key_str) = if s.contains('+') {
let parts: Vec<&str> = s.splitn(2, '+').collect();
let mut mods = KeyModifiers::NONE;
for m in parts[0].to_lowercase().split('+') {
match m.trim() {
"ctrl" | "control" => mods.insert(KeyModifiers::CONTROL),
"shift" => mods.insert(KeyModifiers::SHIFT),
"alt" => mods.insert(KeyModifiers::ALT),
other => return Err(anyhow!("unknown modifier: {}", other)),
}
}
(mods, parts[1].trim())
} else {
(KeyModifiers::NONE, s)
};
let code = parse_key_code(key_str)?;
Ok(Self { code, modifiers })
}
pub fn matches(&self, event: &KeyEvent) -> bool {
event.kind != KeyEventKind::Release
&& event.code == self.code
&& event.modifiers == self.modifiers
}
}
fn parse_key_code(s: &str) -> Result<KeyCode> {
let lower = s.to_lowercase();
match lower.as_str() {
"enter" | "return" => Ok(KeyCode::Enter),
"tab" => Ok(KeyCode::Tab),
"backtab" => Ok(KeyCode::BackTab),
"esc" | "escape" => Ok(KeyCode::Esc),
"up" => Ok(KeyCode::Up),
"down" => Ok(KeyCode::Down),
"left" => Ok(KeyCode::Left),
"right" => Ok(KeyCode::Right),
"home" => Ok(KeyCode::Home),
"end" => Ok(KeyCode::End),
"pageup" | "page_up" => Ok(KeyCode::PageUp),
"pagedown" | "page_down" => Ok(KeyCode::PageDown),
"delete" | "del" => Ok(KeyCode::Delete),
"space" => Ok(KeyCode::Char(' ')),
"backspace" => Ok(KeyCode::Backspace),
"insert" => Ok(KeyCode::Insert),
"f1" => Ok(KeyCode::F(1)),
"f2" => Ok(KeyCode::F(2)),
"f3" => Ok(KeyCode::F(3)),
"f4" => Ok(KeyCode::F(4)),
"f5" => Ok(KeyCode::F(5)),
"f6" => Ok(KeyCode::F(6)),
"f7" => Ok(KeyCode::F(7)),
"f8" => Ok(KeyCode::F(8)),
"f9" => Ok(KeyCode::F(9)),
"f10" => Ok(KeyCode::F(10)),
"f11" => Ok(KeyCode::F(11)),
"f12" => Ok(KeyCode::F(12)),
"null" => Ok(KeyCode::Null),
c if c.len() == 1 => Ok(KeyCode::Char(c.chars().next().unwrap())),
_ => Err(anyhow!("unknown key: {}", s)),
}
}
#[derive(Clone, Debug)]
pub struct Keybindings {
pub quit: KeyBinding,
pub tab_next: KeyBinding,
pub tab_prev: KeyBinding,
pub up: KeyBinding,
pub down: KeyBinding,
pub left: KeyBinding,
pub right: KeyBinding,
pub select: KeyBinding,
pub close_connection: KeyBinding,
pub close_all_connections: KeyBinding,
pub refresh: KeyBinding,
}
impl Keybindings {
pub fn from_config(cfg: &crate::config::KeybindingsConfig) -> Result<Self> {
Ok(Self {
quit: KeyBinding::parse(&cfg.quit)?,
tab_next: KeyBinding::parse(&cfg.tab_next)?,
tab_prev: KeyBinding::parse(&cfg.tab_prev)?,
up: KeyBinding::parse(&cfg.up)?,
down: KeyBinding::parse(&cfg.down)?,
left: KeyBinding::parse(&cfg.left)?,
right: KeyBinding::parse(&cfg.right)?,
select: KeyBinding::parse(&cfg.select)?,
close_connection: KeyBinding::parse(&cfg.close_connection)?,
close_all_connections: KeyBinding::parse(&cfg.close_all_connections)?,
refresh: KeyBinding::parse(&cfg.refresh)?,
})
}
}