use std::collections::VecDeque;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
use tokio::sync::Notify;
use super::TerminalSize;
use crate::time_compat::InputWaitClock;
pub(crate) const MAX_PENDING_INPUT: usize = 1024 * 1024;
pub(crate) const MAX_PENDING_OUTPUT: usize = 4 * 1024 * 1024;
pub(crate) const MAX_OUTPUT_TAP: usize = 256 * 1024;
pub(crate) const SCROLLBACK_LINES: usize = 1000;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum TtyEvent {
Byte(u8),
Resize,
Closed,
}
struct TtyState {
input: VecDeque<u8>,
waiting: bool,
raw: bool,
foreground: bool,
reading_line: bool,
resized: bool,
closed: bool,
screen: vt100::Parser,
output: Vec<u8>,
tap: Vec<u8>,
tap_total: u64,
}
pub(crate) struct TtyShared {
state: Mutex<TtyState>,
input_ready: Notify,
idle: Notify,
clock: InputWaitClock,
cancel: Arc<AtomicBool>,
interrupt: Arc<Notify>,
}
#[derive(Clone)]
pub(crate) struct Tty(Arc<TtyShared>);
impl Tty {
pub(crate) fn new(
size: TerminalSize,
clock: InputWaitClock,
cancel: Arc<AtomicBool>,
interrupt: Arc<Notify>,
) -> Self {
Self(Arc::new(TtyShared {
state: Mutex::new(TtyState {
input: VecDeque::new(),
waiting: false,
raw: false,
foreground: false,
reading_line: false,
resized: false,
closed: false,
screen: vt100::Parser::new(size.rows, size.cols, SCROLLBACK_LINES),
output: Vec::new(),
tap: Vec::new(),
tap_total: 0,
}),
input_ready: Notify::new(),
idle: Notify::new(),
clock,
cancel,
interrupt,
}))
}
fn lock(&self) -> MutexGuard<'_, TtyState> {
self.0.state.lock().unwrap_or_else(PoisonError::into_inner)
}
pub(crate) fn send(&self, bytes: &[u8]) -> usize {
let mut accepted = 0;
{
let mut st = self.lock();
for &b in bytes {
if b == 0x03 && st.foreground && !st.raw {
self.0.cancel.store(true, Ordering::Relaxed);
self.0.interrupt.notify_one();
accepted += 1;
continue;
}
if st.input.len() >= MAX_PENDING_INPUT {
break;
}
st.input.push_back(b);
accepted += 1;
}
if !st.input.is_empty() {
st.waiting = false;
}
}
self.0.input_ready.notify_one();
accepted
}
pub(crate) fn resize(&self, size: TerminalSize) {
{
let mut st = self.lock();
st.screen.screen_mut().set_size(size.rows, size.cols);
st.resized = true;
st.waiting = false;
}
self.0.input_ready.notify_one();
}
pub(crate) fn close(&self) {
{
let mut st = self.lock();
st.closed = true;
st.waiting = false;
}
self.0.input_ready.notify_one();
}
pub(crate) fn is_idle(&self) -> bool {
let st = self.lock();
st.waiting && st.input.is_empty() && !st.resized
}
pub(crate) async fn idle_signal(&self) {
self.0.idle.notified().await;
}
pub(crate) fn take_output(&self) -> Vec<u8> {
std::mem::take(&mut self.lock().output)
}
pub(crate) fn output_mark(&self) -> u64 {
self.lock().tap_total
}
pub(crate) fn output_since(&self, mark: u64) -> Vec<u8> {
let st = self.lock();
let new = st.tap_total.saturating_sub(mark);
let keep = usize::try_from(new).unwrap_or(usize::MAX).min(st.tap.len());
st.tap[st.tap.len() - keep..].to_vec()
}
pub(crate) fn with_screen<R>(&self, f: impl FnOnce(&vt100::Screen) -> R) -> R {
f(self.lock().screen.screen())
}
pub(crate) fn history_rows(&self) -> Vec<String> {
let mut st = self.lock();
let screen = st.screen.screen_mut();
let (rows, cols) = screen.size();
let rows = usize::from(rows);
screen.set_scrollback(usize::MAX);
let total = screen.scrollback();
let mut out = Vec::with_capacity(total + rows);
let mut off = total;
while off > 0 {
screen.set_scrollback(off);
let take = off.min(rows);
out.extend(screen.rows(0, cols).take(take));
off -= take;
}
screen.set_scrollback(0);
out.extend(screen.rows(0, cols));
out
}
pub(crate) fn size(&self) -> TerminalSize {
let (rows, cols) = self.lock().screen.screen().size();
TerminalSize { rows, cols }
}
pub(crate) fn set_raw(&self, raw: bool) {
self.lock().raw = raw;
}
pub(crate) fn reading_line(&self) -> ReadingGuard {
self.lock().reading_line = true;
ReadingGuard(self.clone())
}
pub(crate) fn is_reading_line(&self) -> bool {
self.lock().reading_line
}
pub(crate) fn set_foreground(&self, foreground: bool) {
self.lock().foreground = foreground;
}
pub(crate) fn write(&self, bytes: &[u8]) {
let mut st = self.lock();
st.screen.process(bytes);
st.output.extend_from_slice(bytes);
let len = st.output.len();
if len > MAX_PENDING_OUTPUT {
st.output.drain(..len - MAX_PENDING_OUTPUT);
}
}
pub(crate) fn tap_output(&self, bytes: &[u8]) {
if bytes.is_empty() {
return;
}
let mut st = self.lock();
st.tap.extend_from_slice(bytes);
st.tap_total += bytes.len() as u64;
let len = st.tap.len();
if len > MAX_OUTPUT_TAP {
st.tap.drain(..len - MAX_OUTPUT_TAP);
}
}
pub(crate) fn write_cooked(&self, bytes: &[u8]) {
if !bytes.contains(&b'\n') {
self.write(bytes);
return;
}
let mut out = Vec::with_capacity(bytes.len() + bytes.len() / 8);
let mut prev = 0u8;
for &b in bytes {
if b == b'\n' && prev != b'\r' {
out.push(b'\r');
}
out.push(b);
prev = b;
}
self.write(&out);
}
pub(crate) fn peek_byte(&self) -> Option<u8> {
self.lock().input.front().copied()
}
pub(crate) fn try_read_byte(&self) -> Option<u8> {
self.lock().input.pop_front()
}
pub(crate) async fn read_event(&self) -> TtyEvent {
loop {
{
let mut st = self.lock();
if st.resized {
st.resized = false;
return TtyEvent::Resize;
}
if let Some(b) = st.input.pop_front() {
st.waiting = false;
return TtyEvent::Byte(b);
}
if st.closed {
return TtyEvent::Closed;
}
st.waiting = true;
}
self.0.idle.notify_one();
let _wait = self.0.clock.pause();
let _waiting = WaitingGuard(self);
self.0.input_ready.notified().await;
}
}
}
pub(crate) struct ReadingGuard(Tty);
impl Drop for ReadingGuard {
fn drop(&mut self) {
self.0.lock().reading_line = false;
}
}
struct WaitingGuard<'a>(&'a Tty);
impl Drop for WaitingGuard<'_> {
fn drop(&mut self) {
self.0.lock().waiting = false;
}
}