minecraftd/venv/lib/python3.11/site-packages/rich_toolkit/_input_handler.py

132 lines
4.3 KiB
Python

"""Unified input handler for all platforms."""
import sys
import unicodedata
class TextInputHandler:
"""Input handler with platform-specific key code support."""
# Platform-specific key codes
if sys.platform == "win32":
# Windows uses \xe0 prefix for special keys when using msvcrt.getwch
DOWN_KEY = "\xe0P" # Down arrow
UP_KEY = "\xe0H" # Up arrow
LEFT_KEY = "\xe0K" # Left arrow
RIGHT_KEY = "\xe0M" # Right arrow
DELETE_KEY = "\xe0S" # Delete key
BACKSPACE_KEY = "\x08" # Backspace
TAB_KEY = "\t"
SHIFT_TAB_KEY = "\x00\x0f" # Shift+Tab
ENTER_KEY = "\r"
# Alternative codes that might be sent
ALT_BACKSPACE = "\x7f"
ALT_DELETE = "\x00S"
else:
# Unix/Linux key codes (ANSI escape sequences)
DOWN_KEY = "\x1b[B"
UP_KEY = "\x1b[A"
LEFT_KEY = "\x1b[D"
RIGHT_KEY = "\x1b[C"
BACKSPACE_KEY = "\x7f"
DELETE_KEY = "\x1b[3~"
TAB_KEY = "\t"
SHIFT_TAB_KEY = "\x1b[Z"
ENTER_KEY = "\r"
# Alternative codes
ALT_BACKSPACE = "\x08"
ALT_DELETE = None
def __init__(self):
self.text = ""
self._cursor_index = 0 # Character index in the text string
@property
def cursor_left(self) -> int:
"""Visual cursor position in display columns."""
return self._get_text_width(self.text[: self._cursor_index])
@staticmethod
def _get_char_width(char: str) -> int:
"""Get the display width of a character (1 for normal, 2 for CJK/fullwidth)."""
if not char:
return 0
# Check East Asian Width property
east_asian_width = unicodedata.east_asian_width(char)
# F (Fullwidth) and W (Wide) characters take 2 columns
if east_asian_width in ("F", "W"):
return 2
# A (Ambiguous) characters are typically 2 columns in CJK contexts
# but for simplicity we'll treat them as 1 (can be made configurable)
return 1
def _get_text_width(self, text: str) -> int:
"""Get the total display width of a text string."""
return sum(self._get_char_width(char) for char in text)
def _move_cursor_left(self) -> None:
self._cursor_index = max(0, self._cursor_index - 1)
def _move_cursor_right(self) -> None:
self._cursor_index = min(len(self.text), self._cursor_index + 1)
def _insert_char(self, char: str) -> None:
self.text = (
self.text[: self._cursor_index] + char + self.text[self._cursor_index :]
)
self._cursor_index += 1
def _delete_char(self) -> None:
"""Delete character before cursor (backspace)."""
if self._cursor_index == 0:
return
self.text = (
self.text[: self._cursor_index - 1] + self.text[self._cursor_index :]
)
self._cursor_index -= 1
def _delete_forward(self) -> None:
"""Delete character at cursor (delete key)."""
if self._cursor_index >= len(self.text):
return
self.text = (
self.text[: self._cursor_index] + self.text[self._cursor_index + 1 :]
)
def handle_key(self, key: str) -> None:
# Handle backspace (both possible codes)
if key == self.BACKSPACE_KEY or (
self.ALT_BACKSPACE and key == self.ALT_BACKSPACE
):
self._delete_char()
# Handle delete key
elif key == self.DELETE_KEY or (self.ALT_DELETE and key == self.ALT_DELETE):
self._delete_forward()
elif key == self.LEFT_KEY:
self._move_cursor_left()
elif key == self.RIGHT_KEY:
self._move_cursor_right()
elif key in (
self.UP_KEY,
self.DOWN_KEY,
self.ENTER_KEY,
self.SHIFT_TAB_KEY,
self.TAB_KEY,
):
pass
else:
# Handle regular text input
# Special keys on Windows start with \x00 or \xe0
if sys.platform == "win32" and key and key[0] in ("\x00", "\xe0"):
# Skip special key sequences
return
# Even if we call this handle_key, in some cases we might receive
# multiple keys at once (e.g., during paste operations)
for char in key:
self._insert_char(char)