urwid 2.6.0.post0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of urwid might be problematic. Click here for more details.
- urwid/__init__.py +333 -0
- urwid/canvas.py +1413 -0
- urwid/command_map.py +137 -0
- urwid/container.py +59 -0
- urwid/decoration.py +65 -0
- urwid/display/__init__.py +97 -0
- urwid/display/_posix_raw_display.py +413 -0
- urwid/display/_raw_display_base.py +914 -0
- urwid/display/_web.css +12 -0
- urwid/display/_web.js +462 -0
- urwid/display/_win32.py +171 -0
- urwid/display/_win32_raw_display.py +269 -0
- urwid/display/common.py +1219 -0
- urwid/display/curses.py +690 -0
- urwid/display/escape.py +624 -0
- urwid/display/html_fragment.py +251 -0
- urwid/display/lcd.py +518 -0
- urwid/display/raw.py +37 -0
- urwid/display/web.py +636 -0
- urwid/event_loop/__init__.py +55 -0
- urwid/event_loop/abstract_loop.py +175 -0
- urwid/event_loop/asyncio_loop.py +231 -0
- urwid/event_loop/glib_loop.py +294 -0
- urwid/event_loop/main_loop.py +721 -0
- urwid/event_loop/select_loop.py +230 -0
- urwid/event_loop/tornado_loop.py +206 -0
- urwid/event_loop/trio_loop.py +302 -0
- urwid/event_loop/twisted_loop.py +269 -0
- urwid/event_loop/zmq_loop.py +275 -0
- urwid/font.py +695 -0
- urwid/graphics.py +96 -0
- urwid/highlight.css +19 -0
- urwid/listbox.py +1899 -0
- urwid/monitored_list.py +522 -0
- urwid/numedit.py +376 -0
- urwid/signals.py +330 -0
- urwid/split_repr.py +130 -0
- urwid/str_util.py +358 -0
- urwid/text_layout.py +632 -0
- urwid/treetools.py +515 -0
- urwid/util.py +557 -0
- urwid/version.py +16 -0
- urwid/vterm.py +1806 -0
- urwid/widget/__init__.py +181 -0
- urwid/widget/attr_map.py +161 -0
- urwid/widget/attr_wrap.py +140 -0
- urwid/widget/bar_graph.py +649 -0
- urwid/widget/big_text.py +77 -0
- urwid/widget/box_adapter.py +126 -0
- urwid/widget/columns.py +1145 -0
- urwid/widget/constants.py +574 -0
- urwid/widget/container.py +227 -0
- urwid/widget/divider.py +110 -0
- urwid/widget/edit.py +718 -0
- urwid/widget/filler.py +403 -0
- urwid/widget/frame.py +539 -0
- urwid/widget/grid_flow.py +539 -0
- urwid/widget/line_box.py +194 -0
- urwid/widget/overlay.py +829 -0
- urwid/widget/padding.py +597 -0
- urwid/widget/pile.py +971 -0
- urwid/widget/popup.py +170 -0
- urwid/widget/progress_bar.py +141 -0
- urwid/widget/scrollable.py +597 -0
- urwid/widget/solid_fill.py +44 -0
- urwid/widget/text.py +354 -0
- urwid/widget/widget.py +852 -0
- urwid/widget/widget_decoration.py +166 -0
- urwid/widget/wimp.py +792 -0
- urwid/wimp.py +23 -0
- urwid-2.6.0.post0.dist-info/COPYING +504 -0
- urwid-2.6.0.post0.dist-info/METADATA +332 -0
- urwid-2.6.0.post0.dist-info/RECORD +75 -0
- urwid-2.6.0.post0.dist-info/WHEEL +5 -0
- urwid-2.6.0.post0.dist-info/top_level.txt +1 -0
urwid/widget/text.py
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import typing
|
|
4
|
+
|
|
5
|
+
from urwid import text_layout
|
|
6
|
+
from urwid.canvas import apply_text_layout
|
|
7
|
+
from urwid.split_repr import remove_defaults
|
|
8
|
+
from urwid.str_util import calc_width
|
|
9
|
+
from urwid.util import decompose_tagmarkup, get_encoding
|
|
10
|
+
|
|
11
|
+
from .constants import Align, Sizing, WrapMode
|
|
12
|
+
from .widget import Widget, WidgetError
|
|
13
|
+
|
|
14
|
+
if typing.TYPE_CHECKING:
|
|
15
|
+
from collections.abc import Hashable
|
|
16
|
+
|
|
17
|
+
from typing_extensions import Literal
|
|
18
|
+
|
|
19
|
+
from urwid.canvas import TextCanvas
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class TextError(WidgetError):
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Text(Widget):
|
|
27
|
+
"""
|
|
28
|
+
a horizontally resizeable text widget
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
_sizing = frozenset([Sizing.FLOW, Sizing.FIXED])
|
|
32
|
+
|
|
33
|
+
ignore_focus = True
|
|
34
|
+
_repr_content_length_max = 140
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
markup: str | tuple[Hashable, str] | list[str | tuple[Hashable, str]],
|
|
39
|
+
align: Literal["left", "center", "right"] | Align = Align.LEFT,
|
|
40
|
+
wrap: Literal["space", "any", "clip", "ellipsis"] | WrapMode = WrapMode.SPACE,
|
|
41
|
+
layout: text_layout.TextLayout | None = None,
|
|
42
|
+
) -> None:
|
|
43
|
+
"""
|
|
44
|
+
:param markup: content of text widget, one of:
|
|
45
|
+
|
|
46
|
+
bytes or unicode
|
|
47
|
+
text to be displayed
|
|
48
|
+
|
|
49
|
+
(*display attribute*, *text markup*)
|
|
50
|
+
*text markup* with *display attribute* applied to all parts
|
|
51
|
+
of *text markup* with no display attribute already applied
|
|
52
|
+
|
|
53
|
+
[*text markup*, *text markup*, ... ]
|
|
54
|
+
all *text markup* in the list joined together
|
|
55
|
+
|
|
56
|
+
:type markup: :ref:`text-markup`
|
|
57
|
+
:param align: typically ``'left'``, ``'center'`` or ``'right'``
|
|
58
|
+
:type align: text alignment mode
|
|
59
|
+
:param wrap: typically ``'space'``, ``'any'``, ``'clip'`` or ``'ellipsis'``
|
|
60
|
+
:type wrap: text wrapping mode
|
|
61
|
+
:param layout: defaults to a shared :class:`StandardTextLayout` instance
|
|
62
|
+
:type layout: text layout instance
|
|
63
|
+
|
|
64
|
+
>>> Text(u"Hello")
|
|
65
|
+
<Text fixed/flow widget 'Hello'>
|
|
66
|
+
>>> t = Text(('bold', u"stuff"), 'right', 'any')
|
|
67
|
+
>>> t
|
|
68
|
+
<Text fixed/flow widget 'stuff' align='right' wrap='any'>
|
|
69
|
+
>>> print(t.text)
|
|
70
|
+
stuff
|
|
71
|
+
>>> t.attrib
|
|
72
|
+
[('bold', 5)]
|
|
73
|
+
"""
|
|
74
|
+
super().__init__()
|
|
75
|
+
self._cache_maxcol = None
|
|
76
|
+
self.set_text(markup)
|
|
77
|
+
self.set_layout(align, wrap, layout)
|
|
78
|
+
|
|
79
|
+
def _repr_words(self) -> list[str]:
|
|
80
|
+
"""
|
|
81
|
+
Show the text in the repr in python3 format (b prefix for byte strings) and truncate if it's too long
|
|
82
|
+
"""
|
|
83
|
+
first = super()._repr_words()
|
|
84
|
+
text = self.get_text()[0]
|
|
85
|
+
rest = repr(text)
|
|
86
|
+
if len(rest) > self._repr_content_length_max:
|
|
87
|
+
rest = (
|
|
88
|
+
rest[: self._repr_content_length_max * 2 // 3 - 3] + "..." + rest[-self._repr_content_length_max // 3 :]
|
|
89
|
+
)
|
|
90
|
+
return [*first, rest]
|
|
91
|
+
|
|
92
|
+
def _repr_attrs(self) -> dict[str, typing.Any]:
|
|
93
|
+
attrs = {
|
|
94
|
+
**super()._repr_attrs(),
|
|
95
|
+
"align": self._align_mode,
|
|
96
|
+
"wrap": self._wrap_mode,
|
|
97
|
+
}
|
|
98
|
+
return remove_defaults(attrs, Text.__init__)
|
|
99
|
+
|
|
100
|
+
def _invalidate(self) -> None:
|
|
101
|
+
self._cache_maxcol = None
|
|
102
|
+
super()._invalidate()
|
|
103
|
+
|
|
104
|
+
def set_text(self, markup: str | tuple[Hashable, str] | list[str | tuple[Hashable, str]]) -> None:
|
|
105
|
+
"""
|
|
106
|
+
Set content of text widget.
|
|
107
|
+
|
|
108
|
+
:param markup: see :class:`Text` for description.
|
|
109
|
+
:type markup: text markup
|
|
110
|
+
|
|
111
|
+
>>> t = Text(u"foo")
|
|
112
|
+
>>> print(t.text)
|
|
113
|
+
foo
|
|
114
|
+
>>> t.set_text(u"bar")
|
|
115
|
+
>>> print(t.text)
|
|
116
|
+
bar
|
|
117
|
+
>>> t.text = u"baz" # not supported because text stores text but set_text() takes markup
|
|
118
|
+
Traceback (most recent call last):
|
|
119
|
+
AttributeError: can't set attribute
|
|
120
|
+
"""
|
|
121
|
+
self._text, self._attrib = decompose_tagmarkup(markup)
|
|
122
|
+
self._invalidate()
|
|
123
|
+
|
|
124
|
+
def get_text(self) -> tuple[str | bytes, list[tuple[Hashable, int]]]:
|
|
125
|
+
"""
|
|
126
|
+
:returns: (*text*, *display attributes*)
|
|
127
|
+
|
|
128
|
+
*text*
|
|
129
|
+
complete bytes/unicode content of text widget
|
|
130
|
+
|
|
131
|
+
*display attributes*
|
|
132
|
+
run length encoded display attributes for *text*, eg.
|
|
133
|
+
``[('attr1', 10), ('attr2', 5)]``
|
|
134
|
+
|
|
135
|
+
>>> Text(u"Hello").get_text() # ... = u in Python 2
|
|
136
|
+
(...'Hello', [])
|
|
137
|
+
>>> Text(('bright', u"Headline")).get_text()
|
|
138
|
+
(...'Headline', [('bright', 8)])
|
|
139
|
+
>>> Text([('a', u"one"), u"two", ('b', u"three")]).get_text()
|
|
140
|
+
(...'onetwothree', [('a', 3), (None, 3), ('b', 5)])
|
|
141
|
+
"""
|
|
142
|
+
return self._text, self._attrib
|
|
143
|
+
|
|
144
|
+
@property
|
|
145
|
+
def text(self) -> str | bytes:
|
|
146
|
+
"""
|
|
147
|
+
Read-only property returning the complete bytes/unicode content
|
|
148
|
+
of this widget
|
|
149
|
+
"""
|
|
150
|
+
return self.get_text()[0]
|
|
151
|
+
|
|
152
|
+
@property
|
|
153
|
+
def attrib(self) -> list[tuple[Hashable, int]]:
|
|
154
|
+
"""
|
|
155
|
+
Read-only property returning the run-length encoded display
|
|
156
|
+
attributes of this widget
|
|
157
|
+
"""
|
|
158
|
+
return self.get_text()[1]
|
|
159
|
+
|
|
160
|
+
def set_align_mode(self, mode: Literal["left", "center", "right"] | Align) -> None:
|
|
161
|
+
"""
|
|
162
|
+
Set text alignment mode. Supported modes depend on text layout
|
|
163
|
+
object in use but defaults to a :class:`StandardTextLayout` instance
|
|
164
|
+
|
|
165
|
+
:param mode: typically ``'left'``, ``'center'`` or ``'right'``
|
|
166
|
+
:type mode: text alignment mode
|
|
167
|
+
|
|
168
|
+
>>> t = Text(u"word")
|
|
169
|
+
>>> t.set_align_mode('right')
|
|
170
|
+
>>> t.align
|
|
171
|
+
'right'
|
|
172
|
+
>>> t.render((10,)).text # ... = b in Python 3
|
|
173
|
+
[...' word']
|
|
174
|
+
>>> t.align = 'center'
|
|
175
|
+
>>> t.render((10,)).text
|
|
176
|
+
[...' word ']
|
|
177
|
+
>>> t.align = 'somewhere'
|
|
178
|
+
Traceback (most recent call last):
|
|
179
|
+
TextError: Alignment mode 'somewhere' not supported.
|
|
180
|
+
"""
|
|
181
|
+
if not self.layout.supports_align_mode(mode):
|
|
182
|
+
raise TextError(f"Alignment mode {mode!r} not supported.")
|
|
183
|
+
self._align_mode = mode
|
|
184
|
+
self._invalidate()
|
|
185
|
+
|
|
186
|
+
def set_wrap_mode(self, mode: Literal["space", "any", "clip", "ellipsis"] | WrapMode) -> None:
|
|
187
|
+
"""
|
|
188
|
+
Set text wrapping mode. Supported modes depend on text layout
|
|
189
|
+
object in use but defaults to a :class:`StandardTextLayout` instance
|
|
190
|
+
|
|
191
|
+
:param mode: typically ``'space'``, ``'any'``, ``'clip'`` or ``'ellipsis'``
|
|
192
|
+
:type mode: text wrapping mode
|
|
193
|
+
|
|
194
|
+
>>> t = Text(u"some words")
|
|
195
|
+
>>> t.render((6,)).text # ... = b in Python 3
|
|
196
|
+
[...'some ', ...'words ']
|
|
197
|
+
>>> t.set_wrap_mode('clip')
|
|
198
|
+
>>> t.wrap
|
|
199
|
+
'clip'
|
|
200
|
+
>>> t.render((6,)).text
|
|
201
|
+
[...'some w']
|
|
202
|
+
>>> t.wrap = 'any' # Urwid 0.9.9 or later
|
|
203
|
+
>>> t.render((6,)).text
|
|
204
|
+
[...'some w', ...'ords ']
|
|
205
|
+
>>> t.wrap = 'somehow'
|
|
206
|
+
Traceback (most recent call last):
|
|
207
|
+
TextError: Wrap mode 'somehow' not supported.
|
|
208
|
+
"""
|
|
209
|
+
if not self.layout.supports_wrap_mode(mode):
|
|
210
|
+
raise TextError(f"Wrap mode {mode!r} not supported.")
|
|
211
|
+
self._wrap_mode = mode
|
|
212
|
+
self._invalidate()
|
|
213
|
+
|
|
214
|
+
def set_layout(
|
|
215
|
+
self,
|
|
216
|
+
align: Literal["left", "center", "right"] | Align,
|
|
217
|
+
wrap: Literal["space", "any", "clip", "ellipsis"] | WrapMode,
|
|
218
|
+
layout: text_layout.TextLayout | None = None,
|
|
219
|
+
) -> None:
|
|
220
|
+
"""
|
|
221
|
+
Set the text layout object, alignment and wrapping modes at
|
|
222
|
+
the same time.
|
|
223
|
+
|
|
224
|
+
:type align: text alignment mode
|
|
225
|
+
:param wrap: typically 'space', 'any', 'clip' or 'ellipsis'
|
|
226
|
+
:type wrap: text wrapping mode
|
|
227
|
+
:param layout: defaults to a shared :class:`StandardTextLayout` instance
|
|
228
|
+
:type layout: text layout instance
|
|
229
|
+
|
|
230
|
+
>>> t = Text(u"hi")
|
|
231
|
+
>>> t.set_layout('right', 'clip')
|
|
232
|
+
>>> t
|
|
233
|
+
<Text fixed/flow widget 'hi' align='right' wrap='clip'>
|
|
234
|
+
"""
|
|
235
|
+
if layout is None:
|
|
236
|
+
layout = text_layout.default_layout
|
|
237
|
+
self._layout = layout
|
|
238
|
+
self.set_align_mode(align)
|
|
239
|
+
self.set_wrap_mode(wrap)
|
|
240
|
+
|
|
241
|
+
align = property(lambda self: self._align_mode, set_align_mode)
|
|
242
|
+
wrap = property(lambda self: self._wrap_mode, set_wrap_mode)
|
|
243
|
+
|
|
244
|
+
@property
|
|
245
|
+
def layout(self):
|
|
246
|
+
return self._layout
|
|
247
|
+
|
|
248
|
+
def render(self, size: tuple[int] | tuple[()], focus: bool = False) -> TextCanvas:
|
|
249
|
+
"""
|
|
250
|
+
Render contents with wrapping and alignment. Return canvas.
|
|
251
|
+
|
|
252
|
+
See :meth:`Widget.render` for parameter details.
|
|
253
|
+
|
|
254
|
+
>>> Text(u"important things").render((18,)).text
|
|
255
|
+
[b'important things ']
|
|
256
|
+
>>> Text(u"important things").render((11,)).text
|
|
257
|
+
[b'important ', b'things ']
|
|
258
|
+
>>> Text("demo text").render(()).text
|
|
259
|
+
[b'demo text']
|
|
260
|
+
"""
|
|
261
|
+
text, attr = self.get_text()
|
|
262
|
+
if size:
|
|
263
|
+
(maxcol,) = size
|
|
264
|
+
else:
|
|
265
|
+
maxcol, _ = self.pack(focus=focus)
|
|
266
|
+
|
|
267
|
+
trans = self.get_line_translation(maxcol, (text, attr))
|
|
268
|
+
return apply_text_layout(text, attr, trans, maxcol)
|
|
269
|
+
|
|
270
|
+
def rows(self, size: tuple[int], focus: bool = False) -> int:
|
|
271
|
+
"""
|
|
272
|
+
Return the number of rows the rendered text requires.
|
|
273
|
+
|
|
274
|
+
See :meth:`Widget.rows` for parameter details.
|
|
275
|
+
|
|
276
|
+
>>> Text(u"important things").rows((18,))
|
|
277
|
+
1
|
|
278
|
+
>>> Text(u"important things").rows((11,))
|
|
279
|
+
2
|
|
280
|
+
"""
|
|
281
|
+
(maxcol,) = size
|
|
282
|
+
return len(self.get_line_translation(maxcol))
|
|
283
|
+
|
|
284
|
+
def get_line_translation(
|
|
285
|
+
self,
|
|
286
|
+
maxcol: int,
|
|
287
|
+
ta: tuple[str | bytes, list[tuple[Hashable, int]]] | None = None,
|
|
288
|
+
) -> list[list[tuple[int, int, int | bytes] | tuple[int, int | None]]]:
|
|
289
|
+
"""
|
|
290
|
+
Return layout structure used to map self.text to a canvas.
|
|
291
|
+
This method is used internally, but may be useful for debugging custom layout classes.
|
|
292
|
+
|
|
293
|
+
:param maxcol: columns available for display
|
|
294
|
+
:type maxcol: int
|
|
295
|
+
:param ta: ``None`` or the (*text*, *display attributes*) tuple
|
|
296
|
+
returned from :meth:`.get_text`
|
|
297
|
+
:type ta: text and display attributes
|
|
298
|
+
"""
|
|
299
|
+
if not self._cache_maxcol or self._cache_maxcol != maxcol:
|
|
300
|
+
self._update_cache_translation(maxcol, ta)
|
|
301
|
+
return self._cache_translation
|
|
302
|
+
|
|
303
|
+
def _update_cache_translation(
|
|
304
|
+
self,
|
|
305
|
+
maxcol: int,
|
|
306
|
+
ta: tuple[str | bytes, list[tuple[Hashable, int]]] | None,
|
|
307
|
+
) -> None:
|
|
308
|
+
if ta:
|
|
309
|
+
text, _attr = ta
|
|
310
|
+
else:
|
|
311
|
+
text, _attr = self.get_text()
|
|
312
|
+
self._cache_maxcol = maxcol
|
|
313
|
+
self._cache_translation = self.layout.layout(text, maxcol, self._align_mode, self._wrap_mode)
|
|
314
|
+
|
|
315
|
+
def pack(self, size: tuple[int] | tuple[()] | None = None, focus: bool = False) -> tuple[int, int]:
|
|
316
|
+
"""
|
|
317
|
+
Return the number of screen columns and rows required for
|
|
318
|
+
this Text widget to be displayed without wrapping or
|
|
319
|
+
clipping, as a single element tuple.
|
|
320
|
+
|
|
321
|
+
:param size: ``None`` or ``()`` for unlimited screen columns (like FIXED sizing)
|
|
322
|
+
or (*maxcol*,) to specify a maximum column size
|
|
323
|
+
:type size: widget size
|
|
324
|
+
:param focus: widget is focused on
|
|
325
|
+
:type focus: bool
|
|
326
|
+
|
|
327
|
+
>>> Text(u"important things").pack()
|
|
328
|
+
(16, 1)
|
|
329
|
+
>>> Text(u"important things").pack((15,))
|
|
330
|
+
(9, 2)
|
|
331
|
+
>>> Text(u"important things").pack((8,))
|
|
332
|
+
(8, 2)
|
|
333
|
+
>>> Text(u"important things").pack(())
|
|
334
|
+
(16, 1)
|
|
335
|
+
"""
|
|
336
|
+
text, attr = self.get_text()
|
|
337
|
+
|
|
338
|
+
if size:
|
|
339
|
+
(maxcol,) = size
|
|
340
|
+
if not hasattr(self.layout, "pack"):
|
|
341
|
+
return size
|
|
342
|
+
trans = self.get_line_translation(maxcol, (text, attr))
|
|
343
|
+
cols = self.layout.pack(maxcol, trans)
|
|
344
|
+
return (cols, len(trans))
|
|
345
|
+
|
|
346
|
+
if text:
|
|
347
|
+
if isinstance(text, bytes):
|
|
348
|
+
text = text.decode(get_encoding())
|
|
349
|
+
|
|
350
|
+
return (
|
|
351
|
+
max(calc_width(line, 0, len(line)) for line in text.splitlines(keepends=False)),
|
|
352
|
+
text.count("\n") + 1,
|
|
353
|
+
)
|
|
354
|
+
return 0, 1
|