asterinas/kernel/comps/framebuffer/src/ansi_escape.rs

278 lines
8.2 KiB
Rust

// SPDX-License-Identifier: MPL-2.0
use crate::Pixel;
/// A finite-state machine (FSM) to handle ANSI escape sequences.
#[derive(Debug)]
pub(super) struct EscapeFsm {
state: WaitFor,
params: [u32; MAX_PARAMS],
}
/// A trait to execute operations from ANSI escape sequences.
pub(super) trait EscapeOp {
/// Sets the cursor position.
fn set_cursor(&mut self, x: usize, y: usize);
/// Sets the foreground color.
fn set_fg_color(&mut self, val: Pixel);
/// Sets the background color.
fn set_bg_color(&mut self, val: Pixel);
}
const MAX_PARAMS: usize = 8;
#[derive(Clone, Copy, Debug)]
enum WaitFor {
Escape,
Bracket,
Params(u8),
}
/// Foreground and background colors.
///
/// See <https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit>.
#[rustfmt::skip]
const COLORS: [Pixel; 16] = [
// Black
Pixel { red: 0, green: 0, blue: 0 },
// Red
Pixel { red: 170, green: 0, blue: 0 },
// Green
Pixel { red: 0, green: 170, blue: 0 },
// Yellow
Pixel { red: 170, green: 85, blue: 0 },
// Blue
Pixel { red: 0, green: 0, blue: 170 },
// Magenta
Pixel { red: 170, green: 0, blue: 170 },
// Cyan
Pixel { red: 0, green: 170, blue: 170 },
// White
Pixel { red: 170, green: 170, blue: 170 },
// Bright Black (Gray)
Pixel { red: 85, green: 85, blue: 85 },
// Bright Red
Pixel { red: 255, green: 85, blue: 85 },
// Bright Green
Pixel { red: 85, green: 255, blue: 85 },
// Bright Yellow
Pixel { red: 255, green: 255, blue: 85 },
// Bright Blue
Pixel { red: 85, green: 85, blue: 255 },
// Bright Magenta
Pixel { red: 255, green: 85, blue: 255 },
// Bright Cyan
Pixel { red: 85, green: 255, blue: 255 },
// Bright White
Pixel { red: 255, green: 255, blue: 255 },
];
impl EscapeFsm {
pub(super) fn new() -> Self {
Self {
state: WaitFor::Escape,
params: [0; MAX_PARAMS],
}
}
/// Tries to eat a character as part of the ANSI escape sequence.
///
/// This method returns a boolean value indicating whether the character is part of an ANSI
/// escape sequence. In other words, if the method returns true, then the character has been
/// eaten and should not be displayed in the console.
pub(super) fn eat<T: EscapeOp>(&mut self, byte: u8, op: &mut T) -> bool {
let num_params = match (self.state, byte) {
// Handle '\033'.
(WaitFor::Escape, 0o33) => {
self.state = WaitFor::Bracket;
return true;
}
(WaitFor::Escape, _) => {
// This is not an ANSI escape sequence.
return false;
}
// Handle '['.
(WaitFor::Bracket, b'[') => {
self.state = WaitFor::Params(0);
self.params[0] = 0;
return true;
}
(WaitFor::Bracket, _) => {
// The character is invalid. We cannot handle it, so we are aborting the ANSI
// escape sequence.
self.state = WaitFor::Escape;
return true;
}
// Handle numeric parameters.
(WaitFor::Params(i), b'0'..=b'9') => {
let param = &mut self.params[i as usize];
*param = param.wrapping_mul(10).wrapping_add((byte - b'0') as u32);
return true;
}
(WaitFor::Params(i), b';') if (i as usize + 1) < MAX_PARAMS => {
self.state = WaitFor::Params(i + 1);
self.params[i as usize + 1] = 0;
return true;
}
(WaitFor::Params(_), b';') => {
// There are too many parameters. We cannot handle that many, so we are aborting
// the ANSI escape sequence.
self.state = WaitFor::Escape;
return true;
}
// Break and handle the final action.
(WaitFor::Params(i), _) => {
self.state = WaitFor::Escape;
(i + 1) as usize
}
};
match byte {
// CUP - Cursor Position
b'H' if num_params == 2 => {
op.set_cursor(
self.params[1].saturating_sub(1) as usize,
self.params[0].saturating_sub(1) as usize,
);
}
// SGR - Select Graphic Rendition
b'm' => self.handle_srg(num_params, op),
// Invalid or unsupported
_ => {}
}
true
}
/// Handles the "Select Graphic Rendition" sequence.
fn handle_srg<T: EscapeOp>(&self, num_params: usize, op: &mut T) {
for param in &self.params[..num_params] {
match param {
// Reset text attributes
0 => {
op.set_fg_color(Pixel::WHITE);
op.set_bg_color(Pixel::BLACK);
}
// Set foreground and background colors
// Reference: <https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit>
30..=37 => op.set_fg_color(COLORS[*param as usize - 30]),
90..=97 => op.set_fg_color(COLORS[*param as usize - 90 + 8]),
40..=47 => op.set_bg_color(COLORS[*param as usize - 40]),
100..=107 => op.set_bg_color(COLORS[*param as usize - 100 + 8]),
// Invalid or unsupported
_ => {}
}
}
}
}
#[cfg(ktest)]
mod test {
use ostd::prelude::*;
use super::*;
struct State {
x: usize,
y: usize,
fg: Pixel,
bg: Pixel,
}
impl Default for State {
fn default() -> Self {
Self {
x: 0,
y: 0,
fg: Pixel::WHITE,
bg: Pixel::BLACK,
}
}
}
impl EscapeOp for State {
fn set_cursor(&mut self, x: usize, y: usize) {
self.x = x;
self.y = y;
}
fn set_fg_color(&mut self, val: Pixel) {
self.fg = val;
}
fn set_bg_color(&mut self, val: Pixel) {
self.bg = val;
}
}
fn eat_escape_sequence(esc_fsm: &mut EscapeFsm, state: &mut State, bytes: &[u8]) {
for byte in bytes {
assert!(esc_fsm.eat(*byte, state));
}
}
#[ktest]
fn move_cursor() {
let mut esc_fsm = EscapeFsm::new();
let mut state = State::default();
// Move the cursor to the third row (y=2) and the second column (x=1).
eat_escape_sequence(&mut esc_fsm, &mut state, b"\x1B[3;2H");
assert_eq!(state.x, 1);
assert_eq!(state.y, 2);
assert!(!esc_fsm.eat(b'a', &mut state));
// There is invalid as there is no 0-th row or 0-th column. But in this case, let's move
// the cursor to the first row and the first column.
eat_escape_sequence(&mut esc_fsm, &mut state, b"\x1B[0;0H");
assert_eq!(state.x, 0);
assert_eq!(state.y, 0);
assert!(!esc_fsm.eat(b'a', &mut state));
}
#[ktest]
fn set_color() {
let mut esc_fsm = EscapeFsm::new();
let mut state = State::default();
// Set the foreground color and background color to "Black".
eat_escape_sequence(&mut esc_fsm, &mut state, b"\x1B[30;40m");
assert_eq!(state.fg, Pixel::BLACK);
assert_eq!(state.bg, Pixel::BLACK);
assert!(!esc_fsm.eat(b'a', &mut state));
// Reset.
eat_escape_sequence(&mut esc_fsm, &mut state, b"\x1B[0m");
assert_eq!(state.fg, Pixel::WHITE);
assert_eq!(state.bg, Pixel::BLACK);
assert!(!esc_fsm.eat(b'a', &mut state));
// Set the foreground color and background color to "Bright White".
eat_escape_sequence(&mut esc_fsm, &mut state, b"\x1B[97m");
assert_eq!(state.fg, Pixel::WHITE);
eat_escape_sequence(&mut esc_fsm, &mut state, b"\x1B[107m");
assert_eq!(state.bg, Pixel::WHITE);
assert!(!esc_fsm.eat(b'a', &mut state));
// Reset.
eat_escape_sequence(&mut esc_fsm, &mut state, b"\x1B[m");
assert_eq!(state.fg, Pixel::WHITE);
assert_eq!(state.bg, Pixel::BLACK);
assert!(!esc_fsm.eat(b'a', &mut state));
}
}