patch 9.2.0387: DECRQM request may leave stray chars in terminal
Commit:
https://github.com/vim/vim/commit/cee8fd73eb54a3196ac3cc2292a0e6910aeaff75
Author: Hirohito Higashi <
h.eas...@gmail.com>
Date: Tue Apr 21 20:46:12 2026 +0000
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) <
nor...@anthropic.com>
Co-Authored-By: Hirohito Higashi <
h.eas...@gmail.com>
Co-Authored-By: Foxe Chen <
chen...@gmail.com>
Signed-off-by: Hirohito Higashi <
h.eas...@gmail.com>
Signed-off-by: Foxe Chen <
chen...@gmail.com>
Signed-off-by: Christian Brabandt <
c...@256bit.org>
diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt
index 0fe02ac94..9ea0ae82c 100644
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -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
diff --git a/src/main.c b/src/main.c
index bfabcbbed..e0d0e7e37 100644
--- a/src/main.c
+++ b/src/main.c
@@ -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)
diff --git a/src/proto/
term.pro b/src/proto/
term.pro
index 82c794f4f..5ec909f46 100644
--- a/src/proto/
term.pro
+++ b/src/proto/
term.pro
@@ -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);
diff --git a/src/term.c b/src/term.c
index 882df6655..876e15d95 100644
--- a/src/term.c
+++ b/src/term.c
@@ -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, " [?%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, " [?%d$p", dec_modes[i]);
- out_str(IObuff);
- }
- out_flush();
- }
-}
-
/*
* Should be called when cleaning up terminal state.
*/
diff --git a/src/testdir/test_termcodes.vim b/src/testdir/test_termcodes.vim
index edf64d283..eaf60f53b 100644
--- a/src/testdir/test_termcodes.vim
+++ b/src/testdir/test_termcodes.vim
@@ -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)
diff --git a/src/version.c b/src/version.c
index 36a8da078..b2be3d98b 100644
--- a/src/version.c
+++ b/src/version.c
@@ -729,6 +729,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 387,
/**/
386,
/**/