From: Hirohito Higashi Date: Tue, 21 Apr 2026 20:46:12 +0000 (+0000) Subject: patch 9.2.0387: DECRQM request may leave stray chars in terminal X-Git-Tag: v9.2.0387^0 X-Git-Url: http://git.ipfire.org/index.cgi?a=commitdiff_plain;p=thirdparty%2Fvim.git 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) 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) Co-Authored-By: Hirohito Higashi Co-Authored-By: Foxe Chen Signed-off-by: Hirohito Higashi Signed-off-by: Foxe Chen Signed-off-by: Christian Brabandt --- diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index 0fe02ac944..9ea0ae82cc 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 bfabcbbeda..e0d0e7e37f 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 82c794f4f9..5ec909f46c 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 882df66551..876e15d950 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, "\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. */ diff --git a/src/testdir/test_termcodes.vim b/src/testdir/test_termcodes.vim index edf64d2830..eaf60f53be 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("\[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("\[?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, "\[?2048;1$y") call TermWait(buf) diff --git a/src/version.c b/src/version.c index 36a8da0782..b2be3d98b5 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, /**/