mirror of
https://github.com/vim/vim.git
synced 2026-05-28 00:21:37 +02:00
patch 9.2.0412: channel: term_start() out_cb/err_cb no longer deliver raw chunks
Problem: channel: term_start() out_cb/err_cb no longer deliver raw
chunks (regression from patch 9.2.0224, breaks callers like
vim-fugitive that parse multi-line output)
(D. Ben Knoble, after v9.2.0224)
Solution: Remove the PTY-specific per-line splitting in
may_invoke_callback() so RAW callbacks again receive the
raw chunk as returned by read(), preserving embedded NL.
If per-line handling is desired, the callback must split
"msg" on NL and strip the trailing CR itself; document
this behavior in term_start(). Replace
Test_term_start_cb_per_line() with
Test_term_start_cb_raw_chunk() to verify the raw-chunk
contract.
fixes: #20041
closes: #20045
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Yasuhiro Matsumoto <mattn.jp@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
committed by
Christian Brabandt
parent
e7745b7cbf
commit
41c3379bdf
@@ -1,4 +1,4 @@
|
||||
*channel.txt* For Vim version 9.2. Last change: 2026 Apr 15
|
||||
*channel.txt* For Vim version 9.2. Last change: 2026 Apr 28
|
||||
|
||||
|
||||
VIM REFERENCE MANUAL by Bram Moolenaar
|
||||
|
||||
@@ -965,6 +965,20 @@ term_start({cmd} [, {options}]) *term_start()*
|
||||
input and one output handle, with no separate handle for
|
||||
stderr.
|
||||
|
||||
Note: term_start() always uses RAW mode for its callbacks.
|
||||
"out_cb" and "err_cb" receive the raw chunk of data as read
|
||||
from the OS. A single callback invocation may contain
|
||||
multiple lines separated by NL, and (for stdout via a pty)
|
||||
each line may have a trailing CR from the line discipline
|
||||
(ONLCR). If per-line handling is desired, the callback must
|
||||
split "msg" on NL and strip the trailing CR itself.
|
||||
Example: >
|
||||
func Handle(ch, msg)
|
||||
for line in split(a:msg, "\n")
|
||||
echom substitute(line, '\r$', '', '')
|
||||
endfor
|
||||
endfunc
|
||||
<
|
||||
There are extra options:
|
||||
"term_name" name to use for the buffer name, instead
|
||||
of the command name.
|
||||
|
||||
+1
-40
@@ -3510,46 +3510,7 @@ may_invoke_callback(channel_T *channel, ch_part_T part)
|
||||
// invoke the channel callback
|
||||
ch_log(channel, "Invoking channel callback %s",
|
||||
(char *)callback->cb_name);
|
||||
#ifdef FEAT_TERMINAL
|
||||
// For a terminal job in RAW mode (term_start()), split msg on
|
||||
// NL and invoke the callback once per line with trailing CR
|
||||
// stripped. This ensures out_cb/err_cb receive one line at a
|
||||
// time regardless of how much data arrives in a single read.
|
||||
if (ch_mode == CH_MODE_RAW && msg != NULL
|
||||
&& channel->ch_job != NULL
|
||||
&& channel->ch_job->jv_tty_out != NULL)
|
||||
{
|
||||
char_u *cp = msg;
|
||||
char_u *nl;
|
||||
|
||||
while ((nl = vim_strchr(cp, NL)) != NULL)
|
||||
{
|
||||
long_u len = (long_u)(nl - cp);
|
||||
|
||||
if (len > 0 && cp[len - 1] == CAR)
|
||||
--len;
|
||||
argv[1].vval.v_string = vim_strnsave(cp, len);
|
||||
if (argv[1].vval.v_string != NULL)
|
||||
invoke_callback(channel, callback, argv);
|
||||
vim_free(argv[1].vval.v_string);
|
||||
cp = nl + 1;
|
||||
}
|
||||
if (*cp != NUL)
|
||||
{
|
||||
long_u len = STRLEN(cp);
|
||||
|
||||
if (len > 0 && cp[len - 1] == CAR)
|
||||
--len;
|
||||
argv[1].vval.v_string = vim_strnsave(cp, len);
|
||||
if (argv[1].vval.v_string != NULL)
|
||||
invoke_callback(channel, callback, argv);
|
||||
vim_free(argv[1].vval.v_string);
|
||||
}
|
||||
argv[1].vval.v_string = msg;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
invoke_callback(channel, callback, argv);
|
||||
invoke_callback(channel, callback, argv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2933,13 +2933,15 @@ func Test_error_callback_terminal()
|
||||
unlet! g:out g:error
|
||||
endfunc
|
||||
|
||||
" Verify that term_start() with out_cb/err_cb delivers one line per callback
|
||||
" call (no embedded newlines, no trailing CR), matching the user's expectation.
|
||||
func Test_term_start_cb_per_line()
|
||||
" Verify that term_start() with out_cb/err_cb delivers data in RAW mode,
|
||||
" preserving embedded newlines in the raw chunk received from read(). If
|
||||
" per-line handling is desired, it is the callback's responsibility to split
|
||||
" on NL and strip the trailing CR.
|
||||
func Test_term_start_cb_raw_chunk()
|
||||
CheckUnix
|
||||
CheckFeature terminal
|
||||
let g:Ch_msgs = []
|
||||
let script_file = 'Xterm_cb_per_line.sh'
|
||||
let script_file = 'Xterm_cb_raw_chunk.sh'
|
||||
call writefile(["#!/bin/sh",
|
||||
\ "printf 'err:1\\nerr:2\\n' >&2",
|
||||
\ "printf 'out:3\\n'"], script_file, 'D')
|
||||
@@ -2947,10 +2949,16 @@ func Test_term_start_cb_per_line()
|
||||
let ptybuf = term_start('./' .. script_file, {
|
||||
\ 'out_cb': {ch, msg -> add(g:Ch_msgs, msg)},
|
||||
\ 'err_cb': {ch, msg -> add(g:Ch_msgs, msg)}})
|
||||
call WaitForAssert({-> assert_equal(3, len(g:Ch_msgs))}, 5000)
|
||||
" Each line must arrive as a separate callback call with no embedded CR/NL.
|
||||
call assert_equal(['err:1', 'err:2', 'out:3'], g:Ch_msgs)
|
||||
" Wait until both the raw stderr chunk and a stdout chunk have arrived.
|
||||
call WaitForAssert({-> assert_true(
|
||||
\ index(g:Ch_msgs, "err:1\nerr:2\n") >= 0
|
||||
\ && match(g:Ch_msgs, 'out:3') >= 0)}, 5000)
|
||||
" stderr (via pipe) arrives as a single raw chunk with embedded NL,
|
||||
" not split per line. stdout (via PTY) is delivered, but its exact
|
||||
" CR/LF shape depends on the PTY line discipline, so we only check that
|
||||
" 'out:3' appears somewhere in the received chunks.
|
||||
call job_stop(term_getjob(ptybuf))
|
||||
exe 'bwipe! ' .. ptybuf
|
||||
unlet g:Ch_msgs
|
||||
endfunc
|
||||
|
||||
|
||||
@@ -729,6 +729,8 @@ static char *(features[]) =
|
||||
|
||||
static int included_patches[] =
|
||||
{ /* Add new patch number below this line */
|
||||
/**/
|
||||
412,
|
||||
/**/
|
||||
411,
|
||||
/**/
|
||||
|
||||
Reference in New Issue
Block a user