use std::collections::VecDeque;
use crate::ansi::{AnsiParser, ParserEvent};
use crate::event_bus::{Event, EventQueue, Key, KeyEvent, Modifiers, MouseButton};
use crate::terminal::{KittyKeyEvent, VtMachine};
use crate::tree::{NodeId, Point};
#[derive(Debug, Clone)]
pub struct EventPipelineConfig {
pub kitty_keyboard: bool,
pub bracketed_paste: bool,
pub focus_events: bool,
pub mouse_tracking: bool,
pub width: u16,
pub height: u16,
}
impl Default for EventPipelineConfig {
fn default() -> Self {
Self {
kitty_keyboard: false,
bracketed_paste: false,
focus_events: false,
mouse_tracking: false,
width: 80,
height: 24,
}
}
}
#[derive(Debug)]
pub struct EventPipeline {
parser: AnsiParser,
vt: VtMachine,
bus: EventQueue,
config: EventPipelineConfig,
pending_text: Vec<u8>,
}
impl Default for EventPipeline {
fn default() -> Self {
Self::new(EventPipelineConfig::default())
}
}
impl EventPipeline {
pub fn new(config: EventPipelineConfig) -> Self {
Self {
parser: AnsiParser::new(),
vt: VtMachine::new(config.width, config.height),
bus: EventQueue::new(),
config,
pending_text: Vec::new(),
}
}
pub fn feed(&mut self, data: &[u8]) {
self.parser.feed(data);
self.process_parser_events();
}
fn process_parser_events(&mut self) {
while let Some(event) = self.parser.poll_event() {
self.handle_parser_event(event);
}
}
fn handle_parser_event(&mut self, event: ParserEvent) {
match &event {
ParserEvent::Char(ch) => {
self.pending_text.push(*ch);
}
ParserEvent::LineFeed | ParserEvent::CarriageReturn | ParserEvent::Tab | ParserEvent::Backspace => {
self.flush_pending_text();
self.vt.process(&event);
}
ParserEvent::Csi(cmd) => {
self.flush_pending_text();
self.vt.process(&event);
self.handle_csi_event(cmd, &event);
}
ParserEvent::Osc(_) => {
self.flush_pending_text();
self.vt.process(&event);
}
_ => {
self.flush_pending_text();
self.vt.process(&event);
}
}
}
fn flush_pending_text(&mut self) {
if self.pending_text.is_empty() {
return;
}
let text = String::from_utf8_lossy(&self.pending_text);
let text_str = text.as_ref();
if self.config.bracketed_paste && text_str.len() > 1 {
self.bus.push_paste(text_str, NodeId::default());
} else {
for ch in text_str.chars() {
if !ch.is_control() {
let key = Key::Character(ch);
self.bus.push_key(key, Modifiers::NONE, NodeId::default());
}
}
}
self.pending_text.clear();
}
fn handle_csi_event(&mut self, _cmd: &crate::ansi::CsiCommand, _event: &ParserEvent) {
if let Some(kitty_key) = self.vt.last_kitty_key() {
let key_event = self.kitty_key_to_event(kitty_key);
self.bus.push(key_event);
self.vt.last_kitty_key_mut().take();
}
}
fn kitty_key_to_event(&self, kitty: &KittyKeyEvent) -> Event {
let key = self.keycode_to_key(kitty.keycode);
let modifiers = self.modifiers_from_kitty(kitty.modifiers);
Event::Key(KeyEvent::new(key, NodeId::default()).with_modifiers(modifiers))
}
fn keycode_to_key(&self, keycode: u32) -> Key {
match keycode {
0x0d => Key::Enter,
0x1b => Key::Escape,
0x08 => Key::Backspace,
0x7f => Key::Delete,
0x09 => Key::Tab,
0x20 => Key::Space,
0x4001 => Key::ArrowUp,
0x4002 => Key::ArrowDown,
0x4003 => Key::ArrowLeft,
0x4004 => Key::ArrowRight,
0x4005 => Key::Home,
0x4006 => Key::End,
0x4007 => Key::PageUp,
0x4008 => Key::PageDown,
code if (0x400a..=0x400d).contains(&code) => Key::F((code - 0x400a + 1) as u8),
code => char::from_u32(code).map(Key::Character).unwrap_or(Key::Character('\0')),
}
}
fn modifiers_from_kitty(&self, mods: u32) -> Modifiers {
Modifiers { shift: mods & 1 != 0, alt: mods & 2 != 0, ctrl: mods & 4 != 0, meta: mods & 8 != 0 }
}
pub fn push_key(&mut self, key: Key, modifiers: Modifiers, target: NodeId) {
self.bus.push_key(key, modifiers, target);
}
pub fn push_mouse(&mut self, button: MouseButton, position: Point, target: NodeId) {
self.bus.push_mouse(button, position, target);
}
pub fn push_paste(&mut self, text: impl Into<std::sync::Arc<str>>, target: NodeId) {
self.bus.push_paste(text, target);
}
pub fn push_resize(&mut self, width: u16, height: u16, prev_width: u16, prev_height: u16) {
self.bus.push_resize(width, height, prev_width, prev_height);
}
pub fn drain(&mut self) -> VecDeque<Event> {
self.flush_pending_text();
self.bus.drain()
}
pub fn len(&self) -> usize {
self.bus.len()
}
pub fn is_empty(&self) -> bool {
self.bus.is_empty()
}
pub fn clear(&mut self) {
self.pending_text.clear();
self.bus.clear();
}
pub fn resize(&mut self, width: u16, height: u16) {
self.config.width = width;
self.config.height = height;
self.vt.resize(width, height);
}
pub fn vt(&self) -> &VtMachine {
&self.vt
}
pub fn vt_mut(&mut self) -> &mut VtMachine {
&mut self.vt
}
pub fn bus(&self) -> &EventQueue {
&self.bus
}
pub fn bus_mut(&mut self) -> &mut EventQueue {
&mut self.bus
}
pub fn parser(&self) -> &AnsiParser {
&self.parser
}
pub fn parser_mut(&mut self) -> &mut AnsiParser {
&mut self.parser
}
pub fn reset(&mut self) {
self.parser.reset();
self.bus.clear();
self.pending_text.clear();
}
pub fn process_through_hub(
&mut self,
hub: &mut crate::event_emitter::EventEmitterHub,
arena: &crate::tree::NodeArena,
) {
self.bus.process_all(hub, arena);
}
pub fn process_until_consumed(
&mut self,
hub: &mut crate::event_emitter::EventEmitterHub,
arena: &crate::tree::NodeArena,
) -> Option<crate::event_bus::EventResult> {
self.bus.process_until_consumed(hub, arena)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pipeline_new() {
let pipeline = EventPipeline::default();
assert!(pipeline.is_empty());
assert_eq!(pipeline.len(), 0);
}
#[test]
fn pipeline_feed_plain_text() {
let mut pipeline = EventPipeline::new(EventPipelineConfig::default());
pipeline.feed(b"hello");
let events = pipeline.drain();
assert_eq!(events.len(), 5);
for (i, event) in events.iter().enumerate() {
if let Event::Key(ke) = event {
assert!(matches!(ke.key, Key::Character(c) if c == "hello".chars().nth(i).unwrap()));
} else {
panic!("Expected key event");
}
}
}
#[test]
fn pipeline_feed_single_char() {
let mut pipeline = EventPipeline::new(EventPipelineConfig::default());
pipeline.feed(b"a");
let events = pipeline.drain();
assert_eq!(events.len(), 1);
if let Event::Key(ke) = &events[0] {
assert!(matches!(ke.key, Key::Character('a')));
} else {
panic!("Expected key event");
}
}
#[test]
fn pipeline_push_key() {
let mut pipeline = EventPipeline::default();
pipeline.push_key(Key::Enter, Modifiers::default(), NodeId::default());
let events = pipeline.drain();
assert_eq!(events.len(), 1);
if let Event::Key(ke) = &events[0] {
assert_eq!(ke.key, Key::Enter);
} else {
panic!("Expected key event");
}
}
#[test]
fn pipeline_push_mouse() {
let mut pipeline = EventPipeline::default();
pipeline.push_mouse(MouseButton::Left, Point::new(10, 5), NodeId::default());
let events = pipeline.drain();
assert_eq!(events.len(), 1);
if let Event::Mouse(me) = &events[0] {
assert_eq!(me.button, MouseButton::Left);
assert_eq!(me.position.x, 10);
assert_eq!(me.position.y, 5);
} else {
panic!("Expected mouse event");
}
}
#[test]
fn pipeline_resize() {
let mut pipeline = EventPipeline::new(EventPipelineConfig { width: 80, height: 24, ..Default::default() });
pipeline.resize(120, 40);
assert_eq!(pipeline.vt().framebuffer().width(), 120);
assert_eq!(pipeline.vt().framebuffer().height(), 40);
}
#[test]
fn pipeline_clear() {
let mut pipeline = EventPipeline::default();
pipeline.push_key(Key::Enter, Modifiers::default(), NodeId::default());
assert!(!pipeline.is_empty());
pipeline.clear();
assert!(pipeline.is_empty());
}
#[test]
fn pipeline_reset() {
let mut pipeline = EventPipeline::default();
pipeline.push_key(Key::Enter, Modifiers::default(), NodeId::default());
assert!(!pipeline.is_empty());
pipeline.reset();
assert!(pipeline.is_empty());
}
#[test]
fn pipeline_process_through_hub() {
use crate::event_emitter::{EventEmitterHub, ListenerResult};
use std::sync::{Arc, Mutex};
let mut pipeline = EventPipeline::default();
pipeline.push_key(Key::Enter, Modifiers::default(), NodeId::default());
pipeline.push_key(Key::Escape, Modifiers::default(), NodeId::default());
assert_eq!(pipeline.len(), 2);
let mut hub = EventEmitterHub::new();
let count = Arc::new(Mutex::new(0u32));
let count_clone = count.clone();
hub.on_key(move |_: &crate::event_bus::KeyEvent| {
*count_clone.lock().unwrap() += 1;
ListenerResult::Continue
});
let arena = crate::tree::NodeArena::new();
pipeline.process_through_hub(&mut hub, &arena);
assert!(pipeline.is_empty());
assert_eq!(*count.lock().unwrap(), 2);
}
#[test]
fn pipeline_process_until_consumed() {
use crate::event_emitter::{EventEmitterHub, ListenerResult};
let mut pipeline = EventPipeline::default();
pipeline.push_key(Key::Enter, Modifiers::default(), NodeId::default());
pipeline.push_key(Key::Escape, Modifiers::default(), NodeId::default());
let mut hub = EventEmitterHub::new();
hub.on_key(
|ev: &crate::event_bus::KeyEvent| {
if ev.key == Key::Enter { ListenerResult::Consumed } else { ListenerResult::Continue }
},
);
let arena = crate::tree::NodeArena::new();
let result = pipeline.process_until_consumed(&mut hub, &arena);
assert!(result.is_some());
}
}