import sys import termios from typing import Optional, TextIO from agame.color import colorize from .display import Display ESC = "\u001b" ANSI_MOVE_BOTTOM = f"{ESC}[999;0f" class ANSIDisplay(Display): """ Display for a modern terminal. This display attempts to keep the input at the bottom, and the output scrolling up. This utilizes `termios` and is therefore UNIX-only. """ def __init__( self, input_prompt: str = colorize("{{>}} "), stdin: TextIO = sys.stdin, stdout: TextIO = sys.stdout, ): self.input_prompt = input_prompt self.__stdin = stdin self.__stdout = stdout # Initial set-up: # . Move the cursor to the bottom left part of the screen (this will set # the current line to a really high value and will move us to the # bottom of the screen) self.stdout.write(ANSI_MOVE_BOTTOM) # . Disable echo, set cbreak fd = self.stdin.fileno() self.__old_attrs = termios.tcgetattr(fd) self.cbreak() # . Clear self.clear() def line(self, line: str = ""): self.stdout.write(line) self.stdout.write("\n") def input(self) -> str: try: self.nocbreak() return input(self.input_prompt) finally: self.cbreak() def wait_for_ack(self, prompt: Optional[str] = "Press any key to continue..."): # Flush unread data termios.tcflush(self.stdin.fileno(), termios.TCIFLUSH) if prompt is not None: self.line(prompt) self.stdin.read(1) def cbreak(self): fd = self.stdin.fileno() new = termios.tcgetattr(fd) new[3] &= ~(termios.ECHO | termios.ICANON) termios.tcsetattr(fd, termios.TCSADRAIN, new) def nocbreak(self): fd = self.stdin.fileno() new = termios.tcgetattr(fd) new[3] |= termios.ECHO | termios.ICANON termios.tcsetattr(fd, termios.TCSADRAIN, new) def finish(self): # Clean up by restoring the old terminal state fd = self.stdin.fileno() termios.tcsetattr(fd, termios.TCSADRAIN, self.__old_attrs) def clear(self): self.stdout.write(f"{ESC}[2J") @property def stdin(self) -> TextIO: return self.__stdin @property def stdout(self) -> TextIO: return self.__stdout