summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/test.yaml40
-rw-r--r--CHANGELOG16
-rw-r--r--PKG-INFO263
-rw-r--r--README.rst41
-rw-r--r--docs/concurrency-challenges.rst91
-rw-r--r--examples/ptpython_config/config.py7
-rw-r--r--mypy.ini6
-rw-r--r--ptpython.egg-info/PKG-INFO263
-rw-r--r--ptpython.egg-info/SOURCES.txt54
-rw-r--r--ptpython.egg-info/dependency_links.txt1
-rw-r--r--ptpython.egg-info/entry_points.txt8
-rw-r--r--ptpython.egg-info/requires.txt13
-rw-r--r--ptpython.egg-info/top_level.txt1
-rw-r--r--ptpython/completer.py29
-rw-r--r--ptpython/entry_points/run_ptpython.py12
-rw-r--r--ptpython/eventloop.py14
-rw-r--r--ptpython/filters.py1
-rw-r--r--ptpython/history_browser.py122
-rw-r--r--ptpython/ipython.py46
-rw-r--r--ptpython/key_bindings.py61
-rw-r--r--ptpython/layout.py64
-rw-r--r--ptpython/py.typed0
-rw-r--r--ptpython/python_input.py52
-rw-r--r--ptpython/repl.py15
-rw-r--r--ptpython/signatures.py9
-rw-r--r--ptpython/utils.py36
-rw-r--r--ptpython/validator.py9
-rw-r--r--setup.cfg44
-rw-r--r--setup.py3
29 files changed, 548 insertions, 773 deletions
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
new file mode 100644
index 0000000..ef806cf
--- /dev/null
+++ b/.github/workflows/test.yaml
@@ -0,0 +1,40 @@
+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, "3.10"]
+
+ 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
+ python -m pip install . types-dataclasses # Needed for Python 3.6
+ pip list
+ - name: Type Checker
+ run: |
+ mypy ptpython
+ isort -c --profile black ptpython examples setup.py
+ black --check ptpython examples setup.py
+ if: matrix.python-version != '3.6'
+ - 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/CHANGELOG b/CHANGELOG
index 69a95e7..ebc39c9 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,22 @@
CHANGELOG
=========
+3.0.21: 2022-11-25
+------------------
+
+New features:
+- Make ptipython respect more config changes.
+ (See: https://github.com/prompt-toolkit/ptpython/pull/110 )
+- Improved performance of `DictionaryCompleter` for slow mappings.
+
+Fixes:
+- Call `super()` in `PythonInputFilter`. This will prevent potentially breakage
+ with an upcoming prompt_toolkit change.
+ (See: https://github.com/prompt-toolkit/python-prompt-toolkit/pull/1690 )
+- Improved type annotations.
+- Added `py.typed` to the `package_data`.
+
+
3.0.20: 2021-09-14
------------------
diff --git a/PKG-INFO b/PKG-INFO
deleted file mode 100644
index 9446457..0000000
--- a/PKG-INFO
+++ /dev/null
@@ -1,263 +0,0 @@
-Metadata-Version: 2.1
-Name: ptpython
-Version: 3.0.20
-Summary: Python REPL build on top of prompt_toolkit
-Home-page: https://github.com/prompt-toolkit/ptpython
-Author: Jonathan Slenders
-License: UNKNOWN
-Description: 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 <https://cirw.in/blog/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"
- <https://python-prompt-toolkit.readthedocs.io/en/master/pages/printing_text.html>`_
- 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('<yellow>Hello world!</yellow>')
-
- 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
- <https://docs.python.org/3/tutorial/appendix.html#the-interactive-startup-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
- <https://pypi.org/project/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 <https://github.com/jonathanslenders/ptpython/blob/master/examples/ptpython_config/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 <https://github.com/django-extensions/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
- <https://github.com/jonathanslenders/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 <http://bpython-interpreter.org/downloads.html>`_
- - `IPython <https://ipython.org/>`_
-
- 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 <http://pygments.org/>`_: Syntax highlighter.
- - `Jedi <http://jedi.jedidjah.ch/en/latest/>`_: Autocompletion library.
- - `wcwidth <https://github.com/jquast/wcwidth>`_: Determine columns needed for a wide characters.
- - `prompt_toolkit <http://github.com/jonathanslenders/python-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
-
-Platform: UNKNOWN
-Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.6
-Classifier: Programming Language :: Python :: 3.7
-Classifier: Programming Language :: Python :: 3.8
-Classifier: Programming Language :: Python :: 3 :: Only
-Classifier: Programming Language :: Python
-Requires-Python: >=3.6
-Provides-Extra: ptipython
-Provides-Extra: all
diff --git a/README.rst b/README.rst
index ae12f4d..15464ba 100644
--- a/README.rst
+++ b/README.rst
@@ -50,6 +50,40 @@ Features
[2] If the terminal supports it (most terminals do), this allows pasting
without going into paste mode. It will keep the indentation.
+Command Line Options
+********************
+
+The help menu shows basic command-line options.
+
+::
+
+ $ ptpython --help
+ usage: ptpython [-h] [--vi] [-i] [--light-bg] [--dark-bg] [--config-file CONFIG_FILE]
+ [--history-file HISTORY_FILE] [-V]
+ [args ...]
+
+ 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)
+
+
__pt_repr__: A nicer repr with colors
*************************************
@@ -109,6 +143,8 @@ like this:
else:
sys.exit(embed(globals(), locals()))
+Note config file support currently only works when invoking `ptpython` directly.
+That it, the config file will be ignored when embedding ptpython in an application.
Multiline editing
*****************
@@ -159,6 +195,9 @@ is looked for.
Have a look at this example to see what is possible:
`config.py <https://github.com/jonathanslenders/ptpython/blob/master/examples/ptpython_config/config.py>`_
+Note config file support currently only works when invoking `ptpython` directly.
+That it, the config file will be ignored when embedding ptpython in an application.
+
IPython support
***************
@@ -211,7 +250,7 @@ FAQ
**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
+**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.
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/examples/ptpython_config/config.py b/examples/ptpython_config/config.py
index 2427572..bf9d05f 100644
--- a/examples/ptpython_config/config.py
+++ b/examples/ptpython_config/config.py
@@ -106,8 +106,13 @@ def configure(repl):
repl.enable_input_validation = True
# Use this colorscheme for the code.
+ # Ptpython uses Pygments for code styling, so you can choose from Pygments'
+ # color schemes. See:
+ # https://pygments.org/docs/styles/
+ # https://pygments.org/demo/
repl.use_code_colorscheme("default")
- # repl.use_code_colorscheme("pastie")
+ # A colorscheme that looks good on dark backgrounds is 'native':
+ # repl.use_code_colorscheme("native")
# Set color depth (keep in mind that not all terminals support true color).
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.egg-info/PKG-INFO b/ptpython.egg-info/PKG-INFO
deleted file mode 100644
index 9446457..0000000
--- a/ptpython.egg-info/PKG-INFO
+++ /dev/null
@@ -1,263 +0,0 @@
-Metadata-Version: 2.1
-Name: ptpython
-Version: 3.0.20
-Summary: Python REPL build on top of prompt_toolkit
-Home-page: https://github.com/prompt-toolkit/ptpython
-Author: Jonathan Slenders
-License: UNKNOWN
-Description: 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 <https://cirw.in/blog/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"
- <https://python-prompt-toolkit.readthedocs.io/en/master/pages/printing_text.html>`_
- 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('<yellow>Hello world!</yellow>')
-
- 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
- <https://docs.python.org/3/tutorial/appendix.html#the-interactive-startup-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
- <https://pypi.org/project/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 <https://github.com/jonathanslenders/ptpython/blob/master/examples/ptpython_config/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 <https://github.com/django-extensions/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
- <https://github.com/jonathanslenders/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 <http://bpython-interpreter.org/downloads.html>`_
- - `IPython <https://ipython.org/>`_
-
- 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 <http://pygments.org/>`_: Syntax highlighter.
- - `Jedi <http://jedi.jedidjah.ch/en/latest/>`_: Autocompletion library.
- - `wcwidth <https://github.com/jquast/wcwidth>`_: Determine columns needed for a wide characters.
- - `prompt_toolkit <http://github.com/jonathanslenders/python-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
-
-Platform: UNKNOWN
-Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.6
-Classifier: Programming Language :: Python :: 3.7
-Classifier: Programming Language :: Python :: 3.8
-Classifier: Programming Language :: Python :: 3 :: Only
-Classifier: Programming Language :: Python
-Requires-Python: >=3.6
-Provides-Extra: ptipython
-Provides-Extra: all
diff --git a/ptpython.egg-info/SOURCES.txt b/ptpython.egg-info/SOURCES.txt
deleted file mode 100644
index 01ce1c4..0000000
--- a/ptpython.egg-info/SOURCES.txt
+++ /dev/null
@@ -1,54 +0,0 @@
-.gitignore
-CHANGELOG
-LICENSE
-MANIFEST.in
-README.rst
-pyproject.toml
-setup.cfg
-setup.py
-docs/images/example1.png
-docs/images/file-completion.png
-docs/images/ipython.png
-docs/images/multiline.png
-docs/images/ptpython-history-help.png
-docs/images/ptpython-menu.png
-docs/images/ptpython.png
-docs/images/validation.png
-docs/images/windows.png
-examples/asyncio-python-embed.py
-examples/asyncio-ssh-python-embed.py
-examples/python-embed-with-custom-prompt.py
-examples/python-embed.py
-examples/python-input.py
-examples/ssh-and-telnet-embed.py
-examples/ptpython_config/config.py
-examples/test-cases/ptpython-in-other-thread.py
-ptpython/__init__.py
-ptpython/__main__.py
-ptpython/completer.py
-ptpython/eventloop.py
-ptpython/filters.py
-ptpython/history_browser.py
-ptpython/ipython.py
-ptpython/key_bindings.py
-ptpython/layout.py
-ptpython/lexer.py
-ptpython/prompt_style.py
-ptpython/python_input.py
-ptpython/repl.py
-ptpython/signatures.py
-ptpython/style.py
-ptpython/utils.py
-ptpython/validator.py
-ptpython.egg-info/PKG-INFO
-ptpython.egg-info/SOURCES.txt
-ptpython.egg-info/dependency_links.txt
-ptpython.egg-info/entry_points.txt
-ptpython.egg-info/requires.txt
-ptpython.egg-info/top_level.txt
-ptpython/contrib/__init__.py
-ptpython/contrib/asyncssh_repl.py
-ptpython/entry_points/__init__.py
-ptpython/entry_points/run_ptipython.py
-ptpython/entry_points/run_ptpython.py
-tests/run_tests.py \ No newline at end of file
diff --git a/ptpython.egg-info/dependency_links.txt b/ptpython.egg-info/dependency_links.txt
deleted file mode 100644
index 8b13789..0000000
--- a/ptpython.egg-info/dependency_links.txt
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/ptpython.egg-info/entry_points.txt b/ptpython.egg-info/entry_points.txt
deleted file mode 100644
index c8c4061..0000000
--- a/ptpython.egg-info/entry_points.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-[console_scripts]
-ptipython = ptpython.entry_points.run_ptipython:run
-ptipython3 = ptpython.entry_points.run_ptipython:run
-ptipython3.9 = ptpython.entry_points.run_ptipython:run
-ptpython = ptpython.entry_points.run_ptpython:run
-ptpython3 = ptpython.entry_points.run_ptpython:run
-ptpython3.9 = ptpython.entry_points.run_ptpython:run
-
diff --git a/ptpython.egg-info/requires.txt b/ptpython.egg-info/requires.txt
deleted file mode 100644
index 9cf7d55..0000000
--- a/ptpython.egg-info/requires.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-appdirs
-jedi>=0.16.0
-prompt_toolkit<3.1.0,>=3.0.18
-pygments
-
-[:python_version < "3.8"]
-importlib_metadata
-
-[all]
-black
-
-[ptipython]
-ipython
diff --git a/ptpython.egg-info/top_level.txt b/ptpython.egg-info/top_level.txt
deleted file mode 100644
index 5af80c7..0000000
--- a/ptpython.egg-info/top_level.txt
+++ /dev/null
@@ -1 +0,0 @@
-ptpython
diff --git a/ptpython/completer.py b/ptpython/completer.py
index 51a4086..2b6795d 100644
--- a/ptpython/completer.py
+++ b/ptpython/completer.py
@@ -4,7 +4,7 @@ import inspect
import keyword
import re
from enum import Enum
-from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, List, Optional
+from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, List, Optional, Tuple
from prompt_toolkit.completion import (
CompleteEvent,
@@ -21,6 +21,7 @@ from prompt_toolkit.formatted_text import fragment_list_to_text, to_formatted_te
from ptpython.utils import get_jedi_script_from_document
if TYPE_CHECKING:
+ import jedi.api.classes
from prompt_toolkit.contrib.regular_languages.compiler import _CompiledGrammar
__all__ = ["PythonCompleter", "CompletePrivateAttributes", "HidePrivateCompleter"]
@@ -43,8 +44,8 @@ class PythonCompleter(Completer):
def __init__(
self,
- get_globals: Callable[[], dict],
- get_locals: Callable[[], dict],
+ get_globals: Callable[[], Dict[str, Any]],
+ get_locals: Callable[[], Dict[str, Any]],
enable_dictionary_completion: Callable[[], bool],
) -> None:
super().__init__()
@@ -200,7 +201,11 @@ class JediCompleter(Completer):
Autocompleter that uses the Jedi library.
"""
- def __init__(self, get_globals, get_locals) -> None:
+ def __init__(
+ self,
+ get_globals: Callable[[], Dict[str, Any]],
+ get_locals: Callable[[], Dict[str, Any]],
+ ) -> None:
super().__init__()
self.get_globals = get_globals
@@ -296,7 +301,11 @@ class DictionaryCompleter(Completer):
function calls, so it only triggers attribute access.
"""
- def __init__(self, get_globals, get_locals):
+ def __init__(
+ self,
+ get_globals: Callable[[], Dict[str, Any]],
+ get_locals: Callable[[], Dict[str, Any]],
+ ) -> None:
super().__init__()
self.get_globals = get_globals
@@ -495,7 +504,7 @@ class DictionaryCompleter(Completer):
else:
break
- for k in result:
+ for k, v in result.items():
if str(k).startswith(str(key_obj)):
try:
k_repr = self._do_repr(k)
@@ -503,7 +512,7 @@ class DictionaryCompleter(Completer):
k_repr + "]",
-len(key),
display=f"[{k_repr}]",
- display_meta=abbr_meta(self._do_repr(result[k])),
+ display_meta=abbr_meta(self._do_repr(v)),
)
except KeyError:
# `result[k]` lookup failed. Trying to complete
@@ -574,7 +583,7 @@ class DictionaryCompleter(Completer):
underscore names to the end.
"""
- def sort_key(name: str):
+ def sort_key(name: str) -> Tuple[int, str]:
if name.startswith("__"):
return (2, name) # Double underscore comes latest.
if name.startswith("_"):
@@ -639,7 +648,9 @@ except ImportError: # Python 2.
_builtin_names = []
-def _get_style_for_jedi_completion(jedi_completion) -> str:
+def _get_style_for_jedi_completion(
+ jedi_completion: "jedi.api.classes.Completion",
+) -> str:
"""
Return completion style to use for this name.
"""
diff --git a/ptpython/entry_points/run_ptpython.py b/ptpython/entry_points/run_ptpython.py
index 5ebe2b9..edffa44 100644
--- a/ptpython/entry_points/run_ptpython.py
+++ b/ptpython/entry_points/run_ptpython.py
@@ -26,16 +26,16 @@ import os
import pathlib
import sys
from textwrap import dedent
-from typing import Tuple
+from typing import IO, Optional, 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
+from ptpython.repl import PythonRepl, embed, enable_deprecation_warnings, run_config
try:
- from importlib import metadata
+ from importlib import metadata # type: ignore
except ImportError:
import importlib_metadata as metadata # type: ignore
@@ -44,7 +44,7 @@ __all__ = ["create_parser", "get_config_and_history_file", "run"]
class _Parser(argparse.ArgumentParser):
- def print_help(self):
+ def print_help(self, file: Optional[IO[str]] = None) -> None:
super().print_help()
print(
dedent(
@@ -84,7 +84,7 @@ def create_parser() -> _Parser:
"-V",
"--version",
action="version",
- version=metadata.version("ptpython"), # type: ignore
+ version=metadata.version("ptpython"),
)
parser.add_argument("args", nargs="*", help="Script and arguments")
return parser
@@ -190,7 +190,7 @@ def run() -> None:
enable_deprecation_warnings()
# Apply config file
- def configure(repl) -> None:
+ def configure(repl: PythonRepl) -> None:
if os.path.exists(config_file):
run_config(repl, config_file)
diff --git a/ptpython/eventloop.py b/ptpython/eventloop.py
index c841972..63dd740 100644
--- a/ptpython/eventloop.py
+++ b/ptpython/eventloop.py
@@ -10,10 +10,12 @@ will fix it for Tk.)
import sys
import time
+from prompt_toolkit.eventloop import InputHookContext
+
__all__ = ["inputhook"]
-def _inputhook_tk(inputhook_context):
+def _inputhook_tk(inputhook_context: InputHookContext) -> None:
"""
Inputhook for Tk.
Run the Tk eventloop until prompt-toolkit needs to process the next input.
@@ -23,9 +25,9 @@ def _inputhook_tk(inputhook_context):
import _tkinter # Keep this imports inline!
- root = tkinter._default_root
+ root = tkinter._default_root # type: ignore
- def wait_using_filehandler():
+ def wait_using_filehandler() -> None:
"""
Run the TK eventloop until the file handler that we got from the
inputhook becomes readable.
@@ -34,7 +36,7 @@ def _inputhook_tk(inputhook_context):
# to process.
stop = [False]
- def done(*a):
+ def done(*a: object) -> None:
stop[0] = True
root.createfilehandler(inputhook_context.fileno(), _tkinter.READABLE, done)
@@ -46,7 +48,7 @@ def _inputhook_tk(inputhook_context):
root.deletefilehandler(inputhook_context.fileno())
- def wait_using_polling():
+ def wait_using_polling() -> None:
"""
Windows TK doesn't support 'createfilehandler'.
So, run the TK eventloop and poll until input is ready.
@@ -65,7 +67,7 @@ def _inputhook_tk(inputhook_context):
wait_using_polling()
-def inputhook(inputhook_context):
+def inputhook(inputhook_context: InputHookContext) -> None:
# 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
index 1adac13..be85edf 100644
--- a/ptpython/filters.py
+++ b/ptpython/filters.py
@@ -10,6 +10,7 @@ __all__ = ["HasSignature", "ShowSidebar", "ShowSignature", "ShowDocstring"]
class PythonInputFilter(Filter):
def __init__(self, python_input: "PythonInput") -> None:
+ super().__init__()
self.python_input = python_input
def __call__(self) -> bool:
diff --git a/ptpython/history_browser.py b/ptpython/history_browser.py
index b7fe086..08725ee 100644
--- a/ptpython/history_browser.py
+++ b/ptpython/history_browser.py
@@ -5,6 +5,7 @@ Utility to easily select lines from the history and execute them again.
run as a sub application of the Repl/PythonInput.
"""
from functools import partial
+from typing import TYPE_CHECKING, Callable, List, Optional, Set
from prompt_toolkit.application import Application
from prompt_toolkit.application.current import get_app
@@ -12,8 +13,11 @@ 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.base import StyleAndTextTuples
from prompt_toolkit.formatted_text.utils import fragment_list_to_text
+from prompt_toolkit.history import History
from prompt_toolkit.key_binding import KeyBindings
+from prompt_toolkit.key_binding.key_processor import KeyPressEvent
from prompt_toolkit.layout.containers import (
ConditionalContainer,
Container,
@@ -24,13 +28,23 @@ from prompt_toolkit.layout.containers import (
VSplit,
Window,
WindowAlign,
+ WindowRenderInfo,
+)
+from prompt_toolkit.layout.controls import (
+ BufferControl,
+ FormattedTextControl,
+ UIContent,
)
-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.layout.processors import (
+ Processor,
+ Transformation,
+ TransformationInput,
+)
from prompt_toolkit.lexers import PygmentsLexer
+from prompt_toolkit.mouse_events import MouseEvent
from prompt_toolkit.widgets import Frame
from prompt_toolkit.widgets.toolbars import ArgToolbar, SearchToolbar
from pygments.lexers import Python3Lexer as PythonLexer
@@ -40,10 +54,15 @@ from ptpython.layout import get_inputmode_fragments
from .utils import if_mousedown
+if TYPE_CHECKING:
+ from .python_input import PythonInput
+
HISTORY_COUNT = 2000
__all__ = ["HistoryLayout", "PythonHistory"]
+E = KeyPressEvent
+
HELP_TEXT = """
This interface is meant to select multiple lines from the
history and execute them together.
@@ -109,7 +128,7 @@ class HistoryLayout:
application.
"""
- def __init__(self, history):
+ def __init__(self, history: "PythonHistory") -> None:
search_toolbar = SearchToolbar()
self.help_buffer_control = BufferControl(
@@ -201,19 +220,19 @@ class HistoryLayout:
self.layout = Layout(self.root_container, history_window)
-def _get_top_toolbar_fragments():
+def _get_top_toolbar_fragments() -> StyleAndTextTuples:
return [("class:status-bar.title", "History browser - Insert from history")]
-def _get_bottom_toolbar_fragments(history):
+def _get_bottom_toolbar_fragments(history: "PythonHistory") -> StyleAndTextTuples:
python_input = history.python_input
@if_mousedown
- def f1(mouse_event):
+ def f1(mouse_event: MouseEvent) -> None:
_toggle_help(history)
@if_mousedown
- def tab(mouse_event):
+ def tab(mouse_event: MouseEvent) -> None:
_select_other_window(history)
return (
@@ -239,14 +258,16 @@ class HistoryMargin(Margin):
This displays a green bar for the selected entries.
"""
- def __init__(self, history):
+ def __init__(self, history: "PythonHistory") -> None:
self.history_buffer = history.history_buffer
self.history_mapping = history.history_mapping
- def get_width(self, ui_content):
+ def get_width(self, get_ui_content: Callable[[], UIContent]) -> int:
return 2
- def create_margin(self, window_render_info, width, height):
+ def create_margin(
+ self, window_render_info: WindowRenderInfo, width: int, height: int
+ ) -> StyleAndTextTuples:
document = self.history_buffer.document
lines_starting_new_entries = self.history_mapping.lines_starting_new_entries
@@ -255,7 +276,7 @@ class HistoryMargin(Margin):
current_lineno = document.cursor_position_row
visible_line_to_input_line = window_render_info.visible_line_to_input_line
- result = []
+ result: StyleAndTextTuples = []
for y in range(height):
line_number = visible_line_to_input_line.get(y)
@@ -286,14 +307,16 @@ class ResultMargin(Margin):
The margin to be shown in the result pane.
"""
- def __init__(self, history):
+ def __init__(self, history: "PythonHistory") -> None:
self.history_mapping = history.history_mapping
self.history_buffer = history.history_buffer
- def get_width(self, ui_content):
+ def get_width(self, get_ui_content: Callable[[], UIContent]) -> int:
return 2
- def create_margin(self, window_render_info, width, height):
+ def create_margin(
+ self, window_render_info: WindowRenderInfo, width: int, height: int
+ ) -> StyleAndTextTuples:
document = self.history_buffer.document
current_lineno = document.cursor_position_row
@@ -303,7 +326,7 @@ class ResultMargin(Margin):
visible_line_to_input_line = window_render_info.visible_line_to_input_line
- result = []
+ result: StyleAndTextTuples = []
for y in range(height):
line_number = visible_line_to_input_line.get(y)
@@ -324,7 +347,7 @@ class ResultMargin(Margin):
return result
- def invalidation_hash(self, document):
+ def invalidation_hash(self, document: Document) -> int:
return document.cursor_position_row
@@ -333,13 +356,15 @@ class GrayExistingText(Processor):
Turn the existing input, before and after the inserted code gray.
"""
- def __init__(self, history_mapping):
+ def __init__(self, history_mapping: "HistoryMapping") -> None:
self.history_mapping = history_mapping
self._lines_before = len(
history_mapping.original_document.text_before_cursor.splitlines()
)
- def apply_transformation(self, transformation_input):
+ def apply_transformation(
+ self, transformation_input: TransformationInput
+ ) -> Transformation:
lineno = transformation_input.lineno
fragments = transformation_input.fragments
@@ -357,17 +382,22 @@ class HistoryMapping:
Keep a list of all the lines from the history and the selected lines.
"""
- def __init__(self, history, python_history, original_document):
+ def __init__(
+ self,
+ history: "PythonHistory",
+ python_history: History,
+ original_document: Document,
+ ) -> None:
self.history = history
self.python_history = python_history
self.original_document = original_document
self.lines_starting_new_entries = set()
- self.selected_lines = set()
+ self.selected_lines: Set[int] = set()
# Process history.
history_strings = python_history.get_strings()
- history_lines = []
+ history_lines: List[str] = []
for entry_nr, entry in list(enumerate(history_strings))[-HISTORY_COUNT:]:
self.lines_starting_new_entries.add(len(history_lines))
@@ -389,7 +419,7 @@ class HistoryMapping:
else:
self.result_line_offset = 0
- def get_new_document(self, cursor_pos=None):
+ def get_new_document(self, cursor_pos: Optional[int] = None) -> Document:
"""
Create a `Document` instance that contains the resulting text.
"""
@@ -413,13 +443,13 @@ class HistoryMapping:
cursor_pos = len(text)
return Document(text, cursor_pos)
- def update_default_buffer(self):
+ def update_default_buffer(self) -> None:
b = self.history.default_buffer
b.set_document(self.get_new_document(b.cursor_position), bypass_readonly=True)
-def _toggle_help(history):
+def _toggle_help(history: "PythonHistory") -> None:
"Display/hide help."
help_buffer_control = history.history_layout.help_buffer_control
@@ -429,7 +459,7 @@ def _toggle_help(history):
history.app.layout.current_control = help_buffer_control
-def _select_other_window(history):
+def _select_other_window(history: "PythonHistory") -> None:
"Toggle focus between left/right window."
current_buffer = history.app.current_buffer
layout = history.history_layout.layout
@@ -441,7 +471,11 @@ def _select_other_window(history):
layout.current_control = history.history_layout.history_buffer_control
-def create_key_bindings(history, python_input, history_mapping):
+def create_key_bindings(
+ history: "PythonHistory",
+ python_input: "PythonInput",
+ history_mapping: HistoryMapping,
+) -> KeyBindings:
"""
Key bindings.
"""
@@ -449,7 +483,7 @@ def create_key_bindings(history, python_input, history_mapping):
handle = bindings.add
@handle(" ", filter=has_focus(history.history_buffer))
- def _(event):
+ def _(event: E) -> None:
"""
Space: select/deselect line from history pane.
"""
@@ -486,7 +520,7 @@ def create_key_bindings(history, python_input, history_mapping):
@handle(" ", filter=has_focus(DEFAULT_BUFFER))
@handle("delete", filter=has_focus(DEFAULT_BUFFER))
@handle("c-h", filter=has_focus(DEFAULT_BUFFER))
- def _(event):
+ def _(event: E) -> None:
"""
Space: remove line from default pane.
"""
@@ -512,17 +546,17 @@ def create_key_bindings(history, python_input, history_mapping):
@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):
+ def _(event: E) -> None:
"Select other window."
_select_other_window(history)
@handle("f4")
- def _(event):
+ def _(event: E) -> None:
"Switch between Emacs/Vi mode."
python_input.vi_mode = not python_input.vi_mode
@handle("f1")
- def _(event):
+ def _(event: E) -> None:
"Display/hide help."
_toggle_help(history)
@@ -530,7 +564,7 @@ def create_key_bindings(history, python_input, history_mapping):
@handle("c-c", filter=help_focussed)
@handle("c-g", filter=help_focussed)
@handle("escape", filter=help_focussed)
- def _(event):
+ def _(event: E) -> None:
"Leave help."
event.app.layout.focus_previous()
@@ -538,19 +572,19 @@ def create_key_bindings(history, python_input, history_mapping):
@handle("f3", filter=main_buffer_focussed)
@handle("c-c", filter=main_buffer_focussed)
@handle("c-g", filter=main_buffer_focussed)
- def _(event):
+ def _(event: E) -> None:
"Cancel and go back."
event.app.exit(result=None)
@handle("enter", filter=main_buffer_focussed)
- def _(event):
+ def _(event: E) -> None:
"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):
+ def _(event: E) -> None:
"Suspend to background."
event.app.suspend_to_background()
@@ -558,7 +592,9 @@ def create_key_bindings(history, python_input, history_mapping):
class PythonHistory:
- def __init__(self, python_input, original_document):
+ def __init__(
+ self, python_input: "PythonInput", original_document: Document
+ ) -> None:
"""
Create an `Application` for the history screen.
This has to be run as a sub application of `python_input`.
@@ -577,12 +613,14 @@ class PythonHistory:
+ document.get_start_of_line_position(),
)
+ def accept_handler(buffer: Buffer) -> bool:
+ get_app().exit(result=self.default_buffer.text)
+ return False
+
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)
- ),
+ accept_handler=accept_handler,
read_only=True,
)
@@ -597,7 +635,7 @@ class PythonHistory:
self.history_layout = HistoryLayout(self)
- self.app = Application(
+ self.app: Application[str] = Application(
layout=self.history_layout.layout,
full_screen=True,
style=python_input._current_style,
@@ -605,7 +643,7 @@ class PythonHistory:
key_bindings=create_key_bindings(self, python_input, history_mapping),
)
- def _default_buffer_pos_changed(self, _):
+ def _default_buffer_pos_changed(self, _: Buffer) -> None:
"""When the cursor changes in the default buffer. Synchronize with
history buffer."""
# Only when this buffer has the focus.
@@ -629,7 +667,7 @@ class PythonHistory:
)
)
- def _history_buffer_pos_changed(self, _):
+ def _history_buffer_pos_changed(self, _: Buffer) -> None:
"""When the cursor changes in the history buffer. Synchronize."""
# Only when this buffer has the focus.
if self.app.current_buffer == self.history_buffer:
diff --git a/ptpython/ipython.py b/ptpython/ipython.py
index 9163334..db2a204 100644
--- a/ptpython/ipython.py
+++ b/ptpython/ipython.py
@@ -8,6 +8,7 @@ also the power of for instance all the %-magic functions that IPython has to
offer.
"""
+from typing import Iterable
from warnings import warn
from IPython import utils as ipy_utils
@@ -15,6 +16,7 @@ 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 (
+ CompleteEvent,
Completer,
Completion,
PathCompleter,
@@ -25,15 +27,17 @@ 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.formatted_text import AnyFormattedText, 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 .completer import PythonCompleter
+from .python_input import PythonInput
from .style import default_ui_style
+from .validator import PythonValidator
__all__ = ["embed"]
@@ -46,13 +50,13 @@ class IPythonPrompt(PromptStyle):
def __init__(self, prompts):
self.prompts = prompts
- def in_prompt(self):
+ def in_prompt(self) -> AnyFormattedText:
return PygmentsTokens(self.prompts.in_prompt_tokens())
- def in2_prompt(self, width):
+ def in2_prompt(self, width: int) -> AnyFormattedText:
return PygmentsTokens(self.prompts.continuation_prompt_tokens())
- def out_prompt(self):
+ def out_prompt(self) -> AnyFormattedText:
return []
@@ -61,7 +65,7 @@ class IPythonValidator(PythonValidator):
super(IPythonValidator, self).__init__(*args, **kwargs)
self.isp = IPythonInputSplitter()
- def validate(self, document):
+ def validate(self, document: Document) -> None:
document = Document(text=self.isp.transform_cell(document.text))
super(IPythonValidator, self).validate(document)
@@ -142,7 +146,9 @@ class MagicsCompleter(Completer):
def __init__(self, magics_manager):
self.magics_manager = magics_manager
- def get_completions(self, document, complete_event):
+ def get_completions(
+ self, document: Document, complete_event: CompleteEvent
+ ) -> Iterable[Completion]:
text = document.text_before_cursor.lstrip()
for m in sorted(self.magics_manager.magics["line"]):
@@ -154,7 +160,9 @@ class AliasCompleter(Completer):
def __init__(self, alias_manager):
self.alias_manager = alias_manager
- def get_completions(self, document, complete_event):
+ def get_completions(
+ self, document: Document, complete_event: CompleteEvent
+ ) -> Iterable[Completion]:
text = document.text_before_cursor.lstrip()
# aliases = [a for a, _ in self.alias_manager.aliases]
aliases = self.alias_manager.aliases
@@ -240,7 +248,7 @@ class InteractiveShellEmbed(_InteractiveShellEmbed):
self.python_input = python_input
- def prompt_for_code(self):
+ def prompt_for_code(self) -> str:
try:
return self.python_input.app.run()
except KeyboardInterrupt:
@@ -269,6 +277,25 @@ def initialize_extensions(shell, extensions):
shell.showtraceback()
+def run_exec_lines(shell, exec_lines):
+ """
+ Partial copy of run_exec_lines code from IPython.core.shellapp .
+ """
+ try:
+ iter(exec_lines)
+ except TypeError:
+ pass
+ else:
+ try:
+ for line in exec_lines:
+ try:
+ shell.run_cell(line, store_history=False)
+ except:
+ shell.showtraceback()
+ except:
+ shell.showtraceback()
+
+
def embed(**kwargs):
"""
Copied from `IPython/terminal/embed.py`, but using our `InteractiveShellEmbed` instead.
@@ -282,6 +309,7 @@ def embed(**kwargs):
kwargs["config"] = config
shell = InteractiveShellEmbed.instance(**kwargs)
initialize_extensions(shell, config["InteractiveShellApp"]["extensions"])
+ run_exec_lines(shell, config["InteractiveShellApp"]["exec_lines"])
run_startup_scripts(shell)
shell(header=header, stack_depth=2, compile_flags=compile_flags)
diff --git a/ptpython/key_bindings.py b/ptpython/key_bindings.py
index ae23a3d..147a321 100644
--- a/ptpython/key_bindings.py
+++ b/ptpython/key_bindings.py
@@ -1,4 +1,7 @@
+from typing import TYPE_CHECKING
+
from prompt_toolkit.application 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 (
@@ -11,19 +14,25 @@ from prompt_toolkit.filters import (
)
from prompt_toolkit.key_binding import KeyBindings
from prompt_toolkit.key_binding.bindings.named_commands import get_by_name
+from prompt_toolkit.key_binding.key_processor import KeyPressEvent
from prompt_toolkit.keys import Keys
from .utils import document_is_multiline_python
+if TYPE_CHECKING:
+ from .python_input import PythonInput
+
__all__ = [
"load_python_bindings",
"load_sidebar_bindings",
"load_confirm_exit_bindings",
]
+E = KeyPressEvent
+
@Condition
-def tab_should_insert_whitespace():
+def tab_should_insert_whitespace() -> bool:
"""
When the 'tab' key is pressed with only whitespace character before the
cursor, do autocompletion. Otherwise, insert indentation.
@@ -38,7 +47,7 @@ def tab_should_insert_whitespace():
return bool(b.text and (not before_cursor or before_cursor.isspace()))
-def load_python_bindings(python_input):
+def load_python_bindings(python_input: "PythonInput") -> KeyBindings:
"""
Custom key bindings.
"""
@@ -48,14 +57,14 @@ def load_python_bindings(python_input):
handle = bindings.add
@handle("c-l")
- def _(event):
+ def _(event: E) -> None:
"""
Clear whole screen and render again -- also when the sidebar is visible.
"""
event.app.renderer.clear()
@handle("c-z")
- def _(event):
+ def _(event: E) -> None:
"""
Suspend.
"""
@@ -67,7 +76,7 @@ def load_python_bindings(python_input):
handle("c-w")(get_by_name("backward-kill-word"))
@handle("f2")
- def _(event):
+ def _(event: E) -> None:
"""
Show/hide sidebar.
"""
@@ -78,21 +87,21 @@ def load_python_bindings(python_input):
event.app.layout.focus_last()
@handle("f3")
- def _(event):
+ def _(event: E) -> None:
"""
Select from the history.
"""
python_input.enter_history()
@handle("f4")
- def _(event):
+ def _(event: E) -> None:
"""
Toggle between Vi and Emacs mode.
"""
python_input.vi_mode = not python_input.vi_mode
@handle("f6")
- def _(event):
+ def _(event: E) -> None:
"""
Enable/Disable paste mode.
"""
@@ -101,14 +110,14 @@ def load_python_bindings(python_input):
@handle(
"tab", filter=~sidebar_visible & ~has_selection & tab_should_insert_whitespace
)
- def _(event):
+ def _(event: E) -> None:
"""
When tab should insert whitespace, do that instead of completion.
"""
event.app.current_buffer.insert_text(" ")
@Condition
- def is_multiline():
+ def is_multiline() -> bool:
return document_is_multiline_python(python_input.default_buffer.document)
@handle(
@@ -120,7 +129,7 @@ def load_python_bindings(python_input):
& ~is_multiline,
)
@handle(Keys.Escape, Keys.Enter, filter=~sidebar_visible & emacs_mode)
- def _(event):
+ def _(event: E) -> None:
"""
Accept input (for single line input).
"""
@@ -143,7 +152,7 @@ def load_python_bindings(python_input):
& has_focus(DEFAULT_BUFFER)
& is_multiline,
)
- def _(event):
+ def _(event: E) -> None:
"""
Behaviour of the Enter key.
@@ -153,11 +162,11 @@ def load_python_bindings(python_input):
b = event.current_buffer
empty_lines_required = python_input.accept_input_on_enter or 10000
- def at_the_end(b):
+ def at_the_end(b: Buffer) -> bool:
"""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)
+ return text == "" or (text.isspace() and "\n" not in text)
if python_input.paste_mode:
# In paste mode, always insert text.
@@ -187,7 +196,7 @@ def load_python_bindings(python_input):
not get_app().current_buffer.text
),
)
- def _(event):
+ def _(event: E) -> None:
"""
Override Control-D exit, to ask for confirmation.
"""
@@ -202,14 +211,14 @@ def load_python_bindings(python_input):
event.app.exit(exception=EOFError)
@handle("c-c", filter=has_focus(python_input.default_buffer))
- def _(event):
+ def _(event: E) -> None:
"Abort when Control-C has been pressed."
event.app.exit(exception=KeyboardInterrupt, style="class:aborting")
return bindings
-def load_sidebar_bindings(python_input):
+def load_sidebar_bindings(python_input: "PythonInput") -> KeyBindings:
"""
Load bindings for the navigation in the sidebar.
"""
@@ -221,7 +230,7 @@ def load_sidebar_bindings(python_input):
@handle("up", filter=sidebar_visible)
@handle("c-p", filter=sidebar_visible)
@handle("k", filter=sidebar_visible)
- def _(event):
+ def _(event: E) -> None:
"Go to previous option."
python_input.selected_option_index = (
python_input.selected_option_index - 1
@@ -230,7 +239,7 @@ def load_sidebar_bindings(python_input):
@handle("down", filter=sidebar_visible)
@handle("c-n", filter=sidebar_visible)
@handle("j", filter=sidebar_visible)
- def _(event):
+ def _(event: E) -> None:
"Go to next option."
python_input.selected_option_index = (
python_input.selected_option_index + 1
@@ -239,14 +248,14 @@ def load_sidebar_bindings(python_input):
@handle("right", filter=sidebar_visible)
@handle("l", filter=sidebar_visible)
@handle(" ", filter=sidebar_visible)
- def _(event):
+ def _(event: E) -> None:
"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):
+ def _(event: E) -> None:
"Select previous value for current option."
option = python_input.selected_option
option.activate_previous()
@@ -256,7 +265,7 @@ def load_sidebar_bindings(python_input):
@handle("c-d", filter=sidebar_visible)
@handle("enter", filter=sidebar_visible)
@handle("escape", filter=sidebar_visible)
- def _(event):
+ def _(event: E) -> None:
"Hide sidebar."
python_input.show_sidebar = False
event.app.layout.focus_last()
@@ -264,7 +273,7 @@ def load_sidebar_bindings(python_input):
return bindings
-def load_confirm_exit_bindings(python_input):
+def load_confirm_exit_bindings(python_input: "PythonInput") -> KeyBindings:
"""
Handle yes/no key presses when the exit confirmation is shown.
"""
@@ -277,14 +286,14 @@ def load_confirm_exit_bindings(python_input):
@handle("Y", filter=confirmation_visible)
@handle("enter", filter=confirmation_visible)
@handle("c-d", filter=confirmation_visible)
- def _(event):
+ def _(event: E) -> None:
"""
Really quit.
"""
event.app.exit(exception=EOFError, style="class:exiting")
@handle(Keys.Any, filter=confirmation_visible)
- def _(event):
+ def _(event: E) -> None:
"""
Cancel exit.
"""
@@ -294,7 +303,7 @@ def load_confirm_exit_bindings(python_input):
return bindings
-def auto_newline(buffer):
+def auto_newline(buffer: Buffer) -> None:
r"""
Insert \n at the cursor position. Also add necessary padding.
"""
diff --git a/ptpython/layout.py b/ptpython/layout.py
index dc6b19b..365f381 100644
--- a/ptpython/layout.py
+++ b/ptpython/layout.py
@@ -5,7 +5,7 @@ import platform
import sys
from enum import Enum
from inspect import _ParameterKind as ParameterKind
-from typing import TYPE_CHECKING, Optional
+from typing import TYPE_CHECKING, Any, List, Optional, Type
from prompt_toolkit.application import get_app
from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER
@@ -15,10 +15,15 @@ from prompt_toolkit.filters import (
is_done,
renderer_height_is_known,
)
-from prompt_toolkit.formatted_text import fragment_list_width, to_formatted_text
+from prompt_toolkit.formatted_text import (
+ AnyFormattedText,
+ 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 (
+ AnyContainer,
ConditionalContainer,
Container,
Float,
@@ -40,9 +45,10 @@ from prompt_toolkit.layout.processors import (
HighlightIncrementalSearchProcessor,
HighlightMatchingBracketProcessor,
HighlightSelectionProcessor,
+ Processor,
TabsProcessor,
)
-from prompt_toolkit.lexers import SimpleLexer
+from prompt_toolkit.lexers import Lexer, SimpleLexer
from prompt_toolkit.mouse_events import MouseEvent
from prompt_toolkit.selection import SelectionType
from prompt_toolkit.widgets.toolbars import (
@@ -55,6 +61,7 @@ from prompt_toolkit.widgets.toolbars import (
from pygments.lexers import PythonLexer
from .filters import HasSignature, ShowDocstring, ShowSidebar, ShowSignature
+from .prompt_style import PromptStyle
from .utils import if_mousedown
if TYPE_CHECKING:
@@ -98,7 +105,7 @@ def python_sidebar(python_input: "PythonInput") -> Window:
def get_text_fragments() -> StyleAndTextTuples:
tokens: StyleAndTextTuples = []
- def append_category(category: "OptionCategory") -> None:
+ def append_category(category: "OptionCategory[Any]") -> None:
tokens.extend(
[
("class:sidebar", " "),
@@ -150,10 +157,10 @@ def python_sidebar(python_input: "PythonInput") -> Window:
return tokens
class Control(FormattedTextControl):
- def move_cursor_down(self):
+ def move_cursor_down(self) -> None:
python_input.selected_option_index += 1
- def move_cursor_up(self):
+ def move_cursor_up(self) -> None:
python_input.selected_option_index -= 1
return Window(
@@ -165,12 +172,12 @@ def python_sidebar(python_input: "PythonInput") -> Window:
)
-def python_sidebar_navigation(python_input):
+def python_sidebar_navigation(python_input: "PythonInput") -> Window:
"""
Create the `Layout` showing the navigation information for the sidebar.
"""
- def get_text_fragments():
+ def get_text_fragments() -> StyleAndTextTuples:
# Show navigation info.
return [
("class:sidebar", " "),
@@ -191,13 +198,13 @@ def python_sidebar_navigation(python_input):
)
-def python_sidebar_help(python_input):
+def python_sidebar_help(python_input: "PythonInput") -> Container:
"""
Create the `Layout` for the help text for the current item in the sidebar.
"""
token = "class:sidebar.helptext"
- def get_current_description():
+ def get_current_description() -> str:
"""
Return the description of the selected option.
"""
@@ -209,7 +216,7 @@ def python_sidebar_help(python_input):
i += 1
return ""
- def get_help_text():
+ def get_help_text() -> StyleAndTextTuples:
return [(token, get_current_description())]
return ConditionalContainer(
@@ -225,7 +232,7 @@ def python_sidebar_help(python_input):
)
-def signature_toolbar(python_input):
+def signature_toolbar(python_input: "PythonInput") -> Container:
"""
Return the `Layout` for the signature.
"""
@@ -311,21 +318,23 @@ class PythonPromptMargin(PromptMargin):
It shows something like "In [1]:".
"""
- def __init__(self, python_input) -> None:
+ def __init__(self, python_input: "PythonInput") -> None:
self.python_input = python_input
- def get_prompt_style():
+ def get_prompt_style() -> PromptStyle:
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):
+ def get_continuation(
+ width: int, line_number: int, is_soft_wrap: bool
+ ) -> StyleAndTextTuples:
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)
+ return to_formatted_text(get_prompt_style().in2_prompt(width))
super().__init__(get_prompt, get_continuation)
@@ -510,7 +519,7 @@ def show_sidebar_button_info(python_input: "PythonInput") -> Container:
def create_exit_confirmation(
- python_input: "PythonInput", style="class:exit-confirmation"
+ python_input: "PythonInput", style: str = "class:exit-confirmation"
) -> Container:
"""
Create `Layout` for the exit message.
@@ -567,22 +576,22 @@ class PtPythonLayout:
def __init__(
self,
python_input: "PythonInput",
- lexer=PythonLexer,
- extra_body=None,
- extra_toolbars=None,
- extra_buffer_processors=None,
+ lexer: Lexer,
+ extra_body: Optional[AnyContainer] = None,
+ extra_toolbars: Optional[List[AnyContainer]] = None,
+ extra_buffer_processors: Optional[List[Processor]] = None,
input_buffer_height: Optional[AnyDimension] = None,
) -> None:
D = Dimension
- extra_body = [extra_body] if extra_body else []
+ extra_body_list: List[AnyContainer] = [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():
+ def create_python_input_window() -> Window:
+ def menu_position() -> Optional[int]:
"""
When there is no autocompletion menu to be shown, and we have a
signature, set the pop-up position at `bracket_start`.
@@ -593,6 +602,7 @@ class PtPythonLayout:
row, col = python_input.signatures[0].bracket_start
index = b.document.translate_row_col_to_index(row - 1, col)
return index
+ return None
return Window(
BufferControl(
@@ -622,7 +632,7 @@ class PtPythonLayout:
processor=AppendAutoSuggestion(), filter=~is_done
),
]
- + extra_buffer_processors,
+ + (extra_buffer_processors or []),
menu_position=menu_position,
# Make sure that we always see the result of an reverse-i-search:
preview_search=True,
@@ -654,7 +664,7 @@ class PtPythonLayout:
[
FloatContainer(
content=HSplit(
- [create_python_input_window()] + extra_body
+ [create_python_input_window()] + extra_body_list
),
floats=[
Float(
diff --git a/ptpython/py.typed b/ptpython/py.typed
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/ptpython/py.typed
diff --git a/ptpython/python_input.py b/ptpython/python_input.py
index 1785f52..c561117 100644
--- a/ptpython/python_input.py
+++ b/ptpython/python_input.py
@@ -6,7 +6,18 @@ import __future__
from asyncio import get_event_loop
from functools import partial
-from typing import TYPE_CHECKING, Any, Callable, Dict, Generic, List, Optional, TypeVar
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Callable,
+ Dict,
+ Generic,
+ List,
+ Mapping,
+ Optional,
+ Tuple,
+ TypeVar,
+)
from prompt_toolkit.application import Application, get_app
from prompt_toolkit.auto_suggest import (
@@ -44,6 +55,7 @@ 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.layout.containers import AnyContainer
from prompt_toolkit.lexers import DynamicLexer, Lexer, SimpleLexer
from prompt_toolkit.output import ColorDepth, Output
from prompt_toolkit.styles import (
@@ -88,8 +100,8 @@ if TYPE_CHECKING:
_T = TypeVar("_T", bound="_SupportsLessThan")
-class OptionCategory:
- def __init__(self, title: str, options: List["Option"]) -> None:
+class OptionCategory(Generic[_T]):
+ def __init__(self, title: str, options: List["Option[_T]"]) -> None:
self.title = title
self.options = options
@@ -113,7 +125,7 @@ class Option(Generic[_T]):
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]]],
+ get_values: Callable[[], Mapping[_T, Callable[[], object]]],
) -> None:
self.title = title
self.description = description
@@ -121,7 +133,7 @@ class Option(Generic[_T]):
self.get_values = get_values
@property
- def values(self) -> Dict[_T, Callable[[], object]]:
+ def values(self) -> Mapping[_T, Callable[[], object]]:
return self.get_values()
def activate_next(self, _previous: bool = False) -> None:
@@ -192,12 +204,12 @@ class PythonInput:
output: Optional[Output] = None,
# For internal use.
extra_key_bindings: Optional[KeyBindings] = None,
- create_app=True,
+ create_app: bool = True,
_completer: Optional[Completer] = None,
_validator: Optional[Validator] = None,
_lexer: Optional[Lexer] = None,
_extra_buffer_processors=None,
- _extra_layout_body=None,
+ _extra_layout_body: Optional[AnyContainer] = None,
_extra_toolbars=None,
_input_buffer_height=None,
) -> None:
@@ -239,7 +251,7 @@ class PythonInput:
self.history = InMemoryHistory()
self._input_buffer_height = _input_buffer_height
- self._extra_layout_body = _extra_layout_body or []
+ self._extra_layout_body = _extra_layout_body
self._extra_toolbars = _extra_toolbars or []
self._extra_buffer_processors = _extra_buffer_processors or []
@@ -388,7 +400,9 @@ class PythonInput:
# Create an app if requested. If not, the global get_app() is returned
# for self.app via property getter.
if create_app:
- self._app: Optional[Application] = self._create_application(input, output)
+ self._app: Optional[Application[str]] = self._create_application(
+ input, output
+ )
# Setting vi_mode will not work unless the prompt_toolkit
# application has been created.
if vi_mode:
@@ -408,7 +422,7 @@ class PythonInput:
return sum(len(category.options) for category in self.options)
@property
- def selected_option(self) -> Option:
+ def selected_option(self) -> Option[Any]:
"Return the currently selected option."
i = 0
for category in self.options:
@@ -514,7 +528,7 @@ class PythonInput:
self.ui_styles[self._current_ui_style_name],
)
- def _create_options(self) -> List[OptionCategory]:
+ def _create_options(self) -> List[OptionCategory[Any]]:
"""
Create a list of `Option` instances for the options sidebar.
"""
@@ -530,15 +544,17 @@ class PythonInput:
return True
def simple_option(
- title: str, description: str, field_name: str, values: Optional[List] = None
- ) -> Option:
+ title: str,
+ description: str,
+ field_name: str,
+ values: Tuple[str, str] = ("off", "on"),
+ ) -> Option[str]:
"Create Simple on/of option."
- values = values or ["off", "on"]
- def get_current_value():
+ def get_current_value() -> str:
return values[bool(getattr(self, field_name))]
- def get_values():
+ def get_values() -> Dict[str, Callable[[], bool]]:
return {
values[1]: lambda: enable(field_name),
values[0]: lambda: disable(field_name),
@@ -848,7 +864,7 @@ class PythonInput:
def _create_application(
self, input: Optional[Input], output: Optional[Output]
- ) -> Application:
+ ) -> Application[str]:
"""
Create an `Application` instance.
"""
@@ -926,7 +942,7 @@ class PythonInput:
self.editing_mode = EditingMode.EMACS
@property
- def app(self) -> Application:
+ def app(self) -> Application[str]:
if self._app is None:
return get_app()
return self._app
diff --git a/ptpython/repl.py b/ptpython/repl.py
index b55b5d5..3c729c0 100644
--- a/ptpython/repl.py
+++ b/ptpython/repl.py
@@ -44,6 +44,7 @@ from pygments.token import Token
from .python_input import PythonInput
+PyCF_ALLOW_TOP_LEVEL_AWAIT: int
try:
from ast import PyCF_ALLOW_TOP_LEVEL_AWAIT # type: ignore
except ImportError:
@@ -90,7 +91,7 @@ class PythonRepl(PythonInput):
output = self.app.output
output.write("WARNING | File not found: {}\n\n".format(path))
- def run_and_show_expression(self, expression):
+ def run_and_show_expression(self, expression: str) -> None:
try:
# Eval.
try:
@@ -135,7 +136,7 @@ class PythonRepl(PythonInput):
text = self.read()
except EOFError:
return
- except BaseException as e:
+ except BaseException:
# Something went wrong while reading input.
# (E.g., a bug in the completer that propagates. Don't
# crash the REPL.)
@@ -149,7 +150,7 @@ class PythonRepl(PythonInput):
clear_title()
self._remove_from_namespace()
- async def run_and_show_expression_async(self, text):
+ async def run_and_show_expression_async(self, text: str):
loop = asyncio.get_event_loop()
try:
@@ -349,7 +350,7 @@ class PythonRepl(PythonInput):
if not hasattr(black, "Mode"):
raise ImportError
except ImportError:
- pass # no Black package in your installation
+ pass # no Black package in your installation
else:
result_repr = black.format_str(
result_repr,
@@ -725,17 +726,17 @@ def embed(
configure(repl)
# Start repl.
- patch_context: ContextManager = (
+ patch_context: ContextManager[None] = (
patch_stdout_context() if patch_stdout else DummyContext()
)
if return_asyncio_coroutine:
- async def coroutine():
+ async def coroutine() -> None:
with patch_context:
await repl.run_async()
- return coroutine()
+ return coroutine() # type: ignore
else:
with patch_context:
repl.run()
diff --git a/ptpython/signatures.py b/ptpython/signatures.py
index 228b99b..e836d33 100644
--- a/ptpython/signatures.py
+++ b/ptpython/signatures.py
@@ -8,13 +8,16 @@ 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 typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence, Tuple
from prompt_toolkit.document import Document
from .completer import DictionaryCompleter
from .utils import get_jedi_script_from_document
+if TYPE_CHECKING:
+ import jedi.api.classes
+
__all__ = ["Signature", "get_signatures_using_jedi", "get_signatures_using_eval"]
@@ -120,7 +123,9 @@ class Signature:
)
@classmethod
- def from_jedi_signature(cls, signature) -> "Signature":
+ def from_jedi_signature(
+ cls, signature: "jedi.api.classes.Signature"
+ ) -> "Signature":
parameters = []
for p in signature.params:
diff --git a/ptpython/utils.py b/ptpython/utils.py
index 2fb24a4..ef96ca4 100644
--- a/ptpython/utils.py
+++ b/ptpython/utils.py
@@ -2,12 +2,31 @@
For internal use only.
"""
import re
-from typing import Callable, Iterable, Type, TypeVar, cast
-
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Callable,
+ Dict,
+ Iterable,
+ Optional,
+ Type,
+ TypeVar,
+ cast,
+)
+
+from prompt_toolkit.document import Document
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
+if TYPE_CHECKING:
+ from jedi import Interpreter
+
+ # See: prompt_toolkit/key_binding/key_bindings.py
+ # Annotating these return types as `object` is what works best, because
+ # `NotImplemented` is typed `Any`.
+ NotImplementedOrNone = object
+
__all__ = [
"has_unclosed_brackets",
"get_jedi_script_from_document",
@@ -45,7 +64,9 @@ def has_unclosed_brackets(text: str) -> bool:
return False
-def get_jedi_script_from_document(document, locals, globals):
+def get_jedi_script_from_document(
+ document: Document, locals: Dict[str, Any], globals: Dict[str, Any]
+) -> "Interpreter":
import jedi # We keep this import in-line, to improve start-up time.
# Importing Jedi is 'slow'.
@@ -78,7 +99,7 @@ def get_jedi_script_from_document(document, locals, globals):
_multiline_string_delims = re.compile("""[']{3}|["]{3}""")
-def document_is_multiline_python(document):
+def document_is_multiline_python(document: Document) -> bool:
"""
Determine whether this is a multiline Python document.
"""
@@ -133,7 +154,7 @@ def if_mousedown(handler: _T) -> _T:
by the Window.)
"""
- def handle_if_mouse_down(mouse_event: MouseEvent):
+ def handle_if_mouse_down(mouse_event: MouseEvent) -> "NotImplementedOrNone":
if mouse_event.event_type == MouseEventType.MOUSE_DOWN:
return handler(mouse_event)
else:
@@ -142,7 +163,7 @@ def if_mousedown(handler: _T) -> _T:
return cast(_T, handle_if_mouse_down)
-_T_type = TypeVar("_T_type", bound=Type)
+_T_type = TypeVar("_T_type", bound=type)
def ptrepr_to_repr(cls: _T_type) -> _T_type:
@@ -154,7 +175,8 @@ def ptrepr_to_repr(cls: _T_type) -> _T_type:
"@ptrepr_to_repr can only be applied to classes that have a `__pt_repr__` method."
)
- def __repr__(self) -> str:
+ def __repr__(self: object) -> str:
+ assert hasattr(cls, "__pt_repr__")
return fragment_list_to_text(to_formatted_text(cls.__pt_repr__(self)))
cls.__repr__ = __repr__ # type:ignore
diff --git a/ptpython/validator.py b/ptpython/validator.py
index 0f6a4ea..ffac583 100644
--- a/ptpython/validator.py
+++ b/ptpython/validator.py
@@ -1,3 +1,6 @@
+from typing import Callable, Optional
+
+from prompt_toolkit.document import Document
from prompt_toolkit.validation import ValidationError, Validator
from .utils import unindent_code
@@ -13,10 +16,10 @@ class PythonValidator(Validator):
active compiler flags.
"""
- def __init__(self, get_compiler_flags=None):
+ def __init__(self, get_compiler_flags: Optional[Callable[[], int]] = None) -> None:
self.get_compiler_flags = get_compiler_flags
- def validate(self, document):
+ def validate(self, document: Document) -> None:
"""
Check input for Python syntax errors.
"""
@@ -45,7 +48,7 @@ class PythonValidator(Validator):
# 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
+ (e.lineno or 1) - 1, (e.offset or 1) - 1
)
raise ValidationError(index, f"Syntax Error: {e}")
except TypeError as e:
diff --git a/setup.cfg b/setup.cfg
index adf5ed7..80dfec6 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,7 +1,41 @@
[bdist_wheel]
-universal = 1
-
-[egg_info]
-tag_build =
-tag_date = 0
+universal=1
+[flake8]
+exclude=__init__.py
+max_line_length=150
+ignore=
+ E114,
+ E116,
+ E117,
+ E121,
+ E122,
+ E123,
+ E125,
+ E126,
+ E127,
+ E128,
+ E131,
+ E171,
+ E203,
+ E211,
+ E221,
+ E227,
+ E231,
+ E241,
+ E251,
+ E301,
+ E402,
+ E501,
+ E701,
+ E702,
+ E704,
+ E731,
+ E741,
+ F401,
+ F403,
+ F405,
+ F811,
+ W503,
+ W504,
+ E722
diff --git a/setup.py b/setup.py
index 72a0e8b..274be8e 100644
--- a/setup.py
+++ b/setup.py
@@ -11,11 +11,12 @@ with open(os.path.join(os.path.dirname(__file__), "README.rst")) as f:
setup(
name="ptpython",
author="Jonathan Slenders",
- version="3.0.20",
+ version="3.0.21",
url="https://github.com/prompt-toolkit/ptpython",
description="Python REPL build on top of prompt_toolkit",
long_description=long_description,
packages=find_packages("."),
+ package_data={"ptpython": ["py.typed"]},
install_requires=[
"appdirs",
"importlib_metadata;python_version<'3.8'",