Last active
March 12, 2025 17:28
-
-
Save LukeSavefrogs/1444e5ea62bfe8f8a9ea789922379460 to your computer and use it in GitHub Desktop.
[WIP] Jython 2.1 REPL
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" Alternative Jython interactive console with modern features. | |
Features: | |
- History (Up/Down arrow keys) | |
- Character movement (Left/Right arrow keys) | |
- Word movement (Ctrl+Left/Ctrl+Right arrow keys) | |
- Home/End support | |
- Backspace/DEL support | |
- Ctrl-C/Ctrl-D support | |
- Clear screen (Ctrl-L) | |
- Custom prompt (>>>) | |
- Debug mode showing key presses via environment variables (PYTHONDEBUG, PYTHONVERBOSE) | |
- History file save/load (set via environment variable PYTHON_HISTORY) | |
TODO: Add support for multiline commands | |
Original code: | |
https://github.com/python/cpython/blob/v2.1/Lib/code.py | |
""" | |
import os | |
import sys | |
import code | |
if sys.platform.startswith("java"): | |
import java.lang.System as System # pyright: ignore[reportMissingImports] | |
import java as _java # pyright: ignore[reportMissingImports] | |
# Set the prompt to ">>> " (like the standard Python REPL) | |
sys.ps1 = "\n>>> " | |
sys.ps2 = "... " | |
def _tty_set_raw_mode(shell="/bin/sh"): | |
""" Set the terminal to raw mode. """ | |
execute_command([shell, "-c", "stty -echo </dev/tty && stty raw </dev/tty"]) | |
def _tty_set_cooked_mode(shell="/bin/sh"): | |
""" Put the terminal back to cooked mode. """ | |
execute_command([shell, "-c", "stty echo </dev/tty && stty sane </dev/tty"]) | |
def execute_command(command): | |
# type: (list[str]) -> str | |
"""Execute a command and return the output. | |
Args: | |
command (list[str]): The command that will be executed. | |
Returns: | |
output (str): The output of the command. | |
Example: | |
>>> execute_command(["echo", "Hello World!"]) | |
'Hello World!' | |
""" | |
output = [] | |
try: | |
import subprocess # Used while testing on Windows | |
# Support Python 3.5 and up | |
if hasattr(subprocess, "run"): | |
return subprocess.run( | |
command, | |
capture_output=1==1, | |
text=1==1, | |
shell=1==1, | |
check=1==1, | |
).stdout | |
# Support Python 2.7 | |
return subprocess.check_output(command).decode("utf-8") | |
except ImportError: | |
if not sys.platform.startswith("java"): | |
raise | |
builder = _java.lang.ProcessBuilder([str(word) for word in command]) | |
builder.redirectErrorStream(1) | |
process = builder.start() | |
reader = _java.io.BufferedReader( | |
_java.io.InputStreamReader(process.getInputStream()) | |
) | |
line = reader.readLine() | |
while line != None: | |
output.append(line) | |
line = reader.readLine() | |
reader, process, builder = None, None, None | |
return "\n".join(output) | |
def read_character(shell="/bin/sh"): # "/bin/sh" | |
# type: (str) -> int | |
"""Read a single character from the console""" | |
_tty_set_raw_mode(shell) | |
try: | |
try: | |
if sys.platform.startswith("java"): | |
return System.console().reader().read() | |
# For some reason, on Jython (tested on RHEL) `sys.stdin.read(1)` does not work as expected | |
return ord(sys.stdin.read(1)) | |
except: | |
sys.stderr.write("Error while reading character from console\n") | |
raise | |
finally: | |
_tty_set_cooked_mode(shell) | |
class KeyboardCode: | |
def __init__(self, decimal=None, sequence=None, caret=None): | |
# type: (int|None, tuple[int, ...]|None, str|None) -> None | |
"""Initialize the KeyboardCode. | |
Args: | |
decimal (int): The decimal value of the byte representing the key. | |
sequence (tuple[int]): The sequence of bytes that represent the key. | |
caret (str): The caret representation of the key. | |
""" | |
self.sequence = tuple([]) # type: tuple[int, ...] | |
if sequence is not None: | |
self.sequence = sequence | |
elif decimal is not None: | |
self.sequence = tuple([decimal]) | |
else: | |
raise ValueError("Either 'decimal' or 'sequence' must be set.") | |
self.caret = "" | |
if caret is not None: | |
self.caret = caret | |
def __repr__(self): | |
return "<KeyboardCode DEC=%s C=%s>" % ( | |
'+'.join([str(byte) for byte in self.sequence]), | |
self.caret, | |
) | |
def __add__(self, other): | |
# type: (KeyboardCode|str) -> KeyboardCode | |
"""Add two KeyboardCodes together""" | |
if isinstance(other, KeyboardCode): | |
return KeyboardCode( | |
sequence=self.sequence + other.sequence, | |
caret=self.caret + other.caret, | |
) | |
elif type(other) in [type(""), type(u"")]: | |
return KeyboardCode( | |
sequence=self.sequence + tuple([ord(char) for char in str(other)]), | |
caret=self.caret + str(other), | |
) | |
raise ValueError("Unsupported type for addition: %s" % type(other)) | |
# def __add__(self, other): | |
# # type: (KeyboardCode) -> str | |
# """Add two KeyboardCodes together""" | |
# # return KeyboardCode | |
class KeyboardKey: | |
def __init__(self, name, code): | |
# type: (str, KeyboardCode|list[KeyboardCode]|KeyboardKey|list[KeyboardKey]) -> None | |
"""Initialize the KeyboardKey""" | |
self.name = name | |
self.codes = [] # type: list[KeyboardCode] | |
if type(code) not in [type([]), type(())]: | |
code = [code] | |
for item in code: | |
if isinstance(item, KeyboardCode): | |
self.codes.append(item) | |
elif isinstance(item, KeyboardKey): | |
self.codes += item.codes | |
else: | |
raise ValueError("Unsupported type for code: %s" % type(item)) | |
def __repr__(self): | |
return "<KeyboardKey: %s>" % self.name | |
def __add__(self, other): | |
# type: (KeyboardKey|str) -> KeyboardKey | |
"""Add two KeyboardKeys together""" | |
if isinstance(other, KeyboardKey): | |
return KeyboardKey(self.name + "+" + other.name, self.codes + other.codes) | |
elif type(other) in [type(""), type(u"")]: | |
if len(self.codes) != 1: | |
raise ValueError("Too many codes found (%d). Please add manually." % len(self.codes)) | |
return KeyboardKey( | |
self.name + "+" + str(other), | |
self.codes[0] + str(other), | |
) | |
raise ValueError("Unsupported type for addition: %s" % type(other)) | |
ESC = KeyboardKey("ESC", KeyboardCode(decimal=27, caret="^[")) | |
class Keyboard: | |
""" Keyboard keys codes """ | |
class Keys: | |
# https://www.gaijin.at/en/infos/ascii-ansi-character-table#asciicontrol | |
CTRL_C = KeyboardKey("CTRL_C", KeyboardCode(decimal=3, caret="^C")) | |
CTRL_D = KeyboardKey("CTRL_D", KeyboardCode(decimal=4, caret="^D")) | |
CTRL_L = KeyboardKey("CTRL_L", KeyboardCode(decimal=12, caret="^L")) | |
CTRL_R = KeyboardKey("CTRL_R", KeyboardCode(decimal=18, caret="^R")) | |
CTRL_S = KeyboardKey("CTRL_S", KeyboardCode(decimal=19, caret="^S")) | |
CTRL_X = KeyboardKey("CTRL_X", KeyboardCode(decimal=24, caret="^X")) | |
ENTER = KeyboardKey("ENTER", KeyboardCode(decimal=13, caret="^M")) | |
BACKSPACE = KeyboardKey("BACKSPACE", KeyboardCode(decimal=127, caret="^?")) | |
DEL = KeyboardKey("DEL", KeyboardCode(decimal=127, caret="^?")) | |
HOME = KeyboardKey(name="HOME", code=[ | |
ESC + "[H", | |
ESC + "[1~", | |
]) | |
END = KeyboardKey(name="END", code=[ | |
ESC + "[F", | |
ESC + "[4~", | |
]) | |
PAGE_UP = KeyboardKey(name="PAGE_UP", code=ESC + "[5~") | |
PAGE_DOWN = KeyboardKey(name="PAGE_DOWN", code=ESC + "[6~") | |
ARROW_UP = KeyboardKey(name="ARROW_UP", code=ESC + "[A") | |
ARROW_DOWN = KeyboardKey(name="ARROW_DOWN", code=ESC + "[B") | |
ARROW_RIGHT = KeyboardKey(name="ARROW_RIGHT", code=ESC + "[C") | |
ARROW_LEFT = KeyboardKey(name="ARROW_LEFT", code=ESC + "[D") | |
CTRL_ARROW_RIGHT = KeyboardKey("CTRL+ARROW_RIGHT", [ | |
ESC + "OC", | |
ESC + "[1;5C", | |
]) | |
CTRL_ARROW_LEFT = KeyboardKey("CTRL+ARROW_LEFT", [ | |
ESC + "OD", | |
ESC + "[1;5D", | |
]) | |
def read_keystroke(): | |
# type: () -> KeyboardKey|int | |
"""Read a keystroke from the console and return the key pressed. | |
TODO: Make the list of keys dynamic (based on the terminal) | |
TODO: Make the character sequence dynamic (based on a predefined list) | |
""" | |
# Read the first byte of the keystroke | |
first_byte = read_character() | |
# https://www.gaijin.at/en/infos/ascii-ansi-character-table | |
if first_byte == 3: | |
return Keyboard.Keys.CTRL_C | |
elif first_byte == 4: | |
return Keyboard.Keys.CTRL_D | |
elif first_byte == 12: | |
return Keyboard.Keys.CTRL_L | |
elif first_byte == 18: | |
return Keyboard.Keys.CTRL_R | |
elif first_byte == 13: | |
return Keyboard.Keys.ENTER | |
elif first_byte == 127: | |
return Keyboard.Keys.BACKSPACE | |
# ----> Escape (start of escape sequence) | |
elif first_byte == 27: | |
second_byte = read_character() | |
# ----> [ | |
if second_byte == 91: | |
third_byte = read_character() | |
# Arrow keys | |
# ----> A = Up | |
if third_byte == 65: | |
return Keyboard.Keys.ARROW_UP | |
# ----> B = Down | |
elif third_byte == 66: | |
return Keyboard.Keys.ARROW_DOWN | |
# ----> C = Right | |
elif third_byte == 67: | |
return Keyboard.Keys.ARROW_RIGHT | |
# ----> D = Left | |
elif third_byte == 68: | |
return Keyboard.Keys.ARROW_LEFT | |
# Implementation difference for some keys | |
# - HOME=^[[H END=^[[F (3 bytes) | |
# - HOME=^[[1~ END=^[[4~ (4 bytes) | |
elif third_byte == 72: | |
return Keyboard.Keys.HOME | |
elif third_byte == 70: | |
return Keyboard.Keys.END | |
# All of the following have 4 bytes (with DEC=126 / ASCII=~ as the last byte) | |
elif third_byte in [51, 49, 52, 53, 54]: | |
fourth_byte = read_character() | |
# Unexpected fourth byte (should be `~`) | |
if fourth_byte != 126: | |
raise ValueError("Error in escape sequence: ^[[%s%s (\\e[%s%s) [expected '~' as fourth byte, got '%s']" % ( | |
third_byte, | |
fourth_byte, | |
chr(third_byte), | |
chr(fourth_byte), | |
chr(fourth_byte), | |
)) | |
if third_byte == 49: | |
return Keyboard.Keys.HOME | |
elif third_byte == 51: | |
return Keyboard.Keys.DEL | |
elif third_byte == 52: | |
return Keyboard.Keys.END | |
elif third_byte == 53: | |
return Keyboard.Keys.PAGE_UP | |
elif third_byte == 54: | |
return Keyboard.Keys.PAGE_DOWN | |
else: | |
raise ValueError("Unknown escape sequence: ^[[%s%s (\\e[%s%s)" % ( | |
third_byte, | |
fourth_byte, | |
chr(third_byte), | |
chr(fourth_byte), | |
)) | |
else: | |
raise ValueError("Unknown escape sequence: ^[[%s (\\e[%s)" % ( | |
third_byte, | |
chr(third_byte), | |
)) | |
# ----> O | |
elif second_byte == 79: | |
third_byte = read_character() | |
# ----> CTRL+ARROW_RIGHT | |
if third_byte == 67: | |
return Keyboard.Keys.CTRL_ARROW_RIGHT | |
# ----> CTRL+ARROW_LEFT | |
elif third_byte == 68: | |
return Keyboard.Keys.CTRL_ARROW_LEFT | |
else: | |
raise ValueError("Unknown escape sequence: ^[O%s (\\e[O%s)" % ( | |
third_byte, | |
chr(third_byte), | |
)) | |
else: | |
raise ValueError("Unknown escape sequence: ^[%s (\\e[%s) [expected '[' as second byte, got '%s']" % ( | |
second_byte, | |
chr(second_byte), | |
chr(second_byte), | |
)) | |
else: | |
return first_byte | |
def is_word_character(character): | |
# type: (str) -> bool | |
"""Check if a character is a word character (alphanumeric)""" | |
return character.isalnum() and character not in ["(", ")", "[", "]"] | |
class ModernConsole(code.InteractiveConsole): | |
""" Emulate the modern REPL console with history. """ | |
commands_history = [] # type: list[str] | |
def __init__(self, locals=None, filename="<console>", debug=0==1, history_file=None): | |
"""Initialize the console""" | |
self.commands_history = [] | |
self.is_debug_mode = debug | |
self.current_command_rows = 0 | |
self.history_file = history_file | |
self.load_history() | |
code.InteractiveConsole.__init__(self, locals, filename) | |
def load_history(self): | |
"""Load the history from a file""" | |
if not self.history_file or not os.path.exists(self.history_file): | |
return | |
if self.is_debug_mode: | |
sys.stderr.write("Loading history from file '%s'...\n" % self.history_file) | |
try: | |
self.commands_history = self._safe_file_read(self.history_file).splitlines() | |
except: | |
sys.stderr.write("Error while reading history from file '%s'\n" % self.history_file) | |
def save_history(self): | |
"""Save the history to a file""" | |
if not self.history_file: | |
return | |
if self.is_debug_mode: | |
sys.stderr.write("Saving history to file '%s'...\n" % self.history_file) | |
try: | |
self._safe_file_write(self.history_file, "\n".join(self.commands_history) + "\n") | |
except: | |
sys.stderr.write("Error while writing history to file '%s'\n" % self.history_file) | |
def _safe_file_read(self, filename): | |
# type: (str) -> str | |
"""Read the content from a file""" | |
try: | |
try: | |
file = open(filename, "r") | |
return file.read() | |
except: | |
raise | |
finally: | |
try: | |
file.close() | |
except: | |
pass | |
def _safe_file_write(self, filename, content): | |
# type: (str, str) -> None | |
"""Write the content to a file""" | |
try: | |
try: | |
file = open(filename, "w") | |
file.write(content) | |
except: | |
raise | |
finally: | |
try: | |
file.close() | |
except: | |
pass | |
def raw_input(self, prompt): | |
# type: (str) -> str | |
""" Write a prompt and read a line. | |
The returned line does not include the trailing newline. | |
When the user enters the EOF key sequence, EOFError is raised. | |
Args: | |
prompt (str): The prompt to display to the user. | |
Returns: | |
str: The line read from the user. | |
""" | |
line_buffer = [] | |
# Write the prompt to the console (without newline) | |
self.write(prompt) | |
# Initialize history index (last command in history list) | |
current_history_index = len(self.commands_history) | |
current_cursor_position = 0 | |
# ----> Read character by character | |
while 1: | |
keystroke = read_keystroke() | |
# ----> Any non-special character | |
if not isinstance(keystroke, KeyboardKey): | |
self.write(chr(keystroke)) # Write character to console | |
line_buffer.insert(current_cursor_position, chr(keystroke)) # Insert character at cursor position | |
current_cursor_position += 1 | |
# Force re-render of the prompt (in case the cursor is in the middle of the line) | |
self.write("\r\x1B[K" + prompt.replace("\n", "") + ''.join(line_buffer)) | |
if current_cursor_position < len(line_buffer): | |
# The cursor is moved to the left by the length of the line buffer | |
self.write("\x1B[%dD" % (len(line_buffer)-current_cursor_position)) | |
# ----> Ctrl-C | |
elif keystroke == Keyboard.Keys.CTRL_C: | |
self._debug_key_press(keystroke.name, current_history_index, current_cursor_position) | |
raise KeyboardInterrupt | |
# ----> Ctrl-D | |
elif keystroke == Keyboard.Keys.CTRL_D: | |
self._debug_key_press(keystroke.name, current_history_index, current_cursor_position) | |
raise EOFError("Received EOF: %s" % sys.exc_info()[0]) | |
# ----> Ctrl-L | |
elif keystroke == Keyboard.Keys.CTRL_L: | |
self.write("\x1B[2J\x1B[H") # Clear screen | |
self.write("\n") | |
self.write(prompt.replace("\n", "") + ''.join(line_buffer)) # Redraw prompt | |
self._debug_key_press(keystroke.name, current_history_index, current_cursor_position) | |
continue | |
# ----> Enter | |
elif keystroke == Keyboard.Keys.ENTER: | |
# ----> Add command to history (only if not empty) | |
if line_buffer and [x for x in line_buffer if x.strip() != ""]: | |
self.commands_history.append(''.join(line_buffer)) | |
self._debug_key_press(keystroke.name, current_history_index, current_cursor_position) | |
# ----> Write newline to console (results in a new line) | |
self.write("\n") | |
break | |
# ----> Backspace (delete last character) | |
elif keystroke == Keyboard.Keys.BACKSPACE: | |
if current_cursor_position == 0: | |
continue | |
# Remove character from buffer and move cursor to the left | |
line_buffer.pop(current_cursor_position-1) | |
current_cursor_position -= 1 | |
self.write("\r\x1B[K" + prompt.replace("\n", "") + ''.join(line_buffer)) | |
if current_cursor_position < len(line_buffer): | |
# The cursor is moved to the left by the length of the line buffer | |
self.write("\x1B[%dD" % (len(line_buffer)-current_cursor_position)) | |
self._debug_key_press(keystroke.name, current_history_index, current_cursor_position) | |
continue | |
# ----> A = Up (previous command) | |
elif keystroke == Keyboard.Keys.ARROW_UP: | |
# Prevent index out of range | |
if current_history_index-1 < 0: # -1 because we are going back in history | |
continue | |
current_history_index -= 1 | |
self._debug_key_press(keystroke.name, current_history_index, current_cursor_position) | |
self.write("\r\x1B[K" + prompt.replace("\n", "") + self.commands_history[current_history_index]) | |
line_buffer = list(self.commands_history[current_history_index]) # Convert string to list of characters | |
current_cursor_position = len(line_buffer) | |
continue | |
# ----> B = Down (next command) | |
elif keystroke == Keyboard.Keys.ARROW_DOWN: | |
# Prevent index out of range | |
if current_history_index+1 > len(self.commands_history): # +1 because we are going forward in history | |
continue | |
current_history_index += 1 | |
if current_history_index == len(self.commands_history): # +1 because we are going forward in history | |
# Show clear prompt if no commands in history or after the end of history (like Bash does) | |
self.write("\r\x1B[K" + prompt.replace("\n", "")) | |
line_buffer = [] | |
current_cursor_position = 0 | |
else: | |
self.write("\r\x1B[K" + prompt.replace("\n", "") + self.commands_history[current_history_index]) | |
line_buffer = list(self.commands_history[current_history_index]) # Convert string to list of characters | |
current_cursor_position = len(line_buffer) | |
self._debug_key_press(keystroke.name, current_history_index, current_cursor_position) | |
continue | |
# ----> C = Right (no action) | |
elif keystroke == Keyboard.Keys.ARROW_RIGHT: | |
# Move cursor to the right | |
if current_cursor_position < len(line_buffer): | |
self.write("\x1B[C") | |
current_cursor_position += 1 | |
self._debug_key_press(keystroke.name, current_history_index, current_cursor_position) | |
continue | |
# ----> D = Left (no action) | |
elif keystroke == Keyboard.Keys.ARROW_LEFT: | |
# Move cursor to the left | |
if current_cursor_position > 0: | |
self.write("\x1B[D") | |
current_cursor_position -= 1 | |
self._debug_key_press(keystroke.name, current_history_index, current_cursor_position) | |
continue | |
# ----> Ctrl+Right (move cursor to next word) | |
elif keystroke == Keyboard.Keys.CTRL_ARROW_RIGHT: | |
if current_cursor_position == len(line_buffer): | |
continue | |
# Move cursor to next word on the right (if current position is a space) | |
while current_cursor_position < len(line_buffer) and (line_buffer[current_cursor_position].isspace() or not is_word_character(line_buffer[current_cursor_position])): | |
self.write("\x1B[C") | |
current_cursor_position += 1 | |
# Move cursor to the right by one word | |
while current_cursor_position < len(line_buffer) and not line_buffer[current_cursor_position].isspace() and is_word_character(line_buffer[current_cursor_position]): | |
self.write("\x1B[C") | |
current_cursor_position += 1 | |
self._debug_key_press(keystroke.name, current_history_index, current_cursor_position) | |
continue | |
# ----> Ctrl+Left (move cursor to previous word) | |
elif keystroke == Keyboard.Keys.CTRL_ARROW_LEFT: | |
if current_cursor_position == 0: | |
continue | |
# Move cursor to next word on the left (if current position is a space) | |
while current_cursor_position > 0 and (line_buffer[current_cursor_position-1].isspace() or not is_word_character(line_buffer[current_cursor_position-1])): | |
self.write("\x1B[D") | |
current_cursor_position -= 1 | |
# Move cursor to the left by one word | |
while current_cursor_position > 0 and not line_buffer[current_cursor_position-1].isspace() and is_word_character(line_buffer[current_cursor_position-1]): | |
self.write("\x1B[D") | |
current_cursor_position -= 1 | |
self._debug_key_press(keystroke.name, current_history_index, current_cursor_position) | |
continue | |
# ----> 3 = Delete (delete character under cursor) | |
elif keystroke == Keyboard.Keys.DEL: | |
if current_cursor_position == len(line_buffer): | |
continue | |
# Remove character from buffer and move cursor to the left | |
line_buffer.pop(current_cursor_position) | |
self.write("\r\x1B[K" + prompt.replace("\n", "") + ''.join(line_buffer)) | |
if current_cursor_position < len(line_buffer): | |
# The cursor is moved to the left by the length of the line buffer | |
self.write("\x1B[%dD" % (len(line_buffer)-current_cursor_position)) | |
self._debug_key_press(keystroke.name, current_history_index, current_cursor_position) | |
continue | |
# ----> Home (go to start of line) | |
elif keystroke == Keyboard.Keys.HOME: | |
current_cursor_position = 0 | |
self.write("\r\x1B[K" + prompt.replace("\n", "") + ''.join(line_buffer)) | |
if len(line_buffer) > 0: | |
# The cursor is moved to the left by the length of the line buffer | |
self.write("\x1B[%dD" % len(line_buffer)) | |
self._debug_key_press(keystroke.name, current_history_index, current_cursor_position) | |
continue | |
# ----> End (go to end of line) | |
elif keystroke == Keyboard.Keys.END: | |
current_cursor_position = len(line_buffer) | |
self.write("\r\x1B[K" + prompt.replace("\n", "") + ''.join(line_buffer)) | |
self._debug_key_press(keystroke.name, current_history_index, current_cursor_position) | |
continue | |
else: | |
self._debug_key_press(keystroke, current_history_index, current_cursor_position) | |
continue | |
# raise ValueError("Detected non-implemented keystroke: %s" % keystroke) | |
self._debug_key_press(keystroke, current_history_index, current_cursor_position) | |
# ----> End of while loop | |
return ''.join(line_buffer) | |
def push(self, line): | |
"""Push a line to the interpreter""" | |
return code.InteractiveConsole.push(self, line) | |
def runsource(self, source, filename="<input>", symbol="single"): | |
"""Run the source code""" | |
self._write_debug( | |
"source=%(source)-12s | file=%(filename)-12s | symbol=%(symbol)-12s" % { | |
"source": (source | |
.replace("\n", "\\n") | |
.replace("\r", "\\r") | |
.replace("\t", "\\t") | |
.replace("\v", "\\v") | |
.replace("\f", "\\f") | |
.replace("\b", "\\b") | |
.replace("\a", "\\a") | |
), | |
"filename": filename, | |
"symbol": symbol, | |
}, | |
line_index=+1, | |
) | |
# TODO: Python 2.x compatibility | |
# - Allow multiline assignment with triple quotes (`string = """first line\nsecond line"""`) | |
# - Allow multiline assignment with backslash (`string = "test \\ntest2"`) | |
# - Allow multiline assignment with parenthesis (`string = (\n"test"\n)`) | |
# - Allow multiline assignment with brackets (`array = [\n]` , `dictionary = {\n}`) | |
needs_next_line = code.InteractiveConsole.runsource(self, source, filename, symbol) | |
if needs_next_line == 1: | |
self.current_command_rows += 1 | |
else: | |
self.current_command_rows = 0 | |
return needs_next_line | |
def write(self, data): | |
"""Write data to the console""" | |
sys.stdout.write(data) | |
def _write_debug(self, data, file=sys.stdout, flush=1==1, line_index=0): | |
"""Write debug data to the console""" | |
if not self.is_debug_mode: | |
return | |
# Save the current cursor position | |
file.write("\x1B[s") | |
# Move the cursor up one line and update debug data | |
file.write("\x1B[%dA\x1B[2K\r" % (self.current_command_rows + line_index + 1)) | |
file.write(data) | |
# Restore the cursor position | |
file.write("\x1B[u") | |
if flush: | |
file.flush() | |
def _debug_key_press(self, key, history_index, position_x): | |
"""Debug the key pressed""" | |
current_command = "" | |
if len(self.commands_history) > 0 and history_index < len(self.commands_history): | |
current_command = self.commands_history[history_index] | |
self._write_debug("key=%(key_pressed)-12s | hist=%(history_current)03d/%(history_total)03d | posx=%(position_x)03d/%(position_max)03d" % { | |
"key_pressed": key, | |
"history_current": history_index, | |
"history_total": len(self.commands_history), | |
"position_x": position_x, | |
"position_max": len(current_command), | |
}) | |
if __name__ == "__main__": | |
# Enable debug mode if any of the following environment variables are set | |
# - PYTHONDEBUG | |
# - PYTHONVERBOSE | |
debug_mode = ( | |
os.environ.get("PYTHONDEBUG", "") != "" | |
or os.environ.get("PYTHONVERBOSE", "") != "" | |
) | |
history_file = os.environ.get("PYTHON_HISTORY", None) | |
# Start the REPL console | |
repl = ModernConsole( | |
debug=debug_mode, | |
history_file=history_file, | |
) | |
sys.stderr.write("Console started. Press CTRL+D to exit.\n") | |
try: | |
repl.interact( | |
banner="Python %s on %s\n(%s)\n" % (sys.version, sys.platform, "ModernConsole") | |
) | |
finally: | |
sys.stderr.write("Exiting console...\n") | |
_tty_set_cooked_mode() | |
# Save the history to a file when the console is closed | |
try: | |
repl.save_history() | |
except: | |
sys.stderr.write("Error while saving history: %s\n" % sys.exc_info()[1]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment