patch 9.2.0387: DECRQM request may leave stray chars in terminal

Problem:  Sending DECRQM from handle_version_response() caused DECRPM
          responses to arrive during user input processing, leaving
          bytes in typebuf when clear_showcmd() ran.  This made
          visual-mode showcmd (e.g. "7" line count after V<C-D><C-D>)
          intermittently disappear, failing many screendump tests on CI.
Solution: Move DECRQM request out of handle_version_response() and send
          it at startup via may_req_decrqm(), following the existing
          may_req_termresponse() and may_req_bg_color() pattern.
          Add TPR_DECRQM property set per terminal from the DA2 reply,
          and route DECRQM sends through a may_req_decrqm() helper using
          the termrequest_T pattern, skipping terminals known to
          mishandle it (Foxe Chen, Hirohito Higashi).

fixes:  #19852
closes: #19938

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Hirohito Higashi <h.east.727@gmail.com>
Co-Authored-By: Foxe Chen <chen.foxe@gmail.com>
Signed-off-by: Hirohito Higashi <h.east.727@gmail.com>
Signed-off-by: Foxe Chen <chen.foxe@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
Hirohito Higashi
2026-04-21 20:46:12 +00:00
committed by Christian Brabandt
parent 2ea4a7c3b7
commit cee8fd73eb
6 changed files with 90 additions and 25 deletions
+5 -1
View File
@@ -1,4 +1,4 @@
*builtin.txt* For Vim version 9.2. Last change: 2026 Apr 20
*builtin.txt* For Vim version 9.2. Last change: 2026 Apr 21
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -11990,6 +11990,7 @@ terminalprops() *terminalprops()*
underline_rgb whether |t_8u| works **
mouse mouse type supported
kitty whether Kitty terminal was detected
decrqm whether sending DECRQM sequences work
** value 'u' for unknown, 'y' for yes, 'n' for no
@@ -12009,6 +12010,9 @@ terminalprops() *terminalprops()*
For "mouse" the value 'u' is unknown
If "decrqm" is 'y', then Vim will query support for the
'termsync' and 'termresize' ("inband") options.
Also see:
- 'ambiwidth' - detected by using |t_u7|.
- |v:termstyleresp| and |v:termblinkresp| for the response to
+4 -3
View File
@@ -892,10 +892,11 @@ vim_main2(void)
may_req_termresponse();
may_req_bg_color();
// Same reason as for termresponse: don't want the terminal sending out
// the DECRPM response after Vim has exited.
may_req_decrqm();
# endif
// Same reason for termresponse, don't want the terminal sending out the
// DECRPM response after Vim has exited.
send_decrqm_modes();
// start in insert mode
if (p_im)
+1 -1
View File
@@ -60,6 +60,7 @@ void stoptermcap(void);
void may_req_termresponse(void);
void check_terminal_behavior(void);
void may_req_bg_color(void);
void may_req_decrqm(void);
int swapping_screen(void);
void scroll_start(void);
void cursor_on_force(void);
@@ -96,7 +97,6 @@ void swap_tcap(void);
void ansi_color2rgb(int nr, char_u *r, char_u *g, char_u *b, char_u *ansi_idx);
void cterm_color2rgb(int nr, char_u *r, char_u *g, char_u *b, char_u *ansi_idx);
int term_replace_keycodes(char_u *ta_buf, int ta_len, int len_arg);
void send_decrqm_modes(void);
void term_disable_dec(void);
void term_set_win_resize(bool state);
void term_set_sync_output(int flags);
+61 -19
View File
@@ -154,6 +154,9 @@ static termrequest_T rcs_status = TERMREQUEST_INIT;
// Request window's position report:
static termrequest_T winpos_status = TERMREQUEST_INIT;
// Request DECRQM (DEC mode) report:
static termrequest_T decrqm_status = TERMREQUEST_INIT;
static termrequest_T *all_termrequests[] = {
&crv_status,
&u7_status,
@@ -165,6 +168,7 @@ static termrequest_T *all_termrequests[] = {
&rbm_status,
&rcs_status,
&winpos_status,
&decrqm_status,
NULL
};
@@ -1539,8 +1543,10 @@ typedef struct {
#define TPR_MOUSE 3
// term response indicates kitty
#define TPR_KITTY 4
// can send DECRQM requests to terminal
#define TPR_DECRQM 5
// table size
#define TPR_COUNT 5
#define TPR_COUNT 6
static termprop_T term_props[TPR_COUNT];
@@ -1564,6 +1570,8 @@ init_term_props(int all)
term_props[TPR_MOUSE].tpr_set_by_termresponse = TRUE;
term_props[TPR_KITTY].tpr_name = "kitty";
term_props[TPR_KITTY].tpr_set_by_termresponse = FALSE;
term_props[TPR_DECRQM].tpr_name = "decrqm";
term_props[TPR_DECRQM].tpr_set_by_termresponse = TRUE;
for (i = 0; i < TPR_COUNT; ++i)
if (all || term_props[i].tpr_set_by_termresponse)
@@ -4304,6 +4312,36 @@ may_req_bg_color(void)
}
}
/*
* Query the settings for the DEC modes we support via DECRQM.
* Only sent once, and only when the terminal is known not to dislike it
* (i.e. TPR_DECRQM is TPR_YES, or still TPR_UNKNOWN when the version response
* has not yet been received).
* The DECRPM responses are caught in handle_csi().
*/
void
may_req_decrqm(void)
{
if (decrqm_status.tr_progress == STATUS_GET
&& term_props[TPR_DECRQM].tpr_status != TPR_NO
&& can_get_termresponse()
&& starting == 0)
{
MAY_WANT_TO_LOG_THIS;
LOG_TR1("Sending DECRQM requests");
for (int i = 0; i < (int)ARRAY_LENGTH(dec_modes); i++)
{
vim_snprintf((char *)IObuff, IOSIZE, "\033[?%d$p", dec_modes[i]);
out_str(IObuff);
}
termrequest_sent(&decrqm_status);
// check for the characters now, otherwise they might be eaten by
// get_keystroke()
out_flush();
(void)vpeekc_nomap();
}
}
#endif
/*
@@ -5172,21 +5210,27 @@ handle_version_response(int first, int *arg, int argc, char_u *tp)
may_adjust_color_count(256);
// Libvterm can handle SGR mouse reporting.
term_props[TPR_MOUSE].tpr_status = TPR_MOUSE_SGR;
term_props[TPR_DECRQM].tpr_status = TPR_YES;
}
if (version == 95)
{
// Mac Terminal.app sends 1;95;0
//
// Terminal.app doesn't seem to handle DECRQM sequences
// properly, see issue #19852.
if (arg[0] == 1 && arg[2] == 0)
{
term_props[TPR_UNDERLINE_RGB].tpr_status = TPR_YES;
term_props[TPR_MOUSE].tpr_status = TPR_MOUSE_SGR;
term_props[TPR_DECRQM].tpr_status = TPR_NO;
}
// iTerm2 sends 0;95;0
else if (arg[0] == 0 && arg[2] == 0)
{
// iTerm2 can do SGR mouse reporting
term_props[TPR_MOUSE].tpr_status = TPR_MOUSE_SGR;
term_props[TPR_DECRQM].tpr_status = TPR_YES;
}
// old iTerm2 sends 0;95;
else if (arg[0] == 0 && arg[2] == -1)
@@ -5212,6 +5256,7 @@ handle_version_response(int first, int *arg, int argc, char_u *tp)
term_props[TPR_MOUSE].tpr_status = TPR_MOUSE_SGR;
else if (version >= 95)
term_props[TPR_MOUSE].tpr_status = TPR_MOUSE_XTERM2;
term_props[TPR_DECRQM].tpr_status = TPR_YES;
}
// Detect terminals that set $TERM to something like
@@ -5224,11 +5269,15 @@ handle_version_response(int first, int *arg, int argc, char_u *tp)
// Assuming any version number over 2500 is not an
// xterm (without the limit for rxvt and screen).
if (arg[1] >= 2500)
{
term_props[TPR_UNDERLINE_RGB].tpr_status = TPR_YES;
term_props[TPR_DECRQM].tpr_status = TPR_YES;
}
else if (version == 136 && arg[2] == 0)
{
term_props[TPR_UNDERLINE_RGB].tpr_status = TPR_YES;
term_props[TPR_DECRQM].tpr_status = TPR_YES;
// PuTTY sends 0;136;0
if (arg[0] == 0)
@@ -5252,6 +5301,7 @@ handle_version_response(int first, int *arg, int argc, char_u *tp)
// Kitty can handle SGR mouse reporting.
term_props[TPR_MOUSE].tpr_status = TPR_MOUSE_SGR;
term_props[TPR_DECRQM].tpr_status = TPR_YES;
}
// GNU screen sends 83;30600;0, 83;40500;0, etc.
@@ -5262,6 +5312,9 @@ handle_version_response(int first, int *arg, int argc, char_u *tp)
{
term_props[TPR_CURSOR_STYLE].tpr_status = TPR_NO;
term_props[TPR_CURSOR_BLINK].tpr_status = TPR_NO;
term_props[TPR_DECRQM].tpr_status = TPR_NO; // screen doesn't seem
// to handle DECRQM
// sequences
}
// Xterm first responded to this request at patch level
@@ -5753,6 +5806,13 @@ handle_csi(
key_name[0] = (int)KS_EXTRA;
key_name[1] = (int)KE_IGNORE;
#ifdef FEAT_TERMRESPONSE
// Mark the DECRQM request as answered so it is not sent again and
// stoptermcap() does not wait for it.
if (decrqm_status.tr_progress == STATUS_SENT)
decrqm_status.tr_progress = STATUS_GOT;
#endif
if (setting >= 0 && setting <= 4)
{
LOG_TRN("Received DECRPM mode %d: %s", arg[0], tp);
@@ -7987,24 +8047,6 @@ term_replace_keycodes(char_u *ta_buf, int ta_len, int len_arg)
return len;
}
/*
* Query the settings for the DEC modes we support
*/
void
send_decrqm_modes(void)
{
if (termcap_active && cur_tmode == TMODE_RAW)
{
// Request setting of relevant DEC modes via DECRQM
for (int i = 0; i < (int)ARRAY_LENGTH(dec_modes); i++)
{
vim_snprintf((char *)IObuff, IOSIZE, "\033[?%d$p", dec_modes[i]);
out_str(IObuff);
}
out_flush();
}
}
/*
* Should be called when cleaning up terminal state.
*/
+17 -1
View File
@@ -359,7 +359,7 @@ func Test_term_mouse_middle_click_insert_mode()
let &term = save_term
let &ttymouse = save_ttymouse
call test_override('no_query_mouse', 0)
close!
bw!
endfunc
" Test for switching window using mouse in insert mode
@@ -1720,6 +1720,7 @@ func Test_xx01_term_style_response()
\ underline_rgb: 'u',
\ mouse: 's',
\ kitty: 'u',
\ decrqm: 'y'
\ }, terminalprops())
set t_RV=
@@ -1755,6 +1756,7 @@ func Test_xx02_iTerm2_response()
\ underline_rgb: 'u',
\ mouse: 's',
\ kitty: 'u',
\ decrqm: 'y'
\ }, terminalprops())
set t_RV=
@@ -1775,6 +1777,7 @@ func Run_libvterm_konsole_response(code)
\ underline_rgb: 'u',
\ mouse: 's',
\ kitty: 'u',
\ decrqm: 'y'
\ }, terminalprops())
endfunc
@@ -1818,6 +1821,7 @@ func Test_xx04_Mac_Terminal_response()
\ underline_rgb: 'y',
\ mouse: 's',
\ kitty: 'u',
\ decrqm: 'n'
\ }, terminalprops())
call assert_equal("\<Esc>[58;2;%lu;%lu;%lum", &t_8u)
@@ -1849,6 +1853,7 @@ func Test_xx05_mintty_response()
\ underline_rgb: 'y',
\ mouse: 's',
\ kitty: 'u',
\ decrqm: 'y'
\ }, terminalprops())
set t_RV=
@@ -1885,6 +1890,7 @@ func Test_xx06_screen_response()
\ underline_rgb: 'y',
\ mouse: 's',
\ kitty: 'u',
\ decrqm: 'n'
\ }, terminalprops())
set t_RV=
@@ -1910,6 +1916,7 @@ func Do_check_t_8u_set_reset(set_by_user)
\ underline_rgb: 'u',
\ mouse: 's',
\ kitty: 'u',
\ decrqm: 'y'
\ }, terminalprops())
call assert_equal(a:set_by_user ? default_value : '', &t_8u)
endfunc
@@ -1949,6 +1956,7 @@ func Test_xx07_xterm_response()
\ underline_rgb: 'y',
\ mouse: 'u',
\ kitty: 'u',
\ decrqm: 'y'
\ }, terminalprops())
" xterm >= 95 < 277 "xterm2"
@@ -1965,6 +1973,7 @@ func Test_xx07_xterm_response()
\ underline_rgb: 'u',
\ mouse: '2',
\ kitty: 'u',
\ decrqm: 'y'
\ }, terminalprops())
" xterm >= 277: "sgr"
@@ -1981,6 +1990,7 @@ func Test_xx07_xterm_response()
\ underline_rgb: 'u',
\ mouse: 's',
\ kitty: 'u',
\ decrqm: 'y'
\ }, terminalprops())
" xterm >= 279: "sgr" and cursor_style not reset; also check t_8u reset,
@@ -2010,6 +2020,7 @@ func Test_xx08_kitty_response()
\ underline_rgb: 'y',
\ mouse: 's',
\ kitty: 'y',
\ decrqm: 'y'
\ }, terminalprops())
call feedkeys("\<Esc>[?1u") " simulate the kitty keyboard protocol is enabled
@@ -3055,6 +3066,11 @@ func Test_term_win_resize()
let buf = RunVimInTerminal('-S XTestWinResize', #{rows: 15, cols: 20})
" Must add a delay, since status report is sent internally by vim only when
" version response is received, which may come after we send the status report
" here.
sleep 100m
" Send status report
call term_sendkeys(buf, "\<Esc>[?2048;1$y")
call TermWait(buf)
+2
View File
@@ -729,6 +729,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
387,
/**/
386,
/**/