use crate::ansi::{Attr, ClearMode, Handler, LineClearMode, Mode, Performer};
use crate::cell::{Cell, Flags};
use crate::color::Rgb888;
use crate::graphic::TextOnGraphic;
use crate::text_buffer::TextBuffer;
use crate::text_buffer_cache::TextBufferCache;
use alloc::collections::VecDeque;
use core::cmp::min;
use core::fmt;
use embedded_graphics::prelude::{DrawTarget, OriginDimensions};
use vte::Parser;
pub struct Console<T: TextBuffer> {
parser: Parser,
inner: ConsoleInner<T>,
}
#[derive(Debug, Default, Clone, Copy)]
struct Cursor {
row: usize,
col: usize,
}
struct ConsoleInner<T: TextBuffer> {
cursor: Cursor,
saved_cursor: Cursor,
temp: Cell,
buf: T,
auto_wrap: bool,
report: VecDeque<u8>,
}
pub type ConsoleOnGraphic<D> = Console<TextBufferCache<TextOnGraphic<D>>>;
impl<D: DrawTarget<Color = Rgb888> + OriginDimensions> Console<TextBufferCache<TextOnGraphic<D>>> {
pub fn on_frame_buffer(buffer: D) -> Self {
let size = buffer.size();
Self::on_cached_text_buffer(TextOnGraphic::new(buffer, size.width, size.height))
}
}
impl<T: TextBuffer> Console<TextBufferCache<T>> {
pub fn on_cached_text_buffer(buffer: T) -> Self {
Self::on_text_buffer(TextBufferCache::new(buffer))
}
}
impl<T: TextBuffer> Console<T> {
pub fn on_text_buffer(buffer: T) -> Self {
Console {
parser: Parser::new(),
inner: ConsoleInner {
cursor: Cursor::default(),
saved_cursor: Cursor::default(),
temp: Cell::default(),
buf: buffer,
auto_wrap: true,
report: VecDeque::new(),
},
}
}
pub fn write_byte(&mut self, byte: u8) {
self.parser
.advance(&mut Performer::new(&mut self.inner), byte);
}
pub fn pop_report(&mut self) -> Option<u8> {
self.inner.report.pop_front()
}
pub fn rows(&self) -> usize {
self.inner.buf.height()
}
pub fn columns(&self) -> usize {
self.inner.buf.width()
}
}
impl<T: TextBuffer> fmt::Write for Console<T> {
fn write_str(&mut self, s: &str) -> fmt::Result {
for byte in s.bytes() {
self.write_byte(byte);
}
Ok(())
}
}
impl<T: TextBuffer> Handler for ConsoleInner<T> {
#[inline]
fn input(&mut self, c: char) {
trace!(" [input]: {:?} @ {:?}", c, self.cursor);
if self.cursor.col >= self.buf.width() {
if !self.auto_wrap {
return;
}
self.cursor.col = 0;
self.linefeed();
}
let mut temp = self.temp;
temp.c = c;
self.buf.write(self.cursor.row, self.cursor.col, temp);
self.cursor.col += 1;
}
#[inline]
fn goto(&mut self, row: usize, col: usize) {
trace!("Going to: line={}, col={}", row, col);
self.cursor.row = min(row, self.buf.height());
self.cursor.col = min(col, self.buf.width());
}
#[inline]
fn goto_line(&mut self, row: usize) {
trace!("Going to line: {}", row);
self.goto(row, self.cursor.col)
}
#[inline]
fn goto_col(&mut self, col: usize) {
trace!("Going to column: {}", col);
self.goto(self.cursor.row, col)
}
#[inline]
fn move_up(&mut self, rows: usize) {
trace!("Moving up: {}", rows);
self.goto(self.cursor.row.saturating_sub(rows), self.cursor.col)
}
#[inline]
fn move_down(&mut self, rows: usize) {
trace!("Moving down: {}", rows);
self.goto(
min(self.cursor.row + rows, self.buf.height() - 1) as _,
self.cursor.col,
)
}
#[inline]
fn move_forward(&mut self, cols: usize) {
trace!("Moving forward: {}", cols);
self.cursor.col = min(self.cursor.col + cols, self.buf.width() - 1);
}
#[inline]
fn move_backward(&mut self, cols: usize) {
trace!("Moving backward: {}", cols);
self.cursor.col = self.cursor.col.saturating_sub(cols);
}
#[inline]
fn move_down_and_cr(&mut self, rows: usize) {
trace!("Moving down and cr: {}", rows);
self.goto(min(self.cursor.row + rows, self.buf.height() - 1) as _, 0)
}
#[inline]
fn move_up_and_cr(&mut self, rows: usize) {
trace!("Moving up and cr: {}", rows);
self.goto(self.cursor.row.saturating_sub(rows), 0)
}
#[inline]
fn put_tab(&mut self, count: u16) {
let mut count = count;
let bg = self.temp.bg();
while self.cursor.col < self.buf.width() && count > 0 {
count -= 1;
loop {
self.buf.write(self.cursor.row, self.cursor.col, bg);
self.cursor.col += 1;
if self.cursor.col == self.buf.width() || self.cursor.col % 8 == 0 {
break;
}
}
}
}
#[inline]
fn backspace(&mut self) {
trace!("Backspace");
if self.cursor.col > 0 {
self.cursor.col -= 1;
}
}
#[inline]
fn carriage_return(&mut self) {
trace!("Carriage return");
self.cursor.col = 0;
}
#[inline]
fn linefeed(&mut self) {
trace!("Linefeed");
self.cursor.col = 0;
if self.cursor.row < self.buf.height() - 1 {
self.cursor.row += 1;
} else {
self.buf.new_line(self.temp);
}
}
#[inline]
fn scroll_up(&mut self, rows: usize) {
debug!("[Unhandled CSI] scroll_up {:?}", rows);
}
#[inline]
fn scroll_down(&mut self, rows: usize) {
debug!("[Unhandled CSI] scroll_down {:?}", rows);
}
#[inline]
fn erase_chars(&mut self, count: usize) {
trace!("Erasing chars: count={}, col={}", count, self.cursor.col);
let start = self.cursor.col;
let end = min(start + count, self.buf.width());
let bg = self.temp.bg();
for i in start..end {
self.buf.write(self.cursor.row, i, bg);
}
}
#[inline]
fn delete_chars(&mut self, count: usize) {
let columns = self.buf.width();
let count = min(count, columns - self.cursor.col - 1);
let row = self.cursor.row;
let start = self.cursor.col;
let end = start + count;
let bg = self.temp.bg();
for i in end..columns {
self.buf.write(row, i - count, self.buf.read(row, i));
self.buf.write(row, i, bg);
}
}
fn save_cursor_position(&mut self) {
trace!("Saving cursor position");
self.saved_cursor = self.cursor;
}
fn restore_cursor_position(&mut self) {
trace!("Restoring cursor position");
self.cursor = self.saved_cursor;
}
#[inline]
fn clear_line(&mut self, mode: LineClearMode) {
trace!("Clearing line: {:?}", mode);
let bg = self.temp.bg();
match mode {
LineClearMode::Right => {
for i in self.cursor.col..self.buf.width() {
self.buf.write(self.cursor.row, i, bg);
}
}
LineClearMode::Left => {
for i in 0..=self.cursor.col {
self.buf.write(self.cursor.row, i, bg);
}
}
LineClearMode::All => {
for i in 0..self.buf.width() {
self.buf.write(self.cursor.row, i, bg);
}
}
}
}
#[inline]
fn clear_screen(&mut self, mode: ClearMode) {
trace!("Clearing screen: {:?}", mode);
let bg = self.temp.bg();
let row = self.cursor.row;
let col = self.cursor.col;
match mode {
ClearMode::Above => {
for i in 0..row {
for j in 0..self.buf.width() {
self.buf.write(i, j, bg);
}
}
for j in 0..col {
self.buf.write(row, j, bg);
}
}
ClearMode::Below => {
for j in col..self.buf.width() {
self.buf.write(row, j, bg);
}
for i in row + 1..self.buf.height() {
for j in 0..self.buf.width() {
self.buf.write(i, j, bg);
}
}
}
ClearMode::All => {
self.buf.clear(bg);
self.cursor = Cursor::default();
}
_ => {}
}
}
#[inline]
fn terminal_attribute(&mut self, attr: Attr) {
trace!("Setting attribute: {:?}", attr);
match attr {
Attr::Foreground(color) => self.temp.fg = color,
Attr::Background(color) => self.temp.bg = color,
Attr::Reset => self.temp = Cell::default(),
Attr::Reverse => self.temp.flags |= Flags::INVERSE,
Attr::CancelReverse => self.temp.flags.remove(Flags::INVERSE),
Attr::Bold => self.temp.flags.insert(Flags::BOLD),
Attr::CancelBold => self.temp.flags.remove(Flags::BOLD),
Attr::Dim => self.temp.flags.insert(Flags::DIM),
Attr::CancelBoldDim => self.temp.flags.remove(Flags::BOLD | Flags::DIM),
Attr::Italic => self.temp.flags.insert(Flags::ITALIC),
Attr::CancelItalic => self.temp.flags.remove(Flags::ITALIC),
Attr::Underline => self.temp.flags.insert(Flags::UNDERLINE),
Attr::CancelUnderline => self.temp.flags.remove(Flags::UNDERLINE),
Attr::Hidden => self.temp.flags.insert(Flags::HIDDEN),
Attr::CancelHidden => self.temp.flags.remove(Flags::HIDDEN),
Attr::Strike => self.temp.flags.insert(Flags::STRIKEOUT),
Attr::CancelStrike => self.temp.flags.remove(Flags::STRIKEOUT),
_ => {
debug!("Term got unhandled attr: {:?}", attr);
}
}
}
#[inline]
fn set_mode(&mut self, mode: Mode) {
if mode == Mode::LineWrap {
self.auto_wrap = true;
} else {
debug!("[Unhandled CSI] Setting mode: {:?}", mode);
}
}
#[inline]
fn unset_mode(&mut self, mode: Mode) {
if mode == Mode::LineWrap {
self.auto_wrap = false;
} else {
debug!("[Unhandled CSI] Setting mode: {:?}", mode);
}
}
#[inline]
fn set_scrolling_region(&mut self, top: usize, bottom: Option<usize>) {
let bottom = bottom.unwrap_or_else(|| self.buf.height());
debug!(
"[Unhandled CSI] Setting scrolling region: ({};{})",
top, bottom
);
}
#[inline]
fn device_status(&mut self, arg: usize) {
trace!("Reporting device status: {}", arg);
match arg {
5 => {
for &c in b"\x1b[0n" {
self.report.push_back(c);
}
}
6 => {
let s = alloc::format!("\x1b[{};{}R", self.cursor.row + 1, self.cursor.col + 1);
for c in s.bytes() {
self.report.push_back(c);
}
}
_ => debug!("unknown device status query: {}", arg),
}
}
}