wcwidth 0.8.2__tar.gz → 0.8.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.
- {wcwidth-0.8.2 → wcwidth-0.8.3}/PKG-INFO +15 -17
- {wcwidth-0.8.2 → wcwidth-0.8.3}/docs/intro.rst +13 -15
- {wcwidth-0.8.2 → wcwidth-0.8.3}/docs/specs.rst +8 -5
- {wcwidth-0.8.2 → wcwidth-0.8.3}/pyproject.toml +1 -1
- {wcwidth-0.8.2 → wcwidth-0.8.3}/tests/test_clip.py +81 -1
- {wcwidth-0.8.2 → wcwidth-0.8.3}/tests/test_textwrap.py +20 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/tox.ini +19 -6
- {wcwidth-0.8.2 → wcwidth-0.8.3}/ucs-detect/data/AbsoluteTelnetSSH.yaml +251 -304
- {wcwidth-0.8.2 → wcwidth-0.8.3}/ucs-detect/data/alacritty.yaml +22 -21
- {wcwidth-0.8.2 → wcwidth-0.8.3}/ucs-detect/data/bobcat.yaml +336 -61
- {wcwidth-0.8.2 → wcwidth-0.8.3}/ucs-detect/data/conemu.exe.yaml +5 -4
- {wcwidth-0.8.2 → wcwidth-0.8.3}/ucs-detect/data/contour.yaml +383 -187
- {wcwidth-0.8.2 → wcwidth-0.8.3}/ucs-detect/data/coolretroterm.yaml +170 -91
- {wcwidth-0.8.2 → wcwidth-0.8.3}/ucs-detect/data/extraterm.yaml +16 -860
- {wcwidth-0.8.2 → wcwidth-0.8.3}/ucs-detect/data/foot.yaml +34 -18
- {wcwidth-0.8.2 → wcwidth-0.8.3}/ucs-detect/data/ghostty.yaml +7 -22
- wcwidth-0.8.2/ucs-detect/data/lxterminal.yaml → wcwidth-0.8.3/ucs-detect/data/gnometerminal.yaml +1380 -1352
- {wcwidth-0.8.2 → wcwidth-0.8.3}/ucs-detect/data/hyper.yaml +872 -835
- {wcwidth-0.8.2 → wcwidth-0.8.3}/ucs-detect/data/kitty.yaml +246 -217
- {wcwidth-0.8.2 → wcwidth-0.8.3}/ucs-detect/data/konsole.yaml +135 -116
- {wcwidth-0.8.2 → wcwidth-0.8.3}/ucs-detect/data/libvterm.yaml +228 -155
- wcwidth-0.8.3/ucs-detect/data/lxterminal.yaml +13347 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/ucs-detect/data/mintty.yaml +6 -5
- {wcwidth-0.8.2 → wcwidth-0.8.3}/ucs-detect/data/mlterm.yaml +146 -91
- {wcwidth-0.8.2 → wcwidth-0.8.3}/ucs-detect/data/putty.yaml +24 -20
- {wcwidth-0.8.2 → wcwidth-0.8.3}/ucs-detect/data/qterminal.yaml +59 -46
- {wcwidth-0.8.2 → wcwidth-0.8.3}/ucs-detect/data/rio.yaml +317 -106
- {wcwidth-0.8.2 → wcwidth-0.8.3}/ucs-detect/data/rxvtunicode.yaml +29 -16
- {wcwidth-0.8.2 → wcwidth-0.8.3}/ucs-detect/data/screen.yaml +1812 -1806
- {wcwidth-0.8.2 → wcwidth-0.8.3}/ucs-detect/data/st.yaml +32 -16
- {wcwidth-0.8.2 → wcwidth-0.8.3}/ucs-detect/data/tabby.yaml +1312 -612
- {wcwidth-0.8.2 → wcwidth-0.8.3}/ucs-detect/data/teraterm.yaml +7 -6
- {wcwidth-0.8.2 → wcwidth-0.8.3}/ucs-detect/data/terminalexe.yaml +19 -12
- wcwidth-0.8.3/ucs-detect/data/terminator.yaml +13561 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/ucs-detect/data/terminology.yaml +222 -116
- {wcwidth-0.8.2 → wcwidth-0.8.3}/ucs-detect/data/tmux.yaml +195 -140
- {wcwidth-0.8.2 → wcwidth-0.8.3}/ucs-detect/data/vscodeterminal.yaml +35 -40
- {wcwidth-0.8.2 → wcwidth-0.8.3}/ucs-detect/data/warp.yaml +277 -870
- {wcwidth-0.8.2 → wcwidth-0.8.3}/ucs-detect/data/wezterm.yaml +334 -267
- {wcwidth-0.8.2 → wcwidth-0.8.3}/ucs-detect/data/xfce4terminal.yaml +1488 -1421
- {wcwidth-0.8.2 → wcwidth-0.8.3}/ucs-detect/data/xterm.yaml +18 -21
- {wcwidth-0.8.2 → wcwidth-0.8.3}/ucs-detect/data/zellij.yaml +268 -864
- {wcwidth-0.8.2 → wcwidth-0.8.3}/ucs-detect/data/zutty.yaml +57 -47
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/__init__.py +1 -1
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/_clip.py +65 -32
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/textwrap.py +15 -9
- wcwidth-0.8.3/wcwidth/textwrap.py,cover +664 -0
- wcwidth-0.8.2/ucs-detect/data/gnometerminal.yaml +0 -16247
- wcwidth-0.8.2/ucs-detect/data/terminator.yaml +0 -15308
- {wcwidth-0.8.2 → wcwidth-0.8.3}/.gitignore +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/.pylintrc +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/LICENSE +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/README.rst +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/bin/show-sequences +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/bin/strip-sequences +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/bin/update-tables.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/bin/verify-table-integrity.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/bin/wcwidth-browser.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/bin/wcwidth-libc-comparator.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/code_templates/grapheme_override_per_terminal.py.j2 +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/code_templates/grapheme_override_table.py.j2 +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/code_templates/grapheme_registry.py.j2 +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/code_templates/grapheme_table.py.j2 +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/code_templates/python_table.py.j2 +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/code_templates/table_overrides.py.j2 +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/code_templates/term_programs.py.j2 +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/code_templates/unicode_version.rst.j2 +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/code_templates/unicode_versions.py.j2 +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/docs/api.rst +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/docs/conf.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/docs/index.rst +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/docs/requirements.txt +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/docs/unicode_version.rst +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/requirements-develop.txt +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/requirements-docs.in +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/requirements-tests38.in +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/requirements-tests38.txt +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/requirements-tests39.in +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/requirements-tests39.txt +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/requirements-update.in +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/requirements-update.txt +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/tests/__init__.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/tests/conftest.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/tests/test_ambiguous.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/tests/test_benchmarks.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/tests/test_clip_cjk_emoji.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/tests/test_clip_overtyping.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/tests/test_core.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/tests/test_emojis.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/tests/test_grapheme.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/tests/test_hyperlink.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/tests/test_justify.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/tests/test_sgr_state.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/tests/test_term_overrides.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/tests/test_text_sizing.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/tests/test_ucslevel.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/tests/test_width.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/ucs-detect/data/_linuxfbdev.yaml +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/ucs-detect/data/_syncterm.yaml +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/ucs-detect/data/emoji-test-latest.txt +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/ucs-detect/data/emoji-variation-sequences-latest.txt +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/ucs-detect/data/emoji-zwj-sequences-latest.txt +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/ucs-detect/data/iterm2.yaml +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/ucs-detect/data/securecrt.yaml +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/ucs-detect/data/terminalapp.yaml +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/ucs-detect/data/westonterminal.yaml +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/_constants.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/_wcswidth.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/_wcwidth.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/_width.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/align.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/bisearch.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/control_codes.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/escape_sequences.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/grapheme.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/hyperlink.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/py.typed +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/sgr_state.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/table_ambiguous.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/table_grapheme.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/table_grapheme_overrides/__init__.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/table_grapheme_overrides/_known_27e0693f.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/table_grapheme_overrides/_known_3d4826b8.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/table_grapheme_overrides/_known_45d92e98.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/table_grapheme_overrides/_known_4cdf59ce.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/table_grapheme_overrides/_known_50bf0759.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/table_grapheme_overrides/_known_529fbb4a.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/table_grapheme_overrides/_known_5bfac390.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/table_grapheme_overrides/_known_813fee16.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/table_grapheme_overrides/_known_8589765c.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/table_grapheme_overrides/_known_8f94b404.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/table_grapheme_overrides/_known_970dbe10.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/table_grapheme_overrides/_known_c0a2cdbf.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/table_grapheme_overrides/_known_c2157f7e.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/table_grapheme_overrides/_known_c3db41c0.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/table_grapheme_overrides/_known_da9ceb0a.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/table_grapheme_overrides/_known_e08bd75e.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/table_grapheme_overrides/_known_e22030f3.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/table_grapheme_overrides/_known_fcc05a0f.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/table_grapheme_overrides/_known_fd9d4c44.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/table_grapheme_overrides/_registry.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/table_mc.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/table_overrides.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/table_term_programs.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/table_vs15.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/table_vs16.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/table_wide.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/table_zero.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/text_sizing.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/unicode_versions.py +0 -0
- {wcwidth-0.8.2 → wcwidth-0.8.3}/wcwidth/wcwidth.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: wcwidth
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.3
|
|
4
4
|
Summary: Measures the displayed width of unicode strings in a terminal
|
|
5
5
|
Project-URL: Homepage, https://github.com/jquast/wcwidth
|
|
6
6
|
Author-email: Jeff Quast <contact@jeffquast.com>
|
|
@@ -176,7 +176,7 @@ width()
|
|
|
176
176
|
-------
|
|
177
177
|
|
|
178
178
|
Use function `width()`_ to measure a string with improved handling of ``control_codes`` and
|
|
179
|
-
measurement Corrections_ through ``term_program
|
|
179
|
+
measurement Corrections_ through ``term_program``:
|
|
180
180
|
|
|
181
181
|
.. code-block:: python
|
|
182
182
|
|
|
@@ -511,8 +511,7 @@ recognized names:
|
|
|
511
511
|
.. END_LIST_TERM_PROGRAMS
|
|
512
512
|
|
|
513
513
|
``term_program=False`` (the default for `width()`_, `ljust()`_, `rjust()`_, `center()`_,
|
|
514
|
-
`wrap()`_, and `clip()`_) disables terminal corrections.
|
|
515
|
-
``term_program=True`` for auto-detection.
|
|
514
|
+
`wrap()`_, and `clip()`_) disables terminal corrections.
|
|
516
515
|
|
|
517
516
|
For automatic tests and other purposes that require cross-environment consistency, set static values
|
|
518
517
|
or unset ``TERM`` and ``TERM_PROGRAM`` environment values, such as in ``conftest.py`` with pytest:
|
|
@@ -588,16 +587,6 @@ To upgrade requirements for building documentation, run::
|
|
|
588
587
|
|
|
589
588
|
tox -e update_requirements_docs
|
|
590
589
|
|
|
591
|
-
Utilities
|
|
592
|
-
---------
|
|
593
|
-
|
|
594
|
-
Supplementary tools for browsing and testing terminals for wide unicode
|
|
595
|
-
characters are found in the `bin/`_ of this project's source code. Just ensure
|
|
596
|
-
to first ``pip install -r requirements-develop.txt`` from this projects main
|
|
597
|
-
folder. For example, an interactive browser for testing::
|
|
598
|
-
|
|
599
|
-
python ./bin/wcwidth-browser.py
|
|
600
|
-
|
|
601
590
|
====
|
|
602
591
|
Uses
|
|
603
592
|
====
|
|
@@ -685,9 +674,15 @@ languages:
|
|
|
685
674
|
History
|
|
686
675
|
=======
|
|
687
676
|
|
|
677
|
+
0.8.3 *2026-08-28*
|
|
678
|
+
* **Bugfix** Do not hang on `wrap()`_ calls of width 1 with text containing OSC8 hyperlinks and
|
|
679
|
+
wide characters, `PR #231`_.
|
|
680
|
+
* **Bugfix** `clip()`_ with ``propagate_sgr=True``, should match behavior of `propagate_sgr()`_,
|
|
681
|
+
`PR #235`_.
|
|
682
|
+
|
|
688
683
|
0.8.2 *2026-06-29*
|
|
689
|
-
* **Bugfix** Do not raise IndexError when
|
|
690
|
-
`wcstwidth()`_
|
|
684
|
+
* **Bugfix** Do not raise IndexError when legacy POSIX ``n`` argument to `wcswidth()`_ or
|
|
685
|
+
`wcstwidth()`_ exceeds the given string length. `PR #230`_
|
|
691
686
|
|
|
692
687
|
0.8.1 *2026-06-08*
|
|
693
688
|
* **Improved** `wcstwidth()`_ with new ``zeroer``, ``narrow_wider``, and ``narrow_zeroer``
|
|
@@ -942,6 +937,9 @@ https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c::
|
|
|
942
937
|
.. _`PR #223`: https://github.com/jquast/wcwidth/pull/223
|
|
943
938
|
.. _`PR #224`: https://github.com/jquast/wcwidth/pull/224
|
|
944
939
|
.. _`PR #226`: https://github.com/jquast/wcwidth/pull/226
|
|
940
|
+
.. _`PR #230`: https://github.com/jquast/wcwidth/pull/230
|
|
941
|
+
.. _`PR #231`: https://github.com/jquast/wcwidth/pull/231
|
|
942
|
+
.. _`PR #235`: https://github.com/jquast/wcwidth/pull/235
|
|
945
943
|
.. _`Issue #101`: https://github.com/jquast/wcwidth/issues/101
|
|
946
944
|
.. _`Issue #155`: https://github.com/jquast/wcwidth/issues/155
|
|
947
945
|
.. _`Issue #190`: https://github.com/jquast/wcwidth/issues/190
|
|
@@ -145,7 +145,7 @@ width()
|
|
|
145
145
|
-------
|
|
146
146
|
|
|
147
147
|
Use function `width()`_ to measure a string with improved handling of ``control_codes`` and
|
|
148
|
-
measurement Corrections_ through ``term_program
|
|
148
|
+
measurement Corrections_ through ``term_program``:
|
|
149
149
|
|
|
150
150
|
.. code-block:: python
|
|
151
151
|
|
|
@@ -480,8 +480,7 @@ recognized names:
|
|
|
480
480
|
.. END_LIST_TERM_PROGRAMS
|
|
481
481
|
|
|
482
482
|
``term_program=False`` (the default for `width()`_, `ljust()`_, `rjust()`_, `center()`_,
|
|
483
|
-
`wrap()`_, and `clip()`_) disables terminal corrections.
|
|
484
|
-
``term_program=True`` for auto-detection.
|
|
483
|
+
`wrap()`_, and `clip()`_) disables terminal corrections.
|
|
485
484
|
|
|
486
485
|
For automatic tests and other purposes that require cross-environment consistency, set static values
|
|
487
486
|
or unset ``TERM`` and ``TERM_PROGRAM`` environment values, such as in ``conftest.py`` with pytest:
|
|
@@ -557,16 +556,6 @@ To upgrade requirements for building documentation, run::
|
|
|
557
556
|
|
|
558
557
|
tox -e update_requirements_docs
|
|
559
558
|
|
|
560
|
-
Utilities
|
|
561
|
-
---------
|
|
562
|
-
|
|
563
|
-
Supplementary tools for browsing and testing terminals for wide unicode
|
|
564
|
-
characters are found in the `bin/`_ of this project's source code. Just ensure
|
|
565
|
-
to first ``pip install -r requirements-develop.txt`` from this projects main
|
|
566
|
-
folder. For example, an interactive browser for testing::
|
|
567
|
-
|
|
568
|
-
python ./bin/wcwidth-browser.py
|
|
569
|
-
|
|
570
559
|
====
|
|
571
560
|
Uses
|
|
572
561
|
====
|
|
@@ -654,9 +643,15 @@ languages:
|
|
|
654
643
|
History
|
|
655
644
|
=======
|
|
656
645
|
|
|
646
|
+
0.8.3 *2026-08-28*
|
|
647
|
+
* **Bugfix** Do not hang on `wrap()`_ calls of width 1 with text containing OSC8 hyperlinks and
|
|
648
|
+
wide characters, `PR #231`_.
|
|
649
|
+
* **Bugfix** `clip()`_ with ``propagate_sgr=True``, should match behavior of `propagate_sgr()`_,
|
|
650
|
+
`PR #235`_.
|
|
651
|
+
|
|
657
652
|
0.8.2 *2026-06-29*
|
|
658
|
-
* **Bugfix** Do not raise IndexError when
|
|
659
|
-
`wcstwidth()`_
|
|
653
|
+
* **Bugfix** Do not raise IndexError when legacy POSIX ``n`` argument to `wcswidth()`_ or
|
|
654
|
+
`wcstwidth()`_ exceeds the given string length. `PR #230`_
|
|
660
655
|
|
|
661
656
|
0.8.1 *2026-06-08*
|
|
662
657
|
* **Improved** `wcstwidth()`_ with new ``zeroer``, ``narrow_wider``, and ``narrow_zeroer``
|
|
@@ -911,6 +906,9 @@ https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c::
|
|
|
911
906
|
.. _`PR #223`: https://github.com/jquast/wcwidth/pull/223
|
|
912
907
|
.. _`PR #224`: https://github.com/jquast/wcwidth/pull/224
|
|
913
908
|
.. _`PR #226`: https://github.com/jquast/wcwidth/pull/226
|
|
909
|
+
.. _`PR #230`: https://github.com/jquast/wcwidth/pull/230
|
|
910
|
+
.. _`PR #231`: https://github.com/jquast/wcwidth/pull/231
|
|
911
|
+
.. _`PR #235`: https://github.com/jquast/wcwidth/pull/235
|
|
914
912
|
.. _`Issue #101`: https://github.com/jquast/wcwidth/issues/101
|
|
915
913
|
.. _`Issue #155`: https://github.com/jquast/wcwidth/issues/155
|
|
916
914
|
.. _`Issue #190`: https://github.com/jquast/wcwidth/issues/190
|
|
@@ -81,6 +81,14 @@ Width of 1
|
|
|
81
81
|
String characters are measured width of 1 when they are not
|
|
82
82
|
measured as `Width of 0`_ or `Width of 2`_.
|
|
83
83
|
|
|
84
|
+
Any character with `U+FE0E`_ (Variation Selector 15) defined as ``text style``
|
|
85
|
+
in `emoji-variation-sequences.txt`_, per `UTS #51`_ and `Unicode Standard
|
|
86
|
+
Section 23.4`_: VS15 subtracts 1 cell from the wide character it directly
|
|
87
|
+
follows, making the pair width 1. Narrow characters are unchanged.
|
|
88
|
+
|
|
89
|
+
.. note:: Variation Selector 15 width specification is contested, see
|
|
90
|
+
https://github.com/jquast/wcwidth/issues/211
|
|
91
|
+
|
|
84
92
|
Width of 2
|
|
85
93
|
----------
|
|
86
94
|
|
|
@@ -105,11 +113,6 @@ in `emoji-variation-sequences.txt`_, per `UTS #51`_ and `Unicode Standard
|
|
|
105
113
|
Section 23.4`_: VS16 adds 1 cell to the narrow character it directly follows,
|
|
106
114
|
making the pair width 2. Wide characters are unchanged.
|
|
107
115
|
|
|
108
|
-
Any character with `U+FE0E`_ (Variation Selector 15) defined as ``text style``
|
|
109
|
-
in `emoji-variation-sequences.txt`_, per `UTS #51`_ and `Unicode Standard
|
|
110
|
-
Section 23.4`_: VS15 subtracts 1 cell from the wide character it directly
|
|
111
|
-
follows, making the pair width 1. Narrow characters are unchanged.
|
|
112
|
-
|
|
113
116
|
Any character of non-zero width followed by an ``Mc`` (`Spacing Combining Mark`_)
|
|
114
117
|
character when measured in sequence by :func:`wcwidth.wcswidth` or
|
|
115
118
|
:func:`wcwidth.width`. The ``Mc`` character caps the cluster width at 2,
|
|
@@ -4,7 +4,7 @@ requires = [ "hatchling" ]
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "wcwidth"
|
|
7
|
-
version = "0.8.
|
|
7
|
+
version = "0.8.3" # don't forget to also update wcwidth/__init__.py:__version__
|
|
8
8
|
description = "Measures the displayed width of unicode strings in a terminal"
|
|
9
9
|
readme = "README.rst"
|
|
10
10
|
keywords = [
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import pytest
|
|
5
5
|
|
|
6
6
|
# local
|
|
7
|
-
from wcwidth import clip, width, strip_sequences
|
|
7
|
+
from wcwidth import clip, width, propagate_sgr, strip_sequences
|
|
8
8
|
|
|
9
9
|
STRIP_SEQUENCES_CASES = [
|
|
10
10
|
('', ''),
|
|
@@ -132,6 +132,86 @@ def test_clip_sequences_only():
|
|
|
132
132
|
assert repr(clip('\x1b[31m\x1b[0m', 0, 10, propagate_sgr=False)) == repr('\x1b[31m\x1b[0m')
|
|
133
133
|
|
|
134
134
|
|
|
135
|
+
SGR_PATH_KWARGS = [{}, {'overtyping': True}, {'control_codes': 'ignore'}]
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@pytest.mark.parametrize('kwargs', SGR_PATH_KWARGS)
|
|
139
|
+
def test_clip_sgr_inside_window_preserved(kwargs):
|
|
140
|
+
"""Style changes within (start, end) are emitted at their original position."""
|
|
141
|
+
# nothing is clipped away, so nothing changes,
|
|
142
|
+
assert (clip('\x1b[1mbold\x1b[m normal', 0, 20, **kwargs) ==
|
|
143
|
+
'\x1b[1mbold\x1b[m normal')
|
|
144
|
+
# and the same holds when the tail is clipped, the trailing reset is
|
|
145
|
+
# synthesized because the final style is still active.
|
|
146
|
+
assert (clip('\x1b[31mred\x1b[32mgreen\x1b[0m', 0, 4, **kwargs) ==
|
|
147
|
+
'\x1b[31mred\x1b[32mg\x1b[0m')
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@pytest.mark.parametrize('kwargs', SGR_PATH_KWARGS)
|
|
151
|
+
def test_clip_sgr_before_start_folded_into_prefix(kwargs):
|
|
152
|
+
"""Style set before *start* is synthesized as a prefix, not emitted twice."""
|
|
153
|
+
assert clip('\x1b[1;34mHello world\x1b[0m', 6, 11, **kwargs) == '\x1b[1;34mworld\x1b[0m'
|
|
154
|
+
# the style in effect at *start* is the most recent one,
|
|
155
|
+
assert clip('\x1b[31mred\x1b[32mgreen\x1b[0m', 4, 8, **kwargs) == '\x1b[32mreen\x1b[0m'
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@pytest.mark.parametrize('kwargs', SGR_PATH_KWARGS)
|
|
159
|
+
def test_clip_sgr_after_end_discarded(kwargs):
|
|
160
|
+
"""Style set at or beyond *end* does not appear in the result."""
|
|
161
|
+
assert clip('hello\x1b[31m world\x1b[0m', 0, 5, **kwargs) == 'hello'
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@pytest.mark.parametrize('kwargs', SGR_PATH_KWARGS)
|
|
165
|
+
@pytest.mark.parametrize('text', [
|
|
166
|
+
'plain text',
|
|
167
|
+
'\x1b[31mred',
|
|
168
|
+
'\x1b[31mred\x1b[0m',
|
|
169
|
+
'\x1b[1mbold\x1b[m normal',
|
|
170
|
+
'\x1b[31mred\x1b[32mgreen\x1b[0m',
|
|
171
|
+
'plain \x1b[4munderline',
|
|
172
|
+
])
|
|
173
|
+
def test_clip_matches_propagate_sgr(text, kwargs):
|
|
174
|
+
"""Clipping nothing away agrees with :func:`propagate_sgr`."""
|
|
175
|
+
assert clip(text, 0, width(text), **kwargs) == propagate_sgr([text])[0]
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
@pytest.mark.parametrize('kwargs', SGR_PATH_KWARGS)
|
|
179
|
+
@pytest.mark.parametrize('text', [
|
|
180
|
+
'\x1b[1m\x1b[31mbold red\x1b[0m',
|
|
181
|
+
'\x1b[31mred\x1b[32mgreen\x1b[0m',
|
|
182
|
+
'\x1b[?25l\x1b[31mred',
|
|
183
|
+
])
|
|
184
|
+
def test_clip_sgr_result_is_self_contained(text, kwargs):
|
|
185
|
+
"""The result carries its own style: propagating it again is a no-op."""
|
|
186
|
+
result = clip(text, 0, width(text), **kwargs)
|
|
187
|
+
assert propagate_sgr([result])[0] == result
|
|
188
|
+
assert strip_sequences(result) == strip_sequences(text)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@pytest.mark.parametrize('kwargs', SGR_PATH_KWARGS)
|
|
192
|
+
def test_clip_sgr_after_non_sgr_sequence(kwargs):
|
|
193
|
+
"""A non-SGR sequence before the first visible cell does not swallow the style."""
|
|
194
|
+
assert clip('\x1b[?25l\x1b[31mred', 0, 10, **kwargs) == '\x1b[31m\x1b[?25lred\x1b[0m'
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@pytest.mark.parametrize('kwargs', [{}, {'overtyping': True}])
|
|
198
|
+
def test_clip_sgr_inside_hyperlink_tracked(kwargs):
|
|
199
|
+
"""Style changed by hyperlink inner text still terminates with a reset."""
|
|
200
|
+
text = '\x1b]8;;http://x\x07a\x1b[31mb\x1b]8;;\x07c'
|
|
201
|
+
assert clip(text, 0, 10, **kwargs) == text + '\x1b[0m'
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
@pytest.mark.parametrize('kwargs', [{}, {'overtyping': True}])
|
|
205
|
+
def test_clip_sgr_inside_hyperlink_untracked(kwargs):
|
|
206
|
+
"""With ``propagate_sgr=False``, hyperlink inner style is neither tracked nor reset."""
|
|
207
|
+
text = '\x1b]8;;http://x\x07a\x1b[31mb\x1b]8;;\x07c'
|
|
208
|
+
# sequences pass through verbatim, no synthesized prefix or trailing reset,
|
|
209
|
+
assert clip(text, 0, 10, propagate_sgr=False, **kwargs) == text
|
|
210
|
+
# and that holds when 'c', the only cell outside the hyperlink, is clipped away.
|
|
211
|
+
assert (clip(text, 0, 2, propagate_sgr=False, **kwargs) ==
|
|
212
|
+
'\x1b]8;;http://x\x07a\x1b[31mb\x1b]8;;\x07')
|
|
213
|
+
|
|
214
|
+
|
|
135
215
|
def test_clip_sequences_osc_hyperlink():
|
|
136
216
|
assert repr(clip('\x1b]8;;https://example.com\x07link\x1b]8;;\x07', 0, 4)) == repr(
|
|
137
217
|
'\x1b]8;;https://example.com\x07link\x1b]8;;\x07'
|
|
@@ -387,6 +387,16 @@ OSC_END_BEL = '\x1b]8;;\x07'
|
|
|
387
387
|
'\x1b]8;foo=bar:id=mylink;http://example.com\x1b\\Click\x1b]8;;\x1b\\',
|
|
388
388
|
'\x1b]8;foo=bar:id=mylink;http://example.com\x1b\\here\x1b]8;;\x1b\\',
|
|
389
389
|
],
|
|
390
|
+
),
|
|
391
|
+
( # wide grapheme after OSC 8 open at width 1 (must not hang)
|
|
392
|
+
'\x1b]8;;u\x07😀',
|
|
393
|
+
1,
|
|
394
|
+
['\x1b]8;id=00000001;u\x07😀\x1b]8;;\x07'],
|
|
395
|
+
),
|
|
396
|
+
( # CJK wide char after OSC 8 open at width 1 (must not hang)
|
|
397
|
+
'\x1b]8;;u\x07あ',
|
|
398
|
+
1,
|
|
399
|
+
['\x1b]8;id=00000001;u\x07あ\x1b]8;;\x07'],
|
|
390
400
|
),])
|
|
391
401
|
def test_wrap_hyperlink_word_boundary(text, w, expected):
|
|
392
402
|
"""OSC hyperlink sequences should act as word boundaries."""
|
|
@@ -491,3 +501,13 @@ def test_wrap_bare_esc():
|
|
|
491
501
|
"""Bare ESC not part of a recognized sequence is treated as zero-width."""
|
|
492
502
|
assert wrap('ab\x1bcd ef', 5) == ['ab\x1bcd', 'ef']
|
|
493
503
|
assert wrap('ab\x1b\x00cdef', 3) == ['ab\x1b\x00c', 'def']
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
def test_wrap_bare_esc_at_line_start():
|
|
507
|
+
"""
|
|
508
|
+
Exercises the bare-ESC safety break in _find_first_visible_break.
|
|
509
|
+
|
|
510
|
+
A bare \\x1b that does not match ZERO_WIDTH_PATTERN triggers a safety 'break' statement but is
|
|
511
|
+
not found in any practical terminal sequence or string (ESC followed by NUL).
|
|
512
|
+
"""
|
|
513
|
+
assert wrap('\x1b\x00あ', 1) == ['\x1b', '\x00', 'あ']
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
[tox]
|
|
2
|
-
envlist = update, fetch, compile, autopep8, docformatter, docformatter_check, isort, isort_check, pylint, pylint_tests, flake8, flake8_tests, pydocstyle, mypy, codespell, format, lint, docs, verify_tables, py{38, 39, 310, 311, 312, 313, 314}
|
|
2
|
+
envlist = update, fetch, compile, autopep8, docformatter, docformatter_check, isort, isort_check, pylint, pylint_tests, flake8, flake8_tests, pydocstyle, mypy, codespell, twine_check, format, lint, docs, verify_tables, py{38, 39, 310, 311, 312, 313, 314}
|
|
3
3
|
skip_missing_interpreters = true
|
|
4
4
|
|
|
5
5
|
[base]
|
|
@@ -122,13 +122,13 @@ commands =
|
|
|
122
122
|
wcwidth/ bin/ tests/
|
|
123
123
|
|
|
124
124
|
[testenv:isort]
|
|
125
|
-
deps = isort
|
|
125
|
+
deps = isort==8.0.1
|
|
126
126
|
basepython = python3.13
|
|
127
|
-
commands = isort --quiet
|
|
127
|
+
commands = isort --quiet wcwidth tests bin
|
|
128
128
|
|
|
129
129
|
[testenv:pylint]
|
|
130
130
|
basepython = python3.13
|
|
131
|
-
deps = pylint
|
|
131
|
+
deps = pylint==4.0.7
|
|
132
132
|
commands = pylint --rcfile={toxinidir}/.pylintrc \
|
|
133
133
|
--ignore=tests,docs,conf.py,build,distutils,.pyenv,.git,.tox \
|
|
134
134
|
{posargs:{toxinidir}}/wcwidth
|
|
@@ -183,7 +183,7 @@ commands = docformatter --check --diff --recursive --pre-summary-newline \
|
|
|
183
183
|
|
|
184
184
|
[testenv:isort_check]
|
|
185
185
|
basepython = python3.14
|
|
186
|
-
deps = isort
|
|
186
|
+
deps = {[testenv:isort]deps}
|
|
187
187
|
commands = isort --diff --check-only wcwidth tests bin
|
|
188
188
|
|
|
189
189
|
[testenv:flake8_tests]
|
|
@@ -194,7 +194,7 @@ commands = flake8 --ignore=E501,W504,F401 tests/
|
|
|
194
194
|
[testenv:pylint_tests]
|
|
195
195
|
basepython = python3.13
|
|
196
196
|
deps = pytest
|
|
197
|
-
pylint
|
|
197
|
+
{[testenv:pylint]deps}
|
|
198
198
|
commands = pylint --rcfile={toxinidir}/.pylintrc \
|
|
199
199
|
--disable=protected-access,unused-argument,missing-function-docstring,line-too-long \
|
|
200
200
|
tests
|
|
@@ -217,6 +217,18 @@ commands = codespell --skip="*.pyc,htmlcov,_build,build,*.egg-info,.tox,data,./t
|
|
|
217
217
|
--uri-ignore-words-list '*' \
|
|
218
218
|
--summary --count
|
|
219
219
|
|
|
220
|
+
[testenv:twine_check]
|
|
221
|
+
# Verify the built distributions the way upload.pypi.org does: 'twine check'
|
|
222
|
+
# runs the README through readme_renderer, which halts on any reStructuredText
|
|
223
|
+
# error (such as a reference with no matching target). Neither doc8 (D000 is
|
|
224
|
+
# disabled) nor the python linters inspect README.rst.
|
|
225
|
+
basepython = python3.14
|
|
226
|
+
skip_install = true
|
|
227
|
+
deps = build
|
|
228
|
+
twine
|
|
229
|
+
commands = python -m build --outdir {envtmpdir}/dist
|
|
230
|
+
python -c "import glob, subprocess, sys; subprocess.check_call(['twine', 'check', '--strict'] + sorted(glob.glob(sys.argv[1] + '/dist/*')))" {envtmpdir}
|
|
231
|
+
|
|
220
232
|
[testenv:format]
|
|
221
233
|
basepython = python3.13
|
|
222
234
|
deps = {[testenv:isort]deps}
|
|
@@ -234,6 +246,7 @@ deps = {[testenv:flake8]deps}
|
|
|
234
246
|
{[testenv:pydocstyle]deps}
|
|
235
247
|
{[testenv:pylint_tests]deps}
|
|
236
248
|
{[testenv:codespell]deps}
|
|
249
|
+
{[testenv:twine_check]deps}
|
|
237
250
|
commands = {[testenv:compile]commands}
|
|
238
251
|
{[testenv:flake8]commands}
|
|
239
252
|
{[testenv:mypy]commands}
|