pytermgui.context_managers

Ease-of-use context-manager classes & functions.

There isn't much (or any) additional functionality provided in this module, most things are nicer-packaged combinations to already available methods from pytermgui.ansi_interface.

  1"""
  2Ease-of-use context-manager classes & functions.
  3
  4There isn't much (or any) additional functionality provided in this module,
  5most things are nicer-packaged combinations to already available methods from
  6`pytermgui.ansi_interface`.
  7"""
  8
  9from __future__ import annotations
 10
 11from contextlib import contextmanager
 12from os import name
 13from typing import Any, Callable, Generator, List, Union
 14
 15from .ansi_interface import (
 16    MouseEvent,
 17    cursor_up,
 18    hide_cursor,
 19    print_to,
 20    report_mouse,
 21    restore_cursor,
 22    save_cursor,
 23    set_alt_buffer,
 24    set_echo,
 25    show_cursor,
 26    translate_mouse,
 27    unset_alt_buffer,
 28    unset_echo,
 29)
 30
 31# This is technically meant to be here, but it has to be in `input.py` due
 32# to some package structure issues.
 33from .input import timeout  # pylint: disable=unused-import
 34from .terminal import get_terminal
 35
 36# TODO: Move this absolute beast to a types submodule
 37MouseTranslator = Callable[[str], Union[List[Union[MouseEvent, None]], None]]
 38
 39
 40@contextmanager
 41def cursor_at(pos: tuple[int, int]) -> Generator[Callable[..., None], None, None]:
 42    """Gets callable to print at `pos`, incrementing `y` on every print.
 43
 44    Args:
 45        pos: The position to start printing at. Follows the order (columns, rows).
 46
 47    Yields:
 48        A callable printing function. This function forwards all arguments to `print`,
 49        but positions the cursor before doing so. After every call, the y position is
 50        incremented.
 51    """
 52
 53    offset = 0
 54    posx, posy = pos
 55
 56    def printer(*args: tuple[Any, ...]) -> None:
 57        """Print to posx, current y"""
 58
 59        nonlocal offset
 60
 61        print_to((posx, posy + offset), *args)
 62        offset += 1
 63
 64    try:
 65        save_cursor()
 66        yield printer
 67
 68    finally:
 69        restore_cursor()
 70
 71
 72@contextmanager
 73def alt_buffer(echo: bool = False, cursor: bool = True) -> Generator[None, None, None]:
 74    """Creates non-scrollable alt-buffer.
 75
 76    This is useful for retrieving original terminal state after program end.
 77
 78    Args:
 79        echo: Whether `unset_echo` should be called on startup.
 80        cursor: Whether `hide_cursor` should be called on startup.
 81    """
 82
 83    terminal = get_terminal()
 84
 85    try:
 86        set_alt_buffer()
 87
 88        if not echo and name == "posix" and not terminal.is_interactive():
 89            unset_echo()
 90
 91        if not cursor:
 92            hide_cursor()
 93
 94        yield
 95
 96    finally:
 97        unset_alt_buffer()
 98
 99        if not echo and name == "posix" and not terminal.is_interactive():
100            set_echo()
101            cursor_up()
102
103        if not cursor:
104            show_cursor()
105            cursor_up()
106
107
108@contextmanager
109def mouse_handler(
110    events: list[str], method: str = "decimal_xterm"
111) -> Generator[MouseTranslator | None, None, None]:
112    """Return a mouse handler function
113
114    See `help(report_mouse)` for help about all of the methods.
115
116    Args:
117        events: A list of `pytermgui.ansi_interface.report_mouse` events.
118        method: The method to use for reporting. Only `decimal_urxvt` and
119            `decimal_xterm` are currently supported.
120
121    Example use:
122
123    ```python3
124    import pytermgui as ptg
125
126    with ptg.mouse_handler(["press", "hover"]) as mouse:
127        while True:
128          event = mouse(ptg.getch())
129          print(type(event))
130          print(event.action)
131          print(event.position)
132
133    'pytermgui.ansi_interface.MouseEvent'
134    'pytermgui.ansi_interface.MouseAction.LEFT_CLICK'
135    (33, 55)
136    ```
137
138    """
139
140    event = None
141    try:
142        for event in events:
143            report_mouse(event, method=method)
144
145        yield lambda code: translate_mouse(code, method=method)
146
147    finally:
148        if event is not None:
149            report_mouse(event, method=method, stop=True)
@contextmanager
def cursor_at( pos: tuple[int, int]) -> Generator[Callable[..., NoneType], NoneType, NoneType]:
41@contextmanager
42def cursor_at(pos: tuple[int, int]) -> Generator[Callable[..., None], None, None]:
43    """Gets callable to print at `pos`, incrementing `y` on every print.
44
45    Args:
46        pos: The position to start printing at. Follows the order (columns, rows).
47
48    Yields:
49        A callable printing function. This function forwards all arguments to `print`,
50        but positions the cursor before doing so. After every call, the y position is
51        incremented.
52    """
53
54    offset = 0
55    posx, posy = pos
56
57    def printer(*args: tuple[Any, ...]) -> None:
58        """Print to posx, current y"""
59
60        nonlocal offset
61
62        print_to((posx, posy + offset), *args)
63        offset += 1
64
65    try:
66        save_cursor()
67        yield printer
68
69    finally:
70        restore_cursor()

Gets callable to print at pos, incrementing y on every print.

Args
  • pos: The position to start printing at. Follows the order (columns, rows).
Yields

A callable printing function. This function forwards all arguments to print, but positions the cursor before doing so. After every call, the y position is incremented.

@contextmanager
def alt_buffer( echo: bool = False, cursor: bool = True) -> Generator[NoneType, NoneType, NoneType]:
 73@contextmanager
 74def alt_buffer(echo: bool = False, cursor: bool = True) -> Generator[None, None, None]:
 75    """Creates non-scrollable alt-buffer.
 76
 77    This is useful for retrieving original terminal state after program end.
 78
 79    Args:
 80        echo: Whether `unset_echo` should be called on startup.
 81        cursor: Whether `hide_cursor` should be called on startup.
 82    """
 83
 84    terminal = get_terminal()
 85
 86    try:
 87        set_alt_buffer()
 88
 89        if not echo and name == "posix" and not terminal.is_interactive():
 90            unset_echo()
 91
 92        if not cursor:
 93            hide_cursor()
 94
 95        yield
 96
 97    finally:
 98        unset_alt_buffer()
 99
100        if not echo and name == "posix" and not terminal.is_interactive():
101            set_echo()
102            cursor_up()
103
104        if not cursor:
105            show_cursor()
106            cursor_up()

Creates non-scrollable alt-buffer.

This is useful for retrieving original terminal state after program end.

Args
  • echo: Whether unset_echo should be called on startup.
  • cursor: Whether hide_cursor should be called on startup.
@contextmanager
def mouse_handler( events: list[str], method: str = 'decimal_xterm') -> Generator[Optional[Callable[[str], Optional[List[Optional[pytermgui.ansi_interface.MouseEvent]]]]], NoneType, NoneType]:
109@contextmanager
110def mouse_handler(
111    events: list[str], method: str = "decimal_xterm"
112) -> Generator[MouseTranslator | None, None, None]:
113    """Return a mouse handler function
114
115    See `help(report_mouse)` for help about all of the methods.
116
117    Args:
118        events: A list of `pytermgui.ansi_interface.report_mouse` events.
119        method: The method to use for reporting. Only `decimal_urxvt` and
120            `decimal_xterm` are currently supported.
121
122    Example use:
123
124    ```python3
125    import pytermgui as ptg
126
127    with ptg.mouse_handler(["press", "hover"]) as mouse:
128        while True:
129          event = mouse(ptg.getch())
130          print(type(event))
131          print(event.action)
132          print(event.position)
133
134    'pytermgui.ansi_interface.MouseEvent'
135    'pytermgui.ansi_interface.MouseAction.LEFT_CLICK'
136    (33, 55)
137    ```
138
139    """
140
141    event = None
142    try:
143        for event in events:
144            report_mouse(event, method=method)
145
146        yield lambda code: translate_mouse(code, method=method)
147
148    finally:
149        if event is not None:
150            report_mouse(event, method=method, stop=True)

Return a mouse handler function

See help(report_mouse) for help about all of the methods.

Args

Example use:

import pytermgui as ptg

with ptg.mouse_handler(["press", "hover"]) as mouse:
    while True:
      event = mouse(ptg.getch())
      print(type(event))
      print(event.action)
      print(event.position)

'pytermgui.ansi_interface.MouseEvent'
'pytermgui.ansi_interface.MouseAction.LEFT_CLICK'
(33, 55)