summaryrefslogtreecommitdiffstats
path: root/ptpython/repl.py
diff options
context:
space:
mode:
Diffstat (limited to 'ptpython/repl.py')
-rw-r--r--ptpython/repl.py191
1 files changed, 108 insertions, 83 deletions
diff --git a/ptpython/repl.py b/ptpython/repl.py
index ae7b1d0..220c673 100644
--- a/ptpython/repl.py
+++ b/ptpython/repl.py
@@ -11,7 +11,6 @@ import asyncio
import builtins
import os
import sys
-import threading
import traceback
import types
import warnings
@@ -80,7 +79,7 @@ class PythonRepl(PythonInput):
self._load_start_paths()
def _load_start_paths(self) -> None:
- " Start the Read-Eval-Print Loop. "
+ "Start the Read-Eval-Print Loop."
if self._startup_paths:
for path in self._startup_paths:
if os.path.exists(path):
@@ -91,6 +90,35 @@ class PythonRepl(PythonInput):
output = self.app.output
output.write("WARNING | File not found: {}\n\n".format(path))
+ def run_and_show_expression(self, expression):
+ try:
+ # Eval.
+ try:
+ result = self.eval(expression)
+ except KeyboardInterrupt: # KeyboardInterrupt doesn't inherit from Exception.
+ raise
+ except SystemExit:
+ raise
+ except BaseException as e:
+ self._handle_exception(e)
+ else:
+ # Print.
+ if result is not None:
+ self.show_result(result)
+
+ # Loop.
+ self.current_statement_index += 1
+ self.signatures = []
+
+ except KeyboardInterrupt as e:
+ # Handle all possible `KeyboardInterrupt` errors. This can
+ # happen during the `eval`, but also during the
+ # `show_result` if something takes too long.
+ # (Try/catch is around the whole block, because we want to
+ # prevent that a Control-C keypress terminates the REPL in
+ # any case.)
+ self._handle_keyboard_interrupt(e)
+
def run(self) -> None:
"""
Run the REPL loop.
@@ -102,44 +130,41 @@ class PythonRepl(PythonInput):
try:
while True:
+ # Pull text from the user.
try:
- # Read.
- try:
- text = self.read()
- except EOFError:
- return
-
- # Eval.
- try:
- result = self.eval(text)
- except KeyboardInterrupt as e: # KeyboardInterrupt doesn't inherit from Exception.
- raise
- except SystemExit:
- return
- except BaseException as e:
- self._handle_exception(e)
- else:
- # Print.
- if result is not None:
- self.show_result(result)
-
- # Loop.
- self.current_statement_index += 1
- self.signatures = []
+ text = self.read()
+ except EOFError:
+ return
- except KeyboardInterrupt as e:
- # Handle all possible `KeyboardInterrupt` errors. This can
- # happen during the `eval`, but also during the
- # `show_result` if something takes too long.
- # (Try/catch is around the whole block, because we want to
- # prevent that a Control-C keypress terminates the REPL in
- # any case.)
- self._handle_keyboard_interrupt(e)
+ # Run it; display the result (or errors if applicable).
+ self.run_and_show_expression(text)
finally:
if self.terminal_title:
clear_title()
self._remove_from_namespace()
+ async def run_and_show_expression_async(self, text):
+ loop = asyncio.get_event_loop()
+
+ try:
+ result = await self.eval_async(text)
+ except KeyboardInterrupt: # KeyboardInterrupt doesn't inherit from Exception.
+ raise
+ except SystemExit:
+ return
+ except BaseException as e:
+ self._handle_exception(e)
+ else:
+ # Print.
+ if result is not None:
+ await loop.run_in_executor(None, lambda: self.show_result(result))
+
+ # Loop.
+ self.current_statement_index += 1
+ self.signatures = []
+ # Return the result for future consumers.
+ return result
+
async def run_async(self) -> None:
"""
Run the REPL loop, but run the blocking parts in an executor, so that
@@ -169,24 +194,7 @@ class PythonRepl(PythonInput):
return
# Eval.
- try:
- result = await self.eval_async(text)
- except KeyboardInterrupt as e: # KeyboardInterrupt doesn't inherit from Exception.
- raise
- except SystemExit:
- return
- except BaseException as e:
- self._handle_exception(e)
- else:
- # Print.
- if result is not None:
- await loop.run_in_executor(
- None, lambda: self.show_result(result)
- )
-
- # Loop.
- self.current_statement_index += 1
- self.signatures = []
+ await self.run_and_show_expression_async(text)
except KeyboardInterrupt as e:
# XXX: This does not yet work properly. In some situations,
@@ -231,7 +239,10 @@ class PythonRepl(PythonInput):
# above, then `sys.exc_info()` would not report the right error.
# See issue: https://github.com/prompt-toolkit/ptpython/issues/435
code = self._compile_with_flags(line, "exec")
- exec(code, self.get_globals(), self.get_locals())
+ result = eval(code, self.get_globals(), self.get_locals())
+
+ if _has_coroutine_flag(code):
+ result = asyncio.get_event_loop().run_until_complete(result)
return None
@@ -263,9 +274,14 @@ class PythonRepl(PythonInput):
self._store_eval_result(result)
return result
- # If not a valid `eval` expression, run using `exec` instead.
+ # If not a valid `eval` expression, compile as `exec` expression
+ # but still run with eval to get an awaitable in case of a
+ # awaitable expression.
code = self._compile_with_flags(line, "exec")
- exec(code, self.get_globals(), self.get_locals())
+ result = eval(code, self.get_globals(), self.get_locals())
+
+ if _has_coroutine_flag(code):
+ result = await result
return None
@@ -277,7 +293,7 @@ class PythonRepl(PythonInput):
return super().get_compiler_flags() | PyCF_ALLOW_TOP_LEVEL_AWAIT
def _compile_with_flags(self, code: str, mode: str):
- " Compile code with the right compiler flags. "
+ "Compile code with the right compiler flags."
return compile(
code,
"<stdin>",
@@ -286,9 +302,9 @@ class PythonRepl(PythonInput):
dont_inherit=True,
)
- def show_result(self, result: object) -> None:
+ def _format_result_output(self, result: object) -> StyleAndTextTuples:
"""
- Show __repr__ for an `eval` result.
+ Format __repr__ for an `eval` result.
Note: this can raise `KeyboardInterrupt` if either calling `__repr__`,
`__pt_repr__` or formatting the output with "Black" takes to long
@@ -304,7 +320,7 @@ class PythonRepl(PythonInput):
except BaseException as e:
# Calling repr failed.
self._handle_exception(e)
- return
+ return []
try:
compile(result_repr, "", "eval")
@@ -315,12 +331,15 @@ class PythonRepl(PythonInput):
if self.enable_output_formatting:
# Inline import. Slightly speed up start-up time if black is
# not used.
- import black
-
- result_repr = black.format_str(
- result_repr,
- mode=black.FileMode(line_length=self.app.output.get_size().columns),
- )
+ try:
+ import black
+ except ImportError:
+ pass # no Black package in your installation
+ else:
+ result_repr = black.format_str(
+ result_repr,
+ mode=black.Mode(line_length=self.app.output.get_size().columns),
+ )
formatted_result_repr = to_formatted_text(
PygmentsTokens(list(_lex_python_result(result_repr)))
@@ -363,10 +382,18 @@ class PythonRepl(PythonInput):
out_prompt + [("", fragment_list_to_text(formatted_result_repr))]
)
+ return to_formatted_text(formatted_output)
+
+ def show_result(self, result: object) -> None:
+ """
+ Show __repr__ for an `eval` result and print to ouptut.
+ """
+ formatted_text_output = self._format_result_output(result)
+
if self.enable_pager:
- self.print_paginated_formatted_text(to_formatted_text(formatted_output))
+ self.print_paginated_formatted_text(formatted_text_output)
else:
- self.print_formatted_text(to_formatted_text(formatted_output))
+ self.print_formatted_text(formatted_text_output)
self.app.output.flush()
@@ -421,15 +448,7 @@ class PythonRepl(PythonInput):
# Run pager prompt in another thread.
# Same as for the input. This prevents issues with nested event
# loops.
- pager_result = None
-
- def in_thread() -> None:
- nonlocal pager_result
- pager_result = pager_prompt.prompt()
-
- th = threading.Thread(target=in_thread)
- th.start()
- th.join()
+ pager_result = pager_prompt.prompt(in_thread=True)
if pager_result == PagerResult.ABORT:
print("...")
@@ -494,9 +513,7 @@ class PythonRepl(PythonInput):
"""
return create_pager_prompt(self._current_style, self.title)
- def _handle_exception(self, e: BaseException) -> None:
- output = self.app.output
-
+ def _format_exception_output(self, e: BaseException) -> PygmentsTokens:
# Instead of just calling ``traceback.format_exc``, we take the
# traceback and skip the bottom calls of this framework.
t, v, tb = sys.exc_info()
@@ -525,9 +542,15 @@ class PythonRepl(PythonInput):
tokens = list(_lex_python_traceback(tb_str))
else:
tokens = [(Token, tb_str)]
+ return PygmentsTokens(tokens)
+
+ def _handle_exception(self, e: BaseException) -> None:
+ output = self.app.output
+
+ tokens = self._format_exception_output(e)
print_formatted_text(
- PygmentsTokens(tokens),
+ tokens,
style=self._current_style,
style_transformation=self.style_transformation,
include_default_pygments_style=False,
@@ -564,13 +587,13 @@ class PythonRepl(PythonInput):
def _lex_python_traceback(tb):
- " Return token list for traceback string. "
+ "Return token list for traceback string."
lexer = PythonTracebackLexer()
return lexer.get_tokens(tb)
def _lex_python_result(tb):
- " Return token list for Python string. "
+ "Return token list for Python string."
lexer = PythonLexer()
# Use `get_tokens_unprocessed`, so that we get exactly the same string,
# without line endings appended. `print_formatted_text` already appends a
@@ -590,7 +613,9 @@ def enable_deprecation_warnings() -> None:
warnings.filterwarnings("default", category=DeprecationWarning, module="__main__")
-def run_config(repl: PythonInput, config_file: str = "~/.ptpython/config.py") -> None:
+def run_config(
+ repl: PythonInput, config_file: str = "~/.config/ptpython/config.py"
+) -> None:
"""
Execute REPL config file.
@@ -738,7 +763,7 @@ def create_pager_prompt(
@bindings.add("<any>")
def _(event: KeyPressEvent) -> None:
- " Disallow inserting other text. "
+ "Disallow inserting other text."
pass
style