Skip to content
Draft
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
3 changes: 3 additions & 0 deletions doc/pymode.txt
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,9 @@ Set path to virtualenv manually *'g:pymode_virtualenv_path'
Commands:
*:PymodeRun* -- Run current buffer or selection

When a virtualenv is activated with |:PymodeVirtualenv|, *:PymodeRun* uses the
virtualenv's Python executable.

Turn on the run code script *'g:pymode_run'*
>
let g:pymode_run = 1
Expand Down
122 changes: 91 additions & 31 deletions pymode/run.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
""" Code runnning support. """
import os
import subprocess
import sys
import tempfile
from io import StringIO
from re import compile as re

Expand All @@ -25,46 +28,103 @@ def run_code():
elif encoding.match(lines[1]):
lines.pop(1)

context = dict(
__name__='__main__',
__file__=env.var('expand("%:p")'),
input=env.user_input,
raw_input=env.user_input)
python_cmd = __get_virtualenv_python()
if python_cmd:
result = __run_with_external_python(python_cmd, lines)
if result is not None:
output, err = result
else:
python_cmd = None

if not python_cmd:
context = dict(
__name__='__main__',
__file__=env.var('expand("%:p")'),
input=env.user_input,
raw_input=env.user_input)

sys.stdout, stdout_ = StringIO(), sys.stdout
sys.stderr, stderr_ = StringIO(), sys.stderr

try:
code = compile('\n'.join(lines) + '\n', env.curbuf.name, 'exec')
sys.path.insert(0, env.curdir)
exec(code, context) # noqa
sys.path.pop(0)

except SystemExit as e:
if e.code:
# A non-false code indicates abnormal termination.
# A false code will be treated as a
# successful run, and the error will be hidden from Vim
env.error("Script exited with code %s" % e.code)
return env.stop()

except Exception:
import traceback
err = traceback.format_exc()

else:
err = sys.stderr.getvalue()

output = sys.stdout.getvalue()
sys.stdout, sys.stderr = stdout_, stderr_

sys.stdout, stdout_ = StringIO(), sys.stdout
sys.stderr, stderr_ = StringIO(), sys.stderr

try:
code = compile('\n'.join(lines) + '\n', env.curbuf.name, 'exec')
sys.path.insert(0, env.curdir)
exec(code, context) # noqa
sys.path.pop(0)

except SystemExit as e:
if e.code:
# A non-false code indicates abnormal termination.
# A false code will be treated as a
# successful run, and the error will be hidden from Vim
env.error("Script exited with code %s" % e.code)
return env.stop()

except Exception:
import traceback
err = traceback.format_exc()

else:
err = sys.stderr.getvalue()

output = sys.stdout.getvalue()
output = env.prepare_value(output, dumps=False)
sys.stdout, sys.stderr = stdout_, stderr_

errors += [er for er in err.splitlines() if er and "<string>" not in er]

env.let('l:traceback', errors[2:])
env.let('l:output', [s for s in output.splitlines()])


def __get_virtualenv_python():
path = env.var('g:pymode_virtualenv_enabled', silence=True, default='')
if not path:
return None

if os.name == 'nt':
candidates = [
os.path.join(path, 'Scripts', 'python.exe'),
os.path.join(path, 'Scripts', 'python'),
]
else:
candidates = [os.path.join(path, 'bin', 'python')]

for candidate in candidates:
if os.path.isfile(candidate) and os.access(candidate, os.X_OK):
return candidate

return None


def __run_with_external_python(python_cmd, lines):
source = '\n'.join(lines) + '\n'
temp_file_path = None
try:
with tempfile.NamedTemporaryFile(
mode='w', suffix='.py', delete=False, encoding='utf-8'
) as temp_file:
temp_file.write(source)
temp_file_path = temp_file.name

process = subprocess.Popen(
[python_cmd, temp_file_path],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
cwd=env.curdir,
text=True)
output, err = process.communicate()
except OSError as exc:
env.debug('Failed to execute external python', python_cmd, exc)
return None
finally:
if temp_file_path and os.path.exists(temp_file_path):
os.unlink(temp_file_path)

return output, err


def __prepare_lines(line1, line2):

lines = [l.rstrip() for l in env.lines[int(line1) - 1:int(line2)]]
Expand Down
43 changes: 42 additions & 1 deletion tests/vader/commands.vader
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,47 @@ Execute (Test PymodeRun command):
Assert 1, 'PymodeRun executed without producing a run buffer'
endif

Execute (Test PymodeRun uses active virtualenv python):
if exists(':PymodeRun') && exists(':PymodeVirtualenv')
let pycmd = executable('python3') ? 'python3' : (executable('python') ? 'python' : '')
if !empty(pycmd)
let temp_dir = tempname()
call mkdir(temp_dir, 'p')
let venv_dir = temp_dir . '/.venv'
let sample_file = temp_dir . '/run_from_venv.py'

call writefile(['import sys', 'print(sys.executable)'], sample_file)

call system(pycmd . ' -m venv ' . shellescape(venv_dir))
if v:shell_error == 0
execute 'edit ' . fnameescape(sample_file)
let g:pymode_virtualenv = 1
execute 'PymodeVirtualenv ' . string(venv_dir)
PymodeRun

let run_buffer = bufnr('__run__')
if run_buffer != -1
execute 'buffer ' . run_buffer
let run_output = substitute(join(getline(1, '$'), "\n"), '\\', '/', 'g')
let expected_python = has('win32') || has('win64') ? venv_dir . '/Scripts/python.exe' : venv_dir . '/bin/python'
let expected_python = substitute(expected_python, '\\', '/', 'g')
Assert stridx(run_output, expected_python) >= 0, 'PymodeRun should execute with virtualenv python executable'
else
Assert 0, 'PymodeRun should create run buffer when using virtualenv'
endif
else
Assert 1, 'Unable to create test virtualenv in this environment - test skipped'
endif

call delete(sample_file)
call delete(temp_dir, 'rf')
else
Assert 1, 'No python executable available to create virtualenv - test skipped'
endif
else
Assert 1, 'PymodeRun/PymodeVirtualenv command not available - test skipped'
endif

# Test PymodeLint command
Given python (Python code with lint issues):
import math, sys;
Expand Down Expand Up @@ -275,4 +316,4 @@ Execute (Test PymodeRun with pymoderun_sample.py):
endif
else
Assert 1, 'PymodeRun command not available - test skipped'
endif
endif