--- /dev/null
+"""Python client for Nvim.
+
+Client library for talking with Nvim processes via its msgpack-rpc API.
+"""
+import logging
+import os
+import sys
+
+from pynvim.api import Nvim, NvimError
+from pynvim.compat import IS_PYTHON3
+from pynvim.msgpack_rpc import (ErrorResponse, child_session, socket_session,
+ stdio_session, tcp_session)
+from pynvim.plugin import (Host, autocmd, command, decode, encoding, function,
+ plugin, rpc_export, shutdown_hook)
+from pynvim.util import VERSION, Version
+
+
+__all__ = ('tcp_session', 'socket_session', 'stdio_session', 'child_session',
+ 'start_host', 'autocmd', 'command', 'encoding', 'decode',
+ 'function', 'plugin', 'rpc_export', 'Host', 'Nvim', 'NvimError',
+ 'Version', 'VERSION', 'shutdown_hook', 'attach', 'setup_logging',
+ 'ErrorResponse')
+
+
+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 pynvim "
+ "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 pynvim import socket_session, Nvim
+ session = tcp_session(address=<address>, port=<port>)
+ nvim = Nvim.from_session(session)
+ You can now do:
+ from pynvim import attach
+ nvim = attach('tcp', address=<address>, port=<port>)
+ And also:
+ nvim = attach('socket', path=<path>)
+ nvim = attach('child', argv=<argv>)
+ nvim = attach('stdio')
+
+ When the session is not needed anymore, it is recommended to explicitly
+ close it:
+ nvim.close()
+ It is also possible to use the session as a context mangager:
+ with attach('socket', path=thepath) as nvim:
+ print(nvim.funcs.getpid())
+ print(nvim.current.line)
+ This will automatically close the session when you're done with it, or
+ when an error occured.
+
+
+ """
+ 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', 'utf-8')
+ 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
+ env_log_level = os.environ.get('NVIM_PYTHON_LOG_LEVEL', None)
+ if env_log_level is not None:
+ lvl = getattr(logging, env_log_level.strip(), None)
+ if isinstance(lvl, int):
+ level = lvl
+ else:
+ logger.warning('Invalid NVIM_PYTHON_LOG_LEVEL: %r, using INFO.',
+ env_log_level)
+ 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())