use appcui::prelude::{CharFlags, Character, Color, Surface};
#[derive(Debug, Clone, Copy)]
struct TerminalState {
default_foreground_color: Color,
default_background_color: Color,
foreground: Color,
background: Color,
bold: bool,
dim: bool,
italic: bool,
underline: bool,
cursor_x: i32,
cursor_y: i32,
}
impl TerminalState {
fn reset(&mut self) {
self.foreground = self.default_foreground_color;
self.background = self.default_background_color;
self.bold = false;
self.dim = false;
self.italic = false;
self.underline = false;
self.cursor_x = 0;
self.cursor_y = 0;
}
}
pub struct TerminalParser {
width: u32,
height: u32,
state: TerminalState,
}
impl TerminalParser {
pub fn new(width: u32, height: u32, default_background_color: Color) -> Self {
Self {
width,
height,
state: TerminalState {
default_foreground_color: Color::RGB(255, 255, 255),
default_background_color,
foreground: Color::RGB(255, 255, 255),
background: default_background_color,
bold: false,
dim: false,
italic: false,
underline: false,
cursor_x: 0,
cursor_y: 0,
}
}
}
pub fn parse_to_surface(&mut self, data: &[u8], mut surface: Surface) -> Surface {
let text = String::from_utf8_lossy(data);
let chars: Vec<char> = text.chars().collect();
let mut i = 0;
while i < chars.len() {
if chars[i] == '\u{1b}' && i + 1 < chars.len() && chars[i + 1] == '[' {
let slice: String = chars[i..].iter().collect();
let consumed = self.parse_ansi_sequence(slice.as_bytes(), &mut surface);
let consumed_chars = String::from_utf8_lossy(&slice.as_bytes()[..consumed])
.chars()
.count();
i += consumed_chars;
}
else {
self.write_character(chars[i], &mut surface);
i += 1;
}
}
surface
}
pub fn resize(&mut self, width: u32, height: u32) {
self.width = width;
self.height = height;
}
fn parse_ansi_sequence(&mut self, data: &[u8], surface: &mut Surface) -> usize {
if data.len() < 3 {
return 1; }
let mut i = 2; let mut params = Vec::new();
let mut current_param = String::new();
let mut private_mode = false;
if i < data.len() && data[i] == b'?' {
private_mode = true;
i += 1;
}
while i < data.len() {
let byte = data[i];
match byte {
b'0'..=b'9' => current_param.push(byte as char),
b';' => {
params.push(current_param.parse::<u32>().unwrap_or(0));
current_param.clear();
}
b'A'..=b'Z' | b'a'..=b'z' => {
if !current_param.is_empty() {
params.push(current_param.parse::<u32>().unwrap_or(0));
}
if private_mode {
self.handle_private_ansi_command(byte as char, ¶ms, surface);
}
else {
self.handle_ansi_command(byte as char, ¶ms, surface);
}
return i + 1;
}
_ => break,
}
i += 1;
}
1 }
fn handle_ansi_command(&mut self, command: char, params: &[u32], surface: &mut Surface) {
match command {
'H' | 'f' => {
let row = params.get(0).unwrap_or(&1).saturating_sub(1) as i32;
let col = params.get(1).unwrap_or(&1).saturating_sub(1) as i32;
self.state.cursor_x = col.min(self.width as i32 - 1);
self.state.cursor_y = row.min(self.height as i32 - 1);
}
'A' => {
let count = params.get(0).unwrap_or(&1);
self.state.cursor_y = (self.state.cursor_y - *count as i32).max(0);
}
'B' => {
let count = params.get(0).unwrap_or(&1);
self.state.cursor_y = (self.state.cursor_y + *count as i32).min(self.height as i32 - 1);
}
'C' => {
let count = params.get(0).unwrap_or(&1);
self.state.cursor_x = (self.state.cursor_x + *count as i32).min(self.width as i32 - 1);
}
'D' => {
let count = params.get(0).unwrap_or(&1);
self.state.cursor_x = (self.state.cursor_x - *count as i32).max(0);
}
'm' => {
if params.is_empty() {
self.state.reset();
}
else {
self.handle_sgr_params(params);
}
}
'J' => {
let mode = params.get(0).copied().unwrap_or(0);
self.handle_erase_display(mode, surface);
}
'K' => {
let mode = params.get(0).copied().unwrap_or(0);
self.handle_erase_line(mode, surface);
}
_ => {
}
}
}
fn handle_private_ansi_command(&mut self, command: char, _params: &[u32], surface: &mut Surface) {
match command {
'l' => {
surface.hide_cursor()
}
'h' => {
surface.set_cursor(self.state.cursor_x, self.state.cursor_y);
}
_ => {
}
}
}
fn handle_erase_display(&mut self, param: u32, surface: &mut Surface) {
match param {
0 => {
for y in self.state.cursor_y..self.height as i32 {
let start_x = if y == self.state.cursor_y { self.state.cursor_x } else { 0 };
for x in start_x..self.width as i32 {
surface.write_char(x, y, Character::new(' ', self.state.foreground, self.state.background, CharFlags::None));
}
}
}
1 => {
for y in 0..=self.state.cursor_y {
let end_x = if y == self.state.cursor_y { self.state.cursor_x } else { self.width as i32 - 1 };
for x in 0..=end_x {
surface.write_char(x, y, Character::new(' ', self.state.foreground, self.state.background, CharFlags::None));
}
}
}
2 => {
surface.clear(Character::new(' ', self.state.foreground, self.state.background, CharFlags::None));
}
_ => {}
}
}
fn handle_erase_line(&mut self, param: u32, surface: &mut Surface) {
match param {
0 => {
for x in self.state.cursor_x..self.width as i32 {
surface.write_char(x, self.state.cursor_y, Character::new(' ', self.state.foreground, self.state.background, CharFlags::None));
}
}
1 => {
for x in 0..=self.state.cursor_x {
surface.write_char(x, self.state.cursor_y, Character::new(' ', self.state.foreground, self.state.background, CharFlags::None));
}
}
2 => {
for x in 0..self.width as i32 {
surface.write_char(x, self.state.cursor_y, Character::new(' ', self.state.foreground, self.state.background, CharFlags::None));
}
}
_ => {}
}
}
fn handle_sgr_params(&mut self, params: &[u32]) {
let mut iter = params.iter().copied().peekable();
while let Some(param) = iter.next() {
match param {
0 => self.state.reset(), 1 => self.state.bold = true,
2 => self.state.dim = true,
3 => self.state.italic = true,
4 => self.state.underline = true,
22 => {
self.state.bold = false;
self.state.dim = false;
}
23 => self.state.italic = false,
24 => self.state.underline = false,
39 => self.state.foreground = self.state.default_foreground_color,
49 => self.state.background = self.state.default_background_color,
30..=37 => self.state.foreground = ansi_16_color(param - 30, false),
40..=47 => self.state.background = ansi_16_color(param - 40, false),
90..=97 => self.state.foreground = ansi_16_color(param - 90, true),
100..=107 => self.state.background = ansi_16_color(param - 100, true),
38 | 48 => {
let is_foreground = param == 38;
if let Some(mode) = iter.next() {
match mode {
5 => {
if let Some(idx) = iter.next() {
let color = ansi_256_color(idx);
if is_foreground {
self.state.foreground = color;
} else {
self.state.background = color;
}
}
}
2 => {
if let (Some(r), Some(g), Some(b)) = (iter.next(), iter.next(), iter.next()) {
let color = Color::RGB(r as u8, g as u8, b as u8);
if is_foreground {
self.state.foreground = color;
} else {
self.state.background = color;
}
}
}
_ => {}
}
}
}
_ => {
}
}
}
}
fn write_character(&mut self, ch: char, surface: &mut Surface) {
match ch {
'\r' => {
self.state.cursor_x = 0;
}
'\n' => {
self.state.cursor_x = 0;
self.state.cursor_y += 1;
if self.state.cursor_y >= self.height as i32 {
self.state.cursor_y = self.height as i32 - 1;
}
}
'\t' => {
self.state.cursor_x = ((self.state.cursor_x / 8) + 1) * 8;
self.cursor_forward()
}
'\x08' => {
if self.state.cursor_x > 0 {
self.state.cursor_x -= 1;
}
}
c if c.is_control() => {
}
c => {
let mut flags = CharFlags::None;
if self.state.bold {
flags |= CharFlags::Bold;
}
if self.state.dim {
}
if self.state.italic {
flags |= CharFlags::Italic;
}
if self.state.underline {
flags |= CharFlags::Underline;
}
let character = Character::new(c, self.state.foreground, self.state.background, flags);
surface.write_char(self.state.cursor_x, self.state.cursor_y, character);
self.cursor_forward();
}
}
}
pub fn cursor_forward(&mut self) {
self.state.cursor_x += 1;
if self.state.cursor_x >= self.width as i32 {
self.state.cursor_x = 0;
self.state.cursor_y += 1;
if self.state.cursor_y >= self.height as i32 {
self.state.cursor_y = self.height as i32 - 1;
}
}
}
}
fn ansi_16_color(code: u32, bright: bool) -> Color {
let (r, g, b): (u8, u8, u8) = match code {
0 => (0, 0, 0), 1 => (128, 0, 0), 2 => (0, 128, 0), 3 => (128, 128, 0), 4 => (0, 0, 128), 5 => (128, 0, 128), 6 => (0, 128, 128), 7 => (192, 192, 192), _ => (255, 255, 255),
};
if bright {
Color::RGB(
r.saturating_mul(2).min(255),
g.saturating_mul(2).min(255),
b.saturating_mul(2).min(255)
)
}
else {
Color::RGB(r, g, b)
}
}
fn ansi_256_color(idx: u32) -> Color {
match idx {
0..=15 => {
if idx < 8 {
ansi_16_color(idx, false)
} else {
ansi_16_color(idx - 8, true)
}
}
16..=231 => {
let n = idx - 16;
let r = (n / 36) % 6;
let g = (n / 6) % 6;
let b = n % 6;
Color::RGB(
(r * 51) as u8,
(g * 51) as u8,
(b * 51) as u8,
)
}
232..=255 => {
let level = 8 + (idx - 232) * 10;
Color::RGB(level as u8, level as u8, level as u8)
}
_ => Color::RGB(0, 0, 0),
}
}