neovim module
Python client for Nvim.
Client library for talking with Nvim processes via it's msgpack-rpc API.
"""Python client for Nvim.
Client library for talking with Nvim processes via it's msgpack-rpc API.
"""
import logging
import os
import sys
from .api import Nvim
from .compat import IS_PYTHON3
from .msgpack_rpc import (ErrorResponse, child_session, socket_session,
stdio_session, tcp_session)
from .plugin import (Host, autocmd, command, decode, encoding, function,
plugin, rpc_export, shutdown_hook)
from .util import Version
__all__ = ('tcp_session', 'socket_session', 'stdio_session', 'child_session',
'start_host', 'autocmd', 'command', 'encoding', 'decode',
'function', 'plugin', 'rpc_export', 'Host', 'Nvim', 'VERSION',
'shutdown_hook', 'attach', 'setup_logging', 'ErrorResponse')
VERSION = Version(major=0, minor=1, patch=13, prerelease="")
def start_host(session=None):
"""Promote the current process into python plugin host for Nvim.
Start msgpack-rpc event loop for `session`, listening for Nvim requests
and notifications. It registers Nvim commands for loading/unloading
python plugins.
The sys.stdout and sys.stderr streams are redirected to Nvim through
`session`. That means print statements probably won't work as expected
while this function doesn't return.
This function is normally called at program startup and could have been
defined as a separate executable. It is exposed as a library function for
testing purposes only.
"""
plugins = []
for arg in sys.argv:
_, ext = os.path.splitext(arg)
if ext == '.py':
plugins.append(arg)
elif os.path.isdir(arg):
init = os.path.join(arg, '__init__.py')
if os.path.isfile(init):
plugins.append(arg)
# This is a special case to support the old workaround of
# adding an empty .py file to make a package directory
# visible, and it should be removed soon.
for path in list(plugins):
dup = path + ".py"
if os.path.isdir(path) and dup in plugins:
plugins.remove(dup)
# Special case: the legacy scripthost receives a single relative filename
# while the rplugin host will receive absolute paths.
if plugins == ["script_host.py"]:
name = "script"
else:
name = "rplugin"
setup_logging(name)
if not session:
session = stdio_session()
nvim = Nvim.from_session(session)
if nvim.version.api_level < 1:
sys.stderr.write("This version of the neovim python package "
"requires nvim 0.1.6 or later")
sys.exit(1)
host = Host(nvim)
host.start(plugins)
def attach(session_type, address=None, port=None,
path=None, argv=None, decode=None):
"""Provide a nicer interface to create python api sessions.
Previous machinery to create python api sessions is still there. This only
creates a facade function to make things easier for the most usual cases.
Thus, instead of:
from neovim import socket_session, Nvim
session = tcp_session(address=, port=)
nvim = Nvim.from_session(session)
You can now do:
from neovim import attach
nvim = attach('tcp', address=, port=)
And also:
nvim = attach('socket', path=)
nvim = attach('child', argv=)
nvim = attach('stdio')
"""
session = (tcp_session(address, port) if session_type == 'tcp' else
socket_session(path) if session_type == 'socket' else
stdio_session() if session_type == 'stdio' else
child_session(argv) if session_type == 'child' else
None)
if not session:
raise Exception('Unknown session type "%s"' % session_type)
if decode is None:
decode = IS_PYTHON3
return Nvim.from_session(session).with_decode(decode)
def setup_logging(name):
"""Setup logging according to environment variables."""
logger = logging.getLogger(__name__)
if 'NVIM_PYTHON_LOG_FILE' in os.environ:
prefix = os.environ['NVIM_PYTHON_LOG_FILE'].strip()
major_version = sys.version_info[0]
logfile = '{}_py{}_{}'.format(prefix, major_version, name)
handler = logging.FileHandler(logfile, 'w')
handler.formatter = logging.Formatter(
'%(asctime)s [%(levelname)s @ '
'%(filename)s:%(funcName)s:%(lineno)s] %(process)s - %(message)s')
logging.root.addHandler(handler)
level = logging.INFO
if 'NVIM_PYTHON_LOG_LEVEL' in os.environ:
l = getattr(logging,
os.environ['NVIM_PYTHON_LOG_LEVEL'].strip(),
level)
if isinstance(l, int):
level = l
logger.setLevel(level)
# Required for python 2.6
class NullHandler(logging.Handler):
def emit(self, record):
pass
if not logging.root.handlers:
logging.root.addHandler(NullHandler())
Module variables
var VERSION
Functions
def attach(
session_type, address=None, port=None, path=None, argv=None, decode=None)
Provide a nicer interface to create python api sessions.
Previous machinery to create python api sessions is still there. This only creates a facade function to make things easier for the most usual cases. Thus, instead of: from neovim import socket_session, Nvim session = tcp_session(address=
, port=def attach(session_type, address=None, port=None,
path=None, argv=None, decode=None):
"""Provide a nicer interface to create python api sessions.
Previous machinery to create python api sessions is still there. This only
creates a facade function to make things easier for the most usual cases.
Thus, instead of:
from neovim import socket_session, Nvim
session = tcp_session(address=, port=)
nvim = Nvim.from_session(session)
You can now do:
from neovim import attach
nvim = attach('tcp', address=, port=)
And also:
nvim = attach('socket', path=)
nvim = attach('child', argv=)
nvim = attach('stdio')
"""
session = (tcp_session(address, port) if session_type == 'tcp' else
socket_session(path) if session_type == 'socket' else
stdio_session() if session_type == 'stdio' else
child_session(argv) if session_type == 'child' else
None)
if not session:
raise Exception('Unknown session type "%s"' % session_type)
if decode is None:
decode = IS_PYTHON3
return Nvim.from_session(session).with_decode(decode)
def autocmd(
name, pattern='*', sync=False, eval=None)
Tag a function or plugin method as a Nvim autocommand handler.
def autocmd(name, pattern='*', sync=False, eval=None):
"""Tag a function or plugin method as a Nvim autocommand handler."""
def dec(f):
f._nvim_rpc_method_name = 'autocmd:{}:{}'.format(name, pattern)
f._nvim_rpc_sync = sync
f._nvim_bind = True
f._nvim_prefix_plugin_path = True
opts = {
'pattern': pattern
}
if eval:
opts['eval'] = eval
f._nvim_rpc_spec = {
'type': 'autocmd',
'name': name,
'sync': sync,
'opts': opts
}
return f
return dec
def child_session(
argv)
Create a msgpack-rpc session from a new Nvim instance.
def child_session(argv):
"""Create a msgpack-rpc session from a new Nvim instance."""
return session('child', argv)
def command(
name, nargs=0, complete=None, range=None, count=None, bang=False, register=False, sync=False, eval=None)
Tag a function or plugin method as a Nvim command handler.
def command(name, nargs=0, complete=None, range=None, count=None, bang=False,
register=False, sync=False, eval=None):
"""Tag a function or plugin method as a Nvim command handler."""
def dec(f):
f._nvim_rpc_method_name = 'command:{}'.format(name)
f._nvim_rpc_sync = sync
f._nvim_bind = True
f._nvim_prefix_plugin_path = True
opts = {}
if range is not None:
opts['range'] = '' if range is True else str(range)
elif count:
opts['count'] = count
if bang:
opts['bang'] = ''
if register:
opts['register'] = ''
if nargs:
opts['nargs'] = nargs
if complete:
opts['complete'] = complete
if eval:
opts['eval'] = eval
f._nvim_rpc_spec = {
'type': 'command',
'name': name,
'sync': sync,
'opts': opts
}
return f
return dec
def decode(
mode='surrogateescape')
Configure automatic encoding/decoding of strings.
def decode(mode=unicode_errors_default):
"""Configure automatic encoding/decoding of strings."""
def dec(f):
f._nvim_decode = mode
return f
return dec
def encoding(
encoding=True)
DEPRECATED: use neovim.decode().
def encoding(encoding=True):
"""DEPRECATED: use neovim.decode()."""
if isinstance(encoding, str):
encoding = True
def dec(f):
f._nvim_decode = encoding
return f
return dec
def function(
name, range=False, sync=False, eval=None)
Tag a function or plugin method as a Nvim function handler.
def function(name, range=False, sync=False, eval=None):
"""Tag a function or plugin method as a Nvim function handler."""
def dec(f):
f._nvim_rpc_method_name = 'function:{}'.format(name)
f._nvim_rpc_sync = sync
f._nvim_bind = True
f._nvim_prefix_plugin_path = True
opts = {}
if range:
opts['range'] = '' if range is True else str(range)
if eval:
opts['eval'] = eval
f._nvim_rpc_spec = {
'type': 'function',
'name': name,
'sync': sync,
'opts': opts
}
return f
return dec
def plugin(
cls)
Tag a class as a plugin.
This decorator is required to make the a class methods discoverable by the plugin_load method of the host.
def plugin(cls):
"""Tag a class as a plugin.
This decorator is required to make the a class methods discoverable by the
plugin_load method of the host.
"""
cls._nvim_plugin = True
# the _nvim_bind attribute is set to True by default, meaning that
# decorated functions have a bound Nvim instance as first argument.
# For methods in a plugin-decorated class this is not required, because
# the class initializer will already receive the nvim object.
predicate = lambda fn: hasattr(fn, '_nvim_bind')
for _, fn in inspect.getmembers(cls, predicate):
if IS_PYTHON3:
fn._nvim_bind = False
else:
fn.im_func._nvim_bind = False
return cls
def rpc_export(
rpc_method_name, sync=False)
Export a function or plugin method as a msgpack-rpc request handler.
def rpc_export(rpc_method_name, sync=False):
"""Export a function or plugin method as a msgpack-rpc request handler."""
def dec(f):
f._nvim_rpc_method_name = rpc_method_name
f._nvim_rpc_sync = sync
f._nvim_bind = True
f._nvim_prefix_plugin_path = False
return f
return dec
def setup_logging(
name)
Setup logging according to environment variables.
def setup_logging(name):
"""Setup logging according to environment variables."""
logger = logging.getLogger(__name__)
if 'NVIM_PYTHON_LOG_FILE' in os.environ:
prefix = os.environ['NVIM_PYTHON_LOG_FILE'].strip()
major_version = sys.version_info[0]
logfile = '{}_py{}_{}'.format(prefix, major_version, name)
handler = logging.FileHandler(logfile, 'w')
handler.formatter = logging.Formatter(
'%(asctime)s [%(levelname)s @ '
'%(filename)s:%(funcName)s:%(lineno)s] %(process)s - %(message)s')
logging.root.addHandler(handler)
level = logging.INFO
if 'NVIM_PYTHON_LOG_LEVEL' in os.environ:
l = getattr(logging,
os.environ['NVIM_PYTHON_LOG_LEVEL'].strip(),
level)
if isinstance(l, int):
level = l
logger.setLevel(level)
def shutdown_hook(
f)
Tag a function or method as a shutdown hook.
def shutdown_hook(f):
"""Tag a function or method as a shutdown hook."""
f._nvim_shutdown_hook = True
f._nvim_bind = True
return f
def socket_session(
path)
Create a msgpack-rpc session from a unix domain socket.
def socket_session(path):
"""Create a msgpack-rpc session from a unix domain socket."""
return session('socket', path)
def start_host(
session=None)
Promote the current process into python plugin host for Nvim.
Start msgpack-rpc event loop for session
, listening for Nvim requests
and notifications. It registers Nvim commands for loading/unloading
python plugins.
The sys.stdout and sys.stderr streams are redirected to Nvim through
session
. That means print statements probably won't work as expected
while this function doesn't return.
This function is normally called at program startup and could have been defined as a separate executable. It is exposed as a library function for testing purposes only.
def start_host(session=None):
"""Promote the current process into python plugin host for Nvim.
Start msgpack-rpc event loop for `session`, listening for Nvim requests
and notifications. It registers Nvim commands for loading/unloading
python plugins.
The sys.stdout and sys.stderr streams are redirected to Nvim through
`session`. That means print statements probably won't work as expected
while this function doesn't return.
This function is normally called at program startup and could have been
defined as a separate executable. It is exposed as a library function for
testing purposes only.
"""
plugins = []
for arg in sys.argv:
_, ext = os.path.splitext(arg)
if ext == '.py':
plugins.append(arg)
elif os.path.isdir(arg):
init = os.path.join(arg, '__init__.py')
if os.path.isfile(init):
plugins.append(arg)
# This is a special case to support the old workaround of
# adding an empty .py file to make a package directory
# visible, and it should be removed soon.
for path in list(plugins):
dup = path + ".py"
if os.path.isdir(path) and dup in plugins:
plugins.remove(dup)
# Special case: the legacy scripthost receives a single relative filename
# while the rplugin host will receive absolute paths.
if plugins == ["script_host.py"]:
name = "script"
else:
name = "rplugin"
setup_logging(name)
if not session:
session = stdio_session()
nvim = Nvim.from_session(session)
if nvim.version.api_level < 1:
sys.stderr.write("This version of the neovim python package "
"requires nvim 0.1.6 or later")
sys.exit(1)
host = Host(nvim)
host.start(plugins)
def stdio_session(
)
Create a msgpack-rpc session from stdin/stdout.
def stdio_session():
"""Create a msgpack-rpc session from stdin/stdout."""
return session('stdio')
def tcp_session(
address, port=7450)
Create a msgpack-rpc session from a tcp address/port.
def tcp_session(address, port=7450):
"""Create a msgpack-rpc session from a tcp address/port."""
return session('tcp', address, port)
Classes
class ErrorResponse
Raise this in a request handler to respond with a given error message.
Unlike when other exceptions are caught, this gives full control off the error response sent. When "ErrorResponse(msg)" is caught "msg" will be sent verbatim as the error response.No traceback will be appended.
class ErrorResponse(BaseException):
"""Raise this in a request handler to respond with a given error message.
Unlike when other exceptions are caught, this gives full control off the
error response sent. When "ErrorResponse(msg)" is caught "msg" will be
sent verbatim as the error response.No traceback will be appended.
"""
pass
class Host
Nvim host for python plugins.
Takes care of loading/unloading plugins and routing msgpack-rpc requests/notifications to the appropriate handlers.
class Host(object):
"""Nvim host for python plugins.
Takes care of loading/unloading plugins and routing msgpack-rpc
requests/notifications to the appropriate handlers.
"""
def __init__(self, nvim):
"""Set handlers for plugin_load/plugin_unload."""
self.nvim = nvim
self._specs = {}
self._loaded = {}
self._load_errors = {}
self._notification_handlers = {}
self._request_handlers = {
'poll': lambda: 'ok',
'specs': self._on_specs_request,
'shutdown': self.shutdown
}
# Decode per default for Python3
self._decode_default = IS_PYTHON3
def _on_async_err(self, msg):
self.nvim.err_write(msg, async=True)
def start(self, plugins):
"""Start listening for msgpack-rpc requests and notifications."""
self.nvim.run_loop(self._on_request,
self._on_notification,
lambda: self._load(plugins),
err_cb=self._on_async_err)
def shutdown(self):
"""Shutdown the host."""
self._unload()
self.nvim.stop_loop()
def _wrap_function(self, fn, sync, decode, nvim_bind, name, *args):
if decode:
args = walk(decode_if_bytes, args, decode)
if nvim_bind is not None:
args.insert(0, nvim_bind)
try:
return fn(*args)
except Exception:
if sync:
msg = ("error caught in request handler '{} {}':\n{}"
.format(name, args, format_exc_skip(1)))
raise ErrorResponse(msg)
else:
msg = ("error caught in async handler '{} {}'\n{}\n"
.format(name, args, format_exc_skip(1)))
self._on_async_err(msg + "\n")
def _on_request(self, name, args):
"""Handle a msgpack-rpc request."""
if IS_PYTHON3:
name = decode_if_bytes(name)
handler = self._request_handlers.get(name, None)
if not handler:
msg = self._missing_handler_error(name, 'request')
error(msg)
raise ErrorResponse(msg)
debug('calling request handler for "%s", args: "%s"', name, args)
rv = handler(*args)
debug("request handler for '%s %s' returns: %s", name, args, rv)
return rv
def _on_notification(self, name, args):
"""Handle a msgpack-rpc notification."""
if IS_PYTHON3:
name = decode_if_bytes(name)
handler = self._notification_handlers.get(name, None)
if not handler:
msg = self._missing_handler_error(name, 'notification')
error(msg)
self._on_async_err(msg + "\n")
return
debug('calling notification handler for "%s", args: "%s"', name, args)
handler(*args)
def _missing_handler_error(self, name, kind):
msg = 'no {} handler registered for "{}"'.format(kind, name)
pathmatch = re.match(r'(.+):[^:]+:[^:]+', name)
if pathmatch:
loader_error = self._load_errors.get(pathmatch.group(1))
if loader_error is not None:
msg = msg + "\n" + loader_error
return msg
def _load(self, plugins):
for path in plugins:
err = None
if path in self._loaded:
error('{} is already loaded'.format(path))
continue
try:
if path == "script_host.py":
module = script_host
else:
directory, name = os.path.split(os.path.splitext(path)[0])
file, pathname, descr = find_module(name, [directory])
module = imp.load_module(name, file, pathname, descr)
handlers = []
self._discover_classes(module, handlers, path)
self._discover_functions(module, handlers, path)
if not handlers:
error('{} exports no handlers'.format(path))
continue
self._loaded[path] = {'handlers': handlers, 'module': module}
except Exception as e:
err = ('Encountered {} loading plugin at {}: {}\n{}'
.format(type(e).__name__, path, e, format_exc(5)))
error(err)
self._load_errors[path] = err
def _unload(self):
for path, plugin in self._loaded.items():
handlers = plugin['handlers']
for handler in handlers:
method_name = handler._nvim_rpc_method_name
if hasattr(handler, '_nvim_shutdown_hook'):
handler()
elif handler._nvim_rpc_sync:
del self._request_handlers[method_name]
else:
del self._notification_handlers[method_name]
self._specs = {}
self._loaded = {}
def _discover_classes(self, module, handlers, plugin_path):
for _, cls in inspect.getmembers(module, inspect.isclass):
if getattr(cls, '_nvim_plugin', False):
# create an instance of the plugin and pass the nvim object
plugin = cls(self._configure_nvim_for(cls))
# discover handlers in the plugin instance
self._discover_functions(plugin, handlers, plugin_path)
def _discover_functions(self, obj, handlers, plugin_path):
def predicate(o):
return hasattr(o, '_nvim_rpc_method_name')
specs = []
objdecode = getattr(obj, '_nvim_decode', self._decode_default)
for _, fn in inspect.getmembers(obj, predicate):
sync = fn._nvim_rpc_sync
decode = getattr(fn, '_nvim_decode', objdecode)
nvim_bind = None
if fn._nvim_bind:
nvim_bind = self._configure_nvim_for(fn)
method = fn._nvim_rpc_method_name
if fn._nvim_prefix_plugin_path:
method = '{}:{}'.format(plugin_path, method)
fn_wrapped = functools.partial(self._wrap_function, fn,
sync, decode, nvim_bind, method)
self._copy_attributes(fn, fn_wrapped)
# register in the rpc handler dict
if sync:
if method in self._request_handlers:
raise Exception(('Request handler for "{}" is ' +
'already registered').format(method))
self._request_handlers[method] = fn_wrapped
else:
if method in self._notification_handlers:
raise Exception(('Notification handler for "{}" is ' +
'already registered').format(method))
self._notification_handlers[method] = fn_wrapped
if hasattr(fn, '_nvim_rpc_spec'):
specs.append(fn._nvim_rpc_spec)
handlers.append(fn_wrapped)
if specs:
self._specs[plugin_path] = specs
def _copy_attributes(self, fn, fn2):
# Copy _nvim_* attributes from the original function
for attr in dir(fn):
if attr.startswith('_nvim_'):
setattr(fn2, attr, getattr(fn, attr))
def _on_specs_request(self, path):
if IS_PYTHON3:
path = decode_if_bytes(path)
if path in self._load_errors:
self.nvim.out_write(self._load_errors[path] + '\n')
return self._specs.get(path, 0)
def _configure_nvim_for(self, obj):
# Configure a nvim instance for obj (checks encoding configuration)
nvim = self.nvim
decode = getattr(obj, '_nvim_decode', self._decode_default)
if decode:
nvim = nvim.with_decode(decode)
return nvim
Ancestors (in MRO)
- Host
- builtins.object
Static methods
def __init__(
self, nvim)
Set handlers for plugin_load/plugin_unload.
def __init__(self, nvim):
"""Set handlers for plugin_load/plugin_unload."""
self.nvim = nvim
self._specs = {}
self._loaded = {}
self._load_errors = {}
self._notification_handlers = {}
self._request_handlers = {
'poll': lambda: 'ok',
'specs': self._on_specs_request,
'shutdown': self.shutdown
}
# Decode per default for Python3
self._decode_default = IS_PYTHON3
def shutdown(
self)
Shutdown the host.
def shutdown(self):
"""Shutdown the host."""
self._unload()
self.nvim.stop_loop()
def start(
self, plugins)
Start listening for msgpack-rpc requests and notifications.
def start(self, plugins):
"""Start listening for msgpack-rpc requests and notifications."""
self.nvim.run_loop(self._on_request,
self._on_notification,
lambda: self._load(plugins),
err_cb=self._on_async_err)
Instance variables
var nvim
class Nvim
Class that represents a remote Nvim instance.
This class is main entry point to Nvim remote API, it is a wrapper around Session instances.
The constructor of this class must not be called directly. Instead, the
from_session
class method should be used to create the first instance
from a raw Session
instance.
Subsequent instances for the same session can be created by calling the
with_decode
instance method to change the decoding behavior or
SubClass.from_nvim(nvim)
where SubClass
is a subclass of Nvim
, which
is useful for having multiple Nvim
objects that behave differently
without one affecting the other.
class Nvim(object):
"""Class that represents a remote Nvim instance.
This class is main entry point to Nvim remote API, it is a wrapper
around Session instances.
The constructor of this class must not be called directly. Instead, the
`from_session` class method should be used to create the first instance
from a raw `Session` instance.
Subsequent instances for the same session can be created by calling the
`with_decode` instance method to change the decoding behavior or
`SubClass.from_nvim(nvim)` where `SubClass` is a subclass of `Nvim`, which
is useful for having multiple `Nvim` objects that behave differently
without one affecting the other.
"""
@classmethod
def from_session(cls, session):
"""Create a new Nvim instance for a Session instance.
This method must be called to create the first Nvim instance, since it
queries Nvim metadata for type information and sets a SessionHook for
creating specialized objects from Nvim remote handles.
"""
session.error_wrapper = lambda e: NvimError(e[1])
channel_id, metadata = session.request(b'vim_get_api_info')
if IS_PYTHON3:
# decode all metadata strings for python3
metadata = walk(decode_if_bytes, metadata)
types = {
metadata['types']['Buffer']['id']: Buffer,
metadata['types']['Window']['id']: Window,
metadata['types']['Tabpage']['id']: Tabpage,
}
return cls(session, channel_id, metadata, types)
@classmethod
def from_nvim(cls, nvim):
"""Create a new Nvim instance from an existing instance."""
return cls(nvim._session, nvim.channel_id, nvim.metadata,
nvim.types, nvim._decode, nvim._err_cb)
def __init__(self, session, channel_id, metadata, types,
decode=False, err_cb=None):
"""Initialize a new Nvim instance. This method is module-private."""
self._session = session
self.channel_id = channel_id
self.metadata = metadata
version = metadata.get("version", {"api_level": 0})
self.version = Version(**version)
self.types = types
self.api = RemoteApi(self, 'nvim_')
self.vars = RemoteMap(self, 'nvim_get_var', 'nvim_set_var')
self.vvars = RemoteMap(self, 'vim_get_vvar', None)
self.options = RemoteMap(self, 'nvim_get_option', 'nvim_set_option')
self.buffers = Buffers(self)
self.windows = RemoteSequence(self, 'nvim_list_wins')
self.tabpages = RemoteSequence(self, 'nvim_list_tabpages')
self.current = Current(self)
self.session = CompatibilitySession(self)
self.funcs = Funcs(self)
self.error = NvimError
self._decode = decode
self._err_cb = err_cb
def _from_nvim(self, obj, decode=None):
if decode is None:
decode = self._decode
if type(obj) is ExtType:
cls = self.types[obj.code]
return cls(self, (obj.code, obj.data))
if decode:
obj = decode_if_bytes(obj, decode)
return obj
def _to_nvim(self, obj):
if isinstance(obj, Remote):
return ExtType(*obj.code_data)
return obj
def request(self, name, *args, **kwargs):
r"""Send an API request or notification to nvim.
It is rarely needed to call this function directly, as most API
functions have python wrapper functions. The `api` object can
be also be used to call API functions as methods:
vim.api.err_write('ERROR\n', async=True)
vim.current.buffer.api.get_mark('.')
is equivalent to
vim.request('nvim_err_write', 'ERROR\n', async=True)
vim.request('nvim_buf_get_mark', vim.current.buffer, '.')
Normally a blocking request will be sent. If the `async` flag is
present and True, a asynchronous notification is sent instead. This
will never block, and the return value or error is ignored.
"""
decode = kwargs.pop('decode', self._decode)
args = walk(self._to_nvim, args)
res = self._session.request(name, *args, **kwargs)
return walk(self._from_nvim, res, decode=decode)
def next_message(self):
"""Block until a message(request or notification) is available.
If any messages were previously enqueued, return the first in queue.
If not, run the event loop until one is received.
"""
msg = self._session.next_message()
if msg:
return walk(self._from_nvim, msg)
def run_loop(self, request_cb, notification_cb,
setup_cb=None, err_cb=None):
"""Run the event loop to receive requests and notifications from Nvim.
This should not be called from a plugin running in the host, which
already runs the loop and dispatches events to plugins.
"""
if err_cb is None:
err_cb = sys.stderr.write
self._err_cb = err_cb
def filter_request_cb(name, args):
name = self._from_nvim(name)
args = walk(self._from_nvim, args)
try:
result = request_cb(name, args)
except Exception:
msg = ("error caught in request handler '{} {}'\n{}\n\n"
.format(name, args, format_exc_skip(1)))
self._err_cb(msg)
raise
return walk(self._to_nvim, result)
def filter_notification_cb(name, args):
name = self._from_nvim(name)
args = walk(self._from_nvim, args)
try:
notification_cb(name, args)
except Exception:
msg = ("error caught in notification handler '{} {}'\n{}\n\n"
.format(name, args, format_exc_skip(1)))
self._err_cb(msg)
raise
self._session.run(filter_request_cb, filter_notification_cb, setup_cb)
def stop_loop(self):
"""Stop the event loop being started with `run_loop`."""
self._session.stop()
def with_decode(self, decode=True):
"""Initialize a new Nvim instance."""
return Nvim(self._session, self.channel_id,
self.metadata, self.types, decode, self._err_cb)
def ui_attach(self, width, height, rgb):
"""Register as a remote UI.
After this method is called, the client will receive redraw
notifications.
"""
return self.request('ui_attach', width, height, rgb)
def ui_detach(self):
"""Unregister as a remote UI."""
return self.request('ui_detach')
def ui_try_resize(self, width, height):
"""Notify nvim that the client window has resized.
If possible, nvim will send a redraw request to resize.
"""
return self.request('ui_try_resize', width, height)
def subscribe(self, event):
"""Subscribe to a Nvim event."""
return self.request('nvim_subscribe', event)
def unsubscribe(self, event):
"""Unsubscribe to a Nvim event."""
return self.request('nvim_unsubscribe', event)
def command(self, string, **kwargs):
"""Execute a single ex command."""
return self.request('nvim_command', string, **kwargs)
def command_output(self, string):
"""Execute a single ex command and return the output."""
return self.request('nvim_command_output', string)
def eval(self, string, **kwargs):
"""Evaluate a vimscript expression."""
return self.request('nvim_eval', string, **kwargs)
def call(self, name, *args, **kwargs):
"""Call a vimscript function."""
return self.request('nvim_call_function', name, args, **kwargs)
def strwidth(self, string):
"""Return the number of display cells `string` occupies.
Tab is counted as one cell.
"""
return self.request('nvim_strwidth', string)
def list_runtime_paths(self):
"""Return a list of paths contained in the 'runtimepath' option."""
return self.request('nvim_list_runtime_paths')
def foreach_rtp(self, cb):
"""Invoke `cb` for each path in 'runtimepath'.
Call the given callable for each path in 'runtimepath' until either
callable returns something but None, the exception is raised or there
are no longer paths. If stopped in case callable returned non-None,
vim.foreach_rtp function returns the value returned by callable.
"""
for path in self.request('nvim_list_runtime_paths'):
try:
if cb(path) is not None:
break
except Exception:
break
def chdir(self, dir_path):
"""Run os.chdir, then all appropriate vim stuff."""
os_chdir(dir_path)
return self.request('nvim_set_current_dir', dir_path)
def feedkeys(self, keys, options='', escape_csi=True):
"""Push `keys` to Nvim user input buffer.
Options can be a string with the following character flags:
- 'm': Remap keys. This is default.
- 'n': Do not remap keys.
- 't': Handle keys as if typed; otherwise they are handled as if coming
from a mapping. This matters for undo, opening folds, etc.
"""
return self.request('nvim_feedkeys', keys, options, escape_csi)
def input(self, bytes):
"""Push `bytes` to Nvim low level input buffer.
Unlike `feedkeys()`, this uses the lowest level input buffer and the
call is not deferred. It returns the number of bytes actually
written(which can be less than what was requested if the buffer is
full).
"""
return self.request('nvim_input', bytes)
def replace_termcodes(self, string, from_part=False, do_lt=True,
special=True):
r"""Replace any terminal code strings by byte sequences.
The returned sequences are Nvim's internal representation of keys,
for example:
-> '\x1b'
-> '\r'
-> '\x0c'
-> '\x80ku'
The returned sequences can be used as input to `feedkeys`.
"""
return self.request('vim_replace_termcodes', string,
from_part, do_lt, special)
def out_write(self, msg):
"""Print `msg` as a normal message."""
return self.request('nvim_out_write', msg)
def err_write(self, msg, **kwargs):
"""Print `msg` as an error message."""
return self.request('nvim_err_write', msg, **kwargs)
def quit(self, quit_command='qa!'):
"""Send a quit command to Nvim.
By default, the quit command is 'qa!' which will make Nvim quit without
saving anything.
"""
try:
self.command(quit_command)
except IOError:
# sending a quit command will raise an IOError because the
# connection is closed before a response is received. Safe to
# ignore it.
pass
def new_highlight_source(self):
"""Return new src_id for use with Buffer.add_highlight."""
return self.current.buffer.add_highlight("", 0, src_id=0)
def async_call(self, fn, *args, **kwargs):
"""Schedule `fn` to be called by the event loop soon.
This function is thread-safe, and is the only way code not
on the main thread could interact with nvim api objects.
This function can also be called in a synchronous
event handler, just before it returns, to defer execution
that shouldn't block neovim.
"""
call_point = ''.join(format_stack(None, 5)[:-1])
def handler():
try:
fn(*args, **kwargs)
except Exception as err:
msg = ("error caught while executing async callback:\n"
"{!r}\n{}\n \nthe call was requested at\n{}"
.format(err, format_exc_skip(1), call_point))
self._err_cb(msg)
raise
self._session.threadsafe_call(handler)
Ancestors (in MRO)
- Nvim
- builtins.object
Static methods
def __init__(
self, session, channel_id, metadata, types, decode=False, err_cb=None)
Initialize a new Nvim instance. This method is module-private.
def __init__(self, session, channel_id, metadata, types,
decode=False, err_cb=None):
"""Initialize a new Nvim instance. This method is module-private."""
self._session = session
self.channel_id = channel_id
self.metadata = metadata
version = metadata.get("version", {"api_level": 0})
self.version = Version(**version)
self.types = types
self.api = RemoteApi(self, 'nvim_')
self.vars = RemoteMap(self, 'nvim_get_var', 'nvim_set_var')
self.vvars = RemoteMap(self, 'vim_get_vvar', None)
self.options = RemoteMap(self, 'nvim_get_option', 'nvim_set_option')
self.buffers = Buffers(self)
self.windows = RemoteSequence(self, 'nvim_list_wins')
self.tabpages = RemoteSequence(self, 'nvim_list_tabpages')
self.current = Current(self)
self.session = CompatibilitySession(self)
self.funcs = Funcs(self)
self.error = NvimError
self._decode = decode
self._err_cb = err_cb
def async_call(
self, fn, *args, **kwargs)
Schedule fn
to be called by the event loop soon.
This function is thread-safe, and is the only way code not on the main thread could interact with nvim api objects.
This function can also be called in a synchronous event handler, just before it returns, to defer execution that shouldn't block neovim.
def async_call(self, fn, *args, **kwargs):
"""Schedule `fn` to be called by the event loop soon.
This function is thread-safe, and is the only way code not
on the main thread could interact with nvim api objects.
This function can also be called in a synchronous
event handler, just before it returns, to defer execution
that shouldn't block neovim.
"""
call_point = ''.join(format_stack(None, 5)[:-1])
def handler():
try:
fn(*args, **kwargs)
except Exception as err:
msg = ("error caught while executing async callback:\n"
"{!r}\n{}\n \nthe call was requested at\n{}"
.format(err, format_exc_skip(1), call_point))
self._err_cb(msg)
raise
self._session.threadsafe_call(handler)
def call(
self, name, *args, **kwargs)
Call a vimscript function.
def call(self, name, *args, **kwargs):
"""Call a vimscript function."""
return self.request('nvim_call_function', name, args, **kwargs)
def chdir(
self, dir_path)
Run os.chdir, then all appropriate vim stuff.
def chdir(self, dir_path):
"""Run os.chdir, then all appropriate vim stuff."""
os_chdir(dir_path)
return self.request('nvim_set_current_dir', dir_path)
def command(
self, string, **kwargs)
Execute a single ex command.
def command(self, string, **kwargs):
"""Execute a single ex command."""
return self.request('nvim_command', string, **kwargs)
def command_output(
self, string)
Execute a single ex command and return the output.
def command_output(self, string):
"""Execute a single ex command and return the output."""
return self.request('nvim_command_output', string)
def err_write(
self, msg, **kwargs)
Print msg
as an error message.
def err_write(self, msg, **kwargs):
"""Print `msg` as an error message."""
return self.request('nvim_err_write', msg, **kwargs)
def eval(
self, string, **kwargs)
Evaluate a vimscript expression.
def eval(self, string, **kwargs):
"""Evaluate a vimscript expression."""
return self.request('nvim_eval', string, **kwargs)
def feedkeys(
self, keys, options='', escape_csi=True)
Push keys
to Nvim user input buffer.
Options can be a string with the following character flags: - 'm': Remap keys. This is default. - 'n': Do not remap keys. - 't': Handle keys as if typed; otherwise they are handled as if coming from a mapping. This matters for undo, opening folds, etc.
def feedkeys(self, keys, options='', escape_csi=True):
"""Push `keys` to Nvim user input buffer.
Options can be a string with the following character flags:
- 'm': Remap keys. This is default.
- 'n': Do not remap keys.
- 't': Handle keys as if typed; otherwise they are handled as if coming
from a mapping. This matters for undo, opening folds, etc.
"""
return self.request('nvim_feedkeys', keys, options, escape_csi)
def foreach_rtp(
self, cb)
Invoke cb
for each path in 'runtimepath'.
Call the given callable for each path in 'runtimepath' until either callable returns something but None, the exception is raised or there are no longer paths. If stopped in case callable returned non-None, vim.foreach_rtp function returns the value returned by callable.
def foreach_rtp(self, cb):
"""Invoke `cb` for each path in 'runtimepath'.
Call the given callable for each path in 'runtimepath' until either
callable returns something but None, the exception is raised or there
are no longer paths. If stopped in case callable returned non-None,
vim.foreach_rtp function returns the value returned by callable.
"""
for path in self.request('nvim_list_runtime_paths'):
try:
if cb(path) is not None:
break
except Exception:
break
def input(
self, bytes)
Push bytes
to Nvim low level input buffer.
Unlike feedkeys()
, this uses the lowest level input buffer and the
call is not deferred. It returns the number of bytes actually
written(which can be less than what was requested if the buffer is
full).
def input(self, bytes):
"""Push `bytes` to Nvim low level input buffer.
Unlike `feedkeys()`, this uses the lowest level input buffer and the
call is not deferred. It returns the number of bytes actually
written(which can be less than what was requested if the buffer is
full).
"""
return self.request('nvim_input', bytes)
def list_runtime_paths(
self)
Return a list of paths contained in the 'runtimepath' option.
def list_runtime_paths(self):
"""Return a list of paths contained in the 'runtimepath' option."""
return self.request('nvim_list_runtime_paths')
def new_highlight_source(
self)
Return new src_id for use with Buffer.add_highlight.
def new_highlight_source(self):
"""Return new src_id for use with Buffer.add_highlight."""
return self.current.buffer.add_highlight("", 0, src_id=0)
def next_message(
self)
Block until a message(request or notification) is available.
If any messages were previously enqueued, return the first in queue. If not, run the event loop until one is received.
def next_message(self):
"""Block until a message(request or notification) is available.
If any messages were previously enqueued, return the first in queue.
If not, run the event loop until one is received.
"""
msg = self._session.next_message()
if msg:
return walk(self._from_nvim, msg)
def out_write(
self, msg)
Print msg
as a normal message.
def out_write(self, msg):
"""Print `msg` as a normal message."""
return self.request('nvim_out_write', msg)
def quit(
self, quit_command='qa!')
Send a quit command to Nvim.
By default, the quit command is 'qa!' which will make Nvim quit without saving anything.
def quit(self, quit_command='qa!'):
"""Send a quit command to Nvim.
By default, the quit command is 'qa!' which will make Nvim quit without
saving anything.
"""
try:
self.command(quit_command)
except IOError:
# sending a quit command will raise an IOError because the
# connection is closed before a response is received. Safe to
# ignore it.
pass
def replace_termcodes(
self, string, from_part=False, do_lt=True, special=True)
Replace any terminal code strings by byte sequences.
The returned sequences are Nvim's internal representation of keys, for example:
The returned sequences can be used as input to feedkeys
.
def replace_termcodes(self, string, from_part=False, do_lt=True,
special=True):
r"""Replace any terminal code strings by byte sequences.
The returned sequences are Nvim's internal representation of keys,
for example:
-> '\x1b'
-> '\r'
-> '\x0c'
-> '\x80ku'
The returned sequences can be used as input to `feedkeys`.
"""
return self.request('vim_replace_termcodes', string,
from_part, do_lt, special)
def request(
self, name, *args, **kwargs)
Send an API request or notification to nvim.
It is rarely needed to call this function directly, as most API
functions have python wrapper functions. The api
object can
be also be used to call API functions as methods:
vim.api.err_write('ERROR\n', async=True)
vim.current.buffer.api.get_mark('.')
is equivalent to
vim.request('nvim_err_write', 'ERROR\n', async=True)
vim.request('nvim_buf_get_mark', vim.current.buffer, '.')
Normally a blocking request will be sent. If the async
flag is
present and True, a asynchronous notification is sent instead. This
will never block, and the return value or error is ignored.
def request(self, name, *args, **kwargs):
r"""Send an API request or notification to nvim.
It is rarely needed to call this function directly, as most API
functions have python wrapper functions. The `api` object can
be also be used to call API functions as methods:
vim.api.err_write('ERROR\n', async=True)
vim.current.buffer.api.get_mark('.')
is equivalent to
vim.request('nvim_err_write', 'ERROR\n', async=True)
vim.request('nvim_buf_get_mark', vim.current.buffer, '.')
Normally a blocking request will be sent. If the `async` flag is
present and True, a asynchronous notification is sent instead. This
will never block, and the return value or error is ignored.
"""
decode = kwargs.pop('decode', self._decode)
args = walk(self._to_nvim, args)
res = self._session.request(name, *args, **kwargs)
return walk(self._from_nvim, res, decode=decode)
def run_loop(
self, request_cb, notification_cb, setup_cb=None, err_cb=None)
Run the event loop to receive requests and notifications from Nvim.
This should not be called from a plugin running in the host, which already runs the loop and dispatches events to plugins.
def run_loop(self, request_cb, notification_cb,
setup_cb=None, err_cb=None):
"""Run the event loop to receive requests and notifications from Nvim.
This should not be called from a plugin running in the host, which
already runs the loop and dispatches events to plugins.
"""
if err_cb is None:
err_cb = sys.stderr.write
self._err_cb = err_cb
def filter_request_cb(name, args):
name = self._from_nvim(name)
args = walk(self._from_nvim, args)
try:
result = request_cb(name, args)
except Exception:
msg = ("error caught in request handler '{} {}'\n{}\n\n"
.format(name, args, format_exc_skip(1)))
self._err_cb(msg)
raise
return walk(self._to_nvim, result)
def filter_notification_cb(name, args):
name = self._from_nvim(name)
args = walk(self._from_nvim, args)
try:
notification_cb(name, args)
except Exception:
msg = ("error caught in notification handler '{} {}'\n{}\n\n"
.format(name, args, format_exc_skip(1)))
self._err_cb(msg)
raise
self._session.run(filter_request_cb, filter_notification_cb, setup_cb)
def stop_loop(
self)
Stop the event loop being started with run_loop
.
def stop_loop(self):
"""Stop the event loop being started with `run_loop`."""
self._session.stop()
def strwidth(
self, string)
Return the number of display cells string
occupies.
Tab is counted as one cell.
def strwidth(self, string):
"""Return the number of display cells `string` occupies.
Tab is counted as one cell.
"""
return self.request('nvim_strwidth', string)
def subscribe(
self, event)
Subscribe to a Nvim event.
def subscribe(self, event):
"""Subscribe to a Nvim event."""
return self.request('nvim_subscribe', event)
def ui_attach(
self, width, height, rgb)
Register as a remote UI.
After this method is called, the client will receive redraw notifications.
def ui_attach(self, width, height, rgb):
"""Register as a remote UI.
After this method is called, the client will receive redraw
notifications.
"""
return self.request('ui_attach', width, height, rgb)
def ui_detach(
self)
Unregister as a remote UI.
def ui_detach(self):
"""Unregister as a remote UI."""
return self.request('ui_detach')
def ui_try_resize(
self, width, height)
Notify nvim that the client window has resized.
If possible, nvim will send a redraw request to resize.
def ui_try_resize(self, width, height):
"""Notify nvim that the client window has resized.
If possible, nvim will send a redraw request to resize.
"""
return self.request('ui_try_resize', width, height)
def unsubscribe(
self, event)
Unsubscribe to a Nvim event.
def unsubscribe(self, event):
"""Unsubscribe to a Nvim event."""
return self.request('nvim_unsubscribe', event)
def with_decode(
self, decode=True)
Initialize a new Nvim instance.
def with_decode(self, decode=True):
"""Initialize a new Nvim instance."""
return Nvim(self._session, self.channel_id,
self.metadata, self.types, decode, self._err_cb)
Instance variables
var api
var buffers
var channel_id
var current
var error
var funcs
var metadata
var options
var session
var tabpages
var types
var vars
var version
var vvars
var windows
Methods
def from_nvim(
cls, nvim)
Create a new Nvim instance from an existing instance.
@classmethod
def from_nvim(cls, nvim):
"""Create a new Nvim instance from an existing instance."""
return cls(nvim._session, nvim.channel_id, nvim.metadata,
nvim.types, nvim._decode, nvim._err_cb)
def from_session(
cls, session)
Create a new Nvim instance for a Session instance.
This method must be called to create the first Nvim instance, since it queries Nvim metadata for type information and sets a SessionHook for creating specialized objects from Nvim remote handles.
@classmethod
def from_session(cls, session):
"""Create a new Nvim instance for a Session instance.
This method must be called to create the first Nvim instance, since it
queries Nvim metadata for type information and sets a SessionHook for
creating specialized objects from Nvim remote handles.
"""
session.error_wrapper = lambda e: NvimError(e[1])
channel_id, metadata = session.request(b'vim_get_api_info')
if IS_PYTHON3:
# decode all metadata strings for python3
metadata = walk(decode_if_bytes, metadata)
types = {
metadata['types']['Buffer']['id']: Buffer,
metadata['types']['Window']['id']: Window,
metadata['types']['Tabpage']['id']: Tabpage,
}
return cls(session, channel_id, metadata, types)
Sub-modules
Nvim API subpackage.
This package implements a higher-level API that wraps msgpack-rpc Session
instances.
Code for supporting compatibility across python versions.
Msgpack-rpc subpackage.
This package implements a msgpack-rpc client. While it was designed for handling some Nvim particularities(server->client requests for example), the code here should work with other msgpack-rpc servers.
Shared utility functions.