Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-111201: A new Python REPL #111567

Merged
merged 88 commits into from May 5, 2024
Merged
Show file tree
Hide file tree
Changes from 81 commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
2bf1505
gh-111201: A new Python REPL
pablogsal Oct 20, 2023
35d322e
Clean some files
pablogsal Apr 27, 2024
4d2303b
Fix history across sessions
pablogsal Apr 27, 2024
7763c05
Fore fixes to history
pablogsal Apr 27, 2024
e85d873
Lukasz was wrong all along
pablogsal Apr 27, 2024
842496e
Implement REPL commands and remove F1 for help (curses shows that scr…
pablogsal Apr 27, 2024
1417b9b
Restore F1 for help
pablogsal Apr 27, 2024
4c69853
Implement colored prompt
pablogsal Apr 27, 2024
359407b
WIP for paste mode
pablogsal Apr 27, 2024
85a2b35
Only execute PYTHONSTARTUP when asked (we do it in main.c anyway)
ambv Apr 27, 2024
05a8831
Fixed coloring
pablogsal Apr 27, 2024
c074ca6
Do not run commands if they are shadowed
pablogsal Apr 27, 2024
9584b5b
Move pager routines to _pyrepl, so we can use it in the REPL without …
ambv Apr 27, 2024
d7ddebd
Don't assume stupid paths or you will be tested against your stupid a…
ambv Apr 27, 2024
2d24cd2
<F2> shows history so you can copy it, <F3> is paste mode
ambv Apr 27, 2024
fe1b3ac
Do not include REPL commands in history
pablogsal Apr 27, 2024
e1abd39
Add Blurb
ambv Apr 27, 2024
afbeaab
Reintroduce old names to fix pydoc tests
ambv Apr 27, 2024
aee9fcf
Add some tests
pablogsal Apr 28, 2024
77db960
Add moar tests
pablogsal Apr 28, 2024
2a64680
Add moar tests
pablogsal Apr 28, 2024
5bced59
Cleanup and refactor
pablogsal Apr 28, 2024
20533d3
Refactor unix_console.py
pablogsal Apr 28, 2024
a07a0ce
Refactor unix_console.py more
pablogsal Apr 28, 2024
b8b0e76
Refactor unix_console.py more
pablogsal Apr 28, 2024
c570171
Moar tests
pablogsal Apr 28, 2024
169043f
Fix test___all__
ambv Apr 28, 2024
c613ae3
Fix test_traceback
ambv Apr 28, 2024
98fbee2
Fix weird r-search scrambled text due to color codes
pablogsal Apr 28, 2024
254aaf2
Fix weird r-search scrambled text due to color codes better
pablogsal Apr 28, 2024
cf9bd26
Disgusting fix for help state restoration
pablogsal Apr 28, 2024
b064e1f
Test pasting with/without paste mode
mgmacias95 Apr 28, 2024
0fa01e0
refactor events
mgmacias95 Apr 28, 2024
fa37b07
Merge pull request #71 from mgmacias95/pyrepl
pablogsal Apr 28, 2024
a6d54e6
help() uses its own history, doesn't pollute main history
ambv Apr 28, 2024
ffbf24b
Add typing to reader.py
ambv Apr 29, 2024
6c188fb
Add types to pager.py
ambv Apr 29, 2024
7919bae
Add types to input.py, console.pu, and commands.py
ambv Apr 29, 2024
2a7e81d
Refactor termios stuff in unix console
lysnikolaou Apr 29, 2024
aa9eaf3
Merge pull request #72 from lysnikolaou/pyrepl-refactor-term-unix-con…
pablogsal Apr 29, 2024
8c368d0
Revert "Refactor termios stuff in unix console"
pablogsal Apr 29, 2024
977e79e
Fix cursor position for double-width characters
lysnikolaou Apr 30, 2024
05b1142
Add _pyrepl to installed Lib subdirs
lysnikolaou Apr 30, 2024
c56209a
Merge pull request #74 from lysnikolaou/pyrepl-add-module-install
pablogsal Apr 30, 2024
bc31d3a
Add tests
lysnikolaou Apr 30, 2024
373a8a0
Update Lib/test/test_pyrepl.py
pablogsal Apr 30, 2024
e9be872
Merge pull request #73 from lysnikolaou/pyrepl-double-width-char-cursor
pablogsal Apr 30, 2024
cba260f
Implement better fallback with PYTHON_OLD_REPL
pablogsal Apr 30, 2024
233da02
Cache failures in pyrepl
pablogsal Apr 30, 2024
07695f7
Stay at eol when moving up/down
lysnikolaou Apr 30, 2024
63dabfd
Fix linter
lysnikolaou Apr 30, 2024
162252a
fix early errors
pablogsal Apr 30, 2024
970fd85
Fix CI
pablogsal Apr 30, 2024
3dcf704
Fix paste mode when there are empty line in the middle
lysnikolaou Apr 30, 2024
755728d
Fix disp_str for control characters
lysnikolaou Apr 30, 2024
121ce2b
Fix mac CI
pablogsal Apr 30, 2024
07345da
Merge pull request #77 from lysnikolaou/pyrepl-fix-disp-str-ctrl
pablogsal Apr 30, 2024
e5154d2
Merge pull request #76 from lysnikolaou/pyrepl-paste-mode-newlines-mid
pablogsal Apr 30, 2024
390d778
Merge pull request #75 from lysnikolaou/pyrepl-eol-move-up-down
pablogsal Apr 30, 2024
ba26254
Various fixes to handle wide characters correctly
lysnikolaou May 1, 2024
691c75e
Fix vertical navigation with wide characters
lysnikolaou May 2, 2024
9804074
Write more tests
lysnikolaou May 2, 2024
8f3e713
Fix backspace in second line
lysnikolaou May 2, 2024
abe9fd3
Fixes for pos2xy & setpos_from_xy when wrapped line
lysnikolaou May 3, 2024
4f66170
Fix linter & run black
lysnikolaou May 3, 2024
4bc36ab
Update Lib/_pyrepl/reader.py
pablogsal May 3, 2024
1443793
Merge pull request #78 from lysnikolaou/pyrepl-various-fixed-wide-cha…
pablogsal May 3, 2024
b516831
Add typing to consoles
ambv May 4, 2024
0d24b9d
Merge branch 'main' into pyrepl
ambv May 4, 2024
18cd2cf
Work around mypy being unhappy with _colorize
ambv May 4, 2024
afe2513
Remove duplicate test
ambv May 4, 2024
864ecb8
Add types to HistoricalReader and CompletingReader
ambv May 4, 2024
b5f8895
Add typing to readline.py
ambv May 4, 2024
45537bb
Pin test_pyrepl to the `curses` test resource
ambv May 4, 2024
22c4d3f
Don't call anything curses in test_repl
pablogsal May 4, 2024
6a0fcef
Many machines on CI run with -uall; skip the test if curses import fails
ambv May 4, 2024
b4362b5
Fix refleaks
pablogsal May 4, 2024
a900ccd
Document PYTHON_BASIC_REPL
ambv May 4, 2024
13fda4e
Do not call _setup() on import
pablogsal May 4, 2024
6ebf89c
Document pyrepl
ambv May 4, 2024
6d3155e
Merge branch 'main' into pyrepl
ambv May 4, 2024
058bc7f
Apply suggestions from code review
pablogsal May 5, 2024
7ac9af8
Update Lib/_pyrepl/_minimal_curses.py
pablogsal May 5, 2024
7422f1c
Use more specific exceptions
pablogsal May 5, 2024
d2de559
Use better error messages
ambv May 5, 2024
3053b39
Add to What's New
ambv May 5, 2024
149658b
Forgot the PyPy attribution in "What's New"
ambv May 5, 2024
be2a0c9
let's never talk about this
ambv May 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/mypy.yml
Expand Up @@ -8,6 +8,7 @@ on:
pull_request:
paths:
- ".github/workflows/mypy.yml"
- "Lib/_pyrepl/**"
- "Lib/test/libregrtest/**"
- "Tools/build/generate_sbom.py"
- "Tools/cases_generator/**"
Expand Down Expand Up @@ -35,8 +36,9 @@ jobs:
strategy:
matrix:
target: [
"Lib/_pyrepl",
"Lib/test/libregrtest",
"Tools/build/",
"Tools/build",
"Tools/cases_generator",
"Tools/clinic",
"Tools/jit",
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/reusable-macos.yml
Expand Up @@ -22,6 +22,7 @@ jobs:
HOMEBREW_NO_INSTALL_CLEANUP: 1
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1
PYTHONSTRICTEXTENSIONBUILD: 1
TERM: linux
strategy:
fail-fast: false
matrix:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/reusable-ubuntu.yml
Expand Up @@ -17,6 +17,7 @@ jobs:
FORCE_COLOR: 1
OPENSSL_VER: 3.0.13
PYTHONSTRICTEXTENSIONBUILD: 1
TERM: linux
steps:
- uses: actions/checkout@v4
- name: Register gcc problem matcher
Expand Down
14 changes: 10 additions & 4 deletions Doc/glossary.rst
Expand Up @@ -9,13 +9,14 @@ Glossary
.. glossary::

``>>>``
The default Python prompt of the interactive shell. Often seen for code
examples which can be executed interactively in the interpreter.
The default Python prompt of the :term:`interactive` shell. Often
seen for code examples which can be executed interactively in the
interpreter.

``...``
Can refer to:

* The default Python prompt of the interactive shell when entering the
* The default Python prompt of the :term:`interactive` shell when entering the
code for an indented code block, when within a pair of matching left and
right delimiters (parentheses, square brackets, curly braces or triple
quotes), or after specifying a decorator.
Expand Down Expand Up @@ -620,7 +621,8 @@ Glossary
execute them and see their results. Just launch ``python`` with no
arguments (possibly by selecting it from your computer's main
menu). It is a very powerful way to test out new ideas or inspect
modules and packages (remember ``help(x)``).
modules and packages (remember ``help(x)``). For more on interactive
mode, see :ref:`tut-interac`.

interpreted
Python is an interpreted language, as opposed to a compiled one,
Expand Down Expand Up @@ -1084,6 +1086,10 @@ Glossary

See also :term:`namespace package`.

REPL
An acronym for the "read–eval–print loop", another name for the
:term:`interactive` interpreter shell.

__slots__
A declaration inside a class that saves memory by pre-declaring space for
instance attributes and eliminating instance dictionaries. Though
Expand Down
22 changes: 22 additions & 0 deletions Doc/tutorial/appendix.rst
Expand Up @@ -10,6 +10,28 @@ Appendix
Interactive Mode
================

There are two variants of the interactive :term:`REPL`. The classic
basic interpreter is supported on all platforms with minimal line
control capabilities.

On Unix-like systems (e.g. Linux or macOS) with :mod:`curses` and
:mod:`readline` support, a new interactive shell is used by default.
This one supports color, multiline editing, history browsing, and
paste mode. To disable color, see :ref:`using-on-controlling-color` for
details. Function keys provide some additional functionality.
``F1`` enters the interactive help browser :mod:`pydoc`.
pablogsal marked this conversation as resolved.
Show resolved Hide resolved
``F2`` allows for browsing command-line history without output nor the
pablogsal marked this conversation as resolved.
Show resolved Hide resolved
:term:`>>>` and :term:`...` prompts. ``F3`` enters "paste mode", which
pablogsal marked this conversation as resolved.
Show resolved Hide resolved
makes pasting larger blocks of code easier. Press ``F3`` to return to
pablogsal marked this conversation as resolved.
Show resolved Hide resolved
the regular prompt.

When using the new interactive shell, exit the shell by typing ``exit``
pablogsal marked this conversation as resolved.
Show resolved Hide resolved
or ``quit``. Adding call parentheses after those commands is not
pablogsal marked this conversation as resolved.
Show resolved Hide resolved
required.

If the new interactive shell is not desired, it can be disabled via
the :envvar:`PYTHON_BASIC_REPL` environment variable.

.. _tut-error:

Error Handling
Expand Down
10 changes: 10 additions & 0 deletions Doc/using/cmdline.rst
Expand Up @@ -42,6 +42,7 @@ additional methods of invocation:
* When called with standard input connected to a tty device, it prompts for
commands and executes them until an EOF (an end-of-file character, you can
produce that with :kbd:`Ctrl-D` on UNIX or :kbd:`Ctrl-Z, Enter` on Windows) is read.
For more on interactive mode, see :ref:`tut-interac`.
* When called with a file name argument or with a file as standard input, it
reads and executes a script from that file.
* When called with a directory name argument, it reads and executes an
Expand Down Expand Up @@ -1158,6 +1159,15 @@ conflict.

.. versionadded:: 3.13

.. envvar:: PYTHON_BASIC_REPL

If this variable is set to ``1``, the interpreter will not attempt to
load the Python-based :term:`REPL` that requires :mod:`curses` and
:mod:`readline`, and will instead use the traditional parser-based
:term:`REPL`.

.. versionadded:: 3.13

.. envvar:: PYTHON_HISTORY

This environment variable can be used to set the location of a
Expand Down
19 changes: 19 additions & 0 deletions Lib/_pyrepl/__init__.py
@@ -0,0 +1,19 @@
# Copyright 2000-2008 Michael Hudson-Doyle <micahel@gmail.com>
# Armin Rigo
#
# All Rights Reserved
#
#
# Permission to use, copy, modify, and distribute this software and
# its documentation for any purpose is hereby granted without fee,
# provided that the above copyright notice appear in all copies and
# that both that copyright notice and this permission notice appear in
# supporting documentation.
#
# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
42 changes: 42 additions & 0 deletions Lib/_pyrepl/__main__.py
@@ -0,0 +1,42 @@
import os
import sys

CAN_USE_PYREPL = True

def interactive_console(mainmodule=None, quiet=False, pythonstartup=False):
global CAN_USE_PYREPL
if not CAN_USE_PYREPL:
return sys._baserepl()

startup_path = os.getenv("PYTHONSTARTUP")
if pythonstartup and startup_path:
import tokenize
with tokenize.open(startup_path) as f:
startup_code = compile(f.read(), startup_path, "exec")
exec(startup_code)

# set sys.{ps1,ps2} just before invoking the interactive interpreter. This
# mimics what CPython does in pythonrun.c
if not hasattr(sys, "ps1"):
sys.ps1 = ">>> "
if not hasattr(sys, "ps2"):
sys.ps2 = "... "
#
run_interactive = None
try:
if not os.isatty(sys.stdin.fileno()):
raise ImportError
pablogsal marked this conversation as resolved.
Show resolved Hide resolved
from .simple_interact import check
if not check():
raise ImportError
from .simple_interact import run_multiline_interactive_console
run_interactive = run_multiline_interactive_console
except Exception as e:
print(f"Warning: 'import _pyrepl' failed with '{e}'", file=sys.stderr)
CAN_USE_PYREPL = False
if run_interactive is None:
return sys._baserepl()
return run_interactive(mainmodule)

if __name__ == "__main__":
interactive_console()
68 changes: 68 additions & 0 deletions Lib/_pyrepl/_minimal_curses.py
@@ -0,0 +1,68 @@
"""Minimal '_curses' module, the low-level interface for curses module
which is not meant to be used directly.

Based on ctypes. It's too incomplete to be really called '_curses', so
to use it, you have to import it and stick it in sys.modules['_curses']
manually.

Note that there is also a built-in module _minimal_curses which will
hide this one if compiled in.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it part of this PR? If not, do you consider adding it or not?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, sounds like this comment was carried over from pypy but doesn't apply here.

"""

import ctypes
import ctypes.util


class error(Exception):
pass


def _find_clib():
trylibs = ["ncursesw", "ncurses", "curses"]

for lib in trylibs:
path = ctypes.util.find_library(lib)
if path:
return path
raise ModuleNotFoundError("curses library not found", name="_minimal_curses")
pablogsal marked this conversation as resolved.
Show resolved Hide resolved


_clibpath = _find_clib()
clib = ctypes.cdll.LoadLibrary(_clibpath)

clib.setupterm.argtypes = [ctypes.c_char_p, ctypes.c_int, ctypes.POINTER(ctypes.c_int)]
clib.setupterm.restype = ctypes.c_int

clib.tigetstr.argtypes = [ctypes.c_char_p]
clib.tigetstr.restype = ctypes.POINTER(ctypes.c_char)

clib.tparm.argtypes = [ctypes.c_char_p] + 9 * [ctypes.c_int] # type: ignore[operator]
clib.tparm.restype = ctypes.c_char_p

OK = 0
ERR = -1

# ____________________________________________________________


def setupterm(termstr, fd):
err = ctypes.c_int(0)
result = clib.setupterm(termstr, fd, ctypes.byref(err))
if result == ERR:
raise error("setupterm() failed (err=%d)" % err.value)


def tigetstr(cap):
if not isinstance(cap, bytes):
cap = cap.encode("ascii")
result = clib.tigetstr(cap)
if ctypes.cast(result, ctypes.c_void_p).value == ERR:
return None
return ctypes.cast(result, ctypes.c_char_p).value


def tparm(str, i1=0, i2=0, i3=0, i4=0, i5=0, i6=0, i7=0, i8=0, i9=0):
result = clib.tparm(str, i1, i2, i3, i4, i5, i6, i7, i8, i9)
if result is None:
raise error("tparm() returned NULL")
return result