python (3.11.7)
       1  import sys
       2  import time
       3  from typing import TYPE_CHECKING, Callable, Dict, Iterable, List, Union
       4  
       5  if sys.version_info >= (3, 8):
       6      from typing import Final
       7  else:
       8      from pip._vendor.typing_extensions import Final  # pragma: no cover
       9  
      10  from .segment import ControlCode, ControlType, Segment
      11  
      12  if TYPE_CHECKING:
      13      from .console import Console, ConsoleOptions, RenderResult
      14  
      15  STRIP_CONTROL_CODES: Final = [
      16      7,  # Bell
      17      8,  # Backspace
      18      11,  # Vertical tab
      19      12,  # Form feed
      20      13,  # Carriage return
      21  ]
      22  _CONTROL_STRIP_TRANSLATE: Final = {
      23      _codepoint: None for _codepoint in STRIP_CONTROL_CODES
      24  }
      25  
      26  CONTROL_ESCAPE: Final = {
      27      7: "\\a",
      28      8: "\\b",
      29      11: "\\v",
      30      12: "\\f",
      31      13: "\\r",
      32  }
      33  
      34  CONTROL_CODES_FORMAT: Dict[int, Callable[..., str]] = {
      35      ControlType.BELL: lambda: "\x07",
      36      ControlType.CARRIAGE_RETURN: lambda: "\r",
      37      ControlType.HOME: lambda: "\x1b[H",
      38      ControlType.CLEAR: lambda: "\x1b[2J",
      39      ControlType.ENABLE_ALT_SCREEN: lambda: "\x1b[?1049h",
      40      ControlType.DISABLE_ALT_SCREEN: lambda: "\x1b[?1049l",
      41      ControlType.SHOW_CURSOR: lambda: "\x1b[?25h",
      42      ControlType.HIDE_CURSOR: lambda: "\x1b[?25l",
      43      ControlType.CURSOR_UP: lambda param: f"\x1b[{param}A",
      44      ControlType.CURSOR_DOWN: lambda param: f"\x1b[{param}B",
      45      ControlType.CURSOR_FORWARD: lambda param: f"\x1b[{param}C",
      46      ControlType.CURSOR_BACKWARD: lambda param: f"\x1b[{param}D",
      47      ControlType.CURSOR_MOVE_TO_COLUMN: lambda param: f"\x1b[{param+1}G",
      48      ControlType.ERASE_IN_LINE: lambda param: f"\x1b[{param}K",
      49      ControlType.CURSOR_MOVE_TO: lambda x, y: f"\x1b[{y+1};{x+1}H",
      50      ControlType.SET_WINDOW_TITLE: lambda title: f"\x1b]0;{title}\x07",
      51  }
      52  
      53  
      54  class ESC[4;38;5;81mControl:
      55      """A renderable that inserts a control code (non printable but may move cursor).
      56  
      57      Args:
      58          *codes (str): Positional arguments are either a :class:`~rich.segment.ControlType` enum or a
      59              tuple of ControlType and an integer parameter
      60      """
      61  
      62      __slots__ = ["segment"]
      63  
      64      def __init__(self, *codes: Union[ControlType, ControlCode]) -> None:
      65          control_codes: List[ControlCode] = [
      66              (code,) if isinstance(code, ControlType) else code for code in codes
      67          ]
      68          _format_map = CONTROL_CODES_FORMAT
      69          rendered_codes = "".join(
      70              _format_map[code](*parameters) for code, *parameters in control_codes
      71          )
      72          self.segment = Segment(rendered_codes, None, control_codes)
      73  
      74      @classmethod
      75      def bell(cls) -> "Control":
      76          """Ring the 'bell'."""
      77          return cls(ControlType.BELL)
      78  
      79      @classmethod
      80      def home(cls) -> "Control":
      81          """Move cursor to 'home' position."""
      82          return cls(ControlType.HOME)
      83  
      84      @classmethod
      85      def move(cls, x: int = 0, y: int = 0) -> "Control":
      86          """Move cursor relative to current position.
      87  
      88          Args:
      89              x (int): X offset.
      90              y (int): Y offset.
      91  
      92          Returns:
      93              ~Control: Control object.
      94  
      95          """
      96  
      97          def get_codes() -> Iterable[ControlCode]:
      98              control = ControlType
      99              if x:
     100                  yield (
     101                      control.CURSOR_FORWARD if x > 0 else control.CURSOR_BACKWARD,
     102                      abs(x),
     103                  )
     104              if y:
     105                  yield (
     106                      control.CURSOR_DOWN if y > 0 else control.CURSOR_UP,
     107                      abs(y),
     108                  )
     109  
     110          control = cls(*get_codes())
     111          return control
     112  
     113      @classmethod
     114      def move_to_column(cls, x: int, y: int = 0) -> "Control":
     115          """Move to the given column, optionally add offset to row.
     116  
     117          Returns:
     118              x (int): absolute x (column)
     119              y (int): optional y offset (row)
     120  
     121          Returns:
     122              ~Control: Control object.
     123          """
     124  
     125          return (
     126              cls(
     127                  (ControlType.CURSOR_MOVE_TO_COLUMN, x),
     128                  (
     129                      ControlType.CURSOR_DOWN if y > 0 else ControlType.CURSOR_UP,
     130                      abs(y),
     131                  ),
     132              )
     133              if y
     134              else cls((ControlType.CURSOR_MOVE_TO_COLUMN, x))
     135          )
     136  
     137      @classmethod
     138      def move_to(cls, x: int, y: int) -> "Control":
     139          """Move cursor to absolute position.
     140  
     141          Args:
     142              x (int): x offset (column)
     143              y (int): y offset (row)
     144  
     145          Returns:
     146              ~Control: Control object.
     147          """
     148          return cls((ControlType.CURSOR_MOVE_TO, x, y))
     149  
     150      @classmethod
     151      def clear(cls) -> "Control":
     152          """Clear the screen."""
     153          return cls(ControlType.CLEAR)
     154  
     155      @classmethod
     156      def show_cursor(cls, show: bool) -> "Control":
     157          """Show or hide the cursor."""
     158          return cls(ControlType.SHOW_CURSOR if show else ControlType.HIDE_CURSOR)
     159  
     160      @classmethod
     161      def alt_screen(cls, enable: bool) -> "Control":
     162          """Enable or disable alt screen."""
     163          if enable:
     164              return cls(ControlType.ENABLE_ALT_SCREEN, ControlType.HOME)
     165          else:
     166              return cls(ControlType.DISABLE_ALT_SCREEN)
     167  
     168      @classmethod
     169      def title(cls, title: str) -> "Control":
     170          """Set the terminal window title
     171  
     172          Args:
     173              title (str): The new terminal window title
     174          """
     175          return cls((ControlType.SET_WINDOW_TITLE, title))
     176  
     177      def __str__(self) -> str:
     178          return self.segment.text
     179  
     180      def __rich_console__(
     181          self, console: "Console", options: "ConsoleOptions"
     182      ) -> "RenderResult":
     183          if self.segment.text:
     184              yield self.segment
     185  
     186  
     187  def strip_control_codes(
     188      text: str, _translate_table: Dict[int, None] = _CONTROL_STRIP_TRANSLATE
     189  ) -> str:
     190      """Remove control codes from text.
     191  
     192      Args:
     193          text (str): A string possibly contain control codes.
     194  
     195      Returns:
     196          str: String with control codes removed.
     197      """
     198      return text.translate(_translate_table)
     199  
     200  
     201  def escape_control_codes(
     202      text: str,
     203      _translate_table: Dict[int, str] = CONTROL_ESCAPE,
     204  ) -> str:
     205      """Replace control codes with their "escaped" equivalent in the given text.
     206      (e.g. "\b" becomes "\\b")
     207  
     208      Args:
     209          text (str): A string possibly containing control codes.
     210  
     211      Returns:
     212          str: String with control codes replaced with their escaped version.
     213      """
     214      return text.translate(_translate_table)
     215  
     216  
     217  if __name__ == "__main__":  # pragma: no cover
     218      from pip._vendor.rich.console import Console
     219  
     220      console = Console()
     221      console.print("Look at the title of your terminal window ^")
     222      # console.print(Control((ControlType.SET_WINDOW_TITLE, "Hello, world!")))
     223      for i in range(10):
     224          console.set_window_title("🚀 Loading" + "." * i)
     225          time.sleep(0.5)