diff options
author | monster <monster@ydb.tech> | 2022-07-07 14:41:37 +0300 |
---|---|---|
committer | monster <monster@ydb.tech> | 2022-07-07 14:41:37 +0300 |
commit | 06e5c21a835c0e923506c4ff27929f34e00761c2 (patch) | |
tree | 75efcbc6854ef9bd476eb8bf00cc5c900da436a2 /contrib/python/prompt-toolkit/py3 | |
parent | 03f024c4412e3aa613bb543cf1660176320ba8f4 (diff) | |
download | ydb-06e5c21a835c0e923506c4ff27929f34e00761c2.tar.gz |
fix ya.make
Diffstat (limited to 'contrib/python/prompt-toolkit/py3')
173 files changed, 0 insertions, 43991 deletions
diff --git a/contrib/python/prompt-toolkit/py3/.dist-info/METADATA b/contrib/python/prompt-toolkit/py3/.dist-info/METADATA deleted file mode 100644 index 16bec365bb2..00000000000 --- a/contrib/python/prompt-toolkit/py3/.dist-info/METADATA +++ /dev/null @@ -1,186 +0,0 @@ -Metadata-Version: 2.1 -Name: prompt-toolkit -Version: 3.0.30 -Summary: Library for building powerful interactive command lines in Python -Home-page: https://github.com/prompt-toolkit/python-prompt-toolkit -Author: Jonathan Slenders -License: UNKNOWN -Platform: UNKNOWN -Classifier: Development Status :: 5 - Production/Stable -Classifier: Intended Audience :: Developers -Classifier: License :: OSI Approved :: BSD License -Classifier: Operating System :: OS Independent -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.6 -Classifier: Programming Language :: Python :: 3.7 -Classifier: Programming Language :: Python :: 3.8 -Classifier: Programming Language :: Python :: 3.9 -Classifier: Programming Language :: Python :: 3.10 -Classifier: Programming Language :: Python :: 3 :: Only -Classifier: Programming Language :: Python -Classifier: Topic :: Software Development -Requires-Python: >=3.6.2 -Description-Content-Type: text/x-rst -License-File: LICENSE -License-File: AUTHORS.rst -Requires-Dist: wcwidth - -Python Prompt Toolkit -===================== - -|Build Status| |AppVeyor| |PyPI| |RTD| |License| |Codecov| - -.. image :: https://github.com/prompt-toolkit/python-prompt-toolkit/raw/master/docs/images/logo_400px.png - -``prompt_toolkit`` *is a library for building powerful interactive command line applications in Python.* - -Read the `documentation on readthedocs -<http://python-prompt-toolkit.readthedocs.io/en/stable/>`_. - -NOTICE: prompt_toolkit 3.0 -************************** - -Please notice that this branch is the ``prompt_toolkit`` **3.0** branch. For most -users, it should be compatible with ``prompt_toolkit`` **2.0**, but it requires at -least **Python 3.6**. On the plus side, ``prompt_toolkit`` **3.0** is completely type -annotated and uses asyncio natively. - - -Gallery -******* - -`ptpython <http://github.com/prompt-toolkit/ptpython/>`_ is an interactive -Python Shell, build on top of ``prompt_toolkit``. - -.. image :: https://github.com/prompt-toolkit/python-prompt-toolkit/raw/master/docs/images/ptpython.png - -`More examples <https://python-prompt-toolkit.readthedocs.io/en/stable/pages/gallery.html>`_ - - -prompt_toolkit features -*********************** - -``prompt_toolkit`` could be a replacement for `GNU readline -<https://tiswww.case.edu/php/chet/readline/rltop.html>`_, but it can be much -more than that. - -Some features: - -- **Pure Python**. -- Syntax highlighting of the input while typing. (For instance, with a Pygments lexer.) -- Multi-line input editing. -- Advanced code completion. -- Both Emacs and Vi key bindings. (Similar to readline.) -- Even some advanced Vi functionality, like named registers and digraphs. -- Reverse and forward incremental search. -- Works well with Unicode double width characters. (Chinese input.) -- Selecting text for copy/paste. (Both Emacs and Vi style.) -- Support for `bracketed paste <https://cirw.in/blog/bracketed-paste>`_. -- Mouse support for cursor positioning and scrolling. -- Auto suggestions. (Like `fish shell <http://fishshell.com/>`_.) -- Multiple input buffers. -- No global state. -- Lightweight, the only dependencies are Pygments and wcwidth. -- Runs on Linux, OS X, FreeBSD, OpenBSD and Windows systems. -- And much more... - -Feel free to create tickets for bugs and feature requests, and create pull -requests if you have nice patches that you would like to share with others. - - -Installation -************ - -:: - - pip install prompt_toolkit - -For Conda, do: - -:: - - conda install -c https://conda.anaconda.org/conda-forge prompt_toolkit - - -About Windows support -********************* - -``prompt_toolkit`` is cross platform, and everything that you build on top -should run fine on both Unix and Windows systems. Windows support is best on -recent Windows 10 builds, for which the command line window supports vt100 -escape sequences. (If not supported, we fall back to using Win32 APIs for color -and cursor movements). - -It's worth noting that the implementation is a "best effort of what is -possible". Both Unix and Windows terminals have their limitations. But in -general, the Unix experience will still be a little better. - -For Windows, it's recommended to use either `cmder -<http://cmder.net/>`_ or `conemu <https://conemu.github.io/>`_. - -Getting started -*************** - -The most simple example of the library would look like this: - -.. code:: python - - from prompt_toolkit import prompt - - if __name__ == '__main__': - answer = prompt('Give me some input: ') - print('You said: %s' % answer) - -For more complex examples, have a look in the ``examples`` directory. All -examples are chosen to demonstrate only one thing. Also, don't be afraid to -look at the source code. The implementation of the ``prompt`` function could be -a good start. - -Philosophy -********** - -The source code of ``prompt_toolkit`` should be **readable**, **concise** and -**efficient**. We prefer short functions focusing each on one task and for which -the input and output types are clearly specified. We mostly prefer composition -over inheritance, because inheritance can result in too much functionality in -the same object. We prefer immutable objects where possible (objects don't -change after initialization). Reusability is important. We absolutely refrain -from having a changing global state, it should be possible to have multiple -independent instances of the same code in the same process. The architecture -should be layered: the lower levels operate on primitive operations and data -structures giving -- when correctly combined -- all the possible flexibility; -while at the higher level, there should be a simpler API, ready-to-use and -sufficient for most use cases. Thinking about algorithms and efficiency is -important, but avoid premature optimization. - - -`Projects using prompt_toolkit <PROJECTS.rst>`_ -*********************************************** - -Special thanks to -***************** - -- `Pygments <http://pygments.org/>`_: Syntax highlighter. -- `wcwidth <https://github.com/jquast/wcwidth>`_: Determine columns needed for a wide characters. - -.. |Build Status| image:: https://api.travis-ci.org/prompt-toolkit/python-prompt-toolkit.svg?branch=master - :target: https://travis-ci.org/prompt-toolkit/python-prompt-toolkit# - -.. |PyPI| image:: https://img.shields.io/pypi/v/prompt_toolkit.svg - :target: https://pypi.python.org/pypi/prompt-toolkit/ - :alt: Latest Version - -.. |AppVeyor| image:: https://ci.appveyor.com/api/projects/status/32r7s2skrgm9ubva?svg=true - :target: https://ci.appveyor.com/project/prompt-toolkit/python-prompt-toolkit/ - -.. |RTD| image:: https://readthedocs.org/projects/python-prompt-toolkit/badge/ - :target: https://python-prompt-toolkit.readthedocs.io/en/master/ - -.. |License| image:: https://img.shields.io/github/license/prompt-toolkit/python-prompt-toolkit.svg - :target: https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/LICENSE - -.. |Codecov| image:: https://codecov.io/gh/prompt-toolkit/python-prompt-toolkit/branch/master/graphs/badge.svg?style=flat - :target: https://codecov.io/gh/prompt-toolkit/python-prompt-toolkit/ - - - diff --git a/contrib/python/prompt-toolkit/py3/.dist-info/top_level.txt b/contrib/python/prompt-toolkit/py3/.dist-info/top_level.txt deleted file mode 100644 index 29392dfc5b3..00000000000 --- a/contrib/python/prompt-toolkit/py3/.dist-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -prompt_toolkit diff --git a/contrib/python/prompt-toolkit/py3/AUTHORS.rst b/contrib/python/prompt-toolkit/py3/AUTHORS.rst deleted file mode 100644 index f7c8f60f400..00000000000 --- a/contrib/python/prompt-toolkit/py3/AUTHORS.rst +++ /dev/null @@ -1,11 +0,0 @@ -Authors -======= - -Creator -------- -Jonathan Slenders <jonathan AT slenders.be> - -Contributors ------------- - -- Amjith Ramanujam <amjith.r AT gmail.com> diff --git a/contrib/python/prompt-toolkit/py3/LICENSE b/contrib/python/prompt-toolkit/py3/LICENSE deleted file mode 100644 index e1720e0fb70..00000000000 --- a/contrib/python/prompt-toolkit/py3/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2014, Jonathan Slenders -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, this - list of conditions and the following disclaimer in the documentation and/or - other materials provided with the distribution. - -* Neither the name of the {organization} nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/contrib/python/prompt-toolkit/py3/README.rst b/contrib/python/prompt-toolkit/py3/README.rst deleted file mode 100644 index d3b77b6ce49..00000000000 --- a/contrib/python/prompt-toolkit/py3/README.rst +++ /dev/null @@ -1,157 +0,0 @@ -Python Prompt Toolkit -===================== - -|Build Status| |AppVeyor| |PyPI| |RTD| |License| |Codecov| - -.. image :: https://github.com/prompt-toolkit/python-prompt-toolkit/raw/master/docs/images/logo_400px.png - -``prompt_toolkit`` *is a library for building powerful interactive command line applications in Python.* - -Read the `documentation on readthedocs -<http://python-prompt-toolkit.readthedocs.io/en/stable/>`_. - -NOTICE: prompt_toolkit 3.0 -************************** - -Please notice that this branch is the ``prompt_toolkit`` **3.0** branch. For most -users, it should be compatible with ``prompt_toolkit`` **2.0**, but it requires at -least **Python 3.6**. On the plus side, ``prompt_toolkit`` **3.0** is completely type -annotated and uses asyncio natively. - - -Gallery -******* - -`ptpython <http://github.com/prompt-toolkit/ptpython/>`_ is an interactive -Python Shell, build on top of ``prompt_toolkit``. - -.. image :: https://github.com/prompt-toolkit/python-prompt-toolkit/raw/master/docs/images/ptpython.png - -`More examples <https://python-prompt-toolkit.readthedocs.io/en/stable/pages/gallery.html>`_ - - -prompt_toolkit features -*********************** - -``prompt_toolkit`` could be a replacement for `GNU readline -<https://tiswww.case.edu/php/chet/readline/rltop.html>`_, but it can be much -more than that. - -Some features: - -- **Pure Python**. -- Syntax highlighting of the input while typing. (For instance, with a Pygments lexer.) -- Multi-line input editing. -- Advanced code completion. -- Both Emacs and Vi key bindings. (Similar to readline.) -- Even some advanced Vi functionality, like named registers and digraphs. -- Reverse and forward incremental search. -- Works well with Unicode double width characters. (Chinese input.) -- Selecting text for copy/paste. (Both Emacs and Vi style.) -- Support for `bracketed paste <https://cirw.in/blog/bracketed-paste>`_. -- Mouse support for cursor positioning and scrolling. -- Auto suggestions. (Like `fish shell <http://fishshell.com/>`_.) -- Multiple input buffers. -- No global state. -- Lightweight, the only dependencies are Pygments and wcwidth. -- Runs on Linux, OS X, FreeBSD, OpenBSD and Windows systems. -- And much more... - -Feel free to create tickets for bugs and feature requests, and create pull -requests if you have nice patches that you would like to share with others. - - -Installation -************ - -:: - - pip install prompt_toolkit - -For Conda, do: - -:: - - conda install -c https://conda.anaconda.org/conda-forge prompt_toolkit - - -About Windows support -********************* - -``prompt_toolkit`` is cross platform, and everything that you build on top -should run fine on both Unix and Windows systems. Windows support is best on -recent Windows 10 builds, for which the command line window supports vt100 -escape sequences. (If not supported, we fall back to using Win32 APIs for color -and cursor movements). - -It's worth noting that the implementation is a "best effort of what is -possible". Both Unix and Windows terminals have their limitations. But in -general, the Unix experience will still be a little better. - -For Windows, it's recommended to use either `cmder -<http://cmder.net/>`_ or `conemu <https://conemu.github.io/>`_. - -Getting started -*************** - -The most simple example of the library would look like this: - -.. code:: python - - from prompt_toolkit import prompt - - if __name__ == '__main__': - answer = prompt('Give me some input: ') - print('You said: %s' % answer) - -For more complex examples, have a look in the ``examples`` directory. All -examples are chosen to demonstrate only one thing. Also, don't be afraid to -look at the source code. The implementation of the ``prompt`` function could be -a good start. - -Philosophy -********** - -The source code of ``prompt_toolkit`` should be **readable**, **concise** and -**efficient**. We prefer short functions focusing each on one task and for which -the input and output types are clearly specified. We mostly prefer composition -over inheritance, because inheritance can result in too much functionality in -the same object. We prefer immutable objects where possible (objects don't -change after initialization). Reusability is important. We absolutely refrain -from having a changing global state, it should be possible to have multiple -independent instances of the same code in the same process. The architecture -should be layered: the lower levels operate on primitive operations and data -structures giving -- when correctly combined -- all the possible flexibility; -while at the higher level, there should be a simpler API, ready-to-use and -sufficient for most use cases. Thinking about algorithms and efficiency is -important, but avoid premature optimization. - - -`Projects using prompt_toolkit <PROJECTS.rst>`_ -*********************************************** - -Special thanks to -***************** - -- `Pygments <http://pygments.org/>`_: Syntax highlighter. -- `wcwidth <https://github.com/jquast/wcwidth>`_: Determine columns needed for a wide characters. - -.. |Build Status| image:: https://api.travis-ci.org/prompt-toolkit/python-prompt-toolkit.svg?branch=master - :target: https://travis-ci.org/prompt-toolkit/python-prompt-toolkit# - -.. |PyPI| image:: https://img.shields.io/pypi/v/prompt_toolkit.svg - :target: https://pypi.python.org/pypi/prompt-toolkit/ - :alt: Latest Version - -.. |AppVeyor| image:: https://ci.appveyor.com/api/projects/status/32r7s2skrgm9ubva?svg=true - :target: https://ci.appveyor.com/project/prompt-toolkit/python-prompt-toolkit/ - -.. |RTD| image:: https://readthedocs.org/projects/python-prompt-toolkit/badge/ - :target: https://python-prompt-toolkit.readthedocs.io/en/master/ - -.. |License| image:: https://img.shields.io/github/license/prompt-toolkit/python-prompt-toolkit.svg - :target: https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/LICENSE - -.. |Codecov| image:: https://codecov.io/gh/prompt-toolkit/python-prompt-toolkit/branch/master/graphs/badge.svg?style=flat - :target: https://codecov.io/gh/prompt-toolkit/python-prompt-toolkit/ - diff --git a/contrib/python/prompt-toolkit/py3/patches/01-fix-tests.patch b/contrib/python/prompt-toolkit/py3/patches/01-fix-tests.patch deleted file mode 100644 index 3cb8ffa39de..00000000000 --- a/contrib/python/prompt-toolkit/py3/patches/01-fix-tests.patch +++ /dev/null @@ -1,12 +0,0 @@ ---- contrib/python/prompt-toolkit/py3/tests/test_completion.py (index) -+++ contrib/python/prompt-toolkit/py3/tests/test_completion.py (working tree) -@@ -197,7 +197,8 @@ def test_pathcompleter_does_not_expanduser_by_default(): - assert [] == completions - - --def test_pathcompleter_can_expanduser(): -+def test_pathcompleter_can_expanduser(monkeypatch): -+ monkeypatch.setenv('HOME', '/tmp') - completer = PathCompleter(expanduser=True) - doc_text = "~" - doc = Document(doc_text, len(doc_text)) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/__init__.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/__init__.py deleted file mode 100644 index a66c10f3f5c..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/__init__.py +++ /dev/null @@ -1,40 +0,0 @@ -""" -prompt_toolkit -============== - -Author: Jonathan Slenders - -Description: prompt_toolkit is a Library for building powerful interactive - command lines in Python. It can be a replacement for GNU - Readline, but it can be much more than that. - -See the examples directory to learn about the usage. - -Probably, to get started, you might also want to have a look at -`prompt_toolkit.shortcuts.prompt`. -""" -from .application import Application -from .formatted_text import ANSI, HTML -from .shortcuts import PromptSession, print_formatted_text, prompt - -# Don't forget to update in `docs/conf.py`! -__version__ = "3.0.30" - -# Version tuple. -VERSION = tuple(__version__.split(".")) - - -__all__ = [ - # Application. - "Application", - # Shortcuts. - "prompt", - "PromptSession", - "print_formatted_text", - # Formatted text. - "HTML", - "ANSI", - # Version info. - "__version__", - "VERSION", -] diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/application/__init__.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/application/__init__.py deleted file mode 100644 index dc61ca73c33..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/application/__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -from .application import Application -from .current import ( - AppSession, - create_app_session, - create_app_session_from_tty, - get_app, - get_app_or_none, - get_app_session, - set_app, -) -from .dummy import DummyApplication -from .run_in_terminal import in_terminal, run_in_terminal - -__all__ = [ - # Application. - "Application", - # Current. - "AppSession", - "get_app_session", - "create_app_session", - "create_app_session_from_tty", - "get_app", - "get_app_or_none", - "set_app", - # Dummy. - "DummyApplication", - # Run_in_terminal - "in_terminal", - "run_in_terminal", -] diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/application/application.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/application/application.py deleted file mode 100644 index 5a09918da51..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/application/application.py +++ /dev/null @@ -1,1432 +0,0 @@ -import asyncio -import os -import re -import signal -import sys -import threading -import time -from asyncio import ( - AbstractEventLoop, - CancelledError, - Future, - Task, - ensure_future, - new_event_loop, - set_event_loop, - sleep, -) -from contextlib import contextmanager -from subprocess import Popen -from traceback import format_tb -from typing import ( - Any, - Awaitable, - Callable, - Coroutine, - Dict, - FrozenSet, - Generator, - Generic, - Hashable, - Iterable, - List, - Optional, - Tuple, - Type, - TypeVar, - Union, - cast, - overload, -) - -from prompt_toolkit.buffer import Buffer -from prompt_toolkit.cache import SimpleCache -from prompt_toolkit.clipboard import Clipboard, InMemoryClipboard -from prompt_toolkit.cursor_shapes import AnyCursorShapeConfig, to_cursor_shape_config -from prompt_toolkit.data_structures import Size -from prompt_toolkit.enums import EditingMode -from prompt_toolkit.eventloop import ( - get_traceback_from_context, - run_in_executor_with_context, -) -from prompt_toolkit.eventloop.utils import call_soon_threadsafe, get_event_loop -from prompt_toolkit.filters import Condition, Filter, FilterOrBool, to_filter -from prompt_toolkit.formatted_text import AnyFormattedText -from prompt_toolkit.input.base import Input -from prompt_toolkit.input.typeahead import get_typeahead, store_typeahead -from prompt_toolkit.key_binding.bindings.page_navigation import ( - load_page_navigation_bindings, -) -from prompt_toolkit.key_binding.defaults import load_key_bindings -from prompt_toolkit.key_binding.emacs_state import EmacsState -from prompt_toolkit.key_binding.key_bindings import ( - Binding, - ConditionalKeyBindings, - GlobalOnlyKeyBindings, - KeyBindings, - KeyBindingsBase, - KeysTuple, - merge_key_bindings, -) -from prompt_toolkit.key_binding.key_processor import KeyPressEvent, KeyProcessor -from prompt_toolkit.key_binding.vi_state import ViState -from prompt_toolkit.keys import Keys -from prompt_toolkit.layout.containers import Container, Window -from prompt_toolkit.layout.controls import BufferControl, UIControl -from prompt_toolkit.layout.dummy import create_dummy_layout -from prompt_toolkit.layout.layout import Layout, walk -from prompt_toolkit.output import ColorDepth, Output -from prompt_toolkit.renderer import Renderer, print_formatted_text -from prompt_toolkit.search import SearchState -from prompt_toolkit.styles import ( - BaseStyle, - DummyStyle, - DummyStyleTransformation, - DynamicStyle, - StyleTransformation, - default_pygments_style, - default_ui_style, - merge_styles, -) -from prompt_toolkit.utils import Event, in_main_thread - -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", -] - - -E = KeyPressEvent -_AppResult = TypeVar("_AppResult") -ApplicationEventHandler = Callable[["Application[_AppResult]"], None] - -_SIGWINCH = getattr(signal, "SIGWINCH", None) -_SIGTSTP = getattr(signal, "SIGTSTP", None) - - -class Application(Generic[_AppResult]): - """ - The main Application class! - This glues everything together. - - :param layout: A :class:`~prompt_toolkit.layout.Layout` instance. - :param key_bindings: - :class:`~prompt_toolkit.key_binding.KeyBindingsBase` instance for - the key bindings. - :param clipboard: :class:`~prompt_toolkit.clipboard.Clipboard` to use. - :param full_screen: When True, run the application on the alternate screen buffer. - :param color_depth: Any :class:`~.ColorDepth` value, a callable that - returns a :class:`~.ColorDepth` or `None` for default. - :param erase_when_done: (bool) Clear the application output when it finishes. - :param reverse_vi_search_direction: Normally, in Vi mode, a '/' searches - forward and a '?' searches backward. In Readline mode, this is usually - reversed. - :param min_redraw_interval: Number of seconds to wait between redraws. Use - this for applications where `invalidate` is called a lot. This could cause - a lot of terminal output, which some terminals are not able to process. - - `None` means that every `invalidate` will be scheduled right away - (which is usually fine). - - When one `invalidate` is called, but a scheduled redraw of a previous - `invalidate` call has not been executed yet, nothing will happen in any - case. - - :param max_render_postpone_time: When there is high CPU (a lot of other - scheduled calls), postpone the rendering max x seconds. '0' means: - don't postpone. '.5' means: try to draw at least twice a second. - - :param refresh_interval: Automatically invalidate the UI every so many - seconds. When `None` (the default), only invalidate when `invalidate` - has been called. - - :param terminal_size_polling_interval: Poll the terminal size every so many - seconds. Useful if the applications runs in a thread other then then - main thread where SIGWINCH can't be handled, or on Windows. - - Filters: - - :param mouse_support: (:class:`~prompt_toolkit.filters.Filter` or - boolean). When True, enable mouse support. - :param paste_mode: :class:`~prompt_toolkit.filters.Filter` or boolean. - :param editing_mode: :class:`~prompt_toolkit.enums.EditingMode`. - - :param enable_page_navigation_bindings: When `True`, enable the page - navigation key bindings. These include both Emacs and Vi bindings like - page-up, page-down and so on to scroll through pages. Mostly useful for - creating an editor or other full screen applications. Probably, you - don't want this for the implementation of a REPL. By default, this is - enabled if `full_screen` is set. - - Callbacks (all of these should accept an - :class:`~prompt_toolkit.application.Application` object as input.) - - :param on_reset: Called during reset. - :param on_invalidate: Called when the UI has been invalidated. - :param before_render: Called right before rendering. - :param after_render: Called right after rendering. - - I/O: - (Note that the preferred way to change the input/output is by creating an - `AppSession` with the required input/output objects. If you need multiple - applications running at the same time, you have to create a separate - `AppSession` using a `with create_app_session():` block. - - :param input: :class:`~prompt_toolkit.input.Input` instance. - :param output: :class:`~prompt_toolkit.output.Output` instance. (Probably - Vt100_Output or Win32Output.) - - Usage: - - app = Application(...) - app.run() - - # Or - await app.run_async() - """ - - def __init__( - self, - layout: Optional[Layout] = None, - style: Optional[BaseStyle] = None, - include_default_pygments_style: FilterOrBool = True, - style_transformation: Optional[StyleTransformation] = None, - key_bindings: Optional[KeyBindingsBase] = None, - clipboard: Optional[Clipboard] = None, - full_screen: bool = False, - color_depth: Union[ - ColorDepth, Callable[[], Union[ColorDepth, None]], None - ] = None, - mouse_support: FilterOrBool = False, - enable_page_navigation_bindings: Optional[ - 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, - 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, - # I/O. - input: Optional[Input] = None, - output: Optional[Output] = 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. - if enable_page_navigation_bindings is None: - enable_page_navigation_bindings = Condition(lambda: self.full_screen) - - paste_mode = to_filter(paste_mode) - mouse_support = to_filter(mouse_support) - reverse_vi_search_direction = to_filter(reverse_vi_search_direction) - enable_page_navigation_bindings = to_filter(enable_page_navigation_bindings) - include_default_pygments_style = to_filter(include_default_pygments_style) - - if layout is None: - layout = create_dummy_layout() - - if style_transformation is None: - style_transformation = DummyStyleTransformation() - - self.style = style - self.style_transformation = style_transformation - - # Key bindings. - self.key_bindings = key_bindings - self._default_bindings = load_key_bindings() - self._page_navigation_bindings = load_page_navigation_bindings() - - self.layout = layout - self.clipboard = clipboard or InMemoryClipboard() - self.full_screen: bool = full_screen - self._color_depth = color_depth - self.mouse_support = mouse_support - - self.paste_mode = paste_mode - self.editing_mode = editing_mode - self.erase_when_done = erase_when_done - self.reverse_vi_search_direction = reverse_vi_search_direction - self.enable_page_navigation_bindings = enable_page_navigation_bindings - self.min_redraw_interval = min_redraw_interval - self.max_render_postpone_time = max_render_postpone_time - self.refresh_interval = refresh_interval - self.terminal_size_polling_interval = terminal_size_polling_interval - - self.cursor = to_cursor_shape_config(cursor) - - # Events. - self.on_invalidate = Event(self, on_invalidate) - self.on_reset = Event(self, on_reset) - self.before_render = Event(self, before_render) - self.after_render = Event(self, after_render) - - # I/O. - session = get_app_session() - self.output = output or session.output - self.input = input or session.input - - # List of 'extra' functions to execute before a Application.run. - 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 - - #: Quoted insert. This flag is set if we go into quoted insert mode. - self.quoted_insert = False - - #: Vi state. (For Vi key bindings.) - self.vi_state = ViState() - self.emacs_state = EmacsState() - - #: When to flush the input (For flushing escape keys.) This is important - #: on terminals that use vt100 input. We can't distinguish the escape - #: key from for instance the left-arrow key, if we don't know what follows - #: after "\x1b". This little timer will consider "\x1b" to be escape if - #: nothing did follow in this time span. - #: This seems to work like the `ttimeoutlen` option in Vim. - self.ttimeoutlen = 0.5 # Seconds. - - #: Like Vim's `timeoutlen` option. This can be `None` or a float. For - #: instance, suppose that we have a key binding AB and a second key - #: binding A. If the uses presses A and then waits, we don't handle - #: this binding yet (unless it was marked 'eager'), because we don't - #: know what will follow. This timeout is the maximum amount of time - #: that we wait until we call the handlers anyway. Pass `None` to - #: disable this timeout. - self.timeoutlen = 1.0 - - #: The `Renderer` instance. - # Make sure that the same stdout is used, when a custom renderer has been passed. - self._merged_style = self._create_merged_style(include_default_pygments_style) - - self.renderer = Renderer( - self._merged_style, - self.output, - full_screen=full_screen, - mouse_support=mouse_support, - cpr_not_supported_callback=self.cpr_not_supported_callback, - ) - - #: Render counter. This one is increased every time the UI is rendered. - #: It can be used as a key for caching certain information during one - #: rendering. - self.render_counter = 0 - - # Invalidate flag. When 'True', a repaint has been scheduled. - self._invalidated = False - self._invalidate_events: List[ - Event[object] - ] = [] # Collection of 'invalidate' Event objects. - self._last_redraw_time = 0.0 # Unix timestamp of last redraw. Used when - # `min_redraw_interval` is given. - - #: The `InputProcessor` instance. - self.key_processor = KeyProcessor(_CombinedRegistry(self)) - - # 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 - - # Trigger initialize callback. - self.reset() - - def _create_merged_style(self, include_default_pygments_style: Filter) -> BaseStyle: - """ - Create a `Style` object that merges the default UI style, the default - pygments style, and the custom user style. - """ - dummy_style = DummyStyle() - pygments_style = default_pygments_style() - - @DynamicStyle - def conditional_pygments_style() -> BaseStyle: - if include_default_pygments_style(): - return pygments_style - else: - return dummy_style - - return merge_styles( - [ - default_ui_style(), - conditional_pygments_style, - DynamicStyle(lambda: self.style), - ] - ) - - @property - def color_depth(self) -> ColorDepth: - """ - The active :class:`.ColorDepth`. - - The current value is determined as follows: - - - If a color depth was given explicitly to this application, use that - value. - - Otherwise, fall back to the color depth that is reported by the - :class:`.Output` implementation. If the :class:`.Output` class was - created using `output.defaults.create_output`, then this value is - coming from the $PROMPT_TOOLKIT_COLOR_DEPTH environment variable. - """ - depth = self._color_depth - - if callable(depth): - depth = depth() - - if depth is None: - depth = self.output.get_default_color_depth() - - return depth - - @property - def current_buffer(self) -> Buffer: - """ - The currently focused :class:`~.Buffer`. - - (This returns a dummy :class:`.Buffer` when none of the actual buffers - has the focus. In this case, it's really not practical to check for - `None` values or catch exceptions every time.) - """ - return self.layout.current_buffer or Buffer( - name="dummy-buffer" - ) # Dummy buffer. - - @property - def current_search_state(self) -> SearchState: - """ - Return the current :class:`.SearchState`. (The one for the focused - :class:`.BufferControl`.) - """ - ui_control = self.layout.current_control - if isinstance(ui_control, BufferControl): - return ui_control.search_state - else: - return SearchState() # Dummy search state. (Don't return None!) - - def reset(self) -> None: - """ - Reset everything, for reading the next input. - """ - # Notice that we don't reset the buffers. (This happens just before - # returning, and when we have multiple buffers, we clearly want the - # content in the other buffers to remain unchanged between several - # calls of `run`. (And the same is true for the focus stack.) - - self.exit_style = "" - - self.background_tasks: List[Task[None]] = [] - - self.renderer.reset() - self.key_processor.reset() - self.layout.reset() - self.vi_state.reset() - self.emacs_state.reset() - - # Trigger reset event. - self.on_reset.fire() - - # Make sure that we have a 'focusable' widget focused. - # (The `Layout` class can't determine this.) - layout = self.layout - - if not layout.current_control.is_focusable(): - for w in layout.find_all_windows(): - if w.content.is_focusable(): - layout.current_window = w - break - - def invalidate(self) -> None: - """ - Thread safe way of sending a repaint trigger to the input event loop. - """ - 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. - # See: https://github.com/dbcli/mycli/issues/797 - return - - # `invalidate()` called if we don't have a loop yet (not running?), or - # after the event loop was closed. - if self.loop is None or self.loop.is_closed(): - return - - # Never schedule a second redraw, when a previous one has not yet been - # executed. (This should protect against other threads calling - # 'invalidate' many times, resulting in 100% CPU.) - if self._invalidated: - return - else: - self._invalidated = True - - # Trigger event. - self.loop.call_soon_threadsafe(self.on_invalidate.fire) - - def redraw() -> None: - self._invalidated = False - self._redraw() - - def schedule_redraw() -> None: - call_soon_threadsafe( - redraw, max_postpone_time=self.max_render_postpone_time, loop=self.loop - ) - - if self.min_redraw_interval: - # When a minimum redraw interval is set, wait minimum this amount - # of time between redraws. - diff = time.time() - self._last_redraw_time - if diff < self.min_redraw_interval: - - async def redraw_in_future() -> None: - await sleep(cast(float, self.min_redraw_interval) - diff) - schedule_redraw() - - self.loop.call_soon_threadsafe( - lambda: self.create_background_task(redraw_in_future()) - ) - else: - schedule_redraw() - else: - schedule_redraw() - - @property - def invalidated(self) -> bool: - "True when a redraw operation has been scheduled." - return self._invalidated - - def _redraw(self, render_as_done: bool = False) -> None: - """ - Render the command line again. (Not thread safe!) (From other threads, - or if unsure, use :meth:`.Application.invalidate`.) - - :param render_as_done: make sure to put the cursor after the UI. - """ - - def run_in_context() -> None: - # Only draw when no sub application was started. - if self._is_running and not self._running_in_terminal: - if self.min_redraw_interval: - self._last_redraw_time = time.time() - - # Render - self.render_counter += 1 - self.before_render.fire() - - if render_as_done: - if self.erase_when_done: - self.renderer.erase() - else: - # Draw in 'done' state and reset renderer. - self.renderer.render(self, self.layout, is_done=render_as_done) - else: - self.renderer.render(self, self.layout) - - self.layout.update_parents_relations() - - # Fire render event. - self.after_render.fire() - - self._update_invalidate_events() - - # NOTE: We want to make sure this Application is the active one. The - # invalidate function is often called from a context where this - # application is not the active one. (Like the - # `PromptSession._auto_refresh_context`). - # We copy the context in case the context was already active, to - # prevent RuntimeErrors. (The rendering is not supposed to change - # any context variables.) - if self.context is not None: - self.context.copy().run(run_in_context) - - def _start_auto_refresh_task(self) -> None: - """ - Start a while/true loop in the background for automatic invalidation of - the UI. - """ - if self.refresh_interval is not None and self.refresh_interval != 0: - - async def auto_refresh(refresh_interval: float) -> None: - while True: - await sleep(refresh_interval) - self.invalidate() - - self.create_background_task(auto_refresh(self.refresh_interval)) - - def _update_invalidate_events(self) -> None: - """ - Make sure to attach 'invalidate' handlers to all invalidate events in - the UI. - """ - # Remove all the original event handlers. (Components can be removed - # from the UI.) - for ev in self._invalidate_events: - ev -= self._invalidate_handler - - # Gather all new events. - # (All controls are able to invalidate themselves.) - def gather_events() -> Iterable[Event[object]]: - for c in self.layout.find_all_controls(): - yield from c.get_invalidate_events() - - self._invalidate_events = list(gather_events()) - - for ev in self._invalidate_events: - ev += self._invalidate_handler - - def _invalidate_handler(self, sender: object) -> None: - """ - Handler for invalidate events coming from UIControls. - - (This handles the difference in signature between event handler and - `self.invalidate`. It also needs to be a method -not a nested - function-, so that we can remove it again .) - """ - self.invalidate() - - def _on_resize(self) -> None: - """ - When the window size changes, we erase the current output and request - again the cursor position. When the CPR answer arrives, the output is - drawn again. - """ - # Erase, request position (when cursor is at the start position) - # and redraw again. -- The order is important. - self.renderer.erase(leave_alternate_screen=False) - self._request_absolute_cursor_position() - self._redraw() - - def _pre_run(self, pre_run: Optional[Callable[[], None]] = None) -> None: - """ - Called during `run`. - - `self.future` should be set to the new future at the point where this - is called in order to avoid data races. `pre_run` can be used to set a - `threading.Event` to synchronize with UI termination code, running in - another thread that would call `Application.exit`. (See the progress - bar code for an example.) - """ - if pre_run: - pre_run() - - # Process registered "pre_run_callables" and clear list. - for c in self.pre_run_callables: - c() - del self.pre_run_callables[:] - - async def run_async( - self, - pre_run: Optional[Callable[[], None]] = None, - set_exception_handler: bool = True, - handle_sigint: bool = True, - slow_callback_duration: float = 0.5, - ) -> _AppResult: - """ - Run the prompt_toolkit :class:`~prompt_toolkit.application.Application` - until :meth:`~prompt_toolkit.application.Application.exit` has been - called. Return the value that was passed to - :meth:`~prompt_toolkit.application.Application.exit`. - - This is the main entry point for a prompt_toolkit - :class:`~prompt_toolkit.application.Application` and usually the only - place where the event loop is actually running. - - :param pre_run: Optional callable, which is called right after the - "reset" of the application. - :param set_exception_handler: When set, in case of an exception, go out - of the alternate screen and hide the application, display the - exception, and wait for the user to press ENTER. - :param handle_sigint: Handle SIGINT signal if possible. This will call - the `<sigint>` key binding when a SIGINT is received. (This only - works in the main thread.) - :param slow_callback_duration: Display warnings if code scheduled in - the asyncio event loop takes more time than this. The asyncio - default of `0.1` is sometimes not sufficient on a slow system, - because exceptionally, the drawing of the app, which happens in the - event loop, can take a bit longer from time to time. - """ - assert not self._is_running, "Application is already running." - - if not in_main_thread() or sys.platform == "win32": - # Handling signals in other threads is not supported. - # Also on Windows, `add_signal_handler(signal.SIGINT, ...)` raises - # `NotImplementedError`. - # See: https://github.com/prompt-toolkit/python-prompt-toolkit/issues/1553 - handle_sigint = False - - async def _run_async() -> _AppResult: - "Coroutine." - loop = get_event_loop() - f = loop.create_future() - self.future = f # XXX: make sure to set this before calling '_redraw'. - self.loop = loop - self.context = contextvars.copy_context() - - # Counter for cancelling 'flush' timeouts. Every time when a key is - # 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 - - # Reset. - # (`self.future` needs to be set when `pre_run` is called.) - self.reset() - self._pre_run(pre_run) - - # Feed type ahead input first. - self.key_processor.feed_multiple(get_typeahead(self.input)) - self.key_processor.process_keys() - - def read_from_input() -> None: - nonlocal flush_task - - # Ignore when we aren't running anymore. This callback will - # removed from the loop next time. (It could be that it was - # still in the 'tasks' list of the loop.) - # Except: if we need to process incoming CPRs. - if not self._is_running and not self.renderer.waiting_for_cpr: - return - - # Get keys from the input object. - keys = self.input.read_keys() - - # Feed to key processor. - self.key_processor.feed_multiple(keys) - self.key_processor.process_keys() - - # Quit when the input stream was closed. - if self.input.closed: - if not f.done(): - f.set_exception(EOFError) - else: - # Automatically flush keys. - if flush_task: - flush_task.cancel() - flush_task = self.create_background_task(auto_flush_input()) - - async def auto_flush_input() -> None: - # Flush input after timeout. - # (Used for flushing the enter key.) - # This sleep can be cancelled, in that case we won't flush yet. - await sleep(self.ttimeoutlen) - flush_input() - - def flush_input() -> None: - if not self.is_done: - # Get keys, and feed to key processor. - keys = self.input.flush_keys() - self.key_processor.feed_multiple(keys) - self.key_processor.process_keys() - - if self.input.closed: - f.set_exception(EOFError) - - # Enter raw mode, attach input and attach WINCH event handler. - with self.input.raw_mode(), self.input.attach( - read_from_input - ), attach_winch_signal_handler(self._on_resize): - - # Draw UI. - self._request_absolute_cursor_position() - self._redraw() - self._start_auto_refresh_task() - - self.create_background_task(self._poll_output_size()) - - # Wait for UI to finish. - try: - result = await f - finally: - # In any case, when the application finishes. - # (Successful, or because of an error.) - try: - self._redraw(render_as_done=True) - finally: - # _redraw has a good chance to fail if it calls widgets - # with bad code. Make sure to reset the renderer - # anyway. - self.renderer.reset() - - # Unset `is_running`, this ensures that possibly - # scheduled draws won't paint during the following - # yield. - self._is_running = False - - # Detach event handlers for invalidate events. - # (Important when a UIControl is embedded in multiple - # applications, like ptterm in pymux. An invalidate - # should not trigger a repaint in terminated - # applications.) - for ev in self._invalidate_events: - ev -= self._invalidate_handler - self._invalidate_events = [] - - # Wait for CPR responses. - if self.output.responds_to_cpr: - await self.renderer.wait_for_cpr_responses() - - # Wait for the run-in-terminals to terminate. - previous_run_in_terminal_f = self._running_in_terminal_f - - if previous_run_in_terminal_f: - await previous_run_in_terminal_f - - # Store unprocessed input as typeahead for next time. - store_typeahead(self.input, self.key_processor.empty_queue()) - - return cast(_AppResult, result) - - async def _run_async2() -> _AppResult: - self._is_running = True - try: - # Make sure to set `_invalidated` to `False` to begin with, - # otherwise we're not going to paint anything. This can happen if - # this application had run before on a different event loop, and a - # paint was scheduled using `call_soon_threadsafe` with - # `max_postpone_time`. - self._invalidated = False - - loop = get_event_loop() - - if handle_sigint: - loop.add_signal_handler( - signal.SIGINT, - lambda *_: loop.call_soon_threadsafe( - self.key_processor.send_sigint - ), - ) - - if set_exception_handler: - previous_exc_handler = loop.get_exception_handler() - loop.set_exception_handler(self._handle_exception) - - # Set slow_callback_duration. - original_slow_callback_duration = loop.slow_callback_duration - loop.slow_callback_duration = slow_callback_duration - - try: - with set_app(self), self._enable_breakpointhook(): - try: - result = await _run_async() - finally: - # Wait for the background tasks to be done. This needs to - # go in the finally! If `_run_async` raises - # `KeyboardInterrupt`, we still want to wait for the - # background tasks. - await self.cancel_and_wait_for_background_tasks() - - # Also remove the Future again. (This brings the - # application back to its initial state, where it also - # doesn't have a Future.) - self.future = None - - return result - finally: - if set_exception_handler: - loop.set_exception_handler(previous_exc_handler) - - if handle_sigint: - loop.remove_signal_handler(signal.SIGINT) - - # Reset slow_callback_duration. - loop.slow_callback_duration = original_slow_callback_duration - - finally: - # Set the `_is_running` flag to `False`. Normally this happened - # already in the finally block in `run_async` above, but in - # case of exceptions, that's not always the case. - self._is_running = False - - return await _run_async2() - - def run( - self, - pre_run: Optional[Callable[[], None]] = None, - set_exception_handler: bool = True, - handle_sigint: bool = True, - in_thread: bool = False, - ) -> _AppResult: - """ - A blocking 'run' call that waits until the UI is finished. - - This will start the current asyncio event loop. If no loop is set for - the current thread, then it will create a new loop. If a new loop was - created, this won't close the new loop (if `in_thread=False`). - - :param pre_run: Optional callable, which is called right after the - "reset" of the application. - :param set_exception_handler: When set, in case of an exception, go out - of the alternate screen and hide the application, display the - exception, and wait for the user to press ENTER. - :param in_thread: When true, run the application in a background - thread, and block the current thread until the application - terminates. This is useful if we need to be sure the application - won't use the current event loop (asyncio does not support nested - event loops). A new event loop will be created in this background - thread, and that loop will also be closed when the background - thread terminates. When this is used, it's especially important to - make sure that all asyncio background tasks are managed through - `get_appp().create_background_task()`, so that unfinished tasks are - properly cancelled before the event loop is closed. This is used - for instance in ptpython. - :param handle_sigint: Handle SIGINT signal. Call the key binding for - `Keys.SIGINT`. (This only works in the main thread.) - """ - if in_thread: - result: _AppResult - exception: Optional[BaseException] = None - - def run_in_thread() -> None: - nonlocal result, exception - try: - result = self.run( - pre_run=pre_run, - set_exception_handler=set_exception_handler, - # Signal handling only works in the main thread. - handle_sigint=False, - ) - 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() - thread.join() - - if exception is not 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. - try: - loop = 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, - ) - ) - - def _handle_exception( - self, loop: AbstractEventLoop, context: Dict[str, Any] - ) -> None: - """ - Handler for event loop exceptions. - This will print the exception, using run_in_terminal. - """ - # For Python 2: we have to get traceback at this point, because - # we're still in the 'except:' block of the event loop where the - # traceback is still available. Moving this code in the - # 'print_exception' coroutine will loose the exception. - tb = get_traceback_from_context(context) - formatted_tb = "".join(format_tb(tb)) - - async def in_term() -> None: - async with in_terminal(): - # Print output. Similar to 'loop.default_exception_handler', - # but don't use logger. (This works better on Python 2.) - print("\nUnhandled exception in event loop:") - print(formatted_tb) - print("Exception {}".format(context.get("exception"))) - - await _do_wait_for_enter("Press ENTER to continue...") - - ensure_future(in_term()) - - @contextmanager - def _enable_breakpointhook(self) -> Generator[None, None, None]: - """ - Install our custom breakpointhook for the duration of this context - manager. (We will only install the hook if no other custom hook was - set.) - """ - if sys.version_info >= (3, 7) and sys.breakpointhook == sys.__breakpointhook__: - sys.breakpointhook = self._breakpointhook - - try: - yield - finally: - sys.breakpointhook = sys.__breakpointhook__ - else: - yield - - def _breakpointhook(self, *a: object, **kw: object) -> None: - """ - Breakpointhook which uses PDB, but ensures that the application is - hidden and input echoing is restored during each debugger dispatch. - """ - app = self - # Inline import on purpose. We don't want to import pdb, if not needed. - import pdb - from types import FrameType - - TraceDispatch = Callable[[FrameType, str, Any], Any] - - class CustomPdb(pdb.Pdb): - def trace_dispatch( - self, frame: FrameType, event: str, arg: Any - ) -> TraceDispatch: - # Hide application. - app.renderer.erase() - - # Detach input and dispatch to debugger. - with app.input.detach(): - with app.input.cooked_mode(): - return super().trace_dispatch(frame, event, arg) - - # Note: we don't render the application again here, because - # there's a good chance that there's a breakpoint on the next - # line. This paint/erase cycle would move the PDB prompt back - # to the middle of the screen. - - frame = sys._getframe().f_back - CustomPdb(stdout=sys.__stdout__).set_trace(frame) - - def create_background_task( - self, coroutine: Coroutine[Any, Any, None] - ) -> "asyncio.Task[None]": - """ - Start a background task (coroutine) for the running application. When - the `Application` terminates, unfinished background tasks will be - cancelled. - - If asyncio had nurseries like Trio, we would create a nursery in - `Application.run_async`, and run the given coroutine in that nursery. - - Not threadsafe. - """ - task: asyncio.Task[None] = get_event_loop().create_task(coroutine) - self.background_tasks.append(task) - return task - - async def cancel_and_wait_for_background_tasks(self) -> None: - """ - Cancel all background tasks, and wait for the cancellation to be done. - If any of the background tasks raised an exception, this will also - propagate the exception. - - (If we had nurseries like Trio, this would be the `__aexit__` of a - nursery.) - """ - for task in self.background_tasks: - task.cancel() - - for task in self.background_tasks: - try: - await task - except CancelledError: - pass - - async def _poll_output_size(self) -> None: - """ - Coroutine for polling the terminal dimensions. - - Useful for situations where `attach_winch_signal_handler` is not sufficient: - - If we are not running in the main thread. - - On Windows. - """ - size: Optional[Size] = None - interval = self.terminal_size_polling_interval - - if interval is None: - return - - while True: - await asyncio.sleep(interval) - new_size = self.output.get_size() - - if size is not None and new_size != size: - self._on_resize() - size = new_size - - def cpr_not_supported_callback(self) -> None: - """ - Called when we don't receive the cursor position response in time. - """ - if not self.output.responds_to_cpr: - return # We know about this already. - - def in_terminal() -> None: - self.output.write( - "WARNING: your terminal doesn't support cursor position requests (CPR).\r\n" - ) - self.output.flush() - - run_in_terminal(in_terminal) - - @overload - def exit(self) -> None: - "Exit without arguments." - - @overload - def exit(self, *, result: _AppResult, style: str = "") -> None: - "Exit with `_AppResult`." - - @overload - def exit( - self, *, exception: Union[BaseException, Type[BaseException]], style: str = "" - ) -> None: - "Exit with exception." - - def exit( - self, - result: Optional[_AppResult] = None, - exception: Optional[Union[BaseException, Type[BaseException]]] = None, - style: str = "", - ) -> None: - """ - Exit application. - - .. note:: - - If `Application.exit` is called before `Application.run()` is - called, then the `Application` won't exit (because the - `Application.future` doesn't correspond to the current run). Use a - `pre_run` hook and an event to synchronize the closing if there's a - chance this can happen. - - :param result: Set this result for the application. - :param exception: Set this exception as the result for an application. For - a prompt, this is often `EOFError` or `KeyboardInterrupt`. - :param style: Apply this style on the whole content when quitting, - often this is 'class:exiting' for a prompt. (Used when - `erase_when_done` is not set.) - """ - assert result is None or exception is None - - if self.future is None: - raise Exception("Application is not running. Application.exit() failed.") - - if self.future.done(): - raise Exception("Return value already set. Application.exit() failed.") - - self.exit_style = style - - if exception is not None: - self.future.set_exception(exception) - else: - self.future.set_result(cast(_AppResult, result)) - - def _request_absolute_cursor_position(self) -> None: - """ - Send CPR request. - """ - # Note: only do this if the input queue is not empty, and a return - # value has not been set. Otherwise, we won't be able to read the - # response anyway. - if not self.key_processor.input_queue and not self.is_done: - self.renderer.request_absolute_cursor_position() - - async def run_system_command( - self, - command: str, - wait_for_enter: bool = True, - display_before_text: AnyFormattedText = "", - wait_text: str = "Press ENTER to continue...", - ) -> None: - """ - Run system command (While hiding the prompt. When finished, all the - output will scroll above the prompt.) - - :param command: Shell command to be executed. - :param wait_for_enter: FWait for the user to press enter, when the - command is finished. - :param display_before_text: If given, text to be displayed before the - command executes. - :return: A `Future` object. - """ - async with in_terminal(): - # Try to use the same input/output file descriptors as the one, - # used to run this application. - try: - input_fd = self.input.fileno() - except AttributeError: - input_fd = sys.stdin.fileno() - try: - output_fd = self.output.fileno() - except AttributeError: - output_fd = sys.stdout.fileno() - - # Run sub process. - def run_command() -> None: - self.print_text(display_before_text) - p = Popen(command, shell=True, stdin=input_fd, stdout=output_fd) - p.wait() - - await run_in_executor_with_context(run_command) - - # Wait for the user to press enter. - if wait_for_enter: - await _do_wait_for_enter(wait_text) - - def suspend_to_background(self, suspend_group: bool = True) -> None: - """ - (Not thread safe -- to be called from inside the key bindings.) - Suspend process. - - :param suspend_group: When true, suspend the whole process group. - (This is the default, and probably what you want.) - """ - # Only suspend when the operating system supports it. - # (Not on Windows.) - if _SIGTSTP is not None: - - def run() -> None: - signal = cast(int, _SIGTSTP) - # Send `SIGTSTP` to own process. - # This will cause it to suspend. - - # Usually we want the whole process group to be suspended. This - # handles the case when input is piped from another process. - if suspend_group: - os.kill(0, signal) - else: - os.kill(os.getpid(), signal) - - run_in_terminal(run) - - def print_text( - self, text: AnyFormattedText, style: Optional[BaseStyle] = None - ) -> None: - """ - Print a list of (style_str, text) tuples to the output. - (When the UI is running, this method has to be called through - `run_in_terminal`, otherwise it will destroy the UI.) - - :param text: List of ``(style_str, text)`` tuples. - :param style: Style class to use. Defaults to the active style in the CLI. - """ - print_formatted_text( - output=self.output, - formatted_text=text, - style=style or self._merged_style, - color_depth=self.color_depth, - style_transformation=self.style_transformation, - ) - - @property - def is_running(self) -> bool: - "`True` when the application is currently active/running." - return self._is_running - - @property - def is_done(self) -> bool: - if self.future: - return self.future.done() - return False - - 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`. - """ - attrs_for_style = self.renderer._attrs_for_style - - if attrs_for_style: - return sorted( - re.sub(r"\s+", " ", style_str).strip() - for style_str in attrs_for_style.keys() - ) - - return [] - - -class _CombinedRegistry(KeyBindingsBase): - """ - The `KeyBindings` of key bindings for a `Application`. - This merges the global key bindings with the one of the current user - control. - """ - - def __init__(self, app: Application[_AppResult]) -> None: - self.app = app - self._cache: SimpleCache[ - Tuple[Window, FrozenSet[UIControl]], KeyBindingsBase - ] = SimpleCache() - - @property - def _version(self) -> Hashable: - """Not needed - this object is not going to be wrapped in another - KeyBindings object.""" - raise NotImplementedError - - 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] - ) -> KeyBindingsBase: - """ - Create a `KeyBindings` object that merges the `KeyBindings` from the - `UIControl` with all the parent controls and the global key bindings. - """ - key_bindings = [] - collected_containers = set() - - # Collect key bindings from currently focused control and all parent - # controls. Don't include key bindings of container parent controls. - container: Container = current_window - while True: - collected_containers.add(container) - kb = container.get_key_bindings() - if kb is not None: - key_bindings.append(kb) - - if container.is_modal(): - break - - parent = self.app.layout.get_parent(container) - if parent is None: - break - else: - container = parent - - # Include global bindings (starting at the top-model container). - for c in walk(container): - if c not in collected_containers: - kb = c.get_key_bindings() - if kb is not None: - key_bindings.append(GlobalOnlyKeyBindings(kb)) - - # Add App key bindings - if self.app.key_bindings: - key_bindings.append(self.app.key_bindings) - - # Add mouse bindings. - key_bindings.append( - ConditionalKeyBindings( - self.app._page_navigation_bindings, - self.app.enable_page_navigation_bindings, - ) - ) - key_bindings.append(self.app._default_bindings) - - # Reverse this list. The current control's key bindings should come - # last. They need priority. - key_bindings = key_bindings[::-1] - - return merge_key_bindings(key_bindings) - - @property - def _key_bindings(self) -> KeyBindingsBase: - current_window = self.app.layout.current_window - other_controls = list(self.app.layout.find_all_controls()) - key = current_window, frozenset(other_controls) - - return self._cache.get( - key, lambda: self._create_key_bindings(current_window, other_controls) - ) - - 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]: - return self._key_bindings.get_bindings_starting_with_keys(keys) - - -async def _do_wait_for_enter(wait_text: AnyFormattedText) -> None: - """ - Create a sub application to wait for the enter key press. - This has two advantages over using 'input'/'raw_input': - - This will share the same input/output I/O. - - This doesn't block the event loop. - """ - from prompt_toolkit.shortcuts import PromptSession - - key_bindings = KeyBindings() - - @key_bindings.add("enter") - def _ok(event: E) -> None: - event.app.exit() - - @key_bindings.add(Keys.Any) - def _ignore(event: E) -> None: - "Disallow typing." - pass - - session: PromptSession[None] = PromptSession( - message=wait_text, key_bindings=key_bindings - ) - await session.app.run_async() - - -@contextmanager -def attach_winch_signal_handler( - handler: Callable[[], None] -) -> Generator[None, None, None]: - """ - Attach the given callback as a WINCH signal handler within the context - manager. Restore the original signal handler when done. - - The `Application.run` method will register SIGWINCH, so that it will - properly repaint when the terminal window resizes. However, using - `run_in_terminal`, we can temporarily send an application to the - background, and run an other app in between, which will then overwrite the - SIGWINCH. This is why it's important to restore the handler when the app - terminates. - """ - # The tricky part here is that signals are registered in the Unix event - # loop with a wakeup fd, but another application could have registered - # signals using signal.signal directly. For now, the implementation is - # hard-coded for the `asyncio.unix_events._UnixSelectorEventLoop`. - - # No WINCH? Then don't do anything. - sigwinch = getattr(signal, "SIGWINCH", None) - if sigwinch is None or not in_main_thread(): - yield - return - - # Keep track of the previous handler. - # (Only UnixSelectorEventloop has `_signal_handlers`.) - loop = get_event_loop() - previous_winch_handler = getattr(loop, "_signal_handlers", {}).get(sigwinch) - - try: - loop.add_signal_handler(sigwinch, handler) - yield - finally: - # Restore the previous signal handler. - loop.remove_signal_handler(sigwinch) - if previous_winch_handler is not None: - loop.add_signal_handler( - sigwinch, - previous_winch_handler._callback, - *previous_winch_handler._args, - ) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/application/current.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/application/current.py deleted file mode 100644 index ae69bfd51c7..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/application/current.py +++ /dev/null @@ -1,197 +0,0 @@ -import sys -from contextlib import contextmanager -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 - - from .application import Application - -__all__ = [ - "AppSession", - "get_app_session", - "get_app", - "get_app_or_none", - "set_app", - "create_app_session", - "create_app_session_from_tty", -] - - -class AppSession: - """ - An AppSession is an interactive session, usually connected to one terminal. - Within one such session, interaction with many applications can happen, one - after the other. - - The input/output device is not supposed to change during one session. - - Warning: Always use the `create_app_session` function to create an - instance, so that it gets activated correctly. - - :param input: Use this as a default input for all applications - running in this session, unless an input is passed to the `Application` - explicitely. - :param output: Use this as a default output. - """ - - def __init__( - self, input: Optional["Input"] = None, output: Optional["Output"] = 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 - - def __repr__(self) -> str: - return f"AppSession(app={self.app!r})" - - @property - def input(self) -> "Input": - if self._input is None: - from prompt_toolkit.input.defaults import create_input - - self._input = create_input() - return self._input - - @property - def output(self) -> "Output": - if self._output is None: - from prompt_toolkit.output.defaults import create_output - - self._output = create_output() - return self._output - - -_current_app_session: ContextVar["AppSession"] = ContextVar( - "_current_app_session", default=AppSession() -) - - -def get_app_session() -> AppSession: - return _current_app_session.get() - - -def get_app() -> "Application[Any]": - """ - Get the current active (running) Application. - An :class:`.Application` is active during the - :meth:`.Application.run_async` call. - - We assume that there can only be one :class:`.Application` active at the - same time. There is only one terminal window, with only one stdin and - stdout. This makes the code significantly easier than passing around the - :class:`.Application` everywhere. - - If no :class:`.Application` is running, then return by default a - :class:`.DummyApplication`. For practical reasons, we prefer to not raise - an exception. This way, we don't have to check all over the place whether - an actual `Application` was returned. - - (For applications like pymux where we can have more than one `Application`, - we'll use a work-around to handle that.) - """ - session = _current_app_session.get() - if session.app is not None: - return session.app - - from .dummy import DummyApplication - - return DummyApplication() - - -def get_app_or_none() -> Optional["Application[Any]"]: - """ - Get the current active (running) Application, or return `None` if no - application is running. - """ - session = _current_app_session.get() - return session.app - - -@contextmanager -def set_app(app: "Application[Any]") -> Generator[None, None, None]: - """ - Context manager that sets the given :class:`.Application` active in an - `AppSession`. - - This should only be called by the `Application` itself. - The application will automatically be active while its running. If you want - the application to be active in other threads/coroutines, where that's not - the case, use `contextvars.copy_context()`, or use `Application.context` to - run it in the appropriate context. - """ - session = _current_app_session.get() - - previous_app = session.app - session.app = app - try: - yield - finally: - session.app = previous_app - - -@contextmanager -def create_app_session( - input: Optional["Input"] = None, output: Optional["Output"] = None -) -> Generator[AppSession, None, None]: - """ - Create a separate AppSession. - - This is useful if there can be multiple individual `AppSession`s going on. - Like in the case of an Telnet/SSH server. This functionality uses - contextvars and requires at least Python 3.7. - """ - if sys.version_info <= (3, 6): - raise RuntimeError("Application sessions require Python 3.7.") - - # If no input/output is specified, fall back to the current input/output, - # whatever that is. - if input is None: - input = get_app_session().input - if output is None: - output = get_app_session().output - - # Create new `AppSession` and activate. - session = AppSession(input=input, output=output) - - token = _current_app_session.set(session) - try: - yield session - finally: - _current_app_session.reset(token) - - -@contextmanager -def create_app_session_from_tty() -> Generator[AppSession, None, None]: - """ - Create `AppSession` that always prefers the TTY input/output. - - Even if `sys.stdin` and `sys.stdout` are connected to input/output pipes, - this will still use the terminal for interaction (because `sys.stderr` is - still connected to the terminal). - - Usage:: - - from prompt_toolkit.shortcuts import prompt - - with create_app_session_from_tty(): - prompt('>') - """ - from prompt_toolkit.input.defaults import create_input - from prompt_toolkit.output.defaults import create_output - - input = create_input(always_prefer_tty=True) - output = create_output(always_prefer_tty=True) - - with create_app_session(input=input, output=output) as app_session: - yield app_session diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/application/dummy.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/application/dummy.py deleted file mode 100644 index 4e5e4aafdd0..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/application/dummy.py +++ /dev/null @@ -1,51 +0,0 @@ -from typing import Callable, Optional - -from prompt_toolkit.formatted_text import AnyFormattedText -from prompt_toolkit.input import DummyInput -from prompt_toolkit.output import DummyOutput - -from .application import Application - -__all__ = [ - "DummyApplication", -] - - -class DummyApplication(Application[None]): - """ - When no :class:`.Application` is running, - :func:`.get_app` will run an instance of this :class:`.DummyApplication` instead. - """ - - def __init__(self) -> None: - super().__init__(output=DummyOutput(), input=DummyInput()) - - def run( - self, - pre_run: Optional[Callable[[], None]] = None, - set_exception_handler: bool = True, - handle_sigint: bool = True, - in_thread: bool = False, - ) -> None: - raise NotImplementedError("A DummyApplication is not supposed to run.") - - async def run_async( - self, - pre_run: Optional[Callable[[], None]] = None, - set_exception_handler: bool = True, - handle_sigint: bool = True, - slow_callback_duration: float = 0.5, - ) -> None: - raise NotImplementedError("A DummyApplication is not supposed to run.") - - async def run_system_command( - self, - command: str, - wait_for_enter: bool = True, - display_before_text: AnyFormattedText = "", - wait_text: str = "", - ) -> None: - raise NotImplementedError - - def suspend_to_background(self, suspend_group: bool = True) -> None: - raise NotImplementedError diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/application/run_in_terminal.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/application/run_in_terminal.py deleted file mode 100644 index d5ef8aafa30..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/application/run_in_terminal.py +++ /dev/null @@ -1,117 +0,0 @@ -""" -Tools for running functions on the terminal above the current application or prompt. -""" -import sys -from asyncio import Future, ensure_future -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", -] - -_T = TypeVar("_T") - - -def run_in_terminal( - func: Callable[[], _T], render_cli_done: bool = False, in_executor: bool = False -) -> Awaitable[_T]: - """ - Run function on the terminal above the current application or prompt. - - What this does is first hiding the prompt, then running this callable - (which can safely output to the terminal), and then again rendering the - prompt which causes the output of this function to scroll above the - prompt. - - ``func`` is supposed to be a synchronous function. If you need an - asynchronous version of this function, use the ``in_terminal`` context - manager directly. - - :param func: The callable to execute. - :param render_cli_done: When True, render the interface in the - 'Done' state first, then execute the function. If False, - erase the interface first. - :param in_executor: When True, run in executor. (Use this for long - blocking functions, when you don't want to block the event loop.) - - :returns: A `Future`. - """ - - async def run() -> _T: - async with in_terminal(render_cli_done=render_cli_done): - if in_executor: - return await run_in_executor_with_context(func) - else: - return func() - - return ensure_future(run()) - - -@asynccontextmanager -async def in_terminal(render_cli_done: bool = False) -> AsyncGenerator[None, None]: - """ - Asynchronous context manager that suspends the current application and runs - the body in the terminal. - - .. code:: - - async def f(): - async with in_terminal(): - call_some_function() - await call_some_async_function() - """ - app = get_app_or_none() - if app is None or not app._is_running: - yield - return - - # When a previous `run_in_terminal` call was in progress. Wait for that - # to finish, before starting this one. Chain to previous call. - previous_run_in_terminal_f = app._running_in_terminal_f - new_run_in_terminal_f: Future[None] = Future() - app._running_in_terminal_f = new_run_in_terminal_f - - # Wait for the previous `run_in_terminal` to finish. - if previous_run_in_terminal_f is not None: - await previous_run_in_terminal_f - - # Wait for all CPRs to arrive. We don't want to detach the input until - # all cursor position responses have been arrived. Otherwise, the tty - # will echo its input and can show stuff like ^[[39;1R. - if app.output.responds_to_cpr: - await app.renderer.wait_for_cpr_responses() - - # Draw interface in 'done' state, or erase. - if render_cli_done: - app._redraw(render_as_done=True) - else: - app.renderer.erase() - - # Disable rendering. - app._running_in_terminal = True - - # Detach input. - try: - with app.input.detach(): - with app.input.cooked_mode(): - yield - finally: - # Redraw interface again. - try: - app._running_in_terminal = False - app.renderer.reset() - app._request_absolute_cursor_position() - app._redraw() - finally: - new_run_in_terminal_f.set_result(None) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/auto_suggest.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/auto_suggest.py deleted file mode 100644 index 7099b8e974a..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/auto_suggest.py +++ /dev/null @@ -1,187 +0,0 @@ -""" -`Fish-style <http://fishshell.com/>`_ like auto-suggestion. - -While a user types input in a certain buffer, suggestions are generated -(asynchronously.) Usually, they are displayed after the input. When the cursor -presses the right arrow and the cursor is at the end of the input, the -suggestion will be inserted. - -If you want the auto suggestions to be asynchronous (in a background thread), -because they take too much time, and could potentially block the event loop, -then wrap the :class:`.AutoSuggest` instance into a -:class:`.ThreadedAutoSuggest`. -""" -from abc import ABCMeta, abstractmethod -from typing import TYPE_CHECKING, Callable, Optional, Union - -from prompt_toolkit.eventloop import run_in_executor_with_context - -from .document import Document -from .filters import Filter, to_filter - -if TYPE_CHECKING: - from .buffer import Buffer - -__all__ = [ - "Suggestion", - "AutoSuggest", - "ThreadedAutoSuggest", - "DummyAutoSuggest", - "AutoSuggestFromHistory", - "ConditionalAutoSuggest", - "DynamicAutoSuggest", -] - - -class Suggestion: - """ - Suggestion returned by an auto-suggest algorithm. - - :param text: The suggestion text. - """ - - def __init__(self, text: str) -> None: - self.text = text - - def __repr__(self) -> str: - return "Suggestion(%s)" % self.text - - -class AutoSuggest(metaclass=ABCMeta): - """ - Base class for auto suggestion implementations. - """ - - @abstractmethod - def get_suggestion( - self, buffer: "Buffer", document: Document - ) -> Optional[Suggestion]: - """ - Return `None` or a :class:`.Suggestion` instance. - - We receive both :class:`~prompt_toolkit.buffer.Buffer` and - :class:`~prompt_toolkit.document.Document`. The reason is that auto - suggestions are retrieved asynchronously. (Like completions.) The - buffer text could be changed in the meantime, but ``document`` contains - the buffer document like it was at the start of the auto suggestion - call. So, from here, don't access ``buffer.text``, but use - ``document.text`` instead. - - :param buffer: The :class:`~prompt_toolkit.buffer.Buffer` instance. - :param document: The :class:`~prompt_toolkit.document.Document` instance. - """ - - async def get_suggestion_async( - self, buff: "Buffer", document: Document - ) -> Optional[Suggestion]: - """ - Return a :class:`.Future` which is set when the suggestions are ready. - This function can be overloaded in order to provide an asynchronous - implementation. - """ - return self.get_suggestion(buff, document) - - -class ThreadedAutoSuggest(AutoSuggest): - """ - Wrapper that runs auto suggestions in a thread. - (Use this to prevent the user interface from becoming unresponsive if the - generation of suggestions takes too much time.) - """ - - def __init__(self, auto_suggest: AutoSuggest) -> None: - self.auto_suggest = auto_suggest - - def get_suggestion( - self, buff: "Buffer", document: Document - ) -> Optional[Suggestion]: - return self.auto_suggest.get_suggestion(buff, document) - - async def get_suggestion_async( - self, buff: "Buffer", document: Document - ) -> Optional[Suggestion]: - """ - Run the `get_suggestion` function in a thread. - """ - - def run_get_suggestion_thread() -> Optional[Suggestion]: - return self.get_suggestion(buff, document) - - return await run_in_executor_with_context(run_get_suggestion_thread) - - -class DummyAutoSuggest(AutoSuggest): - """ - AutoSuggest class that doesn't return any suggestion. - """ - - def get_suggestion( - self, buffer: "Buffer", document: Document - ) -> Optional[Suggestion]: - return None # No suggestion - - -class AutoSuggestFromHistory(AutoSuggest): - """ - Give suggestions based on the lines in the history. - """ - - def get_suggestion( - self, buffer: "Buffer", document: Document - ) -> Optional[Suggestion]: - history = buffer.history - - # Consider only the last line for the suggestion. - text = document.text.rsplit("\n", 1)[-1] - - # Only create a suggestion when this is not an empty line. - if text.strip(): - # Find first matching line in history. - for string in reversed(list(history.get_strings())): - for line in reversed(string.splitlines()): - if line.startswith(text): - return Suggestion(line[len(text) :]) - - return None - - -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: - - self.auto_suggest = auto_suggest - self.filter = to_filter(filter) - - def get_suggestion( - self, buffer: "Buffer", document: Document - ) -> Optional[Suggestion]: - if self.filter(): - return self.auto_suggest.get_suggestion(buffer, document) - - return None - - -class DynamicAutoSuggest(AutoSuggest): - """ - Validator class that can dynamically returns any Validator. - - :param get_validator: Callable that returns a :class:`.Validator` instance. - """ - - def __init__(self, get_auto_suggest: Callable[[], Optional[AutoSuggest]]) -> None: - self.get_auto_suggest = get_auto_suggest - - def get_suggestion( - self, buff: "Buffer", document: Document - ) -> Optional[Suggestion]: - 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]: - auto_suggest = self.get_auto_suggest() or DummyAutoSuggest() - return await auto_suggest.get_suggestion_async(buff, document) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/buffer.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/buffer.py deleted file mode 100644 index 6c006a258b5..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/buffer.py +++ /dev/null @@ -1,2017 +0,0 @@ -""" -Data structures for the Buffer. -It holds the text, cursor position, history, etc... -""" -import asyncio -import logging -import os -import re -import shlex -import shutil -import subprocess -import tempfile -from collections import deque -from enum import Enum -from functools import wraps -from typing import ( - Any, - Awaitable, - Callable, - Coroutine, - Deque, - Iterable, - List, - Optional, - Set, - Tuple, - TypeVar, - Union, - cast, -) - -from .application.current import get_app -from .application.run_in_terminal import run_in_terminal -from .auto_suggest import AutoSuggest, Suggestion -from .cache import FastDictCache -from .clipboard import ClipboardData -from .completion import ( - CompleteEvent, - Completer, - Completion, - DummyCompleter, - get_common_complete_suffix, -) -from .document import Document -from .filters import FilterOrBool, to_filter -from .history import History, InMemoryHistory -from .search import SearchDirection, SearchState -from .selection import PasteMode, SelectionState, SelectionType -from .utils import Event, to_str -from .validation import ValidationError, Validator - -__all__ = [ - "EditReadOnlyBuffer", - "Buffer", - "CompletionState", - "indent", - "unindent", - "reshape_text", -] - -logger = logging.getLogger(__name__) - - -class EditReadOnlyBuffer(Exception): - "Attempt editing of read-only :class:`.Buffer`." - - -class ValidationState(Enum): - "The validation state of a buffer. This is set after the validation." - VALID = "VALID" - INVALID = "INVALID" - UNKNOWN = "UNKNOWN" - - -class CompletionState: - """ - Immutable class that contains a completion state. - """ - - def __init__( - self, - original_document: "Document", - completions: Optional[List["Completion"]] = None, - complete_index: Optional[int] = None, - ) -> None: - - #: Document as it was when the completion started. - self.original_document = original_document - - #: List of all the current Completion instances which are possible at - #: this point. - self.completions = completions or [] - - #: Position in the `completions` array. - #: This can be `None` to indicate "no completion", the original text. - self.complete_index = complete_index # Position in the `_completions` array. - - def __repr__(self) -> str: - return "{}({!r}, <{!r}> completions, index={!r})".format( - self.__class__.__name__, - self.original_document, - len(self.completions), - self.complete_index, - ) - - def go_to_index(self, index: Optional[int]) -> None: - """ - Create a new :class:`.CompletionState` object with the new index. - - When `index` is `None` deselect the completion. - """ - if self.completions: - assert index is None or 0 <= index < len(self.completions) - self.complete_index = index - - def new_text_and_position(self) -> Tuple[str, int]: - """ - Return (new_text, new_cursor_position) for this completion. - """ - if self.complete_index is None: - return self.original_document.text, self.original_document.cursor_position - else: - original_text_before_cursor = self.original_document.text_before_cursor - original_text_after_cursor = self.original_document.text_after_cursor - - c = self.completions[self.complete_index] - if c.start_position == 0: - before = original_text_before_cursor - else: - before = original_text_before_cursor[: c.start_position] - - new_text = before + c.text + original_text_after_cursor - new_cursor_position = len(before) + len(c.text) - return new_text, new_cursor_position - - @property - def current_completion(self) -> Optional["Completion"]: - """ - Return the current completion, or return `None` when no completion is - selected. - """ - if self.complete_index is not None: - return self.completions[self.complete_index] - return None - - -_QUOTED_WORDS_RE = re.compile(r"""(\s+|".*?"|'.*?')""") - - -class YankNthArgState: - """ - For yank-last-arg/yank-nth-arg: Keep track of where we are in the history. - """ - - def __init__( - self, history_position: int = 0, n: int = -1, previous_inserted_word: str = "" - ) -> None: - - self.history_position = history_position - self.previous_inserted_word = previous_inserted_word - self.n = n - - def __repr__(self) -> str: - return "{}(history_position={!r}, n={!r}, previous_inserted_word={!r})".format( - self.__class__.__name__, - self.history_position, - self.n, - self.previous_inserted_word, - ) - - -BufferEventHandler = Callable[["Buffer"], None] -BufferAcceptHandler = Callable[["Buffer"], bool] - - -class Buffer: - """ - The core data structure that holds the text and cursor position of the - current input line and implements all text manipulations on top of it. It - also implements the history, undo stack and the completion state. - - :param completer: :class:`~prompt_toolkit.completion.Completer` instance. - :param history: :class:`~prompt_toolkit.history.History` instance. - :param tempfile_suffix: The tempfile suffix (extension) to be used for the - "open in editor" function. For a Python REPL, this would be ".py", so - that the editor knows the syntax highlighting to use. This can also be - a callable that returns a string. - :param tempfile: For more advanced tempfile situations where you need - control over the subdirectories and filename. For a Git Commit Message, - this would be ".git/COMMIT_EDITMSG", so that the editor knows the syntax - highlighting to use. This can also be a callable that returns a string. - :param name: Name for this buffer. E.g. DEFAULT_BUFFER. This is mostly - useful for key bindings where we sometimes prefer to refer to a buffer - by their name instead of by reference. - :param accept_handler: Called when the buffer input is accepted. (Usually - when the user presses `enter`.) The accept handler receives this - `Buffer` as input and should return True when the buffer text should be - kept instead of calling reset. - - In case of a `PromptSession` for instance, we want to keep the text, - because we will exit the application, and only reset it during the next - run. - - Events: - - :param on_text_changed: When the buffer text changes. (Callable or None.) - :param on_text_insert: When new text is inserted. (Callable or None.) - :param on_cursor_position_changed: When the cursor moves. (Callable or None.) - :param on_completions_changed: When the completions were changed. (Callable or None.) - :param on_suggestion_set: When an auto-suggestion text has been set. (Callable or None.) - - Filters: - - :param complete_while_typing: :class:`~prompt_toolkit.filters.Filter` - or `bool`. Decide whether or not to do asynchronous autocompleting while - typing. - :param validate_while_typing: :class:`~prompt_toolkit.filters.Filter` - or `bool`. Decide whether or not to do asynchronous validation while - typing. - :param enable_history_search: :class:`~prompt_toolkit.filters.Filter` or - `bool` to indicate when up-arrow partial string matching is enabled. It - is advised to not enable this at the same time as - `complete_while_typing`, because when there is an autocompletion found, - the up arrows usually browse through the completions, rather than - through the history. - :param read_only: :class:`~prompt_toolkit.filters.Filter`. When True, - changes will not be allowed. - :param multiline: :class:`~prompt_toolkit.filters.Filter` or `bool`. When - not set, pressing `Enter` will call the `accept_handler`. Otherwise, - pressing `Esc-Enter` is required. - """ - - 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]] = "", - 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, - 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, - ): - - # Accept both filters and booleans as input. - enable_history_search = to_filter(enable_history_search) - complete_while_typing = to_filter(complete_while_typing) - validate_while_typing = to_filter(validate_while_typing) - read_only = to_filter(read_only) - multiline = to_filter(multiline) - - self.completer = completer or DummyCompleter() - self.auto_suggest = auto_suggest - self.validator = validator - self.tempfile_suffix = tempfile_suffix - self.tempfile = tempfile - self.name = name - self.accept_handler = accept_handler - - # Filters. (Usually, used by the key bindings to drive the buffer.) - self.complete_while_typing = complete_while_typing - self.validate_while_typing = validate_while_typing - self.enable_history_search = enable_history_search - self.read_only = read_only - self.multiline = multiline - - # Text width. (For wrapping, used by the Vi 'gq' operator.) - self.text_width = 0 - - #: The command buffer history. - # Note that we shouldn't use a lazy 'or' here. bool(history) could be - # False when empty. - self.history = InMemoryHistory() if history is None else history - - 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_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) - - # Document cache. (Avoid creating new Document instances.) - self._document_cache: FastDictCache[ - Tuple[str, int, Optional[SelectionState]], Document - ] = FastDictCache(Document, size=10) - - # Create completer / auto suggestion / validation coroutines. - self._async_suggester = self._create_auto_suggest_coroutine() - self._async_completer = self._create_completer_coroutine() - self._async_validator = self._create_auto_validate_coroutine() - - # Asyncio task for populating the history. - self._load_history_task: Optional[asyncio.Future[None]] = None - - # Reset other attributes. - self.reset(document=document) - - def __repr__(self) -> str: - if len(self.text) < 15: - text = self.text - else: - text = self.text[:12] + "..." - - return f"<Buffer(name={self.name!r}, text={text!r}) at {id(self)!r}>" - - def reset( - self, document: Optional[Document] = None, append_to_history: bool = False - ) -> None: - """ - :param append_to_history: Append current input to history first. - """ - if append_to_history: - self.append_to_history() - - document = document or Document() - - 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 - - # State of the selection. - self.selection_state: Optional[SelectionState] = 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] = [] - - # When doing consecutive up/down movements, prefer to stay at this column. - self.preferred_column: Optional[int] = None - - # State of complete browser - # For interactive completion through Ctrl-N/Ctrl-P. - self.complete_state: Optional[CompletionState] = None - - # State of Emacs yank-nth-arg completion. - self.yank_nth_arg_state: Optional[YankNthArgState] = 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 - - # Current suggestion. - self.suggestion: Optional[Suggestion] = None - - # The history search text. (Used for filtering the history when we - # browse through it.) - self.history_search_text: Optional[str] = None - - # Undo/redo stacks (stack of `(text, cursor_position)`). - 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 - # `BufferControl` we will repopulate it. - if self._load_history_task is not None: - self._load_history_task.cancel() - self._load_history_task = None - - #: The working lines. Similar to history, except that this can be - #: modified. The user can press arrow_up and edit previous entries. - #: Ctrl-C should reset this, and copy the whole history back in here. - #: Enter should process the current command and append to the real - #: history. - self._working_lines: Deque[str] = deque([document.text]) - self.__working_index = 0 - - def load_history_if_not_yet_loaded(self) -> None: - """ - Create task for populating the buffer history (if not yet done). - - Note:: - - This needs to be called from within the event loop of the - application, because history loading is async, and we need to be - sure the right event loop is active. Therefor, we call this method - in the `BufferControl.create_content`. - - There are situations where prompt_toolkit applications are created - in one thread, but will later run in a different thread (Ptpython - is one example. The REPL runs in a separate thread, in order to - prevent interfering with a potential different event loop in the - main thread. The REPL UI however is still created in the main - thread.) We could decide to not support creating prompt_toolkit - objects in one thread and running the application in a different - thread, but history loading is the only place where it matters, and - this solves it. - """ - if self._load_history_task is None: - - async def load_history() -> None: - async for item in self.history.load(): - self._working_lines.appendleft(item) - self.__working_index += 1 - - self._load_history_task = get_app().create_background_task(load_history()) - - def load_history_done(f: "asyncio.Future[None]") -> None: - """ - Handle `load_history` result when either done, cancelled, or - when an exception was raised. - """ - try: - f.result() - except asyncio.CancelledError: - # Ignore cancellation. But handle it, so that we don't get - # this traceback. - pass - except GeneratorExit: - # Probably not needed, but we had situations where - # `GeneratorExit` was raised in `load_history` during - # cancellation. - pass - except BaseException: - # Log error if something goes wrong. (We don't have a - # caller to which we can propagate this exception.) - logger.exception("Loading history failed") - - self._load_history_task.add_done_callback(load_history_done) - - # <getters/setters> - - def _set_text(self, value: str) -> bool: - """set text at current working_index. Return whether it changed.""" - working_index = self.working_index - working_lines = self._working_lines - - original_value = working_lines[working_index] - working_lines[working_index] = value - - # Return True when this text has been changed. - if len(value) != len(original_value): - # For Python 2, it seems that when two strings have a different - # length and one is a prefix of the other, Python still scans - # character by character to see whether the strings are different. - # (Some benchmarking showed significant differences for big - # documents. >100,000 of lines.) - return True - elif value != original_value: - return True - return False - - def _set_cursor_position(self, value: int) -> bool: - """Set cursor position. Return whether it changed.""" - original_position = self.__cursor_position - self.__cursor_position = max(0, value) - - return self.__cursor_position != original_position - - @property - def text(self) -> str: - return self._working_lines[self.working_index] - - @text.setter - def text(self, value: str) -> None: - """ - Setting text. (When doing this, make sure that the cursor_position is - valid for this text. text/cursor_position should be consistent at any time, - otherwise set a Document instead.) - """ - # Ensure cursor position remains within the size of the text. - if self.cursor_position > len(value): - self.cursor_position = len(value) - - # Don't allow editing of read-only buffers. - if self.read_only(): - raise EditReadOnlyBuffer() - - changed = self._set_text(value) - - if changed: - self._text_changed() - - # Reset history search text. - # (Note that this doesn't need to happen when working_index - # changes, which is when we traverse the history. That's why we - # don't do this in `self._text_changed`.) - self.history_search_text = None - - @property - def cursor_position(self) -> int: - return self.__cursor_position - - @cursor_position.setter - def cursor_position(self, value: int) -> None: - """ - Setting cursor position. - """ - assert isinstance(value, int) - - # Ensure cursor position is within the size of the text. - if value > len(self.text): - value = len(self.text) - if value < 0: - value = 0 - - changed = self._set_cursor_position(value) - - if changed: - self._cursor_position_changed() - - @property - def working_index(self) -> int: - return self.__working_index - - @working_index.setter - def working_index(self, value: int) -> None: - if self.__working_index != value: - self.__working_index = value - # Make sure to reset the cursor position, otherwise we end up in - # situations where the cursor position is out of the bounds of the - # text. - self.cursor_position = 0 - self._text_changed() - - def _text_changed(self) -> None: - # Remove any validation errors and complete state. - self.validation_error = None - self.validation_state = ValidationState.UNKNOWN - self.complete_state = None - self.yank_nth_arg_state = None - self.document_before_paste = None - self.selection_state = None - self.suggestion = None - self.preferred_column = None - - # fire 'on_text_changed' event. - self.on_text_changed.fire() - - # Input validation. - # (This happens on all change events, unlike auto completion, also when - # deleting text.) - if self.validator and self.validate_while_typing(): - get_app().create_background_task(self._async_validator()) - - def _cursor_position_changed(self) -> None: - # Remove any complete state. - # (Input validation should only be undone when the cursor position - # changes.) - self.complete_state = None - self.yank_nth_arg_state = None - self.document_before_paste = None - - # Unset preferred_column. (Will be set after the cursor movement, if - # required.) - self.preferred_column = None - - # Note that the cursor position can change if we have a selection the - # new position of the cursor determines the end of the selection. - - # fire 'on_cursor_position_changed' event. - self.on_cursor_position_changed.fire() - - @property - def document(self) -> Document: - """ - Return :class:`~prompt_toolkit.document.Document` instance from the - current text, cursor position and selection state. - """ - return self._document_cache[ - self.text, self.cursor_position, self.selection_state - ] - - @document.setter - def document(self, value: Document) -> None: - """ - Set :class:`~prompt_toolkit.document.Document` instance. - - This will set both the text and cursor position at the same time, but - atomically. (Change events will be triggered only after both have been set.) - """ - self.set_document(value) - - def set_document(self, value: Document, bypass_readonly: bool = False) -> None: - """ - Set :class:`~prompt_toolkit.document.Document` instance. Like the - ``document`` property, but accept an ``bypass_readonly`` argument. - - :param bypass_readonly: When True, don't raise an - :class:`.EditReadOnlyBuffer` exception, even - when the buffer is read-only. - - .. warning:: - - When this buffer is read-only and `bypass_readonly` was not passed, - the `EditReadOnlyBuffer` exception will be caught by the - `KeyProcessor` and is silently suppressed. This is important to - keep in mind when writing key bindings, because it won't do what - you expect, and there won't be a stack trace. Use try/finally - around this function if you need some cleanup code. - """ - # Don't allow editing of read-only buffers. - if not bypass_readonly and self.read_only(): - raise EditReadOnlyBuffer() - - # Set text and cursor position first. - text_changed = self._set_text(value.text) - cursor_position_changed = self._set_cursor_position(value.cursor_position) - - # Now handle change events. (We do this when text/cursor position is - # both set and consistent.) - if text_changed: - self._text_changed() - self.history_search_text = None - - if cursor_position_changed: - self._cursor_position_changed() - - @property - def is_returnable(self) -> bool: - """ - True when there is something handling accept. - """ - return bool(self.accept_handler) - - # End of <getters/setters> - - def save_to_undo_stack(self, clear_redo_stack: bool = True) -> None: - """ - Safe current state (input text and cursor position), so that we can - restore it by calling undo. - """ - # Safe if the text is different from the text at the top of the stack - # is different. If the text is the same, just update the cursor position. - if self._undo_stack and self._undo_stack[-1][0] == self.text: - self._undo_stack[-1] = (self._undo_stack[-1][0], self.cursor_position) - else: - self._undo_stack.append((self.text, self.cursor_position)) - - # Saving anything to the undo stack, clears the redo stack. - if clear_redo_stack: - self._redo_stack = [] - - def transform_lines( - self, - line_index_iterator: Iterable[int], - transform_callback: Callable[[str], str], - ) -> str: - """ - Transforms the text on a range of lines. - When the iterator yield an index not in the range of lines that the - document contains, it skips them silently. - - To uppercase some lines:: - - new_text = transform_lines(range(5,10), lambda text: text.upper()) - - :param line_index_iterator: Iterator of line numbers (int) - :param transform_callback: callable that takes the original text of a - line, and return the new text for this line. - - :returns: The new text. - """ - # Split lines - lines = self.text.split("\n") - - # Apply transformation - for index in line_index_iterator: - try: - lines[index] = transform_callback(lines[index]) - except IndexError: - pass - - return "\n".join(lines) - - def transform_current_line(self, transform_callback: Callable[[str], str]) -> None: - """ - Apply the given transformation function to the current line. - - :param transform_callback: callable that takes a string and return a new string. - """ - document = self.document - a = document.cursor_position + document.get_start_of_line_position() - b = document.cursor_position + document.get_end_of_line_position() - self.text = ( - document.text[:a] - + transform_callback(document.text[a:b]) - + document.text[b:] - ) - - def transform_region( - self, from_: int, to: int, transform_callback: Callable[[str], str] - ) -> None: - """ - Transform a part of the input string. - - :param from_: (int) start position. - :param to: (int) end position. - :param transform_callback: Callable which accepts a string and returns - the transformed string. - """ - assert from_ < to - - self.text = "".join( - [ - self.text[:from_] - + transform_callback(self.text[from_:to]) - + self.text[to:] - ] - ) - - def cursor_left(self, count: int = 1) -> None: - self.cursor_position += self.document.get_cursor_left_position(count=count) - - def cursor_right(self, count: int = 1) -> None: - self.cursor_position += self.document.get_cursor_right_position(count=count) - - def cursor_up(self, count: int = 1) -> None: - """(for multiline edit). Move cursor to the previous line.""" - original_column = self.preferred_column or self.document.cursor_position_col - self.cursor_position += self.document.get_cursor_up_position( - count=count, preferred_column=original_column - ) - - # Remember the original column for the next up/down movement. - self.preferred_column = original_column - - def cursor_down(self, count: int = 1) -> None: - """(for multiline edit). Move cursor to the next line.""" - original_column = self.preferred_column or self.document.cursor_position_col - self.cursor_position += self.document.get_cursor_down_position( - count=count, preferred_column=original_column - ) - - # Remember the original column for the next up/down movement. - self.preferred_column = original_column - - def auto_up( - self, count: int = 1, go_to_start_of_line_if_history_changes: bool = False - ) -> None: - """ - If we're not on the first line (of a multiline input) go a line up, - otherwise go back in history. (If nothing is selected.) - """ - if self.complete_state: - self.complete_previous(count=count) - elif self.document.cursor_position_row > 0: - self.cursor_up(count=count) - elif not self.selection_state: - self.history_backward(count=count) - - # Go to the start of the line? - if go_to_start_of_line_if_history_changes: - self.cursor_position += self.document.get_start_of_line_position() - - def auto_down( - self, count: int = 1, go_to_start_of_line_if_history_changes: bool = False - ) -> None: - """ - If we're not on the last line (of a multiline input) go a line down, - otherwise go forward in history. (If nothing is selected.) - """ - if self.complete_state: - self.complete_next(count=count) - elif self.document.cursor_position_row < self.document.line_count - 1: - self.cursor_down(count=count) - elif not self.selection_state: - self.history_forward(count=count) - - # Go to the start of the line? - if go_to_start_of_line_if_history_changes: - self.cursor_position += self.document.get_start_of_line_position() - - def delete_before_cursor(self, count: int = 1) -> str: - """ - Delete specified number of characters before cursor and return the - deleted text. - """ - assert count >= 0 - deleted = "" - - if self.cursor_position > 0: - deleted = self.text[self.cursor_position - count : self.cursor_position] - - new_text = ( - self.text[: self.cursor_position - count] - + self.text[self.cursor_position :] - ) - new_cursor_position = self.cursor_position - len(deleted) - - # Set new Document atomically. - self.document = Document(new_text, new_cursor_position) - - return deleted - - def delete(self, count: int = 1) -> str: - """ - Delete specified number of characters and Return the deleted text. - """ - if self.cursor_position < len(self.text): - deleted = self.document.text_after_cursor[:count] - self.text = ( - self.text[: self.cursor_position] - + self.text[self.cursor_position + len(deleted) :] - ) - return deleted - else: - return "" - - def join_next_line(self, separator: str = " ") -> None: - """ - Join the next line to the current one by deleting the line ending after - the current line. - """ - if not self.document.on_last_line: - self.cursor_position += self.document.get_end_of_line_position() - self.delete() - - # Remove spaces. - self.text = ( - self.document.text_before_cursor - + separator - + self.document.text_after_cursor.lstrip(" ") - ) - - def join_selected_lines(self, separator: str = " ") -> None: - """ - Join the selected lines. - """ - assert self.selection_state - - # Get lines. - from_, to = sorted( - [self.cursor_position, self.selection_state.original_cursor_position] - ) - - before = self.text[:from_] - lines = self.text[from_:to].splitlines() - after = self.text[to:] - - # Replace leading spaces with just one space. - lines = [l.lstrip(" ") + separator for l in lines] - - # Set new document. - self.document = Document( - text=before + "".join(lines) + after, - cursor_position=len(before + "".join(lines[:-1])) - 1, - ) - - def swap_characters_before_cursor(self) -> None: - """ - Swap the last two characters before the cursor. - """ - pos = self.cursor_position - - if pos >= 2: - a = self.text[pos - 2] - b = self.text[pos - 1] - - self.text = self.text[: pos - 2] + b + a + self.text[pos:] - - def go_to_history(self, index: int) -> None: - """ - Go to this item in the history. - """ - if index < len(self._working_lines): - self.working_index = index - self.cursor_position = len(self.text) - - def complete_next(self, count: int = 1, disable_wrap_around: bool = False) -> None: - """ - Browse to the next completions. - (Does nothing if there are no completion.) - """ - index: Optional[int] - - if self.complete_state: - completions_count = len(self.complete_state.completions) - - if self.complete_state.complete_index is None: - index = 0 - elif self.complete_state.complete_index == completions_count - 1: - index = None - - if disable_wrap_around: - return - else: - index = min( - completions_count - 1, self.complete_state.complete_index + count - ) - self.go_to_completion(index) - - def complete_previous( - self, count: int = 1, disable_wrap_around: bool = False - ) -> None: - """ - Browse to the previous completions. - (Does nothing if there are no completion.) - """ - index: Optional[int] - - if self.complete_state: - if self.complete_state.complete_index == 0: - index = None - - if disable_wrap_around: - return - elif self.complete_state.complete_index is None: - index = len(self.complete_state.completions) - 1 - else: - index = max(0, self.complete_state.complete_index - count) - - self.go_to_completion(index) - - def cancel_completion(self) -> None: - """ - Cancel completion, go back to the original text. - """ - if self.complete_state: - self.go_to_completion(None) - self.complete_state = None - - def _set_completions(self, completions: List[Completion]) -> CompletionState: - """ - Start completions. (Generate list of completions and initialize.) - - By default, no completion will be selected. - """ - self.complete_state = CompletionState( - original_document=self.document, completions=completions - ) - - # Trigger event. This should eventually invalidate the layout. - self.on_completions_changed.fire() - - return self.complete_state - - 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() - completions = [] - - # For every line of the whole history, find matches with the current line. - current_line = self.document.current_line_before_cursor.lstrip() - - for i, string in enumerate(self._working_lines): - for j, l in enumerate(string.split("\n")): - l = l.strip() - if l and l.startswith(current_line): - # When a new line has been found. - if l not in found_completions: - found_completions.add(l) - - # Create completion. - if i == self.working_index: - display_meta = "Current, line %s" % (j + 1) - else: - display_meta = f"History {i + 1}, line {j + 1}" - - completions.append( - Completion( - text=l, - start_position=-len(current_line), - display_meta=display_meta, - ) - ) - - self._set_completions(completions=completions[::-1]) - self.go_to_completion(0) - - def go_to_completion(self, index: Optional[int]) -> None: - """ - Select a completion from the list of current completions. - """ - assert self.complete_state - - # Set new completion - state = self.complete_state - state.go_to_index(index) - - # Set text/cursor position - new_text, new_cursor_position = state.new_text_and_position() - self.document = Document(new_text, new_cursor_position) - - # (changing text/cursor position will unset complete_state.) - self.complete_state = state - - def apply_completion(self, completion: Completion) -> None: - """ - Insert a given completion. - """ - # If there was already a completion active, cancel that one. - if self.complete_state: - self.go_to_completion(None) - self.complete_state = None - - # Insert text from the given completion. - self.delete_before_cursor(-completion.start_position) - self.insert_text(completion.text) - - def _set_history_search(self) -> None: - """ - Set `history_search_text`. - (The text before the cursor will be used for filtering the history.) - """ - if self.enable_history_search(): - if self.history_search_text is None: - self.history_search_text = self.document.text_before_cursor - else: - self.history_search_text = None - - def _history_matches(self, i: int) -> bool: - """ - True when the current entry matches the history search. - (when we don't have history search, it's also True.) - """ - return self.history_search_text is None or self._working_lines[i].startswith( - self.history_search_text - ) - - def history_forward(self, count: int = 1) -> None: - """ - Move forwards through the history. - - :param count: Amount of items to move forward. - """ - self._set_history_search() - - # Go forward in history. - found_something = False - - for i in range(self.working_index + 1, len(self._working_lines)): - if self._history_matches(i): - self.working_index = i - count -= 1 - found_something = True - if count == 0: - break - - # If we found an entry, move cursor to the end of the first line. - if found_something: - self.cursor_position = 0 - self.cursor_position += self.document.get_end_of_line_position() - - def history_backward(self, count: int = 1) -> None: - """ - Move backwards through history. - """ - self._set_history_search() - - # Go back in history. - found_something = False - - for i in range(self.working_index - 1, -1, -1): - if self._history_matches(i): - self.working_index = i - count -= 1 - found_something = True - if count == 0: - break - - # If we move to another entry, move cursor to the end of the line. - if found_something: - self.cursor_position = len(self.text) - - def yank_nth_arg( - self, n: Optional[int] = 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 - history if called repeatedly. If no `n` has been given, take the first - argument. (The second word.) - - :param n: (None or int), The index of the word from the previous line - to take. - """ - assert n is None or isinstance(n, int) - history_strings = self.history.get_strings() - - if not len(history_strings): - return - - # Make sure we have a `YankNthArgState`. - if self.yank_nth_arg_state is None: - state = YankNthArgState(n=-1 if _yank_last_arg else 1) - else: - state = self.yank_nth_arg_state - - if n is not None: - state.n = n - - # Get new history position. - new_pos = state.history_position - 1 - if -new_pos > len(history_strings): - new_pos = -1 - - # Take argument from line. - line = history_strings[new_pos] - - words = [w.strip() for w in _QUOTED_WORDS_RE.split(line)] - words = [w for w in words if w] - try: - word = words[state.n] - except IndexError: - word = "" - - # Insert new argument. - if state.previous_inserted_word: - self.delete_before_cursor(len(state.previous_inserted_word)) - self.insert_text(word) - - # Save state again for next completion. (Note that the 'insert' - # operation from above clears `self.yank_nth_arg_state`.) - state.previous_inserted_word = word - state.history_position = new_pos - self.yank_nth_arg_state = state - - def yank_last_arg(self, n: Optional[int] = None) -> None: - """ - Like `yank_nth_arg`, but if no argument has been given, yank the last - word by default. - """ - self.yank_nth_arg(n=n, _yank_last_arg=True) - - def start_selection( - self, selection_type: SelectionType = SelectionType.CHARACTERS - ) -> None: - """ - Take the current cursor position as the start of this selection. - """ - self.selection_state = SelectionState(self.cursor_position, selection_type) - - def copy_selection(self, _cut: bool = False) -> ClipboardData: - """ - Copy selected text and return :class:`.ClipboardData` instance. - - Notice that this doesn't store the copied data on the clipboard yet. - You can store it like this: - - .. code:: python - - data = buffer.copy_selection() - get_app().clipboard.set_data(data) - """ - new_document, clipboard_data = self.document.cut_selection() - if _cut: - self.document = new_document - - self.selection_state = None - return clipboard_data - - def cut_selection(self) -> ClipboardData: - """ - Delete selected text and return :class:`.ClipboardData` instance. - """ - return self.copy_selection(_cut=True) - - def paste_clipboard_data( - self, - data: ClipboardData, - paste_mode: PasteMode = PasteMode.EMACS, - count: int = 1, - ) -> None: - """ - Insert the data from the clipboard. - """ - assert isinstance(data, ClipboardData) - assert paste_mode in (PasteMode.VI_BEFORE, PasteMode.VI_AFTER, PasteMode.EMACS) - - original_document = self.document - self.document = self.document.paste_clipboard_data( - data, paste_mode=paste_mode, count=count - ) - - # Remember original document. This assignment should come at the end, - # because assigning to 'document' will erase it. - self.document_before_paste = original_document - - def newline(self, copy_margin: bool = True) -> None: - """ - Insert a line ending at the current position. - """ - if copy_margin: - self.insert_text("\n" + self.document.leading_whitespace_in_current_line) - else: - self.insert_text("\n") - - def insert_line_above(self, copy_margin: bool = True) -> None: - """ - Insert a new line above the current one. - """ - if copy_margin: - insert = self.document.leading_whitespace_in_current_line + "\n" - else: - insert = "\n" - - self.cursor_position += self.document.get_start_of_line_position() - self.insert_text(insert) - self.cursor_position -= 1 - - def insert_line_below(self, copy_margin: bool = True) -> None: - """ - Insert a new line below the current one. - """ - if copy_margin: - insert = "\n" + self.document.leading_whitespace_in_current_line - else: - insert = "\n" - - self.cursor_position += self.document.get_end_of_line_position() - self.insert_text(insert) - - def insert_text( - self, - data: str, - overwrite: bool = False, - move_cursor: bool = True, - fire_event: bool = True, - ) -> None: - """ - Insert characters at cursor position. - - :param fire_event: Fire `on_text_insert` event. This is mainly used to - trigger autocompletion while typing. - """ - # Original text & cursor position. - otext = self.text - ocpos = self.cursor_position - - # In insert/text mode. - if overwrite: - # Don't overwrite the newline itself. Just before the line ending, - # it should act like insert mode. - overwritten_text = otext[ocpos : ocpos + len(data)] - if "\n" in overwritten_text: - overwritten_text = overwritten_text[: overwritten_text.find("\n")] - - text = otext[:ocpos] + data + otext[ocpos + len(overwritten_text) :] - else: - text = otext[:ocpos] + data + otext[ocpos:] - - if move_cursor: - cpos = self.cursor_position + len(data) - else: - cpos = self.cursor_position - - # Set new document. - # (Set text and cursor position at the same time. Otherwise, setting - # the text will fire a change event before the cursor position has been - # set. It works better to have this atomic.) - self.document = Document(text, cpos) - - # Fire 'on_text_insert' event. - if fire_event: # XXX: rename to `start_complete`. - self.on_text_insert.fire() - - # Only complete when "complete_while_typing" is enabled. - if self.completer and self.complete_while_typing(): - get_app().create_background_task(self._async_completer()) - - # Call auto_suggest. - if self.auto_suggest: - get_app().create_background_task(self._async_suggester()) - - def undo(self) -> None: - # Pop from the undo-stack until we find a text that if different from - # the current text. (The current logic of `save_to_undo_stack` will - # cause that the top of the undo stack is usually the same as the - # current text, so in that case we have to pop twice.) - while self._undo_stack: - text, pos = self._undo_stack.pop() - - if text != self.text: - # Push current text to redo stack. - self._redo_stack.append((self.text, self.cursor_position)) - - # Set new text/cursor_position. - self.document = Document(text, cursor_position=pos) - break - - def redo(self) -> None: - if self._redo_stack: - # Copy current state on undo stack. - self.save_to_undo_stack(clear_redo_stack=False) - - # Pop state from redo stack. - text, pos = self._redo_stack.pop() - self.document = Document(text, cursor_position=pos) - - def validate(self, set_cursor: bool = False) -> bool: - """ - Returns `True` if valid. - - :param set_cursor: Set the cursor position, if an error was found. - """ - # Don't call the validator again, if it was already called for the - # current input. - if self.validation_state != ValidationState.UNKNOWN: - return self.validation_state == ValidationState.VALID - - # Call validator. - if self.validator: - try: - self.validator.validate(self.document) - except ValidationError as e: - # Set cursor position (don't allow invalid values.) - if set_cursor: - self.cursor_position = min( - max(0, e.cursor_position), len(self.text) - ) - - self.validation_state = ValidationState.INVALID - self.validation_error = e - return False - - # Handle validation result. - self.validation_state = ValidationState.VALID - self.validation_error = None - return True - - async def _validate_async(self) -> None: - """ - Asynchronous version of `validate()`. - This one doesn't set the cursor position. - - We have both variants, because a synchronous version is required. - Handling the ENTER key needs to be completely synchronous, otherwise - stuff like type-ahead is going to give very weird results. (People - could type input while the ENTER key is still processed.) - - An asynchronous version is required if we have `validate_while_typing` - enabled. - """ - while True: - # Don't call the validator again, if it was already called for the - # current input. - if self.validation_state != ValidationState.UNKNOWN: - return - - # Call validator. - error = None - document = self.document - - if self.validator: - try: - await self.validator.validate_async(self.document) - except ValidationError as e: - error = e - - # If the document changed during the validation, try again. - if self.document != document: - continue - - # Handle validation result. - if error: - self.validation_state = ValidationState.INVALID - else: - self.validation_state = ValidationState.VALID - - self.validation_error = error - get_app().invalidate() # Trigger redraw (display error). - - def append_to_history(self) -> None: - """ - Append the current input to the history. - """ - # Save at the tail of the history. (But don't if the last entry the - # history is already the same.) - if self.text: - history_strings = self.history.get_strings() - if not len(history_strings) or history_strings[-1] != self.text: - self.history.append_string(self.text) - - def _search( - self, - search_state: SearchState, - include_current_position: bool = False, - count: int = 1, - ) -> Optional[Tuple[int, int]]: - """ - Execute search. Return (working_index, cursor_position) tuple when this - search is applied. Returns `None` when this text cannot be found. - """ - assert count > 0 - - text = search_state.text - direction = search_state.direction - ignore_case = search_state.ignore_case() - - def search_once( - working_index: int, document: Document - ) -> Optional[Tuple[int, Document]]: - """ - Do search one time. - Return (working_index, document) or `None` - """ - if direction == SearchDirection.FORWARD: - # Try find at the current input. - new_index = document.find( - text, - include_current_position=include_current_position, - ignore_case=ignore_case, - ) - - if new_index is not None: - return ( - working_index, - Document(document.text, document.cursor_position + new_index), - ) - else: - # No match, go forward in the history. (Include len+1 to wrap around.) - # (Here we should always include all cursor positions, because - # it's a different line.) - for i in range(working_index + 1, len(self._working_lines) + 1): - i %= len(self._working_lines) - - document = Document(self._working_lines[i], 0) - new_index = document.find( - text, include_current_position=True, ignore_case=ignore_case - ) - if new_index is not None: - return (i, Document(document.text, new_index)) - else: - # Try find at the current input. - new_index = document.find_backwards(text, ignore_case=ignore_case) - - if new_index is not None: - return ( - working_index, - Document(document.text, document.cursor_position + new_index), - ) - else: - # No match, go back in the history. (Include -1 to wrap around.) - for i in range(working_index - 1, -2, -1): - i %= len(self._working_lines) - - document = Document( - self._working_lines[i], len(self._working_lines[i]) - ) - new_index = document.find_backwards( - text, ignore_case=ignore_case - ) - if new_index is not None: - return ( - i, - Document(document.text, len(document.text) + new_index), - ) - return None - - # Do 'count' search iterations. - working_index = self.working_index - document = self.document - for _ in range(count): - result = search_once(working_index, document) - if result is None: - return None # Nothing found. - else: - working_index, document = result - - return (working_index, document.cursor_position) - - def document_for_search(self, search_state: SearchState) -> Document: - """ - Return a :class:`~prompt_toolkit.document.Document` instance that has - the text/cursor position for this search, if we would apply it. This - will be used in the - :class:`~prompt_toolkit.layout.BufferControl` to display feedback while - searching. - """ - search_result = self._search(search_state, include_current_position=True) - - if search_result is None: - return self.document - else: - working_index, cursor_position = search_result - - # Keep selection, when `working_index` was not changed. - if working_index == self.working_index: - selection = self.selection_state - else: - selection = None - - return Document( - self._working_lines[working_index], cursor_position, selection=selection - ) - - def get_search_position( - self, - search_state: SearchState, - include_current_position: bool = True, - count: int = 1, - ) -> int: - """ - Get the cursor position for this search. - (This operation won't change the `working_index`. It's won't go through - the history. Vi text objects can't span multiple items.) - """ - search_result = self._search( - search_state, include_current_position=include_current_position, count=count - ) - - if search_result is None: - return self.cursor_position - else: - working_index, cursor_position = search_result - return cursor_position - - def apply_search( - self, - search_state: SearchState, - include_current_position: bool = True, - count: int = 1, - ) -> None: - """ - Apply search. If something is found, set `working_index` and - `cursor_position`. - """ - search_result = self._search( - search_state, include_current_position=include_current_position, count=count - ) - - if search_result is not None: - working_index, cursor_position = search_result - self.working_index = working_index - self.cursor_position = cursor_position - - def exit_selection(self) -> None: - self.selection_state = None - - def _editor_simple_tempfile(self) -> Tuple[str, Callable[[], None]]: - """ - Simple (file) tempfile implementation. - Return (tempfile, cleanup_func). - """ - suffix = to_str(self.tempfile_suffix) - descriptor, filename = tempfile.mkstemp(suffix) - - os.write(descriptor, self.text.encode("utf-8")) - os.close(descriptor) - - def cleanup() -> None: - os.unlink(filename) - - return filename, cleanup - - def _editor_complex_tempfile(self) -> Tuple[str, Callable[[], None]]: - # Complex (directory) tempfile implementation. - headtail = to_str(self.tempfile) - if not headtail: - # Revert to simple case. - return self._editor_simple_tempfile() - headtail = str(headtail) - - # Try to make according to tempfile logic. - head, tail = os.path.split(headtail) - if os.path.isabs(head): - head = head[1:] - - dirpath = tempfile.mkdtemp() - if head: - dirpath = os.path.join(dirpath, head) - # Assume there is no issue creating dirs in this temp dir. - os.makedirs(dirpath) - - # Open the filename and write current text. - filename = os.path.join(dirpath, tail) - with open(filename, "w", encoding="utf-8") as fh: - fh.write(self.text) - - def cleanup() -> None: - shutil.rmtree(dirpath) - - return filename, cleanup - - def open_in_editor(self, validate_and_handle: bool = False) -> "asyncio.Task[None]": - """ - Open code in editor. - - This returns a future, and runs in a thread executor. - """ - if self.read_only(): - raise EditReadOnlyBuffer() - - # Write current text to temporary file - if self.tempfile: - filename, cleanup_func = self._editor_complex_tempfile() - else: - filename, cleanup_func = self._editor_simple_tempfile() - - async def run() -> None: - try: - # Open in editor - # (We need to use `run_in_terminal`, because not all editors go to - # the alternate screen buffer, and some could influence the cursor - # position.) - succes = await run_in_terminal( - lambda: self._open_file_in_editor(filename), in_executor=True - ) - - # Read content again. - if succes: - with open(filename, "rb") as f: - text = f.read().decode("utf-8") - - # Drop trailing newline. (Editors are supposed to add it at the - # end, but we don't need it.) - if text.endswith("\n"): - text = text[:-1] - - self.document = Document(text=text, cursor_position=len(text)) - - # Accept the input. - if validate_and_handle: - self.validate_and_handle() - - finally: - # Clean up temp dir/file. - cleanup_func() - - return get_app().create_background_task(run()) - - def _open_file_in_editor(self, filename: str) -> bool: - """ - Call editor executable. - - Return True when we received a zero return code. - """ - # If the 'VISUAL' or 'EDITOR' environment variable has been set, use that. - # Otherwise, fall back to the first available editor that we can find. - visual = os.environ.get("VISUAL") - editor = os.environ.get("EDITOR") - - editors = [ - visual, - editor, - # Order of preference. - "/usr/bin/editor", - "/usr/bin/nano", - "/usr/bin/pico", - "/usr/bin/vi", - "/usr/bin/emacs", - ] - - for e in editors: - if e: - try: - # Use 'shlex.split()', because $VISUAL can contain spaces - # and quotes. - returncode = subprocess.call(shlex.split(e) + [filename]) - return returncode == 0 - - except OSError: - # Executable does not exist, try the next one. - pass - - return False - - def start_completion( - self, - select_first: bool = False, - select_last: bool = False, - insert_common_part: bool = False, - complete_event: Optional[CompleteEvent] = None, - ) -> None: - """ - Start asynchronous autocompletion of this buffer. - (This will do nothing if a previous completion was still in progress.) - """ - # Only one of these options can be selected. - assert select_first + select_last + insert_common_part <= 1 - - get_app().create_background_task( - self._async_completer( - select_first=select_first, - select_last=select_last, - insert_common_part=insert_common_part, - complete_event=complete_event - or CompleteEvent(completion_requested=True), - ) - ) - - def _create_completer_coroutine(self) -> Callable[..., Coroutine[Any, Any, None]]: - """ - Create function for asynchronous autocompletion. - - (This consumes the asynchronous completer generator, which possibly - runs the completion algorithm in another thread.) - """ - - def completion_does_nothing(document: Document, completion: Completion) -> bool: - """ - Return `True` if applying this completion doesn't have any effect. - (When it doesn't insert any new text. - """ - text_before_cursor = document.text_before_cursor - replaced_text = text_before_cursor[ - len(text_before_cursor) + completion.start_position : - ] - return replaced_text == completion.text - - @_only_one_at_a_time - async def async_completer( - select_first: bool = False, - select_last: bool = False, - insert_common_part: bool = False, - complete_event: Optional[CompleteEvent] = None, - ) -> None: - - document = self.document - complete_event = complete_event or CompleteEvent(text_inserted=True) - - # Don't complete when we already have completions. - if self.complete_state or not self.completer: - return - - # Create an empty CompletionState. - complete_state = CompletionState(original_document=self.document) - self.complete_state = complete_state - - def proceed() -> bool: - """Keep retrieving completions. Input text has not yet changed - while generating completions.""" - return self.complete_state == complete_state - - async for completion in self.completer.get_completions_async( - document, complete_event - ): - complete_state.completions.append(completion) - self.on_completions_changed.fire() - - # If the input text changes, abort. - if not proceed(): - break - - completions = complete_state.completions - - # When there is only one completion, which has nothing to add, ignore it. - if len(completions) == 1 and completion_does_nothing( - document, completions[0] - ): - del completions[:] - - # Set completions if the text was not yet changed. - if proceed(): - # When no completions were found, or when the user selected - # already a completion by using the arrow keys, don't do anything. - if ( - not self.complete_state - or self.complete_state.complete_index is not None - ): - return - - # When there are no completions, reset completion state anyway. - if not completions: - self.complete_state = None - # Render the ui if the completion menu was shown - # it is needed especially if there is one completion and it was deleted. - self.on_completions_changed.fire() - return - - # Select first/last or insert common part, depending on the key - # binding. (For this we have to wait until all completions are - # loaded.) - - if select_first: - self.go_to_completion(0) - - elif select_last: - self.go_to_completion(len(completions) - 1) - - elif insert_common_part: - common_part = get_common_complete_suffix(document, completions) - if common_part: - # Insert the common part, update completions. - self.insert_text(common_part) - if len(completions) > 1: - # (Don't call `async_completer` again, but - # recalculate completions. See: - # https://github.com/ipython/ipython/issues/9658) - completions[:] = [ - c.new_completion_from_position(len(common_part)) - for c in completions - ] - - self._set_completions(completions=completions) - else: - self.complete_state = None - else: - # When we were asked to insert the "common" - # prefix, but there was no common suffix but - # still exactly one match, then select the - # first. (It could be that we have a completion - # which does * expansion, like '*.py', with - # exactly one match.) - if len(completions) == 1: - self.go_to_completion(0) - - else: - # If the last operation was an insert, (not a delete), restart - # the completion coroutine. - - if self.document.text_before_cursor == document.text_before_cursor: - return # Nothing changed. - - if self.document.text_before_cursor.startswith( - document.text_before_cursor - ): - raise _Retry - - return async_completer - - def _create_auto_suggest_coroutine(self) -> Callable[[], Coroutine[Any, Any, None]]: - """ - Create function for asynchronous auto suggestion. - (This can be in another thread.) - """ - - @_only_one_at_a_time - async def async_suggestor() -> None: - document = self.document - - # Don't suggest when we already have a suggestion. - if self.suggestion or not self.auto_suggest: - return - - suggestion = await self.auto_suggest.get_suggestion_async(self, document) - - # Set suggestion only if the text was not yet changed. - if self.document == document: - # Set suggestion and redraw interface. - self.suggestion = suggestion - self.on_suggestion_set.fire() - else: - # Otherwise, restart thread. - raise _Retry - - return async_suggestor - - def _create_auto_validate_coroutine( - self, - ) -> Callable[[], Coroutine[Any, Any, None]]: - """ - Create a function for asynchronous validation while typing. - (This can be in another thread.) - """ - - @_only_one_at_a_time - async def async_validator() -> None: - await self._validate_async() - - return async_validator - - def validate_and_handle(self) -> None: - """ - Validate buffer and handle the accept action. - """ - valid = self.validate(set_cursor=True) - - # When the validation succeeded, accept the input. - if valid: - if self.accept_handler: - keep_text = self.accept_handler(self) - else: - keep_text = False - - self.append_to_history() - - if not keep_text: - self.reset() - - -_T = TypeVar("_T", bound=Callable[..., Awaitable[None]]) - - -def _only_one_at_a_time(coroutine: _T) -> _T: - """ - Decorator that only starts the coroutine only if the previous call has - finished. (Used to make sure that we have only one autocompleter, auto - suggestor and validator running at a time.) - - When the coroutine raises `_Retry`, it is restarted. - """ - running = False - - @wraps(coroutine) - async def new_coroutine(*a: Any, **kw: Any) -> Any: - nonlocal running - - # Don't start a new function, if the previous is still in progress. - if running: - return - - running = True - - try: - while True: - try: - await coroutine(*a, **kw) - except _Retry: - continue - else: - return None - finally: - running = False - - return cast(_T, new_coroutine) - - -class _Retry(Exception): - "Retry in `_only_one_at_a_time`." - - -def indent(buffer: Buffer, from_row: int, to_row: int, count: int = 1) -> None: - """ - Indent text of a :class:`.Buffer` object. - """ - current_row = buffer.document.cursor_position_row - line_range = range(from_row, to_row) - - # Apply transformation. - new_text = buffer.transform_lines(line_range, lambda l: " " * count + l) - buffer.document = Document( - new_text, Document(new_text).translate_row_col_to_index(current_row, 0) - ) - - # Go to the start of the line. - buffer.cursor_position += buffer.document.get_start_of_line_position( - after_whitespace=True - ) - - -def unindent(buffer: Buffer, from_row: int, to_row: int, count: int = 1) -> None: - """ - Unindent text of a :class:`.Buffer` object. - """ - current_row = buffer.document.cursor_position_row - line_range = range(from_row, to_row) - - def transform(text: str) -> str: - remove = " " * count - if text.startswith(remove): - return text[len(remove) :] - else: - return text.lstrip() - - # Apply transformation. - new_text = buffer.transform_lines(line_range, transform) - buffer.document = Document( - new_text, Document(new_text).translate_row_col_to_index(current_row, 0) - ) - - # Go to the start of the line. - buffer.cursor_position += buffer.document.get_start_of_line_position( - after_whitespace=True - ) - - -def reshape_text(buffer: Buffer, from_row: int, to_row: int) -> None: - """ - Reformat text, taking the width into account. - `to_row` is included. - (Vi 'gq' operator.) - """ - lines = buffer.text.splitlines(True) - lines_before = lines[:from_row] - lines_after = lines[to_row + 1 :] - lines_to_reformat = lines[from_row : to_row + 1] - - if lines_to_reformat: - # Take indentation from the first line. - match = re.search(r"^\s*", lines_to_reformat[0]) - length = match.end() if match else 0 # `match` can't be None, actually. - - indent = lines_to_reformat[0][:length].replace("\n", "") - - # Now, take all the 'words' from the lines to be reshaped. - words = "".join(lines_to_reformat).split() - - # And reshape. - width = (buffer.text_width or 80) - len(indent) - reshaped_text = [indent] - current_width = 0 - for w in words: - if current_width: - if len(w) + current_width + 1 > width: - reshaped_text.append("\n") - reshaped_text.append(indent) - current_width = 0 - else: - reshaped_text.append(" ") - current_width += 1 - - reshaped_text.append(w) - current_width += len(w) - - if reshaped_text[-1] != "\n": - reshaped_text.append("\n") - - # Apply result. - buffer.document = Document( - text="".join(lines_before + reshaped_text + lines_after), - cursor_position=len("".join(lines_before + reshaped_text)), - ) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/cache.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/cache.py deleted file mode 100644 index e5e9d70eca1..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/cache.py +++ /dev/null @@ -1,125 +0,0 @@ -from collections import deque -from functools import wraps -from typing import Any, Callable, Deque, Dict, Generic, Hashable, Tuple, TypeVar, cast - -__all__ = [ - "SimpleCache", - "FastDictCache", - "memoized", -] - -_T = TypeVar("_T", bound=Hashable) -_U = TypeVar("_U") - - -class SimpleCache(Generic[_T, _U]): - """ - Very simple cache that discards the oldest item when the cache size is - exceeded. - - :param maxsize: Maximum size of the cache. (Don't make it too big.) - """ - - def __init__(self, maxsize: int = 8) -> None: - assert maxsize > 0 - - self._data: Dict[_T, _U] = {} - self._keys: Deque[_T] = deque() - self.maxsize: int = maxsize - - def get(self, key: _T, getter_func: Callable[[], _U]) -> _U: - """ - Get object from the cache. - If not found, call `getter_func` to resolve it, and put that on the top - of the cache instead. - """ - # Look in cache first. - try: - return self._data[key] - except KeyError: - # Not found? Get it. - value = getter_func() - self._data[key] = value - self._keys.append(key) - - # Remove the oldest key when the size is exceeded. - if len(self._data) > self.maxsize: - key_to_remove = self._keys.popleft() - if key_to_remove in self._data: - del self._data[key_to_remove] - - return value - - def clear(self) -> None: - "Clear cache." - self._data = {} - self._keys = deque() - - -_K = TypeVar("_K", bound=Tuple[Hashable, ...]) -_V = TypeVar("_V") - - -class FastDictCache(Dict[_K, _V]): - """ - Fast, lightweight cache which keeps at most `size` items. - It will discard the oldest items in the cache first. - - The cache is a dictionary, which doesn't keep track of access counts. - It is perfect to cache little immutable objects which are not expensive to - create, but where a dictionary lookup is still much faster than an object - instantiation. - - :param get_value: Callable that's called in case of a missing key. - """ - - # NOTE: This cache is used to cache `prompt_toolkit.layout.screen.Char` and - # `prompt_toolkit.Document`. Make sure to keep this really lightweight. - # Accessing the cache should stay faster than instantiating new - # objects. - # (Dictionary lookups are really fast.) - # SimpleCache is still required for cases where the cache key is not - # the same as the arguments given to the function that creates the - # value.) - def __init__(self, get_value: Callable[..., _V], size: int = 1000000) -> None: - assert size > 0 - - self._keys: Deque[_K] = deque() - self.get_value = get_value - self.size = size - - def __missing__(self, key: _K) -> _V: - # Remove the oldest key when the size is exceeded. - if len(self) > self.size: - key_to_remove = self._keys.popleft() - if key_to_remove in self: - del self[key_to_remove] - - result = self.get_value(*key) - self[key] = result - self._keys.append(key) - return result - - -_F = TypeVar("_F", bound=Callable[..., object]) - - -def memoized(maxsize: int = 1024) -> Callable[[_F], _F]: - """ - Memoization decorator for immutable classes and pure functions. - """ - - def decorator(obj: _F) -> _F: - cache: SimpleCache[Hashable, Any] = SimpleCache(maxsize=maxsize) - - @wraps(obj) - def new_callable(*a: Any, **kw: Any) -> Any: - def create_new() -> Any: - return obj(*a, **kw) - - key = (a, tuple(sorted(kw.items()))) - return cache.get(key, create_new) - - return cast(_F, new_callable) - - return decorator diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/clipboard/__init__.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/clipboard/__init__.py deleted file mode 100644 index 160b50aca5e..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/clipboard/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -from .base import Clipboard, ClipboardData, DummyClipboard, DynamicClipboard -from .in_memory import InMemoryClipboard - -# We are not importing `PyperclipClipboard` here, because it would require the -# `pyperclip` module to be present. - -# from .pyperclip import PyperclipClipboard - -__all__ = [ - "Clipboard", - "ClipboardData", - "DummyClipboard", - "DynamicClipboard", - "InMemoryClipboard", -] diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/clipboard/base.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/clipboard/base.py deleted file mode 100644 index c24623dacc0..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/clipboard/base.py +++ /dev/null @@ -1,107 +0,0 @@ -""" -Clipboard for command line interface. -""" -from abc import ABCMeta, abstractmethod -from typing import Callable, Optional - -from prompt_toolkit.selection import SelectionType - -__all__ = [ - "Clipboard", - "ClipboardData", - "DummyClipboard", - "DynamicClipboard", -] - - -class ClipboardData: - """ - Text on the clipboard. - - :param text: string - :param type: :class:`~prompt_toolkit.selection.SelectionType` - """ - - def __init__( - self, text: str = "", type: SelectionType = SelectionType.CHARACTERS - ) -> None: - - self.text = text - self.type = type - - -class Clipboard(metaclass=ABCMeta): - """ - Abstract baseclass for clipboards. - (An implementation can be in memory, it can share the X11 or Windows - keyboard, or can be persistent.) - """ - - @abstractmethod - def set_data(self, data: ClipboardData) -> None: - """ - Set data to the clipboard. - - :param data: :class:`~.ClipboardData` instance. - """ - - def set_text(self, text: str) -> None: # Not abstract. - """ - Shortcut for setting plain text on clipboard. - """ - self.set_data(ClipboardData(text)) - - def rotate(self) -> None: - """ - For Emacs mode, rotate the kill ring. - """ - - @abstractmethod - def get_data(self) -> ClipboardData: - """ - Return clipboard data. - """ - - -class DummyClipboard(Clipboard): - """ - Clipboard implementation that doesn't remember anything. - """ - - def set_data(self, data: ClipboardData) -> None: - pass - - def set_text(self, text: str) -> None: - pass - - def rotate(self) -> None: - pass - - def get_data(self) -> ClipboardData: - return ClipboardData() - - -class DynamicClipboard(Clipboard): - """ - Clipboard class that can dynamically returns any Clipboard. - - :param get_clipboard: Callable that returns a :class:`.Clipboard` instance. - """ - - def __init__(self, get_clipboard: Callable[[], Optional[Clipboard]]) -> None: - self.get_clipboard = get_clipboard - - def _clipboard(self) -> Clipboard: - return self.get_clipboard() or DummyClipboard() - - def set_data(self, data: ClipboardData) -> None: - self._clipboard().set_data(data) - - def set_text(self, text: str) -> None: - self._clipboard().set_text(text) - - def rotate(self) -> None: - self._clipboard().rotate() - - def get_data(self) -> ClipboardData: - return self._clipboard().get_data() diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/clipboard/in_memory.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/clipboard/in_memory.py deleted file mode 100644 index 1902e36a55f..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/clipboard/in_memory.py +++ /dev/null @@ -1,46 +0,0 @@ -from collections import deque -from typing import Deque, Optional - -from .base import Clipboard, ClipboardData - -__all__ = [ - "InMemoryClipboard", -] - - -class InMemoryClipboard(Clipboard): - """ - Default clipboard implementation. - Just keep the data in memory. - - This implements a kill-ring, for Emacs mode. - """ - - def __init__( - self, data: Optional[ClipboardData] = None, max_size: int = 60 - ) -> None: - - assert max_size >= 1 - - self.max_size = max_size - self._ring: Deque[ClipboardData] = deque() - - if data is not None: - self.set_data(data) - - def set_data(self, data: ClipboardData) -> None: - self._ring.appendleft(data) - - while len(self._ring) > self.max_size: - self._ring.pop() - - def get_data(self) -> ClipboardData: - if self._ring: - return self._ring[0] - else: - return ClipboardData() - - def rotate(self) -> None: - if self._ring: - # Add the very first item at the end. - self._ring.append(self._ring.popleft()) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/clipboard/pyperclip.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/clipboard/pyperclip.py deleted file mode 100644 index c8cb7afb672..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/clipboard/pyperclip.py +++ /dev/null @@ -1,42 +0,0 @@ -from typing import Optional - -import pyperclip - -from prompt_toolkit.selection import SelectionType - -from .base import Clipboard, ClipboardData - -__all__ = [ - "PyperclipClipboard", -] - - -class PyperclipClipboard(Clipboard): - """ - Clipboard that synchronizes with the Windows/Mac/Linux system clipboard, - using the pyperclip module. - """ - - def __init__(self) -> None: - self._data: Optional[ClipboardData] = None - - def set_data(self, data: ClipboardData) -> None: - self._data = data - pyperclip.copy(data.text) - - def get_data(self) -> ClipboardData: - text = pyperclip.paste() - - # When the clipboard data is equal to what we copied last time, reuse - # the `ClipboardData` instance. That way we're sure to keep the same - # `SelectionType`. - if self._data and self._data.text == text: - return self._data - - # Pyperclip returned something else. Create a new `ClipboardData` - # instance. - else: - return ClipboardData( - text=text, - type=SelectionType.LINES if "\n" in text else SelectionType.CHARACTERS, - ) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/completion/__init__.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/completion/__init__.py deleted file mode 100644 index 270c74337c8..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/completion/__init__.py +++ /dev/null @@ -1,41 +0,0 @@ -from .base import ( - CompleteEvent, - Completer, - Completion, - ConditionalCompleter, - DummyCompleter, - DynamicCompleter, - ThreadedCompleter, - get_common_complete_suffix, - merge_completers, -) -from .deduplicate import DeduplicateCompleter -from .filesystem import ExecutableCompleter, PathCompleter -from .fuzzy_completer import FuzzyCompleter, FuzzyWordCompleter -from .nested import NestedCompleter -from .word_completer import WordCompleter - -__all__ = [ - # Base. - "Completion", - "Completer", - "ThreadedCompleter", - "DummyCompleter", - "DynamicCompleter", - "CompleteEvent", - "ConditionalCompleter", - "merge_completers", - "get_common_complete_suffix", - # Filesystem. - "PathCompleter", - "ExecutableCompleter", - # Fuzzy - "FuzzyCompleter", - "FuzzyWordCompleter", - # Nested. - "NestedCompleter", - # Word completer. - "WordCompleter", - # Deduplicate - "DeduplicateCompleter", -] diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/completion/base.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/completion/base.py deleted file mode 100644 index 371c0ea6e5c..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/completion/base.py +++ /dev/null @@ -1,396 +0,0 @@ -""" -""" -from abc import ABCMeta, abstractmethod -from typing import AsyncGenerator, Callable, Iterable, Optional, Sequence - -from prompt_toolkit.document import Document -from prompt_toolkit.eventloop import generator_to_async_generator -from prompt_toolkit.filters import FilterOrBool, to_filter -from prompt_toolkit.formatted_text import AnyFormattedText, StyleAndTextTuples - -__all__ = [ - "Completion", - "Completer", - "ThreadedCompleter", - "DummyCompleter", - "DynamicCompleter", - "CompleteEvent", - "ConditionalCompleter", - "merge_completers", - "get_common_complete_suffix", -] - - -class Completion: - """ - :param text: The new string that will be inserted into the document. - :param start_position: Position relative to the cursor_position where the - new text will start. The text will be inserted between the - start_position and the original cursor position. - :param display: (optional string or formatted text) If the completion has - to be displayed differently in the completion menu. - :param display_meta: (Optional string or formatted text) Meta information - about the completion, e.g. the path or source where it's coming from. - This can also be a callable that returns a string. - :param style: Style string. - :param selected_style: Style string, used for a selected completion. - This can override the `style` parameter. - """ - - def __init__( - self, - text: str, - start_position: int = 0, - display: Optional[AnyFormattedText] = None, - display_meta: Optional[AnyFormattedText] = None, - style: str = "", - selected_style: str = "", - ) -> None: - - from prompt_toolkit.formatted_text import to_formatted_text - - self.text = text - self.start_position = start_position - self._display_meta = display_meta - - if display is None: - display = text - - self.display = to_formatted_text(display) - - self.style = style - self.selected_style = selected_style - - assert self.start_position <= 0 - - def __repr__(self) -> str: - if isinstance(self.display, str) and self.display == self.text: - return "{}(text={!r}, start_position={!r})".format( - self.__class__.__name__, - self.text, - self.start_position, - ) - else: - return "{}(text={!r}, start_position={!r}, display={!r})".format( - self.__class__.__name__, - self.text, - self.start_position, - self.display, - ) - - def __eq__(self, other: object) -> bool: - if not isinstance(other, Completion): - return False - return ( - self.text == other.text - and self.start_position == other.start_position - and self.display == other.display - and self._display_meta == other._display_meta - ) - - def __hash__(self) -> int: - return hash((self.text, self.start_position, self.display, self._display_meta)) - - @property - def display_text(self) -> str: - "The 'display' field as plain text." - from prompt_toolkit.formatted_text import fragment_list_to_text - - return fragment_list_to_text(self.display) - - @property - def display_meta(self) -> StyleAndTextTuples: - "Return meta-text. (This is lazy when using a callable)." - from prompt_toolkit.formatted_text import to_formatted_text - - return to_formatted_text(self._display_meta or "") - - @property - def display_meta_text(self) -> str: - "The 'meta' field as plain text." - from prompt_toolkit.formatted_text import fragment_list_to_text - - return fragment_list_to_text(self.display_meta) - - 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 - it needs to have a list of new completions after inserting the common - prefix. - """ - assert position - self.start_position >= 0 - - return Completion( - text=self.text[position - self.start_position :], - display=self.display, - display_meta=self._display_meta, - ) - - -class CompleteEvent: - """ - Event that called the completer. - - :param text_inserted: When True, it means that completions are requested - because of a text insert. (`Buffer.complete_while_typing`.) - :param completion_requested: When True, it means that the user explicitly - pressed the `Tab` key in order to view the completions. - - These two flags can be used for instance to implement a completer that - shows some completions when ``Tab`` has been pressed, but not - automatically when the user presses a space. (Because of - `complete_while_typing`.) - """ - - def __init__( - self, text_inserted: bool = False, completion_requested: bool = False - ) -> None: - assert not (text_inserted and completion_requested) - - #: Automatic completion while typing. - self.text_inserted = text_inserted - - #: Used explicitly requested completion by pressing 'tab'. - self.completion_requested = completion_requested - - def __repr__(self) -> str: - return "{}(text_inserted={!r}, completion_requested={!r})".format( - self.__class__.__name__, - self.text_inserted, - self.completion_requested, - ) - - -class Completer(metaclass=ABCMeta): - """ - Base class for completer implementations. - """ - - @abstractmethod - def get_completions( - self, document: Document, complete_event: CompleteEvent - ) -> Iterable[Completion]: - """ - This should be a generator that yields :class:`.Completion` instances. - - If the generation of completions is something expensive (that takes a - lot of time), consider wrapping this `Completer` class in a - `ThreadedCompleter`. In that case, the completer algorithm runs in a - background thread and completions will be displayed as soon as they - arrive. - - :param document: :class:`~prompt_toolkit.document.Document` instance. - :param complete_event: :class:`.CompleteEvent` instance. - """ - while False: - yield - - async def get_completions_async( - self, document: Document, complete_event: CompleteEvent - ) -> AsyncGenerator[Completion, None]: - """ - Asynchronous generator for completions. (Probably, you won't have to - override this.) - - Asynchronous generator of :class:`.Completion` objects. - """ - for item in self.get_completions(document, complete_event): - yield item - - -class ThreadedCompleter(Completer): - """ - Wrapper that runs the `get_completions` generator in a thread. - - (Use this to prevent the user interface from becoming unresponsive if the - generation of completions takes too much time.) - - The completions will be displayed as soon as they are produced. The user - can already select a completion, even if not all completions are displayed. - """ - - def __init__(self, completer: Completer) -> None: - self.completer = completer - - def get_completions( - self, document: Document, complete_event: CompleteEvent - ) -> Iterable[Completion]: - return self.completer.get_completions(document, complete_event) - - async def get_completions_async( - self, document: Document, complete_event: CompleteEvent - ) -> AsyncGenerator[Completion, None]: - """ - Asynchronous generator of completions. - """ - async for completion in generator_to_async_generator( - lambda: self.completer.get_completions(document, complete_event) - ): - yield completion - - def __repr__(self) -> str: - return f"ThreadedCompleter({self.completer!r})" - - -class DummyCompleter(Completer): - """ - A completer that doesn't return any completion. - """ - - def get_completions( - self, document: Document, complete_event: CompleteEvent - ) -> Iterable[Completion]: - return [] - - def __repr__(self) -> str: - return "DummyCompleter()" - - -class DynamicCompleter(Completer): - """ - Completer class that can dynamically returns any Completer. - - :param get_completer: Callable that returns a :class:`.Completer` instance. - """ - - def __init__(self, get_completer: Callable[[], Optional[Completer]]) -> None: - self.get_completer = get_completer - - def get_completions( - self, document: Document, complete_event: CompleteEvent - ) -> Iterable[Completion]: - completer = self.get_completer() or DummyCompleter() - return completer.get_completions(document, complete_event) - - async def get_completions_async( - self, document: Document, complete_event: CompleteEvent - ) -> AsyncGenerator[Completion, None]: - completer = self.get_completer() or DummyCompleter() - - async for completion in completer.get_completions_async( - document, complete_event - ): - yield completion - - def __repr__(self) -> str: - return f"DynamicCompleter({self.get_completer!r} -> {self.get_completer()!r})" - - -class ConditionalCompleter(Completer): - """ - Wrapper around any other completer that will enable/disable the completions - depending on whether the received condition is satisfied. - - :param completer: :class:`.Completer` instance. - :param filter: :class:`.Filter` instance. - """ - - def __init__(self, completer: Completer, filter: FilterOrBool) -> None: - self.completer = completer - self.filter = to_filter(filter) - - def __repr__(self) -> str: - return f"ConditionalCompleter({self.completer!r}, filter={self.filter!r})" - - def get_completions( - self, document: Document, complete_event: CompleteEvent - ) -> Iterable[Completion]: - # Get all completions in a blocking way. - if self.filter(): - yield from self.completer.get_completions(document, complete_event) - - async def get_completions_async( - self, document: Document, complete_event: CompleteEvent - ) -> AsyncGenerator[Completion, None]: - - # Get all completions in a non-blocking way. - if self.filter(): - async for item in self.completer.get_completions_async( - document, complete_event - ): - yield item - - -class _MergedCompleter(Completer): - """ - Combine several completers into one. - """ - - def __init__(self, completers: Sequence[Completer]) -> None: - self.completers = completers - - def get_completions( - self, document: Document, complete_event: CompleteEvent - ) -> Iterable[Completion]: - # Get all completions from the other completers in a blocking way. - for completer in self.completers: - yield from completer.get_completions(document, complete_event) - - async def get_completions_async( - self, document: Document, complete_event: CompleteEvent - ) -> AsyncGenerator[Completion, None]: - - # Get all completions from the other completers in a non-blocking way. - for completer in self.completers: - async for item in completer.get_completions_async(document, complete_event): - yield item - - -def merge_completers( - completers: Sequence[Completer], deduplicate: bool = False -) -> Completer: - """ - Combine several completers into one. - - :param deduplicate: If `True`, wrap the result in a `DeduplicateCompleter` - so that completions that would result in the same text will be - deduplicated. - """ - if deduplicate: - from .deduplicate import DeduplicateCompleter - - return DeduplicateCompleter(_MergedCompleter(completers)) - - return _MergedCompleter(completers) - - -def get_common_complete_suffix( - document: Document, completions: Sequence[Completion] -) -> str: - """ - Return the common prefix for all completions. - """ - # Take only completions that don't change the text before the cursor. - def doesnt_change_before_cursor(completion: Completion) -> bool: - end = completion.text[: -completion.start_position] - return document.text_before_cursor.endswith(end) - - completions2 = [c for c in completions if doesnt_change_before_cursor(c)] - - # When there is at least one completion that changes the text before the - # cursor, don't return any common part. - if len(completions2) != len(completions): - return "" - - # Return the common prefix. - def get_suffix(completion: Completion) -> str: - return completion.text[-completion.start_position :] - - return _commonprefix([get_suffix(c) for c in completions2]) - - -def _commonprefix(strings: Iterable[str]) -> str: - # Similar to os.path.commonprefix - if not strings: - return "" - - else: - s1 = min(strings) - s2 = max(strings) - - for i, c in enumerate(s1): - if c != s2[i]: - return s1[:i] - - return s1 diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/completion/deduplicate.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/completion/deduplicate.py deleted file mode 100644 index 6ef95224a6c..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/completion/deduplicate.py +++ /dev/null @@ -1,43 +0,0 @@ -from typing import Iterable, Set - -from prompt_toolkit.document import Document - -from .base import CompleteEvent, Completer, Completion - -__all__ = ["DeduplicateCompleter"] - - -class DeduplicateCompleter(Completer): - """ - Wrapper around a completer that removes duplicates. Only the first unique - completions are kept. - - Completions are considered to be a duplicate if they result in the same - document text when they would be applied. - """ - - def __init__(self, completer: Completer) -> None: - self.completer = completer - - 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() - - for completion in self.completer.get_completions(document, complete_event): - text_if_applied = ( - document.text[: document.cursor_position + completion.start_position] - + completion.text - + document.text[document.cursor_position :] - ) - - if text_if_applied == document.text: - # Don't include completions that don't have any effect at all. - continue - - if text_if_applied in found_so_far: - continue - - found_so_far.add(text_if_applied) - yield completion diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/completion/filesystem.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/completion/filesystem.py deleted file mode 100644 index 719eac6097d..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/completion/filesystem.py +++ /dev/null @@ -1,117 +0,0 @@ -import os -from typing import Callable, Iterable, List, Optional - -from prompt_toolkit.completion import CompleteEvent, Completer, Completion -from prompt_toolkit.document import Document - -__all__ = [ - "PathCompleter", - "ExecutableCompleter", -] - - -class PathCompleter(Completer): - """ - Complete for Path variables. - - :param get_paths: Callable which returns a list of directories to look into - when the user enters a relative path. - :param file_filter: Callable which takes a filename and returns whether - this file should show up in the completion. ``None`` - when no filtering has to be done. - :param min_input_len: Don't do autocompletion when the input string is shorter. - """ - - def __init__( - self, - only_directories: bool = False, - get_paths: Optional[Callable[[], List[str]]] = None, - file_filter: Optional[Callable[[str], bool]] = None, - min_input_len: int = 0, - expanduser: bool = False, - ) -> None: - - self.only_directories = only_directories - self.get_paths = get_paths or (lambda: ["."]) - self.file_filter = file_filter or (lambda _: True) - self.min_input_len = min_input_len - self.expanduser = expanduser - - def get_completions( - self, document: Document, complete_event: CompleteEvent - ) -> Iterable[Completion]: - text = document.text_before_cursor - - # Complete only when we have at least the minimal input length, - # otherwise, we can too many results and autocompletion will become too - # heavy. - if len(text) < self.min_input_len: - return - - try: - # Do tilde expansion. - if self.expanduser: - text = os.path.expanduser(text) - - # Directories where to look. - dirname = os.path.dirname(text) - if dirname: - directories = [ - os.path.dirname(os.path.join(p, text)) for p in self.get_paths() - ] - else: - directories = self.get_paths() - - # Start of current file. - prefix = os.path.basename(text) - - # Get all filenames. - filenames = [] - for directory in directories: - # Look for matches in this directory. - if os.path.isdir(directory): - for filename in os.listdir(directory): - if filename.startswith(prefix): - filenames.append((directory, filename)) - - # Sort - filenames = sorted(filenames, key=lambda k: k[1]) - - # Yield them. - for directory, filename in filenames: - completion = filename[len(prefix) :] - full_name = os.path.join(directory, filename) - - if os.path.isdir(full_name): - # For directories, add a slash to the filename. - # (We don't add them to the `completion`. Users can type it - # to trigger the autocompletion themselves.) - filename += "/" - elif self.only_directories: - continue - - if not self.file_filter(full_name): - continue - - yield Completion( - text=completion, - start_position=0, - display=filename, - ) - except OSError: - pass - - -class ExecutableCompleter(PathCompleter): - """ - Complete only executable files in the current path. - """ - - def __init__(self) -> None: - super().__init__( - only_directories=False, - min_input_len=1, - get_paths=lambda: os.environ.get("PATH", "").split(os.pathsep), - file_filter=lambda name: os.access(name, os.X_OK), - expanduser=True, - ), diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/completion/fuzzy_completer.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/completion/fuzzy_completer.py deleted file mode 100644 index 627adc8738a..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/completion/fuzzy_completer.py +++ /dev/null @@ -1,201 +0,0 @@ -import re -from typing import Callable, Dict, Iterable, List, NamedTuple, Optional, Tuple, Union - -from prompt_toolkit.document import Document -from prompt_toolkit.filters import FilterOrBool, to_filter -from prompt_toolkit.formatted_text import AnyFormattedText, StyleAndTextTuples - -from .base import CompleteEvent, Completer, Completion -from .word_completer import WordCompleter - -__all__ = [ - "FuzzyCompleter", - "FuzzyWordCompleter", -] - - -class FuzzyCompleter(Completer): - """ - Fuzzy completion. - This wraps any other completer and turns it into a fuzzy completer. - - If the list of words is: ["leopard" , "gorilla", "dinosaur", "cat", "bee"] - Then trying to complete "oar" would yield "leopard" and "dinosaur", but not - the others, because they match the regular expression 'o.*a.*r'. - Similar, in another application "djm" could expand to "django_migrations". - - The results are sorted by relevance, which is defined as the start position - and the length of the match. - - Notice that this is not really a tool to work around spelling mistakes, - like what would be possible with difflib. The purpose is rather to have a - quicker or more intuitive way to filter the given completions, especially - when many completions have a common prefix. - - Fuzzy algorithm is based on this post: - https://blog.amjith.com/fuzzyfinder-in-10-lines-of-python - - :param completer: A :class:`~.Completer` instance. - :param WORD: When True, use WORD characters. - :param pattern: Regex pattern which selects the characters before the - cursor that are considered for the fuzzy matching. - :param enable_fuzzy: (bool or `Filter`) Enabled the fuzzy behavior. For - easily turning fuzzyness on or off according to a certain condition. - """ - - def __init__( - self, - completer: Completer, - WORD: bool = False, - pattern: Optional[str] = None, - enable_fuzzy: FilterOrBool = True, - ): - - assert pattern is None or pattern.startswith("^") - - self.completer = completer - self.pattern = pattern - self.WORD = WORD - self.pattern = pattern - self.enable_fuzzy = to_filter(enable_fuzzy) - - def get_completions( - self, document: Document, complete_event: CompleteEvent - ) -> Iterable[Completion]: - if self.enable_fuzzy(): - return self._get_fuzzy_completions(document, complete_event) - else: - return self.completer.get_completions(document, complete_event) - - def _get_pattern(self) -> str: - if self.pattern: - return self.pattern - if self.WORD: - return r"[^\s]+" - return "^[a-zA-Z0-9_]*" - - def _get_fuzzy_completions( - self, document: Document, complete_event: CompleteEvent - ) -> Iterable[Completion]: - - word_before_cursor = document.get_word_before_cursor( - pattern=re.compile(self._get_pattern()) - ) - - # Get completions - document2 = Document( - text=document.text[: document.cursor_position - len(word_before_cursor)], - cursor_position=document.cursor_position - len(word_before_cursor), - ) - - completions = list(self.completer.get_completions(document2, complete_event)) - - fuzzy_matches: List[_FuzzyMatch] = [] - - pat = ".*?".join(map(re.escape, word_before_cursor)) - pat = f"(?=({pat}))" # lookahead regex to manage overlapping matches - regex = re.compile(pat, re.IGNORECASE) - for compl in completions: - matches = list(regex.finditer(compl.text)) - if matches: - # Prefer the match, closest to the left, then shortest. - best = min(matches, key=lambda m: (m.start(), len(m.group(1)))) - fuzzy_matches.append( - _FuzzyMatch(len(best.group(1)), best.start(), compl) - ) - - 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 - - fuzzy_matches = sorted(fuzzy_matches, key=sort_key) - - for match in fuzzy_matches: - # Include these completions, but set the correct `display` - # attribute and `start_position`. - yield Completion( - text=match.completion.text, - start_position=match.completion.start_position - - len(word_before_cursor), - display_meta=match.completion.display_meta, - display=self._get_display(match, word_before_cursor), - style=match.completion.style, - ) - - def _get_display( - self, fuzzy_match: "_FuzzyMatch", word_before_cursor: str - ) -> AnyFormattedText: - """ - Generate formatted text for the display label. - """ - m = fuzzy_match - word = m.completion.text - - if m.match_length == 0: - # No highlighting when we have zero length matches (no input text). - # In this case, use the original display text (which can include - # additional styling or characters). - return m.completion.display - - result: StyleAndTextTuples = [] - - # Text before match. - result.append(("class:fuzzymatch.outside", word[: m.start_pos])) - - # The match itself. - characters = list(word_before_cursor) - - for c in word[m.start_pos : m.start_pos + m.match_length]: - classname = "class:fuzzymatch.inside" - if characters and c.lower() == characters[0].lower(): - classname += ".character" - del characters[0] - - result.append((classname, c)) - - # Text after match. - result.append( - ("class:fuzzymatch.outside", word[m.start_pos + m.match_length :]) - ) - - return result - - -class FuzzyWordCompleter(Completer): - """ - Fuzzy completion on a list of words. - - (This is basically a `WordCompleter` wrapped in a `FuzzyCompleter`.) - - :param words: List of words or callable that returns a list of words. - :param meta_dict: Optional dict mapping words to their meta-information. - :param WORD: When True, use WORD characters. - """ - - def __init__( - self, - words: Union[List[str], Callable[[], List[str]]], - meta_dict: Optional[Dict[str, str]] = None, - WORD: bool = False, - ) -> None: - - self.words = words - self.meta_dict = meta_dict or {} - self.WORD = WORD - - self.word_completer = WordCompleter( - words=self.words, WORD=self.WORD, meta_dict=self.meta_dict - ) - - self.fuzzy_completer = FuzzyCompleter(self.word_completer, WORD=self.WORD) - - def get_completions( - self, document: Document, complete_event: CompleteEvent - ) -> Iterable[Completion]: - return self.fuzzy_completer.get_completions(document, complete_event) - - -class _FuzzyMatch(NamedTuple): - match_length: int - start_pos: int - completion: Completion diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/completion/nested.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/completion/nested.py deleted file mode 100644 index f8656b217ad..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/completion/nested.py +++ /dev/null @@ -1,107 +0,0 @@ -""" -Nestedcompleter for completion of hierarchical data structures. -""" -from typing import Any, Dict, Iterable, Mapping, Optional, Set, Union - -from prompt_toolkit.completion import CompleteEvent, Completer, Completion -from prompt_toolkit.completion.word_completer import WordCompleter -from prompt_toolkit.document import Document - -__all__ = ["NestedCompleter"] - -# NestedDict = Mapping[str, Union['NestedDict', Set[str], None, Completer]] -NestedDict = Mapping[str, Union[Any, Set[str], None, Completer]] - - -class NestedCompleter(Completer): - """ - Completer which wraps around several other completers, and calls any the - one that corresponds with the first word of the input. - - By combining multiple `NestedCompleter` instances, we can achieve multiple - hierarchical levels of autocompletion. This is useful when `WordCompleter` - is not sufficient. - - If you need multiple levels, check out the `from_nested_dict` classmethod. - """ - - def __init__( - self, options: Dict[str, Optional[Completer]], ignore_case: bool = True - ) -> None: - - self.options = options - self.ignore_case = ignore_case - - 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": - """ - Create a `NestedCompleter`, starting from a nested dictionary data - structure, like this: - - .. code:: - - data = { - 'show': { - 'version': None, - 'interfaces': None, - 'clock': None, - 'ip': {'interface': {'brief'}} - }, - 'exit': None - 'enable': None - } - - The value should be `None` if there is no further completion at some - point. If all values in the dictionary are None, it is also possible to - use a set instead. - - Values in this data structure can be a completers as well. - """ - options: Dict[str, Optional[Completer]] = {} - for key, value in data.items(): - if isinstance(value, Completer): - options[key] = value - elif isinstance(value, dict): - options[key] = cls.from_nested_dict(value) - elif isinstance(value, set): - options[key] = cls.from_nested_dict({item: None for item in value}) - else: - assert value is None - options[key] = None - - return cls(options) - - def get_completions( - self, document: Document, complete_event: CompleteEvent - ) -> Iterable[Completion]: - # Split document. - text = document.text_before_cursor.lstrip() - stripped_len = len(document.text_before_cursor) - len(text) - - # If there is a space, check for the first term, and use a - # subcompleter. - if " " in text: - first_term = text.split()[0] - completer = self.options.get(first_term) - - # If we have a sub completer, use this for the completions. - if completer is not None: - remaining_text = text[len(first_term) :].lstrip() - move_cursor = len(text) - len(remaining_text) + stripped_len - - new_document = Document( - remaining_text, - cursor_position=document.cursor_position - move_cursor, - ) - - yield from completer.get_completions(new_document, complete_event) - - # No space in the input: behave exactly like `WordCompleter`. - else: - completer = WordCompleter( - list(self.options.keys()), ignore_case=self.ignore_case - ) - yield from completer.get_completions(document, complete_event) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/completion/word_completer.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/completion/word_completer.py deleted file mode 100644 index 28c133e921b..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/completion/word_completer.py +++ /dev/null @@ -1,93 +0,0 @@ -from typing import Callable, Iterable, List, Mapping, Optional, Pattern, Union - -from prompt_toolkit.completion import CompleteEvent, Completer, Completion -from prompt_toolkit.document import Document -from prompt_toolkit.formatted_text import AnyFormattedText - -__all__ = [ - "WordCompleter", -] - - -class WordCompleter(Completer): - """ - Simple autocompletion on a list of words. - - :param words: List of words or callable that returns a list of words. - :param ignore_case: If True, case-insensitive completion. - :param meta_dict: Optional dict mapping words to their meta-text. (This - should map strings to strings or formatted text.) - :param WORD: When True, use WORD characters. - :param sentence: When True, don't complete by comparing the word before the - cursor, but by comparing all the text before the cursor. In this case, - the list of words is just a list of strings, where each string can - contain spaces. (Can not be used together with the WORD option.) - :param match_middle: When True, match not only the start, but also in the - middle of the word. - :param pattern: Optional compiled regex for finding the word before - the cursor to complete. When given, use this regex pattern instead of - default one (see document._FIND_WORD_RE) - """ - - def __init__( - self, - words: Union[List[str], Callable[[], List[str]]], - ignore_case: bool = False, - display_dict: Optional[Mapping[str, AnyFormattedText]] = None, - meta_dict: Optional[Mapping[str, AnyFormattedText]] = None, - WORD: bool = False, - sentence: bool = False, - match_middle: bool = False, - pattern: Optional[Pattern[str]] = None, - ) -> None: - - assert not (WORD and sentence) - - self.words = words - self.ignore_case = ignore_case - self.display_dict = display_dict or {} - self.meta_dict = meta_dict or {} - self.WORD = WORD - self.sentence = sentence - self.match_middle = match_middle - self.pattern = pattern - - def get_completions( - self, document: Document, complete_event: CompleteEvent - ) -> Iterable[Completion]: - # Get list of words. - words = self.words - if callable(words): - words = words() - - # Get word/text before cursor. - if self.sentence: - word_before_cursor = document.text_before_cursor - else: - word_before_cursor = document.get_word_before_cursor( - WORD=self.WORD, pattern=self.pattern - ) - - if self.ignore_case: - word_before_cursor = word_before_cursor.lower() - - def word_matches(word: str) -> bool: - """True when the word before the cursor matches.""" - if self.ignore_case: - word = word.lower() - - if self.match_middle: - return word_before_cursor in word - else: - return word.startswith(word_before_cursor) - - for a in words: - if word_matches(a): - display = self.display_dict.get(a, a) - display_meta = self.meta_dict.get(a, "") - yield Completion( - text=a, - start_position=-len(word_before_cursor), - display=display, - display_meta=display_meta, - ) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/__init__.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/__init__.py +++ /dev/null diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/completers/__init__.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/completers/__init__.py deleted file mode 100644 index 50ca2f91275..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/completers/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .system import SystemCompleter - -__all__ = ["SystemCompleter"] diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/completers/system.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/completers/system.py deleted file mode 100644 index 9e63268b22f..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/completers/system.py +++ /dev/null @@ -1,62 +0,0 @@ -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 - -__all__ = [ - "SystemCompleter", -] - - -class SystemCompleter(GrammarCompleter): - """ - Completer for system commands. - """ - - def __init__(self) -> None: - # Compile grammar. - g = compile( - r""" - # First we have an executable. - (?P<executable>[^\s]+) - - # Ignore literals in between. - ( - \s+ - ("[^"]*" | '[^']*' | [^'"]+ ) - )* - - \s+ - - # Filename as parameters. - ( - (?P<filename>[^\s]+) | - "(?P<double_quoted_filename>[^\s]+)" | - '(?P<single_quoted_filename>[^\s]+)' - ) - """, - escape_funcs={ - "double_quoted_filename": (lambda string: string.replace('"', '\\"')), - "single_quoted_filename": (lambda string: string.replace("'", "\\'")), - }, - unescape_funcs={ - "double_quoted_filename": ( - lambda string: string.replace('\\"', '"') - ), # XXX: not entirely correct. - "single_quoted_filename": (lambda string: string.replace("\\'", "'")), - }, - ) - - # Create GrammarCompleter - super().__init__( - g, - { - "executable": ExecutableCompleter(), - "filename": PathCompleter(only_directories=False, expanduser=True), - "double_quoted_filename": PathCompleter( - only_directories=False, expanduser=True - ), - "single_quoted_filename": PathCompleter( - only_directories=False, expanduser=True - ), - }, - ) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/regular_languages/__init__.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/regular_languages/__init__.py deleted file mode 100644 index 3f306142f7a..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/regular_languages/__init__.py +++ /dev/null @@ -1,77 +0,0 @@ -r""" -Tool for expressing the grammar of an input as a regular language. -================================================================== - -The grammar for the input of many simple command line interfaces can be -expressed by a regular language. Examples are PDB (the Python debugger); a -simple (bash-like) shell with "pwd", "cd", "cat" and "ls" commands; arguments -that you can pass to an executable; etc. It is possible to use regular -expressions for validation and parsing of such a grammar. (More about regular -languages: http://en.wikipedia.org/wiki/Regular_language) - -Example -------- - -Let's take the pwd/cd/cat/ls example. We want to have a shell that accepts -these three commands. "cd" is followed by a quoted directory name and "cat" is -followed by a quoted file name. (We allow quotes inside the filename when -they're escaped with a backslash.) We could define the grammar using the -following regular expression:: - - grammar = \s* ( - pwd | - ls | - (cd \s+ " ([^"]|\.)+ ") | - (cat \s+ " ([^"]|\.)+ ") - ) \s* - - -What can we do with this grammar? ---------------------------------- - -- Syntax highlighting: We could use this for instance to give file names - different colour. -- Parse the result: .. We can extract the file names and commands by using a - regular expression with named groups. -- Input validation: .. Don't accept anything that does not match this grammar. - When combined with a parser, we can also recursively do - filename validation (and accept only existing files.) -- Autocompletion: .... Each part of the grammar can have its own autocompleter. - "cat" has to be completed using file names, while "cd" - has to be completed using directory names. - -How does it work? ------------------ - -As a user of this library, you have to define the grammar of the input as a -regular expression. The parts of this grammar where autocompletion, validation -or any other processing is required need to be marked using a regex named -group. Like ``(?P<varname>...)`` for instance. - -When the input is processed for validation (for instance), the regex will -execute, the named group is captured, and the validator associated with this -named group will test the captured string. - -There is one tricky bit: - - Often we operate on incomplete input (this is by definition the case for - autocompletion) and we have to decide for the cursor position in which - possible state the grammar it could be and in which way variables could be - matched up to that point. - -To solve this problem, the compiler takes the original regular expression and -translates it into a set of other regular expressions which each match certain -prefixes of the original regular expression. We generate one prefix regular -expression for every named variable (with this variable being the end of that -expression). - - -TODO: some examples of: - - How to create a highlighter from this grammar. - - How to create a validator from this grammar. - - How to create an autocompleter from this grammar. - - How to create a parser from this grammar. -""" -from .compiler import compile - -__all__ = ["compile"] diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/regular_languages/compiler.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/regular_languages/compiler.py deleted file mode 100644 index fed78498c0c..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/regular_languages/compiler.py +++ /dev/null @@ -1,574 +0,0 @@ -r""" -Compiler for a regular grammar. - -Example usage:: - - # Create and compile grammar. - p = compile('add \s+ (?P<var1>[^\s]+) \s+ (?P<var2>[^\s]+)') - - # Match input string. - m = p.match('add 23 432') - - # Get variables. - m.variables().get('var1') # Returns "23" - m.variables().get('var2') # Returns "432" - - -Partial matches are possible:: - - # Create and compile grammar. - p = compile(''' - # Operators with two arguments. - ((?P<operator1>[^\s]+) \s+ (?P<var1>[^\s]+) \s+ (?P<var2>[^\s]+)) | - - # Operators with only one arguments. - ((?P<operator2>[^\s]+) \s+ (?P<var1>[^\s]+)) - ''') - - # Match partial input string. - m = p.match_prefix('add 23') - - # Get variables. (Notice that both operator1 and operator2 contain the - # value "add".) This is because our input is incomplete, and we don't know - # yet in which rule of the regex we we'll end up. It could also be that - # `operator1` and `operator2` have a different autocompleter and we want to - # call all possible autocompleters that would result in valid input.) - m.variables().get('var1') # Returns "23" - m.variables().get('operator1') # Returns "add" - m.variables().get('operator2') # Returns "add" - -""" -import re -from typing import Callable, Dict, Iterable, Iterator, List -from typing import Match as RegexMatch -from typing import Optional, Pattern, Tuple - -from .regex_parser import ( - AnyNode, - Lookahead, - Node, - NodeSequence, - Regex, - Repeat, - Variable, - parse_regex, - tokenize_regex, -) - -__all__ = [ - "compile", -] - - -# Name of the named group in the regex, matching trailing input. -# (Trailing input is when the input contains characters after the end of the -# expression has been matched.) -_INVALID_TRAILING_INPUT = "invalid_trailing" - -EscapeFuncDict = Dict[str, Callable[[str], str]] - - -class _CompiledGrammar: - """ - Compiles a grammar. This will take the parse tree of a regular expression - and compile the grammar. - - :param root_node: :class~`.regex_parser.Node` instance. - :param escape_funcs: `dict` mapping variable names to escape callables. - :param unescape_funcs: `dict` mapping variable names to unescape callables. - """ - - def __init__( - self, - root_node: Node, - escape_funcs: Optional[EscapeFuncDict] = None, - unescape_funcs: Optional[EscapeFuncDict] = 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[ - str, str - ] = {} # Maps regex group names to varnames. - counter = [0] - - def create_group_func(node: Variable) -> str: - name = "n%s" % counter[0] - self._group_names_to_nodes[name] = node.varname - counter[0] += 1 - return name - - # Compile regex strings. - self._re_pattern = "^%s$" % self._transform(root_node, create_group_func) - self._re_prefix_patterns = list( - self._transform_prefix(root_node, create_group_func) - ) - - # Compile the regex itself. - flags = re.DOTALL # Note that we don't need re.MULTILINE! (^ and $ - # still represent the start and end of input text.) - self._re = re.compile(self._re_pattern, flags) - self._re_prefix = [re.compile(t, flags) for t in self._re_prefix_patterns] - - # We compile one more set of regexes, similar to `_re_prefix`, but accept any trailing - # input. This will ensure that we can still highlight the input correctly, even when the - # input contains some additional characters at the end that don't match the grammar.) - self._re_prefix_with_trailing_input = [ - re.compile( - r"(?:{})(?P<{}>.*?)$".format(t.rstrip("$"), _INVALID_TRAILING_INPUT), - flags, - ) - for t in self._re_prefix_patterns - ] - - def escape(self, varname: str, value: str) -> str: - """ - Escape `value` to fit in the place of this variable into the grammar. - """ - f = self.escape_funcs.get(varname) - return f(value) if f else value - - def unescape(self, varname: str, value: str) -> str: - """ - Unescape `value`. - """ - f = self.unescape_funcs.get(varname) - return f(value) if f else value - - @classmethod - def _transform( - cls, root_node: Node, create_group_func: Callable[[Variable], str] - ) -> str: - """ - Turn a :class:`Node` object into a regular expression. - - :param root_node: The :class:`Node` instance for which we generate the grammar. - :param create_group_func: A callable which takes a `Node` and returns the next - free name for this node. - """ - - def transform(node: Node) -> str: - # Turn `AnyNode` into an OR. - if isinstance(node, AnyNode): - return "(?:%s)" % "|".join(transform(c) for c in node.children) - - # Concatenate a `NodeSequence` - elif isinstance(node, NodeSequence): - return "".join(transform(c) for c in node.children) - - # For Regex and Lookahead nodes, just insert them literally. - elif isinstance(node, Regex): - return node.regex - - elif isinstance(node, Lookahead): - before = "(?!" if node.negative else "(=" - return before + transform(node.childnode) + ")" - - # A `Variable` wraps the children into a named group. - elif isinstance(node, Variable): - return "(?P<{}>{})".format( - create_group_func(node), - transform(node.childnode), - ) - - # `Repeat`. - elif isinstance(node, Repeat): - if node.max_repeat is None: - if node.min_repeat == 0: - repeat_sign = "*" - elif node.min_repeat == 1: - repeat_sign = "+" - else: - repeat_sign = "{%i,%s}" % ( - node.min_repeat, - ("" if node.max_repeat is None else str(node.max_repeat)), - ) - - return "(?:{}){}{}".format( - transform(node.childnode), - repeat_sign, - ("" if node.greedy else "?"), - ) - else: - raise TypeError(f"Got {node!r}") - - return transform(root_node) - - @classmethod - def _transform_prefix( - cls, root_node: Node, create_group_func: Callable[[Variable], str] - ) -> Iterable[str]: - """ - Yield all the regular expressions matching a prefix of the grammar - defined by the `Node` instance. - - For each `Variable`, one regex pattern will be generated, with this - named group at the end. This is required because a regex engine will - terminate once a match is found. For autocompletion however, we need - the matches for all possible paths, so that we can provide completions - for each `Variable`. - - - So, in the case of an `Any` (`A|B|C)', we generate a pattern for each - clause. This is one for `A`, one for `B` and one for `C`. Unless some - groups don't contain a `Variable`, then these can be merged together. - - In the case of a `NodeSequence` (`ABC`), we generate a pattern for - each prefix that ends with a variable, and one pattern for the whole - sequence. So, that's one for `A`, one for `AB` and one for `ABC`. - - :param root_node: The :class:`Node` instance for which we generate the grammar. - :param create_group_func: A callable which takes a `Node` and returns the next - free name for this node. - """ - - def contains_variable(node: Node) -> bool: - if isinstance(node, Regex): - return False - elif isinstance(node, Variable): - return True - elif isinstance(node, (Lookahead, Repeat)): - return contains_variable(node.childnode) - elif isinstance(node, (NodeSequence, AnyNode)): - return any(contains_variable(child) for child in node.children) - - return False - - def transform(node: Node) -> Iterable[str]: - # Generate separate pattern for all terms that contain variables - # within this OR. Terms that don't contain a variable can be merged - # together in one pattern. - if isinstance(node, AnyNode): - # If we have a definition like: - # (?P<name> .*) | (?P<city> .*) - # Then we want to be able to generate completions for both the - # name as well as the city. We do this by yielding two - # different regular expressions, because the engine won't - # follow multiple paths, if multiple are possible. - children_with_variable = [] - children_without_variable = [] - for c in node.children: - if contains_variable(c): - children_with_variable.append(c) - else: - children_without_variable.append(c) - - for c in children_with_variable: - yield from transform(c) - - # Merge options without variable together. - if children_without_variable: - yield "|".join( - r for c in children_without_variable for r in transform(c) - ) - - # For a sequence, generate a pattern for each prefix that ends with - # a variable + one pattern of the complete sequence. - # (This is because, for autocompletion, we match the text before - # the cursor, and completions are given for the variable that we - # match right before the cursor.) - elif isinstance(node, NodeSequence): - # For all components in the sequence, compute prefix patterns, - # as well as full patterns. - complete = [cls._transform(c, create_group_func) for c in node.children] - prefixes = [list(transform(c)) for c in node.children] - variable_nodes = [contains_variable(c) for c in node.children] - - # If any child is contains a variable, we should yield a - # pattern up to that point, so that we are sure this will be - # matched. - for i in range(len(node.children)): - if variable_nodes[i]: - for c_str in prefixes[i]: - yield "".join(complete[:i]) + c_str - - # If there are non-variable nodes, merge all the prefixes into - # one pattern. If the input is: "[part1] [part2] [part3]", then - # this gets compiled into: - # (complete1 + (complete2 + (complete3 | partial3) | partial2) | partial1 ) - # For nodes that contain a variable, we skip the "|partial" - # part here, because thees are matched with the previous - # patterns. - if not all(variable_nodes): - result = [] - - # Start with complete patterns. - for i in range(len(node.children)): - result.append("(?:") - result.append(complete[i]) - - # Add prefix patterns. - for i in range(len(node.children) - 1, -1, -1): - if variable_nodes[i]: - # No need to yield a prefix for this one, we did - # the variable prefixes earlier. - result.append(")") - else: - result.append("|(?:") - # If this yields multiple, we should yield all combinations. - assert len(prefixes[i]) == 1 - result.append(prefixes[i][0]) - result.append("))") - - yield "".join(result) - - elif isinstance(node, Regex): - yield "(?:%s)?" % node.regex - - elif isinstance(node, Lookahead): - if node.negative: - yield "(?!%s)" % cls._transform(node.childnode, create_group_func) - else: - # Not sure what the correct semantics are in this case. - # (Probably it's not worth implementing this.) - raise Exception("Positive lookahead not yet supported.") - - elif isinstance(node, Variable): - # (Note that we should not append a '?' here. the 'transform' - # method will already recursively do that.) - for c_str in transform(node.childnode): - yield f"(?P<{create_group_func(node)}>{c_str})" - - elif isinstance(node, Repeat): - # If we have a repetition of 8 times. That would mean that the - # current input could have for instance 7 times a complete - # match, followed by a partial match. - prefix = cls._transform(node.childnode, create_group_func) - - if node.max_repeat == 1: - yield from transform(node.childnode) - else: - for c_str in transform(node.childnode): - if node.max_repeat: - repeat_sign = "{,%i}" % (node.max_repeat - 1) - else: - repeat_sign = "*" - yield "(?:{}){}{}{}".format( - prefix, - repeat_sign, - ("" if node.greedy else "?"), - c_str, - ) - - else: - raise TypeError("Got %r" % node) - - for r in transform(root_node): - yield "^(?:%s)$" % r - - def match(self, string: str) -> Optional["Match"]: - """ - Match the string with the grammar. - Returns a :class:`Match` instance or `None` when the input doesn't match the grammar. - - :param string: The input string. - """ - m = self._re.match(string) - - if m: - return Match( - string, [(self._re, m)], self._group_names_to_nodes, self.unescape_funcs - ) - return None - - def match_prefix(self, string: str) -> Optional["Match"]: - """ - Do a partial match of the string with the grammar. The returned - :class:`Match` instance can contain multiple representations of the - match. This will never return `None`. If it doesn't match at all, the "trailing input" - part will capture all of the input. - - :param string: The input string. - """ - # First try to match using `_re_prefix`. If nothing is found, use the patterns that - # also accept trailing characters. - for patterns in [self._re_prefix, self._re_prefix_with_trailing_input]: - matches = [(r, r.match(string)) for r in patterns] - matches2 = [(r, m) for r, m in matches if m] - - if matches2 != []: - return Match( - string, matches2, self._group_names_to_nodes, self.unescape_funcs - ) - - return None - - -class Match: - """ - :param string: The input string. - :param re_matches: List of (compiled_re_pattern, re_match) tuples. - :param group_names_to_nodes: Dictionary mapping all the re group names to the matching Node instances. - """ - - 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]], - ): - 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]]]: - """ - Return a list of (varname, reg) tuples. - """ - - 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: - regs = re_match.regs - reg = regs[group_index] - node = self._group_names_to_nodes[group_name] - yield (node, reg) - - return list(get_tuples()) - - 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: - return sl[0] == -1 and sl[1] == -1 - - def get(sl: Tuple[int, int]) -> str: - return self.string[sl[0] : sl[1]] - - return [ - (varname, get(slice), slice) - for varname, slice in self._nodes_to_regs() - if not is_none(slice) - ] - - 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": - """ - Returns :class:`Variables` instance. - """ - return Variables( - [(k, self._unescape(k, v), sl) for k, v, sl in self._nodes_to_values()] - ) - - def trailing_input(self) -> Optional["MatchVariable"]: - """ - 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]] = [] - - # Find all regex group for the name _INVALID_TRAILING_INPUT. - for r, re_match in self._re_matches: - for group_name, group_index in r.groupindex.items(): - if group_name == _INVALID_TRAILING_INPUT: - slices.append(re_match.regs[group_index]) - - # Take the smallest part. (Smaller trailing text means that a larger input has - # been matched, so that is better.) - if slices: - slice = (max(i[0] for i in slices), max(i[1] for i in slices)) - value = self.string[slice[0] : slice[1]] - return MatchVariable("<trailing_input>", value, slice) - return None - - def end_nodes(self) -> Iterable["MatchVariable"]: - """ - Yields `MatchVariable` instances for all the nodes having their end - position at the end of the input string. - """ - for varname, reg in self._nodes_to_regs(): - # If this part goes until the end of the input string. - if reg[1] == len(self.string): - value = self._unescape(varname, self.string[reg[0] : reg[1]]) - yield MatchVariable(varname, value, (reg[0], reg[1])) - - -class Variables: - def __init__(self, tuples: List[Tuple[str, str, Tuple[int, int]]]) -> None: - #: List of (varname, value, slice) tuples. - self._tuples = tuples - - def __repr__(self) -> str: - return "{}({})".format( - self.__class__.__name__, - ", ".join(f"{k}={v!r}" for k, v, _ in self._tuples), - ) - - def get(self, key: str, default: Optional[str] = None) -> Optional[str]: - items = self.getall(key) - return items[0] if items else default - - 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]: - return self.get(key) - - def __iter__(self) -> Iterator["MatchVariable"]: - """ - Yield `MatchVariable` instances. - """ - for varname, value, slice in self._tuples: - yield MatchVariable(varname, value, slice) - - -class MatchVariable: - """ - Represents a match of a variable in the grammar. - - :param varname: (string) Name of the variable. - :param value: (string) Value of this variable. - :param slice: (start, stop) tuple, indicating the position of this variable - in the input string. - """ - - def __init__(self, varname: str, value: str, slice: Tuple[int, int]) -> None: - self.varname = varname - self.value = value - self.slice = slice - - self.start = self.slice[0] - self.stop = self.slice[1] - - def __repr__(self) -> str: - return f"{self.__class__.__name__}({self.varname!r}, {self.value!r})" - - -def compile( - expression: str, - escape_funcs: Optional[EscapeFuncDict] = None, - unescape_funcs: Optional[EscapeFuncDict] = None, -) -> _CompiledGrammar: - """ - Compile grammar (given as regex string), returning a `CompiledGrammar` - instance. - """ - return _compile_from_parse_tree( - parse_regex(tokenize_regex(expression)), - escape_funcs=escape_funcs, - unescape_funcs=unescape_funcs, - ) - - -def _compile_from_parse_tree( - root_node: Node, - escape_funcs: Optional[EscapeFuncDict] = None, - unescape_funcs: Optional[EscapeFuncDict] = None, -) -> _CompiledGrammar: - """ - Compile grammar (given as parse tree), returning a `CompiledGrammar` - instance. - """ - return _CompiledGrammar( - root_node, escape_funcs=escape_funcs, unescape_funcs=unescape_funcs - ) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/regular_languages/completion.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/regular_languages/completion.py deleted file mode 100644 index 3cebcc03f69..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/regular_languages/completion.py +++ /dev/null @@ -1,93 +0,0 @@ -""" -Completer for a regular grammar. -""" -from typing import Dict, Iterable, List - -from prompt_toolkit.completion import CompleteEvent, Completer, Completion -from prompt_toolkit.document import Document - -from .compiler import Match, _CompiledGrammar - -__all__ = [ - "GrammarCompleter", -] - - -class GrammarCompleter(Completer): - """ - Completer which can be used for autocompletion according to variables in - the grammar. Each variable can have a different autocompleter. - - :param compiled_grammar: `GrammarCompleter` instance. - :param completers: `dict` mapping variable names of the grammar to the - `Completer` instances to be used for each variable. - """ - - def __init__( - self, compiled_grammar: _CompiledGrammar, completers: Dict[str, Completer] - ) -> None: - - self.compiled_grammar = compiled_grammar - self.completers = completers - - def get_completions( - self, document: Document, complete_event: CompleteEvent - ) -> Iterable[Completion]: - m = self.compiled_grammar.match_prefix(document.text_before_cursor) - - if m: - completions = self._remove_duplicates( - self._get_completions_for_match(m, complete_event) - ) - - yield from completions - - def _get_completions_for_match( - self, match: Match, complete_event: CompleteEvent - ) -> Iterable[Completion]: - """ - Yield all the possible completions for this input string. - (The completer assumes that the cursor position was at the end of the - input string.) - """ - for match_variable in match.end_nodes(): - varname = match_variable.varname - start = match_variable.start - - completer = self.completers.get(varname) - - if completer: - text = match_variable.value - - # Unwrap text. - unwrapped_text = self.compiled_grammar.unescape(varname, text) - - # Create a document, for the completions API (text/cursor_position) - document = Document(unwrapped_text, len(unwrapped_text)) - - # Call completer - for completion in completer.get_completions(document, complete_event): - new_text = ( - unwrapped_text[: len(text) + completion.start_position] - + completion.text - ) - - # Wrap again. - yield Completion( - text=self.compiled_grammar.escape(varname, new_text), - start_position=start - len(match.string), - display=completion.display, - display_meta=completion.display_meta, - ) - - 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] = [] - for i in items: - if i not in result: - result.append(i) - return result diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/regular_languages/lexer.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/regular_languages/lexer.py deleted file mode 100644 index 1ddeede8026..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/regular_languages/lexer.py +++ /dev/null @@ -1,92 +0,0 @@ -""" -`GrammarLexer` is compatible with other lexers and can be used to highlight -the input using a regular grammar with annotations. -""" -from typing import Callable, Dict, Optional - -from prompt_toolkit.document import Document -from prompt_toolkit.formatted_text.base import StyleAndTextTuples -from prompt_toolkit.formatted_text.utils import split_lines -from prompt_toolkit.lexers import Lexer - -from .compiler import _CompiledGrammar - -__all__ = [ - "GrammarLexer", -] - - -class GrammarLexer(Lexer): - """ - Lexer which can be used for highlighting of fragments according to variables in the grammar. - - (It does not actual lexing of the string, but it exposes an API, compatible - with the Pygments lexer class.) - - :param compiled_grammar: Grammar as returned by the `compile()` function. - :param lexers: Dictionary mapping variable names of the regular grammar to - the lexers that should be used for this part. (This can - call other lexers recursively.) If you wish a part of the - grammar to just get one fragment, use a - `prompt_toolkit.lexers.SimpleLexer`. - """ - - def __init__( - self, - compiled_grammar: _CompiledGrammar, - default_style: str = "", - lexers: Optional[Dict[str, Lexer]] = None, - ) -> None: - - self.compiled_grammar = compiled_grammar - self.default_style = default_style - self.lexers = lexers or {} - - def _get_text_fragments(self, text: str) -> StyleAndTextTuples: - m = self.compiled_grammar.match_prefix(text) - - if m: - characters: StyleAndTextTuples = [(self.default_style, c) for c in text] - - for v in m.variables(): - # If we have a `Lexer` instance for this part of the input. - # Tokenize recursively and apply tokens. - lexer = self.lexers.get(v.varname) - - if lexer: - document = Document(text[v.start : v.stop]) - lexer_tokens_for_line = lexer.lex_document(document) - text_fragments: StyleAndTextTuples = [] - for i in range(len(document.lines)): - text_fragments.extend(lexer_tokens_for_line(i)) - text_fragments.append(("", "\n")) - if text_fragments: - text_fragments.pop() - - i = v.start - for t, s, *_ in text_fragments: - for c in s: - if characters[i][0] == self.default_style: - characters[i] = (t, characters[i][1]) - i += 1 - - # Highlight trailing input. - trailing_input = m.trailing_input() - if trailing_input: - for i in range(trailing_input.start, trailing_input.stop): - characters[i] = ("class:trailing-input", characters[i][1]) - - return characters - else: - return [("", text)] - - def lex_document(self, document: Document) -> Callable[[int], StyleAndTextTuples]: - lines = list(split_lines(self._get_text_fragments(document.text))) - - def get_line(lineno: int) -> StyleAndTextTuples: - try: - return lines[lineno] - except IndexError: - return [] - - return get_line diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/regular_languages/regex_parser.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/regular_languages/regex_parser.py deleted file mode 100644 index 87e39d498c1..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/regular_languages/regex_parser.py +++ /dev/null @@ -1,281 +0,0 @@ -""" -Parser for parsing a regular expression. -Take a string representing a regular expression and return the root node of its -parse tree. - -usage:: - - root_node = parse_regex('(hello|world)') - -Remarks: -- The regex parser processes multiline, it ignores all whitespace and supports - multiple named groups with the same name and #-style comments. - -Limitations: -- Lookahead is not supported. -""" -import re -from typing import List, Optional - -__all__ = [ - "Repeat", - "Variable", - "Regex", - "Lookahead", - "tokenize_regex", - "parse_regex", -] - - -class Node: - """ - Base class for all the grammar nodes. - (You don't initialize this one.) - """ - - def __add__(self, other_node: "Node") -> "NodeSequence": - return NodeSequence([self, other_node]) - - def __or__(self, other_node: "Node") -> "AnyNode": - return AnyNode([self, other_node]) - - -class AnyNode(Node): - """ - Union operation (OR operation) between several grammars. You don't - initialize this yourself, but it's a result of a "Grammar1 | Grammar2" - operation. - """ - - def __init__(self, children: List[Node]) -> None: - self.children = children - - def __or__(self, other_node: Node) -> "AnyNode": - return AnyNode(self.children + [other_node]) - - def __repr__(self) -> str: - return f"{self.__class__.__name__}({self.children!r})" - - -class NodeSequence(Node): - """ - Concatenation operation of several grammars. You don't initialize this - yourself, but it's a result of a "Grammar1 + Grammar2" operation. - """ - - def __init__(self, children: List[Node]) -> None: - self.children = children - - def __add__(self, other_node: Node) -> "NodeSequence": - return NodeSequence(self.children + [other_node]) - - def __repr__(self) -> str: - return f"{self.__class__.__name__}({self.children!r})" - - -class Regex(Node): - """ - Regular expression. - """ - - def __init__(self, regex: str) -> None: - re.compile(regex) # Validate - - self.regex = regex - - def __repr__(self) -> str: - return f"{self.__class__.__name__}(/{self.regex}/)" - - -class Lookahead(Node): - """ - Lookahead expression. - """ - - def __init__(self, childnode: Node, negative: bool = False) -> None: - self.childnode = childnode - self.negative = negative - - def __repr__(self) -> str: - return f"{self.__class__.__name__}({self.childnode!r})" - - -class Variable(Node): - """ - Mark a variable in the regular grammar. This will be translated into a - named group. Each variable can have his own completer, validator, etc.. - - :param childnode: The grammar which is wrapped inside this variable. - :param varname: String. - """ - - def __init__(self, childnode: Node, varname: str = "") -> None: - self.childnode = childnode - self.varname = varname - - def __repr__(self) -> str: - return "{}(childnode={!r}, varname={!r})".format( - self.__class__.__name__, - self.childnode, - self.varname, - ) - - -class Repeat(Node): - def __init__( - self, - childnode: Node, - min_repeat: int = 0, - max_repeat: Optional[int] = None, - greedy: bool = True, - ) -> None: - self.childnode = childnode - self.min_repeat = min_repeat - self.max_repeat = max_repeat - self.greedy = greedy - - def __repr__(self) -> str: - return f"{self.__class__.__name__}(childnode={self.childnode!r})" - - -def tokenize_regex(input: str) -> List[str]: - """ - Takes a string, representing a regular expression as input, and tokenizes - it. - - :param input: string, representing a regular expression. - :returns: List of tokens. - """ - # Regular expression for tokenizing other regular expressions. - p = re.compile( - r"""^( - \(\?P\<[a-zA-Z0-9_-]+\> | # Start of named group. - \(\?#[^)]*\) | # Comment - \(\?= | # Start of lookahead assertion - \(\?! | # Start of negative lookahead assertion - \(\?<= | # If preceded by. - \(\?< | # If not preceded by. - \(?: | # Start of group. (non capturing.) - \( | # Start of group. - \(?[iLmsux] | # Flags. - \(?P=[a-zA-Z]+\) | # Back reference to named group - \) | # End of group. - \{[^{}]*\} | # Repetition - \*\? | \+\? | \?\?\ | # Non greedy repetition. - \* | \+ | \? | # Repetition - \#.*\n | # Comment - \\. | - - # Character group. - \[ - ( [^\]\\] | \\.)* - \] | - - [^(){}] | - . - )""", - re.VERBOSE, - ) - - tokens = [] - - while input: - m = p.match(input) - if m: - token, input = input[: m.end()], input[m.end() :] - if not token.isspace(): - tokens.append(token) - else: - raise Exception("Could not tokenize input regex.") - - return tokens - - -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] - - def wrap(lst: List[Node]) -> Node: - """Turn list into sequence when it contains several items.""" - if len(lst) == 1: - return lst[0] - else: - return NodeSequence(lst) - - def _parse() -> Node: - or_list: List[List[Node]] = [] - result: List[Node] = [] - - def wrapped_result() -> Node: - if or_list == []: - return wrap(result) - else: - or_list.append(result) - return AnyNode([wrap(i) for i in or_list]) - - while tokens: - t = tokens.pop() - - if t.startswith("(?P<"): - variable = Variable(_parse(), varname=t[4:-1]) - result.append(variable) - - elif t in ("*", "*?"): - greedy = t == "*" - result[-1] = Repeat(result[-1], greedy=greedy) - - elif t in ("+", "+?"): - greedy = t == "+" - result[-1] = Repeat(result[-1], min_repeat=1, greedy=greedy) - - elif t in ("?", "??"): - if result == []: - raise Exception("Nothing to repeat." + repr(tokens)) - else: - greedy = t == "?" - result[-1] = Repeat( - result[-1], min_repeat=0, max_repeat=1, greedy=greedy - ) - - elif t == "|": - or_list.append(result) - result = [] - - elif t in ("(", "(?:"): - result.append(_parse()) - - elif t == "(?!": - result.append(Lookahead(_parse(), negative=True)) - - elif t == "(?=": - result.append(Lookahead(_parse(), negative=False)) - - elif t == ")": - return wrapped_result() - - elif t.startswith("#"): - pass - - elif t.startswith("{"): - # TODO: implement! - raise Exception(f"{t}-style repetition not yet supported") - - elif t.startswith("(?"): - raise Exception("%r not supported" % t) - - elif t.isspace(): - pass - else: - result.append(Regex(t)) - - raise Exception("Expecting ')' token") - - result = _parse() - - if len(tokens) != 0: - raise Exception("Unmatched parentheses.") - else: - return result diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/regular_languages/validation.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/regular_languages/validation.py deleted file mode 100644 index 71d34340353..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/regular_languages/validation.py +++ /dev/null @@ -1,60 +0,0 @@ -""" -Validator for a regular language. -""" -from typing import Dict - -from prompt_toolkit.document import Document -from prompt_toolkit.validation import ValidationError, Validator - -from .compiler import _CompiledGrammar - -__all__ = [ - "GrammarValidator", -] - - -class GrammarValidator(Validator): - """ - Validator which can be used for validation according to variables in - the grammar. Each variable can have its own validator. - - :param compiled_grammar: `GrammarCompleter` instance. - :param validators: `dict` mapping variable names of the grammar to the - `Validator` instances to be used for each variable. - """ - - def __init__( - self, compiled_grammar: _CompiledGrammar, validators: Dict[str, Validator] - ) -> None: - - self.compiled_grammar = compiled_grammar - self.validators = validators - - def validate(self, document: Document) -> None: - # Parse input document. - # We use `match`, not `match_prefix`, because for validation, we want - # the actual, unambiguous interpretation of the input. - m = self.compiled_grammar.match(document.text) - - if m: - for v in m.variables(): - validator = self.validators.get(v.varname) - - if validator: - # Unescape text. - unwrapped_text = self.compiled_grammar.unescape(v.varname, v.value) - - # Create a document, for the completions API (text/cursor_position) - inner_document = Document(unwrapped_text, len(unwrapped_text)) - - try: - validator.validate(inner_document) - except ValidationError as e: - raise ValidationError( - cursor_position=v.start + e.cursor_position, - message=e.message, - ) from e - else: - raise ValidationError( - cursor_position=len(document.text), message="Invalid command" - ) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/ssh/__init__.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/ssh/__init__.py deleted file mode 100644 index a895ca22825..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/ssh/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from .server import PromptToolkitSSHServer, PromptToolkitSSHSession - -__all__ = [ - "PromptToolkitSSHSession", - "PromptToolkitSSHServer", -] diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/ssh/server.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/ssh/server.py deleted file mode 100644 index 98f3b68177c..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/ssh/server.py +++ /dev/null @@ -1,160 +0,0 @@ -""" -Utility for running a prompt_toolkit application in an asyncssh server. -""" -import asyncio -import traceback -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 - -__all__ = ["PromptToolkitSSHSession", "PromptToolkitSSHServer"] - - -class PromptToolkitSSHSession(asyncssh.SSHServerSession): # type: ignore - def __init__( - self, interact: Callable[["PromptToolkitSSHSession"], Awaitable[None]] - ) -> None: - self.interact = interact - self.interact_task: Optional[asyncio.Task[None]] = None - self._chan: Optional[Any] = None - self.app_session: Optional[AppSession] = 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 - - # Output object. Don't render to the real stdout, but write everything - # in the SSH channel. - class Stdout: - def write(s, data: str) -> None: - try: - if self._chan is not None: - self._chan.write(data.replace("\n", "\r\n")) - except BrokenPipeError: - pass # Channel not open for sending. - - def isatty(s) -> bool: - return True - - def flush(s) -> None: - pass - - @property - def encoding(s) -> str: - assert self._chan is not None - return str(self._chan._orig_chan.get_encoding()[0]) - - self.stdout = cast(TextIO, Stdout()) - - def _get_size(self) -> Size: - """ - Callable that returns the current `Size`, required by Vt100_Output. - """ - if self._chan is None: - return Size(rows=20, columns=79) - else: - width, height, pixwidth, pixheight = self._chan.get_terminal_size() - return Size(rows=height, columns=width) - - def connection_made(self, chan: Any) -> None: - self._chan = chan - - def shell_requested(self) -> bool: - return True - - def session_started(self) -> None: - self.interact_task = get_event_loop().create_task(self._interact()) - - async def _interact(self) -> None: - if self._chan is None: - # Should not happen. - raise Exception("`_interact` called before `connection_made`.") - - if hasattr(self._chan, "set_line_mode") and self._chan._editor is not None: - # Disable the line editing provided by asyncssh. Prompt_toolkit - # provides the line editing. - self._chan.set_line_mode(False) - - term = self._chan.get_terminal_type() - - self._output = Vt100_Output(self.stdout, self._get_size, term=term) - - with create_pipe_input() as self._input: - with create_app_session(input=self._input, output=self._output) as session: - self.app_session = session - try: - await self.interact(self) - except BaseException: - traceback.print_exc() - finally: - # Close the connection. - self._chan.close() - self._input.close() - - def terminal_size_changed( - self, width: int, height: int, pixwidth: object, pixheight: object - ) -> None: - # Send resize event to the current application. - if self.app_session and self.app_session.app: - self.app_session.app._on_resize() - - def data_received(self, data: str, datatype: object) -> None: - if self._input is None: - # Should not happen. - return - - self._input.send_text(data) - - -class PromptToolkitSSHServer(asyncssh.SSHServer): # type: ignore - """ - Run a prompt_toolkit application over an asyncssh server. - - This takes one argument, an `interact` function, which is called for each - connection. This should be an asynchronous function that runs the - prompt_toolkit applications. This function runs in an `AppSession`, which - means that we can have multiple UI interactions concurrently. - - Example usage: - - .. code:: python - - async def interact(ssh_session: PromptToolkitSSHSession) -> None: - await yes_no_dialog("my title", "my text").run_async() - - prompt_session = PromptSession() - text = await prompt_session.prompt_async("Type something: ") - print_formatted_text('You said: ', text) - - server = PromptToolkitSSHServer(interact=interact) - loop = get_event_loop() - loop.run_until_complete( - asyncssh.create_server( - lambda: MySSHServer(interact), - "", - port, - server_host_keys=["/etc/ssh/..."], - ) - ) - loop.run_forever() - """ - - def __init__( - self, interact: Callable[[PromptToolkitSSHSession], Awaitable[None]] - ) -> None: - self.interact = interact - - def begin_auth(self, username: str) -> bool: - # No authentication. - return False - - def session_requested(self) -> PromptToolkitSSHSession: - return PromptToolkitSSHSession(self.interact) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/telnet/__init__.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/telnet/__init__.py deleted file mode 100644 index b29f7d2876c..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/telnet/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from .server import TelnetServer - -__all__ = [ - "TelnetServer", -] diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/telnet/log.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/telnet/log.py deleted file mode 100644 index 24487aba26c..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/telnet/log.py +++ /dev/null @@ -1,10 +0,0 @@ -""" -Python logger for the telnet server. -""" -import logging - -logger = logging.getLogger(__package__) - -__all__ = [ - "logger", -] diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/telnet/protocol.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/telnet/protocol.py deleted file mode 100644 index 68f3639880d..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/telnet/protocol.py +++ /dev/null @@ -1,207 +0,0 @@ -""" -Parser for the Telnet protocol. (Not a complete implementation of the telnet -specification, but sufficient for a command line interface.) - -Inspired by `Twisted.conch.telnet`. -""" -import struct -from typing import Callable, Generator - -from .log import logger - -__all__ = [ - "TelnetProtocolParser", -] - - -def int2byte(number: int) -> bytes: - return bytes((number,)) - - -# Telnet constants. -NOP = int2byte(0) -SGA = int2byte(3) - -IAC = int2byte(255) -DO = int2byte(253) -DONT = int2byte(254) -LINEMODE = int2byte(34) -SB = int2byte(250) -WILL = int2byte(251) -WONT = int2byte(252) -MODE = int2byte(1) -SE = int2byte(240) -ECHO = int2byte(1) -NAWS = int2byte(31) -LINEMODE = int2byte(34) -SUPPRESS_GO_AHEAD = int2byte(3) - -TTYPE = int2byte(24) -SEND = int2byte(1) -IS = int2byte(0) - -DM = int2byte(242) -BRK = int2byte(243) -IP = int2byte(244) -AO = int2byte(245) -AYT = int2byte(246) -EC = int2byte(247) -EL = int2byte(248) -GA = int2byte(249) - - -class TelnetProtocolParser: - """ - Parser for the Telnet protocol. - Usage:: - - def data_received(data): - print(data) - - def size_received(rows, columns): - print(rows, columns) - - p = TelnetProtocolParser(data_received, size_received) - p.feed(binary_data) - """ - - def __init__( - self, - data_received_callback: Callable[[bytes], None], - size_received_callback: Callable[[int, int], None], - ttype_received_callback: Callable[[str], None], - ) -> None: - - self.data_received_callback = data_received_callback - self.size_received_callback = size_received_callback - self.ttype_received_callback = ttype_received_callback - - self._parser = self._parse_coroutine() - self._parser.send(None) # type: ignore - - def received_data(self, data: bytes) -> None: - self.data_received_callback(data) - - def do_received(self, data: bytes) -> None: - """Received telnet DO command.""" - logger.info("DO %r", data) - - def dont_received(self, data: bytes) -> None: - """Received telnet DONT command.""" - logger.info("DONT %r", data) - - def will_received(self, data: bytes) -> None: - """Received telnet WILL command.""" - logger.info("WILL %r", data) - - def wont_received(self, data: bytes) -> None: - """Received telnet WONT command.""" - logger.info("WONT %r", data) - - def command_received(self, command: bytes, data: bytes) -> None: - if command == DO: - self.do_received(data) - - elif command == DONT: - self.dont_received(data) - - elif command == WILL: - self.will_received(data) - - elif command == WONT: - self.wont_received(data) - - else: - logger.info("command received %r %r", command, data) - - def naws(self, data: bytes) -> None: - """ - Received NAWS. (Window dimensions.) - """ - if len(data) == 4: - # NOTE: the first parameter of struct.unpack should be - # a 'str' object. Both on Py2/py3. This crashes on OSX - # otherwise. - columns, rows = struct.unpack("!HH", data) - self.size_received_callback(rows, columns) - else: - logger.warning("Wrong number of NAWS bytes") - - def ttype(self, data: bytes) -> None: - """ - Received terminal type. - """ - subcmd, data = data[0:1], data[1:] - if subcmd == IS: - ttype = data.decode("ascii") - self.ttype_received_callback(ttype) - else: - logger.warning("Received a non-IS terminal type Subnegotiation") - - def negotiate(self, data: bytes) -> None: - """ - Got negotiate data. - """ - command, payload = data[0:1], data[1:] - - if command == NAWS: - self.naws(payload) - elif command == TTYPE: - self.ttype(payload) - else: - logger.info("Negotiate (%r got bytes)", len(data)) - - def _parse_coroutine(self) -> Generator[None, bytes, None]: - """ - Parser state machine. - Every 'yield' expression returns the next byte. - """ - while True: - d = yield - - if d == int2byte(0): - pass # NOP - - # Go to state escaped. - elif d == IAC: - d2 = yield - - if d2 == IAC: - self.received_data(d2) - - # Handle simple commands. - elif d2 in (NOP, DM, BRK, IP, AO, AYT, EC, EL, GA): - self.command_received(d2, b"") - - # Handle IAC-[DO/DONT/WILL/WONT] commands. - elif d2 in (DO, DONT, WILL, WONT): - d3 = yield - self.command_received(d2, d3) - - # Subnegotiation - elif d2 == SB: - # Consume everything until next IAC-SE - data = [] - - while True: - d3 = yield - - if d3 == IAC: - d4 = yield - if d4 == SE: - break - else: - data.append(d4) - else: - data.append(d3) - - self.negotiate(b"".join(data)) - else: - self.received_data(d) - - def feed(self, data: bytes) -> None: - """ - Feed data to the parser. - """ - for b in data: - self._parser.send(int2byte(b)) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/telnet/server.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/telnet/server.py deleted file mode 100644 index b6248cd3e57..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/contrib/telnet/server.py +++ /dev/null @@ -1,375 +0,0 @@ -""" -Telnet server. -""" -import asyncio -import socket -import sys -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 -from prompt_toolkit.renderer import print_formatted_text as print_formatted_text -from prompt_toolkit.styles import BaseStyle, DummyStyle - -from .log import logger -from .protocol import ( - DO, - ECHO, - IAC, - LINEMODE, - MODE, - NAWS, - SB, - SE, - SEND, - SUPPRESS_GO_AHEAD, - TTYPE, - WILL, - TelnetProtocolParser, -) - -if sys.version_info >= (3, 7): - import contextvars # Requires Python3.7! -else: - contextvars: Any = None - -__all__ = [ - "TelnetServer", -] - - -def int2byte(number: int) -> bytes: - return bytes((number,)) - - -def _initialize_telnet(connection: socket.socket) -> None: - logger.info("Initializing telnet connection") - - # Iac Do Linemode - connection.send(IAC + DO + LINEMODE) - - # Suppress Go Ahead. (This seems important for Putty to do correct echoing.) - # This will allow bi-directional operation. - connection.send(IAC + WILL + SUPPRESS_GO_AHEAD) - - # Iac sb - connection.send(IAC + SB + LINEMODE + MODE + int2byte(0) + IAC + SE) - - # IAC Will Echo - connection.send(IAC + WILL + ECHO) - - # Negotiate window size - connection.send(IAC + DO + NAWS) - - # Negotiate terminal type - # Assume the client will accept the negociation with `IAC + WILL + TTYPE` - connection.send(IAC + DO + TTYPE) - - # We can then select the first terminal type supported by the client, - # which is generally the best type the client supports - # The client should reply with a `IAC + SB + TTYPE + IS + ttype + IAC + SE` - connection.send(IAC + SB + TTYPE + SEND + IAC + SE) - - -class _ConnectionStdout: - """ - Wrapper around socket which provides `write` and `flush` methods for the - Vt100_Output output. - """ - - def __init__(self, connection: socket.socket, encoding: str) -> None: - self._encoding = encoding - self._connection = connection - self._errors = "strict" - self._buffer: List[bytes] = [] - self._closed = False - - def write(self, data: str) -> None: - data = data.replace("\n", "\r\n") - self._buffer.append(data.encode(self._encoding, errors=self._errors)) - self.flush() - - def isatty(self) -> bool: - return True - - def flush(self) -> None: - try: - if not self._closed: - self._connection.send(b"".join(self._buffer)) - except OSError as e: - logger.warning("Couldn't send data over socket: %s" % e) - - self._buffer = [] - - def close(self) -> None: - self._closed = True - - @property - def encoding(self) -> str: - return self._encoding - - @property - def errors(self) -> str: - return self._errors - - -class TelnetConnection: - """ - Class that represents one Telnet connection. - """ - - def __init__( - self, - conn: socket.socket, - addr: Tuple[str, int], - interact: Callable[["TelnetConnection"], Awaitable[None]], - server: "TelnetServer", - encoding: str, - style: Optional[BaseStyle], - vt100_input: PipeInput, - ) -> None: - - self.conn = conn - self.addr = addr - self.interact = interact - self.server = server - self.encoding = encoding - self.style = style - self._closed = False - self._ready = asyncio.Event() - self.vt100_input = vt100_input - self.vt100_output = None - - # Create "Output" object. - self.size = Size(rows=40, columns=79) - - # Initialize. - _initialize_telnet(conn) - - # Create output. - def get_size() -> Size: - return self.size - - self.stdout = cast(TextIO, _ConnectionStdout(conn, encoding=encoding)) - - def data_received(data: bytes) -> None: - """TelnetProtocolParser 'data_received' callback""" - self.vt100_input.send_bytes(data) - - def size_received(rows: int, columns: int) -> None: - """TelnetProtocolParser 'size_received' callback""" - self.size = Size(rows=rows, columns=columns) - if self.vt100_output is not None and self.context: - self.context.run(lambda: get_app()._on_resize()) - - def ttype_received(ttype: str) -> None: - """TelnetProtocolParser 'ttype_received' callback""" - self.vt100_output = Vt100_Output(self.stdout, get_size, term=ttype) - self._ready.set() - - self.parser = TelnetProtocolParser(data_received, size_received, ttype_received) - self.context: Optional[contextvars.Context] = None - - async def run_application(self) -> None: - """ - Run application. - """ - - def handle_incoming_data() -> None: - data = self.conn.recv(1024) - if data: - self.feed(data) - else: - # Connection closed by client. - logger.info("Connection closed by client. %r %r" % self.addr) - self.close() - - # Add reader. - loop = get_event_loop() - loop.add_reader(self.conn, handle_incoming_data) - - try: - # Wait for v100_output to be properly instantiated - await self._ready.wait() - with create_app_session(input=self.vt100_input, output=self.vt100_output): - self.context = contextvars.copy_context() - await self.interact(self) - finally: - self.close() - - def feed(self, data: bytes) -> None: - """ - Handler for incoming data. (Called by TelnetServer.) - """ - self.parser.feed(data) - - def close(self) -> None: - """ - Closed by client. - """ - if not self._closed: - self._closed = True - - self.vt100_input.close() - get_event_loop().remove_reader(self.conn) - self.conn.close() - self.stdout.close() - - def send(self, formatted_text: AnyFormattedText) -> None: - """ - Send text to the client. - """ - if self.vt100_output is None: - return - formatted_text = to_formatted_text(formatted_text) - print_formatted_text( - self.vt100_output, formatted_text, self.style or DummyStyle() - ) - - def send_above_prompt(self, formatted_text: AnyFormattedText) -> None: - """ - Send text to the client. - This is asynchronous, returns a `Future`. - """ - formatted_text = to_formatted_text(formatted_text) - return self._run_in_terminal(lambda: self.send(formatted_text)) - - def _run_in_terminal(self, func: Callable[[], None]) -> None: - # Make sure that when an application was active for this connection, - # that we print the text above the application. - if self.context: - self.context.run(run_in_terminal, func) # type: ignore - else: - raise RuntimeError("Called _run_in_terminal outside `run_application`.") - - def erase_screen(self) -> None: - """ - Erase the screen and move the cursor to the top. - """ - if self.vt100_output is None: - return - self.vt100_output.erase_screen() - self.vt100_output.cursor_goto(0, 0) - self.vt100_output.flush() - - -async def _dummy_interact(connection: TelnetConnection) -> None: - pass - - -class TelnetServer: - """ - Telnet server implementation. - """ - - def __init__( - self, - host: str = "127.0.0.1", - port: int = 23, - interact: Callable[[TelnetConnection], Awaitable[None]] = _dummy_interact, - encoding: str = "utf-8", - style: Optional[BaseStyle] = None, - ) -> None: - - self.host = host - self.port = port - self.interact = interact - self.encoding = encoding - self.style = style - self._application_tasks: List[asyncio.Task[None]] = [] - - self.connections: Set[TelnetConnection] = set() - self._listen_socket: Optional[socket.socket] = None - - @classmethod - def _create_socket(cls, host: str, port: int) -> socket.socket: - # Create and bind socket - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - s.bind((host, port)) - - s.listen(4) - return s - - def start(self) -> None: - """ - Start the telnet server. - Don't forget to call `loop.run_forever()` after doing this. - """ - self._listen_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) - - 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() - - for t in self._application_tasks: - try: - await t - except asyncio.CancelledError: - logger.debug("Task %s cancelled", str(t)) - - def _accept(self) -> 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() - logger.info("New connection %r %r", *addr) - - # Run application for this connection. - async def run() -> None: - try: - with create_pipe_input() as vt100_input: - connection = TelnetConnection( - conn, - addr, - self.interact, - self, - encoding=self.encoding, - style=self.style, - vt100_input=vt100_input, - ) - self.connections.add(connection) - - logger.info("Starting interaction %r %r", *addr) - try: - await connection.run_application() - finally: - self.connections.remove(connection) - logger.info("Stopping interaction %r %r", *addr) - except EOFError: - # Happens either when the connection is closed by the client - # (e.g., when the user types 'control-]', then 'quit' in the - # telnet client) or when the user types control-d in a prompt - # and this is not handled by the interact function. - logger.info("Unhandled EOFError in telnet application.") - except KeyboardInterrupt: - # Unhandled control-c propagated by a prompt. - logger.info("Unhandled KeyboardInterrupt in telnet application.") - except BaseException as e: - print("Got %s" % type(e).__name__, e) - import traceback - - traceback.print_exc() - finally: - self._application_tasks.remove(task) - - task = get_event_loop().create_task(run()) - self._application_tasks.append(task) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/cursor_shapes.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/cursor_shapes.py deleted file mode 100644 index d38b3505bd8..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/cursor_shapes.py +++ /dev/null @@ -1,102 +0,0 @@ -from abc import ABC, abstractmethod -from enum import Enum -from typing import TYPE_CHECKING, Any, Callable, Union - -from prompt_toolkit.enums import EditingMode -from prompt_toolkit.key_binding.vi_state import InputMode - -if TYPE_CHECKING: - from .application import Application - -__all__ = [ - "CursorShape", - "CursorShapeConfig", - "SimpleCursorShapeConfig", - "ModalCursorShapeConfig", - "DynamicCursorShapeConfig", - "to_cursor_shape_config", -] - - -class CursorShape(Enum): - # Default value that should tell the output implementation to never send - # cursor shape escape sequences. This is the default right now, because - # before this `CursorShape` functionality was introduced into - # prompt_toolkit itself, people had workarounds to send cursor shapes - # escapes into the terminal, by monkey patching some of prompt_toolkit's - # internals. We don't want the default prompt_toolkit implemetation to - # interefere with that. E.g., IPython patches the `ViState.input_mode` - # property. See: https://github.com/ipython/ipython/pull/13501/files - _NEVER_CHANGE = "_NEVER_CHANGE" - - BLOCK = "BLOCK" - BEAM = "BEAM" - UNDERLINE = "UNDERLINE" - BLINKING_BLOCK = "BLINKING_BLOCK" - BLINKING_BEAM = "BLINKING_BEAM" - BLINKING_UNDERLINE = "BLINKING_UNDERLINE" - - -class CursorShapeConfig(ABC): - @abstractmethod - def get_cursor_shape(self, application: "Application[Any]") -> CursorShape: - """ - Return the cursor shape to be used in the current state. - """ - - -AnyCursorShapeConfig = Union[CursorShape, CursorShapeConfig, None] - - -class SimpleCursorShapeConfig(CursorShapeConfig): - """ - Always show the given cursor shape. - """ - - def __init__(self, cursor_shape: CursorShape = CursorShape._NEVER_CHANGE) -> None: - self.cursor_shape = cursor_shape - - def get_cursor_shape(self, application: "Application[Any]") -> CursorShape: - return self.cursor_shape - - -class ModalCursorShapeConfig(CursorShapeConfig): - """ - Show cursor shape according to the current input mode. - """ - - 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 - if application.vi_state.input_mode == InputMode.REPLACE: - return CursorShape.UNDERLINE - - # Default - return CursorShape.BLOCK - - -class DynamicCursorShapeConfig(CursorShapeConfig): - def __init__( - self, get_cursor_shape_config: Callable[[], AnyCursorShapeConfig] - ) -> None: - self.get_cursor_shape_config = get_cursor_shape_config - - def get_cursor_shape(self, application: "Application[Any]") -> CursorShape: - return to_cursor_shape_config(self.get_cursor_shape_config()).get_cursor_shape( - application - ) - - -def to_cursor_shape_config(value: AnyCursorShapeConfig) -> CursorShapeConfig: - """ - Take a `CursorShape` instance or `CursorShapeConfig` and turn it into a - `CursorShapeConfig`. - """ - if value is None: - return SimpleCursorShapeConfig() - - if isinstance(value, CursorShape): - return SimpleCursorShapeConfig(value) - - return value diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/data_structures.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/data_structures.py deleted file mode 100644 index d031acffd2a..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/data_structures.py +++ /dev/null @@ -1,16 +0,0 @@ -from typing import NamedTuple - -__all__ = [ - "Point", - "Size", -] - - -class Point(NamedTuple): - x: int - y: int - - -class Size(NamedTuple): - rows: int - columns: int diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/document.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/document.py deleted file mode 100644 index 19841552054..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/document.py +++ /dev/null @@ -1,1190 +0,0 @@ -""" -The `Document` that implements all the text operations/querying. -""" -import bisect -import re -import string -import weakref -from typing import ( - Callable, - Dict, - Iterable, - List, - NoReturn, - Optional, - Pattern, - Tuple, - cast, -) - -from .clipboard import ClipboardData -from .filters import vi_mode -from .selection import PasteMode, SelectionState, SelectionType - -__all__ = [ - "Document", -] - - -# Regex for finding "words" in documents. (We consider a group of alnum -# characters a word, but also a group of special characters a word, as long as -# it doesn't contain a space.) -# (This is a 'word' in Vi.) -_FIND_WORD_RE = re.compile(r"([a-zA-Z0-9_]+|[^a-zA-Z0-9_\s]+)") -_FIND_CURRENT_WORD_RE = re.compile(r"^([a-zA-Z0-9_]+|[^a-zA-Z0-9_\s]+)") -_FIND_CURRENT_WORD_INCLUDE_TRAILING_WHITESPACE_RE = re.compile( - r"^(([a-zA-Z0-9_]+|[^a-zA-Z0-9_\s]+)\s*)" -) - -# Regex for finding "WORDS" in documents. -# (This is a 'WORD in Vi.) -_FIND_BIG_WORD_RE = re.compile(r"([^\s]+)") -_FIND_CURRENT_BIG_WORD_RE = re.compile(r"^([^\s]+)") -_FIND_CURRENT_BIG_WORD_INCLUDE_TRAILING_WHITESPACE_RE = re.compile(r"^([^\s]+\s*)") - -# Share the Document._cache between all Document instances. -# (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( - Dict[str, "_DocumentCache"], - weakref.WeakValueDictionary(), # Maps document.text to DocumentCache instance. -) - - -class _ImmutableLineList(List[str]): - """ - Some protection for our 'lines' list, which is assumed to be immutable in the cache. - (Useful for detecting obvious bugs.) - """ - - def _error(self, *a: object, **kw: object) -> NoReturn: - raise NotImplementedError("Attempt to modify an immutable list.") - - __setitem__ = _error # type: ignore - append = _error - clear = _error - extend = _error - insert = _error - pop = _error - remove = _error - reverse = _error - sort = _error # type: ignore - - -class _DocumentCache: - def __init__(self) -> None: - #: List of lines for the Document text. - self.lines: Optional[_ImmutableLineList] = None - - #: List of index positions, pointing to the start of all the lines. - self.line_indexes: Optional[List[int]] = None - - -class Document: - """ - This is a immutable class around the text and cursor position, and contains - methods for querying this data, e.g. to give the text before the cursor. - - This class is usually instantiated by a :class:`~prompt_toolkit.buffer.Buffer` - object, and accessed as the `document` property of that class. - - :param text: string - :param cursor_position: int - :param selection: :class:`.SelectionState` - """ - - __slots__ = ("_text", "_cursor_position", "_selection", "_cache") - - def __init__( - self, - text: str = "", - cursor_position: Optional[int] = None, - selection: Optional[SelectionState] = None, - ) -> None: - - # Check cursor position. It can also be right after the end. (Where we - # insert text.) - assert cursor_position is None or cursor_position <= len(text), AssertionError( - f"cursor_position={cursor_position!r}, len_text={len(text)!r}" - ) - - # By default, if no cursor position was given, make sure to put the - # cursor position is at the end of the document. This is what makes - # sense in most places. - if cursor_position is None: - cursor_position = len(text) - - # Keep these attributes private. A `Document` really has to be - # considered to be immutable, because otherwise the caching will break - # things. Because of that, we wrap these into read-only properties. - self._text = text - self._cursor_position = cursor_position - self._selection = selection - - # Cache for lines/indexes. (Shared with other Document instances that - # contain the same text. - try: - self._cache = _text_to_document_cache[self.text] - except KeyError: - self._cache = _DocumentCache() - _text_to_document_cache[self.text] = self._cache - - # XX: For some reason, above, we can't use 'WeakValueDictionary.setdefault'. - # This fails in Pypy3. `self._cache` becomes None, because that's what - # 'setdefault' returns. - # self._cache = _text_to_document_cache.setdefault(self.text, _DocumentCache()) - # assert self._cache - - def __repr__(self) -> str: - return f"{self.__class__.__name__}({self.text!r}, {self.cursor_position!r})" - - def __eq__(self, other: object) -> bool: - if not isinstance(other, Document): - return False - - return ( - self.text == other.text - and self.cursor_position == other.cursor_position - and self.selection == other.selection - ) - - @property - def text(self) -> str: - "The document text." - return self._text - - @property - def cursor_position(self) -> int: - "The document cursor position." - return self._cursor_position - - @property - def selection(self) -> Optional[SelectionState]: - ":class:`.SelectionState` object." - return self._selection - - @property - def current_char(self) -> str: - """Return character under cursor or an empty string.""" - return self._get_char_relative_to_cursor(0) or "" - - @property - def char_before_cursor(self) -> str: - """Return character before the cursor or an empty string.""" - return self._get_char_relative_to_cursor(-1) or "" - - @property - def text_before_cursor(self) -> str: - return self.text[: self.cursor_position :] - - @property - def text_after_cursor(self) -> str: - return self.text[self.cursor_position :] - - @property - def current_line_before_cursor(self) -> str: - """Text from the start of the line until the cursor.""" - _, _, text = self.text_before_cursor.rpartition("\n") - return text - - @property - def current_line_after_cursor(self) -> str: - """Text from the cursor until the end of the line.""" - text, _, _ = self.text_after_cursor.partition("\n") - return text - - @property - def lines(self) -> List[str]: - """ - Array of all the lines. - """ - # Cache, because this one is reused very often. - if self._cache.lines is None: - self._cache.lines = _ImmutableLineList(self.text.split("\n")) - - return self._cache.lines - - @property - def _line_start_indexes(self) -> List[int]: - """ - Array pointing to the start indexes of all the lines. - """ - # Cache, because this is often reused. (If it is used, it's often used - # many times. And this has to be fast for editing big documents!) - if self._cache.line_indexes is None: - # Create list of line lengths. - line_lengths = map(len, self.lines) - - # Calculate cumulative sums. - indexes = [0] - append = indexes.append - pos = 0 - - for line_length in line_lengths: - pos += line_length + 1 - append(pos) - - # Remove the last item. (This is not a new line.) - if len(indexes) > 1: - indexes.pop() - - self._cache.line_indexes = indexes - - return self._cache.line_indexes - - @property - def lines_from_current(self) -> List[str]: - """ - Array of the lines starting from the current line, until the last line. - """ - return self.lines[self.cursor_position_row :] - - @property - def line_count(self) -> int: - r"""Return the number of lines in this document. If the document ends - with a trailing \n, that counts as the beginning of a new line.""" - return len(self.lines) - - @property - def current_line(self) -> str: - """Return the text on the line where the cursor is. (when the input - consists of just one line, it equals `text`.""" - return self.current_line_before_cursor + self.current_line_after_cursor - - @property - def leading_whitespace_in_current_line(self) -> str: - """The leading whitespace in the left margin of the current line.""" - current_line = self.current_line - length = len(current_line) - len(current_line.lstrip()) - return current_line[:length] - - def _get_char_relative_to_cursor(self, offset: int = 0) -> str: - """ - Return character relative to cursor position, or empty string - """ - try: - return self.text[self.cursor_position + offset] - except IndexError: - return "" - - @property - def on_first_line(self) -> bool: - """ - True when we are at the first line. - """ - return self.cursor_position_row == 0 - - @property - def on_last_line(self) -> bool: - """ - True when we are at the last line. - """ - return self.cursor_position_row == self.line_count - 1 - - @property - def cursor_position_row(self) -> int: - """ - Current row. (0-based.) - """ - row, _ = self._find_line_start_index(self.cursor_position) - return row - - @property - def cursor_position_col(self) -> int: - """ - Current column. (0-based.) - """ - # (Don't use self.text_before_cursor to calculate this. Creating - # substrings and doing rsplit is too expensive for getting the cursor - # position.) - _, 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]: - """ - For the index of a character at a certain line, calculate the index of - the first character on that line. - - Return (row, index) tuple. - """ - indexes = self._line_start_indexes - - pos = bisect.bisect_right(indexes, index) - 1 - return pos, indexes[pos] - - 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.) - """ - # Find start of this line. - row, row_index = self._find_line_start_index(index) - col = index - row_index - - return row, col - - def translate_row_col_to_index(self, row: int, col: int) -> int: - """ - Given a (row, col) tuple, return the corresponding index. - (Row and col params are 0-based.) - - Negative row/col values are turned into zero. - """ - try: - result = self._line_start_indexes[row] - line = self.lines[row] - except IndexError: - if row < 0: - result = self._line_start_indexes[0] - line = self.lines[0] - else: - result = self._line_start_indexes[-1] - line = self.lines[-1] - - result += max(0, min(col, len(line))) - - # Keep in range. (len(self.text) is included, because the cursor can be - # right after the end of the text as well.) - result = max(0, min(result, len(self.text))) - return result - - @property - def is_cursor_at_the_end(self) -> bool: - """True when the cursor is at the end of the text.""" - return self.cursor_position == len(self.text) - - @property - def is_cursor_at_the_end_of_line(self) -> bool: - """True when the cursor is at the end of this line.""" - return self.current_char in ("\n", "") - - def has_match_at_current_position(self, sub: str) -> bool: - """ - `True` when this substring is found at the cursor position. - """ - return self.text.find(sub, self.cursor_position) == self.cursor_position - - def find( - self, - sub: str, - in_current_line: bool = False, - include_current_position: bool = False, - ignore_case: bool = False, - count: int = 1, - ) -> Optional[int]: - """ - Find `text` after the cursor, return position relative to the cursor - position. Return `None` if nothing was found. - - :param count: Find the n-th occurrence. - """ - assert isinstance(ignore_case, bool) - - if in_current_line: - text = self.current_line_after_cursor - else: - text = self.text_after_cursor - - if not include_current_position: - if len(text) == 0: - return None # (Otherwise, we always get a match for the empty string.) - else: - text = text[1:] - - flags = re.IGNORECASE if ignore_case else 0 - iterator = re.finditer(re.escape(sub), text, flags) - - try: - for i, match in enumerate(iterator): - if i + 1 == count: - if include_current_position: - return match.start(0) - else: - return match.start(0) + 1 - except StopIteration: - pass - return None - - 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. - """ - flags = re.IGNORECASE if ignore_case else 0 - return [a.start() for a in re.finditer(re.escape(sub), self.text, flags)] - - def find_backwards( - self, - sub: str, - in_current_line: bool = False, - ignore_case: bool = False, - count: int = 1, - ) -> Optional[int]: - """ - Find `text` before the cursor, return position relative to the cursor - position. Return `None` if nothing was found. - - :param count: Find the n-th occurrence. - """ - if in_current_line: - before_cursor = self.current_line_before_cursor[::-1] - else: - before_cursor = self.text_before_cursor[::-1] - - flags = re.IGNORECASE if ignore_case else 0 - iterator = re.finditer(re.escape(sub[::-1]), before_cursor, flags) - - try: - for i, match in enumerate(iterator): - if i + 1 == count: - return -match.start(0) - len(sub) - except StopIteration: - pass - return None - - def get_word_before_cursor( - self, WORD: bool = False, pattern: Optional[Pattern[str]] = None - ) -> str: - """ - Give the word before the cursor. - If we have whitespace before the cursor this returns an empty string. - - :param pattern: (None or compiled regex). When given, use this regex - pattern. - """ - if self._is_word_before_cursor_complete(WORD=WORD, pattern=pattern): - # Space before the cursor or no text before cursor. - return "" - - text_before_cursor = self.text_before_cursor - start = self.find_start_of_previous_word(WORD=WORD, pattern=pattern) or 0 - - return text_before_cursor[len(text_before_cursor) + start :] - - def _is_word_before_cursor_complete( - self, WORD: bool = False, pattern: Optional[Pattern[str]] = None - ) -> bool: - if pattern: - return self.find_start_of_previous_word(WORD=WORD, pattern=pattern) is None - else: - return ( - self.text_before_cursor == "" or self.text_before_cursor[-1:].isspace() - ) - - def find_start_of_previous_word( - self, count: int = 1, WORD: bool = False, pattern: Optional[Pattern[str]] = None - ) -> Optional[int]: - """ - Return an index relative to the cursor position pointing to the start - of the previous word. Return `None` if nothing was found. - - :param pattern: (None or compiled regex). When given, use this regex - pattern. - """ - assert not (WORD and pattern) - - # Reverse the text before the cursor, in order to do an efficient - # backwards search. - text_before_cursor = self.text_before_cursor[::-1] - - if pattern: - regex = pattern - elif WORD: - regex = _FIND_BIG_WORD_RE - else: - regex = _FIND_WORD_RE - - iterator = regex.finditer(text_before_cursor) - - try: - for i, match in enumerate(iterator): - if i + 1 == count: - return -match.end(0) - except StopIteration: - pass - return None - - def find_boundaries_of_current_word( - self, - WORD: bool = False, - include_leading_whitespace: bool = False, - include_trailing_whitespace: bool = False, - ) -> 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 - don't belong to any word.) - If not on a word, this returns (0,0) - """ - text_before_cursor = self.current_line_before_cursor[::-1] - text_after_cursor = self.current_line_after_cursor - - def get_regex(include_whitespace: bool) -> Pattern[str]: - return { - (False, False): _FIND_CURRENT_WORD_RE, - (False, True): _FIND_CURRENT_WORD_INCLUDE_TRAILING_WHITESPACE_RE, - (True, False): _FIND_CURRENT_BIG_WORD_RE, - (True, True): _FIND_CURRENT_BIG_WORD_INCLUDE_TRAILING_WHITESPACE_RE, - }[(WORD, include_whitespace)] - - match_before = get_regex(include_leading_whitespace).search(text_before_cursor) - match_after = get_regex(include_trailing_whitespace).search(text_after_cursor) - - # When there is a match before and after, and we're not looking for - # WORDs, make sure that both the part before and after the cursor are - # either in the [a-zA-Z_] alphabet or not. Otherwise, drop the part - # before the cursor. - if not WORD and match_before and match_after: - c1 = self.text[self.cursor_position - 1] - c2 = self.text[self.cursor_position] - alphabet = string.ascii_letters + "0123456789_" - - if (c1 in alphabet) != (c2 in alphabet): - match_before = None - - return ( - -match_before.end(1) if match_before else 0, - match_after.end(1) if match_after else 0, - ) - - def get_word_under_cursor(self, WORD: bool = False) -> str: - """ - Return the word, currently below the cursor. - This returns an empty string when the cursor is on a whitespace region. - """ - start, end = self.find_boundaries_of_current_word(WORD=WORD) - return self.text[self.cursor_position + start : self.cursor_position + end] - - def find_next_word_beginning( - self, count: int = 1, WORD: bool = False - ) -> Optional[int]: - """ - Return an index relative to the cursor position pointing to the start - of the next word. Return `None` if nothing was found. - """ - if count < 0: - return self.find_previous_word_beginning(count=-count, WORD=WORD) - - regex = _FIND_BIG_WORD_RE if WORD else _FIND_WORD_RE - iterator = regex.finditer(self.text_after_cursor) - - try: - for i, match in enumerate(iterator): - # Take first match, unless it's the word on which we're right now. - if i == 0 and match.start(1) == 0: - count += 1 - - if i + 1 == count: - return match.start(1) - except StopIteration: - pass - return None - - def find_next_word_ending( - self, include_current_position: bool = False, count: int = 1, WORD: bool = False - ) -> Optional[int]: - """ - Return an index relative to the cursor position pointing to the end - of the next word. Return `None` if nothing was found. - """ - if count < 0: - return self.find_previous_word_ending(count=-count, WORD=WORD) - - if include_current_position: - text = self.text_after_cursor - else: - text = self.text_after_cursor[1:] - - regex = _FIND_BIG_WORD_RE if WORD else _FIND_WORD_RE - iterable = regex.finditer(text) - - try: - for i, match in enumerate(iterable): - if i + 1 == count: - value = match.end(1) - - if include_current_position: - return value - else: - return value + 1 - - except StopIteration: - pass - return None - - def find_previous_word_beginning( - self, count: int = 1, WORD: bool = False - ) -> Optional[int]: - """ - Return an index relative to the cursor position pointing to the start - of the previous word. Return `None` if nothing was found. - """ - if count < 0: - return self.find_next_word_beginning(count=-count, WORD=WORD) - - regex = _FIND_BIG_WORD_RE if WORD else _FIND_WORD_RE - iterator = regex.finditer(self.text_before_cursor[::-1]) - - try: - for i, match in enumerate(iterator): - if i + 1 == count: - return -match.end(1) - except StopIteration: - pass - return None - - def find_previous_word_ending( - self, count: int = 1, WORD: bool = False - ) -> Optional[int]: - """ - Return an index relative to the cursor position pointing to the end - of the previous word. Return `None` if nothing was found. - """ - if count < 0: - return self.find_next_word_ending(count=-count, WORD=WORD) - - text_before_cursor = self.text_after_cursor[:1] + self.text_before_cursor[::-1] - - regex = _FIND_BIG_WORD_RE if WORD else _FIND_WORD_RE - iterator = regex.finditer(text_before_cursor) - - try: - for i, match in enumerate(iterator): - # Take first match, unless it's the word on which we're right now. - if i == 0 and match.start(1) == 0: - count += 1 - - if i + 1 == count: - return -match.start(1) + 1 - except StopIteration: - pass - return None - - def find_next_matching_line( - self, match_func: Callable[[str], bool], count: int = 1 - ) -> Optional[int]: - """ - Look downwards for empty lines. - Return the line index, relative to the current line. - """ - result = None - - for index, line in enumerate(self.lines[self.cursor_position_row + 1 :]): - if match_func(line): - result = 1 + index - count -= 1 - - if count == 0: - break - - return result - - def find_previous_matching_line( - self, match_func: Callable[[str], bool], count: int = 1 - ) -> Optional[int]: - """ - Look upwards for empty lines. - Return the line index, relative to the current line. - """ - result = None - - for index, line in enumerate(self.lines[: self.cursor_position_row][::-1]): - if match_func(line): - result = -1 - index - count -= 1 - - if count == 0: - break - - return result - - def get_cursor_left_position(self, count: int = 1) -> int: - """ - Relative position for cursor left. - """ - if count < 0: - return self.get_cursor_right_position(-count) - - return -min(self.cursor_position_col, count) - - def get_cursor_right_position(self, count: int = 1) -> int: - """ - Relative position for cursor_right. - """ - if count < 0: - return self.get_cursor_left_position(-count) - - return min(count, len(self.current_line_after_cursor)) - - def get_cursor_up_position( - self, count: int = 1, preferred_column: Optional[int] = None - ) -> int: - """ - Return the relative cursor position (character index) where we would be if the - user pressed the arrow-up button. - - :param preferred_column: When given, go to this column instead of - staying at the current column. - """ - assert count >= 1 - column = ( - self.cursor_position_col if preferred_column is None else preferred_column - ) - - return ( - self.translate_row_col_to_index( - max(0, self.cursor_position_row - count), column - ) - - self.cursor_position - ) - - def get_cursor_down_position( - self, count: int = 1, preferred_column: Optional[int] = None - ) -> int: - """ - Return the relative cursor position (character index) where we would be if the - user pressed the arrow-down button. - - :param preferred_column: When given, go to this column instead of - staying at the current column. - """ - assert count >= 1 - column = ( - self.cursor_position_col if preferred_column is None else preferred_column - ) - - return ( - self.translate_row_col_to_index(self.cursor_position_row + count, column) - - self.cursor_position - ) - - def find_enclosing_bracket_right( - self, left_ch: str, right_ch: str, end_pos: Optional[int] = None - ) -> Optional[int]: - """ - Find the right bracket enclosing current position. Return the relative - position to the cursor position. - - When `end_pos` is given, don't look past the position. - """ - if self.current_char == right_ch: - return 0 - - if end_pos is None: - end_pos = len(self.text) - else: - end_pos = min(len(self.text), end_pos) - - stack = 1 - - # Look forward. - for i in range(self.cursor_position + 1, end_pos): - c = self.text[i] - - if c == left_ch: - stack += 1 - elif c == right_ch: - stack -= 1 - - if stack == 0: - return i - self.cursor_position - - return None - - def find_enclosing_bracket_left( - self, left_ch: str, right_ch: str, start_pos: Optional[int] = None - ) -> Optional[int]: - """ - Find the left bracket enclosing current position. Return the relative - position to the cursor position. - - When `start_pos` is given, don't look past the position. - """ - if self.current_char == left_ch: - return 0 - - if start_pos is None: - start_pos = 0 - else: - start_pos = max(0, start_pos) - - stack = 1 - - # Look backward. - for i in range(self.cursor_position - 1, start_pos - 1, -1): - c = self.text[i] - - if c == right_ch: - stack += 1 - elif c == left_ch: - stack -= 1 - - if stack == 0: - return i - self.cursor_position - - return None - - def find_matching_bracket_position( - self, start_pos: Optional[int] = None, end_pos: Optional[int] = None - ) -> int: - """ - Return relative cursor position of matching [, (, { or < bracket. - - When `start_pos` or `end_pos` are given. Don't look past the positions. - """ - - # Look for a match. - for pair in "()", "[]", "{}", "<>": - A = pair[0] - B = pair[1] - if self.current_char == A: - return self.find_enclosing_bracket_right(A, B, end_pos=end_pos) or 0 - elif self.current_char == B: - return self.find_enclosing_bracket_left(A, B, start_pos=start_pos) or 0 - - return 0 - - def get_start_of_document_position(self) -> int: - """Relative position for the start of the document.""" - return -self.cursor_position - - def get_end_of_document_position(self) -> int: - """Relative position for the end of the document.""" - return len(self.text) - self.cursor_position - - def get_start_of_line_position(self, after_whitespace: bool = False) -> int: - """Relative position for the start of this line.""" - if after_whitespace: - current_line = self.current_line - return ( - len(current_line) - - len(current_line.lstrip()) - - self.cursor_position_col - ) - else: - return -len(self.current_line_before_cursor) - - def get_end_of_line_position(self) -> int: - """Relative position for the end of this line.""" - return len(self.current_line_after_cursor) - - def last_non_blank_of_current_line_position(self) -> int: - """ - Relative position for the last non blank character of this line. - """ - return len(self.current_line.rstrip()) - self.cursor_position_col - 1 - - def get_column_cursor_position(self, column: int) -> int: - """ - Return the relative cursor position for this column at the current - line. (It will stay between the boundaries of the line in case of a - larger number.) - """ - line_length = len(self.current_line) - current_column = self.cursor_position_col - column = max(0, min(line_length, column)) - - return column - current_column - - def selection_range( - self, - ) -> Tuple[ - int, int - ]: # XXX: shouldn't this return `None` if there is no selection??? - """ - Return (from, to) tuple of the selection. - start and end position are included. - - This doesn't take the selection type into account. Use - `selection_ranges` instead. - """ - if self.selection: - from_, to = sorted( - [self.cursor_position, self.selection.original_cursor_position] - ) - else: - from_, to = self.cursor_position, self.cursor_position - - return from_, to - - 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. - - This will yield several (from, to) tuples in case of a BLOCK selection. - This will return zero ranges, like (8,8) for empty lines in a block - selection. - """ - if self.selection: - from_, to = sorted( - [self.cursor_position, self.selection.original_cursor_position] - ) - - if self.selection.type == SelectionType.BLOCK: - from_line, from_column = self.translate_index_to_position(from_) - to_line, to_column = self.translate_index_to_position(to) - from_column, to_column = sorted([from_column, to_column]) - lines = self.lines - - if vi_mode(): - to_column += 1 - - for l in range(from_line, to_line + 1): - line_length = len(lines[l]) - - if from_column <= line_length: - yield ( - self.translate_row_col_to_index(l, from_column), - self.translate_row_col_to_index( - l, min(line_length, to_column) - ), - ) - else: - # In case of a LINES selection, go to the start/end of the lines. - if self.selection.type == SelectionType.LINES: - from_ = max(0, self.text.rfind("\n", 0, from_) + 1) - - if self.text.find("\n", to) >= 0: - to = self.text.find("\n", to) - else: - to = len(self.text) - 1 - - # In Vi mode, the upper boundary is always included. For Emacs, - # that's not the case. - if vi_mode(): - to += 1 - - yield from_, to - - def selection_range_at_line(self, row: int) -> Optional[Tuple[int, int]]: - """ - If the selection spans a portion of the given line, return a (from, to) tuple. - - The returned upper boundary is not included in the selection, so - `(0, 0)` is an empty selection. `(0, 1)`, is a one character selection. - - Returns None if the selection doesn't cover this line at all. - """ - if self.selection: - line = self.lines[row] - - row_start = self.translate_row_col_to_index(row, 0) - row_end = self.translate_row_col_to_index(row, len(line)) - - from_, to = sorted( - [self.cursor_position, self.selection.original_cursor_position] - ) - - # Take the intersection of the current line and the selection. - intersection_start = max(row_start, from_) - intersection_end = min(row_end, to) - - if intersection_start <= intersection_end: - if self.selection.type == SelectionType.LINES: - intersection_start = row_start - intersection_end = row_end - - elif self.selection.type == SelectionType.BLOCK: - _, col1 = self.translate_index_to_position(from_) - _, col2 = self.translate_index_to_position(to) - col1, col2 = sorted([col1, col2]) - - if col1 > len(line): - return None # Block selection doesn't cross this line. - - intersection_start = self.translate_row_col_to_index(row, col1) - intersection_end = self.translate_row_col_to_index(row, col2) - - _, from_column = self.translate_index_to_position(intersection_start) - _, to_column = self.translate_index_to_position(intersection_end) - - # In Vi mode, the upper boundary is always included. For Emacs - # mode, that's not the case. - if vi_mode(): - to_column += 1 - - return from_column, to_column - return None - - 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 - clipboard data, represents whatever has to be put on the clipboard. - """ - if self.selection: - cut_parts = [] - remaining_parts = [] - new_cursor_position = self.cursor_position - - last_to = 0 - for from_, to in self.selection_ranges(): - if last_to == 0: - new_cursor_position = from_ - - remaining_parts.append(self.text[last_to:from_]) - cut_parts.append(self.text[from_:to]) - last_to = to - - remaining_parts.append(self.text[last_to:]) - - cut_text = "\n".join(cut_parts) - remaining_text = "".join(remaining_parts) - - # In case of a LINES selection, don't include the trailing newline. - if self.selection.type == SelectionType.LINES and cut_text.endswith("\n"): - cut_text = cut_text[:-1] - - return ( - Document(text=remaining_text, cursor_position=new_cursor_position), - ClipboardData(cut_text, self.selection.type), - ) - else: - return self, ClipboardData("") - - def paste_clipboard_data( - self, - data: ClipboardData, - paste_mode: PasteMode = PasteMode.EMACS, - count: int = 1, - ) -> "Document": - """ - Return a new :class:`.Document` instance which contains the result if - we would paste this data at the current cursor position. - - :param paste_mode: Where to paste. (Before/after/emacs.) - :param count: When >1, Paste multiple times. - """ - before = paste_mode == PasteMode.VI_BEFORE - after = paste_mode == PasteMode.VI_AFTER - - if data.type == SelectionType.CHARACTERS: - if after: - new_text = ( - self.text[: self.cursor_position + 1] - + data.text * count - + self.text[self.cursor_position + 1 :] - ) - else: - new_text = ( - self.text_before_cursor + data.text * count + self.text_after_cursor - ) - - new_cursor_position = self.cursor_position + len(data.text) * count - if before: - new_cursor_position -= 1 - - elif data.type == SelectionType.LINES: - l = self.cursor_position_row - if before: - lines = self.lines[:l] + [data.text] * count + self.lines[l:] - new_text = "\n".join(lines) - new_cursor_position = len("".join(self.lines[:l])) + l - else: - lines = self.lines[: l + 1] + [data.text] * count + self.lines[l + 1 :] - new_cursor_position = len("".join(self.lines[: l + 1])) + l + 1 - new_text = "\n".join(lines) - - elif data.type == SelectionType.BLOCK: - lines = self.lines[:] - start_line = self.cursor_position_row - start_column = self.cursor_position_col + (0 if before else 1) - - for i, line in enumerate(data.text.split("\n")): - index = i + start_line - if index >= len(lines): - lines.append("") - - lines[index] = lines[index].ljust(start_column) - lines[index] = ( - lines[index][:start_column] - + line * count - + lines[index][start_column:] - ) - - new_text = "\n".join(lines) - new_cursor_position = self.cursor_position + (0 if before else 1) - - return Document(text=new_text, cursor_position=new_cursor_position) - - def empty_line_count_at_the_end(self) -> int: - """ - Return number of empty lines at the end of the document. - """ - count = 0 - for line in self.lines[::-1]: - if not line or line.isspace(): - count += 1 - else: - break - - return count - - def start_of_paragraph(self, count: int = 1, before: bool = False) -> int: - """ - Return the start of the current paragraph. (Relative cursor position.) - """ - - def match_func(text: str) -> bool: - return not text or text.isspace() - - line_index = self.find_previous_matching_line( - match_func=match_func, count=count - ) - - if line_index: - add = 0 if before else 1 - return min(0, self.get_cursor_up_position(count=-line_index) + add) - else: - return -self.cursor_position - - def end_of_paragraph(self, count: int = 1, after: bool = False) -> int: - """ - Return the end of the current paragraph. (Relative cursor position.) - """ - - def match_func(text: str) -> bool: - return not text or text.isspace() - - line_index = self.find_next_matching_line(match_func=match_func, count=count) - - if line_index: - add = 0 if after else 1 - return max(0, self.get_cursor_down_position(count=line_index) - add) - else: - return len(self.text_after_cursor) - - # Modifiers. - - 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. - """ - return Document( - text=self.text + text, - cursor_position=self.cursor_position, - selection=self.selection, - ) - - 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. - """ - selection_state = self.selection - - if selection_state: - selection_state = SelectionState( - original_cursor_position=selection_state.original_cursor_position - + len(text), - type=selection_state.type, - ) - - return Document( - text=text + self.text, - cursor_position=self.cursor_position + len(text), - selection=selection_state, - ) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/enums.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/enums.py deleted file mode 100644 index 4f496e67adf..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/enums.py +++ /dev/null @@ -1,17 +0,0 @@ -from enum import Enum - - -class EditingMode(Enum): - # The set of key bindings that is active. - VI = "VI" - EMACS = "EMACS" - - -#: Name of the search buffer. -SEARCH_BUFFER = "SEARCH_BUFFER" - -#: Name of the default buffer. -DEFAULT_BUFFER = "DEFAULT_BUFFER" - -#: Name of the system buffer. -SYSTEM_BUFFER = "SYSTEM_BUFFER" diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/eventloop/__init__.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/eventloop/__init__.py deleted file mode 100644 index 3a3e9acf22d..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/eventloop/__init__.py +++ /dev/null @@ -1,28 +0,0 @@ -from .async_generator import generator_to_async_generator -from .inputhook import ( - InputHookContext, - InputHookSelector, - new_eventloop_with_inputhook, - set_eventloop_with_inputhook, -) -from .utils import ( - call_soon_threadsafe, - get_event_loop, - get_traceback_from_context, - run_in_executor_with_context, -) - -__all__ = [ - # Async generator - "generator_to_async_generator", - # Utils. - "run_in_executor_with_context", - "call_soon_threadsafe", - "get_traceback_from_context", - "get_event_loop", - # Inputhooks. - "new_eventloop_with_inputhook", - "set_eventloop_with_inputhook", - "InputHookSelector", - "InputHookContext", -] diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/eventloop/async_context_manager.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/eventloop/async_context_manager.py deleted file mode 100644 index 39146165a0d..00000000000 --- a/contrib/python/prompt-toolkit/py3/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(<arguments>): - <setup> - try: - yield <value> - finally: - <cleanup> - This makes this: - async with some_async_generator(<arguments>) as <variable>: - <body> - equivalent to this: - <setup> - try: - <variable> = <value> - <body> - finally: - <cleanup> - """ - - @wraps(func) - def helper(*args, **kwds): - return _AsyncGeneratorContextManager(func, args, kwds) # type: ignore - - return helper diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/eventloop/async_generator.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/eventloop/async_generator.py deleted file mode 100644 index 7ab3c286738..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/eventloop/async_generator.py +++ /dev/null @@ -1,74 +0,0 @@ -""" -Implementation for async generators. -""" -from asyncio import Queue -from typing import AsyncGenerator, Callable, Iterable, TypeVar, Union - -from .utils import get_event_loop, run_in_executor_with_context - -__all__ = [ - "generator_to_async_generator", -] - - -_T = TypeVar("_T") - - -class _Done: - pass - - -async def generator_to_async_generator( - get_iterable: Callable[[], Iterable[_T]] -) -> AsyncGenerator[_T, None]: - """ - Turn a generator or iterable into an async generator. - - This works by running the generator in a background thread. - - :param get_iterable: Function that returns a generator or iterable when - called. - """ - quitting = False - _done = _Done() - q: Queue[Union[_T, _Done]] = Queue() - loop = get_event_loop() - - def runner() -> None: - """ - Consume the generator in background thread. - When items are received, they'll be pushed to the queue. - """ - try: - for item in get_iterable(): - # When this async generator was cancelled (closed), stop this - # thread. - if quitting: - break - - loop.call_soon_threadsafe(q.put_nowait, item) - - finally: - loop.call_soon_threadsafe(q.put_nowait, _done) - - # Start background thread. - runner_f = run_in_executor_with_context(runner) - - try: - while True: - item = await q.get() - if isinstance(item, _Done): - break - else: - yield item - finally: - # When this async generator is closed (GeneratorExit exception, stop - # the background thread as well. - we don't need that anymore.) - quitting = True - - # Wait for the background thread to finish. (should happen right after - # the next item is yielded). If we don't do this, and the event loop - # gets closed before the runner is done, then we'll get a - # `RuntimeError: Event loop is closed` exception printed to stdout that - # we can't handle. - await runner_f diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/eventloop/dummy_contextvars.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/eventloop/dummy_contextvars.py deleted file mode 100644 index 3fcd2605517..00000000000 --- a/contrib/python/prompt-toolkit/py3/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, Any, 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/contrib/python/prompt-toolkit/py3/prompt_toolkit/eventloop/inputhook.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/eventloop/inputhook.py deleted file mode 100644 index 05d298117e8..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/eventloop/inputhook.py +++ /dev/null @@ -1,183 +0,0 @@ -""" -Similar to `PyOS_InputHook` of the Python API, we can plug in an input hook in -the asyncio event loop. - -The way this works is by using a custom 'selector' that runs the other event -loop until the real selector is ready. - -It's the responsibility of this event hook to return when there is input ready. -There are two ways to detect when input is ready: - -The inputhook itself is a callable that receives an `InputHookContext`. This -callable should run the other event loop, and return when the main loop has -stuff to do. There are two ways to detect when to return: - -- Call the `input_is_ready` method periodically. Quit when this returns `True`. - -- Add the `fileno` as a watch to the external eventloop. Quit when file descriptor - becomes readable. (But don't read from it.) - - Note that this is not the same as checking for `sys.stdin.fileno()`. The - eventloop of prompt-toolkit allows thread-based executors, for example for - asynchronous autocompletion. When the completion for instance is ready, we - also want prompt-toolkit to gain control again in order to display that. -""" -import asyncio -import os -import select -import selectors -import sys -import threading -from asyncio import AbstractEventLoop -from selectors import BaseSelector, SelectorKey -from typing import TYPE_CHECKING, Any, Callable, List, Mapping, Optional, Tuple - -from .utils import get_event_loop - -__all__ = [ - "new_eventloop_with_inputhook", - "set_eventloop_with_inputhook", - "InputHookSelector", - "InputHookContext", -] - -if TYPE_CHECKING: - from _typeshed import FileDescriptorLike - - _EventMask = int - - -def new_eventloop_with_inputhook( - inputhook: Callable[["InputHookContext"], None] -) -> AbstractEventLoop: - """ - Create a new event loop with the given inputhook. - """ - selector = InputHookSelector(selectors.DefaultSelector(), inputhook) - loop = asyncio.SelectorEventLoop(selector) - return loop - - -def set_eventloop_with_inputhook( - inputhook: Callable[["InputHookContext"], None] -) -> AbstractEventLoop: - """ - Create a new event loop with the given inputhook, and activate it. - """ - loop = new_eventloop_with_inputhook(inputhook) - asyncio.set_event_loop(loop) - return loop - - -class InputHookSelector(BaseSelector): - """ - Usage: - - selector = selectors.SelectSelector() - loop = asyncio.SelectorEventLoop(InputHookSelector(selector, inputhook)) - asyncio.set_event_loop(loop) - """ - - def __init__( - 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": - return self.selector.register(fileobj, events, data=data) - - def unregister(self, fileobj: "FileDescriptorLike") -> "SelectorKey": - return self.selector.unregister(fileobj) - - def modify( - 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"]]: - # If there are tasks in the current event loop, - # don't run the input hook. - if len(getattr(get_event_loop(), "_ready", [])) > 0: - return self.selector.select(timeout=timeout) - - ready = False - result = None - - # Run selector in other thread. - def run_selector() -> None: - nonlocal ready, result - result = self.selector.select(timeout=timeout) - os.write(self._w, b"x") - ready = True - - th = threading.Thread(target=run_selector) - th.start() - - def input_is_ready() -> bool: - return ready - - # Call inputhook. - # The inputhook function is supposed to return when our selector - # becomes ready. The inputhook can do that by registering the fd in its - # own loop, or by checking the `input_is_ready` function regularly. - self.inputhook(InputHookContext(self._r, input_is_ready)) - - # Flush the read end of the pipe. - try: - # Before calling 'os.read', call select.select. This is required - # when the gevent monkey patch has been applied. 'os.read' is never - # monkey patched and won't be cooperative, so that would block all - # other select() calls otherwise. - # See: http://www.gevent.org/gevent.os.html - - # Note: On Windows, this is apparently not an issue. - # However, if we would ever want to add a select call, it - # should use `windll.kernel32.WaitForMultipleObjects`, - # because `select.select` can't wait for a pipe on Windows. - if sys.platform != "win32": - select.select([self._r], [], [], None) - - os.read(self._r, 1024) - except OSError: - # This happens when the window resizes and a SIGWINCH was received. - # We get 'Error: [Errno 4] Interrupted system call' - # Just ignore. - pass - - # Wait for the real selector to be done. - th.join() - assert result is not None - return result - - def close(self) -> None: - """ - Clean up resources. - """ - if self._r: - os.close(self._r) - os.close(self._w) - - self._r = self._w = -1 - self.selector.close() - - def get_map(self) -> Mapping["FileDescriptorLike", "SelectorKey"]: - return self.selector.get_map() - - -class InputHookContext: - """ - Given as a parameter to the inputhook. - """ - - def __init__(self, fileno: int, input_is_ready: Callable[[], bool]) -> None: - self._fileno = fileno - self.input_is_ready = input_is_ready - - def fileno(self) -> int: - return self._fileno diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/eventloop/utils.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/eventloop/utils.py deleted file mode 100644 index 2e5a05e8383..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/eventloop/utils.py +++ /dev/null @@ -1,118 +0,0 @@ -import asyncio -import sys -import time -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") - - -def run_in_executor_with_context( - func: Callable[..., _T], - *args: Any, - loop: Optional[asyncio.AbstractEventLoop] = None, -) -> Awaitable[_T]: - """ - Run a function in an executor, but make sure it uses the same contextvars. - This is required so that the function will see the right application. - - See also: https://bugs.python.org/issue34014 - """ - loop = loop or get_event_loop() - ctx: contextvars.Context = contextvars.copy_context() - - return loop.run_in_executor(None, ctx.run, func, *args) - - -def call_soon_threadsafe( - func: Callable[[], None], - max_postpone_time: Optional[float] = None, - loop: Optional[asyncio.AbstractEventLoop] = None, -) -> None: - """ - Wrapper around asyncio's `call_soon_threadsafe`. - - This takes a `max_postpone_time` which can be used to tune the urgency of - the method. - - Asyncio runs tasks in first-in-first-out. However, this is not what we - want for the render function of the prompt_toolkit UI. Rendering is - expensive, but since the UI is invalidated very often, in some situations - we render the UI too often, so much that the rendering CPU usage slows down - the rest of the processing of the application. (Pymux is an example where - we have to balance the CPU time spend on rendering the UI, and parsing - process output.) - 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() - - # If no `max_postpone_time` has been given, schedule right now. - if max_postpone_time is None: - loop2.call_soon_threadsafe(func) - return - - max_postpone_until = time.time() + max_postpone_time - - def schedule() -> None: - # When there are no other tasks scheduled in the event loop. Run it - # now. - # Notice: uvloop doesn't have this _ready attribute. In that case, - # always call immediately. - if not getattr(loop2, "_ready", []): - func() - return - - # If the timeout expired, run this now. - if time.time() > max_postpone_until: - func() - return - - # Schedule again for later. - loop2.call_soon_threadsafe(schedule) - - loop2.call_soon_threadsafe(schedule) - - -def get_traceback_from_context(context: Dict[str, Any]) -> Optional[TracebackType]: - """ - Get the traceback object from the context. - """ - exception = context.get("exception") - if exception: - if hasattr(exception, "__traceback__"): - return cast(TracebackType, exception.__traceback__) - else: - # call_exception_handler() is usually called indirectly - # from an except block. If it's not the case, the traceback - # is undefined... - 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: - return asyncio.get_event_loop_policy().get_event_loop() diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/eventloop/win32.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/eventloop/win32.py deleted file mode 100644 index fbc02d493ab..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/eventloop/win32.py +++ /dev/null @@ -1,73 +0,0 @@ -import sys - -assert sys.platform == "win32" - -from ctypes import pointer - -from ..utils import SPHINX_AUTODOC_RUNNING - -# Do not import win32-specific stuff when generating documentation. -# Otherwise RTD would be unable to generate docs for this module. -if not SPHINX_AUTODOC_RUNNING: - from ctypes import windll - -from ctypes.wintypes import BOOL, DWORD, HANDLE -from typing import List, Optional - -from prompt_toolkit.win32_types import SECURITY_ATTRIBUTES - -__all__ = ["wait_for_handles", "create_win32_event"] - - -WAIT_TIMEOUT = 0x00000102 -INFINITE = -1 - - -def wait_for_handles( - handles: List[HANDLE], timeout: int = INFINITE -) -> Optional[HANDLE]: - """ - Waits for multiple handles. (Similar to 'select') Returns the handle which is ready. - Returns `None` on timeout. - http://msdn.microsoft.com/en-us/library/windows/desktop/ms687025(v=vs.85).aspx - - Note that handles should be a list of `HANDLE` objects, not integers. See - this comment in the patch by @quark-zju for the reason why: - - ''' Make sure HANDLE on Windows has a correct size - - Previously, the type of various HANDLEs are native Python integer - types. The ctypes library will treat them as 4-byte integer when used - in function arguments. On 64-bit Windows, HANDLE is 8-byte and usually - a small integer. Depending on whether the extra 4 bytes are zero-ed out - or not, things can happen to work, or break. ''' - - This function returns either `None` or one of the given `HANDLE` objects. - (The return value can be tested with the `is` operator.) - """ - arrtype = HANDLE * len(handles) - handle_array = arrtype(*handles) - - ret: int = windll.kernel32.WaitForMultipleObjects( - len(handle_array), handle_array, BOOL(False), DWORD(timeout) - ) - - if ret == WAIT_TIMEOUT: - return None - else: - return handles[ret] - - -def create_win32_event() -> HANDLE: - """ - Creates a Win32 unnamed Event . - http://msdn.microsoft.com/en-us/library/windows/desktop/ms682396(v=vs.85).aspx - """ - return HANDLE( - windll.kernel32.CreateEventA( - pointer(SECURITY_ATTRIBUTES()), - BOOL(True), # Manual reset event. - BOOL(False), # Initial state. - None, # Unnamed event object. - ) - ) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/filters/__init__.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/filters/__init__.py deleted file mode 100644 index d97195fb828..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/filters/__init__.py +++ /dev/null @@ -1,68 +0,0 @@ -""" -Filters decide whether something is active or not (they decide about a boolean -state). This is used to enable/disable features, like key bindings, parts of -the layout and other stuff. For instance, we could have a `HasSearch` filter -attached to some part of the layout, in order to show that part of the user -interface only while the user is searching. - -Filters are made to avoid having to attach callbacks to all event in order to -propagate state. However, they are lazy, they don't automatically propagate the -state of what they are observing. Only when a filter is called (it's actually a -callable), it will calculate its value. So, its not really reactive -programming, but it's made to fit for this framework. - -Filters can be chained using ``&`` and ``|`` operations, and inverted using the -``~`` operator, for instance:: - - filter = has_focus('default') & ~ has_selection -""" -from .app import * -from .base import Always, Condition, Filter, FilterOrBool, Never -from .cli import * -from .utils import is_true, to_filter - -__all__ = [ - # app - "has_arg", - "has_completions", - "completion_is_selected", - "has_focus", - "buffer_has_focus", - "has_selection", - "has_validation_error", - "is_done", - "is_read_only", - "is_multiline", - "renderer_height_is_known", - "in_editing_mode", - "in_paste_mode", - "vi_mode", - "vi_navigation_mode", - "vi_insert_mode", - "vi_insert_multiple_mode", - "vi_replace_mode", - "vi_selection_mode", - "vi_waiting_for_text_object_mode", - "vi_digraph_mode", - "vi_recording_macro", - "emacs_mode", - "emacs_insert_mode", - "emacs_selection_mode", - "shift_selection_mode", - "is_searching", - "control_is_searchable", - "vi_search_direction_reversed", - # base. - "Filter", - "Never", - "Always", - "Condition", - "FilterOrBool", - # utils. - "is_true", - "to_filter", -] - -from .cli import __all__ as cli_all - -__all__.extend(cli_all) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/filters/app.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/filters/app.py deleted file mode 100644 index dcc3fc0c6ee..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/filters/app.py +++ /dev/null @@ -1,412 +0,0 @@ -""" -Filters that accept a `Application` as argument. -""" -from typing import TYPE_CHECKING, cast - -from prompt_toolkit.application.current import get_app -from prompt_toolkit.cache import memoized -from prompt_toolkit.enums import EditingMode - -from .base import Condition - -if TYPE_CHECKING: - from prompt_toolkit.layout.layout import FocusableElement - - -__all__ = [ - "has_arg", - "has_completions", - "completion_is_selected", - "has_focus", - "buffer_has_focus", - "has_selection", - "has_suggestion", - "has_validation_error", - "is_done", - "is_read_only", - "is_multiline", - "renderer_height_is_known", - "in_editing_mode", - "in_paste_mode", - "vi_mode", - "vi_navigation_mode", - "vi_insert_mode", - "vi_insert_multiple_mode", - "vi_replace_mode", - "vi_selection_mode", - "vi_waiting_for_text_object_mode", - "vi_digraph_mode", - "vi_recording_macro", - "emacs_mode", - "emacs_insert_mode", - "emacs_selection_mode", - "shift_selection_mode", - "is_searching", - "control_is_searchable", - "vi_search_direction_reversed", -] - - -@memoized() -def has_focus(value: "FocusableElement") -> Condition: - """ - Enable when this buffer has the focus. - """ - from prompt_toolkit.buffer import Buffer - from prompt_toolkit.layout import walk - from prompt_toolkit.layout.containers import Container, Window, to_container - from prompt_toolkit.layout.controls import UIControl - - if isinstance(value, str): - - def test() -> bool: - return get_app().current_buffer.name == value - - elif isinstance(value, Buffer): - - def test() -> bool: - return get_app().current_buffer == value - - elif isinstance(value, UIControl): - - def test() -> bool: - return get_app().layout.current_control == value - - else: - value = to_container(value) - - if isinstance(value, Window): - - def test() -> bool: - return get_app().layout.current_window == value - - else: - - def test() -> bool: - # Consider focused when any window inside this container is - # focused. - current_window = get_app().layout.current_window - - for c in walk(cast(Container, value)): - if isinstance(c, Window) and c == current_window: - return True - return False - - @Condition - def has_focus_filter() -> bool: - return test() - - return has_focus_filter - - -@Condition -def buffer_has_focus() -> bool: - """ - Enabled when the currently focused control is a `BufferControl`. - """ - return get_app().layout.buffer_has_focus - - -@Condition -def has_selection() -> bool: - """ - Enable when the current buffer has a selection. - """ - return bool(get_app().current_buffer.selection_state) - - -@Condition -def has_suggestion() -> bool: - """ - Enable when the current buffer has a suggestion. - """ - buffer = get_app().current_buffer - return buffer.suggestion is not None and buffer.suggestion.text != "" - - -@Condition -def has_completions() -> bool: - """ - Enable when the current buffer has completions. - """ - state = get_app().current_buffer.complete_state - return state is not None and len(state.completions) > 0 - - -@Condition -def completion_is_selected() -> bool: - """ - True when the user selected a completion. - """ - complete_state = get_app().current_buffer.complete_state - return complete_state is not None and complete_state.current_completion is not None - - -@Condition -def is_read_only() -> bool: - """ - True when the current buffer is read only. - """ - return get_app().current_buffer.read_only() - - -@Condition -def is_multiline() -> bool: - """ - True when the current buffer has been marked as multiline. - """ - return get_app().current_buffer.multiline() - - -@Condition -def has_validation_error() -> bool: - "Current buffer has validation error." - return get_app().current_buffer.validation_error is not None - - -@Condition -def has_arg() -> bool: - "Enable when the input processor has an 'arg'." - return get_app().key_processor.arg is not None - - -@Condition -def is_done() -> bool: - """ - True when the CLI is returning, aborting or exiting. - """ - return get_app().is_done - - -@Condition -def renderer_height_is_known() -> bool: - """ - Only True when the renderer knows it's real height. - - (On VT100 terminals, we have to wait for a CPR response, before we can be - sure of the available height between the cursor position and the bottom of - the terminal. And usually it's nicer to wait with drawing bottom toolbars - until we receive the height, in order to avoid flickering -- first drawing - somewhere in the middle, and then again at the bottom.) - """ - return get_app().renderer.height_is_known - - -@memoized() -def in_editing_mode(editing_mode: EditingMode) -> Condition: - """ - Check whether a given editing mode is active. (Vi or Emacs.) - """ - - @Condition - def in_editing_mode_filter() -> bool: - return get_app().editing_mode == editing_mode - - return in_editing_mode_filter - - -@Condition -def in_paste_mode() -> bool: - return get_app().paste_mode() - - -@Condition -def vi_mode() -> bool: - return get_app().editing_mode == EditingMode.VI - - -@Condition -def vi_navigation_mode() -> bool: - """ - Active when the set for Vi navigation key bindings are active. - """ - from prompt_toolkit.key_binding.vi_state import InputMode - - app = get_app() - - if ( - app.editing_mode != EditingMode.VI - or app.vi_state.operator_func - or app.vi_state.waiting_for_digraph - or app.current_buffer.selection_state - ): - return False - - return ( - app.vi_state.input_mode == InputMode.NAVIGATION - or app.vi_state.temporary_navigation_mode - or app.current_buffer.read_only() - ) - - -@Condition -def vi_insert_mode() -> bool: - from prompt_toolkit.key_binding.vi_state import InputMode - - app = get_app() - - if ( - app.editing_mode != EditingMode.VI - or app.vi_state.operator_func - or app.vi_state.waiting_for_digraph - or app.current_buffer.selection_state - or app.vi_state.temporary_navigation_mode - or app.current_buffer.read_only() - ): - return False - - return app.vi_state.input_mode == InputMode.INSERT - - -@Condition -def vi_insert_multiple_mode() -> bool: - from prompt_toolkit.key_binding.vi_state import InputMode - - app = get_app() - - if ( - app.editing_mode != EditingMode.VI - or app.vi_state.operator_func - or app.vi_state.waiting_for_digraph - or app.current_buffer.selection_state - or app.vi_state.temporary_navigation_mode - or app.current_buffer.read_only() - ): - return False - - return app.vi_state.input_mode == InputMode.INSERT_MULTIPLE - - -@Condition -def vi_replace_mode() -> bool: - from prompt_toolkit.key_binding.vi_state import InputMode - - app = get_app() - - if ( - app.editing_mode != EditingMode.VI - or app.vi_state.operator_func - or app.vi_state.waiting_for_digraph - or app.current_buffer.selection_state - or app.vi_state.temporary_navigation_mode - or app.current_buffer.read_only() - ): - return False - - return app.vi_state.input_mode == InputMode.REPLACE - - -@Condition -def vi_replace_single_mode() -> bool: - from prompt_toolkit.key_binding.vi_state import InputMode - - app = get_app() - - if ( - app.editing_mode != EditingMode.VI - or app.vi_state.operator_func - or app.vi_state.waiting_for_digraph - or app.current_buffer.selection_state - or app.vi_state.temporary_navigation_mode - or app.current_buffer.read_only() - ): - return False - - return app.vi_state.input_mode == InputMode.REPLACE_SINGLE - - -@Condition -def vi_selection_mode() -> bool: - app = get_app() - if app.editing_mode != EditingMode.VI: - return False - - return bool(app.current_buffer.selection_state) - - -@Condition -def vi_waiting_for_text_object_mode() -> bool: - app = get_app() - if app.editing_mode != EditingMode.VI: - return False - - return app.vi_state.operator_func is not None - - -@Condition -def vi_digraph_mode() -> bool: - app = get_app() - if app.editing_mode != EditingMode.VI: - return False - - return app.vi_state.waiting_for_digraph - - -@Condition -def vi_recording_macro() -> bool: - "When recording a Vi macro." - app = get_app() - if app.editing_mode != EditingMode.VI: - return False - - return app.vi_state.recording_register is not None - - -@Condition -def emacs_mode() -> bool: - "When the Emacs bindings are active." - return get_app().editing_mode == EditingMode.EMACS - - -@Condition -def emacs_insert_mode() -> bool: - app = get_app() - if ( - app.editing_mode != EditingMode.EMACS - or app.current_buffer.selection_state - or app.current_buffer.read_only() - ): - return False - return True - - -@Condition -def emacs_selection_mode() -> bool: - app = get_app() - return bool( - app.editing_mode == EditingMode.EMACS and app.current_buffer.selection_state - ) - - -@Condition -def shift_selection_mode() -> bool: - app = get_app() - return bool( - app.current_buffer.selection_state - and app.current_buffer.selection_state.shift_mode - ) - - -@Condition -def is_searching() -> bool: - "When we are searching." - app = get_app() - return app.layout.is_searching - - -@Condition -def control_is_searchable() -> bool: - "When the current UIControl is searchable." - from prompt_toolkit.layout.controls import BufferControl - - control = get_app().layout.current_control - - return ( - isinstance(control, BufferControl) and control.search_buffer_control is not None - ) - - -@Condition -def vi_search_direction_reversed() -> bool: - "When the '/' and '?' key bindings for Vi-style searching have been reversed." - return get_app().reverse_vi_search_direction() diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/filters/base.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/filters/base.py deleted file mode 100644 index fd57cca6e90..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/filters/base.py +++ /dev/null @@ -1,217 +0,0 @@ -from abc import ABCMeta, abstractmethod -from typing import Callable, Dict, Iterable, List, Tuple, Union - -__all__ = ["Filter", "Never", "Always", "Condition", "FilterOrBool"] - - -class Filter(metaclass=ABCMeta): - """ - Base class for any filter to activate/deactivate a feature, depending on a - condition. - - The return value of ``__call__`` will tell if the feature should be active. - """ - - @abstractmethod - def __call__(self) -> bool: - """ - The actual call to evaluate the filter. - """ - return True - - def __and__(self, other: "Filter") -> "Filter": - """ - Chaining of filters using the & operator. - """ - return _and_cache[self, other] - - def __or__(self, other: "Filter") -> "Filter": - """ - Chaining of filters using the | operator. - """ - return _or_cache[self, other] - - def __invert__(self) -> "Filter": - """ - Inverting of filters using the ~ operator. - """ - return _invert_cache[self] - - def __bool__(self) -> None: - """ - By purpose, we don't allow bool(...) operations directly on a filter, - because the meaning is ambiguous. - - Executing a filter has to be done always by calling it. Providing - defaults for `None` values should be done through an `is None` check - instead of for instance ``filter1 or Always()``. - """ - raise ValueError( - "The truth value of a Filter is ambiguous. " - "Instead, call it as a function." - ) - - -class _AndCache(Dict[Tuple[Filter, Filter], "_AndList"]): - """ - Cache for And operation between filters. - (Filter classes are stateless, so we can reuse them.) - - Note: This could be a memory leak if we keep creating filters at runtime. - If that is True, the filters should be weakreffed (not the tuple of - filters), and tuples should be removed when one of these filters is - removed. In practise however, there is a finite amount of filters. - """ - - def __missing__(self, filters: Tuple[Filter, Filter]) -> Filter: - a, b = filters - assert isinstance(b, Filter), "Expecting filter, got %r" % b - - if isinstance(b, Always) or isinstance(a, Never): - return a - elif isinstance(b, Never) or isinstance(a, Always): - return b - - result = _AndList(filters) - self[filters] = result - return result - - -class _OrCache(Dict[Tuple[Filter, Filter], "_OrList"]): - """Cache for Or operation between filters.""" - - def __missing__(self, filters: Tuple[Filter, Filter]) -> Filter: - a, b = filters - assert isinstance(b, Filter), "Expecting filter, got %r" % b - - if isinstance(b, Always) or isinstance(a, Never): - return b - elif isinstance(b, Never) or isinstance(a, Always): - return a - - result = _OrList(filters) - self[filters] = result - return result - - -class _InvertCache(Dict[Filter, "_Invert"]): - """Cache for inversion operator.""" - - def __missing__(self, filter: Filter) -> Filter: - result = _Invert(filter) - self[filter] = result - return result - - -_and_cache = _AndCache() -_or_cache = _OrCache() -_invert_cache = _InvertCache() - - -class _AndList(Filter): - """ - Result of &-operation between several filters. - """ - - def __init__(self, filters: Iterable[Filter]) -> None: - self.filters: List[Filter] = [] - - for f in filters: - if isinstance(f, _AndList): # Turn nested _AndLists into one. - self.filters.extend(f.filters) - else: - self.filters.append(f) - - def __call__(self) -> bool: - return all(f() for f in self.filters) - - def __repr__(self) -> str: - return "&".join(repr(f) for f in self.filters) - - -class _OrList(Filter): - """ - Result of |-operation between several filters. - """ - - def __init__(self, filters: Iterable[Filter]) -> None: - self.filters: List[Filter] = [] - - for f in filters: - if isinstance(f, _OrList): # Turn nested _OrLists into one. - self.filters.extend(f.filters) - else: - self.filters.append(f) - - def __call__(self) -> bool: - return any(f() for f in self.filters) - - def __repr__(self) -> str: - return "|".join(repr(f) for f in self.filters) - - -class _Invert(Filter): - """ - Negation of another filter. - """ - - def __init__(self, filter: Filter) -> None: - self.filter = filter - - def __call__(self) -> bool: - return not self.filter() - - def __repr__(self) -> str: - return "~%r" % self.filter - - -class Always(Filter): - """ - Always enable feature. - """ - - def __call__(self) -> bool: - return True - - def __invert__(self) -> "Never": - return Never() - - -class Never(Filter): - """ - Never enable feature. - """ - - def __call__(self) -> bool: - return False - - def __invert__(self) -> Always: - return Always() - - -class Condition(Filter): - """ - Turn any callable into a Filter. The callable is supposed to not take any - arguments. - - This can be used as a decorator:: - - @Condition - def feature_is_active(): # `feature_is_active` becomes a Filter. - return True - - :param func: Callable which takes no inputs and returns a boolean. - """ - - def __init__(self, func: Callable[[], bool]) -> None: - self.func = func - - def __call__(self) -> bool: - return self.func() - - def __repr__(self) -> str: - return "Condition(%r)" % self.func - - -# Often used as type annotation. -FilterOrBool = Union[Filter, bool] diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/filters/cli.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/filters/cli.py deleted file mode 100644 index 7135196cdd0..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/filters/cli.py +++ /dev/null @@ -1,62 +0,0 @@ -""" -For backwards-compatibility. keep this file. -(Many people are going to have key bindings that rely on this file.) -""" -from .app import * - -__all__ = [ - # Old names. - "HasArg", - "HasCompletions", - "HasFocus", - "HasSelection", - "HasValidationError", - "IsDone", - "IsReadOnly", - "IsMultiline", - "RendererHeightIsKnown", - "InEditingMode", - "InPasteMode", - "ViMode", - "ViNavigationMode", - "ViInsertMode", - "ViInsertMultipleMode", - "ViReplaceMode", - "ViSelectionMode", - "ViWaitingForTextObjectMode", - "ViDigraphMode", - "EmacsMode", - "EmacsInsertMode", - "EmacsSelectionMode", - "IsSearching", - "HasSearch", - "ControlIsSearchable", -] - -# Keep the original classnames for backwards compatibility. -HasValidationError = lambda: has_validation_error -HasArg = lambda: has_arg -IsDone = lambda: is_done -RendererHeightIsKnown = lambda: renderer_height_is_known -ViNavigationMode = lambda: vi_navigation_mode -InPasteMode = lambda: in_paste_mode -EmacsMode = lambda: emacs_mode -EmacsInsertMode = lambda: emacs_insert_mode -ViMode = lambda: vi_mode -IsSearching = lambda: is_searching -HasSearch = lambda: is_searching -ControlIsSearchable = lambda: control_is_searchable -EmacsSelectionMode = lambda: emacs_selection_mode -ViDigraphMode = lambda: vi_digraph_mode -ViWaitingForTextObjectMode = lambda: vi_waiting_for_text_object_mode -ViSelectionMode = lambda: vi_selection_mode -ViReplaceMode = lambda: vi_replace_mode -ViInsertMultipleMode = lambda: vi_insert_multiple_mode -ViInsertMode = lambda: vi_insert_mode -HasSelection = lambda: has_selection -HasCompletions = lambda: has_completions -IsReadOnly = lambda: is_read_only -IsMultiline = lambda: is_multiline - -HasFocus = has_focus # No lambda here! (Has_focus is callable that returns a callable.) -InEditingMode = in_editing_mode diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/filters/utils.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/filters/utils.py deleted file mode 100644 index aaf44aa87ec..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/filters/utils.py +++ /dev/null @@ -1,41 +0,0 @@ -from typing import Dict - -from .base import Always, Filter, FilterOrBool, Never - -__all__ = [ - "to_filter", - "is_true", -] - - -_always = Always() -_never = Never() - - -_bool_to_filter: Dict[bool, Filter] = { - True: _always, - False: _never, -} - - -def to_filter(bool_or_filter: FilterOrBool) -> Filter: - """ - Accept both booleans and Filters as input and - turn it into a Filter. - """ - if isinstance(bool_or_filter, bool): - return _bool_to_filter[bool_or_filter] - - if isinstance(bool_or_filter, Filter): - return bool_or_filter - - raise TypeError("Expecting a bool or a Filter instance. Got %r" % bool_or_filter) - - -def is_true(value: FilterOrBool) -> bool: - """ - Test whether `value` is True. In case of a Filter, call it. - - :param value: Boolean or `Filter` instance. - """ - return to_filter(value)() diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/__init__.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/__init__.py deleted file mode 100644 index f0c92c96f94..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/__init__.py +++ /dev/null @@ -1,54 +0,0 @@ -""" -Many places in prompt_toolkit can take either plain text, or formatted text. -For instance the :func:`~prompt_toolkit.shortcuts.prompt` function takes either -plain text or formatted text for the prompt. The -:class:`~prompt_toolkit.layout.FormattedTextControl` can also take either plain -text or formatted text. - -In any case, there is an input that can either be just plain text (a string), -an :class:`.HTML` object, an :class:`.ANSI` object or a sequence of -`(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 .ansi import ANSI -from .base import ( - AnyFormattedText, - FormattedText, - StyleAndTextTuples, - Template, - is_formatted_text, - merge_formatted_text, - to_formatted_text, -) -from .html import HTML -from .pygments import PygmentsTokens -from .utils import ( - fragment_list_len, - fragment_list_to_text, - fragment_list_width, - split_lines, - to_plain_text, -) - -__all__ = [ - # Base. - "AnyFormattedText", - "to_formatted_text", - "is_formatted_text", - "Template", - "merge_formatted_text", - "FormattedText", - "StyleAndTextTuples", - # HTML. - "HTML", - # ANSI. - "ANSI", - # Pygments. - "PygmentsTokens", - # Utils. - "fragment_list_len", - "fragment_list_width", - "fragment_list_to_text", - "split_lines", - "to_plain_text", -] diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/ansi.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/ansi.py deleted file mode 100644 index 2a30b09c218..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/ansi.py +++ /dev/null @@ -1,297 +0,0 @@ -from string import Formatter -from typing import Generator, List, Optional, Tuple, Union - -from prompt_toolkit.output.vt100 import BG_ANSI_COLORS, FG_ANSI_COLORS -from prompt_toolkit.output.vt100 import _256_colors as _256_colors_table - -from .base import StyleAndTextTuples - -__all__ = [ - "ANSI", - "ansi_escape", -] - - -class ANSI: - """ - ANSI formatted text. - Take something ANSI escaped text, for use as a formatted string. E.g. - - :: - - ANSI('\\x1b[31mhello \\x1b[32mworld') - - Characters between ``\\001`` and ``\\002`` are supposed to have a zero width - when printed, but these are literally sent to the terminal output. This can - be used for instance, for inserting Final Term prompt commands. They will - be translated into a prompt_toolkit '[ZeroWidthEscape]' fragment. - """ - - def __init__(self, value: str) -> None: - self.value = value - self._formatted_text: StyleAndTextTuples = [] - - # Default style attributes. - self._color: Optional[str] = None - self._bgcolor: Optional[str] = None - self._bold = False - self._underline = False - self._strike = False - self._italic = False - self._blink = False - self._reverse = False - self._hidden = False - - # Process received text. - parser = self._parse_corot() - parser.send(None) # type: ignore - for c in value: - parser.send(c) - - def _parse_corot(self) -> Generator[None, str, None]: - """ - Coroutine that parses the ANSI escape sequences. - """ - style = "" - formatted_text = self._formatted_text - - while True: - # NOTE: CSI is a special token within a stream of characters that - # introduces an ANSI control sequence used to set the - # style attributes of the following characters. - csi = False - - c = yield - - # Everything between \001 and \002 should become a ZeroWidthEscape. - if c == "\001": - escaped_text = "" - while c != "\002": - c = yield - if c == "\002": - formatted_text.append(("[ZeroWidthEscape]", escaped_text)) - c = yield - break - else: - escaped_text += c - - # Check for CSI - if c == "\x1b": - # Start of color escape sequence. - square_bracket = yield - if square_bracket == "[": - csi = True - else: - continue - elif c == "\x9b": - csi = True - - if csi: - # Got a CSI sequence. Color codes are following. - current = "" - params = [] - - while True: - char = yield - - # Construct number - if char.isdigit(): - current += char - - # Eval number - else: - # Limit and save number value - params.append(min(int(current or 0), 9999)) - - # Get delimiter token if present - if char == ";": - current = "" - - # Check and evaluate color codes - elif char == "m": - # Set attributes and token. - self._select_graphic_rendition(params) - style = self._create_style_string() - break - - # Check and evaluate cursor forward - elif char == "C": - for i in range(params[0]): - # add <SPACE> using current style - formatted_text.append((style, " ")) - break - - else: - # Ignore unsupported sequence. - break - else: - # Add current character. - # NOTE: At this point, we could merge the current character - # into the previous tuple if the style did not change, - # however, it's not worth the effort given that it will - # be "Exploded" once again when it's rendered to the - # output. - formatted_text.append((style, c)) - - def _select_graphic_rendition(self, attrs: List[int]) -> None: - """ - Taken a list of graphics attributes and apply changes. - """ - if not attrs: - attrs = [0] - else: - attrs = list(attrs[::-1]) - - while attrs: - attr = attrs.pop() - - if attr in _fg_colors: - self._color = _fg_colors[attr] - elif attr in _bg_colors: - self._bgcolor = _bg_colors[attr] - elif attr == 1: - self._bold = True - # elif attr == 2: - # self._faint = True - elif attr == 3: - self._italic = True - elif attr == 4: - self._underline = True - elif attr == 5: - self._blink = True # Slow blink - elif attr == 6: - self._blink = True # Fast blink - elif attr == 7: - self._reverse = True - elif attr == 8: - self._hidden = True - elif attr == 9: - self._strike = True - elif attr == 22: - self._bold = False # Normal intensity - elif attr == 23: - self._italic = False - elif attr == 24: - self._underline = False - elif attr == 25: - self._blink = False - elif attr == 27: - self._reverse = False - elif attr == 28: - self._hidden = False - elif attr == 29: - self._strike = False - elif not attr: - # Reset all style attributes - self._color = None - self._bgcolor = None - self._bold = False - self._underline = False - self._strike = False - self._italic = False - self._blink = False - self._reverse = False - self._hidden = False - - elif attr in (38, 48) and len(attrs) > 1: - n = attrs.pop() - - # 256 colors. - if n == 5 and len(attrs) >= 1: - if attr == 38: - m = attrs.pop() - self._color = _256_colors.get(m) - elif attr == 48: - m = attrs.pop() - self._bgcolor = _256_colors.get(m) - - # True colors. - if n == 2 and len(attrs) >= 3: - try: - color_str = "#{:02x}{:02x}{:02x}".format( - attrs.pop(), - attrs.pop(), - attrs.pop(), - ) - except IndexError: - pass - else: - if attr == 38: - self._color = color_str - elif attr == 48: - self._bgcolor = color_str - - def _create_style_string(self) -> str: - """ - Turn current style flags into a string for usage in a formatted text. - """ - result = [] - if self._color: - result.append(self._color) - if self._bgcolor: - result.append("bg:" + self._bgcolor) - if self._bold: - result.append("bold") - if self._underline: - result.append("underline") - if self._strike: - result.append("strike") - if self._italic: - result.append("italic") - if self._blink: - result.append("blink") - if self._reverse: - result.append("reverse") - if self._hidden: - result.append("hidden") - - return " ".join(result) - - def __repr__(self) -> str: - return f"ANSI({self.value!r})" - - def __pt_formatted_text__(self) -> StyleAndTextTuples: - return self._formatted_text - - 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": - """ - ANSI('<b>%s</b>') % value - """ - if not isinstance(value, tuple): - value = (value,) - - value = tuple(ansi_escape(i) for i in value) - return ANSI(self.value % value) - - -# Mapping of the ANSI color codes to their names. -_fg_colors = {v: k for k, v in FG_ANSI_COLORS.items()} -_bg_colors = {v: k for k, v in BG_ANSI_COLORS.items()} - -# Mapping of the escape codes for 256colors to their 'ffffff' value. -_256_colors = {} - -for i, (r, g, b) in enumerate(_256_colors_table.colors): - _256_colors[i] = f"#{r:02x}{g:02x}{b:02x}" - - -def ansi_escape(text: object) -> str: - """ - Replace characters with a special meaning. - """ - return str(text).replace("\x1b", "?").replace("\b", "?") - - -class ANSIFormatter(Formatter): - def format_field(self, value: object, format_spec: str) -> str: - return ansi_escape(format(value, format_spec)) - - -FORMATTER = ANSIFormatter() diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/base.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/base.py deleted file mode 100644 index e88c5935a5f..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/base.py +++ /dev/null @@ -1,176 +0,0 @@ -from typing import TYPE_CHECKING, Any, Callable, Iterable, List, Tuple, Union, cast - -from prompt_toolkit.mouse_events import MouseEvent - -if TYPE_CHECKING: - from typing_extensions import Protocol - -__all__ = [ - "OneStyleAndTextTuple", - "StyleAndTextTuples", - "MagicFormattedText", - "AnyFormattedText", - "to_formatted_text", - "is_formatted_text", - "Template", - "merge_formatted_text", - "FormattedText", -] - -OneStyleAndTextTuple = Union[ - Tuple[str, str], Tuple[str, str, Callable[[MouseEvent], None]] -] - -# List of (style, text) tuples. -StyleAndTextTuples = List[OneStyleAndTextTuple] - - -if TYPE_CHECKING: - from typing_extensions import TypeGuard - - class MagicFormattedText(Protocol): - """ - Any object that implements ``__pt_formatted_text__`` represents formatted - text. - """ - - def __pt_formatted_text__(self) -> StyleAndTextTuples: - ... - - -AnyFormattedText = Union[ - str, - "MagicFormattedText", - StyleAndTextTuples, - # Callable[[], 'AnyFormattedText'] # Recursive definition not supported by mypy. - Callable[[], Any], - None, -] - - -def to_formatted_text( - value: AnyFormattedText, style: str = "", auto_convert: bool = False -) -> "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 - always a `FormattedText` instance, which is a list of (style, text) tuples. - - It can take a plain text string, an `HTML` or `ANSI` object, anything that - implements `__pt_formatted_text__` or a callable that takes no arguments and - returns one of those. - - :param style: An additional style string which is applied to all text - fragments. - :param auto_convert: If `True`, also accept other types, and convert them - to a string first. - """ - result: Union[FormattedText, StyleAndTextTuples] - - if value is None: - result = [] - elif isinstance(value, str): - result = [("", value)] - elif isinstance(value, list): - result = value # StyleAndTextTuples - elif hasattr(value, "__pt_formatted_text__"): - result = cast("MagicFormattedText", value).__pt_formatted_text__() - elif callable(value): - return to_formatted_text(value(), style=style) - elif auto_convert: - result = [("", f"{value}")] - else: - raise ValueError( - "No formatted text. Expecting a unicode object, " - "HTML, ANSI or a FormattedText instance. Got %r" % (value,) - ) - - # Apply extra style. - if style: - result = cast( - StyleAndTextTuples, - [(style + " " + item_style, *rest) for item_style, *rest in result], - ) - - # Make sure the result is wrapped in a `FormattedText`. Among other - # reasons, this is important for `print_formatted_text` to work correctly - # and distinguish between lists and formatted text. - if isinstance(result, FormattedText): - return result - else: - return FormattedText(result) - - -def is_formatted_text(value: object) -> "TypeGuard[AnyFormattedText]": - """ - Check whether the input is valid formatted text (for use in assert - statements). - In case of a callable, it doesn't check the return type. - """ - if callable(value): - return True - if isinstance(value, (str, list)): - return True - if hasattr(value, "__pt_formatted_text__"): - return True - return False - - -class FormattedText(StyleAndTextTuples): - """ - A list of ``(style, text)`` tuples. - - (In some situations, this can also be ``(style, text, mouse_handler)`` - tuples.) - """ - - def __pt_formatted_text__(self) -> StyleAndTextTuples: - return self - - def __repr__(self) -> str: - return "FormattedText(%s)" % super().__repr__() - - -class Template: - """ - Template for string interpolation with formatted text. - - Example:: - - Template(' ... {} ... ').format(HTML(...)) - - :param text: Plain text. - """ - - def __init__(self, text: str) -> None: - assert "{0}" not in text - self.text = text - - def format(self, *values: AnyFormattedText) -> AnyFormattedText: - def get_result() -> AnyFormattedText: - # Split the template in parts. - parts = self.text.split("{}") - assert len(parts) - 1 == len(values) - - result = FormattedText() - for part, val in zip(parts, values): - result.append(("", part)) - result.extend(to_formatted_text(val)) - result.append(("", parts[-1])) - return result - - return get_result - - -def merge_formatted_text(items: Iterable[AnyFormattedText]) -> AnyFormattedText: - """ - Merge (Concatenate) several pieces of formatted text together. - """ - - def _merge_formatted_text() -> AnyFormattedText: - result = FormattedText() - for i in items: - result.extend(to_formatted_text(i)) - return result - - return _merge_formatted_text diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/html.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/html.py deleted file mode 100644 index 0af2b18b57a..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/html.py +++ /dev/null @@ -1,143 +0,0 @@ -import xml.dom.minidom as minidom -from string import Formatter -from typing import Any, List, Tuple, Union - -from .base import FormattedText, StyleAndTextTuples - -__all__ = ["HTML"] - - -class HTML: - """ - HTML formatted text. - Take something HTML-like, for use as a formatted string. - - :: - - # Turn something into red. - HTML('<style fg="ansired" bg="#00ff44">...</style>') - - # Italic, bold, underline and strike. - HTML('<i>...</i>') - HTML('<b>...</b>') - HTML('<u>...</u>') - HTML('<s>...</s>') - - All HTML elements become available as a "class" in the style sheet. - E.g. ``<username>...</username>`` can be styled, by setting a style for - ``username``. - """ - - def __init__(self, value: str) -> None: - self.value = value - document = minidom.parseString(f"<html-root>{value}</html-root>") - - result: StyleAndTextTuples = [] - name_stack: List[str] = [] - fg_stack: List[str] = [] - bg_stack: List[str] = [] - - def get_current_style() -> str: - "Build style string for current node." - parts = [] - if name_stack: - parts.append("class:" + ",".join(name_stack)) - - if fg_stack: - parts.append("fg:" + fg_stack[-1]) - if bg_stack: - parts.append("bg:" + bg_stack[-1]) - return " ".join(parts) - - def process_node(node: Any) -> None: - "Process node recursively." - for child in node.childNodes: - if child.nodeType == child.TEXT_NODE: - result.append((get_current_style(), child.data)) - else: - add_to_name_stack = child.nodeName not in ( - "#document", - "html-root", - "style", - ) - fg = bg = "" - - for k, v in child.attributes.items(): - if k == "fg": - fg = v - if k == "bg": - bg = v - if k == "color": - fg = v # Alias for 'fg'. - - # Check for spaces in attributes. This would result in - # invalid style strings otherwise. - if " " in fg: - raise ValueError('"fg" attribute contains a space.') - if " " in bg: - raise ValueError('"bg" attribute contains a space.') - - if add_to_name_stack: - name_stack.append(child.nodeName) - if fg: - fg_stack.append(fg) - if bg: - bg_stack.append(bg) - - process_node(child) - - if add_to_name_stack: - name_stack.pop() - if fg: - fg_stack.pop() - if bg: - bg_stack.pop() - - process_node(document) - - self.formatted_text = FormattedText(result) - - def __repr__(self) -> str: - return f"HTML({self.value!r})" - - def __pt_formatted_text__(self) -> StyleAndTextTuples: - return self.formatted_text - - 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": - """ - HTML('<b>%s</b>') % value - """ - if not isinstance(value, tuple): - value = (value,) - - value = tuple(html_escape(i) for i in value) - return HTML(self.value % value) - - -class HTMLFormatter(Formatter): - def format_field(self, value: object, format_spec: str) -> str: - return html_escape(format(value, format_spec)) - - -def html_escape(text: object) -> str: - # The string interpolation functions also take integers and other types. - # Convert to string first. - if not isinstance(text, str): - text = f"{text}" - - return ( - text.replace("&", "&") - .replace("<", "<") - .replace(">", ">") - .replace('"', """) - ) - - -FORMATTER = HTMLFormatter() diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/pygments.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/pygments.py deleted file mode 100644 index dd16f0efbeb..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/pygments.py +++ /dev/null @@ -1,30 +0,0 @@ -from typing import TYPE_CHECKING, List, Tuple - -from prompt_toolkit.styles.pygments import pygments_token_to_classname - -from .base import StyleAndTextTuples - -if TYPE_CHECKING: - from pygments.token import Token - -__all__ = [ - "PygmentsTokens", -] - - -class PygmentsTokens: - """ - Turn a pygments token list into a list of prompt_toolkit text fragments - (``(style_str, text)`` tuples). - """ - - def __init__(self, token_list: List[Tuple["Token", str]]) -> None: - self.token_list = token_list - - def __pt_formatted_text__(self) -> StyleAndTextTuples: - result: StyleAndTextTuples = [] - - for token, text in self.token_list: - result.append(("class:" + pygments_token_to_classname(token), text)) - - return result diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/utils.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/utils.py deleted file mode 100644 index cda4233e063..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/utils.py +++ /dev/null @@ -1,98 +0,0 @@ -""" -Utilities for manipulating formatted text. - -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 typing import Iterable, cast - -from prompt_toolkit.utils import get_cwidth - -from .base import ( - AnyFormattedText, - OneStyleAndTextTuple, - StyleAndTextTuples, - to_formatted_text, -) - -__all__ = [ - "to_plain_text", - "fragment_list_len", - "fragment_list_width", - "fragment_list_to_text", - "split_lines", -] - - -def to_plain_text(value: AnyFormattedText) -> str: - """ - Turn any kind of formatted text back into plain text. - """ - return fragment_list_to_text(to_formatted_text(value)) - - -def fragment_list_len(fragments: StyleAndTextTuples) -> int: - """ - Return the amount of characters in this text fragment list. - - :param fragments: List of ``(style_str, text)`` or - ``(style_str, text, mouse_handler)`` tuples. - """ - ZeroWidthEscape = "[ZeroWidthEscape]" - return sum(len(item[1]) for item in fragments if ZeroWidthEscape not in item[0]) - - -def fragment_list_width(fragments: StyleAndTextTuples) -> int: - """ - Return the character width of this text fragment list. - (Take double width characters into account.) - - :param fragments: List of ``(style_str, text)`` or - ``(style_str, text, mouse_handler)`` tuples. - """ - ZeroWidthEscape = "[ZeroWidthEscape]" - return sum( - get_cwidth(c) - for item in fragments - for c in item[1] - if ZeroWidthEscape not in item[0] - ) - - -def fragment_list_to_text(fragments: StyleAndTextTuples) -> str: - """ - Concatenate all the text parts again. - - :param fragments: List of ``(style_str, text)`` or - ``(style_str, text, mouse_handler)`` tuples. - """ - ZeroWidthEscape = "[ZeroWidthEscape]" - return "".join(item[1] for item in fragments if ZeroWidthEscape not in item[0]) - - -def split_lines(fragments: StyleAndTextTuples) -> Iterable[StyleAndTextTuples]: - """ - Take a single list of (style_str, text) tuples and yield one such list for each - line. Just like str.split, this will yield at least one item. - - :param fragments: List of (style_str, text) or (style_str, text, mouse_handler) - tuples. - """ - line: StyleAndTextTuples = [] - - for style, string, *mouse_handler in fragments: - parts = string.split("\n") - - for part in parts[:-1]: - if part: - line.append(cast(OneStyleAndTextTuple, (style, part, *mouse_handler))) - yield line - line = [] - - line.append(cast(OneStyleAndTextTuple, (style, parts[-1], *mouse_handler))) - - # Always yield the last line, even when this is an empty line. This ensures - # that when `fragments` ends with a newline character, an additional empty - # line is yielded. (Otherwise, there's no way to differentiate between the - # cases where `fragments` does and doesn't end with a newline.) - yield line diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/history.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/history.py deleted file mode 100644 index 987d7175de1..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/history.py +++ /dev/null @@ -1,301 +0,0 @@ -""" -Implementations for the history of a `Buffer`. - -NOTE: There is no `DynamicHistory`: - This doesn't work well, because the `Buffer` needs to be able to attach - an event handler to the event when a history entry is loaded. This - loading can be done asynchronously and making the history swappable would - probably break this. -""" -import datetime -import os -import threading -from abc import ABCMeta, abstractmethod -from typing import AsyncGenerator, Iterable, List, Optional, Sequence, Tuple - -from prompt_toolkit.eventloop import get_event_loop - -__all__ = [ - "History", - "ThreadedHistory", - "DummyHistory", - "FileHistory", - "InMemoryHistory", -] - - -class History(metaclass=ABCMeta): - """ - Base ``History`` class. - - This also includes abstract methods for loading/storing history. - """ - - def __init__(self) -> None: - # In memory storage for strings. - self._loaded = False - - # History that's loaded already, in reverse order. Latest, most recent - # item first. - self._loaded_strings: List[str] = [] - - # - # Methods expected by `Buffer`. - # - - async def load(self) -> AsyncGenerator[str, None]: - """ - Load the history and yield all the entries in reverse order (latest, - most recent history entry first). - - This method can be called multiple times from the `Buffer` to - repopulate the history when prompting for a new input. So we are - responsible here for both caching, and making sure that strings that - were were appended to the history will be incorporated next time this - method is called. - """ - if not self._loaded: - self._loaded_strings = list(self.load_history_strings()) - self._loaded = True - - for item in self._loaded_strings: - yield item - - def get_strings(self) -> List[str]: - """ - Get the strings from the history that are loaded so far. - (In order. Oldest item first.) - """ - return self._loaded_strings[::-1] - - def append_string(self, string: str) -> None: - "Add string to the history." - self._loaded_strings.insert(0, string) - self.store_string(string) - - # - # Implementation for specific backends. - # - - @abstractmethod - def load_history_strings(self) -> Iterable[str]: - """ - This should be a generator that yields `str` instances. - - It should yield the most recent items first, because they are the most - important. (The history can already be used, even when it's only - partially loaded.) - """ - while False: - yield - - @abstractmethod - def store_string(self, string: str) -> None: - """ - Store the string in persistent storage. - """ - - -class ThreadedHistory(History): - """ - Wrapper around `History` implementations that run the `load()` generator in - a thread. - - Use this to increase the start-up time of prompt_toolkit applications. - History entries are available as soon as they are loaded. We don't have to - wait for everything to be loaded. - """ - - def __init__(self, history: History) -> None: - super().__init__() - - self.history = history - - self._load_thread: Optional[threading.Thread] = None - - # Lock for accessing/manipulating `_loaded_strings` and `_loaded` - # together in a consistent state. - self._lock = threading.Lock() - - # Events created by each `load()` call. Used to wait for new history - # entries from the loader thread. - self._string_load_events: List[threading.Event] = [] - - async def load(self) -> AsyncGenerator[str, None]: - """ - Like `History.load(), but call `self.load_history_strings()` in a - background thread. - """ - # Start the load thread, if this is called for the first time. - if not self._load_thread: - self._load_thread = threading.Thread( - target=self._in_load_thread, - daemon=True, - ) - self._load_thread.start() - - # Consume the `_loaded_strings` list, using asyncio. - loop = get_event_loop() - - # Create threading Event so that we can wait for new items. - event = threading.Event() - event.set() - self._string_load_events.append(event) - - items_yielded = 0 - - try: - while True: - # Wait for new items to be available. - # (Use a timeout, because the executor thread is not a daemon - # thread. The "slow-history.py" example would otherwise hang if - # Control-C is pressed before the history is fully loaded, - # because there's still this non-daemon executor thread waiting - # for this event.) - got_timeout = await loop.run_in_executor( - None, lambda: event.wait(timeout=0.5) - ) - if not got_timeout: - continue - - # Read new items (in lock). - def in_executor() -> Tuple[List[str], bool]: - with self._lock: - new_items = self._loaded_strings[items_yielded:] - done = self._loaded - event.clear() - return new_items, done - - new_items, done = await loop.run_in_executor(None, in_executor) - - items_yielded += len(new_items) - - for item in new_items: - yield item - - if done: - break - finally: - self._string_load_events.remove(event) - - def _in_load_thread(self) -> None: - try: - # Start with an empty list. In case `append_string()` was called - # before `load()` happened. Then `.store_string()` will have - # written these entries back to disk and we will reload it. - self._loaded_strings = [] - - for item in self.history.load_history_strings(): - with self._lock: - self._loaded_strings.append(item) - - for event in self._string_load_events: - event.set() - finally: - with self._lock: - self._loaded = True - for event in self._string_load_events: - event.set() - - def append_string(self, string: str) -> None: - with self._lock: - self._loaded_strings.insert(0, string) - self.store_string(string) - - # All of the following are proxied to `self.history`. - - def load_history_strings(self) -> Iterable[str]: - return self.history.load_history_strings() - - def store_string(self, string: str) -> None: - self.history.store_string(string) - - def __repr__(self) -> str: - return f"ThreadedHistory({self.history!r})" - - -class InMemoryHistory(History): - """ - :class:`.History` class that keeps a list of all strings in memory. - - In order to prepopulate the history, it's possible to call either - `append_string` for all items or pass a list of strings to `__init__` here. - """ - - def __init__(self, history_strings: Optional[Sequence[str]] = None) -> None: - super().__init__() - # Emulating disk storage. - if history_strings is None: - self._storage = [] - else: - self._storage = list(history_strings) - - def load_history_strings(self) -> Iterable[str]: - yield from self._storage[::-1] - - def store_string(self, string: str) -> None: - self._storage.append(string) - - -class DummyHistory(History): - """ - :class:`.History` object that doesn't remember anything. - """ - - def load_history_strings(self) -> Iterable[str]: - return [] - - def store_string(self, string: str) -> None: - pass - - def append_string(self, string: str) -> None: - # Don't remember this. - pass - - -class FileHistory(History): - """ - :class:`.History` class that stores all strings in a file. - """ - - def __init__(self, filename: str) -> None: - self.filename = filename - super().__init__() - - def load_history_strings(self) -> Iterable[str]: - strings: List[str] = [] - lines: List[str] = [] - - def add() -> None: - if lines: - # Join and drop trailing newline. - string = "".join(lines)[:-1] - - strings.append(string) - - if os.path.exists(self.filename): - with open(self.filename, "rb") as f: - for line_bytes in f: - line = line_bytes.decode("utf-8", errors="replace") - - if line.startswith("+"): - lines.append(line[1:]) - else: - add() - lines = [] - - add() - - # Reverse the order, because newest items have to go first. - return reversed(strings) - - def store_string(self, string: str) -> None: - # Save to file. - with open(self.filename, "ab") as f: - - def write(t: str) -> None: - f.write(t.encode("utf-8")) - - write("\n# %s\n" % datetime.datetime.now()) - for line in string.split("\n"): - write("+%s\n" % line) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/__init__.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/__init__.py deleted file mode 100644 index dc319769ef4..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -from .base import DummyInput, Input, PipeInput -from .defaults import create_input, create_pipe_input - -__all__ = [ - # Base. - "Input", - "PipeInput", - "DummyInput", - # Defaults. - "create_input", - "create_pipe_input", -] diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/ansi_escape_sequences.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/ansi_escape_sequences.py deleted file mode 100644 index 2e6c5b9b286..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/ansi_escape_sequences.py +++ /dev/null @@ -1,343 +0,0 @@ -""" -Mappings from VT100 (ANSI) escape sequences to the corresponding prompt_toolkit -keys. - -We are not using the terminfo/termcap databases to detect the ANSI escape -sequences for the input. Instead, we recognize 99% of the most common -sequences. This works well, because in practice, every modern terminal is -mostly Xterm compatible. - -Some useful docs: -- Mintty: https://github.com/mintty/mintty/blob/master/wiki/Keycodes.md -""" -from typing import Dict, Tuple, Union - -from ..keys import Keys - -__all__ = [ - "ANSI_SEQUENCES", - "REVERSE_ANSI_SEQUENCES", -] - -# Mapping of vt100 escape codes to Keys. -ANSI_SEQUENCES: Dict[str, Union[Keys, Tuple[Keys, ...]]] = { - # Control keys. - "\x00": Keys.ControlAt, # Control-At (Also for Ctrl-Space) - "\x01": Keys.ControlA, # Control-A (home) - "\x02": Keys.ControlB, # Control-B (emacs cursor left) - "\x03": Keys.ControlC, # Control-C (interrupt) - "\x04": Keys.ControlD, # Control-D (exit) - "\x05": Keys.ControlE, # Control-E (end) - "\x06": Keys.ControlF, # Control-F (cursor forward) - "\x07": Keys.ControlG, # Control-G - "\x08": Keys.ControlH, # Control-H (8) (Identical to '\b') - "\x09": Keys.ControlI, # Control-I (9) (Identical to '\t') - "\x0a": Keys.ControlJ, # Control-J (10) (Identical to '\n') - "\x0b": Keys.ControlK, # Control-K (delete until end of line; vertical tab) - "\x0c": Keys.ControlL, # Control-L (clear; form feed) - "\x0d": Keys.ControlM, # Control-M (13) (Identical to '\r') - "\x0e": Keys.ControlN, # Control-N (14) (history forward) - "\x0f": Keys.ControlO, # Control-O (15) - "\x10": Keys.ControlP, # Control-P (16) (history back) - "\x11": Keys.ControlQ, # Control-Q - "\x12": Keys.ControlR, # Control-R (18) (reverse search) - "\x13": Keys.ControlS, # Control-S (19) (forward search) - "\x14": Keys.ControlT, # Control-T - "\x15": Keys.ControlU, # Control-U - "\x16": Keys.ControlV, # Control-V - "\x17": Keys.ControlW, # Control-W - "\x18": Keys.ControlX, # Control-X - "\x19": Keys.ControlY, # Control-Y (25) - "\x1a": Keys.ControlZ, # Control-Z - "\x1b": Keys.Escape, # Also Control-[ - "\x9b": Keys.ShiftEscape, - "\x1c": Keys.ControlBackslash, # Both Control-\ (also Ctrl-| ) - "\x1d": Keys.ControlSquareClose, # Control-] - "\x1e": Keys.ControlCircumflex, # Control-^ - "\x1f": Keys.ControlUnderscore, # Control-underscore (Also for Ctrl-hyphen.) - # ASCII Delete (0x7f) - # Vt220 (and Linux terminal) send this when pressing backspace. We map this - # to ControlH, because that will make it easier to create key bindings that - # work everywhere, with the trade-off that it's no longer possible to - # handle backspace and control-h individually for the few terminals that - # support it. (Most terminals send ControlH when backspace is pressed.) - # See: http://www.ibb.net/~anne/keyboard.html - "\x7f": Keys.ControlH, - # -- - # Various - "\x1b[1~": Keys.Home, # tmux - "\x1b[2~": Keys.Insert, - "\x1b[3~": Keys.Delete, - "\x1b[4~": Keys.End, # tmux - "\x1b[5~": Keys.PageUp, - "\x1b[6~": Keys.PageDown, - "\x1b[7~": Keys.Home, # xrvt - "\x1b[8~": Keys.End, # xrvt - "\x1b[Z": Keys.BackTab, # shift + tab - "\x1b\x09": Keys.BackTab, # Linux console - "\x1b[~": Keys.BackTab, # Windows console - # -- - # Function keys. - "\x1bOP": Keys.F1, - "\x1bOQ": Keys.F2, - "\x1bOR": Keys.F3, - "\x1bOS": Keys.F4, - "\x1b[[A": Keys.F1, # Linux console. - "\x1b[[B": Keys.F2, # Linux console. - "\x1b[[C": Keys.F3, # Linux console. - "\x1b[[D": Keys.F4, # Linux console. - "\x1b[[E": Keys.F5, # Linux console. - "\x1b[11~": Keys.F1, # rxvt-unicode - "\x1b[12~": Keys.F2, # rxvt-unicode - "\x1b[13~": Keys.F3, # rxvt-unicode - "\x1b[14~": Keys.F4, # rxvt-unicode - "\x1b[15~": Keys.F5, - "\x1b[17~": Keys.F6, - "\x1b[18~": Keys.F7, - "\x1b[19~": Keys.F8, - "\x1b[20~": Keys.F9, - "\x1b[21~": Keys.F10, - "\x1b[23~": Keys.F11, - "\x1b[24~": Keys.F12, - "\x1b[25~": Keys.F13, - "\x1b[26~": Keys.F14, - "\x1b[28~": Keys.F15, - "\x1b[29~": Keys.F16, - "\x1b[31~": Keys.F17, - "\x1b[32~": Keys.F18, - "\x1b[33~": Keys.F19, - "\x1b[34~": Keys.F20, - # Xterm - "\x1b[1;2P": Keys.F13, - "\x1b[1;2Q": Keys.F14, - # '\x1b[1;2R': Keys.F15, # Conflicts with CPR response. - "\x1b[1;2S": Keys.F16, - "\x1b[15;2~": Keys.F17, - "\x1b[17;2~": Keys.F18, - "\x1b[18;2~": Keys.F19, - "\x1b[19;2~": Keys.F20, - "\x1b[20;2~": Keys.F21, - "\x1b[21;2~": Keys.F22, - "\x1b[23;2~": Keys.F23, - "\x1b[24;2~": Keys.F24, - # -- - # CSI 27 disambiguated modified "other" keys (xterm) - # Ref: https://invisible-island.net/xterm/modified-keys.html - # These are currently unsupported, so just re-map some common ones to the - # unmodified versions - "\x1b[27;2;13~": Keys.ControlM, # Shift + Enter - "\x1b[27;5;13~": Keys.ControlM, # Ctrl + Enter - "\x1b[27;6;13~": Keys.ControlM, # Ctrl + Shift + Enter - # -- - # Control + function keys. - "\x1b[1;5P": Keys.ControlF1, - "\x1b[1;5Q": Keys.ControlF2, - # "\x1b[1;5R": Keys.ControlF3, # Conflicts with CPR response. - "\x1b[1;5S": Keys.ControlF4, - "\x1b[15;5~": Keys.ControlF5, - "\x1b[17;5~": Keys.ControlF6, - "\x1b[18;5~": Keys.ControlF7, - "\x1b[19;5~": Keys.ControlF8, - "\x1b[20;5~": Keys.ControlF9, - "\x1b[21;5~": Keys.ControlF10, - "\x1b[23;5~": Keys.ControlF11, - "\x1b[24;5~": Keys.ControlF12, - "\x1b[1;6P": Keys.ControlF13, - "\x1b[1;6Q": Keys.ControlF14, - # "\x1b[1;6R": Keys.ControlF15, # Conflicts with CPR response. - "\x1b[1;6S": Keys.ControlF16, - "\x1b[15;6~": Keys.ControlF17, - "\x1b[17;6~": Keys.ControlF18, - "\x1b[18;6~": Keys.ControlF19, - "\x1b[19;6~": Keys.ControlF20, - "\x1b[20;6~": Keys.ControlF21, - "\x1b[21;6~": Keys.ControlF22, - "\x1b[23;6~": Keys.ControlF23, - "\x1b[24;6~": Keys.ControlF24, - # -- - # Tmux (Win32 subsystem) sends the following scroll events. - "\x1b[62~": Keys.ScrollUp, - "\x1b[63~": Keys.ScrollDown, - "\x1b[200~": Keys.BracketedPaste, # Start of bracketed paste. - # -- - # Sequences generated by numpad 5. Not sure what it means. (It doesn't - # appear in 'infocmp'. Just ignore. - "\x1b[E": Keys.Ignore, # Xterm. - "\x1b[G": Keys.Ignore, # Linux console. - # -- - # Meta/control/escape + pageup/pagedown/insert/delete. - "\x1b[3;2~": Keys.ShiftDelete, # xterm, gnome-terminal. - "\x1b[5;2~": Keys.ShiftPageUp, - "\x1b[6;2~": Keys.ShiftPageDown, - "\x1b[2;3~": (Keys.Escape, Keys.Insert), - "\x1b[3;3~": (Keys.Escape, Keys.Delete), - "\x1b[5;3~": (Keys.Escape, Keys.PageUp), - "\x1b[6;3~": (Keys.Escape, Keys.PageDown), - "\x1b[2;4~": (Keys.Escape, Keys.ShiftInsert), - "\x1b[3;4~": (Keys.Escape, Keys.ShiftDelete), - "\x1b[5;4~": (Keys.Escape, Keys.ShiftPageUp), - "\x1b[6;4~": (Keys.Escape, Keys.ShiftPageDown), - "\x1b[3;5~": Keys.ControlDelete, # xterm, gnome-terminal. - "\x1b[5;5~": Keys.ControlPageUp, - "\x1b[6;5~": Keys.ControlPageDown, - "\x1b[3;6~": Keys.ControlShiftDelete, - "\x1b[5;6~": Keys.ControlShiftPageUp, - "\x1b[6;6~": Keys.ControlShiftPageDown, - "\x1b[2;7~": (Keys.Escape, Keys.ControlInsert), - "\x1b[5;7~": (Keys.Escape, Keys.ControlPageDown), - "\x1b[6;7~": (Keys.Escape, Keys.ControlPageDown), - "\x1b[2;8~": (Keys.Escape, Keys.ControlShiftInsert), - "\x1b[5;8~": (Keys.Escape, Keys.ControlShiftPageDown), - "\x1b[6;8~": (Keys.Escape, Keys.ControlShiftPageDown), - # -- - # Arrows. - # (Normal cursor mode). - "\x1b[A": Keys.Up, - "\x1b[B": Keys.Down, - "\x1b[C": Keys.Right, - "\x1b[D": Keys.Left, - "\x1b[H": Keys.Home, - "\x1b[F": Keys.End, - # Tmux sends following keystrokes when control+arrow is pressed, but for - # Emacs ansi-term sends the same sequences for normal arrow keys. Consider - # it a normal arrow press, because that's more important. - # (Application cursor mode). - "\x1bOA": Keys.Up, - "\x1bOB": Keys.Down, - "\x1bOC": Keys.Right, - "\x1bOD": Keys.Left, - "\x1bOF": Keys.End, - "\x1bOH": Keys.Home, - # Shift + arrows. - "\x1b[1;2A": Keys.ShiftUp, - "\x1b[1;2B": Keys.ShiftDown, - "\x1b[1;2C": Keys.ShiftRight, - "\x1b[1;2D": Keys.ShiftLeft, - "\x1b[1;2F": Keys.ShiftEnd, - "\x1b[1;2H": Keys.ShiftHome, - # Meta + arrow keys. Several terminals handle this differently. - # The following sequences are for xterm and gnome-terminal. - # (Iterm sends ESC followed by the normal arrow_up/down/left/right - # sequences, and the OSX Terminal sends ESCb and ESCf for "alt - # arrow_left" and "alt arrow_right." We don't handle these - # explicitly, in here, because would could not distinguish between - # pressing ESC (to go to Vi navigation mode), followed by just the - # 'b' or 'f' key. These combinations are handled in - # the input processor.) - "\x1b[1;3A": (Keys.Escape, Keys.Up), - "\x1b[1;3B": (Keys.Escape, Keys.Down), - "\x1b[1;3C": (Keys.Escape, Keys.Right), - "\x1b[1;3D": (Keys.Escape, Keys.Left), - "\x1b[1;3F": (Keys.Escape, Keys.End), - "\x1b[1;3H": (Keys.Escape, Keys.Home), - # Alt+shift+number. - "\x1b[1;4A": (Keys.Escape, Keys.ShiftDown), - "\x1b[1;4B": (Keys.Escape, Keys.ShiftUp), - "\x1b[1;4C": (Keys.Escape, Keys.ShiftRight), - "\x1b[1;4D": (Keys.Escape, Keys.ShiftLeft), - "\x1b[1;4F": (Keys.Escape, Keys.ShiftEnd), - "\x1b[1;4H": (Keys.Escape, Keys.ShiftHome), - # Control + arrows. - "\x1b[1;5A": Keys.ControlUp, # Cursor Mode - "\x1b[1;5B": Keys.ControlDown, # Cursor Mode - "\x1b[1;5C": Keys.ControlRight, # Cursor Mode - "\x1b[1;5D": Keys.ControlLeft, # Cursor Mode - "\x1b[1;5F": Keys.ControlEnd, - "\x1b[1;5H": Keys.ControlHome, - # Tmux sends following keystrokes when control+arrow is pressed, but for - # Emacs ansi-term sends the same sequences for normal arrow keys. Consider - # it a normal arrow press, because that's more important. - "\x1b[5A": Keys.ControlUp, - "\x1b[5B": Keys.ControlDown, - "\x1b[5C": Keys.ControlRight, - "\x1b[5D": Keys.ControlLeft, - "\x1bOc": Keys.ControlRight, # rxvt - "\x1bOd": Keys.ControlLeft, # rxvt - # Control + shift + arrows. - "\x1b[1;6A": Keys.ControlShiftDown, - "\x1b[1;6B": Keys.ControlShiftUp, - "\x1b[1;6C": Keys.ControlShiftRight, - "\x1b[1;6D": Keys.ControlShiftLeft, - "\x1b[1;6F": Keys.ControlShiftEnd, - "\x1b[1;6H": Keys.ControlShiftHome, - # Control + Meta + arrows. - "\x1b[1;7A": (Keys.Escape, Keys.ControlDown), - "\x1b[1;7B": (Keys.Escape, Keys.ControlUp), - "\x1b[1;7C": (Keys.Escape, Keys.ControlRight), - "\x1b[1;7D": (Keys.Escape, Keys.ControlLeft), - "\x1b[1;7F": (Keys.Escape, Keys.ControlEnd), - "\x1b[1;7H": (Keys.Escape, Keys.ControlHome), - # Meta + Shift + arrows. - "\x1b[1;8A": (Keys.Escape, Keys.ControlShiftDown), - "\x1b[1;8B": (Keys.Escape, Keys.ControlShiftUp), - "\x1b[1;8C": (Keys.Escape, Keys.ControlShiftRight), - "\x1b[1;8D": (Keys.Escape, Keys.ControlShiftLeft), - "\x1b[1;8F": (Keys.Escape, Keys.ControlShiftEnd), - "\x1b[1;8H": (Keys.Escape, Keys.ControlShiftHome), - # Meta + arrow on (some?) Macs when using iTerm defaults (see issue #483). - "\x1b[1;9A": (Keys.Escape, Keys.Up), - "\x1b[1;9B": (Keys.Escape, Keys.Down), - "\x1b[1;9C": (Keys.Escape, Keys.Right), - "\x1b[1;9D": (Keys.Escape, Keys.Left), - # -- - # Control/shift/meta + number in mintty. - # (c-2 will actually send c-@ and c-6 will send c-^.) - "\x1b[1;5p": Keys.Control0, - "\x1b[1;5q": Keys.Control1, - "\x1b[1;5r": Keys.Control2, - "\x1b[1;5s": Keys.Control3, - "\x1b[1;5t": Keys.Control4, - "\x1b[1;5u": Keys.Control5, - "\x1b[1;5v": Keys.Control6, - "\x1b[1;5w": Keys.Control7, - "\x1b[1;5x": Keys.Control8, - "\x1b[1;5y": Keys.Control9, - "\x1b[1;6p": Keys.ControlShift0, - "\x1b[1;6q": Keys.ControlShift1, - "\x1b[1;6r": Keys.ControlShift2, - "\x1b[1;6s": Keys.ControlShift3, - "\x1b[1;6t": Keys.ControlShift4, - "\x1b[1;6u": Keys.ControlShift5, - "\x1b[1;6v": Keys.ControlShift6, - "\x1b[1;6w": Keys.ControlShift7, - "\x1b[1;6x": Keys.ControlShift8, - "\x1b[1;6y": Keys.ControlShift9, - "\x1b[1;7p": (Keys.Escape, Keys.Control0), - "\x1b[1;7q": (Keys.Escape, Keys.Control1), - "\x1b[1;7r": (Keys.Escape, Keys.Control2), - "\x1b[1;7s": (Keys.Escape, Keys.Control3), - "\x1b[1;7t": (Keys.Escape, Keys.Control4), - "\x1b[1;7u": (Keys.Escape, Keys.Control5), - "\x1b[1;7v": (Keys.Escape, Keys.Control6), - "\x1b[1;7w": (Keys.Escape, Keys.Control7), - "\x1b[1;7x": (Keys.Escape, Keys.Control8), - "\x1b[1;7y": (Keys.Escape, Keys.Control9), - "\x1b[1;8p": (Keys.Escape, Keys.ControlShift0), - "\x1b[1;8q": (Keys.Escape, Keys.ControlShift1), - "\x1b[1;8r": (Keys.Escape, Keys.ControlShift2), - "\x1b[1;8s": (Keys.Escape, Keys.ControlShift3), - "\x1b[1;8t": (Keys.Escape, Keys.ControlShift4), - "\x1b[1;8u": (Keys.Escape, Keys.ControlShift5), - "\x1b[1;8v": (Keys.Escape, Keys.ControlShift6), - "\x1b[1;8w": (Keys.Escape, Keys.ControlShift7), - "\x1b[1;8x": (Keys.Escape, Keys.ControlShift8), - "\x1b[1;8y": (Keys.Escape, Keys.ControlShift9), -} - - -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] = {} - - for sequence, key in ANSI_SEQUENCES.items(): - if not isinstance(key, tuple): - if key not in result: - result[key] = sequence - - return result - - -REVERSE_ANSI_SEQUENCES = _get_reverse_ansi_sequences() diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/base.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/base.py deleted file mode 100644 index 313622de5a0..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/base.py +++ /dev/null @@ -1,150 +0,0 @@ -""" -Abstraction of CLI Input. -""" -from abc import ABCMeta, abstractmethod, abstractproperty -from contextlib import contextmanager -from typing import Callable, ContextManager, Generator, List - -from prompt_toolkit.key_binding import KeyPress - -__all__ = [ - "Input", - "PipeInput", - "DummyInput", -] - - -class Input(metaclass=ABCMeta): - """ - Abstraction for any input. - - An instance of this class can be given to the constructor of a - :class:`~prompt_toolkit.application.Application` and will also be - passed to the :class:`~prompt_toolkit.eventloop.base.EventLoop`. - """ - - @abstractmethod - def fileno(self) -> int: - """ - Fileno for putting this in an event loop. - """ - - @abstractmethod - def typeahead_hash(self) -> str: - """ - Identifier for storing type ahead key presses. - """ - - @abstractmethod - 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]: - """ - Flush the underlying parser. and return the pending keys. - (Used for vt100 input.) - """ - return [] - - def flush(self) -> None: - "The event loop can call this when the input has to be flushed." - pass - - @abstractproperty - def closed(self) -> bool: - "Should be true when the input stream is closed." - return False - - @abstractmethod - def raw_mode(self) -> ContextManager[None]: - """ - Context manager that turns the input into raw mode. - """ - - @abstractmethod - def cooked_mode(self) -> ContextManager[None]: - """ - Context manager that turns the input into cooked mode. - """ - - @abstractmethod - def attach(self, input_ready_callback: Callable[[], None]) -> ContextManager[None]: - """ - Return a context manager that makes this input active in the current - event loop. - """ - - @abstractmethod - def detach(self) -> ContextManager[None]: - """ - Return a context manager that makes sure that this input is not active - in the current event loop. - """ - - def close(self) -> None: - "Close input." - pass - - -class PipeInput(Input): - """ - Abstraction for pipe input. - """ - - @abstractmethod - def send_bytes(self, data: bytes) -> None: - """Feed byte string into the pipe""" - - @abstractmethod - def send_text(self, data: str) -> None: - """Feed a text string into the pipe""" - - -class DummyInput(Input): - """ - Input for use in a `DummyApplication` - - If used in an actual application, it will make the application render - itself once and exit immediately, due to an `EOFError`. - """ - - def fileno(self) -> int: - raise NotImplementedError - - def typeahead_hash(self) -> str: - return "dummy-%s" % id(self) - - def read_keys(self) -> List[KeyPress]: - return [] - - @property - def closed(self) -> bool: - # This needs to be true, so that the dummy input will trigger an - # `EOFError` immediately in the application. - return True - - def raw_mode(self) -> ContextManager[None]: - return _dummy_context_manager() - - def cooked_mode(self) -> ContextManager[None]: - return _dummy_context_manager() - - def attach(self, input_ready_callback: Callable[[], None]) -> ContextManager[None]: - # Call the callback immediately once after attaching. - # This tells the callback to call `read_keys` and check the - # `input.closed` flag, after which it won't receive any keys, but knows - # that `EOFError` should be raised. This unblocks `read_from_input` in - # `application.py`. - input_ready_callback() - - return _dummy_context_manager() - - def detach(self) -> ContextManager[None]: - return _dummy_context_manager() - - -@contextmanager -def _dummy_context_manager() -> Generator[None, None, None]: - yield diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/defaults.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/defaults.py deleted file mode 100644 index 197dcb9a605..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/defaults.py +++ /dev/null @@ -1,69 +0,0 @@ -import sys -from typing import ContextManager, Optional, TextIO - -from .base import DummyInput, Input, PipeInput - -__all__ = [ - "create_input", - "create_pipe_input", -] - - -def create_input( - stdin: Optional[TextIO] = None, always_prefer_tty: bool = False -) -> Input: - """ - Create the appropriate `Input` object for the current os/environment. - - :param always_prefer_tty: When set, if `sys.stdin` is connected to a Unix - `pipe`, check whether `sys.stdout` or `sys.stderr` are connected to a - pseudo terminal. If so, open the tty for reading instead of reading for - `sys.stdin`. (We can open `stdout` or `stderr` for reading, this is how - a `$PAGER` works.) - """ - if sys.platform == "win32": - from .win32 import Win32Input - - # If `stdin` was assigned `None` (which happens with pythonw.exe), use - # a `DummyInput`. This triggers `EOFError` in the application code. - if stdin is None and sys.stdin is None: - return DummyInput() - - return Win32Input(stdin or sys.stdin) - else: - from .vt100 import Vt100Input - - # If no input TextIO is given, use stdin/stdout. - if stdin is None: - stdin = sys.stdin - - if always_prefer_tty: - for io in [sys.stdin, sys.stdout, sys.stderr]: - if io.isatty(): - stdin = io - break - - return Vt100Input(stdin) - - -def create_pipe_input() -> ContextManager[PipeInput]: - """ - Create an input pipe. - This is mostly useful for unit testing. - - Usage:: - - with create_pipe_input() as input: - input.send_text('inputdata') - - Breaking change: In prompt_toolkit 3.0.28 and earlier, this was returning - the `PipeInput` directly, rather than through a context manager. - """ - if sys.platform == "win32": - from .win32_pipe import Win32PipeInput - - return Win32PipeInput.create() - else: - from .posix_pipe import PosixPipeInput - - return PosixPipeInput.create() diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/posix_pipe.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/posix_pipe.py deleted file mode 100644 index 1e7dec77fd6..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/posix_pipe.py +++ /dev/null @@ -1,116 +0,0 @@ -import sys - -assert sys.platform != "win32" - -import os -from contextlib import contextmanager -from typing import ContextManager, Iterator, TextIO, cast - -from ..utils import DummyContext -from .base import PipeInput -from .vt100 import Vt100Input - -__all__ = [ - "PosixPipeInput", -] - - -class _Pipe: - "Wrapper around os.pipe, that ensures we don't double close any end." - - def __init__(self) -> None: - self.read_fd, self.write_fd = os.pipe() - self._read_closed = False - self._write_closed = False - - def close_read(self) -> None: - "Close read-end if not yet closed." - if self._read_closed: - return - - os.close(self.read_fd) - self._read_closed = True - - def close_write(self) -> None: - "Close write-end if not yet closed." - if self._write_closed: - return - - os.close(self.write_fd) - self._write_closed = True - - def close(self) -> None: - "Close both read and write ends." - self.close_read() - self.close_write() - - -class PosixPipeInput(Vt100Input, PipeInput): - """ - Input that is send through a pipe. - This is useful if we want to send the input programmatically into the - application. Mostly useful for unit testing. - - Usage:: - - with PosixPipeInput.create() as input: - input.send_text('inputdata') - """ - - _id = 0 - - def __init__(self, _pipe: _Pipe, _text: str = "") -> None: - # Private constructor. Users should use the public `.create()` method. - self.pipe = _pipe - - class Stdin: - encoding = "utf-8" - - def isatty(stdin) -> bool: - return True - - def fileno(stdin) -> int: - return self.pipe.read_fd - - super().__init__(cast(TextIO, Stdin())) - self.send_text(_text) - - # Identifier for every PipeInput for the hash. - self.__class__._id += 1 - self._id = self.__class__._id - - @classmethod - @contextmanager - def create(cls, text: str = "") -> Iterator["PosixPipeInput"]: - pipe = _Pipe() - try: - yield PosixPipeInput(_pipe=pipe, _text=text) - finally: - pipe.close() - - def send_bytes(self, data: bytes) -> None: - os.write(self.pipe.write_fd, data) - - def send_text(self, data: str) -> None: - "Send text to the input." - os.write(self.pipe.write_fd, data.encode("utf-8")) - - def raw_mode(self) -> ContextManager[None]: - return DummyContext() - - def cooked_mode(self) -> ContextManager[None]: - return DummyContext() - - def close(self) -> None: - "Close pipe fds." - # Only close the write-end of the pipe. This will unblock the reader - # callback (in vt100.py > _attached_input), which eventually will raise - # `EOFError`. If we'd also close the read-end, then the event loop - # won't wake up the corresponding callback because of this. - self.pipe.close_write() - - def typeahead_hash(self) -> str: - """ - This needs to be unique for every `PipeInput`. - """ - return f"pipe-input-{self._id}" diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/posix_utils.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/posix_utils.py deleted file mode 100644 index 7cf31eebe6d..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/posix_utils.py +++ /dev/null @@ -1,95 +0,0 @@ -import os -import select -from codecs import getincrementaldecoder - -__all__ = [ - "PosixStdinReader", -] - - -class PosixStdinReader: - """ - Wrapper around stdin which reads (nonblocking) the next available 1024 - bytes and decodes it. - - Note that you can't be sure that the input file is closed if the ``read`` - function returns an empty string. When ``errors=ignore`` is passed, - ``read`` can return an empty string if all malformed input was replaced by - an empty string. (We can't block here and wait for more input.) So, because - of that, check the ``closed`` attribute, to be sure that the file has been - closed. - - :param stdin_fd: File descriptor from which we read. - :param errors: Can be 'ignore', 'strict' or 'replace'. - On Python3, this can be 'surrogateescape', which is the default. - - 'surrogateescape' is preferred, because this allows us to transfer - unrecognised bytes to the key bindings. Some terminals, like lxterminal - and Guake, use the 'Mxx' notation to send mouse events, where each 'x' - can be any possible byte. - """ - - # By default, we want to 'ignore' errors here. The input stream can be full - # of junk. One occurrence of this that I had was when using iTerm2 on OS X, - # with "Option as Meta" checked (You should choose "Option as +Esc".) - - def __init__( - self, stdin_fd: int, errors: str = "surrogateescape", encoding: str = "utf-8" - ) -> None: - self.stdin_fd = stdin_fd - self.errors = errors - - # Create incremental decoder for decoding stdin. - # We can not just do `os.read(stdin.fileno(), 1024).decode('utf-8')`, because - # it could be that we are in the middle of a utf-8 byte sequence. - self._stdin_decoder_cls = getincrementaldecoder(encoding) - self._stdin_decoder = self._stdin_decoder_cls(errors=errors) - - #: True when there is nothing anymore to read. - self.closed = False - - def read(self, count: int = 1024) -> str: - # By default we choose a rather small chunk size, because reading - # big amounts of input at once, causes the event loop to process - # all these key bindings also at once without going back to the - # loop. This will make the application feel unresponsive. - """ - Read the input and return it as a string. - - Return the text. Note that this can return an empty string, even when - the input stream was not yet closed. This means that something went - wrong during the decoding. - """ - if self.closed: - return "" - - # Check whether there is some input to read. `os.read` would block - # otherwise. - # (Actually, the event loop is responsible to make sure that this - # function is only called when there is something to read, but for some - # reason this happens in certain situations.) - try: - if not select.select([self.stdin_fd], [], [], 0)[0]: - return "" - except OSError: - # Happens for instance when the file descriptor was closed. - # (We had this in ptterm, where the FD became ready, a callback was - # scheduled, but in the meantime another callback closed it already.) - self.closed = True - - # Note: the following works better than wrapping `self.stdin` like - # `codecs.getreader('utf-8')(stdin)` and doing `read(1)`. - # Somehow that causes some latency when the escape - # character is pressed. (Especially on combination with the `select`.) - try: - data = os.read(self.stdin_fd, count) - - # Nothing more to read, stream is closed. - if data == b"": - self.closed = True - return "" - except OSError: - # In case of SIGWINCH - data = b"" - - return self._stdin_decoder.decode(data) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/typeahead.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/typeahead.py deleted file mode 100644 index a3d78664493..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/typeahead.py +++ /dev/null @@ -1,76 +0,0 @@ -r""" -Store input key strokes if we did read more than was required. - -The input classes `Vt100Input` and `Win32Input` read the input text in chunks -of a few kilobytes. This means that if we read input from stdin, it could be -that we read a couple of lines (with newlines in between) at once. - -This creates a problem: potentially, we read too much from stdin. Sometimes -people paste several lines at once because they paste input in a REPL and -expect each input() call to process one line. Or they rely on type ahead -because the application can't keep up with the processing. - -However, we need to read input in bigger chunks. We need this mostly to support -pasting of larger chunks of text. We don't want everything to become -unresponsive because we: - - read one character; - - parse one character; - - call the key binding, which does a string operation with one character; - - and render the user interface. -Doing text operations on single characters is very inefficient in Python, so we -prefer to work on bigger chunks of text. This is why we have to read the input -in bigger chunks. - -Further, line buffering is also not an option, because it doesn't work well in -the architecture. We use lower level Posix APIs, that work better with the -event loop and so on. In fact, there is also nothing that defines that only \n -can accept the input, you could create a key binding for any key to accept the -input. - -To support type ahead, this module will store all the key strokes that were -read too early, so that they can be feed into to the next `prompt()` call or to -the next prompt_toolkit `Application`. -""" -from collections import defaultdict -from typing import Dict, List - -from ..key_binding import KeyPress -from .base import Input - -__all__ = [ - "store_typeahead", - "get_typeahead", - "clear_typeahead", -] - -_buffer: Dict[str, List[KeyPress]] = defaultdict(list) - - -def store_typeahead(input_obj: Input, key_presses: List[KeyPress]) -> None: - """ - Insert typeahead key presses for the given input. - """ - global _buffer - key = input_obj.typeahead_hash() - _buffer[key].extend(key_presses) - - -def get_typeahead(input_obj: Input) -> List[KeyPress]: - """ - Retrieve typeahead and reset the buffer for this input. - """ - global _buffer - - key = input_obj.typeahead_hash() - result = _buffer[key] - _buffer[key] = [] - return result - - -def clear_typeahead(input_obj: Input) -> None: - """ - Clear typeahead buffer. - """ - global _buffer - key = input_obj.typeahead_hash() - _buffer[key] = [] diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/vt100.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/vt100.py deleted file mode 100644 index 45ce37208a7..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/vt100.py +++ /dev/null @@ -1,320 +0,0 @@ -import sys - -assert sys.platform != "win32" - -import contextlib -import io -import termios -import tty -from asyncio import AbstractEventLoop -from typing import ( - Callable, - ContextManager, - Dict, - Generator, - List, - Optional, - Set, - TextIO, - Tuple, - Union, -) - -from prompt_toolkit.eventloop import get_event_loop - -from ..key_binding import KeyPress -from .base import Input -from .posix_utils import PosixStdinReader -from .vt100_parser import Vt100Parser - -__all__ = [ - "Vt100Input", - "raw_mode", - "cooked_mode", -] - - -class Vt100Input(Input): - """ - Vt100 input for Posix systems. - (This uses a posix file descriptor that can be registered in the event loop.) - """ - - # For the error messages. Only display "Input is not a terminal" once per - # file descriptor. - _fds_not_a_terminal: Set[int] = set() - - def __init__(self, stdin: TextIO) -> None: - # Test whether the given input object has a file descriptor. - # (Idle reports stdin to be a TTY, but fileno() is not implemented.) - try: - # This should not raise, but can return 0. - stdin.fileno() - except io.UnsupportedOperation as e: - if "idlelib.run" in sys.modules: - raise io.UnsupportedOperation( - "Stdin is not a terminal. Running from Idle is not supported." - ) from e - else: - raise io.UnsupportedOperation("Stdin is not a terminal.") from e - - # Even when we have a file descriptor, it doesn't mean it's a TTY. - # Normally, this requires a real TTY device, but people instantiate - # this class often during unit tests as well. They use for instance - # pexpect to pipe data into an application. For convenience, we print - # an error message and go on. - isatty = stdin.isatty() - fd = stdin.fileno() - - if not isatty and fd not in Vt100Input._fds_not_a_terminal: - msg = "Warning: Input is not a terminal (fd=%r).\n" - sys.stderr.write(msg % fd) - sys.stderr.flush() - Vt100Input._fds_not_a_terminal.add(fd) - - # - self.stdin = stdin - - # Create a backup of the fileno(). We want this to work even if the - # 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.stdin_reader = PosixStdinReader(self._fileno, encoding=stdin.encoding) - self.vt100_parser = Vt100Parser( - lambda key_press: self._buffer.append(key_press) - ) - - def attach(self, input_ready_callback: Callable[[], None]) -> ContextManager[None]: - """ - Return a context manager that makes this input active in the current - event loop. - """ - return _attached_input(self, input_ready_callback) - - def detach(self) -> ContextManager[None]: - """ - Return a context manager that makes sure that this input is not active - in the current event loop. - """ - return _detached_input(self) - - def read_keys(self) -> List[KeyPress]: - "Read list of KeyPress." - # Read text from stdin. - data = self.stdin_reader.read() - - # Pass it through our vt100 parser. - self.vt100_parser.feed(data) - - # Return result. - result = self._buffer - self._buffer = [] - return result - - def flush_keys(self) -> List[KeyPress]: - """ - Flush pending keys and return them. - (Used for flushing the 'escape' key.) - """ - # Flush all pending keys. (This is most important to flush the vt100 - # 'Escape' key early when nothing else follows.) - self.vt100_parser.flush() - - # Return result. - result = self._buffer - self._buffer = [] - return result - - @property - def closed(self) -> bool: - return self.stdin_reader.closed - - def raw_mode(self) -> ContextManager[None]: - return raw_mode(self.stdin.fileno()) - - def cooked_mode(self) -> ContextManager[None]: - return cooked_mode(self.stdin.fileno()) - - def fileno(self) -> int: - return self.stdin.fileno() - - def typeahead_hash(self) -> str: - return f"fd-{self._fileno}" - - -_current_callbacks: Dict[ - Tuple[AbstractEventLoop, int], Optional[Callable[[], None]] -] = {} # (loop, fd) -> current callback - - -@contextlib.contextmanager -def _attached_input( - input: Vt100Input, callback: Callable[[], None] -) -> Generator[None, None, None]: - """ - Context manager that makes this input active in the current event loop. - - :param input: :class:`~prompt_toolkit.input.Input` object. - :param callback: Called when the input is ready to read. - """ - loop = get_event_loop() - fd = input.fileno() - previous = _current_callbacks.get((loop, fd)) - - def callback_wrapper() -> None: - """Wrapper around the callback that already removes the reader when - the input is closed. Otherwise, we keep continuously calling this - callback, until we leave the context manager (which can happen a bit - later). This fixes issues when piping /dev/null into a prompt_toolkit - application.""" - if input.closed: - loop.remove_reader(fd) - callback() - - try: - loop.add_reader(fd, callback_wrapper) - except PermissionError: - # For `EPollSelector`, adding /dev/null to the event loop will raise - # `PermisisonError` (that doesn't happen for `SelectSelector` - # apparently). Whenever we get a `PermissionError`, we can raise - # `EOFError`, because there's not more to be read anyway. `EOFError` is - # an exception that people expect in - # `prompt_toolkit.application.Application.run()`. - # To reproduce, do: `ptpython 0< /dev/null 1< /dev/null` - raise EOFError - - _current_callbacks[loop, fd] = callback - - try: - yield - finally: - loop.remove_reader(fd) - - if previous: - loop.add_reader(fd, previous) - _current_callbacks[loop, fd] = previous - else: - del _current_callbacks[loop, fd] - - -@contextlib.contextmanager -def _detached_input(input: Vt100Input) -> Generator[None, None, None]: - loop = get_event_loop() - fd = input.fileno() - previous = _current_callbacks.get((loop, fd)) - - if previous: - loop.remove_reader(fd) - _current_callbacks[loop, fd] = None - - try: - yield - finally: - if previous: - loop.add_reader(fd, previous) - _current_callbacks[loop, fd] = previous - - -class raw_mode: - """ - :: - - with raw_mode(stdin): - ''' the pseudo-terminal stdin is now used in raw mode ''' - - We ignore errors when executing `tcgetattr` fails. - """ - - # There are several reasons for ignoring errors: - # 1. To avoid the "Inappropriate ioctl for device" crash if somebody would - # execute this code (In a Python REPL, for instance): - # - # import os; f = open(os.devnull); os.dup2(f.fileno(), 0) - # - # The result is that the eventloop will stop correctly, because it has - # to logic to quit when stdin is closed. However, we should not fail at - # this point. See: - # https://github.com/jonathanslenders/python-prompt-toolkit/pull/393 - # https://github.com/jonathanslenders/python-prompt-toolkit/issues/392 - - # 2. Related, when stdin is an SSH pipe, and no full terminal was allocated. - # 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]]]]] - try: - self.attrs_before = termios.tcgetattr(fileno) - except termios.error: - # Ignore attribute errors. - self.attrs_before = None - - def __enter__(self) -> None: - # NOTE: On os X systems, using pty.setraw() fails. Therefor we are using this: - try: - newattr = termios.tcgetattr(self.fileno) - except termios.error: - pass - else: - newattr[tty.LFLAG] = self._patch_lflag(newattr[tty.LFLAG]) - newattr[tty.IFLAG] = self._patch_iflag(newattr[tty.IFLAG]) - - # VMIN defines the number of characters read at a time in - # non-canonical mode. It seems to default to 1 on Linux, but on - # Solaris and derived operating systems it defaults to 4. (This is - # because the VMIN slot is the same as the VEOF slot, which - # defaults to ASCII EOT = Ctrl-D = 4.) - newattr[tty.CC][termios.VMIN] = 1 - - termios.tcsetattr(self.fileno, termios.TCSANOW, newattr) - - @classmethod - def _patch_lflag(cls, attrs: int) -> int: - return attrs & ~(termios.ECHO | termios.ICANON | termios.IEXTEN | termios.ISIG) - - @classmethod - def _patch_iflag(cls, attrs: int) -> int: - return attrs & ~( - # Disable XON/XOFF flow control on output and input. - # (Don't capture Ctrl-S and Ctrl-Q.) - # Like executing: "stty -ixon." - termios.IXON - | termios.IXOFF - | - # Don't translate carriage return into newline on input. - termios.ICRNL - | termios.INLCR - | termios.IGNCR - ) - - def __exit__(self, *a: object) -> None: - if self.attrs_before is not None: - try: - termios.tcsetattr(self.fileno, termios.TCSANOW, self.attrs_before) - except termios.error: - pass - - # # Put the terminal in application mode. - # self._stdout.write('\x1b[?1h') - - -class cooked_mode(raw_mode): - """ - The opposite of ``raw_mode``, used when we need cooked mode inside a - `raw_mode` block. Used in `Application.run_in_terminal`.:: - - with cooked_mode(stdin): - ''' the pseudo-terminal stdin is now used in cooked mode. ''' - """ - - @classmethod - def _patch_lflag(cls, attrs: int) -> int: - return attrs | (termios.ECHO | termios.ICANON | termios.IEXTEN | termios.ISIG) - - @classmethod - def _patch_iflag(cls, attrs: int) -> int: - # Turn the ICRNL flag back on. (Without this, calling `input()` in - # run_in_terminal doesn't work and displays ^M instead. Ptpython - # evaluates commands using `run_in_terminal`, so it's important that - # they translate ^M back into ^J.) - return attrs | termios.ICRNL diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/vt100_parser.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/vt100_parser.py deleted file mode 100644 index 3ee1e14fdda..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/vt100_parser.py +++ /dev/null @@ -1,247 +0,0 @@ -""" -Parser for VT100 input stream. -""" -import re -from typing import Callable, Dict, Generator, Tuple, Union - -from ..key_binding.key_processor import KeyPress -from ..keys import Keys -from .ansi_escape_sequences import ANSI_SEQUENCES - -__all__ = [ - "Vt100Parser", -] - - -# Regex matching any CPR response -# (Note that we use '\Z' instead of '$', because '$' could include a trailing -# newline.) -_cpr_response_re = re.compile("^" + re.escape("\x1b[") + r"\d+;\d+R\Z") - -# Mouse events: -# Typical: "Esc[MaB*" Urxvt: "Esc[96;14;13M" and for Xterm SGR: "Esc[<64;85;12M" -_mouse_event_re = re.compile("^" + re.escape("\x1b[") + r"(<?[\d;]+[mM]|M...)\Z") - -# Regex matching any valid prefix of a CPR response. -# (Note that it doesn't contain the last character, the 'R'. The prefix has to -# be shorter.) -_cpr_response_prefix_re = re.compile("^" + re.escape("\x1b[") + r"[\d;]*\Z") - -_mouse_event_prefix_re = re.compile("^" + re.escape("\x1b[") + r"(<?[\d;]*|M.{0,2})\Z") - - -class _Flush: - """Helper object to indicate flush operation to the parser.""" - - pass - - -class _IsPrefixOfLongerMatchCache(Dict[str, bool]): - """ - Dictionary that maps input sequences to a boolean indicating whether there is - any key that start with this characters. - """ - - def __missing__(self, prefix: str) -> bool: - # (hard coded) If this could be a prefix of a CPR response, return - # True. - if _cpr_response_prefix_re.match(prefix) or _mouse_event_prefix_re.match( - prefix - ): - result = True - else: - # If this could be a prefix of anything else, also return True. - result = any( - v - for k, v in ANSI_SEQUENCES.items() - if k.startswith(prefix) and k != prefix - ) - - self[prefix] = result - return result - - -_IS_PREFIX_OF_LONGER_MATCH_CACHE = _IsPrefixOfLongerMatchCache() - - -class Vt100Parser: - """ - Parser for VT100 input stream. - Data can be fed through the `feed` method and the given callback will be - called with KeyPress objects. - - :: - - def callback(key): - pass - i = Vt100Parser(callback) - i.feed('data\x01...') - - :attr feed_key_callback: Function that will be called when a key is parsed. - """ - - # Lookup table of ANSI escape sequences for a VT100 terminal - # Hint: in order to know what sequences your terminal writes to stdin, run - # "od -c" and start typing. - def __init__(self, feed_key_callback: Callable[[KeyPress], None]) -> None: - self.feed_key_callback = feed_key_callback - self.reset() - - def reset(self, request: bool = False) -> None: - self._in_bracketed_paste = False - self._start_parser() - - def _start_parser(self) -> None: - """ - Start the parser coroutine. - """ - 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, ...]]: - """ - Return the key (or keys) that maps to this prefix. - """ - # (hard coded) If we match a CPR response, return Keys.CPRResponse. - # (This one doesn't fit in the ANSI_SEQUENCES, because it contains - # integer variables.) - if _cpr_response_re.match(prefix): - return Keys.CPRResponse - - elif _mouse_event_re.match(prefix): - return Keys.Vt100MouseEvent - - # Otherwise, use the mappings. - try: - return ANSI_SEQUENCES[prefix] - except KeyError: - return None - - def _input_parser_generator(self) -> Generator[None, Union[str, _Flush], None]: - """ - Coroutine (state machine) for the input parser. - """ - prefix = "" - retry = False - flush = False - - while True: - flush = False - - if retry: - retry = False - else: - # Get next character. - c = yield - - if isinstance(c, _Flush): - flush = True - else: - prefix += c - - # If we have some data, check for matches. - if prefix: - is_prefix_of_longer_match = _IS_PREFIX_OF_LONGER_MATCH_CACHE[prefix] - match = self._get_match(prefix) - - # Exact matches found, call handlers.. - if (flush or not is_prefix_of_longer_match) and match: - self._call_handler(match, prefix) - prefix = "" - - # No exact match found. - elif (flush or not is_prefix_of_longer_match) and not match: - found = False - retry = True - - # Loop over the input, try the longest match first and - # shift. - for i in range(len(prefix), 0, -1): - match = self._get_match(prefix[:i]) - if match: - self._call_handler(match, prefix[:i]) - prefix = prefix[i:] - found = True - - if not found: - self._call_handler(prefix[0], prefix[0]) - prefix = prefix[1:] - - def _call_handler( - self, key: Union[str, Keys, Tuple[Keys, ...]], insert_text: str - ) -> None: - """ - Callback to handler. - """ - if isinstance(key, tuple): - # Received ANSI sequence that corresponds with multiple keys - # (probably alt+something). Handle keys individually, but only pass - # data payload to first KeyPress (so that we won't insert it - # multiple times). - for i, k in enumerate(key): - self._call_handler(k, insert_text if i == 0 else "") - else: - if key == Keys.BracketedPaste: - self._in_bracketed_paste = True - self._paste_buffer = "" - else: - self.feed_key_callback(KeyPress(key, insert_text)) - - def feed(self, data: str) -> None: - """ - Feed the input stream. - - :param data: Input string (unicode). - """ - # Handle bracketed paste. (We bypass the parser that matches all other - # key presses and keep reading input until we see the end mark.) - # This is much faster then parsing character by character. - if self._in_bracketed_paste: - self._paste_buffer += data - end_mark = "\x1b[201~" - - if end_mark in self._paste_buffer: - end_index = self._paste_buffer.index(end_mark) - - # Feed content to key bindings. - paste_content = self._paste_buffer[:end_index] - self.feed_key_callback(KeyPress(Keys.BracketedPaste, paste_content)) - - # Quit bracketed paste mode and handle remaining input. - self._in_bracketed_paste = False - remaining = self._paste_buffer[end_index + len(end_mark) :] - self._paste_buffer = "" - - self.feed(remaining) - - # Handle normal input character by character. - else: - for i, c in enumerate(data): - if self._in_bracketed_paste: - # Quit loop and process from this position when the parser - # entered bracketed paste. - self.feed(data[i:]) - break - else: - self._input_parser.send(c) - - def flush(self) -> None: - """ - Flush the buffer of the input stream. - - This will allow us to handle the escape key (or maybe meta) sooner. - The input received by the escape key is actually the same as the first - characters of e.g. Arrow-Up, so without knowing what follows the escape - sequence, we don't know whether escape has been pressed, or whether - it's something else. This flush function should be called after a - timeout, and processes everything that's still in the buffer as-is, so - without assuming any characters will follow. - """ - self._input_parser.send(_Flush()) - - def feed_and_flush(self, data: str) -> None: - """ - Wrapper around ``feed`` and ``flush``. - """ - self.feed(data) - self.flush() diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/win32.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/win32.py deleted file mode 100644 index db3fa2baddc..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/win32.py +++ /dev/null @@ -1,757 +0,0 @@ -import os -import sys -from abc import abstractmethod -from contextlib import contextmanager - -from prompt_toolkit.eventloop import get_event_loop - -from ..utils import SPHINX_AUTODOC_RUNNING - -assert sys.platform == "win32" - -# Do not import win32-specific stuff when generating documentation. -# Otherwise RTD would be unable to generate docs for this module. -if not SPHINX_AUTODOC_RUNNING: - import msvcrt - from ctypes import windll - -from ctypes import Array, pointer -from ctypes.wintypes import DWORD, HANDLE -from typing import ( - Callable, - ContextManager, - Dict, - Iterable, - Iterator, - List, - Optional, - TextIO, -) - -from prompt_toolkit.eventloop import run_in_executor_with_context -from prompt_toolkit.eventloop.win32 import create_win32_event, wait_for_handles -from prompt_toolkit.key_binding.key_processor import KeyPress -from prompt_toolkit.keys import Keys -from prompt_toolkit.mouse_events import MouseButton, MouseEventType -from prompt_toolkit.win32_types import ( - INPUT_RECORD, - KEY_EVENT_RECORD, - MOUSE_EVENT_RECORD, - STD_INPUT_HANDLE, - EventTypes, -) - -from .ansi_escape_sequences import REVERSE_ANSI_SEQUENCES -from .base import Input - -__all__ = [ - "Win32Input", - "ConsoleInputReader", - "raw_mode", - "cooked_mode", - "attach_win32_input", - "detach_win32_input", -] - -# Win32 Constants for MOUSE_EVENT_RECORD. -# See: https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str -FROM_LEFT_1ST_BUTTON_PRESSED = 0x1 -RIGHTMOST_BUTTON_PRESSED = 0x2 -MOUSE_MOVED = 0x0001 -MOUSE_WHEELED = 0x0004 - - -class _Win32InputBase(Input): - """ - Base class for `Win32Input` and `Win32PipeInput`. - """ - - def __init__(self) -> None: - self.win32_handles = _Win32Handles() - - @property - @abstractmethod - def handle(self) -> HANDLE: - pass - - -class Win32Input(_Win32InputBase): - """ - `Input` class that reads from the Windows console. - """ - - def __init__(self, stdin: Optional[TextIO] = None) -> None: - super().__init__() - self.console_input_reader = ConsoleInputReader() - - def attach(self, input_ready_callback: Callable[[], None]) -> ContextManager[None]: - """ - Return a context manager that makes this input active in the current - event loop. - """ - return attach_win32_input(self, input_ready_callback) - - def detach(self) -> ContextManager[None]: - """ - Return a context manager that makes sure that this input is not active - in the current event loop. - """ - return detach_win32_input(self) - - def read_keys(self) -> List[KeyPress]: - return list(self.console_input_reader.read()) - - def flush(self) -> None: - pass - - @property - def closed(self) -> bool: - return False - - def raw_mode(self) -> ContextManager[None]: - return raw_mode() - - def cooked_mode(self) -> ContextManager[None]: - return cooked_mode() - - def fileno(self) -> int: - # The windows console doesn't depend on the file handle, so - # this is not used for the event loop (which uses the - # handle instead). But it's used in `Application.run_system_command` - # which opens a subprocess with a given stdin/stdout. - return sys.stdin.fileno() - - def typeahead_hash(self) -> str: - return "win32-input" - - def close(self) -> None: - self.console_input_reader.close() - - @property - def handle(self) -> HANDLE: - return self.console_input_reader.handle - - -class ConsoleInputReader: - """ - :param recognize_paste: When True, try to discover paste actions and turn - the event into a BracketedPaste. - """ - - # Keys with character data. - mappings = { - b"\x1b": Keys.Escape, - b"\x00": Keys.ControlSpace, # Control-Space (Also for Ctrl-@) - b"\x01": Keys.ControlA, # Control-A (home) - b"\x02": Keys.ControlB, # Control-B (emacs cursor left) - b"\x03": Keys.ControlC, # Control-C (interrupt) - b"\x04": Keys.ControlD, # Control-D (exit) - b"\x05": Keys.ControlE, # Control-E (end) - b"\x06": Keys.ControlF, # Control-F (cursor forward) - b"\x07": Keys.ControlG, # Control-G - b"\x08": Keys.ControlH, # Control-H (8) (Identical to '\b') - b"\x09": Keys.ControlI, # Control-I (9) (Identical to '\t') - b"\x0a": Keys.ControlJ, # Control-J (10) (Identical to '\n') - b"\x0b": Keys.ControlK, # Control-K (delete until end of line; vertical tab) - b"\x0c": Keys.ControlL, # Control-L (clear; form feed) - b"\x0d": Keys.ControlM, # Control-M (enter) - b"\x0e": Keys.ControlN, # Control-N (14) (history forward) - b"\x0f": Keys.ControlO, # Control-O (15) - b"\x10": Keys.ControlP, # Control-P (16) (history back) - b"\x11": Keys.ControlQ, # Control-Q - b"\x12": Keys.ControlR, # Control-R (18) (reverse search) - b"\x13": Keys.ControlS, # Control-S (19) (forward search) - b"\x14": Keys.ControlT, # Control-T - b"\x15": Keys.ControlU, # Control-U - b"\x16": Keys.ControlV, # Control-V - b"\x17": Keys.ControlW, # Control-W - b"\x18": Keys.ControlX, # Control-X - b"\x19": Keys.ControlY, # Control-Y (25) - b"\x1a": Keys.ControlZ, # Control-Z - b"\x1c": Keys.ControlBackslash, # Both Control-\ and Ctrl-| - b"\x1d": Keys.ControlSquareClose, # Control-] - b"\x1e": Keys.ControlCircumflex, # Control-^ - b"\x1f": Keys.ControlUnderscore, # Control-underscore (Also for Ctrl-hyphen.) - b"\x7f": Keys.Backspace, # (127) Backspace (ASCII Delete.) - } - - # Keys that don't carry character data. - keycodes = { - # Home/End - 33: Keys.PageUp, - 34: Keys.PageDown, - 35: Keys.End, - 36: Keys.Home, - # Arrows - 37: Keys.Left, - 38: Keys.Up, - 39: Keys.Right, - 40: Keys.Down, - 45: Keys.Insert, - 46: Keys.Delete, - # F-keys. - 112: Keys.F1, - 113: Keys.F2, - 114: Keys.F3, - 115: Keys.F4, - 116: Keys.F5, - 117: Keys.F6, - 118: Keys.F7, - 119: Keys.F8, - 120: Keys.F9, - 121: Keys.F10, - 122: Keys.F11, - 123: Keys.F12, - } - - LEFT_ALT_PRESSED = 0x0002 - RIGHT_ALT_PRESSED = 0x0001 - SHIFT_PRESSED = 0x0010 - LEFT_CTRL_PRESSED = 0x0008 - RIGHT_CTRL_PRESSED = 0x0004 - - def __init__(self, recognize_paste: bool = True) -> None: - self._fdcon = None - self.recognize_paste = recognize_paste - - # When stdin is a tty, use that handle, otherwise, create a handle from - # CONIN$. - self.handle: HANDLE - if sys.stdin.isatty(): - self.handle = HANDLE(windll.kernel32.GetStdHandle(STD_INPUT_HANDLE)) - else: - self._fdcon = os.open("CONIN$", os.O_RDWR | os.O_BINARY) - self.handle = HANDLE(msvcrt.get_osfhandle(self._fdcon)) - - def close(self) -> None: - "Close fdcon." - if self._fdcon is not None: - os.close(self._fdcon) - - def read(self) -> Iterable[KeyPress]: - """ - Return a list of `KeyPress` instances. It won't return anything when - there was nothing to read. (This function doesn't block.) - - http://msdn.microsoft.com/en-us/library/windows/desktop/ms684961(v=vs.85).aspx - """ - max_count = 2048 # Max events to read at the same time. - - read = DWORD(0) - arrtype = INPUT_RECORD * max_count - input_records = arrtype() - - # Check whether there is some input to read. `ReadConsoleInputW` would - # block otherwise. - # (Actually, the event loop is responsible to make sure that this - # function is only called when there is something to read, but for some - # reason this happened in the asyncio_win32 loop, and it's better to be - # safe anyway.) - if not wait_for_handles([self.handle], timeout=0): - return - - # Get next batch of input event. - windll.kernel32.ReadConsoleInputW( - self.handle, pointer(input_records), max_count, pointer(read) - ) - - # First, get all the keys from the input buffer, in order to determine - # whether we should consider this a paste event or not. - all_keys = list(self._get_keys(read, input_records)) - - # Fill in 'data' for key presses. - all_keys = [self._insert_key_data(key) for key in all_keys] - - # Correct non-bmp characters that are passed as separate surrogate codes - all_keys = list(self._merge_paired_surrogates(all_keys)) - - if self.recognize_paste and self._is_paste(all_keys): - gen = iter(all_keys) - k: Optional[KeyPress] - - for k in gen: - # Pasting: if the current key consists of text or \n, turn it - # into a BracketedPaste. - data = [] - while k and ( - not isinstance(k.key, Keys) - or k.key in {Keys.ControlJ, Keys.ControlM} - ): - data.append(k.data) - try: - k = next(gen) - except StopIteration: - k = None - - if data: - yield KeyPress(Keys.BracketedPaste, "".join(data)) - if k is not None: - yield k - else: - yield from all_keys - - def _insert_key_data(self, key_press: KeyPress) -> KeyPress: - """ - Insert KeyPress data, for vt100 compatibility. - """ - if key_press.data: - return key_press - - if isinstance(key_press.key, Keys): - data = REVERSE_ANSI_SEQUENCES.get(key_press.key, "") - else: - data = "" - - return KeyPress(key_press.key, data) - - def _get_keys( - self, read: DWORD, input_records: "Array[INPUT_RECORD]" - ) -> Iterator[KeyPress]: - """ - Generator that yields `KeyPress` objects from the input records. - """ - for i in range(read.value): - ir = input_records[i] - - # Get the right EventType from the EVENT_RECORD. - # (For some reason the Windows console application 'cmder' - # [http://gooseberrycreative.com/cmder/] can return '0' for - # ir.EventType. -- Just ignore that.) - if ir.EventType in EventTypes: - ev = getattr(ir.Event, EventTypes[ir.EventType]) - - # Process if this is a key event. (We also have mouse, menu and - # focus events.) - if type(ev) == KEY_EVENT_RECORD and ev.KeyDown: - yield from self._event_to_key_presses(ev) - - elif type(ev) == MOUSE_EVENT_RECORD: - yield from self._handle_mouse(ev) - - @staticmethod - def _merge_paired_surrogates(key_presses: List[KeyPress]) -> Iterator[KeyPress]: - """ - Combines consecutive KeyPresses with high and low surrogates into - single characters - """ - buffered_high_surrogate = None - for key in key_presses: - is_text = not isinstance(key.key, Keys) - is_high_surrogate = is_text and "\uD800" <= key.key <= "\uDBFF" - is_low_surrogate = is_text and "\uDC00" <= key.key <= "\uDFFF" - - if buffered_high_surrogate: - if is_low_surrogate: - # convert high surrogate + low surrogate to single character - fullchar = ( - (buffered_high_surrogate.key + key.key) - .encode("utf-16-le", "surrogatepass") - .decode("utf-16-le") - ) - key = KeyPress(fullchar, fullchar) - else: - yield buffered_high_surrogate - buffered_high_surrogate = None - - if is_high_surrogate: - buffered_high_surrogate = key - else: - yield key - - if buffered_high_surrogate: - yield buffered_high_surrogate - - @staticmethod - 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 - `Keys.BracketedPaste` event. (It's not 100% correct, but it is probably - the best possible way to detect pasting of text and handle that - correctly.) - """ - # Consider paste when it contains at least one newline and at least one - # other character. - text_count = 0 - newline_count = 0 - - for k in keys: - if not isinstance(k.key, Keys): - text_count += 1 - if k.key == Keys.ControlM: - newline_count += 1 - - return newline_count >= 1 and text_count >= 1 - - 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 - - control_key_state = ev.ControlKeyState - u_char = ev.uChar.UnicodeChar - # Use surrogatepass because u_char may be an unmatched surrogate - ascii_char = u_char.encode("utf-8", "surrogatepass") - - # NOTE: We don't use `ev.uChar.AsciiChar`. That appears to be the - # unicode code point truncated to 1 byte. See also: - # https://github.com/ipython/ipython/issues/10004 - # https://github.com/jonathanslenders/python-prompt-toolkit/issues/389 - - if u_char == "\x00": - if ev.VirtualKeyCode in self.keycodes: - result = KeyPress(self.keycodes[ev.VirtualKeyCode], "") - else: - if ascii_char in self.mappings: - if self.mappings[ascii_char] == Keys.ControlJ: - u_char = ( - "\n" # Windows sends \n, turn into \r for unix compatibility. - ) - result = KeyPress(self.mappings[ascii_char], u_char) - else: - result = KeyPress(u_char, u_char) - - # First we handle Shift-Control-Arrow/Home/End (need to do this first) - if ( - ( - control_key_state & self.LEFT_CTRL_PRESSED - or control_key_state & self.RIGHT_CTRL_PRESSED - ) - and control_key_state & self.SHIFT_PRESSED - and result - ): - mapping: Dict[str, str] = { - Keys.Left: Keys.ControlShiftLeft, - Keys.Right: Keys.ControlShiftRight, - Keys.Up: Keys.ControlShiftUp, - Keys.Down: Keys.ControlShiftDown, - Keys.Home: Keys.ControlShiftHome, - Keys.End: Keys.ControlShiftEnd, - Keys.Insert: Keys.ControlShiftInsert, - Keys.PageUp: Keys.ControlShiftPageUp, - Keys.PageDown: Keys.ControlShiftPageDown, - } - result.key = mapping.get(result.key, result.key) - - # Correctly handle Control-Arrow/Home/End and Control-Insert/Delete keys. - if ( - control_key_state & self.LEFT_CTRL_PRESSED - or control_key_state & self.RIGHT_CTRL_PRESSED - ) and result: - mapping = { - Keys.Left: Keys.ControlLeft, - Keys.Right: Keys.ControlRight, - Keys.Up: Keys.ControlUp, - Keys.Down: Keys.ControlDown, - Keys.Home: Keys.ControlHome, - Keys.End: Keys.ControlEnd, - Keys.Insert: Keys.ControlInsert, - Keys.Delete: Keys.ControlDelete, - Keys.PageUp: Keys.ControlPageUp, - Keys.PageDown: Keys.ControlPageDown, - } - result.key = mapping.get(result.key, result.key) - - # Turn 'Tab' into 'BackTab' when shift was pressed. - # Also handle other shift-key combination - if control_key_state & self.SHIFT_PRESSED and result: - mapping = { - Keys.Tab: Keys.BackTab, - Keys.Left: Keys.ShiftLeft, - Keys.Right: Keys.ShiftRight, - Keys.Up: Keys.ShiftUp, - Keys.Down: Keys.ShiftDown, - Keys.Home: Keys.ShiftHome, - Keys.End: Keys.ShiftEnd, - Keys.Insert: Keys.ShiftInsert, - Keys.Delete: Keys.ShiftDelete, - Keys.PageUp: Keys.ShiftPageUp, - Keys.PageDown: Keys.ShiftPageDown, - } - result.key = mapping.get(result.key, result.key) - - # Turn 'Space' into 'ControlSpace' when control was pressed. - if ( - ( - control_key_state & self.LEFT_CTRL_PRESSED - or control_key_state & self.RIGHT_CTRL_PRESSED - ) - and result - and result.data == " " - ): - result = KeyPress(Keys.ControlSpace, " ") - - # Turn Control-Enter into META-Enter. (On a vt100 terminal, we cannot - # detect this combination. But it's really practical on Windows.) - if ( - ( - control_key_state & self.LEFT_CTRL_PRESSED - or control_key_state & self.RIGHT_CTRL_PRESSED - ) - and result - and result.key == Keys.ControlJ - ): - return [KeyPress(Keys.Escape, ""), result] - - # Return result. If alt was pressed, prefix the result with an - # 'Escape' key, just like unix VT100 terminals do. - - # NOTE: Only replace the left alt with escape. The right alt key often - # acts as altgr and is used in many non US keyboard layouts for - # typing some special characters, like a backslash. We don't want - # all backslashes to be prefixed with escape. (Esc-\ has a - # meaning in E-macs, for instance.) - if result: - meta_pressed = control_key_state & self.LEFT_ALT_PRESSED - - if meta_pressed: - return [KeyPress(Keys.Escape, ""), result] - else: - return [result] - - else: - return [] - - 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 - button: MouseButton = MouseButton.NONE - - # Scroll events. - if event_flags & MOUSE_WHEELED: - if button_state > 0: - event_type = MouseEventType.SCROLL_UP - else: - event_type = MouseEventType.SCROLL_DOWN - else: - # Handle button state for non-scroll events. - if button_state == FROM_LEFT_1ST_BUTTON_PRESSED: - button = MouseButton.LEFT - - elif button_state == RIGHTMOST_BUTTON_PRESSED: - button = MouseButton.RIGHT - - # Move events. - if event_flags & MOUSE_MOVED: - event_type = MouseEventType.MOUSE_MOVE - - # No key pressed anymore: mouse up. - if event_type is None: - if button_state > 0: - # Some button pressed. - event_type = MouseEventType.MOUSE_DOWN - else: - # No button pressed. - event_type = MouseEventType.MOUSE_UP - - data = ";".join( - [ - button.value, - event_type.value, - str(ev.MousePosition.X), - str(ev.MousePosition.Y), - ] - ) - return [KeyPress(Keys.WindowsMouseEvent, data)] - - -class _Win32Handles: - """ - Utility to keep track of which handles are connectod to which callbacks. - - `add_win32_handle` starts a tiny event loop in another thread which waits - for the Win32 handle to become ready. When this happens, the callback will - be called in the current asyncio event loop using `call_soon_threadsafe`. - - `remove_win32_handle` will stop this tiny event loop. - - NOTE: We use this technique, so that we don't have to use the - `ProactorEventLoop` on Windows and we can wait for things like stdin - in a `SelectorEventLoop`. This is important, because our inputhook - mechanism (used by IPython), only works with the `SelectorEventLoop`. - """ - - def __init__(self) -> 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] = {} - - def add_win32_handle(self, handle: HANDLE, callback: Callable[[], None]) -> None: - """ - Add a Win32 handle to the event loop. - """ - handle_value = handle.value - - if handle_value is None: - raise ValueError("Invalid handle.") - - # Make sure to remove a previous registered handler first. - self.remove_win32_handle(handle) - - loop = get_event_loop() - self._handle_callbacks[handle_value] = callback - - # Create remove event. - remove_event = create_win32_event() - self._remove_events[handle_value] = remove_event - - # Add reader. - def ready() -> None: - # Tell the callback that input's ready. - try: - callback() - finally: - run_in_executor_with_context(wait, loop=loop) - - # Wait for the input to become ready. - # (Use an executor for this, the Windows asyncio event loop doesn't - # allow us to wait for handles like stdin.) - def wait() -> None: - # Wait until either the handle becomes ready, or the remove event - # has been set. - result = wait_for_handles([remove_event, handle]) - - if result is remove_event: - windll.kernel32.CloseHandle(remove_event) - return - else: - loop.call_soon_threadsafe(ready) - - run_in_executor_with_context(wait, loop=loop) - - def remove_win32_handle(self, handle: HANDLE) -> Optional[Callable[[], None]]: - """ - Remove a Win32 handle from the event loop. - Return either the registered handler or `None`. - """ - if handle.value is None: - return None # Ignore. - - # Trigger remove events, so that the reader knows to stop. - try: - event = self._remove_events.pop(handle.value) - except KeyError: - pass - else: - windll.kernel32.SetEvent(event) - - try: - return self._handle_callbacks.pop(handle.value) - except KeyError: - return None - - -@contextmanager -def attach_win32_input( - input: _Win32InputBase, callback: Callable[[], None] -) -> Iterator[None]: - """ - Context manager that makes this input active in the current event loop. - - :param input: :class:`~prompt_toolkit.input.Input` object. - :param input_ready_callback: Called when the input is ready to read. - """ - win32_handles = input.win32_handles - handle = input.handle - - if handle.value is None: - raise ValueError("Invalid handle.") - - # Add reader. - previous_callback = win32_handles.remove_win32_handle(handle) - win32_handles.add_win32_handle(handle, callback) - - try: - yield - finally: - win32_handles.remove_win32_handle(handle) - - if previous_callback: - win32_handles.add_win32_handle(handle, previous_callback) - - -@contextmanager -def detach_win32_input(input: _Win32InputBase) -> Iterator[None]: - win32_handles = input.win32_handles - handle = input.handle - - if handle.value is None: - raise ValueError("Invalid handle.") - - previous_callback = win32_handles.remove_win32_handle(handle) - - try: - yield - finally: - if previous_callback: - win32_handles.add_win32_handle(handle, previous_callback) - - -class raw_mode: - """ - :: - - with raw_mode(stdin): - ''' the windows terminal is now in 'raw' mode. ''' - - The ``fileno`` attribute is ignored. This is to be compatible with the - `raw_input` method of `.vt100_input`. - """ - - def __init__(self, fileno: Optional[int] = None) -> None: - self.handle = HANDLE(windll.kernel32.GetStdHandle(STD_INPUT_HANDLE)) - - def __enter__(self) -> None: - # Remember original mode. - original_mode = DWORD() - windll.kernel32.GetConsoleMode(self.handle, pointer(original_mode)) - self.original_mode = original_mode - - self._patch() - - def _patch(self) -> None: - # Set raw - ENABLE_ECHO_INPUT = 0x0004 - ENABLE_LINE_INPUT = 0x0002 - ENABLE_PROCESSED_INPUT = 0x0001 - - windll.kernel32.SetConsoleMode( - self.handle, - self.original_mode.value - & ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT), - ) - - def __exit__(self, *a: object) -> None: - # Restore original mode - windll.kernel32.SetConsoleMode(self.handle, self.original_mode) - - -class cooked_mode(raw_mode): - """ - :: - - with cooked_mode(stdin): - ''' The pseudo-terminal stdin is now used in cooked mode. ''' - """ - - def _patch(self) -> None: - # Set cooked. - ENABLE_ECHO_INPUT = 0x0004 - ENABLE_LINE_INPUT = 0x0002 - ENABLE_PROCESSED_INPUT = 0x0001 - - windll.kernel32.SetConsoleMode( - self.handle, - self.original_mode.value - | (ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT), - ) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/win32_pipe.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/win32_pipe.py deleted file mode 100644 index ebee2075edf..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/win32_pipe.py +++ /dev/null @@ -1,154 +0,0 @@ -import sys - -assert sys.platform == "win32" - -from contextlib import contextmanager -from ctypes import windll -from ctypes.wintypes import HANDLE -from typing import Callable, ContextManager, Iterator, List - -from prompt_toolkit.eventloop.win32 import create_win32_event - -from ..key_binding import KeyPress -from ..utils import DummyContext -from .base import PipeInput -from .vt100_parser import Vt100Parser -from .win32 import _Win32InputBase, attach_win32_input, detach_win32_input - -__all__ = ["Win32PipeInput"] - - -class Win32PipeInput(_Win32InputBase, PipeInput): - """ - This is an input pipe that works on Windows. - Text or bytes can be feed into the pipe, and key strokes can be read from - the pipe. This is useful if we want to send the input programmatically into - the application. Mostly useful for unit testing. - - Notice that even though it's Windows, we use vt100 escape sequences over - the pipe. - - Usage:: - - input = Win32PipeInput() - input.send_text('inputdata') - """ - - _id = 0 - - def __init__(self, _event: HANDLE) -> None: - super().__init__() - # Event (handle) for registering this input in the event loop. - # This event is set when there is data available to read from the pipe. - # Note: We use this approach instead of using a regular pipe, like - # returned from `os.pipe()`, because making such a regular pipe - # non-blocking is tricky and this works really well. - self._event = create_win32_event() - - self._closed = False - - # Parser for incoming keys. - 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. - self.__class__._id += 1 - self._id = self.__class__._id - - @classmethod - @contextmanager - def create(cls) -> Iterator["Win32PipeInput"]: - event = create_win32_event() - try: - yield Win32PipeInput(_event=event) - finally: - windll.kernel32.CloseHandle(event) - - @property - def closed(self) -> bool: - return self._closed - - def fileno(self) -> int: - """ - The windows pipe doesn't depend on the file handle. - """ - raise NotImplementedError - - @property - def handle(self) -> HANDLE: - "The handle used for registering this pipe in the event loop." - return self._event - - def attach(self, input_ready_callback: Callable[[], None]) -> ContextManager[None]: - """ - Return a context manager that makes this input active in the current - event loop. - """ - return attach_win32_input(self, input_ready_callback) - - def detach(self) -> ContextManager[None]: - """ - Return a context manager that makes sure that this input is not active - in the current event loop. - """ - return detach_win32_input(self) - - def read_keys(self) -> List[KeyPress]: - "Read list of KeyPress." - - # Return result. - result = self._buffer - self._buffer = [] - - # Reset event. - if not self._closed: - # (If closed, the event should not reset.) - windll.kernel32.ResetEvent(self._event) - - return result - - def flush_keys(self) -> List[KeyPress]: - """ - Flush pending keys and return them. - (Used for flushing the 'escape' key.) - """ - # Flush all pending keys. (This is most important to flush the vt100 - # 'Escape' key early when nothing else follows.) - self.vt100_parser.flush() - - # Return result. - result = self._buffer - self._buffer = [] - return result - - def send_bytes(self, data: bytes) -> None: - "Send bytes to the input." - self.send_text(data.decode("utf-8", "ignore")) - - def send_text(self, text: str) -> None: - "Send text to the input." - if self._closed: - raise ValueError("Attempt to write into a closed pipe.") - - # Pass it through our vt100 parser. - self.vt100_parser.feed(text) - - # Set event. - windll.kernel32.SetEvent(self._event) - - def raw_mode(self) -> ContextManager[None]: - return DummyContext() - - def cooked_mode(self) -> ContextManager[None]: - return DummyContext() - - def close(self) -> None: - "Close write-end of the pipe." - self._closed = True - windll.kernel32.SetEvent(self._event) - - def typeahead_hash(self) -> str: - """ - This needs to be unique for every `PipeInput`. - """ - return f"pipe-input-{self._id}" diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/__init__.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/__init__.py deleted file mode 100644 index be105369152..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -from .key_bindings import ( - ConditionalKeyBindings, - DynamicKeyBindings, - KeyBindings, - KeyBindingsBase, - merge_key_bindings, -) -from .key_processor import KeyPress, KeyPressEvent - -__all__ = [ - # key_bindings. - "ConditionalKeyBindings", - "DynamicKeyBindings", - "KeyBindings", - "KeyBindingsBase", - "merge_key_bindings", - # key_processor - "KeyPress", - "KeyPressEvent", -] diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/__init__.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/__init__.py +++ /dev/null diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/auto_suggest.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/auto_suggest.py deleted file mode 100644 index c016c0688fe..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/auto_suggest.py +++ /dev/null @@ -1,63 +0,0 @@ -""" -Key bindings for auto suggestion (for fish-style auto suggestion). -""" -import re - -from prompt_toolkit.application.current import get_app -from prompt_toolkit.filters import Condition, emacs_mode -from prompt_toolkit.key_binding.key_bindings import KeyBindings -from prompt_toolkit.key_binding.key_processor import KeyPressEvent - -__all__ = [ - "load_auto_suggest_bindings", -] - -E = KeyPressEvent - - -def load_auto_suggest_bindings() -> KeyBindings: - """ - Key bindings for accepting auto suggestion text. - - (This has to come after the Vi bindings, because they also have an - implementation for the "right arrow", but we really want the suggestion - binding when a suggestion is available.) - """ - key_bindings = KeyBindings() - handle = key_bindings.add - - @Condition - def suggestion_available() -> bool: - app = get_app() - return ( - app.current_buffer.suggestion is not None - and len(app.current_buffer.suggestion.text) > 0 - and app.current_buffer.document.is_cursor_at_the_end - ) - - @handle("c-f", filter=suggestion_available) - @handle("c-e", filter=suggestion_available) - @handle("right", filter=suggestion_available) - def _accept(event: E) -> None: - """ - Accept suggestion. - """ - b = event.current_buffer - suggestion = b.suggestion - - if suggestion: - b.insert_text(suggestion.text) - - @handle("escape", "f", filter=suggestion_available & emacs_mode) - def _fill(event: E) -> None: - """ - Fill partial suggestion. - """ - b = event.current_buffer - suggestion = b.suggestion - - if suggestion: - t = re.split(r"(\S+\s+)", suggestion.text) - b.insert_text(next(x for x in t if x)) - - return key_bindings diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/basic.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/basic.py deleted file mode 100644 index fc8f9643596..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/basic.py +++ /dev/null @@ -1,253 +0,0 @@ -# pylint: disable=function-redefined -from prompt_toolkit.application.current import get_app -from prompt_toolkit.filters import ( - Condition, - emacs_insert_mode, - has_selection, - in_paste_mode, - is_multiline, - vi_insert_mode, -) -from prompt_toolkit.key_binding.key_processor import KeyPress, KeyPressEvent -from prompt_toolkit.keys import Keys - -from ..key_bindings import KeyBindings -from .named_commands import get_by_name - -__all__ = [ - "load_basic_bindings", -] - -E = KeyPressEvent - - -def if_no_repeat(event: E) -> bool: - """Callable that returns True when the previous event was delivered to - another handler.""" - return not event.is_repeat - - -def load_basic_bindings() -> KeyBindings: - key_bindings = KeyBindings() - insert_mode = vi_insert_mode | emacs_insert_mode - handle = key_bindings.add - - @handle("c-a") - @handle("c-b") - @handle("c-c") - @handle("c-d") - @handle("c-e") - @handle("c-f") - @handle("c-g") - @handle("c-h") - @handle("c-i") - @handle("c-j") - @handle("c-k") - @handle("c-l") - @handle("c-m") - @handle("c-n") - @handle("c-o") - @handle("c-p") - @handle("c-q") - @handle("c-r") - @handle("c-s") - @handle("c-t") - @handle("c-u") - @handle("c-v") - @handle("c-w") - @handle("c-x") - @handle("c-y") - @handle("c-z") - @handle("f1") - @handle("f2") - @handle("f3") - @handle("f4") - @handle("f5") - @handle("f6") - @handle("f7") - @handle("f8") - @handle("f9") - @handle("f10") - @handle("f11") - @handle("f12") - @handle("f13") - @handle("f14") - @handle("f15") - @handle("f16") - @handle("f17") - @handle("f18") - @handle("f19") - @handle("f20") - @handle("f21") - @handle("f22") - @handle("f23") - @handle("f24") - @handle("c-@") # Also c-space. - @handle("c-\\") - @handle("c-]") - @handle("c-^") - @handle("c-_") - @handle("backspace") - @handle("up") - @handle("down") - @handle("right") - @handle("left") - @handle("s-up") - @handle("s-down") - @handle("s-right") - @handle("s-left") - @handle("home") - @handle("end") - @handle("s-home") - @handle("s-end") - @handle("delete") - @handle("s-delete") - @handle("c-delete") - @handle("pageup") - @handle("pagedown") - @handle("s-tab") - @handle("tab") - @handle("c-s-left") - @handle("c-s-right") - @handle("c-s-home") - @handle("c-s-end") - @handle("c-left") - @handle("c-right") - @handle("c-up") - @handle("c-down") - @handle("c-home") - @handle("c-end") - @handle("insert") - @handle("s-insert") - @handle("c-insert") - @handle("<sigint>") - @handle(Keys.Ignore) - def _ignore(event: E) -> None: - """ - First, for any of these keys, Don't do anything by default. Also don't - catch them in the 'Any' handler which will insert them as data. - - If people want to insert these characters as a literal, they can always - do by doing a quoted insert. (ControlQ in emacs mode, ControlV in Vi - mode.) - """ - pass - - # Readline-style bindings. - handle("home")(get_by_name("beginning-of-line")) - handle("end")(get_by_name("end-of-line")) - handle("left")(get_by_name("backward-char")) - handle("right")(get_by_name("forward-char")) - handle("c-up")(get_by_name("previous-history")) - handle("c-down")(get_by_name("next-history")) - handle("c-l")(get_by_name("clear-screen")) - - handle("c-k", filter=insert_mode)(get_by_name("kill-line")) - handle("c-u", filter=insert_mode)(get_by_name("unix-line-discard")) - handle("backspace", filter=insert_mode, save_before=if_no_repeat)( - get_by_name("backward-delete-char") - ) - handle("delete", filter=insert_mode, save_before=if_no_repeat)( - get_by_name("delete-char") - ) - handle("c-delete", filter=insert_mode, save_before=if_no_repeat)( - get_by_name("delete-char") - ) - handle(Keys.Any, filter=insert_mode, save_before=if_no_repeat)( - get_by_name("self-insert") - ) - handle("c-t", filter=insert_mode)(get_by_name("transpose-chars")) - handle("c-i", filter=insert_mode)(get_by_name("menu-complete")) - handle("s-tab", filter=insert_mode)(get_by_name("menu-complete-backward")) - - # Control-W should delete, using whitespace as separator, while M-Del - # should delete using [^a-zA-Z0-9] as a boundary. - handle("c-w", filter=insert_mode)(get_by_name("unix-word-rubout")) - - handle("pageup", filter=~has_selection)(get_by_name("previous-history")) - handle("pagedown", filter=~has_selection)(get_by_name("next-history")) - - # CTRL keys. - - @Condition - def has_text_before_cursor() -> bool: - return bool(get_app().current_buffer.text) - - handle("c-d", filter=has_text_before_cursor & insert_mode)( - get_by_name("delete-char") - ) - - @handle("enter", filter=insert_mode & is_multiline) - def _newline(event: E) -> None: - """ - Newline (in case of multiline input. - """ - event.current_buffer.newline(copy_margin=not in_paste_mode()) - - @handle("c-j") - def _newline2(event: E) -> None: - r""" - By default, handle \n as if it were a \r (enter). - (It appears that some terminals send \n instead of \r when pressing - enter. - at least the Linux subsystem for Windows.) - """ - event.key_processor.feed(KeyPress(Keys.ControlM, "\r"), first=True) - - # Delete the word before the cursor. - - @handle("up") - def _go_up(event: E) -> None: - event.current_buffer.auto_up(count=event.arg) - - @handle("down") - def _go_down(event: E) -> None: - event.current_buffer.auto_down(count=event.arg) - - @handle("delete", filter=has_selection) - def _cut(event: E) -> None: - data = event.current_buffer.cut_selection() - event.app.clipboard.set_data(data) - - # Global bindings. - - @handle("c-z") - def _insert_ctrl_z(event: E) -> None: - """ - By default, control-Z should literally insert Ctrl-Z. - (Ansi Ctrl-Z, code 26 in MSDOS means End-Of-File. - In a Python REPL for instance, it's possible to type - Control-Z followed by enter to quit.) - - When the system bindings are loaded and suspend-to-background is - supported, that will override this binding. - """ - event.current_buffer.insert_text(event.data) - - @handle(Keys.BracketedPaste) - def _paste(event: E) -> None: - """ - Pasting from clipboard. - """ - data = event.data - - # Be sure to use \n as line ending. - # Some terminals (Like iTerm2) seem to paste \r\n line endings in a - # bracketed paste. See: https://github.com/ipython/ipython/issues/9737 - data = data.replace("\r\n", "\n") - data = data.replace("\r", "\n") - - event.current_buffer.insert_text(data) - - @Condition - def in_quoted_insert() -> bool: - return get_app().quoted_insert - - @handle(Keys.Any, filter=in_quoted_insert, eager=True) - def _insert_text(event: E) -> None: - """ - Handle quoted insert. - """ - event.current_buffer.insert_text(event.data, overwrite=False) - event.app.quoted_insert = False - - return key_bindings diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/completion.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/completion.py deleted file mode 100644 index a30b54e632d..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/completion.py +++ /dev/null @@ -1,203 +0,0 @@ -""" -Key binding handlers for displaying completions. -""" -import asyncio -import math -from typing import TYPE_CHECKING, List - -from prompt_toolkit.application.run_in_terminal import in_terminal -from prompt_toolkit.completion import ( - CompleteEvent, - Completion, - get_common_complete_suffix, -) -from prompt_toolkit.formatted_text import StyleAndTextTuples -from prompt_toolkit.key_binding.key_bindings import KeyBindings -from prompt_toolkit.key_binding.key_processor import KeyPressEvent -from prompt_toolkit.keys import Keys -from prompt_toolkit.utils import get_cwidth - -if TYPE_CHECKING: - from prompt_toolkit.application import Application - from prompt_toolkit.shortcuts import PromptSession - -__all__ = [ - "generate_completions", - "display_completions_like_readline", -] - -E = KeyPressEvent - - -def generate_completions(event: E) -> None: - r""" - Tab-completion: where the first tab completes the common suffix and the - second tab lists all the completions. - """ - b = event.current_buffer - - # When already navigating through completions, select the next one. - if b.complete_state: - b.complete_next() - else: - b.start_completion(insert_common_part=True) - - -def display_completions_like_readline(event: E) -> None: - """ - Key binding handler for readline-style tab completion. - This is meant to be as similar as possible to the way how readline displays - completions. - - Generate the completions immediately (blocking) and display them above the - prompt in columns. - - Usage:: - - # Call this handler when 'Tab' has been pressed. - key_bindings.add(Keys.ControlI)(display_completions_like_readline) - """ - # Request completions. - b = event.current_buffer - if b.completer is None: - return - complete_event = CompleteEvent(completion_requested=True) - completions = list(b.completer.get_completions(b.document, complete_event)) - - # Calculate the common suffix. - common_suffix = get_common_complete_suffix(b.document, completions) - - # One completion: insert it. - if len(completions) == 1: - b.delete_before_cursor(-completions[0].start_position) - b.insert_text(completions[0].text) - # Multiple completions with common part. - elif common_suffix: - b.insert_text(common_suffix) - # Otherwise: display all completions. - elif completions: - _display_completions_like_readline(event.app, completions) - - -def _display_completions_like_readline( - 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 - on a single page and provide a paginator to walk through them. - """ - from prompt_toolkit.formatted_text import to_formatted_text - from prompt_toolkit.shortcuts.prompt import create_confirm_session - - # Get terminal dimensions. - term_size = app.output.get_size() - term_width = term_size.columns - term_height = term_size.rows - - # Calculate amount of required columns/rows for displaying the - # completions. (Keep in mind that completions are displayed - # alphabetically column-wise.) - max_compl_width = min( - term_width, max(get_cwidth(c.display_text) for c in completions) + 1 - ) - column_count = max(1, term_width // max_compl_width) - completions_per_page = column_count * (term_height - 1) - page_count = int(math.ceil(len(completions) / float(completions_per_page))) - # Note: math.ceil can return float on Python2. - - def display(page: int) -> None: - # Display completions. - page_completions = completions[ - page * completions_per_page : (page + 1) * completions_per_page - ] - - page_row_count = int(math.ceil(len(page_completions) / float(column_count))) - page_columns = [ - page_completions[i * page_row_count : (i + 1) * page_row_count] - for i in range(column_count) - ] - - result: StyleAndTextTuples = [] - - for r in range(page_row_count): - for c in range(column_count): - try: - completion = page_columns[c][r] - style = "class:readline-like-completions.completion " + ( - completion.style or "" - ) - - result.extend(to_formatted_text(completion.display, style=style)) - - # Add padding. - padding = max_compl_width - get_cwidth(completion.display_text) - result.append((completion.style, " " * padding)) - except IndexError: - pass - result.append(("", "\n")) - - app.print_text(to_formatted_text(result, "class:readline-like-completions")) - - # User interaction through an application generator function. - async def run_compl() -> None: - "Coroutine." - async with in_terminal(render_cli_done=True): - if len(completions) > completions_per_page: - # Ask confirmation if it doesn't fit on the screen. - confirm = await create_confirm_session( - f"Display all {len(completions)} possibilities?", - ).prompt_async() - - if confirm: - # Display pages. - for page in range(page_count): - display(page) - - if page != page_count - 1: - # Display --MORE-- and go to the next page. - show_more = await _create_more_session( - "--MORE--" - ).prompt_async() - - if not show_more: - return - else: - app.output.flush() - else: - # Display all completions. - display(0) - - return app.create_background_task(run_compl()) - - -def _create_more_session(message: str = "--MORE--") -> "PromptSession[bool]": - """ - Create a `PromptSession` object for displaying the "--MORE--". - """ - from prompt_toolkit.shortcuts import PromptSession - - bindings = KeyBindings() - - @bindings.add(" ") - @bindings.add("y") - @bindings.add("Y") - @bindings.add(Keys.ControlJ) - @bindings.add(Keys.ControlM) - @bindings.add(Keys.ControlI) # Tab. - def _yes(event: E) -> None: - event.app.exit(result=True) - - @bindings.add("n") - @bindings.add("N") - @bindings.add("q") - @bindings.add("Q") - @bindings.add(Keys.ControlC) - def _no(event: E) -> None: - event.app.exit(result=False) - - @bindings.add(Keys.Any) - def _ignore(event: E) -> None: - "Disable inserting of text." - - return PromptSession(message, key_bindings=bindings, erase_when_done=True) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/cpr.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/cpr.py deleted file mode 100644 index 07b0fa75273..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/cpr.py +++ /dev/null @@ -1,28 +0,0 @@ -from prompt_toolkit.key_binding.key_processor import KeyPressEvent -from prompt_toolkit.keys import Keys - -from ..key_bindings import KeyBindings - -__all__ = [ - "load_cpr_bindings", -] - -E = KeyPressEvent - - -def load_cpr_bindings() -> KeyBindings: - key_bindings = KeyBindings() - - @key_bindings.add(Keys.CPRResponse, save_before=lambda e: False) - def _(event: E) -> None: - """ - Handle incoming Cursor-Position-Request response. - """ - # The incoming data looks like u'\x1b[35;1R' - # Parse row/col information. - row, col = map(int, event.data[2:-1].split(";")) - - # Report absolute cursor position to the renderer. - event.app.renderer.report_absolute_cursor_row(row) - - return key_bindings diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/emacs.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/emacs.py deleted file mode 100644 index a4a5e348f83..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/emacs.py +++ /dev/null @@ -1,557 +0,0 @@ -# pylint: disable=function-redefined -from typing import Dict, Union - -from prompt_toolkit.application.current import get_app -from prompt_toolkit.buffer import Buffer, indent, unindent -from prompt_toolkit.completion import CompleteEvent -from prompt_toolkit.filters import ( - Condition, - emacs_insert_mode, - emacs_mode, - has_arg, - has_selection, - in_paste_mode, - is_multiline, - is_read_only, - shift_selection_mode, - vi_search_direction_reversed, -) -from prompt_toolkit.key_binding.key_bindings import Binding -from prompt_toolkit.key_binding.key_processor import KeyPressEvent -from prompt_toolkit.keys import Keys -from prompt_toolkit.selection import SelectionType - -from ..key_bindings import ConditionalKeyBindings, KeyBindings, KeyBindingsBase -from .named_commands import get_by_name - -__all__ = [ - "load_emacs_bindings", - "load_emacs_search_bindings", - "load_emacs_shift_selection_bindings", -] - -E = KeyPressEvent - - -def load_emacs_bindings() -> KeyBindingsBase: - """ - Some e-macs extensions. - """ - # Overview of Readline emacs commands: - # http://www.catonmat.net/download/readline-emacs-editing-mode-cheat-sheet.pdf - key_bindings = KeyBindings() - handle = key_bindings.add - - insert_mode = emacs_insert_mode - - @handle("escape") - def _esc(event: E) -> None: - """ - By default, ignore escape key. - - (If we don't put this here, and Esc is followed by a key which sequence - is not handled, we'll insert an Escape character in the input stream. - Something we don't want and happens to easily in emacs mode. - Further, people can always use ControlQ to do a quoted insert.) - """ - pass - - handle("c-a")(get_by_name("beginning-of-line")) - handle("c-b")(get_by_name("backward-char")) - handle("c-delete", filter=insert_mode)(get_by_name("kill-word")) - handle("c-e")(get_by_name("end-of-line")) - handle("c-f")(get_by_name("forward-char")) - handle("c-left")(get_by_name("backward-word")) - handle("c-right")(get_by_name("forward-word")) - handle("c-x", "r", "y", filter=insert_mode)(get_by_name("yank")) - handle("c-y", filter=insert_mode)(get_by_name("yank")) - handle("escape", "b")(get_by_name("backward-word")) - handle("escape", "c", filter=insert_mode)(get_by_name("capitalize-word")) - handle("escape", "d", filter=insert_mode)(get_by_name("kill-word")) - handle("escape", "f")(get_by_name("forward-word")) - handle("escape", "l", filter=insert_mode)(get_by_name("downcase-word")) - handle("escape", "u", filter=insert_mode)(get_by_name("uppercase-word")) - handle("escape", "y", filter=insert_mode)(get_by_name("yank-pop")) - handle("escape", "backspace", filter=insert_mode)(get_by_name("backward-kill-word")) - handle("escape", "\\", filter=insert_mode)(get_by_name("delete-horizontal-space")) - - handle("c-home")(get_by_name("beginning-of-buffer")) - handle("c-end")(get_by_name("end-of-buffer")) - - handle("c-_", save_before=(lambda e: False), filter=insert_mode)( - get_by_name("undo") - ) - - handle("c-x", "c-u", save_before=(lambda e: False), filter=insert_mode)( - get_by_name("undo") - ) - - handle("escape", "<", filter=~has_selection)(get_by_name("beginning-of-history")) - handle("escape", ">", filter=~has_selection)(get_by_name("end-of-history")) - - handle("escape", ".", filter=insert_mode)(get_by_name("yank-last-arg")) - handle("escape", "_", filter=insert_mode)(get_by_name("yank-last-arg")) - handle("escape", "c-y", filter=insert_mode)(get_by_name("yank-nth-arg")) - handle("escape", "#", filter=insert_mode)(get_by_name("insert-comment")) - handle("c-o")(get_by_name("operate-and-get-next")) - - # ControlQ does a quoted insert. Not that for vt100 terminals, you have to - # disable flow control by running ``stty -ixon``, otherwise Ctrl-Q and - # Ctrl-S are captured by the terminal. - handle("c-q", filter=~has_selection)(get_by_name("quoted-insert")) - - handle("c-x", "(")(get_by_name("start-kbd-macro")) - handle("c-x", ")")(get_by_name("end-kbd-macro")) - handle("c-x", "e")(get_by_name("call-last-kbd-macro")) - - @handle("c-n") - def _next(event: E) -> None: - "Next line." - event.current_buffer.auto_down() - - @handle("c-p") - def _prev(event: E) -> None: - "Previous line." - event.current_buffer.auto_up(count=event.arg) - - def handle_digit(c: str) -> None: - """ - Handle input of arguments. - The first number needs to be preceded by escape. - """ - - @handle(c, filter=has_arg) - @handle("escape", c) - def _(event: E) -> None: - event.append_to_arg_count(c) - - for c in "0123456789": - handle_digit(c) - - @handle("escape", "-", filter=~has_arg) - def _meta_dash(event: E) -> None: - """""" - if event._arg is None: - event.append_to_arg_count("-") - - @handle("-", filter=Condition(lambda: get_app().key_processor.arg == "-")) - def _dash(event: E) -> None: - """ - When '-' is typed again, after exactly '-' has been given as an - argument, ignore this. - """ - event.app.key_processor.arg = "-" - - @Condition - def is_returnable() -> bool: - return get_app().current_buffer.is_returnable - - # Meta + Enter: always accept input. - handle("escape", "enter", filter=insert_mode & is_returnable)( - get_by_name("accept-line") - ) - - # Enter: accept input in single line mode. - handle("enter", filter=insert_mode & is_returnable & ~is_multiline)( - get_by_name("accept-line") - ) - - def character_search(buff: Buffer, char: str, count: int) -> None: - if count < 0: - match = buff.document.find_backwards( - char, in_current_line=True, count=-count - ) - else: - match = buff.document.find(char, in_current_line=True, count=count) - - if match is not None: - buff.cursor_position += match - - @handle("c-]", Keys.Any) - def _goto_char(event: E) -> None: - "When Ctl-] + a character is pressed. go to that character." - # Also named 'character-search' - character_search(event.current_buffer, event.data, event.arg) - - @handle("escape", "c-]", Keys.Any) - def _goto_char_backwards(event: E) -> None: - "Like Ctl-], but backwards." - # Also named 'character-search-backward' - character_search(event.current_buffer, event.data, -event.arg) - - @handle("escape", "a") - def _prev_sentence(event: E) -> None: - "Previous sentence." - # TODO: - - @handle("escape", "e") - def _end_of_sentence(event: E) -> None: - "Move to end of sentence." - # TODO: - - @handle("escape", "t", filter=insert_mode) - def _swap_characters(event: E) -> None: - """ - Swap the last two words before the cursor. - """ - # TODO - - @handle("escape", "*", filter=insert_mode) - def _insert_all_completions(event: E) -> None: - """ - `meta-*`: Insert all possible completions of the preceding text. - """ - buff = event.current_buffer - - # List all completions. - complete_event = CompleteEvent(text_inserted=False, completion_requested=True) - completions = list( - buff.completer.get_completions(buff.document, complete_event) - ) - - # Insert them. - text_to_insert = " ".join(c.text for c in completions) - buff.insert_text(text_to_insert) - - @handle("c-x", "c-x") - def _toggle_start_end(event: E) -> None: - """ - Move cursor back and forth between the start and end of the current - line. - """ - buffer = event.current_buffer - - if buffer.document.is_cursor_at_the_end_of_line: - buffer.cursor_position += buffer.document.get_start_of_line_position( - after_whitespace=False - ) - else: - buffer.cursor_position += buffer.document.get_end_of_line_position() - - @handle("c-@") # Control-space or Control-@ - def _start_selection(event: E) -> None: - """ - Start of the selection (if the current buffer is not empty). - """ - # Take the current cursor position as the start of this selection. - buff = event.current_buffer - if buff.text: - buff.start_selection(selection_type=SelectionType.CHARACTERS) - - @handle("c-g", filter=~has_selection) - def _cancel(event: E) -> None: - """ - Control + G: Cancel completion menu and validation state. - """ - event.current_buffer.complete_state = None - event.current_buffer.validation_error = None - - @handle("c-g", filter=has_selection) - def _cancel_selection(event: E) -> None: - """ - Cancel selection. - """ - event.current_buffer.exit_selection() - - @handle("c-w", filter=has_selection) - @handle("c-x", "r", "k", filter=has_selection) - def _cut(event: E) -> None: - """ - Cut selected text. - """ - data = event.current_buffer.cut_selection() - event.app.clipboard.set_data(data) - - @handle("escape", "w", filter=has_selection) - def _copy(event: E) -> None: - """ - Copy selected text. - """ - data = event.current_buffer.copy_selection() - event.app.clipboard.set_data(data) - - @handle("escape", "left") - def _start_of_word(event: E) -> None: - """ - Cursor to start of previous word. - """ - buffer = event.current_buffer - buffer.cursor_position += ( - buffer.document.find_previous_word_beginning(count=event.arg) or 0 - ) - - @handle("escape", "right") - def _start_next_word(event: E) -> None: - """ - Cursor to start of next word. - """ - buffer = event.current_buffer - buffer.cursor_position += ( - buffer.document.find_next_word_beginning(count=event.arg) - or buffer.document.get_end_of_document_position() - ) - - @handle("escape", "/", filter=insert_mode) - def _complete(event: E) -> None: - """ - M-/: Complete. - """ - b = event.current_buffer - if b.complete_state: - b.complete_next() - else: - b.start_completion(select_first=True) - - @handle("c-c", ">", filter=has_selection) - def _indent(event: E) -> None: - """ - Indent selected text. - """ - buffer = event.current_buffer - - buffer.cursor_position += buffer.document.get_start_of_line_position( - after_whitespace=True - ) - - from_, to = buffer.document.selection_range() - from_, _ = buffer.document.translate_index_to_position(from_) - to, _ = buffer.document.translate_index_to_position(to) - - indent(buffer, from_, to + 1, count=event.arg) - - @handle("c-c", "<", filter=has_selection) - def _unindent(event: E) -> None: - """ - Unindent selected text. - """ - buffer = event.current_buffer - - from_, to = buffer.document.selection_range() - from_, _ = buffer.document.translate_index_to_position(from_) - to, _ = buffer.document.translate_index_to_position(to) - - unindent(buffer, from_, to + 1, count=event.arg) - - return ConditionalKeyBindings(key_bindings, emacs_mode) - - -def load_emacs_search_bindings() -> KeyBindingsBase: - key_bindings = KeyBindings() - handle = key_bindings.add - from . import search - - # NOTE: We don't bind 'Escape' to 'abort_search'. The reason is that we - # want Alt+Enter to accept input directly in incremental search mode. - # Instead, we have double escape. - - handle("c-r")(search.start_reverse_incremental_search) - handle("c-s")(search.start_forward_incremental_search) - - handle("c-c")(search.abort_search) - handle("c-g")(search.abort_search) - handle("c-r")(search.reverse_incremental_search) - handle("c-s")(search.forward_incremental_search) - handle("up")(search.reverse_incremental_search) - handle("down")(search.forward_incremental_search) - handle("enter")(search.accept_search) - - # Handling of escape. - handle("escape", eager=True)(search.accept_search) - - # Like Readline, it's more natural to accept the search when escape has - # been pressed, however instead the following two bindings could be used - # instead. - # #handle('escape', 'escape', eager=True)(search.abort_search) - # #handle('escape', 'enter', eager=True)(search.accept_search_and_accept_input) - - # If Read-only: also include the following key bindings: - - # '/' and '?' key bindings for searching, just like Vi mode. - handle("?", filter=is_read_only & ~vi_search_direction_reversed)( - search.start_reverse_incremental_search - ) - handle("/", filter=is_read_only & ~vi_search_direction_reversed)( - search.start_forward_incremental_search - ) - handle("?", filter=is_read_only & vi_search_direction_reversed)( - search.start_forward_incremental_search - ) - handle("/", filter=is_read_only & vi_search_direction_reversed)( - search.start_reverse_incremental_search - ) - - @handle("n", filter=is_read_only) - def _jump_next(event: E) -> None: - "Jump to next match." - event.current_buffer.apply_search( - event.app.current_search_state, - include_current_position=False, - count=event.arg, - ) - - @handle("N", filter=is_read_only) - def _jump_prev(event: E) -> None: - "Jump to previous match." - event.current_buffer.apply_search( - ~event.app.current_search_state, - include_current_position=False, - count=event.arg, - ) - - return ConditionalKeyBindings(key_bindings, emacs_mode) - - -def load_emacs_shift_selection_bindings() -> KeyBindingsBase: - """ - Bindings to select text with shift + cursor movements - """ - - key_bindings = KeyBindings() - handle = key_bindings.add - - def unshift_move(event: E) -> None: - """ - Used for the shift selection mode. When called with - a shift + movement key press event, moves the cursor - as if shift is not pressed. - """ - key = event.key_sequence[0].key - - if key == Keys.ShiftUp: - event.current_buffer.auto_up(count=event.arg) - return - if key == Keys.ShiftDown: - event.current_buffer.auto_down(count=event.arg) - return - - # the other keys are handled through their readline command - key_to_command: Dict[Union[Keys, str], str] = { - Keys.ShiftLeft: "backward-char", - Keys.ShiftRight: "forward-char", - Keys.ShiftHome: "beginning-of-line", - Keys.ShiftEnd: "end-of-line", - Keys.ControlShiftLeft: "backward-word", - Keys.ControlShiftRight: "forward-word", - Keys.ControlShiftHome: "beginning-of-buffer", - Keys.ControlShiftEnd: "end-of-buffer", - } - - try: - # Both the dict lookup and `get_by_name` can raise KeyError. - binding = get_by_name(key_to_command[key]) - except KeyError: - pass - else: # (`else` is not really needed here.) - if isinstance(binding, Binding): - # (It should always be a binding here) - binding.call(event) - - @handle("s-left", filter=~has_selection) - @handle("s-right", filter=~has_selection) - @handle("s-up", filter=~has_selection) - @handle("s-down", filter=~has_selection) - @handle("s-home", filter=~has_selection) - @handle("s-end", filter=~has_selection) - @handle("c-s-left", filter=~has_selection) - @handle("c-s-right", filter=~has_selection) - @handle("c-s-home", filter=~has_selection) - @handle("c-s-end", filter=~has_selection) - def _start_selection(event: E) -> None: - """ - Start selection with shift + movement. - """ - # Take the current cursor position as the start of this selection. - buff = event.current_buffer - if buff.text: - buff.start_selection(selection_type=SelectionType.CHARACTERS) - - if buff.selection_state is not None: - # (`selection_state` should never be `None`, it is created by - # `start_selection`.) - buff.selection_state.enter_shift_mode() - - # Then move the cursor - original_position = buff.cursor_position - unshift_move(event) - if buff.cursor_position == original_position: - # Cursor didn't actually move - so cancel selection - # to avoid having an empty selection - buff.exit_selection() - - @handle("s-left", filter=shift_selection_mode) - @handle("s-right", filter=shift_selection_mode) - @handle("s-up", filter=shift_selection_mode) - @handle("s-down", filter=shift_selection_mode) - @handle("s-home", filter=shift_selection_mode) - @handle("s-end", filter=shift_selection_mode) - @handle("c-s-left", filter=shift_selection_mode) - @handle("c-s-right", filter=shift_selection_mode) - @handle("c-s-home", filter=shift_selection_mode) - @handle("c-s-end", filter=shift_selection_mode) - def _extend_selection(event: E) -> None: - """ - Extend the selection - """ - # Just move the cursor, like shift was not pressed - unshift_move(event) - buff = event.current_buffer - - if buff.selection_state is not None: - if buff.cursor_position == buff.selection_state.original_cursor_position: - # selection is now empty, so cancel selection - buff.exit_selection() - - @handle(Keys.Any, filter=shift_selection_mode) - def _replace_selection(event: E) -> None: - """ - Replace selection by what is typed - """ - event.current_buffer.cut_selection() - get_by_name("self-insert").call(event) - - @handle("enter", filter=shift_selection_mode & is_multiline) - def _newline(event: E) -> None: - """ - A newline replaces the selection - """ - event.current_buffer.cut_selection() - event.current_buffer.newline(copy_margin=not in_paste_mode()) - - @handle("backspace", filter=shift_selection_mode) - def _delete(event: E) -> None: - """ - Delete selection. - """ - event.current_buffer.cut_selection() - - @handle("c-y", filter=shift_selection_mode) - def _yank(event: E) -> None: - """ - In shift selection mode, yanking (pasting) replace the selection. - """ - buff = event.current_buffer - if buff.selection_state: - buff.cut_selection() - get_by_name("yank").call(event) - - # moving the cursor in shift selection mode cancels the selection - @handle("left", filter=shift_selection_mode) - @handle("right", filter=shift_selection_mode) - @handle("up", filter=shift_selection_mode) - @handle("down", filter=shift_selection_mode) - @handle("home", filter=shift_selection_mode) - @handle("end", filter=shift_selection_mode) - @handle("c-left", filter=shift_selection_mode) - @handle("c-right", filter=shift_selection_mode) - @handle("c-home", filter=shift_selection_mode) - @handle("c-end", filter=shift_selection_mode) - def _cancel(event: E) -> None: - """ - Cancel selection. - """ - event.current_buffer.exit_selection() - # we then process the cursor movement - key_press = event.key_sequence[0] - event.key_processor.feed(key_press, first=True) - - return ConditionalKeyBindings(key_bindings, emacs_mode) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/focus.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/focus.py deleted file mode 100644 index 40844db641c..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/focus.py +++ /dev/null @@ -1,24 +0,0 @@ -from prompt_toolkit.key_binding.key_processor import KeyPressEvent - -__all__ = [ - "focus_next", - "focus_previous", -] - -E = KeyPressEvent - - -def focus_next(event: E) -> None: - """ - Focus the next visible Window. - (Often bound to the `Tab` key.) - """ - event.app.layout.focus_next() - - -def focus_previous(event: E) -> None: - """ - Focus the previous visible Window. - (Often bound to the `BackTab` key.) - """ - event.app.layout.focus_previous() diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/mouse.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/mouse.py deleted file mode 100644 index 916cd41132f..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/mouse.py +++ /dev/null @@ -1,347 +0,0 @@ -import sys -from typing import TYPE_CHECKING, FrozenSet - -from prompt_toolkit.data_structures import Point -from prompt_toolkit.key_binding.key_processor import KeyPress, KeyPressEvent -from prompt_toolkit.keys import Keys -from prompt_toolkit.mouse_events import ( - MouseButton, - MouseEvent, - MouseEventType, - MouseModifier, -) - -from ..key_bindings import KeyBindings - -if TYPE_CHECKING: - from prompt_toolkit.key_binding.key_bindings import NotImplementedOrNone - -__all__ = [ - "load_mouse_bindings", -] - -E = KeyPressEvent - -# fmt: off -# flake8: noqa E201 -SCROLL_UP = MouseEventType.SCROLL_UP -SCROLL_DOWN = MouseEventType.SCROLL_DOWN -MOUSE_DOWN = MouseEventType.MOUSE_DOWN -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() - -LEFT = MouseButton.LEFT -MIDDLE = MouseButton.MIDDLE -RIGHT = MouseButton.RIGHT -NO_BUTTON = MouseButton.NONE -UNKNOWN_BUTTON = MouseButton.UNKNOWN - -xterm_sgr_mouse_events = { - ( 0, 'm') : (LEFT, MOUSE_UP, NO_MODIFIER), # left_up 0+ + + =0 - ( 4, 'm') : (LEFT, MOUSE_UP, SHIFT), # left_up Shift 0+4+ + =4 - ( 8, 'm') : (LEFT, MOUSE_UP, ALT), # left_up Alt 0+ +8+ =8 - (12, 'm') : (LEFT, MOUSE_UP, SHIFT_ALT), # left_up Shift Alt 0+4+8+ =12 - (16, 'm') : (LEFT, MOUSE_UP, CONTROL), # left_up Control 0+ + +16=16 - (20, 'm') : (LEFT, MOUSE_UP, SHIFT_CONTROL), # left_up Shift Control 0+4+ +16=20 - (24, 'm') : (LEFT, MOUSE_UP, ALT_CONTROL), # left_up Alt Control 0+ +8+16=24 - (28, 'm') : (LEFT, MOUSE_UP, SHIFT_ALT_CONTROL), # left_up Shift Alt Control 0+4+8+16=28 - - ( 1, 'm') : (MIDDLE, MOUSE_UP, NO_MODIFIER), # middle_up 1+ + + =1 - ( 5, 'm') : (MIDDLE, MOUSE_UP, SHIFT), # middle_up Shift 1+4+ + =5 - ( 9, 'm') : (MIDDLE, MOUSE_UP, ALT), # middle_up Alt 1+ +8+ =9 - (13, 'm') : (MIDDLE, MOUSE_UP, SHIFT_ALT), # middle_up Shift Alt 1+4+8+ =13 - (17, 'm') : (MIDDLE, MOUSE_UP, CONTROL), # middle_up Control 1+ + +16=17 - (21, 'm') : (MIDDLE, MOUSE_UP, SHIFT_CONTROL), # middle_up Shift Control 1+4+ +16=21 - (25, 'm') : (MIDDLE, MOUSE_UP, ALT_CONTROL), # middle_up Alt Control 1+ +8+16=25 - (29, 'm') : (MIDDLE, MOUSE_UP, SHIFT_ALT_CONTROL), # middle_up Shift Alt Control 1+4+8+16=29 - - ( 2, 'm') : (RIGHT, MOUSE_UP, NO_MODIFIER), # right_up 2+ + + =2 - ( 6, 'm') : (RIGHT, MOUSE_UP, SHIFT), # right_up Shift 2+4+ + =6 - (10, 'm') : (RIGHT, MOUSE_UP, ALT), # right_up Alt 2+ +8+ =10 - (14, 'm') : (RIGHT, MOUSE_UP, SHIFT_ALT), # right_up Shift Alt 2+4+8+ =14 - (18, 'm') : (RIGHT, MOUSE_UP, CONTROL), # right_up Control 2+ + +16=18 - (22, 'm') : (RIGHT, MOUSE_UP, SHIFT_CONTROL), # right_up Shift Control 2+4+ +16=22 - (26, 'm') : (RIGHT, MOUSE_UP, ALT_CONTROL), # right_up Alt Control 2+ +8+16=26 - (30, 'm') : (RIGHT, MOUSE_UP, SHIFT_ALT_CONTROL), # right_up Shift Alt Control 2+4+8+16=30 - - ( 0, 'M') : (LEFT, MOUSE_DOWN, NO_MODIFIER), # left_down 0+ + + =0 - ( 4, 'M') : (LEFT, MOUSE_DOWN, SHIFT), # left_down Shift 0+4+ + =4 - ( 8, 'M') : (LEFT, MOUSE_DOWN, ALT), # left_down Alt 0+ +8+ =8 - (12, 'M') : (LEFT, MOUSE_DOWN, SHIFT_ALT), # left_down Shift Alt 0+4+8+ =12 - (16, 'M') : (LEFT, MOUSE_DOWN, CONTROL), # left_down Control 0+ + +16=16 - (20, 'M') : (LEFT, MOUSE_DOWN, SHIFT_CONTROL), # left_down Shift Control 0+4+ +16=20 - (24, 'M') : (LEFT, MOUSE_DOWN, ALT_CONTROL), # left_down Alt Control 0+ +8+16=24 - (28, 'M') : (LEFT, MOUSE_DOWN, SHIFT_ALT_CONTROL), # left_down Shift Alt Control 0+4+8+16=28 - - ( 1, 'M') : (MIDDLE, MOUSE_DOWN, NO_MODIFIER), # middle_down 1+ + + =1 - ( 5, 'M') : (MIDDLE, MOUSE_DOWN, SHIFT), # middle_down Shift 1+4+ + =5 - ( 9, 'M') : (MIDDLE, MOUSE_DOWN, ALT), # middle_down Alt 1+ +8+ =9 - (13, 'M') : (MIDDLE, MOUSE_DOWN, SHIFT_ALT), # middle_down Shift Alt 1+4+8+ =13 - (17, 'M') : (MIDDLE, MOUSE_DOWN, CONTROL), # middle_down Control 1+ + +16=17 - (21, 'M') : (MIDDLE, MOUSE_DOWN, SHIFT_CONTROL), # middle_down Shift Control 1+4+ +16=21 - (25, 'M') : (MIDDLE, MOUSE_DOWN, ALT_CONTROL), # middle_down Alt Control 1+ +8+16=25 - (29, 'M') : (MIDDLE, MOUSE_DOWN, SHIFT_ALT_CONTROL), # middle_down Shift Alt Control 1+4+8+16=29 - - ( 2, 'M') : (RIGHT, MOUSE_DOWN, NO_MODIFIER), # right_down 2+ + + =2 - ( 6, 'M') : (RIGHT, MOUSE_DOWN, SHIFT), # right_down Shift 2+4+ + =6 - (10, 'M') : (RIGHT, MOUSE_DOWN, ALT), # right_down Alt 2+ +8+ =10 - (14, 'M') : (RIGHT, MOUSE_DOWN, SHIFT_ALT), # right_down Shift Alt 2+4+8+ =14 - (18, 'M') : (RIGHT, MOUSE_DOWN, CONTROL), # right_down Control 2+ + +16=18 - (22, 'M') : (RIGHT, MOUSE_DOWN, SHIFT_CONTROL), # right_down Shift Control 2+4+ +16=22 - (26, 'M') : (RIGHT, MOUSE_DOWN, ALT_CONTROL), # right_down Alt Control 2+ +8+16=26 - (30, 'M') : (RIGHT, MOUSE_DOWN, SHIFT_ALT_CONTROL), # right_down Shift Alt Control 2+4+8+16=30 - - (32, 'M') : (LEFT, MOUSE_MOVE, NO_MODIFIER), # left_drag 32+ + + =32 - (36, 'M') : (LEFT, MOUSE_MOVE, SHIFT), # left_drag Shift 32+4+ + =36 - (40, 'M') : (LEFT, MOUSE_MOVE, ALT), # left_drag Alt 32+ +8+ =40 - (44, 'M') : (LEFT, MOUSE_MOVE, SHIFT_ALT), # left_drag Shift Alt 32+4+8+ =44 - (48, 'M') : (LEFT, MOUSE_MOVE, CONTROL), # left_drag Control 32+ + +16=48 - (52, 'M') : (LEFT, MOUSE_MOVE, SHIFT_CONTROL), # left_drag Shift Control 32+4+ +16=52 - (56, 'M') : (LEFT, MOUSE_MOVE, ALT_CONTROL), # left_drag Alt Control 32+ +8+16=56 - (60, 'M') : (LEFT, MOUSE_MOVE, SHIFT_ALT_CONTROL), # left_drag Shift Alt Control 32+4+8+16=60 - - (33, 'M') : (MIDDLE, MOUSE_MOVE, NO_MODIFIER), # middle_drag 33+ + + =33 - (37, 'M') : (MIDDLE, MOUSE_MOVE, SHIFT), # middle_drag Shift 33+4+ + =37 - (41, 'M') : (MIDDLE, MOUSE_MOVE, ALT), # middle_drag Alt 33+ +8+ =41 - (45, 'M') : (MIDDLE, MOUSE_MOVE, SHIFT_ALT), # middle_drag Shift Alt 33+4+8+ =45 - (49, 'M') : (MIDDLE, MOUSE_MOVE, CONTROL), # middle_drag Control 33+ + +16=49 - (53, 'M') : (MIDDLE, MOUSE_MOVE, SHIFT_CONTROL), # middle_drag Shift Control 33+4+ +16=53 - (57, 'M') : (MIDDLE, MOUSE_MOVE, ALT_CONTROL), # middle_drag Alt Control 33+ +8+16=57 - (61, 'M') : (MIDDLE, MOUSE_MOVE, SHIFT_ALT_CONTROL), # middle_drag Shift Alt Control 33+4+8+16=61 - - (34, 'M') : (RIGHT, MOUSE_MOVE, NO_MODIFIER), # right_drag 34+ + + =34 - (38, 'M') : (RIGHT, MOUSE_MOVE, SHIFT), # right_drag Shift 34+4+ + =38 - (42, 'M') : (RIGHT, MOUSE_MOVE, ALT), # right_drag Alt 34+ +8+ =42 - (46, 'M') : (RIGHT, MOUSE_MOVE, SHIFT_ALT), # right_drag Shift Alt 34+4+8+ =46 - (50, 'M') : (RIGHT, MOUSE_MOVE, CONTROL), # right_drag Control 34+ + +16=50 - (54, 'M') : (RIGHT, MOUSE_MOVE, SHIFT_CONTROL), # right_drag Shift Control 34+4+ +16=54 - (58, 'M') : (RIGHT, MOUSE_MOVE, ALT_CONTROL), # right_drag Alt Control 34+ +8+16=58 - (62, 'M') : (RIGHT, MOUSE_MOVE, SHIFT_ALT_CONTROL), # right_drag Shift Alt Control 34+4+8+16=62 - - (35, 'M') : (NO_BUTTON, MOUSE_MOVE, NO_MODIFIER), # none_drag 35+ + + =35 - (39, 'M') : (NO_BUTTON, MOUSE_MOVE, SHIFT), # none_drag Shift 35+4+ + =39 - (43, 'M') : (NO_BUTTON, MOUSE_MOVE, ALT), # none_drag Alt 35+ +8+ =43 - (47, 'M') : (NO_BUTTON, MOUSE_MOVE, SHIFT_ALT), # none_drag Shift Alt 35+4+8+ =47 - (51, 'M') : (NO_BUTTON, MOUSE_MOVE, CONTROL), # none_drag Control 35+ + +16=51 - (55, 'M') : (NO_BUTTON, MOUSE_MOVE, SHIFT_CONTROL), # none_drag Shift Control 35+4+ +16=55 - (59, 'M') : (NO_BUTTON, MOUSE_MOVE, ALT_CONTROL), # none_drag Alt Control 35+ +8+16=59 - (63, 'M') : (NO_BUTTON, MOUSE_MOVE, SHIFT_ALT_CONTROL), # none_drag Shift Alt Control 35+4+8+16=63 - - (64, 'M') : (NO_BUTTON, SCROLL_UP, NO_MODIFIER), # scroll_up 64+ + + =64 - (68, 'M') : (NO_BUTTON, SCROLL_UP, SHIFT), # scroll_up Shift 64+4+ + =68 - (72, 'M') : (NO_BUTTON, SCROLL_UP, ALT), # scroll_up Alt 64+ +8+ =72 - (76, 'M') : (NO_BUTTON, SCROLL_UP, SHIFT_ALT), # scroll_up Shift Alt 64+4+8+ =76 - (80, 'M') : (NO_BUTTON, SCROLL_UP, CONTROL), # scroll_up Control 64+ + +16=80 - (84, 'M') : (NO_BUTTON, SCROLL_UP, SHIFT_CONTROL), # scroll_up Shift Control 64+4+ +16=84 - (88, 'M') : (NO_BUTTON, SCROLL_UP, ALT_CONTROL), # scroll_up Alt Control 64+ +8+16=88 - (92, 'M') : (NO_BUTTON, SCROLL_UP, SHIFT_ALT_CONTROL), # scroll_up Shift Alt Control 64+4+8+16=92 - - (65, 'M') : (NO_BUTTON, SCROLL_DOWN, NO_MODIFIER), # scroll_down 64+ + + =65 - (69, 'M') : (NO_BUTTON, SCROLL_DOWN, SHIFT), # scroll_down Shift 64+4+ + =69 - (73, 'M') : (NO_BUTTON, SCROLL_DOWN, ALT), # scroll_down Alt 64+ +8+ =73 - (77, 'M') : (NO_BUTTON, SCROLL_DOWN, SHIFT_ALT), # scroll_down Shift Alt 64+4+8+ =77 - (81, 'M') : (NO_BUTTON, SCROLL_DOWN, CONTROL), # scroll_down Control 64+ + +16=81 - (85, 'M') : (NO_BUTTON, SCROLL_DOWN, SHIFT_CONTROL), # scroll_down Shift Control 64+4+ +16=85 - (89, 'M') : (NO_BUTTON, SCROLL_DOWN, ALT_CONTROL), # scroll_down Alt Control 64+ +8+16=89 - (93, 'M') : (NO_BUTTON, SCROLL_DOWN, SHIFT_ALT_CONTROL), # scroll_down Shift Alt Control 64+4+8+16=93 -} - -typical_mouse_events = { - 32: (LEFT , MOUSE_DOWN , UNKNOWN_MODIFIER), - 33: (MIDDLE , MOUSE_DOWN , UNKNOWN_MODIFIER), - 34: (RIGHT , MOUSE_DOWN , UNKNOWN_MODIFIER), - 35: (UNKNOWN_BUTTON , MOUSE_UP , UNKNOWN_MODIFIER), - - 64: (LEFT , MOUSE_MOVE , UNKNOWN_MODIFIER), - 65: (MIDDLE , MOUSE_MOVE , UNKNOWN_MODIFIER), - 66: (RIGHT , MOUSE_MOVE , UNKNOWN_MODIFIER), - 67: (NO_BUTTON , MOUSE_MOVE , UNKNOWN_MODIFIER), - - 96: (NO_BUTTON , SCROLL_UP , UNKNOWN_MODIFIER), - 97: (NO_BUTTON , SCROLL_DOWN, UNKNOWN_MODIFIER), -} - -urxvt_mouse_events={ - 32: (UNKNOWN_BUTTON, MOUSE_DOWN , UNKNOWN_MODIFIER), - 35: (UNKNOWN_BUTTON, MOUSE_UP , UNKNOWN_MODIFIER), - 96: (NO_BUTTON , SCROLL_UP , UNKNOWN_MODIFIER), - 97: (NO_BUTTON , SCROLL_DOWN, UNKNOWN_MODIFIER), -} -# fmt:on - - -def load_mouse_bindings() -> KeyBindings: - """ - Key bindings, required for mouse support. - (Mouse events enter through the key binding system.) - """ - key_bindings = KeyBindings() - - @key_bindings.add(Keys.Vt100MouseEvent) - def _(event: E) -> "NotImplementedOrNone": - """ - Handling of incoming mouse event. - """ - # TypicaL: "eSC[MaB*" - # Urxvt: "Esc[96;14;13M" - # Xterm SGR: "Esc[<64;85;12M" - - # Parse incoming packet. - if event.data[2] == "M": - # Typical. - mouse_event, x, y = map(ord, event.data[3:]) - - # TODO: Is it possible to add modifiers here? - mouse_button, mouse_event_type, mouse_modifiers = typical_mouse_events[ - mouse_event - ] - - # Handle situations where `PosixStdinReader` used surrogateescapes. - if x >= 0xDC00: - x -= 0xDC00 - if y >= 0xDC00: - y -= 0xDC00 - - x -= 32 - y -= 32 - else: - # Urxvt and Xterm SGR. - # When the '<' is not present, we are not using the Xterm SGR mode, - # but Urxvt instead. - data = event.data[2:] - if data[:1] == "<": - sgr = True - data = data[1:] - else: - sgr = False - - # Extract coordinates. - mouse_event, x, y = map(int, data[:-1].split(";")) - m = data[-1] - - # Parse event type. - if sgr: - try: - ( - mouse_button, - mouse_event_type, - mouse_modifiers, - ) = xterm_sgr_mouse_events[mouse_event, m] - except KeyError: - return NotImplemented - - else: - # Some other terminals, like urxvt, Hyper terminal, ... - ( - mouse_button, - mouse_event_type, - mouse_modifiers, - ) = urxvt_mouse_events.get( - mouse_event, (UNKNOWN_BUTTON, MOUSE_MOVE, UNKNOWN_MODIFIER) - ) - - x -= 1 - y -= 1 - - # Only handle mouse events when we know the window height. - if event.app.renderer.height_is_known and mouse_event_type is not None: - # Take region above the layout into account. The reported - # coordinates are absolute to the visible part of the terminal. - from prompt_toolkit.renderer import HeightIsUnknownError - - try: - y -= event.app.renderer.rows_above_layout - except HeightIsUnknownError: - return NotImplemented - - # Call the mouse handler from the renderer. - - # Note: This can return `NotImplemented` if no mouse handler was - # found for this position, or if no repainting needs to - # happen. this way, we avoid excessive repaints during mouse - # movements. - handler = event.app.renderer.mouse_handlers.mouse_handlers[y][x] - return handler( - MouseEvent( - position=Point(x=x, y=y), - event_type=mouse_event_type, - button=mouse_button, - modifiers=mouse_modifiers, - ) - ) - - return NotImplemented - - @key_bindings.add(Keys.ScrollUp) - def _scroll_up(event: E) -> None: - """ - Scroll up event without cursor position. - """ - # We don't receive a cursor position, so we don't know which window to - # scroll. Just send an 'up' key press instead. - event.key_processor.feed(KeyPress(Keys.Up), first=True) - - @key_bindings.add(Keys.ScrollDown) - def _scroll_down(event: E) -> None: - """ - Scroll down event without cursor position. - """ - event.key_processor.feed(KeyPress(Keys.Down), first=True) - - @key_bindings.add(Keys.WindowsMouseEvent) - def _mouse(event: E) -> "NotImplementedOrNone": - """ - Handling of mouse events for Windows. - """ - # This key binding should only exist for Windows. - if sys.platform == "win32": - # Parse data. - pieces = event.data.split(";") - - button = MouseButton(pieces[0]) - event_type = MouseEventType(pieces[1]) - x = int(pieces[2]) - y = int(pieces[3]) - - # Make coordinates absolute to the visible part of the terminal. - output = event.app.renderer.output - - from prompt_toolkit.output.win32 import Win32Output - from prompt_toolkit.output.windows10 import Windows10_Output - - if isinstance(output, (Win32Output, Windows10_Output)): - screen_buffer_info = output.get_win32_screen_buffer_info() - rows_above_cursor = ( - screen_buffer_info.dwCursorPosition.Y - - event.app.renderer._cursor_pos.y - ) - y -= rows_above_cursor - - # Call the mouse event handler. - # (Can return `NotImplemented`.) - handler = event.app.renderer.mouse_handlers.mouse_handlers[y][x] - - return handler( - MouseEvent( - position=Point(x=x, y=y), - event_type=event_type, - button=button, - modifiers=UNKNOWN_MODIFIER, - ) - ) - - # No mouse handler found. Return `NotImplemented` so that we don't - # invalidate the UI. - return NotImplemented - - return key_bindings diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/named_commands.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/named_commands.py deleted file mode 100644 index e0796ef0b8e..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/named_commands.py +++ /dev/null @@ -1,687 +0,0 @@ -""" -Key bindings which are also known by GNU Readline by the given names. - -See: http://www.delorie.com/gnu/docs/readline/rlman_13.html -""" -from typing import Callable, Dict, TypeVar, Union, cast - -from prompt_toolkit.document import Document -from prompt_toolkit.enums import EditingMode -from prompt_toolkit.key_binding.key_bindings import Binding, key_binding -from prompt_toolkit.key_binding.key_processor import KeyPress, KeyPressEvent -from prompt_toolkit.keys import Keys -from prompt_toolkit.layout.controls import BufferControl -from prompt_toolkit.search import SearchDirection -from prompt_toolkit.selection import PasteMode - -from .completion import display_completions_like_readline, generate_completions - -__all__ = [ - "get_by_name", -] - - -# Typing. -_Handler = Callable[[KeyPressEvent], None] -_HandlerOrBinding = Union[_Handler, Binding] -_T = TypeVar("_T", bound=_HandlerOrBinding) -E = KeyPressEvent - - -# Registry that maps the Readline command names to their handlers. -_readline_commands: Dict[str, Binding] = {} - - -def register(name: str) -> Callable[[_T], _T]: - """ - Store handler in the `_readline_commands` dictionary. - """ - - def decorator(handler: _T) -> _T: - "`handler` is a callable or Binding." - if isinstance(handler, Binding): - _readline_commands[name] = handler - else: - _readline_commands[name] = key_binding()(cast(_Handler, handler)) - - return handler - - return decorator - - -def get_by_name(name: str) -> Binding: - """ - Return the handler for the (Readline) command with the given name. - """ - try: - return _readline_commands[name] - except KeyError as e: - raise KeyError("Unknown Readline command: %r" % name) from e - - -# -# Commands for moving -# See: http://www.delorie.com/gnu/docs/readline/rlman_14.html -# - - -@register("beginning-of-buffer") -def beginning_of_buffer(event: E) -> None: - """ - Move to the start of the buffer. - """ - buff = event.current_buffer - buff.cursor_position = 0 - - -@register("end-of-buffer") -def end_of_buffer(event: E) -> None: - """ - Move to the end of the buffer. - """ - buff = event.current_buffer - buff.cursor_position = len(buff.text) - - -@register("beginning-of-line") -def beginning_of_line(event: E) -> None: - """ - Move to the start of the current line. - """ - buff = event.current_buffer - buff.cursor_position += buff.document.get_start_of_line_position( - after_whitespace=False - ) - - -@register("end-of-line") -def end_of_line(event: E) -> None: - """ - Move to the end of the line. - """ - buff = event.current_buffer - buff.cursor_position += buff.document.get_end_of_line_position() - - -@register("forward-char") -def forward_char(event: E) -> None: - """ - Move forward a character. - """ - buff = event.current_buffer - buff.cursor_position += buff.document.get_cursor_right_position(count=event.arg) - - -@register("backward-char") -def backward_char(event: E) -> None: - "Move back a character." - buff = event.current_buffer - buff.cursor_position += buff.document.get_cursor_left_position(count=event.arg) - - -@register("forward-word") -def forward_word(event: E) -> None: - """ - Move forward to the end of the next word. Words are composed of letters and - digits. - """ - buff = event.current_buffer - pos = buff.document.find_next_word_ending(count=event.arg) - - if pos: - buff.cursor_position += pos - - -@register("backward-word") -def backward_word(event: E) -> None: - """ - Move back to the start of the current or previous word. Words are composed - of letters and digits. - """ - buff = event.current_buffer - pos = buff.document.find_previous_word_beginning(count=event.arg) - - if pos: - buff.cursor_position += pos - - -@register("clear-screen") -def clear_screen(event: E) -> None: - """ - Clear the screen and redraw everything at the top of the screen. - """ - event.app.renderer.clear() - - -@register("redraw-current-line") -def redraw_current_line(event: E) -> None: - """ - Refresh the current line. - (Readline defines this command, but prompt-toolkit doesn't have it.) - """ - pass - - -# -# Commands for manipulating the history. -# See: http://www.delorie.com/gnu/docs/readline/rlman_15.html -# - - -@register("accept-line") -def accept_line(event: E) -> None: - """ - Accept the line regardless of where the cursor is. - """ - event.current_buffer.validate_and_handle() - - -@register("previous-history") -def previous_history(event: E) -> None: - """ - Move `back` through the history list, fetching the previous command. - """ - event.current_buffer.history_backward(count=event.arg) - - -@register("next-history") -def next_history(event: E) -> None: - """ - Move `forward` through the history list, fetching the next command. - """ - event.current_buffer.history_forward(count=event.arg) - - -@register("beginning-of-history") -def beginning_of_history(event: E) -> None: - """ - Move to the first line in the history. - """ - event.current_buffer.go_to_history(0) - - -@register("end-of-history") -def end_of_history(event: E) -> None: - """ - Move to the end of the input history, i.e., the line currently being entered. - """ - event.current_buffer.history_forward(count=10**100) - buff = event.current_buffer - buff.go_to_history(len(buff._working_lines) - 1) - - -@register("reverse-search-history") -def reverse_search_history(event: E) -> None: - """ - Search backward starting at the current line and moving `up` through - the history as necessary. This is an incremental search. - """ - control = event.app.layout.current_control - - if isinstance(control, BufferControl) and control.search_buffer_control: - event.app.current_search_state.direction = SearchDirection.BACKWARD - event.app.layout.current_control = control.search_buffer_control - - -# -# Commands for changing text -# - - -@register("end-of-file") -def end_of_file(event: E) -> None: - """ - Exit. - """ - event.app.exit() - - -@register("delete-char") -def delete_char(event: E) -> None: - """ - Delete character before the cursor. - """ - deleted = event.current_buffer.delete(count=event.arg) - if not deleted: - event.app.output.bell() - - -@register("backward-delete-char") -def backward_delete_char(event: E) -> None: - """ - Delete the character behind the cursor. - """ - if event.arg < 0: - # When a negative argument has been given, this should delete in front - # of the cursor. - deleted = event.current_buffer.delete(count=-event.arg) - else: - deleted = event.current_buffer.delete_before_cursor(count=event.arg) - - if not deleted: - event.app.output.bell() - - -@register("self-insert") -def self_insert(event: E) -> None: - """ - Insert yourself. - """ - event.current_buffer.insert_text(event.data * event.arg) - - -@register("transpose-chars") -def transpose_chars(event: E) -> None: - """ - Emulate Emacs transpose-char behavior: at the beginning of the buffer, - do nothing. At the end of a line or buffer, swap the characters before - the cursor. Otherwise, move the cursor right, and then swap the - characters before the cursor. - """ - b = event.current_buffer - p = b.cursor_position - if p == 0: - return - elif p == len(b.text) or b.text[p] == "\n": - b.swap_characters_before_cursor() - else: - b.cursor_position += b.document.get_cursor_right_position() - b.swap_characters_before_cursor() - - -@register("uppercase-word") -def uppercase_word(event: E) -> None: - """ - Uppercase the current (or following) word. - """ - buff = event.current_buffer - - for i in range(event.arg): - pos = buff.document.find_next_word_ending() - words = buff.document.text_after_cursor[:pos] - buff.insert_text(words.upper(), overwrite=True) - - -@register("downcase-word") -def downcase_word(event: E) -> None: - """ - Lowercase the current (or following) word. - """ - buff = event.current_buffer - - for i in range(event.arg): # XXX: not DRY: see meta_c and meta_u!! - pos = buff.document.find_next_word_ending() - words = buff.document.text_after_cursor[:pos] - buff.insert_text(words.lower(), overwrite=True) - - -@register("capitalize-word") -def capitalize_word(event: E) -> None: - """ - Capitalize the current (or following) word. - """ - buff = event.current_buffer - - for i in range(event.arg): - pos = buff.document.find_next_word_ending() - words = buff.document.text_after_cursor[:pos] - buff.insert_text(words.title(), overwrite=True) - - -@register("quoted-insert") -def quoted_insert(event: E) -> None: - """ - Add the next character typed to the line verbatim. This is how to insert - key sequences like C-q, for example. - """ - event.app.quoted_insert = True - - -# -# Killing and yanking. -# - - -@register("kill-line") -def kill_line(event: E) -> None: - """ - Kill the text from the cursor to the end of the line. - - If we are at the end of the line, this should remove the newline. - (That way, it is possible to delete multiple lines by executing this - command multiple times.) - """ - buff = event.current_buffer - if event.arg < 0: - deleted = buff.delete_before_cursor( - count=-buff.document.get_start_of_line_position() - ) - else: - if buff.document.current_char == "\n": - deleted = buff.delete(1) - else: - deleted = buff.delete(count=buff.document.get_end_of_line_position()) - event.app.clipboard.set_text(deleted) - - -@register("kill-word") -def kill_word(event: E) -> None: - """ - Kill from point to the end of the current word, or if between words, to the - end of the next word. Word boundaries are the same as forward-word. - """ - buff = event.current_buffer - pos = buff.document.find_next_word_ending(count=event.arg) - - if pos: - deleted = buff.delete(count=pos) - - if event.is_repeat: - deleted = event.app.clipboard.get_data().text + deleted - - event.app.clipboard.set_text(deleted) - - -@register("unix-word-rubout") -def unix_word_rubout(event: E, WORD: bool = True) -> None: - """ - Kill the word behind point, using whitespace as a word boundary. - Usually bound to ControlW. - """ - buff = event.current_buffer - pos = buff.document.find_start_of_previous_word(count=event.arg, WORD=WORD) - - if pos is None: - # Nothing found? delete until the start of the document. (The - # input starts with whitespace and no words were found before the - # cursor.) - pos = -buff.cursor_position - - if pos: - deleted = buff.delete_before_cursor(count=-pos) - - # If the previous key press was also Control-W, concatenate deleted - # text. - if event.is_repeat: - deleted += event.app.clipboard.get_data().text - - event.app.clipboard.set_text(deleted) - else: - # Nothing to delete. Bell. - event.app.output.bell() - - -@register("backward-kill-word") -def backward_kill_word(event: E) -> None: - """ - Kills the word before point, using "not a letter nor a digit" as a word boundary. - Usually bound to M-Del or M-Backspace. - """ - unix_word_rubout(event, WORD=False) - - -@register("delete-horizontal-space") -def delete_horizontal_space(event: E) -> None: - """ - Delete all spaces and tabs around point. - """ - buff = event.current_buffer - text_before_cursor = buff.document.text_before_cursor - text_after_cursor = buff.document.text_after_cursor - - delete_before = len(text_before_cursor) - len(text_before_cursor.rstrip("\t ")) - delete_after = len(text_after_cursor) - len(text_after_cursor.lstrip("\t ")) - - buff.delete_before_cursor(count=delete_before) - buff.delete(count=delete_after) - - -@register("unix-line-discard") -def unix_line_discard(event: E) -> None: - """ - Kill backward from the cursor to the beginning of the current line. - """ - buff = event.current_buffer - - if buff.document.cursor_position_col == 0 and buff.document.cursor_position > 0: - buff.delete_before_cursor(count=1) - else: - deleted = buff.delete_before_cursor( - count=-buff.document.get_start_of_line_position() - ) - event.app.clipboard.set_text(deleted) - - -@register("yank") -def yank(event: E) -> None: - """ - Paste before cursor. - """ - event.current_buffer.paste_clipboard_data( - event.app.clipboard.get_data(), count=event.arg, paste_mode=PasteMode.EMACS - ) - - -@register("yank-nth-arg") -def yank_nth_arg(event: E) -> None: - """ - Insert the first argument of the previous command. With an argument, insert - the nth word from the previous command (start counting at 0). - """ - n = event.arg if event.arg_present else None - event.current_buffer.yank_nth_arg(n) - - -@register("yank-last-arg") -def yank_last_arg(event: E) -> None: - """ - Like `yank_nth_arg`, but if no argument has been given, yank the last word - of each line. - """ - n = event.arg if event.arg_present else None - event.current_buffer.yank_last_arg(n) - - -@register("yank-pop") -def yank_pop(event: E) -> None: - """ - Rotate the kill ring, and yank the new top. Only works following yank or - yank-pop. - """ - buff = event.current_buffer - doc_before_paste = buff.document_before_paste - clipboard = event.app.clipboard - - if doc_before_paste is not None: - buff.document = doc_before_paste - clipboard.rotate() - buff.paste_clipboard_data(clipboard.get_data(), paste_mode=PasteMode.EMACS) - - -# -# Completion. -# - - -@register("complete") -def complete(event: E) -> None: - """ - Attempt to perform completion. - """ - display_completions_like_readline(event) - - -@register("menu-complete") -def menu_complete(event: E) -> None: - """ - Generate completions, or go to the next completion. (This is the default - way of completing input in prompt_toolkit.) - """ - generate_completions(event) - - -@register("menu-complete-backward") -def menu_complete_backward(event: E) -> None: - """ - Move backward through the list of possible completions. - """ - event.current_buffer.complete_previous() - - -# -# Keyboard macros. -# - - -@register("start-kbd-macro") -def start_kbd_macro(event: E) -> None: - """ - Begin saving the characters typed into the current keyboard macro. - """ - event.app.emacs_state.start_macro() - - -@register("end-kbd-macro") -def end_kbd_macro(event: E) -> None: - """ - Stop saving the characters typed into the current keyboard macro and save - the definition. - """ - event.app.emacs_state.end_macro() - - -@register("call-last-kbd-macro") -@key_binding(record_in_macro=False) -def call_last_kbd_macro(event: E) -> None: - """ - Re-execute the last keyboard macro defined, by making the characters in the - macro appear as if typed at the keyboard. - - Notice that we pass `record_in_macro=False`. This ensures that the 'c-x e' - key sequence doesn't appear in the recording itself. This function inserts - the body of the called macro back into the KeyProcessor, so these keys will - be added later on to the macro of their handlers have `record_in_macro=True`. - """ - # Insert the macro. - macro = event.app.emacs_state.macro - - if macro: - event.app.key_processor.feed_multiple(macro, first=True) - - -@register("print-last-kbd-macro") -def print_last_kbd_macro(event: E) -> None: - """ - Print the last keyboard macro. - """ - # TODO: Make the format suitable for the inputrc file. - def print_macro() -> None: - macro = event.app.emacs_state.macro - if macro: - for k in macro: - print(k) - - from prompt_toolkit.application.run_in_terminal import run_in_terminal - - run_in_terminal(print_macro) - - -# -# Miscellaneous Commands. -# - - -@register("undo") -def undo(event: E) -> None: - """ - Incremental undo. - """ - event.current_buffer.undo() - - -@register("insert-comment") -def insert_comment(event: E) -> None: - """ - Without numeric argument, comment all lines. - With numeric argument, uncomment all lines. - In any case accept the input. - """ - buff = event.current_buffer - - # Transform all lines. - if event.arg != 1: - - def change(line: str) -> str: - return line[1:] if line.startswith("#") else line - - else: - - def change(line: str) -> str: - return "#" + line - - buff.document = Document( - text="\n".join(map(change, buff.text.splitlines())), cursor_position=0 - ) - - # Accept input. - buff.validate_and_handle() - - -@register("vi-editing-mode") -def vi_editing_mode(event: E) -> None: - """ - Switch to Vi editing mode. - """ - event.app.editing_mode = EditingMode.VI - - -@register("emacs-editing-mode") -def emacs_editing_mode(event: E) -> None: - """ - Switch to Emacs editing mode. - """ - event.app.editing_mode = EditingMode.EMACS - - -@register("prefix-meta") -def prefix_meta(event: E) -> None: - """ - Metafy the next character typed. This is for keyboards without a meta key. - - Sometimes people also want to bind other keys to Meta, e.g. 'jj':: - - key_bindings.add_key_binding('j', 'j', filter=ViInsertMode())(prefix_meta) - """ - # ('first' should be true, because we want to insert it at the current - # position in the queue.) - event.app.key_processor.feed(KeyPress(Keys.Escape), first=True) - - -@register("operate-and-get-next") -def operate_and_get_next(event: E) -> None: - """ - Accept the current line for execution and fetch the next line relative to - the current line from the history for editing. - """ - buff = event.current_buffer - new_index = buff.working_index + 1 - - # Accept the current input. (This will also redraw the interface in the - # 'done' state.) - buff.validate_and_handle() - - # Set the new index at the start of the next run. - def set_working_index() -> None: - if new_index < len(buff._working_lines): - buff.working_index = new_index - - event.app.pre_run_callables.append(set_working_index) - - -@register("edit-and-execute-command") -def edit_and_execute(event: E) -> None: - """ - Invoke an editor on the current command line, and accept the result. - """ - buff = event.current_buffer - buff.open_in_editor(validate_and_handle=True) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/open_in_editor.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/open_in_editor.py deleted file mode 100644 index f8699f4a45b..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/open_in_editor.py +++ /dev/null @@ -1,49 +0,0 @@ -""" -Open in editor key bindings. -""" -from prompt_toolkit.filters import emacs_mode, has_selection, vi_navigation_mode - -from ..key_bindings import KeyBindings, KeyBindingsBase, merge_key_bindings -from .named_commands import get_by_name - -__all__ = [ - "load_open_in_editor_bindings", - "load_emacs_open_in_editor_bindings", - "load_vi_open_in_editor_bindings", -] - - -def load_open_in_editor_bindings() -> KeyBindingsBase: - """ - Load both the Vi and emacs key bindings for handling edit-and-execute-command. - """ - return merge_key_bindings( - [ - load_emacs_open_in_editor_bindings(), - load_vi_open_in_editor_bindings(), - ] - ) - - -def load_emacs_open_in_editor_bindings() -> KeyBindings: - """ - Pressing C-X C-E will open the buffer in an external editor. - """ - key_bindings = KeyBindings() - - key_bindings.add("c-x", "c-e", filter=emacs_mode & ~has_selection)( - get_by_name("edit-and-execute-command") - ) - - return key_bindings - - -def load_vi_open_in_editor_bindings() -> KeyBindings: - """ - Pressing 'v' in navigation mode will open the buffer in an external editor. - """ - key_bindings = KeyBindings() - key_bindings.add("v", filter=vi_navigation_mode)( - get_by_name("edit-and-execute-command") - ) - return key_bindings diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/page_navigation.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/page_navigation.py deleted file mode 100644 index 4d531c04377..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/page_navigation.py +++ /dev/null @@ -1,82 +0,0 @@ -""" -Key bindings for extra page navigation: bindings for up/down scrolling through -long pages, like in Emacs or Vi. -""" -from prompt_toolkit.filters import buffer_has_focus, emacs_mode, vi_mode -from prompt_toolkit.key_binding.key_bindings import ( - ConditionalKeyBindings, - KeyBindings, - KeyBindingsBase, - merge_key_bindings, -) - -from .scroll import ( - scroll_backward, - scroll_forward, - scroll_half_page_down, - scroll_half_page_up, - scroll_one_line_down, - scroll_one_line_up, - scroll_page_down, - scroll_page_up, -) - -__all__ = [ - "load_page_navigation_bindings", - "load_emacs_page_navigation_bindings", - "load_vi_page_navigation_bindings", -] - - -def load_page_navigation_bindings() -> KeyBindingsBase: - """ - Load both the Vi and Emacs bindings for page navigation. - """ - # Only enable when a `Buffer` is focused, otherwise, we would catch keys - # when another widget is focused (like for instance `c-d` in a - # ptterm.Terminal). - return ConditionalKeyBindings( - merge_key_bindings( - [ - load_emacs_page_navigation_bindings(), - load_vi_page_navigation_bindings(), - ] - ), - buffer_has_focus, - ) - - -def load_emacs_page_navigation_bindings() -> KeyBindingsBase: - """ - Key bindings, for scrolling up and down through pages. - This are separate bindings, because GNU readline doesn't have them. - """ - key_bindings = KeyBindings() - handle = key_bindings.add - - handle("c-v")(scroll_page_down) - handle("pagedown")(scroll_page_down) - handle("escape", "v")(scroll_page_up) - handle("pageup")(scroll_page_up) - - return ConditionalKeyBindings(key_bindings, emacs_mode) - - -def load_vi_page_navigation_bindings() -> KeyBindingsBase: - """ - Key bindings, for scrolling up and down through pages. - This are separate bindings, because GNU readline doesn't have them. - """ - key_bindings = KeyBindings() - handle = key_bindings.add - - handle("c-f")(scroll_forward) - handle("c-b")(scroll_backward) - handle("c-d")(scroll_half_page_down) - handle("c-u")(scroll_half_page_up) - handle("c-e")(scroll_one_line_down) - handle("c-y")(scroll_one_line_up) - handle("pagedown")(scroll_page_down) - handle("pageup")(scroll_page_up) - - return ConditionalKeyBindings(key_bindings, vi_mode) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/scroll.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/scroll.py deleted file mode 100644 index 4a43ff585ac..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/scroll.py +++ /dev/null @@ -1,187 +0,0 @@ -""" -Key bindings, for scrolling up and down through pages. - -This are separate bindings, because GNU readline doesn't have them, but -they are very useful for navigating through long multiline buffers, like in -Vi, Emacs, etc... -""" -from prompt_toolkit.key_binding.key_processor import KeyPressEvent - -__all__ = [ - "scroll_forward", - "scroll_backward", - "scroll_half_page_up", - "scroll_half_page_down", - "scroll_one_line_up", - "scroll_one_line_down", -] - -E = KeyPressEvent - - -def scroll_forward(event: E, half: bool = False) -> None: - """ - Scroll window down. - """ - w = event.app.layout.current_window - b = event.app.current_buffer - - if w and w.render_info: - info = w.render_info - ui_content = info.ui_content - - # Height to scroll. - scroll_height = info.window_height - if half: - scroll_height //= 2 - - # Calculate how many lines is equivalent to that vertical space. - y = b.document.cursor_position_row + 1 - height = 0 - while y < ui_content.line_count: - line_height = info.get_height_for_line(y) - - if height + line_height < scroll_height: - height += line_height - y += 1 - else: - break - - b.cursor_position = b.document.translate_row_col_to_index(y, 0) - - -def scroll_backward(event: E, half: bool = False) -> None: - """ - Scroll window up. - """ - w = event.app.layout.current_window - b = event.app.current_buffer - - if w and w.render_info: - info = w.render_info - - # Height to scroll. - scroll_height = info.window_height - if half: - scroll_height //= 2 - - # Calculate how many lines is equivalent to that vertical space. - y = max(0, b.document.cursor_position_row - 1) - height = 0 - while y > 0: - line_height = info.get_height_for_line(y) - - if height + line_height < scroll_height: - height += line_height - y -= 1 - else: - break - - b.cursor_position = b.document.translate_row_col_to_index(y, 0) - - -def scroll_half_page_down(event: E) -> None: - """ - Same as ControlF, but only scroll half a page. - """ - scroll_forward(event, half=True) - - -def scroll_half_page_up(event: E) -> None: - """ - Same as ControlB, but only scroll half a page. - """ - scroll_backward(event, half=True) - - -def scroll_one_line_down(event: E) -> None: - """ - scroll_offset += 1 - """ - w = event.app.layout.current_window - b = event.app.current_buffer - - if w: - # When the cursor is at the top, move to the next line. (Otherwise, only scroll.) - if w.render_info: - info = w.render_info - - if w.vertical_scroll < info.content_height - info.window_height: - if info.cursor_position.y <= info.configured_scroll_offsets.top: - b.cursor_position += b.document.get_cursor_down_position() - - w.vertical_scroll += 1 - - -def scroll_one_line_up(event: E) -> None: - """ - scroll_offset -= 1 - """ - w = event.app.layout.current_window - b = event.app.current_buffer - - if w: - # When the cursor is at the bottom, move to the previous line. (Otherwise, only scroll.) - if w.render_info: - info = w.render_info - - if w.vertical_scroll > 0: - first_line_height = info.get_height_for_line(info.first_visible_line()) - - cursor_up = info.cursor_position.y - ( - info.window_height - - 1 - - first_line_height - - info.configured_scroll_offsets.bottom - ) - - # Move cursor up, as many steps as the height of the first line. - # TODO: not entirely correct yet, in case of line wrapping and many long lines. - for _ in range(max(0, cursor_up)): - b.cursor_position += b.document.get_cursor_up_position() - - # Scroll window - w.vertical_scroll -= 1 - - -def scroll_page_down(event: E) -> None: - """ - Scroll page down. (Prefer the cursor at the top of the page, after scrolling.) - """ - w = event.app.layout.current_window - b = event.app.current_buffer - - if w and w.render_info: - # Scroll down one page. - line_index = max(w.render_info.last_visible_line(), w.vertical_scroll + 1) - w.vertical_scroll = line_index - - b.cursor_position = b.document.translate_row_col_to_index(line_index, 0) - b.cursor_position += b.document.get_start_of_line_position( - after_whitespace=True - ) - - -def scroll_page_up(event: E) -> None: - """ - Scroll page up. (Prefer the cursor at the bottom of the page, after scrolling.) - """ - w = event.app.layout.current_window - b = event.app.current_buffer - - if w and w.render_info: - # Put cursor at the first visible line. (But make sure that the cursor - # moves at least one line up.) - line_index = max( - 0, - min(w.render_info.first_visible_line(), b.document.cursor_position_row - 1), - ) - - b.cursor_position = b.document.translate_row_col_to_index(line_index, 0) - b.cursor_position += b.document.get_start_of_line_position( - after_whitespace=True - ) - - # Set the scroll offset. We can safely set it to zero; the Window will - # make sure that it scrolls at least until the cursor becomes visible. - w.vertical_scroll = 0 diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/search.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/search.py deleted file mode 100644 index 06a047e4cd1..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/search.py +++ /dev/null @@ -1,93 +0,0 @@ -""" -Search related key bindings. -""" -from prompt_toolkit import search -from prompt_toolkit.application.current import get_app -from prompt_toolkit.filters import Condition, control_is_searchable, is_searching -from prompt_toolkit.key_binding.key_processor import KeyPressEvent - -from ..key_bindings import key_binding - -__all__ = [ - "abort_search", - "accept_search", - "start_reverse_incremental_search", - "start_forward_incremental_search", - "reverse_incremental_search", - "forward_incremental_search", - "accept_search_and_accept_input", -] - -E = KeyPressEvent - - -@key_binding(filter=is_searching) -def abort_search(event: E) -> None: - """ - Abort an incremental search and restore the original - line. - (Usually bound to ControlG/ControlC.) - """ - search.stop_search() - - -@key_binding(filter=is_searching) -def accept_search(event: E) -> None: - """ - When enter pressed in isearch, quit isearch mode. (Multiline - isearch would be too complicated.) - (Usually bound to Enter.) - """ - search.accept_search() - - -@key_binding(filter=control_is_searchable) -def start_reverse_incremental_search(event: E) -> None: - """ - Enter reverse incremental search. - (Usually ControlR.) - """ - search.start_search(direction=search.SearchDirection.BACKWARD) - - -@key_binding(filter=control_is_searchable) -def start_forward_incremental_search(event: E) -> None: - """ - Enter forward incremental search. - (Usually ControlS.) - """ - search.start_search(direction=search.SearchDirection.FORWARD) - - -@key_binding(filter=is_searching) -def reverse_incremental_search(event: E) -> None: - """ - Apply reverse incremental search, but keep search buffer focused. - """ - search.do_incremental_search(search.SearchDirection.BACKWARD, count=event.arg) - - -@key_binding(filter=is_searching) -def forward_incremental_search(event: E) -> None: - """ - Apply forward incremental search, but keep search buffer focused. - """ - search.do_incremental_search(search.SearchDirection.FORWARD, count=event.arg) - - -@Condition -def _previous_buffer_is_returnable() -> bool: - """ - True if the previously focused buffer has a return handler. - """ - prev_control = get_app().layout.search_target_buffer_control - return bool(prev_control and prev_control.buffer.is_returnable) - - -@key_binding(filter=is_searching & _previous_buffer_is_returnable) -def accept_search_and_accept_input(event: E) -> None: - """ - Accept the search operation first, then accept the input. - """ - search.accept_search() - event.current_buffer.validate_and_handle() diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/vi.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/vi.py deleted file mode 100644 index efbb107de04..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/bindings/vi.py +++ /dev/null @@ -1,2221 +0,0 @@ -# pylint: disable=function-redefined -import codecs -import string -from enum import Enum -from itertools import accumulate -from typing import Callable, Iterable, List, Optional, Tuple, TypeVar, Union - -from prompt_toolkit.application.current import get_app -from prompt_toolkit.buffer import Buffer, indent, reshape_text, unindent -from prompt_toolkit.clipboard import ClipboardData -from prompt_toolkit.document import Document -from prompt_toolkit.filters import ( - Always, - Condition, - Filter, - has_arg, - is_read_only, - is_searching, -) -from prompt_toolkit.filters.app import ( - in_paste_mode, - is_multiline, - vi_digraph_mode, - vi_insert_mode, - vi_insert_multiple_mode, - vi_mode, - vi_navigation_mode, - vi_recording_macro, - vi_replace_mode, - vi_replace_single_mode, - vi_search_direction_reversed, - vi_selection_mode, - vi_waiting_for_text_object_mode, -) -from prompt_toolkit.input.vt100_parser import Vt100Parser -from prompt_toolkit.key_binding.digraphs import DIGRAPHS -from prompt_toolkit.key_binding.key_processor import KeyPress, KeyPressEvent -from prompt_toolkit.key_binding.vi_state import CharacterFind, InputMode -from prompt_toolkit.keys import Keys -from prompt_toolkit.search import SearchDirection -from prompt_toolkit.selection import PasteMode, SelectionState, SelectionType - -from ..key_bindings import ConditionalKeyBindings, KeyBindings, KeyBindingsBase -from .named_commands import get_by_name - -__all__ = [ - "load_vi_bindings", - "load_vi_search_bindings", -] - -E = KeyPressEvent - -ascii_lowercase = string.ascii_lowercase - -vi_register_names = ascii_lowercase + "0123456789" - - -class TextObjectType(Enum): - EXCLUSIVE = "EXCLUSIVE" - INCLUSIVE = "INCLUSIVE" - LINEWISE = "LINEWISE" - BLOCK = "BLOCK" - - -class TextObject: - """ - Return struct for functions wrapped in ``text_object``. - Both `start` and `end` are relative to the current cursor position. - """ - - def __init__( - self, start: int, end: int = 0, type: TextObjectType = TextObjectType.EXCLUSIVE - ): - - self.start = start - self.end = end - self.type = type - - @property - def selection_type(self) -> SelectionType: - if self.type == TextObjectType.LINEWISE: - return SelectionType.LINES - if self.type == TextObjectType.BLOCK: - return SelectionType.BLOCK - else: - return SelectionType.CHARACTERS - - def sorted(self) -> Tuple[int, int]: - """ - Return a (start, end) tuple where start <= end. - """ - if self.start < self.end: - return self.start, self.end - else: - return self.end, self.start - - 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. - `buffer` is used to get start and end of line positions. - - This should return something that can be used in a slice, so the `end` - position is *not* included. - """ - start, end = self.sorted() - doc = document - - if ( - self.type == TextObjectType.EXCLUSIVE - and doc.translate_index_to_position(end + doc.cursor_position)[1] == 0 - ): - # If the motion is exclusive and the end of motion is on the first - # column, the end position becomes end of previous line. - end -= 1 - if self.type == TextObjectType.INCLUSIVE: - end += 1 - if self.type == TextObjectType.LINEWISE: - # Select whole lines - row, col = doc.translate_index_to_position(start + doc.cursor_position) - start = doc.translate_row_col_to_index(row, 0) - doc.cursor_position - row, col = doc.translate_index_to_position(end + doc.cursor_position) - end = ( - doc.translate_row_col_to_index(row, len(doc.lines[row])) - - doc.cursor_position - ) - return start, end - - def get_line_numbers(self, buffer: Buffer) -> Tuple[int, int]: - """ - Return a (start_line, end_line) pair. - """ - # Get absolute cursor positions from the text object. - from_, to = self.operator_range(buffer.document) - from_ += buffer.cursor_position - to += buffer.cursor_position - - # Take the start of the lines. - from_, _ = buffer.document.translate_index_to_position(from_) - to, _ = buffer.document.translate_index_to_position(to) - - return from_, to - - def cut(self, buffer: Buffer) -> Tuple[Document, ClipboardData]: - """ - Turn text object into `ClipboardData` instance. - """ - from_, to = self.operator_range(buffer.document) - - from_ += buffer.cursor_position - to += buffer.cursor_position - - # For Vi mode, the SelectionState does include the upper position, - # while `self.operator_range` does not. So, go one to the left, unless - # we're in the line mode, then we don't want to risk going to the - # previous line, and missing one line in the selection. - if self.type != TextObjectType.LINEWISE: - to -= 1 - - document = Document( - buffer.text, - to, - SelectionState(original_cursor_position=from_, type=self.selection_type), - ) - - new_document, clipboard_data = document.cut_selection() - return new_document, clipboard_data - - -# Typevar for any text object function: -TextObjectFunction = Callable[[E], TextObject] -_TOF = TypeVar("_TOF", bound=TextObjectFunction) - - -def create_text_object_decorator( - key_bindings: KeyBindings, -) -> Callable[..., Callable[[_TOF], _TOF]]: - """ - Create a decorator that can be used to register Vi text object implementations. - """ - - def text_object_decorator( - *keys: Union[Keys, str], - filter: Filter = Always(), - no_move_handler: bool = False, - no_selection_handler: bool = False, - eager: bool = False, - ) -> Callable[[_TOF], _TOF]: - """ - Register a text object function. - - Usage:: - - @text_object('w', filter=..., no_move_handler=False) - def handler(event): - # Return a text object for this key. - return TextObject(...) - - :param no_move_handler: Disable the move handler in navigation mode. - (It's still active in selection mode.) - """ - - def decorator(text_object_func: _TOF) -> _TOF: - @key_bindings.add( - *keys, filter=vi_waiting_for_text_object_mode & filter, eager=eager - ) - def _apply_operator_to_text_object(event: E) -> None: - # Arguments are multiplied. - vi_state = event.app.vi_state - event._arg = str((vi_state.operator_arg or 1) * (event.arg or 1)) - - # Call the text object handler. - text_obj = text_object_func(event) - - # Get the operator function. - # (Should never be None here, given the - # `vi_waiting_for_text_object_mode` filter state.) - operator_func = vi_state.operator_func - - if text_obj is not None and operator_func is not None: - # Call the operator function with the text object. - operator_func(event, text_obj) - - # Clear operator. - event.app.vi_state.operator_func = None - event.app.vi_state.operator_arg = None - - # Register a move operation. (Doesn't need an operator.) - if not no_move_handler: - - @key_bindings.add( - *keys, - filter=~vi_waiting_for_text_object_mode - & filter - & vi_navigation_mode, - eager=eager, - ) - def _move_in_navigation_mode(event: E) -> None: - """ - Move handler for navigation mode. - """ - text_object = text_object_func(event) - event.current_buffer.cursor_position += text_object.start - - # Register a move selection operation. - if not no_selection_handler: - - @key_bindings.add( - *keys, - filter=~vi_waiting_for_text_object_mode - & filter - & vi_selection_mode, - eager=eager, - ) - def _move_in_selection_mode(event: E) -> None: - """ - Move handler for selection mode. - """ - text_object = text_object_func(event) - buff = event.current_buffer - selection_state = buff.selection_state - - if selection_state is None: - return # Should not happen, because of the `vi_selection_mode` filter. - - # When the text object has both a start and end position, like 'i(' or 'iw', - # Turn this into a selection, otherwise the cursor. - if text_object.end: - # Take selection positions from text object. - start, end = text_object.operator_range(buff.document) - start += buff.cursor_position - end += buff.cursor_position - - selection_state.original_cursor_position = start - buff.cursor_position = end - - # Take selection type from text object. - if text_object.type == TextObjectType.LINEWISE: - selection_state.type = SelectionType.LINES - else: - selection_state.type = SelectionType.CHARACTERS - else: - event.current_buffer.cursor_position += text_object.start - - # Make it possible to chain @text_object decorators. - return text_object_func - - return decorator - - return text_object_decorator - - -# Typevar for any operator function: -OperatorFunction = Callable[[E, TextObject], None] -_OF = TypeVar("_OF", bound=OperatorFunction) - - -def create_operator_decorator( - key_bindings: KeyBindings, -) -> Callable[..., Callable[[_OF], _OF]]: - """ - Create a decorator that can be used for registering Vi operators. - """ - - def operator_decorator( - *keys: Union[Keys, str], filter: Filter = Always(), eager: bool = False - ) -> Callable[[_OF], _OF]: - """ - Register a Vi operator. - - Usage:: - - @operator('d', filter=...) - def handler(event, text_object): - # Do something with the text object here. - """ - - def decorator(operator_func: _OF) -> _OF: - @key_bindings.add( - *keys, - filter=~vi_waiting_for_text_object_mode & filter & vi_navigation_mode, - eager=eager, - ) - def _operator_in_navigation(event: E) -> None: - """ - Handle operator in navigation mode. - """ - # When this key binding is matched, only set the operator - # function in the ViState. We should execute it after a text - # object has been received. - event.app.vi_state.operator_func = operator_func - event.app.vi_state.operator_arg = event.arg - - @key_bindings.add( - *keys, - filter=~vi_waiting_for_text_object_mode & filter & vi_selection_mode, - eager=eager, - ) - def _operator_in_selection(event: E) -> None: - """ - Handle operator in selection mode. - """ - buff = event.current_buffer - selection_state = buff.selection_state - - if selection_state is not None: - # Create text object from selection. - if selection_state.type == SelectionType.LINES: - text_obj_type = TextObjectType.LINEWISE - elif selection_state.type == SelectionType.BLOCK: - text_obj_type = TextObjectType.BLOCK - else: - text_obj_type = TextObjectType.INCLUSIVE - - text_object = TextObject( - selection_state.original_cursor_position - buff.cursor_position, - type=text_obj_type, - ) - - # Execute operator. - operator_func(event, text_object) - - # Quit selection mode. - buff.selection_state = None - - return operator_func - - return decorator - - return operator_decorator - - -def load_vi_bindings() -> KeyBindingsBase: - """ - Vi extensions. - - # Overview of Readline Vi commands: - # http://www.catonmat.net/download/bash-vi-editing-mode-cheat-sheet.pdf - """ - # Note: Some key bindings have the "~IsReadOnly()" filter added. This - # prevents the handler to be executed when the focus is on a - # read-only buffer. - # This is however only required for those that change the ViState to - # INSERT mode. The `Buffer` class itself throws the - # `EditReadOnlyBuffer` exception for any text operations which is - # handled correctly. There is no need to add "~IsReadOnly" to all key - # bindings that do text manipulation. - - key_bindings = KeyBindings() - handle = key_bindings.add - - # (Note: Always take the navigation bindings in read-only mode, even when - # ViState says different.) - - TransformFunction = Tuple[Tuple[str, ...], Filter, Callable[[str], str]] - - vi_transform_functions: List[TransformFunction] = [ - # Rot 13 transformation - ( - ("g", "?"), - Always(), - lambda string: codecs.encode(string, "rot_13"), - ), - # To lowercase - (("g", "u"), Always(), lambda string: string.lower()), - # To uppercase. - (("g", "U"), Always(), lambda string: string.upper()), - # Swap case. - (("g", "~"), Always(), lambda string: string.swapcase()), - ( - ("~",), - Condition(lambda: get_app().vi_state.tilde_operator), - lambda string: string.swapcase(), - ), - ] - - # Insert a character literally (quoted insert). - handle("c-v", filter=vi_insert_mode)(get_by_name("quoted-insert")) - - @handle("escape") - def _back_to_navigation(event: E) -> None: - """ - Escape goes to vi navigation mode. - """ - buffer = event.current_buffer - vi_state = event.app.vi_state - - if vi_state.input_mode in (InputMode.INSERT, InputMode.REPLACE): - buffer.cursor_position += buffer.document.get_cursor_left_position() - - vi_state.input_mode = InputMode.NAVIGATION - - if bool(buffer.selection_state): - buffer.exit_selection() - - @handle("k", filter=vi_selection_mode) - def _up_in_selection(event: E) -> None: - """ - Arrow up in selection mode. - """ - event.current_buffer.cursor_up(count=event.arg) - - @handle("j", filter=vi_selection_mode) - def _down_in_selection(event: E) -> None: - """ - Arrow down in selection mode. - """ - event.current_buffer.cursor_down(count=event.arg) - - @handle("up", filter=vi_navigation_mode) - @handle("c-p", filter=vi_navigation_mode) - def _up_in_navigation(event: E) -> None: - """ - Arrow up and ControlP in navigation mode go up. - """ - event.current_buffer.auto_up(count=event.arg) - - @handle("k", filter=vi_navigation_mode) - def _go_up(event: E) -> None: - """ - Go up, but if we enter a new history entry, move to the start of the - line. - """ - event.current_buffer.auto_up( - count=event.arg, go_to_start_of_line_if_history_changes=True - ) - - @handle("down", filter=vi_navigation_mode) - @handle("c-n", filter=vi_navigation_mode) - def _go_down(event: E) -> None: - """ - Arrow down and Control-N in navigation mode. - """ - event.current_buffer.auto_down(count=event.arg) - - @handle("j", filter=vi_navigation_mode) - def _go_down2(event: E) -> None: - """ - Go down, but if we enter a new history entry, go to the start of the line. - """ - event.current_buffer.auto_down( - count=event.arg, go_to_start_of_line_if_history_changes=True - ) - - @handle("backspace", filter=vi_navigation_mode) - def _go_left(event: E) -> None: - """ - In navigation-mode, move cursor. - """ - event.current_buffer.cursor_position += ( - event.current_buffer.document.get_cursor_left_position(count=event.arg) - ) - - @handle("c-n", filter=vi_insert_mode) - def _complete_next(event: E) -> None: - b = event.current_buffer - - if b.complete_state: - b.complete_next() - else: - b.start_completion(select_first=True) - - @handle("c-p", filter=vi_insert_mode) - def _complete_prev(event: E) -> None: - """ - Control-P: To previous completion. - """ - b = event.current_buffer - - if b.complete_state: - b.complete_previous() - else: - b.start_completion(select_last=True) - - @handle("c-g", filter=vi_insert_mode) - @handle("c-y", filter=vi_insert_mode) - def _accept_completion(event: E) -> None: - """ - Accept current completion. - """ - event.current_buffer.complete_state = None - - @handle("c-e", filter=vi_insert_mode) - def _cancel_completion(event: E) -> None: - """ - Cancel completion. Go back to originally typed text. - """ - event.current_buffer.cancel_completion() - - @Condition - def is_returnable() -> bool: - return get_app().current_buffer.is_returnable - - # In navigation mode, pressing enter will always return the input. - handle("enter", filter=vi_navigation_mode & is_returnable)( - get_by_name("accept-line") - ) - - # In insert mode, also accept input when enter is pressed, and the buffer - # has been marked as single line. - handle("enter", filter=is_returnable & ~is_multiline)(get_by_name("accept-line")) - - @handle("enter", filter=~is_returnable & vi_navigation_mode) - def _start_of_next_line(event: E) -> None: - """ - Go to the beginning of next line. - """ - b = event.current_buffer - b.cursor_down(count=event.arg) - b.cursor_position += b.document.get_start_of_line_position( - after_whitespace=True - ) - - # ** In navigation mode ** - - # List of navigation commands: http://hea-www.harvard.edu/~fine/Tech/vi.html - - @handle("insert", filter=vi_navigation_mode) - def _insert_mode(event: E) -> None: - """ - Pressing the Insert key. - """ - event.app.vi_state.input_mode = InputMode.INSERT - - @handle("insert", filter=vi_insert_mode) - def _navigation_mode(event: E) -> None: - """ - Pressing the Insert key. - """ - event.app.vi_state.input_mode = InputMode.NAVIGATION - - @handle("a", filter=vi_navigation_mode & ~is_read_only) - # ~IsReadOnly, because we want to stay in navigation mode for - # read-only buffers. - def _a(event: E) -> None: - event.current_buffer.cursor_position += ( - event.current_buffer.document.get_cursor_right_position() - ) - event.app.vi_state.input_mode = InputMode.INSERT - - @handle("A", filter=vi_navigation_mode & ~is_read_only) - def _A(event: E) -> None: - event.current_buffer.cursor_position += ( - event.current_buffer.document.get_end_of_line_position() - ) - event.app.vi_state.input_mode = InputMode.INSERT - - @handle("C", filter=vi_navigation_mode & ~is_read_only) - def _change_until_end_of_line(event: E) -> None: - """ - Change to end of line. - Same as 'c$' (which is implemented elsewhere.) - """ - buffer = event.current_buffer - - deleted = buffer.delete(count=buffer.document.get_end_of_line_position()) - event.app.clipboard.set_text(deleted) - event.app.vi_state.input_mode = InputMode.INSERT - - @handle("c", "c", filter=vi_navigation_mode & ~is_read_only) - @handle("S", filter=vi_navigation_mode & ~is_read_only) - def _change_current_line(event: E) -> None: # TODO: implement 'arg' - """ - Change current line - """ - buffer = event.current_buffer - - # We copy the whole line. - data = ClipboardData(buffer.document.current_line, SelectionType.LINES) - event.app.clipboard.set_data(data) - - # But we delete after the whitespace - buffer.cursor_position += buffer.document.get_start_of_line_position( - after_whitespace=True - ) - buffer.delete(count=buffer.document.get_end_of_line_position()) - event.app.vi_state.input_mode = InputMode.INSERT - - @handle("D", filter=vi_navigation_mode) - def _delete_until_end_of_line(event: E) -> None: - """ - Delete from cursor position until the end of the line. - """ - buffer = event.current_buffer - deleted = buffer.delete(count=buffer.document.get_end_of_line_position()) - event.app.clipboard.set_text(deleted) - - @handle("d", "d", filter=vi_navigation_mode) - def _delete_line(event: E) -> None: - """ - Delete line. (Or the following 'n' lines.) - """ - buffer = event.current_buffer - - # Split string in before/deleted/after text. - lines = buffer.document.lines - - before = "\n".join(lines[: buffer.document.cursor_position_row]) - deleted = "\n".join( - lines[ - buffer.document.cursor_position_row : buffer.document.cursor_position_row - + event.arg - ] - ) - after = "\n".join(lines[buffer.document.cursor_position_row + event.arg :]) - - # Set new text. - if before and after: - before = before + "\n" - - # Set text and cursor position. - buffer.document = Document( - text=before + after, - # Cursor At the start of the first 'after' line, after the leading whitespace. - cursor_position=len(before) + len(after) - len(after.lstrip(" ")), - ) - - # Set clipboard data - event.app.clipboard.set_data(ClipboardData(deleted, SelectionType.LINES)) - - @handle("x", filter=vi_selection_mode) - def _cut(event: E) -> None: - """ - Cut selection. - ('x' is not an operator.) - """ - clipboard_data = event.current_buffer.cut_selection() - event.app.clipboard.set_data(clipboard_data) - - @handle("i", filter=vi_navigation_mode & ~is_read_only) - def _i(event: E) -> None: - event.app.vi_state.input_mode = InputMode.INSERT - - @handle("I", filter=vi_navigation_mode & ~is_read_only) - def _I(event: E) -> None: - event.app.vi_state.input_mode = InputMode.INSERT - event.current_buffer.cursor_position += ( - event.current_buffer.document.get_start_of_line_position( - after_whitespace=True - ) - ) - - @Condition - def in_block_selection() -> bool: - buff = get_app().current_buffer - return bool( - buff.selection_state and buff.selection_state.type == SelectionType.BLOCK - ) - - @handle("I", filter=in_block_selection & ~is_read_only) - def insert_in_block_selection(event: E, after: bool = False) -> None: - """ - Insert in block selection mode. - """ - buff = event.current_buffer - - # Store all cursor positions. - positions = [] - - if after: - - def get_pos(from_to: Tuple[int, int]) -> int: - return from_to[1] - - else: - - def get_pos(from_to: Tuple[int, int]) -> int: - return from_to[0] - - for i, from_to in enumerate(buff.document.selection_ranges()): - positions.append(get_pos(from_to)) - if i == 0: - buff.cursor_position = get_pos(from_to) - - buff.multiple_cursor_positions = positions - - # Go to 'INSERT_MULTIPLE' mode. - event.app.vi_state.input_mode = InputMode.INSERT_MULTIPLE - buff.exit_selection() - - @handle("A", filter=in_block_selection & ~is_read_only) - def _append_after_block(event: E) -> None: - insert_in_block_selection(event, after=True) - - @handle("J", filter=vi_navigation_mode & ~is_read_only) - def _join(event: E) -> None: - """ - Join lines. - """ - for i in range(event.arg): - event.current_buffer.join_next_line() - - @handle("g", "J", filter=vi_navigation_mode & ~is_read_only) - def _join_nospace(event: E) -> None: - """ - Join lines without space. - """ - for i in range(event.arg): - event.current_buffer.join_next_line(separator="") - - @handle("J", filter=vi_selection_mode & ~is_read_only) - def _join_selection(event: E) -> None: - """ - Join selected lines. - """ - event.current_buffer.join_selected_lines() - - @handle("g", "J", filter=vi_selection_mode & ~is_read_only) - def _join_selection_nospace(event: E) -> None: - """ - Join selected lines without space. - """ - event.current_buffer.join_selected_lines(separator="") - - @handle("p", filter=vi_navigation_mode) - def _paste(event: E) -> None: - """ - Paste after - """ - event.current_buffer.paste_clipboard_data( - event.app.clipboard.get_data(), - count=event.arg, - paste_mode=PasteMode.VI_AFTER, - ) - - @handle("P", filter=vi_navigation_mode) - def _paste_before(event: E) -> None: - """ - Paste before - """ - event.current_buffer.paste_clipboard_data( - event.app.clipboard.get_data(), - count=event.arg, - paste_mode=PasteMode.VI_BEFORE, - ) - - @handle('"', Keys.Any, "p", filter=vi_navigation_mode) - def _paste_register(event: E) -> None: - """ - Paste from named register. - """ - c = event.key_sequence[1].data - if c in vi_register_names: - data = event.app.vi_state.named_registers.get(c) - if data: - event.current_buffer.paste_clipboard_data( - data, count=event.arg, paste_mode=PasteMode.VI_AFTER - ) - - @handle('"', Keys.Any, "P", filter=vi_navigation_mode) - def _paste_register_before(event: E) -> None: - """ - Paste (before) from named register. - """ - c = event.key_sequence[1].data - if c in vi_register_names: - data = event.app.vi_state.named_registers.get(c) - if data: - event.current_buffer.paste_clipboard_data( - data, count=event.arg, paste_mode=PasteMode.VI_BEFORE - ) - - @handle("r", filter=vi_navigation_mode) - def _replace(event: E) -> None: - """ - Go to 'replace-single'-mode. - """ - event.app.vi_state.input_mode = InputMode.REPLACE_SINGLE - - @handle("R", filter=vi_navigation_mode) - def _replace_mode(event: E) -> None: - """ - Go to 'replace'-mode. - """ - event.app.vi_state.input_mode = InputMode.REPLACE - - @handle("s", filter=vi_navigation_mode & ~is_read_only) - def _substitute(event: E) -> None: - """ - Substitute with new text - (Delete character(s) and go to insert mode.) - """ - text = event.current_buffer.delete(count=event.arg) - event.app.clipboard.set_text(text) - event.app.vi_state.input_mode = InputMode.INSERT - - @handle("u", filter=vi_navigation_mode, save_before=(lambda e: False)) - def _undo(event: E) -> None: - for i in range(event.arg): - event.current_buffer.undo() - - @handle("V", filter=vi_navigation_mode) - def _visual_line(event: E) -> None: - """ - Start lines selection. - """ - event.current_buffer.start_selection(selection_type=SelectionType.LINES) - - @handle("c-v", filter=vi_navigation_mode) - def _visual_block(event: E) -> None: - """ - Enter block selection mode. - """ - event.current_buffer.start_selection(selection_type=SelectionType.BLOCK) - - @handle("V", filter=vi_selection_mode) - def _visual_line2(event: E) -> None: - """ - Exit line selection mode, or go from non line selection mode to line - selection mode. - """ - selection_state = event.current_buffer.selection_state - - if selection_state is not None: - if selection_state.type != SelectionType.LINES: - selection_state.type = SelectionType.LINES - else: - event.current_buffer.exit_selection() - - @handle("v", filter=vi_navigation_mode) - def _visual(event: E) -> None: - """ - Enter character selection mode. - """ - event.current_buffer.start_selection(selection_type=SelectionType.CHARACTERS) - - @handle("v", filter=vi_selection_mode) - def _visual2(event: E) -> None: - """ - Exit character selection mode, or go from non-character-selection mode - to character selection mode. - """ - selection_state = event.current_buffer.selection_state - - if selection_state is not None: - if selection_state.type != SelectionType.CHARACTERS: - selection_state.type = SelectionType.CHARACTERS - else: - event.current_buffer.exit_selection() - - @handle("c-v", filter=vi_selection_mode) - def _visual_block2(event: E) -> None: - """ - Exit block selection mode, or go from non block selection mode to block - selection mode. - """ - selection_state = event.current_buffer.selection_state - - if selection_state is not None: - if selection_state.type != SelectionType.BLOCK: - selection_state.type = SelectionType.BLOCK - else: - event.current_buffer.exit_selection() - - @handle("a", "w", filter=vi_selection_mode) - @handle("a", "W", filter=vi_selection_mode) - def _visual_auto_word(event: E) -> None: - """ - Switch from visual linewise mode to visual characterwise mode. - """ - buffer = event.current_buffer - - if ( - buffer.selection_state - and buffer.selection_state.type == SelectionType.LINES - ): - buffer.selection_state.type = SelectionType.CHARACTERS - - @handle("x", filter=vi_navigation_mode) - def _delete(event: E) -> None: - """ - Delete character. - """ - buff = event.current_buffer - count = min(event.arg, len(buff.document.current_line_after_cursor)) - if count: - text = event.current_buffer.delete(count=count) - event.app.clipboard.set_text(text) - - @handle("X", filter=vi_navigation_mode) - def _delete_before_cursor(event: E) -> None: - buff = event.current_buffer - count = min(event.arg, len(buff.document.current_line_before_cursor)) - if count: - text = event.current_buffer.delete_before_cursor(count=count) - event.app.clipboard.set_text(text) - - @handle("y", "y", filter=vi_navigation_mode) - @handle("Y", filter=vi_navigation_mode) - def _yank_line(event: E) -> None: - """ - Yank the whole line. - """ - text = "\n".join(event.current_buffer.document.lines_from_current[: event.arg]) - event.app.clipboard.set_data(ClipboardData(text, SelectionType.LINES)) - - @handle("+", filter=vi_navigation_mode) - def _next_line(event: E) -> None: - """ - Move to first non whitespace of next line - """ - buffer = event.current_buffer - buffer.cursor_position += buffer.document.get_cursor_down_position( - count=event.arg - ) - buffer.cursor_position += buffer.document.get_start_of_line_position( - after_whitespace=True - ) - - @handle("-", filter=vi_navigation_mode) - def _prev_line(event: E) -> None: - """ - Move to first non whitespace of previous line - """ - buffer = event.current_buffer - buffer.cursor_position += buffer.document.get_cursor_up_position( - count=event.arg - ) - buffer.cursor_position += buffer.document.get_start_of_line_position( - after_whitespace=True - ) - - @handle(">", ">", filter=vi_navigation_mode) - def _indent(event: E) -> None: - """ - Indent lines. - """ - buffer = event.current_buffer - current_row = buffer.document.cursor_position_row - indent(buffer, current_row, current_row + event.arg) - - @handle("<", "<", filter=vi_navigation_mode) - def _unindent(event: E) -> None: - """ - Unindent lines. - """ - current_row = event.current_buffer.document.cursor_position_row - unindent(event.current_buffer, current_row, current_row + event.arg) - - @handle("O", filter=vi_navigation_mode & ~is_read_only) - def _open_above(event: E) -> None: - """ - Open line above and enter insertion mode - """ - event.current_buffer.insert_line_above(copy_margin=not in_paste_mode()) - event.app.vi_state.input_mode = InputMode.INSERT - - @handle("o", filter=vi_navigation_mode & ~is_read_only) - def _open_below(event: E) -> None: - """ - Open line below and enter insertion mode - """ - event.current_buffer.insert_line_below(copy_margin=not in_paste_mode()) - event.app.vi_state.input_mode = InputMode.INSERT - - @handle("~", filter=vi_navigation_mode) - def _reverse_case(event: E) -> None: - """ - Reverse case of current character and move cursor forward. - """ - buffer = event.current_buffer - c = buffer.document.current_char - - if c is not None and c != "\n": - buffer.insert_text(c.swapcase(), overwrite=True) - - @handle("g", "u", "u", filter=vi_navigation_mode & ~is_read_only) - def _lowercase_line(event: E) -> None: - """ - Lowercase current line. - """ - buff = event.current_buffer - buff.transform_current_line(lambda s: s.lower()) - - @handle("g", "U", "U", filter=vi_navigation_mode & ~is_read_only) - def _uppercase_line(event: E) -> None: - """ - Uppercase current line. - """ - buff = event.current_buffer - buff.transform_current_line(lambda s: s.upper()) - - @handle("g", "~", "~", filter=vi_navigation_mode & ~is_read_only) - def _swapcase_line(event: E) -> None: - """ - Swap case of the current line. - """ - buff = event.current_buffer - buff.transform_current_line(lambda s: s.swapcase()) - - @handle("#", filter=vi_navigation_mode) - def _prev_occurence(event: E) -> None: - """ - Go to previous occurrence of this word. - """ - b = event.current_buffer - search_state = event.app.current_search_state - - search_state.text = b.document.get_word_under_cursor() - search_state.direction = SearchDirection.BACKWARD - - b.apply_search(search_state, count=event.arg, include_current_position=False) - - @handle("*", filter=vi_navigation_mode) - def _next_occurance(event: E) -> None: - """ - Go to next occurrence of this word. - """ - b = event.current_buffer - search_state = event.app.current_search_state - - search_state.text = b.document.get_word_under_cursor() - search_state.direction = SearchDirection.FORWARD - - b.apply_search(search_state, count=event.arg, include_current_position=False) - - @handle("(", filter=vi_navigation_mode) - def _begin_of_sentence(event: E) -> None: - # TODO: go to begin of sentence. - # XXX: should become text_object. - pass - - @handle(")", filter=vi_navigation_mode) - def _end_of_sentence(event: E) -> None: - # TODO: go to end of sentence. - # XXX: should become text_object. - pass - - operator = create_operator_decorator(key_bindings) - text_object = create_text_object_decorator(key_bindings) - - @handle(Keys.Any, filter=vi_waiting_for_text_object_mode) - def _unknown_text_object(event: E) -> None: - """ - Unknown key binding while waiting for a text object. - """ - event.app.output.bell() - - # - # *** Operators *** - # - - def create_delete_and_change_operators( - delete_only: bool, with_register: bool = False - ) -> None: - """ - Delete and change operators. - - :param delete_only: Create an operator that deletes, but doesn't go to insert mode. - :param with_register: Copy the deleted text to this named register instead of the clipboard. - """ - handler_keys: Iterable[str] - if with_register: - handler_keys = ('"', Keys.Any, "cd"[delete_only]) - else: - handler_keys = "cd"[delete_only] - - @operator(*handler_keys, filter=~is_read_only) - def delete_or_change_operator(event: E, text_object: TextObject) -> None: - clipboard_data = None - buff = event.current_buffer - - if text_object: - new_document, clipboard_data = text_object.cut(buff) - buff.document = new_document - - # Set deleted/changed text to clipboard or named register. - if clipboard_data and clipboard_data.text: - if with_register: - reg_name = event.key_sequence[1].data - if reg_name in vi_register_names: - event.app.vi_state.named_registers[reg_name] = clipboard_data - else: - event.app.clipboard.set_data(clipboard_data) - - # Only go back to insert mode in case of 'change'. - if not delete_only: - event.app.vi_state.input_mode = InputMode.INSERT - - create_delete_and_change_operators(False, False) - create_delete_and_change_operators(False, True) - create_delete_and_change_operators(True, False) - create_delete_and_change_operators(True, True) - - def create_transform_handler( - filter: Filter, transform_func: Callable[[str], str], *a: str - ) -> None: - @operator(*a, filter=filter & ~is_read_only) - def _(event: E, text_object: TextObject) -> None: - """ - Apply transformation (uppercase, lowercase, rot13, swap case). - """ - buff = event.current_buffer - start, end = text_object.operator_range(buff.document) - - if start < end: - # Transform. - buff.transform_region( - buff.cursor_position + start, - buff.cursor_position + end, - transform_func, - ) - - # Move cursor - buff.cursor_position += text_object.end or text_object.start - - for k, f, func in vi_transform_functions: - create_transform_handler(f, func, *k) - - @operator("y") - def _yank(event: E, text_object: TextObject) -> None: - """ - Yank operator. (Copy text.) - """ - _, clipboard_data = text_object.cut(event.current_buffer) - if clipboard_data.text: - event.app.clipboard.set_data(clipboard_data) - - @operator('"', Keys.Any, "y") - def _yank_to_register(event: E, text_object: TextObject) -> None: - """ - Yank selection to named register. - """ - c = event.key_sequence[1].data - if c in vi_register_names: - _, clipboard_data = text_object.cut(event.current_buffer) - event.app.vi_state.named_registers[c] = clipboard_data - - @operator(">") - def _indent_text_object(event: E, text_object: TextObject) -> None: - """ - Indent. - """ - buff = event.current_buffer - from_, to = text_object.get_line_numbers(buff) - indent(buff, from_, to + 1, count=event.arg) - - @operator("<") - def _unindent_text_object(event: E, text_object: TextObject) -> None: - """ - Unindent. - """ - buff = event.current_buffer - from_, to = text_object.get_line_numbers(buff) - unindent(buff, from_, to + 1, count=event.arg) - - @operator("g", "q") - def _reshape(event: E, text_object: TextObject) -> None: - """ - Reshape text. - """ - buff = event.current_buffer - from_, to = text_object.get_line_numbers(buff) - reshape_text(buff, from_, to) - - # - # *** Text objects *** - # - - @text_object("b") - def _b(event: E) -> TextObject: - """ - Move one word or token left. - """ - return TextObject( - event.current_buffer.document.find_start_of_previous_word(count=event.arg) - or 0 - ) - - @text_object("B") - def _B(event: E) -> TextObject: - """ - Move one non-blank word left - """ - return TextObject( - event.current_buffer.document.find_start_of_previous_word( - count=event.arg, WORD=True - ) - or 0 - ) - - @text_object("$") - def _dollar(event: E) -> TextObject: - """ - 'c$', 'd$' and '$': Delete/change/move until end of line. - """ - return TextObject(event.current_buffer.document.get_end_of_line_position()) - - @text_object("w") - def _word_forward(event: E) -> TextObject: - """ - 'word' forward. 'cw', 'dw', 'w': Delete/change/move one word. - """ - return TextObject( - event.current_buffer.document.find_next_word_beginning(count=event.arg) - or event.current_buffer.document.get_end_of_document_position() - ) - - @text_object("W") - def _WORD_forward(event: E) -> TextObject: - """ - 'WORD' forward. 'cW', 'dW', 'W': Delete/change/move one WORD. - """ - return TextObject( - event.current_buffer.document.find_next_word_beginning( - count=event.arg, WORD=True - ) - or event.current_buffer.document.get_end_of_document_position() - ) - - @text_object("e") - def _end_of_word(event: E) -> TextObject: - """ - End of 'word': 'ce', 'de', 'e' - """ - end = event.current_buffer.document.find_next_word_ending(count=event.arg) - return TextObject(end - 1 if end else 0, type=TextObjectType.INCLUSIVE) - - @text_object("E") - def _end_of_WORD(event: E) -> TextObject: - """ - End of 'WORD': 'cE', 'dE', 'E' - """ - end = event.current_buffer.document.find_next_word_ending( - count=event.arg, WORD=True - ) - return TextObject(end - 1 if end else 0, type=TextObjectType.INCLUSIVE) - - @text_object("i", "w", no_move_handler=True) - def _inner_word(event: E) -> TextObject: - """ - Inner 'word': ciw and diw - """ - start, end = event.current_buffer.document.find_boundaries_of_current_word() - return TextObject(start, end) - - @text_object("a", "w", no_move_handler=True) - def _a_word(event: E) -> TextObject: - """ - A 'word': caw and daw - """ - start, end = event.current_buffer.document.find_boundaries_of_current_word( - include_trailing_whitespace=True - ) - return TextObject(start, end) - - @text_object("i", "W", no_move_handler=True) - def _inner_WORD(event: E) -> TextObject: - """ - Inner 'WORD': ciW and diW - """ - start, end = event.current_buffer.document.find_boundaries_of_current_word( - WORD=True - ) - return TextObject(start, end) - - @text_object("a", "W", no_move_handler=True) - def _a_WORD(event: E) -> TextObject: - """ - A 'WORD': caw and daw - """ - start, end = event.current_buffer.document.find_boundaries_of_current_word( - WORD=True, include_trailing_whitespace=True - ) - return TextObject(start, end) - - @text_object("a", "p", no_move_handler=True) - def _paragraph(event: E) -> TextObject: - """ - Auto paragraph. - """ - start = event.current_buffer.document.start_of_paragraph() - end = event.current_buffer.document.end_of_paragraph(count=event.arg) - return TextObject(start, end) - - @text_object("^") - def _start_of_line(event: E) -> TextObject: - """'c^', 'd^' and '^': Soft start of line, after whitespace.""" - return TextObject( - event.current_buffer.document.get_start_of_line_position( - after_whitespace=True - ) - ) - - @text_object("0") - def _hard_start_of_line(event: E) -> TextObject: - """ - 'c0', 'd0': Hard start of line, before whitespace. - (The move '0' key is implemented elsewhere, because a '0' could also change the `arg`.) - """ - return TextObject( - event.current_buffer.document.get_start_of_line_position( - after_whitespace=False - ) - ) - - def create_ci_ca_handles( - ci_start: str, ci_end: str, inner: bool, key: Optional[str] = None - ) -> None: - # TODO: 'dat', 'dit', (tags (like xml) - """ - Delete/Change string between this start and stop character. But keep these characters. - This implements all the ci", ci<, ci{, ci(, di", di<, ca", ca<, ... combinations. - """ - - def handler(event: E) -> TextObject: - if ci_start == ci_end: - # Quotes - start = event.current_buffer.document.find_backwards( - ci_start, in_current_line=False - ) - end = event.current_buffer.document.find(ci_end, in_current_line=False) - else: - # Brackets - start = event.current_buffer.document.find_enclosing_bracket_left( - ci_start, ci_end - ) - end = event.current_buffer.document.find_enclosing_bracket_right( - ci_start, ci_end - ) - - if start is not None and end is not None: - offset = 0 if inner else 1 - return TextObject(start + 1 - offset, end + offset) - else: - # Nothing found. - return TextObject(0) - - if key is None: - text_object("ai"[inner], ci_start, no_move_handler=True)(handler) - text_object("ai"[inner], ci_end, no_move_handler=True)(handler) - else: - text_object("ai"[inner], key, no_move_handler=True)(handler) - - for inner in (False, True): - for ci_start, ci_end in [ - ('"', '"'), - ("'", "'"), - ("`", "`"), - ("[", "]"), - ("<", ">"), - ("{", "}"), - ("(", ")"), - ]: - create_ci_ca_handles(ci_start, ci_end, inner) - - create_ci_ca_handles("(", ")", inner, "b") # 'dab', 'dib' - create_ci_ca_handles("{", "}", inner, "B") # 'daB', 'diB' - - @text_object("{") - def _previous_section(event: E) -> TextObject: - """ - Move to previous blank-line separated section. - Implements '{', 'c{', 'd{', 'y{' - """ - index = event.current_buffer.document.start_of_paragraph( - count=event.arg, before=True - ) - return TextObject(index) - - @text_object("}") - def _next_section(event: E) -> TextObject: - """ - Move to next blank-line separated section. - Implements '}', 'c}', 'd}', 'y}' - """ - index = event.current_buffer.document.end_of_paragraph( - count=event.arg, after=True - ) - return TextObject(index) - - @text_object("f", Keys.Any) - def _next_occurence(event: E) -> TextObject: - """ - Go to next occurrence of character. Typing 'fx' will move the - cursor to the next occurrence of character. 'x'. - """ - event.app.vi_state.last_character_find = CharacterFind(event.data, False) - match = event.current_buffer.document.find( - event.data, in_current_line=True, count=event.arg - ) - if match: - return TextObject(match, type=TextObjectType.INCLUSIVE) - else: - return TextObject(0) - - @text_object("F", Keys.Any) - def _previous_occurance(event: E) -> TextObject: - """ - Go to previous occurrence of character. Typing 'Fx' will move the - cursor to the previous occurrence of character. 'x'. - """ - event.app.vi_state.last_character_find = CharacterFind(event.data, True) - return TextObject( - event.current_buffer.document.find_backwards( - event.data, in_current_line=True, count=event.arg - ) - or 0 - ) - - @text_object("t", Keys.Any) - def _t(event: E) -> TextObject: - """ - Move right to the next occurrence of c, then one char backward. - """ - event.app.vi_state.last_character_find = CharacterFind(event.data, False) - match = event.current_buffer.document.find( - event.data, in_current_line=True, count=event.arg - ) - if match: - return TextObject(match - 1, type=TextObjectType.INCLUSIVE) - else: - return TextObject(0) - - @text_object("T", Keys.Any) - def _T(event: E) -> TextObject: - """ - Move left to the previous occurrence of c, then one char forward. - """ - event.app.vi_state.last_character_find = CharacterFind(event.data, True) - match = event.current_buffer.document.find_backwards( - event.data, in_current_line=True, count=event.arg - ) - return TextObject(match + 1 if match else 0) - - def repeat(reverse: bool) -> None: - """ - Create ',' and ';' commands. - """ - - @text_object("," if reverse else ";") - def _(event: E) -> TextObject: - """ - Repeat the last 'f'/'F'/'t'/'T' command. - """ - pos: Optional[int] = 0 - vi_state = event.app.vi_state - - type = TextObjectType.EXCLUSIVE - - if vi_state.last_character_find: - char = vi_state.last_character_find.character - backwards = vi_state.last_character_find.backwards - - if reverse: - backwards = not backwards - - if backwards: - pos = event.current_buffer.document.find_backwards( - char, in_current_line=True, count=event.arg - ) - else: - pos = event.current_buffer.document.find( - char, in_current_line=True, count=event.arg - ) - type = TextObjectType.INCLUSIVE - if pos: - return TextObject(pos, type=type) - else: - return TextObject(0) - - repeat(True) - repeat(False) - - @text_object("h") - @text_object("left") - def _left(event: E) -> TextObject: - """ - Implements 'ch', 'dh', 'h': Cursor left. - """ - return TextObject( - event.current_buffer.document.get_cursor_left_position(count=event.arg) - ) - - @text_object("j", no_move_handler=True, no_selection_handler=True) - # Note: We also need `no_selection_handler`, because we in - # selection mode, we prefer the other 'j' binding that keeps - # `buffer.preferred_column`. - def _down(event: E) -> TextObject: - """ - Implements 'cj', 'dj', 'j', ... Cursor up. - """ - return TextObject( - event.current_buffer.document.get_cursor_down_position(count=event.arg), - type=TextObjectType.LINEWISE, - ) - - @text_object("k", no_move_handler=True, no_selection_handler=True) - def _up(event: E) -> TextObject: - """ - Implements 'ck', 'dk', 'k', ... Cursor up. - """ - return TextObject( - event.current_buffer.document.get_cursor_up_position(count=event.arg), - type=TextObjectType.LINEWISE, - ) - - @text_object("l") - @text_object(" ") - @text_object("right") - def _right(event: E) -> TextObject: - """ - Implements 'cl', 'dl', 'l', 'c ', 'd ', ' '. Cursor right. - """ - return TextObject( - event.current_buffer.document.get_cursor_right_position(count=event.arg) - ) - - @text_object("H") - def _top_of_screen(event: E) -> TextObject: - """ - Moves to the start of the visible region. (Below the scroll offset.) - Implements 'cH', 'dH', 'H'. - """ - w = event.app.layout.current_window - b = event.current_buffer - - if w and w.render_info: - # When we find a Window that has BufferControl showing this window, - # move to the start of the visible area. - pos = ( - b.document.translate_row_col_to_index( - w.render_info.first_visible_line(after_scroll_offset=True), 0 - ) - - b.cursor_position - ) - - else: - # Otherwise, move to the start of the input. - pos = -len(b.document.text_before_cursor) - return TextObject(pos, type=TextObjectType.LINEWISE) - - @text_object("M") - def _middle_of_screen(event: E) -> TextObject: - """ - Moves cursor to the vertical center of the visible region. - Implements 'cM', 'dM', 'M'. - """ - w = event.app.layout.current_window - b = event.current_buffer - - if w and w.render_info: - # When we find a Window that has BufferControl showing this window, - # move to the center of the visible area. - pos = ( - b.document.translate_row_col_to_index( - w.render_info.center_visible_line(), 0 - ) - - b.cursor_position - ) - - else: - # Otherwise, move to the start of the input. - pos = -len(b.document.text_before_cursor) - return TextObject(pos, type=TextObjectType.LINEWISE) - - @text_object("L") - def _end_of_screen(event: E) -> TextObject: - """ - Moves to the end of the visible region. (Above the scroll offset.) - """ - w = event.app.layout.current_window - b = event.current_buffer - - if w and w.render_info: - # When we find a Window that has BufferControl showing this window, - # move to the end of the visible area. - pos = ( - b.document.translate_row_col_to_index( - w.render_info.last_visible_line(before_scroll_offset=True), 0 - ) - - b.cursor_position - ) - - else: - # Otherwise, move to the end of the input. - pos = len(b.document.text_after_cursor) - return TextObject(pos, type=TextObjectType.LINEWISE) - - @text_object("n", no_move_handler=True) - def _search_next(event: E) -> TextObject: - """ - Search next. - """ - buff = event.current_buffer - search_state = event.app.current_search_state - - cursor_position = buff.get_search_position( - search_state, include_current_position=False, count=event.arg - ) - return TextObject(cursor_position - buff.cursor_position) - - @handle("n", filter=vi_navigation_mode) - def _search_next2(event: E) -> None: - """ - Search next in navigation mode. (This goes through the history.) - """ - search_state = event.app.current_search_state - - event.current_buffer.apply_search( - search_state, include_current_position=False, count=event.arg - ) - - @text_object("N", no_move_handler=True) - def _search_previous(event: E) -> TextObject: - """ - Search previous. - """ - buff = event.current_buffer - search_state = event.app.current_search_state - - cursor_position = buff.get_search_position( - ~search_state, include_current_position=False, count=event.arg - ) - return TextObject(cursor_position - buff.cursor_position) - - @handle("N", filter=vi_navigation_mode) - def _search_previous2(event: E) -> None: - """ - Search previous in navigation mode. (This goes through the history.) - """ - search_state = event.app.current_search_state - - event.current_buffer.apply_search( - ~search_state, include_current_position=False, count=event.arg - ) - - @handle("z", "+", filter=vi_navigation_mode | vi_selection_mode) - @handle("z", "t", filter=vi_navigation_mode | vi_selection_mode) - @handle("z", "enter", filter=vi_navigation_mode | vi_selection_mode) - def _scroll_top(event: E) -> None: - """ - Scrolls the window to makes the current line the first line in the visible region. - """ - b = event.current_buffer - event.app.layout.current_window.vertical_scroll = b.document.cursor_position_row - - @handle("z", "-", filter=vi_navigation_mode | vi_selection_mode) - @handle("z", "b", filter=vi_navigation_mode | vi_selection_mode) - def _scroll_bottom(event: E) -> None: - """ - Scrolls the window to makes the current line the last line in the visible region. - """ - # We can safely set the scroll offset to zero; the Window will make - # sure that it scrolls at least enough to make the cursor visible - # again. - event.app.layout.current_window.vertical_scroll = 0 - - @handle("z", "z", filter=vi_navigation_mode | vi_selection_mode) - def _scroll_center(event: E) -> None: - """ - Center Window vertically around cursor. - """ - w = event.app.layout.current_window - b = event.current_buffer - - if w and w.render_info: - info = w.render_info - - # Calculate the offset that we need in order to position the row - # containing the cursor in the center. - scroll_height = info.window_height // 2 - - y = max(0, b.document.cursor_position_row - 1) - height = 0 - while y > 0: - line_height = info.get_height_for_line(y) - - if height + line_height < scroll_height: - height += line_height - y -= 1 - else: - break - - w.vertical_scroll = y - - @text_object("%") - def _goto_corresponding_bracket(event: E) -> TextObject: - """ - Implements 'c%', 'd%', '%, 'y%' (Move to corresponding bracket.) - If an 'arg' has been given, go this this % position in the file. - """ - buffer = event.current_buffer - - if event._arg: - # If 'arg' has been given, the meaning of % is to go to the 'x%' - # row in the file. - if 0 < event.arg <= 100: - absolute_index = buffer.document.translate_row_col_to_index( - int((event.arg * buffer.document.line_count - 1) / 100), 0 - ) - return TextObject( - absolute_index - buffer.document.cursor_position, - type=TextObjectType.LINEWISE, - ) - else: - return TextObject(0) # Do nothing. - - else: - # Move to the corresponding opening/closing bracket (()'s, []'s and {}'s). - match = buffer.document.find_matching_bracket_position() - if match: - return TextObject(match, type=TextObjectType.INCLUSIVE) - else: - return TextObject(0) - - @text_object("|") - def _to_column(event: E) -> TextObject: - """ - Move to the n-th column (you may specify the argument n by typing it on - number keys, for example, 20|). - """ - return TextObject( - event.current_buffer.document.get_column_cursor_position(event.arg - 1) - ) - - @text_object("g", "g") - def _goto_first_line(event: E) -> TextObject: - """ - Go to the start of the very first line. - Implements 'gg', 'cgg', 'ygg' - """ - d = event.current_buffer.document - - if event._arg: - # Move to the given line. - return TextObject( - d.translate_row_col_to_index(event.arg - 1, 0) - d.cursor_position, - type=TextObjectType.LINEWISE, - ) - else: - # Move to the top of the input. - return TextObject( - d.get_start_of_document_position(), type=TextObjectType.LINEWISE - ) - - @text_object("g", "_") - def _goto_last_line(event: E) -> TextObject: - """ - Go to last non-blank of line. - 'g_', 'cg_', 'yg_', etc.. - """ - return TextObject( - event.current_buffer.document.last_non_blank_of_current_line_position(), - type=TextObjectType.INCLUSIVE, - ) - - @text_object("g", "e") - def _ge(event: E) -> TextObject: - """ - Go to last character of previous word. - 'ge', 'cge', 'yge', etc.. - """ - prev_end = event.current_buffer.document.find_previous_word_ending( - count=event.arg - ) - return TextObject( - prev_end - 1 if prev_end is not None else 0, type=TextObjectType.INCLUSIVE - ) - - @text_object("g", "E") - def _gE(event: E) -> TextObject: - """ - Go to last character of previous WORD. - 'gE', 'cgE', 'ygE', etc.. - """ - prev_end = event.current_buffer.document.find_previous_word_ending( - count=event.arg, WORD=True - ) - return TextObject( - prev_end - 1 if prev_end is not None else 0, type=TextObjectType.INCLUSIVE - ) - - @text_object("g", "m") - def _gm(event: E) -> TextObject: - """ - Like g0, but half a screenwidth to the right. (Or as much as possible.) - """ - w = event.app.layout.current_window - buff = event.current_buffer - - if w and w.render_info: - width = w.render_info.window_width - start = buff.document.get_start_of_line_position(after_whitespace=False) - start += int(min(width / 2, len(buff.document.current_line))) - - return TextObject(start, type=TextObjectType.INCLUSIVE) - return TextObject(0) - - @text_object("G") - def _last_line(event: E) -> TextObject: - """ - Go to the end of the document. (If no arg has been given.) - """ - buf = event.current_buffer - return TextObject( - buf.document.translate_row_col_to_index(buf.document.line_count - 1, 0) - - buf.cursor_position, - type=TextObjectType.LINEWISE, - ) - - # - # *** Other *** - # - - @handle("G", filter=has_arg) - def _to_nth_history_line(event: E) -> None: - """ - If an argument is given, move to this line in the history. (for - example, 15G) - """ - event.current_buffer.go_to_history(event.arg - 1) - - for n in "123456789": - - @handle( - n, - filter=vi_navigation_mode - | vi_selection_mode - | vi_waiting_for_text_object_mode, - ) - def _arg(event: E) -> None: - """ - Always handle numberics in navigation mode as arg. - """ - event.append_to_arg_count(event.data) - - @handle( - "0", - filter=( - vi_navigation_mode | vi_selection_mode | vi_waiting_for_text_object_mode - ) - & has_arg, - ) - def _0_arg(event: E) -> None: - """ - Zero when an argument was already give. - """ - event.append_to_arg_count(event.data) - - @handle(Keys.Any, filter=vi_replace_mode) - def _insert_text(event: E) -> None: - """ - Insert data at cursor position. - """ - event.current_buffer.insert_text(event.data, overwrite=True) - - @handle(Keys.Any, filter=vi_replace_single_mode) - def _replace_single(event: E) -> None: - """ - Replace single character at cursor position. - """ - event.current_buffer.insert_text(event.data, overwrite=True) - event.current_buffer.cursor_position -= 1 - event.app.vi_state.input_mode = InputMode.NAVIGATION - - @handle( - Keys.Any, - filter=vi_insert_multiple_mode, - save_before=(lambda e: not e.is_repeat), - ) - def _insert_text_multiple_cursors(event: E) -> None: - """ - Insert data at multiple cursor positions at once. - (Usually a result of pressing 'I' or 'A' in block-selection mode.) - """ - buff = event.current_buffer - original_text = buff.text - - # Construct new text. - text = [] - p = 0 - - for p2 in buff.multiple_cursor_positions: - text.append(original_text[p:p2]) - text.append(event.data) - p = p2 - - text.append(original_text[p:]) - - # Shift all cursor positions. - new_cursor_positions = [ - pos + i + 1 for i, pos in enumerate(buff.multiple_cursor_positions) - ] - - # Set result. - buff.text = "".join(text) - buff.multiple_cursor_positions = new_cursor_positions - buff.cursor_position += 1 - - @handle("backspace", filter=vi_insert_multiple_mode) - def _delete_before_multiple_cursors(event: E) -> None: - """ - Backspace, using multiple cursors. - """ - buff = event.current_buffer - original_text = buff.text - - # Construct new text. - deleted_something = False - text = [] - p = 0 - - for p2 in buff.multiple_cursor_positions: - if p2 > 0 and original_text[p2 - 1] != "\n": # Don't delete across lines. - text.append(original_text[p : p2 - 1]) - deleted_something = True - else: - text.append(original_text[p:p2]) - p = p2 - - text.append(original_text[p:]) - - if deleted_something: - # Shift all cursor positions. - lengths = [len(part) for part in text[:-1]] - new_cursor_positions = list(accumulate(lengths)) - - # Set result. - buff.text = "".join(text) - buff.multiple_cursor_positions = new_cursor_positions - buff.cursor_position -= 1 - else: - event.app.output.bell() - - @handle("delete", filter=vi_insert_multiple_mode) - def _delete_after_multiple_cursors(event: E) -> None: - """ - Delete, using multiple cursors. - """ - buff = event.current_buffer - original_text = buff.text - - # Construct new text. - deleted_something = False - text = [] - new_cursor_positions = [] - p = 0 - - for p2 in buff.multiple_cursor_positions: - text.append(original_text[p:p2]) - if p2 >= len(original_text) or original_text[p2] == "\n": - # Don't delete across lines. - p = p2 - else: - p = p2 + 1 - deleted_something = True - - text.append(original_text[p:]) - - if deleted_something: - # Shift all cursor positions. - lengths = [len(part) for part in text[:-1]] - new_cursor_positions = list(accumulate(lengths)) - - # Set result. - buff.text = "".join(text) - buff.multiple_cursor_positions = new_cursor_positions - else: - event.app.output.bell() - - @handle("left", filter=vi_insert_multiple_mode) - def _left_multiple(event: E) -> None: - """ - Move all cursors to the left. - (But keep all cursors on the same line.) - """ - buff = event.current_buffer - new_positions = [] - - for p in buff.multiple_cursor_positions: - if buff.document.translate_index_to_position(p)[1] > 0: - p -= 1 - new_positions.append(p) - - buff.multiple_cursor_positions = new_positions - - if buff.document.cursor_position_col > 0: - buff.cursor_position -= 1 - - @handle("right", filter=vi_insert_multiple_mode) - def _right_multiple(event: E) -> None: - """ - Move all cursors to the right. - (But keep all cursors on the same line.) - """ - buff = event.current_buffer - new_positions = [] - - for p in buff.multiple_cursor_positions: - row, column = buff.document.translate_index_to_position(p) - if column < len(buff.document.lines[row]): - p += 1 - new_positions.append(p) - - buff.multiple_cursor_positions = new_positions - - if not buff.document.is_cursor_at_the_end_of_line: - buff.cursor_position += 1 - - @handle("up", filter=vi_insert_multiple_mode) - @handle("down", filter=vi_insert_multiple_mode) - def _updown_multiple(event: E) -> None: - """ - Ignore all up/down key presses when in multiple cursor mode. - """ - - @handle("c-x", "c-l", filter=vi_insert_mode) - def _complete_line(event: E) -> None: - """ - Pressing the ControlX - ControlL sequence in Vi mode does line - completion based on the other lines in the document and the history. - """ - event.current_buffer.start_history_lines_completion() - - @handle("c-x", "c-f", filter=vi_insert_mode) - def _complete_filename(event: E) -> None: - """ - Complete file names. - """ - # TODO - pass - - @handle("c-k", filter=vi_insert_mode | vi_replace_mode) - def _digraph(event: E) -> None: - """ - Go into digraph mode. - """ - event.app.vi_state.waiting_for_digraph = True - - @Condition - def digraph_symbol_1_given() -> bool: - return get_app().vi_state.digraph_symbol1 is not None - - @handle(Keys.Any, filter=vi_digraph_mode & ~digraph_symbol_1_given) - def _digraph1(event: E) -> None: - """ - First digraph symbol. - """ - event.app.vi_state.digraph_symbol1 = event.data - - @handle(Keys.Any, filter=vi_digraph_mode & digraph_symbol_1_given) - def _create_digraph(event: E) -> None: - """ - Insert digraph. - """ - try: - # Lookup. - code: Tuple[str, str] = ( - event.app.vi_state.digraph_symbol1 or "", - event.data, - ) - if code not in DIGRAPHS: - code = code[::-1] # Try reversing. - symbol = DIGRAPHS[code] - except KeyError: - # Unknown digraph. - event.app.output.bell() - else: - # Insert digraph. - overwrite = event.app.vi_state.input_mode == InputMode.REPLACE - event.current_buffer.insert_text(chr(symbol), overwrite=overwrite) - event.app.vi_state.waiting_for_digraph = False - finally: - event.app.vi_state.waiting_for_digraph = False - event.app.vi_state.digraph_symbol1 = None - - @handle("c-o", filter=vi_insert_mode | vi_replace_mode) - def _quick_normal_mode(event: E) -> None: - """ - Go into normal mode for one single action. - """ - event.app.vi_state.temporary_navigation_mode = True - - @handle("q", Keys.Any, filter=vi_navigation_mode & ~vi_recording_macro) - def _start_macro(event: E) -> None: - """ - Start recording macro. - """ - c = event.key_sequence[1].data - if c in vi_register_names: - vi_state = event.app.vi_state - - vi_state.recording_register = c - vi_state.current_recording = "" - - @handle("q", filter=vi_navigation_mode & vi_recording_macro) - def _stop_macro(event: E) -> None: - """ - Stop recording macro. - """ - vi_state = event.app.vi_state - - # Store and stop recording. - if vi_state.recording_register: - vi_state.named_registers[vi_state.recording_register] = ClipboardData( - vi_state.current_recording - ) - vi_state.recording_register = None - vi_state.current_recording = "" - - @handle("@", Keys.Any, filter=vi_navigation_mode, record_in_macro=False) - def _execute_macro(event: E) -> None: - """ - Execute macro. - - Notice that we pass `record_in_macro=False`. This ensures that the `@x` - keys don't appear in the recording itself. This function inserts the - body of the called macro back into the KeyProcessor, so these keys will - be added later on to the macro of their handlers have - `record_in_macro=True`. - """ - # Retrieve macro. - c = event.key_sequence[1].data - try: - macro = event.app.vi_state.named_registers[c] - except KeyError: - return - - # Expand macro (which is a string in the register), in individual keys. - # Use vt100 parser for this. - keys: List[KeyPress] = [] - - parser = Vt100Parser(keys.append) - parser.feed(macro.text) - parser.flush() - - # Now feed keys back to the input processor. - for _ in range(event.arg): - event.app.key_processor.feed_multiple(keys, first=True) - - return ConditionalKeyBindings(key_bindings, vi_mode) - - -def load_vi_search_bindings() -> KeyBindingsBase: - key_bindings = KeyBindings() - handle = key_bindings.add - from . import search - - @Condition - def search_buffer_is_empty() -> bool: - "Returns True when the search buffer is empty." - return get_app().current_buffer.text == "" - - # Vi-style forward search. - handle( - "/", - filter=(vi_navigation_mode | vi_selection_mode) & ~vi_search_direction_reversed, - )(search.start_forward_incremental_search) - handle( - "?", - filter=(vi_navigation_mode | vi_selection_mode) & vi_search_direction_reversed, - )(search.start_forward_incremental_search) - handle("c-s")(search.start_forward_incremental_search) - - # Vi-style backward search. - handle( - "?", - filter=(vi_navigation_mode | vi_selection_mode) & ~vi_search_direction_reversed, - )(search.start_reverse_incremental_search) - handle( - "/", - filter=(vi_navigation_mode | vi_selection_mode) & vi_search_direction_reversed, - )(search.start_reverse_incremental_search) - handle("c-r")(search.start_reverse_incremental_search) - - # Apply the search. (At the / or ? prompt.) - handle("enter", filter=is_searching)(search.accept_search) - - handle("c-r", filter=is_searching)(search.reverse_incremental_search) - handle("c-s", filter=is_searching)(search.forward_incremental_search) - - handle("c-c")(search.abort_search) - handle("c-g")(search.abort_search) - handle("backspace", filter=search_buffer_is_empty)(search.abort_search) - - # Handle escape. This should accept the search, just like readline. - # `abort_search` would be a meaningful alternative. - handle("escape")(search.accept_search) - - return ConditionalKeyBindings(key_bindings, vi_mode) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/defaults.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/defaults.py deleted file mode 100644 index baa5974333b..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/defaults.py +++ /dev/null @@ -1,60 +0,0 @@ -""" -Default key bindings.:: - - key_bindings = load_key_bindings() - app = Application(key_bindings=key_bindings) -""" -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 -from prompt_toolkit.key_binding.bindings.emacs import ( - load_emacs_bindings, - load_emacs_search_bindings, - load_emacs_shift_selection_bindings, -) -from prompt_toolkit.key_binding.bindings.mouse import load_mouse_bindings -from prompt_toolkit.key_binding.bindings.vi import ( - load_vi_bindings, - load_vi_search_bindings, -) -from prompt_toolkit.key_binding.key_bindings import ( - ConditionalKeyBindings, - KeyBindingsBase, - merge_key_bindings, -) - -__all__ = [ - "load_key_bindings", -] - - -def load_key_bindings() -> KeyBindingsBase: - """ - Create a KeyBindings object that contains the default key bindings. - """ - all_bindings = merge_key_bindings( - [ - # Load basic bindings. - load_basic_bindings(), - # Load emacs bindings. - load_emacs_bindings(), - load_emacs_search_bindings(), - load_emacs_shift_selection_bindings(), - # Load Vi bindings. - load_vi_bindings(), - load_vi_search_bindings(), - ] - ) - - return merge_key_bindings( - [ - # Make sure that the above key bindings are only active if the - # currently focused control is a `BufferControl`. For other controls, we - # don't want these key bindings to intervene. (This would break "ptterm" - # for instance, which handles 'Keys.Any' in the user control itself.) - ConditionalKeyBindings(all_bindings, buffer_has_focus), - # Active, even when no buffer has been focused. - load_mouse_bindings(), - load_cpr_bindings(), - ] - ) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/digraphs.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/digraphs.py deleted file mode 100644 index ad3d288a8a3..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/digraphs.py +++ /dev/null @@ -1,1377 +0,0 @@ -""" -Vi Digraphs. -This is a list of special characters that can be inserted in Vi insert mode by -pressing Control-K followed by to normal characters. - -Taken from Neovim and translated to Python: -https://raw.githubusercontent.com/neovim/neovim/master/src/nvim/digraph.c -""" -from typing import Dict, Tuple - -__all__ = [ - "DIGRAPHS", -] - -# digraphs for Unicode from RFC1345 -# (also work for ISO-8859-1 aka latin1) -DIGRAPHS: Dict[Tuple[str, str], int] = { - ("N", "U"): 0x00, - ("S", "H"): 0x01, - ("S", "X"): 0x02, - ("E", "X"): 0x03, - ("E", "T"): 0x04, - ("E", "Q"): 0x05, - ("A", "K"): 0x06, - ("B", "L"): 0x07, - ("B", "S"): 0x08, - ("H", "T"): 0x09, - ("L", "F"): 0x0A, - ("V", "T"): 0x0B, - ("F", "F"): 0x0C, - ("C", "R"): 0x0D, - ("S", "O"): 0x0E, - ("S", "I"): 0x0F, - ("D", "L"): 0x10, - ("D", "1"): 0x11, - ("D", "2"): 0x12, - ("D", "3"): 0x13, - ("D", "4"): 0x14, - ("N", "K"): 0x15, - ("S", "Y"): 0x16, - ("E", "B"): 0x17, - ("C", "N"): 0x18, - ("E", "M"): 0x19, - ("S", "B"): 0x1A, - ("E", "C"): 0x1B, - ("F", "S"): 0x1C, - ("G", "S"): 0x1D, - ("R", "S"): 0x1E, - ("U", "S"): 0x1F, - ("S", "P"): 0x20, - ("N", "b"): 0x23, - ("D", "O"): 0x24, - ("A", "t"): 0x40, - ("<", "("): 0x5B, - ("/", "/"): 0x5C, - (")", ">"): 0x5D, - ("'", ">"): 0x5E, - ("'", "!"): 0x60, - ("(", "!"): 0x7B, - ("!", "!"): 0x7C, - ("!", ")"): 0x7D, - ("'", "?"): 0x7E, - ("D", "T"): 0x7F, - ("P", "A"): 0x80, - ("H", "O"): 0x81, - ("B", "H"): 0x82, - ("N", "H"): 0x83, - ("I", "N"): 0x84, - ("N", "L"): 0x85, - ("S", "A"): 0x86, - ("E", "S"): 0x87, - ("H", "S"): 0x88, - ("H", "J"): 0x89, - ("V", "S"): 0x8A, - ("P", "D"): 0x8B, - ("P", "U"): 0x8C, - ("R", "I"): 0x8D, - ("S", "2"): 0x8E, - ("S", "3"): 0x8F, - ("D", "C"): 0x90, - ("P", "1"): 0x91, - ("P", "2"): 0x92, - ("T", "S"): 0x93, - ("C", "C"): 0x94, - ("M", "W"): 0x95, - ("S", "G"): 0x96, - ("E", "G"): 0x97, - ("S", "S"): 0x98, - ("G", "C"): 0x99, - ("S", "C"): 0x9A, - ("C", "I"): 0x9B, - ("S", "T"): 0x9C, - ("O", "C"): 0x9D, - ("P", "M"): 0x9E, - ("A", "C"): 0x9F, - ("N", "S"): 0xA0, - ("!", "I"): 0xA1, - ("C", "t"): 0xA2, - ("P", "d"): 0xA3, - ("C", "u"): 0xA4, - ("Y", "e"): 0xA5, - ("B", "B"): 0xA6, - ("S", "E"): 0xA7, - ("'", ":"): 0xA8, - ("C", "o"): 0xA9, - ("-", "a"): 0xAA, - ("<", "<"): 0xAB, - ("N", "O"): 0xAC, - ("-", "-"): 0xAD, - ("R", "g"): 0xAE, - ("'", "m"): 0xAF, - ("D", "G"): 0xB0, - ("+", "-"): 0xB1, - ("2", "S"): 0xB2, - ("3", "S"): 0xB3, - ("'", "'"): 0xB4, - ("M", "y"): 0xB5, - ("P", "I"): 0xB6, - (".", "M"): 0xB7, - ("'", ","): 0xB8, - ("1", "S"): 0xB9, - ("-", "o"): 0xBA, - (">", ">"): 0xBB, - ("1", "4"): 0xBC, - ("1", "2"): 0xBD, - ("3", "4"): 0xBE, - ("?", "I"): 0xBF, - ("A", "!"): 0xC0, - ("A", "'"): 0xC1, - ("A", ">"): 0xC2, - ("A", "?"): 0xC3, - ("A", ":"): 0xC4, - ("A", "A"): 0xC5, - ("A", "E"): 0xC6, - ("C", ","): 0xC7, - ("E", "!"): 0xC8, - ("E", "'"): 0xC9, - ("E", ">"): 0xCA, - ("E", ":"): 0xCB, - ("I", "!"): 0xCC, - ("I", "'"): 0xCD, - ("I", ">"): 0xCE, - ("I", ":"): 0xCF, - ("D", "-"): 0xD0, - ("N", "?"): 0xD1, - ("O", "!"): 0xD2, - ("O", "'"): 0xD3, - ("O", ">"): 0xD4, - ("O", "?"): 0xD5, - ("O", ":"): 0xD6, - ("*", "X"): 0xD7, - ("O", "/"): 0xD8, - ("U", "!"): 0xD9, - ("U", "'"): 0xDA, - ("U", ">"): 0xDB, - ("U", ":"): 0xDC, - ("Y", "'"): 0xDD, - ("T", "H"): 0xDE, - ("s", "s"): 0xDF, - ("a", "!"): 0xE0, - ("a", "'"): 0xE1, - ("a", ">"): 0xE2, - ("a", "?"): 0xE3, - ("a", ":"): 0xE4, - ("a", "a"): 0xE5, - ("a", "e"): 0xE6, - ("c", ","): 0xE7, - ("e", "!"): 0xE8, - ("e", "'"): 0xE9, - ("e", ">"): 0xEA, - ("e", ":"): 0xEB, - ("i", "!"): 0xEC, - ("i", "'"): 0xED, - ("i", ">"): 0xEE, - ("i", ":"): 0xEF, - ("d", "-"): 0xF0, - ("n", "?"): 0xF1, - ("o", "!"): 0xF2, - ("o", "'"): 0xF3, - ("o", ">"): 0xF4, - ("o", "?"): 0xF5, - ("o", ":"): 0xF6, - ("-", ":"): 0xF7, - ("o", "/"): 0xF8, - ("u", "!"): 0xF9, - ("u", "'"): 0xFA, - ("u", ">"): 0xFB, - ("u", ":"): 0xFC, - ("y", "'"): 0xFD, - ("t", "h"): 0xFE, - ("y", ":"): 0xFF, - ("A", "-"): 0x0100, - ("a", "-"): 0x0101, - ("A", "("): 0x0102, - ("a", "("): 0x0103, - ("A", ";"): 0x0104, - ("a", ";"): 0x0105, - ("C", "'"): 0x0106, - ("c", "'"): 0x0107, - ("C", ">"): 0x0108, - ("c", ">"): 0x0109, - ("C", "."): 0x010A, - ("c", "."): 0x010B, - ("C", "<"): 0x010C, - ("c", "<"): 0x010D, - ("D", "<"): 0x010E, - ("d", "<"): 0x010F, - ("D", "/"): 0x0110, - ("d", "/"): 0x0111, - ("E", "-"): 0x0112, - ("e", "-"): 0x0113, - ("E", "("): 0x0114, - ("e", "("): 0x0115, - ("E", "."): 0x0116, - ("e", "."): 0x0117, - ("E", ";"): 0x0118, - ("e", ";"): 0x0119, - ("E", "<"): 0x011A, - ("e", "<"): 0x011B, - ("G", ">"): 0x011C, - ("g", ">"): 0x011D, - ("G", "("): 0x011E, - ("g", "("): 0x011F, - ("G", "."): 0x0120, - ("g", "."): 0x0121, - ("G", ","): 0x0122, - ("g", ","): 0x0123, - ("H", ">"): 0x0124, - ("h", ">"): 0x0125, - ("H", "/"): 0x0126, - ("h", "/"): 0x0127, - ("I", "?"): 0x0128, - ("i", "?"): 0x0129, - ("I", "-"): 0x012A, - ("i", "-"): 0x012B, - ("I", "("): 0x012C, - ("i", "("): 0x012D, - ("I", ";"): 0x012E, - ("i", ";"): 0x012F, - ("I", "."): 0x0130, - ("i", "."): 0x0131, - ("I", "J"): 0x0132, - ("i", "j"): 0x0133, - ("J", ">"): 0x0134, - ("j", ">"): 0x0135, - ("K", ","): 0x0136, - ("k", ","): 0x0137, - ("k", "k"): 0x0138, - ("L", "'"): 0x0139, - ("l", "'"): 0x013A, - ("L", ","): 0x013B, - ("l", ","): 0x013C, - ("L", "<"): 0x013D, - ("l", "<"): 0x013E, - ("L", "."): 0x013F, - ("l", "."): 0x0140, - ("L", "/"): 0x0141, - ("l", "/"): 0x0142, - ("N", "'"): 0x0143, - ("n", "'"): 0x0144, - ("N", ","): 0x0145, - ("n", ","): 0x0146, - ("N", "<"): 0x0147, - ("n", "<"): 0x0148, - ("'", "n"): 0x0149, - ("N", "G"): 0x014A, - ("n", "g"): 0x014B, - ("O", "-"): 0x014C, - ("o", "-"): 0x014D, - ("O", "("): 0x014E, - ("o", "("): 0x014F, - ("O", '"'): 0x0150, - ("o", '"'): 0x0151, - ("O", "E"): 0x0152, - ("o", "e"): 0x0153, - ("R", "'"): 0x0154, - ("r", "'"): 0x0155, - ("R", ","): 0x0156, - ("r", ","): 0x0157, - ("R", "<"): 0x0158, - ("r", "<"): 0x0159, - ("S", "'"): 0x015A, - ("s", "'"): 0x015B, - ("S", ">"): 0x015C, - ("s", ">"): 0x015D, - ("S", ","): 0x015E, - ("s", ","): 0x015F, - ("S", "<"): 0x0160, - ("s", "<"): 0x0161, - ("T", ","): 0x0162, - ("t", ","): 0x0163, - ("T", "<"): 0x0164, - ("t", "<"): 0x0165, - ("T", "/"): 0x0166, - ("t", "/"): 0x0167, - ("U", "?"): 0x0168, - ("u", "?"): 0x0169, - ("U", "-"): 0x016A, - ("u", "-"): 0x016B, - ("U", "("): 0x016C, - ("u", "("): 0x016D, - ("U", "0"): 0x016E, - ("u", "0"): 0x016F, - ("U", '"'): 0x0170, - ("u", '"'): 0x0171, - ("U", ";"): 0x0172, - ("u", ";"): 0x0173, - ("W", ">"): 0x0174, - ("w", ">"): 0x0175, - ("Y", ">"): 0x0176, - ("y", ">"): 0x0177, - ("Y", ":"): 0x0178, - ("Z", "'"): 0x0179, - ("z", "'"): 0x017A, - ("Z", "."): 0x017B, - ("z", "."): 0x017C, - ("Z", "<"): 0x017D, - ("z", "<"): 0x017E, - ("O", "9"): 0x01A0, - ("o", "9"): 0x01A1, - ("O", "I"): 0x01A2, - ("o", "i"): 0x01A3, - ("y", "r"): 0x01A6, - ("U", "9"): 0x01AF, - ("u", "9"): 0x01B0, - ("Z", "/"): 0x01B5, - ("z", "/"): 0x01B6, - ("E", "D"): 0x01B7, - ("A", "<"): 0x01CD, - ("a", "<"): 0x01CE, - ("I", "<"): 0x01CF, - ("i", "<"): 0x01D0, - ("O", "<"): 0x01D1, - ("o", "<"): 0x01D2, - ("U", "<"): 0x01D3, - ("u", "<"): 0x01D4, - ("A", "1"): 0x01DE, - ("a", "1"): 0x01DF, - ("A", "7"): 0x01E0, - ("a", "7"): 0x01E1, - ("A", "3"): 0x01E2, - ("a", "3"): 0x01E3, - ("G", "/"): 0x01E4, - ("g", "/"): 0x01E5, - ("G", "<"): 0x01E6, - ("g", "<"): 0x01E7, - ("K", "<"): 0x01E8, - ("k", "<"): 0x01E9, - ("O", ";"): 0x01EA, - ("o", ";"): 0x01EB, - ("O", "1"): 0x01EC, - ("o", "1"): 0x01ED, - ("E", "Z"): 0x01EE, - ("e", "z"): 0x01EF, - ("j", "<"): 0x01F0, - ("G", "'"): 0x01F4, - ("g", "'"): 0x01F5, - (";", "S"): 0x02BF, - ("'", "<"): 0x02C7, - ("'", "("): 0x02D8, - ("'", "."): 0x02D9, - ("'", "0"): 0x02DA, - ("'", ";"): 0x02DB, - ("'", '"'): 0x02DD, - ("A", "%"): 0x0386, - ("E", "%"): 0x0388, - ("Y", "%"): 0x0389, - ("I", "%"): 0x038A, - ("O", "%"): 0x038C, - ("U", "%"): 0x038E, - ("W", "%"): 0x038F, - ("i", "3"): 0x0390, - ("A", "*"): 0x0391, - ("B", "*"): 0x0392, - ("G", "*"): 0x0393, - ("D", "*"): 0x0394, - ("E", "*"): 0x0395, - ("Z", "*"): 0x0396, - ("Y", "*"): 0x0397, - ("H", "*"): 0x0398, - ("I", "*"): 0x0399, - ("K", "*"): 0x039A, - ("L", "*"): 0x039B, - ("M", "*"): 0x039C, - ("N", "*"): 0x039D, - ("C", "*"): 0x039E, - ("O", "*"): 0x039F, - ("P", "*"): 0x03A0, - ("R", "*"): 0x03A1, - ("S", "*"): 0x03A3, - ("T", "*"): 0x03A4, - ("U", "*"): 0x03A5, - ("F", "*"): 0x03A6, - ("X", "*"): 0x03A7, - ("Q", "*"): 0x03A8, - ("W", "*"): 0x03A9, - ("J", "*"): 0x03AA, - ("V", "*"): 0x03AB, - ("a", "%"): 0x03AC, - ("e", "%"): 0x03AD, - ("y", "%"): 0x03AE, - ("i", "%"): 0x03AF, - ("u", "3"): 0x03B0, - ("a", "*"): 0x03B1, - ("b", "*"): 0x03B2, - ("g", "*"): 0x03B3, - ("d", "*"): 0x03B4, - ("e", "*"): 0x03B5, - ("z", "*"): 0x03B6, - ("y", "*"): 0x03B7, - ("h", "*"): 0x03B8, - ("i", "*"): 0x03B9, - ("k", "*"): 0x03BA, - ("l", "*"): 0x03BB, - ("m", "*"): 0x03BC, - ("n", "*"): 0x03BD, - ("c", "*"): 0x03BE, - ("o", "*"): 0x03BF, - ("p", "*"): 0x03C0, - ("r", "*"): 0x03C1, - ("*", "s"): 0x03C2, - ("s", "*"): 0x03C3, - ("t", "*"): 0x03C4, - ("u", "*"): 0x03C5, - ("f", "*"): 0x03C6, - ("x", "*"): 0x03C7, - ("q", "*"): 0x03C8, - ("w", "*"): 0x03C9, - ("j", "*"): 0x03CA, - ("v", "*"): 0x03CB, - ("o", "%"): 0x03CC, - ("u", "%"): 0x03CD, - ("w", "%"): 0x03CE, - ("'", "G"): 0x03D8, - (",", "G"): 0x03D9, - ("T", "3"): 0x03DA, - ("t", "3"): 0x03DB, - ("M", "3"): 0x03DC, - ("m", "3"): 0x03DD, - ("K", "3"): 0x03DE, - ("k", "3"): 0x03DF, - ("P", "3"): 0x03E0, - ("p", "3"): 0x03E1, - ("'", "%"): 0x03F4, - ("j", "3"): 0x03F5, - ("I", "O"): 0x0401, - ("D", "%"): 0x0402, - ("G", "%"): 0x0403, - ("I", "E"): 0x0404, - ("D", "S"): 0x0405, - ("I", "I"): 0x0406, - ("Y", "I"): 0x0407, - ("J", "%"): 0x0408, - ("L", "J"): 0x0409, - ("N", "J"): 0x040A, - ("T", "s"): 0x040B, - ("K", "J"): 0x040C, - ("V", "%"): 0x040E, - ("D", "Z"): 0x040F, - ("A", "="): 0x0410, - ("B", "="): 0x0411, - ("V", "="): 0x0412, - ("G", "="): 0x0413, - ("D", "="): 0x0414, - ("E", "="): 0x0415, - ("Z", "%"): 0x0416, - ("Z", "="): 0x0417, - ("I", "="): 0x0418, - ("J", "="): 0x0419, - ("K", "="): 0x041A, - ("L", "="): 0x041B, - ("M", "="): 0x041C, - ("N", "="): 0x041D, - ("O", "="): 0x041E, - ("P", "="): 0x041F, - ("R", "="): 0x0420, - ("S", "="): 0x0421, - ("T", "="): 0x0422, - ("U", "="): 0x0423, - ("F", "="): 0x0424, - ("H", "="): 0x0425, - ("C", "="): 0x0426, - ("C", "%"): 0x0427, - ("S", "%"): 0x0428, - ("S", "c"): 0x0429, - ("=", '"'): 0x042A, - ("Y", "="): 0x042B, - ("%", '"'): 0x042C, - ("J", "E"): 0x042D, - ("J", "U"): 0x042E, - ("J", "A"): 0x042F, - ("a", "="): 0x0430, - ("b", "="): 0x0431, - ("v", "="): 0x0432, - ("g", "="): 0x0433, - ("d", "="): 0x0434, - ("e", "="): 0x0435, - ("z", "%"): 0x0436, - ("z", "="): 0x0437, - ("i", "="): 0x0438, - ("j", "="): 0x0439, - ("k", "="): 0x043A, - ("l", "="): 0x043B, - ("m", "="): 0x043C, - ("n", "="): 0x043D, - ("o", "="): 0x043E, - ("p", "="): 0x043F, - ("r", "="): 0x0440, - ("s", "="): 0x0441, - ("t", "="): 0x0442, - ("u", "="): 0x0443, - ("f", "="): 0x0444, - ("h", "="): 0x0445, - ("c", "="): 0x0446, - ("c", "%"): 0x0447, - ("s", "%"): 0x0448, - ("s", "c"): 0x0449, - ("=", "'"): 0x044A, - ("y", "="): 0x044B, - ("%", "'"): 0x044C, - ("j", "e"): 0x044D, - ("j", "u"): 0x044E, - ("j", "a"): 0x044F, - ("i", "o"): 0x0451, - ("d", "%"): 0x0452, - ("g", "%"): 0x0453, - ("i", "e"): 0x0454, - ("d", "s"): 0x0455, - ("i", "i"): 0x0456, - ("y", "i"): 0x0457, - ("j", "%"): 0x0458, - ("l", "j"): 0x0459, - ("n", "j"): 0x045A, - ("t", "s"): 0x045B, - ("k", "j"): 0x045C, - ("v", "%"): 0x045E, - ("d", "z"): 0x045F, - ("Y", "3"): 0x0462, - ("y", "3"): 0x0463, - ("O", "3"): 0x046A, - ("o", "3"): 0x046B, - ("F", "3"): 0x0472, - ("f", "3"): 0x0473, - ("V", "3"): 0x0474, - ("v", "3"): 0x0475, - ("C", "3"): 0x0480, - ("c", "3"): 0x0481, - ("G", "3"): 0x0490, - ("g", "3"): 0x0491, - ("A", "+"): 0x05D0, - ("B", "+"): 0x05D1, - ("G", "+"): 0x05D2, - ("D", "+"): 0x05D3, - ("H", "+"): 0x05D4, - ("W", "+"): 0x05D5, - ("Z", "+"): 0x05D6, - ("X", "+"): 0x05D7, - ("T", "j"): 0x05D8, - ("J", "+"): 0x05D9, - ("K", "%"): 0x05DA, - ("K", "+"): 0x05DB, - ("L", "+"): 0x05DC, - ("M", "%"): 0x05DD, - ("M", "+"): 0x05DE, - ("N", "%"): 0x05DF, - ("N", "+"): 0x05E0, - ("S", "+"): 0x05E1, - ("E", "+"): 0x05E2, - ("P", "%"): 0x05E3, - ("P", "+"): 0x05E4, - ("Z", "j"): 0x05E5, - ("Z", "J"): 0x05E6, - ("Q", "+"): 0x05E7, - ("R", "+"): 0x05E8, - ("S", "h"): 0x05E9, - ("T", "+"): 0x05EA, - (",", "+"): 0x060C, - (";", "+"): 0x061B, - ("?", "+"): 0x061F, - ("H", "'"): 0x0621, - ("a", "M"): 0x0622, - ("a", "H"): 0x0623, - ("w", "H"): 0x0624, - ("a", "h"): 0x0625, - ("y", "H"): 0x0626, - ("a", "+"): 0x0627, - ("b", "+"): 0x0628, - ("t", "m"): 0x0629, - ("t", "+"): 0x062A, - ("t", "k"): 0x062B, - ("g", "+"): 0x062C, - ("h", "k"): 0x062D, - ("x", "+"): 0x062E, - ("d", "+"): 0x062F, - ("d", "k"): 0x0630, - ("r", "+"): 0x0631, - ("z", "+"): 0x0632, - ("s", "+"): 0x0633, - ("s", "n"): 0x0634, - ("c", "+"): 0x0635, - ("d", "d"): 0x0636, - ("t", "j"): 0x0637, - ("z", "H"): 0x0638, - ("e", "+"): 0x0639, - ("i", "+"): 0x063A, - ("+", "+"): 0x0640, - ("f", "+"): 0x0641, - ("q", "+"): 0x0642, - ("k", "+"): 0x0643, - ("l", "+"): 0x0644, - ("m", "+"): 0x0645, - ("n", "+"): 0x0646, - ("h", "+"): 0x0647, - ("w", "+"): 0x0648, - ("j", "+"): 0x0649, - ("y", "+"): 0x064A, - (":", "+"): 0x064B, - ('"', "+"): 0x064C, - ("=", "+"): 0x064D, - ("/", "+"): 0x064E, - ("'", "+"): 0x064F, - ("1", "+"): 0x0650, - ("3", "+"): 0x0651, - ("0", "+"): 0x0652, - ("a", "S"): 0x0670, - ("p", "+"): 0x067E, - ("v", "+"): 0x06A4, - ("g", "f"): 0x06AF, - ("0", "a"): 0x06F0, - ("1", "a"): 0x06F1, - ("2", "a"): 0x06F2, - ("3", "a"): 0x06F3, - ("4", "a"): 0x06F4, - ("5", "a"): 0x06F5, - ("6", "a"): 0x06F6, - ("7", "a"): 0x06F7, - ("8", "a"): 0x06F8, - ("9", "a"): 0x06F9, - ("B", "."): 0x1E02, - ("b", "."): 0x1E03, - ("B", "_"): 0x1E06, - ("b", "_"): 0x1E07, - ("D", "."): 0x1E0A, - ("d", "."): 0x1E0B, - ("D", "_"): 0x1E0E, - ("d", "_"): 0x1E0F, - ("D", ","): 0x1E10, - ("d", ","): 0x1E11, - ("F", "."): 0x1E1E, - ("f", "."): 0x1E1F, - ("G", "-"): 0x1E20, - ("g", "-"): 0x1E21, - ("H", "."): 0x1E22, - ("h", "."): 0x1E23, - ("H", ":"): 0x1E26, - ("h", ":"): 0x1E27, - ("H", ","): 0x1E28, - ("h", ","): 0x1E29, - ("K", "'"): 0x1E30, - ("k", "'"): 0x1E31, - ("K", "_"): 0x1E34, - ("k", "_"): 0x1E35, - ("L", "_"): 0x1E3A, - ("l", "_"): 0x1E3B, - ("M", "'"): 0x1E3E, - ("m", "'"): 0x1E3F, - ("M", "."): 0x1E40, - ("m", "."): 0x1E41, - ("N", "."): 0x1E44, - ("n", "."): 0x1E45, - ("N", "_"): 0x1E48, - ("n", "_"): 0x1E49, - ("P", "'"): 0x1E54, - ("p", "'"): 0x1E55, - ("P", "."): 0x1E56, - ("p", "."): 0x1E57, - ("R", "."): 0x1E58, - ("r", "."): 0x1E59, - ("R", "_"): 0x1E5E, - ("r", "_"): 0x1E5F, - ("S", "."): 0x1E60, - ("s", "."): 0x1E61, - ("T", "."): 0x1E6A, - ("t", "."): 0x1E6B, - ("T", "_"): 0x1E6E, - ("t", "_"): 0x1E6F, - ("V", "?"): 0x1E7C, - ("v", "?"): 0x1E7D, - ("W", "!"): 0x1E80, - ("w", "!"): 0x1E81, - ("W", "'"): 0x1E82, - ("w", "'"): 0x1E83, - ("W", ":"): 0x1E84, - ("w", ":"): 0x1E85, - ("W", "."): 0x1E86, - ("w", "."): 0x1E87, - ("X", "."): 0x1E8A, - ("x", "."): 0x1E8B, - ("X", ":"): 0x1E8C, - ("x", ":"): 0x1E8D, - ("Y", "."): 0x1E8E, - ("y", "."): 0x1E8F, - ("Z", ">"): 0x1E90, - ("z", ">"): 0x1E91, - ("Z", "_"): 0x1E94, - ("z", "_"): 0x1E95, - ("h", "_"): 0x1E96, - ("t", ":"): 0x1E97, - ("w", "0"): 0x1E98, - ("y", "0"): 0x1E99, - ("A", "2"): 0x1EA2, - ("a", "2"): 0x1EA3, - ("E", "2"): 0x1EBA, - ("e", "2"): 0x1EBB, - ("E", "?"): 0x1EBC, - ("e", "?"): 0x1EBD, - ("I", "2"): 0x1EC8, - ("i", "2"): 0x1EC9, - ("O", "2"): 0x1ECE, - ("o", "2"): 0x1ECF, - ("U", "2"): 0x1EE6, - ("u", "2"): 0x1EE7, - ("Y", "!"): 0x1EF2, - ("y", "!"): 0x1EF3, - ("Y", "2"): 0x1EF6, - ("y", "2"): 0x1EF7, - ("Y", "?"): 0x1EF8, - ("y", "?"): 0x1EF9, - (";", "'"): 0x1F00, - (",", "'"): 0x1F01, - (";", "!"): 0x1F02, - (",", "!"): 0x1F03, - ("?", ";"): 0x1F04, - ("?", ","): 0x1F05, - ("!", ":"): 0x1F06, - ("?", ":"): 0x1F07, - ("1", "N"): 0x2002, - ("1", "M"): 0x2003, - ("3", "M"): 0x2004, - ("4", "M"): 0x2005, - ("6", "M"): 0x2006, - ("1", "T"): 0x2009, - ("1", "H"): 0x200A, - ("-", "1"): 0x2010, - ("-", "N"): 0x2013, - ("-", "M"): 0x2014, - ("-", "3"): 0x2015, - ("!", "2"): 0x2016, - ("=", "2"): 0x2017, - ("'", "6"): 0x2018, - ("'", "9"): 0x2019, - (".", "9"): 0x201A, - ("9", "'"): 0x201B, - ('"', "6"): 0x201C, - ('"', "9"): 0x201D, - (":", "9"): 0x201E, - ("9", '"'): 0x201F, - ("/", "-"): 0x2020, - ("/", "="): 0x2021, - (".", "."): 0x2025, - ("%", "0"): 0x2030, - ("1", "'"): 0x2032, - ("2", "'"): 0x2033, - ("3", "'"): 0x2034, - ("1", '"'): 0x2035, - ("2", '"'): 0x2036, - ("3", '"'): 0x2037, - ("C", "a"): 0x2038, - ("<", "1"): 0x2039, - (">", "1"): 0x203A, - (":", "X"): 0x203B, - ("'", "-"): 0x203E, - ("/", "f"): 0x2044, - ("0", "S"): 0x2070, - ("4", "S"): 0x2074, - ("5", "S"): 0x2075, - ("6", "S"): 0x2076, - ("7", "S"): 0x2077, - ("8", "S"): 0x2078, - ("9", "S"): 0x2079, - ("+", "S"): 0x207A, - ("-", "S"): 0x207B, - ("=", "S"): 0x207C, - ("(", "S"): 0x207D, - (")", "S"): 0x207E, - ("n", "S"): 0x207F, - ("0", "s"): 0x2080, - ("1", "s"): 0x2081, - ("2", "s"): 0x2082, - ("3", "s"): 0x2083, - ("4", "s"): 0x2084, - ("5", "s"): 0x2085, - ("6", "s"): 0x2086, - ("7", "s"): 0x2087, - ("8", "s"): 0x2088, - ("9", "s"): 0x2089, - ("+", "s"): 0x208A, - ("-", "s"): 0x208B, - ("=", "s"): 0x208C, - ("(", "s"): 0x208D, - (")", "s"): 0x208E, - ("L", "i"): 0x20A4, - ("P", "t"): 0x20A7, - ("W", "="): 0x20A9, - ("=", "e"): 0x20AC, # euro - ("E", "u"): 0x20AC, # euro - ("=", "R"): 0x20BD, # rouble - ("=", "P"): 0x20BD, # rouble - ("o", "C"): 0x2103, - ("c", "o"): 0x2105, - ("o", "F"): 0x2109, - ("N", "0"): 0x2116, - ("P", "O"): 0x2117, - ("R", "x"): 0x211E, - ("S", "M"): 0x2120, - ("T", "M"): 0x2122, - ("O", "m"): 0x2126, - ("A", "O"): 0x212B, - ("1", "3"): 0x2153, - ("2", "3"): 0x2154, - ("1", "5"): 0x2155, - ("2", "5"): 0x2156, - ("3", "5"): 0x2157, - ("4", "5"): 0x2158, - ("1", "6"): 0x2159, - ("5", "6"): 0x215A, - ("1", "8"): 0x215B, - ("3", "8"): 0x215C, - ("5", "8"): 0x215D, - ("7", "8"): 0x215E, - ("1", "R"): 0x2160, - ("2", "R"): 0x2161, - ("3", "R"): 0x2162, - ("4", "R"): 0x2163, - ("5", "R"): 0x2164, - ("6", "R"): 0x2165, - ("7", "R"): 0x2166, - ("8", "R"): 0x2167, - ("9", "R"): 0x2168, - ("a", "R"): 0x2169, - ("b", "R"): 0x216A, - ("c", "R"): 0x216B, - ("1", "r"): 0x2170, - ("2", "r"): 0x2171, - ("3", "r"): 0x2172, - ("4", "r"): 0x2173, - ("5", "r"): 0x2174, - ("6", "r"): 0x2175, - ("7", "r"): 0x2176, - ("8", "r"): 0x2177, - ("9", "r"): 0x2178, - ("a", "r"): 0x2179, - ("b", "r"): 0x217A, - ("c", "r"): 0x217B, - ("<", "-"): 0x2190, - ("-", "!"): 0x2191, - ("-", ">"): 0x2192, - ("-", "v"): 0x2193, - ("<", ">"): 0x2194, - ("U", "D"): 0x2195, - ("<", "="): 0x21D0, - ("=", ">"): 0x21D2, - ("=", "="): 0x21D4, - ("F", "A"): 0x2200, - ("d", "P"): 0x2202, - ("T", "E"): 0x2203, - ("/", "0"): 0x2205, - ("D", "E"): 0x2206, - ("N", "B"): 0x2207, - ("(", "-"): 0x2208, - ("-", ")"): 0x220B, - ("*", "P"): 0x220F, - ("+", "Z"): 0x2211, - ("-", "2"): 0x2212, - ("-", "+"): 0x2213, - ("*", "-"): 0x2217, - ("O", "b"): 0x2218, - ("S", "b"): 0x2219, - ("R", "T"): 0x221A, - ("0", "("): 0x221D, - ("0", "0"): 0x221E, - ("-", "L"): 0x221F, - ("-", "V"): 0x2220, - ("P", "P"): 0x2225, - ("A", "N"): 0x2227, - ("O", "R"): 0x2228, - ("(", "U"): 0x2229, - (")", "U"): 0x222A, - ("I", "n"): 0x222B, - ("D", "I"): 0x222C, - ("I", "o"): 0x222E, - (".", ":"): 0x2234, - (":", "."): 0x2235, - (":", "R"): 0x2236, - (":", ":"): 0x2237, - ("?", "1"): 0x223C, - ("C", "G"): 0x223E, - ("?", "-"): 0x2243, - ("?", "="): 0x2245, - ("?", "2"): 0x2248, - ("=", "?"): 0x224C, - ("H", "I"): 0x2253, - ("!", "="): 0x2260, - ("=", "3"): 0x2261, - ("=", "<"): 0x2264, - (">", "="): 0x2265, - ("<", "*"): 0x226A, - ("*", ">"): 0x226B, - ("!", "<"): 0x226E, - ("!", ">"): 0x226F, - ("(", "C"): 0x2282, - (")", "C"): 0x2283, - ("(", "_"): 0x2286, - (")", "_"): 0x2287, - ("0", "."): 0x2299, - ("0", "2"): 0x229A, - ("-", "T"): 0x22A5, - (".", "P"): 0x22C5, - (":", "3"): 0x22EE, - (".", "3"): 0x22EF, - ("E", "h"): 0x2302, - ("<", "7"): 0x2308, - (">", "7"): 0x2309, - ("7", "<"): 0x230A, - ("7", ">"): 0x230B, - ("N", "I"): 0x2310, - ("(", "A"): 0x2312, - ("T", "R"): 0x2315, - ("I", "u"): 0x2320, - ("I", "l"): 0x2321, - ("<", "/"): 0x2329, - ("/", ">"): 0x232A, - ("V", "s"): 0x2423, - ("1", "h"): 0x2440, - ("3", "h"): 0x2441, - ("2", "h"): 0x2442, - ("4", "h"): 0x2443, - ("1", "j"): 0x2446, - ("2", "j"): 0x2447, - ("3", "j"): 0x2448, - ("4", "j"): 0x2449, - ("1", "."): 0x2488, - ("2", "."): 0x2489, - ("3", "."): 0x248A, - ("4", "."): 0x248B, - ("5", "."): 0x248C, - ("6", "."): 0x248D, - ("7", "."): 0x248E, - ("8", "."): 0x248F, - ("9", "."): 0x2490, - ("h", "h"): 0x2500, - ("H", "H"): 0x2501, - ("v", "v"): 0x2502, - ("V", "V"): 0x2503, - ("3", "-"): 0x2504, - ("3", "_"): 0x2505, - ("3", "!"): 0x2506, - ("3", "/"): 0x2507, - ("4", "-"): 0x2508, - ("4", "_"): 0x2509, - ("4", "!"): 0x250A, - ("4", "/"): 0x250B, - ("d", "r"): 0x250C, - ("d", "R"): 0x250D, - ("D", "r"): 0x250E, - ("D", "R"): 0x250F, - ("d", "l"): 0x2510, - ("d", "L"): 0x2511, - ("D", "l"): 0x2512, - ("L", "D"): 0x2513, - ("u", "r"): 0x2514, - ("u", "R"): 0x2515, - ("U", "r"): 0x2516, - ("U", "R"): 0x2517, - ("u", "l"): 0x2518, - ("u", "L"): 0x2519, - ("U", "l"): 0x251A, - ("U", "L"): 0x251B, - ("v", "r"): 0x251C, - ("v", "R"): 0x251D, - ("V", "r"): 0x2520, - ("V", "R"): 0x2523, - ("v", "l"): 0x2524, - ("v", "L"): 0x2525, - ("V", "l"): 0x2528, - ("V", "L"): 0x252B, - ("d", "h"): 0x252C, - ("d", "H"): 0x252F, - ("D", "h"): 0x2530, - ("D", "H"): 0x2533, - ("u", "h"): 0x2534, - ("u", "H"): 0x2537, - ("U", "h"): 0x2538, - ("U", "H"): 0x253B, - ("v", "h"): 0x253C, - ("v", "H"): 0x253F, - ("V", "h"): 0x2542, - ("V", "H"): 0x254B, - ("F", "D"): 0x2571, - ("B", "D"): 0x2572, - ("T", "B"): 0x2580, - ("L", "B"): 0x2584, - ("F", "B"): 0x2588, - ("l", "B"): 0x258C, - ("R", "B"): 0x2590, - (".", "S"): 0x2591, - (":", "S"): 0x2592, - ("?", "S"): 0x2593, - ("f", "S"): 0x25A0, - ("O", "S"): 0x25A1, - ("R", "O"): 0x25A2, - ("R", "r"): 0x25A3, - ("R", "F"): 0x25A4, - ("R", "Y"): 0x25A5, - ("R", "H"): 0x25A6, - ("R", "Z"): 0x25A7, - ("R", "K"): 0x25A8, - ("R", "X"): 0x25A9, - ("s", "B"): 0x25AA, - ("S", "R"): 0x25AC, - ("O", "r"): 0x25AD, - ("U", "T"): 0x25B2, - ("u", "T"): 0x25B3, - ("P", "R"): 0x25B6, - ("T", "r"): 0x25B7, - ("D", "t"): 0x25BC, - ("d", "T"): 0x25BD, - ("P", "L"): 0x25C0, - ("T", "l"): 0x25C1, - ("D", "b"): 0x25C6, - ("D", "w"): 0x25C7, - ("L", "Z"): 0x25CA, - ("0", "m"): 0x25CB, - ("0", "o"): 0x25CE, - ("0", "M"): 0x25CF, - ("0", "L"): 0x25D0, - ("0", "R"): 0x25D1, - ("S", "n"): 0x25D8, - ("I", "c"): 0x25D9, - ("F", "d"): 0x25E2, - ("B", "d"): 0x25E3, - ("*", "2"): 0x2605, - ("*", "1"): 0x2606, - ("<", "H"): 0x261C, - (">", "H"): 0x261E, - ("0", "u"): 0x263A, - ("0", "U"): 0x263B, - ("S", "U"): 0x263C, - ("F", "m"): 0x2640, - ("M", "l"): 0x2642, - ("c", "S"): 0x2660, - ("c", "H"): 0x2661, - ("c", "D"): 0x2662, - ("c", "C"): 0x2663, - ("M", "d"): 0x2669, - ("M", "8"): 0x266A, - ("M", "2"): 0x266B, - ("M", "b"): 0x266D, - ("M", "x"): 0x266E, - ("M", "X"): 0x266F, - ("O", "K"): 0x2713, - ("X", "X"): 0x2717, - ("-", "X"): 0x2720, - ("I", "S"): 0x3000, - (",", "_"): 0x3001, - (".", "_"): 0x3002, - ("+", '"'): 0x3003, - ("+", "_"): 0x3004, - ("*", "_"): 0x3005, - (";", "_"): 0x3006, - ("0", "_"): 0x3007, - ("<", "+"): 0x300A, - (">", "+"): 0x300B, - ("<", "'"): 0x300C, - (">", "'"): 0x300D, - ("<", '"'): 0x300E, - (">", '"'): 0x300F, - ("(", '"'): 0x3010, - (")", '"'): 0x3011, - ("=", "T"): 0x3012, - ("=", "_"): 0x3013, - ("(", "'"): 0x3014, - (")", "'"): 0x3015, - ("(", "I"): 0x3016, - (")", "I"): 0x3017, - ("-", "?"): 0x301C, - ("A", "5"): 0x3041, - ("a", "5"): 0x3042, - ("I", "5"): 0x3043, - ("i", "5"): 0x3044, - ("U", "5"): 0x3045, - ("u", "5"): 0x3046, - ("E", "5"): 0x3047, - ("e", "5"): 0x3048, - ("O", "5"): 0x3049, - ("o", "5"): 0x304A, - ("k", "a"): 0x304B, - ("g", "a"): 0x304C, - ("k", "i"): 0x304D, - ("g", "i"): 0x304E, - ("k", "u"): 0x304F, - ("g", "u"): 0x3050, - ("k", "e"): 0x3051, - ("g", "e"): 0x3052, - ("k", "o"): 0x3053, - ("g", "o"): 0x3054, - ("s", "a"): 0x3055, - ("z", "a"): 0x3056, - ("s", "i"): 0x3057, - ("z", "i"): 0x3058, - ("s", "u"): 0x3059, - ("z", "u"): 0x305A, - ("s", "e"): 0x305B, - ("z", "e"): 0x305C, - ("s", "o"): 0x305D, - ("z", "o"): 0x305E, - ("t", "a"): 0x305F, - ("d", "a"): 0x3060, - ("t", "i"): 0x3061, - ("d", "i"): 0x3062, - ("t", "U"): 0x3063, - ("t", "u"): 0x3064, - ("d", "u"): 0x3065, - ("t", "e"): 0x3066, - ("d", "e"): 0x3067, - ("t", "o"): 0x3068, - ("d", "o"): 0x3069, - ("n", "a"): 0x306A, - ("n", "i"): 0x306B, - ("n", "u"): 0x306C, - ("n", "e"): 0x306D, - ("n", "o"): 0x306E, - ("h", "a"): 0x306F, - ("b", "a"): 0x3070, - ("p", "a"): 0x3071, - ("h", "i"): 0x3072, - ("b", "i"): 0x3073, - ("p", "i"): 0x3074, - ("h", "u"): 0x3075, - ("b", "u"): 0x3076, - ("p", "u"): 0x3077, - ("h", "e"): 0x3078, - ("b", "e"): 0x3079, - ("p", "e"): 0x307A, - ("h", "o"): 0x307B, - ("b", "o"): 0x307C, - ("p", "o"): 0x307D, - ("m", "a"): 0x307E, - ("m", "i"): 0x307F, - ("m", "u"): 0x3080, - ("m", "e"): 0x3081, - ("m", "o"): 0x3082, - ("y", "A"): 0x3083, - ("y", "a"): 0x3084, - ("y", "U"): 0x3085, - ("y", "u"): 0x3086, - ("y", "O"): 0x3087, - ("y", "o"): 0x3088, - ("r", "a"): 0x3089, - ("r", "i"): 0x308A, - ("r", "u"): 0x308B, - ("r", "e"): 0x308C, - ("r", "o"): 0x308D, - ("w", "A"): 0x308E, - ("w", "a"): 0x308F, - ("w", "i"): 0x3090, - ("w", "e"): 0x3091, - ("w", "o"): 0x3092, - ("n", "5"): 0x3093, - ("v", "u"): 0x3094, - ('"', "5"): 0x309B, - ("0", "5"): 0x309C, - ("*", "5"): 0x309D, - ("+", "5"): 0x309E, - ("a", "6"): 0x30A1, - ("A", "6"): 0x30A2, - ("i", "6"): 0x30A3, - ("I", "6"): 0x30A4, - ("u", "6"): 0x30A5, - ("U", "6"): 0x30A6, - ("e", "6"): 0x30A7, - ("E", "6"): 0x30A8, - ("o", "6"): 0x30A9, - ("O", "6"): 0x30AA, - ("K", "a"): 0x30AB, - ("G", "a"): 0x30AC, - ("K", "i"): 0x30AD, - ("G", "i"): 0x30AE, - ("K", "u"): 0x30AF, - ("G", "u"): 0x30B0, - ("K", "e"): 0x30B1, - ("G", "e"): 0x30B2, - ("K", "o"): 0x30B3, - ("G", "o"): 0x30B4, - ("S", "a"): 0x30B5, - ("Z", "a"): 0x30B6, - ("S", "i"): 0x30B7, - ("Z", "i"): 0x30B8, - ("S", "u"): 0x30B9, - ("Z", "u"): 0x30BA, - ("S", "e"): 0x30BB, - ("Z", "e"): 0x30BC, - ("S", "o"): 0x30BD, - ("Z", "o"): 0x30BE, - ("T", "a"): 0x30BF, - ("D", "a"): 0x30C0, - ("T", "i"): 0x30C1, - ("D", "i"): 0x30C2, - ("T", "U"): 0x30C3, - ("T", "u"): 0x30C4, - ("D", "u"): 0x30C5, - ("T", "e"): 0x30C6, - ("D", "e"): 0x30C7, - ("T", "o"): 0x30C8, - ("D", "o"): 0x30C9, - ("N", "a"): 0x30CA, - ("N", "i"): 0x30CB, - ("N", "u"): 0x30CC, - ("N", "e"): 0x30CD, - ("N", "o"): 0x30CE, - ("H", "a"): 0x30CF, - ("B", "a"): 0x30D0, - ("P", "a"): 0x30D1, - ("H", "i"): 0x30D2, - ("B", "i"): 0x30D3, - ("P", "i"): 0x30D4, - ("H", "u"): 0x30D5, - ("B", "u"): 0x30D6, - ("P", "u"): 0x30D7, - ("H", "e"): 0x30D8, - ("B", "e"): 0x30D9, - ("P", "e"): 0x30DA, - ("H", "o"): 0x30DB, - ("B", "o"): 0x30DC, - ("P", "o"): 0x30DD, - ("M", "a"): 0x30DE, - ("M", "i"): 0x30DF, - ("M", "u"): 0x30E0, - ("M", "e"): 0x30E1, - ("M", "o"): 0x30E2, - ("Y", "A"): 0x30E3, - ("Y", "a"): 0x30E4, - ("Y", "U"): 0x30E5, - ("Y", "u"): 0x30E6, - ("Y", "O"): 0x30E7, - ("Y", "o"): 0x30E8, - ("R", "a"): 0x30E9, - ("R", "i"): 0x30EA, - ("R", "u"): 0x30EB, - ("R", "e"): 0x30EC, - ("R", "o"): 0x30ED, - ("W", "A"): 0x30EE, - ("W", "a"): 0x30EF, - ("W", "i"): 0x30F0, - ("W", "e"): 0x30F1, - ("W", "o"): 0x30F2, - ("N", "6"): 0x30F3, - ("V", "u"): 0x30F4, - ("K", "A"): 0x30F5, - ("K", "E"): 0x30F6, - ("V", "a"): 0x30F7, - ("V", "i"): 0x30F8, - ("V", "e"): 0x30F9, - ("V", "o"): 0x30FA, - (".", "6"): 0x30FB, - ("-", "6"): 0x30FC, - ("*", "6"): 0x30FD, - ("+", "6"): 0x30FE, - ("b", "4"): 0x3105, - ("p", "4"): 0x3106, - ("m", "4"): 0x3107, - ("f", "4"): 0x3108, - ("d", "4"): 0x3109, - ("t", "4"): 0x310A, - ("n", "4"): 0x310B, - ("l", "4"): 0x310C, - ("g", "4"): 0x310D, - ("k", "4"): 0x310E, - ("h", "4"): 0x310F, - ("j", "4"): 0x3110, - ("q", "4"): 0x3111, - ("x", "4"): 0x3112, - ("z", "h"): 0x3113, - ("c", "h"): 0x3114, - ("s", "h"): 0x3115, - ("r", "4"): 0x3116, - ("z", "4"): 0x3117, - ("c", "4"): 0x3118, - ("s", "4"): 0x3119, - ("a", "4"): 0x311A, - ("o", "4"): 0x311B, - ("e", "4"): 0x311C, - ("a", "i"): 0x311E, - ("e", "i"): 0x311F, - ("a", "u"): 0x3120, - ("o", "u"): 0x3121, - ("a", "n"): 0x3122, - ("e", "n"): 0x3123, - ("a", "N"): 0x3124, - ("e", "N"): 0x3125, - ("e", "r"): 0x3126, - ("i", "4"): 0x3127, - ("u", "4"): 0x3128, - ("i", "u"): 0x3129, - ("v", "4"): 0x312A, - ("n", "G"): 0x312B, - ("g", "n"): 0x312C, - ("1", "c"): 0x3220, - ("2", "c"): 0x3221, - ("3", "c"): 0x3222, - ("4", "c"): 0x3223, - ("5", "c"): 0x3224, - ("6", "c"): 0x3225, - ("7", "c"): 0x3226, - ("8", "c"): 0x3227, - ("9", "c"): 0x3228, - # code points 0xe000 - 0xefff excluded, they have no assigned - # characters, only used in proposals. - ("f", "f"): 0xFB00, - ("f", "i"): 0xFB01, - ("f", "l"): 0xFB02, - ("f", "t"): 0xFB05, - ("s", "t"): 0xFB06, - # Vim 5.x compatible digraphs that don't conflict with the above - ("~", "!"): 161, - ("c", "|"): 162, - ("$", "$"): 163, - ("o", "x"): 164, # currency symbol in ISO 8859-1 - ("Y", "-"): 165, - ("|", "|"): 166, - ("c", "O"): 169, - ("-", ","): 172, - ("-", "="): 175, - ("~", "o"): 176, - ("2", "2"): 178, - ("3", "3"): 179, - ("p", "p"): 182, - ("~", "."): 183, - ("1", "1"): 185, - ("~", "?"): 191, - ("A", "`"): 192, - ("A", "^"): 194, - ("A", "~"): 195, - ("A", '"'): 196, - ("A", "@"): 197, - ("E", "`"): 200, - ("E", "^"): 202, - ("E", '"'): 203, - ("I", "`"): 204, - ("I", "^"): 206, - ("I", '"'): 207, - ("N", "~"): 209, - ("O", "`"): 210, - ("O", "^"): 212, - ("O", "~"): 213, - ("/", "\\"): 215, # multiplication symbol in ISO 8859-1 - ("U", "`"): 217, - ("U", "^"): 219, - ("I", "p"): 222, - ("a", "`"): 224, - ("a", "^"): 226, - ("a", "~"): 227, - ("a", '"'): 228, - ("a", "@"): 229, - ("e", "`"): 232, - ("e", "^"): 234, - ("e", '"'): 235, - ("i", "`"): 236, - ("i", "^"): 238, - ("n", "~"): 241, - ("o", "`"): 242, - ("o", "^"): 244, - ("o", "~"): 245, - ("u", "`"): 249, - ("u", "^"): 251, - ("y", '"'): 255, -} diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/emacs_state.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/emacs_state.py deleted file mode 100644 index 4c996224a07..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/emacs_state.py +++ /dev/null @@ -1,36 +0,0 @@ -from typing import List, Optional - -from .key_processor import KeyPress - -__all__ = [ - "EmacsState", -] - - -class EmacsState: - """ - Mutable class to hold Emacs specific state. - """ - - 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 - - def reset(self) -> None: - self.current_recording = None - - @property - def is_recording(self) -> bool: - "Tell whether we are recording a macro." - return self.current_recording is not None - - def start_macro(self) -> None: - "Start recording macro." - self.current_recording = [] - - def end_macro(self) -> None: - "End recording macro." - self.macro = self.current_recording - self.current_recording = None diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/key_bindings.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/key_bindings.py deleted file mode 100644 index 03bc79ef01d..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/key_bindings.py +++ /dev/null @@ -1,672 +0,0 @@ -""" -Key bindings registry. - -A `KeyBindings` object is a container that holds a list of key bindings. It has a -very efficient internal data structure for checking which key bindings apply -for a pressed key. - -Typical usage:: - - kb = KeyBindings() - - @kb.add(Keys.ControlX, Keys.ControlC, filter=INSERT) - def handler(event): - # Handle ControlX-ControlC key sequence. - pass - -It is also possible to combine multiple KeyBindings objects. We do this in the -default key bindings. There are some KeyBindings objects that contain the Emacs -bindings, while others contain the Vi bindings. They are merged together using -`merge_key_bindings`. - -We also have a `ConditionalKeyBindings` object that can enable/disable a group of -key bindings at once. - - -It is also possible to add a filter to a function, before a key binding has -been assigned, through the `key_binding` decorator.:: - - # First define a key handler with the `filter`. - @key_binding(filter=condition) - def my_key_binding(event): - ... - - # Later, add it to the key bindings. - kb.add(Keys.A, my_key_binding) -""" -from abc import ABCMeta, abstractmethod, abstractproperty -from inspect import isawaitable -from typing import ( - TYPE_CHECKING, - Awaitable, - Callable, - Hashable, - List, - Optional, - Sequence, - Tuple, - TypeVar, - Union, - cast, -) - -from prompt_toolkit.cache import SimpleCache -from prompt_toolkit.filters import FilterOrBool, Never, to_filter -from prompt_toolkit.keys import KEY_ALIASES, Keys - -if TYPE_CHECKING: - # Avoid circular imports. - from .key_processor import KeyPressEvent - - # The only two return values for a mouse hander (and key bindings) are - # `None` and `NotImplemented`. For the type checker it's best to annotate - # this as `object`. (The consumer never expects a more specific instance: - # checking for NotImplemented can be done using `is NotImplemented`.) - NotImplementedOrNone = object - # Other non-working options are: - # * Optional[Literal[NotImplemented]] - # --> Doesn't work, Literal can't take an Any. - # * None - # --> Doesn't work. We can't assign the result of a function that - # returns `None` to a variable. - # * Any - # --> Works, but too broad. - - -__all__ = [ - "NotImplementedOrNone", - "Binding", - "KeyBindingsBase", - "KeyBindings", - "ConditionalKeyBindings", - "merge_key_bindings", - "DynamicKeyBindings", - "GlobalOnlyKeyBindings", -] - -# Key bindings can be regular functions or coroutines. -# In both cases, if they return `NotImplemented`, the UI won't be invalidated. -# This is mainly used in case of mouse move events, to prevent excessive -# repainting during mouse move events. -KeyHandlerCallable = Callable[ - ["KeyPressEvent"], Union["NotImplementedOrNone", Awaitable["NotImplementedOrNone"]] -] - - -class Binding: - """ - Key binding: (key sequence + handler + filter). - (Immutable binding class.) - - :param record_in_macro: When True, don't record this key binding when a - macro is recorded. - """ - - def __init__( - self, - keys: Tuple[Union[Keys, str], ...], - handler: KeyHandlerCallable, - filter: FilterOrBool = True, - eager: FilterOrBool = False, - is_global: FilterOrBool = False, - save_before: Callable[["KeyPressEvent"], bool] = (lambda e: True), - record_in_macro: FilterOrBool = True, - ) -> None: - self.keys = keys - self.handler = handler - self.filter = to_filter(filter) - self.eager = to_filter(eager) - self.is_global = to_filter(is_global) - self.save_before = save_before - self.record_in_macro = to_filter(record_in_macro) - - def call(self, event: "KeyPressEvent") -> None: - result = self.handler(event) - - # If the handler is a coroutine, create an asyncio task. - if isawaitable(result): - awaitable = cast(Awaitable["NotImplementedOrNone"], result) - - async def bg_task() -> None: - result = await awaitable - if result != NotImplemented: - event.app.invalidate() - - event.app.create_background_task(bg_task()) - - elif result != NotImplemented: - event.app.invalidate() - - def __repr__(self) -> str: - return "{}(keys={!r}, handler={!r})".format( - self.__class__.__name__, - self.keys, - self.handler, - ) - - -# Sequence of keys presses. -KeysTuple = Tuple[Union[Keys, str], ...] - - -class KeyBindingsBase(metaclass=ABCMeta): - """ - Interface for a KeyBindings. - """ - - @abstractproperty - def _version(self) -> Hashable: - """ - For cache invalidation. - This should increase every time that - something changes. - """ - return 0 - - @abstractmethod - 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 - called, for checking it.) - - :param keys: tuple of keys. - """ - return [] - - @abstractmethod - 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 - longer than `keys`. And like `get_bindings_for_keys`, it also includes - inactive bindings.) - - :param keys: tuple of keys. - """ - return [] - - @abstractproperty - def bindings(self) -> List[Binding]: - """ - List of `Binding` objects. - (These need to be exposed, so that `KeyBindings` objects can be merged - together.) - """ - return [] - - # `add` and `remove` don't have to be part of this interface. - - -T = TypeVar("T", bound=Union[KeyHandlerCallable, Binding]) - - -class KeyBindings(KeyBindingsBase): - """ - A container for a set of key bindings. - - Example usage:: - - kb = KeyBindings() - - @kb.add('c-t') - def _(event): - print('Control-T pressed') - - @kb.add('c-a', 'c-b') - def _(event): - print('Control-A pressed, followed by Control-B') - - @kb.add('c-x', filter=is_searching) - def _(event): - print('Control-X pressed') # Works only if we are searching. - - """ - - def __init__(self) -> None: - self._bindings: List[Binding] = [] - self._get_bindings_for_keys_cache: SimpleCache[ - KeysTuple, List[Binding] - ] = SimpleCache(maxsize=10000) - self._get_bindings_starting_with_keys_cache: SimpleCache[ - KeysTuple, List[Binding] - ] = SimpleCache(maxsize=1000) - self.__version = 0 # For cache invalidation. - - def _clear_cache(self) -> None: - self.__version += 1 - self._get_bindings_for_keys_cache.clear() - self._get_bindings_starting_with_keys_cache.clear() - - @property - def bindings(self) -> List[Binding]: - return self._bindings - - @property - def _version(self) -> Hashable: - return self.__version - - def add( - self, - *keys: Union[Keys, str], - filter: FilterOrBool = True, - eager: FilterOrBool = False, - is_global: FilterOrBool = False, - save_before: Callable[["KeyPressEvent"], bool] = (lambda e: True), - record_in_macro: FilterOrBool = True, - ) -> Callable[[T], T]: - """ - Decorator for adding a key bindings. - - :param filter: :class:`~prompt_toolkit.filters.Filter` to determine - when this key binding is active. - :param eager: :class:`~prompt_toolkit.filters.Filter` or `bool`. - When True, ignore potential longer matches when this key binding is - hit. E.g. when there is an active eager key binding for Ctrl-X, - execute the handler immediately and ignore the key binding for - Ctrl-X Ctrl-E of which it is a prefix. - :param is_global: When this key bindings is added to a `Container` or - `Control`, make it a global (always active) binding. - :param save_before: Callable that takes an `Event` and returns True if - we should save the current buffer, before handling the event. - (That's the default.) - :param record_in_macro: Record these key bindings when a macro is - being recorded. (True by default.) - """ - assert keys - - keys = tuple(_parse_key(k) for k in keys) - - if isinstance(filter, Never): - # When a filter is Never, it will always stay disabled, so in that - # case don't bother putting it in the key bindings. It will slow - # down every key press otherwise. - def decorator(func: T) -> T: - return func - - else: - - def decorator(func: T) -> T: - if isinstance(func, Binding): - # We're adding an existing Binding object. - self.bindings.append( - Binding( - keys, - func.handler, - filter=func.filter & to_filter(filter), - eager=to_filter(eager) | func.eager, - is_global=to_filter(is_global) | func.is_global, - save_before=func.save_before, - record_in_macro=func.record_in_macro, - ) - ) - else: - self.bindings.append( - Binding( - keys, - cast(KeyHandlerCallable, func), - filter=filter, - eager=eager, - is_global=is_global, - save_before=save_before, - record_in_macro=record_in_macro, - ) - ) - self._clear_cache() - - return func - - return decorator - - def remove(self, *args: Union[Keys, str, KeyHandlerCallable]) -> None: - """ - Remove a key binding. - - This expects either a function that was given to `add` method as - parameter or a sequence of key bindings. - - Raises `ValueError` when no bindings was found. - - Usage:: - - remove(handler) # Pass handler. - remove('c-x', 'c-a') # Or pass the key bindings. - """ - found = False - - if callable(args[0]): - assert len(args) == 1 - function = args[0] - - # Remove the given function. - for b in self.bindings: - if b.handler == function: - self.bindings.remove(b) - found = True - - else: - assert len(args) > 0 - args = cast(Tuple[Union[Keys, str]], args) - - # Remove this sequence of key bindings. - keys = tuple(_parse_key(k) for k in args) - - for b in self.bindings: - if b.keys == keys: - self.bindings.remove(b) - found = True - - if found: - self._clear_cache() - else: - # No key binding found for this function. Raise ValueError. - raise ValueError(f"Binding not found: {function!r}") - - # For backwards-compatibility. - add_binding = add - remove_binding = remove - - 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 - called, for checking it.) - - :param keys: tuple of keys. - """ - - def get() -> List[Binding]: - result: List[Tuple[int, Binding]] = [] - - for b in self.bindings: - if len(keys) == len(b.keys): - match = True - any_count = 0 - - for i, j in zip(b.keys, keys): - if i != j and i != Keys.Any: - match = False - break - - if i == Keys.Any: - any_count += 1 - - if match: - result.append((any_count, b)) - - # Place bindings that have more 'Any' occurrences in them at the end. - result = sorted(result, key=lambda item: -item[0]) - - return [item[1] for item in result] - - return self._get_bindings_for_keys_cache.get(keys, get) - - 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 - longer than `keys`. And like `get_bindings_for_keys`, it also includes - inactive bindings.) - - :param keys: tuple of keys. - """ - - def get() -> List[Binding]: - result = [] - for b in self.bindings: - if len(keys) < len(b.keys): - match = True - for i, j in zip(b.keys, keys): - if i != j and i != Keys.Any: - match = False - break - if match: - result.append(b) - return result - - return self._get_bindings_starting_with_keys_cache.get(keys, get) - - -def _parse_key(key: Union[Keys, str]) -> Union[str, Keys]: - """ - Replace key by alias and verify whether it's a valid one. - """ - # Already a parse key? -> Return it. - if isinstance(key, Keys): - return key - - # Lookup aliases. - key = KEY_ALIASES.get(key, key) - - # Replace 'space' by ' ' - if key == "space": - key = " " - - # Return as `Key` object when it's a special key. - try: - return Keys(key) - except ValueError: - pass - - # Final validation. - if len(key) != 1: - raise ValueError(f"Invalid key: {key}") - - return key - - -def key_binding( - filter: FilterOrBool = True, - eager: FilterOrBool = False, - is_global: FilterOrBool = False, - save_before: Callable[["KeyPressEvent"], bool] = (lambda event: True), - record_in_macro: FilterOrBool = True, -) -> Callable[[KeyHandlerCallable], Binding]: - """ - Decorator that turn a function into a `Binding` object. This can be added - to a `KeyBindings` object when a key binding is assigned. - """ - assert save_before is None or callable(save_before) - - filter = to_filter(filter) - eager = to_filter(eager) - is_global = to_filter(is_global) - save_before = save_before - record_in_macro = to_filter(record_in_macro) - keys = () - - def decorator(function: KeyHandlerCallable) -> Binding: - return Binding( - keys, - function, - filter=filter, - eager=eager, - is_global=is_global, - save_before=save_before, - record_in_macro=record_in_macro, - ) - - return decorator - - -class _Proxy(KeyBindingsBase): - """ - Common part for ConditionalKeyBindings and _MergedKeyBindings. - """ - - def __init__(self) -> None: - # `KeyBindings` to be synchronized with all the others. - self._bindings2: KeyBindingsBase = KeyBindings() - self._last_version: Hashable = () - - def _update_cache(self) -> None: - """ - If `self._last_version` is outdated, then this should update - the version and `self._bindings2`. - """ - raise NotImplementedError - - # Proxy methods to self._bindings2. - - @property - def bindings(self) -> List[Binding]: - self._update_cache() - return self._bindings2.bindings - - @property - def _version(self) -> Hashable: - self._update_cache() - return self._last_version - - 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]: - self._update_cache() - return self._bindings2.get_bindings_starting_with_keys(keys) - - -class ConditionalKeyBindings(_Proxy): - """ - Wraps around a `KeyBindings`. Disable/enable all the key bindings according to - the given (additional) filter.:: - - @Condition - def setting_is_true(): - return True # or False - - registry = ConditionalKeyBindings(key_bindings, setting_is_true) - - When new key bindings are added to this object. They are also - enable/disabled according to the given `filter`. - - :param registries: List of :class:`.KeyBindings` objects. - :param filter: :class:`~prompt_toolkit.filters.Filter` object. - """ - - def __init__( - self, key_bindings: KeyBindingsBase, filter: FilterOrBool = True - ) -> None: - - _Proxy.__init__(self) - - self.key_bindings = key_bindings - self.filter = to_filter(filter) - - def _update_cache(self) -> None: - "If the original key bindings was changed. Update our copy version." - expected_version = self.key_bindings._version - - if self._last_version != expected_version: - bindings2 = KeyBindings() - - # Copy all bindings from `self.key_bindings`, adding our condition. - for b in self.key_bindings.bindings: - bindings2.bindings.append( - Binding( - keys=b.keys, - handler=b.handler, - filter=self.filter & b.filter, - eager=b.eager, - is_global=b.is_global, - save_before=b.save_before, - record_in_macro=b.record_in_macro, - ) - ) - - self._bindings2 = bindings2 - self._last_version = expected_version - - -class _MergedKeyBindings(_Proxy): - """ - Merge multiple registries of key bindings into one. - - This class acts as a proxy to multiple :class:`.KeyBindings` objects, but - behaves as if this is just one bigger :class:`.KeyBindings`. - - :param registries: List of :class:`.KeyBindings` objects. - """ - - def __init__(self, registries: Sequence[KeyBindingsBase]) -> None: - _Proxy.__init__(self) - self.registries = registries - - def _update_cache(self) -> None: - """ - If one of the original registries was changed. Update our merged - version. - """ - expected_version = tuple(r._version for r in self.registries) - - if self._last_version != expected_version: - bindings2 = KeyBindings() - - for reg in self.registries: - bindings2.bindings.extend(reg.bindings) - - self._bindings2 = bindings2 - self._last_version = expected_version - - -def merge_key_bindings(bindings: Sequence[KeyBindingsBase]) -> _MergedKeyBindings: - """ - Merge multiple :class:`.Keybinding` objects together. - - Usage:: - - bindings = merge_key_bindings([bindings1, bindings2, ...]) - """ - return _MergedKeyBindings(bindings) - - -class DynamicKeyBindings(_Proxy): - """ - KeyBindings class that can dynamically returns any KeyBindings. - - :param get_key_bindings: Callable that returns a :class:`.KeyBindings` instance. - """ - - def __init__( - self, get_key_bindings: Callable[[], Optional[KeyBindingsBase]] - ) -> None: - self.get_key_bindings = get_key_bindings - self.__version = 0 - self._last_child_version = None - self._dummy = KeyBindings() # Empty key bindings. - - def _update_cache(self) -> None: - key_bindings = self.get_key_bindings() or self._dummy - assert isinstance(key_bindings, KeyBindingsBase) - version = id(key_bindings), key_bindings._version - - self._bindings2 = key_bindings - self._last_version = version - - -class GlobalOnlyKeyBindings(_Proxy): - """ - Wrapper around a :class:`.KeyBindings` object that only exposes the global - key bindings. - """ - - def __init__(self, key_bindings: KeyBindingsBase) -> None: - _Proxy.__init__(self) - self.key_bindings = key_bindings - - def _update_cache(self) -> None: - """ - If one of the original registries was changed. Update our merged - version. - """ - expected_version = self.key_bindings._version - - if self._last_version != expected_version: - bindings2 = KeyBindings() - - for b in self.key_bindings.bindings: - if b.is_global(): - bindings2.bindings.append(b) - - self._bindings2 = bindings2 - self._last_version = expected_version diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/key_processor.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/key_processor.py deleted file mode 100644 index 6fdd5191791..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/key_processor.py +++ /dev/null @@ -1,528 +0,0 @@ -""" -An :class:`~.KeyProcessor` receives callbacks for the keystrokes parsed from -the input in the :class:`~prompt_toolkit.inputstream.InputStream` instance. - -The `KeyProcessor` will according to the implemented keybindings call the -correct callbacks when new key presses are feed through `feed`. -""" -import weakref -from asyncio import Task, sleep -from collections import deque -from typing import TYPE_CHECKING, Any, Deque, Generator, List, Optional, Union - -from prompt_toolkit.application.current import get_app -from prompt_toolkit.enums import EditingMode -from prompt_toolkit.filters.app import vi_navigation_mode -from prompt_toolkit.keys import Keys -from prompt_toolkit.utils import Event - -from .key_bindings import Binding, KeyBindingsBase - -if TYPE_CHECKING: - from prompt_toolkit.application import Application - from prompt_toolkit.buffer import Buffer - - -__all__ = [ - "KeyProcessor", - "KeyPress", - "KeyPressEvent", -] - - -class KeyPress: - """ - :param key: A `Keys` instance or text (one character). - :param data: The received string on stdin. (Often vt100 escape codes.) - """ - - def __init__(self, key: Union[Keys, str], data: Optional[str] = None) -> None: - assert isinstance(key, Keys) or len(key) == 1 - - if data is None: - if isinstance(key, Keys): - data = key.value - else: - data = key # 'key' is a one character string. - - self.key = key - self.data = data - - def __repr__(self) -> str: - return f"{self.__class__.__name__}(key={self.key!r}, data={self.data!r})" - - def __eq__(self, other: object) -> bool: - if not isinstance(other, KeyPress): - return False - return self.key == other.key and self.data == other.data - - -""" -Helper object to indicate flush operation in the KeyProcessor. -NOTE: the implementation is very similar to the VT100 parser. -""" -_Flush = KeyPress("?", data="_Flush") - - -class KeyProcessor: - """ - Statemachine that receives :class:`KeyPress` instances and according to the - key bindings in the given :class:`KeyBindings`, calls the matching handlers. - - :: - - p = KeyProcessor(key_bindings) - - # Send keys into the processor. - p.feed(KeyPress(Keys.ControlX, '\x18')) - p.feed(KeyPress(Keys.ControlC, '\x03') - - # Process all the keys in the queue. - p.process_keys() - - # Now the ControlX-ControlC callback will be called if this sequence is - # registered in the key bindings. - - :param key_bindings: `KeyBindingsBase` instance. - """ - - def __init__(self, key_bindings: KeyBindingsBase) -> None: - self._bindings = key_bindings - - self.before_key_press = Event(self) - self.after_key_press = Event(self) - - self._flush_wait_task: Optional[Task[None]] = None - - self.reset() - - def reset(self) -> None: - self._previous_key_sequence: List[KeyPress] = [] - self._previous_handler: Optional[Binding] = 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] = [] - - #: Readline argument (for repetition of commands.) - #: https://www.gnu.org/software/bash/manual/html_node/Readline-Arguments.html - self.arg: Optional[str] = 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]: - """ - For a list of :class:`KeyPress` instances. Give the matching handlers - that would handle this. - """ - keys = tuple(k.key for k in key_presses) - - # 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: - """ - For a list of :class:`KeyPress` instances. Return True if there is any - handler that is bound to a suffix of this keys. - """ - keys = tuple(k.key for k in key_presses) - - # Get the filters for all the key bindings that have a longer match. - # Note that we transform it into a `set`, because we don't care about - # the actual bindings and executing it more than once doesn't make - # sense. (Many key bindings share the same filter.) - filters = { - b.filter for b in self._bindings.get_bindings_starting_with_keys(keys) - } - - # When any key binding is active, return True. - return any(f() for f in filters) - - def _process(self) -> Generator[None, KeyPress, None]: - """ - Coroutine implementing the key match algorithm. Key strokes are sent - into this generator, and it calls the appropriate handlers. - """ - buffer = self.key_buffer - retry = False - - while True: - flush = False - - if retry: - retry = False - else: - key = yield - if key is _Flush: - flush = True - else: - buffer.append(key) - - # If we have some key presses, check for matches. - if buffer: - matches = self._get_matches(buffer) - - if flush: - is_prefix_of_longer_match = False - else: - is_prefix_of_longer_match = self._is_prefix_of_longer_match(buffer) - - # When eager matches were found, give priority to them and also - # ignore all the longer matches. - eager_matches = [m for m in matches if m.eager()] - - if eager_matches: - matches = eager_matches - is_prefix_of_longer_match = False - - # Exact matches found, call handler. - if not is_prefix_of_longer_match and matches: - self._call_handler(matches[-1], key_sequence=buffer[:]) - del buffer[:] # Keep reference. - - # No match found. - elif not is_prefix_of_longer_match and not matches: - retry = True - found = False - - # Loop over the input, try longest match first and shift. - for i in range(len(buffer), 0, -1): - matches = self._get_matches(buffer[:i]) - if matches: - self._call_handler(matches[-1], key_sequence=buffer[:i]) - del buffer[:i] - found = True - break - - if not found: - del buffer[:1] - - def feed(self, key_press: KeyPress, first: bool = False) -> None: - """ - Add a new :class:`KeyPress` to the input queue. - (Don't forget to call `process_keys` in order to process the queue.) - - :param first: If true, insert before everything else. - """ - if first: - self.input_queue.appendleft(key_press) - else: - self.input_queue.append(key_press) - - def feed_multiple(self, key_presses: List[KeyPress], first: bool = False) -> None: - """ - :param first: If true, insert before everything else. - """ - if first: - self.input_queue.extendleft(reversed(key_presses)) - else: - self.input_queue.extend(key_presses) - - def process_keys(self) -> None: - """ - Process all the keys in the `input_queue`. - (To be called after `feed`.) - - Note: because of the `feed`/`process_keys` separation, it is - possible to call `feed` from inside a key binding. - This function keeps looping until the queue is empty. - """ - app = get_app() - - def not_empty() -> bool: - # When the application result is set, stop processing keys. (E.g. - # if ENTER was received, followed by a few additional key strokes, - # leave the other keys in the queue.) - if app.is_done: - # But if there are still CPRResponse keys in the queue, these - # need to be processed. - return any(k for k in self.input_queue if k.key == Keys.CPRResponse) - else: - return bool(self.input_queue) - - def get_next() -> KeyPress: - if app.is_done: - # Only process CPR responses. Everything else is typeahead. - cpr = [k for k in self.input_queue if k.key == Keys.CPRResponse][0] - self.input_queue.remove(cpr) - return cpr - else: - return self.input_queue.popleft() - - is_flush = False - - while not_empty(): - # Process next key. - key_press = get_next() - - is_flush = key_press is _Flush - is_cpr = key_press.key == Keys.CPRResponse - - if not is_flush and not is_cpr: - self.before_key_press.fire() - - try: - self._process_coroutine.send(key_press) - except Exception: - # If for some reason something goes wrong in the parser, (maybe - # an exception was raised) restart the processor for next time. - self.reset() - self.empty_queue() - raise - - if not is_flush and not is_cpr: - self.after_key_press.fire() - - # Skip timeout if the last key was flush. - if not is_flush: - self._start_timeout() - - def empty_queue(self) -> List[KeyPress]: - """ - Empty the input queue. Return the unprocessed input. - """ - key_presses = list(self.input_queue) - self.input_queue.clear() - - # Filter out CPRs. We don't want to return these. - 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: - app = get_app() - was_recording_emacs = app.emacs_state.is_recording - was_recording_vi = bool(app.vi_state.recording_register) - was_temporary_navigation_mode = app.vi_state.temporary_navigation_mode - arg = self.arg - self.arg = None - - event = KeyPressEvent( - weakref.ref(self), - arg=arg, - key_sequence=key_sequence, - previous_key_sequence=self._previous_key_sequence, - is_repeat=(handler == self._previous_handler), - ) - - # Save the state of the current buffer. - if handler.save_before(event): - event.app.current_buffer.save_to_undo_stack() - - # Call handler. - from prompt_toolkit.buffer import EditReadOnlyBuffer - - try: - handler.call(event) - self._fix_vi_cursor_position(event) - - except EditReadOnlyBuffer: - # When a key binding does an attempt to change a buffer which is - # read-only, we can ignore that. We sound a bell and go on. - app.output.bell() - - if was_temporary_navigation_mode: - self._leave_vi_temp_navigation_mode(event) - - self._previous_key_sequence = key_sequence - self._previous_handler = handler - - # Record the key sequence in our macro. (Only if we're in macro mode - # before and after executing the key.) - if handler.record_in_macro(): - if app.emacs_state.is_recording and was_recording_emacs: - recording = app.emacs_state.current_recording - if recording is not None: # Should always be true, given that - # `was_recording_emacs` is set. - recording.extend(key_sequence) - - if app.vi_state.recording_register and was_recording_vi: - for k in key_sequence: - app.vi_state.current_recording += k.data - - 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 - an empty line.) - """ - app = event.app - buff = app.current_buffer - preferred_column = buff.preferred_column - - if ( - vi_navigation_mode() - and buff.document.is_cursor_at_the_end_of_line - and len(buff.document.current_line) > 0 - ): - buff.cursor_position -= 1 - - # Set the preferred_column for arrow up/down again. - # (This was cleared after changing the cursor position.) - buff.preferred_column = preferred_column - - 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. - """ - app = event.app - - if app.editing_mode == EditingMode.VI: - # Not waiting for a text object and no argument has been given. - if app.vi_state.operator_func is None and self.arg is None: - app.vi_state.temporary_navigation_mode = False - - def _start_timeout(self) -> None: - """ - Start auto flush timeout. Similar to Vim's `timeoutlen` option. - - Start a background coroutine with a timer. When this timeout expires - and no key was pressed in the meantime, we flush all data in the queue - and call the appropriate key binding handlers. - """ - app = get_app() - timeout = app.timeoutlen - - if timeout is None: - return - - async def wait() -> None: - "Wait for timeout." - # This sleep can be cancelled. In that case we don't flush. - await sleep(timeout) - - if len(self.key_buffer) > 0: - # (No keys pressed in the meantime.) - flush_keys() - - def flush_keys() -> None: - "Flush keys." - self.feed(_Flush) - self.process_keys() - - # Automatically flush keys. - if self._flush_wait_task: - self._flush_wait_task.cancel() - self._flush_wait_task = app.create_background_task(wait()) - - def send_sigint(self) -> None: - """ - Send SIGINT. Immediately call the SIGINT key handler. - """ - self.feed(KeyPress(key=Keys.SIGINT), first=True) - self.process_keys() - - -class KeyPressEvent: - """ - Key press event, delivered to key bindings. - - :param key_processor_ref: Weak reference to the `KeyProcessor`. - :param arg: Repetition argument. - :param key_sequence: List of `KeyPress` instances. - :param previouskey_sequence: Previous list of `KeyPress` instances. - :param is_repeat: True when the previous event was delivered to the same handler. - """ - - def __init__( - self, - key_processor_ref: "weakref.ReferenceType[KeyProcessor]", - arg: Optional[str], - key_sequence: List[KeyPress], - previous_key_sequence: List[KeyPress], - is_repeat: bool, - ) -> None: - - self._key_processor_ref = key_processor_ref - self.key_sequence = key_sequence - self.previous_key_sequence = previous_key_sequence - - #: True when the previous key sequence was handled by the same handler. - self.is_repeat = is_repeat - - self._arg = arg - self._app = get_app() - - def __repr__(self) -> str: - return "KeyPressEvent(arg={!r}, key_sequence={!r}, is_repeat={!r})".format( - self.arg, - self.key_sequence, - self.is_repeat, - ) - - @property - def data(self) -> str: - return self.key_sequence[-1].data - - @property - def key_processor(self) -> KeyProcessor: - processor = self._key_processor_ref() - if processor is None: - raise Exception("KeyProcessor was lost. This should not happen.") - return processor - - @property - def app(self) -> "Application[Any]": - """ - The current `Application` object. - """ - return self._app - - @property - def current_buffer(self) -> "Buffer": - """ - The current buffer. - """ - return self.app.current_buffer - - @property - def arg(self) -> int: - """ - Repetition argument. - """ - if self._arg == "-": - return -1 - - result = int(self._arg or 1) - - # Don't exceed a million. - if int(result) >= 1000000: - result = 1 - - return result - - @property - def arg_present(self) -> bool: - """ - True if repetition argument was explicitly provided. - """ - return self._arg is not None - - def append_to_arg_count(self, data: str) -> None: - """ - Add digit to the input argument. - - :param data: the typed digit as string - """ - assert data in "-0123456789" - current = self._arg - - if data == "-": - assert current is None or current == "-" - result = data - elif current is None: - result = data - else: - result = f"{current}{data}" - - self.key_processor.arg = result - - @property - def cli(self) -> "Application[Any]": - "For backward-compatibility." - return self.app diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/vi_state.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/vi_state.py deleted file mode 100644 index 10593a82e62..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/key_binding/vi_state.py +++ /dev/null @@ -1,107 +0,0 @@ -from enum import Enum -from typing import TYPE_CHECKING, Callable, Dict, Optional - -from prompt_toolkit.clipboard import ClipboardData - -if TYPE_CHECKING: - from .key_bindings.vi import TextObject - from .key_processor import KeyPressEvent - -__all__ = [ - "InputMode", - "CharacterFind", - "ViState", -] - - -class InputMode(str, Enum): - value: str - - INSERT = "vi-insert" - INSERT_MULTIPLE = "vi-insert-multiple" - NAVIGATION = "vi-navigation" # Normal mode. - REPLACE = "vi-replace" - REPLACE_SINGLE = "vi-replace-single" - - -class CharacterFind: - def __init__(self, character: str, backwards: bool = False) -> None: - self.character = character - self.backwards = backwards - - -class ViState: - """ - Mutable class to hold the state of the Vi navigation. - """ - - 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 - - # 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 - - #: Named registers. Maps register name (e.g. 'a') to - #: :class:`ClipboardData` instances. - 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.) - - #: When true, make ~ act as an operator. - self.tilde_operator = False - - #: Register in which we are recording a macro. - #: `None` when not recording anything. - # 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.current_recording: str = "" - - # Temporary navigation (normal) mode. - # This happens when control-o has been pressed in insert or replace - # mode. The user can now do one navigation action and we'll return back - # to insert/replace. - self.temporary_navigation_mode = False - - @property - def input_mode(self) -> InputMode: - "Get `InputMode`." - return self.__input_mode - - @input_mode.setter - def input_mode(self, value: InputMode) -> None: - "Set `InputMode`." - if value == InputMode.NAVIGATION: - self.waiting_for_digraph = False - self.operator_func = None - self.operator_arg = None - - self.__input_mode = value - - def reset(self) -> None: - """ - Reset state, go back to the given mode. INSERT by default. - """ - # Go back to insert mode. - self.input_mode = InputMode.INSERT - - self.waiting_for_digraph = False - self.operator_func = None - self.operator_arg = None - - # Reset recording state. - self.recording_register = None - self.current_recording = "" diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/keys.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/keys.py deleted file mode 100644 index e10ba9d9212..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/keys.py +++ /dev/null @@ -1,221 +0,0 @@ -from enum import Enum -from typing import Dict, List - -__all__ = [ - "Keys", - "ALL_KEYS", -] - - -class Keys(str, Enum): - """ - List of keys for use in key bindings. - - Note that this is an "StrEnum", all values can be compared against - strings. - """ - - value: str - - Escape = "escape" # Also Control-[ - ShiftEscape = "s-escape" - - ControlAt = "c-@" # Also Control-Space. - - ControlA = "c-a" - ControlB = "c-b" - ControlC = "c-c" - ControlD = "c-d" - ControlE = "c-e" - ControlF = "c-f" - ControlG = "c-g" - ControlH = "c-h" - ControlI = "c-i" # Tab - ControlJ = "c-j" # Newline - ControlK = "c-k" - ControlL = "c-l" - ControlM = "c-m" # Carriage return - ControlN = "c-n" - ControlO = "c-o" - ControlP = "c-p" - ControlQ = "c-q" - ControlR = "c-r" - ControlS = "c-s" - ControlT = "c-t" - ControlU = "c-u" - ControlV = "c-v" - ControlW = "c-w" - ControlX = "c-x" - ControlY = "c-y" - ControlZ = "c-z" - - Control1 = "c-1" - Control2 = "c-2" - Control3 = "c-3" - Control4 = "c-4" - Control5 = "c-5" - Control6 = "c-6" - Control7 = "c-7" - Control8 = "c-8" - Control9 = "c-9" - Control0 = "c-0" - - ControlShift1 = "c-s-1" - ControlShift2 = "c-s-2" - ControlShift3 = "c-s-3" - ControlShift4 = "c-s-4" - ControlShift5 = "c-s-5" - ControlShift6 = "c-s-6" - ControlShift7 = "c-s-7" - ControlShift8 = "c-s-8" - ControlShift9 = "c-s-9" - ControlShift0 = "c-s-0" - - ControlBackslash = "c-\\" - ControlSquareClose = "c-]" - ControlCircumflex = "c-^" - ControlUnderscore = "c-_" - - Left = "left" - Right = "right" - Up = "up" - Down = "down" - Home = "home" - End = "end" - Insert = "insert" - Delete = "delete" - PageUp = "pageup" - PageDown = "pagedown" - - ControlLeft = "c-left" - ControlRight = "c-right" - ControlUp = "c-up" - ControlDown = "c-down" - ControlHome = "c-home" - ControlEnd = "c-end" - ControlInsert = "c-insert" - ControlDelete = "c-delete" - ControlPageUp = "c-pageup" - ControlPageDown = "c-pagedown" - - ShiftLeft = "s-left" - ShiftRight = "s-right" - ShiftUp = "s-up" - ShiftDown = "s-down" - ShiftHome = "s-home" - ShiftEnd = "s-end" - ShiftInsert = "s-insert" - ShiftDelete = "s-delete" - ShiftPageUp = "s-pageup" - ShiftPageDown = "s-pagedown" - - ControlShiftLeft = "c-s-left" - ControlShiftRight = "c-s-right" - ControlShiftUp = "c-s-up" - ControlShiftDown = "c-s-down" - ControlShiftHome = "c-s-home" - ControlShiftEnd = "c-s-end" - ControlShiftInsert = "c-s-insert" - ControlShiftDelete = "c-s-delete" - ControlShiftPageUp = "c-s-pageup" - ControlShiftPageDown = "c-s-pagedown" - - BackTab = "s-tab" # shift + tab - - F1 = "f1" - F2 = "f2" - F3 = "f3" - F4 = "f4" - F5 = "f5" - F6 = "f6" - F7 = "f7" - F8 = "f8" - F9 = "f9" - F10 = "f10" - F11 = "f11" - F12 = "f12" - F13 = "f13" - F14 = "f14" - F15 = "f15" - F16 = "f16" - F17 = "f17" - F18 = "f18" - F19 = "f19" - F20 = "f20" - F21 = "f21" - F22 = "f22" - F23 = "f23" - F24 = "f24" - - ControlF1 = "c-f1" - ControlF2 = "c-f2" - ControlF3 = "c-f3" - ControlF4 = "c-f4" - ControlF5 = "c-f5" - ControlF6 = "c-f6" - ControlF7 = "c-f7" - ControlF8 = "c-f8" - ControlF9 = "c-f9" - ControlF10 = "c-f10" - ControlF11 = "c-f11" - ControlF12 = "c-f12" - ControlF13 = "c-f13" - ControlF14 = "c-f14" - ControlF15 = "c-f15" - ControlF16 = "c-f16" - ControlF17 = "c-f17" - ControlF18 = "c-f18" - ControlF19 = "c-f19" - ControlF20 = "c-f20" - ControlF21 = "c-f21" - ControlF22 = "c-f22" - ControlF23 = "c-f23" - ControlF24 = "c-f24" - - # Matches any key. - Any = "<any>" - - # Special. - ScrollUp = "<scroll-up>" - ScrollDown = "<scroll-down>" - - CPRResponse = "<cursor-position-response>" - Vt100MouseEvent = "<vt100-mouse-event>" - WindowsMouseEvent = "<windows-mouse-event>" - BracketedPaste = "<bracketed-paste>" - - SIGINT = "<sigint>" - - # For internal use: key which is ignored. - # (The key binding for this key should not do anything.) - Ignore = "<ignore>" - - # Some 'Key' aliases (for backwards-compatibility). - ControlSpace = ControlAt - Tab = ControlI - Enter = ControlM - Backspace = ControlH - - # ShiftControl was renamed to ControlShift in - # 888fcb6fa4efea0de8333177e1bbc792f3ff3c24 (20 Feb 2020). - ShiftControlLeft = ControlShiftLeft - ShiftControlRight = ControlShiftRight - ShiftControlHome = ControlShiftHome - ShiftControlEnd = ControlShiftEnd - - -ALL_KEYS: List[str] = [k.value for k in Keys] - - -# Aliases. -KEY_ALIASES: Dict[str, str] = { - "backspace": "c-h", - "c-space": "c-@", - "enter": "c-m", - "tab": "c-i", - # ShiftControl was renamed to ControlShift. - "s-c-left": "c-s-left", - "s-c-right": "c-s-right", - "s-c-home": "c-s-home", - "s-c-end": "c-s-end", -} diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/__init__.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/__init__.py deleted file mode 100644 index 6669da5d7a8..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/__init__.py +++ /dev/null @@ -1,144 +0,0 @@ -""" -Command line layout definitions -------------------------------- - -The layout of a command line interface is defined by a Container instance. -There are two main groups of classes here. Containers and controls: - -- A container can contain other containers or controls, it can have multiple - children and it decides about the dimensions. -- A control is responsible for rendering the actual content to a screen. - A control can propose some dimensions, but it's the container who decides - about the dimensions -- or when the control consumes more space -- which part - of the control will be visible. - - -Container classes:: - - - Container (Abstract base class) - |- HSplit (Horizontal split) - |- VSplit (Vertical split) - |- FloatContainer (Container which can also contain menus and other floats) - `- Window (Container which contains one actual control - -Control classes:: - - - UIControl (Abstract base class) - |- FormattedTextControl (Renders formatted text, or a simple list of text fragments) - `- BufferControl (Renders an input buffer.) - - -Usually, you end up wrapping every control inside a `Window` object, because -that's the only way to render it in a layout. - -There are some prepared toolbars which are ready to use:: - -- SystemToolbar (Shows the 'system' input buffer, for entering system commands.) -- ArgToolbar (Shows the input 'arg', for repetition of input commands.) -- SearchToolbar (Shows the 'search' input buffer, for incremental search.) -- CompletionsToolbar (Shows the completions of the current buffer.) -- ValidationToolbar (Shows validation errors of the current buffer.) - -And one prepared menu: - -- CompletionsMenu - -""" -from .containers import ( - AnyContainer, - ColorColumn, - ConditionalContainer, - Container, - DynamicContainer, - Float, - FloatContainer, - HorizontalAlign, - HSplit, - ScrollOffsets, - VerticalAlign, - VSplit, - Window, - WindowAlign, - WindowRenderInfo, - is_container, - to_container, - to_window, -) -from .controls import ( - BufferControl, - DummyControl, - FormattedTextControl, - SearchBufferControl, - UIContent, - UIControl, -) -from .dimension import ( - AnyDimension, - D, - Dimension, - is_dimension, - max_layout_dimensions, - sum_layout_dimensions, - to_dimension, -) -from .layout import InvalidLayoutError, Layout, walk -from .margins import ( - ConditionalMargin, - Margin, - NumberedMargin, - PromptMargin, - ScrollbarMargin, -) -from .menus import CompletionsMenu, MultiColumnCompletionsMenu -from .scrollable_pane import ScrollablePane - -__all__ = [ - # Layout. - "Layout", - "InvalidLayoutError", - "walk", - # Dimensions. - "AnyDimension", - "Dimension", - "D", - "sum_layout_dimensions", - "max_layout_dimensions", - "to_dimension", - "is_dimension", - # Containers. - "AnyContainer", - "Container", - "HorizontalAlign", - "VerticalAlign", - "HSplit", - "VSplit", - "FloatContainer", - "Float", - "WindowAlign", - "Window", - "WindowRenderInfo", - "ConditionalContainer", - "ScrollOffsets", - "ColorColumn", - "to_container", - "to_window", - "is_container", - "DynamicContainer", - "ScrollablePane", - # Controls. - "BufferControl", - "SearchBufferControl", - "DummyControl", - "FormattedTextControl", - "UIControl", - "UIContent", - # Margins. - "Margin", - "NumberedMargin", - "ScrollbarMargin", - "ConditionalMargin", - "PromptMargin", - # Menus. - "CompletionsMenu", - "MultiColumnCompletionsMenu", -] diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/containers.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/containers.py deleted file mode 100644 index 03f9e7d2485..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/containers.py +++ /dev/null @@ -1,2757 +0,0 @@ -""" -Container for the layout. -(Containers can contain other containers or user interface controls.) -""" -from abc import ABCMeta, abstractmethod -from enum import Enum -from functools import partial -from typing import ( - TYPE_CHECKING, - Callable, - Dict, - List, - Optional, - Sequence, - Tuple, - Union, - cast, -) - -from prompt_toolkit.application.current import get_app -from prompt_toolkit.cache import SimpleCache -from prompt_toolkit.data_structures import Point -from prompt_toolkit.filters import ( - FilterOrBool, - emacs_insert_mode, - to_filter, - vi_insert_mode, -) -from prompt_toolkit.formatted_text import ( - AnyFormattedText, - StyleAndTextTuples, - to_formatted_text, -) -from prompt_toolkit.formatted_text.utils import ( - fragment_list_to_text, - fragment_list_width, -) -from prompt_toolkit.key_binding import KeyBindingsBase -from prompt_toolkit.mouse_events import MouseEvent, MouseEventType -from prompt_toolkit.utils import get_cwidth, take_using_weights, to_int, to_str - -from .controls import ( - DummyControl, - FormattedTextControl, - GetLinePrefixCallable, - UIContent, - UIControl, -) -from .dimension import ( - AnyDimension, - Dimension, - max_layout_dimensions, - sum_layout_dimensions, - to_dimension, -) -from .margins import Margin -from .mouse_handlers import MouseHandlers -from .screen import _CHAR_CACHE, Screen, WritePosition -from .utils import explode_text_fragments - -if TYPE_CHECKING: - from typing_extensions import Protocol, TypeGuard - - from prompt_toolkit.key_binding.key_bindings import NotImplementedOrNone - - -__all__ = [ - "AnyContainer", - "Container", - "HorizontalAlign", - "VerticalAlign", - "HSplit", - "VSplit", - "FloatContainer", - "Float", - "WindowAlign", - "Window", - "WindowRenderInfo", - "ConditionalContainer", - "ScrollOffsets", - "ColorColumn", - "to_container", - "to_window", - "is_container", - "DynamicContainer", -] - - -class Container(metaclass=ABCMeta): - """ - Base class for user interface layout. - """ - - @abstractmethod - def reset(self) -> None: - """ - Reset the state of this container and all the children. - (E.g. reset scroll offsets, etc...) - """ - - @abstractmethod - def preferred_width(self, max_available_width: int) -> Dimension: - """ - Return a :class:`~prompt_toolkit.layout.Dimension` that represents the - desired width for this container. - """ - - @abstractmethod - def preferred_height(self, width: int, max_available_height: int) -> Dimension: - """ - Return a :class:`~prompt_toolkit.layout.Dimension` that represents the - desired height for this container. - """ - - @abstractmethod - def write_to_screen( - self, - screen: Screen, - mouse_handlers: MouseHandlers, - write_position: WritePosition, - parent_style: str, - erase_bg: bool, - z_index: Optional[int], - ) -> None: - """ - Write the actual content to the screen. - - :param screen: :class:`~prompt_toolkit.layout.screen.Screen` - :param mouse_handlers: :class:`~prompt_toolkit.layout.mouse_handlers.MouseHandlers`. - :param parent_style: Style string to pass to the :class:`.Window` - object. This will be applied to all content of the windows. - :class:`.VSplit` and :class:`.HSplit` can use it to pass their - style down to the windows that they contain. - :param z_index: Used for propagating z_index from parent to child. - """ - - def is_modal(self) -> bool: - """ - When this container is modal, key bindings from parent containers are - not taken into account if a user control in this container is focused. - """ - return False - - def get_key_bindings(self) -> Optional[KeyBindingsBase]: - """ - Returns a :class:`.KeyBindings` object. These bindings become active when any - user control in this container has the focus, except if any containers - between this container and the focused user control is modal. - """ - return None - - @abstractmethod - def get_children(self) -> List["Container"]: - """ - Return the list of child :class:`.Container` objects. - """ - return [] - - -if TYPE_CHECKING: - - class MagicContainer(Protocol): - """ - Any object that implements ``__pt_container__`` represents a container. - """ - - def __pt_container__(self) -> "AnyContainer": - ... - - -AnyContainer = Union[Container, "MagicContainer"] - - -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... ")]) - ) - - -class VerticalAlign(Enum): - "Alignment for `HSplit`." - TOP = "TOP" - CENTER = "CENTER" - BOTTOM = "BOTTOM" - JUSTIFY = "JUSTIFY" - - -class HorizontalAlign(Enum): - "Alignment for `VSplit`." - LEFT = "LEFT" - CENTER = "CENTER" - RIGHT = "RIGHT" - JUSTIFY = "JUSTIFY" - - -class _Split(Container): - """ - The common parts of `VSplit` and `HSplit`. - """ - - def __init__( - self, - children: Sequence[AnyContainer], - window_too_small: Optional[Container] = None, - padding: AnyDimension = Dimension.exact(0), - padding_char: Optional[str] = None, - padding_style: str = "", - width: AnyDimension = None, - height: AnyDimension = None, - z_index: Optional[int] = None, - modal: bool = False, - key_bindings: Optional[KeyBindingsBase] = None, - style: Union[str, Callable[[], str]] = "", - ) -> None: - - self.children = [to_container(c) for c in children] - self.window_too_small = window_too_small or _window_too_small() - self.padding = padding - self.padding_char = padding_char - self.padding_style = padding_style - - self.width = width - self.height = height - self.z_index = z_index - - self.modal = modal - self.key_bindings = key_bindings - self.style = style - - def is_modal(self) -> bool: - return self.modal - - def get_key_bindings(self) -> Optional[KeyBindingsBase]: - return self.key_bindings - - def get_children(self) -> List[Container]: - return self.children - - -class HSplit(_Split): - """ - Several layouts, one stacked above/under the other. :: - - +--------------------+ - | | - +--------------------+ - | | - +--------------------+ - - By default, this doesn't display a horizontal line between the children, - but if this is something you need, then create a HSplit as follows:: - - HSplit(children=[ ... ], padding_char='-', - padding=1, padding_style='#ffff00') - - :param children: List of child :class:`.Container` objects. - :param window_too_small: A :class:`.Container` object that is displayed if - there is not enough space for all the children. By default, this is a - "Window too small" message. - :param align: `VerticalAlign` value. - :param width: When given, use this width instead of looking at the children. - :param height: When given, use this height instead of looking at the children. - :param z_index: (int or None) When specified, this can be used to bring - element in front of floating elements. `None` means: inherit from parent. - :param style: A style string. - :param modal: ``True`` or ``False``. - :param key_bindings: ``None`` or a :class:`.KeyBindings` object. - - :param padding: (`Dimension` or int), size to be used for the padding. - :param padding_char: Character to be used for filling in the padding. - :param padding_style: Style to applied to the padding. - """ - - def __init__( - self, - children: Sequence[AnyContainer], - window_too_small: Optional[Container] = None, - align: VerticalAlign = VerticalAlign.JUSTIFY, - padding: AnyDimension = 0, - padding_char: Optional[str] = None, - padding_style: str = "", - width: AnyDimension = None, - height: AnyDimension = None, - z_index: Optional[int] = None, - modal: bool = False, - key_bindings: Optional[KeyBindingsBase] = None, - style: Union[str, Callable[[], str]] = "", - ) -> None: - - super().__init__( - children=children, - window_too_small=window_too_small, - padding=padding, - padding_char=padding_char, - padding_style=padding_style, - width=width, - height=height, - z_index=z_index, - modal=modal, - key_bindings=key_bindings, - style=style, - ) - - self.align = align - - self._children_cache: SimpleCache[ - Tuple[Container, ...], List[Container] - ] = SimpleCache(maxsize=1) - self._remaining_space_window = Window() # Dummy window. - - def preferred_width(self, max_available_width: int) -> Dimension: - if self.width is not None: - return to_dimension(self.width) - - if self.children: - dimensions = [c.preferred_width(max_available_width) for c in self.children] - return max_layout_dimensions(dimensions) - else: - return Dimension() - - def preferred_height(self, width: int, max_available_height: int) -> Dimension: - if self.height is not None: - return to_dimension(self.height) - - dimensions = [ - c.preferred_height(width, max_available_height) for c in self._all_children - ] - return sum_layout_dimensions(dimensions) - - def reset(self) -> None: - for c in self.children: - c.reset() - - @property - def _all_children(self) -> List[Container]: - """ - List of child objects, including padding. - """ - - def get() -> List[Container]: - result: List[Container] = [] - - # Padding Top. - if self.align in (VerticalAlign.CENTER, VerticalAlign.BOTTOM): - result.append(Window(width=Dimension(preferred=0))) - - # The children with padding. - for child in self.children: - result.append(child) - result.append( - Window( - height=self.padding, - char=self.padding_char, - style=self.padding_style, - ) - ) - if result: - result.pop() - - # Padding right. - if self.align in (VerticalAlign.CENTER, VerticalAlign.TOP): - result.append(Window(width=Dimension(preferred=0))) - - return result - - return self._children_cache.get(tuple(self.children), get) - - def write_to_screen( - self, - screen: Screen, - mouse_handlers: MouseHandlers, - write_position: WritePosition, - parent_style: str, - erase_bg: bool, - z_index: Optional[int], - ) -> None: - """ - Render the prompt to a `Screen` instance. - - :param screen: The :class:`~prompt_toolkit.layout.screen.Screen` class - to which the output has to be written. - """ - sizes = self._divide_heights(write_position) - style = parent_style + " " + to_str(self.style) - z_index = z_index if self.z_index is None else self.z_index - - if sizes is None: - self.window_too_small.write_to_screen( - screen, mouse_handlers, write_position, style, erase_bg, z_index - ) - else: - # - ypos = write_position.ypos - xpos = write_position.xpos - width = write_position.width - - # Draw child panes. - for s, c in zip(sizes, self._all_children): - c.write_to_screen( - screen, - mouse_handlers, - WritePosition(xpos, ypos, width, s), - style, - erase_bg, - z_index, - ) - ypos += s - - # Fill in the remaining space. This happens when a child control - # refuses to take more space and we don't have any padding. Adding a - # dummy child control for this (in `self._all_children`) is not - # desired, because in some situations, it would take more space, even - # when it's not required. This is required to apply the styling. - remaining_height = write_position.ypos + write_position.height - ypos - if remaining_height > 0: - self._remaining_space_window.write_to_screen( - screen, - mouse_handlers, - WritePosition(xpos, ypos, width, remaining_height), - style, - erase_bg, - z_index, - ) - - def _divide_heights(self, write_position: WritePosition) -> Optional[List[int]]: - """ - Return the heights for all rows. - Or None when there is not enough space. - """ - if not self.children: - return [] - - width = write_position.width - height = write_position.height - - # Calculate heights. - dimensions = [c.preferred_height(width, height) for c in self._all_children] - - # Sum dimensions - sum_dimensions = sum_layout_dimensions(dimensions) - - # If there is not enough space for both. - # Don't do anything. - if sum_dimensions.min > height: - return None - - # Find optimal sizes. (Start with minimal size, increase until we cover - # the whole height.) - sizes = [d.min for d in dimensions] - - child_generator = take_using_weights( - items=list(range(len(dimensions))), weights=[d.weight for d in dimensions] - ) - - i = next(child_generator) - - # Increase until we meet at least the 'preferred' size. - preferred_stop = min(height, sum_dimensions.preferred) - preferred_dimensions = [d.preferred for d in dimensions] - - while sum(sizes) < preferred_stop: - if sizes[i] < preferred_dimensions[i]: - sizes[i] += 1 - i = next(child_generator) - - # Increase until we use all the available space. (or until "max") - if not get_app().is_done: - max_stop = min(height, sum_dimensions.max) - max_dimensions = [d.max for d in dimensions] - - while sum(sizes) < max_stop: - if sizes[i] < max_dimensions[i]: - sizes[i] += 1 - i = next(child_generator) - - return sizes - - -class VSplit(_Split): - """ - Several layouts, one stacked left/right of the other. :: - - +---------+----------+ - | | | - | | | - +---------+----------+ - - By default, this doesn't display a vertical line between the children, but - if this is something you need, then create a HSplit as follows:: - - VSplit(children=[ ... ], padding_char='|', - padding=1, padding_style='#ffff00') - - :param children: List of child :class:`.Container` objects. - :param window_too_small: A :class:`.Container` object that is displayed if - there is not enough space for all the children. By default, this is a - "Window too small" message. - :param align: `HorizontalAlign` value. - :param width: When given, use this width instead of looking at the children. - :param height: When given, use this height instead of looking at the children. - :param z_index: (int or None) When specified, this can be used to bring - element in front of floating elements. `None` means: inherit from parent. - :param style: A style string. - :param modal: ``True`` or ``False``. - :param key_bindings: ``None`` or a :class:`.KeyBindings` object. - - :param padding: (`Dimension` or int), size to be used for the padding. - :param padding_char: Character to be used for filling in the padding. - :param padding_style: Style to applied to the padding. - """ - - def __init__( - self, - children: Sequence[AnyContainer], - window_too_small: Optional[Container] = None, - align: HorizontalAlign = HorizontalAlign.JUSTIFY, - padding: AnyDimension = 0, - padding_char: Optional[str] = None, - padding_style: str = "", - width: AnyDimension = None, - height: AnyDimension = None, - z_index: Optional[int] = None, - modal: bool = False, - key_bindings: Optional[KeyBindingsBase] = None, - style: Union[str, Callable[[], str]] = "", - ) -> None: - - super().__init__( - children=children, - window_too_small=window_too_small, - padding=padding, - padding_char=padding_char, - padding_style=padding_style, - width=width, - height=height, - z_index=z_index, - modal=modal, - key_bindings=key_bindings, - style=style, - ) - - self.align = align - - self._children_cache: SimpleCache[ - Tuple[Container, ...], List[Container] - ] = SimpleCache(maxsize=1) - self._remaining_space_window = Window() # Dummy window. - - def preferred_width(self, max_available_width: int) -> Dimension: - if self.width is not None: - return to_dimension(self.width) - - dimensions = [ - c.preferred_width(max_available_width) for c in self._all_children - ] - - return sum_layout_dimensions(dimensions) - - def preferred_height(self, width: int, max_available_height: int) -> Dimension: - if self.height is not None: - return to_dimension(self.height) - - # At the point where we want to calculate the heights, the widths have - # already been decided. So we can trust `width` to be the actual - # `width` that's going to be used for the rendering. So, - # `divide_widths` is supposed to use all of the available width. - # Using only the `preferred` width caused a bug where the reported - # height was more than required. (we had a `BufferControl` which did - # wrap lines because of the smaller width returned by `_divide_widths`. - - sizes = self._divide_widths(width) - children = self._all_children - - if sizes is None: - return Dimension() - else: - dimensions = [ - c.preferred_height(s, max_available_height) - for s, c in zip(sizes, children) - ] - return max_layout_dimensions(dimensions) - - def reset(self) -> None: - for c in self.children: - c.reset() - - @property - def _all_children(self) -> List[Container]: - """ - List of child objects, including padding. - """ - - def get() -> List[Container]: - result: List[Container] = [] - - # Padding left. - if self.align in (HorizontalAlign.CENTER, HorizontalAlign.RIGHT): - result.append(Window(width=Dimension(preferred=0))) - - # The children with padding. - for child in self.children: - result.append(child) - result.append( - Window( - width=self.padding, - char=self.padding_char, - style=self.padding_style, - ) - ) - if result: - result.pop() - - # Padding right. - if self.align in (HorizontalAlign.CENTER, HorizontalAlign.LEFT): - result.append(Window(width=Dimension(preferred=0))) - - return result - - return self._children_cache.get(tuple(self.children), get) - - def _divide_widths(self, width: int) -> Optional[List[int]]: - """ - Return the widths for all columns. - Or None when there is not enough space. - """ - children = self._all_children - - if not children: - return [] - - # Calculate widths. - dimensions = [c.preferred_width(width) for c in children] - preferred_dimensions = [d.preferred for d in dimensions] - - # Sum dimensions - sum_dimensions = sum_layout_dimensions(dimensions) - - # If there is not enough space for both. - # Don't do anything. - if sum_dimensions.min > width: - return None - - # Find optimal sizes. (Start with minimal size, increase until we cover - # the whole width.) - sizes = [d.min for d in dimensions] - - child_generator = take_using_weights( - items=list(range(len(dimensions))), weights=[d.weight for d in dimensions] - ) - - i = next(child_generator) - - # Increase until we meet at least the 'preferred' size. - preferred_stop = min(width, sum_dimensions.preferred) - - while sum(sizes) < preferred_stop: - if sizes[i] < preferred_dimensions[i]: - sizes[i] += 1 - i = next(child_generator) - - # Increase until we use all the available space. - max_dimensions = [d.max for d in dimensions] - max_stop = min(width, sum_dimensions.max) - - while sum(sizes) < max_stop: - if sizes[i] < max_dimensions[i]: - sizes[i] += 1 - i = next(child_generator) - - return sizes - - def write_to_screen( - self, - screen: Screen, - mouse_handlers: MouseHandlers, - write_position: WritePosition, - parent_style: str, - erase_bg: bool, - z_index: Optional[int], - ) -> None: - """ - Render the prompt to a `Screen` instance. - - :param screen: The :class:`~prompt_toolkit.layout.screen.Screen` class - to which the output has to be written. - """ - if not self.children: - return - - children = self._all_children - sizes = self._divide_widths(write_position.width) - style = parent_style + " " + to_str(self.style) - z_index = z_index if self.z_index is None else self.z_index - - # If there is not enough space. - if sizes is None: - self.window_too_small.write_to_screen( - screen, mouse_handlers, write_position, style, erase_bg, z_index - ) - return - - # Calculate heights, take the largest possible, but not larger than - # write_position.height. - heights = [ - child.preferred_height(width, write_position.height).preferred - for width, child in zip(sizes, children) - ] - height = max(write_position.height, min(write_position.height, max(heights))) - - # - ypos = write_position.ypos - xpos = write_position.xpos - - # Draw all child panes. - for s, c in zip(sizes, children): - c.write_to_screen( - screen, - mouse_handlers, - WritePosition(xpos, ypos, s, height), - style, - erase_bg, - z_index, - ) - xpos += s - - # Fill in the remaining space. This happens when a child control - # refuses to take more space and we don't have any padding. Adding a - # dummy child control for this (in `self._all_children`) is not - # desired, because in some situations, it would take more space, even - # when it's not required. This is required to apply the styling. - remaining_width = write_position.xpos + write_position.width - xpos - if remaining_width > 0: - self._remaining_space_window.write_to_screen( - screen, - mouse_handlers, - WritePosition(xpos, ypos, remaining_width, height), - style, - erase_bg, - z_index, - ) - - -class FloatContainer(Container): - """ - Container which can contain another container for the background, as well - as a list of floating containers on top of it. - - Example Usage:: - - FloatContainer(content=Window(...), - floats=[ - Float(xcursor=True, - ycursor=True, - content=CompletionsMenu(...)) - ]) - - :param z_index: (int or None) When specified, this can be used to bring - element in front of floating elements. `None` means: inherit from parent. - This is the z_index for the whole `Float` container as a whole. - """ - - def __init__( - self, - content: AnyContainer, - floats: List["Float"], - modal: bool = False, - key_bindings: Optional[KeyBindingsBase] = None, - style: Union[str, Callable[[], str]] = "", - z_index: Optional[int] = None, - ) -> None: - - self.content = to_container(content) - self.floats = floats - - self.modal = modal - self.key_bindings = key_bindings - self.style = style - self.z_index = z_index - - def reset(self) -> None: - self.content.reset() - - for f in self.floats: - f.content.reset() - - def preferred_width(self, max_available_width: int) -> Dimension: - return self.content.preferred_width(max_available_width) - - def preferred_height(self, width: int, max_available_height: int) -> Dimension: - """ - Return the preferred height of the float container. - (We don't care about the height of the floats, they should always fit - into the dimensions provided by the container.) - """ - return self.content.preferred_height(width, max_available_height) - - def write_to_screen( - self, - screen: Screen, - mouse_handlers: MouseHandlers, - write_position: WritePosition, - parent_style: str, - erase_bg: bool, - z_index: Optional[int], - ) -> None: - style = parent_style + " " + to_str(self.style) - z_index = z_index if self.z_index is None else self.z_index - - self.content.write_to_screen( - screen, mouse_handlers, write_position, style, erase_bg, z_index - ) - - for number, fl in enumerate(self.floats): - # z_index of a Float is computed by summing the z_index of the - # container and the `Float`. - new_z_index = (z_index or 0) + fl.z_index - style = parent_style + " " + to_str(self.style) - - # If the float that we have here, is positioned relative to the - # cursor position, but the Window that specifies the cursor - # position is not drawn yet, because it's a Float itself, we have - # to postpone this calculation. (This is a work-around, but good - # enough for now.) - postpone = fl.xcursor is not None or fl.ycursor is not None - - if postpone: - new_z_index = ( - number + 10**8 - ) # Draw as late as possible, but keep the order. - screen.draw_with_z_index( - z_index=new_z_index, - draw_func=partial( - self._draw_float, - fl, - screen, - mouse_handlers, - write_position, - style, - erase_bg, - new_z_index, - ), - ) - else: - self._draw_float( - fl, - screen, - mouse_handlers, - write_position, - style, - erase_bg, - new_z_index, - ) - - def _draw_float( - self, - fl: "Float", - screen: Screen, - mouse_handlers: MouseHandlers, - write_position: WritePosition, - style: str, - erase_bg: bool, - z_index: Optional[int], - ) -> None: - "Draw a single Float." - # When a menu_position was given, use this instead of the cursor - # position. (These cursor positions are absolute, translate again - # relative to the write_position.) - # Note: This should be inside the for-loop, because one float could - # set the cursor position to be used for the next one. - cpos = screen.get_menu_position( - fl.attach_to_window or get_app().layout.current_window - ) - cursor_position = Point( - x=cpos.x - write_position.xpos, y=cpos.y - write_position.ypos - ) - - fl_width = fl.get_width() - fl_height = fl.get_height() - width: int - height: int - xpos: int - ypos: int - - # Left & width given. - if fl.left is not None and fl_width is not None: - xpos = fl.left - width = fl_width - # Left & right given -> calculate width. - elif fl.left is not None and fl.right is not None: - xpos = fl.left - width = write_position.width - fl.left - fl.right - # Width & right given -> calculate left. - elif fl_width is not None and fl.right is not None: - xpos = write_position.width - fl.right - fl_width - width = fl_width - # Near x position of cursor. - elif fl.xcursor: - if fl_width is None: - width = fl.content.preferred_width(write_position.width).preferred - width = min(write_position.width, width) - else: - width = fl_width - - xpos = cursor_position.x - if xpos + width > write_position.width: - xpos = max(0, write_position.width - width) - # Only width given -> center horizontally. - elif fl_width: - xpos = int((write_position.width - fl_width) / 2) - width = fl_width - # Otherwise, take preferred width from float content. - else: - width = fl.content.preferred_width(write_position.width).preferred - - if fl.left is not None: - xpos = fl.left - elif fl.right is not None: - xpos = max(0, write_position.width - width - fl.right) - else: # Center horizontally. - xpos = max(0, int((write_position.width - width) / 2)) - - # Trim. - width = min(width, write_position.width - xpos) - - # Top & height given. - if fl.top is not None and fl_height is not None: - ypos = fl.top - height = fl_height - # Top & bottom given -> calculate height. - elif fl.top is not None and fl.bottom is not None: - ypos = fl.top - height = write_position.height - fl.top - fl.bottom - # Height & bottom given -> calculate top. - elif fl_height is not None and fl.bottom is not None: - ypos = write_position.height - fl_height - fl.bottom - height = fl_height - # Near cursor. - elif fl.ycursor: - ypos = cursor_position.y + (0 if fl.allow_cover_cursor else 1) - - if fl_height is None: - height = fl.content.preferred_height( - width, write_position.height - ).preferred - else: - height = fl_height - - # Reduce height if not enough space. (We can use the height - # when the content requires it.) - if height > write_position.height - ypos: - if write_position.height - ypos + 1 >= ypos: - # When the space below the cursor is more than - # the space above, just reduce the height. - height = write_position.height - ypos - else: - # Otherwise, fit the float above the cursor. - height = min(height, cursor_position.y) - ypos = cursor_position.y - height - - # Only height given -> center vertically. - elif fl_height: - ypos = int((write_position.height - fl_height) / 2) - height = fl_height - # Otherwise, take preferred height from content. - else: - height = fl.content.preferred_height(width, write_position.height).preferred - - if fl.top is not None: - ypos = fl.top - elif fl.bottom is not None: - ypos = max(0, write_position.height - height - fl.bottom) - else: # Center vertically. - ypos = max(0, int((write_position.height - height) / 2)) - - # Trim. - height = min(height, write_position.height - ypos) - - # Write float. - # (xpos and ypos can be negative: a float can be partially visible.) - if height > 0 and width > 0: - wp = WritePosition( - xpos=xpos + write_position.xpos, - ypos=ypos + write_position.ypos, - width=width, - height=height, - ) - - if not fl.hide_when_covering_content or self._area_is_empty(screen, wp): - fl.content.write_to_screen( - screen, - mouse_handlers, - wp, - style, - erase_bg=not fl.transparent(), - z_index=z_index, - ) - - def _area_is_empty(self, screen: Screen, write_position: WritePosition) -> bool: - """ - Return True when the area below the write position is still empty. - (For floats that should not hide content underneath.) - """ - wp = write_position - - for y in range(wp.ypos, wp.ypos + wp.height): - if y in screen.data_buffer: - row = screen.data_buffer[y] - - for x in range(wp.xpos, wp.xpos + wp.width): - c = row[x] - if c.char != " ": - return False - - return True - - def is_modal(self) -> bool: - return self.modal - - def get_key_bindings(self) -> Optional[KeyBindingsBase]: - return self.key_bindings - - def get_children(self) -> List[Container]: - children = [self.content] - children.extend(f.content for f in self.floats) - return children - - -class Float: - """ - Float for use in a :class:`.FloatContainer`. - Except for the `content` parameter, all other options are optional. - - :param content: :class:`.Container` instance. - - :param width: :class:`.Dimension` or callable which returns a :class:`.Dimension`. - :param height: :class:`.Dimension` or callable which returns a :class:`.Dimension`. - - :param left: Distance to the left edge of the :class:`.FloatContainer`. - :param right: Distance to the right edge of the :class:`.FloatContainer`. - :param top: Distance to the top of the :class:`.FloatContainer`. - :param bottom: Distance to the bottom of the :class:`.FloatContainer`. - - :param attach_to_window: Attach to the cursor from this window, instead of - the current window. - :param hide_when_covering_content: Hide the float when it covers content underneath. - :param allow_cover_cursor: When `False`, make sure to display the float - below the cursor. Not on top of the indicated position. - :param z_index: Z-index position. For a Float, this needs to be at least - one. It is relative to the z_index of the parent container. - :param transparent: :class:`.Filter` indicating whether this float needs to be - drawn transparently. - """ - - 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, - xcursor: bool = False, - ycursor: bool = False, - attach_to_window: Optional[AnyContainer] = None, - hide_when_covering_content: bool = False, - allow_cover_cursor: bool = False, - z_index: int = 1, - transparent: bool = False, - ) -> None: - - assert z_index >= 1 - - self.left = left - self.right = right - self.top = top - self.bottom = bottom - - self.width = width - self.height = height - - self.xcursor = xcursor - self.ycursor = ycursor - - self.attach_to_window = ( - to_window(attach_to_window) if attach_to_window else None - ) - - self.content = to_container(content) - self.hide_when_covering_content = hide_when_covering_content - self.allow_cover_cursor = allow_cover_cursor - self.z_index = z_index - self.transparent = to_filter(transparent) - - def get_width(self) -> Optional[int]: - if callable(self.width): - return self.width() - return self.width - - def get_height(self) -> Optional[int]: - if callable(self.height): - return self.height() - return self.height - - def __repr__(self) -> str: - return "Float(content=%r)" % self.content - - -class WindowRenderInfo: - """ - Render information for the last render time of this control. - It stores mapping information between the input buffers (in case of a - :class:`~prompt_toolkit.layout.controls.BufferControl`) and the actual - render position on the output screen. - - (Could be used for implementation of the Vi 'H' and 'L' key bindings as - well as implementing mouse support.) - - :param ui_content: The original :class:`.UIContent` instance that contains - the whole input, without clipping. (ui_content) - :param horizontal_scroll: The horizontal scroll of the :class:`.Window` instance. - :param vertical_scroll: The vertical scroll of the :class:`.Window` instance. - :param window_width: The width of the window that displays the content, - without the margins. - :param window_height: The height of the window that displays the content. - :param configured_scroll_offsets: The scroll offsets as configured for the - :class:`Window` instance. - :param visible_line_to_row_col: Mapping that maps the row numbers on the - displayed screen (starting from zero for the first visible line) to - (row, col) tuples pointing to the row and column of the :class:`.UIContent`. - :param rowcol_to_yx: Mapping that maps (row, column) tuples representing - coordinates of the :class:`UIContent` to (y, x) absolute coordinates at - the rendered screen. - """ - - def __init__( - self, - 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]], - x_offset: int, - y_offset: int, - wrap_lines: bool, - ) -> None: - - self.window = window - self.ui_content = ui_content - self.vertical_scroll = vertical_scroll - self.window_width = window_width # Width without margins. - self.window_height = window_height - - self.configured_scroll_offsets = configured_scroll_offsets - self.visible_line_to_row_col = visible_line_to_row_col - self.wrap_lines = wrap_lines - - self._rowcol_to_yx = rowcol_to_yx # row/col from input to absolute y/x - # screen coordinates. - self._x_offset = x_offset - self._y_offset = y_offset - - @property - 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() - } - - @property - def cursor_position(self) -> Point: - """ - Return the cursor position coordinates, relative to the left/top corner - of the rendered screen. - """ - cpos = self.ui_content.cursor_position - try: - y, x = self._rowcol_to_yx[cpos.y, cpos.x] - except KeyError: - # For `DummyControl` for instance, the content can be empty, and so - # will `_rowcol_to_yx` be. Return 0/0 by default. - return Point(x=0, y=0) - else: - return Point(x=x - self._x_offset, y=y - self._y_offset) - - @property - 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 - the cursor is completely at the top, the top offset will be zero rather - than what's configured. - """ - if self.displayed_lines[0] == 0: - top = 0 - else: - # Get row where the cursor is displayed. - y = self.input_line_to_visible_line[self.ui_content.cursor_position.y] - top = min(y, self.configured_scroll_offsets.top) - - return ScrollOffsets( - top=top, - bottom=min( - self.ui_content.line_count - self.displayed_lines[-1] - 1, - self.configured_scroll_offsets.bottom, - ), - # For left/right, it probably doesn't make sense to return something. - # (We would have to calculate the widths of all the lines and keep - # double width characters in mind.) - left=0, - right=0, - ) - - @property - 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. - """ - 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]: - """ - 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] = {} - for k, v in self.visible_line_to_input_line.items(): - if v in result: - result[v] = min(result[v], k) - else: - result[v] = k - return result - - def first_visible_line(self, after_scroll_offset: bool = False) -> int: - """ - Return the line number (0 based) of the input document that corresponds - with the first visible line. - """ - if after_scroll_offset: - return self.displayed_lines[self.applied_scroll_offsets.top] - else: - return self.displayed_lines[0] - - def last_visible_line(self, before_scroll_offset: bool = False) -> int: - """ - Like `first_visible_line`, but for the last visible line. - """ - if before_scroll_offset: - return self.displayed_lines[-1 - self.applied_scroll_offsets.bottom] - else: - return self.displayed_lines[-1] - - def center_visible_line( - self, before_scroll_offset: bool = False, after_scroll_offset: bool = False - ) -> int: - """ - Like `first_visible_line`, but for the center visible line. - """ - return ( - self.first_visible_line(after_scroll_offset) - + ( - self.last_visible_line(before_scroll_offset) - - self.first_visible_line(after_scroll_offset) - ) - // 2 - ) - - @property - def content_height(self) -> int: - """ - The full height of the user control. - """ - return self.ui_content.line_count - - @property - def full_height_visible(self) -> bool: - """ - True when the full height is visible (There is no vertical scroll.) - """ - return ( - self.vertical_scroll == 0 - and self.last_visible_line() == self.content_height - ) - - @property - def top_visible(self) -> bool: - """ - True when the top of the buffer is visible. - """ - return self.vertical_scroll == 0 - - @property - def bottom_visible(self) -> bool: - """ - True when the bottom of the buffer is visible. - """ - return self.last_visible_line() == self.content_height - 1 - - @property - def vertical_scroll_percentage(self) -> int: - """ - Vertical scroll as a percentage. (0 means: the top is visible, - 100 means: the bottom is visible.) - """ - if self.bottom_visible: - return 100 - else: - return 100 * self.vertical_scroll // self.content_height - - def get_height_for_line(self, lineno: int) -> int: - """ - Return the height of the given line. - (The height that it would take, if this line became visible.) - """ - if self.wrap_lines: - return self.ui_content.get_height_for_line( - lineno, self.window_width, self.window.get_line_prefix - ) - else: - return 1 - - -class ScrollOffsets: - """ - Scroll offsets for the :class:`.Window` class. - - Note that left/right offsets only make sense if line wrapping is disabled. - """ - - 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, - ) -> None: - - self._top = top - self._bottom = bottom - self._left = left - self._right = right - - @property - def top(self) -> int: - return to_int(self._top) - - @property - def bottom(self) -> int: - return to_int(self._bottom) - - @property - def left(self) -> int: - return to_int(self._left) - - @property - def right(self) -> int: - return to_int(self._right) - - def __repr__(self) -> str: - return "ScrollOffsets(top={!r}, bottom={!r}, left={!r}, right={!r})".format( - self._top, - self._bottom, - self._left, - self._right, - ) - - -class ColorColumn: - """ - Column for a :class:`.Window` to be colored. - """ - - def __init__(self, position: int, style: str = "class:color-column") -> None: - self.position = position - self.style = style - - -_in_insert_mode = vi_insert_mode | emacs_insert_mode - - -class WindowAlign(Enum): - """ - Alignment of the Window content. - - Note that this is different from `HorizontalAlign` and `VerticalAlign`, - which are used for the alignment of the child containers in respectively - `VSplit` and `HSplit`. - """ - - LEFT = "LEFT" - RIGHT = "RIGHT" - CENTER = "CENTER" - - -class Window(Container): - """ - Container that holds a control. - - :param content: :class:`.UIControl` instance. - :param width: :class:`.Dimension` instance or callable. - :param height: :class:`.Dimension` instance or callable. - :param z_index: When specified, this can be used to bring element in front - of floating elements. - :param dont_extend_width: When `True`, don't take up more width then the - preferred width reported by the control. - :param dont_extend_height: When `True`, don't take up more width then the - preferred height reported by the control. - :param ignore_content_width: A `bool` or :class:`.Filter` instance. Ignore - the :class:`.UIContent` width when calculating the dimensions. - :param ignore_content_height: A `bool` or :class:`.Filter` instance. Ignore - the :class:`.UIContent` height when calculating the dimensions. - :param left_margins: A list of :class:`.Margin` instance to be displayed on - the left. For instance: :class:`~prompt_toolkit.layout.NumberedMargin` - can be one of them in order to show line numbers. - :param right_margins: Like `left_margins`, but on the other side. - :param scroll_offsets: :class:`.ScrollOffsets` instance, representing the - preferred amount of lines/columns to be always visible before/after the - cursor. When both top and bottom are a very high number, the cursor - will be centered vertically most of the time. - :param allow_scroll_beyond_bottom: A `bool` or - :class:`.Filter` instance. When True, allow scrolling so far, that the - top part of the content is not visible anymore, while there is still - empty space available at the bottom of the window. In the Vi editor for - instance, this is possible. You will see tildes while the top part of - the body is hidden. - :param wrap_lines: A `bool` or :class:`.Filter` instance. When True, don't - scroll horizontally, but wrap lines instead. - :param get_vertical_scroll: Callable that takes this window - instance as input and returns a preferred vertical scroll. - (When this is `None`, the scroll is only determined by the last and - current cursor position.) - :param get_horizontal_scroll: Callable that takes this window - instance as input and returns a preferred vertical scroll. - :param always_hide_cursor: A `bool` or - :class:`.Filter` instance. When True, never display the cursor, even - when the user control specifies a cursor position. - :param cursorline: A `bool` or :class:`.Filter` instance. When True, - display a cursorline. - :param cursorcolumn: A `bool` or :class:`.Filter` instance. When True, - display a cursorcolumn. - :param colorcolumns: A list of :class:`.ColorColumn` instances that - describe the columns to be highlighted, or a callable that returns such - a list. - :param align: :class:`.WindowAlign` value or callable that returns an - :class:`.WindowAlign` value. alignment of content. - :param style: A style string. Style to be applied to all the cells in this - window. (This can be a callable that returns a string.) - :param char: (string) Character to be used for filling the background. This - can also be a callable that returns a character. - :param get_line_prefix: None or a callable that returns formatted text to - be inserted before a line. It takes a line number (int) and a - wrap_count and returns formatted text. This can be used for - implementation of line continuations, things like Vim "breakindent" and - so on. - """ - - def __init__( - self, - content: Optional[UIControl] = None, - width: AnyDimension = None, - height: AnyDimension = None, - z_index: Optional[int] = 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, - 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, - 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, - ) -> None: - - self.allow_scroll_beyond_bottom = to_filter(allow_scroll_beyond_bottom) - self.always_hide_cursor = to_filter(always_hide_cursor) - self.wrap_lines = to_filter(wrap_lines) - self.cursorline = to_filter(cursorline) - self.cursorcolumn = to_filter(cursorcolumn) - - self.content = content or DummyControl() - self.dont_extend_width = to_filter(dont_extend_width) - self.dont_extend_height = to_filter(dont_extend_height) - self.ignore_content_width = to_filter(ignore_content_width) - self.ignore_content_height = to_filter(ignore_content_height) - self.left_margins = left_margins or [] - self.right_margins = right_margins or [] - self.scroll_offsets = scroll_offsets or ScrollOffsets() - self.get_vertical_scroll = get_vertical_scroll - self.get_horizontal_scroll = get_horizontal_scroll - self.colorcolumns = colorcolumns or [] - self.align = align - self.style = style - self.char = char - self.get_line_prefix = get_line_prefix - - self.width = width - self.height = height - self.z_index = z_index - - # Cache for the screens generated by the margin. - self._ui_content_cache: SimpleCache[ - Tuple[int, int, int], UIContent - ] = SimpleCache(maxsize=8) - self._margin_width_cache: SimpleCache[Tuple[Margin, int], int] = SimpleCache( - maxsize=1 - ) - - self.reset() - - def __repr__(self) -> str: - return "Window(content=%r)" % self.content - - def reset(self) -> None: - self.content.reset() - - #: Scrolling position of the main content. - self.vertical_scroll = 0 - self.horizontal_scroll = 0 - - # Vertical scroll 2: this is the vertical offset that a line is - # scrolled if a single line (the one that contains the cursor) consumes - # all of the vertical space. - self.vertical_scroll_2 = 0 - - #: Keep render information (mappings between buffer input and render - #: output.) - self.render_info: Optional[WindowRenderInfo] = None - - def _get_margin_width(self, margin: Margin) -> int: - """ - Return the width for this margin. - (Calculate only once per render time.) - """ - # Margin.get_width, needs to have a UIContent instance. - def get_ui_content() -> UIContent: - return self._get_ui_content(width=0, height=0) - - def get_width() -> int: - return margin.get_width(get_ui_content) - - key = (margin, get_app().render_counter) - return self._margin_width_cache.get(key, get_width) - - def _get_total_margin_width(self) -> int: - """ - Calculate and return the width of the margin (left + right). - """ - return sum(self._get_margin_width(m) for m in self.left_margins) + sum( - self._get_margin_width(m) for m in self.right_margins - ) - - def preferred_width(self, max_available_width: int) -> Dimension: - """ - Calculate the preferred width for this window. - """ - - def preferred_content_width() -> Optional[int]: - """Content width: is only calculated if no exact width for the - window was given.""" - if self.ignore_content_width(): - return None - - # Calculate the width of the margin. - total_margin_width = self._get_total_margin_width() - - # Window of the content. (Can be `None`.) - preferred_width = self.content.preferred_width( - max_available_width - total_margin_width - ) - - if preferred_width is not None: - # Include width of the margins. - preferred_width += total_margin_width - return preferred_width - - # Merge. - return self._merge_dimensions( - dimension=to_dimension(self.width), - get_preferred=preferred_content_width, - dont_extend=self.dont_extend_width(), - ) - - def preferred_height(self, width: int, max_available_height: int) -> Dimension: - """ - Calculate the preferred height for this window. - """ - - def preferred_content_height() -> Optional[int]: - """Content height: is only calculated if no exact height for the - window was given.""" - if self.ignore_content_height(): - return None - - total_margin_width = self._get_total_margin_width() - wrap_lines = self.wrap_lines() - - return self.content.preferred_height( - width - total_margin_width, - max_available_height, - wrap_lines, - self.get_line_prefix, - ) - - return self._merge_dimensions( - dimension=to_dimension(self.height), - get_preferred=preferred_content_height, - dont_extend=self.dont_extend_height(), - ) - - @staticmethod - def _merge_dimensions( - dimension: Optional[Dimension], - get_preferred: Callable[[], Optional[int]], - dont_extend: bool = False, - ) -> Dimension: - """ - Take the Dimension from this `Window` class and the received preferred - size from the `UIControl` and return a `Dimension` to report to the - parent container. - """ - dimension = dimension or Dimension() - - # When a preferred dimension was explicitly given to the Window, - # ignore the UIControl. - preferred: Optional[int] - - if dimension.preferred_specified: - preferred = dimension.preferred - else: - # Otherwise, calculate the preferred dimension from the UI control - # content. - preferred = get_preferred() - - # When a 'preferred' dimension is given by the UIControl, make sure - # that it stays within the bounds of the Window. - if preferred is not None: - if dimension.max_specified: - preferred = min(preferred, dimension.max) - - if dimension.min_specified: - preferred = max(preferred, dimension.min) - - # When a `dont_extend` flag has been given, use the preferred dimension - # also as the max dimension. - max_: Optional[int] - min_: Optional[int] - - if dont_extend and preferred is not None: - max_ = min(dimension.max, preferred) - else: - max_ = dimension.max if dimension.max_specified else None - - min_ = dimension.min if dimension.min_specified else None - - return Dimension( - min=min_, max=max_, preferred=preferred, weight=dimension.weight - ) - - def _get_ui_content(self, width: int, height: int) -> UIContent: - """ - Create a `UIContent` instance. - """ - - def get_content() -> UIContent: - return self.content.create_content(width=width, height=height) - - key = (get_app().render_counter, width, height) - return self._ui_content_cache.get(key, get_content) - - def _get_digraph_char(self) -> Optional[str]: - "Return `False`, or the Digraph symbol to be used." - app = get_app() - if app.quoted_insert: - return "^" - if app.vi_state.waiting_for_digraph: - if app.vi_state.digraph_symbol1: - return app.vi_state.digraph_symbol1 - return "?" - return None - - def write_to_screen( - self, - screen: Screen, - mouse_handlers: MouseHandlers, - write_position: WritePosition, - parent_style: str, - erase_bg: bool, - z_index: Optional[int], - ) -> None: - """ - Write window to screen. This renders the user control, the margins and - copies everything over to the absolute position at the given screen. - """ - # If dont_extend_width/height was given. Then reduce width/height in - # WritePosition if the parent wanted us to paint in a bigger area. - # (This happens if this window is bundled with another window in a - # HSplit/VSplit, but with different size requirements.) - write_position = WritePosition( - xpos=write_position.xpos, - ypos=write_position.ypos, - width=write_position.width, - height=write_position.height, - ) - - if self.dont_extend_width(): - write_position.width = min( - write_position.width, - self.preferred_width(write_position.width).preferred, - ) - - if self.dont_extend_height(): - write_position.height = min( - write_position.height, - self.preferred_height( - write_position.width, write_position.height - ).preferred, - ) - - # Draw - z_index = z_index if self.z_index is None else self.z_index - - draw_func = partial( - self._write_to_screen_at_index, - screen, - mouse_handlers, - write_position, - parent_style, - erase_bg, - ) - - if z_index is None or z_index <= 0: - # When no z_index is given, draw right away. - draw_func() - else: - # Otherwise, postpone. - screen.draw_with_z_index(z_index=z_index, draw_func=draw_func) - - def _write_to_screen_at_index( - self, - screen: Screen, - mouse_handlers: MouseHandlers, - write_position: WritePosition, - parent_style: str, - erase_bg: bool, - ) -> None: - # Don't bother writing invisible windows. - # (We save some time, but also avoid applying last-line styling.) - if write_position.height <= 0 or write_position.width <= 0: - return - - # Calculate margin sizes. - left_margin_widths = [self._get_margin_width(m) for m in self.left_margins] - right_margin_widths = [self._get_margin_width(m) for m in self.right_margins] - total_margin_width = sum(left_margin_widths + right_margin_widths) - - # Render UserControl. - ui_content = self.content.create_content( - write_position.width - total_margin_width, write_position.height - ) - assert isinstance(ui_content, UIContent) - - # Scroll content. - wrap_lines = self.wrap_lines() - self._scroll( - ui_content, write_position.width - total_margin_width, write_position.height - ) - - # Erase background and fill with `char`. - self._fill_bg(screen, write_position, erase_bg) - - # Resolve `align` attribute. - align = self.align() if callable(self.align) else self.align - - # Write body - visible_line_to_row_col, rowcol_to_yx = self._copy_body( - ui_content, - screen, - write_position, - sum(left_margin_widths), - write_position.width - total_margin_width, - self.vertical_scroll, - self.horizontal_scroll, - wrap_lines=wrap_lines, - highlight_lines=True, - vertical_scroll_2=self.vertical_scroll_2, - always_hide_cursor=self.always_hide_cursor(), - has_focus=get_app().layout.current_control == self.content, - align=align, - get_line_prefix=self.get_line_prefix, - ) - - # Remember render info. (Set before generating the margins. They need this.) - x_offset = write_position.xpos + sum(left_margin_widths) - y_offset = write_position.ypos - - render_info = WindowRenderInfo( - window=self, - ui_content=ui_content, - horizontal_scroll=self.horizontal_scroll, - vertical_scroll=self.vertical_scroll, - window_width=write_position.width - total_margin_width, - window_height=write_position.height, - configured_scroll_offsets=self.scroll_offsets, - visible_line_to_row_col=visible_line_to_row_col, - rowcol_to_yx=rowcol_to_yx, - x_offset=x_offset, - y_offset=y_offset, - wrap_lines=wrap_lines, - ) - self.render_info = render_info - - # Set mouse handlers. - def mouse_handler(mouse_event: MouseEvent) -> "NotImplementedOrNone": - """ - Wrapper around the mouse_handler of the `UIControl` that turns - screen coordinates into line coordinates. - Returns `NotImplemented` if no UI invalidation should be done. - """ - # Don't handle mouse events outside of the current modal part of - # the UI. - if self not in get_app().layout.walk_through_modal_area(): - return NotImplemented - - # Find row/col position first. - yx_to_rowcol = {v: k for k, v in rowcol_to_yx.items()} - y = mouse_event.position.y - x = mouse_event.position.x - - # If clicked below the content area, look for a position in the - # last line instead. - max_y = write_position.ypos + len(visible_line_to_row_col) - 1 - y = min(max_y, y) - result: NotImplementedOrNone - - while x >= 0: - try: - row, col = yx_to_rowcol[y, x] - except KeyError: - # Try again. (When clicking on the right side of double - # width characters, or on the right side of the input.) - x -= 1 - else: - # Found position, call handler of UIControl. - result = self.content.mouse_handler( - MouseEvent( - position=Point(x=col, y=row), - event_type=mouse_event.event_type, - button=mouse_event.button, - modifiers=mouse_event.modifiers, - ) - ) - break - else: - # nobreak. - # (No x/y coordinate found for the content. This happens in - # case of a DummyControl, that does not have any content. - # Report (0,0) instead.) - result = self.content.mouse_handler( - MouseEvent( - position=Point(x=0, y=0), - event_type=mouse_event.event_type, - button=mouse_event.button, - modifiers=mouse_event.modifiers, - ) - ) - - # If it returns NotImplemented, handle it here. - if result == NotImplemented: - result = self._mouse_handler(mouse_event) - - return result - - mouse_handlers.set_mouse_handler_for_range( - x_min=write_position.xpos + sum(left_margin_widths), - x_max=write_position.xpos + write_position.width - total_margin_width, - y_min=write_position.ypos, - y_max=write_position.ypos + write_position.height, - handler=mouse_handler, - ) - - # Render and copy margins. - move_x = 0 - - def render_margin(m: Margin, width: int) -> UIContent: - "Render margin. Return `Screen`." - # Retrieve margin fragments. - fragments = m.create_margin(render_info, width, write_position.height) - - # Turn it into a UIContent object. - # already rendered those fragments using this size.) - return FormattedTextControl(fragments).create_content( - width + 1, write_position.height - ) - - for m, width in zip(self.left_margins, left_margin_widths): - if width > 0: # (ConditionalMargin returns a zero width. -- Don't render.) - # Create screen for margin. - margin_content = render_margin(m, width) - - # Copy and shift X. - self._copy_margin(margin_content, screen, write_position, move_x, width) - move_x += width - - move_x = write_position.width - sum(right_margin_widths) - - for m, width in zip(self.right_margins, right_margin_widths): - # Create screen for margin. - margin_content = render_margin(m, width) - - # Copy and shift X. - self._copy_margin(margin_content, screen, write_position, move_x, width) - move_x += width - - # Apply 'self.style' - self._apply_style(screen, write_position, parent_style) - - # Tell the screen that this user control has been painted at this - # position. - screen.visible_windows_to_write_positions[self] = write_position - - def _copy_body( - self, - ui_content: UIContent, - new_screen: Screen, - write_position: WritePosition, - move_x: int, - width: int, - vertical_scroll: int = 0, - horizontal_scroll: int = 0, - wrap_lines: bool = False, - highlight_lines: bool = False, - vertical_scroll_2: int = 0, - 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]]]: - """ - Copy the UIContent into the output screen. - Return (visible_line_to_row_col, rowcol_to_yx) tuple. - - :param get_line_prefix: None or a callable that takes a line number - (int) and a wrap_count (int) and returns formatted text. - """ - xpos = write_position.xpos + move_x - ypos = write_position.ypos - line_count = ui_content.line_count - new_buffer = new_screen.data_buffer - empty_char = _CHAR_CACHE["", ""] - - # 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]] = {} - - # Maps (row, col) from the input to (y, x) screen coordinates. - rowcol_to_yx: Dict[Tuple[int, int], Tuple[int, int]] = {} - - def copy_line( - line: StyleAndTextTuples, - lineno: int, - x: int, - y: int, - is_input: bool = False, - ) -> 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) - function before every line. - """ - if is_input: - current_rowcol_to_yx = rowcol_to_yx - else: - current_rowcol_to_yx = {} # Throwaway dictionary. - - # Draw line prefix. - if is_input and get_line_prefix: - prompt = to_formatted_text(get_line_prefix(lineno, 0)) - x, y = copy_line(prompt, lineno, x, y, is_input=False) - - # Scroll horizontally. - skipped = 0 # Characters skipped because of horizontal scrolling. - if horizontal_scroll and is_input: - h_scroll = horizontal_scroll - line = explode_text_fragments(line) - while h_scroll > 0 and line: - h_scroll -= get_cwidth(line[0][1]) - skipped += 1 - del line[:1] # Remove first character. - - x -= h_scroll # When scrolling over double width character, - # this can end up being negative. - - # Align this line. (Note that this doesn't work well when we use - # get_line_prefix and that function returns variable width prefixes.) - if align == WindowAlign.CENTER: - line_width = fragment_list_width(line) - if line_width < width: - x += (width - line_width) // 2 - elif align == WindowAlign.RIGHT: - line_width = fragment_list_width(line) - if line_width < width: - x += width - line_width - - col = 0 - wrap_count = 0 - for style, text, *_ in line: - new_buffer_row = new_buffer[y + ypos] - - # Remember raw VT escape sequences. (E.g. FinalTerm's - # escape sequences.) - if "[ZeroWidthEscape]" in style: - new_screen.zero_width_escapes[y + ypos][x + xpos] += text - continue - - for c in text: - char = _CHAR_CACHE[c, style] - char_width = char.width - - # Wrap when the line width is exceeded. - if wrap_lines and x + char_width > width: - visible_line_to_row_col[y + 1] = ( - lineno, - visible_line_to_row_col[y][1] + x, - ) - y += 1 - wrap_count += 1 - x = 0 - - # Insert line prefix (continuation prompt). - if is_input and get_line_prefix: - prompt = to_formatted_text( - get_line_prefix(lineno, wrap_count) - ) - x, y = copy_line(prompt, lineno, x, y, is_input=False) - - new_buffer_row = new_buffer[y + ypos] - - if y >= write_position.height: - return x, y # Break out of all for loops. - - # Set character in screen and shift 'x'. - if x >= 0 and y >= 0 and x < width: - new_buffer_row[x + xpos] = char - - # When we print a multi width character, make sure - # to erase the neighbours positions in the screen. - # (The empty string if different from everything, - # so next redraw this cell will repaint anyway.) - if char_width > 1: - for i in range(1, char_width): - new_buffer_row[x + xpos + i] = empty_char - - # If this is a zero width characters, then it's - # probably part of a decomposed unicode character. - # See: https://en.wikipedia.org/wiki/Unicode_equivalence - # Merge it in the previous cell. - elif char_width == 0: - # Handle all character widths. If the previous - # character is a multiwidth character, then - # merge it two positions back. - for pw in [2, 1]: # Previous character width. - if ( - x - pw >= 0 - and new_buffer_row[x + xpos - pw].width == pw - ): - prev_char = new_buffer_row[x + xpos - pw] - char2 = _CHAR_CACHE[ - prev_char.char + c, prev_char.style - ] - new_buffer_row[x + xpos - pw] = char2 - - # Keep track of write position for each character. - current_rowcol_to_yx[lineno, col + skipped] = ( - y + ypos, - x + xpos, - ) - - col += 1 - x += char_width - return x, y - - # Copy content. - def copy() -> int: - y = -vertical_scroll_2 - lineno = vertical_scroll - - while y < write_position.height and lineno < line_count: - # Take the next line and copy it in the real screen. - line = ui_content.get_line(lineno) - - visible_line_to_row_col[y] = (lineno, horizontal_scroll) - - # Copy margin and actual line. - x = 0 - x, y = copy_line(line, lineno, x, y, is_input=True) - - lineno += 1 - y += 1 - return y - - copy() - - def cursor_pos_to_screen_pos(row: int, col: int) -> Point: - "Translate row/col from UIContent to real Screen coordinates." - try: - y, x = rowcol_to_yx[row, col] - except KeyError: - # Normally this should never happen. (It is a bug, if it happens.) - # But to be sure, return (0, 0) - return Point(x=0, y=0) - - # raise ValueError( - # 'Invalid position. row=%r col=%r, vertical_scroll=%r, ' - # 'horizontal_scroll=%r, height=%r' % - # (row, col, vertical_scroll, horizontal_scroll, write_position.height)) - else: - return Point(x=x, y=y) - - # Set cursor and menu positions. - if ui_content.cursor_position: - screen_cursor_position = cursor_pos_to_screen_pos( - ui_content.cursor_position.y, ui_content.cursor_position.x - ) - - if has_focus: - new_screen.set_cursor_position(self, screen_cursor_position) - - if always_hide_cursor: - new_screen.show_cursor = False - else: - new_screen.show_cursor = ui_content.show_cursor - - self._highlight_digraph(new_screen) - - if highlight_lines: - self._highlight_cursorlines( - new_screen, - screen_cursor_position, - xpos, - ypos, - width, - write_position.height, - ) - - # Draw input characters from the input processor queue. - if has_focus and ui_content.cursor_position: - self._show_key_processor_key_buffer(new_screen) - - # Set menu position. - if ui_content.menu_position: - new_screen.set_menu_position( - self, - cursor_pos_to_screen_pos( - ui_content.menu_position.y, ui_content.menu_position.x - ), - ) - - # Update output screen height. - new_screen.height = max(new_screen.height, ypos + write_position.height) - - return visible_line_to_row_col, rowcol_to_yx - - def _fill_bg( - self, screen: Screen, write_position: WritePosition, erase_bg: bool - ) -> None: - """ - Erase/fill the background. - (Useful for floats and when a `char` has been given.) - """ - char: Optional[str] - if callable(self.char): - char = self.char() - else: - char = self.char - - if erase_bg or char: - wp = write_position - char_obj = _CHAR_CACHE[char or " ", ""] - - for y in range(wp.ypos, wp.ypos + wp.height): - row = screen.data_buffer[y] - for x in range(wp.xpos, wp.xpos + wp.width): - row[x] = char_obj - - def _apply_style( - self, new_screen: Screen, write_position: WritePosition, parent_style: str - ) -> None: - - # Apply `self.style`. - style = parent_style + " " + to_str(self.style) - - new_screen.fill_area(write_position, style=style, after=False) - - # Apply the 'last-line' class to the last line of each Window. This can - # be used to apply an 'underline' to the user control. - wp = WritePosition( - write_position.xpos, - write_position.ypos + write_position.height - 1, - write_position.width, - 1, - ) - new_screen.fill_area(wp, "class:last-line", after=True) - - def _highlight_digraph(self, new_screen: Screen) -> None: - """ - When we are in Vi digraph mode, put a question mark underneath the - cursor. - """ - digraph_char = self._get_digraph_char() - if digraph_char: - cpos = new_screen.get_cursor_position(self) - new_screen.data_buffer[cpos.y][cpos.x] = _CHAR_CACHE[ - digraph_char, "class:digraph" - ] - - def _show_key_processor_key_buffer(self, new_screen: Screen) -> None: - """ - When the user is typing a key binding that consists of several keys, - display the last pressed key if the user is in insert mode and the key - is meaningful to be displayed. - E.g. Some people want to bind 'jj' to escape in Vi insert mode. But the - first 'j' needs to be displayed in order to get some feedback. - """ - app = get_app() - key_buffer = app.key_processor.key_buffer - - if key_buffer and _in_insert_mode() and not app.is_done: - # The textual data for the given key. (Can be a VT100 escape - # sequence.) - data = key_buffer[-1].data - - # Display only if this is a 1 cell width character. - if get_cwidth(data) == 1: - cpos = new_screen.get_cursor_position(self) - new_screen.data_buffer[cpos.y][cpos.x] = _CHAR_CACHE[ - data, "class:partial-key-binding" - ] - - def _highlight_cursorlines( - self, new_screen: Screen, cpos: Point, x: int, y: int, width: int, height: int - ) -> None: - """ - Highlight cursor row/column. - """ - cursor_line_style = " class:cursor-line " - cursor_column_style = " class:cursor-column " - - data_buffer = new_screen.data_buffer - - # Highlight cursor line. - if self.cursorline(): - row = data_buffer[cpos.y] - for x in range(x, x + width): - original_char = row[x] - row[x] = _CHAR_CACHE[ - original_char.char, original_char.style + cursor_line_style - ] - - # Highlight cursor column. - if self.cursorcolumn(): - for y2 in range(y, y + height): - row = data_buffer[y2] - original_char = row[cpos.x] - row[cpos.x] = _CHAR_CACHE[ - original_char.char, original_char.style + cursor_column_style - ] - - # Highlight color columns - colorcolumns = self.colorcolumns - if callable(colorcolumns): - colorcolumns = colorcolumns() - - for cc in colorcolumns: - assert isinstance(cc, ColorColumn) - column = cc.position - - if column < x + width: # Only draw when visible. - color_column_style = " " + cc.style - - for y2 in range(y, y + height): - row = data_buffer[y2] - original_char = row[column + x] - row[column + x] = _CHAR_CACHE[ - original_char.char, original_char.style + color_column_style - ] - - def _copy_margin( - self, - margin_content: UIContent, - new_screen: Screen, - write_position: WritePosition, - move_x: int, - width: int, - ) -> None: - """ - Copy characters from the margin screen to the real screen. - """ - xpos = write_position.xpos + move_x - ypos = write_position.ypos - - margin_write_position = WritePosition(xpos, ypos, width, write_position.height) - self._copy_body(margin_content, new_screen, margin_write_position, 0, width) - - def _scroll(self, ui_content: UIContent, width: int, height: int) -> None: - """ - Scroll body. Ensure that the cursor is visible. - """ - if self.wrap_lines(): - func = self._scroll_when_linewrapping - else: - func = self._scroll_without_linewrapping - - func(ui_content, width, height) - - def _scroll_when_linewrapping( - self, ui_content: UIContent, width: int, height: int - ) -> None: - """ - Scroll to make sure the cursor position is visible and that we maintain - the requested scroll offset. - - Set `self.horizontal_scroll/vertical_scroll`. - """ - scroll_offsets_bottom = self.scroll_offsets.bottom - scroll_offsets_top = self.scroll_offsets.top - - # We don't have horizontal scrolling. - self.horizontal_scroll = 0 - - def get_line_height(lineno: int) -> int: - return ui_content.get_height_for_line(lineno, width, self.get_line_prefix) - - # When there is no space, reset `vertical_scroll_2` to zero and abort. - # This can happen if the margin is bigger than the window width. - # Otherwise the text height will become "infinite" (a big number) and - # the copy_line will spend a huge amount of iterations trying to render - # nothing. - if width <= 0: - self.vertical_scroll = ui_content.cursor_position.y - self.vertical_scroll_2 = 0 - return - - # If the current line consumes more than the whole window height, - # then we have to scroll vertically inside this line. (We don't take - # the scroll offsets into account for this.) - # Also, ignore the scroll offsets in this case. Just set the vertical - # scroll to this line. - line_height = get_line_height(ui_content.cursor_position.y) - if line_height > height - scroll_offsets_top: - # Calculate the height of the text before the cursor (including - # line prefixes). - text_before_height = ui_content.get_height_for_line( - ui_content.cursor_position.y, - width, - self.get_line_prefix, - slice_stop=ui_content.cursor_position.x, - ) - - # Adjust scroll offset. - self.vertical_scroll = ui_content.cursor_position.y - self.vertical_scroll_2 = min( - text_before_height - 1, # Keep the cursor visible. - line_height - - height, # Avoid blank lines at the bottom when scolling up again. - self.vertical_scroll_2, - ) - self.vertical_scroll_2 = max( - 0, text_before_height - height, self.vertical_scroll_2 - ) - return - else: - self.vertical_scroll_2 = 0 - - # Current line doesn't consume the whole height. Take scroll offsets into account. - def get_min_vertical_scroll() -> int: - # Make sure that the cursor line is not below the bottom. - # (Calculate how many lines can be shown between the cursor and the .) - used_height = 0 - prev_lineno = ui_content.cursor_position.y - - for lineno in range(ui_content.cursor_position.y, -1, -1): - used_height += get_line_height(lineno) - - if used_height > height - scroll_offsets_bottom: - return prev_lineno - else: - prev_lineno = lineno - return 0 - - def get_max_vertical_scroll() -> int: - # Make sure that the cursor line is not above the top. - prev_lineno = ui_content.cursor_position.y - used_height = 0 - - for lineno in range(ui_content.cursor_position.y - 1, -1, -1): - used_height += get_line_height(lineno) - - if used_height > scroll_offsets_top: - return prev_lineno - else: - prev_lineno = lineno - return prev_lineno - - def get_topmost_visible() -> int: - """ - Calculate the upper most line that can be visible, while the bottom - is still visible. We should not allow scroll more than this if - `allow_scroll_beyond_bottom` is false. - """ - prev_lineno = ui_content.line_count - 1 - used_height = 0 - for lineno in range(ui_content.line_count - 1, -1, -1): - used_height += get_line_height(lineno) - if used_height > height: - return prev_lineno - else: - prev_lineno = lineno - return prev_lineno - - # Scroll vertically. (Make sure that the whole line which contains the - # cursor is visible. - topmost_visible = get_topmost_visible() - - # Note: the `min(topmost_visible, ...)` is to make sure that we - # don't require scrolling up because of the bottom scroll offset, - # when we are at the end of the document. - self.vertical_scroll = max( - self.vertical_scroll, min(topmost_visible, get_min_vertical_scroll()) - ) - self.vertical_scroll = min(self.vertical_scroll, get_max_vertical_scroll()) - - # Disallow scrolling beyond bottom? - if not self.allow_scroll_beyond_bottom(): - self.vertical_scroll = min(self.vertical_scroll, topmost_visible) - - def _scroll_without_linewrapping( - self, ui_content: UIContent, width: int, height: int - ) -> None: - """ - Scroll to make sure the cursor position is visible and that we maintain - the requested scroll offset. - - Set `self.horizontal_scroll/vertical_scroll`. - """ - cursor_position = ui_content.cursor_position or Point(x=0, y=0) - - # Without line wrapping, we will never have to scroll vertically inside - # a single line. - self.vertical_scroll_2 = 0 - - if ui_content.line_count == 0: - self.vertical_scroll = 0 - self.horizontal_scroll = 0 - return - else: - current_line_text = fragment_list_to_text( - ui_content.get_line(cursor_position.y) - ) - - def do_scroll( - current_scroll: int, - scroll_offset_start: int, - scroll_offset_end: int, - cursor_pos: int, - window_size: int, - content_size: int, - ) -> int: - "Scrolling algorithm. Used for both horizontal and vertical scrolling." - # Calculate the scroll offset to apply. - # This can obviously never be more than have the screen size. Also, when the - # cursor appears at the top or bottom, we don't apply the offset. - scroll_offset_start = int( - min(scroll_offset_start, window_size / 2, cursor_pos) - ) - scroll_offset_end = int( - min(scroll_offset_end, window_size / 2, content_size - 1 - cursor_pos) - ) - - # Prevent negative scroll offsets. - if current_scroll < 0: - current_scroll = 0 - - # Scroll back if we scrolled to much and there's still space to show more of the document. - if ( - not self.allow_scroll_beyond_bottom() - and current_scroll > content_size - window_size - ): - current_scroll = max(0, content_size - window_size) - - # Scroll up if cursor is before visible part. - if current_scroll > cursor_pos - scroll_offset_start: - current_scroll = max(0, cursor_pos - scroll_offset_start) - - # Scroll down if cursor is after visible part. - if current_scroll < (cursor_pos + 1) - window_size + scroll_offset_end: - current_scroll = (cursor_pos + 1) - window_size + scroll_offset_end - - return current_scroll - - # When a preferred scroll is given, take that first into account. - if self.get_vertical_scroll: - self.vertical_scroll = self.get_vertical_scroll(self) - assert isinstance(self.vertical_scroll, int) - if self.get_horizontal_scroll: - self.horizontal_scroll = self.get_horizontal_scroll(self) - assert isinstance(self.horizontal_scroll, int) - - # Update horizontal/vertical scroll to make sure that the cursor - # remains visible. - offsets = self.scroll_offsets - - self.vertical_scroll = do_scroll( - current_scroll=self.vertical_scroll, - scroll_offset_start=offsets.top, - scroll_offset_end=offsets.bottom, - cursor_pos=ui_content.cursor_position.y, - window_size=height, - content_size=ui_content.line_count, - ) - - if self.get_line_prefix: - current_line_prefix_width = fragment_list_width( - to_formatted_text(self.get_line_prefix(ui_content.cursor_position.y, 0)) - ) - else: - current_line_prefix_width = 0 - - self.horizontal_scroll = do_scroll( - current_scroll=self.horizontal_scroll, - scroll_offset_start=offsets.left, - scroll_offset_end=offsets.right, - cursor_pos=get_cwidth(current_line_text[: ui_content.cursor_position.x]), - window_size=width - current_line_prefix_width, - # We can only analyse the current line. Calculating the width off - # all the lines is too expensive. - content_size=max( - get_cwidth(current_line_text), self.horizontal_scroll + width - ), - ) - - def _mouse_handler(self, mouse_event: MouseEvent) -> "NotImplementedOrNone": - """ - Mouse handler. Called when the UI control doesn't handle this - particular event. - - Return `NotImplemented` if nothing was done as a consequence of this - key binding (no UI invalidate required in that case). - """ - if mouse_event.event_type == MouseEventType.SCROLL_DOWN: - self._scroll_down() - return None - elif mouse_event.event_type == MouseEventType.SCROLL_UP: - self._scroll_up() - return None - - return NotImplemented - - def _scroll_down(self) -> None: - "Scroll window down." - info = self.render_info - - if info is None: - return - - if self.vertical_scroll < info.content_height - info.window_height: - if info.cursor_position.y <= info.configured_scroll_offsets.top: - self.content.move_cursor_down() - - self.vertical_scroll += 1 - - def _scroll_up(self) -> None: - "Scroll window up." - info = self.render_info - - if info is None: - return - - if info.vertical_scroll > 0: - # TODO: not entirely correct yet in case of line wrapping and long lines. - if ( - info.cursor_position.y - >= info.window_height - 1 - info.configured_scroll_offsets.bottom - ): - self.content.move_cursor_up() - - self.vertical_scroll -= 1 - - def get_key_bindings(self) -> Optional[KeyBindingsBase]: - return self.content.get_key_bindings() - - def get_children(self) -> List[Container]: - return [] - - -class ConditionalContainer(Container): - """ - Wrapper around any other container that can change the visibility. The - received `filter` determines whether the given container should be - displayed or not. - - :param content: :class:`.Container` instance. - :param filter: :class:`.Filter` instance. - """ - - def __init__(self, content: AnyContainer, filter: FilterOrBool) -> None: - self.content = to_container(content) - self.filter = to_filter(filter) - - def __repr__(self) -> str: - return f"ConditionalContainer({self.content!r}, filter={self.filter!r})" - - def reset(self) -> None: - self.content.reset() - - def preferred_width(self, max_available_width: int) -> Dimension: - if self.filter(): - return self.content.preferred_width(max_available_width) - else: - return Dimension.zero() - - def preferred_height(self, width: int, max_available_height: int) -> Dimension: - if self.filter(): - return self.content.preferred_height(width, max_available_height) - else: - return Dimension.zero() - - def write_to_screen( - self, - screen: Screen, - mouse_handlers: MouseHandlers, - write_position: WritePosition, - parent_style: str, - erase_bg: bool, - z_index: Optional[int], - ) -> 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]: - return [self.content] - - -class DynamicContainer(Container): - """ - Container class that dynamically returns any Container. - - :param get_container: Callable that returns a :class:`.Container` instance - or any widget with a ``__pt_container__`` method. - """ - - def __init__(self, get_container: Callable[[], AnyContainer]) -> None: - self.get_container = get_container - - def _get_container(self) -> Container: - """ - Return the current container object. - - We call `to_container`, because `get_container` can also return a - widget with a ``__pt_container__`` method. - """ - obj = self.get_container() - return to_container(obj) - - def reset(self) -> None: - self._get_container().reset() - - def preferred_width(self, max_available_width: int) -> Dimension: - return self._get_container().preferred_width(max_available_width) - - def preferred_height(self, width: int, max_available_height: int) -> Dimension: - return self._get_container().preferred_height(width, max_available_height) - - def write_to_screen( - self, - screen: Screen, - mouse_handlers: MouseHandlers, - write_position: WritePosition, - parent_style: str, - erase_bg: bool, - z_index: Optional[int], - ) -> None: - self._get_container().write_to_screen( - screen, mouse_handlers, write_position, parent_style, erase_bg, z_index - ) - - def is_modal(self) -> bool: - return False - - def get_key_bindings(self) -> Optional[KeyBindingsBase]: - # Key bindings will be collected when `layout.walk()` finds the child - # container. - return None - - 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 - # can't/shouldn't proxy the "isinstance" check. - return [self._get_container()] - - -def to_container(container: AnyContainer) -> Container: - """ - Make sure that the given object is a :class:`.Container`. - """ - if isinstance(container, Container): - return container - elif hasattr(container, "__pt_container__"): - return to_container(container.__pt_container__()) - else: - raise ValueError(f"Not a container object: {container!r}") - - -def to_window(container: AnyContainer) -> Window: - """ - Make sure that the given argument is a :class:`.Window`. - """ - if isinstance(container, Window): - return container - elif hasattr(container, "__pt_container__"): - return to_window(cast("MagicContainer", container).__pt_container__()) - else: - raise ValueError(f"Not a Window object: {container!r}.") - - -def is_container(value: object) -> "TypeGuard[AnyContainer]": - """ - Checks whether the given value is a container object - (for use in assert statements). - """ - if isinstance(value, Container): - return True - if hasattr(value, "__pt_container__"): - return is_container(cast("MagicContainer", value).__pt_container__()) - return False diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/controls.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/controls.py deleted file mode 100644 index 016d2894665..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/controls.py +++ /dev/null @@ -1,957 +0,0 @@ -""" -User interface Controls for the layout. -""" -import time -from abc import ABCMeta, abstractmethod -from typing import ( - TYPE_CHECKING, - Callable, - Dict, - Hashable, - Iterable, - List, - NamedTuple, - Optional, - Union, -) - -from prompt_toolkit.application.current import get_app -from prompt_toolkit.buffer import Buffer -from prompt_toolkit.cache import SimpleCache -from prompt_toolkit.data_structures import Point -from prompt_toolkit.document import Document -from prompt_toolkit.filters import FilterOrBool, to_filter -from prompt_toolkit.formatted_text import ( - AnyFormattedText, - StyleAndTextTuples, - to_formatted_text, -) -from prompt_toolkit.formatted_text.utils import ( - fragment_list_to_text, - fragment_list_width, - split_lines, -) -from prompt_toolkit.lexers import Lexer, SimpleLexer -from prompt_toolkit.mouse_events import MouseButton, MouseEvent, MouseEventType -from prompt_toolkit.search import SearchState -from prompt_toolkit.selection import SelectionType -from prompt_toolkit.utils import get_cwidth - -from .processors import ( - DisplayMultipleCursors, - HighlightIncrementalSearchProcessor, - HighlightSearchProcessor, - HighlightSelectionProcessor, - Processor, - TransformationInput, - merge_processors, -) - -if TYPE_CHECKING: - from prompt_toolkit.key_binding.key_bindings import ( - KeyBindingsBase, - NotImplementedOrNone, - ) - from prompt_toolkit.utils import Event - - -__all__ = [ - "BufferControl", - "SearchBufferControl", - "DummyControl", - "FormattedTextControl", - "UIControl", - "UIContent", -] - -GetLinePrefixCallable = Callable[[int, int], AnyFormattedText] - - -class UIControl(metaclass=ABCMeta): - """ - Base class for all user interface controls. - """ - - def reset(self) -> None: - # Default reset. (Doesn't have to be implemented.) - pass - - def preferred_width(self, max_available_width: int) -> Optional[int]: - return None - - def preferred_height( - self, - width: int, - max_available_height: int, - wrap_lines: bool, - get_line_prefix: Optional[GetLinePrefixCallable], - ) -> Optional[int]: - return None - - def is_focusable(self) -> bool: - """ - Tell whether this user control is focusable. - """ - return False - - @abstractmethod - 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": - """ - Handle mouse events. - - When `NotImplemented` is returned, it means that the given event is not - handled by the `UIControl` itself. The `Window` or key bindings can - decide to handle this event as scrolling or changing focus. - - :param mouse_event: `MouseEvent` instance. - """ - return NotImplemented - - def move_cursor_down(self) -> None: - """ - Request to move the cursor down. - This happens when scrolling down and the cursor is completely at the - top. - """ - - def move_cursor_up(self) -> None: - """ - Request to move the cursor up. - """ - - def get_key_bindings(self) -> Optional["KeyBindingsBase"]: - """ - The key bindings that are specific for this user control. - - Return a :class:`.KeyBindings` object if some key bindings are - specified, or `None` otherwise. - """ - - 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 - handlers to these events.) - """ - return [] - - -class UIContent: - """ - Content generated by a user control. This content consists of a list of - lines. - - :param get_line: Callable that takes a line number and returns the current - line. This is a list of (style_str, text) tuples. - :param line_count: The number of lines. - :param cursor_position: a :class:`.Point` for the cursor position. - :param menu_position: a :class:`.Point` for the menu position. - :param show_cursor: Make the cursor visible. - """ - - def __init__( - self, - get_line: Callable[[int], StyleAndTextTuples] = (lambda i: []), - line_count: int = 0, - cursor_position: Optional[Point] = None, - menu_position: Optional[Point] = None, - show_cursor: bool = True, - ): - - self.get_line = get_line - self.line_count = line_count - self.cursor_position = cursor_position or Point(x=0, y=0) - self.menu_position = menu_position - self.show_cursor = show_cursor - - # Cache for line heights. Maps cache key -> height - self._line_heights_cache: Dict[Hashable, int] = {} - - def __getitem__(self, lineno: int) -> StyleAndTextTuples: - "Make it iterable (iterate line by line)." - if lineno < self.line_count: - return self.get_line(lineno) - else: - raise IndexError - - def get_height_for_line( - self, - lineno: int, - width: int, - get_line_prefix: Optional[GetLinePrefixCallable], - slice_stop: Optional[int] = None, - ) -> int: - """ - Return the height that a given line would need if it is rendered in a - space with the given width (using line wrapping). - - :param get_line_prefix: None or a `Window.get_line_prefix` callable - that returns the prefix to be inserted before this line. - :param slice_stop: Wrap only "line[:slice_stop]" and return that - partial result. This is needed for scrolling the window correctly - when line wrapping. - :returns: The computed height. - """ - # Instead of using `get_line_prefix` as key, we use render_counter - # instead. This is more reliable, because this function could still be - # the same, while the content would change over time. - key = get_app().render_counter, lineno, width, slice_stop - - try: - return self._line_heights_cache[key] - except KeyError: - if width == 0: - height = 10**8 - else: - # Calculate line width first. - line = fragment_list_to_text(self.get_line(lineno))[:slice_stop] - text_width = get_cwidth(line) - - if get_line_prefix: - # Add prefix width. - text_width += fragment_list_width( - to_formatted_text(get_line_prefix(lineno, 0)) - ) - - # Slower path: compute path when there's a line prefix. - height = 1 - - # Keep wrapping as long as the line doesn't fit. - # Keep adding new prefixes for every wrapped line. - while text_width > width: - height += 1 - text_width -= width - - fragments2 = to_formatted_text( - get_line_prefix(lineno, height - 1) - ) - prefix_width = get_cwidth(fragment_list_to_text(fragments2)) - - if prefix_width >= width: # Prefix doesn't fit. - height = 10**8 - break - - text_width += prefix_width - else: - # Fast path: compute height when there's no line prefix. - try: - quotient, remainder = divmod(text_width, width) - except ZeroDivisionError: - height = 10**8 - else: - if remainder: - quotient += 1 # Like math.ceil. - height = max(1, quotient) - - # Cache and return - self._line_heights_cache[key] = height - return height - - -class FormattedTextControl(UIControl): - """ - Control that displays formatted text. This can be either plain text, an - :class:`~prompt_toolkit.formatted_text.HTML` object an - :class:`~prompt_toolkit.formatted_text.ANSI` object, a list of ``(style_str, - text)`` tuples or a callable that takes no argument and returns one of - those, depending on how you prefer to do the formatting. See - ``prompt_toolkit.layout.formatted_text`` for more information. - - (It's mostly optimized for rather small widgets, like toolbars, menus, etc...) - - When this UI control has the focus, the cursor will be shown in the upper - left corner of this control by default. There are two ways for specifying - the cursor position: - - - Pass a `get_cursor_position` function which returns a `Point` instance - with the current cursor position. - - - If the (formatted) text is passed as a list of ``(style, text)`` tuples - and there is one that looks like ``('[SetCursorPosition]', '')``, then - this will specify the cursor position. - - Mouse support: - - The list of fragments can also contain tuples of three items, looking like: - (style_str, text, handler). When mouse support is enabled and the user - clicks on this fragment, then the given handler is called. That handler - should accept two inputs: (Application, MouseEvent) and it should - either handle the event or return `NotImplemented` in case we want the - containing Window to handle this event. - - :param focusable: `bool` or :class:`.Filter`: Tell whether this control is - focusable. - - :param text: Text or formatted text to be displayed. - :param style: Style string applied to the content. (If you want to style - the whole :class:`~prompt_toolkit.layout.Window`, pass the style to the - :class:`~prompt_toolkit.layout.Window` instead.) - :param key_bindings: a :class:`.KeyBindings` object. - :param get_cursor_position: A callable that returns the cursor position as - a `Point` instance. - """ - - def __init__( - self, - text: AnyFormattedText = "", - style: str = "", - focusable: FilterOrBool = False, - key_bindings: Optional["KeyBindingsBase"] = None, - show_cursor: bool = True, - modal: bool = False, - get_cursor_position: Optional[Callable[[], Optional[Point]]] = None, - ) -> None: - - self.text = text # No type check on 'text'. This is done dynamically. - self.style = style - self.focusable = to_filter(focusable) - - # Key bindings. - self.key_bindings = key_bindings - self.show_cursor = show_cursor - self.modal = modal - self.get_cursor_position = get_cursor_position - - #: Cache for the content. - self._content_cache: SimpleCache[Hashable, UIContent] = SimpleCache(maxsize=18) - self._fragment_cache: SimpleCache[int, StyleAndTextTuples] = SimpleCache( - maxsize=1 - ) - # Only cache one fragment list. We don't need the previous item. - - # Render info for the mouse support. - self._fragments: Optional[StyleAndTextTuples] = None - - def reset(self) -> None: - self._fragments = None - - def is_focusable(self) -> bool: - return self.focusable() - - def __repr__(self) -> str: - return f"{self.__class__.__name__}({self.text!r})" - - def _get_formatted_text_cached(self) -> StyleAndTextTuples: - """ - Get fragments, but only retrieve fragments once during one render run. - (This function is called several times during one rendering, because - we also need those for calculating the dimensions.) - """ - return self._fragment_cache.get( - get_app().render_counter, lambda: to_formatted_text(self.text, self.style) - ) - - def preferred_width(self, max_available_width: int) -> int: - """ - Return the preferred width for this control. - That is the width of the longest line. - """ - text = fragment_list_to_text(self._get_formatted_text_cached()) - line_lengths = [get_cwidth(l) for l in text.split("\n")] - return max(line_lengths) - - def preferred_height( - self, - width: int, - max_available_height: int, - wrap_lines: bool, - get_line_prefix: Optional[GetLinePrefixCallable], - ) -> Optional[int]: - """ - Return the preferred height for this control. - """ - content = self.create_content(width, None) - if wrap_lines: - height = 0 - for i in range(content.line_count): - height += content.get_height_for_line(i, width, get_line_prefix) - if height >= max_available_height: - return max_available_height - return height - else: - return content.line_count - - def create_content(self, width: int, height: Optional[int]) -> UIContent: - # Get fragments - fragments_with_mouse_handlers = self._get_formatted_text_cached() - fragment_lines_with_mouse_handlers = list( - split_lines(fragments_with_mouse_handlers) - ) - - # Strip mouse handlers from fragments. - fragment_lines: List[StyleAndTextTuples] = [ - [(item[0], item[1]) for item in line] - for line in fragment_lines_with_mouse_handlers - ] - - # Keep track of the fragments with mouse handler, for later use in - # `mouse_handler`. - self._fragments = fragments_with_mouse_handlers - - # If there is a `[SetCursorPosition]` in the fragment list, set the - # cursor position here. - def get_cursor_position( - fragment: str = "[SetCursorPosition]", - ) -> Optional[Point]: - for y, line in enumerate(fragment_lines): - x = 0 - for style_str, text, *_ in line: - if fragment in style_str: - return Point(x=x, y=y) - x += len(text) - return None - - # If there is a `[SetMenuPosition]`, set the menu over here. - def get_menu_position() -> Optional[Point]: - return get_cursor_position("[SetMenuPosition]") - - cursor_position = (self.get_cursor_position or get_cursor_position)() - - # Create content, or take it from the cache. - key = (tuple(fragments_with_mouse_handlers), width, cursor_position) - - def get_content() -> UIContent: - return UIContent( - get_line=lambda i: fragment_lines[i], - line_count=len(fragment_lines), - show_cursor=self.show_cursor, - cursor_position=cursor_position, - menu_position=get_menu_position(), - ) - - return self._content_cache.get(key, get_content) - - def mouse_handler(self, mouse_event: MouseEvent) -> "NotImplementedOrNone": - """ - Handle mouse events. - - (When the fragment list contained mouse handlers and the user clicked on - on any of these, the matching handler is called. This handler can still - return `NotImplemented` in case we want the - :class:`~prompt_toolkit.layout.Window` to handle this particular - event.) - """ - if self._fragments: - # Read the generator. - fragments_for_line = list(split_lines(self._fragments)) - - try: - fragments = fragments_for_line[mouse_event.position.y] - except IndexError: - return NotImplemented - else: - # Find position in the fragment list. - xpos = mouse_event.position.x - - # Find mouse handler for this character. - count = 0 - for item in fragments: - count += len(item[1]) - if count > xpos: - if len(item) >= 3: - # Handler found. Call it. - # (Handler can return NotImplemented, so return - # that result.) - handler = item[2] # type: ignore - return handler(mouse_event) - else: - break - - # Otherwise, don't handle here. - return NotImplemented - - def is_modal(self) -> bool: - return self.modal - - def get_key_bindings(self) -> Optional["KeyBindingsBase"]: - return self.key_bindings - - -class DummyControl(UIControl): - """ - A dummy control object that doesn't paint any content. - - Useful for filling a :class:`~prompt_toolkit.layout.Window`. (The - `fragment` and `char` attributes of the `Window` class can be used to - define the filling.) - """ - - def create_content(self, width: int, height: int) -> UIContent: - def get_line(i: int) -> StyleAndTextTuples: - return [] - - return UIContent( - get_line=get_line, line_count=100**100 - ) # Something very big. - - def is_focusable(self) -> bool: - return False - - -class _ProcessedLine(NamedTuple): - fragments: StyleAndTextTuples - source_to_display: Callable[[int], int] - display_to_source: Callable[[int], int] - - -class BufferControl(UIControl): - """ - Control for visualising the content of a :class:`.Buffer`. - - :param buffer: The :class:`.Buffer` object to be displayed. - :param input_processors: A list of - :class:`~prompt_toolkit.layout.processors.Processor` objects. - :param include_default_input_processors: When True, include the default - processors for highlighting of selection, search and displaying of - multiple cursors. - :param lexer: :class:`.Lexer` instance for syntax highlighting. - :param preview_search: `bool` or :class:`.Filter`: Show search while - typing. When this is `True`, probably you want to add a - ``HighlightIncrementalSearchProcessor`` as well. Otherwise only the - cursor position will move, but the text won't be highlighted. - :param focusable: `bool` or :class:`.Filter`: Tell whether this control is focusable. - :param focus_on_click: Focus this buffer when it's click, but not yet focused. - :param key_bindings: a :class:`.KeyBindings` object. - """ - - def __init__( - self, - buffer: Optional[Buffer] = None, - input_processors: Optional[List[Processor]] = None, - include_default_input_processors: bool = True, - lexer: Optional[Lexer] = None, - preview_search: FilterOrBool = False, - focusable: FilterOrBool = True, - search_buffer_control: Union[ - None, "SearchBufferControl", Callable[[], "SearchBufferControl"] - ] = None, - menu_position: Optional[Callable[[], Optional[int]]] = None, - focus_on_click: FilterOrBool = False, - key_bindings: Optional["KeyBindingsBase"] = None, - ): - - self.input_processors = input_processors - self.include_default_input_processors = include_default_input_processors - - self.default_input_processors = [ - HighlightSearchProcessor(), - HighlightIncrementalSearchProcessor(), - HighlightSelectionProcessor(), - DisplayMultipleCursors(), - ] - - self.preview_search = to_filter(preview_search) - self.focusable = to_filter(focusable) - self.focus_on_click = to_filter(focus_on_click) - - self.buffer = buffer or Buffer() - self.menu_position = menu_position - self.lexer = lexer or SimpleLexer() - self.key_bindings = key_bindings - self._search_buffer_control = search_buffer_control - - #: Cache for the lexer. - #: Often, due to cursor movement, undo/redo and window resizing - #: operations, it happens that a short time, the same document has to be - #: lexed. This is a fairly easy way to cache such an expensive operation. - self._fragment_cache: SimpleCache[ - Hashable, Callable[[int], StyleAndTextTuples] - ] = SimpleCache(maxsize=8) - - self._last_click_timestamp: Optional[float] = None - self._last_get_processed_line: Optional[Callable[[int], _ProcessedLine]] = 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] - - if callable(self._search_buffer_control): - result = self._search_buffer_control() - else: - result = self._search_buffer_control - - assert result is None or isinstance(result, SearchBufferControl) - return result - - @property - def search_buffer(self) -> Optional[Buffer]: - control = self.search_buffer_control - if control is not None: - return control.buffer - return None - - @property - def search_state(self) -> SearchState: - """ - Return the `SearchState` for searching this `BufferControl`. This is - always associated with the search control. If one search bar is used - for searching multiple `BufferControls`, then they share the same - `SearchState`. - """ - search_buffer_control = self.search_buffer_control - if search_buffer_control: - return search_buffer_control.searcher_search_state - else: - return SearchState() - - def is_focusable(self) -> bool: - return self.focusable() - - def preferred_width(self, max_available_width: int) -> Optional[int]: - """ - This should return the preferred width. - - Note: We don't specify a preferred width according to the content, - because it would be too expensive. Calculating the preferred - width can be done by calculating the longest line, but this would - require applying all the processors to each line. This is - unfeasible for a larger document, and doing it for small - documents only would result in inconsistent behaviour. - """ - return None - - def preferred_height( - self, - width: int, - max_available_height: int, - wrap_lines: bool, - get_line_prefix: Optional[GetLinePrefixCallable], - ) -> Optional[int]: - - # Calculate the content height, if it was drawn on a screen with the - # given width. - height = 0 - content = self.create_content(width, height=1) # Pass a dummy '1' as height. - - # When line wrapping is off, the height should be equal to the amount - # of lines. - if not wrap_lines: - return content.line_count - - # When the number of lines exceeds the max_available_height, just - # return max_available_height. No need to calculate anything. - if content.line_count >= max_available_height: - return max_available_height - - for i in range(content.line_count): - height += content.get_height_for_line(i, width, get_line_prefix) - - if height >= max_available_height: - return max_available_height - - return height - - def _get_formatted_text_for_line_func( - self, document: Document - ) -> Callable[[int], StyleAndTextTuples]: - """ - Create a function that returns the fragments for a given line. - """ - # Cache using `document.text`. - def get_formatted_text_for_line() -> Callable[[int], StyleAndTextTuples]: - return self.lexer.lex_document(document) - - key = (document.text, self.lexer.invalidation_hash()) - return self._fragment_cache.get(key, get_formatted_text_for_line) - - def _create_get_processed_line_func( - self, document: Document, width: int, height: int - ) -> Callable[[int], _ProcessedLine]: - """ - Create a function that takes a line number of the current document and - returns a _ProcessedLine(processed_fragments, source_to_display, display_to_source) - tuple. - """ - # Merge all input processors together. - input_processors = self.input_processors or [] - if self.include_default_input_processors: - input_processors = self.default_input_processors + input_processors - - merged_processor = merge_processors(input_processors) - - def transform(lineno: int, fragments: StyleAndTextTuples) -> _ProcessedLine: - "Transform the fragments for a given line number." - # Get cursor position at this line. - def source_to_display(i: int) -> int: - """X position from the buffer to the x position in the - processed fragment list. By default, we start from the 'identity' - operation.""" - return i - - transformation = merged_processor.apply_transformation( - TransformationInput( - self, document, lineno, source_to_display, fragments, width, height - ) - ) - - return _ProcessedLine( - transformation.fragments, - transformation.source_to_display, - transformation.display_to_source, - ) - - def create_func() -> Callable[[int], _ProcessedLine]: - get_line = self._get_formatted_text_for_line_func(document) - cache: Dict[int, _ProcessedLine] = {} - - def get_processed_line(i: int) -> _ProcessedLine: - try: - return cache[i] - except KeyError: - processed_line = transform(i, get_line(i)) - cache[i] = processed_line - return processed_line - - return get_processed_line - - return create_func() - - def create_content( - self, width: int, height: int, preview_search: bool = False - ) -> UIContent: - """ - Create a UIContent. - """ - buffer = self.buffer - - # Trigger history loading of the buffer. We do this during the - # rendering of the UI here, because it needs to happen when an - # `Application` with its event loop is running. During the rendering of - # the buffer control is the earliest place we can achieve this, where - # we're sure the right event loop is active, and don't require user - # interaction (like in a key binding). - buffer.load_history_if_not_yet_loaded() - - # Get the document to be shown. If we are currently searching (the - # search buffer has focus, and the preview_search filter is enabled), - # then use the search document, which has possibly a different - # text/cursor position.) - search_control = self.search_buffer_control - preview_now = preview_search or bool( - # Only if this feature is enabled. - self.preview_search() - and - # And something was typed in the associated search field. - search_control - and search_control.buffer.text - and - # And we are searching in this control. (Many controls can point to - # the same search field, like in Pyvim.) - get_app().layout.search_target_buffer_control == self - ) - - if preview_now and search_control is not None: - ss = self.search_state - - document = buffer.document_for_search( - SearchState( - text=search_control.buffer.text, - direction=ss.direction, - ignore_case=ss.ignore_case, - ) - ) - else: - document = buffer.document - - get_processed_line = self._create_get_processed_line_func( - document, width, height - ) - self._last_get_processed_line = get_processed_line - - def translate_rowcol(row: int, col: int) -> Point: - "Return the content column for this coordinate." - return Point(x=get_processed_line(row).source_to_display(col), y=row) - - def get_line(i: int) -> StyleAndTextTuples: - "Return the fragments for a given line number." - fragments = get_processed_line(i).fragments - - # Add a space at the end, because that is a possible cursor - # position. (When inserting after the input.) We should do this on - # all the lines, not just the line containing the cursor. (Because - # otherwise, line wrapping/scrolling could change when moving the - # cursor around.) - fragments = fragments + [("", " ")] - return fragments - - content = UIContent( - get_line=get_line, - line_count=document.line_count, - cursor_position=translate_rowcol( - document.cursor_position_row, document.cursor_position_col - ), - ) - - # If there is an auto completion going on, use that start point for a - # pop-up menu position. (But only when this buffer has the focus -- - # there is only one place for a menu, determined by the focused buffer.) - if get_app().layout.current_control == self: - menu_position = self.menu_position() if self.menu_position else None - if menu_position is not None: - assert isinstance(menu_position, int) - menu_row, menu_col = buffer.document.translate_index_to_position( - menu_position - ) - content.menu_position = translate_rowcol(menu_row, menu_col) - elif buffer.complete_state: - # Position for completion menu. - # Note: We use 'min', because the original cursor position could be - # behind the input string when the actual completion is for - # some reason shorter than the text we had before. (A completion - # can change and shorten the input.) - menu_row, menu_col = buffer.document.translate_index_to_position( - min( - buffer.cursor_position, - buffer.complete_state.original_document.cursor_position, - ) - ) - content.menu_position = translate_rowcol(menu_row, menu_col) - else: - content.menu_position = None - - return content - - def mouse_handler(self, mouse_event: MouseEvent) -> "NotImplementedOrNone": - """ - Mouse handler for this control. - """ - buffer = self.buffer - position = mouse_event.position - - # Focus buffer when clicked. - if get_app().layout.current_control == self: - if self._last_get_processed_line: - processed_line = self._last_get_processed_line(position.y) - - # Translate coordinates back to the cursor position of the - # original input. - xpos = processed_line.display_to_source(position.x) - index = buffer.document.translate_row_col_to_index(position.y, xpos) - - # Set the cursor position. - if mouse_event.event_type == MouseEventType.MOUSE_DOWN: - buffer.exit_selection() - buffer.cursor_position = index - - elif ( - mouse_event.event_type == MouseEventType.MOUSE_MOVE - and mouse_event.button != MouseButton.NONE - ): - # Click and drag to highlight a selection - if ( - buffer.selection_state is None - and abs(buffer.cursor_position - index) > 0 - ): - buffer.start_selection(selection_type=SelectionType.CHARACTERS) - buffer.cursor_position = index - - elif mouse_event.event_type == MouseEventType.MOUSE_UP: - # When the cursor was moved to another place, select the text. - # (The >1 is actually a small but acceptable workaround for - # selecting text in Vi navigation mode. In navigation mode, - # the cursor can never be after the text, so the cursor - # will be repositioned automatically.) - if abs(buffer.cursor_position - index) > 1: - if buffer.selection_state is None: - buffer.start_selection( - selection_type=SelectionType.CHARACTERS - ) - buffer.cursor_position = index - - # Select word around cursor on double click. - # Two MOUSE_UP events in a short timespan are considered a double click. - double_click = ( - self._last_click_timestamp - and time.time() - self._last_click_timestamp < 0.3 - ) - self._last_click_timestamp = time.time() - - if double_click: - start, end = buffer.document.find_boundaries_of_current_word() - buffer.cursor_position += start - buffer.start_selection(selection_type=SelectionType.CHARACTERS) - buffer.cursor_position += end - start - else: - # Don't handle scroll events here. - return NotImplemented - - # Not focused, but focusing on click events. - else: - if ( - self.focus_on_click() - and mouse_event.event_type == MouseEventType.MOUSE_UP - ): - # Focus happens on mouseup. (If we did this on mousedown, the - # up event will be received at the point where this widget is - # focused and be handled anyway.) - get_app().layout.current_control = self - else: - return NotImplemented - - return None - - def move_cursor_down(self) -> None: - b = self.buffer - b.cursor_position += b.document.get_cursor_down_position() - - 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"]: - """ - When additional key bindings are given. Return these. - """ - return self.key_bindings - - def get_invalidate_events(self) -> Iterable["Event[object]"]: - """ - Return the Window invalidate events. - """ - # Whenever the buffer changes, the UI has to be updated. - yield self.buffer.on_text_changed - yield self.buffer.on_cursor_position_changed - - yield self.buffer.on_completions_changed - yield self.buffer.on_suggestion_set - - -class SearchBufferControl(BufferControl): - """ - :class:`.BufferControl` which is used for searching another - :class:`.BufferControl`. - - :param ignore_case: Search case insensitive. - """ - - def __init__( - self, - buffer: Optional[Buffer] = None, - input_processors: Optional[List[Processor]] = None, - lexer: Optional[Lexer] = None, - focus_on_click: FilterOrBool = False, - key_bindings: Optional["KeyBindingsBase"] = None, - ignore_case: FilterOrBool = False, - ): - - super().__init__( - buffer=buffer, - input_processors=input_processors, - lexer=lexer, - focus_on_click=focus_on_click, - key_bindings=key_bindings, - ) - - # If this BufferControl is used as a search field for one or more other - # BufferControls, then represents the search state. - self.searcher_search_state = SearchState(ignore_case=ignore_case) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/dimension.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/dimension.py deleted file mode 100644 index 04c21637cbd..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/dimension.py +++ /dev/null @@ -1,217 +0,0 @@ -""" -Layout dimensions are used to give the minimum, maximum and preferred -dimensions for containers and controls. -""" -from typing import TYPE_CHECKING, Any, Callable, List, Optional, Union - -__all__ = [ - "Dimension", - "D", - "sum_layout_dimensions", - "max_layout_dimensions", - "AnyDimension", - "to_dimension", - "is_dimension", -] - -if TYPE_CHECKING: - from typing_extensions import TypeGuard - - -class Dimension: - """ - Specified dimension (width/height) of a user control or window. - - The layout engine tries to honor the preferred size. If that is not - possible, because the terminal is larger or smaller, it tries to keep in - between min and max. - - :param min: Minimum size. - :param max: Maximum size. - :param weight: For a VSplit/HSplit, the actual size will be determined - by taking the proportion of weights from all the children. - E.g. When there are two children, one with a weight of 1, - and the other with a weight of 2, the second will always be - twice as big as the first, if the min/max values allow it. - :param preferred: Preferred size. - """ - - def __init__( - self, - min: Optional[int] = None, - max: Optional[int] = None, - weight: Optional[int] = None, - preferred: Optional[int] = None, - ) -> None: - if weight is not None: - assert weight >= 0 # Also cannot be a float. - - assert min is None or min >= 0 - assert max is None or max >= 0 - assert preferred is None or preferred >= 0 - - self.min_specified = min is not None - self.max_specified = max is not None - self.preferred_specified = preferred is not None - self.weight_specified = weight is not None - - if min is None: - min = 0 # Smallest possible value. - if max is None: # 0-values are allowed, so use "is None" - max = 1000**10 # Something huge. - if preferred is None: - preferred = min - if weight is None: - weight = 1 - - self.min = min - self.max = max - self.preferred = preferred - self.weight = weight - - # Don't allow situations where max < min. (This would be a bug.) - if max < min: - raise ValueError("Invalid Dimension: max < min.") - - # Make sure that the 'preferred' size is always in the min..max range. - if self.preferred < self.min: - self.preferred = self.min - - if self.preferred > self.max: - self.preferred = self.max - - @classmethod - def exact(cls, amount: int) -> "Dimension": - """ - Return a :class:`.Dimension` with an exact size. (min, max and - preferred set to ``amount``). - """ - return cls(min=amount, max=amount, preferred=amount) - - @classmethod - def zero(cls) -> "Dimension": - """ - Create a dimension that represents a zero size. (Used for 'invisible' - controls.) - """ - return cls.exact(amount=0) - - def is_zero(self) -> bool: - "True if this `Dimension` represents a zero size." - return self.preferred == 0 or self.max == 0 - - def __repr__(self) -> str: - fields = [] - if self.min_specified: - fields.append("min=%r" % self.min) - if self.max_specified: - fields.append("max=%r" % self.max) - if self.preferred_specified: - fields.append("preferred=%r" % self.preferred) - if self.weight_specified: - fields.append("weight=%r" % self.weight) - - return "Dimension(%s)" % ", ".join(fields) - - -def sum_layout_dimensions(dimensions: List[Dimension]) -> Dimension: - """ - Sum a list of :class:`.Dimension` instances. - """ - min = sum(d.min for d in dimensions) - max = sum(d.max for d in dimensions) - preferred = sum(d.preferred for d in dimensions) - - return Dimension(min=min, max=max, preferred=preferred) - - -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.) - """ - if not len(dimensions): - return Dimension.zero() - - # If all dimensions are size zero. Return zero. - # (This is important for HSplit/VSplit, to report the right values to their - # parent when all children are invisible.) - if all(d.is_zero() for d in dimensions): - return dimensions[0] - - # Ignore empty dimensions. (They should not reduce the size of others.) - dimensions = [d for d in dimensions if not d.is_zero()] - - if dimensions: - # Take the highest minimum dimension. - min_ = max(d.min for d in dimensions) - - # For the maximum, we would prefer not to go larger than then smallest - # 'max' value, unless other dimensions have a bigger preferred value. - # This seems to work best: - # - We don't want that a widget with a small height in a VSplit would - # shrink other widgets in the split. - # If it doesn't work well enough, then it's up to the UI designer to - # explicitly pass dimensions. - max_ = min(d.max for d in dimensions) - max_ = max(max_, max(d.preferred for d in dimensions)) - - # Make sure that min>=max. In some scenarios, when certain min..max - # ranges don't have any overlap, we can end up in such an impossible - # situation. In that case, give priority to the max value. - # E.g. taking (1..5) and (8..9) would return (8..5). Instead take (8..8). - if min_ > max_: - max_ = min_ - - preferred = max(d.preferred for d in dimensions) - - return Dimension(min=min_, max=max_, preferred=preferred) - else: - return Dimension() - - -# Anything that can be converted to a dimension. -AnyDimension = Union[ - None, # None is a valid dimension that will fit anything. - int, - Dimension, - # Callable[[], 'AnyDimension'] # Recursive definition not supported by mypy. - Callable[[], Any], -] - - -def to_dimension(value: AnyDimension) -> Dimension: - """ - Turn the given object into a `Dimension` object. - """ - if value is None: - return Dimension() - if isinstance(value, int): - return Dimension.exact(value) - if isinstance(value, Dimension): - return value - if callable(value): - return to_dimension(value()) - - raise ValueError("Not an integer or Dimension object.") - - -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.) - """ - if value is None: - return True - if callable(value): - return True # Assume it's a callable that doesn't take arguments. - if isinstance(value, (int, Dimension)): - return True - return False - - -# Common alias. -D = Dimension - -# For backward-compatibility. -LayoutDimension = Dimension diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/dummy.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/dummy.py deleted file mode 100644 index dcd54e9fc9f..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/dummy.py +++ /dev/null @@ -1,37 +0,0 @@ -""" -Dummy layout. Used when somebody creates an `Application` without specifying a -`Layout`. -""" -from prompt_toolkit.formatted_text import HTML -from prompt_toolkit.key_binding import KeyBindings -from prompt_toolkit.key_binding.key_processor import KeyPressEvent - -from .containers import Window -from .controls import FormattedTextControl -from .dimension import D -from .layout import Layout - -__all__ = [ - "create_dummy_layout", -] - -E = KeyPressEvent - - -def create_dummy_layout() -> Layout: - """ - Create a dummy layout for use in an 'Application' that doesn't have a - layout specified. When ENTER is pressed, the application quits. - """ - kb = KeyBindings() - - @kb.add("enter") - def enter(event: E) -> None: - event.app.exit() - - control = FormattedTextControl( - HTML("No layout specified. Press <reverse>ENTER</reverse> to quit."), - key_bindings=kb, - ) - window = Window(content=control, height=D(min=1)) - return Layout(container=window, focused_element=window) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/layout.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/layout.py deleted file mode 100644 index 62a3184ee22..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/layout.py +++ /dev/null @@ -1,411 +0,0 @@ -""" -Wrapper for the layout. -""" -from typing import Dict, Generator, Iterable, List, Optional, Union - -from prompt_toolkit.buffer import Buffer - -from .containers import ( - AnyContainer, - ConditionalContainer, - Container, - Window, - to_container, -) -from .controls import BufferControl, SearchBufferControl, UIControl - -__all__ = [ - "Layout", - "InvalidLayoutError", - "walk", -] - -FocusableElement = Union[str, Buffer, UIControl, AnyContainer] - - -class Layout: - """ - The layout for a prompt_toolkit - :class:`~prompt_toolkit.application.Application`. - This also keeps track of which user control is focused. - - :param container: The "root" container for the layout. - :param focused_element: element to be focused initially. (Can be anything - the `focus` function accepts.) - """ - - def __init__( - self, - container: AnyContainer, - focused_element: Optional[FocusableElement] = None, - ) -> None: - - self.container = to_container(container) - 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 - # applying the search. - # 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] = {} - - # 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] = {} - - if focused_element is None: - try: - self._stack.append(next(self.find_all_windows())) - except StopIteration as e: - raise InvalidLayoutError( - "Invalid layout. The layout does not contain any Window object." - ) from e - else: - self.focus(focused_element) - - # List of visible windows. - 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})" - - def find_all_windows(self) -> Generator[Window, None, None]: - """ - Find all the :class:`.UIControl` objects in this layout. - """ - for item in self.walk(): - if isinstance(item, Window): - yield item - - def find_all_controls(self) -> Iterable[UIControl]: - for container in self.find_all_windows(): - yield container.content - - def focus(self, value: FocusableElement) -> None: - """ - Focus the given UI element. - - `value` can be either: - - - a :class:`.UIControl` - - a :class:`.Buffer` instance or the name of a :class:`.Buffer` - - a :class:`.Window` - - Any container object. In this case we will focus the :class:`.Window` - from this container that was focused most recent, or the very first - focusable :class:`.Window` of the container. - """ - # BufferControl by buffer name. - if isinstance(value, str): - for control in self.find_all_controls(): - if isinstance(control, BufferControl) and control.buffer.name == value: - self.focus(control) - return - raise ValueError(f"Couldn't find Buffer in the current layout: {value!r}.") - - # BufferControl by buffer object. - elif isinstance(value, Buffer): - for control in self.find_all_controls(): - if isinstance(control, BufferControl) and control.buffer == value: - self.focus(control) - return - raise ValueError(f"Couldn't find Buffer in the current layout: {value!r}.") - - # Focus UIControl. - elif isinstance(value, UIControl): - if value not in self.find_all_controls(): - raise ValueError( - "Invalid value. Container does not appear in the layout." - ) - if not value.is_focusable(): - raise ValueError("Invalid value. UIControl is not focusable.") - - self.current_control = value - - # Otherwise, expecting any Container object. - else: - value = to_container(value) - - if isinstance(value, Window): - # This is a `Window`: focus that. - if value not in self.find_all_windows(): - raise ValueError( - "Invalid value. Window does not appear in the layout: %r" - % (value,) - ) - - self.current_window = value - else: - # Focus a window in this container. - # If we have many windows as part of this container, and some - # of them have been focused before, take the last focused - # item. (This is very useful when the UI is composed of more - # complex sub components.) - windows = [] - for c in walk(value, skip_hidden=True): - if isinstance(c, Window) and c.content.is_focusable(): - windows.append(c) - - # Take the first one that was focused before. - for w in reversed(self._stack): - if w in windows: - self.current_window = w - return - - # None was focused before: take the very first focusable window. - if windows: - self.current_window = windows[0] - return - - raise ValueError( - f"Invalid value. Container cannot be focused: {value!r}" - ) - - def has_focus(self, value: FocusableElement) -> bool: - """ - Check whether the given control has the focus. - :param value: :class:`.UIControl` or :class:`.Window` instance. - """ - if isinstance(value, str): - if self.current_buffer is None: - return False - return self.current_buffer.name == value - if isinstance(value, Buffer): - return self.current_buffer == value - if isinstance(value, UIControl): - return self.current_control == value - else: - value = to_container(value) - if isinstance(value, Window): - return self.current_window == value - else: - # Check whether this "container" is focused. This is true if - # one of the elements inside is focused. - for element in walk(value): - if element == self.current_window: - return True - return False - - @property - def current_control(self) -> UIControl: - """ - Get the :class:`.UIControl` to currently has the focus. - """ - return self._stack[-1].content - - @current_control.setter - def current_control(self, control: UIControl) -> None: - """ - Set the :class:`.UIControl` to receive the focus. - """ - for window in self.find_all_windows(): - if window.content == control: - self.current_window = window - return - - raise ValueError("Control not found in the user interface.") - - @property - def current_window(self) -> Window: - "Return the :class:`.Window` object that is currently focused." - return self._stack[-1] - - @current_window.setter - def current_window(self, value: Window) -> None: - "Set the :class:`.Window` object to be currently focused." - self._stack.append(value) - - @property - def is_searching(self) -> bool: - "True if we are searching right now." - return self.current_control in self.search_links - - @property - def search_target_buffer_control(self) -> Optional[BufferControl]: - """ - Return the :class:`.BufferControl` in which we are searching or `None`. - """ - # Not every `UIControl` is a `BufferControl`. This only applies to - # `BufferControl`. - control = self.current_control - - if isinstance(control, SearchBufferControl): - return self.search_links.get(control) - else: - return None - - def get_focusable_windows(self) -> Iterable[Window]: - """ - Return all the :class:`.Window` objects which are focusable (in the - 'modal' area). - """ - for w in self.walk_through_modal_area(): - if isinstance(w, Window) and w.content.is_focusable(): - yield w - - def get_visible_focusable_windows(self) -> List[Window]: - """ - Return a list of :class:`.Window` objects that are focusable. - """ - # focusable windows are windows that are visible, but also part of the - # modal container. Make sure to keep the ordering. - visible_windows = self.visible_windows - return [w for w in self.get_focusable_windows() if w in visible_windows] - - @property - def current_buffer(self) -> Optional[Buffer]: - """ - The currently focused :class:`~.Buffer` or `None`. - """ - ui_control = self.current_control - if isinstance(ui_control, BufferControl): - return ui_control.buffer - return None - - def get_buffer_by_name(self, buffer_name: str) -> Optional[Buffer]: - """ - Look in the layout for a buffer with the given name. - Return `None` when nothing was found. - """ - for w in self.walk(): - if isinstance(w, Window) and isinstance(w.content, BufferControl): - if w.content.buffer.name == buffer_name: - return w.content.buffer - return None - - @property - def buffer_has_focus(self) -> bool: - """ - Return `True` if the currently focused control is a - :class:`.BufferControl`. (For instance, used to determine whether the - default key bindings should be active or not.) - """ - ui_control = self.current_control - return isinstance(ui_control, BufferControl) - - @property - def previous_control(self) -> UIControl: - """ - Get the :class:`.UIControl` to previously had the focus. - """ - try: - return self._stack[-2].content - except IndexError: - return self._stack[-1].content - - def focus_last(self) -> None: - """ - Give the focus to the last focused control. - """ - if len(self._stack) > 1: - self._stack = self._stack[:-1] - - def focus_next(self) -> None: - """ - Focus the next visible/focusable Window. - """ - windows = self.get_visible_focusable_windows() - - if len(windows) > 0: - try: - index = windows.index(self.current_window) - except ValueError: - index = 0 - else: - index = (index + 1) % len(windows) - - self.focus(windows[index]) - - def focus_previous(self) -> None: - """ - Focus the previous visible/focusable Window. - """ - windows = self.get_visible_focusable_windows() - - if len(windows) > 0: - try: - index = windows.index(self.current_window) - except ValueError: - index = 0 - else: - index = (index - 1) % len(windows) - - self.focus(windows[index]) - - def walk(self) -> Iterable[Container]: - """ - Walk through all the layout nodes (and their children) and yield them. - """ - yield from walk(self.container) - - def walk_through_modal_area(self) -> Iterable[Container]: - """ - Walk through all the containers which are in the current 'modal' part - of the layout. - """ - # Go up in the tree, and find the root. (it will be a part of the - # layout, if the focus is in a modal part.) - root: Container = self.current_window - while not root.is_modal() and root in self._child_to_parent: - root = self._child_to_parent[root] - - yield from walk(root) - - def update_parents_relations(self) -> None: - """ - Update child->parent relationships mapping. - """ - parents = {} - - def walk(e: Container) -> None: - for c in e.get_children(): - parents[c] = e - walk(c) - - walk(self.container) - - self._child_to_parent = parents - - def reset(self) -> None: - # Remove all search links when the UI starts. - # (Important, for instance when control-c is been pressed while - # searching. The prompt cancels, but next `run()` call the search - # links are still there.) - self.search_links.clear() - - self.container.reset() - - def get_parent(self, container: Container) -> Optional[Container]: - """ - Return the parent container for the given container, or ``None``, if it - wasn't found. - """ - try: - return self._child_to_parent[container] - except KeyError: - return None - - -class InvalidLayoutError(Exception): - pass - - -def walk(container: Container, skip_hidden: bool = False) -> Iterable[Container]: - """ - Walk through layout, starting at this container. - """ - # When `skip_hidden` is set, don't go into disabled ConditionalContainer containers. - if ( - skip_hidden - and isinstance(container, ConditionalContainer) - and not container.filter() - ): - return - - yield container - - for c in container.get_children(): - # yield from walk(c) - yield from walk(c, skip_hidden=skip_hidden) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/margins.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/margins.py deleted file mode 100644 index 7c46819c24f..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/margins.py +++ /dev/null @@ -1,305 +0,0 @@ -""" -Margin implementations for a :class:`~prompt_toolkit.layout.containers.Window`. -""" -from abc import ABCMeta, abstractmethod -from typing import TYPE_CHECKING, Callable, Optional - -from prompt_toolkit.filters import FilterOrBool, to_filter -from prompt_toolkit.formatted_text import ( - StyleAndTextTuples, - fragment_list_to_text, - to_formatted_text, -) -from prompt_toolkit.utils import get_cwidth - -from .controls import UIContent - -if TYPE_CHECKING: - from .containers import WindowRenderInfo - -__all__ = [ - "Margin", - "NumberedMargin", - "ScrollbarMargin", - "ConditionalMargin", - "PromptMargin", -] - - -class Margin(metaclass=ABCMeta): - """ - Base interface for a margin. - """ - - @abstractmethod - def get_width(self, get_ui_content: Callable[[], UIContent]) -> int: - """ - Return the width that this margin is going to consume. - - :param get_ui_content: Callable that asks the user control to create - a :class:`.UIContent` instance. This can be used for instance to - obtain the number of lines. - """ - return 0 - - @abstractmethod - def create_margin( - self, window_render_info: "WindowRenderInfo", width: int, height: int - ) -> StyleAndTextTuples: - """ - Creates a margin. - This should return a list of (style_str, text) tuples. - - :param window_render_info: - :class:`~prompt_toolkit.layout.containers.WindowRenderInfo` - instance, generated after rendering and copying the visible part of - the :class:`~prompt_toolkit.layout.controls.UIControl` into the - :class:`~prompt_toolkit.layout.containers.Window`. - :param width: The width that's available for this margin. (As reported - by :meth:`.get_width`.) - :param height: The height that's available for this margin. (The height - of the :class:`~prompt_toolkit.layout.containers.Window`.) - """ - return [] - - -class NumberedMargin(Margin): - """ - Margin that displays the line numbers. - - :param relative: Number relative to the cursor position. Similar to the Vi - 'relativenumber' option. - :param display_tildes: Display tildes after the end of the document, just - like Vi does. - """ - - def __init__( - self, relative: FilterOrBool = False, display_tildes: FilterOrBool = False - ) -> None: - - self.relative = to_filter(relative) - self.display_tildes = to_filter(display_tildes) - - def get_width(self, get_ui_content: Callable[[], UIContent]) -> int: - line_count = get_ui_content().line_count - return max(3, len("%s" % line_count) + 1) - - def create_margin( - self, window_render_info: "WindowRenderInfo", width: int, height: int - ) -> StyleAndTextTuples: - relative = self.relative() - - style = "class:line-number" - style_current = "class:line-number.current" - - # Get current line number. - current_lineno = window_render_info.ui_content.cursor_position.y - - # Construct margin. - result: StyleAndTextTuples = [] - last_lineno = None - - for y, lineno in enumerate(window_render_info.displayed_lines): - # Only display line number if this line is not a continuation of the previous line. - if lineno != last_lineno: - if lineno is None: - pass - elif lineno == current_lineno: - # Current line. - if relative: - # Left align current number in relative mode. - result.append((style_current, "%i" % (lineno + 1))) - else: - result.append( - (style_current, ("%i " % (lineno + 1)).rjust(width)) - ) - else: - # Other lines. - if relative: - lineno = abs(lineno - current_lineno) - 1 - - result.append((style, ("%i " % (lineno + 1)).rjust(width))) - - last_lineno = lineno - result.append(("", "\n")) - - # Fill with tildes. - if self.display_tildes(): - while y < window_render_info.window_height: - result.append(("class:tilde", "~\n")) - y += 1 - - return result - - -class ConditionalMargin(Margin): - """ - Wrapper around other :class:`.Margin` classes to show/hide them. - """ - - def __init__(self, margin: Margin, filter: FilterOrBool) -> None: - self.margin = margin - self.filter = to_filter(filter) - - def get_width(self, get_ui_content: Callable[[], UIContent]) -> int: - if self.filter(): - return self.margin.get_width(get_ui_content) - else: - return 0 - - def create_margin( - 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) - else: - return [] - - -class ScrollbarMargin(Margin): - """ - Margin displaying a scrollbar. - - :param display_arrows: Display scroll up/down arrows. - """ - - def __init__( - self, - display_arrows: FilterOrBool = False, - up_arrow_symbol: str = "^", - down_arrow_symbol: str = "v", - ) -> None: - - self.display_arrows = to_filter(display_arrows) - self.up_arrow_symbol = up_arrow_symbol - self.down_arrow_symbol = down_arrow_symbol - - def get_width(self, get_ui_content: Callable[[], UIContent]) -> int: - return 1 - - def create_margin( - self, window_render_info: "WindowRenderInfo", width: int, height: int - ) -> StyleAndTextTuples: - content_height = window_render_info.content_height - window_height = window_render_info.window_height - display_arrows = self.display_arrows() - - if display_arrows: - window_height -= 2 - - try: - fraction_visible = len(window_render_info.displayed_lines) / float( - content_height - ) - fraction_above = window_render_info.vertical_scroll / float(content_height) - - scrollbar_height = int( - min(window_height, max(1, window_height * fraction_visible)) - ) - scrollbar_top = int(window_height * fraction_above) - except ZeroDivisionError: - return [] - else: - - def is_scroll_button(row: int) -> bool: - "True if we should display a button on this row." - return scrollbar_top <= row <= scrollbar_top + scrollbar_height - - # Up arrow. - result: StyleAndTextTuples = [] - if display_arrows: - result.extend( - [ - ("class:scrollbar.arrow", self.up_arrow_symbol), - ("class:scrollbar", "\n"), - ] - ) - - # Scrollbar body. - scrollbar_background = "class:scrollbar.background" - scrollbar_background_start = "class:scrollbar.background,scrollbar.start" - scrollbar_button = "class:scrollbar.button" - scrollbar_button_end = "class:scrollbar.button,scrollbar.end" - - for i in range(window_height): - if is_scroll_button(i): - if not is_scroll_button(i + 1): - # Give the last cell a different style, because we - # want to underline this. - result.append((scrollbar_button_end, " ")) - else: - result.append((scrollbar_button, " ")) - else: - if is_scroll_button(i + 1): - result.append((scrollbar_background_start, " ")) - else: - result.append((scrollbar_background, " ")) - result.append(("", "\n")) - - # Down arrow - if display_arrows: - result.append(("class:scrollbar.arrow", self.down_arrow_symbol)) - - return result - - -class PromptMargin(Margin): - """ - [Deprecated] - - Create margin that displays a prompt. - This can display one prompt at the first line, and a continuation prompt - (e.g, just dots) on all the following lines. - - This `PromptMargin` implementation has been largely superseded in favor of - the `get_line_prefix` attribute of `Window`. The reason is that a margin is - always a fixed width, while `get_line_prefix` can return a variable width - prefix in front of every line, making it more powerful, especially for line - continuations. - - :param get_prompt: Callable returns formatted text or a list of - `(style_str, type)` tuples to be shown as the prompt at the first line. - :param get_continuation: Callable that takes three inputs. The width (int), - line_number (int), and is_soft_wrap (bool). It should return formatted - text or a list of `(style_str, type)` tuples for the next lines of the - input. - """ - - def __init__( - self, - get_prompt: Callable[[], StyleAndTextTuples], - get_continuation: Optional[ - Callable[[int, int, bool], StyleAndTextTuples] - ] = None, - ) -> None: - - self.get_prompt = get_prompt - self.get_continuation = get_continuation - - def get_width(self, get_ui_content: Callable[[], UIContent]) -> int: - "Width to report to the `Window`." - # Take the width from the first line. - text = fragment_list_to_text(self.get_prompt()) - return get_cwidth(text) - - def create_margin( - self, window_render_info: "WindowRenderInfo", width: int, height: int - ) -> StyleAndTextTuples: - get_continuation = self.get_continuation - result: StyleAndTextTuples = [] - - # First line. - result.extend(to_formatted_text(self.get_prompt())) - - # Next lines. - if get_continuation: - last_y = None - - for y in window_render_info.displayed_lines[1:]: - result.append(("", "\n")) - result.extend( - to_formatted_text(get_continuation(width, y, y == last_y)) - ) - last_y = y - - return result diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/menus.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/menus.py deleted file mode 100644 index 24d6e46af05..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/menus.py +++ /dev/null @@ -1,723 +0,0 @@ -import math -from itertools import zip_longest -from typing import ( - TYPE_CHECKING, - Callable, - Dict, - Iterable, - List, - Optional, - Sequence, - Tuple, - TypeVar, - Union, - cast, -) - -from prompt_toolkit.application.current import get_app -from prompt_toolkit.buffer import CompletionState -from prompt_toolkit.completion import Completion -from prompt_toolkit.data_structures import Point -from prompt_toolkit.filters import ( - Condition, - FilterOrBool, - has_completions, - is_done, - to_filter, -) -from prompt_toolkit.formatted_text import ( - StyleAndTextTuples, - fragment_list_width, - to_formatted_text, -) -from prompt_toolkit.key_binding.key_processor import KeyPressEvent -from prompt_toolkit.layout.utils import explode_text_fragments -from prompt_toolkit.mouse_events import MouseEvent, MouseEventType -from prompt_toolkit.utils import get_cwidth - -from .containers import ConditionalContainer, HSplit, ScrollOffsets, Window -from .controls import GetLinePrefixCallable, UIContent, UIControl -from .dimension import Dimension -from .margins import ScrollbarMargin - -if TYPE_CHECKING: - from prompt_toolkit.key_binding.key_bindings import ( - KeyBindings, - NotImplementedOrNone, - ) - - -__all__ = [ - "CompletionsMenu", - "MultiColumnCompletionsMenu", -] - -E = KeyPressEvent - - -class CompletionsMenuControl(UIControl): - """ - Helper for drawing the complete menu to the screen. - - :param scroll_offset: Number (integer) representing the preferred amount of - completions to be displayed before and after the current one. When this - is a very high number, the current completion will be shown in the - middle most of the time. - """ - - # Preferred minimum size of the menu control. - # The CompletionsMenu class defines a width of 8, and there is a scrollbar - # of 1.) - MIN_WIDTH = 7 - - def has_focus(self) -> bool: - return False - - def preferred_width(self, max_available_width: int) -> Optional[int]: - complete_state = get_app().current_buffer.complete_state - if complete_state: - menu_width = self._get_menu_width(500, complete_state) - menu_meta_width = self._get_menu_meta_width(500, complete_state) - - return menu_width + menu_meta_width - else: - return 0 - - def preferred_height( - self, - width: int, - max_available_height: int, - wrap_lines: bool, - get_line_prefix: Optional[GetLinePrefixCallable], - ) -> Optional[int]: - - complete_state = get_app().current_buffer.complete_state - if complete_state: - return len(complete_state.completions) - else: - return 0 - - def create_content(self, width: int, height: int) -> UIContent: - """ - Create a UIContent object for this control. - """ - complete_state = get_app().current_buffer.complete_state - if complete_state: - completions = complete_state.completions - index = complete_state.complete_index # Can be None! - - # Calculate width of completions menu. - menu_width = self._get_menu_width(width, complete_state) - menu_meta_width = self._get_menu_meta_width( - width - menu_width, complete_state - ) - show_meta = self._show_meta(complete_state) - - def get_line(i: int) -> StyleAndTextTuples: - c = completions[i] - is_current_completion = i == index - result = _get_menu_item_fragments( - c, is_current_completion, menu_width, space_after=True - ) - - if show_meta: - result += self._get_menu_item_meta_fragments( - c, is_current_completion, menu_meta_width - ) - return result - - return UIContent( - get_line=get_line, - cursor_position=Point(x=0, y=index or 0), - line_count=len(completions), - ) - - return UIContent() - - def _show_meta(self, complete_state: CompletionState) -> bool: - """ - Return ``True`` if we need to show a column with meta information. - """ - return any(c.display_meta_text for c in complete_state.completions) - - def _get_menu_width(self, max_width: int, complete_state: CompletionState) -> int: - """ - Return the width of the main column. - """ - return min( - max_width, - max( - self.MIN_WIDTH, - max(get_cwidth(c.display_text) for c in complete_state.completions) + 2, - ), - ) - - def _get_menu_meta_width( - self, max_width: int, complete_state: CompletionState - ) -> int: - """ - Return the width of the meta column. - """ - - def meta_width(completion: Completion) -> int: - return get_cwidth(completion.display_meta_text) - - if self._show_meta(complete_state): - return min( - max_width, max(meta_width(c) for c in complete_state.completions) + 2 - ) - else: - return 0 - - def _get_menu_item_meta_fragments( - self, completion: Completion, is_current_completion: bool, width: int - ) -> StyleAndTextTuples: - - if is_current_completion: - style_str = "class:completion-menu.meta.completion.current" - else: - style_str = "class:completion-menu.meta.completion" - - text, tw = _trim_formatted_text(completion.display_meta, width - 2) - padding = " " * (width - 1 - tw) - - return to_formatted_text( - cast(StyleAndTextTuples, []) + [("", " ")] + text + [("", padding)], - style=style_str, - ) - - def mouse_handler(self, mouse_event: MouseEvent) -> "NotImplementedOrNone": - """ - Handle mouse events: clicking and scrolling. - """ - b = get_app().current_buffer - - if mouse_event.event_type == MouseEventType.MOUSE_UP: - # Select completion. - b.go_to_completion(mouse_event.position.y) - b.complete_state = None - - elif mouse_event.event_type == MouseEventType.SCROLL_DOWN: - # Scroll up. - b.complete_next(count=3, disable_wrap_around=True) - - elif mouse_event.event_type == MouseEventType.SCROLL_UP: - # Scroll down. - b.complete_previous(count=3, disable_wrap_around=True) - - return None - - -def _get_menu_item_fragments( - completion: Completion, - is_current_completion: bool, - width: int, - space_after: bool = False, -) -> StyleAndTextTuples: - """ - Get the style/text tuples for a menu item, styled and trimmed to the given - width. - """ - if is_current_completion: - style_str = "class:completion-menu.completion.current {} {}".format( - completion.style, - completion.selected_style, - ) - else: - style_str = "class:completion-menu.completion " + completion.style - - text, tw = _trim_formatted_text( - completion.display, (width - 2 if space_after else width - 1) - ) - - padding = " " * (width - 1 - tw) - - return to_formatted_text( - cast(StyleAndTextTuples, []) + [("", " ")] + text + [("", padding)], - style=style_str, - ) - - -def _trim_formatted_text( - formatted_text: StyleAndTextTuples, max_width: int -) -> Tuple[StyleAndTextTuples, int]: - """ - Trim the text to `max_width`, append dots when the text is too long. - Returns (text, width) tuple. - """ - width = fragment_list_width(formatted_text) - - # When the text is too wide, trim it. - if width > max_width: - result = [] # Text fragments. - remaining_width = max_width - 3 - - for style_and_ch in explode_text_fragments(formatted_text): - ch_width = get_cwidth(style_and_ch[1]) - - if ch_width <= remaining_width: - result.append(style_and_ch) - remaining_width -= ch_width - else: - break - - result.append(("", "...")) - - return result, max_width - remaining_width - else: - return formatted_text, width - - -class CompletionsMenu(ConditionalContainer): - # NOTE: We use a pretty big z_index by default. Menus are supposed to be - # above anything else. We also want to make sure that the content is - # visible at the point where we draw this menu. - def __init__( - self, - max_height: Optional[int] = None, - scroll_offset: Union[int, Callable[[], int]] = 0, - extra_filter: FilterOrBool = True, - display_arrows: FilterOrBool = False, - z_index: int = 10**8, - ) -> None: - - extra_filter = to_filter(extra_filter) - display_arrows = to_filter(display_arrows) - - super().__init__( - content=Window( - content=CompletionsMenuControl(), - width=Dimension(min=8), - height=Dimension(min=1, max=max_height), - scroll_offsets=ScrollOffsets(top=scroll_offset, bottom=scroll_offset), - right_margins=[ScrollbarMargin(display_arrows=display_arrows)], - dont_extend_width=True, - style="class:completion-menu", - z_index=z_index, - ), - # Show when there are completions but not at the point we are - # returning the input. - filter=has_completions & ~is_done & extra_filter, - ) - - -class MultiColumnCompletionMenuControl(UIControl): - """ - Completion menu that displays all the completions in several columns. - When there are more completions than space for them to be displayed, an - arrow is shown on the left or right side. - - `min_rows` indicates how many rows will be available in any possible case. - When this is larger than one, it will try to use less columns and more - rows until this value is reached. - Be careful passing in a too big value, if less than the given amount of - rows are available, more columns would have been required, but - `preferred_width` doesn't know about that and reports a too small value. - This results in less completions displayed and additional scrolling. - (It's a limitation of how the layout engine currently works: first the - widths are calculated, then the heights.) - - :param suggested_max_column_width: The suggested max width of a column. - The column can still be bigger than this, but if there is place for two - columns of this width, we will display two columns. This to avoid that - if there is one very wide completion, that it doesn't significantly - reduce the amount of columns. - """ - - _required_margin = 3 # One extra padding on the right + space for arrows. - - def __init__(self, min_rows: int = 3, suggested_max_column_width: int = 30) -> None: - assert min_rows >= 1 - - self.min_rows = min_rows - self.suggested_max_column_width = suggested_max_column_width - self.scroll = 0 - - # 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_left_arrow = False - self._render_right_arrow = False - self._render_width = 0 - - def reset(self) -> None: - self.scroll = 0 - - def has_focus(self) -> bool: - return False - - def preferred_width(self, max_available_width: int) -> Optional[int]: - """ - Preferred width: prefer to use at least min_rows, but otherwise as much - as possible horizontally. - """ - complete_state = get_app().current_buffer.complete_state - if complete_state is None: - return 0 - - column_width = self._get_column_width(complete_state) - result = int( - column_width - * math.ceil(len(complete_state.completions) / float(self.min_rows)) - ) - - # When the desired width is still more than the maximum available, - # reduce by removing columns until we are less than the available - # width. - while ( - result > column_width - and result > max_available_width - self._required_margin - ): - result -= column_width - return result + self._required_margin - - def preferred_height( - self, - width: int, - max_available_height: int, - wrap_lines: bool, - get_line_prefix: Optional[GetLinePrefixCallable], - ) -> Optional[int]: - """ - Preferred height: as much as needed in order to display all the completions. - """ - complete_state = get_app().current_buffer.complete_state - if complete_state is None: - return 0 - - column_width = self._get_column_width(complete_state) - column_count = max(1, (width - self._required_margin) // column_width) - - return int(math.ceil(len(complete_state.completions) / float(column_count))) - - def create_content(self, width: int, height: int) -> UIContent: - """ - Create a UIContent object for this menu. - """ - complete_state = get_app().current_buffer.complete_state - if complete_state is None: - return UIContent() - - column_width = self._get_column_width(complete_state) - self._render_pos_to_completion = {} - - _T = TypeVar("_T") - - def grouper( - n: int, iterable: Iterable[_T], fillvalue: Optional[_T] = None - ) -> Iterable[Sequence[Optional[_T]]]: - "grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx" - args = [iter(iterable)] * n - return zip_longest(fillvalue=fillvalue, *args) - - def is_current_completion(completion: Completion) -> bool: - "Returns True when this completion is the currently selected one." - return ( - complete_state is not None - and complete_state.complete_index is not None - and c == complete_state.current_completion - ) - - # Space required outside of the regular columns, for displaying the - # left and right arrow. - HORIZONTAL_MARGIN_REQUIRED = 3 - - # There should be at least one column, but it cannot be wider than - # the available width. - column_width = min(width - HORIZONTAL_MARGIN_REQUIRED, column_width) - - # However, when the columns tend to be very wide, because there are - # some very wide entries, shrink it anyway. - if column_width > self.suggested_max_column_width: - # `column_width` can still be bigger that `suggested_max_column_width`, - # but if there is place for two columns, we divide by two. - column_width //= column_width // self.suggested_max_column_width - - visible_columns = max(1, (width - self._required_margin) // column_width) - - columns_ = list(grouper(height, complete_state.completions)) - rows_ = list(zip(*columns_)) - - # Make sure the current completion is always visible: update scroll offset. - selected_column = (complete_state.complete_index or 0) // height - self.scroll = min( - selected_column, max(self.scroll, selected_column - visible_columns + 1) - ) - - render_left_arrow = self.scroll > 0 - render_right_arrow = self.scroll < len(rows_[0]) - visible_columns - - # Write completions to screen. - fragments_for_line = [] - - for row_index, row in enumerate(rows_): - fragments: StyleAndTextTuples = [] - middle_row = row_index == len(rows_) // 2 - - # Draw left arrow if we have hidden completions on the left. - if render_left_arrow: - fragments.append(("class:scrollbar", "<" if middle_row else " ")) - elif render_right_arrow: - # Reserve one column empty space. (If there is a right - # arrow right now, there can be a left arrow as well.) - fragments.append(("", " ")) - - # Draw row content. - for column_index, c in enumerate(row[self.scroll :][:visible_columns]): - if c is not None: - fragments += _get_menu_item_fragments( - c, is_current_completion(c), column_width, space_after=False - ) - - # Remember render position for mouse click handler. - for x in range(column_width): - self._render_pos_to_completion[ - (column_index * column_width + x, row_index) - ] = c - else: - fragments.append(("class:completion", " " * column_width)) - - # Draw trailing padding for this row. - # (_get_menu_item_fragments only returns padding on the left.) - if render_left_arrow or render_right_arrow: - fragments.append(("class:completion", " ")) - - # Draw right arrow if we have hidden completions on the right. - if render_right_arrow: - fragments.append(("class:scrollbar", ">" if middle_row else " ")) - elif render_left_arrow: - fragments.append(("class:completion", " ")) - - # Add line. - fragments_for_line.append( - to_formatted_text(fragments, style="class:completion-menu") - ) - - self._rendered_rows = height - self._rendered_columns = visible_columns - self._total_columns = len(columns_) - self._render_left_arrow = render_left_arrow - self._render_right_arrow = render_right_arrow - self._render_width = ( - column_width * visible_columns + render_left_arrow + render_right_arrow + 1 - ) - - def get_line(i: int) -> StyleAndTextTuples: - return fragments_for_line[i] - - return UIContent(get_line=get_line, line_count=len(rows_)) - - def _get_column_width(self, complete_state: CompletionState) -> int: - """ - Return the width of each column. - """ - return max(get_cwidth(c.display_text) for c in complete_state.completions) + 1 - - def mouse_handler(self, mouse_event: MouseEvent) -> "NotImplementedOrNone": - """ - Handle scroll and click events. - """ - b = get_app().current_buffer - - def scroll_left() -> None: - b.complete_previous(count=self._rendered_rows, disable_wrap_around=True) - self.scroll = max(0, self.scroll - 1) - - def scroll_right() -> None: - b.complete_next(count=self._rendered_rows, disable_wrap_around=True) - self.scroll = min( - self._total_columns - self._rendered_columns, self.scroll + 1 - ) - - if mouse_event.event_type == MouseEventType.SCROLL_DOWN: - scroll_right() - - elif mouse_event.event_type == MouseEventType.SCROLL_UP: - scroll_left() - - elif mouse_event.event_type == MouseEventType.MOUSE_UP: - x = mouse_event.position.x - y = mouse_event.position.y - - # Mouse click on left arrow. - if x == 0: - if self._render_left_arrow: - scroll_left() - - # Mouse click on right arrow. - elif x == self._render_width - 1: - if self._render_right_arrow: - scroll_right() - - # Mouse click on completion. - else: - completion = self._render_pos_to_completion.get((x, y)) - if completion: - b.apply_completion(completion) - - return None - - def get_key_bindings(self) -> "KeyBindings": - """ - Expose key bindings that handle the left/right arrow keys when the menu - is displayed. - """ - from prompt_toolkit.key_binding.key_bindings import KeyBindings - - kb = KeyBindings() - - @Condition - def filter() -> bool: - "Only handle key bindings if this menu is visible." - app = get_app() - complete_state = app.current_buffer.complete_state - - # There need to be completions, and one needs to be selected. - if complete_state is None or complete_state.complete_index is None: - return False - - # This menu needs to be visible. - return any(window.content == self for window in app.layout.visible_windows) - - def move(right: bool = False) -> None: - buff = get_app().current_buffer - complete_state = buff.complete_state - - if complete_state is not None and complete_state.complete_index is not None: - # Calculate new complete index. - new_index = complete_state.complete_index - if right: - new_index += self._rendered_rows - else: - new_index -= self._rendered_rows - - if 0 <= new_index < len(complete_state.completions): - buff.go_to_completion(new_index) - - # NOTE: the is_global is required because the completion menu will - # never be focussed. - - @kb.add("left", is_global=True, filter=filter) - def _left(event: E) -> None: - move() - - @kb.add("right", is_global=True, filter=filter) - def _right(event: E) -> None: - move(True) - - return kb - - -class MultiColumnCompletionsMenu(HSplit): - """ - Container that displays the completions in several columns. - When `show_meta` (a :class:`~prompt_toolkit.filters.Filter`) evaluates - to True, it shows the meta information at the bottom. - """ - - def __init__( - self, - min_rows: int = 3, - suggested_max_column_width: int = 30, - show_meta: FilterOrBool = True, - extra_filter: FilterOrBool = True, - z_index: int = 10**8, - ) -> None: - - show_meta = to_filter(show_meta) - extra_filter = to_filter(extra_filter) - - # Display filter: show when there are completions but not at the point - # we are returning the input. - full_filter = has_completions & ~is_done & extra_filter - - @Condition - def any_completion_has_meta() -> bool: - complete_state = get_app().current_buffer.complete_state - return complete_state is not None and any( - c.display_meta for c in complete_state.completions - ) - - # Create child windows. - # NOTE: We don't set style='class:completion-menu' to the - # `MultiColumnCompletionMenuControl`, because this is used in a - # Float that is made transparent, and the size of the control - # doesn't always correspond exactly with the size of the - # generated content. - completions_window = ConditionalContainer( - content=Window( - content=MultiColumnCompletionMenuControl( - min_rows=min_rows, - suggested_max_column_width=suggested_max_column_width, - ), - width=Dimension(min=8), - height=Dimension(min=1), - ), - filter=full_filter, - ) - - meta_window = ConditionalContainer( - content=Window(content=_SelectedCompletionMetaControl()), - filter=show_meta & full_filter & any_completion_has_meta, - ) - - # Initialise split. - super().__init__([completions_window, meta_window], z_index=z_index) - - -class _SelectedCompletionMetaControl(UIControl): - """ - Control that shows the meta information of the selected completion. - """ - - def preferred_width(self, max_available_width: int) -> Optional[int]: - """ - Report the width of the longest meta text as the preferred width of this control. - - It could be that we use less width, but this way, we're sure that the - layout doesn't change when we select another completion (E.g. that - completions are suddenly shown in more or fewer columns.) - """ - app = get_app() - if app.current_buffer.complete_state: - state = app.current_buffer.complete_state - return 2 + max(get_cwidth(c.display_meta_text) for c in state.completions) - else: - return 0 - - def preferred_height( - self, - width: int, - max_available_height: int, - wrap_lines: bool, - get_line_prefix: Optional[GetLinePrefixCallable], - ) -> Optional[int]: - return 1 - - def create_content(self, width: int, height: int) -> UIContent: - fragments = self._get_text_fragments() - - def get_line(i: int) -> StyleAndTextTuples: - return fragments - - return UIContent(get_line=get_line, line_count=1 if fragments else 0) - - def _get_text_fragments(self) -> StyleAndTextTuples: - style = "class:completion-menu.multi-column-meta" - state = get_app().current_buffer.complete_state - - if ( - state - and state.current_completion - and state.current_completion.display_meta_text - ): - return to_formatted_text( - cast(StyleAndTextTuples, [("", " ")]) - + state.current_completion.display_meta - + [("", " ")], - style=style, - ) - - return [] diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/mouse_handlers.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/mouse_handlers.py deleted file mode 100644 index 256231793a9..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/mouse_handlers.py +++ /dev/null @@ -1,54 +0,0 @@ -from collections import defaultdict -from typing import TYPE_CHECKING, Callable, DefaultDict - -from prompt_toolkit.mouse_events import MouseEvent - -if TYPE_CHECKING: - from prompt_toolkit.key_binding.key_bindings import NotImplementedOrNone - -__all__ = [ - "MouseHandler", - "MouseHandlers", -] - - -MouseHandler = Callable[[MouseEvent], "NotImplementedOrNone"] - - -class MouseHandlers: - """ - Two dimensional raster of callbacks for mouse events. - """ - - def __init__(self) -> None: - def dummy_callback(mouse_event: MouseEvent) -> "NotImplementedOrNone": - """ - :param mouse_event: `MouseEvent` instance. - """ - return NotImplemented - - # NOTE: Previously, the data structure was a dictionary mapping (x,y) - # to the handlers. This however would be more inefficient when copying - # over the mouse handlers of the visible region in the scrollable pane. - - # Map y (row) to x (column) to handlers. - self.mouse_handlers: DefaultDict[ - int, DefaultDict[int, MouseHandler] - ] = defaultdict(lambda: defaultdict(lambda: dummy_callback)) - - def set_mouse_handler_for_range( - self, - x_min: int, - x_max: int, - y_min: int, - y_max: int, - handler: Callable[[MouseEvent], "NotImplementedOrNone"], - ) -> None: - """ - Set mouse handler for a region. - """ - for y in range(y_min, y_max): - row = self.mouse_handlers[y] - - for x in range(x_min, x_max): - row[x] = handler diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/processors.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/processors.py deleted file mode 100644 index 722658a846b..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/processors.py +++ /dev/null @@ -1,1029 +0,0 @@ -""" -Processors are little transformation blocks that transform the fragments list -from a buffer before the BufferControl will render it to the screen. - -They can insert fragments before or after, or highlight fragments by replacing the -fragment types. -""" -import re -from abc import ABCMeta, abstractmethod -from typing import ( - TYPE_CHECKING, - Callable, - Hashable, - List, - Optional, - Tuple, - Type, - Union, - cast, -) - -from prompt_toolkit.application.current import get_app -from prompt_toolkit.cache import SimpleCache -from prompt_toolkit.document import Document -from prompt_toolkit.filters import FilterOrBool, to_filter, vi_insert_multiple_mode -from prompt_toolkit.formatted_text import ( - AnyFormattedText, - StyleAndTextTuples, - to_formatted_text, -) -from prompt_toolkit.formatted_text.utils import fragment_list_len, fragment_list_to_text -from prompt_toolkit.search import SearchDirection -from prompt_toolkit.utils import to_int, to_str - -from .utils import explode_text_fragments - -if TYPE_CHECKING: - from .controls import BufferControl, UIContent - -__all__ = [ - "Processor", - "TransformationInput", - "Transformation", - "DummyProcessor", - "HighlightSearchProcessor", - "HighlightIncrementalSearchProcessor", - "HighlightSelectionProcessor", - "PasswordProcessor", - "HighlightMatchingBracketProcessor", - "DisplayMultipleCursors", - "BeforeInput", - "ShowArg", - "AfterInput", - "AppendAutoSuggestion", - "ConditionalProcessor", - "ShowLeadingWhiteSpaceProcessor", - "ShowTrailingWhiteSpaceProcessor", - "TabsProcessor", - "ReverseSearchProcessor", - "DynamicProcessor", - "merge_processors", -] - - -class Processor(metaclass=ABCMeta): - """ - Manipulate the fragments for a given line in a - :class:`~prompt_toolkit.layout.controls.BufferControl`. - """ - - @abstractmethod - def apply_transformation( - self, transformation_input: "TransformationInput" - ) -> "Transformation": - """ - Apply transformation. Returns a :class:`.Transformation` instance. - - :param transformation_input: :class:`.TransformationInput` object. - """ - return Transformation(transformation_input.fragments) - - -SourceToDisplay = Callable[[int], int] -DisplayToSource = Callable[[int], int] - - -class TransformationInput: - """ - :param buffer_control: :class:`.BufferControl` instance. - :param lineno: The number of the line to which we apply the processor. - :param source_to_display: A function that returns the position in the - `fragments` for any position in the source string. (This takes - previous processors into account.) - :param fragments: List of fragments that we can transform. (Received from the - previous processor.) - """ - - def __init__( - self, - buffer_control: "BufferControl", - document: Document, - lineno: int, - source_to_display: SourceToDisplay, - fragments: StyleAndTextTuples, - width: int, - height: int, - ) -> None: - - self.buffer_control = buffer_control - self.document = document - self.lineno = lineno - self.source_to_display = source_to_display - self.fragments = fragments - self.width = width - self.height = height - - def unpack( - self, - ) -> Tuple[ - "BufferControl", Document, int, SourceToDisplay, StyleAndTextTuples, int, int - ]: - return ( - self.buffer_control, - self.document, - self.lineno, - self.source_to_display, - self.fragments, - self.width, - self.height, - ) - - -class Transformation: - """ - Transformation result, as returned by :meth:`.Processor.apply_transformation`. - - Important: Always make sure that the length of `document.text` is equal to - the length of all the text in `fragments`! - - :param fragments: The transformed fragments. To be displayed, or to pass to - the next processor. - :param source_to_display: Cursor position transformation from original - string to transformed string. - :param display_to_source: Cursor position transformed from source string to - original string. - """ - - def __init__( - self, - fragments: StyleAndTextTuples, - source_to_display: Optional[SourceToDisplay] = None, - display_to_source: Optional[DisplayToSource] = None, - ) -> None: - - self.fragments = fragments - self.source_to_display = source_to_display or (lambda i: i) - self.display_to_source = display_to_source or (lambda i: i) - - -class DummyProcessor(Processor): - """ - A `Processor` that doesn't do anything. - """ - - def apply_transformation( - self, transformation_input: TransformationInput - ) -> Transformation: - return Transformation(transformation_input.fragments) - - -class HighlightSearchProcessor(Processor): - """ - Processor that highlights search matches in the document. - Note that this doesn't support multiline search matches yet. - - The style classes 'search' and 'search.current' will be applied to the - content. - """ - - _classname = "search" - _classname_current = "search.current" - - def _get_search_text(self, buffer_control: "BufferControl") -> str: - """ - The text we are searching for. - """ - return buffer_control.search_state.text - - def apply_transformation( - self, transformation_input: TransformationInput - ) -> Transformation: - - ( - buffer_control, - document, - lineno, - source_to_display, - fragments, - _, - _, - ) = transformation_input.unpack() - - search_text = self._get_search_text(buffer_control) - searchmatch_fragment = f" class:{self._classname} " - searchmatch_current_fragment = f" class:{self._classname_current} " - - if search_text and not get_app().is_done: - # For each search match, replace the style string. - line_text = fragment_list_to_text(fragments) - fragments = explode_text_fragments(fragments) - - if buffer_control.search_state.ignore_case(): - flags = re.IGNORECASE - else: - flags = re.RegexFlag(0) - - # Get cursor column. - cursor_column: Optional[int] - if document.cursor_position_row == lineno: - cursor_column = source_to_display(document.cursor_position_col) - else: - cursor_column = None - - for match in re.finditer(re.escape(search_text), line_text, flags=flags): - if cursor_column is not None: - on_cursor = match.start() <= cursor_column < match.end() - else: - on_cursor = False - - for i in range(match.start(), match.end()): - old_fragment, text, *_ = fragments[i] - if on_cursor: - fragments[i] = ( - old_fragment + searchmatch_current_fragment, - fragments[i][1], - ) - else: - fragments[i] = ( - old_fragment + searchmatch_fragment, - fragments[i][1], - ) - - return Transformation(fragments) - - -class HighlightIncrementalSearchProcessor(HighlightSearchProcessor): - """ - Highlight the search terms that are used for highlighting the incremental - search. The style class 'incsearch' will be applied to the content. - - Important: this requires the `preview_search=True` flag to be set for the - `BufferControl`. Otherwise, the cursor position won't be set to the search - match while searching, and nothing happens. - """ - - _classname = "incsearch" - _classname_current = "incsearch.current" - - def _get_search_text(self, buffer_control: "BufferControl") -> str: - """ - The text we are searching for. - """ - # When the search buffer has focus, take that text. - search_buffer = buffer_control.search_buffer - if search_buffer is not None and search_buffer.text: - return search_buffer.text - return "" - - -class HighlightSelectionProcessor(Processor): - """ - Processor that highlights the selection in the document. - """ - - def apply_transformation( - self, transformation_input: TransformationInput - ) -> Transformation: - ( - buffer_control, - document, - lineno, - source_to_display, - fragments, - _, - _, - ) = transformation_input.unpack() - - selected_fragment = " class:selected " - - # In case of selection, highlight all matches. - selection_at_line = document.selection_range_at_line(lineno) - - if selection_at_line: - from_, to = selection_at_line - from_ = source_to_display(from_) - to = source_to_display(to) - - fragments = explode_text_fragments(fragments) - - if from_ == 0 and to == 0 and len(fragments) == 0: - # When this is an empty line, insert a space in order to - # visualise the selection. - return Transformation([(selected_fragment, " ")]) - else: - for i in range(from_, to): - if i < len(fragments): - old_fragment, old_text, *_ = fragments[i] - fragments[i] = (old_fragment + selected_fragment, old_text) - elif i == len(fragments): - fragments.append((selected_fragment, " ")) - - return Transformation(fragments) - - -class PasswordProcessor(Processor): - """ - Processor that masks the input. (For passwords.) - - :param char: (string) Character to be used. "*" by default. - """ - - def __init__(self, char: str = "*") -> None: - self.char = char - - def apply_transformation(self, ti: TransformationInput) -> Transformation: - fragments: StyleAndTextTuples = cast( - StyleAndTextTuples, - [ - (style, self.char * len(text), *handler) - for style, text, *handler in ti.fragments - ], - ) - - return Transformation(fragments) - - -class HighlightMatchingBracketProcessor(Processor): - """ - When the cursor is on or right after a bracket, it highlights the matching - bracket. - - :param max_cursor_distance: Only highlight matching brackets when the - cursor is within this distance. (From inside a `Processor`, we can't - know which lines will be visible on the screen. But we also don't want - to scan the whole document for matching brackets on each key press, so - we limit to this value.) - """ - - _closing_braces = "])}>" - - def __init__( - self, chars: str = "[](){}<>", max_cursor_distance: int = 1000 - ) -> None: - self.chars = chars - self.max_cursor_distance = max_cursor_distance - - self._positions_cache: SimpleCache[ - Hashable, List[Tuple[int, int]] - ] = SimpleCache(maxsize=8) - - 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] - - # Try for the character under the cursor. - if document.current_char and document.current_char in self.chars: - pos = document.find_matching_bracket_position( - start_pos=document.cursor_position - self.max_cursor_distance, - end_pos=document.cursor_position + self.max_cursor_distance, - ) - - # Try for the character before the cursor. - elif ( - document.char_before_cursor - and document.char_before_cursor in self._closing_braces - and document.char_before_cursor in self.chars - ): - document = Document(document.text, document.cursor_position - 1) - - pos = document.find_matching_bracket_position( - start_pos=document.cursor_position - self.max_cursor_distance, - end_pos=document.cursor_position + self.max_cursor_distance, - ) - else: - pos = None - - # Return a list of (row, col) tuples that need to be highlighted. - if pos: - pos += document.cursor_position # pos is relative. - row, col = document.translate_index_to_position(pos) - return [ - (row, col), - (document.cursor_position_row, document.cursor_position_col), - ] - else: - return [] - - def apply_transformation( - self, transformation_input: TransformationInput - ) -> Transformation: - - ( - buffer_control, - document, - lineno, - source_to_display, - fragments, - _, - _, - ) = transformation_input.unpack() - - # When the application is in the 'done' state, don't highlight. - if get_app().is_done: - return Transformation(fragments) - - # Get the highlight positions. - key = (get_app().render_counter, document.text, document.cursor_position) - positions = self._positions_cache.get( - key, lambda: self._get_positions_to_highlight(document) - ) - - # Apply if positions were found at this line. - if positions: - for row, col in positions: - if row == lineno: - col = source_to_display(col) - fragments = explode_text_fragments(fragments) - style, text, *_ = fragments[col] - - if col == document.cursor_position_col: - style += " class:matching-bracket.cursor " - else: - style += " class:matching-bracket.other " - - fragments[col] = (style, text) - - return Transformation(fragments) - - -class DisplayMultipleCursors(Processor): - """ - When we're in Vi block insert mode, display all the cursors. - """ - - def apply_transformation( - self, transformation_input: TransformationInput - ) -> Transformation: - - ( - buffer_control, - document, - lineno, - source_to_display, - fragments, - _, - _, - ) = transformation_input.unpack() - - buff = buffer_control.buffer - - if vi_insert_multiple_mode(): - cursor_positions = buff.multiple_cursor_positions - fragments = explode_text_fragments(fragments) - - # If any cursor appears on the current line, highlight that. - start_pos = document.translate_row_col_to_index(lineno, 0) - end_pos = start_pos + len(document.lines[lineno]) - - fragment_suffix = " class:multiple-cursors" - - for p in cursor_positions: - if start_pos <= p <= end_pos: - column = source_to_display(p - start_pos) - - # Replace fragment. - try: - style, text, *_ = fragments[column] - except IndexError: - # Cursor needs to be displayed after the current text. - fragments.append((fragment_suffix, " ")) - else: - style += fragment_suffix - fragments[column] = (style, text) - - return Transformation(fragments) - else: - return Transformation(fragments) - - -class BeforeInput(Processor): - """ - Insert text before the input. - - :param text: This can be either plain text or formatted text - (or a callable that returns any of those). - :param style: style to be applied to this prompt/prefix. - """ - - def __init__(self, text: AnyFormattedText, style: str = "") -> None: - self.text = text - self.style = style - - def apply_transformation(self, ti: TransformationInput) -> Transformation: - source_to_display: Optional[SourceToDisplay] - display_to_source: Optional[DisplayToSource] - - if ti.lineno == 0: - # Get fragments. - fragments_before = to_formatted_text(self.text, self.style) - fragments = fragments_before + ti.fragments - - shift_position = fragment_list_len(fragments_before) - source_to_display = lambda i: i + shift_position - display_to_source = lambda i: i - shift_position - else: - fragments = ti.fragments - source_to_display = None - display_to_source = None - - return Transformation( - fragments, - source_to_display=source_to_display, - display_to_source=display_to_source, - ) - - def __repr__(self) -> str: - return f"BeforeInput({self.text!r}, {self.style!r})" - - -class ShowArg(BeforeInput): - """ - Display the 'arg' in front of the input. - - This was used by the `PromptSession`, but now it uses the - `Window.get_line_prefix` function instead. - """ - - def __init__(self) -> None: - super().__init__(self._get_text_fragments) - - def _get_text_fragments(self) -> StyleAndTextTuples: - app = get_app() - if app.key_processor.arg is None: - return [] - else: - arg = app.key_processor.arg - - return [ - ("class:prompt.arg", "(arg: "), - ("class:prompt.arg.text", str(arg)), - ("class:prompt.arg", ") "), - ] - - def __repr__(self) -> str: - return "ShowArg()" - - -class AfterInput(Processor): - """ - Insert text after the input. - - :param text: This can be either plain text or formatted text - (or a callable that returns any of those). - :param style: style to be applied to this prompt/prefix. - """ - - def __init__(self, text: AnyFormattedText, style: str = "") -> None: - self.text = text - self.style = style - - def apply_transformation(self, ti: TransformationInput) -> Transformation: - # Insert fragments after the last line. - if ti.lineno == ti.document.line_count - 1: - # Get fragments. - fragments_after = to_formatted_text(self.text, self.style) - return Transformation(fragments=ti.fragments + fragments_after) - else: - return Transformation(fragments=ti.fragments) - - def __repr__(self) -> str: - return f"{self.__class__.__name__}({self.text!r}, style={self.style!r})" - - -class AppendAutoSuggestion(Processor): - """ - Append the auto suggestion to the input. - (The user can then press the right arrow the insert the suggestion.) - """ - - def __init__(self, style: str = "class:auto-suggestion") -> None: - self.style = style - - def apply_transformation(self, ti: TransformationInput) -> Transformation: - # Insert fragments after the last line. - if ti.lineno == ti.document.line_count - 1: - buffer = ti.buffer_control.buffer - - if buffer.suggestion and ti.document.is_cursor_at_the_end: - suggestion = buffer.suggestion.text - else: - suggestion = "" - - return Transformation(fragments=ti.fragments + [(self.style, suggestion)]) - else: - return Transformation(fragments=ti.fragments) - - -class ShowLeadingWhiteSpaceProcessor(Processor): - """ - Make leading whitespace visible. - - :param get_char: Callable that returns one character. - """ - - def __init__( - self, - get_char: Optional[Callable[[], str]] = None, - style: str = "class:leading-whitespace", - ) -> None: - def default_get_char() -> str: - if "\xb7".encode(get_app().output.encoding(), "replace") == b"?": - return "." - else: - return "\xb7" - - self.style = style - self.get_char = get_char or default_get_char - - def apply_transformation(self, ti: TransformationInput) -> Transformation: - fragments = ti.fragments - - # Walk through all te fragments. - if fragments and fragment_list_to_text(fragments).startswith(" "): - t = (self.style, self.get_char()) - fragments = explode_text_fragments(fragments) - - for i in range(len(fragments)): - if fragments[i][1] == " ": - fragments[i] = t - else: - break - - return Transformation(fragments) - - -class ShowTrailingWhiteSpaceProcessor(Processor): - """ - Make trailing whitespace visible. - - :param get_char: Callable that returns one character. - """ - - def __init__( - self, - get_char: Optional[Callable[[], str]] = None, - style: str = "class:training-whitespace", - ) -> None: - def default_get_char() -> str: - if "\xb7".encode(get_app().output.encoding(), "replace") == b"?": - return "." - else: - return "\xb7" - - self.style = style - self.get_char = get_char or default_get_char - - def apply_transformation(self, ti: TransformationInput) -> Transformation: - fragments = ti.fragments - - if fragments and fragments[-1][1].endswith(" "): - t = (self.style, self.get_char()) - fragments = explode_text_fragments(fragments) - - # Walk backwards through all te fragments and replace whitespace. - for i in range(len(fragments) - 1, -1, -1): - char = fragments[i][1] - if char == " ": - fragments[i] = t - else: - break - - return Transformation(fragments) - - -class TabsProcessor(Processor): - """ - Render tabs as spaces (instead of ^I) or make them visible (for instance, - by replacing them with dots.) - - :param tabstop: Horizontal space taken by a tab. (`int` or callable that - returns an `int`). - :param char1: Character or callable that returns a character (text of - length one). This one is used for the first space taken by the tab. - :param char2: Like `char1`, but for the rest of the space. - """ - - def __init__( - self, - tabstop: Union[int, Callable[[], int]] = 4, - char1: Union[str, Callable[[], str]] = "|", - char2: Union[str, Callable[[], str]] = "\u2508", - style: str = "class:tab", - ) -> None: - - self.char1 = char1 - self.char2 = char2 - self.tabstop = tabstop - self.style = style - - def apply_transformation(self, ti: TransformationInput) -> Transformation: - tabstop = to_int(self.tabstop) - style = self.style - - # Create separator for tabs. - separator1 = to_str(self.char1) - separator2 = to_str(self.char2) - - # Transform fragments. - fragments = explode_text_fragments(ti.fragments) - - position_mappings = {} - result_fragments: StyleAndTextTuples = [] - pos = 0 - - for i, fragment_and_text in enumerate(fragments): - position_mappings[i] = pos - - if fragment_and_text[1] == "\t": - # Calculate how many characters we have to insert. - count = tabstop - (pos % tabstop) - if count == 0: - count = tabstop - - # Insert tab. - result_fragments.append((style, separator1)) - result_fragments.append((style, separator2 * (count - 1))) - pos += count - else: - result_fragments.append(fragment_and_text) - pos += 1 - - position_mappings[len(fragments)] = pos - # Add `pos+1` to mapping, because the cursor can be right after the - # line as well. - position_mappings[len(fragments) + 1] = pos + 1 - - def source_to_display(from_position: int) -> int: - "Maps original cursor position to the new one." - return position_mappings[from_position] - - def display_to_source(display_pos: int) -> int: - "Maps display cursor position to the original one." - position_mappings_reversed = {v: k for k, v in position_mappings.items()} - - while display_pos >= 0: - try: - return position_mappings_reversed[display_pos] - except KeyError: - display_pos -= 1 - return 0 - - return Transformation( - result_fragments, - source_to_display=source_to_display, - display_to_source=display_to_source, - ) - - -class ReverseSearchProcessor(Processor): - """ - Process to display the "(reverse-i-search)`...`:..." stuff around - the search buffer. - - Note: This processor is meant to be applied to the BufferControl that - contains the search buffer, it's not meant for the original input. - """ - - _excluded_input_processors: List[Type[Processor]] = [ - HighlightSearchProcessor, - HighlightSelectionProcessor, - BeforeInput, - AfterInput, - ] - - def _get_main_buffer( - self, buffer_control: "BufferControl" - ) -> Optional["BufferControl"]: - from prompt_toolkit.layout.controls import BufferControl - - prev_control = get_app().layout.search_target_buffer_control - if ( - isinstance(prev_control, BufferControl) - and prev_control.search_buffer_control == buffer_control - ): - return prev_control - return None - - def _content( - 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]: - """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. - if isinstance(item, _MergedProcessor): - accepted_processors = [filter_processor(p) for p in item.processors] - return merge_processors( - [p for p in accepted_processors if p is not None] - ) - - # For a `ConditionalProcessor`, check the body. - elif isinstance(item, ConditionalProcessor): - p = filter_processor(item.processor) - if p: - return ConditionalProcessor(p, item.filter) - - # Otherwise, check the processor itself. - else: - if not isinstance(item, excluded_processors): - return item - - return None - - filtered_processor = filter_processor( - merge_processors(main_control.input_processors or []) - ) - highlight_processor = HighlightIncrementalSearchProcessor() - - if filtered_processor: - new_processors = [filtered_processor, highlight_processor] - else: - new_processors = [highlight_processor] - - from .controls import SearchBufferControl - - assert isinstance(ti.buffer_control, SearchBufferControl) - - buffer_control = BufferControl( - buffer=main_control.buffer, - input_processors=new_processors, - include_default_input_processors=False, - lexer=main_control.lexer, - preview_search=True, - search_buffer_control=ti.buffer_control, - ) - - return buffer_control.create_content(ti.width, ti.height, preview_search=True) - - def apply_transformation(self, ti: TransformationInput) -> Transformation: - from .controls import SearchBufferControl - - assert isinstance( - ti.buffer_control, SearchBufferControl - ), "`ReverseSearchProcessor` should be applied to a `SearchBufferControl` only." - - source_to_display: Optional[SourceToDisplay] - display_to_source: Optional[DisplayToSource] - - main_control = self._get_main_buffer(ti.buffer_control) - - if ti.lineno == 0 and main_control: - content = self._content(main_control, ti) - - # Get the line from the original document for this search. - line_fragments = content.get_line(content.cursor_position.y) - - if main_control.search_state.direction == SearchDirection.FORWARD: - direction_text = "i-search" - else: - direction_text = "reverse-i-search" - - fragments_before: StyleAndTextTuples = [ - ("class:prompt.search", "("), - ("class:prompt.search", direction_text), - ("class:prompt.search", ")`"), - ] - - fragments = ( - fragments_before - + [ - ("class:prompt.search.text", fragment_list_to_text(ti.fragments)), - ("", "': "), - ] - + line_fragments - ) - - shift_position = fragment_list_len(fragments_before) - source_to_display = lambda i: i + shift_position - display_to_source = lambda i: i - shift_position - else: - source_to_display = None - display_to_source = None - fragments = ti.fragments - - return Transformation( - fragments, - source_to_display=source_to_display, - display_to_source=display_to_source, - ) - - -class ConditionalProcessor(Processor): - """ - Processor that applies another processor, according to a certain condition. - Example:: - - # Create a function that returns whether or not the processor should - # currently be applied. - def highlight_enabled(): - return true_or_false - - # Wrapped it in a `ConditionalProcessor` for usage in a `BufferControl`. - BufferControl(input_processors=[ - ConditionalProcessor(HighlightSearchProcessor(), - Condition(highlight_enabled))]) - - :param processor: :class:`.Processor` instance. - :param filter: :class:`~prompt_toolkit.filters.Filter` instance. - """ - - def __init__(self, processor: Processor, filter: FilterOrBool) -> None: - self.processor = processor - self.filter = to_filter(filter) - - def apply_transformation( - self, transformation_input: TransformationInput - ) -> Transformation: - # Run processor when enabled. - if self.filter(): - return self.processor.apply_transformation(transformation_input) - else: - return Transformation(transformation_input.fragments) - - def __repr__(self) -> str: - return "{}(processor={!r}, filter={!r})".format( - self.__class__.__name__, - self.processor, - self.filter, - ) - - -class DynamicProcessor(Processor): - """ - Processor class that dynamically returns any Processor. - - :param get_processor: Callable that returns a :class:`.Processor` instance. - """ - - def __init__(self, get_processor: Callable[[], Optional[Processor]]) -> None: - self.get_processor = get_processor - - def apply_transformation(self, ti: TransformationInput) -> Transformation: - processor = self.get_processor() or DummyProcessor() - return processor.apply_transformation(ti) - - -def merge_processors(processors: List[Processor]) -> Processor: - """ - Merge multiple `Processor` objects into one. - """ - if len(processors) == 0: - return DummyProcessor() - - if len(processors) == 1: - return processors[0] # Nothing to merge. - - return _MergedProcessor(processors) - - -class _MergedProcessor(Processor): - """ - Processor that groups multiple other `Processor` objects, but exposes an - API as if it is one `Processor`. - """ - - def __init__(self, processors: List[Processor]): - self.processors = processors - - def apply_transformation(self, ti: TransformationInput) -> Transformation: - source_to_display_functions = [ti.source_to_display] - display_to_source_functions = [] - fragments = ti.fragments - - def source_to_display(i: int) -> int: - """Translate x position from the buffer to the x position in the - processor fragments list.""" - for f in source_to_display_functions: - i = f(i) - return i - - for p in self.processors: - transformation = p.apply_transformation( - TransformationInput( - ti.buffer_control, - ti.document, - ti.lineno, - source_to_display, - fragments, - ti.width, - ti.height, - ) - ) - fragments = transformation.fragments - display_to_source_functions.append(transformation.display_to_source) - source_to_display_functions.append(transformation.source_to_display) - - def display_to_source(i: int) -> int: - for f in reversed(display_to_source_functions): - i = f(i) - return i - - # In the case of a nested _MergedProcessor, each processor wants to - # receive a 'source_to_display' function (as part of the - # TransformationInput) that has everything in the chain before - # included, because it can be called as part of the - # `apply_transformation` function. However, this first - # `source_to_display` should not be part of the output that we are - # returning. (This is the most consistent with `display_to_source`.) - del source_to_display_functions[:1] - - return Transformation(fragments, source_to_display, display_to_source) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/screen.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/screen.py deleted file mode 100644 index 8874fba6f88..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/screen.py +++ /dev/null @@ -1,328 +0,0 @@ -from collections import defaultdict -from typing import TYPE_CHECKING, Callable, DefaultDict, Dict, List, Optional, Tuple - -from prompt_toolkit.cache import FastDictCache -from prompt_toolkit.data_structures import Point -from prompt_toolkit.utils import get_cwidth - -if TYPE_CHECKING: - from .containers import Window - - -__all__ = [ - "Screen", - "Char", -] - - -class Char: - """ - Represent a single character in a :class:`.Screen`. - - This should be considered immutable. - - :param char: A single character (can be a double-width character). - :param style: A style string. (Can contain classnames.) - """ - - __slots__ = ("char", "style", "width") - - # 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] = { - "\x00": "^@", # Control space - "\x01": "^A", - "\x02": "^B", - "\x03": "^C", - "\x04": "^D", - "\x05": "^E", - "\x06": "^F", - "\x07": "^G", - "\x08": "^H", - "\x09": "^I", - "\x0a": "^J", - "\x0b": "^K", - "\x0c": "^L", - "\x0d": "^M", - "\x0e": "^N", - "\x0f": "^O", - "\x10": "^P", - "\x11": "^Q", - "\x12": "^R", - "\x13": "^S", - "\x14": "^T", - "\x15": "^U", - "\x16": "^V", - "\x17": "^W", - "\x18": "^X", - "\x19": "^Y", - "\x1a": "^Z", - "\x1b": "^[", # Escape - "\x1c": "^\\", - "\x1d": "^]", - "\x1e": "^^", - "\x1f": "^_", - "\x7f": "^?", # ASCII Delete (backspace). - # Special characters. All visualized like Vim does. - "\x80": "<80>", - "\x81": "<81>", - "\x82": "<82>", - "\x83": "<83>", - "\x84": "<84>", - "\x85": "<85>", - "\x86": "<86>", - "\x87": "<87>", - "\x88": "<88>", - "\x89": "<89>", - "\x8a": "<8a>", - "\x8b": "<8b>", - "\x8c": "<8c>", - "\x8d": "<8d>", - "\x8e": "<8e>", - "\x8f": "<8f>", - "\x90": "<90>", - "\x91": "<91>", - "\x92": "<92>", - "\x93": "<93>", - "\x94": "<94>", - "\x95": "<95>", - "\x96": "<96>", - "\x97": "<97>", - "\x98": "<98>", - "\x99": "<99>", - "\x9a": "<9a>", - "\x9b": "<9b>", - "\x9c": "<9c>", - "\x9d": "<9d>", - "\x9e": "<9e>", - "\x9f": "<9f>", - # For the non-breaking space: visualize like Emacs does by default. - # (Print a space, but attach the 'nbsp' class that applies the - # underline style.) - "\xa0": " ", - } - - def __init__(self, char: str = " ", style: str = "") -> None: - # If this character has to be displayed otherwise, take that one. - if char in self.display_mappings: - if char == "\xa0": - style += " class:nbsp " # Will be underlined. - else: - style += " class:control-character " - - char = self.display_mappings[char] - - self.char = char - self.style = style - - # Calculate width. (We always need this, so better to store it directly - # as a member for performance.) - self.width = get_cwidth(char) - - # 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: - return self.char == other.char and self.style == other.style - - 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 - - if not TYPE_CHECKING: - __eq__ = _equal - __ne__ = _not_equal - - def __repr__(self) -> str: - return f"{self.__class__.__name__}({self.char!r}, {self.style!r})" - - -_CHAR_CACHE: FastDictCache[Tuple[str, str], Char] = FastDictCache( - Char, size=1000 * 1000 -) -Transparent = "[transparent]" - - -class Screen: - """ - Two dimensional buffer of :class:`.Char` instances. - """ - - def __init__( - self, - default_char: Optional[Char] = None, - initial_width: int = 0, - initial_height: int = 0, - ) -> None: - - if default_char is None: - default_char2 = _CHAR_CACHE[" ", Transparent] - else: - default_char2 = default_char - - self.data_buffer: DefaultDict[int, DefaultDict[int, Char]] = defaultdict( - lambda: defaultdict(lambda: default_char2) - ) - - #: Escape sequences to be injected. - self.zero_width_escapes: DefaultDict[int, DefaultDict[int, str]] = defaultdict( - lambda: defaultdict(lambda: "") - ) - - #: Position of the cursor. - self.cursor_positions: Dict[ - "Window", Point - ] = {} # Map `Window` objects to `Point` objects. - - #: Visibility of the cursor. - self.show_cursor = True - - #: (Optional) Where to position the menu. E.g. at the start of a completion. - #: (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 - ] = {} # Map `Window` objects to `Point` objects. - - #: Currently used width/height of the screen. This will increase when - #: data is written to the screen. - self.width = initial_width or 0 - self.height = initial_height or 0 - - # Windows that have been drawn. (Each `Window` class will add itself to - # this list.) - self.visible_windows_to_write_positions: Dict["Window", "WritePosition"] = {} - - # List of (z_index, draw_func) - self._draw_float_functions: List[Tuple[int, Callable[[], None]]] = [] - - @property - 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: - """ - Set the cursor position for a given window. - """ - self.cursor_positions[window] = position - - 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: - """ - Get the cursor position for a given window. - Returns a `Point`. - """ - try: - return self.cursor_positions[window] - except KeyError: - return Point(x=0, y=0) - - 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.) - """ - try: - return self.menu_positions[window] - except KeyError: - try: - return self.cursor_positions[window] - except KeyError: - return Point(x=0, y=0) - - def draw_with_z_index(self, z_index: int, draw_func: Callable[[], None]) -> None: - """ - Add a draw-function for a `Window` which has a >= 0 z_index. - This will be postponed until `draw_all_floats` is called. - """ - self._draw_float_functions.append((z_index, draw_func)) - - def draw_all_floats(self) -> None: - """ - Draw all float functions in order of z-index. - """ - # We keep looping because some draw functions could add new functions - # to this list. See `FloatContainer`. - while self._draw_float_functions: - # Sort the floats that we have so far by z_index. - functions = sorted(self._draw_float_functions, key=lambda item: item[0]) - - # Draw only one at a time, then sort everything again. Now floats - # might have been added. - self._draw_float_functions = functions[1:] - functions[0][1]() - - def append_style_to_content(self, style_str: str) -> None: - """ - For all the characters in the screen. - Set the style string to the given `style_str`. - """ - b = self.data_buffer - char_cache = _CHAR_CACHE - - append_style = " " + style_str - - for y, row in b.items(): - for x, char in row.items(): - b[y][x] = char_cache[char.char, char.style + append_style] - - def fill_area( - self, write_position: "WritePosition", style: str = "", after: bool = False - ) -> None: - """ - Fill the content of this area, using the given `style`. - The style is prepended before whatever was here before. - """ - if not style.strip(): - return - - xmin = write_position.xpos - xmax = write_position.xpos + write_position.width - char_cache = _CHAR_CACHE - data_buffer = self.data_buffer - - if after: - append_style = " " + style - prepend_style = "" - else: - append_style = "" - prepend_style = style + " " - - for y in range( - write_position.ypos, write_position.ypos + write_position.height - ): - row = data_buffer[y] - for x in range(xmin, xmax): - cell = row[x] - row[x] = char_cache[ - cell.char, prepend_style + cell.style + append_style - ] - - -class WritePosition: - def __init__(self, xpos: int, ypos: int, width: int, height: int) -> None: - assert height >= 0 - assert width >= 0 - # xpos and ypos can be negative. (A float can be partially visible.) - - self.xpos = xpos - self.ypos = ypos - self.width = width - self.height = height - - def __repr__(self) -> str: - return "{}(x={!r}, y={!r}, width={!r}, height={!r})".format( - self.__class__.__name__, - self.xpos, - self.ypos, - self.width, - self.height, - ) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/scrollable_pane.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/scrollable_pane.py deleted file mode 100644 index a5500d7f7ce..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/scrollable_pane.py +++ /dev/null @@ -1,493 +0,0 @@ -from typing import Dict, List, Optional - -from prompt_toolkit.data_structures import Point -from prompt_toolkit.filters import FilterOrBool, to_filter -from prompt_toolkit.key_binding import KeyBindingsBase -from prompt_toolkit.mouse_events import MouseEvent - -from .containers import Container, ScrollOffsets -from .dimension import AnyDimension, Dimension, sum_layout_dimensions, to_dimension -from .mouse_handlers import MouseHandler, MouseHandlers -from .screen import Char, Screen, WritePosition - -__all__ = ["ScrollablePane"] - -# Never go beyond this height, because performance will degrade. -MAX_AVAILABLE_HEIGHT = 10_000 - - -class ScrollablePane(Container): - """ - Container widget that exposes a larger virtual screen to its content and - displays it in a vertical scrollbale region. - - Typically this is wrapped in a large `HSplit` container. Make sure in that - case to not specify a `height` dimension of the `HSplit`, so that it will - scale according to the content. - - .. note:: - - If you want to display a completion menu for widgets in this - `ScrollablePane`, then it's still a good practice to use a - `FloatContainer` with a `CompletionsMenu` in a `Float` at the top-level - of the layout hierarchy, rather then nesting a `FloatContainer` in this - `ScrollablePane`. (Otherwise, it's possible that the completion menu - is clipped.) - - :param content: The content container. - :param scrolloffset: Try to keep the cursor within this distance from the - top/bottom (left/right offset is not used). - :param keep_cursor_visible: When `True`, automatically scroll the pane so - that the cursor (of the focused window) is always visible. - :param keep_focused_window_visible: When `True`, automatically scroll th e - pane so that the focused window is visible, or as much visible as - possible if it doen't completely fit the screen. - :param max_available_height: Always constraint the height to this amount - for performance reasons. - :param width: When given, use this width instead of looking at the children. - :param height: When given, use this height instead of looking at the children. - :param show_scrollbar: When `True` display a scrollbar on the right. - """ - - def __init__( - self, - content: Container, - scroll_offsets: Optional[ScrollOffsets] = None, - keep_cursor_visible: FilterOrBool = True, - keep_focused_window_visible: FilterOrBool = True, - max_available_height: int = MAX_AVAILABLE_HEIGHT, - width: AnyDimension = None, - height: AnyDimension = None, - show_scrollbar: FilterOrBool = True, - display_arrows: FilterOrBool = True, - up_arrow_symbol: str = "^", - down_arrow_symbol: str = "v", - ) -> None: - self.content = content - self.scroll_offsets = scroll_offsets or ScrollOffsets(top=1, bottom=1) - self.keep_cursor_visible = to_filter(keep_cursor_visible) - self.keep_focused_window_visible = to_filter(keep_focused_window_visible) - self.max_available_height = max_available_height - self.width = width - self.height = height - self.show_scrollbar = to_filter(show_scrollbar) - self.display_arrows = to_filter(display_arrows) - self.up_arrow_symbol = up_arrow_symbol - self.down_arrow_symbol = down_arrow_symbol - - self.vertical_scroll = 0 - - def __repr__(self) -> str: - return f"ScrollablePane({self.content!r})" - - def reset(self) -> None: - self.content.reset() - - def preferred_width(self, max_available_width: int) -> Dimension: - if self.width is not None: - return to_dimension(self.width) - - # We're only scrolling vertical. So the preferred width is equal to - # that of the content. - content_width = self.content.preferred_width(max_available_width) - - # If a scrollbar needs to be displayed, add +1 to the content width. - if self.show_scrollbar(): - return sum_layout_dimensions([Dimension.exact(1), content_width]) - - return content_width - - def preferred_height(self, width: int, max_available_height: int) -> Dimension: - if self.height is not None: - return to_dimension(self.height) - - # Prefer a height large enough so that it fits all the content. If not, - # we'll make the pane scrollable. - if self.show_scrollbar(): - # If `show_scrollbar` is set. Always reserve space for the scrollbar. - width -= 1 - - dimension = self.content.preferred_height(width, self.max_available_height) - - # Only take 'preferred' into account. Min/max can be anything. - return Dimension(min=0, preferred=dimension.preferred) - - def write_to_screen( - self, - screen: Screen, - mouse_handlers: MouseHandlers, - write_position: WritePosition, - parent_style: str, - erase_bg: bool, - z_index: Optional[int], - ) -> None: - """ - Render scrollable pane content. - - This works by rendering on an off-screen canvas, and copying over the - visible region. - """ - show_scrollbar = self.show_scrollbar() - - if show_scrollbar: - virtual_width = write_position.width - 1 - else: - virtual_width = write_position.width - - # Compute preferred height again. - virtual_height = self.content.preferred_height( - virtual_width, self.max_available_height - ).preferred - - # Ensure virtual height is at least the available height. - virtual_height = max(virtual_height, write_position.height) - virtual_height = min(virtual_height, self.max_available_height) - - # First, write the content to a virtual screen, then copy over the - # visible part to the real screen. - temp_screen = Screen(default_char=Char(char=" ", style=parent_style)) - temp_write_position = WritePosition( - xpos=0, ypos=0, width=virtual_width, height=virtual_height - ) - - temp_mouse_handlers = MouseHandlers() - - self.content.write_to_screen( - temp_screen, - temp_mouse_handlers, - temp_write_position, - parent_style, - erase_bg, - z_index, - ) - temp_screen.draw_all_floats() - - # If anything in the virtual screen is focused, move vertical scroll to - from prompt_toolkit.application import get_app - - focused_window = get_app().layout.current_window - - try: - visible_win_write_pos = temp_screen.visible_windows_to_write_positions[ - focused_window - ] - except KeyError: - pass # No window focused here. Don't scroll. - else: - # Make sure this window is visible. - self._make_window_visible( - write_position.height, - virtual_height, - visible_win_write_pos, - temp_screen.cursor_positions.get(focused_window), - ) - - # Copy over virtual screen and zero width escapes to real screen. - self._copy_over_screen(screen, temp_screen, write_position, virtual_width) - - # Copy over mouse handlers. - self._copy_over_mouse_handlers( - mouse_handlers, temp_mouse_handlers, write_position, virtual_width - ) - - # Set screen.width/height. - ypos = write_position.ypos - xpos = write_position.xpos - - screen.width = max(screen.width, xpos + virtual_width) - screen.height = max(screen.height, ypos + write_position.height) - - # Copy over window write positions. - self._copy_over_write_positions(screen, temp_screen, write_position) - - if temp_screen.show_cursor: - screen.show_cursor = True - - # Copy over cursor positions, if they are visible. - for window, point in temp_screen.cursor_positions.items(): - if ( - 0 <= point.x < write_position.width - and self.vertical_scroll - <= point.y - < write_position.height + self.vertical_scroll - ): - screen.cursor_positions[window] = Point( - x=point.x + xpos, y=point.y + ypos - self.vertical_scroll - ) - - # Copy over menu positions, but clip them to the visible area. - for window, point in temp_screen.menu_positions.items(): - screen.menu_positions[window] = self._clip_point_to_visible_area( - Point(x=point.x + xpos, y=point.y + ypos - self.vertical_scroll), - write_position, - ) - - # Draw scrollbar. - if show_scrollbar: - self._draw_scrollbar( - write_position, - virtual_height, - screen, - ) - - def _clip_point_to_visible_area( - self, point: Point, write_position: WritePosition - ) -> Point: - """ - Ensure that the cursor and menu positions always are always reported - """ - if point.x < write_position.xpos: - point = point._replace(x=write_position.xpos) - if point.y < write_position.ypos: - point = point._replace(y=write_position.ypos) - if point.x >= write_position.xpos + write_position.width: - point = point._replace(x=write_position.xpos + write_position.width - 1) - if point.y >= write_position.ypos + write_position.height: - point = point._replace(y=write_position.ypos + write_position.height - 1) - - return point - - def _copy_over_screen( - self, - screen: Screen, - temp_screen: Screen, - write_position: WritePosition, - virtual_width: int, - ) -> None: - """ - Copy over visible screen content and "zero width escape sequences". - """ - ypos = write_position.ypos - xpos = write_position.xpos - - for y in range(write_position.height): - temp_row = temp_screen.data_buffer[y + self.vertical_scroll] - row = screen.data_buffer[y + ypos] - temp_zero_width_escapes = temp_screen.zero_width_escapes[ - y + self.vertical_scroll - ] - zero_width_escapes = screen.zero_width_escapes[y + ypos] - - for x in range(virtual_width): - row[x + xpos] = temp_row[x] - - if x in temp_zero_width_escapes: - zero_width_escapes[x + xpos] = temp_zero_width_escapes[x] - - def _copy_over_mouse_handlers( - self, - mouse_handlers: MouseHandlers, - temp_mouse_handlers: MouseHandlers, - write_position: WritePosition, - virtual_width: int, - ) -> None: - """ - Copy over mouse handlers from virtual screen to real screen. - - Note: we take `virtual_width` because we don't want to copy over mouse - handlers that we possibly have behind the scrollbar. - """ - ypos = write_position.ypos - xpos = write_position.xpos - - # Cache mouse handlers when wrapping them. Very often the same mouse - # handler is registered for many positions. - mouse_handler_wrappers: Dict[MouseHandler, MouseHandler] = {} - - def wrap_mouse_handler(handler: MouseHandler) -> MouseHandler: - "Wrap mouse handler. Translate coordinates in `MouseEvent`." - if handler not in mouse_handler_wrappers: - - def new_handler(event: MouseEvent) -> None: - new_event = MouseEvent( - position=Point( - x=event.position.x - xpos, - y=event.position.y + self.vertical_scroll - ypos, - ), - event_type=event.event_type, - button=event.button, - modifiers=event.modifiers, - ) - handler(new_event) - - mouse_handler_wrappers[handler] = new_handler - return mouse_handler_wrappers[handler] - - # Copy handlers. - mouse_handlers_dict = mouse_handlers.mouse_handlers - temp_mouse_handlers_dict = temp_mouse_handlers.mouse_handlers - - for y in range(write_position.height): - if y in temp_mouse_handlers_dict: - temp_mouse_row = temp_mouse_handlers_dict[y + self.vertical_scroll] - mouse_row = mouse_handlers_dict[y + ypos] - for x in range(virtual_width): - if x in temp_mouse_row: - mouse_row[x + xpos] = wrap_mouse_handler(temp_mouse_row[x]) - - def _copy_over_write_positions( - self, screen: Screen, temp_screen: Screen, write_position: WritePosition - ) -> None: - """ - Copy over window write positions. - """ - ypos = write_position.ypos - xpos = write_position.xpos - - for win, write_pos in temp_screen.visible_windows_to_write_positions.items(): - screen.visible_windows_to_write_positions[win] = WritePosition( - xpos=write_pos.xpos + xpos, - ypos=write_pos.ypos + ypos - self.vertical_scroll, - # TODO: if the window is only partly visible, then truncate width/height. - # This could be important if we have nested ScrollablePanes. - height=write_pos.height, - width=write_pos.width, - ) - - def is_modal(self) -> bool: - return self.content.is_modal() - - def get_key_bindings(self) -> Optional[KeyBindingsBase]: - return self.content.get_key_bindings() - - def get_children(self) -> List["Container"]: - return [self.content] - - def _make_window_visible( - self, - visible_height: int, - virtual_height: int, - visible_win_write_pos: WritePosition, - cursor_position: Optional[Point], - ) -> None: - """ - Scroll the scrollable pane, so that this window becomes visible. - - :param visible_height: Height of this `ScrollablePane` that is rendered. - :param virtual_height: Height of the virtual, temp screen. - :param visible_win_write_pos: `WritePosition` of the nested window on the - temp screen. - :param cursor_position: The location of the cursor position of this - window on the temp screen. - """ - # Start with maximum allowed scroll range, and then reduce according to - # the focused window and cursor position. - min_scroll = 0 - max_scroll = virtual_height - visible_height - - if self.keep_cursor_visible(): - # Reduce min/max scroll according to the cursor in the focused window. - if cursor_position is not None: - offsets = self.scroll_offsets - cpos_min_scroll = ( - cursor_position.y - visible_height + 1 + offsets.bottom - ) - cpos_max_scroll = cursor_position.y - offsets.top - min_scroll = max(min_scroll, cpos_min_scroll) - max_scroll = max(0, min(max_scroll, cpos_max_scroll)) - - if self.keep_focused_window_visible(): - # Reduce min/max scroll according to focused window position. - # If the window is small enough, bot the top and bottom of the window - # should be visible. - if visible_win_write_pos.height <= visible_height: - window_min_scroll = ( - visible_win_write_pos.ypos - + visible_win_write_pos.height - - visible_height - ) - window_max_scroll = visible_win_write_pos.ypos - else: - # Window does not fit on the screen. Make sure at least the whole - # screen is occupied with this window, and nothing else is shown. - window_min_scroll = visible_win_write_pos.ypos - window_max_scroll = ( - visible_win_write_pos.ypos - + visible_win_write_pos.height - - visible_height - ) - - min_scroll = max(min_scroll, window_min_scroll) - max_scroll = min(max_scroll, window_max_scroll) - - if min_scroll > max_scroll: - min_scroll = max_scroll # Should not happen. - - # Finally, properly clip the vertical scroll. - if self.vertical_scroll > max_scroll: - self.vertical_scroll = max_scroll - if self.vertical_scroll < min_scroll: - self.vertical_scroll = min_scroll - - def _draw_scrollbar( - self, write_position: WritePosition, content_height: int, screen: Screen - ) -> None: - """ - Draw the scrollbar on the screen. - - Note: There is some code duplication with the `ScrollbarMargin` - implementation. - """ - - window_height = write_position.height - display_arrows = self.display_arrows() - - if display_arrows: - window_height -= 2 - - try: - fraction_visible = write_position.height / float(content_height) - fraction_above = self.vertical_scroll / float(content_height) - - scrollbar_height = int( - min(window_height, max(1, window_height * fraction_visible)) - ) - scrollbar_top = int(window_height * fraction_above) - except ZeroDivisionError: - return - else: - - def is_scroll_button(row: int) -> bool: - "True if we should display a button on this row." - return scrollbar_top <= row <= scrollbar_top + scrollbar_height - - xpos = write_position.xpos + write_position.width - 1 - ypos = write_position.ypos - data_buffer = screen.data_buffer - - # Up arrow. - if display_arrows: - data_buffer[ypos][xpos] = Char( - self.up_arrow_symbol, "class:scrollbar.arrow" - ) - ypos += 1 - - # Scrollbar body. - scrollbar_background = "class:scrollbar.background" - scrollbar_background_start = "class:scrollbar.background,scrollbar.start" - scrollbar_button = "class:scrollbar.button" - scrollbar_button_end = "class:scrollbar.button,scrollbar.end" - - for i in range(window_height): - style = "" - if is_scroll_button(i): - if not is_scroll_button(i + 1): - # Give the last cell a different style, because we want - # to underline this. - style = scrollbar_button_end - else: - style = scrollbar_button - else: - if is_scroll_button(i + 1): - style = scrollbar_background_start - else: - style = scrollbar_background - - data_buffer[ypos][xpos] = Char(" ", style) - ypos += 1 - - # Down arrow - if display_arrows: - data_buffer[ypos][xpos] = Char( - self.down_arrow_symbol, "class:scrollbar.arrow" - ) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/utils.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/utils.py deleted file mode 100644 index 2e0f34388ba..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/utils.py +++ /dev/null @@ -1,80 +0,0 @@ -from typing import TYPE_CHECKING, Iterable, List, TypeVar, Union, cast, overload - -from prompt_toolkit.formatted_text.base import OneStyleAndTextTuple - -if TYPE_CHECKING: - from typing_extensions import SupportsIndex - -__all__ = [ - "explode_text_fragments", -] - -_T = TypeVar("_T", bound=OneStyleAndTextTuple) - - -class _ExplodedList(List[_T]): - """ - Wrapper around a list, that marks it as 'exploded'. - - As soon as items are added or the list is extended, the new items are - automatically exploded as well. - """ - - exploded = True - - def append(self, item: _T) -> None: - self.extend([item]) - - def extend(self, lst: Iterable[_T]) -> None: - super().extend(explode_text_fragments(lst)) - - 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: - ... - - @overload - def __setitem__(self, index: slice, value: Iterable[_T]) -> None: - ... - - def __setitem__( - self, index: Union["SupportsIndex", slice], value: Union[_T, Iterable[_T]] - ) -> None: - """ - Ensure that when `(style_str, 'long string')` is set, the string will be - exploded. - """ - if not isinstance(index, slice): - int_index = index.__index__() - index = slice(int_index, int_index + 1) - if isinstance(value, tuple): # In case of `OneStyleAndTextTuple`. - value = cast("List[_T]", [value]) - - super().__setitem__(index, explode_text_fragments(cast("Iterable[_T]", value))) - - -def explode_text_fragments(fragments: Iterable[_T]) -> _ExplodedList[_T]: - """ - Turn a list of (style_str, text) tuples into another list where each string is - exactly one character. - - It should be fine to call this function several times. Calling this on a - list that is already exploded, is a null operation. - - :param fragments: List of (style, text) tuples. - """ - # When the fragments is already exploded, don't explode again. - if isinstance(fragments, _ExplodedList): - return fragments - - result: List[_T] = [] - - for style, string, *rest in fragments: # type: ignore - for c in string: # type: ignore - result.append((style, c, *rest)) # type: ignore - - return _ExplodedList(result) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/lexers/__init__.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/lexers/__init__.py deleted file mode 100644 index 3e875e65730..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/lexers/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -""" -Lexer interface and implementations. -Used for syntax highlighting. -""" -from .base import DynamicLexer, Lexer, SimpleLexer -from .pygments import PygmentsLexer, RegexSync, SyncFromStart, SyntaxSync - -__all__ = [ - # Base. - "Lexer", - "SimpleLexer", - "DynamicLexer", - # Pygments. - "PygmentsLexer", - "RegexSync", - "SyncFromStart", - "SyntaxSync", -] diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/lexers/base.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/lexers/base.py deleted file mode 100644 index dd9f2459af4..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/lexers/base.py +++ /dev/null @@ -1,82 +0,0 @@ -""" -Base classes for prompt_toolkit lexers. -""" -from abc import ABCMeta, abstractmethod -from typing import Callable, Hashable, Optional - -from prompt_toolkit.document import Document -from prompt_toolkit.formatted_text.base import StyleAndTextTuples - -__all__ = [ - "Lexer", - "SimpleLexer", - "DynamicLexer", -] - - -class Lexer(metaclass=ABCMeta): - """ - Base class for all lexers. - """ - - @abstractmethod - def lex_document(self, document: Document) -> Callable[[int], StyleAndTextTuples]: - """ - Takes a :class:`~prompt_toolkit.document.Document` and returns a - callable that takes a line number and returns a list of - ``(style_str, text)`` tuples for that line. - - XXX: Note that in the past, this was supposed to return a list - of ``(Token, text)`` tuples, just like a Pygments lexer. - """ - - def invalidation_hash(self) -> Hashable: - """ - When this changes, `lex_document` could give a different output. - (Only used for `DynamicLexer`.) - """ - return id(self) - - -class SimpleLexer(Lexer): - """ - Lexer that doesn't do any tokenizing and returns the whole input as one - token. - - :param style: The style string for this lexer. - """ - - def __init__(self, style: str = "") -> None: - self.style = style - - def lex_document(self, document: Document) -> Callable[[int], StyleAndTextTuples]: - lines = document.lines - - def get_line(lineno: int) -> StyleAndTextTuples: - "Return the tokens for the given line." - try: - return [(self.style, lines[lineno])] - except IndexError: - return [] - - return get_line - - -class DynamicLexer(Lexer): - """ - Lexer class that can dynamically returns any Lexer. - - :param get_lexer: Callable that returns a :class:`.Lexer` instance. - """ - - def __init__(self, get_lexer: Callable[[], Optional[Lexer]]) -> None: - self.get_lexer = get_lexer - self._dummy = SimpleLexer() - - def lex_document(self, document: Document) -> Callable[[int], StyleAndTextTuples]: - lexer = self.get_lexer() or self._dummy - return lexer.lex_document(document) - - def invalidation_hash(self) -> Hashable: - lexer = self.get_lexer() or self._dummy - return id(lexer) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/lexers/pygments.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/lexers/pygments.py deleted file mode 100644 index d50f8afde40..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/lexers/pygments.py +++ /dev/null @@ -1,335 +0,0 @@ -""" -Adaptor classes for using Pygments lexers within prompt_toolkit. - -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. -""" -import re -from abc import ABCMeta, abstractmethod -from typing import ( - TYPE_CHECKING, - Callable, - Dict, - Generator, - Iterable, - Optional, - Tuple, - Type, -) - -from prompt_toolkit.document import Document -from prompt_toolkit.filters import FilterOrBool, to_filter -from prompt_toolkit.formatted_text.base import StyleAndTextTuples -from prompt_toolkit.formatted_text.utils import split_lines -from prompt_toolkit.styles.pygments import pygments_token_to_classname - -from .base import Lexer, SimpleLexer - -if TYPE_CHECKING: - from pygments.lexer import Lexer as PygmentsLexerCls - -__all__ = [ - "PygmentsLexer", - "SyntaxSync", - "SyncFromStart", - "RegexSync", -] - - -class SyntaxSync(metaclass=ABCMeta): - """ - Syntax synchroniser. This is a tool that finds a start position for the - lexer. This is especially important when editing big documents; we don't - want to start the highlighting by running the lexer from the beginning of - the file. That is very slow when editing. - """ - - @abstractmethod - def get_sync_start_position( - self, document: Document, lineno: int - ) -> Tuple[int, int]: - """ - Return the position from where we can start lexing as a (row, column) - tuple. - - :param document: `Document` instance that contains all the lines. - :param lineno: The line that we want to highlight. (We need to return - this line, or an earlier position.) - """ - - -class SyncFromStart(SyntaxSync): - """ - Always start the syntax highlighting from the beginning. - """ - - def get_sync_start_position( - self, document: Document, lineno: int - ) -> Tuple[int, int]: - return 0, 0 - - -class RegexSync(SyntaxSync): - """ - Synchronize by starting at a line that matches the given regex pattern. - """ - - # Never go more than this amount of lines backwards for synchronisation. - # That would be too CPU intensive. - MAX_BACKWARDS = 500 - - # Start lexing at the start, if we are in the first 'n' lines and no - # synchronisation position was found. - FROM_START_IF_NO_SYNC_POS_FOUND = 100 - - def __init__(self, pattern: str) -> None: - self._compiled_pattern = re.compile(pattern) - - def get_sync_start_position( - self, document: Document, lineno: int - ) -> Tuple[int, int]: - """ - Scan backwards, and find a possible position to start. - """ - pattern = self._compiled_pattern - lines = document.lines - - # Scan upwards, until we find a point where we can start the syntax - # synchronisation. - for i in range(lineno, max(-1, lineno - self.MAX_BACKWARDS), -1): - match = pattern.match(lines[i]) - if match: - return i, match.start() - - # No synchronisation point found. If we aren't that far from the - # beginning, start at the very beginning, otherwise, just try to start - # at the current line. - if lineno < self.FROM_START_IF_NO_SYNC_POS_FOUND: - return 0, 0 - else: - return lineno, 0 - - @classmethod - def from_pygments_lexer_cls(cls, lexer_cls: "PygmentsLexerCls") -> "RegexSync": - """ - Create a :class:`.RegexSync` instance for this Pygments lexer class. - """ - patterns = { - # For Python, start highlighting at any class/def block. - "Python": r"^\s*(class|def)\s+", - "Python 3": r"^\s*(class|def)\s+", - # For HTML, start at any open/close tag definition. - "HTML": r"<[/a-zA-Z]", - # For javascript, start at a function. - "JavaScript": r"\bfunction\b" - # TODO: Add definitions for other languages. - # By default, we start at every possible line. - } - p = patterns.get(lexer_cls.name, "^") - return cls(p) - - -class _TokenCache(Dict[Tuple[str, ...], str]): - """ - Cache that converts Pygments tokens into `prompt_toolkit` style objects. - - ``Token.A.B.C`` will be converted into: - ``class:pygments,pygments.A,pygments.A.B,pygments.A.B.C`` - """ - - def __missing__(self, key: Tuple[str, ...]) -> str: - result = "class:" + pygments_token_to_classname(key) - self[key] = result - return result - - -_token_cache = _TokenCache() - - -class PygmentsLexer(Lexer): - """ - Lexer that calls a pygments lexer. - - Example:: - - from pygments.lexers.html import HtmlLexer - lexer = PygmentsLexer(HtmlLexer) - - Note: Don't forget to also load a Pygments compatible style. E.g.:: - - from prompt_toolkit.styles.from_pygments import style_from_pygments_cls - from pygments.styles import get_style_by_name - style = style_from_pygments_cls(get_style_by_name('monokai')) - - :param pygments_lexer_cls: A `Lexer` from Pygments. - :param sync_from_start: Start lexing at the start of the document. This - will always give the best results, but it will be slow for bigger - documents. (When the last part of the document is display, then the - whole document will be lexed by Pygments on every key stroke.) It is - recommended to disable this for inputs that are expected to be more - than 1,000 lines. - :param syntax_sync: `SyntaxSync` object. - """ - - # Minimum amount of lines to go backwards when starting the parser. - # This is important when the lines are retrieved in reverse order, or when - # scrolling upwards. (Due to the complexity of calculating the vertical - # scroll offset in the `Window` class, lines are not always retrieved in - # order.) - MIN_LINES_BACKWARDS = 50 - - # When a parser was started this amount of lines back, read the parser - # until we get the current line. Otherwise, start a new parser. - # (This should probably be bigger than MIN_LINES_BACKWARDS.) - REUSE_GENERATOR_MAX_DISTANCE = 100 - - def __init__( - self, - pygments_lexer_cls: Type["PygmentsLexerCls"], - sync_from_start: FilterOrBool = True, - syntax_sync: Optional[SyntaxSync] = None, - ) -> None: - - self.pygments_lexer_cls = pygments_lexer_cls - self.sync_from_start = to_filter(sync_from_start) - - # Instantiate the Pygments lexer. - self.pygments_lexer = pygments_lexer_cls( - stripnl=False, stripall=False, ensurenl=False - ) - - # Create syntax sync instance. - self.syntax_sync = syntax_sync or RegexSync.from_pygments_lexer_cls( - pygments_lexer_cls - ) - - @classmethod - def from_filename( - cls, filename: str, sync_from_start: FilterOrBool = True - ) -> "Lexer": - """ - Create a `Lexer` from a filename. - """ - # Inline imports: the Pygments dependency is optional! - from pygments.lexers import get_lexer_for_filename - from pygments.util import ClassNotFound - - try: - pygments_lexer = get_lexer_for_filename(filename) - except ClassNotFound: - return SimpleLexer() - else: - return cls(pygments_lexer.__class__, sync_from_start=sync_from_start) - - def lex_document(self, document: Document) -> Callable[[int], StyleAndTextTuples]: - """ - Create a lexer function that takes a line number and returns the list - of (style_str, text) tuples as the Pygments lexer returns for that line. - """ - LineGenerator = Generator[Tuple[int, StyleAndTextTuples], None, None] - - # Cache of already lexed lines. - cache: Dict[int, StyleAndTextTuples] = {} - - # Pygments generators that are currently lexing. - # Map lexer generator to the line number. - line_generators: Dict[LineGenerator, int] = {} - - def get_syntax_sync() -> SyntaxSync: - "The Syntax synchronisation object that we currently use." - if self.sync_from_start(): - return SyncFromStart() - else: - return self.syntax_sync - - def find_closest_generator(i: int) -> Optional[LineGenerator]: - "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: - return generator - return None - - def create_line_generator(start_lineno: int, column: int = 0) -> LineGenerator: - """ - Create a generator that yields the lexed lines. - Each iteration it yields a (line_number, [(style_str, text), ...]) tuple. - """ - - 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 - # still replace \r\n and \r by \n. (We don't want that, - # Pygments should return exactly the same amount of text, as we - # have given as input.) - for _, t, v in self.pygments_lexer.get_tokens_unprocessed(text): - # Turn Pygments `Token` object into prompt_toolkit style - # strings. - yield _token_cache[t], v - - yield from enumerate(split_lines(list(get_text_fragments())), start_lineno) - - def get_generator(i: int) -> LineGenerator: - """ - Find an already started generator that is close, or create a new one. - """ - # Find closest line generator. - generator = find_closest_generator(i) - if generator: - return generator - - # No generator found. Determine starting point for the syntax - # synchronisation first. - - # Go at least x lines back. (Make scrolling upwards more - # efficient.) - i = max(0, i - self.MIN_LINES_BACKWARDS) - - if i == 0: - row = 0 - column = 0 - else: - row, column = get_syntax_sync().get_sync_start_position(document, i) - - # Find generator close to this point, or otherwise create a new one. - generator = find_closest_generator(i) - if generator: - return generator - else: - generator = create_line_generator(row, column) - - # If the column is not 0, ignore the first line. (Which is - # incomplete. This happens when the synchronisation algorithm tells - # us to start parsing in the middle of a line.) - if column: - next(generator) - row += 1 - - line_generators[generator] = row - return generator - - def get_line(i: int) -> StyleAndTextTuples: - "Return the tokens for a given line number." - try: - return cache[i] - except KeyError: - generator = get_generator(i) - - # Exhaust the generator, until we find the requested line. - for num, line in generator: - cache[num] = line - if num == i: - line_generators[generator] = i - - # Remove the next item from the cache. - # (It could happen that it's already there, because of - # another generator that started filling these lines, - # but we want to synchronise these lines with the - # current lexer's state.) - if num + 1 in cache: - del cache[num + 1] - - return cache[num] - return [] - - return get_line diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/log.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/log.py deleted file mode 100644 index 36ceced49e4..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/log.py +++ /dev/null @@ -1,10 +0,0 @@ -""" -Logging configuration. -""" -import logging - -__all__ = [ - "logger", -] - -logger = logging.getLogger(__package__) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/mouse_events.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/mouse_events.py deleted file mode 100644 index e10ff0322ab..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/mouse_events.py +++ /dev/null @@ -1,88 +0,0 @@ -""" -Mouse events. - - -How it works ------------- - -The renderer has a 2 dimensional grid of mouse event handlers. -(`prompt_toolkit.layout.MouseHandlers`.) When the layout is rendered, the -`Window` class will make sure that this grid will also be filled with -callbacks. For vt100 terminals, mouse events are received through stdin, just -like any other key press. There is a handler among the key bindings that -catches these events and forwards them to such a mouse event handler. It passes -through the `Window` class where the coordinates are translated from absolute -coordinates to coordinates relative to the user control, and there -`UIControl.mouse_handler` is called. -""" -from enum import Enum -from typing import FrozenSet - -from .data_structures import Point - -__all__ = ["MouseEventType", "MouseButton", "MouseModifier", "MouseEvent"] - - -class MouseEventType(Enum): - # Mouse up: This same event type is fired for all three events: left mouse - # up, right mouse up, or middle mouse up - MOUSE_UP = "MOUSE_UP" - - # Mouse down: This implicitly refers to the left mouse down (this event is - # not fired upon pressing the middle or right mouse buttons). - MOUSE_DOWN = "MOUSE_DOWN" - - SCROLL_UP = "SCROLL_UP" - SCROLL_DOWN = "SCROLL_DOWN" - - # Triggered when the left mouse button is held down, and the mouse moves - MOUSE_MOVE = "MOUSE_MOVE" - - -class MouseButton(Enum): - LEFT = "LEFT" - MIDDLE = "MIDDLE" - RIGHT = "RIGHT" - - # When we're scrolling, or just moving the mouse and not pressing a button. - NONE = "NONE" - - # This is for when we don't know which mouse button was pressed, but we do - # know that one has been pressed during this mouse event (as opposed to - # scrolling, for example) - UNKNOWN = "UNKNOWN" - - -class MouseModifier(Enum): - SHIFT = "SHIFT" - ALT = "ALT" - CONTROL = "CONTROL" - - -class MouseEvent: - """ - Mouse event, sent to `UIControl.mouse_handler`. - - :param position: `Point` instance. - :param event_type: `MouseEventType`. - """ - - def __init__( - self, - position: Point, - event_type: MouseEventType, - button: MouseButton, - modifiers: FrozenSet[MouseModifier], - ) -> None: - self.position = position - self.event_type = event_type - self.button = button - self.modifiers = modifiers - - def __repr__(self) -> str: - return "MouseEvent({!r},{!r},{!r},{!r})".format( - self.position, - self.event_type, - self.button, - self.modifiers, - ) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/__init__.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/__init__.py deleted file mode 100644 index 7b90b476bd3..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -from .base import DummyOutput, Output -from .color_depth import ColorDepth -from .defaults import create_output - -__all__ = [ - # Base. - "Output", - "DummyOutput", - # Color depth. - "ColorDepth", - # Defaults. - "create_output", -] diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/base.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/base.py deleted file mode 100644 index c78677bc8b2..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/base.py +++ /dev/null @@ -1,329 +0,0 @@ -""" -Interface for an output. -""" -from abc import ABCMeta, abstractmethod -from typing import Optional, TextIO - -from prompt_toolkit.cursor_shapes import CursorShape -from prompt_toolkit.data_structures import Size -from prompt_toolkit.styles import Attrs - -from .color_depth import ColorDepth - -__all__ = [ - "Output", - "DummyOutput", -] - - -class Output(metaclass=ABCMeta): - """ - Base class defining the output interface for a - :class:`~prompt_toolkit.renderer.Renderer`. - - Actual implementations are - :class:`~prompt_toolkit.output.vt100.Vt100_Output` and - :class:`~prompt_toolkit.output.win32.Win32Output`. - """ - - stdout: Optional[TextIO] = None - - @abstractmethod - def fileno(self) -> int: - "Return the file descriptor to which we can write for the output." - - @abstractmethod - def encoding(self) -> str: - """ - Return the encoding for this output, e.g. 'utf-8'. - (This is used mainly to know which characters are supported by the - output the data, so that the UI can provide alternatives, when - required.) - """ - - @abstractmethod - def write(self, data: str) -> None: - "Write text (Terminal escape sequences will be removed/escaped.)" - - @abstractmethod - def write_raw(self, data: str) -> None: - "Write text." - - @abstractmethod - def set_title(self, title: str) -> None: - "Set terminal title." - - @abstractmethod - def clear_title(self) -> None: - "Clear title again. (or restore previous title.)" - - @abstractmethod - def flush(self) -> None: - "Write to output stream and flush." - - @abstractmethod - def erase_screen(self) -> None: - """ - Erases the screen with the background colour and moves the cursor to - home. - """ - - @abstractmethod - def enter_alternate_screen(self) -> None: - "Go to the alternate screen buffer. (For full screen applications)." - - @abstractmethod - def quit_alternate_screen(self) -> None: - "Leave the alternate screen buffer." - - @abstractmethod - def enable_mouse_support(self) -> None: - "Enable mouse." - - @abstractmethod - def disable_mouse_support(self) -> None: - "Disable mouse." - - @abstractmethod - def erase_end_of_line(self) -> None: - """ - Erases from the current cursor position to the end of the current line. - """ - - @abstractmethod - def erase_down(self) -> None: - """ - Erases the screen from the current line down to the bottom of the - screen. - """ - - @abstractmethod - def reset_attributes(self) -> None: - "Reset color and styling attributes." - - @abstractmethod - def set_attributes(self, attrs: Attrs, color_depth: ColorDepth) -> None: - "Set new color and styling attributes." - - @abstractmethod - def disable_autowrap(self) -> None: - "Disable auto line wrapping." - - @abstractmethod - def enable_autowrap(self) -> None: - "Enable auto line wrapping." - - @abstractmethod - def cursor_goto(self, row: int = 0, column: int = 0) -> None: - "Move cursor position." - - @abstractmethod - def cursor_up(self, amount: int) -> None: - "Move cursor `amount` place up." - - @abstractmethod - def cursor_down(self, amount: int) -> None: - "Move cursor `amount` place down." - - @abstractmethod - def cursor_forward(self, amount: int) -> None: - "Move cursor `amount` place forward." - - @abstractmethod - def cursor_backward(self, amount: int) -> None: - "Move cursor `amount` place backward." - - @abstractmethod - def hide_cursor(self) -> None: - "Hide cursor." - - @abstractmethod - def show_cursor(self) -> None: - "Show cursor." - - @abstractmethod - def set_cursor_shape(self, cursor_shape: CursorShape) -> None: - "Set cursor shape to block, beam or underline." - - @abstractmethod - def reset_cursor_shape(self) -> None: - "Reset cursor shape." - - def ask_for_cpr(self) -> None: - """ - Asks for a cursor position report (CPR). - (VT100 only.) - """ - - @property - def responds_to_cpr(self) -> bool: - """ - `True` if the `Application` can expect to receive a CPR response after - calling `ask_for_cpr` (this will come back through the corresponding - `Input`). - - This is used to determine the amount of available rows we have below - the cursor position. In the first place, we have this so that the drop - down autocompletion menus are sized according to the available space. - - On Windows, we don't need this, there we have - `get_rows_below_cursor_position`. - """ - return False - - @abstractmethod - def get_size(self) -> Size: - "Return the size of the output window." - - def bell(self) -> None: - "Sound bell." - - def enable_bracketed_paste(self) -> None: - "For vt100 only." - - def disable_bracketed_paste(self) -> None: - "For vt100 only." - - def reset_cursor_key_mode(self) -> None: - """ - For vt100 only. - Put the terminal in normal cursor mode (instead of application mode). - - See: https://vt100.net/docs/vt100-ug/chapter3.html - """ - - def scroll_buffer_to_prompt(self) -> None: - "For Win32 only." - - def get_rows_below_cursor_position(self) -> int: - "For Windows only." - raise NotImplementedError - - @abstractmethod - def get_default_color_depth(self) -> ColorDepth: - """ - Get default color depth for this output. - - This value will be used if no color depth was explicitely passed to the - `Application`. - - .. note:: - - If the `$PROMPT_TOOLKIT_COLOR_DEPTH` environment variable has been - set, then `outputs.defaults.create_output` will pass this value to - the implementation as the default_color_depth, which is returned - here. (This is not used when the output corresponds to a - prompt_toolkit SSH/Telnet session.) - """ - - -class DummyOutput(Output): - """ - For testing. An output class that doesn't render anything. - """ - - def fileno(self) -> int: - "There is no sensible default for fileno()." - raise NotImplementedError - - def encoding(self) -> str: - return "utf-8" - - def write(self, data: str) -> None: - pass - - def write_raw(self, data: str) -> None: - pass - - def set_title(self, title: str) -> None: - pass - - def clear_title(self) -> None: - pass - - def flush(self) -> None: - pass - - def erase_screen(self) -> None: - pass - - def enter_alternate_screen(self) -> None: - pass - - def quit_alternate_screen(self) -> None: - pass - - def enable_mouse_support(self) -> None: - pass - - def disable_mouse_support(self) -> None: - pass - - def erase_end_of_line(self) -> None: - pass - - def erase_down(self) -> None: - pass - - def reset_attributes(self) -> None: - pass - - def set_attributes(self, attrs: Attrs, color_depth: ColorDepth) -> None: - pass - - def disable_autowrap(self) -> None: - pass - - def enable_autowrap(self) -> None: - pass - - def cursor_goto(self, row: int = 0, column: int = 0) -> None: - pass - - def cursor_up(self, amount: int) -> None: - pass - - def cursor_down(self, amount: int) -> None: - pass - - def cursor_forward(self, amount: int) -> None: - pass - - def cursor_backward(self, amount: int) -> None: - pass - - def hide_cursor(self) -> None: - pass - - def show_cursor(self) -> None: - pass - - def set_cursor_shape(self, cursor_shape: CursorShape) -> None: - pass - - def reset_cursor_shape(self) -> None: - pass - - def ask_for_cpr(self) -> None: - pass - - def bell(self) -> None: - pass - - def enable_bracketed_paste(self) -> None: - pass - - def disable_bracketed_paste(self) -> None: - pass - - def scroll_buffer_to_prompt(self) -> None: - pass - - def get_size(self) -> Size: - return Size(rows=40, columns=80) - - def get_rows_below_cursor_position(self) -> int: - return 40 - - def get_default_color_depth(self) -> ColorDepth: - return ColorDepth.DEPTH_1_BIT diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/color_depth.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/color_depth.py deleted file mode 100644 index a6166bacafb..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/color_depth.py +++ /dev/null @@ -1,58 +0,0 @@ -import os -from enum import Enum -from typing import Optional - -__all__ = [ - "ColorDepth", -] - - -class ColorDepth(str, Enum): - """ - Possible color depth values for the output. - """ - - value: str - - #: One color only. - DEPTH_1_BIT = "DEPTH_1_BIT" - - #: ANSI Colors. - DEPTH_4_BIT = "DEPTH_4_BIT" - - #: The default. - DEPTH_8_BIT = "DEPTH_8_BIT" - - #: 24 bit True color. - DEPTH_24_BIT = "DEPTH_24_BIT" - - # Aliases. - MONOCHROME = DEPTH_1_BIT - ANSI_COLORS_ONLY = DEPTH_4_BIT - DEFAULT = DEPTH_8_BIT - TRUE_COLOR = DEPTH_24_BIT - - @classmethod - def from_env(cls) -> Optional["ColorDepth"]: - """ - Return the color depth if the $PROMPT_TOOLKIT_COLOR_DEPTH environment - variable has been set. - - This is a way to enforce a certain color depth in all prompt_toolkit - applications. - """ - # Check the `PROMPT_TOOLKIT_COLOR_DEPTH` environment variable. - all_values = [i.value for i in ColorDepth] - if os.environ.get("PROMPT_TOOLKIT_COLOR_DEPTH") in all_values: - return cls(os.environ["PROMPT_TOOLKIT_COLOR_DEPTH"]) - - return None - - @classmethod - def default(cls) -> "ColorDepth": - """ - Return the default color depth for the default output. - """ - from .defaults import create_output - - return create_output().get_default_color_depth() diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/conemu.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/conemu.py deleted file mode 100644 index fc46cc4afd2..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/conemu.py +++ /dev/null @@ -1,63 +0,0 @@ -import sys - -assert sys.platform == "win32" - -from typing import Any, Optional, TextIO - -from prompt_toolkit.data_structures import Size - -from .base import Output -from .color_depth import ColorDepth -from .vt100 import Vt100_Output -from .win32 import Win32Output - -__all__ = [ - "ConEmuOutput", -] - - -class ConEmuOutput: - """ - ConEmu (Windows) output abstraction. - - ConEmu is a Windows console application, but it also supports ANSI escape - sequences. This output class is actually a proxy to both `Win32Output` and - `Vt100_Output`. It uses `Win32Output` for console sizing and scrolling, but - all cursor movements and scrolling happens through the `Vt100_Output`. - - This way, we can have 256 colors in ConEmu and Cmder. Rendering will be - even a little faster as well. - - http://conemu.github.io/ - http://gooseberrycreative.com/cmder/ - """ - - def __init__( - self, stdout: TextIO, default_color_depth: Optional[ColorDepth] = None - ) -> None: - self.win32_output = Win32Output(stdout, default_color_depth=default_color_depth) - self.vt100_output = Vt100_Output( - stdout, lambda: Size(0, 0), default_color_depth=default_color_depth - ) - - @property - def responds_to_cpr(self) -> bool: - return False # We don't need this on Windows. - - def __getattr__(self, name: str) -> Any: - if name in ( - "get_size", - "get_rows_below_cursor_position", - "enable_mouse_support", - "disable_mouse_support", - "scroll_buffer_to_prompt", - "get_win32_screen_buffer_info", - "enable_bracketed_paste", - "disable_bracketed_paste", - ): - return getattr(self.win32_output, name) - else: - return getattr(self.vt100_output, name) - - -Output.register(ConEmuOutput) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/defaults.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/defaults.py deleted file mode 100644 index 7fb0f8931d8..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/defaults.py +++ /dev/null @@ -1,100 +0,0 @@ -import sys -from typing import Optional, TextIO, cast - -from prompt_toolkit.utils import ( - get_bell_environment_variable, - get_term_environment_variable, - is_conemu_ansi, -) - -from .base import DummyOutput, Output -from .color_depth import ColorDepth -from .plain_text import PlainTextOutput - -__all__ = [ - "create_output", -] - - -def create_output( - stdout: Optional[TextIO] = None, always_prefer_tty: bool = False -) -> Output: - """ - Return an :class:`~prompt_toolkit.output.Output` instance for the command - line. - - :param stdout: The stdout object - :param always_prefer_tty: When set, look for `sys.stderr` if `sys.stdout` - is not a TTY. Useful if `sys.stdout` is redirected to a file, but we - still want user input and output on the terminal. - - By default, this is `False`. If `sys.stdout` is not a terminal (maybe - it's redirected to a file), then a `PlainTextOutput` will be returned. - That way, tools like `print_formatted_text` will write plain text into - that file. - """ - # Consider TERM, PROMPT_TOOLKIT_BELL, and PROMPT_TOOLKIT_COLOR_DEPTH - # environment variables. Notice that PROMPT_TOOLKIT_COLOR_DEPTH value is - # the default that's used if the Application doesn't override it. - term_from_env = get_term_environment_variable() - bell_from_env = get_bell_environment_variable() - color_depth_from_env = ColorDepth.from_env() - - if stdout is None: - # By default, render to stdout. If the output is piped somewhere else, - # render to stderr. - stdout = sys.stdout - - if always_prefer_tty: - for io in [sys.stdout, sys.stderr]: - if io is not None and io.isatty(): - # (This is `None` when using `pythonw.exe` on Windows.) - stdout = io - break - - # If the output is still `None`, use a DummyOutput. - # This happens for instance on Windows, when running the application under - # `pythonw.exe`. In that case, there won't be a terminal Window, and - # stdin/stdout/stderr are `None`. - if stdout is None: - return DummyOutput() - - # If the patch_stdout context manager has been used, then sys.stdout is - # replaced by this proxy. For prompt_toolkit applications, we want to use - # the real stdout. - from prompt_toolkit.patch_stdout import StdoutProxy - - while isinstance(stdout, StdoutProxy): - stdout = stdout.original_stdout - - if sys.platform == "win32": - from .conemu import ConEmuOutput - from .win32 import Win32Output - from .windows10 import Windows10_Output, is_win_vt100_enabled - - if is_win_vt100_enabled(): - return cast( - Output, - Windows10_Output(stdout, default_color_depth=color_depth_from_env), - ) - if is_conemu_ansi(): - return cast( - Output, ConEmuOutput(stdout, default_color_depth=color_depth_from_env) - ) - else: - return Win32Output(stdout, default_color_depth=color_depth_from_env) - else: - from .vt100 import Vt100_Output - - # Stdout is not a TTY? Render as plain text. - # This is mostly useful if stdout is redirected to a file, and - # `print_formatted_text` is used. - if not stdout.isatty(): - return PlainTextOutput(stdout) - - return Vt100_Output.from_pty( - stdout, - term=term_from_env, - default_color_depth=color_depth_from_env, - enable_bell=bell_from_env, - ) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/flush_stdout.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/flush_stdout.py deleted file mode 100644 index 805a81e010e..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/flush_stdout.py +++ /dev/null @@ -1,86 +0,0 @@ -import errno -import os -import sys -from contextlib import contextmanager -from typing import IO, Iterator, TextIO, cast - -__all__ = ["flush_stdout"] - - -def flush_stdout(stdout: TextIO, data: str) -> None: - # If the IO object has an `encoding` and `buffer` attribute, it means that - # we can access the underlying BinaryIO object and write into it in binary - # mode. This is preferred if possible. - # NOTE: When used in a Jupyter notebook, don't write binary. - # `ipykernel.iostream.OutStream` has an `encoding` attribute, but not - # a `buffer` attribute, so we can't write binary in it. - has_binary_io = hasattr(stdout, "encoding") and hasattr(stdout, "buffer") - - try: - # Ensure that `stdout` is made blocking when writing into it. - # Otherwise, when uvloop is activated (which makes stdout - # non-blocking), and we write big amounts of text, then we get a - # `BlockingIOError` here. - with _blocking_io(stdout): - # (We try to encode ourself, because that way we can replace - # characters that don't exist in the character set, avoiding - # UnicodeEncodeError crashes. E.g. u'\xb7' does not appear in 'ascii'.) - # My Arch Linux installation of july 2015 reported 'ANSI_X3.4-1968' - # for sys.stdout.encoding in xterm. - out: IO[bytes] - if has_binary_io: - stdout.buffer.write(data.encode(stdout.encoding or "utf-8", "replace")) - else: - stdout.write(data) - - stdout.flush() - except OSError as e: - if e.args and e.args[0] == errno.EINTR: - # Interrupted system call. Can happen in case of a window - # resize signal. (Just ignore. The resize handler will render - # again anyway.) - pass - elif e.args and e.args[0] == 0: - # This can happen when there is a lot of output and the user - # sends a KeyboardInterrupt by pressing Control-C. E.g. in - # a Python REPL when we execute "while True: print('test')". - # (The `ptpython` REPL uses this `Output` class instead of - # `stdout` directly -- in order to be network transparent.) - # So, just ignore. - pass - else: - raise - - -@contextmanager -def _blocking_io(io: IO[str]) -> Iterator[None]: - """ - Ensure that the FD for `io` is set to blocking in here. - """ - if sys.platform == "win32": - # On Windows, the `os` module doesn't have a `get/set_blocking` - # function. - yield - return - - try: - fd = io.fileno() - blocking = os.get_blocking(fd) - except: # noqa - # Failed somewhere. - # `get_blocking` can raise `OSError`. - # The io object can raise `AttributeError` when no `fileno()` method is - # present if we're not a real file object. - blocking = True # Assume we're good, and don't do anything. - - try: - # Make blocking if we weren't blocking yet. - if not blocking: - os.set_blocking(fd, True) - - yield - - finally: - # Restore original blocking mode. - if not blocking: - os.set_blocking(fd, blocking) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/plain_text.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/plain_text.py deleted file mode 100644 index 4360355f4f2..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/plain_text.py +++ /dev/null @@ -1,141 +0,0 @@ -from typing import List, TextIO - -from prompt_toolkit.cursor_shapes import CursorShape -from prompt_toolkit.data_structures import Size -from prompt_toolkit.styles import Attrs - -from .base import Output -from .color_depth import ColorDepth -from .flush_stdout import flush_stdout - -__all__ = ["PlainTextOutput"] - - -class PlainTextOutput(Output): - """ - Output that won't include any ANSI escape sequences. - - Useful when stdout is not a terminal. Maybe stdout is redirected to a file. - In this case, if `print_formatted_text` is used, for instance, we don't - want to include formatting. - - (The code is mostly identical to `Vt100_Output`, but without the - formatting.) - """ - - def __init__(self, stdout: TextIO) -> None: - assert all(hasattr(stdout, a) for a in ("write", "flush")) - - self.stdout: TextIO = stdout - self._buffer: List[str] = [] - - def fileno(self) -> int: - "There is no sensible default for fileno()." - return self.stdout.fileno() - - def encoding(self) -> str: - return "utf-8" - - def write(self, data: str) -> None: - self._buffer.append(data) - - def write_raw(self, data: str) -> None: - self._buffer.append(data) - - def set_title(self, title: str) -> None: - pass - - def clear_title(self) -> None: - pass - - def flush(self) -> None: - if not self._buffer: - return - - data = "".join(self._buffer) - self._buffer = [] - flush_stdout(self.stdout, data) - - def erase_screen(self) -> None: - pass - - def enter_alternate_screen(self) -> None: - pass - - def quit_alternate_screen(self) -> None: - pass - - def enable_mouse_support(self) -> None: - pass - - def disable_mouse_support(self) -> None: - pass - - def erase_end_of_line(self) -> None: - pass - - def erase_down(self) -> None: - pass - - def reset_attributes(self) -> None: - pass - - def set_attributes(self, attrs: Attrs, color_depth: ColorDepth) -> None: - pass - - def disable_autowrap(self) -> None: - pass - - def enable_autowrap(self) -> None: - pass - - def cursor_goto(self, row: int = 0, column: int = 0) -> None: - pass - - def cursor_up(self, amount: int) -> None: - pass - - def cursor_down(self, amount: int) -> None: - self._buffer.append("\n") - - def cursor_forward(self, amount: int) -> None: - self._buffer.append(" " * amount) - - def cursor_backward(self, amount: int) -> None: - pass - - def hide_cursor(self) -> None: - pass - - def show_cursor(self) -> None: - pass - - def set_cursor_shape(self, cursor_shape: CursorShape) -> None: - pass - - def reset_cursor_shape(self) -> None: - pass - - def ask_for_cpr(self) -> None: - pass - - def bell(self) -> None: - pass - - def enable_bracketed_paste(self) -> None: - pass - - def disable_bracketed_paste(self) -> None: - pass - - def scroll_buffer_to_prompt(self) -> None: - pass - - def get_size(self) -> Size: - return Size(rows=40, columns=80) - - def get_rows_below_cursor_position(self) -> int: - return 8 - - def get_default_color_depth(self) -> ColorDepth: - return ColorDepth.DEPTH_1_BIT diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/vt100.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/vt100.py deleted file mode 100644 index 3a03de6fa59..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/vt100.py +++ /dev/null @@ -1,746 +0,0 @@ -""" -Output for vt100 terminals. - -A lot of thanks, regarding outputting of colors, goes to the Pygments project: -(We don't rely on Pygments anymore, because many things are very custom, and -everything has been highly optimized.) -http://pygments.org/ -""" -import io -import os -import sys -from typing import ( - Callable, - Dict, - Hashable, - Iterable, - List, - Optional, - Sequence, - Set, - TextIO, - Tuple, -) - -from prompt_toolkit.cursor_shapes import CursorShape -from prompt_toolkit.data_structures import Size -from prompt_toolkit.output import Output -from prompt_toolkit.styles import ANSI_COLOR_NAMES, Attrs -from prompt_toolkit.utils import is_dumb_terminal - -from .color_depth import ColorDepth -from .flush_stdout import flush_stdout - -__all__ = [ - "Vt100_Output", -] - - -FG_ANSI_COLORS = { - "ansidefault": 39, - # Low intensity. - "ansiblack": 30, - "ansired": 31, - "ansigreen": 32, - "ansiyellow": 33, - "ansiblue": 34, - "ansimagenta": 35, - "ansicyan": 36, - "ansigray": 37, - # High intensity. - "ansibrightblack": 90, - "ansibrightred": 91, - "ansibrightgreen": 92, - "ansibrightyellow": 93, - "ansibrightblue": 94, - "ansibrightmagenta": 95, - "ansibrightcyan": 96, - "ansiwhite": 97, -} - -BG_ANSI_COLORS = { - "ansidefault": 49, - # Low intensity. - "ansiblack": 40, - "ansired": 41, - "ansigreen": 42, - "ansiyellow": 43, - "ansiblue": 44, - "ansimagenta": 45, - "ansicyan": 46, - "ansigray": 47, - # High intensity. - "ansibrightblack": 100, - "ansibrightred": 101, - "ansibrightgreen": 102, - "ansibrightyellow": 103, - "ansibrightblue": 104, - "ansibrightmagenta": 105, - "ansibrightcyan": 106, - "ansiwhite": 107, -} - - -ANSI_COLORS_TO_RGB = { - "ansidefault": ( - 0x00, - 0x00, - 0x00, - ), # Don't use, 'default' doesn't really have a value. - "ansiblack": (0x00, 0x00, 0x00), - "ansigray": (0xE5, 0xE5, 0xE5), - "ansibrightblack": (0x7F, 0x7F, 0x7F), - "ansiwhite": (0xFF, 0xFF, 0xFF), - # Low intensity. - "ansired": (0xCD, 0x00, 0x00), - "ansigreen": (0x00, 0xCD, 0x00), - "ansiyellow": (0xCD, 0xCD, 0x00), - "ansiblue": (0x00, 0x00, 0xCD), - "ansimagenta": (0xCD, 0x00, 0xCD), - "ansicyan": (0x00, 0xCD, 0xCD), - # High intensity. - "ansibrightred": (0xFF, 0x00, 0x00), - "ansibrightgreen": (0x00, 0xFF, 0x00), - "ansibrightyellow": (0xFF, 0xFF, 0x00), - "ansibrightblue": (0x00, 0x00, 0xFF), - "ansibrightmagenta": (0xFF, 0x00, 0xFF), - "ansibrightcyan": (0x00, 0xFF, 0xFF), -} - - -assert set(FG_ANSI_COLORS) == set(ANSI_COLOR_NAMES) -assert set(BG_ANSI_COLORS) == set(ANSI_COLOR_NAMES) -assert set(ANSI_COLORS_TO_RGB) == set(ANSI_COLOR_NAMES) - - -def _get_closest_ansi_color(r: int, g: int, b: int, exclude: Sequence[str] = ()) -> str: - """ - Find closest ANSI color. Return it by name. - - :param r: Red (Between 0 and 255.) - :param g: Green (Between 0 and 255.) - :param b: Blue (Between 0 and 255.) - :param exclude: A tuple of color names to exclude. (E.g. ``('ansired', )``.) - """ - exclude = list(exclude) - - # When we have a bit of saturation, avoid the gray-like colors, otherwise, - # too often the distance to the gray color is less. - saturation = abs(r - g) + abs(g - b) + abs(b - r) # Between 0..510 - - if saturation > 30: - exclude.extend(["ansilightgray", "ansidarkgray", "ansiwhite", "ansiblack"]) - - # Take the closest color. - # (Thanks to Pygments for this part.) - distance = 257 * 257 * 3 # "infinity" (>distance from #000000 to #ffffff) - match = "ansidefault" - - for name, (r2, g2, b2) in ANSI_COLORS_TO_RGB.items(): - if name != "ansidefault" and name not in exclude: - d = (r - r2) ** 2 + (g - g2) ** 2 + (b - b2) ** 2 - - if d < distance: - match = name - distance = d - - return match - - -_ColorCodeAndName = Tuple[int, str] - - -class _16ColorCache: - """ - Cache which maps (r, g, b) tuples to 16 ansi colors. - - :param bg: Cache for background colors, instead of foreground. - """ - - def __init__(self, bg: bool = False) -> None: - self.bg = bg - self._cache: Dict[Hashable, _ColorCodeAndName] = {} - - def get_code( - self, value: Tuple[int, int, int], exclude: Sequence[str] = () - ) -> _ColorCodeAndName: - """ - Return a (ansi_code, ansi_name) tuple. (E.g. ``(44, 'ansiblue')``.) for - a given (r,g,b) value. - """ - key: Hashable = (value, tuple(exclude)) - cache = self._cache - - if key not in cache: - cache[key] = self._get(value, exclude) - - return cache[key] - - def _get( - self, value: Tuple[int, int, int], exclude: Sequence[str] = () - ) -> _ColorCodeAndName: - - r, g, b = value - match = _get_closest_ansi_color(r, g, b, exclude=exclude) - - # Turn color name into code. - if self.bg: - code = BG_ANSI_COLORS[match] - else: - code = FG_ANSI_COLORS[match] - - return code, match - - -class _256ColorCache(Dict[Tuple[int, int, int], int]): - """ - Cache which maps (r, g, b) tuples to 256 colors. - """ - - def __init__(self) -> None: - # Build color table. - colors: List[Tuple[int, int, int]] = [] - - # colors 0..15: 16 basic colors - colors.append((0x00, 0x00, 0x00)) # 0 - colors.append((0xCD, 0x00, 0x00)) # 1 - colors.append((0x00, 0xCD, 0x00)) # 2 - colors.append((0xCD, 0xCD, 0x00)) # 3 - colors.append((0x00, 0x00, 0xEE)) # 4 - colors.append((0xCD, 0x00, 0xCD)) # 5 - colors.append((0x00, 0xCD, 0xCD)) # 6 - colors.append((0xE5, 0xE5, 0xE5)) # 7 - colors.append((0x7F, 0x7F, 0x7F)) # 8 - colors.append((0xFF, 0x00, 0x00)) # 9 - colors.append((0x00, 0xFF, 0x00)) # 10 - colors.append((0xFF, 0xFF, 0x00)) # 11 - colors.append((0x5C, 0x5C, 0xFF)) # 12 - colors.append((0xFF, 0x00, 0xFF)) # 13 - colors.append((0x00, 0xFF, 0xFF)) # 14 - colors.append((0xFF, 0xFF, 0xFF)) # 15 - - # colors 16..232: the 6x6x6 color cube - valuerange = (0x00, 0x5F, 0x87, 0xAF, 0xD7, 0xFF) - - for i in range(217): - r = valuerange[(i // 36) % 6] - g = valuerange[(i // 6) % 6] - b = valuerange[i % 6] - colors.append((r, g, b)) - - # colors 233..253: grayscale - for i in range(1, 22): - v = 8 + i * 10 - colors.append((v, v, v)) - - self.colors = colors - - def __missing__(self, value: Tuple[int, int, int]) -> int: - r, g, b = value - - # Find closest color. - # (Thanks to Pygments for this!) - distance = 257 * 257 * 3 # "infinity" (>distance from #000000 to #ffffff) - match = 0 - - for i, (r2, g2, b2) in enumerate(self.colors): - if i >= 16: # XXX: We ignore the 16 ANSI colors when mapping RGB - # to the 256 colors, because these highly depend on - # the color scheme of the terminal. - d = (r - r2) ** 2 + (g - g2) ** 2 + (b - b2) ** 2 - - if d < distance: - match = i - distance = d - - # Turn color name into code. - self[value] = match - return match - - -_16_fg_colors = _16ColorCache(bg=False) -_16_bg_colors = _16ColorCache(bg=True) -_256_colors = _256ColorCache() - - -class _EscapeCodeCache(Dict[Attrs, str]): - """ - Cache for VT100 escape codes. It maps - (fgcolor, bgcolor, bold, underline, strike, reverse) tuples to VT100 - escape sequences. - - :param true_color: When True, use 24bit colors instead of 256 colors. - """ - - def __init__(self, color_depth: ColorDepth) -> None: - self.color_depth = color_depth - - def __missing__(self, attrs: Attrs) -> str: - ( - fgcolor, - bgcolor, - bold, - underline, - strike, - italic, - blink, - reverse, - hidden, - ) = attrs - parts: List[str] = [] - - parts.extend(self._colors_to_code(fgcolor or "", bgcolor or "")) - - if bold: - parts.append("1") - if italic: - parts.append("3") - if blink: - parts.append("5") - if underline: - parts.append("4") - if reverse: - parts.append("7") - if hidden: - parts.append("8") - if strike: - parts.append("9") - - if parts: - result = "\x1b[0;" + ";".join(parts) + "m" - else: - result = "\x1b[0m" - - self[attrs] = result - return result - - def _color_name_to_rgb(self, color: str) -> Tuple[int, int, int]: - "Turn 'ffffff', into (0xff, 0xff, 0xff)." - try: - rgb = int(color, 16) - except ValueError: - raise - else: - r = (rgb >> 16) & 0xFF - g = (rgb >> 8) & 0xFF - b = rgb & 0xFF - return r, g, b - - def _colors_to_code(self, fg_color: str, bg_color: str) -> Iterable[str]: - """ - Return a tuple with the vt100 values that represent this color. - """ - # When requesting ANSI colors only, and both fg/bg color were converted - # to ANSI, ensure that the foreground and background color are not the - # same. (Unless they were explicitly defined to be the same color.) - fg_ansi = "" - - def get(color: str, bg: bool) -> List[int]: - nonlocal fg_ansi - - table = BG_ANSI_COLORS if bg else FG_ANSI_COLORS - - if not color or self.color_depth == ColorDepth.DEPTH_1_BIT: - return [] - - # 16 ANSI colors. (Given by name.) - elif color in table: - return [table[color]] - - # RGB colors. (Defined as 'ffffff'.) - else: - try: - rgb = self._color_name_to_rgb(color) - except ValueError: - return [] - - # When only 16 colors are supported, use that. - if self.color_depth == ColorDepth.DEPTH_4_BIT: - if bg: # Background. - if fg_color != bg_color: - exclude = [fg_ansi] - else: - exclude = [] - code, name = _16_bg_colors.get_code(rgb, exclude=exclude) - return [code] - else: # Foreground. - code, name = _16_fg_colors.get_code(rgb) - fg_ansi = name - return [code] - - # True colors. (Only when this feature is enabled.) - elif self.color_depth == ColorDepth.DEPTH_24_BIT: - r, g, b = rgb - return [(48 if bg else 38), 2, r, g, b] - - # 256 RGB colors. - else: - return [(48 if bg else 38), 5, _256_colors[rgb]] - - 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]: - """ - Get the size of this pseudo terminal. - - :param fileno: stdout.fileno() - :returns: A (rows, cols) tuple. - """ - size = os.get_terminal_size(fileno) - return size.lines, size.columns - - -class Vt100_Output(Output): - """ - :param get_size: A callable which returns the `Size` of the output terminal. - :param stdout: Any object with has a `write` and `flush` method + an 'encoding' property. - :param term: The terminal environment variable. (xterm, xterm-256color, linux, ...) - """ - - # For the error messages. Only display "Output is not a terminal" once per - # file descriptor. - _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, - enable_bell: bool = True, - ) -> None: - - assert all(hasattr(stdout, a) for a in ("write", "flush")) - - self._buffer: List[str] = [] - self.stdout: TextIO = stdout - self.default_color_depth = default_color_depth - self._get_size = get_size - self.term = term - self.enable_bell = enable_bell - - # Cache for escape codes. - 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), - ColorDepth.DEPTH_24_BIT: _EscapeCodeCache(ColorDepth.DEPTH_24_BIT), - } - - # Keep track of whether the cursor shape was ever changed. - # (We don't restore the cursor shape if it was never changed - by - # default, we don't change them.) - self._cursor_shape_changed = False - - @classmethod - def from_pty( - cls, - stdout: TextIO, - term: Optional[str] = None, - default_color_depth: Optional[ColorDepth] = None, - enable_bell: bool = True, - ) -> "Vt100_Output": - """ - Create an Output class from a pseudo terminal. - (This will take the dimensions by reading the pseudo - terminal attributes.) - """ - fd: Optional[int] - # 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. - try: - fd = stdout.fileno() - except io.UnsupportedOperation: - fd = None - - if not stdout.isatty() and (fd is None or fd not in cls._fds_not_a_terminal): - msg = "Warning: Output is not a terminal (fd=%r).\n" - sys.stderr.write(msg % fd) - sys.stderr.flush() - if fd is not None: - cls._fds_not_a_terminal.add(fd) - - def get_size() -> Size: - # If terminal (incorrectly) reports its size as 0, pick a - # reasonable default. See - # https://github.com/ipython/ipython/issues/10071 - rows, columns = (None, None) - - # It is possible that `stdout` is no longer a TTY device at this - # point. In that case we get an `OSError` in the ioctl call in - # `get_size`. See: - # https://github.com/prompt-toolkit/python-prompt-toolkit/pull/1021 - try: - rows, columns = _get_size(stdout.fileno()) - except OSError: - pass - return Size(rows=rows or 24, columns=columns or 80) - - return cls( - stdout, - get_size, - term=term, - default_color_depth=default_color_depth, - enable_bell=enable_bell, - ) - - def get_size(self) -> Size: - return self._get_size() - - def fileno(self) -> int: - "Return file descriptor." - return self.stdout.fileno() - - def encoding(self) -> str: - "Return encoding used for stdout." - return self.stdout.encoding - - def write_raw(self, data: str) -> None: - """ - Write raw data to output. - """ - self._buffer.append(data) - - def write(self, data: str) -> None: - """ - Write text to output. - (Removes vt100 escape codes. -- used for safely writing text.) - """ - self._buffer.append(data.replace("\x1b", "?")) - - def set_title(self, title: str) -> None: - """ - Set terminal title. - """ - if self.term not in ( - "linux", - "eterm-color", - ): # Not supported by the Linux console. - self.write_raw( - "\x1b]2;%s\x07" % title.replace("\x1b", "").replace("\x07", "") - ) - - def clear_title(self) -> None: - self.set_title("") - - def erase_screen(self) -> None: - """ - Erases the screen with the background colour and moves the cursor to - home. - """ - self.write_raw("\x1b[2J") - - def enter_alternate_screen(self) -> None: - self.write_raw("\x1b[?1049h\x1b[H") - - def quit_alternate_screen(self) -> None: - self.write_raw("\x1b[?1049l") - - def enable_mouse_support(self) -> None: - self.write_raw("\x1b[?1000h") - - # Enable mouse-drag support. - self.write_raw("\x1b[?1003h") - - # Enable urxvt Mouse mode. (For terminals that understand this.) - self.write_raw("\x1b[?1015h") - - # Also enable Xterm SGR mouse mode. (For terminals that understand this.) - self.write_raw("\x1b[?1006h") - - # Note: E.g. lxterminal understands 1000h, but not the urxvt or sgr - # extensions. - - def disable_mouse_support(self) -> None: - self.write_raw("\x1b[?1000l") - self.write_raw("\x1b[?1015l") - self.write_raw("\x1b[?1006l") - self.write_raw("\x1b[?1003l") - - def erase_end_of_line(self) -> None: - """ - Erases from the current cursor position to the end of the current line. - """ - self.write_raw("\x1b[K") - - def erase_down(self) -> None: - """ - Erases the screen from the current line down to the bottom of the - screen. - """ - self.write_raw("\x1b[J") - - def reset_attributes(self) -> None: - self.write_raw("\x1b[0m") - - def set_attributes(self, attrs: Attrs, color_depth: ColorDepth) -> None: - """ - Create new style and output. - - :param attrs: `Attrs` instance. - """ - # Get current depth. - escape_code_cache = self._escape_code_caches[color_depth] - - # Write escape character. - self.write_raw(escape_code_cache[attrs]) - - def disable_autowrap(self) -> None: - self.write_raw("\x1b[?7l") - - def enable_autowrap(self) -> None: - self.write_raw("\x1b[?7h") - - def enable_bracketed_paste(self) -> None: - self.write_raw("\x1b[?2004h") - - def disable_bracketed_paste(self) -> None: - self.write_raw("\x1b[?2004l") - - def reset_cursor_key_mode(self) -> None: - """ - For vt100 only. - Put the terminal in cursor mode (instead of application mode). - """ - # Put the terminal in cursor mode. (Instead of application mode.) - self.write_raw("\x1b[?1l") - - def cursor_goto(self, row: int = 0, column: int = 0) -> None: - """ - Move cursor position. - """ - self.write_raw("\x1b[%i;%iH" % (row, column)) - - def cursor_up(self, amount: int) -> None: - if amount == 0: - pass - elif amount == 1: - self.write_raw("\x1b[A") - else: - self.write_raw("\x1b[%iA" % amount) - - def cursor_down(self, amount: int) -> None: - if amount == 0: - pass - elif amount == 1: - # Note: Not the same as '\n', '\n' can cause the window content to - # scroll. - self.write_raw("\x1b[B") - else: - self.write_raw("\x1b[%iB" % amount) - - def cursor_forward(self, amount: int) -> None: - if amount == 0: - pass - elif amount == 1: - self.write_raw("\x1b[C") - else: - self.write_raw("\x1b[%iC" % amount) - - def cursor_backward(self, amount: int) -> None: - if amount == 0: - pass - elif amount == 1: - self.write_raw("\b") # '\x1b[D' - else: - self.write_raw("\x1b[%iD" % amount) - - def hide_cursor(self) -> None: - self.write_raw("\x1b[?25l") - - def show_cursor(self) -> None: - self.write_raw("\x1b[?12l\x1b[?25h") # Stop blinking cursor and show. - - def set_cursor_shape(self, cursor_shape: CursorShape) -> None: - if cursor_shape == CursorShape._NEVER_CHANGE: - return - - self._cursor_shape_changed = True - self.write_raw( - { - CursorShape.BLOCK: "\x1b[2 q", - CursorShape.BEAM: "\x1b[6 q", - CursorShape.UNDERLINE: "\x1b[4 q", - CursorShape.BLINKING_BLOCK: "\x1b[1 q", - CursorShape.BLINKING_BEAM: "\x1b[5 q", - CursorShape.BLINKING_UNDERLINE: "\x1b[3 q", - }.get(cursor_shape, "") - ) - - def reset_cursor_shape(self) -> None: - "Reset cursor shape." - # (Only reset cursor shape, if we ever changed it.) - if self._cursor_shape_changed: - self._cursor_shape_changed = False - - # Reset cursor shape. - self.write_raw("\x1b[0 q") - - def flush(self) -> None: - """ - Write to output stream and flush. - """ - if not self._buffer: - return - - data = "".join(self._buffer) - self._buffer = [] - - flush_stdout(self.stdout, data) - - def ask_for_cpr(self) -> None: - """ - Asks for a cursor position report (CPR). - """ - self.write_raw("\x1b[6n") - self.flush() - - @property - def responds_to_cpr(self) -> bool: - # When the input is a tty, we assume that CPR is supported. - # It's not when the input is piped from Pexpect. - if os.environ.get("PROMPT_TOOLKIT_NO_CPR", "") == "1": - return False - - if is_dumb_terminal(self.term): - return False - try: - return self.stdout.isatty() - except ValueError: - return False # ValueError: I/O operation on closed file - - def bell(self) -> None: - "Sound bell." - if self.enable_bell: - self.write_raw("\a") - self.flush() - - def get_default_color_depth(self) -> ColorDepth: - """ - Return the default color depth for a vt100 terminal, according to the - our term value. - - We prefer 256 colors almost always, because this is what most terminals - support these days, and is a good default. - """ - if self.default_color_depth is not None: - return self.default_color_depth - - term = self.term - - if term is None: - return ColorDepth.DEFAULT - - if is_dumb_terminal(term): - return ColorDepth.DEPTH_1_BIT - - if term in ("linux", "eterm-color"): - return ColorDepth.DEPTH_4_BIT - - return ColorDepth.DEFAULT diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/win32.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/win32.py deleted file mode 100644 index 1724eae5da7..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/win32.py +++ /dev/null @@ -1,687 +0,0 @@ -import sys - -assert sys.platform == "win32" - -import os -from ctypes import ArgumentError, byref, c_char, c_long, c_uint, c_ulong, pointer - -from ..utils import SPHINX_AUTODOC_RUNNING - -# Do not import win32-specific stuff when generating documentation. -# Otherwise RTD would be unable to generate docs for this module. -if not SPHINX_AUTODOC_RUNNING: - from ctypes import windll - -from ctypes.wintypes import DWORD, HANDLE -from typing import Callable, Dict, List, Optional, TextIO, Tuple, Type, TypeVar, Union - -from prompt_toolkit.cursor_shapes import CursorShape -from prompt_toolkit.data_structures import Size -from prompt_toolkit.styles import ANSI_COLOR_NAMES, Attrs -from prompt_toolkit.utils import get_cwidth -from prompt_toolkit.win32_types import ( - CONSOLE_SCREEN_BUFFER_INFO, - COORD, - SMALL_RECT, - STD_INPUT_HANDLE, - STD_OUTPUT_HANDLE, -) - -from .base import Output -from .color_depth import ColorDepth - -__all__ = [ - "Win32Output", -] - - -def _coord_byval(coord: COORD) -> c_long: - """ - Turns a COORD object into a c_long. - This will cause it to be passed by value instead of by reference. (That is what I think at least.) - - When running ``ptipython`` is run (only with IPython), we often got the following error:: - - Error in 'SetConsoleCursorPosition'. - ArgumentError("argument 2: <class 'TypeError'>: wrong type",) - argument 2: <class 'TypeError'>: wrong type - - It was solved by turning ``COORD`` parameters into a ``c_long`` like this. - - More info: http://msdn.microsoft.com/en-us/library/windows/desktop/ms686025(v=vs.85).aspx - """ - return c_long(coord.Y * 0x10000 | coord.X & 0xFFFF) - - -#: If True: write the output of the renderer also to the following file. This -#: is very useful for debugging. (e.g.: to see that we don't write more bytes -#: than required.) -_DEBUG_RENDER_OUTPUT = False -_DEBUG_RENDER_OUTPUT_FILENAME = r"prompt-toolkit-windows-output.log" - - -class NoConsoleScreenBufferError(Exception): - """ - Raised when the application is not running inside a Windows Console, but - the user tries to instantiate Win32Output. - """ - - def __init__(self) -> None: - # Are we running in 'xterm' on Windows, like git-bash for instance? - xterm = "xterm" in os.environ.get("TERM", "") - - if xterm: - message = ( - "Found %s, while expecting a Windows console. " - 'Maybe try to run this program using "winpty" ' - "or run it in cmd.exe instead. Or otherwise, " - "in case of Cygwin, use the Python executable " - "that is compiled for Cygwin." % os.environ["TERM"] - ) - else: - message = "No Windows console found. Are you running cmd.exe?" - super().__init__(message) - - -_T = TypeVar("_T") - - -class Win32Output(Output): - """ - I/O abstraction for rendering to Windows consoles. - (cmd.exe and similar.) - """ - - def __init__( - self, - stdout: TextIO, - use_complete_width: bool = False, - default_color_depth: Optional[ColorDepth] = None, - ) -> None: - self.use_complete_width = use_complete_width - self.default_color_depth = default_color_depth - - self._buffer: List[str] = [] - self.stdout: TextIO = stdout - self.hconsole = HANDLE(windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)) - - self._in_alternate_screen = False - self._hidden = False - - self.color_lookup_table = ColorLookupTable() - - # Remember the default console colors. - info = self.get_win32_screen_buffer_info() - self.default_attrs = info.wAttributes if info else 15 - - if _DEBUG_RENDER_OUTPUT: - self.LOG = open(_DEBUG_RENDER_OUTPUT_FILENAME, "ab") - - def fileno(self) -> int: - "Return file descriptor." - return self.stdout.fileno() - - def encoding(self) -> str: - "Return encoding used for stdout." - return self.stdout.encoding - - def write(self, data: str) -> None: - if self._hidden: - data = " " * get_cwidth(data) - - self._buffer.append(data) - - def write_raw(self, data: str) -> None: - "For win32, there is no difference between write and write_raw." - self.write(data) - - def get_size(self) -> Size: - info = self.get_win32_screen_buffer_info() - - # We take the width of the *visible* region as the size. Not the width - # of the complete screen buffer. (Unless use_complete_width has been - # set.) - if self.use_complete_width: - width = info.dwSize.X - else: - width = info.srWindow.Right - info.srWindow.Left - - height = info.srWindow.Bottom - info.srWindow.Top + 1 - - # We avoid the right margin, windows will wrap otherwise. - maxwidth = info.dwSize.X - 1 - width = min(maxwidth, width) - - # Create `Size` object. - return Size(rows=height, columns=width) - - def _winapi(self, func: Callable[..., _T], *a: object, **kw: object) -> _T: - """ - Flush and call win API function. - """ - self.flush() - - if _DEBUG_RENDER_OUTPUT: - self.LOG.write(("%r" % func.__name__).encode("utf-8") + b"\n") - self.LOG.write( - b" " + ", ".join(["%r" % i for i in a]).encode("utf-8") + b"\n" - ) - self.LOG.write( - b" " - + ", ".join(["%r" % type(i) for i in a]).encode("utf-8") - + b"\n" - ) - self.LOG.flush() - - try: - return func(*a, **kw) - except ArgumentError as e: - if _DEBUG_RENDER_OUTPUT: - self.LOG.write((f" Error in {func.__name__!r} {e!r} {e}\n").encode()) - - raise - - def get_win32_screen_buffer_info(self) -> CONSOLE_SCREEN_BUFFER_INFO: - """ - Return Screen buffer info. - """ - # NOTE: We don't call the `GetConsoleScreenBufferInfo` API through - # `self._winapi`. Doing so causes Python to crash on certain 64bit - # Python versions. (Reproduced with 64bit Python 2.7.6, on Windows - # 10). It is not clear why. Possibly, it has to do with passing - # these objects as an argument, or through *args. - - # The Python documentation contains the following - possibly related - warning: - # ctypes does not support passing unions or structures with - # bit-fields to functions by value. While this may work on 32-bit - # x86, it's not guaranteed by the library to work in the general - # case. Unions and structures with bit-fields should always be - # passed to functions by pointer. - - # Also see: - # - https://github.com/ipython/ipython/issues/10070 - # - https://github.com/jonathanslenders/python-prompt-toolkit/issues/406 - # - https://github.com/jonathanslenders/python-prompt-toolkit/issues/86 - - self.flush() - sbinfo = CONSOLE_SCREEN_BUFFER_INFO() - success = windll.kernel32.GetConsoleScreenBufferInfo( - self.hconsole, byref(sbinfo) - ) - - # success = self._winapi(windll.kernel32.GetConsoleScreenBufferInfo, - # self.hconsole, byref(sbinfo)) - - if success: - return sbinfo - else: - raise NoConsoleScreenBufferError - - def set_title(self, title: str) -> None: - """ - Set terminal title. - """ - self._winapi(windll.kernel32.SetConsoleTitleW, title) - - def clear_title(self) -> None: - self._winapi(windll.kernel32.SetConsoleTitleW, "") - - def erase_screen(self) -> None: - start = COORD(0, 0) - sbinfo = self.get_win32_screen_buffer_info() - length = sbinfo.dwSize.X * sbinfo.dwSize.Y - - self.cursor_goto(row=0, column=0) - self._erase(start, length) - - def erase_down(self) -> None: - sbinfo = self.get_win32_screen_buffer_info() - size = sbinfo.dwSize - - start = sbinfo.dwCursorPosition - length = (size.X - size.X) + size.X * (size.Y - sbinfo.dwCursorPosition.Y) - - self._erase(start, length) - - def erase_end_of_line(self) -> None: - """""" - sbinfo = self.get_win32_screen_buffer_info() - start = sbinfo.dwCursorPosition - length = sbinfo.dwSize.X - sbinfo.dwCursorPosition.X - - self._erase(start, length) - - def _erase(self, start: COORD, length: int) -> None: - chars_written = c_ulong() - - self._winapi( - windll.kernel32.FillConsoleOutputCharacterA, - self.hconsole, - c_char(b" "), - DWORD(length), - _coord_byval(start), - byref(chars_written), - ) - - # Reset attributes. - sbinfo = self.get_win32_screen_buffer_info() - self._winapi( - windll.kernel32.FillConsoleOutputAttribute, - self.hconsole, - sbinfo.wAttributes, - length, - _coord_byval(start), - byref(chars_written), - ) - - def reset_attributes(self) -> None: - "Reset the console foreground/background color." - self._winapi( - windll.kernel32.SetConsoleTextAttribute, self.hconsole, self.default_attrs - ) - self._hidden = False - - def set_attributes(self, attrs: Attrs, color_depth: ColorDepth) -> None: - ( - fgcolor, - bgcolor, - bold, - underline, - strike, - italic, - blink, - reverse, - hidden, - ) = attrs - self._hidden = bool(hidden) - - # Start from the default attributes. - win_attrs: int = self.default_attrs - - if color_depth != ColorDepth.DEPTH_1_BIT: - # Override the last four bits: foreground color. - if fgcolor: - win_attrs = win_attrs & ~0xF - win_attrs |= self.color_lookup_table.lookup_fg_color(fgcolor) - - # Override the next four bits: background color. - if bgcolor: - win_attrs = win_attrs & ~0xF0 - win_attrs |= self.color_lookup_table.lookup_bg_color(bgcolor) - - # Reverse: swap these four bits groups. - if reverse: - win_attrs = ( - (win_attrs & ~0xFF) - | ((win_attrs & 0xF) << 4) - | ((win_attrs & 0xF0) >> 4) - ) - - self._winapi(windll.kernel32.SetConsoleTextAttribute, self.hconsole, win_attrs) - - def disable_autowrap(self) -> None: - # Not supported by Windows. - pass - - def enable_autowrap(self) -> None: - # Not supported by Windows. - pass - - def cursor_goto(self, row: int = 0, column: int = 0) -> None: - pos = COORD(X=column, Y=row) - self._winapi( - windll.kernel32.SetConsoleCursorPosition, self.hconsole, _coord_byval(pos) - ) - - def cursor_up(self, amount: int) -> None: - sr = self.get_win32_screen_buffer_info().dwCursorPosition - pos = COORD(X=sr.X, Y=sr.Y - amount) - self._winapi( - windll.kernel32.SetConsoleCursorPosition, self.hconsole, _coord_byval(pos) - ) - - def cursor_down(self, amount: int) -> None: - self.cursor_up(-amount) - - def cursor_forward(self, amount: int) -> None: - sr = self.get_win32_screen_buffer_info().dwCursorPosition - # assert sr.X + amount >= 0, 'Negative cursor position: x=%r amount=%r' % (sr.X, amount) - - pos = COORD(X=max(0, sr.X + amount), Y=sr.Y) - self._winapi( - windll.kernel32.SetConsoleCursorPosition, self.hconsole, _coord_byval(pos) - ) - - def cursor_backward(self, amount: int) -> None: - self.cursor_forward(-amount) - - def flush(self) -> None: - """ - Write to output stream and flush. - """ - if not self._buffer: - # Only flush stdout buffer. (It could be that Python still has - # something in its buffer. -- We want to be sure to print that in - # the correct color.) - self.stdout.flush() - return - - data = "".join(self._buffer) - - if _DEBUG_RENDER_OUTPUT: - self.LOG.write(("%r" % data).encode("utf-8") + b"\n") - self.LOG.flush() - - # Print characters one by one. This appears to be the best solution - # in oder to avoid traces of vertical lines when the completion - # menu disappears. - for b in data: - written = DWORD() - - retval = windll.kernel32.WriteConsoleW( - self.hconsole, b, 1, byref(written), None - ) - assert retval != 0 - - self._buffer = [] - - def get_rows_below_cursor_position(self) -> int: - info = self.get_win32_screen_buffer_info() - return info.srWindow.Bottom - info.dwCursorPosition.Y + 1 - - def scroll_buffer_to_prompt(self) -> None: - """ - To be called before drawing the prompt. This should scroll the console - to left, with the cursor at the bottom (if possible). - """ - # Get current window size - info = self.get_win32_screen_buffer_info() - sr = info.srWindow - cursor_pos = info.dwCursorPosition - - result = SMALL_RECT() - - # Scroll to the left. - result.Left = 0 - result.Right = sr.Right - sr.Left - - # Scroll vertical - win_height = sr.Bottom - sr.Top - if 0 < sr.Bottom - cursor_pos.Y < win_height - 1: - # no vertical scroll if cursor already on the screen - result.Bottom = sr.Bottom - else: - result.Bottom = max(win_height, cursor_pos.Y) - result.Top = result.Bottom - win_height - - # Scroll API - self._winapi( - windll.kernel32.SetConsoleWindowInfo, self.hconsole, True, byref(result) - ) - - def enter_alternate_screen(self) -> None: - """ - Go to alternate screen buffer. - """ - if not self._in_alternate_screen: - GENERIC_READ = 0x80000000 - GENERIC_WRITE = 0x40000000 - - # Create a new console buffer and activate that one. - handle = HANDLE( - self._winapi( - windll.kernel32.CreateConsoleScreenBuffer, - GENERIC_READ | GENERIC_WRITE, - DWORD(0), - None, - DWORD(1), - None, - ) - ) - - self._winapi(windll.kernel32.SetConsoleActiveScreenBuffer, handle) - self.hconsole = handle - self._in_alternate_screen = True - - def quit_alternate_screen(self) -> None: - """ - Make stdout again the active buffer. - """ - if self._in_alternate_screen: - stdout = HANDLE( - self._winapi(windll.kernel32.GetStdHandle, STD_OUTPUT_HANDLE) - ) - self._winapi(windll.kernel32.SetConsoleActiveScreenBuffer, stdout) - self._winapi(windll.kernel32.CloseHandle, self.hconsole) - self.hconsole = stdout - self._in_alternate_screen = False - - def enable_mouse_support(self) -> None: - ENABLE_MOUSE_INPUT = 0x10 - - # This `ENABLE_QUICK_EDIT_MODE` flag needs to be cleared for mouse - # support to work, but it's possible that it was already cleared - # before. - ENABLE_QUICK_EDIT_MODE = 0x0040 - - handle = HANDLE(windll.kernel32.GetStdHandle(STD_INPUT_HANDLE)) - - original_mode = DWORD() - self._winapi(windll.kernel32.GetConsoleMode, handle, pointer(original_mode)) - self._winapi( - windll.kernel32.SetConsoleMode, - handle, - (original_mode.value | ENABLE_MOUSE_INPUT) & ~ENABLE_QUICK_EDIT_MODE, - ) - - def disable_mouse_support(self) -> None: - ENABLE_MOUSE_INPUT = 0x10 - handle = HANDLE(windll.kernel32.GetStdHandle(STD_INPUT_HANDLE)) - - original_mode = DWORD() - self._winapi(windll.kernel32.GetConsoleMode, handle, pointer(original_mode)) - self._winapi( - windll.kernel32.SetConsoleMode, - handle, - original_mode.value & ~ENABLE_MOUSE_INPUT, - ) - - def hide_cursor(self) -> None: - pass - - def show_cursor(self) -> None: - pass - - def set_cursor_shape(self, cursor_shape: CursorShape) -> None: - pass - - def reset_cursor_shape(self) -> None: - pass - - @classmethod - def win32_refresh_window(cls) -> None: - """ - Call win32 API to refresh the whole Window. - - This is sometimes necessary when the application paints background - for completion menus. When the menu disappears, it leaves traces due - to a bug in the Windows Console. Sending a repaint request solves it. - """ - # Get console handle - handle = HANDLE(windll.kernel32.GetConsoleWindow()) - - RDW_INVALIDATE = 0x0001 - windll.user32.RedrawWindow(handle, None, None, c_uint(RDW_INVALIDATE)) - - def get_default_color_depth(self) -> ColorDepth: - """ - Return the default color depth for a windows terminal. - - Contrary to the Vt100 implementation, this doesn't depend on a $TERM - variable. - """ - if self.default_color_depth is not None: - return self.default_color_depth - - # For now, by default, always use 4 bit color on Windows 10 by default, - # even when vt100 escape sequences with - # ENABLE_VIRTUAL_TERMINAL_PROCESSING are supported. We don't have a - # reliable way yet to know whether our console supports true color or - # only 4-bit. - return ColorDepth.DEPTH_4_BIT - - -class FOREGROUND_COLOR: - BLACK = 0x0000 - BLUE = 0x0001 - GREEN = 0x0002 - CYAN = 0x0003 - RED = 0x0004 - MAGENTA = 0x0005 - YELLOW = 0x0006 - GRAY = 0x0007 - INTENSITY = 0x0008 # Foreground color is intensified. - - -class BACKGROUND_COLOR: - BLACK = 0x0000 - BLUE = 0x0010 - GREEN = 0x0020 - CYAN = 0x0030 - RED = 0x0040 - MAGENTA = 0x0050 - YELLOW = 0x0060 - GRAY = 0x0070 - INTENSITY = 0x0080 # Background color is intensified. - - -def _create_ansi_color_dict( - color_cls: Union[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, - "ansiblack": color_cls.BLACK, - "ansigray": color_cls.GRAY, - "ansibrightblack": color_cls.BLACK | color_cls.INTENSITY, - "ansiwhite": color_cls.GRAY | color_cls.INTENSITY, - # Low intensity. - "ansired": color_cls.RED, - "ansigreen": color_cls.GREEN, - "ansiyellow": color_cls.YELLOW, - "ansiblue": color_cls.BLUE, - "ansimagenta": color_cls.MAGENTA, - "ansicyan": color_cls.CYAN, - # High intensity. - "ansibrightred": color_cls.RED | color_cls.INTENSITY, - "ansibrightgreen": color_cls.GREEN | color_cls.INTENSITY, - "ansibrightyellow": color_cls.YELLOW | color_cls.INTENSITY, - "ansibrightblue": color_cls.BLUE | color_cls.INTENSITY, - "ansibrightmagenta": color_cls.MAGENTA | color_cls.INTENSITY, - "ansibrightcyan": color_cls.CYAN | color_cls.INTENSITY, - } - - -FG_ANSI_COLORS = _create_ansi_color_dict(FOREGROUND_COLOR) -BG_ANSI_COLORS = _create_ansi_color_dict(BACKGROUND_COLOR) - -assert set(FG_ANSI_COLORS) == set(ANSI_COLOR_NAMES) -assert set(BG_ANSI_COLORS) == set(ANSI_COLOR_NAMES) - - -class ColorLookupTable: - """ - Inspired by pygments/formatters/terminal256.py - """ - - 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]] = {} - - @staticmethod - def _build_color_table() -> List[Tuple[int, int, int, int, int]]: - """ - Build an RGB-to-256 color conversion table - """ - FG = FOREGROUND_COLOR - BG = BACKGROUND_COLOR - - return [ - (0x00, 0x00, 0x00, FG.BLACK, BG.BLACK), - (0x00, 0x00, 0xAA, FG.BLUE, BG.BLUE), - (0x00, 0xAA, 0x00, FG.GREEN, BG.GREEN), - (0x00, 0xAA, 0xAA, FG.CYAN, BG.CYAN), - (0xAA, 0x00, 0x00, FG.RED, BG.RED), - (0xAA, 0x00, 0xAA, FG.MAGENTA, BG.MAGENTA), - (0xAA, 0xAA, 0x00, FG.YELLOW, BG.YELLOW), - (0x88, 0x88, 0x88, FG.GRAY, BG.GRAY), - (0x44, 0x44, 0xFF, FG.BLUE | FG.INTENSITY, BG.BLUE | BG.INTENSITY), - (0x44, 0xFF, 0x44, FG.GREEN | FG.INTENSITY, BG.GREEN | BG.INTENSITY), - (0x44, 0xFF, 0xFF, FG.CYAN | FG.INTENSITY, BG.CYAN | BG.INTENSITY), - (0xFF, 0x44, 0x44, FG.RED | FG.INTENSITY, BG.RED | BG.INTENSITY), - (0xFF, 0x44, 0xFF, FG.MAGENTA | FG.INTENSITY, BG.MAGENTA | BG.INTENSITY), - (0xFF, 0xFF, 0x44, FG.YELLOW | FG.INTENSITY, BG.YELLOW | BG.INTENSITY), - (0x44, 0x44, 0x44, FG.BLACK | FG.INTENSITY, BG.BLACK | BG.INTENSITY), - (0xFF, 0xFF, 0xFF, FG.GRAY | FG.INTENSITY, BG.GRAY | BG.INTENSITY), - ] - - 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 - - for r_, g_, b_, fg_, bg_ in self._win32_colors: - rd = r - r_ - gd = g - g_ - bd = b - b_ - - d = rd * rd + gd * gd + bd * bd - - if d < distance: - fg_match = fg_ - bg_match = bg_ - distance = d - return fg_match, bg_match - - def _color_indexes(self, color: str) -> Tuple[int, int]: - indexes = self.best_match.get(color, None) - if indexes is None: - try: - rgb = int(str(color), 16) - except ValueError: - rgb = 0 - - r = (rgb >> 16) & 0xFF - g = (rgb >> 8) & 0xFF - b = rgb & 0xFF - indexes = self._closest_color(r, g, b) - self.best_match[color] = indexes - return indexes - - def lookup_fg_color(self, fg_color: str) -> int: - """ - Return the color for use in the - `windll.kernel32.SetConsoleTextAttribute` API call. - - :param fg_color: Foreground as text. E.g. 'ffffff' or 'red' - """ - # Foreground. - if fg_color in FG_ANSI_COLORS: - return FG_ANSI_COLORS[fg_color] - else: - return self._color_indexes(fg_color)[0] - - def lookup_bg_color(self, bg_color: str) -> int: - """ - Return the color for use in the - `windll.kernel32.SetConsoleTextAttribute` API call. - - :param bg_color: Background as text. E.g. 'ffffff' or 'red' - """ - # Background. - if bg_color in BG_ANSI_COLORS: - return BG_ANSI_COLORS[bg_color] - else: - return self._color_indexes(bg_color)[1] diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/windows10.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/windows10.py deleted file mode 100644 index d5d55f18ca3..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/output/windows10.py +++ /dev/null @@ -1,107 +0,0 @@ -import sys - -assert sys.platform == "win32" - -from ctypes import byref, windll -from ctypes.wintypes import DWORD, HANDLE -from typing import Any, Optional, TextIO - -from prompt_toolkit.data_structures import Size -from prompt_toolkit.win32_types import STD_OUTPUT_HANDLE - -from .base import Output -from .color_depth import ColorDepth -from .vt100 import Vt100_Output -from .win32 import Win32Output - -__all__ = [ - "Windows10_Output", -] - -# See: https://msdn.microsoft.com/pl-pl/library/windows/desktop/ms686033(v=vs.85).aspx -ENABLE_PROCESSED_INPUT = 0x0001 -ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 - - -class Windows10_Output: - """ - Windows 10 output abstraction. This enables and uses vt100 escape sequences. - """ - - def __init__( - self, stdout: TextIO, default_color_depth: Optional[ColorDepth] = None - ) -> None: - self.win32_output = Win32Output(stdout, default_color_depth=default_color_depth) - self.vt100_output = Vt100_Output( - stdout, lambda: Size(0, 0), default_color_depth=default_color_depth - ) - self._hconsole = HANDLE(windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)) - - def flush(self) -> None: - """ - Write to output stream and flush. - """ - original_mode = DWORD(0) - - # Remember the previous console mode. - windll.kernel32.GetConsoleMode(self._hconsole, byref(original_mode)) - - # Enable processing of vt100 sequences. - windll.kernel32.SetConsoleMode( - self._hconsole, - DWORD(ENABLE_PROCESSED_INPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING), - ) - - try: - self.vt100_output.flush() - finally: - # Restore console mode. - windll.kernel32.SetConsoleMode(self._hconsole, original_mode) - - @property - def responds_to_cpr(self) -> bool: - return False # We don't need this on Windows. - - def __getattr__(self, name: str) -> Any: - if name in ( - "get_size", - "get_rows_below_cursor_position", - "enable_mouse_support", - "disable_mouse_support", - "scroll_buffer_to_prompt", - "get_win32_screen_buffer_info", - "enable_bracketed_paste", - "disable_bracketed_paste", - "get_default_color_depth", - ): - return getattr(self.win32_output, name) - else: - return getattr(self.vt100_output, name) - - -Output.register(Windows10_Output) - - -def is_win_vt100_enabled() -> bool: - """ - Returns True when we're running Windows and VT100 escape sequences are - supported. - """ - if sys.platform != "win32": - return False - - hconsole = HANDLE(windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)) - - # Get original console mode. - original_mode = DWORD(0) - windll.kernel32.GetConsoleMode(hconsole, byref(original_mode)) - - try: - # Try to enable VT100 sequences. - result: int = windll.kernel32.SetConsoleMode( - hconsole, DWORD(ENABLE_PROCESSED_INPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING) - ) - - return result == 1 - finally: - windll.kernel32.SetConsoleMode(hconsole, original_mode) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/patch_stdout.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/patch_stdout.py deleted file mode 100644 index 0abbcdb8478..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/patch_stdout.py +++ /dev/null @@ -1,288 +0,0 @@ -""" -patch_stdout -============ - -This implements a context manager that ensures that print statements within -it won't destroy the user interface. The context manager will replace -`sys.stdout` by something that draws the output above the current prompt, -rather than overwriting the UI. - -Usage:: - - with patch_stdout(application): - ... - application.run() - ... - -Multiple applications can run in the body of the context manager, one after the -other. -""" -import asyncio -import queue -import sys -import threading -import time -from contextlib import contextmanager -from typing import Generator, List, Optional, TextIO, Union, cast - -from .application import get_app_session, run_in_terminal -from .output import Output - -__all__ = [ - "patch_stdout", - "StdoutProxy", -] - - -@contextmanager -def patch_stdout(raw: bool = False) -> Generator[None, None, None]: - """ - Replace `sys.stdout` by an :class:`_StdoutProxy` instance. - - Writing to this proxy will make sure that the text appears above the - prompt, and that it doesn't destroy the output from the renderer. If no - application is curring, the behaviour should be identical to writing to - `sys.stdout` directly. - - Warning: If a new event loop is installed using `asyncio.set_event_loop()`, - then make sure that the context manager is applied after the event loop - is changed. Printing to stdout will be scheduled in the event loop - that's active when the context manager is created. - - :param raw: (`bool`) When True, vt100 terminal escape sequences are not - removed/escaped. - """ - with StdoutProxy(raw=raw) as proxy: - original_stdout = sys.stdout - original_stderr = sys.stderr - - # Enter. - sys.stdout = cast(TextIO, proxy) - sys.stderr = cast(TextIO, proxy) - - try: - yield - finally: - sys.stdout = original_stdout - sys.stderr = original_stderr - - -class _Done: - "Sentinel value for stopping the stdout proxy." - - -class StdoutProxy: - """ - File-like object, which prints everything written to it, output above the - current application/prompt. This class is compatible with other file - objects and can be used as a drop-in replacement for `sys.stdout` or can - for instance be passed to `logging.StreamHandler`. - - The current application, above which we print, is determined by looking - what application currently runs in the `AppSession` that is active during - the creation of this instance. - - This class can be used as a context manager. - - In order to avoid having to repaint the prompt continuously for every - little write, a short delay of `sleep_between_writes` seconds will be added - between writes in order to bundle many smaller writes in a short timespan. - """ - - def __init__( - self, - sleep_between_writes: float = 0.2, - raw: bool = False, - ) -> None: - - self.sleep_between_writes = sleep_between_writes - self.raw = raw - - self._lock = threading.RLock() - self._buffer: List[str] = [] - - # Keep track of the curret app session. - self.app_session = get_app_session() - - # See what output is active *right now*. We should do it at this point, - # before this `StdoutProxy` instance is possibly assigned to `sys.stdout`. - # Otherwise, if `patch_stdout` is used, and no `Output` instance has - # been created, then the default output creation code will see this - # proxy object as `sys.stdout`, and get in a recursive loop trying to - # access `StdoutProxy.isatty()` which will again retrieve the output. - self._output: Output = self.app_session.output - - # Flush thread - self._flush_queue: queue.Queue[Union[str, _Done]] = queue.Queue() - self._flush_thread = self._start_write_thread() - self.closed = False - - def __enter__(self) -> "StdoutProxy": - return self - - def __exit__(self, *args: object) -> None: - self.close() - - def close(self) -> None: - """ - Stop `StdoutProxy` proxy. - - This will terminate the write thread, make sure everything is flushed - and wait for the write thread to finish. - """ - if not self.closed: - self._flush_queue.put(_Done()) - self._flush_thread.join() - self.closed = True - - def _start_write_thread(self) -> threading.Thread: - thread = threading.Thread( - target=self._write_thread, - name="patch-stdout-flush-thread", - daemon=True, - ) - thread.start() - return thread - - def _write_thread(self) -> None: - done = False - - while not done: - item = self._flush_queue.get() - - if isinstance(item, _Done): - break - - # Don't bother calling when we got an empty string. - if not item: - continue - - text = [] - text.append(item) - - # Read the rest of the queue if more data was queued up. - while True: - try: - item = self._flush_queue.get_nowait() - except queue.Empty: - break - else: - if isinstance(item, _Done): - done = True - else: - text.append(item) - - app_loop = self._get_app_loop() - self._write_and_flush(app_loop, "".join(text)) - - # If an application was running that requires repainting, then wait - # for a very short time, in order to bundle actual writes and avoid - # having to repaint to often. - if app_loop is not None: - time.sleep(self.sleep_between_writes) - - def _get_app_loop(self) -> Optional[asyncio.AbstractEventLoop]: - """ - Return the event loop for the application currently running in our - `AppSession`. - """ - app = self.app_session.app - - if app is None: - return None - - return app.loop - - def _write_and_flush( - self, loop: Optional[asyncio.AbstractEventLoop], text: str - ) -> None: - """ - Write the given text to stdout and flush. - If an application is running, use `run_in_terminal`. - """ - - def write_and_flush() -> None: - if self.raw: - self._output.write_raw(text) - else: - self._output.write(text) - - self._output.flush() - - def write_and_flush_in_loop() -> None: - # If an application is running, use `run_in_terminal`, otherwise - # call it directly. - run_in_terminal(write_and_flush, in_executor=False) - - if loop is None: - # No loop, write immediately. - write_and_flush() - else: - # Make sure `write_and_flush` is executed *in* the event loop, not - # in another thread. - loop.call_soon_threadsafe(write_and_flush_in_loop) - - def _write(self, data: str) -> None: - """ - Note: print()-statements cause to multiple write calls. - (write('line') and write('\n')). Of course we don't want to call - `run_in_terminal` for every individual call, because that's too - expensive, and as long as the newline hasn't been written, the - text itself is again overwritten by the rendering of the input - command line. Therefor, we have a little buffer which holds the - text until a newline is written to stdout. - """ - if "\n" in data: - # When there is a newline in the data, write everything before the - # newline, including the newline itself. - before, after = data.rsplit("\n", 1) - to_write = self._buffer + [before, "\n"] - self._buffer = [after] - - text = "".join(to_write) - self._flush_queue.put(text) - else: - # Otherwise, cache in buffer. - self._buffer.append(data) - - def _flush(self) -> None: - text = "".join(self._buffer) - self._buffer = [] - self._flush_queue.put(text) - - def write(self, data: str) -> int: - with self._lock: - self._write(data) - - return len(data) # Pretend everything was written. - - def flush(self) -> None: - """ - Flush buffered output. - """ - with self._lock: - self._flush() - - @property - def original_stdout(self) -> TextIO: - return self._output.stdout or sys.__stdout__ - - # Attributes for compatibility with sys.__stdout__: - - def fileno(self) -> int: - return self._output.fileno() - - def isatty(self) -> bool: - stdout = self._output.stdout - if stdout is None: - return False - - return stdout.isatty() - - @property - def encoding(self) -> str: - return self._output.encoding() - - @property - def errors(self) -> str: - return "strict" diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/py.typed b/contrib/python/prompt-toolkit/py3/prompt_toolkit/py.typed deleted file mode 100644 index e69de29bb2d..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/py.typed +++ /dev/null diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/renderer.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/renderer.py deleted file mode 100644 index 463555c9ddd..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/renderer.py +++ /dev/null @@ -1,815 +0,0 @@ -""" -Renders the command line on the console. -(Redraws parts of the input line that were changed.) -""" -from asyncio import FIRST_COMPLETED, Future, ensure_future, sleep, wait -from collections import deque -from enum import Enum -from typing import TYPE_CHECKING, Any, Callable, Deque, Dict, Hashable, Optional, Tuple - -from prompt_toolkit.application.current import get_app -from prompt_toolkit.cursor_shapes import CursorShape -from prompt_toolkit.data_structures import Point, Size -from prompt_toolkit.filters import FilterOrBool, to_filter -from prompt_toolkit.formatted_text import AnyFormattedText, to_formatted_text -from prompt_toolkit.layout.mouse_handlers import MouseHandlers -from prompt_toolkit.layout.screen import Char, Screen, WritePosition -from prompt_toolkit.output import ColorDepth, Output -from prompt_toolkit.styles import ( - Attrs, - BaseStyle, - DummyStyleTransformation, - StyleTransformation, -) - -if TYPE_CHECKING: - from prompt_toolkit.application import Application - from prompt_toolkit.layout.layout import Layout - - -__all__ = [ - "Renderer", - "print_formatted_text", -] - - -def _output_screen_diff( - app: "Application[Any]", - output: Output, - screen: Screen, - current_pos: Point, - color_depth: ColorDepth, - previous_screen: Optional[Screen], - last_style: Optional[str], - is_done: bool, # XXX: drop is_done - full_screen: bool, - attrs_for_style_string: "_StyleStringToAttrsCache", - style_string_has_style: "_StyleStringHasStyleCache", - size: Size, - previous_width: int, -) -> Tuple[Point, Optional[str]]: - """ - Render the diff between this screen and the previous screen. - - This takes two `Screen` instances. The one that represents the output like - it was during the last rendering and one that represents the current - output raster. Looking at these two `Screen` instances, this function will - render the difference by calling the appropriate methods of the `Output` - object that only paint the changes to the terminal. - - This is some performance-critical code which is heavily optimized. - Don't change things without profiling first. - - :param current_pos: Current cursor position. - :param last_style: The style string, used for drawing the last drawn - character. (Color/attributes.) - :param attrs_for_style_string: :class:`._StyleStringToAttrsCache` instance. - :param width: The width of the terminal. - :param previous_width: The width of the terminal during the last rendering. - """ - width, height = size.columns, size.rows - - #: Variable for capturing the output. - write = output.write - write_raw = output.write_raw - - # Create locals for the most used output methods. - # (Save expensive attribute lookups.) - _output_set_attributes = output.set_attributes - _output_reset_attributes = output.reset_attributes - _output_cursor_forward = output.cursor_forward - _output_cursor_up = output.cursor_up - _output_cursor_backward = output.cursor_backward - - # Hide cursor before rendering. (Avoid flickering.) - output.hide_cursor() - - def reset_attributes() -> None: - "Wrapper around Output.reset_attributes." - nonlocal last_style - _output_reset_attributes() - last_style = None # Forget last char after resetting attributes. - - def move_cursor(new: Point) -> Point: - "Move cursor to this `new` point. Returns the given Point." - current_x, current_y = current_pos.x, current_pos.y - - if new.y > current_y: - # Use newlines instead of CURSOR_DOWN, because this might add new lines. - # CURSOR_DOWN will never create new lines at the bottom. - # Also reset attributes, otherwise the newline could draw a - # background color. - reset_attributes() - write("\r\n" * (new.y - current_y)) - current_x = 0 - _output_cursor_forward(new.x) - return new - elif new.y < current_y: - _output_cursor_up(current_y - new.y) - - if current_x >= width - 1: - write("\r") - _output_cursor_forward(new.x) - elif new.x < current_x or current_x >= width - 1: - _output_cursor_backward(current_x - new.x) - elif new.x > current_x: - _output_cursor_forward(new.x - current_x) - - return new - - def output_char(char: Char) -> None: - """ - Write the output of this character. - """ - nonlocal last_style - - # If the last printed character has the same style, don't output the - # style again. - if last_style == char.style: - write(char.char) - else: - # Look up `Attr` for this style string. Only set attributes if different. - # (Two style strings can still have the same formatting.) - # Note that an empty style string can have formatting that needs to - # be applied, because of style transformations. - new_attrs = attrs_for_style_string[char.style] - if not last_style or new_attrs != attrs_for_style_string[last_style]: - _output_set_attributes(new_attrs, color_depth) - - write(char.char) - last_style = char.style - - 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 - terminal output. - - There are two reasons we are sometimes seeing whitespace at the end: - - `BufferControl` adds a trailing space to each line, because it's a - possible cursor position, so that the line wrapping won't change if - the cursor position moves around. - - The `Window` adds a style class to the current line for highlighting - (cursor-line). - """ - numbers = [ - index - for index, cell in row.items() - if cell.char != " " or style_string_has_style[cell.style] - ] - numbers.append(0) - return max(numbers) - - # Render for the first time: reset styling. - if not previous_screen: - reset_attributes() - - # Disable autowrap. (When entering a the alternate screen, or anytime when - # we have a prompt. - In the case of a REPL, like IPython, people can have - # background threads, and it's hard for debugging if their output is not - # wrapped.) - if not previous_screen or not full_screen: - output.disable_autowrap() - - # When the previous screen has a different size, redraw everything anyway. - # Also when we are done. (We might take up less rows, so clearing is important.) - if ( - is_done or not previous_screen or previous_width != width - ): # XXX: also consider height?? - current_pos = move_cursor(Point(x=0, y=0)) - reset_attributes() - output.erase_down() - - previous_screen = Screen() - - # Get height of the screen. - # (height changes as we loop over data_buffer, so remember the current value.) - # (Also make sure to clip the height to the size of the output.) - current_height = min(screen.height, height) - - # Loop over the rows. - row_count = min(max(screen.height, previous_screen.height), height) - c = 0 # Column counter. - - for y in range(row_count): - new_row = screen.data_buffer[y] - previous_row = previous_screen.data_buffer[y] - zero_width_escapes_row = screen.zero_width_escapes[y] - - new_max_line_len = min(width - 1, get_max_column_index(new_row)) - previous_max_line_len = min(width - 1, get_max_column_index(previous_row)) - - # Loop over the columns. - c = 0 - while c <= new_max_line_len: - new_char = new_row[c] - old_char = previous_row[c] - char_width = new_char.width or 1 - - # When the old and new character at this position are different, - # draw the output. (Because of the performance, we don't call - # `Char.__ne__`, but inline the same expression.) - if new_char.char != old_char.char or new_char.style != old_char.style: - current_pos = move_cursor(Point(x=c, y=y)) - - # Send injected escape sequences to output. - if c in zero_width_escapes_row: - write_raw(zero_width_escapes_row[c]) - - output_char(new_char) - current_pos = Point(x=current_pos.x + char_width, y=current_pos.y) - - c += char_width - - # If the new line is shorter, trim it. - if previous_screen and new_max_line_len < previous_max_line_len: - current_pos = move_cursor(Point(x=new_max_line_len + 1, y=y)) - reset_attributes() - output.erase_end_of_line() - - # Correctly reserve vertical space as required by the layout. - # When this is a new screen (drawn for the first time), or for some reason - # higher than the previous one. Move the cursor once to the bottom of the - # output. That way, we're sure that the terminal scrolls up, even when the - # lower lines of the canvas just contain whitespace. - - # The most obvious reason that we actually want this behaviour is the avoid - # the artifact of the input scrolling when the completion menu is shown. - # (If the scrolling is actually wanted, the layout can still be build in a - # way to behave that way by setting a dynamic height.) - if current_height > previous_screen.height: - current_pos = move_cursor(Point(x=0, y=current_height - 1)) - - # Move cursor: - if is_done: - current_pos = move_cursor(Point(x=0, y=current_height)) - output.erase_down() - else: - current_pos = move_cursor(screen.get_cursor_position(app.layout.current_window)) - - if is_done or not full_screen: - output.enable_autowrap() - - # Always reset the color attributes. This is important because a background - # thread could print data to stdout and we want that to be displayed in the - # default colors. (Also, if a background color has been set, many terminals - # give weird artifacts on resize events.) - reset_attributes() - - if screen.show_cursor or is_done: - output.show_cursor() - - return current_pos, last_style - - -class HeightIsUnknownError(Exception): - "Information unavailable. Did not yet receive the CPR response." - - -class _StyleStringToAttrsCache(Dict[str, Attrs]): - """ - A cache structure that maps style strings to :class:`.Attr`. - (This is an important speed up.) - """ - - def __init__( - self, - get_attrs_for_style_str: Callable[["str"], Attrs], - style_transformation: StyleTransformation, - ) -> None: - - self.get_attrs_for_style_str = get_attrs_for_style_str - self.style_transformation = style_transformation - - def __missing__(self, style_str: str) -> Attrs: - attrs = self.get_attrs_for_style_str(style_str) - attrs = self.style_transformation.transform_attrs(attrs) - - self[style_str] = attrs - return attrs - - -class _StyleStringHasStyleCache(Dict[str, bool]): - """ - Cache for remember which style strings don't render the default output - style (default fg/bg, no underline and no reverse and no blink). That way - we know that we should render these cells, even when they're empty (when - they contain a space). - - Note: we don't consider bold/italic/hidden because they don't change the - output if there's no text in the cell. - """ - - 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: - attrs = self.style_string_to_attrs[style_str] - is_default = bool( - attrs.color - or attrs.bgcolor - or attrs.underline - or attrs.strike - or attrs.blink - or attrs.reverse - ) - - self[style_str] = is_default - return is_default - - -class CPR_Support(Enum): - "Enum: whether or not CPR is supported." - SUPPORTED = "SUPPORTED" - NOT_SUPPORTED = "NOT_SUPPORTED" - UNKNOWN = "UNKNOWN" - - -class Renderer: - """ - Typical usage: - - :: - - output = Vt100_Output.from_pty(sys.stdout) - r = Renderer(style, output) - r.render(app, layout=...) - """ - - CPR_TIMEOUT = 2 # Time to wait until we consider CPR to be not supported. - - def __init__( - self, - style: BaseStyle, - output: Output, - full_screen: bool = False, - mouse_support: FilterOrBool = False, - cpr_not_supported_callback: Optional[Callable[[], None]] = None, - ) -> None: - - self.style = style - self.output = output - self.full_screen = full_screen - self.mouse_support = to_filter(mouse_support) - self.cpr_not_supported_callback = cpr_not_supported_callback - - self._in_alternate_screen = False - self._mouse_support_enabled = False - self._bracketed_paste_enabled = False - self._cursor_key_mode_reset = False - - # Future set when we are waiting for a CPR flag. - self._waiting_for_cpr_futures: Deque[Future[None]] = deque() - self.cpr_support = CPR_Support.UNKNOWN - - if not output.responds_to_cpr: - 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.reset(_scroll=True) - - def reset(self, _scroll: bool = False, leave_alternate_screen: bool = True) -> None: - - # Reset position - self._cursor_pos = Point(x=0, y=0) - - # Remember the last screen instance between renderers. This way, - # 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 - - # Default MouseHandlers. (Just empty.) - self.mouse_handlers = MouseHandlers() - - #: Space from the top of the layout, until the bottom of the terminal. - #: We don't know this until a `report_absolute_cursor_row` call. - self._min_available_height = 0 - - # In case of Windows, also make sure to scroll to the current cursor - # position. (Only when rendering the first time.) - # It does nothing for vt100 terminals. - if _scroll: - self.output.scroll_buffer_to_prompt() - - # Quit alternate screen. - if self._in_alternate_screen and leave_alternate_screen: - self.output.quit_alternate_screen() - self._in_alternate_screen = False - - # Disable mouse support. - if self._mouse_support_enabled: - self.output.disable_mouse_support() - self._mouse_support_enabled = False - - # Disable bracketed paste. - if self._bracketed_paste_enabled: - self.output.disable_bracketed_paste() - self._bracketed_paste_enabled = False - - self.output.reset_cursor_shape() - - # NOTE: No need to set/reset cursor key mode here. - - # Flush output. `disable_mouse_support` needs to write to stdout. - self.output.flush() - - @property - def last_rendered_screen(self) -> Optional[Screen]: - """ - The `Screen` class that was generated during the last rendering. - This can be `None`. - """ - return self._last_screen - - @property - def height_is_known(self) -> bool: - """ - True when the height from the cursor until the bottom of the terminal - is known. (It's often nicer to draw bottom toolbars only if the height - is known, in order to avoid flickering when the CPR response arrives.) - """ - if self.full_screen or self._min_available_height > 0: - return True - try: - self._min_available_height = self.output.get_rows_below_cursor_position() - return True - except NotImplementedError: - return False - - @property - def rows_above_layout(self) -> int: - """ - Return the number of rows visible in the terminal above the layout. - """ - if self._in_alternate_screen: - return 0 - elif self._min_available_height > 0: - total_rows = self.output.get_size().rows - last_screen_height = self._last_screen.height if self._last_screen else 0 - return total_rows - max(self._min_available_height, last_screen_height) - else: - raise HeightIsUnknownError("Rows above layout is unknown.") - - def request_absolute_cursor_position(self) -> None: - """ - Get current cursor position. - - We do this to calculate the minimum available height that we can - consume for rendering the prompt. This is the available space below te - cursor. - - For vt100: Do CPR request. (answer will arrive later.) - For win32: Do API call. (Answer comes immediately.) - """ - # Only do this request when the cursor is at the top row. (after a - # clear or reset). We will rely on that in `report_absolute_cursor_row`. - assert self._cursor_pos.y == 0 - - # In full-screen mode, always use the total height as min-available-height. - if self.full_screen: - self._min_available_height = self.output.get_size().rows - return - - # For Win32, we have an API call to get the number of rows below the - # cursor. - try: - self._min_available_height = self.output.get_rows_below_cursor_position() - return - except NotImplementedError: - pass - - # Use CPR. - if self.cpr_support == CPR_Support.NOT_SUPPORTED: - return - - def do_cpr() -> None: - # Asks for a cursor position report (CPR). - self._waiting_for_cpr_futures.append(Future()) - self.output.ask_for_cpr() - - if self.cpr_support == CPR_Support.SUPPORTED: - do_cpr() - return - - # If we don't know whether CPR is supported, only do a request if - # none is pending, and test it, using a timer. - if self.waiting_for_cpr: - return - - do_cpr() - - async def timer() -> None: - await sleep(self.CPR_TIMEOUT) - - # Not set in the meantime -> not supported. - if self.cpr_support == CPR_Support.UNKNOWN: - self.cpr_support = CPR_Support.NOT_SUPPORTED - - if self.cpr_not_supported_callback: - # Make sure to call this callback in the main thread. - self.cpr_not_supported_callback() - - get_app().create_background_task(timer()) - - def report_absolute_cursor_row(self, row: int) -> None: - """ - To be called when we know the absolute cursor position. - (As an answer of a "Cursor Position Request" response.) - """ - self.cpr_support = CPR_Support.SUPPORTED - - # Calculate the amount of rows from the cursor position until the - # bottom of the terminal. - total_rows = self.output.get_size().rows - rows_below_cursor = total_rows - row + 1 - - # Set the minimum available height. - self._min_available_height = rows_below_cursor - - # Pop and set waiting for CPR future. - try: - f = self._waiting_for_cpr_futures.popleft() - except IndexError: - pass # Received CPR response without having a CPR. - else: - f.set_result(None) - - @property - def waiting_for_cpr(self) -> bool: - """ - Waiting for CPR flag. True when we send the request, but didn't got a - response. - """ - return bool(self._waiting_for_cpr_futures) - - async def wait_for_cpr_responses(self, timeout: int = 1) -> None: - """ - Wait for a CPR response. - """ - cpr_futures = list(self._waiting_for_cpr_futures) # Make copy. - - # When there are no CPRs in the queue. Don't do anything. - if not cpr_futures or self.cpr_support == CPR_Support.NOT_SUPPORTED: - return None - - async def wait_for_responses() -> None: - for response_f in cpr_futures: - await response_f - - async def wait_for_timeout() -> None: - await sleep(timeout) - - # Got timeout, erase queue. - for response_f in cpr_futures: - response_f.cancel() - self._waiting_for_cpr_futures = deque() - - tasks = { - ensure_future(wait_for_responses()), - ensure_future(wait_for_timeout()), - } - _, pending = await wait(tasks, return_when=FIRST_COMPLETED) - for task in pending: - task.cancel() - - def render( - self, app: "Application[Any]", layout: "Layout", is_done: bool = False - ) -> None: - """ - Render the current interface to the output. - - :param is_done: When True, put the cursor at the end of the interface. We - won't print any changes to this part. - """ - output = self.output - - # Enter alternate screen. - if self.full_screen and not self._in_alternate_screen: - self._in_alternate_screen = True - output.enter_alternate_screen() - - # Enable bracketed paste. - if not self._bracketed_paste_enabled: - self.output.enable_bracketed_paste() - self._bracketed_paste_enabled = True - - # Reset cursor key mode. - if not self._cursor_key_mode_reset: - self.output.reset_cursor_key_mode() - self._cursor_key_mode_reset = True - - # Enable/disable mouse support. - needs_mouse_support = self.mouse_support() - - if needs_mouse_support and not self._mouse_support_enabled: - output.enable_mouse_support() - self._mouse_support_enabled = True - - elif not needs_mouse_support and self._mouse_support_enabled: - output.disable_mouse_support() - self._mouse_support_enabled = False - - # Create screen and write layout to it. - size = output.get_size() - screen = Screen() - screen.show_cursor = False # Hide cursor by default, unless one of the - # containers decides to display it. - mouse_handlers = MouseHandlers() - - # Calculate height. - if self.full_screen: - height = size.rows - elif is_done: - # When we are done, we don't necessary want to fill up until the bottom. - height = layout.container.preferred_height( - size.columns, size.rows - ).preferred - else: - last_height = self._last_screen.height if self._last_screen else 0 - height = max( - self._min_available_height, - last_height, - layout.container.preferred_height(size.columns, size.rows).preferred, - ) - - height = min(height, size.rows) - - # When the size changes, don't consider the previous screen. - if self._last_size != size: - self._last_screen = None - - # When we render using another style or another color depth, do a full - # repaint. (Forget about the previous rendered screen.) - # (But note that we still use _last_screen to calculate the height.) - if ( - self.style.invalidation_hash() != self._last_style_hash - or app.style_transformation.invalidation_hash() - != self._last_transformation_hash - or app.color_depth != self._last_color_depth - ): - self._last_screen = None - self._attrs_for_style = None - self._style_string_has_style = None - - if self._attrs_for_style is None: - self._attrs_for_style = _StyleStringToAttrsCache( - self.style.get_attrs_for_style_str, app.style_transformation - ) - if self._style_string_has_style is None: - self._style_string_has_style = _StyleStringHasStyleCache( - self._attrs_for_style - ) - - self._last_style_hash = self.style.invalidation_hash() - self._last_transformation_hash = app.style_transformation.invalidation_hash() - self._last_color_depth = app.color_depth - - layout.container.write_to_screen( - screen, - mouse_handlers, - WritePosition(xpos=0, ypos=0, width=size.columns, height=height), - parent_style="", - erase_bg=False, - z_index=None, - ) - screen.draw_all_floats() - - # When grayed. Replace all styles in the new screen. - if app.exit_style: - screen.append_style_to_content(app.exit_style) - - # Process diff and write to output. - self._cursor_pos, self._last_style = _output_screen_diff( - app, - output, - screen, - self._cursor_pos, - app.color_depth, - self._last_screen, - self._last_style, - is_done, - full_screen=self.full_screen, - attrs_for_style_string=self._attrs_for_style, - style_string_has_style=self._style_string_has_style, - size=size, - previous_width=(self._last_size.columns if self._last_size else 0), - ) - self._last_screen = screen - self._last_size = size - self.mouse_handlers = mouse_handlers - - # Handle cursor shapes. - new_cursor_shape = app.cursor.get_cursor_shape(app) - if ( - self._last_cursor_shape is None - or self._last_cursor_shape != new_cursor_shape - ): - output.set_cursor_shape(new_cursor_shape) - self._last_cursor_shape = new_cursor_shape - - # Flush buffered output. - output.flush() - - # Set visible windows in layout. - app.layout.visible_windows = screen.visible_windows - - if is_done: - self.reset() - - def erase(self, leave_alternate_screen: bool = True) -> None: - """ - Hide all output and put the cursor back at the first line. This is for - instance used for running a system command (while hiding the CLI) and - later resuming the same CLI.) - - :param leave_alternate_screen: When True, and when inside an alternate - screen buffer, quit the alternate screen. - """ - output = self.output - - output.cursor_backward(self._cursor_pos.x) - output.cursor_up(self._cursor_pos.y) - output.erase_down() - output.reset_attributes() - output.enable_autowrap() - - output.flush() - - self.reset(leave_alternate_screen=leave_alternate_screen) - - def clear(self) -> None: - """ - Clear screen and go to 0,0 - """ - # Erase current output first. - self.erase() - - # Send "Erase Screen" command and go to (0, 0). - output = self.output - - output.erase_screen() - output.cursor_goto(0, 0) - output.flush() - - self.request_absolute_cursor_position() - - -def print_formatted_text( - output: Output, - formatted_text: AnyFormattedText, - style: BaseStyle, - style_transformation: Optional[StyleTransformation] = None, - color_depth: Optional[ColorDepth] = None, -) -> None: - """ - Print a list of (style_str, text) tuples in the given style to the output. - """ - fragments = to_formatted_text(formatted_text) - style_transformation = style_transformation or DummyStyleTransformation() - color_depth = color_depth or output.get_default_color_depth() - - # Reset first. - output.reset_attributes() - output.enable_autowrap() - last_attrs: Optional[Attrs] = None - - # Print all (style_str, text) tuples. - attrs_for_style_string = _StyleStringToAttrsCache( - style.get_attrs_for_style_str, style_transformation - ) - - for style_str, text, *_ in fragments: - attrs = attrs_for_style_string[style_str] - - # Set style attributes if something changed. - if attrs != last_attrs: - if attrs: - output.set_attributes(attrs, color_depth) - else: - output.reset_attributes() - last_attrs = attrs - - # Print escape sequences as raw output - if "[ZeroWidthEscape]" in style_str: - output.write_raw(text) - else: - # Eliminate carriage returns - text = text.replace("\r", "") - # Insert a carriage return before every newline (important when the - # front-end is a telnet client). - text = text.replace("\n", "\r\n") - output.write(text) - - # Reset again. - output.reset_attributes() - output.flush() diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/search.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/search.py deleted file mode 100644 index 413cc6ad9c7..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/search.py +++ /dev/null @@ -1,229 +0,0 @@ -""" -Search operations. - -For the key bindings implementation with attached filters, check -`prompt_toolkit.key_binding.bindings.search`. (Use these for new key bindings -instead of calling these function directly.) -""" -from enum import Enum -from typing import TYPE_CHECKING, Dict, Optional - -from .application.current import get_app -from .filters import FilterOrBool, is_searching, to_filter -from .key_binding.vi_state import InputMode - -if TYPE_CHECKING: - from prompt_toolkit.layout.controls import BufferControl, SearchBufferControl - from prompt_toolkit.layout.layout import Layout - -__all__ = [ - "SearchDirection", - "start_search", - "stop_search", -] - - -class SearchDirection(Enum): - FORWARD = "FORWARD" - BACKWARD = "BACKWARD" - - -class SearchState: - """ - A search 'query', associated with a search field (like a SearchToolbar). - - Every searchable `BufferControl` points to a `search_buffer_control` - (another `BufferControls`) which represents the search field. The - `SearchState` attached to that search field is used for storing the current - search query. - - It is possible to have one searchfield for multiple `BufferControls`. In - that case, they'll share the same `SearchState`. - If there are multiple `BufferControls` that display the same `Buffer`, then - they can have a different `SearchState` each (if they have a different - search control). - """ - - __slots__ = ("text", "direction", "ignore_case") - - def __init__( - self, - text: str = "", - direction: SearchDirection = SearchDirection.FORWARD, - ignore_case: FilterOrBool = False, - ) -> None: - - self.text = text - self.direction = direction - self.ignore_case = to_filter(ignore_case) - - def __repr__(self) -> str: - return "{}({!r}, direction={!r}, ignore_case={!r})".format( - self.__class__.__name__, - self.text, - self.direction, - self.ignore_case, - ) - - def __invert__(self) -> "SearchState": - """ - Create a new SearchState where backwards becomes forwards and the other - way around. - """ - if self.direction == SearchDirection.BACKWARD: - direction = SearchDirection.FORWARD - else: - direction = SearchDirection.BACKWARD - - return SearchState( - text=self.text, direction=direction, ignore_case=self.ignore_case - ) - - -def start_search( - buffer_control: Optional["BufferControl"] = None, - direction: SearchDirection = SearchDirection.FORWARD, -) -> None: - """ - Start search through the given `buffer_control` using the - `search_buffer_control`. - - :param buffer_control: Start search for this `BufferControl`. If not given, - search through the current control. - """ - from prompt_toolkit.layout.controls import BufferControl - - assert buffer_control is None or isinstance(buffer_control, BufferControl) - - layout = get_app().layout - - # When no control is given, use the current control if that's a BufferControl. - if buffer_control is None: - if not isinstance(layout.current_control, BufferControl): - return - buffer_control = layout.current_control - - # Only if this control is searchable. - search_buffer_control = buffer_control.search_buffer_control - - if search_buffer_control: - buffer_control.search_state.direction = direction - - # Make sure to focus the search BufferControl - layout.focus(search_buffer_control) - - # Remember search link. - layout.search_links[search_buffer_control] = buffer_control - - # If we're in Vi mode, make sure to go into insert mode. - get_app().vi_state.input_mode = InputMode.INSERT - - -def stop_search(buffer_control: Optional["BufferControl"] = None) -> None: - """ - Stop search through the given `buffer_control`. - """ - layout = get_app().layout - - if buffer_control is None: - buffer_control = layout.search_target_buffer_control - if buffer_control is None: - # (Should not happen, but possible when `stop_search` is called - # when we're not searching.) - return - search_buffer_control = buffer_control.search_buffer_control - else: - assert buffer_control in layout.search_links.values() - search_buffer_control = _get_reverse_search_links(layout)[buffer_control] - - # Focus the original buffer again. - layout.focus(buffer_control) - - if search_buffer_control is not None: - # Remove the search link. - del layout.search_links[search_buffer_control] - - # Reset content of search control. - search_buffer_control.buffer.reset() - - # If we're in Vi mode, go back to navigation mode. - get_app().vi_state.input_mode = InputMode.NAVIGATION - - -def do_incremental_search(direction: SearchDirection, count: int = 1) -> None: - """ - Apply search, but keep search buffer focused. - """ - assert is_searching() - - layout = get_app().layout - - # Only search if the current control is a `BufferControl`. - from prompt_toolkit.layout.controls import BufferControl - - search_control = layout.current_control - if not isinstance(search_control, BufferControl): - return - - prev_control = layout.search_target_buffer_control - if prev_control is None: - return - search_state = prev_control.search_state - - # Update search_state. - direction_changed = search_state.direction != direction - - search_state.text = search_control.buffer.text - search_state.direction = direction - - # Apply search to current buffer. - if not direction_changed: - prev_control.buffer.apply_search( - search_state, include_current_position=False, count=count - ) - - -def accept_search() -> None: - """ - Accept current search query. Focus original `BufferControl` again. - """ - layout = get_app().layout - - search_control = layout.current_control - target_buffer_control = layout.search_target_buffer_control - - from prompt_toolkit.layout.controls import BufferControl - - if not isinstance(search_control, BufferControl): - return - if target_buffer_control is None: - return - - search_state = target_buffer_control.search_state - - # Update search state. - if search_control.buffer.text: - search_state.text = search_control.buffer.text - - # Apply search. - target_buffer_control.buffer.apply_search( - search_state, include_current_position=True - ) - - # Add query to history of search line. - search_control.buffer.append_to_history() - - # Stop search and focus previous control again. - stop_search(target_buffer_control) - - -def _get_reverse_search_links( - layout: "Layout", -) -> Dict["BufferControl", "SearchBufferControl"]: - """ - Return mapping from BufferControl to SearchBufferControl. - """ - return { - buffer_control: search_buffer_control - for search_buffer_control, buffer_control in layout.search_links.items() - } diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/selection.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/selection.py deleted file mode 100644 index a02aa8708cb..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/selection.py +++ /dev/null @@ -1,60 +0,0 @@ -""" -Data structures for the selection. -""" -from enum import Enum - -__all__ = [ - "SelectionType", - "PasteMode", - "SelectionState", -] - - -class SelectionType(Enum): - """ - Type of selection. - """ - - #: Characters. (Visual in Vi.) - CHARACTERS = "CHARACTERS" - - #: Whole lines. (Visual-Line in Vi.) - LINES = "LINES" - - #: A block selection. (Visual-Block in Vi.) - BLOCK = "BLOCK" - - -class PasteMode(Enum): - EMACS = "EMACS" # Yank like emacs. - VI_AFTER = "VI_AFTER" # When pressing 'p' in Vi. - VI_BEFORE = "VI_BEFORE" # When pressing 'P' in Vi. - - -class SelectionState: - """ - State of the current selection. - - :param original_cursor_position: int - :param type: :class:`~.SelectionType` - """ - - def __init__( - self, - original_cursor_position: int = 0, - type: SelectionType = SelectionType.CHARACTERS, - ) -> None: - - self.original_cursor_position = original_cursor_position - self.type = type - self.shift_mode = False - - def enter_shift_mode(self) -> None: - self.shift_mode = True - - def __repr__(self) -> str: - return "{}(original_cursor_position={!r}, type={!r})".format( - self.__class__.__name__, - self.original_cursor_position, - self.type, - ) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/shortcuts/__init__.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/shortcuts/__init__.py deleted file mode 100644 index 10ad31483ed..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/shortcuts/__init__.py +++ /dev/null @@ -1,44 +0,0 @@ -from .dialogs import ( - button_dialog, - checkboxlist_dialog, - input_dialog, - message_dialog, - progress_dialog, - radiolist_dialog, - yes_no_dialog, -) -from .progress_bar import ProgressBar, ProgressBarCounter -from .prompt import ( - CompleteStyle, - PromptSession, - confirm, - create_confirm_session, - prompt, -) -from .utils import clear, clear_title, print_container, print_formatted_text, set_title - -__all__ = [ - # Dialogs. - "input_dialog", - "message_dialog", - "progress_dialog", - "checkboxlist_dialog", - "radiolist_dialog", - "yes_no_dialog", - "button_dialog", - # Prompts. - "PromptSession", - "prompt", - "confirm", - "create_confirm_session", - "CompleteStyle", - # Progress bars. - "ProgressBar", - "ProgressBarCounter", - # Utils. - "clear", - "clear_title", - "print_container", - "print_formatted_text", - "set_title", -] diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/shortcuts/dialogs.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/shortcuts/dialogs.py deleted file mode 100644 index eacb05a00cc..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/shortcuts/dialogs.py +++ /dev/null @@ -1,327 +0,0 @@ -import functools -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.filters import FilterOrBool -from prompt_toolkit.formatted_text import AnyFormattedText -from prompt_toolkit.key_binding.bindings.focus import focus_next, focus_previous -from prompt_toolkit.key_binding.defaults import load_key_bindings -from prompt_toolkit.key_binding.key_bindings import KeyBindings, merge_key_bindings -from prompt_toolkit.layout import Layout -from prompt_toolkit.layout.containers import AnyContainer, HSplit -from prompt_toolkit.layout.dimension import Dimension as D -from prompt_toolkit.styles import BaseStyle -from prompt_toolkit.validation import Validator -from prompt_toolkit.widgets import ( - Box, - Button, - CheckboxList, - Dialog, - Label, - ProgressBar, - RadioList, - TextArea, - ValidationToolbar, -) - -__all__ = [ - "yes_no_dialog", - "button_dialog", - "input_dialog", - "message_dialog", - "radiolist_dialog", - "checkboxlist_dialog", - "progress_dialog", -] - - -def yes_no_dialog( - title: AnyFormattedText = "", - text: AnyFormattedText = "", - yes_text: str = "Yes", - no_text: str = "No", - style: Optional[BaseStyle] = None, -) -> Application[bool]: - """ - Display a Yes/No dialog. - Return a boolean. - """ - - def yes_handler() -> None: - get_app().exit(result=True) - - def no_handler() -> None: - get_app().exit(result=False) - - dialog = Dialog( - title=title, - body=Label(text=text, dont_extend_height=True), - buttons=[ - Button(text=yes_text, handler=yes_handler), - Button(text=no_text, handler=no_handler), - ], - with_background=True, - ) - - return _create_app(dialog, style) - - -_T = TypeVar("_T") - - -def button_dialog( - title: AnyFormattedText = "", - text: AnyFormattedText = "", - buttons: List[Tuple[str, _T]] = [], - style: Optional[BaseStyle] = None, -) -> Application[_T]: - """ - Display a dialog with button choices (given as a list of tuples). - Return the value associated with button. - """ - - def button_handler(v: _T) -> None: - get_app().exit(result=v) - - dialog = Dialog( - title=title, - body=Label(text=text, dont_extend_height=True), - buttons=[ - Button(text=t, handler=functools.partial(button_handler, v)) - for t, v in buttons - ], - with_background=True, - ) - - return _create_app(dialog, style) - - -def input_dialog( - title: AnyFormattedText = "", - text: AnyFormattedText = "", - ok_text: str = "OK", - cancel_text: str = "Cancel", - completer: Optional[Completer] = None, - validator: Optional[Validator] = None, - password: FilterOrBool = False, - style: Optional[BaseStyle] = None, - default: str = "", -) -> Application[str]: - """ - Display a text input box. - Return the given text, or None when cancelled. - """ - - def accept(buf: Buffer) -> bool: - get_app().layout.focus(ok_button) - return True # Keep text. - - def ok_handler() -> None: - get_app().exit(result=textfield.text) - - ok_button = Button(text=ok_text, handler=ok_handler) - cancel_button = Button(text=cancel_text, handler=_return_none) - - textfield = TextArea( - text=default, - multiline=False, - password=password, - completer=completer, - validator=validator, - accept_handler=accept, - ) - - dialog = Dialog( - title=title, - body=HSplit( - [ - Label(text=text, dont_extend_height=True), - textfield, - ValidationToolbar(), - ], - padding=D(preferred=1, max=1), - ), - buttons=[ok_button, cancel_button], - with_background=True, - ) - - return _create_app(dialog, style) - - -def message_dialog( - title: AnyFormattedText = "", - text: AnyFormattedText = "", - ok_text: str = "Ok", - style: Optional[BaseStyle] = None, -) -> Application[None]: - """ - Display a simple message box and wait until the user presses enter. - """ - dialog = Dialog( - title=title, - body=Label(text=text, dont_extend_height=True), - buttons=[Button(text=ok_text, handler=_return_none)], - with_background=True, - ) - - return _create_app(dialog, style) - - -def radiolist_dialog( - title: AnyFormattedText = "", - 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, -) -> Application[_T]: - """ - Display a simple list of element the user can choose amongst. - - Only one element can be selected at a time using Arrow keys and Enter. - The focus can be moved between the list and the Ok/Cancel button with tab. - """ - if values is None: - values = [] - - def ok_handler() -> None: - get_app().exit(result=radio_list.current_value) - - radio_list = RadioList(values=values, default=default) - - dialog = Dialog( - title=title, - body=HSplit( - [Label(text=text, dont_extend_height=True), radio_list], - padding=1, - ), - buttons=[ - Button(text=ok_text, handler=ok_handler), - Button(text=cancel_text, handler=_return_none), - ], - with_background=True, - ) - - return _create_app(dialog, style) - - -def checkboxlist_dialog( - title: AnyFormattedText = "", - 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]]: - """ - Display a simple list of element the user can choose multiple values amongst. - - Several elements can be selected at a time using Arrow keys and Enter. - The focus can be moved between the list and the Ok/Cancel button with tab. - """ - if values is None: - values = [] - - def ok_handler() -> None: - get_app().exit(result=cb_list.current_values) - - cb_list = CheckboxList(values=values, default_values=default_values) - - dialog = Dialog( - title=title, - body=HSplit( - [Label(text=text, dont_extend_height=True), cb_list], - padding=1, - ), - buttons=[ - Button(text=ok_text, handler=ok_handler), - Button(text=cancel_text, handler=_return_none), - ], - with_background=True, - ) - - return _create_app(dialog, style) - - -def progress_dialog( - title: AnyFormattedText = "", - text: AnyFormattedText = "", - run_callback: Callable[[Callable[[int], None], Callable[[str], None]], None] = ( - lambda *a: None - ), - style: Optional[BaseStyle] = 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() - progressbar = ProgressBar() - text_area = TextArea( - focusable=False, - # Prefer this text area as big as possible, to avoid having a window - # that keeps resizing when we add text to it. - height=D(preferred=10**10), - ) - - dialog = Dialog( - body=HSplit( - [ - Box(Label(text=text)), - Box(text_area, padding=D.exact(1)), - progressbar, - ] - ), - title=title, - with_background=True, - ) - app = _create_app(dialog, style) - - def set_percentage(value: int) -> None: - progressbar.percentage = int(value) - app.invalidate() - - def log_text(text: str) -> None: - loop.call_soon_threadsafe(text_area.buffer.insert_text, text) - app.invalidate() - - # Run the callback in the executor. When done, set a return value for the - # UI, so that it quits. - def start() -> None: - try: - run_callback(set_percentage, log_text) - finally: - app.exit() - - def pre_run() -> None: - run_in_executor_with_context(start) - - app.pre_run_callables.append(pre_run) - - return app - - -def _create_app(dialog: AnyContainer, style: Optional[BaseStyle]) -> Application[Any]: - # Key bindings. - bindings = KeyBindings() - bindings.add("tab")(focus_next) - bindings.add("s-tab")(focus_previous) - - return Application( - layout=Layout(dialog), - key_bindings=merge_key_bindings([load_key_bindings(), bindings]), - mouse_support=True, - style=style, - full_screen=True, - ) - - -def _return_none() -> None: - "Button handler that returns None." - get_app().exit() diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/shortcuts/progress_bar/__init__.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/shortcuts/progress_bar/__init__.py deleted file mode 100644 index 7d0fbb50d59..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/shortcuts/progress_bar/__init__.py +++ /dev/null @@ -1,31 +0,0 @@ -from .base import ProgressBar, ProgressBarCounter -from .formatters import ( - Bar, - Formatter, - IterationsPerSecond, - Label, - Percentage, - Progress, - Rainbow, - SpinningWheel, - Text, - TimeElapsed, - TimeLeft, -) - -__all__ = [ - "ProgressBar", - "ProgressBarCounter", - # Formatters. - "Formatter", - "Text", - "Label", - "Percentage", - "Bar", - "Progress", - "TimeElapsed", - "TimeLeft", - "IterationsPerSecond", - "SpinningWheel", - "Rainbow", -] diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/shortcuts/progress_bar/base.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/shortcuts/progress_bar/base.py deleted file mode 100644 index c22507e25ce..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/shortcuts/progress_bar/base.py +++ /dev/null @@ -1,438 +0,0 @@ -""" -Progress bar implementation on top of prompt_toolkit. - -:: - - with ProgressBar(...) as pb: - for item in pb(data): - ... -""" -import datetime -import functools -import os -import signal -import threading -import traceback -from asyncio import new_event_loop, set_event_loop -from typing import ( - Generic, - Iterable, - Iterator, - List, - Optional, - Sequence, - Sized, - TextIO, - TypeVar, - cast, -) - -from prompt_toolkit.application import Application -from prompt_toolkit.application.current import get_app_session -from prompt_toolkit.eventloop import get_event_loop -from prompt_toolkit.filters import Condition, is_done, renderer_height_is_known -from prompt_toolkit.formatted_text import ( - AnyFormattedText, - StyleAndTextTuples, - to_formatted_text, -) -from prompt_toolkit.input import Input -from prompt_toolkit.key_binding import KeyBindings -from prompt_toolkit.key_binding.key_processor import KeyPressEvent -from prompt_toolkit.layout import ( - ConditionalContainer, - FormattedTextControl, - HSplit, - Layout, - VSplit, - Window, -) -from prompt_toolkit.layout.controls import UIContent, UIControl -from prompt_toolkit.layout.dimension import AnyDimension, D -from prompt_toolkit.output import ColorDepth, Output -from prompt_toolkit.styles import BaseStyle - -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 - -_SIGWINCH = getattr(signal, "SIGWINCH", None) - - -def create_key_bindings() -> KeyBindings: - """ - Key bindings handled by the progress bar. - (The main thread is not supposed to handle any key bindings.) - """ - kb = KeyBindings() - - @kb.add("c-l") - def _clear(event: E) -> None: - event.app.renderer.clear() - - @kb.add("c-c") - def _interrupt(event: E) -> None: - # Send KeyboardInterrupt to the main thread. - os.kill(os.getpid(), signal.SIGINT) - - return kb - - -_T = TypeVar("_T") - - -class ProgressBar: - """ - Progress bar context manager. - - Usage :: - - with ProgressBar(...) as pb: - for item in pb(data): - ... - - :param title: Text to be displayed above the progress bars. This can be a - callable or formatted text as well. - :param formatters: List of :class:`.Formatter` instances. - :param bottom_toolbar: Text to be displayed in the bottom toolbar. This - can be a callable or formatted text. - :param style: :class:`prompt_toolkit.styles.BaseStyle` instance. - :param key_bindings: :class:`.KeyBindings` instance. - :param file: The file object used for rendering, by default `sys.stderr` is used. - - :param color_depth: `prompt_toolkit` `ColorDepth` instance. - :param output: :class:`~prompt_toolkit.output.Output` instance. - :param input: :class:`~prompt_toolkit.input.Input` instance. - """ - - def __init__( - self, - title: AnyFormattedText = None, - formatters: Optional[Sequence[Formatter]] = None, - bottom_toolbar: AnyFormattedText = None, - style: Optional[BaseStyle] = None, - key_bindings: Optional[KeyBindings] = None, - file: Optional[TextIO] = None, - color_depth: Optional[ColorDepth] = None, - output: Optional[Output] = None, - input: Optional[Input] = None, - ) -> None: - - self.title = title - self.formatters = formatters or create_default_formatters() - self.bottom_toolbar = bottom_toolbar - self.counters: List[ProgressBarCounter[object]] = [] - self.style = style - self.key_bindings = key_bindings - - # Note that we use __stderr__ as default error output, because that - # works best with `patch_stdout`. - self.color_depth = color_depth - self.output = output or get_app_session().output - self.input = input or get_app_session().input - - self._thread: Optional[threading.Thread] = None - - self._loop = get_event_loop() - self._app_loop = new_event_loop() - self._has_sigwinch = False - self._app_started = threading.Event() - - def __enter__(self) -> "ProgressBar": - # Create UI Application. - title_toolbar = ConditionalContainer( - Window( - FormattedTextControl(lambda: self.title), - height=1, - style="class:progressbar,title", - ), - filter=Condition(lambda: self.title is not None), - ) - - bottom_toolbar = ConditionalContainer( - Window( - FormattedTextControl( - lambda: self.bottom_toolbar, style="class:bottom-toolbar.text" - ), - style="class:bottom-toolbar", - height=1, - ), - filter=~is_done - & renderer_height_is_known - & Condition(lambda: self.bottom_toolbar is not None), - ) - - def width_for_formatter(formatter: Formatter) -> AnyDimension: - # Needs to be passed as callable (partial) to the 'width' - # parameter, because we want to call it on every resize. - return formatter.get_width(progress_bar=self) - - progress_controls = [ - Window( - content=_ProgressControl(self, f), - width=functools.partial(width_for_formatter, f), - ) - for f in self.formatters - ] - - self.app: Application[None] = Application( - min_redraw_interval=0.05, - layout=Layout( - HSplit( - [ - title_toolbar, - VSplit( - progress_controls, - height=lambda: D( - preferred=len(self.counters), max=len(self.counters) - ), - ), - Window(), - bottom_toolbar, - ] - ) - ), - style=self.style, - key_bindings=self.key_bindings, - refresh_interval=0.3, - color_depth=self.color_depth, - output=self.output, - input=self.input, - ) - - # 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: - traceback.print_exc() - print(e) - - ctx: contextvars.Context = contextvars.copy_context() - - self._thread = threading.Thread(target=ctx.run, args=(run,)) - self._thread.start() - - return self - - def __exit__(self, *a: object) -> None: - # Wait for the app to be started. Make sure we don't quit earlier, - # otherwise `self.app.exit` won't terminate the app because - # `self.app.future` has not yet been set. - self._app_started.wait() - - # Quit UI application. - if self.app.is_running: - 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, - label: AnyFormattedText = "", - remove_when_done: bool = False, - total: Optional[int] = None, - ) -> "ProgressBarCounter[_T]": - """ - Start a new counter. - - :param label: Title text or description for this progress. (This can be - formatted text as well). - :param remove_when_done: When `True`, hide this progress bar. - :param total: Specify the maximum value if it can't be calculated by - calling ``len``. - """ - counter = ProgressBarCounter( - self, data, label=label, remove_when_done=remove_when_done, total=total - ) - self.counters.append(counter) - return counter - - def invalidate(self) -> None: - self.app.invalidate() - - -class _ProgressControl(UIControl): - """ - User control for the progress bar. - """ - - def __init__(self, progress_bar: ProgressBar, formatter: Formatter) -> None: - self.progress_bar = progress_bar - self.formatter = formatter - self._key_bindings = create_key_bindings() - - def create_content(self, width: int, height: int) -> UIContent: - items: List[StyleAndTextTuples] = [] - - for pr in self.progress_bar.counters: - try: - text = self.formatter.format(self.progress_bar, pr, width) - except BaseException: - traceback.print_exc() - text = "ERROR" - - items.append(to_formatted_text(text)) - - def get_line(i: int) -> StyleAndTextTuples: - return items[i] - - return UIContent(get_line=get_line, line_count=len(items), show_cursor=False) - - def is_focusable(self) -> bool: - return True # Make sure that the key bindings work. - - def get_key_bindings(self) -> KeyBindings: - return self._key_bindings - - -_CounterItem = TypeVar("_CounterItem", covariant=True) - - -class ProgressBarCounter(Generic[_CounterItem]): - """ - An individual counter (A progress bar can have multiple counters). - """ - - def __init__( - self, - progress_bar: ProgressBar, - data: Optional[Iterable[_CounterItem]] = None, - label: AnyFormattedText = "", - remove_when_done: bool = False, - total: Optional[int] = None, - ) -> None: - - self.start_time = datetime.datetime.now() - self.stop_time: Optional[datetime.datetime] = 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] - - if total is None: - try: - self.total = len(cast(Sized, data)) - except TypeError: - self.total = None # We don't know the total length. - else: - self.total = total - - def __iter__(self) -> Iterator[_CounterItem]: - if self.data is not None: - try: - for item in self.data: - yield item - self.item_completed() - - # Only done if we iterate to the very end. - self.done = True - finally: - # Ensure counter has stopped even if we did not iterate to the - # end (e.g. break or exceptions). - self.stopped = True - else: - raise NotImplementedError("No data defined to iterate over.") - - def item_completed(self) -> None: - """ - Start handling the next item. - - (Can be called manually in case we don't have a collection to loop through.) - """ - self.items_completed += 1 - self.progress_bar.invalidate() - - @property - def done(self) -> bool: - """Whether a counter has been completed. - - Done counter have been stopped (see stopped) and removed depending on - remove_when_done value. - - Contrast this with stopped. A stopped counter may be terminated before - 100% completion. A done counter has reached its 100% completion. - """ - return self._done - - @done.setter - def done(self, value: bool) -> None: - self._done = value - self.stopped = value - - if value and self.remove_when_done: - self.progress_bar.counters.remove(self) - - @property - def stopped(self) -> bool: - """Whether a counter has been stopped. - - Stopped counters no longer have increasing time_elapsed. This distinction is - also used to prevent the Bar formatter with unknown totals from continuing to run. - - A stopped counter (but not done) can be used to signal that a given counter has - encountered an error but allows other counters to continue - (e.g. download X of Y failed). Given how only done counters are removed - (see remove_when_done) this can help aggregate failures from a large number of - successes. - - Contrast this with done. A done counter has reached its 100% completion. - A stopped counter may be terminated before 100% completion. - """ - return self.stop_time is not None - - @stopped.setter - def stopped(self, value: bool) -> None: - if value: - # This counter has not already been stopped. - if not self.stop_time: - self.stop_time = datetime.datetime.now() - else: - # Clearing any previously set stop_time. - self.stop_time = None - - @property - def percentage(self) -> float: - if self.total is None: - return 0 - else: - return self.items_completed * 100 / max(self.total, 1) - - @property - def time_elapsed(self) -> datetime.timedelta: - """ - Return how much time has been elapsed since the start. - """ - if self.stop_time is None: - return datetime.datetime.now() - self.start_time - else: - return self.stop_time - self.start_time - - @property - def time_left(self) -> Optional[datetime.timedelta]: - """ - Timedelta representing the time left. - """ - if self.total is None or not self.percentage: - return None - elif self.done or self.stopped: - return datetime.timedelta(0) - else: - return self.time_elapsed * (100 - self.percentage) / self.percentage diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/shortcuts/progress_bar/formatters.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/shortcuts/progress_bar/formatters.py deleted file mode 100644 index 1383d7a6b5b..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/shortcuts/progress_bar/formatters.py +++ /dev/null @@ -1,436 +0,0 @@ -""" -Formatter classes for the progress bar. -Each progress bar consists of a list of these formatters. -""" -import datetime -import time -from abc import ABCMeta, abstractmethod -from typing import TYPE_CHECKING, List, Tuple - -from prompt_toolkit.formatted_text import ( - HTML, - AnyFormattedText, - StyleAndTextTuples, - to_formatted_text, -) -from prompt_toolkit.formatted_text.utils import fragment_list_width -from prompt_toolkit.layout.dimension import AnyDimension, D -from prompt_toolkit.layout.utils import explode_text_fragments -from prompt_toolkit.utils import get_cwidth - -if TYPE_CHECKING: - from .base import ProgressBar, ProgressBarCounter - -__all__ = [ - "Formatter", - "Text", - "Label", - "Percentage", - "Bar", - "Progress", - "TimeElapsed", - "TimeLeft", - "IterationsPerSecond", - "SpinningWheel", - "Rainbow", - "create_default_formatters", -] - - -class Formatter(metaclass=ABCMeta): - """ - Base class for any formatter. - """ - - @abstractmethod - def format( - self, - progress_bar: "ProgressBar", - progress: "ProgressBarCounter[object]", - width: int, - ) -> AnyFormattedText: - pass - - def get_width(self, progress_bar: "ProgressBar") -> AnyDimension: - return D() - - -class Text(Formatter): - """ - Display plain text. - """ - - def __init__(self, text: AnyFormattedText, style: str = "") -> None: - self.text = to_formatted_text(text, style=style) - - def format( - self, - progress_bar: "ProgressBar", - progress: "ProgressBarCounter[object]", - width: int, - ) -> AnyFormattedText: - return self.text - - def get_width(self, progress_bar: "ProgressBar") -> AnyDimension: - return fragment_list_width(self.text) - - -class Label(Formatter): - """ - Display the name of the current task. - - :param width: If a `width` is given, use this width. Scroll the text if it - doesn't fit in this width. - :param suffix: String suffix to be added after the task name, e.g. ': '. - If no task name was given, no suffix will be added. - """ - - def __init__(self, width: AnyDimension = None, suffix: str = "") -> None: - self.width = width - self.suffix = suffix - - def _add_suffix(self, label: AnyFormattedText) -> StyleAndTextTuples: - label = to_formatted_text(label, style="class:label") - return label + [("", self.suffix)] - - def format( - self, - progress_bar: "ProgressBar", - progress: "ProgressBarCounter[object]", - width: int, - ) -> AnyFormattedText: - - label = self._add_suffix(progress.label) - cwidth = fragment_list_width(label) - - if cwidth > width: - # It doesn't fit -> scroll task name. - label = explode_text_fragments(label) - max_scroll = cwidth - width - current_scroll = int(time.time() * 3 % max_scroll) - label = label[current_scroll:] - - return label - - def get_width(self, progress_bar: "ProgressBar") -> AnyDimension: - if self.width: - return self.width - - all_labels = [self._add_suffix(c.label) for c in progress_bar.counters] - if all_labels: - max_widths = max(fragment_list_width(l) for l in all_labels) - return D(preferred=max_widths, max=max_widths) - else: - return D() - - -class Percentage(Formatter): - """ - Display the progress as a percentage. - """ - - template = "<percentage>{percentage:>5}%</percentage>" - - def format( - self, - 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: - return D.exact(6) - - -class Bar(Formatter): - """ - Display the progress bar itself. - """ - - template = "<bar>{start}<bar-a>{bar_a}</bar-a><bar-b>{bar_b}</bar-b><bar-c>{bar_c}</bar-c>{end}</bar>" - - def __init__( - self, - start: str = "[", - end: str = "]", - sym_a: str = "=", - sym_b: str = ">", - sym_c: str = " ", - unknown: str = "#", - ) -> None: - - assert len(sym_a) == 1 and get_cwidth(sym_a) == 1 - assert len(sym_c) == 1 and get_cwidth(sym_c) == 1 - - self.start = start - self.end = end - self.sym_a = sym_a - self.sym_b = sym_b - self.sym_c = sym_c - self.unknown = unknown - - def format( - self, - progress_bar: "ProgressBar", - progress: "ProgressBarCounter[object]", - width: int, - ) -> AnyFormattedText: - if progress.done or progress.total or progress.stopped: - sym_a, sym_b, sym_c = self.sym_a, self.sym_b, self.sym_c - - # Compute pb_a based on done, total, or stopped states. - if progress.done: - # 100% completed irrelevant of how much was actually marked as completed. - percent = 1.0 - else: - # Show percentage completed. - percent = progress.percentage / 100 - else: - # Total is unknown and bar is still running. - sym_a, sym_b, sym_c = self.sym_c, self.unknown, self.sym_c - - # Compute percent based on the time. - percent = time.time() * 20 % 100 / 100 - - # Subtract left, sym_b, and right. - width -= get_cwidth(self.start + sym_b + self.end) - - # Scale percent by width - pb_a = int(percent * width) - bar_a = sym_a * pb_a - bar_b = sym_b - bar_c = sym_c * (width - pb_a) - - return HTML(self.template).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: - return D(min=9) - - -class Progress(Formatter): - """ - Display the progress as text. E.g. "8/20" - """ - - template = "<current>{current:>3}</current>/<total>{total:>3}</total>" - - def format( - self, - 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: - all_lengths = [ - len("{:>3}".format(c.total or "?")) for c in progress_bar.counters - ] - all_lengths.append(1) - return D.exact(max(all_lengths) * 2 + 1) - - -def _format_timedelta(timedelta: datetime.timedelta) -> str: - """ - Return hh:mm:ss, or mm:ss if the amount of hours is zero. - """ - result = f"{timedelta}".split(".")[0] - if result.startswith("0:"): - result = result[2:] - return result - - -class TimeElapsed(Formatter): - """ - Display the elapsed time. - """ - - def format( - self, - progress_bar: "ProgressBar", - progress: "ProgressBarCounter[object]", - width: int, - ) -> AnyFormattedText: - - text = _format_timedelta(progress.time_elapsed).rjust(width) - return HTML("<time-elapsed>{time_elapsed}</time-elapsed>").format( - time_elapsed=text - ) - - def get_width(self, progress_bar: "ProgressBar") -> AnyDimension: - all_values = [ - len(_format_timedelta(c.time_elapsed)) for c in progress_bar.counters - ] - if all_values: - return max(all_values) - return 0 - - -class TimeLeft(Formatter): - """ - Display the time left. - """ - - template = "<time-left>{time_left}</time-left>" - unknown = "?:??:??" - - def format( - self, - progress_bar: "ProgressBar", - progress: "ProgressBarCounter[object]", - width: int, - ) -> AnyFormattedText: - - time_left = progress.time_left - if time_left is not None: - formatted_time_left = _format_timedelta(time_left) - else: - formatted_time_left = self.unknown - - return HTML(self.template).format(time_left=formatted_time_left.rjust(width)) - - 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 - ] - if all_values: - return max(all_values) - return 0 - - -class IterationsPerSecond(Formatter): - """ - Display the iterations per second. - """ - - template = ( - "<iterations-per-second>{iterations_per_second:.2f}</iterations-per-second>" - ) - - def format( - self, - 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: - all_values = [ - len(f"{c.items_completed / c.time_elapsed.total_seconds():.2f}") - for c in progress_bar.counters - ] - if all_values: - return max(all_values) - return 0 - - -class SpinningWheel(Formatter): - """ - Display a spinning wheel. - """ - - characters = r"/-\|" - - def format( - self, - progress_bar: "ProgressBar", - progress: "ProgressBarCounter[object]", - width: int, - ) -> AnyFormattedText: - - index = int(time.time() * 3) % len(self.characters) - return HTML("<spinning-wheel>{0}</spinning-wheel>").format( - self.characters[index] - ) - - def get_width(self, progress_bar: "ProgressBar") -> AnyDimension: - return D.exact(1) - - -def _hue_to_rgb(hue: float) -> Tuple[int, int, int]: - """ - Take hue between 0 and 1, return (r, g, b). - """ - i = int(hue * 6.0) - f = (hue * 6.0) - i - - q = int(255 * (1.0 - f)) - t = int(255 * (1.0 - (1.0 - f))) - - i %= 6 - - return [ - (255, t, 0), - (q, 255, 0), - (0, 255, t), - (0, q, 255), - (t, 0, 255), - (255, 0, q), - ][i] - - -class Rainbow(Formatter): - """ - For the fun. Add rainbow colors to any of the other formatters. - """ - - colors = ["#%.2x%.2x%.2x" % _hue_to_rgb(h / 100.0) for h in range(0, 100)] - - def __init__(self, formatter: Formatter) -> None: - self.formatter = formatter - - def format( - self, - progress_bar: "ProgressBar", - progress: "ProgressBarCounter[object]", - width: int, - ) -> AnyFormattedText: - - # Get formatted text from nested formatter, and explode it in - # text/style tuples. - result = self.formatter.format(progress_bar, progress, width) - result = explode_text_fragments(to_formatted_text(result)) - - # Insert colors. - result2: StyleAndTextTuples = [] - shift = int(time.time() * 3) % len(self.colors) - - for i, (style, text, *_) in enumerate(result): - result2.append( - (style + " " + self.colors[(i + shift) % len(self.colors)], text) - ) - return result2 - - def get_width(self, progress_bar: "ProgressBar") -> AnyDimension: - return self.formatter.get_width(progress_bar) - - -def create_default_formatters() -> List[Formatter]: - """ - Return the list of default formatters. - """ - return [ - Label(), - Text(" "), - Percentage(), - Text(" "), - Bar(), - Text(" "), - Progress(), - Text(" "), - Text("eta [", style="class:time-left"), - TimeLeft(), - Text("]", style="class:time-left"), - Text(" "), - ] diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/shortcuts/prompt.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/shortcuts/prompt.py deleted file mode 100644 index 4dc1b18d1ca..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/shortcuts/prompt.py +++ /dev/null @@ -1,1504 +0,0 @@ -""" -Line editing functionality. ---------------------------- - -This provides a UI for a line input, similar to GNU Readline, libedit and -linenoise. - -Either call the `prompt` function for every line input. Or create an instance -of the :class:`.PromptSession` class and call the `prompt` method from that -class. In the second case, we'll have a 'session' that keeps all the state like -the history in between several calls. - -There is a lot of overlap between the arguments taken by the `prompt` function -and the `PromptSession` (like `completer`, `style`, etcetera). There we have -the freedom to decide which settings we want for the whole 'session', and which -we want for an individual `prompt`. - -Example:: - - # Simple `prompt` call. - result = prompt('Say something: ') - - # Using a 'session'. - s = PromptSession() - result = s.prompt('Say something: ') -""" -from contextlib import contextmanager -from enum import Enum -from functools import partial -from typing import ( - TYPE_CHECKING, - Callable, - Generic, - Iterator, - List, - Optional, - Tuple, - TypeVar, - Union, - cast, -) - -from prompt_toolkit.application import Application -from prompt_toolkit.application.current import get_app -from prompt_toolkit.auto_suggest import AutoSuggest, DynamicAutoSuggest -from prompt_toolkit.buffer import Buffer -from prompt_toolkit.clipboard import Clipboard, DynamicClipboard, InMemoryClipboard -from prompt_toolkit.completion import Completer, DynamicCompleter, ThreadedCompleter -from prompt_toolkit.cursor_shapes import ( - AnyCursorShapeConfig, - CursorShapeConfig, - DynamicCursorShapeConfig, -) -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, - has_arg, - has_focus, - is_done, - is_true, - renderer_height_is_known, - to_filter, -) -from prompt_toolkit.formatted_text import ( - AnyFormattedText, - StyleAndTextTuples, - fragment_list_to_text, - merge_formatted_text, - to_formatted_text, -) -from prompt_toolkit.history import History, InMemoryHistory -from prompt_toolkit.input.base import Input -from prompt_toolkit.key_binding.bindings.auto_suggest import load_auto_suggest_bindings -from prompt_toolkit.key_binding.bindings.completion import ( - display_completions_like_readline, -) -from prompt_toolkit.key_binding.bindings.open_in_editor import ( - load_open_in_editor_bindings, -) -from prompt_toolkit.key_binding.key_bindings import ( - ConditionalKeyBindings, - DynamicKeyBindings, - KeyBindings, - KeyBindingsBase, - merge_key_bindings, -) -from prompt_toolkit.key_binding.key_processor import KeyPressEvent -from prompt_toolkit.keys import Keys -from prompt_toolkit.layout import Float, FloatContainer, HSplit, Window -from prompt_toolkit.layout.containers import ConditionalContainer, WindowAlign -from prompt_toolkit.layout.controls import ( - BufferControl, - FormattedTextControl, - SearchBufferControl, -) -from prompt_toolkit.layout.dimension import Dimension -from prompt_toolkit.layout.layout import Layout -from prompt_toolkit.layout.menus import CompletionsMenu, MultiColumnCompletionsMenu -from prompt_toolkit.layout.processors import ( - AfterInput, - AppendAutoSuggestion, - ConditionalProcessor, - DisplayMultipleCursors, - DynamicProcessor, - HighlightIncrementalSearchProcessor, - HighlightSelectionProcessor, - PasswordProcessor, - Processor, - ReverseSearchProcessor, - merge_processors, -) -from prompt_toolkit.layout.utils import explode_text_fragments -from prompt_toolkit.lexers import DynamicLexer, Lexer -from prompt_toolkit.output import ColorDepth, DummyOutput, Output -from prompt_toolkit.styles import ( - BaseStyle, - ConditionalStyleTransformation, - DynamicStyle, - DynamicStyleTransformation, - StyleTransformation, - SwapLightAndDarkStyleTransformation, - merge_style_transformations, -) -from prompt_toolkit.utils import ( - get_cwidth, - is_dumb_terminal, - suspend_to_background_supported, - to_str, -) -from prompt_toolkit.validation import DynamicValidator, Validator -from prompt_toolkit.widgets.toolbars import ( - SearchToolbar, - SystemToolbar, - ValidationToolbar, -) - -if TYPE_CHECKING: - from prompt_toolkit.formatted_text.base import MagicFormattedText - -__all__ = [ - "PromptSession", - "prompt", - "confirm", - "create_confirm_session", # Used by '_display_completions_like_readline'. - "CompleteStyle", -] - -_StyleAndTextTuplesCallable = Callable[[], StyleAndTextTuples] -E = KeyPressEvent - - -def _split_multiline_prompt( - get_prompt_text: _StyleAndTextTuplesCallable, -) -> Tuple[ - Callable[[], bool], _StyleAndTextTuplesCallable, _StyleAndTextTuplesCallable -]: - """ - Take a `get_prompt_text` function and return three new functions instead. - One that tells whether this prompt consists of multiple lines; one that - returns the fragments to be shown on the lines above the input; and another - one with the fragments to be shown at the first line of the input. - """ - - def has_before_fragments() -> bool: - for fragment, char, *_ in get_prompt_text(): - if "\n" in char: - return True - return False - - def before() -> StyleAndTextTuples: - result: StyleAndTextTuples = [] - found_nl = False - for fragment, char, *_ in reversed(explode_text_fragments(get_prompt_text())): - if found_nl: - result.insert(0, (fragment, char)) - elif char == "\n": - found_nl = True - return result - - def first_input_line() -> StyleAndTextTuples: - result: StyleAndTextTuples = [] - for fragment, char, *_ in reversed(explode_text_fragments(get_prompt_text())): - if char == "\n": - break - else: - result.insert(0, (fragment, char)) - return result - - return has_before_fragments, before, first_input_line - - -class _RPrompt(Window): - """ - The prompt that is displayed on the right side of the Window. - """ - - def __init__(self, text: AnyFormattedText) -> None: - super().__init__( - FormattedTextControl(text=text), - align=WindowAlign.RIGHT, - style="class:rprompt", - ) - - -class CompleteStyle(str, Enum): - """ - How to display autocompletions for the prompt. - """ - - value: str - - COLUMN = "COLUMN" - MULTI_COLUMN = "MULTI_COLUMN" - READLINE_LIKE = "READLINE_LIKE" - - -# Formatted text for the continuation prompt. It's the same like other -# formatted text, except that if it's a callable, it takes three arguments. -PromptContinuationText = Union[ - str, - "MagicFormattedText", - StyleAndTextTuples, - # (prompt_width, line_number, wrap_count) -> AnyFormattedText. - Callable[[int, int, int], AnyFormattedText], -] - -_T = TypeVar("_T") - - -class PromptSession(Generic[_T]): - """ - PromptSession for a prompt application, which can be used as a GNU Readline - replacement. - - This is a wrapper around a lot of ``prompt_toolkit`` functionality and can - be a replacement for `raw_input`. - - All parameters that expect "formatted text" can take either just plain text - (a unicode object), a list of ``(style_str, text)`` tuples or an HTML object. - - Example usage:: - - s = PromptSession(message='>') - text = s.prompt() - - :param message: Plain text or formatted text to be shown before the prompt. - This can also be a callable that returns formatted text. - :param multiline: `bool` or :class:`~prompt_toolkit.filters.Filter`. - When True, prefer a layout that is more adapted for multiline input. - Text after newlines is automatically indented, and search/arg input is - shown below the input, instead of replacing the prompt. - :param wrap_lines: `bool` or :class:`~prompt_toolkit.filters.Filter`. - When True (the default), automatically wrap long lines instead of - scrolling horizontally. - :param is_password: Show asterisks instead of the actual typed characters. - :param editing_mode: ``EditingMode.VI`` or ``EditingMode.EMACS``. - :param vi_mode: `bool`, if True, Identical to ``editing_mode=EditingMode.VI``. - :param complete_while_typing: `bool` or - :class:`~prompt_toolkit.filters.Filter`. Enable autocompletion while - typing. - :param validate_while_typing: `bool` or - :class:`~prompt_toolkit.filters.Filter`. Enable input validation while - typing. - :param enable_history_search: `bool` or - :class:`~prompt_toolkit.filters.Filter`. Enable up-arrow parting - string matching. - :param search_ignore_case: - :class:`~prompt_toolkit.filters.Filter`. Search case insensitive. - :param lexer: :class:`~prompt_toolkit.lexers.Lexer` to be used for the - syntax highlighting. - :param validator: :class:`~prompt_toolkit.validation.Validator` instance - for input validation. - :param completer: :class:`~prompt_toolkit.completion.Completer` instance - for input completion. - :param complete_in_thread: `bool` or - :class:`~prompt_toolkit.filters.Filter`. Run the completer code in a - background thread in order to avoid blocking the user interface. - For ``CompleteStyle.READLINE_LIKE``, this setting has no effect. There - we always run the completions in the main thread. - :param reserve_space_for_menu: Space to be reserved for displaying the menu. - (0 means that no space needs to be reserved.) - :param auto_suggest: :class:`~prompt_toolkit.auto_suggest.AutoSuggest` - instance for input suggestions. - :param style: :class:`.Style` instance for the color scheme. - :param include_default_pygments_style: `bool` or - :class:`~prompt_toolkit.filters.Filter`. Tell whether the default - styling for Pygments lexers has to be included. By default, this is - true, but it is recommended to be disabled if another Pygments style is - passed as the `style` argument, otherwise, two Pygments styles will be - merged. - :param style_transformation: - :class:`~prompt_toolkit.style.StyleTransformation` instance. - :param swap_light_and_dark_colors: `bool` or - :class:`~prompt_toolkit.filters.Filter`. When enabled, apply - :class:`~prompt_toolkit.style.SwapLightAndDarkStyleTransformation`. - This is useful for switching between dark and light terminal - backgrounds. - :param enable_system_prompt: `bool` or - :class:`~prompt_toolkit.filters.Filter`. Pressing Meta+'!' will show - a system prompt. - :param enable_suspend: `bool` or :class:`~prompt_toolkit.filters.Filter`. - Enable Control-Z style suspension. - :param enable_open_in_editor: `bool` or - :class:`~prompt_toolkit.filters.Filter`. Pressing 'v' in Vi mode or - C-X C-E in emacs mode will open an external editor. - :param history: :class:`~prompt_toolkit.history.History` instance. - :param clipboard: :class:`~prompt_toolkit.clipboard.Clipboard` instance. - (e.g. :class:`~prompt_toolkit.clipboard.InMemoryClipboard`) - :param rprompt: Text or formatted text to be displayed on the right side. - This can also be a callable that returns (formatted) text. - :param bottom_toolbar: Formatted text or callable which is supposed to - return formatted text. - :param prompt_continuation: Text that needs to be displayed for a multiline - prompt continuation. This can either be formatted text or a callable - that takes a `prompt_width`, `line_number` and `wrap_count` as input - and returns formatted text. When this is `None` (the default), then - `prompt_width` spaces will be used. - :param complete_style: ``CompleteStyle.COLUMN``, - ``CompleteStyle.MULTI_COLUMN`` or ``CompleteStyle.READLINE_LIKE``. - :param mouse_support: `bool` or :class:`~prompt_toolkit.filters.Filter` - to enable mouse support. - :param placeholder: Text to be displayed when no input has been given - yet. Unlike the `default` parameter, this won't be returned as part of - the output ever. This can be formatted text or a callable that returns - formatted text. - :param refresh_interval: (number; in seconds) When given, refresh the UI - every so many seconds. - :param input: `Input` object. (Note that the preferred way to change the - input/output is by creating an `AppSession`.) - :param output: `Output` object. - """ - - _fields = ( - "message", - "lexer", - "completer", - "complete_in_thread", - "is_password", - "editing_mode", - "key_bindings", - "is_password", - "bottom_toolbar", - "style", - "style_transformation", - "swap_light_and_dark_colors", - "color_depth", - "cursor", - "include_default_pygments_style", - "rprompt", - "multiline", - "prompt_continuation", - "wrap_lines", - "enable_history_search", - "search_ignore_case", - "complete_while_typing", - "validate_while_typing", - "complete_style", - "mouse_support", - "auto_suggest", - "clipboard", - "validator", - "refresh_interval", - "input_processors", - "placeholder", - "enable_system_prompt", - "enable_suspend", - "enable_open_in_editor", - "reserve_space_for_menu", - "tempfile_suffix", - "tempfile", - ) - - def __init__( - self, - message: AnyFormattedText = "", - *, - multiline: FilterOrBool = False, - wrap_lines: FilterOrBool = True, - is_password: FilterOrBool = False, - vi_mode: bool = False, - editing_mode: EditingMode = EditingMode.EMACS, - complete_while_typing: FilterOrBool = True, - validate_while_typing: FilterOrBool = True, - enable_history_search: FilterOrBool = False, - search_ignore_case: FilterOrBool = False, - lexer: Optional[Lexer] = None, - enable_system_prompt: FilterOrBool = False, - enable_suspend: FilterOrBool = False, - enable_open_in_editor: FilterOrBool = False, - validator: Optional[Validator] = None, - completer: Optional[Completer] = 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, - swap_light_and_dark_colors: FilterOrBool = False, - color_depth: Optional[ColorDepth] = None, - cursor: AnyCursorShapeConfig = None, - include_default_pygments_style: FilterOrBool = True, - history: Optional[History] = None, - clipboard: Optional[Clipboard] = None, - prompt_continuation: Optional[PromptContinuationText] = 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, - erase_when_done: bool = False, - tempfile_suffix: Optional[Union[str, Callable[[], str]]] = ".txt", - tempfile: Optional[Union[str, Callable[[], str]]] = None, - refresh_interval: float = 0, - input: Optional[Input] = None, - output: Optional[Output] = None, - ) -> None: - - history = history or InMemoryHistory() - clipboard = clipboard or InMemoryClipboard() - - # Ensure backwards-compatibility, when `vi_mode` is passed. - if vi_mode: - editing_mode = EditingMode.VI - - # Store all settings in this class. - self._input = input - self._output = output - - # Store attributes. - # (All except 'editing_mode'.) - self.message = message - self.lexer = lexer - self.completer = completer - self.complete_in_thread = complete_in_thread - self.is_password = is_password - self.key_bindings = key_bindings - self.bottom_toolbar = bottom_toolbar - self.style = style - self.style_transformation = style_transformation - self.swap_light_and_dark_colors = swap_light_and_dark_colors - self.color_depth = color_depth - self.cursor = cursor - self.include_default_pygments_style = include_default_pygments_style - self.rprompt = rprompt - self.multiline = multiline - self.prompt_continuation = prompt_continuation - self.wrap_lines = wrap_lines - self.enable_history_search = enable_history_search - self.search_ignore_case = search_ignore_case - self.complete_while_typing = complete_while_typing - self.validate_while_typing = validate_while_typing - self.complete_style = complete_style - self.mouse_support = mouse_support - self.auto_suggest = auto_suggest - self.clipboard = clipboard - self.validator = validator - self.refresh_interval = refresh_interval - self.input_processors = input_processors - self.placeholder = placeholder - self.enable_system_prompt = enable_system_prompt - self.enable_suspend = enable_suspend - self.enable_open_in_editor = enable_open_in_editor - self.reserve_space_for_menu = reserve_space_for_menu - self.tempfile_suffix = tempfile_suffix - self.tempfile = tempfile - - # Create buffers, layout and Application. - self.history = history - self.default_buffer = self._create_default_buffer() - self.search_buffer = self._create_search_buffer() - self.layout = self._create_layout() - self.app = self._create_application(editing_mode, erase_when_done) - - def _dyncond(self, attr_name: str) -> Condition: - """ - Dynamically take this setting from this 'PromptSession' class. - `attr_name` represents an attribute name of this class. Its value - can either be a boolean or a `Filter`. - - This returns something that can be used as either a `Filter` - or `Filter`. - """ - - @Condition - def dynamic() -> bool: - value = cast(FilterOrBool, getattr(self, attr_name)) - return to_filter(value)() - - return dynamic - - def _create_default_buffer(self) -> Buffer: - """ - Create and return the default input buffer. - """ - dyncond = self._dyncond - - # Create buffers list. - def accept(buff: Buffer) -> bool: - """Accept the content of the default buffer. This is called when - the validation succeeds.""" - cast(Application[str], get_app()).exit(result=buff.document.text) - return True # Keep text, we call 'reset' later on. - - return Buffer( - name=DEFAULT_BUFFER, - # Make sure that complete_while_typing is disabled when - # enable_history_search is enabled. (First convert to Filter, - # to avoid doing bitwise operations on bool objects.) - complete_while_typing=Condition( - lambda: is_true(self.complete_while_typing) - and not is_true(self.enable_history_search) - and not self.complete_style == CompleteStyle.READLINE_LIKE - ), - validate_while_typing=dyncond("validate_while_typing"), - enable_history_search=dyncond("enable_history_search"), - validator=DynamicValidator(lambda: self.validator), - completer=DynamicCompleter( - lambda: ThreadedCompleter(self.completer) - if self.complete_in_thread and self.completer - else self.completer - ), - history=self.history, - auto_suggest=DynamicAutoSuggest(lambda: self.auto_suggest), - accept_handler=accept, - tempfile_suffix=lambda: to_str(self.tempfile_suffix or ""), - tempfile=lambda: to_str(self.tempfile or ""), - ) - - def _create_search_buffer(self) -> Buffer: - return Buffer(name=SEARCH_BUFFER) - - def _create_layout(self) -> Layout: - """ - Create `Layout` for this prompt. - """ - dyncond = self._dyncond - - # Create functions that will dynamically split the prompt. (If we have - # a multiline prompt.) - ( - has_before_fragments, - get_prompt_text_1, - get_prompt_text_2, - ) = _split_multiline_prompt(self._get_prompt) - - default_buffer = self.default_buffer - search_buffer = self.search_buffer - - # Create processors list. - @Condition - def display_placeholder() -> bool: - return self.placeholder is not None and self.default_buffer.text == "" - - all_input_processors = [ - HighlightIncrementalSearchProcessor(), - HighlightSelectionProcessor(), - ConditionalProcessor( - AppendAutoSuggestion(), has_focus(default_buffer) & ~is_done - ), - ConditionalProcessor(PasswordProcessor(), dyncond("is_password")), - DisplayMultipleCursors(), - # Users can insert processors here. - DynamicProcessor(lambda: merge_processors(self.input_processors or [])), - ConditionalProcessor( - AfterInput(lambda: self.placeholder), - filter=display_placeholder, - ), - ] - - # Create bottom toolbars. - bottom_toolbar = ConditionalContainer( - Window( - FormattedTextControl( - lambda: self.bottom_toolbar, style="class:bottom-toolbar.text" - ), - style="class:bottom-toolbar", - dont_extend_height=True, - height=Dimension(min=1), - ), - filter=~is_done - & renderer_height_is_known - & Condition(lambda: self.bottom_toolbar is not None), - ) - - search_toolbar = SearchToolbar( - search_buffer, ignore_case=dyncond("search_ignore_case") - ) - - search_buffer_control = SearchBufferControl( - buffer=search_buffer, - input_processors=[ReverseSearchProcessor()], - ignore_case=dyncond("search_ignore_case"), - ) - - system_toolbar = SystemToolbar( - enable_global_bindings=dyncond("enable_system_prompt") - ) - - def get_search_buffer_control() -> SearchBufferControl: - "Return the UIControl to be focused when searching start." - if is_true(self.multiline): - return search_toolbar.control - else: - return search_buffer_control - - default_buffer_control = BufferControl( - buffer=default_buffer, - search_buffer_control=get_search_buffer_control, - input_processors=all_input_processors, - include_default_input_processors=False, - lexer=DynamicLexer(lambda: self.lexer), - preview_search=True, - ) - - default_buffer_window = Window( - default_buffer_control, - height=self._get_default_buffer_control_height, - get_line_prefix=partial( - self._get_line_prefix, get_prompt_text_2=get_prompt_text_2 - ), - wrap_lines=dyncond("wrap_lines"), - ) - - @Condition - def multi_column_complete_style() -> bool: - return self.complete_style == CompleteStyle.MULTI_COLUMN - - # Build the layout. - layout = HSplit( - [ - # The main input, with completion menus floating on top of it. - FloatContainer( - HSplit( - [ - ConditionalContainer( - Window( - FormattedTextControl(get_prompt_text_1), - dont_extend_height=True, - ), - Condition(has_before_fragments), - ), - ConditionalContainer( - default_buffer_window, - Condition( - lambda: get_app().layout.current_control - != search_buffer_control - ), - ), - ConditionalContainer( - Window(search_buffer_control), - Condition( - lambda: get_app().layout.current_control - == search_buffer_control - ), - ), - ] - ), - [ - # Completion menus. - # NOTE: Especially the multi-column menu needs to be - # transparent, because the shape is not always - # rectangular due to the meta-text below the menu. - Float( - xcursor=True, - ycursor=True, - transparent=True, - content=CompletionsMenu( - max_height=16, - scroll_offset=1, - extra_filter=has_focus(default_buffer) - & ~multi_column_complete_style, - ), - ), - Float( - xcursor=True, - ycursor=True, - transparent=True, - content=MultiColumnCompletionsMenu( - show_meta=True, - extra_filter=has_focus(default_buffer) - & multi_column_complete_style, - ), - ), - # The right prompt. - Float( - right=0, - bottom=0, - hide_when_covering_content=True, - content=_RPrompt(lambda: self.rprompt), - ), - ], - ), - ConditionalContainer(ValidationToolbar(), filter=~is_done), - ConditionalContainer( - system_toolbar, dyncond("enable_system_prompt") & ~is_done - ), - # In multiline mode, we use two toolbars for 'arg' and 'search'. - ConditionalContainer( - Window(FormattedTextControl(self._get_arg_text), height=1), - dyncond("multiline") & has_arg, - ), - ConditionalContainer(search_toolbar, dyncond("multiline") & ~is_done), - bottom_toolbar, - ] - ) - - return Layout(layout, default_buffer_window) - - def _create_application( - self, editing_mode: EditingMode, erase_when_done: bool - ) -> Application[_T]: - """ - Create the `Application` object. - """ - dyncond = self._dyncond - - # Default key bindings. - auto_suggest_bindings = load_auto_suggest_bindings() - open_in_editor_bindings = load_open_in_editor_bindings() - prompt_bindings = self._create_prompt_bindings() - - # Create application - application: Application[_T] = Application( - layout=self.layout, - style=DynamicStyle(lambda: self.style), - style_transformation=merge_style_transformations( - [ - DynamicStyleTransformation(lambda: self.style_transformation), - ConditionalStyleTransformation( - SwapLightAndDarkStyleTransformation(), - dyncond("swap_light_and_dark_colors"), - ), - ] - ), - include_default_pygments_style=dyncond("include_default_pygments_style"), - clipboard=DynamicClipboard(lambda: self.clipboard), - key_bindings=merge_key_bindings( - [ - merge_key_bindings( - [ - auto_suggest_bindings, - ConditionalKeyBindings( - open_in_editor_bindings, - dyncond("enable_open_in_editor") - & has_focus(DEFAULT_BUFFER), - ), - prompt_bindings, - ] - ), - DynamicKeyBindings(lambda: self.key_bindings), - ] - ), - mouse_support=dyncond("mouse_support"), - editing_mode=editing_mode, - erase_when_done=erase_when_done, - reverse_vi_search_direction=True, - color_depth=lambda: self.color_depth, - cursor=DynamicCursorShapeConfig(lambda: self.cursor), - refresh_interval=self.refresh_interval, - input=self._input, - output=self._output, - ) - - # During render time, make sure that we focus the right search control - # (if we are searching). - This could be useful if people make the - # 'multiline' property dynamic. - """ - def on_render(app): - multiline = is_true(self.multiline) - current_control = app.layout.current_control - - if multiline: - if current_control == search_buffer_control: - app.layout.current_control = search_toolbar.control - app.invalidate() - else: - if current_control == search_toolbar.control: - app.layout.current_control = search_buffer_control - app.invalidate() - - app.on_render += on_render - """ - - return application - - def _create_prompt_bindings(self) -> KeyBindings: - """ - Create the KeyBindings for a prompt application. - """ - kb = KeyBindings() - handle = kb.add - default_focused = has_focus(DEFAULT_BUFFER) - - @Condition - def do_accept() -> bool: - return not is_true(self.multiline) and self.app.layout.has_focus( - DEFAULT_BUFFER - ) - - @handle("enter", filter=do_accept & default_focused) - def _accept_input(event: E) -> None: - "Accept input when enter has been pressed." - self.default_buffer.validate_and_handle() - - @Condition - def readline_complete_style() -> bool: - return self.complete_style == CompleteStyle.READLINE_LIKE - - @handle("tab", filter=readline_complete_style & default_focused) - def _complete_like_readline(event: E) -> None: - "Display completions (like Readline)." - display_completions_like_readline(event) - - @handle("c-c", filter=default_focused) - @handle("<sigint>") - def _keyboard_interrupt(event: E) -> None: - "Abort when Control-C has been pressed." - event.app.exit(exception=KeyboardInterrupt, style="class:aborting") - - @Condition - def ctrl_d_condition() -> bool: - """Ctrl-D binding is only active when the default buffer is selected - and empty.""" - app = get_app() - return ( - app.current_buffer.name == DEFAULT_BUFFER - and not app.current_buffer.text - ) - - @handle("c-d", filter=ctrl_d_condition & default_focused) - def _eof(event: E) -> None: - "Exit when Control-D has been pressed." - event.app.exit(exception=EOFError, style="class:exiting") - - suspend_supported = Condition(suspend_to_background_supported) - - @Condition - def enable_suspend() -> bool: - return to_filter(self.enable_suspend)() - - @handle("c-z", filter=suspend_supported & enable_suspend) - def _suspend(event: E) -> None: - """ - Suspend process to background. - """ - event.app.suspend_to_background() - - return kb - - def prompt( - self, - # When any of these arguments are passed, this value is overwritten - # in this PromptSession. - message: Optional[AnyFormattedText] = 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, - # Following arguments are specific to the current `prompt()` call. - default: Union[str, Document] = "", - accept_default: bool = False, - pre_run: Optional[Callable[[], None]] = None, - set_exception_handler: bool = True, - handle_sigint: bool = True, - in_thread: bool = False, - ) -> _T: - """ - Display the prompt. - - The first set of arguments is a subset of the :class:`~.PromptSession` - class itself. For these, passing in ``None`` will keep the current - values that are active in the session. Passing in a value will set the - attribute for the session, which means that it applies to the current, - but also to the next prompts. - - Note that in order to erase a ``Completer``, ``Validator`` or - ``AutoSuggest``, you can't use ``None``. Instead pass in a - ``DummyCompleter``, ``DummyValidator`` or ``DummyAutoSuggest`` instance - respectively. For a ``Lexer`` you can pass in an empty ``SimpleLexer``. - - Additional arguments, specific for this prompt: - - :param default: The default input text to be shown. (This can be edited - by the user). - :param accept_default: When `True`, automatically accept the default - value without allowing the user to edit the input. - :param pre_run: Callable, called at the start of `Application.run`. - :param in_thread: Run the prompt in a background thread; block the - current thread. This avoids interference with an event loop in the - current thread. Like `Application.run(in_thread=True)`. - - This method will raise ``KeyboardInterrupt`` when control-c has been - pressed (for abort) and ``EOFError`` when control-d has been pressed - (for exit). - """ - # NOTE: We used to create a backup of the PromptSession attributes and - # restore them after exiting the prompt. This code has been - # removed, because it was confusing and didn't really serve a use - # case. (People were changing `Application.editing_mode` - # dynamically and surprised that it was reset after every call.) - - # NOTE 2: YES, this is a lot of repeation below... - # However, it is a very convenient for a user to accept all - # these parameters in this `prompt` method as well. We could - # use `locals()` and `setattr` to avoid the repetition, but - # then we loose the advantage of mypy and pyflakes to be able - # to verify the code. - if message is not None: - self.message = message - if editing_mode is not None: - self.editing_mode = editing_mode - if refresh_interval is not None: - self.refresh_interval = refresh_interval - if vi_mode: - self.editing_mode = EditingMode.VI - if lexer is not None: - self.lexer = lexer - if completer is not None: - self.completer = completer - if complete_in_thread is not None: - self.complete_in_thread = complete_in_thread - if is_password is not None: - self.is_password = is_password - if key_bindings is not None: - self.key_bindings = key_bindings - if bottom_toolbar is not None: - self.bottom_toolbar = bottom_toolbar - if style is not None: - self.style = style - if color_depth is not None: - self.color_depth = color_depth - if cursor is not None: - self.cursor = cursor - if include_default_pygments_style is not None: - self.include_default_pygments_style = include_default_pygments_style - if style_transformation is not None: - self.style_transformation = style_transformation - if swap_light_and_dark_colors is not None: - self.swap_light_and_dark_colors = swap_light_and_dark_colors - if rprompt is not None: - self.rprompt = rprompt - if multiline is not None: - self.multiline = multiline - if prompt_continuation is not None: - self.prompt_continuation = prompt_continuation - if wrap_lines is not None: - self.wrap_lines = wrap_lines - if enable_history_search is not None: - self.enable_history_search = enable_history_search - if search_ignore_case is not None: - self.search_ignore_case = search_ignore_case - if complete_while_typing is not None: - self.complete_while_typing = complete_while_typing - if validate_while_typing is not None: - self.validate_while_typing = validate_while_typing - if complete_style is not None: - self.complete_style = complete_style - if auto_suggest is not None: - self.auto_suggest = auto_suggest - if validator is not None: - self.validator = validator - if clipboard is not None: - self.clipboard = clipboard - if mouse_support is not None: - self.mouse_support = mouse_support - if input_processors is not None: - self.input_processors = input_processors - if placeholder is not None: - self.placeholder = placeholder - if reserve_space_for_menu is not None: - self.reserve_space_for_menu = reserve_space_for_menu - if enable_system_prompt is not None: - self.enable_system_prompt = enable_system_prompt - if enable_suspend is not None: - self.enable_suspend = enable_suspend - if enable_open_in_editor is not None: - self.enable_open_in_editor = enable_open_in_editor - if tempfile_suffix is not None: - self.tempfile_suffix = tempfile_suffix - if tempfile is not None: - self.tempfile = tempfile - - self._add_pre_run_callables(pre_run, accept_default) - self.default_buffer.reset( - default if isinstance(default, Document) else Document(default) - ) - self.app.refresh_interval = self.refresh_interval # This is not reactive. - - # If we are using the default output, and have a dumb terminal. Use the - # dumb prompt. - if self._output is None and is_dumb_terminal(): - with self._dumb_prompt(self.message) as dump_app: - return dump_app.run(in_thread=in_thread, handle_sigint=handle_sigint) - - return self.app.run( - set_exception_handler=set_exception_handler, - in_thread=in_thread, - handle_sigint=handle_sigint, - ) - - @contextmanager - def _dumb_prompt(self, message: AnyFormattedText = "") -> Iterator[Application[_T]]: - """ - Create prompt `Application` for prompt function for dumb terminals. - - Dumb terminals have minimum rendering capabilities. We can only print - text to the screen. We can't use colors, and we can't do cursor - movements. The Emacs inferior shell is an example of a dumb terminal. - - We will show the prompt, and wait for the input. We still handle arrow - keys, and all custom key bindings, but we don't really render the - cursor movements. Instead we only print the typed character that's - right before the cursor. - """ - # Send prompt to output. - self.output.write(fragment_list_to_text(to_formatted_text(self.message))) - self.output.flush() - - # Key bindings for the dumb prompt: mostly the same as the full prompt. - key_bindings: KeyBindingsBase = self._create_prompt_bindings() - if self.key_bindings: - key_bindings = merge_key_bindings([self.key_bindings, key_bindings]) - - # Create and run application. - application = cast( - Application[_T], - Application( - input=self.input, - output=DummyOutput(), - layout=self.layout, - key_bindings=key_bindings, - ), - ) - - def on_text_changed(_: object) -> None: - self.output.write(self.default_buffer.document.text_before_cursor[-1:]) - self.output.flush() - - self.default_buffer.on_text_changed += on_text_changed - - try: - yield application - finally: - # Render line ending. - self.output.write("\r\n") - self.output.flush() - - self.default_buffer.on_text_changed -= on_text_changed - - async def prompt_async( - self, - # When any of these arguments are passed, this value is overwritten - # in this PromptSession. - message: Optional[AnyFormattedText] = 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, - # Following arguments are specific to the current `prompt()` call. - default: Union[str, Document] = "", - accept_default: bool = False, - pre_run: Optional[Callable[[], None]] = None, - set_exception_handler: bool = True, - handle_sigint: bool = True, - ) -> _T: - - if message is not None: - self.message = message - if editing_mode is not None: - self.editing_mode = editing_mode - if refresh_interval is not None: - self.refresh_interval = refresh_interval - if vi_mode: - self.editing_mode = EditingMode.VI - if lexer is not None: - self.lexer = lexer - if completer is not None: - self.completer = completer - if complete_in_thread is not None: - self.complete_in_thread = complete_in_thread - if is_password is not None: - self.is_password = is_password - if key_bindings is not None: - self.key_bindings = key_bindings - if bottom_toolbar is not None: - self.bottom_toolbar = bottom_toolbar - if style is not None: - self.style = style - if color_depth is not None: - self.color_depth = color_depth - if cursor is not None: - self.cursor = cursor - if include_default_pygments_style is not None: - self.include_default_pygments_style = include_default_pygments_style - if style_transformation is not None: - self.style_transformation = style_transformation - if swap_light_and_dark_colors is not None: - self.swap_light_and_dark_colors = swap_light_and_dark_colors - if rprompt is not None: - self.rprompt = rprompt - if multiline is not None: - self.multiline = multiline - if prompt_continuation is not None: - self.prompt_continuation = prompt_continuation - if wrap_lines is not None: - self.wrap_lines = wrap_lines - if enable_history_search is not None: - self.enable_history_search = enable_history_search - if search_ignore_case is not None: - self.search_ignore_case = search_ignore_case - if complete_while_typing is not None: - self.complete_while_typing = complete_while_typing - if validate_while_typing is not None: - self.validate_while_typing = validate_while_typing - if complete_style is not None: - self.complete_style = complete_style - if auto_suggest is not None: - self.auto_suggest = auto_suggest - if validator is not None: - self.validator = validator - if clipboard is not None: - self.clipboard = clipboard - if mouse_support is not None: - self.mouse_support = mouse_support - if input_processors is not None: - self.input_processors = input_processors - if placeholder is not None: - self.placeholder = placeholder - if reserve_space_for_menu is not None: - self.reserve_space_for_menu = reserve_space_for_menu - if enable_system_prompt is not None: - self.enable_system_prompt = enable_system_prompt - if enable_suspend is not None: - self.enable_suspend = enable_suspend - if enable_open_in_editor is not None: - self.enable_open_in_editor = enable_open_in_editor - if tempfile_suffix is not None: - self.tempfile_suffix = tempfile_suffix - if tempfile is not None: - self.tempfile = tempfile - - self._add_pre_run_callables(pre_run, accept_default) - self.default_buffer.reset( - default if isinstance(default, Document) else Document(default) - ) - self.app.refresh_interval = self.refresh_interval # This is not reactive. - - # If we are using the default output, and have a dumb terminal. Use the - # dumb prompt. - if self._output is None and is_dumb_terminal(): - with self._dumb_prompt(self.message) as dump_app: - return await dump_app.run_async(handle_sigint=handle_sigint) - - return await self.app.run_async( - set_exception_handler=set_exception_handler, handle_sigint=handle_sigint - ) - - def _add_pre_run_callables( - self, pre_run: Optional[Callable[[], None]], accept_default: bool - ) -> None: - def pre_run2() -> None: - if pre_run: - pre_run() - - if accept_default: - # Validate and handle input. We use `call_from_executor` in - # 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) - - self.app.pre_run_callables.append(pre_run2) - - @property - def editing_mode(self) -> EditingMode: - return self.app.editing_mode - - @editing_mode.setter - def editing_mode(self, value: EditingMode) -> None: - self.app.editing_mode = value - - def _get_default_buffer_control_height(self) -> Dimension: - # If there is an autocompletion menu to be shown, make sure that our - # layout has at least a minimal height in order to display it. - if ( - self.completer is not None - and self.complete_style != CompleteStyle.READLINE_LIKE - ): - space = self.reserve_space_for_menu - else: - space = 0 - - if space and not get_app().is_done: - buff = self.default_buffer - - # Reserve the space, either when there are completions, or when - # `complete_while_typing` is true and we expect completions very - # soon. - if buff.complete_while_typing() or buff.complete_state is not None: - return Dimension(min=space) - - return Dimension() - - def _get_prompt(self) -> StyleAndTextTuples: - return to_formatted_text(self.message, style="class:prompt") - - def _get_continuation( - self, width: int, line_number: int, wrap_count: int - ) -> StyleAndTextTuples: - """ - Insert the prompt continuation. - - :param width: The width that was used for the prompt. (more or less can - be used.) - :param line_number: - :param wrap_count: Amount of times that the line has been wrapped. - """ - prompt_continuation = self.prompt_continuation - - if callable(prompt_continuation): - continuation: AnyFormattedText = prompt_continuation( - width, line_number, wrap_count - ) - else: - continuation = prompt_continuation - - # When the continuation prompt is not given, choose the same width as - # the actual prompt. - if continuation is None and is_true(self.multiline): - continuation = " " * width - - return to_formatted_text(continuation, style="class:prompt-continuation") - - def _get_line_prefix( - self, - line_number: int, - wrap_count: int, - get_prompt_text_2: _StyleAndTextTuplesCallable, - ) -> StyleAndTextTuples: - """ - Return whatever needs to be inserted before every line. - (the prompt, or a line continuation.) - """ - # First line: display the "arg" or the prompt. - if line_number == 0 and wrap_count == 0: - if not is_true(self.multiline) and get_app().key_processor.arg is not None: - return self._inline_arg() - else: - return get_prompt_text_2() - - # For the next lines, display the appropriate continuation. - prompt_width = get_cwidth(fragment_list_to_text(get_prompt_text_2())) - return self._get_continuation(prompt_width, line_number, wrap_count) - - def _get_arg_text(self) -> StyleAndTextTuples: - "'arg' toolbar, for in multiline mode." - arg = self.app.key_processor.arg - if arg is None: - # Should not happen because of the `has_arg` filter in the layout. - return [] - - if arg == "-": - arg = "-1" - - return [("class:arg-toolbar", "Repeat: "), ("class:arg-toolbar.text", arg)] - - def _inline_arg(self) -> StyleAndTextTuples: - "'arg' prefix, for in single line mode." - app = get_app() - if app.key_processor.arg is None: - return [] - else: - arg = app.key_processor.arg - - return [ - ("class:prompt.arg", "(arg: "), - ("class:prompt.arg.text", str(arg)), - ("class:prompt.arg", ") "), - ] - - # Expose the Input and Output objects as attributes, mainly for - # backward-compatibility. - - @property - def input(self) -> Input: - return self.app.input - - @property - def output(self) -> Output: - return self.app.output - - -def prompt( - message: Optional[AnyFormattedText] = 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, - 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, - # Following arguments are specific to the current `prompt()` call. - default: str = "", - accept_default: bool = False, - pre_run: Optional[Callable[[], None]] = None, -) -> str: - """ - The global `prompt` function. This will create a new `PromptSession` - instance for every call. - """ - # The history is the only attribute that has to be passed to the - # `PromptSession`, it can't be passed into the `prompt()` method. - session: PromptSession[str] = PromptSession(history=history) - - return session.prompt( - message, - editing_mode=editing_mode, - refresh_interval=refresh_interval, - vi_mode=vi_mode, - lexer=lexer, - completer=completer, - complete_in_thread=complete_in_thread, - is_password=is_password, - key_bindings=key_bindings, - bottom_toolbar=bottom_toolbar, - style=style, - color_depth=color_depth, - cursor=cursor, - include_default_pygments_style=include_default_pygments_style, - style_transformation=style_transformation, - swap_light_and_dark_colors=swap_light_and_dark_colors, - rprompt=rprompt, - multiline=multiline, - prompt_continuation=prompt_continuation, - wrap_lines=wrap_lines, - enable_history_search=enable_history_search, - search_ignore_case=search_ignore_case, - complete_while_typing=complete_while_typing, - validate_while_typing=validate_while_typing, - complete_style=complete_style, - auto_suggest=auto_suggest, - validator=validator, - clipboard=clipboard, - mouse_support=mouse_support, - input_processors=input_processors, - placeholder=placeholder, - reserve_space_for_menu=reserve_space_for_menu, - enable_system_prompt=enable_system_prompt, - enable_suspend=enable_suspend, - enable_open_in_editor=enable_open_in_editor, - tempfile_suffix=tempfile_suffix, - tempfile=tempfile, - default=default, - accept_default=accept_default, - pre_run=pre_run, - ) - - -prompt.__doc__ = PromptSession.prompt.__doc__ - - -def create_confirm_session( - message: str, suffix: str = " (y/n) " -) -> PromptSession[bool]: - """ - Create a `PromptSession` object for the 'confirm' function. - """ - bindings = KeyBindings() - - @bindings.add("y") - @bindings.add("Y") - def yes(event: E) -> None: - session.default_buffer.text = "y" - event.app.exit(result=True) - - @bindings.add("n") - @bindings.add("N") - def no(event: E) -> None: - session.default_buffer.text = "n" - event.app.exit(result=False) - - @bindings.add(Keys.Any) - def _(event: E) -> None: - "Disallow inserting other text." - pass - - complete_message = merge_formatted_text([message, suffix]) - session: PromptSession[bool] = PromptSession( - complete_message, key_bindings=bindings - ) - return session - - -def confirm(message: str = "Confirm?", suffix: str = " (y/n) ") -> bool: - """ - Display a confirmation prompt that returns True/False. - """ - session = create_confirm_session(message, suffix) - return session.prompt() diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/shortcuts/utils.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/shortcuts/utils.py deleted file mode 100644 index a628f4b6aef..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/shortcuts/utils.py +++ /dev/null @@ -1,238 +0,0 @@ -from asyncio.events import AbstractEventLoop -from typing import TYPE_CHECKING, Any, Optional, TextIO - -from prompt_toolkit.application import Application -from prompt_toolkit.application.current import get_app_or_none, get_app_session -from prompt_toolkit.application.run_in_terminal import run_in_terminal -from prompt_toolkit.eventloop import get_event_loop -from prompt_toolkit.formatted_text import ( - FormattedText, - StyleAndTextTuples, - to_formatted_text, -) -from prompt_toolkit.input import DummyInput -from prompt_toolkit.layout import Layout -from prompt_toolkit.output import ColorDepth, Output -from prompt_toolkit.output.defaults import create_output -from prompt_toolkit.renderer import ( - print_formatted_text as renderer_print_formatted_text, -) -from prompt_toolkit.styles import ( - BaseStyle, - StyleTransformation, - default_pygments_style, - default_ui_style, - merge_styles, -) - -if TYPE_CHECKING: - from prompt_toolkit.layout.containers import AnyContainer - -__all__ = [ - "print_formatted_text", - "print_container", - "clear", - "set_title", - "clear_title", -] - - -def print_formatted_text( - *values: Any, - sep: str = " ", - end: str = "\n", - file: Optional[TextIO] = None, - flush: bool = False, - style: Optional[BaseStyle] = None, - output: Optional[Output] = None, - color_depth: Optional[ColorDepth] = None, - style_transformation: Optional[StyleTransformation] = None, - include_default_pygments_style: bool = True, -) -> None: - """ - :: - - print_formatted_text(*values, sep=' ', end='\\n', file=None, flush=False, style=None, output=None) - - Print text to stdout. This is supposed to be compatible with Python's print - function, but supports printing of formatted text. You can pass a - :class:`~prompt_toolkit.formatted_text.FormattedText`, - :class:`~prompt_toolkit.formatted_text.HTML` or - :class:`~prompt_toolkit.formatted_text.ANSI` object to print formatted - text. - - * Print HTML as follows:: - - print_formatted_text(HTML('<i>Some italic text</i> <ansired>This is red!</ansired>')) - - style = Style.from_dict({ - 'hello': '#ff0066', - 'world': '#884444 italic', - }) - print_formatted_text(HTML('<hello>Hello</hello> <world>world</world>!'), style=style) - - * Print a list of (style_str, text) tuples in the given style to the - output. E.g.:: - - style = Style.from_dict({ - 'hello': '#ff0066', - 'world': '#884444 italic', - }) - fragments = FormattedText([ - ('class:hello', 'Hello'), - ('class:world', 'World'), - ]) - print_formatted_text(fragments, style=style) - - If you want to print a list of Pygments tokens, wrap it in - :class:`~prompt_toolkit.formatted_text.PygmentsTokens` to do the - conversion. - - If a prompt_toolkit `Application` is currently running, this will always - print above the application or prompt (similar to `patch_stdout`). So, - `print_formatted_text` will erase the current application, print the text, - and render the application again. - - :param values: Any kind of printable object, or formatted string. - :param sep: String inserted between values, default a space. - :param end: String appended after the last value, default a newline. - :param style: :class:`.Style` instance for the color scheme. - :param include_default_pygments_style: `bool`. Include the default Pygments - style when set to `True` (the default). - """ - assert not (output and file) - - # Create Output object. - if output is None: - if file: - output = create_output(stdout=file) - else: - output = get_app_session().output - - assert isinstance(output, Output) - - # Get color depth. - color_depth = color_depth or output.get_default_color_depth() - - # Merges values. - def to_text(val: Any) -> StyleAndTextTuples: - # Normal lists which are not instances of `FormattedText` are - # considered plain text. - if isinstance(val, list) and not isinstance(val, FormattedText): - return to_formatted_text(f"{val}") - return to_formatted_text(val, auto_convert=True) - - fragments = [] - for i, value in enumerate(values): - fragments.extend(to_text(value)) - - if sep and i != len(values) - 1: - fragments.extend(to_text(sep)) - - fragments.extend(to_text(end)) - - # Print output. - def render() -> None: - assert isinstance(output, Output) - - renderer_print_formatted_text( - output, - fragments, - _create_merged_style( - style, include_default_pygments_style=include_default_pygments_style - ), - color_depth=color_depth, - style_transformation=style_transformation, - ) - - # Flush the output stream. - if flush: - output.flush() - - # If an application is running, print above the app. This does not require - # `patch_stdout`. - loop: Optional[AbstractEventLoop] = None - - app = get_app_or_none() - if app is not None: - loop = app.loop - - if loop is not None: - loop.call_soon_threadsafe(lambda: run_in_terminal(render)) - else: - render() - - -def print_container( - container: "AnyContainer", - file: Optional[TextIO] = None, - style: Optional[BaseStyle] = None, - include_default_pygments_style: bool = True, -) -> None: - """ - Print any layout to the output in a non-interactive way. - - Example usage:: - - from prompt_toolkit.widgets import Frame, TextArea - print_container( - Frame(TextArea(text='Hello world!'))) - """ - if file: - output = create_output(stdout=file) - else: - output = get_app_session().output - - app: Application[None] = Application( - layout=Layout(container=container), - output=output, - # `DummyInput` will cause the application to terminate immediately. - input=DummyInput(), - style=_create_merged_style( - style, include_default_pygments_style=include_default_pygments_style - ), - ) - try: - app.run(in_thread=True) - except EOFError: - pass - - -def _create_merged_style( - style: Optional[BaseStyle], include_default_pygments_style: bool -) -> BaseStyle: - """ - Merge user defined style with built-in style. - """ - styles = [default_ui_style()] - if include_default_pygments_style: - styles.append(default_pygments_style()) - if style: - styles.append(style) - - return merge_styles(styles) - - -def clear() -> None: - """ - Clear the screen. - """ - output = get_app_session().output - output.erase_screen() - output.cursor_goto(0, 0) - output.flush() - - -def set_title(text: str) -> None: - """ - Set the terminal title. - """ - output = get_app_session().output - output.set_title(text) - - -def clear_title() -> None: - """ - Erase the current title. - """ - set_title("") diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/styles/__init__.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/styles/__init__.py deleted file mode 100644 index c270ae01bb7..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/styles/__init__.py +++ /dev/null @@ -1,64 +0,0 @@ -""" -Styling for prompt_toolkit applications. -""" -from .base import ( - ANSI_COLOR_NAMES, - DEFAULT_ATTRS, - Attrs, - BaseStyle, - DummyStyle, - DynamicStyle, -) -from .defaults import default_pygments_style, default_ui_style -from .named_colors import NAMED_COLORS -from .pygments import ( - pygments_token_to_classname, - style_from_pygments_cls, - style_from_pygments_dict, -) -from .style import Priority, Style, merge_styles, parse_color -from .style_transformation import ( - AdjustBrightnessStyleTransformation, - ConditionalStyleTransformation, - DummyStyleTransformation, - DynamicStyleTransformation, - ReverseStyleTransformation, - SetDefaultColorStyleTransformation, - StyleTransformation, - SwapLightAndDarkStyleTransformation, - merge_style_transformations, -) - -__all__ = [ - # Base. - "Attrs", - "DEFAULT_ATTRS", - "ANSI_COLOR_NAMES", - "BaseStyle", - "DummyStyle", - "DynamicStyle", - # Defaults. - "default_ui_style", - "default_pygments_style", - # Style. - "Style", - "Priority", - "merge_styles", - "parse_color", - # Style transformation. - "StyleTransformation", - "SwapLightAndDarkStyleTransformation", - "ReverseStyleTransformation", - "SetDefaultColorStyleTransformation", - "AdjustBrightnessStyleTransformation", - "DummyStyleTransformation", - "ConditionalStyleTransformation", - "DynamicStyleTransformation", - "merge_style_transformations", - # Pygments. - "style_from_pygments_cls", - "style_from_pygments_dict", - "pygments_token_to_classname", - # Named colors. - "NAMED_COLORS", -] diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/styles/base.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/styles/base.py deleted file mode 100644 index aa77b9ad086..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/styles/base.py +++ /dev/null @@ -1,181 +0,0 @@ -""" -The base classes for the styling. -""" -from abc import ABCMeta, abstractmethod, abstractproperty -from typing import Callable, Dict, Hashable, List, NamedTuple, Optional, Tuple - -__all__ = [ - "Attrs", - "DEFAULT_ATTRS", - "ANSI_COLOR_NAMES", - "ANSI_COLOR_NAMES_ALIASES", - "BaseStyle", - "DummyStyle", - "DynamicStyle", -] - - -#: 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] - - -""" -:param color: Hexadecimal string. E.g. '000000' or Ansi color name: e.g. 'ansiblue' -:param bgcolor: Hexadecimal string. E.g. 'ffffff' or Ansi color name: e.g. 'ansired' -:param bold: Boolean -:param underline: Boolean -:param strike: Boolean -:param italic: Boolean -:param blink: Boolean -:param reverse: Boolean -:param hidden: Boolean -""" - -#: The default `Attrs`. -DEFAULT_ATTRS = Attrs( - color="", - bgcolor="", - bold=False, - underline=False, - strike=False, - italic=False, - blink=False, - reverse=False, - hidden=False, -) - - -#: ``Attrs.bgcolor/fgcolor`` can be in either 'ffffff' format, or can be any of -#: the following in case we want to take colors from the 8/16 color palette. -#: Usually, in that case, the terminal application allows to configure the RGB -#: values for these names. -#: ISO 6429 colors -ANSI_COLOR_NAMES = [ - "ansidefault", - # Low intensity, dark. (One or two components 0x80, the other 0x00.) - "ansiblack", - "ansired", - "ansigreen", - "ansiyellow", - "ansiblue", - "ansimagenta", - "ansicyan", - "ansigray", - # High intensity, bright. (One or two components 0xff, the other 0x00. Not supported everywhere.) - "ansibrightblack", - "ansibrightred", - "ansibrightgreen", - "ansibrightyellow", - "ansibrightblue", - "ansibrightmagenta", - "ansibrightcyan", - "ansiwhite", -] - - -# People don't use the same ANSI color names everywhere. In prompt_toolkit 1.0 -# we used some unconventional names (which were contributed like that to -# 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] = { - "ansidarkgray": "ansibrightblack", - "ansiteal": "ansicyan", - "ansiturquoise": "ansibrightcyan", - "ansibrown": "ansiyellow", - "ansipurple": "ansimagenta", - "ansifuchsia": "ansibrightmagenta", - "ansilightgray": "ansigray", - "ansidarkred": "ansired", - "ansidarkgreen": "ansigreen", - "ansidarkblue": "ansiblue", -} -assert set(ANSI_COLOR_NAMES_ALIASES.values()).issubset(set(ANSI_COLOR_NAMES)) -assert not (set(ANSI_COLOR_NAMES_ALIASES.keys()) & set(ANSI_COLOR_NAMES)) - - -class BaseStyle(metaclass=ABCMeta): - """ - Abstract base class for prompt_toolkit styles. - """ - - @abstractmethod - def get_attrs_for_style_str( - self, style_str: str, default: Attrs = DEFAULT_ATTRS - ) -> Attrs: - """ - Return :class:`.Attrs` for the given style string. - - :param style_str: The style string. This can contain inline styling as - well as classnames (e.g. "class:title"). - :param default: `Attrs` to be used if no styling was defined. - """ - - @abstractproperty - 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.) - """ - return [] - - @abstractmethod - def invalidation_hash(self) -> Hashable: - """ - Invalidation hash for the style. When this changes over time, the - renderer knows that something in the style changed, and that everything - has to be redrawn. - """ - - -class DummyStyle(BaseStyle): - """ - A style that doesn't style anything. - """ - - def get_attrs_for_style_str( - self, style_str: str, default: Attrs = DEFAULT_ATTRS - ) -> Attrs: - return default - - def invalidation_hash(self) -> Hashable: - return 1 # Always the same value. - - @property - def style_rules(self) -> List[Tuple[str, str]]: - return [] - - -class DynamicStyle(BaseStyle): - """ - Style class that can dynamically returns an other Style. - - :param get_style: Callable that returns a :class:`.Style` instance. - """ - - def __init__(self, get_style: Callable[[], Optional[BaseStyle]]): - self.get_style = get_style - self._dummy = DummyStyle() - - def get_attrs_for_style_str( - self, style_str: str, default: Attrs = DEFAULT_ATTRS - ) -> Attrs: - style = self.get_style() or self._dummy - - return style.get_attrs_for_style_str(style_str, default) - - def invalidation_hash(self) -> Hashable: - return (self.get_style() or self._dummy).invalidation_hash() - - @property - def style_rules(self) -> List[Tuple[str, str]]: - return (self.get_style() or self._dummy).style_rules diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/styles/defaults.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/styles/defaults.py deleted file mode 100644 index 4ac554562cd..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/styles/defaults.py +++ /dev/null @@ -1,231 +0,0 @@ -""" -The default styling. -""" -from prompt_toolkit.cache import memoized - -from .base import ANSI_COLOR_NAMES, BaseStyle -from .named_colors import NAMED_COLORS -from .style import Style, merge_styles - -__all__ = [ - "default_ui_style", - "default_pygments_style", -] - -#: Default styling. Mapping from classnames to their style definition. -PROMPT_TOOLKIT_STYLE = [ - # Highlighting of search matches in document. - ("search", "bg:ansibrightyellow ansiblack"), - ("search.current", ""), - # Incremental search. - ("incsearch", ""), - ("incsearch.current", "reverse"), - # Highlighting of select text in document. - ("selected", "reverse"), - ("cursor-column", "bg:#dddddd"), - ("cursor-line", "underline"), - ("color-column", "bg:#ccaacc"), - # Highlighting of matching brackets. - ("matching-bracket", ""), - ("matching-bracket.other", "#000000 bg:#aacccc"), - ("matching-bracket.cursor", "#ff8888 bg:#880000"), - # Styling of other cursors, in case of block editing. - ("multiple-cursors", "#000000 bg:#ccccaa"), - # Line numbers. - ("line-number", "#888888"), - ("line-number.current", "bold"), - ("tilde", "#8888ff"), - # Default prompt. - ("prompt", ""), - ("prompt.arg", "noinherit"), - ("prompt.arg.text", ""), - ("prompt.search", "noinherit"), - ("prompt.search.text", ""), - # Search toolbar. - ("search-toolbar", "bold"), - ("search-toolbar.text", "nobold"), - # System toolbar - ("system-toolbar", "bold"), - ("system-toolbar.text", "nobold"), - # "arg" toolbar. - ("arg-toolbar", "bold"), - ("arg-toolbar.text", "nobold"), - # Validation toolbar. - ("validation-toolbar", "bg:#550000 #ffffff"), - ("window-too-small", "bg:#550000 #ffffff"), - # Completions toolbar. - ("completion-toolbar", "bg:#bbbbbb #000000"), - ("completion-toolbar.arrow", "bg:#bbbbbb #000000 bold"), - ("completion-toolbar.completion", "bg:#bbbbbb #000000"), - ("completion-toolbar.completion.current", "bg:#444444 #ffffff"), - # Completions menu. - ("completion-menu", "bg:#bbbbbb #000000"), - ("completion-menu.completion", ""), - ("completion-menu.completion.current", "bg:#888888 #ffffff"), - ("completion-menu.meta.completion", "bg:#999999 #000000"), - ("completion-menu.meta.completion.current", "bg:#aaaaaa #000000"), - ("completion-menu.multi-column-meta", "bg:#aaaaaa #000000"), - # Fuzzy matches in completion menu (for FuzzyCompleter). - ("completion-menu.completion fuzzymatch.outside", "fg:#444444"), - ("completion-menu.completion fuzzymatch.inside", "bold"), - ("completion-menu.completion fuzzymatch.inside.character", "underline"), - ("completion-menu.completion.current fuzzymatch.outside", "fg:default"), - ("completion-menu.completion.current fuzzymatch.inside", "nobold"), - # Styling of readline-like completions. - ("readline-like-completions", ""), - ("readline-like-completions.completion", ""), - ("readline-like-completions.completion fuzzymatch.outside", "#888888"), - ("readline-like-completions.completion fuzzymatch.inside", ""), - ("readline-like-completions.completion fuzzymatch.inside.character", "underline"), - # Scrollbars. - ("scrollbar.background", "bg:#aaaaaa"), - ("scrollbar.button", "bg:#444444"), - ("scrollbar.arrow", "noinherit bold"), - # Start/end of scrollbars. Adding 'underline' here provides a nice little - # detail to the progress bar, but it doesn't look good on all terminals. - # ('scrollbar.start', 'underline #ffffff'), - # ('scrollbar.end', 'underline #000000'), - # Auto suggestion text. - ("auto-suggestion", "#666666"), - # Trailing whitespace and tabs. - ("trailing-whitespace", "#999999"), - ("tab", "#999999"), - # When Control-C/D has been pressed. Grayed. - ("aborting", "#888888 bg:default noreverse noitalic nounderline noblink"), - ("exiting", "#888888 bg:default noreverse noitalic nounderline noblink"), - # Entering a Vi digraph. - ("digraph", "#4444ff"), - # Control characters, like ^C, ^X. - ("control-character", "ansiblue"), - # Non-breaking space. - ("nbsp", "underline ansiyellow"), - # Default styling of HTML elements. - ("i", "italic"), - ("u", "underline"), - ("s", "strike"), - ("b", "bold"), - ("em", "italic"), - ("strong", "bold"), - ("del", "strike"), - ("hidden", "hidden"), - # It should be possible to use the style names in HTML. - # <reverse>...</reverse> or <noreverse>...</noreverse>. - ("italic", "italic"), - ("underline", "underline"), - ("strike", "strike"), - ("bold", "bold"), - ("reverse", "reverse"), - ("noitalic", "noitalic"), - ("nounderline", "nounderline"), - ("nostrike", "nostrike"), - ("nobold", "nobold"), - ("noreverse", "noreverse"), - # Prompt bottom toolbar - ("bottom-toolbar", "reverse"), -] - - -# Style that will turn for instance the class 'red' into 'red'. -COLORS_STYLE = [(name, "fg:" + name) for name in ANSI_COLOR_NAMES] + [ - (name.lower(), "fg:" + name) for name in NAMED_COLORS -] - - -WIDGETS_STYLE = [ - # Dialog windows. - ("dialog", "bg:#4444ff"), - ("dialog.body", "bg:#ffffff #000000"), - ("dialog.body text-area", "bg:#cccccc"), - ("dialog.body text-area last-line", "underline"), - ("dialog frame.label", "#ff0000 bold"), - # Scrollbars in dialogs. - ("dialog.body scrollbar.background", ""), - ("dialog.body scrollbar.button", "bg:#000000"), - ("dialog.body scrollbar.arrow", ""), - ("dialog.body scrollbar.start", "nounderline"), - ("dialog.body scrollbar.end", "nounderline"), - # Buttons. - ("button", ""), - ("button.arrow", "bold"), - ("button.focused", "bg:#aa0000 #ffffff"), - # Menu bars. - ("menu-bar", "bg:#aaaaaa #000000"), - ("menu-bar.selected-item", "bg:#ffffff #000000"), - ("menu", "bg:#888888 #ffffff"), - ("menu.border", "#aaaaaa"), - ("menu.border shadow", "#444444"), - # Shadows. - ("dialog shadow", "bg:#000088"), - ("dialog.body shadow", "bg:#aaaaaa"), - ("progress-bar", "bg:#000088"), - ("progress-bar.used", "bg:#ff0000"), -] - - -# The default Pygments style, include this by default in case a Pygments lexer -# is used. -PYGMENTS_DEFAULT_STYLE = { - "pygments.whitespace": "#bbbbbb", - "pygments.comment": "italic #408080", - "pygments.comment.preproc": "noitalic #bc7a00", - "pygments.keyword": "bold #008000", - "pygments.keyword.pseudo": "nobold", - "pygments.keyword.type": "nobold #b00040", - "pygments.operator": "#666666", - "pygments.operator.word": "bold #aa22ff", - "pygments.name.builtin": "#008000", - "pygments.name.function": "#0000ff", - "pygments.name.class": "bold #0000ff", - "pygments.name.namespace": "bold #0000ff", - "pygments.name.exception": "bold #d2413a", - "pygments.name.variable": "#19177c", - "pygments.name.constant": "#880000", - "pygments.name.label": "#a0a000", - "pygments.name.entity": "bold #999999", - "pygments.name.attribute": "#7d9029", - "pygments.name.tag": "bold #008000", - "pygments.name.decorator": "#aa22ff", - # Note: In Pygments, Token.String is an alias for Token.Literal.String, - # and Token.Number as an alias for Token.Literal.Number. - "pygments.literal.string": "#ba2121", - "pygments.literal.string.doc": "italic", - "pygments.literal.string.interpol": "bold #bb6688", - "pygments.literal.string.escape": "bold #bb6622", - "pygments.literal.string.regex": "#bb6688", - "pygments.literal.string.symbol": "#19177c", - "pygments.literal.string.other": "#008000", - "pygments.literal.number": "#666666", - "pygments.generic.heading": "bold #000080", - "pygments.generic.subheading": "bold #800080", - "pygments.generic.deleted": "#a00000", - "pygments.generic.inserted": "#00a000", - "pygments.generic.error": "#ff0000", - "pygments.generic.emph": "italic", - "pygments.generic.strong": "bold", - "pygments.generic.prompt": "bold #000080", - "pygments.generic.output": "#888", - "pygments.generic.traceback": "#04d", - "pygments.error": "border:#ff0000", -} - - -@memoized() -def default_ui_style() -> BaseStyle: - """ - Create a default `Style` object. - """ - return merge_styles( - [ - Style(PROMPT_TOOLKIT_STYLE), - Style(COLORS_STYLE), - Style(WIDGETS_STYLE), - ] - ) - - -@memoized() -def default_pygments_style() -> Style: - """ - Create a `Style` object that contains the default Pygments style. - """ - return Style.from_dict(PYGMENTS_DEFAULT_STYLE) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/styles/named_colors.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/styles/named_colors.py deleted file mode 100644 index 37bd6de4466..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/styles/named_colors.py +++ /dev/null @@ -1,161 +0,0 @@ -""" -All modern web browsers support these 140 color names. -Taken from: https://www.w3schools.com/colors/colors_names.asp -""" -from typing import Dict - -__all__ = [ - "NAMED_COLORS", -] - - -NAMED_COLORS: Dict[str, str] = { - "AliceBlue": "#f0f8ff", - "AntiqueWhite": "#faebd7", - "Aqua": "#00ffff", - "Aquamarine": "#7fffd4", - "Azure": "#f0ffff", - "Beige": "#f5f5dc", - "Bisque": "#ffe4c4", - "Black": "#000000", - "BlanchedAlmond": "#ffebcd", - "Blue": "#0000ff", - "BlueViolet": "#8a2be2", - "Brown": "#a52a2a", - "BurlyWood": "#deb887", - "CadetBlue": "#5f9ea0", - "Chartreuse": "#7fff00", - "Chocolate": "#d2691e", - "Coral": "#ff7f50", - "CornflowerBlue": "#6495ed", - "Cornsilk": "#fff8dc", - "Crimson": "#dc143c", - "Cyan": "#00ffff", - "DarkBlue": "#00008b", - "DarkCyan": "#008b8b", - "DarkGoldenRod": "#b8860b", - "DarkGray": "#a9a9a9", - "DarkGreen": "#006400", - "DarkGrey": "#a9a9a9", - "DarkKhaki": "#bdb76b", - "DarkMagenta": "#8b008b", - "DarkOliveGreen": "#556b2f", - "DarkOrange": "#ff8c00", - "DarkOrchid": "#9932cc", - "DarkRed": "#8b0000", - "DarkSalmon": "#e9967a", - "DarkSeaGreen": "#8fbc8f", - "DarkSlateBlue": "#483d8b", - "DarkSlateGray": "#2f4f4f", - "DarkSlateGrey": "#2f4f4f", - "DarkTurquoise": "#00ced1", - "DarkViolet": "#9400d3", - "DeepPink": "#ff1493", - "DeepSkyBlue": "#00bfff", - "DimGray": "#696969", - "DimGrey": "#696969", - "DodgerBlue": "#1e90ff", - "FireBrick": "#b22222", - "FloralWhite": "#fffaf0", - "ForestGreen": "#228b22", - "Fuchsia": "#ff00ff", - "Gainsboro": "#dcdcdc", - "GhostWhite": "#f8f8ff", - "Gold": "#ffd700", - "GoldenRod": "#daa520", - "Gray": "#808080", - "Green": "#008000", - "GreenYellow": "#adff2f", - "Grey": "#808080", - "HoneyDew": "#f0fff0", - "HotPink": "#ff69b4", - "IndianRed": "#cd5c5c", - "Indigo": "#4b0082", - "Ivory": "#fffff0", - "Khaki": "#f0e68c", - "Lavender": "#e6e6fa", - "LavenderBlush": "#fff0f5", - "LawnGreen": "#7cfc00", - "LemonChiffon": "#fffacd", - "LightBlue": "#add8e6", - "LightCoral": "#f08080", - "LightCyan": "#e0ffff", - "LightGoldenRodYellow": "#fafad2", - "LightGray": "#d3d3d3", - "LightGreen": "#90ee90", - "LightGrey": "#d3d3d3", - "LightPink": "#ffb6c1", - "LightSalmon": "#ffa07a", - "LightSeaGreen": "#20b2aa", - "LightSkyBlue": "#87cefa", - "LightSlateGray": "#778899", - "LightSlateGrey": "#778899", - "LightSteelBlue": "#b0c4de", - "LightYellow": "#ffffe0", - "Lime": "#00ff00", - "LimeGreen": "#32cd32", - "Linen": "#faf0e6", - "Magenta": "#ff00ff", - "Maroon": "#800000", - "MediumAquaMarine": "#66cdaa", - "MediumBlue": "#0000cd", - "MediumOrchid": "#ba55d3", - "MediumPurple": "#9370db", - "MediumSeaGreen": "#3cb371", - "MediumSlateBlue": "#7b68ee", - "MediumSpringGreen": "#00fa9a", - "MediumTurquoise": "#48d1cc", - "MediumVioletRed": "#c71585", - "MidnightBlue": "#191970", - "MintCream": "#f5fffa", - "MistyRose": "#ffe4e1", - "Moccasin": "#ffe4b5", - "NavajoWhite": "#ffdead", - "Navy": "#000080", - "OldLace": "#fdf5e6", - "Olive": "#808000", - "OliveDrab": "#6b8e23", - "Orange": "#ffa500", - "OrangeRed": "#ff4500", - "Orchid": "#da70d6", - "PaleGoldenRod": "#eee8aa", - "PaleGreen": "#98fb98", - "PaleTurquoise": "#afeeee", - "PaleVioletRed": "#db7093", - "PapayaWhip": "#ffefd5", - "PeachPuff": "#ffdab9", - "Peru": "#cd853f", - "Pink": "#ffc0cb", - "Plum": "#dda0dd", - "PowderBlue": "#b0e0e6", - "Purple": "#800080", - "RebeccaPurple": "#663399", - "Red": "#ff0000", - "RosyBrown": "#bc8f8f", - "RoyalBlue": "#4169e1", - "SaddleBrown": "#8b4513", - "Salmon": "#fa8072", - "SandyBrown": "#f4a460", - "SeaGreen": "#2e8b57", - "SeaShell": "#fff5ee", - "Sienna": "#a0522d", - "Silver": "#c0c0c0", - "SkyBlue": "#87ceeb", - "SlateBlue": "#6a5acd", - "SlateGray": "#708090", - "SlateGrey": "#708090", - "Snow": "#fffafa", - "SpringGreen": "#00ff7f", - "SteelBlue": "#4682b4", - "Tan": "#d2b48c", - "Teal": "#008080", - "Thistle": "#d8bfd8", - "Tomato": "#ff6347", - "Turquoise": "#40e0d0", - "Violet": "#ee82ee", - "Wheat": "#f5deb3", - "White": "#ffffff", - "WhiteSmoke": "#f5f5f5", - "Yellow": "#ffff00", - "YellowGreen": "#9acd32", -} diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/styles/pygments.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/styles/pygments.py deleted file mode 100644 index 382e5e315bb..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/styles/pygments.py +++ /dev/null @@ -1,67 +0,0 @@ -""" -Adaptor for building prompt_toolkit styles, starting from a Pygments style. - -Usage:: - - from pygments.styles.tango import TangoStyle - style = style_from_pygments_cls(pygments_style_cls=TangoStyle) -""" -from typing import TYPE_CHECKING, Dict, Type - -from .style import Style - -if TYPE_CHECKING: - from pygments.style import Style as PygmentsStyle - from pygments.token import Token - - -__all__ = [ - "style_from_pygments_cls", - "style_from_pygments_dict", - "pygments_token_to_classname", -] - - -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. - - Example:: - - from prompt_toolkit.styles.from_pygments import style_from_pygments_cls - from pygments.styles import get_style_by_name - style = style_from_pygments_cls(get_style_by_name('monokai')) - - :param pygments_style_cls: Pygments style class to start from. - """ - # Import inline. - from pygments.style import Style as PygmentsStyle - - assert issubclass(pygments_style_cls, PygmentsStyle) - - return style_from_pygments_dict(pygments_style_cls.styles) - - -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.) - """ - pygments_style = [] - - for token, style in pygments_dict.items(): - pygments_style.append((pygments_token_to_classname(token), style)) - - return Style(pygments_style) - - -def pygments_token_to_classname(token: "Token") -> str: - """ - Turn e.g. `Token.Name.Exception` into `'pygments.name.exception'`. - - (Our Pygments lexer will also turn the tokens that pygments produces in a - prompt_toolkit list of fragments that match these styling rules.) - """ - parts = ("pygments",) + token - return ".".join(parts).lower() diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/styles/style.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/styles/style.py deleted file mode 100644 index 6e4bd1f43c3..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/styles/style.py +++ /dev/null @@ -1,399 +0,0 @@ -""" -Tool for creating styles from a dictionary. -""" -import itertools -import re -import sys -from enum import Enum -from typing import Dict, Hashable, List, Set, Tuple, TypeVar - -from prompt_toolkit.cache import SimpleCache - -from .base import ( - ANSI_COLOR_NAMES, - ANSI_COLOR_NAMES_ALIASES, - DEFAULT_ATTRS, - Attrs, - BaseStyle, -) -from .named_colors import NAMED_COLORS - -__all__ = [ - "Style", - "parse_color", - "Priority", - "merge_styles", -] - -_named_colors_lowercase = {k.lower(): v.lstrip("#") for k, v in NAMED_COLORS.items()} - - -def parse_color(text: str) -> str: - """ - Parse/validate color format. - - Like in Pygments, but also support the ANSI color names. - (These will map to the colors of the 16 color palette.) - """ - # ANSI color names. - if text in ANSI_COLOR_NAMES: - return text - if text in ANSI_COLOR_NAMES_ALIASES: - return ANSI_COLOR_NAMES_ALIASES[text] - - # 140 named colors. - try: - # Replace by 'hex' value. - return _named_colors_lowercase[text.lower()] - except KeyError: - pass - - # Hex codes. - if text[0:1] == "#": - col = text[1:] - - # Keep this for backwards-compatibility (Pygments does it). - # I don't like the '#' prefix for named colors. - if col in ANSI_COLOR_NAMES: - return col - elif col in ANSI_COLOR_NAMES_ALIASES: - return ANSI_COLOR_NAMES_ALIASES[col] - - # 6 digit hex color. - elif len(col) == 6: - return col - - # 3 digit hex color. - elif len(col) == 3: - return col[0] * 2 + col[1] * 2 + col[2] * 2 - - # Default. - elif text in ("", "default"): - return text - - raise ValueError("Wrong color format %r" % text) - - -# Attributes, when they are not filled in by a style. None means that we take -# the value from the parent. -_EMPTY_ATTRS = Attrs( - color=None, - bgcolor=None, - bold=None, - underline=None, - strike=None, - italic=None, - blink=None, - reverse=None, - hidden=None, -) - - -def _expand_classname(classname: str) -> List[str]: - """ - Split a single class name at the `.` operator, and build a list of classes. - - E.g. 'a.b.c' becomes ['a', 'a.b', 'a.b.c'] - """ - result = [] - parts = classname.split(".") - - for i in range(1, len(parts) + 1): - result.append(".".join(parts[:i]).lower()) - - return result - - -def _parse_style_str(style_str: str) -> Attrs: - """ - Take a style string, e.g. 'bg:red #88ff00 class:title' - and return a `Attrs` instance. - """ - # Start from default Attrs. - if "noinherit" in style_str: - attrs = DEFAULT_ATTRS - else: - attrs = _EMPTY_ATTRS - - # Now update with the given attributes. - for part in style_str.split(): - if part == "noinherit": - pass - elif part == "bold": - attrs = attrs._replace(bold=True) - elif part == "nobold": - attrs = attrs._replace(bold=False) - elif part == "italic": - attrs = attrs._replace(italic=True) - elif part == "noitalic": - attrs = attrs._replace(italic=False) - elif part == "underline": - attrs = attrs._replace(underline=True) - elif part == "nounderline": - attrs = attrs._replace(underline=False) - elif part == "strike": - attrs = attrs._replace(strike=True) - elif part == "nostrike": - attrs = attrs._replace(strike=False) - - # prompt_toolkit extensions. Not in Pygments. - elif part == "blink": - attrs = attrs._replace(blink=True) - elif part == "noblink": - attrs = attrs._replace(blink=False) - elif part == "reverse": - attrs = attrs._replace(reverse=True) - elif part == "noreverse": - attrs = attrs._replace(reverse=False) - elif part == "hidden": - attrs = attrs._replace(hidden=True) - elif part == "nohidden": - attrs = attrs._replace(hidden=False) - - # Pygments properties that we ignore. - elif part in ("roman", "sans", "mono"): - pass - elif part.startswith("border:"): - pass - - # Ignore pieces in between square brackets. This is internal stuff. - # Like '[transparent]' or '[set-cursor-position]'. - elif part.startswith("[") and part.endswith("]"): - pass - - # Colors. - elif part.startswith("bg:"): - attrs = attrs._replace(bgcolor=parse_color(part[3:])) - elif part.startswith("fg:"): # The 'fg:' prefix is optional. - attrs = attrs._replace(color=parse_color(part[3:])) - else: - attrs = attrs._replace(color=parse_color(part)) - - return attrs - - -CLASS_NAMES_RE = re.compile(r"^[a-z0-9.\s_-]*$") # This one can't contain a comma! - - -class Priority(Enum): - """ - The priority of the rules, when a style is created from a dictionary. - - In a `Style`, rules that are defined later will always override previous - defined rules, however in a dictionary, the key order was arbitrary before - Python 3.6. This means that the style could change at random between rules. - - We have two options: - - - `DICT_KEY_ORDER`: This means, iterate through the dictionary, and take - the key/value pairs in order as they come. This is a good option if you - have Python >3.6. Rules at the end will override rules at the beginning. - - `MOST_PRECISE`: keys that are defined with most precision will get higher - priority. (More precise means: more elements.) - """ - - DICT_KEY_ORDER = "KEY_ORDER" - MOST_PRECISE = "MOST_PRECISE" - - -# We don't support Python versions older than 3.6 anymore, so we can always -# depend on dictionary ordering. This is the default. -default_priority = Priority.DICT_KEY_ORDER - - -class Style(BaseStyle): - """ - Create a ``Style`` instance from a list of style rules. - - The `style_rules` is supposed to be a list of ('classnames', 'style') tuples. - The classnames are a whitespace separated string of class names and the - style string is just like a Pygments style definition, but with a few - additions: it supports 'reverse' and 'blink'. - - Later rules always override previous rules. - - Usage:: - - Style([ - ('title', '#ff0000 bold underline'), - ('something-else', 'reverse'), - ('class1 class2', 'reverse'), - ]) - - The ``from_dict`` classmethod is similar, but takes a dictionary as input. - """ - - def __init__(self, style_rules: List[Tuple[str, str]]) -> None: - class_names_and_attrs = [] - - # Loop through the rules in the order they were defined. - # Rules that are defined later get priority. - for class_names, style_str in style_rules: - assert CLASS_NAMES_RE.match(class_names), repr(class_names) - - # The order of the class names doesn't matter. - # (But the order of rules does matter.) - class_names_set = frozenset(class_names.lower().split()) - attrs = _parse_style_str(style_str) - - class_names_and_attrs.append((class_names_set, attrs)) - - self._style_rules = style_rules - self.class_names_and_attrs = class_names_and_attrs - - @property - 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": - """ - :param style_dict: Style dictionary. - :param priority: `Priority` value. - """ - if priority == Priority.MOST_PRECISE: - - def key(item: Tuple[str, str]) -> int: - # Split on '.' and whitespace. Count elements. - return sum(len(i.split(".")) for i in item[0].split()) - - return cls(sorted(style_dict.items(), key=key)) - else: - return cls(list(style_dict.items())) - - def get_attrs_for_style_str( - self, style_str: str, default: Attrs = DEFAULT_ATTRS - ) -> Attrs: - """ - Get `Attrs` for the given style string. - """ - list_of_attrs = [default] - class_names: Set[str] = set() - - # Apply default styling. - for names, attr in self.class_names_and_attrs: - if not names: - list_of_attrs.append(attr) - - # Go from left to right through the style string. Things on the right - # take precedence. - for part in style_str.split(): - # This part represents a class. - # Do lookup of this class name in the style definition, as well - # as all class combinations that we have so far. - if part.startswith("class:"): - # Expand all class names (comma separated list). - new_class_names = [] - for p in part[6:].lower().split(","): - new_class_names.extend(_expand_classname(p)) - - for new_name in new_class_names: - # Build a set of all possible class combinations to be applied. - combos = set() - combos.add(frozenset([new_name])) - - for count in range(1, len(class_names) + 1): - for c2 in itertools.combinations(class_names, count): - combos.add(frozenset(c2 + (new_name,))) - - # Apply the styles that match these class names. - for names, attr in self.class_names_and_attrs: - if names in combos: - list_of_attrs.append(attr) - - class_names.add(new_name) - - # Process inline style. - else: - inline_attrs = _parse_style_str(part) - list_of_attrs.append(inline_attrs) - - return _merge_attrs(list_of_attrs) - - def invalidation_hash(self) -> Hashable: - return id(self.class_names_and_attrs) - - -_T = TypeVar("_T") - - -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, - the last one has highest priority. - """ - - def _or(*values: _T) -> _T: - "Take first not-None value, starting at the end." - for v in values[::-1]: - if v is not None: - return v - raise ValueError # Should not happen, there's always one non-null value. - - return Attrs( - color=_or("", *[a.color for a in list_of_attrs]), - bgcolor=_or("", *[a.bgcolor for a in list_of_attrs]), - bold=_or(False, *[a.bold for a in list_of_attrs]), - underline=_or(False, *[a.underline for a in list_of_attrs]), - strike=_or(False, *[a.strike for a in list_of_attrs]), - italic=_or(False, *[a.italic for a in list_of_attrs]), - blink=_or(False, *[a.blink for a in list_of_attrs]), - reverse=_or(False, *[a.reverse for a in list_of_attrs]), - hidden=_or(False, *[a.hidden for a in list_of_attrs]), - ) - - -def merge_styles(styles: List[BaseStyle]) -> "_MergedStyle": - """ - Merge multiple `Style` objects. - """ - styles = [s for s in styles if s is not None] - return _MergedStyle(styles) - - -class _MergedStyle(BaseStyle): - """ - Merge multiple `Style` objects into one. - This is supposed to ensure consistency: if any of the given styles changes, - then this style will be updated. - """ - - # NOTE: previously, we used an algorithm where we did not generate the - # combined style. Instead this was a proxy that called one style - # after the other, passing the outcome of the previous style as the - # default for the next one. This did not work, because that way, the - # priorities like described in the `Style` class don't work. - # 'class:aborted' was for instance never displayed in gray, because - # 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: - self.styles = styles - self._style: SimpleCache[Hashable, Style] = SimpleCache(maxsize=1) - - @property - def _merged_style(self) -> Style: - "The `Style` object that has the other styles merged together." - - def get() -> Style: - return Style(self.style_rules) - - return self._style.get(self.invalidation_hash(), get) - - @property - def style_rules(self) -> List[Tuple[str, str]]: - style_rules = [] - for s in self.styles: - style_rules.extend(s.style_rules) - return style_rules - - def get_attrs_for_style_str( - self, style_str: str, default: Attrs = DEFAULT_ATTRS - ) -> Attrs: - return self._merged_style.get_attrs_for_style_str(style_str, default) - - def invalidation_hash(self) -> Hashable: - return tuple(s.invalidation_hash() for s in self.styles) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/styles/style_transformation.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/styles/style_transformation.py deleted file mode 100644 index 91308f91274..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/styles/style_transformation.py +++ /dev/null @@ -1,375 +0,0 @@ -""" -Collection of style transformations. - -Think of it as a kind of color post processing after the rendering is done. -This could be used for instance to change the contrast/saturation; swap light -and dark colors or even change certain colors for other colors. - -When the UI is rendered, these transformations can be applied right after the -style strings are turned into `Attrs` objects that represent the actual -formatting. -""" -from abc import ABCMeta, abstractmethod -from colorsys import hls_to_rgb, rgb_to_hls -from typing import Callable, Hashable, Optional, Sequence, Tuple, Union - -from prompt_toolkit.cache import memoized -from prompt_toolkit.filters import FilterOrBool, to_filter -from prompt_toolkit.utils import AnyFloat, to_float, to_str - -from .base import ANSI_COLOR_NAMES, Attrs -from .style import parse_color - -__all__ = [ - "StyleTransformation", - "SwapLightAndDarkStyleTransformation", - "ReverseStyleTransformation", - "SetDefaultColorStyleTransformation", - "AdjustBrightnessStyleTransformation", - "DummyStyleTransformation", - "ConditionalStyleTransformation", - "DynamicStyleTransformation", - "merge_style_transformations", -] - - -class StyleTransformation(metaclass=ABCMeta): - """ - Base class for any style transformation. - """ - - @abstractmethod - def transform_attrs(self, attrs: Attrs) -> Attrs: - """ - Take an `Attrs` object and return a new `Attrs` object. - - Remember that the color formats can be either "ansi..." or a 6 digit - lowercase hexadecimal color (without '#' prefix). - """ - - def invalidation_hash(self) -> Hashable: - """ - When this changes, the cache should be invalidated. - """ - return f"{self.__class__.__name__}-{id(self)}" - - -class SwapLightAndDarkStyleTransformation(StyleTransformation): - """ - Turn dark colors into light colors and the other way around. - - This is meant to make color schemes that work on a dark background usable - on a light background (and the other way around). - - Notice that this doesn't swap foreground and background like "reverse" - does. It turns light green into dark green and the other way around. - Foreground and background colors are considered individually. - - Also notice that when <reverse> is used somewhere and no colors are given - in particular (like what is the default for the bottom toolbar), then this - doesn't change anything. This is what makes sense, because when the - 'default' color is chosen, it's what works best for the terminal, and - reverse works good with that. - """ - - def transform_attrs(self, attrs: Attrs) -> Attrs: - """ - Return the `Attrs` used when opposite luminosity should be used. - """ - # Reverse colors. - attrs = attrs._replace(color=get_opposite_color(attrs.color)) - attrs = attrs._replace(bgcolor=get_opposite_color(attrs.bgcolor)) - - return attrs - - -class ReverseStyleTransformation(StyleTransformation): - """ - Swap the 'reverse' attribute. - - (This is still experimental.) - """ - - def transform_attrs(self, attrs: Attrs) -> Attrs: - return attrs._replace(reverse=not attrs.reverse) - - -class SetDefaultColorStyleTransformation(StyleTransformation): - """ - Set default foreground/background color for output that doesn't specify - anything. This is useful for overriding the terminal default colors. - - :param fg: Color string or callable that returns a color string for the - foreground. - :param bg: Like `fg`, but for the background. - """ - - def __init__( - self, fg: Union[str, Callable[[], str]], bg: Union[str, Callable[[], str]] - ) -> None: - - self.fg = fg - self.bg = bg - - def transform_attrs(self, attrs: Attrs) -> Attrs: - if attrs.bgcolor in ("", "default"): - attrs = attrs._replace(bgcolor=parse_color(to_str(self.bg))) - - if attrs.color in ("", "default"): - attrs = attrs._replace(color=parse_color(to_str(self.fg))) - - return attrs - - def invalidation_hash(self) -> Hashable: - return ( - "set-default-color", - to_str(self.fg), - to_str(self.bg), - ) - - -class AdjustBrightnessStyleTransformation(StyleTransformation): - """ - Adjust the brightness to improve the rendering on either dark or light - backgrounds. - - For dark backgrounds, it's best to increase `min_brightness`. For light - backgrounds it's best to decrease `max_brightness`. Usually, only one - setting is adjusted. - - This will only change the brightness for text that has a foreground color - defined, but no background color. It works best for 256 or true color - output. - - .. note:: Notice that there is no universal way to detect whether the - application is running in a light or dark terminal. As a - developer of an command line application, you'll have to make - this configurable for the user. - - :param min_brightness: Float between 0.0 and 1.0 or a callable that returns - a float. - :param max_brightness: Float between 0.0 and 1.0 or a callable that returns - a float. - """ - - def __init__( - self, min_brightness: AnyFloat = 0.0, max_brightness: AnyFloat = 1.0 - ) -> None: - - self.min_brightness = min_brightness - self.max_brightness = max_brightness - - def transform_attrs(self, attrs: Attrs) -> Attrs: - min_brightness = to_float(self.min_brightness) - max_brightness = to_float(self.max_brightness) - assert 0 <= min_brightness <= 1 - assert 0 <= max_brightness <= 1 - - # Don't do anything if the whole brightness range is acceptable. - # This also avoids turning ansi colors into RGB sequences. - if min_brightness == 0.0 and max_brightness == 1.0: - return attrs - - # If a foreground color is given without a background color. - no_background = not attrs.bgcolor or attrs.bgcolor == "default" - has_fgcolor = attrs.color and attrs.color != "ansidefault" - - if has_fgcolor and no_background: - # Calculate new RGB values. - r, g, b = self._color_to_rgb(attrs.color or "") - hue, brightness, saturation = rgb_to_hls(r, g, b) - brightness = self._interpolate_brightness( - brightness, min_brightness, max_brightness - ) - r, g, b = hls_to_rgb(hue, brightness, saturation) - new_color = f"{int(r * 255):02x}{int(g * 255):02x}{int(b * 255):02x}" - - attrs = attrs._replace(color=new_color) - - return attrs - - def _color_to_rgb(self, color: str) -> Tuple[float, float, float]: - """ - Parse `style.Attrs` color into RGB tuple. - """ - # Do RGB lookup for ANSI colors. - try: - from prompt_toolkit.output.vt100 import ANSI_COLORS_TO_RGB - - r, g, b = ANSI_COLORS_TO_RGB[color] - return r / 255.0, g / 255.0, b / 255.0 - except KeyError: - pass - - # Parse RRGGBB format. - return ( - int(color[0:2], 16) / 255.0, - int(color[2:4], 16) / 255.0, - int(color[4:6], 16) / 255.0, - ) - - # NOTE: we don't have to support named colors here. They are already - # transformed into RGB values in `style.parse_color`. - - def _interpolate_brightness( - self, value: float, min_brightness: float, max_brightness: float - ) -> float: - """ - Map the brightness to the (min_brightness..max_brightness) range. - """ - return min_brightness + (max_brightness - min_brightness) * value - - def invalidation_hash(self) -> Hashable: - return ( - "adjust-brightness", - to_float(self.min_brightness), - to_float(self.max_brightness), - ) - - -class DummyStyleTransformation(StyleTransformation): - """ - Don't transform anything at all. - """ - - def transform_attrs(self, attrs: Attrs) -> Attrs: - return attrs - - def invalidation_hash(self) -> Hashable: - # Always return the same hash for these dummy instances. - return "dummy-style-transformation" - - -class DynamicStyleTransformation(StyleTransformation): - """ - StyleTransformation class that can dynamically returns any - `StyleTransformation`. - - :param get_style_transformation: Callable that returns a - :class:`.StyleTransformation` instance. - """ - - def __init__( - self, get_style_transformation: Callable[[], Optional[StyleTransformation]] - ) -> None: - - self.get_style_transformation = get_style_transformation - - def transform_attrs(self, attrs: Attrs) -> Attrs: - style_transformation = ( - self.get_style_transformation() or DummyStyleTransformation() - ) - return style_transformation.transform_attrs(attrs) - - def invalidation_hash(self) -> Hashable: - style_transformation = ( - self.get_style_transformation() or DummyStyleTransformation() - ) - return style_transformation.invalidation_hash() - - -class ConditionalStyleTransformation(StyleTransformation): - """ - Apply the style transformation depending on a condition. - """ - - def __init__( - self, style_transformation: StyleTransformation, filter: FilterOrBool - ) -> None: - - self.style_transformation = style_transformation - self.filter = to_filter(filter) - - def transform_attrs(self, attrs: Attrs) -> Attrs: - if self.filter(): - return self.style_transformation.transform_attrs(attrs) - return attrs - - def invalidation_hash(self) -> Hashable: - return (self.filter(), self.style_transformation.invalidation_hash()) - - -class _MergedStyleTransformation(StyleTransformation): - def __init__(self, style_transformations: Sequence[StyleTransformation]) -> None: - self.style_transformations = style_transformations - - def transform_attrs(self, attrs: Attrs) -> Attrs: - for transformation in self.style_transformations: - attrs = transformation.transform_attrs(attrs) - return attrs - - def invalidation_hash(self) -> Hashable: - return tuple(t.invalidation_hash() for t in self.style_transformations) - - -def merge_style_transformations( - style_transformations: Sequence[StyleTransformation], -) -> StyleTransformation: - """ - Merge multiple transformations together. - """ - return _MergedStyleTransformation(style_transformations) - - -# Dictionary that maps ANSI color names to their opposite. This is useful for -# turning color schemes that are optimized for a black background usable for a -# white background. -OPPOSITE_ANSI_COLOR_NAMES = { - "ansidefault": "ansidefault", - "ansiblack": "ansiwhite", - "ansired": "ansibrightred", - "ansigreen": "ansibrightgreen", - "ansiyellow": "ansibrightyellow", - "ansiblue": "ansibrightblue", - "ansimagenta": "ansibrightmagenta", - "ansicyan": "ansibrightcyan", - "ansigray": "ansibrightblack", - "ansiwhite": "ansiblack", - "ansibrightred": "ansired", - "ansibrightgreen": "ansigreen", - "ansibrightyellow": "ansiyellow", - "ansibrightblue": "ansiblue", - "ansibrightmagenta": "ansimagenta", - "ansibrightcyan": "ansicyan", - "ansibrightblack": "ansigray", -} -assert set(OPPOSITE_ANSI_COLOR_NAMES.keys()) == set(ANSI_COLOR_NAMES) -assert set(OPPOSITE_ANSI_COLOR_NAMES.values()) == set(ANSI_COLOR_NAMES) - - -@memoized() -def get_opposite_color(colorname: Optional[str]) -> Optional[str]: - """ - Take a color name in either 'ansi...' format or 6 digit RGB, return the - color of opposite luminosity (same hue/saturation). - - This is used for turning color schemes that work on a light background - usable on a dark background. - """ - if colorname is None: # Because color/bgcolor can be None in `Attrs`. - return None - - # Special values. - if colorname in ("", "default"): - return colorname - - # Try ANSI color names. - try: - return OPPOSITE_ANSI_COLOR_NAMES[colorname] - except KeyError: - # Try 6 digit RGB colors. - r = int(colorname[:2], 16) / 255.0 - g = int(colorname[2:4], 16) / 255.0 - b = int(colorname[4:6], 16) / 255.0 - - h, l, s = rgb_to_hls(r, g, b) - - l = 1 - l - - r, g, b = hls_to_rgb(h, l, s) - - r = int(r * 255) - g = int(g * 255) - b = int(b * 255) - - return f"{r:02x}{g:02x}{b:02x}" diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/token.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/token.py deleted file mode 100644 index 76fd9c46cd7..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/token.py +++ /dev/null @@ -1,8 +0,0 @@ -""" -""" - -__all__ = [ - "ZeroWidthEscape", -] - -ZeroWidthEscape = "[ZeroWidthEscape]" diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/utils.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/utils.py deleted file mode 100644 index 4ceded34c41..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/utils.py +++ /dev/null @@ -1,328 +0,0 @@ -import os -import signal -import sys -import threading -from collections import deque -from typing import ( - Callable, - ContextManager, - Deque, - Dict, - Generator, - Generic, - List, - Optional, - TypeVar, - Union, -) - -from wcwidth import wcwidth - -__all__ = [ - "Event", - "DummyContext", - "get_cwidth", - "suspend_to_background_supported", - "is_conemu_ansi", - "is_windows", - "in_main_thread", - "get_bell_environment_variable", - "get_term_environment_variable", - "take_using_weights", - "to_str", - "to_int", - "AnyFloat", - "to_float", - "is_dumb_terminal", -] - -# Used to ensure sphinx autodoc does not try to import platform-specific -# stuff when documenting win32.py modules. -SPHINX_AUTODOC_RUNNING = "sphinx.ext.autodoc" in sys.modules - -_Sender = TypeVar("_Sender", covariant=True) - - -class Event(Generic[_Sender]): - """ - Simple event to which event handlers can be attached. For instance:: - - class Cls: - def __init__(self): - # Define event. The first parameter is the sender. - self.event = Event(self) - - obj = Cls() - - def handler(sender): - pass - - # Add event handler by using the += operator. - obj.event += handler - - # Fire event. - obj.event() - """ - - def __init__( - self, sender: _Sender, handler: Optional[Callable[[_Sender], None]] = None - ) -> None: - self.sender = sender - self._handlers: List[Callable[[_Sender], None]] = [] - - if handler is not None: - self += handler - - def __call__(self) -> None: - "Fire event." - for handler in self._handlers: - handler(self.sender) - - def fire(self) -> None: - "Alias for just calling the event." - self() - - def add_handler(self, handler: Callable[[_Sender], None]) -> None: - """ - Add another handler to this callback. - (Handler should be a callable that takes exactly one parameter: the - sender object.) - """ - # Add to list of event handlers. - self._handlers.append(handler) - - def remove_handler(self, handler: Callable[[_Sender], None]) -> None: - """ - Remove a handler from this callback. - """ - if handler in self._handlers: - self._handlers.remove(handler) - - 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]": - """ - `event -= handler` notation for removing a handler. - """ - self.remove_handler(handler) - return self - - -class DummyContext(ContextManager[None]): - """ - (contextlib.nested is not available on Py3) - """ - - def __enter__(self) -> None: - pass - - def __exit__(self, *a: object) -> None: - pass - - -class _CharSizesCache(Dict[str, int]): - """ - Cache for wcwidth sizes. - """ - - LONG_STRING_MIN_LEN = 64 # Minimum string length for considering it long. - MAX_LONG_STRINGS = 16 # Maximum number of long strings to remember. - - def __init__(self) -> None: - super().__init__() - # Keep track of the "long" strings in this cache. - self._long_strings: Deque[str] = deque() - - def __missing__(self, string: str) -> int: - # Note: We use the `max(0, ...` because some non printable control - # characters, like e.g. Ctrl-underscore get a -1 wcwidth value. - # It can be possible that these characters end up in the input - # text. - result: int - if len(string) == 1: - result = max(0, wcwidth(string)) - else: - result = sum(self[c] for c in string) - - # Store in cache. - self[string] = result - - # Rotate long strings. - # (It's hard to tell what we can consider short...) - if len(string) > self.LONG_STRING_MIN_LEN: - long_strings = self._long_strings - long_strings.append(string) - - if len(long_strings) > self.MAX_LONG_STRINGS: - key_to_remove = long_strings.popleft() - if key_to_remove in self: - del self[key_to_remove] - - return result - - -_CHAR_SIZES_CACHE = _CharSizesCache() - - -def get_cwidth(string: str) -> int: - """ - Return width of a string. Wrapper around ``wcwidth``. - """ - return _CHAR_SIZES_CACHE[string] - - -def suspend_to_background_supported() -> bool: - """ - Returns `True` when the Python implementation supports - suspend-to-background. This is typically `False' on Windows systems. - """ - return hasattr(signal, "SIGTSTP") - - -def is_windows() -> bool: - """ - True when we are using Windows. - """ - return sys.platform == "win32" # Not 'darwin' or 'linux2' - - -def is_windows_vt100_supported() -> bool: - """ - True when we are using Windows, but VT100 escape sequences are supported. - """ - if sys.platform == "win32": - # Import needs to be inline. Windows libraries are not always available. - from prompt_toolkit.output.windows10 import is_win_vt100_enabled - - return is_win_vt100_enabled() - - return False - - -def is_conemu_ansi() -> bool: - """ - True when the ConEmu Windows console is used. - """ - return sys.platform == "win32" and os.environ.get("ConEmuANSI", "OFF") == "ON" - - -def in_main_thread() -> bool: - """ - True when the current thread is the main thread. - """ - return threading.current_thread().__class__.__name__ == "_MainThread" - - -def get_bell_environment_variable() -> bool: - """ - True if env variable is set to true (true, TRUE, TrUe, 1). - """ - value = os.environ.get("PROMPT_TOOLKIT_BELL", "true") - return value.lower() in ("1", "true") - - -def get_term_environment_variable() -> str: - "Return the $TERM environment variable." - return os.environ.get("TERM", "") - - -_T = TypeVar("_T") - - -def take_using_weights( - items: List[_T], weights: List[int] -) -> Generator[_T, None, None]: - """ - Generator that keeps yielding items from the items list, in proportion to - their weight. For instance:: - - # Getting the first 70 items from this generator should have yielded 10 - # times A, 20 times B and 40 times C, all distributed equally.. - take_using_weights(['A', 'B', 'C'], [5, 10, 20]) - - :param items: List of items to take from. - :param weights: Integers representing the weight. (Numbers have to be - integers, not floats.) - """ - assert len(items) == len(weights) - assert len(items) > 0 - - # Remove items with zero-weight. - items2 = [] - weights2 = [] - for item, w in zip(items, weights): - if w > 0: - items2.append(item) - weights2.append(w) - - items = items2 - weights = weights2 - - # Make sure that we have some items left. - if not items: - raise ValueError("Did't got any items with a positive weight.") - - # - already_taken = [0 for i in items] - item_count = len(items) - max_weight = max(weights) - - i = 0 - while True: - # Each iteration of this loop, we fill up until by (total_weight/max_weight). - adding = True - while adding: - adding = False - - for item_i, item, weight in zip(range(item_count), items, weights): - if already_taken[item_i] < i * weight / float(max_weight): - yield item - already_taken[item_i] += 1 - adding = True - - i += 1 - - -def to_str(value: Union[Callable[[], str], str]) -> str: - "Turn callable or string into string." - if callable(value): - return to_str(value()) - else: - return str(value) - - -def to_int(value: Union[Callable[[], int], int]) -> int: - "Turn callable or int into int." - if callable(value): - return to_int(value()) - else: - return int(value) - - -AnyFloat = Union[Callable[[], float], float] - - -def to_float(value: AnyFloat) -> float: - "Turn callable or float into float." - if callable(value): - return to_float(value()) - else: - return float(value) - - -def is_dumb_terminal(term: Optional[str] = None) -> bool: - """ - True if this terminal type is considered "dumb". - - If so, we should fall back to the simplest possible form of line editing, - without cursor positioning and color support. - """ - if term is None: - return is_dumb_terminal(os.environ.get("TERM", "")) - - return term.lower() in ["dumb", "unknown"] diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/validation.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/validation.py deleted file mode 100644 index 8bdffff524d..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/validation.py +++ /dev/null @@ -1,194 +0,0 @@ -""" -Input validation for a `Buffer`. -(Validators will be called before accepting input.) -""" -from abc import ABCMeta, abstractmethod -from typing import Callable, Optional - -from prompt_toolkit.eventloop import run_in_executor_with_context - -from .document import Document -from .filters import FilterOrBool, to_filter - -__all__ = [ - "ConditionalValidator", - "ValidationError", - "Validator", - "ThreadedValidator", - "DummyValidator", - "DynamicValidator", -] - - -class ValidationError(Exception): - """ - Error raised by :meth:`.Validator.validate`. - - :param cursor_position: The cursor position where the error occurred. - :param message: Text. - """ - - def __init__(self, cursor_position: int = 0, message: str = "") -> None: - super().__init__(message) - self.cursor_position = cursor_position - self.message = message - - def __repr__(self) -> str: - return "{}(cursor_position={!r}, message={!r})".format( - self.__class__.__name__, - self.cursor_position, - self.message, - ) - - -class Validator(metaclass=ABCMeta): - """ - Abstract base class for an input validator. - - A validator is typically created in one of the following two ways: - - - Either by overriding this class and implementing the `validate` method. - - Or by passing a callable to `Validator.from_callable`. - - If the validation takes some time and needs to happen in a background - thread, this can be wrapped in a :class:`.ThreadedValidator`. - """ - - @abstractmethod - def validate(self, document: Document) -> None: - """ - Validate the input. - If invalid, this should raise a :class:`.ValidationError`. - - :param document: :class:`~prompt_toolkit.document.Document` instance. - """ - pass - - async def validate_async(self, document: Document) -> None: - """ - Return a `Future` which is set when the validation is ready. - This function can be overloaded in order to provide an asynchronous - implementation. - """ - try: - self.validate(document) - except ValidationError: - raise - - @classmethod - def from_callable( - cls, - validate_func: Callable[[str], bool], - error_message: str = "Invalid input", - move_cursor_to_end: bool = False, - ) -> "Validator": - """ - Create a validator from a simple validate callable. E.g.: - - .. code:: python - - def is_valid(text): - return text in ['hello', 'world'] - Validator.from_callable(is_valid, error_message='Invalid input') - - :param validate_func: Callable that takes the input string, and returns - `True` if the input is valid input. - :param error_message: Message to be displayed if the input is invalid. - :param move_cursor_to_end: Move the cursor to the end of the input, if - the input is invalid. - """ - return _ValidatorFromCallable(validate_func, error_message, move_cursor_to_end) - - -class _ValidatorFromCallable(Validator): - """ - Validate input from a simple callable. - """ - - def __init__( - self, func: Callable[[str], bool], error_message: str, move_cursor_to_end: bool - ) -> None: - - self.func = func - self.error_message = error_message - self.move_cursor_to_end = move_cursor_to_end - - def __repr__(self) -> str: - return f"Validator.from_callable({self.func!r})" - - def validate(self, document: Document) -> None: - if not self.func(document.text): - if self.move_cursor_to_end: - index = len(document.text) - else: - index = 0 - - raise ValidationError(cursor_position=index, message=self.error_message) - - -class ThreadedValidator(Validator): - """ - Wrapper that runs input validation in a thread. - (Use this to prevent the user interface from becoming unresponsive if the - input validation takes too much time.) - """ - - def __init__(self, validator: Validator) -> None: - self.validator = validator - - def validate(self, document: Document) -> None: - self.validator.validate(document) - - async def validate_async(self, document: Document) -> None: - """ - Run the `validate` function in a thread. - """ - - def run_validation_thread() -> None: - return self.validate(document) - - await run_in_executor_with_context(run_validation_thread) - - -class DummyValidator(Validator): - """ - Validator class that accepts any input. - """ - - def validate(self, document: Document) -> None: - pass # Don't raise any exception. - - -class ConditionalValidator(Validator): - """ - Validator that can be switched on/off according to - a filter. (This wraps around another validator.) - """ - - def __init__(self, validator: Validator, filter: FilterOrBool) -> None: - self.validator = validator - self.filter = to_filter(filter) - - def validate(self, document: Document) -> None: - # Call the validator only if the filter is active. - if self.filter(): - self.validator.validate(document) - - -class DynamicValidator(Validator): - """ - Validator class that can dynamically returns any Validator. - - :param get_validator: Callable that returns a :class:`.Validator` instance. - """ - - def __init__(self, get_validator: Callable[[], Optional[Validator]]) -> None: - self.get_validator = get_validator - - def validate(self, document: Document) -> None: - validator = self.get_validator() or DummyValidator() - validator.validate(document) - - async def validate_async(self, document: Document) -> None: - validator = self.get_validator() or DummyValidator() - await validator.validate_async(document) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/widgets/__init__.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/widgets/__init__.py deleted file mode 100644 index 552d3559488..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/widgets/__init__.py +++ /dev/null @@ -1,60 +0,0 @@ -""" -Collection of reusable components for building full screen applications. -These are higher level abstractions on top of the `prompt_toolkit.layout` -module. - -Most of these widgets implement the ``__pt_container__`` method, which makes it -possible to embed these in the layout like any other container. -""" -from .base import ( - Box, - Button, - Checkbox, - CheckboxList, - Frame, - HorizontalLine, - Label, - ProgressBar, - RadioList, - Shadow, - TextArea, - VerticalLine, -) -from .dialogs import Dialog -from .menus import MenuContainer, MenuItem -from .toolbars import ( - ArgToolbar, - CompletionsToolbar, - FormattedTextToolbar, - SearchToolbar, - SystemToolbar, - ValidationToolbar, -) - -__all__ = [ - # Base. - "TextArea", - "Label", - "Button", - "Frame", - "Shadow", - "Box", - "VerticalLine", - "HorizontalLine", - "CheckboxList", - "RadioList", - "Checkbox", - "ProgressBar", - # Toolbars. - "ArgToolbar", - "CompletionsToolbar", - "FormattedTextToolbar", - "SearchToolbar", - "SystemToolbar", - "ValidationToolbar", - # Dialogs. - "Dialog", - # Menus. - "MenuContainer", - "MenuItem", -] diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/widgets/base.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/widgets/base.py deleted file mode 100644 index bd2d3322094..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/widgets/base.py +++ /dev/null @@ -1,982 +0,0 @@ -""" -Collection of reusable components for building full screen applications. - -All of these widgets implement the ``__pt_container__`` method, which makes -them usable in any situation where we are expecting a `prompt_toolkit` -container object. - -.. warning:: - - At this point, the API for these widgets is considered unstable, and can - potentially change between minor releases (we try not too, but no - guarantees are made yet). The public API in - `prompt_toolkit.shortcuts.dialogs` on the other hand is considered stable. -""" -from functools import partial -from typing import Callable, Generic, List, Optional, Sequence, Tuple, TypeVar, Union - -from prompt_toolkit.application.current import get_app -from prompt_toolkit.auto_suggest import AutoSuggest, DynamicAutoSuggest -from prompt_toolkit.buffer import Buffer, BufferAcceptHandler -from prompt_toolkit.completion import Completer, DynamicCompleter -from prompt_toolkit.document import Document -from prompt_toolkit.filters import ( - Condition, - FilterOrBool, - has_focus, - is_done, - is_true, - to_filter, -) -from prompt_toolkit.formatted_text import ( - AnyFormattedText, - StyleAndTextTuples, - Template, - to_formatted_text, -) -from prompt_toolkit.formatted_text.utils import fragment_list_to_text -from prompt_toolkit.history import History -from prompt_toolkit.key_binding.key_bindings import KeyBindings -from prompt_toolkit.key_binding.key_processor import KeyPressEvent -from prompt_toolkit.keys import Keys -from prompt_toolkit.layout.containers import ( - AnyContainer, - ConditionalContainer, - Container, - DynamicContainer, - Float, - FloatContainer, - HSplit, - VSplit, - Window, - WindowAlign, -) -from prompt_toolkit.layout.controls import ( - BufferControl, - FormattedTextControl, - GetLinePrefixCallable, -) -from prompt_toolkit.layout.dimension import AnyDimension -from prompt_toolkit.layout.dimension import Dimension as D -from prompt_toolkit.layout.dimension import to_dimension -from prompt_toolkit.layout.margins import ( - ConditionalMargin, - NumberedMargin, - ScrollbarMargin, -) -from prompt_toolkit.layout.processors import ( - AppendAutoSuggestion, - BeforeInput, - ConditionalProcessor, - PasswordProcessor, - Processor, -) -from prompt_toolkit.lexers import DynamicLexer, Lexer -from prompt_toolkit.mouse_events import MouseEvent, MouseEventType -from prompt_toolkit.utils import get_cwidth -from prompt_toolkit.validation import DynamicValidator, Validator - -from .toolbars import SearchToolbar - -__all__ = [ - "TextArea", - "Label", - "Button", - "Frame", - "Shadow", - "Box", - "VerticalLine", - "HorizontalLine", - "RadioList", - "CheckboxList", - "Checkbox", # backward compatibility - "ProgressBar", -] - -E = KeyPressEvent - - -class Border: - "Box drawing characters. (Thin)" - HORIZONTAL = "\u2500" - VERTICAL = "\u2502" - TOP_LEFT = "\u250c" - TOP_RIGHT = "\u2510" - BOTTOM_LEFT = "\u2514" - BOTTOM_RIGHT = "\u2518" - - -class TextArea: - """ - A simple input field. - - This is a higher level abstraction on top of several other classes with - sane defaults. - - This widget does have the most common options, but it does not intend to - cover every single use case. For more configurations options, you can - always build a text area manually, using a - :class:`~prompt_toolkit.buffer.Buffer`, - :class:`~prompt_toolkit.layout.BufferControl` and - :class:`~prompt_toolkit.layout.Window`. - - Buffer attributes: - - :param text: The initial text. - :param multiline: If True, allow multiline input. - :param completer: :class:`~prompt_toolkit.completion.Completer` instance - for auto completion. - :param complete_while_typing: Boolean. - :param accept_handler: Called when `Enter` is pressed (This should be a - callable that takes a buffer as input). - :param history: :class:`~prompt_toolkit.history.History` instance. - :param auto_suggest: :class:`~prompt_toolkit.auto_suggest.AutoSuggest` - instance for input suggestions. - - BufferControl attributes: - - :param password: When `True`, display using asterisks. - :param focusable: When `True`, allow this widget to receive the focus. - :param focus_on_click: When `True`, focus after mouse click. - :param input_processors: `None` or a list of - :class:`~prompt_toolkit.layout.Processor` objects. - :param validator: `None` or a :class:`~prompt_toolkit.validation.Validator` - object. - - Window attributes: - - :param lexer: :class:`~prompt_toolkit.lexers.Lexer` instance for syntax - highlighting. - :param wrap_lines: When `True`, don't scroll horizontally, but wrap lines. - :param width: Window width. (:class:`~prompt_toolkit.layout.Dimension` object.) - :param height: Window height. (:class:`~prompt_toolkit.layout.Dimension` object.) - :param scrollbar: When `True`, display a scroll bar. - :param style: A style string. - :param dont_extend_width: When `True`, don't take up more width then the - preferred width reported by the control. - :param dont_extend_height: When `True`, don't take up more width then the - preferred height reported by the control. - :param get_line_prefix: None or a callable that returns formatted text to - be inserted before a line. It takes a line number (int) and a - wrap_count and returns formatted text. This can be used for - implementation of line continuations, things like Vim "breakindent" and - so on. - - Other attributes: - - :param search_field: An optional `SearchToolbar` object. - """ - - def __init__( - self, - text: str = "", - multiline: FilterOrBool = True, - password: FilterOrBool = False, - lexer: Optional[Lexer] = None, - auto_suggest: Optional[AutoSuggest] = None, - completer: Optional[Completer] = None, - complete_while_typing: FilterOrBool = True, - validator: Optional[Validator] = None, - accept_handler: Optional[BufferAcceptHandler] = None, - history: Optional[History] = None, - focusable: FilterOrBool = True, - focus_on_click: FilterOrBool = False, - wrap_lines: FilterOrBool = True, - read_only: FilterOrBool = False, - width: AnyDimension = None, - height: AnyDimension = None, - dont_extend_height: FilterOrBool = False, - dont_extend_width: FilterOrBool = False, - line_numbers: bool = False, - get_line_prefix: Optional[GetLinePrefixCallable] = None, - scrollbar: bool = False, - style: str = "", - search_field: Optional[SearchToolbar] = None, - preview_search: FilterOrBool = True, - prompt: AnyFormattedText = "", - input_processors: Optional[List[Processor]] = None, - ) -> None: - - if search_field is None: - search_control = None - elif isinstance(search_field, SearchToolbar): - search_control = search_field.control - - if input_processors is None: - input_processors = [] - - # Writeable attributes. - self.completer = completer - self.complete_while_typing = complete_while_typing - self.lexer = lexer - self.auto_suggest = auto_suggest - self.read_only = read_only - self.wrap_lines = wrap_lines - self.validator = validator - - self.buffer = Buffer( - document=Document(text, 0), - multiline=multiline, - read_only=Condition(lambda: is_true(self.read_only)), - completer=DynamicCompleter(lambda: self.completer), - complete_while_typing=Condition( - lambda: is_true(self.complete_while_typing) - ), - validator=DynamicValidator(lambda: self.validator), - auto_suggest=DynamicAutoSuggest(lambda: self.auto_suggest), - accept_handler=accept_handler, - history=history, - ) - - self.control = BufferControl( - buffer=self.buffer, - lexer=DynamicLexer(lambda: self.lexer), - input_processors=[ - ConditionalProcessor( - AppendAutoSuggestion(), has_focus(self.buffer) & ~is_done - ), - ConditionalProcessor( - processor=PasswordProcessor(), filter=to_filter(password) - ), - BeforeInput(prompt, style="class:text-area.prompt"), - ] - + input_processors, - search_buffer_control=search_control, - preview_search=preview_search, - focusable=focusable, - focus_on_click=focus_on_click, - ) - - if multiline: - if scrollbar: - right_margins = [ScrollbarMargin(display_arrows=True)] - else: - right_margins = [] - if line_numbers: - left_margins = [NumberedMargin()] - else: - left_margins = [] - else: - height = D.exact(1) - left_margins = [] - right_margins = [] - - style = "class:text-area " + style - - # If no height was given, guarantee height of at least 1. - if height is None: - height = D(min=1) - - self.window = Window( - height=height, - width=width, - dont_extend_height=dont_extend_height, - dont_extend_width=dont_extend_width, - content=self.control, - style=style, - wrap_lines=Condition(lambda: is_true(self.wrap_lines)), - left_margins=left_margins, - right_margins=right_margins, - get_line_prefix=get_line_prefix, - ) - - @property - def text(self) -> str: - """ - The `Buffer` text. - """ - return self.buffer.text - - @text.setter - def text(self, value: str) -> None: - self.document = Document(value, 0) - - @property - def document(self) -> Document: - """ - The `Buffer` document (text + cursor position). - """ - return self.buffer.document - - @document.setter - def document(self, value: Document) -> None: - self.buffer.set_document(value, bypass_readonly=True) - - @property - def accept_handler(self) -> Optional[BufferAcceptHandler]: - """ - The accept handler. Called when the user accepts the input. - """ - return self.buffer.accept_handler - - @accept_handler.setter - def accept_handler(self, value: BufferAcceptHandler) -> None: - self.buffer.accept_handler = value - - def __pt_container__(self) -> Container: - return self.window - - -class Label: - """ - Widget that displays the given text. It is not editable or focusable. - - :param text: Text to display. Can be multiline. All value types accepted by - :class:`prompt_toolkit.layout.FormattedTextControl` are allowed, - including a callable. - :param style: A style string. - :param width: When given, use this width, rather than calculating it from - the text size. - :param dont_extend_width: When `True`, don't take up more width than - preferred, i.e. the length of the longest line of - the text, or value of `width` parameter, if - given. `True` by default - :param dont_extend_height: When `True`, don't take up more width than the - preferred height, i.e. the number of lines of - the text. `False` by default. - """ - - def __init__( - self, - text: AnyFormattedText, - style: str = "", - width: AnyDimension = None, - dont_extend_height: bool = True, - dont_extend_width: bool = False, - align: Union[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, - ) -> None: - - self.text = text - - def get_width() -> AnyDimension: - if width is None: - text_fragments = to_formatted_text(self.text) - text = fragment_list_to_text(text_fragments) - if text: - longest_line = max(get_cwidth(line) for line in text.splitlines()) - else: - return D(preferred=0) - return D(preferred=longest_line) - else: - return width - - self.formatted_text_control = FormattedTextControl(text=lambda: self.text) - - self.window = Window( - content=self.formatted_text_control, - width=get_width, - height=D(min=1), - style="class:label " + style, - dont_extend_height=dont_extend_height, - dont_extend_width=dont_extend_width, - align=align, - wrap_lines=wrap_lines, - ) - - def __pt_container__(self) -> Container: - return self.window - - -class Button: - """ - Clickable button. - - :param text: The caption for the button. - :param handler: `None` or callable. Called when the button is clicked. No - parameters are passed to this callable. Use for instance Python's - `functools.partial` to pass parameters to this callable if needed. - :param width: Width of the button. - """ - - def __init__( - self, - text: str, - handler: Optional[Callable[[], None]] = None, - width: int = 12, - left_symbol: str = "<", - right_symbol: str = ">", - ) -> None: - - self.text = text - self.left_symbol = left_symbol - self.right_symbol = right_symbol - self.handler = handler - self.width = width - self.control = FormattedTextControl( - self._get_text_fragments, - key_bindings=self._get_key_bindings(), - focusable=True, - ) - - def get_style() -> str: - if get_app().layout.has_focus(self): - return "class:button.focused" - else: - return "class:button" - - # Note: `dont_extend_width` is False, because we want to allow buttons - # to take more space if the parent container provides more space. - # Otherwise, we will also truncate the text. - # Probably we need a better way here to adjust to width of the - # button to the text. - - self.window = Window( - self.control, - align=WindowAlign.CENTER, - height=1, - width=width, - style=get_style, - dont_extend_width=False, - dont_extend_height=True, - ) - - def _get_text_fragments(self) -> StyleAndTextTuples: - width = self.width - ( - get_cwidth(self.left_symbol) + get_cwidth(self.right_symbol) - ) - text = (f"{{:^{width}}}").format(self.text) - - def handler(mouse_event: MouseEvent) -> None: - if ( - self.handler is not None - and mouse_event.event_type == MouseEventType.MOUSE_UP - ): - self.handler() - - return [ - ("class:button.arrow", self.left_symbol, handler), - ("[SetCursorPosition]", ""), - ("class:button.text", text, handler), - ("class:button.arrow", self.right_symbol, handler), - ] - - def _get_key_bindings(self) -> KeyBindings: - "Key bindings for the Button." - kb = KeyBindings() - - @kb.add(" ") - @kb.add("enter") - def _(event: E) -> None: - if self.handler is not None: - self.handler() - - return kb - - def __pt_container__(self) -> Container: - return self.window - - -class Frame: - """ - Draw a border around any container, optionally with a title text. - - Changing the title and body of the frame is possible at runtime by - assigning to the `body` and `title` attributes of this class. - - :param body: Another container object. - :param title: Text to be displayed in the top of the frame (can be formatted text). - :param style: Style string to be applied to this widget. - """ - - def __init__( - self, - body: AnyContainer, - title: AnyFormattedText = "", - style: str = "", - width: AnyDimension = None, - height: AnyDimension = None, - key_bindings: Optional[KeyBindings] = None, - modal: bool = False, - ) -> None: - - self.title = title - self.body = body - - fill = partial(Window, style="class:frame.border") - style = "class:frame " + style - - top_row_with_title = VSplit( - [ - fill(width=1, height=1, char=Border.TOP_LEFT), - fill(char=Border.HORIZONTAL), - fill(width=1, height=1, char="|"), - # Notice: we use `Template` here, because `self.title` can be an - # `HTML` object for instance. - Label( - lambda: Template(" {} ").format(self.title), - style="class:frame.label", - dont_extend_width=True, - ), - fill(width=1, height=1, char="|"), - fill(char=Border.HORIZONTAL), - fill(width=1, height=1, char=Border.TOP_RIGHT), - ], - height=1, - ) - - top_row_without_title = VSplit( - [ - fill(width=1, height=1, char=Border.TOP_LEFT), - fill(char=Border.HORIZONTAL), - fill(width=1, height=1, char=Border.TOP_RIGHT), - ], - height=1, - ) - - @Condition - def has_title() -> bool: - return bool(self.title) - - self.container = HSplit( - [ - ConditionalContainer(content=top_row_with_title, filter=has_title), - ConditionalContainer(content=top_row_without_title, filter=~has_title), - VSplit( - [ - fill(width=1, char=Border.VERTICAL), - DynamicContainer(lambda: self.body), - fill(width=1, char=Border.VERTICAL), - # Padding is required to make sure that if the content is - # too small, the right frame border is still aligned. - ], - padding=0, - ), - VSplit( - [ - fill(width=1, height=1, char=Border.BOTTOM_LEFT), - fill(char=Border.HORIZONTAL), - fill(width=1, height=1, char=Border.BOTTOM_RIGHT), - ], - # specifying height here will increase the rendering speed. - height=1, - ), - ], - width=width, - height=height, - style=style, - key_bindings=key_bindings, - modal=modal, - ) - - def __pt_container__(self) -> Container: - return self.container - - -class Shadow: - """ - Draw a shadow underneath/behind this container. - (This applies `class:shadow` the the cells under the shadow. The Style - should define the colors for the shadow.) - - :param body: Another container object. - """ - - def __init__(self, body: AnyContainer) -> None: - self.container = FloatContainer( - content=body, - floats=[ - Float( - bottom=-1, - height=1, - left=1, - right=-1, - transparent=True, - content=Window(style="class:shadow"), - ), - Float( - bottom=-1, - top=1, - width=1, - right=-1, - transparent=True, - content=Window(style="class:shadow"), - ), - ], - ) - - def __pt_container__(self) -> Container: - return self.container - - -class Box: - """ - Add padding around a container. - - This also makes sure that the parent can provide more space than required by - the child. This is very useful when wrapping a small element with a fixed - size into a ``VSplit`` or ``HSplit`` object. The ``HSplit`` and ``VSplit`` - try to make sure to adapt respectively the width and height, possibly - shrinking other elements. Wrapping something in a ``Box`` makes it flexible. - - :param body: Another container object. - :param padding: The margin to be used around the body. This can be - overridden by `padding_left`, padding_right`, `padding_top` and - `padding_bottom`. - :param style: A style string. - :param char: Character to be used for filling the space around the body. - (This is supposed to be a character with a terminal width of 1.) - """ - - def __init__( - self, - body: AnyContainer, - padding: AnyDimension = None, - padding_left: AnyDimension = None, - padding_right: AnyDimension = None, - padding_top: AnyDimension = None, - padding_bottom: AnyDimension = None, - width: AnyDimension = None, - height: AnyDimension = None, - style: str = "", - char: Union[None, str, Callable[[], str]] = None, - modal: bool = False, - key_bindings: Optional[KeyBindings] = None, - ) -> None: - - if padding is None: - padding = D(preferred=0) - - def get(value: AnyDimension) -> D: - if value is None: - value = padding - return to_dimension(value) - - self.padding_left = get(padding_left) - self.padding_right = get(padding_right) - self.padding_top = get(padding_top) - self.padding_bottom = get(padding_bottom) - self.body = body - - self.container = HSplit( - [ - Window(height=self.padding_top, char=char), - VSplit( - [ - Window(width=self.padding_left, char=char), - body, - Window(width=self.padding_right, char=char), - ] - ), - Window(height=self.padding_bottom, char=char), - ], - width=width, - height=height, - style=style, - modal=modal, - key_bindings=None, - ) - - def __pt_container__(self) -> Container: - return self.container - - -_T = TypeVar("_T") - - -class _DialogList(Generic[_T]): - """ - Common code for `RadioList` and `CheckboxList`. - """ - - open_character: str = "" - close_character: str = "" - container_style: str = "" - default_style: str = "" - selected_style: str = "" - checked_style: str = "" - multiple_selection: bool = False - show_scrollbar: bool = True - - def __init__( - self, - values: Sequence[Tuple[_T, AnyFormattedText]], - default_values: Optional[Sequence[_T]] = None, - ) -> None: - assert len(values) > 0 - default_values = default_values or [] - - 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] = [ - value for value in default_values if value in keys - ] - self.current_value: _T = ( - default_values[0] - if len(default_values) and default_values[0] in keys - else values[0][0] - ) - - # Cursor index: take first selected item or first item otherwise. - if len(self.current_values) > 0: - self._selected_index = keys.index(self.current_values[0]) - else: - self._selected_index = 0 - - # Key bindings. - kb = KeyBindings() - - @kb.add("up") - def _up(event: E) -> None: - self._selected_index = max(0, self._selected_index - 1) - - @kb.add("down") - def _down(event: E) -> None: - self._selected_index = min(len(self.values) - 1, self._selected_index + 1) - - @kb.add("pageup") - def _pageup(event: E) -> None: - w = event.app.layout.current_window - if w.render_info: - self._selected_index = max( - 0, self._selected_index - len(w.render_info.displayed_lines) - ) - - @kb.add("pagedown") - def _pagedown(event: E) -> None: - w = event.app.layout.current_window - if w.render_info: - self._selected_index = min( - len(self.values) - 1, - self._selected_index + len(w.render_info.displayed_lines), - ) - - @kb.add("enter") - @kb.add(" ") - def _click(event: E) -> None: - self._handle_enter() - - @kb.add(Keys.Any) - def _find(event: E) -> None: - # We first check values after the selected value, then all values. - values = list(self.values) - for value in values[self._selected_index + 1 :] + values: - text = fragment_list_to_text(to_formatted_text(value[1])).lower() - - if text.startswith(event.data.lower()): - self._selected_index = self.values.index(value) - return - - # Control and window. - self.control = FormattedTextControl( - self._get_text_fragments, key_bindings=kb, focusable=True - ) - - self.window = Window( - content=self.control, - style=self.container_style, - right_margins=[ - ConditionalMargin( - margin=ScrollbarMargin(display_arrows=True), - filter=Condition(lambda: self.show_scrollbar), - ), - ], - dont_extend_height=True, - ) - - def _handle_enter(self) -> None: - if self.multiple_selection: - val = self.values[self._selected_index][0] - if val in self.current_values: - self.current_values.remove(val) - else: - self.current_values.append(val) - else: - self.current_value = self.values[self._selected_index][0] - - def _get_text_fragments(self) -> StyleAndTextTuples: - def mouse_handler(mouse_event: MouseEvent) -> None: - """ - Set `_selected_index` and `current_value` according to the y - position of the mouse click event. - """ - if mouse_event.event_type == MouseEventType.MOUSE_UP: - self._selected_index = mouse_event.position.y - self._handle_enter() - - result: StyleAndTextTuples = [] - for i, value in enumerate(self.values): - if self.multiple_selection: - checked = value[0] in self.current_values - else: - checked = value[0] == self.current_value - selected = i == self._selected_index - - style = "" - if checked: - style += " " + self.checked_style - if selected: - style += " " + self.selected_style - - result.append((style, self.open_character)) - - if selected: - result.append(("[SetCursorPosition]", "")) - - if checked: - result.append((style, "*")) - else: - result.append((style, " ")) - - result.append((style, self.close_character)) - result.append((self.default_style, " ")) - result.extend(to_formatted_text(value[1], style=self.default_style)) - result.append(("", "\n")) - - # Add mouse handler to all fragments. - for i in range(len(result)): - result[i] = (result[i][0], result[i][1], mouse_handler) - - result.pop() # Remove last newline. - return result - - def __pt_container__(self) -> Container: - return self.window - - -class RadioList(_DialogList[_T]): - """ - List of radio buttons. Only one can be checked at the same time. - - :param values: List of (value, label) tuples. - """ - - open_character = "(" - close_character = ")" - container_style = "class:radio-list" - default_style = "class:radio" - selected_style = "class:radio-selected" - checked_style = "class:radio-checked" - multiple_selection = False - - def __init__( - self, - values: Sequence[Tuple[_T, AnyFormattedText]], - default: Optional[_T] = None, - ) -> None: - if default is None: - default_values = None - else: - default_values = [default] - - super().__init__(values, default_values=default_values) - - -class CheckboxList(_DialogList[_T]): - """ - List of checkbox buttons. Several can be checked at the same time. - - :param values: List of (value, label) tuples. - """ - - open_character = "[" - close_character = "]" - container_style = "class:checkbox-list" - default_style = "class:checkbox" - selected_style = "class:checkbox-selected" - checked_style = "class:checkbox-checked" - multiple_selection = True - - -class Checkbox(CheckboxList[str]): - """Backward compatibility util: creates a 1-sized CheckboxList - - :param text: the text - """ - - show_scrollbar = False - - def __init__(self, text: AnyFormattedText = "", checked: bool = False) -> None: - values = [("value", text)] - super().__init__(values=values) - self.checked = checked - - @property - def checked(self) -> bool: - return "value" in self.current_values - - @checked.setter - def checked(self, value: bool) -> None: - if value: - self.current_values = ["value"] - else: - self.current_values = [] - - -class VerticalLine: - """ - A simple vertical line with a width of 1. - """ - - def __init__(self) -> None: - self.window = Window( - char=Border.VERTICAL, style="class:line,vertical-line", width=1 - ) - - def __pt_container__(self) -> Container: - return self.window - - -class HorizontalLine: - """ - A simple horizontal line with a height of 1. - """ - - def __init__(self) -> None: - self.window = Window( - char=Border.HORIZONTAL, style="class:line,horizontal-line", height=1 - ) - - def __pt_container__(self) -> Container: - return self.window - - -class ProgressBar: - def __init__(self) -> None: - self._percentage = 60 - - self.label = Label("60%") - self.container = FloatContainer( - content=Window(height=1), - floats=[ - # We first draw the label, then the actual progress bar. Right - # now, this is the only way to have the colors of the progress - # bar appear on top of the label. The problem is that our label - # can't be part of any `Window` below. - Float(content=self.label, top=0, bottom=0), - Float( - left=0, - top=0, - right=0, - bottom=0, - content=VSplit( - [ - Window( - style="class:progress-bar.used", - width=lambda: D(weight=int(self._percentage)), - ), - Window( - style="class:progress-bar", - width=lambda: D(weight=int(100 - self._percentage)), - ), - ] - ), - ), - ], - ) - - @property - def percentage(self) -> int: - return self._percentage - - @percentage.setter - def percentage(self, value: int) -> None: - self._percentage = value - self.label.text = f"{value}%" - - def __pt_container__(self) -> Container: - return self.container diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/widgets/dialogs.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/widgets/dialogs.py deleted file mode 100644 index 920582b4e68..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/widgets/dialogs.py +++ /dev/null @@ -1,106 +0,0 @@ -""" -Collection of reusable components for building full screen applications. -""" -from typing import Optional, Sequence, Union - -from prompt_toolkit.filters import has_completions, has_focus -from prompt_toolkit.formatted_text import AnyFormattedText -from prompt_toolkit.key_binding.bindings.focus import focus_next, focus_previous -from prompt_toolkit.key_binding.key_bindings import KeyBindings -from prompt_toolkit.layout.containers import ( - AnyContainer, - DynamicContainer, - HSplit, - VSplit, -) -from prompt_toolkit.layout.dimension import AnyDimension -from prompt_toolkit.layout.dimension import Dimension as D - -from .base import Box, Button, Frame, Shadow - -__all__ = [ - "Dialog", -] - - -class Dialog: - """ - Simple dialog window. This is the base for input dialogs, message dialogs - and confirmation dialogs. - - Changing the title and body of the dialog is possible at runtime by - assigning to the `body` and `title` attributes of this class. - - :param body: Child container object. - :param title: Text to be displayed in the heading of the dialog. - :param buttons: A list of `Button` widgets, displayed at the bottom. - """ - - def __init__( - self, - body: AnyContainer, - title: AnyFormattedText = "", - buttons: Optional[Sequence[Button]] = None, - modal: bool = True, - width: AnyDimension = None, - with_background: bool = False, - ) -> None: - - self.body = body - self.title = title - - buttons = buttons or [] - - # When a button is selected, handle left/right key bindings. - buttons_kb = KeyBindings() - if len(buttons) > 1: - first_selected = has_focus(buttons[0]) - last_selected = has_focus(buttons[-1]) - - buttons_kb.add("left", filter=~first_selected)(focus_previous) - buttons_kb.add("right", filter=~last_selected)(focus_next) - - frame_body: AnyContainer - if buttons: - frame_body = HSplit( - [ - # Add optional padding around the body. - Box( - body=DynamicContainer(lambda: self.body), - padding=D(preferred=1, max=1), - padding_bottom=0, - ), - # The buttons. - Box( - body=VSplit(buttons, padding=1, key_bindings=buttons_kb), - height=D(min=1, max=3, preferred=3), - ), - ] - ) - else: - frame_body = body - - # Key bindings for whole dialog. - kb = KeyBindings() - kb.add("tab", filter=~has_completions)(focus_next) - kb.add("s-tab", filter=~has_completions)(focus_previous) - - frame = Shadow( - body=Frame( - title=lambda: self.title, - body=frame_body, - style="class:dialog.body", - width=(None if with_background is None else width), - key_bindings=kb, - modal=modal, - ) - ) - - self.container: Union[Box, Shadow] - if with_background: - self.container = Box(body=frame, style="class:dialog", width=width) - else: - self.container = frame - - def __pt_container__(self) -> AnyContainer: - return self.container diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/widgets/menus.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/widgets/menus.py deleted file mode 100644 index 6827ebecc7c..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/widgets/menus.py +++ /dev/null @@ -1,374 +0,0 @@ -from typing import Callable, Iterable, List, Optional, Sequence, Union - -from prompt_toolkit.application.current import get_app -from prompt_toolkit.filters import Condition -from prompt_toolkit.formatted_text.base import OneStyleAndTextTuple, StyleAndTextTuples -from prompt_toolkit.key_binding.key_bindings import KeyBindings, KeyBindingsBase -from prompt_toolkit.key_binding.key_processor import KeyPressEvent -from prompt_toolkit.keys import Keys -from prompt_toolkit.layout.containers import ( - AnyContainer, - ConditionalContainer, - Container, - Float, - FloatContainer, - HSplit, - Window, -) -from prompt_toolkit.layout.controls import FormattedTextControl -from prompt_toolkit.mouse_events import MouseEvent, MouseEventType -from prompt_toolkit.utils import get_cwidth -from prompt_toolkit.widgets import Shadow - -from .base import Border - -__all__ = [ - "MenuContainer", - "MenuItem", -] - -E = KeyPressEvent - - -class MenuContainer: - """ - :param floats: List of extra Float objects to display. - :param menu_items: List of `MenuItem` objects. - """ - - def __init__( - self, - body: AnyContainer, - menu_items: List["MenuItem"], - floats: Optional[List[Float]] = None, - key_bindings: Optional[KeyBindingsBase] = None, - ) -> None: - - self.body = body - self.menu_items = menu_items - self.selected_menu = [0] - - # Key bindings. - kb = KeyBindings() - - @Condition - def in_main_menu() -> bool: - return len(self.selected_menu) == 1 - - @Condition - def in_sub_menu() -> bool: - return len(self.selected_menu) > 1 - - # Navigation through the main menu. - - @kb.add("left", filter=in_main_menu) - def _left(event: E) -> None: - self.selected_menu[0] = max(0, self.selected_menu[0] - 1) - - @kb.add("right", filter=in_main_menu) - def _right(event: E) -> None: - self.selected_menu[0] = min( - len(self.menu_items) - 1, self.selected_menu[0] + 1 - ) - - @kb.add("down", filter=in_main_menu) - def _down(event: E) -> None: - self.selected_menu.append(0) - - @kb.add("c-c", filter=in_main_menu) - @kb.add("c-g", filter=in_main_menu) - def _cancel(event: E) -> None: - "Leave menu." - event.app.layout.focus_last() - - # Sub menu navigation. - - @kb.add("left", filter=in_sub_menu) - @kb.add("c-g", filter=in_sub_menu) - @kb.add("c-c", filter=in_sub_menu) - def _back(event: E) -> None: - "Go back to parent menu." - if len(self.selected_menu) > 1: - self.selected_menu.pop() - - @kb.add("right", filter=in_sub_menu) - def _submenu(event: E) -> None: - "go into sub menu." - if self._get_menu(len(self.selected_menu) - 1).children: - self.selected_menu.append(0) - - # If This item does not have a sub menu. Go up in the parent menu. - elif ( - len(self.selected_menu) == 2 - and self.selected_menu[0] < len(self.menu_items) - 1 - ): - self.selected_menu = [ - min(len(self.menu_items) - 1, self.selected_menu[0] + 1) - ] - if self.menu_items[self.selected_menu[0]].children: - self.selected_menu.append(0) - - @kb.add("up", filter=in_sub_menu) - def _up_in_submenu(event: E) -> None: - "Select previous (enabled) menu item or return to main menu." - # Look for previous enabled items in this sub menu. - menu = self._get_menu(len(self.selected_menu) - 2) - index = self.selected_menu[-1] - - previous_indexes = [ - i - for i, item in enumerate(menu.children) - if i < index and not item.disabled - ] - - if previous_indexes: - self.selected_menu[-1] = previous_indexes[-1] - elif len(self.selected_menu) == 2: - # Return to main menu. - self.selected_menu.pop() - - @kb.add("down", filter=in_sub_menu) - def _down_in_submenu(event: E) -> None: - "Select next (enabled) menu item." - menu = self._get_menu(len(self.selected_menu) - 2) - index = self.selected_menu[-1] - - next_indexes = [ - i - for i, item in enumerate(menu.children) - if i > index and not item.disabled - ] - - if next_indexes: - self.selected_menu[-1] = next_indexes[0] - - @kb.add("enter") - def _click(event: E) -> None: - "Click the selected menu item." - item = self._get_menu(len(self.selected_menu) - 1) - if item.handler: - event.app.layout.focus_last() - item.handler() - - # Controls. - self.control = FormattedTextControl( - self._get_menu_fragments, key_bindings=kb, focusable=True, show_cursor=False - ) - - self.window = Window(height=1, content=self.control, style="class:menu-bar") - - submenu = self._submenu(0) - submenu2 = self._submenu(1) - submenu3 = self._submenu(2) - - @Condition - def has_focus() -> bool: - return get_app().layout.current_window == self.window - - self.container = FloatContainer( - content=HSplit( - [ - # The titlebar. - self.window, - # The 'body', like defined above. - body, - ] - ), - floats=[ - Float( - xcursor=True, - ycursor=True, - content=ConditionalContainer( - content=Shadow(body=submenu), filter=has_focus - ), - ), - Float( - attach_to_window=submenu, - xcursor=True, - ycursor=True, - allow_cover_cursor=True, - content=ConditionalContainer( - content=Shadow(body=submenu2), - filter=has_focus - & Condition(lambda: len(self.selected_menu) >= 1), - ), - ), - Float( - attach_to_window=submenu2, - xcursor=True, - ycursor=True, - allow_cover_cursor=True, - content=ConditionalContainer( - content=Shadow(body=submenu3), - filter=has_focus - & Condition(lambda: len(self.selected_menu) >= 2), - ), - ), - # -- - ] - + (floats or []), - key_bindings=key_bindings, - ) - - def _get_menu(self, level: int) -> "MenuItem": - menu = self.menu_items[self.selected_menu[0]] - - for i, index in enumerate(self.selected_menu[1:]): - if i < level: - try: - menu = menu.children[index] - except IndexError: - return MenuItem("debug") - - return menu - - def _get_menu_fragments(self) -> StyleAndTextTuples: - focused = get_app().layout.has_focus(self.window) - - # This is called during the rendering. When we discover that this - # widget doesn't have the focus anymore. Reset menu state. - if not focused: - self.selected_menu = [0] - - # Generate text fragments for the main menu. - def one_item(i: int, item: MenuItem) -> Iterable[OneStyleAndTextTuple]: - def mouse_handler(mouse_event: MouseEvent) -> None: - hover = mouse_event.event_type == MouseEventType.MOUSE_MOVE - if ( - mouse_event.event_type == MouseEventType.MOUSE_DOWN - or hover - and focused - ): - # Toggle focus. - app = get_app() - if not hover: - if app.layout.has_focus(self.window): - if self.selected_menu == [i]: - app.layout.focus_last() - else: - app.layout.focus(self.window) - self.selected_menu = [i] - - yield ("class:menu-bar", " ", mouse_handler) - if i == self.selected_menu[0] and focused: - yield ("[SetMenuPosition]", "", mouse_handler) - style = "class:menu-bar.selected-item" - else: - style = "class:menu-bar" - yield style, item.text, mouse_handler - - result: StyleAndTextTuples = [] - for i, item in enumerate(self.menu_items): - result.extend(one_item(i, item)) - - return result - - def _submenu(self, level: int = 0) -> Window: - def get_text_fragments() -> StyleAndTextTuples: - result: StyleAndTextTuples = [] - if level < len(self.selected_menu): - menu = self._get_menu(level) - if menu.children: - result.append(("class:menu", Border.TOP_LEFT)) - result.append(("class:menu", Border.HORIZONTAL * (menu.width + 4))) - result.append(("class:menu", Border.TOP_RIGHT)) - result.append(("", "\n")) - try: - selected_item = self.selected_menu[level + 1] - except IndexError: - selected_item = -1 - - def one_item( - i: int, item: MenuItem - ) -> Iterable[OneStyleAndTextTuple]: - def mouse_handler(mouse_event: MouseEvent) -> None: - if item.disabled: - # The arrow keys can't interact with menu items that are disabled. - # The mouse shouldn't be able to either. - return - hover = mouse_event.event_type == MouseEventType.MOUSE_MOVE - if ( - mouse_event.event_type == MouseEventType.MOUSE_UP - or hover - ): - app = get_app() - if not hover and item.handler: - app.layout.focus_last() - item.handler() - else: - self.selected_menu = self.selected_menu[ - : level + 1 - ] + [i] - - if i == selected_item: - yield ("[SetCursorPosition]", "") - style = "class:menu-bar.selected-item" - else: - style = "" - - yield ("class:menu", Border.VERTICAL) - if item.text == "-": - yield ( - style + "class:menu-border", - f"{Border.HORIZONTAL * (menu.width + 3)}", - mouse_handler, - ) - else: - yield ( - style, - f" {item.text}".ljust(menu.width + 3), - mouse_handler, - ) - - if item.children: - yield (style, ">", mouse_handler) - else: - yield (style, " ", mouse_handler) - - if i == selected_item: - yield ("[SetMenuPosition]", "") - yield ("class:menu", Border.VERTICAL) - - yield ("", "\n") - - for i, item in enumerate(menu.children): - result.extend(one_item(i, item)) - - result.append(("class:menu", Border.BOTTOM_LEFT)) - result.append(("class:menu", Border.HORIZONTAL * (menu.width + 4))) - result.append(("class:menu", Border.BOTTOM_RIGHT)) - return result - - return Window(FormattedTextControl(get_text_fragments), style="class:menu") - - @property - def floats(self) -> Optional[List[Float]]: - return self.container.floats - - def __pt_container__(self) -> Container: - return self.container - - -class MenuItem: - def __init__( - self, - text: str = "", - handler: Optional[Callable[[], None]] = None, - children: Optional[List["MenuItem"]] = None, - shortcut: Optional[Sequence[Union[Keys, str]]] = None, - disabled: bool = False, - ) -> None: - - self.text = text - self.handler = handler - self.children = children or [] - self.shortcut = shortcut - self.disabled = disabled - self.selected_item = 0 - - @property - def width(self) -> int: - if self.children: - return max(get_cwidth(c.text) for c in self.children) - else: - return 0 diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/widgets/toolbars.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/widgets/toolbars.py deleted file mode 100644 index 402ecaa9824..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/widgets/toolbars.py +++ /dev/null @@ -1,374 +0,0 @@ -from typing import Any, Optional - -from prompt_toolkit.application.current import get_app -from prompt_toolkit.buffer import Buffer -from prompt_toolkit.enums import SYSTEM_BUFFER -from prompt_toolkit.filters import ( - Condition, - FilterOrBool, - emacs_mode, - has_arg, - has_completions, - has_focus, - has_validation_error, - to_filter, - vi_mode, - vi_navigation_mode, -) -from prompt_toolkit.formatted_text import ( - AnyFormattedText, - StyleAndTextTuples, - fragment_list_len, - to_formatted_text, -) -from prompt_toolkit.key_binding.key_bindings import ( - ConditionalKeyBindings, - KeyBindings, - KeyBindingsBase, - merge_key_bindings, -) -from prompt_toolkit.key_binding.key_processor import KeyPressEvent -from prompt_toolkit.key_binding.vi_state import InputMode -from prompt_toolkit.keys import Keys -from prompt_toolkit.layout.containers import ConditionalContainer, Container, Window -from prompt_toolkit.layout.controls import ( - BufferControl, - FormattedTextControl, - SearchBufferControl, - UIContent, - UIControl, -) -from prompt_toolkit.layout.dimension import Dimension -from prompt_toolkit.layout.processors import BeforeInput -from prompt_toolkit.lexers import SimpleLexer -from prompt_toolkit.search import SearchDirection - -__all__ = [ - "ArgToolbar", - "CompletionsToolbar", - "FormattedTextToolbar", - "SearchToolbar", - "SystemToolbar", - "ValidationToolbar", -] - -E = KeyPressEvent - - -class FormattedTextToolbar(Window): - def __init__(self, text: AnyFormattedText, style: str = "", **kw: Any) -> None: - # Note: The style needs to be applied to the toolbar as a whole, not - # just the `FormattedTextControl`. - super().__init__( - FormattedTextControl(text, **kw), - style=style, - dont_extend_height=True, - height=Dimension(min=1), - ) - - -class SystemToolbar: - """ - Toolbar for a system prompt. - - :param prompt: Prompt to be displayed to the user. - """ - - def __init__( - self, - prompt: AnyFormattedText = "Shell command: ", - enable_global_bindings: FilterOrBool = True, - ) -> None: - - self.prompt = prompt - self.enable_global_bindings = to_filter(enable_global_bindings) - - self.system_buffer = Buffer(name=SYSTEM_BUFFER) - - self._bindings = self._build_key_bindings() - - self.buffer_control = BufferControl( - buffer=self.system_buffer, - lexer=SimpleLexer(style="class:system-toolbar.text"), - input_processors=[ - BeforeInput(lambda: self.prompt, style="class:system-toolbar") - ], - key_bindings=self._bindings, - ) - - self.window = Window( - self.buffer_control, height=1, style="class:system-toolbar" - ) - - self.container = ConditionalContainer( - content=self.window, filter=has_focus(self.system_buffer) - ) - - def _get_display_before_text(self) -> StyleAndTextTuples: - return [ - ("class:system-toolbar", "Shell command: "), - ("class:system-toolbar.text", self.system_buffer.text), - ("", "\n"), - ] - - def _build_key_bindings(self) -> KeyBindingsBase: - focused = has_focus(self.system_buffer) - - # Emacs - emacs_bindings = KeyBindings() - handle = emacs_bindings.add - - @handle("escape", filter=focused) - @handle("c-g", filter=focused) - @handle("c-c", filter=focused) - def _cancel(event: E) -> None: - "Hide system prompt." - self.system_buffer.reset() - event.app.layout.focus_last() - - @handle("enter", filter=focused) - async def _accept(event: E) -> None: - "Run system command." - await event.app.run_system_command( - self.system_buffer.text, - display_before_text=self._get_display_before_text(), - ) - self.system_buffer.reset(append_to_history=True) - event.app.layout.focus_last() - - # Vi. - vi_bindings = KeyBindings() - handle = vi_bindings.add - - @handle("escape", filter=focused) - @handle("c-c", filter=focused) - def _cancel_vi(event: E) -> None: - "Hide system prompt." - event.app.vi_state.input_mode = InputMode.NAVIGATION - self.system_buffer.reset() - event.app.layout.focus_last() - - @handle("enter", filter=focused) - async def _accept_vi(event: E) -> None: - "Run system command." - event.app.vi_state.input_mode = InputMode.NAVIGATION - await event.app.run_system_command( - self.system_buffer.text, - display_before_text=self._get_display_before_text(), - ) - self.system_buffer.reset(append_to_history=True) - event.app.layout.focus_last() - - # Global bindings. (Listen to these bindings, even when this widget is - # not focussed.) - global_bindings = KeyBindings() - handle = global_bindings.add - - @handle(Keys.Escape, "!", filter=~focused & emacs_mode, is_global=True) - def _focus_me(event: E) -> None: - "M-'!' will focus this user control." - event.app.layout.focus(self.window) - - @handle("!", filter=~focused & vi_mode & vi_navigation_mode, is_global=True) - def _focus_me_vi(event: E) -> None: - "Focus." - event.app.vi_state.input_mode = InputMode.INSERT - event.app.layout.focus(self.window) - - return merge_key_bindings( - [ - ConditionalKeyBindings(emacs_bindings, emacs_mode), - ConditionalKeyBindings(vi_bindings, vi_mode), - ConditionalKeyBindings(global_bindings, self.enable_global_bindings), - ] - ) - - def __pt_container__(self) -> Container: - return self.container - - -class ArgToolbar: - def __init__(self) -> None: - def get_formatted_text() -> StyleAndTextTuples: - arg = get_app().key_processor.arg or "" - if arg == "-": - arg = "-1" - - return [ - ("class:arg-toolbar", "Repeat: "), - ("class:arg-toolbar.text", arg), - ] - - self.window = Window(FormattedTextControl(get_formatted_text), height=1) - - self.container = ConditionalContainer(content=self.window, filter=has_arg) - - def __pt_container__(self) -> Container: - return self.container - - -class SearchToolbar: - """ - :param vi_mode: Display '/' and '?' instead of I-search. - :param ignore_case: Search case insensitive. - """ - - def __init__( - self, - search_buffer: Optional[Buffer] = None, - vi_mode: bool = False, - text_if_not_searching: AnyFormattedText = "", - forward_search_prompt: AnyFormattedText = "I-search: ", - backward_search_prompt: AnyFormattedText = "I-search backward: ", - ignore_case: FilterOrBool = False, - ) -> None: - - if search_buffer is None: - search_buffer = Buffer() - - @Condition - def is_searching() -> bool: - return self.control in get_app().layout.search_links - - def get_before_input() -> AnyFormattedText: - if not is_searching(): - return text_if_not_searching - elif ( - self.control.searcher_search_state.direction == SearchDirection.BACKWARD - ): - return "?" if vi_mode else backward_search_prompt - else: - return "/" if vi_mode else forward_search_prompt - - self.search_buffer = search_buffer - - self.control = SearchBufferControl( - buffer=search_buffer, - input_processors=[ - BeforeInput(get_before_input, style="class:search-toolbar.prompt") - ], - lexer=SimpleLexer(style="class:search-toolbar.text"), - ignore_case=ignore_case, - ) - - self.container = ConditionalContainer( - content=Window(self.control, height=1, style="class:search-toolbar"), - filter=is_searching, - ) - - def __pt_container__(self) -> Container: - return self.container - - -class _CompletionsToolbarControl(UIControl): - def create_content(self, width: int, height: int) -> UIContent: - all_fragments: StyleAndTextTuples = [] - - complete_state = get_app().current_buffer.complete_state - if complete_state: - completions = complete_state.completions - index = complete_state.complete_index # Can be None! - - # Width of the completions without the left/right arrows in the margins. - content_width = width - 6 - - # Booleans indicating whether we stripped from the left/right - cut_left = False - cut_right = False - - # Create Menu content. - fragments: StyleAndTextTuples = [] - - for i, c in enumerate(completions): - # When there is no more place for the next completion - if fragment_list_len(fragments) + len(c.display_text) >= content_width: - # If the current one was not yet displayed, page to the next sequence. - if i <= (index or 0): - fragments = [] - cut_left = True - # If the current one is visible, stop here. - else: - cut_right = True - break - - fragments.extend( - to_formatted_text( - c.display_text, - style=( - "class:completion-toolbar.completion.current" - if i == index - else "class:completion-toolbar.completion" - ), - ) - ) - fragments.append(("", " ")) - - # Extend/strip until the content width. - fragments.append(("", " " * (content_width - fragment_list_len(fragments)))) - fragments = fragments[:content_width] - - # Return fragments - all_fragments.append(("", " ")) - all_fragments.append( - ("class:completion-toolbar.arrow", "<" if cut_left else " ") - ) - all_fragments.append(("", " ")) - - all_fragments.extend(fragments) - - all_fragments.append(("", " ")) - all_fragments.append( - ("class:completion-toolbar.arrow", ">" if cut_right else " ") - ) - all_fragments.append(("", " ")) - - def get_line(i: int) -> StyleAndTextTuples: - return all_fragments - - return UIContent(get_line=get_line, line_count=1) - - -class CompletionsToolbar: - def __init__(self) -> None: - self.container = ConditionalContainer( - content=Window( - _CompletionsToolbarControl(), height=1, style="class:completion-toolbar" - ), - filter=has_completions, - ) - - def __pt_container__(self) -> Container: - return self.container - - -class ValidationToolbar: - def __init__(self, show_position: bool = False) -> None: - def get_formatted_text() -> StyleAndTextTuples: - buff = get_app().current_buffer - - if buff.validation_error: - row, column = buff.document.translate_index_to_position( - buff.validation_error.cursor_position - ) - - if show_position: - text = "{} (line={} column={})".format( - buff.validation_error.message, - row + 1, - column + 1, - ) - else: - text = buff.validation_error.message - - return [("class:validation-toolbar", text)] - else: - return [] - - self.control = FormattedTextControl(get_formatted_text) - - self.container = ConditionalContainer( - content=Window(self.control, height=1), filter=has_validation_error - ) - - def __pt_container__(self) -> Container: - return self.container diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/win32_types.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/win32_types.py deleted file mode 100644 index 4ae2e393f48..00000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/win32_types.py +++ /dev/null @@ -1,227 +0,0 @@ -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 - -# Input/Output standard device numbers. Note that these are not handle objects. -# It's the `windll.kernel32.GetStdHandle` system call that turns them into a -# real handle object. -STD_INPUT_HANDLE = c_ulong(-10) -STD_OUTPUT_HANDLE = c_ulong(-11) -STD_ERROR_HANDLE = c_ulong(-12) - - -class COORD(Structure): - """ - Struct in wincon.h - http://msdn.microsoft.com/en-us/library/windows/desktop/ms682119(v=vs.85).aspx - """ - - if TYPE_CHECKING: - X: int - Y: int - - _fields_ = [ - ("X", c_short), # Short - ("Y", c_short), # Short - ] - - def __repr__(self) -> str: - return "{}(X={!r}, Y={!r}, type_x={!r}, type_y={!r})".format( - self.__class__.__name__, - self.X, - self.Y, - type(self.X), - type(self.Y), - ) - - -class UNICODE_OR_ASCII(Union): - if TYPE_CHECKING: - AsciiChar: bytes - UnicodeChar: str - - _fields_ = [ - ("AsciiChar", c_char), - ("UnicodeChar", WCHAR), - ] - - -class KEY_EVENT_RECORD(Structure): - """ - http://msdn.microsoft.com/en-us/library/windows/desktop/ms684166(v=vs.85).aspx - """ - - if TYPE_CHECKING: - KeyDown: int - RepeatCount: int - VirtualKeyCode: int - VirtualScanCode: int - uChar: UNICODE_OR_ASCII - ControlKeyState: int - - _fields_ = [ - ("KeyDown", c_long), # bool - ("RepeatCount", c_short), # word - ("VirtualKeyCode", c_short), # word - ("VirtualScanCode", c_short), # word - ("uChar", UNICODE_OR_ASCII), # Unicode or ASCII. - ("ControlKeyState", c_long), # double word - ] - - -class MOUSE_EVENT_RECORD(Structure): - """ - http://msdn.microsoft.com/en-us/library/windows/desktop/ms684239(v=vs.85).aspx - """ - - if TYPE_CHECKING: - MousePosition: COORD - ButtonState: int - ControlKeyState: int - EventFlags: int - - _fields_ = [ - ("MousePosition", COORD), - ("ButtonState", c_long), # dword - ("ControlKeyState", c_long), # dword - ("EventFlags", c_long), # dword - ] - - -class WINDOW_BUFFER_SIZE_RECORD(Structure): - """ - http://msdn.microsoft.com/en-us/library/windows/desktop/ms687093(v=vs.85).aspx - """ - - if TYPE_CHECKING: - Size: COORD - - _fields_ = [("Size", COORD)] - - -class MENU_EVENT_RECORD(Structure): - """ - http://msdn.microsoft.com/en-us/library/windows/desktop/ms684213(v=vs.85).aspx - """ - - if TYPE_CHECKING: - CommandId: int - - _fields_ = [("CommandId", c_long)] # uint - - -class FOCUS_EVENT_RECORD(Structure): - """ - http://msdn.microsoft.com/en-us/library/windows/desktop/ms683149(v=vs.85).aspx - """ - - if TYPE_CHECKING: - SetFocus: int - - _fields_ = [("SetFocus", c_long)] # bool - - -class EVENT_RECORD(Union): - if TYPE_CHECKING: - KeyEvent: KEY_EVENT_RECORD - MouseEvent: MOUSE_EVENT_RECORD - WindowBufferSizeEvent: WINDOW_BUFFER_SIZE_RECORD - MenuEvent: MENU_EVENT_RECORD - FocusEvent: FOCUS_EVENT_RECORD - - _fields_ = [ - ("KeyEvent", KEY_EVENT_RECORD), - ("MouseEvent", MOUSE_EVENT_RECORD), - ("WindowBufferSizeEvent", WINDOW_BUFFER_SIZE_RECORD), - ("MenuEvent", MENU_EVENT_RECORD), - ("FocusEvent", FOCUS_EVENT_RECORD), - ] - - -class INPUT_RECORD(Structure): - """ - http://msdn.microsoft.com/en-us/library/windows/desktop/ms683499(v=vs.85).aspx - """ - - if TYPE_CHECKING: - EventType: int - Event: EVENT_RECORD - - _fields_ = [("EventType", c_short), ("Event", EVENT_RECORD)] # word # Union. - - -EventTypes = { - 1: "KeyEvent", - 2: "MouseEvent", - 4: "WindowBufferSizeEvent", - 8: "MenuEvent", - 16: "FocusEvent", -} - - -class SMALL_RECT(Structure): - """struct in wincon.h.""" - - if TYPE_CHECKING: - Left: int - Top: int - Right: int - Bottom: int - - _fields_ = [ - ("Left", c_short), - ("Top", c_short), - ("Right", c_short), - ("Bottom", c_short), - ] - - -class CONSOLE_SCREEN_BUFFER_INFO(Structure): - """struct in wincon.h.""" - - if TYPE_CHECKING: - dwSize: COORD - dwCursorPosition: COORD - wAttributes: int - srWindow: SMALL_RECT - dwMaximumWindowSize: COORD - - _fields_ = [ - ("dwSize", COORD), - ("dwCursorPosition", COORD), - ("wAttributes", WORD), - ("srWindow", SMALL_RECT), - ("dwMaximumWindowSize", COORD), - ] - - def __repr__(self) -> str: - return "CONSOLE_SCREEN_BUFFER_INFO({!r},{!r},{!r},{!r},{!r},{!r},{!r},{!r},{!r},{!r},{!r})".format( - self.dwSize.Y, - self.dwSize.X, - self.dwCursorPosition.Y, - self.dwCursorPosition.X, - self.wAttributes, - self.srWindow.Top, - self.srWindow.Left, - self.srWindow.Bottom, - self.srWindow.Right, - self.dwMaximumWindowSize.Y, - self.dwMaximumWindowSize.X, - ) - - -class SECURITY_ATTRIBUTES(Structure): - """ - http://msdn.microsoft.com/en-us/library/windows/desktop/aa379560(v=vs.85).aspx - """ - - if TYPE_CHECKING: - nLength: int - lpSecurityDescriptor: int - bInheritHandle: int # BOOL comes back as 'int'. - - _fields_ = [ - ("nLength", DWORD), - ("lpSecurityDescriptor", LPVOID), - ("bInheritHandle", BOOL), - ] diff --git a/contrib/python/prompt-toolkit/py3/tests/test_async_generator.py b/contrib/python/prompt-toolkit/py3/tests/test_async_generator.py deleted file mode 100644 index fd2c109c955..00000000000 --- a/contrib/python/prompt-toolkit/py3/tests/test_async_generator.py +++ /dev/null @@ -1,24 +0,0 @@ -from prompt_toolkit.eventloop import generator_to_async_generator, get_event_loop - - -def _sync_generator(): - yield 1 - yield 10 - - -def test_generator_to_async_generator(): - """ - Test conversion of sync to asycn generator. - This should run the synchronous parts in a background thread. - """ - async_gen = generator_to_async_generator(_sync_generator) - - items = [] - - async def consume_async_generator(): - async for item in async_gen: - items.append(item) - - # Run the event loop until all items are collected. - get_event_loop().run_until_complete(consume_async_generator()) - assert items == [1, 10] diff --git a/contrib/python/prompt-toolkit/py3/tests/test_buffer.py b/contrib/python/prompt-toolkit/py3/tests/test_buffer.py deleted file mode 100644 index 413ec98743e..00000000000 --- a/contrib/python/prompt-toolkit/py3/tests/test_buffer.py +++ /dev/null @@ -1,110 +0,0 @@ -import pytest - -from prompt_toolkit.buffer import Buffer - - -@pytest.fixture -def _buffer(): - buff = Buffer() - return buff - - -def test_initial(_buffer): - assert _buffer.text == "" - assert _buffer.cursor_position == 0 - - -def test_insert_text(_buffer): - _buffer.insert_text("some_text") - assert _buffer.text == "some_text" - assert _buffer.cursor_position == len("some_text") - - -def test_cursor_movement(_buffer): - _buffer.insert_text("some_text") - _buffer.cursor_left() - _buffer.cursor_left() - _buffer.cursor_left() - _buffer.cursor_right() - _buffer.insert_text("A") - - assert _buffer.text == "some_teAxt" - assert _buffer.cursor_position == len("some_teA") - - -def test_backspace(_buffer): - _buffer.insert_text("some_text") - _buffer.cursor_left() - _buffer.cursor_left() - _buffer.delete_before_cursor() - - assert _buffer.text == "some_txt" - assert _buffer.cursor_position == len("some_t") - - -def test_cursor_up(_buffer): - # Cursor up to a line thats longer. - _buffer.insert_text("long line1\nline2") - _buffer.cursor_up() - - assert _buffer.document.cursor_position == 5 - - # Going up when already at the top. - _buffer.cursor_up() - assert _buffer.document.cursor_position == 5 - - # Going up to a line that's shorter. - _buffer.reset() - _buffer.insert_text("line1\nlong line2") - - _buffer.cursor_up() - assert _buffer.document.cursor_position == 5 - - -def test_cursor_down(_buffer): - _buffer.insert_text("line1\nline2") - _buffer.cursor_position = 3 - - # Normally going down - _buffer.cursor_down() - assert _buffer.document.cursor_position == len("line1\nlin") - - # Going down to a line that's shorter. - _buffer.reset() - _buffer.insert_text("long line1\na\nb") - _buffer.cursor_position = 3 - - _buffer.cursor_down() - assert _buffer.document.cursor_position == len("long line1\na") - - -def test_join_next_line(_buffer): - _buffer.insert_text("line1\nline2\nline3") - _buffer.cursor_up() - _buffer.join_next_line() - - assert _buffer.text == "line1\nline2 line3" - - # Test when there is no '\n' in the text - _buffer.reset() - _buffer.insert_text("line1") - _buffer.cursor_position = 0 - _buffer.join_next_line() - - assert _buffer.text == "line1" - - -def test_newline(_buffer): - _buffer.insert_text("hello world") - _buffer.newline() - - assert _buffer.text == "hello world\n" - - -def test_swap_characters_before_cursor(_buffer): - _buffer.insert_text("hello world") - _buffer.cursor_left() - _buffer.cursor_left() - _buffer.swap_characters_before_cursor() - - assert _buffer.text == "hello wrold" diff --git a/contrib/python/prompt-toolkit/py3/tests/test_cli.py b/contrib/python/prompt-toolkit/py3/tests/test_cli.py deleted file mode 100644 index 53d1e4f2841..00000000000 --- a/contrib/python/prompt-toolkit/py3/tests/test_cli.py +++ /dev/null @@ -1,939 +0,0 @@ -""" -These are almost end-to-end tests. They create a Prompt, feed it with some -input and check the result. -""" -from functools import partial - -import pytest - -from prompt_toolkit.clipboard import ClipboardData, InMemoryClipboard -from prompt_toolkit.enums import EditingMode -from prompt_toolkit.filters import ViInsertMode -from prompt_toolkit.history import InMemoryHistory -from prompt_toolkit.input.defaults import create_pipe_input -from prompt_toolkit.input.vt100_parser import ANSI_SEQUENCES -from prompt_toolkit.key_binding.bindings.named_commands import prefix_meta -from prompt_toolkit.key_binding.key_bindings import KeyBindings -from prompt_toolkit.output import DummyOutput -from prompt_toolkit.shortcuts import PromptSession - - -def _history(): - h = InMemoryHistory() - h.append_string("line1 first input") - h.append_string("line2 second input") - h.append_string("line3 third input") - return h - - -def _feed_cli_with_input( - text, - editing_mode=EditingMode.EMACS, - clipboard=None, - history=None, - multiline=False, - check_line_ending=True, - key_bindings=None, -): - """ - Create a Prompt, feed it with the given user input and return the CLI - object. - - This returns a (result, Application) tuple. - """ - # If the given text doesn't end with a newline, the interface won't finish. - if check_line_ending: - assert text.endswith("\r") - - with create_pipe_input() as inp: - inp.send_text(text) - session = PromptSession( - input=inp, - output=DummyOutput(), - editing_mode=editing_mode, - history=history, - multiline=multiline, - clipboard=clipboard, - key_bindings=key_bindings, - ) - - _ = session.prompt() - return session.default_buffer.document, session.app - - -def test_simple_text_input(): - # Simple text input, followed by enter. - result, cli = _feed_cli_with_input("hello\r") - assert result.text == "hello" - assert cli.current_buffer.text == "hello" - - -def test_emacs_cursor_movements(): - """ - Test cursor movements with Emacs key bindings. - """ - # ControlA (beginning-of-line) - result, cli = _feed_cli_with_input("hello\x01X\r") - assert result.text == "Xhello" - - # ControlE (end-of-line) - result, cli = _feed_cli_with_input("hello\x01X\x05Y\r") - assert result.text == "XhelloY" - - # ControlH or \b - result, cli = _feed_cli_with_input("hello\x08X\r") - assert result.text == "hellX" - - # Delete. (Left, left, delete) - result, cli = _feed_cli_with_input("hello\x1b[D\x1b[D\x1b[3~\r") - assert result.text == "helo" - - # Left. - result, cli = _feed_cli_with_input("hello\x1b[DX\r") - assert result.text == "hellXo" - - # ControlA, right - result, cli = _feed_cli_with_input("hello\x01\x1b[CX\r") - assert result.text == "hXello" - - # ControlB (backward-char) - result, cli = _feed_cli_with_input("hello\x02X\r") - assert result.text == "hellXo" - - # ControlF (forward-char) - result, cli = _feed_cli_with_input("hello\x01\x06X\r") - assert result.text == "hXello" - - # ControlD: delete after cursor. - result, cli = _feed_cli_with_input("hello\x01\x04\r") - assert result.text == "ello" - - # ControlD at the end of the input ssshould not do anything. - result, cli = _feed_cli_with_input("hello\x04\r") - assert result.text == "hello" - - # Left, Left, ControlK (kill-line) - result, cli = _feed_cli_with_input("hello\x1b[D\x1b[D\x0b\r") - assert result.text == "hel" - - # Left, Left Esc- ControlK (kill-line, but negative) - result, cli = _feed_cli_with_input("hello\x1b[D\x1b[D\x1b-\x0b\r") - assert result.text == "lo" - - # ControlL: should not influence the result. - result, cli = _feed_cli_with_input("hello\x0c\r") - assert result.text == "hello" - - # ControlRight (forward-word) - result, cli = _feed_cli_with_input("hello world\x01X\x1b[1;5CY\r") - assert result.text == "XhelloY world" - - # ContrlolLeft (backward-word) - result, cli = _feed_cli_with_input("hello world\x1b[1;5DY\r") - assert result.text == "hello Yworld" - - # <esc>-f with argument. (forward-word) - result, cli = _feed_cli_with_input("hello world abc def\x01\x1b3\x1bfX\r") - assert result.text == "hello world abcX def" - - # <esc>-f with negative argument. (forward-word) - result, cli = _feed_cli_with_input("hello world abc def\x1b-\x1b3\x1bfX\r") - assert result.text == "hello Xworld abc def" - - # <esc>-b with argument. (backward-word) - result, cli = _feed_cli_with_input("hello world abc def\x1b3\x1bbX\r") - assert result.text == "hello Xworld abc def" - - # <esc>-b with negative argument. (backward-word) - result, cli = _feed_cli_with_input("hello world abc def\x01\x1b-\x1b3\x1bbX\r") - assert result.text == "hello world abc Xdef" - - # ControlW (kill-word / unix-word-rubout) - result, cli = _feed_cli_with_input("hello world\x17\r") - assert result.text == "hello " - assert cli.clipboard.get_data().text == "world" - - result, cli = _feed_cli_with_input("test hello world\x1b2\x17\r") - assert result.text == "test " - - # Escape Backspace (unix-word-rubout) - result, cli = _feed_cli_with_input("hello world\x1b\x7f\r") - assert result.text == "hello " - assert cli.clipboard.get_data().text == "world" - - result, cli = _feed_cli_with_input("hello world\x1b\x08\r") - assert result.text == "hello " - assert cli.clipboard.get_data().text == "world" - - # Backspace (backward-delete-char) - result, cli = _feed_cli_with_input("hello world\x7f\r") - assert result.text == "hello worl" - assert result.cursor_position == len("hello worl") - - result, cli = _feed_cli_with_input("hello world\x08\r") - assert result.text == "hello worl" - assert result.cursor_position == len("hello worl") - - # Delete (delete-char) - result, cli = _feed_cli_with_input("hello world\x01\x1b[3~\r") - assert result.text == "ello world" - assert result.cursor_position == 0 - - # Escape-\\ (delete-horizontal-space) - result, cli = _feed_cli_with_input("hello world\x1b8\x02\x1b\\\r") - assert result.text == "helloworld" - assert result.cursor_position == len("hello") - - -def test_emacs_kill_multiple_words_and_paste(): - # Using control-w twice should place both words on the clipboard. - result, cli = _feed_cli_with_input( - "hello world test" "\x17\x17" "--\x19\x19\r" # Twice c-w. # Twice c-y. - ) - assert result.text == "hello --world testworld test" - assert cli.clipboard.get_data().text == "world test" - - # Using alt-d twice should place both words on the clipboard. - result, cli = _feed_cli_with_input( - "hello world test" - "\x1bb\x1bb" # Twice left. - "\x1bd\x1bd" # Twice kill-word. - "abc" - "\x19" # Paste. - "\r" - ) - assert result.text == "hello abcworld test" - assert cli.clipboard.get_data().text == "world test" - - -def test_interrupts(): - # ControlC: raise KeyboardInterrupt. - with pytest.raises(KeyboardInterrupt): - result, cli = _feed_cli_with_input("hello\x03\r") - - with pytest.raises(KeyboardInterrupt): - result, cli = _feed_cli_with_input("hello\x03\r") - - # ControlD without any input: raises EOFError. - with pytest.raises(EOFError): - result, cli = _feed_cli_with_input("\x04\r") - - -def test_emacs_yank(): - # ControlY (yank) - c = InMemoryClipboard(ClipboardData("XYZ")) - result, cli = _feed_cli_with_input("hello\x02\x19\r", clipboard=c) - assert result.text == "hellXYZo" - assert result.cursor_position == len("hellXYZ") - - -def test_quoted_insert(): - # ControlQ - ControlB (quoted-insert) - result, cli = _feed_cli_with_input("hello\x11\x02\r") - assert result.text == "hello\x02" - - -def test_transformations(): - # Meta-c (capitalize-word) - result, cli = _feed_cli_with_input("hello world\01\x1bc\r") - assert result.text == "Hello world" - assert result.cursor_position == len("Hello") - - # Meta-u (uppercase-word) - result, cli = _feed_cli_with_input("hello world\01\x1bu\r") - assert result.text == "HELLO world" - assert result.cursor_position == len("Hello") - - # Meta-u (downcase-word) - result, cli = _feed_cli_with_input("HELLO WORLD\01\x1bl\r") - assert result.text == "hello WORLD" - assert result.cursor_position == len("Hello") - - # ControlT (transpose-chars) - result, cli = _feed_cli_with_input("hello\x14\r") - assert result.text == "helol" - assert result.cursor_position == len("hello") - - # Left, Left, Control-T (transpose-chars) - result, cli = _feed_cli_with_input("abcde\x1b[D\x1b[D\x14\r") - assert result.text == "abdce" - assert result.cursor_position == len("abcd") - - -def test_emacs_other_bindings(): - # Transpose characters. - result, cli = _feed_cli_with_input("abcde\x14X\r") # Ctrl-T - assert result.text == "abcedX" - - # Left, Left, Transpose. (This is slightly different.) - result, cli = _feed_cli_with_input("abcde\x1b[D\x1b[D\x14X\r") - assert result.text == "abdcXe" - - # Clear before cursor. - result, cli = _feed_cli_with_input("hello\x1b[D\x1b[D\x15X\r") - assert result.text == "Xlo" - - # unix-word-rubout: delete word before the cursor. - # (ControlW). - result, cli = _feed_cli_with_input("hello world test\x17X\r") - assert result.text == "hello world X" - - result, cli = _feed_cli_with_input("hello world /some/very/long/path\x17X\r") - assert result.text == "hello world X" - - # (with argument.) - result, cli = _feed_cli_with_input("hello world test\x1b2\x17X\r") - assert result.text == "hello X" - - result, cli = _feed_cli_with_input("hello world /some/very/long/path\x1b2\x17X\r") - assert result.text == "hello X" - - # backward-kill-word: delete word before the cursor. - # (Esc-ControlH). - result, cli = _feed_cli_with_input("hello world /some/very/long/path\x1b\x08X\r") - assert result.text == "hello world /some/very/long/X" - - # (with arguments.) - result, cli = _feed_cli_with_input( - "hello world /some/very/long/path\x1b3\x1b\x08X\r" - ) - assert result.text == "hello world /some/very/X" - - -def test_controlx_controlx(): - # At the end: go to the start of the line. - result, cli = _feed_cli_with_input("hello world\x18\x18X\r") - assert result.text == "Xhello world" - assert result.cursor_position == 1 - - # At the start: go to the end of the line. - result, cli = _feed_cli_with_input("hello world\x01\x18\x18X\r") - assert result.text == "hello worldX" - - # Left, Left Control-X Control-X: go to the end of the line. - result, cli = _feed_cli_with_input("hello world\x1b[D\x1b[D\x18\x18X\r") - assert result.text == "hello worldX" - - -def test_emacs_history_bindings(): - # Adding a new item to the history. - history = _history() - result, cli = _feed_cli_with_input("new input\r", history=history) - assert result.text == "new input" - history.get_strings()[-1] == "new input" - - # Go up in history, and accept the last item. - result, cli = _feed_cli_with_input("hello\x1b[A\r", history=history) - assert result.text == "new input" - - # Esc< (beginning-of-history) - result, cli = _feed_cli_with_input("hello\x1b<\r", history=history) - assert result.text == "line1 first input" - - # Esc> (end-of-history) - result, cli = _feed_cli_with_input( - "another item\x1b[A\x1b[a\x1b>\r", history=history - ) - assert result.text == "another item" - - # ControlUp (previous-history) - result, cli = _feed_cli_with_input("\x1b[1;5A\r", history=history) - assert result.text == "another item" - - # Esc< ControlDown (beginning-of-history, next-history) - result, cli = _feed_cli_with_input("\x1b<\x1b[1;5B\r", history=history) - assert result.text == "line2 second input" - - -def test_emacs_reverse_search(): - history = _history() - - # ControlR (reverse-search-history) - result, cli = _feed_cli_with_input("\x12input\r\r", history=history) - assert result.text == "line3 third input" - - # Hitting ControlR twice. - result, cli = _feed_cli_with_input("\x12input\x12\r\r", history=history) - assert result.text == "line2 second input" - - -def test_emacs_arguments(): - """ - Test various combinations of arguments in Emacs mode. - """ - # esc 4 - result, cli = _feed_cli_with_input("\x1b4x\r") - assert result.text == "xxxx" - - # esc 4 4 - result, cli = _feed_cli_with_input("\x1b44x\r") - assert result.text == "x" * 44 - - # esc 4 esc 4 - result, cli = _feed_cli_with_input("\x1b4\x1b4x\r") - assert result.text == "x" * 44 - - # esc - right (-1 position to the right, equals 1 to the left.) - result, cli = _feed_cli_with_input("aaaa\x1b-\x1b[Cbbbb\r") - assert result.text == "aaabbbba" - - # esc - 3 right - result, cli = _feed_cli_with_input("aaaa\x1b-3\x1b[Cbbbb\r") - assert result.text == "abbbbaaa" - - # esc - - - 3 right - result, cli = _feed_cli_with_input("aaaa\x1b---3\x1b[Cbbbb\r") - assert result.text == "abbbbaaa" - - -def test_emacs_arguments_for_all_commands(): - """ - Test all Emacs commands with Meta-[0-9] arguments (both positive and - negative). No one should crash. - """ - for key in ANSI_SEQUENCES: - # Ignore BracketedPaste. This would hang forever, because it waits for - # the end sequence. - if key != "\x1b[200~": - try: - # Note: we add an 'X' after the key, because Ctrl-Q (quoted-insert) - # expects something to follow. We add an additional \r, because - # Ctrl-R and Ctrl-S (reverse-search) expect that. - result, cli = _feed_cli_with_input("hello\x1b4" + key + "X\r\r") - - result, cli = _feed_cli_with_input("hello\x1b-" + key + "X\r\r") - except KeyboardInterrupt: - # This exception should only be raised for Ctrl-C - assert key == "\x03" - - -def test_emacs_kill_ring(): - operations = ( - # abc ControlA ControlK - "abc\x01\x0b" - # def ControlA ControlK - "def\x01\x0b" - # ghi ControlA ControlK - "ghi\x01\x0b" - # ControlY (yank) - "\x19" - ) - - result, cli = _feed_cli_with_input(operations + "\r") - assert result.text == "ghi" - - result, cli = _feed_cli_with_input(operations + "\x1by\r") - assert result.text == "def" - - result, cli = _feed_cli_with_input(operations + "\x1by\x1by\r") - assert result.text == "abc" - - result, cli = _feed_cli_with_input(operations + "\x1by\x1by\x1by\r") - assert result.text == "ghi" - - -def test_emacs_selection(): - # Copy/paste empty selection should not do anything. - operations = ( - "hello" - # Twice left. - "\x1b[D\x1b[D" - # Control-Space - "\x00" - # ControlW (cut) - "\x17" - # ControlY twice. (paste twice) - "\x19\x19\r" - ) - - result, cli = _feed_cli_with_input(operations) - assert result.text == "hello" - - # Copy/paste one character. - operations = ( - "hello" - # Twice left. - "\x1b[D\x1b[D" - # Control-Space - "\x00" - # Right. - "\x1b[C" - # ControlW (cut) - "\x17" - # ControlA (Home). - "\x01" - # ControlY (paste) - "\x19\r" - ) - - result, cli = _feed_cli_with_input(operations) - assert result.text == "lhelo" - - -def test_emacs_insert_comment(): - # Test insert-comment (M-#) binding. - result, cli = _feed_cli_with_input("hello\x1b#", check_line_ending=False) - assert result.text == "#hello" - - result, cli = _feed_cli_with_input( - "hello\rworld\x1b#", check_line_ending=False, multiline=True - ) - assert result.text == "#hello\n#world" - - -def test_emacs_record_macro(): - operations = ( - " " - "\x18(" # Start recording macro. C-X( - "hello" - "\x18)" # Stop recording macro. - " " - "\x18e" # Execute macro. - "\x18e" # Execute macro. - "\r" - ) - - result, cli = _feed_cli_with_input(operations) - assert result.text == " hello hellohello" - - -def test_emacs_nested_macro(): - "Test calling the macro within a macro." - # Calling a macro within a macro should take the previous recording (if one - # exists), not the one that is in progress. - operations = ( - "\x18(" # Start recording macro. C-X( - "hello" - "\x18e" # Execute macro. - "\x18)" # Stop recording macro. - "\x18e" # Execute macro. - "\r" - ) - - result, cli = _feed_cli_with_input(operations) - assert result.text == "hellohello" - - operations = ( - "\x18(" # Start recording macro. C-X( - "hello" - "\x18)" # Stop recording macro. - "\x18(" # Start recording macro. C-X( - "\x18e" # Execute macro. - "world" - "\x18)" # Stop recording macro. - "\x01\x0b" # Delete all (c-a c-k). - "\x18e" # Execute macro. - "\r" - ) - - result, cli = _feed_cli_with_input(operations) - assert result.text == "helloworld" - - -def test_prefix_meta(): - # Test the prefix-meta command. - b = KeyBindings() - b.add("j", "j", filter=ViInsertMode())(prefix_meta) - - result, cli = _feed_cli_with_input( - "hellojjIX\r", key_bindings=b, editing_mode=EditingMode.VI - ) - assert result.text == "Xhello" - - -def test_bracketed_paste(): - result, cli = _feed_cli_with_input("\x1b[200~hello world\x1b[201~\r") - assert result.text == "hello world" - - result, cli = _feed_cli_with_input("\x1b[200~hello\rworld\x1b[201~\x1b\r") - assert result.text == "hello\nworld" - - # With \r\n endings. - result, cli = _feed_cli_with_input("\x1b[200~hello\r\nworld\x1b[201~\x1b\r") - assert result.text == "hello\nworld" - - # With \n endings. - result, cli = _feed_cli_with_input("\x1b[200~hello\nworld\x1b[201~\x1b\r") - assert result.text == "hello\nworld" - - -def test_vi_cursor_movements(): - """ - Test cursor movements with Vi key bindings. - """ - feed = partial(_feed_cli_with_input, editing_mode=EditingMode.VI) - - result, cli = feed("\x1b\r") - assert result.text == "" - assert cli.editing_mode == EditingMode.VI - - # Esc h a X - result, cli = feed("hello\x1bhaX\r") - assert result.text == "hellXo" - - # Esc I X - result, cli = feed("hello\x1bIX\r") - assert result.text == "Xhello" - - # Esc I X - result, cli = feed("hello\x1bIX\r") - assert result.text == "Xhello" - - # Esc 2hiX - result, cli = feed("hello\x1b2hiX\r") - assert result.text == "heXllo" - - # Esc 2h2liX - result, cli = feed("hello\x1b2h2liX\r") - assert result.text == "hellXo" - - # Esc \b\b - result, cli = feed("hello\b\b\r") - assert result.text == "hel" - - # Esc \b\b - result, cli = feed("hello\b\b\r") - assert result.text == "hel" - - # Esc 2h D - result, cli = feed("hello\x1b2hD\r") - assert result.text == "he" - - # Esc 2h rX \r - result, cli = feed("hello\x1b2hrX\r") - assert result.text == "heXlo" - - -def test_vi_operators(): - feed = partial(_feed_cli_with_input, editing_mode=EditingMode.VI) - - # Esc g~0 - result, cli = feed("hello\x1bg~0\r") - assert result.text == "HELLo" - - # Esc gU0 - result, cli = feed("hello\x1bgU0\r") - assert result.text == "HELLo" - - # Esc d0 - result, cli = feed("hello\x1bd0\r") - assert result.text == "o" - - -def test_vi_text_objects(): - feed = partial(_feed_cli_with_input, editing_mode=EditingMode.VI) - - # Esc gUgg - result, cli = feed("hello\x1bgUgg\r") - assert result.text == "HELLO" - - # Esc gUU - result, cli = feed("hello\x1bgUU\r") - assert result.text == "HELLO" - - # Esc di( - result, cli = feed("before(inside)after\x1b8hdi(\r") - assert result.text == "before()after" - - # Esc di[ - result, cli = feed("before[inside]after\x1b8hdi[\r") - assert result.text == "before[]after" - - # Esc da( - result, cli = feed("before(inside)after\x1b8hda(\r") - assert result.text == "beforeafter" - - -def test_vi_digraphs(): - feed = partial(_feed_cli_with_input, editing_mode=EditingMode.VI) - - # C-K o/ - result, cli = feed("hello\x0bo/\r") - assert result.text == "helloø" - - # C-K /o (reversed input.) - result, cli = feed("hello\x0b/o\r") - assert result.text == "helloø" - - # C-K e: - result, cli = feed("hello\x0be:\r") - assert result.text == "helloë" - - # C-K xxy (Unknown digraph.) - result, cli = feed("hello\x0bxxy\r") - assert result.text == "helloy" - - -def test_vi_block_editing(): - "Test Vi Control-V style block insertion." - feed = partial(_feed_cli_with_input, editing_mode=EditingMode.VI, multiline=True) - - operations = ( - # Six lines of text. - "-line1\r-line2\r-line3\r-line4\r-line5\r-line6" - # Go to the second character of the second line. - "\x1bkkkkkkkj0l" - # Enter Visual block mode. - "\x16" - # Go down two more lines. - "jj" - # Go 3 characters to the right. - "lll" - # Go to insert mode. - "insert" # (Will be replaced.) - # Insert stars. - "***" - # Escape again. - "\x1b\r" - ) - - # Control-I - result, cli = feed(operations.replace("insert", "I")) - - assert result.text == "-line1\n-***line2\n-***line3\n-***line4\n-line5\n-line6" - - # Control-A - result, cli = feed(operations.replace("insert", "A")) - - assert result.text == "-line1\n-line***2\n-line***3\n-line***4\n-line5\n-line6" - - -def test_vi_block_editing_empty_lines(): - "Test block editing on empty lines." - feed = partial(_feed_cli_with_input, editing_mode=EditingMode.VI, multiline=True) - - operations = ( - # Six empty lines. - "\r\r\r\r\r" - # Go to beginning of the document. - "\x1bgg" - # Enter Visual block mode. - "\x16" - # Go down two more lines. - "jj" - # Go 3 characters to the right. - "lll" - # Go to insert mode. - "insert" # (Will be replaced.) - # Insert stars. - "***" - # Escape again. - "\x1b\r" - ) - - # Control-I - result, cli = feed(operations.replace("insert", "I")) - - assert result.text == "***\n***\n***\n\n\n" - - # Control-A - result, cli = feed(operations.replace("insert", "A")) - - assert result.text == "***\n***\n***\n\n\n" - - -def test_vi_visual_line_copy(): - feed = partial(_feed_cli_with_input, editing_mode=EditingMode.VI, multiline=True) - - operations = ( - # Three lines of text. - "-line1\r-line2\r-line3\r-line4\r-line5\r-line6" - # Go to the second character of the second line. - "\x1bkkkkkkkj0l" - # Enter Visual linemode. - "V" - # Go down one line. - "j" - # Go 3 characters to the right (should not do much). - "lll" - # Copy this block. - "y" - # Go down one line. - "j" - # Insert block twice. - "2p" - # Escape again. - "\x1b\r" - ) - - result, cli = feed(operations) - - assert ( - result.text - == "-line1\n-line2\n-line3\n-line4\n-line2\n-line3\n-line2\n-line3\n-line5\n-line6" - ) - - -def test_vi_visual_empty_line(): - """ - Test edge case with an empty line in Visual-line mode. - """ - feed = partial(_feed_cli_with_input, editing_mode=EditingMode.VI, multiline=True) - - # 1. Delete first two lines. - operations = ( - # Three lines of text. The middle one is empty. - "hello\r\rworld" - # Go to the start. - "\x1bgg" - # Visual line and move down. - "Vj" - # Delete. - "d\r" - ) - result, cli = feed(operations) - assert result.text == "world" - - # 1. Delete middle line. - operations = ( - # Three lines of text. The middle one is empty. - "hello\r\rworld" - # Go to middle line. - "\x1bggj" - # Delete line - "Vd\r" - ) - - result, cli = feed(operations) - assert result.text == "hello\nworld" - - -def test_vi_character_delete_after_cursor(): - "Test 'x' keypress." - feed = partial(_feed_cli_with_input, editing_mode=EditingMode.VI, multiline=True) - - # Delete one character. - result, cli = feed("abcd\x1bHx\r") - assert result.text == "bcd" - - # Delete multiple character.s - result, cli = feed("abcd\x1bH3x\r") - assert result.text == "d" - - # Delete on empty line. - result, cli = feed("\x1bo\x1bo\x1bggx\r") - assert result.text == "\n\n" - - # Delete multiple on empty line. - result, cli = feed("\x1bo\x1bo\x1bgg10x\r") - assert result.text == "\n\n" - - # Delete multiple on empty line. - result, cli = feed("hello\x1bo\x1bo\x1bgg3x\r") - assert result.text == "lo\n\n" - - -def test_vi_character_delete_before_cursor(): - "Test 'X' keypress." - feed = partial(_feed_cli_with_input, editing_mode=EditingMode.VI, multiline=True) - - # Delete one character. - result, cli = feed("abcd\x1bX\r") - assert result.text == "abd" - - # Delete multiple character. - result, cli = feed("hello world\x1b3X\r") - assert result.text == "hello wd" - - # Delete multiple character on multiple lines. - result, cli = feed("hello\x1boworld\x1bgg$3X\r") - assert result.text == "ho\nworld" - - result, cli = feed("hello\x1boworld\x1b100X\r") - assert result.text == "hello\nd" - - # Delete on empty line. - result, cli = feed("\x1bo\x1bo\x1b10X\r") - assert result.text == "\n\n" - - -def test_vi_character_paste(): - feed = partial(_feed_cli_with_input, editing_mode=EditingMode.VI) - - # Test 'p' character paste. - result, cli = feed("abcde\x1bhhxp\r") - assert result.text == "abdce" - assert result.cursor_position == 3 - - # Test 'P' character paste. - result, cli = feed("abcde\x1bhhxP\r") - assert result.text == "abcde" - assert result.cursor_position == 2 - - -def test_vi_temp_navigation_mode(): - """ - Test c-o binding: go for one action into navigation mode. - """ - feed = partial(_feed_cli_with_input, editing_mode=EditingMode.VI) - - result, cli = feed("abcde" "\x0f" "3h" "x\r") # c-o # 3 times to the left. - assert result.text == "axbcde" - assert result.cursor_position == 2 - - result, cli = feed("abcde" "\x0f" "b" "x\r") # c-o # One word backwards. - assert result.text == "xabcde" - assert result.cursor_position == 1 - - # In replace mode - result, cli = feed( - "abcdef" - "\x1b" # Navigation mode. - "0l" # Start of line, one character to the right. - "R" # Replace mode - "78" - "\x0f" # c-o - "l" # One character forwards. - "9\r" - ) - assert result.text == "a78d9f" - assert result.cursor_position == 5 - - -def test_vi_macros(): - feed = partial(_feed_cli_with_input, editing_mode=EditingMode.VI) - - # Record and execute macro. - result, cli = feed("\x1bqcahello\x1bq@c\r") - assert result.text == "hellohello" - assert result.cursor_position == 9 - - # Running unknown macro. - result, cli = feed("\x1b@d\r") - assert result.text == "" - assert result.cursor_position == 0 - - # When a macro is called within a macro. - # It shouldn't result in eternal recursion. - result, cli = feed("\x1bqxahello\x1b@xq@x\r") - assert result.text == "hellohello" - assert result.cursor_position == 9 - - # Nested macros. - result, cli = feed( - # Define macro 'x'. - "\x1bqxahello\x1bq" - # Define macro 'y' which calls 'x'. - "qya\x1b@xaworld\x1bq" - # Delete line. - "2dd" - # Execute 'y' - "@y\r" - ) - - assert result.text == "helloworld" - - -def test_accept_default(): - """ - Test `prompt(accept_default=True)`. - """ - with create_pipe_input() as inp: - session = PromptSession(input=inp, output=DummyOutput()) - result = session.prompt(default="hello", accept_default=True) - assert result == "hello" - - # Test calling prompt() for a second time. (We had an issue where the - # prompt reset between calls happened at the wrong time, breaking this.) - result = session.prompt(default="world", accept_default=True) - assert result == "world" diff --git a/contrib/python/prompt-toolkit/py3/tests/test_completion.py b/contrib/python/prompt-toolkit/py3/tests/test_completion.py deleted file mode 100644 index 6c71620d113..00000000000 --- a/contrib/python/prompt-toolkit/py3/tests/test_completion.py +++ /dev/null @@ -1,469 +0,0 @@ -import os -import re -import shutil -import tempfile -from contextlib import contextmanager - -from prompt_toolkit.completion import ( - CompleteEvent, - DeduplicateCompleter, - FuzzyWordCompleter, - NestedCompleter, - PathCompleter, - WordCompleter, - merge_completers, -) -from prompt_toolkit.document import Document - - -@contextmanager -def chdir(directory): - """Context manager for current working directory temporary change.""" - orig_dir = os.getcwd() - os.chdir(directory) - - try: - yield - finally: - os.chdir(orig_dir) - - -def write_test_files(test_dir, names=None): - """Write test files in test_dir using the names list.""" - names = names or range(10) - for i in names: - with open(os.path.join(test_dir, str(i)), "wb") as out: - out.write(b"") - - -def test_pathcompleter_completes_in_current_directory(): - completer = PathCompleter() - doc_text = "" - doc = Document(doc_text, len(doc_text)) - event = CompleteEvent() - completions = list(completer.get_completions(doc, event)) - assert len(completions) > 0 - - -def test_pathcompleter_completes_files_in_current_directory(): - # setup: create a test dir with 10 files - test_dir = tempfile.mkdtemp() - write_test_files(test_dir) - - expected = sorted(str(i) for i in range(10)) - - if not test_dir.endswith(os.path.sep): - test_dir += os.path.sep - - with chdir(test_dir): - completer = PathCompleter() - # this should complete on the cwd - doc_text = "" - doc = Document(doc_text, len(doc_text)) - event = CompleteEvent() - completions = list(completer.get_completions(doc, event)) - result = sorted(c.text for c in completions) - assert expected == result - - # cleanup - shutil.rmtree(test_dir) - - -def test_pathcompleter_completes_files_in_absolute_directory(): - # setup: create a test dir with 10 files - test_dir = tempfile.mkdtemp() - write_test_files(test_dir) - - expected = sorted(str(i) for i in range(10)) - - test_dir = os.path.abspath(test_dir) - if not test_dir.endswith(os.path.sep): - test_dir += os.path.sep - - completer = PathCompleter() - # force unicode - doc_text = str(test_dir) - doc = Document(doc_text, len(doc_text)) - event = CompleteEvent() - completions = list(completer.get_completions(doc, event)) - result = sorted(c.text for c in completions) - assert expected == result - - # cleanup - shutil.rmtree(test_dir) - - -def test_pathcompleter_completes_directories_with_only_directories(): - # setup: create a test dir with 10 files - test_dir = tempfile.mkdtemp() - write_test_files(test_dir) - - # create a sub directory there - os.mkdir(os.path.join(test_dir, "subdir")) - - if not test_dir.endswith(os.path.sep): - test_dir += os.path.sep - - with chdir(test_dir): - completer = PathCompleter(only_directories=True) - doc_text = "" - doc = Document(doc_text, len(doc_text)) - event = CompleteEvent() - completions = list(completer.get_completions(doc, event)) - result = [c.text for c in completions] - assert ["subdir"] == result - - # check that there is no completion when passing a file - with chdir(test_dir): - completer = PathCompleter(only_directories=True) - doc_text = "1" - doc = Document(doc_text, len(doc_text)) - event = CompleteEvent() - completions = list(completer.get_completions(doc, event)) - assert [] == completions - - # cleanup - shutil.rmtree(test_dir) - - -def test_pathcompleter_respects_completions_under_min_input_len(): - # setup: create a test dir with 10 files - test_dir = tempfile.mkdtemp() - write_test_files(test_dir) - - # min len:1 and no text - with chdir(test_dir): - completer = PathCompleter(min_input_len=1) - doc_text = "" - doc = Document(doc_text, len(doc_text)) - event = CompleteEvent() - completions = list(completer.get_completions(doc, event)) - assert [] == completions - - # min len:1 and text of len 1 - with chdir(test_dir): - completer = PathCompleter(min_input_len=1) - doc_text = "1" - doc = Document(doc_text, len(doc_text)) - event = CompleteEvent() - completions = list(completer.get_completions(doc, event)) - result = [c.text for c in completions] - assert [""] == result - - # min len:0 and text of len 2 - with chdir(test_dir): - completer = PathCompleter(min_input_len=0) - doc_text = "1" - doc = Document(doc_text, len(doc_text)) - event = CompleteEvent() - completions = list(completer.get_completions(doc, event)) - result = [c.text for c in completions] - assert [""] == result - - # create 10 files with a 2 char long name - for i in range(10): - with open(os.path.join(test_dir, str(i) * 2), "wb") as out: - out.write(b"") - - # min len:1 and text of len 1 - with chdir(test_dir): - completer = PathCompleter(min_input_len=1) - doc_text = "2" - doc = Document(doc_text, len(doc_text)) - event = CompleteEvent() - completions = list(completer.get_completions(doc, event)) - result = sorted(c.text for c in completions) - assert ["", "2"] == result - - # min len:2 and text of len 1 - with chdir(test_dir): - completer = PathCompleter(min_input_len=2) - doc_text = "2" - doc = Document(doc_text, len(doc_text)) - event = CompleteEvent() - completions = list(completer.get_completions(doc, event)) - assert [] == completions - - # cleanup - shutil.rmtree(test_dir) - - -def test_pathcompleter_does_not_expanduser_by_default(): - completer = PathCompleter() - doc_text = "~" - doc = Document(doc_text, len(doc_text)) - event = CompleteEvent() - completions = list(completer.get_completions(doc, event)) - assert [] == completions - - -def test_pathcompleter_can_expanduser(monkeypatch): - monkeypatch.setenv('HOME', '/tmp') - completer = PathCompleter(expanduser=True) - doc_text = "~" - doc = Document(doc_text, len(doc_text)) - event = CompleteEvent() - completions = list(completer.get_completions(doc, event)) - assert len(completions) > 0 - - -def test_pathcompleter_can_apply_file_filter(): - # setup: create a test dir with 10 files - test_dir = tempfile.mkdtemp() - write_test_files(test_dir) - - # add a .csv file - with open(os.path.join(test_dir, "my.csv"), "wb") as out: - out.write(b"") - - file_filter = lambda f: f and f.endswith(".csv") - - with chdir(test_dir): - completer = PathCompleter(file_filter=file_filter) - doc_text = "" - doc = Document(doc_text, len(doc_text)) - event = CompleteEvent() - completions = list(completer.get_completions(doc, event)) - result = [c.text for c in completions] - assert ["my.csv"] == result - - # cleanup - shutil.rmtree(test_dir) - - -def test_pathcompleter_get_paths_constrains_path(): - # setup: create a test dir with 10 files - test_dir = tempfile.mkdtemp() - write_test_files(test_dir) - - # add a subdir with 10 other files with different names - subdir = os.path.join(test_dir, "subdir") - os.mkdir(subdir) - write_test_files(subdir, "abcdefghij") - - get_paths = lambda: ["subdir"] - - with chdir(test_dir): - completer = PathCompleter(get_paths=get_paths) - doc_text = "" - doc = Document(doc_text, len(doc_text)) - event = CompleteEvent() - completions = list(completer.get_completions(doc, event)) - result = [c.text for c in completions] - expected = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"] - assert expected == result - - # cleanup - shutil.rmtree(test_dir) - - -def test_word_completer_static_word_list(): - completer = WordCompleter(["abc", "def", "aaa"]) - - # Static list on empty input. - completions = completer.get_completions(Document(""), CompleteEvent()) - assert [c.text for c in completions] == ["abc", "def", "aaa"] - - # Static list on non-empty input. - completions = completer.get_completions(Document("a"), CompleteEvent()) - assert [c.text for c in completions] == ["abc", "aaa"] - - completions = completer.get_completions(Document("A"), CompleteEvent()) - assert [c.text for c in completions] == [] - - # Multiple words ending with space. (Accept all options) - completions = completer.get_completions(Document("test "), CompleteEvent()) - assert [c.text for c in completions] == ["abc", "def", "aaa"] - - # Multiple words. (Check last only.) - completions = completer.get_completions(Document("test a"), CompleteEvent()) - assert [c.text for c in completions] == ["abc", "aaa"] - - -def test_word_completer_ignore_case(): - completer = WordCompleter(["abc", "def", "aaa"], ignore_case=True) - completions = completer.get_completions(Document("a"), CompleteEvent()) - assert [c.text for c in completions] == ["abc", "aaa"] - - completions = completer.get_completions(Document("A"), CompleteEvent()) - assert [c.text for c in completions] == ["abc", "aaa"] - - -def test_word_completer_match_middle(): - completer = WordCompleter(["abc", "def", "abca"], match_middle=True) - completions = completer.get_completions(Document("bc"), CompleteEvent()) - assert [c.text for c in completions] == ["abc", "abca"] - - -def test_word_completer_sentence(): - # With sentence=True - completer = WordCompleter( - ["hello world", "www", "hello www", "hello there"], sentence=True - ) - completions = completer.get_completions(Document("hello w"), CompleteEvent()) - assert [c.text for c in completions] == ["hello world", "hello www"] - - # With sentence=False - completer = WordCompleter( - ["hello world", "www", "hello www", "hello there"], sentence=False - ) - completions = completer.get_completions(Document("hello w"), CompleteEvent()) - assert [c.text for c in completions] == ["www"] - - -def test_word_completer_dynamic_word_list(): - called = [0] - - def get_words(): - called[0] += 1 - return ["abc", "def", "aaa"] - - completer = WordCompleter(get_words) - - # Dynamic list on empty input. - completions = completer.get_completions(Document(""), CompleteEvent()) - assert [c.text for c in completions] == ["abc", "def", "aaa"] - assert called[0] == 1 - - # Static list on non-empty input. - completions = completer.get_completions(Document("a"), CompleteEvent()) - assert [c.text for c in completions] == ["abc", "aaa"] - assert called[0] == 2 - - -def test_word_completer_pattern(): - # With a pattern which support '.' - completer = WordCompleter( - ["abc", "a.b.c", "a.b", "xyz"], - pattern=re.compile(r"^([a-zA-Z0-9_.]+|[^a-zA-Z0-9_.\s]+)"), - ) - completions = completer.get_completions(Document("a."), CompleteEvent()) - assert [c.text for c in completions] == ["a.b.c", "a.b"] - - # Without pattern - completer = WordCompleter(["abc", "a.b.c", "a.b", "xyz"]) - completions = completer.get_completions(Document("a."), CompleteEvent()) - assert [c.text for c in completions] == [] - - -def test_fuzzy_completer(): - collection = [ - "migrations.py", - "django_migrations.py", - "django_admin_log.py", - "api_user.doc", - "user_group.doc", - "users.txt", - "accounts.txt", - "123.py", - "test123test.py", - ] - completer = FuzzyWordCompleter(collection) - completions = completer.get_completions(Document("txt"), CompleteEvent()) - assert [c.text for c in completions] == ["users.txt", "accounts.txt"] - - completions = completer.get_completions(Document("djmi"), CompleteEvent()) - assert [c.text for c in completions] == [ - "django_migrations.py", - "django_admin_log.py", - ] - - completions = completer.get_completions(Document("mi"), CompleteEvent()) - assert [c.text for c in completions] == [ - "migrations.py", - "django_migrations.py", - "django_admin_log.py", - ] - - completions = completer.get_completions(Document("user"), CompleteEvent()) - assert [c.text for c in completions] == [ - "user_group.doc", - "users.txt", - "api_user.doc", - ] - - completions = completer.get_completions(Document("123"), CompleteEvent()) - assert [c.text for c in completions] == ["123.py", "test123test.py"] - - completions = completer.get_completions(Document("miGr"), CompleteEvent()) - assert [c.text for c in completions] == [ - "migrations.py", - "django_migrations.py", - ] - - # Multiple words ending with space. (Accept all options) - completions = completer.get_completions(Document("test "), CompleteEvent()) - assert [c.text for c in completions] == collection - - # Multiple words. (Check last only.) - completions = completer.get_completions(Document("test txt"), CompleteEvent()) - assert [c.text for c in completions] == ["users.txt", "accounts.txt"] - - -def test_nested_completer(): - completer = NestedCompleter.from_nested_dict( - { - "show": { - "version": None, - "clock": None, - "interfaces": None, - "ip": {"interface": {"brief"}}, - }, - "exit": None, - } - ) - - # Empty input. - completions = completer.get_completions(Document(""), CompleteEvent()) - assert {c.text for c in completions} == {"show", "exit"} - - # One character. - completions = completer.get_completions(Document("s"), CompleteEvent()) - assert {c.text for c in completions} == {"show"} - - # One word. - completions = completer.get_completions(Document("show"), CompleteEvent()) - assert {c.text for c in completions} == {"show"} - - # One word + space. - completions = completer.get_completions(Document("show "), CompleteEvent()) - assert {c.text for c in completions} == {"version", "clock", "interfaces", "ip"} - - # One word + space + one character. - completions = completer.get_completions(Document("show i"), CompleteEvent()) - assert {c.text for c in completions} == {"ip", "interfaces"} - - # One space + one word + space + one character. - completions = completer.get_completions(Document(" show i"), CompleteEvent()) - assert {c.text for c in completions} == {"ip", "interfaces"} - - # Test nested set. - completions = completer.get_completions( - Document("show ip interface br"), CompleteEvent() - ) - assert {c.text for c in completions} == {"brief"} - - -def test_deduplicate_completer(): - def create_completer(deduplicate: bool): - return merge_completers( - [ - WordCompleter(["hello", "world", "abc", "def"]), - WordCompleter(["xyz", "xyz", "abc", "def"]), - ], - deduplicate=deduplicate, - ) - - completions = list( - create_completer(deduplicate=False).get_completions( - Document(""), CompleteEvent() - ) - ) - assert len(completions) == 8 - - completions = list( - create_completer(deduplicate=True).get_completions( - Document(""), CompleteEvent() - ) - ) - assert len(completions) == 5 diff --git a/contrib/python/prompt-toolkit/py3/tests/test_document.py b/contrib/python/prompt-toolkit/py3/tests/test_document.py deleted file mode 100644 index ba65ffc8342..00000000000 --- a/contrib/python/prompt-toolkit/py3/tests/test_document.py +++ /dev/null @@ -1,67 +0,0 @@ -import pytest - -from prompt_toolkit.document import Document - - -@pytest.fixture -def document(): - return Document( - "line 1\n" + "line 2\n" + "line 3\n" + "line 4\n", len("line 1\n" + "lin") - ) - - -def test_current_char(document): - assert document.current_char == "e" - assert document.char_before_cursor == "n" - - -def test_text_before_cursor(document): - assert document.text_before_cursor == "line 1\nlin" - - -def test_text_after_cursor(document): - assert document.text_after_cursor == "e 2\n" + "line 3\n" + "line 4\n" - - -def test_lines(document): - assert document.lines == ["line 1", "line 2", "line 3", "line 4", ""] - - -def test_line_count(document): - assert document.line_count == 5 - - -def test_current_line_before_cursor(document): - assert document.current_line_before_cursor == "lin" - - -def test_current_line_after_cursor(document): - assert document.current_line_after_cursor == "e 2" - - -def test_current_line(document): - assert document.current_line == "line 2" - - -def test_cursor_position(document): - assert document.cursor_position_row == 1 - assert document.cursor_position_col == 3 - - d = Document("", 0) - assert d.cursor_position_row == 0 - assert d.cursor_position_col == 0 - - -def test_translate_index_to_position(document): - pos = document.translate_index_to_position(len("line 1\nline 2\nlin")) - - assert pos[0] == 2 - assert pos[1] == 3 - - pos = document.translate_index_to_position(0) - assert pos == (0, 0) - - -def test_is_cursor_at_the_end(document): - assert Document("hello", 5).is_cursor_at_the_end - assert not Document("hello", 4).is_cursor_at_the_end diff --git a/contrib/python/prompt-toolkit/py3/tests/test_filter.py b/contrib/python/prompt-toolkit/py3/tests/test_filter.py deleted file mode 100644 index e47b3b5cfa5..00000000000 --- a/contrib/python/prompt-toolkit/py3/tests/test_filter.py +++ /dev/null @@ -1,60 +0,0 @@ -import pytest - -from prompt_toolkit.filters import Always, Condition, Filter, Never, to_filter - - -def test_never(): - assert not Never()() - - -def test_always(): - assert Always()() - - -def test_invert(): - assert not (~Always())() - assert ~Never()() - - c = ~Condition(lambda: False) - assert c() - - -def test_or(): - for a in (True, False): - for b in (True, False): - c1 = Condition(lambda: a) - c2 = Condition(lambda: b) - c3 = c1 | c2 - - assert isinstance(c3, Filter) - assert c3() == a or b - - -def test_and(): - for a in (True, False): - for b in (True, False): - c1 = Condition(lambda: a) - c2 = Condition(lambda: b) - c3 = c1 & c2 - - assert isinstance(c3, Filter) - assert c3() == (a and b) - - -def test_to_filter(): - f1 = to_filter(True) - f2 = to_filter(False) - f3 = to_filter(Condition(lambda: True)) - f4 = to_filter(Condition(lambda: False)) - - assert isinstance(f1, Filter) - assert isinstance(f2, Filter) - assert isinstance(f3, Filter) - assert isinstance(f4, Filter) - assert f1() - assert not f2() - assert f3() - assert not f4() - - with pytest.raises(TypeError): - to_filter(4) diff --git a/contrib/python/prompt-toolkit/py3/tests/test_formatted_text.py b/contrib/python/prompt-toolkit/py3/tests/test_formatted_text.py deleted file mode 100644 index 8b4924f968a..00000000000 --- a/contrib/python/prompt-toolkit/py3/tests/test_formatted_text.py +++ /dev/null @@ -1,284 +0,0 @@ -from prompt_toolkit.formatted_text import ( - ANSI, - HTML, - FormattedText, - PygmentsTokens, - Template, - merge_formatted_text, - to_formatted_text, -) -from prompt_toolkit.formatted_text.utils import split_lines - - -def test_basic_html(): - html = HTML("<i>hello</i>") - assert to_formatted_text(html) == [("class:i", "hello")] - - html = HTML("<i><b>hello</b></i>") - assert to_formatted_text(html) == [("class:i,b", "hello")] - - html = HTML("<i><b>hello</b>world<strong>test</strong></i>after") - assert to_formatted_text(html) == [ - ("class:i,b", "hello"), - ("class:i", "world"), - ("class:i,strong", "test"), - ("", "after"), - ] - - # It's important that `to_formatted_text` returns a `FormattedText` - # instance. Otherwise, `print_formatted_text` won't recognise it and will - # print a list literal instead. - assert isinstance(to_formatted_text(html), FormattedText) - - -def test_html_with_fg_bg(): - html = HTML('<style bg="ansired">hello</style>') - assert to_formatted_text(html) == [ - ("bg:ansired", "hello"), - ] - - html = HTML('<style bg="ansired" fg="#ff0000">hello</style>') - assert to_formatted_text(html) == [ - ("fg:#ff0000 bg:ansired", "hello"), - ] - - html = HTML( - '<style bg="ansired" fg="#ff0000">hello <world fg="ansiblue">world</world></style>' - ) - assert to_formatted_text(html) == [ - ("fg:#ff0000 bg:ansired", "hello "), - ("class:world fg:ansiblue bg:ansired", "world"), - ] - - -def test_ansi_formatting(): - value = ANSI("\x1b[32mHe\x1b[45mllo") - - assert to_formatted_text(value) == [ - ("ansigreen", "H"), - ("ansigreen", "e"), - ("ansigreen bg:ansimagenta", "l"), - ("ansigreen bg:ansimagenta", "l"), - ("ansigreen bg:ansimagenta", "o"), - ] - - # Bold and italic. - value = ANSI("\x1b[1mhe\x1b[0mllo") - - assert to_formatted_text(value) == [ - ("bold", "h"), - ("bold", "e"), - ("", "l"), - ("", "l"), - ("", "o"), - ] - - # Zero width escapes. - value = ANSI("ab\001cd\002ef") - - assert to_formatted_text(value) == [ - ("", "a"), - ("", "b"), - ("[ZeroWidthEscape]", "cd"), - ("", "e"), - ("", "f"), - ] - - assert isinstance(to_formatted_text(value), FormattedText) - - -def test_ansi_256_color(): - assert to_formatted_text(ANSI("\x1b[38;5;124mtest")) == [ - ("#af0000", "t"), - ("#af0000", "e"), - ("#af0000", "s"), - ("#af0000", "t"), - ] - - -def test_ansi_true_color(): - assert to_formatted_text(ANSI("\033[38;2;144;238;144m$\033[0;39;49m ")) == [ - ("#90ee90", "$"), - ("ansidefault bg:ansidefault", " "), - ] - - -def test_ansi_interpolation(): - # %-style interpolation. - value = ANSI("\x1b[1m%s\x1b[0m") % "hello\x1b" - assert to_formatted_text(value) == [ - ("bold", "h"), - ("bold", "e"), - ("bold", "l"), - ("bold", "l"), - ("bold", "o"), - ("bold", "?"), - ] - - value = ANSI("\x1b[1m%s\x1b[0m") % ("\x1bhello",) - assert to_formatted_text(value) == [ - ("bold", "?"), - ("bold", "h"), - ("bold", "e"), - ("bold", "l"), - ("bold", "l"), - ("bold", "o"), - ] - - value = ANSI("\x1b[32m%s\x1b[45m%s") % ("He", "\x1bllo") - assert to_formatted_text(value) == [ - ("ansigreen", "H"), - ("ansigreen", "e"), - ("ansigreen bg:ansimagenta", "?"), - ("ansigreen bg:ansimagenta", "l"), - ("ansigreen bg:ansimagenta", "l"), - ("ansigreen bg:ansimagenta", "o"), - ] - - # Format function. - value = ANSI("\x1b[32m{0}\x1b[45m{1}").format("He\x1b", "llo") - assert to_formatted_text(value) == [ - ("ansigreen", "H"), - ("ansigreen", "e"), - ("ansigreen", "?"), - ("ansigreen bg:ansimagenta", "l"), - ("ansigreen bg:ansimagenta", "l"), - ("ansigreen bg:ansimagenta", "o"), - ] - - value = ANSI("\x1b[32m{a}\x1b[45m{b}").format(a="\x1bHe", b="llo") - assert to_formatted_text(value) == [ - ("ansigreen", "?"), - ("ansigreen", "H"), - ("ansigreen", "e"), - ("ansigreen bg:ansimagenta", "l"), - ("ansigreen bg:ansimagenta", "l"), - ("ansigreen bg:ansimagenta", "o"), - ] - - value = ANSI("\x1b[32m{:02d}\x1b[45m{:.3f}").format(3, 3.14159) - assert to_formatted_text(value) == [ - ("ansigreen", "0"), - ("ansigreen", "3"), - ("ansigreen bg:ansimagenta", "3"), - ("ansigreen bg:ansimagenta", "."), - ("ansigreen bg:ansimagenta", "1"), - ("ansigreen bg:ansimagenta", "4"), - ("ansigreen bg:ansimagenta", "2"), - ] - - -def test_interpolation(): - value = Template(" {} ").format(HTML("<b>hello</b>")) - - assert to_formatted_text(value) == [ - ("", " "), - ("class:b", "hello"), - ("", " "), - ] - - value = Template("a{}b{}c").format(HTML("<b>hello</b>"), "world") - - assert to_formatted_text(value) == [ - ("", "a"), - ("class:b", "hello"), - ("", "b"), - ("", "world"), - ("", "c"), - ] - - -def test_html_interpolation(): - # %-style interpolation. - value = HTML("<b>%s</b>") % "&hello" - assert to_formatted_text(value) == [("class:b", "&hello")] - - value = HTML("<b>%s</b>") % ("<hello>",) - assert to_formatted_text(value) == [("class:b", "<hello>")] - - value = HTML("<b>%s</b><u>%s</u>") % ("<hello>", "</world>") - assert to_formatted_text(value) == [("class:b", "<hello>"), ("class:u", "</world>")] - - # Format function. - value = HTML("<b>{0}</b><u>{1}</u>").format("'hello'", '"world"') - assert to_formatted_text(value) == [("class:b", "'hello'"), ("class:u", '"world"')] - - value = HTML("<b>{a}</b><u>{b}</u>").format(a="hello", b="world") - assert to_formatted_text(value) == [("class:b", "hello"), ("class:u", "world")] - - value = HTML("<b>{:02d}</b><u>{:.3f}</u>").format(3, 3.14159) - assert to_formatted_text(value) == [("class:b", "03"), ("class:u", "3.142")] - - -def test_merge_formatted_text(): - html1 = HTML("<u>hello</u>") - html2 = HTML("<b>world</b>") - result = merge_formatted_text([html1, html2]) - - assert to_formatted_text(result) == [ - ("class:u", "hello"), - ("class:b", "world"), - ] - - -def test_pygments_tokens(): - text = [ - (("A", "B"), "hello"), # Token.A.B - (("C", "D", "E"), "hello"), # Token.C.D.E - ((), "world"), # Token - ] - - assert to_formatted_text(PygmentsTokens(text)) == [ - ("class:pygments.a.b", "hello"), - ("class:pygments.c.d.e", "hello"), - ("class:pygments", "world"), - ] - - -def test_split_lines(): - lines = list(split_lines([("class:a", "line1\nline2\nline3")])) - - assert lines == [ - [("class:a", "line1")], - [("class:a", "line2")], - [("class:a", "line3")], - ] - - -def test_split_lines_2(): - lines = list( - split_lines([("class:a", "line1"), ("class:b", "line2\nline3\nline4")]) - ) - - assert lines == [ - [("class:a", "line1"), ("class:b", "line2")], - [("class:b", "line3")], - [("class:b", "line4")], - ] - - -def test_split_lines_3(): - "Edge cases: inputs ending with newlines." - # -1- - lines = list(split_lines([("class:a", "line1\nline2\n")])) - - assert lines == [ - [("class:a", "line1")], - [("class:a", "line2")], - [("class:a", "")], - ] - - # -2- - lines = list(split_lines([("class:a", "\n")])) - - assert lines == [ - [], - [("class:a", "")], - ] - - # -3- - lines = list(split_lines([("class:a", "")])) - - assert lines == [ - [("class:a", "")], - ] diff --git a/contrib/python/prompt-toolkit/py3/tests/test_history.py b/contrib/python/prompt-toolkit/py3/tests/test_history.py deleted file mode 100644 index 07db8117e60..00000000000 --- a/contrib/python/prompt-toolkit/py3/tests/test_history.py +++ /dev/null @@ -1,100 +0,0 @@ -from prompt_toolkit.eventloop import get_event_loop -from prompt_toolkit.history import FileHistory, InMemoryHistory, ThreadedHistory - - -def _call_history_load(history): - """ - Helper: Call the history "load" method and return the result as a list of strings. - """ - result = [] - - async def call_load(): - async for item in history.load(): - result.append(item) - - get_event_loop().run_until_complete(call_load()) - return result - - -def test_in_memory_history(): - history = InMemoryHistory() - history.append_string("hello") - history.append_string("world") - - # Newest should yield first. - assert _call_history_load(history) == ["world", "hello"] - - # Test another call. - assert _call_history_load(history) == ["world", "hello"] - - history.append_string("test3") - assert _call_history_load(history) == ["test3", "world", "hello"] - - # Passing history as a parameter. - history2 = InMemoryHistory(["abc", "def"]) - assert _call_history_load(history2) == ["def", "abc"] - - -def test_file_history(tmpdir): - histfile = tmpdir.join("history") - - history = FileHistory(histfile) - - history.append_string("hello") - history.append_string("world") - - # Newest should yield first. - assert _call_history_load(history) == ["world", "hello"] - - # Test another call. - assert _call_history_load(history) == ["world", "hello"] - - history.append_string("test3") - assert _call_history_load(history) == ["test3", "world", "hello"] - - # Create another history instance pointing to the same file. - history2 = FileHistory(histfile) - assert _call_history_load(history2) == ["test3", "world", "hello"] - - -def test_threaded_file_history(tmpdir): - histfile = tmpdir.join("history") - - history = ThreadedHistory(FileHistory(histfile)) - - history.append_string("hello") - history.append_string("world") - - # Newest should yield first. - assert _call_history_load(history) == ["world", "hello"] - - # Test another call. - assert _call_history_load(history) == ["world", "hello"] - - history.append_string("test3") - assert _call_history_load(history) == ["test3", "world", "hello"] - - # Create another history instance pointing to the same file. - history2 = ThreadedHistory(FileHistory(histfile)) - assert _call_history_load(history2) == ["test3", "world", "hello"] - - -def test_threaded_in_memory_history(): - # Threaded in memory history is not useful. But testing it anyway, just to - # see whether everything plays nicely together. - history = ThreadedHistory(InMemoryHistory()) - history.append_string("hello") - history.append_string("world") - - # Newest should yield first. - assert _call_history_load(history) == ["world", "hello"] - - # Test another call. - assert _call_history_load(history) == ["world", "hello"] - - history.append_string("test3") - assert _call_history_load(history) == ["test3", "world", "hello"] - - # Passing history as a parameter. - history2 = ThreadedHistory(InMemoryHistory(["abc", "def"])) - assert _call_history_load(history2) == ["def", "abc"] diff --git a/contrib/python/prompt-toolkit/py3/tests/test_inputstream.py b/contrib/python/prompt-toolkit/py3/tests/test_inputstream.py deleted file mode 100644 index 8c3d8fd7c45..00000000000 --- a/contrib/python/prompt-toolkit/py3/tests/test_inputstream.py +++ /dev/null @@ -1,139 +0,0 @@ -import pytest - -from prompt_toolkit.input.vt100_parser import Vt100Parser -from prompt_toolkit.keys import Keys - - -class _ProcessorMock: - def __init__(self): - self.keys = [] - - def feed_key(self, key_press): - self.keys.append(key_press) - - -@pytest.fixture -def processor(): - return _ProcessorMock() - - -@pytest.fixture -def stream(processor): - return Vt100Parser(processor.feed_key) - - -def test_control_keys(processor, stream): - stream.feed("\x01\x02\x10") - - assert len(processor.keys) == 3 - assert processor.keys[0].key == Keys.ControlA - assert processor.keys[1].key == Keys.ControlB - assert processor.keys[2].key == Keys.ControlP - assert processor.keys[0].data == "\x01" - assert processor.keys[1].data == "\x02" - assert processor.keys[2].data == "\x10" - - -def test_arrows(processor, stream): - stream.feed("\x1b[A\x1b[B\x1b[C\x1b[D") - - assert len(processor.keys) == 4 - assert processor.keys[0].key == Keys.Up - assert processor.keys[1].key == Keys.Down - assert processor.keys[2].key == Keys.Right - assert processor.keys[3].key == Keys.Left - assert processor.keys[0].data == "\x1b[A" - assert processor.keys[1].data == "\x1b[B" - assert processor.keys[2].data == "\x1b[C" - assert processor.keys[3].data == "\x1b[D" - - -def test_escape(processor, stream): - stream.feed("\x1bhello") - - assert len(processor.keys) == 1 + len("hello") - assert processor.keys[0].key == Keys.Escape - assert processor.keys[1].key == "h" - assert processor.keys[0].data == "\x1b" - assert processor.keys[1].data == "h" - - -def test_special_double_keys(processor, stream): - stream.feed("\x1b[1;3D") # Should both send escape and left. - - assert len(processor.keys) == 2 - assert processor.keys[0].key == Keys.Escape - assert processor.keys[1].key == Keys.Left - assert processor.keys[0].data == "\x1b[1;3D" - assert processor.keys[1].data == "" - - -def test_flush_1(processor, stream): - # Send left key in two parts without flush. - stream.feed("\x1b") - stream.feed("[D") - - assert len(processor.keys) == 1 - assert processor.keys[0].key == Keys.Left - assert processor.keys[0].data == "\x1b[D" - - -def test_flush_2(processor, stream): - # Send left key with a 'Flush' in between. - # The flush should make sure that we process everything before as-is, - # with makes the first part just an escape character instead. - stream.feed("\x1b") - stream.flush() - stream.feed("[D") - - assert len(processor.keys) == 3 - assert processor.keys[0].key == Keys.Escape - assert processor.keys[1].key == "[" - assert processor.keys[2].key == "D" - - assert processor.keys[0].data == "\x1b" - assert processor.keys[1].data == "[" - assert processor.keys[2].data == "D" - - -def test_meta_arrows(processor, stream): - stream.feed("\x1b\x1b[D") - - assert len(processor.keys) == 2 - assert processor.keys[0].key == Keys.Escape - assert processor.keys[1].key == Keys.Left - - -def test_control_square_close(processor, stream): - stream.feed("\x1dC") - - assert len(processor.keys) == 2 - assert processor.keys[0].key == Keys.ControlSquareClose - assert processor.keys[1].key == "C" - - -def test_invalid(processor, stream): - # Invalid sequence that has at two characters in common with other - # sequences. - stream.feed("\x1b[*") - - assert len(processor.keys) == 3 - assert processor.keys[0].key == Keys.Escape - assert processor.keys[1].key == "[" - assert processor.keys[2].key == "*" - - -def test_cpr_response(processor, stream): - stream.feed("a\x1b[40;10Rb") - assert len(processor.keys) == 3 - assert processor.keys[0].key == "a" - assert processor.keys[1].key == Keys.CPRResponse - assert processor.keys[2].key == "b" - - -def test_cpr_response_2(processor, stream): - # Make sure that the newline is not included in the CPR response. - stream.feed("\x1b[40;1R\n") - assert len(processor.keys) == 2 - assert processor.keys[0].key == Keys.CPRResponse - assert processor.keys[1].key == Keys.ControlJ diff --git a/contrib/python/prompt-toolkit/py3/tests/test_key_binding.py b/contrib/python/prompt-toolkit/py3/tests/test_key_binding.py deleted file mode 100644 index 6f03f2deab4..00000000000 --- a/contrib/python/prompt-toolkit/py3/tests/test_key_binding.py +++ /dev/null @@ -1,188 +0,0 @@ -from contextlib import contextmanager - -import pytest - -from prompt_toolkit.application import Application -from prompt_toolkit.application.current import set_app -from prompt_toolkit.input.defaults import create_pipe_input -from prompt_toolkit.key_binding.key_bindings import KeyBindings -from prompt_toolkit.key_binding.key_processor import KeyPress, KeyProcessor -from prompt_toolkit.keys import Keys -from prompt_toolkit.layout import Layout, Window -from prompt_toolkit.output import DummyOutput - - -class Handlers: - def __init__(self): - self.called = [] - - def __getattr__(self, name): - def func(event): - self.called.append(name) - - return func - - -@contextmanager -def set_dummy_app(): - """ - Return a context manager that makes sure that this dummy application is - active. This is important, because we need an `Application` with - `is_done=False` flag, otherwise no keys will be processed. - """ - with create_pipe_input() as pipe_input: - app = Application( - layout=Layout(Window()), - output=DummyOutput(), - input=pipe_input, - ) - with set_app(app): - yield - - -@pytest.fixture -def handlers(): - return Handlers() - - -@pytest.fixture -def bindings(handlers): - bindings = KeyBindings() - bindings.add(Keys.ControlX, Keys.ControlC)(handlers.controlx_controlc) - bindings.add(Keys.ControlX)(handlers.control_x) - bindings.add(Keys.ControlD)(handlers.control_d) - bindings.add(Keys.ControlSquareClose, Keys.Any)(handlers.control_square_close_any) - - return bindings - - -@pytest.fixture -def processor(bindings): - return KeyProcessor(bindings) - - -def test_remove_bindings(handlers): - with set_dummy_app(): - h = handlers.controlx_controlc - h2 = handlers.controld - - # Test passing a handler to the remove() function. - bindings = KeyBindings() - bindings.add(Keys.ControlX, Keys.ControlC)(h) - bindings.add(Keys.ControlD)(h2) - assert len(bindings.bindings) == 2 - bindings.remove(h) - assert len(bindings.bindings) == 1 - - # Test passing a key sequence to the remove() function. - bindings = KeyBindings() - bindings.add(Keys.ControlX, Keys.ControlC)(h) - bindings.add(Keys.ControlD)(h2) - assert len(bindings.bindings) == 2 - bindings.remove(Keys.ControlX, Keys.ControlC) - assert len(bindings.bindings) == 1 - - -def test_feed_simple(processor, handlers): - with set_dummy_app(): - processor.feed(KeyPress(Keys.ControlX, "\x18")) - processor.feed(KeyPress(Keys.ControlC, "\x03")) - processor.process_keys() - - assert handlers.called == ["controlx_controlc"] - - -def test_feed_several(processor, handlers): - with set_dummy_app(): - # First an unknown key first. - processor.feed(KeyPress(Keys.ControlQ, "")) - processor.process_keys() - - assert handlers.called == [] - - # Followed by a know key sequence. - processor.feed(KeyPress(Keys.ControlX, "")) - processor.feed(KeyPress(Keys.ControlC, "")) - processor.process_keys() - - assert handlers.called == ["controlx_controlc"] - - # Followed by another unknown sequence. - processor.feed(KeyPress(Keys.ControlR, "")) - processor.feed(KeyPress(Keys.ControlS, "")) - - # Followed again by a know key sequence. - processor.feed(KeyPress(Keys.ControlD, "")) - processor.process_keys() - - assert handlers.called == ["controlx_controlc", "control_d"] - - -def test_control_square_closed_any(processor, handlers): - with set_dummy_app(): - processor.feed(KeyPress(Keys.ControlSquareClose, "")) - processor.feed(KeyPress("C", "C")) - processor.process_keys() - - assert handlers.called == ["control_square_close_any"] - - -def test_common_prefix(processor, handlers): - with set_dummy_app(): - # Sending Control_X should not yet do anything, because there is - # another sequence starting with that as well. - processor.feed(KeyPress(Keys.ControlX, "")) - processor.process_keys() - - assert handlers.called == [] - - # When another key is pressed, we know that we did not meant the longer - # "ControlX ControlC" sequence and the callbacks are called. - processor.feed(KeyPress(Keys.ControlD, "")) - processor.process_keys() - - assert handlers.called == ["control_x", "control_d"] - - -def test_previous_key_sequence(processor): - """ - test whether we receive the correct previous_key_sequence. - """ - with set_dummy_app(): - events = [] - - def handler(event): - events.append(event) - - # Build registry. - registry = KeyBindings() - registry.add("a", "a")(handler) - registry.add("b", "b")(handler) - processor = KeyProcessor(registry) - - # Create processor and feed keys. - processor.feed(KeyPress("a", "a")) - processor.feed(KeyPress("a", "a")) - processor.feed(KeyPress("b", "b")) - processor.feed(KeyPress("b", "b")) - processor.process_keys() - - # Test. - assert len(events) == 2 - assert len(events[0].key_sequence) == 2 - assert events[0].key_sequence[0].key == "a" - assert events[0].key_sequence[0].data == "a" - assert events[0].key_sequence[1].key == "a" - assert events[0].key_sequence[1].data == "a" - assert events[0].previous_key_sequence == [] - - assert len(events[1].key_sequence) == 2 - assert events[1].key_sequence[0].key == "b" - assert events[1].key_sequence[0].data == "b" - assert events[1].key_sequence[1].key == "b" - assert events[1].key_sequence[1].data == "b" - assert len(events[1].previous_key_sequence) == 2 - assert events[1].previous_key_sequence[0].key == "a" - assert events[1].previous_key_sequence[0].data == "a" - assert events[1].previous_key_sequence[1].key == "a" - assert events[1].previous_key_sequence[1].data == "a" diff --git a/contrib/python/prompt-toolkit/py3/tests/test_layout.py b/contrib/python/prompt-toolkit/py3/tests/test_layout.py deleted file mode 100644 index 8a0826d33bf..00000000000 --- a/contrib/python/prompt-toolkit/py3/tests/test_layout.py +++ /dev/null @@ -1,51 +0,0 @@ -import pytest - -from prompt_toolkit.layout import InvalidLayoutError, Layout -from prompt_toolkit.layout.containers import HSplit, VSplit, Window -from prompt_toolkit.layout.controls import BufferControl - - -def test_layout_class(): - c1 = BufferControl() - c2 = BufferControl() - c3 = BufferControl() - win1 = Window(content=c1) - win2 = Window(content=c2) - win3 = Window(content=c3) - - layout = Layout(container=VSplit([HSplit([win1, win2]), win3])) - - # Listing of windows/controls. - assert list(layout.find_all_windows()) == [win1, win2, win3] - assert list(layout.find_all_controls()) == [c1, c2, c3] - - # Focusing something. - layout.focus(c1) - assert layout.has_focus(c1) - assert layout.has_focus(win1) - assert layout.current_control == c1 - assert layout.previous_control == c1 - - layout.focus(c2) - assert layout.has_focus(c2) - assert layout.has_focus(win2) - assert layout.current_control == c2 - assert layout.previous_control == c1 - - layout.focus(win3) - assert layout.has_focus(c3) - assert layout.has_focus(win3) - assert layout.current_control == c3 - assert layout.previous_control == c2 - - # Pop focus. This should focus the previous control again. - layout.focus_last() - assert layout.has_focus(c2) - assert layout.has_focus(win2) - assert layout.current_control == c2 - assert layout.previous_control == c1 - - -def test_create_invalid_layout(): - with pytest.raises(InvalidLayoutError): - Layout(HSplit([])) diff --git a/contrib/python/prompt-toolkit/py3/tests/test_print_formatted_text.py b/contrib/python/prompt-toolkit/py3/tests/test_print_formatted_text.py deleted file mode 100644 index 6a344a732d3..00000000000 --- a/contrib/python/prompt-toolkit/py3/tests/test_print_formatted_text.py +++ /dev/null @@ -1,90 +0,0 @@ -""" -Test the `print` function. -""" -import pytest - -from prompt_toolkit import print_formatted_text as pt_print -from prompt_toolkit.formatted_text import HTML, FormattedText, to_formatted_text -from prompt_toolkit.output import ColorDepth -from prompt_toolkit.styles import Style -from prompt_toolkit.utils import is_windows - - -class _Capture: - "Emulate an stdout object." - - def __init__(self): - self._data = [] - - def write(self, data): - self._data.append(data) - - @property - def data(self): - return "".join(self._data) - - def flush(self): - pass - - def isatty(self): - return True - - def fileno(self): - # File descriptor is not used for printing formatted text. - # (It is only needed for getting the terminal size.) - return -1 - - -@pytest.mark.skipif(is_windows(), reason="Doesn't run on Windows yet.") -def test_print_formatted_text(): - f = _Capture() - pt_print([("", "hello"), ("", "world")], file=f) - assert "hello" in f.data - assert "world" in f.data - - -@pytest.mark.skipif(is_windows(), reason="Doesn't run on Windows yet.") -def test_print_formatted_text_backslash_r(): - f = _Capture() - pt_print("hello\r\n", file=f) - assert "hello" in f.data - - -@pytest.mark.skipif(is_windows(), reason="Doesn't run on Windows yet.") -def test_formatted_text_with_style(): - f = _Capture() - style = Style.from_dict( - { - "hello": "#ff0066", - "world": "#44ff44 italic", - } - ) - tokens = FormattedText( - [ - ("class:hello", "Hello "), - ("class:world", "world"), - ] - ) - - # NOTE: We pass the default (8bit) color depth, so that the unit tests - # don't start failing when environment variables change. - pt_print(tokens, style=style, file=f, color_depth=ColorDepth.DEFAULT) - assert "\x1b[0;38;5;197mHello" in f.data - assert "\x1b[0;38;5;83;3mworld" in f.data - - -@pytest.mark.skipif(is_windows(), reason="Doesn't run on Windows yet.") -def test_html_with_style(): - """ - Text `print_formatted_text` with `HTML` wrapped in `to_formatted_text`. - """ - f = _Capture() - - html = HTML("<ansigreen>hello</ansigreen> <b>world</b>") - formatted_text = to_formatted_text(html, style="class:myhtml") - pt_print(formatted_text, file=f, color_depth=ColorDepth.DEFAULT) - - assert ( - f.data - == "\x1b[0m\x1b[?7h\x1b[0;32mhello\x1b[0m \x1b[0;1mworld\x1b[0m\r\n\x1b[0m" - ) diff --git a/contrib/python/prompt-toolkit/py3/tests/test_regular_languages.py b/contrib/python/prompt-toolkit/py3/tests/test_regular_languages.py deleted file mode 100644 index 7404b231a4b..00000000000 --- a/contrib/python/prompt-toolkit/py3/tests/test_regular_languages.py +++ /dev/null @@ -1,100 +0,0 @@ -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 -from prompt_toolkit.contrib.regular_languages.completion import GrammarCompleter -from prompt_toolkit.document import Document - - -def test_simple_match(): - g = compile("hello|world") - - m = g.match("hello") - assert isinstance(m, Match) - - m = g.match("world") - assert isinstance(m, Match) - - m = g.match("somethingelse") - assert m is None - - -def test_variable_varname(): - """ - Test `Variable` with varname. - """ - g = compile("((?P<varname>hello|world)|test)") - - m = g.match("hello") - variables = m.variables() - assert isinstance(variables, Variables) - assert variables.get("varname") == "hello" - assert variables["varname"] == "hello" - - m = g.match("world") - variables = m.variables() - assert isinstance(variables, Variables) - assert variables.get("varname") == "world" - assert variables["varname"] == "world" - - m = g.match("test") - variables = m.variables() - assert isinstance(variables, Variables) - assert variables.get("varname") is None - assert variables["varname"] is None - - -def test_prefix(): - """ - Test `match_prefix`. - """ - g = compile(r"(hello\ world|something\ else)") - - m = g.match_prefix("hello world") - assert isinstance(m, Match) - - m = g.match_prefix("he") - assert isinstance(m, Match) - - m = g.match_prefix("") - assert isinstance(m, Match) - - m = g.match_prefix("som") - assert isinstance(m, Match) - - m = g.match_prefix("hello wor") - assert isinstance(m, Match) - - m = g.match_prefix("no-match") - assert m.trailing_input().start == 0 - assert m.trailing_input().stop == len("no-match") - - m = g.match_prefix("hellotest") - assert m.trailing_input().start == len("hello") - assert m.trailing_input().stop == len("hellotest") - - -def test_completer(): - class completer1(Completer): - def get_completions(self, document, complete_event): - yield Completion("before-%s-after" % document.text, -len(document.text)) - yield Completion("before-%s-after-B" % document.text, -len(document.text)) - - class completer2(Completer): - def get_completions(self, document, complete_event): - yield Completion("before2-%s-after2" % document.text, -len(document.text)) - yield Completion("before2-%s-after2-B" % document.text, -len(document.text)) - - # Create grammar. "var1" + "whitespace" + "var2" - g = compile(r"(?P<var1>[a-z]*) \s+ (?P<var2>[a-z]*)") - - # Test 'get_completions()' - completer = GrammarCompleter(g, {"var1": completer1(), "var2": completer2()}) - completions = list( - completer.get_completions(Document("abc def", len("abc def")), CompleteEvent()) - ) - - assert len(completions) == 2 - assert completions[0].text == "before2-def-after2" - assert completions[0].start_position == -3 - assert completions[1].text == "before2-def-after2-B" - assert completions[1].start_position == -3 diff --git a/contrib/python/prompt-toolkit/py3/tests/test_shortcuts.py b/contrib/python/prompt-toolkit/py3/tests/test_shortcuts.py deleted file mode 100644 index 10ee73a20ea..00000000000 --- a/contrib/python/prompt-toolkit/py3/tests/test_shortcuts.py +++ /dev/null @@ -1,67 +0,0 @@ -from prompt_toolkit.shortcuts import print_container -from prompt_toolkit.shortcuts.prompt import _split_multiline_prompt -from prompt_toolkit.shortcuts.utils import print_container -from prompt_toolkit.widgets import Frame, TextArea - - -def test_split_multiline_prompt(): - # Test 1: no newlines: - tokens = [("class:testclass", "ab")] - has_before_tokens, before, first_input_line = _split_multiline_prompt( - lambda: tokens - ) - assert has_before_tokens() is False - assert before() == [] - assert first_input_line() == [ - ("class:testclass", "a"), - ("class:testclass", "b"), - ] - - # Test 1: multiple lines. - tokens = [("class:testclass", "ab\ncd\nef")] - has_before_tokens, before, first_input_line = _split_multiline_prompt( - lambda: tokens - ) - assert has_before_tokens() is True - assert before() == [ - ("class:testclass", "a"), - ("class:testclass", "b"), - ("class:testclass", "\n"), - ("class:testclass", "c"), - ("class:testclass", "d"), - ] - assert first_input_line() == [ - ("class:testclass", "e"), - ("class:testclass", "f"), - ] - - # Edge case 1: starting with a newline. - tokens = [("class:testclass", "\nab")] - has_before_tokens, before, first_input_line = _split_multiline_prompt( - lambda: tokens - ) - assert has_before_tokens() is True - assert before() == [] - assert first_input_line() == [("class:testclass", "a"), ("class:testclass", "b")] - - # Edge case 2: starting with two newlines. - tokens = [("class:testclass", "\n\nab")] - has_before_tokens, before, first_input_line = _split_multiline_prompt( - lambda: tokens - ) - assert has_before_tokens() is True - assert before() == [("class:testclass", "\n")] - assert first_input_line() == [("class:testclass", "a"), ("class:testclass", "b")] - - -def test_print_container(tmpdir): - # Call `print_container`, render to a dummy file. - f = tmpdir.join("output") - with open(f, "w") as fd: - print_container(Frame(TextArea(text="Hello world!\n"), title="Title"), file=fd) - - # Verify rendered output. - with open(f, "r") as fd: - text = fd.read() - assert "Hello world" in text - assert "Title" in text diff --git a/contrib/python/prompt-toolkit/py3/tests/test_style.py b/contrib/python/prompt-toolkit/py3/tests/test_style.py deleted file mode 100644 index e78373419e4..00000000000 --- a/contrib/python/prompt-toolkit/py3/tests/test_style.py +++ /dev/null @@ -1,274 +0,0 @@ -from prompt_toolkit.styles import Attrs, Style, SwapLightAndDarkStyleTransformation - - -def test_style_from_dict(): - style = Style.from_dict( - { - "a": "#ff0000 bold underline strike italic", - "b": "bg:#00ff00 blink reverse", - } - ) - - # Lookup of class:a. - expected = Attrs( - color="ff0000", - bgcolor="", - bold=True, - underline=True, - strike=True, - italic=True, - blink=False, - reverse=False, - hidden=False, - ) - assert style.get_attrs_for_style_str("class:a") == expected - - # Lookup of class:b. - expected = Attrs( - color="", - bgcolor="00ff00", - bold=False, - underline=False, - strike=False, - italic=False, - blink=True, - reverse=True, - hidden=False, - ) - assert style.get_attrs_for_style_str("class:b") == expected - - # Test inline style. - expected = Attrs( - color="ff0000", - bgcolor="", - bold=False, - underline=False, - strike=False, - italic=False, - blink=False, - reverse=False, - hidden=False, - ) - assert style.get_attrs_for_style_str("#ff0000") == expected - - # Combine class name and inline style (Whatever is defined later gets priority.) - expected = Attrs( - color="00ff00", - bgcolor="", - bold=True, - underline=True, - strike=True, - italic=True, - blink=False, - reverse=False, - hidden=False, - ) - assert style.get_attrs_for_style_str("class:a #00ff00") == expected - - expected = Attrs( - color="ff0000", - bgcolor="", - bold=True, - underline=True, - strike=True, - italic=True, - blink=False, - reverse=False, - hidden=False, - ) - assert style.get_attrs_for_style_str("#00ff00 class:a") == expected - - -def test_class_combinations_1(): - # In this case, our style has both class 'a' and 'b'. - # Given that the style for 'a b' is defined at the end, that one is used. - style = Style( - [ - ("a", "#0000ff"), - ("b", "#00ff00"), - ("a b", "#ff0000"), - ] - ) - expected = Attrs( - color="ff0000", - bgcolor="", - bold=False, - underline=False, - strike=False, - italic=False, - blink=False, - reverse=False, - hidden=False, - ) - assert style.get_attrs_for_style_str("class:a class:b") == expected - assert style.get_attrs_for_style_str("class:a,b") == expected - assert style.get_attrs_for_style_str("class:a,b,c") == expected - - # Changing the order shouldn't matter. - assert style.get_attrs_for_style_str("class:b class:a") == expected - assert style.get_attrs_for_style_str("class:b,a") == expected - - -def test_class_combinations_2(): - # In this case, our style has both class 'a' and 'b'. - # The style that is defined the latest get priority. - style = Style( - [ - ("a b", "#ff0000"), - ("b", "#00ff00"), - ("a", "#0000ff"), - ] - ) - expected = Attrs( - color="00ff00", - bgcolor="", - bold=False, - underline=False, - strike=False, - italic=False, - blink=False, - reverse=False, - hidden=False, - ) - assert style.get_attrs_for_style_str("class:a class:b") == expected - assert style.get_attrs_for_style_str("class:a,b") == expected - assert style.get_attrs_for_style_str("class:a,b,c") == expected - - # Defining 'a' latest should give priority to 'a'. - expected = Attrs( - color="0000ff", - bgcolor="", - bold=False, - underline=False, - strike=False, - italic=False, - blink=False, - reverse=False, - hidden=False, - ) - assert style.get_attrs_for_style_str("class:b class:a") == expected - assert style.get_attrs_for_style_str("class:b,a") == expected - - -def test_substyles(): - style = Style( - [ - ("a.b", "#ff0000 bold"), - ("a", "#0000ff"), - ("b", "#00ff00"), - ("b.c", "#0000ff italic"), - ] - ) - - # Starting with a.* - expected = Attrs( - color="0000ff", - bgcolor="", - bold=False, - underline=False, - strike=False, - italic=False, - blink=False, - reverse=False, - hidden=False, - ) - assert style.get_attrs_for_style_str("class:a") == expected - - expected = Attrs( - color="ff0000", - bgcolor="", - bold=True, - underline=False, - strike=False, - italic=False, - blink=False, - reverse=False, - hidden=False, - ) - assert style.get_attrs_for_style_str("class:a.b") == expected - assert style.get_attrs_for_style_str("class:a.b.c") == expected - - # Starting with b.* - expected = Attrs( - color="00ff00", - bgcolor="", - bold=False, - underline=False, - strike=False, - italic=False, - blink=False, - reverse=False, - hidden=False, - ) - assert style.get_attrs_for_style_str("class:b") == expected - assert style.get_attrs_for_style_str("class:b.a") == expected - - expected = Attrs( - color="0000ff", - bgcolor="", - bold=False, - underline=False, - strike=False, - italic=True, - blink=False, - reverse=False, - hidden=False, - ) - assert style.get_attrs_for_style_str("class:b.c") == expected - assert style.get_attrs_for_style_str("class:b.c.d") == expected - - -def test_swap_light_and_dark_style_transformation(): - transformation = SwapLightAndDarkStyleTransformation() - - # Test with 6 digit hex colors. - before = Attrs( - color="440000", - bgcolor="888844", - bold=True, - underline=True, - strike=True, - italic=True, - blink=False, - reverse=False, - hidden=False, - ) - after = Attrs( - color="ffbbbb", - bgcolor="bbbb76", - bold=True, - underline=True, - strike=True, - italic=True, - blink=False, - reverse=False, - hidden=False, - ) - - assert transformation.transform_attrs(before) == after - - # Test with ANSI colors. - before = Attrs( - color="ansired", - bgcolor="ansiblack", - bold=True, - underline=True, - strike=True, - italic=True, - blink=False, - reverse=False, - hidden=False, - ) - after = Attrs( - color="ansibrightred", - bgcolor="ansiwhite", - bold=True, - underline=True, - strike=True, - italic=True, - blink=False, - reverse=False, - hidden=False, - ) - - assert transformation.transform_attrs(before) == after diff --git a/contrib/python/prompt-toolkit/py3/tests/test_style_transformation.py b/contrib/python/prompt-toolkit/py3/tests/test_style_transformation.py deleted file mode 100644 index 1193649e346..00000000000 --- a/contrib/python/prompt-toolkit/py3/tests/test_style_transformation.py +++ /dev/null @@ -1,49 +0,0 @@ -import pytest - -from prompt_toolkit.styles import AdjustBrightnessStyleTransformation, Attrs - - -@pytest.fixture -def default_attrs(): - return Attrs( - color="", - bgcolor="", - bold=False, - underline=False, - strike=False, - italic=False, - blink=False, - reverse=False, - hidden=False, - ) - - -def test_adjust_brightness_style_transformation(default_attrs): - tr = AdjustBrightnessStyleTransformation(0.5, 1.0) - - attrs = tr.transform_attrs(default_attrs._replace(color="ff0000")) - assert attrs.color == "ff7f7f" - - attrs = tr.transform_attrs(default_attrs._replace(color="00ffaa")) - assert attrs.color == "7fffd4" - - # When a background color is given, nothing should change. - attrs = tr.transform_attrs(default_attrs._replace(color="00ffaa", bgcolor="white")) - assert attrs.color == "00ffaa" - - # Test ansi colors. - attrs = tr.transform_attrs(default_attrs._replace(color="ansiblue")) - assert attrs.color == "6666ff" - - # Test 'ansidefault'. This shouldn't change. - attrs = tr.transform_attrs(default_attrs._replace(color="ansidefault")) - assert attrs.color == "ansidefault" - - # When 0 and 1 are given, don't do any style transformation. - tr2 = AdjustBrightnessStyleTransformation(0, 1) - - attrs = tr2.transform_attrs(default_attrs._replace(color="ansiblue")) - assert attrs.color == "ansiblue" - - attrs = tr2.transform_attrs(default_attrs._replace(color="00ffaa")) - assert attrs.color == "00ffaa" diff --git a/contrib/python/prompt-toolkit/py3/tests/test_utils.py b/contrib/python/prompt-toolkit/py3/tests/test_utils.py deleted file mode 100644 index 6a1c17955c4..00000000000 --- a/contrib/python/prompt-toolkit/py3/tests/test_utils.py +++ /dev/null @@ -1,76 +0,0 @@ -import itertools - -import pytest - -from prompt_toolkit.utils import take_using_weights - - -def test_using_weights(): - def take(generator, count): - return list(itertools.islice(generator, 0, count)) - - # Check distribution. - data = take(take_using_weights(["A", "B", "C"], [5, 10, 20]), 35) - assert data.count("A") == 5 - assert data.count("B") == 10 - assert data.count("C") == 20 - - assert data == [ - "A", - "B", - "C", - "C", - "B", - "C", - "C", - "A", - "B", - "C", - "C", - "B", - "C", - "C", - "A", - "B", - "C", - "C", - "B", - "C", - "C", - "A", - "B", - "C", - "C", - "B", - "C", - "C", - "A", - "B", - "C", - "C", - "B", - "C", - "C", - ] - - # Another order. - data = take(take_using_weights(["A", "B", "C"], [20, 10, 5]), 35) - assert data.count("A") == 20 - assert data.count("B") == 10 - assert data.count("C") == 5 - - # Bigger numbers. - data = take(take_using_weights(["A", "B", "C"], [20, 10, 5]), 70) - assert data.count("A") == 40 - assert data.count("B") == 20 - assert data.count("C") == 10 - - # Negative numbers. - data = take(take_using_weights(["A", "B", "C"], [-20, 10, 0]), 70) - assert data.count("A") == 0 - assert data.count("B") == 70 - assert data.count("C") == 0 - - # All zero-weight items. - with pytest.raises(ValueError): - take(take_using_weights(["A", "B", "C"], [0, 0, 0]), 70) diff --git a/contrib/python/prompt-toolkit/py3/tests/test_vt100_output.py b/contrib/python/prompt-toolkit/py3/tests/test_vt100_output.py deleted file mode 100644 index f7163da8393..00000000000 --- a/contrib/python/prompt-toolkit/py3/tests/test_vt100_output.py +++ /dev/null @@ -1,18 +0,0 @@ -from prompt_toolkit.output.vt100 import _get_closest_ansi_color - - -def test_get_closest_ansi_color(): - # White - assert _get_closest_ansi_color(255, 255, 255) == "ansiwhite" - assert _get_closest_ansi_color(250, 250, 250) == "ansiwhite" - - # Black - assert _get_closest_ansi_color(0, 0, 0) == "ansiblack" - assert _get_closest_ansi_color(5, 5, 5) == "ansiblack" - - # Green - assert _get_closest_ansi_color(0, 255, 0) == "ansibrightgreen" - assert _get_closest_ansi_color(10, 255, 0) == "ansibrightgreen" - assert _get_closest_ansi_color(0, 255, 10) == "ansibrightgreen" - - assert _get_closest_ansi_color(220, 220, 100) == "ansiyellow" diff --git a/contrib/python/prompt-toolkit/py3/tests/test_widgets.py b/contrib/python/prompt-toolkit/py3/tests/test_widgets.py deleted file mode 100644 index bf89a2581e3..00000000000 --- a/contrib/python/prompt-toolkit/py3/tests/test_widgets.py +++ /dev/null @@ -1,18 +0,0 @@ -from prompt_toolkit.formatted_text import fragment_list_to_text -from prompt_toolkit.layout import to_window -from prompt_toolkit.widgets import Button - - -def _to_text(button: Button) -> str: - control = to_window(button).content - return fragment_list_to_text(control.text()) - - -def test_defaulf_button(): - button = Button("Exit") - assert _to_text(button) == "< Exit >" - - -def test_custom_button(): - button = Button("Exit", left_symbol="[", right_symbol="]") - assert _to_text(button) == "[ Exit ]" diff --git a/contrib/python/prompt-toolkit/py3/tests/test_yank_nth_arg.py b/contrib/python/prompt-toolkit/py3/tests/test_yank_nth_arg.py deleted file mode 100644 index edd3f2f5903..00000000000 --- a/contrib/python/prompt-toolkit/py3/tests/test_yank_nth_arg.py +++ /dev/null @@ -1,84 +0,0 @@ -import pytest - -from prompt_toolkit.buffer import Buffer -from prompt_toolkit.history import InMemoryHistory - - -@pytest.fixture -def _history(): - "Prefilled history." - history = InMemoryHistory() - history.append_string("alpha beta gamma delta") - history.append_string("one two three four") - return history - - -# Test yank_last_arg. - - -def test_empty_history(): - buf = Buffer() - buf.yank_last_arg() - assert buf.document.current_line == "" - - -def test_simple_search(_history): - buff = Buffer(history=_history) - buff.yank_last_arg() - assert buff.document.current_line == "four" - - -def test_simple_search_with_quotes(_history): - _history.append_string("""one two "three 'x' four"\n""") - buff = Buffer(history=_history) - buff.yank_last_arg() - assert buff.document.current_line == '''"three 'x' four"''' - - -def test_simple_search_with_arg(_history): - buff = Buffer(history=_history) - buff.yank_last_arg(n=2) - assert buff.document.current_line == "three" - - -def test_simple_search_with_arg_out_of_bounds(_history): - buff = Buffer(history=_history) - buff.yank_last_arg(n=8) - assert buff.document.current_line == "" - - -def test_repeated_search(_history): - buff = Buffer(history=_history) - buff.yank_last_arg() - buff.yank_last_arg() - assert buff.document.current_line == "delta" - - -def test_repeated_search_with_wraparound(_history): - buff = Buffer(history=_history) - buff.yank_last_arg() - buff.yank_last_arg() - buff.yank_last_arg() - assert buff.document.current_line == "four" - - -# Test yank_last_arg. - - -def test_yank_nth_arg(_history): - buff = Buffer(history=_history) - buff.yank_nth_arg() - assert buff.document.current_line == "two" - - -def test_repeated_yank_nth_arg(_history): - buff = Buffer(history=_history) - buff.yank_nth_arg() - buff.yank_nth_arg() - assert buff.document.current_line == "beta" - - -def test_yank_nth_arg_with_arg(_history): - buff = Buffer(history=_history) - buff.yank_nth_arg(n=2) - assert buff.document.current_line == "three" |