Commit: patch 9.1.1932: OSC terminal response hard to detect

3 views
Skip to first unread message

Christian Brabandt

unread,
Nov 27, 2025, 4:00:36 PM (2 days ago) Nov 27
to vim...@googlegroups.com
patch 9.1.1932: OSC terminal response hard to detect

Commit: https://github.com/vim/vim/commit/c53150174859c34e96a65c7435743614890cd335
Author: Foxe Chen <chen...@gmail.com>
Date: Thu Nov 27 20:53:36 2025 +0000

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...@gmail.com>
Signed-off-by: Christian Brabandt <c...@256bit.org>

diff --git a/runtime/doc/intro.txt b/runtime/doc/intro.txt
index a25c31342..c09e8e744 100644
--- a/runtime/doc/intro.txt
+++ b/runtime/doc/intro.txt
@@ -1,4 +1,4 @@
-*intro.txt* For Vim version 9.1. Last change: 2025 Nov 09
+*intro.txt* For Vim version 9.1. Last change: 2025 Nov 27


VIM REFERENCE MANUAL by Bram Moolenaar
@@ -450,6 +450,8 @@ notation meaning equivalent decimal value(s) ~
<Del> delete 127
<CSI> command sequence intro ALT-Esc 155 *<CSI>*
<xCSI> CSI when typed in the GUI *<xCSI>*
+<OSC> operating system command 157 *<OSC>*
+<xOSC> received OSC response *<xOSC>*

<EOL> end-of-line (can be <CR>, <NL> or <CR><NL>,
depends on system and 'fileformat') *<EOL>*
diff --git a/runtime/doc/tags b/runtime/doc/tags
index 64b548a86..0e7000d28 100644
--- a/runtime/doc/tags
+++ b/runtime/doc/tags
@@ -3841,6 +3841,7 @@ $quote eval.txt /*$quote*
<NL> motion.txt /*<NL>*
<Nop> map.txt /*<Nop>*
<Nul> intro.txt /*<Nul>*
+<OSC> intro.txt /*<OSC>*
<PageDown> scroll.txt /*<PageDown>*
<PageUp> scroll.txt /*<PageUp>*
<Plug> map.txt /*<Plug>*
@@ -3961,6 +3962,7 @@ $quote eval.txt /*$quote*
<xHome> term.txt /*<xHome>*
<xHome>-xterm term.txt /*<xHome>-xterm*
<xLeft> term.txt /*<xLeft>*
+<xOSC> intro.txt /*<xOSC>*
<xRight> term.txt /*<xRight>*
<xUp> term.txt /*<xUp>*
= change.txt /*=*
diff --git a/src/evalfunc.c b/src/evalfunc.c
index e770321ea..eca95bbcf 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -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;
diff --git a/src/getchar.c b/src/getchar.c
index 3219fd2ce..a7b9741bd 100644
--- a/src/getchar.c
+++ b/src/getchar.c
@@ -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;

diff --git a/src/globals.h b/src/globals.h
index 7e72ea744..f412ab5fc 100644
--- a/src/globals.h
+++ b/src/globals.h
@@ -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);
diff --git a/src/keymap.h b/src/keymap.h
index e993e0999..b3be60c48 100644
--- a/src/keymap.h
+++ b/src/keymap.h
@@ -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)
diff --git a/src/misc2.c b/src/misc2.c
index 8380e745c..fcbf0dd1c 100644
--- a/src/misc2.c
+++ b/src/misc2.c
@@ -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},
diff --git a/src/term.c b/src/term.c
index c317ca601..d3939ef4d 100644
--- a/src/term.c
+++ b/src/term.c
@@ -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.
diff --git a/src/testdir/test_termcodes.vim b/src/testdir/test_termcodes.vim
index ffe3576ff..5c30f5a8d 100644
--- a/src/testdir/test_termcodes.vim
+++ b/src/testdir/test_termcodes.vim
@@ -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! ", '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
diff --git a/src/version.c b/src/version.c
index 75095d126..38609746c 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 */
+/**/
+ 1932,
/**/
1931,
/**/
Reply all
Reply to author
Forward
0 new messages