console-window 2.0.0.8__tar.gz → 2.0.0.9__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.
- {console_window-2.0.0.8 → console_window-2.0.0.9}/CHANGELOG.md +4 -0
- {console_window-2.0.0.8 → console_window-2.0.0.9}/PKG-INFO +4 -2
- {console_window-2.0.0.8 → console_window-2.0.0.9}/README.md +3 -1
- {console_window-2.0.0.8 → console_window-2.0.0.9}/console_window/__init__.py +1 -1
- {console_window-2.0.0.8 → console_window-2.0.0.9}/console_window/window.py +42 -18
- {console_window-2.0.0.8 → console_window-2.0.0.9}/tests/test_header.py +54 -0
- {console_window-2.0.0.8 → console_window-2.0.0.9}/.github/workflows/ci.yml +0 -0
- {console_window-2.0.0.8 → console_window-2.0.0.9}/.gitignore +0 -0
- {console_window-2.0.0.8 → console_window-2.0.0.9}/AUDIT.md +0 -0
- {console_window-2.0.0.8 → console_window-2.0.0.9}/LICENSE +0 -0
- {console_window-2.0.0.8 → console_window-2.0.0.9}/SampleApp.py +0 -0
- {console_window-2.0.0.8 → console_window-2.0.0.9}/console_window/backend.py +0 -0
- {console_window-2.0.0.8 → console_window-2.0.0.9}/console_window/context.py +0 -0
- {console_window-2.0.0.8 → console_window-2.0.0.9}/console_window/geometry.py +0 -0
- {console_window-2.0.0.8 → console_window-2.0.0.9}/console_window/header.py +0 -0
- {console_window-2.0.0.8 → console_window-2.0.0.9}/console_window/keys.py +0 -0
- {console_window-2.0.0.8 → console_window-2.0.0.9}/console_window/opts.py +0 -0
- {console_window-2.0.0.8 → console_window-2.0.0.9}/console_window/screens.py +0 -0
- {console_window-2.0.0.8 → console_window-2.0.0.9}/console_window/signals.py +0 -0
- {console_window-2.0.0.8 → console_window-2.0.0.9}/console_window/spinner.py +0 -0
- {console_window-2.0.0.8 → console_window-2.0.0.9}/console_window/textwidth.py +0 -0
- {console_window-2.0.0.8 → console_window-2.0.0.9}/console_window/theme.py +0 -0
- {console_window-2.0.0.8 → console_window-2.0.0.9}/console_window/widgets.py +0 -0
- {console_window-2.0.0.8 → console_window-2.0.0.9}/pyproject.toml +0 -0
- {console_window-2.0.0.8 → console_window-2.0.0.9}/runner +0 -0
- {console_window-2.0.0.8 → console_window-2.0.0.9}/tests/test_backend.py +0 -0
- {console_window-2.0.0.8 → console_window-2.0.0.9}/tests/test_context.py +0 -0
- {console_window-2.0.0.8 → console_window-2.0.0.9}/tests/test_demo_mode.py +0 -0
- {console_window-2.0.0.8 → console_window-2.0.0.9}/tests/test_dialogs.py +0 -0
- {console_window-2.0.0.8 → console_window-2.0.0.9}/tests/test_flash.py +0 -0
- {console_window-2.0.0.8 → console_window-2.0.0.9}/tests/test_geometry.py +0 -0
- {console_window-2.0.0.8 → console_window-2.0.0.9}/tests/test_interactive.py +0 -0
- {console_window-2.0.0.8 → console_window-2.0.0.9}/tests/test_low_colour.py +0 -0
- {console_window-2.0.0.8 → console_window-2.0.0.9}/tests/test_overflow.py +0 -0
- {console_window-2.0.0.8 → console_window-2.0.0.9}/tests/test_public_api.py +0 -0
- {console_window-2.0.0.8 → console_window-2.0.0.9}/tests/test_render_retry.py +0 -0
- {console_window-2.0.0.8 → console_window-2.0.0.9}/tests/test_sample_app.py +0 -0
- {console_window-2.0.0.8 → console_window-2.0.0.9}/tests/test_screens.py +0 -0
- {console_window-2.0.0.8 → console_window-2.0.0.9}/tests/test_signals.py +0 -0
- {console_window-2.0.0.8 → console_window-2.0.0.9}/tests/test_spinner.py +0 -0
- {console_window-2.0.0.8 → console_window-2.0.0.9}/tests/test_textwidth.py +0 -0
- {console_window-2.0.0.8 → console_window-2.0.0.9}/tests/test_theme.py +0 -0
- {console_window-2.0.0.8 → console_window-2.0.0.9}/tests/test_viewport_pad.py +0 -0
- {console_window-2.0.0.8 → console_window-2.0.0.9}/tests/test_widechars.py +0 -0
- {console_window-2.0.0.8 → console_window-2.0.0.9}/tests/test_widgets.py +0 -0
|
@@ -6,6 +6,10 @@ This project follows [Semantic Versioning](https://semver.org/).
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
8
|
### Added
|
|
9
|
+
- `line_attr` on `add_fancy_header()`/`add_header_line()`: an attribute OR'd
|
|
10
|
+
into every span of the header (including plain spans and the raw line emitted
|
|
11
|
+
for `mode='Off'`), so a caller can dim a whole fancy header while keeping the
|
|
12
|
+
per-span bold/underline/reverse styling.
|
|
9
13
|
- `suspend_curses()`/`resume_curses()` on `ConsoleWindow`: leave curses mode
|
|
10
14
|
with `endwin()` while keeping the window (pads, content model, pick state)
|
|
11
15
|
alive, then reattach a screen and repaint. This is the supported way for an
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: console-window
|
|
3
|
-
Version: 2.0.0.
|
|
3
|
+
Version: 2.0.0.9
|
|
4
4
|
Summary: A robust curses wrapper for creating Text User Interfaces (TUIs).
|
|
5
5
|
Author-email: Joe Defen <joedef@duck.com>
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -122,7 +122,9 @@ demonstrates:
|
|
|
122
122
|
sizes.
|
|
123
123
|
* **`flash()`** — transient, non-destructive status messages.
|
|
124
124
|
* **Token-based headers** — `HeaderLine` (`text`, `bracket_key`, `key`, `extend`)
|
|
125
|
-
rendered with `add_header_line()`, rather than string markup.
|
|
125
|
+
rendered with `add_header_line()`, rather than string markup. Both header
|
|
126
|
+
helpers accept `line_attr`, an attribute OR'd into every span, so a whole
|
|
127
|
+
header can be dimmed (`curses.A_DIM`) without losing its per-span styling.
|
|
126
128
|
* **`Ctrl-C` as an application key** — `ctrl_c_terminates=False` and
|
|
127
129
|
`keys={0x3, ord('q')}` on the quit action. This works only while no live
|
|
128
130
|
window in the process has requested terminating Ctrl-C: once any window uses
|
|
@@ -103,7 +103,9 @@ demonstrates:
|
|
|
103
103
|
sizes.
|
|
104
104
|
* **`flash()`** — transient, non-destructive status messages.
|
|
105
105
|
* **Token-based headers** — `HeaderLine` (`text`, `bracket_key`, `key`, `extend`)
|
|
106
|
-
rendered with `add_header_line()`, rather than string markup.
|
|
106
|
+
rendered with `add_header_line()`, rather than string markup. Both header
|
|
107
|
+
helpers accept `line_attr`, an attribute OR'd into every span, so a whole
|
|
108
|
+
header can be dimmed (`curses.A_DIM`) without losing its per-span styling.
|
|
107
109
|
* **`Ctrl-C` as an application key** — `ctrl_c_terminates=False` and
|
|
108
110
|
`keys={0x3, ord('q')}` on the quit action. This works only while no live
|
|
109
111
|
window in the process has requested terminating Ctrl-C: once any window uses
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Single source of truth for the package version: pyproject.toml declares
|
|
2
2
|
# ``dynamic = ["version"]``, so flit reads this value instead of duplicating it.
|
|
3
|
-
__version__ = "2.0.0.
|
|
3
|
+
__version__ = "2.0.0.9"
|
|
4
4
|
|
|
5
5
|
from .context import Context
|
|
6
6
|
from .header import HeaderLine, Span
|
|
@@ -985,7 +985,8 @@ class ConsoleWindow:
|
|
|
985
985
|
self._add(self.body, text, attr, resume, context, is_body=True)
|
|
986
986
|
|
|
987
987
|
def add_fancy_header(self, line: str | HeaderLine, mode: str = 'Underline',
|
|
988
|
-
search_pattern_attr: int | None = None
|
|
988
|
+
search_pattern_attr: int | None = None,
|
|
989
|
+
line_attr: int | None = None) -> None:
|
|
989
990
|
"""Add a header line with legacy ``[x]``/``KEY:``/``/pattern`` markup.
|
|
990
991
|
|
|
991
992
|
String input is parsed unless ``mode='Off'``. A :class:`HeaderLine`
|
|
@@ -993,44 +994,67 @@ class ConsoleWindow:
|
|
|
993
994
|
treated as markup too: in every mode except ``'Off'`` they are removed,
|
|
994
995
|
while the enclosed key keeps its ``KEY`` styling. Use
|
|
995
996
|
:meth:`add_header_line` when literal brackets are wanted.
|
|
997
|
+
|
|
998
|
+
*line_attr* is OR'd into the attribute of every span (and into the raw
|
|
999
|
+
line emitted for ``mode='Off'``), so a caller can, for example, dim a
|
|
1000
|
+
whole header with ``line_attr=curses.A_DIM`` while keeping each span's
|
|
1001
|
+
own bold/underline/reverse styling.
|
|
996
1002
|
"""
|
|
997
1003
|
if isinstance(line, str):
|
|
998
1004
|
if mode == 'Off':
|
|
999
|
-
# Legacy: no markup processing, add the raw line unchanged
|
|
1000
|
-
|
|
1005
|
+
# Legacy: no markup processing, add the raw line unchanged
|
|
1006
|
+
# (except for the caller's whole-line attribute).
|
|
1007
|
+
self.add_header(line, attr=line_attr)
|
|
1001
1008
|
return
|
|
1002
1009
|
spans = parse_fancy_header(line)
|
|
1003
1010
|
else:
|
|
1004
1011
|
spans = list(line)
|
|
1005
1012
|
if mode != 'Off':
|
|
1006
1013
|
spans = strip_key_brackets(spans)
|
|
1007
|
-
self._emit_header_spans(spans, mode, search_pattern_attr)
|
|
1014
|
+
self._emit_header_spans(spans, mode, search_pattern_attr, line_attr)
|
|
1008
1015
|
|
|
1009
1016
|
def add_header_line(self, header_line: HeaderLine | Iterable[Span],
|
|
1010
1017
|
mode: str = 'Underline',
|
|
1011
|
-
search_pattern_attr: int | None = None
|
|
1012
|
-
|
|
1018
|
+
search_pattern_attr: int | None = None,
|
|
1019
|
+
line_attr: int | None = None) -> None:
|
|
1020
|
+
"""Render explicit header spans; see :meth:`add_fancy_header`.
|
|
1021
|
+
|
|
1022
|
+
*line_attr* is OR'd into every span's resolved attribute, including
|
|
1023
|
+
plain spans, so the whole line can carry an extra style such as
|
|
1024
|
+
``curses.A_DIM``.
|
|
1025
|
+
"""
|
|
1026
|
+
self._emit_header_spans(list(header_line), mode, search_pattern_attr,
|
|
1027
|
+
line_attr)
|
|
1013
1028
|
|
|
1014
1029
|
@staticmethod
|
|
1015
1030
|
def _resolve_header_attr(role: str, mode: str,
|
|
1016
|
-
search_pattern_attr: int | None
|
|
1031
|
+
search_pattern_attr: int | None,
|
|
1032
|
+
line_attr: int | None = None) -> int | None:
|
|
1017
1033
|
if mode == 'Off' or role == PLAIN:
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1034
|
+
attr = None
|
|
1035
|
+
elif role == LABEL:
|
|
1036
|
+
attr = curses.A_BOLD
|
|
1037
|
+
elif role == SEARCH:
|
|
1022
1038
|
if search_pattern_attr is not None:
|
|
1023
|
-
|
|
1024
|
-
|
|
1039
|
+
attr = search_pattern_attr
|
|
1040
|
+
else:
|
|
1041
|
+
attr = curses.A_BOLD | curses.A_REVERSE
|
|
1025
1042
|
# KEY (and any unknown role) follows the mode's key styling.
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1043
|
+
elif mode == 'Underline':
|
|
1044
|
+
attr = curses.A_UNDERLINE | curses.A_BOLD
|
|
1045
|
+
else:
|
|
1046
|
+
attr = curses.A_REVERSE
|
|
1047
|
+
if line_attr is not None:
|
|
1048
|
+
attr = (attr or 0) | line_attr
|
|
1049
|
+
return attr
|
|
1029
1050
|
|
|
1030
1051
|
def _emit_header_spans(self, spans: Iterable[Span], mode: str,
|
|
1031
|
-
search_pattern_attr: int | None = None
|
|
1052
|
+
search_pattern_attr: int | None = None,
|
|
1053
|
+
line_attr: int | None = None) -> None:
|
|
1032
1054
|
result_sections = [
|
|
1033
|
-
(span.text,
|
|
1055
|
+
(span.text,
|
|
1056
|
+
self._resolve_header_attr(span.role, mode, search_pattern_attr,
|
|
1057
|
+
line_attr))
|
|
1034
1058
|
for span in spans
|
|
1035
1059
|
]
|
|
1036
1060
|
|
|
@@ -207,6 +207,48 @@ def test_add_fancy_header_headerline_preserves_explicit_roles():
|
|
|
207
207
|
win.stop_curses()
|
|
208
208
|
|
|
209
209
|
|
|
210
|
+
def test_add_fancy_header_line_attr_dims_every_run():
|
|
211
|
+
# line_attr is OR'd into each span's own style, so a dimmed legend keeps
|
|
212
|
+
# its underlined keys and its explicitly styled search field.
|
|
213
|
+
win = _make_window()
|
|
214
|
+
try:
|
|
215
|
+
win.add_fancy_header('[v]iew /pat', mode='Underline',
|
|
216
|
+
search_pattern_attr=curses.A_BOLD,
|
|
217
|
+
line_attr=curses.A_DIM)
|
|
218
|
+
assert win.head.texts[0] == 'view /pat'
|
|
219
|
+
attrs = [attr for attr, _count in win.head.text_attrs[0]]
|
|
220
|
+
assert all(attr & curses.A_DIM for attr in attrs)
|
|
221
|
+
assert KEY_ATTR | curses.A_DIM in attrs
|
|
222
|
+
assert curses.A_BOLD | curses.A_DIM in attrs
|
|
223
|
+
finally:
|
|
224
|
+
win.stop_curses()
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def test_add_fancy_header_line_attr_applies_in_off_mode():
|
|
228
|
+
# mode='Off' emits the raw string, but the whole-line attribute still
|
|
229
|
+
# applies (otherwise a caller could not dim it at all).
|
|
230
|
+
win = _make_window()
|
|
231
|
+
try:
|
|
232
|
+
win.add_fancy_header('[v]iew', mode='Off', line_attr=curses.A_DIM)
|
|
233
|
+
assert win.head.texts[0] == '[v]iew'
|
|
234
|
+
assert all(attr & curses.A_DIM
|
|
235
|
+
for attr, _count in win.head.text_attrs[0])
|
|
236
|
+
finally:
|
|
237
|
+
win.stop_curses()
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def test_add_header_line_line_attr_dims_plain_spans():
|
|
241
|
+
win = _make_window()
|
|
242
|
+
try:
|
|
243
|
+
win.add_header_line(HeaderLine().text('plain').label('T'),
|
|
244
|
+
mode='Underline', line_attr=curses.A_DIM)
|
|
245
|
+
attrs = [attr for attr, _count in win.head.text_attrs[0]]
|
|
246
|
+
assert curses.A_DIM in attrs # the PLAIN run
|
|
247
|
+
assert curses.A_BOLD | curses.A_DIM in attrs # the LABEL run
|
|
248
|
+
finally:
|
|
249
|
+
win.stop_curses()
|
|
250
|
+
|
|
251
|
+
|
|
210
252
|
def test_string_reparse_drops_keys_the_legacy_grammar_cannot_spell():
|
|
211
253
|
# str(line) is pure text, so the legacy scanner's two KEY rules ('[x]' and
|
|
212
254
|
# 'UPPERCASE:') are all that survive a round trip. 'l:' and '?:' match
|
|
@@ -253,3 +295,15 @@ def test_token_and_string_paths_converge_for_legacy_spelled_keys():
|
|
|
253
295
|
|
|
254
296
|
def test_resolve_header_attr_off_is_plain(role):
|
|
255
297
|
assert ConsoleWindow._resolve_header_attr(role, 'Off', None) is None
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def test_resolve_header_attr_line_attr_combines_with_roles():
|
|
301
|
+
assert (ConsoleWindow._resolve_header_attr(
|
|
302
|
+
PLAIN, 'Underline', None, curses.A_DIM)
|
|
303
|
+
== curses.A_DIM)
|
|
304
|
+
assert (ConsoleWindow._resolve_header_attr(
|
|
305
|
+
KEY, 'Underline', None, curses.A_DIM)
|
|
306
|
+
== KEY_ATTR | curses.A_DIM)
|
|
307
|
+
assert (ConsoleWindow._resolve_header_attr(
|
|
308
|
+
SEARCH, 'Underline', curses.A_BOLD, curses.A_DIM)
|
|
309
|
+
== curses.A_BOLD | curses.A_DIM)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|