urwid 3.0.4__tar.gz → 4.0.0__tar.gz

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 (318) hide show
  1. {urwid-3.0.4 → urwid-4.0.0}/.github/workflows/documentation.yml +1 -1
  2. {urwid-3.0.4 → urwid-4.0.0}/.github/workflows/pythonpackage.yml +4 -4
  3. {urwid-3.0.4 → urwid-4.0.0}/PKG-INFO +2 -2
  4. {urwid-3.0.4 → urwid-4.0.0}/docs/changelog.rst +22 -1
  5. urwid-4.0.0/docs/examples/edit_text.txt +0 -0
  6. {urwid-3.0.4 → urwid-4.0.0}/docs/examples/real_edit.py +2 -1
  7. {urwid-3.0.4 → urwid-4.0.0}/examples/edit.py +2 -1
  8. {urwid-3.0.4 → urwid-4.0.0}/pyproject.toml +3 -8
  9. urwid-4.0.0/requirements.txt +1 -0
  10. urwid-4.0.0/ruff-requirements.txt +1 -0
  11. {urwid-3.0.4 → urwid-4.0.0}/tests/test_event_loops.py +1 -0
  12. urwid-4.0.0/tests/test_grapheme_clusters.py +210 -0
  13. {urwid-3.0.4 → urwid-4.0.0}/tests/test_scrollable.py +2 -1
  14. {urwid-3.0.4 → urwid-4.0.0}/tests/test_text_layout.py +15 -3
  15. {urwid-3.0.4 → urwid-4.0.0}/urwid/__init__.py +1 -9
  16. {urwid-3.0.4 → urwid-4.0.0}/urwid/display/_posix_raw_display.py +4 -1
  17. {urwid-3.0.4 → urwid-4.0.0}/urwid/display/common.py +2 -1
  18. {urwid-3.0.4 → urwid-4.0.0}/urwid/event_loop/main_loop.py +0 -21
  19. {urwid-3.0.4 → urwid-4.0.0}/urwid/font.py +7 -22
  20. {urwid-3.0.4 → urwid-4.0.0}/urwid/signals.py +9 -18
  21. {urwid-3.0.4 → urwid-4.0.0}/urwid/str_util.py +90 -42
  22. {urwid-3.0.4 → urwid-4.0.0}/urwid/text_layout.py +8 -6
  23. urwid-4.0.0/urwid/version.py +24 -0
  24. {urwid-3.0.4 → urwid-4.0.0}/urwid/vterm.py +2 -2
  25. {urwid-3.0.4 → urwid-4.0.0}/urwid/widget/attr_wrap.py +0 -16
  26. {urwid-3.0.4 → urwid-4.0.0}/urwid/widget/columns.py +0 -20
  27. {urwid-3.0.4 → urwid-4.0.0}/urwid/widget/filler.py +0 -17
  28. {urwid-3.0.4 → urwid-4.0.0}/urwid/widget/listbox.py +1 -1
  29. {urwid-3.0.4 → urwid-4.0.0}/urwid/widget/monitored_list.py +0 -21
  30. {urwid-3.0.4 → urwid-4.0.0}/urwid/widget/pile.py +0 -24
  31. {urwid-3.0.4 → urwid-4.0.0}/urwid/widget/text.py +16 -2
  32. {urwid-3.0.4 → urwid-4.0.0}/urwid/widget/wimp.py +10 -13
  33. {urwid-3.0.4 → urwid-4.0.0}/urwid.egg-info/PKG-INFO +2 -2
  34. {urwid-3.0.4 → urwid-4.0.0}/urwid.egg-info/SOURCES.txt +1 -4
  35. {urwid-3.0.4 → urwid-4.0.0}/urwid.egg-info/requires.txt +1 -1
  36. urwid-3.0.4/docs/examples/edit_text.txt +0 -11
  37. urwid-3.0.4/requirements.txt +0 -1
  38. urwid-3.0.4/ruff-requirements.txt +0 -1
  39. urwid-3.0.4/tests/test_moved_imports.py +0 -19
  40. urwid-3.0.4/urwid/container.py +0 -59
  41. urwid-3.0.4/urwid/decoration.py +0 -65
  42. urwid-3.0.4/urwid/version.py +0 -34
  43. urwid-3.0.4/urwid/wimp.py +0 -23
  44. {urwid-3.0.4 → urwid-4.0.0}/.coveralls.yml +0 -0
  45. {urwid-3.0.4 → urwid-4.0.0}/.devcontainer/dev.dockerfile +0 -0
  46. {urwid-3.0.4 → urwid-4.0.0}/.devcontainer/devcontainer.json +0 -0
  47. {urwid-3.0.4 → urwid-4.0.0}/.dockerignore +0 -0
  48. {urwid-3.0.4 → urwid-4.0.0}/.editorconfig +0 -0
  49. {urwid-3.0.4 → urwid-4.0.0}/.github/CODE_OF_CONDUCT.md +0 -0
  50. {urwid-3.0.4 → urwid-4.0.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  51. {urwid-3.0.4 → urwid-4.0.0}/.github/ISSUE_TEMPLATE/docs.md +0 -0
  52. {urwid-3.0.4 → urwid-4.0.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  53. {urwid-3.0.4 → urwid-4.0.0}/.github/ISSUE_TEMPLATE/question.md +0 -0
  54. {urwid-3.0.4 → urwid-4.0.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  55. {urwid-3.0.4 → urwid-4.0.0}/.github/dependabot.yml +0 -0
  56. {urwid-3.0.4 → urwid-4.0.0}/.github/labeler.yml +0 -0
  57. {urwid-3.0.4 → urwid-4.0.0}/.github/release.yml +0 -0
  58. {urwid-3.0.4 → urwid-4.0.0}/.github/workflows/isolated_static_check.yml +0 -0
  59. {urwid-3.0.4 → urwid-4.0.0}/.github/workflows/labels.yml +0 -0
  60. {urwid-3.0.4 → urwid-4.0.0}/.github/workflows/yamllint.yml +0 -0
  61. {urwid-3.0.4 → urwid-4.0.0}/.gitignore +0 -0
  62. {urwid-3.0.4 → urwid-4.0.0}/.pre-commit-config.yaml +0 -0
  63. {urwid-3.0.4 → urwid-4.0.0}/.yamllint.yml +0 -0
  64. {urwid-3.0.4 → urwid-4.0.0}/COPYING +0 -0
  65. {urwid-3.0.4 → urwid-4.0.0}/MANIFEST.in +0 -0
  66. {urwid-3.0.4 → urwid-4.0.0}/README.rst +0 -0
  67. {urwid-3.0.4 → urwid-4.0.0}/classifiers.txt +0 -0
  68. {urwid-3.0.4 → urwid-4.0.0}/docs/Makefile +0 -0
  69. {urwid-3.0.4 → urwid-4.0.0}/docs/conf.py +0 -0
  70. {urwid-3.0.4 → urwid-4.0.0}/docs/examples/bigtext.py +0 -0
  71. {urwid-3.0.4 → urwid-4.0.0}/docs/examples/bigtext.py.xdotool +0 -0
  72. {urwid-3.0.4 → urwid-4.0.0}/docs/examples/bigtext1.png +0 -0
  73. {urwid-3.0.4 → urwid-4.0.0}/docs/examples/bigtext2.png +0 -0
  74. {urwid-3.0.4 → urwid-4.0.0}/docs/examples/bigtext3.png +0 -0
  75. {urwid-3.0.4 → urwid-4.0.0}/docs/examples/browse.py +0 -0
  76. {urwid-3.0.4 → urwid-4.0.0}/docs/examples/browse.py.xdotool +0 -0
  77. {urwid-3.0.4 → urwid-4.0.0}/docs/examples/browse1.png +0 -0
  78. {urwid-3.0.4 → urwid-4.0.0}/docs/examples/browse2.png +0 -0
  79. {urwid-3.0.4 → urwid-4.0.0}/docs/examples/edit.py +0 -0
  80. {urwid-3.0.4 → urwid-4.0.0}/docs/examples/edit.py.xdotool +0 -0
  81. {urwid-3.0.4 → urwid-4.0.0}/docs/examples/edit1.png +0 -0
  82. {urwid-3.0.4 → urwid-4.0.0}/docs/examples/edit2.png +0 -0
  83. {urwid-3.0.4 → urwid-4.0.0}/docs/examples/graph.py +0 -0
  84. {urwid-3.0.4 → urwid-4.0.0}/docs/examples/graph.py.xdotool +0 -0
  85. {urwid-3.0.4 → urwid-4.0.0}/docs/examples/graph1.png +0 -0
  86. {urwid-3.0.4 → urwid-4.0.0}/docs/examples/graph2.png +0 -0
  87. {urwid-3.0.4 → urwid-4.0.0}/docs/examples/index.rst +0 -0
  88. {urwid-3.0.4 → urwid-4.0.0}/docs/examples/palette_test.py +0 -0
  89. {urwid-3.0.4 → urwid-4.0.0}/docs/examples/palette_test.py.xdotool +0 -0
  90. {urwid-3.0.4 → urwid-4.0.0}/docs/examples/palette_test1.png +0 -0
  91. {urwid-3.0.4 → urwid-4.0.0}/docs/examples/palette_test2.png +0 -0
  92. {urwid-3.0.4 → urwid-4.0.0}/docs/examples/pop_up.py +0 -0
  93. {urwid-3.0.4 → urwid-4.0.0}/docs/examples/pop_up.py.xdotool +0 -0
  94. {urwid-3.0.4 → urwid-4.0.0}/docs/examples/pop_up1.png +0 -0
  95. {urwid-3.0.4 → urwid-4.0.0}/docs/examples/pop_up2.png +0 -0
  96. {urwid-3.0.4 → urwid-4.0.0}/docs/examples/real_browse.py +0 -0
  97. {urwid-3.0.4 → urwid-4.0.0}/docs/examples/subproc.py +0 -0
  98. {urwid-3.0.4 → urwid-4.0.0}/docs/examples/subproc.py.xdotool +0 -0
  99. {urwid-3.0.4 → urwid-4.0.0}/docs/examples/subproc1.png +0 -0
  100. {urwid-3.0.4 → urwid-4.0.0}/docs/examples/subproc2.png +0 -0
  101. {urwid-3.0.4 → urwid-4.0.0}/docs/examples/subproc2.py +0 -0
  102. {urwid-3.0.4 → urwid-4.0.0}/docs/examples/tour.py +0 -0
  103. {urwid-3.0.4 → urwid-4.0.0}/docs/examples/tour.py.xdotool +0 -0
  104. {urwid-3.0.4 → urwid-4.0.0}/docs/examples/tour1.png +0 -0
  105. {urwid-3.0.4 → urwid-4.0.0}/docs/examples/tour2.png +0 -0
  106. {urwid-3.0.4 → urwid-4.0.0}/docs/index.rst +0 -0
  107. {urwid-3.0.4 → urwid-4.0.0}/docs/manual/bright_combinations.py +0 -0
  108. {urwid-3.0.4 → urwid-4.0.0}/docs/manual/bright_combinations.py.xdotool +0 -0
  109. {urwid-3.0.4 → urwid-4.0.0}/docs/manual/bright_combinations1.png +0 -0
  110. {urwid-3.0.4 → urwid-4.0.0}/docs/manual/canvascache.rst +0 -0
  111. {urwid-3.0.4 → urwid-4.0.0}/docs/manual/displayattributes.rst +0 -0
  112. {urwid-3.0.4 → urwid-4.0.0}/docs/manual/displaymodules.rst +0 -0
  113. {urwid-3.0.4 → urwid-4.0.0}/docs/manual/encodings.rst +0 -0
  114. {urwid-3.0.4 → urwid-4.0.0}/docs/manual/images/display_modules.png +0 -0
  115. {urwid-3.0.4 → urwid-4.0.0}/docs/manual/images/introduction.png +0 -0
  116. {urwid-3.0.4 → urwid-4.0.0}/docs/manual/images/urwid_widgets.svgz +0 -0
  117. {urwid-3.0.4 → urwid-4.0.0}/docs/manual/images/urwid_widgets_1.png +0 -0
  118. {urwid-3.0.4 → urwid-4.0.0}/docs/manual/images/urwid_widgets_1.xcf +0 -0
  119. {urwid-3.0.4 → urwid-4.0.0}/docs/manual/images/urwid_widgets_2.png +0 -0
  120. {urwid-3.0.4 → urwid-4.0.0}/docs/manual/images/urwid_widgets_2.xcf +0 -0
  121. {urwid-3.0.4 → urwid-4.0.0}/docs/manual/images/widget_layout.png +0 -0
  122. {urwid-3.0.4 → urwid-4.0.0}/docs/manual/index.rst +0 -0
  123. {urwid-3.0.4 → urwid-4.0.0}/docs/manual/mainloop.rst +0 -0
  124. {urwid-3.0.4 → urwid-4.0.0}/docs/manual/overview.rst +0 -0
  125. {urwid-3.0.4 → urwid-4.0.0}/docs/manual/safe_combinations.py +0 -0
  126. {urwid-3.0.4 → urwid-4.0.0}/docs/manual/safe_combinations.py.xdotool +0 -0
  127. {urwid-3.0.4 → urwid-4.0.0}/docs/manual/safe_combinations1.png +0 -0
  128. {urwid-3.0.4 → urwid-4.0.0}/docs/manual/textlayout.rst +0 -0
  129. {urwid-3.0.4 → urwid-4.0.0}/docs/manual/userinput.rst +0 -0
  130. {urwid-3.0.4 → urwid-4.0.0}/docs/manual/wanat.py +0 -0
  131. {urwid-3.0.4 → urwid-4.0.0}/docs/manual/wanat_multi.py +0 -0
  132. {urwid-3.0.4 → urwid-4.0.0}/docs/manual/wcur1.py +0 -0
  133. {urwid-3.0.4 → urwid-4.0.0}/docs/manual/wcur2.py +0 -0
  134. {urwid-3.0.4 → urwid-4.0.0}/docs/manual/widgets.rst +0 -0
  135. {urwid-3.0.4 → urwid-4.0.0}/docs/manual/wmod.py +0 -0
  136. {urwid-3.0.4 → urwid-4.0.0}/docs/manual/wsel.py +0 -0
  137. {urwid-3.0.4 → urwid-4.0.0}/docs/reference/attrspec.rst +0 -0
  138. {urwid-3.0.4 → urwid-4.0.0}/docs/reference/canvas.rst +0 -0
  139. {urwid-3.0.4 → urwid-4.0.0}/docs/reference/command_map.rst +0 -0
  140. {urwid-3.0.4 → urwid-4.0.0}/docs/reference/constants.rst +0 -0
  141. {urwid-3.0.4 → urwid-4.0.0}/docs/reference/deprecated.rst +0 -0
  142. {urwid-3.0.4 → urwid-4.0.0}/docs/reference/display_modules.rst +0 -0
  143. {urwid-3.0.4 → urwid-4.0.0}/docs/reference/exceptions.rst +0 -0
  144. {urwid-3.0.4 → urwid-4.0.0}/docs/reference/global_settings.rst +0 -0
  145. {urwid-3.0.4 → urwid-4.0.0}/docs/reference/index.rst +0 -0
  146. {urwid-3.0.4 → urwid-4.0.0}/docs/reference/list_walkers.rst +0 -0
  147. {urwid-3.0.4 → urwid-4.0.0}/docs/reference/main_loop.rst +0 -0
  148. {urwid-3.0.4 → urwid-4.0.0}/docs/reference/meta.rst +0 -0
  149. {urwid-3.0.4 → urwid-4.0.0}/docs/reference/signals.rst +0 -0
  150. {urwid-3.0.4 → urwid-4.0.0}/docs/reference/text_layout.rst +0 -0
  151. {urwid-3.0.4 → urwid-4.0.0}/docs/reference/widget.rst +0 -0
  152. {urwid-3.0.4 → urwid-4.0.0}/docs/tools/compile_pngs.sh +0 -0
  153. {urwid-3.0.4 → urwid-4.0.0}/docs/tools/screenshots.sh +0 -0
  154. {urwid-3.0.4 → urwid-4.0.0}/docs/tools/static/.placeholder +0 -0
  155. {urwid-3.0.4 → urwid-4.0.0}/docs/tools/templates/indexcontent.html +0 -0
  156. {urwid-3.0.4 → urwid-4.0.0}/docs/tools/templates/indexsidebar.html +0 -0
  157. {urwid-3.0.4 → urwid-4.0.0}/docs/tools/templates/localtoc.html +0 -0
  158. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/adventure.py +0 -0
  159. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/adventure.py.xdotool +0 -0
  160. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/adventure1.png +0 -0
  161. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/adventure2.png +0 -0
  162. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/adventure3.png +0 -0
  163. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/adventure4.png +0 -0
  164. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/cmenu.py +0 -0
  165. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/cmenu.py.xdotool +0 -0
  166. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/cmenu1.png +0 -0
  167. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/cmenu2.png +0 -0
  168. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/cmenu3.png +0 -0
  169. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/cmenu4.png +0 -0
  170. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/highcolors.py +0 -0
  171. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/highcolors.py.xdotool +0 -0
  172. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/highcolors1.png +0 -0
  173. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/hmenu.py +0 -0
  174. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/hmenu.py.xdotool +0 -0
  175. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/hmenu1.png +0 -0
  176. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/hmenu2.png +0 -0
  177. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/hmenu3.png +0 -0
  178. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/hmenu4.png +0 -0
  179. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/index.rst +0 -0
  180. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/input.py +0 -0
  181. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/input.py.xdotool +0 -0
  182. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/input1.png +0 -0
  183. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/input2.png +0 -0
  184. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/input3.png +0 -0
  185. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/input4.png +0 -0
  186. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/input5.png +0 -0
  187. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/menu25.png +0 -0
  188. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/minimal.py +0 -0
  189. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/minimal.py.xdotool +0 -0
  190. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/minimal1.png +0 -0
  191. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/multiple.py +0 -0
  192. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/multiple.py.xdotool +0 -0
  193. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/multiple1.png +0 -0
  194. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/multiple2.png +0 -0
  195. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/multiple3.png +0 -0
  196. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/multiple4.png +0 -0
  197. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/qa.py +0 -0
  198. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/qa.py.xdotool +0 -0
  199. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/qa1.png +0 -0
  200. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/qa2.png +0 -0
  201. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/qa3.png +0 -0
  202. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/sig.py +0 -0
  203. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/sig.py.xdotool +0 -0
  204. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/sig1.png +0 -0
  205. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/sig2.png +0 -0
  206. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/sig3.png +0 -0
  207. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/sig4.png +0 -0
  208. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/smenu.py +0 -0
  209. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/smenu.py.xdotool +0 -0
  210. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/smenu1.png +0 -0
  211. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/smenu2.png +0 -0
  212. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/smenu3.png +0 -0
  213. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/urwid_attr.py +0 -0
  214. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/urwid_attr.py.xdotool +0 -0
  215. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/urwid_attr1.png +0 -0
  216. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/urwid_attr2.png +0 -0
  217. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/urwid_attr3.png +0 -0
  218. {urwid-3.0.4 → urwid-4.0.0}/docs/tutorial/urwid_attr4.png +0 -0
  219. {urwid-3.0.4 → urwid-4.0.0}/docs/urwid-logo.png +0 -0
  220. {urwid-3.0.4 → urwid-4.0.0}/examples/asyncio_socket_server.py +0 -0
  221. {urwid-3.0.4 → urwid-4.0.0}/examples/bigtext.py +0 -0
  222. {urwid-3.0.4 → urwid-4.0.0}/examples/browse.py +0 -0
  223. {urwid-3.0.4 → urwid-4.0.0}/examples/calc.py +0 -0
  224. {urwid-3.0.4 → urwid-4.0.0}/examples/dialog.py +0 -0
  225. {urwid-3.0.4 → urwid-4.0.0}/examples/fib.py +0 -0
  226. {urwid-3.0.4 → urwid-4.0.0}/examples/graph.py +0 -0
  227. {urwid-3.0.4 → urwid-4.0.0}/examples/input_test.py +0 -0
  228. {urwid-3.0.4 → urwid-4.0.0}/examples/lcd_cf635.py +0 -0
  229. {urwid-3.0.4 → urwid-4.0.0}/examples/palette_test.py +0 -0
  230. {urwid-3.0.4 → urwid-4.0.0}/examples/pop_up.py +0 -0
  231. {urwid-3.0.4 → urwid-4.0.0}/examples/subproc.py +0 -0
  232. {urwid-3.0.4 → urwid-4.0.0}/examples/subproc2.py +0 -0
  233. {urwid-3.0.4 → urwid-4.0.0}/examples/terminal.py +0 -0
  234. {urwid-3.0.4 → urwid-4.0.0}/examples/tour.py +0 -0
  235. {urwid-3.0.4 → urwid-4.0.0}/examples/treesample.py +0 -0
  236. {urwid-3.0.4 → urwid-4.0.0}/examples/twisted_serve_ssh.py +0 -0
  237. {urwid-3.0.4 → urwid-4.0.0}/examples/twisted_serve_ssh.tac +0 -0
  238. {urwid-3.0.4 → urwid-4.0.0}/pylint-requirements.txt +0 -0
  239. {urwid-3.0.4 → urwid-4.0.0}/setup.cfg +0 -0
  240. {urwid-3.0.4 → urwid-4.0.0}/test_requirements.txt +0 -0
  241. {urwid-3.0.4 → urwid-4.0.0}/tests/__init__.py +0 -0
  242. {urwid-3.0.4 → urwid-4.0.0}/tests/test_canvas.py +0 -0
  243. {urwid-3.0.4 → urwid-4.0.0}/tests/test_columns.py +0 -0
  244. {urwid-3.0.4 → urwid-4.0.0}/tests/test_container.py +0 -0
  245. {urwid-3.0.4 → urwid-4.0.0}/tests/test_doctests.py +0 -0
  246. {urwid-3.0.4 → urwid-4.0.0}/tests/test_escapes.py +0 -0
  247. {urwid-3.0.4 → urwid-4.0.0}/tests/test_filler.py +0 -0
  248. {urwid-3.0.4 → urwid-4.0.0}/tests/test_floatedit.py +0 -0
  249. {urwid-3.0.4 → urwid-4.0.0}/tests/test_font.py +0 -0
  250. {urwid-3.0.4 → urwid-4.0.0}/tests/test_frame.py +0 -0
  251. {urwid-3.0.4 → urwid-4.0.0}/tests/test_graphics.py +0 -0
  252. {urwid-3.0.4 → urwid-4.0.0}/tests/test_grid_flow.py +0 -0
  253. {urwid-3.0.4 → urwid-4.0.0}/tests/test_line_box.py +0 -0
  254. {urwid-3.0.4 → urwid-4.0.0}/tests/test_listbox.py +0 -0
  255. {urwid-3.0.4 → urwid-4.0.0}/tests/test_main_loop.py +0 -0
  256. {urwid-3.0.4 → urwid-4.0.0}/tests/test_overlay.py +0 -0
  257. {urwid-3.0.4 → urwid-4.0.0}/tests/test_padding.py +0 -0
  258. {urwid-3.0.4 → urwid-4.0.0}/tests/test_pile.py +0 -0
  259. {urwid-3.0.4 → urwid-4.0.0}/tests/test_raw_display.py +0 -0
  260. {urwid-3.0.4 → urwid-4.0.0}/tests/test_signals.py +0 -0
  261. {urwid-3.0.4 → urwid-4.0.0}/tests/test_str_util.py +0 -0
  262. {urwid-3.0.4 → urwid-4.0.0}/tests/test_tree.py +0 -0
  263. {urwid-3.0.4 → urwid-4.0.0}/tests/test_util.py +0 -0
  264. {urwid-3.0.4 → urwid-4.0.0}/tests/test_vterm.py +0 -0
  265. {urwid-3.0.4 → urwid-4.0.0}/tests/test_widget.py +0 -0
  266. {urwid-3.0.4 → urwid-4.0.0}/tests/util.py +0 -0
  267. {urwid-3.0.4 → urwid-4.0.0}/tox.ini +0 -0
  268. {urwid-3.0.4 → urwid-4.0.0}/urwid/canvas.py +0 -0
  269. {urwid-3.0.4 → urwid-4.0.0}/urwid/command_map.py +0 -0
  270. {urwid-3.0.4 → urwid-4.0.0}/urwid/display/__init__.py +0 -0
  271. {urwid-3.0.4 → urwid-4.0.0}/urwid/display/_raw_display_base.py +0 -0
  272. {urwid-3.0.4 → urwid-4.0.0}/urwid/display/_web.css +0 -0
  273. {urwid-3.0.4 → urwid-4.0.0}/urwid/display/_web.js +0 -0
  274. {urwid-3.0.4 → urwid-4.0.0}/urwid/display/_win32.py +0 -0
  275. {urwid-3.0.4 → urwid-4.0.0}/urwid/display/_win32_raw_display.py +0 -0
  276. {urwid-3.0.4 → urwid-4.0.0}/urwid/display/curses.py +0 -0
  277. {urwid-3.0.4 → urwid-4.0.0}/urwid/display/escape.py +0 -0
  278. {urwid-3.0.4 → urwid-4.0.0}/urwid/display/html_fragment.py +0 -0
  279. {urwid-3.0.4 → urwid-4.0.0}/urwid/display/lcd.py +0 -0
  280. {urwid-3.0.4 → urwid-4.0.0}/urwid/display/raw.py +0 -0
  281. {urwid-3.0.4 → urwid-4.0.0}/urwid/display/web.py +0 -0
  282. {urwid-3.0.4 → urwid-4.0.0}/urwid/event_loop/__init__.py +0 -0
  283. {urwid-3.0.4 → urwid-4.0.0}/urwid/event_loop/abstract_loop.py +0 -0
  284. {urwid-3.0.4 → urwid-4.0.0}/urwid/event_loop/asyncio_loop.py +0 -0
  285. {urwid-3.0.4 → urwid-4.0.0}/urwid/event_loop/glib_loop.py +0 -0
  286. {urwid-3.0.4 → urwid-4.0.0}/urwid/event_loop/select_loop.py +0 -0
  287. {urwid-3.0.4 → urwid-4.0.0}/urwid/event_loop/tornado_loop.py +0 -0
  288. {urwid-3.0.4 → urwid-4.0.0}/urwid/event_loop/trio_loop.py +0 -0
  289. {urwid-3.0.4 → urwid-4.0.0}/urwid/event_loop/twisted_loop.py +0 -0
  290. {urwid-3.0.4 → urwid-4.0.0}/urwid/event_loop/zmq_loop.py +0 -0
  291. {urwid-3.0.4 → urwid-4.0.0}/urwid/graphics.py +0 -0
  292. {urwid-3.0.4 → urwid-4.0.0}/urwid/numedit.py +0 -0
  293. {urwid-3.0.4 → urwid-4.0.0}/urwid/split_repr.py +0 -0
  294. {urwid-3.0.4 → urwid-4.0.0}/urwid/util.py +0 -0
  295. {urwid-3.0.4 → urwid-4.0.0}/urwid/widget/__init__.py +0 -0
  296. {urwid-3.0.4 → urwid-4.0.0}/urwid/widget/attr_map.py +0 -0
  297. {urwid-3.0.4 → urwid-4.0.0}/urwid/widget/bar_graph.py +0 -0
  298. {urwid-3.0.4 → urwid-4.0.0}/urwid/widget/big_text.py +0 -0
  299. {urwid-3.0.4 → urwid-4.0.0}/urwid/widget/box_adapter.py +0 -0
  300. {urwid-3.0.4 → urwid-4.0.0}/urwid/widget/constants.py +0 -0
  301. {urwid-3.0.4 → urwid-4.0.0}/urwid/widget/container.py +0 -0
  302. {urwid-3.0.4 → urwid-4.0.0}/urwid/widget/divider.py +0 -0
  303. {urwid-3.0.4 → urwid-4.0.0}/urwid/widget/edit.py +0 -0
  304. {urwid-3.0.4 → urwid-4.0.0}/urwid/widget/frame.py +0 -0
  305. {urwid-3.0.4 → urwid-4.0.0}/urwid/widget/grid_flow.py +0 -0
  306. {urwid-3.0.4 → urwid-4.0.0}/urwid/widget/line_box.py +0 -0
  307. {urwid-3.0.4 → urwid-4.0.0}/urwid/widget/overlay.py +0 -0
  308. {urwid-3.0.4 → urwid-4.0.0}/urwid/widget/padding.py +0 -0
  309. {urwid-3.0.4 → urwid-4.0.0}/urwid/widget/popup.py +0 -0
  310. {urwid-3.0.4 → urwid-4.0.0}/urwid/widget/progress_bar.py +0 -0
  311. {urwid-3.0.4 → urwid-4.0.0}/urwid/widget/scrollable.py +0 -0
  312. {urwid-3.0.4 → urwid-4.0.0}/urwid/widget/solid_fill.py +0 -0
  313. {urwid-3.0.4 → urwid-4.0.0}/urwid/widget/treetools.py +0 -0
  314. {urwid-3.0.4 → urwid-4.0.0}/urwid/widget/widget.py +0 -0
  315. {urwid-3.0.4 → urwid-4.0.0}/urwid/widget/widget_decoration.py +0 -0
  316. {urwid-3.0.4 → urwid-4.0.0}/urwid.egg-info/dependency_links.txt +0 -0
  317. {urwid-3.0.4 → urwid-4.0.0}/urwid.egg-info/not-zip-safe +0 -0
  318. {urwid-3.0.4 → urwid-4.0.0}/urwid.egg-info/top_level.txt +0 -0
@@ -44,7 +44,7 @@ jobs:
44
44
  run: pip install -e .
45
45
  - name: Build documentation
46
46
  run: sphinx-build docs build/documentation
47
- - uses: actions/upload-artifact@v5
47
+ - uses: actions/upload-artifact@v7
48
48
  with:
49
49
  path: build/documentation
50
50
  name: documentation
@@ -172,13 +172,13 @@ jobs:
172
172
  - name: Build dist
173
173
  run: python -m build
174
174
 
175
- - uses: actions/upload-artifact@v5
175
+ - uses: actions/upload-artifact@v7
176
176
  with:
177
177
  path: dist/*.tar.gz
178
178
  name: built-sdist
179
179
  retention-days: 3
180
180
 
181
- - uses: actions/upload-artifact@v5
181
+ - uses: actions/upload-artifact@v7
182
182
  with:
183
183
  path: dist/*.whl
184
184
  name: built-bdist
@@ -199,7 +199,7 @@ jobs:
199
199
  run: |
200
200
  python -m pip install --upgrade pip
201
201
  pip install --upgrade twine
202
- - uses: actions/download-artifact@v6
202
+ - uses: actions/download-artifact@v8
203
203
  with:
204
204
  # unpacks default artifact into dist/
205
205
  # if `name: wheels` is omitted, the action will create extra parent dir
@@ -224,7 +224,7 @@ jobs:
224
224
  permissions:
225
225
  id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
226
226
  steps:
227
- - uses: actions/download-artifact@v6
227
+ - uses: actions/download-artifact@v8
228
228
  with:
229
229
  pattern: built-*
230
230
  merge-multiple: true
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: urwid
3
- Version: 3.0.4
3
+ Version: 4.0.0
4
4
  Summary: A full-featured console (xterm et al.) user interface library
5
5
  Author-email: Ian Ward <ian@excess.org>
6
6
  License-Expression: LGPL-2.1-only
@@ -33,7 +33,7 @@ Classifier: Programming Language :: Python :: Implementation :: PyPy
33
33
  Requires-Python: >=3.9.0
34
34
  Description-Content-Type: text/x-rst
35
35
  License-File: COPYING
36
- Requires-Dist: wcwidth
36
+ Requires-Dist: wcwidth>=0.4
37
37
  Provides-Extra: curses
38
38
  Requires-Dist: windows-curses; sys_platform == "win32" and extra == "curses"
39
39
  Provides-Extra: glib
@@ -2,6 +2,26 @@
2
2
  Changelog
3
3
  ---------
4
4
 
5
+ Urwid 3.0.5
6
+ ===========
7
+
8
+ 2026-02-02
9
+
10
+ Deprecations ⚡
11
+ ++++++++++++++
12
+ * Un-deprecate `user_arg` for signals by @penguinolog in https://github.com/urwid/urwid/pull/1092
13
+ Bug fixes 🕷
14
+ ++++++++++++
15
+ * Fix terminal scroll TypeError by @penguinolog in https://github.com/urwid/urwid/pull/1098
16
+ * Bugfix some kinds of emojis with grapheme boundry support by @jquast in https://github.com/urwid/urwid/pull/1100
17
+ * Fix: Text.pack line splitting not match Text.render by @penguinolog in https://github.com/urwid/urwid/pull/1099
18
+
19
+ New Contributors
20
+ ++++++++++++++++
21
+ * @jquast made their first contribution in https://github.com/urwid/urwid/pull/1100
22
+
23
+ **Full Changelog**: https://github.com/urwid/urwid/compare/3.0.4...3.0.5
24
+
5
25
  Urwid 3.0.4
6
26
  ===========
7
27
 
@@ -14,7 +34,8 @@ Other Changes
14
34
  +++++++++++++
15
35
  * Refactoring: use official python way for lazy imports by @penguinolog in https://github.com/urwid/urwid/pull/1077
16
36
 
17
- ## New Contributors
37
+ New Contributors
38
+ ++++++++++++++++
18
39
  * @sawdmn made their first contribution in https://github.com/urwid/urwid/pull/1079
19
40
 
20
41
  **Full Changelog**: https://github.com/urwid/urwid/compare/3.0.3...3.0.4
Binary file
@@ -48,7 +48,8 @@ class LineWalker(urwid.ListWalker):
48
48
  self.focus = 0
49
49
 
50
50
  def __del__(self) -> None:
51
- self.file.close()
51
+ if self.file is not None:
52
+ self.file.close()
52
53
 
53
54
  def get_focus(self):
54
55
  return self._get_at_pos(self.focus)
@@ -48,7 +48,8 @@ class LineWalker(urwid.ListWalker):
48
48
  self.focus = 0
49
49
 
50
50
  def __del__(self) -> None:
51
- self.file.close()
51
+ if self.file is not None:
52
+ self.file.close()
52
53
 
53
54
  def get_focus(self):
54
55
  return self._get_at_pos(self.focus)
@@ -30,14 +30,9 @@ platforms=["unix-like"]
30
30
  zip-safe = false
31
31
 
32
32
  [tool.setuptools.packages.find]
33
- exclude = [
34
- "doc",
35
- "docs",
36
- "examples",
37
- "test",
38
- "tests",
39
- "bin",
40
- ".*"
33
+ include = [
34
+ "urwid",
35
+ "urwid.*",
41
36
  ]
42
37
  namespaces = false
43
38
 
@@ -0,0 +1 @@
1
+ wcwidth>=0.4
@@ -0,0 +1 @@
1
+ ruff==0.15.8
@@ -247,6 +247,7 @@ class AsyncioEventLoopTest(unittest.TestCase, EventLoopTestMixin):
247
247
  self.assertRaises(ZeroDivisionError, evl.run)
248
248
 
249
249
 
250
+ @unittest.skip("Unstable test: race conditions happens")
250
251
  @unittest.skipUnless(GLIB_AVAILABLE, "GLIB unavailable")
251
252
  class GLibEventLoopTest(unittest.TestCase, EventLoopTestMixin):
252
253
  def setUp(self):
@@ -0,0 +1,210 @@
1
+ from __future__ import annotations
2
+
3
+ import unittest
4
+
5
+ from urwid import str_util
6
+
7
+ # 🇨🇦 regional indicator Canada flag
8
+ FLAG_CA = "\U0001F1E8\U0001F1E6"
9
+ # 👨‍👩‍👧 family (man + ZWJ + woman + ZWJ + girl)
10
+ FAMILY = "\U0001F468\u200D\U0001F469\u200D\U0001F467"
11
+ # 👩🏻‍💻 woman technologist with light skin tone
12
+ TECHNOLOGIST = "\U0001F469\U0001F3FB\u200D\U0001F4BB"
13
+ # 👋🏻 waving hand with light skin tone
14
+ WAVE_SKIN = "\U0001F44B\U0001F3FB"
15
+ # café with combining acute accent on e
16
+ CAFE = "cafe\u0301"
17
+ # ❤️ red heart with VS16 (emoji presentation)
18
+ HEART_VS16 = "\u2764\ufe0f"
19
+ # ❤ red heart (text presentation)
20
+ HEART = "\u2764"
21
+ # 中文 Chinese characters
22
+ CJK = "\u4e2d\u6587"
23
+
24
+
25
+ class CalcWidthGraphemeTest(unittest.TestCase):
26
+ def wtest(self, text, expected):
27
+ result = str_util.calc_width(text, 0, len(text))
28
+ assert result == expected, f"got:{result!r} expected:{expected!r}"
29
+
30
+ def test_basic(self):
31
+ self.wtest("hello", 5)
32
+ self.wtest("", 0)
33
+ self.wtest(CJK, 4)
34
+
35
+ def test_flags(self):
36
+ self.wtest(FLAG_CA, 2)
37
+ self.wtest(FLAG_CA + FLAG_CA, 4)
38
+
39
+ def test_zwj_sequences(self):
40
+ self.wtest(FAMILY, 2)
41
+ self.wtest(TECHNOLOGIST, 2)
42
+
43
+ def test_skin_tones(self):
44
+ self.wtest(WAVE_SKIN, 2)
45
+
46
+ def test_combining(self):
47
+ self.wtest(CAFE, 4)
48
+ self.wtest("e\u0301", 1)
49
+
50
+ def test_variation_selectors(self):
51
+ self.wtest(HEART_VS16, 2)
52
+ self.wtest(HEART, 1)
53
+
54
+
55
+ class CalcTextPosGraphemeTest(unittest.TestCase):
56
+ def ptest(self, text, pref_col, expected_pos):
57
+ pos, _ = str_util.calc_text_pos(text, 0, len(text), pref_col)
58
+ assert pos == expected_pos, f"got:{pos!r} expected:{expected_pos!r}"
59
+
60
+ def test_basic(self):
61
+ self.ptest("hello", 3, 3)
62
+
63
+ def test_flag_boundaries(self):
64
+ text = f"A{FLAG_CA}B"
65
+ self.ptest(text, 1, 1)
66
+ self.ptest(text, 2, 1)
67
+ self.ptest(text, 3, 3)
68
+
69
+ def test_zwj_boundaries(self):
70
+ text = f"A{FAMILY}B"
71
+ self.ptest(text, 2, 1)
72
+ self.ptest(text, 3, 6)
73
+
74
+
75
+ class MoveNextCharGraphemeTest(unittest.TestCase):
76
+ def test_move_through_family_emoji(self):
77
+ text = f"A{FAMILY}B"
78
+ pos = str_util.move_next_char(text, 1, len(text))
79
+ assert pos == 6
80
+
81
+ def test_move_through_flag(self):
82
+ text = f"A{FLAG_CA}B"
83
+ pos = str_util.move_next_char(text, 1, len(text))
84
+ assert pos == 3
85
+
86
+ def test_move_through_combining(self):
87
+ pos = str_util.move_next_char(CAFE, 3, len(CAFE))
88
+ assert pos == 5
89
+
90
+ def test_move_through_skin_tone(self):
91
+ text = f"A{WAVE_SKIN}B"
92
+ pos = str_util.move_next_char(text, 1, len(text))
93
+ assert pos == 3
94
+
95
+ def test_basic_ascii(self):
96
+ pos = str_util.move_next_char("hello", 0, 5)
97
+ assert pos == 1
98
+
99
+
100
+ class MovePrevCharGraphemeTest(unittest.TestCase):
101
+ def test_move_back_through_family_emoji(self):
102
+ text = f"A{FAMILY}B"
103
+ pos = str_util.move_prev_char(text, 0, 6)
104
+ assert pos == 1
105
+
106
+ def test_move_back_through_flag(self):
107
+ text = f"A{FLAG_CA}B"
108
+ pos = str_util.move_prev_char(text, 0, 3)
109
+ assert pos == 1
110
+
111
+ def test_move_back_through_combining(self):
112
+ pos = str_util.move_prev_char(CAFE, 0, 5)
113
+ assert pos == 3
114
+
115
+ def test_move_back_through_skin_tone(self):
116
+ text = f"A{WAVE_SKIN}B"
117
+ pos = str_util.move_prev_char(text, 0, 3)
118
+ assert pos == 1
119
+
120
+ def test_basic_ascii(self):
121
+ pos = str_util.move_prev_char("hello", 0, 5)
122
+ assert pos == 4
123
+
124
+
125
+ class IsWideCharGraphemeTest(unittest.TestCase):
126
+ def test_family_emoji_is_wide(self):
127
+ assert str_util.is_wide_char(FAMILY, 0)
128
+
129
+ def test_flag_is_wide(self):
130
+ assert str_util.is_wide_char(FLAG_CA, 0)
131
+
132
+ def test_cjk_is_wide(self):
133
+ assert str_util.is_wide_char(CJK, 0)
134
+
135
+ def test_ascii_not_wide(self):
136
+ assert not str_util.is_wide_char("hello", 0)
137
+
138
+ def test_skin_tone_is_wide(self):
139
+ assert str_util.is_wide_char(WAVE_SKIN, 0)
140
+
141
+
142
+ # UTF-8 bytes test constants
143
+ FLAG_CA_BYTES = FLAG_CA.encode("utf-8") # 8 bytes
144
+ FAMILY_BYTES = FAMILY.encode("utf-8") # 18 bytes
145
+
146
+
147
+ class Utf8BytesGraphemeTest(unittest.TestCase):
148
+ """Tests for grapheme clustering with UTF-8 bytes."""
149
+
150
+ def setUp(self):
151
+ self._orig_encoding = str_util.get_byte_encoding()
152
+ str_util.set_byte_encoding("utf8")
153
+
154
+ def tearDown(self):
155
+ str_util.set_byte_encoding(self._orig_encoding)
156
+
157
+ def test_calc_width_flag_bytes(self):
158
+ result = str_util.calc_width(FLAG_CA_BYTES, 0, len(FLAG_CA_BYTES))
159
+ assert result == 2
160
+
161
+ def test_calc_width_family_emoji_bytes(self):
162
+ result = str_util.calc_width(FAMILY_BYTES, 0, len(FAMILY_BYTES))
163
+ assert result == 2
164
+
165
+ def test_move_next_through_family_emoji_bytes(self):
166
+ # After 'A' (1 byte), should skip entire family emoji
167
+ text = f"A{FAMILY}B".encode("utf-8")
168
+ pos = str_util.move_next_char(text, 1, len(text))
169
+ assert pos == 1 + len(FAMILY_BYTES)
170
+
171
+ def test_move_next_through_flag_bytes(self):
172
+ # After 'A' (1 byte), should skip entire flag
173
+ text = f"A{FLAG_CA}B".encode("utf-8")
174
+ pos = str_util.move_next_char(text, 1, len(text))
175
+ assert pos == 1 + len(FLAG_CA_BYTES)
176
+
177
+ def test_move_prev_through_family_emoji_bytes(self):
178
+ # From position after family emoji, should go back to after 'A'
179
+ text = f"A{FAMILY}B".encode("utf-8")
180
+ end_pos = 1 + len(FAMILY_BYTES)
181
+ pos = str_util.move_prev_char(text, 0, end_pos)
182
+ assert pos == 1
183
+
184
+ def test_move_prev_through_flag_bytes(self):
185
+ # From position after flag, should go back to after 'A'
186
+ text = f"A{FLAG_CA}B".encode("utf-8")
187
+ end_pos = 1 + len(FLAG_CA_BYTES)
188
+ pos = str_util.move_prev_char(text, 0, end_pos)
189
+ assert pos == 1
190
+
191
+ def test_is_wide_family_emoji_bytes(self):
192
+ assert str_util.is_wide_char(FAMILY_BYTES, 0)
193
+
194
+ def test_is_wide_flag_bytes(self):
195
+ assert str_util.is_wide_char(FLAG_CA_BYTES, 0)
196
+
197
+ def test_calc_text_pos_flag_boundaries_bytes(self):
198
+ text = f"A{FLAG_CA}B".encode("utf-8")
199
+ # Column 1 should be at position 1 (after 'A')
200
+ pos, col = str_util.calc_text_pos(text, 0, len(text), 1)
201
+ assert pos == 1
202
+ assert col == 1
203
+ # Column 2 should still be at position 1 (inside flag grapheme)
204
+ pos, col = str_util.calc_text_pos(text, 0, len(text), 2)
205
+ assert pos == 1
206
+ assert col == 1
207
+ # Column 3 should be at position after flag
208
+ pos, col = str_util.calc_text_pos(text, 0, len(text), 3)
209
+ assert pos == 1 + len(FLAG_CA_BYTES)
210
+ assert col == 3
@@ -38,7 +38,8 @@ class TestScrollable(unittest.TestCase):
38
38
  widget = urwid.Scrollable(long_content)
39
39
  self.assertEqual(frozenset((urwid.BOX,)), widget.sizing())
40
40
 
41
- cropped_content_canvas = urwid.CompositeCanvas(long_content.render((reduced_size[0],)))
41
+ long_content_canvas = long_content.render((reduced_size[0],))
42
+ cropped_content_canvas = urwid.CompositeCanvas(long_content_canvas)
42
43
  cropped_content_canvas.trim_end(content_size[1] - reduced_size[1])
43
44
  top_decoded = cropped_content_canvas.decoded_text
44
45
 
@@ -169,15 +169,27 @@ class CalcTranslateTest(unittest.TestCase):
169
169
  def check(self, text, mode, width, result_left, result_center, result_right) -> None:
170
170
  with self.subTest("left"):
171
171
  result = urwid.default_layout.layout(text, width, "left", mode)
172
- self.assertEqual(result_left, result)
172
+ self.assertEqual(
173
+ result_left,
174
+ result,
175
+ f"For {text=} {width=} {mode=} got {result!r} instead of {result_left!r}",
176
+ )
173
177
 
174
178
  with self.subTest("center"):
175
179
  result = urwid.default_layout.layout(text, width, "center", mode)
176
- self.assertEqual(result_center, result)
180
+ self.assertEqual(
181
+ result_center,
182
+ result,
183
+ f"For {text=} {width=} {mode=} got {result!r} instead of {result_center!r}",
184
+ )
177
185
 
178
186
  with self.subTest("right"):
179
187
  result = urwid.default_layout.layout(text, width, "right", mode)
180
- self.assertEqual(result_right, result)
188
+ self.assertEqual(
189
+ result_right,
190
+ result,
191
+ f"For {text=} {width=} {mode=} got {result!r} instead of {result_right!r}",
192
+ )
181
193
 
182
194
  def test_calc_translate_char(self):
183
195
  self.check(
@@ -255,15 +255,7 @@ VERSION = __version_tuple__
255
255
  __locals: dict[str, typing.Any] = locals() # use mutable access for pure lazy loading
256
256
 
257
257
  # Backward compatible lazy load with deprecation warnings
258
- _moved_warn: dict[str, str] = {
259
- "lcd_display": "urwid.display.lcd",
260
- "html_fragment": "urwid.display.html_fragment",
261
- "web_display": "urwid.display.web",
262
- "monitored_list": "urwid.widget.monitored_list",
263
- "listbox": "urwid.widget.listbox",
264
- "treetools": "urwid.widget.treetools",
265
- }
266
- # Backward compatible lazy load without any warnings
258
+ _moved_warn: dict[str, str] = {}
267
259
  # Before DeprecationWarning need to start PendingDeprecationWarning process.
268
260
 
269
261
 
@@ -315,7 +315,10 @@ class Screen(_raw_display_base.Screen):
315
315
  selector.register(fd, selectors.EVENT_READ)
316
316
  input_ready = selector.select(0)
317
317
  while input_ready:
318
- chars.extend(os.read(fd, 1024))
318
+ chunk = os.read(fd, 1024)
319
+ if len(chunk) == 0:
320
+ raise RuntimeError("stdin has been closed")
321
+ chars.extend(chunk)
319
322
  input_ready = selector.select(0)
320
323
 
321
324
  return chars
@@ -1018,7 +1018,8 @@ class BaseScreen(abc.ABC, metaclass=signals.MetaSignals):
1018
1018
  Deprecated in favor of calling `start` as a context manager.
1019
1019
  """
1020
1020
  warnings.warn(
1021
- "run_wrapper is deprecated in favor of calling `start` as a context manager.",
1021
+ "run_wrapper is deprecated in favor of calling `start` as a context manager."
1022
+ "API will be removed in version 5.0.",
1022
1023
  DeprecationWarning,
1023
1024
  stacklevel=3,
1024
1025
  )
@@ -28,7 +28,6 @@ import os
28
28
  import sys
29
29
  import time
30
30
  import typing
31
- import warnings
32
31
  from contextlib import suppress
33
32
 
34
33
  from urwid import display, signals
@@ -169,16 +168,6 @@ class MainLoop:
169
168
  else:
170
169
  self._topmost_widget = self._widget
171
170
 
172
- def _set_widget(self, widget: Widget) -> None:
173
- warnings.warn(
174
- f"method `{self.__class__.__name__}._set_widget` is deprecated, "
175
- f"please use `{self.__class__.__name__}.widget` property."
176
- "API will be removed in version 4.0.",
177
- DeprecationWarning,
178
- stacklevel=2,
179
- )
180
- self.widget = widget
181
-
182
171
  @property
183
172
  def pop_ups(self) -> bool:
184
173
  return self._pop_ups
@@ -191,16 +180,6 @@ class MainLoop:
191
180
  else:
192
181
  self._topmost_widget = self._widget
193
182
 
194
- def _set_pop_ups(self, pop_ups: bool) -> None:
195
- warnings.warn(
196
- f"method `{self.__class__.__name__}._set_pop_ups` is deprecated, "
197
- f"please use `{self.__class__.__name__}.pop_ups` property."
198
- "API will be removed in version 4.0.",
199
- DeprecationWarning,
200
- stacklevel=2,
201
- )
202
- self.pop_ups = pop_ups
203
-
204
183
  def set_alarm_in(self, sec: float, callback: Callable[[Self, _T], typing.Any], user_data: _T = None):
205
184
  """
206
185
  Schedule an alarm in *sec* seconds that will call *callback* from the
@@ -24,15 +24,15 @@ import typing
24
24
  import warnings
25
25
  from pprint import pformat
26
26
 
27
+ import wcwidth
28
+
27
29
  from urwid.canvas import CanvasError, TextCanvas
28
30
  from urwid.display.escape import SAFE_ASCII_DEC_SPECIAL_RE
29
- from urwid.util import apply_target_encoding, str_util
31
+ from urwid.util import apply_target_encoding
30
32
 
31
33
  if typing.TYPE_CHECKING:
32
34
  from collections.abc import Iterator, Sequence
33
35
 
34
- from typing_extensions import Literal
35
-
36
36
 
37
37
  def separate_glyphs(gdata: str, height: int) -> tuple[dict[str, tuple[int, list[str]]], bool]:
38
38
  """return (dictionary of glyphs, utf8 required)"""
@@ -60,7 +60,7 @@ def separate_glyphs(gdata: str, height: int) -> tuple[dict[str, tuple[int, list[
60
60
  character = key_line[key_index]
61
61
 
62
62
  if key_index < len(key_line) and key_line[key_index] == character:
63
- end_col += str_util.get_char_width(character)
63
+ end_col += wcwidth.width(character, control_codes="ignore")
64
64
  key_index += 1
65
65
  continue
66
66
 
@@ -76,7 +76,7 @@ def separate_glyphs(gdata: str, height: int) -> tuple[dict[str, tuple[int, list[
76
76
  if j >= len(line):
77
77
  fill = end_col - start_col - y
78
78
  break
79
- y += str_util.get_char_width(line[j])
79
+ y += wcwidth.width(line[j], control_codes="ignore")
80
80
  j += 1
81
81
  if y + fill != end_col - start_col:
82
82
  raise ValueError(repr((y, fill, end_col)))
@@ -98,7 +98,8 @@ def add_font(name: str, cls: FontRegistry) -> None:
98
98
  warnings.warn(
99
99
  "`add_font` is deprecated, please set 'name' attribute to the font class,"
100
100
  " use metaclass keyword argument 'font_name'"
101
- " or use `Font.register(<name>)`",
101
+ " or use `Font.register(<name>)`"
102
+ "API will be removed in version 5.0.",
102
103
  DeprecationWarning,
103
104
  stacklevel=2,
104
105
  )
@@ -219,22 +220,6 @@ class Font(metaclass=FontRegistry):
219
220
  """Font description."""
220
221
  return f"{self.__class__.__name__}():\n {self.height!r}\n {pformat(self.data, indent=4)}"
221
222
 
222
- @staticmethod
223
- def _to_text(
224
- obj: str,
225
- encoding: str = "utf-8",
226
- errors: Literal["strict", "ignore", "replace"] = "strict",
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
- )
233
- if isinstance(obj, str):
234
- return obj
235
-
236
- raise TypeError(f"{obj!r} is not str")
237
-
238
223
  def add_glyphs(self, gdata: str) -> None:
239
224
  d, utf8_required = separate_glyphs(gdata, self.height)
240
225
  self.char.update(d)
@@ -23,7 +23,6 @@ from __future__ import annotations
23
23
  import abc
24
24
  import itertools
25
25
  import typing
26
- import warnings
27
26
  import weakref
28
27
 
29
28
  if typing.TYPE_CHECKING:
@@ -99,9 +98,9 @@ class Signals:
99
98
  :type name: signal name
100
99
  :param callback: the function to call when that signal is sent
101
100
  :type callback: function
102
- :param user_arg: deprecated additional argument to callback (appended
103
- after the arguments passed when the signal is
104
- emitted). If None no arguments will be added.
101
+ :param user_arg: additional argument to callback
102
+ (appended after the arguments passed when the signal is emitted).
103
+ If None no arguments will be added.
105
104
  Don't use this argument, use user_args instead.
106
105
  :param weak_args: additional arguments passed to the callback
107
106
  (before any arguments passed when the signal
@@ -167,12 +166,6 @@ class Signals:
167
166
  handler can also be disconnected by calling
168
167
  urwid.disconnect_signal, which doesn't need this key.
169
168
  """
170
- if user_arg is not None:
171
- warnings.warn(
172
- "Don't use user_arg argument, use user_args instead.",
173
- DeprecationWarning,
174
- stacklevel=2,
175
- )
176
169
 
177
170
  sig_cls = obj.__class__
178
171
  if name not in self._supported.get(sig_cls, ()):
@@ -195,9 +188,8 @@ class Signals:
195
188
  # their callbacks) from existing.
196
189
  obj_weak = weakref.ref(obj)
197
190
 
198
- def weakref_callback(weakref): # pylint: disable=redefined-outer-name # bad, but not changing API
199
- o = obj_weak()
200
- if o:
191
+ def weakref_callback(_ref: weakref.ReferenceType[typing.Any]) -> None:
192
+ if o := obj_weak():
201
193
  self.disconnect_by_key(o, name, key)
202
194
 
203
195
  user_args = self._prepare_user_args(weak_args, user_args, weakref_callback)
@@ -209,7 +201,7 @@ class Signals:
209
201
  self,
210
202
  weak_args: Iterable[typing.Any] = (),
211
203
  user_args: Iterable[typing.Any] = (),
212
- callback: Callable[..., typing.Any] | None = None,
204
+ callback: Callable[[weakref.ReferenceType[typing.Any]], typing.Any] | None = None,
213
205
  ) -> tuple[Collection[weakref.ReferenceType], Collection[typing.Any]]:
214
206
  # Turn weak_args into weakrefs and prepend them to user_args
215
207
  w_args = tuple(weakref.ref(w_arg, callback) for w_arg in weak_args)
@@ -303,14 +295,13 @@ class Signals:
303
295
  self,
304
296
  callback,
305
297
  user_arg: typing.Any,
306
- weak_args: Collection[weakref.ReferenceType],
307
- user_args: Collection[typing.Any],
298
+ weak_args: Iterable[weakref.ReferenceType],
299
+ user_args: Iterable[typing.Any],
308
300
  emit_args: Iterable[typing.Any],
309
301
  ) -> bool:
310
302
  args_to_pass = []
311
303
  for w_arg in weak_args:
312
- real_arg = w_arg()
313
- if real_arg is not None:
304
+ if (real_arg := w_arg()) is not None:
314
305
  args_to_pass.append(real_arg)
315
306
  else:
316
307
  # de-referenced