patch 9.2.0412: channel: term_start() out_cb/err_cb no longer deliver raw chunks

Commit: 
https://github.com/vim/vim/commit/41c3379bdf944dcee7f64b8e95094c03e2dce968
Author: Hirohito Higashi <[email protected]>
Date:   Tue Apr 28 21:03:12 2026 +0000

    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) <[email protected]>
    Co-Authored-By: Yasuhiro Matsumoto <[email protected]>
    Signed-off-by: Christian Brabandt <[email protected]>

diff --git a/runtime/doc/channel.txt b/runtime/doc/channel.txt
index ce0ceb45b..26cdc6a00 100644
--- a/runtime/doc/channel.txt
+++ b/runtime/doc/channel.txt
@@ -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
diff --git a/runtime/doc/terminal.txt b/runtime/doc/terminal.txt
index 6c9430dc8..01fd88ca6 100644
--- a/runtime/doc/terminal.txt
+++ b/runtime/doc/terminal.txt
@@ -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, "
")
+                           echom substitute(line, '
$', '', '')
+                         endfor
+                       endfunc
+<
                There are extra options:
                   "term_name"       name to use for the buffer name, instead
                                     of the command name.
diff --git a/src/channel.c b/src/channel.c
index dc8645b06..a0b7b953e 100644
--- a/src/channel.c
+++ b/src/channel.c
@@ -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);
            }
        }
     }
diff --git a/src/testdir/test_channel.vim b/src/testdir/test_channel.vim
index ceb553263..abdaed0dc 100644
--- a/src/testdir/test_channel.vim
+++ b/src/testdir/test_channel.vim
@@ -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
err:2
") >= 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
 
diff --git a/src/version.c b/src/version.c
index d3ed29922..778a227b7 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 */
+/**/
+    412,
 /**/
     411,
 /**/

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion visit 
https://groups.google.com/d/msgid/vim_dev/E1wHplo-006Gsr-7n%40256bit.org.

Raspunde prin e-mail lui