use std::io;
use std::str;
use log::{debug, trace};
use serde::{Deserialize, Serialize};
use crate::index::{Column, Line};
use crate::term::color::Rgb;
fn xparse_color(color: &[u8]) -> Option<Rgb> {
if !color.is_empty() && color[0] == b'#' {
parse_legacy_color(&color[1..])
} else if color.len() >= 4 && &color[..4] == b"rgb:" {
parse_rgb_color(&color[4..])
} else {
None
}
}
fn parse_rgb_color(color: &[u8]) -> Option<Rgb> {
let colors = str::from_utf8(color).ok()?.split('/').collect::<Vec<_>>();
if colors.len() != 3 {
return None;
}
let scale = |input: &str| {
let max = u32::pow(16, input.len() as u32) - 1;
let value = u32::from_str_radix(input, 16).ok()?;
Some((255 * value / max) as u8)
};
Some(Rgb { r: scale(colors[0])?, g: scale(colors[1])?, b: scale(colors[2])? })
}
fn parse_legacy_color(color: &[u8]) -> Option<Rgb> {
let item_len = color.len() / 3;
let color_from_slice = |slice: &[u8]| {
let col = usize::from_str_radix(str::from_utf8(slice).ok()?, 16).ok()? << 4;
Some((col >> (4 * slice.len().saturating_sub(1))) as u8)
};
Some(Rgb {
r: color_from_slice(&color[0..item_len])?,
g: color_from_slice(&color[item_len..item_len * 2])?,
b: color_from_slice(&color[item_len * 2..])?,
})
}
fn parse_number(input: &[u8]) -> Option<u8> {
if input.is_empty() {
return None;
}
let mut num: u8 = 0;
for c in input {
let c = *c as char;
if let Some(digit) = c.to_digit(10) {
num = match num.checked_mul(10).and_then(|v| v.checked_add(digit as u8)) {
Some(v) => v,
None => return None,
}
} else {
return None;
}
}
Some(num)
}
pub struct Processor {
state: ProcessorState,
parser: vte::Parser,
}
struct ProcessorState {
preceding_char: Option<char>,
}
struct Performer<'a, H: Handler + TermInfo, W: io::Write> {
state: &'a mut ProcessorState,
handler: &'a mut H,
writer: &'a mut W,
}
impl<'a, H: Handler + TermInfo + 'a, W: io::Write> Performer<'a, H, W> {
#[inline]
pub fn new<'b>(
state: &'b mut ProcessorState,
handler: &'b mut H,
writer: &'b mut W,
) -> Performer<'b, H, W> {
Performer { state, handler, writer }
}
}
impl Default for Processor {
fn default() -> Processor {
Processor { state: ProcessorState { preceding_char: None }, parser: vte::Parser::new() }
}
}
impl Processor {
pub fn new() -> Processor {
Default::default()
}
#[inline]
pub fn advance<H, W>(&mut self, handler: &mut H, byte: u8, writer: &mut W)
where
H: Handler + TermInfo,
W: io::Write,
{
let mut performer = Performer::new(&mut self.state, handler, writer);
self.parser.advance(&mut performer, byte);
}
}
pub trait TermInfo {
fn lines(&self) -> Line;
fn cols(&self) -> Column;
}
pub trait Handler {
fn set_title(&mut self, _: &str) {}
fn set_cursor_style(&mut self, _: Option<CursorStyle>) {}
fn input(&mut self, _c: char) {}
fn goto(&mut self, _: Line, _: Column) {}
fn goto_line(&mut self, _: Line) {}
fn goto_col(&mut self, _: Column) {}
fn insert_blank(&mut self, _: Column) {}
fn move_up(&mut self, _: Line) {}
fn move_down(&mut self, _: Line) {}
fn identify_terminal<W: io::Write>(&mut self, _: &mut W) {}
fn device_status<W: io::Write>(&mut self, _: &mut W, _: usize) {}
fn move_forward(&mut self, _: Column) {}
fn move_backward(&mut self, _: Column) {}
fn move_down_and_cr(&mut self, _: Line) {}
fn move_up_and_cr(&mut self, _: Line) {}
fn put_tab(&mut self, _count: i64) {}
fn backspace(&mut self) {}
fn carriage_return(&mut self) {}
fn linefeed(&mut self) {}
fn bell(&mut self) {}
fn substitute(&mut self) {}
fn newline(&mut self) {}
fn set_horizontal_tabstop(&mut self) {}
fn scroll_up(&mut self, _: Line) {}
fn scroll_down(&mut self, _: Line) {}
fn insert_blank_lines(&mut self, _: Line) {}
fn delete_lines(&mut self, _: Line) {}
fn erase_chars(&mut self, _: Column) {}
fn delete_chars(&mut self, _: Column) {}
fn move_backward_tabs(&mut self, _count: i64) {}
fn move_forward_tabs(&mut self, _count: i64) {}
fn save_cursor_position(&mut self) {}
fn restore_cursor_position(&mut self) {}
fn clear_line(&mut self, _mode: LineClearMode) {}
fn clear_screen(&mut self, _mode: ClearMode) {}
fn clear_tabs(&mut self, _mode: TabulationClearMode) {}
fn reset_state(&mut self) {}
fn reverse_index(&mut self) {}
fn terminal_attribute(&mut self, _attr: Attr) {}
fn set_mode(&mut self, _mode: Mode) {}
fn unset_mode(&mut self, _: Mode) {}
fn set_scrolling_region(&mut self, _top: usize, _bottom: usize) {}
fn set_keypad_application_mode(&mut self) {}
fn unset_keypad_application_mode(&mut self) {}
fn set_active_charset(&mut self, _: CharsetIndex) {}
fn configure_charset(&mut self, _: CharsetIndex, _: StandardCharset) {}
fn set_color(&mut self, _: usize, _: Rgb) {}
fn dynamic_color_sequence<W: io::Write>(&mut self, _: &mut W, _: u8, _: usize) {}
fn reset_color(&mut self, _: usize) {}
fn set_clipboard(&mut self, _: u8, _: &[u8]) {}
fn write_clipboard<W: io::Write>(&mut self, _: u8, _: &mut W) {}
fn decaln(&mut self) {}
fn push_title(&mut self) {}
fn pop_title(&mut self) {}
}
#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash, Deserialize)]
pub enum CursorStyle {
Block,
Underline,
Beam,
HollowBlock,
Hidden,
}
impl Default for CursorStyle {
fn default() -> CursorStyle {
CursorStyle::Block
}
}
#[derive(Debug, Eq, PartialEq)]
pub enum Mode {
CursorKeys = 1,
DECCOLM = 3,
Insert = 4,
Origin = 6,
LineWrap = 7,
BlinkingCursor = 12,
LineFeedNewLine = 20,
ShowCursor = 25,
ReportMouseClicks = 1000,
ReportCellMouseMotion = 1002,
ReportAllMouseMotion = 1003,
ReportFocusInOut = 1004,
Utf8Mouse = 1005,
SgrMouse = 1006,
AlternateScroll = 1007,
SwapScreenAndSetRestoreCursor = 1049,
BracketedPaste = 2004,
}
impl Mode {
pub fn from_primitive(intermediate: Option<&u8>, num: i64) -> Option<Mode> {
let private = match intermediate {
Some(b'?') => true,
None => false,
_ => return None,
};
if private {
Some(match num {
1 => Mode::CursorKeys,
3 => Mode::DECCOLM,
6 => Mode::Origin,
7 => Mode::LineWrap,
12 => Mode::BlinkingCursor,
25 => Mode::ShowCursor,
1000 => Mode::ReportMouseClicks,
1002 => Mode::ReportCellMouseMotion,
1003 => Mode::ReportAllMouseMotion,
1004 => Mode::ReportFocusInOut,
1005 => Mode::Utf8Mouse,
1006 => Mode::SgrMouse,
1007 => Mode::AlternateScroll,
1049 => Mode::SwapScreenAndSetRestoreCursor,
2004 => Mode::BracketedPaste,
_ => {
trace!("[unimplemented] primitive mode: {}", num);
return None;
},
})
} else {
Some(match num {
4 => Mode::Insert,
20 => Mode::LineFeedNewLine,
_ => return None,
})
}
}
}
#[derive(Debug)]
pub enum LineClearMode {
Right,
Left,
All,
}
#[derive(Debug)]
pub enum ClearMode {
Below,
Above,
All,
Saved,
}
#[derive(Debug)]
pub enum TabulationClearMode {
Current,
All,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum NamedColor {
Black = 0,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
BrightBlack,
BrightRed,
BrightGreen,
BrightYellow,
BrightBlue,
BrightMagenta,
BrightCyan,
BrightWhite,
Foreground = 256,
Background,
Cursor,
DimBlack,
DimRed,
DimGreen,
DimYellow,
DimBlue,
DimMagenta,
DimCyan,
DimWhite,
BrightForeground,
DimForeground,
}
impl NamedColor {
pub fn to_bright(self) -> Self {
match self {
NamedColor::Foreground => NamedColor::BrightForeground,
NamedColor::Black => NamedColor::BrightBlack,
NamedColor::Red => NamedColor::BrightRed,
NamedColor::Green => NamedColor::BrightGreen,
NamedColor::Yellow => NamedColor::BrightYellow,
NamedColor::Blue => NamedColor::BrightBlue,
NamedColor::Magenta => NamedColor::BrightMagenta,
NamedColor::Cyan => NamedColor::BrightCyan,
NamedColor::White => NamedColor::BrightWhite,
NamedColor::DimForeground => NamedColor::Foreground,
NamedColor::DimBlack => NamedColor::Black,
NamedColor::DimRed => NamedColor::Red,
NamedColor::DimGreen => NamedColor::Green,
NamedColor::DimYellow => NamedColor::Yellow,
NamedColor::DimBlue => NamedColor::Blue,
NamedColor::DimMagenta => NamedColor::Magenta,
NamedColor::DimCyan => NamedColor::Cyan,
NamedColor::DimWhite => NamedColor::White,
val => val,
}
}
pub fn to_dim(self) -> Self {
match self {
NamedColor::Black => NamedColor::DimBlack,
NamedColor::Red => NamedColor::DimRed,
NamedColor::Green => NamedColor::DimGreen,
NamedColor::Yellow => NamedColor::DimYellow,
NamedColor::Blue => NamedColor::DimBlue,
NamedColor::Magenta => NamedColor::DimMagenta,
NamedColor::Cyan => NamedColor::DimCyan,
NamedColor::White => NamedColor::DimWhite,
NamedColor::Foreground => NamedColor::DimForeground,
NamedColor::BrightBlack => NamedColor::Black,
NamedColor::BrightRed => NamedColor::Red,
NamedColor::BrightGreen => NamedColor::Green,
NamedColor::BrightYellow => NamedColor::Yellow,
NamedColor::BrightBlue => NamedColor::Blue,
NamedColor::BrightMagenta => NamedColor::Magenta,
NamedColor::BrightCyan => NamedColor::Cyan,
NamedColor::BrightWhite => NamedColor::White,
NamedColor::BrightForeground => NamedColor::Foreground,
val => val,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Color {
Named(NamedColor),
Spec(Rgb),
Indexed(u8),
}
#[derive(Debug, Eq, PartialEq)]
pub enum Attr {
Reset,
Bold,
Dim,
Italic,
Underline,
BlinkSlow,
BlinkFast,
Reverse,
Hidden,
Strike,
CancelBold,
CancelBoldDim,
CancelItalic,
CancelUnderline,
CancelBlink,
CancelReverse,
CancelHidden,
CancelStrike,
Foreground(Color),
Background(Color),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CharsetIndex {
G0,
G1,
G2,
G3,
}
impl Default for CharsetIndex {
fn default() -> Self {
CharsetIndex::G0
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum StandardCharset {
Ascii,
SpecialCharacterAndLineDrawing,
}
impl Default for StandardCharset {
fn default() -> Self {
StandardCharset::Ascii
}
}
impl<'a, H, W> vte::Perform for Performer<'a, H, W>
where
H: Handler + TermInfo + 'a,
W: io::Write + 'a,
{
#[inline]
fn print(&mut self, c: char) {
self.handler.input(c);
self.state.preceding_char = Some(c);
}
#[inline]
fn execute(&mut self, byte: u8) {
match byte {
C0::HT => self.handler.put_tab(1),
C0::BS => self.handler.backspace(),
C0::CR => self.handler.carriage_return(),
C0::LF | C0::VT | C0::FF => self.handler.linefeed(),
C0::BEL => self.handler.bell(),
C0::SUB => self.handler.substitute(),
C0::SI => self.handler.set_active_charset(CharsetIndex::G0),
C0::SO => self.handler.set_active_charset(CharsetIndex::G1),
_ => debug!("[unhandled] execute byte={:02x}", byte),
}
}
#[inline]
fn hook(&mut self, params: &[i64], intermediates: &[u8], ignore: bool) {
debug!(
"[unhandled hook] params={:?}, ints: {:?}, ignore: {:?}",
params, intermediates, ignore
);
}
#[inline]
fn put(&mut self, byte: u8) {
debug!("[unhandled put] byte={:?}", byte);
}
#[inline]
fn unhook(&mut self) {
debug!("[unhandled unhook]");
}
#[inline]
fn osc_dispatch(&mut self, params: &[&[u8]]) {
let writer = &mut self.writer;
fn unhandled(params: &[&[u8]]) {
let mut buf = String::new();
for items in params {
buf.push_str("[");
for item in *items {
buf.push_str(&format!("{:?},", *item as char));
}
buf.push_str("],");
}
debug!("[unhandled osc_dispatch]: [{}] at line {}", &buf, line!());
}
if params.is_empty() || params[0].is_empty() {
return;
}
match params[0] {
b"0" | b"2" => {
if params.len() >= 2 {
let title = params[1..]
.iter()
.flat_map(|x| str::from_utf8(x))
.collect::<Vec<&str>>()
.join(";");
self.handler.set_title(&title);
return;
}
unhandled(params);
},
b"1" => (),
b"4" => {
if params.len() > 1 && params.len() % 2 != 0 {
for chunk in params[1..].chunks(2) {
let index = parse_number(chunk[0]);
let color = xparse_color(chunk[1]);
if let (Some(i), Some(c)) = (index, color) {
self.handler.set_color(i as usize, c);
return;
}
}
}
unhandled(params);
},
b"10" | b"11" | b"12" => {
if params.len() >= 2 {
if let Some(mut dynamic_code) = parse_number(params[0]) {
for param in ¶ms[1..] {
let offset = dynamic_code as usize - 10;
let index = NamedColor::Foreground as usize + offset;
if index > NamedColor::Cursor as usize {
unhandled(params);
break;
}
if let Some(color) = xparse_color(param) {
self.handler.set_color(index, color);
} else if param == b"?" {
self.handler.dynamic_color_sequence(writer, dynamic_code, index);
} else {
unhandled(params);
}
dynamic_code += 1;
}
return;
}
}
unhandled(params);
},
b"50" => {
if params.len() >= 2
&& params[1].len() >= 13
&& params[1][0..12] == *b"CursorShape="
{
let style = match params[1][12] as char {
'0' => CursorStyle::Block,
'1' => CursorStyle::Beam,
'2' => CursorStyle::Underline,
_ => return unhandled(params),
};
self.handler.set_cursor_style(Some(style));
return;
}
unhandled(params);
},
b"52" => {
if params.len() < 3 {
return unhandled(params);
}
let clipboard = params[1].get(0).unwrap_or(&b'c');
match params[2] {
b"?" => self.handler.write_clipboard(*clipboard, writer),
base64 => self.handler.set_clipboard(*clipboard, base64),
}
},
b"104" => {
if params.len() == 1 {
for i in 0..256 {
self.handler.reset_color(i);
}
return;
}
for param in ¶ms[1..] {
match parse_number(param) {
Some(index) => self.handler.reset_color(index as usize),
None => unhandled(params),
}
}
},
b"110" => self.handler.reset_color(NamedColor::Foreground as usize),
b"111" => self.handler.reset_color(NamedColor::Background as usize),
b"112" => self.handler.reset_color(NamedColor::Cursor as usize),
_ => unhandled(params),
}
}
#[inline]
fn csi_dispatch(
&mut self,
args: &[i64],
intermediates: &[u8],
has_ignored_intermediates: bool,
action: char,
) {
macro_rules! unhandled {
() => {{
debug!(
"[Unhandled CSI] action={:?}, args={:?}, intermediates={:?}",
action, args, intermediates
);
}};
}
macro_rules! arg_or_default {
(idx: $idx:expr, default: $default:expr) => {
args.get($idx)
.and_then(|v| if *v == 0 { None } else { Some(*v) })
.unwrap_or($default)
};
}
if has_ignored_intermediates || intermediates.len() > 1 {
unhandled!();
return;
}
let handler = &mut self.handler;
let writer = &mut self.writer;
match (action, intermediates.get(0)) {
('@', None) => {
handler.insert_blank(Column(arg_or_default!(idx: 0, default: 1) as usize))
},
('A', None) => {
handler.move_up(Line(arg_or_default!(idx: 0, default: 1) as usize));
},
('b', None) => {
if let Some(c) = self.state.preceding_char {
for _ in 0..arg_or_default!(idx: 0, default: 1) {
handler.input(c);
}
} else {
debug!("tried to repeat with no preceding char");
}
},
('B', None) | ('e', None) => {
handler.move_down(Line(arg_or_default!(idx: 0, default: 1) as usize))
},
('c', None) if arg_or_default!(idx: 0, default: 0) == 0 => {
handler.identify_terminal(writer)
},
('C', None) | ('a', None) => {
handler.move_forward(Column(arg_or_default!(idx: 0, default: 1) as usize))
},
('D', None) => {
handler.move_backward(Column(arg_or_default!(idx: 0, default: 1) as usize))
},
('E', None) => {
handler.move_down_and_cr(Line(arg_or_default!(idx: 0, default: 1) as usize))
},
('F', None) => {
handler.move_up_and_cr(Line(arg_or_default!(idx: 0, default: 1) as usize))
},
('g', None) => {
let mode = match arg_or_default!(idx: 0, default: 0) {
0 => TabulationClearMode::Current,
3 => TabulationClearMode::All,
_ => {
unhandled!();
return;
},
};
handler.clear_tabs(mode);
},
('G', None) | ('`', None) => {
handler.goto_col(Column(arg_or_default!(idx: 0, default: 1) as usize - 1))
},
('H', None) | ('f', None) => {
let y = arg_or_default!(idx: 0, default: 1) as usize;
let x = arg_or_default!(idx: 1, default: 1) as usize;
handler.goto(Line(y - 1), Column(x - 1));
},
('I', None) => handler.move_forward_tabs(arg_or_default!(idx: 0, default: 1)),
('J', None) => {
let mode = match arg_or_default!(idx: 0, default: 0) {
0 => ClearMode::Below,
1 => ClearMode::Above,
2 => ClearMode::All,
3 => ClearMode::Saved,
_ => {
unhandled!();
return;
},
};
handler.clear_screen(mode);
},
('K', None) => {
let mode = match arg_or_default!(idx: 0, default: 0) {
0 => LineClearMode::Right,
1 => LineClearMode::Left,
2 => LineClearMode::All,
_ => {
unhandled!();
return;
},
};
handler.clear_line(mode);
},
('S', None) => handler.scroll_up(Line(arg_or_default!(idx: 0, default: 1) as usize)),
('t', None) => match arg_or_default!(idx: 0, default: 1) as usize {
22 => handler.push_title(),
23 => handler.pop_title(),
_ => unhandled!(),
},
('T', None) => handler.scroll_down(Line(arg_or_default!(idx: 0, default: 1) as usize)),
('L', None) => {
handler.insert_blank_lines(Line(arg_or_default!(idx: 0, default: 1) as usize))
},
('l', intermediate) => {
for arg in args {
match Mode::from_primitive(intermediate, *arg) {
Some(mode) => handler.unset_mode(mode),
None => {
unhandled!();
return;
},
}
}
},
('M', None) => handler.delete_lines(Line(arg_or_default!(idx: 0, default: 1) as usize)),
('X', None) => {
handler.erase_chars(Column(arg_or_default!(idx: 0, default: 1) as usize))
},
('P', None) => {
handler.delete_chars(Column(arg_or_default!(idx: 0, default: 1) as usize))
},
('Z', None) => handler.move_backward_tabs(arg_or_default!(idx: 0, default: 1)),
('d', None) => {
handler.goto_line(Line(arg_or_default!(idx: 0, default: 1) as usize - 1))
},
('h', intermediate) => {
for arg in args {
match Mode::from_primitive(intermediate, *arg) {
Some(mode) => handler.set_mode(mode),
None => {
unhandled!();
return;
},
}
}
},
('m', None) => {
if args.is_empty() {
handler.terminal_attribute(Attr::Reset);
} else {
for attr in attrs_from_sgr_parameters(args) {
match attr {
Some(attr) => handler.terminal_attribute(attr),
None => {
unhandled!();
return;
},
}
}
}
},
('n', None) => {
handler.device_status(writer, arg_or_default!(idx: 0, default: 0) as usize)
},
('q', Some(b' ')) => {
let style = match arg_or_default!(idx: 0, default: 0) {
0 => None,
1 | 2 => Some(CursorStyle::Block),
3 | 4 => Some(CursorStyle::Underline),
5 | 6 => Some(CursorStyle::Beam),
_ => {
unhandled!();
return;
},
};
handler.set_cursor_style(style);
},
('r', None) => {
let top = arg_or_default!(idx: 0, default: 1) as usize;
let bottom = arg_or_default!(idx: 1, default: handler.lines().0 as _) as usize;
handler.set_scrolling_region(top, bottom);
},
('s', None) => handler.save_cursor_position(),
('u', None) => handler.restore_cursor_position(),
_ => unhandled!(),
}
}
#[inline]
fn esc_dispatch(&mut self, params: &[i64], intermediates: &[u8], _ignore: bool, byte: u8) {
macro_rules! unhandled {
() => {{
debug!(
"[unhandled] esc_dispatch params={:?}, ints={:?}, byte={:?} ({:02x})",
params, intermediates, byte as char, byte
);
}};
}
macro_rules! configure_charset {
($charset:path, $intermediate:expr) => {{
let index: CharsetIndex = match $intermediate {
Some(b'(') => CharsetIndex::G0,
Some(b')') => CharsetIndex::G1,
Some(b'*') => CharsetIndex::G2,
Some(b'+') => CharsetIndex::G3,
_ => {
unhandled!();
return;
},
};
self.handler.configure_charset(index, $charset)
}};
}
match (byte, intermediates.get(0)) {
(b'B', intermediate) => configure_charset!(StandardCharset::Ascii, intermediate),
(b'D', None) => self.handler.linefeed(),
(b'E', None) => {
self.handler.linefeed();
self.handler.carriage_return();
},
(b'H', None) => self.handler.set_horizontal_tabstop(),
(b'M', None) => self.handler.reverse_index(),
(b'Z', None) => self.handler.identify_terminal(self.writer),
(b'c', None) => self.handler.reset_state(),
(b'0', intermediate) => {
configure_charset!(StandardCharset::SpecialCharacterAndLineDrawing, intermediate)
},
(b'7', None) => self.handler.save_cursor_position(),
(b'8', Some(b'#')) => self.handler.decaln(),
(b'8', None) => self.handler.restore_cursor_position(),
(b'=', None) => self.handler.set_keypad_application_mode(),
(b'>', None) => self.handler.unset_keypad_application_mode(),
(b'\\', None) => (),
_ => unhandled!(),
}
}
}
fn attrs_from_sgr_parameters(parameters: &[i64]) -> Vec<Option<Attr>> {
let mut i = 0; let mut attrs = Vec::with_capacity(parameters.len());
loop {
if i >= parameters.len() {
break;
}
let attr = match parameters[i] {
0 => Some(Attr::Reset),
1 => Some(Attr::Bold),
2 => Some(Attr::Dim),
3 => Some(Attr::Italic),
4 => Some(Attr::Underline),
5 => Some(Attr::BlinkSlow),
6 => Some(Attr::BlinkFast),
7 => Some(Attr::Reverse),
8 => Some(Attr::Hidden),
9 => Some(Attr::Strike),
21 => Some(Attr::CancelBold),
22 => Some(Attr::CancelBoldDim),
23 => Some(Attr::CancelItalic),
24 => Some(Attr::CancelUnderline),
25 => Some(Attr::CancelBlink),
27 => Some(Attr::CancelReverse),
28 => Some(Attr::CancelHidden),
29 => Some(Attr::CancelStrike),
30 => Some(Attr::Foreground(Color::Named(NamedColor::Black))),
31 => Some(Attr::Foreground(Color::Named(NamedColor::Red))),
32 => Some(Attr::Foreground(Color::Named(NamedColor::Green))),
33 => Some(Attr::Foreground(Color::Named(NamedColor::Yellow))),
34 => Some(Attr::Foreground(Color::Named(NamedColor::Blue))),
35 => Some(Attr::Foreground(Color::Named(NamedColor::Magenta))),
36 => Some(Attr::Foreground(Color::Named(NamedColor::Cyan))),
37 => Some(Attr::Foreground(Color::Named(NamedColor::White))),
38 => {
let mut start = 0;
if let Some(color) = parse_sgr_color(¶meters[i..], &mut start) {
i += start;
Some(Attr::Foreground(color))
} else {
None
}
},
39 => Some(Attr::Foreground(Color::Named(NamedColor::Foreground))),
40 => Some(Attr::Background(Color::Named(NamedColor::Black))),
41 => Some(Attr::Background(Color::Named(NamedColor::Red))),
42 => Some(Attr::Background(Color::Named(NamedColor::Green))),
43 => Some(Attr::Background(Color::Named(NamedColor::Yellow))),
44 => Some(Attr::Background(Color::Named(NamedColor::Blue))),
45 => Some(Attr::Background(Color::Named(NamedColor::Magenta))),
46 => Some(Attr::Background(Color::Named(NamedColor::Cyan))),
47 => Some(Attr::Background(Color::Named(NamedColor::White))),
48 => {
let mut start = 0;
if let Some(color) = parse_sgr_color(¶meters[i..], &mut start) {
i += start;
Some(Attr::Background(color))
} else {
None
}
},
49 => Some(Attr::Background(Color::Named(NamedColor::Background))),
90 => Some(Attr::Foreground(Color::Named(NamedColor::BrightBlack))),
91 => Some(Attr::Foreground(Color::Named(NamedColor::BrightRed))),
92 => Some(Attr::Foreground(Color::Named(NamedColor::BrightGreen))),
93 => Some(Attr::Foreground(Color::Named(NamedColor::BrightYellow))),
94 => Some(Attr::Foreground(Color::Named(NamedColor::BrightBlue))),
95 => Some(Attr::Foreground(Color::Named(NamedColor::BrightMagenta))),
96 => Some(Attr::Foreground(Color::Named(NamedColor::BrightCyan))),
97 => Some(Attr::Foreground(Color::Named(NamedColor::BrightWhite))),
100 => Some(Attr::Background(Color::Named(NamedColor::BrightBlack))),
101 => Some(Attr::Background(Color::Named(NamedColor::BrightRed))),
102 => Some(Attr::Background(Color::Named(NamedColor::BrightGreen))),
103 => Some(Attr::Background(Color::Named(NamedColor::BrightYellow))),
104 => Some(Attr::Background(Color::Named(NamedColor::BrightBlue))),
105 => Some(Attr::Background(Color::Named(NamedColor::BrightMagenta))),
106 => Some(Attr::Background(Color::Named(NamedColor::BrightCyan))),
107 => Some(Attr::Background(Color::Named(NamedColor::BrightWhite))),
_ => None,
};
attrs.push(attr);
i += 1; }
attrs
}
fn parse_sgr_color(attrs: &[i64], i: &mut usize) -> Option<Color> {
if attrs.len() < 2 {
return None;
}
match attrs[*i + 1] {
2 => {
if attrs.len() < 5 {
debug!("Expected RGB color spec; got {:?}", attrs);
return None;
}
let r = attrs[*i + 2];
let g = attrs[*i + 3];
let b = attrs[*i + 4];
*i += 4;
let range = 0..256;
if !range.contains(&r) || !range.contains(&g) || !range.contains(&b) {
debug!("Invalid RGB color spec: ({}, {}, {})", r, g, b);
return None;
}
Some(Color::Spec(Rgb { r: r as u8, g: g as u8, b: b as u8 }))
},
5 => {
if attrs.len() < 3 {
debug!("Expected color index; got {:?}", attrs);
None
} else {
*i += 2;
let idx = attrs[*i];
match idx {
0..=255 => Some(Color::Indexed(idx as u8)),
_ => {
debug!("Invalid color index: {}", idx);
None
},
}
}
},
_ => {
debug!("Unexpected color attr: {}", attrs[*i + 1]);
None
},
}
}
#[allow(non_snake_case)]
pub mod C0 {
pub const NUL: u8 = 0x00;
pub const SOH: u8 = 0x01;
pub const STX: u8 = 0x02;
pub const ETX: u8 = 0x03;
pub const EOT: u8 = 0x04;
pub const ENQ: u8 = 0x05;
pub const ACK: u8 = 0x06;
pub const BEL: u8 = 0x07;
pub const BS: u8 = 0x08;
pub const HT: u8 = 0x09;
pub const LF: u8 = 0x0A;
pub const VT: u8 = 0x0B;
pub const FF: u8 = 0x0C;
pub const CR: u8 = 0x0D;
pub const SO: u8 = 0x0E;
pub const SI: u8 = 0x0F;
pub const DLE: u8 = 0x10;
pub const XON: u8 = 0x11;
pub const DC2: u8 = 0x12;
pub const XOFF: u8 = 0x13;
pub const DC4: u8 = 0x14;
pub const NAK: u8 = 0x15;
pub const SYN: u8 = 0x16;
pub const ETB: u8 = 0x17;
pub const CAN: u8 = 0x18;
pub const EM: u8 = 0x19;
pub const SUB: u8 = 0x1A;
pub const ESC: u8 = 0x1B;
pub const FS: u8 = 0x1C;
pub const GS: u8 = 0x1D;
pub const RS: u8 = 0x1E;
pub const US: u8 = 0x1F;
pub const DEL: u8 = 0x7f;
}
#[cfg(test)]
mod tests {
use super::{
parse_number, xparse_color, Attr, CharsetIndex, Color, Handler, Processor, StandardCharset,
TermInfo,
};
use crate::index::{Column, Line};
use crate::term::color::Rgb;
use std::io;
struct MockHandler {
index: CharsetIndex,
charset: StandardCharset,
attr: Option<Attr>,
identity_reported: bool,
}
impl Handler for MockHandler {
fn terminal_attribute(&mut self, attr: Attr) {
self.attr = Some(attr);
}
fn configure_charset(&mut self, index: CharsetIndex, charset: StandardCharset) {
self.index = index;
self.charset = charset;
}
fn set_active_charset(&mut self, index: CharsetIndex) {
self.index = index;
}
fn identify_terminal<W: io::Write>(&mut self, _: &mut W) {
self.identity_reported = true;
}
fn reset_state(&mut self) {
*self = Self::default();
}
}
impl TermInfo for MockHandler {
fn lines(&self) -> Line {
Line(200)
}
fn cols(&self) -> Column {
Column(90)
}
}
impl Default for MockHandler {
fn default() -> MockHandler {
MockHandler {
index: CharsetIndex::G0,
charset: StandardCharset::Ascii,
attr: None,
identity_reported: false,
}
}
}
#[test]
fn parse_control_attribute() {
static BYTES: &[u8] = &[0x1b, b'[', b'1', b'm'];
let mut parser = Processor::new();
let mut handler = MockHandler::default();
for byte in &BYTES[..] {
parser.advance(&mut handler, *byte, &mut io::sink());
}
assert_eq!(handler.attr, Some(Attr::Bold));
}
#[test]
fn parse_terminal_identity_csi() {
let bytes: &[u8] = &[0x1b, b'[', b'1', b'c'];
let mut parser = Processor::new();
let mut handler = MockHandler::default();
for byte in &bytes[..] {
parser.advance(&mut handler, *byte, &mut io::sink());
}
assert!(!handler.identity_reported);
handler.reset_state();
let bytes: &[u8] = &[0x1b, b'[', b'c'];
for byte in &bytes[..] {
parser.advance(&mut handler, *byte, &mut io::sink());
}
assert!(handler.identity_reported);
handler.reset_state();
let bytes: &[u8] = &[0x1b, b'[', b'0', b'c'];
for byte in &bytes[..] {
parser.advance(&mut handler, *byte, &mut io::sink());
}
assert!(handler.identity_reported);
}
#[test]
fn parse_terminal_identity_esc() {
let bytes: &[u8] = &[0x1b, b'Z'];
let mut parser = Processor::new();
let mut handler = MockHandler::default();
for byte in &bytes[..] {
parser.advance(&mut handler, *byte, &mut io::sink());
}
assert!(handler.identity_reported);
handler.reset_state();
let bytes: &[u8] = &[0x1b, b'#', b'Z'];
let mut parser = Processor::new();
let mut handler = MockHandler::default();
for byte in &bytes[..] {
parser.advance(&mut handler, *byte, &mut io::sink());
}
assert!(!handler.identity_reported);
handler.reset_state();
}
#[test]
fn parse_truecolor_attr() {
static BYTES: &[u8] = &[
0x1b, b'[', b'3', b'8', b';', b'2', b';', b'1', b'2', b'8', b';', b'6', b'6', b';',
b'2', b'5', b'5', b'm',
];
let mut parser = Processor::new();
let mut handler = MockHandler::default();
for byte in &BYTES[..] {
parser.advance(&mut handler, *byte, &mut io::sink());
}
let spec = Rgb { r: 128, g: 66, b: 255 };
assert_eq!(handler.attr, Some(Attr::Foreground(Color::Spec(spec))));
}
#[test]
fn parse_zsh_startup() {
static BYTES: &[u8] = &[
0x1b, b'[', b'1', b'm', 0x1b, b'[', b'7', b'm', b'%', 0x1b, b'[', b'2', b'7', b'm',
0x1b, b'[', b'1', b'm', 0x1b, b'[', b'0', b'm', b' ', b' ', b' ', b' ', b' ', b' ',
b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ',
b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ',
b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ',
b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ',
b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ',
b' ', b' ', b' ', b'\r', b' ', b'\r', b'\r', 0x1b, b'[', b'0', b'm', 0x1b, b'[', b'2',
b'7', b'm', 0x1b, b'[', b'2', b'4', b'm', 0x1b, b'[', b'J', b'j', b'w', b'i', b'l',
b'm', b'@', b'j', b'w', b'i', b'l', b'm', b'-', b'd', b'e', b's', b'k', b' ', 0x1b,
b'[', b'0', b'1', b';', b'3', b'2', b'm', 0xe2, 0x9e, 0x9c, b' ', 0x1b, b'[', b'0',
b'1', b';', b'3', b'2', b'm', b' ', 0x1b, b'[', b'3', b'6', b'm', b'~', b'/', b'c',
b'o', b'd', b'e',
];
let mut handler = MockHandler::default();
let mut parser = Processor::new();
for byte in &BYTES[..] {
parser.advance(&mut handler, *byte, &mut io::sink());
}
}
#[test]
fn parse_designate_g0_as_line_drawing() {
static BYTES: &[u8] = &[0x1b, b'(', b'0'];
let mut parser = Processor::new();
let mut handler = MockHandler::default();
for byte in &BYTES[..] {
parser.advance(&mut handler, *byte, &mut io::sink());
}
assert_eq!(handler.index, CharsetIndex::G0);
assert_eq!(handler.charset, StandardCharset::SpecialCharacterAndLineDrawing);
}
#[test]
fn parse_designate_g1_as_line_drawing_and_invoke() {
static BYTES: &[u8] = &[0x1b, b')', b'0', 0x0e];
let mut parser = Processor::new();
let mut handler = MockHandler::default();
for byte in &BYTES[..3] {
parser.advance(&mut handler, *byte, &mut io::sink());
}
assert_eq!(handler.index, CharsetIndex::G1);
assert_eq!(handler.charset, StandardCharset::SpecialCharacterAndLineDrawing);
let mut handler = MockHandler::default();
parser.advance(&mut handler, BYTES[3], &mut io::sink());
assert_eq!(handler.index, CharsetIndex::G1);
}
#[test]
fn parse_valid_rgb_colors() {
assert_eq!(xparse_color(b"rgb:f/e/d"), Some(Rgb { r: 0xff, g: 0xee, b: 0xdd }));
assert_eq!(xparse_color(b"rgb:11/aa/ff"), Some(Rgb { r: 0x11, g: 0xaa, b: 0xff }));
assert_eq!(xparse_color(b"rgb:f/ed1/cb23"), Some(Rgb { r: 0xff, g: 0xec, b: 0xca }));
assert_eq!(xparse_color(b"rgb:ffff/0/0"), Some(Rgb { r: 0xff, g: 0x0, b: 0x0 }));
}
#[test]
fn parse_valid_legacy_rgb_colors() {
assert_eq!(xparse_color(b"#1af"), Some(Rgb { r: 0x10, g: 0xa0, b: 0xf0 }));
assert_eq!(xparse_color(b"#11aaff"), Some(Rgb { r: 0x11, g: 0xaa, b: 0xff }));
assert_eq!(xparse_color(b"#110aa0ff0"), Some(Rgb { r: 0x11, g: 0xaa, b: 0xff }));
assert_eq!(xparse_color(b"#1100aa00ff00"), Some(Rgb { r: 0x11, g: 0xaa, b: 0xff }));
}
#[test]
fn parse_invalid_rgb_colors() {
assert_eq!(xparse_color(b"rgb:0//"), None);
assert_eq!(xparse_color(b"rgb://///"), None);
}
#[test]
fn parse_invalid_legacy_rgb_colors() {
assert_eq!(xparse_color(b"#"), None);
assert_eq!(xparse_color(b"#f"), None);
}
#[test]
fn parse_invalid_number() {
assert_eq!(parse_number(b"1abc"), None);
}
#[test]
fn parse_valid_number() {
assert_eq!(parse_number(b"123"), Some(123));
}
#[test]
fn parse_number_too_large() {
assert_eq!(parse_number(b"321"), None);
}
}