Refactor the processing of ANSI escape sequences

This commit is contained in:
wyt8 2026-02-10 16:19:03 +00:00
parent 1fd4a65f4b
commit 643df09722
3 changed files with 446 additions and 249 deletions

View File

@ -4,13 +4,28 @@ use crate::Pixel;
/// A finite-state machine (FSM) to handle ANSI escape sequences.
#[derive(Debug)]
pub(super) struct EscapeFsm {
pub struct EscapeFsm {
state: WaitFor,
params: [u32; MAX_PARAMS],
params: [Option<u32>; MAX_PARAMS],
}
impl Default for EscapeFsm {
fn default() -> Self {
Self::new()
}
}
/// The mode for "Erase in Display" (ED) command.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum EraseInDisplay {
CursorToEnd,
CursorToBeginning,
EntireScreen,
EntireScreenAndScrollback,
}
/// A trait to execute operations from ANSI escape sequences.
pub(super) trait EscapeOp {
pub trait EscapeOp {
/// Sets the cursor position.
fn set_cursor(&mut self, x: usize, y: usize);
@ -18,15 +33,30 @@ pub(super) trait EscapeOp {
fn set_fg_color(&mut self, val: Pixel);
/// Sets the background color.
fn set_bg_color(&mut self, val: Pixel);
/// Erases part or all of the display.
fn erase_in_display(&mut self, mode: EraseInDisplay);
}
const MAX_PARAMS: usize = 8;
// FIXME: Currently we only support a few ANSI escape sequences, and we just swallow the unsupported ones.
#[derive(Clone, Copy, Debug)]
enum WaitFor {
Escape,
Bracket,
Params(u8),
/// Just saw ESC (0x1B), expecting the next selector.
AfterEscape,
/// Currently parsing CSI parameters.
Csi {
idx: u8,
is_private: bool,
saw_digit: bool,
in_intermediate: bool,
},
/// OSC payload after ESC] . Swallow until BEL (0x07) or ST (ESC \).
Osc,
/// Saw ESC inside OSC and it maybe ST.
OscEscape,
}
/// Foreground and background colors.
@ -69,92 +99,265 @@ const COLORS: [Pixel; 16] = [
];
impl EscapeFsm {
pub(super) fn new() -> Self {
pub fn new() -> Self {
Self {
state: WaitFor::Escape,
params: [0; MAX_PARAMS],
params: [None; MAX_PARAMS],
}
}
/// Tries to eat a character as part of the ANSI escape sequence.
/// Tries to eat a byte 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;
/// Returns `true` if the byte is consumed by the FSM and must not be rendered as text.
/// Returns `false` if the byte is not part of an escape sequence and should be rendered.
pub fn eat<T: EscapeOp>(&mut self, byte: u8, op: &mut T) -> bool {
match self.state {
WaitFor::Escape => {
if byte == 0x1b {
self.state = WaitFor::AfterEscape;
return true;
}
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;
WaitFor::AfterEscape => {
match byte {
b'[' => {
// CSI begins.
self.params.fill(None);
self.state = WaitFor::Csi {
idx: 0,
is_private: false,
saw_digit: false,
in_intermediate: false,
};
true
}
b']' => {
// OSC begins.
self.state = WaitFor::Osc;
true
}
_ => {
// The character is invalid. We cannot handle it, so we are aborting the ANSI
// escape sequence.
self.state = WaitFor::Escape;
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;
WaitFor::Osc => {
match byte {
0x07 => {
// BEL terminator
self.state = WaitFor::Escape;
true
}
0x1b => {
// Might be ST
self.state = WaitFor::OscEscape;
true
}
_ => {
// Swallow OSC payload.
true
}
}
}
// Break and handle the final action.
(WaitFor::Params(i), _) => {
self.state = WaitFor::Escape;
(i + 1) as usize
WaitFor::OscEscape => {
if byte == b'\\' {
self.state = WaitFor::Escape;
true
} else {
// Not ST and we go back to OSC and keep swallowing.
self.state = WaitFor::Osc;
true
}
}
};
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,
);
WaitFor::Csi {
idx,
is_private,
saw_digit,
in_intermediate,
} => {
match byte {
// Intermediate bytes (0x20..=0x2F).
// Once we see any intermediate, we are in the intermediate section;
// later bytes must be intermediate or final.
0x20..=0x2f => {
// If we already entered intermediate section, just keep swallowing them.
// If we were still in parameter section, we now transition to intermediate.
self.state = WaitFor::Csi {
idx,
is_private,
saw_digit,
in_intermediate: true,
};
true
}
// Parameter bytes (0x30..=0x3F).
0x30..=0x3f if !in_intermediate => {
match byte {
// digits: contribute to numeric params
b'0'..=b'9' => {
let i = idx as usize;
if i < MAX_PARAMS {
let p = &mut self.params[i];
*p = Some(
p.unwrap_or(0)
.saturating_mul(10)
.saturating_add((byte - b'0') as u32),
);
}
self.state = WaitFor::Csi {
idx,
is_private,
saw_digit: true,
in_intermediate: false,
};
}
// ';' separates numeric parameters.
b';' => {
let next = idx.saturating_add(1);
if (next as usize) < MAX_PARAMS {
// If there were no digits for this param, it already stays None.
self.state = WaitFor::Csi {
idx: next,
is_private,
saw_digit: false,
in_intermediate: false,
};
} else {
// There are too many parameters. We cannot handle that many, so we are aborting
// the ANSI escape sequence.
self.state = WaitFor::Escape;
}
}
b':' => {
// The behavior of ':' is not defined by the standard.
log::warn!("EscapeFsm: unsupported ':' parameter separator in CSI");
}
// Sequences containing <=>? are "private". We swallow them and mark `is_private`.
b'<' | b'=' | b'>' | b'?' => {
self.state = WaitFor::Csi {
idx,
is_private: true,
saw_digit,
in_intermediate: false,
};
}
_ => unreachable!(),
}
true
}
// Parameter bytes after intermediate section is illegal by the formal grammar.
// We'll abort and swallow to avoid leaking garbage.
0x30..=0x3f if in_intermediate => {
self.state = WaitFor::Escape;
true
}
// Final byte (0x40..=0x7E): ends the CSI.
0x40..=0x7e => {
self.state = WaitFor::Escape;
let num_params = (idx as usize).saturating_add(1).min(MAX_PARAMS);
self.dispatch_csi(byte, num_params, is_private, op);
true
}
_ => {
// Terminal behavior is undefined if a CSI contains bytes outside 0x20..=0x7E.
log::warn!("EscapeFsm: invalid byte {:#x} in CSI sequence", byte);
self.state = WaitFor::Escape;
true
}
}
}
}
}
/// Gets the parameter at the given index, or returns the default value if the parameter is not present.
fn param_or(&self, i: usize, default: u32) -> u32 {
self.params.get(i).and_then(|p| *p).unwrap_or(default)
}
fn dispatch_csi<T: EscapeOp>(
&self,
final_byte: u8,
num_params: usize,
is_private: bool,
op: &mut T,
) {
if is_private {
// For now we don't handle any private sequences, so just swallow them.
return;
}
match final_byte {
// CUP - Cursor Position: CSI n ; m H
//
// - n=row, m=col
// - default to 1 if omitted
//
// Examples:
// - CSI H -> 1;1
// - CSI ;5H -> 1;5
// - CSI 17H -> 17;1
// - CSI 17;H -> 17;1
// - CSI 17;1H -> 17;1
b'H' => {
let row_1b = self.param_or(0, 1);
let col_1b = self.param_or(1, 1);
op.set_cursor((col_1b - 1) as usize, (row_1b - 1) as usize);
}
// ED - Erase in Display: CSI n J
//
// n:
// - 0 (or missing): cursor to end of screen
// - 1: cursor to beginning of screen
// - 2: entire screen
// - 3: entire screen + scrollback
b'J' => {
let n = self.param_or(0, 0);
let mode = match n {
0 => EraseInDisplay::CursorToEnd,
1 => EraseInDisplay::CursorToBeginning,
2 => EraseInDisplay::EntireScreen,
3 => EraseInDisplay::EntireScreenAndScrollback,
_ => {
// Invalid parameter.
return;
}
};
op.erase_in_display(mode);
}
// SGR - Select Graphic Rendition
b'm' => self.handle_srg(num_params, op),
b'm' => self.handle_sgr(num_params, op),
// Invalid or unsupported
// Unknown CSI: swallow silently.
_ => {}
}
true
}
/// Handles the "Select Graphic Rendition" sequence.
fn handle_srg<T: EscapeOp>(&self, num_params: usize, op: &mut T) {
fn handle_sgr<T: EscapeOp>(&self, num_params: usize, op: &mut T) {
let mut cursor = 0;
while cursor < num_params {
let op_code = self.params[cursor];
let op_code = self.param_or(cursor, 0) as u8;
cursor += 1;
match op_code {
@ -167,15 +370,15 @@ impl EscapeFsm {
// Set foreground colors
// Reference: <https://en.wikipedia.org/wiki/ANSI_escape_code#Colors>
30..=37 => op.set_fg_color(COLORS[op_code as usize - 30]),
38 if num_params - cursor >= 2 && self.params[cursor] == 5 => {
op.set_fg_color(Self::get_256_color(self.params[cursor + 1] as u8));
38 if num_params - cursor >= 2 && self.param_or(cursor, 0) == 5 => {
op.set_fg_color(Self::get_256_color(self.param_or(cursor + 1, 0) as u8));
cursor += 2;
}
38 if num_params - cursor >= 4 && self.params[cursor] == 2 => {
38 if num_params - cursor >= 4 && self.param_or(cursor, 0) == 2 => {
op.set_fg_color(Pixel {
red: self.params[cursor + 1] as u8,
green: self.params[cursor + 2] as u8,
blue: self.params[cursor + 3] as u8,
red: self.param_or(cursor + 1, 0) as u8,
green: self.param_or(cursor + 2, 0) as u8,
blue: self.param_or(cursor + 3, 0) as u8,
});
cursor += 4;
}
@ -186,15 +389,15 @@ impl EscapeFsm {
// Set background colors
// Reference: <https://en.wikipedia.org/wiki/ANSI_escape_code#Colors>
40..=47 => op.set_bg_color(COLORS[op_code as usize - 40]),
48 if num_params - cursor >= 2 && self.params[cursor] == 5 => {
op.set_bg_color(Self::get_256_color(self.params[cursor + 1] as u8));
48 if num_params - cursor >= 2 && self.param_or(cursor, 0) == 5 => {
op.set_bg_color(Self::get_256_color(self.param_or(cursor + 1, 0) as u8));
cursor += 2;
}
48 if num_params - cursor >= 4 && self.params[cursor] == 2 => {
48 if num_params - cursor >= 4 && self.param_or(cursor, 0) == 2 => {
op.set_bg_color(Pixel {
red: self.params[cursor + 1] as u8,
green: self.params[cursor + 2] as u8,
blue: self.params[cursor + 3] as u8,
red: self.param_or(cursor + 1, 0) as u8,
green: self.param_or(cursor + 2, 0) as u8,
blue: self.param_or(cursor + 3, 0) as u8,
});
cursor += 4;
}
@ -260,6 +463,7 @@ mod test {
y: usize,
fg: Pixel,
bg: Pixel,
last_ed: Option<EraseInDisplay>,
}
impl Default for State {
@ -269,6 +473,7 @@ mod test {
y: 0,
fg: Pixel::WHITE,
bg: Pixel::BLACK,
last_ed: None,
}
}
}
@ -286,6 +491,10 @@ mod test {
fn set_bg_color(&mut self, val: Pixel) {
self.bg = val;
}
fn erase_in_display(&mut self, mode: EraseInDisplay) {
self.last_ed = Some(mode);
}
}
fn eat_escape_sequence(esc_fsm: &mut EscapeFsm, state: &mut State, bytes: &[u8]) {
@ -306,15 +515,38 @@ mod test {
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");
// CUP defaults to 1;1 when omitted.
eat_escape_sequence(&mut esc_fsm, &mut state, b"\x1B[H");
assert_eq!(state.x, 0);
assert_eq!(state.y, 0);
assert!(!esc_fsm.eat(b'a', &mut state));
}
#[ktest]
fn erase_in_display() {
let mut esc_fsm = EscapeFsm::new();
let mut state = State::default();
// Default (or missing) is 0: cursor to end of screen.
eat_escape_sequence(&mut esc_fsm, &mut state, b"\x1B[J");
assert_eq!(state.last_ed, Some(EraseInDisplay::CursorToEnd));
eat_escape_sequence(&mut esc_fsm, &mut state, b"\x1B[1J");
assert_eq!(state.last_ed, Some(EraseInDisplay::CursorToBeginning));
eat_escape_sequence(&mut esc_fsm, &mut state, b"\x1B[2J");
assert_eq!(state.last_ed, Some(EraseInDisplay::EntireScreen));
eat_escape_sequence(&mut esc_fsm, &mut state, b"\x1B[3J");
assert_eq!(
state.last_ed,
Some(EraseInDisplay::EntireScreenAndScrollback)
);
assert!(!esc_fsm.eat(b'a', &mut state));
}
#[ktest]
fn set_color() {
let mut esc_fsm = EscapeFsm::new();

View File

@ -2,167 +2,63 @@
use alloc::{sync::Arc, vec::Vec};
use aster_console::{
AnyConsoleDevice, ConsoleCallback, ConsoleSetFontError,
font::BitmapFont,
mode::{ConsoleMode, KeyboardMode},
};
use ostd::{
mm::{HasSize, VmReader},
sync::{LocalIrqDisabled, SpinLock},
};
use spin::Once;
use aster_console::{ConsoleSetFontError, font::BitmapFont, mode::ConsoleMode};
use ostd::mm::HasSize;
use crate::{
FRAMEBUFFER, FrameBuffer, Pixel,
ansi_escape::{EscapeFsm, EscapeOp},
FrameBuffer, Pixel,
ansi_escape::{EraseInDisplay, EscapeOp},
};
/// A text console rendered onto the framebuffer.
pub struct FramebufferConsole {
callbacks: SpinLock<ConsoleCallbacks, LocalIrqDisabled>,
inner: SpinLock<(ConsoleState, EscapeFsm), LocalIrqDisabled>,
}
pub const CONSOLE_NAME: &str = "Framebuffer-Console";
pub static FRAMEBUFFER_CONSOLE: Once<Arc<FramebufferConsole>> = Once::new();
pub(crate) fn init() {
let Some(fb) = FRAMEBUFFER.get() else {
log::warn!("Framebuffer not initialized");
return;
};
FRAMEBUFFER_CONSOLE.call_once(|| Arc::new(FramebufferConsole::new(fb.clone())));
}
impl AnyConsoleDevice for FramebufferConsole {
fn send(&self, buf: &[u8]) {
let mut inner = self.inner.lock();
let (state, esc_fsm) = &mut *inner;
for byte in buf {
if esc_fsm.eat(*byte, state) {
// The character is part of an ANSI escape sequence.
continue;
}
if *byte == 0 {
// The character is a NUL character.
continue;
}
state.send_char(*byte);
}
}
fn register_callback(&self, callback: &'static ConsoleCallback) {
self.callbacks.lock().callbacks.push(callback);
}
fn set_font(&self, font: BitmapFont) -> Result<(), ConsoleSetFontError> {
self.inner.lock().0.set_font(font)
}
fn set_mode(&self, mode: ConsoleMode) -> bool {
self.inner.lock().0.set_mode(mode);
true
}
fn mode(&self) -> Option<ConsoleMode> {
Some(self.inner.lock().0.mode())
}
fn set_keyboard_mode(&self, mode: KeyboardMode) -> bool {
match mode {
KeyboardMode::Xlate => self.callbacks.lock().is_input_enabled = true,
KeyboardMode::Off => self.callbacks.lock().is_input_enabled = false,
_ => return false,
}
true
}
fn keyboard_mode(&self) -> Option<KeyboardMode> {
if self.callbacks.lock().is_input_enabled {
Some(KeyboardMode::Xlate)
} else {
Some(KeyboardMode::Off)
}
}
}
impl FramebufferConsole {
/// Creates a new framebuffer console.
pub(self) fn new(framebuffer: Arc<FrameBuffer>) -> Self {
let callbacks = ConsoleCallbacks {
callbacks: Vec::new(),
is_input_enabled: true,
};
let state = ConsoleState {
x_pos: 0,
y_pos: 0,
fg_color: Pixel::WHITE,
bg_color: Pixel::BLACK,
font: BitmapFont::new_basic8x8(),
is_output_enabled: true,
bytes: alloc::vec![0u8; framebuffer.io_mem().size()],
backend: framebuffer,
};
let esc_fsm = EscapeFsm::new();
Self {
callbacks: SpinLock::new(callbacks),
inner: SpinLock::new((state, esc_fsm)),
}
}
/// Triggers the registered input callbacks with the given data.
pub(crate) fn trigger_input_callbacks(&self, bytes: &[u8]) {
let callbacks = self.callbacks.lock();
if !callbacks.is_input_enabled {
return;
}
let reader = VmReader::from(bytes);
for callback in callbacks.callbacks.iter() {
callback(reader.clone());
}
}
}
impl core::fmt::Debug for FramebufferConsole {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("FramebufferConsole").finish_non_exhaustive()
}
}
struct ConsoleCallbacks {
callbacks: Vec<&'static ConsoleCallback>,
/// Whether the input characters will be handled by the callbacks.
is_input_enabled: bool,
}
#[derive(Debug)]
struct ConsoleState {
pub struct ConsoleState {
x_pos: usize,
y_pos: usize,
fg_color: Pixel,
bg_color: Pixel,
font: BitmapFont,
/// Whether the output characters will be drawn in the framebuffer.
is_output_enabled: bool,
is_rendering_enabled: bool,
mode: ConsoleMode,
bytes: Vec<u8>,
backend: Arc<FrameBuffer>,
}
impl ConsoleState {
pub fn new(backend: Arc<FrameBuffer>) -> Self {
let buffer_size = backend.io_mem().size();
Self {
x_pos: 0,
y_pos: 0,
fg_color: Pixel::WHITE,
bg_color: Pixel::BLACK,
font: BitmapFont::new_basic8x8(),
is_rendering_enabled: false,
mode: ConsoleMode::Text,
bytes: alloc::vec![0; buffer_size],
backend,
}
}
/// Enables rendering to the framebuffer.
pub fn enable_rendering(&mut self) {
self.is_rendering_enabled = true;
}
/// Disables rendering to the framebuffer.
pub fn disable_rendering(&mut self) {
self.is_rendering_enabled = false;
}
/// Flushes the entire console buffer to the framebuffer.
pub fn flush_fullscreen(&self) {
if self.is_rendering_enabled && self.mode == ConsoleMode::Text {
self.backend.write_bytes_at(0, &self.bytes).unwrap();
}
}
/// Sends a single character to be drawn on the framebuffer.
pub(self) fn send_char(&mut self, ch: u8) {
pub fn send_char(&mut self, ch: u8) {
if ch == b'\n' {
self.newline();
return;
@ -197,7 +93,7 @@ impl ConsoleState {
self.bytes.copy_within(offset.., 0);
self.bytes[self.backend.io_mem().size() - offset..].fill(0);
if self.is_output_enabled {
if self.is_rendering_enabled && self.mode == ConsoleMode::Text {
self.backend.write_bytes_at(0, &self.bytes).unwrap();
}
@ -243,7 +139,7 @@ impl ConsoleState {
}
// Write pixels to the framebuffer.
if self.is_output_enabled {
if self.is_rendering_enabled && self.mode == ConsoleMode::Text {
self.backend.write_bytes_at(off_st, render_buf).unwrap();
}
@ -252,7 +148,7 @@ impl ConsoleState {
}
/// Sets the font for the framebuffer console.
pub(self) fn set_font(&mut self, font: BitmapFont) -> Result<(), ConsoleSetFontError> {
pub fn set_font(&mut self, font: BitmapFont) -> Result<(), ConsoleSetFontError> {
// Note that the font height cannot exceed the half the height of the framebuffer.
// Otherwise, `shift_lines_up` will underflow `x_pos`.
if font.width() > self.backend.width() || font.height() > self.backend.height() / 2 {
@ -269,29 +165,66 @@ impl ConsoleState {
}
/// Sets the console mode (text or graphics).
pub(self) fn set_mode(&mut self, mode: ConsoleMode) {
if mode == ConsoleMode::Graphics {
self.is_output_enabled = false;
pub fn set_mode(&mut self, mode: ConsoleMode) {
if self.mode == mode {
return;
}
if self.is_output_enabled {
return;
}
// We're switching from the graphics mode back to the text mode. The characters need to be
// redrawn in the framebuffer.
self.is_output_enabled = true;
self.backend.write_bytes_at(0, &self.bytes).unwrap();
self.mode = mode;
}
/// Gets the current console mode.
pub(self) fn mode(&self) -> ConsoleMode {
if self.is_output_enabled {
ConsoleMode::Text
} else {
ConsoleMode::Graphics
pub fn mode(&self) -> ConsoleMode {
self.mode
}
/// Fill a rectangular pixel region [x0, x1) × [y0, y1) with the given color.
///
/// This writes to the shadow buffer, and optionally flushes
/// the affected region to the real framebuffer.
fn fill_rect_pixels(&mut self, x0: usize, y0: usize, x1: usize, y1: usize, color: Pixel) {
if x0 >= x1 || y0 >= y1 {
return;
}
let w = self.backend.width();
let h = self.backend.height();
let x0 = x0.min(w);
let x1 = x1.min(w);
let y0 = y0.min(h);
let y1 = y1.min(h);
if x0 >= x1 || y0 >= y1 {
return;
}
let rendered_pixel = self.backend.render_pixel(color);
let rendered_pixel_size = rendered_pixel.nbytes();
let row_bytes = (x1 - x0) * rendered_pixel_size;
// Fill shadow buffer row by row.
for y in y0..y1 {
let off = self.backend.calc_offset(x0, y).as_usize();
let buf = &mut self.bytes[off..off + row_bytes];
// Write pixels.
for chunk in buf.chunks_exact_mut(rendered_pixel_size) {
chunk.copy_from_slice(rendered_pixel.as_slice());
}
// Flush to framebuffer if needed.
if self.is_rendering_enabled && self.mode == ConsoleMode::Text {
self.backend.write_bytes_at(off, buf).unwrap();
}
}
}
/// Pixel coordinates for the cursor cell.
fn cursor_cell_rect(&self) -> (usize, usize, usize, usize) {
let cx = self.x_pos.min(self.backend.width());
let cy = self.y_pos.min(self.backend.height());
let x1 = (cx + self.font.width()).min(self.backend.width());
let y1 = (cy + self.font.height()).min(self.backend.height());
(cx, cy, x1, y1)
}
}
@ -313,4 +246,38 @@ impl EscapeOp for ConsoleState {
fn set_bg_color(&mut self, val: Pixel) {
self.bg_color = val;
}
fn erase_in_display(&mut self, mode: EraseInDisplay) {
let bg = self.bg_color;
let w = self.backend.width();
let h = self.backend.height();
let (cx0, cy0, cx1, cy1) = self.cursor_cell_rect();
match mode {
EraseInDisplay::CursorToEnd => {
// Clear from cursor x to end-of-line, within the cursor row.
self.fill_rect_pixels(cx0, cy0, w, cy1, bg);
// Clear all rows below cursor row.
if cy1 < h {
self.fill_rect_pixels(0, cy1, w, h, bg);
}
}
EraseInDisplay::CursorToBeginning => {
// Clear all rows above cursor row.
if cy0 > 0 {
self.fill_rect_pixels(0, 0, w, cy0, bg);
}
// Clear from start-of-line to cursor cell end, within cursor row.
self.fill_rect_pixels(0, cy0, cx1, cy1, bg);
}
EraseInDisplay::EntireScreen | EraseInDisplay::EntireScreenAndScrollback => {
self.fill_rect_pixels(0, 0, w, h, bg);
}
}
}
}

View File

@ -8,13 +8,13 @@ extern crate alloc;
mod ansi_escape;
mod console;
mod console_input;
mod dummy_console;
mod framebuffer;
mod pixel;
pub use ansi_escape::{EscapeFsm, EscapeOp};
use component::{ComponentInitError, init_component};
pub use console::{CONSOLE_NAME, FRAMEBUFFER_CONSOLE, FramebufferConsole};
pub use console::ConsoleState;
pub use dummy_console::DummyFramebufferConsole;
pub use framebuffer::{ColorMapEntry, FRAMEBUFFER, FrameBuffer, MAX_CMAP_SIZE};
pub use pixel::{Pixel, PixelFormat, RenderedPixel};
@ -22,7 +22,5 @@ pub use pixel::{Pixel, PixelFormat, RenderedPixel};
#[init_component]
fn init() -> Result<(), ComponentInitError> {
framebuffer::init();
console::init();
console_input::init();
Ok(())
}