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.

Files changed (60) hide show
  1. clitt/.version +1 -1
  2. clitt/__classpath__.py +1 -1
  3. clitt/__init__.py +4 -3
  4. clitt/__main__.py +8 -41
  5. clitt/addons/__init__.py +2 -3
  6. clitt/addons/appman/__init__.py +2 -2
  7. clitt/addons/appman/appman.py +1 -1
  8. clitt/addons/appman/templates/__init__.py +2 -2
  9. clitt/addons/widman/__init__.py +2 -2
  10. clitt/addons/widman/widgets/__init__.py +2 -2
  11. clitt/addons/widman/widgets/widget_free.py +2 -1
  12. clitt/addons/widman/widgets/widget_punch.py +3 -2
  13. clitt/core/__init__.py +3 -3
  14. clitt/core/icons/__init__.py +2 -2
  15. clitt/core/icons/emojis/__init__.py +2 -2
  16. clitt/core/icons/font_awesome/__init__.py +2 -2
  17. clitt/core/term/__init__.py +14 -0
  18. clitt/core/term/commons.py +63 -0
  19. clitt/core/term/cursor.py +124 -0
  20. clitt/core/term/screen.py +96 -0
  21. clitt/core/term/terminal.py +173 -0
  22. clitt/core/tui/__init__.py +3 -4
  23. clitt/core/tui/mchoose/__init__.py +2 -2
  24. clitt/core/tui/mchoose/menu_choose.py +6 -5
  25. clitt/core/tui/mdashboard/__init__.py +2 -2
  26. clitt/core/tui/mdashboard/menu_dashboard.py +9 -9
  27. clitt/core/tui/menu/__init__.py +2 -2
  28. clitt/core/tui/menu/tui_menu_action.py +1 -3
  29. clitt/core/tui/menu/tui_menu_item.py +6 -5
  30. clitt/core/tui/menu/tui_menu_ui.py +5 -7
  31. clitt/core/tui/menu/tui_menu_view.py +2 -3
  32. clitt/core/tui/minput/__init__.py +2 -2
  33. clitt/core/tui/minput/menu_input.py +22 -18
  34. clitt/core/tui/minput/minput_utils.py +4 -3
  35. clitt/core/tui/mselect/__init__.py +2 -2
  36. clitt/core/tui/mselect/menu_select.py +6 -5
  37. clitt/core/tui/table/__init__.py +2 -2
  38. clitt/core/tui/table/table_renderer.py +13 -15
  39. clitt/core/tui/tui_application.py +27 -13
  40. clitt/core/tui/tui_component.py +28 -21
  41. clitt/core/tui/tui_preferences.py +3 -0
  42. clitt/utils/__init__.py +11 -0
  43. clitt/utils/git_utils.py +67 -0
  44. clitt/welcome.txt +6 -6
  45. {hspylib_clitt-0.9.41.dist-info → hspylib_clitt-0.9.51.dist-info}/METADATA +4 -4
  46. hspylib_clitt-0.9.51.dist-info/RECORD +90 -0
  47. clitt/addons/setman/__init__.py +0 -13
  48. clitt/addons/setman/setman.py +0 -148
  49. clitt/addons/setman/setman_config.py +0 -26
  50. clitt/addons/setman/setman_enums.py +0 -54
  51. clitt/core/settings/__init__.py +0 -15
  52. clitt/core/settings/settings.py +0 -182
  53. clitt/core/settings/settings_config.py +0 -47
  54. clitt/core/settings/settings_entry.py +0 -108
  55. clitt/core/settings/settings_repository.py +0 -103
  56. clitt/core/settings/settings_service.py +0 -50
  57. clitt/core/tui/tui_screen.py +0 -222
  58. hspylib_clitt-0.9.41.dist-info/RECORD +0 -94
  59. {hspylib_clitt-0.9.41.dist-info → hspylib_clitt-0.9.51.dist-info}/WHEEL +0 -0
  60. {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.41
1
+ 0.9.51
clitt/__classpath__.py CHANGED
@@ -25,4 +25,4 @@ class _Classpath(Classpath):
25
25
 
26
26
 
27
27
  # Instantiate the classpath singleton
28
- _Classpath()
28
+ assert _Classpath().INSTANCE is not None, "Failed to create Classpath instance"
clitt/__init__.py CHANGED
@@ -1,12 +1,13 @@
1
1
  # _*_ coding: utf-8 _*_
2
2
  #
3
- # hspylib-clitt v0.9.41
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.41'
13
+ __version__ = '0.9.51'
clitt/__main__.py CHANGED
@@ -12,22 +12,20 @@
12
12
 
13
13
  Copyright 2023, HsPyLib team
14
14
  """
15
- from clitt.__classpath__ import _Classpath
16
- from clitt.addons.appman.appman import AppManager
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 sys
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.41
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.41'
12
+ __version__ = '0.9.51'
@@ -1,6 +1,6 @@
1
1
  # _*_ coding: utf-8 _*_
2
2
  #
3
- # hspylib-clitt v0.9.41
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.41'
13
+ __version__ = '0.9.51'
@@ -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
@@ -1,6 +1,6 @@
1
1
  # _*_ coding: utf-8 _*_
2
2
  #
3
- # hspylib-clitt v0.9.41
3
+ # hspylib-clitt v0.9.51
4
4
  #
5
5
  # Package: main.clitt.addons.appman.templates
6
6
  """Package initialization."""
@@ -8,4 +8,4 @@
8
8
  __all__ = [
9
9
 
10
10
  ]
11
- __version__ = '0.9.41'
11
+ __version__ = '0.9.51'
@@ -1,6 +1,6 @@
1
1
  # _*_ coding: utf-8 _*_
2
2
  #
3
- # hspylib-clitt v0.9.41
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.41'
14
+ __version__ = '0.9.51'
@@ -1,6 +1,6 @@
1
1
  # _*_ coding: utf-8 _*_
2
2
  #
3
- # hspylib-clitt v0.9.41
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.41'
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.open_file(self.HHS_PUNCH_FILE)
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.41
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
- 'settings',
10
+ 'term',
11
11
  'tui'
12
12
  ]
13
- __version__ = '0.9.41'
13
+ __version__ = '0.9.51'
@@ -1,6 +1,6 @@
1
1
  # _*_ coding: utf-8 _*_
2
2
  #
3
- # hspylib-clitt v0.9.41
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.41'
12
+ __version__ = '0.9.51'
@@ -1,6 +1,6 @@
1
1
  # _*_ coding: utf-8 _*_
2
2
  #
3
- # hspylib-clitt v0.9.41
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.41'
12
+ __version__ = '0.9.51'
@@ -1,6 +1,6 @@
1
1
  # _*_ coding: utf-8 _*_
2
2
  #
3
- # hspylib-clitt v0.9.41
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.41'
17
+ __version__ = '0.9.51'
@@ -0,0 +1,14 @@
1
+ # _*_ coding: utf-8 _*_
2
+ #
3
+ # hspylib-clitt v0.9.51
4
+ #
5
+ # Package: main.clitt.core.term
6
+ """Package initialization."""
7
+
8
+ __all__ = [
9
+ 'commons',
10
+ 'cursor',
11
+ 'screen',
12
+ 'terminal'
13
+ ]
14
+ __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()