1
0
Fork 0

Merging upstream version 3.0.21.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-02-09 18:28:35 +01:00
parent 8fd03f3098
commit 3623c97041
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
29 changed files with 547 additions and 772 deletions

40
.github/workflows/test.yaml vendored Normal file
View file

@ -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

View file

@ -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
------------------

263
PKG-INFO
View file

@ -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

View file

@ -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
***************

View file

@ -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.

View file

@ -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).

6
mypy.ini Normal file
View file

@ -0,0 +1,6 @@
[mypy]
ignore_missing_imports = True
no_implicit_optional = True
platform = win32
strict_equality = True
strict_optional = True

View file

@ -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

View file

@ -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

View file

@ -1 +0,0 @@

View file

@ -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

View file

@ -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

View file

@ -1 +0,0 @@
ptpython

View file

@ -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.
"""

View file

@ -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)

View file

@ -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)

View file

@ -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:

View file

@ -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:

View file

@ -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)

View file

@ -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.
"""

View file

@ -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(

0
ptpython/py.typed Normal file
View file

View file

@ -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

View file

@ -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()

View file

@ -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:

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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'",