-*builtin.txt* For Vim version 9.2. Last change: 2026 Apr 10
+*builtin.txt* For Vim version 9.2. Last change: 2026 Apr 14
VIM REFERENCE MANUAL by Bram Moolenaar
ch_getjob({channel}) Job get the Job of {channel}
ch_info({handle}) Dict info about channel {handle}
ch_listen({address} [, {options}])
- Channel listen on {address}
+ Channel listen on {address} - port on loopback
+ or UNIX domain socket
ch_log({msg} [, {handle}]) none write {msg} in the channel log file
ch_logfile({fname} [, {mode}]) none start logging channel activity
ch_open({address} [, {options}])
-*channel.txt* For Vim version 9.2. Last change: 2026 Apr 06
+*channel.txt* For Vim version 9.2. Last change: 2026 Apr 14
VIM REFERENCE MANUAL by Bram Moolenaar
endfunc
" Start listening on port 8765
- let server = ch_listen('localhost:8765', {"callback": "OnAccept"})
+ let server = ch_listen('8765', {"callback": "OnAccept"})
From another Vim instance (or any program) you can connect to it: >
let channel = ch_open('localhost:8765')
"status" "open", "buffered" or "closed", like
ch_status()
When opened with ch_open():
- "hostname" the hostname of the address
- "port" the port of the address
+ "port" the port on loopback
"path" the path of the Unix-domain socket
"sock_status" "open" or "closed"
"sock_mode" "NL", "RAW", "JSON" or "JS"
Return type: dict<any>
ch_listen({address} [, {options}]) *E1573* *E1574* *ch_listen()*
- Listen on {address} for incoming channel connections.
- This creates a server-side channel, unlike |ch_open()|
- which connects to an existing server.
+ Listen on {address} - port on loopback or UNIX domain socket
+ for incoming channel connections. This creates a server-side
+ channel, unlike |ch_open()|which connects to an existing server.
Returns a Channel. Use |ch_status()| to check for failure.
{address} is a String, see |channel-address| for the possible
- accepted forms, however binding to all interfaces is not
- allowed for security reasons.
+ accepted forms, however in case of TCP sockets it allows to
+ set only a port and binds to loopback address for
+ security reasons.
Note: IPv6 is not yet supported.
If {options} is given it must be a |Dictionary|.
channel_T *
channel_listen_func(typval_T *argvars)
{
- char_u *address;
- char_u *p;
+ char_u *arg;
char *rest;
int port;
int is_unix = FALSE;
channel_T *channel = NULL;
if (in_vim9script()
- && (check_for_string_arg(argvars, 0) == FAIL
- || check_for_opt_dict_arg(argvars, 1) == FAIL))
- return NULL;
-
- address = tv_get_string(&argvars[0]);
- if (argvars[1].v_type != VAR_UNKNOWN
- && check_for_nonnull_dict_arg(argvars, 1) == FAIL)
+ && check_for_string_arg(argvars, 0) == FAIL)
return NULL;
- if (*address == NUL)
+ arg = tv_get_string(&argvars[0]);
+ if (*arg == NUL)
{
- semsg(_(e_invalid_argument_str), address);
+ semsg(_(e_invalid_argument_str), arg);
return NULL;
}
- if (!STRNCMP(address, "unix:", 5))
+ if (!STRNCMP(arg, "unix:", 5))
{
is_unix = TRUE;
- address += 5;
+ arg += 5;
port = 0;
}
- else if (*address == '[')
- {
- // ipv6 address
- p = vim_strchr(address + 1, ']');
- if (p == NULL || *++p != ':')
- {
- semsg(_(e_invalid_argument_str), address);
- return NULL;
- }
- port = strtol((char *)(p + 1), &rest, 10);
- if (port < 0 || port >= 65536 || *rest != NUL)
- {
- semsg(_(e_invalid_argument_str), address);
- return NULL;
- }
- // strip '[' and ']'
- ++address;
- *(p - 1) = NUL;
- }
else
{
- // ipv4 address
- p = vim_strchr(address, ':');
- if (p == NULL)
- {
- semsg(_(e_invalid_argument_str), address);
- return NULL;
- }
- port = strtol((char *)(p + 1), &rest, 10);
+ port = strtol((char *)(arg), &rest, 10);
if (port < 0 || port >= 65536 || *rest != NUL)
{
- semsg(_(e_invalid_argument_str), address);
+ semsg(_(e_invalid_argument_str), arg);
return NULL;
}
- *p = NUL;
+ *arg = NUL;
}
// parse options
}
if (is_unix)
- channel = channel_listen_unix((char *)address, NULL);
+ channel = channel_listen_unix((char *)arg, NULL);
else
- channel = channel_listen((char *)address, port, NULL);
+ channel = channel_listen(port, NULL);
if (channel != NULL)
{
opt.jo_set = JO_ALL;
*/
channel_T *
channel_listen(
- char *hostname,
int port_in,
void (*nb_close_cb)(void))
{
int val = 1;
channel_T *channel;
- if (hostname == NULL || *hostname == NUL)
- {
- ch_error(NULL, "Hostname/address not defined.");
- return NULL;
- }
-
#ifdef MSWIN
channel_init_winsock();
#endif
vim_memset((char *)&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(port_in);
-
-#ifdef FEAT_IPV6
- struct addrinfo hints;
- struct addrinfo *res = NULL;
- int err;
-
- CLEAR_FIELD(hints);
- hints.ai_family = AF_INET;
- hints.ai_socktype = SOCK_STREAM;
- if ((err = getaddrinfo(hostname, NULL, &hints, &res)) != 0)
- {
- ch_error(channel, "in getaddrinfo() in channel_listen()");
- PERROR(_(e_gethostbyname_in_channel_listen));
- channel_free(channel);
- return NULL;
- }
- memcpy(&server.sin_addr,
- &((struct sockaddr_in *)res->ai_addr)->sin_addr,
- sizeof(server.sin_addr));
- freeaddrinfo(res);
-#else
- if ((host = gethostbyname(hostname)) == NULL)
- {
- ch_error(channel, "in gethostbyname() in channel_listen()");
- PERROR(_(e_gethostbyname_in_channel_listen));
- channel_free(channel);
- return NULL;
- }
-
- char *p;
-
- // When using host->h_addr_list[0] directly ubsan warns for it to
- // not be aligned. First copy the pointer to avoid that.
- memcpy(&p, &host->h_addr_list[0], sizeof(p));
- memcpy((char *)&server.sin_addr, p, host->h_length);
-#endif
+ server.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
sd = socket(AF_INET, SOCK_STREAM, 0);
if (sd == -1)
channel->ch_listen = TRUE;
channel->CH_SOCK_FD = (sock_T)sd;
channel->ch_nb_close_cb = nb_close_cb;
- channel->ch_hostname = (char *)vim_strsave((char_u *)hostname);
+ channel->ch_hostname = (char *)vim_strsave((char_u *)"");
channel->ch_port = port_in;
channel->ch_to_be_closed |= (1U << PART_SOCK);
if (channel->ch_hostname != NULL)
{
if (channel->ch_port)
- {
- dict_add_string(dict, "hostname", (char_u *)channel->ch_hostname);
dict_add_number(dict, "port", channel->ch_port);
- }
else
// Unix-domain socket.
dict_add_string(dict, "path", (char_u *)channel->ch_hostname);
void channel_gui_register_all(void);
channel_T *channel_open(const char *hostname, int port, int waittime, void (*nb_close_cb)(void));
channel_T *channel_listen_func(typval_T *argvars);
-channel_T *channel_listen(char *hostname, int port_in, void (*nb_close_cb)(void));
+channel_T *channel_listen(int port_in, void (*nb_close_cb)(void));
channel_T *channel_listen_unix(char *path, void (*nb_close_cb)(void));
void ch_close_part(channel_T *channel, ch_part_T part);
void channel_set_pipes(channel_T *channel, sock_T in, sock_T out, sock_T err);
func Test_listen()
call ch_log('Test_listen()')
- let server = ch_listen('127.0.0.1:12345', {'callback': function('s:test_listen_accept')})
+ let server = ch_listen('12345', {'callback': function('s:test_listen_accept')})
if ch_status(server) == 'fail'
call assert_report("Can't listen channel")
return
call assert_match('127.0.0.1:', g:server_received_addr)
endfunc
-func Test_listen_invalid_address()
- call ch_log('Test_listen_invalid_address()')
-
- " empty address
- call assert_fails("call ch_listen('')", 'E475:')
+func Test_listen_invalid_port()
+ call ch_log('Test_listen_invalid_port()')
" missing port
- call assert_fails("call ch_listen('localhost')", 'E475:')
+ call assert_fails("call ch_listen('')", 'E475:')
" port number too large
- call assert_fails("call ch_listen('localhost:99999')", 'E475:')
+ call assert_fails("call ch_listen('99999')", 'E475:')
" port number zero should let the OS assign an available port
- let ch = ch_listen('localhost:0')
+ let ch = ch_listen('0')
call assert_equal('open', ch_status(ch))
call assert_notequal(0, ch_info(ch).port)
call ch_close(ch)
" port number negative
- call assert_fails("call ch_listen('localhost:-1')", 'E475:')
-
- " invalid ipv6 format (missing closing bracket)
- call assert_fails("call ch_listen('[::1:8765')", 'E475:')
-
- " invalid ipv6 format (missing port)
- call assert_fails("call ch_listen('[::1]')", 'E475:')
+ call assert_fails("call ch_listen('-1')", 'E475:')
+endfunc
- " TODO: IPv6 should actually work
- call assert_fails("call ch_listen('[::1]:9999')", 'E1574:')
+func Test_listen_info_no_hostname()
+ let ch = ch_listen('0')
+ call assert_fails("call ch_info(ch).hostname", 'E716:')
+ call ch_close(ch)
endfunc
func Test_channel_lsp_mode()
static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 344,
/**/
343,
/**/