urwid 2.4.6__cp37-cp37m-win_amd64.whl → 2.5.1__cp37-cp37m-win_amd64.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 (44) hide show
  1. urwid/__init__.py +2 -0
  2. urwid/canvas.py +34 -22
  3. urwid/command_map.py +6 -4
  4. urwid/display/_posix_raw_display.py +15 -4
  5. urwid/display/_raw_display_base.py +37 -37
  6. urwid/display/_win32_raw_display.py +15 -4
  7. urwid/display/curses.py +2 -4
  8. urwid/display/html_fragment.py +2 -4
  9. urwid/display/web.py +2 -4
  10. urwid/listbox.py +111 -26
  11. urwid/monitored_list.py +2 -4
  12. urwid/str_util.pyd +0 -0
  13. urwid/util.py +1 -2
  14. urwid/version.py +2 -2
  15. urwid/vterm.py +3 -3
  16. urwid/widget/__init__.py +4 -0
  17. urwid/widget/attr_map.py +5 -3
  18. urwid/widget/bar_graph.py +9 -9
  19. urwid/widget/box_adapter.py +6 -7
  20. urwid/widget/columns.py +17 -10
  21. urwid/widget/constants.py +169 -63
  22. urwid/widget/divider.py +27 -3
  23. urwid/widget/edit.py +1 -1
  24. urwid/widget/filler.py +9 -8
  25. urwid/widget/frame.py +9 -1
  26. urwid/widget/grid_flow.py +4 -6
  27. urwid/widget/line_box.py +64 -19
  28. urwid/widget/overlay.py +1 -1
  29. urwid/widget/padding.py +11 -8
  30. urwid/widget/pile.py +10 -7
  31. urwid/widget/popup.py +16 -6
  32. urwid/widget/progress_bar.py +13 -6
  33. urwid/widget/scrollable.py +599 -0
  34. urwid/widget/solid_fill.py +3 -1
  35. urwid/widget/text.py +1 -1
  36. urwid/widget/widget.py +51 -113
  37. urwid/widget/widget_decoration.py +21 -12
  38. urwid/widget/wimp.py +12 -20
  39. {urwid-2.4.6.dist-info → urwid-2.5.1.dist-info}/METADATA +7 -2
  40. urwid-2.5.1.dist-info/RECORD +76 -0
  41. urwid-2.4.6.dist-info/RECORD +0 -75
  42. {urwid-2.4.6.dist-info → urwid-2.5.1.dist-info}/COPYING +0 -0
  43. {urwid-2.4.6.dist-info → urwid-2.5.1.dist-info}/WHEEL +0 -0
  44. {urwid-2.4.6.dist-info → urwid-2.5.1.dist-info}/top_level.txt +0 -0
urwid/__init__.py CHANGED
@@ -185,6 +185,8 @@ from urwid.widget import (
185
185
  PopUpTarget,
186
186
  ProgressBar,
187
187
  RadioButton,
188
+ Scrollable,
189
+ ScrollBar,
188
190
  SelectableIcon,
189
191
  Sizing,
190
192
  SolidFill,
urwid/canvas.py CHANGED
@@ -40,7 +40,7 @@ from urwid.util import (
40
40
  )
41
41
 
42
42
  if typing.TYPE_CHECKING:
43
- from collections.abc import Sequence
43
+ from collections.abc import Hashable, Iterable, Sequence
44
44
 
45
45
  from .widget import Widget
46
46
 
@@ -294,7 +294,7 @@ class Canvas:
294
294
  cols: int | None = None,
295
295
  rows: int | None = None,
296
296
  attr=None,
297
- ):
297
+ ) -> Iterable[list[tuple[object, object, bytes]]]:
298
298
  raise NotImplementedError()
299
299
 
300
300
  def cols(self):
@@ -489,10 +489,10 @@ class TextCanvas(Canvas):
489
489
  self,
490
490
  trim_left: int = 0,
491
491
  trim_top: int = 0,
492
- cols: int = 0,
493
- rows: int = 0,
494
- attr_map=None,
495
- ):
492
+ cols: int | None = 0,
493
+ rows: int | None = 0,
494
+ attr=None,
495
+ ) -> Iterable[tuple[object, object, bytes]]:
496
496
  """
497
497
  Return the canvas content as a list of rows where each row
498
498
  is a list of (attr, cs, text) tuples.
@@ -534,8 +534,8 @@ class TextCanvas(Canvas):
534
534
  i = 0
535
535
  row = []
536
536
  for (a, cs), run in attr_cs:
537
- if attr_map and a in attr_map:
538
- a = attr_map[a] # noqa: PLW2901
537
+ if attr and a in attr:
538
+ a = attr[a] # noqa: PLW2901
539
539
  row.append((a, cs, text[i : i + run]))
540
540
  i += run
541
541
  yield row
@@ -566,10 +566,10 @@ class BlankCanvas(Canvas):
566
566
  self,
567
567
  trim_left: int = 0,
568
568
  trim_top: int = 0,
569
- cols: int = 0,
570
- rows: int = 0,
569
+ cols: int | None = 0,
570
+ rows: int | None = 0,
571
571
  attr=None,
572
- ):
572
+ ) -> Iterable[list[tuple[object, object, bytes]]]:
573
573
  """
574
574
  return (cols, rows) of spaces with default attributes.
575
575
  """
@@ -621,7 +621,7 @@ class SolidCanvas(Canvas):
621
621
  cols: int | None = None,
622
622
  rows: int | None = None,
623
623
  attr=None,
624
- ):
624
+ ) -> Iterable[list[tuple[object, object, bytes]]]:
625
625
  if cols is None:
626
626
  cols = self.size[0]
627
627
  if rows is None:
@@ -668,7 +668,12 @@ class CompositeCanvas(Canvas):
668
668
  super().__init__()
669
669
 
670
670
  if canv is None:
671
- self.shards: list[tuple[int, list[tuple[int, int, int, int, typing.Any, Canvas]]]] = []
671
+ self.shards: list[
672
+ tuple[
673
+ int,
674
+ list[tuple[int, int, int, int, dict[Hashable | None, Hashable] | None, Canvas]],
675
+ ]
676
+ ] = []
672
677
  self.children: list[tuple[int, int, Canvas, typing.Any]] = []
673
678
  else:
674
679
  if hasattr(canv, "shards"):
@@ -712,7 +717,14 @@ class CompositeCanvas(Canvas):
712
717
  raise TypeError(cols)
713
718
  return cols
714
719
 
715
- def content(self):
720
+ def content(
721
+ self,
722
+ trim_left: int = 0,
723
+ trim_top: int = 0,
724
+ cols: int | None = None,
725
+ rows: int | None = None,
726
+ attr=None,
727
+ ) -> Iterable[list[tuple[object, object, bytes]]]:
716
728
  """
717
729
  Return the canvas content as a list of rows where each row
718
730
  is a list of (attr, cs, text) tuples.
@@ -889,14 +901,14 @@ class CompositeCanvas(Canvas):
889
901
 
890
902
  self.coords.update(other.translate_coords(left, top))
891
903
 
892
- def fill_attr(self, a) -> None:
904
+ def fill_attr(self, a: Hashable) -> None:
905
+ """
906
+ Apply attribute a to all areas of this canvas with default attribute currently set to None,
907
+ leaving other attributes intact.
893
908
  """
894
- Apply attribute a to all areas of this canvas with default
895
- attribute currently set to None, leaving other attributes
896
- intact."""
897
909
  self.fill_attr_apply({None: a})
898
910
 
899
- def fill_attr_apply(self, mapping) -> None:
911
+ def fill_attr_apply(self, mapping: dict[Hashable | None, Hashable]) -> None:
900
912
  """
901
913
  Apply an attribute-mapping dictionary to the canvas.
902
914
 
@@ -1209,7 +1221,7 @@ def cview_trim_cols(cv, cols: int):
1209
1221
  return cv[:2] + (cols,) + cv[3:]
1210
1222
 
1211
1223
 
1212
- def CanvasCombine(canvas_info):
1224
+ def CanvasCombine(canvas_info: Iterable[tuple[Canvas, typing.Any, bool]]) -> CompositeCanvas:
1213
1225
  """Stack canvases in l vertically and return resulting canvas.
1214
1226
 
1215
1227
  :param canvas_info: list of (canvas, position, focus) tuples:
@@ -1245,7 +1257,7 @@ def CanvasCombine(canvas_info):
1245
1257
  return combined_canvas
1246
1258
 
1247
1259
 
1248
- def CanvasOverlay(top_c, bottom_c, left: int, top: int):
1260
+ def CanvasOverlay(top_c, bottom_c, left: int, top: int) -> CompositeCanvas:
1249
1261
  """
1250
1262
  Overlay canvas top_c onto bottom_c at position (left, top).
1251
1263
  """
@@ -1258,7 +1270,7 @@ def CanvasOverlay(top_c, bottom_c, left: int, top: int):
1258
1270
  return overlayed_canvas
1259
1271
 
1260
1272
 
1261
- def CanvasJoin(canvas_info):
1273
+ def CanvasJoin(canvas_info: Iterable[tuple[Canvas, typing.Any, bool, int]]) -> CompositeCanvas:
1262
1274
  """
1263
1275
  Join canvases in l horizontally. Return result.
1264
1276
 
urwid/command_map.py CHANGED
@@ -39,6 +39,8 @@ class Command(str, enum.Enum):
39
39
  MAX_RIGHT = "cursor max right"
40
40
  ACTIVATE = "activate"
41
41
  MENU = "menu"
42
+ SELECT_NEXT = "next selectable"
43
+ SELECT_PREVIOUS = "prev selectable"
42
44
 
43
45
 
44
46
  REDRAW_SCREEN = Command.REDRAW_SCREEN
@@ -84,10 +86,10 @@ class CommandMap(typing.Mapping[str, typing.Union[str, Command, None]]):
84
86
  return len(self._command)
85
87
 
86
88
  _command_defaults: typing.ClassVar[dict[str, str | Command]] = {
87
- "tab": "next selectable",
88
- "ctrl n": "next selectable",
89
- "shift tab": "prev selectable",
90
- "ctrl p": "prev selectable",
89
+ "tab": Command.SELECT_NEXT,
90
+ "ctrl n": Command.SELECT_NEXT,
91
+ "shift tab": Command.SELECT_PREVIOUS,
92
+ "ctrl p": Command.SELECT_PREVIOUS,
91
93
  "ctrl l": Command.REDRAW_SCREEN,
92
94
  "esc": Command.MENU,
93
95
  "up": Command.UP,
@@ -28,6 +28,7 @@ import contextlib
28
28
  import fcntl
29
29
  import functools
30
30
  import os
31
+ import selectors
31
32
  import signal
32
33
  import struct
33
34
  import sys
@@ -289,14 +290,24 @@ class Screen(_raw_display_base.Screen):
289
290
  raise
290
291
  return codes
291
292
 
292
- def _getch(self, timeout: int) -> int:
293
+ def _read_raw_input(self, timeout: int) -> bytearray:
293
294
  ready = self._wait_for_input_ready(timeout)
294
295
  if self.gpm_mev is not None and self.gpm_mev.stdout.fileno() in ready:
295
296
  self.gpm_event_pending = True
296
297
  fd = self._input_fileno()
297
- if fd is not None and fd in ready:
298
- return ord(os.read(fd, 1))
299
- return -1
298
+ chars = bytearray()
299
+
300
+ if fd is None or fd not in ready:
301
+ return chars
302
+
303
+ with selectors.DefaultSelector() as selector:
304
+ selector.register(fd, selectors.EVENT_READ)
305
+ input_ready = selector.select(0)
306
+ while input_ready:
307
+ chars.extend(os.read(fd, 1024))
308
+ input_ready = selector.select(0)
309
+
310
+ return chars
300
311
 
301
312
  def _encode_gpm_event(self) -> list[int]:
302
313
  self.gpm_event_pending = False
@@ -28,6 +28,7 @@ import abc
28
28
  import contextlib
29
29
  import functools
30
30
  import os
31
+ import platform
31
32
  import selectors
32
33
  import signal
33
34
  import socket
@@ -41,7 +42,7 @@ from .common import UNPRINTABLE_TRANS_TABLE, UPDATE_PALETTE_ENTRY, AttrSpec, Bas
41
42
 
42
43
  if typing.TYPE_CHECKING:
43
44
  import io
44
- from collections.abc import Callable
45
+ from collections.abc import Callable, Iterable
45
46
  from types import FrameType
46
47
 
47
48
  from typing_extensions import Literal
@@ -49,6 +50,7 @@ if typing.TYPE_CHECKING:
49
50
  from urwid import Canvas, EventLoop
50
51
 
51
52
  IS_WINDOWS = sys.platform == "win32"
53
+ IS_WSL = (sys.platform == "linux") and ("wsl" in platform.platform().lower())
52
54
 
53
55
 
54
56
  class Screen(BaseScreen, RealTerminal):
@@ -227,12 +229,10 @@ class Screen(BaseScreen, RealTerminal):
227
229
  self._term_output_file.flush()
228
230
 
229
231
  @typing.overload
230
- def get_input(self, raw_keys: Literal[False]) -> list[str]:
231
- ...
232
+ def get_input(self, raw_keys: Literal[False]) -> list[str]: ...
232
233
 
233
234
  @typing.overload
234
- def get_input(self, raw_keys: Literal[True]) -> tuple[list[str], list[int]]:
235
- ...
235
+ def get_input(self, raw_keys: Literal[True]) -> tuple[list[str], list[int]]: ...
236
236
 
237
237
  def get_input(self, raw_keys: bool = False) -> list[str] | tuple[list[str], list[int]]:
238
238
  """Return pending input as a list.
@@ -373,7 +373,7 @@ class Screen(BaseScreen, RealTerminal):
373
373
  return wrapper
374
374
 
375
375
  def _get_input_codes(self) -> list[int]:
376
- return self._get_keyboard_codes()
376
+ return list(self._get_keyboard_codes())
377
377
 
378
378
  def get_available_raw_input(self) -> list[int]:
379
379
  """
@@ -406,8 +406,7 @@ class Screen(BaseScreen, RealTerminal):
406
406
  callback: None,
407
407
  codes: list[int],
408
408
  wait_for_more: bool = ...,
409
- ) -> tuple[list[str], list[int]]:
410
- ...
409
+ ) -> tuple[list[str], list[int]]: ...
411
410
 
412
411
  @typing.overload
413
412
  def parse_input(
@@ -416,8 +415,7 @@ class Screen(BaseScreen, RealTerminal):
416
415
  callback: None,
417
416
  codes: list[int],
418
417
  wait_for_more: bool = ...,
419
- ) -> tuple[list[str], list[int]]:
420
- ...
418
+ ) -> tuple[list[str], list[int]]: ...
421
419
 
422
420
  @typing.overload
423
421
  def parse_input(
@@ -426,8 +424,7 @@ class Screen(BaseScreen, RealTerminal):
426
424
  callback: Callable[[list[str], list[int]], typing.Any],
427
425
  codes: list[int],
428
426
  wait_for_more: bool = ...,
429
- ) -> None:
430
- ...
427
+ ) -> None: ...
431
428
 
432
429
  def parse_input(
433
430
  self,
@@ -496,15 +493,6 @@ class Screen(BaseScreen, RealTerminal):
496
493
  # For get_input
497
494
  return decoded_codes, raw_codes
498
495
 
499
- def _get_keyboard_codes(self) -> list[int]:
500
- codes = []
501
- while True:
502
- code = self._getch_nodelay()
503
- if code < 0:
504
- break
505
- codes.append(code)
506
- return codes
507
-
508
496
  def _wait_for_input_ready(self, timeout: float | None) -> list[int]:
509
497
  logger = self.logger.getChild("wait_for_input_ready")
510
498
  fd_list = self.get_input_descriptors()
@@ -521,11 +509,10 @@ class Screen(BaseScreen, RealTerminal):
521
509
  return [event.fd for event, _ in ready]
522
510
 
523
511
  @abc.abstractmethod
524
- def _getch(self, timeout: int) -> int:
525
- ...
512
+ def _read_raw_input(self, timeout: int) -> Iterable[int]: ...
526
513
 
527
- def _getch_nodelay(self) -> int:
528
- return self._getch(0)
514
+ def _get_keyboard_codes(self) -> Iterable[int]:
515
+ return self._read_raw_input(0)
529
516
 
530
517
  def _setup_G1(self) -> None:
531
518
  """
@@ -609,10 +596,10 @@ class Screen(BaseScreen, RealTerminal):
609
596
  if isinstance(a, AttrSpec):
610
597
  return self._attrspec_to_escape(a)
611
598
  # undefined attributes use default/default
612
- # TODO: track and report these
599
+ self.logger.debug(f"Undefined attribute: {a!r}")
613
600
  return self._attrspec_to_escape(AttrSpec("default", "default"))
614
601
 
615
- def using_standout_or_underline(a):
602
+ def using_standout_or_underline(a: AttrSpec | str) -> bool:
616
603
  a = self._pal_attrspec.get(a, a)
617
604
  return isinstance(a, AttrSpec) and (a.standout or a.underline)
618
605
 
@@ -655,14 +642,17 @@ class Screen(BaseScreen, RealTerminal):
655
642
  first = True
656
643
  lasta = lastcs = None
657
644
  for a, cs, run in row:
658
- if not isinstance(run, bytes): # canvases should render with bytes
645
+ if not isinstance(run, bytes): # canvases render with bytes
659
646
  raise TypeError(run)
647
+
660
648
  if cs != "U":
661
649
  run = run.translate(UNPRINTABLE_TRANS_TABLE) # noqa: PLW2901
650
+
662
651
  if first or lasta != a:
663
652
  o.append(attr_to_escape(a))
664
653
  lasta = a
665
- if first or lastcs != cs:
654
+
655
+ if not (IS_WINDOWS or IS_WSL) and (first or lastcs != cs):
666
656
  if cs not in {None, "0", "U"}:
667
657
  raise ValueError(cs)
668
658
  if lastcs == "U":
@@ -675,23 +665,33 @@ class Screen(BaseScreen, RealTerminal):
675
665
  else:
676
666
  o.append(escape.SO)
677
667
  lastcs = cs
668
+
678
669
  o.append(run)
679
670
  first = False
671
+
680
672
  if ins:
681
673
  (inserta, insertcs, inserttext) = ins
682
674
  ias = attr_to_escape(inserta)
683
675
  if insertcs not in {None, "0", "U"}:
684
676
  raise ValueError(insertcs)
685
- if cs is None:
686
- icss = escape.SI
687
- elif cs == "U":
688
- icss = escape.IBMPC_ON
689
- else:
690
- icss = escape.SO
691
- o += ["\x08" * back, ias, icss, escape.INSERT_ON, inserttext, escape.INSERT_OFF]
692
677
 
693
- if cs == "U":
678
+ o.extend(("\x08" * back, ias))
679
+
680
+ if not (IS_WINDOWS or IS_WSL):
681
+ if cs is None:
682
+ icss = escape.SI
683
+ elif cs == "U":
684
+ icss = escape.IBMPC_ON
685
+ else:
686
+ icss = escape.SO
687
+
688
+ o.append(icss)
689
+
690
+ o += [escape.INSERT_ON, inserttext, escape.INSERT_OFF]
691
+
692
+ if not (IS_WINDOWS or IS_WSL) and cs == "U":
694
693
  o.append(escape.IBMPC_OFF)
694
+
695
695
  if whitespace_at_end:
696
696
  o.append(escape.ERASE_IN_LINE_RIGHT)
697
697
 
@@ -27,6 +27,7 @@ from __future__ import annotations
27
27
  import contextlib
28
28
  import functools
29
29
  import logging
30
+ import selectors
30
31
  import socket
31
32
  import sys
32
33
  import threading
@@ -177,13 +178,23 @@ class Screen(_raw_display_base.Screen):
177
178
 
178
179
  _input_thread: ReadInputThread | None = None
179
180
 
180
- def _getch(self, timeout: int) -> int:
181
+ def _read_raw_input(self, timeout: int) -> bytearray:
181
182
  ready = self._wait_for_input_ready(timeout)
182
183
 
183
184
  fd = self._input_fileno()
184
- if fd is not None and fd in ready:
185
- return ord(self._term_input_file.recv(1))
186
- return -1
185
+ chars = bytearray()
186
+
187
+ if fd is None or fd not in ready:
188
+ return chars
189
+
190
+ with selectors.DefaultSelector() as selector:
191
+ selector.register(fd, selectors.EVENT_READ)
192
+ input_ready = selector.select(0)
193
+ while input_ready:
194
+ chars.extend(self._term_input_file.recv(1024))
195
+ input_ready = selector.select(0)
196
+
197
+ return chars
187
198
 
188
199
  def get_cols_rows(self) -> tuple[int, int]:
189
200
  """Return the terminal dimensions (num columns, num rows)."""
urwid/display/curses.py CHANGED
@@ -295,12 +295,10 @@ class Screen(BaseScreen, RealTerminal):
295
295
  self.resize_tenths = convert_to_tenths(resize_wait)
296
296
 
297
297
  @typing.overload
298
- def get_input(self, raw_keys: Literal[False]) -> list[str]:
299
- ...
298
+ def get_input(self, raw_keys: Literal[False]) -> list[str]: ...
300
299
 
301
300
  @typing.overload
302
- def get_input(self, raw_keys: Literal[True]) -> tuple[list[str], list[int]]:
303
- ...
301
+ def get_input(self, raw_keys: Literal[True]) -> tuple[list[str], list[int]]: ...
304
302
 
305
303
  def get_input(self, raw_keys: bool = False) -> list[str] | tuple[list[str], list[int]]:
306
304
  """Return pending input as a list.
@@ -135,12 +135,10 @@ class HtmlGenerator(BaseScreen):
135
135
  return self.sizes.pop(0)
136
136
 
137
137
  @typing.overload
138
- def get_input(self, raw_keys: Literal[False]) -> list[str]:
139
- ...
138
+ def get_input(self, raw_keys: Literal[False]) -> list[str]: ...
140
139
 
141
140
  @typing.overload
142
- def get_input(self, raw_keys: Literal[True]) -> tuple[list[str], list[int]]:
143
- ...
141
+ def get_input(self, raw_keys: Literal[True]) -> tuple[list[str], list[int]]: ...
144
142
 
145
143
  def get_input(self, raw_keys: bool = False) -> list[str] | tuple[list[str], list[int]]:
146
144
  """Return the next list of keypresses in HtmlGenerator.keys."""
urwid/display/web.py CHANGED
@@ -418,12 +418,10 @@ class Screen(BaseScreen):
418
418
  return self.screen_size
419
419
 
420
420
  @typing.overload
421
- def get_input(self, raw_keys: Literal[False]) -> list[str]:
422
- ...
421
+ def get_input(self, raw_keys: Literal[False]) -> list[str]: ...
423
422
 
424
423
  @typing.overload
425
- def get_input(self, raw_keys: Literal[True]) -> tuple[list[str], list[int]]:
426
- ...
424
+ def get_input(self, raw_keys: Literal[True]) -> tuple[list[str], list[int]]: ...
427
425
 
428
426
  def get_input(self, raw_keys: bool = False) -> list[str] | tuple[list[str], list[int]]:
429
427
  """Return pending input as a list."""