| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132 |
- """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)
|