diff --git a/examples/prompts/asyncio-prompt.py b/examples/prompts/asyncio-prompt.py index 5a30e73e27..6f94ed9513 100755 --- a/examples/prompts/asyncio-prompt.py +++ b/examples/prompts/asyncio-prompt.py @@ -1,6 +1,5 @@ #!/usr/bin/env python """ -(Python >= 3.6) This is an example of how to prompt inside an application that uses the asyncio eventloop. The ``prompt_toolkit`` library will make sure that when other coroutines are writing to stdout, they write above the prompt, not destroying diff --git a/examples/ssh/asyncssh-server.py b/examples/ssh/asyncssh-server.py index 1fa1800a46..3951528463 100755 --- a/examples/ssh/asyncssh-server.py +++ b/examples/ssh/asyncssh-server.py @@ -4,13 +4,13 @@ """ import asyncio import logging +from asyncio import run import asyncssh from pygments.lexers.html import HtmlLexer from prompt_toolkit.completion import WordCompleter from prompt_toolkit.contrib.ssh import PromptToolkitSSHServer, PromptToolkitSSHSession -from prompt_toolkit.eventloop import get_event_loop from prompt_toolkit.lexers import PygmentsLexer from prompt_toolkit.shortcuts import ProgressBar, print_formatted_text from prompt_toolkit.shortcuts.dialogs import input_dialog, yes_no_dialog @@ -101,22 +101,21 @@ async def interact(ssh_session: PromptToolkitSSHSession) -> None: await input_dialog("Input dialog", "Running over asyncssh").run_async() -def main(port=8222): +async def main(port=8222): # Set up logging. logging.basicConfig() logging.getLogger().setLevel(logging.DEBUG) - loop = get_event_loop() - loop.run_until_complete( - asyncssh.create_server( - lambda: PromptToolkitSSHServer(interact), - "", - port, - server_host_keys=["/etc/ssh/ssh_host_ecdsa_key"], - ) + await asyncssh.create_server( + lambda: PromptToolkitSSHServer(interact), + "", + port, + server_host_keys=["/etc/ssh/ssh_host_ecdsa_key"], ) - loop.run_forever() + + # Run forever. + await asyncio.Future() if __name__ == "__main__": - main() + asyncio.run(main()) diff --git a/examples/telnet/chat-app.py b/examples/telnet/chat-app.py index 809ca7a79d..2e3508d44e 100755 --- a/examples/telnet/chat-app.py +++ b/examples/telnet/chat-app.py @@ -6,9 +6,9 @@ """ import logging import random +from asyncio import Future, run from prompt_toolkit.contrib.telnet.server import TelnetServer -from prompt_toolkit.eventloop import get_event_loop from prompt_toolkit.formatted_text import HTML from prompt_toolkit.shortcuts import PromptSession, clear @@ -91,11 +91,13 @@ def _send_to_everyone(sender_connection, name, message, color): ) -def main(): +async def main(): server = TelnetServer(interact=interact, port=2323) server.start() - get_event_loop().run_forever() + + # Run forever. + await Future() if __name__ == "__main__": - main() + run(main()) diff --git a/examples/telnet/dialog.py b/examples/telnet/dialog.py index 86057116e1..c674a9da17 100755 --- a/examples/telnet/dialog.py +++ b/examples/telnet/dialog.py @@ -3,9 +3,9 @@ Example of a telnet application that displays a dialog window. """ import logging +from asyncio import Future, run from prompt_toolkit.contrib.telnet.server import TelnetServer -from prompt_toolkit.eventloop import get_event_loop from prompt_toolkit.shortcuts.dialogs import yes_no_dialog # Set up logging @@ -22,11 +22,13 @@ async def interact(connection): connection.send("Bye.\n") -def main(): +async def main(): server = TelnetServer(interact=interact, port=2323) server.start() - get_event_loop().run_forever() + + # Run forever. + await Future() if __name__ == "__main__": - main() + run(main()) diff --git a/examples/telnet/hello-world.py b/examples/telnet/hello-world.py index e9d48f6e09..68b6918afa 100755 --- a/examples/telnet/hello-world.py +++ b/examples/telnet/hello-world.py @@ -7,9 +7,9 @@ That is probably the preferred way if you only need Python 3 support. """ import logging +from asyncio import Future, run from prompt_toolkit.contrib.telnet.server import TelnetServer -from prompt_toolkit.eventloop import get_event_loop from prompt_toolkit.shortcuts import PromptSession, clear # Set up logging @@ -30,11 +30,10 @@ async def interact(connection): connection.send("Bye.\n") -def main(): +async def main(): server = TelnetServer(interact=interact, port=2323) - server.start() - get_event_loop().run_forever() + await server.run() if __name__ == "__main__": - main() + run(main()) diff --git a/examples/telnet/toolbar.py b/examples/telnet/toolbar.py index cc40ec1c56..d73a4db116 100755 --- a/examples/telnet/toolbar.py +++ b/examples/telnet/toolbar.py @@ -4,10 +4,10 @@ in the prompt. """ import logging +from asyncio import Future, run from prompt_toolkit.completion import WordCompleter from prompt_toolkit.contrib.telnet.server import TelnetServer -from prompt_toolkit.eventloop import get_event_loop from prompt_toolkit.shortcuts import PromptSession # Set up logging @@ -35,11 +35,10 @@ def get_toolbar(): connection.send("Bye.\n") -def main(): +async def main(): server = TelnetServer(interact=interact, port=2323) - server.start() - get_event_loop().run_forever() + await server.run() if __name__ == "__main__": - main() + run(main()) diff --git a/setup.py b/setup.py index 567e58e47d..61975989ff 100755 --- a/setup.py +++ b/setup.py @@ -30,26 +30,16 @@ def get_version(package): package_dir={"": "src"}, package_data={"prompt_toolkit": ["py.typed"]}, install_requires=["wcwidth"], - # We require Python 3.6.2 for three reasons: - # - Syntax for variable annotations - PEP 526. - # - Asynchronous generators - PEP 525. - # - New type annotations in 3.6.2 (i.e. NoReturn) - # Also, 3.6.0 doesn't have `typing.AsyncGenerator` yet. 3.6.1 does. - # Python 3.7 is suggested, because: + # We require Python 3.7, because we need: # - Context variables - PEP 567 - # (The current application is derived from a context variable.) - # There is no intension to support Python 3.5, because prompt_toolkit 2.0 - # does run fine on any older Python version starting from Python 2.6, and - # it is possible to write code that runs both against prompt_toolkit - # version 2 and 3. - python_requires=">=3.6.2", + # - `asyncio.run()` + python_requires=">=3.7.0", classifiers=[ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", diff --git a/src/prompt_toolkit/__init__.py b/src/prompt_toolkit/__init__.py index 2bfe7ed79a..720c970029 100644 --- a/src/prompt_toolkit/__init__.py +++ b/src/prompt_toolkit/__init__.py @@ -13,6 +13,8 @@ Probably, to get started, you might also want to have a look at `prompt_toolkit.shortcuts.prompt`. """ +from __future__ import annotations + import re # note: this is a bit more lax than the actual pep 440 to allow for a/b/rc/dev without a number diff --git a/src/prompt_toolkit/application/__init__.py b/src/prompt_toolkit/application/__init__.py index dc61ca73c3..569d8c094a 100644 --- a/src/prompt_toolkit/application/__init__.py +++ b/src/prompt_toolkit/application/__init__.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from .application import Application from .current import ( AppSession, diff --git a/src/prompt_toolkit/application/application.py b/src/prompt_toolkit/application/application.py index 9cbb2d0a1c..47b13c652e 100644 --- a/src/prompt_toolkit/application/application.py +++ b/src/prompt_toolkit/application/application.py @@ -1,4 +1,7 @@ +from __future__ import annotations + import asyncio +import contextvars import os import re import signal @@ -10,8 +13,7 @@ Future, Task, ensure_future, - new_event_loop, - set_event_loop, + get_running_loop, sleep, ) from contextlib import ExitStack, contextmanager @@ -49,7 +51,7 @@ get_traceback_from_context, run_in_executor_with_context, ) -from prompt_toolkit.eventloop.utils import call_soon_threadsafe, get_event_loop +from prompt_toolkit.eventloop.utils import call_soon_threadsafe from prompt_toolkit.filters import Condition, Filter, FilterOrBool, to_filter from prompt_toolkit.formatted_text import AnyFormattedText from prompt_toolkit.input.base import Input @@ -93,12 +95,6 @@ from .current import get_app_session, set_app from .run_in_terminal import in_terminal, run_in_terminal -try: - import contextvars -except ImportError: - import prompt_toolkit.eventloop.dummy_contextvars as contextvars # type: ignore - - __all__ = [ "Application", ] @@ -195,36 +191,33 @@ class Application(Generic[_AppResult]): def __init__( self, - layout: Optional[Layout] = None, - style: Optional[BaseStyle] = None, + layout: Layout | None = None, + style: BaseStyle | None = None, include_default_pygments_style: FilterOrBool = True, - style_transformation: Optional[StyleTransformation] = None, - key_bindings: Optional[KeyBindingsBase] = None, - clipboard: Optional[Clipboard] = None, + style_transformation: StyleTransformation | None = None, + key_bindings: KeyBindingsBase | None = None, + clipboard: Clipboard | None = None, full_screen: bool = False, - color_depth: Union[ - ColorDepth, Callable[[], Union[ColorDepth, None]], None - ] = None, + color_depth: (ColorDepth | Callable[[], ColorDepth | None] | None) = None, mouse_support: FilterOrBool = False, - enable_page_navigation_bindings: Optional[ - FilterOrBool - ] = None, # Can be None, True or False. + enable_page_navigation_bindings: None + | (FilterOrBool) = None, # Can be None, True or False. paste_mode: FilterOrBool = False, editing_mode: EditingMode = EditingMode.EMACS, erase_when_done: bool = False, reverse_vi_search_direction: FilterOrBool = False, - min_redraw_interval: Union[float, int, None] = None, - max_render_postpone_time: Union[float, int, None] = 0.01, - refresh_interval: Optional[float] = None, - terminal_size_polling_interval: Optional[float] = 0.5, + min_redraw_interval: float | int | None = None, + max_render_postpone_time: float | int | None = 0.01, + refresh_interval: float | None = None, + terminal_size_polling_interval: float | None = 0.5, cursor: AnyCursorShapeConfig = None, - on_reset: Optional["ApplicationEventHandler[_AppResult]"] = None, - on_invalidate: Optional["ApplicationEventHandler[_AppResult]"] = None, - before_render: Optional["ApplicationEventHandler[_AppResult]"] = None, - after_render: Optional["ApplicationEventHandler[_AppResult]"] = None, + on_reset: ApplicationEventHandler[_AppResult] | None = None, + on_invalidate: ApplicationEventHandler[_AppResult] | None = None, + before_render: ApplicationEventHandler[_AppResult] | None = None, + after_render: ApplicationEventHandler[_AppResult] | None = None, # I/O. - input: Optional[Input] = None, - output: Optional[Output] = None, + input: Input | None = None, + output: Output | None = None, ) -> None: # If `enable_page_navigation_bindings` is not specified, enable it in # case of full screen applications only. This can be overridden by the user. @@ -281,12 +274,12 @@ def __init__( self.input = input or session.input # List of 'extra' functions to execute before a Application.run. - self.pre_run_callables: List[Callable[[], None]] = [] + self.pre_run_callables: list[Callable[[], None]] = [] self._is_running = False - self.future: Optional[Future[_AppResult]] = None - self.loop: Optional[AbstractEventLoop] = None - self.context: Optional[contextvars.Context] = None + self.future: Future[_AppResult] | None = None + self.loop: AbstractEventLoop | None = None + self.context: contextvars.Context | None = None #: Quoted insert. This flag is set if we go into quoted insert mode. self.quoted_insert = False @@ -331,7 +324,7 @@ def __init__( # Invalidate flag. When 'True', a repaint has been scheduled. self._invalidated = False - self._invalidate_events: List[ + self._invalidate_events: list[ Event[object] ] = [] # Collection of 'invalidate' Event objects. self._last_redraw_time = 0.0 # Unix timestamp of last redraw. Used when @@ -343,7 +336,7 @@ def __init__( # If `run_in_terminal` was called. This will point to a `Future` what will be # set at the point when the previous run finishes. self._running_in_terminal = False - self._running_in_terminal_f: Optional[Future[None]] = None + self._running_in_terminal_f: Future[None] | None = None # Trigger initialize callback. self.reset() @@ -431,7 +424,7 @@ def reset(self) -> None: self.exit_style = "" - self._background_tasks: Set[Task[None]] = set() + self._background_tasks: set[Task[None]] = set() self.renderer.reset() self.key_processor.reset() @@ -458,7 +451,7 @@ def invalidate(self) -> None: """ if not self._is_running: # Don't schedule a redraw if we're not running. - # Otherwise, `get_event_loop()` in `call_soon_threadsafe` can fail. + # Otherwise, `get_running_loop()` in `call_soon_threadsafe` can fail. # See: https://github.com/dbcli/mycli/issues/797 return @@ -611,7 +604,7 @@ def _on_resize(self) -> None: self._request_absolute_cursor_position() self._redraw() - def _pre_run(self, pre_run: Optional[Callable[[], None]] = None) -> None: + def _pre_run(self, pre_run: Callable[[], None] | None = None) -> None: """ Called during `run`. @@ -631,7 +624,7 @@ def _pre_run(self, pre_run: Optional[Callable[[], None]] = None) -> None: async def run_async( self, - pre_run: Optional[Callable[[], None]] = None, + pre_run: Callable[[], None] | None = None, set_exception_handler: bool = True, handle_sigint: bool = True, slow_callback_duration: float = 0.5, @@ -676,7 +669,7 @@ async def _run_async(f: "asyncio.Future[_AppResult]") -> _AppResult: # pressed, we start a 'flush' timer for flushing our escape key. But # when any subsequent input is received, a new timer is started and # the current timer will be ignored. - flush_task: Optional[asyncio.Task[None]] = None + flush_task: asyncio.Task[None] | None = None # Reset. # (`self.future` needs to be set when `pre_run` is called.) @@ -787,7 +780,7 @@ def flush_input() -> None: @contextmanager def get_loop() -> Iterator[AbstractEventLoop]: - loop = get_event_loop() + loop = get_running_loop() self.loop = loop try: @@ -846,7 +839,7 @@ def set_callback_duration(loop: AbstractEventLoop) -> Iterator[None]: @contextmanager def create_future( loop: AbstractEventLoop, - ) -> "Iterator[asyncio.Future[_AppResult]]": + ) -> Iterator[asyncio.Future[_AppResult]]: f = loop.create_future() self.future = f # XXX: make sure to set this before calling '_redraw'. @@ -895,7 +888,7 @@ def create_future( def run( self, - pre_run: Optional[Callable[[], None]] = None, + pre_run: Callable[[], None] | None = None, set_exception_handler: bool = True, handle_sigint: bool = True, in_thread: bool = False, @@ -928,7 +921,7 @@ def run( """ if in_thread: result: _AppResult - exception: Optional[BaseException] = None + exception: BaseException | None = None def run_in_thread() -> None: nonlocal result, exception @@ -941,14 +934,6 @@ def run_in_thread() -> None: ) except BaseException as e: exception = e - finally: - # Make sure to close the event loop in this thread. Running - # the application creates a new loop (because we're in - # another thread), but it doesn't get closed automatically - # (also not by the garbage collector). - loop = get_event_loop() - loop.run_until_complete(loop.shutdown_asyncgens()) - loop.close() thread = threading.Thread(target=run_in_thread) thread.start() @@ -958,30 +943,25 @@ def run_in_thread() -> None: raise exception return result - # We don't create a new event loop by default, because we want to be - # sure that when this is called multiple times, each call of `run()` - # goes through the same event loop. This way, users can schedule - # background-tasks that keep running across multiple prompts. + coro = self.run_async( + pre_run=pre_run, + set_exception_handler=set_exception_handler, + handle_sigint=handle_sigint, + ) try: - loop = get_event_loop() + # See whether a loop was installed already. If so, use that. That's + # required for the input hooks to work, they are installed using + # `set_event_loop`. + loop = asyncio.get_event_loop() except RuntimeError: - # Possibly we are not running in the main thread, where no event - # loop is set by default. Or somebody called `asyncio.run()` - # before, which closes the existing event loop. We can create a new - # loop. - loop = new_event_loop() - set_event_loop(loop) - - return loop.run_until_complete( - self.run_async( - pre_run=pre_run, - set_exception_handler=set_exception_handler, - handle_sigint=handle_sigint, - ) - ) + # No loop installed. Run like usual. + return asyncio.run(coro) + else: + # Use existing loop. + return loop.run_until_complete(coro) def _handle_exception( - self, loop: AbstractEventLoop, context: Dict[str, Any] + self, loop: AbstractEventLoop, context: dict[str, Any] ) -> None: """ Handler for event loop exceptions. @@ -1057,7 +1037,7 @@ def trace_dispatch( def create_background_task( self, coroutine: Coroutine[Any, Any, None] - ) -> "asyncio.Task[None]": + ) -> asyncio.Task[None]: """ Start a background task (coroutine) for the running application. When the `Application` terminates, unfinished background tasks will be @@ -1074,14 +1054,14 @@ def create_background_task( This is not threadsafe. """ - loop = self.loop or get_event_loop() + loop = self.loop or get_running_loop() task: asyncio.Task[None] = loop.create_task(coroutine) self._background_tasks.add(task) task.add_done_callback(self._on_background_task_done) return task - def _on_background_task_done(self, task: "asyncio.Task[None]") -> None: + def _on_background_task_done(self, task: asyncio.Task[None]) -> None: """ Called when a background task completes. Remove it from `_background_tasks`, and handle exceptions if any. @@ -1093,7 +1073,7 @@ def _on_background_task_done(self, task: "asyncio.Task[None]") -> None: exc = task.exception() if exc is not None: - get_event_loop().call_exception_handler( + get_running_loop().call_exception_handler( { "message": f"prompt_toolkit.Application background task {task!r} " "raised an unexpected exception.", @@ -1142,7 +1122,7 @@ async def _poll_output_size(self) -> None: - If we are not running in the main thread. - On Windows. """ - size: Optional[Size] = None + size: Size | None = None interval = self.terminal_size_polling_interval if interval is None: @@ -1181,14 +1161,14 @@ def exit(self, *, result: _AppResult, style: str = "") -> None: @overload def exit( - self, *, exception: Union[BaseException, Type[BaseException]], style: str = "" + self, *, exception: BaseException | type[BaseException], style: str = "" ) -> None: "Exit with exception." def exit( self, - result: Optional[_AppResult] = None, - exception: Optional[Union[BaseException, Type[BaseException]]] = None, + result: _AppResult | None = None, + exception: BaseException | type[BaseException] | None = None, style: str = "", ) -> None: """ @@ -1303,7 +1283,7 @@ def run() -> None: run_in_terminal(run) def print_text( - self, text: AnyFormattedText, style: Optional[BaseStyle] = None + self, text: AnyFormattedText, style: BaseStyle | None = None ) -> None: """ Print a list of (style_str, text) tuples to the output. @@ -1332,7 +1312,7 @@ def is_done(self) -> bool: return self.future.done() return False - def get_used_style_strings(self) -> List[str]: + def get_used_style_strings(self) -> list[str]: """ Return a list of used style strings. This is helpful for debugging, and for writing a new `Style`. @@ -1358,7 +1338,7 @@ class _CombinedRegistry(KeyBindingsBase): def __init__(self, app: Application[_AppResult]) -> None: self.app = app self._cache: SimpleCache[ - Tuple[Window, FrozenSet[UIControl]], KeyBindingsBase + tuple[Window, frozenset[UIControl]], KeyBindingsBase ] = SimpleCache() @property @@ -1368,13 +1348,13 @@ def _version(self) -> Hashable: raise NotImplementedError @property - def bindings(self) -> List[Binding]: + def bindings(self) -> list[Binding]: """Not needed - this object is not going to be wrapped in another KeyBindings object.""" raise NotImplementedError def _create_key_bindings( - self, current_window: Window, other_controls: List[UIControl] + self, current_window: Window, other_controls: list[UIControl] ) -> KeyBindingsBase: """ Create a `KeyBindings` object that merges the `KeyBindings` from the @@ -1437,10 +1417,10 @@ def _key_bindings(self) -> KeyBindingsBase: key, lambda: self._create_key_bindings(current_window, other_controls) ) - def get_bindings_for_keys(self, keys: KeysTuple) -> List[Binding]: + def get_bindings_for_keys(self, keys: KeysTuple) -> list[Binding]: return self._key_bindings.get_bindings_for_keys(keys) - def get_bindings_starting_with_keys(self, keys: KeysTuple) -> List[Binding]: + def get_bindings_starting_with_keys(self, keys: KeysTuple) -> list[Binding]: return self._key_bindings.get_bindings_starting_with_keys(keys) @@ -1501,7 +1481,7 @@ def attach_winch_signal_handler( # Keep track of the previous handler. # (Only UnixSelectorEventloop has `_signal_handlers`.) - loop = get_event_loop() + loop = get_running_loop() previous_winch_handler = getattr(loop, "_signal_handlers", {}).get(sigwinch) try: diff --git a/src/prompt_toolkit/application/current.py b/src/prompt_toolkit/application/current.py index d7141f21ef..3a44699df3 100644 --- a/src/prompt_toolkit/application/current.py +++ b/src/prompt_toolkit/application/current.py @@ -1,12 +1,10 @@ +from __future__ import annotations + import sys from contextlib import contextmanager +from contextvars import ContextVar from typing import TYPE_CHECKING, Any, Generator, Optional -try: - from contextvars import ContextVar -except ImportError: - from prompt_toolkit.eventloop.dummy_contextvars import ContextVar # type: ignore - if TYPE_CHECKING: from prompt_toolkit.input.base import Input from prompt_toolkit.output.base import Output @@ -42,20 +40,20 @@ class AppSession: """ def __init__( - self, input: Optional["Input"] = None, output: Optional["Output"] = None + self, input: Input | None = None, output: Output | None = None ) -> None: self._input = input self._output = output # The application will be set dynamically by the `set_app` context # manager. This is called in the application itself. - self.app: Optional["Application[Any]"] = None + self.app: Application[Any] | None = None def __repr__(self) -> str: return f"AppSession(app={self.app!r})" @property - def input(self) -> "Input": + def input(self) -> Input: if self._input is None: from prompt_toolkit.input.defaults import create_input @@ -63,7 +61,7 @@ def input(self) -> "Input": return self._input @property - def output(self) -> "Output": + def output(self) -> Output: if self._output is None: from prompt_toolkit.output.defaults import create_output @@ -71,7 +69,7 @@ def output(self) -> "Output": return self._output -_current_app_session: ContextVar["AppSession"] = ContextVar( +_current_app_session: ContextVar[AppSession] = ContextVar( "_current_app_session", default=AppSession() ) @@ -80,7 +78,7 @@ def get_app_session() -> AppSession: return _current_app_session.get() -def get_app() -> "Application[Any]": +def get_app() -> Application[Any]: """ Get the current active (running) Application. An :class:`.Application` is active during the @@ -108,7 +106,7 @@ def get_app() -> "Application[Any]": return DummyApplication() -def get_app_or_none() -> Optional["Application[Any]"]: +def get_app_or_none() -> Application[Any] | None: """ Get the current active (running) Application, or return `None` if no application is running. @@ -118,7 +116,7 @@ def get_app_or_none() -> Optional["Application[Any]"]: @contextmanager -def set_app(app: "Application[Any]") -> Generator[None, None, None]: +def set_app(app: Application[Any]) -> Generator[None, None, None]: """ Context manager that sets the given :class:`.Application` active in an `AppSession`. @@ -141,7 +139,7 @@ def set_app(app: "Application[Any]") -> Generator[None, None, None]: @contextmanager def create_app_session( - input: Optional["Input"] = None, output: Optional["Output"] = None + input: Input | None = None, output: Output | None = None ) -> Generator[AppSession, None, None]: """ Create a separate AppSession. diff --git a/src/prompt_toolkit/application/dummy.py b/src/prompt_toolkit/application/dummy.py index 4e5e4aafdd..195e36ae78 100644 --- a/src/prompt_toolkit/application/dummy.py +++ b/src/prompt_toolkit/application/dummy.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Callable, Optional from prompt_toolkit.formatted_text import AnyFormattedText @@ -22,7 +24,7 @@ def __init__(self) -> None: def run( self, - pre_run: Optional[Callable[[], None]] = None, + pre_run: Callable[[], None] | None = None, set_exception_handler: bool = True, handle_sigint: bool = True, in_thread: bool = False, @@ -31,7 +33,7 @@ def run( async def run_async( self, - pre_run: Optional[Callable[[], None]] = None, + pre_run: Callable[[], None] | None = None, set_exception_handler: bool = True, handle_sigint: bool = True, slow_callback_duration: float = 0.5, diff --git a/src/prompt_toolkit/application/run_in_terminal.py b/src/prompt_toolkit/application/run_in_terminal.py index d5ef8aafa3..b953623b45 100644 --- a/src/prompt_toolkit/application/run_in_terminal.py +++ b/src/prompt_toolkit/application/run_in_terminal.py @@ -1,20 +1,17 @@ """ Tools for running functions on the terminal above the current application or prompt. """ +from __future__ import annotations + import sys from asyncio import Future, ensure_future +from contextlib import asynccontextmanager from typing import AsyncGenerator, Awaitable, Callable, TypeVar from prompt_toolkit.eventloop import run_in_executor_with_context from .current import get_app_or_none -if sys.version_info >= (3, 7): - from contextlib import asynccontextmanager -else: - from prompt_toolkit.eventloop.async_context_manager import asynccontextmanager - - __all__ = [ "run_in_terminal", "in_terminal", diff --git a/src/prompt_toolkit/auto_suggest.py b/src/prompt_toolkit/auto_suggest.py index c049e167c5..c2496b1f5f 100644 --- a/src/prompt_toolkit/auto_suggest.py +++ b/src/prompt_toolkit/auto_suggest.py @@ -11,6 +11,8 @@ then wrap the :class:`.AutoSuggest` instance into a :class:`.ThreadedAutoSuggest`. """ +from __future__ import annotations + from abc import ABCMeta, abstractmethod from typing import TYPE_CHECKING, Callable, Optional, Union @@ -53,9 +55,7 @@ class AutoSuggest(metaclass=ABCMeta): """ @abstractmethod - def get_suggestion( - self, buffer: "Buffer", document: Document - ) -> Optional[Suggestion]: + def get_suggestion(self, buffer: Buffer, document: Document) -> Suggestion | None: """ Return `None` or a :class:`.Suggestion` instance. @@ -73,7 +73,7 @@ def get_suggestion( async def get_suggestion_async( self, buff: "Buffer", document: Document - ) -> Optional[Suggestion]: + ) -> Suggestion | None: """ Return a :class:`.Future` which is set when the suggestions are ready. This function can be overloaded in order to provide an asynchronous @@ -92,19 +92,17 @@ class ThreadedAutoSuggest(AutoSuggest): def __init__(self, auto_suggest: AutoSuggest) -> None: self.auto_suggest = auto_suggest - def get_suggestion( - self, buff: "Buffer", document: Document - ) -> Optional[Suggestion]: + def get_suggestion(self, buff: Buffer, document: Document) -> Suggestion | None: return self.auto_suggest.get_suggestion(buff, document) async def get_suggestion_async( self, buff: "Buffer", document: Document - ) -> Optional[Suggestion]: + ) -> Suggestion | None: """ Run the `get_suggestion` function in a thread. """ - def run_get_suggestion_thread() -> Optional[Suggestion]: + def run_get_suggestion_thread() -> Suggestion | None: return self.get_suggestion(buff, document) return await run_in_executor_with_context(run_get_suggestion_thread) @@ -115,9 +113,7 @@ class DummyAutoSuggest(AutoSuggest): AutoSuggest class that doesn't return any suggestion. """ - def get_suggestion( - self, buffer: "Buffer", document: Document - ) -> Optional[Suggestion]: + def get_suggestion(self, buffer: Buffer, document: Document) -> Suggestion | None: return None # No suggestion @@ -126,9 +122,7 @@ class AutoSuggestFromHistory(AutoSuggest): Give suggestions based on the lines in the history. """ - def get_suggestion( - self, buffer: "Buffer", document: Document - ) -> Optional[Suggestion]: + def get_suggestion(self, buffer: Buffer, document: Document) -> Suggestion | None: history = buffer.history # Consider only the last line for the suggestion. @@ -150,13 +144,11 @@ class ConditionalAutoSuggest(AutoSuggest): Auto suggest that can be turned on and of according to a certain condition. """ - def __init__(self, auto_suggest: AutoSuggest, filter: Union[bool, Filter]) -> None: + def __init__(self, auto_suggest: AutoSuggest, filter: bool | Filter) -> None: self.auto_suggest = auto_suggest self.filter = to_filter(filter) - def get_suggestion( - self, buffer: "Buffer", document: Document - ) -> Optional[Suggestion]: + def get_suggestion(self, buffer: Buffer, document: Document) -> Suggestion | None: if self.filter(): return self.auto_suggest.get_suggestion(buffer, document) @@ -170,17 +162,15 @@ class DynamicAutoSuggest(AutoSuggest): :param get_validator: Callable that returns a :class:`.Validator` instance. """ - def __init__(self, get_auto_suggest: Callable[[], Optional[AutoSuggest]]) -> None: + def __init__(self, get_auto_suggest: Callable[[], AutoSuggest | None]) -> None: self.get_auto_suggest = get_auto_suggest - def get_suggestion( - self, buff: "Buffer", document: Document - ) -> Optional[Suggestion]: + def get_suggestion(self, buff: Buffer, document: Document) -> Suggestion | None: auto_suggest = self.get_auto_suggest() or DummyAutoSuggest() return auto_suggest.get_suggestion(buff, document) async def get_suggestion_async( self, buff: "Buffer", document: Document - ) -> Optional[Suggestion]: + ) -> Suggestion | None: auto_suggest = self.get_auto_suggest() or DummyAutoSuggest() return await auto_suggest.get_suggestion_async(buff, document) diff --git a/src/prompt_toolkit/buffer.py b/src/prompt_toolkit/buffer.py index 4f731a55e9..a3424fdd13 100644 --- a/src/prompt_toolkit/buffer.py +++ b/src/prompt_toolkit/buffer.py @@ -2,6 +2,8 @@ Data structures for the Buffer. It holds the text, cursor position, history, etc... """ +from __future__ import annotations + import asyncio import logging import os @@ -80,9 +82,9 @@ class CompletionState: def __init__( self, - original_document: "Document", - completions: Optional[List["Completion"]] = None, - complete_index: Optional[int] = None, + original_document: Document, + completions: list[Completion] | None = None, + complete_index: int | None = None, ) -> None: #: Document as it was when the completion started. self.original_document = original_document @@ -103,7 +105,7 @@ def __repr__(self) -> str: self.complete_index, ) - def go_to_index(self, index: Optional[int]) -> None: + def go_to_index(self, index: int | None) -> None: """ Create a new :class:`.CompletionState` object with the new index. @@ -113,7 +115,7 @@ def go_to_index(self, index: Optional[int]) -> None: assert index is None or 0 <= index < len(self.completions) self.complete_index = index - def new_text_and_position(self) -> Tuple[str, int]: + def new_text_and_position(self) -> tuple[str, int]: """ Return (new_text, new_cursor_position) for this completion. """ @@ -134,7 +136,7 @@ def new_text_and_position(self) -> Tuple[str, int]: return new_text, new_cursor_position @property - def current_completion(self) -> Optional["Completion"]: + def current_completion(self) -> Completion | None: """ Return the current completion, or return `None` when no completion is selected. @@ -231,25 +233,25 @@ class Buffer: def __init__( self, - completer: Optional[Completer] = None, - auto_suggest: Optional[AutoSuggest] = None, - history: Optional[History] = None, - validator: Optional[Validator] = None, - tempfile_suffix: Union[str, Callable[[], str]] = "", - tempfile: Union[str, Callable[[], str]] = "", + completer: Completer | None = None, + auto_suggest: AutoSuggest | None = None, + history: History | None = None, + validator: Validator | None = None, + tempfile_suffix: str | Callable[[], str] = "", + tempfile: str | Callable[[], str] = "", name: str = "", complete_while_typing: FilterOrBool = False, validate_while_typing: FilterOrBool = False, enable_history_search: FilterOrBool = False, - document: Optional[Document] = None, - accept_handler: Optional[BufferAcceptHandler] = None, + document: Document | None = None, + accept_handler: BufferAcceptHandler | None = None, read_only: FilterOrBool = False, multiline: FilterOrBool = True, - on_text_changed: Optional[BufferEventHandler] = None, - on_text_insert: Optional[BufferEventHandler] = None, - on_cursor_position_changed: Optional[BufferEventHandler] = None, - on_completions_changed: Optional[BufferEventHandler] = None, - on_suggestion_set: Optional[BufferEventHandler] = None, + on_text_changed: BufferEventHandler | None = None, + on_text_insert: BufferEventHandler | None = None, + on_cursor_position_changed: BufferEventHandler | None = None, + on_completions_changed: BufferEventHandler | None = None, + on_suggestion_set: BufferEventHandler | None = None, ): # Accept both filters and booleans as input. enable_history_search = to_filter(enable_history_search) @@ -284,19 +286,17 @@ def __init__( self.__cursor_position = 0 # Events - self.on_text_changed: Event["Buffer"] = Event(self, on_text_changed) - self.on_text_insert: Event["Buffer"] = Event(self, on_text_insert) - self.on_cursor_position_changed: Event["Buffer"] = Event( + self.on_text_changed: Event[Buffer] = Event(self, on_text_changed) + self.on_text_insert: Event[Buffer] = Event(self, on_text_insert) + self.on_cursor_position_changed: Event[Buffer] = Event( self, on_cursor_position_changed ) - self.on_completions_changed: Event["Buffer"] = Event( - self, on_completions_changed - ) - self.on_suggestion_set: Event["Buffer"] = Event(self, on_suggestion_set) + self.on_completions_changed: Event[Buffer] = Event(self, on_completions_changed) + self.on_suggestion_set: Event[Buffer] = Event(self, on_suggestion_set) # Document cache. (Avoid creating new Document instances.) self._document_cache: FastDictCache[ - Tuple[str, int, Optional[SelectionState]], Document + tuple[str, int, SelectionState | None], Document ] = FastDictCache(Document, size=10) # Create completer / auto suggestion / validation coroutines. @@ -305,7 +305,7 @@ def __init__( self._async_validator = self._create_auto_validate_coroutine() # Asyncio task for populating the history. - self._load_history_task: Optional[asyncio.Future[None]] = None + self._load_history_task: asyncio.Future[None] | None = None # Reset other attributes. self.reset(document=document) @@ -319,7 +319,7 @@ def __repr__(self) -> str: return f"" def reset( - self, document: Optional[Document] = None, append_to_history: bool = False + self, document: Document | None = None, append_to_history: bool = False ) -> None: """ :param append_to_history: Append current input to history first. @@ -332,41 +332,41 @@ def reset( self.__cursor_position = document.cursor_position # `ValidationError` instance. (Will be set when the input is wrong.) - self.validation_error: Optional[ValidationError] = None - self.validation_state: Optional[ValidationState] = ValidationState.UNKNOWN + self.validation_error: ValidationError | None = None + self.validation_state: ValidationState | None = ValidationState.UNKNOWN # State of the selection. - self.selection_state: Optional[SelectionState] = None + self.selection_state: SelectionState | None = None # Multiple cursor mode. (When we press 'I' or 'A' in visual-block mode, # we can insert text on multiple lines at once. This is implemented by # using multiple cursors.) - self.multiple_cursor_positions: List[int] = [] + self.multiple_cursor_positions: list[int] = [] # When doing consecutive up/down movements, prefer to stay at this column. - self.preferred_column: Optional[int] = None + self.preferred_column: int | None = None # State of complete browser # For interactive completion through Ctrl-N/Ctrl-P. - self.complete_state: Optional[CompletionState] = None + self.complete_state: CompletionState | None = None # State of Emacs yank-nth-arg completion. - self.yank_nth_arg_state: Optional[YankNthArgState] = None # for yank-nth-arg. + self.yank_nth_arg_state: YankNthArgState | None = None # for yank-nth-arg. # Remember the document that we had *right before* the last paste # operation. This is used for rotating through the kill ring. - self.document_before_paste: Optional[Document] = None + self.document_before_paste: Document | None = None # Current suggestion. - self.suggestion: Optional[Suggestion] = None + self.suggestion: Suggestion | None = None # The history search text. (Used for filtering the history when we # browse through it.) - self.history_search_text: Optional[str] = None + self.history_search_text: str | None = None # Undo/redo stacks (stack of `(text, cursor_position)`). - self._undo_stack: List[Tuple[str, int]] = [] - self._redo_stack: List[Tuple[str, int]] = [] + self._undo_stack: list[tuple[str, int]] = [] + self._redo_stack: list[tuple[str, int]] = [] # Cancel history loader. If history loading was still ongoing. # Cancel the `_load_history_task`, so that next repaint of the @@ -413,7 +413,7 @@ async def load_history() -> None: self._load_history_task = get_app().create_background_task(load_history()) - def load_history_done(f: "asyncio.Future[None]") -> None: + def load_history_done(f: asyncio.Future[None]) -> None: """ Handle `load_history` result when either done, cancelled, or when an exception was raised. @@ -880,7 +880,7 @@ def complete_next(self, count: int = 1, disable_wrap_around: bool = False) -> No Browse to the next completions. (Does nothing if there are no completion.) """ - index: Optional[int] + index: int | None if self.complete_state: completions_count = len(self.complete_state.completions) @@ -905,7 +905,7 @@ def complete_previous( Browse to the previous completions. (Does nothing if there are no completion.) """ - index: Optional[int] + index: int | None if self.complete_state: if self.complete_state.complete_index == 0: @@ -928,7 +928,7 @@ def cancel_completion(self) -> None: self.go_to_completion(None) self.complete_state = None - def _set_completions(self, completions: List[Completion]) -> CompletionState: + def _set_completions(self, completions: list[Completion]) -> CompletionState: """ Start completions. (Generate list of completions and initialize.) @@ -948,7 +948,7 @@ def start_history_lines_completion(self) -> None: Start a completion based on all the other lines in the document and the history. """ - found_completions: Set[str] = set() + found_completions: set[str] = set() completions = [] # For every line of the whole history, find matches with the current line. @@ -979,7 +979,7 @@ def start_history_lines_completion(self) -> None: self._set_completions(completions=completions[::-1]) self.go_to_completion(0) - def go_to_completion(self, index: Optional[int]) -> None: + def go_to_completion(self, index: int | None) -> None: """ Select a completion from the list of current completions. """ @@ -1074,9 +1074,7 @@ def history_backward(self, count: int = 1) -> None: if found_something: self.cursor_position = len(self.text) - def yank_nth_arg( - self, n: Optional[int] = None, _yank_last_arg: bool = False - ) -> None: + def yank_nth_arg(self, n: int | None = None, _yank_last_arg: bool = False) -> None: """ Pick nth word from previous history entry (depending on current `yank_nth_arg_state`) and insert it at current position. Rotate through @@ -1127,7 +1125,7 @@ def yank_nth_arg( state.history_position = new_pos self.yank_nth_arg_state = state - def yank_last_arg(self, n: Optional[int] = None) -> None: + def yank_last_arg(self, n: int | None = None) -> None: """ Like `yank_nth_arg`, but if no argument has been given, yank the last word by default. @@ -1388,7 +1386,7 @@ def _search( search_state: SearchState, include_current_position: bool = False, count: int = 1, - ) -> Optional[Tuple[int, int]]: + ) -> tuple[int, int] | None: """ Execute search. Return (working_index, cursor_position) tuple when this search is applied. Returns `None` when this text cannot be found. @@ -1401,7 +1399,7 @@ def _search( def search_once( working_index: int, document: Document - ) -> Optional[Tuple[int, Document]]: + ) -> tuple[int, Document] | None: """ Do search one time. Return (working_index, document) or `None` @@ -1539,7 +1537,7 @@ def apply_search( def exit_selection(self) -> None: self.selection_state = None - def _editor_simple_tempfile(self) -> Tuple[str, Callable[[], None]]: + def _editor_simple_tempfile(self) -> tuple[str, Callable[[], None]]: """ Simple (file) tempfile implementation. Return (tempfile, cleanup_func). @@ -1555,7 +1553,7 @@ def cleanup() -> None: return filename, cleanup - def _editor_complex_tempfile(self) -> Tuple[str, Callable[[], None]]: + def _editor_complex_tempfile(self) -> tuple[str, Callable[[], None]]: # Complex (directory) tempfile implementation. headtail = to_str(self.tempfile) if not headtail: @@ -1584,7 +1582,7 @@ def cleanup() -> None: return filename, cleanup - def open_in_editor(self, validate_and_handle: bool = False) -> "asyncio.Task[None]": + def open_in_editor(self, validate_and_handle: bool = False) -> asyncio.Task[None]: """ Open code in editor. @@ -1672,7 +1670,7 @@ def start_completion( select_first: bool = False, select_last: bool = False, insert_common_part: bool = False, - complete_event: Optional[CompleteEvent] = None, + complete_event: CompleteEvent | None = None, ) -> None: """ Start asynchronous autocompletion of this buffer. @@ -1715,7 +1713,7 @@ async def async_completer( select_first: bool = False, select_last: bool = False, insert_common_part: bool = False, - complete_event: Optional[CompleteEvent] = None, + complete_event: CompleteEvent | None = None, ) -> None: document = self.document complete_event = complete_event or CompleteEvent(text_inserted=True) diff --git a/src/prompt_toolkit/cache.py b/src/prompt_toolkit/cache.py index e5e9d70eca..ada8d9856a 100644 --- a/src/prompt_toolkit/cache.py +++ b/src/prompt_toolkit/cache.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from collections import deque from functools import wraps from typing import Any, Callable, Deque, Dict, Generic, Hashable, Tuple, TypeVar, cast @@ -23,7 +25,7 @@ class SimpleCache(Generic[_T, _U]): def __init__(self, maxsize: int = 8) -> None: assert maxsize > 0 - self._data: Dict[_T, _U] = {} + self._data: dict[_T, _U] = {} self._keys: Deque[_T] = deque() self.maxsize: int = maxsize diff --git a/src/prompt_toolkit/clipboard/__init__.py b/src/prompt_toolkit/clipboard/__init__.py index 160b50aca5..e72f30e4da 100644 --- a/src/prompt_toolkit/clipboard/__init__.py +++ b/src/prompt_toolkit/clipboard/__init__.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from .base import Clipboard, ClipboardData, DummyClipboard, DynamicClipboard from .in_memory import InMemoryClipboard diff --git a/src/prompt_toolkit/clipboard/base.py b/src/prompt_toolkit/clipboard/base.py index 8921290ffc..4e03f205d8 100644 --- a/src/prompt_toolkit/clipboard/base.py +++ b/src/prompt_toolkit/clipboard/base.py @@ -1,6 +1,8 @@ """ Clipboard for command line interface. """ +from __future__ import annotations + from abc import ABCMeta, abstractmethod from typing import Callable, Optional @@ -87,7 +89,7 @@ class DynamicClipboard(Clipboard): :param get_clipboard: Callable that returns a :class:`.Clipboard` instance. """ - def __init__(self, get_clipboard: Callable[[], Optional[Clipboard]]) -> None: + def __init__(self, get_clipboard: Callable[[], Clipboard | None]) -> None: self.get_clipboard = get_clipboard def _clipboard(self) -> Clipboard: diff --git a/src/prompt_toolkit/clipboard/in_memory.py b/src/prompt_toolkit/clipboard/in_memory.py index 3f4b698009..07d1c0cd0d 100644 --- a/src/prompt_toolkit/clipboard/in_memory.py +++ b/src/prompt_toolkit/clipboard/in_memory.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from collections import deque from typing import Deque, Optional @@ -16,9 +18,7 @@ class InMemoryClipboard(Clipboard): This implements a kill-ring, for Emacs mode. """ - def __init__( - self, data: Optional[ClipboardData] = None, max_size: int = 60 - ) -> None: + def __init__(self, data: ClipboardData | None = None, max_size: int = 60) -> None: assert max_size >= 1 self.max_size = max_size diff --git a/src/prompt_toolkit/clipboard/pyperclip.py b/src/prompt_toolkit/clipboard/pyperclip.py index c8cb7afb67..e5876e77ea 100644 --- a/src/prompt_toolkit/clipboard/pyperclip.py +++ b/src/prompt_toolkit/clipboard/pyperclip.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Optional import pyperclip @@ -18,7 +20,7 @@ class PyperclipClipboard(Clipboard): """ def __init__(self) -> None: - self._data: Optional[ClipboardData] = None + self._data: ClipboardData | None = None def set_data(self, data: ClipboardData) -> None: self._data = data diff --git a/src/prompt_toolkit/completion/__init__.py b/src/prompt_toolkit/completion/__init__.py index 270c74337c..f65a94e60a 100644 --- a/src/prompt_toolkit/completion/__init__.py +++ b/src/prompt_toolkit/completion/__init__.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from .base import ( CompleteEvent, Completer, diff --git a/src/prompt_toolkit/completion/base.py b/src/prompt_toolkit/completion/base.py index c929294e93..7180e825c7 100644 --- a/src/prompt_toolkit/completion/base.py +++ b/src/prompt_toolkit/completion/base.py @@ -1,5 +1,7 @@ """ """ +from __future__ import annotations + from abc import ABCMeta, abstractmethod from typing import AsyncGenerator, Callable, Iterable, Optional, Sequence @@ -41,8 +43,8 @@ def __init__( self, text: str, start_position: int = 0, - display: Optional[AnyFormattedText] = None, - display_meta: Optional[AnyFormattedText] = None, + display: AnyFormattedText | None = None, + display_meta: AnyFormattedText | None = None, style: str = "", selected_style: str = "", ) -> None: @@ -111,7 +113,7 @@ def display_meta_text(self) -> str: return fragment_list_to_text(self.display_meta) - def new_completion_from_position(self, position: int) -> "Completion": + def new_completion_from_position(self, position: int) -> Completion: """ (Only for internal use!) Get a new completion by splitting this one. Used by `Application` when @@ -267,7 +269,7 @@ async def get_completions_async( # def get_all_in_thread() -> List[Completion]: # return list(self.get_completions(document, complete_event)) - # completions = await get_event_loop().run_in_executor(None, get_all_in_thread) + # completions = await get_running_loop().run_in_executor(None, get_all_in_thread) # for completion in completions: # yield completion @@ -304,7 +306,7 @@ class DynamicCompleter(Completer): :param get_completer: Callable that returns a :class:`.Completer` instance. """ - def __init__(self, get_completer: Callable[[], Optional[Completer]]) -> None: + def __init__(self, get_completer: Callable[[], Completer | None]) -> None: self.get_completer = get_completer def get_completions( diff --git a/src/prompt_toolkit/completion/deduplicate.py b/src/prompt_toolkit/completion/deduplicate.py index 6ef95224a6..cc78991e66 100644 --- a/src/prompt_toolkit/completion/deduplicate.py +++ b/src/prompt_toolkit/completion/deduplicate.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Iterable, Set from prompt_toolkit.document import Document @@ -23,7 +25,7 @@ def get_completions( self, document: Document, complete_event: CompleteEvent ) -> Iterable[Completion]: # Keep track of the document strings we'd get after applying any completion. - found_so_far: Set[str] = set() + found_so_far: set[str] = set() for completion in self.completer.get_completions(document, complete_event): text_if_applied = ( diff --git a/src/prompt_toolkit/completion/filesystem.py b/src/prompt_toolkit/completion/filesystem.py index 77ad82c3c9..038131ae32 100644 --- a/src/prompt_toolkit/completion/filesystem.py +++ b/src/prompt_toolkit/completion/filesystem.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os from typing import Callable, Iterable, List, Optional @@ -25,8 +27,8 @@ class PathCompleter(Completer): def __init__( self, only_directories: bool = False, - get_paths: Optional[Callable[[], List[str]]] = None, - file_filter: Optional[Callable[[str], bool]] = None, + get_paths: Callable[[], list[str]] | None = None, + file_filter: Callable[[str], bool] | None = None, min_input_len: int = 0, expanduser: bool = False, ) -> None: diff --git a/src/prompt_toolkit/completion/fuzzy_completer.py b/src/prompt_toolkit/completion/fuzzy_completer.py index f18d44c1b1..a3025aab6f 100644 --- a/src/prompt_toolkit/completion/fuzzy_completer.py +++ b/src/prompt_toolkit/completion/fuzzy_completer.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import re from typing import Callable, Dict, Iterable, List, NamedTuple, Optional, Tuple, Union @@ -47,7 +49,7 @@ def __init__( self, completer: Completer, WORD: bool = False, - pattern: Optional[str] = None, + pattern: str | None = None, enable_fuzzy: FilterOrBool = True, ) -> None: assert pattern is None or pattern.startswith("^") @@ -90,7 +92,7 @@ def _get_fuzzy_completions( self.completer.get_completions(document2, complete_event) ) - fuzzy_matches: List[_FuzzyMatch] = [] + fuzzy_matches: list[_FuzzyMatch] = [] if word_before_cursor == "": # If word before the cursor is an empty string, consider all @@ -110,7 +112,7 @@ def _get_fuzzy_completions( _FuzzyMatch(len(best.group(1)), best.start(), compl) ) - def sort_key(fuzzy_match: "_FuzzyMatch") -> Tuple[int, int]: + def sort_key(fuzzy_match: _FuzzyMatch) -> tuple[int, int]: "Sort by start position, then by the length of the match." return fuzzy_match.start_pos, fuzzy_match.match_length @@ -130,7 +132,7 @@ def sort_key(fuzzy_match: "_FuzzyMatch") -> Tuple[int, int]: ) def _get_display( - self, fuzzy_match: "_FuzzyMatch", word_before_cursor: str + self, fuzzy_match: _FuzzyMatch, word_before_cursor: str ) -> AnyFormattedText: """ Generate formatted text for the display label. @@ -185,8 +187,8 @@ class FuzzyWordCompleter(Completer): def __init__( self, - words: Union[List[str], Callable[[], List[str]]], - meta_dict: Optional[Dict[str, str]] = None, + words: list[str] | Callable[[], list[str]], + meta_dict: dict[str, str] | None = None, WORD: bool = False, ) -> None: self.words = words diff --git a/src/prompt_toolkit/completion/nested.py b/src/prompt_toolkit/completion/nested.py index ded08a8de5..74788295eb 100644 --- a/src/prompt_toolkit/completion/nested.py +++ b/src/prompt_toolkit/completion/nested.py @@ -1,6 +1,8 @@ """ Nestedcompleter for completion of hierarchical data structures. """ +from __future__ import annotations + from typing import Any, Dict, Iterable, Mapping, Optional, Set, Union from prompt_toolkit.completion import CompleteEvent, Completer, Completion @@ -26,7 +28,7 @@ class NestedCompleter(Completer): """ def __init__( - self, options: Dict[str, Optional[Completer]], ignore_case: bool = True + self, options: dict[str, Completer | None], ignore_case: bool = True ) -> None: self.options = options self.ignore_case = ignore_case @@ -35,7 +37,7 @@ def __repr__(self) -> str: return f"NestedCompleter({self.options!r}, ignore_case={self.ignore_case!r})" @classmethod - def from_nested_dict(cls, data: NestedDict) -> "NestedCompleter": + def from_nested_dict(cls, data: NestedDict) -> NestedCompleter: """ Create a `NestedCompleter`, starting from a nested dictionary data structure, like this: @@ -59,7 +61,7 @@ def from_nested_dict(cls, data: NestedDict) -> "NestedCompleter": Values in this data structure can be a completers as well. """ - options: Dict[str, Optional[Completer]] = {} + options: dict[str, Completer | None] = {} for key, value in data.items(): if isinstance(value, Completer): options[key] = value diff --git a/src/prompt_toolkit/completion/word_completer.py b/src/prompt_toolkit/completion/word_completer.py index dd7f78fc11..e740c68f4c 100644 --- a/src/prompt_toolkit/completion/word_completer.py +++ b/src/prompt_toolkit/completion/word_completer.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Callable, Iterable, List, Mapping, Optional, Pattern, Union from prompt_toolkit.completion import CompleteEvent, Completer, Completion @@ -31,14 +33,14 @@ class WordCompleter(Completer): def __init__( self, - words: Union[List[str], Callable[[], List[str]]], + words: list[str] | Callable[[], list[str]], ignore_case: bool = False, - display_dict: Optional[Mapping[str, AnyFormattedText]] = None, - meta_dict: Optional[Mapping[str, AnyFormattedText]] = None, + display_dict: Mapping[str, AnyFormattedText] | None = None, + meta_dict: Mapping[str, AnyFormattedText] | None = None, WORD: bool = False, sentence: bool = False, match_middle: bool = False, - pattern: Optional[Pattern[str]] = None, + pattern: Pattern[str] | None = None, ) -> None: assert not (WORD and sentence) diff --git a/src/prompt_toolkit/contrib/completers/__init__.py b/src/prompt_toolkit/contrib/completers/__init__.py index 50ca2f9127..172fe6f567 100644 --- a/src/prompt_toolkit/contrib/completers/__init__.py +++ b/src/prompt_toolkit/contrib/completers/__init__.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from .system import SystemCompleter __all__ = ["SystemCompleter"] diff --git a/src/prompt_toolkit/contrib/completers/system.py b/src/prompt_toolkit/contrib/completers/system.py index 9e63268b22..5d990e5280 100644 --- a/src/prompt_toolkit/contrib/completers/system.py +++ b/src/prompt_toolkit/contrib/completers/system.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from prompt_toolkit.completion.filesystem import ExecutableCompleter, PathCompleter from prompt_toolkit.contrib.regular_languages.compiler import compile from prompt_toolkit.contrib.regular_languages.completion import GrammarCompleter diff --git a/src/prompt_toolkit/contrib/regular_languages/__init__.py b/src/prompt_toolkit/contrib/regular_languages/__init__.py index 3f306142f7..1743af4e74 100644 --- a/src/prompt_toolkit/contrib/regular_languages/__init__.py +++ b/src/prompt_toolkit/contrib/regular_languages/__init__.py @@ -72,6 +72,8 @@ - How to create an autocompleter from this grammar. - How to create a parser from this grammar. """ +from __future__ import annotations + from .compiler import compile __all__ = ["compile"] diff --git a/src/prompt_toolkit/contrib/regular_languages/compiler.py b/src/prompt_toolkit/contrib/regular_languages/compiler.py index 5ac0a1a500..6063862dae 100644 --- a/src/prompt_toolkit/contrib/regular_languages/compiler.py +++ b/src/prompt_toolkit/contrib/regular_languages/compiler.py @@ -38,6 +38,8 @@ m.variables().get('operator2') # Returns "add" """ +from __future__ import annotations + import re from typing import Callable, Dict, Iterable, Iterator, List from typing import Match as RegexMatch @@ -81,15 +83,15 @@ class _CompiledGrammar: def __init__( self, root_node: Node, - escape_funcs: Optional[EscapeFuncDict] = None, - unescape_funcs: Optional[EscapeFuncDict] = None, + escape_funcs: EscapeFuncDict | None = None, + unescape_funcs: EscapeFuncDict | None = None, ) -> None: self.root_node = root_node self.escape_funcs = escape_funcs or {} self.unescape_funcs = unescape_funcs or {} #: Dictionary that will map the regex names to Node instances. - self._group_names_to_nodes: Dict[ + self._group_names_to_nodes: dict[ str, str ] = {} # Maps regex group names to varnames. counter = [0] @@ -356,7 +358,7 @@ def transform(node: Node) -> Iterable[str]: for r in transform(root_node): yield "^(?:%s)$" % r - def match(self, string: str) -> Optional["Match"]: + def match(self, string: str) -> Match | None: """ Match the string with the grammar. Returns a :class:`Match` instance or `None` when the input doesn't match the grammar. @@ -371,7 +373,7 @@ def match(self, string: str) -> Optional["Match"]: ) return None - def match_prefix(self, string: str) -> Optional["Match"]: + def match_prefix(self, string: str) -> Match | None: """ Do a partial match of the string with the grammar. The returned :class:`Match` instance can contain multiple representations of the @@ -404,21 +406,21 @@ class Match: def __init__( self, string: str, - re_matches: List[Tuple[Pattern[str], RegexMatch[str]]], - group_names_to_nodes: Dict[str, str], - unescape_funcs: Dict[str, Callable[[str], str]], + re_matches: list[tuple[Pattern[str], RegexMatch[str]]], + group_names_to_nodes: dict[str, str], + unescape_funcs: dict[str, Callable[[str], str]], ): self.string = string self._re_matches = re_matches self._group_names_to_nodes = group_names_to_nodes self._unescape_funcs = unescape_funcs - def _nodes_to_regs(self) -> List[Tuple[str, Tuple[int, int]]]: + def _nodes_to_regs(self) -> list[tuple[str, tuple[int, int]]]: """ Return a list of (varname, reg) tuples. """ - def get_tuples() -> Iterable[Tuple[str, Tuple[int, int]]]: + def get_tuples() -> Iterable[tuple[str, tuple[int, int]]]: for r, re_match in self._re_matches: for group_name, group_index in r.groupindex.items(): if group_name != _INVALID_TRAILING_INPUT: @@ -429,15 +431,15 @@ def get_tuples() -> Iterable[Tuple[str, Tuple[int, int]]]: return list(get_tuples()) - def _nodes_to_values(self) -> List[Tuple[str, str, Tuple[int, int]]]: + def _nodes_to_values(self) -> list[tuple[str, str, tuple[int, int]]]: """ Returns list of (Node, string_value) tuples. """ - def is_none(sl: Tuple[int, int]) -> bool: + def is_none(sl: tuple[int, int]) -> bool: return sl[0] == -1 and sl[1] == -1 - def get(sl: Tuple[int, int]) -> str: + def get(sl: tuple[int, int]) -> str: return self.string[sl[0] : sl[1]] return [ @@ -450,7 +452,7 @@ def _unescape(self, varname: str, value: str) -> str: unwrapper = self._unescape_funcs.get(varname) return unwrapper(value) if unwrapper else value - def variables(self) -> "Variables": + def variables(self) -> Variables: """ Returns :class:`Variables` instance. """ @@ -458,13 +460,13 @@ def variables(self) -> "Variables": [(k, self._unescape(k, v), sl) for k, v, sl in self._nodes_to_values()] ) - def trailing_input(self) -> Optional["MatchVariable"]: + def trailing_input(self) -> MatchVariable | None: """ Get the `MatchVariable` instance, representing trailing input, if there is any. "Trailing input" is input at the end that does not match the grammar anymore, but when this is removed from the end of the input, the input would be a valid string. """ - slices: List[Tuple[int, int]] = [] + slices: list[tuple[int, int]] = [] # Find all regex group for the name _INVALID_TRAILING_INPUT. for r, re_match in self._re_matches: @@ -480,7 +482,7 @@ def trailing_input(self) -> Optional["MatchVariable"]: return MatchVariable("", value, slice) return None - def end_nodes(self) -> Iterable["MatchVariable"]: + def end_nodes(self) -> Iterable[MatchVariable]: """ Yields `MatchVariable` instances for all the nodes having their end position at the end of the input string. @@ -493,7 +495,7 @@ def end_nodes(self) -> Iterable["MatchVariable"]: class Variables: - def __init__(self, tuples: List[Tuple[str, str, Tuple[int, int]]]) -> None: + def __init__(self, tuples: list[tuple[str, str, tuple[int, int]]]) -> None: #: List of (varname, value, slice) tuples. self._tuples = tuples @@ -503,17 +505,17 @@ def __repr__(self) -> str: ", ".join(f"{k}={v!r}" for k, v, _ in self._tuples), ) - def get(self, key: str, default: Optional[str] = None) -> Optional[str]: + def get(self, key: str, default: str | None = None) -> str | None: items = self.getall(key) return items[0] if items else default - def getall(self, key: str) -> List[str]: + def getall(self, key: str) -> list[str]: return [v for k, v, _ in self._tuples if k == key] - def __getitem__(self, key: str) -> Optional[str]: + def __getitem__(self, key: str) -> str | None: return self.get(key) - def __iter__(self) -> Iterator["MatchVariable"]: + def __iter__(self) -> Iterator[MatchVariable]: """ Yield `MatchVariable` instances. """ @@ -531,7 +533,7 @@ class MatchVariable: in the input string. """ - def __init__(self, varname: str, value: str, slice: Tuple[int, int]) -> None: + def __init__(self, varname: str, value: str, slice: tuple[int, int]) -> None: self.varname = varname self.value = value self.slice = slice @@ -545,8 +547,8 @@ def __repr__(self) -> str: def compile( expression: str, - escape_funcs: Optional[EscapeFuncDict] = None, - unescape_funcs: Optional[EscapeFuncDict] = None, + escape_funcs: EscapeFuncDict | None = None, + unescape_funcs: EscapeFuncDict | None = None, ) -> _CompiledGrammar: """ Compile grammar (given as regex string), returning a `CompiledGrammar` @@ -561,8 +563,8 @@ def compile( def _compile_from_parse_tree( root_node: Node, - escape_funcs: Optional[EscapeFuncDict] = None, - unescape_funcs: Optional[EscapeFuncDict] = None, + escape_funcs: EscapeFuncDict | None = None, + unescape_funcs: EscapeFuncDict | None = None, ) -> _CompiledGrammar: """ Compile grammar (given as parse tree), returning a `CompiledGrammar` diff --git a/src/prompt_toolkit/contrib/regular_languages/completion.py b/src/prompt_toolkit/contrib/regular_languages/completion.py index d51a949a58..5761b6696e 100644 --- a/src/prompt_toolkit/contrib/regular_languages/completion.py +++ b/src/prompt_toolkit/contrib/regular_languages/completion.py @@ -1,6 +1,8 @@ """ Completer for a regular grammar. """ +from __future__ import annotations + from typing import Dict, Iterable, List from prompt_toolkit.completion import CompleteEvent, Completer, Completion @@ -24,7 +26,7 @@ class GrammarCompleter(Completer): """ def __init__( - self, compiled_grammar: _CompiledGrammar, completers: Dict[str, Completer] + self, compiled_grammar: _CompiledGrammar, completers: dict[str, Completer] ) -> None: self.compiled_grammar = compiled_grammar self.completers = completers @@ -79,13 +81,13 @@ def _get_completions_for_match( display_meta=completion.display_meta, ) - def _remove_duplicates(self, items: Iterable[Completion]) -> List[Completion]: + def _remove_duplicates(self, items: Iterable[Completion]) -> list[Completion]: """ Remove duplicates, while keeping the order. (Sometimes we have duplicates, because the there several matches of the same grammar, each yielding similar completions.) """ - result: List[Completion] = [] + result: list[Completion] = [] for i in items: if i not in result: result.append(i) diff --git a/src/prompt_toolkit/contrib/regular_languages/lexer.py b/src/prompt_toolkit/contrib/regular_languages/lexer.py index 30d05d455c..7cbf4e2518 100644 --- a/src/prompt_toolkit/contrib/regular_languages/lexer.py +++ b/src/prompt_toolkit/contrib/regular_languages/lexer.py @@ -2,6 +2,8 @@ `GrammarLexer` is compatible with other lexers and can be used to highlight the input using a regular grammar with annotations. """ +from __future__ import annotations + from typing import Callable, Dict, Optional from prompt_toolkit.document import Document @@ -35,7 +37,7 @@ def __init__( self, compiled_grammar: _CompiledGrammar, default_style: str = "", - lexers: Optional[Dict[str, Lexer]] = None, + lexers: dict[str, Lexer] | None = None, ) -> None: self.compiled_grammar = compiled_grammar self.default_style = default_style diff --git a/src/prompt_toolkit/contrib/regular_languages/regex_parser.py b/src/prompt_toolkit/contrib/regular_languages/regex_parser.py index 87e39d498c..c473fc1b5c 100644 --- a/src/prompt_toolkit/contrib/regular_languages/regex_parser.py +++ b/src/prompt_toolkit/contrib/regular_languages/regex_parser.py @@ -14,6 +14,8 @@ Limitations: - Lookahead is not supported. """ +from __future__ import annotations + import re from typing import List, Optional @@ -33,10 +35,10 @@ class Node: (You don't initialize this one.) """ - def __add__(self, other_node: "Node") -> "NodeSequence": + def __add__(self, other_node: Node) -> NodeSequence: return NodeSequence([self, other_node]) - def __or__(self, other_node: "Node") -> "AnyNode": + def __or__(self, other_node: Node) -> AnyNode: return AnyNode([self, other_node]) @@ -47,10 +49,10 @@ class AnyNode(Node): operation. """ - def __init__(self, children: List[Node]) -> None: + def __init__(self, children: list[Node]) -> None: self.children = children - def __or__(self, other_node: Node) -> "AnyNode": + def __or__(self, other_node: Node) -> AnyNode: return AnyNode(self.children + [other_node]) def __repr__(self) -> str: @@ -63,10 +65,10 @@ class NodeSequence(Node): yourself, but it's a result of a "Grammar1 + Grammar2" operation. """ - def __init__(self, children: List[Node]) -> None: + def __init__(self, children: list[Node]) -> None: self.children = children - def __add__(self, other_node: Node) -> "NodeSequence": + def __add__(self, other_node: Node) -> NodeSequence: return NodeSequence(self.children + [other_node]) def __repr__(self) -> str: @@ -126,7 +128,7 @@ def __init__( self, childnode: Node, min_repeat: int = 0, - max_repeat: Optional[int] = None, + max_repeat: int | None = None, greedy: bool = True, ) -> None: self.childnode = childnode @@ -138,7 +140,7 @@ def __repr__(self) -> str: return f"{self.__class__.__name__}(childnode={self.childnode!r})" -def tokenize_regex(input: str) -> List[str]: +def tokenize_regex(input: str) -> list[str]: """ Takes a string, representing a regular expression as input, and tokenizes it. @@ -191,14 +193,14 @@ def tokenize_regex(input: str) -> List[str]: return tokens -def parse_regex(regex_tokens: List[str]) -> Node: +def parse_regex(regex_tokens: list[str]) -> Node: """ Takes a list of tokens from the tokenizer, and returns a parse tree. """ # We add a closing brace because that represents the final pop of the stack. - tokens: List[str] = [")"] + regex_tokens[::-1] + tokens: list[str] = [")"] + regex_tokens[::-1] - def wrap(lst: List[Node]) -> Node: + def wrap(lst: list[Node]) -> Node: """Turn list into sequence when it contains several items.""" if len(lst) == 1: return lst[0] @@ -206,8 +208,8 @@ def wrap(lst: List[Node]) -> Node: return NodeSequence(lst) def _parse() -> Node: - or_list: List[List[Node]] = [] - result: List[Node] = [] + or_list: list[list[Node]] = [] + result: list[Node] = [] def wrapped_result() -> Node: if or_list == []: diff --git a/src/prompt_toolkit/contrib/regular_languages/validation.py b/src/prompt_toolkit/contrib/regular_languages/validation.py index ce25e20626..98b2a71638 100644 --- a/src/prompt_toolkit/contrib/regular_languages/validation.py +++ b/src/prompt_toolkit/contrib/regular_languages/validation.py @@ -1,6 +1,8 @@ """ Validator for a regular language. """ +from __future__ import annotations + from typing import Dict from prompt_toolkit.document import Document @@ -24,7 +26,7 @@ class GrammarValidator(Validator): """ def __init__( - self, compiled_grammar: _CompiledGrammar, validators: Dict[str, Validator] + self, compiled_grammar: _CompiledGrammar, validators: dict[str, Validator] ) -> None: self.compiled_grammar = compiled_grammar self.validators = validators diff --git a/src/prompt_toolkit/contrib/ssh/__init__.py b/src/prompt_toolkit/contrib/ssh/__init__.py index a895ca2282..bbc1c215b1 100644 --- a/src/prompt_toolkit/contrib/ssh/__init__.py +++ b/src/prompt_toolkit/contrib/ssh/__init__.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from .server import PromptToolkitSSHServer, PromptToolkitSSHSession __all__ = [ diff --git a/src/prompt_toolkit/contrib/ssh/server.py b/src/prompt_toolkit/contrib/ssh/server.py index 793d259c9b..03c81a1a74 100644 --- a/src/prompt_toolkit/contrib/ssh/server.py +++ b/src/prompt_toolkit/contrib/ssh/server.py @@ -1,15 +1,17 @@ """ Utility for running a prompt_toolkit application in an asyncssh server. """ +from __future__ import annotations + import asyncio import traceback +from asyncio import get_running_loop from typing import Any, Awaitable, Callable, Optional, TextIO, cast 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 PipeInput, create_pipe_input from prompt_toolkit.output.vt100 import Vt100_Output @@ -19,21 +21,21 @@ class PromptToolkitSSHSession(asyncssh.SSHServerSession): # type: ignore def __init__( self, - interact: Callable[["PromptToolkitSSHSession"], Awaitable[None]], + interact: Callable[[PromptToolkitSSHSession], Awaitable[None]], *, enable_cpr: bool, ) -> None: self.interact = interact self.enable_cpr = enable_cpr - self.interact_task: Optional[asyncio.Task[None]] = None - self._chan: Optional[Any] = None - self.app_session: Optional[AppSession] = None + self.interact_task: asyncio.Task[None] | None = None + self._chan: Any | None = None + self.app_session: AppSession | None = None # 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: Optional[PipeInput] = None - self._output: Optional[Vt100_Output] = None + self._input: PipeInput | None = None + self._output: Vt100_Output | None = None # Output object. Don't render to the real stdout, but write everything # in the SSH channel. @@ -75,7 +77,7 @@ def shell_requested(self) -> bool: return True def session_started(self) -> None: - self.interact_task = get_event_loop().create_task(self._interact()) + self.interact_task = get_running_loop().create_task(self._interact()) async def _interact(self) -> None: if self._chan is None: @@ -141,7 +143,7 @@ async def interact(ssh_session: PromptToolkitSSHSession) -> None: print_formatted_text('You said: ', text) server = PromptToolkitSSHServer(interact=interact) - loop = get_event_loop() + loop = get_running_loop() loop.run_until_complete( asyncssh.create_server( lambda: MySSHServer(interact), diff --git a/src/prompt_toolkit/contrib/telnet/__init__.py b/src/prompt_toolkit/contrib/telnet/__init__.py index b29f7d2876..de902b4399 100644 --- a/src/prompt_toolkit/contrib/telnet/__init__.py +++ b/src/prompt_toolkit/contrib/telnet/__init__.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from .server import TelnetServer __all__ = [ diff --git a/src/prompt_toolkit/contrib/telnet/log.py b/src/prompt_toolkit/contrib/telnet/log.py index 24487aba26..0fe843375c 100644 --- a/src/prompt_toolkit/contrib/telnet/log.py +++ b/src/prompt_toolkit/contrib/telnet/log.py @@ -1,6 +1,8 @@ """ Python logger for the telnet server. """ +from __future__ import annotations + import logging logger = logging.getLogger(__package__) diff --git a/src/prompt_toolkit/contrib/telnet/protocol.py b/src/prompt_toolkit/contrib/telnet/protocol.py index b9cde6ef6b..4b90e9853a 100644 --- a/src/prompt_toolkit/contrib/telnet/protocol.py +++ b/src/prompt_toolkit/contrib/telnet/protocol.py @@ -4,6 +4,8 @@ Inspired by `Twisted.conch.telnet`. """ +from __future__ import annotations + import struct from typing import Callable, Generator diff --git a/src/prompt_toolkit/contrib/telnet/server.py b/src/prompt_toolkit/contrib/telnet/server.py index aa28dea698..ddd7ca7858 100644 --- a/src/prompt_toolkit/contrib/telnet/server.py +++ b/src/prompt_toolkit/contrib/telnet/server.py @@ -1,15 +1,18 @@ """ Telnet server. """ +from __future__ import annotations + import asyncio +import contextvars import socket import sys +from asyncio import get_running_loop from typing import Any, Awaitable, Callable, List, Optional, Set, TextIO, Tuple, cast from prompt_toolkit.application.current import create_app_session, get_app 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 PipeInput, create_pipe_input from prompt_toolkit.output.vt100 import Vt100_Output @@ -33,11 +36,6 @@ TelnetProtocolParser, ) -if sys.version_info >= (3, 7): - import contextvars # Requires Python3.7! -else: - contextvars: Any = None - __all__ = [ "TelnetServer", ] @@ -86,7 +84,7 @@ def __init__(self, connection: socket.socket, encoding: str) -> None: self._encoding = encoding self._connection = connection self._errors = "strict" - self._buffer: List[bytes] = [] + self._buffer: list[bytes] = [] self._closed = False def write(self, data: str) -> None: @@ -126,11 +124,11 @@ class TelnetConnection: def __init__( self, conn: socket.socket, - addr: Tuple[str, int], - interact: Callable[["TelnetConnection"], Awaitable[None]], - server: "TelnetServer", + addr: tuple[str, int], + interact: Callable[[TelnetConnection], Awaitable[None]], + server: TelnetServer, encoding: str, - style: Optional[BaseStyle], + style: BaseStyle | None, vt100_input: PipeInput, enable_cpr: bool = True, ) -> None: @@ -144,7 +142,7 @@ def __init__( self._ready = asyncio.Event() self.vt100_input = vt100_input self.enable_cpr = enable_cpr - self.vt100_output: Optional[Vt100_Output] = None + self.vt100_output: Vt100_Output | None = None # Create "Output" object. self.size = Size(rows=40, columns=79) @@ -176,7 +174,7 @@ def ttype_received(ttype: str) -> None: self._ready.set() self.parser = TelnetProtocolParser(data_received, size_received, ttype_received) - self.context: Optional[contextvars.Context] = None + self.context: contextvars.Context | None = None async def run_application(self) -> None: """ @@ -193,7 +191,7 @@ def handle_incoming_data() -> None: self.close() # Add reader. - loop = get_event_loop() + loop = get_running_loop() loop.add_reader(self.conn, handle_incoming_data) try: @@ -219,7 +217,7 @@ def close(self) -> None: self._closed = True self.vt100_input.close() - get_event_loop().remove_reader(self.conn) + get_running_loop().remove_reader(self.conn) self.conn.close() self.stdout.close() @@ -276,7 +274,7 @@ def __init__( port: int = 23, interact: Callable[[TelnetConnection], Awaitable[None]] = _dummy_interact, encoding: str = "utf-8", - style: Optional[BaseStyle] = None, + style: BaseStyle | None = None, enable_cpr: bool = True, ) -> None: self.host = host @@ -285,10 +283,11 @@ def __init__( self.encoding = encoding self.style = style self.enable_cpr = enable_cpr - self._application_tasks: List[asyncio.Task[None]] = [] - self.connections: Set[TelnetConnection] = set() - self._listen_socket: Optional[socket.socket] = None + self._run_task: asyncio.Task[None] | None = None + self._application_tasks: list[asyncio.Task[None]] = [] + + self.connections: set[TelnetConnection] = set() @classmethod def _create_socket(cls, host: str, port: int) -> socket.socket: @@ -300,44 +299,74 @@ def _create_socket(cls, host: str, port: int) -> socket.socket: s.listen(4) return s - def start(self) -> None: + async def run(self, ready_cb: Callable[[], None] | None = None) -> None: """ - Start the telnet server. - Don't forget to call `loop.run_forever()` after doing this. + Run the telnet server, until this gets cancelled. + + :param ready_cb: Callback that will be called at the point that we're + actually listening. """ - self._listen_socket = self._create_socket(self.host, self.port) + socket = self._create_socket(self.host, self.port) logger.info( "Listening for telnet connections on %s port %r", self.host, self.port ) - get_event_loop().add_reader(self._listen_socket, self._accept) + get_running_loop().add_reader(socket, lambda: self._accept(socket)) + + if ready_cb: + ready_cb() + + try: + # Run forever, until cancelled. + await asyncio.Future() + finally: + get_running_loop().remove_reader(socket) + socket.close() + + # Wait for all applications to finish. + for t in self._application_tasks: + t.cancel() + + # (This is similar to + # `Application.cancel_and_wait_for_background_tasks`. We wait for the + # background tasks to complete, but don't propagate exceptions, because + # we can't use `ExceptionGroup` yet.) + if len(self._application_tasks) > 0: + await asyncio.wait( + self._application_tasks, + timeout=None, + return_when=asyncio.ALL_COMPLETED, + ) + + def start(self) -> None: + """ + Start the telnet server (stop by calling and awaiting `stop()`). + + Note: When possible, it's better to call `.run()` instead. + """ + if self._run_task is not None: + # Already running. + return + + self._run_task = get_running_loop().create_task(self.run()) async def stop(self) -> None: - if self._listen_socket: - get_event_loop().remove_reader(self._listen_socket) - self._listen_socket.close() - - # Wait for all applications to finish. - for t in self._application_tasks: - t.cancel() - - # (This is similar to - # `Application.cancel_and_wait_for_background_tasks`. We wait for the - # background tasks to complete, but don't propagate exceptions, because - # we can't use `ExceptionGroup` yet.) - if len(self._application_tasks) > 0: - await asyncio.wait( - self._application_tasks, timeout=None, return_when=asyncio.ALL_COMPLETED - ) + """ + Stop a telnet server that was started using `.start()` and wait for the + cancellation to complete. + """ + if self._run_task is not None: + self._run_task.cancel() + try: + await self._run_task + except asyncio.CancelledError: + pass - def _accept(self) -> None: + def _accept(self, listen_socket: socket.socket) -> None: """ Accept new incoming connection. """ - if self._listen_socket is None: - return # Should not happen. `_accept` is called after `start`. - - conn, addr = self._listen_socket.accept() + conn, addr = listen_socket.accept() logger.info("New connection %r %r", *addr) # Run application for this connection. @@ -379,5 +408,5 @@ async def run() -> None: finally: self._application_tasks.remove(task) - task = get_event_loop().create_task(run()) + task = get_running_loop().create_task(run()) self._application_tasks.append(task) diff --git a/src/prompt_toolkit/cursor_shapes.py b/src/prompt_toolkit/cursor_shapes.py index 59172f2903..adf6bca011 100644 --- a/src/prompt_toolkit/cursor_shapes.py +++ b/src/prompt_toolkit/cursor_shapes.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from abc import ABC, abstractmethod from enum import Enum from typing import TYPE_CHECKING, Any, Callable, Union @@ -39,7 +41,7 @@ class CursorShape(Enum): class CursorShapeConfig(ABC): @abstractmethod - def get_cursor_shape(self, application: "Application[Any]") -> CursorShape: + def get_cursor_shape(self, application: Application[Any]) -> CursorShape: """ Return the cursor shape to be used in the current state. """ @@ -56,7 +58,7 @@ class SimpleCursorShapeConfig(CursorShapeConfig): def __init__(self, cursor_shape: CursorShape = CursorShape._NEVER_CHANGE) -> None: self.cursor_shape = cursor_shape - def get_cursor_shape(self, application: "Application[Any]") -> CursorShape: + def get_cursor_shape(self, application: Application[Any]) -> CursorShape: return self.cursor_shape @@ -65,7 +67,7 @@ class ModalCursorShapeConfig(CursorShapeConfig): Show cursor shape according to the current input mode. """ - def get_cursor_shape(self, application: "Application[Any]") -> CursorShape: + def get_cursor_shape(self, application: Application[Any]) -> CursorShape: if application.editing_mode == EditingMode.VI: if application.vi_state.input_mode == InputMode.INSERT: return CursorShape.BEAM @@ -82,7 +84,7 @@ def __init__( ) -> None: self.get_cursor_shape_config = get_cursor_shape_config - def get_cursor_shape(self, application: "Application[Any]") -> CursorShape: + def get_cursor_shape(self, application: Application[Any]) -> CursorShape: return to_cursor_shape_config(self.get_cursor_shape_config()).get_cursor_shape( application ) diff --git a/src/prompt_toolkit/data_structures.py b/src/prompt_toolkit/data_structures.py index d031acffd2..27dd458543 100644 --- a/src/prompt_toolkit/data_structures.py +++ b/src/prompt_toolkit/data_structures.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import NamedTuple __all__ = [ diff --git a/src/prompt_toolkit/document.py b/src/prompt_toolkit/document.py index a39d9a4319..ad66f4cb0f 100644 --- a/src/prompt_toolkit/document.py +++ b/src/prompt_toolkit/document.py @@ -1,6 +1,8 @@ """ The `Document` that implements all the text operations/querying. """ +from __future__ import annotations + import bisect import re import string @@ -46,7 +48,7 @@ # (Document instances are considered immutable. That means that if another # `Document` is constructed with the same text, it should have the same # `_DocumentCache`.) -_text_to_document_cache: Dict[str, "_DocumentCache"] = cast( +_text_to_document_cache: dict[str, _DocumentCache] = cast( Dict[str, "_DocumentCache"], weakref.WeakValueDictionary(), # Maps document.text to DocumentCache instance. ) @@ -75,10 +77,10 @@ def _error(self, *a: object, **kw: object) -> NoReturn: class _DocumentCache: def __init__(self) -> None: #: List of lines for the Document text. - self.lines: Optional[_ImmutableLineList] = None + self.lines: _ImmutableLineList | None = None #: List of index positions, pointing to the start of all the lines. - self.line_indexes: Optional[List[int]] = None + self.line_indexes: list[int] | None = None class Document: @@ -99,8 +101,8 @@ class Document: def __init__( self, text: str = "", - cursor_position: Optional[int] = None, - selection: Optional[SelectionState] = None, + cursor_position: int | None = None, + selection: SelectionState | None = None, ) -> None: # Check cursor position. It can also be right after the end. (Where we # insert text.) @@ -159,7 +161,7 @@ def cursor_position(self) -> int: return self._cursor_position @property - def selection(self) -> Optional[SelectionState]: + def selection(self) -> SelectionState | None: ":class:`.SelectionState` object." return self._selection @@ -194,7 +196,7 @@ def current_line_after_cursor(self) -> str: return text @property - def lines(self) -> List[str]: + def lines(self) -> list[str]: """ Array of all the lines. """ @@ -205,7 +207,7 @@ def lines(self) -> List[str]: return self._cache.lines @property - def _line_start_indexes(self) -> List[int]: + def _line_start_indexes(self) -> list[int]: """ Array pointing to the start indexes of all the lines. """ @@ -233,7 +235,7 @@ def _line_start_indexes(self) -> List[int]: return self._cache.line_indexes @property - def lines_from_current(self) -> List[str]: + def lines_from_current(self) -> list[str]: """ Array of the lines starting from the current line, until the last line. """ @@ -300,7 +302,7 @@ def cursor_position_col(self) -> int: _, line_start_index = self._find_line_start_index(self.cursor_position) return self.cursor_position - line_start_index - def _find_line_start_index(self, index: int) -> Tuple[int, int]: + def _find_line_start_index(self, index: int) -> tuple[int, int]: """ For the index of a character at a certain line, calculate the index of the first character on that line. @@ -312,7 +314,7 @@ def _find_line_start_index(self, index: int) -> Tuple[int, int]: pos = bisect.bisect_right(indexes, index) - 1 return pos, indexes[pos] - def translate_index_to_position(self, index: int) -> Tuple[int, int]: + def translate_index_to_position(self, index: int) -> tuple[int, int]: """ Given an index for the text, return the corresponding (row, col) tuple. (0-based. Returns (0, 0) for index=0.) @@ -371,7 +373,7 @@ def find( include_current_position: bool = False, ignore_case: bool = False, count: int = 1, - ) -> Optional[int]: + ) -> int | None: """ Find `text` after the cursor, return position relative to the cursor position. Return `None` if nothing was found. @@ -405,7 +407,7 @@ def find( pass return None - def find_all(self, sub: str, ignore_case: bool = False) -> List[int]: + def find_all(self, sub: str, ignore_case: bool = False) -> list[int]: """ Find all occurrences of the substring. Return a list of absolute positions in the document. @@ -419,7 +421,7 @@ def find_backwards( in_current_line: bool = False, ignore_case: bool = False, count: int = 1, - ) -> Optional[int]: + ) -> int | None: """ Find `text` before the cursor, return position relative to the cursor position. Return `None` if nothing was found. @@ -443,7 +445,7 @@ def find_backwards( return None def get_word_before_cursor( - self, WORD: bool = False, pattern: Optional[Pattern[str]] = None + self, WORD: bool = False, pattern: Pattern[str] | None = None ) -> str: """ Give the word before the cursor. @@ -462,7 +464,7 @@ def get_word_before_cursor( return text_before_cursor[len(text_before_cursor) + start :] def _is_word_before_cursor_complete( - self, WORD: bool = False, pattern: Optional[Pattern[str]] = None + self, WORD: bool = False, pattern: Pattern[str] | None = None ) -> bool: if pattern: return self.find_start_of_previous_word(WORD=WORD, pattern=pattern) is None @@ -472,8 +474,8 @@ def _is_word_before_cursor_complete( ) def find_start_of_previous_word( - self, count: int = 1, WORD: bool = False, pattern: Optional[Pattern[str]] = None - ) -> Optional[int]: + self, count: int = 1, WORD: bool = False, pattern: Pattern[str] | None = None + ) -> int | None: """ Return an index relative to the cursor position pointing to the start of the previous word. Return `None` if nothing was found. @@ -509,7 +511,7 @@ def find_boundaries_of_current_word( WORD: bool = False, include_leading_whitespace: bool = False, include_trailing_whitespace: bool = False, - ) -> Tuple[int, int]: + ) -> tuple[int, int]: """ Return the relative boundaries (startpos, endpos) of the current word under the cursor. (This is at the current line, because line boundaries obviously @@ -557,7 +559,7 @@ def get_word_under_cursor(self, WORD: bool = False) -> str: def find_next_word_beginning( self, count: int = 1, WORD: bool = False - ) -> Optional[int]: + ) -> int | None: """ Return an index relative to the cursor position pointing to the start of the next word. Return `None` if nothing was found. @@ -582,7 +584,7 @@ def find_next_word_beginning( def find_next_word_ending( self, include_current_position: bool = False, count: int = 1, WORD: bool = False - ) -> Optional[int]: + ) -> int | None: """ Return an index relative to the cursor position pointing to the end of the next word. Return `None` if nothing was found. @@ -614,7 +616,7 @@ def find_next_word_ending( def find_previous_word_beginning( self, count: int = 1, WORD: bool = False - ) -> Optional[int]: + ) -> int | None: """ Return an index relative to the cursor position pointing to the start of the previous word. Return `None` if nothing was found. @@ -635,7 +637,7 @@ def find_previous_word_beginning( def find_previous_word_ending( self, count: int = 1, WORD: bool = False - ) -> Optional[int]: + ) -> int | None: """ Return an index relative to the cursor position pointing to the end of the previous word. Return `None` if nothing was found. @@ -662,7 +664,7 @@ def find_previous_word_ending( def find_next_matching_line( self, match_func: Callable[[str], bool], count: int = 1 - ) -> Optional[int]: + ) -> int | None: """ Look downwards for empty lines. Return the line index, relative to the current line. @@ -681,7 +683,7 @@ def find_next_matching_line( def find_previous_matching_line( self, match_func: Callable[[str], bool], count: int = 1 - ) -> Optional[int]: + ) -> int | None: """ Look upwards for empty lines. Return the line index, relative to the current line. @@ -717,7 +719,7 @@ def get_cursor_right_position(self, count: int = 1) -> int: return min(count, len(self.current_line_after_cursor)) def get_cursor_up_position( - self, count: int = 1, preferred_column: Optional[int] = None + self, count: int = 1, preferred_column: int | None = None ) -> int: """ Return the relative cursor position (character index) where we would be if the @@ -739,7 +741,7 @@ def get_cursor_up_position( ) def get_cursor_down_position( - self, count: int = 1, preferred_column: Optional[int] = None + self, count: int = 1, preferred_column: int | None = None ) -> int: """ Return the relative cursor position (character index) where we would be if the @@ -759,8 +761,8 @@ def get_cursor_down_position( ) def find_enclosing_bracket_right( - self, left_ch: str, right_ch: str, end_pos: Optional[int] = None - ) -> Optional[int]: + self, left_ch: str, right_ch: str, end_pos: int | None = None + ) -> int | None: """ Find the right bracket enclosing current position. Return the relative position to the cursor position. @@ -792,8 +794,8 @@ def find_enclosing_bracket_right( return None def find_enclosing_bracket_left( - self, left_ch: str, right_ch: str, start_pos: Optional[int] = None - ) -> Optional[int]: + self, left_ch: str, right_ch: str, start_pos: int | None = None + ) -> int | None: """ Find the left bracket enclosing current position. Return the relative position to the cursor position. @@ -825,7 +827,7 @@ def find_enclosing_bracket_left( return None def find_matching_bracket_position( - self, start_pos: Optional[int] = None, end_pos: Optional[int] = None + self, start_pos: int | None = None, end_pos: int | None = None ) -> int: """ Return relative cursor position of matching [, (, { or < bracket. @@ -888,7 +890,7 @@ def get_column_cursor_position(self, column: int) -> int: def selection_range( self, - ) -> Tuple[ + ) -> tuple[ int, int ]: # XXX: shouldn't this return `None` if there is no selection??? """ @@ -907,7 +909,7 @@ def selection_range( return from_, to - def selection_ranges(self) -> Iterable[Tuple[int, int]]: + def selection_ranges(self) -> Iterable[tuple[int, int]]: """ Return a list of `(from, to)` tuples for the selection or none if nothing was selected. The upper boundary is not included. @@ -957,7 +959,7 @@ def selection_ranges(self) -> Iterable[Tuple[int, int]]: yield from_, to - def selection_range_at_line(self, row: int) -> Optional[Tuple[int, int]]: + def selection_range_at_line(self, row: int) -> tuple[int, int] | None: """ If the selection spans a portion of the given line, return a (from, to) tuple. @@ -1007,7 +1009,7 @@ def selection_range_at_line(self, row: int) -> Optional[Tuple[int, int]]: return from_column, to_column return None - def cut_selection(self) -> Tuple["Document", ClipboardData]: + def cut_selection(self) -> tuple[Document, ClipboardData]: """ Return a (:class:`.Document`, :class:`.ClipboardData`) tuple, where the document represents the new document when the selection is cut, and the @@ -1048,7 +1050,7 @@ def paste_clipboard_data( data: ClipboardData, paste_mode: PasteMode = PasteMode.EMACS, count: int = 1, - ) -> "Document": + ) -> Document: """ Return a new :class:`.Document` instance which contains the result if we would paste this data at the current cursor position. @@ -1157,7 +1159,7 @@ def match_func(text: str) -> bool: # Modifiers. - def insert_after(self, text: str) -> "Document": + def insert_after(self, text: str) -> Document: """ Create a new document, with this text inserted after the buffer. It keeps selection ranges and cursor position in sync. @@ -1168,7 +1170,7 @@ def insert_after(self, text: str) -> "Document": selection=self.selection, ) - def insert_before(self, text: str) -> "Document": + def insert_before(self, text: str) -> Document: """ Create a new document, with this text inserted before the buffer. It keeps selection ranges and cursor position in sync. diff --git a/src/prompt_toolkit/enums.py b/src/prompt_toolkit/enums.py index 4f496e67ad..da03633711 100644 --- a/src/prompt_toolkit/enums.py +++ b/src/prompt_toolkit/enums.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from enum import Enum diff --git a/src/prompt_toolkit/eventloop/__init__.py b/src/prompt_toolkit/eventloop/__init__.py index e92e7a2346..89eb71c318 100644 --- a/src/prompt_toolkit/eventloop/__init__.py +++ b/src/prompt_toolkit/eventloop/__init__.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from .async_generator import aclosing, generator_to_async_generator from .inputhook import ( InputHookContext, @@ -7,7 +9,6 @@ ) from .utils import ( call_soon_threadsafe, - get_event_loop, get_traceback_from_context, run_in_executor_with_context, ) @@ -20,7 +21,6 @@ "run_in_executor_with_context", "call_soon_threadsafe", "get_traceback_from_context", - "get_event_loop", # Inputhooks. "new_eventloop_with_inputhook", "set_eventloop_with_inputhook", diff --git a/src/prompt_toolkit/eventloop/async_context_manager.py b/src/prompt_toolkit/eventloop/async_context_manager.py deleted file mode 100644 index 39146165a0..0000000000 --- a/src/prompt_toolkit/eventloop/async_context_manager.py +++ /dev/null @@ -1,132 +0,0 @@ -""" -@asynccontextmanager code, copied from Python 3.7's contextlib. -For usage in Python 3.6. -Types have been added to this file, just enough to make Mypy happy. -""" -# mypy: allow-untyped-defs -import abc -from functools import wraps -from typing import AsyncContextManager, AsyncIterator, Callable, TypeVar - -import _collections_abc - -__all__ = ["asynccontextmanager"] - - -class AbstractAsyncContextManager(abc.ABC): - - """An abstract base class for asynchronous context managers.""" - - async def __aenter__(self): - """Return `self` upon entering the runtime context.""" - return self - - @abc.abstractmethod - async def __aexit__(self, exc_type, exc_value, traceback): - """Raise any exception triggered within the runtime context.""" - return None - - @classmethod - def __subclasshook__(cls, C): - if cls is AbstractAsyncContextManager: - return _collections_abc._check_methods(C, "__aenter__", "__aexit__") # type: ignore - return NotImplemented - - -class _GeneratorContextManagerBase: - """Shared functionality for @contextmanager and @asynccontextmanager.""" - - def __init__(self, func, args, kwds): - self.gen = func(*args, **kwds) - self.func, self.args, self.kwds = func, args, kwds - # Issue 19330: ensure context manager instances have good docstrings - doc = getattr(func, "__doc__", None) - if doc is None: - doc = type(self).__doc__ - self.__doc__ = doc - # Unfortunately, this still doesn't provide good help output when - # inspecting the created context manager instances, since pydoc - # currently bypasses the instance docstring and shows the docstring - # for the class instead. - # See http://bugs.python.org/issue19404 for more details. - - -class _AsyncGeneratorContextManager( - _GeneratorContextManagerBase, AbstractAsyncContextManager -): - """Helper for @asynccontextmanager.""" - - async def __aenter__(self): - try: - return await self.gen.__anext__() - except StopAsyncIteration: - raise RuntimeError("generator didn't yield") from None - - async def __aexit__(self, typ, value, traceback): - if typ is None: - try: - await self.gen.__anext__() - except StopAsyncIteration: - return - else: - raise RuntimeError("generator didn't stop") - else: - if value is None: - value = typ() - # See _GeneratorContextManager.__exit__ for comments on subtleties - # in this implementation - try: - await self.gen.athrow(typ, value, traceback) - raise RuntimeError("generator didn't stop after athrow()") - except StopAsyncIteration as exc: - return exc is not value - except RuntimeError as exc: - if exc is value: - return False - # Avoid suppressing if a StopIteration exception - # was passed to throw() and later wrapped into a RuntimeError - # (see PEP 479 for sync generators; async generators also - # have this behavior). But do this only if the exception wrapped - # by the RuntimeError is actully Stop(Async)Iteration (see - # issue29692). - if isinstance(value, (StopIteration, StopAsyncIteration)): - if exc.__cause__ is value: - return False - raise - except BaseException as exc: - if exc is not value: - raise - - -_T = TypeVar("_T") - - -def asynccontextmanager( - func: Callable[..., AsyncIterator[_T]] -) -> Callable[..., AsyncContextManager[_T]]: - """@asynccontextmanager decorator. - Typical usage: - @asynccontextmanager - async def some_async_generator(): - - try: - yield - finally: - - This makes this: - async with some_async_generator() as : - - equivalent to this: - - try: - = - - finally: - - """ - - @wraps(func) - def helper(*args, **kwds): - return _AsyncGeneratorContextManager(func, args, kwds) # type: ignore - - return helper diff --git a/src/prompt_toolkit/eventloop/async_generator.py b/src/prompt_toolkit/eventloop/async_generator.py index 168bcfd99f..063d1c898e 100644 --- a/src/prompt_toolkit/eventloop/async_generator.py +++ b/src/prompt_toolkit/eventloop/async_generator.py @@ -1,11 +1,14 @@ """ Implementation for async generators. """ +from __future__ import annotations + +from asyncio import get_running_loop +from contextlib import asynccontextmanager from queue import Empty, Full, Queue from typing import Any, AsyncGenerator, Callable, Iterable, TypeVar, Union -from .async_context_manager import asynccontextmanager -from .utils import get_event_loop, run_in_executor_with_context +from .utils import run_in_executor_with_context __all__ = [ "aclosing", @@ -62,8 +65,8 @@ async def generator_to_async_generator( """ quitting = False # NOTE: We are limiting the queue size in order to have back-pressure. - q: Queue[Union[_T, _Done]] = Queue(maxsize=buffer_size) - loop = get_event_loop() + q: Queue[_T | _Done] = Queue(maxsize=buffer_size) + loop = get_running_loop() def runner() -> None: """ diff --git a/src/prompt_toolkit/eventloop/dummy_contextvars.py b/src/prompt_toolkit/eventloop/dummy_contextvars.py deleted file mode 100644 index beb3fecd48..0000000000 --- a/src/prompt_toolkit/eventloop/dummy_contextvars.py +++ /dev/null @@ -1,56 +0,0 @@ -""" -Dummy contextvars implementation, to make prompt_toolkit work on Python 3.6. - -As long as there is only one application running at a time, we don't need the -real contextvars. So, stuff like the telnet-server and so on requires 3.7. -""" -from typing import TYPE_CHECKING, Callable, Generic, Optional, TypeVar - -if TYPE_CHECKING: - from typing_extensions import ParamSpec - - -def copy_context() -> "Context": - return Context() - - -if TYPE_CHECKING: - _P = ParamSpec("_P") -_T = TypeVar("_T") - - -class Context: - def run( - self, callable: "Callable[_P, _T]", *args: "_P.args", **kwargs: "_P.kwargs" - ) -> _T: - return callable(*args, **kwargs) - - def copy(self) -> "Context": - return self - - -class Token(Generic[_T]): - pass - - -class ContextVar(Generic[_T]): - def __init__(self, name: str, *, default: Optional[_T] = None) -> None: - self._name = name - self._value = default - - @property - def name(self) -> str: - return self._name - - def get(self, default: Optional[_T] = None) -> _T: - result = self._value or default - if result is None: - raise LookupError - return result - - def set(self, value: _T) -> Token[_T]: - self._value = value - return Token() - - def reset(self, token: Token[_T]) -> None: - pass diff --git a/src/prompt_toolkit/eventloop/inputhook.py b/src/prompt_toolkit/eventloop/inputhook.py index 05d298117e..be918efb08 100644 --- a/src/prompt_toolkit/eventloop/inputhook.py +++ b/src/prompt_toolkit/eventloop/inputhook.py @@ -22,18 +22,18 @@ asynchronous autocompletion. When the completion for instance is ready, we also want prompt-toolkit to gain control again in order to display that. """ +from __future__ import annotations + import asyncio import os import select import selectors import sys import threading -from asyncio import AbstractEventLoop +from asyncio import AbstractEventLoop, get_running_loop from selectors import BaseSelector, SelectorKey from typing import TYPE_CHECKING, Any, Callable, List, Mapping, Optional, Tuple -from .utils import get_event_loop - __all__ = [ "new_eventloop_with_inputhook", "set_eventloop_with_inputhook", @@ -48,7 +48,7 @@ def new_eventloop_with_inputhook( - inputhook: Callable[["InputHookContext"], None] + inputhook: Callable[[InputHookContext], None] ) -> AbstractEventLoop: """ Create a new event loop with the given inputhook. @@ -59,7 +59,7 @@ def new_eventloop_with_inputhook( def set_eventloop_with_inputhook( - inputhook: Callable[["InputHookContext"], None] + inputhook: Callable[[InputHookContext], None] ) -> AbstractEventLoop: """ Create a new event loop with the given inputhook, and activate it. @@ -79,31 +79,31 @@ class InputHookSelector(BaseSelector): """ def __init__( - self, selector: BaseSelector, inputhook: Callable[["InputHookContext"], None] + self, selector: BaseSelector, inputhook: Callable[[InputHookContext], None] ) -> None: self.selector = selector self.inputhook = inputhook self._r, self._w = os.pipe() def register( - self, fileobj: "FileDescriptorLike", events: "_EventMask", data: Any = None - ) -> "SelectorKey": + self, fileobj: FileDescriptorLike, events: _EventMask, data: Any = None + ) -> SelectorKey: return self.selector.register(fileobj, events, data=data) - def unregister(self, fileobj: "FileDescriptorLike") -> "SelectorKey": + def unregister(self, fileobj: FileDescriptorLike) -> SelectorKey: return self.selector.unregister(fileobj) def modify( - self, fileobj: "FileDescriptorLike", events: "_EventMask", data: Any = None - ) -> "SelectorKey": + self, fileobj: FileDescriptorLike, events: _EventMask, data: Any = None + ) -> SelectorKey: return self.selector.modify(fileobj, events, data=None) def select( - self, timeout: Optional[float] = None - ) -> List[Tuple["SelectorKey", "_EventMask"]]: + self, timeout: float | None = None + ) -> list[tuple[SelectorKey, _EventMask]]: # If there are tasks in the current event loop, # don't run the input hook. - if len(getattr(get_event_loop(), "_ready", [])) > 0: + if len(getattr(get_running_loop(), "_ready", [])) > 0: return self.selector.select(timeout=timeout) ready = False @@ -166,7 +166,7 @@ def close(self) -> None: self._r = self._w = -1 self.selector.close() - def get_map(self) -> Mapping["FileDescriptorLike", "SelectorKey"]: + def get_map(self) -> Mapping[FileDescriptorLike, SelectorKey]: return self.selector.get_map() diff --git a/src/prompt_toolkit/eventloop/utils.py b/src/prompt_toolkit/eventloop/utils.py index 26dabb40e5..a0eea74e60 100644 --- a/src/prompt_toolkit/eventloop/utils.py +++ b/src/prompt_toolkit/eventloop/utils.py @@ -1,19 +1,17 @@ +from __future__ import annotations + import asyncio +import contextvars import sys import time +from asyncio import get_running_loop from types import TracebackType from typing import Any, Awaitable, Callable, Dict, Optional, TypeVar, cast -try: - import contextvars -except ImportError: - from . import dummy_contextvars as contextvars # type: ignore - __all__ = [ "run_in_executor_with_context", "call_soon_threadsafe", "get_traceback_from_context", - "get_event_loop", ] _T = TypeVar("_T") @@ -22,7 +20,7 @@ def run_in_executor_with_context( func: Callable[..., _T], *args: Any, - loop: Optional[asyncio.AbstractEventLoop] = None, + loop: asyncio.AbstractEventLoop | None = None, ) -> Awaitable[_T]: """ Run a function in an executor, but make sure it uses the same contextvars. @@ -30,7 +28,7 @@ def run_in_executor_with_context( See also: https://bugs.python.org/issue34014 """ - loop = loop or get_event_loop() + loop = loop or get_running_loop() ctx: contextvars.Context = contextvars.copy_context() return loop.run_in_executor(None, ctx.run, func, *args) @@ -38,8 +36,8 @@ def run_in_executor_with_context( def call_soon_threadsafe( func: Callable[[], None], - max_postpone_time: Optional[float] = None, - loop: Optional[asyncio.AbstractEventLoop] = None, + max_postpone_time: float | None = None, + loop: asyncio.AbstractEventLoop | None = None, ) -> None: """ Wrapper around asyncio's `call_soon_threadsafe`. @@ -57,7 +55,7 @@ def call_soon_threadsafe( However, we want to set a deadline value, for when the rendering should happen. (The UI should stay responsive). """ - loop2 = loop or get_event_loop() + loop2 = loop or get_running_loop() # If no `max_postpone_time` has been given, schedule right now. if max_postpone_time is None: @@ -86,7 +84,7 @@ def schedule() -> None: loop2.call_soon_threadsafe(schedule) -def get_traceback_from_context(context: Dict[str, Any]) -> Optional[TracebackType]: +def get_traceback_from_context(context: dict[str, Any]) -> TracebackType | None: """ Get the traceback object from the context. """ @@ -101,20 +99,3 @@ def get_traceback_from_context(context: Dict[str, Any]) -> Optional[TracebackTyp return sys.exc_info()[2] return None - - -def get_event_loop() -> asyncio.AbstractEventLoop: - """Backward compatible way to get the event loop""" - # Python 3.6 doesn't have get_running_loop - # Python 3.10 deprecated get_event_loop - if sys.version_info >= (3, 7): - getloop = asyncio.get_running_loop - else: - getloop = asyncio.get_event_loop - - try: - return getloop() - except RuntimeError: - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - return loop diff --git a/src/prompt_toolkit/eventloop/win32.py b/src/prompt_toolkit/eventloop/win32.py index fbc02d493a..9bb6011cf2 100644 --- a/src/prompt_toolkit/eventloop/win32.py +++ b/src/prompt_toolkit/eventloop/win32.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import sys assert sys.platform == "win32" @@ -23,9 +25,7 @@ INFINITE = -1 -def wait_for_handles( - handles: List[HANDLE], timeout: int = INFINITE -) -> Optional[HANDLE]: +def wait_for_handles(handles: list[HANDLE], timeout: int = INFINITE) -> HANDLE | None: """ Waits for multiple handles. (Similar to 'select') Returns the handle which is ready. Returns `None` on timeout. diff --git a/src/prompt_toolkit/filters/__init__.py b/src/prompt_toolkit/filters/__init__.py index d97195fb82..277f428ee9 100644 --- a/src/prompt_toolkit/filters/__init__.py +++ b/src/prompt_toolkit/filters/__init__.py @@ -16,6 +16,8 @@ filter = has_focus('default') & ~ has_selection """ +from __future__ import annotations + from .app import * from .base import Always, Condition, Filter, FilterOrBool, Never from .cli import * diff --git a/src/prompt_toolkit/filters/app.py b/src/prompt_toolkit/filters/app.py index a850ec0aaa..303a078c4e 100644 --- a/src/prompt_toolkit/filters/app.py +++ b/src/prompt_toolkit/filters/app.py @@ -1,6 +1,8 @@ """ Filters that accept a `Application` as argument. """ +from __future__ import annotations + from typing import TYPE_CHECKING, cast from prompt_toolkit.application.current import get_app @@ -52,7 +54,7 @@ # `PromptSession` instances, then previous instances won't be released, # because this memoize (which caches results in the global scope) will # still refer to each instance. -def has_focus(value: "FocusableElement") -> Condition: +def has_focus(value: FocusableElement) -> Condition: """ Enable when this buffer has the focus. """ diff --git a/src/prompt_toolkit/filters/base.py b/src/prompt_toolkit/filters/base.py index db20c0796e..7acefe772b 100644 --- a/src/prompt_toolkit/filters/base.py +++ b/src/prompt_toolkit/filters/base.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import weakref from abc import ABCMeta, abstractmethod from typing import Callable, Dict, Iterable, List, Optional, Tuple, Union @@ -14,13 +16,13 @@ class Filter(metaclass=ABCMeta): """ def __init__(self) -> None: - self._and_cache: "weakref.WeakValueDictionary[Filter, _AndList]" = ( - weakref.WeakValueDictionary() - ) - self._or_cache: "weakref.WeakValueDictionary[Filter, _OrList]" = ( - weakref.WeakValueDictionary() - ) - self._invert_result: Optional[Filter] = None + self._and_cache: weakref.WeakValueDictionary[ + Filter, _AndList + ] = weakref.WeakValueDictionary() + self._or_cache: weakref.WeakValueDictionary[ + Filter, _OrList + ] = weakref.WeakValueDictionary() + self._invert_result: Filter | None = None @abstractmethod def __call__(self) -> bool: @@ -29,7 +31,7 @@ def __call__(self) -> bool: """ return True - def __and__(self, other: "Filter") -> "Filter": + def __and__(self, other: Filter) -> Filter: """ Chaining of filters using the & operator. """ @@ -47,7 +49,7 @@ def __and__(self, other: "Filter") -> "Filter": self._and_cache[other] = result return result - def __or__(self, other: "Filter") -> "Filter": + def __or__(self, other: Filter) -> Filter: """ Chaining of filters using the | operator. """ @@ -65,7 +67,7 @@ def __or__(self, other: "Filter") -> "Filter": self._or_cache[other] = result return result - def __invert__(self) -> "Filter": + def __invert__(self) -> Filter: """ Inverting of filters using the ~ operator. """ @@ -96,7 +98,7 @@ class _AndList(Filter): def __init__(self, filters: Iterable[Filter]) -> None: super().__init__() - self.filters: List[Filter] = [] + self.filters: list[Filter] = [] for f in filters: if isinstance(f, _AndList): # Turn nested _AndLists into one. @@ -118,7 +120,7 @@ class _OrList(Filter): def __init__(self, filters: Iterable[Filter]) -> None: super().__init__() - self.filters: List[Filter] = [] + self.filters: list[Filter] = [] for f in filters: if isinstance(f, _OrList): # Turn nested _OrLists into one. @@ -157,10 +159,10 @@ class Always(Filter): def __call__(self) -> bool: return True - def __or__(self, other: "Filter") -> "Filter": + def __or__(self, other: Filter) -> Filter: return self - def __invert__(self) -> "Never": + def __invert__(self) -> Never: return Never() @@ -172,7 +174,7 @@ class Never(Filter): def __call__(self) -> bool: return False - def __and__(self, other: "Filter") -> "Filter": + def __and__(self, other: Filter) -> Filter: return self def __invert__(self) -> Always: diff --git a/src/prompt_toolkit/filters/cli.py b/src/prompt_toolkit/filters/cli.py index 7135196cdd..c95080a927 100644 --- a/src/prompt_toolkit/filters/cli.py +++ b/src/prompt_toolkit/filters/cli.py @@ -2,6 +2,8 @@ For backwards-compatibility. keep this file. (Many people are going to have key bindings that rely on this file.) """ +from __future__ import annotations + from .app import * __all__ = [ diff --git a/src/prompt_toolkit/filters/utils.py b/src/prompt_toolkit/filters/utils.py index aaf44aa87e..bd2e0acca5 100644 --- a/src/prompt_toolkit/filters/utils.py +++ b/src/prompt_toolkit/filters/utils.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Dict from .base import Always, Filter, FilterOrBool, Never @@ -12,7 +14,7 @@ _never = Never() -_bool_to_filter: Dict[bool, Filter] = { +_bool_to_filter: dict[bool, Filter] = { True: _always, False: _never, } diff --git a/src/prompt_toolkit/formatted_text/__init__.py b/src/prompt_toolkit/formatted_text/__init__.py index f0c92c96f9..e34db13d80 100644 --- a/src/prompt_toolkit/formatted_text/__init__.py +++ b/src/prompt_toolkit/formatted_text/__init__.py @@ -10,6 +10,8 @@ `(style_string, text)` tuples. The :func:`.to_formatted_text` conversion function takes any of these and turns all of them into such a tuple sequence. """ +from __future__ import annotations + from .ansi import ANSI from .base import ( AnyFormattedText, diff --git a/src/prompt_toolkit/formatted_text/ansi.py b/src/prompt_toolkit/formatted_text/ansi.py index 1766e15809..1c8209bb0d 100644 --- a/src/prompt_toolkit/formatted_text/ansi.py +++ b/src/prompt_toolkit/formatted_text/ansi.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from string import Formatter from typing import Generator, List, Optional @@ -32,8 +34,8 @@ def __init__(self, value: str) -> None: self._formatted_text: StyleAndTextTuples = [] # Default style attributes. - self._color: Optional[str] = None - self._bgcolor: Optional[str] = None + self._color: str | None = None + self._bgcolor: str | None = None self._bold = False self._underline = False self._strike = False @@ -133,7 +135,7 @@ def _parse_corot(self) -> Generator[None, str, None]: # output. formatted_text.append((style, c)) - def _select_graphic_rendition(self, attrs: List[int]) -> None: + def _select_graphic_rendition(self, attrs: list[int]) -> None: """ Taken a list of graphics attributes and apply changes. """ @@ -253,14 +255,14 @@ def __repr__(self) -> str: def __pt_formatted_text__(self) -> StyleAndTextTuples: return self._formatted_text - def format(self, *args: str, **kwargs: str) -> "ANSI": + def format(self, *args: str, **kwargs: str) -> ANSI: """ Like `str.format`, but make sure that the arguments are properly escaped. (No ANSI escapes can be injected.) """ return ANSI(FORMATTER.vformat(self.value, args, kwargs)) - def __mod__(self, value: object) -> "ANSI": + def __mod__(self, value: object) -> ANSI: """ ANSI('%s') % value """ diff --git a/src/prompt_toolkit/formatted_text/base.py b/src/prompt_toolkit/formatted_text/base.py index 9d991ce67f..cec6debcfb 100644 --- a/src/prompt_toolkit/formatted_text/base.py +++ b/src/prompt_toolkit/formatted_text/base.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import TYPE_CHECKING, Any, Callable, Iterable, List, Tuple, Union, cast from prompt_toolkit.mouse_events import MouseEvent @@ -52,7 +54,7 @@ def __pt_formatted_text__(self) -> StyleAndTextTuples: def to_formatted_text( value: AnyFormattedText, style: str = "", auto_convert: bool = False -) -> "FormattedText": +) -> FormattedText: """ Convert the given value (which can be formatted text) into a list of text fragments. (Which is the canonical form of formatted text.) The outcome is @@ -67,7 +69,7 @@ def to_formatted_text( :param auto_convert: If `True`, also accept other types, and convert them to a string first. """ - result: Union[FormattedText, StyleAndTextTuples] + result: FormattedText | StyleAndTextTuples if value is None: result = [] @@ -103,7 +105,7 @@ def to_formatted_text( return FormattedText(result) -def is_formatted_text(value: object) -> "TypeGuard[AnyFormattedText]": +def is_formatted_text(value: object) -> TypeGuard[AnyFormattedText]: """ Check whether the input is valid formatted text (for use in assert statements). diff --git a/src/prompt_toolkit/formatted_text/html.py b/src/prompt_toolkit/formatted_text/html.py index 88bfd416c6..0e52bd854f 100644 --- a/src/prompt_toolkit/formatted_text/html.py +++ b/src/prompt_toolkit/formatted_text/html.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import xml.dom.minidom as minidom from string import Formatter from typing import Any, List @@ -33,9 +35,9 @@ def __init__(self, value: str) -> None: document = minidom.parseString(f"{value}") result: StyleAndTextTuples = [] - name_stack: List[str] = [] - fg_stack: List[str] = [] - bg_stack: List[str] = [] + name_stack: list[str] = [] + fg_stack: list[str] = [] + bg_stack: list[str] = [] def get_current_style() -> str: "Build style string for current node." @@ -103,14 +105,14 @@ def __repr__(self) -> str: def __pt_formatted_text__(self) -> StyleAndTextTuples: return self.formatted_text - def format(self, *args: object, **kwargs: object) -> "HTML": + def format(self, *args: object, **kwargs: object) -> HTML: """ Like `str.format`, but make sure that the arguments are properly escaped. """ return HTML(FORMATTER.vformat(self.value, args, kwargs)) - def __mod__(self, value: object) -> "HTML": + def __mod__(self, value: object) -> HTML: """ HTML('%s') % value """ diff --git a/src/prompt_toolkit/formatted_text/pygments.py b/src/prompt_toolkit/formatted_text/pygments.py index dd16f0efbe..92556dd716 100644 --- a/src/prompt_toolkit/formatted_text/pygments.py +++ b/src/prompt_toolkit/formatted_text/pygments.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import TYPE_CHECKING, List, Tuple from prompt_toolkit.styles.pygments import pygments_token_to_classname @@ -18,7 +20,7 @@ class PygmentsTokens: (``(style_str, text)`` tuples). """ - def __init__(self, token_list: List[Tuple["Token", str]]) -> None: + def __init__(self, token_list: list[tuple[Token, str]]) -> None: self.token_list = token_list def __pt_formatted_text__(self) -> StyleAndTextTuples: diff --git a/src/prompt_toolkit/formatted_text/utils.py b/src/prompt_toolkit/formatted_text/utils.py index cda4233e06..b242c2cc89 100644 --- a/src/prompt_toolkit/formatted_text/utils.py +++ b/src/prompt_toolkit/formatted_text/utils.py @@ -4,6 +4,8 @@ When ``to_formatted_text`` has been called, we get a list of ``(style, text)`` tuples. This file contains functions for manipulating such a list. """ +from __future__ import annotations + from typing import Iterable, cast from prompt_toolkit.utils import get_cwidth diff --git a/src/prompt_toolkit/history.py b/src/prompt_toolkit/history.py index 987d7175de..43fe8faa6c 100644 --- a/src/prompt_toolkit/history.py +++ b/src/prompt_toolkit/history.py @@ -7,14 +7,15 @@ loading can be done asynchronously and making the history swappable would probably break this. """ +from __future__ import annotations + import datetime import os import threading from abc import ABCMeta, abstractmethod +from asyncio import get_running_loop from typing import AsyncGenerator, Iterable, List, Optional, Sequence, Tuple -from prompt_toolkit.eventloop import get_event_loop - __all__ = [ "History", "ThreadedHistory", @@ -37,7 +38,7 @@ def __init__(self) -> None: # History that's loaded already, in reverse order. Latest, most recent # item first. - self._loaded_strings: List[str] = [] + self._loaded_strings: list[str] = [] # # Methods expected by `Buffer`. @@ -61,7 +62,7 @@ async def load(self) -> AsyncGenerator[str, None]: for item in self._loaded_strings: yield item - def get_strings(self) -> List[str]: + def get_strings(self) -> list[str]: """ Get the strings from the history that are loaded so far. (In order. Oldest item first.) @@ -111,7 +112,7 @@ def __init__(self, history: History) -> None: self.history = history - self._load_thread: Optional[threading.Thread] = None + self._load_thread: threading.Thread | None = None # Lock for accessing/manipulating `_loaded_strings` and `_loaded` # together in a consistent state. @@ -119,7 +120,7 @@ def __init__(self, history: History) -> None: # Events created by each `load()` call. Used to wait for new history # entries from the loader thread. - self._string_load_events: List[threading.Event] = [] + self._string_load_events: list[threading.Event] = [] async def load(self) -> AsyncGenerator[str, None]: """ @@ -135,7 +136,7 @@ async def load(self) -> AsyncGenerator[str, None]: self._load_thread.start() # Consume the `_loaded_strings` list, using asyncio. - loop = get_event_loop() + loop = get_running_loop() # Create threading Event so that we can wait for new items. event = threading.Event() @@ -159,7 +160,7 @@ async def load(self) -> AsyncGenerator[str, None]: continue # Read new items (in lock). - def in_executor() -> Tuple[List[str], bool]: + def in_executor() -> tuple[list[str], bool]: with self._lock: new_items = self._loaded_strings[items_yielded:] done = self._loaded @@ -222,7 +223,7 @@ class InMemoryHistory(History): `append_string` for all items or pass a list of strings to `__init__` here. """ - def __init__(self, history_strings: Optional[Sequence[str]] = None) -> None: + def __init__(self, history_strings: Sequence[str] | None = None) -> None: super().__init__() # Emulating disk storage. if history_strings is None: @@ -263,8 +264,8 @@ def __init__(self, filename: str) -> None: super().__init__() def load_history_strings(self) -> Iterable[str]: - strings: List[str] = [] - lines: List[str] = [] + strings: list[str] = [] + lines: list[str] = [] def add() -> None: if lines: diff --git a/src/prompt_toolkit/input/__init__.py b/src/prompt_toolkit/input/__init__.py index dc319769ef..ed8631b4fa 100644 --- a/src/prompt_toolkit/input/__init__.py +++ b/src/prompt_toolkit/input/__init__.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from .base import DummyInput, Input, PipeInput from .defaults import create_input, create_pipe_input diff --git a/src/prompt_toolkit/input/ansi_escape_sequences.py b/src/prompt_toolkit/input/ansi_escape_sequences.py index 2e6c5b9b28..e106a4da8a 100644 --- a/src/prompt_toolkit/input/ansi_escape_sequences.py +++ b/src/prompt_toolkit/input/ansi_escape_sequences.py @@ -10,6 +10,8 @@ Some useful docs: - Mintty: https://github.com/mintty/mintty/blob/master/wiki/Keycodes.md """ +from __future__ import annotations + from typing import Dict, Tuple, Union from ..keys import Keys @@ -20,7 +22,7 @@ ] # Mapping of vt100 escape codes to Keys. -ANSI_SEQUENCES: Dict[str, Union[Keys, Tuple[Keys, ...]]] = { +ANSI_SEQUENCES: dict[str, Keys | tuple[Keys, ...]] = { # Control keys. "\x00": Keys.ControlAt, # Control-At (Also for Ctrl-Space) "\x01": Keys.ControlA, # Control-A (home) @@ -325,12 +327,12 @@ } -def _get_reverse_ansi_sequences() -> Dict[Keys, str]: +def _get_reverse_ansi_sequences() -> dict[Keys, str]: """ Create a dictionary that maps prompt_toolkit keys back to the VT100 escape sequences. """ - result: Dict[Keys, str] = {} + result: dict[Keys, str] = {} for sequence, key in ANSI_SEQUENCES.items(): if not isinstance(key, tuple): diff --git a/src/prompt_toolkit/input/base.py b/src/prompt_toolkit/input/base.py index 313622de5a..e598bd72b0 100644 --- a/src/prompt_toolkit/input/base.py +++ b/src/prompt_toolkit/input/base.py @@ -1,6 +1,8 @@ """ Abstraction of CLI Input. """ +from __future__ import annotations + from abc import ABCMeta, abstractmethod, abstractproperty from contextlib import contextmanager from typing import Callable, ContextManager, Generator, List @@ -36,12 +38,12 @@ def typeahead_hash(self) -> str: """ @abstractmethod - def read_keys(self) -> List[KeyPress]: + def read_keys(self) -> list[KeyPress]: """ Return a list of Key objects which are read/parsed from the input. """ - def flush_keys(self) -> List[KeyPress]: + def flush_keys(self) -> list[KeyPress]: """ Flush the underlying parser. and return the pending keys. (Used for vt100 input.) @@ -116,7 +118,7 @@ def fileno(self) -> int: def typeahead_hash(self) -> str: return "dummy-%s" % id(self) - def read_keys(self) -> List[KeyPress]: + def read_keys(self) -> list[KeyPress]: return [] @property diff --git a/src/prompt_toolkit/input/defaults.py b/src/prompt_toolkit/input/defaults.py index 564f8484eb..29ccc9fb8c 100644 --- a/src/prompt_toolkit/input/defaults.py +++ b/src/prompt_toolkit/input/defaults.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import io import sys from typing import ContextManager, Optional, TextIO @@ -10,9 +12,7 @@ ] -def create_input( - stdin: Optional[TextIO] = None, always_prefer_tty: bool = False -) -> Input: +def create_input(stdin: TextIO | None = None, always_prefer_tty: bool = False) -> Input: """ Create the appropriate `Input` object for the current os/environment. diff --git a/src/prompt_toolkit/input/posix_pipe.py b/src/prompt_toolkit/input/posix_pipe.py index 1e7dec77fd..c131fb816e 100644 --- a/src/prompt_toolkit/input/posix_pipe.py +++ b/src/prompt_toolkit/input/posix_pipe.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import sys assert sys.platform != "win32" @@ -81,7 +83,7 @@ def fileno(stdin) -> int: @classmethod @contextmanager - def create(cls, text: str = "") -> Iterator["PosixPipeInput"]: + def create(cls, text: str = "") -> Iterator[PosixPipeInput]: pipe = _Pipe() try: yield PosixPipeInput(_pipe=pipe, _text=text) diff --git a/src/prompt_toolkit/input/posix_utils.py b/src/prompt_toolkit/input/posix_utils.py index 7cf31eebe6..e9c73fecc6 100644 --- a/src/prompt_toolkit/input/posix_utils.py +++ b/src/prompt_toolkit/input/posix_utils.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os import select from codecs import getincrementaldecoder diff --git a/src/prompt_toolkit/input/typeahead.py b/src/prompt_toolkit/input/typeahead.py index a3d7866449..c6a9263950 100644 --- a/src/prompt_toolkit/input/typeahead.py +++ b/src/prompt_toolkit/input/typeahead.py @@ -31,6 +31,8 @@ read too early, so that they can be feed into to the next `prompt()` call or to the next prompt_toolkit `Application`. """ +from __future__ import annotations + from collections import defaultdict from typing import Dict, List @@ -43,10 +45,10 @@ "clear_typeahead", ] -_buffer: Dict[str, List[KeyPress]] = defaultdict(list) +_buffer: dict[str, list[KeyPress]] = defaultdict(list) -def store_typeahead(input_obj: Input, key_presses: List[KeyPress]) -> None: +def store_typeahead(input_obj: Input, key_presses: list[KeyPress]) -> None: """ Insert typeahead key presses for the given input. """ @@ -55,7 +57,7 @@ def store_typeahead(input_obj: Input, key_presses: List[KeyPress]) -> None: _buffer[key].extend(key_presses) -def get_typeahead(input_obj: Input) -> List[KeyPress]: +def get_typeahead(input_obj: Input) -> list[KeyPress]: """ Retrieve typeahead and reset the buffer for this input. """ diff --git a/src/prompt_toolkit/input/vt100.py b/src/prompt_toolkit/input/vt100.py index 45ce37208a..31ba4fc918 100644 --- a/src/prompt_toolkit/input/vt100.py +++ b/src/prompt_toolkit/input/vt100.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import sys assert sys.platform != "win32" @@ -6,7 +8,7 @@ import io import termios import tty -from asyncio import AbstractEventLoop +from asyncio import AbstractEventLoop, get_running_loop from typing import ( Callable, ContextManager, @@ -20,8 +22,6 @@ Union, ) -from prompt_toolkit.eventloop import get_event_loop - from ..key_binding import KeyPress from .base import Input from .posix_utils import PosixStdinReader @@ -42,7 +42,7 @@ class Vt100Input(Input): # For the error messages. Only display "Input is not a terminal" once per # file descriptor. - _fds_not_a_terminal: Set[int] = set() + _fds_not_a_terminal: set[int] = set() def __init__(self, stdin: TextIO) -> None: # Test whether the given input object has a file descriptor. @@ -79,7 +79,7 @@ def __init__(self, stdin: TextIO) -> None: # underlying file is closed, so that `typeahead_hash()` keeps working. self._fileno = stdin.fileno() - self._buffer: List[KeyPress] = [] # Buffer to collect the Key objects. + self._buffer: list[KeyPress] = [] # Buffer to collect the Key objects. self.stdin_reader = PosixStdinReader(self._fileno, encoding=stdin.encoding) self.vt100_parser = Vt100Parser( lambda key_press: self._buffer.append(key_press) @@ -99,7 +99,7 @@ def detach(self) -> ContextManager[None]: """ return _detached_input(self) - def read_keys(self) -> List[KeyPress]: + def read_keys(self) -> list[KeyPress]: "Read list of KeyPress." # Read text from stdin. data = self.stdin_reader.read() @@ -112,7 +112,7 @@ def read_keys(self) -> List[KeyPress]: self._buffer = [] return result - def flush_keys(self) -> List[KeyPress]: + def flush_keys(self) -> list[KeyPress]: """ Flush pending keys and return them. (Used for flushing the 'escape' key.) @@ -143,8 +143,8 @@ def typeahead_hash(self) -> str: return f"fd-{self._fileno}" -_current_callbacks: Dict[ - Tuple[AbstractEventLoop, int], Optional[Callable[[], None]] +_current_callbacks: dict[ + tuple[AbstractEventLoop, int], Callable[[], None] | None ] = {} # (loop, fd) -> current callback @@ -158,7 +158,7 @@ def _attached_input( :param input: :class:`~prompt_toolkit.input.Input` object. :param callback: Called when the input is ready to read. """ - loop = get_event_loop() + loop = get_running_loop() fd = input.fileno() previous = _current_callbacks.get((loop, fd)) @@ -200,7 +200,7 @@ def callback_wrapper() -> None: @contextlib.contextmanager def _detached_input(input: Vt100Input) -> Generator[None, None, None]: - loop = get_event_loop() + loop = get_running_loop() fd = input.fileno() previous = _current_callbacks.get((loop, fd)) @@ -242,7 +242,7 @@ class raw_mode: # See: https://github.com/jonathanslenders/python-prompt-toolkit/pull/165 def __init__(self, fileno: int) -> None: self.fileno = fileno - self.attrs_before: Optional[List[Union[int, List[Union[bytes, int]]]]] + self.attrs_before: list[int | list[bytes | int]] | None try: self.attrs_before = termios.tcgetattr(fileno) except termios.error: diff --git a/src/prompt_toolkit/input/vt100_parser.py b/src/prompt_toolkit/input/vt100_parser.py index 3ee1e14fdd..5b8cff3f48 100644 --- a/src/prompt_toolkit/input/vt100_parser.py +++ b/src/prompt_toolkit/input/vt100_parser.py @@ -1,6 +1,8 @@ """ Parser for VT100 input stream. """ +from __future__ import annotations + import re from typing import Callable, Dict, Generator, Tuple, Union @@ -98,7 +100,7 @@ def _start_parser(self) -> None: self._input_parser = self._input_parser_generator() self._input_parser.send(None) # type: ignore - def _get_match(self, prefix: str) -> Union[None, Keys, Tuple[Keys, ...]]: + def _get_match(self, prefix: str) -> None | Keys | tuple[Keys, ...]: """ Return the key (or keys) that maps to this prefix. """ @@ -117,7 +119,7 @@ def _get_match(self, prefix: str) -> Union[None, Keys, Tuple[Keys, ...]]: except KeyError: return None - def _input_parser_generator(self) -> Generator[None, Union[str, _Flush], None]: + def _input_parser_generator(self) -> Generator[None, str | _Flush, None]: """ Coroutine (state machine) for the input parser. """ @@ -168,7 +170,7 @@ def _input_parser_generator(self) -> Generator[None, Union[str, _Flush], None]: prefix = prefix[1:] def _call_handler( - self, key: Union[str, Keys, Tuple[Keys, ...]], insert_text: str + self, key: str | Keys | tuple[Keys, ...], insert_text: str ) -> None: """ Callback to handler. diff --git a/src/prompt_toolkit/input/win32.py b/src/prompt_toolkit/input/win32.py index db3fa2badd..9c6653a5bd 100644 --- a/src/prompt_toolkit/input/win32.py +++ b/src/prompt_toolkit/input/win32.py @@ -1,10 +1,11 @@ +from __future__ import annotations + import os import sys from abc import abstractmethod +from asyncio import get_running_loop from contextlib import contextmanager -from prompt_toolkit.eventloop import get_event_loop - from ..utils import SPHINX_AUTODOC_RUNNING assert sys.platform == "win32" @@ -80,7 +81,7 @@ class Win32Input(_Win32InputBase): `Input` class that reads from the Windows console. """ - def __init__(self, stdin: Optional[TextIO] = None) -> None: + def __init__(self, stdin: TextIO | None = None) -> None: super().__init__() self.console_input_reader = ConsoleInputReader() @@ -98,7 +99,7 @@ def detach(self) -> ContextManager[None]: """ return detach_win32_input(self) - def read_keys(self) -> List[KeyPress]: + def read_keys(self) -> list[KeyPress]: return list(self.console_input_reader.read()) def flush(self) -> None: @@ -267,7 +268,7 @@ def read(self) -> Iterable[KeyPress]: if self.recognize_paste and self._is_paste(all_keys): gen = iter(all_keys) - k: Optional[KeyPress] + k: KeyPress | None for k in gen: # Pasting: if the current key consists of text or \n, turn it @@ -305,7 +306,7 @@ def _insert_key_data(self, key_press: KeyPress) -> KeyPress: return KeyPress(key_press.key, data) def _get_keys( - self, read: DWORD, input_records: "Array[INPUT_RECORD]" + self, read: DWORD, input_records: Array[INPUT_RECORD] ) -> Iterator[KeyPress]: """ Generator that yields `KeyPress` objects from the input records. @@ -329,7 +330,7 @@ def _get_keys( yield from self._handle_mouse(ev) @staticmethod - def _merge_paired_surrogates(key_presses: List[KeyPress]) -> Iterator[KeyPress]: + def _merge_paired_surrogates(key_presses: list[KeyPress]) -> Iterator[KeyPress]: """ Combines consecutive KeyPresses with high and low surrogates into single characters @@ -362,7 +363,7 @@ def _merge_paired_surrogates(key_presses: List[KeyPress]) -> Iterator[KeyPress]: yield buffered_high_surrogate @staticmethod - def _is_paste(keys: List[KeyPress]) -> bool: + def _is_paste(keys: list[KeyPress]) -> bool: """ Return `True` when we should consider this list of keys as a paste event. Pasted text on windows will be turned into a @@ -383,13 +384,13 @@ def _is_paste(keys: List[KeyPress]) -> bool: return newline_count >= 1 and text_count >= 1 - def _event_to_key_presses(self, ev: KEY_EVENT_RECORD) -> List[KeyPress]: + def _event_to_key_presses(self, ev: KEY_EVENT_RECORD) -> list[KeyPress]: """ For this `KEY_EVENT_RECORD`, return a list of `KeyPress` instances. """ assert type(ev) == KEY_EVENT_RECORD and ev.KeyDown - result: Optional[KeyPress] = None + result: KeyPress | None = None control_key_state = ev.ControlKeyState u_char = ev.uChar.UnicodeChar @@ -423,7 +424,7 @@ def _event_to_key_presses(self, ev: KEY_EVENT_RECORD) -> List[KeyPress]: and control_key_state & self.SHIFT_PRESSED and result ): - mapping: Dict[str, str] = { + mapping: dict[str, str] = { Keys.Left: Keys.ControlShiftLeft, Keys.Right: Keys.ControlShiftRight, Keys.Up: Keys.ControlShiftUp, @@ -515,14 +516,14 @@ def _event_to_key_presses(self, ev: KEY_EVENT_RECORD) -> List[KeyPress]: else: return [] - def _handle_mouse(self, ev: MOUSE_EVENT_RECORD) -> List[KeyPress]: + def _handle_mouse(self, ev: MOUSE_EVENT_RECORD) -> list[KeyPress]: """ Handle mouse events. Return a list of KeyPress instances. """ event_flags = ev.EventFlags button_state = ev.ButtonState - event_type: Optional[MouseEventType] = None + event_type: MouseEventType | None = None button: MouseButton = MouseButton.NONE # Scroll events. @@ -580,11 +581,11 @@ class _Win32Handles: """ def __init__(self) -> None: - self._handle_callbacks: Dict[int, Callable[[], None]] = {} + self._handle_callbacks: dict[int, Callable[[], None]] = {} # Windows Events that are triggered when we have to stop watching this # handle. - self._remove_events: Dict[int, HANDLE] = {} + self._remove_events: dict[int, HANDLE] = {} def add_win32_handle(self, handle: HANDLE, callback: Callable[[], None]) -> None: """ @@ -598,7 +599,7 @@ def add_win32_handle(self, handle: HANDLE, callback: Callable[[], None]) -> None # Make sure to remove a previous registered handler first. self.remove_win32_handle(handle) - loop = get_event_loop() + loop = get_running_loop() self._handle_callbacks[handle_value] = callback # Create remove event. @@ -629,7 +630,7 @@ def wait() -> None: run_in_executor_with_context(wait, loop=loop) - def remove_win32_handle(self, handle: HANDLE) -> Optional[Callable[[], None]]: + def remove_win32_handle(self, handle: HANDLE) -> Callable[[], None] | None: """ Remove a Win32 handle from the event loop. Return either the registered handler or `None`. @@ -708,7 +709,7 @@ class raw_mode: `raw_input` method of `.vt100_input`. """ - def __init__(self, fileno: Optional[int] = None) -> None: + def __init__(self, fileno: int | None = None) -> None: self.handle = HANDLE(windll.kernel32.GetStdHandle(STD_INPUT_HANDLE)) def __enter__(self) -> None: diff --git a/src/prompt_toolkit/input/win32_pipe.py b/src/prompt_toolkit/input/win32_pipe.py index ebee2075ed..8d54b63bf6 100644 --- a/src/prompt_toolkit/input/win32_pipe.py +++ b/src/prompt_toolkit/input/win32_pipe.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import sys assert sys.platform == "win32" @@ -48,7 +50,7 @@ def __init__(self, _event: HANDLE) -> None: self._closed = False # Parser for incoming keys. - self._buffer: List[KeyPress] = [] # Buffer to collect the Key objects. + self._buffer: list[KeyPress] = [] # Buffer to collect the Key objects. self.vt100_parser = Vt100Parser(lambda key: self._buffer.append(key)) # Identifier for every PipeInput for the hash. @@ -57,7 +59,7 @@ def __init__(self, _event: HANDLE) -> None: @classmethod @contextmanager - def create(cls) -> Iterator["Win32PipeInput"]: + def create(cls) -> Iterator[Win32PipeInput]: event = create_win32_event() try: yield Win32PipeInput(_event=event) @@ -93,7 +95,7 @@ def detach(self) -> ContextManager[None]: """ return detach_win32_input(self) - def read_keys(self) -> List[KeyPress]: + def read_keys(self) -> list[KeyPress]: "Read list of KeyPress." # Return result. @@ -107,7 +109,7 @@ def read_keys(self) -> List[KeyPress]: return result - def flush_keys(self) -> List[KeyPress]: + def flush_keys(self) -> list[KeyPress]: """ Flush pending keys and return them. (Used for flushing the 'escape' key.) diff --git a/src/prompt_toolkit/key_binding/__init__.py b/src/prompt_toolkit/key_binding/__init__.py index be10536915..c31746aba1 100644 --- a/src/prompt_toolkit/key_binding/__init__.py +++ b/src/prompt_toolkit/key_binding/__init__.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from .key_bindings import ( ConditionalKeyBindings, DynamicKeyBindings, diff --git a/src/prompt_toolkit/key_binding/bindings/auto_suggest.py b/src/prompt_toolkit/key_binding/bindings/auto_suggest.py index c016c0688f..32006f13aa 100644 --- a/src/prompt_toolkit/key_binding/bindings/auto_suggest.py +++ b/src/prompt_toolkit/key_binding/bindings/auto_suggest.py @@ -1,6 +1,8 @@ """ Key bindings for auto suggestion (for fish-style auto suggestion). """ +from __future__ import annotations + import re from prompt_toolkit.application.current import get_app diff --git a/src/prompt_toolkit/key_binding/bindings/basic.py b/src/prompt_toolkit/key_binding/bindings/basic.py index fc8f964359..084548d666 100644 --- a/src/prompt_toolkit/key_binding/bindings/basic.py +++ b/src/prompt_toolkit/key_binding/bindings/basic.py @@ -1,4 +1,6 @@ # pylint: disable=function-redefined +from __future__ import annotations + from prompt_toolkit.application.current import get_app from prompt_toolkit.filters import ( Condition, diff --git a/src/prompt_toolkit/key_binding/bindings/completion.py b/src/prompt_toolkit/key_binding/bindings/completion.py index a30b54e632..b4bf3dd58f 100644 --- a/src/prompt_toolkit/key_binding/bindings/completion.py +++ b/src/prompt_toolkit/key_binding/bindings/completion.py @@ -1,6 +1,8 @@ """ Key binding handlers for displaying completions. """ +from __future__ import annotations + import asyncio import math from typing import TYPE_CHECKING, List @@ -80,8 +82,8 @@ def display_completions_like_readline(event: E) -> None: def _display_completions_like_readline( - app: "Application[object]", completions: List[Completion] -) -> "asyncio.Task[None]": + app: Application[object], completions: list[Completion] +) -> asyncio.Task[None]: """ Display the list of completions in columns above the prompt. This will ask for a confirmation if there are too many completions to fit @@ -171,7 +173,7 @@ async def run_compl() -> None: return app.create_background_task(run_compl()) -def _create_more_session(message: str = "--MORE--") -> "PromptSession[bool]": +def _create_more_session(message: str = "--MORE--") -> PromptSession[bool]: """ Create a `PromptSession` object for displaying the "--MORE--". """ diff --git a/src/prompt_toolkit/key_binding/bindings/cpr.py b/src/prompt_toolkit/key_binding/bindings/cpr.py index 07b0fa7527..cd9df0a60e 100644 --- a/src/prompt_toolkit/key_binding/bindings/cpr.py +++ b/src/prompt_toolkit/key_binding/bindings/cpr.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from prompt_toolkit.key_binding.key_processor import KeyPressEvent from prompt_toolkit.keys import Keys diff --git a/src/prompt_toolkit/key_binding/bindings/emacs.py b/src/prompt_toolkit/key_binding/bindings/emacs.py index a4a5e348f8..957d9433a4 100644 --- a/src/prompt_toolkit/key_binding/bindings/emacs.py +++ b/src/prompt_toolkit/key_binding/bindings/emacs.py @@ -1,4 +1,6 @@ # pylint: disable=function-redefined +from __future__ import annotations + from typing import Dict, Union from prompt_toolkit.application.current import get_app @@ -425,7 +427,7 @@ def unshift_move(event: E) -> None: return # the other keys are handled through their readline command - key_to_command: Dict[Union[Keys, str], str] = { + key_to_command: dict[Keys | str, str] = { Keys.ShiftLeft: "backward-char", Keys.ShiftRight: "forward-char", Keys.ShiftHome: "beginning-of-line", diff --git a/src/prompt_toolkit/key_binding/bindings/focus.py b/src/prompt_toolkit/key_binding/bindings/focus.py index 40844db641..24aa3ce39b 100644 --- a/src/prompt_toolkit/key_binding/bindings/focus.py +++ b/src/prompt_toolkit/key_binding/bindings/focus.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from prompt_toolkit.key_binding.key_processor import KeyPressEvent __all__ = [ diff --git a/src/prompt_toolkit/key_binding/bindings/mouse.py b/src/prompt_toolkit/key_binding/bindings/mouse.py index 916cd41132..41838e603c 100644 --- a/src/prompt_toolkit/key_binding/bindings/mouse.py +++ b/src/prompt_toolkit/key_binding/bindings/mouse.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import sys from typing import TYPE_CHECKING, FrozenSet @@ -30,15 +32,15 @@ MOUSE_MOVE = MouseEventType.MOUSE_MOVE MOUSE_UP = MouseEventType.MOUSE_UP -NO_MODIFIER : FrozenSet[MouseModifier] = frozenset() -SHIFT : FrozenSet[MouseModifier] = frozenset({MouseModifier.SHIFT}) -ALT : FrozenSet[MouseModifier] = frozenset({MouseModifier.ALT}) -SHIFT_ALT : FrozenSet[MouseModifier] = frozenset({MouseModifier.SHIFT, MouseModifier.ALT}) -CONTROL : FrozenSet[MouseModifier] = frozenset({MouseModifier.CONTROL}) -SHIFT_CONTROL : FrozenSet[MouseModifier] = frozenset({MouseModifier.SHIFT, MouseModifier.CONTROL}) -ALT_CONTROL : FrozenSet[MouseModifier] = frozenset({MouseModifier.ALT, MouseModifier.CONTROL}) -SHIFT_ALT_CONTROL: FrozenSet[MouseModifier] = frozenset({MouseModifier.SHIFT, MouseModifier.ALT, MouseModifier.CONTROL}) -UNKNOWN_MODIFIER : FrozenSet[MouseModifier] = frozenset() +NO_MODIFIER : frozenset[MouseModifier] = frozenset() +SHIFT : frozenset[MouseModifier] = frozenset({MouseModifier.SHIFT}) +ALT : frozenset[MouseModifier] = frozenset({MouseModifier.ALT}) +SHIFT_ALT : frozenset[MouseModifier] = frozenset({MouseModifier.SHIFT, MouseModifier.ALT}) +CONTROL : frozenset[MouseModifier] = frozenset({MouseModifier.CONTROL}) +SHIFT_CONTROL : frozenset[MouseModifier] = frozenset({MouseModifier.SHIFT, MouseModifier.CONTROL}) +ALT_CONTROL : frozenset[MouseModifier] = frozenset({MouseModifier.ALT, MouseModifier.CONTROL}) +SHIFT_ALT_CONTROL: frozenset[MouseModifier] = frozenset({MouseModifier.SHIFT, MouseModifier.ALT, MouseModifier.CONTROL}) +UNKNOWN_MODIFIER : frozenset[MouseModifier] = frozenset() LEFT = MouseButton.LEFT MIDDLE = MouseButton.MIDDLE @@ -188,7 +190,7 @@ def load_mouse_bindings() -> KeyBindings: key_bindings = KeyBindings() @key_bindings.add(Keys.Vt100MouseEvent) - def _(event: E) -> "NotImplementedOrNone": + def _(event: E) -> NotImplementedOrNone: """ Handling of incoming mouse event. """ @@ -299,7 +301,7 @@ def _scroll_down(event: E) -> None: event.key_processor.feed(KeyPress(Keys.Down), first=True) @key_bindings.add(Keys.WindowsMouseEvent) - def _mouse(event: E) -> "NotImplementedOrNone": + def _mouse(event: E) -> NotImplementedOrNone: """ Handling of mouse events for Windows. """ diff --git a/src/prompt_toolkit/key_binding/bindings/named_commands.py b/src/prompt_toolkit/key_binding/bindings/named_commands.py index 5def09bfb5..9a91bb991a 100644 --- a/src/prompt_toolkit/key_binding/bindings/named_commands.py +++ b/src/prompt_toolkit/key_binding/bindings/named_commands.py @@ -3,6 +3,8 @@ See: http://www.delorie.com/gnu/docs/readline/rlman_13.html """ +from __future__ import annotations + from typing import Callable, Dict, TypeVar, Union, cast from prompt_toolkit.document import Document @@ -29,7 +31,7 @@ # Registry that maps the Readline command names to their handlers. -_readline_commands: Dict[str, Binding] = {} +_readline_commands: dict[str, Binding] = {} def register(name: str) -> Callable[[_T], _T]: diff --git a/src/prompt_toolkit/key_binding/bindings/open_in_editor.py b/src/prompt_toolkit/key_binding/bindings/open_in_editor.py index f8699f4a45..d156424f20 100644 --- a/src/prompt_toolkit/key_binding/bindings/open_in_editor.py +++ b/src/prompt_toolkit/key_binding/bindings/open_in_editor.py @@ -1,6 +1,8 @@ """ Open in editor key bindings. """ +from __future__ import annotations + from prompt_toolkit.filters import emacs_mode, has_selection, vi_navigation_mode from ..key_bindings import KeyBindings, KeyBindingsBase, merge_key_bindings diff --git a/src/prompt_toolkit/key_binding/bindings/page_navigation.py b/src/prompt_toolkit/key_binding/bindings/page_navigation.py index 4d531c0437..3918e14120 100644 --- a/src/prompt_toolkit/key_binding/bindings/page_navigation.py +++ b/src/prompt_toolkit/key_binding/bindings/page_navigation.py @@ -2,6 +2,8 @@ Key bindings for extra page navigation: bindings for up/down scrolling through long pages, like in Emacs or Vi. """ +from __future__ import annotations + from prompt_toolkit.filters import buffer_has_focus, emacs_mode, vi_mode from prompt_toolkit.key_binding.key_bindings import ( ConditionalKeyBindings, diff --git a/src/prompt_toolkit/key_binding/bindings/scroll.py b/src/prompt_toolkit/key_binding/bindings/scroll.py index 4a43ff585a..83a4be1f8f 100644 --- a/src/prompt_toolkit/key_binding/bindings/scroll.py +++ b/src/prompt_toolkit/key_binding/bindings/scroll.py @@ -5,6 +5,8 @@ they are very useful for navigating through long multiline buffers, like in Vi, Emacs, etc... """ +from __future__ import annotations + from prompt_toolkit.key_binding.key_processor import KeyPressEvent __all__ = [ diff --git a/src/prompt_toolkit/key_binding/bindings/search.py b/src/prompt_toolkit/key_binding/bindings/search.py index 06a047e4cd..ba5e117ff6 100644 --- a/src/prompt_toolkit/key_binding/bindings/search.py +++ b/src/prompt_toolkit/key_binding/bindings/search.py @@ -1,6 +1,8 @@ """ Search related key bindings. """ +from __future__ import annotations + from prompt_toolkit import search from prompt_toolkit.application.current import get_app from prompt_toolkit.filters import Condition, control_is_searchable, is_searching diff --git a/src/prompt_toolkit/key_binding/bindings/vi.py b/src/prompt_toolkit/key_binding/bindings/vi.py index 95d015afb3..6cf6ce6f67 100644 --- a/src/prompt_toolkit/key_binding/bindings/vi.py +++ b/src/prompt_toolkit/key_binding/bindings/vi.py @@ -1,4 +1,6 @@ # pylint: disable=function-redefined +from __future__ import annotations + import codecs import string from enum import Enum @@ -84,7 +86,7 @@ def selection_type(self) -> SelectionType: else: return SelectionType.CHARACTERS - def sorted(self) -> Tuple[int, int]: + def sorted(self) -> tuple[int, int]: """ Return a (start, end) tuple where start <= end. """ @@ -93,7 +95,7 @@ def sorted(self) -> Tuple[int, int]: else: return self.end, self.start - def operator_range(self, document: Document) -> Tuple[int, int]: + def operator_range(self, document: Document) -> tuple[int, int]: """ Return a (start, end) tuple with start <= end that indicates the range operators should operate on. @@ -125,7 +127,7 @@ def operator_range(self, document: Document) -> Tuple[int, int]: ) return start, end - def get_line_numbers(self, buffer: Buffer) -> Tuple[int, int]: + def get_line_numbers(self, buffer: Buffer) -> tuple[int, int]: """ Return a (start_line, end_line) pair. """ @@ -140,7 +142,7 @@ def get_line_numbers(self, buffer: Buffer) -> Tuple[int, int]: return from_, to - def cut(self, buffer: Buffer) -> Tuple[Document, ClipboardData]: + def cut(self, buffer: Buffer) -> tuple[Document, ClipboardData]: """ Turn text object into `ClipboardData` instance. """ @@ -179,7 +181,7 @@ def create_text_object_decorator( """ def text_object_decorator( - *keys: Union[Keys, str], + *keys: Keys | str, filter: Filter = Always(), no_move_handler: bool = False, no_selection_handler: bool = False, @@ -302,7 +304,7 @@ def create_operator_decorator( """ def operator_decorator( - *keys: Union[Keys, str], filter: Filter = Always(), eager: bool = False + *keys: Keys | str, filter: Filter = Always(), eager: bool = False ) -> Callable[[_OF], _OF]: """ Register a Vi operator. @@ -393,7 +395,7 @@ def load_vi_bindings() -> KeyBindingsBase: TransformFunction = Tuple[Tuple[str, ...], Filter, Callable[[str], str]] - vi_transform_functions: List[TransformFunction] = [ + vi_transform_functions: list[TransformFunction] = [ # Rot 13 transformation ( ("g", "?"), @@ -698,12 +700,12 @@ def insert_in_block_selection(event: E, after: bool = False) -> None: if after: - def get_pos(from_to: Tuple[int, int]) -> int: + def get_pos(from_to: tuple[int, int]) -> int: return from_to[1] else: - def get_pos(from_to: Tuple[int, int]) -> int: + def get_pos(from_to: tuple[int, int]) -> int: return from_to[0] for i, from_to in enumerate(buff.document.selection_ranges()): @@ -1334,7 +1336,7 @@ def _hard_start_of_line(event: E) -> TextObject: ) def create_ci_ca_handles( - ci_start: str, ci_end: str, inner: bool, key: Optional[str] = None + ci_start: str, ci_end: str, inner: bool, key: str | None = None ) -> None: # TODO: 'dat', 'dit', (tags (like xml) """ @@ -1472,7 +1474,7 @@ def _(event: E) -> TextObject: """ Repeat the last 'f'/'F'/'t'/'T' command. """ - pos: Optional[int] = 0 + pos: int | None = 0 vi_state = event.app.vi_state type = TextObjectType.EXCLUSIVE @@ -2085,7 +2087,7 @@ def _create_digraph(event: E) -> None: """ try: # Lookup. - code: Tuple[str, str] = ( + code: tuple[str, str] = ( event.app.vi_state.digraph_symbol1 or "", event.data, ) @@ -2158,7 +2160,7 @@ def _execute_macro(event: E) -> None: # Expand macro (which is a string in the register), in individual keys. # Use vt100 parser for this. - keys: List[KeyPress] = [] + keys: list[KeyPress] = [] parser = Vt100Parser(keys.append) parser.feed(macro.text) diff --git a/src/prompt_toolkit/key_binding/defaults.py b/src/prompt_toolkit/key_binding/defaults.py index baa5974333..166da8d4ac 100644 --- a/src/prompt_toolkit/key_binding/defaults.py +++ b/src/prompt_toolkit/key_binding/defaults.py @@ -4,6 +4,8 @@ key_bindings = load_key_bindings() app = Application(key_bindings=key_bindings) """ +from __future__ import annotations + from prompt_toolkit.filters import buffer_has_focus from prompt_toolkit.key_binding.bindings.basic import load_basic_bindings from prompt_toolkit.key_binding.bindings.cpr import load_cpr_bindings diff --git a/src/prompt_toolkit/key_binding/digraphs.py b/src/prompt_toolkit/key_binding/digraphs.py index ad3d288a8a..0c83d1e246 100644 --- a/src/prompt_toolkit/key_binding/digraphs.py +++ b/src/prompt_toolkit/key_binding/digraphs.py @@ -6,6 +6,8 @@ Taken from Neovim and translated to Python: https://raw.githubusercontent.com/neovim/neovim/master/src/nvim/digraph.c """ +from __future__ import annotations + from typing import Dict, Tuple __all__ = [ @@ -14,7 +16,7 @@ # digraphs for Unicode from RFC1345 # (also work for ISO-8859-1 aka latin1) -DIGRAPHS: Dict[Tuple[str, str], int] = { +DIGRAPHS: dict[tuple[str, str], int] = { ("N", "U"): 0x00, ("S", "H"): 0x01, ("S", "X"): 0x02, diff --git a/src/prompt_toolkit/key_binding/emacs_state.py b/src/prompt_toolkit/key_binding/emacs_state.py index 4c996224a0..d9d1a89cfe 100644 --- a/src/prompt_toolkit/key_binding/emacs_state.py +++ b/src/prompt_toolkit/key_binding/emacs_state.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import List, Optional from .key_processor import KeyPress @@ -15,8 +17,8 @@ class EmacsState: def __init__(self) -> None: # Simple macro recording. (Like Readline does.) # (For Emacs mode.) - self.macro: Optional[List[KeyPress]] = [] - self.current_recording: Optional[List[KeyPress]] = None + self.macro: list[KeyPress] | None = [] + self.current_recording: list[KeyPress] | None = None def reset(self) -> None: self.current_recording = None diff --git a/src/prompt_toolkit/key_binding/key_bindings.py b/src/prompt_toolkit/key_binding/key_bindings.py index 6d9d49e989..f8babd4fd5 100644 --- a/src/prompt_toolkit/key_binding/key_bindings.py +++ b/src/prompt_toolkit/key_binding/key_bindings.py @@ -34,6 +34,8 @@ def my_key_binding(event): # Later, add it to the key bindings. kb.add(Keys.A, my_key_binding) """ +from __future__ import annotations + from abc import ABCMeta, abstractmethod, abstractproperty from inspect import isawaitable from typing import ( @@ -104,12 +106,12 @@ class Binding: def __init__( self, - keys: Tuple[Union[Keys, str], ...], + keys: tuple[Keys | str, ...], handler: KeyHandlerCallable, filter: FilterOrBool = True, eager: FilterOrBool = False, is_global: FilterOrBool = False, - save_before: Callable[["KeyPressEvent"], bool] = (lambda e: True), + save_before: Callable[[KeyPressEvent], bool] = (lambda e: True), record_in_macro: FilterOrBool = True, ) -> None: self.keys = keys @@ -120,7 +122,7 @@ def __init__( self.save_before = save_before self.record_in_macro = to_filter(record_in_macro) - def call(self, event: "KeyPressEvent") -> None: + def call(self, event: KeyPressEvent) -> None: result = self.handler(event) # If the handler is a coroutine, create an asyncio task. @@ -163,7 +165,7 @@ def _version(self) -> Hashable: return 0 @abstractmethod - def get_bindings_for_keys(self, keys: KeysTuple) -> List[Binding]: + def get_bindings_for_keys(self, keys: KeysTuple) -> list[Binding]: """ Return a list of key bindings that can handle these keys. (This return also inactive bindings, so the `filter` still has to be @@ -174,7 +176,7 @@ def get_bindings_for_keys(self, keys: KeysTuple) -> List[Binding]: return [] @abstractmethod - def get_bindings_starting_with_keys(self, keys: KeysTuple) -> List[Binding]: + def get_bindings_starting_with_keys(self, keys: KeysTuple) -> list[Binding]: """ Return a list of key bindings that handle a key sequence starting with `keys`. (It does only return bindings for which the sequences are @@ -186,7 +188,7 @@ def get_bindings_starting_with_keys(self, keys: KeysTuple) -> List[Binding]: return [] @abstractproperty - def bindings(self) -> List[Binding]: + def bindings(self) -> list[Binding]: """ List of `Binding` objects. (These need to be exposed, so that `KeyBindings` objects can be merged @@ -223,12 +225,12 @@ def _(event): """ def __init__(self) -> None: - self._bindings: List[Binding] = [] + self._bindings: list[Binding] = [] self._get_bindings_for_keys_cache: SimpleCache[ - KeysTuple, List[Binding] + KeysTuple, list[Binding] ] = SimpleCache(maxsize=10000) self._get_bindings_starting_with_keys_cache: SimpleCache[ - KeysTuple, List[Binding] + KeysTuple, list[Binding] ] = SimpleCache(maxsize=1000) self.__version = 0 # For cache invalidation. @@ -238,7 +240,7 @@ def _clear_cache(self) -> None: self._get_bindings_starting_with_keys_cache.clear() @property - def bindings(self) -> List[Binding]: + def bindings(self) -> list[Binding]: return self._bindings @property @@ -247,11 +249,11 @@ def _version(self) -> Hashable: def add( self, - *keys: Union[Keys, str], + *keys: Keys | str, filter: FilterOrBool = True, eager: FilterOrBool = False, is_global: FilterOrBool = False, - save_before: Callable[["KeyPressEvent"], bool] = (lambda e: True), + save_before: Callable[[KeyPressEvent], bool] = (lambda e: True), record_in_macro: FilterOrBool = True, ) -> Callable[[T], T]: """ @@ -317,7 +319,7 @@ def decorator(func: T) -> T: return decorator - def remove(self, *args: Union[Keys, str, KeyHandlerCallable]) -> None: + def remove(self, *args: Keys | str | KeyHandlerCallable) -> None: """ Remove a key binding. @@ -365,7 +367,7 @@ def remove(self, *args: Union[Keys, str, KeyHandlerCallable]) -> None: add_binding = add remove_binding = remove - def get_bindings_for_keys(self, keys: KeysTuple) -> List[Binding]: + def get_bindings_for_keys(self, keys: KeysTuple) -> list[Binding]: """ Return a list of key bindings that can handle this key. (This return also inactive bindings, so the `filter` still has to be @@ -374,8 +376,8 @@ def get_bindings_for_keys(self, keys: KeysTuple) -> List[Binding]: :param keys: tuple of keys. """ - def get() -> List[Binding]: - result: List[Tuple[int, Binding]] = [] + def get() -> list[Binding]: + result: list[tuple[int, Binding]] = [] for b in self.bindings: if len(keys) == len(b.keys): @@ -400,7 +402,7 @@ def get() -> List[Binding]: return self._get_bindings_for_keys_cache.get(keys, get) - def get_bindings_starting_with_keys(self, keys: KeysTuple) -> List[Binding]: + def get_bindings_starting_with_keys(self, keys: KeysTuple) -> list[Binding]: """ Return a list of key bindings that handle a key sequence starting with `keys`. (It does only return bindings for which the sequences are @@ -410,7 +412,7 @@ def get_bindings_starting_with_keys(self, keys: KeysTuple) -> List[Binding]: :param keys: tuple of keys. """ - def get() -> List[Binding]: + def get() -> list[Binding]: result = [] for b in self.bindings: if len(keys) < len(b.keys): @@ -426,7 +428,7 @@ def get() -> List[Binding]: return self._get_bindings_starting_with_keys_cache.get(keys, get) -def _parse_key(key: Union[Keys, str]) -> Union[str, Keys]: +def _parse_key(key: Keys | str) -> str | Keys: """ Replace key by alias and verify whether it's a valid one. """ @@ -458,7 +460,7 @@ def key_binding( filter: FilterOrBool = True, eager: FilterOrBool = False, is_global: FilterOrBool = False, - save_before: Callable[["KeyPressEvent"], bool] = (lambda event: True), + save_before: Callable[[KeyPressEvent], bool] = (lambda event: True), record_in_macro: FilterOrBool = True, ) -> Callable[[KeyHandlerCallable], Binding]: """ @@ -508,7 +510,7 @@ def _update_cache(self) -> None: # Proxy methods to self._bindings2. @property - def bindings(self) -> List[Binding]: + def bindings(self) -> list[Binding]: self._update_cache() return self._bindings2.bindings @@ -517,11 +519,11 @@ def _version(self) -> Hashable: self._update_cache() return self._last_version - def get_bindings_for_keys(self, keys: KeysTuple) -> List[Binding]: + def get_bindings_for_keys(self, keys: KeysTuple) -> list[Binding]: self._update_cache() return self._bindings2.get_bindings_for_keys(keys) - def get_bindings_starting_with_keys(self, keys: KeysTuple) -> List[Binding]: + def get_bindings_starting_with_keys(self, keys: KeysTuple) -> list[Binding]: self._update_cache() return self._bindings2.get_bindings_starting_with_keys(keys) @@ -626,9 +628,7 @@ class DynamicKeyBindings(_Proxy): :param get_key_bindings: Callable that returns a :class:`.KeyBindings` instance. """ - def __init__( - self, get_key_bindings: Callable[[], Optional[KeyBindingsBase]] - ) -> None: + def __init__(self, get_key_bindings: Callable[[], KeyBindingsBase | None]) -> None: self.get_key_bindings = get_key_bindings self.__version = 0 self._last_child_version = None diff --git a/src/prompt_toolkit/key_binding/key_processor.py b/src/prompt_toolkit/key_binding/key_processor.py index e5b5d4c2c8..cd07d4f33c 100644 --- a/src/prompt_toolkit/key_binding/key_processor.py +++ b/src/prompt_toolkit/key_binding/key_processor.py @@ -5,6 +5,8 @@ The `KeyProcessor` will according to the implemented keybindings call the correct callbacks when new key presses are feed through `feed`. """ +from __future__ import annotations + import weakref from asyncio import Task, sleep from collections import deque @@ -36,7 +38,7 @@ class KeyPress: :param data: The received string on stdin. (Often vt100 escape codes.) """ - def __init__(self, key: Union[Keys, str], data: Optional[str] = None) -> None: + def __init__(self, key: Keys | str, data: str | None = None) -> None: assert isinstance(key, Keys) or len(key) == 1 if data is None: @@ -92,30 +94,30 @@ def __init__(self, key_bindings: KeyBindingsBase) -> None: self.before_key_press = Event(self) self.after_key_press = Event(self) - self._flush_wait_task: Optional[Task[None]] = None + self._flush_wait_task: Task[None] | None = None self.reset() def reset(self) -> None: - self._previous_key_sequence: List[KeyPress] = [] - self._previous_handler: Optional[Binding] = None + self._previous_key_sequence: list[KeyPress] = [] + self._previous_handler: Binding | None = None # The queue of keys not yet send to our _process generator/state machine. self.input_queue: Deque[KeyPress] = deque() # The key buffer that is matched in the generator state machine. # (This is at at most the amount of keys that make up for one key binding.) - self.key_buffer: List[KeyPress] = [] + self.key_buffer: list[KeyPress] = [] #: Readline argument (for repetition of commands.) #: https://www.gnu.org/software/bash/manual/html_node/Readline-Arguments.html - self.arg: Optional[str] = None + self.arg: str | None = None # Start the processor coroutine. self._process_coroutine = self._process() self._process_coroutine.send(None) # type: ignore - def _get_matches(self, key_presses: List[KeyPress]) -> List[Binding]: + def _get_matches(self, key_presses: list[KeyPress]) -> list[Binding]: """ For a list of :class:`KeyPress` instances. Give the matching handlers that would handle this. @@ -125,7 +127,7 @@ def _get_matches(self, key_presses: List[KeyPress]) -> List[Binding]: # Try match, with mode flag return [b for b in self._bindings.get_bindings_for_keys(keys) if b.filter()] - def _is_prefix_of_longer_match(self, key_presses: List[KeyPress]) -> bool: + def _is_prefix_of_longer_match(self, key_presses: list[KeyPress]) -> bool: """ For a list of :class:`KeyPress` instances. Return True if there is any handler that is bound to a suffix of this keys. @@ -214,7 +216,7 @@ def feed(self, key_press: KeyPress, first: bool = False) -> None: else: self.input_queue.append(key_press) - def feed_multiple(self, key_presses: List[KeyPress], first: bool = False) -> None: + def feed_multiple(self, key_presses: list[KeyPress], first: bool = False) -> None: """ :param first: If true, insert before everything else. """ @@ -282,7 +284,7 @@ def get_next() -> KeyPress: if not is_flush: self._start_timeout() - def empty_queue(self) -> List[KeyPress]: + def empty_queue(self) -> list[KeyPress]: """ Empty the input queue. Return the unprocessed input. """ @@ -293,7 +295,7 @@ def empty_queue(self) -> List[KeyPress]: key_presses = [k for k in key_presses if k.key != Keys.CPRResponse] return key_presses - def _call_handler(self, handler: Binding, key_sequence: List[KeyPress]) -> None: + def _call_handler(self, handler: Binding, key_sequence: list[KeyPress]) -> None: app = get_app() was_recording_emacs = app.emacs_state.is_recording was_recording_vi = bool(app.vi_state.recording_register) @@ -344,7 +346,7 @@ def _call_handler(self, handler: Binding, key_sequence: List[KeyPress]) -> None: for k in key_sequence: app.vi_state.current_recording += k.data - def _fix_vi_cursor_position(self, event: "KeyPressEvent") -> None: + def _fix_vi_cursor_position(self, event: KeyPressEvent) -> None: """ After every command, make sure that if we are in Vi navigation mode, we never put the cursor after the last character of a line. (Unless it's @@ -365,7 +367,7 @@ def _fix_vi_cursor_position(self, event: "KeyPressEvent") -> None: # (This was cleared after changing the cursor position.) buff.preferred_column = preferred_column - def _leave_vi_temp_navigation_mode(self, event: "KeyPressEvent") -> None: + def _leave_vi_temp_navigation_mode(self, event: KeyPressEvent) -> None: """ If we're in Vi temporary navigation (normal) mode, return to insert/replace mode after executing one action. @@ -431,10 +433,10 @@ class KeyPressEvent: def __init__( self, - key_processor_ref: "weakref.ReferenceType[KeyProcessor]", - arg: Optional[str], - key_sequence: List[KeyPress], - previous_key_sequence: List[KeyPress], + key_processor_ref: weakref.ReferenceType[KeyProcessor], + arg: str | None, + key_sequence: list[KeyPress], + previous_key_sequence: list[KeyPress], is_repeat: bool, ) -> None: self._key_processor_ref = key_processor_ref @@ -466,14 +468,14 @@ def key_processor(self) -> KeyProcessor: return processor @property - def app(self) -> "Application[Any]": + def app(self) -> Application[Any]: """ The current `Application` object. """ return self._app @property - def current_buffer(self) -> "Buffer": + def current_buffer(self) -> Buffer: """ The current buffer. """ @@ -522,6 +524,6 @@ def append_to_arg_count(self, data: str) -> None: self.key_processor.arg = result @property - def cli(self) -> "Application[Any]": + def cli(self) -> Application[Any]: "For backward-compatibility." return self.app diff --git a/src/prompt_toolkit/key_binding/vi_state.py b/src/prompt_toolkit/key_binding/vi_state.py index 10593a82e6..96da93d432 100644 --- a/src/prompt_toolkit/key_binding/vi_state.py +++ b/src/prompt_toolkit/key_binding/vi_state.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from enum import Enum from typing import TYPE_CHECKING, Callable, Dict, Optional @@ -38,26 +40,24 @@ class ViState: def __init__(self) -> None: #: None or CharacterFind instance. (This is used to repeat the last #: search in Vi mode, by pressing the 'n' or 'N' in navigation mode.) - self.last_character_find: Optional[CharacterFind] = None + self.last_character_find: CharacterFind | None = None # When an operator is given and we are waiting for text object, # -- e.g. in the case of 'dw', after the 'd' --, an operator callback # is set here. - self.operator_func: Optional[ - Callable[["KeyPressEvent", "TextObject"], None] - ] = None - self.operator_arg: Optional[int] = None + self.operator_func: None | (Callable[[KeyPressEvent, TextObject], None]) = None + self.operator_arg: int | None = None #: Named registers. Maps register name (e.g. 'a') to #: :class:`ClipboardData` instances. - self.named_registers: Dict[str, ClipboardData] = {} + self.named_registers: dict[str, ClipboardData] = {} #: The Vi mode we're currently in to. self.__input_mode = InputMode.INSERT #: Waiting for digraph. self.waiting_for_digraph = False - self.digraph_symbol1: Optional[str] = None # (None or a symbol.) + self.digraph_symbol1: str | None = None # (None or a symbol.) #: When true, make ~ act as an operator. self.tilde_operator = False @@ -67,7 +67,7 @@ def __init__(self) -> None: # Note that the recording is only stored in the register after the # recording is stopped. So we record in a separate `current_recording` # variable. - self.recording_register: Optional[str] = None + self.recording_register: str | None = None self.current_recording: str = "" # Temporary navigation (normal) mode. diff --git a/src/prompt_toolkit/keys.py b/src/prompt_toolkit/keys.py index e10ba9d921..c7e076fff8 100644 --- a/src/prompt_toolkit/keys.py +++ b/src/prompt_toolkit/keys.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from enum import Enum from typing import Dict, List @@ -204,11 +206,11 @@ class Keys(str, Enum): ShiftControlEnd = ControlShiftEnd -ALL_KEYS: List[str] = [k.value for k in Keys] +ALL_KEYS: list[str] = [k.value for k in Keys] # Aliases. -KEY_ALIASES: Dict[str, str] = { +KEY_ALIASES: dict[str, str] = { "backspace": "c-h", "c-space": "c-@", "enter": "c-m", diff --git a/src/prompt_toolkit/layout/__init__.py b/src/prompt_toolkit/layout/__init__.py index 6669da5d7a..c5fce461a0 100644 --- a/src/prompt_toolkit/layout/__init__.py +++ b/src/prompt_toolkit/layout/__init__.py @@ -44,6 +44,8 @@ - CompletionsMenu """ +from __future__ import annotations + from .containers import ( AnyContainer, ColorColumn, diff --git a/src/prompt_toolkit/layout/containers.py b/src/prompt_toolkit/layout/containers.py index 28b32d5572..ffb39fe285 100644 --- a/src/prompt_toolkit/layout/containers.py +++ b/src/prompt_toolkit/layout/containers.py @@ -2,6 +2,8 @@ Container for the layout. (Containers can contain other containers or user interface controls.) """ +from __future__ import annotations + from abc import ABCMeta, abstractmethod from enum import Enum from functools import partial @@ -120,7 +122,7 @@ def write_to_screen( write_position: WritePosition, parent_style: str, erase_bg: bool, - z_index: Optional[int], + z_index: int | None, ) -> None: """ Write the actual content to the screen. @@ -141,7 +143,7 @@ def is_modal(self) -> bool: """ return False - def get_key_bindings(self) -> Optional[KeyBindingsBase]: + def get_key_bindings(self) -> KeyBindingsBase | None: """ Returns a :class:`.KeyBindings` object. These bindings become active when any user control in this container has the focus, except if any containers @@ -150,7 +152,7 @@ def get_key_bindings(self) -> Optional[KeyBindingsBase]: return None @abstractmethod - def get_children(self) -> List["Container"]: + def get_children(self) -> list[Container]: """ Return the list of child :class:`.Container` objects. """ @@ -164,14 +166,14 @@ class MagicContainer(Protocol): Any object that implements ``__pt_container__`` represents a container. """ - def __pt_container__(self) -> "AnyContainer": + def __pt_container__(self) -> AnyContainer: ... AnyContainer = Union[Container, "MagicContainer"] -def _window_too_small() -> "Window": +def _window_too_small() -> Window: "Create a `Window` that displays the 'Window too small' text." return Window( FormattedTextControl(text=[("class:window-too-small", " Window too small... ")]) @@ -202,16 +204,16 @@ class _Split(Container): def __init__( self, children: Sequence[AnyContainer], - window_too_small: Optional[Container] = None, + window_too_small: Container | None = None, padding: AnyDimension = Dimension.exact(0), - padding_char: Optional[str] = None, + padding_char: str | None = None, padding_style: str = "", width: AnyDimension = None, height: AnyDimension = None, - z_index: Optional[int] = None, + z_index: int | None = None, modal: bool = False, - key_bindings: Optional[KeyBindingsBase] = None, - style: Union[str, Callable[[], str]] = "", + key_bindings: KeyBindingsBase | None = None, + style: str | Callable[[], str] = "", ) -> None: self.children = [to_container(c) for c in children] self.window_too_small = window_too_small or _window_too_small() @@ -230,10 +232,10 @@ def __init__( def is_modal(self) -> bool: return self.modal - def get_key_bindings(self) -> Optional[KeyBindingsBase]: + def get_key_bindings(self) -> KeyBindingsBase | None: return self.key_bindings - def get_children(self) -> List[Container]: + def get_children(self) -> list[Container]: return self.children @@ -274,17 +276,17 @@ class HSplit(_Split): def __init__( self, children: Sequence[AnyContainer], - window_too_small: Optional[Container] = None, + window_too_small: Container | None = None, align: VerticalAlign = VerticalAlign.JUSTIFY, padding: AnyDimension = 0, - padding_char: Optional[str] = None, + padding_char: str | None = None, padding_style: str = "", width: AnyDimension = None, height: AnyDimension = None, - z_index: Optional[int] = None, + z_index: int | None = None, modal: bool = False, - key_bindings: Optional[KeyBindingsBase] = None, - style: Union[str, Callable[[], str]] = "", + key_bindings: KeyBindingsBase | None = None, + style: str | Callable[[], str] = "", ) -> None: super().__init__( children=children, @@ -303,7 +305,7 @@ def __init__( self.align = align self._children_cache: SimpleCache[ - Tuple[Container, ...], List[Container] + tuple[Container, ...], list[Container] ] = SimpleCache(maxsize=1) self._remaining_space_window = Window() # Dummy window. @@ -331,13 +333,13 @@ def reset(self) -> None: c.reset() @property - def _all_children(self) -> List[Container]: + def _all_children(self) -> list[Container]: """ List of child objects, including padding. """ - def get() -> List[Container]: - result: List[Container] = [] + def get() -> list[Container]: + result: list[Container] = [] # Padding Top. if self.align in (VerticalAlign.CENTER, VerticalAlign.BOTTOM): @@ -371,7 +373,7 @@ def write_to_screen( write_position: WritePosition, parent_style: str, erase_bg: bool, - z_index: Optional[int], + z_index: int | None, ) -> None: """ Render the prompt to a `Screen` instance. @@ -421,7 +423,7 @@ def write_to_screen( z_index, ) - def _divide_heights(self, write_position: WritePosition) -> Optional[List[int]]: + def _divide_heights(self, write_position: WritePosition) -> list[int] | None: """ Return the heights for all rows. Or None when there is not enough space. @@ -511,17 +513,17 @@ class VSplit(_Split): def __init__( self, children: Sequence[AnyContainer], - window_too_small: Optional[Container] = None, + window_too_small: Container | None = None, align: HorizontalAlign = HorizontalAlign.JUSTIFY, padding: AnyDimension = 0, - padding_char: Optional[str] = None, + padding_char: str | None = None, padding_style: str = "", width: AnyDimension = None, height: AnyDimension = None, - z_index: Optional[int] = None, + z_index: int | None = None, modal: bool = False, - key_bindings: Optional[KeyBindingsBase] = None, - style: Union[str, Callable[[], str]] = "", + key_bindings: KeyBindingsBase | None = None, + style: str | Callable[[], str] = "", ) -> None: super().__init__( children=children, @@ -540,7 +542,7 @@ def __init__( self.align = align self._children_cache: SimpleCache[ - Tuple[Container, ...], List[Container] + tuple[Container, ...], list[Container] ] = SimpleCache(maxsize=1) self._remaining_space_window = Window() # Dummy window. @@ -583,13 +585,13 @@ def reset(self) -> None: c.reset() @property - def _all_children(self) -> List[Container]: + def _all_children(self) -> list[Container]: """ List of child objects, including padding. """ - def get() -> List[Container]: - result: List[Container] = [] + def get() -> list[Container]: + result: list[Container] = [] # Padding left. if self.align in (HorizontalAlign.CENTER, HorizontalAlign.RIGHT): @@ -616,7 +618,7 @@ def get() -> List[Container]: return self._children_cache.get(tuple(self.children), get) - def _divide_widths(self, width: int) -> Optional[List[int]]: + def _divide_widths(self, width: int) -> list[int] | None: """ Return the widths for all columns. Or None when there is not enough space. @@ -674,7 +676,7 @@ def write_to_screen( write_position: WritePosition, parent_style: str, erase_bg: bool, - z_index: Optional[int], + z_index: int | None, ) -> None: """ Render the prompt to a `Screen` instance. @@ -760,11 +762,11 @@ class FloatContainer(Container): def __init__( self, content: AnyContainer, - floats: List["Float"], + floats: list[Float], modal: bool = False, - key_bindings: Optional[KeyBindingsBase] = None, - style: Union[str, Callable[[], str]] = "", - z_index: Optional[int] = None, + key_bindings: KeyBindingsBase | None = None, + style: str | Callable[[], str] = "", + z_index: int | None = None, ) -> None: self.content = to_container(content) self.floats = floats @@ -798,7 +800,7 @@ def write_to_screen( write_position: WritePosition, parent_style: str, erase_bg: bool, - z_index: Optional[int], + z_index: int | None, ) -> None: style = parent_style + " " + to_str(self.style) z_index = z_index if self.z_index is None else self.z_index @@ -850,13 +852,13 @@ def write_to_screen( def _draw_float( self, - fl: "Float", + fl: Float, screen: Screen, mouse_handlers: MouseHandlers, write_position: WritePosition, style: str, erase_bg: bool, - z_index: Optional[int], + z_index: int | None, ) -> None: "Draw a single Float." # When a menu_position was given, use this instead of the cursor @@ -1013,10 +1015,10 @@ def _area_is_empty(self, screen: Screen, write_position: WritePosition) -> bool: def is_modal(self) -> bool: return self.modal - def get_key_bindings(self) -> Optional[KeyBindingsBase]: + def get_key_bindings(self) -> KeyBindingsBase | None: return self.key_bindings - def get_children(self) -> List[Container]: + def get_children(self) -> list[Container]: children = [self.content] children.extend(f.content for f in self.floats) return children @@ -1051,15 +1053,15 @@ class Float: def __init__( self, content: AnyContainer, - top: Optional[int] = None, - right: Optional[int] = None, - bottom: Optional[int] = None, - left: Optional[int] = None, - width: Optional[Union[int, Callable[[], int]]] = None, - height: Optional[Union[int, Callable[[], int]]] = None, + top: int | None = None, + right: int | None = None, + bottom: int | None = None, + left: int | None = None, + width: int | Callable[[], int] | None = None, + height: int | Callable[[], int] | None = None, xcursor: bool = False, ycursor: bool = False, - attach_to_window: Optional[AnyContainer] = None, + attach_to_window: AnyContainer | None = None, hide_when_covering_content: bool = False, allow_cover_cursor: bool = False, z_index: int = 1, @@ -1088,12 +1090,12 @@ def __init__( self.z_index = z_index self.transparent = to_filter(transparent) - def get_width(self) -> Optional[int]: + def get_width(self) -> int | None: if callable(self.width): return self.width() return self.width - def get_height(self) -> Optional[int]: + def get_height(self) -> int | None: if callable(self.height): return self.height() return self.height @@ -1131,15 +1133,15 @@ class WindowRenderInfo: def __init__( self, - window: "Window", + window: Window, ui_content: UIContent, horizontal_scroll: int, vertical_scroll: int, window_width: int, window_height: int, - configured_scroll_offsets: "ScrollOffsets", - visible_line_to_row_col: Dict[int, Tuple[int, int]], - rowcol_to_yx: Dict[Tuple[int, int], Tuple[int, int]], + configured_scroll_offsets: ScrollOffsets, + visible_line_to_row_col: dict[int, tuple[int, int]], + rowcol_to_yx: dict[tuple[int, int], tuple[int, int]], x_offset: int, y_offset: int, wrap_lines: bool, @@ -1160,7 +1162,7 @@ def __init__( self._y_offset = y_offset @property - def visible_line_to_input_line(self) -> Dict[int, int]: + def visible_line_to_input_line(self) -> dict[int, int]: return { visible_line: rowcol[0] for visible_line, rowcol in self.visible_line_to_row_col.items() @@ -1183,7 +1185,7 @@ def cursor_position(self) -> Point: return Point(x=x - self._x_offset, y=y - self._y_offset) @property - def applied_scroll_offsets(self) -> "ScrollOffsets": + def applied_scroll_offsets(self) -> ScrollOffsets: """ Return a :class:`.ScrollOffsets` instance that indicates the actual offset. This can be less than or equal to what's configured. E.g, when @@ -1211,7 +1213,7 @@ def applied_scroll_offsets(self) -> "ScrollOffsets": ) @property - def displayed_lines(self) -> List[int]: + def displayed_lines(self) -> list[int]: """ List of all the visible rows. (Line numbers of the input buffer.) The last line may not be entirely visible. @@ -1219,13 +1221,13 @@ def displayed_lines(self) -> List[int]: return sorted(row for row, col in self.visible_line_to_row_col.values()) @property - def input_line_to_visible_line(self) -> Dict[int, int]: + def input_line_to_visible_line(self) -> dict[int, int]: """ Return the dictionary mapping the line numbers of the input buffer to the lines of the screen. When a line spans several rows at the screen, the first row appears in the dictionary. """ - result: Dict[int, int] = {} + result: dict[int, int] = {} for k, v in self.visible_line_to_input_line.items(): if v in result: result[v] = min(result[v], k) @@ -1331,10 +1333,10 @@ class ScrollOffsets: def __init__( self, - top: Union[int, Callable[[], int]] = 0, - bottom: Union[int, Callable[[], int]] = 0, - left: Union[int, Callable[[], int]] = 0, - right: Union[int, Callable[[], int]] = 0, + top: int | Callable[[], int] = 0, + bottom: int | Callable[[], int] = 0, + left: int | Callable[[], int] = 0, + right: int | Callable[[], int] = 0, ) -> None: self._top = top self._bottom = bottom @@ -1457,31 +1459,31 @@ class Window(Container): def __init__( self, - content: Optional[UIControl] = None, + content: UIControl | None = None, width: AnyDimension = None, height: AnyDimension = None, - z_index: Optional[int] = None, + z_index: int | None = None, dont_extend_width: FilterOrBool = False, dont_extend_height: FilterOrBool = False, ignore_content_width: FilterOrBool = False, ignore_content_height: FilterOrBool = False, - left_margins: Optional[Sequence[Margin]] = None, - right_margins: Optional[Sequence[Margin]] = None, - scroll_offsets: Optional[ScrollOffsets] = None, + left_margins: Sequence[Margin] | None = None, + right_margins: Sequence[Margin] | None = None, + scroll_offsets: ScrollOffsets | None = None, allow_scroll_beyond_bottom: FilterOrBool = False, wrap_lines: FilterOrBool = False, - get_vertical_scroll: Optional[Callable[["Window"], int]] = None, - get_horizontal_scroll: Optional[Callable[["Window"], int]] = None, + get_vertical_scroll: Callable[[Window], int] | None = None, + get_horizontal_scroll: Callable[[Window], int] | None = None, always_hide_cursor: FilterOrBool = False, cursorline: FilterOrBool = False, cursorcolumn: FilterOrBool = False, - colorcolumns: Union[ - None, List[ColorColumn], Callable[[], List[ColorColumn]] - ] = None, - align: Union[WindowAlign, Callable[[], WindowAlign]] = WindowAlign.LEFT, - style: Union[str, Callable[[], str]] = "", - char: Union[None, str, Callable[[], str]] = None, - get_line_prefix: Optional[GetLinePrefixCallable] = None, + colorcolumns: ( + None | list[ColorColumn] | Callable[[], list[ColorColumn]] + ) = None, + align: WindowAlign | Callable[[], WindowAlign] = WindowAlign.LEFT, + style: str | Callable[[], str] = "", + char: None | str | Callable[[], str] = None, + get_line_prefix: GetLinePrefixCallable | None = None, ) -> None: self.allow_scroll_beyond_bottom = to_filter(allow_scroll_beyond_bottom) self.always_hide_cursor = to_filter(always_hide_cursor) @@ -1511,9 +1513,9 @@ def __init__( # Cache for the screens generated by the margin. self._ui_content_cache: SimpleCache[ - Tuple[int, int, int], UIContent + tuple[int, int, int], UIContent ] = SimpleCache(maxsize=8) - self._margin_width_cache: SimpleCache[Tuple[Margin, int], int] = SimpleCache( + self._margin_width_cache: SimpleCache[tuple[Margin, int], int] = SimpleCache( maxsize=1 ) @@ -1536,7 +1538,7 @@ def reset(self) -> None: #: Keep render information (mappings between buffer input and render #: output.) - self.render_info: Optional[WindowRenderInfo] = None + self.render_info: WindowRenderInfo | None = None def _get_margin_width(self, margin: Margin) -> int: """ @@ -1567,7 +1569,7 @@ def preferred_width(self, max_available_width: int) -> Dimension: Calculate the preferred width for this window. """ - def preferred_content_width() -> Optional[int]: + def preferred_content_width() -> int | None: """Content width: is only calculated if no exact width for the window was given.""" if self.ignore_content_width(): @@ -1598,7 +1600,7 @@ def preferred_height(self, width: int, max_available_height: int) -> Dimension: Calculate the preferred height for this window. """ - def preferred_content_height() -> Optional[int]: + def preferred_content_height() -> int | None: """Content height: is only calculated if no exact height for the window was given.""" if self.ignore_content_height(): @@ -1622,8 +1624,8 @@ def preferred_content_height() -> Optional[int]: @staticmethod def _merge_dimensions( - dimension: Optional[Dimension], - get_preferred: Callable[[], Optional[int]], + dimension: Dimension | None, + get_preferred: Callable[[], int | None], dont_extend: bool = False, ) -> Dimension: """ @@ -1635,7 +1637,7 @@ def _merge_dimensions( # When a preferred dimension was explicitly given to the Window, # ignore the UIControl. - preferred: Optional[int] + preferred: int | None if dimension.preferred_specified: preferred = dimension.preferred @@ -1655,8 +1657,8 @@ def _merge_dimensions( # When a `dont_extend` flag has been given, use the preferred dimension # also as the max dimension. - max_: Optional[int] - min_: Optional[int] + max_: int | None + min_: int | None if dont_extend and preferred is not None: max_ = min(dimension.max, preferred) @@ -1680,7 +1682,7 @@ def get_content() -> UIContent: key = (get_app().render_counter, width, height) return self._ui_content_cache.get(key, get_content) - def _get_digraph_char(self) -> Optional[str]: + def _get_digraph_char(self) -> str | None: "Return `False`, or the Digraph symbol to be used." app = get_app() if app.quoted_insert: @@ -1698,7 +1700,7 @@ def write_to_screen( write_position: WritePosition, parent_style: str, erase_bg: bool, - z_index: Optional[int], + z_index: int | None, ) -> None: """ Write window to screen. This renders the user control, the margins and @@ -1823,7 +1825,7 @@ def _write_to_screen_at_index( self.render_info = render_info # Set mouse handlers. - def mouse_handler(mouse_event: MouseEvent) -> "NotImplementedOrNone": + def mouse_handler(mouse_event: MouseEvent) -> NotImplementedOrNone: """ Wrapper around the mouse_handler of the `UIControl` that turns screen coordinates into line coordinates. @@ -1946,8 +1948,8 @@ def _copy_body( always_hide_cursor: bool = False, has_focus: bool = False, align: WindowAlign = WindowAlign.LEFT, - get_line_prefix: Optional[Callable[[int, int], AnyFormattedText]] = None, - ) -> Tuple[Dict[int, Tuple[int, int]], Dict[Tuple[int, int], Tuple[int, int]]]: + get_line_prefix: Callable[[int, int], AnyFormattedText] | None = None, + ) -> tuple[dict[int, tuple[int, int]], dict[tuple[int, int], tuple[int, int]]]: """ Copy the UIContent into the output screen. Return (visible_line_to_row_col, rowcol_to_yx) tuple. @@ -1963,10 +1965,10 @@ def _copy_body( # Map visible line number to (row, col) of input. # 'col' will always be zero if line wrapping is off. - visible_line_to_row_col: Dict[int, Tuple[int, int]] = {} + visible_line_to_row_col: dict[int, tuple[int, int]] = {} # Maps (row, col) from the input to (y, x) screen coordinates. - rowcol_to_yx: Dict[Tuple[int, int], Tuple[int, int]] = {} + rowcol_to_yx: dict[tuple[int, int], tuple[int, int]] = {} def copy_line( line: StyleAndTextTuples, @@ -1974,7 +1976,7 @@ def copy_line( x: int, y: int, is_input: bool = False, - ) -> Tuple[int, int]: + ) -> tuple[int, int]: """ Copy over a single line to the output screen. This can wrap over multiple lines in the output. It will call the prefix (prompt) @@ -2180,7 +2182,7 @@ def _fill_bg( Erase/fill the background. (Useful for floats and when a `char` has been given.) """ - char: Optional[str] + char: str | None if callable(self.char): char = self.char() else: @@ -2550,7 +2552,7 @@ def do_scroll( ), ) - def _mouse_handler(self, mouse_event: MouseEvent) -> "NotImplementedOrNone": + def _mouse_handler(self, mouse_event: MouseEvent) -> NotImplementedOrNone: """ Mouse handler. Called when the UI control doesn't handle this particular event. @@ -2597,10 +2599,10 @@ def _scroll_up(self) -> None: self.vertical_scroll -= 1 - def get_key_bindings(self) -> Optional[KeyBindingsBase]: + def get_key_bindings(self) -> KeyBindingsBase | None: return self.content.get_key_bindings() - def get_children(self) -> List[Container]: + def get_children(self) -> list[Container]: return [] @@ -2643,14 +2645,14 @@ def write_to_screen( write_position: WritePosition, parent_style: str, erase_bg: bool, - z_index: Optional[int], + z_index: int | None, ) -> None: if self.filter(): return self.content.write_to_screen( screen, mouse_handlers, write_position, parent_style, erase_bg, z_index ) - def get_children(self) -> List[Container]: + def get_children(self) -> list[Container]: return [self.content] @@ -2691,7 +2693,7 @@ def write_to_screen( write_position: WritePosition, parent_style: str, erase_bg: bool, - z_index: Optional[int], + z_index: int | None, ) -> None: self._get_container().write_to_screen( screen, mouse_handlers, write_position, parent_style, erase_bg, z_index @@ -2700,12 +2702,12 @@ def write_to_screen( def is_modal(self) -> bool: return False - def get_key_bindings(self) -> Optional[KeyBindingsBase]: + def get_key_bindings(self) -> KeyBindingsBase | None: # Key bindings will be collected when `layout.walk()` finds the child # container. return None - def get_children(self) -> List[Container]: + def get_children(self) -> list[Container]: # Here we have to return the current active container itself, not its # children. Otherwise, we run into issues where `layout.walk()` will # never see an object of type `Window` if this contains a window. We @@ -2737,7 +2739,7 @@ def to_window(container: AnyContainer) -> Window: raise ValueError(f"Not a Window object: {container!r}.") -def is_container(value: object) -> "TypeGuard[AnyContainer]": +def is_container(value: object) -> TypeGuard[AnyContainer]: """ Checks whether the given value is a container object (for use in assert statements). diff --git a/src/prompt_toolkit/layout/controls.py b/src/prompt_toolkit/layout/controls.py index d79e813347..5e2e3b0db6 100644 --- a/src/prompt_toolkit/layout/controls.py +++ b/src/prompt_toolkit/layout/controls.py @@ -1,6 +1,8 @@ """ User interface Controls for the layout. """ +from __future__ import annotations + import time from abc import ABCMeta, abstractmethod from typing import ( @@ -76,7 +78,7 @@ def reset(self) -> None: # Default reset. (Doesn't have to be implemented.) pass - def preferred_width(self, max_available_width: int) -> Optional[int]: + def preferred_width(self, max_available_width: int) -> int | None: return None def preferred_height( @@ -84,8 +86,8 @@ def preferred_height( width: int, max_available_height: int, wrap_lines: bool, - get_line_prefix: Optional[GetLinePrefixCallable], - ) -> Optional[int]: + get_line_prefix: GetLinePrefixCallable | None, + ) -> int | None: return None def is_focusable(self) -> bool: @@ -95,14 +97,14 @@ def is_focusable(self) -> bool: return False @abstractmethod - def create_content(self, width: int, height: int) -> "UIContent": + def create_content(self, width: int, height: int) -> UIContent: """ Generate the content for this user control. Returns a :class:`.UIContent` instance. """ - def mouse_handler(self, mouse_event: MouseEvent) -> "NotImplementedOrNone": + def mouse_handler(self, mouse_event: MouseEvent) -> NotImplementedOrNone: """ Handle mouse events. @@ -126,7 +128,7 @@ def move_cursor_up(self) -> None: Request to move the cursor up. """ - def get_key_bindings(self) -> Optional["KeyBindingsBase"]: + def get_key_bindings(self) -> KeyBindingsBase | None: """ The key bindings that are specific for this user control. @@ -134,7 +136,7 @@ def get_key_bindings(self) -> Optional["KeyBindingsBase"]: specified, or `None` otherwise. """ - def get_invalidate_events(self) -> Iterable["Event[object]"]: + def get_invalidate_events(self) -> Iterable[Event[object]]: """ Return a list of `Event` objects. This can be a generator. (The application collects all these events, in order to bind redraw @@ -160,8 +162,8 @@ def __init__( self, get_line: Callable[[int], StyleAndTextTuples] = (lambda i: []), line_count: int = 0, - cursor_position: Optional[Point] = None, - menu_position: Optional[Point] = None, + cursor_position: Point | None = None, + menu_position: Point | None = None, show_cursor: bool = True, ): self.get_line = get_line @@ -171,7 +173,7 @@ def __init__( self.show_cursor = show_cursor # Cache for line heights. Maps cache key -> height - self._line_heights_cache: Dict[Hashable, int] = {} + self._line_heights_cache: dict[Hashable, int] = {} def __getitem__(self, lineno: int) -> StyleAndTextTuples: "Make it iterable (iterate line by line)." @@ -184,8 +186,8 @@ def get_height_for_line( self, lineno: int, width: int, - get_line_prefix: Optional[GetLinePrefixCallable], - slice_stop: Optional[int] = None, + get_line_prefix: GetLinePrefixCallable | None, + slice_stop: int | None = None, ) -> int: """ Return the height that a given line would need if it is rendered in a @@ -302,10 +304,10 @@ def __init__( text: AnyFormattedText = "", style: str = "", focusable: FilterOrBool = False, - key_bindings: Optional["KeyBindingsBase"] = None, + key_bindings: KeyBindingsBase | None = None, show_cursor: bool = True, modal: bool = False, - get_cursor_position: Optional[Callable[[], Optional[Point]]] = None, + get_cursor_position: Callable[[], Point | None] | None = None, ) -> None: self.text = text # No type check on 'text'. This is done dynamically. self.style = style @@ -325,7 +327,7 @@ def __init__( # Only cache one fragment list. We don't need the previous item. # Render info for the mouse support. - self._fragments: Optional[StyleAndTextTuples] = None + self._fragments: StyleAndTextTuples | None = None def reset(self) -> None: self._fragments = None @@ -360,8 +362,8 @@ def preferred_height( width: int, max_available_height: int, wrap_lines: bool, - get_line_prefix: Optional[GetLinePrefixCallable], - ) -> Optional[int]: + get_line_prefix: GetLinePrefixCallable | None, + ) -> int | None: """ Return the preferred height for this control. """ @@ -376,7 +378,7 @@ def preferred_height( else: return content.line_count - def create_content(self, width: int, height: Optional[int]) -> UIContent: + def create_content(self, width: int, height: int | None) -> UIContent: # Get fragments fragments_with_mouse_handlers = self._get_formatted_text_cached() fragment_lines_with_mouse_handlers = list( @@ -384,7 +386,7 @@ def create_content(self, width: int, height: Optional[int]) -> UIContent: ) # Strip mouse handlers from fragments. - fragment_lines: List[StyleAndTextTuples] = [ + fragment_lines: list[StyleAndTextTuples] = [ [(item[0], item[1]) for item in line] for line in fragment_lines_with_mouse_handlers ] @@ -397,7 +399,7 @@ def create_content(self, width: int, height: Optional[int]) -> UIContent: # cursor position here. def get_cursor_position( fragment: str = "[SetCursorPosition]", - ) -> Optional[Point]: + ) -> Point | None: for y, line in enumerate(fragment_lines): x = 0 for style_str, text, *_ in line: @@ -407,7 +409,7 @@ def get_cursor_position( return None # If there is a `[SetMenuPosition]`, set the menu over here. - def get_menu_position() -> Optional[Point]: + def get_menu_position() -> Point | None: return get_cursor_position("[SetMenuPosition]") cursor_position = (self.get_cursor_position or get_cursor_position)() @@ -426,7 +428,7 @@ def get_content() -> UIContent: return self._content_cache.get(key, get_content) - def mouse_handler(self, mouse_event: MouseEvent) -> "NotImplementedOrNone": + def mouse_handler(self, mouse_event: MouseEvent) -> NotImplementedOrNone: """ Handle mouse events. @@ -468,7 +470,7 @@ def mouse_handler(self, mouse_event: MouseEvent) -> "NotImplementedOrNone": def is_modal(self) -> bool: return self.modal - def get_key_bindings(self) -> Optional["KeyBindingsBase"]: + def get_key_bindings(self) -> KeyBindingsBase | None: return self.key_bindings @@ -521,18 +523,18 @@ class BufferControl(UIControl): def __init__( self, - buffer: Optional[Buffer] = None, - input_processors: Optional[List[Processor]] = None, + buffer: Buffer | None = None, + input_processors: list[Processor] | None = None, include_default_input_processors: bool = True, - lexer: Optional[Lexer] = None, + lexer: Lexer | None = None, preview_search: FilterOrBool = False, focusable: FilterOrBool = True, - search_buffer_control: Union[ - None, "SearchBufferControl", Callable[[], "SearchBufferControl"] - ] = None, - menu_position: Optional[Callable[[], Optional[int]]] = None, + search_buffer_control: ( + None | SearchBufferControl | Callable[[], SearchBufferControl] + ) = None, + menu_position: Callable[[], int | None] | None = None, focus_on_click: FilterOrBool = False, - key_bindings: Optional["KeyBindingsBase"] = None, + key_bindings: KeyBindingsBase | None = None, ): self.input_processors = input_processors self.include_default_input_processors = include_default_input_processors @@ -562,15 +564,15 @@ def __init__( Hashable, Callable[[int], StyleAndTextTuples] ] = SimpleCache(maxsize=8) - self._last_click_timestamp: Optional[float] = None - self._last_get_processed_line: Optional[Callable[[int], _ProcessedLine]] = None + self._last_click_timestamp: float | None = None + self._last_get_processed_line: Callable[[int], _ProcessedLine] | None = None def __repr__(self) -> str: return f"<{self.__class__.__name__} buffer={self.buffer!r} at {id(self)!r}>" @property - def search_buffer_control(self) -> Optional["SearchBufferControl"]: - result: Optional[SearchBufferControl] + def search_buffer_control(self) -> SearchBufferControl | None: + result: SearchBufferControl | None if callable(self._search_buffer_control): result = self._search_buffer_control() @@ -581,7 +583,7 @@ def search_buffer_control(self) -> Optional["SearchBufferControl"]: return result @property - def search_buffer(self) -> Optional[Buffer]: + def search_buffer(self) -> Buffer | None: control = self.search_buffer_control if control is not None: return control.buffer @@ -604,7 +606,7 @@ def search_state(self) -> SearchState: def is_focusable(self) -> bool: return self.focusable() - def preferred_width(self, max_available_width: int) -> Optional[int]: + def preferred_width(self, max_available_width: int) -> int | None: """ This should return the preferred width. @@ -622,8 +624,8 @@ def preferred_height( width: int, max_available_height: int, wrap_lines: bool, - get_line_prefix: Optional[GetLinePrefixCallable], - ) -> Optional[int]: + get_line_prefix: GetLinePrefixCallable | None, + ) -> int | None: # Calculate the content height, if it was drawn on a screen with the # given width. height = 0 @@ -700,7 +702,7 @@ def source_to_display(i: int) -> int: def create_func() -> Callable[[int], _ProcessedLine]: get_line = self._get_formatted_text_for_line_func(document) - cache: Dict[int, _ProcessedLine] = {} + cache: dict[int, _ProcessedLine] = {} def get_processed_line(i: int) -> _ProcessedLine: try: @@ -819,7 +821,7 @@ def get_line(i: int) -> StyleAndTextTuples: return content - def mouse_handler(self, mouse_event: MouseEvent) -> "NotImplementedOrNone": + def mouse_handler(self, mouse_event: MouseEvent) -> NotImplementedOrNone: """ Mouse handler for this control. """ @@ -906,13 +908,13 @@ def move_cursor_up(self) -> None: b = self.buffer b.cursor_position += b.document.get_cursor_up_position() - def get_key_bindings(self) -> Optional["KeyBindingsBase"]: + def get_key_bindings(self) -> KeyBindingsBase | None: """ When additional key bindings are given. Return these. """ return self.key_bindings - def get_invalidate_events(self) -> Iterable["Event[object]"]: + def get_invalidate_events(self) -> Iterable[Event[object]]: """ Return the Window invalidate events. """ @@ -934,11 +936,11 @@ class SearchBufferControl(BufferControl): def __init__( self, - buffer: Optional[Buffer] = None, - input_processors: Optional[List[Processor]] = None, - lexer: Optional[Lexer] = None, + buffer: Buffer | None = None, + input_processors: list[Processor] | None = None, + lexer: Lexer | None = None, focus_on_click: FilterOrBool = False, - key_bindings: Optional["KeyBindingsBase"] = None, + key_bindings: KeyBindingsBase | None = None, ignore_case: FilterOrBool = False, ): super().__init__( diff --git a/src/prompt_toolkit/layout/dimension.py b/src/prompt_toolkit/layout/dimension.py index 04c21637cb..f3f2954bb4 100644 --- a/src/prompt_toolkit/layout/dimension.py +++ b/src/prompt_toolkit/layout/dimension.py @@ -2,6 +2,8 @@ Layout dimensions are used to give the minimum, maximum and preferred dimensions for containers and controls. """ +from __future__ import annotations + from typing import TYPE_CHECKING, Any, Callable, List, Optional, Union __all__ = [ @@ -38,10 +40,10 @@ class Dimension: def __init__( self, - min: Optional[int] = None, - max: Optional[int] = None, - weight: Optional[int] = None, - preferred: Optional[int] = None, + min: int | None = None, + max: int | None = None, + weight: int | None = None, + preferred: int | None = None, ) -> None: if weight is not None: assert weight >= 0 # Also cannot be a float. @@ -81,7 +83,7 @@ def __init__( self.preferred = self.max @classmethod - def exact(cls, amount: int) -> "Dimension": + def exact(cls, amount: int) -> Dimension: """ Return a :class:`.Dimension` with an exact size. (min, max and preferred set to ``amount``). @@ -89,7 +91,7 @@ def exact(cls, amount: int) -> "Dimension": return cls(min=amount, max=amount, preferred=amount) @classmethod - def zero(cls) -> "Dimension": + def zero(cls) -> Dimension: """ Create a dimension that represents a zero size. (Used for 'invisible' controls.) @@ -114,7 +116,7 @@ def __repr__(self) -> str: return "Dimension(%s)" % ", ".join(fields) -def sum_layout_dimensions(dimensions: List[Dimension]) -> Dimension: +def sum_layout_dimensions(dimensions: list[Dimension]) -> Dimension: """ Sum a list of :class:`.Dimension` instances. """ @@ -125,7 +127,7 @@ def sum_layout_dimensions(dimensions: List[Dimension]) -> Dimension: return Dimension(min=min, max=max, preferred=preferred) -def max_layout_dimensions(dimensions: List[Dimension]) -> Dimension: +def max_layout_dimensions(dimensions: list[Dimension]) -> Dimension: """ Take the maximum of a list of :class:`.Dimension` instances. Used when we have a HSplit/VSplit, and we want to get the best width/height.) @@ -196,7 +198,7 @@ def to_dimension(value: AnyDimension) -> Dimension: raise ValueError("Not an integer or Dimension object.") -def is_dimension(value: object) -> "TypeGuard[AnyDimension]": +def is_dimension(value: object) -> TypeGuard[AnyDimension]: """ Test whether the given value could be a valid dimension. (For usage in an assertion. It's not guaranteed in case of a callable.) diff --git a/src/prompt_toolkit/layout/dummy.py b/src/prompt_toolkit/layout/dummy.py index dcd54e9fc9..139f311579 100644 --- a/src/prompt_toolkit/layout/dummy.py +++ b/src/prompt_toolkit/layout/dummy.py @@ -2,6 +2,8 @@ Dummy layout. Used when somebody creates an `Application` without specifying a `Layout`. """ +from __future__ import annotations + from prompt_toolkit.formatted_text import HTML from prompt_toolkit.key_binding import KeyBindings from prompt_toolkit.key_binding.key_processor import KeyPressEvent diff --git a/src/prompt_toolkit/layout/layout.py b/src/prompt_toolkit/layout/layout.py index 57eaa59bee..075288ed62 100644 --- a/src/prompt_toolkit/layout/layout.py +++ b/src/prompt_toolkit/layout/layout.py @@ -1,6 +1,8 @@ """ Wrapper for the layout. """ +from __future__ import annotations + from typing import Dict, Generator, Iterable, List, Optional, Union from prompt_toolkit.buffer import Buffer @@ -37,10 +39,10 @@ class Layout: def __init__( self, container: AnyContainer, - focused_element: Optional[FocusableElement] = None, + focused_element: FocusableElement | None = None, ) -> None: self.container = to_container(container) - self._stack: List[Window] = [] + self._stack: list[Window] = [] # Map search BufferControl back to the original BufferControl. # This is used to keep track of when exactly we are searching, and for @@ -48,12 +50,12 @@ def __init__( # When a link exists in this dictionary, that means the search is # currently active. # Map: search_buffer_control -> original buffer control. - self.search_links: Dict[SearchBufferControl, BufferControl] = {} + self.search_links: dict[SearchBufferControl, BufferControl] = {} # Mapping that maps the children in the layout to their parent. # This relationship is calculated dynamically, each time when the UI # is rendered. (UI elements have only references to their children.) - self._child_to_parent: Dict[Container, Container] = {} + self._child_to_parent: dict[Container, Container] = {} if focused_element is None: try: @@ -66,7 +68,7 @@ def __init__( self.focus(focused_element) # List of visible windows. - self.visible_windows: List[Window] = [] # List of `Window` objects. + self.visible_windows: list[Window] = [] # List of `Window` objects. def __repr__(self) -> str: return f"Layout({self.container!r}, current_window={self.current_window!r})" @@ -222,7 +224,7 @@ def is_searching(self) -> bool: return self.current_control in self.search_links @property - def search_target_buffer_control(self) -> Optional[BufferControl]: + def search_target_buffer_control(self) -> BufferControl | None: """ Return the :class:`.BufferControl` in which we are searching or `None`. """ @@ -244,7 +246,7 @@ def get_focusable_windows(self) -> Iterable[Window]: if isinstance(w, Window) and w.content.is_focusable(): yield w - def get_visible_focusable_windows(self) -> List[Window]: + def get_visible_focusable_windows(self) -> list[Window]: """ Return a list of :class:`.Window` objects that are focusable. """ @@ -254,7 +256,7 @@ def get_visible_focusable_windows(self) -> List[Window]: return [w for w in self.get_focusable_windows() if w in visible_windows] @property - def current_buffer(self) -> Optional[Buffer]: + def current_buffer(self) -> Buffer | None: """ The currently focused :class:`~.Buffer` or `None`. """ @@ -263,7 +265,7 @@ def current_buffer(self) -> Optional[Buffer]: return ui_control.buffer return None - def get_buffer_by_name(self, buffer_name: str) -> Optional[Buffer]: + def get_buffer_by_name(self, buffer_name: str) -> Buffer | None: """ Look in the layout for a buffer with the given name. Return `None` when nothing was found. @@ -376,7 +378,7 @@ def reset(self) -> None: self.container.reset() - def get_parent(self, container: Container) -> Optional[Container]: + def get_parent(self, container: Container) -> Container | None: """ Return the parent container for the given container, or ``None``, if it wasn't found. diff --git a/src/prompt_toolkit/layout/margins.py b/src/prompt_toolkit/layout/margins.py index a3808cbc77..88c3ff3d2d 100644 --- a/src/prompt_toolkit/layout/margins.py +++ b/src/prompt_toolkit/layout/margins.py @@ -1,6 +1,8 @@ """ Margin implementations for a :class:`~prompt_toolkit.layout.containers.Window`. """ +from __future__ import annotations + from abc import ABCMeta, abstractmethod from typing import TYPE_CHECKING, Callable, Optional @@ -44,7 +46,7 @@ def get_width(self, get_ui_content: Callable[[], UIContent]) -> int: @abstractmethod def create_margin( - self, window_render_info: "WindowRenderInfo", width: int, height: int + self, window_render_info: WindowRenderInfo, width: int, height: int ) -> StyleAndTextTuples: """ Creates a margin. @@ -84,7 +86,7 @@ def get_width(self, get_ui_content: Callable[[], UIContent]) -> int: return max(3, len("%s" % line_count) + 1) def create_margin( - self, window_render_info: "WindowRenderInfo", width: int, height: int + self, window_render_info: WindowRenderInfo, width: int, height: int ) -> StyleAndTextTuples: relative = self.relative() @@ -147,7 +149,7 @@ def get_width(self, get_ui_content: Callable[[], UIContent]) -> int: return 0 def create_margin( - self, window_render_info: "WindowRenderInfo", width: int, height: int + self, window_render_info: WindowRenderInfo, width: int, height: int ) -> StyleAndTextTuples: if width and self.filter(): return self.margin.create_margin(window_render_info, width, height) @@ -176,7 +178,7 @@ def get_width(self, get_ui_content: Callable[[], UIContent]) -> int: return 1 def create_margin( - self, window_render_info: "WindowRenderInfo", width: int, height: int + self, window_render_info: WindowRenderInfo, width: int, height: int ) -> StyleAndTextTuples: content_height = window_render_info.content_height window_height = window_render_info.window_height @@ -266,9 +268,8 @@ class PromptMargin(Margin): def __init__( self, get_prompt: Callable[[], StyleAndTextTuples], - get_continuation: Optional[ - Callable[[int, int, bool], StyleAndTextTuples] - ] = None, + get_continuation: None + | (Callable[[int, int, bool], StyleAndTextTuples]) = None, ) -> None: self.get_prompt = get_prompt self.get_continuation = get_continuation @@ -280,7 +281,7 @@ def get_width(self, get_ui_content: Callable[[], UIContent]) -> int: return get_cwidth(text) def create_margin( - self, window_render_info: "WindowRenderInfo", width: int, height: int + self, window_render_info: WindowRenderInfo, width: int, height: int ) -> StyleAndTextTuples: get_continuation = self.get_continuation result: StyleAndTextTuples = [] diff --git a/src/prompt_toolkit/layout/menus.py b/src/prompt_toolkit/layout/menus.py index 519435cd73..87f169f51c 100644 --- a/src/prompt_toolkit/layout/menus.py +++ b/src/prompt_toolkit/layout/menus.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import math from itertools import zip_longest from typing import ( @@ -73,7 +75,7 @@ class CompletionsMenuControl(UIControl): def has_focus(self) -> bool: return False - def preferred_width(self, max_available_width: int) -> Optional[int]: + def preferred_width(self, max_available_width: int) -> int | None: complete_state = get_app().current_buffer.complete_state if complete_state: menu_width = self._get_menu_width(500, complete_state) @@ -88,8 +90,8 @@ def preferred_height( width: int, max_available_height: int, wrap_lines: bool, - get_line_prefix: Optional[GetLinePrefixCallable], - ) -> Optional[int]: + get_line_prefix: GetLinePrefixCallable | None, + ) -> int | None: complete_state = get_app().current_buffer.complete_state if complete_state: return len(complete_state.completions) @@ -188,7 +190,7 @@ def _get_menu_item_meta_fragments( style=style_str, ) - def mouse_handler(self, mouse_event: MouseEvent) -> "NotImplementedOrNone": + def mouse_handler(self, mouse_event: MouseEvent) -> NotImplementedOrNone: """ Handle mouse events: clicking and scrolling. """ @@ -242,7 +244,7 @@ def _get_menu_item_fragments( def _trim_formatted_text( formatted_text: StyleAndTextTuples, max_width: int -) -> Tuple[StyleAndTextTuples, int]: +) -> tuple[StyleAndTextTuples, int]: """ Trim the text to `max_width`, append dots when the text is too long. Returns (text, width) tuple. @@ -276,8 +278,8 @@ class CompletionsMenu(ConditionalContainer): # visible at the point where we draw this menu. def __init__( self, - max_height: Optional[int] = None, - scroll_offset: Union[int, Callable[[], int]] = 0, + max_height: int | None = None, + scroll_offset: int | Callable[[], int] = 0, extra_filter: FilterOrBool = True, display_arrows: FilterOrBool = False, z_index: int = 10**8, @@ -340,15 +342,15 @@ def __init__(self, min_rows: int = 3, suggested_max_column_width: int = 30) -> N # (map `completion_state` to `(completion_count, width)`. We remember # the count, because a completer can add new completions to the # `CompletionState` while loading.) - self._column_width_for_completion_state: "WeakKeyDictionary[CompletionState, Tuple[int, int]]" = ( - WeakKeyDictionary() - ) + self._column_width_for_completion_state: WeakKeyDictionary[ + CompletionState, Tuple[int, int] + ] = WeakKeyDictionary() # Info of last rendering. self._rendered_rows = 0 self._rendered_columns = 0 self._total_columns = 0 - self._render_pos_to_completion: Dict[Tuple[int, int], Completion] = {} + self._render_pos_to_completion: dict[tuple[int, int], Completion] = {} self._render_left_arrow = False self._render_right_arrow = False self._render_width = 0 @@ -359,7 +361,7 @@ def reset(self) -> None: def has_focus(self) -> bool: return False - def preferred_width(self, max_available_width: int) -> Optional[int]: + def preferred_width(self, max_available_width: int) -> int | None: """ Preferred width: prefer to use at least min_rows, but otherwise as much as possible horizontally. @@ -389,8 +391,8 @@ def preferred_height( width: int, max_available_height: int, wrap_lines: bool, - get_line_prefix: Optional[GetLinePrefixCallable], - ) -> Optional[int]: + get_line_prefix: GetLinePrefixCallable | None, + ) -> int | None: """ Preferred height: as much as needed in order to display all the completions. """ @@ -417,8 +419,8 @@ def create_content(self, width: int, height: int) -> UIContent: _T = TypeVar("_T") def grouper( - n: int, iterable: Iterable[_T], fillvalue: Optional[_T] = None - ) -> Iterable[Sequence[Optional[_T]]]: + n: int, iterable: Iterable[_T], fillvalue: _T | None = None + ) -> Iterable[Sequence[_T | None]]: "grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx" args = [iter(iterable)] * n return zip_longest(fillvalue=fillvalue, *args) @@ -541,7 +543,7 @@ def _get_column_width(self, completion_state: CompletionState) -> int: ) return result - def mouse_handler(self, mouse_event: MouseEvent) -> "NotImplementedOrNone": + def mouse_handler(self, mouse_event: MouseEvent) -> NotImplementedOrNone: """ Handle scroll and click events. """ @@ -585,7 +587,7 @@ def scroll_right() -> None: return None - def get_key_bindings(self) -> "KeyBindings": + def get_key_bindings(self) -> KeyBindings: """ Expose key bindings that handle the left/right arrow keys when the menu is displayed. @@ -697,7 +699,7 @@ class _SelectedCompletionMetaControl(UIControl): Control that shows the meta information of the selected completion. """ - def preferred_width(self, max_available_width: int) -> Optional[int]: + def preferred_width(self, max_available_width: int) -> int | None: """ Report the width of the longest meta text as the preferred width of this control. @@ -729,8 +731,8 @@ def preferred_height( width: int, max_available_height: int, wrap_lines: bool, - get_line_prefix: Optional[GetLinePrefixCallable], - ) -> Optional[int]: + get_line_prefix: GetLinePrefixCallable | None, + ) -> int | None: return 1 def create_content(self, width: int, height: int) -> UIContent: diff --git a/src/prompt_toolkit/layout/mouse_handlers.py b/src/prompt_toolkit/layout/mouse_handlers.py index 256231793a..2faf99e980 100644 --- a/src/prompt_toolkit/layout/mouse_handlers.py +++ b/src/prompt_toolkit/layout/mouse_handlers.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from collections import defaultdict from typing import TYPE_CHECKING, Callable, DefaultDict @@ -21,7 +23,7 @@ class MouseHandlers: """ def __init__(self) -> None: - def dummy_callback(mouse_event: MouseEvent) -> "NotImplementedOrNone": + def dummy_callback(mouse_event: MouseEvent) -> NotImplementedOrNone: """ :param mouse_event: `MouseEvent` instance. """ @@ -42,7 +44,7 @@ def set_mouse_handler_for_range( x_max: int, y_min: int, y_max: int, - handler: Callable[[MouseEvent], "NotImplementedOrNone"], + handler: Callable[[MouseEvent], NotImplementedOrNone], ) -> None: """ Set mouse handler for a region. diff --git a/src/prompt_toolkit/layout/processors.py b/src/prompt_toolkit/layout/processors.py index 6f4a935b81..30b1f73969 100644 --- a/src/prompt_toolkit/layout/processors.py +++ b/src/prompt_toolkit/layout/processors.py @@ -5,6 +5,8 @@ They can insert fragments before or after, or highlight fragments by replacing the fragment types. """ +from __future__ import annotations + import re from abc import ABCMeta, abstractmethod from typing import ( @@ -70,8 +72,8 @@ class Processor(metaclass=ABCMeta): @abstractmethod def apply_transformation( - self, transformation_input: "TransformationInput" - ) -> "Transformation": + self, transformation_input: TransformationInput + ) -> Transformation: """ Apply transformation. Returns a :class:`.Transformation` instance. @@ -97,7 +99,7 @@ class TransformationInput: def __init__( self, - buffer_control: "BufferControl", + buffer_control: BufferControl, document: Document, lineno: int, source_to_display: SourceToDisplay, @@ -115,8 +117,8 @@ def __init__( def unpack( self, - ) -> Tuple[ - "BufferControl", Document, int, SourceToDisplay, StyleAndTextTuples, int, int + ) -> tuple[ + BufferControl, Document, int, SourceToDisplay, StyleAndTextTuples, int, int ]: return ( self.buffer_control, @@ -147,8 +149,8 @@ class Transformation: def __init__( self, fragments: StyleAndTextTuples, - source_to_display: Optional[SourceToDisplay] = None, - display_to_source: Optional[DisplayToSource] = None, + source_to_display: SourceToDisplay | None = None, + display_to_source: DisplayToSource | None = None, ) -> None: self.fragments = fragments self.source_to_display = source_to_display or (lambda i: i) @@ -178,7 +180,7 @@ class HighlightSearchProcessor(Processor): _classname = "search" _classname_current = "search.current" - def _get_search_text(self, buffer_control: "BufferControl") -> str: + def _get_search_text(self, buffer_control: BufferControl) -> str: """ The text we are searching for. """ @@ -212,7 +214,7 @@ def apply_transformation( flags = re.RegexFlag(0) # Get cursor column. - cursor_column: Optional[int] + cursor_column: int | None if document.cursor_position_row == lineno: cursor_column = source_to_display(document.cursor_position_col) else: @@ -253,7 +255,7 @@ class HighlightIncrementalSearchProcessor(HighlightSearchProcessor): _classname = "incsearch" _classname_current = "incsearch.current" - def _get_search_text(self, buffer_control: "BufferControl") -> str: + def _get_search_text(self, buffer_control: BufferControl) -> str: """ The text we are searching for. """ @@ -352,14 +354,14 @@ def __init__( self.max_cursor_distance = max_cursor_distance self._positions_cache: SimpleCache[ - Hashable, List[Tuple[int, int]] + Hashable, list[tuple[int, int]] ] = SimpleCache(maxsize=8) - def _get_positions_to_highlight(self, document: Document) -> List[Tuple[int, int]]: + def _get_positions_to_highlight(self, document: Document) -> list[tuple[int, int]]: """ Return a list of (row, col) tuples that need to be highlighted. """ - pos: Optional[int] + pos: int | None # Try for the character under the cursor. if document.current_char and document.current_char in self.chars: @@ -498,8 +500,8 @@ def __init__(self, text: AnyFormattedText, style: str = "") -> None: self.style = style def apply_transformation(self, ti: TransformationInput) -> Transformation: - source_to_display: Optional[SourceToDisplay] - display_to_source: Optional[DisplayToSource] + source_to_display: SourceToDisplay | None + display_to_source: DisplayToSource | None if ti.lineno == 0: # Get fragments. @@ -611,7 +613,7 @@ class ShowLeadingWhiteSpaceProcessor(Processor): def __init__( self, - get_char: Optional[Callable[[], str]] = None, + get_char: Callable[[], str] | None = None, style: str = "class:leading-whitespace", ) -> None: def default_get_char() -> str: @@ -649,7 +651,7 @@ class ShowTrailingWhiteSpaceProcessor(Processor): def __init__( self, - get_char: Optional[Callable[[], str]] = None, + get_char: Callable[[], str] | None = None, style: str = "class:training-whitespace", ) -> None: def default_get_char() -> str: @@ -693,9 +695,9 @@ class TabsProcessor(Processor): def __init__( self, - tabstop: Union[int, Callable[[], int]] = 4, - char1: Union[str, Callable[[], str]] = "|", - char2: Union[str, Callable[[], str]] = "\u2508", + tabstop: int | Callable[[], int] = 4, + char1: str | Callable[[], str] = "|", + char2: str | Callable[[], str] = "\u2508", style: str = "class:tab", ) -> None: self.char1 = char1 @@ -771,16 +773,14 @@ class ReverseSearchProcessor(Processor): contains the search buffer, it's not meant for the original input. """ - _excluded_input_processors: List[Type[Processor]] = [ + _excluded_input_processors: list[type[Processor]] = [ HighlightSearchProcessor, HighlightSelectionProcessor, BeforeInput, AfterInput, ] - def _get_main_buffer( - self, buffer_control: "BufferControl" - ) -> Optional["BufferControl"]: + def _get_main_buffer(self, buffer_control: BufferControl) -> BufferControl | None: from prompt_toolkit.layout.controls import BufferControl prev_control = get_app().layout.search_target_buffer_control @@ -792,15 +792,15 @@ def _get_main_buffer( return None def _content( - self, main_control: "BufferControl", ti: TransformationInput - ) -> "UIContent": + self, main_control: BufferControl, ti: TransformationInput + ) -> UIContent: from prompt_toolkit.layout.controls import BufferControl # Emulate the BufferControl through which we are searching. # For this we filter out some of the input processors. excluded_processors = tuple(self._excluded_input_processors) - def filter_processor(item: Processor) -> Optional[Processor]: + def filter_processor(item: Processor) -> Processor | None: """Filter processors from the main control that we want to disable here. This returns either an accepted processor or None.""" # For a `_MergedProcessor`, check each individual processor, recursively. @@ -855,8 +855,8 @@ def apply_transformation(self, ti: TransformationInput) -> Transformation: ti.buffer_control, SearchBufferControl ), "`ReverseSearchProcessor` should be applied to a `SearchBufferControl` only." - source_to_display: Optional[SourceToDisplay] - display_to_source: Optional[DisplayToSource] + source_to_display: SourceToDisplay | None + display_to_source: DisplayToSource | None main_control = self._get_main_buffer(ti.buffer_control) @@ -948,7 +948,7 @@ class DynamicProcessor(Processor): :param get_processor: Callable that returns a :class:`.Processor` instance. """ - def __init__(self, get_processor: Callable[[], Optional[Processor]]) -> None: + def __init__(self, get_processor: Callable[[], Processor | None]) -> None: self.get_processor = get_processor def apply_transformation(self, ti: TransformationInput) -> Transformation: @@ -956,7 +956,7 @@ def apply_transformation(self, ti: TransformationInput) -> Transformation: return processor.apply_transformation(ti) -def merge_processors(processors: List[Processor]) -> Processor: +def merge_processors(processors: list[Processor]) -> Processor: """ Merge multiple `Processor` objects into one. """ @@ -975,7 +975,7 @@ class _MergedProcessor(Processor): API as if it is one `Processor`. """ - def __init__(self, processors: List[Processor]): + def __init__(self, processors: list[Processor]): self.processors = processors def apply_transformation(self, ti: TransformationInput) -> Transformation: diff --git a/src/prompt_toolkit/layout/screen.py b/src/prompt_toolkit/layout/screen.py index 5e011a4123..f4f1fb28c1 100644 --- a/src/prompt_toolkit/layout/screen.py +++ b/src/prompt_toolkit/layout/screen.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from collections import defaultdict from typing import TYPE_CHECKING, Callable, DefaultDict, Dict, List, Optional, Tuple @@ -30,7 +32,7 @@ class Char: # If we end up having one of these special control sequences in the input string, # we should display them as follows: # Usually this happens after a "quoted insert". - display_mappings: Dict[str, str] = { + display_mappings: dict[str, str] = { "\x00": "^@", # Control space "\x01": "^A", "\x02": "^B", @@ -123,10 +125,10 @@ def __init__(self, char: str = " ", style: str = "") -> None: # In theory, `other` can be any type of object, but because of performance # we don't want to do an `isinstance` check every time. We assume "other" # is always a "Char". - def _equal(self, other: "Char") -> bool: + def _equal(self, other: Char) -> bool: return self.char == other.char and self.style == other.style - def _not_equal(self, other: "Char") -> bool: + def _not_equal(self, other: Char) -> bool: # Not equal: We don't do `not char.__eq__` here, because of the # performance of calling yet another function. return self.char != other.char or self.style != other.style @@ -139,7 +141,7 @@ def __repr__(self) -> str: return f"{self.__class__.__name__}({self.char!r}, {self.style!r})" -_CHAR_CACHE: FastDictCache[Tuple[str, str], Char] = FastDictCache( +_CHAR_CACHE: FastDictCache[tuple[str, str], Char] = FastDictCache( Char, size=1000 * 1000 ) Transparent = "[transparent]" @@ -152,7 +154,7 @@ class Screen: def __init__( self, - default_char: Optional[Char] = None, + default_char: Char | None = None, initial_width: int = 0, initial_height: int = 0, ) -> None: @@ -171,8 +173,8 @@ def __init__( ) #: Position of the cursor. - self.cursor_positions: Dict[ - "Window", Point + self.cursor_positions: dict[ + Window, Point ] = {} # Map `Window` objects to `Point` objects. #: Visibility of the cursor. @@ -182,8 +184,8 @@ def __init__( #: (We can't use the cursor position, because we don't want the #: completion menu to change its position when we browse through all the #: completions.) - self.menu_positions: Dict[ - "Window", Point + self.menu_positions: dict[ + Window, Point ] = {} # Map `Window` objects to `Point` objects. #: Currently used width/height of the screen. This will increase when @@ -193,28 +195,28 @@ def __init__( # Windows that have been drawn. (Each `Window` class will add itself to # this list.) - self.visible_windows_to_write_positions: Dict["Window", "WritePosition"] = {} + self.visible_windows_to_write_positions: dict[Window, WritePosition] = {} # List of (z_index, draw_func) - self._draw_float_functions: List[Tuple[int, Callable[[], None]]] = [] + self._draw_float_functions: list[tuple[int, Callable[[], None]]] = [] @property - def visible_windows(self) -> List["Window"]: + def visible_windows(self) -> list[Window]: return list(self.visible_windows_to_write_positions.keys()) - def set_cursor_position(self, window: "Window", position: Point) -> None: + def set_cursor_position(self, window: Window, position: Point) -> None: """ Set the cursor position for a given window. """ self.cursor_positions[window] = position - def set_menu_position(self, window: "Window", position: Point) -> None: + def set_menu_position(self, window: Window, position: Point) -> None: """ Set the cursor position for a given window. """ self.menu_positions[window] = position - def get_cursor_position(self, window: "Window") -> Point: + def get_cursor_position(self, window: Window) -> Point: """ Get the cursor position for a given window. Returns a `Point`. @@ -224,7 +226,7 @@ def get_cursor_position(self, window: "Window") -> Point: except KeyError: return Point(x=0, y=0) - def get_menu_position(self, window: "Window") -> Point: + def get_menu_position(self, window: Window) -> Point: """ Get the menu position for a given window. (This falls back to the cursor position if no menu position was set.) @@ -274,7 +276,7 @@ def append_style_to_content(self, style_str: str) -> None: row[x] = char_cache[char.char, char.style + append_style] def fill_area( - self, write_position: "WritePosition", style: str = "", after: bool = False + self, write_position: WritePosition, style: str = "", after: bool = False ) -> None: """ Fill the content of this area, using the given `style`. diff --git a/src/prompt_toolkit/layout/scrollable_pane.py b/src/prompt_toolkit/layout/scrollable_pane.py index 810e405a93..f6c2f88dd1 100644 --- a/src/prompt_toolkit/layout/scrollable_pane.py +++ b/src/prompt_toolkit/layout/scrollable_pane.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Dict, List, Optional from prompt_toolkit.data_structures import Point @@ -52,7 +54,7 @@ class ScrollablePane(Container): def __init__( self, content: Container, - scroll_offsets: Optional[ScrollOffsets] = None, + scroll_offsets: ScrollOffsets | None = None, keep_cursor_visible: FilterOrBool = True, keep_focused_window_visible: FilterOrBool = True, max_available_height: int = MAX_AVAILABLE_HEIGHT, @@ -119,7 +121,7 @@ def write_to_screen( write_position: WritePosition, parent_style: str, erase_bg: bool, - z_index: Optional[int], + z_index: int | None, ) -> None: """ Render scrollable pane content. @@ -293,7 +295,7 @@ def _copy_over_mouse_handlers( # Cache mouse handlers when wrapping them. Very often the same mouse # handler is registered for many positions. - mouse_handler_wrappers: Dict[MouseHandler, MouseHandler] = {} + mouse_handler_wrappers: dict[MouseHandler, MouseHandler] = {} def wrap_mouse_handler(handler: MouseHandler) -> MouseHandler: "Wrap mouse handler. Translate coordinates in `MouseEvent`." @@ -348,10 +350,10 @@ def _copy_over_write_positions( def is_modal(self) -> bool: return self.content.is_modal() - def get_key_bindings(self) -> Optional[KeyBindingsBase]: + def get_key_bindings(self) -> KeyBindingsBase | None: return self.content.get_key_bindings() - def get_children(self) -> List["Container"]: + def get_children(self) -> list[Container]: return [self.content] def _make_window_visible( @@ -359,7 +361,7 @@ def _make_window_visible( visible_height: int, virtual_height: int, visible_win_write_pos: WritePosition, - cursor_position: Optional[Point], + cursor_position: Point | None, ) -> None: """ Scroll the scrollable pane, so that this window becomes visible. diff --git a/src/prompt_toolkit/layout/utils.py b/src/prompt_toolkit/layout/utils.py index cebdf28bc5..757358ce89 100644 --- a/src/prompt_toolkit/layout/utils.py +++ b/src/prompt_toolkit/layout/utils.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import TYPE_CHECKING, Iterable, List, TypeVar, Union, cast, overload from prompt_toolkit.formatted_text.base import OneStyleAndTextTuple @@ -28,13 +30,13 @@ def append(self, item: _T) -> None: def extend(self, lst: Iterable[_T]) -> None: super().extend(explode_text_fragments(lst)) - def insert(self, index: "SupportsIndex", item: _T) -> None: + def insert(self, index: SupportsIndex, item: _T) -> None: raise NotImplementedError # TODO # TODO: When creating a copy() or [:], return also an _ExplodedList. @overload - def __setitem__(self, index: "SupportsIndex", value: _T) -> None: + def __setitem__(self, index: SupportsIndex, value: _T) -> None: ... @overload @@ -42,7 +44,7 @@ def __setitem__(self, index: slice, value: Iterable[_T]) -> None: ... def __setitem__( - self, index: Union["SupportsIndex", slice], value: Union[_T, Iterable[_T]] + self, index: SupportsIndex | slice, value: _T | Iterable[_T] ) -> None: """ Ensure that when `(style_str, 'long string')` is set, the string will be @@ -71,7 +73,7 @@ def explode_text_fragments(fragments: Iterable[_T]) -> _ExplodedList[_T]: if isinstance(fragments, _ExplodedList): return fragments - result: List[_T] = [] + result: list[_T] = [] for style, string, *rest in fragments: for c in string: diff --git a/src/prompt_toolkit/lexers/__init__.py b/src/prompt_toolkit/lexers/__init__.py index 3e875e6573..9bdc599847 100644 --- a/src/prompt_toolkit/lexers/__init__.py +++ b/src/prompt_toolkit/lexers/__init__.py @@ -2,6 +2,8 @@ Lexer interface and implementations. Used for syntax highlighting. """ +from __future__ import annotations + from .base import DynamicLexer, Lexer, SimpleLexer from .pygments import PygmentsLexer, RegexSync, SyncFromStart, SyntaxSync diff --git a/src/prompt_toolkit/lexers/base.py b/src/prompt_toolkit/lexers/base.py index dd9f2459af..3c59de2418 100644 --- a/src/prompt_toolkit/lexers/base.py +++ b/src/prompt_toolkit/lexers/base.py @@ -1,6 +1,8 @@ """ Base classes for prompt_toolkit lexers. """ +from __future__ import annotations + from abc import ABCMeta, abstractmethod from typing import Callable, Hashable, Optional @@ -69,7 +71,7 @@ class DynamicLexer(Lexer): :param get_lexer: Callable that returns a :class:`.Lexer` instance. """ - def __init__(self, get_lexer: Callable[[], Optional[Lexer]]) -> None: + def __init__(self, get_lexer: Callable[[], Lexer | None]) -> None: self.get_lexer = get_lexer self._dummy = SimpleLexer() diff --git a/src/prompt_toolkit/lexers/pygments.py b/src/prompt_toolkit/lexers/pygments.py index 9a871125fe..0c31220cc4 100644 --- a/src/prompt_toolkit/lexers/pygments.py +++ b/src/prompt_toolkit/lexers/pygments.py @@ -4,6 +4,8 @@ This includes syntax synchronization code, so that we don't have to start lexing at the beginning of a document, when displaying a very large text. """ +from __future__ import annotations + import re from abc import ABCMeta, abstractmethod from typing import ( @@ -47,7 +49,7 @@ class SyntaxSync(metaclass=ABCMeta): @abstractmethod def get_sync_start_position( self, document: Document, lineno: int - ) -> Tuple[int, int]: + ) -> tuple[int, int]: """ Return the position from where we can start lexing as a (row, column) tuple. @@ -65,7 +67,7 @@ class SyncFromStart(SyntaxSync): def get_sync_start_position( self, document: Document, lineno: int - ) -> Tuple[int, int]: + ) -> tuple[int, int]: return 0, 0 @@ -87,7 +89,7 @@ def __init__(self, pattern: str) -> None: def get_sync_start_position( self, document: Document, lineno: int - ) -> Tuple[int, int]: + ) -> tuple[int, int]: """ Scan backwards, and find a possible position to start. """ @@ -110,7 +112,7 @@ def get_sync_start_position( return lineno, 0 @classmethod - def from_pygments_lexer_cls(cls, lexer_cls: "PygmentsLexerCls") -> "RegexSync": + def from_pygments_lexer_cls(cls, lexer_cls: PygmentsLexerCls) -> RegexSync: """ Create a :class:`.RegexSync` instance for this Pygments lexer class. """ @@ -137,7 +139,7 @@ class _TokenCache(Dict[Tuple[str, ...], str]): ``class:pygments,pygments.A,pygments.A.B,pygments.A.B.C`` """ - def __missing__(self, key: Tuple[str, ...]) -> str: + def __missing__(self, key: tuple[str, ...]) -> str: result = "class:" + pygments_token_to_classname(key) self[key] = result return result @@ -185,9 +187,9 @@ class PygmentsLexer(Lexer): def __init__( self, - pygments_lexer_cls: Type["PygmentsLexerCls"], + pygments_lexer_cls: type[PygmentsLexerCls], sync_from_start: FilterOrBool = True, - syntax_sync: Optional[SyntaxSync] = None, + syntax_sync: SyntaxSync | None = None, ) -> None: self.pygments_lexer_cls = pygments_lexer_cls self.sync_from_start = to_filter(sync_from_start) @@ -205,7 +207,7 @@ def __init__( @classmethod def from_filename( cls, filename: str, sync_from_start: FilterOrBool = True - ) -> "Lexer": + ) -> Lexer: """ Create a `Lexer` from a filename. """ @@ -228,11 +230,11 @@ def lex_document(self, document: Document) -> Callable[[int], StyleAndTextTuples LineGenerator = Generator[Tuple[int, StyleAndTextTuples], None, None] # Cache of already lexed lines. - cache: Dict[int, StyleAndTextTuples] = {} + cache: dict[int, StyleAndTextTuples] = {} # Pygments generators that are currently lexing. # Map lexer generator to the line number. - line_generators: Dict[LineGenerator, int] = {} + line_generators: dict[LineGenerator, int] = {} def get_syntax_sync() -> SyntaxSync: "The Syntax synchronisation object that we currently use." @@ -241,7 +243,7 @@ def get_syntax_sync() -> SyntaxSync: else: return self.syntax_sync - def find_closest_generator(i: int) -> Optional[LineGenerator]: + def find_closest_generator(i: int) -> LineGenerator | None: "Return a generator close to line 'i', or None if none was found." for generator, lineno in line_generators.items(): if lineno < i and i - lineno < self.REUSE_GENERATOR_MAX_DISTANCE: @@ -254,7 +256,7 @@ def create_line_generator(start_lineno: int, column: int = 0) -> LineGenerator: Each iteration it yields a (line_number, [(style_str, text), ...]) tuple. """ - def get_text_fragments() -> Iterable[Tuple[str, str]]: + def get_text_fragments() -> Iterable[tuple[str, str]]: text = "\n".join(document.lines[start_lineno:])[column:] # We call `get_text_fragments_unprocessed`, because `get_tokens` will diff --git a/src/prompt_toolkit/log.py b/src/prompt_toolkit/log.py index 36ceced49e..adb5172afd 100644 --- a/src/prompt_toolkit/log.py +++ b/src/prompt_toolkit/log.py @@ -1,6 +1,8 @@ """ Logging configuration. """ +from __future__ import annotations + import logging __all__ = [ diff --git a/src/prompt_toolkit/mouse_events.py b/src/prompt_toolkit/mouse_events.py index e10ff0322a..29fef3b868 100644 --- a/src/prompt_toolkit/mouse_events.py +++ b/src/prompt_toolkit/mouse_events.py @@ -15,6 +15,8 @@ coordinates to coordinates relative to the user control, and there `UIControl.mouse_handler` is called. """ +from __future__ import annotations + from enum import Enum from typing import FrozenSet @@ -72,7 +74,7 @@ def __init__( position: Point, event_type: MouseEventType, button: MouseButton, - modifiers: FrozenSet[MouseModifier], + modifiers: frozenset[MouseModifier], ) -> None: self.position = position self.event_type = event_type diff --git a/src/prompt_toolkit/output/__init__.py b/src/prompt_toolkit/output/__init__.py index 7b90b476bd..6b4c5f3b66 100644 --- a/src/prompt_toolkit/output/__init__.py +++ b/src/prompt_toolkit/output/__init__.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from .base import DummyOutput, Output from .color_depth import ColorDepth from .defaults import create_output diff --git a/src/prompt_toolkit/output/base.py b/src/prompt_toolkit/output/base.py index c78677bc8b..05b222a2d6 100644 --- a/src/prompt_toolkit/output/base.py +++ b/src/prompt_toolkit/output/base.py @@ -1,6 +1,8 @@ """ Interface for an output. """ +from __future__ import annotations + from abc import ABCMeta, abstractmethod from typing import Optional, TextIO @@ -26,7 +28,7 @@ class Output(metaclass=ABCMeta): :class:`~prompt_toolkit.output.win32.Win32Output`. """ - stdout: Optional[TextIO] = None + stdout: TextIO | None = None @abstractmethod def fileno(self) -> int: diff --git a/src/prompt_toolkit/output/color_depth.py b/src/prompt_toolkit/output/color_depth.py index a6166bacaf..66723354bc 100644 --- a/src/prompt_toolkit/output/color_depth.py +++ b/src/prompt_toolkit/output/color_depth.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os from enum import Enum from typing import Optional @@ -33,7 +35,7 @@ class ColorDepth(str, Enum): TRUE_COLOR = DEPTH_24_BIT @classmethod - def from_env(cls) -> Optional["ColorDepth"]: + def from_env(cls) -> ColorDepth | None: """ Return the color depth if the $PROMPT_TOOLKIT_COLOR_DEPTH environment variable has been set. @@ -49,7 +51,7 @@ def from_env(cls) -> Optional["ColorDepth"]: return None @classmethod - def default(cls) -> "ColorDepth": + def default(cls) -> ColorDepth: """ Return the default color depth for the default output. """ diff --git a/src/prompt_toolkit/output/conemu.py b/src/prompt_toolkit/output/conemu.py index fc46cc4afd..b922fef847 100644 --- a/src/prompt_toolkit/output/conemu.py +++ b/src/prompt_toolkit/output/conemu.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import sys assert sys.platform == "win32" @@ -33,7 +35,7 @@ class ConEmuOutput: """ def __init__( - self, stdout: TextIO, default_color_depth: Optional[ColorDepth] = None + self, stdout: TextIO, default_color_depth: ColorDepth | None = None ) -> None: self.win32_output = Win32Output(stdout, default_color_depth=default_color_depth) self.vt100_output = Vt100_Output( diff --git a/src/prompt_toolkit/output/defaults.py b/src/prompt_toolkit/output/defaults.py index 7fb0f8931d..166dc551e6 100644 --- a/src/prompt_toolkit/output/defaults.py +++ b/src/prompt_toolkit/output/defaults.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import sys from typing import Optional, TextIO, cast @@ -17,7 +19,7 @@ def create_output( - stdout: Optional[TextIO] = None, always_prefer_tty: bool = False + stdout: TextIO | None = None, always_prefer_tty: bool = False ) -> Output: """ Return an :class:`~prompt_toolkit.output.Output` instance for the command diff --git a/src/prompt_toolkit/output/flush_stdout.py b/src/prompt_toolkit/output/flush_stdout.py index a1cbb71b39..daf58efee6 100644 --- a/src/prompt_toolkit/output/flush_stdout.py +++ b/src/prompt_toolkit/output/flush_stdout.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import errno import os import sys diff --git a/src/prompt_toolkit/output/plain_text.py b/src/prompt_toolkit/output/plain_text.py index 4360355f4f..6f73e6f950 100644 --- a/src/prompt_toolkit/output/plain_text.py +++ b/src/prompt_toolkit/output/plain_text.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import List, TextIO from prompt_toolkit.cursor_shapes import CursorShape @@ -27,7 +29,7 @@ def __init__(self, stdout: TextIO) -> None: assert all(hasattr(stdout, a) for a in ("write", "flush")) self.stdout: TextIO = stdout - self._buffer: List[str] = [] + self._buffer: list[str] = [] def fileno(self) -> int: "There is no sensible default for fileno()." diff --git a/src/prompt_toolkit/output/vt100.py b/src/prompt_toolkit/output/vt100.py index 604aa90e12..dfbf4100e7 100644 --- a/src/prompt_toolkit/output/vt100.py +++ b/src/prompt_toolkit/output/vt100.py @@ -6,6 +6,8 @@ everything has been highly optimized.) http://pygments.org/ """ +from __future__ import annotations + import io import os import sys @@ -159,10 +161,10 @@ class _16ColorCache: def __init__(self, bg: bool = False) -> None: self.bg = bg - self._cache: Dict[Hashable, _ColorCodeAndName] = {} + self._cache: dict[Hashable, _ColorCodeAndName] = {} def get_code( - self, value: Tuple[int, int, int], exclude: Sequence[str] = () + self, value: tuple[int, int, int], exclude: Sequence[str] = () ) -> _ColorCodeAndName: """ Return a (ansi_code, ansi_name) tuple. (E.g. ``(44, 'ansiblue')``.) for @@ -177,7 +179,7 @@ def get_code( return cache[key] def _get( - self, value: Tuple[int, int, int], exclude: Sequence[str] = () + self, value: tuple[int, int, int], exclude: Sequence[str] = () ) -> _ColorCodeAndName: r, g, b = value match = _get_closest_ansi_color(r, g, b, exclude=exclude) @@ -198,7 +200,7 @@ class _256ColorCache(Dict[Tuple[int, int, int], int]): def __init__(self) -> None: # Build color table. - colors: List[Tuple[int, int, int]] = [] + colors: list[tuple[int, int, int]] = [] # colors 0..15: 16 basic colors colors.append((0x00, 0x00, 0x00)) # 0 @@ -234,7 +236,7 @@ def __init__(self) -> None: self.colors = colors - def __missing__(self, value: Tuple[int, int, int]) -> int: + def __missing__(self, value: tuple[int, int, int]) -> int: r, g, b = value # Find closest color. @@ -286,7 +288,7 @@ def __missing__(self, attrs: Attrs) -> str: reverse, hidden, ) = attrs - parts: List[str] = [] + parts: list[str] = [] parts.extend(self._colors_to_code(fgcolor or "", bgcolor or "")) @@ -313,7 +315,7 @@ def __missing__(self, attrs: Attrs) -> str: self[attrs] = result return result - def _color_name_to_rgb(self, color: str) -> Tuple[int, int, int]: + def _color_name_to_rgb(self, color: str) -> tuple[int, int, int]: "Turn 'ffffff', into (0xff, 0xff, 0xff)." try: rgb = int(color, 16) @@ -334,7 +336,7 @@ def _colors_to_code(self, fg_color: str, bg_color: str) -> Iterable[str]: # same. (Unless they were explicitly defined to be the same color.) fg_ansi = "" - def get(color: str, bg: bool) -> List[int]: + def get(color: str, bg: bool) -> list[int]: nonlocal fg_ansi table = BG_ANSI_COLORS if bg else FG_ANSI_COLORS @@ -376,14 +378,14 @@ def get(color: str, bg: bool) -> List[int]: else: return [(48 if bg else 38), 5, _256_colors[rgb]] - result: List[int] = [] + result: list[int] = [] result.extend(get(fg_color, False)) result.extend(get(bg_color, True)) return map(str, result) -def _get_size(fileno: int) -> Tuple[int, int]: +def _get_size(fileno: int) -> tuple[int, int]: """ Get the size of this pseudo terminal. @@ -410,20 +412,20 @@ class Vt100_Output(Output): # For the error messages. Only display "Output is not a terminal" once per # file descriptor. - _fds_not_a_terminal: Set[int] = set() + _fds_not_a_terminal: set[int] = set() def __init__( self, stdout: TextIO, get_size: Callable[[], Size], - term: Optional[str] = None, - default_color_depth: Optional[ColorDepth] = None, + term: str | None = None, + default_color_depth: ColorDepth | None = None, enable_bell: bool = True, enable_cpr: bool = True, ) -> None: assert all(hasattr(stdout, a) for a in ("write", "flush")) - self._buffer: List[str] = [] + self._buffer: list[str] = [] self.stdout: TextIO = stdout self.default_color_depth = default_color_depth self._get_size = get_size @@ -432,7 +434,7 @@ def __init__( self.enable_cpr = enable_cpr # Cache for escape codes. - self._escape_code_caches: Dict[ColorDepth, _EscapeCodeCache] = { + self._escape_code_caches: dict[ColorDepth, _EscapeCodeCache] = { ColorDepth.DEPTH_1_BIT: _EscapeCodeCache(ColorDepth.DEPTH_1_BIT), ColorDepth.DEPTH_4_BIT: _EscapeCodeCache(ColorDepth.DEPTH_4_BIT), ColorDepth.DEPTH_8_BIT: _EscapeCodeCache(ColorDepth.DEPTH_8_BIT), @@ -448,16 +450,16 @@ def __init__( def from_pty( cls, stdout: TextIO, - term: Optional[str] = None, - default_color_depth: Optional[ColorDepth] = None, + term: str | None = None, + default_color_depth: ColorDepth | None = None, enable_bell: bool = True, - ) -> "Vt100_Output": + ) -> Vt100_Output: """ Create an Output class from a pseudo terminal. (This will take the dimensions by reading the pseudo terminal attributes.) """ - fd: Optional[int] + fd: int | None # Normally, this requires a real TTY device, but people instantiate # this class often during unit tests as well. For convenience, we print # an error message, use standard dimensions, and go on. diff --git a/src/prompt_toolkit/output/win32.py b/src/prompt_toolkit/output/win32.py index 1724eae5da..191a05d142 100644 --- a/src/prompt_toolkit/output/win32.py +++ b/src/prompt_toolkit/output/win32.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import sys assert sys.platform == "win32" @@ -96,12 +98,12 @@ def __init__( self, stdout: TextIO, use_complete_width: bool = False, - default_color_depth: Optional[ColorDepth] = None, + default_color_depth: ColorDepth | None = None, ) -> None: self.use_complete_width = use_complete_width self.default_color_depth = default_color_depth - self._buffer: List[str] = [] + self._buffer: list[str] = [] self.stdout: TextIO = stdout self.hconsole = HANDLE(windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)) @@ -556,8 +558,8 @@ class BACKGROUND_COLOR: def _create_ansi_color_dict( - color_cls: Union[Type[FOREGROUND_COLOR], Type[BACKGROUND_COLOR]] -) -> Dict[str, int]: + color_cls: type[FOREGROUND_COLOR] | type[BACKGROUND_COLOR], +) -> dict[str, int]: "Create a table that maps the 16 named ansi colors to their Windows code." return { "ansidefault": color_cls.BLACK, @@ -598,10 +600,10 @@ def __init__(self) -> None: self._win32_colors = self._build_color_table() # Cache (map color string to foreground and background code). - self.best_match: Dict[str, Tuple[int, int]] = {} + self.best_match: dict[str, tuple[int, int]] = {} @staticmethod - def _build_color_table() -> List[Tuple[int, int, int, int, int]]: + def _build_color_table() -> list[tuple[int, int, int, int, int]]: """ Build an RGB-to-256 color conversion table """ @@ -627,7 +629,7 @@ def _build_color_table() -> List[Tuple[int, int, int, int, int]]: (0xFF, 0xFF, 0xFF, FG.GRAY | FG.INTENSITY, BG.GRAY | BG.INTENSITY), ] - def _closest_color(self, r: int, g: int, b: int) -> Tuple[int, int]: + def _closest_color(self, r: int, g: int, b: int) -> tuple[int, int]: distance = 257 * 257 * 3 # "infinity" (>distance from #000000 to #ffffff) fg_match = 0 bg_match = 0 @@ -645,7 +647,7 @@ def _closest_color(self, r: int, g: int, b: int) -> Tuple[int, int]: distance = d return fg_match, bg_match - def _color_indexes(self, color: str) -> Tuple[int, int]: + def _color_indexes(self, color: str) -> tuple[int, int]: indexes = self.best_match.get(color, None) if indexes is None: try: diff --git a/src/prompt_toolkit/output/windows10.py b/src/prompt_toolkit/output/windows10.py index d5d55f18ca..5546ea0865 100644 --- a/src/prompt_toolkit/output/windows10.py +++ b/src/prompt_toolkit/output/windows10.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import sys assert sys.platform == "win32" @@ -29,7 +31,7 @@ class Windows10_Output: """ def __init__( - self, stdout: TextIO, default_color_depth: Optional[ColorDepth] = None + self, stdout: TextIO, default_color_depth: ColorDepth | None = None ) -> None: self.win32_output = Win32Output(stdout, default_color_depth=default_color_depth) self.vt100_output = Vt100_Output( diff --git a/src/prompt_toolkit/patch_stdout.py b/src/prompt_toolkit/patch_stdout.py index c155df104c..4518de0625 100644 --- a/src/prompt_toolkit/patch_stdout.py +++ b/src/prompt_toolkit/patch_stdout.py @@ -17,6 +17,8 @@ Multiple applications can run in the body of the context manager, one after the other. """ +from __future__ import annotations + import asyncio import queue import sys @@ -98,7 +100,7 @@ def __init__( self.raw = raw self._lock = threading.RLock() - self._buffer: List[str] = [] + self._buffer: list[str] = [] # Keep track of the curret app session. self.app_session = get_app_session() @@ -112,11 +114,11 @@ def __init__( self._output: Output = self.app_session.output # Flush thread - self._flush_queue: queue.Queue[Union[str, _Done]] = queue.Queue() + self._flush_queue: queue.Queue[str | _Done] = queue.Queue() self._flush_thread = self._start_write_thread() self.closed = False - def __enter__(self) -> "StdoutProxy": + def __enter__(self) -> StdoutProxy: return self def __exit__(self, *args: object) -> None: @@ -180,7 +182,7 @@ def _write_thread(self) -> None: if app_loop is not None: time.sleep(self.sleep_between_writes) - def _get_app_loop(self) -> Optional[asyncio.AbstractEventLoop]: + def _get_app_loop(self) -> asyncio.AbstractEventLoop | None: """ Return the event loop for the application currently running in our `AppSession`. @@ -193,7 +195,7 @@ def _get_app_loop(self) -> Optional[asyncio.AbstractEventLoop]: return app.loop def _write_and_flush( - self, loop: Optional[asyncio.AbstractEventLoop], text: str + self, loop: asyncio.AbstractEventLoop | None, text: str ) -> None: """ Write the given text to stdout and flush. diff --git a/src/prompt_toolkit/renderer.py b/src/prompt_toolkit/renderer.py index dcb2d01650..3c746295eb 100644 --- a/src/prompt_toolkit/renderer.py +++ b/src/prompt_toolkit/renderer.py @@ -2,6 +2,8 @@ Renders the command line on the console. (Redraws parts of the input line that were changed.) """ +from __future__ import annotations + from asyncio import FIRST_COMPLETED, Future, ensure_future, sleep, wait from collections import deque from enum import Enum @@ -34,20 +36,20 @@ def _output_screen_diff( - app: "Application[Any]", + app: Application[Any], output: Output, screen: Screen, current_pos: Point, color_depth: ColorDepth, - previous_screen: Optional[Screen], - last_style: Optional[str], + previous_screen: Screen | None, + last_style: str | None, is_done: bool, # XXX: drop is_done full_screen: bool, - attrs_for_style_string: "_StyleStringToAttrsCache", - style_string_has_style: "_StyleStringHasStyleCache", + attrs_for_style_string: _StyleStringToAttrsCache, + style_string_has_style: _StyleStringHasStyleCache, size: Size, previous_width: int, -) -> Tuple[Point, Optional[str]]: +) -> tuple[Point, str | None]: """ Render the diff between this screen and the previous screen. @@ -139,7 +141,7 @@ def output_char(char: Char) -> None: write(char.char) last_style = char.style - def get_max_column_index(row: Dict[int, Char]) -> int: + def get_max_column_index(row: dict[int, Char]) -> int: """ Return max used column index, ignoring whitespace (without style) at the end of the line. This is important for people that copy/paste @@ -272,7 +274,7 @@ class _StyleStringToAttrsCache(Dict[str, Attrs]): def __init__( self, - get_attrs_for_style_str: Callable[["str"], Attrs], + get_attrs_for_style_str: Callable[[str], Attrs], style_transformation: StyleTransformation, ) -> None: self.get_attrs_for_style_str = get_attrs_for_style_str @@ -297,7 +299,7 @@ class _StyleStringHasStyleCache(Dict[str, bool]): output if there's no text in the cell. """ - def __init__(self, style_string_to_attrs: Dict[str, Attrs]) -> None: + def __init__(self, style_string_to_attrs: dict[str, Attrs]) -> None: self.style_string_to_attrs = style_string_to_attrs def __missing__(self, style_str: str) -> bool: @@ -341,7 +343,7 @@ def __init__( output: Output, full_screen: bool = False, mouse_support: FilterOrBool = False, - cpr_not_supported_callback: Optional[Callable[[], None]] = None, + cpr_not_supported_callback: Callable[[], None] | None = None, ) -> None: self.style = style self.output = output @@ -362,11 +364,11 @@ def __init__( self.cpr_support = CPR_Support.NOT_SUPPORTED # Cache for the style. - self._attrs_for_style: Optional[_StyleStringToAttrsCache] = None - self._style_string_has_style: Optional[_StyleStringHasStyleCache] = None - self._last_style_hash: Optional[Hashable] = None - self._last_transformation_hash: Optional[Hashable] = None - self._last_color_depth: Optional[ColorDepth] = None + self._attrs_for_style: _StyleStringToAttrsCache | None = None + self._style_string_has_style: _StyleStringHasStyleCache | None = None + self._last_style_hash: Hashable | None = None + self._last_transformation_hash: Hashable | None = None + self._last_color_depth: ColorDepth | None = None self.reset(_scroll=True) @@ -378,10 +380,10 @@ def reset(self, _scroll: bool = False, leave_alternate_screen: bool = True) -> N # we can create a `diff` between two screens and only output the # difference. It's also to remember the last height. (To show for # instance a toolbar at the bottom position.) - self._last_screen: Optional[Screen] = None - self._last_size: Optional[Size] = None - self._last_style: Optional[str] = None - self._last_cursor_shape: Optional[CursorShape] = None + self._last_screen: Screen | None = None + self._last_size: Size | None = None + self._last_style: str | None = None + self._last_cursor_shape: CursorShape | None = None # Default MouseHandlers. (Just empty.) self.mouse_handlers = MouseHandlers() @@ -419,7 +421,7 @@ def reset(self, _scroll: bool = False, leave_alternate_screen: bool = True) -> N self.output.flush() @property - def last_rendered_screen(self) -> Optional[Screen]: + def last_rendered_screen(self) -> Screen | None: """ The `Screen` class that was generated during the last rendering. This can be `None`. @@ -578,7 +580,7 @@ async def wait_for_timeout() -> None: task.cancel() def render( - self, app: "Application[Any]", layout: "Layout", is_done: bool = False + self, app: Application[Any], layout: Layout, is_done: bool = False ) -> None: """ Render the current interface to the output. @@ -763,8 +765,8 @@ def print_formatted_text( output: Output, formatted_text: AnyFormattedText, style: BaseStyle, - style_transformation: Optional[StyleTransformation] = None, - color_depth: Optional[ColorDepth] = None, + style_transformation: StyleTransformation | None = None, + color_depth: ColorDepth | None = None, ) -> None: """ Print a list of (style_str, text) tuples in the given style to the output. @@ -776,7 +778,7 @@ def print_formatted_text( # Reset first. output.reset_attributes() output.enable_autowrap() - last_attrs: Optional[Attrs] = None + last_attrs: Attrs | None = None # Print all (style_str, text) tuples. attrs_for_style_string = _StyleStringToAttrsCache( diff --git a/src/prompt_toolkit/search.py b/src/prompt_toolkit/search.py index e8996326a0..19fa48a36f 100644 --- a/src/prompt_toolkit/search.py +++ b/src/prompt_toolkit/search.py @@ -5,6 +5,8 @@ `prompt_toolkit.key_binding.bindings.search`. (Use these for new key bindings instead of calling these function directly.) """ +from __future__ import annotations + from enum import Enum from typing import TYPE_CHECKING, Dict, Optional @@ -64,7 +66,7 @@ def __repr__(self) -> str: self.ignore_case, ) - def __invert__(self) -> "SearchState": + def __invert__(self) -> SearchState: """ Create a new SearchState where backwards becomes forwards and the other way around. @@ -80,7 +82,7 @@ def __invert__(self) -> "SearchState": def start_search( - buffer_control: Optional["BufferControl"] = None, + buffer_control: BufferControl | None = None, direction: SearchDirection = SearchDirection.FORWARD, ) -> None: """ @@ -118,7 +120,7 @@ def start_search( get_app().vi_state.input_mode = InputMode.INSERT -def stop_search(buffer_control: Optional["BufferControl"] = None) -> None: +def stop_search(buffer_control: BufferControl | None = None) -> None: """ Stop search through the given `buffer_control`. """ @@ -217,8 +219,8 @@ def accept_search() -> None: def _get_reverse_search_links( - layout: "Layout", -) -> Dict["BufferControl", "SearchBufferControl"]: + layout: Layout, +) -> dict[BufferControl, SearchBufferControl]: """ Return mapping from BufferControl to SearchBufferControl. """ diff --git a/src/prompt_toolkit/selection.py b/src/prompt_toolkit/selection.py index 616ce876a6..2158fa92a2 100644 --- a/src/prompt_toolkit/selection.py +++ b/src/prompt_toolkit/selection.py @@ -1,6 +1,8 @@ """ Data structures for the selection. """ +from __future__ import annotations + from enum import Enum __all__ = [ diff --git a/src/prompt_toolkit/shortcuts/__init__.py b/src/prompt_toolkit/shortcuts/__init__.py index 10ad31483e..49e5ac4ea9 100644 --- a/src/prompt_toolkit/shortcuts/__init__.py +++ b/src/prompt_toolkit/shortcuts/__init__.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from .dialogs import ( button_dialog, checkboxlist_dialog, diff --git a/src/prompt_toolkit/shortcuts/dialogs.py b/src/prompt_toolkit/shortcuts/dialogs.py index eacb05a00c..3229e72f2e 100644 --- a/src/prompt_toolkit/shortcuts/dialogs.py +++ b/src/prompt_toolkit/shortcuts/dialogs.py @@ -1,11 +1,14 @@ +from __future__ import annotations + import functools +from asyncio import get_running_loop from typing import Any, Callable, List, Optional, Sequence, Tuple, TypeVar from prompt_toolkit.application import Application from prompt_toolkit.application.current import get_app from prompt_toolkit.buffer import Buffer from prompt_toolkit.completion import Completer -from prompt_toolkit.eventloop import get_event_loop, run_in_executor_with_context +from prompt_toolkit.eventloop import run_in_executor_with_context from prompt_toolkit.filters import FilterOrBool from prompt_toolkit.formatted_text import AnyFormattedText from prompt_toolkit.key_binding.bindings.focus import focus_next, focus_previous @@ -44,7 +47,7 @@ def yes_no_dialog( text: AnyFormattedText = "", yes_text: str = "Yes", no_text: str = "No", - style: Optional[BaseStyle] = None, + style: BaseStyle | None = None, ) -> Application[bool]: """ Display a Yes/No dialog. @@ -76,8 +79,8 @@ def no_handler() -> None: def button_dialog( title: AnyFormattedText = "", text: AnyFormattedText = "", - buttons: List[Tuple[str, _T]] = [], - style: Optional[BaseStyle] = None, + buttons: list[tuple[str, _T]] = [], + style: BaseStyle | None = None, ) -> Application[_T]: """ Display a dialog with button choices (given as a list of tuples). @@ -105,10 +108,10 @@ def input_dialog( text: AnyFormattedText = "", ok_text: str = "OK", cancel_text: str = "Cancel", - completer: Optional[Completer] = None, - validator: Optional[Validator] = None, + completer: Completer | None = None, + validator: Validator | None = None, password: FilterOrBool = False, - style: Optional[BaseStyle] = None, + style: BaseStyle | None = None, default: str = "", ) -> Application[str]: """ @@ -156,7 +159,7 @@ def message_dialog( title: AnyFormattedText = "", text: AnyFormattedText = "", ok_text: str = "Ok", - style: Optional[BaseStyle] = None, + style: BaseStyle | None = None, ) -> Application[None]: """ Display a simple message box and wait until the user presses enter. @@ -176,9 +179,9 @@ def radiolist_dialog( text: AnyFormattedText = "", ok_text: str = "Ok", cancel_text: str = "Cancel", - values: Optional[Sequence[Tuple[_T, AnyFormattedText]]] = None, - default: Optional[_T] = None, - style: Optional[BaseStyle] = None, + values: Sequence[tuple[_T, AnyFormattedText]] | None = None, + default: _T | None = None, + style: BaseStyle | None = None, ) -> Application[_T]: """ Display a simple list of element the user can choose amongst. @@ -215,10 +218,10 @@ def checkboxlist_dialog( text: AnyFormattedText = "", ok_text: str = "Ok", cancel_text: str = "Cancel", - values: Optional[Sequence[Tuple[_T, AnyFormattedText]]] = None, - default_values: Optional[Sequence[_T]] = None, - style: Optional[BaseStyle] = None, -) -> Application[List[_T]]: + values: Sequence[tuple[_T, AnyFormattedText]] | None = None, + default_values: Sequence[_T] | None = None, + style: BaseStyle | None = None, +) -> Application[list[_T]]: """ Display a simple list of element the user can choose multiple values amongst. @@ -255,13 +258,13 @@ def progress_dialog( run_callback: Callable[[Callable[[int], None], Callable[[str], None]], None] = ( lambda *a: None ), - style: Optional[BaseStyle] = None, + style: BaseStyle | None = None, ) -> Application[None]: """ :param run_callback: A function that receives as input a `set_percentage` function and it does the work. """ - loop = get_event_loop() + loop = get_running_loop() progressbar = ProgressBar() text_area = TextArea( focusable=False, @@ -307,7 +310,7 @@ def pre_run() -> None: return app -def _create_app(dialog: AnyContainer, style: Optional[BaseStyle]) -> Application[Any]: +def _create_app(dialog: AnyContainer, style: BaseStyle | None) -> Application[Any]: # Key bindings. bindings = KeyBindings() bindings.add("tab")(focus_next) diff --git a/src/prompt_toolkit/shortcuts/progress_bar/__init__.py b/src/prompt_toolkit/shortcuts/progress_bar/__init__.py index 7d0fbb50d5..2261a5b6e7 100644 --- a/src/prompt_toolkit/shortcuts/progress_bar/__init__.py +++ b/src/prompt_toolkit/shortcuts/progress_bar/__init__.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from .base import ProgressBar, ProgressBarCounter from .formatters import ( Bar, diff --git a/src/prompt_toolkit/shortcuts/progress_bar/base.py b/src/prompt_toolkit/shortcuts/progress_bar/base.py index abba070bb7..c2009b3c26 100644 --- a/src/prompt_toolkit/shortcuts/progress_bar/base.py +++ b/src/prompt_toolkit/shortcuts/progress_bar/base.py @@ -7,13 +7,15 @@ for item in pb(data): ... """ +from __future__ import annotations + +import contextvars import datetime import functools import os import signal import threading import traceback -from asyncio import new_event_loop, set_event_loop from typing import ( Callable, Generic, @@ -55,14 +57,6 @@ from .formatters import Formatter, create_default_formatters -try: - import contextvars -except ImportError: - from prompt_toolkit.eventloop import dummy_contextvars - - contextvars = dummy_contextvars # type: ignore - - __all__ = ["ProgressBar"] E = KeyPressEvent @@ -70,7 +64,7 @@ _SIGWINCH = getattr(signal, "SIGWINCH", None) -def create_key_bindings(cancel_callback: Optional[Callable[[], None]]) -> KeyBindings: +def create_key_bindings(cancel_callback: Callable[[], None] | None) -> KeyBindings: """ Key bindings handled by the progress bar. (The main thread is not supposed to handle any key bindings.) @@ -125,20 +119,20 @@ class ProgressBar: def __init__( self, title: AnyFormattedText = None, - formatters: Optional[Sequence[Formatter]] = None, + formatters: Sequence[Formatter] | None = None, bottom_toolbar: AnyFormattedText = None, - style: Optional[BaseStyle] = None, - key_bindings: Optional[KeyBindings] = None, - cancel_callback: Optional[Callable[[], None]] = None, - file: Optional[TextIO] = None, - color_depth: Optional[ColorDepth] = None, - output: Optional[Output] = None, - input: Optional[Input] = None, + style: BaseStyle | None = None, + key_bindings: KeyBindings | None = None, + cancel_callback: Callable[[], None] | None = None, + file: TextIO | None = None, + color_depth: ColorDepth | None = None, + output: Output | None = None, + input: Input | None = None, ) -> None: self.title = title self.formatters = formatters or create_default_formatters() self.bottom_toolbar = bottom_toolbar - self.counters: List[ProgressBarCounter[object]] = [] + self.counters: list[ProgressBarCounter[object]] = [] self.style = style self.key_bindings = key_bindings self.cancel_callback = cancel_callback @@ -159,13 +153,12 @@ def keyboard_interrupt_to_main_thread() -> None: self.output = output or get_app_session().output self.input = input or get_app_session().input - self._thread: Optional[threading.Thread] = None + self._thread: threading.Thread | None = None - self._app_loop = new_event_loop() self._has_sigwinch = False self._app_started = threading.Event() - def __enter__(self) -> "ProgressBar": + def __enter__(self) -> ProgressBar: # Create UI Application. title_toolbar = ConditionalContainer( Window( @@ -229,7 +222,6 @@ def width_for_formatter(formatter: Formatter) -> AnyDimension: # Run application in different thread. def run() -> None: - set_event_loop(self._app_loop) try: self.app.run(pre_run=self._app_started.set) except BaseException as e: @@ -250,20 +242,19 @@ def __exit__(self, *a: object) -> None: self._app_started.wait() # Quit UI application. - if self.app.is_running: - self._app_loop.call_soon_threadsafe(self.app.exit) + if self.app.is_running and self.app.loop is not None: + self.app.loop.call_soon_threadsafe(self.app.exit) if self._thread is not None: self._thread.join() - self._app_loop.close() def __call__( self, - data: Optional[Iterable[_T]] = None, + data: Iterable[_T] | None = None, label: AnyFormattedText = "", remove_when_done: bool = False, - total: Optional[int] = None, - ) -> "ProgressBarCounter[_T]": + total: int | None = None, + ) -> ProgressBarCounter[_T]: """ Start a new counter. @@ -292,14 +283,14 @@ def __init__( self, progress_bar: ProgressBar, formatter: Formatter, - cancel_callback: Optional[Callable[[], None]], + cancel_callback: Callable[[], None] | None, ) -> None: self.progress_bar = progress_bar self.formatter = formatter self._key_bindings = create_key_bindings(cancel_callback) def create_content(self, width: int, height: int) -> UIContent: - items: List[StyleAndTextTuples] = [] + items: list[StyleAndTextTuples] = [] for pr in self.progress_bar.counters: try: @@ -333,20 +324,20 @@ class ProgressBarCounter(Generic[_CounterItem]): def __init__( self, progress_bar: ProgressBar, - data: Optional[Iterable[_CounterItem]] = None, + data: Iterable[_CounterItem] | None = None, label: AnyFormattedText = "", remove_when_done: bool = False, - total: Optional[int] = None, + total: int | None = None, ) -> None: self.start_time = datetime.datetime.now() - self.stop_time: Optional[datetime.datetime] = None + self.stop_time: datetime.datetime | None = None self.progress_bar = progress_bar self.data = data self.items_completed = 0 self.label = label self.remove_when_done = remove_when_done self._done = False - self.total: Optional[int] + self.total: int | None if total is None: try: @@ -447,7 +438,7 @@ def time_elapsed(self) -> datetime.timedelta: return self.stop_time - self.start_time @property - def time_left(self) -> Optional[datetime.timedelta]: + def time_left(self) -> datetime.timedelta | None: """ Timedelta representing the time left. """ diff --git a/src/prompt_toolkit/shortcuts/progress_bar/formatters.py b/src/prompt_toolkit/shortcuts/progress_bar/formatters.py index 1f0d98e715..3b05ad954b 100644 --- a/src/prompt_toolkit/shortcuts/progress_bar/formatters.py +++ b/src/prompt_toolkit/shortcuts/progress_bar/formatters.py @@ -2,6 +2,8 @@ Formatter classes for the progress bar. Each progress bar consists of a list of these formatters. """ +from __future__ import annotations + import datetime import time from abc import ABCMeta, abstractmethod @@ -45,13 +47,13 @@ class Formatter(metaclass=ABCMeta): @abstractmethod def format( self, - progress_bar: "ProgressBar", - progress: "ProgressBarCounter[object]", + progress_bar: ProgressBar, + progress: ProgressBarCounter[object], width: int, ) -> AnyFormattedText: pass - def get_width(self, progress_bar: "ProgressBar") -> AnyDimension: + def get_width(self, progress_bar: ProgressBar) -> AnyDimension: return D() @@ -65,13 +67,13 @@ def __init__(self, text: AnyFormattedText, style: str = "") -> None: def format( self, - progress_bar: "ProgressBar", - progress: "ProgressBarCounter[object]", + progress_bar: ProgressBar, + progress: ProgressBarCounter[object], width: int, ) -> AnyFormattedText: return self.text - def get_width(self, progress_bar: "ProgressBar") -> AnyDimension: + def get_width(self, progress_bar: ProgressBar) -> AnyDimension: return fragment_list_width(self.text) @@ -95,8 +97,8 @@ def _add_suffix(self, label: AnyFormattedText) -> StyleAndTextTuples: def format( self, - progress_bar: "ProgressBar", - progress: "ProgressBarCounter[object]", + progress_bar: ProgressBar, + progress: ProgressBarCounter[object], width: int, ) -> AnyFormattedText: label = self._add_suffix(progress.label) @@ -111,7 +113,7 @@ def format( return label - def get_width(self, progress_bar: "ProgressBar") -> AnyDimension: + def get_width(self, progress_bar: ProgressBar) -> AnyDimension: if self.width: return self.width @@ -132,13 +134,13 @@ class Percentage(Formatter): def format( self, - progress_bar: "ProgressBar", - progress: "ProgressBarCounter[object]", + progress_bar: ProgressBar, + progress: ProgressBarCounter[object], width: int, ) -> AnyFormattedText: return HTML(self.template).format(percentage=round(progress.percentage, 1)) - def get_width(self, progress_bar: "ProgressBar") -> AnyDimension: + def get_width(self, progress_bar: ProgressBar) -> AnyDimension: return D.exact(6) @@ -170,8 +172,8 @@ def __init__( def format( self, - progress_bar: "ProgressBar", - progress: "ProgressBarCounter[object]", + progress_bar: ProgressBar, + progress: ProgressBarCounter[object], width: int, ) -> AnyFormattedText: if progress.done or progress.total or progress.stopped: @@ -204,7 +206,7 @@ def format( start=self.start, end=self.end, bar_a=bar_a, bar_b=bar_b, bar_c=bar_c ) - def get_width(self, progress_bar: "ProgressBar") -> AnyDimension: + def get_width(self, progress_bar: ProgressBar) -> AnyDimension: return D(min=9) @@ -217,15 +219,15 @@ class Progress(Formatter): def format( self, - progress_bar: "ProgressBar", - progress: "ProgressBarCounter[object]", + progress_bar: ProgressBar, + progress: ProgressBarCounter[object], width: int, ) -> AnyFormattedText: return HTML(self.template).format( current=progress.items_completed, total=progress.total or "?" ) - def get_width(self, progress_bar: "ProgressBar") -> AnyDimension: + def get_width(self, progress_bar: ProgressBar) -> AnyDimension: all_lengths = [ len("{:>3}".format(c.total or "?")) for c in progress_bar.counters ] @@ -250,8 +252,8 @@ class TimeElapsed(Formatter): def format( self, - progress_bar: "ProgressBar", - progress: "ProgressBarCounter[object]", + progress_bar: ProgressBar, + progress: ProgressBarCounter[object], width: int, ) -> AnyFormattedText: text = _format_timedelta(progress.time_elapsed).rjust(width) @@ -259,7 +261,7 @@ def format( time_elapsed=text ) - def get_width(self, progress_bar: "ProgressBar") -> AnyDimension: + def get_width(self, progress_bar: ProgressBar) -> AnyDimension: all_values = [ len(_format_timedelta(c.time_elapsed)) for c in progress_bar.counters ] @@ -278,8 +280,8 @@ class TimeLeft(Formatter): def format( self, - progress_bar: "ProgressBar", - progress: "ProgressBarCounter[object]", + progress_bar: ProgressBar, + progress: ProgressBarCounter[object], width: int, ) -> AnyFormattedText: time_left = progress.time_left @@ -290,7 +292,7 @@ def format( return HTML(self.template).format(time_left=formatted_time_left.rjust(width)) - def get_width(self, progress_bar: "ProgressBar") -> AnyDimension: + def get_width(self, progress_bar: ProgressBar) -> AnyDimension: all_values = [ len(_format_timedelta(c.time_left)) if c.time_left is not None else 7 for c in progress_bar.counters @@ -311,14 +313,14 @@ class IterationsPerSecond(Formatter): def format( self, - progress_bar: "ProgressBar", - progress: "ProgressBarCounter[object]", + progress_bar: ProgressBar, + progress: ProgressBarCounter[object], width: int, ) -> AnyFormattedText: value = progress.items_completed / progress.time_elapsed.total_seconds() return HTML(self.template.format(iterations_per_second=value)) - def get_width(self, progress_bar: "ProgressBar") -> AnyDimension: + def get_width(self, progress_bar: ProgressBar) -> AnyDimension: all_values = [ len(f"{c.items_completed / c.time_elapsed.total_seconds():.2f}") for c in progress_bar.counters @@ -337,8 +339,8 @@ class SpinningWheel(Formatter): def format( self, - progress_bar: "ProgressBar", - progress: "ProgressBarCounter[object]", + progress_bar: ProgressBar, + progress: ProgressBarCounter[object], width: int, ) -> AnyFormattedText: index = int(time.time() * 3) % len(self.characters) @@ -346,11 +348,11 @@ def format( self.characters[index] ) - def get_width(self, progress_bar: "ProgressBar") -> AnyDimension: + def get_width(self, progress_bar: ProgressBar) -> AnyDimension: return D.exact(1) -def _hue_to_rgb(hue: float) -> Tuple[int, int, int]: +def _hue_to_rgb(hue: float) -> tuple[int, int, int]: """ Take hue between 0 and 1, return (r, g, b). """ @@ -384,8 +386,8 @@ def __init__(self, formatter: Formatter) -> None: def format( self, - progress_bar: "ProgressBar", - progress: "ProgressBarCounter[object]", + progress_bar: ProgressBar, + progress: ProgressBarCounter[object], width: int, ) -> AnyFormattedText: # Get formatted text from nested formatter, and explode it in @@ -403,11 +405,11 @@ def format( ) return result2 - def get_width(self, progress_bar: "ProgressBar") -> AnyDimension: + def get_width(self, progress_bar: ProgressBar) -> AnyDimension: return self.formatter.get_width(progress_bar) -def create_default_formatters() -> List[Formatter]: +def create_default_formatters() -> list[Formatter]: """ Return the list of default formatters. """ diff --git a/src/prompt_toolkit/shortcuts/prompt.py b/src/prompt_toolkit/shortcuts/prompt.py index f17676c57a..b6f3b52470 100644 --- a/src/prompt_toolkit/shortcuts/prompt.py +++ b/src/prompt_toolkit/shortcuts/prompt.py @@ -24,6 +24,9 @@ s = PromptSession() result = s.prompt('Say something: ') """ +from __future__ import annotations + +from asyncio import get_running_loop from contextlib import contextmanager from enum import Enum from functools import partial @@ -53,7 +56,6 @@ ) from prompt_toolkit.document import Document from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER, EditingMode -from prompt_toolkit.eventloop import get_event_loop from prompt_toolkit.filters import ( Condition, FilterOrBool, @@ -154,7 +156,7 @@ def _split_multiline_prompt( get_prompt_text: _StyleAndTextTuplesCallable, -) -> Tuple[ +) -> tuple[ Callable[[], bool], _StyleAndTextTuplesCallable, _StyleAndTextTuplesCallable ]: """ @@ -386,37 +388,37 @@ def __init__( validate_while_typing: FilterOrBool = True, enable_history_search: FilterOrBool = False, search_ignore_case: FilterOrBool = False, - lexer: Optional[Lexer] = None, + lexer: Lexer | None = None, enable_system_prompt: FilterOrBool = False, enable_suspend: FilterOrBool = False, enable_open_in_editor: FilterOrBool = False, - validator: Optional[Validator] = None, - completer: Optional[Completer] = None, + validator: Validator | None = None, + completer: Completer | None = None, complete_in_thread: bool = False, reserve_space_for_menu: int = 8, complete_style: CompleteStyle = CompleteStyle.COLUMN, - auto_suggest: Optional[AutoSuggest] = None, - style: Optional[BaseStyle] = None, - style_transformation: Optional[StyleTransformation] = None, + auto_suggest: AutoSuggest | None = None, + style: BaseStyle | None = None, + style_transformation: StyleTransformation | None = None, swap_light_and_dark_colors: FilterOrBool = False, - color_depth: Optional[ColorDepth] = None, + color_depth: ColorDepth | None = None, cursor: AnyCursorShapeConfig = None, include_default_pygments_style: FilterOrBool = True, - history: Optional[History] = None, - clipboard: Optional[Clipboard] = None, - prompt_continuation: Optional[PromptContinuationText] = None, + history: History | None = None, + clipboard: Clipboard | None = None, + prompt_continuation: PromptContinuationText | None = None, rprompt: AnyFormattedText = None, bottom_toolbar: AnyFormattedText = None, mouse_support: FilterOrBool = False, - input_processors: Optional[List[Processor]] = None, - placeholder: Optional[AnyFormattedText] = None, - key_bindings: Optional[KeyBindingsBase] = None, + input_processors: list[Processor] | None = None, + placeholder: AnyFormattedText | None = None, + key_bindings: KeyBindingsBase | None = None, erase_when_done: bool = False, - tempfile_suffix: Optional[Union[str, Callable[[], str]]] = ".txt", - tempfile: Optional[Union[str, Callable[[], str]]] = None, + tempfile_suffix: str | Callable[[], str] | None = ".txt", + tempfile: str | Callable[[], str] | None = None, refresh_interval: float = 0, - input: Optional[Input] = None, - output: Optional[Output] = None, + input: Input | None = None, + output: Output | None = None, ) -> None: history = history or InMemoryHistory() clipboard = clipboard or InMemoryClipboard() @@ -854,50 +856,50 @@ def prompt( self, # When any of these arguments are passed, this value is overwritten # in this PromptSession. - message: Optional[AnyFormattedText] = None, + message: AnyFormattedText | None = None, # `message` should go first, because people call it as # positional argument. *, - editing_mode: Optional[EditingMode] = None, - refresh_interval: Optional[float] = None, - vi_mode: Optional[bool] = None, - lexer: Optional[Lexer] = None, - completer: Optional[Completer] = None, - complete_in_thread: Optional[bool] = None, - is_password: Optional[bool] = None, - key_bindings: Optional[KeyBindingsBase] = None, - bottom_toolbar: Optional[AnyFormattedText] = None, - style: Optional[BaseStyle] = None, - color_depth: Optional[ColorDepth] = None, - cursor: Optional[AnyCursorShapeConfig] = None, - include_default_pygments_style: Optional[FilterOrBool] = None, - style_transformation: Optional[StyleTransformation] = None, - swap_light_and_dark_colors: Optional[FilterOrBool] = None, - rprompt: Optional[AnyFormattedText] = None, - multiline: Optional[FilterOrBool] = None, - prompt_continuation: Optional[PromptContinuationText] = None, - wrap_lines: Optional[FilterOrBool] = None, - enable_history_search: Optional[FilterOrBool] = None, - search_ignore_case: Optional[FilterOrBool] = None, - complete_while_typing: Optional[FilterOrBool] = None, - validate_while_typing: Optional[FilterOrBool] = None, - complete_style: Optional[CompleteStyle] = None, - auto_suggest: Optional[AutoSuggest] = None, - validator: Optional[Validator] = None, - clipboard: Optional[Clipboard] = None, - mouse_support: Optional[FilterOrBool] = None, - input_processors: Optional[List[Processor]] = None, - placeholder: Optional[AnyFormattedText] = None, - reserve_space_for_menu: Optional[int] = None, - enable_system_prompt: Optional[FilterOrBool] = None, - enable_suspend: Optional[FilterOrBool] = None, - enable_open_in_editor: Optional[FilterOrBool] = None, - tempfile_suffix: Optional[Union[str, Callable[[], str]]] = None, - tempfile: Optional[Union[str, Callable[[], str]]] = None, + editing_mode: EditingMode | None = None, + refresh_interval: float | None = None, + vi_mode: bool | None = None, + lexer: Lexer | None = None, + completer: Completer | None = None, + complete_in_thread: bool | None = None, + is_password: bool | None = None, + key_bindings: KeyBindingsBase | None = None, + bottom_toolbar: AnyFormattedText | None = None, + style: BaseStyle | None = None, + color_depth: ColorDepth | None = None, + cursor: AnyCursorShapeConfig | None = None, + include_default_pygments_style: FilterOrBool | None = None, + style_transformation: StyleTransformation | None = None, + swap_light_and_dark_colors: FilterOrBool | None = None, + rprompt: AnyFormattedText | None = None, + multiline: FilterOrBool | None = None, + prompt_continuation: PromptContinuationText | None = None, + wrap_lines: FilterOrBool | None = None, + enable_history_search: FilterOrBool | None = None, + search_ignore_case: FilterOrBool | None = None, + complete_while_typing: FilterOrBool | None = None, + validate_while_typing: FilterOrBool | None = None, + complete_style: CompleteStyle | None = None, + auto_suggest: AutoSuggest | None = None, + validator: Validator | None = None, + clipboard: Clipboard | None = None, + mouse_support: FilterOrBool | None = None, + input_processors: list[Processor] | None = None, + placeholder: AnyFormattedText | None = None, + reserve_space_for_menu: int | None = None, + enable_system_prompt: FilterOrBool | None = None, + enable_suspend: FilterOrBool | None = None, + enable_open_in_editor: FilterOrBool | None = None, + tempfile_suffix: str | Callable[[], str] | None = None, + tempfile: str | Callable[[], str] | None = None, # Following arguments are specific to the current `prompt()` call. - default: Union[str, Document] = "", + default: str | Document = "", accept_default: bool = False, - pre_run: Optional[Callable[[], None]] = None, + pre_run: Callable[[], None] | None = None, set_exception_handler: bool = True, handle_sigint: bool = True, in_thread: bool = False, @@ -1089,50 +1091,50 @@ async def prompt_async( self, # When any of these arguments are passed, this value is overwritten # in this PromptSession. - message: Optional[AnyFormattedText] = None, + message: AnyFormattedText | None = None, # `message` should go first, because people call it as # positional argument. *, - editing_mode: Optional[EditingMode] = None, - refresh_interval: Optional[float] = None, - vi_mode: Optional[bool] = None, - lexer: Optional[Lexer] = None, - completer: Optional[Completer] = None, - complete_in_thread: Optional[bool] = None, - is_password: Optional[bool] = None, - key_bindings: Optional[KeyBindingsBase] = None, - bottom_toolbar: Optional[AnyFormattedText] = None, - style: Optional[BaseStyle] = None, - color_depth: Optional[ColorDepth] = None, - cursor: Optional[CursorShapeConfig] = None, - include_default_pygments_style: Optional[FilterOrBool] = None, - style_transformation: Optional[StyleTransformation] = None, - swap_light_and_dark_colors: Optional[FilterOrBool] = None, - rprompt: Optional[AnyFormattedText] = None, - multiline: Optional[FilterOrBool] = None, - prompt_continuation: Optional[PromptContinuationText] = None, - wrap_lines: Optional[FilterOrBool] = None, - enable_history_search: Optional[FilterOrBool] = None, - search_ignore_case: Optional[FilterOrBool] = None, - complete_while_typing: Optional[FilterOrBool] = None, - validate_while_typing: Optional[FilterOrBool] = None, - complete_style: Optional[CompleteStyle] = None, - auto_suggest: Optional[AutoSuggest] = None, - validator: Optional[Validator] = None, - clipboard: Optional[Clipboard] = None, - mouse_support: Optional[FilterOrBool] = None, - input_processors: Optional[List[Processor]] = None, - placeholder: Optional[AnyFormattedText] = None, - reserve_space_for_menu: Optional[int] = None, - enable_system_prompt: Optional[FilterOrBool] = None, - enable_suspend: Optional[FilterOrBool] = None, - enable_open_in_editor: Optional[FilterOrBool] = None, - tempfile_suffix: Optional[Union[str, Callable[[], str]]] = None, - tempfile: Optional[Union[str, Callable[[], str]]] = None, + editing_mode: EditingMode | None = None, + refresh_interval: float | None = None, + vi_mode: bool | None = None, + lexer: Lexer | None = None, + completer: Completer | None = None, + complete_in_thread: bool | None = None, + is_password: bool | None = None, + key_bindings: KeyBindingsBase | None = None, + bottom_toolbar: AnyFormattedText | None = None, + style: BaseStyle | None = None, + color_depth: ColorDepth | None = None, + cursor: CursorShapeConfig | None = None, + include_default_pygments_style: FilterOrBool | None = None, + style_transformation: StyleTransformation | None = None, + swap_light_and_dark_colors: FilterOrBool | None = None, + rprompt: AnyFormattedText | None = None, + multiline: FilterOrBool | None = None, + prompt_continuation: PromptContinuationText | None = None, + wrap_lines: FilterOrBool | None = None, + enable_history_search: FilterOrBool | None = None, + search_ignore_case: FilterOrBool | None = None, + complete_while_typing: FilterOrBool | None = None, + validate_while_typing: FilterOrBool | None = None, + complete_style: CompleteStyle | None = None, + auto_suggest: AutoSuggest | None = None, + validator: Validator | None = None, + clipboard: Clipboard | None = None, + mouse_support: FilterOrBool | None = None, + input_processors: list[Processor] | None = None, + placeholder: AnyFormattedText | None = None, + reserve_space_for_menu: int | None = None, + enable_system_prompt: FilterOrBool | None = None, + enable_suspend: FilterOrBool | None = None, + enable_open_in_editor: FilterOrBool | None = None, + tempfile_suffix: str | Callable[[], str] | None = None, + tempfile: str | Callable[[], str] | None = None, # Following arguments are specific to the current `prompt()` call. - default: Union[str, Document] = "", + default: str | Document = "", accept_default: bool = False, - pre_run: Optional[Callable[[], None]] = None, + pre_run: Callable[[], None] | None = None, set_exception_handler: bool = True, handle_sigint: bool = True, ) -> _T: @@ -1228,7 +1230,7 @@ async def prompt_async( ) def _add_pre_run_callables( - self, pre_run: Optional[Callable[[], None]], accept_default: bool + self, pre_run: Callable[[], None] | None, accept_default: bool ) -> None: def pre_run2() -> None: if pre_run: @@ -1239,7 +1241,7 @@ def pre_run2() -> None: # order to run it "soon" (during the next iteration of the # event loop), instead of right now. Otherwise, it won't # display the default value. - get_event_loop().call_soon(self.default_buffer.validate_and_handle) + get_running_loop().call_soon(self.default_buffer.validate_and_handle) self.app.pre_run_callables.append(pre_run2) @@ -1363,49 +1365,49 @@ def output(self) -> Output: def prompt( - message: Optional[AnyFormattedText] = None, + message: AnyFormattedText | None = None, *, - history: Optional[History] = None, - editing_mode: Optional[EditingMode] = None, - refresh_interval: Optional[float] = None, - vi_mode: Optional[bool] = None, - lexer: Optional[Lexer] = None, - completer: Optional[Completer] = None, - complete_in_thread: Optional[bool] = None, - is_password: Optional[bool] = None, - key_bindings: Optional[KeyBindingsBase] = None, - bottom_toolbar: Optional[AnyFormattedText] = None, - style: Optional[BaseStyle] = None, - color_depth: Optional[ColorDepth] = None, + history: History | None = None, + editing_mode: EditingMode | None = None, + refresh_interval: float | None = None, + vi_mode: bool | None = None, + lexer: Lexer | None = None, + completer: Completer | None = None, + complete_in_thread: bool | None = None, + is_password: bool | None = None, + key_bindings: KeyBindingsBase | None = None, + bottom_toolbar: AnyFormattedText | None = None, + style: BaseStyle | None = None, + color_depth: ColorDepth | None = None, cursor: AnyCursorShapeConfig = None, - include_default_pygments_style: Optional[FilterOrBool] = None, - style_transformation: Optional[StyleTransformation] = None, - swap_light_and_dark_colors: Optional[FilterOrBool] = None, - rprompt: Optional[AnyFormattedText] = None, - multiline: Optional[FilterOrBool] = None, - prompt_continuation: Optional[PromptContinuationText] = None, - wrap_lines: Optional[FilterOrBool] = None, - enable_history_search: Optional[FilterOrBool] = None, - search_ignore_case: Optional[FilterOrBool] = None, - complete_while_typing: Optional[FilterOrBool] = None, - validate_while_typing: Optional[FilterOrBool] = None, - complete_style: Optional[CompleteStyle] = None, - auto_suggest: Optional[AutoSuggest] = None, - validator: Optional[Validator] = None, - clipboard: Optional[Clipboard] = None, - mouse_support: Optional[FilterOrBool] = None, - input_processors: Optional[List[Processor]] = None, - placeholder: Optional[AnyFormattedText] = None, - reserve_space_for_menu: Optional[int] = None, - enable_system_prompt: Optional[FilterOrBool] = None, - enable_suspend: Optional[FilterOrBool] = None, - enable_open_in_editor: Optional[FilterOrBool] = None, - tempfile_suffix: Optional[Union[str, Callable[[], str]]] = None, - tempfile: Optional[Union[str, Callable[[], str]]] = None, + include_default_pygments_style: FilterOrBool | None = None, + style_transformation: StyleTransformation | None = None, + swap_light_and_dark_colors: FilterOrBool | None = None, + rprompt: AnyFormattedText | None = None, + multiline: FilterOrBool | None = None, + prompt_continuation: PromptContinuationText | None = None, + wrap_lines: FilterOrBool | None = None, + enable_history_search: FilterOrBool | None = None, + search_ignore_case: FilterOrBool | None = None, + complete_while_typing: FilterOrBool | None = None, + validate_while_typing: FilterOrBool | None = None, + complete_style: CompleteStyle | None = None, + auto_suggest: AutoSuggest | None = None, + validator: Validator | None = None, + clipboard: Clipboard | None = None, + mouse_support: FilterOrBool | None = None, + input_processors: list[Processor] | None = None, + placeholder: AnyFormattedText | None = None, + reserve_space_for_menu: int | None = None, + enable_system_prompt: FilterOrBool | None = None, + enable_suspend: FilterOrBool | None = None, + enable_open_in_editor: FilterOrBool | None = None, + tempfile_suffix: str | Callable[[], str] | None = None, + tempfile: str | Callable[[], str] | None = None, # Following arguments are specific to the current `prompt()` call. default: str = "", accept_default: bool = False, - pre_run: Optional[Callable[[], None]] = None, + pre_run: Callable[[], None] | None = None, ) -> str: """ The global `prompt` function. This will create a new `PromptSession` diff --git a/src/prompt_toolkit/shortcuts/utils.py b/src/prompt_toolkit/shortcuts/utils.py index 38307ff6a4..e71718a9cb 100644 --- a/src/prompt_toolkit/shortcuts/utils.py +++ b/src/prompt_toolkit/shortcuts/utils.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from asyncio.events import AbstractEventLoop from typing import TYPE_CHECKING, Any, Optional, TextIO @@ -40,12 +42,12 @@ def print_formatted_text( *values: Any, sep: str = " ", end: str = "\n", - file: Optional[TextIO] = None, + file: TextIO | None = None, flush: bool = False, - style: Optional[BaseStyle] = None, - output: Optional[Output] = None, - color_depth: Optional[ColorDepth] = None, - style_transformation: Optional[StyleTransformation] = None, + style: BaseStyle | None = None, + output: Output | None = None, + color_depth: ColorDepth | None = None, + style_transformation: StyleTransformation | None = None, include_default_pygments_style: bool = True, ) -> None: """ @@ -150,7 +152,7 @@ def render() -> None: # If an application is running, print above the app. This does not require # `patch_stdout`. - loop: Optional[AbstractEventLoop] = None + loop: AbstractEventLoop | None = None app = get_app_or_none() if app is not None: @@ -163,9 +165,9 @@ def render() -> None: def print_container( - container: "AnyContainer", - file: Optional[TextIO] = None, - style: Optional[BaseStyle] = None, + container: AnyContainer, + file: TextIO | None = None, + style: BaseStyle | None = None, include_default_pygments_style: bool = True, ) -> None: """ @@ -198,7 +200,7 @@ def print_container( def _create_merged_style( - style: Optional[BaseStyle], include_default_pygments_style: bool + style: BaseStyle | None, include_default_pygments_style: bool ) -> BaseStyle: """ Merge user defined style with built-in style. diff --git a/src/prompt_toolkit/styles/__init__.py b/src/prompt_toolkit/styles/__init__.py index c270ae01bb..23f61bb0f6 100644 --- a/src/prompt_toolkit/styles/__init__.py +++ b/src/prompt_toolkit/styles/__init__.py @@ -1,6 +1,8 @@ """ Styling for prompt_toolkit applications. """ +from __future__ import annotations + from .base import ( ANSI_COLOR_NAMES, DEFAULT_ATTRS, diff --git a/src/prompt_toolkit/styles/base.py b/src/prompt_toolkit/styles/base.py index aa77b9ad08..cb959ce2cc 100644 --- a/src/prompt_toolkit/styles/base.py +++ b/src/prompt_toolkit/styles/base.py @@ -1,6 +1,8 @@ """ The base classes for the styling. """ +from __future__ import annotations + from abc import ABCMeta, abstractmethod, abstractproperty from typing import Callable, Dict, Hashable, List, NamedTuple, Optional, Tuple @@ -17,15 +19,15 @@ #: Style attributes. class Attrs(NamedTuple): - color: Optional[str] - bgcolor: Optional[str] - bold: Optional[bool] - underline: Optional[bool] - strike: Optional[bool] - italic: Optional[bool] - blink: Optional[bool] - reverse: Optional[bool] - hidden: Optional[bool] + color: str | None + bgcolor: str | None + bold: bool | None + underline: bool | None + strike: bool | None + italic: bool | None + blink: bool | None + reverse: bool | None + hidden: bool | None """ @@ -87,7 +89,7 @@ class Attrs(NamedTuple): # Pygments). This is fixed now, but we still support the old names. # The table below maps the old aliases to the current names. -ANSI_COLOR_NAMES_ALIASES: Dict[str, str] = { +ANSI_COLOR_NAMES_ALIASES: dict[str, str] = { "ansidarkgray": "ansibrightblack", "ansiteal": "ansicyan", "ansiturquoise": "ansibrightcyan", @@ -121,7 +123,7 @@ def get_attrs_for_style_str( """ @abstractproperty - def style_rules(self) -> List[Tuple[str, str]]: + def style_rules(self) -> list[tuple[str, str]]: """ The list of style rules, used to create this style. (Required for `DynamicStyle` and `_MergedStyle` to work.) @@ -151,7 +153,7 @@ def invalidation_hash(self) -> Hashable: return 1 # Always the same value. @property - def style_rules(self) -> List[Tuple[str, str]]: + def style_rules(self) -> list[tuple[str, str]]: return [] @@ -162,7 +164,7 @@ class DynamicStyle(BaseStyle): :param get_style: Callable that returns a :class:`.Style` instance. """ - def __init__(self, get_style: Callable[[], Optional[BaseStyle]]): + def __init__(self, get_style: Callable[[], BaseStyle | None]): self.get_style = get_style self._dummy = DummyStyle() @@ -177,5 +179,5 @@ def invalidation_hash(self) -> Hashable: return (self.get_style() or self._dummy).invalidation_hash() @property - def style_rules(self) -> List[Tuple[str, str]]: + def style_rules(self) -> list[tuple[str, str]]: return (self.get_style() or self._dummy).style_rules diff --git a/src/prompt_toolkit/styles/defaults.py b/src/prompt_toolkit/styles/defaults.py index 4ac554562c..a33b32f127 100644 --- a/src/prompt_toolkit/styles/defaults.py +++ b/src/prompt_toolkit/styles/defaults.py @@ -1,6 +1,8 @@ """ The default styling. """ +from __future__ import annotations + from prompt_toolkit.cache import memoized from .base import ANSI_COLOR_NAMES, BaseStyle diff --git a/src/prompt_toolkit/styles/named_colors.py b/src/prompt_toolkit/styles/named_colors.py index 37bd6de446..33df47bbb8 100644 --- a/src/prompt_toolkit/styles/named_colors.py +++ b/src/prompt_toolkit/styles/named_colors.py @@ -2,6 +2,8 @@ All modern web browsers support these 140 color names. Taken from: https://www.w3schools.com/colors/colors_names.asp """ +from __future__ import annotations + from typing import Dict __all__ = [ @@ -9,7 +11,7 @@ ] -NAMED_COLORS: Dict[str, str] = { +NAMED_COLORS: dict[str, str] = { "AliceBlue": "#f0f8ff", "AntiqueWhite": "#faebd7", "Aqua": "#00ffff", diff --git a/src/prompt_toolkit/styles/pygments.py b/src/prompt_toolkit/styles/pygments.py index 382e5e315b..a7e5f8af81 100644 --- a/src/prompt_toolkit/styles/pygments.py +++ b/src/prompt_toolkit/styles/pygments.py @@ -6,6 +6,8 @@ from pygments.styles.tango import TangoStyle style = style_from_pygments_cls(pygments_style_cls=TangoStyle) """ +from __future__ import annotations + from typing import TYPE_CHECKING, Dict, Type from .style import Style @@ -22,7 +24,7 @@ ] -def style_from_pygments_cls(pygments_style_cls: Type["PygmentsStyle"]) -> Style: +def style_from_pygments_cls(pygments_style_cls: type[PygmentsStyle]) -> Style: """ Shortcut to create a :class:`.Style` instance from a Pygments style class and a style dictionary. @@ -43,7 +45,7 @@ def style_from_pygments_cls(pygments_style_cls: Type["PygmentsStyle"]) -> Style: return style_from_pygments_dict(pygments_style_cls.styles) -def style_from_pygments_dict(pygments_dict: Dict["Token", str]) -> Style: +def style_from_pygments_dict(pygments_dict: dict[Token, str]) -> Style: """ Create a :class:`.Style` instance from a Pygments style dictionary. (One that maps Token objects to style strings.) @@ -56,7 +58,7 @@ def style_from_pygments_dict(pygments_dict: Dict["Token", str]) -> Style: return Style(pygments_style) -def pygments_token_to_classname(token: "Token") -> str: +def pygments_token_to_classname(token: Token) -> str: """ Turn e.g. `Token.Name.Exception` into `'pygments.name.exception'`. diff --git a/src/prompt_toolkit/styles/style.py b/src/prompt_toolkit/styles/style.py index 87f6e3e9b5..2a44063700 100644 --- a/src/prompt_toolkit/styles/style.py +++ b/src/prompt_toolkit/styles/style.py @@ -1,6 +1,8 @@ """ Tool for creating styles from a dictionary. """ +from __future__ import annotations + import itertools import re from enum import Enum @@ -88,7 +90,7 @@ def parse_color(text: str) -> str: ) -def _expand_classname(classname: str) -> List[str]: +def _expand_classname(classname: str) -> list[str]: """ Split a single class name at the `.` operator, and build a list of classes. @@ -222,7 +224,7 @@ class Style(BaseStyle): The ``from_dict`` classmethod is similar, but takes a dictionary as input. """ - def __init__(self, style_rules: List[Tuple[str, str]]) -> None: + def __init__(self, style_rules: list[tuple[str, str]]) -> None: class_names_and_attrs = [] # Loop through the rules in the order they were defined. @@ -241,20 +243,20 @@ def __init__(self, style_rules: List[Tuple[str, str]]) -> None: self.class_names_and_attrs = class_names_and_attrs @property - def style_rules(self) -> List[Tuple[str, str]]: + def style_rules(self) -> list[tuple[str, str]]: return self._style_rules @classmethod def from_dict( - cls, style_dict: Dict[str, str], priority: Priority = default_priority - ) -> "Style": + cls, style_dict: dict[str, str], priority: Priority = default_priority + ) -> Style: """ :param style_dict: Style dictionary. :param priority: `Priority` value. """ if priority == Priority.MOST_PRECISE: - def key(item: Tuple[str, str]) -> int: + def key(item: tuple[str, str]) -> int: # Split on '.' and whitespace. Count elements. return sum(len(i.split(".")) for i in item[0].split()) @@ -269,7 +271,7 @@ def get_attrs_for_style_str( Get `Attrs` for the given style string. """ list_of_attrs = [default] - class_names: Set[str] = set() + class_names: set[str] = set() # Apply default styling. for names, attr in self.class_names_and_attrs: @@ -318,7 +320,7 @@ def invalidation_hash(self) -> Hashable: _T = TypeVar("_T") -def _merge_attrs(list_of_attrs: List[Attrs]) -> Attrs: +def _merge_attrs(list_of_attrs: list[Attrs]) -> Attrs: """ Take a list of :class:`.Attrs` instances and merge them into one. Every `Attr` in the list can override the styling of the previous one. So, @@ -345,7 +347,7 @@ def _or(*values: _T) -> _T: ) -def merge_styles(styles: List[BaseStyle]) -> "_MergedStyle": +def merge_styles(styles: list[BaseStyle]) -> _MergedStyle: """ Merge multiple `Style` objects. """ @@ -369,7 +371,7 @@ class _MergedStyle(BaseStyle): # the next style specified a default color for any text. (The # explicit styling of class:aborted should have taken priority, # because it was more precise.) - def __init__(self, styles: List[BaseStyle]) -> None: + def __init__(self, styles: list[BaseStyle]) -> None: self.styles = styles self._style: SimpleCache[Hashable, Style] = SimpleCache(maxsize=1) @@ -383,7 +385,7 @@ def get() -> Style: return self._style.get(self.invalidation_hash(), get) @property - def style_rules(self) -> List[Tuple[str, str]]: + def style_rules(self) -> list[tuple[str, str]]: style_rules = [] for s in self.styles: style_rules.extend(s.style_rules) diff --git a/src/prompt_toolkit/styles/style_transformation.py b/src/prompt_toolkit/styles/style_transformation.py index 3010d0cfff..16bf8b0d7e 100644 --- a/src/prompt_toolkit/styles/style_transformation.py +++ b/src/prompt_toolkit/styles/style_transformation.py @@ -9,6 +9,8 @@ style strings are turned into `Attrs` objects that represent the actual formatting. """ +from __future__ import annotations + from abc import ABCMeta, abstractmethod from colorsys import hls_to_rgb, rgb_to_hls from typing import Callable, Hashable, Optional, Sequence, Tuple, Union @@ -105,7 +107,7 @@ class SetDefaultColorStyleTransformation(StyleTransformation): """ def __init__( - self, fg: Union[str, Callable[[], str]], bg: Union[str, Callable[[], str]] + self, fg: str | Callable[[], str], bg: str | Callable[[], str] ) -> None: self.fg = fg self.bg = bg @@ -186,7 +188,7 @@ def transform_attrs(self, attrs: Attrs) -> Attrs: return attrs - def _color_to_rgb(self, color: str) -> Tuple[float, float, float]: + def _color_to_rgb(self, color: str) -> tuple[float, float, float]: """ Parse `style.Attrs` color into RGB tuple. """ @@ -248,7 +250,7 @@ class DynamicStyleTransformation(StyleTransformation): """ def __init__( - self, get_style_transformation: Callable[[], Optional[StyleTransformation]] + self, get_style_transformation: Callable[[], StyleTransformation | None] ) -> None: self.get_style_transformation = get_style_transformation @@ -334,7 +336,7 @@ def merge_style_transformations( @memoized() -def get_opposite_color(colorname: Optional[str]) -> Optional[str]: +def get_opposite_color(colorname: str | None) -> str | None: """ Take a color name in either 'ansi...' format or 6 digit RGB, return the color of opposite luminosity (same hue/saturation). diff --git a/src/prompt_toolkit/token.py b/src/prompt_toolkit/token.py index 76fd9c46cd..a2c80e54c5 100644 --- a/src/prompt_toolkit/token.py +++ b/src/prompt_toolkit/token.py @@ -1,6 +1,8 @@ """ """ +from __future__ import annotations + __all__ = [ "ZeroWidthEscape", ] diff --git a/src/prompt_toolkit/utils.py b/src/prompt_toolkit/utils.py index 4ceded34c4..d372df871e 100644 --- a/src/prompt_toolkit/utils.py +++ b/src/prompt_toolkit/utils.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os import signal import sys @@ -65,10 +67,10 @@ def handler(sender): """ def __init__( - self, sender: _Sender, handler: Optional[Callable[[_Sender], None]] = None + self, sender: _Sender, handler: Callable[[_Sender], None] | None = None ) -> None: self.sender = sender - self._handlers: List[Callable[[_Sender], None]] = [] + self._handlers: list[Callable[[_Sender], None]] = [] if handler is not None: self += handler @@ -98,14 +100,14 @@ def remove_handler(self, handler: Callable[[_Sender], None]) -> None: if handler in self._handlers: self._handlers.remove(handler) - def __iadd__(self, handler: Callable[[_Sender], None]) -> "Event[_Sender]": + def __iadd__(self, handler: Callable[[_Sender], None]) -> Event[_Sender]: """ `event += handler` notation for adding a handler. """ self.add_handler(handler) return self - def __isub__(self, handler: Callable[[_Sender], None]) -> "Event[_Sender]": + def __isub__(self, handler: Callable[[_Sender], None]) -> Event[_Sender]: """ `event -= handler` notation for removing a handler. """ @@ -235,7 +237,7 @@ def get_term_environment_variable() -> str: def take_using_weights( - items: List[_T], weights: List[int] + items: list[_T], weights: list[int] ) -> Generator[_T, None, None]: """ Generator that keeps yielding items from the items list, in proportion to @@ -288,7 +290,7 @@ def take_using_weights( i += 1 -def to_str(value: Union[Callable[[], str], str]) -> str: +def to_str(value: Callable[[], str] | str) -> str: "Turn callable or string into string." if callable(value): return to_str(value()) @@ -296,7 +298,7 @@ def to_str(value: Union[Callable[[], str], str]) -> str: return str(value) -def to_int(value: Union[Callable[[], int], int]) -> int: +def to_int(value: Callable[[], int] | int) -> int: "Turn callable or int into int." if callable(value): return to_int(value()) @@ -315,7 +317,7 @@ def to_float(value: AnyFloat) -> float: return float(value) -def is_dumb_terminal(term: Optional[str] = None) -> bool: +def is_dumb_terminal(term: str | None = None) -> bool: """ True if this terminal type is considered "dumb". diff --git a/src/prompt_toolkit/validation.py b/src/prompt_toolkit/validation.py index d95aa48c57..611b340f4a 100644 --- a/src/prompt_toolkit/validation.py +++ b/src/prompt_toolkit/validation.py @@ -2,6 +2,8 @@ Input validation for a `Buffer`. (Validators will be called before accepting input.) """ +from __future__ import annotations + from abc import ABCMeta, abstractmethod from typing import Callable, Optional @@ -81,7 +83,7 @@ def from_callable( validate_func: Callable[[str], bool], error_message: str = "Invalid input", move_cursor_to_end: bool = False, - ) -> "Validator": + ) -> Validator: """ Create a validator from a simple validate callable. E.g.: @@ -181,7 +183,7 @@ class DynamicValidator(Validator): :param get_validator: Callable that returns a :class:`.Validator` instance. """ - def __init__(self, get_validator: Callable[[], Optional[Validator]]) -> None: + def __init__(self, get_validator: Callable[[], Validator | None]) -> None: self.get_validator = get_validator def validate(self, document: Document) -> None: diff --git a/src/prompt_toolkit/widgets/__init__.py b/src/prompt_toolkit/widgets/__init__.py index 552d355948..9d1d4e3dee 100644 --- a/src/prompt_toolkit/widgets/__init__.py +++ b/src/prompt_toolkit/widgets/__init__.py @@ -6,6 +6,8 @@ Most of these widgets implement the ``__pt_container__`` method, which makes it possible to embed these in the layout like any other container. """ +from __future__ import annotations + from .base import ( Box, Button, diff --git a/src/prompt_toolkit/widgets/base.py b/src/prompt_toolkit/widgets/base.py index d2c2af1741..19c4b8387f 100644 --- a/src/prompt_toolkit/widgets/base.py +++ b/src/prompt_toolkit/widgets/base.py @@ -12,6 +12,8 @@ guarantees are made yet). The public API in `prompt_toolkit.shortcuts.dialogs` on the other hand is considered stable. """ +from __future__ import annotations + from functools import partial from typing import Callable, Generic, List, Optional, Sequence, Tuple, TypeVar, Union @@ -172,13 +174,13 @@ def __init__( text: str = "", multiline: FilterOrBool = True, password: FilterOrBool = False, - lexer: Optional[Lexer] = None, - auto_suggest: Optional[AutoSuggest] = None, - completer: Optional[Completer] = None, + lexer: Lexer | None = None, + auto_suggest: AutoSuggest | None = None, + completer: Completer | None = None, complete_while_typing: FilterOrBool = True, - validator: Optional[Validator] = None, - accept_handler: Optional[BufferAcceptHandler] = None, - history: Optional[History] = None, + validator: Validator | None = None, + accept_handler: BufferAcceptHandler | None = None, + history: History | None = None, focusable: FilterOrBool = True, focus_on_click: FilterOrBool = False, wrap_lines: FilterOrBool = True, @@ -188,13 +190,13 @@ def __init__( dont_extend_height: FilterOrBool = False, dont_extend_width: FilterOrBool = False, line_numbers: bool = False, - get_line_prefix: Optional[GetLinePrefixCallable] = None, + get_line_prefix: GetLinePrefixCallable | None = None, scrollbar: bool = False, style: str = "", - search_field: Optional[SearchToolbar] = None, + search_field: SearchToolbar | None = None, preview_search: FilterOrBool = True, prompt: AnyFormattedText = "", - input_processors: Optional[List[Processor]] = None, + input_processors: list[Processor] | None = None, name: str = "", ) -> None: if search_field is None: @@ -304,7 +306,7 @@ def document(self, value: Document) -> None: self.buffer.set_document(value, bypass_readonly=True) @property - def accept_handler(self) -> Optional[BufferAcceptHandler]: + def accept_handler(self) -> BufferAcceptHandler | None: """ The accept handler. Called when the user accepts the input. """ @@ -344,7 +346,7 @@ def __init__( width: AnyDimension = None, dont_extend_height: bool = True, dont_extend_width: bool = False, - align: Union[WindowAlign, Callable[[], WindowAlign]] = WindowAlign.LEFT, + align: WindowAlign | Callable[[], WindowAlign] = WindowAlign.LEFT, # There is no cursor navigation in a label, so it makes sense to always # wrap lines by default. wrap_lines: FilterOrBool = True, @@ -394,7 +396,7 @@ class Button: def __init__( self, text: str, - handler: Optional[Callable[[], None]] = None, + handler: Callable[[], None] | None = None, width: int = 12, left_symbol: str = "<", right_symbol: str = ">", @@ -487,7 +489,7 @@ def __init__( style: str = "", width: AnyDimension = None, height: AnyDimension = None, - key_bindings: Optional[KeyBindings] = None, + key_bindings: KeyBindings | None = None, modal: bool = False, ) -> None: self.title = title @@ -629,9 +631,9 @@ def __init__( width: AnyDimension = None, height: AnyDimension = None, style: str = "", - char: Union[None, str, Callable[[], str]] = None, + char: None | str | Callable[[], str] = None, modal: bool = False, - key_bindings: Optional[KeyBindings] = None, + key_bindings: KeyBindings | None = None, ) -> None: if padding is None: padding = D(preferred=0) @@ -689,8 +691,8 @@ class _DialogList(Generic[_T]): def __init__( self, - values: Sequence[Tuple[_T, AnyFormattedText]], - default_values: Optional[Sequence[_T]] = None, + values: Sequence[tuple[_T, AnyFormattedText]], + default_values: Sequence[_T] | None = None, ) -> None: assert len(values) > 0 default_values = default_values or [] @@ -698,8 +700,8 @@ def __init__( self.values = values # current_values will be used in multiple_selection, # current_value will be used otherwise. - keys: List[_T] = [value for (value, _) in values] - self.current_values: List[_T] = [ + keys: list[_T] = [value for (value, _) in values] + self.current_values: list[_T] = [ value for value in default_values if value in keys ] self.current_value: _T = ( @@ -852,8 +854,8 @@ class RadioList(_DialogList[_T]): def __init__( self, - values: Sequence[Tuple[_T, AnyFormattedText]], - default: Optional[_T] = None, + values: Sequence[tuple[_T, AnyFormattedText]], + default: _T | None = None, ) -> None: if default is None: default_values = None diff --git a/src/prompt_toolkit/widgets/dialogs.py b/src/prompt_toolkit/widgets/dialogs.py index fab632ad77..0ea0e7e533 100644 --- a/src/prompt_toolkit/widgets/dialogs.py +++ b/src/prompt_toolkit/widgets/dialogs.py @@ -1,6 +1,8 @@ """ Collection of reusable components for building full screen applications. """ +from __future__ import annotations + from typing import Optional, Sequence, Union from prompt_toolkit.filters import has_completions, has_focus @@ -40,7 +42,7 @@ def __init__( self, body: AnyContainer, title: AnyFormattedText = "", - buttons: Optional[Sequence[Button]] = None, + buttons: Sequence[Button] | None = None, modal: bool = True, width: AnyDimension = None, with_background: bool = False, @@ -95,7 +97,7 @@ def __init__( ) ) - self.container: Union[Box, Shadow] + self.container: Box | Shadow if with_background: self.container = Box(body=frame, style="class:dialog", width=width) else: diff --git a/src/prompt_toolkit/widgets/menus.py b/src/prompt_toolkit/widgets/menus.py index 50a7f0d3e4..6d5698641f 100644 --- a/src/prompt_toolkit/widgets/menus.py +++ b/src/prompt_toolkit/widgets/menus.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Callable, Iterable, List, Optional, Sequence, Union from prompt_toolkit.application.current import get_app @@ -39,9 +41,9 @@ class MenuContainer: def __init__( self, body: AnyContainer, - menu_items: List["MenuItem"], - floats: Optional[List[Float]] = None, - key_bindings: Optional[KeyBindingsBase] = None, + menu_items: list[MenuItem], + floats: list[Float] | None = None, + key_bindings: KeyBindingsBase | None = None, ) -> None: self.body = body self.menu_items = menu_items @@ -209,7 +211,7 @@ def has_focus() -> bool: key_bindings=key_bindings, ) - def _get_menu(self, level: int) -> "MenuItem": + def _get_menu(self, level: int) -> MenuItem: menu = self.menu_items[self.selected_menu[0]] for i, index in enumerate(self.selected_menu[1:]): @@ -341,7 +343,7 @@ def mouse_handler(mouse_event: MouseEvent) -> None: return Window(FormattedTextControl(get_text_fragments), style="class:menu") @property - def floats(self) -> Optional[List[Float]]: + def floats(self) -> list[Float] | None: return self.container.floats def __pt_container__(self) -> Container: @@ -352,9 +354,9 @@ class MenuItem: def __init__( self, text: str = "", - handler: Optional[Callable[[], None]] = None, - children: Optional[List["MenuItem"]] = None, - shortcut: Optional[Sequence[Union[Keys, str]]] = None, + handler: Callable[[], None] | None = None, + children: list[MenuItem] | None = None, + shortcut: Sequence[Keys | str] | None = None, disabled: bool = False, ) -> None: self.text = text diff --git a/src/prompt_toolkit/widgets/toolbars.py b/src/prompt_toolkit/widgets/toolbars.py index 1040f43d33..f74c8f9411 100644 --- a/src/prompt_toolkit/widgets/toolbars.py +++ b/src/prompt_toolkit/widgets/toolbars.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Any, Optional from prompt_toolkit.application.current import get_app @@ -214,7 +216,7 @@ class SearchToolbar: def __init__( self, - search_buffer: Optional[Buffer] = None, + search_buffer: Buffer | None = None, vi_mode: bool = False, text_if_not_searching: AnyFormattedText = "", forward_search_prompt: AnyFormattedText = "I-search: ", diff --git a/src/prompt_toolkit/win32_types.py b/src/prompt_toolkit/win32_types.py index 4ae2e393f4..79283b8b06 100644 --- a/src/prompt_toolkit/win32_types.py +++ b/src/prompt_toolkit/win32_types.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from ctypes import Structure, Union, c_char, c_long, c_short, c_ulong from ctypes.wintypes import BOOL, DWORD, LPVOID, WCHAR, WORD from typing import TYPE_CHECKING diff --git a/tests/test_async_generator.py b/tests/test_async_generator.py index fd2c109c95..4a01c0e3d6 100644 --- a/tests/test_async_generator.py +++ b/tests/test_async_generator.py @@ -1,4 +1,8 @@ -from prompt_toolkit.eventloop import generator_to_async_generator, get_event_loop +from __future__ import annotations + +from asyncio import run + +from prompt_toolkit.eventloop import generator_to_async_generator def _sync_generator(): @@ -20,5 +24,5 @@ async def consume_async_generator(): items.append(item) # Run the event loop until all items are collected. - get_event_loop().run_until_complete(consume_async_generator()) + run(consume_async_generator()) assert items == [1, 10] diff --git a/tests/test_buffer.py b/tests/test_buffer.py index 413ec98743..e636137d55 100644 --- a/tests/test_buffer.py +++ b/tests/test_buffer.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from prompt_toolkit.buffer import Buffer diff --git a/tests/test_cli.py b/tests/test_cli.py index 53d1e4f284..39aeedd45d 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -2,6 +2,8 @@ These are almost end-to-end tests. They create a Prompt, feed it with some input and check the result. """ +from __future__ import annotations + from functools import partial import pytest diff --git a/tests/test_completion.py b/tests/test_completion.py index 3dce43cd82..8b3541af0b 100644 --- a/tests/test_completion.py +++ b/tests/test_completion.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os import re import shutil diff --git a/tests/test_document.py b/tests/test_document.py index ba65ffc834..d052d537ce 100644 --- a/tests/test_document.py +++ b/tests/test_document.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from prompt_toolkit.document import Document diff --git a/tests/test_filter.py b/tests/test_filter.py index e47b3b5cfa..0d4ad130dd 100644 --- a/tests/test_filter.py +++ b/tests/test_filter.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from prompt_toolkit.filters import Always, Condition, Filter, Never, to_filter diff --git a/tests/test_formatted_text.py b/tests/test_formatted_text.py index 8b4924f968..2d8e184ade 100644 --- a/tests/test_formatted_text.py +++ b/tests/test_formatted_text.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from prompt_toolkit.formatted_text import ( ANSI, HTML, diff --git a/tests/test_history.py b/tests/test_history.py index 07db8117e6..500b7f119a 100644 --- a/tests/test_history.py +++ b/tests/test_history.py @@ -1,4 +1,7 @@ -from prompt_toolkit.eventloop import get_event_loop +from __future__ import annotations + +from asyncio import run + from prompt_toolkit.history import FileHistory, InMemoryHistory, ThreadedHistory @@ -12,7 +15,7 @@ async def call_load(): async for item in history.load(): result.append(item) - get_event_loop().run_until_complete(call_load()) + run(call_load()) return result diff --git a/tests/test_inputstream.py b/tests/test_inputstream.py index 8c3d8fd7c4..ab1b036893 100644 --- a/tests/test_inputstream.py +++ b/tests/test_inputstream.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from prompt_toolkit.input.vt100_parser import Vt100Parser diff --git a/tests/test_key_binding.py b/tests/test_key_binding.py index 6f03f2deab..1c60880af9 100644 --- a/tests/test_key_binding.py +++ b/tests/test_key_binding.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from contextlib import contextmanager import pytest @@ -36,6 +38,16 @@ def set_dummy_app(): output=DummyOutput(), input=pipe_input, ) + + # Don't start background tasks for these tests. The `KeyProcessor` + # wants to create a background task for flushing keys. We can ignore it + # here for these tests. + # This patch is not clean. In the future, when we can use Taskgroups, + # the `Application` should pass its task group to the constructor of + # `KeyProcessor`. That way, it doesn't have to do a lookup using + # `get_app()`. + app.create_background_task = lambda *_, **kw: None + with set_app(app): yield diff --git a/tests/test_layout.py b/tests/test_layout.py index 8a0826d33b..cbbbcd0bdd 100644 --- a/tests/test_layout.py +++ b/tests/test_layout.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from prompt_toolkit.layout import InvalidLayoutError, Layout diff --git a/tests/test_memory_leaks.py b/tests/test_memory_leaks.py index 0ad392a4bc..31ea7c8ce8 100644 --- a/tests/test_memory_leaks.py +++ b/tests/test_memory_leaks.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import gc import pytest diff --git a/tests/test_print_formatted_text.py b/tests/test_print_formatted_text.py index 6a344a732d..26c7265151 100644 --- a/tests/test_print_formatted_text.py +++ b/tests/test_print_formatted_text.py @@ -1,6 +1,8 @@ """ Test the `print` function. """ +from __future__ import annotations + import pytest from prompt_toolkit import print_formatted_text as pt_print diff --git a/tests/test_regular_languages.py b/tests/test_regular_languages.py index 7404b231a4..deef6b80b4 100644 --- a/tests/test_regular_languages.py +++ b/tests/test_regular_languages.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from prompt_toolkit.completion import CompleteEvent, Completer, Completion from prompt_toolkit.contrib.regular_languages import compile from prompt_toolkit.contrib.regular_languages.compiler import Match, Variables diff --git a/tests/test_shortcuts.py b/tests/test_shortcuts.py index 10ee73a20e..2c0a84b67a 100644 --- a/tests/test_shortcuts.py +++ b/tests/test_shortcuts.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from prompt_toolkit.shortcuts import print_container from prompt_toolkit.shortcuts.prompt import _split_multiline_prompt from prompt_toolkit.shortcuts.utils import print_container @@ -61,7 +63,7 @@ def test_print_container(tmpdir): print_container(Frame(TextArea(text="Hello world!\n"), title="Title"), file=fd) # Verify rendered output. - with open(f, "r") as fd: + with open(f) as fd: text = fd.read() assert "Hello world" in text assert "Title" in text diff --git a/tests/test_style.py b/tests/test_style.py index e78373419e..d0a4790b8a 100644 --- a/tests/test_style.py +++ b/tests/test_style.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from prompt_toolkit.styles import Attrs, Style, SwapLightAndDarkStyleTransformation diff --git a/tests/test_style_transformation.py b/tests/test_style_transformation.py index 1193649e34..e4eee7c7b1 100644 --- a/tests/test_style_transformation.py +++ b/tests/test_style_transformation.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from prompt_toolkit.styles import AdjustBrightnessStyleTransformation, Attrs diff --git a/tests/test_utils.py b/tests/test_utils.py index 6a1c17955c..9d4c808769 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import itertools import pytest diff --git a/tests/test_vt100_output.py b/tests/test_vt100_output.py index f7163da839..65c83779c6 100644 --- a/tests/test_vt100_output.py +++ b/tests/test_vt100_output.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from prompt_toolkit.output.vt100 import _get_closest_ansi_color diff --git a/tests/test_widgets.py b/tests/test_widgets.py index bf89a2581e..1fc8ae4398 100644 --- a/tests/test_widgets.py +++ b/tests/test_widgets.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from prompt_toolkit.formatted_text import fragment_list_to_text from prompt_toolkit.layout import to_window from prompt_toolkit.widgets import Button diff --git a/tests/test_yank_nth_arg.py b/tests/test_yank_nth_arg.py index edd3f2f590..7167a26ab7 100644 --- a/tests/test_yank_nth_arg.py +++ b/tests/test_yank_nth_arg.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from prompt_toolkit.buffer import Buffer