Skip to content

Commit

Permalink
remove decodehook
Browse files Browse the repository at this point in the history
  • Loading branch information
bfredl committed Mar 29, 2016
1 parent 69d6d0f commit 0886e84
Show file tree
Hide file tree
Showing 9 changed files with 81 additions and 93 deletions.
10 changes: 5 additions & 5 deletions neovim/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@
import os
import sys

from .api import DecodeHook, Nvim
from .api import Nvim
from .msgpack_rpc import (ErrorResponse, child_session, socket_session,
stdio_session, tcp_session)
from .plugin import (Host, autocmd, command, encoding, function, plugin,
rpc_export, shutdown_hook)
from .plugin import (Host, autocmd, command, decode, encoding, function,
plugin, rpc_export, shutdown_hook)


__all__ = ('tcp_session', 'socket_session', 'stdio_session', 'child_session',
'start_host', 'autocmd', 'command', 'encoding', 'function',
'plugin', 'rpc_export', 'Host', 'DecodeHook', 'Nvim',
'start_host', 'autocmd', 'command', 'encoding', 'decode',
'function', 'plugin', 'rpc_export', 'Host', 'Nvim',
'shutdown_hook', 'attach', 'setup_logging', 'ErrorResponse')


Expand Down
4 changes: 2 additions & 2 deletions neovim/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
"""

from .buffer import Buffer
from .common import DecodeHook
from .common import decode_if_bytes, walk
from .nvim import Nvim, NvimError
from .tabpage import Tabpage
from .window import Window


__all__ = ('Nvim', 'Buffer', 'Window', 'Tabpage', 'NvimError',
'DecodeHook')
'decode_if_bytes', 'walk')
36 changes: 9 additions & 27 deletions neovim/api/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,38 +153,20 @@ def _identity(obj, session, method, kind):
return obj


class DecodeHook(object):

"""SessionHook subclass that decodes utf-8 strings coming from Nvim.
This class is useful for python3, where strings are now unicode by
default(byte strings need to be prefixed with "b").
"""

def __init__(self, encoding='utf-8', encoding_errors='strict'):
"""Initialize with encoding and encoding errors policy."""
self.encoding = encoding
self.encoding_errors = encoding_errors

def decode_if_bytes(self, obj):
"""Decode obj if it is bytes."""
if isinstance(obj, bytes):
return obj.decode(self.encoding, errors=self.encoding_errors)
return obj

def walk(self, obj):
"""Decode bytes found in obj (any msgpack object).
Uses encoding and policy specified in constructor.
"""
return walk(self.decode_if_bytes, obj)
def decode_if_bytes(obj, mode=True):
"""Decode obj if it is bytes."""
if mode is True:
mode = "strict"
if isinstance(obj, bytes):
return obj.decode("utf-8", errors=mode)
return obj


def walk(fn, obj, *args):
def walk(fn, obj, *args, **kwargs):
"""Recursively walk an object graph applying `fn`/`args` to objects."""
if type(obj) in [list, tuple]:
return list(walk(fn, o, *args) for o in obj)
if type(obj) is dict:
return dict((walk(fn, k, *args), walk(fn, v, *args)) for k, v in
obj.items())
return fn(obj, *args)
return fn(obj, *args, **kwargs)
45 changes: 22 additions & 23 deletions neovim/api/nvim.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
from msgpack import ExtType

from .buffer import Buffer
from .common import (DecodeHook, Remote, RemoteApi,
RemoteMap, RemoteSequence, walk)
from .common import (Remote, RemoteApi, RemoteMap, RemoteSequence,
decode_if_bytes, walk)
from .tabpage import Tabpage
from .window import Window
from ..compat import IS_PYTHON3
Expand All @@ -33,7 +33,7 @@ class Nvim(object):
from a raw `Session` instance.
Subsequent instances for the same session can be created by calling the
`with_decodehook` instance method to change the decoding behavior or
`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.
Expand All @@ -52,7 +52,7 @@ def from_session(cls, session):

if IS_PYTHON3:
# decode all metadata strings for python3
metadata = DecodeHook().walk(metadata)
metadata = walk(decode_if_bytes, metadata)

types = {
metadata['types']['Buffer']['id']: Buffer,
Expand All @@ -66,10 +66,10 @@ def from_session(cls, session):
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._decodehook, nvim._err_cb)
nvim.types, nvim._decode, nvim._err_cb)

def __init__(self, session, channel_id, metadata, types,
decodehook=None, err_cb=None):
decode=False, err_cb=None):
"""Initialize a new Nvim instance. This method is module-private."""
self._session = session
self.channel_id = channel_id
Expand All @@ -85,15 +85,17 @@ def __init__(self, session, channel_id, metadata, types,
self.current = Current(self)
self.funcs = Funcs(self)
self.error = NvimError
self._decodehook = decodehook
self._decode = decode
self._err_cb = err_cb

def _from_nvim(self, obj):
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 self._decodehook is not None:
obj = self._decodehook.decode_if_bytes(obj)
if decode:
obj = decode_if_bytes(obj, decode)
return obj

def _to_nvim(self, obj):
Expand Down Expand Up @@ -121,9 +123,10 @@ def request(self, name, *args, **kwargs):
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)
return walk(self._from_nvim, res, decode=decode)

def next_message(self):
"""Block until a message(request or notification) is available.
Expand Down Expand Up @@ -160,10 +163,10 @@ def stop_loop(self):
"""Stop the event loop being started with `run_loop`."""
self._session.stop()

def with_decodehook(self, hook):
def with_decode(self, decode=True):
"""Initialize a new Nvim instance."""
return Nvim(self._session, self.channel_id,
self.metadata, self.types, hook, self._err_cb)
self.metadata, self.types, decode, self._err_cb)

def ui_attach(self, width, height, rgb):
"""Register as a remote UI.
Expand Down Expand Up @@ -192,24 +195,20 @@ def unsubscribe(self, event):
"""Unsubscribe to a Nvim event."""
return self.request('vim_unsubscribe', event)

def command(self, string, async=False):
def command(self, string, **kwargs):
"""Execute a single ex command."""
return self.request('vim_command', string, async=async)
return self.request('vim_command', string, **kwargs)

def command_output(self, string):
"""Execute a single ex command and return the output."""
return self.request('vim_command_output', string)

def eval(self, string, async=False):
def eval(self, string, **kwargs):
"""Evaluate a vimscript expression."""
return self.request('vim_eval', string, async=async)
return self.request('vim_eval', string, **kwargs)

def call(self, name, *args, **kwargs):
"""Call a vimscript function."""
for k in kwargs:
if k != "async":
raise TypeError(
"call() got an unexpected keyword argument '{}'".format(k))
return self.request('vim_call_function', name, args, **kwargs)

def strwidth(self, string):
Expand Down Expand Up @@ -285,9 +284,9 @@ def out_write(self, msg):
"""Print `msg` as a normal message."""
return self.request('vim_out_write', msg)

def err_write(self, msg, async=False):
def err_write(self, msg, **kwargs):
"""Print `msg` as an error message."""
return self.request('vim_err_write', msg, async=async)
return self.request('vim_err_write', msg, **kwargs)

def quit(self, quit_command='qa!'):
"""Send a quit command to Nvim.
Expand Down
6 changes: 5 additions & 1 deletion neovim/msgpack_rpc/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,15 @@ def request(self, method, *args, **kwargs):
sent instead. This will never block, and the return value or error is
ignored.
"""
async = kwargs.get('async', False)
async = kwargs.pop('async', False)
if async:
self._async_session.notify(method, args)
return

if kwargs:
raise ValueError("request got unsupported keyword argument(s): {0}"
.format(', '.join(kwargs.keys())))

if self._is_running:
v = self._yielding_request(method, args)
else:
Expand Down
6 changes: 3 additions & 3 deletions neovim/plugin/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
"""Nvim plugin/host subpackage."""

from .decorators import (autocmd, command, encoding, function, plugin,
rpc_export, shutdown_hook)
from .decorators import (autocmd, command, decode, encoding, function,
plugin, rpc_export, shutdown_hook)
from .host import Host


__all__ = ('Host', 'plugin', 'rpc_export', 'command', 'autocmd',
'function', 'encoding', 'shutdown_hook')
'function', 'encoding', 'decode', 'shutdown_hook')
17 changes: 14 additions & 3 deletions neovim/plugin/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
logger = logging.getLogger(__name__)
debug, info, warn = (logger.debug, logger.info, logger.warning,)
__all__ = ('plugin', 'rpc_export', 'command', 'autocmd', 'function',
'encoding', 'shutdown_hook')
'encoding', 'decode', 'shutdown_hook')


def plugin(cls):
Expand Down Expand Up @@ -141,9 +141,20 @@ def shutdown_hook(f):
return f


def encoding(encoding=True):
def decode(mode='strict'):
"""Configure automatic encoding/decoding of strings."""
def dec(f):
f._nvim_encoding = encoding
f._nvim_decode = mode
return f
return dec


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
48 changes: 20 additions & 28 deletions neovim/plugin/host.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from traceback import format_exc

from . import script_host
from ..api import DecodeHook
from ..api import decode_if_bytes, walk
from ..compat import IS_PYTHON3, find_module
from ..msgpack_rpc import ErrorResponse

Expand Down Expand Up @@ -41,9 +41,9 @@ def __init__(self, nvim):
'specs': self._on_specs_request,
'shutdown': self.shutdown
}
self._nvim_encoding = nvim.options['encoding']
if IS_PYTHON3 and isinstance(self._nvim_encoding, bytes):
self._nvim_encoding = self._nvim_encoding.decode('ascii')

# Decode per default for Python3
self._decode_default = IS_PYTHON3

def _on_async_err(self, msg):
self.nvim.err_write(msg, async=True)
Expand All @@ -62,8 +62,8 @@ def shutdown(self):

def _on_request(self, name, args):
"""Handle a msgpack-rpc request."""
if IS_PYTHON3 and isinstance(name, bytes):
name = name.decode(self._nvim_encoding)
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')
Expand All @@ -77,8 +77,8 @@ def _on_request(self, name, args):

def _on_notification(self, name, args):
"""Handle a msgpack-rpc notification."""
if IS_PYTHON3 and isinstance(name, bytes):
name = name.decode(self._nvim_encoding)
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')
Expand Down Expand Up @@ -155,20 +155,21 @@ def _discover_classes(self, module, handlers, plugin_path):
def _discover_functions(self, obj, handlers, plugin_path):
def predicate(o):
return hasattr(o, '_nvim_rpc_method_name')

def decoder(fn, decode, *args):
return fn(*walk(decode_if_bytes, args, decode))
specs = []
objenc = getattr(obj, '_nvim_encoding', None)
objdecode = getattr(obj, '_nvim_decode', self._decode_default)
for _, fn in inspect.getmembers(obj, predicate):
enc = getattr(fn, '_nvim_encoding', objenc)
decode = getattr(fn, '_nvim_decode', objdecode)
if fn._nvim_bind:
# bind a nvim instance to the handler
fn2 = functools.partial(fn, self._configure_nvim_for(fn))
# copy _nvim_* attributes from the original function
self._copy_attributes(fn, fn2)
fn = fn2
decodehook = self._decodehook_for(enc)
if decodehook is not None:
decoder = lambda fn, hook, *args: fn(*hook.walk(args))
fn2 = functools.partial(decoder, fn, decodehook)
if decode:
fn2 = functools.partial(decoder, fn, decode)
self._copy_attributes(fn, fn2)
fn = fn2

Expand Down Expand Up @@ -199,25 +200,16 @@ def _copy_attributes(self, fn, fn2):
setattr(fn2, attr, getattr(fn, attr))

def _on_specs_request(self, path):
if IS_PYTHON3 and isinstance(path, bytes):
path = path.decode(self._nvim_encoding)
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 _decodehook_for(self, encoding):
if IS_PYTHON3 and encoding is None:
encoding = True
if encoding is True:
encoding = self._nvim_encoding
if encoding:
return DecodeHook(encoding)

def _configure_nvim_for(self, obj):
# Configure a nvim instance for obj (checks encoding configuration)
nvim = self.nvim
encoding = getattr(obj, '_nvim_encoding', None)
hook = self._decodehook_for(encoding)
if hook is not None:
nvim = nvim.with_decodehook(hook)
decode = getattr(obj, '_nvim_decode', self._decode_default)
if decode:
nvim = nvim.with_decode(decode)
return nvim
2 changes: 1 addition & 1 deletion test/test_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
if sys.version_info >= (3, 0):
# For Python3 we decode binary strings as Unicode for compatibility
# with Python2
vim = vim.with_decodehook(neovim.DecodeHook())
vim = vim.with_decode()

cleanup_func = ''':function BeforeEachTest()
set all&
Expand Down

0 comments on commit 0886e84

Please sign in to comment.