use super::events::{AppEvent, Event};
use super::Resizer;
use crate::input::parser::*;
use crate::mmap::MmapVec;
use ansitok::ElementKind;
use futures_lite::io::{self};
use futures_lite::{ready, stream, Stream, StreamExt};
use log::*;
use std::{iter::once, pin::Pin, task::Poll};
use crate::Control;
pub fn terminal_input_stream<'a, A: AppEvent>(
control: impl Control + 'a,
reader: impl io::AsyncRead + 'a,
raw_mode: bool,
) -> impl Stream<Item = io::Result<Event<A>>> + 'a {
let ansi = terminal_ansi_input_stream(reader, raw_mode);
Resizer::new(Box::pin(ansi), Box::pin(control))
}
pub fn terminal_ansi_input_stream<'a, A: AppEvent>(
reader: impl io::AsyncRead + 'a,
raw_mode: bool,
) -> impl Stream<Item = io::Result<Event<A>>> + 'a {
AnsiStream::terminal(Box::pin(reader), 8 * 1024)
.flat_map(move |item| {
let mut err = None;
let mut items = None;
match ansi_input_item_stream(item, raw_mode) {
Ok(i) => items = Some(stream::iter(i)),
Err(e) => err = Some(Err(e)),
}
stream::iter(err).chain(stream::iter(items).flatten().map(Result::Ok))
})
.chain(stream::once(Ok(Event::InputClosed)))
}
fn ansi_input_item_stream<A: AppEvent>(
item: io::Result<AnsiData>,
raw_mode: bool,
) -> io::Result<impl IntoIterator<Item = Event<A>>> {
trace!("ansi item {item:?}");
let item = item?;
let mut items: Vec<Event<A>> = vec![];
let unknown = |e| {
warn!("Failed to parse input: {}", e);
Event::Unknown(item.data.to_vec())
};
match item.kind {
Some(ansitok::ElementKind::Text) => {
for key in std::str::from_utf8(item.data.as_slice())
.expect("utf-8 already in parser")
.chars()
.map(|c| parse_key(c, raw_mode))
{
items.push(
key.unwrap_or_else(|e| Some(unknown(e)))
.ok_or("missing key - not likely")
.unwrap_or_else(unknown),
);
}
}
Some(ansitok::ElementKind::Sgr) => match item.data.as_slice() {
[0x1b, b'[', b'>', ..] => {
let e = parse_csi_sgr_mouse(&item.data)
.unwrap_or_else(|e| Some(unknown(e)))
.ok_or("missing SGR data")
.unwrap_or_else(unknown);
trace!("sgr mouse {e:?}");
items.push(e);
}
_ => {
items.push(unknown("unrecognized SGR sequence"));
}
},
Some(ansitok::ElementKind::Csi) => {
let e = parse_csi(&item.data, raw_mode)
.unwrap_or_else(|e| Some(unknown(e)))
.ok_or("missing CSI data")
.unwrap_or_else(unknown);
trace!("csi {e:?}");
items.push(e);
}
Some(ansitok::ElementKind::Esc) => {
let e = parse_esc(&item.data, raw_mode)
.unwrap_or_else(|e| Some(unknown(e)))
.ok_or("missing ESC data")
.unwrap_or_else(unknown);
trace!("esc {e:?}");
items.push(e);
}
Some(ansitok::ElementKind::Osc) => items.push(unknown("unsupported OSC")),
None => items.push(unknown("invalid data, most likely non-utf-8")),
}
Ok(items)
}
struct AnsiStream<T> {
reader: Pin<Box<T>>,
buffer: MmapVec<u8>,
start: usize,
end: usize,
terminal: bool,
}
#[derive(Debug, Clone, PartialEq)]
struct AnsiData {
kind: Option<ansitok::ElementKind>,
data: Vec<u8>,
}
impl<T> AnsiStream<T> {
pub fn application(reader: Pin<Box<T>>, capacity: usize) -> Self {
Self {
reader,
buffer: MmapVec::zeroed(capacity),
start: 0,
end: 0,
terminal: false,
}
}
pub fn terminal(reader: Pin<Box<T>>, capacity: usize) -> Self {
Self {
reader,
buffer: MmapVec::zeroed(capacity),
start: 0,
end: 0,
terminal: true,
}
}
pub fn buffer(&self) -> &[u8] {
&self.buffer[self.start..self.end]
}
}
impl<T> Stream for AnsiStream<T>
where
T: io::AsyncRead,
{
type Item = io::Result<AnsiData>;
fn poll_next(
mut self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
if self.start == self.end {
trace!(
"start {} == end {} => empty buffer reset",
self.start,
self.end
);
self.start = 0;
self.end = 0;
}
if self.end == self.buffer.len() && self.start != 0 {
trace!(
"start {}, end {} => buffer is full, but we've got space, shifting",
self.start,
self.end
);
let range = self.start..self.end;
self.end = range.len();
self.start = 0;
self.buffer.copy_within(range, 0);
}
if self.end != self.buffer.len() {
let length = self.end - self.start;
let range = self.end..;
let Self { reader, buffer, .. } = &mut *self;
let buffer = &mut buffer[range];
self.end += if length == 0 {
trace!("filling buffer - blocking");
let read = ready!(reader.as_mut().poll_read(cx, buffer))?;
trace!("filled buffer with {read} bytes");
read
} else if length < 1024 {
trace!("filling buffer - chancing it");
let read = match reader.as_mut().poll_read(cx, buffer) {
std::task::Poll::Ready(read) => read,
std::task::Poll::Pending => Ok(0),
}?;
trace!("filled buffer with {read} bytes");
read
} else {
0
}
}
let buffer = self.buffer();
let text = match std::str::from_utf8(buffer) {
Ok(str) => str,
Err(error) => {
let (valid, _) = buffer.split_at(error.valid_up_to());
if valid.is_empty() {
let data = once(&buffer[0])
.chain(
buffer
.iter()
.skip(1)
.take_while(|b| utf8_char_width(**b) == 0),
)
.cloned()
.collect();
let data = AnsiData { kind: None, data };
assert!(!buffer.is_empty());
assert!(!data.data.is_empty());
self.start += data.data.len();
return Poll::Ready(Some(Ok(data)));
} else {
std::str::from_utf8(valid).expect("already checked utf-8")
}
}
};
if self.terminal {
match text.as_bytes() {
[0x1b, b'O', _param, ..] => {
let data = AnsiData {
kind: Some(ElementKind::Esc),
data: buffer[0..3].to_vec(),
};
self.start += data.data.len();
return Poll::Ready(Some(Ok(data)));
}
_ => {
}
}
}
match ansitok::parse_ansi(text).next() {
Some(element) => {
assert!(element.start() == 0);
trace!("{element:?}");
let data = buffer[0..element.end()].to_vec();
self.start += data.len();
let data = AnsiData {
kind: Some(element.kind()),
data,
};
Poll::Ready(Some(Ok(data)))
}
None => {
if self.end != self.start && self.end == self.buffer.len() {
Poll::Ready(Some(Err(io::Error::new(
io::ErrorKind::OutOfMemory,
"The buffer is full, but input is incomplete",
))))
} else {
Poll::Ready(None)
}
}
}
}
}
#[inline]
const fn utf8_char_width(b: u8) -> usize {
const UTF8_CHAR_WIDTH: &[u8; 256] = &[
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ];
UTF8_CHAR_WIDTH[b as usize] as usize
}