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/util.py
ADDED
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
# Urwid utility functions
|
|
2
|
+
# Copyright (C) 2004-2011 Ian Ward
|
|
3
|
+
#
|
|
4
|
+
# This library is free software; you can redistribute it and/or
|
|
5
|
+
# modify it under the terms of the GNU Lesser General Public
|
|
6
|
+
# License as published by the Free Software Foundation; either
|
|
7
|
+
# version 2.1 of the License, or (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This library is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
12
|
+
# Lesser General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU Lesser General Public
|
|
15
|
+
# License along with this library; if not, write to the Free Software
|
|
16
|
+
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
17
|
+
#
|
|
18
|
+
# Urwid web site: https://urwid.org/
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import codecs
|
|
24
|
+
import contextlib
|
|
25
|
+
import sys
|
|
26
|
+
import typing
|
|
27
|
+
import warnings
|
|
28
|
+
from contextlib import suppress
|
|
29
|
+
|
|
30
|
+
from urwid import str_util
|
|
31
|
+
|
|
32
|
+
if typing.TYPE_CHECKING:
|
|
33
|
+
from collections.abc import Generator, Iterable
|
|
34
|
+
from types import TracebackType
|
|
35
|
+
|
|
36
|
+
from typing_extensions import Literal, Protocol, Self
|
|
37
|
+
|
|
38
|
+
class CanBeStopped(Protocol):
|
|
39
|
+
def stop(self) -> None: ...
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def __getattr__(name: str) -> typing.Any:
|
|
43
|
+
if hasattr(str_util, name):
|
|
44
|
+
warnings.warn(
|
|
45
|
+
f"Do not import {name!r} from {__package__}.{__name__}, import it from 'urwid'.",
|
|
46
|
+
DeprecationWarning,
|
|
47
|
+
stacklevel=3,
|
|
48
|
+
)
|
|
49
|
+
return getattr(str_util, name)
|
|
50
|
+
raise AttributeError(f"{name} is not defined in {__package__}.{__name__}")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def detect_encoding() -> str:
|
|
54
|
+
# Windows is a special case:
|
|
55
|
+
# CMD is Unicode and non-unicode at the same time:
|
|
56
|
+
# Unicode display support depends on C API usage and partially limited by font settings.
|
|
57
|
+
# By default, python is distributed in "Unicode" version, and since Python version 3.8 only Unicode.
|
|
58
|
+
# In case of curses, "windows-curses" is distributed in unicode-only.
|
|
59
|
+
# Since Windows 10 default console font is already unicode,
|
|
60
|
+
# in older versions maybe need to set TTF font manually to support more symbols
|
|
61
|
+
# (this does not affect unicode IO).
|
|
62
|
+
if sys.platform == "win32" and sys.getdefaultencoding() == "utf-8":
|
|
63
|
+
return "utf-8"
|
|
64
|
+
# Try to determine if using a supported double-byte encoding
|
|
65
|
+
import locale
|
|
66
|
+
|
|
67
|
+
no_set_locale = locale.getpreferredencoding(False)
|
|
68
|
+
|
|
69
|
+
if no_set_locale != "ascii":
|
|
70
|
+
# ascii is fallback locale in case of detect failed
|
|
71
|
+
|
|
72
|
+
return no_set_locale
|
|
73
|
+
|
|
74
|
+
# Use actual `getpreferredencoding` with public API only
|
|
75
|
+
old_loc = locale.setlocale(locale.LC_CTYPE) # == getlocale, but not mangle data
|
|
76
|
+
try:
|
|
77
|
+
with suppress(locale.Error):
|
|
78
|
+
locale.setlocale(locale.LC_CTYPE, "")
|
|
79
|
+
# internally call private `_get_locale_encoding`
|
|
80
|
+
return locale.getpreferredencoding(False)
|
|
81
|
+
finally:
|
|
82
|
+
with suppress(locale.Error):
|
|
83
|
+
locale.setlocale(locale.LC_CTYPE, old_loc)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
if "detected_encoding" not in locals():
|
|
87
|
+
detected_encoding = detect_encoding()
|
|
88
|
+
else:
|
|
89
|
+
raise RuntimeError("Encoding detection broken")
|
|
90
|
+
|
|
91
|
+
_target_encoding = "ascii"
|
|
92
|
+
_use_dec_special = True
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def set_encoding(encoding: str) -> None:
|
|
96
|
+
"""
|
|
97
|
+
Set the byte encoding to assume when processing strings and the
|
|
98
|
+
encoding to use when converting unicode strings.
|
|
99
|
+
"""
|
|
100
|
+
encoding = encoding.lower()
|
|
101
|
+
|
|
102
|
+
global _target_encoding, _use_dec_special # noqa: PLW0603 # noqa: PLW0603 # pylint: disable=global-statement
|
|
103
|
+
|
|
104
|
+
if encoding in {"utf-8", "utf8", "utf"}:
|
|
105
|
+
str_util.set_byte_encoding("utf8")
|
|
106
|
+
|
|
107
|
+
_use_dec_special = False
|
|
108
|
+
elif encoding in {
|
|
109
|
+
"euc-jp", # JISX 0208 only
|
|
110
|
+
"euc-kr",
|
|
111
|
+
"euc-cn",
|
|
112
|
+
"euc-tw", # CNS 11643 plain 1 only
|
|
113
|
+
"gb2312",
|
|
114
|
+
"gbk",
|
|
115
|
+
"big5",
|
|
116
|
+
"cn-gb",
|
|
117
|
+
"uhc",
|
|
118
|
+
# these shouldn't happen, should they?
|
|
119
|
+
"eucjp",
|
|
120
|
+
"euckr",
|
|
121
|
+
"euccn",
|
|
122
|
+
"euctw",
|
|
123
|
+
"cncb",
|
|
124
|
+
}:
|
|
125
|
+
str_util.set_byte_encoding("wide")
|
|
126
|
+
|
|
127
|
+
_use_dec_special = True
|
|
128
|
+
else:
|
|
129
|
+
str_util.set_byte_encoding("narrow")
|
|
130
|
+
_use_dec_special = True
|
|
131
|
+
|
|
132
|
+
# if encoding is valid for conversion from unicode, remember it
|
|
133
|
+
_target_encoding = "ascii"
|
|
134
|
+
with contextlib.suppress(LookupError):
|
|
135
|
+
if encoding:
|
|
136
|
+
"".encode(encoding)
|
|
137
|
+
_target_encoding = encoding
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def get_encoding() -> str:
|
|
141
|
+
"""Get target encoding."""
|
|
142
|
+
return _target_encoding
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@contextlib.contextmanager
|
|
146
|
+
def set_temporary_encoding(encoding_name: str) -> Generator[None, None, None]:
|
|
147
|
+
"""Internal helper for encoding specific validation in unittests/doctests.
|
|
148
|
+
|
|
149
|
+
Not exported globally.
|
|
150
|
+
"""
|
|
151
|
+
old_encoding = _target_encoding
|
|
152
|
+
try:
|
|
153
|
+
set_encoding(encoding_name)
|
|
154
|
+
yield
|
|
155
|
+
finally:
|
|
156
|
+
set_encoding(old_encoding)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def get_encoding_mode() -> Literal["wide", "narrow", "utf8"]:
|
|
160
|
+
"""
|
|
161
|
+
Get the mode Urwid is using when processing text strings.
|
|
162
|
+
Returns 'narrow' for 8-bit encodings, 'wide' for CJK encodings
|
|
163
|
+
or 'utf8' for UTF-8 encodings.
|
|
164
|
+
"""
|
|
165
|
+
return str_util.get_byte_encoding()
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def apply_target_encoding(s: str | bytes) -> tuple[bytes, list[tuple[Literal["U", "0"] | None, int]]]:
|
|
169
|
+
"""
|
|
170
|
+
Return (encoded byte string, character set rle).
|
|
171
|
+
"""
|
|
172
|
+
# Import locally to warranty no circular imports
|
|
173
|
+
from urwid.display import escape
|
|
174
|
+
|
|
175
|
+
if _use_dec_special and isinstance(s, str):
|
|
176
|
+
# first convert drawing characters
|
|
177
|
+
s = s.translate(escape.DEC_SPECIAL_CHARMAP)
|
|
178
|
+
|
|
179
|
+
if isinstance(s, str):
|
|
180
|
+
s = s.replace(escape.SI + escape.SO, "") # remove redundant shifts
|
|
181
|
+
s = codecs.encode(s, _target_encoding, "replace")
|
|
182
|
+
|
|
183
|
+
if not isinstance(s, bytes):
|
|
184
|
+
raise TypeError(s)
|
|
185
|
+
SO = escape.SO.encode("ascii")
|
|
186
|
+
SI = escape.SI.encode("ascii")
|
|
187
|
+
|
|
188
|
+
sis = s.split(SO)
|
|
189
|
+
|
|
190
|
+
sis0 = sis[0].replace(SI, b"")
|
|
191
|
+
sout = []
|
|
192
|
+
cout = []
|
|
193
|
+
if sis0:
|
|
194
|
+
sout.append(sis0)
|
|
195
|
+
cout.append((None, len(sis0)))
|
|
196
|
+
|
|
197
|
+
if len(sis) == 1:
|
|
198
|
+
return sis0, cout
|
|
199
|
+
|
|
200
|
+
for sn in sis[1:]:
|
|
201
|
+
sl = sn.split(SI, 1)
|
|
202
|
+
if len(sl) == 1:
|
|
203
|
+
sin = sl[0]
|
|
204
|
+
sout.append(sin)
|
|
205
|
+
rle_append_modify(cout, (escape.DEC_TAG, len(sin)))
|
|
206
|
+
continue
|
|
207
|
+
|
|
208
|
+
sin, son = sl
|
|
209
|
+
son = son.replace(SI, b"")
|
|
210
|
+
if sin:
|
|
211
|
+
sout.append(sin)
|
|
212
|
+
rle_append_modify(cout, (escape.DEC_TAG, len(sin)))
|
|
213
|
+
|
|
214
|
+
if son:
|
|
215
|
+
sout.append(son)
|
|
216
|
+
rle_append_modify(cout, (None, len(son)))
|
|
217
|
+
|
|
218
|
+
outstr = b"".join(sout)
|
|
219
|
+
return outstr, cout
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
######################################################################
|
|
223
|
+
# Try to set the encoding using the one detected by the locale module
|
|
224
|
+
set_encoding(detected_encoding)
|
|
225
|
+
######################################################################
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def supports_unicode() -> bool:
|
|
229
|
+
"""
|
|
230
|
+
Return True if python is able to convert non-ascii unicode strings
|
|
231
|
+
to the current encoding.
|
|
232
|
+
"""
|
|
233
|
+
return _target_encoding and _target_encoding != "ascii"
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def calc_trim_text(
|
|
237
|
+
text: str | bytes,
|
|
238
|
+
start_offs: int,
|
|
239
|
+
end_offs: int,
|
|
240
|
+
start_col: int,
|
|
241
|
+
end_col: int,
|
|
242
|
+
) -> tuple[int, int, int, int]:
|
|
243
|
+
"""
|
|
244
|
+
Calculate the result of trimming text.
|
|
245
|
+
start_offs -- offset into text to treat as screen column 0
|
|
246
|
+
end_offs -- offset into text to treat as the end of the line
|
|
247
|
+
start_col -- screen column to trim at the left
|
|
248
|
+
end_col -- screen column to trim at the right
|
|
249
|
+
|
|
250
|
+
Returns (start, end, pad_left, pad_right), where:
|
|
251
|
+
start -- resulting start offset
|
|
252
|
+
end -- resulting end offset
|
|
253
|
+
pad_left -- 0 for no pad or 1 for one space to be added
|
|
254
|
+
pad_right -- 0 for no pad or 1 for one space to be added
|
|
255
|
+
"""
|
|
256
|
+
spos = start_offs
|
|
257
|
+
pad_left = pad_right = 0
|
|
258
|
+
if start_col > 0:
|
|
259
|
+
spos, sc = str_util.calc_text_pos(text, spos, end_offs, start_col)
|
|
260
|
+
if sc < start_col:
|
|
261
|
+
pad_left = 1
|
|
262
|
+
spos, sc = str_util.calc_text_pos(text, start_offs, end_offs, start_col + 1)
|
|
263
|
+
run = end_col - start_col - pad_left
|
|
264
|
+
pos, sc = str_util.calc_text_pos(text, spos, end_offs, run)
|
|
265
|
+
if sc < run:
|
|
266
|
+
pad_right = 1
|
|
267
|
+
return (spos, pos, pad_left, pad_right)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def trim_text_attr_cs(text: bytes, attr, cs, start_col: int, end_col: int):
|
|
271
|
+
"""
|
|
272
|
+
Return ( trimmed text, trimmed attr, trimmed cs ).
|
|
273
|
+
"""
|
|
274
|
+
spos, epos, pad_left, pad_right = calc_trim_text(text, 0, len(text), start_col, end_col)
|
|
275
|
+
attrtr = rle_subseg(attr, spos, epos)
|
|
276
|
+
cstr = rle_subseg(cs, spos, epos)
|
|
277
|
+
if pad_left:
|
|
278
|
+
al = rle_get_at(attr, spos - 1)
|
|
279
|
+
rle_prepend_modify(attrtr, (al, 1))
|
|
280
|
+
rle_prepend_modify(cstr, (None, 1))
|
|
281
|
+
if pad_right:
|
|
282
|
+
al = rle_get_at(attr, epos)
|
|
283
|
+
rle_append_modify(attrtr, (al, 1))
|
|
284
|
+
rle_append_modify(cstr, (None, 1))
|
|
285
|
+
|
|
286
|
+
return (b"".rjust(pad_left) + text[spos:epos] + b"".rjust(pad_right), attrtr, cstr)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def rle_get_at(rle, pos: int):
|
|
290
|
+
"""
|
|
291
|
+
Return the attribute at offset pos.
|
|
292
|
+
"""
|
|
293
|
+
x = 0
|
|
294
|
+
if pos < 0:
|
|
295
|
+
return None
|
|
296
|
+
for a, run in rle:
|
|
297
|
+
if x + run > pos:
|
|
298
|
+
return a
|
|
299
|
+
x += run
|
|
300
|
+
return None
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def rle_subseg(rle, start: int, end: int):
|
|
304
|
+
"""Return a sub segment of a rle list."""
|
|
305
|
+
sub_segment = []
|
|
306
|
+
x = 0
|
|
307
|
+
for a, run in rle:
|
|
308
|
+
if start:
|
|
309
|
+
if start >= run:
|
|
310
|
+
start -= run
|
|
311
|
+
x += run
|
|
312
|
+
continue
|
|
313
|
+
x += start
|
|
314
|
+
run -= start # noqa: PLW2901
|
|
315
|
+
start = 0
|
|
316
|
+
if x >= end:
|
|
317
|
+
break
|
|
318
|
+
if x + run > end:
|
|
319
|
+
run = end - x # noqa: PLW2901
|
|
320
|
+
x += run
|
|
321
|
+
sub_segment.append((a, run))
|
|
322
|
+
return sub_segment
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def rle_len(rle: Iterable[tuple[typing.Any, int]]) -> int:
|
|
326
|
+
"""
|
|
327
|
+
Return the number of characters covered by a run length
|
|
328
|
+
encoded attribute list.
|
|
329
|
+
"""
|
|
330
|
+
|
|
331
|
+
run = 0
|
|
332
|
+
for v in rle:
|
|
333
|
+
if not isinstance(v, tuple):
|
|
334
|
+
raise TypeError(rle)
|
|
335
|
+
_a, r = v
|
|
336
|
+
run += r
|
|
337
|
+
return run
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def rle_prepend_modify(rle, a_r) -> None:
|
|
341
|
+
"""
|
|
342
|
+
Append (a, r) (unpacked from *a_r*) to BEGINNING of rle.
|
|
343
|
+
Merge with first run when possible
|
|
344
|
+
|
|
345
|
+
MODIFIES rle parameter contents. Returns None.
|
|
346
|
+
"""
|
|
347
|
+
a, r = a_r
|
|
348
|
+
if not rle:
|
|
349
|
+
rle[:] = [(a, r)]
|
|
350
|
+
else:
|
|
351
|
+
al, run = rle[0]
|
|
352
|
+
if a == al:
|
|
353
|
+
rle[0] = (a, run + r)
|
|
354
|
+
else:
|
|
355
|
+
rle[0:0] = [(a, r)]
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def rle_append_modify(rle, a_r) -> None:
|
|
359
|
+
"""
|
|
360
|
+
Append (a, r) (unpacked from *a_r*) to the rle list rle.
|
|
361
|
+
Merge with last run when possible.
|
|
362
|
+
|
|
363
|
+
MODIFIES rle parameter contents. Returns None.
|
|
364
|
+
"""
|
|
365
|
+
a, r = a_r
|
|
366
|
+
if not rle or rle[-1][0] != a:
|
|
367
|
+
rle.append((a, r))
|
|
368
|
+
return
|
|
369
|
+
_la, lr = rle[-1]
|
|
370
|
+
rle[-1] = (a, lr + r)
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def rle_join_modify(rle, rle2) -> None:
|
|
374
|
+
"""
|
|
375
|
+
Append attribute list rle2 to rle.
|
|
376
|
+
Merge last run of rle with first run of rle2 when possible.
|
|
377
|
+
|
|
378
|
+
MODIFIES attr parameter contents. Returns None.
|
|
379
|
+
"""
|
|
380
|
+
if not rle2:
|
|
381
|
+
return
|
|
382
|
+
rle_append_modify(rle, rle2[0])
|
|
383
|
+
rle += rle2[1:]
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def rle_product(rle1, rle2):
|
|
387
|
+
"""
|
|
388
|
+
Merge the runs of rle1 and rle2 like this:
|
|
389
|
+
eg.
|
|
390
|
+
rle1 = [ ("a", 10), ("b", 5) ]
|
|
391
|
+
rle2 = [ ("Q", 5), ("P", 10) ]
|
|
392
|
+
rle_product: [ (("a","Q"), 5), (("a","P"), 5), (("b","P"), 5) ]
|
|
393
|
+
|
|
394
|
+
rle1 and rle2 are assumed to cover the same total run.
|
|
395
|
+
"""
|
|
396
|
+
i1 = i2 = 1 # rle1, rle2 indexes
|
|
397
|
+
if not rle1 or not rle2:
|
|
398
|
+
return []
|
|
399
|
+
a1, r1 = rle1[0]
|
|
400
|
+
a2, r2 = rle2[0]
|
|
401
|
+
|
|
402
|
+
result = []
|
|
403
|
+
while r1 and r2:
|
|
404
|
+
r = min(r1, r2)
|
|
405
|
+
rle_append_modify(result, ((a1, a2), r))
|
|
406
|
+
r1 -= r
|
|
407
|
+
if r1 == 0 and i1 < len(rle1):
|
|
408
|
+
a1, r1 = rle1[i1]
|
|
409
|
+
i1 += 1
|
|
410
|
+
r2 -= r
|
|
411
|
+
if r2 == 0 and i2 < len(rle2):
|
|
412
|
+
a2, r2 = rle2[i2]
|
|
413
|
+
i2 += 1
|
|
414
|
+
return result
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def rle_factor(rle):
|
|
418
|
+
"""
|
|
419
|
+
Inverse of rle_product.
|
|
420
|
+
"""
|
|
421
|
+
rle1 = []
|
|
422
|
+
rle2 = []
|
|
423
|
+
for (a1, a2), r in rle:
|
|
424
|
+
rle_append_modify(rle1, (a1, r))
|
|
425
|
+
rle_append_modify(rle2, (a2, r))
|
|
426
|
+
return rle1, rle2
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
class TagMarkupException(Exception):
|
|
430
|
+
pass
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def decompose_tagmarkup(tm):
|
|
434
|
+
"""Return (text string, attribute list) for tagmarkup passed."""
|
|
435
|
+
|
|
436
|
+
tl, al = _tagmarkup_recurse(tm, None)
|
|
437
|
+
# join as unicode or bytes based on type of first element
|
|
438
|
+
if tl:
|
|
439
|
+
text = tl[0][:0].join(tl)
|
|
440
|
+
else:
|
|
441
|
+
text = ""
|
|
442
|
+
|
|
443
|
+
if al and al[-1][0] is None:
|
|
444
|
+
del al[-1]
|
|
445
|
+
|
|
446
|
+
return text, al
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def _tagmarkup_recurse(tm, attr):
|
|
450
|
+
"""Return (text list, attribute list) for tagmarkup passed.
|
|
451
|
+
|
|
452
|
+
tm -- tagmarkup
|
|
453
|
+
attr -- current attribute or None"""
|
|
454
|
+
|
|
455
|
+
if isinstance(tm, list):
|
|
456
|
+
# for lists recurse to process each subelement
|
|
457
|
+
rtl = []
|
|
458
|
+
ral = []
|
|
459
|
+
for element in tm:
|
|
460
|
+
tl, al = _tagmarkup_recurse(element, attr)
|
|
461
|
+
if ral:
|
|
462
|
+
# merge attributes when possible
|
|
463
|
+
last_attr, last_run = ral[-1]
|
|
464
|
+
top_attr, top_run = al[0]
|
|
465
|
+
if last_attr == top_attr:
|
|
466
|
+
ral[-1] = (top_attr, last_run + top_run)
|
|
467
|
+
del al[0]
|
|
468
|
+
rtl += tl
|
|
469
|
+
ral += al
|
|
470
|
+
return rtl, ral
|
|
471
|
+
|
|
472
|
+
if isinstance(tm, tuple):
|
|
473
|
+
# tuples mark a new attribute boundary
|
|
474
|
+
if len(tm) != 2:
|
|
475
|
+
raise TagMarkupException(f"Tuples must be in the form (attribute, tagmarkup): {tm!r}")
|
|
476
|
+
|
|
477
|
+
attr, element = tm
|
|
478
|
+
return _tagmarkup_recurse(element, attr)
|
|
479
|
+
|
|
480
|
+
if not isinstance(tm, (str, bytes)):
|
|
481
|
+
raise TagMarkupException(f"Invalid markup element: {tm!r}")
|
|
482
|
+
|
|
483
|
+
# text
|
|
484
|
+
return [tm], [(attr, len(tm))]
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def is_mouse_event(ev: tuple[str, int, int, int] | typing.Any) -> bool:
|
|
488
|
+
return isinstance(ev, tuple) and len(ev) == 4 and "mouse" in ev[0]
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
def is_mouse_press(ev: str) -> bool:
|
|
492
|
+
return "press" in ev
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
class MetaSuper(type):
|
|
496
|
+
"""adding .__super"""
|
|
497
|
+
|
|
498
|
+
def __init__(cls, name: str, bases, d):
|
|
499
|
+
super().__init__(name, bases, d)
|
|
500
|
+
if hasattr(cls, f"_{name}__super"):
|
|
501
|
+
raise AttributeError("Class has same name as one of its super classes")
|
|
502
|
+
|
|
503
|
+
@property
|
|
504
|
+
def _super(self):
|
|
505
|
+
warnings.warn(
|
|
506
|
+
f"`{name}.__super` was a deprecated feature for old python versions."
|
|
507
|
+
f"Please use `super()` call instead.",
|
|
508
|
+
DeprecationWarning,
|
|
509
|
+
stacklevel=3,
|
|
510
|
+
)
|
|
511
|
+
return super(cls, self)
|
|
512
|
+
|
|
513
|
+
setattr(cls, f"_{name}__super", _super)
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
def int_scale(val: int, val_range: int, out_range: int) -> int:
|
|
517
|
+
"""
|
|
518
|
+
Scale val in the range [0, val_range-1] to an integer in the range
|
|
519
|
+
[0, out_range-1]. This implementation uses the "round-half-up" rounding
|
|
520
|
+
method.
|
|
521
|
+
|
|
522
|
+
>>> "%x" % int_scale(0x7, 0x10, 0x10000)
|
|
523
|
+
'7777'
|
|
524
|
+
>>> "%x" % int_scale(0x5f, 0x100, 0x10)
|
|
525
|
+
'6'
|
|
526
|
+
>>> int_scale(2, 6, 101)
|
|
527
|
+
40
|
|
528
|
+
>>> int_scale(1, 3, 4)
|
|
529
|
+
2
|
|
530
|
+
"""
|
|
531
|
+
num = int(val * (out_range - 1) * 2 + (val_range - 1))
|
|
532
|
+
dem = (val_range - 1) * 2
|
|
533
|
+
# if num % dem == 0 then we are exactly half-way and have rounded up.
|
|
534
|
+
return num // dem
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
class StoppingContext(typing.ContextManager["StoppingContext"]):
|
|
538
|
+
"""Context manager that calls ``stop`` on a given object on exit. Used to
|
|
539
|
+
make the ``start`` method on `MainLoop` and `BaseScreen` optionally act as
|
|
540
|
+
context managers.
|
|
541
|
+
"""
|
|
542
|
+
|
|
543
|
+
__slots__ = ("_wrapped",)
|
|
544
|
+
|
|
545
|
+
def __init__(self, wrapped: CanBeStopped) -> None:
|
|
546
|
+
self._wrapped = wrapped
|
|
547
|
+
|
|
548
|
+
def __enter__(self) -> Self:
|
|
549
|
+
return self
|
|
550
|
+
|
|
551
|
+
def __exit__(
|
|
552
|
+
self,
|
|
553
|
+
exc_type: type[BaseException] | None,
|
|
554
|
+
exc_val: BaseException | None,
|
|
555
|
+
exc_tb: TracebackType | None,
|
|
556
|
+
) -> None:
|
|
557
|
+
self._wrapped.stop()
|
urwid/version.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# file generated by setuptools_scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
TYPE_CHECKING = False
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from typing import Tuple, Union
|
|
6
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
7
|
+
else:
|
|
8
|
+
VERSION_TUPLE = object
|
|
9
|
+
|
|
10
|
+
version: str
|
|
11
|
+
__version__: str
|
|
12
|
+
__version_tuple__: VERSION_TUPLE
|
|
13
|
+
version_tuple: VERSION_TUPLE
|
|
14
|
+
|
|
15
|
+
__version__ = version = '2.6.0.post0'
|
|
16
|
+
__version_tuple__ = version_tuple = (2, 6, 0)
|