changeset 20774:10535993e913 v8.2.0939

patch 8.2.0939: checking for term escape sequences is long and confusing Commit: https://github.com/vim/vim/commit/218cb0fb62d29fba552281c2e8ffeb4046d540c4 Author: Bram Moolenaar <Bram@vim.org> Date: Tue Jun 9 21:26:36 2020 +0200 patch 8.2.0939: checking for term escape sequences is long and confusing Problem: checking for term escape sequences is long and confusing Solution: Refactor code into separate functions.
author Bram Moolenaar <Bram@vim.org>
date Tue, 09 Jun 2020 21:30:03 +0200
parents 764cb6c1e56c
children 7ea49c0a73c2
files src/term.c src/version.c
diffstat 2 files changed, 661 insertions(+), 551 deletions(-) [+]
line wrap: on
line diff
--- a/src/term.c
+++ b/src/term.c
@@ -4384,6 +4384,652 @@ modifiers2keycode(int modifiers, int *ke
     return new_slen;
 }
 
+    static void
+handle_u7_response(int *arg, char_u *tp, int csi_len)
+{
+    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 (arg[1] == 2)
+	    aw = "single";
+	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.
+	    set_option_value((char_u *)"ambw", 0L,
+			     (char_u *)aw, 0);
+# ifdef DEBUG_TERMRESPONSE
+	    {
+		int r = redraw_asap(CLEAR);
+
+		log_tr("set 'ambiwidth', redraw_asap(): %d", r);
+	    }
+# else
+	    redraw_asap(CLEAR);
+# endif
+# ifdef FEAT_EVAL
+	    set_vim_var_string(VV_TERMU7RESP, tp, csi_len);
+# endif
+	}
+    }
+    else if (arg[0] == 3)
+    {
+	LOG_TR(("Received compatibility test result: %s", tp));
+	// Third row: xterm compatibility test.
+	// If the cursor is not on the first column then the
+	// terminal is not xterm compatible.
+	if (arg[1] != 1)
+	    xcc_test_failed = TRUE;
+	xcc_status.tr_progress = STATUS_GOT;
+    }
+}
+
+/*
+ * Handle a response to T_CRV.
+ */
+    static void
+handle_version_response(int first, int *arg, int argc, char_u *tp)
+{
+    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 (tp[0] == CSI)
+	switch_to_8bit();
+
+    // Screen sends 40500.
+    // rxvt sends its version number: "20703" is 2.7.3.
+    // Ignore it for when the user has set 'term' to xterm,
+    // even though it's an rxvt.
+    if (version > 20000)
+	version = 0;
+
+    if (first == '>' && argc == 3)
+    {
+	int need_flush = FALSE;
+	int is_iterm2 = FALSE;
+	int is_mintty = FALSE;
+	int is_screen = FALSE;
+
+	// mintty 2.9.5 sends 77;20905;0c.
+	// (77 is ASCII 'M' for mintty.)
+	if (arg[0] == 77)
+	    is_mintty = TRUE;
+
+	// if xterm version >= 141 try to get termcap codes
+	if (version >= 141)
+	{
+	    LOG_TR(("Enable checking for XT codes"));
+	    check_for_codes = TRUE;
+	    need_gather = TRUE;
+	    req_codes_from_term();
+	}
+
+	// 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 (mch_getenv((char_u *)"COLORS") == NULL)
+		may_adjust_color_count(256);
+	    // Libvterm can handle SGR mouse reporting.
+	    if (!option_was_set((char_u *)"ttym"))
+		set_option_value((char_u *)"ttym", 0L,
+					   (char_u *)"sgr", 0);
+	}
+
+	if (version == 95)
+	{
+	    // Mac Terminal.app sends 1;95;0
+	    if (arg[0] == 1 && arg[2] == 0)
+	    {
+		is_not_xterm = TRUE;
+		is_mac_terminal = TRUE;
+	    }
+	    // iTerm2 sends 0;95;0
+	    else if (arg[0] == 0 && arg[2] == 0)
+		is_iterm2 = TRUE;
+	    // old iTerm2 sends 0;95;
+	    else if (arg[0] == 0 && arg[2] == -1)
+		is_not_xterm = TRUE;
+	}
+
+	// screen sends 83;40500;0 83 is 'S' in ASCII.
+	if (arg[0] == 83)
+	    is_screen = TRUE;
+
+	// 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, mintty, and screen 4.7+.
+	    if ((!is_screen && version >= 277)
+		    || is_iterm2
+		    || is_mac_terminal
+		    || is_mintty
+		    || (is_screen && arg[1] >= 40700))
+		set_option_value((char_u *)"ttym", 0L,
+					  (char_u *)"sgr", 0);
+	    // For xterm version >= 95 mouse dragging works.
+	    else if (version >= 95)
+		set_option_value((char_u *)"ttym", 0L,
+				       (char_u *)"xterm2", 0);
+	}
+
+	// Detect terminals that set $TERM to something like
+	// "xterm-256color" 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
+	else if (version == 136 && arg[2] == 0)
+	{
+	    is_not_xterm = TRUE;
+
+	    // PuTTY supports sgr-like mouse reporting, but
+	    // only set 'ttymouse' if it was not set by the
+	    // user already.
+	    if (arg[0] == 0
+			  && !option_was_set((char_u *)"ttym"))
+		set_option_value((char_u *)"ttym", 0L,
+					(char_u *)"sgr", 0);
+	}
+
+	// Konsole sends 0;115;0
+	else if (version == 115 && arg[0] == 0 && arg[2] == 0)
+	    is_not_xterm = TRUE;
+
+	// GNU screen sends 83;30600;0, 83;40500;0, etc.
+	// 30600/40500 is a version number of GNU screen. DA2
+	// support is added on 3.6.  DCS string has a special
+	// meaning to GNU screen, but xterm compatibility
+	// checking does not detect GNU screen.
+	if (version >= 30600 && arg[0] == 83)
+	    xcc_test_failed = TRUE;
+
+	// Xterm first responded to this request at patch level
+	// 95, so assume anything below 95 is not xterm.
+	if (version < 95)
+	    is_not_xterm = TRUE;
+
+	// With the real Xterm setting the underline RGB color
+	// clears the background color, disable "t_8u".
+	if (!is_not_xterm && *T_8U != NUL)
+	    T_8U = empty_option;
+
+	// 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).
+	// Only when the xcc_status was set, the test finished,
+	// and xcc_test_failed is FALSE;
+	// Not for Terminal.app, it can't handle t_RS, it
+	// echoes the characters to the screen.
+	if (rcs_status.tr_progress == STATUS_GET
+		&& xcc_status.tr_progress == STATUS_GOT
+		&& !xcc_test_failed
+		&& version >= 279
+		&& *T_CSH != NUL
+		&& *T_CRS != NUL)
+	{
+	    LOG_TR(("Sending cursor style request"));
+	    out_str(T_CRS);
+	    termrequest_sent(&rcs_status);
+	    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 when the xcc_status was set, the test finished,
+	// and xcc_test_failed is FALSE;
+	if (rbm_status.tr_progress == STATUS_GET
+		&& xcc_status.tr_progress == STATUS_GOT
+		&& !xcc_test_failed
+		&& *T_CRC != NUL)
+	{
+	    LOG_TR(("Sending cursor blink mode request"));
+	    out_str(T_CRC);
+	    termrequest_sent(&rbm_status);
+	    need_flush = TRUE;
+	}
+
+	if (need_flush)
+	    out_flush();
+    }
+}
+
+/*
+ * Handle a sequence with key and modifier, one of:
+ *	{lead}27;{modifier};{key}~
+ *	{lead}{key};{modifier}u
+ * Returns the difference in length.
+ */
+    static int
+handle_key_with_modifier(
+	int	*arg,
+	int	trail,
+	int	csi_len,
+	int	offset,
+	char_u	*buf,
+	int	bufsize,
+	int	*buflen)
+{
+    int	    key;
+    int	    modifiers;
+    int	    new_slen;
+    char_u  string[MAX_KEY_CODE_LEN + 1];
+
+    seenModifyOtherKeys = TRUE;
+    if (trail == 'u')
+	key = arg[0];
+    else
+	key = arg[2];
+
+    modifiers = decode_modifiers(arg[1]);
+
+    // Some keys already have Shift included, pass them as
+    // normal keys.  Not when Ctrl is also used, because <C-H>
+    // and <C-S-H> are different.
+    if (modifiers == MOD_MASK_SHIFT
+	    && ((key >= '@' && key <= 'Z')
+		|| key == '^' || key == '_'
+		|| (key >= '{' && key <= '~')))
+	modifiers = 0;
+
+    // When used with Ctrl we always make a letter upper case,
+    // so that mapping <C-H> and <C-h> are the same.  Typing
+    // <C-S-H> also uses "H" but modifier is different.
+    if ((modifiers & MOD_MASK_CTRL) && ASCII_ISALPHA(key))
+	key = TOUPPER_ASC(key);
+
+    // insert modifiers with KS_MODIFIER
+    new_slen = modifiers2keycode(modifiers, &key, string);
+
+    if (has_mbyte)
+	new_slen += (*mb_char2bytes)(key, string + new_slen);
+    else
+	string[new_slen++] = key;
+
+    if (put_string_in_typebuf(offset, csi_len, string, new_slen,
+						 buf, bufsize, buflen) == FAIL)
+	return -1;
+    return new_slen - csi_len + offset;
+}
+
+/*
+ * Handle a CSI escape sequence.
+ * - Xterm version string: {lead}>{x};{vers};{y}c
+ *   Also eat other possible responses to t_RV, rxvt returns
+ *   "{lead}?1;2c".
+ *
+ * - 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: {lead}3;{x};{y}t
+ *
+ * - key with modifiers when modifyOtherKeys is enabled:
+ *	    {lead}27;{modifier};{key}~
+ *	    {lead}{key};{modifier}u
+ * Return 0 for no match, -1 for partial match, > 0 for full match.
+ */
+    static int
+handle_csi(
+	char_u	*tp,
+	int	len,
+	char_u	*argp,
+	int	offset,
+	char_u  *buf,
+	int	bufsize,
+	int	*buflen,
+	char_u	*key_name,
+	int	*slen)
+{
+    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)
+	    return -1;
+	if (*ap == ';')
+	    arg[argc++] = -1;  // omitted number
+	else if (VIM_ISDIGIT(*ap))
+	{
+	    arg[argc] = 0;
+	    for (;;)
+	    {
+		if (ap >= tp + len)
+		    return -1;
+		if (!VIM_ISDIGIT(*ap))
+		    break;
+		arg[argc] = arg[argc] * 10 + (*ap - '0');
+		++ap;
+	    }
+	    ++argc;
+	}
+	if (*ap == ';')
+	    ++ap;
+	else
+	    break;
+    }
+
+    // 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)
+	return -1;
+    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')
+    {
+	handle_u7_response(arg, tp, csi_len);
+
+	key_name[0] = (int)KS_EXTRA;
+	key_name[1] = (int)KE_IGNORE;
+	*slen = csi_len;
+    }
+
+    // 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')
+    {
+	handle_version_response(first, arg, argc, tp);
+
+	*slen = csi_len;
+# ifdef FEAT_EVAL
+	set_vim_var_string(VV_TERMRESPONSE, tp, *slen);
+# endif
+	apply_autocmds(EVENT_TERMRESPONSE,
+					NULL, NULL, FALSE, curbuf);
+	key_name[0] = (int)KS_EXTRA;
+	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
+    else if (rbm_status.tr_progress == STATUS_SENT
+	    && first == '?'
+	    && ap == argp + 6
+	    && arg[0] == 12
+	    && ap[-1] == '$'
+	    && trail == 'y')
+    {
+	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 = 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 && argc == 3 && arg[0] == 3
+						   && trail == 't')
+    {
+	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;
+    }
+
+    // Key with modifier:
+    //	{lead}27;{modifier};{key}~
+    //	{lead}{key};{modifier}u
+    else if ((arg[0] == 27 && argc == 3 && trail == '~')
+	    || (argc == 2 && trail == 'u'))
+    {
+	return len + handle_key_with_modifier(arg, trail,
+			    csi_len, offset, buf, bufsize, buflen);
+    }
+
+    // else: Unknown CSI sequence.  We could drop it, but then the
+    // user can't create a map for it.
+    return 0;
+}
+
+/*
+ * Handle an OSC sequence, fore/background color response from the terminal:
+ *
+ *       {lead}{code};rgb:{rrrr}/{gggg}/{bbbb}{tail}
+ * or    {lead}{code};rgb:{rr}/{gg}/{bb}{tail}
+ *
+ * {code} is 10 for foreground, 11 for background
+ * {lead} can be <Esc>] or OSC
+ * {tail} can be '\007', <Esc>\ or STERM.
+ *
+ * Consume any code that starts with "{lead}11;", it's also
+ * possible that "rgba" is following.
+ */
+    static int
+handle_osc(char_u *tp, char_u *argp, int len, char_u *key_name, int *slen)
+{
+    int		i, j;
+
+    j = 1 + (tp[0] == ESC);
+    if (len >= j + 3 && (argp[0] != '1'
+			     || (argp[1] != '1' && argp[1] != '0')
+			     || argp[2] != ';'))
+	i = 0; // no match
+    else
+	for (i = j; i < len; ++i)
+	    if (tp[i] == '\007' || (tp[0] == OSC ? tp[i] == STERM
+			: (tp[i] == ESC && i + 1 < len && tp[i + 1] == '\\')))
+	    {
+		int is_bg = argp[1] == '1';
+		int is_4digit = i - j >= 21 && tp[j + 11] == '/'
+						  && tp[j + 16] == '/';
+
+		if (i - j >= 15 && STRNCMP(tp + j + 3, "rgb:", 4) == 0
+			    && (is_4digit
+				   || (tp[j + 9] == '/' && tp[i + 12 == '/'])))
+		{
+		    char_u *tp_r = tp + j + 7;
+		    char_u *tp_g = tp + j + (is_4digit ? 12 : 10);
+		    char_u *tp_b = tp + j + (is_4digit ? 17 : 13);
+# ifdef FEAT_TERMINAL
+		    int rval, gval, bval;
+
+		    rval = hexhex2nr(tp_r);
+		    gval = hexhex2nr(tp_b);
+		    bval = hexhex2nr(tp_g);
+# endif
+		    if (is_bg)
+		    {
+			char *new_bg_val = (3 * '6' < *tp_r + *tp_g +
+					     *tp_b) ? "light" : "dark";
+
+			LOG_TR(("Received RBG response: %s", tp));
+			rbg_status.tr_progress = STATUS_GOT;
+# ifdef FEAT_TERMINAL
+			bg_r = rval;
+			bg_g = gval;
+			bg_b = bval;
+# endif
+			if (!option_was_set((char_u *)"bg")
+				      && STRCMP(p_bg, new_bg_val) != 0)
+			{
+			    // value differs, apply it
+			    set_option_value((char_u *)"bg", 0L,
+					      (char_u *)new_bg_val, 0);
+			    reset_option_was_set((char_u *)"bg");
+			    redraw_asap(CLEAR);
+			}
+		    }
+# ifdef FEAT_TERMINAL
+		    else
+		    {
+			LOG_TR(("Received RFG response: %s", tp));
+			rfg_status.tr_progress = STATUS_GOT;
+			fg_r = rval;
+			fg_g = gval;
+			fg_b = bval;
+		    }
+# endif
+		}
+
+		// got finished code: consume it
+		key_name[0] = (int)KS_EXTRA;
+		key_name[1] = (int)KE_IGNORE;
+		*slen = i + 1 + (tp[i] == ESC);
+# ifdef FEAT_EVAL
+		set_vim_var_string(is_bg ? VV_TERMRBGRESP
+						  : VV_TERMRFGRESP, tp, *slen);
+# endif
+		break;
+	    }
+    if (i == len)
+    {
+	LOG_TR(("not enough characters for RB"));
+	return FAIL;
+    }
+    return OK;
+}
+
+/*
+ * Check for key code response from xterm:
+ * {lead}{flag}+r<hex bytes><{tail}
+ *
+ * {lead} can be <Esc>P or DCS
+ * {flag} can be '0' or '1'
+ * {tail} can be Esc>\ or STERM
+ *
+ * Check for cursor shape response from xterm:
+ * {lead}1$r<digit> q{tail}
+ *
+ * {lead} can be <Esc>P or DCS
+ * {tail} can be <Esc>\ or STERM
+ *
+ * Consume any code that starts with "{lead}.+r" or "{lead}.$r".
+ */
+    static int
+handle_dcs(char_u *tp, char_u *argp, int len, char_u *key_name, int *slen)
+{
+    int i, j;
+
+    j = 1 + (tp[0] == ESC);
+    if (len < j + 3)
+	i = len; // need more chars
+    else if ((argp[1] != '+' && argp[1] != '$') || argp[2] != 'r')
+	i = 0; // no match
+    else if (argp[1] == '+')
+	// key code response
+	for (i = j; i < len; ++i)
+	{
+	    if ((tp[i] == ESC && i + 1 < len && tp[i + 1] == '\\')
+		    || tp[i] == STERM)
+	    {
+		if (i - j >= 3)
+		    got_code_from_term(tp + j, i);
+		key_name[0] = (int)KS_EXTRA;
+		key_name[1] = (int)KE_IGNORE;
+		*slen = i + 1 + (tp[i] == ESC);
+		break;
+	    }
+	}
+    else
+    {
+	// Probably the cursor shape response.  Make sure that "i"
+	// is equal to "len" when there are not sufficient
+	// characters.
+	for (i = j + 3; i < len; ++i)
+	{
+	    if (i - j == 3 && !isdigit(tp[i]))
+		break;
+	    if (i - j == 4 && tp[i] != ' ')
+		break;
+	    if (i - j == 5 && tp[i] != 'q')
+		break;
+	    if (i - j == 6 && tp[i] != ESC && tp[i] != STERM)
+		break;
+	    if ((i - j == 6 && tp[i] == STERM)
+	     || (i - j == 7 && tp[i] == '\\'))
+	    {
+		int number = argp[3] - '0';
+
+		// 0, 1 = block blink, 2 = block
+		// 3 = underline blink, 4 = underline
+		// 5 = vertical bar blink, 6 = vertical bar
+		number = number == 0 ? 1 : number;
+		initial_cursor_shape = (number + 1) / 2;
+		// The blink flag is actually inverted, compared to
+		// the value set with T_SH.
+		initial_cursor_shape_blink =
+				       (number & 1) ? FALSE : TRUE;
+		rcs_status.tr_progress = STATUS_GOT;
+		LOG_TR(("Received cursor shape response: %s", tp));
+
+		key_name[0] = (int)KS_EXTRA;
+		key_name[1] = (int)KE_IGNORE;
+		*slen = i + 1;
+# ifdef FEAT_EVAL
+		set_vim_var_string(VV_TERMSTYLERESP, tp, *slen);
+# endif
+		break;
+	    }
+	}
+    }
+
+    if (i == len)
+    {
+	// These codes arrive many together, each code can be
+	// truncated at any point.
+	LOG_TR(("not enough characters for XT"));
+	return FAIL;
+    }
+    return OK;
+}
+
 /*
  * Check if typebuf.tb_buf[] contains a terminal key code.
  * Check from typebuf.tb_buf[typebuf.tb_off] to typebuf.tb_buf[typebuf.tb_off
@@ -4637,574 +5283,36 @@ check_termcode(
 			    || (tp[0] == CSI && len >= 2))
 		    && (VIM_ISDIGIT(*argp) || *argp == '>' || *argp == '?'))
 	    {
-		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))
-		    {
-			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;
-		}
-		// 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 (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 (arg[1] == 2)
-			    aw = "single";
-			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.
-			    set_option_value((char_u *)"ambw", 0L,
-					     (char_u *)aw, 0);
-# ifdef DEBUG_TERMRESPONSE
-			    {
-				int r = redraw_asap(CLEAR);
-
-				log_tr("set 'ambiwidth', redraw_asap(): %d", r);
-			    }
-# else
-			    redraw_asap(CLEAR);
-# endif
-			}
-		    }
-		    else if (arg[0] == 3)
-		    {
-			// Third row: xterm compatibility test.
-			// If the cursor is not on the first column then the
-			// terminal is not xterm compatible.
-			if (arg[1] != 1)
-			    xcc_test_failed = TRUE;
-			xcc_status.tr_progress = STATUS_GOT;
-		    }
-
-		    key_name[0] = (int)KS_EXTRA;
-		    key_name[1] = (int)KE_IGNORE;
-		    slen = csi_len;
-# ifdef FEAT_EVAL
-		    set_vim_var_string(VV_TERMU7RESP, tp, slen);
-# endif
-		}
-
-		// 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 resp = handle_csi(tp, len, argp, offset, buf,
+					     bufsize, buflen, key_name, &slen);
+		if (resp != 0)
 		{
-		    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 (tp[0] == CSI)
-			switch_to_8bit();
-
-		    // Screen sends 40500.
-		    // rxvt sends its version number: "20703" is 2.7.3.
-		    // Ignore it for when the user has set 'term' to xterm,
-		    // even though it's an rxvt.
-		    if (version > 20000)
-			version = 0;
-
-		    if (first == '>' && argc == 3)
-		    {
-			int need_flush = FALSE;
-			int is_iterm2 = FALSE;
-			int is_mintty = FALSE;
-			int is_screen = FALSE;
-
-			// mintty 2.9.5 sends 77;20905;0c.
-			// (77 is ASCII 'M' for mintty.)
-			if (arg[0] == 77)
-			    is_mintty = TRUE;
-
-			// if xterm version >= 141 try to get termcap codes
-			if (version >= 141)
-			{
-			    LOG_TR(("Enable checking for XT codes"));
-			    check_for_codes = TRUE;
-			    need_gather = TRUE;
-			    req_codes_from_term();
-			}
-
-			// 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 (mch_getenv((char_u *)"COLORS") == NULL)
-				may_adjust_color_count(256);
-			    // Libvterm can handle SGR mouse reporting.
-			    if (!option_was_set((char_u *)"ttym"))
-				set_option_value((char_u *)"ttym", 0L,
-							   (char_u *)"sgr", 0);
-			}
-
-			if (version == 95)
-			{
-			    // Mac Terminal.app sends 1;95;0
-			    if (arg[0] == 1 && arg[2] == 0)
-			    {
-				is_not_xterm = TRUE;
-				is_mac_terminal = TRUE;
-			    }
-			    // iTerm2 sends 0;95;0
-			    else if (arg[0] == 0 && arg[2] == 0)
-				is_iterm2 = TRUE;
-			    // old iTerm2 sends 0;95;
-			    else if (arg[0] == 0 && arg[2] == -1)
-				is_not_xterm = TRUE;
-			}
-
-			// screen sends 83;40500;0 83 is 'S' in ASCII.
-			if (arg[0] == 83)
-			    is_screen = TRUE;
-
-			// 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, mintty, and screen 4.7+.
-			    if ((!is_screen && version >= 277)
-				    || is_iterm2
-				    || is_mac_terminal
-				    || is_mintty
-				    || (is_screen && arg[1] >= 40700))
-				set_option_value((char_u *)"ttym", 0L,
-							  (char_u *)"sgr", 0);
-			    // For xterm version >= 95 mouse dragging works.
-			    else if (version >= 95)
-				set_option_value((char_u *)"ttym", 0L,
-						       (char_u *)"xterm2", 0);
-			}
-
-			// Detect terminals that set $TERM to something like
-			// "xterm-256color" 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
-			else if (version == 136 && arg[2] == 0)
-			{
-			    is_not_xterm = TRUE;
-
-			    // PuTTY supports sgr-like mouse reporting, but
-			    // only set 'ttymouse' if it was not set by the
-			    // user already.
-			    if (arg[0] == 0
-					  && !option_was_set((char_u *)"ttym"))
-				set_option_value((char_u *)"ttym", 0L,
-							(char_u *)"sgr", 0);
-			}
-
-			// Konsole sends 0;115;0
-			else if (version == 115 && arg[0] == 0 && arg[2] == 0)
-			    is_not_xterm = TRUE;
-
-			// GNU screen sends 83;30600;0, 83;40500;0, etc.
-			// 30600/40500 is a version number of GNU screen. DA2
-			// support is added on 3.6.  DCS string has a special
-			// meaning to GNU screen, but xterm compatibility
-			// checking does not detect GNU screen.
-			if (version >= 30600 && arg[0] == 83)
-			    xcc_test_failed = TRUE;
-
-			// Xterm first responded to this request at patch level
-			// 95, so assume anything below 95 is not xterm.
-			if (version < 95)
-			    is_not_xterm = TRUE;
-
-			// With the real Xterm setting the underline RGB color
-			// clears the background color, disable "t_8u".
-			if (!is_not_xterm && *T_8U != NUL)
-			    T_8U = empty_option;
-
-			// 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).
-			// Only when the xcc_status was set, the test finished,
-			// and xcc_test_failed is FALSE;
-			// Not for Terminal.app, it can't handle t_RS, it
-			// echoes the characters to the screen.
-			if (rcs_status.tr_progress == STATUS_GET
-				&& xcc_status.tr_progress == STATUS_GOT
-				&& !xcc_test_failed
-				&& version >= 279
-				&& *T_CSH != NUL
-				&& *T_CRS != NUL)
-			{
-			    LOG_TR(("Sending cursor style request"));
-			    out_str(T_CRS);
-			    termrequest_sent(&rcs_status);
-			    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 when the xcc_status was set, the test finished,
-			// and xcc_test_failed is FALSE;
-			if (rbm_status.tr_progress == STATUS_GET
-				&& xcc_status.tr_progress == STATUS_GOT
-				&& !xcc_test_failed
-				&& *T_CRC != NUL)
-			{
-			    LOG_TR(("Sending cursor blink mode request"));
-			    out_str(T_CRC);
-			    termrequest_sent(&rbm_status);
-			    need_flush = TRUE;
-			}
-
-			if (need_flush)
-			    out_flush();
-		    }
-		    slen = csi_len;
-# ifdef FEAT_EVAL
-		    set_vim_var_string(VV_TERMRESPONSE, tp, slen);
+# ifdef DEBUG_TERMRESPONSE
+		    if (resp == -1)
+			LOG_TR(("Not enough characters for CSI sequence"));
 # endif
-		    apply_autocmds(EVENT_TERMRESPONSE,
-						   NULL, NULL, FALSE, curbuf);
-		    key_name[0] = (int)KS_EXTRA;
-		    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
-		else if (rbm_status.tr_progress == STATUS_SENT
-			&& first == '?'
-			&& ap == argp + 6
-			&& arg[0] == 12
-			&& ap[-1] == '$'
-			&& trail == 'y')
-		{
-		    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 = 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 && argc == 3 && arg[0] == 3
-							       && trail == 't')
-		{
-		    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;
+		    return resp;
 		}
-
-		// Key with modifier:
-		//	{lead}27;{modifier};{key}~
-		//	{lead}{key};{modifier}u
-		else if ((arg[0] == 27 && argc == 3 && trail == '~')
-			|| (argc == 2 && trail == 'u'))
-		{
-		    seenModifyOtherKeys = TRUE;
-		    if (trail == 'u')
-			key = arg[0];
-		    else
-			key = arg[2];
-
-		    modifiers = decode_modifiers(arg[1]);
-
-		    // Some keys already have Shift included, pass them as
-		    // normal keys.  Not when Ctrl is also used, because <C-H>
-		    // and <C-S-H> are different.
-		    if (modifiers == MOD_MASK_SHIFT
-			    && ((key >= '@' && key <= 'Z')
-				|| key == '^' || key == '_'
-				|| (key >= '{' && key <= '~')))
-			modifiers = 0;
-
-		    // When used with Ctrl we always make a letter upper case,
-		    // so that mapping <C-H> and <C-h> are the same.  Typing
-		    // <C-S-H> also uses "H" but modifier is different.
-		    if ((modifiers & MOD_MASK_CTRL) && ASCII_ISALPHA(key))
-			key = TOUPPER_ASC(key);
-
-		    // insert modifiers with KS_MODIFIER
-		    new_slen = modifiers2keycode(modifiers, &key, string);
-		    slen = csi_len;
-
-		    if (has_mbyte)
-			new_slen += (*mb_char2bytes)(key, string + new_slen);
-		    else
-			string[new_slen++] = key;
-
-		    if (put_string_in_typebuf(offset, slen, string, new_slen,
-						 buf, bufsize, buflen) == FAIL)
-			return -1;
-		    return len + new_slen - slen + offset;
-		}
-
-		// 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:
-	    //
-	    //       {lead}{code};rgb:{rrrr}/{gggg}/{bbbb}{tail}
-	    // or    {lead}{code};rgb:{rr}/{gg}/{bb}{tail}
-	    //
-	    // {code} is 10 for foreground, 11 for background
-	    // {lead} can be <Esc>] or OSC
-	    // {tail} can be '\007', <Esc>\ or STERM.
-	    //
-	    // Consume any code that starts with "{lead}11;", it's also
-	    // possible that "rgba" is following.
+	    // Check for fore/background color response from the terminal,
+	    // starting} with <Esc>] or OSC
 	    else if ((*T_RBG != NUL || *T_RFG != NUL)
 			&& ((tp[0] == ESC && len >= 2 && tp[1] == ']')
 			    || tp[0] == OSC))
 	    {
-		j = 1 + (tp[0] == ESC);
-		if (len >= j + 3 && (argp[0] != '1'
-					 || (argp[1] != '1' && argp[1] != '0')
-					 || argp[2] != ';'))
-		  i = 0; // no match
-		else
-		  for (i = j; i < len; ++i)
-		    if (tp[i] == '\007' || (tp[0] == OSC ? tp[i] == STERM
-			: (tp[i] == ESC && i + 1 < len && tp[i + 1] == '\\')))
-		    {
-			int is_bg = argp[1] == '1';
-			int is_4digit = i - j >= 21 && tp[j + 11] == '/'
-							  && tp[j + 16] == '/';
-
-			if (i - j >= 15 && STRNCMP(tp + j + 3, "rgb:", 4) == 0
-			    && (is_4digit
-				   || (tp[j + 9] == '/' && tp[i + 12 == '/'])))
-			{
-			    char_u *tp_r = tp + j + 7;
-			    char_u *tp_g = tp + j + (is_4digit ? 12 : 10);
-			    char_u *tp_b = tp + j + (is_4digit ? 17 : 13);
-# ifdef FEAT_TERMINAL
-			    int rval, gval, bval;
-
-			    rval = hexhex2nr(tp_r);
-			    gval = hexhex2nr(tp_b);
-			    bval = hexhex2nr(tp_g);
-# endif
-			    if (is_bg)
-			    {
-				char *new_bg_val = (3 * '6' < *tp_r + *tp_g +
-						     *tp_b) ? "light" : "dark";
-
-				LOG_TR(("Received RBG response: %s", tp));
-				rbg_status.tr_progress = STATUS_GOT;
-# ifdef FEAT_TERMINAL
-				bg_r = rval;
-				bg_g = gval;
-				bg_b = bval;
-# endif
-				if (!option_was_set((char_u *)"bg")
-					      && STRCMP(p_bg, new_bg_val) != 0)
-				{
-				    // value differs, apply it
-				    set_option_value((char_u *)"bg", 0L,
-						      (char_u *)new_bg_val, 0);
-				    reset_option_was_set((char_u *)"bg");
-				    redraw_asap(CLEAR);
-				}
-			    }
-# ifdef FEAT_TERMINAL
-			    else
-			    {
-				LOG_TR(("Received RFG response: %s", tp));
-				rfg_status.tr_progress = STATUS_GOT;
-				fg_r = rval;
-				fg_g = gval;
-				fg_b = bval;
-			    }
-# endif
-			}
-
-			// got finished code: consume it
-			key_name[0] = (int)KS_EXTRA;
-			key_name[1] = (int)KE_IGNORE;
-			slen = i + 1 + (tp[i] == ESC);
-# ifdef FEAT_EVAL
-			set_vim_var_string(is_bg ? VV_TERMRBGRESP
-						   : VV_TERMRFGRESP, tp, slen);
-# endif
-			break;
-		    }
-		if (i == len)
-		{
-		    LOG_TR(("not enough characters for RB"));
+		if (handle_osc(tp, argp, len, key_name, &slen) == FAIL)
 		    return -1;
-		}
 	    }
 
-	    // Check for key code response from xterm:
-	    // {lead}{flag}+r<hex bytes><{tail}
-	    //
-	    // {lead} can be <Esc>P or DCS
-	    // {flag} can be '0' or '1'
-	    // {tail} can be Esc>\ or STERM
-	    //
-	    // Check for cursor shape response from xterm:
-	    // {lead}1$r<digit> q{tail}
-	    //
-	    // {lead} can be <Esc>P or DCS
-	    // {tail} can be <Esc>\ or STERM
-	    //
-	    // Consume any code that starts with "{lead}.+r" or "{lead}.$r".
+	    // Check for key code response from xterm,
+	    // starting with <Esc>P or DCS
 	    else if ((check_for_codes || rcs_status.tr_progress == STATUS_SENT)
 		    && ((tp[0] == ESC && len >= 2 && tp[1] == 'P')
 			|| tp[0] == DCS))
 	    {
-		j = 1 + (tp[0] == ESC);
-		if (len < j + 3)
-		    i = len; // need more chars
-		else if ((argp[1] != '+' && argp[1] != '$') || argp[2] != 'r')
-		    i = 0; // no match
-		else if (argp[1] == '+')
-		    // key code response
-		    for (i = j; i < len; ++i)
-		    {
-			if ((tp[i] == ESC && i + 1 < len && tp[i + 1] == '\\')
-				|| tp[i] == STERM)
-			{
-			    if (i - j >= 3)
-				got_code_from_term(tp + j, i);
-			    key_name[0] = (int)KS_EXTRA;
-			    key_name[1] = (int)KE_IGNORE;
-			    slen = i + 1 + (tp[i] == ESC);
-			    break;
-			}
-		    }
-		else
-		{
-		    // Probably the cursor shape response.  Make sure that "i"
-		    // is equal to "len" when there are not sufficient
-		    // characters.
-		    for (i = j + 3; i < len; ++i)
-		    {
-			if (i - j == 3 && !isdigit(tp[i]))
-			    break;
-			if (i - j == 4 && tp[i] != ' ')
-			    break;
-			if (i - j == 5 && tp[i] != 'q')
-			    break;
-			if (i - j == 6 && tp[i] != ESC && tp[i] != STERM)
-			    break;
-			if ((i - j == 6 && tp[i] == STERM)
-			 || (i - j == 7 && tp[i] == '\\'))
-			{
-			    int number = argp[3] - '0';
-
-			    // 0, 1 = block blink, 2 = block
-			    // 3 = underline blink, 4 = underline
-			    // 5 = vertical bar blink, 6 = vertical bar
-			    number = number == 0 ? 1 : number;
-			    initial_cursor_shape = (number + 1) / 2;
-			    // The blink flag is actually inverted, compared to
-			    // the value set with T_SH.
-			    initial_cursor_shape_blink =
-						   (number & 1) ? FALSE : TRUE;
-			    rcs_status.tr_progress = STATUS_GOT;
-			    LOG_TR(("Received cursor shape response: %s", tp));
-
-			    key_name[0] = (int)KS_EXTRA;
-			    key_name[1] = (int)KE_IGNORE;
-			    slen = i + 1;
-# ifdef FEAT_EVAL
-			    set_vim_var_string(VV_TERMSTYLERESP, tp, slen);
-# endif
-			    break;
-			}
-		    }
-		}
-
-		if (i == len)
-		{
-		    // These codes arrive many together, each code can be
-		    // truncated at any point.
-		    LOG_TR(("not enough characters for XT"));
+		if (handle_dcs(tp, argp, len, key_name, &slen) == FAIL)
 		    return -1;
-		}
 	    }
 	}
 #endif
--- a/src/version.c
+++ b/src/version.c
@@ -755,6 +755,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    939,
+/**/
     938,
 /**/
     937,