use crate::{
output::{
ansi::ScreenStyle,
color::Color,
style::{Style, Stylist},
},
prelude::{point, window, Point, Window},
Painter, Screen,
};
use log::trace;
#[cfg_attr(docsrs, doc(cfg(feature = "temu")))]
pub struct TerminalEmulator {
pub title: String,
screens: [(Screen, Window, Style); 2],
screen_idx: usize,
console: ransid::Console,
raw: bool,
}
impl Default for TerminalEmulator {
fn default() -> Self {
TerminalEmulator::new(String::new())
}
}
impl TerminalEmulator {
pub fn new(title: impl ToString) -> Self {
let screen = || (Screen::default(), Window::default(), Style::DEFAULT);
Self {
title: title.to_string(),
screens: [screen(), screen()],
screen_idx: 0,
console: ransid::Console::new(0, 0),
raw: false,
}
}
pub fn size(&self) -> Point {
point(self.console.state.w as u16, self.console.state.h as u16)
}
pub fn resize(&mut self, size: Point) {
if self.size() != size {
self.console.resize(size.col as usize, size.row as usize);
for (screen, view, ..) in &mut self.screens {
screen.resize(size);
*view = size.window()
}
}
}
pub fn pan(&mut self, new_view: Window) {
let Self {
ref mut screens,
screen_idx: screen,
..
} = *self;
let (_, view, ..) = &mut screens[screen];
*view = new_view
}
pub fn symbols(&mut self) -> impl Iterator<Item = (Point, &ScreenStyle, &str)> {
let Self {
ref mut screens,
screen_idx: screen,
..
} = *self;
let (screen, ..) = &mut screens[screen];
screen.symbols()
}
fn move_cursor(
_from_x: usize,
_from_y: usize,
to_x: usize,
to_y: usize,
_w: usize,
_h: usize,
screen: &mut Screen,
view: &Window,
) {
let pos = point(to_x as u16, to_y as u16) + view.position();
screen.set_last_write(pos);
}
fn fill(
x: usize,
y: usize,
w: usize,
h: usize,
color: ransid::Color,
screen: &mut Screen,
view: &Window,
style: Style,
) {
let pos = point(x as u16, y as u16) + view.position();
let size = point(w as u16, h as u16);
let scope = view & &window(pos, size);
let color = match color {
ransid::Color::Ansi(c) => Color::ansi(c),
ransid::Color::TrueColor(r, g, b) => Color::rgb(r, g, b),
};
Painter::default()
.with_scope(scope)
.with_new_style(style.back(color))
.fill(screen)
}
fn data(data: &[u8], screen: &mut Screen, view: &Window, style: Style, raw: bool) {
let text = std::str::from_utf8(data).unwrap_or_default();
let offset = screen
.last_write()
.and_then(|o| view.contains_point(o))
.map(|o| o - view.position())
.map(|o| 1 + o.row * view.width + o.col)
.unwrap_or_default();
Painter::default()
.with_scope(*view)
.with_new_style(style)
.paint(text, screen, offset, raw);
}
fn char(
x: usize,
y: usize,
c: char,
bold: bool,
italic: bool,
underlined: bool,
strikethrough: bool,
color: ransid::Color,
screen: &mut Screen,
view: &Window,
mut style: Style,
_raw: bool,
) {
let point = point(x as u16, y as u16) + view.position();
if bold {
style = style.bold(true)
}
if italic {
style = style.italic(true)
}
if underlined {
style = style.underlined(underlined);
}
if strikethrough {
style = style.crossed_out(strikethrough)
}
style = style.front(match color {
ransid::Color::Ansi(c) => Color::ansi(c),
ransid::Color::TrueColor(r, g, b) => Color::rgb(r, g, b),
});
if let Some((curr_style, _)) = screen.get(point) {
if let Some(bg) = curr_style.back_color() {
style = style.back(bg);
}
}
screen.set(point, &style, c.to_string().as_str());
}
}
impl std::io::Write for TerminalEmulator {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
trace!("write {} {:?}", buf.len(), std::str::from_utf8(buf));
let mut size_next = self.size();
let size = &mut size_next;
let mut clear = false;
let clear_screen = &mut clear;
let Self {
ref mut title,
ref mut screens,
ref mut screen_idx,
ref mut console,
ref mut raw,
} = *self;
let (screen, view, style) = &mut screens[*screen_idx];
console.write(buf, move |e| {
trace!("event {:?}", e);
match e {
ransid::Event::Char {
x,
y,
c,
bold,
italic,
underlined,
strikethrough,
color,
} => Self::char(
x,
y,
c,
bold,
italic,
underlined,
strikethrough,
color,
screen,
view,
*style,
*raw,
),
ransid::Event::Input { data } => Self::data(data, screen, view, *style, *raw),
ransid::Event::Rect { x, y, w, h, color } => {
Self::fill(x, y, w, h, color, screen, view, *style)
}
ransid::Event::ScreenBuffer { alternate, clear } => {
*screen_idx = if alternate { 1 } else { 0 };
*clear_screen = clear;
}
ransid::Event::Move {
from_x,
from_y,
to_x,
to_y,
w,
h,
} => Self::move_cursor(from_x, from_y, to_x, to_y, w, h, screen, view),
ransid::Event::Resize { w, h } => *size = point(w as u16, h as u16),
ransid::Event::Title { title: t } => *title = t,
}
});
if clear {
let (screen, view, style) = &mut screens[self.screen_idx];
Self::fill(
0,
0,
view.width.into(),
view.height.into(),
ransid::Color::Ansi(0),
screen,
view,
*style,
);
}
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}