urwid 4.0.4__tar.gz → 4.0.5__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.
- {urwid-4.0.4/urwid.egg-info → urwid-4.0.5}/PKG-INFO +1 -1
- {urwid-4.0.4 → urwid-4.0.5}/docs/changelog.rst +27 -0
- urwid-4.0.5/ruff-requirements.txt +1 -0
- {urwid-4.0.4 → urwid-4.0.5}/tests/test_scrollable.py +66 -0
- {urwid-4.0.4 → urwid-4.0.5}/tests/test_signals.py +24 -0
- {urwid-4.0.4 → urwid-4.0.5}/tests/test_widget.py +52 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/display/_posix_raw_display.py +1 -1
- {urwid-4.0.4 → urwid-4.0.5}/urwid/display/_raw_display_base.py +2 -4
- {urwid-4.0.4 → urwid-4.0.5}/urwid/signals.py +1 -1
- {urwid-4.0.4 → urwid-4.0.5}/urwid/split_repr.py +1 -1
- {urwid-4.0.4 → urwid-4.0.5}/urwid/util.py +12 -7
- {urwid-4.0.4 → urwid-4.0.5}/urwid/version.py +3 -3
- {urwid-4.0.4 → urwid-4.0.5}/urwid/widget/attr_map.py +6 -2
- {urwid-4.0.4 → urwid-4.0.5}/urwid/widget/edit.py +110 -48
- {urwid-4.0.4 → urwid-4.0.5}/urwid/widget/filler.py +10 -5
- {urwid-4.0.4 → urwid-4.0.5}/urwid/widget/pile.py +4 -3
- {urwid-4.0.4 → urwid-4.0.5}/urwid/widget/scrollable.py +82 -15
- {urwid-4.0.4 → urwid-4.0.5}/urwid/widget/widget.py +1 -1
- {urwid-4.0.4 → urwid-4.0.5}/urwid/widget/widget_decoration.py +11 -6
- {urwid-4.0.4 → urwid-4.0.5/urwid.egg-info}/PKG-INFO +1 -1
- {urwid-4.0.4 → urwid-4.0.5}/urwid.egg-info/scm_file_list.json +265 -265
- urwid-4.0.5/urwid.egg-info/scm_version.json +8 -0
- urwid-4.0.4/ruff-requirements.txt +0 -1
- urwid-4.0.4/urwid.egg-info/scm_version.json +0 -8
- {urwid-4.0.4 → urwid-4.0.5}/.coveralls.yml +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/.devcontainer/dev.dockerfile +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/.devcontainer/devcontainer.json +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/.dockerignore +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/.editorconfig +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/.github/CODE_OF_CONDUCT.md +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/.github/ISSUE_TEMPLATE/docs.md +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/.github/ISSUE_TEMPLATE/question.md +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/.github/dependabot.yml +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/.github/labeler.yml +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/.github/release.yml +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/.github/workflows/documentation.yml +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/.github/workflows/isolated_static_check.yml +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/.github/workflows/labels.yml +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/.github/workflows/pythonpackage.yml +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/.github/workflows/yamllint.yml +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/.gitignore +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/.pre-commit-config.yaml +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/.yamllint.yml +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/COPYING +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/MANIFEST.in +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/README.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/SECURITY.md +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/classifiers.txt +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/Makefile +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/conf.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/examples/bigtext.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/examples/bigtext.py.xdotool +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/examples/bigtext1.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/examples/bigtext2.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/examples/bigtext3.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/examples/browse.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/examples/browse.py.xdotool +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/examples/browse1.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/examples/browse2.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/examples/edit.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/examples/edit.py.xdotool +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/examples/edit1.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/examples/edit2.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/examples/edit_text.txt +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/examples/graph.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/examples/graph.py.xdotool +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/examples/graph1.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/examples/graph2.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/examples/index.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/examples/palette_test.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/examples/palette_test.py.xdotool +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/examples/palette_test1.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/examples/palette_test2.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/examples/pop_up.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/examples/pop_up.py.xdotool +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/examples/pop_up1.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/examples/pop_up2.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/examples/real_browse.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/examples/real_edit.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/examples/subproc.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/examples/subproc.py.xdotool +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/examples/subproc1.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/examples/subproc2.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/examples/subproc2.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/examples/tour.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/examples/tour.py.xdotool +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/examples/tour1.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/examples/tour2.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/index.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/manual/bright_combinations.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/manual/bright_combinations.py.xdotool +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/manual/bright_combinations1.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/manual/canvascache.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/manual/displayattributes.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/manual/displaymodules.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/manual/encodings.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/manual/images/display_modules.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/manual/images/introduction.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/manual/images/urwid_widgets.svgz +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/manual/images/urwid_widgets_1.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/manual/images/urwid_widgets_1.xcf +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/manual/images/urwid_widgets_2.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/manual/images/urwid_widgets_2.xcf +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/manual/images/widget_layout.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/manual/index.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/manual/mainloop.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/manual/overview.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/manual/safe_combinations.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/manual/safe_combinations.py.xdotool +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/manual/safe_combinations1.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/manual/textlayout.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/manual/userinput.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/manual/wanat.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/manual/wanat_multi.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/manual/wcur1.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/manual/wcur2.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/manual/widgets.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/manual/wmod.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/manual/wsel.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/reference/attrspec.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/reference/canvas.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/reference/command_map.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/reference/constants.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/reference/deprecated.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/reference/display_modules.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/reference/exceptions.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/reference/global_settings.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/reference/index.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/reference/list_walkers.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/reference/main_loop.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/reference/meta.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/reference/signals.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/reference/text_layout.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/reference/widget.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tools/compile_pngs.sh +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tools/screenshots.sh +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tools/static/.placeholder +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tools/templates/indexcontent.html +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tools/templates/indexsidebar.html +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tools/templates/localtoc.html +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/adventure.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/adventure.py.xdotool +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/adventure1.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/adventure2.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/adventure3.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/adventure4.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/cmenu.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/cmenu.py.xdotool +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/cmenu1.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/cmenu2.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/cmenu3.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/cmenu4.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/highcolors.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/highcolors.py.xdotool +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/highcolors1.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/hmenu.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/hmenu.py.xdotool +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/hmenu1.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/hmenu2.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/hmenu3.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/hmenu4.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/index.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/input.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/input.py.xdotool +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/input1.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/input2.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/input3.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/input4.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/input5.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/menu25.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/minimal.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/minimal.py.xdotool +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/minimal1.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/multiple.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/multiple.py.xdotool +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/multiple1.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/multiple2.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/multiple3.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/multiple4.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/qa.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/qa.py.xdotool +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/qa1.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/qa2.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/qa3.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/sig.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/sig.py.xdotool +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/sig1.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/sig2.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/sig3.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/sig4.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/smenu.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/smenu.py.xdotool +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/smenu1.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/smenu2.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/smenu3.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/urwid_attr.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/urwid_attr.py.xdotool +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/urwid_attr1.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/urwid_attr2.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/urwid_attr3.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/tutorial/urwid_attr4.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/docs/urwid-logo.png +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/examples/asyncio_socket_server.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/examples/bigtext.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/examples/browse.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/examples/calc.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/examples/dialog.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/examples/edit.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/examples/fib.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/examples/graph.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/examples/input_test.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/examples/lcd_cf635.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/examples/palette_test.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/examples/pop_up.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/examples/subproc.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/examples/subproc2.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/examples/terminal.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/examples/tour.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/examples/treesample.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/examples/twisted_serve_ssh.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/examples/twisted_serve_ssh.tac +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/pylint-requirements.txt +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/pyproject.toml +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/requirements.txt +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/setup.cfg +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/test_requirements.txt +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/tests/__init__.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/tests/test_canvas.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/tests/test_columns.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/tests/test_container.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/tests/test_doctests.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/tests/test_escapes.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/tests/test_event_loops.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/tests/test_filler.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/tests/test_floatedit.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/tests/test_font.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/tests/test_frame.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/tests/test_grapheme_clusters.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/tests/test_graphics.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/tests/test_grid_flow.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/tests/test_line_box.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/tests/test_listbox.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/tests/test_main_loop.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/tests/test_overlay.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/tests/test_padding.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/tests/test_pile.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/tests/test_raw_display.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/tests/test_str_util.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/tests/test_text_layout.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/tests/test_tree.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/tests/test_util.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/tests/test_vterm.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/tests/util.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/tox.ini +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/__init__.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/canvas.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/command_map.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/display/__init__.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/display/_web.css +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/display/_web.js +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/display/_win32.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/display/_win32_raw_display.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/display/common.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/display/curses.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/display/escape.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/display/html_fragment.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/display/lcd.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/display/raw.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/display/web.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/event_loop/__init__.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/event_loop/abstract_loop.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/event_loop/asyncio_loop.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/event_loop/glib_loop.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/event_loop/main_loop.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/event_loop/select_loop.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/event_loop/tornado_loop.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/event_loop/trio_loop.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/event_loop/twisted_loop.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/event_loop/zmq_loop.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/font.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/graphics.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/numedit.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/py.typed +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/str_util.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/text_layout.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/vterm.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/widget/__init__.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/widget/attr_wrap.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/widget/bar_graph.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/widget/big_text.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/widget/box_adapter.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/widget/columns.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/widget/constants.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/widget/container.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/widget/divider.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/widget/frame.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/widget/grid_flow.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/widget/line_box.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/widget/listbox.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/widget/monitored_list.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/widget/overlay.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/widget/padding.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/widget/popup.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/widget/progress_bar.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/widget/solid_fill.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/widget/text.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/widget/treetools.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid/widget/wimp.py +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid.egg-info/SOURCES.txt +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid.egg-info/dependency_links.txt +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid.egg-info/not-zip-safe +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid.egg-info/requires.txt +0 -0
- {urwid-4.0.4 → urwid-4.0.5}/urwid.egg-info/top_level.txt +0 -0
|
@@ -2,6 +2,33 @@
|
|
|
2
2
|
Changelog
|
|
3
3
|
---------
|
|
4
4
|
|
|
5
|
+
Urwid 4.0.5
|
|
6
|
+
===========
|
|
7
|
+
|
|
8
|
+
2026-07-20
|
|
9
|
+
|
|
10
|
+
New features 🗹
|
|
11
|
+
+++++++++++++++
|
|
12
|
+
* Copy signal handlers before emission by @Sanjays2402 in https://github.com/urwid/urwid/pull/1187
|
|
13
|
+
* Implement `_scrollbar_layout` and left click handling for `Scrollable` by @penguinolog in https://github.com/urwid/urwid/pull/1192
|
|
14
|
+
Bug fixes 🕷
|
|
15
|
+
++++++++++++
|
|
16
|
+
* Fix rle_product for zero-len elements by @penguinolog in https://github.com/urwid/urwid/pull/1191
|
|
17
|
+
Documentation 🕮
|
|
18
|
+
++++++++++++++++
|
|
19
|
+
* typing: fix `Filler` typing annotations by @penguinolog in https://github.com/urwid/urwid/pull/1183
|
|
20
|
+
* typing: extend widget_decoration module typing coverage by @penguinolog in https://github.com/urwid/urwid/pull/1184
|
|
21
|
+
* Typing: annotate and fix attr_map by @penguinolog in https://github.com/urwid/urwid/pull/1186
|
|
22
|
+
Refactoring 🛠
|
|
23
|
+
++++++++++++++
|
|
24
|
+
* Migrate `Edit` widget to use `WidgetWrap` as base class and update methods by @penguinolog in https://github.com/urwid/urwid/pull/1180
|
|
25
|
+
|
|
26
|
+
New Contributors
|
|
27
|
+
++++++++++++++++
|
|
28
|
+
* @Sanjays2402 made their first contribution in https://github.com/urwid/urwid/pull/1187
|
|
29
|
+
|
|
30
|
+
**Full Changelog**: https://github.com/urwid/urwid/compare/4.0.4...4.0.5
|
|
31
|
+
|
|
5
32
|
Urwid 4.0.4
|
|
6
33
|
===========
|
|
7
34
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ruff==0.15.22
|
|
@@ -142,6 +142,72 @@ class TestScrollBarScrollable(unittest.TestCase):
|
|
|
142
142
|
|
|
143
143
|
self.assertEqual(top_position_rendered, widget.render(reduced_size).decoded_text)
|
|
144
144
|
|
|
145
|
+
def test_mouse_left_click_scrollbar(self):
|
|
146
|
+
"""Left click on the rendered scrollbar jumps to the clicked position."""
|
|
147
|
+
content = urwid.Text("\n".join(string.ascii_letters)) # 52 single-char lines
|
|
148
|
+
reduced_size = (3, 5)
|
|
149
|
+
widget = urwid.ScrollBar(urwid.Scrollable(content))
|
|
150
|
+
scrollable = widget.original_widget
|
|
151
|
+
scrollbar_col = reduced_size[0] - 1 # right side scrollbar
|
|
152
|
+
|
|
153
|
+
self.assertEqual(0, scrollable.get_scrollpos())
|
|
154
|
+
|
|
155
|
+
# posmax == 52 - 5 == 47, thumb_height == 1, thumb travel == 5 - 1 == 4.
|
|
156
|
+
# Clicking the bottom row moves past the end and is clamped to posmax.
|
|
157
|
+
self.assertTrue(widget.mouse_event(reduced_size, "mouse press", 1, scrollbar_col, 4, False))
|
|
158
|
+
self.assertEqual(47, scrollable.get_scrollpos())
|
|
159
|
+
|
|
160
|
+
# Clicking the top row scrolls back to the top.
|
|
161
|
+
self.assertTrue(widget.mouse_event(reduced_size, "mouse press", 1, scrollbar_col, 0, False))
|
|
162
|
+
self.assertEqual(0, scrollable.get_scrollpos())
|
|
163
|
+
|
|
164
|
+
# Intermediate rows map proportionally: round(row * posmax / travel).
|
|
165
|
+
self.assertTrue(widget.mouse_event(reduced_size, "mouse press", 1, scrollbar_col, 1, False))
|
|
166
|
+
self.assertEqual(12, scrollable.get_scrollpos())
|
|
167
|
+
|
|
168
|
+
self.assertTrue(widget.mouse_event(reduced_size, "mouse press", 1, scrollbar_col, 3, False))
|
|
169
|
+
self.assertEqual(35, scrollable.get_scrollpos())
|
|
170
|
+
|
|
171
|
+
def test_mouse_left_click_content_ignored(self):
|
|
172
|
+
"""Left click outside of the scrollbar columns does not scroll."""
|
|
173
|
+
content = urwid.Text("\n".join(string.ascii_letters))
|
|
174
|
+
reduced_size = (3, 5)
|
|
175
|
+
widget = urwid.ScrollBar(urwid.Scrollable(content))
|
|
176
|
+
scrollable = widget.original_widget
|
|
177
|
+
|
|
178
|
+
self.assertEqual(0, scrollable.get_scrollpos())
|
|
179
|
+
|
|
180
|
+
# Column 0 belongs to the wrapped content, not the scrollbar.
|
|
181
|
+
self.assertFalse(widget.mouse_event(reduced_size, "mouse press", 1, 0, 4, False))
|
|
182
|
+
self.assertEqual(0, scrollable.get_scrollpos())
|
|
183
|
+
|
|
184
|
+
def test_mouse_left_click_left_side_scrollbar(self):
|
|
185
|
+
"""Left click detection honours a left-aligned scrollbar."""
|
|
186
|
+
content = urwid.Text("\n".join(string.ascii_letters))
|
|
187
|
+
reduced_size = (3, 5)
|
|
188
|
+
widget = urwid.ScrollBar(urwid.Scrollable(content), side="left")
|
|
189
|
+
scrollable = widget.original_widget
|
|
190
|
+
|
|
191
|
+
self.assertEqual(0, scrollable.get_scrollpos())
|
|
192
|
+
|
|
193
|
+
# The scrollbar occupies column 0 when aligned to the left.
|
|
194
|
+
self.assertTrue(widget.mouse_event(reduced_size, "mouse press", 1, 0, 4, False))
|
|
195
|
+
self.assertEqual(47, scrollable.get_scrollpos())
|
|
196
|
+
|
|
197
|
+
# A click on the content columns is ignored.
|
|
198
|
+
self.assertFalse(widget.mouse_event(reduced_size, "mouse press", 1, 1, 0, False))
|
|
199
|
+
self.assertEqual(47, scrollable.get_scrollpos())
|
|
200
|
+
|
|
201
|
+
def test_mouse_left_click_without_scrollbar(self):
|
|
202
|
+
"""Without a rendered scrollbar a left click is not handled."""
|
|
203
|
+
content = urwid.Text("a\nb") # fits into the available height
|
|
204
|
+
reduced_size = (3, 5)
|
|
205
|
+
widget = urwid.ScrollBar(urwid.Scrollable(content))
|
|
206
|
+
scrollable = widget.original_widget
|
|
207
|
+
|
|
208
|
+
self.assertFalse(widget.mouse_event(reduced_size, "mouse press", 1, 2, 2, False))
|
|
209
|
+
self.assertEqual(0, scrollable.get_scrollpos())
|
|
210
|
+
|
|
145
211
|
def test_alt_symbols(self):
|
|
146
212
|
long_content = urwid.Text(LGPL_HEADER)
|
|
147
213
|
reduced_size = (40, 5)
|
|
@@ -25,6 +25,30 @@ class SiglnalsTest(unittest.TestCase):
|
|
|
25
25
|
edit.set_edit_text("another text")
|
|
26
26
|
handler.assert_not_called()
|
|
27
27
|
|
|
28
|
+
def test_handler_mutation_during_emit(self):
|
|
29
|
+
emitter = self.EmClass()
|
|
30
|
+
calls = []
|
|
31
|
+
|
|
32
|
+
def third():
|
|
33
|
+
calls.append("third")
|
|
34
|
+
|
|
35
|
+
def first():
|
|
36
|
+
calls.append("first")
|
|
37
|
+
disconnect_signal(emitter, "test", first)
|
|
38
|
+
connect_signal(emitter, "test", third)
|
|
39
|
+
|
|
40
|
+
def second():
|
|
41
|
+
calls.append("second")
|
|
42
|
+
|
|
43
|
+
connect_signal(emitter, "test", first)
|
|
44
|
+
connect_signal(emitter, "test", second)
|
|
45
|
+
|
|
46
|
+
emit_signal(emitter, "test")
|
|
47
|
+
self.assertEqual(calls, ["first", "second"])
|
|
48
|
+
|
|
49
|
+
emit_signal(emitter, "test")
|
|
50
|
+
self.assertEqual(calls, ["first", "second", "second", "third"])
|
|
51
|
+
|
|
28
52
|
@unittest.skipIf(sys.implementation.name == "pypy", "WeakRef works differently on PyPy")
|
|
29
53
|
def test_weak_del(self):
|
|
30
54
|
emitter = SiglnalsTest.EmClass()
|
|
@@ -39,6 +39,58 @@ class TextTest(unittest.TestCase):
|
|
|
39
39
|
got = urwid.Text("û").render((3,))._text
|
|
40
40
|
assert got == expected, f"got: {got!r} expected: {expected!r}"
|
|
41
41
|
|
|
42
|
+
def test_empty_markup_segment_middle(self) -> None:
|
|
43
|
+
"""An empty markup segment in the middle must not truncate the rendered row."""
|
|
44
|
+
widget = urwid.Text([("a", "X" * 10), ("b", ""), ("c", " END")])
|
|
45
|
+
|
|
46
|
+
self.assertEqual(("XXXXXXXXXX END", [("a", 10), ("b", 0), ("c", 4)]), widget.get_text())
|
|
47
|
+
|
|
48
|
+
canvas = widget.render(())
|
|
49
|
+
self.assertEqual([b"XXXXXXXXXX END"], canvas.text)
|
|
50
|
+
self.assertEqual(("XXXXXXXXXX END",), canvas.decoded_text)
|
|
51
|
+
|
|
52
|
+
def test_empty_markup_segment_start(self) -> None:
|
|
53
|
+
"""An empty markup segment at the start must keep the whole rendered row."""
|
|
54
|
+
widget = urwid.Text([("b", ""), ("a", "X" * 10), ("c", " END")])
|
|
55
|
+
|
|
56
|
+
self.assertEqual(("XXXXXXXXXX END", [("b", 0), ("a", 10), ("c", 4)]), widget.get_text())
|
|
57
|
+
|
|
58
|
+
canvas = widget.render(())
|
|
59
|
+
self.assertEqual([b"XXXXXXXXXX END"], canvas.text)
|
|
60
|
+
self.assertEqual(("XXXXXXXXXX END",), canvas.decoded_text)
|
|
61
|
+
|
|
62
|
+
def test_empty_markup_segment_end(self) -> None:
|
|
63
|
+
"""An empty markup segment at the end must keep the whole rendered row."""
|
|
64
|
+
widget = urwid.Text([("a", "X" * 10), ("c", " END"), ("b", "")])
|
|
65
|
+
|
|
66
|
+
self.assertEqual(("XXXXXXXXXX END", [("a", 10), ("c", 4), ("b", 0)]), widget.get_text())
|
|
67
|
+
|
|
68
|
+
canvas = widget.render(())
|
|
69
|
+
self.assertEqual([b"XXXXXXXXXX END"], canvas.text)
|
|
70
|
+
self.assertEqual(("XXXXXXXXXX END",), canvas.decoded_text)
|
|
71
|
+
|
|
72
|
+
def test_multiple_empty_markup_segments(self) -> None:
|
|
73
|
+
"""Several consecutive empty markup segments must not drop following content."""
|
|
74
|
+
widget = urwid.Text([("a", "AB"), ("b", ""), ("c", ""), ("d", "CD")])
|
|
75
|
+
|
|
76
|
+
self.assertEqual(("ABCD", [("a", 2), ("b", 0), ("c", 0), ("d", 2)]), widget.get_text())
|
|
77
|
+
|
|
78
|
+
canvas = widget.render(())
|
|
79
|
+
self.assertEqual([b"ABCD"], canvas.text)
|
|
80
|
+
self.assertEqual(("ABCD",), canvas.decoded_text)
|
|
81
|
+
|
|
82
|
+
def test_empty_markup_segment_render_content(self) -> None:
|
|
83
|
+
"""The canvas content rows must expose the display attribute of each segment."""
|
|
84
|
+
widget = urwid.Text([("a", "X" * 10), ("b", ""), ("c", " END")])
|
|
85
|
+
|
|
86
|
+
content = list(widget.render(()).content())
|
|
87
|
+
attrs = [attr for row in content for attr, _cs, _text in row]
|
|
88
|
+
rendered = b"".join(text for row in content for _attr, _cs, text in row)
|
|
89
|
+
|
|
90
|
+
self.assertEqual(b"XXXXXXXXXX END", rendered)
|
|
91
|
+
self.assertIn("a", attrs)
|
|
92
|
+
self.assertIn("c", attrs)
|
|
93
|
+
|
|
42
94
|
|
|
43
95
|
class EditTest(unittest.TestCase):
|
|
44
96
|
def setUp(self):
|
|
@@ -333,7 +333,7 @@ class Screen(_raw_display_base.Screen):
|
|
|
333
333
|
input_ready = selector.select(0)
|
|
334
334
|
while input_ready:
|
|
335
335
|
chunk = os.read(fd, 1024)
|
|
336
|
-
if
|
|
336
|
+
if not chunk:
|
|
337
337
|
raise RuntimeError("stdin has been closed")
|
|
338
338
|
chars.extend(chunk)
|
|
339
339
|
input_ready = selector.select(0)
|
|
@@ -483,7 +483,7 @@ class Screen(BaseScreen, RealTerminal):
|
|
|
483
483
|
try:
|
|
484
484
|
while codes:
|
|
485
485
|
run, remaining_codes = escape.process_keyqueue(codes, wait_for_more)
|
|
486
|
-
codes =
|
|
486
|
+
codes = remaining_codes.copy()
|
|
487
487
|
decoded_codes.extend(run)
|
|
488
488
|
except escape.MoreInputRequired:
|
|
489
489
|
# Set a timer to wait for the rest of the input; if it goes off
|
|
@@ -587,9 +587,7 @@ class Screen(BaseScreen, RealTerminal):
|
|
|
587
587
|
run: bytes,
|
|
588
588
|
last: bool,
|
|
589
589
|
) -> None:
|
|
590
|
-
nonlocal last_charset_flag # type: ignore[misc]
|
|
591
|
-
nonlocal last_attributes # type: ignore[misc]
|
|
592
|
-
nonlocal first # type: ignore[misc]
|
|
590
|
+
nonlocal last_charset_flag, last_attributes, first # type: ignore[misc]
|
|
593
591
|
|
|
594
592
|
if not isinstance(run, bytes): # canvases render with bytes
|
|
595
593
|
raise TypeError(run)
|
|
@@ -293,7 +293,7 @@ class Signals:
|
|
|
293
293
|
"""
|
|
294
294
|
result = False
|
|
295
295
|
handlers = getattr(obj, self._signal_attr, {}).get(name, [])
|
|
296
|
-
for _key, callback, user_arg, (weak_args, user_args) in handlers:
|
|
296
|
+
for _key, callback, user_arg, (weak_args, user_args) in handlers.copy():
|
|
297
297
|
result |= self._call_callback(callback, user_arg, weak_args, user_args, args)
|
|
298
298
|
return result
|
|
299
299
|
|
|
@@ -52,7 +52,7 @@ def split_repr(self: Widget) -> str:
|
|
|
52
52
|
>>> Bar()
|
|
53
53
|
<Bar words here too attrs='appear too' barttr=42>
|
|
54
54
|
"""
|
|
55
|
-
alist = sorted((
|
|
55
|
+
alist = sorted((k, normalize_repr(v)) for k, v in self._repr_attrs().items())
|
|
56
56
|
|
|
57
57
|
words = self._repr_words()
|
|
58
58
|
if not words and not alist:
|
|
@@ -429,17 +429,22 @@ def rle_product(
|
|
|
429
429
|
a2, r2 = rle2[0]
|
|
430
430
|
|
|
431
431
|
result: list[tuple[tuple[Hashable, Hashable], int]] = []
|
|
432
|
-
while
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
r1
|
|
436
|
-
if r1 == 0 and i1 < len(rle1):
|
|
432
|
+
while True:
|
|
433
|
+
# Skip zero-length runs (e.g. produced by empty markup segments): they carry no
|
|
434
|
+
# columns but must not stop the merge early, otherwise following runs are dropped.
|
|
435
|
+
while r1 == 0 and i1 < len(rle1):
|
|
437
436
|
a1, r1 = rle1[i1]
|
|
438
437
|
i1 += 1
|
|
439
|
-
r2
|
|
440
|
-
if r2 == 0 and i2 < len(rle2):
|
|
438
|
+
while r2 == 0 and i2 < len(rle2):
|
|
441
439
|
a2, r2 = rle2[i2]
|
|
442
440
|
i2 += 1
|
|
441
|
+
if r1 == 0 or r2 == 0:
|
|
442
|
+
break
|
|
443
|
+
|
|
444
|
+
r = min(r1, r2)
|
|
445
|
+
rle_append_modify(result, ((a1, a2), r)) # type: ignore[arg-type]
|
|
446
|
+
r1 -= r
|
|
447
|
+
r2 -= r
|
|
443
448
|
return result
|
|
444
449
|
|
|
445
450
|
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '4.0.
|
|
22
|
-
__version_tuple__ = version_tuple = (4, 0,
|
|
21
|
+
__version__ = version = '4.0.5'
|
|
22
|
+
__version_tuple__ = version_tuple = (4, 0, 5)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'gecd583268'
|
|
@@ -90,7 +90,7 @@ class AttrMap(delegate_to_widget_mixin("_original_widget"), WidgetDecoration[Wra
|
|
|
90
90
|
# FIXME: a dictionary that detects modifications would be better
|
|
91
91
|
return dict(self._attr_map)
|
|
92
92
|
|
|
93
|
-
def set_attr_map(self, attr_map: dict[Hashable, Hashable]
|
|
93
|
+
def set_attr_map(self, attr_map: dict[Hashable, Hashable]) -> None:
|
|
94
94
|
"""
|
|
95
95
|
Set the attribute mapping dictionary {from_attr: to_attr, ...}
|
|
96
96
|
|
|
@@ -152,7 +152,11 @@ class AttrMap(delegate_to_widget_mixin("_original_widget"), WidgetDecoration[Wra
|
|
|
152
152
|
|
|
153
153
|
focus_map = property(get_focus_map, set_focus_map)
|
|
154
154
|
|
|
155
|
-
def render(
|
|
155
|
+
def render(
|
|
156
|
+
self,
|
|
157
|
+
size: tuple[()] | tuple[int] | tuple[int, int],
|
|
158
|
+
focus: bool = False,
|
|
159
|
+
) -> CompositeCanvas:
|
|
156
160
|
"""
|
|
157
161
|
Render wrapped widget and apply attribute. Return canvas.
|
|
158
162
|
"""
|
|
@@ -4,7 +4,7 @@ import string
|
|
|
4
4
|
import typing
|
|
5
5
|
|
|
6
6
|
from urwid import text_layout
|
|
7
|
-
from urwid.canvas import CompositeCanvas
|
|
7
|
+
from urwid.canvas import CompositeCanvas, apply_text_layout
|
|
8
8
|
from urwid.command_map import Command
|
|
9
9
|
from urwid.split_repr import remove_defaults
|
|
10
10
|
from urwid.str_util import is_wide_char, move_next_char, move_prev_char
|
|
@@ -12,6 +12,7 @@ from urwid.util import decompose_tagmarkup
|
|
|
12
12
|
|
|
13
13
|
from .constants import Align, Sizing, WrapMode
|
|
14
14
|
from .text import Text, TextError
|
|
15
|
+
from .widget import WidgetWrap
|
|
15
16
|
|
|
16
17
|
if typing.TYPE_CHECKING:
|
|
17
18
|
from collections.abc import Hashable
|
|
@@ -27,7 +28,7 @@ class EditError(TextError):
|
|
|
27
28
|
pass
|
|
28
29
|
|
|
29
30
|
|
|
30
|
-
class Edit(Text):
|
|
31
|
+
class Edit(WidgetWrap[Text]):
|
|
31
32
|
"""
|
|
32
33
|
Text editing widget implements cursor movement, text insertion and
|
|
33
34
|
deletion. A caption may prefix the editing area. Uses text class
|
|
@@ -78,12 +79,10 @@ class Edit(Text):
|
|
|
78
79
|
mask: str | None = None,
|
|
79
80
|
) -> None:
|
|
80
81
|
"""
|
|
81
|
-
:param caption: markup for caption preceding edit_text, see
|
|
82
|
-
:class:`Text` for description of text markup.
|
|
82
|
+
:param caption: markup for caption preceding edit_text, see :class:`Text` for description of text markup.
|
|
83
83
|
:type caption: text markup
|
|
84
|
-
:param edit_text: initial text for editing, type (bytes or
|
|
85
|
-
|
|
86
|
-
:type edit_text: bytes or unicode
|
|
84
|
+
:param edit_text: initial text for editing, type (bytes or str) must match the text in the caption
|
|
85
|
+
:type edit_text: bytes or str
|
|
87
86
|
:param multiline: True: 'enter' inserts newline False: return it
|
|
88
87
|
:type multiline: bool
|
|
89
88
|
:param align: typically 'left', 'center' or 'right'
|
|
@@ -97,7 +96,7 @@ class Edit(Text):
|
|
|
97
96
|
:param layout: defaults to a shared :class:`StandardTextLayout` instance
|
|
98
97
|
:type layout: text layout instance
|
|
99
98
|
:param mask: hide text entered with this character, None:disable mask
|
|
100
|
-
:type mask: bytes or
|
|
99
|
+
:type mask: bytes or str
|
|
101
100
|
|
|
102
101
|
>>> Edit()
|
|
103
102
|
<Edit selectable flow widget '' edit_pos=0>
|
|
@@ -108,33 +107,60 @@ class Edit(Text):
|
|
|
108
107
|
>>> Edit("", "3.14", align="right")
|
|
109
108
|
<Edit selectable flow widget '3.14' align='right' edit_pos=4>
|
|
110
109
|
"""
|
|
111
|
-
|
|
112
|
-
super().__init__("", align, wrap, layout)
|
|
110
|
+
super().__init__(Text("", align, wrap, layout))
|
|
113
111
|
self.multiline = multiline
|
|
114
112
|
self.allow_tab = allow_tab
|
|
115
113
|
self._edit_pos = 0
|
|
116
|
-
self._caption, self._attrib = decompose_tagmarkup(caption) # type: ignore[arg-type]
|
|
114
|
+
self._caption, self._attrib = decompose_tagmarkup(caption) # type: ignore[arg-type]
|
|
117
115
|
self._edit_text = ""
|
|
118
116
|
self.highlight: tuple[int, int] | None = None
|
|
117
|
+
self._mask: str | None = None
|
|
118
|
+
self._shift_view_to_cursor = False
|
|
119
|
+
self.pref_col_maxcol: tuple[int | None, int | None] = (None, None)
|
|
119
120
|
self.set_edit_text(edit_text)
|
|
120
121
|
if edit_pos is None:
|
|
121
122
|
edit_pos = len(edit_text)
|
|
122
123
|
self.set_edit_pos(edit_pos)
|
|
123
124
|
self.set_mask(mask)
|
|
124
|
-
|
|
125
|
+
|
|
126
|
+
def selectable(self) -> bool:
|
|
127
|
+
"""Selectable mark.
|
|
128
|
+
|
|
129
|
+
Wrapped Text widget is not selectable while the edit widget has to be selectable.
|
|
130
|
+
"""
|
|
131
|
+
return True
|
|
132
|
+
|
|
133
|
+
def sizing(self) -> frozenset[Sizing]:
|
|
134
|
+
"""Sizing information.
|
|
135
|
+
|
|
136
|
+
Edit widget sizing is narrower than Text: we cannot predict the final input length.
|
|
137
|
+
"""
|
|
138
|
+
return self._sizing
|
|
125
139
|
|
|
126
140
|
def _repr_words(self) -> list[str]:
|
|
127
|
-
return
|
|
128
|
-
super()._repr_words()
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
141
|
+
return [
|
|
142
|
+
*super()._repr_words(),
|
|
143
|
+
repr(self._edit_text),
|
|
144
|
+
*([f"caption={self._caption!r}"] if self._caption else []),
|
|
145
|
+
*(["multiline"] if self.multiline is True else []),
|
|
146
|
+
]
|
|
133
147
|
|
|
134
148
|
def _repr_attrs(self) -> dict[str, typing.Any]:
|
|
135
|
-
attrs = {
|
|
149
|
+
attrs = {
|
|
150
|
+
**super()._repr_attrs(),
|
|
151
|
+
"align": self._w.align,
|
|
152
|
+
"wrap": self._w.wrap,
|
|
153
|
+
"edit_pos": self._edit_pos,
|
|
154
|
+
}
|
|
136
155
|
return remove_defaults(attrs, Edit.__init__)
|
|
137
156
|
|
|
157
|
+
def _sync_wrapped(self) -> None:
|
|
158
|
+
"""Synchronize the wrapped Text widget with current caption+edit_text state."""
|
|
159
|
+
text, attrib = self.get_text()
|
|
160
|
+
self._w._text = text # pylint: disable=protected-access
|
|
161
|
+
self._w._attrib = attrib # pylint: disable=protected-access
|
|
162
|
+
self._w._invalidate()
|
|
163
|
+
|
|
138
164
|
def get_text(self) -> tuple[str | bytes, list[tuple[Hashable, int]]]:
|
|
139
165
|
"""
|
|
140
166
|
Returns ``(text, display attributes)``. See :meth:`Text.get_text`
|
|
@@ -151,27 +177,59 @@ class Edit(Text):
|
|
|
151
177
|
"""
|
|
152
178
|
|
|
153
179
|
if self._mask is None:
|
|
154
|
-
return self._caption + self._edit_text, self._attrib
|
|
180
|
+
return self._caption + self._edit_text, self._attrib # type: ignore[operator] # type normalised
|
|
155
181
|
|
|
156
|
-
return self._caption + (self._mask * len(self._edit_text)), self._attrib
|
|
182
|
+
return self._caption + (self._mask * len(self._edit_text)), self._attrib # type: ignore[operator]
|
|
157
183
|
|
|
158
|
-
|
|
184
|
+
@property
|
|
185
|
+
def text(self) -> str | bytes:
|
|
159
186
|
"""
|
|
160
|
-
|
|
187
|
+
Read-only property returning the complete bytes/unicode content
|
|
188
|
+
of this widget (caption plus edit_text, possibly masked).
|
|
189
|
+
"""
|
|
190
|
+
return self.get_text()[0]
|
|
161
191
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
192
|
+
@property
|
|
193
|
+
def attrib(self) -> list[tuple[Hashable, int]]:
|
|
194
|
+
"""
|
|
195
|
+
Read-only property returning the run-length encoded display
|
|
196
|
+
attributes of this widget.
|
|
165
197
|
"""
|
|
166
|
-
|
|
167
|
-
# clean this up
|
|
198
|
+
return self.get_text()[1]
|
|
168
199
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
200
|
+
def set_align_mode(self, mode: Literal["left", "center", "right"] | Align) -> None:
|
|
201
|
+
"""
|
|
202
|
+
Set text alignment mode. See :meth:`Text.set_align_mode` for details.
|
|
203
|
+
"""
|
|
204
|
+
self._w.set_align_mode(mode)
|
|
205
|
+
self._invalidate()
|
|
173
206
|
|
|
174
|
-
|
|
207
|
+
def set_wrap_mode(self, mode: Literal["space", "any", "clip", "ellipsis"] | WrapMode) -> None:
|
|
208
|
+
"""
|
|
209
|
+
Set text wrapping mode. See :meth:`Text.set_wrap_mode` for details.
|
|
210
|
+
"""
|
|
211
|
+
self._w.set_wrap_mode(mode)
|
|
212
|
+
self._invalidate()
|
|
213
|
+
|
|
214
|
+
def set_layout(
|
|
215
|
+
self,
|
|
216
|
+
align: Literal["left", "center", "right"] | Align,
|
|
217
|
+
wrap: Literal["space", "any", "clip", "ellipsis"] | WrapMode,
|
|
218
|
+
layout: text_layout.TextLayout | None = None,
|
|
219
|
+
) -> None:
|
|
220
|
+
"""
|
|
221
|
+
Set the text layout object, alignment and wrapping modes at the same time.
|
|
222
|
+
See :meth:`Text.set_layout` for details.
|
|
223
|
+
"""
|
|
224
|
+
self._w.set_layout(align, wrap, layout)
|
|
225
|
+
self._invalidate()
|
|
226
|
+
|
|
227
|
+
align = property(lambda self: self._w.align, set_align_mode)
|
|
228
|
+
wrap = property(lambda self: self._w.wrap, set_wrap_mode)
|
|
229
|
+
|
|
230
|
+
@property
|
|
231
|
+
def layout(self) -> text_layout.TextLayout:
|
|
232
|
+
return self._w.layout
|
|
175
233
|
|
|
176
234
|
def get_pref_col(self, size: tuple[int]) -> int:
|
|
177
235
|
"""
|
|
@@ -209,7 +267,7 @@ class Edit(Text):
|
|
|
209
267
|
if then_maxcol != maxcol:
|
|
210
268
|
return self.get_cursor_coords((maxcol,))[0]
|
|
211
269
|
|
|
212
|
-
return pref_col
|
|
270
|
+
return typing.cast("int", pref_col)
|
|
213
271
|
|
|
214
272
|
def set_caption(self, caption: str | tuple[Hashable, str] | list[str | tuple[Hashable, str]]) -> None:
|
|
215
273
|
"""
|
|
@@ -231,11 +289,12 @@ class Edit(Text):
|
|
|
231
289
|
Traceback (most recent call last):
|
|
232
290
|
AttributeError: can't set attribute
|
|
233
291
|
"""
|
|
234
|
-
self._caption, self._attrib = decompose_tagmarkup(caption)
|
|
292
|
+
self._caption, self._attrib = decompose_tagmarkup(caption) # type: ignore[arg-type]
|
|
293
|
+
self._sync_wrapped()
|
|
235
294
|
self._invalidate()
|
|
236
295
|
|
|
237
296
|
@property
|
|
238
|
-
def caption(self) -> str:
|
|
297
|
+
def caption(self) -> str | bytes:
|
|
239
298
|
"""
|
|
240
299
|
Read-only property returning the caption for this widget.
|
|
241
300
|
"""
|
|
@@ -285,6 +344,7 @@ class Edit(Text):
|
|
|
285
344
|
"""
|
|
286
345
|
|
|
287
346
|
self._mask = mask
|
|
347
|
+
self._sync_wrapped()
|
|
288
348
|
self._invalidate()
|
|
289
349
|
|
|
290
350
|
def set_edit_text(self, text: str) -> None:
|
|
@@ -310,6 +370,7 @@ class Edit(Text):
|
|
|
310
370
|
self._emit("change", text)
|
|
311
371
|
old_text = self._edit_text
|
|
312
372
|
self._edit_text = text
|
|
373
|
+
self._sync_wrapped()
|
|
313
374
|
self.edit_pos = min(self.edit_pos, len(text))
|
|
314
375
|
|
|
315
376
|
self._emit("postchange", old_text)
|
|
@@ -367,8 +428,8 @@ class Edit(Text):
|
|
|
367
428
|
if tu == cu:
|
|
368
429
|
return text
|
|
369
430
|
if tu:
|
|
370
|
-
return text.encode("ascii") # follow python2's implicit conversion
|
|
371
|
-
return text.decode("ascii")
|
|
431
|
+
return text.encode("ascii") # type: ignore[union-attr] # follow python2's implicit conversion
|
|
432
|
+
return text.decode("ascii") # type: ignore[union-attr]
|
|
372
433
|
|
|
373
434
|
def insert_text_result(self, text: str) -> tuple[str | bytes, int]:
|
|
374
435
|
"""
|
|
@@ -401,7 +462,7 @@ class Edit(Text):
|
|
|
401
462
|
|
|
402
463
|
def keypress(
|
|
403
464
|
self,
|
|
404
|
-
size: tuple[int],
|
|
465
|
+
size: tuple[int],
|
|
405
466
|
key: str,
|
|
406
467
|
) -> str | None:
|
|
407
468
|
"""
|
|
@@ -460,9 +521,6 @@ class Edit(Text):
|
|
|
460
521
|
if pref_col is None:
|
|
461
522
|
raise ValueError(pref_col)
|
|
462
523
|
|
|
463
|
-
# if pref_col is None:
|
|
464
|
-
# pref_col = x
|
|
465
|
-
|
|
466
524
|
if self._command_map[key] == Command.UP:
|
|
467
525
|
y -= 1
|
|
468
526
|
else:
|
|
@@ -546,7 +604,7 @@ class Edit(Text):
|
|
|
546
604
|
|
|
547
605
|
def mouse_event(
|
|
548
606
|
self,
|
|
549
|
-
size: tuple[int],
|
|
607
|
+
size: tuple[int],
|
|
550
608
|
event: str,
|
|
551
609
|
button: int,
|
|
552
610
|
col: int,
|
|
@@ -581,9 +639,9 @@ class Edit(Text):
|
|
|
581
639
|
self.highlight = None
|
|
582
640
|
return True
|
|
583
641
|
|
|
584
|
-
def render(
|
|
642
|
+
def render(
|
|
585
643
|
self,
|
|
586
|
-
size: tuple[int],
|
|
644
|
+
size: tuple[int],
|
|
587
645
|
focus: bool = False,
|
|
588
646
|
) -> TextCanvas | CompositeCanvas:
|
|
589
647
|
"""
|
|
@@ -599,7 +657,11 @@ class Edit(Text):
|
|
|
599
657
|
"""
|
|
600
658
|
self._shift_view_to_cursor = bool(focus)
|
|
601
659
|
|
|
602
|
-
|
|
660
|
+
(maxcol,) = size
|
|
661
|
+
text, attr = self.get_text()
|
|
662
|
+
trans = self.get_line_translation(maxcol, (text, attr))
|
|
663
|
+
canv: TextCanvas | CompositeCanvas = apply_text_layout(text, attr, trans, maxcol)
|
|
664
|
+
|
|
603
665
|
if focus:
|
|
604
666
|
canv = CompositeCanvas(canv)
|
|
605
667
|
canv.cursor = self.get_cursor_coords(size)
|
|
@@ -615,7 +677,7 @@ class Edit(Text):
|
|
|
615
677
|
maxcol: int,
|
|
616
678
|
ta: tuple[str | bytes, list[tuple[Hashable, int]]] | None = None,
|
|
617
679
|
) -> list[list[tuple[int, int, int | bytes] | tuple[int, int | None]]]:
|
|
618
|
-
trans =
|
|
680
|
+
trans = self._w.get_line_translation(maxcol, ta)
|
|
619
681
|
if not self._shift_view_to_cursor:
|
|
620
682
|
return trans
|
|
621
683
|
|
|
@@ -685,7 +747,7 @@ class IntEdit(Edit):
|
|
|
685
747
|
|
|
686
748
|
def keypress(
|
|
687
749
|
self,
|
|
688
|
-
size: tuple[int],
|
|
750
|
+
size: tuple[int],
|
|
689
751
|
key: str,
|
|
690
752
|
) -> str | None:
|
|
691
753
|
"""
|