Channels¶
The introduction of asynchronous I/O support in Vim 8.0, with channels and jobs created many new possibilities for extending Vim with plug-ins. For example, interfacing to a third party code quality server.
It also introduced new Vim script types that (currently) have no Python equivalents. Also, it is advantageous to be able to use callback functions to receive data in order to keep your plug-in responsive. This makes direct use from Python non-trivial so VPE provides extensions to make things easier.
Status¶
VPE’s support for channels is not yet particularly mature. However is is functional and seems stable, and you should not feel discouraged from using the current API.
There are a number of areas that zero or minimal support.
Vim function/feature |
Status |
---|---|
Jobs |
No support. For quite a few use cases it is arguably better to use Python subprocess module. |
ch_readblob |
Currently there is no way to read pure binary data. Even
you use a |
ch_getbufnr |
VPE does not really attempt to support associating buffers with channels. |
ch_logfile |
Not supported, although ch_log is. This should really be fixed. |
ch_readraw |
Not implemented, but it is arguably better to use on_message to handle input. But I must admit, I have not really given this much thought. |
I am happy to receive suggestions about the best way to support the missing features at https://github.com/paul-ollis/vim-vpe/issues.
The Channel class¶
Introduction¶
VPE provides the vpe.channels.Channel
cnd related classes as the basis for socket and pipe
I/O. A vpe.channels.Channel
provides a Pythonic, object oriented, interface to the various
Vim ch_...
functions. vpe.channels.Channel
is a base class from which four API classes
are ultimately derived.
Has an underlying Vim channel with mode == ‘raw’. |
|
Has an underlying Vim channel with mode == ‘nl’. |
|
Has an underlying Vim channel with mode == ‘js’. |
|
Has an underlying Vim channel with mode == ‘json’. |
Python code should use one of the above four classes.
Here is an approximate mapping from vim’s functions to vpe.channels.Channel
methods.
Vim function |
|
This is invoked when a |
|
vpe.Channel.close_in` |
|
vpe.Channel.close` |
|
vpe.Channel.read` |
|
Not (yet) implemented, but the data delivered by vpe.Channel.on_message` is the same as ch_readraw provides. |
|
ch_readblob() |
Not (yet) implemented. |
vpe.Channel.send` |
|
vpe.SyncChannel.evalexpr`. Only available for |
|
vpe.SyncChannel.sendexpr`. Only available for |
|
Not (yet) supported. |
|
vpe.Channel.getbufnr` |
|
Not (yet) supported |
|
vpe.Channel.info`. Note that the id, port and sock_timeout values are integers; not strings. |
|
Not (yet) implemented. |
|
vpe.Channel.log` |
|
The socket timeout can be set using vpe.Channel.settimeout`. The mode cannot be changed and the callback cannot be explicitly set. |
|
vpe.Channel.status` |
Channel paradigm¶
The channel classes are intended to be used by inheritence. Below is some code showing the basic pattern.
from vpe import channels
class ServerChannel(channels.JsonChannel):
"""Interface to the server program."""
def on_connect(self):
"""Handle a new outgoing connection."""
def on_message(self, message: Any) -> None:
"""Handle a new incoming message.
:message: The incoming, JSON-encoded message.
"""
ch = ServerChannel('localhost:6789')
When the ServerChannel
is created, a connection attempt is immediately made
(internally ch_open() is invoked). If the connection attempt succeeds then the
vpe.Channel.on_connect` method is invoked in the very near future (via vpe.call_soon`).
Normally you use your vpe.Channel.on_connect` method to perform any operations that need to
happen immediately upon a successful connection.
The vpe.Channel.on_message` method is invoked whenever Vim’s channel’s input buffer
contains a complete message or, for a vpe.channels.RawChannel
, whenever any data is
received.
Messages are typically sent using the vpe.SyncChannel.sendexpr` method. Unformatted data can be
sent using vpe.Channel.send`, but this will more typically be used with vpe.channels.RawChannel
instances.
If the initial connection attempt does time out, the attempt can be retried using the vpe.Channel.connect` method. Sometimes using a timer to keep retrying is a good approach.
class ServerChannel(channels.JsonChannel):
"""Interface to the server program."""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.conn_timer = vpe.Timer(
ms=1000, func=self.connect, repeat=-1, pass_timer=False)
def on_connect(self):
"""Handle a new outgoing connection."""
self.conn_timer.stop()
...
In the above example, the timer is set to fire every second forever. The vpe.Channel.connect` method can be invoked directly by the timer - vpe.Channel.connect` does nothing if the connection is already active. The vpe.Channel.on_connect` method is a good place to stop the timer running.
A vpe.channels.Channel
always sets a the channel-callback and close_cb options on channel. So
incoming messages are handled asynchronously, invoking the vpe.Channel.on_message` method.
The close_cb option is used by the vpe.channels.Channel
to properly clean things up. As
part of this cleanup the methods vpe.Channel.on_close` and vpe.Channel.close` are invoked in that
order. You can override vpe.Channel.on_close` to perform any additional clean up; the
base class implementation does nothing.
Channel functions¶
The vpe.channels
module also provides a set of very thin wrapper around most of
the Vim ‘ch_…’ functions. All those listed in the mapping
table above are provided, except for ch_canread(),
ch_readraw(), ch_readblob(), ch_open() and ch_logfile(). These wrappers are
provided mainly for use by the vpe.channels.Channel
class, but you can use them in your own
code.
from vpe import channels
class ServerChannel(channels.JsonChannel):
...
ch = ServerChannel('localhost:6789')
info = channels.ch_info(ch.vch)
The functions takes the same arguments as the built-in Vim functions except that a Channel’s vpe.Channel.vch` attribute must be used in cases where the Vim function expects a handle.
If you find it necessary to use any of these functions, please raise an issue
at https://github.com/paul-ollis/vim-vpe/issues, explaining why you could not
achieve you aim using only vpe.channels.Channel
class methods.