diff options
author | arcadia-devtools <arcadia-devtools@yandex-team.ru> | 2022-04-06 18:18:01 +0300 |
---|---|---|
committer | arcadia-devtools <arcadia-devtools@yandex-team.ru> | 2022-04-06 18:18:01 +0300 |
commit | 01fbacb386809436dfa331780875aed72cb76118 (patch) | |
tree | 04c911ad96ff0523bd4d3e7a45c23cf2f2d7607d | |
parent | 48fb997d7f820a474b9094a72d9798a95ec612b7 (diff) | |
download | ydb-01fbacb386809436dfa331780875aed72cb76118.tar.gz |
intermediate changes
ref:b4f892f3c2b06a356c155f73c27efc5661a7fb89
25 files changed, 300 insertions, 152 deletions
diff --git a/contrib/python/prompt-toolkit/py3/.dist-info/METADATA b/contrib/python/prompt-toolkit/py3/.dist-info/METADATA index cdf27c97ff..bc64b6812b 100644 --- a/contrib/python/prompt-toolkit/py3/.dist-info/METADATA +++ b/contrib/python/prompt-toolkit/py3/.dist-info/METADATA @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: prompt-toolkit -Version: 3.0.28 +Version: 3.0.29 Summary: Library for building powerful interactive command lines in Python Home-page: https://github.com/prompt-toolkit/python-prompt-toolkit Author: Jonathan Slenders @@ -21,6 +21,8 @@ Classifier: Programming Language :: Python Classifier: Topic :: Software Development Requires-Python: >=3.6.2 Description-Content-Type: text/x-rst +License-File: LICENSE +License-File: AUTHORS.rst Requires-Dist: wcwidth Python Prompt Toolkit diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/__init__.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/__init__.py index f3423df38c..4b36db1a64 100644 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/__init__.py +++ b/contrib/python/prompt-toolkit/py3/prompt_toolkit/__init__.py @@ -18,7 +18,7 @@ from .formatted_text import ANSI, HTML from .shortcuts import PromptSession, print_formatted_text, prompt # Don't forget to update in `docs/conf.py`! -__version__ = "3.0.28" +__version__ = "3.0.29" # Version tuple. VERSION = tuple(__version__.split(".")) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/application/application.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/application/application.py index 07b81d5ec1..b00c2c2a73 100644 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/application/application.py +++ b/contrib/python/prompt-toolkit/py3/prompt_toolkit/application/application.py @@ -89,7 +89,6 @@ from prompt_toolkit.styles import ( ) from prompt_toolkit.utils import Event, in_main_thread -from ..utils import is_windows from .current import get_app_session, set_app from .run_in_terminal import in_terminal, run_in_terminal @@ -663,7 +662,7 @@ class Application(Generic[_AppResult]): """ assert not self._is_running, "Application is already running." - if not in_main_thread() or is_windows(): + if not in_main_thread() or sys.platform == "win32": # Handling signals in other threads is not supported. # Also on Windows, `add_signal_handler(signal.SIGINT, ...)` raises # `NotImplementedError`. @@ -935,7 +934,11 @@ class Application(Generic[_AppResult]): set_event_loop(loop) return loop.run_until_complete( - self.run_async(pre_run=pre_run, set_exception_handler=set_exception_handler) + self.run_async( + pre_run=pre_run, + set_exception_handler=set_exception_handler, + handle_sigint=handle_sigint, + ) ) def _handle_exception( diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/ssh/server.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/ssh/server.py index ba11036fc2..2b5935557d 100644 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/ssh/server.py +++ b/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/ssh/server.py @@ -10,7 +10,7 @@ import asyncssh from prompt_toolkit.application.current import AppSession, create_app_session from prompt_toolkit.data_structures import Size from prompt_toolkit.eventloop import get_event_loop -from prompt_toolkit.input import create_pipe_input +from prompt_toolkit.input import PipeInput, create_pipe_input from prompt_toolkit.output.vt100 import Vt100_Output __all__ = ["PromptToolkitSSHSession", "PromptToolkitSSHServer"] @@ -28,7 +28,7 @@ class PromptToolkitSSHSession(asyncssh.SSHServerSession): # type: ignore # PipInput object, for sending input in the CLI. # (This is something that we can use in the prompt_toolkit event loop, # but still write date in manually.) - self._input = create_pipe_input() + self._input: Optional[PipeInput] = None self._output: Optional[Vt100_Output] = None # Output object. Don't render to the real stdout, but write everything @@ -88,16 +88,17 @@ class PromptToolkitSSHSession(asyncssh.SSHServerSession): # type: ignore self._output = Vt100_Output( self.stdout, self._get_size, term=term, write_binary=False ) - with create_app_session(input=self._input, output=self._output) as session: - self.app_session = session - try: - await self.interact(self) - except BaseException: - traceback.print_exc() - finally: - # Close the connection. - self._chan.close() - self._input.close() + with create_pipe_input() as self._input: + with create_app_session(input=self._input, output=self._output) as session: + self.app_session = session + try: + await self.interact(self) + except BaseException: + traceback.print_exc() + finally: + # Close the connection. + self._chan.close() + self._input.close() def terminal_size_changed( self, width: int, height: int, pixwidth: object, pixheight: object @@ -107,6 +108,10 @@ class PromptToolkitSSHSession(asyncssh.SSHServerSession): # type: ignore self.app_session.app._on_resize() def data_received(self, data: str, datatype: object) -> None: + if self._input is None: + # Should not happen. + return + self._input.send_text(data) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/telnet/server.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/telnet/server.py index 2e042c95d1..4dfeb7fe05 100644 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/telnet/server.py +++ b/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/telnet/server.py @@ -11,7 +11,7 @@ from prompt_toolkit.application.run_in_terminal import run_in_terminal from prompt_toolkit.data_structures import Size from prompt_toolkit.eventloop import get_event_loop from prompt_toolkit.formatted_text import AnyFormattedText, to_formatted_text -from prompt_toolkit.input import create_pipe_input +from prompt_toolkit.input import PipeInput, create_pipe_input from prompt_toolkit.output.vt100 import Vt100_Output from prompt_toolkit.renderer import print_formatted_text as print_formatted_text from prompt_toolkit.styles import BaseStyle, DummyStyle @@ -87,6 +87,7 @@ class _ConnectionStdout: self._connection = connection self._errors = "strict" self._buffer: List[bytes] = [] + self._closed = False def write(self, data: str) -> None: data = data.replace("\n", "\r\n") @@ -98,12 +99,16 @@ class _ConnectionStdout: def flush(self) -> None: try: - self._connection.send(b"".join(self._buffer)) + if not self._closed: + self._connection.send(b"".join(self._buffer)) except OSError as e: logger.warning("Couldn't send data over socket: %s" % e) self._buffer = [] + def close(self) -> None: + self._closed = True + @property def encoding(self) -> str: return self._encoding @@ -126,6 +131,7 @@ class TelnetConnection: server: "TelnetServer", encoding: str, style: Optional[BaseStyle], + vt100_input: PipeInput, ) -> None: self.conn = conn @@ -136,6 +142,7 @@ class TelnetConnection: self.style = style self._closed = False self._ready = asyncio.Event() + self.vt100_input = vt100_input self.vt100_output = None # Create "Output" object. @@ -144,9 +151,6 @@ class TelnetConnection: # Initialize. _initialize_telnet(conn) - # Create input. - self.vt100_input = create_pipe_input() - # Create output. def get_size() -> Size: return self.size @@ -160,8 +164,8 @@ class TelnetConnection: def size_received(rows: int, columns: int) -> None: """TelnetProtocolParser 'size_received' callback""" self.size = Size(rows=rows, columns=columns) - if self.vt100_output is not None: - get_app()._on_resize() + if self.vt100_output is not None and self.context: + self.context.run(lambda: get_app()._on_resize()) def ttype_received(ttype: str) -> None: """TelnetProtocolParser 'ttype_received' callback""" @@ -197,12 +201,6 @@ class TelnetConnection: with create_app_session(input=self.vt100_input, output=self.vt100_output): self.context = contextvars.copy_context() await self.interact(self) - except Exception as e: - print("Got %s" % type(e).__name__, e) - import traceback - - traceback.print_exc() - raise finally: self.close() @@ -222,6 +220,7 @@ class TelnetConnection: self.vt100_input.close() get_event_loop().remove_reader(self.conn) self.conn.close() + self.stdout.close() def send(self, formatted_text: AnyFormattedText) -> None: """ @@ -336,22 +335,43 @@ class TelnetServer: conn, addr = self._listen_socket.accept() logger.info("New connection %r %r", *addr) - connection = TelnetConnection( - conn, addr, self.interact, self, encoding=self.encoding, style=self.style - ) - self.connections.add(connection) - # Run application for this connection. async def run() -> None: - logger.info("Starting interaction %r %r", *addr) try: - await connection.run_application() - except Exception as e: - print(e) + with create_pipe_input() as vt100_input: + connection = TelnetConnection( + conn, + addr, + self.interact, + self, + encoding=self.encoding, + style=self.style, + vt100_input=vt100_input, + ) + self.connections.add(connection) + + logger.info("Starting interaction %r %r", *addr) + try: + await connection.run_application() + finally: + self.connections.remove(connection) + logger.info("Stopping interaction %r %r", *addr) + except EOFError: + # Happens either when the connection is closed by the client + # (e.g., when the user types 'control-]', then 'quit' in the + # telnet client) or when the user types control-d in a prompt + # and this is not handled by the interact function. + logger.info("Unhandled EOFError in telnet application.") + except KeyboardInterrupt: + # Unhandled control-c propagated by a prompt. + logger.info("Unhandled KeyboardInterrupt in telnet application.") + except BaseException as e: + print("Got %s" % type(e).__name__, e) + import traceback + + traceback.print_exc() finally: - self.connections.remove(connection) self._application_tasks.remove(task) - logger.info("Stopping interaction %r %r", *addr) task = get_event_loop().create_task(run()) self._application_tasks.append(task) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/eventloop/inputhook.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/eventloop/inputhook.py index 26228a2af3..05d298117e 100644 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/eventloop/inputhook.py +++ b/contrib/python/prompt-toolkit/py3/prompt_toolkit/eventloop/inputhook.py @@ -26,13 +26,12 @@ import asyncio import os import select import selectors +import sys import threading from asyncio import AbstractEventLoop from selectors import BaseSelector, SelectorKey from typing import TYPE_CHECKING, Any, Callable, List, Mapping, Optional, Tuple -from prompt_toolkit.utils import is_windows - from .utils import get_event_loop __all__ = [ @@ -141,7 +140,7 @@ class InputHookSelector(BaseSelector): # However, if we would ever want to add a select call, it # should use `windll.kernel32.WaitForMultipleObjects`, # because `select.select` can't wait for a pipe on Windows. - if not is_windows(): + if sys.platform != "win32": select.select([self._r], [], [], None) os.read(self._r, 1024) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/eventloop/win32.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/eventloop/win32.py index a53632e0e8..fbc02d493a 100644 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/eventloop/win32.py +++ b/contrib/python/prompt-toolkit/py3/prompt_toolkit/eventloop/win32.py @@ -1,3 +1,7 @@ +import sys + +assert sys.platform == "win32" + from ctypes import pointer from ..utils import SPHINX_AUTODOC_RUNNING diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/__init__.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/__init__.py index 421d4ccdf4..dc319769ef 100644 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/__init__.py +++ b/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/__init__.py @@ -1,9 +1,10 @@ -from .base import DummyInput, Input +from .base import DummyInput, Input, PipeInput from .defaults import create_input, create_pipe_input __all__ = [ # Base. "Input", + "PipeInput", "DummyInput", # Defaults. "create_input", diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/base.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/base.py index 9885a37bc2..313622de5a 100644 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/base.py +++ b/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/base.py @@ -9,6 +9,7 @@ from prompt_toolkit.key_binding import KeyPress __all__ = [ "Input", + "PipeInput", "DummyInput", ] @@ -104,6 +105,9 @@ class PipeInput(Input): class DummyInput(Input): """ Input for use in a `DummyApplication` + + If used in an actual application, it will make the application render + itself once and exit immediately, due to an `EOFError`. """ def fileno(self) -> int: @@ -117,6 +121,8 @@ class DummyInput(Input): @property def closed(self) -> bool: + # This needs to be true, so that the dummy input will trigger an + # `EOFError` immediately in the application. return True def raw_mode(self) -> ContextManager[None]: diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/defaults.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/defaults.py index 347f8c6ad3..197dcb9a60 100644 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/defaults.py +++ b/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/defaults.py @@ -1,7 +1,5 @@ import sys -from typing import Optional, TextIO - -from prompt_toolkit.utils import is_windows +from typing import ContextManager, Optional, TextIO from .base import DummyInput, Input, PipeInput @@ -23,7 +21,7 @@ def create_input( `sys.stdin`. (We can open `stdout` or `stderr` for reading, this is how a `$PAGER` works.) """ - if is_windows(): + if sys.platform == "win32": from .win32 import Win32Input # If `stdin` was assigned `None` (which happens with pythonw.exe), use @@ -48,16 +46,24 @@ def create_input( return Vt100Input(stdin) -def create_pipe_input() -> PipeInput: +def create_pipe_input() -> ContextManager[PipeInput]: """ Create an input pipe. This is mostly useful for unit testing. + + Usage:: + + with create_pipe_input() as input: + input.send_text('inputdata') + + Breaking change: In prompt_toolkit 3.0.28 and earlier, this was returning + the `PipeInput` directly, rather than through a context manager. """ - if is_windows(): + if sys.platform == "win32": from .win32_pipe import Win32PipeInput - return Win32PipeInput() + return Win32PipeInput.create() else: from .posix_pipe import PosixPipeInput - return PosixPipeInput() + return PosixPipeInput.create() diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/posix_pipe.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/posix_pipe.py index 22dd7be6b5..1e7dec77fd 100644 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/posix_pipe.py +++ b/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/posix_pipe.py @@ -1,5 +1,10 @@ +import sys + +assert sys.platform != "win32" + import os -from typing import ContextManager, TextIO, cast +from contextlib import contextmanager +from typing import ContextManager, Iterator, TextIO, cast from ..utils import DummyContext from .base import PipeInput @@ -10,6 +15,36 @@ __all__ = [ ] +class _Pipe: + "Wrapper around os.pipe, that ensures we don't double close any end." + + def __init__(self) -> None: + self.read_fd, self.write_fd = os.pipe() + self._read_closed = False + self._write_closed = False + + def close_read(self) -> None: + "Close read-end if not yet closed." + if self._read_closed: + return + + os.close(self.read_fd) + self._read_closed = True + + def close_write(self) -> None: + "Close write-end if not yet closed." + if self._write_closed: + return + + os.close(self.write_fd) + self._write_closed = True + + def close(self) -> None: + "Close both read and write ends." + self.close_read() + self.close_write() + + class PosixPipeInput(Vt100Input, PipeInput): """ Input that is send through a pipe. @@ -18,14 +53,15 @@ class PosixPipeInput(Vt100Input, PipeInput): Usage:: - input = PosixPipeInput() - input.send_text('inputdata') + with PosixPipeInput.create() as input: + input.send_text('inputdata') """ _id = 0 - def __init__(self, text: str = "") -> None: - self._r, self._w = os.pipe() + def __init__(self, _pipe: _Pipe, _text: str = "") -> None: + # Private constructor. Users should use the public `.create()` method. + self.pipe = _pipe class Stdin: encoding = "utf-8" @@ -34,21 +70,30 @@ class PosixPipeInput(Vt100Input, PipeInput): return True def fileno(stdin) -> int: - return self._r + return self.pipe.read_fd super().__init__(cast(TextIO, Stdin())) - self.send_text(text) + self.send_text(_text) # Identifier for every PipeInput for the hash. self.__class__._id += 1 self._id = self.__class__._id + @classmethod + @contextmanager + def create(cls, text: str = "") -> Iterator["PosixPipeInput"]: + pipe = _Pipe() + try: + yield PosixPipeInput(_pipe=pipe, _text=text) + finally: + pipe.close() + def send_bytes(self, data: bytes) -> None: - os.write(self._w, data) + os.write(self.pipe.write_fd, data) def send_text(self, data: str) -> None: "Send text to the input." - os.write(self._w, data.encode("utf-8")) + os.write(self.pipe.write_fd, data.encode("utf-8")) def raw_mode(self) -> ContextManager[None]: return DummyContext() @@ -58,12 +103,11 @@ class PosixPipeInput(Vt100Input, PipeInput): def close(self) -> None: "Close pipe fds." - os.close(self._r) - os.close(self._w) - - # We should assign `None` to 'self._r` and 'self._w', - # The event loop still needs to know the the fileno for this input in order - # to properly remove it from the selectors. + # Only close the write-end of the pipe. This will unblock the reader + # callback (in vt100.py > _attached_input), which eventually will raise + # `EOFError`. If we'd also close the read-end, then the event loop + # won't wake up the corresponding callback because of this. + self.pipe.close_write() def typeahead_hash(self) -> str: """ diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/vt100.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/vt100.py index 639d372609..45ce37208a 100644 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/vt100.py +++ b/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/vt100.py @@ -1,6 +1,9 @@ +import sys + +assert sys.platform != "win32" + import contextlib import io -import sys import termios import tty from asyncio import AbstractEventLoop diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/win32.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/win32.py index c59375b3d4..db3fa2badd 100644 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/win32.py +++ b/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/win32.py @@ -7,6 +7,8 @@ from prompt_toolkit.eventloop import get_event_loop from ..utils import SPHINX_AUTODOC_RUNNING +assert sys.platform == "win32" + # Do not import win32-specific stuff when generating documentation. # Otherwise RTD would be unable to generate docs for this module. if not SPHINX_AUTODOC_RUNNING: diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/win32_pipe.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/win32_pipe.py index fdbcb8ee83..ebee2075ed 100644 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/win32_pipe.py +++ b/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/win32_pipe.py @@ -1,6 +1,11 @@ +import sys + +assert sys.platform == "win32" + +from contextlib import contextmanager from ctypes import windll from ctypes.wintypes import HANDLE -from typing import Callable, ContextManager, List +from typing import Callable, ContextManager, Iterator, List from prompt_toolkit.eventloop.win32 import create_win32_event @@ -31,7 +36,7 @@ class Win32PipeInput(_Win32InputBase, PipeInput): _id = 0 - def __init__(self) -> None: + def __init__(self, _event: HANDLE) -> None: super().__init__() # Event (handle) for registering this input in the event loop. # This event is set when there is data available to read from the pipe. @@ -50,6 +55,15 @@ class Win32PipeInput(_Win32InputBase, PipeInput): self.__class__._id += 1 self._id = self.__class__._id + @classmethod + @contextmanager + def create(cls) -> Iterator["Win32PipeInput"]: + event = create_win32_event() + try: + yield Win32PipeInput(_event=event) + finally: + windll.kernel32.CloseHandle(event) + @property def closed(self) -> bool: return self._closed @@ -87,7 +101,9 @@ class Win32PipeInput(_Win32InputBase, PipeInput): self._buffer = [] # Reset event. - windll.kernel32.ResetEvent(self._event) + if not self._closed: + # (If closed, the event should not reset.) + windll.kernel32.ResetEvent(self._event) return result @@ -111,6 +127,9 @@ class Win32PipeInput(_Win32InputBase, PipeInput): def send_text(self, text: str) -> None: "Send text to the input." + if self._closed: + raise ValueError("Attempt to write into a closed pipe.") + # Pass it through our vt100 parser. self.vt100_parser.feed(text) @@ -124,9 +143,9 @@ class Win32PipeInput(_Win32InputBase, PipeInput): return DummyContext() def close(self) -> None: - "Close pipe handles." - windll.kernel32.CloseHandle(self._event) + "Close write-end of the pipe." self._closed = True + windll.kernel32.SetEvent(self._event) def typeahead_hash(self) -> str: """ diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/mouse.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/mouse.py index 949c33f72c..916cd41132 100644 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/mouse.py +++ b/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/mouse.py @@ -1,3 +1,4 @@ +import sys from typing import TYPE_CHECKING, FrozenSet from prompt_toolkit.data_structures import Point @@ -9,7 +10,6 @@ from prompt_toolkit.mouse_events import ( MouseEventType, MouseModifier, ) -from prompt_toolkit.utils import is_windows from ..key_bindings import KeyBindings @@ -202,7 +202,7 @@ def load_mouse_bindings() -> KeyBindings: mouse_event, x, y = map(ord, event.data[3:]) # TODO: Is it possible to add modifiers here? - mouse_button, mouse_event_type, mouse_modifier = typical_mouse_events[ + mouse_button, mouse_event_type, mouse_modifiers = typical_mouse_events[ mouse_event ] @@ -303,41 +303,42 @@ def load_mouse_bindings() -> KeyBindings: """ Handling of mouse events for Windows. """ - assert is_windows() # This key binding should only exist for Windows. - - # Parse data. - pieces = event.data.split(";") - - button = MouseButton(pieces[0]) - event_type = MouseEventType(pieces[1]) - x = int(pieces[2]) - y = int(pieces[3]) - - # Make coordinates absolute to the visible part of the terminal. - output = event.app.renderer.output - - from prompt_toolkit.output.win32 import Win32Output - from prompt_toolkit.output.windows10 import Windows10_Output - - if isinstance(output, (Win32Output, Windows10_Output)): - screen_buffer_info = output.get_win32_screen_buffer_info() - rows_above_cursor = ( - screen_buffer_info.dwCursorPosition.Y - event.app.renderer._cursor_pos.y - ) - y -= rows_above_cursor - - # Call the mouse event handler. - # (Can return `NotImplemented`.) - handler = event.app.renderer.mouse_handlers.mouse_handlers[y][x] - - return handler( - MouseEvent( - position=Point(x=x, y=y), - event_type=event_type, - button=button, - modifiers=UNKNOWN_MODIFIER, + # This key binding should only exist for Windows. + if sys.platform == "win32": + # Parse data. + pieces = event.data.split(";") + + button = MouseButton(pieces[0]) + event_type = MouseEventType(pieces[1]) + x = int(pieces[2]) + y = int(pieces[3]) + + # Make coordinates absolute to the visible part of the terminal. + output = event.app.renderer.output + + from prompt_toolkit.output.win32 import Win32Output + from prompt_toolkit.output.windows10 import Windows10_Output + + if isinstance(output, (Win32Output, Windows10_Output)): + screen_buffer_info = output.get_win32_screen_buffer_info() + rows_above_cursor = ( + screen_buffer_info.dwCursorPosition.Y + - event.app.renderer._cursor_pos.y + ) + y -= rows_above_cursor + + # Call the mouse event handler. + # (Can return `NotImplemented`.) + handler = event.app.renderer.mouse_handlers.mouse_handlers[y][x] + + return handler( + MouseEvent( + position=Point(x=x, y=y), + event_type=event_type, + button=button, + modifiers=UNKNOWN_MODIFIER, + ) ) - ) # No mouse handler found. Return `NotImplemented` so that we don't # invalidate the UI. diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/conemu.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/conemu.py index ee1ac41d4d..fc46cc4afd 100644 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/conemu.py +++ b/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/conemu.py @@ -1,3 +1,7 @@ +import sys + +assert sys.platform == "win32" + from typing import Any, Optional, TextIO from prompt_toolkit.data_structures import Size diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/defaults.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/defaults.py index bd4bf950c4..7fb0f8931d 100644 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/defaults.py +++ b/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/defaults.py @@ -5,7 +5,6 @@ from prompt_toolkit.utils import ( get_bell_environment_variable, get_term_environment_variable, is_conemu_ansi, - is_windows, ) from .base import DummyOutput, Output @@ -68,7 +67,7 @@ def create_output( while isinstance(stdout, StdoutProxy): stdout = stdout.original_stdout - if is_windows(): + if sys.platform == "win32": from .conemu import ConEmuOutput from .win32 import Win32Output from .windows10 import Windows10_Output, is_win_vt100_enabled diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/win32.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/win32.py index a73978c3ef..1724eae5da 100644 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/win32.py +++ b/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/win32.py @@ -1,3 +1,7 @@ +import sys + +assert sys.platform == "win32" + import os from ctypes import ArgumentError, byref, c_char, c_long, c_uint, c_ulong, pointer diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/windows10.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/windows10.py index 933f54a28f..d5d55f18ca 100644 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/windows10.py +++ b/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/windows10.py @@ -1,9 +1,12 @@ +import sys + +assert sys.platform == "win32" + from ctypes import byref, windll from ctypes.wintypes import DWORD, HANDLE from typing import Any, Optional, TextIO from prompt_toolkit.data_structures import Size -from prompt_toolkit.utils import is_windows from prompt_toolkit.win32_types import STD_OUTPUT_HANDLE from .base import Output @@ -84,7 +87,7 @@ def is_win_vt100_enabled() -> bool: Returns True when we're running Windows and VT100 escape sequences are supported. """ - if not is_windows(): + if sys.platform != "win32": return False hconsole = HANDLE(windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/shortcuts/prompt.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/shortcuts/prompt.py index a8d8a58555..4dc1b18d1c 100644 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/shortcuts/prompt.py +++ b/contrib/python/prompt-toolkit/py3/prompt_toolkit/shortcuts/prompt.py @@ -900,6 +900,7 @@ class PromptSession(Generic[_T]): accept_default: bool = False, pre_run: Optional[Callable[[], None]] = None, set_exception_handler: bool = True, + handle_sigint: bool = True, in_thread: bool = False, ) -> _T: """ @@ -1028,10 +1029,12 @@ class PromptSession(Generic[_T]): # dumb prompt. if self._output is None and is_dumb_terminal(): with self._dumb_prompt(self.message) as dump_app: - return dump_app.run(in_thread=in_thread) + return dump_app.run(in_thread=in_thread, handle_sigint=handle_sigint) return self.app.run( - set_exception_handler=set_exception_handler, in_thread=in_thread + set_exception_handler=set_exception_handler, + in_thread=in_thread, + handle_sigint=handle_sigint, ) @contextmanager @@ -1132,6 +1135,7 @@ class PromptSession(Generic[_T]): accept_default: bool = False, pre_run: Optional[Callable[[], None]] = None, set_exception_handler: bool = True, + handle_sigint: bool = True, ) -> _T: if message is not None: @@ -1219,9 +1223,11 @@ class PromptSession(Generic[_T]): # dumb prompt. if self._output is None and is_dumb_terminal(): with self._dumb_prompt(self.message) as dump_app: - return await dump_app.run_async() + return await dump_app.run_async(handle_sigint=handle_sigint) - return await self.app.run_async(set_exception_handler=set_exception_handler) + return await self.app.run_async( + set_exception_handler=set_exception_handler, handle_sigint=handle_sigint + ) def _add_pre_run_callables( self, pre_run: Optional[Callable[[], None]], accept_default: bool diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/shortcuts/utils.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/shortcuts/utils.py index c7ce74e627..a628f4b6ae 100644 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/shortcuts/utils.py +++ b/contrib/python/prompt-toolkit/py3/prompt_toolkit/shortcuts/utils.py @@ -183,20 +183,19 @@ def print_container( else: output = get_app_session().output - def exit_immediately() -> None: - # Use `call_from_executor` to exit "soon", so that we still render one - # initial time, before exiting the application. - get_event_loop().call_soon(lambda: app.exit()) - app: Application[None] = Application( layout=Layout(container=container), output=output, + # `DummyInput` will cause the application to terminate immediately. input=DummyInput(), style=_create_merged_style( style, include_default_pygments_style=include_default_pygments_style ), ) - app.run(pre_run=exit_immediately, in_thread=True) + try: + app.run(in_thread=True) + except EOFError: + pass def _create_merged_style( diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/utils.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/utils.py index 8b501e5b23..4ceded34c4 100644 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/utils.py +++ b/contrib/python/prompt-toolkit/py3/prompt_toolkit/utils.py @@ -188,24 +188,27 @@ def is_windows() -> bool: """ True when we are using Windows. """ - return sys.platform.startswith("win") # E.g. 'win32', not 'darwin' or 'linux2' + return sys.platform == "win32" # Not 'darwin' or 'linux2' def is_windows_vt100_supported() -> bool: """ True when we are using Windows, but VT100 escape sequences are supported. """ - # Import needs to be inline. Windows libraries are not always available. - from prompt_toolkit.output.windows10 import is_win_vt100_enabled + if sys.platform == "win32": + # Import needs to be inline. Windows libraries are not always available. + from prompt_toolkit.output.windows10 import is_win_vt100_enabled - return is_windows() and is_win_vt100_enabled() + return is_win_vt100_enabled() + + return False def is_conemu_ansi() -> bool: """ True when the ConEmu Windows console is used. """ - return is_windows() and os.environ.get("ConEmuANSI", "OFF") == "ON" + return sys.platform == "win32" and os.environ.get("ConEmuANSI", "OFF") == "ON" def in_main_thread() -> bool: diff --git a/contrib/python/prompt-toolkit/py3/tests/test_cli.py b/contrib/python/prompt-toolkit/py3/tests/test_cli.py index 678bc52636..53d1e4f284 100644 --- a/contrib/python/prompt-toolkit/py3/tests/test_cli.py +++ b/contrib/python/prompt-toolkit/py3/tests/test_cli.py @@ -45,9 +45,7 @@ def _feed_cli_with_input( if check_line_ending: assert text.endswith("\r") - inp = create_pipe_input() - - try: + with create_pipe_input() as inp: inp.send_text(text) session = PromptSession( input=inp, @@ -59,12 +57,9 @@ def _feed_cli_with_input( key_bindings=key_bindings, ) - result = session.prompt() + _ = session.prompt() return session.default_buffer.document, session.app - finally: - inp.close() - def test_simple_text_input(): # Simple text input, followed by enter. @@ -933,15 +928,12 @@ def test_accept_default(): """ Test `prompt(accept_default=True)`. """ - inp = create_pipe_input() - - session = PromptSession(input=inp, output=DummyOutput()) - result = session.prompt(default="hello", accept_default=True) - assert result == "hello" - - # Test calling prompt() for a second time. (We had an issue where the - # prompt reset between calls happened at the wrong time, breaking this.) - result = session.prompt(default="world", accept_default=True) - assert result == "world" - - inp.close() + with create_pipe_input() as inp: + session = PromptSession(input=inp, output=DummyOutput()) + result = session.prompt(default="hello", accept_default=True) + assert result == "hello" + + # Test calling prompt() for a second time. (We had an issue where the + # prompt reset between calls happened at the wrong time, breaking this.) + result = session.prompt(default="world", accept_default=True) + assert result == "world" diff --git a/contrib/python/prompt-toolkit/py3/tests/test_key_binding.py b/contrib/python/prompt-toolkit/py3/tests/test_key_binding.py index 617e71c1e1..6f03f2deab 100644 --- a/contrib/python/prompt-toolkit/py3/tests/test_key_binding.py +++ b/contrib/python/prompt-toolkit/py3/tests/test_key_binding.py @@ -1,3 +1,5 @@ +from contextlib import contextmanager + import pytest from prompt_toolkit.application import Application @@ -21,16 +23,21 @@ class Handlers: return func +@contextmanager def set_dummy_app(): """ Return a context manager that makes sure that this dummy application is active. This is important, because we need an `Application` with `is_done=False` flag, otherwise no keys will be processed. """ - app = Application( - layout=Layout(Window()), output=DummyOutput(), input=create_pipe_input() - ) - return set_app(app) + with create_pipe_input() as pipe_input: + app = Application( + layout=Layout(Window()), + output=DummyOutput(), + input=pipe_input, + ) + with set_app(app): + yield @pytest.fixture diff --git a/contrib/python/prompt-toolkit/py3/tests/test_shortcuts.py b/contrib/python/prompt-toolkit/py3/tests/test_shortcuts.py index dc4d65b272..10ee73a20e 100644 --- a/contrib/python/prompt-toolkit/py3/tests/test_shortcuts.py +++ b/contrib/python/prompt-toolkit/py3/tests/test_shortcuts.py @@ -1,4 +1,7 @@ +from prompt_toolkit.shortcuts import print_container from prompt_toolkit.shortcuts.prompt import _split_multiline_prompt +from prompt_toolkit.shortcuts.utils import print_container +from prompt_toolkit.widgets import Frame, TextArea def test_split_multiline_prompt(): @@ -49,3 +52,16 @@ def test_split_multiline_prompt(): assert has_before_tokens() is True assert before() == [("class:testclass", "\n")] assert first_input_line() == [("class:testclass", "a"), ("class:testclass", "b")] + + +def test_print_container(tmpdir): + # Call `print_container`, render to a dummy file. + f = tmpdir.join("output") + with open(f, "w") as fd: + print_container(Frame(TextArea(text="Hello world!\n"), title="Title"), file=fd) + + # Verify rendered output. + with open(f, "r") as fd: + text = fd.read() + assert "Hello world" in text + assert "Title" in text |