-*channel.txt* For Vim version 9.2. Last change: 2026 Apr 28
+*channel.txt* For Vim version 9.2. Last change: 2026 Apr 29
VIM REFERENCE MANUAL by Bram Moolenaar
"js" - Use JS (JavaScript) encoding, more efficient than JSON.
"nl" - Use messages that end in a NL character
"raw" - Use raw messages
+ "blob" - Use raw messages and pass callback data as a |Blob|
"lsp" - Use language server protocol encoding
"dap" - Use debug adapter protocol encoding
*channel-callback* *E921*
excluding the NL.
When "mode" is "raw" the "msg" argument is the whole message
as a string.
+ When "mode" is "blob" the "msg" argument is the whole message
+ as a |Blob|.
For all callbacks: Use |function()| to bind it to arguments
and/or a Dictionary. Or use the form "dict.function" to bind
height. Also added the "scrollbar" sub-option to 'tabpanelopt'.
- Added the "noinsert" value to the 'wildmode' option for symmetry with the
'completeopt' option
+- Channel can handle |Blob| messages |channel-open-options|.
Platform specific ~
-----------------
may_invoke_callback(channel_T *channel, ch_part_T part)
{
char_u *msg = NULL;
+ blob_T *blob = NULL;
typval_T *listtv = NULL;
typval_T argv[CH_JSON_MAX_ARGS];
int seq_nr = -1;
buf_T *buffer = NULL;
char_u *p;
int called_otc; // one time callbackup
+ int raw_len = 0;
if (channel->ch_nb_close_cb != NULL)
// this channel is handled elsewhere (netbeans)
{
// For a raw channel we don't know where the message ends, just
// get everything we have.
- // Convert NUL to NL, the internal representation.
- msg = channel_get_all(channel, part, NULL);
+ raw_len = 0;
+ msg = channel_get_all(channel, part,
+ ch_mode == CH_MODE_BLOB ? &raw_len : NULL);
}
if (msg == NULL)
return FALSE; // out of memory (and avoids Coverity warning)
- argv[1].v_type = VAR_STRING;
- argv[1].vval.v_string = msg;
+ if (ch_mode == CH_MODE_BLOB)
+ {
+ blob = blob_alloc();
+ if (blob == NULL)
+ {
+ vim_free(msg);
+ return FALSE;
+ }
+ if (ga_grow(&blob->bv_ga, raw_len) == FAIL)
+ {
+ blob_free(blob);
+ vim_free(msg);
+ return FALSE;
+ }
+ if (raw_len > 0)
+ {
+ mch_memmove(blob->bv_ga.ga_data, msg, (size_t)raw_len);
+ blob->bv_ga.ga_len = raw_len;
+ }
+ argv[1].v_type = VAR_BLOB;
+ argv[1].vval.v_blob = blob;
+ ++blob->bv_refcount;
+ }
+ else
+ {
+ argv[1].v_type = VAR_STRING;
+ argv[1].vval.v_string = msg;
+ }
}
called_otc = FALSE;
if (listtv != NULL)
free_tv(listtv);
+ if (blob != NULL)
+ blob_unref(blob);
vim_free(msg);
return TRUE;
case CH_MODE_RAW:
STR_LITERAL_SET(s, "RAW");
break;
+ case CH_MODE_BLOB:
+ STR_LITERAL_SET(s, "BLOB");
+ break;
case CH_MODE_JSON:
STR_LITERAL_SET(s, "JSON");
break;
readq_T *node;
ch_log(channel, "Blocking %s read, timeout: %d msec",
- mode == CH_MODE_RAW ? "RAW" : "NL", timeout);
+ mode == CH_MODE_RAW ? "RAW"
+ : mode == CH_MODE_BLOB ? "BLOB" : "NL", timeout);
while (TRUE)
{
node = channel_peek(channel, part);
if (node != NULL)
{
- if (mode == CH_MODE_RAW || (mode == CH_MODE_NL
+ if (mode == CH_MODE_RAW || mode == CH_MODE_BLOB
+ || (mode == CH_MODE_NL
&& channel_first_nl(node) != NULL))
// got a complete message
break;
}
// We have a complete message now.
- if (mode == CH_MODE_RAW || outlen != NULL)
+ if (mode == CH_MODE_RAW || mode == CH_MODE_BLOB || outlen != NULL)
{
msg = channel_get_all(channel, part, outlen);
}
}
}
if (ch_log_active())
- ch_log(channel, "Returning %d bytes", (int)STRLEN(msg));
+ ch_log(channel, "Returning %d bytes",
+ outlen != NULL ? *outlen : (int)STRLEN(msg));
return msg;
}
if (opt.jo_set & JO_TIMEOUT)
timeout = opt.jo_timeout;
- if (blob)
+ if (blob || mode == CH_MODE_BLOB)
{
int outlen = 0;
char_u *p = channel_read_block(channel, part,
part_send = channel_part_send(channel);
ch_mode = channel_get_mode(channel, part_send);
- if (ch_mode == CH_MODE_RAW || ch_mode == CH_MODE_NL)
+ if (ch_mode == CH_MODE_RAW || ch_mode == CH_MODE_BLOB
+ || ch_mode == CH_MODE_NL)
{
- emsg(_(e_cannot_use_evalexpr_sendexpr_with_raw_or_nl_channel));
+ emsg(_(e_cannot_use_evalexpr_sendexpr_with_raw_nl_or_blob_channel));
return;
}
INIT(= N_("E910: Using a Job as a Number"));
EXTERN char e_using_job_as_float[]
INIT(= N_("E911: Using a Job as a Float"));
-EXTERN char e_cannot_use_evalexpr_sendexpr_with_raw_or_nl_channel[]
- INIT(= N_("E912: Cannot use ch_evalexpr()/ch_sendexpr() with a raw or nl channel"));
+EXTERN char e_cannot_use_evalexpr_sendexpr_with_raw_nl_or_blob_channel[]
+ INIT(= N_("E912: Cannot use ch_evalexpr()/ch_sendexpr() with a raw, nl or blob channel"));
EXTERN char e_using_channel_as_number[]
INIT(= N_("E913: Using a Channel as a Number"));
EXTERN char e_using_channel_as_float[]
*modep = CH_MODE_NL;
else if (STRCMP(val, "raw") == 0)
*modep = CH_MODE_RAW;
+ else if (STRCMP(val, "blob") == 0)
+ *modep = CH_MODE_BLOB;
else if (STRCMP(val, "js") == 0)
*modep = CH_MODE_JS;
else if (STRCMP(val, "json") == 0)
msgstr ""
"Project-Id-Version: Vim\n"
"Report-Msgid-Bugs-To: vim-dev@vim.org\n"
-"POT-Creation-Date: 2026-04-27 18:39+0000\n"
+"POT-Creation-Date: 2026-04-29 19:49+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
msgid "E911: Using a Job as a Float"
msgstr ""
-msgid "E912: Cannot use ch_evalexpr()/ch_sendexpr() with a raw or nl channel"
+msgid ""
+"E912: Cannot use ch_evalexpr()/ch_sendexpr() with a raw, nl or blob channel"
msgstr ""
msgid "E913: Using a Channel as a Number"
{
CH_MODE_NL = 0,
CH_MODE_RAW,
+ CH_MODE_BLOB,
CH_MODE_JSON,
CH_MODE_JS,
CH_MODE_LSP, // Language Server Protocol (http + json)
endtry
endfunc
+func Test_out_cb_blob_mode()
+ let g:Ch_blob_bytes = []
+ func OutBlobCb(chan, msg)
+ call assert_equal(v:t_blob, type(a:msg))
+ let g:Ch_blob_bytes += blob2list(a:msg)
+ endfunc
+
+ let cmd = [s:python, '-c',
+ \ 'import sys,time;'
+ \ .. 'sys.stdout.buffer.write(bytes([0, 1, 2, 10, 255]));'
+ \ .. 'sys.stdout.flush();'
+ \ .. 'time.sleep(0.1)']
+ let job = job_start(cmd, #{
+ \ out_mode: 'blob',
+ \ out_cb: 'OutBlobCb',
+ \ })
+ try
+ call WaitForAssert({-> assert_equal([0, 1, 2, 10, 255], g:Ch_blob_bytes)})
+ finally
+ call job_stop(job)
+ delfunc OutBlobCb
+ unlet g:Ch_blob_bytes
+ endtry
+endfunc
+
+func Test_pty_out_cb_blob_mode()
+ CheckUnix
+
+ let g:Ch_blob_bytes = []
+ func PtyBlobCb(chan, msg)
+ call assert_equal(v:t_blob, type(a:msg))
+ let g:Ch_blob_bytes += blob2list(a:msg)
+ endfunc
+
+ " Put the pty in raw mode so the line discipline does not translate LF
+ " to CRLF or strip NUL bytes, then write bytes that include NULs on
+ " both sides of an embedded LF.
+ let cmd = [s:python, '-c',
+ \ 'import os,sys,time;'
+ \ .. 'os.system("stty raw -echo");'
+ \ .. 'sys.stdout.buffer.write(bytes([65, 0, 66, 10, 67, 0, 68]));'
+ \ .. 'sys.stdout.flush();'
+ \ .. 'time.sleep(0.1)']
+ let job = job_start(cmd, #{
+ \ pty: 1,
+ \ out_mode: 'blob',
+ \ out_cb: 'PtyBlobCb',
+ \ })
+ try
+ call WaitForAssert({-> assert_equal(
+ \ [65, 0, 66, 10, 67, 0, 68], g:Ch_blob_bytes)})
+ finally
+ call job_stop(job)
+ delfunc PtyBlobCb
+ unlet g:Ch_blob_bytes
+ endtry
+endfunc
+
func Test_close_and_exit_cb()
let g:test_is_flaky = 1
let g:retdict = {'ret': {}}
static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 420,
/**/
419,
/**/