[vim/vim] revenge: Add ch_listen() function for socket server support (PR #19231)

20 views
Skip to first unread message

mattn

unread,
Jan 21, 2026, 5:09:31 AMJan 21
to vim/vim, Subscribed

Summary

#3639

This patch adds ch_listen() function to Vim, allowing scripts to create listening sockets and accept incoming network connections. This enables Vim to act as a simple network server for various use cases.

Changes

New Functions

  • ch_listen(address [, options]) - Listen on a socket and accept connections

    • Returns a channel representing the listening socket
    • Calls a callback function when a new client connects
    • Supports both IPv4 and Unix domain sockets
  • channel_listen_unix(path, callback) - Unix domain socket support

  • channel_listen_func(argvars) - Vim script interface

Implementation Details

  • Added ch_listen field to struct channel_S to mark listening sockets
  • Modified channel_read() to handle accept() on listening sockets
  • When a connection is accepted, a new channel is created and passed to the callback
  • The callback receives the accepted channel and client address

API Usage

function! OnConnect(channel, address)
    echomsg 'Client connected from: ' . a:address
    call ch_setoptions(a:channel, {
        \ 'callback': function('OnData'),
    \ })
endfunction

function! OnData(channel, data)
    echomsg 'Received: ' . a:data
endfunction

let server = ch_listen('127.0.0.1:8080', {
    \ 'callback': function('OnConnect'),
\ })

Use Cases

  • Simple HTTP server for serving static files
  • Network protocol implementation for testing
  • Local IPC between Vim and other processes
  • Learning network programming concepts

Testing

  • Implemented test case Test_listen() in test_channel.vim
  • Tested with multiple concurrent connections
  • Verified callback invocation and data transmission

Related Features

This complements the existing ch_open() function:

  • ch_open() - connect to a server
  • ch_listen() - accept connections as a server

Limitations

  • No SSL/TLS support
  • Single-threaded (blocks Vim event loop during processing)
  • No built-in HTTP parsing (simple callbacks only)

This is demonstration of HTTP server written in Vim script.

image.png (view on web)

You can view, comment on, or merge this pull request online at:

  https://github.com/vim/vim/pull/19231

Commit Summary

File Changes

(7 files)

Patch Links:


Reply to this email directly, view it on GitHub.
You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/pull/19231@github.com>

mattn

unread,
Jan 21, 2026, 5:21:40 AMJan 21
to vim/vim, Subscribed
mattn left a comment (vim/vim#19231)

I'll fix test fail in later.


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/pull/19231/c3777297345@github.com>

mattn

unread,
Jan 21, 2026, 5:46:04 AMJan 21
to vim/vim, Push

@mattn pushed 1 commit.


View it on GitHub or unsubscribe.
You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/pull/19231/before/61d4cdacc9907894bc7a17578bb7ced62a957a2d/after/2d56f553297f04b74ad87fbdc80d3be7aef256a5@github.com>

mattn

unread,
Jan 21, 2026, 5:53:09 AMJan 21
to vim/vim, Push

@mattn pushed 1 commit.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/pull/19231/before/2d56f553297f04b74ad87fbdc80d3be7aef256a5/after/bf360c48f8912c2cb76d114de6ede6eeb61f7131@github.com>

mattn

unread,
Jan 21, 2026, 6:05:08 AMJan 21
to vim/vim, Push

@mattn pushed 1 commit.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/pull/19231/before/bf360c48f8912c2cb76d114de6ede6eeb61f7131/after/28d6f7ab946fda985d283b600869a6610f2288d2@github.com>

mattn

unread,
Jan 21, 2026, 6:09:51 AMJan 21
to vim/vim, Push

@mattn pushed 1 commit.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/pull/19231/before/28d6f7ab946fda985d283b600869a6610f2288d2/after/2fe4734196247b8cfdc9f0577220501dd2a78106@github.com>

mattn

unread,
Jan 21, 2026, 6:20:55 AMJan 21
to vim/vim, Push

@mattn pushed 1 commit.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/pull/19231/before/2fe4734196247b8cfdc9f0577220501dd2a78106/after/72aaa64f881bcc277f8c701c173b49956737491d@github.com>

mattn

unread,
Jan 21, 2026, 6:32:43 AMJan 21
to vim/vim, Push

@mattn pushed 1 commit.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/pull/19231/before/72aaa64f881bcc277f8c701c173b49956737491d/after/d0ff4c9f48a17ff57422d54458b96dd3bf6a5f73@github.com>

mattn

unread,
Jan 21, 2026, 6:34:10 AMJan 21
to vim/vim, Push

@mattn pushed 1 commit.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/pull/19231/before/d0ff4c9f48a17ff57422d54458b96dd3bf6a5f73/after/b37fc0f202b595131b4aaf967ccd40e14628a8c5@github.com>

mattn

unread,
Jan 21, 2026, 7:02:08 AMJan 21
to vim/vim, Push

@mattn pushed 1 commit.

  • 58661c8 fix order in function list

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/pull/19231/before/b37fc0f202b595131b4aaf967ccd40e14628a8c5/after/58661c822dbf8c536e63352953f997b43d00fd89@github.com>

mattn

unread,
Jan 21, 2026, 7:10:25 AMJan 21
to vim/vim, Push

@mattn pushed 1 commit.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/pull/19231/before/58661c822dbf8c536e63352953f997b43d00fd89/after/c61af317834d683c5f5fc88698ee058252285649@github.com>

mattn

unread,
Jan 21, 2026, 7:51:53 AMJan 21
to vim/vim, Push

@mattn pushed 1 commit.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/pull/19231/before/c61af317834d683c5f5fc88698ee058252285649/after/95c71009883c2e9030e66e8f4617fb499efcf793@github.com>

Christian Brabandt

unread,
Jan 21, 2026, 2:32:48 PMJan 21
to vim/vim, Subscribed
chrisbra left a comment (vim/vim#19231)

Thanks that is nice. But I'll move this after the Vim 9.2 release.
Can a few people try it out? I'd also think that we should have a very simple practical example for this in the help.


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/pull/19231/c3780776946@github.com>

bfrg

unread,
Jan 24, 2026, 12:41:35 PMJan 24
to vim/vim, Subscribed
bfrg left a comment (vim/vim#19231)

It is not clear from the docs how ch_listen() differs from ch_open({address} [, {options}])? See :h channel-address for possible values of address.


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/pull/19231/c3795227881@github.com>

mattn

unread,
Jan 24, 2026, 2:01:37 PMJan 24
to vim/vim, Subscribed
mattn left a comment (vim/vim#19231)

@chrisbra This is simple practice example working as HTTP file server.

https://gist.github.com/mattn/3e691419c37fe7ce501dc37e2a1bb545


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/pull/19231/c3795360900@github.com>

Christian Brabandt

unread,
Jan 27, 2026, 2:12:19 PMJan 27
to vim/vim, Subscribed
chrisbra left a comment (vim/vim#19231)

Thanks.

ch_listen(address [, options]) - Listen on a socket and accept connections
Returns a channel representing the listening socket
Calls a callback function when a new client connects
Supports both IPv4 and Unix domain sockets
channel_listen_unix(path, callback) - Unix domain socket support

Why do we need channel_list_unix()? It looks like this is already supported via ch_listen() anyhow, no?


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/pull/19231/c3807014055@github.com>

Maxim Kim

unread,
Jan 27, 2026, 5:32:57 PMJan 27
to vim/vim, Subscribed
habamax left a comment (vim/vim#19231)

I wonder if this would help @yegappan lsp to support lsp servers without stdio, e.g. godot lsp server that works only over http?


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/pull/19231/c3807847925@github.com>

mattn

unread,
Jan 27, 2026, 8:16:58 PMJan 27
to vim/vim, Subscribed
mattn left a comment (vim/vim#19231)

Call tree is below. Should we rename channel_listen to channel_listen_tcp ?

f_ch_listen
  channel_listen_func
    channel_listen (TCP)
    channel_listen_unix (Unix Domain Socket)


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/pull/19231/c3808409138@github.com>

Christian Brabandt

unread,
Jan 28, 2026, 1:49:13 PMJan 28
to vim/vim, Subscribed
chrisbra left a comment (vim/vim#19231)

I think we should have one single ch_listen() function, that accepts either a TCP address or a Unix domain socket, similar to ch_open(). I don't think we need separate vim script functions for tcp and unix domain sockets.


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/pull/19231/c3813195283@github.com>

bfrg

unread,
Jan 28, 2026, 3:21:03 PMJan 28
to vim/vim, Subscribed
bfrg left a comment (vim/vim#19231)

I wonder if this would help @yegappan lsp to support lsp servers without stdio, e.g. godot lsp server that works only over http?

This is already possible, see :h channel-address. You need to open a channel with ch_open() and then pass that channel to job_start() using its channel option (explained under :h job-options [below :h job-term] ). There's also an example in the docs under :h channel-demo using a Python server.


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/pull/19231/c3813734749@github.com>

mattn

unread,
Jan 29, 2026, 7:35:11 AMJan 29
to vim/vim, Subscribed
mattn left a comment (vim/vim#19231)

@chrisbra
The current implementation already follows this approach. ch_listen() accepts both TCP addresses and Unix domain sockets using the same pattern as ch_open():

  • TCP: ch_listen('127.0.0.1:12345', ...)
  • Unix socket: ch_listen('unix:/path/to/socket', ...)

The channel_listen_func() detects the unix: prefix and internally calls either channel_listen_unix() or channel_listen() accordingly. There is no separate ch_listen_tcp() or ch_listen_unix() function exposed to Vim script.


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/pull/19231/c3817373440@github.com>

dkearns

unread,
Jan 29, 2026, 7:55:26 AMJan 29
to vim/vim, Subscribed
dkearns left a comment (vim/vim#19231)

I think we should have one single ch_listen() function, that accepts either a TCP address or a Unix domain socket, similar to ch_open(). I don't think we need separate vim script functions for tcp and unix domain sockets.

ch_open() is the only builtin Vim script function added, the others are implementation functions.


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/pull/19231/c3817513114@github.com>

Copilot

unread,
1:21 PM (2 hours ago) 1:21 PM
to vim/vim, Subscribed

@Copilot commented on this pull request.

Pull request overview

This PR adds server socket functionality to Vim by implementing the ch_listen() function, enabling Vim scripts to accept incoming network connections. This complements the existing ch_open() function, which allows Vim to act as a client.

Changes:

  • Added ch_listen() function with support for IPv4, IPv6, and Unix domain sockets
  • Implemented callback mechanism to handle incoming connections
  • Added test coverage in test_channel.vim

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/channel.c Core implementation of listen functionality and accept handling
src/structs.h Added ch_listen field to mark listening sockets
src/proto/channel.pro Function prototypes for new channel listening functions
src/evalfunc.c Registered ch_listen as a Vim script function
src/errors.h Added error messages for listen failures
src/testdir/test_channel.vim Added test case and fixed indentation throughout file
runtime/doc/usr_41.txt Updated function list and alphabetical ordering
runtime/doc/channel.txt Added documentation for ch_listen()
runtime/doc/builtin.txt Added ch_listen() to built-in function list

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


In src/channel.c:

> +	    else
+		sprintf((char *)namebuf, "unix:%s",
+		    ((struct sockaddr_un*)&client)->sun_path);

For Unix domain sockets, the client address structure populated by accept() does not contain a meaningful path in sun_path. Unix domain sockets only provide the path for the listening socket, not for accepted connections. This will likely result in garbage or empty data being passed to the callback. Consider passing an empty string or a generic identifier like 'unix:anonymous' for Unix domain socket clients.

⬇️ Suggested change
-	    else
-		sprintf((char *)namebuf, "unix:%s",
-		    ((struct sockaddr_un*)&client)->sun_path);
+	    else if (client.ss_family == AF_UNIX)
+		sprintf((char *)namebuf, "unix:anonymous");
+	    else
+		sprintf((char *)namebuf, "unknown");

In src/channel.c:

> +	    if (client.ss_family == AF_INET)
+		sprintf((char *)namebuf, "%s:%d",
+		    inet_ntoa(((struct sockaddr_in*)&client)->sin_addr),
+		    ntohs(((struct sockaddr_in*)&client)->sin_port));
+	    else
+		sprintf((char *)namebuf, "unix:%s",
+		    ((struct sockaddr_un*)&client)->sun_path);

The else branch assumes all non-IPv4 connections are Unix domain sockets, but the code supports IPv6 addresses (as shown in channel_listen_func). When an IPv6 client connects, this will incorrectly try to interpret the address as a Unix socket path. Add explicit handling for AF_INET6 using inet_ntop() to format IPv6 addresses.


In src/channel.c:

> +		sprintf((char *)namebuf, "%s:%d",
+		    inet_ntoa(((struct sockaddr_in*)&client)->sin_addr),
+		    ntohs(((struct sockaddr_in*)&client)->sin_port));
+	    else
+		sprintf((char *)namebuf, "unix:%s",

Using sprintf without bounds checking creates a buffer overflow risk. The namebuf is declared as 256 bytes, but sprintf doesn't verify this limit. Use snprintf instead to ensure the formatted string doesn't exceed the buffer size.

⬇️ Suggested change
-		sprintf((char *)namebuf, "%s:%d",
-		    inet_ntoa(((struct sockaddr_in*)&client)->sin_addr),
-		    ntohs(((struct sockaddr_in*)&client)->sin_port));
-	    else
-		sprintf((char *)namebuf, "unix:%s",
+		snprintf((char *)namebuf, sizeof(namebuf), "%s:%d",
+		    inet_ntoa(((struct sockaddr_in*)&client)->sin_addr),
+		    ntohs(((struct sockaddr_in*)&client)->sin_port));
+	    else
+		snprintf((char *)namebuf, sizeof(namebuf), "unix:%s",

In src/testdir/test_channel.vim:

> +  func s:test_listen_accept(ch, addr)
+      let g:server_received_addr = a:addr
+      let g:server_received_msg = ch_readraw(a:ch)
+  endfunction

The test doesn't verify that g:server_received_addr contains the expected client address. Add an assertion like call assert_match('127.0.0.1:', g:server_received_addr) to ensure the callback receives correct address information.


In src/testdir/test_channel.vim:

> +  func Test_listen()
+      call ch_log('Test_listen()')
+      let server = ch_listen('127.0.0.1:12345', {'callback': function('s:test_listen_accept')})
+      if ch_status(server) == 'fail'
+          call assert_report("Can't listen channel")
+          return
+      endif

The test doesn't verify error handling for invalid addresses or port conflicts. Consider adding test cases for scenarios like invalid port numbers, ports already in use, or malformed addresses to ensure proper error reporting.


Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/pull/19231/review/3827672792@github.com>

Reply all
Reply to author
Forward
0 new messages