File: cursor.py

package info (click to toggle)
python-easy-ansi 0.3-4
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bullseye, forky, sid, trixie
  • size: 488 kB
  • sloc: python: 3,109; sh: 127; makefile: 2
file content (161 lines) | stat: -rw-r--r-- 5,450 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
_WINDOWS = False
from typing import Tuple
import sys
try:
    import tty
    import termios
except ModuleNotFoundError:
    import msvcrt
    _WINDOWS = True
from easyansi._core.codes import CSI as _CSI
from easyansi._core.validations import validate_at_least_minimum as _validate_at_least_minimum
from easyansi._core.prnt import prnt as _prnt

MIN_COLUMN = 0
MIN_ROW = 0
MIN_MOVEMENT = 1


def up_code(rows: int = 1) -> str:
    """Return the ANSI code to move the cursor up n rows on the screen."""
    _validate_at_least_minimum(rows, MIN_MOVEMENT, "Rows up")
    return _CSI + str(rows) + "A"


def up(rows: int = 1) -> None:
    """Output the ANSI code to move the cursor up n rows on the screen."""
    _prnt(up_code(rows=rows))


def down_code(rows: int = 1) -> str:
    """Return the ANSI code to move the cursor down n rows on the screen."""
    _validate_at_least_minimum(rows, MIN_MOVEMENT, "Rows down")
    return _CSI + str(rows) + "B"


def down(rows: int = 1) -> None:
    """Output the ANSI code to move the cursor down n rows on the screen."""
    _prnt(down_code(rows=rows))


def right_code(cols: int = 1) -> str:
    """Return the ANSI code to move the cursor right n columns on the screen."""
    _validate_at_least_minimum(cols, MIN_MOVEMENT, "Columns right")
    return _CSI + str(cols) + "C"


def right(cols: int = 1) -> None:
    """Output the ANSI code to move the cursor right n columns on the screen."""
    _prnt(right_code(cols=cols))


def left_code(cols: int = 1) -> str:
    """Return the ANSI code to move the cursor left n columns on the screen."""
    _validate_at_least_minimum(cols, MIN_MOVEMENT, "Columns left")
    return _CSI + str(cols) + "D"


def left(cols: int = 1) -> None:
    """Output the ANSI code to move the cursor left n columns on the screen."""
    _prnt(left_code(cols=cols))


def next_line_code(rows_down: int = 1) -> str:
    """Return the ANSI code to move the cursor to the beginning of a specified number of rows down."""
    _validate_at_least_minimum(rows_down, MIN_MOVEMENT, "Rows down")
    return _CSI + str(rows_down) + "E"


def next_line(rows_down: int = 1) -> None:
    """Output the ANSI code to move the cursor to the beginning of a specified number of rows down."""
    _prnt(next_line_code(rows_down=rows_down))


def previous_line_code(rows_up: int = 1) -> str:
    """Return the ANSI code to move the cursor to the beginning of a specified number of rows up."""
    _validate_at_least_minimum(rows_up, MIN_MOVEMENT, "Rows up")
    return _CSI + str(rows_up) + "F"


def previous_line(rows_up: int = 1) -> None:
    """Output the ANSI code to move the cursor to the beginning of a specified number of rows up."""
    _prnt(previous_line_code(rows_up=rows_up))


def locate_code(x: int, y: int) -> str:
    """Return the ANSI code to move the cursor to the x, y coordinates on the screen.
       The coordinate system is 0, 0 based."""
    _validate_at_least_minimum(x, MIN_COLUMN, "X-Coordinate")
    _validate_at_least_minimum(y, MIN_ROW, "Y-Coordinate")
    return _CSI + str(y+1) + ";" + str(x+1) + "H"


def locate(x: int, y: int) -> None:
    """Output the ANSI code to move the cursor to the x, y coordinates on the screen.
       The coordinate system is 0, 0 based."""
    _prnt(locate_code(x=x, y=y))


def locate_column_code(x: int) -> str:
    """Return the ANSI code to move the cursor to the specified column (x) on the current line."""
    _validate_at_least_minimum(x, MIN_COLUMN, "Column Number")
    return _CSI + str(x+1) + "G"


def locate_column(x: int) -> None:
    """Output the ANSI code to move the cursor to the specified column (x) on the current line."""
    _prnt(locate_column_code(x=x))


def hide_code() -> str:
    """Return the ANSI code to hide the cursor."""
    return _CSI + "?25l"


def hide() -> None:
    """Output the ANSI code to hide the cursor."""
    _prnt(hide_code())


def show_code() -> str:
    """Return the ANSI code to show the cursor."""
    return _CSI + "?25h"


def show() -> None:
    """Output the ANSI code to show the cursor."""
    _prnt(show_code())


def get_location() -> Tuple[int, int]:
    """Return the x, y coordinates of the cursor."""
    response = _get_location_response()
    location = response.split(";")
    x = int(location[1]) - 1
    y = int(location[0]) - 1
    return x, y


def _get_location_response() -> str:
    """Make the actual ANSI call for the cursor location.
    This code is separated out to make it easy to mock in unit tests."""
    response = ""
    _prnt(_CSI + "6n")  # Send ANSI request for cursor location
    if _WINDOWS:
        while msvcrt.kbhit():  # type: ignore
            char_in = msvcrt.getch()  # type: ignore
            response += char_in.decode("utf-8")
        response = response[2:-1]  # We do not need the first 2 bytes or the last byte
    else:
        f = sys.stdin.fileno()
        terminal_settings = termios.tcgetattr(f)  # Save terminal settings
        try:
            tty.setraw(f)
            sys.stdin.read(2)  # We do not need the first 2 bytes
            char_in = sys.stdin.read(1)
            while char_in != "R":  # R will be the last character of the ANSI response, and we don't need it
                response += char_in
                char_in = sys.stdin.read(1)  # Read a single character
        finally:
            termios.tcsetattr(f, termios.TCSADRAIN, terminal_settings)  # Restore terminal settings
    return response