Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Ex command :py= evaluate and print python expression #548

Merged
merged 3 commits into from
Nov 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions pynvim/msgpack_rpc/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
24 changes: 19 additions & 5 deletions pynvim/plugin/script_host.py
Original file line number Diff line number Diff line change
Expand Up @@ -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('='):
wookayin marked this conversation as resolved.
Show resolved Hide resolved
# 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):
Expand All @@ -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):
Expand Down Expand Up @@ -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):
Expand Down
36 changes: 35 additions & 1 deletion test/test_vim.py
Original file line number Diff line number Diff line change
@@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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 "<string>", line 1, in <module>
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 "<string>", line 1, in <module>
File "<string>", 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())')
Expand Down