urwid 2.6.15__py3-none-any.whl → 3.0.0__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.

Files changed (54) hide show
  1. urwid/__init__.py +1 -4
  2. urwid/canvas.py +27 -46
  3. urwid/command_map.py +6 -4
  4. urwid/container.py +1 -1
  5. urwid/decoration.py +1 -1
  6. urwid/display/_posix_raw_display.py +16 -4
  7. urwid/display/_raw_display_base.py +8 -5
  8. urwid/display/_win32_raw_display.py +16 -17
  9. urwid/display/common.py +26 -55
  10. urwid/display/curses.py +1 -1
  11. urwid/display/escape.py +15 -12
  12. urwid/display/lcd.py +4 -6
  13. urwid/display/web.py +7 -12
  14. urwid/event_loop/asyncio_loop.py +1 -2
  15. urwid/event_loop/main_loop.py +13 -18
  16. urwid/event_loop/tornado_loop.py +4 -5
  17. urwid/event_loop/trio_loop.py +1 -1
  18. urwid/font.py +13 -18
  19. urwid/signals.py +2 -1
  20. urwid/str_util.py +15 -18
  21. urwid/text_layout.py +6 -7
  22. urwid/util.py +7 -18
  23. urwid/version.py +9 -4
  24. urwid/vterm.py +18 -45
  25. urwid/widget/__init__.py +0 -6
  26. urwid/widget/attr_wrap.py +8 -10
  27. urwid/widget/bar_graph.py +3 -8
  28. urwid/widget/big_text.py +9 -7
  29. urwid/widget/box_adapter.py +4 -4
  30. urwid/widget/columns.py +52 -83
  31. urwid/widget/container.py +29 -75
  32. urwid/widget/edit.py +8 -8
  33. urwid/widget/filler.py +6 -6
  34. urwid/widget/frame.py +28 -37
  35. urwid/widget/grid_flow.py +25 -110
  36. urwid/widget/line_box.py +13 -0
  37. urwid/widget/listbox.py +12 -50
  38. urwid/widget/monitored_list.py +6 -4
  39. urwid/widget/overlay.py +4 -37
  40. urwid/widget/padding.py +11 -48
  41. urwid/widget/pile.py +179 -158
  42. urwid/widget/popup.py +2 -2
  43. urwid/widget/progress_bar.py +10 -11
  44. urwid/widget/scrollable.py +25 -33
  45. urwid/widget/treetools.py +27 -48
  46. urwid/widget/widget.py +7 -124
  47. urwid/widget/widget_decoration.py +4 -33
  48. urwid/wimp.py +1 -1
  49. {urwid-2.6.15.dist-info → urwid-3.0.0.dist-info}/METADATA +18 -18
  50. urwid-3.0.0.dist-info/RECORD +74 -0
  51. {urwid-2.6.15.dist-info → urwid-3.0.0.dist-info}/WHEEL +1 -1
  52. urwid-2.6.15.dist-info/RECORD +0 -74
  53. {urwid-2.6.15.dist-info → urwid-3.0.0.dist-info/licenses}/COPYING +0 -0
  54. {urwid-2.6.15.dist-info → urwid-3.0.0.dist-info}/top_level.txt +0 -0
urwid/display/escape.py CHANGED
@@ -32,7 +32,7 @@ from collections.abc import MutableMapping, Sequence
32
32
  from urwid import str_util
33
33
 
34
34
  if typing.TYPE_CHECKING:
35
- from collections.abc import Collection, Iterable
35
+ from collections.abc import Iterable
36
36
 
37
37
  # NOTE: because of circular imports (urwid.util -> urwid.escape -> urwid.util)
38
38
  # from urwid.util import is_mouse_event -- will not work here
@@ -85,6 +85,8 @@ input_sequences = [
85
85
  ("[F", "end"),
86
86
  ("[G", "5"),
87
87
  ("[H", "home"),
88
+ ("[I", "focus in"),
89
+ ("[O", "focus out"),
88
90
  ("[1~", "home"),
89
91
  ("[2~", "insert"),
90
92
  ("[3~", "delete"),
@@ -214,10 +216,10 @@ class KeyqueueTrie:
214
216
  root[ord(s)] = result
215
217
 
216
218
  def get(self, keys, more_available: bool):
217
- result = self.get_recurse(self.data, keys, more_available)
218
- if not result:
219
- result = self.read_cursor_position(keys, more_available)
220
- return result
219
+ if result := self.get_recurse(self.data, keys, more_available):
220
+ return result
221
+
222
+ return self.read_cursor_position(keys, more_available)
221
223
 
222
224
  def get_recurse(
223
225
  self,
@@ -225,7 +227,7 @@ class KeyqueueTrie:
225
227
  MutableMapping[int, str | MutableMapping[int, str | MutableMapping[int, str]]]
226
228
  | typing.Literal["mouse", "sgrmouse"]
227
229
  ),
228
- keys: Collection[int],
230
+ keys: Sequence[int],
229
231
  more_available: bool,
230
232
  ):
231
233
  if not isinstance(root, MutableMapping):
@@ -245,7 +247,7 @@ class KeyqueueTrie:
245
247
  return None
246
248
  return self.get_recurse(root[keys[0]], keys[1:], more_available)
247
249
 
248
- def read_mouse_info(self, keys: Collection[int], more_available: bool):
250
+ def read_mouse_info(self, keys: Sequence[int], more_available: bool):
249
251
  if len(keys) < 3:
250
252
  if more_available:
251
253
  raise MoreInputRequired()
@@ -284,7 +286,7 @@ class KeyqueueTrie:
284
286
 
285
287
  return ((f"{prefix}mouse {action}", button, x, y), keys[3:])
286
288
 
287
- def read_sgrmouse_info(self, keys: Collection[int], more_available: bool):
289
+ def read_sgrmouse_info(self, keys: Sequence[int], more_available: bool):
288
290
  # Helpful links:
289
291
  # https://stackoverflow.com/questions/5966903/how-to-get-mousemove-and-mouseclick-in-bash
290
292
  # http://invisible-island.net/xterm/ctlseqs/ctlseqs.pdf
@@ -538,9 +540,7 @@ def process_keyqueue(codes: Sequence[int], more_available: bool) -> tuple[list[s
538
540
  if code != 27:
539
541
  return [f"<{code:d}>"], codes[1:]
540
542
 
541
- result = input_trie.get(codes[1:], more_available)
542
-
543
- if result is not None:
543
+ if (result := input_trie.get(codes[1:], more_available)) is not None:
544
544
  result, remaining_codes = result
545
545
  return [result], remaining_codes
546
546
 
@@ -551,7 +551,7 @@ def process_keyqueue(codes: Sequence[int], more_available: bool) -> tuple[list[s
551
551
  return ["esc", *run], remaining_codes
552
552
  if run[0] == "esc" or run[0].find("meta ") >= 0:
553
553
  return ["esc", *run], remaining_codes
554
- return [f"meta {run[0]}"] + run[1:], remaining_codes
554
+ return [f"meta {run[0]}", *run[1:]], remaining_codes
555
555
 
556
556
  return ["esc"], codes[1:]
557
557
 
@@ -574,6 +574,9 @@ RESTORE_NORMAL_BUFFER = f"{ESC}[?1049l"
574
574
  ENABLE_BRACKETED_PASTE_MODE = f"{ESC}[?2004h"
575
575
  DISABLE_BRACKETED_PASTE_MODE = f"{ESC}[?2004l"
576
576
 
577
+ ENABLE_FOCUS_REPORTING = f"{ESC}[?1004h"
578
+ DISABLE_FOCUS_REPORTING = f"{ESC}[?1004l"
579
+
577
580
  # RESET_SCROLL_REGION = ESC+"[;r"
578
581
  # RESET = ESC+"c"
579
582
 
urwid/display/lcd.py CHANGED
@@ -218,10 +218,9 @@ class CFLCDScreen(LCDScreen, abc.ABC):
218
218
  raise cls.MoreDataRequired
219
219
 
220
220
  data_end = 2 + packet_len
221
- crc = cls.get_crc(data[:data_end])
222
- pcrc = data[data_end : data_end + 2]
223
- if crc != pcrc:
224
- raise cls.InvalidPacket("CRC doesn't match")
221
+ if (crc := cls.get_crc(data[:data_end])) != (packet_crc := data[data_end : data_end + 2]):
222
+ raise cls.InvalidPacket(f"CRC doesn't match ({crc=}, {packet_crc=})")
223
+
225
224
  return command, data[2:data_end], data[data_end + 2 :]
226
225
 
227
226
 
@@ -401,8 +400,7 @@ class CF635Screen(CFLCDScreen):
401
400
 
402
401
  packet = self._read_packet()
403
402
 
404
- next_repeat = self.key_repeat.next_event()
405
- if next_repeat:
403
+ if next_repeat := self.key_repeat.next_event():
406
404
  timeout, key = next_repeat
407
405
  if not timeout:
408
406
  data_input.append(key)
urwid/display/web.py CHANGED
@@ -21,6 +21,7 @@
21
21
  """
22
22
  Urwid web application display module
23
23
  """
24
+
24
25
  from __future__ import annotations
25
26
 
26
27
  import dataclasses
@@ -216,7 +217,7 @@ class Screen(BaseScreen):
216
217
  sys.stdout.write("Status: 503 Sever Busy\r\n\r\n")
217
218
  sys.exit(0)
218
219
 
219
- urwid_id = f"{random.randrange(10 ** 9):09d}{random.randrange(10 ** 9):09d}" # noqa: S311
220
+ urwid_id = f"{random.randrange(10**9):09d}{random.randrange(10**9):09d}" # noqa: S311
220
221
  self.pipe_name = os.path.join(_prefs.pipe_dir, f"urwid{urwid_id}")
221
222
  os.mkfifo(f"{self.pipe_name}.in", 0o600)
222
223
  signal.signal(signal.SIGTERM, self._cleanup_pipe)
@@ -224,11 +225,7 @@ class Screen(BaseScreen):
224
225
  self.input_fd = os.open(f"{self.pipe_name}.in", os.O_NONBLOCK | os.O_RDONLY)
225
226
  self.input_tail = ""
226
227
  self.content_head = (
227
- "Content-type: "
228
- "multipart/x-mixed-replace;boundary=ZZ\r\n"
229
- "X-Urwid-ID: " + urwid_id + "\r\n"
230
- "\r\n\r\n"
231
- "--ZZ\r\n"
228
+ f"Content-type: multipart/x-mixed-replace;boundary=ZZ\r\nX-Urwid-ID: {urwid_id}\r\n\r\n\r\n--ZZ\r\n"
232
229
  )
233
230
  if self.update_method == "polling":
234
231
  self.content_head = f"Content-type: text/plain\r\nX-Urwid-ID: {urwid_id}\r\n\r\n\r\n"
@@ -328,8 +325,8 @@ class Screen(BaseScreen):
328
325
  if y == cy:
329
326
  sig = (*sig, cx)
330
327
  new_screen[sig] = [*new_screen.get(sig, []), y]
331
- old_line_numbers = self.last_screen.get(sig, None)
332
- if old_line_numbers is not None:
328
+
329
+ if (old_line_numbers := self.last_screen.get(sig, None)) is not None:
333
330
  if y in old_line_numbers:
334
331
  old_line = y
335
332
  else:
@@ -609,16 +606,14 @@ def daemonize(errfile: str) -> None:
609
606
  """
610
607
  Detach process and become a daemon.
611
608
  """
612
- pid = os.fork()
613
- if pid:
609
+ if os.fork():
614
610
  os._exit(0)
615
611
 
616
612
  os.setsid()
617
613
  signal.signal(signal.SIGHUP, signal.SIG_IGN)
618
614
  os.umask(0)
619
615
 
620
- pid = os.fork()
621
- if pid:
616
+ if os.fork():
622
617
  os._exit(0)
623
618
 
624
619
  os.chdir("/")
@@ -205,8 +205,7 @@ class AsyncioEventLoop(EventLoop):
205
205
  return True
206
206
 
207
207
  def _exception_handler(self, loop: asyncio.AbstractEventLoop, context):
208
- exc = context.get("exception")
209
- if exc:
208
+ if exc := context.get("exception"):
210
209
  loop.stop()
211
210
 
212
211
  if self._idle_asyncio_handle:
@@ -172,7 +172,8 @@ class MainLoop:
172
172
  def _set_widget(self, widget: Widget) -> None:
173
173
  warnings.warn(
174
174
  f"method `{self.__class__.__name__}._set_widget` is deprecated, "
175
- f"please use `{self.__class__.__name__}.widget` property",
175
+ f"please use `{self.__class__.__name__}.widget` property."
176
+ "API will be removed in version 4.0.",
176
177
  DeprecationWarning,
177
178
  stacklevel=2,
178
179
  )
@@ -193,7 +194,8 @@ class MainLoop:
193
194
  def _set_pop_ups(self, pop_ups: bool) -> None:
194
195
  warnings.warn(
195
196
  f"method `{self.__class__.__name__}._set_pop_ups` is deprecated, "
196
- f"please use `{self.__class__.__name__}.pop_ups` property",
197
+ f"please use `{self.__class__.__name__}.pop_ups` property."
198
+ "API will be removed in version 4.0.",
197
199
  DeprecationWarning,
198
200
  stacklevel=2,
199
201
  )
@@ -460,9 +462,7 @@ class MainLoop:
460
462
  widget.mouse_event((15, 5), 'mouse press', 1, 5, 4, focus=True)
461
463
  >>> ml._update([], [])
462
464
  """
463
- keys = self.input_filter(keys, raw)
464
-
465
- if keys:
465
+ if keys := self.input_filter(keys, raw):
466
466
  self.process_input(keys)
467
467
  if "window resize" in keys:
468
468
  self.screen_size = None
@@ -493,19 +493,14 @@ class MainLoop:
493
493
  else:
494
494
  self.screen.set_input_timeouts(None)
495
495
  keys, raw = self.screen.get_input(True)
496
- if not keys and next_alarm:
497
- sec = next_alarm[0] - time.time()
498
- if sec <= 0:
499
- break
500
-
501
- keys = self.input_filter(keys, raw)
496
+ if not keys and next_alarm and next_alarm[0] - time.time() <= 0:
497
+ break
502
498
 
503
- if keys:
499
+ if keys := self.input_filter(keys, raw):
504
500
  self.process_input(keys)
505
501
 
506
502
  while next_alarm:
507
- sec = next_alarm[0] - time.time()
508
- if sec > 0:
503
+ if (next_alarm[0] - time.time()) > 0:
509
504
  break
510
505
  _tm, _tie_break, callback = next_alarm
511
506
  callback()
@@ -563,13 +558,13 @@ class MainLoop:
563
558
 
564
559
  if isinstance(key, str):
565
560
  if self._topmost_widget.selectable():
566
- handled_key = self._topmost_widget.keypress(self.screen_size, key)
567
- if not handled_key:
561
+ if handled_key := self._topmost_widget.keypress(self.screen_size, key):
562
+ key = handled_key # noqa: PLW2901
563
+
564
+ else:
568
565
  something_handled = True
569
566
  continue
570
567
 
571
- key = handled_key # noqa: PLW2901
572
-
573
568
  elif is_mouse_event(key):
574
569
  event, button, col, row = key
575
570
  if hasattr(self._topmost_widget, "mouse_event") and self._topmost_widget.mouse_event(
@@ -155,12 +155,11 @@ class TornadoEventLoop(EventLoop):
155
155
  return handle
156
156
 
157
157
  def remove_watch_file(self, handle: int) -> bool:
158
- fd = self._watch_handles.pop(handle, None)
159
- if fd is None:
160
- return False
158
+ if (fd := self._watch_handles.pop(handle, None)) is not None:
159
+ self._loop.remove_handler(fd)
160
+ return True
161
161
 
162
- self._loop.remove_handler(fd)
163
- return True
162
+ return False
164
163
 
165
164
  def enter_idle(self, callback: Callable[[], typing.Any]) -> int:
166
165
  """
@@ -261,7 +261,7 @@ class TrioEventLoop(EventLoop):
261
261
  """
262
262
  for task, scope, args in self._pending_tasks:
263
263
  self._nursery.start_soon(task, scope, *args)
264
- del self._pending_tasks[:]
264
+ self._pending_tasks.clear()
265
265
 
266
266
  def _start_task(
267
267
  self,
urwid/font.py CHANGED
@@ -29,7 +29,7 @@ from urwid.display.escape import SAFE_ASCII_DEC_SPECIAL_RE
29
29
  from urwid.util import apply_target_encoding, str_util
30
30
 
31
31
  if typing.TYPE_CHECKING:
32
- from collections.abc import Iterable, Iterator, Sequence
32
+ from collections.abc import Iterator, Sequence
33
33
 
34
34
  from typing_extensions import Literal
35
35
 
@@ -192,9 +192,9 @@ class Font(metaclass=FontRegistry):
192
192
 
193
193
  __slots__ = ("canvas", "char", "utf8_required")
194
194
 
195
- height: int
196
- data: Sequence[str]
197
- name: str
195
+ height: int # pylint: disable=declare-non-slot
196
+ data: Sequence[str] # pylint: disable=declare-non-slot
197
+ name: str # pylint: disable=declare-non-slot
198
198
 
199
199
  def __init__(self) -> None:
200
200
  if not self.height:
@@ -206,12 +206,10 @@ class Font(metaclass=FontRegistry):
206
206
  self.canvas: dict[str, TextCanvas] = {}
207
207
  self.utf8_required = False
208
208
  if isinstance(self.data, str):
209
- self.add_glyphs(self._to_text(self.data))
209
+ self.add_glyphs(self.data)
210
210
 
211
211
  else:
212
- data: Iterable[str] = (self._to_text(block) for block in self.data)
213
-
214
- for gdata in data:
212
+ for gdata in self.data:
215
213
  self.add_glyphs(gdata)
216
214
 
217
215
  def __repr__(self) -> str:
@@ -223,22 +221,19 @@ class Font(metaclass=FontRegistry):
223
221
 
224
222
  @staticmethod
225
223
  def _to_text(
226
- obj: str | bytes,
224
+ obj: str,
227
225
  encoding: str = "utf-8",
228
226
  errors: Literal["strict", "ignore", "replace"] = "strict",
229
227
  ) -> str:
228
+ warnings.warn(
229
+ "_to_text is deprecated: only text fonts are supported. API will be removed in version 4.0.",
230
+ DeprecationWarning,
231
+ stacklevel=3,
232
+ )
230
233
  if isinstance(obj, str):
231
234
  return obj
232
235
 
233
- if isinstance(obj, bytes):
234
- warnings.warn(
235
- "Bytes based fonts are deprecated, please switch to the text one",
236
- DeprecationWarning,
237
- stacklevel=3,
238
- )
239
- return obj.decode(encoding, errors)
240
-
241
- raise TypeError(f"{obj!r} is not str|bytes")
236
+ raise TypeError(f"{obj!r} is not str")
242
237
 
243
238
  def add_glyphs(self, gdata: str) -> None:
244
239
  d, utf8_required = separate_glyphs(gdata, self.height)
urwid/signals.py CHANGED
@@ -20,6 +20,7 @@
20
20
 
21
21
  from __future__ import annotations
22
22
 
23
+ import abc
23
24
  import itertools
24
25
  import typing
25
26
  import warnings
@@ -29,7 +30,7 @@ if typing.TYPE_CHECKING:
29
30
  from collections.abc import Callable, Collection, Container, Hashable, Iterable
30
31
 
31
32
 
32
- class MetaSignals(type):
33
+ class MetaSignals(abc.ABCMeta):
33
34
  """
34
35
  register the list of signals in the class variable signals,
35
36
  including signals in superclasses.
urwid/str_util.py CHANGED
@@ -29,17 +29,17 @@ import wcwidth
29
29
  if typing.TYPE_CHECKING:
30
30
  from typing_extensions import Literal
31
31
 
32
- SAFE_ASCII_RE = re.compile("^[ -~]*$")
33
- SAFE_ASCII_BYTES_RE = re.compile(b"^[ -~]*$")
32
+ SAFE_ASCII_RE = re.compile(r"^[ -~]*$")
33
+ SAFE_ASCII_BYTES_RE = re.compile(rb"^[ -~]*$")
34
34
 
35
35
  _byte_encoding: Literal["utf8", "narrow", "wide"] = "narrow"
36
36
 
37
37
 
38
38
  def get_char_width(char: str) -> Literal[0, 1, 2]:
39
- width = wcwidth.wcwidth(char)
40
- if width < 0:
41
- return 0
42
- return width
39
+ if (width := wcwidth.wcwidth(char)) >= 0:
40
+ return width
41
+
42
+ return 0
43
43
 
44
44
 
45
45
  def get_width(o: int) -> Literal[0, 1, 2]:
@@ -86,10 +86,9 @@ def decode_one(text: bytes | str, pos: int) -> tuple[int, int]:
86
86
  if b1 & 0xE0 == 0xC0:
87
87
  if b2 & 0xC0 != 0x80:
88
88
  return error
89
- o = ((b1 & 0x1F) << 6) | (b2 & 0x3F)
90
- if o < 0x80:
91
- return error
92
- return o, pos + 2
89
+ if (o := ((b1 & 0x1F) << 6) | (b2 & 0x3F)) >= 0x80:
90
+ return o, pos + 2
91
+ return error
93
92
  if lt < 3:
94
93
  return error
95
94
  if b1 & 0xF0 == 0xE0:
@@ -97,10 +96,9 @@ def decode_one(text: bytes | str, pos: int) -> tuple[int, int]:
97
96
  return error
98
97
  if b3 & 0xC0 != 0x80:
99
98
  return error
100
- o = ((b1 & 0x0F) << 12) | ((b2 & 0x3F) << 6) | (b3 & 0x3F)
101
- if o < 0x800:
102
- return error
103
- return o, pos + 3
99
+ if (o := ((b1 & 0x0F) << 12) | ((b2 & 0x3F) << 6) | (b3 & 0x3F)) >= 0x800:
100
+ return o, pos + 3
101
+ return error
104
102
  if lt < 4:
105
103
  return error
106
104
  if b1 & 0xF8 == 0xF0:
@@ -110,10 +108,9 @@ def decode_one(text: bytes | str, pos: int) -> tuple[int, int]:
110
108
  return error
111
109
  if b4 & 0xC0 != 0x80:
112
110
  return error
113
- o = ((b1 & 0x07) << 18) | ((b2 & 0x3F) << 12) | ((b3 & 0x3F) << 6) | (b4 & 0x3F)
114
- if o < 0x10000:
115
- return error
116
- return o, pos + 4
111
+ if (o := ((b1 & 0x07) << 18) | ((b2 & 0x3F) << 12) | ((b3 & 0x3F) << 6) | (b4 & 0x3F)) >= 0x10000:
112
+ return o, pos + 4
113
+ return error
117
114
  return error
118
115
 
119
116
 
urwid/text_layout.py CHANGED
@@ -461,7 +461,7 @@ def shift_line(
461
461
  # existing shift
462
462
  amount += segs[0][0]
463
463
  if amount:
464
- return [(amount, None)] + segs[1:]
464
+ return [(amount, None), *segs[1:]]
465
465
  return segs[1:]
466
466
 
467
467
  if amount:
@@ -581,8 +581,7 @@ def calc_pos(
581
581
  if row < 0 or row >= len(layout):
582
582
  raise ValueError("calculate_pos: out of layout row range")
583
583
 
584
- pos = calc_line_pos(text, layout[row], pref_col)
585
- if pos is not None:
584
+ if (pos := calc_line_pos(text, layout[row], pref_col)) is not None:
586
585
  return pos
587
586
 
588
587
  rows_above = list(range(row - 1, -1, -1))
@@ -590,14 +589,14 @@ def calc_pos(
590
589
  while rows_above and rows_below:
591
590
  if rows_above:
592
591
  r = rows_above.pop(0)
593
- pos = calc_line_pos(text, layout[r], pref_col)
594
- if pos is not None:
592
+ if (pos := calc_line_pos(text, layout[r], pref_col)) is not None:
595
593
  return pos
594
+
596
595
  if rows_below:
597
596
  r = rows_below.pop(0)
598
- pos = calc_line_pos(text, layout[r], pref_col)
599
- if pos is not None:
597
+ if (pos := calc_line_pos(text, layout[r], pref_col)) is not None:
600
598
  return pos
599
+
601
600
  return 0
602
601
 
603
602
 
urwid/util.py CHANGED
@@ -143,7 +143,7 @@ def get_encoding() -> str:
143
143
 
144
144
 
145
145
  @contextlib.contextmanager
146
- def set_temporary_encoding(encoding_name: str) -> Generator[None, None, None]:
146
+ def set_temporary_encoding(encoding_name: str) -> Generator[None]:
147
147
  """Internal helper for encoding specific validation in unittests/doctests.
148
148
 
149
149
  Not exported globally.
@@ -493,24 +493,13 @@ def is_mouse_press(ev: str) -> bool:
493
493
 
494
494
 
495
495
  class MetaSuper(type):
496
- """adding .__super"""
496
+ """Deprecated metaclass.
497
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)
498
+ Present only for code compatibility, all logic has been removed.
499
+ Please move to the last position in the class bases to allow future changes.
500
+ """
512
501
 
513
- setattr(cls, f"_{name}__super", _super)
502
+ __slots__ = ()
514
503
 
515
504
 
516
505
  def int_scale(val: int, val_range: int, out_range: int) -> int:
@@ -534,7 +523,7 @@ def int_scale(val: int, val_range: int, out_range: int) -> int:
534
523
  return num // dem
535
524
 
536
525
 
537
- class StoppingContext(typing.ContextManager["StoppingContext"]):
526
+ class StoppingContext(contextlib.AbstractContextManager["StoppingContext"]):
538
527
  """Context manager that calls ``stop`` on a given object on exit. Used to
539
528
  make the ``start`` method on `MainLoop` and `BaseScreen` optionally act as
540
529
  context managers.
urwid/version.py CHANGED
@@ -1,8 +1,13 @@
1
- # file generated by setuptools_scm
1
+ # file generated by setuptools-scm
2
2
  # don't change, don't track in version control
3
+
4
+ __all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
5
+
3
6
  TYPE_CHECKING = False
4
7
  if TYPE_CHECKING:
5
- from typing import Tuple, Union
8
+ from typing import Tuple
9
+ from typing import Union
10
+
6
11
  VERSION_TUPLE = Tuple[Union[int, str], ...]
7
12
  else:
8
13
  VERSION_TUPLE = object
@@ -12,5 +17,5 @@ __version__: str
12
17
  __version_tuple__: VERSION_TUPLE
13
18
  version_tuple: VERSION_TUPLE
14
19
 
15
- __version__ = version = '2.6.15'
16
- __version_tuple__ = version_tuple = (2, 6, 15)
20
+ __version__ = version = '3.0.0'
21
+ __version_tuple__ = version_tuple = (3, 0, 0)
urwid/vterm.py CHANGED
@@ -35,7 +35,6 @@ import termios
35
35
  import time
36
36
  import traceback
37
37
  import typing
38
- import warnings
39
38
  from collections import deque
40
39
  from contextlib import suppress
41
40
  from dataclasses import dataclass
@@ -248,8 +247,7 @@ class TermCharset:
248
247
 
249
248
  def apply_mapping(self, char: bytes) -> bytes:
250
249
  if self._sgr_mapping or self._g[self.active] == "ibmpc":
251
- dec_pos = DEC_SPECIAL_CHARS.find(char.decode("cp437"))
252
- if dec_pos >= 0:
250
+ if (dec_pos := DEC_SPECIAL_CHARS.find(char.decode("cp437"))) >= 0:
253
251
  self.current = "0"
254
252
  return ALT_DEC_SPECIAL_CHARS[dec_pos].encode("cp437")
255
253
 
@@ -259,39 +257,6 @@ class TermCharset:
259
257
  return char
260
258
 
261
259
 
262
- class TermScroller(list):
263
- """
264
- List subclass that handles the terminal scrollback buffer,
265
- truncating it as necessary.
266
- """
267
-
268
- SCROLLBACK_LINES = 10000
269
-
270
- def __init__(self, iterable: Iterable[typing.Any]) -> None:
271
- warnings.warn(
272
- "`TermScroller` is deprecated. Please use `collections.deque` with non-zero `maxlen` instead.",
273
- DeprecationWarning,
274
- stacklevel=3,
275
- )
276
- super().__init__(iterable)
277
-
278
- def trunc(self) -> None:
279
- if len(self) >= self.SCROLLBACK_LINES:
280
- self.pop(0)
281
-
282
- def append(self, obj) -> None:
283
- self.trunc()
284
- super().append(obj)
285
-
286
- def insert(self, idx: typing.SupportsIndex, obj) -> None:
287
- self.trunc()
288
- super().insert(idx, obj)
289
-
290
- def extend(self, seq) -> None:
291
- self.trunc()
292
- super().extend(seq)
293
-
294
-
295
260
  class TermCanvas(Canvas):
296
261
  cacheable = False
297
262
 
@@ -375,7 +340,7 @@ class TermCanvas(Canvas):
375
340
  lines = self.height // 2
376
341
 
377
342
  if not up:
378
- lines = -lines # pylint: disable=invalid-unary-operand-type # type already narrowed
343
+ lines = -lines
379
344
 
380
345
  maxscroll = len(self.scrollback_buffer)
381
346
  self.scrolling_up += lines
@@ -489,8 +454,7 @@ class TermCanvas(Canvas):
489
454
  continue
490
455
 
491
456
  # adjust x axis of scrollback buffer to the current width
492
- padding = self.width - len(last_line)
493
- if padding > 0:
457
+ if (padding := self.width - len(last_line)) > 0:
494
458
  last_line += [self.empty_char()] * padding
495
459
  else:
496
460
  last_line = last_line[: self.width]
@@ -549,8 +513,7 @@ class TermCanvas(Canvas):
549
513
 
550
514
  escbuf.append(num)
551
515
 
552
- cmd_ = CSI_COMMANDS[char]
553
- if cmd_ is not None:
516
+ if (cmd_ := CSI_COMMANDS[char]) is not None:
554
517
  if isinstance(cmd_, CSIAlias):
555
518
  csi_cmd: CSICommand = CSI_COMMANDS[cmd_.alias] # type: ignore[assignment]
556
519
  elif isinstance(cmd_, CSICommand):
@@ -699,11 +662,13 @@ class TermCanvas(Canvas):
699
662
 
700
663
  # end multibyte sequence
701
664
  self.utf8_eat_bytes = None
702
- sequence = (self.utf8_buffer + bytes([byte])).decode("utf-8", "ignore")
703
- if not sequence:
665
+
666
+ if sequence := (self.utf8_buffer + bytes([byte])).decode("utf-8", "ignore"):
667
+ char = sequence.encode(util.get_encoding(), "replace")
668
+
669
+ else:
704
670
  # invalid multibyte sequence, stop processing
705
671
  return
706
- char = sequence.encode(util.get_encoding(), "replace")
707
672
  else:
708
673
  self.utf8_eat_bytes = None
709
674
  char = bytes([byte])
@@ -1118,6 +1083,14 @@ class TermCanvas(Canvas):
1118
1083
  elif 40 <= attr <= 47:
1119
1084
  bg = attr - 40
1120
1085
  colors = max(16, colors)
1086
+ # AIXTERM bright color spec
1087
+ # https://en.wikipedia.org/wiki/ANSI_escape_code
1088
+ elif 90 <= attr <= 97:
1089
+ fg = attr - 90 + 8
1090
+ colors = max(16, colors)
1091
+ elif 100 <= attr <= 107:
1092
+ bg = attr - 100 + 8
1093
+ colors = max(16, colors)
1121
1094
  elif attr in {38, 48}:
1122
1095
  if idx + 2 < len(attrs) and attrs[idx + 1] == 5:
1123
1096
  # 8 bit color specification
@@ -1264,7 +1237,7 @@ class TermCanvas(Canvas):
1264
1237
  for x in range(self.width):
1265
1238
  char = self.term[y][x]
1266
1239
  attrs = self.reverse_attrspec(char[0], undo=undo)
1267
- self.term[y][x] = (attrs,) + char[1:]
1240
+ self.term[y][x] = (attrs, *char[1:])
1268
1241
 
1269
1242
  def set_mode(
1270
1243
  self,