urwid 2.6.15__py3-none-any.whl → 3.0.5__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.
Files changed (62) hide show
  1. urwid/__init__.py +30 -20
  2. urwid/canvas.py +34 -53
  3. urwid/command_map.py +6 -4
  4. urwid/container.py +1 -1
  5. urwid/decoration.py +1 -1
  6. urwid/display/__init__.py +53 -48
  7. urwid/display/_posix_raw_display.py +20 -8
  8. urwid/display/_raw_display_base.py +21 -16
  9. urwid/display/_win32_raw_display.py +16 -17
  10. urwid/display/common.py +45 -74
  11. urwid/display/curses.py +3 -5
  12. urwid/display/escape.py +28 -13
  13. urwid/display/lcd.py +8 -10
  14. urwid/display/web.py +11 -16
  15. urwid/event_loop/asyncio_loop.py +35 -15
  16. urwid/event_loop/main_loop.py +18 -23
  17. urwid/event_loop/tornado_loop.py +4 -5
  18. urwid/event_loop/trio_loop.py +1 -1
  19. urwid/font.py +19 -22
  20. urwid/numedit.py +65 -65
  21. urwid/signals.py +19 -27
  22. urwid/split_repr.py +9 -3
  23. urwid/str_util.py +105 -60
  24. urwid/text_layout.py +14 -13
  25. urwid/util.py +8 -19
  26. urwid/version.py +22 -4
  27. urwid/vterm.py +20 -47
  28. urwid/widget/__init__.py +0 -6
  29. urwid/widget/attr_map.py +10 -10
  30. urwid/widget/attr_wrap.py +11 -13
  31. urwid/widget/bar_graph.py +3 -8
  32. urwid/widget/big_text.py +8 -9
  33. urwid/widget/box_adapter.py +6 -6
  34. urwid/widget/columns.py +52 -83
  35. urwid/widget/container.py +29 -75
  36. urwid/widget/divider.py +6 -6
  37. urwid/widget/edit.py +50 -50
  38. urwid/widget/filler.py +14 -14
  39. urwid/widget/frame.py +31 -40
  40. urwid/widget/grid_flow.py +25 -110
  41. urwid/widget/line_box.py +31 -18
  42. urwid/widget/listbox.py +16 -51
  43. urwid/widget/monitored_list.py +75 -49
  44. urwid/widget/overlay.py +4 -37
  45. urwid/widget/padding.py +31 -68
  46. urwid/widget/pile.py +179 -158
  47. urwid/widget/popup.py +2 -2
  48. urwid/widget/progress_bar.py +17 -18
  49. urwid/widget/scrollable.py +26 -34
  50. urwid/widget/solid_fill.py +3 -3
  51. urwid/widget/text.py +44 -30
  52. urwid/widget/treetools.py +27 -48
  53. urwid/widget/widget.py +13 -130
  54. urwid/widget/widget_decoration.py +6 -35
  55. urwid/widget/wimp.py +61 -61
  56. urwid/wimp.py +1 -1
  57. {urwid-2.6.15.dist-info → urwid-3.0.5.dist-info}/METADATA +24 -24
  58. urwid-3.0.5.dist-info/RECORD +74 -0
  59. {urwid-2.6.15.dist-info → urwid-3.0.5.dist-info}/WHEEL +1 -1
  60. urwid-2.6.15.dist-info/RECORD +0 -74
  61. {urwid-2.6.15.dist-info → urwid-3.0.5.dist-info/licenses}/COPYING +0 -0
  62. {urwid-2.6.15.dist-info → urwid-3.0.5.dist-info}/top_level.txt +0 -0
@@ -172,7 +172,7 @@ class Screen(BaseScreen, RealTerminal):
172
172
  After calling this function get_input will include mouse
173
173
  click events along with keystrokes.
174
174
  """
175
- enable = bool(enable) # noqa: FURB123,RUF100
175
+ enable = bool(enable)
176
176
  if enable == self._mouse_tracking_enabled:
177
177
  return
178
178
 
@@ -211,7 +211,7 @@ class Screen(BaseScreen, RealTerminal):
211
211
  Restore the screen.
212
212
  """
213
213
 
214
- def write(self, data):
214
+ def write(self, data: str) -> None:
215
215
  """Write some data to the terminal.
216
216
 
217
217
  You may wish to override this if you're using something other than
@@ -219,7 +219,7 @@ class Screen(BaseScreen, RealTerminal):
219
219
  """
220
220
  self._term_output_file.write(data)
221
221
 
222
- def flush(self):
222
+ def flush(self) -> None:
223
223
  """Flush the output buffer.
224
224
 
225
225
  You may wish to override this if you're using something other than
@@ -323,8 +323,8 @@ class Screen(BaseScreen, RealTerminal):
323
323
  return []
324
324
 
325
325
  fd_list: list[socket.socket | typing.IO | int] = [self._resize_pipe_rd]
326
- input_io = self._term_input_io
327
- if input_io is not None:
326
+
327
+ if (input_io := self._term_input_io) is not None:
328
328
  fd_list.append(input_io)
329
329
  return fd_list
330
330
 
@@ -352,7 +352,11 @@ class Screen(BaseScreen, RealTerminal):
352
352
 
353
353
  _input_timeout = None
354
354
 
355
- def _make_legacy_input_wrapper(self, event_loop, callback):
355
+ def _make_legacy_input_wrapper(
356
+ self,
357
+ event_loop: EventLoop,
358
+ callback: Callable[[list[str], list[int]], typing.Any],
359
+ ) -> Callable[[], None]:
356
360
  """
357
361
  Support old Screen classes that still have a get_input_nonblocking and expect it to work.
358
362
  """
@@ -641,7 +645,7 @@ class Screen(BaseScreen, RealTerminal):
641
645
  a, cs, run = row[-1]
642
646
  if run[-1:] == b" " and self.back_color_erase and not using_standout_or_underline(a):
643
647
  whitespace_at_end = True
644
- row = row[:-1] + [(a, cs, run.rstrip(b" "))] # noqa: PLW2901
648
+ row = [*row[:-1], (a, cs, run.rstrip(b" "))] # noqa: PLW2901
645
649
  elif y == maxrow - 1 and maxcol > 1:
646
650
  row, back, ins = self._last_row(row) # noqa: PLW2901
647
651
 
@@ -727,7 +731,10 @@ class Screen(BaseScreen, RealTerminal):
727
731
  self.screen_buf = sb
728
732
  self._screen_buf_canvas = canvas
729
733
 
730
- def _last_row(self, row: list[tuple[object, Literal["0", "U"] | None, bytes]]) -> tuple[
734
+ def _last_row(
735
+ self,
736
+ row: list[tuple[object, Literal["0", "U"] | None, bytes]],
737
+ ) -> tuple[
731
738
  list[tuple[object, Literal["0", "U"] | None, bytes]],
732
739
  int,
733
740
  tuple[object, Literal["0", "U"] | None, bytes],
@@ -783,9 +790,9 @@ class Screen(BaseScreen, RealTerminal):
783
790
  >>> s = Screen()
784
791
  >>> s.set_terminal_properties(colors=256)
785
792
  >>> a2e = s._attrspec_to_escape
786
- >>> a2e(s.AttrSpec('brown', 'dark green'))
793
+ >>> a2e(s.AttrSpec("brown", "dark green"))
787
794
  '\\x1b[0;33;42m'
788
- >>> a2e(s.AttrSpec('#fea,underline', '#d0d'))
795
+ >>> a2e(s.AttrSpec("#fea,underline", "#d0d"))
789
796
  '\\x1b[0;38;5;229;4;48;5;164m'
790
797
  """
791
798
  if self.term == "fbterm":
@@ -870,9 +877,8 @@ class Screen(BaseScreen, RealTerminal):
870
877
 
871
878
  def reset_default_terminal_palette(self) -> None:
872
879
  """
873
- Attempt to set the terminal palette to default values as taken
874
- from xterm. Uses number of colors from current
875
- set_terminal_properties() screen setting.
880
+ Attempt to set the terminal palette to default values as taken from xterm.
881
+ Uses a number of colors from the current set_terminal_properties() screen setting.
876
882
  """
877
883
  if self.colors == 1:
878
884
  return
@@ -895,9 +901,8 @@ class Screen(BaseScreen, RealTerminal):
895
901
  """
896
902
  entries - list of (index, red, green, blue) tuples.
897
903
 
898
- Attempt to set part of the terminal palette (this does not work
899
- on all terminals.) The changes are sent as a single escape
900
- sequence so they should all take effect at the same time.
904
+ Attempt to set part of the terminal palette (this does not work on all terminals.)
905
+ The changes are sent as a single escape sequence so they should all take effect at the same time.
901
906
 
902
907
  0 <= index < 256 (some terminals will only have 16 or 88 colors)
903
908
  0 <= red, green, blue < 256
@@ -94,11 +94,10 @@ class Screen(_raw_display_base.Screen):
94
94
  self._dwOriginalInMode.value | _win32.ENABLE_WINDOW_INPUT | _win32.ENABLE_VIRTUAL_TERMINAL_INPUT
95
95
  )
96
96
 
97
- ok = _win32.SetConsoleMode(handle_out, dword_out_mode)
98
- if not ok:
97
+ if not (ok := _win32.SetConsoleMode(handle_out, dword_out_mode)):
99
98
  raise RuntimeError(f"ConsoleMode set failed for output. Err: {ok!r}")
100
- ok = _win32.SetConsoleMode(handle_in, dword_in_mode)
101
- if not ok:
99
+
100
+ if not (ok := _win32.SetConsoleMode(handle_in, dword_in_mode)):
102
101
  raise RuntimeError(f"ConsoleMode set failed for input. Err: {ok!r}")
103
102
  self._alternate_buffer = alternate_buffer
104
103
  self._next_timeout = self.max_wait
@@ -121,11 +120,11 @@ class Screen(_raw_display_base.Screen):
121
120
 
122
121
  handle_out = _win32.GetStdHandle(_win32.STD_OUTPUT_HANDLE)
123
122
  handle_in = _win32.GetStdHandle(_win32.STD_INPUT_HANDLE)
124
- ok = _win32.SetConsoleMode(handle_out, self._dwOriginalOutMode)
125
- if not ok:
123
+
124
+ if not (ok := _win32.SetConsoleMode(handle_out, self._dwOriginalOutMode)):
126
125
  raise RuntimeError(f"ConsoleMode set failed for output. Err: {ok!r}")
127
- ok = _win32.SetConsoleMode(handle_in, self._dwOriginalInMode)
128
- if not ok:
126
+
127
+ if not (ok := _win32.SetConsoleMode(handle_in, self._dwOriginalInMode)):
129
128
  raise RuntimeError(f"ConsoleMode set failed for input. Err: {ok!r}")
130
129
 
131
130
  super()._stop()
@@ -188,10 +187,9 @@ class Screen(_raw_display_base.Screen):
188
187
 
189
188
  with selectors.DefaultSelector() as selector:
190
189
  selector.register(fd, selectors.EVENT_READ)
191
- input_ready = selector.select(0)
192
- while input_ready:
190
+
191
+ while selector.select(0):
193
192
  chars.extend(self._term_input_file.recv(1024))
194
- input_ready = selector.select(0)
195
193
 
196
194
  return chars
197
195
 
@@ -204,8 +202,8 @@ class Screen(_raw_display_base.Screen):
204
202
  raise RuntimeError("Unexpected terminal output file")
205
203
  handle = _win32.GetStdHandle(_win32.STD_OUTPUT_HANDLE)
206
204
  info = _win32.CONSOLE_SCREEN_BUFFER_INFO()
207
- ok = _win32.GetConsoleScreenBufferInfo(handle, byref(info))
208
- if ok:
205
+
206
+ if _win32.GetConsoleScreenBufferInfo(handle, byref(info)):
209
207
  # Fallback will be used in case of term size could not be determined
210
208
  y, x = info.dwSize.Y, info.dwSize.X
211
209
 
@@ -246,11 +244,12 @@ class ReadInputThread(threading.Thread):
246
244
  if not inp.Event.KeyEvent.bKeyDown:
247
245
  continue
248
246
 
249
- input_data = inp.Event.KeyEvent.uChar.AsciiChar
250
- # On Windows atomic press/release of modifier keys produce phantom input with code NULL
247
+ input_data = inp.Event.KeyEvent.uChar.UnicodeChar
248
+ # On Windows atomic press/release of modifier keys produce phantom input with code NULL.
251
249
  # This input cannot be decoded and should be handled as garbage.
252
- if input_data != b"\x00":
253
- self._input.send(input_data)
250
+ input_bytes = input_data.encode("utf-8")
251
+ if input_bytes != b"\x00":
252
+ self._input.send(input_bytes)
254
253
 
255
254
  elif inp.EventType == _win32.EventType.WINDOW_BUFFER_SIZE_EVENT:
256
255
  self._resize()
urwid/display/common.py CHANGED
@@ -361,19 +361,21 @@ def _color_desc_88(num: int) -> str:
361
361
 
362
362
 
363
363
  def _parse_color_true(desc: str) -> int | None:
364
- c = _parse_color_256(desc)
365
- if c is not None:
364
+ if (c := _parse_color_256(desc)) is not None:
366
365
  (r, g, b) = _COLOR_VALUES_256[c]
367
366
  return (r << 16) + (g << 8) + b
368
367
 
369
368
  if not desc.startswith("#"):
370
369
  return None
370
+
371
371
  if len(desc) == 7:
372
372
  h = desc[1:]
373
373
  return int(h, 16)
374
+
374
375
  if len(desc) == 4:
375
376
  h = f"0x{desc[1]}0{desc[2]}0{desc[3]}"
376
377
  return int(h, 16)
378
+
377
379
  return None
378
380
 
379
381
 
@@ -387,13 +389,13 @@ def _parse_color_256(desc: str) -> int | None:
387
389
 
388
390
  Returns None if desc is invalid.
389
391
 
390
- >>> _parse_color_256('h142')
392
+ >>> _parse_color_256("h142")
391
393
  142
392
- >>> _parse_color_256('#f00')
394
+ >>> _parse_color_256("#f00")
393
395
  196
394
- >>> _parse_color_256('g100')
396
+ >>> _parse_color_256("g100")
395
397
  231
396
- >>> _parse_color_256('g#80')
398
+ >>> _parse_color_256("g#80")
397
399
  244
398
400
  """
399
401
  if len(desc) > 4:
@@ -409,21 +411,21 @@ def _parse_color_256(desc: str) -> int | None:
409
411
 
410
412
  if desc.startswith("#") and len(desc) == 4:
411
413
  # color-cube coordinates
412
- rgb = int(desc[1:], 16)
413
- if rgb < 0:
414
- return None
415
- b, rgb = rgb % 16, rgb // 16
416
- g, r = rgb % 16, rgb // 16
417
- # find the closest rgb values
418
- r = _CUBE_256_LOOKUP_16[r]
419
- g = _CUBE_256_LOOKUP_16[g]
420
- b = _CUBE_256_LOOKUP_16[b]
421
- return _CUBE_START + (r * _CUBE_SIZE_256 + g) * _CUBE_SIZE_256 + b
414
+ if (rgb := int(desc[1:], 16)) >= 0:
415
+ b, rgb = rgb % 16, rgb // 16
416
+ g, r = rgb % 16, rgb // 16
417
+ # find the closest rgb values
418
+ r = _CUBE_256_LOOKUP_16[r]
419
+ g = _CUBE_256_LOOKUP_16[g]
420
+ b = _CUBE_256_LOOKUP_16[b]
421
+ return _CUBE_START + (r * _CUBE_SIZE_256 + g) * _CUBE_SIZE_256 + b
422
+
423
+ return None
422
424
 
423
425
  # Only remaining possibility is gray value
424
426
  if desc.startswith("g#"):
425
427
  # hex value 00..ff
426
- gray = int(desc[2:], 16)
428
+ gray = int(desc[2:], 16) # noqa: FURB166
427
429
  if gray < 0 or gray > 255:
428
430
  return None
429
431
  gray = _GRAY_256_LOOKUP[gray]
@@ -464,14 +466,14 @@ def _parse_color_88(desc: str) -> int | None:
464
466
 
465
467
  Returns None if desc is invalid.
466
468
 
467
- >>> _parse_color_88('h142')
468
- >>> _parse_color_88('h42')
469
+ >>> _parse_color_88("h142")
470
+ >>> _parse_color_88("h42")
469
471
  42
470
- >>> _parse_color_88('#f00')
472
+ >>> _parse_color_88("#f00")
471
473
  64
472
- >>> _parse_color_88('g100')
474
+ >>> _parse_color_88("g100")
473
475
  79
474
- >>> _parse_color_88('g#80')
476
+ >>> _parse_color_88("g#80")
475
477
  83
476
478
  """
477
479
  if len(desc) == 7:
@@ -489,21 +491,21 @@ def _parse_color_88(desc: str) -> int | None:
489
491
 
490
492
  if desc.startswith("#") and len(desc) == 4:
491
493
  # color-cube coordinates
492
- rgb = int(desc[1:], 16)
493
- if rgb < 0:
494
- return None
495
- b, rgb = rgb % 16, rgb // 16
496
- g, r = rgb % 16, rgb // 16
497
- # find the closest rgb values
498
- r = _CUBE_88_LOOKUP_16[r]
499
- g = _CUBE_88_LOOKUP_16[g]
500
- b = _CUBE_88_LOOKUP_16[b]
501
- return _CUBE_START + (r * _CUBE_SIZE_88 + g) * _CUBE_SIZE_88 + b
494
+ if (rgb := int(desc[1:], 16)) >= 0:
495
+ b, rgb = rgb % 16, rgb // 16
496
+ g, r = rgb % 16, rgb // 16
497
+ # find the closest rgb values
498
+ r = _CUBE_88_LOOKUP_16[r]
499
+ g = _CUBE_88_LOOKUP_16[g]
500
+ b = _CUBE_88_LOOKUP_16[b]
501
+ return _CUBE_START + (r * _CUBE_SIZE_88 + g) * _CUBE_SIZE_88 + b
502
+
503
+ return None
502
504
 
503
505
  # Only remaining possibility is gray value
504
506
  if desc.startswith("g#"):
505
507
  # hex value 00..ff
506
- gray = int(desc[2:], 16)
508
+ gray = int(desc[2:], 16) # noqa: FURB166
507
509
  if gray < 0 or gray > 255:
508
510
  return None
509
511
  gray = _GRAY_88_LOOKUP[gray]
@@ -580,13 +582,13 @@ class AttrSpec:
580
582
  values are only usable with 88, 256, or 2**24 colors. With
581
583
  1 color only the foreground settings may be used.
582
584
 
583
- >>> AttrSpec('dark red', 'light gray', 16)
585
+ >>> AttrSpec("dark red", "light gray", 16)
584
586
  AttrSpec('dark red', 'light gray')
585
- >>> AttrSpec('yellow, underline, bold', 'dark blue')
587
+ >>> AttrSpec("yellow, underline, bold", "dark blue")
586
588
  AttrSpec('yellow,bold,underline', 'dark blue')
587
- >>> AttrSpec('#ddb', '#004', 256) # closest colors will be found
589
+ >>> AttrSpec("#ddb", "#004", 256) # closest colors will be found
588
590
  AttrSpec('#dda', '#006')
589
- >>> AttrSpec('#ddb', '#004', 88)
591
+ >>> AttrSpec("#ddb", "#004", 88)
590
592
  AttrSpec('#ccc', '#000', colors=88)
591
593
  """
592
594
  if colors not in {1, 16, 88, 256, 2**24}:
@@ -704,15 +706,6 @@ class AttrSpec:
704
706
  return 16
705
707
  return 1
706
708
 
707
- def _colors(self) -> int:
708
- warnings.warn(
709
- f"Method `{self.__class__.__name__}._colors` is deprecated, "
710
- f"please use property `{self.__class__.__name__}.colors`",
711
- DeprecationWarning,
712
- stacklevel=2,
713
- )
714
- return self.colors
715
-
716
709
  def __repr__(self) -> str:
717
710
  """
718
711
  Return an executable python representation of the AttrSpec
@@ -775,7 +768,7 @@ class AttrSpec:
775
768
  else:
776
769
  scolor = _parse_color_256(_true_to_256(part) or part)
777
770
  flags |= _FG_HIGH_COLOR
778
- # _parse_color_*() return None for unrecognised colors
771
+ # _parse_color_*() return None for unrecognized colors
779
772
  if scolor is None:
780
773
  raise AttrSpecError(f"Unrecognised color specification {part!r} in foreground ({foreground!r})")
781
774
  if color is not None:
@@ -785,15 +778,6 @@ class AttrSpec:
785
778
  color = 0
786
779
  self.__value = (self.__value & ~_FG_MASK) | color | flags
787
780
 
788
- def _foreground(self) -> str:
789
- warnings.warn(
790
- f"Method `{self.__class__.__name__}._foreground` is deprecated, "
791
- f"please use property `{self.__class__.__name__}.foreground`",
792
- DeprecationWarning,
793
- stacklevel=2,
794
- )
795
- return self.foreground
796
-
797
781
  @property
798
782
  def background(self) -> str:
799
783
  """Return the background color."""
@@ -827,15 +811,6 @@ class AttrSpec:
827
811
  raise AttrSpecError(f"Unrecognised color specification in background ({background!r})")
828
812
  self.__value = (self.__value & ~_BG_MASK) | (color << _BG_SHIFT) | flags
829
813
 
830
- def _background(self) -> str:
831
- warnings.warn(
832
- f"Method `{self.__class__.__name__}._background` is deprecated, "
833
- f"please use property `{self.__class__.__name__}.background`",
834
- DeprecationWarning,
835
- stacklevel=2,
836
- )
837
- return self.background
838
-
839
814
  def get_rgb_values(self) -> tuple[int | None, int | None, int | None, int | None, int | None, int | None]:
840
815
  """
841
816
  Return (fg_red, fg_green, fg_blue, bg_red, bg_green, bg_blue) color
@@ -845,9 +820,9 @@ class AttrSpec:
845
820
  If the foreground or background is 'default' then all their compenents
846
821
  will be returned as None.
847
822
 
848
- >>> AttrSpec('yellow', '#ccf', colors=88).get_rgb_values()
823
+ >>> AttrSpec("yellow", "#ccf", colors=88).get_rgb_values()
849
824
  (255, 255, 0, 205, 205, 255)
850
- >>> AttrSpec('default', 'g92').get_rgb_values()
825
+ >>> AttrSpec("default", "g92").get_rgb_values()
851
826
  (None, None, None, 238, 238, 238)
852
827
  """
853
828
  if not (self.foreground_basic or self.foreground_high or self.foreground_true):
@@ -988,13 +963,9 @@ class ScreenError(Exception):
988
963
  pass
989
964
 
990
965
 
991
- class BaseMeta(signals.MetaSignals, abc.ABCMeta):
992
- """Base metaclass for abstra"""
993
-
994
-
995
- class BaseScreen(metaclass=BaseMeta):
966
+ class BaseScreen(abc.ABC, metaclass=signals.MetaSignals):
996
967
  """
997
- Base class for Screen classes (raw_display.Screen, .. etc)
968
+ Base class for Screen classes (raw_display.Screen, ..., etc.)
998
969
  """
999
970
 
1000
971
  signals: typing.ClassVar[list[str]] = [UPDATE_PALETTE_ENTRY, INPUT_DESCRIPTORS_CHANGED]
@@ -1040,7 +1011,7 @@ class BaseScreen(metaclass=BaseMeta):
1040
1011
  def _stop(self) -> None:
1041
1012
  pass
1042
1013
 
1043
- def run_wrapper(self, fn, *args, **kwargs):
1014
+ def run_wrapper(self, fn, *args, **kwargs) -> None:
1044
1015
  """Start the screen, call a function, then stop the screen. Extra
1045
1016
  arguments are passed to `start`.
1046
1017
 
urwid/display/curses.py CHANGED
@@ -124,7 +124,7 @@ class Screen(BaseScreen, RealTerminal):
124
124
  After calling this function get_input will include mouse
125
125
  click events along with keystrokes.
126
126
  """
127
- enable = bool(enable) # noqa: FURB123,RUF100
127
+ enable = bool(enable)
128
128
  if enable == self._mouse_tracking_enabled:
129
129
  return
130
130
 
@@ -563,9 +563,7 @@ class Screen(BaseScreen, RealTerminal):
563
563
 
564
564
  logger.debug(f"Drawing screen with size {size!r}")
565
565
 
566
- y = -1
567
- for row in canvas.content():
568
- y += 1
566
+ for y, row in enumerate(canvas.content()):
569
567
  try:
570
568
  self.s.move(y, 0)
571
569
  except curses.error:
@@ -596,7 +594,7 @@ class Screen(BaseScreen, RealTerminal):
596
594
  raise TypeError(seg)
597
595
  self.s.addstr(seg.decode(util.get_encoding()))
598
596
  except curses.error:
599
- # it's ok to get out of the
597
+ # it's OK to get out of the
600
598
  # screen on the lower right
601
599
  if y != rows - 1 or nr != len(row) - 1:
602
600
  # perhaps screen size changed
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,11 @@ 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(
251
+ self,
252
+ keys: Sequence[int],
253
+ more_available: bool,
254
+ ) -> tuple[tuple[str, int, int, int], Sequence[int]] | None:
249
255
  if len(keys) < 3:
250
256
  if more_available:
251
257
  raise MoreInputRequired()
@@ -284,7 +290,11 @@ class KeyqueueTrie:
284
290
 
285
291
  return ((f"{prefix}mouse {action}", button, x, y), keys[3:])
286
292
 
287
- def read_sgrmouse_info(self, keys: Collection[int], more_available: bool):
293
+ def read_sgrmouse_info(
294
+ self,
295
+ keys: Sequence[int],
296
+ more_available: bool,
297
+ ) -> tuple[tuple[str, int, int, int], Sequence[int]] | None:
288
298
  # Helpful links:
289
299
  # https://stackoverflow.com/questions/5966903/how-to-get-mousemove-and-mouseclick-in-bash
290
300
  # http://invisible-island.net/xterm/ctlseqs/ctlseqs.pdf
@@ -343,7 +353,11 @@ class KeyqueueTrie:
343
353
 
344
354
  return ((f"{prefix}mouse {action}", button, x, y), keys[pos_m + 1 :])
345
355
 
346
- def read_cursor_position(self, keys, more_available: bool):
356
+ def read_cursor_position(
357
+ self,
358
+ keys: Sequence[int],
359
+ more_available: bool,
360
+ ) -> tuple[tuple[str, int, int], Sequence[int]] | None:
347
361
  """
348
362
  Interpret cursor position information being sent by the
349
363
  user's terminal. Returned as ('cursor position', x, y)
@@ -538,9 +552,7 @@ def process_keyqueue(codes: Sequence[int], more_available: bool) -> tuple[list[s
538
552
  if code != 27:
539
553
  return [f"<{code:d}>"], codes[1:]
540
554
 
541
- result = input_trie.get(codes[1:], more_available)
542
-
543
- if result is not None:
555
+ if (result := input_trie.get(codes[1:], more_available)) is not None:
544
556
  result, remaining_codes = result
545
557
  return [result], remaining_codes
546
558
 
@@ -551,7 +563,7 @@ def process_keyqueue(codes: Sequence[int], more_available: bool) -> tuple[list[s
551
563
  return ["esc", *run], remaining_codes
552
564
  if run[0] == "esc" or run[0].find("meta ") >= 0:
553
565
  return ["esc", *run], remaining_codes
554
- return [f"meta {run[0]}"] + run[1:], remaining_codes
566
+ return [f"meta {run[0]}", *run[1:]], remaining_codes
555
567
 
556
568
  return ["esc"], codes[1:]
557
569
 
@@ -574,6 +586,9 @@ RESTORE_NORMAL_BUFFER = f"{ESC}[?1049l"
574
586
  ENABLE_BRACKETED_PASTE_MODE = f"{ESC}[?2004h"
575
587
  DISABLE_BRACKETED_PASTE_MODE = f"{ESC}[?2004l"
576
588
 
589
+ ENABLE_FOCUS_REPORTING = f"{ESC}[?1004h"
590
+ DISABLE_FOCUS_REPORTING = f"{ESC}[?1004l"
591
+
577
592
  # RESET_SCROLL_REGION = ESC+"[;r"
578
593
  # RESET = ESC+"c"
579
594
 
urwid/display/lcd.py CHANGED
@@ -125,8 +125,8 @@ class CFLCDScreen(LCDScreen, abc.ABC):
125
125
 
126
126
  @classmethod
127
127
  def get_crc(cls, buf: Iterable[int]) -> bytes:
128
- # This seed makes the output of this shift based algorithm match
129
- # the table based algorithm. The center 16 bits of the 32-bit
128
+ # This seed makes the output of this shift-based algorithm match
129
+ # the table-based algorithm. The center 16 bits of the 32-bit
130
130
  # "newCRC" are used for the CRC. The MSB of the lower byte is used
131
131
  # to see what bit was shifted out of the center 16 bit CRC
132
132
  # accumulator ("carry flag analog");
@@ -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
 
@@ -230,7 +229,7 @@ class KeyRepeatSimulator:
230
229
  Provide simulated repeat key events when given press and
231
230
  release events.
232
231
 
233
- If two or more keys are pressed disable repeating until all
232
+ If two or more keys are pressed, disable repeating until all
234
233
  keys are released.
235
234
  """
236
235
 
@@ -287,7 +286,7 @@ class CF635Screen(CFLCDScreen):
287
286
  20x4 character display + cursor
288
287
  no foreground/background colors or settings supported
289
288
 
290
- see CGROM for list of close unicode matches to characters available
289
+ see CGROM for a list of close Unicode matches to characters available
291
290
 
292
291
  6 button input
293
292
  up, down, left, right, enter (check mark), exit (cross)
@@ -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)