0
0
mirror of https://github.com/vim/vim.git synced 2025-07-26 11:04:33 -04:00

patch 8.1.2123: parsing CSI sequence is messy

Problem:    Parsing CSI sequence is messy.
Solution:   Generalize parsing a CSI sequence.
This commit is contained in:
Bram Moolenaar 2019-10-08 20:15:39 +02:00
parent fbbd102be0
commit c3e555b22f
2 changed files with 158 additions and 143 deletions

View File

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

View File

@ -753,6 +753,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
2123,
/**/ /**/
2122, 2122,
/**/ /**/