patch 9.1.1932: OSC terminal response hard to detect

Problem:  OSC terminal response hard to detect
Solution: Add the <OSC> and <xOSC> pseudo keys
          (Foxe Chen).

related: #18660
closes: #18799

Signed-off-by: Foxe Chen <chen.foxe@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
Foxe Chen
2025-11-27 20:53:36 +00:00
committed by Christian Brabandt
parent b217ffbef2
commit c531501748
10 changed files with 67 additions and 13 deletions

View File

@ -5150,7 +5150,9 @@ f_feedkeys(typval_T *argvars, typval_T *rettv UNUSED)
++ex_normal_busy;
++in_feedkeys;
}
++allow_osc_key;
exec_normal(TRUE, lowlevel, TRUE);
--allow_osc_key;
if (!dangerous)
{
--ex_normal_busy;

View File

@ -1977,6 +1977,9 @@ vgetc(void)
}
c = TO_SPECIAL(c2, c);
if (allow_osc_key == 0 && c == K_OSC)
continue;
// K_ESC is used to avoid ambiguity with the single Esc
// character that might be the start of an escape sequence.
// Convert it back to a single Esc here.
@ -2452,6 +2455,7 @@ getchar_common(typval_T *argvars, typval_T *rettv, int allow_number)
++no_mapping;
++allow_keys;
++allow_osc_key;
if (!simplify)
++no_reduce_keys;
for (;;)
@ -2479,6 +2483,7 @@ getchar_common(typval_T *argvars, typval_T *rettv, int allow_number)
}
--no_mapping;
--allow_keys;
--allow_osc_key;
if (!simplify)
--no_reduce_keys;

View File

@ -2124,3 +2124,6 @@ INIT(= CLIENTSERVER_METHOD_NONE);
// Path to socket of last client that communicated with us
EXTERN char_u *client_socket INIT(= NULL);
#endif
// If the <xOSC> key should be propogated from vgetc()
EXTERN int allow_osc_key INIT(= 0);

View File

@ -280,6 +280,7 @@ enum key_extra
, KE_SID = 106 // <SID> special key, followed by {nr};
, KE_ESC = 107 // used for K_ESC
, KE_WILD = 108 // triggers wildmode completion
, KE_OSC = 109 // finished OSC sequence
};
/*
@ -478,6 +479,7 @@ enum key_extra
#define K_MOUSERIGHT TERMCAP2KEY(KS_EXTRA, KE_MOUSERIGHT)
#define K_CSI TERMCAP2KEY(KS_EXTRA, KE_CSI)
#define K_OSC TERMCAP2KEY(KS_EXTRA, KE_OSC)
#define K_SNR TERMCAP2KEY(KS_EXTRA, KE_SNR)
#define K_PLUG TERMCAP2KEY(KS_EXTRA, KE_PLUG)
#define K_CMDWIN TERMCAP2KEY(KS_EXTRA, KE_CMDWIN)

View File

@ -1053,6 +1053,7 @@ static struct key_name_entry
{TRUE, NL, STRING_INIT("NewLine"), TRUE},
{TRUE, NL, STRING_INIT("NL"), FALSE},
{TRUE, K_ZERO, STRING_INIT("Nul"), FALSE},
{TRUE, OSC, STRING_INIT("OSC"), FALSE},
{TRUE, K_PAGEDOWN, STRING_INIT("PageDown"), FALSE},
{TRUE, K_PAGEUP, STRING_INIT("PageUp"), FALSE},
{TRUE, K_PE, STRING_INIT("PasteEnd"), FALSE},
@ -1111,6 +1112,7 @@ static struct key_name_entry
{TRUE, K_XF4, STRING_INIT("xF4"), FALSE},
{TRUE, K_XHOME, STRING_INIT("xHome"), FALSE},
{TRUE, K_XLEFT, STRING_INIT("xLeft"), FALSE},
{TRUE, K_OSC, STRING_INIT("xOSC"), FALSE},
{TRUE, K_XRIGHT, STRING_INIT("xRight"), FALSE},
{TRUE, K_XUP, STRING_INIT("xUp"), FALSE},
{TRUE, K_ZEND, STRING_INIT("zEnd"), FALSE},

View File

@ -5921,6 +5921,9 @@ handle_osc(char_u *tp, int len, char_u *key_name, int *slen)
// The whole OSC response may be larger than the typeahead buffer.
// To handle this, keep reading data in and out of the typeahead
// buffer until we read an OSC terminator or timeout.
// We can't use the previous buffer since we transferred ownership of it
// to the vim var.
ga_init2(&osc_state.buf, 1, 1024);
#ifdef ELAPSED_FUNC
ELAPSED_INIT(osc_state.start_tv);
@ -5933,7 +5936,6 @@ handle_osc(char_u *tp, int len, char_u *key_name, int *slen)
last_char = ((char_u *)osc_state.buf.ga_data)[osc_state.buf.ga_len - 1];
key_name[0] = (int)KS_EXTRA;
key_name[1] = (int)KE_IGNORE;
// Read data and append to buffer. If we reach a terminator, then
// finally set the vim var.
@ -5945,6 +5947,8 @@ handle_osc(char_u *tp, int len, char_u *key_name, int *slen)
{
osc_state.processing = FALSE;
key_name[1] = (int)KE_OSC;
ga_concat_len(&osc_state.buf, tp, i + 1 + (tp[i] == ESC));
ga_append(&osc_state.buf, NUL);
*slen = i + 1 + (tp[i] == ESC);
@ -5962,6 +5966,8 @@ handle_osc(char_u *tp, int len, char_u *key_name, int *slen)
return OK;
}
key_name[1] = (int)KE_IGNORE;
#ifdef ELAPSED_FUNC
if (ELAPSED_FUNC(osc_state.start_tv) >= p_ost)
{
@ -6167,9 +6173,15 @@ check_termcode(
}
if (osc_state.processing)
{
// Still processing OSC response data, go straight to handler
// function.
tp[len] = NUL;
key_name[0] = NUL;
key_name[1] = NUL;
modifiers = 0;
goto handle_osc;
}
/*
* Skip this position if the character does not appear as the first
@ -6690,12 +6702,8 @@ handle_osc:
*/
key = handle_x_keys(TERMCAP2KEY(key_name[0], key_name[1]));
if (osc_state.processing)
// We don't want to add anything to the typeahead buffer.
new_slen = 0;
else
// Add any modifier codes to our string.
new_slen = modifiers2keycode(modifiers, &key, string);
// Add any modifier codes to our string.
new_slen = modifiers2keycode(modifiers, &key, string);
// Finally, add the special key code to our string
key_name[0] = KEY2TERMCAP0(key);
@ -6708,8 +6716,10 @@ handle_osc:
else
string[new_slen++] = key_name[1];
}
else if (new_slen == 0 && key_name[0] == KS_EXTRA
&& key_name[1] == KE_IGNORE)
else if (osc_state.processing ||
(new_slen == 0
&& key_name[0] == KS_EXTRA
&& key_name[1] == KE_IGNORE))
{
// Do not put K_IGNORE into the buffer, do return KEYLEN_REMOVED
// to indicate what happened.

View File

@ -2847,14 +2847,38 @@ func Test_term_response_osc()
" Test if large OSC responses (that must be processed in chunks) are handled
let data = repeat('a', 3000)
call feedkeys("\<Esc>]12;" .. data .. "\x07", 'Lx!')
call assert_equal("\<Esc>]12;" .. data .. "\x07", v:termosc)
call feedkeys("\<Esc>]12;" .. data .. "\<Esc>\\", 'Lx!')
call assert_equal("\<Esc>]12;" .. data .. "\<Esc>\\", v:termosc)
" Test small OSC responses
call feedkeys("\<Esc>]15;hello world!\07", 'Lx!')
call feedkeys("\<Esc>]15;hello world!\x07", 'Lx!')
call assert_equal("\<Esc>]15;hello world!\x07", v:termosc)
endfunc
" Test if xOSC key is emitted.
func Test_term_response_xosc_key()
CheckRunVimInTerminal
let lines =<< trim END
func Test()
while getcharstr(-1) != "\<xOSC>"
endwhile
call writefile(["done"], 'XTestResult')
endfunc
END
call writefile(lines, 'XTest', 'D')
defer delete('XTestResult')
let buf = RunVimInTerminal("-S XTest", {'rows': 10})
call TermWait(buf)
call term_sendkeys(buf, "\<Esc>:call Test()\<CR>")
call TermWait(buf)
call term_sendkeys(buf, "\<Esc>]52;hello;\<Esc>\\")
call TermWait(buf)
call WaitForAssert({-> assert_equal(["done"], readfile('XTestResult'))})
call StopVimInTerminal(buf)
endfunc
" This only checks if the sequence is recognized.
func Test_term_rgb_response()
set t_RF=x

View File

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