]> granicus.if.org Git - vim/commitdiff
patch 8.1.2123: parsing CSI sequence is messy v8.1.2123
authorBram Moolenaar <Bram@vim.org>
Tue, 8 Oct 2019 18:15:39 +0000 (20:15 +0200)
committerBram Moolenaar <Bram@vim.org>
Tue, 8 Oct 2019 18:15:39 +0000 (20:15 +0200)
Problem:    Parsing CSI sequence is messy.
Solution:   Generalize parsing a CSI sequence.

src/term.c
src/version.c

index 2319453fb7cf2bd41c7facdacf2666bf83d807e9..f021a17ff741e10e6b9b0d3f48194e16223f39fd 100644 (file)
@@ -4435,72 +4435,105 @@ check_termcode(
 # endif
           )
        {
-           /* Check for some responses from the terminal starting with
-            * "<Esc>[" or CSI:
+           char_u *argp = tp[0] == ESC ? tp + 2 : tp + 1;
+
+           /*
+            * Check for responses from the terminal starting with {lead}:
+            * "<Esc>[" or CSI followed by [0-9>?]
             *
-            * - Xterm version string: <Esc>[>{x};{vers};{y}c
-            *   Libvterm returns {x} == 0, {vers} == 100, {y} == 0.
+            * - Xterm version string: {lead}>{x};{vers};{y}c
             *   Also eat other possible responses to t_RV, rxvt returns
-            *   "<Esc>[?1;2c". Also accept CSI instead of <Esc>[.
-            *   mrxvt has been reported to have "+" in the version. Assume
-            *   the escape sequence ends with a letter or one of "{|}~".
+            *   "{lead}?1;2c".
             *
-            * - Cursor position report: <Esc>[{row};{col}R
+            * - Cursor position report: {lead}{row};{col}R
             *   The final byte must be 'R'. It is used for checking the
             *   ambiguous-width character state.
             *
-            * - window position reply: <Esc>[3;{x};{y}t
+            * - window position reply: {lead}3;{x};{y}t
+            *
+            * - key with modifiers when modifyOtherKeys is enabled:
+            *      {lead}27;{modifier};{key}~
+            *      {lead}{key};{modifier}u
             */
-           char_u *argp = tp[0] == ESC ? tp + 2 : tp + 1;
-
-           if ((*T_CRV != NUL || *T_U7 != NUL || did_request_winpos)
-                       && ((tp[0] == ESC && len >= 3 && tp[1] == '[')
+           if (((tp[0] == ESC && len >= 3 && tp[1] == '[')
                            || (tp[0] == CSI && len >= 2))
-                       && (VIM_ISDIGIT(*argp) || *argp == '>' || *argp == '?'))
+                   && (VIM_ISDIGIT(*argp) || *argp == '>' || *argp == '?'))
            {
-               int col = 0;
-               int semicols = 0;
-               int row_char = NUL;
-
-               extra = 0;
-               for (i = 2 + (tp[0] != CSI); i < len
-                               && !(tp[i] >= '{' && tp[i] <= '~')
-                               && !ASCII_ISALPHA(tp[i]); ++i)
-                   if (tp[i] == ';' && ++semicols == 1)
+               int     first = -1;  // optional char right after {lead}
+               int     trail;       // char that ends CSI sequence
+               int     arg[3] = {-1, -1, -1};  // argument numbers
+               int     argc;                   // number of arguments
+               char_u  *ap = argp;
+               int     csi_len;
+
+               // Check for non-digit after CSI.
+               if (!VIM_ISDIGIT(*ap))
+                   first = *ap++;
+
+               // Find up to three argument numbers.
+               for (argc = 0; argc < 3; )
+               {
+                   if (ap >= tp + len)
                    {
-                       extra = i + 1;
-                       row_char = tp[i - 1];
+not_enough:
+                       LOG_TR(("Not enough characters for CSI sequence"));
+                       return -1;
                    }
-               if (i == len)
-               {
-                   LOG_TR(("Not enough characters for CRV"));
-                   return -1;
+                   if (*ap == ';')
+                       arg[argc++] = -1;  // omitted number
+                   else if (VIM_ISDIGIT(*ap))
+                   {
+                       arg[argc] = 0;
+                       for (;;)
+                       {
+                           if (ap >= tp + len)
+                               goto not_enough;
+                           if (!VIM_ISDIGIT(*ap))
+                               break;
+                           arg[argc] = arg[argc] * 10 + (*ap - '0');
+                           ++ap;
+                       }
+                       ++argc;
+                   }
+                   if (*ap == ';')
+                       ++ap;
+                   else
+                       break;
                }
-               if (extra > 0)
-                   col = atoi((char *)tp + extra);
-
-               /* Eat it when it has 2 arguments and ends in 'R'. Also when
-                * u7_status is not "sent", it may be from a previous Vim that
-                * just exited.  But not for <S-F3>, it sends something
-                * similar, check for row and column to make sense. */
-               if (semicols == 1 && tp[i] == 'R')
+               // mrxvt has been reported to have "+" in the version. Assume
+               // the escape sequence ends with a letter or one of "{|}~".
+               while (ap < tp + len
+                       && !(*ap >= '{' && *ap <= '~')
+                       && !ASCII_ISALPHA(*ap))
+                   ++ap;
+               if (ap >= tp + len)
+                   goto not_enough;
+               trail = *ap;
+               csi_len = (int)(ap - tp) + 1;
+
+               // Cursor position report: Eat it when there are 2 arguments
+               // and it ends in 'R'. Also when u7_status is not "sent", it
+               // may be from a previous Vim that just exited.  But not for
+               // <S-F3>, it sends something similar, check for row and column
+               // to make sense.
+               if (first == -1 && argc == 2 && trail == 'R')
                {
-                   if (row_char == '2' && col >= 2)
+                   if (arg[0] == 2 && arg[1] >= 2)
                    {
                        char *aw = NULL;
 
                        LOG_TR(("Received U7 status: %s", tp));
                        u7_status.tr_progress = STATUS_GOT;
                        did_cursorhold = TRUE;
-                       if (col == 2)
+                       if (arg[1] == 2)
                            aw = "single";
-                       else if (col == 3)
+                       else if (arg[1] == 3)
                            aw = "double";
                        if (aw != NULL && STRCMP(aw, p_ambw) != 0)
                        {
-                           /* Setting the option causes a screen redraw. Do
-                            * that right away if possible, keeping any
-                            * messages. */
+                           // Setting the option causes a screen redraw. Do
+                           // that right away if possible, keeping any
+                           // messages.
                            set_option_value((char_u *)"ambw", 0L,
                                             (char_u *)aw, 0);
 # ifdef DEBUG_TERMRESPONSE
@@ -4516,34 +4549,35 @@ check_termcode(
                    }
                    key_name[0] = (int)KS_EXTRA;
                    key_name[1] = (int)KE_IGNORE;
-                   slen = i + 1;
+                   slen = csi_len;
 # ifdef FEAT_EVAL
                    set_vim_var_string(VV_TERMU7RESP, tp, slen);
 # endif
                }
-               /* eat it when at least one digit and ending in 'c' */
-               else if (*T_CRV != NUL && i > 2 + (tp[0] != CSI)
-                                                              && tp[i] == 'c')
+
+               // Version string: Eat it when there is at least one digit and
+               // it ends in 'c'
+               else if (*T_CRV != NUL && ap > argp + 1 && trail == 'c')
                {
-                   int version = col;
+                   int version = arg[1];
 
                    LOG_TR(("Received CRV response: %s", tp));
                    crv_status.tr_progress = STATUS_GOT;
                    did_cursorhold = TRUE;
 
-                   /* If this code starts with CSI, you can bet that the
-                    * terminal uses 8-bit codes. */
+                   // If this code starts with CSI, you can bet that the
+                   // terminal uses 8-bit codes.
                    if (tp[0] == CSI)
                        switch_to_8bit();
 
-                   /* rxvt sends its version number: "20703" is 2.7.3.
-                    * Screen sends 40500.
-                    * Ignore it for when the user has set 'term' to xterm,
-                    * even though it's an rxvt. */
+                   // rxvt sends its version number: "20703" is 2.7.3.
+                   // Screen sends 40500.
+                   // Ignore it for when the user has set 'term' to xterm,
+                   // even though it's an rxvt.
                    if (version > 20000)
                        version = 0;
 
-                   if (tp[1 + (tp[0] != CSI)] == '>' && semicols == 2)
+                   if (first == '>' && argc == 3)
                    {
                        int need_flush = FALSE;
                        int is_iterm2 = FALSE;
@@ -4551,10 +4585,10 @@ check_termcode(
 
                        // mintty 2.9.5 sends 77;20905;0c.
                        // (77 is ASCII 'M' for mintty.)
-                       if (STRNCMP(tp + extra - 3, "77;", 3) == 0)
+                       if (arg[0] == 77)
                            is_mintty = TRUE;
 
-                       /* if xterm version >= 141 try to get termcap codes */
+                       // if xterm version >= 141 try to get termcap codes
                        if (version >= 141)
                        {
                            LOG_TR(("Enable checking for XT codes"));
@@ -4563,13 +4597,12 @@ check_termcode(
                            req_codes_from_term();
                        }
 
-                       /* libvterm sends 0;100;0 */
-                       if (version == 100
-                               && STRNCMP(tp + extra - 2, "0;100;0c", 8) == 0)
+                       // libvterm sends 0;100;0
+                       if (version == 100 && arg[0] == 0 && arg[2] == 0)
                        {
-                           /* If run from Vim $COLORS is set to the number of
-                            * colors the terminal supports.  Otherwise assume
-                            * 256, libvterm supports even more. */
+                           // If run from Vim $COLORS is set to the number of
+                           // colors the terminal supports.  Otherwise assume
+                           // 256, libvterm supports even more.
                            if (mch_getenv((char_u *)"COLORS") == NULL)
                                may_adjust_color_count(256);
                            /* Libvterm can handle SGR mouse reporting. */
@@ -4581,56 +4614,54 @@ check_termcode(
                        if (version == 95)
                        {
                            // Mac Terminal.app sends 1;95;0
-                           if (STRNCMP(tp + extra - 2, "1;95;0c", 7) == 0)
+                           if (arg[0] == 1 && arg[2] == 0)
                            {
                                is_not_xterm = TRUE;
                                is_mac_terminal = TRUE;
                            }
                            // iTerm2 sends 0;95;0
-                           if (STRNCMP(tp + extra - 2, "0;95;0c", 7) == 0)
+                           else if (arg[0] == 0 && arg[2] == 0)
                                is_iterm2 = TRUE;
                            // old iTerm2 sends 0;95;
-                           else if (STRNCMP(tp + extra - 2, "0;95;c", 6) == 0)
+                           else if (arg[0] == 0 && arg[2] == -1)
                                is_not_xterm = TRUE;
                        }
 
-                       /* Only set 'ttymouse' automatically if it was not set
-                        * by the user already. */
+                       // Only set 'ttymouse' automatically if it was not set
+                       // by the user already.
                        if (!option_was_set((char_u *)"ttym"))
                        {
-                           /* Xterm version 277 supports SGR.  Also support
-                            * Terminal.app, iTerm2 and mintty. */
+                           // Xterm version 277 supports SGR.  Also support
+                           // Terminal.app, iTerm2 and mintty.
                            if (version >= 277 || is_iterm2 || is_mac_terminal
                                    || is_mintty)
                                set_option_value((char_u *)"ttym", 0L,
                                                          (char_u *)"sgr", 0);
-                           /* if xterm version >= 95 use mouse dragging */
+                           // if xterm version >= 95 use mouse dragging
                            else if (version >= 95)
                                set_option_value((char_u *)"ttym", 0L,
                                                       (char_u *)"xterm2", 0);
                        }
 
-                       /* Detect terminals that set $TERM to something like
-                        * "xterm-256colors"  but are not fully xterm
-                        * compatible. */
+                       // Detect terminals that set $TERM to something like
+                       // "xterm-256colors"  but are not fully xterm
+                       // compatible.
 
-                       /* Gnome terminal sends 1;3801;0, 1;4402;0 or 1;2501;0.
-                        * xfce4-terminal sends 1;2802;0.
-                        * screen sends 83;40500;0
-                        * Assuming any version number over 2500 is not an
-                        * xterm (without the limit for rxvt and screen). */
-                       if (col >= 2500)
+                       // Gnome terminal sends 1;3801;0, 1;4402;0 or 1;2501;0.
+                       // xfce4-terminal sends 1;2802;0.
+                       // screen sends 83;40500;0
+                       // Assuming any version number over 2500 is not an
+                       // xterm (without the limit for rxvt and screen).
+                       if (arg[1] >= 2500)
                            is_not_xterm = TRUE;
 
-                       /* PuTTY sends 0;136;0
-                        * vandyke SecureCRT sends 1;136;0 */
-                       if (version == 136
-                               && STRNCMP(tp + extra - 1, ";136;0c", 7) == 0)
+                       // PuTTY sends 0;136;0
+                       // vandyke SecureCRT sends 1;136;0
+                       else if (version == 136 && arg[2] == 0)
                            is_not_xterm = TRUE;
 
-                       /* Konsole sends 0;115;0 */
-                       if (version == 115
-                               && STRNCMP(tp + extra - 2, "0;115;0c", 8) == 0)
+                       // Konsole sends 0;115;0
+                       else if (version == 115 && arg[0] == 0 && arg[2] == 0)
                            is_not_xterm = TRUE;
 
                        // Xterm first responded to this request at patch level
@@ -4638,11 +4669,11 @@ check_termcode(
                        if (version < 95)
                            is_not_xterm = TRUE;
 
-                       /* Only request the cursor style if t_SH and t_RS are
-                        * set. Only supported properly by xterm since version
-                        * 279 (otherwise it returns 0x18).
-                        * Not for Terminal.app, it can't handle t_RS, it
-                        * echoes the characters to the screen. */
+                       // Only request the cursor style if t_SH and t_RS are
+                       // set. Only supported properly by xterm since version
+                       // 279 (otherwise it returns 0x18).
+                       // Not for Terminal.app, it can't handle t_RS, it
+                       // echoes the characters to the screen.
                        if (rcs_status.tr_progress == STATUS_GET
                                && version >= 279
                                && !is_not_xterm
@@ -4655,9 +4686,9 @@ check_termcode(
                            need_flush = TRUE;
                        }
 
-                       /* Only request the cursor blink mode if t_RC set. Not
-                        * for Gnome terminal, it can't handle t_RC, it
-                        * echoes the characters to the screen. */
+                       // Only request the cursor blink mode if t_RC set. Not
+                       // for Gnome terminal, it can't handle t_RC, it
+                       // echoes the characters to the screen.
                        if (rbm_status.tr_progress == STATUS_GET
                                && !is_not_xterm
                                && *T_CRC != NUL)
@@ -4671,7 +4702,7 @@ check_termcode(
                        if (need_flush)
                            out_flush();
                    }
-                   slen = i + 1;
+                   slen = csi_len;
 # ifdef FEAT_EVAL
                    set_vim_var_string(VV_TERMRESPONSE, tp, slen);
 # endif
@@ -4681,69 +4712,51 @@ check_termcode(
                    key_name[1] = (int)KE_IGNORE;
                }
 
-               /* Check blinking cursor from xterm:
-                * {lead}?12;1$y       set
-                * {lead}?12;2$y       not set
-                *
-                * {lead} can be <Esc>[ or CSI
-                */
+               // Check blinking cursor from xterm:
+               // {lead}?12;1$y       set
+               // {lead}?12;2$y       not set
+               //
+               // {lead} can be <Esc>[ or CSI
                else if (rbm_status.tr_progress == STATUS_SENT
-                       && tp[(j = 1 + (tp[0] == ESC))] == '?'
-                       && i == j + 6
-                       && tp[j + 1] == '1'
-                       && tp[j + 2] == '2'
-                       && tp[j + 3] == ';'
-                       && tp[i - 1] == '$'
-                       && tp[i] == 'y')
+                       && first == '?'
+                       && ap == argp + 6
+                       && arg[0] == 12
+                       && ap[-1] == '$'
+                       && trail == 'y')
                {
-                   initial_cursor_blink = (tp[j + 4] == '1');
+                   initial_cursor_blink = (arg[1] == '1');
                    rbm_status.tr_progress = STATUS_GOT;
                    LOG_TR(("Received cursor blinking mode response: %s", tp));
                    key_name[0] = (int)KS_EXTRA;
                    key_name[1] = (int)KE_IGNORE;
-                   slen = i + 1;
+                   slen = csi_len;
 # ifdef FEAT_EVAL
                    set_vim_var_string(VV_TERMBLINKRESP, tp, slen);
 # endif
                }
 
-               /*
-                * Check for a window position response from the terminal:
-                *       {lead}3;{x};{y}t
-                */
-               else if (did_request_winpos
-                           && ((len >= 4 && tp[0] == ESC && tp[1] == '[')
-                               || (len >= 3 && tp[0] == CSI))
-                           && tp[(j = 1 + (tp[0] == ESC))] == '3'
-                           && tp[j + 1] == ';')
+               // Check for a window position response from the terminal:
+               //       {lead}3;{x};{y}t
+               else if (did_request_winpos && argc == 3 && arg[0] == 3
+                                                              && trail == 't')
                {
-                   j += 2;
-                   for (i = j; i < len && vim_isdigit(tp[i]); ++i)
-                       ;
-                   if (i < len && tp[i] == ';')
-                   {
-                       winpos_x = atoi((char *)tp + j);
-                       j = i + 1;
-                       for (i = j; i < len && vim_isdigit(tp[i]); ++i)
-                           ;
-                       if (i < len && tp[i] == 't')
-                       {
-                           winpos_y = atoi((char *)tp + j);
-                           /* got finished code: consume it */
-                           key_name[0] = (int)KS_EXTRA;
-                           key_name[1] = (int)KE_IGNORE;
-                           slen = i + 1;
+                   winpos_x = arg[1];
+                   winpos_y = arg[2];
+                   // got finished code: consume it
+                   key_name[0] = (int)KS_EXTRA;
+                   key_name[1] = (int)KE_IGNORE;
+                   slen = csi_len;
 
-                           if (--did_request_winpos <= 0)
-                               winpos_status.tr_progress = STATUS_GOT;
-                       }
-                   }
-                   if (i == len)
-                   {
-                       LOG_TR(("not enough characters for winpos"));
-                       return -1;
-                   }
+                   if (--did_request_winpos <= 0)
+                       winpos_status.tr_progress = STATUS_GOT;
                }
+
+               // TODO: key with modifier:
+               //      {lead}27;{modifier};{key}~
+               //      {lead}{key};{modifier}u
+
+               // else: Unknown CSI sequence.  We could drop it, but then the
+               // user can't create a map for it.
            }
 
            /* Check for fore/background color response from the terminal:
index e207a371070e9ba4483ce0b361a9a68ba1370c24..f07d0382cead3bdf38ea4285944df0cc9660960c 100644 (file)
@@ -753,6 +753,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    2123,
 /**/
     2122,
 /**/