use super::OneBased;
use crate::cell::{Blink, Intensity, Underline, VerticalAlign};
use crate::color::{AnsiColor, ColorSpec, RgbColor, SrgbaTuple};
use crate::input::{Modifiers, MouseButtons};
use num_derive::*;
use num_traits::{FromPrimitive, ToPrimitive};
use std::convert::TryInto;
use std::fmt::{Display, Error as FmtError, Formatter};
pub use vtparse::CsiParam;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CSI {
Sgr(Sgr),
Cursor(Cursor),
Edit(Edit),
Mode(Mode),
Device(Box<Device>),
Mouse(MouseReport),
Window(Box<Window>),
Keyboard(Keyboard),
SelectCharacterPath(CharacterPath, i64),
Unspecified(Box<Unspecified>),
}
#[cfg(all(test, target_pointer_width = "64"))]
#[test]
fn csi_size() {
assert_eq!(std::mem::size_of::<Sgr>(), 24);
assert_eq!(std::mem::size_of::<Cursor>(), 12);
assert_eq!(std::mem::size_of::<Edit>(), 8);
assert_eq!(std::mem::size_of::<Mode>(), 24);
assert_eq!(std::mem::size_of::<MouseReport>(), 8);
assert_eq!(std::mem::size_of::<Window>(), 40);
assert_eq!(std::mem::size_of::<Keyboard>(), 8);
assert_eq!(std::mem::size_of::<CSI>(), 32);
}
bitflags::bitflags! {
pub struct KittyKeyboardFlags: u16 {
const NONE = 0;
const DISAMBIGUATE_ESCAPE_CODES = 1;
const REPORT_EVENT_TYPES = 2;
const REPORT_ALTERNATE_KEYS = 4;
const REPORT_ALL_KEYS_AS_ESCAPE_CODES = 8;
const REPORT_ASSOCIATED_TEXT = 16;
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[repr(u16)]
pub enum KittyKeyboardMode {
AssignAll = 1,
SetSpecified = 2,
ClearSpecified = 3,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Keyboard {
SetKittyState {
flags: KittyKeyboardFlags,
mode: KittyKeyboardMode,
},
PushKittyState {
flags: KittyKeyboardFlags,
mode: KittyKeyboardMode,
},
PopKittyState(u32),
QueryKittySupport,
ReportKittyState(KittyKeyboardFlags),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CharacterPath {
ImplementationDefault,
LeftToRightOrTopToBottom,
RightToLeftOrBottomToTop,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Unspecified {
pub params: Vec<CsiParam>,
pub parameters_truncated: bool,
pub control: char,
}
impl Display for Unspecified {
fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
for p in &self.params {
write!(f, "{}", p)?;
}
write!(f, "{}", self.control)
}
}
impl Display for CSI {
fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
write!(f, "\x1b[")?;
match self {
CSI::Sgr(sgr) => sgr.fmt(f)?,
CSI::Cursor(c) => c.fmt(f)?,
CSI::Edit(e) => e.fmt(f)?,
CSI::Mode(mode) => mode.fmt(f)?,
CSI::Unspecified(unspec) => unspec.fmt(f)?,
CSI::Mouse(mouse) => mouse.fmt(f)?,
CSI::Device(dev) => dev.fmt(f)?,
CSI::Window(window) => window.fmt(f)?,
CSI::Keyboard(Keyboard::SetKittyState { flags, mode }) => {
write!(f, "={};{}u", flags.bits(), *mode as u16)?
}
CSI::Keyboard(Keyboard::PushKittyState { flags, mode }) => {
write!(f, ">{};{}u", flags.bits(), *mode as u16)?
}
CSI::Keyboard(Keyboard::PopKittyState(n)) => write!(f, "<{}u", *n)?,
CSI::Keyboard(Keyboard::QueryKittySupport) => write!(f, "?u")?,
CSI::Keyboard(Keyboard::ReportKittyState(flags)) => write!(f, "?{}u", flags.bits())?,
CSI::SelectCharacterPath(path, n) => {
let a = match path {
CharacterPath::ImplementationDefault => 0,
CharacterPath::LeftToRightOrTopToBottom => 1,
CharacterPath::RightToLeftOrBottomToTop => 2,
};
match (a, n) {
(0, 0) => write!(f, " k")?,
(a, 0) => write!(f, "{} k", a)?,
(a, n) => write!(f, "{};{} k", a, n)?,
}
}
};
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive)]
pub enum CursorStyle {
Default = 0,
BlinkingBlock = 1,
SteadyBlock = 2,
BlinkingUnderline = 3,
SteadyUnderline = 4,
BlinkingBar = 5,
SteadyBar = 6,
}
impl Default for CursorStyle {
fn default() -> CursorStyle {
CursorStyle::Default
}
}
#[derive(Debug, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)]
pub enum DeviceAttributeCodes {
Columns132 = 1,
Printer = 2,
RegisGraphics = 3,
SixelGraphics = 4,
SelectiveErase = 6,
UserDefinedKeys = 8,
NationalReplacementCharsets = 9,
TechnicalCharacters = 15,
UserWindows = 18,
HorizontalScrolling = 21,
AnsiColor = 22,
AnsiTextLocator = 29,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DeviceAttribute {
Code(DeviceAttributeCodes),
Unspecified(CsiParam),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DeviceAttributeFlags {
pub attributes: Vec<DeviceAttribute>,
}
impl DeviceAttributeFlags {
fn emit(&self, f: &mut Formatter, leader: &str) -> Result<(), FmtError> {
write!(f, "{}", leader)?;
for item in &self.attributes {
match item {
DeviceAttribute::Code(c) => write!(f, ";{}", c.to_u16().ok_or_else(|| FmtError)?)?,
DeviceAttribute::Unspecified(param) => write!(f, ";{}", param)?,
}
}
write!(f, "c")?;
Ok(())
}
pub fn new(attributes: Vec<DeviceAttribute>) -> Self {
Self { attributes }
}
fn from_params(params: &[CsiParam]) -> Self {
let mut attributes = Vec::new();
for i in params {
match i {
CsiParam::Integer(p) => match FromPrimitive::from_i64(*p) {
Some(c) => attributes.push(DeviceAttribute::Code(c)),
None => attributes.push(DeviceAttribute::Unspecified(i.clone())),
},
CsiParam::P(b';') => {}
_ => attributes.push(DeviceAttribute::Unspecified(i.clone())),
}
}
Self { attributes }
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DeviceAttributes {
Vt100WithAdvancedVideoOption,
Vt101WithNoOptions,
Vt102,
Vt220(DeviceAttributeFlags),
Vt320(DeviceAttributeFlags),
Vt420(DeviceAttributeFlags),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum XtSmGraphicsItem {
NumberOfColorRegisters,
SixelGraphicsGeometry,
RegisGraphicsGeometry,
Unspecified(i64),
}
impl Display for XtSmGraphicsItem {
fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
match self {
Self::NumberOfColorRegisters => write!(f, "1"),
Self::SixelGraphicsGeometry => write!(f, "2"),
Self::RegisGraphicsGeometry => write!(f, "3"),
Self::Unspecified(n) => write!(f, "{}", n),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum XtSmGraphicsAction {
ReadAttribute,
ResetToDefault,
SetToValue,
ReadMaximumAllowedValue,
}
impl XtSmGraphicsAction {
pub fn to_i64(&self) -> i64 {
match self {
Self::ReadAttribute => 1,
Self::ResetToDefault => 2,
Self::SetToValue => 3,
Self::ReadMaximumAllowedValue => 4,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum XtSmGraphicsStatus {
Success,
InvalidItem,
InvalidAction,
Failure,
}
impl XtSmGraphicsStatus {
pub fn to_i64(&self) -> i64 {
match self {
Self::Success => 0,
Self::InvalidItem => 1,
Self::InvalidAction => 2,
Self::Failure => 3,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct XtSmGraphics {
pub item: XtSmGraphicsItem,
pub action_or_status: i64,
pub value: Vec<i64>,
}
impl XtSmGraphics {
pub fn action(&self) -> Option<XtSmGraphicsAction> {
match self.action_or_status {
1 => Some(XtSmGraphicsAction::ReadAttribute),
2 => Some(XtSmGraphicsAction::ResetToDefault),
3 => Some(XtSmGraphicsAction::SetToValue),
4 => Some(XtSmGraphicsAction::ReadMaximumAllowedValue),
_ => None,
}
}
pub fn status(&self) -> Option<XtSmGraphicsStatus> {
match self.action_or_status {
0 => Some(XtSmGraphicsStatus::Success),
1 => Some(XtSmGraphicsStatus::InvalidItem),
2 => Some(XtSmGraphicsStatus::InvalidAction),
3 => Some(XtSmGraphicsStatus::Failure),
_ => None,
}
}
pub fn parse(params: &[CsiParam]) -> Result<CSI, ()> {
let params = Cracked::parse(¶ms[1..])?;
Ok(CSI::Device(Box::new(Device::XtSmGraphics(XtSmGraphics {
item: match params.get(0).ok_or(())? {
CsiParam::Integer(1) => XtSmGraphicsItem::NumberOfColorRegisters,
CsiParam::Integer(2) => XtSmGraphicsItem::SixelGraphicsGeometry,
CsiParam::Integer(3) => XtSmGraphicsItem::RegisGraphicsGeometry,
CsiParam::Integer(n) => XtSmGraphicsItem::Unspecified(*n),
_ => return Err(()),
},
action_or_status: match params.get(1).ok_or(())? {
CsiParam::Integer(n) => *n,
_ => return Err(()),
},
value: params.params[2..]
.iter()
.filter_map(|p| match p {
Some(CsiParam::Integer(n)) => Some(*n),
_ => None,
})
.collect(),
}))))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Device {
DeviceAttributes(DeviceAttributes),
SoftReset,
RequestPrimaryDeviceAttributes,
RequestSecondaryDeviceAttributes,
RequestTertiaryDeviceAttributes,
StatusReport,
RequestTerminalNameAndVersion,
RequestTerminalParameters(i64),
XtSmGraphics(XtSmGraphics),
}
impl Display for Device {
fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
match self {
Device::DeviceAttributes(DeviceAttributes::Vt100WithAdvancedVideoOption) => {
write!(f, "?1;2c")?
}
Device::DeviceAttributes(DeviceAttributes::Vt101WithNoOptions) => write!(f, "?1;0c")?,
Device::DeviceAttributes(DeviceAttributes::Vt102) => write!(f, "?6c")?,
Device::DeviceAttributes(DeviceAttributes::Vt220(attr)) => attr.emit(f, "?62")?,
Device::DeviceAttributes(DeviceAttributes::Vt320(attr)) => attr.emit(f, "?63")?,
Device::DeviceAttributes(DeviceAttributes::Vt420(attr)) => attr.emit(f, "?64")?,
Device::SoftReset => write!(f, "!p")?,
Device::RequestPrimaryDeviceAttributes => write!(f, "c")?,
Device::RequestSecondaryDeviceAttributes => write!(f, ">c")?,
Device::RequestTertiaryDeviceAttributes => write!(f, "=c")?,
Device::RequestTerminalNameAndVersion => write!(f, ">q")?,
Device::RequestTerminalParameters(n) => write!(f, "{};1;1;128;128;1;0x", n + 2)?,
Device::StatusReport => write!(f, "5n")?,
Device::XtSmGraphics(g) => {
write!(f, "?{};{}", g.item, g.action_or_status)?;
for v in &g.value {
write!(f, ";{}", v)?;
}
write!(f, "S")?;
}
};
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MouseButton {
Button1Press,
Button2Press,
Button3Press,
Button4Press,
Button5Press,
Button1Release,
Button2Release,
Button3Release,
Button4Release,
Button5Release,
Button1Drag,
Button2Drag,
Button3Drag,
None,
}
impl From<MouseButton> for MouseButtons {
fn from(button: MouseButton) -> MouseButtons {
match button {
MouseButton::Button1Press | MouseButton::Button1Drag => MouseButtons::LEFT,
MouseButton::Button2Press | MouseButton::Button2Drag => MouseButtons::MIDDLE,
MouseButton::Button3Press | MouseButton::Button3Drag => MouseButtons::RIGHT,
MouseButton::Button4Press => MouseButtons::VERT_WHEEL | MouseButtons::WHEEL_POSITIVE,
MouseButton::Button5Press => MouseButtons::VERT_WHEEL,
_ => MouseButtons::NONE,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Window {
DeIconify,
Iconify,
MoveWindow {
x: i64,
y: i64,
},
ResizeWindowPixels {
width: Option<i64>,
height: Option<i64>,
},
RaiseWindow,
LowerWindow,
RefreshWindow,
ResizeWindowCells {
width: Option<i64>,
height: Option<i64>,
},
RestoreMaximizedWindow,
MaximizeWindow,
MaximizeWindowVertically,
MaximizeWindowHorizontally,
UndoFullScreenMode,
ChangeToFullScreenMode,
ToggleFullScreen,
ReportWindowState,
ReportWindowPosition,
ReportTextAreaPosition,
ReportTextAreaSizePixels,
ReportWindowSizePixels,
ReportScreenSizePixels,
ReportCellSizePixels,
ReportCellSizePixelsResponse {
width: Option<i64>,
height: Option<i64>,
},
ReportTextAreaSizeCells,
ReportScreenSizeCells,
ReportIconLabel,
ReportWindowTitle,
PushIconAndWindowTitle,
PushIconTitle,
PushWindowTitle,
PopIconAndWindowTitle,
PopIconTitle,
PopWindowTitle,
ChecksumRectangularArea {
request_id: i64,
page_number: i64,
top: OneBased,
left: OneBased,
bottom: OneBased,
right: OneBased,
},
}
fn numstr_or_empty(x: &Option<i64>) -> String {
match x {
Some(x) => format!("{}", x),
None => "".to_owned(),
}
}
impl Display for Window {
fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
match self {
Window::DeIconify => write!(f, "1t"),
Window::Iconify => write!(f, "2t"),
Window::MoveWindow { x, y } => write!(f, "3;{};{}t", x, y),
Window::ResizeWindowPixels { width, height } => write!(
f,
"4;{};{}t",
numstr_or_empty(height),
numstr_or_empty(width),
),
Window::RaiseWindow => write!(f, "5t"),
Window::LowerWindow => write!(f, "6t"),
Window::RefreshWindow => write!(f, "7t"),
Window::ResizeWindowCells { width, height } => write!(
f,
"8;{};{}t",
numstr_or_empty(height),
numstr_or_empty(width),
),
Window::RestoreMaximizedWindow => write!(f, "9;0t"),
Window::MaximizeWindow => write!(f, "9;1t"),
Window::MaximizeWindowVertically => write!(f, "9;2t"),
Window::MaximizeWindowHorizontally => write!(f, "9;3t"),
Window::UndoFullScreenMode => write!(f, "10;0t"),
Window::ChangeToFullScreenMode => write!(f, "10;1t"),
Window::ToggleFullScreen => write!(f, "10;2t"),
Window::ReportWindowState => write!(f, "11t"),
Window::ReportWindowPosition => write!(f, "13t"),
Window::ReportTextAreaPosition => write!(f, "13;2t"),
Window::ReportTextAreaSizePixels => write!(f, "14t"),
Window::ReportWindowSizePixels => write!(f, "14;2t"),
Window::ReportScreenSizePixels => write!(f, "15t"),
Window::ReportCellSizePixels => write!(f, "16t"),
Window::ReportCellSizePixelsResponse { width, height } => write!(
f,
"6;{};{}t",
numstr_or_empty(height),
numstr_or_empty(width),
),
Window::ReportTextAreaSizeCells => write!(f, "18t"),
Window::ReportScreenSizeCells => write!(f, "19t"),
Window::ReportIconLabel => write!(f, "20t"),
Window::ReportWindowTitle => write!(f, "21t"),
Window::PushIconAndWindowTitle => write!(f, "22;0t"),
Window::PushIconTitle => write!(f, "22;1t"),
Window::PushWindowTitle => write!(f, "22;2t"),
Window::PopIconAndWindowTitle => write!(f, "23;0t"),
Window::PopIconTitle => write!(f, "23;1t"),
Window::PopWindowTitle => write!(f, "23;2t"),
Window::ChecksumRectangularArea {
request_id,
page_number,
top,
left,
bottom,
right,
} => write!(
f,
"{};{};{};{};{};{}*y",
request_id, page_number, top, left, bottom, right,
),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MouseReport {
SGR1006 {
x: u16,
y: u16,
button: MouseButton,
modifiers: Modifiers,
},
SGR1016 {
x_pixels: u16,
y_pixels: u16,
button: MouseButton,
modifiers: Modifiers,
},
}
impl Display for MouseReport {
fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
match self {
MouseReport::SGR1006 {
x,
y,
button,
modifiers,
} => {
let mut b = 0;
if (*modifiers & Modifiers::SHIFT) != Modifiers::NONE {
b |= 4;
}
if (*modifiers & Modifiers::ALT) != Modifiers::NONE {
b |= 8;
}
if (*modifiers & Modifiers::CTRL) != Modifiers::NONE {
b |= 16;
}
b |= match button {
MouseButton::Button1Press | MouseButton::Button1Release => 0,
MouseButton::Button2Press | MouseButton::Button2Release => 1,
MouseButton::Button3Press | MouseButton::Button3Release => 2,
MouseButton::Button4Press | MouseButton::Button4Release => 64,
MouseButton::Button5Press | MouseButton::Button5Release => 65,
MouseButton::Button1Drag => 32,
MouseButton::Button2Drag => 33,
MouseButton::Button3Drag => 34,
MouseButton::None => 35,
};
let trailer = match button {
MouseButton::Button1Press
| MouseButton::Button2Press
| MouseButton::Button3Press
| MouseButton::Button4Press
| MouseButton::Button5Press
| MouseButton::Button1Drag
| MouseButton::Button2Drag
| MouseButton::Button3Drag
| MouseButton::None => 'M',
_ => 'm',
};
write!(f, "<{};{};{}{}", b, x, y, trailer)
}
MouseReport::SGR1016 {
x_pixels,
y_pixels,
button,
modifiers,
} => {
let mut b = 0;
if (*modifiers & Modifiers::SHIFT) != Modifiers::NONE {
b |= 4;
}
if (*modifiers & Modifiers::ALT) != Modifiers::NONE {
b |= 8;
}
if (*modifiers & Modifiers::CTRL) != Modifiers::NONE {
b |= 16;
}
b |= match button {
MouseButton::Button1Press | MouseButton::Button1Release => 0,
MouseButton::Button2Press | MouseButton::Button2Release => 1,
MouseButton::Button3Press | MouseButton::Button3Release => 2,
MouseButton::Button4Press | MouseButton::Button4Release => 64,
MouseButton::Button5Press | MouseButton::Button5Release => 65,
MouseButton::Button1Drag => 32,
MouseButton::Button2Drag => 33,
MouseButton::Button3Drag => 34,
MouseButton::None => 35,
};
let trailer = match button {
MouseButton::Button1Press
| MouseButton::Button2Press
| MouseButton::Button3Press
| MouseButton::Button4Press
| MouseButton::Button5Press
| MouseButton::Button1Drag
| MouseButton::Button2Drag
| MouseButton::Button3Drag
| MouseButton::None => 'M',
_ => 'm',
};
write!(f, "<{};{};{}{}", b, x_pixels, y_pixels, trailer)
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum XtermKeyModifierResource {
Keyboard,
CursorKeys,
FunctionKeys,
OtherKeys,
}
impl XtermKeyModifierResource {
pub fn parse(value: i64) -> Option<Self> {
Some(match value {
0 => XtermKeyModifierResource::Keyboard,
1 => XtermKeyModifierResource::CursorKeys,
2 => XtermKeyModifierResource::FunctionKeys,
4 => XtermKeyModifierResource::OtherKeys,
_ => return None,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Mode {
SetDecPrivateMode(DecPrivateMode),
ResetDecPrivateMode(DecPrivateMode),
SaveDecPrivateMode(DecPrivateMode),
RestoreDecPrivateMode(DecPrivateMode),
QueryDecPrivateMode(DecPrivateMode),
SetMode(TerminalMode),
ResetMode(TerminalMode),
QueryMode(TerminalMode),
XtermKeyMode {
resource: XtermKeyModifierResource,
value: Option<i64>,
},
}
impl Display for Mode {
fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
macro_rules! emit {
($flag:expr, $mode:expr) => {{
let value = match $mode {
DecPrivateMode::Code(mode) => mode.to_u16().ok_or_else(|| FmtError)?,
DecPrivateMode::Unspecified(mode) => *mode,
};
write!(f, "?{}{}", value, $flag)
}};
}
macro_rules! emit_mode {
($flag:expr, $mode:expr) => {{
let value = match $mode {
TerminalMode::Code(mode) => mode.to_u16().ok_or_else(|| FmtError)?,
TerminalMode::Unspecified(mode) => *mode,
};
write!(f, "{}{}", value, $flag)
}};
}
match self {
Mode::SetDecPrivateMode(mode) => emit!("h", mode),
Mode::ResetDecPrivateMode(mode) => emit!("l", mode),
Mode::SaveDecPrivateMode(mode) => emit!("s", mode),
Mode::RestoreDecPrivateMode(mode) => emit!("r", mode),
Mode::QueryDecPrivateMode(DecPrivateMode::Code(mode)) => {
write!(f, "?{}$p", mode.to_u16().ok_or_else(|| FmtError)?)
}
Mode::QueryDecPrivateMode(DecPrivateMode::Unspecified(mode)) => {
write!(f, "?{}$p", mode)
}
Mode::SetMode(mode) => emit_mode!("h", mode),
Mode::ResetMode(mode) => emit_mode!("l", mode),
Mode::QueryMode(TerminalMode::Code(mode)) => {
write!(f, "?{}$p", mode.to_u16().ok_or_else(|| FmtError)?)
}
Mode::QueryMode(TerminalMode::Unspecified(mode)) => write!(f, "?{}$p", mode),
Mode::XtermKeyMode { resource, value } => {
write!(
f,
">{}",
match resource {
XtermKeyModifierResource::Keyboard => 0,
XtermKeyModifierResource::CursorKeys => 1,
XtermKeyModifierResource::FunctionKeys => 2,
XtermKeyModifierResource::OtherKeys => 4,
}
)?;
if let Some(value) = value {
write!(f, ";{}", value)?;
} else {
write!(f, ";")?;
}
write!(f, "m")
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DecPrivateMode {
Code(DecPrivateModeCode),
Unspecified(u16),
}
#[derive(Debug, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)]
pub enum DecPrivateModeCode {
ApplicationCursorKeys = 1,
DecAnsiMode = 2,
Select132Columns = 3,
SmoothScroll = 4,
ReverseVideo = 5,
OriginMode = 6,
AutoWrap = 7,
AutoRepeat = 8,
StartBlinkingCursor = 12,
ShowCursor = 25,
ReverseWraparound = 45,
LeftRightMarginMode = 69,
SixelDisplayMode = 80,
MouseTracking = 1000,
HighlightMouseTracking = 1001,
ButtonEventMouse = 1002,
AnyEventMouse = 1003,
FocusTracking = 1004,
Utf8Mouse = 1005,
SGRMouse = 1006,
SGRPixelsMouse = 1016,
XTermMetaSendsEscape = 1036,
XTermAltSendsEscape = 1039,
SaveCursor = 1048,
ClearAndEnableAlternateScreen = 1049,
EnableAlternateScreen = 47,
OptEnableAlternateScreen = 1047,
BracketedPaste = 2004,
UsePrivateColorRegistersForEachGraphic = 1070,
SynchronizedOutput = 2026,
MinTTYApplicationEscapeKeyMode = 7727,
SixelScrollsRight = 8452,
Win32InputMode = 9001,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TerminalMode {
Code(TerminalModeCode),
Unspecified(u16),
}
#[derive(Debug, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)]
pub enum TerminalModeCode {
KeyboardAction = 2,
Insert = 4,
BiDirectionalSupportMode = 8,
SendReceive = 12,
AutomaticNewline = 20,
ShowCursor = 25,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Cursor {
BackwardTabulation(u32),
TabulationClear(TabulationClear),
CharacterAbsolute(OneBased),
CharacterPositionAbsolute(OneBased),
CharacterPositionBackward(u32),
CharacterPositionForward(u32),
CharacterAndLinePosition {
line: OneBased,
col: OneBased,
},
LinePositionAbsolute(u32),
LinePositionBackward(u32),
LinePositionForward(u32),
ForwardTabulation(u32),
NextLine(u32),
PrecedingLine(u32),
ActivePositionReport {
line: OneBased,
col: OneBased,
},
RequestActivePositionReport,
SaveCursor,
RestoreCursor,
TabulationControl(CursorTabulationControl),
Left(u32),
Down(u32),
Right(u32),
Position {
line: OneBased,
col: OneBased,
},
Up(u32),
LineTabulation(u32),
SetTopAndBottomMargins {
top: OneBased,
bottom: OneBased,
},
SetLeftAndRightMargins {
left: OneBased,
right: OneBased,
},
CursorStyle(CursorStyle),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Edit {
DeleteCharacter(u32),
DeleteLine(u32),
EraseCharacter(u32),
EraseInLine(EraseInLine),
InsertCharacter(u32),
InsertLine(u32),
ScrollDown(u32),
ScrollUp(u32),
EraseInDisplay(EraseInDisplay),
Repeat(u32),
}
trait EncodeCSIParam {
fn write_csi(&self, f: &mut Formatter, control: &str) -> Result<(), FmtError>;
}
impl<T: ParamEnum + PartialEq + ToPrimitive> EncodeCSIParam for T {
fn write_csi(&self, f: &mut Formatter, control: &str) -> Result<(), FmtError> {
if *self == ParamEnum::default() {
write!(f, "{}", control)
} else {
let value = self.to_i64().ok_or_else(|| FmtError)?;
write!(f, "{}{}", value, control)
}
}
}
impl EncodeCSIParam for u32 {
fn write_csi(&self, f: &mut Formatter, control: &str) -> Result<(), FmtError> {
if *self == 1 {
write!(f, "{}", control)
} else {
write!(f, "{}{}", *self, control)
}
}
}
impl EncodeCSIParam for OneBased {
fn write_csi(&self, f: &mut Formatter, control: &str) -> Result<(), FmtError> {
if self.as_one_based() == 1 {
write!(f, "{}", control)
} else {
write!(f, "{}{}", *self, control)
}
}
}
impl Display for Edit {
fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
match self {
Edit::DeleteCharacter(n) => n.write_csi(f, "P")?,
Edit::DeleteLine(n) => n.write_csi(f, "M")?,
Edit::EraseCharacter(n) => n.write_csi(f, "X")?,
Edit::EraseInLine(n) => n.write_csi(f, "K")?,
Edit::InsertCharacter(n) => n.write_csi(f, "@")?,
Edit::InsertLine(n) => n.write_csi(f, "L")?,
Edit::ScrollDown(n) => n.write_csi(f, "T")?,
Edit::ScrollUp(n) => n.write_csi(f, "S")?,
Edit::EraseInDisplay(n) => n.write_csi(f, "J")?,
Edit::Repeat(n) => n.write_csi(f, "b")?,
}
Ok(())
}
}
impl Display for Cursor {
fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
match self {
Cursor::BackwardTabulation(n) => n.write_csi(f, "Z")?,
Cursor::CharacterAbsolute(col) => col.write_csi(f, "G")?,
Cursor::ForwardTabulation(n) => n.write_csi(f, "I")?,
Cursor::NextLine(n) => n.write_csi(f, "E")?,
Cursor::PrecedingLine(n) => n.write_csi(f, "F")?,
Cursor::ActivePositionReport { line, col } => write!(f, "{};{}R", line, col)?,
Cursor::Left(n) => n.write_csi(f, "D")?,
Cursor::Down(n) => n.write_csi(f, "B")?,
Cursor::Right(n) => n.write_csi(f, "C")?,
Cursor::Up(n) => n.write_csi(f, "A")?,
Cursor::Position { line, col } => write!(f, "{};{}H", line, col)?,
Cursor::LineTabulation(n) => n.write_csi(f, "Y")?,
Cursor::TabulationControl(n) => n.write_csi(f, "W")?,
Cursor::TabulationClear(n) => n.write_csi(f, "g")?,
Cursor::CharacterPositionAbsolute(n) => n.write_csi(f, "`")?,
Cursor::CharacterPositionBackward(n) => n.write_csi(f, "j")?,
Cursor::CharacterPositionForward(n) => n.write_csi(f, "a")?,
Cursor::CharacterAndLinePosition { line, col } => write!(f, "{};{}f", line, col)?,
Cursor::LinePositionAbsolute(n) => n.write_csi(f, "d")?,
Cursor::LinePositionBackward(n) => n.write_csi(f, "k")?,
Cursor::LinePositionForward(n) => n.write_csi(f, "e")?,
Cursor::SetTopAndBottomMargins { top, bottom } => {
if top.as_one_based() == 1 && bottom.as_one_based() == u32::max_value() {
write!(f, "r")?;
} else {
write!(f, "{};{}r", top, bottom)?;
}
}
Cursor::SetLeftAndRightMargins { left, right } => {
if left.as_one_based() == 1 && right.as_one_based() == u32::max_value() {
write!(f, "s")?;
} else {
write!(f, "{};{}s", left, right)?;
}
}
Cursor::RequestActivePositionReport => write!(f, "6n")?,
Cursor::SaveCursor => write!(f, "s")?,
Cursor::RestoreCursor => write!(f, "u")?,
Cursor::CursorStyle(style) => write!(f, "{} q", *style as u8)?,
}
Ok(())
}
}
trait ParseParams: Sized {
fn parse_params(params: &[CsiParam]) -> Result<Self, ()>;
}
impl ParseParams for u32 {
fn parse_params(params: &[CsiParam]) -> Result<u32, ()> {
match params {
[] => Ok(1),
[p] => to_1b_u32(p),
_ => Err(()),
}
}
}
impl ParseParams for OneBased {
fn parse_params(params: &[CsiParam]) -> Result<OneBased, ()> {
match params {
[] => Ok(OneBased::new(1)),
[p] => OneBased::from_esc_param(p),
_ => Err(()),
}
}
}
impl ParseParams for (OneBased, OneBased) {
fn parse_params(params: &[CsiParam]) -> Result<(OneBased, OneBased), ()> {
match params {
[] => Ok((OneBased::new(1), OneBased::new(1))),
[p] => Ok((OneBased::from_esc_param(p)?, OneBased::new(1))),
[a, CsiParam::P(b';'), b] => {
Ok((OneBased::from_esc_param(a)?, OneBased::from_esc_param(b)?))
}
[CsiParam::P(b';'), b] => Ok((OneBased::new(1), OneBased::from_esc_param(b)?)),
_ => Err(()),
}
}
}
trait ParamEnum: FromPrimitive {
fn default() -> Self;
}
impl<T: ParamEnum> ParseParams for T {
fn parse_params(params: &[CsiParam]) -> Result<Self, ()> {
match params {
[] => Ok(ParamEnum::default()),
[CsiParam::Integer(i)] => FromPrimitive::from_i64(*i).ok_or(()),
_ => Err(()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, FromPrimitive, Copy, ToPrimitive)]
pub enum CursorTabulationControl {
SetCharacterTabStopAtActivePosition = 0,
SetLineTabStopAtActiveLine = 1,
ClearCharacterTabStopAtActivePosition = 2,
ClearLineTabstopAtActiveLine = 3,
ClearAllCharacterTabStopsAtActiveLine = 4,
ClearAllCharacterTabStops = 5,
ClearAllLineTabStops = 6,
}
impl ParamEnum for CursorTabulationControl {
fn default() -> Self {
CursorTabulationControl::SetCharacterTabStopAtActivePosition
}
}
#[derive(Debug, Clone, PartialEq, Eq, FromPrimitive, Copy, ToPrimitive)]
pub enum TabulationClear {
ClearCharacterTabStopAtActivePosition = 0,
ClearLineTabStopAtActiveLine = 1,
ClearCharacterTabStopsAtActiveLine = 2,
ClearAllCharacterTabStops = 3,
ClearAllLineTabStops = 4,
ClearAllTabStops = 5,
}
impl ParamEnum for TabulationClear {
fn default() -> Self {
TabulationClear::ClearCharacterTabStopAtActivePosition
}
}
#[derive(Debug, Clone, PartialEq, Eq, FromPrimitive, Copy, ToPrimitive)]
pub enum EraseInLine {
EraseToEndOfLine = 0,
EraseToStartOfLine = 1,
EraseLine = 2,
}
impl ParamEnum for EraseInLine {
fn default() -> Self {
EraseInLine::EraseToEndOfLine
}
}
#[derive(Debug, Clone, PartialEq, Eq, FromPrimitive, Copy, ToPrimitive)]
pub enum EraseInDisplay {
EraseToEndOfDisplay = 0,
EraseToStartOfDisplay = 1,
EraseDisplay = 2,
EraseScrollback = 3,
}
impl ParamEnum for EraseInDisplay {
fn default() -> Self {
EraseInDisplay::EraseToEndOfDisplay
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Sgr {
Reset,
Intensity(Intensity),
Underline(Underline),
UnderlineColor(ColorSpec),
Blink(Blink),
Italic(bool),
Inverse(bool),
Invisible(bool),
StrikeThrough(bool),
Font(Font),
Foreground(ColorSpec),
Background(ColorSpec),
Overline(bool),
VerticalAlign(VerticalAlign),
}
#[cfg(all(test, target_pointer_width = "64"))]
#[test]
fn sgr_size() {
assert_eq!(std::mem::size_of::<Intensity>(), 1);
assert_eq!(std::mem::size_of::<Underline>(), 1);
assert_eq!(std::mem::size_of::<ColorSpec>(), 20);
assert_eq!(std::mem::size_of::<Blink>(), 1);
assert_eq!(std::mem::size_of::<Font>(), 2);
}
impl Display for Sgr {
fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
macro_rules! code {
($t:ident) => {
write!(f, "{}m", SgrCode::$t as i64)?
};
}
macro_rules! ansi_color {
($idx:expr, $eightbit:ident, $( ($Ansi:ident, $code:ident) ),*) => {
if let Some(ansi) = FromPrimitive::from_u8($idx) {
match ansi {
$(AnsiColor::$Ansi => code!($code) ,)*
}
} else {
write!(f, "{}:5:{}m", SgrCode::$eightbit as i64, $idx)?
}
}
}
match self {
Sgr::Reset => code!(Reset),
Sgr::Intensity(Intensity::Bold) => code!(IntensityBold),
Sgr::Intensity(Intensity::Half) => code!(IntensityDim),
Sgr::Intensity(Intensity::Normal) => code!(NormalIntensity),
Sgr::Underline(Underline::Single) => code!(UnderlineOn),
Sgr::Underline(Underline::Double) => code!(UnderlineDouble),
Sgr::Underline(Underline::Curly) => write!(f, "4:3m")?,
Sgr::Underline(Underline::Dotted) => write!(f, "4:4m")?,
Sgr::Underline(Underline::Dashed) => write!(f, "4:5m")?,
Sgr::Underline(Underline::None) => code!(UnderlineOff),
Sgr::Blink(Blink::Slow) => code!(BlinkOn),
Sgr::Blink(Blink::Rapid) => code!(RapidBlinkOn),
Sgr::Blink(Blink::None) => code!(BlinkOff),
Sgr::Italic(true) => code!(ItalicOn),
Sgr::Italic(false) => code!(ItalicOff),
Sgr::Inverse(true) => code!(InverseOn),
Sgr::Inverse(false) => code!(InverseOff),
Sgr::Invisible(true) => code!(InvisibleOn),
Sgr::Invisible(false) => code!(InvisibleOff),
Sgr::StrikeThrough(true) => code!(StrikeThroughOn),
Sgr::StrikeThrough(false) => code!(StrikeThroughOff),
Sgr::Overline(true) => code!(OverlineOn),
Sgr::Overline(false) => code!(OverlineOff),
Sgr::VerticalAlign(VerticalAlign::BaseLine) => code!(VerticalAlignBaseLine),
Sgr::VerticalAlign(VerticalAlign::SuperScript) => code!(VerticalAlignSuperScript),
Sgr::VerticalAlign(VerticalAlign::SubScript) => code!(VerticalAlignSubScript),
Sgr::Font(Font::Default) => code!(DefaultFont),
Sgr::Font(Font::Alternate(1)) => code!(AltFont1),
Sgr::Font(Font::Alternate(2)) => code!(AltFont2),
Sgr::Font(Font::Alternate(3)) => code!(AltFont3),
Sgr::Font(Font::Alternate(4)) => code!(AltFont4),
Sgr::Font(Font::Alternate(5)) => code!(AltFont5),
Sgr::Font(Font::Alternate(6)) => code!(AltFont6),
Sgr::Font(Font::Alternate(7)) => code!(AltFont7),
Sgr::Font(Font::Alternate(8)) => code!(AltFont8),
Sgr::Font(Font::Alternate(9)) => code!(AltFont9),
Sgr::Font(_) => { }
Sgr::Foreground(ColorSpec::Default) => code!(ForegroundDefault),
Sgr::Background(ColorSpec::Default) => code!(BackgroundDefault),
Sgr::Foreground(ColorSpec::PaletteIndex(idx)) => ansi_color!(
*idx,
ForegroundColor,
(Black, ForegroundBlack),
(Maroon, ForegroundRed),
(Green, ForegroundGreen),
(Olive, ForegroundYellow),
(Navy, ForegroundBlue),
(Purple, ForegroundMagenta),
(Teal, ForegroundCyan),
(Silver, ForegroundWhite),
(Grey, ForegroundBrightBlack),
(Red, ForegroundBrightRed),
(Lime, ForegroundBrightGreen),
(Yellow, ForegroundBrightYellow),
(Blue, ForegroundBrightBlue),
(Fuchsia, ForegroundBrightMagenta),
(Aqua, ForegroundBrightCyan),
(White, ForegroundBrightWhite)
),
Sgr::Foreground(ColorSpec::TrueColor(c)) => {
let (red, green, blue, alpha) = c.to_srgb_u8();
if alpha == 255 {
write!(
f,
"{}:2::{}:{}:{}m",
SgrCode::ForegroundColor as i64,
red,
green,
blue
)?
} else {
write!(
f,
"{}:6::{}:{}:{}:{}m",
SgrCode::ForegroundColor as i64,
red,
green,
blue,
alpha
)?
}
}
Sgr::Background(ColorSpec::PaletteIndex(idx)) => ansi_color!(
*idx,
BackgroundColor,
(Black, BackgroundBlack),
(Maroon, BackgroundRed),
(Green, BackgroundGreen),
(Olive, BackgroundYellow),
(Navy, BackgroundBlue),
(Purple, BackgroundMagenta),
(Teal, BackgroundCyan),
(Silver, BackgroundWhite),
(Grey, BackgroundBrightBlack),
(Red, BackgroundBrightRed),
(Lime, BackgroundBrightGreen),
(Yellow, BackgroundBrightYellow),
(Blue, BackgroundBrightBlue),
(Fuchsia, BackgroundBrightMagenta),
(Aqua, BackgroundBrightCyan),
(White, BackgroundBrightWhite)
),
Sgr::Background(ColorSpec::TrueColor(c)) => {
let (red, green, blue, alpha) = c.to_srgb_u8();
if alpha == 255 {
write!(
f,
"{}:2::{}:{}:{}m",
SgrCode::BackgroundColor as i64,
red,
green,
blue
)?
} else {
write!(
f,
"{}:6::{}:{}:{}:{}m",
SgrCode::BackgroundColor as i64,
red,
green,
blue,
alpha
)?
}
}
Sgr::UnderlineColor(ColorSpec::Default) => code!(ResetUnderlineColor),
Sgr::UnderlineColor(ColorSpec::TrueColor(c)) => {
let (red, green, blue, alpha) = c.to_srgb_u8();
if alpha == 255 {
write!(
f,
"{}:2::{}:{}:{}m",
SgrCode::UnderlineColor as i64,
red,
green,
blue
)?
} else {
write!(
f,
"{}:6::{}:{}:{}:{}m",
SgrCode::UnderlineColor as i64,
red,
green,
blue,
alpha
)?
}
}
Sgr::UnderlineColor(ColorSpec::PaletteIndex(idx)) => {
write!(f, "{}:5:{}m", SgrCode::UnderlineColor as i64, *idx)?
}
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Font {
Default,
Alternate(u8),
}
struct CSIParser<'a> {
parameters_truncated: bool,
control: char,
params: Option<&'a [CsiParam]>,
orig_params: &'a [CsiParam],
}
impl CSI {
pub fn parse<'a>(
params: &'a [CsiParam],
parameters_truncated: bool,
control: char,
) -> impl Iterator<Item = CSI> + 'a {
CSIParser {
parameters_truncated,
control,
params: Some(params),
orig_params: params,
}
}
}
fn to_u8(v: &CsiParam) -> Result<u8, ()> {
match v {
CsiParam::P(_) => Err(()),
CsiParam::Integer(v) => {
if *v <= i64::from(u8::max_value()) {
Ok(*v as u8)
} else {
Err(())
}
}
}
}
fn to_1b_u32(v: &CsiParam) -> Result<u32, ()> {
match v {
CsiParam::Integer(v) if *v == 0 => Ok(1),
CsiParam::Integer(v) if *v > 0 && *v <= i64::from(u32::max_value()) => Ok(*v as u32),
_ => Err(()),
}
}
struct Cracked {
params: Vec<Option<CsiParam>>,
}
impl Cracked {
pub fn parse(params: &[CsiParam]) -> Result<Self, ()> {
let mut res = vec![];
let mut iter = params.iter().peekable();
while let Some(p) = iter.next() {
match p {
CsiParam::P(b';') => {
res.push(None);
}
CsiParam::Integer(_) => {
res.push(Some(p.clone()));
if let Some(CsiParam::P(b';')) = iter.peek() {
iter.next();
}
}
_ => return Err(()),
}
}
Ok(Self { params: res })
}
pub fn get(&self, idx: usize) -> Option<&CsiParam> {
self.params.get(idx)?.as_ref()
}
pub fn opt_int(&self, idx: usize) -> Option<i64> {
self.get(idx).and_then(CsiParam::as_integer)
}
pub fn int(&self, idx: usize) -> Result<i64, ()> {
self.get(idx).and_then(CsiParam::as_integer).ok_or(())
}
pub fn len(&self) -> usize {
self.params.len()
}
}
macro_rules! noparams {
($ns:ident, $variant:ident, $params:expr) => {{
if $params.len() != 0 {
Err(())
} else {
Ok(CSI::$ns($ns::$variant))
}
}};
}
macro_rules! parse {
($ns:ident, $variant:ident, $params:expr) => {{
let value = ParseParams::parse_params($params)?;
Ok(CSI::$ns($ns::$variant(value)))
}};
($ns:ident, $variant:ident, $first:ident, $second:ident, $params:expr) => {{
let (p1, p2): (OneBased, OneBased) = ParseParams::parse_params($params)?;
Ok(CSI::$ns($ns::$variant {
$first: p1,
$second: p2,
}))
}};
}
impl<'a> CSIParser<'a> {
fn parse_next(&mut self, params: &'a [CsiParam]) -> Result<CSI, ()> {
match (self.control, self.orig_params) {
('k', [.., CsiParam::P(b' ')]) => self.select_character_path(params),
('q', [.., CsiParam::P(b' ')]) => self.cursor_style(params),
('y', [.., CsiParam::P(b'*')]) => self.checksum_area(params),
('c', [CsiParam::P(b'='), ..]) => self
.req_tertiary_device_attributes(params)
.map(|dev| CSI::Device(Box::new(dev))),
('c', [CsiParam::P(b'>'), ..]) => self
.req_secondary_device_attributes(params)
.map(|dev| CSI::Device(Box::new(dev))),
('m', [CsiParam::P(b'<'), ..]) | ('M', [CsiParam::P(b'<'), ..]) => {
self.mouse_sgr1006(params).map(CSI::Mouse)
}
('c', [CsiParam::P(b'?'), ..]) => self
.secondary_device_attributes(params)
.map(|dev| CSI::Device(Box::new(dev))),
('S', [CsiParam::P(b'?'), ..]) => XtSmGraphics::parse(params),
('p', [CsiParam::Integer(_), CsiParam::P(b'$')])
| ('p', [CsiParam::P(b'?'), CsiParam::Integer(_), CsiParam::P(b'$')]) => {
self.decrqm(params)
}
('h', [CsiParam::P(b'?'), ..]) => self
.dec(self.focus(params, 1, 0))
.map(|mode| CSI::Mode(Mode::SetDecPrivateMode(mode))),
('l', [CsiParam::P(b'?'), ..]) => self
.dec(self.focus(params, 1, 0))
.map(|mode| CSI::Mode(Mode::ResetDecPrivateMode(mode))),
('r', [CsiParam::P(b'?'), ..]) => self
.dec(self.focus(params, 1, 0))
.map(|mode| CSI::Mode(Mode::RestoreDecPrivateMode(mode))),
('q', [CsiParam::P(b'>'), ..]) => self
.req_terminal_name_and_version(params)
.map(|dev| CSI::Device(Box::new(dev))),
('s', [CsiParam::P(b'?'), ..]) => self
.dec(self.focus(params, 1, 0))
.map(|mode| CSI::Mode(Mode::SaveDecPrivateMode(mode))),
('m', [CsiParam::P(b'>'), ..]) => self.xterm_key_modifier(params),
('p', [CsiParam::P(b'!')]) => Ok(CSI::Device(Box::new(Device::SoftReset))),
('u', [CsiParam::P(b'='), CsiParam::Integer(flags)]) => {
Ok(CSI::Keyboard(Keyboard::SetKittyState {
flags: KittyKeyboardFlags::from_bits_truncate(
(*flags).try_into().map_err(|_| ())?,
),
mode: KittyKeyboardMode::AssignAll,
}))
}
(
'u',
[CsiParam::P(b'='), CsiParam::Integer(flags), CsiParam::P(b';'), CsiParam::Integer(mode)],
) => Ok(CSI::Keyboard(Keyboard::SetKittyState {
flags: KittyKeyboardFlags::from_bits_truncate((*flags).try_into().map_err(|_| ())?),
mode: match *mode {
1 => KittyKeyboardMode::AssignAll,
2 => KittyKeyboardMode::SetSpecified,
3 => KittyKeyboardMode::ClearSpecified,
_ => return Err(()),
},
})),
('u', [CsiParam::P(b'>')]) => Ok(CSI::Keyboard(Keyboard::PushKittyState {
flags: KittyKeyboardFlags::NONE,
mode: KittyKeyboardMode::AssignAll,
})),
('u', [CsiParam::P(b'>'), CsiParam::Integer(flags)]) => {
Ok(CSI::Keyboard(Keyboard::PushKittyState {
flags: KittyKeyboardFlags::from_bits_truncate(
(*flags).try_into().map_err(|_| ())?,
),
mode: KittyKeyboardMode::AssignAll,
}))
}
(
'u',
[CsiParam::P(b'>'), CsiParam::Integer(flags), CsiParam::P(b';'), CsiParam::Integer(mode)],
) => Ok(CSI::Keyboard(Keyboard::PushKittyState {
flags: KittyKeyboardFlags::from_bits_truncate((*flags).try_into().map_err(|_| ())?),
mode: match *mode {
1 => KittyKeyboardMode::AssignAll,
2 => KittyKeyboardMode::SetSpecified,
3 => KittyKeyboardMode::ClearSpecified,
_ => return Err(()),
},
})),
('u', [CsiParam::P(b'?')]) => Ok(CSI::Keyboard(Keyboard::QueryKittySupport)),
('u', [CsiParam::P(b'?'), CsiParam::Integer(flags)]) => {
Ok(CSI::Keyboard(Keyboard::ReportKittyState(
KittyKeyboardFlags::from_bits_truncate((*flags).try_into().map_err(|_| ())?),
)))
}
('u', [CsiParam::P(b'<'), CsiParam::Integer(how_many)]) => Ok(CSI::Keyboard(
Keyboard::PopKittyState((*how_many).try_into().map_err(|_| ())?),
)),
('u', [CsiParam::P(b'<')]) => Ok(CSI::Keyboard(Keyboard::PopKittyState(1))),
_ => match self.control {
'c' => self
.req_primary_device_attributes(params)
.map(|dev| CSI::Device(Box::new(dev))),
'@' => parse!(Edit, InsertCharacter, params),
'`' => parse!(Cursor, CharacterPositionAbsolute, params),
'A' => parse!(Cursor, Up, params),
'B' => parse!(Cursor, Down, params),
'C' => parse!(Cursor, Right, params),
'D' => parse!(Cursor, Left, params),
'E' => parse!(Cursor, NextLine, params),
'F' => parse!(Cursor, PrecedingLine, params),
'G' => parse!(Cursor, CharacterAbsolute, params),
'H' => parse!(Cursor, Position, line, col, params),
'I' => parse!(Cursor, ForwardTabulation, params),
'J' => parse!(Edit, EraseInDisplay, params),
'K' => parse!(Edit, EraseInLine, params),
'L' => parse!(Edit, InsertLine, params),
'M' => parse!(Edit, DeleteLine, params),
'P' => parse!(Edit, DeleteCharacter, params),
'R' => parse!(Cursor, ActivePositionReport, line, col, params),
'S' => parse!(Edit, ScrollUp, params),
'T' => parse!(Edit, ScrollDown, params),
'W' => parse!(Cursor, TabulationControl, params),
'X' => parse!(Edit, EraseCharacter, params),
'Y' => parse!(Cursor, LineTabulation, params),
'Z' => parse!(Cursor, BackwardTabulation, params),
'a' => parse!(Cursor, CharacterPositionForward, params),
'b' => parse!(Edit, Repeat, params),
'd' => parse!(Cursor, LinePositionAbsolute, params),
'e' => parse!(Cursor, LinePositionForward, params),
'f' => parse!(Cursor, CharacterAndLinePosition, line, col, params),
'g' => parse!(Cursor, TabulationClear, params),
'h' => self
.terminal_mode(params)
.map(|mode| CSI::Mode(Mode::SetMode(mode))),
'j' => parse!(Cursor, CharacterPositionBackward, params),
'k' => parse!(Cursor, LinePositionBackward, params),
'l' => self
.terminal_mode(params)
.map(|mode| CSI::Mode(Mode::ResetMode(mode))),
'm' => self.sgr(params).map(CSI::Sgr),
'n' => self.dsr(params),
'r' => self.decstbm(params),
's' => self.decslrm(params),
't' => self.window(params).map(|p| CSI::Window(Box::new(p))),
'u' => noparams!(Cursor, RestoreCursor, params),
'x' => self
.req_terminal_parameters(params)
.map(|dev| CSI::Device(Box::new(dev))),
_ => Err(()),
},
}
}
fn advance_by<T>(&mut self, n: usize, params: &'a [CsiParam], result: T) -> T {
let n = if matches!(params.get(n), Some(CsiParam::P(b';'))) {
n + 1
} else {
n
};
let (_, next) = params.split_at(n);
if !next.is_empty() {
self.params = Some(next);
}
result
}
fn focus(&self, params: &'a [CsiParam], from_start: usize, from_end: usize) -> &'a [CsiParam] {
if params == self.orig_params {
let len = params.len();
¶ms[from_start..len - from_end]
} else {
params
}
}
fn select_character_path(&mut self, params: &'a [CsiParam]) -> Result<CSI, ()> {
fn path(n: i64) -> Result<CharacterPath, ()> {
Ok(match n {
0 => CharacterPath::ImplementationDefault,
1 => CharacterPath::LeftToRightOrTopToBottom,
2 => CharacterPath::RightToLeftOrBottomToTop,
_ => return Err(()),
})
}
match params {
[CsiParam::P(b' ')] => Ok(self.advance_by(
1,
params,
CSI::SelectCharacterPath(CharacterPath::ImplementationDefault, 0),
)),
[CsiParam::Integer(a), CsiParam::P(b' ')] => {
Ok(self.advance_by(2, params, CSI::SelectCharacterPath(path(*a)?, 0)))
}
[CsiParam::Integer(a), CsiParam::P(b';'), CsiParam::Integer(b), CsiParam::P(b' ')] => {
Ok(self.advance_by(4, params, CSI::SelectCharacterPath(path(*a)?, *b)))
}
_ => Err(()),
}
}
fn cursor_style(&mut self, params: &'a [CsiParam]) -> Result<CSI, ()> {
match params {
[CsiParam::Integer(p), CsiParam::P(b' ')] => match FromPrimitive::from_i64(*p) {
None => Err(()),
Some(style) => {
Ok(self.advance_by(2, params, CSI::Cursor(Cursor::CursorStyle(style))))
}
},
_ => Err(()),
}
}
fn checksum_area(&mut self, params: &'a [CsiParam]) -> Result<CSI, ()> {
let params = Cracked::parse(¶ms[..params.len() - 1])?;
let request_id = params.int(0)?;
let page_number = params.int(1)?;
let top = OneBased::from_optional_esc_param(params.get(2))?;
let left = OneBased::from_optional_esc_param(params.get(3))?;
let bottom = OneBased::from_optional_esc_param(params.get(4))?;
let right = OneBased::from_optional_esc_param(params.get(5))?;
Ok(CSI::Window(Box::new(Window::ChecksumRectangularArea {
request_id,
page_number,
top,
left,
bottom,
right,
})))
}
fn dsr(&mut self, params: &'a [CsiParam]) -> Result<CSI, ()> {
match params {
[CsiParam::Integer(5)] => {
Ok(self.advance_by(1, params, CSI::Device(Box::new(Device::StatusReport))))
}
[CsiParam::Integer(6)] => {
Ok(self.advance_by(1, params, CSI::Cursor(Cursor::RequestActivePositionReport)))
}
_ => Err(()),
}
}
fn decstbm(&mut self, params: &'a [CsiParam]) -> Result<CSI, ()> {
match params {
[] => Ok(CSI::Cursor(Cursor::SetTopAndBottomMargins {
top: OneBased::new(1),
bottom: OneBased::new(u32::max_value()),
})),
[p] => Ok(self.advance_by(
1,
params,
CSI::Cursor(Cursor::SetTopAndBottomMargins {
top: OneBased::from_esc_param(p)?,
bottom: OneBased::new(u32::max_value()),
}),
)),
[a, CsiParam::P(b';'), b] => Ok(self.advance_by(
3,
params,
CSI::Cursor(Cursor::SetTopAndBottomMargins {
top: OneBased::from_esc_param(a)?,
bottom: OneBased::from_esc_param_with_big_default(b)?,
}),
)),
[CsiParam::P(b';'), b] => Ok(self.advance_by(
2,
params,
CSI::Cursor(Cursor::SetTopAndBottomMargins {
top: OneBased::new(1),
bottom: OneBased::from_esc_param_with_big_default(b)?,
}),
)),
_ => Err(()),
}
}
fn xterm_key_modifier(&mut self, params: &'a [CsiParam]) -> Result<CSI, ()> {
match params {
[CsiParam::P(b'>'), a, CsiParam::P(b';'), b] => {
let resource = XtermKeyModifierResource::parse(a.as_integer().ok_or_else(|| ())?)
.ok_or_else(|| ())?;
Ok(self.advance_by(
4,
params,
CSI::Mode(Mode::XtermKeyMode {
resource,
value: Some(b.as_integer().ok_or_else(|| ())?),
}),
))
}
[CsiParam::P(b'>'), a, CsiParam::P(b';')] => {
let resource = XtermKeyModifierResource::parse(a.as_integer().ok_or_else(|| ())?)
.ok_or_else(|| ())?;
Ok(self.advance_by(
3,
params,
CSI::Mode(Mode::XtermKeyMode {
resource,
value: None,
}),
))
}
[CsiParam::P(b'>'), p] => {
let resource = XtermKeyModifierResource::parse(p.as_integer().ok_or_else(|| ())?)
.ok_or_else(|| ())?;
Ok(self.advance_by(
2,
params,
CSI::Mode(Mode::XtermKeyMode {
resource,
value: None,
}),
))
}
_ => Err(()),
}
}
fn decslrm(&mut self, params: &'a [CsiParam]) -> Result<CSI, ()> {
match params {
[] => {
Ok(CSI::Cursor(Cursor::SaveCursor))
}
[p] => Ok(self.advance_by(
1,
params,
CSI::Cursor(Cursor::SetLeftAndRightMargins {
left: OneBased::from_esc_param(p)?,
right: OneBased::new(u32::max_value()),
}),
)),
[a, CsiParam::P(b';'), b] => Ok(self.advance_by(
3,
params,
CSI::Cursor(Cursor::SetLeftAndRightMargins {
left: OneBased::from_esc_param(a)?,
right: OneBased::from_esc_param(b)?,
}),
)),
[CsiParam::P(b';'), b] => Ok(self.advance_by(
2,
params,
CSI::Cursor(Cursor::SetLeftAndRightMargins {
left: OneBased::new(1),
right: OneBased::from_esc_param(b)?,
}),
)),
_ => Err(()),
}
}
fn req_primary_device_attributes(&mut self, params: &'a [CsiParam]) -> Result<Device, ()> {
match params {
[] => Ok(Device::RequestPrimaryDeviceAttributes),
[CsiParam::Integer(0)] => {
Ok(self.advance_by(1, params, Device::RequestPrimaryDeviceAttributes))
}
_ => Err(()),
}
}
fn req_terminal_name_and_version(&mut self, params: &'a [CsiParam]) -> Result<Device, ()> {
match params {
[_] => Ok(Device::RequestTerminalNameAndVersion),
[_, CsiParam::Integer(0)] => {
Ok(self.advance_by(2, params, Device::RequestTerminalNameAndVersion))
}
_ => Err(()),
}
}
fn req_secondary_device_attributes(&mut self, params: &'a [CsiParam]) -> Result<Device, ()> {
match params {
[CsiParam::P(b'>')] => Ok(Device::RequestSecondaryDeviceAttributes),
[CsiParam::P(b'>'), CsiParam::Integer(0)] => {
Ok(self.advance_by(2, params, Device::RequestSecondaryDeviceAttributes))
}
_ => Err(()),
}
}
fn req_tertiary_device_attributes(&mut self, params: &'a [CsiParam]) -> Result<Device, ()> {
match params {
[CsiParam::P(b'=')] => Ok(Device::RequestTertiaryDeviceAttributes),
[CsiParam::P(b'='), CsiParam::Integer(0)] => {
Ok(self.advance_by(2, params, Device::RequestTertiaryDeviceAttributes))
}
_ => Err(()),
}
}
fn secondary_device_attributes(&mut self, params: &'a [CsiParam]) -> Result<Device, ()> {
match params {
[_, CsiParam::Integer(1), CsiParam::P(b';'), CsiParam::Integer(0)] => Ok(self
.advance_by(
4,
params,
Device::DeviceAttributes(DeviceAttributes::Vt101WithNoOptions),
)),
[_, CsiParam::Integer(6)] => {
Ok(self.advance_by(2, params, Device::DeviceAttributes(DeviceAttributes::Vt102)))
}
[_, CsiParam::Integer(1), CsiParam::P(b';'), CsiParam::Integer(2)] => Ok(self
.advance_by(
4,
params,
Device::DeviceAttributes(DeviceAttributes::Vt100WithAdvancedVideoOption),
)),
[_, CsiParam::Integer(62), ..] => Ok(self.advance_by(
params.len(),
params,
Device::DeviceAttributes(DeviceAttributes::Vt220(
DeviceAttributeFlags::from_params(¶ms[2..]),
)),
)),
[_, CsiParam::Integer(63), ..] => Ok(self.advance_by(
params.len(),
params,
Device::DeviceAttributes(DeviceAttributes::Vt320(
DeviceAttributeFlags::from_params(¶ms[2..]),
)),
)),
[_, CsiParam::Integer(64), ..] => Ok(self.advance_by(
params.len(),
params,
Device::DeviceAttributes(DeviceAttributes::Vt420(
DeviceAttributeFlags::from_params(¶ms[2..]),
)),
)),
_ => Err(()),
}
}
fn req_terminal_parameters(&mut self, params: &'a [CsiParam]) -> Result<Device, ()> {
match params {
[] | [CsiParam::Integer(0)] => Ok(Device::RequestTerminalParameters(0)),
[CsiParam::Integer(1)] => Ok(Device::RequestTerminalParameters(1)),
_ => Err(()),
}
}
fn mouse_sgr1006(&mut self, params: &'a [CsiParam]) -> Result<MouseReport, ()> {
let (p0, p1, p2) = match params {
[CsiParam::P(b'<'), CsiParam::Integer(p0), CsiParam::P(b';'), CsiParam::Integer(p1), CsiParam::P(b';'), CsiParam::Integer(p2)] => {
(*p0, *p1, *p2)
}
_ => return Err(()),
};
let button = match (self.control, p0 & 0b110_0011) {
('M', 0) => MouseButton::Button1Press,
('m', 0) => MouseButton::Button1Release,
('M', 1) => MouseButton::Button2Press,
('m', 1) => MouseButton::Button2Release,
('M', 2) => MouseButton::Button3Press,
('m', 2) => MouseButton::Button3Release,
('M', 64) => MouseButton::Button4Press,
('m', 64) => MouseButton::Button4Release,
('M', 65) => MouseButton::Button5Press,
('m', 65) => MouseButton::Button5Release,
('M', 32) => MouseButton::Button1Drag,
('M', 33) => MouseButton::Button2Drag,
('M', 34) => MouseButton::Button3Drag,
('M', 35) => MouseButton::None, ('M', 3) => MouseButton::None, ('m', 3) => MouseButton::None, _ => {
return Err(());
}
};
let mut modifiers = Modifiers::NONE;
if p0 & 4 != 0 {
modifiers |= Modifiers::SHIFT;
}
if p0 & 8 != 0 {
modifiers |= Modifiers::ALT;
}
if p0 & 16 != 0 {
modifiers |= Modifiers::CTRL;
}
Ok(self.advance_by(
6,
params,
MouseReport::SGR1006 {
x: p1 as u16,
y: p2 as u16,
button,
modifiers,
},
))
}
fn decrqm(&mut self, params: &'a [CsiParam]) -> Result<CSI, ()> {
Ok(CSI::Mode(match params {
[CsiParam::Integer(p), CsiParam::P(b'$')] => {
Mode::QueryMode(match FromPrimitive::from_i64(*p) {
None => TerminalMode::Unspecified(p.to_u16().ok_or(())?),
Some(mode) => TerminalMode::Code(mode),
})
}
[CsiParam::P(b'?'), CsiParam::Integer(p), CsiParam::P(b'$')] => {
Mode::QueryDecPrivateMode(match FromPrimitive::from_i64(*p) {
None => DecPrivateMode::Unspecified(p.to_u16().ok_or(())?),
Some(mode) => DecPrivateMode::Code(mode),
})
}
_ => return Err(()),
}))
}
fn dec(&mut self, params: &'a [CsiParam]) -> Result<DecPrivateMode, ()> {
match params {
[CsiParam::Integer(p0), ..] => match FromPrimitive::from_i64(*p0) {
None => Ok(self.advance_by(
1,
params,
DecPrivateMode::Unspecified(p0.to_u16().ok_or(())?),
)),
Some(mode) => Ok(self.advance_by(1, params, DecPrivateMode::Code(mode))),
},
_ => Err(()),
}
}
fn terminal_mode(&mut self, params: &'a [CsiParam]) -> Result<TerminalMode, ()> {
let p0 = params
.get(0)
.and_then(CsiParam::as_integer)
.ok_or_else(|| ())?;
match FromPrimitive::from_i64(p0) {
None => {
Ok(self.advance_by(1, params, TerminalMode::Unspecified(p0.to_u16().ok_or(())?)))
}
Some(mode) => Ok(self.advance_by(1, params, TerminalMode::Code(mode))),
}
}
fn parse_sgr_color(&mut self, params: &'a [CsiParam]) -> Result<ColorSpec, ()> {
match params {
[_, CsiParam::P(b':'), CsiParam::Integer(6), CsiParam::P(b':'),
CsiParam::Integer(_colorspace), CsiParam::P(b':'),
red, CsiParam::P(b':'), green, CsiParam::P(b':'), blue, CsiParam::P(b':'), alpha, ..] => {
let res: SrgbaTuple = (to_u8(red)?, to_u8(green)?, to_u8(blue)?, to_u8(alpha)?).into();
Ok(self.advance_by(13, params, res.into()))
}
[_, CsiParam::P(b':'), CsiParam::Integer(6), CsiParam::P(b':'),
CsiParam::P(b':'),
red, CsiParam::P(b':'), green, CsiParam::P(b':'), blue, CsiParam::P(b':'), alpha, ..] => {
let res: SrgbaTuple = (to_u8(red)?, to_u8(green)?, to_u8(blue)?, to_u8(alpha)?).into();
Ok(self.advance_by(12, params, res.into()))
}
[_, CsiParam::P(b':'), CsiParam::Integer(6), CsiParam::P(b':'), red, CsiParam::P(b':'), green,
CsiParam::P(b':'), blue, CsiParam::P(b':'), alpha, ..] =>
{
let res: SrgbaTuple = (to_u8(red)?, to_u8(green)?, to_u8(blue)?, to_u8(alpha)?).into();
Ok(self.advance_by(11, params, res.into()))
}
[_, CsiParam::P(b':'), CsiParam::Integer(2), CsiParam::P(b':'),
CsiParam::Integer(_colorspace), CsiParam::P(b':'),
red, CsiParam::P(b':'), green, CsiParam::P(b':'), blue, ..] => {
let res = RgbColor::new_8bpc(to_u8(red)?, to_u8(green)?, to_u8(blue)?).into();
Ok(self.advance_by(11, params, res))
}
[_, CsiParam::P(b':'), CsiParam::Integer(2), CsiParam::P(b':'), CsiParam::P(b':'), red, CsiParam::P(b':'), green, CsiParam::P(b':'), blue, ..] => {
let res = RgbColor::new_8bpc(to_u8(red)?, to_u8(green)?, to_u8(blue)?).into();
Ok(self.advance_by(10, params, res))
}
[_, CsiParam::P(b';'), CsiParam::Integer(2), CsiParam::P(b';'), red, CsiParam::P(b';'), green, CsiParam::P(b';'), blue, ..] |
[_, CsiParam::P(b':'), CsiParam::Integer(2), CsiParam::P(b':'), red, CsiParam::P(b':'), green, CsiParam::P(b':'), blue, ..] =>
{
let res = RgbColor::new_8bpc(to_u8(red)?, to_u8(green)?, to_u8(blue)?).into();
Ok(self.advance_by(9, params, res))
}
[_, CsiParam::P(b';'), CsiParam::Integer(5), CsiParam::P(b';'), idx, ..] |
[_, CsiParam::P(b':'), CsiParam::Integer(5), CsiParam::P(b':'), idx, ..] => {
Ok(self.advance_by(5, params, ColorSpec::PaletteIndex(to_u8(idx)?)))
}
_ => Err(()),
}
}
fn window(&mut self, params: &'a [CsiParam]) -> Result<Window, ()> {
let params = Cracked::parse(params)?;
let p = params.int(0)?;
let arg1 = params.opt_int(1);
let arg2 = params.opt_int(2);
match p {
1 => Ok(Window::DeIconify),
2 => Ok(Window::Iconify),
3 => Ok(Window::MoveWindow {
x: arg1.unwrap_or(0),
y: arg2.unwrap_or(0),
}),
4 => Ok(Window::ResizeWindowPixels {
height: arg1,
width: arg2,
}),
5 => Ok(Window::RaiseWindow),
6 => match params.len() {
1 => Ok(Window::LowerWindow),
_ => Ok(Window::ReportCellSizePixelsResponse {
height: arg1,
width: arg2,
}),
},
7 => Ok(Window::RefreshWindow),
8 => Ok(Window::ResizeWindowCells {
height: arg1,
width: arg2,
}),
9 => match arg1 {
Some(0) => Ok(Window::RestoreMaximizedWindow),
Some(1) => Ok(Window::MaximizeWindow),
Some(2) => Ok(Window::MaximizeWindowVertically),
Some(3) => Ok(Window::MaximizeWindowHorizontally),
_ => Err(()),
},
10 => match arg1 {
Some(0) => Ok(Window::UndoFullScreenMode),
Some(1) => Ok(Window::ChangeToFullScreenMode),
Some(2) => Ok(Window::ToggleFullScreen),
_ => Err(()),
},
11 => Ok(Window::ReportWindowState),
13 => match arg1 {
None => Ok(Window::ReportWindowPosition),
Some(2) => Ok(Window::ReportTextAreaPosition),
_ => Err(()),
},
14 => match arg1 {
None => Ok(Window::ReportTextAreaSizePixels),
Some(2) => Ok(Window::ReportWindowSizePixels),
_ => Err(()),
},
15 => Ok(Window::ReportScreenSizePixels),
16 => Ok(Window::ReportCellSizePixels),
18 => Ok(Window::ReportTextAreaSizeCells),
19 => Ok(Window::ReportScreenSizeCells),
20 => Ok(Window::ReportIconLabel),
21 => Ok(Window::ReportWindowTitle),
22 => match arg1 {
Some(0) => Ok(Window::PushIconAndWindowTitle),
Some(1) => Ok(Window::PushIconTitle),
Some(2) => Ok(Window::PushWindowTitle),
_ => Err(()),
},
23 => match arg1 {
Some(0) => Ok(Window::PopIconAndWindowTitle),
Some(1) => Ok(Window::PopIconTitle),
Some(2) => Ok(Window::PopWindowTitle),
_ => Err(()),
},
_ => Err(()),
}
}
fn underline(&mut self, params: &'a [CsiParam]) -> Result<Sgr, ()> {
let (sgr, n) = match params {
[_, CsiParam::P(b':'), CsiParam::Integer(0), ..] => {
(Sgr::Underline(Underline::None), 3)
}
[_, CsiParam::P(b':'), CsiParam::Integer(1), ..] => {
(Sgr::Underline(Underline::Single), 3)
}
[_, CsiParam::P(b':'), CsiParam::Integer(2), ..] => {
(Sgr::Underline(Underline::Double), 3)
}
[_, CsiParam::P(b':'), CsiParam::Integer(3), ..] => {
(Sgr::Underline(Underline::Curly), 3)
}
[_, CsiParam::P(b':'), CsiParam::Integer(4), ..] => {
(Sgr::Underline(Underline::Dotted), 3)
}
[_, CsiParam::P(b':'), CsiParam::Integer(5), ..] => {
(Sgr::Underline(Underline::Dashed), 3)
}
_ => (Sgr::Underline(Underline::Single), 1),
};
Ok(self.advance_by(n, params, sgr))
}
fn sgr(&mut self, params: &'a [CsiParam]) -> Result<Sgr, ()> {
if params.is_empty() {
Ok(Sgr::Reset)
} else {
for p in params {
match p {
CsiParam::P(b';')
| CsiParam::P(b':')
| CsiParam::P(b'?')
| CsiParam::Integer(_) => {}
_ => return Err(()),
}
}
macro_rules! one {
($t:expr) => {
Ok(self.advance_by(1, params, $t))
};
}
macro_rules! two {
($t:expr) => {
Ok(self.advance_by(2, params, $t))
};
}
match ¶ms[0] {
CsiParam::P(b';') => {
self.advance_by(1, params, Ok(Sgr::Reset))
}
CsiParam::P(b'?') if params.len() > 1 => match ¶ms[1] {
CsiParam::Integer(i) => match FromPrimitive::from_i64(*i) {
None => Err(()),
Some(code) => match code {
0 => two!(Sgr::Reset),
4 => two!(Sgr::VerticalAlign(VerticalAlign::SuperScript)),
5 => two!(Sgr::VerticalAlign(VerticalAlign::SubScript)),
6 => two!(Sgr::Overline(true)),
24 => two!(Sgr::VerticalAlign(VerticalAlign::BaseLine)),
26 => two!(Sgr::Overline(false)),
_ => Err(()),
},
},
_ => Err(()),
},
CsiParam::P(_) => Err(()),
CsiParam::Integer(i) => match FromPrimitive::from_i64(*i) {
None => Err(()),
Some(sgr) => match sgr {
SgrCode::Reset => one!(Sgr::Reset),
SgrCode::IntensityBold => one!(Sgr::Intensity(Intensity::Bold)),
SgrCode::IntensityDim => one!(Sgr::Intensity(Intensity::Half)),
SgrCode::NormalIntensity => one!(Sgr::Intensity(Intensity::Normal)),
SgrCode::UnderlineOn => {
self.underline(params) }
SgrCode::UnderlineDouble => one!(Sgr::Underline(Underline::Double)),
SgrCode::UnderlineOff => one!(Sgr::Underline(Underline::None)),
SgrCode::UnderlineColor => {
self.parse_sgr_color(params).map(Sgr::UnderlineColor)
}
SgrCode::ResetUnderlineColor => {
one!(Sgr::UnderlineColor(ColorSpec::default()))
}
SgrCode::BlinkOn => one!(Sgr::Blink(Blink::Slow)),
SgrCode::RapidBlinkOn => one!(Sgr::Blink(Blink::Rapid)),
SgrCode::BlinkOff => one!(Sgr::Blink(Blink::None)),
SgrCode::ItalicOn => one!(Sgr::Italic(true)),
SgrCode::ItalicOff => one!(Sgr::Italic(false)),
SgrCode::VerticalAlignSuperScript => {
one!(Sgr::VerticalAlign(VerticalAlign::SuperScript))
}
SgrCode::VerticalAlignSubScript => {
one!(Sgr::VerticalAlign(VerticalAlign::SubScript))
}
SgrCode::VerticalAlignBaseLine => {
one!(Sgr::VerticalAlign(VerticalAlign::BaseLine))
}
SgrCode::ForegroundColor => {
self.parse_sgr_color(params).map(Sgr::Foreground)
}
SgrCode::ForegroundBlack => one!(Sgr::Foreground(AnsiColor::Black.into())),
SgrCode::ForegroundRed => one!(Sgr::Foreground(AnsiColor::Maroon.into())),
SgrCode::ForegroundGreen => one!(Sgr::Foreground(AnsiColor::Green.into())),
SgrCode::ForegroundYellow => one!(Sgr::Foreground(AnsiColor::Olive.into())),
SgrCode::ForegroundBlue => one!(Sgr::Foreground(AnsiColor::Navy.into())),
SgrCode::ForegroundMagenta => {
one!(Sgr::Foreground(AnsiColor::Purple.into()))
}
SgrCode::ForegroundCyan => one!(Sgr::Foreground(AnsiColor::Teal.into())),
SgrCode::ForegroundWhite => one!(Sgr::Foreground(AnsiColor::Silver.into())),
SgrCode::ForegroundDefault => one!(Sgr::Foreground(ColorSpec::Default)),
SgrCode::ForegroundBrightBlack => {
one!(Sgr::Foreground(AnsiColor::Grey.into()))
}
SgrCode::ForegroundBrightRed => {
one!(Sgr::Foreground(AnsiColor::Red.into()))
}
SgrCode::ForegroundBrightGreen => {
one!(Sgr::Foreground(AnsiColor::Lime.into()))
}
SgrCode::ForegroundBrightYellow => {
one!(Sgr::Foreground(AnsiColor::Yellow.into()))
}
SgrCode::ForegroundBrightBlue => {
one!(Sgr::Foreground(AnsiColor::Blue.into()))
}
SgrCode::ForegroundBrightMagenta => {
one!(Sgr::Foreground(AnsiColor::Fuchsia.into()))
}
SgrCode::ForegroundBrightCyan => {
one!(Sgr::Foreground(AnsiColor::Aqua.into()))
}
SgrCode::ForegroundBrightWhite => {
one!(Sgr::Foreground(AnsiColor::White.into()))
}
SgrCode::BackgroundColor => {
self.parse_sgr_color(params).map(Sgr::Background)
}
SgrCode::BackgroundBlack => one!(Sgr::Background(AnsiColor::Black.into())),
SgrCode::BackgroundRed => one!(Sgr::Background(AnsiColor::Maroon.into())),
SgrCode::BackgroundGreen => one!(Sgr::Background(AnsiColor::Green.into())),
SgrCode::BackgroundYellow => one!(Sgr::Background(AnsiColor::Olive.into())),
SgrCode::BackgroundBlue => one!(Sgr::Background(AnsiColor::Navy.into())),
SgrCode::BackgroundMagenta => {
one!(Sgr::Background(AnsiColor::Purple.into()))
}
SgrCode::BackgroundCyan => one!(Sgr::Background(AnsiColor::Teal.into())),
SgrCode::BackgroundWhite => one!(Sgr::Background(AnsiColor::Silver.into())),
SgrCode::BackgroundDefault => one!(Sgr::Background(ColorSpec::Default)),
SgrCode::BackgroundBrightBlack => {
one!(Sgr::Background(AnsiColor::Grey.into()))
}
SgrCode::BackgroundBrightRed => {
one!(Sgr::Background(AnsiColor::Red.into()))
}
SgrCode::BackgroundBrightGreen => {
one!(Sgr::Background(AnsiColor::Lime.into()))
}
SgrCode::BackgroundBrightYellow => {
one!(Sgr::Background(AnsiColor::Yellow.into()))
}
SgrCode::BackgroundBrightBlue => {
one!(Sgr::Background(AnsiColor::Blue.into()))
}
SgrCode::BackgroundBrightMagenta => {
one!(Sgr::Background(AnsiColor::Fuchsia.into()))
}
SgrCode::BackgroundBrightCyan => {
one!(Sgr::Background(AnsiColor::Aqua.into()))
}
SgrCode::BackgroundBrightWhite => {
one!(Sgr::Background(AnsiColor::White.into()))
}
SgrCode::InverseOn => one!(Sgr::Inverse(true)),
SgrCode::InverseOff => one!(Sgr::Inverse(false)),
SgrCode::InvisibleOn => one!(Sgr::Invisible(true)),
SgrCode::InvisibleOff => one!(Sgr::Invisible(false)),
SgrCode::StrikeThroughOn => one!(Sgr::StrikeThrough(true)),
SgrCode::StrikeThroughOff => one!(Sgr::StrikeThrough(false)),
SgrCode::OverlineOn => one!(Sgr::Overline(true)),
SgrCode::OverlineOff => one!(Sgr::Overline(false)),
SgrCode::DefaultFont => one!(Sgr::Font(Font::Default)),
SgrCode::AltFont1 => one!(Sgr::Font(Font::Alternate(1))),
SgrCode::AltFont2 => one!(Sgr::Font(Font::Alternate(2))),
SgrCode::AltFont3 => one!(Sgr::Font(Font::Alternate(3))),
SgrCode::AltFont4 => one!(Sgr::Font(Font::Alternate(4))),
SgrCode::AltFont5 => one!(Sgr::Font(Font::Alternate(5))),
SgrCode::AltFont6 => one!(Sgr::Font(Font::Alternate(6))),
SgrCode::AltFont7 => one!(Sgr::Font(Font::Alternate(7))),
SgrCode::AltFont8 => one!(Sgr::Font(Font::Alternate(8))),
SgrCode::AltFont9 => one!(Sgr::Font(Font::Alternate(9))),
},
},
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, FromPrimitive)]
pub enum SgrCode {
Reset = 0,
IntensityBold = 1,
IntensityDim = 2,
ItalicOn = 3,
UnderlineOn = 4,
BlinkOn = 5,
RapidBlinkOn = 6,
InverseOn = 7,
InvisibleOn = 8,
StrikeThroughOn = 9,
DefaultFont = 10,
AltFont1 = 11,
AltFont2 = 12,
AltFont3 = 13,
AltFont4 = 14,
AltFont5 = 15,
AltFont6 = 16,
AltFont7 = 17,
AltFont8 = 18,
AltFont9 = 19,
UnderlineDouble = 21,
NormalIntensity = 22,
ItalicOff = 23,
UnderlineOff = 24,
BlinkOff = 25,
InverseOff = 27,
InvisibleOff = 28,
StrikeThroughOff = 29,
ForegroundBlack = 30,
ForegroundRed = 31,
ForegroundGreen = 32,
ForegroundYellow = 33,
ForegroundBlue = 34,
ForegroundMagenta = 35,
ForegroundCyan = 36,
ForegroundWhite = 37,
ForegroundDefault = 39,
BackgroundBlack = 40,
BackgroundRed = 41,
BackgroundGreen = 42,
BackgroundYellow = 43,
BackgroundBlue = 44,
BackgroundMagenta = 45,
BackgroundCyan = 46,
BackgroundWhite = 47,
BackgroundDefault = 49,
OverlineOn = 53,
OverlineOff = 55,
UnderlineColor = 58,
ResetUnderlineColor = 59,
VerticalAlignSuperScript = 73,
VerticalAlignSubScript = 74,
VerticalAlignBaseLine = 75,
ForegroundBrightBlack = 90,
ForegroundBrightRed = 91,
ForegroundBrightGreen = 92,
ForegroundBrightYellow = 93,
ForegroundBrightBlue = 94,
ForegroundBrightMagenta = 95,
ForegroundBrightCyan = 96,
ForegroundBrightWhite = 97,
BackgroundBrightBlack = 100,
BackgroundBrightRed = 101,
BackgroundBrightGreen = 102,
BackgroundBrightYellow = 103,
BackgroundBrightBlue = 104,
BackgroundBrightMagenta = 105,
BackgroundBrightCyan = 106,
BackgroundBrightWhite = 107,
ForegroundColor = 38,
BackgroundColor = 48,
}
impl<'a> Iterator for CSIParser<'a> {
type Item = CSI;
fn next(&mut self) -> Option<CSI> {
let params = match self.params.take() {
None => return None,
Some(params) => params,
};
match self.parse_next(¶ms) {
Ok(csi) => Some(csi),
Err(()) => Some(CSI::Unspecified(Box::new(Unspecified {
params: params.to_vec(),
parameters_truncated: self.parameters_truncated,
control: self.control,
}))),
}
}
}
#[cfg(test)]
mod test {
use super::*;
use std::io::Write;
fn parse(control: char, params: &[i64], expected: &str) -> Vec<CSI> {
let mut cparams = vec![];
for &p in params {
if !cparams.is_empty() {
cparams.push(CsiParam::P(b';'));
}
cparams.push(CsiParam::Integer(p));
}
let res = CSI::parse(&cparams, false, control).collect();
println!("parsed -> {:#?}", res);
assert_eq!(encode(&res), expected);
res
}
fn encode(seq: &Vec<CSI>) -> String {
let mut res = Vec::new();
for s in seq {
write!(res, "{}", s).unwrap();
}
String::from_utf8(res).unwrap()
}
#[test]
fn basic() {
assert_eq!(parse('m', &[], "\x1b[0m"), vec![CSI::Sgr(Sgr::Reset)]);
assert_eq!(parse('m', &[0], "\x1b[0m"), vec![CSI::Sgr(Sgr::Reset)]);
assert_eq!(
parse('m', &[1], "\x1b[1m"),
vec![CSI::Sgr(Sgr::Intensity(Intensity::Bold))]
);
assert_eq!(
parse('m', &[1, 3], "\x1b[1m\x1b[3m"),
vec![
CSI::Sgr(Sgr::Intensity(Intensity::Bold)),
CSI::Sgr(Sgr::Italic(true)),
]
);
assert_eq!(
parse('m', &[1, 3, 1231231], "\x1b[1m\x1b[3m\x1b[1231231m"),
vec![
CSI::Sgr(Sgr::Intensity(Intensity::Bold)),
CSI::Sgr(Sgr::Italic(true)),
CSI::Unspecified(Box::new(Unspecified {
params: [CsiParam::Integer(1231231)].to_vec(),
parameters_truncated: false,
control: 'm',
})),
]
);
assert_eq!(
parse('m', &[1, 1231231, 3], "\x1b[1m\x1b[1231231;3m"),
vec![
CSI::Sgr(Sgr::Intensity(Intensity::Bold)),
CSI::Unspecified(Box::new(Unspecified {
params: [
CsiParam::Integer(1231231),
CsiParam::P(b';'),
CsiParam::Integer(3)
]
.to_vec(),
parameters_truncated: false,
control: 'm',
})),
]
);
assert_eq!(
parse('m', &[1231231, 3], "\x1b[1231231;3m"),
vec![CSI::Unspecified(Box::new(Unspecified {
params: [
CsiParam::Integer(1231231),
CsiParam::P(b';'),
CsiParam::Integer(3)
]
.to_vec(),
parameters_truncated: false,
control: 'm',
}))]
);
}
#[test]
fn blinks() {
assert_eq!(
parse('m', &[5], "\x1b[5m"),
vec![CSI::Sgr(Sgr::Blink(Blink::Slow))]
);
assert_eq!(
parse('m', &[6], "\x1b[6m"),
vec![CSI::Sgr(Sgr::Blink(Blink::Rapid))]
);
assert_eq!(
parse('m', &[25], "\x1b[25m"),
vec![CSI::Sgr(Sgr::Blink(Blink::None))]
);
}
#[test]
fn underlines() {
assert_eq!(
parse('m', &[21], "\x1b[21m"),
vec![CSI::Sgr(Sgr::Underline(Underline::Double))]
);
assert_eq!(
parse('m', &[4], "\x1b[4m"),
vec![CSI::Sgr(Sgr::Underline(Underline::Single))]
);
}
#[test]
fn underline_color() {
assert_eq!(
parse('m', &[58, 2], "\x1b[58;2m"),
vec![CSI::Unspecified(Box::new(Unspecified {
params: [
CsiParam::Integer(58),
CsiParam::P(b';'),
CsiParam::Integer(2)
]
.to_vec(),
parameters_truncated: false,
control: 'm',
}))]
);
assert_eq!(
parse('m', &[58, 2, 255, 255, 255], "\x1b[58:2::255:255:255m"),
vec![CSI::Sgr(Sgr::UnderlineColor(ColorSpec::TrueColor(
(255, 255, 255).into(),
)))]
);
assert_eq!(
parse('m', &[58, 5, 220, 255, 255], "\x1b[58:5:220m\x1b[255;255m"),
vec![
CSI::Sgr(Sgr::UnderlineColor(ColorSpec::PaletteIndex(220))),
CSI::Unspecified(Box::new(Unspecified {
params: [
CsiParam::Integer(255),
CsiParam::P(b';'),
CsiParam::Integer(255)
]
.to_vec(),
parameters_truncated: false,
control: 'm',
})),
]
);
}
#[test]
fn color() {
assert_eq!(
parse('m', &[38, 2], "\x1b[38;2m"),
vec![CSI::Unspecified(Box::new(Unspecified {
params: [
CsiParam::Integer(38),
CsiParam::P(b';'),
CsiParam::Integer(2)
]
.to_vec(),
parameters_truncated: false,
control: 'm',
}))]
);
assert_eq!(
parse('m', &[38, 2, 255, 255, 255], "\x1b[38:2::255:255:255m"),
vec![CSI::Sgr(Sgr::Foreground(ColorSpec::TrueColor(
(255, 255, 255).into(),
)))]
);
assert_eq!(
parse('m', &[38, 5, 220, 255, 255], "\x1b[38:5:220m\x1b[255;255m"),
vec![
CSI::Sgr(Sgr::Foreground(ColorSpec::PaletteIndex(220))),
CSI::Unspecified(Box::new(Unspecified {
params: [
CsiParam::Integer(255),
CsiParam::P(b';'),
CsiParam::Integer(255)
]
.to_vec(),
parameters_truncated: false,
control: 'm',
})),
]
);
}
#[test]
fn edit() {
assert_eq!(
parse('J', &[], "\x1b[J"),
vec![CSI::Edit(Edit::EraseInDisplay(
EraseInDisplay::EraseToEndOfDisplay,
))]
);
assert_eq!(
parse('J', &[0], "\x1b[J"),
vec![CSI::Edit(Edit::EraseInDisplay(
EraseInDisplay::EraseToEndOfDisplay,
))]
);
assert_eq!(
parse('J', &[1], "\x1b[1J"),
vec![CSI::Edit(Edit::EraseInDisplay(
EraseInDisplay::EraseToStartOfDisplay,
))]
);
}
#[test]
fn window() {
assert_eq!(
parse('t', &[6], "\x1b[6t"),
vec![CSI::Window(Box::new(Window::LowerWindow))]
);
assert_eq!(
parse('t', &[6, 15, 7], "\x1b[6;15;7t"),
vec![CSI::Window(Box::new(
Window::ReportCellSizePixelsResponse {
width: Some(7),
height: Some(15)
}
))]
);
}
#[test]
fn cursor() {
assert_eq!(
parse('C', &[], "\x1b[C"),
vec![CSI::Cursor(Cursor::Right(1))]
);
assert_eq!(
parse('C', &[0], "\x1b[C"),
vec![CSI::Cursor(Cursor::Right(1))]
);
assert_eq!(
parse('C', &[1], "\x1b[C"),
vec![CSI::Cursor(Cursor::Right(1))]
);
assert_eq!(
parse('C', &[4], "\x1b[4C"),
vec![CSI::Cursor(Cursor::Right(4))]
);
assert_eq!(
parse('H', &[2], "\x1b[2;1H"),
vec![CSI::Cursor(Cursor::Position {
line: OneBased::new(2),
col: OneBased::new(1)
})]
);
}
#[test]
fn ansiset() {
assert_eq!(
parse('h', &[20], "\x1b[20h"),
vec![CSI::Mode(Mode::SetMode(TerminalMode::Code(
TerminalModeCode::AutomaticNewline
)))]
);
assert_eq!(
parse('l', &[20], "\x1b[20l"),
vec![CSI::Mode(Mode::ResetMode(TerminalMode::Code(
TerminalModeCode::AutomaticNewline
)))]
);
}
#[test]
fn bidi_modes() {
assert_eq!(
parse('h', &[8], "\x1b[8h"),
vec![CSI::Mode(Mode::SetMode(TerminalMode::Code(
TerminalModeCode::BiDirectionalSupportMode
)))]
);
assert_eq!(
parse('l', &[8], "\x1b[8l"),
vec![CSI::Mode(Mode::ResetMode(TerminalMode::Code(
TerminalModeCode::BiDirectionalSupportMode
)))]
);
}
#[test]
fn mouse() {
let res: Vec<_> = CSI::parse(
&[
CsiParam::P(b'<'),
CsiParam::Integer(0),
CsiParam::P(b';'),
CsiParam::Integer(12),
CsiParam::P(b';'),
CsiParam::Integer(300),
],
false,
'M',
)
.collect();
assert_eq!(encode(&res), "\x1b[<0;12;300M");
assert_eq!(
res,
vec![CSI::Mouse(MouseReport::SGR1006 {
x: 12,
y: 300,
button: MouseButton::Button1Press,
modifiers: Modifiers::NONE,
})]
);
}
#[test]
fn soft_reset() {
let res: Vec<_> = CSI::parse(&[CsiParam::P(b'!')], false, 'p').collect();
assert_eq!(encode(&res), "\x1b[!p");
assert_eq!(res, vec![CSI::Device(Box::new(Device::SoftReset))],);
}
#[test]
fn device_attr() {
let res: Vec<_> = CSI::parse(
&[
CsiParam::P(b'?'),
CsiParam::Integer(63),
CsiParam::P(b';'),
CsiParam::Integer(1),
CsiParam::P(b';'),
CsiParam::Integer(2),
CsiParam::P(b';'),
CsiParam::Integer(4),
CsiParam::P(b';'),
CsiParam::Integer(6),
CsiParam::P(b';'),
CsiParam::Integer(9),
CsiParam::P(b';'),
CsiParam::Integer(15),
CsiParam::P(b';'),
CsiParam::Integer(22),
],
false,
'c',
)
.collect();
assert_eq!(
res,
vec![CSI::Device(Box::new(Device::DeviceAttributes(
DeviceAttributes::Vt320(DeviceAttributeFlags::new(vec![
DeviceAttribute::Code(DeviceAttributeCodes::Columns132),
DeviceAttribute::Code(DeviceAttributeCodes::Printer),
DeviceAttribute::Code(DeviceAttributeCodes::SixelGraphics),
DeviceAttribute::Code(DeviceAttributeCodes::SelectiveErase),
DeviceAttribute::Code(DeviceAttributeCodes::NationalReplacementCharsets),
DeviceAttribute::Code(DeviceAttributeCodes::TechnicalCharacters),
DeviceAttribute::Code(DeviceAttributeCodes::AnsiColor),
])),
)))]
);
assert_eq!(encode(&res), "\x1b[?63;1;2;4;6;9;15;22c");
}
}