wcwidth 0.9.2__tar.gz → 0.9.3__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 (188) hide show
  1. {wcwidth-0.9.2 → wcwidth-0.9.3}/PKG-INFO +1 -1
  2. {wcwidth-0.9.2 → wcwidth-0.9.3}/docs/history.rst +13 -2
  3. {wcwidth-0.9.2 → wcwidth-0.9.3}/docs/libwcwidth.rst +8 -0
  4. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/Makefile +2 -2
  5. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/include/wcwidth/utf8.h +17 -5
  6. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/include/wcwidth/wcwidth_config.h +2 -2
  7. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/src/align.c +13 -3
  8. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/src/utf8.c +22 -9
  9. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/tests/test_align.c +36 -0
  10. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/tests/test_utf8.c +90 -0
  11. {wcwidth-0.9.2 → wcwidth-0.9.3}/pyproject.toml +1 -1
  12. {wcwidth-0.9.2 → wcwidth-0.9.3}/requirements-wheels.txt +6 -8
  13. {wcwidth-0.9.2 → wcwidth-0.9.3}/tests/test_hyperlink.py +6 -0
  14. {wcwidth-0.9.2 → wcwidth-0.9.3}/tests/test_textwrap.py +11 -0
  15. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/__init__.py +1 -1
  16. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/hyperlink.py +1 -1
  17. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/textwrap.py +13 -17
  18. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth.egg-info/PKG-INFO +1 -1
  19. {wcwidth-0.9.2 → wcwidth-0.9.3}/.gitignore +0 -0
  20. {wcwidth-0.9.2 → wcwidth-0.9.3}/.pylintrc +0 -0
  21. {wcwidth-0.9.2 → wcwidth-0.9.3}/LICENSE +0 -0
  22. {wcwidth-0.9.2 → wcwidth-0.9.3}/MANIFEST.in +0 -0
  23. {wcwidth-0.9.2 → wcwidth-0.9.3}/README.rst +0 -0
  24. {wcwidth-0.9.2 → wcwidth-0.9.3}/bin/check-release.py +0 -0
  25. {wcwidth-0.9.2 → wcwidth-0.9.3}/bin/update-docs.py +0 -0
  26. {wcwidth-0.9.2 → wcwidth-0.9.3}/bin/update-tables.py +0 -0
  27. {wcwidth-0.9.2 → wcwidth-0.9.3}/bin/verify-table-integrity.py +0 -0
  28. {wcwidth-0.9.2 → wcwidth-0.9.3}/bin/wcwidth-browser.py +0 -0
  29. {wcwidth-0.9.2 → wcwidth-0.9.3}/bin/wcwidth-libc-comparator.py +0 -0
  30. {wcwidth-0.9.2 → wcwidth-0.9.3}/code_templates/api_c.rst.j2 +0 -0
  31. {wcwidth-0.9.2 → wcwidth-0.9.3}/code_templates/c_gcb_class.c.j2 +0 -0
  32. {wcwidth-0.9.2 → wcwidth-0.9.3}/code_templates/c_table.c.j2 +0 -0
  33. {wcwidth-0.9.2 → wcwidth-0.9.3}/code_templates/c_term_programs.c.j2 +0 -0
  34. {wcwidth-0.9.2 → wcwidth-0.9.3}/code_templates/c_terminal_overrides.c.j2 +0 -0
  35. {wcwidth-0.9.2 → wcwidth-0.9.3}/code_templates/grapheme_override_per_terminal.py.j2 +0 -0
  36. {wcwidth-0.9.2 → wcwidth-0.9.3}/code_templates/grapheme_override_table.py.j2 +0 -0
  37. {wcwidth-0.9.2 → wcwidth-0.9.3}/code_templates/grapheme_registry.py.j2 +0 -0
  38. {wcwidth-0.9.2 → wcwidth-0.9.3}/code_templates/grapheme_table.c.j2 +0 -0
  39. {wcwidth-0.9.2 → wcwidth-0.9.3}/code_templates/grapheme_table.py.j2 +0 -0
  40. {wcwidth-0.9.2 → wcwidth-0.9.3}/code_templates/python_table.py.j2 +0 -0
  41. {wcwidth-0.9.2 → wcwidth-0.9.3}/code_templates/table_overrides.py.j2 +0 -0
  42. {wcwidth-0.9.2 → wcwidth-0.9.3}/code_templates/tables.h.j2 +0 -0
  43. {wcwidth-0.9.2 → wcwidth-0.9.3}/code_templates/term_programs.py.j2 +0 -0
  44. {wcwidth-0.9.2 → wcwidth-0.9.3}/code_templates/unicode_version.rst.j2 +0 -0
  45. {wcwidth-0.9.2 → wcwidth-0.9.3}/code_templates/unicode_versions.py.j2 +0 -0
  46. {wcwidth-0.9.2 → wcwidth-0.9.3}/code_templates/wcwidth_config.h.j2 +0 -0
  47. {wcwidth-0.9.2 → wcwidth-0.9.3}/docs/api.rst +0 -0
  48. {wcwidth-0.9.2 → wcwidth-0.9.3}/docs/conf.py +0 -0
  49. {wcwidth-0.9.2 → wcwidth-0.9.3}/docs/developing.rst +0 -0
  50. {wcwidth-0.9.2 → wcwidth-0.9.3}/docs/index.rst +0 -0
  51. {wcwidth-0.9.2 → wcwidth-0.9.3}/docs/intro.rst +0 -0
  52. {wcwidth-0.9.2 → wcwidth-0.9.3}/docs/related.rst +0 -0
  53. {wcwidth-0.9.2 → wcwidth-0.9.3}/docs/specs.rst +0 -0
  54. {wcwidth-0.9.2 → wcwidth-0.9.3}/docs/unicode_version.rst +0 -0
  55. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/.clang-format +0 -0
  56. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/CMakeLists.txt +0 -0
  57. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/LICENSE +0 -0
  58. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/README.rst +0 -0
  59. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/cmake/libwcwidth-config.cmake.in +0 -0
  60. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/examples/align.c +0 -0
  61. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/examples/textwrap.c +0 -0
  62. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/examples/width.c +0 -0
  63. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/include/wcwidth/align.h +0 -0
  64. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/include/wcwidth/clip.h +0 -0
  65. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/include/wcwidth/escape.h +0 -0
  66. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/include/wcwidth/grapheme.h +0 -0
  67. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/include/wcwidth/sgr.h +0 -0
  68. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/include/wcwidth/table_types.h +0 -0
  69. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/include/wcwidth/tables.h +0 -0
  70. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/include/wcwidth/terminal_override.h +0 -0
  71. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/include/wcwidth/text_sizing.h +0 -0
  72. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/include/wcwidth/textwrap.h +0 -0
  73. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/include/wcwidth/unicode.h +0 -0
  74. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/include/wcwidth/wcwidth.h +0 -0
  75. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/include/wcwidth/width.h +0 -0
  76. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/src/bisearch.c +0 -0
  77. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/src/clip.c +0 -0
  78. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/src/escape.c +0 -0
  79. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/src/grapheme.c +0 -0
  80. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/src/sgr.c +0 -0
  81. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/src/tables/table_ambiguous.c +0 -0
  82. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/src/tables/table_gcb_class.c +0 -0
  83. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/src/tables/table_grapheme.c +0 -0
  84. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/src/tables/table_mc.c +0 -0
  85. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/src/tables/table_term_programs.c +0 -0
  86. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/src/tables/table_terminal_overrides.c +0 -0
  87. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/src/tables/table_vs15.c +0 -0
  88. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/src/tables/table_vs16.c +0 -0
  89. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/src/tables/table_wide.c +0 -0
  90. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/src/tables/table_zero.c +0 -0
  91. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/src/terminal_override.c +0 -0
  92. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/src/text_sizing.c +0 -0
  93. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/src/textwrap.c +0 -0
  94. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/src/wcstwidth.c +0 -0
  95. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/src/wcswidth.c +0 -0
  96. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/src/wcwidth.c +0 -0
  97. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/src/width.c +0 -0
  98. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/tests/test_clip.c +0 -0
  99. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/tests/test_common.h +0 -0
  100. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/tests/test_escape.c +0 -0
  101. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/tests/test_gcb_class.c +0 -0
  102. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/tests/test_grapheme.c +0 -0
  103. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/tests/test_sgr.c +0 -0
  104. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/tests/test_text_sizing.c +0 -0
  105. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/tests/test_textwrap.c +0 -0
  106. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/tests/test_wcswidth.c +0 -0
  107. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/tests/test_wcwidth.c +0 -0
  108. {wcwidth-0.9.2 → wcwidth-0.9.3}/libwcwidth/tests/test_width.c +0 -0
  109. {wcwidth-0.9.2 → wcwidth-0.9.3}/requirements-develop.txt +0 -0
  110. {wcwidth-0.9.2 → wcwidth-0.9.3}/requirements-docs.in +0 -0
  111. {wcwidth-0.9.2 → wcwidth-0.9.3}/requirements-tests39.in +0 -0
  112. {wcwidth-0.9.2 → wcwidth-0.9.3}/requirements-tests39.txt +0 -0
  113. {wcwidth-0.9.2 → wcwidth-0.9.3}/requirements-update.in +0 -0
  114. {wcwidth-0.9.2 → wcwidth-0.9.3}/requirements-update.txt +0 -0
  115. {wcwidth-0.9.2 → wcwidth-0.9.3}/requirements-wheels.in +0 -0
  116. {wcwidth-0.9.2 → wcwidth-0.9.3}/setup.cfg +0 -0
  117. {wcwidth-0.9.2 → wcwidth-0.9.3}/setup.py +0 -0
  118. {wcwidth-0.9.2 → wcwidth-0.9.3}/tests/__init__.py +0 -0
  119. {wcwidth-0.9.2 → wcwidth-0.9.3}/tests/conftest.py +0 -0
  120. {wcwidth-0.9.2 → wcwidth-0.9.3}/tests/test_ambiguous.py +0 -0
  121. {wcwidth-0.9.2 → wcwidth-0.9.3}/tests/test_benchmarks.py +0 -0
  122. {wcwidth-0.9.2 → wcwidth-0.9.3}/tests/test_check_release.py +0 -0
  123. {wcwidth-0.9.2 → wcwidth-0.9.3}/tests/test_clip.py +0 -0
  124. {wcwidth-0.9.2 → wcwidth-0.9.3}/tests/test_clip_cjk_emoji.py +0 -0
  125. {wcwidth-0.9.2 → wcwidth-0.9.3}/tests/test_clip_overtyping.py +0 -0
  126. {wcwidth-0.9.2 → wcwidth-0.9.3}/tests/test_core.py +0 -0
  127. {wcwidth-0.9.2 → wcwidth-0.9.3}/tests/test_emojis.py +0 -0
  128. {wcwidth-0.9.2 → wcwidth-0.9.3}/tests/test_fastcall_args.py +0 -0
  129. {wcwidth-0.9.2 → wcwidth-0.9.3}/tests/test_grapheme.py +0 -0
  130. {wcwidth-0.9.2 → wcwidth-0.9.3}/tests/test_justify.py +0 -0
  131. {wcwidth-0.9.2 → wcwidth-0.9.3}/tests/test_setup.py +0 -0
  132. {wcwidth-0.9.2 → wcwidth-0.9.3}/tests/test_sgr_state.py +0 -0
  133. {wcwidth-0.9.2 → wcwidth-0.9.3}/tests/test_term_overrides.py +0 -0
  134. {wcwidth-0.9.2 → wcwidth-0.9.3}/tests/test_text_sizing.py +0 -0
  135. {wcwidth-0.9.2 → wcwidth-0.9.3}/tests/test_ucslevel.py +0 -0
  136. {wcwidth-0.9.2 → wcwidth-0.9.3}/tests/test_width.py +0 -0
  137. {wcwidth-0.9.2 → wcwidth-0.9.3}/tox.ini +0 -0
  138. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/_clip.py +0 -0
  139. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/_constants.py +0 -0
  140. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/_wcswidth.py +0 -0
  141. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/_wcwidth.py +0 -0
  142. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/_wcwidth_c.c +0 -0
  143. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/_wcwidth_c.pyi +0 -0
  144. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/_width.py +0 -0
  145. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/align.py +0 -0
  146. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/bisearch.py +0 -0
  147. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/control_codes.py +0 -0
  148. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/escape_sequences.py +0 -0
  149. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/grapheme.py +0 -0
  150. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/py.typed +0 -0
  151. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/sgr_state.py +0 -0
  152. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/table_ambiguous.py +0 -0
  153. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/table_grapheme.py +0 -0
  154. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/table_grapheme_overrides/__init__.py +0 -0
  155. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/table_grapheme_overrides/_known_058c7585.py +0 -0
  156. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/table_grapheme_overrides/_known_2148ff29.py +0 -0
  157. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/table_grapheme_overrides/_known_244dc88b.py +0 -0
  158. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/table_grapheme_overrides/_known_336a71a1.py +0 -0
  159. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/table_grapheme_overrides/_known_33ac73f2.py +0 -0
  160. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/table_grapheme_overrides/_known_3a1895d5.py +0 -0
  161. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/table_grapheme_overrides/_known_59180a0d.py +0 -0
  162. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/table_grapheme_overrides/_known_65c6beb5.py +0 -0
  163. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/table_grapheme_overrides/_known_78f828e8.py +0 -0
  164. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/table_grapheme_overrides/_known_8c2ae14d.py +0 -0
  165. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/table_grapheme_overrides/_known_900356bf.py +0 -0
  166. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/table_grapheme_overrides/_known_97904ece.py +0 -0
  167. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/table_grapheme_overrides/_known_a5880eeb.py +0 -0
  168. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/table_grapheme_overrides/_known_b6d33cf4.py +0 -0
  169. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/table_grapheme_overrides/_known_c57c295b.py +0 -0
  170. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/table_grapheme_overrides/_known_cf02297d.py +0 -0
  171. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/table_grapheme_overrides/_known_e18ac6a7.py +0 -0
  172. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/table_grapheme_overrides/_known_e2b75ee3.py +0 -0
  173. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/table_grapheme_overrides/_known_fb5fd79b.py +0 -0
  174. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/table_grapheme_overrides/_known_fdc99132.py +0 -0
  175. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/table_grapheme_overrides/_registry.py +0 -0
  176. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/table_mc.py +0 -0
  177. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/table_overrides.py +0 -0
  178. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/table_term_programs.py +0 -0
  179. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/table_vs15.py +0 -0
  180. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/table_vs16.py +0 -0
  181. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/table_wide.py +0 -0
  182. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/table_zero.py +0 -0
  183. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/text_sizing.py +0 -0
  184. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/unicode_versions.py +0 -0
  185. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth/wcwidth.py +0 -0
  186. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth.egg-info/SOURCES.txt +0 -0
  187. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth.egg-info/dependency_links.txt +0 -0
  188. {wcwidth-0.9.2 → wcwidth-0.9.3}/wcwidth.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wcwidth
3
- Version: 0.9.2
3
+ Version: 0.9.3
4
4
  Summary: Measures the displayed width of unicode strings in a terminal
5
5
  Author-email: Jeff Quast <contact@jeffquast.com>
6
6
  Project-URL: Homepage, https://github.com/jquast/wcwidth
@@ -2,6 +2,12 @@
2
2
  History
3
3
  =======
4
4
 
5
+ 0.9.3 *unreleased*
6
+ * **Bugfix** libwcwidth no longer has undefined behavior on NULL or empty input in the UTF-8 and
7
+ alignment functions, `PR #293`_, `PR #294`_.
8
+ * **Changed** `HyperlinkParams.parse()`_ now rejects trailing text, `PR #296`_.
9
+ * **Bugfix** `wrap()`_ should keep a link's own OSC 8 params across a line break, `PR #299`_.
10
+
5
11
  0.9.2 *2026-10-04*
6
12
  * **Bugfix** `wrap()`_ should omit an empty line as output, like `textwrap.wrap()`_ `PR #291`_.
7
13
  * **Bugfix** `clip()`_ when using non-default argument, ``tabsize=0``, `PR #288`_.
@@ -11,7 +17,7 @@ History
11
17
 
12
18
  0.9.1 *2026-09-23*
13
19
  * **Bugfix** `width()`_ in Python and libwcwidth mis-measured CSI sequences over 64 bytes, a 0.9.0
14
- release regression! `PR #271`_.
20
+ release regression! `PR #272`_.
15
21
  * **Performance** improvement of ~15% for many Python API functions when using default arguments
16
22
  like ``ambiguous_width`` and ``term_program``. `PR #273`_
17
23
 
@@ -312,12 +318,16 @@ https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c::
312
318
  .. _`PR #253`: https://github.com/jquast/wcwidth/pull/253
313
319
  .. _`PR #259`: https://github.com/jquast/wcwidth/pull/259
314
320
  .. _`PR #262`: https://github.com/jquast/wcwidth/pull/262
315
- .. _`PR #271`: https://github.com/jquast/wcwidth/pull/271
321
+ .. _`PR #272`: https://github.com/jquast/wcwidth/pull/272
316
322
  .. _`PR #273`: https://github.com/jquast/wcwidth/pull/273
317
323
  .. _`PR #281`: https://github.com/jquast/wcwidth/pull/281
318
324
  .. _`PR #284`: https://github.com/jquast/wcwidth/pull/284
319
325
  .. _`PR #288`: https://github.com/jquast/wcwidth/pull/288
320
326
  .. _`PR #291`: https://github.com/jquast/wcwidth/pull/291
327
+ .. _`PR #293`: https://github.com/jquast/wcwidth/pull/293
328
+ .. _`PR #294`: https://github.com/jquast/wcwidth/pull/294
329
+ .. _`PR #296`: https://github.com/jquast/wcwidth/pull/296
330
+ .. _`PR #299`: https://github.com/jquast/wcwidth/pull/299
321
331
  .. _`Issue #101`: https://github.com/jquast/wcwidth/issues/101
322
332
  .. _`Issue #155`: https://github.com/jquast/wcwidth/issues/155
323
333
  .. _`Issue #211`: https://github.com/jquast/wcwidth/issues/211
@@ -343,6 +353,7 @@ https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c::
343
353
  .. _`iter_sequences()`: https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.iter_sequences
344
354
  .. _`list_versions()`: https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.list_versions
345
355
  .. _`list_term_programs()`: https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.list_term_programs
356
+ .. _`HyperlinkParams.parse()`: https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.HyperlinkParams
346
357
  .. _`parity padding`: https://jazcap53.github.io/pythons-eccentric-strcenter.html
347
358
  .. _`kitty text sizing protocol`: https://sw.kovidgoyal.net/kitty/text-sizing-protocol/
348
359
  .. _Corrections: https://wcwidth.readthedocs.io/en/latest/intro.html#corrections
@@ -147,6 +147,14 @@ Every string function reads exactly the length you pass:
147
147
  Pass ``strlen(text)`` when the text is a C string. A NUL counts as an ordinary zero-width character
148
148
  in any position. Transform functions copy it through and report the full length in ``*out_len``.
149
149
 
150
+ Pointer contract
151
+ ~~~~~~~~~~~~~~~~
152
+
153
+ Input pointers and the ``count`` / ``out_len`` out-length pointers must not be NULL.
154
+
155
+ ``stack`` accepts NULL to select the heap path. ``wcwidth_utf8_decode_single()`` accepts
156
+ NULL as ``cp_out``, reporting the length of the next character.
157
+
150
158
  Alternate encodings
151
159
  ~~~~~~~~~~~~~~~~~~~
152
160
 
@@ -13,8 +13,8 @@ CFLAGS ?= -std=c11 -Wall -Wextra -O2 -g
13
13
  INCLUDE = -Iinclude
14
14
  BUILD = build
15
15
 
16
- ASAN_CFLAGS = -std=c11 -Wall -Wextra -O1 -g -fsanitize=address,undefined -fno-omit-frame-pointer
17
- UBSAN_CFLAGS = -std=c11 -Wall -Wextra -O2 -g -fsanitize=undefined -fno-omit-frame-pointer
16
+ ASAN_CFLAGS = -std=c11 -Wall -Wextra -O1 -g -fsanitize=address,undefined -fno-sanitize-recover=all -fno-omit-frame-pointer
17
+ UBSAN_CFLAGS = -std=c11 -Wall -Wextra -O2 -g -fsanitize=undefined -fno-sanitize-recover=all -fno-omit-frame-pointer
18
18
 
19
19
  LIB_SRCS = $(wildcard src/*.c) $(wildcard src/tables/*.c)
20
20
  LIB_OBJS = $(patsubst %.c,$(BUILD)/%.o,$(LIB_SRCS))
@@ -17,6 +17,9 @@ extern "C" {
17
17
  * U+FFFD and the return value is the number of bytes to skip (1 for a bad
18
18
  * leading byte, the remaining length for a truncated sequence, or 0 when
19
19
  * *len* is 0).
20
+ *
21
+ * *s* must not be NULL. *cp_out* may be NULL to consume the sequence and
22
+ * query the length of the next character.
20
23
  */
21
24
  size_t wcwidth_utf8_decode_single(const char *s, size_t len, uint32_t *cp_out);
22
25
 
@@ -28,6 +31,11 @@ size_t wcwidth_utf8_decode_single(const char *s, size_t len, uint32_t *cp_out);
28
31
  * is undefined behavior. Sets *count* and returns NULL on allocation
29
32
  * failure.
30
33
  *
34
+ * *utf8* and *count* must not be NULL; *count* is required because the
35
+ * returned array carries no terminator. A *stack* of NULL forces the heap
36
+ * path. When *n* is 0 and *stack* is NULL, the result is a freeable array
37
+ * with *count* set to 0.
38
+ *
31
39
  * The result type allows a plain free() release, matching wcwidth_encode_u32()
32
40
  * below. Treat the contents as read-only.
33
41
  *
@@ -39,7 +47,8 @@ uint32_t *wcwidth_decode_u32(const char *utf8, size_t n, uint32_t *stack, size_t
39
47
 
40
48
  /*
41
49
  * Decode *utf8* into a malloc'd codepoint array (one free()). Returns the
42
- * array and sets *count*, or NULL on allocation failure.
50
+ * array and sets *count*, or NULL on allocation failure. An *n* of 0 returns
51
+ * a freeable array with *count* set to 0.
43
52
  *
44
53
  * Follows the length and NUL semantics of wcwidth_decode_u32().
45
54
  */
@@ -51,12 +60,15 @@ uint32_t *wcwidth_decode_u32_heap(const char *utf8, size_t n, size_t *count);
51
60
  * in *stack* (no allocation), otherwise a heap buffer is allocated. The
52
61
  * caller must free() the result only when it is not *stack*; freeing *stack*
53
62
  * is undefined behavior. Sets *out_len* and returns NULL on allocation
54
- * failure.
63
+ * failure. An *n* of 0 returns a freeable buffer with *out_len* set to 0.
64
+ *
65
+ * *codepoints* and *out_len* must not be NULL; *out_len* is required because
66
+ * the returned bytes carry no terminator. A *stack* of NULL forces the heap
67
+ * path.
55
68
  *
56
69
  * Encoding writes exactly *n* codepoints: a U+0000 becomes a NUL byte, so the
57
- * output can contain embedded NUL bytes. The result is not NUL-terminated.
58
- * Invalid codepoints (lone surrogates and values above U+10FFFF) are encoded
59
- * as U+FFFD.
70
+ * output can contain embedded NUL bytes. Invalid codepoints (lone surrogates
71
+ * and values above U+10FFFF) are encoded as U+FFFD.
60
72
  */
61
73
  char *wcwidth_encode_u32(const uint32_t *codepoints, size_t n, char *stack, size_t stack_cap,
62
74
  size_t *out_len);
@@ -18,8 +18,8 @@ extern "C" {
18
18
 
19
19
  #define WCWIDTH_VERSION_MAJOR 0
20
20
  #define WCWIDTH_VERSION_MINOR 9
21
- #define WCWIDTH_VERSION_PATCH 2
22
- #define WCWIDTH_VERSION "0.9.2"
21
+ #define WCWIDTH_VERSION_PATCH 3
22
+ #define WCWIDTH_VERSION "0.9.3"
23
23
 
24
24
  /* Unicode version the library tables were generated from. */
25
25
  #define WCWIDTH_UNICODE_VERSION "18.0.0"
@@ -87,6 +87,10 @@ fill_repeat(char *dst, const char *fillchar, size_t fillchar_len, size_t count)
87
87
  {
88
88
  size_t i;
89
89
 
90
+ if (fillchar_len == 0) {
91
+ return dst;
92
+ }
93
+
90
94
  for (i = 0; i < count; i++) {
91
95
  memcpy(dst, fillchar, fillchar_len);
92
96
  dst += fillchar_len;
@@ -133,7 +137,9 @@ ljust_impl(const char *text, size_t text_len, size_t dest_width, const char *fil
133
137
  return NULL;
134
138
  }
135
139
 
136
- memcpy(result, text, text_len);
140
+ if (text_len > 0) {
141
+ memcpy(result, text, text_len);
142
+ }
137
143
  fill_repeat(result + text_len, fillchar, fillchar_len, padding);
138
144
  result[total] = '\0';
139
145
 
@@ -184,7 +190,9 @@ rjust_impl(const char *text, size_t text_len, size_t dest_width, const char *fil
184
190
 
185
191
  fill_repeat(result, fillchar, fillchar_len, padding);
186
192
  /* add_fill_bytes() above already proved this product cannot wrap. */
187
- memcpy(result + padding * fillchar_len, text, text_len);
193
+ if (text_len > 0) {
194
+ memcpy(result + padding * fillchar_len, text, text_len);
195
+ }
188
196
  result[total] = '\0';
189
197
 
190
198
  if (out_len != NULL) {
@@ -246,7 +254,9 @@ center_impl(const char *text, size_t text_len, size_t dest_width, const char *fi
246
254
 
247
255
  {
248
256
  char *dst = fill_repeat(result, fillchar, fillchar_len, left_pad);
249
- memcpy(dst, text, text_len);
257
+ if (text_len > 0) {
258
+ memcpy(dst, text, text_len);
259
+ }
250
260
  fill_repeat(dst + text_len, fillchar, fillchar_len, right_pad);
251
261
  }
252
262
  result[total] = '\0';
@@ -16,6 +16,10 @@ wcwidth_utf8_decode_single(const char *s, size_t len, uint32_t *cp_out)
16
16
  size_t expected;
17
17
  size_t i;
18
18
 
19
+ if (cp_out == NULL) {
20
+ cp_out = &cp;
21
+ }
22
+
19
23
  if (len == 0) {
20
24
  *cp_out = 0xFFFD;
21
25
  return 0;
@@ -97,7 +101,7 @@ wcwidth_decode_u32(const char *utf8, size_t n, uint32_t *stack, size_t stack_cap
97
101
  {
98
102
  uint32_t *heap = NULL;
99
103
  uint32_t *out = stack;
100
- size_t cap = stack_cap;
104
+ size_t cap = (stack != NULL) ? stack_cap : 0;
101
105
  size_t used = 0;
102
106
  size_t pos = 0;
103
107
 
@@ -142,17 +146,18 @@ wcwidth_decode_u32(const char *utf8, size_t n, uint32_t *stack, size_t stack_cap
142
146
  pos += consumed;
143
147
  }
144
148
 
149
+ if (n == 0 && stack == NULL) {
150
+ *count = 0;
151
+ return (uint32_t *) calloc(1, sizeof(uint32_t)); /* freeable empty result */
152
+ }
153
+
145
154
  *count = used;
146
- return (heap != NULL) ? heap : stack;
155
+ return out;
147
156
  }
148
157
 
149
158
  uint32_t *
150
159
  wcwidth_decode_u32_heap(const char *utf8, size_t n, size_t *count)
151
160
  {
152
- if (n == 0) {
153
- *count = 0;
154
- return (uint32_t *) malloc(sizeof(uint32_t)); /* freeable empty result */
155
- }
156
161
  return wcwidth_decode_u32(utf8, n, NULL, 0, count);
157
162
  }
158
163
 
@@ -195,7 +200,7 @@ wcwidth_encode_u32(const uint32_t *codepoints, size_t n, char *stack, size_t sta
195
200
  {
196
201
  char *heap = NULL;
197
202
  char *out = stack;
198
- size_t cap = stack_cap;
203
+ size_t cap = (stack != NULL) ? stack_cap : 0;
199
204
  size_t used = 0;
200
205
  size_t i;
201
206
 
@@ -216,7 +221,10 @@ wcwidth_encode_u32(const uint32_t *codepoints, size_t n, char *stack, size_t sta
216
221
  *out_len = 0;
217
222
  return NULL;
218
223
  }
219
- memcpy(nd, stack, used);
224
+ if (used > 0) {
225
+ /* stack may be NULL when stack_cap is 0 */
226
+ memcpy(nd, stack, used);
227
+ }
220
228
  }
221
229
  else {
222
230
  nd = (char *) realloc(heap, new_cap);
@@ -234,6 +242,11 @@ wcwidth_encode_u32(const uint32_t *codepoints, size_t n, char *stack, size_t sta
234
242
  used += enc_len;
235
243
  }
236
244
 
245
+ if (n == 0 && stack == NULL) {
246
+ *out_len = 0;
247
+ return (char *) calloc(1, 1); /* freeable empty result */
248
+ }
249
+
237
250
  *out_len = used;
238
- return (heap != NULL) ? heap : stack;
251
+ return out;
239
252
  }
@@ -262,6 +262,41 @@ TEST(null_opts_defaults)
262
262
  free(u);
263
263
  }
264
264
 
265
+ TEST(align_null_and_empty_input)
266
+ {
267
+ wcwidth_align_opts_t opts = WCWIDTH_ALIGN_OPTS_DEFAULT;
268
+ size_t len = 1;
269
+ int error = WCWIDTH_ERROR_NONE;
270
+ char *s;
271
+
272
+ s = wcwidth_ljust_u8(NULL, 0, WCWIDTH_PARSE, &opts, &len, &error);
273
+ ASSERT_NOT_NULL(s);
274
+ ASSERT_EQ(0, len);
275
+ ASSERT_STREQ("", s);
276
+ free(s);
277
+
278
+ s = wcwidth_rjust_u8(NULL, 0, WCWIDTH_PARSE, &opts, &len, &error);
279
+ ASSERT_NOT_NULL(s);
280
+ ASSERT_EQ(0, len);
281
+ ASSERT_STREQ("", s);
282
+ free(s);
283
+
284
+ s = wcwidth_center_u8(NULL, 0, WCWIDTH_PARSE, &opts, &len, &error);
285
+ ASSERT_NOT_NULL(s);
286
+ ASSERT_EQ(0, len);
287
+ ASSERT_STREQ("", s);
288
+ free(s);
289
+
290
+ opts.dest_width = 5;
291
+ opts.fillchar = NULL;
292
+ opts.fillchar_len = 0;
293
+ s = wcwidth_ljust_u8("x", 1, WCWIDTH_PARSE, &opts, &len, &error);
294
+ ASSERT_NOT_NULL(s);
295
+ ASSERT_EQ(1, len);
296
+ ASSERT_STREQ("x", s);
297
+ free(s);
298
+ }
299
+
265
300
  int
266
301
  main(void)
267
302
  {
@@ -276,5 +311,6 @@ main(void)
276
311
  RUN_TEST(center_huge_dest_width);
277
312
  RUN_TEST(align_control_codes_strict);
278
313
  RUN_TEST(null_opts_defaults);
314
+ RUN_TEST(align_null_and_empty_input);
279
315
  return test_summary();
280
316
  }
@@ -74,6 +74,89 @@ TEST(decode_u32_heap_empty)
74
74
 
75
75
  ASSERT_NOT_NULL(out);
76
76
  ASSERT_EQ((int64_t) 0, (int64_t) count);
77
+ ASSERT_EQ((int64_t) 0, (int64_t) out[0]);
78
+ free(out);
79
+ }
80
+
81
+ TEST(decode_single_null_out)
82
+ {
83
+ ASSERT_EQ((int64_t) 3, (int64_t) wcwidth_utf8_decode_single("\xe4\xb8\xad", 3, NULL));
84
+ ASSERT_EQ((int64_t) 1, (int64_t) wcwidth_utf8_decode_single("abc", 3, NULL));
85
+ }
86
+
87
+ TEST(decode_u32_null_stack)
88
+ {
89
+ uint32_t *out;
90
+ size_t count = 0;
91
+
92
+ out = wcwidth_decode_u32("a", 1, NULL, 8, &count);
93
+ ASSERT_NOT_NULL(out);
94
+ ASSERT_EQ((int64_t) 1, (int64_t) count);
95
+ ASSERT_EQ((int64_t) 'a', (int64_t) out[0]);
96
+ free(out);
97
+ }
98
+
99
+ TEST(encode_u32_empty_heap)
100
+ {
101
+ const uint32_t cps[] = {0};
102
+ char *out;
103
+ size_t len = 1;
104
+
105
+ out = wcwidth_encode_u32(cps, 0, NULL, 0, &len);
106
+ ASSERT_NOT_NULL(out);
107
+ ASSERT_EQ((int64_t) 0, (int64_t) len);
108
+ ASSERT_EQ((int64_t) 0, (int64_t) out[0]);
109
+ free(out);
110
+ }
111
+
112
+ TEST(encode_u32_null_stack)
113
+ {
114
+ const uint32_t cps[] = {'a', 'b'};
115
+ char *out;
116
+ size_t len = 0;
117
+
118
+ out = wcwidth_encode_u32(cps, 2, NULL, 8, &len);
119
+ ASSERT_NOT_NULL(out);
120
+ ASSERT_EQ((int64_t) 2, (int64_t) len);
121
+ ASSERT_EQ(0, memcmp(out, "ab", 2));
122
+ free(out);
123
+ }
124
+
125
+ TEST(decode_u32_empty_stack)
126
+ {
127
+ uint32_t stack[4];
128
+ uint32_t *out;
129
+ size_t count = 1;
130
+
131
+ out = wcwidth_decode_u32("", 0, stack, 4, &count);
132
+ ASSERT_TRUE(out == stack);
133
+ ASSERT_EQ((int64_t) 0, (int64_t) count);
134
+ }
135
+
136
+ TEST(encode_u32_empty_stack)
137
+ {
138
+ const uint32_t cps[] = {0};
139
+ char stack[8];
140
+ char *out;
141
+ size_t len = 1;
142
+
143
+ out = wcwidth_encode_u32(cps, 0, stack, 8, &len);
144
+ ASSERT_TRUE(out == stack);
145
+ ASSERT_EQ((int64_t) 0, (int64_t) len);
146
+ }
147
+
148
+ TEST(decode_u32_stack_overflow)
149
+ {
150
+ uint32_t stack[2];
151
+ uint32_t *out;
152
+ size_t count = 0;
153
+
154
+ out = wcwidth_decode_u32("abcdef", 6, stack, 2, &count);
155
+ ASSERT_NOT_NULL(out);
156
+ ASSERT_TRUE(out != stack);
157
+ ASSERT_EQ((int64_t) 6, (int64_t) count);
158
+ ASSERT_EQ((int64_t) 'a', (int64_t) out[0]);
159
+ ASSERT_EQ((int64_t) 'f', (int64_t) out[5]);
77
160
  free(out);
78
161
  }
79
162
 
@@ -83,5 +166,12 @@ main(void)
83
166
  RUN_TEST(encode_roundtrip);
84
167
  RUN_TEST(decode_malformed);
85
168
  RUN_TEST(decode_u32_heap_empty);
169
+ RUN_TEST(decode_single_null_out);
170
+ RUN_TEST(decode_u32_null_stack);
171
+ RUN_TEST(encode_u32_empty_heap);
172
+ RUN_TEST(encode_u32_null_stack);
173
+ RUN_TEST(decode_u32_empty_stack);
174
+ RUN_TEST(encode_u32_empty_stack);
175
+ RUN_TEST(decode_u32_stack_overflow);
86
176
  return test_summary();
87
177
  }
@@ -7,7 +7,7 @@ requires = [ "setuptools>=61" ]
7
7
 
8
8
  [project]
9
9
  name = "wcwidth"
10
- version = "0.9.2" # stamped into __init__.py and wcwidth_config.h by bin/update-tables.py
10
+ version = "0.9.3" # stamped into __init__.py and wcwidth_config.h by bin/update-tables.py
11
11
  description = "Measures the displayed width of unicode strings in a terminal"
12
12
  readme = "README.rst"
13
13
  keywords = [
@@ -2,23 +2,21 @@
2
2
  # This file is autogenerated by pip-compile with Python 3.14
3
3
  # by the following command:
4
4
  #
5
- # pip-compile --allow-unsafe --no-emit-index-url --no-index --output-file=requirements-wheels.txt --strip-extras requirements-wheels.in
5
+ # pip-compile --allow-unsafe --no-emit-index-url --output-file=requirements-wheels.txt --strip-extras requirements-wheels.in
6
6
  #
7
- --trusted-host localhost
8
-
9
7
  bashlex==0.18
10
8
  # via cibuildwheel
11
- bracex==3.0.1
9
+ bracex==3.1
12
10
  # via cibuildwheel
13
11
  build==1.6.1
14
12
  # via cibuildwheel
15
- certifi==2026.7.22
13
+ certifi==2026.10.19
16
14
  # via cibuildwheel
17
- cibuildwheel==4.2.1
15
+ cibuildwheel==4.3.1
18
16
  # via -r requirements-wheels.in
19
17
  dependency-groups==1.3.2
20
18
  # via cibuildwheel
21
- filelock==4.0.9
19
+ filelock==4.2.4
22
20
  # via cibuildwheel
23
21
  humanize==4.16.0
24
22
  # via cibuildwheel
@@ -27,7 +25,7 @@ packaging==26.3
27
25
  # build
28
26
  # cibuildwheel
29
27
  # dependency-groups
30
- platformdirs==4.12.2
28
+ platformdirs==4.13.5
31
29
  # via cibuildwheel
32
30
  pyproject-hooks==1.3.3
33
31
  # via build
@@ -26,6 +26,12 @@ def test_hyperlinkparams_parse_valid(seq, url, params, term):
26
26
  'not an escape',
27
27
  '\x1b[31m',
28
28
  '',
29
+ '\x1b]8;;https://example.com\x07text',
30
+ '\x1b]8;;https://example.com\x07\n',
31
+ '\x1b]8;;https://example.com\x07\x1b[31m',
32
+ '\x1b]8;;https://example.com\x1b\\text',
33
+ '\x1b]8;;https://example.com\x1b\\\n',
34
+ '\x1b]8;;https://example.com\x1b\\\x1b[31m',
29
35
  ])
30
36
  def test_hyperlinkparams_parse_invalid(seq):
31
37
  """Parse an invalid/non-OSC-8 sequence returns None."""
@@ -486,6 +486,17 @@ OSC_END_BEL = '\x1b]8;;\x07'
486
486
  '\x1b]8;id=00000002;http://example.com\x07gh\x1b]8;;\x07',
487
487
  ],
488
488
  ),
489
+ ( # a link opened after a preceding link
490
+ '\x1b]8;id=first;https://a\x07aa bb\x1b]8;;\x07 '
491
+ '\x1b]8;id=second;https://b\x07cc dd ee\x1b]8;;\x07',
492
+ 5,
493
+ [
494
+ '\x1b]8;id=first;https://a\x07aa bb\x1b]8;;\x07',
495
+ ('\x1b]8;id=first;https://a\x07\x1b]8;;\x07' +
496
+ '\x1b]8;id=second;https://b\x07cc dd\x1b]8;;\x07'),
497
+ '\x1b]8;id=second;https://b\x07ee\x1b]8;;\x07',
498
+ ],
499
+ ),
489
500
  ( # long word inside hyperlink forces character-level breaking
490
501
  f'{OSC_START_ST}abcdefgh{OSC_END_ST}',
491
502
  3,
@@ -98,4 +98,4 @@ __all__ = ('wcwidth', 'wcswidth', 'wcstwidth', 'width', 'iter_sequences', 'iter_
98
98
  'Hyperlink', 'HyperlinkParams', 'TextSizing', 'TextSizingParams')
99
99
 
100
100
  # Version is stamped by code generation (bin/update-tables.py) from pyproject.toml.
101
- __version__ = '0.9.2'
101
+ __version__ = '0.9.3'
@@ -44,7 +44,7 @@ class HyperlinkParams(typing.NamedTuple):
44
44
  >>> HyperlinkParams.parse('\x1b]8;;http://example.com\x07')
45
45
  HyperlinkParams(url='http://example.com', params='', terminator='\x07')
46
46
  """
47
- m = HYPERLINK_OPEN_RE.match(seq)
47
+ m = HYPERLINK_OPEN_RE.fullmatch(seq)
48
48
  if m is None:
49
49
  return None
50
50
  return cls(url=m.group(2), params=m.group(1), terminator=m.group(3))
@@ -224,8 +224,6 @@ class SequenceTextWrapper(textwrap.TextWrapper):
224
224
  is_first_line = True
225
225
 
226
226
  hyperlink_state: Optional[HyperlinkParams] = None
227
- # Track the id we're using for the current hyperlink continuation
228
- current_hyperlink_id: Optional[str] = None
229
227
 
230
228
  # Arrange in reverse order so items can be efficiently popped
231
229
  chunks = list(reversed(chunks))
@@ -312,17 +310,16 @@ class SequenceTextWrapper(textwrap.TextWrapper):
312
310
 
313
311
  # If we end inside a hyperlink, append close sequence
314
312
  if new_state is not None:
315
- # Ensure we have an id for continuation
316
- if current_hyperlink_id is None:
317
- if 'id=' in new_state.params:
318
- current_hyperlink_id = new_state.params
319
- elif new_state.params:
320
- # Prepend id to existing params. Per OSC 8 spec, params can have
321
- # multiple key=value pairs separated by ':'.
322
- current_hyperlink_id = (
323
- f'id={self._next_hyperlink_id()}:{new_state.params}')
324
- else:
325
- current_hyperlink_id = f'id={self._next_hyperlink_id()}'
313
+ # Use the active link's params; wrapper state can leak a prior link's id.
314
+ if 'id=' in new_state.params:
315
+ continuation_params = new_state.params
316
+ elif new_state.params:
317
+ # Prepend id to existing params. Per OSC 8 spec, params can have
318
+ # multiple key=value pairs separated by ':'.
319
+ continuation_params = (
320
+ f'id={self._next_hyperlink_id()}:{new_state.params}')
321
+ else:
322
+ continuation_params = f'id={self._next_hyperlink_id()}'
326
323
  line_content += HyperlinkParams(
327
324
  terminator=new_state.terminator, url='').make_close()
328
325
 
@@ -337,17 +334,16 @@ class SequenceTextWrapper(textwrap.TextWrapper):
337
334
  ).make_open()
338
335
  new_open = HyperlinkParams(
339
336
  url=new_state.url,
340
- params=current_hyperlink_id,
337
+ params=continuation_params,
341
338
  terminator=new_state.terminator,
342
339
  ).make_open()
343
340
  line_content = line_content.replace(old_open, new_open, 1)
344
341
 
345
- # Update state for next line, using computed id
342
+ # Update state for next line, using computed params
346
343
  hyperlink_state = HyperlinkParams(
347
- new_state.url, current_hyperlink_id, new_state.terminator)
344
+ new_state.url, continuation_params, new_state.terminator)
348
345
  else:
349
346
  hyperlink_state = None
350
- current_hyperlink_id = None # Reset id when hyperlink closes
351
347
 
352
348
  # Strip trailing whitespace when drop_whitespace is enabled
353
349
  # (matches CPython #140627 fix behavior)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wcwidth
3
- Version: 0.9.2
3
+ Version: 0.9.3
4
4
  Summary: Measures the displayed width of unicode strings in a terminal
5
5
  Author-email: Jeff Quast <contact@jeffquast.com>
6
6
  Project-URL: Homepage, https://github.com/jquast/wcwidth
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes