diff --git a/src/lib/vterm.h b/src/lib/vterm.h index 08b3723..59c07f8 100644 --- a/src/lib/vterm.h +++ b/src/lib/vterm.h @@ -267,7 +267,12 @@ class VTerm { ESdcs = 8, EStermcap = 9, ESst = 10, - ESkeep = 11 + EScsiInter = 11, + // ESkeep is a sentinel meaning "leave the state unchanged", not a state + // of its own. NR_STATES is defined as its value and sizes escape_map[], + // and init_state() stops walking escape_sequences[] once the section + // counter reaches it. It must therefore stay last: add new states above. + ESkeep = 12 } EscapeState; static inline const char* EscapeStateToString(EscapeState state) { @@ -283,6 +288,7 @@ class VTerm { case ESdcs: return "DCS"; case EStermcap: return "DCS + q"; case ESst: return "ST"; + case EScsiInter: return "CSI + intermediate"; case ESkeep: return ""; } diff --git a/src/lib/vterm_states.cpp b/src/lib/vterm_states.cpp index a75ba61..c5b1c62 100644 --- a/src/lib/vterm_states.cpp +++ b/src/lib/vterm_states.cpp @@ -79,6 +79,13 @@ const VTerm::Sequence VTerm::escape_sequences[] = { { CSPAN('0', '9'), &VTerm::param_digit, ESkeep }, { ';', &VTerm::next_param, ESkeep }, { ':', &VTerm::next_param, ESkeep }, // some codes use ':' instead of ';' as the argument separator + // ECMA-48 5.4 defines a control sequence as "CSI P...P I...I F", where the + // Intermediate Bytes I (02/00 to 02/15) "together with the Final Byte F + // identify the control function". They therefore cannot be skipped: e.g. + // "CSI 2 SP q" (DECSCUSR) is a different function from "CSI 2 q" (DECLL). + // Track them in a separate state so the final byte is consumed as part of + // the sequence instead of falling through and being printed as text. + { CSPAN(' ', '/'), nullptr, EScsiInter }, { '@', &VTerm::insert_char, ESnormal }, { 'A', &VTerm::cursor_up, ESnormal }, { 'B', &VTerm::cursor_down, ESnormal }, @@ -112,7 +119,6 @@ const VTerm::Sequence VTerm::escape_sequences[] = { { '`', &VTerm::cursor_position_col, ESnormal }, { ']', &VTerm::linux_specific, ESnormal }, { '}', &VTerm::fbterm_specific, ESnormal }, - { '%', 0, ESkeep }, // this is a workaround for the wierd, undocumented code "\e[0%m" - otherwise the trailing 'm' gets printed ENDSEQ, // ESosc #3 "ESC ]" @@ -151,27 +157,40 @@ const VTerm::Sequence VTerm::escape_sequences[] = { { '9', &VTerm::screen_clear, ESnormal }, ENDSEQ, - // ESgreater #8 "ESC [ >" + // ESgreater #7 "ESC [ >" { CSPAN('0', '9'), &VTerm::param_digit, ESkeep }, { ';', &VTerm::next_param, ESkeep }, { 'c', &VTerm::get_device_attribute, ESnormal }, // Send Device Attributes (Secondary DA) { 'm', &VTerm::set_key_modifier, ESnormal }, // Set/reset key modifier options ENDSEQ, - // ESdcs #9 "ESC P" + // ESdcs #8 "ESC P" { '+', nullptr, ESkeep }, { 'q', nullptr, EStermcap }, ENDSEQ, - // EStermcap #10 "ESC P + q" + // EStermcap #9 "ESC P + q" { CSPAN('0', '9'), &VTerm::param_hex_digit, ESkeep }, { CSPAN('a', 'f'), &VTerm::param_hex_digit, ESkeep }, { CSPAN('A', 'F'), &VTerm::param_hex_digit, ESkeep }, { 0x1B, &VTerm::request_termcap, ESst }, ENDSEQ, - // ESst #11 + // ESst #10 { '\\', nullptr, ESnormal }, + ENDSEQ, + + // EScsiInter #11 "CSI ... I" where I is an intermediate byte (0x20-0x2F) + // Sequences reaching this state are not implemented by fbterm. Consume the + // remaining intermediate bytes and the final byte so that nothing is printed. + { CSPAN(' ', '/'), nullptr, ESkeep }, + { CSPAN('@', '~'), nullptr, ESnormal }, + // Exception: the undocumented "\e[0%m" is emitted by some applications and + // was previously handled by keeping '%' in EScsi so that the trailing 'm' + // reached the SGR entry. ECMA-48 assigns no function to an intermediate + // byte followed by 06/13, so treating it as SGR here does not shadow a + // standard sequence. + { 'm', &VTerm::set_display_attr, ESnormal }, ENDSEQ };