changeset 18257:f5a6c8261f64 v8.1.2123

patch 8.1.2123: parsing CSI sequence is messy Commit: https://github.com/vim/vim/commit/c3e555b22f24f93aabd31943c35a9228abb6ecb6 Author: Bram Moolenaar <Bram@vim.org> Date: Tue Oct 8 20:15:39 2019 +0200 patch 8.1.2123: parsing CSI sequence is messy Problem: Parsing CSI sequence is messy. Solution: Generalize parsing a CSI sequence.
author Bram Moolenaar <Bram@vim.org>
date Tue, 08 Oct 2019 20:30:04 +0200
parents dcbc79d6a137
children 7392f162e56c
files src/term.c src/version.c
diffstat 2 files changed, 162 insertions(+), 147 deletions(-) [+]
line wrap: on
line diff
--- a/src/term.c
+++ b/src/term.c
@@ -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)
+		    {
+not_enough:
+			LOG_TR(("Not enough characters for CSI sequence"));
+			return -1;
+		    }
+		    if (*ap == ';')
+			arg[argc++] = -1;  // omitted number
+		    else if (VIM_ISDIGIT(*ap))
 		    {
-			extra = i + 1;
-			row_char = tp[i - 1];
+			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 (i == len)
-		{
-		    LOG_TR(("Not enough characters for CRV"));
-		    return -1;
+		    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. */
-
-			/* 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)
+			// 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 (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;
-
-			    if (--did_request_winpos <= 0)
-				winpos_status.tr_progress = STATUS_GOT;
-			}
-		    }
-		    if (i == len)
-		    {
-			LOG_TR(("not enough characters for winpos"));
-			return -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;
 		}
+
+		// 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:
--- a/src/version.c
+++ b/src/version.c
@@ -754,6 +754,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    2123,
+/**/
     2122,
 /**/
     2121,