diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2021-07-17 07:40:54 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2021-07-17 07:40:54 +0000 |
commit | 8188ef390db734738d245336d1d619516ca746d4 (patch) | |
tree | 36dc902621c535d2567244ce020d614858c9ddee /ptpython/repl.py | |
parent | Adding upstream version 3.0.16. (diff) | |
download | ptpython-8188ef390db734738d245336d1d619516ca746d4.tar.xz ptpython-8188ef390db734738d245336d1d619516ca746d4.zip |
Adding upstream version 3.0.19.upstream/3.0.19
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ptpython/repl.py')
-rw-r--r-- | ptpython/repl.py | 191 |
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 |