hspylib-clitt 0.9.41__py3-none-any.whl → 0.9.51__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of hspylib-clitt might be problematic. Click here for more details.
- clitt/.version +1 -1
- clitt/__classpath__.py +1 -1
- clitt/__init__.py +4 -3
- clitt/__main__.py +8 -41
- clitt/addons/__init__.py +2 -3
- clitt/addons/appman/__init__.py +2 -2
- clitt/addons/appman/appman.py +1 -1
- clitt/addons/appman/templates/__init__.py +2 -2
- clitt/addons/widman/__init__.py +2 -2
- clitt/addons/widman/widgets/__init__.py +2 -2
- clitt/addons/widman/widgets/widget_free.py +2 -1
- clitt/addons/widman/widgets/widget_punch.py +3 -2
- clitt/core/__init__.py +3 -3
- clitt/core/icons/__init__.py +2 -2
- clitt/core/icons/emojis/__init__.py +2 -2
- clitt/core/icons/font_awesome/__init__.py +2 -2
- clitt/core/term/__init__.py +14 -0
- clitt/core/term/commons.py +63 -0
- clitt/core/term/cursor.py +124 -0
- clitt/core/term/screen.py +96 -0
- clitt/core/term/terminal.py +173 -0
- clitt/core/tui/__init__.py +3 -4
- clitt/core/tui/mchoose/__init__.py +2 -2
- clitt/core/tui/mchoose/menu_choose.py +6 -5
- clitt/core/tui/mdashboard/__init__.py +2 -2
- clitt/core/tui/mdashboard/menu_dashboard.py +9 -9
- clitt/core/tui/menu/__init__.py +2 -2
- clitt/core/tui/menu/tui_menu_action.py +1 -3
- clitt/core/tui/menu/tui_menu_item.py +6 -5
- clitt/core/tui/menu/tui_menu_ui.py +5 -7
- clitt/core/tui/menu/tui_menu_view.py +2 -3
- clitt/core/tui/minput/__init__.py +2 -2
- clitt/core/tui/minput/menu_input.py +22 -18
- clitt/core/tui/minput/minput_utils.py +4 -3
- clitt/core/tui/mselect/__init__.py +2 -2
- clitt/core/tui/mselect/menu_select.py +6 -5
- clitt/core/tui/table/__init__.py +2 -2
- clitt/core/tui/table/table_renderer.py +13 -15
- clitt/core/tui/tui_application.py +27 -13
- clitt/core/tui/tui_component.py +28 -21
- clitt/core/tui/tui_preferences.py +3 -0
- clitt/utils/__init__.py +11 -0
- clitt/utils/git_utils.py +67 -0
- clitt/welcome.txt +6 -6
- {hspylib_clitt-0.9.41.dist-info → hspylib_clitt-0.9.51.dist-info}/METADATA +4 -4
- hspylib_clitt-0.9.51.dist-info/RECORD +90 -0
- clitt/addons/setman/__init__.py +0 -13
- clitt/addons/setman/setman.py +0 -148
- clitt/addons/setman/setman_config.py +0 -26
- clitt/addons/setman/setman_enums.py +0 -54
- clitt/core/settings/__init__.py +0 -15
- clitt/core/settings/settings.py +0 -182
- clitt/core/settings/settings_config.py +0 -47
- clitt/core/settings/settings_entry.py +0 -108
- clitt/core/settings/settings_repository.py +0 -103
- clitt/core/settings/settings_service.py +0 -50
- clitt/core/tui/tui_screen.py +0 -222
- hspylib_clitt-0.9.41.dist-info/RECORD +0 -94
- {hspylib_clitt-0.9.41.dist-info → hspylib_clitt-0.9.51.dist-info}/WHEEL +0 -0
- {hspylib_clitt-0.9.41.dist-info → hspylib_clitt-0.9.51.dist-info}/top_level.txt +0 -0
clitt/.version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.9.
|
|
1
|
+
0.9.51
|
clitt/__classpath__.py
CHANGED
clitt/__init__.py
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
# _*_ coding: utf-8 _*_
|
|
2
2
|
#
|
|
3
|
-
# hspylib-clitt v0.9.
|
|
3
|
+
# hspylib-clitt v0.9.51
|
|
4
4
|
#
|
|
5
5
|
# Package: main.clitt
|
|
6
6
|
"""Package initialization."""
|
|
7
7
|
|
|
8
8
|
__all__ = [
|
|
9
9
|
'addons',
|
|
10
|
-
'core'
|
|
10
|
+
'core',
|
|
11
|
+
'utils'
|
|
11
12
|
]
|
|
12
|
-
__version__ = '0.9.
|
|
13
|
+
__version__ = '0.9.51'
|
clitt/__main__.py
CHANGED
|
@@ -12,22 +12,20 @@
|
|
|
12
12
|
|
|
13
13
|
Copyright 2023, HsPyLib team
|
|
14
14
|
"""
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
from clitt.addons.appman.appman_enums import AppExtension, AppType
|
|
18
|
-
from clitt.addons.setman.setman import SetMan
|
|
19
|
-
from clitt.addons.setman.setman_enums import SetmanOps, SettingsType
|
|
20
|
-
from clitt.addons.widman.widman import WidgetManager
|
|
21
|
-
from clitt.core.tui.tui_application import TUIApplication
|
|
15
|
+
import sys
|
|
16
|
+
|
|
22
17
|
from hspylib.core.enums.charset import Charset
|
|
23
18
|
from hspylib.core.enums.enumeration import Enumeration
|
|
24
19
|
from hspylib.core.tools.commons import run_dir, syserr
|
|
25
20
|
from hspylib.core.tools.text_tools import strip_linebreaks
|
|
26
|
-
from hspylib.modules.application.argparse.parser_action import ParserAction
|
|
27
21
|
from hspylib.modules.application.exit_status import ExitStatus
|
|
28
22
|
from hspylib.modules.application.version import Version
|
|
29
23
|
|
|
30
|
-
import
|
|
24
|
+
from clitt.__classpath__ import _Classpath
|
|
25
|
+
from clitt.addons.appman.appman import AppManager
|
|
26
|
+
from clitt.addons.appman.appman_enums import AppExtension, AppType
|
|
27
|
+
from clitt.addons.widman.widman import WidgetManager
|
|
28
|
+
from clitt.core.tui.tui_application import TUIApplication
|
|
31
29
|
|
|
32
30
|
|
|
33
31
|
class Main(TUIApplication):
|
|
@@ -45,7 +43,6 @@ class Main(TUIApplication):
|
|
|
45
43
|
# fmt: off
|
|
46
44
|
APPMAN = 'appman'
|
|
47
45
|
WIDGETS = 'widgets'
|
|
48
|
-
SETMAN = 'setman'
|
|
49
46
|
# fmt: on
|
|
50
47
|
|
|
51
48
|
@property
|
|
@@ -85,25 +82,7 @@ class Main(TUIApplication):
|
|
|
85
82
|
'presented in a dashboard',
|
|
86
83
|
nargs='?') \
|
|
87
84
|
.add_parameter(
|
|
88
|
-
'widget-args', "the widget's arguments (if applicable)", nargs='*')
|
|
89
|
-
.argument(self.Addon.SETMAN.val, 'app Settings Manager: Manage your terminal settings') \
|
|
90
|
-
.add_parameter(
|
|
91
|
-
'operation',
|
|
92
|
-
'the operation to be performed against your settings. ',
|
|
93
|
-
choices=SetmanOps.choices()) \
|
|
94
|
-
.add_option(
|
|
95
|
-
'name', 'n', 'name',
|
|
96
|
-
'the settings name. ', nargs='?') \
|
|
97
|
-
.add_option(
|
|
98
|
-
'value', 'v', 'value',
|
|
99
|
-
'the settings value. ', nargs='?') \
|
|
100
|
-
.add_option(
|
|
101
|
-
'stype', 't', 'type',
|
|
102
|
-
'the settings type. ',
|
|
103
|
-
choices=SettingsType.choices()) \
|
|
104
|
-
.add_option(
|
|
105
|
-
'simple', 's', 'simple',
|
|
106
|
-
'display without formatting. ', action=ParserAction.STORE_TRUE) # fmt: on
|
|
85
|
+
'widget-args', "the widget's arguments (if applicable)", nargs='*')
|
|
107
86
|
|
|
108
87
|
def _main(self, *params, **kwargs) -> ExitStatus:
|
|
109
88
|
"""Main entry point handler."""
|
|
@@ -117,8 +96,6 @@ class Main(TUIApplication):
|
|
|
117
96
|
self.start_appman()
|
|
118
97
|
elif app == self.Addon.WIDGETS.val:
|
|
119
98
|
self.start_widman()
|
|
120
|
-
elif app == self.Addon.SETMAN.val:
|
|
121
|
-
self.start_setman()
|
|
122
99
|
else:
|
|
123
100
|
syserr(f"### Invalid Addon application: {app}")
|
|
124
101
|
self.usage(ExitStatus.FAILED)
|
|
@@ -157,16 +134,6 @@ class Main(TUIApplication):
|
|
|
157
134
|
app_ext.append(AppExtension.GIT)
|
|
158
135
|
addon.create(args.app_name, AppType.of_value(args.app_type), list(app_ext), args.dest_dir or run_dir())
|
|
159
136
|
|
|
160
|
-
def start_setman(self) -> None:
|
|
161
|
-
"""Start the Setman application."""
|
|
162
|
-
addon = SetMan(self)
|
|
163
|
-
op = SetmanOps.of_value(self.get_arg("operation"), ignore_case=True)
|
|
164
|
-
name = self.get_arg("name")
|
|
165
|
-
value = self.get_arg("value")
|
|
166
|
-
stype = self.get_arg("stype")
|
|
167
|
-
simple_fmt: bool = bool(self.get_arg("simple"))
|
|
168
|
-
addon.execute(op, name, value, SettingsType.of_value(stype) if stype else None, simple_fmt)
|
|
169
|
-
|
|
170
137
|
|
|
171
138
|
# Application entry point
|
|
172
139
|
if __name__ == "__main__":
|
clitt/addons/__init__.py
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
# _*_ coding: utf-8 _*_
|
|
2
2
|
#
|
|
3
|
-
# hspylib-clitt v0.9.
|
|
3
|
+
# hspylib-clitt v0.9.51
|
|
4
4
|
#
|
|
5
5
|
# Package: main.clitt.addons
|
|
6
6
|
"""Package initialization."""
|
|
7
7
|
|
|
8
8
|
__all__ = [
|
|
9
9
|
'appman',
|
|
10
|
-
'setman',
|
|
11
10
|
'widman'
|
|
12
11
|
]
|
|
13
|
-
__version__ = '0.9.
|
|
12
|
+
__version__ = '0.9.51'
|
clitt/addons/appman/__init__.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# _*_ coding: utf-8 _*_
|
|
2
2
|
#
|
|
3
|
-
# hspylib-clitt v0.9.
|
|
3
|
+
# hspylib-clitt v0.9.51
|
|
4
4
|
#
|
|
5
5
|
# Package: main.clitt.addons.appman
|
|
6
6
|
"""Package initialization."""
|
|
@@ -10,4 +10,4 @@ __all__ = [
|
|
|
10
10
|
'appman_enums',
|
|
11
11
|
'templates'
|
|
12
12
|
]
|
|
13
|
-
__version__ = '0.9.
|
|
13
|
+
__version__ = '0.9.51'
|
clitt/addons/appman/appman.py
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
Copyright 2023, HsPyLib team
|
|
14
14
|
"""
|
|
15
15
|
from clitt.addons.appman.appman_enums import AppExtension, AppType
|
|
16
|
+
from clitt.core.term.terminal import Terminal
|
|
16
17
|
from clitt.core.tui.minput.input_validator import InputValidator
|
|
17
18
|
from clitt.core.tui.minput.minput import MenuInput, minput
|
|
18
19
|
from hspylib.core.enums.charset import Charset
|
|
@@ -25,7 +26,6 @@ from hspylib.core.tools.text_tools import camelcase, ensure_endswith
|
|
|
25
26
|
from hspylib.modules.application.application import Application
|
|
26
27
|
from hspylib.modules.application.exit_status import ExitStatus
|
|
27
28
|
from hspylib.modules.application.version import Version
|
|
28
|
-
from hspylib.modules.cli.terminal import Terminal
|
|
29
29
|
from hspylib.modules.fetch.fetch import get
|
|
30
30
|
from textwrap import dedent
|
|
31
31
|
from typing import List
|
clitt/addons/widman/__init__.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# _*_ coding: utf-8 _*_
|
|
2
2
|
#
|
|
3
|
-
# hspylib-clitt v0.9.
|
|
3
|
+
# hspylib-clitt v0.9.51
|
|
4
4
|
#
|
|
5
5
|
# Package: main.clitt.addons.widman
|
|
6
6
|
"""Package initialization."""
|
|
@@ -11,4 +11,4 @@ __all__ = [
|
|
|
11
11
|
'widgets',
|
|
12
12
|
'widman'
|
|
13
13
|
]
|
|
14
|
-
__version__ = '0.9.
|
|
14
|
+
__version__ = '0.9.51'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# _*_ coding: utf-8 _*_
|
|
2
2
|
#
|
|
3
|
-
# hspylib-clitt v0.9.
|
|
3
|
+
# hspylib-clitt v0.9.51
|
|
4
4
|
#
|
|
5
5
|
# Package: main.clitt.addons.widman.widgets
|
|
6
6
|
"""Package initialization."""
|
|
@@ -11,4 +11,4 @@ __all__ = [
|
|
|
11
11
|
'widget_send_msg',
|
|
12
12
|
'widget_time_calc'
|
|
13
13
|
]
|
|
14
|
-
__version__ = '0.9.
|
|
14
|
+
__version__ = '0.9.51'
|
|
@@ -19,12 +19,13 @@ from hspylib.core.tools.commons import human_readable_bytes, sysout
|
|
|
19
19
|
from hspylib.modules.application.exit_status import ExitStatus
|
|
20
20
|
from hspylib.modules.application.version import Version
|
|
21
21
|
from hspylib.modules.cli.keyboard import Keyboard
|
|
22
|
-
from hspylib.modules.cli.terminal import Terminal
|
|
23
22
|
from textwrap import dedent
|
|
24
23
|
from time import sleep
|
|
25
24
|
|
|
26
25
|
import re
|
|
27
26
|
|
|
27
|
+
from clitt.core.terminal import Terminal
|
|
28
|
+
|
|
28
29
|
|
|
29
30
|
class WidgetFree(Widget):
|
|
30
31
|
"""HsPyLib Widget to Report current system memory usage"""
|
|
@@ -21,7 +21,6 @@ from hspylib.core.zoned_datetime import now
|
|
|
21
21
|
from hspylib.modules.application.argparse.argument_parser import HSArgumentParser
|
|
22
22
|
from hspylib.modules.application.exit_status import ExitStatus
|
|
23
23
|
from hspylib.modules.application.version import Version
|
|
24
|
-
from hspylib.modules.cli.terminal import Terminal
|
|
25
24
|
from textwrap import dedent
|
|
26
25
|
from typing import List
|
|
27
26
|
|
|
@@ -30,6 +29,8 @@ import os
|
|
|
30
29
|
import re
|
|
31
30
|
import sys
|
|
32
31
|
|
|
32
|
+
from clitt.core.terminal import Terminal
|
|
33
|
+
|
|
33
34
|
|
|
34
35
|
class WidgetPunch(Widget):
|
|
35
36
|
"""HsPyLib Widget to Report current system memory usage"""
|
|
@@ -232,7 +233,7 @@ class WidgetPunch(Widget):
|
|
|
232
233
|
|
|
233
234
|
def _edit_punches(self) -> None:
|
|
234
235
|
"""Open the default system editor to edit punches."""
|
|
235
|
-
Terminal.
|
|
236
|
+
Terminal.open(self.HHS_PUNCH_FILE)
|
|
236
237
|
|
|
237
238
|
def _reset_punches(self) -> None:
|
|
238
239
|
"""Rename the punch file as a weekly punch file and reset current punch file."""
|
clitt/core/__init__.py
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# _*_ coding: utf-8 _*_
|
|
2
2
|
#
|
|
3
|
-
# hspylib-clitt v0.9.
|
|
3
|
+
# hspylib-clitt v0.9.51
|
|
4
4
|
#
|
|
5
5
|
# Package: main.clitt.core
|
|
6
6
|
"""Package initialization."""
|
|
7
7
|
|
|
8
8
|
__all__ = [
|
|
9
9
|
'icons',
|
|
10
|
-
'
|
|
10
|
+
'term',
|
|
11
11
|
'tui'
|
|
12
12
|
]
|
|
13
|
-
__version__ = '0.9.
|
|
13
|
+
__version__ = '0.9.51'
|
clitt/core/icons/__init__.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# _*_ coding: utf-8 _*_
|
|
2
2
|
#
|
|
3
|
-
# hspylib-clitt v0.9.
|
|
3
|
+
# hspylib-clitt v0.9.51
|
|
4
4
|
#
|
|
5
5
|
# Package: main.clitt.core.icons
|
|
6
6
|
"""Package initialization."""
|
|
@@ -9,4 +9,4 @@ __all__ = [
|
|
|
9
9
|
'emojis',
|
|
10
10
|
'font_awesome'
|
|
11
11
|
]
|
|
12
|
-
__version__ = '0.9.
|
|
12
|
+
__version__ = '0.9.51'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# _*_ coding: utf-8 _*_
|
|
2
2
|
#
|
|
3
|
-
# hspylib-clitt v0.9.
|
|
3
|
+
# hspylib-clitt v0.9.51
|
|
4
4
|
#
|
|
5
5
|
# Package: main.clitt.core.icons.emojis
|
|
6
6
|
"""Package initialization."""
|
|
@@ -9,4 +9,4 @@ __all__ = [
|
|
|
9
9
|
'emojis',
|
|
10
10
|
'face_smiling'
|
|
11
11
|
]
|
|
12
|
-
__version__ = '0.9.
|
|
12
|
+
__version__ = '0.9.51'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# _*_ coding: utf-8 _*_
|
|
2
2
|
#
|
|
3
|
-
# hspylib-clitt v0.9.
|
|
3
|
+
# hspylib-clitt v0.9.51
|
|
4
4
|
#
|
|
5
5
|
# Package: main.clitt.core.icons.font_awesome
|
|
6
6
|
"""Package initialization."""
|
|
@@ -14,4 +14,4 @@ __all__ = [
|
|
|
14
14
|
'nav_icons',
|
|
15
15
|
'widget_icons'
|
|
16
16
|
]
|
|
17
|
-
__version__ = '0.9.
|
|
17
|
+
__version__ = '0.9.51'
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import logging as log
|
|
2
|
+
import re
|
|
3
|
+
import sys
|
|
4
|
+
import termios
|
|
5
|
+
import tty
|
|
6
|
+
from shutil import get_terminal_size
|
|
7
|
+
from typing import TypeVar, Tuple, Callable, Union
|
|
8
|
+
|
|
9
|
+
from hspylib.core.exception.exceptions import NotATerminalError
|
|
10
|
+
from hspylib.core.tools.commons import is_debugging
|
|
11
|
+
from hspylib.modules.cli.vt100.vt_100 import Vt100
|
|
12
|
+
|
|
13
|
+
DIMENSION = TypeVar("DIMENSION", bound=Tuple[int, int])
|
|
14
|
+
|
|
15
|
+
POSITION = TypeVar("POSITION", bound=Tuple[int, int])
|
|
16
|
+
|
|
17
|
+
CB_RESIZE = TypeVar("CB_RESIZE", bound=Callable[[None], None])
|
|
18
|
+
|
|
19
|
+
MOVE_DIRECTION = TypeVar("MOVE_DIRECTION", bound="Cursor.Direction")
|
|
20
|
+
|
|
21
|
+
ERASE_DIRECTION = TypeVar("ERASE_DIRECTION", bound=Union["Cursor.Direction", "Screen.Portion"])
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_dimensions(fallback: Tuple[int, int] = (24, 80)) -> Tuple[int, int]:
|
|
25
|
+
"""Retrieve the size of the terminal.
|
|
26
|
+
:return lines, columns
|
|
27
|
+
"""
|
|
28
|
+
if not sys.stdout.isatty():
|
|
29
|
+
log.error(NotATerminalError("screen_size:: Requires a terminal (TTY)"))
|
|
30
|
+
return fallback
|
|
31
|
+
dim = get_terminal_size((fallback[1], fallback[0]))
|
|
32
|
+
return dim.lines, dim.columns
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def get_cursor_position(fallback: Tuple[int, int] = (0, 0)) -> Tuple[int, int]:
|
|
36
|
+
"""Get the terminal cursor position.
|
|
37
|
+
:return line, column
|
|
38
|
+
"""
|
|
39
|
+
pos, buf = fallback, ""
|
|
40
|
+
|
|
41
|
+
if not sys.stdout.isatty():
|
|
42
|
+
log.error(NotATerminalError("get_cursor_position:: Requires a terminal (TTY)"))
|
|
43
|
+
return pos
|
|
44
|
+
|
|
45
|
+
if is_debugging():
|
|
46
|
+
return pos
|
|
47
|
+
|
|
48
|
+
stdin = sys.stdin.fileno() # Get the stdin file descriptor.
|
|
49
|
+
attrs = termios.tcgetattr(stdin) # Save terminal attributes.
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
tty.setcbreak(stdin, termios.TCSANOW)
|
|
53
|
+
sys.stdout.write(Vt100.get_cursor_pos())
|
|
54
|
+
sys.stdout.flush()
|
|
55
|
+
while not buf or buf[-1] != "R":
|
|
56
|
+
buf += sys.stdin.read(1)
|
|
57
|
+
if matches := re.match(r"^\x1b\[(\d*);(\d*)R", buf): # If the response is 'Esc[r;cR'
|
|
58
|
+
groups = matches.groups()
|
|
59
|
+
pos = int(groups[0]), int(groups[1])
|
|
60
|
+
finally:
|
|
61
|
+
termios.tcsetattr(stdin, termios.TCSANOW, attrs) # Reset terminal attributes
|
|
62
|
+
|
|
63
|
+
return pos
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from hspylib.core.enums.enumeration import Enumeration
|
|
5
|
+
from hspylib.core.metaclass.singleton import Singleton
|
|
6
|
+
from hspylib.core.tools.commons import sysout
|
|
7
|
+
from hspylib.core.tools.text_tools import last_index_of
|
|
8
|
+
from hspylib.modules.cli.vt100.vt_100 import Vt100
|
|
9
|
+
from hspylib.modules.cli.vt100.vt_code import VtCode
|
|
10
|
+
from hspylib.modules.cli.vt100.vt_color import VtColor
|
|
11
|
+
|
|
12
|
+
from clitt.core.term.commons import POSITION, MOVE_DIRECTION, ERASE_DIRECTION, get_cursor_position
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Cursor(metaclass=Singleton):
|
|
16
|
+
"""Provide a base class for the screen cursor."""
|
|
17
|
+
|
|
18
|
+
INSTANCE = None
|
|
19
|
+
|
|
20
|
+
CURSOR_HOME = 1, 1
|
|
21
|
+
|
|
22
|
+
class Direction(Enumeration):
|
|
23
|
+
"""Provide a base class for the cursor direction."""
|
|
24
|
+
# fmt: off
|
|
25
|
+
UP = '%ED1%', '%CUU({n})%' # Cursor up (line)
|
|
26
|
+
RIGHT = '%EL0%', '%CUF({n})%' # Cursor right (forward)
|
|
27
|
+
DOWN = '%ED0%', '%CUD({n})%' # Cursor down (line)
|
|
28
|
+
LEFT = '%EL1%', '%CUB({n})%' # Cursor left (backward)
|
|
29
|
+
# fmt: on
|
|
30
|
+
|
|
31
|
+
def __init__(self):
|
|
32
|
+
self._position: POSITION = get_cursor_position() or self.CURSOR_HOME
|
|
33
|
+
self._bottom: POSITION = self.CURSOR_HOME
|
|
34
|
+
self._saved_attrs = self._position, self._bottom
|
|
35
|
+
|
|
36
|
+
def __str__(self):
|
|
37
|
+
return f"({', '.join(list(map(str, self._position)))})"
|
|
38
|
+
|
|
39
|
+
def __repr__(self):
|
|
40
|
+
return str(self)
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def position(self) -> POSITION:
|
|
44
|
+
return self._position
|
|
45
|
+
|
|
46
|
+
@position.setter
|
|
47
|
+
def position(self, new_position: POSITION) -> None:
|
|
48
|
+
self._bottom = (new_position[0], new_position[1]) if new_position >= self._bottom else self._bottom
|
|
49
|
+
self._position = new_position
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def bottom(self) -> POSITION:
|
|
53
|
+
return self._bottom
|
|
54
|
+
|
|
55
|
+
def home(self) -> None:
|
|
56
|
+
"""Move the cursor to home position."""
|
|
57
|
+
self.move_to(self.CURSOR_HOME[0], self.CURSOR_HOME[1])
|
|
58
|
+
|
|
59
|
+
def end(self) -> None:
|
|
60
|
+
"""Move the cursor to the bottom most position on the screen."""
|
|
61
|
+
self.move_to(self.bottom[0], self.bottom[1])
|
|
62
|
+
|
|
63
|
+
def move_to(self, row: int = None, column: int = None) -> POSITION:
|
|
64
|
+
"""Move the cursor to the specified position."""
|
|
65
|
+
row_pos = max(self.CURSOR_HOME[0], row if row is not None else self.position[0])
|
|
66
|
+
col_pos = max(self.CURSOR_HOME[1], column if column is not None else self.position[1])
|
|
67
|
+
sysout(f"%CUP({row_pos};{col_pos})%", end="")
|
|
68
|
+
self.position = row_pos, col_pos
|
|
69
|
+
return self.position
|
|
70
|
+
|
|
71
|
+
def move(self, amount: int, direction: MOVE_DIRECTION) -> POSITION:
|
|
72
|
+
"""Move the cursor towards the specified direction."""
|
|
73
|
+
sysout(direction.value[1].format(n=amount), end="")
|
|
74
|
+
row_pos, col_pos = self.position
|
|
75
|
+
match direction:
|
|
76
|
+
case Cursor.Direction.UP:
|
|
77
|
+
row_pos -= max(0, amount)
|
|
78
|
+
case Cursor.Direction.DOWN:
|
|
79
|
+
row_pos += max(0, amount)
|
|
80
|
+
case Cursor.Direction.LEFT:
|
|
81
|
+
col_pos -= max(0, amount)
|
|
82
|
+
case Cursor.Direction.RIGHT:
|
|
83
|
+
col_pos += max(0, amount)
|
|
84
|
+
self.position = row_pos, col_pos
|
|
85
|
+
return self.position
|
|
86
|
+
|
|
87
|
+
def erase(self, direction: ERASE_DIRECTION) -> POSITION:
|
|
88
|
+
"""Erase the screen following the specified direction.
|
|
89
|
+
Note: It does not move the cursor along the way."""
|
|
90
|
+
sysout(direction.value[0], end="")
|
|
91
|
+
return self.position
|
|
92
|
+
|
|
93
|
+
def track(self) -> POSITION:
|
|
94
|
+
"""Track the cursor position."""
|
|
95
|
+
self.position = get_cursor_position() or self.position
|
|
96
|
+
return self.position
|
|
97
|
+
|
|
98
|
+
def write(self, obj: Any, end: str = "") -> POSITION:
|
|
99
|
+
"""Write the string representation of the object to the screen."""
|
|
100
|
+
sysout(obj, end=end)
|
|
101
|
+
text = (str(obj) + end).replace("%EOL%", os.linesep)
|
|
102
|
+
text = VtColor.strip_colors(VtCode.strip_codes(text))
|
|
103
|
+
text_offset = len(text[max(0, last_index_of(text, os.linesep)):])
|
|
104
|
+
self.position = self.position[0] + text.count(os.linesep), text_offset + (
|
|
105
|
+
self.position[1] if text.count(os.linesep) == 0 else 0
|
|
106
|
+
)
|
|
107
|
+
return self.position
|
|
108
|
+
|
|
109
|
+
def writeln(self, obj: Any) -> POSITION:
|
|
110
|
+
"""Write the string representation of the object to the screen, appending a new line."""
|
|
111
|
+
return self.write(obj, end=os.linesep)
|
|
112
|
+
|
|
113
|
+
def save(self) -> POSITION:
|
|
114
|
+
"""Save the current cursor position and attributes."""
|
|
115
|
+
sysout(Vt100.save_cursor(), end="")
|
|
116
|
+
self._saved_attrs = self._position, self._bottom
|
|
117
|
+
return self.position
|
|
118
|
+
|
|
119
|
+
def restore(self) -> POSITION:
|
|
120
|
+
"""Save the current cursor position and attributes."""
|
|
121
|
+
sysout(Vt100.restore_cursor(), end="")
|
|
122
|
+
self._position = self._saved_attrs[0]
|
|
123
|
+
self._bottom = self._saved_attrs[1]
|
|
124
|
+
return self.position
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
from threading import Timer
|
|
3
|
+
from typing import Optional, List
|
|
4
|
+
|
|
5
|
+
from hspylib.core.enums.enumeration import Enumeration
|
|
6
|
+
from hspylib.core.metaclass.singleton import Singleton
|
|
7
|
+
from hspylib.core.tools.commons import sysout
|
|
8
|
+
|
|
9
|
+
from clitt.core.term.commons import DIMENSION, CB_RESIZE, get_dimensions
|
|
10
|
+
from clitt.core.term.cursor import Cursor
|
|
11
|
+
from clitt.core.tui.tui_preferences import TUIPreferences
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Screen(metaclass=Singleton):
|
|
15
|
+
"""Provide a base class for terminal UI components."""
|
|
16
|
+
|
|
17
|
+
INSTANCE = None
|
|
18
|
+
|
|
19
|
+
RESIZE_WATCH_INTERVAL = 0.5
|
|
20
|
+
|
|
21
|
+
class Portion(Enumeration):
|
|
22
|
+
"""Provide a base class for the portions of the screen."""
|
|
23
|
+
# fmt: off
|
|
24
|
+
SCREEN = '%ED2%', '' # Entire screen (screen)
|
|
25
|
+
LINE = '%EL2%', '' # Entire line (line)
|
|
26
|
+
# fmt: on
|
|
27
|
+
|
|
28
|
+
def __init__(self):
|
|
29
|
+
self._preferences: TUIPreferences = TUIPreferences.INSTANCE
|
|
30
|
+
self._dimension: DIMENSION = get_dimensions()
|
|
31
|
+
self._cursor: Cursor = Cursor.INSTANCE or Cursor()
|
|
32
|
+
self._resize_timer: Optional[Timer] = None
|
|
33
|
+
self._cb_watchers: List[CB_RESIZE] = []
|
|
34
|
+
self._alternate: bool = False
|
|
35
|
+
self._resize_watcher()
|
|
36
|
+
|
|
37
|
+
def __str__(self):
|
|
38
|
+
return f"Terminal.Screen(rows={self.lines}, columns={self.columns}, cursor={self.cursor})"
|
|
39
|
+
|
|
40
|
+
def __repr__(self):
|
|
41
|
+
return str(self)
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def preferences(self) -> TUIPreferences:
|
|
45
|
+
return self._preferences
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def dimension(self) -> DIMENSION:
|
|
49
|
+
return self._dimension
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def lines(self) -> int:
|
|
53
|
+
return self._dimension[0]
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def columns(self) -> int:
|
|
57
|
+
return self._dimension[1]
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def cursor(self) -> Cursor:
|
|
61
|
+
return self._cursor
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def alternate(self) -> bool:
|
|
65
|
+
return self._alternate
|
|
66
|
+
|
|
67
|
+
@alternate.setter
|
|
68
|
+
def alternate(self, enable: bool) -> None:
|
|
69
|
+
"""Switch to the alternate screen buffer.
|
|
70
|
+
:param enable: alternate enable on/off.
|
|
71
|
+
"""
|
|
72
|
+
self._alternate = enable
|
|
73
|
+
sysout(f"%SC{'A' if enable else 'M'}%", end="")
|
|
74
|
+
self.cursor.track()
|
|
75
|
+
|
|
76
|
+
def clear(self) -> None:
|
|
77
|
+
"""Clear terminal and move the cursor to HOME position (0, 0)."""
|
|
78
|
+
sysout("%ED2%%HOM%", end="")
|
|
79
|
+
self.cursor.home()
|
|
80
|
+
|
|
81
|
+
def add_watcher(self, watcher: CB_RESIZE) -> None:
|
|
82
|
+
"""Add a resize watcher."""
|
|
83
|
+
self._cb_watchers.append(watcher)
|
|
84
|
+
if not self._resize_timer:
|
|
85
|
+
self._resize_watcher()
|
|
86
|
+
|
|
87
|
+
def _resize_watcher(self) -> None:
|
|
88
|
+
"""Add a watcher for screen resizes. If a resize is detected, the callback is called with the
|
|
89
|
+
new dimension."""
|
|
90
|
+
if self._cb_watchers and threading.main_thread().is_alive():
|
|
91
|
+
dimension: DIMENSION = get_dimensions()
|
|
92
|
+
self._resize_timer = Timer(self.RESIZE_WATCH_INTERVAL, self._resize_watcher)
|
|
93
|
+
if dimension != self._dimension:
|
|
94
|
+
list(map(lambda cb_w: cb_w(), self._cb_watchers))
|
|
95
|
+
self._dimension = dimension
|
|
96
|
+
self._resize_timer.start()
|