zettcode-tui 0.4.0__tar.gz

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.
Files changed (83) hide show
  1. zettcode_tui-0.4.0/.gitignore +37 -0
  2. zettcode_tui-0.4.0/PKG-INFO +20 -0
  3. zettcode_tui-0.4.0/pyproject.toml +45 -0
  4. zettcode_tui-0.4.0/src/zettcode/tui/__init__.py +422 -0
  5. zettcode_tui-0.4.0/src/zettcode/tui/_compat.py +32 -0
  6. zettcode_tui-0.4.0/src/zettcode/tui/capabilities.py +107 -0
  7. zettcode_tui-0.4.0/src/zettcode/tui/clipboard.py +91 -0
  8. zettcode_tui-0.4.0/src/zettcode/tui/console.py +229 -0
  9. zettcode_tui-0.4.0/src/zettcode/tui/core/__init__.py +63 -0
  10. zettcode_tui-0.4.0/src/zettcode/tui/core/app.py +658 -0
  11. zettcode_tui-0.4.0/src/zettcode/tui/core/events.py +134 -0
  12. zettcode_tui-0.4.0/src/zettcode/tui/core/focus.py +85 -0
  13. zettcode_tui-0.4.0/src/zettcode/tui/core/geometry.py +212 -0
  14. zettcode_tui-0.4.0/src/zettcode/tui/core/host.py +66 -0
  15. zettcode_tui-0.4.0/src/zettcode/tui/core/keymap.py +218 -0
  16. zettcode_tui-0.4.0/src/zettcode/tui/core/scheduler.py +105 -0
  17. zettcode_tui-0.4.0/src/zettcode/tui/core/screen.py +80 -0
  18. zettcode_tui-0.4.0/src/zettcode/tui/core/theme.py +248 -0
  19. zettcode_tui-0.4.0/src/zettcode/tui/core/widget.py +204 -0
  20. zettcode_tui-0.4.0/src/zettcode/tui/gallery.py +588 -0
  21. zettcode_tui-0.4.0/src/zettcode/tui/glyphs.py +73 -0
  22. zettcode_tui-0.4.0/src/zettcode/tui/input/__init__.py +18 -0
  23. zettcode_tui-0.4.0/src/zettcode/tui/input/bridge.py +46 -0
  24. zettcode_tui-0.4.0/src/zettcode/tui/input/decoder.py +234 -0
  25. zettcode_tui-0.4.0/src/zettcode/tui/input/events.py +64 -0
  26. zettcode_tui-0.4.0/src/zettcode/tui/input/keys.py +289 -0
  27. zettcode_tui-0.4.0/src/zettcode/tui/input/reader.py +147 -0
  28. zettcode_tui-0.4.0/src/zettcode/tui/layout/__init__.py +24 -0
  29. zettcode_tui-0.4.0/src/zettcode/tui/layout/box.py +153 -0
  30. zettcode_tui-0.4.0/src/zettcode/tui/layout/overlay.py +164 -0
  31. zettcode_tui-0.4.0/src/zettcode/tui/layout/scroll.py +477 -0
  32. zettcode_tui-0.4.0/src/zettcode/tui/layout/solver.py +95 -0
  33. zettcode_tui-0.4.0/src/zettcode/tui/layout/spacing.py +137 -0
  34. zettcode_tui-0.4.0/src/zettcode/tui/py.typed +0 -0
  35. zettcode_tui-0.4.0/src/zettcode/tui/render/__init__.py +71 -0
  36. zettcode_tui-0.4.0/src/zettcode/tui/render/ansi.py +371 -0
  37. zettcode_tui-0.4.0/src/zettcode/tui/render/canvas.py +153 -0
  38. zettcode_tui-0.4.0/src/zettcode/tui/render/code.py +456 -0
  39. zettcode_tui-0.4.0/src/zettcode/tui/render/color.py +177 -0
  40. zettcode_tui-0.4.0/src/zettcode/tui/render/renderer.py +151 -0
  41. zettcode_tui-0.4.0/src/zettcode/tui/render/rich_text.py +80 -0
  42. zettcode_tui-0.4.0/src/zettcode/tui/render/style.py +121 -0
  43. zettcode_tui-0.4.0/src/zettcode/tui/render/sweep.py +87 -0
  44. zettcode_tui-0.4.0/src/zettcode/tui/render/text.py +319 -0
  45. zettcode_tui-0.4.0/src/zettcode/tui/runner.py +316 -0
  46. zettcode_tui-0.4.0/src/zettcode/tui/terminal.py +222 -0
  47. zettcode_tui-0.4.0/src/zettcode/tui/testing/__init__.py +6 -0
  48. zettcode_tui-0.4.0/src/zettcode/tui/testing/harness.py +220 -0
  49. zettcode_tui-0.4.0/src/zettcode/tui/testing/snapshot.py +64 -0
  50. zettcode_tui-0.4.0/src/zettcode/tui/theme_file.py +300 -0
  51. zettcode_tui-0.4.0/src/zettcode/tui/themes/ayu-mirage.toml +44 -0
  52. zettcode_tui-0.4.0/src/zettcode/tui/themes/catppuccin-latte.toml +44 -0
  53. zettcode_tui-0.4.0/src/zettcode/tui/themes/catppuccin-mocha.toml +44 -0
  54. zettcode_tui-0.4.0/src/zettcode/tui/themes/dracula.toml +44 -0
  55. zettcode_tui-0.4.0/src/zettcode/tui/themes/github-dimmed.toml +44 -0
  56. zettcode_tui-0.4.0/src/zettcode/tui/themes/github-light.toml +44 -0
  57. zettcode_tui-0.4.0/src/zettcode/tui/themes/gruvbox-dark.toml +44 -0
  58. zettcode_tui-0.4.0/src/zettcode/tui/themes/gruvbox-light.toml +44 -0
  59. zettcode_tui-0.4.0/src/zettcode/tui/themes/kanagawa.toml +44 -0
  60. zettcode_tui-0.4.0/src/zettcode/tui/themes/nord.toml +44 -0
  61. zettcode_tui-0.4.0/src/zettcode/tui/themes/one-dark.toml +44 -0
  62. zettcode_tui-0.4.0/src/zettcode/tui/themes/rose-pine-dawn.toml +44 -0
  63. zettcode_tui-0.4.0/src/zettcode/tui/themes/rose-pine.toml +44 -0
  64. zettcode_tui-0.4.0/src/zettcode/tui/themes/solarized-dark.toml +44 -0
  65. zettcode_tui-0.4.0/src/zettcode/tui/themes/solarized-light.toml +44 -0
  66. zettcode_tui-0.4.0/src/zettcode/tui/themes/tokyo-night.toml +44 -0
  67. zettcode_tui-0.4.0/src/zettcode/tui/widgets/__init__.py +121 -0
  68. zettcode_tui-0.4.0/src/zettcode/tui/widgets/collapsible.py +116 -0
  69. zettcode_tui-0.4.0/src/zettcode/tui/widgets/completion.py +211 -0
  70. zettcode_tui-0.4.0/src/zettcode/tui/widgets/console.py +442 -0
  71. zettcode_tui-0.4.0/src/zettcode/tui/widgets/dialog.py +165 -0
  72. zettcode_tui-0.4.0/src/zettcode/tui/widgets/diff.py +236 -0
  73. zettcode_tui-0.4.0/src/zettcode/tui/widgets/list.py +293 -0
  74. zettcode_tui-0.4.0/src/zettcode/tui/widgets/list_page.py +121 -0
  75. zettcode_tui-0.4.0/src/zettcode/tui/widgets/markdown.py +657 -0
  76. zettcode_tui-0.4.0/src/zettcode/tui/widgets/progress.py +166 -0
  77. zettcode_tui-0.4.0/src/zettcode/tui/widgets/rich_text.py +84 -0
  78. zettcode_tui-0.4.0/src/zettcode/tui/widgets/status.py +106 -0
  79. zettcode_tui-0.4.0/src/zettcode/tui/widgets/table.py +101 -0
  80. zettcode_tui-0.4.0/src/zettcode/tui/widgets/tasks.py +130 -0
  81. zettcode_tui-0.4.0/src/zettcode/tui/widgets/text.py +111 -0
  82. zettcode_tui-0.4.0/src/zettcode/tui/widgets/textarea.py +671 -0
  83. zettcode_tui-0.4.0/src/zettcode/tui/widgets/toast.py +137 -0
@@ -0,0 +1,37 @@
1
+ **/.venv/
2
+ **/__pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ build/
6
+ dist/
7
+ .smoke/
8
+ .coverage
9
+ **/.pytest_cache/
10
+ **/.ruff_cache/
11
+ .zett-tool-output/
12
+ .env
13
+ .DS_Store
14
+
15
+ # viztracer writes its trace here by default.
16
+ /result.json
17
+
18
+ # The docs site: dependencies, build output, and VitePress' cache.
19
+ node_modules/
20
+ docs/.vitepress/dist/
21
+ docs/.vitepress/cache/
22
+ docs/.vitepress/.temp/
23
+
24
+ # The TypeScript client is built from its sources; only dist/ is published.
25
+ clients/typescript/dist/
26
+
27
+ # The gateway's page is built from clients/gateway into the package when a distribution is made (`make gateway-ui`);
28
+ # the build output is not kept in the repository. pyproject's `artifacts` puts it in the wheel all the same.
29
+ packages/zettcode-gateway/src/zettcode/gateway/static/
30
+
31
+ # The palette gallery is generated by `make palettes`: every widget painted in
32
+ # every palette, written by docs/scripts/palette_gallery.py. Regenerate it
33
+ # rather than committing a 2 MB copy that goes stale the moment a colour moves.
34
+ docs/internal/palettes.html
35
+
36
+ # The release order is generated from the pyproject.toml files (`make order`); it is not kept in git.
37
+ packages/order.txt
@@ -0,0 +1,20 @@
1
+ Metadata-Version: 2.5
2
+ Name: zettcode-tui
3
+ Version: 0.4.0
4
+ Summary: The widget framework ZettCode's terminal is drawn with, and nothing else
5
+ Project-URL: Repository, https://github.com/Chang-LeHung/zettcode
6
+ Author: Chang-LeHung
7
+ License-Expression: MIT
8
+ Keywords: terminal,tui,widgets,zettcode
9
+ Classifier: Development Status :: 5 - Production/Stable
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Programming Language :: Python :: 3.14
15
+ Classifier: Programming Language :: Python :: 3.15
16
+ Classifier: Typing :: Typed
17
+ Requires-Python: >=3.10
18
+ Requires-Dist: pyperclip<2,>=1.9
19
+ Requires-Dist: tomli<3,>=2; python_version < '3.11'
20
+ Requires-Dist: wcwidth<1,>=0.2
@@ -0,0 +1,45 @@
1
+ [project]
2
+ name = "zettcode-tui"
3
+ description = "The widget framework ZettCode's terminal is drawn with, and nothing else"
4
+ requires-python = ">=3.10"
5
+ dynamic = ["version"]
6
+ license = "MIT"
7
+ authors = [{name = "Chang-LeHung"}]
8
+ keywords = ["zettcode", "tui", "terminal", "widgets"]
9
+ classifiers = [
10
+ "Development Status :: 5 - Production/Stable",
11
+ "Programming Language :: Python :: 3.10",
12
+ "Programming Language :: Python :: 3.11",
13
+ "Programming Language :: Python :: 3.12",
14
+ "Programming Language :: Python :: 3.13",
15
+ "Programming Language :: Python :: 3.14",
16
+ "Programming Language :: Python :: 3.15",
17
+ "Typing :: Typed",
18
+ ]
19
+ dependencies = [
20
+ "wcwidth>=0.2,<1",
21
+ "pyperclip>=1.9,<2",
22
+ # `tui/_compat.py` reads a theme file; the stdlib only learned TOML in 3.11.
23
+ "tomli>=2,<3; python_version < '3.11'",
24
+ ]
25
+
26
+ [project.urls]
27
+ Repository = "https://github.com/Chang-LeHung/zettcode"
28
+
29
+ [build-system]
30
+ requires = ["hatchling"]
31
+ build-backend = "hatchling.build"
32
+
33
+ [tool.hatch.version]
34
+ # One version for the whole repository: the engine ships the file.
35
+ path = "../zettcode-engine/src/zettcode/version.py"
36
+
37
+ [tool.hatch.build.targets.wheel]
38
+ packages = ["src/zettcode"]
39
+
40
+ [tool.hatch.build.targets.sdist]
41
+ include = ["/src", "/pyproject.toml"]
42
+
43
+ [tool.pytest.ini_options]
44
+ testpaths = ["tests"]
45
+ asyncio_mode = "auto"
@@ -0,0 +1,422 @@
1
+ """ZettCode's internally implemented terminal UI framework."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib
6
+ from typing import TYPE_CHECKING
7
+
8
+ #: Where each public name lives; read by :func:`__getattr__` on first use.
9
+ _EXPORTS = {
10
+ "ARROW_DOWN": ".glyphs",
11
+ "ARROW_UP": ".glyphs",
12
+ "Anchor": ".layout",
13
+ "AnyEvent": ".core",
14
+ "AsyncInput": ".input",
15
+ "BAR_EMPTY": ".glyphs",
16
+ "BAR_FULL": ".glyphs",
17
+ "Binding": ".core",
18
+ "Border": ".layout",
19
+ "AnsiBuffer": ".render",
20
+ "Canvas": ".render",
21
+ "Collapsible": ".widgets",
22
+ "ColorDepth": ".render",
23
+ "Column": ".widgets",
24
+ "Command": ".core",
25
+ "CommandRegistry": ".core",
26
+ "Completer": ".widgets",
27
+ "CompletionItem": ".widgets",
28
+ "CompletionPopup": ".widgets",
29
+ "ConsolePanel": ".widgets",
30
+ "Constraints": ".core",
31
+ "DARK": ".core",
32
+ "DONE": ".glyphs",
33
+ "Dialog": ".widgets",
34
+ "DialogAction": ".widgets",
35
+ "DiffLine": ".widgets",
36
+ "DiffSegment": ".widgets",
37
+ "DiffView": ".widgets",
38
+ "DifferentialRenderer": ".render",
39
+ "ELLIPSIS": ".glyphs",
40
+ "EXAMPLE": ".theme_file",
41
+ "EXPANDED": ".glyphs",
42
+ "EdgeInsets": ".core",
43
+ "Event": ".core",
44
+ "EventKind": ".core",
45
+ "EventType": ".input",
46
+ "FAILED": ".glyphs",
47
+ "FocusEvent": ".core",
48
+ "FocusManager": ".core",
49
+ "HBox": ".layout",
50
+ "HEADER": ".glyphs",
51
+ "Host": ".core",
52
+ "InputDecoder": ".input",
53
+ "InputEvent": ".input",
54
+ "KeyEvent": ".core",
55
+ "Keymap": ".core",
56
+ "LIGHT": ".core",
57
+ "LineSource": ".layout",
58
+ "ListItem": ".widgets",
59
+ "ListPage": ".widgets",
60
+ "ListView": ".widgets",
61
+ "MARKER": ".glyphs",
62
+ "Markdown": ".widgets",
63
+ "MarkdownSource": ".widgets",
64
+ "MarkdownView": ".widgets",
65
+ "MouseAction": ".core",
66
+ "MouseEvent": ".core",
67
+ "Overlay": ".layout",
68
+ "OverlaySlot": ".layout",
69
+ "PENDING": ".glyphs",
70
+ "PROMPT": ".glyphs",
71
+ "Padding": ".layout",
72
+ "PasteEvent": ".core",
73
+ "Point": ".core",
74
+ "ProgressBar": ".widgets",
75
+ "RULE": ".glyphs",
76
+ "RUNNING": ".glyphs",
77
+ "Rect": ".core",
78
+ "ResizeEvent": ".core",
79
+ "RichText": ".widgets",
80
+ "Rule": ".widgets",
81
+ "SEPARATOR": ".glyphs",
82
+ "OPTION": ".capabilities",
83
+ "SKIPPED": ".glyphs",
84
+ "SPINNER": ".glyphs",
85
+ "STATUS": ".glyphs",
86
+ "STEERING": ".glyphs",
87
+ "Scheduler": ".core",
88
+ "Screen": ".core",
89
+ "ScreenStack": ".core",
90
+ "ScrollView": ".layout",
91
+ "Size": ".core",
92
+ "Slot": ".layout",
93
+ "Span": ".render",
94
+ "Spinner": ".widgets",
95
+ "StaticLines": ".layout",
96
+ "StatusBar": ".widgets",
97
+ "Style": ".render",
98
+ "Table": ".widgets",
99
+ "TaskPanel": ".widgets",
100
+ "Terminal": ".terminal",
101
+ "TerminalCapabilities": ".capabilities",
102
+ "TerminalRunner": ".runner",
103
+ "Text": ".widgets",
104
+ "TextArea": ".widgets",
105
+ "TextEvent": ".core",
106
+ "TextLine": ".render",
107
+ "Theme": ".core",
108
+ "ThemeFileError": ".theme_file",
109
+ "Toast": ".widgets",
110
+ "ToolTheme": ".core",
111
+ "Track": ".layout",
112
+ "TuiApp": ".core",
113
+ "VBox": ".layout",
114
+ "Widget": ".core",
115
+ "build_unified": ".widgets",
116
+ "centered": ".layout",
117
+ "detect_capabilities": ".capabilities",
118
+ "detect_reduced_motion": ".capabilities",
119
+ "bundled_themes": ".theme_file",
120
+ "file_theme": ".theme_file",
121
+ "ThemeCatalog": ".theme_file",
122
+ "ThemeSource": ".theme_file",
123
+ "RESERVED_THEMES": ".theme_file",
124
+ "THEME_NAME": ".theme_file",
125
+ "event_key": ".core",
126
+ "key_id": ".core",
127
+ "load_theme": ".theme_file",
128
+ "normalize_key": ".core",
129
+ "parse_background": ".terminal",
130
+ "parse_unified": ".widgets",
131
+ "resolve_tracks": ".layout",
132
+ "run_app": ".runner",
133
+ "scheme_named": ".core",
134
+ "theme_from_toml": ".theme_file",
135
+ "theme_named": ".core",
136
+ "theme_names": ".core",
137
+ "title_sequence": ".terminal",
138
+ "walk": ".core",
139
+ "word_diff": ".widgets",
140
+ }
141
+
142
+
143
+ if TYPE_CHECKING: # pragma: no cover - for type checkers, not the runtime
144
+ from .capabilities import OPTION, TerminalCapabilities, detect_capabilities, detect_reduced_motion
145
+ from .core import (
146
+ DARK,
147
+ LIGHT,
148
+ AnyEvent,
149
+ Binding,
150
+ Command,
151
+ CommandRegistry,
152
+ Constraints,
153
+ EdgeInsets,
154
+ Event,
155
+ EventKind,
156
+ FocusEvent,
157
+ FocusManager,
158
+ Host,
159
+ KeyEvent,
160
+ Keymap,
161
+ MouseAction,
162
+ MouseEvent,
163
+ PasteEvent,
164
+ Point,
165
+ Rect,
166
+ ResizeEvent,
167
+ Scheduler,
168
+ Screen,
169
+ ScreenStack,
170
+ Size,
171
+ TextEvent,
172
+ Theme,
173
+ ToolTheme,
174
+ TuiApp,
175
+ Widget,
176
+ event_key,
177
+ key_id,
178
+ normalize_key,
179
+ scheme_named,
180
+ theme_named,
181
+ theme_names,
182
+ walk,
183
+ )
184
+ from .glyphs import (
185
+ ARROW_DOWN,
186
+ ARROW_UP,
187
+ BAR_EMPTY,
188
+ BAR_FULL,
189
+ DONE,
190
+ ELLIPSIS,
191
+ EXPANDED,
192
+ FAILED,
193
+ HEADER,
194
+ MARKER,
195
+ PENDING,
196
+ PROMPT,
197
+ RULE,
198
+ RUNNING,
199
+ SEPARATOR,
200
+ SKIPPED,
201
+ SPINNER,
202
+ STATUS,
203
+ STEERING,
204
+ )
205
+ from .input import AsyncInput, EventType, InputDecoder, InputEvent
206
+ from .layout import (
207
+ Anchor,
208
+ Border,
209
+ HBox,
210
+ LineSource,
211
+ Overlay,
212
+ OverlaySlot,
213
+ Padding,
214
+ ScrollView,
215
+ Slot,
216
+ StaticLines,
217
+ Track,
218
+ VBox,
219
+ centered,
220
+ resolve_tracks,
221
+ )
222
+ from .render import AnsiBuffer, Canvas, ColorDepth, DifferentialRenderer, Span, Style, TextLine
223
+ from .runner import TerminalRunner, run_app
224
+ from .terminal import Terminal, parse_background, title_sequence
225
+ from .theme_file import (
226
+ EXAMPLE,
227
+ RESERVED_THEMES,
228
+ THEME_NAME,
229
+ ThemeCatalog,
230
+ ThemeFileError,
231
+ ThemeSource,
232
+ bundled_themes,
233
+ file_theme,
234
+ load_theme,
235
+ theme_from_toml,
236
+ )
237
+ from .widgets import (
238
+ Collapsible,
239
+ Column,
240
+ Completer,
241
+ CompletionItem,
242
+ CompletionPopup,
243
+ ConsolePanel,
244
+ Dialog,
245
+ DialogAction,
246
+ DiffLine,
247
+ DiffSegment,
248
+ DiffView,
249
+ ListItem,
250
+ ListPage,
251
+ ListView,
252
+ Markdown,
253
+ MarkdownSource,
254
+ MarkdownView,
255
+ ProgressBar,
256
+ RichText,
257
+ Rule,
258
+ Spinner,
259
+ StatusBar,
260
+ Table,
261
+ TaskPanel,
262
+ Text,
263
+ TextArea,
264
+ Toast,
265
+ build_unified,
266
+ parse_unified,
267
+ word_diff,
268
+ )
269
+
270
+
271
+ def __getattr__(name: str) -> object:
272
+ """Import the module a public name lives in, the first time it is asked for.
273
+
274
+ This package is a facade over several subpackages, and a caller that wants
275
+ one name should not pay for the rest of them — the TUI alone holds the
276
+ widget library, the Markdown parser, the diff viewer, and the syntax
277
+ scanners. ``__all__`` is still the public surface.
278
+ """
279
+ try:
280
+ module = _EXPORTS[name]
281
+ except KeyError:
282
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}") from None
283
+ return getattr(importlib.import_module(module, __name__), name)
284
+
285
+
286
+ def __dir__() -> list[str]:
287
+ """Return the names this package publishes."""
288
+ return sorted(__all__)
289
+
290
+
291
+ __all__ = [
292
+ "AnyEvent",
293
+ "ARROW_DOWN",
294
+ "ARROW_UP",
295
+ "BAR_EMPTY",
296
+ "BAR_FULL",
297
+ "DONE",
298
+ "ELLIPSIS",
299
+ "EXPANDED",
300
+ "FAILED",
301
+ "HEADER",
302
+ "MARKER",
303
+ "OPTION",
304
+ "PENDING",
305
+ "PROMPT",
306
+ "RULE",
307
+ "RUNNING",
308
+ "SEPARATOR",
309
+ "SKIPPED",
310
+ "SPINNER",
311
+ "STATUS",
312
+ "STEERING",
313
+ "Anchor",
314
+ "AsyncInput",
315
+ "TuiApp",
316
+ "Binding",
317
+ "Border",
318
+ "AnsiBuffer",
319
+ "Canvas",
320
+ "Collapsible",
321
+ "ColorDepth",
322
+ "Column",
323
+ "Command",
324
+ "CommandRegistry",
325
+ "Completer",
326
+ "CompletionItem",
327
+ "CompletionPopup",
328
+ "ConsolePanel",
329
+ "Constraints",
330
+ "DARK",
331
+ "DifferentialRenderer",
332
+ "Dialog",
333
+ "DialogAction",
334
+ "DiffLine",
335
+ "DiffSegment",
336
+ "DiffView",
337
+ "EdgeInsets",
338
+ "Event",
339
+ "EventKind",
340
+ "EventType",
341
+ "FocusEvent",
342
+ "FocusManager",
343
+ "HBox",
344
+ "Host",
345
+ "InputDecoder",
346
+ "InputEvent",
347
+ "KeyEvent",
348
+ "Keymap",
349
+ "LIGHT",
350
+ "LineSource",
351
+ "ListItem",
352
+ "ListPage",
353
+ "ListView",
354
+ "Markdown",
355
+ "MarkdownSource",
356
+ "MarkdownView",
357
+ "MouseAction",
358
+ "MouseEvent",
359
+ "Overlay",
360
+ "OverlaySlot",
361
+ "Padding",
362
+ "PasteEvent",
363
+ "Point",
364
+ "ProgressBar",
365
+ "RichText",
366
+ "Rect",
367
+ "ResizeEvent",
368
+ "Rule",
369
+ "Scheduler",
370
+ "Screen",
371
+ "ScreenStack",
372
+ "ScrollView",
373
+ "Size",
374
+ "Slot",
375
+ "Span",
376
+ "Spinner",
377
+ "StaticLines",
378
+ "StatusBar",
379
+ "Style",
380
+ "Table",
381
+ "TaskPanel",
382
+ "Terminal",
383
+ "TerminalRunner",
384
+ "TerminalCapabilities",
385
+ "Text",
386
+ "TextArea",
387
+ "TextEvent",
388
+ "TextLine",
389
+ "Theme",
390
+ "ThemeFileError",
391
+ "Toast",
392
+ "Track",
393
+ "VBox",
394
+ "Widget",
395
+ "build_unified",
396
+ "EXAMPLE",
397
+ "detect_capabilities",
398
+ "bundled_themes",
399
+ "file_theme",
400
+ "RESERVED_THEMES",
401
+ "THEME_NAME",
402
+ "ThemeCatalog",
403
+ "ThemeSource",
404
+ "detect_reduced_motion",
405
+ "centered",
406
+ "event_key",
407
+ "key_id",
408
+ "load_theme",
409
+ "normalize_key",
410
+ "parse_unified",
411
+ "resolve_tracks",
412
+ "run_app",
413
+ "theme_named",
414
+ "title_sequence",
415
+ "scheme_named",
416
+ "parse_background",
417
+ "ToolTheme",
418
+ "theme_names",
419
+ "theme_from_toml",
420
+ "walk",
421
+ "word_diff",
422
+ ]
@@ -0,0 +1,32 @@
1
+ """The two version shims the framework needs, so `zettcode-tui` depends on nothing of ours.
2
+
3
+ The release notes of the framework say what it is: a widget toolkit that knows nothing of the agent. It needs the
4
+ string-valued enum (the standard library's is 3.11's, and its spelling is what the rows and the theme files rely on) and
5
+ a TOML reader for a theme file. Both are ten lines here rather than an import of another distribution, which would put
6
+ `zett-agent` behind a widget.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import sys
12
+ from enum import Enum
13
+
14
+ if sys.version_info >= (3, 11):
15
+ import tomllib
16
+ else: # pragma: no cover - the branch only the 3.10 runtime takes
17
+ import tomli as tomllib
18
+
19
+ __all__ = ["StrEnum", "tomllib"]
20
+
21
+
22
+ class StrEnum(str, Enum):
23
+ """A string-valued enum, for the versions before ``enum.StrEnum``.
24
+
25
+ The standard library's version formats as its value, so ``f"{member}"`` is ``"ready"`` rather than
26
+ ``"Activity.READY"``; the rows and the theme files rely on that, so every interpreter uses this class instead of the
27
+ standard one and the wording cannot drift with the version.
28
+ """
29
+
30
+ def __str__(self) -> str:
31
+ """Return the member's value, as ``enum.StrEnum`` does."""
32
+ return str(self.value)
@@ -0,0 +1,107 @@
1
+ """Terminal capability detection used to pick conservative rendering options."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import sys
7
+ from collections.abc import Mapping
8
+ from dataclasses import dataclass
9
+
10
+ from .render.color import ColorDepth
11
+
12
+ #: What the modifier that sends ``alt``-prefixed keys is called on this machine: the Option key on a Mac, Alt
13
+ #: elsewhere. The same physical key sends the same bytes; only its name differs, and a hint that says "alt" to
14
+ #: someone looking at a key marked "option" is a hint they have to translate.
15
+ OPTION = "option" if sys.platform == "darwin" else "alt"
16
+
17
+ _TRUECOLOR_COLORTERM = {"truecolor", "24bit"}
18
+ _MOTION_VARIABLES = ("ZETTCODE_REDUCED_MOTION", "NO_MOTION", "REDUCED_MOTION")
19
+
20
+
21
+ @dataclass(frozen=True, slots=True)
22
+ class TerminalCapabilities:
23
+ """What the attached terminal can be trusted to render.
24
+
25
+ Attributes:
26
+ color_depth: Safest depth the environment advertises.
27
+ unicode: The locale looks like it can encode UTF-8. Advisory for now: no
28
+ widget switches to an ASCII glyph set when it is False.
29
+ mouse: The terminal accepts mouse reporting. Advisory, like ``unicode``.
30
+ bracketed_paste: The terminal wraps pastes in ``ESC[200~`` guards.
31
+ Advisory as well; the decoder accepts them either way.
32
+ width: Initial width guess in cells, replaced on the first resize.
33
+ height: Initial height guess in cells.
34
+ """
35
+
36
+ color_depth: ColorDepth = ColorDepth.ANSI16
37
+ unicode: bool = True
38
+ mouse: bool = True
39
+ bracketed_paste: bool = True
40
+ width: int = 80
41
+ height: int = 24
42
+
43
+ @property
44
+ def truecolor(self) -> bool:
45
+ """Return whether the terminal can be trusted with 24-bit colour."""
46
+ return self.color_depth is ColorDepth.TRUECOLOR
47
+
48
+
49
+ def detect_capabilities(environ: Mapping[str, str] | None = None) -> TerminalCapabilities:
50
+ """Derive capabilities from environment variables.
51
+
52
+ Everything here is a heuristic, so each default errs toward the option that
53
+ still looks correct on a weaker terminal.
54
+
55
+ Args:
56
+ environ: Environment to read; defaults to ``os.environ`` and is
57
+ injectable so tests can describe a terminal without owning one.
58
+ """
59
+ env = os.environ if environ is None else environ
60
+ return TerminalCapabilities(
61
+ color_depth=detect_color_depth(env),
62
+ unicode=detect_unicode(env),
63
+ )
64
+
65
+
66
+ def detect_color_depth(environ: Mapping[str, str] | None = None) -> ColorDepth:
67
+ """Return the safest color depth the environment advertises.
68
+
69
+ Args:
70
+ environ: Environment to read; defaults to ``os.environ``.
71
+ """
72
+ env = os.environ if environ is None else environ
73
+ term = env.get("TERM", "")
74
+ if env.get("NO_COLOR") or term == "dumb":
75
+ return ColorDepth.MONO
76
+ colorterm = env.get("COLORTERM", "").lower()
77
+ if colorterm in _TRUECOLOR_COLORTERM or "truecolor" in term or "direct" in term:
78
+ return ColorDepth.TRUECOLOR
79
+ if "256" in term or colorterm:
80
+ return ColorDepth.ANSI256
81
+ return ColorDepth.ANSI16
82
+
83
+
84
+ def detect_unicode(environ: Mapping[str, str] | None = None) -> bool:
85
+ """Return whether the environment looks like it can encode UTF-8.
86
+
87
+ Args:
88
+ environ: Environment to read; defaults to ``os.environ``. An unset
89
+ locale is treated as UTF-8 capable, because that is the common case
90
+ on modern systems.
91
+ """
92
+ env = os.environ if environ is None else environ
93
+ locale = env.get("LC_ALL") or env.get("LC_CTYPE") or env.get("LANG", "")
94
+ if locale:
95
+ return "utf" in locale.lower()
96
+ return True
97
+
98
+
99
+ def detect_reduced_motion(environ: Mapping[str, str] | None = None) -> bool:
100
+ """Return whether decorative animation should be suppressed.
101
+
102
+ Args:
103
+ environ: Environment to read; defaults to ``os.environ``. Any of
104
+ ``_MOTION_VARIABLES`` being set to a non-empty value wins.
105
+ """
106
+ env = os.environ if environ is None else environ
107
+ return any(env.get(name) for name in _MOTION_VARIABLES)