console-window 2.2.0__tar.gz → 2.2.2__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 (22) hide show
  1. {console_window-2.2.0 → console_window-2.2.2}/PKG-INFO +40 -18
  2. {console_window-2.2.0 → console_window-2.2.2}/README.md +39 -17
  3. {console_window-2.2.0 → console_window-2.2.2}/console_window/__init__.py +1 -1
  4. {console_window-2.2.0 → console_window-2.2.2}/console_window/appearance.py +18 -16
  5. {console_window-2.2.0 → console_window-2.2.2}/console_window/backend.py +9 -9
  6. {console_window-2.2.0 → console_window-2.2.2}/console_window/cli.py +7 -7
  7. {console_window-2.2.0 → console_window-2.2.2}/console_window/glyphs.py +9 -3
  8. {console_window-2.2.0 → console_window-2.2.2}/console_window/help.py +1 -1
  9. {console_window-2.2.0 → console_window-2.2.2}/console_window/opts.py +1 -1
  10. {console_window-2.2.0 → console_window-2.2.2}/console_window/screens.py +1 -1
  11. {console_window-2.2.0 → console_window-2.2.2}/console_window/spinner.py +2 -2
  12. {console_window-2.2.0 → console_window-2.2.2}/console_window/theme.py +66 -25
  13. {console_window-2.2.0 → console_window-2.2.2}/console_window/window.py +18 -18
  14. {console_window-2.2.0 → console_window-2.2.2}/pyproject.toml +1 -1
  15. {console_window-2.2.0 → console_window-2.2.2}/LICENSE +0 -0
  16. {console_window-2.2.0 → console_window-2.2.2}/console_window/context.py +0 -0
  17. {console_window-2.2.0 → console_window-2.2.2}/console_window/geometry.py +0 -0
  18. {console_window-2.2.0 → console_window-2.2.2}/console_window/header.py +0 -0
  19. {console_window-2.2.0 → console_window-2.2.2}/console_window/keys.py +0 -0
  20. {console_window-2.2.0 → console_window-2.2.2}/console_window/signals.py +0 -0
  21. {console_window-2.2.0 → console_window-2.2.2}/console_window/textwidth.py +0 -0
  22. {console_window-2.2.0 → console_window-2.2.2}/console_window/widgets.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: console-window
3
- Version: 2.2.0
3
+ Version: 2.2.2
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
@@ -76,6 +76,17 @@ no install or build step required:
76
76
  (`runner` executes `SampleApp` as `__main__`, so live edits are picked
77
77
  up immediately. The equivalent one-off is `python3 SampleApp.py`.)
78
78
 
79
+ The sample takes the same display switches as any app built on the library:
80
+
81
+ ```bash
82
+ ./runner --no-color # or NO_COLOR=1 ./runner
83
+ ./runner --theme nord
84
+ ```
85
+
86
+ `--no-color` starts in the monochrome mode that the `THEMES` picker also offers
87
+ as its `mono` choice, so `t` on the `THEMES` screen can turn color back on (or
88
+ off again) without restarting.
89
+
79
90
  ## Core Features (The "Secret Sauce")
80
91
 
81
92
  The wrapper handles several advanced terminal interaction patterns automatically:
@@ -83,7 +94,7 @@ The wrapper handles several advanced terminal interaction patterns automatically
83
94
  * **Viewport-Sized Pads:** Separate `head` and `body` pads give the static header band and the independently scrolling body, but the pads are blit buffers sized to the terminal rather than content stores. `add_body()`/`add_header()`/`draw()` write the model, and `render()` paints the visible page into pad row 0, so pad memory and per-frame `clear()` cost stay constant no matter how many rows an app adds. Row counts need no configuration at all: `opts.body_rows` / `opts.head_rows` are advisory hints only, and `opts.max_rows` (default `32767`, `None` for unlimited) is the single content ceiling.
84
95
  * **Loud Content Limits:** A row added past `opts.max_rows` is discarded, but no longer quietly: the first discarded row of each fill episode emits a `UserWarning` naming the namespace and the option to change, and every discarded row is counted in `ConsoleWindow.rows_dropped`. It stays non-fatal — run `python -W error::UserWarning` if you would rather it killed the app.
85
96
  * **Seamless Resizing:** Automatically detects terminal size changes (`KEY_RESIZE`) and recalculates layout dimensions, with robust error handling to prevent common `curses.error` crashes during resizing.
86
- * **Graceful Colour Degradation:** Themes are written against the 256-colour xterm palette, but installation asks the backend how many colours the terminal actually exposes. A 256-colour terminal gets the theme's literal specs; 16-, 88- and 8-colour terminals get the nearest available palette entry, with a contrast guard so a pair never collapses to `fg == bg`; below 8 colours (or with no colour support) the theme falls back to attributes. `-1` is only emitted when `use_default_colors()` succeeds, and `COLOR_PAIRS` is respected. Stock tmux (`TERM=screen`, 8 colours) therefore works instead of raising `ValueError: Color number is greater than COLORS-1`.
97
+ * **Graceful Color Degradation:** Themes are written against the 256-color xterm palette, but installation asks the backend how many colors the terminal actually exposes. A 256-color terminal gets the theme's literal specs; 16-, 88- and 8-color terminals get the nearest available palette entry, with a contrast guard so a pair never collapses to `fg == bg`; below 8 colors (or with no color support) the theme falls back to attributes. `-1` is only emitted when `use_default_colors()` succeeds, and `COLOR_PAIRS` is respected. Stock tmux (`TERM=screen`, 8 colors) therefore works instead of raising `ValueError: Color number is greater than COLORS-1`.
87
98
  * **Scroll & Pick Synchronization:** The `Window` class manages **scroll position** and a separate **pick position** (highlighted row) simultaneously.
88
99
  * When using pick mode, scrolling automatically tracks the highlighted element to keep it visible in `.pick_pos`.
89
100
  * To use that value, you must separately be able to associate `.pick_pos` to its value by, say, keeping an array of values for every added line to the `body`.
@@ -121,15 +132,20 @@ demonstrates:
121
132
  `handle_prompt_key()`, and `N` edits a note in the header band with the
122
133
  buffered text mode. Both use `PromptLine` underneath; completion callbacks
123
134
  run after passthrough is restored, so they can report with `status()`.
124
- * **Themes** — a `THEMES` screen of colour samples backed by
135
+ * **Themes** — a `THEMES` screen of color samples backed by
125
136
  `Theme.list_all()` / `get_current()` / `set()`. The active theme is now
126
137
  per-window state: `ConsoleWindowOpts(theme=...)` seeds `window.theme`,
127
- `window.set_theme()` switches it (and re-initialises the terminal's colour
138
+ `window.set_theme()` switches it (and re-initializes the terminal's color
128
139
  pairs, which curses keeps per terminal rather than per window), and
129
140
  `Theme.set()` remains the process-wide switch. Theme names are semantic:
130
- asking for `nord` on an 8-colour terminal installs the closest supported
141
+ asking for `nord` on an 8-color terminal installs the closest supported
131
142
  representation rather than raising, and startup, `set_theme()` and
132
- `Theme.set()` all share `Theme.install()`.
143
+ `Theme.set()` all share `Theme.install()`. The demo's picker also offers a
144
+ `mono` choice that is not a palette: selecting it calls
145
+ `appearance.set_colors_enabled(False)` and the swatches switch from color
146
+ pairs to the bold/reverse attribute hierarchy — the same thing `--no-color`
147
+ does at startup. While color is off the header names `mono`, not the palette
148
+ still installed underneath, so one choice has one name.
133
149
  * **Dialogs** — `a` calls `answer()` for text input, `l` calls `alert()`, and `n`
134
150
  drives `answer()` from an `OptionSpinner` option (`add_key(..., prompt=...)`).
135
151
  `answer()` returns `None` only when the user aborts; if the terminal cannot
@@ -300,7 +316,7 @@ loop. The window never learns which item the prompt belongs to.
300
316
  `BasicTextScreen` is the small `Screen` for preformatted, scrollable text. It
301
317
  resolves `app.win` at draw time, disables pick mode, adds each header before the
302
318
  body, and adds each line with `add_body()` and no `Context` (so normal
303
- navigation scrolls it). Content is normalised by `set_text()`, which is safe
319
+ navigation scrolls it). Content is normalized by `set_text()`, which is safe
304
320
  before or after the first draw:
305
321
 
306
322
  ```python
@@ -345,7 +361,7 @@ vertical key is allowed (and logged as "reduced navigation"), since the
345
361
  application is then responsible for its own scrolling.
346
362
 
347
363
  `ScreenStack.perform_actions()` is the normal router: it walks the spinner
348
- options, honours their scopes, and calls the matching `<attr>_ACTION` method.
364
+ options, honors their scopes, and calls the matching `<attr>_ACTION` method.
349
365
  For apps that dispatch an action themselves, `ScreenStack.act_in(action,
350
366
  screens=None)` is the low-level single-flag variant: it clears that flag from
351
367
  `stack.obj` and returns whether it was set and the current screen matched.
@@ -433,11 +449,11 @@ degrades by itself:
433
449
 
434
450
  `--ascii` forces the fallback (`--ascii=no` forces the full set) and
435
451
  `--glyphs {auto,ascii,unicode}` is its symmetric spelling; `--glyphs` wins if
436
- both are given. `--theme` seeds the first window's colour theme and
452
+ both are given. `--theme` seeds the first window's color theme and
437
453
  `--no-color` draws the semantic roles with bold/reverse attributes instead of
438
- colour. Apps without argparse can use
454
+ color. Apps without argparse can use
439
455
  `CONSOLE_WINDOW_GLYPHS=auto|ascii|unicode`, which outranks detection.
440
- * **The mode is process-wide**, like the colour-pair table, because there is one
456
+ * **The mode is process-wide**, like the color-pair table, because there is one
441
457
  terminal. `ConsoleWindowOpts(ascii=...)` seeds it and `win.ascii_mode` reads
442
458
  it; a window constructed later never reverts an explicit choice.
443
459
  * **Help screens can explain it for you.** Return
@@ -450,22 +466,28 @@ Themes work the same way: `Theme.create_picker_screen(base, cycle_key=None,
450
466
  store=None, note=None)` discovers the key you bound to `spin_theme` for its hint
451
467
  line, remembers the choice through any object with `load()`/`save()`, and
452
468
  `Theme.initial(cli_theme, store)` returns the theme a new process should start
453
- on. The library never opens a file.
469
+ on. The library never opens a file. Its swatches follow the shared color
470
+ switch, so a picker opened while color is off previews the monochrome
471
+ hierarchy rather than a previous theme's colors; override `swatch_attr()` on a
472
+ subclass to style swatches differently, and `theme_label()` to name an app
473
+ pseudo-theme such as `mono` in the header.
454
474
 
455
475
  The appearance layer is shared too. `console_window.appearance.semantic_attr(role)`
456
- returns the installed colour pair for a semantic role, or the matching attribute
457
- from `MONO_ROLES` when colour is off; it degrades to plain text when the terminal
458
- has no colour or curses has not been initialised, so a redraw is never aborted by
459
- a colour. `set_colors_enabled()` / `colors_enabled()` hold the process-wide
476
+ returns the installed color pair for a semantic role, or the matching attribute
477
+ from `MONO_ROLES` when color is off; it degrades to plain text when the terminal
478
+ has no color or curses has not been initialized, so a redraw is never aborted by
479
+ a color. `set_colors_enabled()` / `colors_enabled()` hold the process-wide
460
480
  switch, and `resolve_appearance(cli_theme=None, no_color=False, saved_theme='',
461
481
  saved_colors=True, environ=None)` returns the `(theme, colors)` to start with:
462
482
  `--no-color` first, then an explicit `--theme`, then `NO_COLOR`, then the
463
483
  remembered choice. Apps keep their preference file and their own pseudo-themes;
464
- the library gives them the policy, not the storage.
484
+ the library gives them the policy, not the storage. `SampleApp`'s `mono` choice
485
+ is the worked example of that split: it lives in the app, flips the switch, and
486
+ leaves the installed palette untouched.
465
487
 
466
488
  ## Versioning and Deprecation
467
489
 
468
- `console-window` follows semantic versioning: patch releases fix behaviour
490
+ `console-window` follows semantic versioning: patch releases fix behavior
469
491
  without changing the public API, minor releases add functionality, and major
470
492
  releases may make incompatible changes.
471
493
 
@@ -45,6 +45,17 @@ no install or build step required:
45
45
  (`runner` executes `SampleApp` as `__main__`, so live edits are picked
46
46
  up immediately. The equivalent one-off is `python3 SampleApp.py`.)
47
47
 
48
+ The sample takes the same display switches as any app built on the library:
49
+
50
+ ```bash
51
+ ./runner --no-color # or NO_COLOR=1 ./runner
52
+ ./runner --theme nord
53
+ ```
54
+
55
+ `--no-color` starts in the monochrome mode that the `THEMES` picker also offers
56
+ as its `mono` choice, so `t` on the `THEMES` screen can turn color back on (or
57
+ off again) without restarting.
58
+
48
59
  ## Core Features (The "Secret Sauce")
49
60
 
50
61
  The wrapper handles several advanced terminal interaction patterns automatically:
@@ -52,7 +63,7 @@ The wrapper handles several advanced terminal interaction patterns automatically
52
63
  * **Viewport-Sized Pads:** Separate `head` and `body` pads give the static header band and the independently scrolling body, but the pads are blit buffers sized to the terminal rather than content stores. `add_body()`/`add_header()`/`draw()` write the model, and `render()` paints the visible page into pad row 0, so pad memory and per-frame `clear()` cost stay constant no matter how many rows an app adds. Row counts need no configuration at all: `opts.body_rows` / `opts.head_rows` are advisory hints only, and `opts.max_rows` (default `32767`, `None` for unlimited) is the single content ceiling.
53
64
  * **Loud Content Limits:** A row added past `opts.max_rows` is discarded, but no longer quietly: the first discarded row of each fill episode emits a `UserWarning` naming the namespace and the option to change, and every discarded row is counted in `ConsoleWindow.rows_dropped`. It stays non-fatal — run `python -W error::UserWarning` if you would rather it killed the app.
54
65
  * **Seamless Resizing:** Automatically detects terminal size changes (`KEY_RESIZE`) and recalculates layout dimensions, with robust error handling to prevent common `curses.error` crashes during resizing.
55
- * **Graceful Colour Degradation:** Themes are written against the 256-colour xterm palette, but installation asks the backend how many colours the terminal actually exposes. A 256-colour terminal gets the theme's literal specs; 16-, 88- and 8-colour terminals get the nearest available palette entry, with a contrast guard so a pair never collapses to `fg == bg`; below 8 colours (or with no colour support) the theme falls back to attributes. `-1` is only emitted when `use_default_colors()` succeeds, and `COLOR_PAIRS` is respected. Stock tmux (`TERM=screen`, 8 colours) therefore works instead of raising `ValueError: Color number is greater than COLORS-1`.
66
+ * **Graceful Color Degradation:** Themes are written against the 256-color xterm palette, but installation asks the backend how many colors the terminal actually exposes. A 256-color terminal gets the theme's literal specs; 16-, 88- and 8-color terminals get the nearest available palette entry, with a contrast guard so a pair never collapses to `fg == bg`; below 8 colors (or with no color support) the theme falls back to attributes. `-1` is only emitted when `use_default_colors()` succeeds, and `COLOR_PAIRS` is respected. Stock tmux (`TERM=screen`, 8 colors) therefore works instead of raising `ValueError: Color number is greater than COLORS-1`.
56
67
  * **Scroll & Pick Synchronization:** The `Window` class manages **scroll position** and a separate **pick position** (highlighted row) simultaneously.
57
68
  * When using pick mode, scrolling automatically tracks the highlighted element to keep it visible in `.pick_pos`.
58
69
  * To use that value, you must separately be able to associate `.pick_pos` to its value by, say, keeping an array of values for every added line to the `body`.
@@ -90,15 +101,20 @@ demonstrates:
90
101
  `handle_prompt_key()`, and `N` edits a note in the header band with the
91
102
  buffered text mode. Both use `PromptLine` underneath; completion callbacks
92
103
  run after passthrough is restored, so they can report with `status()`.
93
- * **Themes** — a `THEMES` screen of colour samples backed by
104
+ * **Themes** — a `THEMES` screen of color samples backed by
94
105
  `Theme.list_all()` / `get_current()` / `set()`. The active theme is now
95
106
  per-window state: `ConsoleWindowOpts(theme=...)` seeds `window.theme`,
96
- `window.set_theme()` switches it (and re-initialises the terminal's colour
107
+ `window.set_theme()` switches it (and re-initializes the terminal's color
97
108
  pairs, which curses keeps per terminal rather than per window), and
98
109
  `Theme.set()` remains the process-wide switch. Theme names are semantic:
99
- asking for `nord` on an 8-colour terminal installs the closest supported
110
+ asking for `nord` on an 8-color terminal installs the closest supported
100
111
  representation rather than raising, and startup, `set_theme()` and
101
- `Theme.set()` all share `Theme.install()`.
112
+ `Theme.set()` all share `Theme.install()`. The demo's picker also offers a
113
+ `mono` choice that is not a palette: selecting it calls
114
+ `appearance.set_colors_enabled(False)` and the swatches switch from color
115
+ pairs to the bold/reverse attribute hierarchy — the same thing `--no-color`
116
+ does at startup. While color is off the header names `mono`, not the palette
117
+ still installed underneath, so one choice has one name.
102
118
  * **Dialogs** — `a` calls `answer()` for text input, `l` calls `alert()`, and `n`
103
119
  drives `answer()` from an `OptionSpinner` option (`add_key(..., prompt=...)`).
104
120
  `answer()` returns `None` only when the user aborts; if the terminal cannot
@@ -269,7 +285,7 @@ loop. The window never learns which item the prompt belongs to.
269
285
  `BasicTextScreen` is the small `Screen` for preformatted, scrollable text. It
270
286
  resolves `app.win` at draw time, disables pick mode, adds each header before the
271
287
  body, and adds each line with `add_body()` and no `Context` (so normal
272
- navigation scrolls it). Content is normalised by `set_text()`, which is safe
288
+ navigation scrolls it). Content is normalized by `set_text()`, which is safe
273
289
  before or after the first draw:
274
290
 
275
291
  ```python
@@ -314,7 +330,7 @@ vertical key is allowed (and logged as "reduced navigation"), since the
314
330
  application is then responsible for its own scrolling.
315
331
 
316
332
  `ScreenStack.perform_actions()` is the normal router: it walks the spinner
317
- options, honours their scopes, and calls the matching `<attr>_ACTION` method.
333
+ options, honors their scopes, and calls the matching `<attr>_ACTION` method.
318
334
  For apps that dispatch an action themselves, `ScreenStack.act_in(action,
319
335
  screens=None)` is the low-level single-flag variant: it clears that flag from
320
336
  `stack.obj` and returns whether it was set and the current screen matched.
@@ -402,11 +418,11 @@ degrades by itself:
402
418
 
403
419
  `--ascii` forces the fallback (`--ascii=no` forces the full set) and
404
420
  `--glyphs {auto,ascii,unicode}` is its symmetric spelling; `--glyphs` wins if
405
- both are given. `--theme` seeds the first window's colour theme and
421
+ both are given. `--theme` seeds the first window's color theme and
406
422
  `--no-color` draws the semantic roles with bold/reverse attributes instead of
407
- colour. Apps without argparse can use
423
+ color. Apps without argparse can use
408
424
  `CONSOLE_WINDOW_GLYPHS=auto|ascii|unicode`, which outranks detection.
409
- * **The mode is process-wide**, like the colour-pair table, because there is one
425
+ * **The mode is process-wide**, like the color-pair table, because there is one
410
426
  terminal. `ConsoleWindowOpts(ascii=...)` seeds it and `win.ascii_mode` reads
411
427
  it; a window constructed later never reverts an explicit choice.
412
428
  * **Help screens can explain it for you.** Return
@@ -419,22 +435,28 @@ Themes work the same way: `Theme.create_picker_screen(base, cycle_key=None,
419
435
  store=None, note=None)` discovers the key you bound to `spin_theme` for its hint
420
436
  line, remembers the choice through any object with `load()`/`save()`, and
421
437
  `Theme.initial(cli_theme, store)` returns the theme a new process should start
422
- on. The library never opens a file.
438
+ on. The library never opens a file. Its swatches follow the shared color
439
+ switch, so a picker opened while color is off previews the monochrome
440
+ hierarchy rather than a previous theme's colors; override `swatch_attr()` on a
441
+ subclass to style swatches differently, and `theme_label()` to name an app
442
+ pseudo-theme such as `mono` in the header.
423
443
 
424
444
  The appearance layer is shared too. `console_window.appearance.semantic_attr(role)`
425
- returns the installed colour pair for a semantic role, or the matching attribute
426
- from `MONO_ROLES` when colour is off; it degrades to plain text when the terminal
427
- has no colour or curses has not been initialised, so a redraw is never aborted by
428
- a colour. `set_colors_enabled()` / `colors_enabled()` hold the process-wide
445
+ returns the installed color pair for a semantic role, or the matching attribute
446
+ from `MONO_ROLES` when color is off; it degrades to plain text when the terminal
447
+ has no color or curses has not been initialized, so a redraw is never aborted by
448
+ a color. `set_colors_enabled()` / `colors_enabled()` hold the process-wide
429
449
  switch, and `resolve_appearance(cli_theme=None, no_color=False, saved_theme='',
430
450
  saved_colors=True, environ=None)` returns the `(theme, colors)` to start with:
431
451
  `--no-color` first, then an explicit `--theme`, then `NO_COLOR`, then the
432
452
  remembered choice. Apps keep their preference file and their own pseudo-themes;
433
- the library gives them the policy, not the storage.
453
+ the library gives them the policy, not the storage. `SampleApp`'s `mono` choice
454
+ is the worked example of that split: it lives in the app, flips the switch, and
455
+ leaves the installed palette untouched.
434
456
 
435
457
  ## Versioning and Deprecation
436
458
 
437
- `console-window` follows semantic versioning: patch releases fix behaviour
459
+ `console-window` follows semantic versioning: patch releases fix behavior
438
460
  without changing the public API, minor releases add functionality, and major
439
461
  releases may make incompatible changes.
440
462
 
@@ -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.2.0"
3
+ __version__ = "2.2.2"
4
4
 
5
5
  from .appearance import (
6
6
  MONO_ROLES,
@@ -1,26 +1,26 @@
1
- """Semantic colour roles, and one resolver for the colour switch.
1
+ """Semantic color roles, and one resolver for the color switch.
2
2
 
3
- Colour here is decoration, never information. Every role degrades to an
4
- attribute when colour is off, so the same hierarchy survives on a monochrome
3
+ Color here is decoration, never information. Every role degrades to an
4
+ attribute when color is off, so the same hierarchy survives on a monochrome
5
5
  terminal or under ``--no-color``. Applications kept writing this layer twice:
6
6
  the role-to-attribute mapping, the process-wide switch seeded by ``--no-color``
7
7
  or ``NO_COLOR``, and a lookup that must never abort a redraw. It now lives
8
8
  here.
9
9
 
10
- The role ids are :class:`Theme`'s semantic pairs rather than literal colour
10
+ The role ids are :class:`Theme`'s semantic pairs rather than literal color
11
11
  numbers. The library installs those pairs at startup and re-installs them
12
- whenever a theme is applied, so changing the active theme recolours the whole UI
12
+ whenever a theme is applied, so changing the active theme recolors the whole UI
13
13
  without an edit to any draw code.
14
14
 
15
15
  ``--no-color`` is a per-run switch, not a saved preference: it replaces the
16
16
  pairs with the attributes in :data:`MONO_ROLES` for this process. Picking a
17
- theme from the colour-theme screen is a request for colour, so the caller can
17
+ theme from the color-theme screen is a request for color, so the caller can
18
18
  lift the switch again -- :func:`resolve_appearance` does that when it sees an
19
19
  explicit theme.
20
20
 
21
21
  Nothing application-specific belongs in this module: preference files, app
22
22
  vocabulary and app pseudo-themes stay with the application. An app that has a
23
- ``mono`` pseudo-theme normalises that name to ``''`` plus ``saved_colors=False``
23
+ ``mono`` pseudo-theme normalizes that name to ``''`` plus ``saved_colors=False``
24
24
  before calling :func:`resolve_appearance`.
25
25
  """
26
26
 
@@ -40,7 +40,7 @@ __all__ = [
40
40
  'set_colors_enabled',
41
41
  ]
42
42
 
43
- # Role -> attribute used when colour is off. The hierarchy is kept with
43
+ # Role -> attribute used when color is off. The hierarchy is kept with
44
44
  # emphasis instead of hue: the destructive roles are bold+reverse, warnings,
45
45
  # successes and emphasis are bold, and the informational roles carry nothing
46
46
  # (their surrounding structure already distinguishes them).
@@ -62,13 +62,14 @@ _COLORS_ENABLED = True
62
62
 
63
63
 
64
64
  def set_colors_enabled(enabled: bool) -> None:
65
- """Turn semantic colour on or off for the whole process."""
66
- global _COLORS_ENABLED # one terminal, one process-wide switch
65
+ """Turn semantic color on or off for the whole process."""
66
+ # One terminal, so one process-wide switch.
67
+ global _COLORS_ENABLED # pylint: disable=global-statement
67
68
  _COLORS_ENABLED = bool(enabled)
68
69
 
69
70
 
70
71
  def colors_enabled() -> bool:
71
- """True while semantic roles resolve to colour pairs rather than attributes."""
72
+ """True while semantic roles resolve to color pairs rather than attributes."""
72
73
  return _COLORS_ENABLED
73
74
 
74
75
 
@@ -82,8 +83,8 @@ def resolve_appearance(
82
83
  """Starting ``(theme, colors)`` from the switches, environment and memory.
83
84
 
84
85
  Precedence, highest first: ``--no-color``, an explicit ``--theme`` (a
85
- statement of intent, so it is honoured even when ``NO_COLOR`` is set), the
86
- ``NO_COLOR`` convention (any non-empty value disables colour, per
86
+ statement of intent, so it is honored even when ``NO_COLOR`` is set), the
87
+ ``NO_COLOR`` convention (any non-empty value disables color, per
87
88
  no-color.org), then the remembered choice.
88
89
 
89
90
  ``cli_theme``/``saved_theme`` are returned resolved to a known library
@@ -115,9 +116,9 @@ def resolve_appearance(
115
116
 
116
117
 
117
118
  def semantic_attr(pair_id: int) -> int:
118
- """Attribute for a :class:`Theme` role, or plain when colour is off.
119
+ """Attribute for a :class:`Theme` role, or plain when color is off.
119
120
 
120
- With colour off the role becomes an attribute from :data:`MONO_ROLES`.
121
+ With color off the role becomes an attribute from :data:`MONO_ROLES`.
121
122
  Otherwise both ``curses.has_colors()`` and ``curses.color_pair()`` can raise
122
123
  before ``initscr()`` (``color_pair()`` refuses to run until then), and a
123
124
  failed lookup must never abort a redraw, so any failure degrades to plain
@@ -131,5 +132,6 @@ def semantic_attr(pair_id: int) -> int:
131
132
  if not curses.has_colors():
132
133
  return 0
133
134
  return curses.color_pair(pair_id)
134
- except Exception: # terminal not initialised yet; never abort a redraw
135
+ except Exception: # pylint: disable=broad-exception-caught
136
+ # The terminal is not initialized yet; never abort a redraw.
135
137
  return 0
@@ -92,10 +92,10 @@ class RenderBackend(Protocol):
92
92
  ...
93
93
 
94
94
  def colors(self) -> int:
95
- """Number of colours the terminal supports (0 when unknown/none)."""
95
+ """Number of colors the terminal supports (0 when unknown/none)."""
96
96
 
97
97
  def color_pairs(self) -> int:
98
- """Size of the terminal's colour-pair table."""
98
+ """Size of the terminal's color-pair table."""
99
99
 
100
100
  def start_color(self) -> None:
101
101
  ...
@@ -115,12 +115,12 @@ class RenderBackend(Protocol):
115
115
 
116
116
  class CursesBackend:
117
117
  # Curses exposes COLORS/COLOR_PAIRS only after start_color(), so the values
118
- # are captured there. Before that, colours()/color_pairs() fall back to the
118
+ # are captured there. Before that, colors()/color_pairs() fall back to the
119
119
  # module globals (valid once initscr() has run) and finally to a conservative
120
120
  # default so a caller never sees a bogus zero-capability terminal merely
121
121
  # because it constructed a second backend for an already-running curses
122
122
  # session.
123
- _FALLBACK_COLOR_PAIRS = 64 # the ncurses floor for a colour terminal
123
+ _FALLBACK_COLOR_PAIRS = 64 # the ncurses floor for a color terminal
124
124
 
125
125
  def __init__(self) -> None:
126
126
  self._colors = 0
@@ -156,7 +156,7 @@ class CursesBackend:
156
156
  def has_colors(self) -> bool:
157
157
  try:
158
158
  return bool(curses.has_colors())
159
- except curses.error: # curses not initialised yet
159
+ except curses.error: # curses not initialized yet
160
160
  return False
161
161
 
162
162
  def colors(self) -> int:
@@ -176,7 +176,7 @@ class CursesBackend:
176
176
  pairs = 0
177
177
  if pairs > 0:
178
178
  return pairs
179
- # A colour-capable terminal always has at least the ncurses minimum;
179
+ # A color-capable terminal always has at least the ncurses minimum;
180
180
  # reporting 0 here would make a theme install silently do nothing.
181
181
  return self._FALLBACK_COLOR_PAIRS if self.colors() > 0 else 0
182
182
 
@@ -184,7 +184,7 @@ class CursesBackend:
184
184
  try:
185
185
  curses.start_color()
186
186
  except curses.error:
187
- # No colour support after all: degrade to monochrome instead of
187
+ # No color support after all: degrade to monochrome instead of
188
188
  # letting the caller crash during startup.
189
189
  self._colors = 0
190
190
  self._color_pairs = 0
@@ -289,8 +289,8 @@ class HeadlessBackend:
289
289
  self.pads: list[_NoopSurface] = []
290
290
  self.stopped = False
291
291
  # ``colors`` used to be a bare bool. It still accepts one (True means a
292
- # 256-colour terminal, False means none) but now also accepts the exact
293
- # count, so the low-colour degradation paths are unit-testable without a
292
+ # 256-color terminal, False means none) but now also accepts the exact
293
+ # count, so the low-color degradation paths are unit-testable without a
294
294
  # real terminal.
295
295
  if isinstance(colors, bool):
296
296
  self._colors = 256 if colors else 0
@@ -1,8 +1,8 @@
1
1
  """Argument-parser boilerplate for the display options.
2
2
 
3
3
  Every curses app ends up writing the same four switches: a way to force the
4
- plain-ASCII symbol set, a way to pick a colour theme, a way to turn semantic
5
- colour off, and a way to print what was detected. This module supplies them,
4
+ plain-ASCII symbol set, a way to pick a color theme, a way to turn semantic
5
+ color off, and a way to print what was detected. This module supplies them,
6
6
  so an app's ``main()`` gains two lines instead of sixty:
7
7
 
8
8
  parser = cli.add_display_arguments(parser)
@@ -15,7 +15,7 @@ optional boolean (``--ascii``, ``--ascii=no``) and ``--glyphs`` accepts a mode
15
15
  "absent" everywhere means *detect*, never "assume Unicode".
16
16
 
17
17
  ``--no-color`` is the appearance switch. :func:`apply_display_arguments` acts on
18
- it, and an app that remembers a colour preference can settle the whole decision
18
+ it, and an app that remembers a color preference can settle the whole decision
19
19
  with :func:`console_window.appearance.resolve_appearance`.
20
20
  """
21
21
 
@@ -101,7 +101,7 @@ def add_display_arguments(
101
101
  action='store_true',
102
102
  default=None,
103
103
  help=(
104
- 'draw the semantic roles with attributes instead of colour; wins '
104
+ 'draw the semantic roles with attributes instead of color; wins '
105
105
  'over --theme, and NO_COLOR does the same when the switch is absent'
106
106
  ),
107
107
  )
@@ -139,17 +139,17 @@ def apply_display_arguments(
139
139
  environ: Mapping[str, str] | None = None,
140
140
  stream: TextIO | None = None,
141
141
  ) -> TerminalReport:
142
- """Set the glyph mode, print the report's notes, seed the theme, honour colour.
142
+ """Set the glyph mode, print the report's notes, seed the theme, honor color.
143
143
 
144
144
  Call this once, *before* any window is created, so the mode is decided while
145
145
  nothing has been drawn. Returns the :class:`~console_window.glyphs.
146
146
  TerminalReport` so a caller can react to ``ascii_required`` (for example by
147
147
  printing a warning of its own).
148
148
 
149
- An explicit ``--no-color`` turns the process-wide colour switch off here; an
149
+ An explicit ``--no-color`` turns the process-wide color switch off here; an
150
150
  absent switch is left alone, so an app with a remembered preference is free
151
151
  to call ``set_colors_enabled()`` with its own decision. The two halves of
152
- that decision -- the theme to install and whether colour is on -- come from
152
+ that decision -- the theme to install and whether color is on -- come from
153
153
  :func:`console_window.appearance.resolve_appearance`.
154
154
 
155
155
  *printer* is a callable taking one string; pass ``sys.stderr.write`` style
@@ -132,7 +132,7 @@ BUILTIN_GLYPHS: dict[str, Glyph] = {
132
132
  'nav_prev': Glyph('\u2190', '<-'), # help legend, "left arrow"
133
133
  'nav_next': Glyph('\u2192', '->'), # help legend, "right arrow"
134
134
  'bullet': Glyph('\u00b7', '.'), # help-list bullet
135
- 'swatch': Glyph('\u2588', '#'), # theme colour swatch
135
+ 'swatch': Glyph('\u2588', '#'), # theme color swatch
136
136
  # The message-box frame. `curses.textpad.rectangle()` already draws with
137
137
  # ACS line art, but a message box painted by hand needs its own fallback;
138
138
  # registering the corners keeps the ASCII mode invariant ("every painted
@@ -178,7 +178,13 @@ def _reset_state() -> None:
178
178
  _audited.clear()
179
179
 
180
180
 
181
- def register(name: str, unicode_text: str, ascii_text: str, *, probe: bool = False) -> None:
181
+ def register(
182
+ name: str,
183
+ unicode_text: str,
184
+ ascii_text: str,
185
+ *,
186
+ probe: bool = False, # pylint: disable=redefined-outer-name
187
+ ) -> None:
182
188
  """Teach the library one decorative glyph.
183
189
 
184
190
  *ascii_text* must be plain ASCII (it may be empty). ``probe=True`` adds the
@@ -196,7 +202,7 @@ def register(name: str, unicode_text: str, ascii_text: str, *, probe: bool = Fal
196
202
  def register_many(
197
203
  mapping: Mapping[str, Glyph | tuple[str, str] | list[str]],
198
204
  *,
199
- probe: Iterable[str] = (),
205
+ probe: Iterable[str] = (), # pylint: disable=redefined-outer-name
200
206
  ) -> None:
201
207
  """Register a whole table at once.
202
208
 
@@ -1,7 +1,7 @@
1
1
  """Ready-made help-screen paragraphs for the display features.
2
2
 
3
3
  A help screen is where a user goes when the display looks wrong, so the two
4
- things that most often look wrong -- the colour theme and the symbol set --
4
+ things that most often look wrong -- the color theme and the symbol set --
5
5
  deserve standard wording. Keeping the wording here means every app that uses
6
6
  this library says the same true thing, and an app that adds a theme picker
7
7
  does not have to re-explain what ``t`` does.
@@ -207,7 +207,7 @@ class ConsoleWindowOpts:
207
207
  self.relax_handled_keys = kwargs.get('relax_handled_keys', True)
208
208
  # Per-window theme (ConsoleWindow.theme). None means "inherit whatever
209
209
  # theme is current when this window starts", which is the pre-existing
210
- # process-default behaviour.
210
+ # process-default behavior.
211
211
  self.theme = kwargs.get('theme')
212
212
  # Glyph mode and app vocabulary. The mode itself is process state (one
213
213
  # terminal); the option only seeds it, exactly as ``theme`` seeds the
@@ -300,7 +300,7 @@ class BasicTextScreen(Screen):
300
300
  objects before the ConsoleWindow exists (BasicHelpScreen relies on the same
301
301
  rule). ``lines`` items are ``str`` or ``(str, attr)``; ``head`` is a
302
302
  ``str``, a :class:`HeaderLine`, or an iterable of either. Content is
303
- normalised by :meth:`set_text`, so ``draw_screen()`` is a flat loop and
303
+ normalized by :meth:`set_text`, so ``draw_screen()`` is a flat loop and
304
304
  nothing is cached across draws.
305
305
 
306
306
  There is no pick mode, no action handling and no spinner. ESC/back stays
@@ -76,7 +76,7 @@ class OptionSpinner:
76
76
  return getattr(ns.obj, ns.attr, None)
77
77
 
78
78
  def normalize(self, attr: str) -> object:
79
- """Initialise a missing option to its first value (or '' for a prompt).
79
+ """Initialize a missing option to its first value (or '' for a prompt).
80
80
 
81
81
  This is the only path that writes while an option is being registered;
82
82
  ordinary reads (help rendering, key handling) use get_value() and stay
@@ -265,7 +265,7 @@ class OptionSpinner:
265
265
  @staticmethod
266
266
  def show_help_nav_keys(win: ConsoleWindow) -> None:
267
267
  # Render from the live window so its own opts.taken_nav_keys are
268
- # honoured: keys the app owns are not advertised as navigation.
268
+ # honored: keys the app owns are not advertised as navigation.
269
269
  for line in win.nav_help_lines():
270
270
  win.add_header(line)
271
271
 
@@ -46,11 +46,11 @@ def _discover_cycle_key(screen: object, fallback: str = 't') -> str:
46
46
  return fallback
47
47
 
48
48
  # --- palette model -----------------------------------------------------------
49
- # Standard xterm palette model. 0..15 are the ANSI/base colours, 16..231 the
50
- # 6x6x6 colour cube with the levels below, and 232..255 the grayscale ramp
49
+ # Standard xterm palette model. 0..15 are the ANSI/base colors, 16..231 the
50
+ # 6x6x6 color cube with the levels below, and 232..255 the grayscale ramp
51
51
  # ``8 + 10 * n``. The RGB values are the conventional xterm ones; exact
52
52
  # fidelity is not required because the result only picks the closest entry a
53
- # low-colour terminal can actually display.
53
+ # low-color terminal can actually display.
54
54
  _ANSI_RGB: tuple[tuple[int, int, int], ...] = (
55
55
  (0, 0, 0), # 0 black
56
56
  (128, 0, 0), # 1 red
@@ -99,12 +99,12 @@ def _rgb_distance(left: tuple[int, int, int],
99
99
 
100
100
 
101
101
  def nearest_palette_index(index: int, colors: int) -> int:
102
- """Map 256-colour *index* to the nearest entry a *colors*-terminal has.
102
+ """Map 256-color *index* to the nearest entry a *colors*-terminal has.
103
103
 
104
104
  Entries 0..15 are the base palette, so the search is restricted to
105
- ``0 .. min(colors, 16) - 1``. A 256-colour terminal gets *index* back
105
+ ``0 .. min(colors, 16) - 1``. A 256-color terminal gets *index* back
106
106
  unchanged (exact theme specs, no visual regression). Returns ``-1`` when
107
- the terminal has no usable colours at all.
107
+ the terminal has no usable colors at all.
108
108
  """
109
109
  if colors >= 256:
110
110
  return index
@@ -148,10 +148,10 @@ def resolve_color_pair(fg: int, bg: int, colors: int,
148
148
 
149
149
  ``-1`` is passed through only when :meth:`RenderBackend.use_default_colors`
150
150
  succeeded; otherwise it is replaced with concrete defaults (fg 7, bg 0).
151
- On terminals with fewer than 256 colours each index is mapped to the
151
+ On terminals with fewer than 256 colors each index is mapped to the
152
152
  nearest available palette entry. If the mapping would collapse the pair to
153
153
  ``fg == bg`` the foreground is moved to the most contrasting entry. Returns
154
- ``None`` when no legible pair exists (e.g. a 1-colour terminal), in which
154
+ ``None`` when no legible pair exists (e.g. a 1-color terminal), in which
155
155
  case the caller falls back to attributes and installs nothing.
156
156
  """
157
157
  if colors <= 0:
@@ -166,7 +166,7 @@ def resolve_color_pair(fg: int, bg: int, colors: int,
166
166
  if bg != -1:
167
167
  bg = nearest_palette_index(bg, colors)
168
168
  # A pair of terminal defaults is left alone: the terminal guarantees a
169
- # legible default combination, and preserving (-1, -1) keeps the 256-colour
169
+ # legible default combination, and preserving (-1, -1) keeps the 256-color
170
170
  # path byte-for-byte identical to the theme spec.
171
171
  if fg != -1 and bg != -1 and fg == bg:
172
172
  partner = _contrast_partner(bg, colors)
@@ -195,10 +195,10 @@ class Theme:
195
195
  # to be the bare literal ``8`` in two places.
196
196
  DEFAULT_BG = 8
197
197
 
198
- # The semantic roles, in initialisation order. Both startup
199
- # (ConsoleWindow._init_screen) and Theme.set() reinitialise exactly this set,
198
+ # The semantic roles, in initialization order. Both startup
199
+ # (ConsoleWindow._init_screen) and Theme.set() reinitialize exactly this set,
200
200
  # so the two paths cannot drift apart: startup used to omit OLD_SUCCESS,
201
- # leaving it on an uninitialised pair until a theme change.
201
+ # leaving it on an uninitialized pair until a theme change.
202
202
  SEMANTIC_PAIRS: ClassVar[tuple[int, ...]] = (
203
203
  DANGER, SUCCESS, OLD_SUCCESS, WARNING, INFO, EMPHASIS, ERROR, PROGRESS, HOTSWAP,
204
204
  )
@@ -337,7 +337,8 @@ class Theme:
337
337
  if cli_theme is None and store is not None:
338
338
  try:
339
339
  remembered = store.load()
340
- except Exception: # a broken prefs file must not kill start-up
340
+ # A broken prefs file must not kill start-up.
341
+ except Exception: # pylint: disable=broad-exception-caught
341
342
  logger.warning('theme store could not be read; using the default',
342
343
  exc_info=True)
343
344
  return cls.resolve(cli_theme or remembered)
@@ -349,7 +350,7 @@ class Theme:
349
350
  ``DEFAULT_BG`` comes first (the screen and pad background), then the
350
351
  semantic roles in ``SEMANTIC_PAIRS`` order. Startup
351
352
  (``ConsoleWindow._init_screen``), :meth:`set` and
352
- ``ConsoleWindow.set_theme`` all install colours through :meth:`install`
353
+ ``ConsoleWindow.set_theme`` all install colors through :meth:`install`
353
354
  from this one list, so the paths cannot drift apart.
354
355
  """
355
356
  theme = cls.THEMES[cls.resolve(theme_name)]
@@ -369,13 +370,13 @@ class Theme:
369
370
  @classmethod
370
371
  def install(cls, backend: RenderBackend,
371
372
  theme_name: str | None = None) -> str:
372
- """Install *theme_name*'s colours through *backend*, degrading safely.
373
+ """Install *theme_name*'s colors through *backend*, degrading safely.
373
374
 
374
375
  This is the single installation path used by startup
375
376
  (``ConsoleWindow._init_screen``), ``ConsoleWindow.set_theme()`` and
376
- :meth:`set`. It consults the backend's colour capability instead of the
377
- ``curses`` globals: on a 256-colour terminal the theme's literal specs
378
- are used unchanged, while fewer colours are mapped to the nearest
377
+ :meth:`set`. It consults the backend's color capability instead of the
378
+ ``curses`` globals: on a 256-color terminal the theme's literal specs
379
+ are used unchanged, while fewer colors are mapped to the nearest
379
380
  available palette entry and ``-1`` is only emitted when the terminal
380
381
  accepted ``use_default_colors()``. Pair ids beyond ``COLOR_PAIRS`` and
381
382
  pairs with no legible mapping are skipped rather than raising. Returns
@@ -403,10 +404,10 @@ class Theme:
403
404
  # Late import to avoid a circular import at module load time.
404
405
  from .window import ConsoleWindow
405
406
 
406
- # Colour pairs are terminal-global, so a live window's backend is the
407
+ # Color pairs are terminal-global, so a live window's backend is the
407
408
  # authoritative seam. Without one, fall back to curses itself, but go
408
409
  # through CursesBackend so this method never calls curses.init_pair()
409
- # directly (that path used to bypass every low-colour guard).
410
+ # directly (that path used to bypass every low-color guard).
410
411
  win = ConsoleWindow.primary_window()
411
412
  backend = getattr(win, '_backend', None) if win is not None else None
412
413
  if backend is None:
@@ -471,7 +472,7 @@ class Theme:
471
472
  note: str | None = None) -> type:
472
473
  """Build a theme-picker screen class on top of *base_class*.
473
474
 
474
- The generated screen renders the current theme, a set of labelled colour
475
+ The generated screen renders the current theme, a set of labeled color
475
476
  swatches, and an action (``spin_theme_ACTION``) that cycles themes. The
476
477
  host application is expected to expose ``win``, and may expose
477
478
  ``opts.theme``: when present, that option is treated as the selected
@@ -485,6 +486,15 @@ class Theme:
485
486
  ``store.save()`` after each cycle, and a failing store is logged rather
486
487
  than raised. *note* is one extra dim line (for example "the choice is
487
488
  remembered for the next start").
489
+
490
+ Swatches are painted through the appearance layer rather than
491
+ ``curses.color_pair()``, so a picker opened while color is off shows the
492
+ monochrome role hierarchy instead of re-painting a previous theme's
493
+ colors. A host that wants a different swatch style can subclass the
494
+ generated screen and override its ``swatch_attr()`` hook, and a host
495
+ with its own pseudo-theme (``mono``, say) can override
496
+ ``theme_label()`` so the header names the choice the user made rather
497
+ than the palette left installed underneath it.
488
498
  """
489
499
  swatch = glyphs.get('swatch') * 8
490
500
 
@@ -504,7 +514,7 @@ class Theme:
504
514
 
505
515
  # Name the theme this window is on (default when unset).
506
516
  current_theme = cls.theme_of(win)
507
- display_theme = current_theme if current_theme else '(default)'
517
+ display_theme = self.theme_label(current_theme)
508
518
  hint_key = cycle_key or _discover_cycle_key(self)
509
519
 
510
520
  # Add header showing current theme
@@ -531,11 +541,41 @@ class Theme:
531
541
  # is a registered glyph, so a terminal that cannot draw U+2588
532
542
  # shows '#' blocks instead of nine empty cells.
533
543
  line = f'{label:12} {swatch} {description}'
534
- attr = curses.color_pair(color_id)
535
- win.add_body(line, attr=attr)
544
+ win.add_body(line, attr=self.swatch_attr(color_id))
536
545
  if note:
537
546
  win.add_body(note, attr=curses.A_DIM)
538
547
 
548
+ def theme_label(self, theme_name: str | None) -> str:
549
+ """Header label for the theme the window is on.
550
+
551
+ The picker names the palette it is previewing. A host with its
552
+ own pseudo-theme can override this to name the mode actually in
553
+ effect -- for example ``mono`` while color is off -- so the
554
+ header and the swatches tell one story instead of two.
555
+
556
+ This is a hook, like :meth:`swatch_attr`.
557
+ """
558
+ return theme_name if theme_name else '(default)'
559
+
560
+ @staticmethod
561
+ def swatch_attr(color_id: int) -> int:
562
+ """Attribute for one role's swatch in the picker.
563
+
564
+ Delegates to :func:`console_window.appearance.semantic_attr`, so
565
+ the swatch obeys the same process-wide color switch as every
566
+ other semantic role. With color off the role's monochrome
567
+ attribute carries the hierarchy (bold+reverse, bold, plain) and
568
+ the preview shows the real no-color appearance; with color on
569
+ it is the installed pair.
570
+
571
+ This is a hook: a host may subclass the generated screen and
572
+ override it to style swatches differently.
573
+ """
574
+ # Late import: appearance imports Theme at module load time.
575
+ from .appearance import semantic_attr
576
+
577
+ return semantic_attr(color_id)
578
+
539
579
  def spin_theme_ACTION(self) -> None:
540
580
  win = self.app.win
541
581
  vals = cls.list_all()
@@ -551,7 +591,8 @@ class Theme:
551
591
  if store is not None:
552
592
  try:
553
593
  store.save(value)
554
- except Exception: # persistence never breaks a cycle
594
+ # Persistence never breaks a cycle.
595
+ except Exception: # pylint: disable=broad-exception-caught
555
596
  logger.warning('could not remember theme %r', value,
556
597
  exc_info=True)
557
598
 
@@ -181,7 +181,7 @@ def render_nav_help(effective: Iterable[int],
181
181
  # dedented triple-quoted string: editing the indentation can no longer change
182
182
  # what the help screen renders. ``ConsoleWindow.nav_keys`` remains the joined
183
183
  # string for callers that want the whole blurb at once; live windows render
184
- # through ``nav_help_lines()`` so their own ``taken_nav_keys`` are honoured.
184
+ # through ``nav_help_lines()`` so their own ``taken_nav_keys`` are honored.
185
185
  NAV_KEY_LINES: tuple[str, ...] = render_nav_help(NAVIGATION_KEYS)
186
186
 
187
187
  # Editing keys that passthrough mode hands to the application unchanged, in
@@ -200,7 +200,7 @@ _PASSTHROUGH_EDITING_KEYS = frozenset({
200
200
 
201
201
  # Compact demo-mode names for the keys whose plain curses name would be
202
202
  # unwieldy or ambiguous in the header indicator. Printable keys are handled
203
- # separately (centred with spaces), so this table only covers the rest.
203
+ # separately (centered with spaces), so this table only covers the rest.
204
204
  _DEMO_KEY_LABELS: dict[int, str] = {
205
205
  27: 'ESC',
206
206
  ord('\t'): 'TAB',
@@ -659,11 +659,11 @@ class ConsoleWindow:
659
659
  glyphs.ensure_policy()
660
660
 
661
661
  def set_theme(self, theme_name: str) -> bool:
662
- """Make *theme_name* this window's theme and install its colours.
662
+ """Make *theme_name* this window's theme and install its colors.
663
663
 
664
- Returns False for an unknown theme, or when the backend has no colour
665
- support (the choice is still recorded, so a later colour-capable start
666
- uses it). Colour pairs are per-terminal, so installing a palette
664
+ Returns False for an unknown theme, or when the backend has no color
665
+ support (the choice is still recorded, so a later color-capable start
666
+ uses it). Color pairs are per-terminal, so installing a palette
667
667
  necessarily also affects any other window drawing on the same screen —
668
668
  that is a curses constraint, not a choice. What is per-window is the
669
669
  selection: it is used at startup, when pads are reallocated, and
@@ -686,9 +686,9 @@ class ConsoleWindow:
686
686
  return True
687
687
 
688
688
  def _apply_pad_theme(self, pad: CursesSurface) -> None:
689
- # A freshly created pad has no background, so re-apply the theme colour
689
+ # A freshly created pad has no background, so re-apply the theme color
690
690
  # after every reallocation; otherwise a resize leaves the new pad on the
691
- # terminal default instead of the configured background. The colour
691
+ # terminal default instead of the configured background. The color
692
692
  # comes from *this window's* theme, not the process default.
693
693
  if not self._backend.has_colors():
694
694
  return
@@ -696,7 +696,7 @@ class ConsoleWindow:
696
696
  if ('default_fg' in theme and 'default_bg' in theme
697
697
  and Theme.DEFAULT_BG < self._backend.color_pairs()):
698
698
  # Theme.install() has already installed this pair with either the
699
- # literal values or the low-colour mapping, so bkgd() never sees a
699
+ # literal values or the low-color mapping, so bkgd() never sees a
700
700
  # stale/out-of-range pair.
701
701
  pad.bkgd(' ', self._backend.color_pair(Theme.DEFAULT_BG))
702
702
 
@@ -815,7 +815,7 @@ class ConsoleWindow:
815
815
  return True
816
816
 
817
817
  def _new_screen(self) -> CursesSurface:
818
- """Create and initialise a brand-new screen for this window."""
818
+ """Create and initialize a brand-new screen for this window."""
819
819
  return self._init_screen(self._backend, self.theme)
820
820
 
821
821
  def _start_curses(self) -> CursesSurface:
@@ -972,11 +972,11 @@ class ConsoleWindow:
972
972
  @staticmethod
973
973
  def _install_theme(backend: RenderBackend, scr: CursesSurface,
974
974
  theme_name: str | None) -> str:
975
- """Install *theme_name*'s colours into the terminal's pair table.
975
+ """Install *theme_name*'s colors into the terminal's pair table.
976
976
 
977
- Colour pairs are per-terminal (one table, not one per window), so both
977
+ Color pairs are per-terminal (one table, not one per window), so both
978
978
  startup and ``set_theme()`` call this; the caller decides *whose*
979
- theme wins. The actual installation (including low-colour mapping and
979
+ theme wins. The actual installation (including low-color mapping and
980
980
  ``-1`` resolution) lives in :meth:`Theme.install`, which ``Theme.set()``
981
981
  uses too, so the two entry points cannot diverge. Returns the resolved
982
982
  theme name.
@@ -984,7 +984,7 @@ class ConsoleWindow:
984
984
  name = Theme.install(backend, theme_name)
985
985
  theme = Theme.THEMES[name]
986
986
  # Can't change pair 0, so the screen background is set directly. The
987
- # pair has just been installed above (with mapped values on low-colour
987
+ # pair has just been installed above (with mapped values on low-color
988
988
  # terminals), so this never references a stale or out-of-range pair.
989
989
  if ('default_fg' in theme and 'default_bg' in theme
990
990
  and Theme.DEFAULT_BG < backend.color_pairs()):
@@ -998,11 +998,11 @@ class ConsoleWindow:
998
998
  signals.setup_ctrl_c()
999
999
  scr = cast(CursesSurface, backend.start(ConsoleWindow.timeout_ms))
1000
1000
 
1001
- # Initialize color support with theme. Every colour call goes through
1002
- # the backend, so a non-curses backend that reports colours can use it.
1001
+ # Initialize color support with theme. Every color call goes through
1002
+ # the backend, so a non-curses backend that reports colors can use it.
1003
1003
  if backend.has_colors():
1004
1004
  # start_color() is where a curses backend captures COLORS and
1005
- # COLOR_PAIRS; Theme.install() then enables default colours (and
1005
+ # COLOR_PAIRS; Theme.install() then enables default colors (and
1006
1006
  # remembers whether -1 is usable) before resolving each pair.
1007
1007
  backend.start_color()
1008
1008
  # The window's own theme (per-window since AUDIT.md §3.1) or the
@@ -2541,7 +2541,7 @@ class ConsoleWindow:
2541
2541
  def _alert_scroll_target(key: int, scroll_top: int, visible_rows: int,
2542
2542
  max_scroll: int) -> int:
2543
2543
  # "vim"-style scroll commands, matching the navigation family prompt()
2544
- # honours. Any other key is not a scroll command and leaves the view put.
2544
+ # honors. Any other key is not a scroll command and leaves the view put.
2545
2545
  if key in (ord('k'), curses.KEY_UP):
2546
2546
  target = scroll_top - 1
2547
2547
  elif key in (ord('j'), curses.KEY_DOWN):
@@ -94,7 +94,7 @@ omit = ["tests/*"]
94
94
 
95
95
  [tool.coverage.report]
96
96
  # Measured at 90.69% when the shared release gate was adopted. The floor is a
97
- # ratchet against new code arriving without a test, not a target to optimise;
97
+ # ratchet against new code arriving without a test, not a target to optimize;
98
98
  # raise it when the number rises, never lower it.
99
99
  fail_under = 90
100
100
  show_missing = true
File without changes