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
)
{
/* 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;
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;
extra = 0;
for (i = 2 + (tp[0] != CSI); i < len
&& !(tp[i] >= '{' && tp[i] <= '~')
&& !ASCII_ISALPHA(tp[i]); ++i)
if (tp[i] == ';' && ++semicols == 1)
// 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);
// 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;
/* 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')
// 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:

View File

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