diff --git a/pynvim/msgpack_rpc/session.py b/pynvim/msgpack_rpc/session.py index 453f218b..e578f911 100644 --- a/pynvim/msgpack_rpc/session.py +++ b/pynvim/msgpack_rpc/session.py @@ -243,12 +243,12 @@ def handler(): + 'sending %s as response', gr, rv) response.send(rv) except ErrorResponse as err: - warn("error response from request '%s %s': %s", name, - args, format_exc()) + debug("error response from request '%s %s': %s", + name, args, format_exc()) response.send(err.args[0], error=True) except Exception as err: - warn("error caught while processing request '%s %s': %s", name, - args, format_exc()) + warn("error caught while processing request '%s %s': %s", + name, args, format_exc()) response.send(repr(err) + "\n" + format_exc(5), error=True) debug('greenlet %s is now dying...', gr) diff --git a/pynvim/plugin/script_host.py b/pynvim/plugin/script_host.py index 72685f50..f647e5e3 100644 --- a/pynvim/plugin/script_host.py +++ b/pynvim/plugin/script_host.py @@ -81,10 +81,19 @@ def teardown(self): def python_execute(self, script, range_start, range_stop): """Handle the `python` ex command.""" self._set_current_range(range_start, range_stop) + + if script.startswith('='): + # Handle ":py= ...". Evaluate as an expression and print. + # (note: a valid python statement can't start with "=") + expr = script[1:] + print(self.python_eval(expr)) + return + try: + # pylint: disable-next=exec-used exec(script, self.module.__dict__) - except Exception: - raise ErrorResponse(format_exc_skip(1)) + except Exception as exc: + raise ErrorResponse(format_exc_skip(1)) from exc @rpc_export('python_execute_file', sync=True) def python_execute_file(self, file_path, range_start, range_stop): @@ -93,9 +102,10 @@ def python_execute_file(self, file_path, range_start, range_stop): with open(file_path, 'rb') as f: script = compile(f.read(), file_path, 'exec') try: + # pylint: disable-next=exec-used exec(script, self.module.__dict__) - except Exception: - raise ErrorResponse(format_exc_skip(1)) + except Exception as exc: + raise ErrorResponse(format_exc_skip(1)) from exc @rpc_export('python_do_range', sync=True) def python_do_range(self, start, stop, code): @@ -154,7 +164,11 @@ def python_do_range(self, start, stop, code): @rpc_export('python_eval', sync=True) def python_eval(self, expr): """Handle the `pyeval` vim function.""" - return eval(expr, self.module.__dict__) + try: + # pylint: disable-next=eval-used + return eval(expr, self.module.__dict__) + except Exception as exc: + raise ErrorResponse(format_exc_skip(1)) from exc @rpc_export('python_chdir', sync=False) def python_chdir(self, cwd): diff --git a/test/test_vim.py b/test/test_vim.py index 1e59333e..1c12e26e 100644 --- a/test/test_vim.py +++ b/test/test_vim.py @@ -1,11 +1,12 @@ import os import sys import tempfile +import textwrap from pathlib import Path import pytest -from pynvim.api import Nvim +from pynvim.api import Nvim, NvimError def source(vim: Nvim, code: str) -> None: @@ -40,6 +41,10 @@ def test_command(vim: Nvim) -> None: def test_command_output(vim: Nvim) -> None: assert vim.command_output('echo "test"') == 'test' + # can capture multi-line outputs + vim.command("let g:multiline_string = join(['foo', 'bar'], nr2char(10))") + assert vim.command_output('echo g:multiline_string') == "foo\nbar" + def test_command_error(vim: Nvim) -> None: with pytest.raises(vim.error) as excinfo: @@ -213,6 +218,35 @@ def test_python3(vim: Nvim) -> None: assert 1 == vim.eval('has("python3")') +def test_python3_ex_eval(vim: Nvim) -> None: + assert '42' == vim.command_output('python3 =42') + assert '42' == vim.command_output('python3 = 42 ') + assert '42' == vim.command_output('py3= 42 ') + assert '42' == vim.command_output('py=42') + + # On syntax error or evaluation error, stacktrace information is printed + # Note: the pynvim API command_output() throws an exception on error + # because the Ex command :python will throw (wrapped with provider#python3#Call) + with pytest.raises(NvimError) as excinfo: + vim.command('py3= 1/0') + assert textwrap.dedent('''\ + Traceback (most recent call last): + File "", line 1, in + ZeroDivisionError: division by zero + ''').strip() in excinfo.value.args[0] + + vim.command('python3 def raise_error(): raise RuntimeError("oops")') + with pytest.raises(NvimError) as excinfo: + vim.command_output('python3 =print("nooo", raise_error())') + assert textwrap.dedent('''\ + Traceback (most recent call last): + File "", line 1, in + File "", line 1, in raise_error + RuntimeError: oops + ''').strip() in excinfo.value.args[0] + assert 'nooo' not in vim.command_output(':messages') + + def test_python_cwd(vim: Nvim, tmp_path: Path) -> None: vim.command('python3 import os') cwd_before = vim.command_output('python3 print(os.getcwd())')