File: measuring.rst

package info (click to toggle)
python-blessed 1.25-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 8,812 kB
  • sloc: python: 14,645; makefile: 13; sh: 7
file content (175 lines) | stat: -rw-r--r-- 6,735 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
Measuring
=========

Any string containing sequences can be measured by blessed using the :meth:`~.Terminal.length`
method. This means that blessed can measure, right-align, center, truncate, or word-wrap its
own output!

The :attr:`~.Terminal.height` and :attr:`~.Terminal.width` properties always provide a current
readout of the size of the window in character cells:

    >>> term.height, term.width
    (34, 102)

The :attr:`~.Terminal.pixel_height` and :attr:`~.Terminal.pixel_width` properties provide the
window size in pixels when available:

    >>> term.pixel_height, term.pixel_width
    (1080, 1920)

.. note::

   For sixel graphics, use :meth:`~.Terminal.get_sixel_height_and_width` instead,
   which returns the actual drawable area by accounting for margins. See
   :doc:`sixel` for details.

By combining the measure of the printable width of strings containing sequences with the terminal
width, the :meth:`~.Terminal.center`, :meth:`~.Terminal.ljust`, :meth:`~.Terminal.rjust`,
:meth:`~Terminal.truncate`, and :meth:`~Terminal.wrap` methods "just work" for strings that
contain sequences.

.. code-block:: python

    with term.location(y=term.height // 2):
        print(term.center(term.bold('press return to begin!')))
        term.inkey()

In the following example, :meth:`~Terminal.wrap` word-wraps a short poem containing sequences:

.. code-block:: python

    from blessed import Terminal

    term = Terminal()

    poem = (term.bold_cyan('Plan difficult tasks'),
            term.cyan('through the simplest tasks'),
            term.bold_cyan('Achieve large tasks'),
            term.cyan('through the smallest tasks'))

    for line in poem:
        print('\n'.join(term.wrap(line, width=25, subsequent_indent=' ' * 4)))

Resizing
--------

The terminal can notify your application when the window size changes. Blessed provides a modern
cross-platform method using in-band resize notifications, with SIGWINCH_ as a fallback for older
terminals.

Checking for support
~~~~~~~~~~~~~~~~~~~~

Use :meth:`~.Terminal.does_inband_resize` to check if the terminal supports in-band resize
notifications (DEC mode 2048):

.. code-block:: python

    from blessed import Terminal

    term = Terminal()

    if term.does_inband_resize():
        print('In-band resize notifications are supported!')
    else:
        print('Falling back to SIGWINCH or polling')

.. note::

    In-band resize notification support (DEC mode 2048) is currently **very limited** among
    terminal emulators. Most terminals do not support this feature yet. Always check with
    :meth:`~.Terminal.does_inband_resize` and provide a fallback using SIGWINCH on Unix systems
    (see example below)

Using :meth:`~.Terminal.notify_on_resize` (recommended)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The :meth:`~.Terminal.notify_on_resize` context manager enables automatic resize event reporting.
When the window is resized, :meth:`~.Terminal.inkey` will return a keystroke with
:attr:`~.Keystroke.name` equal to ``'RESIZE_EVENT'``. The new dimensions are immediately available
through :attr:`~.Terminal.height`, :attr:`~.Terminal.width`, :attr:`~.Terminal.pixel_height`, and
:attr:`~.Terminal.pixel_width`.

This method is preferred because it:

- Works cross-platform (Linux, Mac, BSD, Windows)
- Avoids race conditions inherent in signal handlers
- Delivers resize events in-band with other input
- Automatically caches dimensions for fast access

Combined with signals
~~~~~~~~~~~~~~~~~~~~~

The following example demonstrates checking for support, using in-band resize notifications when
available, but **falling back to SIGWINCH on Unix systems**:

.. literalinclude:: ../bin/on_resize.py
   :language: python

Make note of these designs:

1. **Signal handlers only set a flag**: The ``on_resize()`` signal handler only calls
   ``_resize_pending.set()`` and does nothing else. Signal handlers should never
   perform I/O operations, terminal queries, or other complex work, because
   these callbacks can occur *at any time* in your code, even part-way through
   drawing, for example.

2. **Main loop processes events**: The main event loop checks if
   ``_resize_pending.is_set()`` periodically, every 100ms by timed input delay,
   ``inkey(timeout=0.1)`` to aides with debouncing.

3. **Signal debouncing**: When signals are received, ``_resize_pending.set()``
   is set, and main loop displays only one size notification for any number
   of signals received within 100ms.

4. **Input debouncing**: When input is received through
   :meth:`~.Terminal.inkey` method as ``RESIZE_EVENT``` for terminals supporting
   :meth:`~Terminal.notify_on_resize`, a pending event is set but it is not
   immediately processed -- another 100ms delay is incurred, effectively
   de-bouncing rapidly received resize events, there.

This pattern avoids race conditions. Although Python's GIL may provide some
protection, signal handlers can still interrupt code at inconvenient times, and
doing complex work in handlers can lead to subtle bugs.

Note the use of ``force=True`` when calling :meth:`~.Terminal.get_sixel_height_and_width`, the
return value is cached, so ``force=True`` ensures updated dimensions are retrieved after a resize
event. See :doc:`sixel` for more on caching behavior.

SIGWINCH (fallback for older terminals)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

For terminals that don't support in-band resize notifications (DEC mode 2048), you can use
SIGWINCH_ signals on Unix systems. See the example above for a complete implementation with
automatic fallback

.. image:: https://dxtz6bzwq9sxx.cloudfront.net/demo_resize_window.gif
    :alt: A visual animated example of the on_resize() function callback

.. warning:: SIGWINCH has limitations:

    - Not compatible with Windows
    - Signal handlers should avoid blocking operations
    - Race conditions can occur between signal delivery and terminal state
    - Requires careful synchronization with application state

    For new code, prefer :meth:`~.Terminal.notify_on_resize` instead.

Sometimes it is necessary to make sense of sequences, and to distinguish them
from plain text.  The :meth:`~.Terminal.split_seqs` method can allow us to
iterate over a terminal string by its characters or sequences:

    >>> term.split_seqs(term.bold('bbq'))
    ['\x1b[1m', 'b', 'b', 'q', '\x1b(B', '\x1b[m']

Will display something like, ``['\x1b[1m', 'b', 'b', 'q', '\x1b(B', '\x1b[m']``

Method :meth:`~.Terminal.strip_seqs` can remove all sequences from a string:

    >>> phrase = term.bold_black('coffee')
    >>> phrase
    '\x1b[1m\x1b[30mcoffee\x1b(B\x1b[m'
    >>> term.strip_seqs(phrase)
    'coffee'

.. _SIGWINCH: https://en.wikipedia.org/wiki/SIGWINCH