LCOV - code coverage report
Current view: top level - src/tty - terminal.cc (source / functions) Hit Total Coverage
Test: main_coverage.info Lines: 744 776 95.9 %
Date: 2022-06-15 20:16:21 Functions: 72 80 90.0 %
Branches: 431 640 67.3 %

           Branch data     Line data    Source code
       1                 :            : #ifdef RUN_TESTS
       2                 :            : # include <gtest/gtest.h>
       3                 :            : #endif
       4                 :            : /** @file tty/terminal.cc
       5                 :            :  * @brief Defines TerminalWindow, the bulk of the terminal emulator. Its main purpose is to render anything that is printed by the subprocess.
       6                 :            :  */
       7                 :            : 
       8                 :            : #include <array>
       9                 :            : #include <cstdio> // sprintf
      10                 :            : 
      11                 :            : #include <SDL.h> // for window title
      12                 :            : 
      13                 :            : #include "terminal.hh"
      14                 :            : #include "ctype.hh"
      15                 :            : #include "256color.hh"
      16                 :            : #include "color.hh"
      17                 :            : #include "ui.hh"
      18                 :            : 
      19                 :        403 : void TerminalWindow::ResetAttr()
      20                 :            : {
      21                 :        403 :     bool prot = wnd.blank.protect;
      22                 :        403 :     wnd.blank = Cell{};
      23                 :        403 :     wnd.blank.protect = prot;
      24                 :        403 : }
      25                 :        401 : void TerminalWindow::Reset(bool full)
      26                 :            : {
      27                 :        401 :     top    = 0;
      28                 :        401 :     bottom = wnd.ysize-1;
      29                 :            : 
      30                 :        401 :     gset = {0,0,0,0}; activeset = 0; scs = 0;
      31                 :        401 :     utfmode = 0;
      32                 :        401 :     lastch = U' ';
      33                 :            : 
      34                 :        401 :     state = 0;
      35         [ -  + ]:        401 :     p.clear();
      36                 :            : 
      37                 :        401 :     wnd.inverse   = false;
      38                 :        401 :     wnd.cursorvis = true;
      39         [ +  + ]:        401 :     if(full)
      40                 :            :     {
      41                 :        400 :         edgeflag = false;
      42                 :        400 :         wnd.cursx = 0;
      43                 :        400 :         wnd.cursy = 0;
      44                 :        400 :         wnd.FillBox(0,0, wnd.xsize,wnd.ysize, wnd.blank); // Clear screen
      45                 :            :     }
      46                 :        401 : }
      47                 :            : 
      48                 :          2 : void TerminalWindow::YScrollDown(unsigned y1, unsigned y2, int amount) const
      49                 :            : {
      50         [ +  - ]:          2 :     if(amount <= 0) return;
      51                 :          2 :     unsigned hei = y2-y1+1;
      52         [ -  + ]:          2 :     if(unsigned(amount) > hei) amount = hei;
      53                 :            :     //fprintf(stderr, "Height=%d, amount=%d, scrolling DOWN by %d lines\n", hei,amount, hei-amount);
      54                 :          2 :     wnd.CopyText(0,y1+amount, 0,y1, wnd.xsize,hei-amount);
      55                 :          2 :     wnd.FillBox(0,y1, wnd.xsize,amount);
      56                 :            : }
      57                 :            : 
      58                 :         25 : void TerminalWindow::YScrollUp(unsigned y1, unsigned y2, int amount) const
      59                 :            : {
      60         [ +  - ]:         25 :     if(amount <= 0) return;
      61                 :         25 :     unsigned hei = y2-y1+1;
      62         [ -  + ]:         25 :     if(unsigned(amount) > hei) amount = hei;
      63                 :            :     //fprintf(stderr, "Height=%d, amount=%d, scrolling UP by %d lines\n", hei,amount, hei-amount);
      64                 :         25 :     wnd.CopyText(0,y1, 0,y1+amount, wnd.xsize,hei-amount);
      65                 :         25 :     wnd.FillBox(0,y2-amount+1, wnd.xsize,amount);
      66                 :            : }
      67                 :            : 
      68                 :        773 : void TerminalWindow::Write(std::u32string_view s)
      69                 :            : {
      70                 :        773 :     unsigned color = 0;
      71                 :            :     /** Repositions cursor horizontally and ensures the new location is within allowed range. */
      72                 :        970 :     auto ClampedMoveX = [&](int tgtx)
      73                 :            :     {
      74   [ +  +  +  + ]:        358 :         wnd.cursx = std::min(std::size_t(std::max(0,tgtx)), wnd.xsize-1);
      75                 :        197 :         edgeflag = false;
      76                 :        970 :     };
      77                 :            :     /** Repositions cursor vertically and ensures the new location is within allowed range.
      78                 :            :      * param tgty = Target y coordinate
      79                 :            :      * param strict = If set, only permits moving inside current window; otherwise permits moving anywhere on screen.
      80                 :            :      */
      81                 :        966 :     auto ClampedMoveY = [&](int tgty, bool strict = true)
      82                 :            :     {
      83   [ +  -  +  -  :        193 :         if(wnd.cursy >= top && wnd.cursy <= bottom && strict)
                   +  + ]
      84                 :            :         {
      85                 :            :             // Only permit moving inside window
      86   [ +  +  +  + ]:        288 :             wnd.cursy = std::min(std::size_t(std::max(int(top), tgty)), bottom);
      87                 :            :         }
      88                 :            :         else
      89                 :            :         {
      90                 :            :             // Permit moving anywhere
      91   [ +  +  +  + ]:         78 :             wnd.cursy = std::min(std::size_t(std::max(0,tgty)), wnd.ysize-1);
      92                 :            :         }
      93                 :        966 :     };
      94                 :            :     /** Combination of ClampedMoveX and ClampedMoveY. */
      95                 :        820 :     auto ClampedMove = [&](int tgtx, int tgty, bool strict = true)
      96                 :            :     {
      97                 :         47 :         ClampedMoveX(tgtx);
      98                 :         47 :         ClampedMoveY(tgty, strict);
      99                 :        820 :     };
     100                 :            :     /** Performs line feed. */
     101                 :        924 :     auto Lf = [&]
     102                 :            :     {
     103         [ +  + ]:        151 :         if(wnd.cursy == bottom)
     104                 :         23 :             YScrollUp(top, bottom, 1);
     105                 :            :         else
     106                 :        128 :             ClampedMoveY(wnd.cursy+1);
     107                 :        924 :     };
     108                 :            :     /** Performs typewriter write for one character. */
     109                 :       6862 :     auto PutC = [&](char32_t c, bool doublewidth)
     110                 :            :     {
     111         [ +  + ]:       6089 :         if(edgeflag)
     112                 :            :         {
     113         [ +  - ]:        135 :             if(wnd.cursx == wnd.xsize-1)
     114                 :            :             {
     115                 :        135 :                 Lf();
     116                 :        135 :                 wnd.cursx = 0;
     117                 :            :             }
     118                 :        135 :             edgeflag = false;
     119                 :            :         }
     120                 :       6089 :         wnd.PutCh(wnd.cursx,wnd.cursy, c, gset[activeset]);
     121         [ +  + ]:       6089 :         if(wnd.cursx == wnd.xsize-1) edgeflag = true;
     122                 :       5951 :         else                         ++wnd.cursx;
     123                 :            : 
     124         [ +  + ]:       6089 :         if(doublewidth)
     125                 :            :         {
     126                 :            :             // If this character was double-width,
     127                 :            :             // skip the next column but mark it also dirty.
     128         [ -  + ]:         32 :             wnd.Dirtify(wnd.cursx,wnd.cursy);
     129         [ -  + ]:         32 :             if(wnd.cursx == wnd.xsize-1) edgeflag = true;
     130                 :         32 :             else                         ++wnd.cursx;
     131                 :            :         }
     132                 :       6862 :     };
     133                 :            : 
     134                 :        773 :     enum : unsigned {
     135                 :            :         MODE38_2 = 70,  MODE38_2_x1, MODE38_2_x2, MODE_38_x = 108,
     136                 :            :         MODE38_3 = 80,  MODE38_3_x1, MODE38_3_x2, MODE38_5 = 56,
     137                 :            :         MODE48_2 = 87,  MODE48_2_x1, MODE48_2_x2, MODE_48_x = 109,
     138                 :            :         MODE48_3 = 111, MODE48_3_x1, MODE48_3_x2, MODE48_5 = 57,
     139                 :            :         MODE58_2 = 114, MODE58_2_x1, MODE58_2_x2, MODE_58_x = 110,
     140                 :            :         MODE58_3 = 117, MODE58_3_x1, MODE58_3_x2, MODE58_5 = 99,
     141                 :            :         MODE38_4 = 66,  MODE38_4_x1, MODE38_4_x2, MODE38_4_x3,
     142                 :            :         MODE48_4 = 76,  MODE48_4_x1, MODE48_4_x2, MODE48_4_x3,
     143                 :            :         MODE58_4 = 83,  MODE58_4_x1, MODE58_4_x2, MODE58_4_x3,
     144                 :            :     };
     145                 :        773 :     static_assert(MODE38_4_x1 == MODE38_4+1);    static_assert(MODE38_3_x1 == MODE38_3+1);    static_assert(MODE38_2_x1 == MODE38_2+1);
     146                 :        773 :     static_assert(MODE48_4_x1 == MODE48_4+1);    static_assert(MODE48_3_x1 == MODE48_3+1);    static_assert(MODE48_2_x1 == MODE48_2+1);
     147                 :        773 :     static_assert(MODE58_4_x1 == MODE58_4+1);    static_assert(MODE58_3_x1 == MODE58_3+1);    static_assert(MODE58_2_x1 == MODE58_2+1);
     148                 :        773 :     static_assert(MODE38_4_x2 == MODE38_4_x1+1); static_assert(MODE38_3_x2 == MODE38_3_x1+1); static_assert(MODE38_2_x2 == MODE38_2_x1+1);
     149                 :        773 :     static_assert(MODE48_4_x2 == MODE48_4_x1+1); static_assert(MODE48_3_x2 == MODE48_3_x1+1); static_assert(MODE48_2_x2 == MODE48_2_x1+1);
     150                 :        773 :     static_assert(MODE58_4_x2 == MODE58_4_x1+1); static_assert(MODE58_3_x2 == MODE58_3_x1+1); static_assert(MODE58_2_x2 == MODE58_2_x1+1);
     151                 :        773 :     static_assert(MODE38_4_x3 == MODE38_4_x2+1);
     152                 :        773 :     static_assert(MODE48_4_x3 == MODE48_4_x2+1);
     153                 :        773 :     static_assert(MODE58_4_x3 == MODE58_4_x2+1);
     154                 :        773 :     static_assert(MODE_48_x == MODE_38_x+1); static_assert(MODE_58_x == MODE_48_x+1);
     155                 :            :     /* Translation maps for ProcessSGR for a smaller jump table */
     156                 :        773 :     auto M = [](unsigned n) constexpr -> unsigned char
     157                 :            :     {
     158                 :            :         // 16? 36, 37?
     159                 :            :         unsigned char result = 0;
     160                 :            :         // SKIP the following, as they are not implemented:
     161                 :            :         //   8 (conceal)
     162                 :            :         //  20 (fraktur)
     163                 :            :         //  26 (proportional)
     164                 :            :         //  28 (clear conceal)
     165                 :            :         //  50 (clear proportional)
     166                 :            :         //  51-52 (framed & encircled)
     167                 :            :         //  54 (clear framed & encircled)
     168                 :            :         //  59 (default underline color)
     169                 :            :         //  60-65 (ideogram settings)
     170                 :            :         //  73-75 (superscript & subscript settings)
     171                 :            :         // This makes the code smaller. They can be enabled when implemented.
     172                 :            :         //
     173                 :            :         // Put most common things first
     174                 :            :         if(/*n >= 0 &&*/ n <= 1) return result+n-0; else result += 2;
     175                 :            :         //
     176                 :            :         if(n >= 30 && n <= 37) return result; else result+=1; // 2 -- handled by a single case
     177                 :            :         if(n >= 90 && n <= 97) return result; else result+=1; // 3 -- handled by a single case
     178                 :            :         if(n == MODE38_5)      return result; else result+=1; // 4 -- fallback from above
     179                 :            :         if(n == MODE38_2_x2)   return result; else result+=1; // 5 -- uncommon, but works as fallback from MODE38_5
     180                 :            :         if(n == MODE38_3_x2)   return result; else result+=1; // 6
     181                 :            :         if(n == MODE38_4_x3)   return result; else result+=1; // 7
     182                 :            :         //
     183                 :            :         if(n >= 40 && n <= 47) return result; else result+=1; // 8 -- handled by a single case
     184                 :            :         if(n >=100 && n <=107) return result; else result+=1; // 9 -- handled by a single case
     185                 :            :         if(n == MODE48_5)      return result; else result+=1; // 10 -- fallback from above
     186                 :            :         if(n == MODE48_2_x2)   return result; else result+=1; // 11 -- uncommon, but works as fallback from MODE48_5
     187                 :            :         if(n == MODE48_3_x2)   return result; else result+=1; // 12
     188                 :            :         if(n == MODE48_4_x3)   return result; else result+=1; // 13
     189                 :            :         //
     190                 :            :         if(n == 38)            return result; else result+=1; // 14 -- sets MODE_38_x
     191                 :            :         if(n == 48)            return result; else result+=1; // 15 -- sets MODE_48_x
     192                 :            :         if(n == 58)            return result; else result+=1; // 16 -- sets MODE_58_x
     193                 :            :         if(n == MODE_38_x
     194                 :            :         || n == MODE_48_x
     195                 :            :         || n == MODE_58_x)     return result; else result+=1; // 17 -- handled by a single case
     196                 :            :         //
     197                 :            :         if((n >= MODE38_4 && n <= MODE38_4_x2)
     198                 :            :         || (n >= MODE38_3 && n <= MODE38_3_x1)
     199                 :            :         || (n >= MODE38_2 && n <= MODE38_2_x1)
     200                 :            :         || (n >= MODE48_4 && n <= MODE48_4_x2)
     201                 :            :         || (n >= MODE48_3 && n <= MODE48_3_x1)
     202                 :            :         || (n >= MODE48_2 && n <= MODE48_2_x1)
     203                 :            :         || (n >= MODE58_4 && n <= MODE58_4_x2)
     204                 :            :         || (n >= MODE58_3 && n <= MODE58_3_x1)
     205                 :            :         || (n >= MODE58_2 && n <= MODE58_2_x1)) return result; else result+=1; // 18 -- handled by a single case
     206                 :            :         // Skip these, as they are not implemented:
     207                 :            :         //if(n == MODE58_4_x3)                  return result; else result+=1;
     208                 :            :         //if(n == MODE58_3_x2)                  return result; else result+=1;
     209                 :            :         //if(n == MODE58_2_x2)                  return result; else result+=1;
     210                 :            :         //if(n == MODE58_5)                     return result; else result+=1;
     211                 :            :         //
     212                 :            :         if(n == 39)            return result; else result+=1; // 19
     213                 :            :         if(n == 49)            return result; else result+=1; // 20
     214                 :            :         //
     215                 :            :         if(n == 9)             return result; else result+=1; // 21
     216                 :            :         if(n >= 2 && n <= 7)   return result+n-2; else result += 6; // 22..27
     217                 :            :         //
     218                 :            :         if(n == 27)            return result; else result+=1; // 28
     219                 :            :         if(n == 29)            return result; else result+=1; // 29
     220                 :            :         if(n == 53)            return result; else result+=1; // 30
     221                 :            :         if(n >= 21 && n <= 25) return result+n-21; else result+=5; // 31..35
     222                 :            :         if(n == 55)            return result; else result+=1; // 36
     223                 :            :         //
     224                 :            :         return result;
     225                 :            :     };
     226                 :        773 :     static constexpr unsigned char __ = M(~0u);
     227                 :        773 :     static constexpr unsigned char translate[] =
     228                 :            :     {
     229                 :            :         #define a(n) M(n+0),M(n+1),M(n+2),M(n+3),M(n+4),M(n+5),M(n+6),M(n+7),M(n+8),M(n+9),
     230                 :            :         a(0)a(10)a(20)a(30)a(40)a(50)a(60)a(70)a(80)a(90)a(100)a(110)
     231                 :            :         #undef a
     232                 :            :     };
     233                 :        773 :     static constexpr unsigned char modetab[] = {MODE38_2,MODE38_3,MODE38_4,MODE38_5,
     234                 :            :                                                 MODE48_2,MODE48_3,MODE48_4,MODE48_5,
     235                 :            :                                                 MODE58_2,MODE58_3,MODE58_4,MODE58_5};
     236                 :            : 
     237                 :            :     /** ProcessSGR processes a SGR command. */
     238                 :       5042 :     auto ProcessSGR = [&](char32_t& c, unsigned a,
     239                 :            :                           auto&& reset_attr,
     240                 :            :                           auto&& change_attr)
     241                 :            :     {
     242                 :            :         /* c is a state code internal to this function, a is the input number from the SGR code.
     243                 :            :          * Codes:
     244                 :            :          *   0 = default state -- Will parse "a" (input number) as verbatim
     245                 :            :          *   Other, see below:
     246                 :            :          * Switch-case map (* = regular use, _ = UDEFINED, # = internal use):
     247                 :            :          *     0-9     **********
     248                 :            :          *    10-19    __________ (font commands, 0=default, 1-9=alt font)
     249                 :            :          *    20-29    **********
     250                 :            :          *    30-39    **********
     251                 :            :          *    40-49    **********
     252                 :            :          *    50-59    ******##**    56: MODE38_5[1]  57: MODE48_5[1]
     253                 :            :          *    60-69    ******####    66: MODE38_4[4]
     254                 :            :          *    70-79    ###***####    70: MODE38_2[3]  76: MODE48_4[4]
     255                 :            :          *    80-89    ##########    80: MODE38_3[3]  83: MODE58_4[4] 87: MODE48_2[3]
     256                 :            :          *    90-99    ********_#    99: MODE58_5[1]
     257                 :            :          *   100-109   ********##    108: MODE_38_x[3]
     258                 :            :          *   110-119   ##########    111: MODE48_3[3] 114: MODE58_2[3] 117: MODE58_3[3]
     259                 :            :          *
     260                 :            :          * Detailed layout on internal use codes --- what happens if "a" has that same value:
     261                 :            :          *    56               mistakenly interpreted as 38;5;56
     262                 :            :          *    57               mistakenly interpreted as 48;5;57
     263                 :            :          *    99               mistakenly interpreted as 58;5;99
     264                 :            :          *    72               mistakenly interpreted as 38;2;?;?;72
     265                 :            :          *    82               mistakenly interpreted as 38;3;?;?;82
     266                 :            :          *    89               mistakenly interpreted as 48;2;?;?;89
     267                 :            :          *   113               mistakenly interpreted as 48;3;?;?;113
     268                 :            :          *   116               mistakenly interpreted as 58;2;?;?;116
     269                 :            :          *   119               mistakenly interpreted as 58;3;?;?;119
     270                 :            :          *    69               mistakenly interpreted as 38;4;?;?;?;69
     271                 :            :          *    79               mistakenly interpreted as 48;4;?;?;?;79
     272                 :            :          *    86               mistakenly interpreted as 58;4;?;?;?;86
     273                 :            :          *   108               mistakenly interpreted as 38;108 -- does nothing
     274                 :            :          *   109               mistakenly interpreted as 48;109 -- does nothing
     275                 :            :          *   110               mistakenly interpreted as 58;110 -- does nothing
     276                 :            :          *   Anything else:    ignores 1 parameter and sets bold mode (as in 1)
     277                 :            :          */
     278         [ +  + ]:       4269 :         unsigned switchval = c ? c : a;
     279                 :       4269 :         auto m = [](unsigned n) constexpr
     280                 :            :         {
     281         [ +  + ]:       4269 :             return (n < sizeof(translate)) ? translate[n] : __;
     282                 :            :         };
     283   [ +  +  +  +  :       4133 :         switch(m(switchval))
          +  +  +  +  +  
          +  +  +  +  +  
          +  +  +  +  +  
          +  +  +  +  +  
          +  +  +  +  +  
          +  +  +  +  +  
             +  +  +  + ]
     284                 :            :         {
     285                 :            :             #define set(what, v) change_attr([&](Cell& c, auto x){c.what = x;}, \
     286                 :            :                                              [&](const Cell& c) { return c.what; }, \
     287                 :            :                                              v)
     288                 :          2 :             case m(0): reset_attr(); c = 0; break;
     289                 :         26 :             case m(1): set(bold, true); c = 0; break;
     290                 :          9 :             case m(2): set(dim, true); c = 0; break;
     291                 :          7 :             case m(3): set(italic, true); c = 0; break;
     292                 :          8 :             case m(4): set(underline, true); c = 0; break;
     293                 :          3 :             case m(5): set(blink, 1); c = 0; break;
     294                 :          2 :             case m(6): set(blink, 2); c = 0; break;
     295                 :          8 :             case m(7): set(inverse, true); c = 0; break;
     296                 :            :             //case m(8): set(conceal, true); c = 0; break;
     297                 :          3 :             case m(9): set(overstrike, true); c = 0; break;
     298                 :            :             //case m(20): set(fraktur, true); c = 0; break;
     299                 :          8 :             case m(21): set(underline2, true); c = 0; break;
     300                 :          2 :             case m(22): set(dim, false); set(bold, false); c = 0; break;
     301                 :          2 :             case m(23): set(italic, false); set(fraktur, false); c = 0; break;
     302                 :          2 :             case m(24): set(underline, false); set(underline2, false); c = 0; break;
     303                 :          2 :             case m(25): set(blink, 0); c = 0; break;
     304                 :            :             //case m(26): set(proportional, true); c = 0; break;
     305                 :          2 :             case m(27): set(inverse, false); c = 0; break;
     306                 :            :             //case m(28): set(conceal, false); c = 0; break;
     307                 :          2 :             case m(29): set(overstrike, false); c = 0; break;
     308                 :          1 :             case m(39): set(underline, false); set(underline2, false);
     309                 :          2 :                         set(fgcolor, Cell{}.fgcolor); c = 0; break; // Set default foreground color
     310                 :          1 :             case m(49): set(bgcolor, Cell{}.bgcolor); c = 0; break; // Set default background color
     311                 :            :             //case m(59): c = 0; break; // Set default underline color
     312                 :            :             //case m(50): set(proportional, false); c = 0; break;
     313                 :            :             //case m(51): set(framed, true); c = 0; break;
     314                 :            :             //case m(52): set(encircled, true); c = 0; break;
     315                 :          3 :             case m(53): set(overlined, true); c = 0; break;
     316                 :            :             //case m(54): set(framed, false); set(encircled, false); c = 0; break;
     317                 :          2 :             case m(55): set(overlined, false); c = 0; break;
     318                 :            :             //case m(60): set(ideo_underline, true); c = 0; break;
     319                 :            :             //case m(61): set(ideo_underline2, true); c = 0; break;
     320                 :            :             //case m(62): set(ideo_overline, true); c = 0; break;
     321                 :            :             //case m(63): set(ideo_overline2, true); c = 0; break;
     322                 :            :             //case m(64): set(ideo_stress, true); c = 0; break;
     323                 :            :             //case m(65): set(ideo_underline, false); set(ideo_underline2, false);
     324                 :            :             //            set(ideo_overline, false); set(ideo_overline2, false);
     325                 :            :             //            set(ideo_stress, false); c = 0; break;
     326                 :            :             //case m(73): set(scriptsize, 1); c = 0; break;
     327                 :            :             //case m(74): set(scriptsize, 2); c = 0; break;
     328                 :            :             //case m(75): set(scriptsize, 0); c = 0; break;
     329                 :        518 :             case m(38): c = MODE_38_x; break;
     330                 :        518 :             case m(48): c = MODE_48_x; break;
     331                 :        259 :             case m(58): c = MODE_58_x; break;
     332                 :            : 
     333                 :            :             // MODE_38_x, MODE_48_x, MODE_58_x are handled by single case.
     334                 :       1298 :             case m(MODE_38_x):/*
     335                 :            :             case m(MODE_48_x):
     336                 :            :             case m(MODE_58_x):*/
     337                 :            :             {
     338                 :       3549 :                 color = 0;
     339         [ +  + ]:       1298 :                 if(a >= 2 && a <= 5) { c = modetab[(c-MODE_38_x)*4 + a - 2]; break; }
     340                 :          3 :                 c     = 0;
     341                 :          3 :                 break;
     342                 :            :             }
     343                 :            : 
     344                 :            :             // All of these are handled by a single case. They update color and increment mode number.
     345                 :         47 :             case m(MODE38_4): /*//color = (color << 8) + a; c+=1; break; // 38;4;n
     346                 :            :             case m(MODE38_3): //color = (color << 8) + a; c+=1; break; // 38;3;n
     347                 :            :             case m(MODE38_2): //color = (color << 8) + a; c+=1; break; // 38;2;n
     348                 :            :             case m(MODE48_4): //color = (color << 8) + a; c+=1; break; // 48;4;n
     349                 :            :             case m(MODE48_3): //color = (color << 8) + a; c+=1; break; // 48;3;n
     350                 :            :             case m(MODE48_2): //color = (color << 8) + a; c+=1; break; // 48;2;n
     351                 :            :             case m(MODE58_4): //color = (color << 8) + a; c+=1; break; // 58;4;n
     352                 :            :             case m(MODE58_3): //color = (color << 8) + a; c+=1; break; // 58;3;n
     353                 :            :             case m(MODE58_2): //color = (color << 8) + a; c+=1; break; // 58;2;n
     354                 :            :             case m(MODE38_4_x1): //color = (color << 8) + a; c+=1; break; // 38;4;#;n
     355                 :            :             case m(MODE38_3_x1)://color = (color << 8) + a; c+=1; break; // 38;3;#;n
     356                 :            :             case m(MODE38_2_x1)://color = (color << 8) + a; c+=1; break; // 38;2;#;n
     357                 :            :             case m(MODE48_4_x1)://color = (color << 8) + a; c+=1; break; // 48;4;#;n
     358                 :            :             case m(MODE48_3_x1)://color = (color << 8) + a; c+=1; break; // 48;3;#;n
     359                 :            :             case m(MODE48_2_x1)://color = (color << 8) + a; c+=1; break; // 48;2;#;n
     360                 :            :             case m(MODE58_4_x1)://color = (color << 8) + a; c+=1; break; // 58;4;#;n
     361                 :            :             case m(MODE58_3_x1)://color = (color << 8) + a; c+=1; break; // 58;3;#;n
     362                 :            :             case m(MODE58_2_x1)://color = (color << 8) + a; c+=1; break; // 58;2;#;n
     363                 :            :             case m(MODE38_4_x2)://color = (color << 8) + a; c+=1; break; // 38;4;#;#;n
     364                 :            :             case m(MODE48_4_x2)://color = (color << 8) + a; c+=1; break; // 48;4;#;#;n
     365                 :         47 :             case m(MODE58_4_x2):*/color = (color << 8) + a; c+=1; break; // 58;4;#;#;n
     366                 :            : 
     367                 :            :             // Foreground colors. 30..37 are handled by a single case, likewise 90..97
     368                 :         20 :             case m(30):/*case m(31):case m(32):case m(33):
     369                 :         20 :             case m(34):case m(35):case m(36):case m(37):*/     a -= 30; a += 90-8;  [[fallthrough]];
     370                 :         36 :             case m(90):/*case m(91):case m(92):case m(93):
     371                 :         36 :             case m(94):case m(95):case m(96):case m(97):*/     a -= 90-8;           [[fallthrough]];
     372                 :        549 :             case m(MODE38_5):     color = 0; a = xterm256table[a & 0xFF];           [[fallthrough]];     // 38;5;n
     373                 :        552 :             case m(MODE38_2_x2):  color = (color << 8) + a; set(fgcolor, color); c = 0; break;           // 38;2;#;#;n   (RGB24)
     374         [ #  # ]:          3 :             case m(MODE38_3_x2):  color = (color << 8) + a; set(fgcolor, cmy2rgb(color));  c = 0; break; // 38;3;#;#;n   (CMY->RGB)
     375                 :          2 :             case m(MODE38_4_x3):  color = (color << 8) + a; set(fgcolor, cmyk2rgb(color)); c = 0; break; // 38;4;#;#;#;n (CMYK->RGB)
     376                 :            : 
     377                 :            :             // Background colors. 40..47 are handled by a single case, likewise 100..107
     378                 :         16 :             case m(40):/*case m(41):case m(42):case m(43):
     379                 :         16 :             case m(44):case m(45):case m(46):case m(47):*/     a -= 40; a += 100-8; [[fallthrough]];
     380                 :         32 :             case m(100):/*case m(101):case m(102):case m(103):
     381                 :         32 :             case m(104):case m(105):case m(106):case m(107):*/ a -= 100-8;          [[fallthrough]];
     382                 :        545 :             case m(MODE48_5):     color = 0; a = xterm256table[a & 0xFF];           [[fallthrough]];     // 48;5;n
     383                 :        548 :             case m(MODE48_2_x2):  color = (color << 8) + a; set(bgcolor, color);           c = 0; break; // 48;2;#;#;n   (RGB24)
     384         [ #  # ]:          3 :             case m(MODE48_3_x2):  color = (color << 8) + a; set(bgcolor, cmy2rgb(color));  c = 0; break; // 48;3;#;#;n   (CMY->RGB)
     385                 :          2 :             case m(MODE48_4_x3):  color = (color << 8) + a; set(bgcolor, cmyk2rgb(color)); c = 0; break; // 48;4;#;#;#;n (CMYK->RGB)
     386                 :            : 
     387                 :            :             // These don't do anything, so we just ignore them and reset the mode number c.
     388                 :            :             //
     389                 :            :             //case m(MODE58_5):  color = 0; a = xterm256table[a & 0xFF]; [[fallthrough]];           // 58;5;n
     390                 :            :             //case m(MODE58_4_x3)://color = (color << 8) + a; /*IGNORED*/ c = 0; break;             // 58;4;#;#;#;n (TODO CMYK->RGB)
     391                 :            :             //case m(MODE58_3_x2)://color = (color << 8) + a; /*IGNORED*/ c = 0; break;             // 58;3;#;#;n   (TODO CMY->RGB)
     392                 :            :             //case m(MODE58_2_x2):  color = (color << 8) + a; /*IGNORED*/ c = 0; break;             // 58;2;#;#;n   (RGB24)
     393                 :            : 
     394                 :        427 :             default: case m(~0u): c = 0; break; // undefined
     395                 :            :             #undef set
     396                 :            :         }
     397                 :       5042 :     };
     398                 :            : 
     399         [ -  + ]:        773 :     if(bottom >= wnd.ysize) bottom = wnd.ysize-1;
     400         [ -  + ]:        773 :     if(top    > bottom)     top = bottom;
     401                 :            : 
     402                 :        773 :     enum States: unsigned
     403                 :            :     {
     404                 :            :         st_default,
     405                 :            :         st_esc,
     406                 :            :         st_scs,         // esc ()*+-./
     407                 :            :         st_scr,         // esc #
     408                 :            :         st_esc_percent, // esc %
     409                 :            :         st_csi,         // esc [
     410                 :            :         st_csi_dec,     // csi ?
     411                 :            :         st_csi_dec2,    // csi >
     412                 :            :         st_csi_dec3,    // csi =
     413                 :            :         st_csi_ex,      // csi !
     414                 :            :         st_csi_quo,     // csi "
     415                 :            :         st_csi_dol,     // csi $
     416                 :            :         st_csi_star,    // csi *
     417                 :            :         st_csi_dec_dol, // csi ? $
     418                 :            :         st_string,
     419                 :            :         st_string_str,
     420                 :            :         //
     421                 :            :         st_num_states
     422                 :            :     };
     423                 :      23260 :     auto State = [](char32_t c, unsigned st) constexpr
     424                 :            :     {
     425                 :      22487 :         return c*st_num_states + st;
     426                 :            :     };
     427                 :       2120 :     auto GetParams = [&](unsigned min_params, bool change_zero_to_one)
     428                 :            :     {
     429         [ +  + ]:       1347 :         if(p.size() < min_params) { p.resize(min_params); }
     430   [ +  +  +  +  :       1606 :         if(change_zero_to_one) for(auto& v: p) if(!v) v = 1;
                   +  + ]
     431                 :       1347 :         state = st_default;
     432                 :       2120 :     };
     433                 :            : 
     434         [ +  + ]:      23260 :     for(char32_t c: s)
     435                 :            :     {
     436   [ +  +  +  +  :      22487 :         switch(State(c,state))
          +  +  +  +  +  
          +  +  +  +  +  
          +  +  +  +  +  
          +  +  +  +  +  
          +  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  +  
          +  +  +  +  -  
          +  +  +  +  +  
          +  +  +  +  +  
          +  +  +  +  +  
          +  +  +  +  +  
          +  +  +  +  +  
          +  +  +  +  +  
          +  +  +  +  +  
          +  +  +  +  +  
          +  +  +  +  +  
          +  +  +  +  +  
          +  +  +  +  +  
          +  +  +  +  +  
          +  +  +  +  +  
          +  +  +  +  +  
          +  +  +  +  +  
                   +  + ]
     437                 :            :         {
     438                 :            :             #define CsiState(c)      State(c,st_csi):     case State(c,st_csi_dec2): \
     439                 :            :                                 case State(c,st_csi_dec): case State(c,st_csi_dec3): \
     440                 :            :                                 case State(c,st_string)
     441                 :            :                                 /* csi_quo, csi_dol, csi_dec_dol, csi_ex are excluded
     442                 :            :                                  * from csistate because you can't have parameters
     443                 :            :                                  * after this particular symbol.
     444                 :            :                                  */
     445                 :            :             #define AnyState(c)      State(c,st_default):    case State(c,st_esc): \
     446                 :            :                                 case State(c,st_scs):        case State(c,st_csi_ex): \
     447                 :            :                                 case State(c,st_string_str): case State(c,st_csi_quo): \
     448                 :            :                                 case State(c,st_scr):        case State(c,st_esc_percent): \
     449                 :            :                                 case State(c,st_csi_dol):    case State(c,st_csi_dec_dol): \
     450                 :            :                                 case CsiState(c)
     451                 :            : 
     452                 :            :             // Note: These escapes are recognized even in the middle of an ANSI/VT code.
     453                 :          2 :             case AnyState(U'\b'):  { lastch=c; ClampedMoveX(wnd.cursx-1); break; }
     454                 :         16 :             case AnyState(U'\t'):  { lastch=c; ClampedMoveX(wnd.cursx + 8 - (wnd.cursx & 7)); break; }
     455                 :          9 :             case AnyState(U'\r'):  { lastch=c; ClampedMoveX(0); break; }
     456                 :         27 :             case AnyState(U'\16'): { activeset = 1; break; }
     457                 :         27 :             case AnyState(U'\17'): { activeset = 0; break; }
     458                 :            :             case AnyState(U'\177'): { /* del - ignore */ break; }
     459                 :        149 :             case AnyState(U'\30'): [[fallthrough]];
     460                 :        149 :             case AnyState(U'\32'): [[fallthrough]];
     461                 :        149 :             case AnyState(U'\u0080'): [[fallthrough]];
     462                 :        149 :             case AnyState(U'\u0081'): [[fallthrough]];
     463                 :        149 :             case AnyState(U'\u0082'): [[fallthrough]];
     464                 :        149 :             case AnyState(U'\u0083'): [[fallthrough]];
     465                 :        149 :             case AnyState(U'\u0086'): [[fallthrough]];
     466                 :        149 :             case AnyState(U'\u0087'): [[fallthrough]];
     467                 :        149 :             case AnyState(U'\u0089'): [[fallthrough]];
     468                 :        149 :             case AnyState(U'\u008A'): [[fallthrough]];
     469                 :        149 :             case AnyState(U'\u008B'): [[fallthrough]];
     470                 :        149 :             case AnyState(U'\u008C'): [[fallthrough]];
     471                 :        149 :             case AnyState(U'\u0091'): [[fallthrough]];
     472                 :        149 :             case AnyState(U'\u0092'): [[fallthrough]];
     473                 :        149 :             case AnyState(U'\u0093'): [[fallthrough]];
     474                 :        149 :             case AnyState(U'\u0094'): [[fallthrough]];
     475                 :        149 :             case AnyState(U'\u0095'): [[fallthrough]];
     476                 :        149 :             case AnyState(U'\u0099'): { Ground: scs=0; state=st_default; break; }
     477                 :         46 :             case State(U'\33', st_string): [[fallthrough]];
     478                 :         46 :             case State(U'\33', st_string_str): state = st_esc; break; // don't clear params
     479         [ +  + ]:       1446 :             case State(U'\33', st_default): state = st_esc; p.clear(); break;
     480                 :          3 :             case State(U'(', st_esc): scs = 0; state = st_scs; break; // esc (
     481                 :         30 :             case State(U')', st_esc): scs = 1; state = st_scs; break; // esc )
     482                 :          3 :             case State(U'*', st_esc): scs = 2; state = st_scs; break; // esc *
     483                 :          3 :             case State(U'+', st_esc): scs = 3; state = st_scs; break; // esc +
     484                 :          3 :             case State(U'-', st_esc): scs = 1; state = st_scs; break; // esc -
     485                 :          4 :             case State(U'.', st_esc): scs = 2; state = st_scs; break; // esc .
     486                 :          9 :             case State(U'/', st_esc): scs = 3; state = st_scs; break; // esc /
     487                 :          6 :             case State(U'#', st_esc): state = st_scr; break;  // esc #
     488                 :       1325 :             case State(U'[', st_esc): state = st_csi; break;  // esc [
     489                 :          3 :             case State(U'%', st_esc): state = st_esc_percent; break; // esc %
     490                 :          5 :             case State(U'?', st_csi): state = st_csi_dec; break;  // csi ?
     491                 :          2 :             case State(U'>', st_csi): state = st_csi_dec2; break; // csi >
     492                 :          2 :             case State(U'=', st_csi): state = st_csi_dec3; break; // csi =
     493                 :          2 :             case State(U'!', st_csi): state = st_csi_ex; break;  // csi ! (note: after numbers)
     494                 :          7 :             case State(U'"', st_csi): state = st_csi_quo; break; // csi " (note: after numbers)
     495                 :          8 :             case State(U'$', st_csi): state = st_csi_dol; break; // csi $ (note: after numbers)
     496                 :          1 :             case State(U'*', st_csi): state = st_csi_star; break; // csi * (note: after numbers)
     497                 :          2 :             case State(U'$', st_csi_dec): state = st_csi_dec_dol; break; // csi ? $ (note: after numbers)
     498                 :          0 :             case AnyState(U'\7'):
     499                 :          0 :             {
     500         [ #  # ]:          0 :                 if(state == st_string || state == st_string_str) // Treat as ST (string termination)
     501                 :            :                 {
     502                 :         44 :             case AnyState(U'\u009C'): [[fallthrough]];
     503                 :         44 :             case State(U'\\', st_esc): // String termination
     504      [ +  +  + ]:         44 :                     switch(scs)
     505                 :            :                     {
     506                 :         25 :                         case 4: // Parse DCS
     507                 :         25 :                             GetParams(1, false);
     508         [ +  - ]:         25 :                             if(string.empty()) break;
     509      [ +  +  + ]:         25 :                             switch(string[0])
     510                 :            :                             {
     511                 :          9 :                                 case U'$':
     512   [ +  +  -  + ]:          9 :                                     if(string.size() <= 1 || string[1] != 'q')
     513                 :          2 :                                         EchoBack(U"\u0018");
     514                 :            :                                     else
     515                 :            :                                     {
     516                 :          8 :                                         string.erase(0,2);
     517         [ +  + ]:          8 :                                         std::string response;
     518                 :          8 :                                         char Buf[40];
     519   [ +  +  +  - ]:          8 :                                         if(string == U"r") response.assign(Buf, std::sprintf(Buf, "%zu;%zu", top,bottom));
     520         [ +  + ]:          7 :                                         else if(string == U"m") response.assign(Buf, std::sprintf(Buf, "38;2;%u;%u;%u;48;2;%u;%u;%u",
     521                 :          1 :                                                                     (wnd.blank.fgcolor >> 16)&0xFF,
     522                 :          1 :                                                                     (wnd.blank.fgcolor >> 8)&0xFF,
     523                 :          1 :                                                                     (wnd.blank.fgcolor >> 0)&0xFF,
     524                 :          1 :                                                                     (wnd.blank.bgcolor >> 16)&0xFF,
     525                 :          1 :                                                                     (wnd.blank.bgcolor >> 8)&0xFF,
     526         [ +  - ]:          1 :                                                                     (wnd.blank.bgcolor >> 0)&0xFF));
     527   [ +  +  -  +  :          6 :                                         else if(string == U"t") response = std::to_string(std::max(25u, unsigned(wnd.ysize))-1);
                   +  - ]
     528   [ +  +  +  - ]:          5 :                                         else if(string == U" q") response = "1"; //cursor type
     529   [ +  +  +  - ]:          4 :                                         else if(string == U"\"q") response = wnd.blank.protect ? "1" : "0"; //protected mode
     530   [ +  +  +  - ]:          3 :                                         else if(string == U"\"p") response = "64;1"; //vt index, 8-bit controls disabled
     531   [ +  +  +  - ]:          2 :                                         else if(string == U"$|") response = std::to_string(wnd.xsize); //window width
     532   [ +  -  +  - ]:          1 :                                         else if(string == U"*|") response.assign(Buf, std::sprintf(Buf, "%zu", wnd.ysize));
     533   [ #  #  #  # ]:          0 :                                         else {fprintf(stderr, "Unrecognized DCS<%s>\n", ToUTF8(string).c_str());
     534         [ #  # ]:          0 :                                             EchoBack(U"\033$P0\033\\");}
     535         [ +  - ]:          8 :                                         if(!response.empty())
     536   [ +  -  +  -  :         16 :                                             EchoBack(U"\033$P1$r" + FromUTF8(response) + string + U"\033\\");
          +  -  +  -  +  
                      - ]
     537                 :          8 :                                     }
     538                 :            :                                     break;
     539                 :         15 :                                 case U'q': /* Sixel graphics */
     540                 :         15 :                                 {
     541                 :         15 :                                     [[maybe_unused]] unsigned pad=1, pan=2, ph=1, pv=1, rep=1, x=0, y=0, color=3;
     542                 :         15 :                                     [[maybe_unused]] bool trans=false;
     543                 :            :                                     // Parse pre-q parameters
     544   [ +  -  +  - ]:         30 :                                     if(p.size()>=1) pad = std::array<int,10>{2,2,5,4,4,3,3,2,2,1}[std::min(9u,p[0])];
     545                 :         15 :                                     if(p.size()>=2) { trans=p[1]; }
     546         [ +  + ]:         15 :                                     if(p.size()>=2) { pad = std::max(1u,pad*p[2]/10); pan = std::max(1u,pan*p[2]/10); }
     547                 :         15 :                                     if(p.size()>=7) { pad=p[3]; pan=p[4]; ph=p[5]; pv=p[6]; }
     548                 :         31 :                                     for(std::size_t b=0,a=0; a<string.size(); a = ++b)
     549                 :            :                                     {
     550                 :            :                                         // Parse parameters that follow the command
     551   [ +  +  +  + ]:         38 :                                         for(p.clear(); (b+1) < string.size(); ++b)
     552         [ +  + ]:          7 :                                             if(string[b+1] >= U';') p.emplace_back();
     553   [ +  +  +  - ]:          5 :                                             else if(string[b+1] >= U'0' && string[b+1] <= U'9')
     554                 :            :                                             {
     555         [ +  - ]:          4 :                                                 if(p.empty()) p.emplace_back();
     556                 :          4 :                                                 p.back() = p.back() * 10u + (string[b+1]-U'0');
     557                 :            :                                             }
     558                 :            :                                             else break;
     559   [ -  -  -  +  :         16 :                                         switch(string[a])
                      + ]
     560                 :            :                                         {
     561                 :          0 :                                             case '\u0034': GetParams(4,false);
     562                 :          0 :                                                            pad = std::max(1u,p[0]);
     563                 :          0 :                                                            pan = std::max(1u,p[1]);
     564                 :          0 :                                                            if(p[2]) ph = p[2];
     565                 :          0 :                                                            if(p[3]) pv = p[3];
     566                 :            :                                                            break;
     567                 :          0 :                                             case '\u0035': switch(GetParams(5,false); p[1])
     568                 :            :                                                            {
     569                 :         31 :                                                                case 0: color=p[0]; break; // choose color p[0]
     570                 :            :                                                                case 1: break; // change color p[0] into hls: p[2..4]
     571                 :            :                                                                case 2: break; // change color p[0] into rgb: p[2..4]
     572                 :            :                                                            }
     573                 :            :                                                            break;
     574         [ +  + ]:         31 :                                             case '\u002D': y+=6; [[fallthrough]]; // TODO: scroll if necessary
     575                 :            :                                             case '\u0036': x=0; break;
     576         [ #  # ]:          0 :                                             case '\u0033': GetParams(1,false); rep = std::max(1u, p[0]); break;
     577                 :         15 :                                             default:
     578   [ +  -  +  - ]:         15 :                                                 if(string[a] >= '\u003F' && string[a] <= '\u007E')
     579                 :            :                                                 {
     580                 :         30 :                                                     unsigned bitmask = string[a] - '\u003F';
     581         [ +  + ]:         30 :                                                     for(; rep-- > 0; ++x)
     582         [ +  + ]:        105 :                                                         for(unsigned py=0; py<6; ++py)
     583                 :            :                                                             if(bitmask & (1u << py))
     584                 :            :                                                             {
     585                 :            :                                                             }
     586                 :            :                                                     rep = 1;
     587                 :            :                                                 }
     588                 :            :                                         }
     589                 :            :                                     }
     590                 :            :                                     break;
     591                 :            :                                 }
     592                 :            :                                 case U'p': /* ReGIS graphics */ break;
     593                 :            :                             }
     594                 :            :                             break;
     595                 :         13 :                         case 5: // Parse OSC
     596                 :         13 :                         {
     597                 :         13 :                             GetParams(2,false);
     598   [ +  +  +  +  :         13 :                             bool dfl = p[0] >= 100;
          +  +  +  +  +  
                      + ]
     599                 :         21 :                             auto DoColor = [&](const char* params,unsigned idx, unsigned& color, unsigned dflcolor)
     600                 :            :                             {
     601                 :          8 :                                 char Buf[32];
     602         [ -  + ]:          8 :                                 if(string == U"?")
     603                 :            :                                 {
     604                 :          0 :                                     char st[3] = {'\33', char(c), '\0'};
     605         [ #  # ]:          0 :                                     if(c != '\\') { st[0] = char(c); st[1] = '\0'; }
     606                 :            :                                     // ^ Echo back using the same ST code
     607         [ #  # ]:          0 :                                     EchoBack(FromUTF8({Buf, 0u+std::sprintf(Buf, "\33]%s%u;#%06X%s", params,idx, color, st)}));
     608                 :            :                                 }
     609         [ -  + ]:          8 :                                 else if(dfl) color = dflcolor;
     610                 :          8 :                                 else color = ParseColorName(string);
     611                 :          8 :                             };
     612   [ +  +  +  +  :         13 :                             switch(unsigned p100 = p[0] % 100)
          +  +  +  +  +  
                      + ]
     613                 :            :                             {
     614                 :          3 :                                 case 0:
     615                 :          3 :                                 case 1:
     616                 :          3 :                                 case 2:
     617                 :          3 :                                 {
     618                 :          3 :                                     std::string utstring = ToUTF8(string);
     619         [ +  + ]:          3 :                                     if(!(p100 & 2)) // 0 and 1: set icon name
     620         [ +  - ]:          2 :                                         ui.SetIconName(utstring);
     621         [ +  + ]:          3 :                                     if(!(p100 & 1)) // 0 and 2: set window title
     622         [ +  - ]:          2 :                                         ui.SetWindowTitle(utstring);
     623                 :          3 :                                     break;
     624                 :          3 :                                 }
     625                 :            :                                 case 6: break; // set or clear color{BD,UL,BL,RV,IT} mode
     626                 :          1 :                                 case 5:  p[1] += 256; [[fallthrough]];
     627                 :          2 :                                 case 4: {unsigned v = xterm256table[p[1]&0xFF];
     628                 :          2 :                                          DoColor("4;",p[1]&0xFF, v, v);
     629                 :          2 :                                          break;} // change color [p[1]] to string, or if ?, report the string
     630                 :          1 :                                 case 10: DoColor("",p[0], wnd.blank.fgcolor, Cell{}.fgcolor); break; // Change text foreground color
     631                 :          1 :                                 case 11: DoColor("",p[0], wnd.blank.bgcolor, Cell{}.bgcolor); break; // Change text background color
     632                 :          1 :                                 case 12: DoColor("",p[0], wnd.cursorcolor, 0xFFFFFF); break; // Change text cursor color
     633                 :          1 :                                 case 13: DoColor("",p[0], wnd.mousecolor1, 0xFFFFFF); break; // Change mouse foreground color
     634                 :          1 :                                 case 14: DoColor("",p[0], wnd.mousecolor2, 0xFFFFFF); break; // Change mouse background color
     635                 :          1 :                                 case 17: DoColor("",p[0], wnd.mouseselectcolor, 0xFFFFFF); break; // Change mouse select-text background color
     636                 :            :                                 case 50: break; // set font
     637                 :            :                             }
     638                 :         13 :                             break;
     639                 :            :                         }
     640                 :            :                         default:
     641                 :            :                             break;
     642                 :            :                     }
     643                 :         44 :                     goto Ground;
     644                 :            :                 }
     645                 :          0 :                 lastch=c; ui.BeepOn(); break;
     646                 :            :             }
     647         [ -  - ]:         26 :             case AnyState(U'\u0090'): p.clear(); [[fallthrough]];
     648                 :         26 :             case State(U'P', st_esc): state = st_string; scs = 4; string.clear(); break; // DCS
     649         [ -  - ]:         14 :             case AnyState(U'\u009D'): p.clear(); [[fallthrough]];
     650                 :         14 :             case State(U']', st_esc): state = st_string; scs = 5; string.clear(); break; // OSC
     651         [ -  - ]:          2 :             case AnyState(U'\u009E'): p.clear(); [[fallthrough]];
     652                 :          2 :             case State(U'^', st_esc): state = st_string; scs = 6; string.clear(); break; // PM
     653         [ -  - ]:          2 :             case AnyState(U'\u009F'): p.clear(); [[fallthrough]];
     654                 :          2 :             case State(U'_', st_esc): state = st_string; scs = 7; string.clear(); break; // APC
     655         [ -  - ]:          2 :             case AnyState(U'\u0098'): p.clear(); [[fallthrough]];
     656                 :          2 :             case State(U'X', st_esc): state = st_string; scs = 8; string.clear(); break; // SOS
     657                 :            : 
     658                 :       8506 :             case CsiState(U'0'): case CsiState(U'1'):
     659                 :       8506 :             case CsiState(U'2'): case CsiState(U'3'):
     660                 :       8506 :             case CsiState(U'4'): case CsiState(U'5'):
     661                 :       8506 :             case CsiState(U'6'): case CsiState(U'7'):
     662                 :       8506 :             case CsiState(U'8'): case CsiState(U'9'):
     663         [ +  + ]:       8506 :                 if(p.empty()) p.emplace_back();
     664                 :       8506 :                 p.back() = p.back() * 10u + (c - U'0');
     665                 :       8506 :                 break;
     666                 :       3295 :             case CsiState(U':'): case CsiState(U';'):
     667                 :       3295 :                 p.emplace_back();
     668                 :            :                 break;
     669                 :            : 
     670                 :            :             //case AnyState(U'\u0085'): // CASE_NEL
     671                 :          3 :             case State(U'E', st_esc): // esc E = CR+LF
     672                 :          3 :                 ClampedMoveX(0);
     673                 :          3 :                 state = st_default;
     674                 :         16 :                 [[fallthrough]];
     675                 :         16 :             case AnyState(10):
     676                 :         16 :             case AnyState(11):
     677                 :         16 :             case AnyState(12):
     678                 :            :             //case AnyState(U'\u0084'):
     679                 :         16 :             case State(U'D', st_esc): // esc D = CASE_IND
     680                 :         16 :                 lastch = U'\n';
     681                 :            :                 /* Within window: move cursor down; scroll the window up if at bottom */
     682                 :         16 :                 Lf();
     683                 :         16 :                 goto Ground;
     684                 :            : 
     685                 :            :             //case AnyState(U'\u008D'):
     686                 :          1 :             case State(U'M', st_esc): // esc M = CASE_RI
     687                 :            :                 /* Within window: move cursor up; scroll the window down if at top */
     688         [ -  + ]:          1 :                 if(wnd.cursy == top)
     689                 :          0 :                     YScrollDown(top, bottom, 1);
     690                 :            :                 else
     691                 :          1 :                     ClampedMoveY(wnd.cursy-1);
     692                 :          1 :                 goto Ground;
     693                 :          0 :             case State(U'q', st_csi_quo): // DECSCA: if param0=1, do CASE_SPA; if 0 or 2, do CASE_EPA. Otherwise ignore.
     694                 :          0 :             {
     695                 :          0 :                 GetParams(1,false);
     696         [ #  # ]:          0 :                 if(p[0]==1)
     697                 :            :                 {
     698                 :            :             //case AnyState(U'\u0096'):
     699                 :          1 :             case State(U'V', st_esc): // esc V = SPA: start protected area
     700                 :          1 :                     wnd.blank.protect = true;
     701                 :            :                 }
     702   [ #  #  #  # ]:          0 :                 else if(p[0]==0 || p[0]==2)
     703                 :            :                 {
     704                 :            :             //case AnyState(U'\u0097'):
     705                 :          1 :             case State(U'W', st_esc): // esc W = EPA: end protected area
     706                 :          1 :                     wnd.blank.protect = false;
     707                 :            :                 }
     708                 :          2 :                 goto Ground;
     709                 :            :             }
     710                 :            :             /*//case AnyState(U'\u0088'): [[fallthrough]];
     711                 :            :             case State(U'H', st_esc): // esc H = CASE_HTS: horizontal tab set
     712                 :            :                 goto Ground;
     713                 :            :             //case AnyState(U'\u0090'): [[fallthrough]];
     714                 :            :             case State(U'P', st_esc): // esc P = CASE_DCS (starts string)
     715                 :            :                 goto Ground;
     716                 :            :             //case AnyState(U'\u009D'): [[fallthrough]];
     717                 :            :             case State(U']', st_esc): // esc ] = CASE_OSC (starts string)
     718                 :            :                 goto Ground;
     719                 :            :             //case AnyState(U'\u009E'): [[fallthrough]];
     720                 :            :             case State(U'^', st_esc): // esc ^ = CASE_PM (starts string)
     721                 :            :                 goto Ground;
     722                 :            :             //case AnyState(U'\u009F'): [[fallthrough]];
     723                 :            :             case State(U'_', st_esc): // esc _ = CASE_APC (starts string)
     724                 :            :                 goto Ground;
     725                 :            :             //case AnyState(U'\u0098'): [[fallthrough]];
     726                 :            :             case State(U'X', st_esc): // esc X = CASE_SOS: start of string
     727                 :            :                 goto Ground;
     728                 :            :             //case AnyState(U'\u009C'): [[fallthrough]];
     729                 :            :             case State(U'\\', st_esc):// esc \\ = CASE_ST: end of string
     730                 :            :                 goto Ground;*/
     731                 :            : 
     732                 :          2 :             case State(U'c', st_esc): ResetAttr(); Reset(); goto Ground;         // esc c   (RI - full reset)
     733                 :          1 :             case State(U'p', st_csi_ex): ResetAttr(); Reset(false); goto Ground; // CSI ! p (DECSTR - CSI reset)
     734                 :            : 
     735                 :          3 :             case State(U'7', st_esc): [[fallthrough]]; // esc 7 (DECSC), csi s (ANSI_SC)
     736                 :          3 :             case State(U's', st_csi): SaveCur(); goto Ground;
     737                 :          3 :             case State(U'8', st_esc): [[fallthrough]]; // esc 8 (DECRC), csi u (ANSI_RC)
     738                 :          3 :             case State(U'u', st_csi): RestoreCur(); goto Ground;
     739                 :          2 :             case State(U'0', st_scs): gset[scs&3] = 1; goto Ground; // DEC graphics (TODO)
     740                 :          2 :             case State(U'1', st_scs): gset[scs&3] = 0; goto Ground; // DEC alt chars?
     741                 :          2 :             case State(U'2', st_scs): gset[scs&3] = 0; goto Ground; // DEC alt gfx?
     742                 :          2 :             case State(U'`', st_scs): gset[scs&3] = 0; goto Ground; // nor/dan?
     743                 :          2 :             case State(U'4', st_scs): gset[scs&3] = 0; goto Ground; // dut?
     744                 :          2 :             case State(U'5', st_scs): gset[scs&3] = 0; goto Ground; // fin?
     745                 :          2 :             case State(U'6', st_scs): gset[scs&3] = 0; goto Ground; // nor/dan3?
     746                 :          2 :             case State(U'7', st_scs): gset[scs&3] = 0; goto Ground; // swe?
     747                 :          2 :             case State(U'9', st_scs): gset[scs&3] = 0; goto Ground; // fre/can2?
     748                 :          2 :             case State(U'A', st_scs): gset[scs&3] = 0; goto Ground; // british?
     749                 :          2 :             case State(U'B', st_scs): gset[scs&3] = 0; goto Ground; // ASCII (TODO)
     750                 :          2 :             case State(U'C', st_scs): gset[scs&3] = 0; goto Ground; // fin2?
     751                 :          2 :             case State(U'E', st_scs): gset[scs&3] = 0; goto Ground; // nor/dan2?
     752                 :          2 :             case State(U'f', st_scs): gset[scs&3] = 0; goto Ground; // fre2?
     753                 :          2 :             case State(U'F', st_scs): gset[scs&3] = 0; goto Ground; // iso greek supp?
     754                 :          2 :             case State(U'H', st_scs): gset[scs&3] = 0; goto Ground; // swe2? iso hebrew supp?
     755                 :          2 :             case State(U'K', st_scs): gset[scs&3] = 0; goto Ground; // ger?
     756                 :          2 :             case State(U'L', st_scs): gset[scs&3] = 0; goto Ground; // iso cyr?
     757                 :          2 :             case State(U'M', st_scs): gset[scs&3] = 0; goto Ground; // iso5 supp?
     758                 :          2 :             case State(U'Q', st_scs): gset[scs&3] = 0; goto Ground; // fre/can?
     759                 :          2 :             case State(U'R', st_scs): gset[scs&3] = 0; goto Ground; // fre?
     760                 :          2 :             case State(U'<', st_scs): gset[scs&3] = 0; goto Ground; // DEC supp?
     761                 :          2 :             case State(U'=', st_scs): gset[scs&3] = 0; goto Ground; // swiss?
     762                 :          2 :             case State(U'>', st_scs): gset[scs&3] = 0; goto Ground; // DEC technical
     763                 :          2 :             case State(U'U', st_scs): gset[scs&3] = 0; goto Ground; // linux pc?
     764                 :          2 :             case State(U'Y', st_scs): gset[scs&3] = 0; goto Ground; // ita?
     765                 :          2 :             case State(U'Z', st_scs): gset[scs&3] = 0; goto Ground; // spa?
     766                 :          1 :             case State(U'3', st_scr): wnd.LineSetRenderSize(2); goto Ground; // DECDHL top
     767                 :          1 :             case State(U'4', st_scr): wnd.LineSetRenderSize(3); goto Ground; // DECDHL bottom
     768                 :          1 :             case State(U'5', st_scr): wnd.LineSetRenderSize(0); goto Ground; // DECSWL
     769                 :          1 :             case State(U'6', st_scr): wnd.LineSetRenderSize(1); goto Ground; // DECDWL
     770                 :          1 :             case State(U'8', st_scr): // clear screen with 'E' // esc # 8
     771                 :          1 :                 wnd.blank.ch = U'E';
     772                 :          1 :                 wnd.FillBox(0,0, wnd.xsize,wnd.ysize);
     773                 :          1 :                 wnd.blank.ch = U' ';
     774                 :          1 :                 wnd.cursx = wnd.cursy = 0;
     775                 :          1 :                 goto Ground;
     776                 :          1 :             case State(U'@', st_esc_percent): utfmode = 0; goto Ground; // esc % @
     777                 :          2 :             case State(U'G', st_esc_percent): [[fallthrough]];          // esc % G
     778                 :          2 :             case State(U'8', st_esc_percent): utfmode = 1; goto Ground; // esc % 8
     779                 :          1 :             case State(U'g', st_csi): /* TODO: set tab stops */ goto Ground;
     780                 :          1 :             case State(U'q', st_csi): /* TODO: set leds */ goto Ground;
     781                 :          2 :             case State(U'G', st_csi): [[fallthrough]];
     782                 :          2 :             case State(U'`', st_csi): { GetParams(1,true); ClampedMoveX(p[0]-1);        break; } // absolute hpos
     783                 :          1 :             case State(U'd', st_csi): { GetParams(1,true); ClampedMoveY(p[0]-1, false); break; } // absolute vpos
     784                 :          2 :             case State(U'F', st_csi): ClampedMoveX(0); [[fallthrough]];
     785                 :          7 :             case State(U'A', st_csi): { GetParams(1,true); ClampedMoveY(wnd.cursy-p[0]); break; }
     786                 :          2 :             case State(U'E', st_csi): ClampedMoveX(0); [[fallthrough]];
     787                 :          9 :             case State(U'e', st_csi): [[fallthrough]];
     788                 :          9 :             case State(U'B', st_csi): { GetParams(1,true); ClampedMoveY(wnd.cursy+p[0]); break; }
     789                 :        109 :             case State(U'a', st_csi): [[fallthrough]];
     790                 :        109 :             case State(U'C', st_csi): { GetParams(1,true); ClampedMoveX(wnd.cursx+p[0]); break; }
     791                 :          5 :             case State(U'D', st_csi): { GetParams(1,true); ClampedMoveX(wnd.cursx-p[0]); break; }
     792                 :         45 :             case State(U'H', st_csi): [[fallthrough]];
     793                 :         45 :             case State(U'f', st_csi): { GetParams(2,true); ClampedMove(p[1]-1, p[0]-1, false); break; }
     794                 :          6 :             case State(U'J', st_csi):
     795                 :          6 :             case State(U'J', st_csi_quo):
     796                 :          6 :                 GetParams(1,false);
     797   [ +  +  +  - ]:          6 :                 switch(p[0])
     798                 :            :                 {
     799                 :          2 :                     case 0: // erase from cursor to end of display
     800         [ +  - ]:          2 :                         if(wnd.cursy < wnd.ysize-1)
     801                 :          2 :                             wnd.FillBox(0,wnd.cursy+1, wnd.xsize, wnd.ysize-wnd.cursy-1, wnd.blank);
     802                 :          2 :                         goto clreol;
     803                 :          2 :                     case 1: // erase from start to cursor
     804         [ -  + ]:          2 :                         if(wnd.cursy > 0) wnd.FillBox(0,0, wnd.xsize,wnd.cursy, wnd.blank);
     805                 :          2 :                         goto clrbol;
     806                 :          2 :                     case 2: // erase whole display
     807                 :          2 :                         wnd.FillBox(0,0, wnd.xsize,wnd.ysize, wnd.blank);
     808                 :            :                         break;
     809                 :            :                 }
     810                 :            :                 break;
     811                 :          6 :             case State(U'K', st_csi):
     812                 :          6 :             case State(U'K', st_csi_quo):
     813                 :          6 :                 GetParams(1,false);
     814                 :            :                 // 0: erase from cursor to end of line
     815                 :            :                 // 1: erase from start of line to cursor
     816                 :            :                 // 2: erase whole line
     817   [ +  +  +  - ]:          6 :                 switch(p[0])
     818                 :            :                 {
     819                 :          4 :                     case 0: clreol: wnd.FillBox(wnd.cursx,wnd.cursy, wnd.xsize-wnd.cursx, 1, wnd.blank); break;
     820                 :          4 :                     case 1: clrbol: wnd.FillBox(0,        wnd.cursy, wnd.cursx+1,  1, wnd.blank); break;
     821                 :          2 :                     case 2: wnd.FillBox(0, wnd.cursy, wnd.xsize,    1, wnd.blank); break;
     822                 :            :                 }
     823                 :            :                 break;
     824                 :          1 :             case State(U'M', st_csi):
     825                 :          1 :                 GetParams(1,true);
     826                 :          1 :                 YScrollUp(wnd.cursy, bottom, p[0]);
     827                 :            :                 break;
     828                 :          1 :             case State(U'L', st_csi):
     829                 :          1 :                 GetParams(1,true);
     830                 :            :                 // scroll the rest of window c lines down,
     831                 :            :                 // including where cursor is. Don't move cursor.
     832                 :          1 :                 YScrollDown(wnd.cursy, bottom, p[0]);
     833                 :            :                 break;
     834                 :          1 :             case State(U'S', st_csi): // xterm version?
     835                 :          1 :                 GetParams(1,true);
     836                 :          1 :                 YScrollUp(top, bottom, p[0]);
     837                 :            :                 break;
     838                 :          1 :             case State(U'T', st_csi): // csi T, track mouse
     839   [ +  -  -  +  :          1 :                 if(p.size() > 1 || p.empty() || p[0]==0)
                   -  - ]
     840                 :            :                 {
     841                 :            :                     // mouse track
     842                 :          1 :                     goto Ground;
     843                 :            :                 }
     844                 :          1 :                 [[fallthrough]];
     845                 :          1 :             case State(U'^', st_csi): // csi ^ , scroll down
     846                 :          1 :                 GetParams(1,true);
     847                 :            :                 // Reverse scrolling by N lines
     848                 :            :                 // scroll the entire of window c lines down. Don't move cursor.
     849                 :          1 :                 YScrollDown(top, bottom, p[0]);
     850                 :            :                 break;
     851                 :          1 :             case State(U'P', st_csi):
     852         [ -  + ]:          1 :                 GetParams(1,true); c = std::min(p[0], unsigned(wnd.xsize-wnd.cursx));
     853                 :            :                 // insert c black holes at cursor (eat c characters
     854                 :            :                 // and scroll line horizontally to left)
     855         [ +  - ]:          1 :                 if(c)
     856                 :            :                 {
     857                 :          1 :                     unsigned remain = wnd.xsize - (wnd.cursx+c);
     858                 :          1 :                     wnd.CopyText(wnd.cursx,wnd.cursy, wnd.xsize-remain,wnd.cursy, remain,1);
     859                 :          1 :                     wnd.FillBox(wnd.xsize-c,wnd.cursy, c,1);
     860                 :            :                 }
     861                 :            :                 break;
     862                 :          1 :             case State(U'X', st_csi): // (ECH)
     863                 :          1 :                 GetParams(1,true);
     864                 :            :                 // write c spaces at cursor (overwrite)
     865         [ -  + ]:          1 :                 wnd.FillBox(wnd.cursx,wnd.cursy, std::min(std::size_t(p[0]), wnd.xsize-wnd.cursx), 1);
     866                 :          1 :                 break;
     867                 :          1 :             case State(U'@', st_csi):
     868         [ -  + ]:          1 :                 GetParams(1,true); c = std::min(p[0], unsigned(wnd.xsize-wnd.cursx));
     869                 :            :                 // insert c spaces at cursor
     870         [ +  - ]:          1 :                 if(c)
     871                 :            :                 {
     872                 :          1 :                     unsigned remain = wnd.xsize - (wnd.cursx+c);
     873                 :          1 :                     wnd.CopyText(wnd.cursx+c,wnd.cursy, wnd.cursx,wnd.cursy, remain,1);
     874                 :          1 :                     wnd.FillBox(wnd.cursx,wnd.cursy, c,1);
     875                 :            :                 }
     876                 :            :                 break;
     877                 :          1 :             case State(U'r', st_csi): // CSI r
     878                 :          1 :                 GetParams(2,false);
     879         [ +  - ]:          1 :                 if(!p[0]) p[0]=1;
     880         [ +  - ]:          1 :                 if(!p[1]) p[1]=wnd.ysize;
     881   [ +  -  +  - ]:          1 :                 if(p[0] < p[1] && p[1] <= wnd.ysize)
     882                 :            :                 {
     883                 :          1 :                     top = p[0]-1; bottom = p[1]-1;
     884                 :            :                     //fprintf(stderr, "Creating a window with top=%zu, bottom=%zu\n", top,bottom);
     885                 :          1 :                     ClampedMove(0, top, false);
     886                 :            :                 }
     887                 :            :                 break;
     888                 :          3 :             case State(U'n', st_csi):
     889                 :          3 :                 GetParams(1,false);
     890      [ +  +  + ]:          3 :                 switch(p[0])
     891                 :            :                 {
     892                 :          1 :                     char Buf[32];
     893                 :          2 :                     case 5: EchoBack(U"\33[0n"); break;
     894         [ +  - ]:          2 :                     case 6: EchoBack(FromUTF8(std::string_view{Buf, (std::size_t)std::sprintf(Buf, "\33[%zu;%zuR", wnd.cursy+1, wnd.cursx+1)})); break;
     895                 :            :                 }
     896                 :            :                 break;
     897                 :          1 :             case State(U'c', st_csi_dec3): // csi = 0 c, Tertiary device attributes (printer?)
     898                 :          1 :                 GetParams(1,false); // Tertiary device attributes (printer?)
     899                 :            :                 // Example response: <ESC> P ! | 0 <ST>
     900         [ +  - ]:          2 :                 if(!p[0]) EchoBack(U"\33P!|00000000\x9C"); // Note: DCS response
     901                 :            :                 break;
     902                 :          1 :             case State(U'c', st_csi_dec2): // csi > 0 c, Secondary device attributes (terminal)
     903                 :          1 :                 GetParams(1,false);
     904                 :            :                 // Example response: ^[[>41;344;0c  (middle=firmware version)
     905         [ +  - ]:          2 :                 if(!p[0]) EchoBack(U"\33[>1;1;0c");
     906                 :            :                 break;
     907                 :          2 :             case State(U'c', st_csi): // csi 0 c // Primary device attributes (host computer)
     908                 :          2 :                 GetParams(1,false);
     909         [ +  - ]:          2 :                 if(!p[0])
     910                 :            :                 {
     911                 :            :             //case AnyState(U'\u009A'): [[fallthrough]];
     912                 :          3 :             case State(U'Z', st_esc): // esc Z = CASE_DECID
     913                 :          6 :                     EchoBack(U"\33[?65;1;6;8;15;22c");
     914                 :            :                 }
     915                 :            :                 // Example response: ^[[?64;1;2;6;9;15;18;21;22c
     916                 :            :                 //  1=132 columns, 2=printer port, 4=sixel extension
     917                 :            :                 //  6=selective erase, 7=DRCS, 8=user-defines keys
     918                 :            :                 //  9=national replacement charsets, 12=SCS extension
     919                 :            :                 // 15=technical charset, 18=windowing capability, 21=horiz scrolling,
     920                 :            :                 // 22=ansi color/vt525, 29=ansi text locator
     921                 :            :                 // 23=greek ext, 24=turkish ext, 42=latin2 cset, 44=pcterm,
     922                 :            :                 // 45=softkeymap, 46=ascii emulation
     923                 :            :                 // 62..69 = VT level (62=VT200, 63=VT300, 64=VT400)
     924                 :            :                 break;
     925                 :          3 :             case State(U'h', st_csi_dec): // csi ? h, misc modes on
     926                 :          3 :             case State(U'l', st_csi_dec): // csi ? l, misc modes off
     927                 :          3 :             case State(U'p', st_csi_dec_dol): // csi ? $p, query
     928                 :          3 :             {
     929                 :          3 :                 bool set = c == U'h', query = c == U'p';
     930                 :          3 :                 char Buf[32];
     931                 :            :                 // 1=CKM, 2=ANM, 3=COLM, 4=SCLM, 5=SCNM, 6=OM, 7=AWM, 8=ARM,
     932                 :            :                 // 18=PFF, 19=PEX, 25=TCEM, 40=132COLS, 42=NRCM,
     933                 :            :                 // 44=MARGINBELL, ...
     934                 :            :                 //   6 puts cursor at (0,0) both set,clear
     935                 :            :                 //  25 enables/disables cursor visibility
     936                 :            :                 //  40 enables/disables 80/132 mode (note: if enabled, RESET changes to one of these)
     937                 :            :                 //   3 sets width at 132(enable), 80(disable) if "40" is enabled
     938                 :            :                 //   5 = screenwide inverse color
     939                 :          3 :                 GetParams(0, false);
     940         [ +  + ]:          6 :                 for(auto a: p)
     941                 :            :                 {
     942                 :          3 :                     unsigned value = 0; // unsupported
     943   [ +  +  +  - ]:          3 :                     switch(a)
     944                 :            :                     {
     945         [ +  - ]:          1 :                         case 6:  if(query) value = 2; else ClampedMove(0, top, false); break;
     946   [ +  -  -  + ]:          1 :                         case 25: if(query) value = wnd.cursorvis?1:2; else wnd.cursorvis = set; break;
     947   [ -  +  -  - ]:          1 :                         case 5:  if(query) value = wnd.inverse?1:2;   else wnd.inverse   = set; break;
     948                 :            :                     }
     949         [ +  + ]:          3 :                     if(query)
     950         [ +  - ]:          2 :                         EchoBack(FromUTF8(std::string_view{Buf, (std::size_t)
     951         [ +  - ]:          1 :                             std::sprintf(Buf, "\33[?%u;%u$y", a, value)}));
     952                 :            :                 }
     953                 :          3 :                 break;
     954                 :            :             }
     955                 :          4 :             case State(U'h', st_csi): // csi h, ansi modes on
     956                 :          4 :             case State(U'l', st_csi): // csi l, ansi modes off
     957                 :          4 :             case State(U'p', st_csi_dol): // csi $p, query
     958                 :          4 :             {
     959                 :          4 :                 bool /*set = c == U'h',*/ query = c == U'p';
     960                 :          4 :                 char Buf[32];
     961                 :            :                 // 2 = keyboard locked
     962                 :            :                 // 4 = insert mode
     963                 :            :                 // 12 = no local echo
     964                 :            :                 // 20 = auto linefeed
     965                 :          4 :                 GetParams(0, false);
     966         [ +  + ]:          8 :                 for(auto a: p)
     967                 :            :                 {
     968                 :          4 :                     unsigned value = 0; // unsupported
     969   [ +  +  +  +  :          4 :                     switch(a)
                      - ]
     970                 :            :                     {
     971                 :          1 :                         case 2: value = 4; break; // permanently unset
     972                 :          1 :                         case 4: value = 4; break; // permanently unset
     973                 :          1 :                         case 12: value = 4; break; // permanently unset
     974                 :          1 :                         case 20: value = 4; break; // permanently unset
     975                 :            :                     }
     976         [ +  + ]:          4 :                     if(query)
     977         [ +  - ]:          4 :                         EchoBack(FromUTF8(std::string_view{Buf, (std::size_t)
     978                 :          2 :                             std::sprintf(Buf, "\33[%u;%u$y", a, value)}));
     979                 :            :                 }
     980                 :          4 :                 break;
     981                 :            :             }
     982                 :          1 :             case State(U'v', st_csi_dol): // csi $v (DECCRA): copy rectangular area
     983                 :          1 :             {
     984                 :          1 :                 GetParams(7,true);
     985         [ -  + ]:          1 :                 unsigned pts=p[0], pls=p[1], pbs=p[2], prs=p[3], ptd=p[5], pld=p[6];
     986                 :            :                 // Ignores [4] = source page number, [7] = target page number.
     987                 :            :                 // Note: Xterm parses params from right to left, meaning that
     988                 :            :                 //       for xterm, our [3] is actually [n-5]
     989         [ -  + ]:          1 :                 if(pbs > wnd.ysize) pbs = wnd.ysize;
     990         [ -  + ]:          1 :                 if(prs > wnd.xsize) prs = wnd.xsize;
     991         [ -  + ]:          1 :                 if(ptd > wnd.ysize) ptd = wnd.ysize;
     992         [ -  + ]:          1 :                 if(pld > wnd.xsize) pld = wnd.ysize;
     993         [ +  - ]:          1 :                 if(pts <= pbs && pls <= prs)
     994                 :            :                 {
     995                 :          1 :                     unsigned width = prs-pls+1, height = pbs-pts+1;
     996                 :          1 :                     --pld;--ptd;--pls;--pts;
     997         [ -  + ]:          1 :                     if(pld+width > wnd.xsize) width = wnd.xsize-pld;
     998         [ -  + ]:          1 :                     if(ptd+height > wnd.ysize) height = wnd.ysize-ptd;
     999         [ +  - ]:          1 :                     if(width && height)
    1000                 :          1 :                         wnd.CopyText(pld,ptd, pls,pts, width,height);
    1001                 :            :                 }
    1002                 :            :                 break;
    1003                 :            :             }
    1004                 :          1 :             case State(U'z', st_csi_dol): // csi $z: fill rectangular area with space
    1005                 :          1 :             {                             //         but don't touch attributes.
    1006                 :          1 :                 GetParams(4,true);
    1007         [ -  + ]:          1 :                 unsigned pts=p[0], pls=p[1], pbs=p[2], prs=p[3];
    1008         [ -  + ]:          1 :                 if(pbs > wnd.ysize) pbs = wnd.ysize;
    1009         [ -  + ]:          1 :                 if(prs > wnd.xsize) prs = wnd.xsize;
    1010         [ +  + ]:          2 :                 for(unsigned y=pts; y<=pbs; ++y)
    1011         [ +  + ]:          2 :                     for(unsigned x=pls; x<=prs; ++x)
    1012                 :          1 :                         wnd.PutCh_KeepAttr(x-1, y-1, U' ', false);
    1013                 :            :                 break;
    1014                 :            :             }
    1015                 :          1 :             case State(U'x', st_csi_dol): // csi $x: fill rectangular area with given char
    1016                 :          1 :             {
    1017                 :          1 :                 GetParams(5,true);
    1018         [ -  + ]:          1 :                 unsigned ch=p[0], pts=p[1], pls=p[2], pbs=p[3], prs=p[4];
    1019         [ -  + ]:          1 :                 if(pbs > wnd.ysize) pbs = wnd.ysize;
    1020         [ -  + ]:          1 :                 if(prs > wnd.xsize) prs = wnd.xsize;
    1021                 :          1 :                 bool dbl = isdouble(ch);
    1022         [ +  + ]:          2 :                 for(unsigned y=pts; y<=pbs; ++y)
    1023         [ +  + ]:          2 :                     for(unsigned x=pls; x<=prs; ++x)
    1024                 :          1 :                         wnd.PutCh(x-1, y-1, ch, dbl);
    1025                 :            :                 break;
    1026                 :            :             }
    1027                 :          2 :             case State(U'r', st_csi_dol): // csi $r: DECCARA: change attributes in rectangular area
    1028                 :          2 :             case State(U't', st_csi_dol): // csi $t: DECRARA: toggle attributes in rectangular area
    1029                 :          2 :             {
    1030                 :          2 :                 bool toggle = (c == U't');
    1031                 :          2 :                 GetParams(5, true); // Make sure there is at least 1 SGR param
    1032         [ -  + ]:          2 :                 unsigned pts=p[0], pls=p[1], pbs=p[2], prs=p[3];
    1033         [ -  + ]:          2 :                 if(pbs > wnd.ysize) pbs = wnd.ysize;
    1034         [ -  + ]:          2 :                 if(prs > wnd.xsize) prs = wnd.xsize;
    1035                 :          2 :                 --pts;--pbs;--pls;--prs;
    1036         [ +  + ]:          4 :                 for(unsigned y=pts; y<=pbs; ++y)
    1037         [ +  + ]:          4 :                     for(unsigned x=pls; x<=prs; ++x)
    1038                 :            :                     {
    1039                 :          2 :                         auto& cell = wnd.cells[y*wnd.xsize+x];
    1040                 :          2 :                         auto temp = cell;
    1041                 :          2 :                         c=0;
    1042         [ +  + ]:          4 :                         for(auto ai = std::next(p.cbegin(), 4); ai != p.end(); ++ai)
    1043                 :          2 :                             ProcessSGR(c, *ai,
    1044         [ #  # ]:          0 :                                 [&]() { if(!toggle) temp = Cell{}; },
    1045                 :          2 :                                 [&](auto&& setter, auto&& getter, auto newvalue)
    1046                 :            :                                 {
    1047   [ +  +  -  -  :          2 :                                     if(toggle)
          -  -  -  -  -  
          -  -  -  -  -  
          -  -  -  -  -  
          -  -  -  -  -  
          -  -  -  -  -  
          -  -  -  -  -  
          -  -  -  -  -  
          -  -  -  -  -  
          -  -  -  -  -  
                -  -  - ]
    1048                 :            :                                     {
    1049                 :            :                                         // Note: toggling using a SGR command
    1050                 :            :                                         //       that clears the attribute does nothing,
    1051                 :            :                                         //       because value XOR 0 is value.
    1052                 :            :                                         //       This matches what XTerm does.
    1053                 :          1 :                                         newvalue ^= getter(temp);
    1054                 :            :                                     }
    1055   [ -  -  -  -  :          2 :                                     setter(temp, newvalue);
             -  -  -  - ]
    1056                 :            :                                 }
    1057                 :            :                                       );
    1058                 :          2 :                         temp.ch = cell.ch;
    1059         [ +  - ]:          2 :                         if(temp != cell)
    1060                 :            :                         {
    1061                 :          2 :                             cell       = temp;
    1062                 :          2 :                             cell.dirty = true;
    1063                 :            :                         }
    1064                 :            :                     }
    1065                 :          2 :                 break;
    1066                 :            :             }
    1067                 :          1 :             case State(U'b', st_csi):
    1068                 :          1 :             {
    1069                 :          1 :                 GetParams(1,true);
    1070                 :            :                 // Repeat last printed character n times
    1071                 :          1 :                 bool dbl = isdouble(lastch);
    1072   [ -  +  +  + ]:          6 :                 for(unsigned m = std::min(p[0], unsigned(wnd.xsize*wnd.ysize)), c=0; c<m; ++c)
    1073                 :            :                 {
    1074                 :          5 :                     PutC(lastch, dbl);
    1075                 :            :                 }
    1076                 :            :                 break;
    1077                 :            :             }
    1078                 :       1091 :             case State(U'm', st_csi): // csi m (SGR)
    1079                 :       1091 :             {
    1080                 :       1091 :                 GetParams(1, false); // Make sure there is at least 1 param
    1081                 :       1091 :                 c=0;
    1082         [ +  + ]:       5358 :                 for(auto a: p)
    1083                 :       4267 :                     ProcessSGR(c,a,
    1084                 :          2 :                         [&]() { ResetAttr(); },
    1085                 :       1199 :                         [&](auto&& setter, auto&& /*getter*/, auto newvalue)
    1086                 :       1198 :                             { setter(wnd.blank, newvalue); }
    1087                 :            :                               );
    1088                 :       1091 :                 break;
    1089                 :            :             }
    1090                 :            : 
    1091                 :       6200 :             default:
    1092         [ +  + ]:       6200 :                 if(state == st_string) state = st_string_str;
    1093         [ +  + ]:       6200 :                 if(state == st_string_str)
    1094                 :            :                 {
    1095                 :      22592 :                     string += c;
    1096                 :            :                     break;
    1097                 :            :                 }
    1098         [ +  + ]:       6095 :                 if(state != st_default) goto Ground;
    1099                 :       6084 :                 PutC(lastch = c, isdouble(c));
    1100                 :            :                 break;
    1101                 :            :         }
    1102                 :            :     }
    1103                 :            :     #undef AnyState
    1104                 :        773 : }
    1105                 :            : 
    1106                 :         19 : void TerminalWindow::EchoBack(std::u32string_view buffer)
    1107                 :            : {
    1108                 :            :     //Write(buffer, size); // DEBUGGING
    1109                 :         19 :     OutBuffer.insert(OutBuffer.end(), buffer.begin(), buffer.end());
    1110                 :         19 : }
    1111                 :            : 
    1112                 :        401 : void TerminalWindow::SaveCur()
    1113                 :            : {
    1114                 :        401 :     backup.cx = wnd.cursx;
    1115                 :        401 :     backup.cy = wnd.cursy;
    1116                 :        401 :     backup.attr = wnd.blank;
    1117                 :        401 : }
    1118                 :            : 
    1119                 :          3 : void TerminalWindow::RestoreCur()
    1120                 :            : {
    1121                 :          3 :     wnd.cursx = backup.cx;
    1122                 :          3 :     wnd.cursy = backup.cy;
    1123                 :          3 :     wnd.blank = backup.attr;
    1124                 :          3 : }
    1125                 :            : 
    1126                 :          1 : void TerminalWindow::Resize(std::size_t newsx, std::size_t newsy)
    1127                 :            : {
    1128         [ +  - ]:          1 :     if(bottom == wnd.ysize-1)
    1129                 :            :     {
    1130                 :          1 :         bottom = newsy-1;
    1131                 :            :         //fprintf(stderr, "Creating a window with top=%zu, bottom=%zu\n", top,bottom);
    1132                 :            :     }
    1133                 :          1 :     wnd.Resize(newsx, newsy);
    1134                 :          1 : }
    1135                 :            : 
    1136                 :            : #ifdef RUN_TESTS
    1137                 :            : template<typename F>
    1138                 :        397 : static auto TerminalTest(int w, int h, std::u32string_view sample, F&& test)
    1139                 :            : {
    1140                 :        397 :     Window wnd(w, h);
    1141         [ +  - ]:        397 :     TerminalWindow term(wnd);
    1142         [ +  - ]:        397 :     term.Write(sample);
    1143                 :        741 :     return test(wnd);
    1144                 :        397 : }
    1145                 :            : template<typename F>
    1146                 :        345 : static auto TerminalTest(std::u32string_view sample, F&& test)
    1147                 :            : {
    1148                 :        345 :     return TerminalTest(80, 25, sample, test);
    1149                 :            : }
    1150                 :        309 : static auto TerminalTestSGR(std::u32string_view sample)
    1151                 :            : {
    1152                 :        618 :     return TerminalTest(sample, [&](auto& wnd) { return wnd.blank; });
    1153                 :            : }
    1154                 :         52 : static auto TerminalTestCursor(int w, int h, std::u32string_view sample)
    1155                 :            : {
    1156                 :         52 :     return TerminalTest(w,h, sample, [&](auto& wnd)
    1157                 :            :     {
    1158                 :            :         // Return the following:
    1159                 :            :         // Cursor location (x, y)
    1160                 :            :         // Location of first symbol non-space within window
    1161                 :            :         // Location of last symbol non-space within window
    1162                 :            :         // Number of non-space symbols within window
    1163                 :         52 :         std::array<unsigned,5> result{ (unsigned)wnd.cursx, (unsigned)wnd.cursy, ~0u, 0, 0 };
    1164         [ +  + ]:       1716 :         for(unsigned p=0; p<wnd.cells.size(); ++p)
    1165         [ +  + ]:       1664 :             if(wnd.cells[p].ch != U' ')
    1166                 :            :             {
    1167         [ +  + ]:         54 :                 if(result[2] == ~0u) result[2] = p;
    1168                 :         54 :                 result[3] = p;
    1169                 :         54 :                 ++result[4];
    1170                 :            :             }
    1171         [ +  + ]:         52 :         if(result[2] == ~0u) result[2] = 0u;
    1172                 :         52 :         return result;
    1173                 :         52 :     });
    1174                 :            : }
    1175                 :            : 
    1176                 :            : 
    1177                 :          3 : TEST(terminal, text_rendering_works)
    1178                 :            : {
    1179                 :          2 :     EXPECT_TRUE(TerminalTest(U"", [&](auto& wnd)
    1180                 :            :     {
    1181                 :            :         return wnd.cells[0].ch == U' ';
    1182                 :          0 :     }));
    1183                 :          3 :     EXPECT_TRUE(TerminalTest(U"OK", [&](auto& wnd)
    1184                 :            :     {
    1185                 :            :         return wnd.cells[0].ch == U'O' && wnd.cells[1].ch == U'K' && wnd.cells[2].ch == U' ';
    1186                 :          0 :     }));
    1187                 :          3 :     EXPECT_TRUE(TerminalTest(U"O\rK", [&](auto& wnd)
    1188                 :            :     {
    1189                 :            :         return wnd.cells[0].ch == U'K' && wnd.cells[1].ch == U' ';
    1190                 :          0 :     }));
    1191                 :          3 :     EXPECT_TRUE(TerminalTest(U"OK\b!", [&](auto& wnd)
    1192                 :            :     {
    1193                 :            :         return wnd.cells[0].ch == U'O' && wnd.cells[1].ch == U'!';
    1194                 :          0 :     }));
    1195                 :          3 :     EXPECT_TRUE(TerminalTest(U"O\tK", [&](auto& wnd)
    1196                 :            :     {
    1197                 :            :         return wnd.cells[0].ch == U'O' && wnd.cells[8].ch == U'K';
    1198                 :          0 :     }));
    1199                 :          3 :     EXPECT_TRUE(TerminalTest(U" O\tK", [&](auto& wnd)
    1200                 :            :     {
    1201                 :            :         return wnd.cells[1].ch == U'O' && wnd.cells[8].ch == U'K';
    1202                 :          0 :     }));
    1203                 :          3 :     EXPECT_TRUE(TerminalTest(U"O\nK", [&](auto& wnd)
    1204                 :            :     {
    1205                 :            :         return wnd.cells[0].ch == U'O' && wnd.cells[81].ch == U'K';
    1206                 :          1 :     }));
    1207                 :          1 : }
    1208                 :          3 : TEST(terminal, sgr_attributes)
    1209                 :            : {
    1210                 :          3 :     EXPECT_EQ(TerminalTestSGR(U"\33[0m"), []{Cell tmp; return tmp; }());
    1211                 :          3 :     EXPECT_EQ(TerminalTestSGR(U"\33[1m"), []{Cell tmp; tmp.bold=true; return tmp; }());
    1212                 :          3 :     EXPECT_EQ(TerminalTestSGR(U"\33[2m"), []{Cell tmp; tmp.dim=true; return tmp; }());
    1213                 :          3 :     EXPECT_EQ(TerminalTestSGR(U"\33[3m"), []{Cell tmp; tmp.italic=true; return tmp; }());
    1214                 :          3 :     EXPECT_EQ(TerminalTestSGR(U"\33[4m"), []{Cell tmp; tmp.underline=true; return tmp; }());
    1215                 :          3 :     EXPECT_EQ(TerminalTestSGR(U"\33[5m"), []{Cell tmp; tmp.blink=1; return tmp; }());
    1216                 :          3 :     EXPECT_EQ(TerminalTestSGR(U"\33[6m"), []{Cell tmp; tmp.blink=2; return tmp; }());
    1217                 :          3 :     EXPECT_EQ(TerminalTestSGR(U"\33[7m"), []{Cell tmp; tmp.inverse=true; return tmp; }());
    1218                 :          3 :     EXPECT_EQ(TerminalTestSGR(U"\33[9m"), []{Cell tmp; tmp.overstrike=true; return tmp; }());
    1219                 :          3 :     EXPECT_EQ(TerminalTestSGR(U"\33[21m"), []{Cell tmp; tmp.underline2=true; return tmp; }());
    1220                 :          3 :     EXPECT_EQ(TerminalTestSGR(U"\33[53m"), []{Cell tmp; tmp.overlined=true; return tmp; }());
    1221                 :            : 
    1222                 :          4 :     EXPECT_EQ(TerminalTestSGR(U"\33[1;2;3;4;7;21;22m"), []{Cell tmp; tmp.bold=tmp.dim=tmp.italic=tmp.underline=tmp.underline2=tmp.inverse=1; tmp.dim=tmp.bold=0; return tmp; }());
    1223                 :          4 :     EXPECT_EQ(TerminalTestSGR(U"\33[1;2;3;4;7;21;23m"), []{Cell tmp; tmp.bold=tmp.dim=tmp.italic=tmp.underline=tmp.underline2=tmp.inverse=1; tmp.italic=tmp.fraktur=0; return tmp; }());
    1224                 :          4 :     EXPECT_EQ(TerminalTestSGR(U"\33[1;2;3;4;7;21;24m"), []{Cell tmp; tmp.bold=tmp.dim=tmp.italic=tmp.underline=tmp.underline2=tmp.inverse=1; tmp.underline=tmp.underline2=0; return tmp; }());
    1225                 :          4 :     EXPECT_EQ(TerminalTestSGR(U"\33[1;2;3;4;7;21;25m"), []{Cell tmp; tmp.bold=tmp.dim=tmp.italic=tmp.underline=tmp.underline2=tmp.inverse=1; tmp.blink=0; return tmp; }());
    1226                 :          4 :     EXPECT_EQ(TerminalTestSGR(U"\33[1;2;3;4;7;21;27m"), []{Cell tmp; tmp.bold=tmp.dim=tmp.italic=tmp.underline=tmp.underline2=tmp.inverse=1; tmp.inverse=0; return tmp; }());
    1227                 :          4 :     EXPECT_EQ(TerminalTestSGR(U"\33[1;2;9;4;7;21;29m"), []{Cell tmp; tmp.bold=tmp.dim=tmp.overstrike=tmp.underline=tmp.underline2=tmp.inverse=1; tmp.overstrike=0; return tmp; }());
    1228                 :          4 :     EXPECT_EQ(TerminalTestSGR(U"\33[1;5;53;2;55m"),     []{Cell tmp; tmp.blink=tmp.bold=tmp.overlined=tmp.dim=1; tmp.overlined=0; return tmp; }());
    1229                 :          1 : }
    1230                 :          3 : TEST(terminal, sgr_colors)
    1231                 :            : {
    1232                 :          1 :     char Buf[32];
    1233         [ +  + ]:          9 :     for(unsigned n=0; n<8; ++n)
    1234                 :            :     {
    1235                 :          8 :         std::sprintf(Buf, "\33[%dm", 30+n);
    1236                 :         24 :         EXPECT_EQ(TerminalTestSGR(FromUTF8(Buf)), [n]{Cell tmp; tmp.fgcolor=xterm256table[n]; return tmp; }());
    1237                 :          8 :         std::sprintf(Buf, "\33[%dm", 40+n);
    1238                 :         24 :         EXPECT_EQ(TerminalTestSGR(FromUTF8(Buf)), [n]{Cell tmp; tmp.bgcolor=xterm256table[n]; return tmp; }());
    1239                 :          8 :         std::sprintf(Buf, "\33[%dm", 90+n);
    1240                 :         24 :         EXPECT_EQ(TerminalTestSGR(FromUTF8(Buf)), [n]{Cell tmp; tmp.fgcolor=xterm256table[n+8]; return tmp; }());
    1241                 :          8 :         std::sprintf(Buf, "\33[%dm", 100+n);
    1242                 :         32 :         EXPECT_EQ(TerminalTestSGR(FromUTF8(Buf)), [n]{Cell tmp; tmp.bgcolor=xterm256table[n+8]; return tmp; }());
    1243                 :            :     }
    1244         [ +  + ]:        257 :     for(unsigned n=0; n<256; ++n)
    1245                 :            :     {
    1246                 :        256 :         std::sprintf(Buf, "\33[38;5;%d;48;5;%dm", n, 255-n);
    1247                 :        768 :         EXPECT_EQ(TerminalTestSGR(FromUTF8(Buf)), [n]{Cell tmp; tmp.fgcolor=xterm256table[n];
    1248                 :        256 :                                                                 tmp.bgcolor=xterm256table[255-n]; return tmp; }());
    1249                 :            :     }
    1250                 :            :     // RGB24
    1251                 :          3 :     EXPECT_EQ(TerminalTestSGR(U"\33[38;2;0;0;0;48;2;255;255;255m"),
    1252                 :            :                                                   []{Cell tmp; tmp.fgcolor=0x000000;
    1253                 :          0 :                                                                tmp.bgcolor=0xFFFFFF; return tmp; }());
    1254                 :            :     // CMY
    1255                 :          3 :     EXPECT_EQ(TerminalTestSGR(U"\33[38;3;0;0;0;48;3;255;255;255m"),
    1256                 :            :                                                   []{Cell tmp; tmp.fgcolor=cmy2rgb(0x000000);
    1257                 :          0 :                                                                tmp.bgcolor=cmy2rgb(0xFFFFFF); return tmp; }());
    1258                 :            :     // CMYK
    1259                 :          4 :     EXPECT_EQ(TerminalTestSGR(U"\33[38;4;0;0;0;0;48;4;255;255;255;255m"),
    1260                 :            :                                                   []{Cell tmp; tmp.fgcolor=cmyk2rgb(0x00000000);
    1261                 :          0 :                                                                tmp.bgcolor=cmyk2rgb(0xFFFFFFFF); return tmp; }());
    1262                 :            :     // Make sure it gets rendered by this color
    1263                 :          3 :     EXPECT_TRUE(TerminalTest(U"\33[32mA", [&](Window& wnd)
    1264                 :            :     {
    1265                 :            :         return wnd.cells[0].fgcolor == xterm256table[2];
    1266                 :          1 :     }));
    1267                 :          1 : }
    1268                 :          3 : TEST(terminal, save_restore)
    1269                 :            : {
    1270                 :          3 :     EXPECT_TRUE(TerminalTest(U"ABC" U"\33[s" U"\33[32mDE\33[34mF" U"\33[u" U"G\33[35mH", [&](Window& wnd)
    1271                 :            :     {
    1272                 :            :         return wnd.cells[0].ch == 'A' && wnd.cells[1].ch == 'B' && wnd.cells[2].ch == 'C'
    1273                 :            :         &&     wnd.cells[3].ch == 'G' && wnd.cells[4].ch == 'H' && wnd.cells[5].ch == 'F'
    1274                 :            :         &&     wnd.cells[3].fgcolor == Cell{}.fgcolor
    1275                 :            :         &&     wnd.cells[4].fgcolor == xterm256table[5]
    1276                 :            :         &&     wnd.cells[5].fgcolor == xterm256table[4];
    1277                 :          1 :     }));
    1278                 :          1 : }
    1279                 :          3 : TEST(terminal, cursor_movements)
    1280                 :            : {
    1281                 :            :     // CR, LF....
    1282                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"ABC"),      (std::array{3u,0u, 0u,2u,3u}));
    1283                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"AB\rC"),    (std::array{1u,0u, 0u,1u,2u})); // CR
    1284                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"AB\nC"),    (std::array{3u,1u, 0u,10u,3u})); // LF
    1285                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"AB\013C"),  (std::array{3u,1u, 0u,10u,3u})); // LF, alias
    1286                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"AB\014C"),  (std::array{3u,1u, 0u,10u,3u})); // LF, alias
    1287                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"AB\033DC"), (std::array{3u,1u, 0u,10u,3u})); // LF, alias
    1288                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"AB\033EC"), (std::array{1u,1u, 0u,8u,3u})); // CR+LF
    1289                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"AB\033EC"), (std::array{1u,1u, 0u,8u,3u})); // CR+LF
    1290                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"AAAAAAAA"), (std::array{7u,0u, 0u,7u,8u})); // test word wrap
    1291                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"AAAAAAAAA"),(std::array{1u,1u, 0u,8u,9u})); // test word wrap+1
    1292                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"A\r\nB\r\nC\r\nAAAAAAAA"),(std::array{7u,3u, 0u,037u,11u})); // right-bottom column
    1293                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"A\r\nB\r\nC\r\nD\r\n"),   (std::array{0u,3u, 0u,020u,3u}));  // scroll down
    1294                 :            :     // Absolute
    1295                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"\33[2;2H"),         (std::array{1u,1u, 0u,0u,0u}));
    1296                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"\33[1;1H"),         (std::array{0u,0u, 0u,0u,0u}));
    1297                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"\33[2;2H\33[1;1H"), (std::array{0u,0u, 0u,0u,0u}));
    1298                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"\33[8;1H"),         (std::array{0u,3u, 0u,0u,0u})); // clamped
    1299                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"\33[1;8H"),         (std::array{7u,0u, 0u,0u,0u})); // clamped
    1300                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"\33[2;4H"),         (std::array{3u,1u, 0u,0u,0u}));
    1301                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"\33[2;4;5H"),       (std::array{3u,1u, 0u,0u,0u})); // extra param
    1302                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"\33[2;2H\33[H"),    (std::array{0u,0u, 0u,0u,0u})); // no params
    1303                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"\33[2;2H\33[0;0H"), (std::array{0u,0u, 0u,0u,0u})); // zero params
    1304                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"\33[4;8H"),         (std::array{7u,3u, 0u,0u,0u})); // bottom-right corner
    1305                 :            :     // Same set with 'f'
    1306                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"\33[2;2f"),         (std::array{1u,1u, 0u,0u,0u}));
    1307                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"\33[1;1f"),         (std::array{0u,0u, 0u,0u,0u}));
    1308                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"\33[2;2H\33[1;1f"), (std::array{0u,0u, 0u,0u,0u}));
    1309                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"\33[8;1f"),         (std::array{0u,3u, 0u,0u,0u})); // clamped
    1310                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"\33[1;8f"),         (std::array{7u,0u, 0u,0u,0u})); // clamped
    1311                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"\33[2;4f"),         (std::array{3u,1u, 0u,0u,0u}));
    1312                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"\33[2;4;5f"),       (std::array{3u,1u, 0u,0u,0u})); // extra param
    1313                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"\33[2;2H\33[f"),    (std::array{0u,0u, 0u,0u,0u})); // no params
    1314                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"\33[2;2H\33[0;0f"), (std::array{0u,0u, 0u,0u,0u})); // zero params
    1315                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"\33[4;8f"),         (std::array{7u,3u, 0u,0u,0u})); // bottom-right corner
    1316                 :            :     // Right
    1317                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"\33[C"),     (std::array{1u,0u, 0u,0u,0u}));
    1318                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"\33[1C"),    (std::array{1u,0u, 0u,0u,0u}));
    1319                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"\33[4C"),    (std::array{4u,0u, 0u,0u,0u}));
    1320                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"\33[8C"),    (std::array{7u,0u, 0u,0u,0u}));
    1321                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"\33[2;3H\33[2a"), (std::array{4u,1u, 0u,0u,0u})); // a = alias to C
    1322                 :            :     // Down
    1323                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"\33[B"),     (std::array{0u,1u, 0u,0u,0u}));
    1324                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"\33[1B"),    (std::array{0u,1u, 0u,0u,0u}));
    1325                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"\33[2B"),    (std::array{0u,2u, 0u,0u,0u}));
    1326                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"\33[8B"),    (std::array{0u,3u, 0u,0u,0u}));
    1327                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"\33[2;3H\33[2e"), (std::array{2u,3u, 0u,0u,0u})); // e = alias to B
    1328                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"\33[2;3H\33[2E"), (std::array{0u,3u, 0u,0u,0u})); // E = alias to e, resets X to 0
    1329                 :            :     // Up
    1330                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"\33[4;8H\33[A"),     (std::array{7u,2u, 0u,0u,0u}));
    1331                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"\33[4;8H\33[1A"),    (std::array{7u,2u, 0u,0u,0u}));
    1332                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"\33[4;8H\33[2A"),    (std::array{7u,1u, 0u,0u,0u}));
    1333                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"\33[4;8H\33[8A"),    (std::array{7u,0u, 0u,0u,0u}));
    1334                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"\33[4;8H\33[1F"),    (std::array{0u,2u, 0u,0u,0u})); // F = same as A, puts X to 0
    1335                 :            :     // Left
    1336                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"\33[4;8H\33[D"),     (std::array{6u,3u, 0u,0u,0u}));
    1337                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"\33[4;8H\33[1D"),    (std::array{6u,3u, 0u,0u,0u}));
    1338                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"\33[4;8H\33[2D"),    (std::array{5u,3u, 0u,0u,0u}));
    1339                 :          3 :     EXPECT_EQ(TerminalTestCursor(8,4, U"\33[4;8H\33[8D"),    (std::array{0u,3u, 0u,0u,0u}));
    1340                 :          1 : }
    1341                 :          3 : TEST(terminal, scs)
    1342                 :            : {
    1343                 :          1 :     char32_t pattern[] = U"\033)0\016z\017";
    1344                 :          1 :     char32_t symbols[] = U"012`45679ABCEfFHKLMQR<=>UYZ";
    1345         [ +  + ]:         29 :     for(char32_t c: symbols)
    1346                 :            :     {
    1347                 :         28 :         char32_t expect = 'z';
    1348                 :         28 :         pattern[2] = c;
    1349         [ +  + ]:         28 :         if(c == U'0') expect = U'\u2265';
    1350         [ +  + ]:         28 :         if(c)
    1351                 :            :         {
    1352                 :        108 :             EXPECT_EQ(TerminalTest(pattern, [&](auto& wnd) { return wnd.cells[0].ch; }), expect);
    1353                 :            :         }
    1354                 :            :     }
    1355                 :          1 : }
    1356                 :            : 
    1357                 :            : #include <fstream>
    1358                 :          3 : TEST(terminal, stress_test)
    1359                 :            : {
    1360                 :          1 :     Window wnd(4, 4);
    1361         [ +  - ]:          1 :     TerminalWindow term(wnd);
    1362         [ +  - ]:          1 :     term.Resize(80, 25);
    1363                 :            : 
    1364                 :            :     // Issue the patterns from the following files:
    1365                 :          5 :     for(std::string s: {"test/escapes.txt", "test/xterm-symbols.txt", "test/xterm-symbols2.txt",
    1366   [ +  -  +  + ]:          5 :                         "test/ocs_dcs.txt"})
    1367                 :            :     {
    1368         [ +  - ]:          4 :         std::ifstream f(s);
    1369   [ +  -  +  + ]:        380 :         for(std::string line; std::getline(f,line), f; )
    1370   [ +  -  +  - ]:        752 :             term.Write(FromUTF8(line));
    1371                 :          4 :     }
    1372   [ +  -  +  - ]:          1 :     std::u32string result(term.OutBuffer.begin(), term.OutBuffer.end());
    1373                 :          1 :     EXPECT_EQ(result,
    1374                 :            :         U"\33[?65;1;6;8;15;22c\33[?65;1;6;8;15;22c\33[0n\33[1;1R\033P!|00000000\x9C\33[>1;1;0c\33[?65;1;6;8;15;22c\33[?25;1$y\33[12;4$y\33[20;4$y"
    1375                 :            :         U"\x18\033$P1$r0;24r\033\\\033$P1$r38;2;204;204;204;48;2;0;0;0m\033\\\033$P1$r24t\033\\\033$P1$r1 q\033\\\033$P1$r0\"q\033\\\033$P1$r64;1\"p\033\\\033$P1$r80$|\033\\\033$P1$r25*|\033\\"
    1376                 :          1 :     );
    1377                 :          1 : }
    1378                 :            : 
    1379                 :            : #endif

Generated by: LCOV version 1.16