urwid 2.6.8__py3-none-any.whl → 2.6.10__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.
urwid/display/escape.py CHANGED
@@ -310,17 +310,15 @@ class KeyqueueTrie:
310
310
 
311
311
  (b, x, y) = (int(val) for val in value[:-1].split(";"))
312
312
  action = value[-1]
313
-
314
- # shift, etc. is not communicated on my machine, so I
315
- # can't and won't be able to add support for it.
316
- # Double and triple clicks are not supported as well. They can be
317
- # implemented by using a timer. This timer can check if the last
318
- # registered click is below a certain threshold. This threshold
319
- # is normally set in the operating system itself, so setting one
320
- # here will cause an inconsistent behaviour. I do not plan to use
321
- # that feature, so I won't implement it.
313
+ # Double and triple clicks are not supported.
314
+ # They can be implemented by using a timer.
315
+ # This timer can check if the last registered click is below a certain threshold.
316
+ # This threshold is normally set in the operating system itself,
317
+ # so setting one here will cause an inconsistent behaviour.
322
318
 
323
319
  prefixes = []
320
+ if b & 4:
321
+ prefixes.append("shift ")
324
322
  if b & 8:
325
323
  prefixes.append("meta ")
326
324
  if b & 16:
@@ -222,9 +222,10 @@ class AsyncioEventLoop(EventLoop):
222
222
  loop.default_exception_handler(context)
223
223
 
224
224
  def run(self) -> None:
225
- """
226
- Start the event loop. Exit the loop when any callback raises
227
- an exception. If ExitMainLoop is raised, exit cleanly.
225
+ """Start the event loop.
226
+
227
+ Exit the loop when any callback raises an exception.
228
+ If ExitMainLoop is raised, exit cleanly.
228
229
  """
229
230
  self._loop.set_exception_handler(self._exception_handler)
230
231
  self._loop.run_forever()
@@ -160,9 +160,10 @@ class TrioEventLoop(EventLoop):
160
160
  trio.run(self._main_task, instruments=[emulate_idle_callbacks])
161
161
 
162
162
  async def run_async(self) -> None:
163
- """Starts the main loop and blocks asynchronously until the main loop
164
- exits. This allows one to embed an urwid app in a Trio app even if the
165
- Trio event loop is already running. Example::
163
+ """Starts the main loop and blocks asynchronously until the main loop exits.
164
+
165
+ This allows one to embed an urwid app in a Trio app even if the Trio event loop is already running.
166
+ Example::
166
167
 
167
168
  with trio.open_nursery() as nursery:
168
169
  event_loop = urwid.TrioEventLoop()
urwid/version.py CHANGED
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '2.6.8'
16
- __version_tuple__ = version_tuple = (2, 6, 8)
15
+ __version__ = version = '2.6.10'
16
+ __version_tuple__ = version_tuple = (2, 6, 10)
urwid/widget/__init__.py CHANGED
@@ -61,8 +61,6 @@ from .wimp import Button, CheckBox, CheckBoxError, RadioButton, SelectableIcon
61
61
  __all__ = (
62
62
  "ANY",
63
63
  "BOTTOM",
64
- "MonitoredList",
65
- "MonitoredFocusList",
66
64
  "BOX",
67
65
  "CENTER",
68
66
  "CLIP",
@@ -72,7 +70,6 @@ __all__ = (
72
70
  "GIVEN",
73
71
  "LEFT",
74
72
  "MIDDLE",
75
- "GridFlowWarning",
76
73
  "PACK",
77
74
  "RELATIVE",
78
75
  "RELATIVE_100",
@@ -91,20 +88,8 @@ __all__ = (
91
88
  "BoxAdapter",
92
89
  "BoxAdapterError",
93
90
  "BoxWidget",
94
- "ListWalkerError",
95
- "ListWalker",
96
- "SimpleListWalker",
97
- "SimpleFocusListWalker",
98
- "ListBoxError",
99
- "ListBox",
100
91
  "Button",
101
92
  "CheckBox",
102
- "TreeWidgetError",
103
- "TreeWidget",
104
- "TreeNode",
105
- "ParentNode",
106
- "TreeWalker",
107
- "TreeListBox",
108
93
  "CheckBoxError",
109
94
  "Columns",
110
95
  "ColumnsError",
@@ -121,14 +106,22 @@ __all__ = (
121
106
  "GraphVScale",
122
107
  "GridFlow",
123
108
  "GridFlowError",
109
+ "GridFlowWarning",
124
110
  "IntEdit",
125
111
  "LineBox",
112
+ "ListBox",
113
+ "ListBoxError",
114
+ "ListWalker",
115
+ "ListWalkerError",
116
+ "MonitoredFocusList",
117
+ "MonitoredList",
126
118
  "Overlay",
127
119
  "OverlayError",
128
120
  "OverlayWarning",
129
121
  "Padding",
130
122
  "PaddingError",
131
123
  "PaddingWarning",
124
+ "ParentNode",
132
125
  "Pile",
133
126
  "PileError",
134
127
  "PileWarning",
@@ -140,10 +133,17 @@ __all__ = (
140
133
  "Scrollable",
141
134
  "ScrollableError",
142
135
  "SelectableIcon",
136
+ "SimpleFocusListWalker",
137
+ "SimpleListWalker",
143
138
  "Sizing",
144
139
  "SolidFill",
145
140
  "Text",
146
141
  "TextError",
142
+ "TreeListBox",
143
+ "TreeNode",
144
+ "TreeWalker",
145
+ "TreeWidget",
146
+ "TreeWidgetError",
147
147
  "VAlign",
148
148
  "WHSettings",
149
149
  "Widget",
urwid/widget/listbox.py CHANGED
@@ -20,6 +20,7 @@
20
20
 
21
21
  from __future__ import annotations
22
22
 
23
+ import operator
23
24
  import typing
24
25
  import warnings
25
26
  from collections.abc import Iterable, Sized
@@ -43,7 +44,18 @@ if typing.TYPE_CHECKING:
43
44
 
44
45
  from urwid.canvas import Canvas, CompositeCanvas
45
46
 
46
- __all__ = ("ListWalkerError", "ListWalker", "SimpleListWalker", "SimpleFocusListWalker", "ListBoxError", "ListBox")
47
+ __all__ = (
48
+ "ListBox",
49
+ "ListBoxError",
50
+ "ListWalker",
51
+ "ListWalkerError",
52
+ "SimpleFocusListWalker",
53
+ "SimpleListWalker",
54
+ "VisibleInfo",
55
+ "VisibleInfoFillItem",
56
+ "VisibleInfoMiddle",
57
+ "VisibleInfoTopBottom",
58
+ )
47
59
 
48
60
  _T = typing.TypeVar("_T")
49
61
  _K = typing.TypeVar("_K")
@@ -54,22 +66,30 @@ class ListWalkerError(Exception):
54
66
 
55
67
 
56
68
  @runtime_checkable
57
- class ScrollSupportingBody(Sized, Protocol):
58
- """Protocol for ListWalkers that support Scrolling."""
69
+ class ScrollSupportingBody(Protocol):
70
+ """Protocol for ListWalkers."""
59
71
 
60
72
  def get_focus(self) -> tuple[Widget, _K]: ...
61
73
 
62
74
  def set_focus(self, position: _K) -> None: ...
63
75
 
64
- def __getitem__(self, index: _K) -> _T: ...
65
-
66
- def positions(self, reverse: bool = False) -> Iterable[_K]: ...
67
-
68
76
  def get_next(self, position: _K) -> tuple[Widget, _K] | tuple[None, None]: ...
69
77
 
70
78
  def get_prev(self, position: _K) -> tuple[Widget, _K] | tuple[None, None]: ...
71
79
 
72
80
 
81
+ @runtime_checkable
82
+ class EstimatedSized(Protocol):
83
+ """Widget can estimate it's size.
84
+
85
+ PEP 424 defines API for memory-efficiency.
86
+ For the ListBox it's a sign of the limited body length.
87
+ The main use-case is lazy-load, where real length calculation is expensive.
88
+ """
89
+
90
+ def __length_hint__(self) -> int: ...
91
+
92
+
73
93
  class ListWalker(metaclass=signals.MetaSignals): # pylint: disable=no-member, unsubscriptable-object
74
94
  # mixin not named as mixin
75
95
  signals: typing.ClassVar[list[str]] = ["modified"]
@@ -276,7 +296,7 @@ class ListBoxError(Exception):
276
296
  pass
277
297
 
278
298
 
279
- class _Middle(typing.NamedTuple):
299
+ class VisibleInfoMiddle(typing.NamedTuple):
280
300
  """Named tuple for ListBox internals."""
281
301
 
282
302
  offset: int
@@ -286,7 +306,7 @@ class _Middle(typing.NamedTuple):
286
306
  cursor: tuple[int, int] | tuple[int] | None
287
307
 
288
308
 
289
- class _FillItem(typing.NamedTuple):
309
+ class VisibleInfoFillItem(typing.NamedTuple):
290
310
  """Named tuple for ListBox internals."""
291
311
 
292
312
  widget: Widget
@@ -294,11 +314,45 @@ class _FillItem(typing.NamedTuple):
294
314
  rows: int
295
315
 
296
316
 
297
- class _TopBottom(typing.NamedTuple):
317
+ class VisibleInfoTopBottom(typing.NamedTuple):
298
318
  """Named tuple for ListBox internals."""
299
319
 
300
320
  trim: int
301
- fill: list[_FillItem]
321
+ fill: list[VisibleInfoFillItem]
322
+
323
+ @classmethod
324
+ def from_raw_data(
325
+ cls,
326
+ trim: int,
327
+ fill: Iterable[tuple[Widget, Hashable, int]],
328
+ ) -> Self:
329
+ """Construct from not typed data.
330
+
331
+ Useful for overridden cases."""
332
+ return cls(trim=trim, fill=[VisibleInfoFillItem(*item) for item in fill]) # pragma: no cover
333
+
334
+
335
+ class VisibleInfo(typing.NamedTuple):
336
+ middle: VisibleInfoMiddle
337
+ top: VisibleInfoTopBottom
338
+ bottom: VisibleInfoTopBottom
339
+
340
+ @classmethod
341
+ def from_raw_data(
342
+ cls,
343
+ middle: tuple[int, Widget, Hashable, int, tuple[int, int] | tuple[int] | None],
344
+ top: tuple[int, Iterable[tuple[Widget, Hashable, int]]],
345
+ bottom: tuple[int, Iterable[tuple[Widget, Hashable, int]]],
346
+ ) -> Self:
347
+ """Construct from not typed data.
348
+
349
+ Useful for overridden cases.
350
+ """
351
+ return cls( # pragma: no cover
352
+ middle=VisibleInfoMiddle(*middle),
353
+ top=VisibleInfoTopBottom.from_raw_data(*top),
354
+ bottom=VisibleInfoTopBottom.from_raw_data(*bottom),
355
+ )
302
356
 
303
357
 
304
358
  class ListBox(Widget, WidgetContainerMixin):
@@ -390,16 +444,23 @@ class ListBox(Widget, WidgetContainerMixin):
390
444
  )
391
445
  self.body = body
392
446
 
393
- def __len__(self) -> int:
447
+ @property
448
+ def __len__(self) -> Callable[[], int]:
394
449
  if isinstance(self._body, Sized):
395
- return len(self._body)
450
+ return self._body.__len__
396
451
  raise AttributeError(f"{self._body.__class__.__name__} is not Sized")
397
452
 
453
+ @property
454
+ def __length_hint__(self) -> Callable[[], int]: # pylint: disable=invalid-length-hint-returned
455
+ if isinstance(self._body, (Sized, EstimatedSized)):
456
+ return lambda: operator.length_hint(self._body)
457
+ raise AttributeError(f'{self._body.__class__.__name__} is not Sized and do not implement "__length_hint__"')
458
+
398
459
  def calculate_visible(
399
460
  self,
400
461
  size: tuple[int, int],
401
462
  focus: bool = False,
402
- ) -> tuple[_Middle, _TopBottom, _TopBottom] | tuple[None, None, None]:
463
+ ) -> VisibleInfo | tuple[None, None, None]:
403
464
  """
404
465
  Returns the widgets that would be displayed in
405
466
  the ListBox given the current *size* and *focus*.
@@ -470,7 +531,7 @@ class ListBox(Widget, WidgetContainerMixin):
470
531
 
471
532
  p_rows = prev.rows((maxcol,))
472
533
  if p_rows: # filter out 0-height widgets
473
- fill_above.append(_FillItem(prev, pos, p_rows))
534
+ fill_above.append(VisibleInfoFillItem(prev, pos, p_rows))
474
535
  if p_rows > fill_lines: # crosses top edge?
475
536
  trim_top = p_rows - fill_lines
476
537
  break
@@ -489,7 +550,7 @@ class ListBox(Widget, WidgetContainerMixin):
489
550
 
490
551
  n_rows = next_pos.rows((maxcol,))
491
552
  if n_rows: # filter out 0-height widgets
492
- fill_below.append(_FillItem(next_pos, pos, n_rows))
553
+ fill_below.append(VisibleInfoFillItem(next_pos, pos, n_rows))
493
554
  if n_rows > fill_lines: # crosses bottom edge?
494
555
  trim_bottom = n_rows - fill_lines
495
556
  fill_lines -= n_rows
@@ -515,7 +576,7 @@ class ListBox(Widget, WidgetContainerMixin):
515
576
  break
516
577
 
517
578
  p_rows = prev.rows((maxcol,))
518
- fill_above.append(_FillItem(prev, pos, p_rows))
579
+ fill_above.append(VisibleInfoFillItem(prev, pos, p_rows))
519
580
  if p_rows > fill_lines: # more than required
520
581
  trim_top = p_rows - fill_lines
521
582
  offset_rows += fill_lines
@@ -524,17 +585,31 @@ class ListBox(Widget, WidgetContainerMixin):
524
585
  offset_rows += p_rows
525
586
 
526
587
  # 5. return the interesting bits
527
- return (
528
- _Middle(offset_rows - inset_rows, focus_widget, focus_pos, focus_rows, cursor),
529
- _TopBottom(trim_top, fill_above),
530
- _TopBottom(trim_bottom, fill_below),
588
+ return VisibleInfo(
589
+ VisibleInfoMiddle(offset_rows - inset_rows, focus_widget, focus_pos, focus_rows, cursor),
590
+ VisibleInfoTopBottom(trim_top, fill_above),
591
+ VisibleInfoTopBottom(trim_bottom, fill_below),
531
592
  )
532
593
 
533
- def get_scrollpos(self, size: tuple[int, int] | None = None, focus: bool = False) -> int:
534
- """Current scrolling position."""
594
+ def _check_support_scrolling(self) -> None:
595
+ from .treetools import TreeWalker
596
+
535
597
  if not isinstance(self._body, ScrollSupportingBody):
536
598
  raise ListBoxError(f"{self} body do not implement methods required for scrolling protocol")
537
599
 
600
+ if not isinstance(self._body, (Sized, EstimatedSized, TreeWalker)):
601
+ raise ListBoxError(
602
+ f"{self} body is not a Sized, can not estimate it's size and not a TreeWalker."
603
+ f"Scroll is not allowed due to risk of infinite cycle of widgets load."
604
+ )
605
+
606
+ if getattr(self._body, "wrap_around", False):
607
+ raise ListBoxError("Body is wrapped around. Scroll position calculation is undefined.")
608
+
609
+ def get_scrollpos(self, size: tuple[int, int] | None = None, focus: bool = False) -> int:
610
+ """Current scrolling position."""
611
+ self._check_support_scrolling()
612
+
538
613
  if not self._body:
539
614
  return 0
540
615
 
@@ -560,22 +635,65 @@ class ListBox(Widget, WidgetContainerMixin):
560
635
 
561
636
  def rows_max(self, size: tuple[int, int] | None = None, focus: bool = False) -> int:
562
637
  """Scrollable protocol for sized iterable and not wrapped around contents."""
563
- if not isinstance(self._body, ScrollSupportingBody):
564
- raise ListBoxError(f"{self} body do not implement methods required for scrolling protocol")
565
-
566
- if getattr(self._body, "wrap_around", False):
567
- raise ListBoxError("Body is wrapped around")
638
+ self._check_support_scrolling()
568
639
 
569
640
  if size is not None:
570
641
  self._rendered_size = size
571
642
 
572
643
  if size or not self._rows_max_cached:
573
- self._rows_max_cached = sum(
574
- self._body[position].rows((self._rendered_size[0],), focus) for position in self._body.positions()
575
- )
644
+ cols = self._rendered_size[0]
645
+ rows = 0
646
+
647
+ focused_w, idx = self.body.get_focus()
648
+ rows += focused_w.rows((cols,), focus)
649
+
650
+ prev, pos = self._body.get_prev(idx)
651
+ while prev is not None:
652
+ rows += prev.rows((cols,), False)
653
+ prev, pos = self._body.get_prev(pos)
654
+
655
+ next_, pos = self.body.get_next(idx)
656
+ while next_ is not None:
657
+ rows += next_.rows((cols,), True)
658
+ next_, pos = self._body.get_next(pos)
659
+
660
+ self._rows_max_cached = rows
576
661
 
577
662
  return self._rows_max_cached
578
663
 
664
+ def require_relative_scroll(self, size: tuple[int, int], focus: bool = False) -> bool:
665
+ """Widget require relative scroll due to performance limitations of real lines count calculation."""
666
+ return isinstance(self._body, (Sized, EstimatedSized)) and (size[1] * 3 < operator.length_hint(self.body))
667
+
668
+ def get_first_visible_pos(self, size: tuple[int, int], focus: bool = False) -> int:
669
+ self._check_support_scrolling()
670
+
671
+ if not self._body:
672
+ return 0
673
+
674
+ _mid, top, _bottom = self.calculate_visible(size, focus)
675
+ if top.fill:
676
+ first_pos = top.fill[-1].position
677
+ else:
678
+ first_pos = self.focus_position
679
+
680
+ over = 0
681
+ _widget, first_pos = self.body.get_prev(first_pos)
682
+ while first_pos is not None:
683
+ over += 1
684
+ _widget, first_pos = self.body.get_prev(first_pos)
685
+
686
+ return over
687
+
688
+ def get_visible_amount(self, size: tuple[int, int], focus: bool = False) -> int:
689
+ self._check_support_scrolling()
690
+
691
+ if not self._body:
692
+ return 1
693
+
694
+ _mid, top, bottom = self.calculate_visible(size, focus)
695
+ return 1 + len(top.fill) + len(bottom.fill)
696
+
579
697
  def render(
580
698
  self,
581
699
  size: tuple[int, int], # type: ignore[override]
@@ -594,9 +712,9 @@ class ListBox(Widget, WidgetContainerMixin):
594
712
  if middle is None:
595
713
  return SolidCanvas(" ", maxcol, maxrow)
596
714
 
597
- _ignore, focus_widget, focus_pos, focus_rows, cursor = middle
598
- trim_top, fill_above = top
599
- trim_bottom, fill_below = bottom
715
+ _ignore, focus_widget, focus_pos, focus_rows, cursor = middle # pylint: disable=unpacking-non-sequence
716
+ trim_top, fill_above = top # pylint: disable=unpacking-non-sequence
717
+ trim_bottom, fill_below = bottom # pylint: disable=unpacking-non-sequence
600
718
 
601
719
  combinelist: list[tuple[Canvas, int, bool]] = []
602
720
  rows = 0
@@ -706,7 +824,7 @@ class ListBox(Widget, WidgetContainerMixin):
706
824
  if middle is None:
707
825
  return None
708
826
 
709
- offset_inset, _ignore1, _ignore2, _ignore3, cursor = middle
827
+ offset_inset, _ignore1, _ignore2, _ignore3, cursor = middle # pylint: disable=unpacking-non-sequence
710
828
  if not cursor:
711
829
  return None
712
830
 
@@ -897,9 +1015,9 @@ class ListBox(Widget, WidgetContainerMixin):
897
1015
  if middle is None:
898
1016
  return
899
1017
 
900
- row_offset, focus_widget, _focus_pos, focus_rows, _cursor = middle
901
- _trim_top, _fill_above = top
902
- trim_bottom, fill_below = bottom
1018
+ row_offset, focus_widget, _focus_pos, focus_rows, _cursor = middle # pylint: disable=unpacking-non-sequence
1019
+ _trim_top, _fill_above = top # pylint: disable=unpacking-non-sequence
1020
+ trim_bottom, fill_below = bottom # pylint: disable=unpacking-non-sequence
903
1021
 
904
1022
  if focus_widget.selectable():
905
1023
  return
@@ -935,9 +1053,9 @@ class ListBox(Widget, WidgetContainerMixin):
935
1053
  self._body.set_focus(focus_pos)
936
1054
 
937
1055
  middle, top, bottom = self.calculate_visible((maxcol, maxrow), focus)
938
- focus_offset, _focus_widget, focus_pos, focus_rows, _cursor = middle
939
- _trim_top, fill_above = top
940
- _trim_bottom, fill_below = bottom
1056
+ focus_offset, _focus_widget, focus_pos, focus_rows, _cursor = middle # pylint: disable=unpacking-non-sequence
1057
+ _trim_top, fill_above = top # pylint: disable=unpacking-non-sequence
1058
+ _trim_bottom, fill_below = bottom # pylint: disable=unpacking-non-sequence
941
1059
 
942
1060
  offset = focus_offset
943
1061
  for _widget, pos, rows in fill_above:
@@ -1251,8 +1369,8 @@ class ListBox(Widget, WidgetContainerMixin):
1251
1369
  if middle is None:
1252
1370
  return True
1253
1371
 
1254
- focus_row_offset, focus_widget, focus_pos, _ignore, cursor = middle
1255
- _trim_top, fill_above = top
1372
+ focus_row_offset, focus_widget, focus_pos, _ignore, cursor = middle # pylint: disable=unpacking-non-sequence
1373
+ _trim_top, fill_above = top # pylint: disable=unpacking-non-sequence
1256
1374
 
1257
1375
  row_offset = focus_row_offset
1258
1376
 
@@ -1324,8 +1442,8 @@ class ListBox(Widget, WidgetContainerMixin):
1324
1442
  if middle is None:
1325
1443
  return True
1326
1444
 
1327
- focus_row_offset, focus_widget, focus_pos, focus_rows, cursor = middle
1328
- _trim_bottom, fill_below = bottom
1445
+ focus_row_offset, focus_widget, focus_pos, focus_rows, cursor = middle # pylint: disable=unpacking-non-sequence
1446
+ _trim_bottom, fill_below = bottom # pylint: disable=unpacking-non-sequence
1329
1447
 
1330
1448
  row_offset = focus_row_offset + focus_rows
1331
1449
  rows = focus_rows
@@ -1363,9 +1481,6 @@ class ListBox(Widget, WidgetContainerMixin):
1363
1481
  if widget is None:
1364
1482
  self.shift_focus((maxcol, maxrow), row_offset - rows)
1365
1483
  return None
1366
- # FIXME: catch this bug in testcase
1367
- # self.change_focus((maxcol,maxrow), pos,
1368
- # row_offset+rows, 'above')
1369
1484
  self.change_focus((maxcol, maxrow), pos, row_offset - rows, "above")
1370
1485
  return None
1371
1486
 
@@ -1406,8 +1521,8 @@ class ListBox(Widget, WidgetContainerMixin):
1406
1521
  if middle is None:
1407
1522
  return True
1408
1523
 
1409
- row_offset, focus_widget, focus_pos, focus_rows, cursor = middle
1410
- _trim_top, fill_above = top
1524
+ row_offset, focus_widget, focus_pos, focus_rows, cursor = middle # pylint: disable=unpacking-non-sequence
1525
+ _trim_top, fill_above = top # pylint: disable=unpacking-non-sequence
1411
1526
 
1412
1527
  # topmost_visible is row_offset rows above top row of
1413
1528
  # focus (+ve) or -row_offset rows below top row of focus (-ve)
@@ -1516,7 +1631,7 @@ class ListBox(Widget, WidgetContainerMixin):
1516
1631
 
1517
1632
  # find out where that actually puts us
1518
1633
  middle, top, _bottom = self.calculate_visible((maxcol, maxrow), True)
1519
- act_row_offset, _ign1, _ign2, _ign3, _ign4 = middle
1634
+ act_row_offset, _ign1, _ign2, _ign3, _ign4 = middle # pylint: disable=unpacking-non-sequence
1520
1635
 
1521
1636
  # discard chosen widget if it will reduce scroll amount
1522
1637
  # because of a fixed cursor (absolute last resort)
@@ -1566,7 +1681,7 @@ class ListBox(Widget, WidgetContainerMixin):
1566
1681
 
1567
1682
  # final check for pathological case where we may fall short
1568
1683
  middle, top, _bottom = self.calculate_visible((maxcol, maxrow), True)
1569
- act_row_offset, _ign1, pos, _ign2, _ign3 = middle
1684
+ act_row_offset, _ign1, pos, _ign2, _ign3 = middle # pylint: disable=unpacking-non-sequence
1570
1685
  if act_row_offset >= row_offset:
1571
1686
  # no problem
1572
1687
  return None
@@ -1598,8 +1713,8 @@ class ListBox(Widget, WidgetContainerMixin):
1598
1713
  if middle is None:
1599
1714
  return True
1600
1715
 
1601
- row_offset, focus_widget, focus_pos, focus_rows, cursor = middle
1602
- _trim_bottom, fill_below = bottom
1716
+ row_offset, focus_widget, focus_pos, focus_rows, cursor = middle # pylint: disable=unpacking-non-sequence
1717
+ _trim_bottom, fill_below = bottom # pylint: disable=unpacking-non-sequence
1603
1718
 
1604
1719
  # bottom_edge is maxrow-focus_pos rows below top row of focus
1605
1720
  bottom_edge = maxrow - row_offset
@@ -1704,7 +1819,7 @@ class ListBox(Widget, WidgetContainerMixin):
1704
1819
 
1705
1820
  # find out where that actually puts us
1706
1821
  middle, _top, bottom = self.calculate_visible((maxcol, maxrow), True)
1707
- act_row_offset, _ign1, _ign2, _ign3, _ign4 = middle
1822
+ act_row_offset, _ign1, _ign2, _ign3, _ign4 = middle # pylint: disable=unpacking-non-sequence
1708
1823
 
1709
1824
  # discard chosen widget if it will reduce scroll amount
1710
1825
  # because of a fixed cursor (absolute last resort)
@@ -1750,7 +1865,7 @@ class ListBox(Widget, WidgetContainerMixin):
1750
1865
 
1751
1866
  # final check for pathological case where we may fall short
1752
1867
  middle, _top, bottom = self.calculate_visible((maxcol, maxrow), True)
1753
- act_row_offset, _ign1, pos, _ign2, _ign3 = middle
1868
+ act_row_offset, _ign1, pos, _ign2, _ign3 = middle # pylint: disable=unpacking-non-sequence
1754
1869
  if act_row_offset <= row_offset:
1755
1870
  # no problem
1756
1871
  return None
@@ -1795,9 +1910,9 @@ class ListBox(Widget, WidgetContainerMixin):
1795
1910
  if middle is None:
1796
1911
  return False
1797
1912
 
1798
- _ignore, focus_widget, focus_pos, focus_rows, _cursor = middle
1799
- trim_top, fill_above = top
1800
- _ignore, fill_below = bottom
1913
+ _ignore, focus_widget, focus_pos, focus_rows, _cursor = middle # pylint: disable=unpacking-non-sequence
1914
+ trim_top, fill_above = top # pylint: disable=unpacking-non-sequence
1915
+ _ignore, fill_below = bottom # pylint: disable=unpacking-non-sequence
1801
1916
 
1802
1917
  fill_above.reverse() # fill_above is in bottom-up order
1803
1918
  w_list = [*fill_above, (focus_widget, focus_pos, focus_rows), *fill_below]
@@ -1850,11 +1965,11 @@ class ListBox(Widget, WidgetContainerMixin):
1850
1965
  middle, top, bottom = self.calculate_visible((maxcol, maxrow), focus=focus)
1851
1966
  if middle is None: # empty listbox
1852
1967
  return ["top", "bottom"]
1853
- trim_top, above = top
1854
- trim_bottom, below = bottom
1968
+ trim_top, above = top # pylint: disable=unpacking-non-sequence
1969
+ trim_bottom, below = bottom # pylint: disable=unpacking-non-sequence
1855
1970
 
1856
1971
  if trim_bottom == 0:
1857
- row_offset, _w, pos, rows, _c = middle
1972
+ row_offset, _w, pos, rows, _c = middle # pylint: disable=unpacking-non-sequence
1858
1973
  row_offset += rows
1859
1974
  for _w, pos, rows in below: # noqa: B007 # magic with scope
1860
1975
  row_offset += rows
@@ -1862,7 +1977,7 @@ class ListBox(Widget, WidgetContainerMixin):
1862
1977
  result.append("bottom")
1863
1978
 
1864
1979
  if trim_top == 0:
1865
- row_offset, _w, pos, _rows, _c = middle
1980
+ row_offset, _w, pos, _rows, _c = middle # pylint: disable=unpacking-non-sequence
1866
1981
  for _w, pos, rows in above: # noqa: B007 # magic with scope
1867
1982
  row_offset -= rows
1868
1983
  if self._body.get_prev(pos) == (None, None):
@@ -31,7 +31,7 @@ if typing.TYPE_CHECKING:
31
31
  ArgSpec = ParamSpec("ArgSpec")
32
32
  Ret = typing.TypeVar("Ret")
33
33
 
34
- __all__ = ("MonitoredList", "MonitoredFocusList")
34
+ __all__ = ("MonitoredFocusList", "MonitoredList")
35
35
 
36
36
  _T = typing.TypeVar("_T")
37
37
 
@@ -85,10 +85,10 @@ class ScrollbarSymbols(str, enum.Enum):
85
85
 
86
86
 
87
87
  @runtime_checkable
88
- class SupportsScroll(Protocol):
89
- """Protocol for scroll supporting widget.
88
+ class WidgetProto(Protocol):
89
+ """Protocol for widget.
90
90
 
91
- Due to protocol can not inherit non-protocol bases, require also several obligatory Widget methods.
91
+ Due to protocol cannot inherit non-protocol bases, define several obligatory Widget methods.
92
92
  """
93
93
 
94
94
  # Base widget methods (from Widget)
@@ -116,12 +116,27 @@ class SupportsScroll(Protocol):
116
116
 
117
117
  def render(self, size: tuple[int, int], focus: bool = False) -> Canvas: ...
118
118
 
119
- # Scroll specific methods
119
+
120
+ @runtime_checkable
121
+ class SupportsScroll(WidgetProto, Protocol):
122
+ """Scroll specific methods."""
123
+
120
124
  def get_scrollpos(self, size: tuple[int, int], focus: bool = False) -> int: ...
121
125
 
122
126
  def rows_max(self, size: tuple[int, int] | None = None, focus: bool = False) -> int: ...
123
127
 
124
128
 
129
+ @runtime_checkable
130
+ class SupportsRelativeScroll(WidgetProto, Protocol):
131
+ """Relative scroll-specific methods."""
132
+
133
+ def require_relative_scroll(self, size: tuple[int, int], focus: bool = False) -> bool: ...
134
+
135
+ def get_first_visible_pos(self, size: tuple[int, int], focus: bool = False) -> int: ...
136
+
137
+ def get_visible_amount(self, size: tuple[int, int], focus: bool = False) -> int: ...
138
+
139
+
125
140
  class Scrollable(WidgetDecoration[WrappedWidget]):
126
141
  def sizing(self) -> frozenset[Sizing]:
127
142
  return frozenset((Sizing.BOX,))
@@ -286,7 +301,7 @@ class Scrollable(WidgetDecoration[WrappedWidget]):
286
301
  ow = self._original_widget
287
302
  ow_size = self._get_original_widget_size(size)
288
303
 
289
- # Remember previous cursor position if possible
304
+ # Remember the previous cursor position if possible
290
305
  if hasattr(ow, "get_cursor_coords"):
291
306
  self._old_cursor_coords = ow.get_cursor_coords(ow_size)
292
307
 
@@ -294,7 +309,7 @@ class Scrollable(WidgetDecoration[WrappedWidget]):
294
309
  if key is None:
295
310
  return None
296
311
 
297
- # Handle up/down, page up/down, etc
312
+ # Handle up/down, page up/down, etc.
298
313
  command_map = self._command_map
299
314
  if command_map[key] == Command.UP:
300
315
  self._scroll_action = SCROLL_LINE_UP
@@ -438,10 +453,10 @@ class Scrollable(WidgetDecoration[WrappedWidget]):
438
453
  class ScrollBar(WidgetDecoration[WrappedWidget]):
439
454
  Symbols = ScrollbarSymbols
440
455
 
441
- def sizing(self):
456
+ def sizing(self) -> frozenset[Sizing]:
442
457
  return frozenset((Sizing.BOX,))
443
458
 
444
- def selectable(self):
459
+ def selectable(self) -> bool:
445
460
  return True
446
461
 
447
462
  def __init__(
@@ -483,30 +498,60 @@ class ScrollBar(WidgetDecoration[WrappedWidget]):
483
498
  ) -> Canvas:
484
499
  from urwid import canvas
485
500
 
501
+ def render_no_scrollbar() -> Canvas:
502
+ self._original_widget_size = size
503
+ return ow.render(size, focus)
504
+
505
+ def render_for_scrollbar() -> Canvas:
506
+ self._original_widget_size = ow_size
507
+ return ow.render(ow_size, focus)
508
+
486
509
  maxcol, maxrow = size
487
510
 
488
- sb_width = self._scrollbar_width
489
- ow_size = (max(0, maxcol - sb_width), maxrow)
511
+ ow_size = (max(0, maxcol - self._scrollbar_width), maxrow)
490
512
  sb_width = maxcol - ow_size[0]
491
513
 
492
514
  ow = self._original_widget
493
515
  ow_base = self.scrolling_base_widget
494
- ow_rows_max = ow_base.rows_max(size, focus)
495
- if ow_rows_max <= maxrow:
496
- # Canvas fits without scrolling - no scrollbar needed
497
- self._original_widget_size = size
498
- return ow.render(size, focus)
499
- ow_rows_max = ow_base.rows_max(ow_size, focus)
500
516
 
501
- ow_canv = ow.render(ow_size, focus)
502
- self._original_widget_size = ow_size
503
-
504
- pos = ow_base.get_scrollpos(ow_size, focus)
505
- posmax = ow_rows_max - maxrow
517
+ # Use hasattr instead of protocol: hasattr will return False in case of getattr raise AttributeError
518
+ # Use __length_hint__ first since it's less resource intensive
519
+ use_relative = (
520
+ isinstance(ow_base, SupportsRelativeScroll)
521
+ and any(hasattr(ow_base, attrib) for attrib in ("__length_hint__", "__len__"))
522
+ and ow_base.require_relative_scroll(size, focus)
523
+ )
506
524
 
507
- # Thumb shrinks/grows according to the ratio of
508
- # <number of visible lines> / <number of total lines>
509
- thumb_weight = min(1.0, maxrow / max(1, ow_rows_max))
525
+ if use_relative:
526
+ # `operator.length_hint` is Protocol (Spec) over class based and can end false-negative on the instance
527
+ # use length_hint-like approach with safe `AttributeError` handling
528
+ ow_len = getattr(ow_base, "__len__", getattr(ow_base, "__length_hint__", lambda: 0))()
529
+ ow_canv = render_for_scrollbar()
530
+ visible_amount = ow_base.get_visible_amount(ow_size, focus)
531
+ pos = ow_base.get_first_visible_pos(ow_size, focus)
532
+
533
+ # in the case of estimated length, it can be smaller than real widget length
534
+ ow_len = max(ow_len, visible_amount, pos)
535
+ posmax = ow_len - visible_amount
536
+ thumb_weight = min(1.0, visible_amount / max(1, ow_len))
537
+
538
+ if ow_len == visible_amount:
539
+ # Corner case: formally all contents indexes should be visible, but this does not mean all rows
540
+ use_relative = False
541
+
542
+ if not use_relative:
543
+ ow_rows_max = ow_base.rows_max(size, focus)
544
+ if ow_rows_max <= maxrow:
545
+ # Canvas fits without scrolling - no scrollbar needed
546
+ return render_no_scrollbar()
547
+
548
+ ow_canv = render_for_scrollbar()
549
+ ow_rows_max = ow_base.rows_max(ow_size, focus)
550
+ pos = ow_base.get_scrollpos(ow_size, focus)
551
+ posmax = ow_rows_max - maxrow
552
+ thumb_weight = min(1.0, maxrow / max(1, ow_rows_max))
553
+
554
+ # Thumb shrinks/grows according to the ratio of <number of visible lines> / <number of total lines>
510
555
  thumb_height = max(1, round(thumb_weight * maxrow))
511
556
 
512
557
  # Thumb may only touch top/bottom if the first/last row is visible
@@ -564,7 +609,7 @@ class ScrollBar(WidgetDecoration[WrappedWidget]):
564
609
  self._invalidate()
565
610
 
566
611
  @property
567
- def scrolling_base_widget(self) -> SupportsScroll:
612
+ def scrolling_base_widget(self) -> SupportsScroll | SupportsRelativeScroll:
568
613
  """Nearest `original_widget` that is compatible with the scrolling API"""
569
614
 
570
615
  def orig_iter(w: Widget) -> Iterator[Widget]:
urwid/widget/treetools.py CHANGED
@@ -43,7 +43,7 @@ from .wimp import SelectableIcon
43
43
  if typing.TYPE_CHECKING:
44
44
  from collections.abc import Hashable, Sequence
45
45
 
46
- __all__ = ("TreeWidgetError", "TreeWidget", "TreeNode", "ParentNode", "TreeWalker", "TreeListBox")
46
+ __all__ = ("ParentNode", "TreeListBox", "TreeNode", "TreeWalker", "TreeWidget", "TreeWidgetError")
47
47
 
48
48
 
49
49
  class TreeWidgetError(RuntimeError):
@@ -85,7 +85,7 @@ class TreeWidget(WidgetWrap[Padding[typing.Union[Text, Columns]]]):
85
85
  """Update display widget text for parent widgets"""
86
86
  # icon is first element in columns indented widget
87
87
  icon = [self.unexpanded_icon, self.expanded_icon][self.expanded]
88
- self._w.original_widget.contents[0] = (icon, (WHSettings.GIVEN, 1, False))
88
+ self._w.base_widget.contents[0] = (icon, (WHSettings.GIVEN, 1, False))
89
89
 
90
90
  def get_indent_cols(self) -> int:
91
91
  return self.indent_cols * self.get_node().get_depth()
@@ -496,8 +496,8 @@ class TreeListBox(ListBox):
496
496
 
497
497
  middle, top, _bottom = self.calculate_visible(size)
498
498
 
499
- row_offset, _focus_widget, _focus_pos, _focus_rows, _cursor = middle
500
- _trim_top, fill_above = top
499
+ row_offset, _focus_widget, _focus_pos, _focus_rows, _cursor = middle # pylint: disable=unpacking-non-sequence
500
+ _trim_top, fill_above = top # pylint: disable=unpacking-non-sequence
501
501
 
502
502
  for _widget, pos, rows in fill_above:
503
503
  row_offset -= rows
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: urwid
3
- Version: 2.6.8
3
+ Version: 2.6.10
4
4
  Summary: A full-featured console (xterm et al.) user interface library
5
5
  Home-page: https://urwid.org/
6
6
  Author-email: Ian Ward <ian@excess.org>
@@ -11,7 +11,7 @@ urwid/split_repr.py,sha256=pXzuddzQ4RnfIl537Gvoe8PVaBRHCPnxgdYvKK0qm8k,3899
11
11
  urwid/str_util.py,sha256=1ss-LHAhjXTynTVBcoSgYWIpBeN_RirqHYhP7fq7MrA,10844
12
12
  urwid/text_layout.py,sha256=lHiGfo7clmwHt5dMl_AaQSs2ov9IbY9JySTATZBm7h0,22821
13
13
  urwid/util.py,sha256=pGXnma03_IBx3v6_qN9cDWPXPj_YjkhQ9IxLjm_0TpU,15747
14
- urwid/version.py,sha256=jdg0k_UZTwIu69t9K_2liDiTcorgM7X8Mp2eI1v4r40,411
14
+ urwid/version.py,sha256=Q6B6SJpzx7ArqUrw_2w87t72hs-NzqHnm-ERmJDUGVM,413
15
15
  urwid/vterm.py,sha256=q9-8dxJpVr_7a0Bj_z_FYDiRgzos3IS17BjhYcy1RDw,58410
16
16
  urwid/wimp.py,sha256=o1YsjL_tBLXba8ZRr1MTHH0poLSlXT_atY3-uD_Ualg,567
17
17
  urwid/display/__init__.py,sha256=y90WbHPNRHlimPrXhzsSfFsBbBHy8QErmB1y4Xza-xo,1732
@@ -23,22 +23,22 @@ urwid/display/_win32.py,sha256=hD4hytElpfIv5qxGNTMSa2QeBNByqJDNVpmiiQSj6CM,5401
23
23
  urwid/display/_win32_raw_display.py,sha256=0PuHEhqJRl8uQmaWSOWceM1aEuolPZ-1MbHYN7s9yRY,9248
24
24
  urwid/display/common.py,sha256=_WiwXtt6KULEVLGeZvRIwmNcSfL92BPFv-uhb0_w2d0,39528
25
25
  urwid/display/curses.py,sha256=poYvs4iGPzl1jXpi8UUNyrTX2Ap9qxXPVhSu_zE4IuY,22800
26
- urwid/display/escape.py,sha256=rUIaEpyTMmZYFdn2tXn_wmecEW-D0tO59FUk0t5neZo,17968
26
+ urwid/display/escape.py,sha256=rG7x82_S91SRdfKXOEzkawDFtmVk5_KiKIY4Bh2mf7s,17826
27
27
  urwid/display/html_fragment.py,sha256=lQRqr-hTrknQyZTtzPDp9zr-TWOT4NcuSsusS-LXIOU,8547
28
28
  urwid/display/lcd.py,sha256=qXfuyZcicarrZM-O-ccPFBPjflD3_bLMv3IQzmYI43E,17607
29
29
  urwid/display/raw.py,sha256=TSkIEM6KnQHPmmpBkWKdirZOwVXhSV67k7hWmuuR2OY,1125
30
30
  urwid/display/web.py,sha256=39o12Cl79Ft669-mIbq_ZGGVzzkSfHb3Ny54mASegec,19250
31
31
  urwid/event_loop/__init__.py,sha256=6di4AYD6HbCf6v5p0rZOoteKnM9U5tfk4NKcWMH4FV4,1199
32
32
  urwid/event_loop/abstract_loop.py,sha256=6Kkw3KCJ4ndiI2I1P8fPdtHzjuekRxUuvzFOiR0Myok,5494
33
- urwid/event_loop/asyncio_loop.py,sha256=QoK1zO6YHg5dn2DPlCXSf9uXPHEe6k6P9pPJMWISB-Q,8473
33
+ urwid/event_loop/asyncio_loop.py,sha256=9B3redIwvfYzqKSbnxiocb6wx9VXLi5XB08cUJkserE,8471
34
34
  urwid/event_loop/glib_loop.py,sha256=GFoLAenoWKm-13rTBKzCF2JdCEryJKpMo25aidZ7BWs,9351
35
35
  urwid/event_loop/main_loop.py,sha256=gR2jGmqg1-CXFdNDlt-NizdS5AA7EZfmp6sW-nXwa18,26000
36
36
  urwid/event_loop/select_loop.py,sha256=5ZkIPqyIVJdWRTC89Ca5h8ub_-jpqXjFZaKqiWhdkDg,7534
37
37
  urwid/event_loop/tornado_loop.py,sha256=VNgXW2gO4edcYDjyEGb86yOHrE0NNXQUPXIOfJyZnAk,7100
38
- urwid/event_loop/trio_loop.py,sha256=2SBX1pbiJddAsXbe1itPPXYdoX5_U00m56A4h0x9dWI,10467
38
+ urwid/event_loop/trio_loop.py,sha256=46uFQ1O-AM8EaJETCsXzECDNviYMyzOsDKQoJgMHQSk,10468
39
39
  urwid/event_loop/twisted_loop.py,sha256=CtpWBzxNp_y7LHejnXIS67B-iboM3irb_MuI_FtXzc0,9219
40
40
  urwid/event_loop/zmq_loop.py,sha256=wt4lE0hnDODTVaAMuB5wCSAD5yQ6D9pj8_G_7iNGBUI,9074
41
- urwid/widget/__init__.py,sha256=Mbfr6N4sfUFoknlkMDvTbfyR65rUFkRAeNKzjz4sENY,4683
41
+ urwid/widget/__init__.py,sha256=lEFAuQKYnGKS9-dhUggItnnm9Bvbbuk7RAKvzrs8xkI,4683
42
42
  urwid/widget/attr_map.py,sha256=L6svBcabtXeSaU2cNrgDv4Nne6QYk-nvVbr88sA4WnY,6205
43
43
  urwid/widget/attr_wrap.py,sha256=pJQHVNlwcX5IgpdSmTqYOt63EX-p5hA0mJfXtWXJ6wg,4830
44
44
  urwid/widget/bar_graph.py,sha256=Ui2Z-DJ7Try9rkAQ14rK__-oSGfsWncpJH-AxSF00bk,21615
@@ -53,22 +53,22 @@ urwid/widget/filler.py,sha256=EEnyAawdKXuwf79pErfNuvqsU1SVTutcMUrwWkU4wfo,14665
53
53
  urwid/widget/frame.py,sha256=AOe4FwjvwcIwrpXqyZkCwSZWwAALNl1XRMAKx6ZXYWs,21834
54
54
  urwid/widget/grid_flow.py,sha256=otujeOTWYDr4gwuDikBm9zZd8SUgma9yvQ1hDWI-dGk,21514
55
55
  urwid/widget/line_box.py,sha256=V750xiZtkw6_uRXLhNY91ER3pXwwrZstVv_IJUZd_YY,6884
56
- urwid/widget/listbox.py,sha256=s3hg3VoiDY3pfqEmkURIE_Fa8WcLLzuaJn-_LVfG0EM,69495
57
- urwid/widget/monitored_list.py,sha256=M0PjNc_Vtuk6fnWdDelx-nbs3jLtXAo4JKB_OTk8McI,18773
56
+ urwid/widget/listbox.py,sha256=EaGo13fpt4Oy9V00n27U-2PhOWa7-nmsTgnddKv9GII,74392
57
+ urwid/widget/monitored_list.py,sha256=8lBb8kowLAqKMKByiBZp4jt9PSnXjYAoAjXFXm-7xcg,18773
58
58
  urwid/widget/overlay.py,sha256=7EtTHOomIF4xzwYiY7SiMCQZF2IHrmO88YeosFOMFo0,34505
59
59
  urwid/widget/padding.py,sha256=tdBJybrKJqR8OLo5DHixn6KZA3tefYlfo9kkqvxYLEc,21252
60
60
  urwid/widget/pile.py,sha256=aIzKYNrSQtbHVk_bn2PElHUiSdsO_NKnL4mTu-sXm4I,38628
61
61
  urwid/widget/popup.py,sha256=UQ1MTzt8RNcfIvmwIqDM4mypxxXUOMsGoiozom8HfTg,5814
62
62
  urwid/widget/progress_bar.py,sha256=HRnIu2V2_4wgsnAdrhK-GVVToyLaXRH0gtlkWllA4Mo,4767
63
- urwid/widget/scrollable.py,sha256=PZjcelZlyfl0MnxY7DHuaae_XbjuJQ1bVNPlSlmaWJI,22198
63
+ urwid/widget/scrollable.py,sha256=yB3S3k5dOdZ-wrQMlgxQH-1AZONSf5X6Y2accIa9tpk,24279
64
64
  urwid/widget/solid_fill.py,sha256=NZRDSRK0lP4r7gXcKDgVaKEoeOcWvtrY5k2aueQEEL4,1260
65
65
  urwid/widget/text.py,sha256=jy15hL6rCBoJdZviq2jJ-i9eO6vEcxvzCIVAZs12AUw,12187
66
- urwid/widget/treetools.py,sha256=7b2US3Dj_akQ2JCf5gPo1SKGfvIhnQjbkpcLfYl0Kk4,17307
66
+ urwid/widget/treetools.py,sha256=5s3Dnp2PCz4wQccutmdvsTTbAROJMLehXe4moB5POY0,17387
67
67
  urwid/widget/widget.py,sha256=dMGNVewj2NGFdGKAtDJYgOzhuJAtrr_BALOMwNzUyj0,29550
68
68
  urwid/widget/widget_decoration.py,sha256=aHP9Y7JKVCI2dw2OdRrzMj9p--C1pMIbxSJskWBLIgc,5633
69
69
  urwid/widget/wimp.py,sha256=4wfzTQBLMbhicSlL64hBPb-1W5FAFrIKfb43i10MKSY,27305
70
- urwid-2.6.8.dist-info/COPYING,sha256=NrbT-keRaUP9X-wxPFhHhJRgR-wTN6eLRA5ZkstZX4k,26434
71
- urwid-2.6.8.dist-info/METADATA,sha256=MYAhcUjx0jFXDZeMC8x8xCVYSJhAAwnLgbnQSixNIrw,11042
72
- urwid-2.6.8.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
73
- urwid-2.6.8.dist-info/top_level.txt,sha256=AwxQA43kNkjHbhYELXHBKrQ01X5CR2KnDzU07cVqilY,6
74
- urwid-2.6.8.dist-info/RECORD,,
70
+ urwid-2.6.10.dist-info/COPYING,sha256=NrbT-keRaUP9X-wxPFhHhJRgR-wTN6eLRA5ZkstZX4k,26434
71
+ urwid-2.6.10.dist-info/METADATA,sha256=o9GKYe-Nt5Hkz7EF_S6E3DXp8CblUs66umXNmAj5-jI,11043
72
+ urwid-2.6.10.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
73
+ urwid-2.6.10.dist-info/top_level.txt,sha256=AwxQA43kNkjHbhYELXHBKrQ01X5CR2KnDzU07cVqilY,6
74
+ urwid-2.6.10.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.42.0)
2
+ Generator: bdist_wheel (0.43.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5