pytermgui.ansi_interface
Various functions to interface with the terminal, using ANSI sequences.
Credits:
- https://wiki.bash-hackers.org/scripting/terminalcodes
- https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797
View Source
0""" 1Various functions to interface with the terminal, using ANSI sequences. 2 3Credits: 4 5- https://wiki.bash-hackers.org/scripting/terminalcodes 6- https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797 7""" 8 9# The entirety of Terminal will soon be moved over to a new submodule, so 10# this ignore is temporary. 11# pylint: disable=too-many-lines 12 13from __future__ import annotations 14 15import re 16 17from typing import Optional, Any, Union, Pattern 18from dataclasses import dataclass, fields 19from enum import Enum, auto as _auto 20from os import name as _name, system 21 22from .input import getch 23from .terminal import terminal 24 25__all__ = [ 26 "save_screen", 27 "restore_screen", 28 "set_alt_buffer", 29 "unset_alt_buffer", 30 "clear", 31 "hide_cursor", 32 "show_cursor", 33 "save_cursor", 34 "restore_cursor", 35 "report_cursor", 36 "move_cursor", 37 "cursor_up", 38 "cursor_down", 39 "cursor_right", 40 "cursor_left", 41 "cursor_next_line", 42 "cursor_prev_line", 43 "cursor_column", 44 "cursor_home", 45 "set_echo", 46 "unset_echo", 47 "set_mode", 48 "MouseAction", 49 "MouseEvent", 50 "report_mouse", 51 "translate_mouse", 52 "print_to", 53 "reset", 54 "bold", 55 "dim", 56 "italic", 57 "underline", 58 "blink", 59 "inverse", 60 "invisible", 61 "strikethrough", 62 "overline", 63] 64 65 66RE_MOUSE: dict[str, Pattern] = { 67 "decimal_xterm": re.compile(r"<(\d{1,2})\;(\d{1,3})\;(\d{1,3})(\w)"), 68 "decimal_urxvt": re.compile(r"(\d{1,2})\;(\d{1,3})\;(\d{1,3})()"), 69} 70 71 72# screen commands 73def save_screen() -> None: 74 """Saves the contents of the screen, and wipes it. 75 76 Use `restore_screen()` to get them back. 77 """ 78 79 print("\x1b[?47h") 80 81 82def restore_screen() -> None: 83 """Restores the contents of the screen saved by `save_screen()`.""" 84 85 print("\x1b[?47l") 86 87 88def set_alt_buffer() -> None: 89 """Starts an alternate buffer.""" 90 91 print("\x1b[?1049h") 92 93 94def unset_alt_buffer() -> None: 95 """Returns to main buffer, restoring its original state.""" 96 97 print("\x1b[?1049l") 98 99 100def clear(what: str = "screen") -> None: 101 """Clears the specified screen region. 102 103 Args: 104 what: The specifier defining the screen area. 105 106 Available options: 107 * screen: clear whole screen and go to origin 108 * bos: clear screen from cursor backwards 109 * eos: clear screen from cursor forwards 110 * line: clear line and go to beginning 111 * bol: clear line from cursor backwards 112 * eol: clear line from cursor forwards 113 """ 114 115 commands = { 116 "eos": "\x1b[0J", 117 "bos": "\x1b[1J", 118 "screen": "\x1b[2J", 119 "eol": "\x1b[0K", 120 "bol": "\x1b[1K", 121 "line": "\x1b[2K", 122 } 123 124 terminal.write(commands[what]) 125 126 127# cursor commands 128def hide_cursor() -> None: 129 """Stops printing the cursor.""" 130 131 print("\x1b[?25l") 132 133 134def show_cursor() -> None: 135 """Starts printing the cursor.""" 136 137 print("\x1b[?25h") 138 139 140def save_cursor() -> None: 141 """Saves the current cursor position. 142 143 Use `restore_cursor()` to restore it. 144 """ 145 146 terminal.write("\x1b[s") 147 148 149def restore_cursor() -> None: 150 """Restore cursor position as saved by `save_cursor`.""" 151 152 terminal.write("\x1b[u") 153 154 155def report_cursor() -> tuple[int, int] | None: 156 """Gets position of cursor. 157 158 Returns: 159 A tuple of integers, (columns, rows), describing the 160 current (printing) cursor's position. Returns None if 161 this could not be determined. 162 163 Note that this position is **not** the mouse position. See 164 `report_mouse` if that is what you are interested in. 165 """ 166 167 print("\x1b[6n") 168 chars = getch() 169 posy, posx = chars[2:-1].split(";") 170 171 if not posx.isdigit() or not posy.isdigit(): 172 return None 173 174 return int(posx), int(posy) 175 176 177def move_cursor(pos: tuple[int, int]) -> None: 178 """Moves the cursor. 179 180 Args: 181 pos: Tuple of (columns, rows) that the cursor will be moved to. 182 183 This does not flush the terminal for performance reasons. You 184 can do it manually with `sys.stdout.flush()`. 185 """ 186 187 posx, posy = pos 188 terminal.write(f"\x1b[{posy};{posx}H") 189 190 191def cursor_up(num: int = 1) -> None: 192 """Moves the cursor up by `num` lines. 193 194 Args: 195 num: How many lines the cursor should move by. Must be positive, 196 to move in the opposite direction use `cursor_down`. 197 Note: 198 This does not flush the terminal for performance reasons. You 199 can do it manually with `sys.stdout.flush()`. 200 """ 201 202 terminal.write(f"\x1b[{num}A") 203 204 205def cursor_down(num: int = 1) -> None: 206 """Moves the cursor up by `num` lines. 207 208 Args: 209 num: How many lines the cursor should move by. Must be positive, 210 to move in the opposite direction use `cursor_up`. 211 Note: 212 This does not flush the terminal for performance reasons. You 213 can do it manually with `sys.stdout.flush()`. 214 """ 215 216 terminal.write(f"\x1b[{num}B") 217 218 219def cursor_right(num: int = 1) -> None: 220 """Moves the cursor right by `num` lines. 221 222 Args: 223 num: How many characters the cursor should move by. Must be positive, 224 to move in the opposite direction use `cursor_left`. 225 Note: 226 This does not flush the terminal for performance reasons. You 227 can do it manually with `sys.stdout.flush()`. 228 """ 229 230 terminal.write(f"\x1b[{num}C") 231 232 233def cursor_left(num: int = 1) -> None: 234 """Moves the cursor left by `num` lines. 235 236 Args: 237 num: How many characters the cursor should move by. Must be positive, 238 to move in the opposite direction use `cursor_right`. 239 Note: 240 This does not flush the terminal for performance reasons. You 241 can do it manually with `sys.stdout.flush()`. 242 """ 243 244 terminal.write(f"\x1b[{num}D") 245 246 247def cursor_next_line(num: int = 1) -> None: 248 """Moves the cursor to the beginning of the `num`-th line downwards. 249 250 Args: 251 num: The amount the cursor should move by. Must be positive, to move 252 in the opposite direction use `cursor_prev_line`. 253 Note: 254 This does not flush the terminal for performance reasons. You 255 can do it manually with `sys.stdout.flush()`. 256 """ 257 258 terminal.write(f"\x1b[{num}E") 259 260 261def cursor_prev_line(num: int = 1) -> None: 262 """Moves the cursor to the beginning of the `num`-th line upwards. 263 264 Args: 265 num: The amount the cursor should move by. Must be positive, to move 266 in the opposite direction use `cursor_next_line`. 267 Note: 268 This does not flush the terminal for performance reasons. You 269 can do it manually with `sys.stdout.flush()`. 270 """ 271 272 terminal.write(f"\x1b[{num}F") 273 274 275def cursor_column(num: int = 0) -> None: 276 """Moves the cursor to the `num`-th character of the current line. 277 278 Args: 279 num: The new cursor position. 280 281 Note: 282 This does not flush the terminal for performance reasons. You 283 can do it manually with `sys.stdout.flush()`. 284 """ 285 286 terminal.write(f"\x1b[{num}G") 287 288 289def cursor_home() -> None: 290 """Moves cursor to `terminal.origin`. 291 292 Note: 293 This does not flush the terminal for performance reasons. You 294 can do it manually with `sys.stdout.flush()`. 295 """ 296 297 terminal.write("\x1b[H") 298 299 300def set_mode(mode: Union[str, int], write: bool = True) -> str: 301 """Sets terminal display mode. 302 303 This is better left internal. To use these modes, you can call their 304 specific functions, such as `bold("text")` or `italic("text")`. 305 306 Args: 307 mode: One of the available modes. Strings and integers both work. 308 write: Boolean that determines whether the output should be written 309 to stdout. 310 311 Returns: 312 A string that sets the given mode. 313 314 Available modes: 315 - 0: reset 316 - 1: bold 317 - 2: dim 318 - 3: italic 319 - 4: underline 320 - 5: blink 321 - 7: inverse 322 - 8: invisible 323 - 9: strikethrough 324 - 53: overline 325 """ 326 327 options = { 328 "reset": 0, 329 "bold": 1, 330 "dim": 2, 331 "italic": 3, 332 "underline": 4, 333 "blink": 5, 334 "inverse": 7, 335 "invisible": 8, 336 "strikethrough": 9, 337 "overline": 53, 338 } 339 340 if not str(mode).isdigit(): 341 mode = options[str(mode)] 342 343 code = f"\x1b[{mode}m" 344 if write: 345 terminal.write(code) 346 347 return code 348 349 350def set_echo() -> None: 351 """Starts echoing of user input. 352 353 Note: 354 This is currently only available on POSIX. 355 """ 356 357 if not _name == "posix": 358 return 359 360 system("stty echo") 361 362 363def unset_echo() -> None: 364 """Stops echoing of user input. 365 366 Note: 367 This is currently only available on POSIX. 368 """ 369 370 if not _name == "posix": 371 return 372 373 system("stty -echo") 374 375 376class MouseAction(Enum): 377 """An enumeration of all the polled mouse actions""" 378 379 LEFT_CLICK = _auto() 380 """Start of a left button action sequence.""" 381 382 LEFT_DRAG = _auto() 383 """Mouse moved while left button was held down.""" 384 385 RIGHT_CLICK = _auto() 386 """Start of a right button action sequence.""" 387 388 RIGHT_DRAG = _auto() 389 """Mouse moved while right button was held down.""" 390 391 SCROLL_UP = _auto() 392 """Mouse wheel or touchpad scroll upwards.""" 393 394 SCROLL_DOWN = _auto() 395 """Mouse wheel or touchpad scroll downwards.""" 396 397 HOVER = _auto() 398 """Mouse moved without clicking.""" 399 400 # TODO: Support left & right mouse release separately, without breaking 401 # current API. 402 RELEASE = _auto() 403 """Mouse button released; end of any and all mouse action sequences.""" 404 405 406@dataclass 407class MouseEvent: 408 """A class to represent events created by mouse actions. 409 410 Its first argument is a `MouseAction` describing what happened, 411 and its second argument is a `tuple[int, int]` describing where 412 it happened. 413 414 This class mostly exists for readability & typing reasons. It also 415 implements the iterable protocol, so you can use the unpacking syntax, 416 such as: 417 418 ```python3 419 action, position = MouseEvent(...) 420 ``` 421 """ 422 423 action: MouseAction 424 position: tuple[int, int] 425 426 def __post_init__(self) -> None: 427 """Initialize iteration counter""" 428 429 self._iter_index = 0 430 431 def __next__(self) -> MouseAction | tuple[int, int]: 432 """Get next iteration item""" 433 434 data = fields(self) 435 436 if self._iter_index >= len(data): 437 self._iter_index = 0 438 raise StopIteration 439 440 self._iter_index += 1 441 return getattr(self, data[self._iter_index - 1].name) 442 443 def __iter__(self) -> MouseEvent: 444 """Start iteration""" 445 446 return self 447 448 def is_scroll(self) -> bool: 449 """Returns True if event.action is one of the scrolling actions.""" 450 451 return self.action in {MouseAction.SCROLL_DOWN, MouseAction.SCROLL_UP} 452 453 def is_primary(self) -> bool: 454 """Returns True if event.action is one of the primary (left-button) actions.""" 455 456 return self.action in {MouseAction.LEFT_CLICK, MouseAction.LEFT_DRAG} 457 458 def is_secondary(self) -> bool: 459 """Returns True if event.action is one of the secondary (secondary-button) actions.""" 460 461 return self.action in {MouseAction.RIGHT_CLICK, MouseAction.RIGHT_DRAG} 462 463 464def report_mouse( 465 event: str, method: Optional[str] = "decimal_xterm", stop: bool = False 466) -> None: 467 """Starts reporting of mouse events. 468 469 You can specify multiple events to report on. 470 471 Args: 472 event: The type of event to report on. See below for options. 473 method: The method of reporting to use. See below for options. 474 stop: If set to True, the stopping code is written to stdout. 475 476 Raises: 477 NotImplementedError: The given event is not supported. 478 479 Note: 480 If you need this functionality, you're probably better off using the wrapper 481 `pytermgui.context_managers.mouse_handler`, which allows listening on multiple 482 events, gives a translator method and handles exceptions. 483 484 Possible events: 485 - **press**: Report when the mouse is clicked, left or right button. 486 - **highlight**: Report highlighting. 487 - **press_hold**: Report with a left or right click, as well as both 488 left & right drag and release. 489 - **hover**: Report even when no active action is done, only the mouse 490 is moved. 491 492 Methods: 493 - **None**: Non-decimal xterm method. Limited in coordinates. 494 - **decimal_xterm**: The default setting. Most universally supported. 495 - **decimal_urxvt**: Older, less compatible, but useful on some systems. 496 - **decimal_utf8**: Apparently not too stable. 497 498 More information <a href='https://stackoverflow.com/a/5970472'>here</a>. 499 """ 500 501 if event == "press": 502 terminal.write("\x1b[?1000") 503 504 elif event == "highlight": 505 terminal.write("\x1b[?1001") 506 507 elif event == "press_hold": 508 terminal.write("\x1b[?1002") 509 510 elif event == "hover": 511 terminal.write("\x1b[?1003") 512 513 else: 514 raise NotImplementedError(f"Mouse report event {event} is not supported!") 515 516 terminal.write("l" if stop else "h") 517 518 if method == "decimal_utf8": 519 terminal.write("\x1b[?1005") 520 521 elif method == "decimal_xterm": 522 terminal.write("\x1b[?1006") 523 524 elif method == "decimal_urxvt": 525 terminal.write("\x1b[?1015") 526 527 elif method is None: 528 return 529 530 else: 531 raise NotImplementedError(f"Mouse report method {method} is not supported!") 532 533 terminal.write("l" if stop else "h", flush=True) 534 535 536def translate_mouse(code: str, method: str) -> list[MouseEvent | None] | None: 537 """Translates the output of produced by setting `report_mouse` into MouseEvents. 538 539 This method currently only supports `decimal_xterm` and `decimal_urxvt`. 540 541 Args: 542 code: The string of mouse code(s) to translate. 543 method: The reporting method to translate. One of [`decimal_xterm`, `decimal_urxvt`]. 544 545 Returns: 546 A list of optional mouse events obtained from the code argument. If the code was malformed, 547 and no codes could be determined None is returned. 548 """ 549 550 if code == "\x1b": 551 return None 552 553 mouse_codes = { 554 "decimal_xterm": { 555 "0M": MouseAction.LEFT_CLICK, 556 "0m": MouseAction.RELEASE, 557 "2M": MouseAction.RIGHT_CLICK, 558 "2m": MouseAction.RELEASE, 559 "32": MouseAction.LEFT_DRAG, 560 "34": MouseAction.RIGHT_DRAG, 561 "35": MouseAction.HOVER, 562 "64": MouseAction.SCROLL_UP, 563 "65": MouseAction.SCROLL_DOWN, 564 }, 565 "decimal_urxvt": { 566 "32": MouseAction.LEFT_CLICK, 567 "34": MouseAction.RIGHT_CLICK, 568 "35": MouseAction.RELEASE, 569 "64": MouseAction.LEFT_DRAG, 570 "66": MouseAction.RIGHT_DRAG, 571 "96": MouseAction.SCROLL_UP, 572 "97": MouseAction.SCROLL_DOWN, 573 }, 574 } 575 576 mapping = mouse_codes[method] 577 pattern: Pattern = RE_MOUSE[method] 578 579 events: list[MouseEvent | None] = [] 580 581 for sequence in code.split("\x1b"): 582 if len(sequence) == 0: 583 continue 584 585 matches = list(pattern.finditer(sequence)) 586 if len(matches) == 0: 587 return None 588 589 for match in matches: 590 identifier, *pos, release_code = match.groups() 591 592 # decimal_xterm uses the last character's 593 # capitalization to signify press/release state 594 if len(release_code) > 0 and identifier in ["0", "2"]: 595 identifier += release_code 596 597 if identifier in mapping: 598 action = mapping[identifier] 599 assert isinstance(action, MouseAction) 600 601 events.append(MouseEvent(action, (int(pos[0]), int(pos[1])))) 602 continue 603 604 events.append(None) 605 606 return events 607 608 609# shorthand functions 610def print_to(pos: tuple[int, int], *args: Any, **kwargs: Any) -> None: 611 """Prints text to given `pos`. 612 613 Note: 614 This method passes through all arguments (except for `pos`) to the `print` 615 method. 616 """ 617 618 move_cursor(pos) 619 print(*args, **kwargs, end="", flush=True) 620 621 622def reset() -> str: 623 """Resets printing mode.""" 624 625 return set_mode("reset", False) 626 627 628def bold(text: str, reset_style: Optional[bool] = True) -> str: 629 """Returns text in bold. 630 631 Args: 632 reset_style: Boolean that determines whether a reset character should 633 be appended to the end of the string. 634 """ 635 636 return set_mode("bold", False) + text + (reset() if reset_style else "") 637 638 639def dim(text: str, reset_style: Optional[bool] = True) -> str: 640 """Returns text in dim. 641 642 Args: 643 reset_style: Boolean that determines whether a reset character should 644 be appended to the end of the string. 645 """ 646 647 return set_mode("dim", False) + text + (reset() if reset_style else "") 648 649 650def italic(text: str, reset_style: Optional[bool] = True) -> str: 651 """Returns text in italic. 652 653 Args: 654 reset_style: Boolean that determines whether a reset character should 655 be appended to the end of the string. 656 """ 657 658 return set_mode("italic", False) + text + (reset() if reset_style else "") 659 660 661def underline(text: str, reset_style: Optional[bool] = True) -> str: 662 """Returns text underlined. 663 664 Args: 665 reset_style: Boolean that determines whether a reset character should 666 be appended to the end of the string. 667 """ 668 669 return set_mode("underline", False) + text + (reset() if reset_style else "") 670 671 672def blink(text: str, reset_style: Optional[bool] = True) -> str: 673 """Returns text blinking. 674 675 Args: 676 reset_style: Boolean that determines whether a reset character should 677 be appended to the end of the string. 678 """ 679 680 return set_mode("blink", False) + text + (reset() if reset_style else "") 681 682 683def inverse(text: str, reset_style: Optional[bool] = True) -> str: 684 """Returns text inverse-colored. 685 686 Args: 687 reset_style: Boolean that determines whether a reset character should 688 be appended to the end of the string. 689 """ 690 691 return set_mode("inverse", False) + text + (reset() if reset_style else "") 692 693 694def invisible(text: str, reset_style: Optional[bool] = True) -> str: 695 """Returns text as invisible. 696 697 Args: 698 reset_style: Boolean that determines whether a reset character should 699 be appended to the end of the string. 700 701 Note: 702 This isn't very widely supported. 703 """ 704 705 return set_mode("invisible", False) + text + (reset() if reset_style else "") 706 707 708def strikethrough(text: str, reset_style: Optional[bool] = True) -> str: 709 """Return text as strikethrough. 710 711 Args: 712 reset_style: Boolean that determines whether a reset character should 713 be appended to the end of the string. 714 """ 715 716 return set_mode("strikethrough", False) + text + (reset() if reset_style else "") 717 718 719def overline(text: str, reset_style: Optional[bool] = True) -> str: 720 """Return text overlined. 721 722 Args: 723 reset_style: Boolean that determines whether a reset character should 724 be appended to the end of the string. 725 726 Note: 727 This isnt' very widely supported. 728 """ 729 730 return set_mode("overline", False) + text + (reset() if reset_style else "")
View Source
Saves the contents of the screen, and wipes it.
Use restore_screen()
to get them back.
View Source
Restores the contents of the screen saved by save_screen()
.
View Source
Starts an alternate buffer.
View Source
Returns to main buffer, restoring its original state.
View Source
101def clear(what: str = "screen") -> None: 102 """Clears the specified screen region. 103 104 Args: 105 what: The specifier defining the screen area. 106 107 Available options: 108 * screen: clear whole screen and go to origin 109 * bos: clear screen from cursor backwards 110 * eos: clear screen from cursor forwards 111 * line: clear line and go to beginning 112 * bol: clear line from cursor backwards 113 * eol: clear line from cursor forwards 114 """ 115 116 commands = { 117 "eos": "\x1b[0J", 118 "bos": "\x1b[1J", 119 "screen": "\x1b[2J", 120 "eol": "\x1b[0K", 121 "bol": "\x1b[1K", 122 "line": "\x1b[2K", 123 } 124 125 terminal.write(commands[what])
Clears the specified screen region.
Args
- what: The specifier defining the screen area.
Available options:
- screen: clear whole screen and go to origin
- bos: clear screen from cursor backwards
- eos: clear screen from cursor forwards
- line: clear line and go to beginning
- bol: clear line from cursor backwards
- eol: clear line from cursor forwards
View Source
Stops printing the cursor.
View Source
Starts printing the cursor.
View Source
Saves the current cursor position.
Use restore_cursor()
to restore it.
View Source
Restore cursor position as saved by save_cursor
.
View Source
156def report_cursor() -> tuple[int, int] | None: 157 """Gets position of cursor. 158 159 Returns: 160 A tuple of integers, (columns, rows), describing the 161 current (printing) cursor's position. Returns None if 162 this could not be determined. 163 164 Note that this position is **not** the mouse position. See 165 `report_mouse` if that is what you are interested in. 166 """ 167 168 print("\x1b[6n") 169 chars = getch() 170 posy, posx = chars[2:-1].split(";") 171 172 if not posx.isdigit() or not posy.isdigit(): 173 return None 174 175 return int(posx), int(posy)
Gets position of cursor.
Returns
A tuple of integers, (columns, rows), describing the current (printing) cursor's position. Returns None if this could not be determined.
Note that this position is not the mouse position. See
report_mouse
if that is what you are interested in.
View Source
178def move_cursor(pos: tuple[int, int]) -> None: 179 """Moves the cursor. 180 181 Args: 182 pos: Tuple of (columns, rows) that the cursor will be moved to. 183 184 This does not flush the terminal for performance reasons. You 185 can do it manually with `sys.stdout.flush()`. 186 """ 187 188 posx, posy = pos 189 terminal.write(f"\x1b[{posy};{posx}H")
Moves the cursor.
Args
- pos: Tuple of (columns, rows) that the cursor will be moved to.
This does not flush the terminal for performance reasons. You
can do it manually with sys.stdout.flush()
.
View Source
192def cursor_up(num: int = 1) -> None: 193 """Moves the cursor up by `num` lines. 194 195 Args: 196 num: How many lines the cursor should move by. Must be positive, 197 to move in the opposite direction use `cursor_down`. 198 Note: 199 This does not flush the terminal for performance reasons. You 200 can do it manually with `sys.stdout.flush()`. 201 """ 202 203 terminal.write(f"\x1b[{num}A")
Moves the cursor up by num
lines.
Args
- num: How many lines the cursor should move by. Must be positive,
to move in the opposite direction use
cursor_down
.
Note
This does not flush the terminal for performance reasons. You can do it manually with
sys.stdout.flush()
.
View Source
206def cursor_down(num: int = 1) -> None: 207 """Moves the cursor up by `num` lines. 208 209 Args: 210 num: How many lines the cursor should move by. Must be positive, 211 to move in the opposite direction use `cursor_up`. 212 Note: 213 This does not flush the terminal for performance reasons. You 214 can do it manually with `sys.stdout.flush()`. 215 """ 216 217 terminal.write(f"\x1b[{num}B")
Moves the cursor up by num
lines.
Args
- num: How many lines the cursor should move by. Must be positive,
to move in the opposite direction use
cursor_up
.
Note
This does not flush the terminal for performance reasons. You can do it manually with
sys.stdout.flush()
.
View Source
220def cursor_right(num: int = 1) -> None: 221 """Moves the cursor right by `num` lines. 222 223 Args: 224 num: How many characters the cursor should move by. Must be positive, 225 to move in the opposite direction use `cursor_left`. 226 Note: 227 This does not flush the terminal for performance reasons. You 228 can do it manually with `sys.stdout.flush()`. 229 """ 230 231 terminal.write(f"\x1b[{num}C")
Moves the cursor right by num
lines.
Args
- num: How many characters the cursor should move by. Must be positive,
to move in the opposite direction use
cursor_left
.
Note
This does not flush the terminal for performance reasons. You can do it manually with
sys.stdout.flush()
.
View Source
234def cursor_left(num: int = 1) -> None: 235 """Moves the cursor left by `num` lines. 236 237 Args: 238 num: How many characters the cursor should move by. Must be positive, 239 to move in the opposite direction use `cursor_right`. 240 Note: 241 This does not flush the terminal for performance reasons. You 242 can do it manually with `sys.stdout.flush()`. 243 """ 244 245 terminal.write(f"\x1b[{num}D")
Moves the cursor left by num
lines.
Args
- num: How many characters the cursor should move by. Must be positive,
to move in the opposite direction use
cursor_right
.
Note
This does not flush the terminal for performance reasons. You can do it manually with
sys.stdout.flush()
.
View Source
248def cursor_next_line(num: int = 1) -> None: 249 """Moves the cursor to the beginning of the `num`-th line downwards. 250 251 Args: 252 num: The amount the cursor should move by. Must be positive, to move 253 in the opposite direction use `cursor_prev_line`. 254 Note: 255 This does not flush the terminal for performance reasons. You 256 can do it manually with `sys.stdout.flush()`. 257 """ 258 259 terminal.write(f"\x1b[{num}E")
Moves the cursor to the beginning of the num
-th line downwards.
Args
- num: The amount the cursor should move by. Must be positive, to move
in the opposite direction use
cursor_prev_line
.
Note
This does not flush the terminal for performance reasons. You can do it manually with
sys.stdout.flush()
.
View Source
262def cursor_prev_line(num: int = 1) -> None: 263 """Moves the cursor to the beginning of the `num`-th line upwards. 264 265 Args: 266 num: The amount the cursor should move by. Must be positive, to move 267 in the opposite direction use `cursor_next_line`. 268 Note: 269 This does not flush the terminal for performance reasons. You 270 can do it manually with `sys.stdout.flush()`. 271 """ 272 273 terminal.write(f"\x1b[{num}F")
Moves the cursor to the beginning of the num
-th line upwards.
Args
- num: The amount the cursor should move by. Must be positive, to move
in the opposite direction use
cursor_next_line
.
Note
This does not flush the terminal for performance reasons. You can do it manually with
sys.stdout.flush()
.
View Source
276def cursor_column(num: int = 0) -> None: 277 """Moves the cursor to the `num`-th character of the current line. 278 279 Args: 280 num: The new cursor position. 281 282 Note: 283 This does not flush the terminal for performance reasons. You 284 can do it manually with `sys.stdout.flush()`. 285 """ 286 287 terminal.write(f"\x1b[{num}G")
Moves the cursor to the num
-th character of the current line.
Args
- num: The new cursor position.
Note
This does not flush the terminal for performance reasons. You can do it manually with
sys.stdout.flush()
.
View Source
Moves cursor to terminal.origin
.
Note
This does not flush the terminal for performance reasons. You can do it manually with
sys.stdout.flush()
.
View Source
Starts echoing of user input.
Note
This is currently only available on POSIX.
View Source
Stops echoing of user input.
Note
This is currently only available on POSIX.
View Source
301def set_mode(mode: Union[str, int], write: bool = True) -> str: 302 """Sets terminal display mode. 303 304 This is better left internal. To use these modes, you can call their 305 specific functions, such as `bold("text")` or `italic("text")`. 306 307 Args: 308 mode: One of the available modes. Strings and integers both work. 309 write: Boolean that determines whether the output should be written 310 to stdout. 311 312 Returns: 313 A string that sets the given mode. 314 315 Available modes: 316 - 0: reset 317 - 1: bold 318 - 2: dim 319 - 3: italic 320 - 4: underline 321 - 5: blink 322 - 7: inverse 323 - 8: invisible 324 - 9: strikethrough 325 - 53: overline 326 """ 327 328 options = { 329 "reset": 0, 330 "bold": 1, 331 "dim": 2, 332 "italic": 3, 333 "underline": 4, 334 "blink": 5, 335 "inverse": 7, 336 "invisible": 8, 337 "strikethrough": 9, 338 "overline": 53, 339 } 340 341 if not str(mode).isdigit(): 342 mode = options[str(mode)] 343 344 code = f"\x1b[{mode}m" 345 if write: 346 terminal.write(code) 347 348 return code
Sets terminal display mode.
This is better left internal. To use these modes, you can call their
specific functions, such as bold("text")
or italic("text")
.
Args
- mode: One of the available modes. Strings and integers both work.
- write: Boolean that determines whether the output should be written to stdout.
Returns
A string that sets the given mode.
Available modes
- 0: reset
- 1: bold
- 2: dim
- 3: italic
- 4: underline
- 5: blink
- 7: inverse
- 8: invisible
- 9: strikethrough
- 53: overline
View Source
377class MouseAction(Enum): 378 """An enumeration of all the polled mouse actions""" 379 380 LEFT_CLICK = _auto() 381 """Start of a left button action sequence.""" 382 383 LEFT_DRAG = _auto() 384 """Mouse moved while left button was held down.""" 385 386 RIGHT_CLICK = _auto() 387 """Start of a right button action sequence.""" 388 389 RIGHT_DRAG = _auto() 390 """Mouse moved while right button was held down.""" 391 392 SCROLL_UP = _auto() 393 """Mouse wheel or touchpad scroll upwards.""" 394 395 SCROLL_DOWN = _auto() 396 """Mouse wheel or touchpad scroll downwards.""" 397 398 HOVER = _auto() 399 """Mouse moved without clicking.""" 400 401 # TODO: Support left & right mouse release separately, without breaking 402 # current API. 403 RELEASE = _auto() 404 """Mouse button released; end of any and all mouse action sequences."""
An enumeration of all the polled mouse actions
Start of a left button action sequence.
Mouse moved while left button was held down.
Start of a right button action sequence.
Mouse moved while right button was held down.
Mouse wheel or touchpad scroll upwards.
Mouse wheel or touchpad scroll downwards.
Mouse moved without clicking.
Mouse button released; end of any and all mouse action sequences.
Inherited Members
- enum.Enum
- name
- value
View Source
407@dataclass 408class MouseEvent: 409 """A class to represent events created by mouse actions. 410 411 Its first argument is a `MouseAction` describing what happened, 412 and its second argument is a `tuple[int, int]` describing where 413 it happened. 414 415 This class mostly exists for readability & typing reasons. It also 416 implements the iterable protocol, so you can use the unpacking syntax, 417 such as: 418 419 ```python3 420 action, position = MouseEvent(...) 421 ``` 422 """ 423 424 action: MouseAction 425 position: tuple[int, int] 426 427 def __post_init__(self) -> None: 428 """Initialize iteration counter""" 429 430 self._iter_index = 0 431 432 def __next__(self) -> MouseAction | tuple[int, int]: 433 """Get next iteration item""" 434 435 data = fields(self) 436 437 if self._iter_index >= len(data): 438 self._iter_index = 0 439 raise StopIteration 440 441 self._iter_index += 1 442 return getattr(self, data[self._iter_index - 1].name) 443 444 def __iter__(self) -> MouseEvent: 445 """Start iteration""" 446 447 return self 448 449 def is_scroll(self) -> bool: 450 """Returns True if event.action is one of the scrolling actions.""" 451 452 return self.action in {MouseAction.SCROLL_DOWN, MouseAction.SCROLL_UP} 453 454 def is_primary(self) -> bool: 455 """Returns True if event.action is one of the primary (left-button) actions.""" 456 457 return self.action in {MouseAction.LEFT_CLICK, MouseAction.LEFT_DRAG} 458 459 def is_secondary(self) -> bool: 460 """Returns True if event.action is one of the secondary (secondary-button) actions.""" 461 462 return self.action in {MouseAction.RIGHT_CLICK, MouseAction.RIGHT_DRAG}
A class to represent events created by mouse actions.
Its first argument is a MouseAction
describing what happened,
and its second argument is a tuple[int, int]
describing where
it happened.
This class mostly exists for readability & typing reasons. It also implements the iterable protocol, so you can use the unpacking syntax, such as:
action, position = MouseEvent(...)
View Source
Returns True if event.action is one of the scrolling actions.
View Source
Returns True if event.action is one of the primary (left-button) actions.
View Source
Returns True if event.action is one of the secondary (secondary-button) actions.
View Source
465def report_mouse( 466 event: str, method: Optional[str] = "decimal_xterm", stop: bool = False 467) -> None: 468 """Starts reporting of mouse events. 469 470 You can specify multiple events to report on. 471 472 Args: 473 event: The type of event to report on. See below for options. 474 method: The method of reporting to use. See below for options. 475 stop: If set to True, the stopping code is written to stdout. 476 477 Raises: 478 NotImplementedError: The given event is not supported. 479 480 Note: 481 If you need this functionality, you're probably better off using the wrapper 482 `pytermgui.context_managers.mouse_handler`, which allows listening on multiple 483 events, gives a translator method and handles exceptions. 484 485 Possible events: 486 - **press**: Report when the mouse is clicked, left or right button. 487 - **highlight**: Report highlighting. 488 - **press_hold**: Report with a left or right click, as well as both 489 left & right drag and release. 490 - **hover**: Report even when no active action is done, only the mouse 491 is moved. 492 493 Methods: 494 - **None**: Non-decimal xterm method. Limited in coordinates. 495 - **decimal_xterm**: The default setting. Most universally supported. 496 - **decimal_urxvt**: Older, less compatible, but useful on some systems. 497 - **decimal_utf8**: Apparently not too stable. 498 499 More information <a href='https://stackoverflow.com/a/5970472'>here</a>. 500 """ 501 502 if event == "press": 503 terminal.write("\x1b[?1000") 504 505 elif event == "highlight": 506 terminal.write("\x1b[?1001") 507 508 elif event == "press_hold": 509 terminal.write("\x1b[?1002") 510 511 elif event == "hover": 512 terminal.write("\x1b[?1003") 513 514 else: 515 raise NotImplementedError(f"Mouse report event {event} is not supported!") 516 517 terminal.write("l" if stop else "h") 518 519 if method == "decimal_utf8": 520 terminal.write("\x1b[?1005") 521 522 elif method == "decimal_xterm": 523 terminal.write("\x1b[?1006") 524 525 elif method == "decimal_urxvt": 526 terminal.write("\x1b[?1015") 527 528 elif method is None: 529 return 530 531 else: 532 raise NotImplementedError(f"Mouse report method {method} is not supported!") 533 534 terminal.write("l" if stop else "h", flush=True)
Starts reporting of mouse events.
You can specify multiple events to report on.
Args
- event: The type of event to report on. See below for options.
- method: The method of reporting to use. See below for options.
- stop: If set to True, the stopping code is written to stdout.
Raises
- NotImplementedError: The given event is not supported.
Note
If you need this functionality, you're probably better off using the wrapper
pytermgui.context_managers.mouse_handler
, which allows listening on multiple events, gives a translator method and handles exceptions.
Possible events
- press: Report when the mouse is clicked, left or right button.
- highlight: Report highlighting.
- press_hold: Report with a left or right click, as well as both left & right drag and release.
- hover: Report even when no active action is done, only the mouse is moved.
Methods
- None: Non-decimal xterm method. Limited in coordinates.
- decimal_xterm: The default setting. Most universally supported.
- decimal_urxvt: Older, less compatible, but useful on some systems.
- decimal_utf8: Apparently not too stable.
More information here.
View Source
537def translate_mouse(code: str, method: str) -> list[MouseEvent | None] | None: 538 """Translates the output of produced by setting `report_mouse` into MouseEvents. 539 540 This method currently only supports `decimal_xterm` and `decimal_urxvt`. 541 542 Args: 543 code: The string of mouse code(s) to translate. 544 method: The reporting method to translate. One of [`decimal_xterm`, `decimal_urxvt`]. 545 546 Returns: 547 A list of optional mouse events obtained from the code argument. If the code was malformed, 548 and no codes could be determined None is returned. 549 """ 550 551 if code == "\x1b": 552 return None 553 554 mouse_codes = { 555 "decimal_xterm": { 556 "0M": MouseAction.LEFT_CLICK, 557 "0m": MouseAction.RELEASE, 558 "2M": MouseAction.RIGHT_CLICK, 559 "2m": MouseAction.RELEASE, 560 "32": MouseAction.LEFT_DRAG, 561 "34": MouseAction.RIGHT_DRAG, 562 "35": MouseAction.HOVER, 563 "64": MouseAction.SCROLL_UP, 564 "65": MouseAction.SCROLL_DOWN, 565 }, 566 "decimal_urxvt": { 567 "32": MouseAction.LEFT_CLICK, 568 "34": MouseAction.RIGHT_CLICK, 569 "35": MouseAction.RELEASE, 570 "64": MouseAction.LEFT_DRAG, 571 "66": MouseAction.RIGHT_DRAG, 572 "96": MouseAction.SCROLL_UP, 573 "97": MouseAction.SCROLL_DOWN, 574 }, 575 } 576 577 mapping = mouse_codes[method] 578 pattern: Pattern = RE_MOUSE[method] 579 580 events: list[MouseEvent | None] = [] 581 582 for sequence in code.split("\x1b"): 583 if len(sequence) == 0: 584 continue 585 586 matches = list(pattern.finditer(sequence)) 587 if len(matches) == 0: 588 return None 589 590 for match in matches: 591 identifier, *pos, release_code = match.groups() 592 593 # decimal_xterm uses the last character's 594 # capitalization to signify press/release state 595 if len(release_code) > 0 and identifier in ["0", "2"]: 596 identifier += release_code 597 598 if identifier in mapping: 599 action = mapping[identifier] 600 assert isinstance(action, MouseAction) 601 602 events.append(MouseEvent(action, (int(pos[0]), int(pos[1])))) 603 continue 604 605 events.append(None) 606 607 return events
Translates the output of produced by setting report_mouse
into MouseEvents.
This method currently only supports decimal_xterm
and decimal_urxvt
.
Args
- code: The string of mouse code(s) to translate.
- method: The reporting method to translate. One of [
decimal_xterm
,decimal_urxvt
].
Returns
A list of optional mouse events obtained from the code argument. If the code was malformed, and no codes could be determined None is returned.
View Source
Prints text to given pos
.
Note
This method passes through all arguments (except for
pos
) to the
View Source
Resets printing mode.
View Source
629def bold(text: str, reset_style: Optional[bool] = True) -> str: 630 """Returns text in bold. 631 632 Args: 633 reset_style: Boolean that determines whether a reset character should 634 be appended to the end of the string. 635 """ 636 637 return set_mode("bold", False) + text + (reset() if reset_style else "")
Returns text in bold.
Args
- reset_style: Boolean that determines whether a reset character should be appended to the end of the string.
View Source
640def dim(text: str, reset_style: Optional[bool] = True) -> str: 641 """Returns text in dim. 642 643 Args: 644 reset_style: Boolean that determines whether a reset character should 645 be appended to the end of the string. 646 """ 647 648 return set_mode("dim", False) + text + (reset() if reset_style else "")
Returns text in dim.
Args
- reset_style: Boolean that determines whether a reset character should be appended to the end of the string.
View Source
651def italic(text: str, reset_style: Optional[bool] = True) -> str: 652 """Returns text in italic. 653 654 Args: 655 reset_style: Boolean that determines whether a reset character should 656 be appended to the end of the string. 657 """ 658 659 return set_mode("italic", False) + text + (reset() if reset_style else "")
Returns text in italic.
Args
- reset_style: Boolean that determines whether a reset character should be appended to the end of the string.
View Source
662def underline(text: str, reset_style: Optional[bool] = True) -> str: 663 """Returns text underlined. 664 665 Args: 666 reset_style: Boolean that determines whether a reset character should 667 be appended to the end of the string. 668 """ 669 670 return set_mode("underline", False) + text + (reset() if reset_style else "")
Returns text underlined.
Args
- reset_style: Boolean that determines whether a reset character should be appended to the end of the string.
View Source
673def blink(text: str, reset_style: Optional[bool] = True) -> str: 674 """Returns text blinking. 675 676 Args: 677 reset_style: Boolean that determines whether a reset character should 678 be appended to the end of the string. 679 """ 680 681 return set_mode("blink", False) + text + (reset() if reset_style else "")
Returns text blinking.
Args
- reset_style: Boolean that determines whether a reset character should be appended to the end of the string.
View Source
684def inverse(text: str, reset_style: Optional[bool] = True) -> str: 685 """Returns text inverse-colored. 686 687 Args: 688 reset_style: Boolean that determines whether a reset character should 689 be appended to the end of the string. 690 """ 691 692 return set_mode("inverse", False) + text + (reset() if reset_style else "")
Returns text inverse-colored.
Args
- reset_style: Boolean that determines whether a reset character should be appended to the end of the string.
View Source
695def invisible(text: str, reset_style: Optional[bool] = True) -> str: 696 """Returns text as invisible. 697 698 Args: 699 reset_style: Boolean that determines whether a reset character should 700 be appended to the end of the string. 701 702 Note: 703 This isn't very widely supported. 704 """ 705 706 return set_mode("invisible", False) + text + (reset() if reset_style else "")
Returns text as invisible.
Args
- reset_style: Boolean that determines whether a reset character should be appended to the end of the string.
Note
This isn't very widely supported.
View Source
709def strikethrough(text: str, reset_style: Optional[bool] = True) -> str: 710 """Return text as strikethrough. 711 712 Args: 713 reset_style: Boolean that determines whether a reset character should 714 be appended to the end of the string. 715 """ 716 717 return set_mode("strikethrough", False) + text + (reset() if reset_style else "")
Return text as strikethrough.
Args
- reset_style: Boolean that determines whether a reset character should be appended to the end of the string.
View Source
720def overline(text: str, reset_style: Optional[bool] = True) -> str: 721 """Return text overlined. 722 723 Args: 724 reset_style: Boolean that determines whether a reset character should 725 be appended to the end of the string. 726 727 Note: 728 This isnt' very widely supported. 729 """ 730 731 return set_mode("overline", False) + text + (reset() if reset_style else "")
Return text overlined.
Args
- reset_style: Boolean that determines whether a reset character should be appended to the end of the string.
Note
This isnt' very widely supported.