| 
									
										
										
										
											2024-05-05 21:32:23 +02:00
										 |  |  | #   Copyright 2000-2004 Michael Hudson-Doyle <micahel@gmail.com> | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | #                        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. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | from __future__ import annotations | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-31 16:26:02 -04:00
										 |  |  | import _colorize  # type: ignore[import-not-found] | 
					
						
							| 
									
										
										
										
											2024-05-31 00:49:03 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-05 21:32:23 +02:00
										 |  |  | from abc import ABC, abstractmethod | 
					
						
							| 
									
										
										
										
											2024-05-31 16:26:02 -04:00
										 |  |  | import ast | 
					
						
							|  |  |  | import code | 
					
						
							| 
									
										
										
										
											2025-03-10 21:54:05 +00:00
										 |  |  | import linecache | 
					
						
							| 
									
										
										
										
											2024-05-05 21:32:23 +02:00
										 |  |  | from dataclasses import dataclass, field | 
					
						
							| 
									
										
										
										
											2024-05-31 16:26:02 -04:00
										 |  |  | import os.path | 
					
						
							|  |  |  | import sys | 
					
						
							| 
									
										
										
										
											2024-05-05 21:32:23 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-31 00:49:03 -07:00
										 |  |  | TYPE_CHECKING = False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | if TYPE_CHECKING: | 
					
						
							|  |  |  |     from typing import IO | 
					
						
							| 
									
										
										
										
											2024-06-04 19:32:43 +01:00
										 |  |  |     from typing import Callable | 
					
						
							| 
									
										
										
										
											2024-05-31 00:49:03 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-05 21:32:23 +02:00
										 |  |  | @dataclass | 
					
						
							|  |  |  | class Event: | 
					
						
							|  |  |  |     evt: str | 
					
						
							|  |  |  |     data: str | 
					
						
							|  |  |  |     raw: bytes = b"" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @dataclass | 
					
						
							|  |  |  | class Console(ABC): | 
					
						
							| 
									
										
										
										
											2025-01-21 22:04:30 +01:00
										 |  |  |     posxy: tuple[int, int] | 
					
						
							| 
									
										
										
										
											2024-05-05 21:32:23 +02:00
										 |  |  |     screen: list[str] = field(default_factory=list) | 
					
						
							|  |  |  |     height: int = 25 | 
					
						
							|  |  |  |     width: int = 80 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-31 00:49:03 -07:00
										 |  |  |     def __init__( | 
					
						
							|  |  |  |         self, | 
					
						
							|  |  |  |         f_in: IO[bytes] | int = 0, | 
					
						
							|  |  |  |         f_out: IO[bytes] | int = 1, | 
					
						
							|  |  |  |         term: str = "", | 
					
						
							|  |  |  |         encoding: str = "", | 
					
						
							|  |  |  |     ): | 
					
						
							|  |  |  |         self.encoding = encoding or sys.getdefaultencoding() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if isinstance(f_in, int): | 
					
						
							|  |  |  |             self.input_fd = f_in | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self.input_fd = f_in.fileno() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if isinstance(f_out, int): | 
					
						
							|  |  |  |             self.output_fd = f_out | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self.output_fd = f_out.fileno() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-05 21:32:23 +02:00
										 |  |  |     @abstractmethod | 
					
						
							|  |  |  |     def refresh(self, screen: list[str], xy: tuple[int, int]) -> None: ... | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @abstractmethod | 
					
						
							|  |  |  |     def prepare(self) -> None: ... | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @abstractmethod | 
					
						
							|  |  |  |     def restore(self) -> None: ... | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @abstractmethod | 
					
						
							|  |  |  |     def move_cursor(self, x: int, y: int) -> None: ... | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @abstractmethod | 
					
						
							|  |  |  |     def set_cursor_vis(self, visible: bool) -> None: ... | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @abstractmethod | 
					
						
							|  |  |  |     def getheightwidth(self) -> tuple[int, int]: | 
					
						
							|  |  |  |         """Return (height, width) where height and width are the height
 | 
					
						
							|  |  |  |         and width of the terminal window in characters."""
 | 
					
						
							|  |  |  |         ... | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @abstractmethod | 
					
						
							|  |  |  |     def get_event(self, block: bool = True) -> Event | None: | 
					
						
							|  |  |  |         """Return an Event instance.  Returns None if |block| is false
 | 
					
						
							|  |  |  |         and there is no event pending, otherwise waits for the | 
					
						
							|  |  |  |         completion of an event."""
 | 
					
						
							|  |  |  |         ... | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @abstractmethod | 
					
						
							|  |  |  |     def push_char(self, char: int | bytes) -> None: | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         Push a character to the console event queue. | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         ... | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @abstractmethod | 
					
						
							|  |  |  |     def beep(self) -> None: ... | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @abstractmethod | 
					
						
							|  |  |  |     def clear(self) -> None: | 
					
						
							|  |  |  |         """Wipe the screen""" | 
					
						
							|  |  |  |         ... | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @abstractmethod | 
					
						
							|  |  |  |     def finish(self) -> None: | 
					
						
							|  |  |  |         """Move the cursor to the end of the display and otherwise get
 | 
					
						
							|  |  |  |         ready for end.  XXX could be merged with restore?  Hmm."""
 | 
					
						
							|  |  |  |         ... | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @abstractmethod | 
					
						
							|  |  |  |     def flushoutput(self) -> None: | 
					
						
							|  |  |  |         """Flush all output to the screen (assuming there's some
 | 
					
						
							|  |  |  |         buffering going on somewhere)."""
 | 
					
						
							|  |  |  |         ... | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @abstractmethod | 
					
						
							|  |  |  |     def forgetinput(self) -> None: | 
					
						
							|  |  |  |         """Forget all pending, but not yet processed input.""" | 
					
						
							|  |  |  |         ... | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @abstractmethod | 
					
						
							|  |  |  |     def getpending(self) -> Event: | 
					
						
							|  |  |  |         """Return the characters that have been typed but not yet
 | 
					
						
							|  |  |  |         processed."""
 | 
					
						
							|  |  |  |         ... | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @abstractmethod | 
					
						
							| 
									
										
										
										
											2024-06-04 19:32:43 +01:00
										 |  |  |     def wait(self, timeout: float | None) -> bool: | 
					
						
							|  |  |  |         """Wait for an event. The return value is True if an event is
 | 
					
						
							|  |  |  |         available, False if the timeout has been reached. If timeout is | 
					
						
							|  |  |  |         None, wait forever. The timeout is in milliseconds."""
 | 
					
						
							|  |  |  |         ... | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def input_hook(self) -> Callable[[], int] | None: | 
					
						
							|  |  |  |         """Returns the current input hook.""" | 
					
						
							| 
									
										
										
										
											2024-05-05 21:32:23 +02:00
										 |  |  |         ... | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @abstractmethod | 
					
						
							| 
									
										
										
										
											2024-05-31 00:49:03 -07:00
										 |  |  |     def repaint(self) -> None: ... | 
					
						
							| 
									
										
										
										
											2024-05-31 16:26:02 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class InteractiveColoredConsole(code.InteractiveConsole): | 
					
						
							| 
									
										
										
										
											2025-02-24 15:50:13 +01:00
										 |  |  |     STATEMENT_FAILED = object() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-31 16:26:02 -04:00
										 |  |  |     def __init__( | 
					
						
							|  |  |  |         self, | 
					
						
							|  |  |  |         locals: dict[str, object] | None = None, | 
					
						
							|  |  |  |         filename: str = "<console>", | 
					
						
							|  |  |  |         *, | 
					
						
							|  |  |  |         local_exit: bool = False, | 
					
						
							|  |  |  |     ) -> None: | 
					
						
							|  |  |  |         super().__init__(locals=locals, filename=filename, local_exit=local_exit)  # type: ignore[call-arg] | 
					
						
							|  |  |  |         self.can_colorize = _colorize.can_colorize() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-19 17:19:23 +03:00
										 |  |  |     def showsyntaxerror(self, filename=None, **kwargs): | 
					
						
							| 
									
										
										
										
											2024-08-22 14:55:30 +03:00
										 |  |  |         super().showsyntaxerror(filename=filename, **kwargs) | 
					
						
							| 
									
										
										
										
											2024-08-19 17:19:23 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-18 13:28:23 +02:00
										 |  |  |     def _excepthook(self, typ, value, tb): | 
					
						
							|  |  |  |         import traceback | 
					
						
							|  |  |  |         lines = traceback.format_exception( | 
					
						
							|  |  |  |                 typ, value, tb, | 
					
						
							|  |  |  |                 colorize=self.can_colorize, | 
					
						
							|  |  |  |                 limit=traceback.BUILTIN_EXCEPTION_LIMIT) | 
					
						
							|  |  |  |         self.write(''.join(lines)) | 
					
						
							| 
									
										
										
										
											2024-05-31 16:26:02 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-24 15:50:13 +01:00
										 |  |  |     def runcode(self, code): | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             exec(code, self.locals) | 
					
						
							|  |  |  |         except SystemExit: | 
					
						
							|  |  |  |             raise | 
					
						
							|  |  |  |         except BaseException: | 
					
						
							|  |  |  |             self.showtraceback() | 
					
						
							|  |  |  |             return self.STATEMENT_FAILED | 
					
						
							|  |  |  |         return None | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-31 16:26:02 -04:00
										 |  |  |     def runsource(self, source, filename="<input>", symbol="single"): | 
					
						
							|  |  |  |         try: | 
					
						
							| 
									
										
										
										
											2024-10-14 21:53:50 +08:00
										 |  |  |             tree = self.compile.compiler( | 
					
						
							|  |  |  |                 source, | 
					
						
							|  |  |  |                 filename, | 
					
						
							|  |  |  |                 "exec", | 
					
						
							|  |  |  |                 ast.PyCF_ONLY_AST, | 
					
						
							|  |  |  |                 incomplete_input=False, | 
					
						
							|  |  |  |             ) | 
					
						
							| 
									
										
										
										
											2024-05-31 16:26:02 -04:00
										 |  |  |         except (SyntaxError, OverflowError, ValueError): | 
					
						
							| 
									
										
										
										
											2024-08-19 17:19:23 +03:00
										 |  |  |             self.showsyntaxerror(filename, source=source) | 
					
						
							| 
									
										
										
										
											2024-05-31 16:26:02 -04:00
										 |  |  |             return False | 
					
						
							|  |  |  |         if tree.body: | 
					
						
							|  |  |  |             *_, last_stmt = tree.body | 
					
						
							|  |  |  |         for stmt in tree.body: | 
					
						
							|  |  |  |             wrapper = ast.Interactive if stmt is last_stmt else ast.Module | 
					
						
							|  |  |  |             the_symbol = symbol if stmt is last_stmt else "exec" | 
					
						
							|  |  |  |             item = wrapper([stmt]) | 
					
						
							|  |  |  |             try: | 
					
						
							| 
									
										
										
										
											2024-10-14 21:53:50 +08:00
										 |  |  |                 code = self.compile.compiler(item, filename, the_symbol) | 
					
						
							| 
									
										
										
										
											2025-03-10 21:54:05 +00:00
										 |  |  |                 linecache._register_code(code, source, filename) | 
					
						
							| 
									
										
										
										
											2024-05-31 16:26:02 -04:00
										 |  |  |             except SyntaxError as e: | 
					
						
							|  |  |  |                 if e.args[0] == "'await' outside function": | 
					
						
							|  |  |  |                     python = os.path.basename(sys.executable) | 
					
						
							|  |  |  |                     e.add_note( | 
					
						
							|  |  |  |                         f"Try the asyncio REPL ({python} -m asyncio) to use" | 
					
						
							|  |  |  |                         f" top-level 'await' and run background asyncio tasks." | 
					
						
							|  |  |  |                     ) | 
					
						
							| 
									
										
										
										
											2024-08-19 17:19:23 +03:00
										 |  |  |                 self.showsyntaxerror(filename, source=source) | 
					
						
							| 
									
										
										
										
											2024-05-31 16:26:02 -04:00
										 |  |  |                 return False | 
					
						
							|  |  |  |             except (OverflowError, ValueError): | 
					
						
							| 
									
										
										
										
											2024-08-19 17:19:23 +03:00
										 |  |  |                 self.showsyntaxerror(filename, source=source) | 
					
						
							| 
									
										
										
										
											2024-05-31 16:26:02 -04:00
										 |  |  |                 return False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if code is None: | 
					
						
							|  |  |  |                 return True | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-24 15:50:13 +01:00
										 |  |  |             result = self.runcode(code) | 
					
						
							|  |  |  |             if result is self.STATEMENT_FAILED: | 
					
						
							|  |  |  |                 break | 
					
						
							| 
									
										
										
										
											2024-05-31 16:26:02 -04:00
										 |  |  |         return False |