@ -1,4 +1,8 @@
use std ::fmt ::{ Display , Formatter } ;
use std ::{
fmt ::{ Display , Formatter } ,
iter ::Peekable ,
str ::CharIndices ,
} ;
// Wrapper to avoid unnecessary branching when input doesn't have ANSI escape sequences.
pub struct AnsiStyle {
@ -10,7 +14,7 @@ impl AnsiStyle {
AnsiStyle { attributes : None }
}
pub fn update ( & mut self , sequence : & str ) -> bool {
pub fn update ( & mut self , sequence : EscapeSequence ) -> bool {
match & mut self . attributes {
Some ( a ) = > a . update ( sequence ) ,
None = > {
@ -19,6 +23,13 @@ impl AnsiStyle {
}
}
}
pub fn to_reset_sequence ( & mut self ) -> String {
match & mut self . attributes {
Some ( a ) = > a . to_reset_sequence ( ) ,
None = > String ::new ( ) ,
}
}
}
impl Display for AnsiStyle {
@ -31,6 +42,8 @@ impl Display for AnsiStyle {
}
struct Attributes {
has_sgr_sequences : bool ,
foreground : String ,
background : String ,
underlined : String ,
@ -61,11 +74,20 @@ struct Attributes {
/// ON: ^[9m
/// OFF: ^[29m
strike : String ,
/// The hyperlink sequence.
/// FORMAT: \x1B]8;{ID};{URL}\e\\
///
/// `\e\\` may be replaced with BEL `\x07`.
/// Setting both {ID} and {URL} to an empty string represents no hyperlink.
hyperlink : String ,
}
impl Attributes {
pub fn new ( ) -> Self {
Attributes {
has_sgr_sequences : false ,
foreground : "" . to_owned ( ) ,
background : "" . to_owned ( ) ,
underlined : "" . to_owned ( ) ,
@ -76,34 +98,56 @@ impl Attributes {
underline : "" . to_owned ( ) ,
italic : "" . to_owned ( ) ,
strike : "" . to_owned ( ) ,
hyperlink : "" . to_owned ( ) ,
}
}
/// Update the attributes with an escape sequence.
/// Returns `false` if the sequence is unsupported.
pub fn update ( & mut self , sequence : & str ) -> bool {
let mut chars = sequence . char_indices ( ) . skip ( 1 ) ;
if let Some ( ( _ , t ) ) = chars . next ( ) {
match t {
'(' = > self . update_with_charset ( '(' , chars . map ( | ( _ , c ) | c ) ) ,
')' = > self . update_with_charset ( ')' , chars . map ( | ( _ , c ) | c ) ) ,
'[' = > {
if let Some ( ( i , last ) ) = chars . last ( ) {
// SAFETY: Always starts with ^[ and ends with m.
self . update_with_csi ( last , & sequence [ 2 .. i ] )
} else {
false
pub fn update ( & mut self , sequence : EscapeSequence ) -> bool {
use EscapeSequence ::* ;
match sequence {
Text ( _ ) = > return false ,
Unknown ( _ ) = > { /* defer to update_with_unsupported */ }
OSC {
raw_sequence ,
command ,
..
} = > {
if command . starts_with ( "8;" ) {
return self . update_with_hyperlink ( raw_sequence ) ;
}
/* defer to update_with_unsupported */
}
CSI {
final_byte ,
parameters ,
..
} = > {
match final_byte {
"m" = > return self . update_with_sgr ( parameters ) ,
_ = > {
// NOTE(eth-p): We might want to ignore these, since they involve cursor or buffer manipulation.
/* defer to update_with_unsupported */
}
}
_ = > self . update_with_unsupported ( sequence ) ,
}
} else {
false
NF { nf_sequence , .. } = > {
let mut iter = nf_sequence . chars ( ) ;
match iter . next ( ) {
Some ( '(' ) = > return self . update_with_charset ( '(' , iter ) ,
Some ( ')' ) = > return self . update_with_charset ( ')' , iter ) ,
_ = > { /* defer to update_with_unsupported */ }
}
}
}
self . update_with_unsupported ( sequence . raw ( ) )
}
fn sgr_reset ( & mut self ) {
self . has_sgr_sequences = false ;
self . foreground . clear ( ) ;
self . background . clear ( ) ;
self . underlined . clear ( ) ;
@ -121,6 +165,7 @@ impl Attributes {
. map ( | p | p . parse ::< u16 > ( ) )
. map ( | p | p . unwrap_or ( 0 ) ) ; // Treat errors as 0.
self . has_sgr_sequences = true ;
while let Some ( p ) = iter . next ( ) {
match p {
0 = > self . sgr_reset ( ) ,
@ -149,19 +194,23 @@ impl Attributes {
true
}
fn update_with_csi ( & mut self , finalizer : char , sequence : & str ) -> bool {
if finalizer = = 'm' {
self . update_with_sgr ( sequence )
} else {
false
}
}
fn update_with_unsupported ( & mut self , sequence : & str ) -> bool {
self . unknown_buffer . push_str ( sequence ) ;
false
}
fn update_with_hyperlink ( & mut self , sequence : & str ) -> bool {
if sequence = = "8;;" {
// Empty hyperlink ID and HREF -> end of hyperlink.
self . hyperlink . clear ( ) ;
} else {
self . hyperlink . clear ( ) ;
self . hyperlink . push_str ( sequence ) ;
}
true
}
fn update_with_charset ( & mut self , kind : char , set : impl Iterator < Item = char > ) -> bool {
self . charset = format! ( "\x1B{}{}" , kind , set . take ( 1 ) . collect ::< String > ( ) ) ;
true
@ -179,13 +228,35 @@ impl Attributes {
_ = > format! ( "\x1B[{}m" , color ) ,
}
}
/// Gets an ANSI escape sequence to reset all the known attributes.
pub fn to_reset_sequence ( & self ) -> String {
let mut buf = String ::with_capacity ( 17 ) ;
// TODO: Enable me in a later pull request.
// if self.has_sgr_sequences {
// buf.push_str("\x1B[m");
// }
if ! self . hyperlink . is_empty ( ) {
buf . push_str ( "\x1B]8;;\x1B\\" ) ; // Disable hyperlink.
}
// TODO: Enable me in a later pull request.
// if !self.charset.is_empty() {
// // https://espterm.github.io/docs/VT100%20escape%20codes.html
// buf.push_str("\x1B(B\x1B)B"); // setusg0 and setusg1
// }
buf
}
}
impl Display for Attributes {
fn fmt ( & self , f : & mut Formatter < ' _ > ) -> std ::fmt ::Result {
write! (
f ,
"{}{}{}{}{}{}{}{}{}" ,
"{}{}{}{}{}{}{}{}{} {} ",
self . foreground ,
self . background ,
self . underlined ,
@ -195,6 +266,7 @@ impl Display for Attributes {
self . underline ,
self . italic ,
self . strike ,
self . hyperlink ,
)
}
}
@ -210,3 +282,612 @@ fn join(
. collect ::< Vec < String > > ( )
. join ( delimiter )
}
/// A range of indices for a raw ANSI escape sequence.
#[ derive(Debug, PartialEq) ]
enum EscapeSequenceOffsets {
Text {
start : usize ,
end : usize ,
} ,
Unknown {
start : usize ,
end : usize ,
} ,
NF {
// https://en.wikipedia.org/wiki/ANSI_escape_code#nF_Escape_sequences
start_sequence : usize ,
start : usize ,
end : usize ,
} ,
OSC {
// https://en.wikipedia.org/wiki/ANSI_escape_code#OSC_(Operating_System_Command)_sequences
start_sequence : usize ,
start_command : usize ,
start_terminator : usize ,
end : usize ,
} ,
CSI {
// https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_(Control_Sequence_Introducer)_sequences
start_sequence : usize ,
start_parameters : usize ,
start_intermediates : usize ,
start_final_byte : usize ,
end : usize ,
} ,
}
/// An iterator over the offests of ANSI/VT escape sequences within a string.
///
/// ## Example
///
/// ```ignore
/// let iter = EscapeSequenceOffsetsIterator::new("\x1B[33mThis is yellow text.\x1B[m");
/// ```
struct EscapeSequenceOffsetsIterator < ' a > {
text : & ' a str ,
chars : Peekable < CharIndices < ' a > > ,
}
impl < ' a > EscapeSequenceOffsetsIterator < ' a > {
pub fn new ( text : & ' a str ) -> EscapeSequenceOffsetsIterator < ' a > {
return EscapeSequenceOffsetsIterator {
text ,
chars : text . char_indices ( ) . peekable ( ) ,
} ;
}
/// Takes values from the iterator while the predicate returns true.
/// If the predicate returns false, that value is left.
fn chars_take_while ( & mut self , pred : impl Fn ( char ) -> bool ) -> Option < ( usize , usize ) > {
if self . chars . peek ( ) . is_none ( ) {
return None ;
}
let start = self . chars . peek ( ) . unwrap ( ) . 0 ;
let mut end : usize = start ;
while let Some ( ( i , c ) ) = self . chars . peek ( ) {
if ! pred ( * c ) {
break ;
}
end = * i + c . len_utf8 ( ) ;
self . chars . next ( ) ;
}
Some ( ( start , end ) )
}
fn next_text ( & mut self ) -> Option < EscapeSequenceOffsets > {
match self . chars_take_while ( | c | c ! = '\x1B' ) {
None = > None ,
Some ( ( start , end ) ) = > Some ( EscapeSequenceOffsets ::Text { start , end } ) ,
}
}
fn next_sequence ( & mut self ) -> Option < EscapeSequenceOffsets > {
let ( start_sequence , c ) = self . chars . next ( ) . expect ( "to not be finished" ) ;
match self . chars . peek ( ) {
None = > Some ( EscapeSequenceOffsets ::Unknown {
start : start_sequence ,
end : start_sequence + c . len_utf8 ( ) ,
} ) ,
Some ( ( _ , ']' ) ) = > self . next_osc ( start_sequence ) ,
Some ( ( _ , '[' ) ) = > self . next_csi ( start_sequence ) ,
Some ( ( i , c ) ) = > match c {
'\x20' ..= '\x2F' = > self . next_nf ( start_sequence ) ,
c = > Some ( EscapeSequenceOffsets ::Unknown {
start : start_sequence ,
end : i + c . len_utf8 ( ) ,
} ) ,
} ,
}
}
fn next_osc ( & mut self , start_sequence : usize ) -> Option < EscapeSequenceOffsets > {
let ( osc_open_index , osc_open_char ) = self . chars . next ( ) . expect ( "to not be finished" ) ;
debug_assert_eq! ( osc_open_char , ']' ) ;
let mut start_terminator : usize ;
let mut end_sequence : usize ;
loop {
match self . chars_take_while ( | c | ! matches! ( c , '\x07' | '\x1B' ) ) {
None = > {
start_terminator = self . text . len ( ) ;
end_sequence = start_terminator ;
break ;
}
Some ( ( _ , end ) ) = > {
start_terminator = end ;
end_sequence = end ;
}
}
match self . chars . next ( ) {
Some ( ( ti , '\x07' ) ) = > {
end_sequence = ti + '\x07' . len_utf8 ( ) ;
break ;
}
Some ( ( ti , '\x1B' ) ) = > {
match self . chars . next ( ) {
Some ( ( i , '\\' ) ) = > {
end_sequence = i + '\\' . len_utf8 ( ) ;
break ;
}
None = > {
end_sequence = ti + '\x1B' . len_utf8 ( ) ;
break ;
}
_ = > {
// Repeat, since `\\`(anything) isn't a valid ST.
}
}
}
None = > {
// Prematurely ends.
break ;
}
Some ( ( _ , tc ) ) = > {
panic! ( "this should not be reached: char {:?}" , tc )
}
}
}
Some ( EscapeSequenceOffsets ::OSC {
start_sequence ,
start_command : osc_open_index + osc_open_char . len_utf8 ( ) ,
start_terminator : start_terminator ,
end : end_sequence ,
} )
}
fn next_csi ( & mut self , start_sequence : usize ) -> Option < EscapeSequenceOffsets > {
let ( csi_open_index , csi_open_char ) = self . chars . next ( ) . expect ( "to not be finished" ) ;
debug_assert_eq! ( csi_open_char , '[' ) ;
let start_parameters : usize = csi_open_index + csi_open_char . len_utf8 ( ) ;
// Keep iterating while within the range of `0x30-0x3F`.
let mut start_intermediates : usize = start_parameters ;
if let Some ( ( _ , end ) ) = self . chars_take_while ( | c | matches! ( c , '\x30' ..= '\x3F' ) ) {
start_intermediates = end ;
}
// Keep iterating while within the range of `0x20-0x2F`.
let mut start_final_byte : usize = start_intermediates ;
if let Some ( ( _ , end ) ) = self . chars_take_while ( | c | matches! ( c , '\x20' ..= '\x2F' ) ) {
start_final_byte = end ;
}
// Take the last char.
let end_of_sequence = match self . chars . next ( ) {
None = > start_final_byte ,
Some ( ( i , c ) ) = > i + c . len_utf8 ( ) ,
} ;
Some ( EscapeSequenceOffsets ::CSI {
start_sequence ,
start_parameters ,
start_intermediates ,
start_final_byte ,
end : end_of_sequence ,
} )
}
fn next_nf ( & mut self , start_sequence : usize ) -> Option < EscapeSequenceOffsets > {
let ( nf_open_index , nf_open_char ) = self . chars . next ( ) . expect ( "to not be finished" ) ;
debug_assert! ( matches! ( nf_open_char , '\x20' ..= '\x2F' ) ) ;
let start : usize = nf_open_index ;
let mut end : usize = start ;
// Keep iterating while within the range of `0x20-0x2F`.
match self . chars_take_while ( | c | matches! ( c , '\x20' ..= '\x2F' ) ) {
Some ( ( _ , i ) ) = > end = i ,
None = > {
return Some ( EscapeSequenceOffsets ::NF {
start_sequence ,
start ,
end ,
} )
}
}
// Get the final byte.
match self . chars . next ( ) {
Some ( ( i , c ) ) = > end = i + c . len_utf8 ( ) ,
None = > { }
}
Some ( EscapeSequenceOffsets ::NF {
start_sequence ,
start ,
end ,
} )
}
}
impl < ' a > Iterator for EscapeSequenceOffsetsIterator < ' a > {
type Item = EscapeSequenceOffsets ;
fn next ( & mut self ) -> Option < Self ::Item > {
match self . chars . peek ( ) {
Some ( ( _ , '\x1B' ) ) = > self . next_sequence ( ) ,
Some ( ( _ , _ ) ) = > self . next_text ( ) ,
None = > None ,
}
}
}
/// An iterator over ANSI/VT escape sequences within a string.
///
/// ## Example
///
/// ```ignore
/// let iter = EscapeSequenceIterator::new("\x1B[33mThis is yellow text.\x1B[m");
/// ```
pub struct EscapeSequenceIterator < ' a > {
text : & ' a str ,
offset_iter : EscapeSequenceOffsetsIterator < ' a > ,
}
impl < ' a > EscapeSequenceIterator < ' a > {
pub fn new ( text : & ' a str ) -> EscapeSequenceIterator < ' a > {
return EscapeSequenceIterator {
text ,
offset_iter : EscapeSequenceOffsetsIterator ::new ( text ) ,
} ;
}
}
impl < ' a > Iterator for EscapeSequenceIterator < ' a > {
type Item = EscapeSequence < ' a > ;
fn next ( & mut self ) -> Option < Self ::Item > {
use EscapeSequenceOffsets ::* ;
self . offset_iter . next ( ) . map ( | offsets | match offsets {
Unknown { start , end } = > EscapeSequence ::Unknown ( & self . text [ start .. end ] ) ,
Text { start , end } = > EscapeSequence ::Text ( & self . text [ start .. end ] ) ,
NF {
start_sequence ,
start ,
end ,
} = > EscapeSequence ::NF {
raw_sequence : & self . text [ start_sequence .. end ] ,
nf_sequence : & self . text [ start .. end ] ,
} ,
OSC {
start_sequence ,
start_command ,
start_terminator ,
end ,
} = > EscapeSequence ::OSC {
raw_sequence : & self . text [ start_sequence .. end ] ,
command : & self . text [ start_command .. start_terminator ] ,
terminator : & self . text [ start_terminator .. end ] ,
} ,
CSI {
start_sequence ,
start_parameters ,
start_intermediates ,
start_final_byte ,
end ,
} = > EscapeSequence ::CSI {
raw_sequence : & self . text [ start_sequence .. end ] ,
parameters : & self . text [ start_parameters .. start_intermediates ] ,
intermediates : & self . text [ start_intermediates .. start_final_byte ] ,
final_byte : & self . text [ start_final_byte .. end ] ,
} ,
} )
}
}
/// A parsed ANSI/VT100 escape sequence.
#[ derive(Debug, PartialEq) ]
pub enum EscapeSequence < ' a > {
Text ( & ' a str ) ,
Unknown ( & ' a str ) ,
NF {
raw_sequence : & ' a str ,
nf_sequence : & ' a str ,
} ,
OSC {
raw_sequence : & ' a str ,
command : & ' a str ,
terminator : & ' a str ,
} ,
CSI {
raw_sequence : & ' a str ,
parameters : & ' a str ,
intermediates : & ' a str ,
final_byte : & ' a str ,
} ,
}
impl < ' a > EscapeSequence < ' a > {
pub fn raw ( & self ) -> & ' a str {
use EscapeSequence ::* ;
match * self {
Text ( raw ) = > raw ,
Unknown ( raw ) = > raw ,
NF { raw_sequence , .. } = > raw_sequence ,
OSC { raw_sequence , .. } = > raw_sequence ,
CSI { raw_sequence , .. } = > raw_sequence ,
}
}
}
#[ cfg(test) ]
mod tests {
use crate ::vscreen ::{
EscapeSequence , EscapeSequenceIterator , EscapeSequenceOffsets ,
EscapeSequenceOffsetsIterator ,
} ;
#[ test ]
fn test_escape_sequence_offsets_iterator_parses_text ( ) {
let mut iter = EscapeSequenceOffsetsIterator ::new ( "text" ) ;
assert_eq! (
iter . next ( ) ,
Some ( EscapeSequenceOffsets ::Text { start : 0 , end : 4 } )
) ;
}
#[ test ]
fn test_escape_sequence_offsets_iterator_parses_text_stops_at_esc ( ) {
let mut iter = EscapeSequenceOffsetsIterator ::new ( "text\x1B[ming" ) ;
assert_eq! (
iter . next ( ) ,
Some ( EscapeSequenceOffsets ::Text { start : 0 , end : 4 } )
) ;
}
#[ test ]
fn test_escape_sequence_offsets_iterator_parses_osc_with_bel ( ) {
let mut iter = EscapeSequenceOffsetsIterator ::new ( "\x1B]abc\x07" ) ;
assert_eq! (
iter . next ( ) ,
Some ( EscapeSequenceOffsets ::OSC {
start_sequence : 0 ,
start_command : 2 ,
start_terminator : 5 ,
end : 6 ,
} )
) ;
}
#[ test ]
fn test_escape_sequence_offsets_iterator_parses_osc_with_st ( ) {
let mut iter = EscapeSequenceOffsetsIterator ::new ( "\x1B]abc\x1B\\" ) ;
assert_eq! (
iter . next ( ) ,
Some ( EscapeSequenceOffsets ::OSC {
start_sequence : 0 ,
start_command : 2 ,
start_terminator : 5 ,
end : 7 ,
} )
) ;
}
#[ test ]
fn test_escape_sequence_offsets_iterator_parses_osc_thats_broken ( ) {
let mut iter = EscapeSequenceOffsetsIterator ::new ( "\x1B]ab" ) ;
assert_eq! (
iter . next ( ) ,
Some ( EscapeSequenceOffsets ::OSC {
start_sequence : 0 ,
start_command : 2 ,
start_terminator : 4 ,
end : 4 ,
} )
) ;
}
#[ test ]
fn test_escape_sequence_offsets_iterator_parses_csi ( ) {
let mut iter = EscapeSequenceOffsetsIterator ::new ( "\x1B[m" ) ;
assert_eq! (
iter . next ( ) ,
Some ( EscapeSequenceOffsets ::CSI {
start_sequence : 0 ,
start_parameters : 2 ,
start_intermediates : 2 ,
start_final_byte : 2 ,
end : 3
} )
) ;
}
#[ test ]
fn test_escape_sequence_offsets_iterator_parses_csi_with_parameters ( ) {
let mut iter = EscapeSequenceOffsetsIterator ::new ( "\x1B[1;34m" ) ;
assert_eq! (
iter . next ( ) ,
Some ( EscapeSequenceOffsets ::CSI {
start_sequence : 0 ,
start_parameters : 2 ,
start_intermediates : 6 ,
start_final_byte : 6 ,
end : 7
} )
) ;
}
#[ test ]
fn test_escape_sequence_offsets_iterator_parses_csi_with_intermediates ( ) {
let mut iter = EscapeSequenceOffsetsIterator ::new ( "\x1B[$m" ) ;
assert_eq! (
iter . next ( ) ,
Some ( EscapeSequenceOffsets ::CSI {
start_sequence : 0 ,
start_parameters : 2 ,
start_intermediates : 2 ,
start_final_byte : 3 ,
end : 4
} )
) ;
}
#[ test ]
fn test_escape_sequence_offsets_iterator_parses_csi_with_parameters_and_intermediates ( ) {
let mut iter = EscapeSequenceOffsetsIterator ::new ( "\x1B[1$m" ) ;
assert_eq! (
iter . next ( ) ,
Some ( EscapeSequenceOffsets ::CSI {
start_sequence : 0 ,
start_parameters : 2 ,
start_intermediates : 3 ,
start_final_byte : 4 ,
end : 5
} )
) ;
}
#[ test ]
fn test_escape_sequence_offsets_iterator_parses_csi_thats_broken ( ) {
let mut iter = EscapeSequenceOffsetsIterator ::new ( "\x1B[" ) ;
assert_eq! (
iter . next ( ) ,
Some ( EscapeSequenceOffsets ::CSI {
start_sequence : 0 ,
start_parameters : 2 ,
start_intermediates : 2 ,
start_final_byte : 2 ,
end : 2
} )
) ;
let mut iter = EscapeSequenceOffsetsIterator ::new ( "\x1B[1" ) ;
assert_eq! (
iter . next ( ) ,
Some ( EscapeSequenceOffsets ::CSI {
start_sequence : 0 ,
start_parameters : 2 ,
start_intermediates : 3 ,
start_final_byte : 3 ,
end : 3
} )
) ;
let mut iter = EscapeSequenceOffsetsIterator ::new ( "\x1B[1$" ) ;
assert_eq! (
iter . next ( ) ,
Some ( EscapeSequenceOffsets ::CSI {
start_sequence : 0 ,
start_parameters : 2 ,
start_intermediates : 3 ,
start_final_byte : 4 ,
end : 4
} )
) ;
}
#[ test ]
fn test_escape_sequence_offsets_iterator_parses_nf ( ) {
let mut iter = EscapeSequenceOffsetsIterator ::new ( "\x1B($0" ) ;
assert_eq! (
iter . next ( ) ,
Some ( EscapeSequenceOffsets ::NF {
start_sequence : 0 ,
start : 1 ,
end : 4
} )
) ;
}
#[ test ]
fn test_escape_sequence_offsets_iterator_parses_nf_thats_broken ( ) {
let mut iter = EscapeSequenceOffsetsIterator ::new ( "\x1B(" ) ;
assert_eq! (
iter . next ( ) ,
Some ( EscapeSequenceOffsets ::NF {
start_sequence : 0 ,
start : 1 ,
end : 1
} )
) ;
}
#[ test ]
fn test_escape_sequence_offsets_iterator_iterates ( ) {
let mut iter = EscapeSequenceOffsetsIterator ::new ( "text\x1B[33m\x1B]OSC\x07\x1B(0" ) ;
assert_eq! (
iter . next ( ) ,
Some ( EscapeSequenceOffsets ::Text { start : 0 , end : 4 } )
) ;
assert_eq! (
iter . next ( ) ,
Some ( EscapeSequenceOffsets ::CSI {
start_sequence : 4 ,
start_parameters : 6 ,
start_intermediates : 8 ,
start_final_byte : 8 ,
end : 9
} )
) ;
assert_eq! (
iter . next ( ) ,
Some ( EscapeSequenceOffsets ::OSC {
start_sequence : 9 ,
start_command : 11 ,
start_terminator : 14 ,
end : 15
} )
) ;
assert_eq! (
iter . next ( ) ,
Some ( EscapeSequenceOffsets ::NF {
start_sequence : 15 ,
start : 16 ,
end : 18
} )
) ;
assert_eq! ( iter . next ( ) , None ) ;
}
#[ test ]
fn test_escape_sequence_iterator_iterates ( ) {
let mut iter = EscapeSequenceIterator ::new ( "text\x1B[33m\x1B]OSC\x07\x1B]OSC\x1B\\\x1B(0" ) ;
assert_eq! ( iter . next ( ) , Some ( EscapeSequence ::Text ( "text" ) ) ) ;
assert_eq! (
iter . next ( ) ,
Some ( EscapeSequence ::CSI {
raw_sequence : "\x1B[33m" ,
parameters : "33" ,
intermediates : "" ,
final_byte : "m" ,
} )
) ;
assert_eq! (
iter . next ( ) ,
Some ( EscapeSequence ::OSC {
raw_sequence : "\x1B]OSC\x07" ,
command : "OSC" ,
terminator : "\x07" ,
} )
) ;
assert_eq! (
iter . next ( ) ,
Some ( EscapeSequence ::OSC {
raw_sequence : "\x1B]OSC\x1B\\" ,
command : "OSC" ,
terminator : "\x1B\\" ,
} )
) ;
assert_eq! (
iter . next ( ) ,
Some ( EscapeSequence ::NF {
raw_sequence : "\x1B(0" ,
nf_sequence : "(0" ,
} )
) ;
assert_eq! ( iter . next ( ) , None ) ;
}
}