From 0014608abcb7eafc0906f1a7f6d4e78fb860ed2c Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 9 Feb 2025 18:23:09 +0100 Subject: [PATCH] Adding upstream version 3.0.16. Signed-off-by: Daniel Baumann --- .github/workflows/test.yaml | 38 + .gitignore | 54 + CHANGELOG | 587 +++++++++ LICENSE | 27 + MANIFEST.in | 3 + README.rst | 245 ++++ docs/concurrency-challenges.rst | 91 ++ docs/images/example1.png | Bin 0 -> 28700 bytes docs/images/file-completion.png | Bin 0 -> 37364 bytes docs/images/ipython.png | Bin 0 -> 36967 bytes docs/images/multiline.png | Bin 0 -> 33129 bytes docs/images/ptpython-history-help.png | Bin 0 -> 103337 bytes docs/images/ptpython-menu.png | Bin 0 -> 60225 bytes docs/images/ptpython.png | Bin 0 -> 28700 bytes docs/images/validation.png | Bin 0 -> 17124 bytes docs/images/windows.png | Bin 0 -> 18368 bytes examples/asyncio-python-embed.py | 56 + examples/asyncio-ssh-python-embed.py | 62 + examples/ptpython_config/config.py | 198 ++++ examples/python-embed-with-custom-prompt.py | 38 + examples/python-embed.py | 12 + examples/python-input.py | 15 + examples/ssh-and-telnet-embed.py | 49 + .../test-cases/ptpython-in-other-thread.py | 24 + mypy.ini | 6 + ptpython/__init__.py | 3 + ptpython/__main__.py | 6 + ptpython/completer.py | 650 ++++++++++ ptpython/contrib/__init__.py | 0 ptpython/contrib/asyncssh_repl.py | 119 ++ ptpython/entry_points/__init__.py | 0 ptpython/entry_points/run_ptipython.py | 80 ++ ptpython/entry_points/run_ptpython.py | 217 ++++ ptpython/eventloop.py | 71 ++ ptpython/filters.py | 36 + ptpython/history_browser.py | 648 ++++++++++ ptpython/ipython.py | 285 +++++ ptpython/key_bindings.py | 326 +++++ ptpython/layout.py | 763 ++++++++++++ ptpython/lexer.py | 28 + ptpython/prompt_style.py | 77 ++ ptpython/py.typed | 0 ptpython/python_input.py | 1054 +++++++++++++++++ ptpython/repl.py | 765 ++++++++++++ ptpython/signatures.py | 266 +++++ ptpython/style.py | 175 +++ ptpython/utils.py | 198 ++++ ptpython/validator.py | 57 + pyproject.toml | 13 + setup.cfg | 2 + setup.py | 51 + tests/run_tests.py | 22 + 52 files changed, 7417 insertions(+) create mode 100644 .github/workflows/test.yaml create mode 100644 .gitignore create mode 100644 CHANGELOG create mode 100644 LICENSE create mode 100644 MANIFEST.in create mode 100644 README.rst create mode 100644 docs/concurrency-challenges.rst create mode 100644 docs/images/example1.png create mode 100644 docs/images/file-completion.png create mode 100644 docs/images/ipython.png create mode 100644 docs/images/multiline.png create mode 100644 docs/images/ptpython-history-help.png create mode 100644 docs/images/ptpython-menu.png create mode 100644 docs/images/ptpython.png create mode 100644 docs/images/validation.png create mode 100644 docs/images/windows.png create mode 100755 examples/asyncio-python-embed.py create mode 100755 examples/asyncio-ssh-python-embed.py create mode 100644 examples/ptpython_config/config.py create mode 100755 examples/python-embed-with-custom-prompt.py create mode 100755 examples/python-embed.py create mode 100755 examples/python-input.py create mode 100755 examples/ssh-and-telnet-embed.py create mode 100644 examples/test-cases/ptpython-in-other-thread.py create mode 100644 mypy.ini create mode 100644 ptpython/__init__.py create mode 100644 ptpython/__main__.py create mode 100644 ptpython/completer.py create mode 100644 ptpython/contrib/__init__.py create mode 100644 ptpython/contrib/asyncssh_repl.py create mode 100644 ptpython/entry_points/__init__.py create mode 100644 ptpython/entry_points/run_ptipython.py create mode 100644 ptpython/entry_points/run_ptpython.py create mode 100644 ptpython/eventloop.py create mode 100644 ptpython/filters.py create mode 100644 ptpython/history_browser.py create mode 100644 ptpython/ipython.py create mode 100644 ptpython/key_bindings.py create mode 100644 ptpython/layout.py create mode 100644 ptpython/lexer.py create mode 100644 ptpython/prompt_style.py create mode 100644 ptpython/py.typed create mode 100644 ptpython/python_input.py create mode 100644 ptpython/repl.py create mode 100644 ptpython/signatures.py create mode 100644 ptpython/style.py create mode 100644 ptpython/utils.py create mode 100644 ptpython/validator.py create mode 100644 pyproject.toml create mode 100644 setup.cfg create mode 100644 setup.py create mode 100755 tests/run_tests.py diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..00ed1b0 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,38 @@ +name: test + +on: + push: # any branch + pull_request: + branches: [master] + +jobs: + test-ubuntu: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.6, 3.7, 3.8, 3.9] + + steps: + - uses: actions/checkout@v2 + - name: Setup Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Install Dependencies + run: | + sudo apt remove python3-pip + python -m pip install --upgrade pip + python -m pip install . black isort mypy pytest readme_renderer + pip list + - name: Type Checker + run: | + mypy ptpython + isort -c --profile black ptpython examples setup.py + black --check ptpython examples setup.py + - name: Run Tests + run: | + ./tests/run_tests.py + - name: Validate README.md + # Ensure that the README renders correctly (required for uploading to PyPI). + run: | + python -m readme_renderer README.rst > /dev/null diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..db4561e --- /dev/null +++ b/.gitignore @@ -0,0 +1,54 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 0000000..67ac0a8 --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,587 @@ +CHANGELOG +========= + +3.0.16: 2020-02-11 +------------------ + +(Commit 7f619e was missing in previous release.) + +Fixes: +- Several fixes to the completion code: + * Give dictionary completions priority over path completions. + * Always call non-fuzzy completer after fuzzy completer to prevent that some + completions were missed out if the fuzzy completer doesn't find them. + + +3.0.15: 2020-02-11 +------------------ + +New features: +- When pressing control-w, only delete characters until a punctuation. + +Fixes: +- Fix `AttributeError` during retrieval of signatures with type annotations. + + +3.0.14: 2020-02-10 +------------------ + +New features: +- Display of signature and completion drop down together. +- If `DictionaryCompleter` is enabled, also retrieve signatures when Jedi + fails, using the same logic. +- List function parameters first and private attributes at the end in the + completion menu. +- Cleanup of the completion code. + +Fixes: +- Handle exceptions raised when `repr()` is called. +- Fix leakage of `exc_info` from eval to exec call. +- Fix handling of `KeyboardInterrupt` in REPL during evaluation of `__repr__`. +- Fix style for signature toolbar. +- Hide signature when sidebar is visible. + + +3.0.13: 2020-01-26 +------------------ + +New features: +- Added 'print all' option to pager. +- Improve handling of indented code: + * Allow multiline input to be indented as a whole (we will unindent before + executing). + * Correctly visualize tabs (instead of ^I, which happens when pasted in + bracketed paste). + +Fixes: +- Fix line ending bug in pager. + + +3.0.12: 2020-01-24 +------------------ + +New features: +- Expose a `get_ptpython` function in the global namespace, to get programmatic + access to the REPL. +- Expose `embed()` at the top level of the package. Make it possible to do + `from ptpython import embed`. + +Fixes: +- Properly handle exceptions when trying to access `__pt_repr__`. +- Properly handle `SystemExit`. + + +3.0.11: 2020-01-20 +------------------ + +New features: +- Add support for top-level await. +- Refactoring of event loop usage: + + * The ptpython input UI will now run in a separate thread. This makes it + possible to properly embed ptpython in an asyncio application, without + having to deal with nested event loops (which asyncio does not support). + + * The "eval" part doesn't anymore take place within a ptpython coroutine, so + it can spawn its own loop if needed. This also fixes `asyncio.run()` usage + in the REPL, which was broken before. + +- Added syntax highlighting and autocompletion for !-style system commands. + +Fixes: +- Remove unexpected additional line after output. +- Fix system prompt. Accept !-style inputs again. +- Don't execute PYTHONSTARTUP when -i flag was given. + + +3.0.10: 2020-01-13 +------------------ + +Fixes: +- Do dictionary completion on Sequence and Mapping objects (from + collections.abc). Note that dictionary completion is still turned off by + default. + + +3.0.9: 2020-01-10 +----------------- + +New features: +- Allow replacing `PythonInput.completer` at runtime (useful for tools build on + top of ptpython). +- Show REPL title in pager. + + +3.0.8: 2020-01-05 +----------------- + +New features: +- Optional output formatting using Black. +- Optional pager for displaying outputs that don't fit on the screen. +- Added --light-bg and --dark-bg flags to automatically optimize the brightness + of the colors according to the terminal background. +- Addd `PTPYTHON_CONFIG_HOME` for explicitely setting the config directory. +- Show completion suffixes (like '(' for functions). + +Fixes: +- Fix dictionary completion on Pandas objects. +- Stop using deprecated Jedi functions. + + +3.0.7: 2020-09-25 +----------------- + +New features: +- Option to show/hide private attributes during a completion +- Added `insert_blank_line_after_input` option similar to + `insert_blank_line_after_output`. + +Fixes: +- Fixed some formatting issues of `__pt_repr__`. +- Abbreviate completion meta information for dictionary completer if needed. + + +3.0.6: 2020-09-23 +----------------- + +New features: +- (Experimental) support for `__pt_repr__` methods. If objects implement this + method, this will be used to print the result in the REPL instead of the + normal `__repr__`. +- Added py.typed file, to enable type checking for applications that are + embedding ptpython. + + +3.0.5: 2020-08-10 +----------------- + +Fixes: +- Handle bug in dictionary completion when numeric keys are used. + + +3.0.4: 2020-08-10 +----------------- + +New features: +- Allow leading whitespace before single line expressions. +- Show full syntax error in validator. +- Added `vi_start_in_navigation_mode` and `vi_keep_last_used_mode` options. + +Fixes: +- Improved dictionary completion: handle keys that contain spaces and don't + recognize numbers as variable names. +- Fix in exit confirmation. + + +3.0.3: 2020-07-10 +----------------- + +Fixes: +- Sort attribute names for `DictionaryCompleter` and move underscored + attributes to the end. +- Handle unhandled exceptions in `get_compiler_flags`. +- Improved `run_async` code. +- Fix --version parameter. + + +3.0.2: 2020-04-14 +----------------- + +New features: +- Improved custom dictionary completion: + * Also complete list indexes. + * Also complete attributes after doing a dictionary lookup. + * Also complete iterators in a for-loop. +- Added a 'title' option, so that applications embedding ptpython can set a + title in the status bar. + + +3.0.1: 2020-02-24 +----------------- + +- Fix backwards-compatibility of the `run_config` function. (used by + django-extensions). +- Fix input mode in status bar for block selection. + + +3.0.0: 2020-01-29 +----------------- + +Upgrade to prompt_toolkit 3.0. +Requires at least Python 3.6. + +New features: +- Uses XDG base directory specification. + + +2.0.5: 2019-10-09 +----------------- + +New features: +- Added dictionary completer (off by default). +- Added fuzzy completion (off by default). +- Highlight keywords in completion dropdown menu. +- Enable universal wheels. + +Fixes: +- Fixed embedding repl as asyncio coroutine. +- Fixed patching stdout in embedded repl. +- Fixed ResourceWarning in setup.py. + + +2.0.4: 2018-10-30 +----------------- + +- Fixed ptipython. +- Fixed config: setting of color depth. +- Fixed auto-suggest key bindings. +- Fixed Control-D key binding for exiting REPL when (confirm_exit=False). +- Correctly focus/unfocus sidebar. +- Fixed open_in_editor and suspend key bindings. + + +2.0.3: 2018-10-12 +----------------- + +- Allow changing the min/max brightness. +- Some changes for compatibility with the latest prompt_toolkit. + +2.0.2: 2018-09-30 +----------------- + +Fixes: +- Don't crash the history browser when there was no history. +- Set last exception in the sys module, when an exception was raised. +- Require prompt_toolkit 2.0.5. + + +2.0.1: 2018-09-30 +----------------- + +Upgrade to prompt_toolkit 2.0.x. + + +0.36: 2016-10-16 +---------------- + +New features: +- Support for editing in Vi block mode. (Only enabled for + prompt_toolkit>=1.0.8.) + +Fixes: +- Handle two Jedi crashes. (GitHub ptpython issues #136 and #91.) + +0.35: 2016-07-19 +---------------- + +Fixes: +- Fix in completer. Don't hang when pasting a long string with many + backslashes. +- Fix Python2 bug: crash when filenames contain non-ascii characters. +- Added `pt[i]pythonX` and `pt[i]pythonX.X` commands. +- Compatibility with IPython 5.0. + + +0.34: 2016-05-06 +--------------- + +Bugfix in ptipython: reset input buffer before every read in run(). + + +0.33: 2016-05-05 +--------------- + +Upgrade to prompt_toolkit 1.0.0 + +Improvements: +- Unindent after typing 'pass'. +- Make it configurable whether or not a blank line has to be inserted after the output. + + +0.32: 2016-03-29 +--------------- + +Fixes: +- Fixed bug when PYTHONSTARTUP was not found. +- Support $PYTHONSTARTUP for ptipython. + + +0.31: 2016-03-14 +--------------- + +Upgrade to prompt_toolkit 0.60 + + +0.30: 2016-02-27 +--------------- + +Upgrade to prompt_toolkit 0.59 + + +0.29: 2016-02-24 +---------------- + +Upgrade to prompt_toolkit 0.58 + +New features: +- Improved mouse support + + +0.28: 2016-01-04 +---------------- + +Upgrade to prompt_toolkit 0.57 + + +0.27: 2016-01-03 +---------------- + +Upgrade to prompt_toolkit 0.56 + + +0.26: 2016-01-03 +---------------- + +Upgrade to prompt_toolkit 0.55 + +Fixes: +- Handle several bugs in Jedi. +- Correctly handle sys.argv when pt(i)python is started with --interactive. +- Support for 24bit true color. +- Take compiler flags into account for ptipython. + + +0.25: 2015-10-29 +---------------- + +Upgrade to prompt_toolkit 0.54 + +Fixes: +- Consider input multiline when there's a colon at the end of the line. +- Handle bug in Jedi. +- Enable search bindings in history browser. + + +0.24: 2015-09-24 +---------------- + +Upgrade to prompt_toolkit 0.52 + + +0.23: 2015-09-24 +---------------- + +Upgrade to prompt_toolkit 0.51 + +New features: +- Mouse support +- Fish style auto suggestion. +- Optionally disabling of line wraps. +- Use Python3Lexer for Python 3. + + +0.22: 2015-09-06 +---------------- + +Upgrade to prompt_toolkit 0.50 + +Fixes: +- Correctly accept file parameter in the print function of + asyncssh_repl.ReplSSHServerSession. +- Create config directory if it doesn't exist yet (For IPython entry point.) + +New features: +- Implementation of history-selection: a tool to select lines from the history. +- Make exit message configurable. +- Improve start-up time: Lazy load completer grammar and lazy-import Jedi. +- Make multi-column the default completion visualisation. +- Implementation of a custom prompts. In_tokens and out_tokens can be + customized. +- Made an option to show/hide highlighting for matching parenthesis. +- Some styling improvements. + + +0.21: 2015-08-08 +--------------- + +Upgrade to prompt_toolkit 0.46 + +Fixes: +- Correctly add current directory to sys.path. +- Only show search highlighting when the search is the current input buffer. +- Styling fix. + + +0.20: 2015-07-30 +--------------- + +Upgrade to prompt_toolkit 0.45 + + +0.19: 2015-07-30 +--------------- + +Upgrade to prompt_toolkit 0.44 + +New features: +- Added --interactive option for ptipython. +- A few style improvements. + + +0.18: 2015-07-15 +--------------- + +Fixes: +- Python 2.6 compatibility. + + +0.17: 2015-07-15 +--------------- + +Upgrade to prompt_toolkit 0.43 + +New features: +- Integration with Tk eventloop. (This makes turtle and other Tk stuff work + again from the REPL.) +- Multi column completion visualisation. + + +0.16: 2015-06-25 +--------------- + +Upgrade to prompt_toolkit 0.42 + +Fixes: +- Workaround for Jedi bug. (Signatures of functions with keyword-only arguments.) +- Correctly show traceback on Python 3. +- Better styling of the options sidebar. + +New features: +- Exit REPL when input starts with Control-Z. +- Set terminal title. +- Display help text in options sidebar. +- Better colorscheme for Windows. + + +0.15: 2015-06-20 +--------------- + +Upgrade to prompt_toolkit 0.41 + +Fixes: +- Correct tokens for IPython prompt. +- Syntax fix in asyncssh_repl. + + +0.14: 2015-06-16 +--------------- + +Fix: +- Correct dependency for prompt_toolkit. + +0.13: 2015-06-15 +--------------- + +New features: +- Upgrade to prompt_toolkit 0.40 +- Options sidebar. +- Custom color schemes. +- Syntax highlighting of the output. +- Input validation can now be turned off. +- Determine compiler flags dynamically. (Fixes importing unicode_literals). +- Exit confirmation and dialog. +- Autocompletion of IPython %cat command. +- Correctly render tracebacks on Windows. + +0.12: 2015-06-04 +--------------- + +Upgrade to prompt_toolkit 0.39 + +0.11: 2015-05-31 +--------------- + +New features: +- Upgrade to prompt-toolkit 0.38. +- Upgrade to Jedi 0.9.0 +- Fixed default globals for repl (correct __name, __builtins__, etc...) +- Display deprecation warnings in the REPL. +- Added configuration support. +- Added asyncio-ssh-python-embed example. + + +0.10: 2015-05-11 +--------------- + +Upgrade to prompt-toolkit 0.37. + + +0.9: 2015-05-07 +--------------- + +Upgrade to prompt-toolkit 0.35. + + +0.8: 2015-04-26 +--------------- + +Fixed: +- eval() doesn't run using unicode_literals anymore. +- Upgrade to prompt-toolkit 0.34. + + +0.7: 2015-04-25 +--------------- + +Fixed: +- Upgrade to prompt-toolkit 0.33. + +New features: +- Added complete_while_typing option. + + +0.6: 2015-04-22 +--------------- + +Fixed: +- Upgrade to prompt-toolkit 0.32 which has many new features. + +Changes: +- Removal of tab pages + clean up. +- Pressing enter twice will now always automatically execute the input. +- Improved Control-D key binding. +- Hide docstring by default. + + +0.5: 2015-01-30 +--------------- + +Fixed: +- Tab autocompletion on first line. +- Upgrade to prompt-toolkit 0.31 + +New features: +- Simple autocompletion for IPython magics. + +0.4: 2015-01-26 +--------------- + +Fixed: +- Upgrade to prompt-toolkit 0.30 + +0.3: 2015-01-25 +--------------- + +Fixed: +- Upgrade to prompt-toolkit 0.28 + +0.2: 2015-01-25 +--------------- + +Moved ptpython code from prompt-toolkit inside this repository. + +0.1: 2014-09-29 +--------------- + +Initial ptpython version. (Source code was still in the +prompt-toolkit repository itself.) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..910b80a --- /dev/null +++ b/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2015, 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/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..b010432 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,3 @@ +include *rst LICENSE CHANGELOG MANIFEST.in +recursive-include examples *.py +prune examples/sample?/build diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..ae12f4d --- /dev/null +++ b/README.rst @@ -0,0 +1,245 @@ +ptpython +======== + +|Build Status| |PyPI| |License| + +*A better Python REPL* + +:: + + pip install ptpython + +.. image :: https://github.com/jonathanslenders/ptpython/raw/master/docs/images/example1.png + +Ptpython is an advanced Python REPL. It should work on all +Python versions from 2.6 up to 3.9 and work cross platform (Linux, +BSD, OS X and Windows). + +Note: this version of ptpython requires at least Python 3.6. Install ptpython +2.0.5 for older Python versions. + + +Installation +************ + +Install it using pip: + +:: + + pip install ptpython + +Start it by typing ``ptpython``. + + +Features +******** + +- Syntax highlighting. +- Multiline editing (the up arrow works). +- Autocompletion. +- Mouse support. [1] +- Support for color schemes. +- Support for `bracketed paste `_ [2]. +- Both Vi and Emacs key bindings. +- Support for double width (Chinese) characters. +- ... and many other things. + + +[1] Disabled by default. (Enable in the menu.) + +[2] If the terminal supports it (most terminals do), this allows pasting +without going into paste mode. It will keep the indentation. + +__pt_repr__: A nicer repr with colors +************************************* + +When classes implement a ``__pt_repr__`` method, this will be used instead of +``__repr__`` for printing. Any `prompt_toolkit "formatted text" +`_ +can be returned from here. In order to avoid writing a ``__repr__`` as well, +the ``ptpython.utils.ptrepr_to_repr`` decorator can be applied. For instance: + +.. code:: python + + from ptpython.utils import ptrepr_to_repr + from prompt_toolkit.formatted_text import HTML + + @ptrepr_to_repr + class MyClass: + def __pt_repr__(self): + return HTML('Hello world!') + +More screenshots +**************** + +The configuration menu: + +.. image :: https://github.com/jonathanslenders/ptpython/raw/master/docs/images/ptpython-menu.png + +The history page and its help: + +.. image :: https://github.com/jonathanslenders/ptpython/raw/master/docs/images/ptpython-history-help.png + +Autocompletion: + +.. image :: https://github.com/jonathanslenders/ptpython/raw/master/docs/images/file-completion.png + + +Embedding the REPL +****************** + +Embedding the REPL in any Python application is easy: + +.. code:: python + + from ptpython.repl import embed + embed(globals(), locals()) + +You can make ptpython your default Python REPL by creating a `PYTHONSTARTUP file +`_ containing code +like this: + +.. code:: python + + import sys + try: + from ptpython.repl import embed + except ImportError: + print("ptpython is not available: falling back to standard prompt") + else: + sys.exit(embed(globals(), locals())) + + +Multiline editing +***************** + +Multi-line editing mode will automatically turn on when you press enter after a +colon. + +To execute the input in multi-line mode, you can either press ``Alt+Enter``, or +``Esc`` followed by ``Enter``. (If you want the first to work in the OS X +terminal, you have to check the "Use option as meta key" checkbox in your +terminal settings. For iTerm2, you have to check "Left option acts as +Esc" in +the options.) + +.. image :: https://github.com/jonathanslenders/ptpython/raw/master/docs/images/multiline.png + + +Syntax validation +***************** + +Before execution, ``ptpython`` will see whether the input is syntactically +correct Python code. If not, it will show a warning, and move the cursor to the +error. + +.. image :: https://github.com/jonathanslenders/ptpython/raw/master/docs/images/validation.png + + +Additional features +******************* + +Running system commands: Press ``Meta-!`` in Emacs mode or just ``!`` in Vi +navigation mode to see the "Shell command" prompt. There you can enter system +commands without leaving the REPL. + +Selecting text: Press ``Control+Space`` in Emacs mode or ``V`` (major V) in Vi +navigation mode. + + +Configuration +************* + +It is possible to create a ``config.py`` file to customize configuration. +ptpython will look in an appropriate platform-specific directory via `appdirs +`. See the ``appdirs`` documentation for the +precise location for your platform. A ``PTPYTHON_CONFIG_HOME`` environment +variable, if set, can also be used to explicitly override where configuration +is looked for. + +Have a look at this example to see what is possible: +`config.py `_ + + +IPython support +*************** + +Run ``ptipython`` (prompt_toolkit - IPython), to get a nice interactive shell +with all the power that IPython has to offer, like magic functions and shell +integration. Make sure that IPython has been installed. (``pip install +ipython``) + +.. image :: https://github.com/jonathanslenders/ptpython/raw/master/docs/images/ipython.png + +This is also available for embedding: + +.. code:: python + + from ptpython.ipython.repl import embed + embed(globals(), locals()) + + +Django support +************** + +`django-extensions `_ +has a ``shell_plus`` management command. When ``ptpython`` has been installed, +it will by default use ``ptpython`` or ``ptipython``. + + +PDB +*** + +There is an experimental PDB replacement: `ptpdb +`_. + + +Windows support +*************** + +``prompt_toolkit`` and ``ptpython`` works better on Linux and OS X than on +Windows. Some things might not work, but it is usable: + +.. image :: https://github.com/jonathanslenders/ptpython/raw/master/docs/images/windows.png + + +FAQ +*** + +**Q**: The ``Ctrl-S`` forward search doesn't work and freezes my terminal. + +**A**: Try to run ``stty -ixon`` in your terminal to disable flow control. + +**Q**: The ``Meta``-key doesn't work. + +**A**: For some terminals you have to enable the Alt-key to act as meta key, but you +can also type ``Escape`` before any key instead. + + +Alternatives +************ + +- `BPython `_ +- `IPython `_ + +If you find another alternative, you can create an issue and we'll list it +here. If you find a nice feature somewhere that is missing in ``ptpython``, +also create a GitHub issue and maybe we'll implement it. + + +Special thanks to +***************** + +- `Pygments `_: Syntax highlighter. +- `Jedi `_: Autocompletion library. +- `wcwidth `_: Determine columns needed for a wide characters. +- `prompt_toolkit `_ for the interface. + +.. |Build Status| image:: https://api.travis-ci.org/prompt-toolkit/ptpython.svg?branch=master + :target: https://travis-ci.org/prompt-toolkit/ptpython# + +.. |License| image:: https://img.shields.io/github/license/prompt-toolkit/ptpython.svg + :target: https://github.com/prompt-toolkit/ptpython/blob/master/LICENSE + +.. |PyPI| image:: https://pypip.in/version/ptpython/badge.svg + :target: https://pypi.python.org/pypi/ptpython/ + :alt: Latest Version diff --git a/docs/concurrency-challenges.rst b/docs/concurrency-challenges.rst new file mode 100644 index 0000000..b56d969 --- /dev/null +++ b/docs/concurrency-challenges.rst @@ -0,0 +1,91 @@ + +Concurrency-related challenges regarding embedding of ptpython in asyncio code +============================================================================== + +Things we want to be possible +----------------------------- + +- Embed blocking ptpython in non-asyncio code (the normal use case). +- Embed blocking ptpython in asyncio code (the event loop will block). +- Embed awaitable ptpython in asyncio code (the loop will continue). +- React to resize events (SIGWINCH). +- Support top-level await. +- Be able to patch_stdout, so that logging messages from another thread will be + printed above the prompt. +- It should be possible to handle `KeyboardInterrupt` during evaluation of an + expression. +- The "eval" should happen in the same thread from where embed() was called. + + +Limitations of asyncio/python +----------------------------- + +- We can only listen to SIGWINCH signal (resize) events in the main thread. + +- Usage of Control-C for triggering a `KeyboardInterrupt` only works for code + running in the main thread. (And only if the terminal was not set in raw + input mode). + +- Spawning a new event loop from within a coroutine, that's being executed in + an existing event loop is not allowed in asyncio. We can however spawn any + event loop in a separate thread, and wait for that thread to finish. + +- For patch_stdout to work correctly, we have to know what prompt_toolkit + application is running on the terminal, then tell that application to print + the output and redraw itself. + + +Additional challenges for IPython +--------------------------------- + +IPython supports integration of 3rd party event loops (for various GUI +toolkits). These event loops are supposed to continue running while we are +prompting for input. In an asyncio environment, it means that there are +situations where we have to juggle three event loops: + +- The asyncio loop in which the code was embedded. +- The asyncio loop from the prompt. +- The 3rd party GUI loop. + +Approach taken in ptpython 3.0.11 +--------------------------------- + +For ptpython, the most reliable solution is to to run the prompt_toolkit input +prompt in a separate background thread. This way it can use its own asyncio +event loop without ever having to interfere with whatever runs in the main +thread. + +Then, depending on how we embed, we do the following: +When a normal blocking embed is used: + * We start the UI thread for the input, and do a blocking wait on + `thread.join()` here. + * The "eval" happens in the main thread. + * The "print" happens also in the main thread. Unless a pager is shown, + which is also a prompt_toolkit application, then the pager itself is runs + also in another thread, similar to the way we do the input. + +When an awaitable embed is used, for embedding in a coroutine, but having the +event loop continue: + * We run the input method from the blocking embed in an asyncio executor + and do an `await loop.run_in_excecutor(...)`. + * The "eval" happens again in the main thread. + * "print" is also similar, except that the pager code (if used) runs in an + executor too. + +This means that the prompt_toolkit application code will always run in a +different thread. It means it won't be able to respond to SIGWINCH (window +resize events), but prompt_toolkit's 3.0.11 has now terminal size polling which +solves this. + +Control-C key presses won't interrupt the main thread while we wait for input, +because the prompt_toolkit application turns the terminal in raw mode, while +it's reading, which means that it will receive control-c key presses as raw +data in its own thread. + +Top-level await works in most situations as expected. +- If a blocking embed is used. We execute ``loop.run_until_complete(code)``. + This assumes that the blocking embed is not used in a coroutine of a running + event loop, otherwise, this will attempt to start a nested event loop, which + asyncio does not support. In that case we will get an exception. +- If an awaitable embed is used. We literally execute ``await code``. This will + integrate nicely in the current event loop. diff --git a/docs/images/example1.png b/docs/images/example1.png new file mode 100644 index 0000000000000000000000000000000000000000..c46b55a8e6bfb489a558cf67390f10c7c7c5a606 GIT binary patch literal 28700 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 literal 0 HcmV?d00001 diff --git a/docs/images/file-completion.png b/docs/images/file-completion.png new file mode 100644 index 0000000000000000000000000000000000000000..3143293e4bbc1f9746cd2f70f65b1252ef713127 GIT binary patch literal 37364 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 AhyVZp literal 0 HcmV?d00001 diff --git a/docs/images/ipython.png b/docs/images/ipython.png new file mode 100644 index 0000000000000000000000000000000000000000..4ee77de61a473ec29b05530e230e321610d481ce GIT binary patch literal 36967 zcma&O1yodP+dqsQh$x6i%RxX|xl&|3=AxZ_acfI7`G@fFmBwve-~WwVC7*1 zf8EuW5*NX^M*m4`&W!?B9@xHDcfi1SECc>ax&c2w>=uKk^ zaEZWC^n;_Yjg^&=wIhbGy^)@yk>N`hGe^^x;u13QYM&qDV_>|*kPvyN>^i+Q=i=E9 zyZ*5U>rYV)5W%_qF8V`x(9=RyO$rT-Ox4hWf&g04_Se;tnk{2%uQDiW608(Nx26RZ z6KF(4j+;>^@r|0;S15<5#LuXMyP#MhHGmytQ3cQ8lKWZFKjev2j3gF%~UE(p=)tJKR^uQ>MkMso{u=kH7KJ zdE2;RqNhiZ>eb)(|06l>gSt^FLperSRhg@PIuR#D{rOAZL;6LW+L}-HVXCfbC9l{S z{(71&NVfkSvn&(D+SOWBw-4p4jGuk=1BB1s9yivKyP~M*P7V9}IuBVY%D=yzk3S*s z14g}0!+2E~eu}kGsO046)MtbbH#IxG-xJC%-+SF@H$8Y8iLpf>(QN)tg!o9t%-LyZ z@+YUJ^sgp;EB4Q}?ZQ{cpSv+E&2PPJ-_H&mNCTs0+337OLBYxjL(J4#geE6jrLP)z zt%aMi5$f#V`!zRf2gxYiCr>#dIpuXh-u%6#HSqhfhhEUdrgI8ZtzgaUO=xtqG7>KJ zYW*FH^|H33QFo6bE)l+*s_JuE$isX0JVf1k927H#^M>_j8=qAZhE{Qr`<-+q`!#0e z=3cYPn;^1G_EOJJ*hJZkCyLZbsx+FNFv>F>Ptms zB_+6zy1IH>TgRd*{7&)nIls1Fziw)361A@{*E{tn7QVAuN=9}mHTgYRh=83R4O@Lv zExx+@38~FHzp?Q}wQ#v-d{accGfmaP*2!+gAK!d~(Vyq++^$N4q1w~J_Uf1D7$H`% zCi`mFupmaBLNnnilhz+}bRpABV_C}iiXuro!sZ%;a0snpbpZ>BWv1lo)tO>}QCt2^p?%lrEKhRw709+3Ny zH~PTk43`*sVit8?NDOHD#BO@bq-j{ceA0O92T*hVIun}T8U78yh8D*b)E^jy~UuW4Z zioL3-sd05z#rWMfbpTe9o`r?5v^2e1sm|(7o+*#Zqv+q@d7dI?>!rlR?y9P)HUxEN zUPB#L8JBw-`5?}aR2gOagBH@kLZ|f_v57>u$oD?%eLo|{_ja73L9+)%GFB=9u z5qj|i4=;+JoJJ`@pjk&KTd~}$lhpc}q&<`^*JL>G3pw)p8BjZ@-6Q?95b&rt=8!4 zQ+7`cHQL=T{?_+skye{h?jtxegmI9@h&5NHJ%MY@YGgSAxh^BQan}6F-@hFf_Xd+3 zc3)p#S#9OweCsn>cQ=gRA5*==mZ7l^mU4n!j4*nl8SlJ36ZDCKK-xbGLvaY)HiMuN z-Y4H#vZbV+fc;CZhM!B_;^BBYI!X_g-(($c-ne;BOgEg-(AXG<$R%*UWI&AMN6)2n z&`K&RrI@TFeILV+`O+B!xnEaEsLXM{&vFkW+b%P}9SX5`96GnOvPw1}Y*1PZC(Vru zufz|(TGj(uWbjax5K`?GG82+rkn0x*D1*e#f$?xZZ^+qODCt zD=FFSdr}{m#Ia({#>oTJ+L(v2@$&~RJK&A!baNNMo32$>S2H|a%pW0|c8H3MEJ*x* z;j*s)9~b3hmXML5JE8cToSbxWw7c^`36e3C@9vAb$QYgth z%7EGt!3&`)*z#1w~X<72C+@Xm?-VD^^zAYOzcY{Ggt` zM-Hs4^z`&)6&1gxrwNIOh>D8nv%%x|39YXaWMyaTnV5uR8+8*1R%>UHo^E`z`@?61rF|oEqKu zt))uA{`U`F+XtbRrT_k6MqGE6$X_LmJPPOUPe@BjW}RZHl^q^b<4}@smwm#)je5bO z|A!B;AjJ*#a&TaHPgf+KqsZPv(YM%_*x{!P3{U#~<7cKjW?W+8-p{t-hQY*El(5%^+4A0KK^WXt0j@f~^U!z6Ha+V}h&HK>wc5>t6msbOGxfvA=%@*!x8*}8C*taLbJ=Yr7An5NOKr0hqElFQLpW!D0{iRZ3xUDKN>IOZ z6XEHnq;ot&nVR=vyjxn~%w@12%d4xq%Mq`G{3G2tvfL^IF6>^-zVZ?_n>yHU3L8Z4DC;{dxouPV^hX;L z3k%B@7_TTPl^VUpTQn-)+0>J_`Vug2`1)`z-QXu+;d}Avaq{`%CjQq_-Is-`r9PJT zjxmrrp|%J6onU;tc|9+JNPtFLS@U%Yu^90-7FWi< ze{z0D%C?u9YC}5aWGLIUl~}#D(>_=%KQ0F$f(EO+05{vz))= zlpAu!$1%NKNuoab(1mh2I7IH_NteV}3F4^<%H37j`b%Q0pl0nhJMDAHjVbvQT-+!} z#Oc``77D{X+|9}A)h8|9pS7Bum3GdoB1 zu6t-=LLCsABw}nFgGA+z3Q)3H@?x&Y=!u_b(5mcbp$aL2f`UdFz~Jz)n=2cw);L){ z)(k2RWxsVVoSKr79bq(KUhR2)8O~-i@g0otvPIzO?5UI|s?|8gyFA9F6RUl8w$9e^ zzNN%9OsC`5uZw&$TQjrutr?#<(zzL>`bg?(Pp`@P%``YmPB1J`X|>$E-+g89^1DG` z?NH%r$Rkpo&x_Opgw2e?+e72y@q@7ewp+W;z4^-`0l3QE?g8aMX-glF(du@=&{OlzL}Ys^_I%YYD#A2^`Hi`so7Kymz{$H zxb9&`SQ(kiP@1<@W)I_^8{b5|WgsdyE9WU5UN1n?U{vJ5yhTxlV*GHSrR& z-xnq(-U{M!N&%CfWw4bapYUM~| zx}O%*nUOIqgfE&*)}EhxUV3?Y-n5!Vi55@D4;S9l=BagKB%E!z3{=QhZisU>MFf*H zKIXG~p!RMNVv8azoG3Ec`(k$c2$?`qNwYXvet-dn$#iMQWUOJ|JP<=wHO6t{Nt&kj zRZ{oK*&X|{zEm*B`KH=n;}MWct<9FR7pe`wE@iX@b-@har>4z3^<>X$v~Dhh1w7Z# z!WvIczp|EY!r*bf@4C<^{-|9@f}gIt5}fASGe(^g!_2EvP%u(vh!)EEnPcXS;u{#m zm;7Jy@;>MwhnaPaV)xWRy_A-`Dz`cpsIfu>`ulT_dll>+ZTP#(d2P%UVuFg&b)0-} zZ6=o}(EE}^PR_-AB2Oc&&t2{KW<_|+O|3dx{~~&JhJ^FYnDX+P4g{1jm$dQK>s-f- z%*B03`1dKPQ~P#K&r0K`)%&N11THRW3z@QZ+wQMMO&BvAYCjVT;>h|xTn@=5|K_$z z;XPR0RjU_4XR;saR& z#A*h0*?5p>8{XA5bH|)0yjJ*AUmyKM(M;EsulRdu>0jgR4?bmO-3x{}f#Zj{e9v)b zqUG&!gEv1;s92`U81yxsQIoy?YxL0(t>c!D+X_z*adad`B1p~OQ3)cst<0`gT z{w${@RFq6kJ6#!axrz;r`NT}z_H;W!fyXyIq28=s=Z=f3&Crp0gUy>J6Vua@wWBxE zhRreFaKfOT-Ltcgs>PBmc*3oN0b3Vaoj+iL8V{QkerPnmk<437Bku+PQyy}$f8)A8 z*S`9>pUruEZ2(k<9!qcN6THf5R2ntaop0F^!)&;7g{bqnzAGwptm`2Z_~!dc43Lz*puxl$jEZLBK1ww{u@f&_)_&| zT3SdjF7@EXzz0L4d~T<+FK|lM9Q(Zmc7cTQPu%2Dk4TN0?GB#fGis&xhFD<zh<8Lz)0vgV^%*Xl!a<^`u83 znNaavq2dinzy!?9^a)vLn9-amRCKkt)Xdf?Ra(*0#Z_I<&vfD$ZQM1 zygzQm{iG@_Vp8?=O-!WgPncK*H)z;NPVSjUA`5CQ!&8Loo#=u^Q@z`MfA%``o~hr& zs5V#0^>j1QH`saOauo~4+-$C*8+WshE_rN>twbC8z);Yhm4_^96cycUZjRw{>x$q@kHQ88Fowm}K^i18>7PuAcLGOAY-#T&X@07uafA#3lW@7b;$TOZg1~swsT-nHtHBYA z)xlv*-Q3gbTE^sL=&DGg`7=1A^?wsJACd}cjaeO;k)|FHVx!mQ++mF~8n&v;2zZuX zSO9InY`g}$_?GW}vg@t~#a93$z(12;wtfBsyspI0%n#M>w*XXNvxfW12qM|cNe)(| zbG$F74yKj@N|4p`ecNm2YKA{Pbb!bX|&) zi&d)CGnwVP%%3S^JwLwei0w6A-)?&?v3lW8*TBc;8TSjn7n%be?4rzDD#+3@(EF6+ zn%H{UY9i|fYDe(**g9rTvIrwqx@PQKIOgMY?@UHClKRBiiA(0wxn!i-Y%idoP~Ie8 zdTY4{mhE250alWM$ls6p>0HQ0_4E*Z8a2_c8UmFPaDB91iRdnN z^4or%&yGGu_kr*i&9XZX)){Xv{bmdygU3Yq=ZJq(Fc8BM$yOY$8mB`K$B@j~x^kgL zkhS*B?yjzneO;g1r&Uu|_P9&;LoDITIpsY?98AiiBFAq%U0q8Z{hHB?%byYxErM(c zet5ww zMn{0l5x6sd62yuoEdWvjXu1ZF#DuCU)sF6r%T}k_JSKt6@=13kz3v_ZtAKlLTW4Wb zIN&`BTv3s6u3@QMQXwl>vrFCA$ z2C*Z+<<9mc)&qOdvdx;-UB%USY@4nJ#%hI{bQ+QesYMxw=qzIfCf-BR_q%?fpO^D2#EiGgos)f#`3y}kDD=G6g* zhmKoY_6X*#t}Zrp{wx#2@9_~qzP?^9S1o^&z~A+x}`(~ z)qZURGi5V0G}JdR&|bUj1%z_BCx$sL_a1g+d2Ma4+BplAj^uP)h}}uSC{e1rQ>&FA0F8qn7@D9_%e({uo!v^ ze<%u2(i>|-^@8B&1VFaJ{K&l-Yj-yyDo-0qF@QV*)blnclTVHHOd%(?D-+l4@Sf0o z`F!U7TkiHtYZA>)l$4YQD`hBf?WwiS)#3K_Qns!pT zN`GVfi|xT-=V&8M3S8y^)=WvAR#1>c!;a_W&G@GkTri37bbFe76H04DSdKr5oA|9Y!uG&3z2e~+FmyV}D}GFRCV&CdlA&cE#OGFj0WmIKNVd+LhE|iO z49wXaw$r6%!Li1`Y3TZfLd@&qSlx{>2iT3-=8BngFkKhUC$8zoOxW0*e0;O^b3vql z)B`HqRKV6c)hv+Q(4=>frS`5Vy2_36QX3$A0#?YkdC7gt0`zR@+Ve*rf}`GYt|fe1 zbK*S%UO8YzTu<#{?u4j~G^q=VJ=#=D>n(YN=1YX^1^Y+SqK3wXI4mACX!^MjzTH3( z>0lzWWZ33r>_=~WHfC2>$8+PiQom-b?BB%B=i>g&TbdtjOr(H$;baaQV_%Z>Mcgju!r_v3x~jcr_c{rhPQL%akKuKX_Rd_(X{*LRXS=7t;J zaZ43b@$iI=Rk1a5@a=EY9D3HXj^>87cIVUA+K4MPI%CbT1oRl$*mRDxvtq=Z{L})H zoqdroC6G=ACF>e^4o*j&(5mgfqM-1V6VM)5D_1fbuV4ny;#sK_7#+R5Er!H%haIGs zH~bt^V4QX{tRetB0-XQ4*=M;m|6uY-P3WnEU{*;g)247M0iWYdQr?#A2xw{sM~v~Sn?z$WDbDfF#QJop%=JA)^_>YLVgDRei|dK}YK9$GV|__VUN*1%y>vgBI#m2U9^qpil#_``YXM+CF6 zpKT61rt*urVe9MbvZEbGH{!0l4R220u|xhc0=neAw|{bH4Gc7OUS;nk;k~`aAsbG% ztkcX9{ha`J?RvTwgUwVvk>`k~!f;As#qTyQnu(qQAlJxf1DGhG3Td%w@3-%ij>yS` zak|ybmeS2!=q-~xt(chSRW>jj4Drq+rb|Ud#_XdaKL~^}ndkPJz+fsXfiCo|Z0c#$ z3I&R-l&U|@t@k-h^Zp9!^g-w?4{22_pbOmSlJJ0%#> zi7a6xyx30&Yqo=qqcw??Di|r5RU*ll1|MEjxp87xK;d17@genYM{j(@+kXRie6r7% zz1&TBbHsT+1y+x7)N-YXSr=Wo2zSf?O0D5pVomzk-9)DWP-jPY(mgGzm zI8!%jA~dfR6HJuG0lc81^yJ%Q{c`4*>Cic$DC}?8$4j=?8oa&+wporPF-{AK z<3I2cIJ2eKEuQ)T9)7ZU^@)eGr)HkpLCIG3Oj+EBKc-q&kK$%n&CSUVnu|NSu}ZlE zw`?!nUlE1|P=#*dnQDVAgo{fghqB(rq{n@ruTQ_fA#_X7)vvAXm6mozcrX) zol9to#aqGPvAzK=!)ePIH zmvhhZmUvtKfoHYxtwitXAUSrM0PJnGH8&4O;S7yp4zmWr;=33Q4i3#b{r&y&78Ni^ zWWqzk)b*|qU{S`q&88(E$-#^F_wJAWdL2u%>Gr>pX6-wCSpUcZga+r2xO1pX>HaSi z`QtYvO1ziDOiRv1;^A8$VeB2+a?;8LOl12-UW^r~Lx4mvkks5oMlH+gY3c(Y6X5X+ znw1*qwW+_36;WaIXO6VlQn5XIBMP{?qv7x%A=DKr7NT$#D z{?Yu>j;1R-);uA20*8Ks02JobMw_%kbOGsG&y z&;;qWf*8WS4)Er_iiwYJ4+zy%uXg7Lu5gajsiq+MOOzF*?JHPF4CTa_a7R0-pMvg?H-@6t7I zRIvgUL{bz`E_cLgV7e1cP=4eE@OIOi3Rw@H7o~yy^vE+4U)%jrhB+ko&}SPj3Z|z} zSyPjxRo|2<&u)LR*);3%0(y0Pk6JdxphJ8y#Ax;nJJ2)&9wB?dI+{8hl?`}=z0y8l zwLaMb>7Gch>ZjY@3D}q#iZkosBT@?Fw1zMI0#L-Qyjc2!pX&fW^G)E$hRPLlRGA^v z%yZ*Rs|cSn>WF@vd7i{ypI8c`??d^$VifocNcGcyDgnSW=hiiIQ#=0k>sQkFrVv`b z=ou#x<0tR?7vDdBOuPahFAKn0ZJpF_oclh`u>e`gU}DzD@-YIq)LLP&J5?5=NtgE? zAB@J#fyBT8OxI^~u!fg0T9ODYhUTcNVTX8ehNv~6iqH=oSW44%X7`|EA-}IK?`37@ zUN=TrnM|V+URxPRo;RkubFdjFuncyL=>d7e&1;Bj!mCn(<4TBcHHAwlG9}`jyE_kX z+oZOi;abqq$&$_lNM_U8Y5>}AFp*@`Fjzc&{Bw5pn_nEOSnl%1LK|3meJSVM*P2JS zFm^(T#EP*Y&`>vcQuntP{P8;PY;H`26KdCcmcZFh4pwEwjEp;er{g-t;!jOa%Wnt{ zu97hqk>w6vbfd2#xOsTw8=^Cj2YxPrN;!@+0`b?BQi8gepU`H}BxgK_QPS`-A*=aI z57f2TsOpsXeT0|ES4I-bWfll1m8|+>hB|q$cQ!c2CQBY{KrGMKe<)vUPTAn}zz~+= z`;CBmH^{N&1EJ9FJqG}sMNr6Rpf<@8W5M|KBxr;;6>*d(PxlHJd;7a)8WO7z#LwDJ z4`TB#g+hh$g_Yhc$6-9VfZx|4V+#W&*??UiZGl~@G8z{Vac&PCJs={o5DO=ke{E6# zsbbE)oRdd`OBslX!Nw3E3$@mw>)Ma0{9Fa{Wr+m~idEHpr~%}C47QFW#Qr-WjvP=| z{{UFdUe@TFcjE!Yo(mGN{Vd*w*eDMa5*HUYyf_&vg%run=t4bD3V7CQ?Viv3%i8v{ zG$a2`+;gPk6;Bz}#Kfq;PRnql2_$xj2J2HfBST+8()|-~NlQuwPQ3{-fnla14S6pi zu{^PD3PMe`4h{@e;(BzUW(q60uV=#Z0hH!?cPZwuvQ)z7LLXv2`^F!TouHs*Um%KG zTQh>PiEmCqYxdX5Qe`nt_+wCV^9?(DC`cjzp^b(}jXcEwlU>R|4hNA3R~%a_fC-p{M&~j z1JQrm(ZT)IiQQcXsJ~KDPueB_8GzDPDMCeqMGp-9chl8ovg;#Zr;-L&zwp459vrW| z6OGvcRa@ZP6N{Q+K+nQJIC+}gZFT!+eGBHi`d(Fsls+x`OBE{OFI@s106BT&dB9(y z)ts*R8_k>z*EfXJPlK<$I>B;{JLq0Ll{GjIySzA^?A?7BrCOM+hNwO9_Tnnm;K%`d zCFv_yh`&*{m+_Bu-7w~@;-Et$ZzMpceJEbjlnz(^LS{U)j^P@!cHGbcbT=YSo8ba~ zL4!^i1K4rt4Of$kM-5J9YOm>RmULq`1mJ8I6TkEG%X(u=9at8K5k8j$+B%k+3kQwf z!dw)H6d{1*#tJnrq2=SxF^oGN^N=NXq z1W-$>!tv*|n<)X4t#dmat&p(+AXlkY8Www()o1?U+$!*209WNc>}7s=e&z*0@EnwY zJj9{@BM{{unBw~-<_ZF4yLV_UX*zkV6??r5GZ-@)3OXYCb>z$g!U-bVXC!vcrGeth zRNw}cPBS{q$v=S8lD{>26BQh~YcUkmKksqq(sRq5#dYl*!x72zHul=<-s42RH{_ME z`>XARcO(~422W?36W>E&IwM}IfEvuy^iNed$pwYR;)oI`QLe|)3N9j2C5|+#Z1d&G zI79x+&Dq!WmRJQ0e1*3er!eVc7LWLXzIzW|0Ah;`{VG)+DSxWe7%4TT{y zo!aG(d${7G1x6G=$}=ANj`|uDdhK$L0r?onTqiE~B>rZDTNE|N$IkTHe78OYk@W$m zkfyWmvdkM3y-WsUd0D+SxRR23?16(*Z2{}DI~9f!FOI`H-+0dgX>3Lq<@JAZ)r0vl zH_Q7o9T)Xvim<_00FrmQHrT~uU?S3|mD{@38(Y(PY9LR0$6TQvcmj*BkNh< zp7PBzbcA~0b_wiT(@RNVPS;vI_>^fdAjdgkUJagDXf>=@z1hA6dEVg7Er%i0T5~XP zvfh`>9sx+-fL(kT$xZuNaw!o0P_dY(bp6Tp#ogu3$LVOhtx2~}LuFPC&%EyP-ArINbn`YTHQAH%~p zK&Gd8rBy+2R>S-?bqN&}+C{=2>J2$CZ=^ zJ66z^a`u(?N4-O=LLqw*_FPZR_W9LZy;PmJ;nKDc4I<7(U?C7o z(=k#|A+1>1Tgw`Y8GmMp$yUAf(deT##(05BM|VBF!;05usE>AmO0nTs!&t(W z6Z`-0?9!Dl(rz@H&E&)*E0q{c1kp?s97>Y_rg|2*0k$C6zG>^?Kn{XjjV`z}Y9-&0 z=aZAJeEhR1)2?_%(jmOb;wCiX*JGuT?u=o_AV({7U#~(!LfVq=0O%!QyVdCF-w;p% z-1VtRZiR?w=ejegTyp*Q)u>=Ct)#C_c-gOS0bhVyfTZi+Y_jHtrFgMy$rQ_rdCa?y zNabLpzLO1Z?BiZdcxqp4Ld}8GRp6?&1VH;UiS~2OLyraTWN{YO%E>Z2IuJ|&#z(K? z`fQjCQOsyYJyc=SbS#e%;~bkb-DS{cb9dDXI^3k*n#-XhN-!E369!7Xv-ZCMZ)==a ze-=8+sK{=m73SSgov|VX2uL;9u55{dh)sQzEjD-=G!e(=tuJmmTLkDF(a6hFtL0_V&sAOm@@p+05a_`E$XYi|2ry1=FSr`R_N4HQ!Ro@d^f2 z)hI*^WXBw~r)4zmv9Yfk$x8mk5ZdXOoEFd`H2C{y|iHf&Z9FA>z7OTZhe9xe>DjRaDgd)*gI>Sw1K7$79R*wK=&xdH{j zK=t$STu=}6yLLa;TpdCdD4@FZSmYkzQbK<>6R(tp%+GHpPKq%`B3nS_+M1V2$FBbZ3=w%IQEv1k`X zL_sl`%UfBqxI#EgFefX^{(R>J*gl7C0}IsOU9Aw0I z+whTW;@7%xN?7vNw{Oy5SVh+rA9Ev<%-9J3xIRk+*Ffq=DMvbAsPOxLI8A>DN-2Jb zDZK~-3(amRmJBEq=3%{jp?kDFaWPen0HsGdW#{BP1!>hD2%nM2GxRe?G&-|#???pU z09y2`^j$77HD$&U2uQDvUm2jqly_bY!;PyU?j};`WeiF{iG;*wxmzF9HRR9z3AuGP zC?p>fSy*^K(*6lDHg&PCsQ@PAKSS5q`wD+NJNZY-QD`Yv;-9!u1n>bMw;pH@~~9C-hDiTfLavJdhQ*Y!av_uUE2FJ2J{_7eGz8{ zUOa%}immOIr@=N;Ee3)>Cw%p4)}W)Woz3F(VY!{lH`&d#;rl!;ByTU08!2-<|8ip_ z)3Q@zPHO%{h7{D4K=Z1qI^?^S2Qj>a1l8Y`>H^;Ccufk*8P3{Cx+wNEExcb05$@ZrdeT|nxDe=|8J&`r*q%c zwQhcXK4w#P6MR{n+u0&8M1U`|KDLaNx!EV5LM@j(IJ20;zvlufEyxnScXR~2m1W6n zGZ$nasz_*o=2Byx=M6lxOylIYJsxaX)~y4N$hd<*qEMmG+5<>guWv=8m+E9xBmn+? zRwt3WWNPzUTWw_#QHNn0-ds+9=Ao`;vALaHF`M06x&R~L@?I!XpcC*~kux9P*V`%C zxj(Z@Nz2NrgXB-DJMmi;cny-qwtp;y+=G%Hb%b>(s##4HBL5e z@^*v~SUP$U^nj!{C2%Fj#Utlq7mQVsO<1{MZY1yS#S*N657 zWG`AE4c^r+&XJBAY-?R#+UY>e99%n8R2&5;ba|^j<0 z^uZMPLshHZS-6H6Y*i6GImxSDAVFc2m+xUD@wyOHYqXvX6(4SMYM_|N=Sd?XYB8fD zz!Eau(vAJPx`b20yyNK?rKvp!)vV1(ncXW=44Ps^td7)Qe)G1#a9H(v2jzrwxNFbU z>}0CW9an9h5JE`=e!>dzN2=_+=60GroXDk;+d*IsgX?f}$JM<-sZwnGCS-5#*kS$` zUPZ-Nl_|1Rv!i30(C4}#iN^{4ktf{w0+R5?n+t`coQIXM?#Q0R0o9N|`!1zh$^R{~ zo9^55hh8d6&_`DZE47VHo&jk#Z+r?B6&Ii1)zgDMa-ZZFMZm%*CZ@tey&ToQYD6{b zS&!6_GG1#%-W@GouE{8v=o< z)ZtC{d=K&GUObSIY$-kKt0lg%;wDUz>3h4EXZ?rUqoJv&D@i~|WS+=f9%l|4>wYn| zwl1kns;`ePv0CPG;OHGV$RrWY22*akk19q3_Ol^djq2@IkX8Ed#wM6iMmTz@-nhR2 zbb{CcP;moP7UUdiprNf`PZ^u|c*l;oDD(t_NyvuSV~p(eU+OwP=7PyAV7N+h}$ z|0DX<{(B#Q*x=GwV1g$JIK10Y3^j@O5f%g?vgH#^(5^wmVQnH3`eg2Wp0V&$>eM;)!B*kr+J?;*V8BuACGrKBQ*nyyDh^Z zjQn>MswG9wpvRICwZ)Osa*#G530~z$SsknK$+LA3d*yF%>T-w&a*G{UM}T0f?k&AX z&+%zewDP}{*sA-NZ}OhV`1gk$=Q|1(#cr< z!G$;dO?-UntJ-~!D}XG)B&3|D{U=PNi^QL)erzfPG$(Y>92nFI9QoI6EO1D3S=k$t zqXv0632*ehZtU)!o{{Y$qJ{Z+v^m$8Q27Xa7I?IV-6QNk#$MXI>nO}&J^eX0m#Br^ ztE}b}WfXDj+=H;lU>o#yGDlZb03-4KB^et5JysO%EvYZ4LWn86EpNsHTvvll@gP$E zK>JPAXCVTY?>pXr0mvw$ro{Lc_RR3G`3(G8>ac*tm6d|pt+j|-CvFHE5Ma9X$pu01 ztBPjP5!%^_$OaMcNJw_a#6$p7Y`_}7*{Yk7l~H@Q!Hj&qvS>QZ9yuWw8YHTN zeot_$F&}060j6y|7d+BPERCM#p1%dysh?|t(*Lf0_8+b31KIT2Ws#{Tn#N!A)CWO4 zm*b`D>E#v2>47uMyyxoHU|?ZE@svgk996RN(BUNRdb+{nLZB+5?aNhVF_fvEK|7)z zM3EwqU!C>bc3*-snirh6fHHgut#)b|r7hq~RFv4rl{C=HBO%0961Gw?yLKZ36=_gyV%Tv<7;5SO#;sfJhF^pqIn1sX#c1n9nu35rv~( zwM0kNEI9Ie7!DHe3IK)QEQYn$Yef*Na2TEc&a25>+W=r8q2!X6)kToW4@+epc$qHwuvU={+Uw$@X(h4L@d{p60* zwlT~8mL#~2E)nIC|70tzSQ0x zU~F10#Wt^sHJV!{4=gMbnIw+MxPAe}4^MqcO3{bObSl+&&|y|e;~b>i{yV!R&vD!T z6*F^CItA5!=N@ZCDDHxKYP2PyJXMfMTdC>X$v}QKGeD50j;jv<0m=&HKG=T>S(ImRm}m7 zG}s+_(096YKJ)``3ZvhV^)*#z(U58 zSyE}7)eyQqI{>fg+SkE3^RNSN;YPYxVwl8ftobpRr^)k_2Z-n8w(w_YP76|=CuWP+ z4-Z?iOsu}SKkucS;UeFs?rdfMM@DO(^t+OxA{OI8-odiipFKX-yZ)A7GC*!-;rB-y zNAk__;`W@OW%uQoCk;pS5B7(CmX1_Q@q!Ic=6Ui!7y%?jMD6_EWTpP;)28R1k!VF;31@StaPftT;v|rU&*oYEVBY- zT4QOl@X%0pgeNLf0i7>JM}3NoF-l0Wb_+%x0cztt+a!hZ0B4X+V%*b;y0^ZE|4mg! znWF(a(~&@|pN)K6VbV%JC&t4I0ew#(ajcqloP>{!T}w@sIw<8cSh$sw@hV3WjS(a2 z5}lIG`fC)O?`kpc;84gl%Ki5q=w|HuOTM|@2Xv{4E6qUV6h z-9$1+_8i^ML?6d_2jsoc=Tx&Ui9}nuRb74k#^wCh_aIm0*MmK!-XDjCP$!G;xl{@b zQyXfltBpX%T7yyNx67|t_s%#yy#R#*DPCqDWZ>A*fsvOfmv5l#4};EHwtdvC-{{_* z)8l9EEwl0KN}cmd$}uywnfcm{0gTD@l~ndD3w*3X&(SKnmR2&xZvINL!DpWjQ2*i? zRJm7g0uE91f2GF6VfeS0IhjrraDcB$$bPuk*6o0Peg4M(j|^5>ZS81v$lnS+VmXfY zS}Ke* zuhyg)x~k%=r0`7Xj+KhU<@;F~-T@+woV0D~TE6q~J<#k0fIUc|tzPa?+<#>GxjC5u zY>=^h?a{o87s39TZunGp!qF1d2F&9#M{y#{*>*nA2V*$TP;8)4Y~QkPGBNgl&8iX1 zJx(3BN_rxU zod8i10RiofBx0&P2v~1)Vg4iTH5C%LYic?8---g+&fHyouEJg&vyEAw{!Ti(&ZcRoxs;9Sb~l3%cHuxoi; zb0=Wma;t8*T=;Fv%$E_4;~uGGbUF~3GqRqA~EqxzC*HeL>+wQ!Z{`;PR?P=0IAkff7Fu&<6iHm zej24Qklgr9qgc;tO|?0(1Q!SSl)#OsOF)M3uhf%_>SQ=*5tC1~25Gd0NjmTUk+XVN z21M7slv4pRFRqx<3CoXXeWF`%hp+s+506FSz8yX)DlUH3DU|;|I*2IG-zzDO+=_xB z{PtSc2@aooWATpWs^PZ29827%h5AOg?|M zT&E{Zlz6mve&N0KEJRSVf8oyPL#qXw;O+LK%?R*Y4HDQ&Od=};L(nHik3^7I;f)`l z@zmyqg9Garp;Jl_P}WHWq!pS*#MgmYAh*M2^;e+iUImkHmjH{NQN^#v1he>19?DE5 zY{H?$8mokL0p4Z%V~T|_H_$ZtSw?WXEjNL)HSm~jw}DB!rGytJ|J8HJQ1^4;h8Fa~HuUbH!2*ipAWeaP(UhtJ+gO*0-4dWl+U7=PwW(e@h zNT`*5_Q#3NyRGerzLSu~0_E!XT~K3%UCS#=%!fFb1_YxW;5$!wY0(`-&f5ZAz}LXc zzQ~!>$;!&PImKC)n6Wjuz#;oJ*B&$>|;EW+owMAEJfhxiur`xEU9h)o%b1`w7k9 zfj+`et?&-e#|2R-ggIROGHP(N?##jJJqpWPRs(4?c{lZr4G1quN}88I{|{bwT>K0c$m}*y401#0<`xlnFiRYD_ICYC zupCNEO=ODoK*v_z=4yzh+alTE={5Ry$fz7$p6tQqDC#*q0xjRaf4No0{Pni7txB7@ z_IQC4P`eQmXI4HF6E(WLDg{pA(+jtaxIR?0#>m`#@-n0-5r~U2$g|6*bxR4iBcHtd zeSYeC@_qK4d({)U>M(Kc*|Bb&oMNrJ-Oc>^2+p~BdS%P7W~F^7=7K7ZnNYR9o*(QY zU`@*PK7BH}Z#|#w)7%@r(^xzh{u7?$(Ma1GdOFkHL+q#uz0_~%AYtV0fH(VVIV~$c z_|PWWUyJH!7j+m7RXaErZ0ZVD+2nRzyDxZt%Brje+m8xF?rY|?v_wqvV2ZX=^6l;_ zjg5`%b+x)Ud1RKCD`@e0@W@@R3^tv^sfocC4;w5OX;t5-ZQNdu&?sJ@#-8iGKnX7T zTqATR8%w_zY-l_te!cReR%Zi}#D-C=(N1LJ^Hx+8&tEiw34TJEAtzH{k9UORh?o3j$O+7n+tR}zAZaj=wr zRtU4qC@R7U?-Vx3tnTH*l9nkFfG}%-Xgg0(N zU~mH@12)U*%02gLPnV5D>2q_ruy^^WO2I)?ti?OIjRi|57c!5tcprHsi+SCH6r=a5 zJlE%isk?_eSMR!O8@P|!nJ1$*+t+(+kxA+oSK&+*yOpJRMMWD!l$(vVp^Ym69w$GO z!}pIH$$GVm>+(+fTaJ8uTqDm(JtKGK90i9szR(So)+vobgAFiY2`h9;-?N}|)!UMgJ5_Itb_KMIGb z&!e|CMcij!*!y5Cv&BhN%x^D*n)LUS;J_jTc7^%FApGiUpDxy+$IY>k5h10hYJ^&_ zY@yLiPCH3YQ75EO5trCYHFEW`h zZZXw(2K~(Si9kd}T%IaBCK=Em7xm4VsyEK&pzFDZ6&z=C9-HL2N+a;n1d#>)MTuoC zha)?wwRPn7j)3zsh3^sC`Tf=e%Hr(a01-{`3&s{;(HrHDE_m>gL=w?uSB*<38F99@ zZqTQjs46L;6W~ZI8M$@i_=i$IeH#9C;NxPUWcBneC03|S)9XEH=iyWbdIkYmLqkmR z_b)zwUN&P*g=EJ}s89q+MWP#_>>hU&RgL##wu{4~u)A*QjE))Q9=jnEL;#)^T;U;g zoOnDp?|moTjUp&h95ADSD6}D%l|@#M#6P4o=)JDUx6qKCTNQ_|I^R|BS>&?so^O1I znfHCFi>vP5K1fJDE+8Sf&}|L6XP2I?^H1P*>$#TVni`a`%B!nKv=&hC)8~ykG%A(a z4-BA0s*Xp;;xV{Ztz;={8SL>=a%tXs>ridZQbi=eNq&}?I59V z9AX?=LP6M>+YnxCFOThQ1XFgov0XvRCfG-)rMH=r`>)a>_1U!@p}ZsylYYQM(2~da z*CMail>e06dM8izkK6EHuRVvC{Lj}u!TeJV>p!ju`(gYwDE{NxOQCW#eBR?jM!kvWU5{^MQhlD?ePf>H`HnIaU}|x@W1c& zV!vK*zUH?k0sei@nNPZQI&X|=_idy24wnU;X;;?l4gYm(%g*KaRuFxXWPz>u+7WW| z=0d;b&k%xVEsl#fh)s&!?bEKkI*$`{FCm0N5*2-|x)ArOQPFI&Ks)1=G5+}OD3C@1=^bYgz1@_4v4ckZi4+(E^gH#GP}-12|q zADi%K9$s>H-{kIQfF_l^p2iSNqr9OA*z!|zI7`;{X&zGSxYfN6)vmfu#Nj9tk;lM9 z+t$2`Yr0<#i5Nk5?*#p!@Ln9 zVCgGyNBs~qc^3#BZ{Wjsg-+hRfoA^vMDypiy~3noRQtGaI1@&(82lU?6*_S!DW0mVgFwYI>;Yfc67$zqDD?fXN3w6r4Ej3kKFk3>5^4yDl})S z86aO}f7D z{ovfZE8GHF^P7wQ$TMbUgXc6-94X^hygH~+wikR27O01(U(=bHOq`V@xByhsmCi8JeB4SRZzlJ5ldp z=G9@?eo2*Twp5K8AE2mf{0C3sd(`G>cr0<~Z(uA03y3 zzphMn$-H*pD+xD3N~i6{#1NITiPWvPwfOTHpFMhg64EWM3i%|QxmG`Pko&D}B4Gpc zWqG+3G=r>9V5e$x>qWt5ZJYDyqx<#|bz3HKvSc1r{;t-pSLKs`+x5Uj-eJTM)2H*{ zGQ9;1eN?jYI^i|1qloY*Jm;{5W3}Qyh$!K!CttBx^1AzKwD{vU2um{7zxC9PVEoz@ z5Ivd0C$6`C0-m0c+JJI&h5WcR+`Rys`VMk}4D~I^@-o8>z8U|Pk^?nm;M0}@FZb@g zYz-Hc36i5?se$k*rB3P<1sRflW4$Y6x9V}gu6%Krc>gpX_RVKsFSzbx%d0a~mThMth80e%xHnUvpMq=%Z`_8DgnGw4KIEiq|yBV}H8HOk_$SVH%N5Ow{* zwAB-svV)15Y@ga%<}0Gi4gHKR!YQvy>skDm8)|b>&iXLfdaG-@_UzBENiCHKJ#eEd z4KIwiNGi}>l{kOj&9m-ASNq)6*Q_#)oYzX70qu#W8yD^PHZU0RuG+exa zxsMeWx7E1t9G{r!EjYyqqD8O+ifraqKwb+e_ z!Fs+HTr#oFV33J@nKBgho$PF08LH!%D3kA1SWXhAap~hCUbFznpw`n%-$2g~fnmC5-lx>FRViW#+k-};O!pAF*>vQ{; z5>h>`AG-JQ$}-U2OEOb8-F=@}w{fxK6(IF=hefC&Fm%HDyxN^PVv(Eim=i@y-$_Xb zAd~gRmo6PrAli*hT=CnMsc}M>n!9(378(QE5pfjDO!lUY`*(8P?ykr77G(4k$-2Ip zSs5EsDp41*K0@8F<%r@4>bT9kSXig9p72v~Tn7DL#5K@%PCbf#qi|<#-cRhyw%XgC z6EQA*KVB~=Y}L_`?M)$sCt#JQetarG1{?~@RaG!z`Gc1#S*am+1Rv_>J?(fjo|E#` zuRbL~)hVs%!wGBo1oLds_aKpD_h<Uk?!2$Mh<)d#jDwC&l+Lk!+fIogO0?`zX{$NNO)W}vw`vOqntx9^Zk4`iy|i#Xdi zfxFiA9!TJK+CnqP-*B0lD;sLg$jm^?Z=%~`6n%>>V95RP=m^3Jr{f)=QiS{=t7g%< z?^U$hnetb~_cI0r31Q{yGzIf@h^2i2FPZ%=@ZDj^U%yNJ8x$qBzPZn@NF5z8*mzvz z%S@2-){J^py(751(Yk8Ynw|diN+Nwm&slWmF?Y0m3X64X$}g*_d6+R~F7eeF8@{4M zxKL%)fs=battsoNwkW5+&HWQ-+e?1Qs-$*OhL+Kp-%^+y-rNd?j!?l(Uf*n+0=M2X zgwkr3eW~a3#AjrQ`dBj`%Fg-K|G+o6qELcb`7vr9G9^4d^QT~Y!{LBH40emx$glw& z>l-U)9y~eYNdtI>hD^D6(cI_cu?Vor6wlgeVkP^jsJ^~el;3vFpU<1HB7ec3){-Oo ztp96dpGBvR^7{3HlYt&jr868)osRPdbg29Rv4KCt9XjlZKlIz5OTT-u9k`=lF{ZR` z`$h6^M$rDUuX(Fvrh=nWn&8K=K<3eFgyMaQ9?6Y}{z@E;&s{OUufT~r{K$T)w|CHD zb|yNyS}?n32CVle`qsL7wDT=jsqlGlSi6c2J@#J`SpS;t&8^zNgmposMyv)MLRC`# zwU4Os9r@f|y|QzGd=){!@owJMS%l_xj&WX}F>e~?{lx@*uKXV}zDL{xYV3hJqs04BzH2KouGjzd-=?xOjRih5p_pxm;hNP|0HEy6&D=^0rtnMvRApne+OAY&b5kwa=jI-{;R-jRMc)2jliaIjMCYNJ{xgs)~k`}KAIoIG;fLV z1kd7&E7@7mm<-&yGhWh$cdhtyek+AQ~qgq0W}LEBNp zC8sqzS$9MX!rz9(h}DuNKuyqNpm}9Kp$r9+i<;#y?}4!BlaO&LRDk?US0jI3cDZ>G zItngVOk`zO-G{{>6^n~K2z=D8Z?1LdQUUxbPb$fU71Gzw-JgeW-t*qyR;Tuvz1W_y z{1p)RF86ON(^FO3OmE3ZA;f;+lp|4Bohw@BEe7|_mWhab7I8`GwkmUqj^42;(`{ar z#g?pK!Kz)-zT+}#P$-@|n#GhjIBn}Ry<1B(uq7hwb-+@mrae94(8bD34SkiY` zs`HJCIaQq+WtGOMzJ97t{>Zlcwv%b3pfdI8vi>G->2IiP+i!Y*$NHdKDa~8EqmiJ1 zOg1=)hrKa?q=qF`#%gt7ls1VWEE9SD8S>>bOiCQpN$`8rADoCIivOsk|3=OLZ8GCF zXY*h4qAtSY@`nfUGFUuQJZn~_1_Ez&Yvm1OD2iS^RI&KW)4%!#NFT>O9d!x$s~2s7 zD4BKT82>q=)m&HZoYv3R4jqySmQIGvE<;6aI)xl^rYcH2hG5pv&)Ak*W+f%RzKHI1 zu}r097fwpuvM;e*99Ih)D0Wucn`#IM_8c70XlS_NH>c)cta_Vk5E48zzt*2>R)Uz2 z3mSR~PL>lT=~b8aAOW)RKw5VkJ}QRr(}s6Us~U!f9KrHqTTUo8S@YNgL0ikCm5tI< z7}*7mJuZO#6rHiAXecY2Wie`EwZ8uKOAM9n8QaFG5Z9>9Z$M)7nw6Fbg(3xq;PTj4@^`EeV@M&aDHl=x77UL+AE zEa>CBReSC-RZxGoa=Ku)DtT(I?>S+7#7pYX4;N2-X6Cfbz}hyxJ&o-ly4re0hq{YuEQSpvxPfo0)((%VC;7nPxk(?6jKH;{V{vv9|pGSI(ylsJ=jW8Dmm7aLadilmeauy(Pc#T`c*1`+LoO+j7(1g zge9c3i9hy8_^|jr?hkqn!2eCgksbV>a%K!uZCF7t2B{~D^AGo2;U!5w&FCHGI?wE3 z-Cgq?y2)Fxz8*{qc*#9;?tOeVsc^h)3fg(05ScR0he1C)H7SgVJTNeeY>tlZlaY~~ zSGyS2-pK3z`lON4c>CQj2JZt=1@kH-$h{6Ld5luhqmp?TH3qV>WILH~vdUjtTb4Wz zGOG&Vwap52XEG|4R!;I(D>a6b1}NDGnhzrE%Z&K#1P1fS=TP7zQ&D8}ULfS&cUZRK zZ^M}8Kwmto*bx?%?PSuR0~COq-LLWZ)60DM~Im%qFZUJ^nBFz!mW>$BkOcTMWl z{mMQ|={$N(z@u~`gtLz#R12r8ksU3lXO@-*PtZxovHxa1U>s!tP_sn&?H?>IVR$4o z7ilu~`S=}RPabIvj+ta9@^v~_nNqg!bnL604H_N&a}}gA8@hdqF8S92&X6GKm;nefchbb=%&s?kud-&a(;P zGv6w6_-y6#iXr~r*joUgnH4s?R#=29xnV`VWE$mBirm0on7*F*VdwV zr?DEvHx`EcVJO7$y?^g-Go>>2RHUe{x7P4b&xgwct0*$=cqfI4mX52f`Wtn#Q+r$c zw5n$JsT>ZoY0suxqYh4gedu%l)`Sf?ObSLe{$^&&X)1N6sU7jp?F&J1Kgx7yg=BoEn z=|`t#dgE7>!?}-K{!}i#0Ms%$O7+ZamOd)TT6r7^B@VytuRyD#06cDrPo&(;!aBFG z3Oi_apca*XWm$h325<`cRl7Pn_nEF#JSA(U{Hz$9c$OoNCYTfxp1)ZRklTE@&}9`wprK&T&fGrQ4=eD^RIB6~h#J9M z@BGgBJ4`ybC;;pfno@+Xrb5GFsnLdqgc_*1;c3K$+WDtx1hpsF7#YE=W@Q(Yb2uLS zdinaRbYqn*DU`7a4Jj7R(c0JHF;<=_C3T6c>9OrUU^uj$atk|o+?k$Iww-z0l^NzM zJwEIM+BQ&f&_^nK{>*!NMgSEbAA5N5bJ@#h&nljXKC?ty9>a$5aC%q0AWkw#o1Wyx zgRQ~B0*LT3A83-&1dcqpRU?WLlZ3Fm2^HO>ZyP2h(vmrp+b&SO*6fh`PE{fctJr@> zputXj#6o3`AXUi8>x-(+CXK-Y6-->bpRIw#Ty z|8}}WOO4$60F|UID_P90MH01mF2qf|?7;_k`dJveQM@n4qf9vSh=&CTsQAk@$>;~Y5u+gm9#%)f38BB=p^snW=O z@ize8%4kRqR`4)0Us-AI^z~kQrLJh^oIR4Tm6VwH|7=$WAJA_@&WJZ{%rlLa^C8Xt zBZ}~(V7>azH?10yTn(AE+A_r-j~|yco0FSEdR#jnOQ+kqEQhmHz;`zI87C}YmGI=+ zNfRL#!%!Nj>nq-FTz8qO^0|jLazFs9s;U(1BLfDOIlW7jX3elc86aQX!<|xI>7?4_ zSh*VGH9Q~0MEYZUI;0cBCte>E{Wa%-Bj57Bu))T)fgP9C{jPAOG?qq=w|`k|jFGfN zGZJ>(lEbQfodTtDEip>3>i5^igAtTi2)gBlQUcwI^1%&t_#Zyha<1gdwc`;YRz~1* z9sb@CjUW{Vjw57U5{DKh_e+jt>9$M!f6zl~b7}IG(%O`ze(7eXiC5xNx97e~<QedLb*H3=4nz_weOLFV;z~SL)-{wR{lr8+!H^2XjEE7; zw(ElI%v`eIlehPYQKKf%g1Mua@JKnC1s7w&B&f)5J7Gk{&ne}SS_CmnhfEH=&%f9Y zp)z%KIT#%&YO#9;Robg+wYJRKbm9^AEl1)0GAR2;Y&s>5`JAD_Iv`gQTH7+V>S#>gyl-X8D z!=lFCs8j;t0WT{HFPeimYBZ#&yp2Iy&wwmbr2IjbUe;U6!@4a+1qGc`3g|=yCGGK;aXaB9WaG;SLP71vkA@Q~P{RjDv?{!(`IlscSjUoW zHCt5Mh~$oEjaeuZj2kVSzxsB}ajC(_UHm9n$B?u^1Nx1O0F#060}yRrM@KwT60*Z^ z-j{hGcTM57iH9FXXmVi&2!G74dP@z?8Ue!yq`@+Kjbfz|?d}gWrb|{ZpjVd@q7} zE=el7UVnxKxuqA{_LnC_f>g>jq_+7s!;zp$Mg#(Z!sx_Yq0-+0Pjq#GY2d)%?=p%| zgZrR|{o$tXk7=MpW$@&H3V`}j+8p^sPQdU0@nj~TQnERL>l^e_GfG+m)n$PX)#m>1 zZIk@zUyJ@@syIoMIroX#YJ6$zx%P55(zY*+KdG!9nHl>;&pLKq@&$MtqNM0TUJFhs zDl{O1bHzrtkHHY+x@ZkD;yW}Pd(uT;1B8=#nVS6enb}jGecZqNuorU_nI`rw!WxLR zgn8gM%q;0OpMTQ|FWK!1BQurl`$DYw&=pw;p;F&R6dlUsEvkU=#(gUbM&rmf_cJsV z%ULV+fdQh$+Rq?u+>`#WAvz+xD1!MQbGF9neP~WwrCA zx1|WQaxzEum|1WlbHL%}S2@4_;ug%Ctb^A&o&)C1sKH^cR?`}CGyeX1GkJyWKaBj} zoY2cv0{EA&fY;W2a#EI{njv4rBqol|wOCE{hJz7fc(Z_HPg!n%F19kC;**PPq6#B zTJ}qmULz=iu&~^$zpl@$cNUh=yKPtzb%V10>&$oK0e+q%Gi# z)@lL#6WSZpcGIQ@_G8D;&sU5uTWivwOCewh@-xBV*@NnJ^mmO{)Y64iwaLW0zGrN@ zSHChvf>V=b8x{|afRjl#K3l!&!ubmz2hW~^@DuQ3kwB>LX|m7^|1pGps}S_ z1ddlP!tKj^ws+&=fJYXboD|kDE!25+_TYCW3P4aF@*{mwTz!*gNE$1;qXmLw#IuC) zKz{DtnzV9kn!?$NgeNQ~zXCqVgSUlSypQg`J~(S?;d`YDVOV^YdXo{S{CU`*gl_~s}2h7c3I=T&mL)(B@M$|}QCuEy-Mf03CY70aL zjrlQ$utdC2;eA|lPfg=?M~jQuPL{E(L~F_yKkk<2mB18|Fiv&D)KO@ zS1=x2&HqQAw7w~IO>b4Xq{u6|^nv+%f)8()JfgLjX*b!rz=Y&i|-fo=LBg`PS+~#exTzD+9hZ6>M zbUyB>QT=X#0+p(Bt1>?ZqqjFS5f@KOzf&Q{E}G0>OCAmkwDU6LmraiNX}{_b=c=uS z7tsl4ucTTtFtUD>5fywWhe;s0+A5N-gW^v1Kl=|AQGc@?Xh^ORw^|rab^H(tlM00Y z9fklmGV_EsS+ifO=)@ZGZMM#5BzRZiZ$xE*A0w_9IFxoe_ z&i!jglvZUdMQ{v@i=w4-5a*AF0#~_k_>|k0KA9LW74LV!2Yj!a)*oZ|75lmOd_VS^Mhhd_Fhj(dXuXkjFlTNs2=y~K{D>^0wgYrK%b?Z0_?wiElzI~&=T;wt6c>WBTVC~v-D2;D-zTrH@%5s0M z&d1mHxQBvs=ulD0-UaN1SnZB7y}!HNbn;c9jteK`h~)G9^J{8qYNru}m^=3H&~DC# zqpGTEce z^H~nSHQVmm4Q;9~Q!D~?ua}lu{aHCU(wUlcN=?DRZdMD8iP_>&E}lGfb#^bL2tZ&~4)GN(`7F`7{dzzp8>sfsJ!-BcsYG&VN2Z<5D^fO;6G9(;>lClQOTMJmNlU$C;q9<6qtTS%_wstml)N}3!KoKLkh?|MI9RKIl=+oEELxac+NE-GLkiXm{NKu@D` zA@GEr`u9!N*;-6R-ClLbceS!o5qSp(LJjK!Q-*zNfd&`in4+h4liIHOjCfNuwxr;p z%t!OkNlAxpE>FD8yP`tHweeylOnX7k#Ms!mwyZw-l8ZD&)-*wG_|>ad_}orrL70Z#-rhkmTNg*G5)u-b z5D28bljzV|#2x1#1#A>;(_@rZQ3(X&2b{)aWNKRBv^`?O_hh%Qpn%e(|7&XysZa2@ zMVx;H=JfQm=-antX2V%1*Kg(M1wlN32g~ZCsF;^HIh)jVyj)LkoHkA<(`$XIp{bec zCmO{`gc3=-bEClv+MZl4t^e-!nuo8(`P}2lMK!3lRQs+j=XonEhmaf)J7SSKhRIf_IlPFd?-3NAv_hi<^nJrKvhTLH@RbcmsZ8@bWKc}v4 ze6rV`+i^3}c%y%p9y;YDk+fJv#oZ5+d0Keh0c@;SR3Y%SXQl{hM zM~~e9Y-10J8DzZ@Nsc^W7>$B$?L)nyph|?hc#*UiJdcu;f@pT zHg79UhV zc)cz^(8I;|P8?^7uDn>m0{B{ilpC!=Y{`NgSFDDN0Dh4LnY<*7P!*IM;5k-Bj zX9;UpLZzL!kx8NwQhA~rP6qB9UoKV8->44Z(FET9tOU(H&IP^{y&S znJm^62Pc=-jHZCK>}_CoJsy|CYt0&)Cd!8SkT6x(5?pWB_uqExFsJXjv`Z&XeuAL8(a|5K0aa95acQ_b zY)9qcW#ye58HHnVSbLt7oP62I;IqB+bWE9}s;x~J5TGG}`={W|A{pH9nT?(^G_ghn z28xwH>i0YzU{8u+AiKUkX{V&&BLK~#KHa-0Tiv>oL7v*%KeMvCs&R8}t3=J#O_v*l zU_X+EhKAdAuJ2`4*IO~M;~m|5hHUYgh|lHIR*hG`cYMrsyw7w&9fvEwVQ|>Q-Q`fi z#bJAthmx4Cx3fihxf(4XQHIQkjLH5UM6nNO_>cuHC%zl^#`ZMTlIMa{-wV@7XjC(^&&|ahb}U}Rj0>ENre$X#v}`Ns9b?d>uCC*tro@a5(hKgZv9M>_^c-w# z*7G9Z_*{5+_@!Q&`+j0qX@jot%sIRE6W-%@Bp|m*??bN7Fy+DtFV`6amZr+MD>c|q z`8gv@)0;laB%>&Tg|;O-rKFvt_imU{3HxgdzL!tKI>n@=8R1Y;xb68*e_@9c#C7nU zHwwAC^Syj2mX&qYEro`S0Q*WjG6#IMPa)!ugraa^eCO4i1G#?{sSz zY=MvV^Lv^nlPu=#-BfyDYbw<95gJbRnsvi#BVRzF=oWS04~K`jxp{0t0wxKGtbqZU zN>Q9)cVuc!z6Ka(2jr7BH5E4V+(}7E^3-uir2HuWITX~@w-I5)!|_1LbS%(p*eXTX zO*&D-W~Z75;Io2*gY^L7TFuvw*H~Q>-o|ju5pvk%l$1n*^4$)Oj_UPJ<~4BfJ7*q% zhoyTR!zU&tf=#YR)28Vlpx%i^5OjjE09(l>l%JRPB|Tm4{d-K2KqURE(`}Gix~KMB zGKvq}w6usUCrdg_Dn6&ANZZ-{Sp0KEHbn8}^=rF}!)5xNdLH-vZz(&!!SPSHd!v=E z0D-|c0OfMF%V{FfnsUc2Zv8%#q|vAEdko|L*`00&j6IRdksKWKyMLHbR8=)T8Q}Ji z1mM^fUCzc;W8&kx{9qBy%*^zDM!ysl6T8ZNczJses_D^6C!o2E08MP_x5;%p_o`63 zr*L(>gbWQ|adC0YIgQFZ00hLa3M30yL>xU}@)eD*ZEQHqJ5SnQ9O^HM(8ctKy120G z`rfj2c6I`K|Mclo`k$O-%k4qXKb&`}Ng^Z|6Qv=Rx!tS!2MbN02|%I)u$fy>@b#rg zIVdFo^uc8ZvNtg}W!j+SgBuwc8K=(;=lVJ}#h`Ol>N7#Gy+JIJsI?tc;OFo0B0zxz zGvyDj7X{jZv%}@`Jr+<}K+U?dqwYS9j2Hs^TX4No0dG59j$NfO02H+Q>9Dw3gNt@l zA2%CYoPK+GBC9zWVCml1e-z~9e`Pz1ii@8-c;!oiPZ*kSN_Al0KtDr*(p!Yw8X4uV z*vxS*GDSx-W^A7GZ(4LSC!X$39Z5I%k9YpcY(Bo_bKO^a`qZiMxcA#hc{bd$-|9_Q zW70a^Q5=lMy%jzb^&&yvfQ`(ks>2oD)+Raeooeasn!)y*bzxvqH~sgYlYW-;a+875 zpS_`m14&P@5MZPDT;3F^>}}=?@QpWm0==43K*=H_gMxraQ&?K_UPGgMe?IqiWlg~Q z%4ubF)gAOP4^_M1e!~GjPy`?`5~F-r?@Ne@j&7l=nG*&(Bmvff0@zKXi0{nIC>0bG zyZ|e_B@?*ad+If7T?4k6G|V@GECy+AZ*TW*{n)oo#><-m&?}x+eIygMkO3q!(5=6A z&X9;WqXA2Ap4NHLLvNV{1k&pqH!*N<#LUeL8A@gIF#&8yNlE3ezLiaz%e2kJ`-m&I-h*e)PxI(Kcrsw?dp%2{%W%~ePp1juP8uj$Cc~~D#(g*{#Tx57 z80$xFl=Sp}h%r+a!(blL{Yq!pbX`Sp!dq`|J}?}7P<_Bayx6T6nyRwIg#xez=)C{# zu2u8N9#{zCWgbbw*J~KX*Hvr3>IJL#UNXRj6svs6$RhV?D5 zbNDVQBO_BYqU*jM2LlIZe|t6weNm6yFmJwPnT0+gE3V zYRx{-0=L`Q4=g&}_pZ~tJNBme{#-MXh^vjJv{4$2L&L7{<%%KxR}>UM;Fb)^#vHqe zT@7kx>&_-L-7c1cYOnVjyoGuseD6+lZzdeeOFRwcYRhX;n69|raK*Iyy;8UiOpBAT_oT}X z?t^`iHdWkTnO5suI20G?ZB=9k4?(Lc6k_eA>Cn_&ZrFpziq3fPiY*lFi8mA!YPy7| zkP@2=1PbW|IeBpKbIiW@RG-kv1Raf@ozlgD-neGH;Vk0*^yZM;>pq5y9^aWtbLp>) zA1_ZfU1yYLrlx+5$_dD*so?=pJm-Br|Cp_)Bpwp{yLZol(jW)pypO&iJlPhKn4s%Z z*It=bfPpdY4z#*2D#u> zQc|y&n1&DX*4N&bFL1h_?45ad-vb#dlqr9~*W}PJ4>o>Y_z71`hYM$NTI&rbxT_S6 z7btzBw{-yl?bx$ycTw*JKMZu2##atoo&3%AG%bcJNub675jO;21+wv%&!1ZXsUNb5 z(uGGv6v>oNarwRY^(!982JOJtWnmd*P%~e2{xi!^=R+xK#*9#f5=e)HYI{oU^Mbx|n<5CvWU zhVIvUu7Kjf$mYk~+{l=o5k?VlNrDhuUe*`X#3la#TEAkS0+QVhUCC% zA3qAXKG)nIKfd6zq^-_aiUY9AR;xWk-xwO&+|I|<`SIiclU!Qwc&*3luoW(!7-s*> zqp+}W86ebJLi5cV5MXPYo7swmKl}PtL3VU2E5<2CZE6Ukx>qN4XAH`Ijg5(_sZDY93;`VnhCQEbe#Pij6011|Kqfta zqpWOfz@_jqq@CG3T8$DQr=yDi6s4r;57-IJDd^+lQ`3-|ii8IY!kc;-K#|TptRlKs zx1EqguLaj z`i;yW5(ALG0ixz=_F1qd*`#xTgJm{c(>UD@m+);5fzh`Jok?g@`w>vIa{E;=S{y_$ z?!JHj?x?~J6u{XInfEbyQ-iFmcY0FNOz|lNTpKBuL!}6fgU=l~P=P;xzN28UiSyX+ z*c+PS?tu&Z@KaPu(xrRvfk>meZ+iCoM1V#KIDsGydEH%Z&XbHy+!Y@85Ub~pUJi_{ z+Hn>X6`dge;Gv?V{5gr*EC4hg2-_bT#l&gxsbPsGGjPKB$z?!~2N3hPT-1{Rzy(@* zbQ}Jn@dP!7}<}lcEM2 z9esH=2+#;n%@T5xCJj!59bZRM_DJn=%eBqh2>A1eenVMGZ18@JbMAsV#^gy7| zWgFR?bcmBG5=mh*4dwYAI%_OkK;fKXIwMwbZ zp6Ayu2??%)y*Z%J==VX6%gbcDhWXbN6c5hRHJ{+_F8BH3{ouiI2xNB8WVuc5avAkt zrL5cMS7-JaFyTo|&>?$<6{ zK>3sS-Y`9AH`@h)+dRpIM)$BqXFf{GU=Vp}6B8;6o(BS)M0E2Saza8+Y-*B$o8;%` z2YBxb5L-~5=YMY88ea%W05!6{ekaFYQfC5VQ3YmM5e9qMm8swO!*ySIZ`g5idDULq zl9hb7(!04}#i5@YJX%HmDjjG`3`L^j>x~@6`$Zgh)N%szLf{V|FEM#+G;2bqE5zk> zY%&YBIO*u}lWJzm-;0XNUosHPEG+AY3Y=Hh>rO81W1?CQ9 zUzI%>Ijpm-BLoxh+ZLOen9rYutiXYn=E88OB;ljmr_KWPzb@;hu&}W|17ZT8y`Z@G z4GRlqlgk zXY%biiIkL;(ed!6^!dCKHv#cxW?|v+RxktkqXfj?FW_?No6x_!YCkI~U0YiN9_E85 zYEhL0NK(IvWBw|mg@pwk=iz;gYe|_*swvSuPYCfWm&?lQNSTD-Pf{^ZpV8>-J4E*j zqWe7Xjl4dX=aQ1wzXQ7>mEMk1^Jm~FCv7RQDox^bySphBWjwQftSn*XV2+Pju#iV`{E{1`uQ>MfunjDkd@m`o=#3!w0$eU z&I9q$GB$?wUB&Y2=a5xSBxGcL;3|#O;=V<{yu1VnPDt&^=#s9rHC=gmxiFFsQMW#* zQjSMN1k-JgxKSNPOHNLHxSp{2!}GkdXcoMb==JqzS|U-!rC9wqG!KCrx@mS$~5uuICznH!HKUrcO_tNLPQNuQpb z1?&c>lxR^=y^-r4@306mR@bU?fQ^WVxVTt-4-Q4^jp3z*Bm%8*vgE-or+XBm>FaYhM( zHmOiQ&Og7t<@CMd1rZq_5`-cVvk^hak@KSQ#m;1DH<*l4>ht-wu5JUm<0Y7Cp`)Wi zdyDSVOgdSrtIJAL)oCE)?92wjEyq&M^d}W9t*C@V$mxJ*lg?a&D+BFnaNl}cv1UyU zR=wa0k~e0T2ix01V9-@tO_LG3Bl=bx&ptD~z;G)e|cSb*^~JTpU`8@=vM z0!-!Taz!p86fkSDa=6_7<>|=uG=4caB*_y8DB=!)s5zEdl=R!e`J_G<6mGmEl~sFf z8v{d9%tLQf%)Gpa=I7_ltgK?o1^9uW^!2x%CD3P11k)x^dNUEU=r=wBkworr;!%*W z&`^Z$I!#x;=Kl9YIBWd62nYyECztZNy0}?*UZ=wnNJw)?b*@H6qM=kN)3Vt&E#cbLJm;R zi=UJTMZDws%bSyW=N&Lz35rHBh--6Ve&$jqtOBxN%Zb~`*;zpvo{0t+PI`=Zzd*r` za{jQeurk|4ej*+hE7>wwKn#CvI{`e^Q)jnnZ3Fd-YUlTZx2=KO&Psp`BLvk~j=l*~ zrxgj13v@KE`|;~tk~m3NaKp`usNm_H8{rWD9PyHtrRD|$!(KrF9jr97V%MB&i(&yd zf(G-%qlv_H&GUXI&w3h zme(oeusO6W2NNpx9AHkMJM~k{8^^;OTC>Zr*hPNy#26Lvduzp>yL(j%bGL*6tT2)=0(bPoj z;QkC89c#BgcUc>M4W>ceeSM=y1-f|8@8S1Ftl_vyoPtq06g3@7nbnjz7@uLYc*2|FZkk}RVGfT z9w}5-+H3T_7+}^JSXfZo+(rkOI5=Rq?$3qPnUe*p5gIFCy-7&?`0?Z8d%|$Y^)2Uo z_?j-v!fX)m;`!G0B?bTWE>csh@rO0YTQnG<$M4Sw4yU8WY_HFY?L7&a@cmvqfBaT# zJ8aIk?7InC7o2(i_mx6uVUh$;2=cH0zT!FAI?P#gI9q49)kUU1nI8N a_sPmJ|LgB4!tbHLm&99H(Gp=jzyAYeFv_6- literal 0 HcmV?d00001 diff --git a/docs/images/multiline.png b/docs/images/multiline.png new file mode 100644 index 0000000000000000000000000000000000000000..cdfeb3283d37c4714eff9688c35e697188407bab GIT binary patch literal 33129 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�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%0DdZKU}N;Yqg#N_g_3A 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 W_vG7W01uvGVDNPHb6Mw<&;$U2N{_Ap literal 0 HcmV?d00001 diff --git a/docs/images/ptpython.png b/docs/images/ptpython.png new file mode 100644 index 0000000000000000000000000000000000000000..c46b55a8e6bfb489a558cf67390f10c7c7c5a606 GIT binary patch literal 28700 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 literal 0 HcmV?d00001 diff --git a/docs/images/validation.png b/docs/images/validation.png new file mode 100644 index 0000000000000000000000000000000000000000..cce14a12f364e0cf6f167912e18f5704af6896c3 GIT binary patch literal 17124 zcmdUX^jh2-e<41<{Wd3Ip-o=QC8uJC0{+U_ECr;C$sqbt&F(1_sJfA*b%t4XmP5cDlFm&zWQ^XYE4c_Ntu11Y@C#u z|7d1$Pp(KA*E%EzQy|v<4ZK5HKb-UqZxeUV1}{<^5xL98Kl3w9VCu&qq1dpnuw5SN z1j*IkN}^2hNF{BC5X{If#e(mc;bM%`|G8_DWUe1Vf`b423J0HX_cmDt87Arq>W$4* z(9zW7G?YNc*^z=7(^qPLCI&3=S{Dy?OG^b-f;Zp;_j~5o1>gEHrH1uH6GRCMFU!Ak zi_IWbzRoa2!igJT=j4Q<6v|O^y*r)P`ynOfOs&C)$If+-rB*~f4~9{Xl!f zG$tg-6$Id%#IeO_5n+m4uXW zh_<#Vq+10G`lFUrt?)bBnbn#leCuJQI8z)|EJV>I_=KJah87J1-O~)t%{`r{aMqlm zE;v{pa1hnhrZRJL;UJ2ds(}#Sl_ES$U8X9n;E-;bq#@Jq>M9g;qhy@KK|+f32_m`5 zS8nc0x|&0HDEbbLut?nW$-5pE>tA!*gD+Ode4rs3+`gR9&<_TcbM^uZ2qq?59^ zr`MvNKSzyjrhM}t;7moU=*W(XbBD((!u>?6(AxH06ymp=71!I#hf+BgwdTNjdV1Pg zu|b=~MZofg9Tp2ql<9N+8|+zY&cB!D7TQ4F0r~leYg~lMW{NsG zFo9!;b)V6F1@%xP3H$xN?W&U^LJP^r45*?ZHWWTKsFWBGhl>Sd3i%o|NDt5lAvo84 zKEC)O)xa#o#;CTF;!SOcWbKtjGUjq}za*HInTfc(tQ$M9)jQlLF0V_9_bv&gLL(|_ zkgZ&+q_=NK!pW&}^s@u~20MhDfdK(CT%Uy?R!ca>3(OL`4R|Mp+ zU%(PmZG?(4C8A`Em~9D7)<-oqLg%Z{qY}MBg>WaQLse;^B}j+=`-g07vX%MkFRGL2 z(CRSN9d1Esskn>`%BEu-CT8_xLw!A-zyH(B<`=0q*swSE*^SxARr4MYznB`cGU!F7 zKw)C*a*aWjWI6eFqcjN>jRMX17_d!z70!KzVPcflD-|eR)VaL_4EFg`#Oy2t z@wCyEHSB-cJTBFj1L z&%3v;Pt-xG026iDW55vNC!ZHMX5Lv%(x9lQ_=Y_TbF3^-k}*Ui%daH^BSNMh0)cvJ z&Mz#5D?R)0!-kXCnv=NGFs{SQOy^H>ZP(=b=d4I>`j}l0^-oqOK_w+cJ@?N~yV)%k zBmr%4XAZH_xg^l#ZGrG2jCi6sYNB~oAA48*ArP+UCin{PczN~q&>$#CBOgBm(mlNb ztoCru6#@q*PU181YocvnBAz#HSDmtTrKZExW~g}PJutI~eoNr6Zk}^x7BullxCv8; zsHaZ*Sv3bK!d6y8nelCzDwM%P{a*~T50aHFPLd8cHljCKrO z#yG2lnJo|IeVooNH0D0I)6vldv}7=wj-)^ps6@mF%#Gen4n9hVHBG2yG^a;FzEDSY z1%Qvv%B5my9Wj7NiJMe)&}}Ru1eOeS4o{=@|8mTeAjcW2G8t{vr^7`yuX4#RDhh1L zAR|gmiA_}Zi=ztFyL_nPh)Q11f951EU z$-&0ENg`djI4n{7tIv4!3`M6)!(^EF#4rUa^s+IcVlDD%9E7|bxEy|^8Xp%PO7RsK z-zChawxp(B%FDf@;&J5enx0GR~f19b2ru3UG;WzYyUQVKdq7g*=gTl@6;i9V1UX6zw}`9#l#+!bi75cVI(&l zlkF)uQin=D4QT|2n|2wpb{ew&!hZWp8R-r7+c((g

)tN_3lwbeoS%dc%Xw3St*l zU;0QF3@Zp6>yoOx#lY7Lh1v#)8Cd^~@!5J*Zy`7C+M3v6_{r~GRbwWOpKVD{qQb&T zKe$}-@3KjPJ2>_YCCainPRuaUfe2<=k(>lVNm$!U=)rm<(y20C=P`omw)erZQhShvnmmD+Uh)BHI)0N4-OZikM*nX@E8iuQ&xFr(UiLq!u#ZC*wP;!SIY0& z8n6hl<|`PlUyI0mVuSwpv4hIkuVV$-M^;qS;X^i_T3cfV)qGWS?kWMwI1VEE#8Dk~-K4Vt3Z>QxZ+ zC>XCddnUT3{C<$6amM8sg}wWNm=bYC<}Z~eakJRhG+b0s3N>Zs7(C+0|Ap{O-m`?y1$c)6_;DTnuA*|aiiT)*}C^$nq&-0PSzAD>1?Osug>d{j>D3%AG4 z`!t$*TGN7oz&6fhWCiPtmga$$jA_%grzT%9k0HWA0`3Y?7H%ON@`tC#K{Pj>tu3qU zRsO)qQ8$KF4n-HufdU4b?z4BdNp3TWbW08C1P^4-cU;QjU)Oq01SMph#quoPl-R|^ z(&2jl84@=7NKXD1ZwR-F``<{6XIi)Bl1-xIoA#C!Pu!NeYt}{A=aV>(Rbn@fdRNJF z>3;-#aVa5Y6X-_g#mV-G4%lCS

}i#izf=IMlr${K9;P%DNl}@!OFQ3|NX3=*XcW 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 literal 0 HcmV?d00001 diff --git a/docs/images/windows.png b/docs/images/windows.png new file mode 100644 index 0000000000000000000000000000000000000000..188d69822390f567cfa27f6775c9f5f0aa949f38 GIT binary patch literal 18368 zcmZU51zeNS_cw}yL8yR;lnK&^0@5PV-Ca`BH5fG}prBGpOG>i=Lpnx^3X;NrNsN#j z14fM)vGosO0=?WLQSC@3gsRaKtrQBY9QQ&61Y zxIhK;^!?=N0A8qGs4G9GAd&xlZYfR#dMF(~}0isawaj*qB*xv#>gI?ZJR@Kxo4x?kHptwb$`dr>1 zU}7Buv9MUoB9Zo4R1DsN-BrxPK0GL38g_@zsZ&^T6A<@aqAWEutLe+#nO%NfUyE?_ zN7ZC(C>?1gdUQl;+O7wo`W-X=fY;SNm%6hX-ySY-nS2VRtvSc=m@3~$;oim0!;^<# zr^v=PXQ%>XAZ(Pr1C0ZI2#H4)IldUS&c~Yi0K*hl!eZ}`U!QK0pI^fkf07Co$T|8iyO1Bup|ir!-ONAWN8ty zgUngP%g4rNIELGC6;YV!3u2Rcp`oo~71k9}8x0EmLT&P}v%v62CPRLlV|XiwW%3@0 zLG0CqiEXc+_fuEmq&9w?rfUEEAn*Kl*zd6A_?5eg<4v1`o^(h|NlMi5aR>%VI+2t- zmRGw9P)buAPO&Z&g7Ql(+<5z0fWEP@arJ23Jc)zMR5VSmj*riZL~=OdlN_{_X5L*5 z*lvn6&5q8uF#6EVh|2Ty%vJ%pZd7*cK>w@pg=UseNVnL<7u4j-+1ov{VNu$YK)aeR zK={XX>0j;_fs3u(J@eub%qUE*MKq9M$0F!(vFB$$$|1E)&UV{|ZWp(V#OlueuTcFyT^PDIfbZ=rx)YtHM=cA0`J7Zg2(n}qV(Npf& zB6IaS9{QwiZG*!`?ZcC!Ir{!sUI@TsA6ukt_v69~GIoD@9$R)1Mr0Un(Tq&!pWaX`oK3p|GALtXkLOd!4(54hO@cS$B0uw+3S&?VyF8lVE)I z`-c8y(zayF{svDQbaDd9!gV5h0_yB1<`!QznMUspA~(jUn{gy#Ig`Vh6uBL3VwyvR zHPq=HohA6W^@A@S#ud9z(nnb=c4BnPuN->nxZYT8Y)*nJ@|EH8uHEZzxEN}65jKxA zBZ1|%Pl@kHI36%*e}F)=&qhCQm3!w_mn}|VGjX0ibaeiw45E00#hqg!^t2{)bo%`TGtgH*e$pA;E7m?ji3bRlc)bsODS{7>@dvmaUp zMbfZDYHoQx0VX)VU&c{|=7S8%CVzvy2)t8m5zM}iq|E`?17 zo)~G#`C*rMS-|W-+#1cN9N!~+U3C@JO9%7X$@7-!{K_Dz^?d7`OvMwW$*rE9D3R-j zp{Cx|9>aqc*op?_(qpR%^gCnPWQ|{entMlyDuFZ_c9NOm6cpLS8T&e^G?6Lj-Pnu0 z5iE<1N43&4^sWn2fq_m+wMz6wxcDRPDna9MC*B%Qyoc&=YYVd0AJZ~X53W~Ivd}!| zipG>`+gl3y9yB|NtB2Bni@DHSd9ZnUoZe*15B0<=Spw~ z%Y6^EMqQ$}Z+jOx6Yo6P9~?vHY0rE}Sz5W&d0jfsJD#zUMQpsrq%h~`OY2yuMxkr< z6f3_GFfg%2I@QnQUFziexzw8JR>dO55~Lf? zal(3Xa7bOiEQX6qhmKaDWk1LF=Khc(shUa!`;jhTzqPiF%%~8_H=N=?$F`950jUvU zPP{82rvg=fx(_wfeVJ-?@X5-Qx8=_O?%m$Wa2_hMeGF$9M>xV239+7>rG zU{Wk4ja3iAHnub!`nELX*;T{O)tcKUb_1&5TX=sYIG%G~*Ym402@i{4{j1AIhXAq%@Jn8AY2-d>> zL8z7SYyO5Jbkg!3&trJh+4k$*;8Y1S^fnU|@{wk7rIQ4S zuj3Cj+BfGb3R_NQu}6M22l@7+f&x)$5R1W;SL~b}k%KMGyLY$G7@fL?)(l5pS zTJ}3;mh?KPZpydS{lF2WyL5Nz$WHU|f<%SEcW}1RxZnQD5}s{T)V9~@vNGV}p!Z%b)?FnZ0C1sBz^>5W)kE*Z8C$bTW_;^BEj35-UcwrYCn{ zZKT&R$~Sj|+H(Ep#UKv3w=-Hld$J#dTOAEqmy{hCH!bEIO)X#bvbr4MzJIYCHeE;! zohs(5QLmWV9aO7lxm`#5M6UD0uBp4krhw(VarWSA!Klw|NG434`4rQD>!U(+=q_{9 z%--A6nWE&m86i8+q3RHuc+Y;<^@H-OYnhOo;qFDd79-0=wk5thw>XsvW)=3o?^<}~ z*U@gtc9|*VBJ|CZ@DC{{ylL2IIzI^TY;&f|9naD07P9aN4EBr77?dN>33&&0;EgE> z)!{wrQ?Ug})vKsWeG&IqR8`fqRA~)MQ|wDHDMhKV`>scnP@K$kQUA=|k`vG_)y!xU zFyCfliluoysGz53;CH-p)zSP?V3Ano1*FO4&PPkP4Gb19@|T5uTY1ck+Z(Jv>s$8^ z?^}Oqo+f?SYqROz`|5b2c-R?)sT$mFn}D}2v$eW2PnY0!HQh^-MVN!Ekb0)tA^N1f z$@m9$xzJPE$(5XwQ0sugs?a0NlW$o$=zWN`wkaAbx7*7ca(KMOfUC{x_7gl5S9K@t z$`CN1{NdQ|J#E;1;^H(1wum*^Ln3NdilmOjBaI+k#Ai9iAJ&H}kebB6{LpP*Y!mK! z6lM&6Jau@w)!O!WOb$*gkm9S%xq)nbh`+z+rfLEVh1%)IQhZcs$@b7u&Rv zKi@}omtHGJZ=ks+CvyYixp?U4FLA2?1LCS<3tKMWI}5B~>#w9@Pn|~g%m-_)u7y1F zWL<^C!N?k;+uFtd_u*|u)2XwaDbci<7yVT*0d#tN_%^!Wt@HiCPxBih3pr7j1)CnM zT~p_{Qc zLya@Upp!=vb)M73%(45(y{&?WgDnlHPl=M+s?#3ob2Irnc3JRIVYi3RKPvV0KbA)c zO(1flv-35fjXJK<6%bHqE9rygk25o&(@7$p!vq;amyI^vdIy!^WMGBOPM+5D$f2q+ zvAoAMyGgPz*Oo3&e4wz~^sa>*H3F%dT#F;XR^J4v4J&UyTsa zrm?I>J_J(p2RLWM&%7eZwjmU{h}2#sc3HOJ+PNU`3Dlr_%W3X>&+M`v|2O|n)+1Tt zEX1PFk{tV{LB5yi#_&xP7?fvw{L+;<^b9VTQQJrSUWKT8mdhZ7n5lx>5F_jKCpul1Lk_*%S>0vtw8)*A zjFnt2C%zaoYVn!bfj_>EeSgF;l6p#<**%GBy^$9JgyBt^k%-Z}{C(&C_tt@f{eJFK zH#S3GPl|mzVtX7b?7+A!8EY^`LGi6|Q9d#uG0|`CJo9VJ#_A$p%_3(j>%*4P)QY!h zMm|1OV6~m+KO^qlw+zT#>O-2GCYBQAFy-w}aH&z@(F3cI2KuTI`PMnWu7&vu1m=h> z%UV75JDyX)=d%2&cpxWdO-;`+k{D%0JCg*OZS$Kk-e5<)*}+hQaqzE`^S(@Vp2IVH zm{QLQ^T*Y)mb#hf=ovM17%S1x^l|loEAi;f>W*vjp*5*|^3jjB^_q~JGZYtIkmlu# z3>RfqCA@=%<362q1j0+9CZ(k9xYs`wEPoP$c0aC59`0rz6u5e>!Dx5ONQpG6YB3BUoj?%rg{vb2UD$333IJVtaH zH45{@hDuuK>ld0_F-?}pDg9QnobwNZl+hz!!g3>U0Yf7lQC+f=dI#*j@8!O8+oiUbJ2_ z)Co8NGZzc|nv-jWJJg2;4R3F5&v{s8*4=u6xu>^zrD{^ISDm}zE09LHBJSC`6=@YR z_bM_nl50JUoMu3n>Xz*H=i~?n1`yCf@I!NRKVrW=a4ZkR2e+w_LQ4?`zeR29OSRLB z6Rk~H0BYT{bHze!159+}^Z1=wrT1D8@9-aga|me?nTdz5oyqA06k4UMqcc|_>g&el zgt|ZyFVH8fiGl26f>y;la#YqIcXmeAp zFYTU!g2K%kH-Lmm)zUx~Uq(O4d5D|==-2I}mmW;Jar5Rd;IX@ssaZYA84-$bG9`+0 zML8%8S32W8l8l5*?+x$IgT#tj-cGUl9*iS%!H+~X^9QNeN>HZ^N94$&3 zi#VT@;kV^>y)`5}RhNH}Q(fYF{hCw6&xTeqHT_MU={wcsY8a+S@U0&hGgiO*)!yb= zL26^KF|DHs{)(>eZoVk{c74>pcMV}URKV>1(FMtR&-0XT#B6xBbgJH%uWK_a!}f;A zd)!Dq2fGBD<{=}j4ykE~VG4C#FsSD?VhZZ%f%?SBjV=Sz5Y`Njx-;s*ud8iYX(URt za<4!b+104l;8Ca9*W6>aJp$+JBnc(YU5mbBru+?;Z=_T?k1&+$?XA|{=1X5RhbLj0 z6sC#LhHW0R($R4}97AlP4@YFBT5O`_>qUW@0ANzjd3bnKeU}Fe_4M@4Qqe2A)=DCk zkMa6P;e8>_1XHWHK`5GlR~4@D)&?T?K)r2(9_vH~ z4@(;~yLLUJE4rZl#7^?IJO4!*s;^1f!KqF6ligyx&{AK5lNKQl;OqW=(Y&4)C)R)J zM|L1%&Pze9#j{@awl&`mNEI+{uI<50FqT<#1s}iM%aKO|$;q^>u!w#~)O#jA`|5En z!B@hR0anBu>l~UU>Fw^jOD+4;uJGpXkNdAzTZl0E^N5>kpdKS#`}02IqWdO1P;NZ& z$3cP7qU^>^p-0K}F_K!k?%=m}$YF5-=YwLr)r{UoPRf3!%n>-FROdf@5^&rW&$w8%^lPGBbsJpGWGn!5kZW#)7s zeYr#=;QBi{0BljI`C^7MHKnkD6t)2Zio?YsC9Rk`JjZc$*cCJD-%^jv& zN~55pRU2NfTjM%&%8~qfCE8$k7WruMgMRS~Pf3KwQy`OrDcQs8^S7Lu^MzPO-^_VQ z`_k0e@J*q%Q80n7mI{>NQr^Ag|7d#>I%2s^m0V&d6&|Ph0G$GNtKGO@q-5LE^~fVH z&yd&ohTn2)h~~QyvpIA*zskjuulhynma9f}Dg5l_hmAel%8iv%?1NV2wO8vrD+WI5 zS`=qS4pDvucJbGdb}{ZUU?`TZM->#@6R>PD2n-Bt!WL!hZpA|_{QUeFhR;46n7#eu z{fq9NIl9^uH=2q2osu?^!W3JqI%|lS^|$C(bA{?jGOOP^C5IV;^%e!(;XNC=mfoA* zl!~>ihQs>B?y;4r@ujznehSktp$R1g9VkMfN~45s%)6J6uL<17pX*&LB3Se2WhY8~ z4J^0~YhyR`m_!J0fvZ2Be!G7BdJ@`XVNF3{<4;Xbj*n~n*UgfXlX(Eo1u)CY&p%Q< zC@ZzfGcunny(Gh$qvNU&VN@ZY=XVsqXAm2dS1ZqIr&$Wg+1syiwsZeT=UQt3-akjb zY6-tTUb(JoFj*d%z%j~nv~i32_4Nt8pWVO&rR z$%{YCE-uE!_>2A|rl>AOkR@zLJutd2@>;FYjUMM1{8s0N94$f!un$NeCm{o zrCm6cGOupL;zE!4&98M=&Ru4kfHQJ=eK2U{(f}9z?kOhkc(pa+g_j?=2N47srW%h+ z7vL@0_B1vIe|T)J@rhOf&KEazN2GP0dQz^k0$_@w7zG3D33jT{R}y;Kyl*}D2PDaG zKVTCih23IJ-7MLQUEe4RHXBYWwsjAARz7~87dw5mIefZ7jj4RR74cAa&;(UtxZ(8Z z9H;NqN?y2t;dnw>7hR@qiG={|{Z-iH^~S!lz1-ajOJ{e);pRF?mJw6boBiiTbnd{O z)5`Y@dTPAqRjR-FnupZ>Ddncms(~A(tyz)wemPwD8MXDiL@4NpdzEk=SieJ>eH!&v zW*UVe=T+DY%{k0mZK~PG&`>T03GR{;Ulb4#=Xh6n)(~+WMG&qtd~_1Gp4Q`(*U-cw zF#gbRQFeM>8A;-A0-VFsXRLrDYHxo9L{Xx^xkY!LMVb)MDXi4uaRV>`k)XXbQwMwd z*JU`l*=C(VE8ll#pPdsjf0nE=&*0LJ7*4ri#_4Ei9RDzh{s(KtQ1S(!;Bi-?vIIQtMltwo;TWuXa@UUoWs}n!?_pS8* zE}`7=NZ=t?E^LCHc>j4#BoaZZBQRauv;I2JxI-$Bl$1G8F=1eqK8K_A&{Dhz(Hp+h zi#NBMnrVW>7eI5$Q_Wy?Qe#+?#h~@og$QxTbTy8U2}w-np#e%qu!;(8KBKZOL$Q5yPM@akZaLY@*^QC|#b4t1Arb@%!KAeQL1dbzR%krsA(#x) zr^f4abQ*zgUWBRCia5X(8N?}Fv&Gh6x=JWYdCDjsu+5EtWVu|XuaKdi_?#JkOwC6cu%s`6wLKM`ya)s=ubSl} zoYQZzuy_j$Tm|!W24=|wYdH;Oj{-poVLDi0+RqMwS7w*ipssI>hzi4oTw8~y+*T;% za&-CCOgmx0B`Whpeb*>GPO=sk#zkN*Lq$rf0fEiI*|#6BT2st{;$HriD4UzJ&5{FK zEe~X+O1uc7j1Bnodv=a*yOu5>rSC~mn`j*N+Z3i}6jP$O=<*SJQt{n0+T#;%D!)0% zeBy!y4icQAtkE_(>Cj#6nNfA$5Y}H6YrXcRN(XyFy{nPdojb(-GHy}+WwxMrcHx78 zRGso4W3@1&OAjt;Hy$oOvLI%=JDASM#BDpXx*mdM^C?fTycVjv@$J>5QR7J$TTcw-8{}J!5SPH`C9KXoFE8L#Cw||U zS7dk!QthFuZk37L)!ditOsIHC35?(ED$*ZhTQLq=deK@5DCi3+db^dgOjC&oagESZ z%kY%1MzMiqN~JXA+eRwj5<`g|+TVH4MmLZ>f8E-*q|i7xMov8PqDXizD$jeM+wiIv zt%>g!CB3Z0eI;;6gP4|Qugm^cIn$}pzV}@THl26-HMjVaJ=a2Nu*ThJ-d_TWC#UW- z4|2SFP3}zqg}$3(VjlPN@iy-_L@X0l~pOcTV@q>XQs2$cB3oM*!Rz4K_1Y+# zvbNNGc=d|qn<>cRWn(QBaO#Oqt+YmxQR5GqgC4Z#(hKvXBQTN=ue{(Q^T3;$+7h6f zK3`FHJT11Av|UprYB|T8rs1#3-{WJn zt|pl*sk`XBi7DZ>AV{+fB*CsVMp_*WmM~@+ZiJ0=IGL}8EWIM4dO_%O(p;a-5GmC9 z9ZnWi1NQe0sjSgTjiJfNaQt|YJVL%}XyB6HH}T{RxZ^uU6@FWf`Sf{xEkhYQZ zEWCs|@|$Iu0LrGXkfB#M%cxNx8GOglTGZ8IrM|1NX?M3`*pt}QZA8=x<2 z?px6pOiJ!qns>(7diB38_~2UV@Y=c1tDK&OF}x6cL(m>ORoKGr05`3d5A3g$!QJM5 zSt26&s)UZg!a(_g?48=-7^e^w8)E4TjW(IDQ4i9Om!I!QT;h~?*|{VFgXtmPlG%~+ z@&am<($f6JC@f^Ub8|D0mY&}AH+B~hz5Iyd-Vf=%OZ^$VqXixhIMvD1plF z+;*=JdeXB^p!0*9SVlsEo0^u^aHhmhHPuEH7z_r~yrcaN7gJqMk7kPL`BhMjzRRHT z!qo4J2e-{RuDQI8C{|Bm4CV{-JIWpp`sC+=CT8`30Grb6xM8EsTG&*&*5E%8DEWkf z!s};G&x2r!<^PKX7~UC_tI+q%ejk+6E7+oO`8xkwb%-3OzVP`wj?h)BA1=7(4*Vwd zdC$knH1`~3y-}C&EEE)*sUJV)Dn?&--k55v8b6l0$`W}S_1NLYj}kPHNCzGbxFe7` z*%e($#t!{0TR*k%#E0XRR>SpvDXLRB=$LoQG1|4b zP`zr@cfpGWTMxfeY_^^-GF|B5z-OTGCx>JP7%TFT+otNd@l3o(zzyi4tg%Fokzq!m zLteR>H!iIjr23(T4yTeNj-Ny}6=qs6!7N;T%tq6zl@@vRJmLWG0SAOwuHAi&!#UaZ zrQyid)B2`~M)F_7|7T%Eftzwa|ihxybY>Ked(-=sDWl=uk6)4 z*j?$Kds4ue%(f^Z#0PLw^wgqDIz}q!MBGFoouI2CGU># zz_XtMr;(H-r2o6PHCN+#hH_rhYDM)bO!=94O*#0ylWT1;y>Z}W$|CYo{u=5$S0`Ol zDp_peCqbKXX4G->Q_M}Z1=f)Dz;25ILadpCK}qem)KCSjtoJhW^mio7?#kSUn?ar* zNq0O`eWw-_*FCx^JiVLW4fW147+sohoWIJnBMk&?6!$#VZUHbB6#jhY2j_Gbx$UKr zSZ#U<0+0Y%U0M0HfkE4YUFqPH2;u)5%{jhrY0c4C0q9X&k&F=vxD`Zh zI(lphM-p3OUy$=5S7L<)=pW0F`yY*(tRBS#lADfpz!1od9df(x$E+<40rEF*@@m#L zq=S+@73hA&!Y=zwDB$xVuwoQx9k~h?NCDk!pxxd_%kkq2y#P7G((XpX@n>@TDcSW? z^!=Ufjkv z5;u8_Zv^mOA{%-HxB%#_gq=YFjdr&fb{cIO+wLTZf!VMka)QTZiHbgj*ot@ZE$fI_ zq)z+_g!2@RMw?fVD?g-vT_98LBd&yvrvMF#7g63+^cy29m4A^#?Xw+vGx1^{RN@$; zf1Uk1vU~Hk3Itk4S;X>ymVD>huiHN)p+1ZqoOqZ5L(o`vDnN zMKSdD$-fzWa^fdI%m5D$Ecf3e*xQ`9gMn|?E6GS8ilV6BxP#jf)TkP*gC0t*P6C7dh(K%n9oR<0LhI*uhk&t?Z@ zwE+2^x0gD|tNNdG*H&WYro?eU0aMCM{f~w=zatJ+(m)0|J*N@6xyhu8zZ|fxsY?Gt zB6W`U9Mn*@z7T*Ru0_3LTsvO8FnXaBA$081c;d z0}5y*FQnsDRz)(MTQ^bj7g6ItDD4*OZH?QLX`Tam$ub`buarT`+tK{pdkxgAt>i?L zp?}ofKVlSUKqS+&{;AVHv>L^Kb|-4wF!04UDZtF7XOJphgm!iDJ zR4t&{h??;Gm21gJmd^_uSJbBegp4{BHbGf`X(<<{Q`YhHSC?}2Z-1R-Wn}w1#4%%F zI-+V{&FllW^2Q3GFwlRrMBTqj)%wfB93Nmh?ISjQ3VG@ECb(g$(GhS$6>t~8(jAHX z{9+!ms$7x2mB%QMuFM0p0gmS~IYzt-;llmwGW^}uGzc>8kc*SDsJKtsz1tcgqAsmt zS6`|W;UWprL2BiU6e++aszVytOcSV%48}!26ZzH1!gh|nT7z*A`~*`fj{#BFzm6_#YvEz7kZo%uxS492fIwN2h(m*wTb>|j2EBPPfpH0HUCEK1rj_cx| zv*NTwsWJAId;EiXO-Ja{Rt2+};PgE5)^$*x4UaCZE@)M#Vh{$d0V5_By<31~bSR=* zZO30AFWJ*gVb?{00b_^}{HR_S^VckdM~#DL`m{9X{a@|zYf913PkS`f(r~ejaMjr4_-T$#H{PV_+u!A&x)J}`u zzr}hHLT2v*$1DT(kdA}9_NRBkA3%57lk)%n1-bBF=HtJY09aEX2>Xu+C4B|x0rM>? zZT^1fvmj)|p$KpxjN5vR%YCTLF?aShP{r>Fa%i9YofGjP- zxinW-Bn#|Jdks@0ONtbzYjL}OH@L4=kVoA3{02s{iERQjocPu?LuByw*|?AOPpk<% zkSh9upC2>0R9Ep*1%#Gj%Iy?*P;knT)8JMh>F*4Moh5_md!})8j6M-=A5u#FAW;V6 z;M!H+RCUX!k)&FSH_kiF$@}3`7JeWJdpRr}mCuerP!+&i`J+0ob z&!P%ezpyiRATYYt>2P#H*h>dF71(Q@SwTGXEZMfHkPPevKGIS2hxEOgGwbVq$`>Ob zL2C;u0%ZcUSuK!No%h%K6CVp{zJXCrj0X;1E8B%BI5&lckskU7osB?6NFodzigdOC z8aHB7qFB2054ClPC4NpIH=Ul{Xm+ZQ@+*=s&A3Fy+vLT}TeRi+lnSSzUH$z@hqd6r zqvRL=tY4XNdUD}zm@GQrZ6f8WPP)r>2qZ~u$H(wW-jV);tO%!qkwY_y_-#Ph($-h% z9y^?MdY0g_=06(X_~^Yza<~Q3^9$i=>e}=}IzTXPyVo!G*n&NUe={GoFDGVAd~!7I z_d)hpyc7N_Djk$u#;=#9GB=Vi4AcRR4hbVzOxGymtehyWBpv$uAx%~bwL|=!;{GhU z-UhPM>NENmW&cCa-UPLq}`lFxaK+u4oYC*uxa zt_*XMCEa((PCY!z`Ro3NhGA)>i#U~UTLG$9p#O>Lnant@jfP80 z3{%zUH^NmDC7bB5CSyLyg>-Q2FZp%J_0_$karFJgC3t&_xpJyPMlU!|ib<>R;nXdSdOLN2Vjwb=z08_6H@)vJ^EL?QK zd{Tg~u^Py4_&Dsb>!EDmmrzTA;ym5`$xUokQfwRR+XgP>0Xf(N%$#XX-c6)a3;sr2 z*&{gUiM`wiw}WLl?M?w;QKuh^{$4i2dv$MIxWER8;+IK?`gFMN@)iD*Zd_L?6j+8U z(tRS76S&4`+26%o;WPqY>K=pk_FxJ;W{TYFkCa~DKTZ|f0nk7*m7k{3?2Nud8pc8~A-VdZkEe7gv}B5O==`y{Oj zSqh)fhsGs^mJlvqyGK^E&Hqmv{Umc{{~cyJD6c(tHcA<$CR*5C)1oU;aE4dL$Uo@s zHI#q5p`8n&%l@dh?7C=joc-!@SqrZd?rtLt+cdkH8 zqF~)0+%)`~W7fY)UkbGbH}x?7#CMf6o-)jjjVIjbpl8cD2CUmH4;s{C&9;9jRO}3m zN+jt=5l_pWUbLj7WDU89CzMof&Mk1zy;iN&|Nhfg+xjR=&-gEZwd<4qe=B=!3?GX* zJpbpc_}?+Gv7y}ORw!xnP0(JtI^&f)0slEW8vK*r=Wia;qsE-qC09P*0M<#^`Q_g* z_rLMu|2*=m=igpoc^D2pJgIlyzE$&2njd7TlRa+nZfp?bPp|h=DfTRvrLTaV-wH?z zw5Yzneo6nT_Bpa>%8_WZ*MOWS*)wS3KgsjlKWI9A&vdyp~Zmxt$lLeu<9Ef+3Od44-L_tJ{cVJ z-AFlU38Y!8V+^O5gaP0loC&6*{YyuCwGI;DLZmwH0UoA>uPXVGg23TS3)is$FP5lT6fq3v@rrvqV+M;C$z;exBWf|F;CWKy2u&b@wh zecRPn{$@XP8^(&U#Pg?Jzp*TXUO_rt$?(1;lkK!T4cZcgo!VhbtekAXgn8qP{ZHSW z_NTvmX~4&Eg4XKtn?Sb^0%@^VR{*OxAHX}qqsCdg z5fewN_9@hF|BSkEPU6JETiaj$F7K}ksp$S-g#1H9`;z7GfzE3V%Ye*RkK+#h`2wtqHS*!AU#IZn$BzrDod$XD-c`^( zacnmnr7ywH$c5x)w=7*d|N4Rz$o%VC(lz_1hs_2K-QwnqfW{mHW~CZcvSA{4NHCqfOTb!j9ep{)9ahK`2gV%;4n}5nW zHdjb&o_ybDe#Jw_aZk0JY2(nr81~TKW)46w7(SLf2jsVSP-{lzGd)V>K7;1wLz2_& zTvJ<4$2nKrf6ssDG&}q``B`R6D75zvNxZ$f!gZVhv1iaE^Nq7aRr2&X_BRg_BZ!0~ z&QQ3Kx7G4r?FqCgSGb9nlmku7HVi0=UWdj$_36#FPgSqV9{#A+P{6qgubQiz)M^=B7h z`kxp;zUbuBk?BG9uKvYWKGlLl+zejFx&$=VeU)0H-l*GT^u`<{#K3S>caV`Cx?~Io z$}K0W^{u}f)p6@cCF2*Xm;K7n#IAfL7R}@;WnEX^me!{yn#uW&PtwcgwZ}P^G;^0g z=?BN)wVGbvgW+|$(I<84XOdrU8Fk|>)0g<1AWV-8JkW&6f}To6x`{818kYiTUt7;K z;?9UNjg>kE!_vhcf^Tr)j(VAdU5hRE9B07e*|aTYoT>OKY4_jy6;<<64%UNyGIdS9 z|5J?J9Vv7%MEK)1r^YTRWUp!mM#HG86)!dXWNq~rE3X?pGR)L3-~0AH9mxFMGQ1&j zQ-0wS1aIRmq#e++B0$tublqOCS8 z_AlSZq}TfBl%^2%*qJ{vC7uzvjJ4Y4STFw{QG3od+H5%kbY6{eE6ZzXo4FI*<0@&OwM^e zS9j`WO4;mC2vmV_W-D#VZfp((Zuk>Wn! z=w@0oW1AC^K~!GM%ho9w=;cc%yr+qf<8x(xT+dI{>!a{v3vJWr7(8}Z$2vu@Fcmh9 zjD@yA+fMATA_h&CaqETCC-tdR*a0+l$cBc** zoK)5!4*&`3c>RnI>K^iI8UcpNr5mPSIcF{hlPTF2dub^j<*zh^=`d7v8Md3&1is-E z|8s&y?5wkc2Axl8+bVh(8-*>l_@4A?VEQYHp7GY(je^YF^!GyRialto!I z;WbBfTg`@9$gy6J`qC z^T>fL^kb`>%#3ZEUzf#9e6ptA_|nfq)>_cJs?l=9jTced0){;sFjSoqtR*PQjMDse z54xnFSPN`iU)mz69BEt9fKXno9JBQ~_SkJ&)yyX9*1$)TFbhF>ARA;A5DW znO(=L9L(z6J|SB6%rHT?@+N%i5jmC~ciSr8(1rkjzHO_53FXp)i!mejOsRa4%xx3F zrTziaYqyxSg8kyG9on`ol5{5*UDXQ4-fy0f-*YL?N_aVeN+ni4NDsy&lUh;`EP>HU zxuE3oM2^i+33Ug8Jg;~my0pFGkKE6$9JTYJr<8Op^&8yWr1PS!c4+&@hq0O~Q?*uO z4dsW8x1g_4mqqJXCkmTdKU-_2h_@V8auK?ctfKaU?tE;=agz&iCzOK&mcgTQ*p$T$ zF7&R+&bg(*(ICb<6SO&POPU*MU0Qx6)|$!wB>7Tc9}_E8YGiEmTHBP)_lWX-0lra| z>zg_j98L+A&EsfiD;{j~>r2D!WaKIaJ`91v6{KWl8ncxBEU;l=fv;qii>oWD%Awc5z@PvCWTBe&r-N%fFp`*H zd<#(Je}~(;25tu(UN?kkeILCjT~h$~J$J*Ny4Eq@Z^0c)ODg43H#?PzzONdYXTW&D zuSA?Fle?RF zzKknXw?P^p1EDayi} zQGPRbDRjd4rw-sql?{dO%AN@+Sz# z=DHMw>ZcRfF7Yf+%Tod!sbU~JD5~d}_ZkG$;qYWg>9LAv-M(Isw`!?p!!5d?X=wBjf;Hi8%b zTn-A3y>yl;K4qGz*>%{SUC#Ty*{OPMOVO>V91nIGjz9E4Ul6hz^34ulO&#v4rR9;_ zv!f=&hYdZ0n3kUy#dfK;4Im`x)z5~7L7A~zziA$Q+-9EP%{01RR0<_N?@zlO7EyXC zEpa9Q=BlFznu%xFJfzQD7plV#s#eH5n_I?Ll};RU*?Mb^-t^o3yvkk~K$)FZ9ELn>GEt&>Gk#l;y6&&cUtq$0e| zWWZD1uy0=Hj;fm4r4nvdKz{**l3A@wvIc6GiZLwgN5K6-qGx*o724){e2KXz0H*WE zpMXy+xUgY$EiFscjLZ=^?H;OUu&MQF+tK1ds|J~gDubC`klR$jVi5~Xd@YYqbFMNs zM$5mCR_wOhW$A;CzlqfzdXCv?;tZ^fgwS(Gqa!1^;m_2up5Sgg-WHn3OSx>}IbD#2 zmNJ-kKi!||$BiHe$-~(}5^SPpoWISp0V*iy4jwre-d9JOX7DmQYquO76Eh4N_2Jgb zr3YPip;?1$I#AP38@w2%Us<>GPXCxjo5Q|-K{cM;B29XwB1NpO{~~bLAw+NAO}u3p zUY5hT|N2}CtqU97Xh0kE&(5JxiFY@0qT;gg0)7y-R1STdwtc`#PTo^rA^$Fbfj1EH z*&+AWJvtsZm;Zv>B)-cTUba%dJ@6yR-!XvI{N#vQ!eQH` zvTU1+-wBNR4?ZqEZ=w6HR3ABpH|Q#mTWmA{C$sNsp1-(x@}}V&ZAX#kchE(Sf&+iu zOO9WWe=B}BfQjt@5pr7AW;;A8TBtR^!Z}kW*x%wPULqg>u$o;p zIil}XwY0|gDw^|*tpXK2TL>uyLz)8F>)cHj$>p7Q_kUyXy+Umkmzf1ezP`C!d~*2R ztgQw35rrb@K|4B*gv2hnUROQ61gpSBCX81nZPQv+?=!J@;8u!+-7#}dY~yk^D%sdd z;%)?WMn6|Ze-W^?_@pwi3{9tjOx3s2@e*g#9ytv(-0faius^|SLx&ertq~uuqFQ7R z_Wl-o`UJ;<@^KwbTKyT|O067OnWd(ecg|nx7z?D53l5KbUP=i}=Yn{B8FzzKK^_uIs;XNCD97wC>V8i8MAkbXUXk*)RMyU=QG6vrhNX_LFE#lGM0umB3- zjJ~Xl0&~wsQvm!sahL_=Adqp}@YSPM_?#_JDX$g}@QQmP>Js$XdBCw6+k``)$Z#28 zo)TH_PGmUuXFC|4@SE3ZxS|oUK0^9;i&Qr$Y{o=1^*Il_l?^N`*mjrYj;iH+#m9Hp zFWRI4Ih838*XOB{;l6%x``PvL*J z9baT0_iLNNMvR*Nu9~_!@G}qA9J|Zvf-MF`Kf(82nX*a8#B{R(25!P};jplvU?pSb zxTwwp!`mtRXBz^=7N)KO+cEa%do+W1zym?SF>2Y(GXen1sn(%^21fv>J~#;9vJcvr zA_J9nut1r~!sngm^aiS!TH*8Di~NY~&f@P{<;8y&=*vuVkh z10PF5m!z(>yU}rEc6Q$M9%wcN{hvKMKY#sF@9DtSz`9FU!{cSQhdt}o+vTAhzOE-b z?y;PW$EE#$PVZl_Z(p2voJ4HO3of~sMrPpRgtYVz53aP^eNhDNDbas%9%$Lb-_?`e zdoXPNQeBkdaxQVXPw}(T`)AIcy$W)4f6Wu+BCEN;Dnasdj`y{7UYz%HJtvi?Ej@M0 zYwOmnM+IzlEL;AFU%BCC2CzXA85z0j$D{7%e{&{HdNlVK(6-m#p8!uI`F=CO_~ay2 z?Po&MB8N4lOzJYA6cFWf4OB<7qU%7jC zZpGbcO?wmD5`VC=Ulj2xE8A98UH$dj?flQ6Ej|trz{Ry!feELvu>AeZt_2F$7?f`P zU2}J`-p(gNcZ*KzE{>c3>49Qg^S4WvFHfB}@0>btyo@2xy8K;?kNMi=!5=1^I3{9~ zb8k;%`pfyi5lIH0SuQK;kIUHH0d5l713cS?;ed{aQH8D{ literal 0 HcmV?d00001 diff --git a/examples/asyncio-python-embed.py b/examples/asyncio-python-embed.py new file mode 100755 index 0000000..05f52f1 --- /dev/null +++ b/examples/asyncio-python-embed.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python +""" +(Python >3.3) + +This is an example of how we can embed a Python REPL into an asyncio +application. In this example, we have one coroutine that runs in the +background, prints some output and alters a global state. The REPL, which runs +inside another coroutine can access and change this global state, interacting +with the running asyncio application. +The ``patch_stdout`` option makes sure that when another coroutine is writing +to stdout, it won't break the input line, but instead writes nicely above the +prompt. +""" +import asyncio + +from ptpython.repl import embed + +loop = asyncio.get_event_loop() +counter = [0] + + +async def print_counter(): + """ + Coroutine that prints counters and saves it in a global variable. + """ + while True: + print("Counter: %i" % counter[0]) + counter[0] += 1 + await asyncio.sleep(3) + + +async def interactive_shell(): + """ + Coroutine that starts a Python REPL from which we can access the global + counter variable. + """ + print( + 'You should be able to read and update the "counter[0]" variable from this shell.' + ) + try: + await embed(globals=globals(), return_asyncio_coroutine=True, patch_stdout=True) + except EOFError: + # Stop the loop when quitting the repl. (Ctrl-D press.) + loop.stop() + + +def main(): + asyncio.ensure_future(print_counter()) + asyncio.ensure_future(interactive_shell()) + + loop.run_forever() + loop.close() + + +if __name__ == "__main__": + main() diff --git a/examples/asyncio-ssh-python-embed.py b/examples/asyncio-ssh-python-embed.py new file mode 100755 index 0000000..86b5607 --- /dev/null +++ b/examples/asyncio-ssh-python-embed.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python +""" +Example of running the Python REPL through an SSH connection in an asyncio process. +This requires Python 3, asyncio and asyncssh. + +Run this example and then SSH to localhost, port 8222. +""" +import asyncio +import logging + +import asyncssh + +from ptpython.contrib.asyncssh_repl import ReplSSHServerSession + +logging.basicConfig() +logging.getLogger().setLevel(logging.INFO) + + +class MySSHServer(asyncssh.SSHServer): + """ + Server without authentication, running `ReplSSHServerSession`. + """ + + def __init__(self, get_namespace): + self.get_namespace = get_namespace + + def begin_auth(self, username): + # No authentication. + return False + + def session_requested(self): + return ReplSSHServerSession(self.get_namespace) + + +def main(port=8222): + """ + Example that starts the REPL through an SSH server. + """ + loop = asyncio.get_event_loop() + + # Namespace exposed in the REPL. + environ = {"hello": "world"} + + # Start SSH server. + def create_server(): + return MySSHServer(lambda: environ) + + print("Listening on :%i" % port) + print('To connect, do "ssh localhost -p %i"' % port) + + loop.run_until_complete( + asyncssh.create_server( + create_server, "", port, server_host_keys=["/etc/ssh/ssh_host_dsa_key"] + ) + ) + + # Run eventloop. + loop.run_forever() + + +if __name__ == "__main__": + main() diff --git a/examples/ptpython_config/config.py b/examples/ptpython_config/config.py new file mode 100644 index 0000000..8532f93 --- /dev/null +++ b/examples/ptpython_config/config.py @@ -0,0 +1,198 @@ +""" +Configuration example for ``ptpython``. + +Copy this file to $XDG_CONFIG_HOME/ptpython/config.py +On Linux, this is: ~/.config/ptpython/config.py +""" +from prompt_toolkit.filters import ViInsertMode +from prompt_toolkit.key_binding.key_processor import KeyPress +from prompt_toolkit.keys import Keys +from prompt_toolkit.styles import Style + +from ptpython.layout import CompletionVisualisation + +__all__ = ["configure"] + + +def configure(repl): + """ + Configuration method. This is called during the start-up of ptpython. + + :param repl: `PythonRepl` instance. + """ + # Show function signature (bool). + repl.show_signature = True + + # Show docstring (bool). + repl.show_docstring = False + + # Show the "[Meta+Enter] Execute" message when pressing [Enter] only + # inserts a newline instead of executing the code. + repl.show_meta_enter_message = True + + # Show completions. (NONE, POP_UP, MULTI_COLUMN or TOOLBAR) + repl.completion_visualisation = CompletionVisualisation.POP_UP + + # When CompletionVisualisation.POP_UP has been chosen, use this + # scroll_offset in the completion menu. + repl.completion_menu_scroll_offset = 0 + + # Show line numbers (when the input contains multiple lines.) + repl.show_line_numbers = False + + # Show status bar. + repl.show_status_bar = True + + # When the sidebar is visible, also show the help text. + repl.show_sidebar_help = True + + # Swap light/dark colors on or off + repl.swap_light_and_dark = False + + # Highlight matching parethesis. + repl.highlight_matching_parenthesis = True + + # Line wrapping. (Instead of horizontal scrolling.) + repl.wrap_lines = True + + # Mouse support. + repl.enable_mouse_support = True + + # Complete while typing. (Don't require tab before the + # completion menu is shown.) + repl.complete_while_typing = True + + # Fuzzy and dictionary completion. + repl.enable_fuzzy_completion = False + repl.enable_dictionary_completion = False + + # Vi mode. + repl.vi_mode = False + + # Paste mode. (When True, don't insert whitespace after new line.) + repl.paste_mode = False + + # Use the classic prompt. (Display '>>>' instead of 'In [1]'.) + repl.prompt_style = "classic" # 'classic' or 'ipython' + + # Don't insert a blank line after the output. + repl.insert_blank_line_after_output = False + + # History Search. + # When True, going back in history will filter the history on the records + # starting with the current input. (Like readline.) + # Note: When enable, please disable the `complete_while_typing` option. + # otherwise, when there is a completion available, the arrows will + # browse through the available completions instead of the history. + repl.enable_history_search = False + + # Enable auto suggestions. (Pressing right arrow will complete the input, + # based on the history.) + repl.enable_auto_suggest = False + + # Enable open-in-editor. Pressing C-x C-e in emacs mode or 'v' in + # Vi navigation mode will open the input in the current editor. + repl.enable_open_in_editor = True + + # Enable system prompt. Pressing meta-! will display the system prompt. + # Also enables Control-Z suspend. + repl.enable_system_bindings = True + + # Ask for confirmation on exit. + repl.confirm_exit = True + + # Enable input validation. (Don't try to execute when the input contains + # syntax errors.) + repl.enable_input_validation = True + + # Use this colorscheme for the code. + repl.use_code_colorscheme("default") + # repl.use_code_colorscheme("pastie") + + # Set color depth (keep in mind that not all terminals support true color). + + # repl.color_depth = "DEPTH_1_BIT" # Monochrome. + # repl.color_depth = "DEPTH_4_BIT" # ANSI colors only. + repl.color_depth = "DEPTH_8_BIT" # The default, 256 colors. + # repl.color_depth = "DEPTH_24_BIT" # True color. + + # Min/max brightness + repl.min_brightness = 0.0 # Increase for dark terminal backgrounds. + repl.max_brightness = 1.0 # Decrease for light terminal backgrounds. + + # Syntax. + repl.enable_syntax_highlighting = True + + # Get into Vi navigation mode at startup + repl.vi_start_in_navigation_mode = False + + # Preserve last used Vi input mode between main loop iterations + repl.vi_keep_last_used_mode = False + + # Install custom colorscheme named 'my-colorscheme' and use it. + """ + repl.install_ui_colorscheme("my-colorscheme", Style.from_dict(_custom_ui_colorscheme)) + repl.use_ui_colorscheme("my-colorscheme") + """ + + # Add custom key binding for PDB. + """ + @repl.add_key_binding("c-b") + def _(event): + " Pressing Control-B will insert "pdb.set_trace()" " + event.cli.current_buffer.insert_text("\nimport pdb; pdb.set_trace()\n") + """ + + # Typing ControlE twice should also execute the current command. + # (Alternative for Meta-Enter.) + """ + @repl.add_key_binding("c-e", "c-e") + def _(event): + event.current_buffer.validate_and_handle() + """ + + # Typing 'jj' in Vi Insert mode, should send escape. (Go back to navigation + # mode.) + """ + @repl.add_key_binding("j", "j", filter=ViInsertMode()) + def _(event): + " Map 'jj' to Escape. " + event.cli.key_processor.feed(KeyPress("escape")) + """ + + # Custom key binding for some simple autocorrection while typing. + """ + corrections = { + "impotr": "import", + "pritn": "print", + } + + @repl.add_key_binding(" ") + def _(event): + " When a space is pressed. Check & correct word before cursor. " + b = event.cli.current_buffer + w = b.document.get_word_before_cursor() + + if w is not None: + if w in corrections: + b.delete_before_cursor(count=len(w)) + b.insert_text(corrections[w]) + + b.insert_text(" ") + """ + + # Add a custom title to the status bar. This is useful when ptpython is + # embedded in other applications. + """ + repl.title = "My custom prompt." + """ + + +# Custom colorscheme for the UI. See `ptpython/layout.py` and +# `ptpython/style.py` for all possible tokens. +_custom_ui_colorscheme = { + # Blue prompt. + "prompt": "bg:#eeeeff #000000 bold", + # Make the status toolbar red. + "status-toolbar": "bg:#ff0000 #000000", +} diff --git a/examples/python-embed-with-custom-prompt.py b/examples/python-embed-with-custom-prompt.py new file mode 100755 index 0000000..968aedc --- /dev/null +++ b/examples/python-embed-with-custom-prompt.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python +""" +Example of embedding a Python REPL, and setting a custom prompt. +""" +from prompt_toolkit.formatted_text import HTML + +from ptpython.prompt_style import PromptStyle +from ptpython.repl import embed + + +def configure(repl): + # Probably, the best is to add a new PromptStyle to `all_prompt_styles` and + # activate it. This way, the other styles are still selectable from the + # menu. + class CustomPrompt(PromptStyle): + def in_prompt(self): + return HTML("Input[%s]: ") % ( + repl.current_statement_index, + ) + + def in2_prompt(self, width): + return "...: ".rjust(width) + + def out_prompt(self): + return HTML("Result[%s]: ") % ( + repl.current_statement_index, + ) + + repl.all_prompt_styles["custom"] = CustomPrompt() + repl.prompt_style = "custom" + + +def main(): + embed(globals(), locals(), configure=configure) + + +if __name__ == "__main__": + main() diff --git a/examples/python-embed.py b/examples/python-embed.py new file mode 100755 index 0000000..ac2cd06 --- /dev/null +++ b/examples/python-embed.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python +""" +""" +from ptpython.repl import embed + + +def main(): + embed(globals(), locals(), vi_mode=False) + + +if __name__ == "__main__": + main() diff --git a/examples/python-input.py b/examples/python-input.py new file mode 100755 index 0000000..567c2ee --- /dev/null +++ b/examples/python-input.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python +""" +""" +from ptpython.python_input import PythonInput + + +def main(): + prompt = PythonInput() + + text = prompt.app.run() + print("You said: " + text) + + +if __name__ == "__main__": + main() diff --git a/examples/ssh-and-telnet-embed.py b/examples/ssh-and-telnet-embed.py new file mode 100755 index 0000000..378784c --- /dev/null +++ b/examples/ssh-and-telnet-embed.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python +""" +Serve a ptpython console using both telnet and ssh. + +Thanks to Vincent Michel for this! +https://gist.github.com/vxgmichel/7685685b3e5ead04ada4a3ba75a48eef +""" + +import asyncio +import pathlib + +import asyncssh +from prompt_toolkit import print_formatted_text +from prompt_toolkit.contrib.ssh.server import PromptToolkitSSHServer +from prompt_toolkit.contrib.telnet.server import TelnetServer + +from ptpython.repl import embed + + +def ensure_key(filename="ssh_host_key"): + path = pathlib.Path(filename) + if not path.exists(): + rsa_key = asyncssh.generate_private_key("ssh-rsa") + path.write_bytes(rsa_key.export_private_key()) + return str(path) + + +async def interact(connection=None): + global_dict = {**globals(), "print": print_formatted_text} + await embed(return_asyncio_coroutine=True, globals=global_dict) + + +async def main(ssh_port=8022, telnet_port=8023): + ssh_server = PromptToolkitSSHServer(interact=interact) + await asyncssh.create_server( + lambda: ssh_server, "", ssh_port, server_host_keys=[ensure_key()] + ) + print(f"Running ssh server on port {ssh_port}...") + + telnet_server = TelnetServer(interact=interact, port=telnet_port) + telnet_server.start() + print(f"Running telnet server on port {telnet_port}...") + + while True: + await asyncio.sleep(60) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/test-cases/ptpython-in-other-thread.py b/examples/test-cases/ptpython-in-other-thread.py new file mode 100644 index 0000000..7c78846 --- /dev/null +++ b/examples/test-cases/ptpython-in-other-thread.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python +""" +Example of running ptpython in another thread. + +(For testing whether it's working fine if it's not embedded in the main +thread.) +""" +import threading + +from ptpython.repl import embed + + +def in_thread(): + embed(globals(), locals(), vi_mode=False) + + +def main(): + th = threading.Thread(target=in_thread) + th.start() + th.join() + + +if __name__ == "__main__": + main() diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 0000000..5a7ef2e --- /dev/null +++ b/mypy.ini @@ -0,0 +1,6 @@ +[mypy] +ignore_missing_imports = True +no_implicit_optional = True +platform = win32 +strict_equality = True +strict_optional = True diff --git a/ptpython/__init__.py b/ptpython/__init__.py new file mode 100644 index 0000000..4908eba --- /dev/null +++ b/ptpython/__init__.py @@ -0,0 +1,3 @@ +from .repl import embed + +__all__ = ["embed"] diff --git a/ptpython/__main__.py b/ptpython/__main__.py new file mode 100644 index 0000000..83340a7 --- /dev/null +++ b/ptpython/__main__.py @@ -0,0 +1,6 @@ +""" +Make `python -m ptpython` an alias for running `./ptpython`. +""" +from .entry_points.run_ptpython import run + +run() diff --git a/ptpython/completer.py b/ptpython/completer.py new file mode 100644 index 0000000..9f7e10b --- /dev/null +++ b/ptpython/completer.py @@ -0,0 +1,650 @@ +import ast +import collections.abc as collections_abc +import inspect +import keyword +import re +from enum import Enum +from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, List, Optional + +from prompt_toolkit.completion import ( + CompleteEvent, + Completer, + Completion, + PathCompleter, +) +from prompt_toolkit.contrib.completers.system import SystemCompleter +from prompt_toolkit.contrib.regular_languages.compiler import compile as compile_grammar +from prompt_toolkit.contrib.regular_languages.completion import GrammarCompleter +from prompt_toolkit.document import Document +from prompt_toolkit.formatted_text import fragment_list_to_text, to_formatted_text + +from ptpython.utils import get_jedi_script_from_document + +if TYPE_CHECKING: + from prompt_toolkit.contrib.regular_languages.compiler import _CompiledGrammar + +__all__ = ["PythonCompleter", "CompletePrivateAttributes", "HidePrivateCompleter"] + + +class CompletePrivateAttributes(Enum): + """ + Should we display private attributes in the completion pop-up? + """ + + NEVER = "NEVER" + IF_NO_PUBLIC = "IF_NO_PUBLIC" + ALWAYS = "ALWAYS" + + +class PythonCompleter(Completer): + """ + Completer for Python code. + """ + + def __init__( + self, + get_globals: Callable[[], dict], + get_locals: Callable[[], dict], + enable_dictionary_completion: Callable[[], bool], + ) -> None: + super().__init__() + + self.get_globals = get_globals + self.get_locals = get_locals + self.enable_dictionary_completion = enable_dictionary_completion + + self._system_completer = SystemCompleter() + self._jedi_completer = JediCompleter(get_globals, get_locals) + self._dictionary_completer = DictionaryCompleter(get_globals, get_locals) + + self._path_completer_cache: Optional[GrammarCompleter] = None + self._path_completer_grammar_cache: Optional["_CompiledGrammar"] = None + + @property + def _path_completer(self) -> GrammarCompleter: + if self._path_completer_cache is None: + self._path_completer_cache = GrammarCompleter( + self._path_completer_grammar, + { + "var1": PathCompleter(expanduser=True), + "var2": PathCompleter(expanduser=True), + }, + ) + return self._path_completer_cache + + @property + def _path_completer_grammar(self) -> "_CompiledGrammar": + """ + Return the grammar for matching paths inside strings inside Python + code. + """ + # We make this lazy, because it delays startup time a little bit. + # This way, the grammar is build during the first completion. + if self._path_completer_grammar_cache is None: + self._path_completer_grammar_cache = self._create_path_completer_grammar() + return self._path_completer_grammar_cache + + def _create_path_completer_grammar(self) -> "_CompiledGrammar": + def unwrapper(text: str) -> str: + return re.sub(r"\\(.)", r"\1", text) + + def single_quoted_wrapper(text: str) -> str: + return text.replace("\\", "\\\\").replace("'", "\\'") + + def double_quoted_wrapper(text: str) -> str: + return text.replace("\\", "\\\\").replace('"', '\\"') + + grammar = r""" + # Text before the current string. + ( + [^'"#] | # Not quoted characters. + ''' ([^'\\]|'(?!')|''(?!')|\\.])* ''' | # Inside single quoted triple strings + "" " ([^"\\]|"(?!")|""(?!^)|\\.])* "" " | # Inside double quoted triple strings + + \#[^\n]*(\n|$) | # Comment. + "(?!"") ([^"\\]|\\.)*" | # Inside double quoted strings. + '(?!'') ([^'\\]|\\.)*' # Inside single quoted strings. + + # Warning: The negative lookahead in the above two + # statements is important. If we drop that, + # then the regex will try to interpret every + # triple quoted string also as a single quoted + # string, making this exponentially expensive to + # execute! + )* + # The current string that we're completing. + ( + ' (?P([^\n'\\]|\\.)*) | # Inside a single quoted string. + " (?P([^\n"\\]|\\.)*) # Inside a double quoted string. + ) + """ + + return compile_grammar( + grammar, + escape_funcs={"var1": single_quoted_wrapper, "var2": double_quoted_wrapper}, + unescape_funcs={"var1": unwrapper, "var2": unwrapper}, + ) + + def _complete_path_while_typing(self, document: Document) -> bool: + char_before_cursor = document.char_before_cursor + return bool( + document.text + and (char_before_cursor.isalnum() or char_before_cursor in "/.~") + ) + + def _complete_python_while_typing(self, document: Document) -> bool: + """ + When `complete_while_typing` is set, only return completions when this + returns `True`. + """ + text = document.text_before_cursor # .rstrip() + char_before_cursor = text[-1:] + return bool( + text and (char_before_cursor.isalnum() or char_before_cursor in "_.([,") + ) + + def get_completions( + self, document: Document, complete_event: CompleteEvent + ) -> Iterable[Completion]: + """ + Get Python completions. + """ + # If the input starts with an exclamation mark. Use the system completer. + if document.text.lstrip().startswith("!"): + yield from self._system_completer.get_completions( + Document( + text=document.text[1:], cursor_position=document.cursor_position - 1 + ), + complete_event, + ) + return + + # Do dictionary key completions. + if complete_event.completion_requested or self._complete_python_while_typing( + document + ): + if self.enable_dictionary_completion(): + has_dict_completions = False + for c in self._dictionary_completer.get_completions( + document, complete_event + ): + if c.text not in "[.": + # If we get the [ or . completion, still include the other + # completions. + has_dict_completions = True + yield c + if has_dict_completions: + return + + # Do Path completions (if there were no dictionary completions). + if complete_event.completion_requested or self._complete_path_while_typing( + document + ): + yield from self._path_completer.get_completions(document, complete_event) + + # Do Jedi completions. + if complete_event.completion_requested or self._complete_python_while_typing( + document + ): + # If we are inside a string, Don't do Jedi completion. + if not self._path_completer_grammar.match(document.text_before_cursor): + + # Do Jedi Python completions. + yield from self._jedi_completer.get_completions( + document, complete_event + ) + + +class JediCompleter(Completer): + """ + Autocompleter that uses the Jedi library. + """ + + def __init__(self, get_globals, get_locals) -> None: + super().__init__() + + self.get_globals = get_globals + self.get_locals = get_locals + + def get_completions( + self, document: Document, complete_event: CompleteEvent + ) -> Iterable[Completion]: + script = get_jedi_script_from_document( + document, self.get_locals(), self.get_globals() + ) + + if script: + try: + jedi_completions = script.complete( + column=document.cursor_position_col, + line=document.cursor_position_row + 1, + ) + except TypeError: + # Issue #9: bad syntax causes completions() to fail in jedi. + # https://github.com/jonathanslenders/python-prompt-toolkit/issues/9 + pass + except UnicodeDecodeError: + # Issue #43: UnicodeDecodeError on OpenBSD + # https://github.com/jonathanslenders/python-prompt-toolkit/issues/43 + pass + except AttributeError: + # Jedi issue #513: https://github.com/davidhalter/jedi/issues/513 + pass + except ValueError: + # Jedi issue: "ValueError: invalid \x escape" + pass + except KeyError: + # Jedi issue: "KeyError: u'a_lambda'." + # https://github.com/jonathanslenders/ptpython/issues/89 + pass + except IOError: + # Jedi issue: "IOError: No such file or directory." + # https://github.com/jonathanslenders/ptpython/issues/71 + pass + except AssertionError: + # In jedi.parser.__init__.py: 227, in remove_last_newline, + # the assertion "newline.value.endswith('\n')" can fail. + pass + except SystemError: + # In jedi.api.helpers.py: 144, in get_stack_at_position + # raise SystemError("This really shouldn't happen. There's a bug in Jedi.") + pass + except NotImplementedError: + # See: https://github.com/jonathanslenders/ptpython/issues/223 + pass + except Exception: + # Supress all other Jedi exceptions. + pass + else: + # Move function parameters to the top. + jedi_completions = sorted( + jedi_completions, + key=lambda jc: ( + # Params first. + jc.type != "param", + # Private at the end. + jc.name.startswith("_"), + # Then sort by name. + jc.name_with_symbols.lower(), + ), + ) + + for jc in jedi_completions: + if jc.type == "function": + suffix = "()" + else: + suffix = "" + + if jc.type == "param": + suffix = "..." + + yield Completion( + jc.name_with_symbols, + len(jc.complete) - len(jc.name_with_symbols), + display=jc.name_with_symbols + suffix, + display_meta=jc.type, + style=_get_style_for_jedi_completion(jc), + ) + + +class DictionaryCompleter(Completer): + """ + Experimental completer for Python dictionary keys. + + Warning: This does an `eval` and `repr` on some Python expressions before + the cursor, which is potentially dangerous. It doesn't match on + function calls, so it only triggers attribute access. + """ + + def __init__(self, get_globals, get_locals): + super().__init__() + + self.get_globals = get_globals + self.get_locals = get_locals + + # Pattern for expressions that are "safe" to eval for auto-completion. + # These are expressions that contain only attribute and index lookups. + varname = r"[a-zA-Z_][a-zA-Z0-9_]*" + + expression = rf""" + # Any expression safe enough to eval while typing. + # No operators, except dot, and only other dict lookups. + # Technically, this can be unsafe of course, if bad code runs + # in `__getattr__` or ``__getitem__``. + ( + # Variable name + {varname} + + \s* + + (?: + # Attribute access. + \s* \. \s* {varname} \s* + + | + + # Item lookup. + # (We match the square brackets. The key can be anything. + # We don't care about matching quotes here in the regex. + # Nested square brackets are not supported.) + \s* \[ [^\[\]]+ \] \s* + )* + ) + """ + + # Pattern for recognizing for-loops, so that we can provide + # autocompletion on the iterator of the for-loop. (According to the + # first item of the collection we're iterating over.) + self.for_loop_pattern = re.compile( + rf""" + for \s+ ([a-zA-Z0-9_]+) \s+ in \s+ {expression} \s* : + """, + re.VERBOSE, + ) + + # Pattern for matching a simple expression (for completing [ or . + # operators). + self.expression_pattern = re.compile( + rf""" + {expression} + $ + """, + re.VERBOSE, + ) + + # Pattern for matching item lookups. + self.item_lookup_pattern = re.compile( + rf""" + {expression} + + # Dict loopup to complete (square bracket open + start of + # string). + \[ + \s* ([^\[\]]*)$ + """, + re.VERBOSE, + ) + + # Pattern for matching attribute lookups. + self.attribute_lookup_pattern = re.compile( + rf""" + {expression} + + # Attribute loopup to complete (dot + varname). + \. + \s* ([a-zA-Z0-9_]*)$ + """, + re.VERBOSE, + ) + + def _lookup(self, expression: str, temp_locals: Dict[str, Any]) -> object: + """ + Do lookup of `object_var` in the context. + `temp_locals` is a dictionary, used for the locals. + """ + try: + return eval(expression.strip(), self.get_globals(), temp_locals) + except BaseException: + return None # Many exception, like NameError can be thrown here. + + def get_completions( + self, document: Document, complete_event: CompleteEvent + ) -> Iterable[Completion]: + + # First, find all for-loops, and assign the first item of the + # collections they're iterating to the iterator variable, so that we + # can provide code completion on the iterators. + temp_locals = self.get_locals().copy() + + for match in self.for_loop_pattern.finditer(document.text_before_cursor): + varname, expression = match.groups() + expression_val = self._lookup(expression, temp_locals) + + # We do this only for lists and tuples. Calling `next()` on any + # collection would create undesired side effects. + if isinstance(expression_val, (list, tuple)) and expression_val: + temp_locals[varname] = expression_val[0] + + # Get all completions. + yield from self._get_expression_completions( + document, complete_event, temp_locals + ) + yield from self._get_item_lookup_completions( + document, complete_event, temp_locals + ) + yield from self._get_attribute_completions( + document, complete_event, temp_locals + ) + + def _do_repr(self, obj: object) -> str: + try: + return str(repr(obj)) + except BaseException: + raise ReprFailedError + + def eval_expression(self, document: Document, locals: Dict[str, Any]) -> object: + """ + Evaluate + """ + match = self.expression_pattern.search(document.text_before_cursor) + if match is not None: + object_var = match.groups()[0] + return self._lookup(object_var, locals) + + return None + + def _get_expression_completions( + self, + document: Document, + complete_event: CompleteEvent, + temp_locals: Dict[str, Any], + ) -> Iterable[Completion]: + """ + Complete the [ or . operator after an object. + """ + result = self.eval_expression(document, temp_locals) + + if result is not None: + + if isinstance( + result, + (list, tuple, dict, collections_abc.Mapping, collections_abc.Sequence), + ): + yield Completion("[", 0) + + else: + # Note: Don't call `if result` here. That can fail for types + # that have custom truthness checks. + yield Completion(".", 0) + + def _get_item_lookup_completions( + self, + document: Document, + complete_event: CompleteEvent, + temp_locals: Dict[str, Any], + ) -> Iterable[Completion]: + """ + Complete dictionary keys. + """ + + def abbr_meta(text: str) -> str: + " Abbreviate meta text, make sure it fits on one line. " + # Take first line, if multiple lines. + if len(text) > 20: + text = text[:20] + "..." + if "\n" in text: + text = text.split("\n", 1)[0] + "..." + return text + + match = self.item_lookup_pattern.search(document.text_before_cursor) + if match is not None: + object_var, key = match.groups() + + # Do lookup of `object_var` in the context. + result = self._lookup(object_var, temp_locals) + + # If this object is a dictionary, complete the keys. + if isinstance(result, (dict, collections_abc.Mapping)): + # Try to evaluate the key. + key_obj = key + for k in [key, key + '"', key + "'"]: + try: + key_obj = ast.literal_eval(k) + except (SyntaxError, ValueError): + continue + else: + break + + for k in result: + if str(k).startswith(str(key_obj)): + try: + k_repr = self._do_repr(k) + yield Completion( + k_repr + "]", + -len(key), + display=f"[{k_repr}]", + display_meta=abbr_meta(self._do_repr(result[k])), + ) + except ReprFailedError: + pass + + # Complete list/tuple index keys. + elif isinstance(result, (list, tuple, collections_abc.Sequence)): + if not key or key.isdigit(): + for k in range(min(len(result), 1000)): + if str(k).startswith(key): + try: + k_repr = self._do_repr(k) + yield Completion( + k_repr + "]", + -len(key), + display=f"[{k_repr}]", + display_meta=abbr_meta(self._do_repr(result[k])), + ) + except ReprFailedError: + pass + + def _get_attribute_completions( + self, + document: Document, + complete_event: CompleteEvent, + temp_locals: Dict[str, Any], + ) -> Iterable[Completion]: + """ + Complete attribute names. + """ + match = self.attribute_lookup_pattern.search(document.text_before_cursor) + if match is not None: + object_var, attr_name = match.groups() + + # Do lookup of `object_var` in the context. + result = self._lookup(object_var, temp_locals) + + names = self._sort_attribute_names(dir(result)) + + def get_suffix(name: str) -> str: + try: + obj = getattr(result, name, None) + if inspect.isfunction(obj): + return "()" + + if isinstance(obj, dict): + return "{}" + if isinstance(obj, (list, tuple)): + return "[]" + except: + pass + return "" + + for name in names: + if name.startswith(attr_name): + suffix = get_suffix(name) + yield Completion(name, -len(attr_name), display=name + suffix) + + def _sort_attribute_names(self, names: List[str]) -> List[str]: + """ + Sort attribute names alphabetically, but move the double underscore and + underscore names to the end. + """ + + def sort_key(name: str): + if name.startswith("__"): + return (2, name) # Double underscore comes latest. + if name.startswith("_"): + return (1, name) # Single underscore before that. + return (0, name) # Other names first. + + return sorted(names, key=sort_key) + + +class HidePrivateCompleter(Completer): + """ + Wrapper around completer that hides private fields, deponding on whether or + not public fields are shown. + + (The reason this is implemented as a `Completer` wrapper is because this + way it works also with `FuzzyCompleter`.) + """ + + def __init__( + self, + completer: Completer, + complete_private_attributes: Callable[[], CompletePrivateAttributes], + ) -> None: + self.completer = completer + self.complete_private_attributes = complete_private_attributes + + def get_completions( + self, document: Document, complete_event: CompleteEvent + ) -> Iterable[Completion]: + + completions = list(self.completer.get_completions(document, complete_event)) + complete_private_attributes = self.complete_private_attributes() + hide_private = False + + def is_private(completion: Completion) -> bool: + text = fragment_list_to_text(to_formatted_text(completion.display)) + return text.startswith("_") + + if complete_private_attributes == CompletePrivateAttributes.NEVER: + hide_private = True + + elif complete_private_attributes == CompletePrivateAttributes.IF_NO_PUBLIC: + hide_private = any(not is_private(completion) for completion in completions) + + if hide_private: + completions = [ + completion for completion in completions if not is_private(completion) + ] + + return completions + + +class ReprFailedError(Exception): + " Raised when the repr() call in `DictionaryCompleter` fails. " + + +try: + import builtins + + _builtin_names = dir(builtins) +except ImportError: # Python 2. + _builtin_names = [] + + +def _get_style_for_jedi_completion(jedi_completion) -> str: + """ + Return completion style to use for this name. + """ + name = jedi_completion.name_with_symbols + + if jedi_completion.type == "param": + return "class:completion.param" + + if name in _builtin_names: + return "class:completion.builtin" + + if keyword.iskeyword(name): + return "class:completion.keyword" + + return "" diff --git a/ptpython/contrib/__init__.py b/ptpython/contrib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ptpython/contrib/asyncssh_repl.py b/ptpython/contrib/asyncssh_repl.py new file mode 100644 index 0000000..4c36217 --- /dev/null +++ b/ptpython/contrib/asyncssh_repl.py @@ -0,0 +1,119 @@ +""" +Tool for embedding a REPL inside a Python 3 asyncio process. +See ./examples/asyncio-ssh-python-embed.py for a demo. + +Note that the code in this file is Python 3 only. However, we +should make sure not to use Python 3-only syntax, because this +package should be installable in Python 2 as well! +""" +import asyncio +from typing import Any, Optional, TextIO, cast + +import asyncssh +from prompt_toolkit.data_structures import Size +from prompt_toolkit.input import create_pipe_input +from prompt_toolkit.output.vt100 import Vt100_Output + +from ptpython.python_input import _GetNamespace +from ptpython.repl import PythonRepl + +__all__ = ["ReplSSHServerSession"] + + +class ReplSSHServerSession(asyncssh.SSHServerSession): + """ + SSH server session that runs a Python REPL. + + :param get_globals: callable that returns the current globals. + :param get_locals: (optional) callable that returns the current locals. + """ + + def __init__( + self, get_globals: _GetNamespace, get_locals: Optional[_GetNamespace] = None + ) -> None: + self._chan: Any = None + + def _globals() -> dict: + data = get_globals() + data.setdefault("print", self._print) + return data + + # 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_pipe = create_pipe_input() + + # Output object. Don't render to the real stdout, but write everything + # in the SSH channel. + class Stdout: + def write(s, data: str) -> None: + if self._chan is not None: + data = data.replace("\n", "\r\n") + self._chan.write(data) + + def flush(s) -> None: + pass + + self.repl = PythonRepl( + get_globals=_globals, + get_locals=get_locals or _globals, + input=self._input_pipe, + output=Vt100_Output(cast(TextIO, Stdout()), self._get_size), + ) + + # Disable open-in-editor and system prompt. Because it would run and + # display these commands on the server side, rather than in the SSH + # client. + self.repl.enable_open_in_editor = False + self.repl.enable_system_bindings = False + + 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): + """ + Client connected, run repl in coroutine. + """ + self._chan = chan + + # Run REPL interface. + f = asyncio.ensure_future(self.repl.run_async()) + + # Close channel when done. + def done(_) -> None: + chan.close() + self._chan = None + + f.add_done_callback(done) + + def shell_requested(self) -> bool: + return True + + def terminal_size_changed(self, width, height, pixwidth, pixheight): + """ + When the terminal size changes, report back to CLI. + """ + self.repl.app._on_resize() + + def data_received(self, data, datatype): + """ + When data is received, send to inputstream of the CLI and repaint. + """ + self._input_pipe.send(data) + + def _print(self, *data, sep=" ", end="\n", file=None) -> None: + """ + Alternative 'print' function that prints back into the SSH channel. + """ + # Pop keyword-only arguments. (We cannot use the syntax from the + # signature. Otherwise, Python2 will give a syntax error message when + # installing.) + data = sep.join(map(str, data)) + self._chan.write(data + end) diff --git a/ptpython/entry_points/__init__.py b/ptpython/entry_points/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ptpython/entry_points/run_ptipython.py b/ptpython/entry_points/run_ptipython.py new file mode 100644 index 0000000..650633e --- /dev/null +++ b/ptpython/entry_points/run_ptipython.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python +import os +import sys + +from .run_ptpython import create_parser, get_config_and_history_file + + +def run(user_ns=None): + a = create_parser().parse_args() + + config_file, history_file = get_config_and_history_file(a) + + # If IPython is not available, show message and exit here with error status + # code. + try: + import IPython + except ImportError: + print("IPython not found. Please install IPython (pip install ipython).") + sys.exit(1) + else: + from ptpython.ipython import embed + from ptpython.repl import enable_deprecation_warnings, run_config + + # Add the current directory to `sys.path`. + if sys.path[0] != "": + sys.path.insert(0, "") + + # When a file has been given, run that, otherwise start the shell. + if a.args and not a.interactive: + sys.argv = a.args + path = a.args[0] + with open(path, "rb") as f: + code = compile(f.read(), path, "exec") + exec(code, {}) + else: + enable_deprecation_warnings() + + # Create an empty namespace for this interactive shell. (If we don't do + # that, all the variables from this function will become available in + # the IPython shell.) + if user_ns is None: + user_ns = {} + + # Startup path + startup_paths = [] + if "PYTHONSTARTUP" in os.environ: + startup_paths.append(os.environ["PYTHONSTARTUP"]) + + # --interactive + if a.interactive: + startup_paths.append(a.args[0]) + sys.argv = a.args + + # exec scripts from startup paths + for path in startup_paths: + if os.path.exists(path): + with open(path, "rb") as f: + code = compile(f.read(), path, "exec") + exec(code, user_ns, user_ns) + else: + print("File not found: {}\n\n".format(path)) + sys.exit(1) + + # Apply config file + def configure(repl): + if os.path.exists(config_file): + run_config(repl, config_file) + + # Run interactive shell. + embed( + vi_mode=a.vi, + history_filename=history_file, + configure=configure, + user_ns=user_ns, + title="IPython REPL (ptipython)", + ) + + +if __name__ == "__main__": + run() diff --git a/ptpython/entry_points/run_ptpython.py b/ptpython/entry_points/run_ptpython.py new file mode 100644 index 0000000..0b3dbdb --- /dev/null +++ b/ptpython/entry_points/run_ptpython.py @@ -0,0 +1,217 @@ +#!/usr/bin/env python +""" +ptpython: Interactive Python shell. + +positional arguments: + args Script and arguments + +optional arguments: + -h, --help show this help message and exit + --vi Enable Vi key bindings + -i, --interactive Start interactive shell after executing this file. + --light-bg Run on a light background (use dark colors for text). + --dark-bg Run on a dark background (use light colors for text). + --config-file CONFIG_FILE + Location of configuration file. + --history-file HISTORY_FILE + Location of history file. + -V, --version show program's version number and exit + +environment variables: + PTPYTHON_CONFIG_HOME: a configuration directory to use + PYTHONSTARTUP: file executed on interactive startup (no default) +""" +import argparse +import os +import pathlib +import sys +from textwrap import dedent +from typing import Tuple + +import appdirs +from prompt_toolkit.formatted_text import HTML +from prompt_toolkit.shortcuts import print_formatted_text + +from ptpython.repl import embed, enable_deprecation_warnings, run_config + +try: + from importlib import metadata +except ImportError: + import importlib_metadata as metadata # type: ignore + + +__all__ = ["create_parser", "get_config_and_history_file", "run"] + + +class _Parser(argparse.ArgumentParser): + def print_help(self): + super().print_help() + print( + dedent( + """ + environment variables: + PTPYTHON_CONFIG_HOME: a configuration directory to use + PYTHONSTARTUP: file executed on interactive startup (no default) + """, + ).rstrip(), + ) + + +def create_parser() -> _Parser: + parser = _Parser(description="ptpython: Interactive Python shell.") + parser.add_argument("--vi", action="store_true", help="Enable Vi key bindings") + parser.add_argument( + "-i", + "--interactive", + action="store_true", + help="Start interactive shell after executing this file.", + ) + parser.add_argument( + "--light-bg", + action="store_true", + help="Run on a light background (use dark colors for text).", + ), + parser.add_argument( + "--dark-bg", + action="store_true", + help="Run on a dark background (use light colors for text).", + ), + parser.add_argument( + "--config-file", type=str, help="Location of configuration file." + ) + parser.add_argument("--history-file", type=str, help="Location of history file.") + parser.add_argument( + "-V", + "--version", + action="version", + version=metadata.version("ptpython"), # type: ignore + ) + parser.add_argument("args", nargs="*", help="Script and arguments") + return parser + + +def get_config_and_history_file(namespace: argparse.Namespace) -> Tuple[str, str]: + """ + Check which config/history files to use, ensure that the directories for + these files exist, and return the config and history path. + """ + config_dir = os.environ.get( + "PTPYTHON_CONFIG_HOME", + appdirs.user_config_dir("ptpython", "prompt_toolkit"), + ) + data_dir = appdirs.user_data_dir("ptpython", "prompt_toolkit") + + # Create directories. + for d in (config_dir, data_dir): + pathlib.Path(d).mkdir(parents=True, exist_ok=True) + + # Determine config file to be used. + config_file = os.path.join(config_dir, "config.py") + legacy_config_file = os.path.join(os.path.expanduser("~/.ptpython"), "config.py") + + warnings = [] + + # Config file + if namespace.config_file: + # Override config_file. + config_file = os.path.expanduser(namespace.config_file) + + elif os.path.isfile(legacy_config_file): + # Warn about the legacy configuration file. + warnings.append( + HTML( + " ~/.ptpython/config.py is deprecated, move your configuration to %s\n" + ) + % config_file + ) + config_file = legacy_config_file + + # Determine history file to be used. + history_file = os.path.join(data_dir, "history") + legacy_history_file = os.path.join(os.path.expanduser("~/.ptpython"), "history") + + if namespace.history_file: + # Override history_file. + history_file = os.path.expanduser(namespace.history_file) + + elif os.path.isfile(legacy_history_file): + # Warn about the legacy history file. + warnings.append( + HTML( + " ~/.ptpython/history is deprecated, move your history to %s\n" + ) + % history_file + ) + history_file = legacy_history_file + + # Print warnings. + if warnings: + print_formatted_text(HTML("Warning:")) + for w in warnings: + print_formatted_text(w) + + return config_file, history_file + + +def run() -> None: + a = create_parser().parse_args() + + config_file, history_file = get_config_and_history_file(a) + + # Startup path + startup_paths = [] + if "PYTHONSTARTUP" in os.environ: + startup_paths.append(os.environ["PYTHONSTARTUP"]) + + # --interactive + if a.interactive and a.args: + # Note that we shouldn't run PYTHONSTARTUP when -i is given. + startup_paths = [a.args[0]] + sys.argv = a.args + + # Add the current directory to `sys.path`. + if sys.path[0] != "": + sys.path.insert(0, "") + + # When a file has been given, run that, otherwise start the shell. + if a.args and not a.interactive: + sys.argv = a.args + path = a.args[0] + with open(path, "rb") as f: + code = compile(f.read(), path, "exec") + # NOTE: We have to pass an empty dictionary as namespace. Omitting + # this argument causes imports to not be found. See issue #326. + exec(code, {}) + + # Run interactive shell. + else: + enable_deprecation_warnings() + + # Apply config file + def configure(repl) -> None: + if os.path.exists(config_file): + run_config(repl, config_file) + + # Adjust colors if dark/light background flag has been given. + if a.light_bg: + repl.min_brightness = 0.0 + repl.max_brightness = 0.60 + elif a.dark_bg: + repl.min_brightness = 0.60 + repl.max_brightness = 1.0 + + import __main__ + + embed( + vi_mode=a.vi, + history_filename=history_file, + configure=configure, + locals=__main__.__dict__, + globals=__main__.__dict__, + startup_paths=startup_paths, + title="Python REPL (ptpython)", + ) + + +if __name__ == "__main__": + run() diff --git a/ptpython/eventloop.py b/ptpython/eventloop.py new file mode 100644 index 0000000..c841972 --- /dev/null +++ b/ptpython/eventloop.py @@ -0,0 +1,71 @@ +""" +Wrapper around the eventloop that gives some time to the Tkinter GUI to process +events when it's loaded and while we are waiting for input at the REPL. This +way we don't block the UI of for instance ``turtle`` and other Tk libraries. + +(Normally Tkinter registers it's callbacks in ``PyOS_InputHook`` to integrate +in readline. ``prompt-toolkit`` doesn't understand that input hook, but this +will fix it for Tk.) +""" +import sys +import time + +__all__ = ["inputhook"] + + +def _inputhook_tk(inputhook_context): + """ + Inputhook for Tk. + Run the Tk eventloop until prompt-toolkit needs to process the next input. + """ + # Get the current TK application. + import tkinter + + import _tkinter # Keep this imports inline! + + root = tkinter._default_root + + def wait_using_filehandler(): + """ + Run the TK eventloop until the file handler that we got from the + inputhook becomes readable. + """ + # Add a handler that sets the stop flag when `prompt-toolkit` has input + # to process. + stop = [False] + + def done(*a): + stop[0] = True + + root.createfilehandler(inputhook_context.fileno(), _tkinter.READABLE, done) + + # Run the TK event loop as long as we don't receive input. + while root.dooneevent(_tkinter.ALL_EVENTS): + if stop[0]: + break + + root.deletefilehandler(inputhook_context.fileno()) + + def wait_using_polling(): + """ + Windows TK doesn't support 'createfilehandler'. + So, run the TK eventloop and poll until input is ready. + """ + while not inputhook_context.input_is_ready(): + while root.dooneevent(_tkinter.ALL_EVENTS | _tkinter.DONT_WAIT): + pass + # Sleep to make the CPU idle, but not too long, so that the UI + # stays responsive. + time.sleep(0.01) + + if root is not None: + if hasattr(root, "createfilehandler"): + wait_using_filehandler() + else: + wait_using_polling() + + +def inputhook(inputhook_context): + # Only call the real input hook when the 'Tkinter' library was loaded. + if "Tkinter" in sys.modules or "tkinter" in sys.modules: + _inputhook_tk(inputhook_context) diff --git a/ptpython/filters.py b/ptpython/filters.py new file mode 100644 index 0000000..1adac13 --- /dev/null +++ b/ptpython/filters.py @@ -0,0 +1,36 @@ +from typing import TYPE_CHECKING + +from prompt_toolkit.filters import Filter + +if TYPE_CHECKING: + from .python_input import PythonInput + +__all__ = ["HasSignature", "ShowSidebar", "ShowSignature", "ShowDocstring"] + + +class PythonInputFilter(Filter): + def __init__(self, python_input: "PythonInput") -> None: + self.python_input = python_input + + def __call__(self) -> bool: + raise NotImplementedError + + +class HasSignature(PythonInputFilter): + def __call__(self) -> bool: + return bool(self.python_input.signatures) + + +class ShowSidebar(PythonInputFilter): + def __call__(self) -> bool: + return self.python_input.show_sidebar + + +class ShowSignature(PythonInputFilter): + def __call__(self) -> bool: + return self.python_input.show_signature + + +class ShowDocstring(PythonInputFilter): + def __call__(self) -> bool: + return self.python_input.show_docstring diff --git a/ptpython/history_browser.py b/ptpython/history_browser.py new file mode 100644 index 0000000..798a280 --- /dev/null +++ b/ptpython/history_browser.py @@ -0,0 +1,648 @@ +""" +Utility to easily select lines from the history and execute them again. + +`create_history_application` creates an `Application` instance that runs will +run as a sub application of the Repl/PythonInput. +""" +from functools import partial + +from prompt_toolkit.application import Application +from prompt_toolkit.application.current import get_app +from prompt_toolkit.buffer import Buffer +from prompt_toolkit.document import Document +from prompt_toolkit.enums import DEFAULT_BUFFER +from prompt_toolkit.filters import Condition, has_focus +from prompt_toolkit.formatted_text.utils import fragment_list_to_text +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.layout.containers import ( + ConditionalContainer, + Container, + Float, + FloatContainer, + HSplit, + ScrollOffsets, + VSplit, + Window, + WindowAlign, +) +from prompt_toolkit.layout.controls import BufferControl, FormattedTextControl +from prompt_toolkit.layout.dimension import Dimension as D +from prompt_toolkit.layout.layout import Layout +from prompt_toolkit.layout.margins import Margin, ScrollbarMargin +from prompt_toolkit.layout.processors import Processor, Transformation +from prompt_toolkit.lexers import PygmentsLexer +from prompt_toolkit.widgets import Frame +from prompt_toolkit.widgets.toolbars import ArgToolbar, SearchToolbar +from pygments.lexers import Python3Lexer as PythonLexer +from pygments.lexers import RstLexer + +from ptpython.layout import get_inputmode_fragments + +from .utils import if_mousedown + +HISTORY_COUNT = 2000 + +__all__ = ["HistoryLayout", "PythonHistory"] + +HELP_TEXT = """ +This interface is meant to select multiple lines from the +history and execute them together. + +Typical usage +------------- + +1. Move the ``cursor up`` in the history pane, until the + cursor is on the first desired line. +2. Hold down the ``space bar``, or press it multiple + times. Each time it will select one line and move to + the next one. Each selected line will appear on the + right side. +3. When all the required lines are displayed on the right + side, press ``Enter``. This will go back to the Python + REPL and show these lines as the current input. They + can still be edited from there. + +Key bindings +------------ + +Many Emacs and Vi navigation key bindings should work. +Press ``F4`` to switch between Emacs and Vi mode. + +Additional bindings: + +- ``Space``: Select or delect a line. +- ``Tab``: Move the focus between the history and input + pane. (Alternative: ``Ctrl-W``) +- ``Ctrl-C``: Cancel. Ignore the result and go back to + the REPL. (Alternatives: ``q`` and ``Control-G``.) +- ``Enter``: Accept the result and go back to the REPL. +- ``F1``: Show/hide help. Press ``Enter`` to quit this + help message. + +Further, remember that searching works like in Emacs +(using ``Ctrl-R``) or Vi (using ``/``). +""" + + +class BORDER: + " Box drawing characters. " + HORIZONTAL = "\u2501" + VERTICAL = "\u2503" + TOP_LEFT = "\u250f" + TOP_RIGHT = "\u2513" + BOTTOM_LEFT = "\u2517" + BOTTOM_RIGHT = "\u251b" + LIGHT_VERTICAL = "\u2502" + + +def _create_popup_window(title: str, body: Container) -> Frame: + """ + Return the layout for a pop-up window. It consists of a title bar showing + the `title` text, and a body layout. The window is surrounded by borders. + """ + return Frame(body=body, title=title) + + +class HistoryLayout: + """ + Create and return a `Container` instance for the history + application. + """ + + def __init__(self, history): + search_toolbar = SearchToolbar() + + self.help_buffer_control = BufferControl( + buffer=history.help_buffer, lexer=PygmentsLexer(RstLexer) + ) + + help_window = _create_popup_window( + title="History Help", + body=Window( + content=self.help_buffer_control, + right_margins=[ScrollbarMargin(display_arrows=True)], + scroll_offsets=ScrollOffsets(top=2, bottom=2), + ), + ) + + self.default_buffer_control = BufferControl( + buffer=history.default_buffer, + input_processors=[GrayExistingText(history.history_mapping)], + lexer=PygmentsLexer(PythonLexer), + ) + + self.history_buffer_control = BufferControl( + buffer=history.history_buffer, + lexer=PygmentsLexer(PythonLexer), + search_buffer_control=search_toolbar.control, + preview_search=True, + ) + + history_window = Window( + content=self.history_buffer_control, + wrap_lines=False, + left_margins=[HistoryMargin(history)], + scroll_offsets=ScrollOffsets(top=2, bottom=2), + ) + + self.root_container = HSplit( + [ + # Top title bar. + Window( + content=FormattedTextControl(_get_top_toolbar_fragments), + align=WindowAlign.CENTER, + style="class:status-toolbar", + ), + FloatContainer( + content=VSplit( + [ + # Left side: history. + history_window, + # Separator. + Window( + width=D.exact(1), + char=BORDER.LIGHT_VERTICAL, + style="class:separator", + ), + # Right side: result. + Window( + content=self.default_buffer_control, + wrap_lines=False, + left_margins=[ResultMargin(history)], + scroll_offsets=ScrollOffsets(top=2, bottom=2), + ), + ] + ), + floats=[ + # Help text as a float. + Float( + width=60, + top=3, + bottom=2, + content=ConditionalContainer( + content=help_window, + filter=has_focus(history.help_buffer), + ), + ) + ], + ), + # Bottom toolbars. + ArgToolbar(), + search_toolbar, + Window( + content=FormattedTextControl( + partial(_get_bottom_toolbar_fragments, history=history) + ), + style="class:status-toolbar", + ), + ] + ) + + self.layout = Layout(self.root_container, history_window) + + +def _get_top_toolbar_fragments(): + return [("class:status-bar.title", "History browser - Insert from history")] + + +def _get_bottom_toolbar_fragments(history): + python_input = history.python_input + + @if_mousedown + def f1(mouse_event): + _toggle_help(history) + + @if_mousedown + def tab(mouse_event): + _select_other_window(history) + + return ( + [("class:status-toolbar", " ")] + + get_inputmode_fragments(python_input) + + [ + ("class:status-toolbar", " "), + ("class:status-toolbar.key", "[Space]"), + ("class:status-toolbar", " Toggle "), + ("class:status-toolbar.key", "[Tab]", tab), + ("class:status-toolbar", " Focus ", tab), + ("class:status-toolbar.key", "[Enter]"), + ("class:status-toolbar", " Accept "), + ("class:status-toolbar.key", "[F1]", f1), + ("class:status-toolbar", " Help ", f1), + ] + ) + + +class HistoryMargin(Margin): + """ + Margin for the history buffer. + This displays a green bar for the selected entries. + """ + + def __init__(self, history): + self.history_buffer = history.history_buffer + self.history_mapping = history.history_mapping + + def get_width(self, ui_content): + return 2 + + def create_margin(self, window_render_info, width, height): + document = self.history_buffer.document + + lines_starting_new_entries = self.history_mapping.lines_starting_new_entries + selected_lines = self.history_mapping.selected_lines + + current_lineno = document.cursor_position_row + + visible_line_to_input_line = window_render_info.visible_line_to_input_line + result = [] + + for y in range(height): + line_number = visible_line_to_input_line.get(y) + + # Show stars at the start of each entry. + # (Visualises multiline entries.) + if line_number in lines_starting_new_entries: + char = "*" + else: + char = " " + + if line_number in selected_lines: + t = "class:history-line,selected" + else: + t = "class:history-line" + + if line_number == current_lineno: + t = t + ",current" + + result.append((t, char)) + result.append(("", "\n")) + + return result + + +class ResultMargin(Margin): + """ + The margin to be shown in the result pane. + """ + + def __init__(self, history): + self.history_mapping = history.history_mapping + self.history_buffer = history.history_buffer + + def get_width(self, ui_content): + return 2 + + def create_margin(self, window_render_info, width, height): + document = self.history_buffer.document + + current_lineno = document.cursor_position_row + offset = ( + self.history_mapping.result_line_offset + ) # original_document.cursor_position_row + + visible_line_to_input_line = window_render_info.visible_line_to_input_line + + result = [] + + for y in range(height): + line_number = visible_line_to_input_line.get(y) + + if ( + line_number is None + or line_number < offset + or line_number >= offset + len(self.history_mapping.selected_lines) + ): + t = "" + elif line_number == current_lineno: + t = "class:history-line,selected,current" + else: + t = "class:history-line,selected" + + result.append((t, " ")) + result.append(("", "\n")) + + return result + + def invalidation_hash(self, document): + return document.cursor_position_row + + +class GrayExistingText(Processor): + """ + Turn the existing input, before and after the inserted code gray. + """ + + def __init__(self, history_mapping): + self.history_mapping = history_mapping + self._lines_before = len( + history_mapping.original_document.text_before_cursor.splitlines() + ) + + def apply_transformation(self, transformation_input): + lineno = transformation_input.lineno + fragments = transformation_input.fragments + + if lineno < self._lines_before or lineno >= self._lines_before + len( + self.history_mapping.selected_lines + ): + text = fragment_list_to_text(fragments) + return Transformation(fragments=[("class:history.existing-input", text)]) + else: + return Transformation(fragments=fragments) + + +class HistoryMapping: + """ + Keep a list of all the lines from the history and the selected lines. + """ + + def __init__(self, history, python_history, original_document): + self.history = history + self.python_history = python_history + self.original_document = original_document + + self.lines_starting_new_entries = set() + self.selected_lines = set() + + # Process history. + history_strings = python_history.get_strings() + history_lines = [] + + for entry_nr, entry in list(enumerate(history_strings))[-HISTORY_COUNT:]: + self.lines_starting_new_entries.add(len(history_lines)) + + for line in entry.splitlines(): + history_lines.append(line) + + if len(history_strings) > HISTORY_COUNT: + history_lines[0] = ( + "# *** History has been truncated to %s lines ***" % HISTORY_COUNT + ) + + self.history_lines = history_lines + self.concatenated_history = "\n".join(history_lines) + + # Line offset. + if self.original_document.text_before_cursor: + self.result_line_offset = self.original_document.cursor_position_row + 1 + else: + self.result_line_offset = 0 + + def get_new_document(self, cursor_pos=None): + """ + Create a `Document` instance that contains the resulting text. + """ + lines = [] + + # Original text, before cursor. + if self.original_document.text_before_cursor: + lines.append(self.original_document.text_before_cursor) + + # Selected entries from the history. + for line_no in sorted(self.selected_lines): + lines.append(self.history_lines[line_no]) + + # Original text, after cursor. + if self.original_document.text_after_cursor: + lines.append(self.original_document.text_after_cursor) + + # Create `Document` with cursor at the right position. + text = "\n".join(lines) + if cursor_pos is not None and cursor_pos > len(text): + cursor_pos = len(text) + return Document(text, cursor_pos) + + def update_default_buffer(self): + b = self.history.default_buffer + + b.set_document(self.get_new_document(b.cursor_position), bypass_readonly=True) + + +def _toggle_help(history): + " Display/hide help. " + help_buffer_control = history.history_layout.help_buffer_control + + if history.app.layout.current_control == help_buffer_control: + history.app.layout.focus_previous() + else: + history.app.layout.current_control = help_buffer_control + + +def _select_other_window(history): + " Toggle focus between left/right window. " + current_buffer = history.app.current_buffer + layout = history.history_layout.layout + + if current_buffer == history.history_buffer: + layout.current_control = history.history_layout.default_buffer_control + + elif current_buffer == history.default_buffer: + layout.current_control = history.history_layout.history_buffer_control + + +def create_key_bindings(history, python_input, history_mapping): + """ + Key bindings. + """ + bindings = KeyBindings() + handle = bindings.add + + @handle(" ", filter=has_focus(history.history_buffer)) + def _(event): + """ + Space: select/deselect line from history pane. + """ + b = event.current_buffer + line_no = b.document.cursor_position_row + + if not history_mapping.history_lines: + # If we've no history, then nothing to do + return + + if line_no in history_mapping.selected_lines: + # Remove line. + history_mapping.selected_lines.remove(line_no) + history_mapping.update_default_buffer() + else: + # Add line. + history_mapping.selected_lines.add(line_no) + history_mapping.update_default_buffer() + + # Update cursor position + default_buffer = history.default_buffer + default_lineno = ( + sorted(history_mapping.selected_lines).index(line_no) + + history_mapping.result_line_offset + ) + default_buffer.cursor_position = ( + default_buffer.document.translate_row_col_to_index(default_lineno, 0) + ) + + # Also move the cursor to the next line. (This way they can hold + # space to select a region.) + b.cursor_position = b.document.translate_row_col_to_index(line_no + 1, 0) + + @handle(" ", filter=has_focus(DEFAULT_BUFFER)) + @handle("delete", filter=has_focus(DEFAULT_BUFFER)) + @handle("c-h", filter=has_focus(DEFAULT_BUFFER)) + def _(event): + """ + Space: remove line from default pane. + """ + b = event.current_buffer + line_no = b.document.cursor_position_row - history_mapping.result_line_offset + + if line_no >= 0: + try: + history_lineno = sorted(history_mapping.selected_lines)[line_no] + except IndexError: + pass # When `selected_lines` is an empty set. + else: + history_mapping.selected_lines.remove(history_lineno) + + history_mapping.update_default_buffer() + + help_focussed = has_focus(history.help_buffer) + main_buffer_focussed = has_focus(history.history_buffer) | has_focus( + history.default_buffer + ) + + @handle("tab", filter=main_buffer_focussed) + @handle("c-x", filter=main_buffer_focussed, eager=True) + # Eager: ignore the Emacs [Ctrl-X Ctrl-X] binding. + @handle("c-w", filter=main_buffer_focussed) + def _(event): + " Select other window. " + _select_other_window(history) + + @handle("f4") + def _(event): + " Switch between Emacs/Vi mode. " + python_input.vi_mode = not python_input.vi_mode + + @handle("f1") + def _(event): + " Display/hide help. " + _toggle_help(history) + + @handle("enter", filter=help_focussed) + @handle("c-c", filter=help_focussed) + @handle("c-g", filter=help_focussed) + @handle("escape", filter=help_focussed) + def _(event): + " Leave help. " + event.app.layout.focus_previous() + + @handle("q", filter=main_buffer_focussed) + @handle("f3", filter=main_buffer_focussed) + @handle("c-c", filter=main_buffer_focussed) + @handle("c-g", filter=main_buffer_focussed) + def _(event): + " Cancel and go back. " + event.app.exit(result=None) + + @handle("enter", filter=main_buffer_focussed) + def _(event): + " Accept input. " + event.app.exit(result=history.default_buffer.text) + + enable_system_bindings = Condition(lambda: python_input.enable_system_bindings) + + @handle("c-z", filter=enable_system_bindings) + def _(event): + " Suspend to background. " + event.app.suspend_to_background() + + return bindings + + +class PythonHistory: + def __init__(self, python_input, original_document): + """ + Create an `Application` for the history screen. + This has to be run as a sub application of `python_input`. + + When this application runs and returns, it retuns the selected lines. + """ + self.python_input = python_input + + history_mapping = HistoryMapping(self, python_input.history, original_document) + self.history_mapping = history_mapping + + document = Document(history_mapping.concatenated_history) + document = Document( + document.text, + cursor_position=document.cursor_position + + document.get_start_of_line_position(), + ) + + self.history_buffer = Buffer( + document=document, + on_cursor_position_changed=self._history_buffer_pos_changed, + accept_handler=( + lambda buff: get_app().exit(result=self.default_buffer.text) + ), + read_only=True, + ) + + self.default_buffer = Buffer( + name=DEFAULT_BUFFER, + document=history_mapping.get_new_document(), + on_cursor_position_changed=self._default_buffer_pos_changed, + read_only=True, + ) + + self.help_buffer = Buffer(document=Document(HELP_TEXT, 0), read_only=True) + + self.history_layout = HistoryLayout(self) + + self.app = Application( + layout=self.history_layout.layout, + full_screen=True, + style=python_input._current_style, + mouse_support=Condition(lambda: python_input.enable_mouse_support), + key_bindings=create_key_bindings(self, python_input, history_mapping), + ) + + def _default_buffer_pos_changed(self, _): + """When the cursor changes in the default buffer. Synchronize with + history buffer.""" + # Only when this buffer has the focus. + if self.app.current_buffer == self.default_buffer: + try: + line_no = ( + self.default_buffer.document.cursor_position_row + - self.history_mapping.result_line_offset + ) + + if line_no < 0: # When the cursor is above the inserted region. + raise IndexError + + history_lineno = sorted(self.history_mapping.selected_lines)[line_no] + except IndexError: + pass + else: + self.history_buffer.cursor_position = ( + self.history_buffer.document.translate_row_col_to_index( + history_lineno, 0 + ) + ) + + def _history_buffer_pos_changed(self, _): + """ When the cursor changes in the history buffer. Synchronize. """ + # Only when this buffer has the focus. + if self.app.current_buffer == self.history_buffer: + line_no = self.history_buffer.document.cursor_position_row + + if line_no in self.history_mapping.selected_lines: + default_lineno = ( + sorted(self.history_mapping.selected_lines).index(line_no) + + self.history_mapping.result_line_offset + ) + + self.default_buffer.cursor_position = ( + self.default_buffer.document.translate_row_col_to_index( + default_lineno, 0 + ) + ) diff --git a/ptpython/ipython.py b/ptpython/ipython.py new file mode 100644 index 0000000..2e8d119 --- /dev/null +++ b/ptpython/ipython.py @@ -0,0 +1,285 @@ +""" + +Adaptor for using the input system of `prompt_toolkit` with the IPython +backend. + +This gives a powerful interactive shell that has a nice user interface, but +also the power of for instance all the %-magic functions that IPython has to +offer. + +""" +from warnings import warn + +from IPython import utils as ipy_utils +from IPython.core.inputsplitter import IPythonInputSplitter +from IPython.terminal.embed import InteractiveShellEmbed as _InteractiveShellEmbed +from IPython.terminal.ipapp import load_default_config +from prompt_toolkit.completion import ( + Completer, + Completion, + PathCompleter, + WordCompleter, +) +from prompt_toolkit.contrib.completers import SystemCompleter +from prompt_toolkit.contrib.regular_languages.compiler import compile +from prompt_toolkit.contrib.regular_languages.completion import GrammarCompleter +from prompt_toolkit.contrib.regular_languages.lexer import GrammarLexer +from prompt_toolkit.document import Document +from prompt_toolkit.formatted_text import PygmentsTokens +from prompt_toolkit.lexers import PygmentsLexer, SimpleLexer +from prompt_toolkit.styles import Style +from pygments.lexers import BashLexer, PythonLexer + +from ptpython.prompt_style import PromptStyle + +from .python_input import PythonCompleter, PythonInput, PythonValidator +from .style import default_ui_style + +__all__ = ["embed"] + + +class IPythonPrompt(PromptStyle): + """ + Style for IPython >5.0, use the prompt_toolkit tokens directly. + """ + + def __init__(self, prompts): + self.prompts = prompts + + def in_prompt(self): + return PygmentsTokens(self.prompts.in_prompt_tokens()) + + def in2_prompt(self, width): + return PygmentsTokens(self.prompts.continuation_prompt_tokens()) + + def out_prompt(self): + return [] + + +class IPythonValidator(PythonValidator): + def __init__(self, *args, **kwargs): + super(IPythonValidator, self).__init__(*args, **kwargs) + self.isp = IPythonInputSplitter() + + def validate(self, document): + document = Document(text=self.isp.transform_cell(document.text)) + super(IPythonValidator, self).validate(document) + + +def create_ipython_grammar(): + """ + Return compiled IPython grammar. + """ + return compile( + r""" + \s* + ( + (?P%)( + (?Ppycat|run|loadpy|load) \s+ (?P[^\s]+) | + (?Pcat) \s+ (?P[^\s]+) | + (?Ppushd|cd|ls) \s+ (?P[^\s]+) | + (?Ppdb) \s+ (?P[^\s]+) | + (?Pautocall) \s+ (?P[^\s]+) | + (?Ptime|timeit|prun) \s+ (?P.+) | + (?Ppsource|pfile|pinfo|pinfo2) \s+ (?P.+) | + (?Psystem) \s+ (?P.+) | + (?Punalias) \s+ (?P.+) | + (?P[^\s]+) .* | + ) .* | + !(?P.+) | + (?![%!]) (?P.+) + ) + \s* + """ + ) + + +def create_completer( + get_globals, + get_locals, + magics_manager, + alias_manager, + get_enable_dictionary_completion, +): + g = create_ipython_grammar() + + return GrammarCompleter( + g, + { + "python": PythonCompleter( + get_globals, get_locals, get_enable_dictionary_completion + ), + "magic": MagicsCompleter(magics_manager), + "alias_name": AliasCompleter(alias_manager), + "pdb_arg": WordCompleter(["on", "off"], ignore_case=True), + "autocall_arg": WordCompleter(["0", "1", "2"], ignore_case=True), + "py_filename": PathCompleter( + only_directories=False, file_filter=lambda name: name.endswith(".py") + ), + "filename": PathCompleter(only_directories=False), + "directory": PathCompleter(only_directories=True), + "system": SystemCompleter(), + }, + ) + + +def create_lexer(): + g = create_ipython_grammar() + + return GrammarLexer( + g, + lexers={ + "percent": SimpleLexer("class:pygments.operator"), + "magic": SimpleLexer("class:pygments.keyword"), + "filename": SimpleLexer("class:pygments.name"), + "python": PygmentsLexer(PythonLexer), + "system": PygmentsLexer(BashLexer), + }, + ) + + +class MagicsCompleter(Completer): + def __init__(self, magics_manager): + self.magics_manager = magics_manager + + def get_completions(self, document, complete_event): + text = document.text_before_cursor.lstrip() + + for m in sorted(self.magics_manager.magics["line"]): + if m.startswith(text): + yield Completion("%s" % m, -len(text)) + + +class AliasCompleter(Completer): + def __init__(self, alias_manager): + self.alias_manager = alias_manager + + def get_completions(self, document, complete_event): + text = document.text_before_cursor.lstrip() + # aliases = [a for a, _ in self.alias_manager.aliases] + aliases = self.alias_manager.aliases + + for a, cmd in sorted(aliases, key=lambda a: a[0]): + if a.startswith(text): + yield Completion("%s" % a, -len(text), display_meta=cmd) + + +class IPythonInput(PythonInput): + """ + Override our `PythonCommandLineInterface` to add IPython specific stuff. + """ + + def __init__(self, ipython_shell, *a, **kw): + kw["_completer"] = create_completer( + kw["get_globals"], + kw["get_globals"], + ipython_shell.magics_manager, + ipython_shell.alias_manager, + lambda: self.enable_dictionary_completion, + ) + kw["_lexer"] = create_lexer() + kw["_validator"] = IPythonValidator(get_compiler_flags=self.get_compiler_flags) + + super().__init__(*a, **kw) + self.ipython_shell = ipython_shell + + self.all_prompt_styles["ipython"] = IPythonPrompt(ipython_shell.prompts) + self.prompt_style = "ipython" + + # UI style for IPython. Add tokens that are used by IPython>5.0 + style_dict = {} + style_dict.update(default_ui_style) + style_dict.update( + { + "pygments.prompt": "#009900", + "pygments.prompt-num": "#00ff00 bold", + "pygments.out-prompt": "#990000", + "pygments.out-prompt-num": "#ff0000 bold", + } + ) + + self.ui_styles = {"default": Style.from_dict(style_dict)} + self.use_ui_colorscheme("default") + + +class InteractiveShellEmbed(_InteractiveShellEmbed): + """ + Override the `InteractiveShellEmbed` from IPython, to replace the front-end + with our input shell. + + :param configure: Callable for configuring the repl. + """ + + def __init__(self, *a, **kw): + vi_mode = kw.pop("vi_mode", False) + history_filename = kw.pop("history_filename", None) + configure = kw.pop("configure", None) + title = kw.pop("title", None) + + # Don't ask IPython to confirm for exit. We have our own exit prompt. + self.confirm_exit = False + + super().__init__(*a, **kw) + + def get_globals(): + return self.user_ns + + python_input = IPythonInput( + self, + get_globals=get_globals, + vi_mode=vi_mode, + history_filename=history_filename, + ) + + if title: + python_input.terminal_title = title + + if configure: + configure(python_input) + python_input.prompt_style = "ipython" # Don't take from config. + + self.python_input = python_input + + def prompt_for_code(self): + try: + return self.python_input.app.run() + except KeyboardInterrupt: + self.python_input.default_buffer.document = Document() + return "" + + +def initialize_extensions(shell, extensions): + """ + Partial copy of `InteractiveShellApp.init_extensions` from IPython. + """ + try: + iter(extensions) + except TypeError: + pass # no extensions found + else: + for ext in extensions: + try: + shell.extension_manager.load_extension(ext) + except: + warn( + "Error in loading extension: %s" % ext + + "\nCheck your config files in %s" + % ipy_utils.path.get_ipython_dir() + ) + shell.showtraceback() + + +def embed(**kwargs): + """ + Copied from `IPython/terminal/embed.py`, but using our `InteractiveShellEmbed` instead. + """ + config = kwargs.get("config") + header = kwargs.pop("header", "") + compile_flags = kwargs.pop("compile_flags", None) + if config is None: + config = load_default_config() + config.InteractiveShellEmbed = config.TerminalInteractiveShell + kwargs["config"] = config + shell = InteractiveShellEmbed.instance(**kwargs) + initialize_extensions(shell, config["InteractiveShellApp"]["extensions"]) + shell(header=header, stack_depth=2, compile_flags=compile_flags) diff --git a/ptpython/key_bindings.py b/ptpython/key_bindings.py new file mode 100644 index 0000000..86317f9 --- /dev/null +++ b/ptpython/key_bindings.py @@ -0,0 +1,326 @@ +from prompt_toolkit.application import get_app +from prompt_toolkit.document import Document +from prompt_toolkit.enums import DEFAULT_BUFFER +from prompt_toolkit.filters import ( + Condition, + emacs_insert_mode, + emacs_mode, + has_focus, + has_selection, + vi_insert_mode, +) +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.key_binding.bindings.named_commands import get_by_name +from prompt_toolkit.keys import Keys + +from .utils import document_is_multiline_python + +__all__ = [ + "load_python_bindings", + "load_sidebar_bindings", + "load_confirm_exit_bindings", +] + + +@Condition +def tab_should_insert_whitespace(): + """ + When the 'tab' key is pressed with only whitespace character before the + cursor, do autocompletion. Otherwise, insert indentation. + + Except for the first character at the first line. Then always do a + completion. It doesn't make sense to start the first line with + indentation. + """ + b = get_app().current_buffer + before_cursor = b.document.current_line_before_cursor + + return bool(b.text and (not before_cursor or before_cursor.isspace())) + + +def load_python_bindings(python_input): + """ + Custom key bindings. + """ + bindings = KeyBindings() + + sidebar_visible = Condition(lambda: python_input.show_sidebar) + handle = bindings.add + + @handle("c-l") + def _(event): + """ + Clear whole screen and render again -- also when the sidebar is visible. + """ + event.app.renderer.clear() + + @handle("c-z") + def _(event): + """ + Suspend. + """ + if python_input.enable_system_bindings: + event.app.suspend_to_background() + + # Delete word before cursor, but use all Python symbols as separators + # (WORD=False). + handle("c-w")(get_by_name("backward-kill-word")) + + @handle("f2") + def _(event): + """ + Show/hide sidebar. + """ + python_input.show_sidebar = not python_input.show_sidebar + if python_input.show_sidebar: + event.app.layout.focus(python_input.ptpython_layout.sidebar) + else: + event.app.layout.focus_last() + + @handle("f3") + def _(event): + """ + Select from the history. + """ + python_input.enter_history() + + @handle("f4") + def _(event): + """ + Toggle between Vi and Emacs mode. + """ + python_input.vi_mode = not python_input.vi_mode + + @handle("f6") + def _(event): + """ + Enable/Disable paste mode. + """ + python_input.paste_mode = not python_input.paste_mode + + @handle( + "tab", filter=~sidebar_visible & ~has_selection & tab_should_insert_whitespace + ) + def _(event): + """ + When tab should insert whitespace, do that instead of completion. + """ + event.app.current_buffer.insert_text(" ") + + @Condition + def is_multiline(): + return document_is_multiline_python(python_input.default_buffer.document) + + @handle( + "enter", + filter=~sidebar_visible + & ~has_selection + & (vi_insert_mode | emacs_insert_mode) + & has_focus(DEFAULT_BUFFER) + & ~is_multiline, + ) + @handle(Keys.Escape, Keys.Enter, filter=~sidebar_visible & emacs_mode) + def _(event): + """ + Accept input (for single line input). + """ + b = event.current_buffer + + if b.validate(): + # When the cursor is at the end, and we have an empty line: + # drop the empty lines, but return the value. + b.document = Document( + text=b.text.rstrip(), cursor_position=len(b.text.rstrip()) + ) + + b.validate_and_handle() + + @handle( + "enter", + filter=~sidebar_visible + & ~has_selection + & (vi_insert_mode | emacs_insert_mode) + & has_focus(DEFAULT_BUFFER) + & is_multiline, + ) + def _(event): + """ + Behaviour of the Enter key. + + Auto indent after newline/Enter. + (When not in Vi navigaton mode, and when multiline is enabled.) + """ + b = event.current_buffer + empty_lines_required = python_input.accept_input_on_enter or 10000 + + def at_the_end(b): + """we consider the cursor at the end when there is no text after + the cursor, or only whitespace.""" + text = b.document.text_after_cursor + return text == "" or (text.isspace() and not "\n" in text) + + if python_input.paste_mode: + # In paste mode, always insert text. + b.insert_text("\n") + + elif at_the_end(b) and b.document.text.replace(" ", "").endswith( + "\n" * (empty_lines_required - 1) + ): + # When the cursor is at the end, and we have an empty line: + # drop the empty lines, but return the value. + if b.validate(): + b.document = Document( + text=b.text.rstrip(), cursor_position=len(b.text.rstrip()) + ) + + b.validate_and_handle() + else: + auto_newline(b) + + @handle( + "c-d", + filter=~sidebar_visible + & has_focus(python_input.default_buffer) + & Condition( + lambda: + # The current buffer is empty. + not get_app().current_buffer.text + ), + ) + def _(event): + """ + Override Control-D exit, to ask for confirmation. + """ + if python_input.confirm_exit: + # Show exit confirmation and focus it (focusing is important for + # making sure the default buffer key bindings are not active). + python_input.show_exit_confirmation = True + python_input.app.layout.focus( + python_input.ptpython_layout.exit_confirmation + ) + else: + event.app.exit(exception=EOFError) + + @handle("c-c", filter=has_focus(python_input.default_buffer)) + def _(event): + " Abort when Control-C has been pressed. " + event.app.exit(exception=KeyboardInterrupt, style="class:aborting") + + return bindings + + +def load_sidebar_bindings(python_input): + """ + Load bindings for the navigation in the sidebar. + """ + bindings = KeyBindings() + + handle = bindings.add + sidebar_visible = Condition(lambda: python_input.show_sidebar) + + @handle("up", filter=sidebar_visible) + @handle("c-p", filter=sidebar_visible) + @handle("k", filter=sidebar_visible) + def _(event): + " Go to previous option. " + python_input.selected_option_index = ( + python_input.selected_option_index - 1 + ) % python_input.option_count + + @handle("down", filter=sidebar_visible) + @handle("c-n", filter=sidebar_visible) + @handle("j", filter=sidebar_visible) + def _(event): + " Go to next option. " + python_input.selected_option_index = ( + python_input.selected_option_index + 1 + ) % python_input.option_count + + @handle("right", filter=sidebar_visible) + @handle("l", filter=sidebar_visible) + @handle(" ", filter=sidebar_visible) + def _(event): + " Select next value for current option. " + option = python_input.selected_option + option.activate_next() + + @handle("left", filter=sidebar_visible) + @handle("h", filter=sidebar_visible) + def _(event): + " Select previous value for current option. " + option = python_input.selected_option + option.activate_previous() + + @handle("c-c", filter=sidebar_visible) + @handle("c-d", filter=sidebar_visible) + @handle("c-d", filter=sidebar_visible) + @handle("enter", filter=sidebar_visible) + @handle("escape", filter=sidebar_visible) + def _(event): + " Hide sidebar. " + python_input.show_sidebar = False + event.app.layout.focus_last() + + return bindings + + +def load_confirm_exit_bindings(python_input): + """ + Handle yes/no key presses when the exit confirmation is shown. + """ + bindings = KeyBindings() + + handle = bindings.add + confirmation_visible = Condition(lambda: python_input.show_exit_confirmation) + + @handle("y", filter=confirmation_visible) + @handle("Y", filter=confirmation_visible) + @handle("enter", filter=confirmation_visible) + @handle("c-d", filter=confirmation_visible) + def _(event): + """ + Really quit. + """ + event.app.exit(exception=EOFError, style="class:exiting") + + @handle(Keys.Any, filter=confirmation_visible) + def _(event): + """ + Cancel exit. + """ + python_input.show_exit_confirmation = False + python_input.app.layout.focus_previous() + + return bindings + + +def auto_newline(buffer): + r""" + Insert \n at the cursor position. Also add necessary padding. + """ + insert_text = buffer.insert_text + + if buffer.document.current_line_after_cursor: + # When we are in the middle of a line. Always insert a newline. + insert_text("\n") + else: + # Go to new line, but also add indentation. + current_line = buffer.document.current_line_before_cursor.rstrip() + insert_text("\n") + + # Unident if the last line ends with 'pass', remove four spaces. + unindent = current_line.rstrip().endswith(" pass") + + # Copy whitespace from current line + current_line2 = current_line[4:] if unindent else current_line + + for c in current_line2: + if c.isspace(): + insert_text(c) + else: + break + + # If the last line ends with a colon, add four extra spaces. + if current_line[-1:] == ":": + for x in range(4): + insert_text(" ") diff --git a/ptpython/layout.py b/ptpython/layout.py new file mode 100644 index 0000000..6482cbd --- /dev/null +++ b/ptpython/layout.py @@ -0,0 +1,763 @@ +""" +Creation of the `Layout` instance for the Python input/REPL. +""" +import platform +import sys +from enum import Enum +from inspect import _ParameterKind as ParameterKind +from typing import TYPE_CHECKING, Optional + +from prompt_toolkit.application import get_app +from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER +from prompt_toolkit.filters import ( + Condition, + has_focus, + is_done, + renderer_height_is_known, +) +from prompt_toolkit.formatted_text import fragment_list_width, to_formatted_text +from prompt_toolkit.formatted_text.base import StyleAndTextTuples +from prompt_toolkit.key_binding.vi_state import InputMode +from prompt_toolkit.layout.containers import ( + ConditionalContainer, + Container, + Float, + FloatContainer, + HSplit, + ScrollOffsets, + VSplit, + Window, +) +from prompt_toolkit.layout.controls import BufferControl, FormattedTextControl +from prompt_toolkit.layout.dimension import AnyDimension, Dimension +from prompt_toolkit.layout.layout import Layout +from prompt_toolkit.layout.margins import PromptMargin +from prompt_toolkit.layout.menus import CompletionsMenu, MultiColumnCompletionsMenu +from prompt_toolkit.layout.processors import ( + AppendAutoSuggestion, + ConditionalProcessor, + DisplayMultipleCursors, + HighlightIncrementalSearchProcessor, + HighlightMatchingBracketProcessor, + HighlightSelectionProcessor, + TabsProcessor, +) +from prompt_toolkit.lexers import SimpleLexer +from prompt_toolkit.mouse_events import MouseEvent +from prompt_toolkit.selection import SelectionType +from prompt_toolkit.widgets.toolbars import ( + ArgToolbar, + CompletionsToolbar, + SearchToolbar, + SystemToolbar, + ValidationToolbar, +) +from pygments.lexers import PythonLexer + +from .filters import HasSignature, ShowDocstring, ShowSidebar, ShowSignature +from .utils import if_mousedown + +if TYPE_CHECKING: + from .python_input import OptionCategory, PythonInput + +__all__ = ["PtPythonLayout", "CompletionVisualisation"] + + +class CompletionVisualisation(Enum): + " Visualisation method for the completions. " + NONE = "none" + POP_UP = "pop-up" + MULTI_COLUMN = "multi-column" + TOOLBAR = "toolbar" + + +def show_completions_toolbar(python_input: "PythonInput") -> Condition: + return Condition( + lambda: python_input.completion_visualisation == CompletionVisualisation.TOOLBAR + ) + + +def show_completions_menu(python_input: "PythonInput") -> Condition: + return Condition( + lambda: python_input.completion_visualisation == CompletionVisualisation.POP_UP + ) + + +def show_multi_column_completions_menu(python_input: "PythonInput") -> Condition: + return Condition( + lambda: python_input.completion_visualisation + == CompletionVisualisation.MULTI_COLUMN + ) + + +def python_sidebar(python_input: "PythonInput") -> Window: + """ + Create the `Layout` for the sidebar with the configurable options. + """ + + def get_text_fragments() -> StyleAndTextTuples: + tokens: StyleAndTextTuples = [] + + def append_category(category: "OptionCategory") -> None: + tokens.extend( + [ + ("class:sidebar", " "), + ("class:sidebar.title", " %-36s" % category.title), + ("class:sidebar", "\n"), + ] + ) + + def append(index: int, label: str, status: str) -> None: + selected = index == python_input.selected_option_index + + @if_mousedown + def select_item(mouse_event: MouseEvent) -> None: + python_input.selected_option_index = index + + @if_mousedown + def goto_next(mouse_event: MouseEvent) -> None: + " Select item and go to next value. " + python_input.selected_option_index = index + option = python_input.selected_option + option.activate_next() + + sel = ",selected" if selected else "" + + tokens.append(("class:sidebar" + sel, " >" if selected else " ")) + tokens.append(("class:sidebar.label" + sel, "%-24s" % label, select_item)) + tokens.append(("class:sidebar.status" + sel, " ", select_item)) + tokens.append(("class:sidebar.status" + sel, "%s" % status, goto_next)) + + if selected: + tokens.append(("[SetCursorPosition]", "")) + + tokens.append( + ("class:sidebar.status" + sel, " " * (13 - len(status)), goto_next) + ) + tokens.append(("class:sidebar", "<" if selected else "")) + tokens.append(("class:sidebar", "\n")) + + i = 0 + for category in python_input.options: + append_category(category) + + for option in category.options: + append(i, option.title, "%s" % option.get_current_value()) + i += 1 + + tokens.pop() # Remove last newline. + + return tokens + + class Control(FormattedTextControl): + def move_cursor_down(self): + python_input.selected_option_index += 1 + + def move_cursor_up(self): + python_input.selected_option_index -= 1 + + return Window( + Control(get_text_fragments), + style="class:sidebar", + width=Dimension.exact(43), + height=Dimension(min=3), + scroll_offsets=ScrollOffsets(top=1, bottom=1), + ) + + +def python_sidebar_navigation(python_input): + """ + Create the `Layout` showing the navigation information for the sidebar. + """ + + def get_text_fragments(): + # Show navigation info. + return [ + ("class:sidebar", " "), + ("class:sidebar.key", "[Arrows]"), + ("class:sidebar", " "), + ("class:sidebar.description", "Navigate"), + ("class:sidebar", " "), + ("class:sidebar.key", "[Enter]"), + ("class:sidebar", " "), + ("class:sidebar.description", "Hide menu"), + ] + + return Window( + FormattedTextControl(get_text_fragments), + style="class:sidebar", + width=Dimension.exact(43), + height=Dimension.exact(1), + ) + + +def python_sidebar_help(python_input): + """ + Create the `Layout` for the help text for the current item in the sidebar. + """ + token = "class:sidebar.helptext" + + def get_current_description(): + """ + Return the description of the selected option. + """ + i = 0 + for category in python_input.options: + for option in category.options: + if i == python_input.selected_option_index: + return option.description + i += 1 + return "" + + def get_help_text(): + return [(token, get_current_description())] + + return ConditionalContainer( + content=Window( + FormattedTextControl(get_help_text), + style=token, + height=Dimension(min=3), + wrap_lines=True, + ), + filter=ShowSidebar(python_input) + & Condition(lambda: python_input.show_sidebar_help) + & ~is_done, + ) + + +def signature_toolbar(python_input): + """ + Return the `Layout` for the signature. + """ + + def get_text_fragments() -> StyleAndTextTuples: + result: StyleAndTextTuples = [] + append = result.append + Signature = "class:signature-toolbar" + + if python_input.signatures: + sig = python_input.signatures[0] # Always take the first one. + + append((Signature, " ")) + try: + append((Signature, sig.name)) + except IndexError: + # Workaround for #37: https://github.com/jonathanslenders/python-prompt-toolkit/issues/37 + # See also: https://github.com/davidhalter/jedi/issues/490 + return [] + + append((Signature + ",operator", "(")) + + got_positional_only = False + got_keyword_only = False + + for i, p in enumerate(sig.parameters): + # Detect transition between positional-only and not positional-only. + if p.kind == ParameterKind.POSITIONAL_ONLY: + got_positional_only = True + if got_positional_only and p.kind != ParameterKind.POSITIONAL_ONLY: + got_positional_only = False + append((Signature, "/")) + append((Signature + ",operator", ", ")) + + if not got_keyword_only and p.kind == ParameterKind.KEYWORD_ONLY: + got_keyword_only = True + append((Signature, "*")) + append((Signature + ",operator", ", ")) + + sig_index = getattr(sig, "index", 0) + + if i == sig_index: + # Note: we use `_Param.description` instead of + # `_Param.name`, that way we also get the '*' before args. + append((Signature + ",current-name", p.description)) + else: + append((Signature, p.description)) + + if p.default: + # NOTE: For the jedi-based completion, the default is + # currently still part of the name. + append((Signature, f"={p.default}")) + + append((Signature + ",operator", ", ")) + + if sig.parameters: + # Pop last comma + result.pop() + + append((Signature + ",operator", ")")) + append((Signature, " ")) + return result + + return ConditionalContainer( + content=Window( + FormattedTextControl(get_text_fragments), height=Dimension.exact(1) + ), + filter= + # Show only when there is a signature + HasSignature(python_input) & + # Signature needs to be shown. + ShowSignature(python_input) & + # And no sidebar is visible. + ~ShowSidebar(python_input) & + # Not done yet. + ~is_done, + ) + + +class PythonPromptMargin(PromptMargin): + """ + Create margin that displays the prompt. + It shows something like "In [1]:". + """ + + def __init__(self, python_input) -> None: + self.python_input = python_input + + def get_prompt_style(): + return python_input.all_prompt_styles[python_input.prompt_style] + + def get_prompt() -> StyleAndTextTuples: + return to_formatted_text(get_prompt_style().in_prompt()) + + def get_continuation(width, line_number, is_soft_wrap): + if python_input.show_line_numbers and not is_soft_wrap: + text = ("%i " % (line_number + 1)).rjust(width) + return [("class:line-number", text)] + else: + return get_prompt_style().in2_prompt(width) + + super().__init__(get_prompt, get_continuation) + + +def status_bar(python_input: "PythonInput") -> Container: + """ + Create the `Layout` for the status bar. + """ + TB = "class:status-toolbar" + + @if_mousedown + def toggle_paste_mode(mouse_event: MouseEvent) -> None: + python_input.paste_mode = not python_input.paste_mode + + @if_mousedown + def enter_history(mouse_event: MouseEvent) -> None: + python_input.enter_history() + + def get_text_fragments() -> StyleAndTextTuples: + python_buffer = python_input.default_buffer + + result: StyleAndTextTuples = [] + append = result.append + + append((TB, " ")) + result.extend(get_inputmode_fragments(python_input)) + append((TB, " ")) + + # Position in history. + append( + ( + TB, + "%i/%i " + % (python_buffer.working_index + 1, len(python_buffer._working_lines)), + ) + ) + + # Shortcuts. + app = get_app() + if ( + not python_input.vi_mode + and app.current_buffer == python_input.search_buffer + ): + append((TB, "[Ctrl-G] Cancel search [Enter] Go to this position.")) + elif bool(app.current_buffer.selection_state) and not python_input.vi_mode: + # Emacs cut/copy keys. + append((TB, "[Ctrl-W] Cut [Meta-W] Copy [Ctrl-Y] Paste [Ctrl-G] Cancel")) + else: + result.extend( + [ + (TB + " class:status-toolbar.key", "[F3]", enter_history), + (TB, " History ", enter_history), + (TB + " class:status-toolbar.key", "[F6]", toggle_paste_mode), + (TB, " ", toggle_paste_mode), + ] + ) + + if python_input.paste_mode: + append( + (TB + " class:paste-mode-on", "Paste mode (on)", toggle_paste_mode) + ) + else: + append((TB, "Paste mode", toggle_paste_mode)) + + return result + + return ConditionalContainer( + content=Window(content=FormattedTextControl(get_text_fragments), style=TB), + filter=~is_done + & renderer_height_is_known + & Condition( + lambda: python_input.show_status_bar + and not python_input.show_exit_confirmation + ), + ) + + +def get_inputmode_fragments(python_input: "PythonInput") -> StyleAndTextTuples: + """ + Return current input mode as a list of (token, text) tuples for use in a + toolbar. + """ + app = get_app() + + @if_mousedown + def toggle_vi_mode(mouse_event: MouseEvent) -> None: + python_input.vi_mode = not python_input.vi_mode + + token = "class:status-toolbar" + input_mode_t = "class:status-toolbar.input-mode" + + mode = app.vi_state.input_mode + result: StyleAndTextTuples = [] + append = result.append + + if python_input.title: + result.extend(to_formatted_text(python_input.title)) + + append((input_mode_t, "[F4] ", toggle_vi_mode)) + + # InputMode + if python_input.vi_mode: + recording_register = app.vi_state.recording_register + if recording_register: + append((token, " ")) + append((token + " class:record", "RECORD({})".format(recording_register))) + append((token, " - ")) + + if app.current_buffer.selection_state is not None: + if app.current_buffer.selection_state.type == SelectionType.LINES: + append((input_mode_t, "Vi (VISUAL LINE)", toggle_vi_mode)) + elif app.current_buffer.selection_state.type == SelectionType.CHARACTERS: + append((input_mode_t, "Vi (VISUAL)", toggle_vi_mode)) + append((token, " ")) + elif app.current_buffer.selection_state.type == SelectionType.BLOCK: + append((input_mode_t, "Vi (VISUAL BLOCK)", toggle_vi_mode)) + append((token, " ")) + elif mode in (InputMode.INSERT, "vi-insert-multiple"): + append((input_mode_t, "Vi (INSERT)", toggle_vi_mode)) + append((token, " ")) + elif mode == InputMode.NAVIGATION: + append((input_mode_t, "Vi (NAV)", toggle_vi_mode)) + append((token, " ")) + elif mode == InputMode.REPLACE: + append((input_mode_t, "Vi (REPLACE)", toggle_vi_mode)) + append((token, " ")) + else: + if app.emacs_state.is_recording: + append((token, " ")) + append((token + " class:record", "RECORD")) + append((token, " - ")) + + append((input_mode_t, "Emacs", toggle_vi_mode)) + append((token, " ")) + + return result + + +def show_sidebar_button_info(python_input: "PythonInput") -> Container: + """ + Create `Layout` for the information in the right-bottom corner. + (The right part of the status bar.) + """ + + @if_mousedown + def toggle_sidebar(mouse_event: MouseEvent) -> None: + " Click handler for the menu. " + python_input.show_sidebar = not python_input.show_sidebar + + version = sys.version_info + tokens: StyleAndTextTuples = [ + ("class:status-toolbar.key", "[F2]", toggle_sidebar), + ("class:status-toolbar", " Menu", toggle_sidebar), + ("class:status-toolbar", " - "), + ( + "class:status-toolbar.python-version", + "%s %i.%i.%i" + % (platform.python_implementation(), version[0], version[1], version[2]), + ), + ("class:status-toolbar", " "), + ] + width = fragment_list_width(tokens) + + def get_text_fragments() -> StyleAndTextTuples: + # Python version + return tokens + + return ConditionalContainer( + content=Window( + FormattedTextControl(get_text_fragments), + style="class:status-toolbar", + height=Dimension.exact(1), + width=Dimension.exact(width), + ), + filter=~is_done + & renderer_height_is_known + & Condition( + lambda: python_input.show_status_bar + and not python_input.show_exit_confirmation + ), + ) + + +def create_exit_confirmation( + python_input: "PythonInput", style="class:exit-confirmation" +) -> Container: + """ + Create `Layout` for the exit message. + """ + + def get_text_fragments() -> StyleAndTextTuples: + # Show "Do you really want to exit?" + return [ + (style, "\n %s ([y]/n) " % python_input.exit_message), + ("[SetCursorPosition]", ""), + (style, " \n"), + ] + + visible = ~is_done & Condition(lambda: python_input.show_exit_confirmation) + + return ConditionalContainer( + content=Window( + FormattedTextControl(get_text_fragments, focusable=True), style=style + ), + filter=visible, + ) + + +def meta_enter_message(python_input: "PythonInput") -> Container: + """ + Create the `Layout` for the 'Meta+Enter` message. + """ + + def get_text_fragments() -> StyleAndTextTuples: + return [("class:accept-message", " [Meta+Enter] Execute ")] + + @Condition + def extra_condition() -> bool: + " Only show when... " + b = python_input.default_buffer + + return ( + python_input.show_meta_enter_message + and ( + not b.document.is_cursor_at_the_end + or python_input.accept_input_on_enter is None + ) + and "\n" in b.text + ) + + visible = ~is_done & has_focus(DEFAULT_BUFFER) & extra_condition + + return ConditionalContainer( + content=Window(FormattedTextControl(get_text_fragments)), filter=visible + ) + + +class PtPythonLayout: + def __init__( + self, + python_input: "PythonInput", + lexer=PythonLexer, + extra_body=None, + extra_toolbars=None, + extra_buffer_processors=None, + input_buffer_height: Optional[AnyDimension] = None, + ) -> None: + D = Dimension + extra_body = [extra_body] if extra_body else [] + extra_toolbars = extra_toolbars or [] + extra_buffer_processors = extra_buffer_processors or [] + input_buffer_height = input_buffer_height or D(min=6) + + search_toolbar = SearchToolbar(python_input.search_buffer) + + def create_python_input_window(): + def menu_position(): + """ + When there is no autocompletion menu to be shown, and we have a + signature, set the pop-up position at `bracket_start`. + """ + b = python_input.default_buffer + + if python_input.signatures: + row, col = python_input.signatures[0].bracket_start + index = b.document.translate_row_col_to_index(row - 1, col) + return index + + return Window( + BufferControl( + buffer=python_input.default_buffer, + search_buffer_control=search_toolbar.control, + lexer=lexer, + include_default_input_processors=False, + input_processors=[ + ConditionalProcessor( + processor=HighlightIncrementalSearchProcessor(), + filter=has_focus(SEARCH_BUFFER) + | has_focus(search_toolbar.control), + ), + HighlightSelectionProcessor(), + DisplayMultipleCursors(), + TabsProcessor(), + # Show matching parentheses, but only while editing. + ConditionalProcessor( + processor=HighlightMatchingBracketProcessor(chars="[](){}"), + filter=has_focus(DEFAULT_BUFFER) + & ~is_done + & Condition( + lambda: python_input.highlight_matching_parenthesis + ), + ), + ConditionalProcessor( + processor=AppendAutoSuggestion(), filter=~is_done + ), + ] + + extra_buffer_processors, + menu_position=menu_position, + # Make sure that we always see the result of an reverse-i-search: + preview_search=True, + ), + left_margins=[PythonPromptMargin(python_input)], + # Scroll offsets. The 1 at the bottom is important to make sure + # the cursor is never below the "Press [Meta+Enter]" message + # which is a float. + scroll_offsets=ScrollOffsets(bottom=1, left=4, right=4), + # As long as we're editing, prefer a minimal height of 6. + height=( + lambda: ( + None + if get_app().is_done or python_input.show_exit_confirmation + else input_buffer_height + ) + ), + wrap_lines=Condition(lambda: python_input.wrap_lines), + ) + + sidebar = python_sidebar(python_input) + self.exit_confirmation = create_exit_confirmation(python_input) + + root_container = HSplit( + [ + VSplit( + [ + HSplit( + [ + FloatContainer( + content=HSplit( + [create_python_input_window()] + extra_body + ), + floats=[ + Float( + xcursor=True, + ycursor=True, + content=HSplit( + [ + signature_toolbar(python_input), + ConditionalContainer( + content=CompletionsMenu( + scroll_offset=( + lambda: python_input.completion_menu_scroll_offset + ), + max_height=12, + ), + filter=show_completions_menu( + python_input + ), + ), + ConditionalContainer( + content=MultiColumnCompletionsMenu(), + filter=show_multi_column_completions_menu( + python_input + ), + ), + ] + ), + ), + Float( + left=2, + bottom=1, + content=self.exit_confirmation, + ), + Float( + bottom=0, + right=0, + height=1, + content=meta_enter_message(python_input), + hide_when_covering_content=True, + ), + Float( + bottom=1, + left=1, + right=0, + content=python_sidebar_help(python_input), + ), + ], + ), + ArgToolbar(), + search_toolbar, + SystemToolbar(), + ValidationToolbar(), + ConditionalContainer( + content=CompletionsToolbar(), + filter=show_completions_toolbar(python_input) + & ~is_done, + ), + # Docstring region. + ConditionalContainer( + content=Window( + height=D.exact(1), + char="\u2500", + style="class:separator", + ), + filter=HasSignature(python_input) + & ShowDocstring(python_input) + & ~is_done, + ), + ConditionalContainer( + content=Window( + BufferControl( + buffer=python_input.docstring_buffer, + lexer=SimpleLexer(style="class:docstring"), + # lexer=PythonLexer, + ), + height=D(max=12), + ), + filter=HasSignature(python_input) + & ShowDocstring(python_input) + & ~is_done, + ), + ] + ), + ConditionalContainer( + content=HSplit( + [ + sidebar, + Window(style="class:sidebar,separator", height=1), + python_sidebar_navigation(python_input), + ] + ), + filter=ShowSidebar(python_input) & ~is_done, + ), + ] + ) + ] + + extra_toolbars + + [ + VSplit( + [status_bar(python_input), show_sidebar_button_info(python_input)] + ) + ] + ) + + self.layout = Layout(root_container) + self.sidebar = sidebar diff --git a/ptpython/lexer.py b/ptpython/lexer.py new file mode 100644 index 0000000..62e470f --- /dev/null +++ b/ptpython/lexer.py @@ -0,0 +1,28 @@ +from typing import Callable, Optional + +from prompt_toolkit.document import Document +from prompt_toolkit.formatted_text import StyleAndTextTuples +from prompt_toolkit.lexers import Lexer, PygmentsLexer +from pygments.lexers import BashLexer +from pygments.lexers import Python3Lexer as PythonLexer + +__all__ = ["PtpythonLexer"] + + +class PtpythonLexer(Lexer): + """ + Lexer for ptpython input. + + If the input starts with an exclamation mark, use a Bash lexer, otherwise, + use a Python 3 lexer. + """ + + def __init__(self, python_lexer: Optional[Lexer] = None) -> None: + self.python_lexer = python_lexer or PygmentsLexer(PythonLexer) + self.system_lexer = PygmentsLexer(BashLexer) + + def lex_document(self, document: Document) -> Callable[[int], StyleAndTextTuples]: + if document.text.startswith("!"): + return self.system_lexer.lex_document(document) + + return self.python_lexer.lex_document(document) diff --git a/ptpython/prompt_style.py b/ptpython/prompt_style.py new file mode 100644 index 0000000..24e5f88 --- /dev/null +++ b/ptpython/prompt_style.py @@ -0,0 +1,77 @@ +from abc import ABCMeta, abstractmethod +from typing import TYPE_CHECKING + +from prompt_toolkit.formatted_text import AnyFormattedText + +if TYPE_CHECKING: + from .python_input import PythonInput + +__all__ = ["PromptStyle", "IPythonPrompt", "ClassicPrompt"] + + +class PromptStyle(metaclass=ABCMeta): + """ + Base class for all prompts. + """ + + @abstractmethod + def in_prompt(self) -> AnyFormattedText: + " Return the input tokens. " + return [] + + @abstractmethod + def in2_prompt(self, width: int) -> AnyFormattedText: + """ + Tokens for every following input line. + + :param width: The available width. This is coming from the width taken + by `in_prompt`. + """ + return [] + + @abstractmethod + def out_prompt(self) -> AnyFormattedText: + " Return the output tokens. " + return [] + + +class IPythonPrompt(PromptStyle): + """ + A prompt resembling the IPython prompt. + """ + + def __init__(self, python_input: "PythonInput") -> None: + self.python_input = python_input + + def in_prompt(self) -> AnyFormattedText: + return [ + ("class:in", "In ["), + ("class:in.number", "%s" % self.python_input.current_statement_index), + ("class:in", "]: "), + ] + + def in2_prompt(self, width: int) -> AnyFormattedText: + return [("class:in", "...: ".rjust(width))] + + def out_prompt(self) -> AnyFormattedText: + return [ + ("class:out", "Out["), + ("class:out.number", "%s" % self.python_input.current_statement_index), + ("class:out", "]:"), + ("", " "), + ] + + +class ClassicPrompt(PromptStyle): + """ + The classic Python prompt. + """ + + def in_prompt(self) -> AnyFormattedText: + return [("class:prompt", ">>> ")] + + def in2_prompt(self, width: int) -> AnyFormattedText: + return [("class:prompt.dots", "...")] + + def out_prompt(self) -> AnyFormattedText: + return [] diff --git a/ptpython/py.typed b/ptpython/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/ptpython/python_input.py b/ptpython/python_input.py new file mode 100644 index 0000000..e63cdf1 --- /dev/null +++ b/ptpython/python_input.py @@ -0,0 +1,1054 @@ +""" +Application for reading Python input. +This can be used for creation of Python REPLs. +""" +import __future__ + +import threading +from asyncio import get_event_loop +from functools import partial +from typing import TYPE_CHECKING, Any, Callable, Dict, Generic, List, Optional, TypeVar + +from prompt_toolkit.application import Application, get_app +from prompt_toolkit.auto_suggest import ( + AutoSuggestFromHistory, + ConditionalAutoSuggest, + ThreadedAutoSuggest, +) +from prompt_toolkit.buffer import Buffer +from prompt_toolkit.completion import ( + Completer, + ConditionalCompleter, + DynamicCompleter, + FuzzyCompleter, + ThreadedCompleter, + merge_completers, +) +from prompt_toolkit.document import Document +from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode +from prompt_toolkit.filters import Condition +from prompt_toolkit.formatted_text import AnyFormattedText +from prompt_toolkit.history import ( + FileHistory, + History, + InMemoryHistory, + ThreadedHistory, +) +from prompt_toolkit.input import Input +from prompt_toolkit.key_binding import ( + ConditionalKeyBindings, + KeyBindings, + merge_key_bindings, +) +from prompt_toolkit.key_binding.bindings.auto_suggest import load_auto_suggest_bindings +from prompt_toolkit.key_binding.bindings.open_in_editor import ( + load_open_in_editor_bindings, +) +from prompt_toolkit.key_binding.vi_state import InputMode +from prompt_toolkit.lexers import DynamicLexer, Lexer, SimpleLexer +from prompt_toolkit.output import ColorDepth, Output +from prompt_toolkit.styles import ( + AdjustBrightnessStyleTransformation, + BaseStyle, + ConditionalStyleTransformation, + DynamicStyle, + SwapLightAndDarkStyleTransformation, + merge_style_transformations, +) +from prompt_toolkit.utils import is_windows +from prompt_toolkit.validation import ConditionalValidator, Validator + +from .completer import CompletePrivateAttributes, HidePrivateCompleter, PythonCompleter +from .history_browser import PythonHistory +from .key_bindings import ( + load_confirm_exit_bindings, + load_python_bindings, + load_sidebar_bindings, +) +from .layout import CompletionVisualisation, PtPythonLayout +from .lexer import PtpythonLexer +from .prompt_style import ClassicPrompt, IPythonPrompt, PromptStyle +from .signatures import Signature, get_signatures_using_eval, get_signatures_using_jedi +from .style import generate_style, get_all_code_styles, get_all_ui_styles +from .utils import unindent_code +from .validator import PythonValidator + +__all__ = ["PythonInput"] + + +if TYPE_CHECKING: + from typing_extensions import Protocol + + class _SupportsLessThan(Protocol): + # Taken from typeshed. _T is used by "sorted", which needs anything + # sortable. + def __lt__(self, __other: Any) -> bool: + ... + + +_T = TypeVar("_T", bound="_SupportsLessThan") + + +class OptionCategory: + def __init__(self, title: str, options: List["Option"]) -> None: + self.title = title + self.options = options + + +class Option(Generic[_T]): + """ + Ptpython configuration option that can be shown and modified from the + sidebar. + + :param title: Text. + :param description: Text. + :param get_values: Callable that returns a dictionary mapping the + possible values to callbacks that activate these value. + :param get_current_value: Callable that returns the current, active value. + """ + + def __init__( + self, + title: str, + description: str, + get_current_value: Callable[[], _T], + # We accept `object` as return type for the select functions, because + # often they return an unused boolean. Maybe this can be improved. + get_values: Callable[[], Dict[_T, Callable[[], object]]], + ) -> None: + self.title = title + self.description = description + self.get_current_value = get_current_value + self.get_values = get_values + + @property + def values(self) -> Dict[_T, Callable[[], object]]: + return self.get_values() + + def activate_next(self, _previous: bool = False) -> None: + """ + Activate next value. + """ + current = self.get_current_value() + options = sorted(self.values.keys()) + + # Get current index. + try: + index = options.index(current) + except ValueError: + index = 0 + + # Go to previous/next index. + if _previous: + index -= 1 + else: + index += 1 + + # Call handler for this option. + next_option = options[index % len(options)] + self.values[next_option]() + + def activate_previous(self) -> None: + """ + Activate previous value. + """ + self.activate_next(_previous=True) + + +COLOR_DEPTHS = { + ColorDepth.DEPTH_1_BIT: "Monochrome", + ColorDepth.DEPTH_4_BIT: "ANSI Colors", + ColorDepth.DEPTH_8_BIT: "256 colors", + ColorDepth.DEPTH_24_BIT: "True color", +} + +_Namespace = Dict[str, Any] +_GetNamespace = Callable[[], _Namespace] + + +class PythonInput: + """ + Prompt for reading Python input. + + :: + + python_input = PythonInput(...) + python_code = python_input.app.run() + """ + + def __init__( + self, + get_globals: Optional[_GetNamespace] = None, + get_locals: Optional[_GetNamespace] = None, + history_filename: Optional[str] = None, + vi_mode: bool = False, + color_depth: Optional[ColorDepth] = None, + # Input/output. + input: Optional[Input] = None, + output: Optional[Output] = None, + # For internal use. + extra_key_bindings: Optional[KeyBindings] = None, + _completer: Optional[Completer] = None, + _validator: Optional[Validator] = None, + _lexer: Optional[Lexer] = None, + _extra_buffer_processors=None, + _extra_layout_body=None, + _extra_toolbars=None, + _input_buffer_height=None, + ) -> None: + + self.get_globals: _GetNamespace = get_globals or (lambda: {}) + self.get_locals: _GetNamespace = get_locals or self.get_globals + + self.completer = _completer or PythonCompleter( + self.get_globals, + self.get_locals, + lambda: self.enable_dictionary_completion, + ) + + self._completer = HidePrivateCompleter( + # If fuzzy is enabled, first do fuzzy completion, but always add + # the non-fuzzy completions, if somehow the fuzzy completer didn't + # find them. (Due to the way the cursor position is moved in the + # fuzzy completer, some completions will not always be found by the + # fuzzy completer, but will be found with the normal completer.) + merge_completers( + [ + ConditionalCompleter( + FuzzyCompleter(DynamicCompleter(lambda: self.completer)), + Condition(lambda: self.enable_fuzzy_completion), + ), + DynamicCompleter(lambda: self.completer), + ], + deduplicate=True, + ), + lambda: self.complete_private_attributes, + ) + self._validator = _validator or PythonValidator(self.get_compiler_flags) + self._lexer = PtpythonLexer(_lexer) + + self.history: History + if history_filename: + self.history = ThreadedHistory(FileHistory(history_filename)) + else: + self.history = InMemoryHistory() + + self._input_buffer_height = _input_buffer_height + self._extra_layout_body = _extra_layout_body or [] + self._extra_toolbars = _extra_toolbars or [] + self._extra_buffer_processors = _extra_buffer_processors or [] + + self.extra_key_bindings = extra_key_bindings or KeyBindings() + + # Settings. + self.title: AnyFormattedText = "" + self.show_signature: bool = False + self.show_docstring: bool = False + self.show_meta_enter_message: bool = True + self.completion_visualisation: CompletionVisualisation = ( + CompletionVisualisation.MULTI_COLUMN + ) + self.completion_menu_scroll_offset: int = 1 + + self.show_line_numbers: bool = False + self.show_status_bar: bool = True + self.wrap_lines: bool = True + self.complete_while_typing: bool = True + self.paste_mode: bool = ( + False # When True, don't insert whitespace after newline. + ) + self.confirm_exit: bool = ( + True # Ask for confirmation when Control-D is pressed. + ) + self.accept_input_on_enter: int = 2 # Accept when pressing Enter 'n' times. + # 'None' means that meta-enter is always required. + self.enable_open_in_editor: bool = True + self.enable_system_bindings: bool = True + self.enable_input_validation: bool = True + self.enable_auto_suggest: bool = False + self.enable_mouse_support: bool = False + self.enable_history_search: bool = False # When True, like readline, going + # back in history will filter the + # history on the records starting + # with the current input. + + self.enable_syntax_highlighting: bool = True + self.enable_fuzzy_completion: bool = False + self.enable_dictionary_completion: bool = False # Also eval-based completion. + self.complete_private_attributes: CompletePrivateAttributes = ( + CompletePrivateAttributes.ALWAYS + ) + self.swap_light_and_dark: bool = False + self.highlight_matching_parenthesis: bool = False + self.show_sidebar: bool = False # Currently show the sidebar. + + # Pager. + self.enable_output_formatting: bool = False + self.enable_pager: bool = False + + # When the sidebar is visible, also show the help text. + self.show_sidebar_help: bool = True + + # Currently show 'Do you really want to exit?' + self.show_exit_confirmation: bool = False + + # The title to be displayed in the terminal. (None or string.) + self.terminal_title: Optional[str] = None + + self.exit_message: str = "Do you really want to exit?" + self.insert_blank_line_after_output: bool = True # (For the REPL.) + self.insert_blank_line_after_input: bool = False # (For the REPL.) + + # The buffers. + self.default_buffer = self._create_buffer() + self.search_buffer: Buffer = Buffer() + self.docstring_buffer: Buffer = Buffer(read_only=True) + + # Tokens to be shown at the prompt. + self.prompt_style: str = "classic" # The currently active style. + + # Styles selectable from the menu. + self.all_prompt_styles: Dict[str, PromptStyle] = { + "ipython": IPythonPrompt(self), + "classic": ClassicPrompt(), + } + + self.get_input_prompt = lambda: self.all_prompt_styles[ + self.prompt_style + ].in_prompt() + + self.get_output_prompt = lambda: self.all_prompt_styles[ + self.prompt_style + ].out_prompt() + + #: Load styles. + self.code_styles: Dict[str, BaseStyle] = get_all_code_styles() + self.ui_styles = get_all_ui_styles() + self._current_code_style_name: str = "default" + self._current_ui_style_name: str = "default" + + if is_windows(): + self._current_code_style_name = "win32" + + self._current_style = self._generate_style() + self.color_depth: ColorDepth = color_depth or ColorDepth.default() + + self.max_brightness: float = 1.0 + self.min_brightness: float = 0.0 + + # Options to be configurable from the sidebar. + self.options = self._create_options() + self.selected_option_index: int = 0 + + #: Incremeting integer counting the current statement. + self.current_statement_index: int = 1 + + # Code signatures. (This is set asynchronously after a timeout.) + self.signatures: List[Signature] = [] + + # Boolean indicating whether we have a signatures thread running. + # (Never run more than one at the same time.) + self._get_signatures_thread_running: bool = False + + # Get into Vi navigation mode at startup + self.vi_start_in_navigation_mode: bool = False + + # Preserve last used Vi input mode between main loop iterations + self.vi_keep_last_used_mode: bool = False + + self.style_transformation = merge_style_transformations( + [ + ConditionalStyleTransformation( + SwapLightAndDarkStyleTransformation(), + filter=Condition(lambda: self.swap_light_and_dark), + ), + AdjustBrightnessStyleTransformation( + lambda: self.min_brightness, lambda: self.max_brightness + ), + ] + ) + self.ptpython_layout = PtPythonLayout( + self, + lexer=DynamicLexer( + lambda: self._lexer + if self.enable_syntax_highlighting + else SimpleLexer() + ), + input_buffer_height=self._input_buffer_height, + extra_buffer_processors=self._extra_buffer_processors, + extra_body=self._extra_layout_body, + extra_toolbars=self._extra_toolbars, + ) + + self.app = self._create_application(input, output) + + if vi_mode: + self.app.editing_mode = EditingMode.VI + + def _accept_handler(self, buff: Buffer) -> bool: + app = get_app() + app.exit(result=buff.text) + app.pre_run_callables.append(buff.reset) + return True # Keep text, we call 'reset' later on. + + @property + def option_count(self) -> int: + " Return the total amount of options. (In all categories together.) " + return sum(len(category.options) for category in self.options) + + @property + def selected_option(self) -> Option: + " Return the currently selected option. " + i = 0 + for category in self.options: + for o in category.options: + if i == self.selected_option_index: + return o + else: + i += 1 + + raise ValueError("Nothing selected") + + def get_compiler_flags(self) -> int: + """ + Give the current compiler flags by looking for _Feature instances + in the globals. + """ + flags = 0 + + for value in self.get_globals().values(): + try: + if isinstance(value, __future__._Feature): + f = value.compiler_flag + flags |= f + except BaseException: + # get_compiler_flags should never raise to not run into an + # `Unhandled exception in event loop` + + # See: https://github.com/prompt-toolkit/ptpython/issues/351 + # An exception can be raised when some objects in the globals + # raise an exception in a custom `__getattribute__`. + pass + + return flags + + @property + def add_key_binding(self) -> Callable[[_T], _T]: + """ + Shortcut for adding new key bindings. + (Mostly useful for a config.py file, that receives + a PythonInput/Repl instance as input.) + + :: + + @python_input.add_key_binding(Keys.ControlX, filter=...) + def handler(event): + ... + """ + + def add_binding_decorator(*k, **kw): + return self.extra_key_bindings.add(*k, **kw) + + return add_binding_decorator + + def install_code_colorscheme(self, name: str, style: BaseStyle) -> None: + """ + Install a new code color scheme. + """ + self.code_styles[name] = style + + def use_code_colorscheme(self, name: str) -> None: + """ + Apply new colorscheme. (By name.) + """ + assert name in self.code_styles + + self._current_code_style_name = name + self._current_style = self._generate_style() + + def install_ui_colorscheme(self, name: str, style: BaseStyle) -> None: + """ + Install a new UI color scheme. + """ + self.ui_styles[name] = style + + def use_ui_colorscheme(self, name: str) -> None: + """ + Apply new colorscheme. (By name.) + """ + assert name in self.ui_styles + + self._current_ui_style_name = name + self._current_style = self._generate_style() + + def _use_color_depth(self, depth: ColorDepth) -> None: + self.color_depth = depth + + def _set_min_brightness(self, value: float) -> None: + self.min_brightness = value + self.max_brightness = max(self.max_brightness, value) + + def _set_max_brightness(self, value: float) -> None: + self.max_brightness = value + self.min_brightness = min(self.min_brightness, value) + + def _generate_style(self) -> BaseStyle: + """ + Create new Style instance. + (We don't want to do this on every key press, because each time the + renderer receives a new style class, he will redraw everything.) + """ + return generate_style( + self.code_styles[self._current_code_style_name], + self.ui_styles[self._current_ui_style_name], + ) + + def _create_options(self) -> List[OptionCategory]: + """ + Create a list of `Option` instances for the options sidebar. + """ + + def enable(attribute: str, value: Any = True) -> bool: + setattr(self, attribute, value) + + # Return `True`, to be able to chain this in the lambdas below. + return True + + def disable(attribute: str) -> bool: + setattr(self, attribute, False) + return True + + def simple_option( + title: str, description: str, field_name: str, values: Optional[List] = None + ) -> Option: + " Create Simple on/of option. " + values = values or ["off", "on"] + + def get_current_value(): + return values[bool(getattr(self, field_name))] + + def get_values(): + return { + values[1]: lambda: enable(field_name), + values[0]: lambda: disable(field_name), + } + + return Option( + title=title, + description=description, + get_values=get_values, + get_current_value=get_current_value, + ) + + brightness_values = [1.0 / 20 * value for value in range(0, 21)] + + return [ + OptionCategory( + "Input", + [ + Option( + title="Editing mode", + description="Vi or emacs key bindings.", + get_current_value=lambda: ["Emacs", "Vi"][self.vi_mode], + get_values=lambda: { + "Emacs": lambda: disable("vi_mode"), + "Vi": lambda: enable("vi_mode"), + }, + ), + simple_option( + title="Paste mode", + description="When enabled, don't indent automatically.", + field_name="paste_mode", + ), + Option( + title="Complete while typing", + description="Generate autocompletions automatically while typing. " + 'Don\'t require pressing TAB. (Not compatible with "History search".)', + get_current_value=lambda: ["off", "on"][ + self.complete_while_typing + ], + get_values=lambda: { + "on": lambda: enable("complete_while_typing") + and disable("enable_history_search"), + "off": lambda: disable("complete_while_typing"), + }, + ), + Option( + title="Complete private attrs", + description="Show or hide private attributes in the completions. " + "'If no public' means: show private attributes only if no public " + "matches are found or if an underscore was typed.", + get_current_value=lambda: { + CompletePrivateAttributes.NEVER: "Never", + CompletePrivateAttributes.ALWAYS: "Always", + CompletePrivateAttributes.IF_NO_PUBLIC: "If no public", + }[self.complete_private_attributes], + get_values=lambda: { + "Never": lambda: enable( + "complete_private_attributes", + CompletePrivateAttributes.NEVER, + ), + "Always": lambda: enable( + "complete_private_attributes", + CompletePrivateAttributes.ALWAYS, + ), + "If no public": lambda: enable( + "complete_private_attributes", + CompletePrivateAttributes.IF_NO_PUBLIC, + ), + }, + ), + Option( + title="Enable fuzzy completion", + description="Enable fuzzy completion.", + get_current_value=lambda: ["off", "on"][ + self.enable_fuzzy_completion + ], + get_values=lambda: { + "on": lambda: enable("enable_fuzzy_completion"), + "off": lambda: disable("enable_fuzzy_completion"), + }, + ), + Option( + title="Dictionary completion", + description="Enable experimental dictionary/list completion.\n" + 'WARNING: this does "eval" on fragments of\n' + " your Python input and is\n" + " potentially unsafe.", + get_current_value=lambda: ["off", "on"][ + self.enable_dictionary_completion + ], + get_values=lambda: { + "on": lambda: enable("enable_dictionary_completion"), + "off": lambda: disable("enable_dictionary_completion"), + }, + ), + Option( + title="History search", + description="When pressing the up-arrow, filter the history on input starting " + 'with the current text. (Not compatible with "Complete while typing".)', + get_current_value=lambda: ["off", "on"][ + self.enable_history_search + ], + get_values=lambda: { + "on": lambda: enable("enable_history_search") + and disable("complete_while_typing"), + "off": lambda: disable("enable_history_search"), + }, + ), + simple_option( + title="Mouse support", + description="Respond to mouse clicks and scrolling for positioning the cursor, " + "selecting text and scrolling through windows.", + field_name="enable_mouse_support", + ), + simple_option( + title="Confirm on exit", + description="Require confirmation when exiting.", + field_name="confirm_exit", + ), + simple_option( + title="Input validation", + description="In case of syntax errors, move the cursor to the error " + "instead of showing a traceback of a SyntaxError.", + field_name="enable_input_validation", + ), + simple_option( + title="Auto suggestion", + description="Auto suggest inputs by looking at the history. " + "Pressing right arrow or Ctrl-E will complete the entry.", + field_name="enable_auto_suggest", + ), + Option( + title="Accept input on enter", + description="Amount of ENTER presses required to execute input when the cursor " + "is at the end of the input. (Note that META+ENTER will always execute.)", + get_current_value=lambda: str( + self.accept_input_on_enter or "meta-enter" + ), + get_values=lambda: { + "2": lambda: enable("accept_input_on_enter", 2), + "3": lambda: enable("accept_input_on_enter", 3), + "4": lambda: enable("accept_input_on_enter", 4), + "meta-enter": lambda: enable("accept_input_on_enter", None), + }, + ), + ], + ), + OptionCategory( + "Display", + [ + Option( + title="Completions", + description="Visualisation to use for displaying the completions. (Multiple columns, one column, a toolbar or nothing.)", + get_current_value=lambda: self.completion_visualisation.value, + get_values=lambda: { + CompletionVisualisation.NONE.value: lambda: enable( + "completion_visualisation", CompletionVisualisation.NONE + ), + CompletionVisualisation.POP_UP.value: lambda: enable( + "completion_visualisation", + CompletionVisualisation.POP_UP, + ), + CompletionVisualisation.MULTI_COLUMN.value: lambda: enable( + "completion_visualisation", + CompletionVisualisation.MULTI_COLUMN, + ), + CompletionVisualisation.TOOLBAR.value: lambda: enable( + "completion_visualisation", + CompletionVisualisation.TOOLBAR, + ), + }, + ), + Option( + title="Prompt", + description="Visualisation of the prompt. ('>>>' or 'In [1]:')", + get_current_value=lambda: self.prompt_style, + get_values=lambda: dict( + (s, partial(enable, "prompt_style", s)) + for s in self.all_prompt_styles + ), + ), + simple_option( + title="Blank line after input", + description="Insert a blank line after the input.", + field_name="insert_blank_line_after_input", + ), + simple_option( + title="Blank line after output", + description="Insert a blank line after the output.", + field_name="insert_blank_line_after_output", + ), + simple_option( + title="Show signature", + description="Display function signatures.", + field_name="show_signature", + ), + simple_option( + title="Show docstring", + description="Display function docstrings.", + field_name="show_docstring", + ), + simple_option( + title="Show line numbers", + description="Show line numbers when the input consists of multiple lines.", + field_name="show_line_numbers", + ), + simple_option( + title="Show Meta+Enter message", + description="Show the [Meta+Enter] message when this key combination is required to execute commands. " + + "(This is the case when a simple [Enter] key press will insert a newline.", + field_name="show_meta_enter_message", + ), + simple_option( + title="Wrap lines", + description="Wrap lines instead of scrolling horizontally.", + field_name="wrap_lines", + ), + simple_option( + title="Show status bar", + description="Show the status bar at the bottom of the terminal.", + field_name="show_status_bar", + ), + simple_option( + title="Show sidebar help", + description="When the sidebar is visible, also show this help text.", + field_name="show_sidebar_help", + ), + simple_option( + title="Highlight parenthesis", + description="Highlight matching parenthesis, when the cursor is on or right after one.", + field_name="highlight_matching_parenthesis", + ), + simple_option( + title="Reformat output (black)", + description="Reformat outputs using Black, if possible (experimental).", + field_name="enable_output_formatting", + ), + simple_option( + title="Enable pager for output", + description="Use a pager for displaying outputs that don't " + "fit on the screen.", + field_name="enable_pager", + ), + ], + ), + OptionCategory( + "Colors", + [ + simple_option( + title="Syntax highlighting", + description="Use colors for syntax highligthing", + field_name="enable_syntax_highlighting", + ), + simple_option( + title="Swap light/dark colors", + description="Swap light and dark colors.", + field_name="swap_light_and_dark", + ), + Option( + title="Code", + description="Color scheme to use for the Python code.", + get_current_value=lambda: self._current_code_style_name, + get_values=lambda: { + name: partial(self.use_code_colorscheme, name) + for name in self.code_styles + }, + ), + Option( + title="User interface", + description="Color scheme to use for the user interface.", + get_current_value=lambda: self._current_ui_style_name, + get_values=lambda: dict( + (name, partial(self.use_ui_colorscheme, name)) + for name in self.ui_styles + ), + ), + Option( + title="Color depth", + description="Monochrome (1 bit), 16 ANSI colors (4 bit),\n256 colors (8 bit), or 24 bit.", + get_current_value=lambda: COLOR_DEPTHS[self.color_depth], + get_values=lambda: { + name: partial(self._use_color_depth, depth) + for depth, name in COLOR_DEPTHS.items() + }, + ), + Option( + title="Min brightness", + description="Minimum brightness for the color scheme (default=0.0).", + get_current_value=lambda: "%.2f" % self.min_brightness, + get_values=lambda: { + "%.2f" % value: partial(self._set_min_brightness, value) + for value in brightness_values + }, + ), + Option( + title="Max brightness", + description="Maximum brightness for the color scheme (default=1.0).", + get_current_value=lambda: "%.2f" % self.max_brightness, + get_values=lambda: { + "%.2f" % value: partial(self._set_max_brightness, value) + for value in brightness_values + }, + ), + ], + ), + ] + + def _create_application( + self, input: Optional[Input], output: Optional[Output] + ) -> Application: + """ + Create an `Application` instance. + """ + return Application( + layout=self.ptpython_layout.layout, + key_bindings=merge_key_bindings( + [ + load_python_bindings(self), + load_auto_suggest_bindings(), + load_sidebar_bindings(self), + load_confirm_exit_bindings(self), + ConditionalKeyBindings( + load_open_in_editor_bindings(), + Condition(lambda: self.enable_open_in_editor), + ), + # Extra key bindings should not be active when the sidebar is visible. + ConditionalKeyBindings( + self.extra_key_bindings, + Condition(lambda: not self.show_sidebar), + ), + ] + ), + color_depth=lambda: self.color_depth, + paste_mode=Condition(lambda: self.paste_mode), + mouse_support=Condition(lambda: self.enable_mouse_support), + style=DynamicStyle(lambda: self._current_style), + style_transformation=self.style_transformation, + include_default_pygments_style=False, + reverse_vi_search_direction=True, + input=input, + output=output, + ) + + def _create_buffer(self) -> Buffer: + """ + Create the `Buffer` for the Python input. + """ + python_buffer = Buffer( + name=DEFAULT_BUFFER, + complete_while_typing=Condition(lambda: self.complete_while_typing), + enable_history_search=Condition(lambda: self.enable_history_search), + tempfile_suffix=".py", + history=self.history, + completer=ThreadedCompleter(self._completer), + validator=ConditionalValidator( + self._validator, Condition(lambda: self.enable_input_validation) + ), + auto_suggest=ConditionalAutoSuggest( + ThreadedAutoSuggest(AutoSuggestFromHistory()), + Condition(lambda: self.enable_auto_suggest), + ), + accept_handler=self._accept_handler, + on_text_changed=self._on_input_timeout, + ) + + return python_buffer + + @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 + + @property + def vi_mode(self) -> bool: + return self.editing_mode == EditingMode.VI + + @vi_mode.setter + def vi_mode(self, value: bool) -> None: + if value: + self.editing_mode = EditingMode.VI + else: + self.editing_mode = EditingMode.EMACS + + def _on_input_timeout(self, buff: Buffer, loop=None) -> None: + """ + When there is no input activity, + in another thread, get the signature of the current code. + """ + app = self.app + + # Never run multiple get-signature threads. + if self._get_signatures_thread_running: + return + self._get_signatures_thread_running = True + + document = buff.document + + loop = loop or get_event_loop() + + def run(): + # First, get signatures from Jedi. If we didn't found any and if + # "dictionary completion" (eval-based completion) is enabled, then + # get signatures using eval. + signatures = get_signatures_using_jedi( + document, self.get_locals(), self.get_globals() + ) + if not signatures and self.enable_dictionary_completion: + signatures = get_signatures_using_eval( + document, self.get_locals(), self.get_globals() + ) + + self._get_signatures_thread_running = False + + # Set signatures and redraw if the text didn't change in the + # meantime. Otherwise request new signatures. + if buff.text == document.text: + self.signatures = signatures + + # Set docstring in docstring buffer. + if signatures: + self.docstring_buffer.reset( + document=Document(signatures[0].docstring, cursor_position=0) + ) + else: + self.docstring_buffer.reset() + + app.invalidate() + else: + self._on_input_timeout(buff, loop=loop) + + loop.run_in_executor(None, run) + + def on_reset(self) -> None: + self.signatures = [] + + def enter_history(self) -> None: + """ + Display the history. + """ + app = get_app() + app.vi_state.input_mode = InputMode.NAVIGATION + + history = PythonHistory(self, self.default_buffer.document) + + import asyncio + + from prompt_toolkit.application import in_terminal + + async def do_in_terminal() -> None: + async with in_terminal(): + result = await history.app.run_async() + if result is not None: + self.default_buffer.text = result + + app.vi_state.input_mode = InputMode.INSERT + + asyncio.ensure_future(do_in_terminal()) + + def read(self) -> str: + """ + Read the input. + + This will run the Python input user interface in another thread, wait + for input to be accepted and return that. By running the UI in another + thread, we avoid issues regarding possibly nested event loops. + + This can raise EOFError, when Control-D is pressed. + """ + # Capture the current input_mode in order to restore it after reset, + # for ViState.reset() sets it to InputMode.INSERT unconditionally and + # doesn't accept any arguments. + def pre_run( + last_input_mode: InputMode = self.app.vi_state.input_mode, + ) -> None: + if self.vi_keep_last_used_mode: + self.app.vi_state.input_mode = last_input_mode + + if not self.vi_keep_last_used_mode and self.vi_start_in_navigation_mode: + self.app.vi_state.input_mode = InputMode.NAVIGATION + + # Run the UI. + result: str = "" + exception: Optional[BaseException] = None + + def in_thread() -> None: + nonlocal result, exception + try: + while True: + try: + result = self.app.run(pre_run=pre_run) + + if result.lstrip().startswith("\x1a"): + # When the input starts with Ctrl-Z, quit the REPL. + # (Important for Windows users.) + raise EOFError + + # Remove leading whitespace. + # (Users can add extra indentation, which happens for + # instance because of copy/pasting code.) + result = unindent_code(result) + + if result and not result.isspace(): + return + except KeyboardInterrupt: + # Abort - try again. + self.default_buffer.document = Document() + except BaseException as e: + exception = e + return + + finally: + if self.insert_blank_line_after_input: + self.app.output.write("\n") + + thread = threading.Thread(target=in_thread) + thread.start() + thread.join() + + if exception is not None: + raise exception + return result diff --git a/ptpython/repl.py b/ptpython/repl.py new file mode 100644 index 0000000..ae7b1d0 --- /dev/null +++ b/ptpython/repl.py @@ -0,0 +1,765 @@ +""" +Utility for creating a Python repl. + +:: + + from ptpython.repl import embed + embed(globals(), locals(), vi_mode=False) + +""" +import asyncio +import builtins +import os +import sys +import threading +import traceback +import types +import warnings +from dis import COMPILER_FLAG_NAMES +from enum import Enum +from typing import Any, Callable, ContextManager, Dict, Optional + +from prompt_toolkit.formatted_text import ( + HTML, + AnyFormattedText, + FormattedText, + PygmentsTokens, + StyleAndTextTuples, + fragment_list_width, + merge_formatted_text, + to_formatted_text, +) +from prompt_toolkit.formatted_text.utils import fragment_list_to_text, split_lines +from prompt_toolkit.key_binding import KeyBindings, KeyPressEvent +from prompt_toolkit.patch_stdout import patch_stdout as patch_stdout_context +from prompt_toolkit.shortcuts import ( + PromptSession, + clear_title, + print_formatted_text, + set_title, +) +from prompt_toolkit.styles import BaseStyle +from prompt_toolkit.utils import DummyContext, get_cwidth +from pygments.lexers import PythonLexer, PythonTracebackLexer +from pygments.token import Token + +from .python_input import PythonInput + +try: + from ast import PyCF_ALLOW_TOP_LEVEL_AWAIT # type: ignore +except ImportError: + PyCF_ALLOW_TOP_LEVEL_AWAIT = 0 + +__all__ = ["PythonRepl", "enable_deprecation_warnings", "run_config", "embed"] + + +def _get_coroutine_flag() -> Optional[int]: + for k, v in COMPILER_FLAG_NAMES.items(): + if v == "COROUTINE": + return k + + # Flag not found. + return None + + +COROUTINE_FLAG: Optional[int] = _get_coroutine_flag() + + +def _has_coroutine_flag(code: types.CodeType) -> bool: + if COROUTINE_FLAG is None: + # Not supported on this Python version. + return False + + return bool(code.co_flags & COROUTINE_FLAG) + + +class PythonRepl(PythonInput): + def __init__(self, *a, **kw) -> None: + self._startup_paths = kw.pop("startup_paths", None) + super().__init__(*a, **kw) + self._load_start_paths() + + def _load_start_paths(self) -> None: + " Start the Read-Eval-Print Loop. " + if self._startup_paths: + for path in self._startup_paths: + if os.path.exists(path): + with open(path, "rb") as f: + code = compile(f.read(), path, "exec") + exec(code, self.get_globals(), self.get_locals()) + else: + output = self.app.output + output.write("WARNING | File not found: {}\n\n".format(path)) + + def run(self) -> None: + """ + Run the REPL loop. + """ + if self.terminal_title: + set_title(self.terminal_title) + + self._add_to_namespace() + + try: + while True: + try: + # Read. + try: + text = self.read() + except EOFError: + return + + # Eval. + try: + result = self.eval(text) + except KeyboardInterrupt as e: # KeyboardInterrupt doesn't inherit from Exception. + raise + except SystemExit: + return + except BaseException as e: + self._handle_exception(e) + else: + # Print. + if result is not None: + self.show_result(result) + + # Loop. + self.current_statement_index += 1 + self.signatures = [] + + except KeyboardInterrupt as e: + # Handle all possible `KeyboardInterrupt` errors. This can + # happen during the `eval`, but also during the + # `show_result` if something takes too long. + # (Try/catch is around the whole block, because we want to + # prevent that a Control-C keypress terminates the REPL in + # any case.) + self._handle_keyboard_interrupt(e) + finally: + if self.terminal_title: + clear_title() + self._remove_from_namespace() + + async def run_async(self) -> None: + """ + Run the REPL loop, but run the blocking parts in an executor, so that + we don't block the event loop. Both the input and output (which can + display a pager) will run in a separate thread with their own event + loop, this way ptpython's own event loop won't interfere with the + asyncio event loop from where this is called. + + The "eval" however happens in the current thread, which is important. + (Both for control-C to work, as well as for the code to see the right + thread in which it was embedded). + """ + loop = asyncio.get_event_loop() + + if self.terminal_title: + set_title(self.terminal_title) + + self._add_to_namespace() + + try: + while True: + try: + # Read. + try: + text = await loop.run_in_executor(None, self.read) + except EOFError: + return + + # Eval. + try: + result = await self.eval_async(text) + except KeyboardInterrupt as e: # KeyboardInterrupt doesn't inherit from Exception. + raise + except SystemExit: + return + except BaseException as e: + self._handle_exception(e) + else: + # Print. + if result is not None: + await loop.run_in_executor( + None, lambda: self.show_result(result) + ) + + # Loop. + self.current_statement_index += 1 + self.signatures = [] + + except KeyboardInterrupt as e: + # XXX: This does not yet work properly. In some situations, + # `KeyboardInterrupt` exceptions can end up in the event + # loop selector. + self._handle_keyboard_interrupt(e) + finally: + if self.terminal_title: + clear_title() + self._remove_from_namespace() + + def eval(self, line: str) -> object: + """ + Evaluate the line and print the result. + """ + # WORKAROUND: Due to a bug in Jedi, the current directory is removed + # from sys.path. See: https://github.com/davidhalter/jedi/issues/1148 + if "" not in sys.path: + sys.path.insert(0, "") + + if line.lstrip().startswith("!"): + # Run as shell command + os.system(line[1:]) + else: + # Try eval first + try: + code = self._compile_with_flags(line, "eval") + except SyntaxError: + pass + else: + # No syntax errors for eval. Do eval. + result = eval(code, self.get_globals(), self.get_locals()) + + if _has_coroutine_flag(code): + result = asyncio.get_event_loop().run_until_complete(result) + + self._store_eval_result(result) + return result + + # If not a valid `eval` expression, run using `exec` instead. + # Note that we shouldn't run this in the `except SyntaxError` block + # above, then `sys.exc_info()` would not report the right error. + # See issue: https://github.com/prompt-toolkit/ptpython/issues/435 + code = self._compile_with_flags(line, "exec") + exec(code, self.get_globals(), self.get_locals()) + + return None + + async def eval_async(self, line: str) -> object: + """ + Evaluate the line and print the result. + """ + # WORKAROUND: Due to a bug in Jedi, the current directory is removed + # from sys.path. See: https://github.com/davidhalter/jedi/issues/1148 + if "" not in sys.path: + sys.path.insert(0, "") + + if line.lstrip().startswith("!"): + # Run as shell command + os.system(line[1:]) + else: + # Try eval first + try: + code = self._compile_with_flags(line, "eval") + except SyntaxError: + pass + else: + # No syntax errors for eval. Do eval. + result = eval(code, self.get_globals(), self.get_locals()) + + if _has_coroutine_flag(code): + result = await result + + self._store_eval_result(result) + return result + + # If not a valid `eval` expression, run using `exec` instead. + code = self._compile_with_flags(line, "exec") + exec(code, self.get_globals(), self.get_locals()) + + return None + + def _store_eval_result(self, result: object) -> None: + locals: Dict[str, Any] = self.get_locals() + locals["_"] = locals["_%i" % self.current_statement_index] = result + + def get_compiler_flags(self) -> int: + return super().get_compiler_flags() | PyCF_ALLOW_TOP_LEVEL_AWAIT + + def _compile_with_flags(self, code: str, mode: str): + " Compile code with the right compiler flags. " + return compile( + code, + "", + mode, + flags=self.get_compiler_flags(), + dont_inherit=True, + ) + + def show_result(self, result: object) -> None: + """ + Show __repr__ for an `eval` result. + + Note: this can raise `KeyboardInterrupt` if either calling `__repr__`, + `__pt_repr__` or formatting the output with "Black" takes to long + and the user presses Control-C. + """ + out_prompt = to_formatted_text(self.get_output_prompt()) + + # If the repr is valid Python code, use the Pygments lexer. + try: + result_repr = repr(result) + except KeyboardInterrupt: + raise # Don't catch here. + except BaseException as e: + # Calling repr failed. + self._handle_exception(e) + return + + try: + compile(result_repr, "", "eval") + except SyntaxError: + formatted_result_repr = to_formatted_text(result_repr) + else: + # Syntactically correct. Format with black and syntax highlight. + if self.enable_output_formatting: + # Inline import. Slightly speed up start-up time if black is + # not used. + import black + + result_repr = black.format_str( + result_repr, + mode=black.FileMode(line_length=self.app.output.get_size().columns), + ) + + formatted_result_repr = to_formatted_text( + PygmentsTokens(list(_lex_python_result(result_repr))) + ) + + # If __pt_repr__ is present, take this. This can return prompt_toolkit + # formatted text. + try: + if hasattr(result, "__pt_repr__"): + formatted_result_repr = to_formatted_text( + getattr(result, "__pt_repr__")() + ) + if isinstance(formatted_result_repr, list): + formatted_result_repr = FormattedText(formatted_result_repr) + except KeyboardInterrupt: + raise # Don't catch here. + except: + # For bad code, `__getattr__` can raise something that's not an + # `AttributeError`. This happens already when calling `hasattr()`. + pass + + # Align every line to the prompt. + line_sep = "\n" + " " * fragment_list_width(out_prompt) + indented_repr: StyleAndTextTuples = [] + + lines = list(split_lines(formatted_result_repr)) + + for i, fragment in enumerate(lines): + indented_repr.extend(fragment) + + # Add indentation separator between lines, not after the last line. + if i != len(lines) - 1: + indented_repr.append(("", line_sep)) + + # Write output tokens. + if self.enable_syntax_highlighting: + formatted_output = merge_formatted_text([out_prompt, indented_repr]) + else: + formatted_output = FormattedText( + out_prompt + [("", fragment_list_to_text(formatted_result_repr))] + ) + + if self.enable_pager: + self.print_paginated_formatted_text(to_formatted_text(formatted_output)) + else: + self.print_formatted_text(to_formatted_text(formatted_output)) + + self.app.output.flush() + + if self.insert_blank_line_after_output: + self.app.output.write("\n") + + def print_formatted_text( + self, formatted_text: StyleAndTextTuples, end: str = "\n" + ) -> None: + print_formatted_text( + FormattedText(formatted_text), + style=self._current_style, + style_transformation=self.style_transformation, + include_default_pygments_style=False, + output=self.app.output, + end=end, + ) + + def print_paginated_formatted_text( + self, + formatted_text: StyleAndTextTuples, + end: str = "\n", + ) -> None: + """ + Print formatted text, using --MORE-- style pagination. + (Avoid filling up the terminal's scrollback buffer.) + """ + pager_prompt = self.create_pager_prompt() + size = self.app.output.get_size() + + abort = False + print_all = False + + # Max number of lines allowed in the buffer before painting. + max_rows = size.rows - 1 + + # Page buffer. + rows_in_buffer = 0 + columns_in_buffer = 0 + page: StyleAndTextTuples = [] + + def flush_page() -> None: + nonlocal page, columns_in_buffer, rows_in_buffer + self.print_formatted_text(page, end="") + page = [] + columns_in_buffer = 0 + rows_in_buffer = 0 + + def show_pager() -> None: + nonlocal abort, max_rows, print_all + + # Run pager prompt in another thread. + # Same as for the input. This prevents issues with nested event + # loops. + pager_result = None + + def in_thread() -> None: + nonlocal pager_result + pager_result = pager_prompt.prompt() + + th = threading.Thread(target=in_thread) + th.start() + th.join() + + if pager_result == PagerResult.ABORT: + print("...") + abort = True + + elif pager_result == PagerResult.NEXT_LINE: + max_rows = 1 + + elif pager_result == PagerResult.NEXT_PAGE: + max_rows = size.rows - 1 + + elif pager_result == PagerResult.PRINT_ALL: + print_all = True + + # Loop over lines. Show --MORE-- prompt when page is filled. + + formatted_text = formatted_text + [("", end)] + lines = list(split_lines(formatted_text)) + + for lineno, line in enumerate(lines): + for style, text, *_ in line: + for c in text: + width = get_cwidth(c) + + # (Soft) wrap line if it doesn't fit. + if columns_in_buffer + width > size.columns: + # Show pager first if we get too many lines after + # wrapping. + if rows_in_buffer + 1 >= max_rows and not print_all: + page.append(("", "\n")) + flush_page() + show_pager() + if abort: + return + + rows_in_buffer += 1 + columns_in_buffer = 0 + + columns_in_buffer += width + page.append((style, c)) + + if rows_in_buffer + 1 >= max_rows and not print_all: + page.append(("", "\n")) + flush_page() + show_pager() + if abort: + return + else: + # Add line ending between lines (if `end="\n"` was given, one + # more empty line is added in `split_lines` automatically to + # take care of the final line ending). + if lineno != len(lines) - 1: + page.append(("", "\n")) + rows_in_buffer += 1 + columns_in_buffer = 0 + + flush_page() + + def create_pager_prompt(self) -> PromptSession["PagerResult"]: + """ + Create pager --MORE-- prompt. + """ + return create_pager_prompt(self._current_style, self.title) + + def _handle_exception(self, e: BaseException) -> None: + output = self.app.output + + # Instead of just calling ``traceback.format_exc``, we take the + # traceback and skip the bottom calls of this framework. + t, v, tb = sys.exc_info() + + # Required for pdb.post_mortem() to work. + sys.last_type, sys.last_value, sys.last_traceback = t, v, tb + + tblist = list(traceback.extract_tb(tb)) + + for line_nr, tb_tuple in enumerate(tblist): + if tb_tuple[0] == "": + tblist = tblist[line_nr:] + break + + l = traceback.format_list(tblist) + if l: + l.insert(0, "Traceback (most recent call last):\n") + l.extend(traceback.format_exception_only(t, v)) + + tb_str = "".join(l) + + # Format exception and write to output. + # (We use the default style. Most other styles result + # in unreadable colors for the traceback.) + if self.enable_syntax_highlighting: + tokens = list(_lex_python_traceback(tb_str)) + else: + tokens = [(Token, tb_str)] + + print_formatted_text( + PygmentsTokens(tokens), + style=self._current_style, + style_transformation=self.style_transformation, + include_default_pygments_style=False, + output=output, + ) + + output.write("%s\n" % e) + output.flush() + + def _handle_keyboard_interrupt(self, e: KeyboardInterrupt) -> None: + output = self.app.output + + output.write("\rKeyboardInterrupt\n\n") + output.flush() + + def _add_to_namespace(self) -> None: + """ + Add ptpython built-ins to global namespace. + """ + globals = self.get_globals() + + # Add a 'get_ptpython', similar to 'get_ipython' + def get_ptpython() -> PythonInput: + return self + + globals["get_ptpython"] = get_ptpython + + def _remove_from_namespace(self) -> None: + """ + Remove added symbols from the globals. + """ + globals = self.get_globals() + del globals["get_ptpython"] + + +def _lex_python_traceback(tb): + " Return token list for traceback string. " + lexer = PythonTracebackLexer() + return lexer.get_tokens(tb) + + +def _lex_python_result(tb): + " Return token list for Python string. " + lexer = PythonLexer() + # Use `get_tokens_unprocessed`, so that we get exactly the same string, + # without line endings appended. `print_formatted_text` already appends a + # line ending, and otherwise we'll have two line endings. + tokens = lexer.get_tokens_unprocessed(tb) + return [(tokentype, value) for index, tokentype, value in tokens] + + +def enable_deprecation_warnings() -> None: + """ + Show deprecation warnings, when they are triggered directly by actions in + the REPL. This is recommended to call, before calling `embed`. + + e.g. This will show an error message when the user imports the 'sha' + library on Python 2.7. + """ + warnings.filterwarnings("default", category=DeprecationWarning, module="__main__") + + +def run_config(repl: PythonInput, config_file: str = "~/.ptpython/config.py") -> None: + """ + Execute REPL config file. + + :param repl: `PythonInput` instance. + :param config_file: Path of the configuration file. + """ + # Expand tildes. + config_file = os.path.expanduser(config_file) + + def enter_to_continue() -> None: + input("\nPress ENTER to continue...") + + # Check whether this file exists. + if not os.path.exists(config_file): + print("Impossible to read %r" % config_file) + enter_to_continue() + return + + # Run the config file in an empty namespace. + try: + namespace: Dict[str, Any] = {} + + with open(config_file, "rb") as f: + code = compile(f.read(), config_file, "exec") + exec(code, namespace, namespace) + + # Now we should have a 'configure' method in this namespace. We call this + # method with the repl as an argument. + if "configure" in namespace: + namespace["configure"](repl) + + except Exception: + traceback.print_exc() + enter_to_continue() + + +def embed( + globals=None, + locals=None, + configure: Optional[Callable[[PythonRepl], None]] = None, + vi_mode: bool = False, + history_filename: Optional[str] = None, + title: Optional[str] = None, + startup_paths=None, + patch_stdout: bool = False, + return_asyncio_coroutine: bool = False, +) -> None: + """ + Call this to embed Python shell at the current point in your program. + It's similar to `IPython.embed` and `bpython.embed`. :: + + from prompt_toolkit.contrib.repl import embed + embed(globals(), locals()) + + :param vi_mode: Boolean. Use Vi instead of Emacs key bindings. + :param configure: Callable that will be called with the `PythonRepl` as a first + argument, to trigger configuration. + :param title: Title to be displayed in the terminal titlebar. (None or string.) + :param patch_stdout: When true, patch `sys.stdout` so that background + threads that are printing will print nicely above the prompt. + """ + # Default globals/locals + if globals is None: + globals = { + "__name__": "__main__", + "__package__": None, + "__doc__": None, + "__builtins__": builtins, + } + + locals = locals or globals + + def get_globals(): + return globals + + def get_locals(): + return locals + + # Create REPL. + repl = PythonRepl( + get_globals=get_globals, + get_locals=get_locals, + vi_mode=vi_mode, + history_filename=history_filename, + startup_paths=startup_paths, + ) + + if title: + repl.terminal_title = title + + if configure: + configure(repl) + + # Start repl. + patch_context: ContextManager = ( + patch_stdout_context() if patch_stdout else DummyContext() + ) + + if return_asyncio_coroutine: + + async def coroutine(): + with patch_context: + await repl.run_async() + + return coroutine() + else: + with patch_context: + repl.run() + + +class PagerResult(Enum): + ABORT = "ABORT" + NEXT_LINE = "NEXT_LINE" + NEXT_PAGE = "NEXT_PAGE" + PRINT_ALL = "PRINT_ALL" + + +def create_pager_prompt( + style: BaseStyle, title: AnyFormattedText = "" +) -> PromptSession[PagerResult]: + """ + Create a "continue" prompt for paginated output. + """ + bindings = KeyBindings() + + @bindings.add("enter") + @bindings.add("down") + def next_line(event: KeyPressEvent) -> None: + event.app.exit(result=PagerResult.NEXT_LINE) + + @bindings.add("space") + def next_page(event: KeyPressEvent) -> None: + event.app.exit(result=PagerResult.NEXT_PAGE) + + @bindings.add("a") + def print_all(event: KeyPressEvent) -> None: + event.app.exit(result=PagerResult.PRINT_ALL) + + @bindings.add("q") + @bindings.add("c-c") + @bindings.add("c-d") + @bindings.add("escape", eager=True) + def no(event: KeyPressEvent) -> None: + event.app.exit(result=PagerResult.ABORT) + + @bindings.add("") + def _(event: KeyPressEvent) -> None: + " Disallow inserting other text. " + pass + + style + + session: PromptSession[PagerResult] = PromptSession( + merge_formatted_text( + [ + title, + HTML( + "" + " -- MORE -- " + "[Enter] Scroll " + "[Space] Next page " + "[a] Print all " + "[q] Quit " + ": " + ), + ] + ), + key_bindings=bindings, + erase_when_done=True, + style=style, + ) + return session diff --git a/ptpython/signatures.py b/ptpython/signatures.py new file mode 100644 index 0000000..228b99b --- /dev/null +++ b/ptpython/signatures.py @@ -0,0 +1,266 @@ +""" +Helpers for retrieving the function signature of the function call that we are +editing. + +Either with the Jedi library, or using `inspect.signature` if Jedi fails and we +can use `eval()` to evaluate the function object. +""" +import inspect +from inspect import Signature as InspectSignature +from inspect import _ParameterKind as ParameterKind +from typing import Any, Dict, List, Optional, Sequence, Tuple + +from prompt_toolkit.document import Document + +from .completer import DictionaryCompleter +from .utils import get_jedi_script_from_document + +__all__ = ["Signature", "get_signatures_using_jedi", "get_signatures_using_eval"] + + +class Parameter: + def __init__( + self, + name: str, + annotation: Optional[str], + default: Optional[str], + kind: ParameterKind, + ) -> None: + self.name = name + self.kind = kind + + self.annotation = annotation + self.default = default + + def __repr__(self) -> str: + return f"Parameter(name={self.name!r})" + + @property + def description(self) -> str: + """ + Name + annotation. + """ + description = self.name + + if self.annotation is not None: + description += f": {self.annotation}" + + return description + + +class Signature: + """ + Signature definition used wrap around both Jedi signatures and + python-inspect signatures. + + :param index: Parameter index of the current cursor position. + :param bracket_start: (line, column) tuple for the open bracket that starts + the function call. + """ + + def __init__( + self, + name: str, + docstring: str, + parameters: Sequence[Parameter], + index: Optional[int] = None, + returns: str = "", + bracket_start: Tuple[int, int] = (0, 0), + ) -> None: + self.name = name + self.docstring = docstring + self.parameters = parameters + self.index = index + self.returns = returns + self.bracket_start = bracket_start + + @classmethod + def from_inspect_signature( + cls, + name: str, + docstring: str, + signature: InspectSignature, + index: int, + ) -> "Signature": + parameters = [] + + def get_annotation_name(annotation: object) -> str: + """ + Get annotation as string from inspect signature. + """ + try: + # In case the annotation is a class like "int", "float", ... + return str(annotation.__name__) # type: ignore + except AttributeError: + pass # No attribute `__name__`, e.g., in case of `List[int]`. + + annotation = str(annotation) + if annotation.startswith("typing."): + annotation = annotation[len("typing:") :] + return annotation + + for p in signature.parameters.values(): + parameters.append( + Parameter( + name=p.name, + annotation=get_annotation_name(p.annotation), + default=repr(p.default) + if p.default is not inspect.Parameter.empty + else None, + kind=p.kind, + ) + ) + + return cls( + name=name, + docstring=docstring, + parameters=parameters, + index=index, + returns="", + ) + + @classmethod + def from_jedi_signature(cls, signature) -> "Signature": + parameters = [] + + for p in signature.params: + if p is None: + # We just hit the "*". + continue + + parameters.append( + Parameter( + name=p.to_string(), # p.name, (`to_string()` already includes the annotation). + annotation=None, # p.infer_annotation() + default=None, # p.infer_default() + kind=p.kind, + ) + ) + + docstring = signature.docstring() + if not isinstance(docstring, str): + docstring = docstring.decode("utf-8") + + return cls( + name=signature.name, + docstring=docstring, + parameters=parameters, + index=signature.index, + returns="", + bracket_start=signature.bracket_start, + ) + + def __repr__(self) -> str: + return f"Signature({self.name!r}, parameters={self.parameters!r})" + + +def get_signatures_using_jedi( + document: Document, locals: Dict[str, Any], globals: Dict[str, Any] +) -> List[Signature]: + script = get_jedi_script_from_document(document, locals, globals) + + # Show signatures in help text. + if not script: + return [] + + try: + signatures = script.get_signatures() + except ValueError: + # e.g. in case of an invalid \\x escape. + signatures = [] + except Exception: + # Sometimes we still get an exception (TypeError), because + # of probably bugs in jedi. We can silence them. + # See: https://github.com/davidhalter/jedi/issues/492 + signatures = [] + else: + # Try to access the params attribute just once. For Jedi + # signatures containing the keyword-only argument star, + # this will crash when retrieving it the first time with + # AttributeError. Every following time it works. + # See: https://github.com/jonathanslenders/ptpython/issues/47 + # https://github.com/davidhalter/jedi/issues/598 + try: + if signatures: + signatures[0].params + except AttributeError: + pass + + return [Signature.from_jedi_signature(sig) for sig in signatures] + + +def get_signatures_using_eval( + document: Document, locals: Dict[str, Any], globals: Dict[str, Any] +) -> List[Signature]: + """ + Look for the signature of the function before the cursor position without + use of Jedi. This uses a similar approach as the `DictionaryCompleter` of + running `eval()` over the detected function name. + """ + # Look for open parenthesis, before cursor position. + text = document.text_before_cursor + pos = document.cursor_position - 1 + + paren_mapping = {")": "(", "}": "{", "]": "["} + paren_stack = [ + ")" + ] # Start stack with closing ')'. We are going to look for the matching open ')'. + comma_count = 0 # Number of comma's between start of function call and cursor pos. + found_start = False # Found something. + + while pos >= 0: + char = document.text[pos] + if char in ")]}": + paren_stack.append(char) + elif char in "([{": + if not paren_stack: + # Open paren, while no closing paren was found. Mouse cursor is + # positioned in nested parentheses. Not at the "top-level" of a + # function call. + break + if paren_mapping[paren_stack[-1]] != char: + # Unmatching parentheses: syntax error? + break + + paren_stack.pop() + + if len(paren_stack) == 0: + found_start = True + break + + elif char == "," and len(paren_stack) == 1: + comma_count += 1 + + pos -= 1 + + if not found_start: + return [] + + # We found the start of the function call. Now look for the object before + # this position on which we can do an 'eval' to retrieve the function + # object. + obj = DictionaryCompleter(lambda: globals, lambda: locals).eval_expression( + Document(document.text, cursor_position=pos), locals + ) + if obj is None: + return [] + + try: + name = obj.__name__ # type:ignore + except Exception: + name = obj.__class__.__name__ + + try: + signature = inspect.signature(obj) # type: ignore + except TypeError: + return [] # Not a callable object. + except ValueError: + return [] # No signature found, like for build-ins like "print". + + try: + doc = obj.__doc__ or "" + except: + doc = "" + + # TODO: `index` is not yet correct when dealing with keyword-only arguments. + return [Signature.from_inspect_signature(name, doc, signature, index=comma_count)] diff --git a/ptpython/style.py b/ptpython/style.py new file mode 100644 index 0000000..4b54d0c --- /dev/null +++ b/ptpython/style.py @@ -0,0 +1,175 @@ +from typing import Dict + +from prompt_toolkit.styles import BaseStyle, Style, merge_styles +from prompt_toolkit.styles.pygments import style_from_pygments_cls +from prompt_toolkit.utils import is_conemu_ansi, is_windows, is_windows_vt100_supported +from pygments.styles import get_all_styles, get_style_by_name + +__all__ = ["get_all_code_styles", "get_all_ui_styles", "generate_style"] + + +def get_all_code_styles() -> Dict[str, BaseStyle]: + """ + Return a mapping from style names to their classes. + """ + result: Dict[str, BaseStyle] = { + name: style_from_pygments_cls(get_style_by_name(name)) + for name in get_all_styles() + } + result["win32"] = Style.from_dict(win32_code_style) + return result + + +def get_all_ui_styles() -> Dict[str, BaseStyle]: + """ + Return a dict mapping {ui_style_name -> style_dict}. + """ + return { + "default": Style.from_dict(default_ui_style), + "blue": Style.from_dict(blue_ui_style), + } + + +def generate_style(python_style: BaseStyle, ui_style: BaseStyle) -> BaseStyle: + """ + Generate Pygments Style class from two dictionaries + containing style rules. + """ + return merge_styles([python_style, ui_style]) + + +# Code style for Windows consoles. They support only 16 colors, +# so we choose a combination that displays nicely. +win32_code_style = { + "pygments.comment": "#00ff00", + "pygments.keyword": "#44ff44", + "pygments.number": "", + "pygments.operator": "", + "pygments.string": "#ff44ff", + "pygments.name": "", + "pygments.name.decorator": "#ff4444", + "pygments.name.class": "#ff4444", + "pygments.name.function": "#ff4444", + "pygments.name.builtin": "#ff4444", + "pygments.name.attribute": "", + "pygments.name.constant": "", + "pygments.name.entity": "", + "pygments.name.exception": "", + "pygments.name.label": "", + "pygments.name.namespace": "", + "pygments.name.tag": "", + "pygments.name.variable": "", +} + + +default_ui_style = { + "control-character": "ansiblue", + # Classic prompt. + "prompt": "bold", + "prompt.dots": "noinherit", + # (IPython <5.0) Prompt: "In [1]:" + "in": "bold #008800", + "in.number": "", + # Return value. + "out": "#ff0000", + "out.number": "#ff0000", + # Completions. + "completion.builtin": "", + "completion.param": "#006666 italic", + "completion.keyword": "fg:#008800", + "completion.keyword fuzzymatch.inside": "fg:#008800", + "completion.keyword fuzzymatch.outside": "fg:#44aa44", + # Separator between windows. (Used above docstring.) + "separator": "#bbbbbb", + # System toolbar + "system-toolbar": "#22aaaa noinherit", + # "arg" toolbar. + "arg-toolbar": "#22aaaa noinherit", + "arg-toolbar.text": "noinherit", + # Signature toolbar. + "signature-toolbar": "bg:#44bbbb #000000", + "signature-toolbar current-name": "bg:#008888 #ffffff bold", + "signature-toolbar operator": "#000000 bold", + "docstring": "#888888", + # Validation toolbar. + "validation-toolbar": "bg:#440000 #aaaaaa", + # Status toolbar. + "status-toolbar": "bg:#222222 #aaaaaa", + "status-toolbar.title": "underline", + "status-toolbar.inputmode": "bg:#222222 #ffffaa", + "status-toolbar.key": "bg:#000000 #888888", + "status-toolbar key": "bg:#000000 #888888", + "status-toolbar.pastemodeon": "bg:#aa4444 #ffffff", + "status-toolbar.pythonversion": "bg:#222222 #ffffff bold", + "status-toolbar paste-mode-on": "bg:#aa4444 #ffffff", + "record": "bg:#884444 white", + "status-toolbar more": "#ffff44", + "status-toolbar.input-mode": "#ffff44", + # The options sidebar. + "sidebar": "bg:#bbbbbb #000000", + "sidebar.title": "bg:#668866 #ffffff", + "sidebar.label": "bg:#bbbbbb #222222", + "sidebar.status": "bg:#dddddd #000011", + "sidebar.label selected": "bg:#222222 #eeeeee", + "sidebar.status selected": "bg:#444444 #ffffff bold", + "sidebar.separator": "underline", + "sidebar.key": "bg:#bbddbb #000000 bold", + "sidebar.key.description": "bg:#bbbbbb #000000", + "sidebar.helptext": "bg:#fdf6e3 #000011", + # # Styling for the history layout. + # history.line: '', + # history.line.selected: 'bg:#008800 #000000', + # history.line.current: 'bg:#ffffff #000000', + # history.line.selected.current: 'bg:#88ff88 #000000', + # history.existinginput: '#888888', + # Help Window. + "window-border": "#aaaaaa", + "window-title": "bg:#bbbbbb #000000", + # Meta-enter message. + "accept-message": "bg:#ffff88 #444444", + # Exit confirmation. + "exit-confirmation": "bg:#884444 #ffffff", +} + + +# Some changes to get a bit more contrast on Windows consoles. +# (They only support 16 colors.) +if is_windows() and not is_conemu_ansi() and not is_windows_vt100_supported(): + default_ui_style.update( + { + "sidebar.title": "bg:#00ff00 #ffffff", + "exitconfirmation": "bg:#ff4444 #ffffff", + "toolbar.validation": "bg:#ff4444 #ffffff", + "menu.completions.completion": "bg:#ffffff #000000", + "menu.completions.completion.current": "bg:#aaaaaa #000000", + } + ) + + +blue_ui_style = {} +blue_ui_style.update(default_ui_style) +# blue_ui_style.update({ +# # Line numbers. +# Token.LineNumber: '#aa6666', +# +# # Highlighting of search matches in document. +# Token.SearchMatch: '#ffffff bg:#4444aa', +# Token.SearchMatch.Current: '#ffffff bg:#44aa44', +# +# # Highlighting of select text in document. +# Token.SelectedText: '#ffffff bg:#6666aa', +# +# # Completer toolbar. +# Token.Toolbar.Completions: 'bg:#44bbbb #000000', +# Token.Toolbar.Completions.Arrow: 'bg:#44bbbb #000000 bold', +# Token.Toolbar.Completions.Completion: 'bg:#44bbbb #000000', +# Token.Toolbar.Completions.Completion.Current: 'bg:#008888 #ffffff', +# +# # Completer menu. +# Token.Menu.Completions.Completion: 'bg:#44bbbb #000000', +# Token.Menu.Completions.Completion.Current: 'bg:#008888 #ffffff', +# Token.Menu.Completions.Meta: 'bg:#449999 #000000', +# Token.Menu.Completions.Meta.Current: 'bg:#00aaaa #000000', +# Token.Menu.Completions.ProgressBar: 'bg:#aaaaaa', +# Token.Menu.Completions.ProgressButton: 'bg:#000000', +# }) diff --git a/ptpython/utils.py b/ptpython/utils.py new file mode 100644 index 0000000..2fb24a4 --- /dev/null +++ b/ptpython/utils.py @@ -0,0 +1,198 @@ +""" +For internal use only. +""" +import re +from typing import Callable, Iterable, Type, TypeVar, cast + +from prompt_toolkit.formatted_text import to_formatted_text +from prompt_toolkit.formatted_text.utils import fragment_list_to_text +from prompt_toolkit.mouse_events import MouseEvent, MouseEventType + +__all__ = [ + "has_unclosed_brackets", + "get_jedi_script_from_document", + "document_is_multiline_python", + "unindent_code", +] + + +def has_unclosed_brackets(text: str) -> bool: + """ + Starting at the end of the string. If we find an opening bracket + for which we didn't had a closing one yet, return True. + """ + stack = [] + + # Ignore braces inside strings + text = re.sub(r"""('[^']*'|"[^"]*")""", "", text) # XXX: handle escaped quotes.! + + for c in reversed(text): + if c in "])}": + stack.append(c) + + elif c in "[({": + if stack: + if ( + (c == "[" and stack[-1] == "]") + or (c == "{" and stack[-1] == "}") + or (c == "(" and stack[-1] == ")") + ): + stack.pop() + else: + # Opening bracket for which we didn't had a closing one. + return True + + return False + + +def get_jedi_script_from_document(document, locals, globals): + import jedi # We keep this import in-line, to improve start-up time. + + # Importing Jedi is 'slow'. + + try: + return jedi.Interpreter( + document.text, + path="input-text", + namespaces=[locals, globals], + ) + except ValueError: + # Invalid cursor position. + # ValueError('`column` parameter is not in a valid range.') + return None + except AttributeError: + # Workaround for #65: https://github.com/jonathanslenders/python-prompt-toolkit/issues/65 + # See also: https://github.com/davidhalter/jedi/issues/508 + return None + except IndexError: + # Workaround Jedi issue #514: for https://github.com/davidhalter/jedi/issues/514 + return None + except KeyError: + # Workaroud for a crash when the input is "u'", the start of a unicode string. + return None + except Exception: + # Workaround for: https://github.com/jonathanslenders/ptpython/issues/91 + return None + + +_multiline_string_delims = re.compile("""[']{3}|["]{3}""") + + +def document_is_multiline_python(document): + """ + Determine whether this is a multiline Python document. + """ + + def ends_in_multiline_string() -> bool: + """ + ``True`` if we're inside a multiline string at the end of the text. + """ + delims = _multiline_string_delims.findall(document.text) + opening = None + for delim in delims: + if opening is None: + opening = delim + elif delim == opening: + opening = None + return bool(opening) + + if "\n" in document.text or ends_in_multiline_string(): + return True + + def line_ends_with_colon() -> bool: + return document.current_line.rstrip()[-1:] == ":" + + # If we just typed a colon, or still have open brackets, always insert a real newline. + if ( + line_ends_with_colon() + or ( + document.is_cursor_at_the_end + and has_unclosed_brackets(document.text_before_cursor) + ) + or document.text.startswith("@") + ): + return True + + # If the character before the cursor is a backslash (line continuation + # char), insert a new line. + elif document.text_before_cursor[-1:] == "\\": + return True + + return False + + +_T = TypeVar("_T", bound=Callable[[MouseEvent], None]) + + +def if_mousedown(handler: _T) -> _T: + """ + Decorator for mouse handlers. + Only handle event when the user pressed mouse down. + + (When applied to a token list. Scroll events will bubble up and are handled + by the Window.) + """ + + def handle_if_mouse_down(mouse_event: MouseEvent): + if mouse_event.event_type == MouseEventType.MOUSE_DOWN: + return handler(mouse_event) + else: + return NotImplemented + + return cast(_T, handle_if_mouse_down) + + +_T_type = TypeVar("_T_type", bound=Type) + + +def ptrepr_to_repr(cls: _T_type) -> _T_type: + """ + Generate a normal `__repr__` method for classes that have a `__pt_repr__`. + """ + if not hasattr(cls, "__pt_repr__"): + raise TypeError( + "@ptrepr_to_repr can only be applied to classes that have a `__pt_repr__` method." + ) + + def __repr__(self) -> str: + return fragment_list_to_text(to_formatted_text(cls.__pt_repr__(self))) + + cls.__repr__ = __repr__ # type:ignore + return cls + + +def unindent_code(text: str) -> str: + """ + Remove common leading whitespace when all lines are indented. + """ + lines = text.splitlines(keepends=True) + + # Look for common prefix. + common_prefix = _common_whitespace_prefix(lines) + + # Remove indentation. + lines = [line[len(common_prefix) :] for line in lines] + + return "".join(lines) + + +def _common_whitespace_prefix(strings: Iterable[str]) -> str: + """ + Return common prefix for a list of lines. + This will ignore lines that contain whitespace only. + """ + # Ignore empty lines and lines that have whitespace only. + strings = [s for s in strings if not s.isspace() and not len(s) == 0] + + if not strings: + return "" + + else: + s1 = min(strings) + s2 = max(strings) + + for i, c in enumerate(s1): + if c != s2[i] or c not in " \t": + return s1[:i] + + return s1 diff --git a/ptpython/validator.py b/ptpython/validator.py new file mode 100644 index 0000000..0f6a4ea --- /dev/null +++ b/ptpython/validator.py @@ -0,0 +1,57 @@ +from prompt_toolkit.validation import ValidationError, Validator + +from .utils import unindent_code + +__all__ = ["PythonValidator"] + + +class PythonValidator(Validator): + """ + Validation of Python input. + + :param get_compiler_flags: Callable that returns the currently + active compiler flags. + """ + + def __init__(self, get_compiler_flags=None): + self.get_compiler_flags = get_compiler_flags + + def validate(self, document): + """ + Check input for Python syntax errors. + """ + text = unindent_code(document.text) + + # When the input starts with Ctrl-Z, always accept. This means EOF in a + # Python REPL. + if text.startswith("\x1a"): + return + + # When the input starts with an exclamation mark. Accept as shell + # command. + if text.lstrip().startswith("!"): + return + + try: + if self.get_compiler_flags: + flags = self.get_compiler_flags() + else: + flags = 0 + + compile(text, "", "exec", flags=flags, dont_inherit=True) + except SyntaxError as e: + # Note, the 'or 1' for offset is required because Python 2.7 + # gives `None` as offset in case of '4=4' as input. (Looks like + # fixed in Python 3.) + # TODO: This is not correct if indentation was removed. + index = document.translate_row_col_to_index( + e.lineno - 1, (e.offset or 1) - 1 + ) + raise ValidationError(index, f"Syntax Error: {e}") + except TypeError as e: + # e.g. "compile() expected string without null bytes" + raise ValidationError(0, str(e)) + except ValueError as e: + # In Python 2, compiling "\x9" (an invalid escape sequence) raises + # ValueError instead of SyntaxError. + raise ValidationError(0, "Syntax Error: %s" % e) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..b356239 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,13 @@ +[tool.black] +target-version = ['py36'] + + +[tool.isort] +# isort configuration that is compatible with Black. +multi_line_output = 3 +include_trailing_comma = true +known_first_party = "ptpython" +known_third_party = "prompt_toolkit,pygments,asyncssh" +force_grid_wrap = 0 +use_parentheses = true +line_length = 88 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..3c6e79c --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[bdist_wheel] +universal=1 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..dbbe55b --- /dev/null +++ b/setup.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python +import os +import sys + +from setuptools import find_packages, setup + +with open(os.path.join(os.path.dirname(__file__), "README.rst")) as f: + long_description = f.read() + + +setup( + name="ptpython", + author="Jonathan Slenders", + version="3.0.16", + url="https://github.com/prompt-toolkit/ptpython", + description="Python REPL build on top of prompt_toolkit", + long_description=long_description, + packages=find_packages("."), + install_requires=[ + "appdirs", + "importlib_metadata;python_version<'3.8'", + "jedi>=0.16.0", + # Use prompt_toolkit 3.0.16, because of the `DeduplicateCompleter`. + "prompt_toolkit>=3.0.16,<3.1.0", + "pygments", + "black", + ], + python_requires=">=3.6", + classifiers=[ + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python", + ], + entry_points={ + "console_scripts": [ + "ptpython = ptpython.entry_points.run_ptpython:run", + "ptipython = ptpython.entry_points.run_ptipython:run", + "ptpython%s = ptpython.entry_points.run_ptpython:run" % sys.version_info[0], + "ptpython%s.%s = ptpython.entry_points.run_ptpython:run" + % sys.version_info[:2], + "ptipython%s = ptpython.entry_points.run_ptipython:run" + % sys.version_info[0], + "ptipython%s.%s = ptpython.entry_points.run_ptipython:run" + % sys.version_info[:2], + ] + }, + extras_require={"ptipython": ["ipython"]}, # For ptipython, we need to have IPython +) diff --git a/tests/run_tests.py b/tests/run_tests.py new file mode 100755 index 0000000..2f94516 --- /dev/null +++ b/tests/run_tests.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +import unittest + +import ptpython.completer +import ptpython.eventloop +import ptpython.filters +import ptpython.history_browser +import ptpython.key_bindings +import ptpython.layout +import ptpython.python_input +import ptpython.repl +import ptpython.style +import ptpython.utils +import ptpython.validator + +# For now there are no tests here. +# However this is sufficient for Travis to do at least a syntax check. +# That way we are at least sure to restrict to the Python 2.6 syntax. + + +if __name__ == "__main__": + unittest.main()