use std::os::unix::io::IntoRawFd;
use std::os::unix::io::RawFd;
use std::sync::{
atomic::{AtomicBool, Ordering},
mpsc::{self, Receiver, Sender},
Arc, Mutex,
};
use std::time::Duration;
use std::{fs, io, thread};
use crossterm_utils::{ErrorKind, Result};
use libc::{c_int, c_void, size_t, ssize_t};
use mio::unix::EventedFd;
use mio::{Events, Poll, PollOpt, Ready, Token};
use lazy_static::lazy_static;
use crate::{InputEvent, InternalEvent, KeyEvent, MouseButton, MouseEvent};
use self::utils::{check_for_error, check_for_error_result};
lazy_static! {
static ref INTERNAL_EVENT_PROVIDER: Mutex<Box<dyn InternalEventProvider>> =
Mutex::new(default_internal_event_provider());
}
mod utils {
use std::io;
use libc::c_int;
pub fn check_for_error(result: c_int) -> io::Result<()> {
if result == -1 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
pub fn check_for_error_result(result: c_int) -> io::Result<libc::c_int> {
if result == -1 {
Err(io::Error::last_os_error())
} else {
Ok(result)
}
}
}
pub(crate) trait InternalEventProvider: Send {
fn pause(&mut self);
fn receiver(&mut self) -> Result<Receiver<InternalEvent>>;
}
fn default_internal_event_provider() -> Box<dyn InternalEventProvider> {
#[cfg(unix)]
Box::new(UnixInternalEventProvider::new())
}
#[derive(Clone)]
struct UnixInternalEventChannels {
senders: Arc<Mutex<Vec<Sender<InternalEvent>>>>,
}
impl UnixInternalEventChannels {
fn new() -> UnixInternalEventChannels {
UnixInternalEventChannels {
senders: Arc::new(Mutex::new(vec![])),
}
}
fn send(&self, event: InternalEvent) {
let mut guard = self.senders.lock().unwrap();
guard.retain(|sender| sender.send(event.clone()).is_ok());
}
fn receiver(&self) -> Receiver<InternalEvent> {
let (tx, rx) = mpsc::channel();
let mut guard = self.senders.lock().unwrap();
guard.push(tx);
rx
}
}
pub(crate) struct UnixInternalEventProvider {
channels: UnixInternalEventChannels,
reading_thread: Option<TtyReadingThread>,
}
impl UnixInternalEventProvider {
fn new() -> UnixInternalEventProvider {
UnixInternalEventProvider {
channels: UnixInternalEventChannels::new(),
reading_thread: None,
}
}
}
impl InternalEventProvider for UnixInternalEventProvider {
fn pause(&mut self) {
self.reading_thread = None;
}
fn receiver(&mut self) -> Result<Receiver<InternalEvent>> {
if !self
.reading_thread
.as_ref()
.map(TtyReadingThread::is_running)
.unwrap_or(false)
{
self.reading_thread = None;
}
let rx = self.channels.receiver();
if self.reading_thread.is_none() {
let reading_thread = TtyReadingThread::new(self.channels.clone())?;
self.reading_thread = Some(reading_thread);
}
Ok(rx)
}
}
fn max_len() -> usize {
if cfg!(target_os = "macos") {
<c_int>::max_value() as usize - 1
} else {
<ssize_t>::max_value() as usize
}
}
struct FileDesc {
fd: RawFd,
close_on_drop: bool,
}
impl FileDesc {
fn new(fd: RawFd) -> FileDesc {
FileDesc::with_close_on_drop(fd, true)
}
fn with_close_on_drop(fd: RawFd, close_on_drop: bool) -> FileDesc {
FileDesc { fd, close_on_drop }
}
fn read_byte(&self) -> Result<u8> {
let mut buf: [u8; 1] = [0];
utils::check_for_error(unsafe {
libc::read(self.fd, buf.as_mut_ptr() as *mut libc::c_void, 1) as c_int
})?;
Ok(buf[0])
}
fn write(&self, buf: &[u8]) -> io::Result<usize> {
let ret = check_for_error_result(unsafe {
libc::write(
self.fd,
buf.as_ptr() as *const c_void,
std::cmp::min(buf.len(), max_len()) as size_t,
) as c_int
})?;
Ok(ret as usize)
}
fn raw_fd(&self) -> RawFd {
self.fd
}
}
impl Drop for FileDesc {
fn drop(&mut self) {
if self.close_on_drop {
let _ = unsafe { libc::close(self.fd) };
}
}
}
fn pipe() -> Result<(FileDesc, FileDesc)> {
let (read_fd, write_fd) = unsafe {
let mut pipe_fds: [libc::c_int; 2] = [0; 2];
check_for_error(libc::pipe(pipe_fds.as_mut_ptr()))?;
(pipe_fds[0], pipe_fds[1])
};
let read_fd = FileDesc::new(read_fd);
let write_fd = FileDesc::new(write_fd);
Ok((read_fd, write_fd))
}
fn tty_fd() -> Result<FileDesc> {
let (fd, close_on_drop) = if unsafe { libc::isatty(libc::STDIN_FILENO) == 1 } {
(libc::STDIN_FILENO, false)
} else {
(
fs::OpenOptions::new()
.read(true)
.write(true)
.open("/dev/tty")?
.into_raw_fd(),
true,
)
};
Ok(FileDesc::with_close_on_drop(fd, close_on_drop))
}
fn tty_reading_thread(channels: UnixInternalEventChannels, shutdown_rx_fd: FileDesc) -> Result<()> {
const TTY_TOKEN: Token = Token(0);
const SHUTDOWN_TOKEN: Token = Token(1);
let tty_fd = tty_fd()?;
let tty_raw_fd = tty_fd.raw_fd();
let shutdown_rx_raw_fd = shutdown_rx_fd.raw_fd();
let tty_ev = EventedFd(&tty_raw_fd);
let shutdown_ev = EventedFd(&shutdown_rx_raw_fd);
let poll = Poll::new()?;
poll.register(&tty_ev, TTY_TOKEN, Ready::readable(), PollOpt::level())?;
poll.register(
&shutdown_ev,
SHUTDOWN_TOKEN,
Ready::readable(),
PollOpt::level(),
)?;
let mut events = Events::with_capacity(2);
let mut buffer: Vec<u8> = Vec::with_capacity(32);
let get_tokens =
|events: &Events| -> Vec<Token> { events.iter().map(|ev| ev.token()).collect() };
loop {
poll.poll(&mut events, None)?;
let tokens = get_tokens(&events);
if tokens.contains(&SHUTDOWN_TOKEN) {
break;
}
if tokens.contains(&TTY_TOKEN) {
if let Ok(byte) = tty_fd.read_byte() {
poll.poll(&mut events, Some(Duration::from_secs(0)))?;
let tokens = get_tokens(&events);
if tokens.contains(&SHUTDOWN_TOKEN) {
break;
}
let input_available = tokens.contains(&TTY_TOKEN);
buffer.push(byte);
match parse_event(&buffer, input_available) {
Ok(None) => {}
Ok(Some(event)) => {
buffer.clear();
channels.send(event);
}
Err(_) => buffer.clear(),
}
}
}
}
Ok(())
}
struct TtyReadingThread {
running: Arc<AtomicBool>,
shutdown_tx: FileDesc,
handle: Option<thread::JoinHandle<Result<()>>>,
}
impl TtyReadingThread {
fn new(channels: UnixInternalEventChannels) -> Result<TtyReadingThread> {
let (shutdown_rx, shutdown_tx) = pipe()?;
let running = Arc::new(AtomicBool::new(false));
let handle = thread::spawn({
let running = running.clone();
move || -> Result<()> {
running.store(true, Ordering::SeqCst);
let result = tty_reading_thread(channels, shutdown_rx);
running.store(false, Ordering::SeqCst);
result
}
});
Ok(TtyReadingThread {
running,
shutdown_tx,
handle: Some(handle),
})
}
fn is_running(&self) -> bool {
self.running.load(Ordering::SeqCst)
}
fn shutdown(&self) {
let _ = self.shutdown_tx.write("My precious, shutdown.".as_bytes());
}
}
impl Drop for TtyReadingThread {
fn drop(&mut self) {
self.shutdown();
let handle = self.handle.take().unwrap();
let _ = handle.join();
}
}
pub(crate) fn internal_event_receiver() -> Result<Receiver<InternalEvent>> {
INTERNAL_EVENT_PROVIDER.lock().unwrap().receiver()
}
fn could_not_parse_event_error() -> ErrorKind {
ErrorKind::IoError(io::Error::new(
io::ErrorKind::Other,
"Could not parse an event",
))
}
fn parse_event(buffer: &[u8], input_available: bool) -> Result<Option<InternalEvent>> {
if buffer.is_empty() {
return Ok(None);
}
match buffer[0] {
b'\x1B' => {
if buffer.len() == 1 {
if input_available {
Ok(None)
} else {
Ok(Some(InternalEvent::Input(InputEvent::Keyboard(
KeyEvent::Esc,
))))
}
} else {
match buffer[1] {
b'O' => {
if buffer.len() == 2 {
Ok(None)
} else {
match buffer[2] {
val @ b'P'..=b'S' => Ok(Some(InternalEvent::Input(
InputEvent::Keyboard(KeyEvent::F(1 + val - b'P')),
))),
_ => Err(could_not_parse_event_error()),
}
}
}
b'[' => parse_csi(buffer),
b'\x1B' => Ok(Some(InternalEvent::Input(InputEvent::Keyboard(
KeyEvent::Esc,
)))),
_ => parse_utf8_char(&buffer[1..]).map(|maybe_char| {
maybe_char
.map(KeyEvent::Alt)
.map(InputEvent::Keyboard)
.map(InternalEvent::Input)
}),
}
}
}
b'\r' | b'\n' => Ok(Some(InternalEvent::Input(InputEvent::Keyboard(
KeyEvent::Enter,
)))),
b'\t' => Ok(Some(InternalEvent::Input(InputEvent::Keyboard(
KeyEvent::Tab,
)))),
b'\x7F' => Ok(Some(InternalEvent::Input(InputEvent::Keyboard(
KeyEvent::Backspace,
)))),
c @ b'\x01'..=b'\x1A' => Ok(Some(InternalEvent::Input(InputEvent::Keyboard(
KeyEvent::Ctrl((c as u8 - 0x1 + b'a') as char),
)))),
c @ b'\x1C'..=b'\x1F' => Ok(Some(InternalEvent::Input(InputEvent::Keyboard(
KeyEvent::Ctrl((c as u8 - 0x1C + b'4') as char),
)))),
b'\0' => Ok(Some(InternalEvent::Input(InputEvent::Keyboard(
KeyEvent::Null,
)))),
_ => parse_utf8_char(buffer).map(|maybe_char| {
maybe_char
.map(KeyEvent::Char)
.map(InputEvent::Keyboard)
.map(InternalEvent::Input)
}),
}
}
fn parse_csi(buffer: &[u8]) -> Result<Option<InternalEvent>> {
assert!(buffer.starts_with(&[b'\x1B', b'[']));
if buffer.len() == 2 {
return Ok(None);
}
let input_event = match buffer[2] {
b'[' => {
if buffer.len() == 3 {
None
} else {
match buffer[3] {
val @ b'A'..=b'E' => Some(InputEvent::Keyboard(KeyEvent::F(1 + val - b'A'))),
_ => Some(InputEvent::Unknown),
}
}
}
b'D' => Some(InputEvent::Keyboard(KeyEvent::Left)),
b'C' => Some(InputEvent::Keyboard(KeyEvent::Right)),
b'A' => Some(InputEvent::Keyboard(KeyEvent::Up)),
b'B' => Some(InputEvent::Keyboard(KeyEvent::Down)),
b'H' => Some(InputEvent::Keyboard(KeyEvent::Home)),
b'F' => Some(InputEvent::Keyboard(KeyEvent::End)),
b'Z' => Some(InputEvent::Keyboard(KeyEvent::BackTab)),
b'M' => return parse_csi_x10_mouse(buffer),
b'<' => return parse_csi_xterm_mouse(buffer),
b'0'..=b'9' => {
if buffer.len() == 3 {
None
} else {
let last_byte = *buffer.last().unwrap();
if last_byte < 64 || last_byte > 126 {
None
} else {
match buffer[buffer.len() - 1] {
b'M' => return parse_csi_rxvt_mouse(buffer),
b'~' => return parse_csi_special_key_code(buffer),
b'R' => return parse_csi_cursor_position(buffer),
_ => return parse_csi_modifier_key_code(buffer),
}
}
}
}
_ => Some(InputEvent::Unknown),
};
Ok(input_event.map(InternalEvent::Input))
}
fn next_parsed<T>(iter: &mut dyn Iterator<Item = &str>) -> Result<T>
where
T: std::str::FromStr,
{
iter.next()
.ok_or_else(|| could_not_parse_event_error())?
.parse::<T>()
.map_err(|_| could_not_parse_event_error())
}
fn parse_csi_cursor_position(buffer: &[u8]) -> Result<Option<InternalEvent>> {
assert!(buffer.starts_with(&[b'\x1B', b'['])); assert!(buffer.ends_with(&[b'R']));
let s = std::str::from_utf8(&buffer[2..buffer.len() - 1])
.map_err(|_| could_not_parse_event_error())?;
let mut split = s.split(';');
let y = next_parsed::<u16>(&mut split)? - 1;
let x = next_parsed::<u16>(&mut split)? - 1;
Ok(Some(InternalEvent::CursorPosition(x, y)))
}
fn parse_csi_modifier_key_code(buffer: &[u8]) -> Result<Option<InternalEvent>> {
assert!(buffer.starts_with(&[b'\x1B', b'[']));
let modifier = buffer[buffer.len() - 2];
let key = buffer[buffer.len() - 1];
let input_event = match (modifier, key) {
(53, 65) => InputEvent::Keyboard(KeyEvent::CtrlUp),
(53, 66) => InputEvent::Keyboard(KeyEvent::CtrlDown),
(53, 67) => InputEvent::Keyboard(KeyEvent::CtrlRight),
(53, 68) => InputEvent::Keyboard(KeyEvent::CtrlLeft),
(50, 65) => InputEvent::Keyboard(KeyEvent::ShiftUp),
(50, 66) => InputEvent::Keyboard(KeyEvent::ShiftDown),
(50, 67) => InputEvent::Keyboard(KeyEvent::ShiftRight),
(50, 68) => InputEvent::Keyboard(KeyEvent::ShiftLeft),
_ => InputEvent::Unknown,
};
Ok(Some(InternalEvent::Input(input_event)))
}
fn parse_csi_special_key_code(buffer: &[u8]) -> Result<Option<InternalEvent>> {
assert!(buffer.starts_with(&[b'\x1B', b'['])); assert!(buffer.ends_with(&[b'~']));
let s = std::str::from_utf8(&buffer[2..buffer.len() - 1])
.map_err(|_| could_not_parse_event_error())?;
let mut split = s.split(';');
let first = next_parsed::<u8>(&mut split)?;
if next_parsed::<u8>(&mut split).is_ok() {
return Ok(Some(InternalEvent::Input(InputEvent::Unknown)));
}
let input_event = match first {
1 | 7 => InputEvent::Keyboard(KeyEvent::Home),
2 => InputEvent::Keyboard(KeyEvent::Insert),
3 => InputEvent::Keyboard(KeyEvent::Delete),
4 | 8 => InputEvent::Keyboard(KeyEvent::End),
5 => InputEvent::Keyboard(KeyEvent::PageUp),
6 => InputEvent::Keyboard(KeyEvent::PageDown),
v @ 11..=15 => InputEvent::Keyboard(KeyEvent::F(v - 10)),
v @ 17..=21 => InputEvent::Keyboard(KeyEvent::F(v - 11)),
v @ 23..=24 => InputEvent::Keyboard(KeyEvent::F(v - 12)),
_ => InputEvent::Unknown,
};
Ok(Some(InternalEvent::Input(input_event)))
}
fn parse_csi_rxvt_mouse(buffer: &[u8]) -> Result<Option<InternalEvent>> {
assert!(buffer.starts_with(&[b'\x1B', b'['])); assert!(buffer.ends_with(&[b'M']));
let s = std::str::from_utf8(&buffer[2..buffer.len() - 1])
.map_err(|_| could_not_parse_event_error())?;
let mut split = s.split(';');
let cb = next_parsed::<u16>(&mut split)?;
let cx = next_parsed::<u16>(&mut split)? - 1;
let cy = next_parsed::<u16>(&mut split)? - 1;
let mouse_input_event = match cb {
32 => MouseEvent::Press(MouseButton::Left, cx, cy),
33 => MouseEvent::Press(MouseButton::Middle, cx, cy),
34 => MouseEvent::Press(MouseButton::Right, cx, cy),
35 => MouseEvent::Release(cx, cy),
64 => MouseEvent::Hold(cx, cy),
96 | 97 => MouseEvent::Press(MouseButton::WheelUp, cx, cy),
_ => MouseEvent::Unknown,
};
Ok(Some(InternalEvent::Input(InputEvent::Mouse(
mouse_input_event,
))))
}
fn parse_csi_x10_mouse(buffer: &[u8]) -> Result<Option<InternalEvent>> {
assert!(buffer.starts_with(&[b'\x1B', b'[', b'M']));
if buffer.len() < 6 {
return Ok(None);
}
let cb = buffer[3] as i8 - 32;
let cx = buffer[4].saturating_sub(32) as u16 - 1;
let cy = buffer[5].saturating_sub(32) as u16 - 1;
let mouse_input_event = match cb & 0b11 {
0 => {
if cb & 0x40 != 0 {
MouseEvent::Press(MouseButton::WheelUp, cx, cy)
} else {
MouseEvent::Press(MouseButton::Left, cx, cy)
}
}
1 => {
if cb & 0x40 != 0 {
MouseEvent::Press(MouseButton::WheelDown, cx, cy)
} else {
MouseEvent::Press(MouseButton::Middle, cx, cy)
}
}
2 => MouseEvent::Press(MouseButton::Right, cx, cy),
3 => MouseEvent::Release(cx, cy),
_ => MouseEvent::Unknown,
};
Ok(Some(InternalEvent::Input(InputEvent::Mouse(
mouse_input_event,
))))
}
fn parse_csi_xterm_mouse(buffer: &[u8]) -> Result<Option<InternalEvent>> {
assert!(buffer.starts_with(&[b'\x1B', b'[', b'<']));
if !buffer.ends_with(&[b'm']) && !buffer.ends_with(&[b'M']) {
return Ok(None);
}
let s = std::str::from_utf8(&buffer[3..buffer.len() - 1])
.map_err(|_| could_not_parse_event_error())?;
let mut split = s.split(';');
let cb = next_parsed::<u16>(&mut split)?;
let cx = next_parsed::<u16>(&mut split)? - 1;
let cy = next_parsed::<u16>(&mut split)? - 1;
let input_event = match cb {
0..=2 | 64..=65 => {
let button = match cb {
0 => MouseButton::Left,
1 => MouseButton::Middle,
2 => MouseButton::Right,
64 => MouseButton::WheelUp,
65 => MouseButton::WheelDown,
_ => unreachable!(),
};
match buffer.last().unwrap() {
b'M' => InputEvent::Mouse(MouseEvent::Press(button, cx, cy)),
b'm' => InputEvent::Mouse(MouseEvent::Release(cx, cy)),
_ => InputEvent::Unknown,
}
}
32 => InputEvent::Mouse(MouseEvent::Hold(cx, cy)),
3 => InputEvent::Mouse(MouseEvent::Release(cx, cy)),
_ => InputEvent::Unknown,
};
Ok(Some(InternalEvent::Input(input_event)))
}
fn parse_utf8_char(buffer: &[u8]) -> Result<Option<char>> {
match std::str::from_utf8(buffer) {
Ok(s) => {
let ch = s
.chars()
.next()
.ok_or_else(|| could_not_parse_event_error())?;
Ok(Some(ch))
}
Err(_) => {
let required_bytes = match buffer[0] {
(0x00..=0x7F) => 1, (0xC0..=0xDF) => 2, (0xE0..=0xEF) => 3, (0xF0..=0xF7) => 4, (0x80..=0xBF) | (0xF8..=0xFF) => return Err(could_not_parse_event_error()),
};
if required_bytes > 1 && buffer.len() > 1 {
for byte in &buffer[1..] {
if byte & !0b0011_1111 != 0b1000_0000 {
return Err(could_not_parse_event_error());
}
}
}
if buffer.len() < required_bytes {
Ok(None)
} else {
Err(could_not_parse_event_error())
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_esc_key() {
assert_eq!(
parse_event("\x1B".as_bytes(), false).unwrap(),
Some(InternalEvent::Input(InputEvent::Keyboard(KeyEvent::Esc))),
);
}
#[test]
fn test_possible_esc_sequence() {
assert_eq!(parse_event("\x1B".as_bytes(), true).unwrap(), None,);
}
#[test]
fn test_alt_key() {
assert_eq!(
parse_event("\x1Bc".as_bytes(), false).unwrap(),
Some(InternalEvent::Input(InputEvent::Keyboard(KeyEvent::Alt(
'c'
)))),
);
}
#[test]
fn test_parse_event_subsequent_calls() {
assert_eq!(
parse_event("\x1B[20;10R".as_bytes(), false).unwrap(),
Some(InternalEvent::CursorPosition(9, 19))
);
assert_eq!(
parse_event("\x1B[D".as_bytes(), false).unwrap(),
Some(InternalEvent::Input(InputEvent::Keyboard(KeyEvent::Left))),
);
assert_eq!(
parse_event("\x1B[2D".as_bytes(), false).unwrap(),
Some(InternalEvent::Input(InputEvent::Keyboard(
KeyEvent::ShiftLeft
))),
);
assert_eq!(
parse_event("\x1B[3~".as_bytes(), false).unwrap(),
Some(InternalEvent::Input(InputEvent::Keyboard(KeyEvent::Delete))),
);
assert_eq!(
parse_event("\x1B[32;30;40;M".as_bytes(), false).unwrap(),
Some(InternalEvent::Input(InputEvent::Mouse(MouseEvent::Press(
MouseButton::Left,
29,
39
))))
);
assert_eq!(
parse_event("\x1B[M0\x60\x70".as_bytes(), false).unwrap(),
Some(InternalEvent::Input(InputEvent::Mouse(MouseEvent::Press(
MouseButton::Left,
63,
79
))))
);
assert_eq!(
parse_event("\x1B[<0;20;10;M".as_bytes(), false).unwrap(),
Some(InternalEvent::Input(InputEvent::Mouse(MouseEvent::Press(
MouseButton::Left,
19,
9
))))
);
assert_eq!(
parse_event("Ž".as_bytes(), false).unwrap(),
Some(InternalEvent::Input(InputEvent::Keyboard(KeyEvent::Char(
'Ž'
)))),
);
}
#[test]
fn test_parse_event() {
assert_eq!(
parse_event("\t".as_bytes(), false).unwrap(),
Some(InternalEvent::Input(InputEvent::Keyboard(KeyEvent::Tab))),
);
}
#[test]
fn test_parse_csi_cursor_position() {
assert_eq!(
parse_csi_cursor_position("\x1B[20;10R".as_bytes()).unwrap(),
Some(InternalEvent::CursorPosition(9, 19))
);
}
#[test]
fn test_parse_csi() {
assert_eq!(
parse_csi("\x1B[D".as_bytes()).unwrap(),
Some(InternalEvent::Input(InputEvent::Keyboard(KeyEvent::Left))),
);
}
#[test]
fn test_parse_csi_modifier_key_code() {
assert_eq!(
parse_csi_modifier_key_code("\x1B[2D".as_bytes()).unwrap(),
Some(InternalEvent::Input(InputEvent::Keyboard(
KeyEvent::ShiftLeft
))),
);
}
#[test]
fn test_parse_csi_special_key_code() {
assert_eq!(
parse_csi_special_key_code("\x1B[3~".as_bytes()).unwrap(),
Some(InternalEvent::Input(InputEvent::Keyboard(KeyEvent::Delete))),
);
}
#[test]
fn test_parse_csi_special_key_code_multiple_values_not_supported() {
assert_eq!(
parse_csi_special_key_code("\x1B[3;2~".as_bytes()).unwrap(),
Some(InternalEvent::Input(InputEvent::Unknown)),
);
}
#[test]
fn test_parse_csi_rxvt_mouse() {
assert_eq!(
parse_csi_rxvt_mouse("\x1B[32;30;40;M".as_bytes()).unwrap(),
Some(InternalEvent::Input(InputEvent::Mouse(MouseEvent::Press(
MouseButton::Left,
29,
39
))))
);
}
#[test]
fn test_parse_csi_x10_mouse() {
assert_eq!(
parse_csi_x10_mouse("\x1B[M0\x60\x70".as_bytes()).unwrap(),
Some(InternalEvent::Input(InputEvent::Mouse(MouseEvent::Press(
MouseButton::Left,
63,
79
))))
);
}
#[test]
fn test_parse_csi_xterm_mouse() {
assert_eq!(
parse_csi_xterm_mouse("\x1B[<0;20;10;M".as_bytes()).unwrap(),
Some(InternalEvent::Input(InputEvent::Mouse(MouseEvent::Press(
MouseButton::Left,
19,
9
))))
);
assert_eq!(
parse_csi_xterm_mouse("\x1B[<0;20;10M".as_bytes()).unwrap(),
Some(InternalEvent::Input(InputEvent::Mouse(MouseEvent::Press(
MouseButton::Left,
19,
9
))))
);
assert_eq!(
parse_csi_xterm_mouse("\x1B[<0;20;10;m".as_bytes()).unwrap(),
Some(InternalEvent::Input(InputEvent::Mouse(
MouseEvent::Release(19, 9)
)))
);
assert_eq!(
parse_csi_xterm_mouse("\x1B[<0;20;10m".as_bytes()).unwrap(),
Some(InternalEvent::Input(InputEvent::Mouse(
MouseEvent::Release(19, 9)
)))
);
}
#[test]
fn test_utf8() {
assert_eq!(parse_utf8_char("a".as_bytes()).unwrap(), Some('a'),);
assert_eq!(parse_utf8_char(&[0xC3, 0xB1]).unwrap(), Some('ñ'),);
assert!(parse_utf8_char(&[0xC3, 0x28]).is_err());
assert!(parse_utf8_char(&[0xA0, 0xA1]).is_err());
assert_eq!(
parse_utf8_char(&[0xE2, 0x81, 0xA1]).unwrap(),
Some('\u{2061}'),
);
assert!(parse_utf8_char(&[0xE2, 0x28, 0xA1]).is_err());
assert!(parse_utf8_char(&[0xE2, 0x82, 0x28]).is_err());
assert_eq!(
parse_utf8_char(&[0xF0, 0x90, 0x8C, 0xBC]).unwrap(),
Some('𐌼'),
);
assert!(parse_utf8_char(&[0xF0, 0x28, 0x8C, 0xBC]).is_err());
assert!(parse_utf8_char(&[0xF0, 0x90, 0x28, 0xBC]).is_err());
assert!(parse_utf8_char(&[0xF0, 0x28, 0x8C, 0x28]).is_err());
}
}