use anyhow::Result;
use crossterm::event::{
self, Event, KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEvent, MouseEventKind,
};
use std::time::Duration;
use tokio::sync::mpsc;
use crate::agent::r#loop::AgentEvent;
pub enum AppEvent {
Key(KeyEvent),
Mouse(MouseEvent),
Paste(String),
Agent(Box<AgentEvent>),
Tick,
}
const CROSSTERM_CHANNEL_CAP: usize = 1024;
pub fn spawn_crossterm_thread() -> mpsc::Receiver<AppEvent> {
let (tx, rx) = mpsc::channel(CROSSTERM_CHANNEL_CAP);
std::thread::spawn(move || {
loop {
match event::poll(Duration::from_millis(50)) {
Ok(true) => match event::read() {
Ok(Event::Key(k)) => {
if tx.blocking_send(AppEvent::Key(k)).is_err() {
break;
}
}
Ok(Event::Mouse(m)) => {
let _ = tx.try_send(AppEvent::Mouse(m));
if tx.is_closed() {
break;
}
}
Ok(Event::Paste(t)) => {
const MAX_PASTE_BYTES: usize = 2 * 1024 * 1024; let t = if t.len() > MAX_PASTE_BYTES {
t[..MAX_PASTE_BYTES].to_string()
} else {
t
};
if tx.blocking_send(AppEvent::Paste(t)).is_err() {
break;
}
}
Ok(_) => {}
Err(_) => break,
},
Ok(false) => {
if tx.is_closed() {
break;
}
}
Err(_) => break,
}
}
});
rx
}
pub async fn next_event(
key_rx: &mut mpsc::Receiver<AppEvent>,
agent_rx: &mut mpsc::UnboundedReceiver<AgentEvent>,
) -> Result<AppEvent> {
tokio::select! {
biased;
Some(event) = key_rx.recv() => Ok(event),
Some(agent_event) = agent_rx.recv() => Ok(AppEvent::Agent(Box::new(agent_event))),
_ = tokio::time::sleep(Duration::from_millis(16)) => Ok(AppEvent::Tick),
}
}
pub enum KeyAction {
Char(char),
Backspace,
Delete,
Submit,
NewLine,
Quit,
ScrollUp,
ScrollDown,
HistoryUp,
HistoryDown,
NextAgent,
PrevAgent,
EscapePressed,
CursorLeft,
CursorRight,
CursorWordLeft,
CursorWordRight,
CursorLineStart,
CursorLineEnd,
DeleteWordBack,
DeleteToLineStart,
DeleteToLineEnd,
CycleApproveMode,
None,
}
pub enum MouseAction {
ScrollUp,
ScrollDown,
Click { column: u16, row: u16 },
None,
}
pub fn interpret_key(key: KeyEvent) -> KeyAction {
if key.modifiers.contains(KeyModifiers::CONTROL) {
match key.code {
KeyCode::Char('c' | 'd') => return KeyAction::Quit,
KeyCode::Char('j') => return KeyAction::NewLine,
KeyCode::Char('a') => return KeyAction::CursorLineStart,
KeyCode::Char('e') => return KeyAction::CursorLineEnd,
KeyCode::Char('w') => return KeyAction::DeleteWordBack,
KeyCode::Char('u') => return KeyAction::DeleteToLineStart,
KeyCode::Char('k') => return KeyAction::DeleteToLineEnd,
KeyCode::Char('b') => return KeyAction::CursorLeft,
KeyCode::Char('f') => return KeyAction::CursorRight,
KeyCode::Backspace => return KeyAction::DeleteWordBack,
KeyCode::Left => return KeyAction::CursorWordLeft,
KeyCode::Right => return KeyAction::CursorWordRight,
_ => return KeyAction::None,
}
}
if key.modifiers.contains(KeyModifiers::SUPER) {
match key.code {
KeyCode::Backspace => return KeyAction::DeleteToLineStart,
KeyCode::Delete => return KeyAction::DeleteToLineEnd,
KeyCode::Left => return KeyAction::CursorLineStart,
KeyCode::Right => return KeyAction::CursorLineEnd,
_ => {}
}
}
if key.modifiers.contains(KeyModifiers::ALT) {
match key.code {
KeyCode::Left => return KeyAction::CursorWordLeft,
KeyCode::Right => return KeyAction::CursorWordRight,
KeyCode::Char('b') => return KeyAction::CursorWordLeft,
KeyCode::Char('f') => return KeyAction::CursorWordRight,
KeyCode::Backspace => return KeyAction::DeleteWordBack,
KeyCode::Char('d') => return KeyAction::DeleteToLineEnd,
KeyCode::Char('y') => return KeyAction::CycleApproveMode,
KeyCode::Char('¥') => return KeyAction::CycleApproveMode,
_ => {}
}
}
if key.modifiers == KeyModifiers::NONE {
match key.code {
KeyCode::Char('¥') => return KeyAction::CycleApproveMode, KeyCode::Char('∫') => return KeyAction::CursorWordLeft, KeyCode::Char('ƒ') => return KeyAction::CursorWordRight, KeyCode::Char('∂') => return KeyAction::DeleteToLineEnd, _ => {}
}
}
if key.modifiers.contains(KeyModifiers::SHIFT) {
match key.code {
KeyCode::Tab | KeyCode::BackTab => return KeyAction::PrevAgent,
KeyCode::Enter => return KeyAction::NewLine,
KeyCode::Up => return KeyAction::ScrollUp,
KeyCode::Down => return KeyAction::ScrollDown,
_ => {}
}
}
if key.code == KeyCode::BackTab {
return KeyAction::PrevAgent;
}
match key.code {
KeyCode::Tab => KeyAction::NextAgent,
KeyCode::Enter => KeyAction::Submit,
KeyCode::Backspace => KeyAction::Backspace,
KeyCode::Delete => KeyAction::Delete,
KeyCode::Char(c) => KeyAction::Char(c),
KeyCode::Up => KeyAction::HistoryUp,
KeyCode::Down => KeyAction::HistoryDown,
KeyCode::Left => KeyAction::CursorLeft,
KeyCode::Right => KeyAction::CursorRight,
KeyCode::Home => KeyAction::CursorLineStart,
KeyCode::End => KeyAction::CursorLineEnd,
KeyCode::PageUp => KeyAction::ScrollUp,
KeyCode::PageDown => KeyAction::ScrollDown,
KeyCode::Esc => KeyAction::EscapePressed,
_ => KeyAction::None,
}
}
pub fn interpret_mouse(mouse: MouseEvent) -> MouseAction {
match mouse.kind {
MouseEventKind::ScrollUp => MouseAction::ScrollUp,
MouseEventKind::ScrollDown => MouseAction::ScrollDown,
MouseEventKind::Down(MouseButton::Left) => MouseAction::Click {
column: mouse.column,
row: mouse.row,
},
_ => MouseAction::None,
}
}