Bram
I suggest to only focus on the job control feature, at least for now. The reason is that job control as it currently exists in Neovim, is enough to cover any asynchronicity requirements by plugins. Let me give some examples using Neovim API.
```vimL
" Assume function named 's:JobCallback' exists in this script
let s:opts = {
\ 'on_stdout': 's:JobCallback',
\ 'on_stderr': 's:JobCallback',
\ 'on_exit': 's:JobCallback'
\ }
" First, the common use case which is to spawn a long running
program to process some data and return to Vim(in this case a syntax checking
command from gcc):
let job1 = jobstart(['gcc', '-c', '-gnats', 'x.adb'], s:opts)
" A socket connection to a daemon or remote server
let job2 = jobstart(['nc', '127.0.0.1', '1234'], s:opts)
" Download something with curl, saving to a file and notifying the user when
" complete(in neovim API, passing a string as first argument to `jobstart` will
" pass the program to the shell, just like `system()`)
let job3 = jobstart('curl -L -o - http://tarball.url.tgz > out.tgz', s:opts)
" Listen on a socket, waiting for rpc commands
let job4 = jobstart(['nc', '4567'], s:opts)
" Do something after 5 seconds
let job5 = jobstart('sleep 5', s:opts)
```
As you can see, by exposing a job control API that talks with processes through stdin/stdout is enough to cover anything plugins might require. Even if you want to spawn a program connected to a new pty, it would be possible with an external process that forwards data to Vim!
The main reason neovim directly implements a rpc protocol in C is to support the remote plugin host API (mainly to create language bindings without modifying the core) and remote UIs. This would be impossible to do in pure vimscript due to performance reasons.
But Vim does not have these requirements and IMO implementing the suggested
channel feature will only complicate maintenance when a simple job control could do anything users require(let external processes worry about system-specific differences since we only care about stdio!).
Another complication to keep in mind if implementing a json rpc: Vim buffers can contain binary data, what would happen if one evals `getline(lnum)` in such a buffer? The result would probably be invalid json. You'd have to walk through every string returned by the json rpc engine in order to find and escape binary data.
As I said in a previous thread, the old job control patch has almost everything required to make this work, it just needs to be adapted to the current code and for a callback-based API(the old implementation uses autocmd events). It will also have to be adapted for windows, but there seem to be quite a few competent windows developers that contribute to Vim(mattn) and could probably port this without trouble.
Also, I hope you see benefit of following Neovim API: There are already few
popular plugins out there that use it with a good amount of success(neomake,
vim-go, vim-R...).
Just my two cents.
Hi Christian, I also appreciate you taking notice.
> It's also appreciated, that certain
> bugs, which have been found when developing Neovim have at least found
> their way back to Vim itself (although, that communication could be
> improved).
I'd like to improve communication and hope that my experience in adding
asynchronous events to Neovim helps the Vim community to avoid some of the
mistakes I made. I also would welcome the Vim community to engage more in Neovim
development, even if it is only to use it as a "test bench" for new features
that could be integrated back into Vim.
> I can only speak for myself, but I think it would be appreciated by the
> Vim/Neovim user community, if the API is at least standardized, so that
> not every plugin writer needs to either decide whether he wants to
> support Vim or Neovim or has to work around the particularities in both
> applications.
My thoughts exactly. In a previous
[message](https://groups.google.com/d/msg/vim_dev/_SbMTGshzVc/XZEXxaxDDQAJ) I
said:
> I will be glad to add a compatibility layer to Vim's channel feature if plugin
> authors start to use it, but unfortunately I can't do the same for this job
> control API as it will only make things harder for users.
Tha doesn't mean we are unwilling to discuss changes in Neovim job API. On the
contrary, we are open to standardizing it, but we need to carefully examine the
strengths and weakness of each interface before making harsh decisions.
The reason I said we would not follow Vim is because I honestly felt that the
job proposal for Vim was a regression when compared to what we have in
Neovim(and I know many plugin developers that use it will agree with me), but
could be convinced otherwise with arguments and examples.
> Since you seem to know both interfaces, would you be willing to
> contribute a patch, that will help develop a consistent interface as far
> as this seems feasible? Bram, since you are actually working on that
> part of the code, can we assume, that you would look into such a patch
> and help the community to have at least some kind of consistent
> interface for its users?
As I said in the previous post, yes, I'm willing to update my old patch to Vim,
possibly making changes to the API(should we reach an agreement on it). But I
need to wait Bram's response, as so far I've had pratically no feedback from
him(and one can only have so much faith).
> I really hope, that we can find a way, to work together and specify the
> details of those interfaces which would gain the users a lot. So let's
> not have personal prejudice get in our way! (I don't want to see more of
> those angry discussions, as its happening currently at reddit, so let's
> just make the best for all users).
Same here! It is not unreasonable to think that Neovim had a major impact in
Bram's decision to implement the channel/job features, and that thought also
gave a bump into my motivation for making Neovim even better. This confirms that
competition is only good for everyone, but there's no reason we can't do it in a
friendly way, don't you think? So, instead of wasting our time with angry
discussions, let's join forces and focus on the "real enemy": Emacs!(jk hahaha)
Before I finish, let me quote something LCD said in a previous message:
> I don't really understand what problem do channels solve, or where
is all this heading.
This says everything: we should focus on solving the problem rather than simply
adding new features without a proper plan. Since you were so friendly, I will
later post another message that expands my reasoning as to why I think Vim
should focus on job control(at least for now).
Thanks again Chris!
Thiago.
On Wednesday, February 10, 2016 at 1:14:21 PM UTC-3, Bram Moolenaar wrote:
> I wonder if you read my proposal wrong, or perhaps I didn't explain
> something properly. I read your response, and can only say that it's
> all covered in what I proposed.
>
> I think it may be caused by the notion that in NeoVim a job includes the
> communication with the job (over pipes), while in my proposal I separate
> them out.
I understood, but failed to see the advantage of the separate channel
abstraction in the context of job control. Please correct me if I missed
something, but this is how one spawns a job with a channel associated with
stdin/stdout:
```vim
let job = job_filter("cmd", {options})
let handle = job_getchannel(job)
call ch_setcallback(handle, "receive", "Callback")
call ch_setcallback(handle, "error", "Callback")
call ch_setcallback(handle, "close", "Callback")
" send some data to stdin
call ch_send(handle, "data")
```
Now compare with Neovim version:
```vim
let job = jobstart("cmd", {"on_stdout": "Callback", "on_exit": "Callback"})
" send some data to stdin
call jobsend(job, ["line1", "line2", ""])
```
As you can see, Neovim's version is shorter and covers the same use cases as the
one you proposed. It doesn't need a "close" or "error" event, the user can consider it was closed after the "on_exit" event, after which there will be no more "on_stdout" events. On top of that, I don't see how one can read stderr, but will
assume that you plan to add another function to get the stderr channel:
```vim
let job = job_filter("cmd", {options})
let handle = job_getchannel(job)
call ch_setcallback(handle, "receive", "Callback")
call ch_setcallback(handle, "error", "Callback")
call ch_setcallback(handle, "close", "Callback")
" send some data to stdin
call ch_send(handle, "data")
" listen for stderr
let stderr_handle = job_getstderrchannel(job)
call ch_setcallback(stderr_handle, "receive", "Callback")
call ch_setcallback(stderr_handle, "error", "Callback")
call ch_setcallback(stderr_handle, "close", "Callback")
```
That's even more verbose. Here's Neovim version of the same thing:
```vim
let job = jobstart("cmd", {"on_stdout": "Callback",
\ "on_stderr": "Callback", "on_exit": "Callback"})
" send some data to stdin
call jobsend(job, ["line1", "line2", ""])
```
Again, does the same with a much simpler API(note that omitting a callback from
the startup options simply redirects the handle to /dev/null).
I can see how the channel abstraction could be useful in a mainstream programming language with
support for custom types and libraries that operate in such types(eg: Node.js
Streams are everywhere in their echosystem) but in vimscript it only seems to be
an unnecessary complication.
Here are some related concerns:
- If the `job_getchannel` returns a shared handle for stdin/stdout, then what
happens if `ch_close` is called on that handle?(Will it have a separate
function for getting the stdin handle?)
- The API suggests that you get a job's channel after it was created, but
there's no way to redirect the stdio handles after the job is spawned. In this
case, you always have to connect pipes to the job's stdio and the user can
chose or not to attach callbacks, and there's no way the job will share stdout
with Vim(as it was suggested in a previous message).
- I see that channels can have an associated RPC protocol with them, but that
would not be useful for arbitrary processes that have no knowledge of Vim(eg:
compilers), which seem to be the main target of job control.
> So you can have a channel in JSON mode communication to a job
> with a pipe. I havent seen something like that in NeoVim. But then I
> haven't spent much time trying to understand what NeoVim is doing. Can
> NeoVim use msgpack on stdout?
Yes. In fact, Neovim also has the concept of channels, but it is unrelated to
job control only applies in the context of msgpack-rpc:
- `rpcrequest({channel}, {method}[, {args}...])` sends a msgpack-rpc request to
a channel and returns the response(the call only returns when the other end of
the channel sends a response)
- `rpcnotify({channel}, {event}[, {args}...])` sends a msgpack-rpc notification
These two functions can be called on any msgpack-rpc channel connected to
Neovim. At the moment, there are three ways a msgpack-rpc channel can be created
in Neovim:
- A process may connect to Neovim UNIX domain socket(or tcp address if it is
listening on one). The address is provided in the `$NVIM_LISTEN_ADRESS`
environment variable.
- Neovim may spawn a process that has a channel connected to its stdin/stdout.
This uses the job infrastructure internally, but the API exposed to vimscript
is separate(`rpcstart()`/`rpcstop()` are the msgpack-rpc equivalent to
`jobstart()`/`jobstop()`).
- Neovim may be spawned in embedding mode, where instead of sending terminal
escape codes to stdout(And parsing terminal input from stdin), it talks using
msgpack-rpc. This mode is used mainly external UIs and for its testing
infrastructure.
Currently we do not have a way to open a msgpack-rpc connection to an arbitrary
address, but it is only a matter of submitting a relatively small patch I made
a few months ago:
https://github.com/neovim/neovim/issues/3119#issuecomment-130906214
But this is not very relevant to the discussion, I have only talked about
msgpack-rpc to highlight how its API is completely independent from job
control(which can be seen as a more low-level/general-purpose asynchronicity
feature)
> I don't think that old patch adds much, now we already have working
> channels. Only the part with making channels work with
> stdin/stdout/stderr still needs to be done. And that code already
> exists in os_unix.c. MS-Windows is the most difficult (since sockets
> there work differently from file handles) and I thought your patch
> doesn't have that part.
Yes, I have very little Window programming experience, so I have only added an
UNIX version for review. But wasn't this the case for the channel and job
features? From what I could see in the mailing list patches, the initial
implementations of channel/jobs either partially worked on UNIX(with no Windows
support) or had quite a few bugs. The patch I sent was successfully tested
by a few people at the time.
I understand if you don't like my code and/or the patch, but please consider my
arguments and the possibility of following our API. As I said, there are already a
few popular plugins that successfully use it(some even helped us to develop/evolve the API).
> The whole thing I propose was to support what plugin writers were asking
> for. With future feature requests in mind, and combining with the
> existing code for netbeans.
>
> Problem with netbeans is that it uses a weird message format. I want to
> take that in the right direction, don't want more people to use the
> netbeans format. JSON appears to be the best choice. I know NeoVim
> uses msgpack, but I just don't like it.
I hope you understood that job control has nothing to do with msgpack in
Neovim(or in my old patch). It is just a simple way of implementing exactly what
plugin authors have been asking for quite some time: A simple and flexible way
to communicate with Vim asynchronously.
My optinion is:
1. ch_readraw/ch_sendraw should block if callback is not specified
2. ch_readraw/ch_sendraw should return empty string if callback is specified
This is my suggestion.
https://gist.github.com/mattn/d6198b38d9b18edcb2aa
ch_readraw, ch_sendraw, ch_sendjson take arguments dict can be taken callback.
If callback is specified, those functions return empty string. And callback in later. If callback is not specified, those function will block while reading data.
Now below's script is working on windows.
----------------------------------
function! Callback(id, msg)
echo a:msg
endfunction
let g:job = job_start("tail -f logfile")
let handle = job_getchannel(job)
call ch_readraw(handle, {"callback": "Callback"})
----------------------------------
- mattn
I updated patch.
Windows can't select file handle. So can't return OK always.