Hacky extensions and helper functions for the new Python REPL.
Project description
pyrepl-hacks 🙀
Hacky extensions and helper functions for the new Python REPL.
import pyrepl_hacks as repl
repl.bind("Alt+M", "move-to-indentation") # Move to first non-space in current line
repl.bind("Shift+Tab", "dedent") # Dedent the whole input
repl.bind("Alt+Down", "move-line-down") # Swap current line with next in block
repl.bind("Alt+Up", "move-line-up") # Swap current line with previous in block
repl.bind("Shift+Home", "home") # Move to first character in the input
repl.bind("Shift+End", "end") # Move to last character in the input
repl.bind("Alt+{", "previous-paragraph") # Move to previous blank line
repl.bind("Alt+}", "next-paragraph") # Move to next blank line
# Make Ctrl+N insert a specific list of numbers
repl.bind_to_insert("Ctrl+N", "[2, 1, 3, 4, 7, 11, 18, 29]")
@repl.bind(r"Ctrl+X Ctrl+R", with_event=True)
def subprocess_run(reader, event_name, event):
"""Ctrl+X followed by Ctrl+R will insert a subprocess.run command."""
reader.insert("import subprocess\n")
code = 'subprocess.run("", shell=True)'
reader.insert(code)
for _ in range(len(code) - code.index('""') - 1):
repl.commands.left(reader, event_name, event)
⚠️ WARNING: here be dragons 🐉
This library relies on Python implementation details which may change in future Python versions.
This library uses the _pyrepl module (and optionally _colorize).
As the _ prefix implies, these modules are not designed for public use.
That means that when you upgrade to a newer Python (for example Python 3.15) this code may break. For that reason, the Python versions this package claims to work with are pinned to only known-to-be-working Python versions.
Installing 💾
This package is meant to be installed in its own directory that will then be added to sys.path within your PYTHONSTARTUP file.
For example, you could install pyrepl-hacks into a ~/.pyhacks directory:
mkdir -p ~/.pyhacks
python -m pip install --upgrade pyrepl-hacks --target ~/.pyhacks
Then you can use it in a ~/.pythonrc file:
def _main():
from pathlib import Path
import sys
sys.path.append(str(Path.home() / ".pyhacks"))
try:
import pyrepl_hacks as repl
except ImportError:
pass # We must be on Python 3.12 or earlier
else:
repl.bind("Alt+M", "move-to-indentation")
repl.bind("Shift+Tab", "dedent")
repl.bind("Alt+Down", "move-line-down")
repl.bind("Alt+Up", "move-line-up")
repl.bind("Shift+Home", "home")
repl.bind("Shift+End", "end")
repl.bind("Alt+{", "previous-paragraph")
repl.bind("Alt+}", "next-paragraph")
repl.bind_to_insert("Ctrl+N", "[2, 1, 3, 4, 7, 11, 18, 29]")
_main()
del _main # Don't pollute the global namespace in our REPL
And put this something like this in your shell configuration file (~/.bashrc, ~/.zshrc, etc.):
export PYTHONSTARTUP="$HOME/.pythonrc"
This will modify the REPL in every Python environment you start.
See my blog post on pyrepl-hacks for more explanation on PYTHONSTARTUP files and that sys.path.append call above.
Command Registering and Key Binding ⌨️
This library includes features for easily registering and binding new REPL commands.
Binding to existing commands
You can bind a key to an existing command:
import pyrepl_hacks as repl
repl.bind("Shift+Home", "home")
Inserting text with a binding
You can use the bind_to_insert helper to bind a key to insert specific text:
import pyrepl_hacks as repl
repl.bind_to_insert("Ctrl+P", "Python?!")
Registering new commands
Need something fancy that doesn't exist yet?
You can register a new command:
import pyrepl_hacks as repl
@repl.register_command
def exit(reader):
"""Exits Python immediately."""
import sys
sys.exit(0)
The register_command decorator will turn the under_score separated name into a kebab-case name by default.
The register_command can optionally accept a command name and, if the command needs access to the event name and event object, a with_event=True argument can be provided:
import pyrepl_hacks as repl
@repl.register_command("delete-line", with_event=True)
def delete_whole_line(reader, event_name, event):
"""Move to beginning of line and delete all text."""
reader.pos = reader.bol()
repl.commands.kill_line(reader, event_name, event)
After commands have been registered, they can be used with the bind function to bind them to specific keys:
import pyrepl_hacks as repl
repl.bind("F4", "exit")
repl.bind("Ctrl+X Ctrl+D", "delete-line")
Binding keys while registering
The bind function can also be used as a decorator to register a command and bind it to a specific key combination at the same time:
import pyrepl_hacks as repl
@repl.bind("F4")
def exit(reader):
"""Exits Python immediately."""
import sys
sys.exit(0)
Since there's not much point in making a new command without binding it, you'll usually want to use bind instead of register_command.
Just like register_command, bind decorator can also accept a with_event=True argument to pass the event name and event object into the command function.
Available Commands 📑
Here are some of the interesting commands provided by Python (in _pyrepl.commands):
clear-screen: Clear screen (Ctrl+L)previous-history: Show previous block (Ctrl+P)next-history: Show next block (Ctrl+N)accept: Run current code block (Alt+Enter)beginning-of-line: Move cursor to the first character of the current line (Ctrl+AorHome)end-of-line: Move cursor to the last character of the current line (Ctrl+EorEnd)home: Move cursor the first character in the code blockend: Move cursor the last character in the code blockkill-line: Delete to end of line (Ctrl+K)unix-line-discard: Delete to beginning of line (Ctrl+U)backward-word: Move cursor back one word (Ctrl+Left)forward-word: Move cursor forward one word (Ctrl+Right)backward-kill-word: Delete to beginning of word (Alt+Backspace)kill-word: Delete to end of word (Alt+D)
See here for which keyboard shortcuts these are bound to by default.
This pyrepl-hacks project provides some additional commands as well:
move-to-indentation: Move to first non-space in current linededent: Dedent the whole code blockmove-line-down: Swap current line with next one in the blockmove-line-up: Swap current line with previous one in the blockprevious-paragraph: Move to the previous blank linenext-paragraph: Move to the next blank line
These 6 additional commands have no key bindings by default.
I recommend binding these commands as well as the home and end commands (provided by _pyrepl.commands) which are also unbound by default:
repl.bind("Alt+M", "move-to-indentation") # Move to first non-space in current line
repl.bind("Shift+Tab", "dedent") # Dedent the whole input
repl.bind("Alt+Down", "move-line-down") # Swap current line with next in block
repl.bind("Alt+Up", "move-line-up") # Swap current line with previous in block
repl.bind("Shift+Home", "home") # Move to first character in the input
repl.bind("Shift+End", "end") # Move to last character in the input
repl.bind("Alt+{", "previous-paragraph") # Move to previous blank line
repl.bind("Alt+}", "next-paragraph") # Move to next blank line
Note that these custom REPL commands and all existing commands provided by _pyrepl.commands include wrapper functions in the commands submodule.
These functions are named the same as their command name, except - must be replaced by _:
from pyrepl_hacks.commands import move_to_indentation, clear_screen
Customizing Your Syntax Theme 🎨
Python 3.14 includes syntax highlighting in the REPL.
You can use the update_theme command from pyrepl-hacks to customize the colors your REPL uses:
try:
import pyrepl_hacks as repl
repl.update_theme(
keyword="green",
builtin="blue",
comment="intense blue",
string="cyan",
number="cyan",
definition="blue",
soft_keyword="bold green",
op="intense green",
reset="reset, intense green",
)
except ImportError:
pass # We're on Python 3.13 or below
These are the supported colors:
blackbluecyangreengreymagentaredwhiteyellow
Each supports the modifiers bold, intense, background and intense background.
Also the "color" of reset will reset all modifiers.
The Future is Obsolescence? 🦤
This project came out of the things I learned while hacking on my own REPL shortcuts and customizing my REPL's syntax highlighting.
My hope is that this package will be obsolete one day.
I hope that Python will eventually include an official interface for creating new REPL commands and binding keys to commands.
I also hope that some (or all?) of the 4 new commands this module includes will eventually be included with Python by default.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pyrepl_hacks-0.5.0.tar.gz.
File metadata
- Download URL: pyrepl_hacks-0.5.0.tar.gz
- Upload date:
- Size: 21.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2a6f0fad0ac52b7f1d26f6fac5187f7a93aeeacab0ae33ba3d198b615cf31bf6
|
|
| MD5 |
1410c1c1f95f00ea7c3561ab8aa8a11e
|
|
| BLAKE2b-256 |
13a7f22f5e2042e8093deb522f16037290d39ed7cb4a82690bb1cb571f54c28b
|
File details
Details for the file pyrepl_hacks-0.5.0-py3-none-any.whl.
File metadata
- Download URL: pyrepl_hacks-0.5.0-py3-none-any.whl
- Upload date:
- Size: 14.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
115997dbb12ec51a2865dca5309a58de0b88caecd4f7bab93ed7b6c8a922b27d
|
|
| MD5 |
33933936381d5a7b6efdb0c02010fedc
|
|
| BLAKE2b-256 |
ba2e246b9fd7950e834d742ec4e6b47fdb8dfcbdc76c4d1995904e138a611c5f
|