wcwidth 0.6.0__tar.gz → 0.7.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. {wcwidth-0.6.0 → wcwidth-0.7.0}/PKG-INFO +136 -33
  2. wcwidth-0.7.0/bin/show-sequences +23 -0
  3. wcwidth-0.7.0/bin/strip-sequences +18 -0
  4. {wcwidth-0.6.0 → wcwidth-0.7.0}/bin/update-tables.py +11 -6
  5. {wcwidth-0.6.0 → wcwidth-0.7.0}/bin/verify-table-integrity.py +1 -0
  6. {wcwidth-0.6.0 → wcwidth-0.7.0}/bin/wcwidth-browser.py +9 -9
  7. {wcwidth-0.6.0 → wcwidth-0.7.0}/bin/wcwidth-libc-comparator.py +1 -0
  8. {wcwidth-0.6.0 → wcwidth-0.7.0}/code_templates/grapheme_table.py.j2 +1 -1
  9. {wcwidth-0.6.0 → wcwidth-0.7.0}/code_templates/python_table.py.j2 +1 -1
  10. {wcwidth-0.6.0 → wcwidth-0.7.0}/code_templates/unicode_versions.py.j2 +1 -1
  11. {wcwidth-0.6.0 → wcwidth-0.7.0}/docs/api.rst +8 -0
  12. {wcwidth-0.6.0 → wcwidth-0.7.0}/docs/intro.rst +135 -32
  13. {wcwidth-0.6.0 → wcwidth-0.7.0}/docs/requirements.txt +9 -10
  14. {wcwidth-0.6.0 → wcwidth-0.7.0}/docs/specs.rst +27 -8
  15. {wcwidth-0.6.0 → wcwidth-0.7.0}/docs/unicode_version.rst +15 -0
  16. {wcwidth-0.6.0 → wcwidth-0.7.0}/pyproject.toml +1 -1
  17. {wcwidth-0.6.0 → wcwidth-0.7.0}/requirements-tests38.in +1 -2
  18. {wcwidth-0.6.0 → wcwidth-0.7.0}/requirements-tests38.txt +4 -9
  19. {wcwidth-0.6.0 → wcwidth-0.7.0}/requirements-tests39.in +2 -1
  20. {wcwidth-0.6.0 → wcwidth-0.7.0}/requirements-tests39.txt +7 -10
  21. {wcwidth-0.6.0 → wcwidth-0.7.0}/requirements-update.in +1 -1
  22. {wcwidth-0.6.0 → wcwidth-0.7.0}/requirements-update.txt +8 -9
  23. {wcwidth-0.6.0 → wcwidth-0.7.0}/tests/conftest.py +2 -0
  24. {wcwidth-0.6.0 → wcwidth-0.7.0}/tests/test_ambiguous.py +1 -0
  25. {wcwidth-0.6.0 → wcwidth-0.7.0}/tests/test_benchmarks.py +173 -6
  26. wcwidth-0.7.0/tests/test_clip.py +454 -0
  27. wcwidth-0.7.0/tests/test_clip_cjk_emoji.py +47 -0
  28. wcwidth-0.7.0/tests/test_clip_overtyping.py +159 -0
  29. {wcwidth-0.6.0 → wcwidth-0.7.0}/tests/test_core.py +32 -16
  30. {wcwidth-0.6.0 → wcwidth-0.7.0}/tests/test_emojis.py +1 -0
  31. {wcwidth-0.6.0 → wcwidth-0.7.0}/tests/test_grapheme.py +1 -0
  32. wcwidth-0.7.0/tests/test_hyperlink.py +75 -0
  33. {wcwidth-0.6.0 → wcwidth-0.7.0}/tests/test_justify.py +1 -0
  34. {wcwidth-0.6.0 → wcwidth-0.7.0}/tests/test_sgr_state.py +1 -0
  35. wcwidth-0.7.0/tests/test_text_sizing.py +327 -0
  36. {wcwidth-0.6.0 → wcwidth-0.7.0}/tests/test_textwrap.py +39 -69
  37. {wcwidth-0.6.0 → wcwidth-0.7.0}/tests/test_ucslevel.py +1 -0
  38. {wcwidth-0.6.0 → wcwidth-0.7.0}/tests/test_width.py +148 -22
  39. {wcwidth-0.6.0 → wcwidth-0.7.0}/tox.ini +26 -28
  40. wcwidth-0.7.0/wcwidth/__init__.py +52 -0
  41. wcwidth-0.7.0/wcwidth/_clip.py +809 -0
  42. wcwidth-0.7.0/wcwidth/_constants.py +65 -0
  43. wcwidth-0.7.0/wcwidth/_wcswidth.py +150 -0
  44. wcwidth-0.7.0/wcwidth/_wcwidth.py +158 -0
  45. wcwidth-0.7.0/wcwidth/_width.py +339 -0
  46. wcwidth-0.7.0/wcwidth/align.py +136 -0
  47. {wcwidth-0.6.0 → wcwidth-0.7.0}/wcwidth/bisearch.py +3 -2
  48. wcwidth-0.7.0/wcwidth/escape_sequences.py +194 -0
  49. {wcwidth-0.6.0 → wcwidth-0.7.0}/wcwidth/grapheme.py +6 -6
  50. wcwidth-0.7.0/wcwidth/hyperlink.py +142 -0
  51. {wcwidth-0.6.0 → wcwidth-0.7.0}/wcwidth/sgr_state.py +2 -1
  52. {wcwidth-0.6.0 → wcwidth-0.7.0}/wcwidth/table_ambiguous.py +1 -1
  53. {wcwidth-0.6.0 → wcwidth-0.7.0}/wcwidth/table_grapheme.py +1 -1
  54. {wcwidth-0.6.0 → wcwidth-0.7.0}/wcwidth/table_mc.py +1 -1
  55. {wcwidth-0.6.0 → wcwidth-0.7.0}/wcwidth/table_vs16.py +1 -1
  56. {wcwidth-0.6.0 → wcwidth-0.7.0}/wcwidth/table_wide.py +3 -3
  57. {wcwidth-0.6.0 → wcwidth-0.7.0}/wcwidth/table_zero.py +1 -1
  58. wcwidth-0.7.0/wcwidth/text_sizing.py +200 -0
  59. {wcwidth-0.6.0 → wcwidth-0.7.0}/wcwidth/textwrap.py +35 -54
  60. wcwidth-0.7.0/wcwidth/wcwidth.py +69 -0
  61. wcwidth-0.6.0/bin/new-wide-by-version.py +0 -48
  62. wcwidth-0.6.0/tests/test_clip.py +0 -262
  63. wcwidth-0.6.0/wcwidth/__init__.py +0 -43
  64. wcwidth-0.6.0/wcwidth/escape_sequences.py +0 -69
  65. wcwidth-0.6.0/wcwidth/wcwidth.py +0 -1030
  66. {wcwidth-0.6.0 → wcwidth-0.7.0}/.gitignore +0 -0
  67. {wcwidth-0.6.0 → wcwidth-0.7.0}/.pylintrc +0 -0
  68. {wcwidth-0.6.0 → wcwidth-0.7.0}/LICENSE +0 -0
  69. {wcwidth-0.6.0 → wcwidth-0.7.0}/README.rst +0 -0
  70. {wcwidth-0.6.0 → wcwidth-0.7.0}/code_templates/unicode_version.rst.j2 +0 -0
  71. {wcwidth-0.6.0 → wcwidth-0.7.0}/docs/conf.py +0 -0
  72. {wcwidth-0.6.0 → wcwidth-0.7.0}/docs/index.rst +0 -0
  73. {wcwidth-0.6.0 → wcwidth-0.7.0}/requirements-develop.txt +0 -0
  74. {wcwidth-0.6.0 → wcwidth-0.7.0}/requirements-docs.in +0 -0
  75. {wcwidth-0.6.0 → wcwidth-0.7.0}/tests/__init__.py +0 -0
  76. {wcwidth-0.6.0 → wcwidth-0.7.0}/wcwidth/control_codes.py +0 -0
  77. {wcwidth-0.6.0 → wcwidth-0.7.0}/wcwidth/py.typed +0 -0
  78. {wcwidth-0.6.0 → wcwidth-0.7.0}/wcwidth/unicode_versions.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wcwidth
3
- Version: 0.6.0
3
+ Version: 0.7.0
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>
@@ -28,6 +28,7 @@ Classifier: Typing :: Typed
28
28
  Requires-Python: >=3.8
29
29
  Description-Content-Type: text/x-rst
30
30
 
31
+
31
32
  |pypi_downloads| |codecov| |license|
32
33
 
33
34
  ============
@@ -65,33 +66,42 @@ Some examples of **incorrect results**:
65
66
  Solution
66
67
  --------
67
68
 
68
- The lowest-level functions in this library are the POSIX.1-2001 and POSIX.1-2008 `wcwidth(3)`_ and
69
- `wcswidth(3)`_, which this library precisely copies by interface as `wcwidth()`_ and `wcswidth()`_.
70
- These functions return -1 when C0 and C1 control codes are present.
69
+ The lowest-level functions in this library are derived from POSIX.1-2001 and POSIX.1-2008
70
+ `wcwidth(3)`_ and `wcswidth(3)`_, which this library precisely copies by interface as `wcwidth()`_
71
+ and `wcswidth()`_. These functions return -1 when C0 and C1 control codes are present.
71
72
 
72
73
  An easy-to-use `width()`_ function is provided as a wrapper of `wcswidth()`_ that is also capable of
73
74
  measuring most terminal control codes and sequences, like colors, bold, tabstops, and horizontal
74
75
  cursor movement.
75
76
 
76
- Text-justification is solved by the grapheme and sequence-aware functions `ljust()`_,
77
- `rjust()`_, `center()`_, and `wrap()`_, serving as drop-in replacements to python standard functions
78
- of the same names.
77
+ Text-justification is solved by the sequence-aware functions `ljust()`_, `rjust()`_, `center()`_,
78
+ and the grapheme-aware function `wrap()`_, serving as drop-in replacements to python standard
79
+ functions.
80
+
81
+ The `clip()`_ function extracts substrings by their displayed column positions, and
82
+ `strip_sequences()`_ removes terminal escape sequences from text altogether.
79
83
 
80
84
  The iterator functions `iter_graphemes()`_ and `iter_sequences()`_ allow for careful navigation of
81
- grapheme and terminal control sequence boundaries. `iter_graphemes_reverse()`_, and
82
- `grapheme_boundary_before()`_ are useful for editing and searching of complex unicode. The
83
- `clip()`_ function extracts substrings by display column positions, and `strip_sequences()`_ removes
84
- terminal escape sequences from text altogether.
85
+ grapheme and terminal control sequence boundaries as required by editors or REPLs with cursor
86
+ control. `iter_graphemes_reverse()`_, and `grapheme_boundary_before()`_ are often necessary for
87
+ backward cursor control over complex unicode.
85
88
 
86
89
  Discrepancies
87
90
  -------------
88
91
 
89
- You may find that support *varies* for complex unicode sequences or codepoints.
92
+ You may find that support *varies* for complex unicode sequences or codepoints. This library may be
93
+ considered to presume the terminal is enabled for DEC Private Mode 2027 ("Grapheme Clustering"), but
94
+ the specification does not fully describe varying unicode versions, feature levels, or details of
95
+ specific language support. This library does *not* support any alternate "legacy width"
96
+ measurement.
90
97
 
91
- A companion utility, `jquast/ucs-detect`_ was authored to gather and publish the results of Wide
92
- character, language/grapheme clustering and complex script support, emojis and zero-width joiner,
93
- variations, and regional indicator (flags) as a `General Tabulated Summary`_ by terminal emulator
94
- software and version.
98
+ See `Grapheme Clusters and Terminal Emulators`_ and `terminal-unicode-core.tex`_, and `State of
99
+ Terminal Emulators in 2025`_ for more details on Mode 2027 and unicode-aware terminals.
100
+
101
+ The `jquast/ucs-detect`_ utility is used to gather and publish the results of compliance to our
102
+ standard for Wide character, Languages, grapheme clustering, complex or combining scripts, emojis,
103
+ zero-width joiner, variations, and regional indicator (flags) as a `General
104
+ Tabulated Summary`_ by terminal emulator software and version.
95
105
 
96
106
  ========
97
107
  Overview
@@ -148,30 +158,61 @@ Use function `width()`_ to measure a string with improved handling of ``control_
148
158
  >>> # same support as wcswidth(), eg. regional indicator flag:
149
159
  >>> wcwidth.width('\U0001F1FF\U0001F1FC')
150
160
  2
151
- >>> # but also supports SGR colored text, 'WARN', followed by SGR reset
161
+ >>> # but also supports sequences, like SGR colored text, "WARN", followed by reset
152
162
  >>> wcwidth.width('\x1b[38;2;255;150;100mWARN\x1b[0m')
153
163
  4
154
- >>> # tabs,
164
+ >>> # tabs are measured as though the string begins at a tabstop,
155
165
  >>> wcwidth.width('\t', tabsize=4)
156
166
  4
157
- >>> # or, tab and all other control characters can be ignored
158
- >>> wcwidth.width('\t', control_codes='ignore')
159
- 0
160
- >>> # "vertical" control characters are ignored
161
- >>> wcwidth.width('\n')
167
+ >>> # or, all control characters can be ignored (including tab)
168
+ >>> wcwidth.width('\t\n\a\r', control_codes='ignore')
162
169
  0
163
- >>> # as well as sequences with "indeterminate" effects like Home + Clear
170
+ >>> # sequences with "indeterminate" effects like Home + Clear are zero-width
164
171
  >>> wcwidth.width('\x1b[H\x1b[2J')
165
172
  0
173
+ >>> # horizontal cursor movements are parsed,
174
+ >>> wcwidth.width('hello\b\b\b\b\bworld')
175
+ 5
176
+ >>> wcwidth.width('hello\x1b[5Dworld')
177
+ 5
178
+ >>> # or ignored,
179
+ >>> wcwidth.width('hello\x1b[5Dworld', control_codes='ignore')
180
+ 10
181
+ >>> # Measure width of text using kitty text sizing protocol (OSC 66),
182
+ >>> width('\x1b]66;w=2;XY\x07')
183
+ 2
184
+ >>> # Scaled text sizing: each grapheme occupies 'scale' cells
185
+ >>> width('\x1b]66;s=2;ABC\x07')
186
+ 6
187
+
188
+ Use ``control_codes='ignore'`` when the input is known not to contain any control characters or
189
+ terminal sequences for slightly improved performance. Note that TAB (``'\t'``) is a control
190
+ character and is also ignored, you may want to use `str.expandtabs()`_, first.
191
+
192
+ Use ``control_codes='strict'`` when input is known to contain some control sequences, such as
193
+ SGR color, bold, hyperlinks and cursor movement. Any sequence that cannot be accurately parsed,
194
+ such as clearing the screen, vertical, or absolute cursor movement will raise ``ValueError``:
195
+
196
+ .. code-block:: python
197
+
166
198
  >>> # or, raise ValueError for "indeterminate" effects using control_codes='strict'
167
199
  >>> wcwidth.width('\n', control_codes='strict')
168
200
  Traceback (most recent call last):
169
201
  ...
170
202
  ValueError: Vertical movement character 0xa at position 0
171
203
 
172
- Use ``control_codes='ignore'`` when the input is known not to contain any control characters or
173
- terminal sequences for slightly improved performance. Note that TAB (``'\t'``) is a control
174
- character and is also ignored, you may want to use `str.expandtabs()`_, first.
204
+
205
+ >>> wcwidth.width('\x1b[H\x1b[2J', control_codes='strict')
206
+ Traceback (most recent call last):
207
+ ...
208
+ ValueError: Indeterminate cursor sequence at position 0, '\x1b[H'
209
+
210
+
211
+ >>> # cursor left movement beyond string start raises in strict mode,
212
+ >>> wcwidth.width('a\x1b[5Da', control_codes='strict')
213
+ Traceback (most recent call last):
214
+ ...
215
+ ValueError: Cursor left movement at position 1 would move 5 cells left from column 1, exceeding string start
175
216
 
176
217
  iter_sequences()
177
218
  ----------------
@@ -290,9 +331,29 @@ Use `clip()`_ to extract a substring by column positions, preserving terminal se
290
331
  >>> clip('\x1b[1;31mHello world\x1b[0m', 6, 11)
291
332
  '\x1b[1;31mworld\x1b[0m'
292
333
 
293
- >>> # Disable SGR propagation to preserve original sequences as-is
294
- >>> clip('\x1b[31m中文\x1b[0m', 0, 3, propagate_sgr=False)
295
- '\x1b[31m中 \x1b[0m'
334
+ >>> # Disable SGR propagation to preserve sequence order outside of clip boundary
335
+ >>> clip('\x1b[31m中文\x1b[32m', 0, 3, propagate_sgr=False)
336
+ '\x1b[31m中 \x1b[32m'
337
+
338
+ >>> # Cursor-left overwrites previous text (painter's algorithm)
339
+ >>> clip('hello\x1b[2DXY', 0, 5)
340
+ 'helXY'
341
+ >>> # Carriage return resets to column 0, overwriting earlier cells
342
+ >>> clip('abc\rXY', 0, 5)
343
+ 'XYc'
344
+
345
+ >>> # even OSC 8 hyperlink text may be clipped, 'Click This link' -> 'is link' !
346
+ >>> clip('\x1b]8;;http://example.com\x07Click This link\x1b]8;;\x07', 8, 15)
347
+ '\x1b]8;;http://example.com\x07is link\x1b]8;;\x07'
348
+
349
+ >>> # and OSC 66 kitty text sizing, supporting width and scale, 'Look' -> '...ook'
350
+ >>> clip('\x1b]66;w=4:s=4;Look\x07', 1, 16, fillchar='.')
351
+ '...\x1b]66;s=4:w=3;ook\x07'
352
+
353
+ Use ``overtyping=False`` when the input is known not to contain any cursor movement characters
354
+ (``\b``, ``\r``, ``CSI C``, ``CSI D``, ``CSI G``) for improved performance. When
355
+ ``overtyping=None`` (default), a slower "Painter's algorithm" may be used after testing for the
356
+ presence of these characters. ``overtyping`` has no effect when ``control_codes='ignore'``.
296
357
 
297
358
  strip_sequences()
298
359
  -----------------
@@ -336,7 +397,7 @@ mode is to display an ambiguous character surrounded by a pair of Cursor Positio
336
397
  queries with a terminal in cooked or raw mode, and to parse the responses for their ``(y, x)``
337
398
  locations and measure the difference ``x``.
338
399
 
339
- This code should also be careful check whether it is attached to a terminal and be careful of
400
+ This code should also be careful to check whether it is attached to a terminal and be careful of
340
401
  possible timeout, slow network, or non-response when working with "dumb terminals" like a CI build.
341
402
 
342
403
  `jquast/blessed`_ library provides such a helping `Terminal.detect_ambiguous_width()`_ method:
@@ -429,9 +490,18 @@ This library is used in:
429
490
  - `jquast/blessed`_: a thin, practical wrapper around terminal capabilities in
430
491
  Python.
431
492
 
493
+ - `jquast/telix`_: A Modern telnet client especially designed for BBSs and MUDs.
494
+
432
495
  - `prompt-toolkit/python-prompt-toolkit`_: a Library for building powerful
433
496
  interactive command lines in Python.
434
497
 
498
+ - `urwid/urwid`_: Console user interface library for Python
499
+
500
+ - `prettytable/prettytable`_: Display tabular data in a visually appealing ASCII table format
501
+
502
+ - `leviathan0992/Pylsy`_: Pylsy is a simple python library draw tables in the Terminal. Just two
503
+ lines of code.
504
+
435
505
  - `dbcli/pgcli`_: Postgres CLI with autocompletion and syntax highlighting.
436
506
 
437
507
  - `thomasballinger/curtsies`_: a Curses-like terminal wrapper with a display
@@ -448,8 +518,8 @@ This library is used in:
448
518
  - `nbedos/termtosvg`_: Terminal recorder that renders sessions as SVG
449
519
  animations.
450
520
 
451
- - `peterbrittain/asciimatics`_: Package to help people create full-screen text
452
- UIs.
521
+ - `peterbrittain/asciimatics`_: A cross platform package to do curses-like operations, plus higher
522
+ level APIs and widgets to create text UIs and ASCII art animations
453
523
 
454
524
  - `python-cmd2/cmd2`_: A tool for building interactive command line apps
455
525
 
@@ -469,6 +539,10 @@ Other Languages
469
539
  There are similar implementations of the `wcwidth()`_ and `wcswidth()`_ functions in other
470
540
  languages.
471
541
 
542
+ - `contour-terminal/libunicode`_: C++20
543
+ - `ridiculousfish/widecharwidth`_: Python
544
+ - `termux/wcwidth`_: C
545
+ - `powerman/wcwidth-icons`_: C
472
546
  - `timoxley/wcwidth`_: JavaScript
473
547
  - `janlelis/unicode-display_width`_: Ruby
474
548
  - `alecrabbit/php-wcwidth`_: PHP
@@ -478,6 +552,9 @@ languages.
478
552
  - `grepsuzette/wcwidth`_: Haxe
479
553
  - `aperezdc/lua-wcwidth`_: Lua
480
554
  - `joachimschmidt557/zig-wcwidth`_: Zig
555
+ - `mycoboco/wcwidth.js`_: JavaScript
556
+ - `ainame/swift-displaywidth`_: Swift
557
+ - `pmonks/clj-wcwidth`_: Clojure
481
558
  - `fumiyas/wcwidth-cjk`_: `LD_PRELOAD` override
482
559
  - `joshuarubin/wcwidth9`_: Unicode version 9 in C
483
560
  - `spectreconsole/wcwidth`_: C#
@@ -486,6 +563,15 @@ languages.
486
563
  History
487
564
  =======
488
565
 
566
+ 0.7.0 *2026-05-02*
567
+ * **New** support for `kitty text sizing protocol`_ (OSC 66) in `width()`_ and `clip()`_.
568
+ * **New** `clip()`_ parameter ``control_codes='parse'``, ``'ignore'``, and ``'strict'``. `clip()`_
569
+ is now able to clip OSC 8 hyperlinks and OSC 66 text sizing sequences.
570
+ * **Improved** `clip()`_ and `width()`_ to support horizontal cursor sequences (``cub``, ``cuf``,
571
+ ``hpa``). Cursor-left (``cub``) or backspace (``\b``) now overwrites text. ``column_address``
572
+ (``hpa``) and carriage return (``\r``) are now parsed, and more values conditionally raise
573
+ ``ValueError`` when ``control_codes='strict'``.
574
+
489
575
  0.6.0 *2026-02-06*
490
576
  * **New** Parameters ``expand_tabs``, ``replace_whitespace``, ``fix_sentence_endings``,
491
577
  ``drop_whitespace``, ``max_lines``, and ``placeholder`` for `wrap()`_, completing stdlib
@@ -711,6 +797,7 @@ https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c::
711
797
  .. _`Issue #155`: https://github.com/jquast/wcwidth/issues/155
712
798
  .. _`Issue #190`: https://github.com/jquast/wcwidth/issues/190
713
799
  .. _`jquast/blessed`: https://github.com/jquast/blessed
800
+ .. _`jquast/telix`: https://github.com/jquast/telix
714
801
  .. _`selectel/pyte`: https://github.com/selectel/pyte
715
802
  .. _`thomasballinger/curtsies`: https://github.com/thomasballinger/curtsies
716
803
  .. _`dbcli/pgcli`: https://github.com/dbcli/pgcli
@@ -735,10 +822,20 @@ https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c::
735
822
  .. _`fumiyas/wcwidth-cjk`: https://github.com/fumiyas/wcwidth-cjk
736
823
  .. _`joshuarubin/wcwidth9`: https://github.com/joshuarubin/wcwidth9
737
824
  .. _`spectreconsole/wcwidth`: https://github.com/spectreconsole/wcwidth
825
+ .. _`contour-terminal/libunicode`: https://github.com/contour-terminal/libunicode
826
+ .. _`ridiculousfish/widecharwidth`: https://github.com/ridiculousfish/widecharwidth
827
+ .. _`termux/wcwidth`: https://github.com/termux/wcwidth
828
+ .. _`powerman/wcwidth-icons`: https://github.com/powerman/wcwidth-icons
829
+ .. _`mycoboco/wcwidth.js`: https://github.com/mycoboco/wcwidth.js
830
+ .. _`ainame/swift-displaywidth`: https://github.com/ainame/swift-displaywidth
831
+ .. _`pmonks/clj-wcwidth`: https://github.com/pmonks/clj-wcwidth
738
832
  .. _`python-cmd2/cmd2`: https://github.com/python-cmd2/cmd2
739
833
  .. _`stratis-storage/stratis-cli`: https://github.com/stratis-storage/stratis-cli
740
834
  .. _`ihabunek/toot`: https://github.com/ihabunek/toot
741
835
  .. _`saulpw/visidata`: https://github.com/saulpw/visidata
836
+ .. _`urwid/urwid`: https://github.com/urwid/urwid
837
+ .. _`prettytable/prettytable`: https://github.com/urwid/urwid
838
+ .. _`leviathan0992/Pylsy`: https://github.com/leviathan0992/Pylsy
742
839
  .. _`pip-tools`: https://pip-tools.readthedocs.io/
743
840
  .. _`sphinx`: https://www.sphinx-doc.org/
744
841
  .. _`textwrap.wrap()`: https://docs.python.org/3/library/textwrap.html#textwrap.wrap
@@ -760,11 +857,17 @@ https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c::
760
857
  .. _`clip()`: https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.clip
761
858
  .. _`strip_sequences()`: https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.strip_sequences
762
859
  .. _`propagate_sgr()`: https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.propagate_sgr
860
+ .. _`TextSizing`: https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.TextSizing
861
+ .. _`TextSizingParams`: https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.TextSizingParams
763
862
  .. _`iter_sequences()`: https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.iter_sequences
764
863
  .. _`list_versions()`: https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.list_versions
765
864
  .. _`Unicode Standard Annex #29`: https://www.unicode.org/reports/tr29/
766
865
  .. _`Terminal.detect_ambiguous_width()`: https://blessed.readthedocs.io/en/latest/api/terminal.html#blessed.terminal.Terminal.detect_ambiguous_width
767
866
  .. _`parity padding`: https://jazcap53.github.io/pythons-eccentric-strcenter.html
867
+ .. _`kitty text sizing protocol`: https://sw.kovidgoyal.net/kitty/text-sizing-protocol/
868
+ .. _`Grapheme Clusters and Terminal Emulators`: https://mitchellh.com/writing/grapheme-clusters-in-terminals
869
+ .. _`terminal-unicode-core.tex`: https://github.com/contour-terminal/terminal-unicode-core/blob/master/spec/terminal-unicode-core.tex
870
+ .. _`State of Terminal Emulators in 2025`: https://www.jeffquast.com/post/state-of-terminal-emulation-2025/
768
871
  .. |pypi_downloads| image:: https://img.shields.io/pypi/dm/wcwidth.svg?logo=pypi
769
872
  :alt: Downloads
770
873
  :target: https://pypi.org/project/wcwidth/
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env python3
2
+ """Show terminal escape sequences in a file or stdin as repr() literals."""
3
+ # std imports
4
+ import sys
5
+
6
+ # local
7
+ from wcwidth import iter_sequences
8
+
9
+
10
+ def _read_input():
11
+ # some care is taken regarding '\r\n' because this program is meant to show
12
+ # raw terminal data to terminals that are *not* in raw mode, so different
13
+ # interpretation of '\r\n' is needed.
14
+ if len(sys.argv) > 1:
15
+ with open(sys.argv[1], "rb") as f:
16
+ return f.read().replace(b"\r\n", b"\n").decode()
17
+ return sys.stdin.buffer.read().replace(b"\r\n", b"\n").decode()
18
+
19
+
20
+ for line in _read_input().split("\n"):
21
+ for item, is_seq in iter_sequences(line):
22
+ print(repr(item) if is_seq else item.replace("\r", repr("\r")), end="")
23
+ print("", flush=True)
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env python3
2
+ """Strip terminal escape sequences from a file or stdin."""
3
+ # std imports
4
+ import sys
5
+
6
+ # local
7
+ from wcwidth import strip_sequences
8
+
9
+ # some care is taken regarding '\r\n' because this program is meant to show
10
+ # raw terminal data to terminals that are *not* in raw mode, so different
11
+ # interpretation of '\r\n' is needed.
12
+
13
+ if len(sys.argv) > 1:
14
+ with open(sys.argv[1], "rb") as f:
15
+ text = f.read().replace(b"\r\n", b"\n").replace(b"\r", b"").decode()
16
+ else:
17
+ text = sys.stdin.buffer.read().replace(b"\r\n", b"\n").replace(b"\r", b"").decode()
18
+ sys.stdout.write(strip_sequences(text))
@@ -1,13 +1,14 @@
1
1
  #!/usr/bin/env python
2
2
  """
3
- Update the Unicode code tables for wcwidth. This is code generation using jinja2.
3
+ Update the Unicode code tables for wcwidth.
4
4
 
5
- This is typically executed through tox,
5
+ This is code generation using jinja2. This is typically executed through tox,
6
6
 
7
7
  $ tox -e update
8
8
 
9
9
  https://github.com/jquast/wcwidth
10
10
  """
11
+
11
12
  from __future__ import annotations
12
13
 
13
14
  # std imports
@@ -24,7 +25,7 @@ import unicodedata
24
25
  from pathlib import Path
25
26
  from dataclasses import field, fields, dataclass
26
27
 
27
- from typing import Any, Mapping, Iterable, Iterator, Sequence, Collection
28
+ from typing import Any, Mapping, Iterable, Iterator, Optional, Sequence, Collection
28
29
 
29
30
  try:
30
31
  from typing import Self
@@ -108,9 +109,10 @@ def _bisearch(ucs, table):
108
109
  @dataclass(order=True, frozen=True)
109
110
  class UnicodeVersion:
110
111
  """A class for comparable unicode version."""
112
+
111
113
  major: int
112
114
  minor: int
113
- micro: int | None
115
+ micro: Optional[int]
114
116
 
115
117
  @classmethod
116
118
  def parse(cls, version_str: str) -> UnicodeVersion:
@@ -138,7 +140,8 @@ class UnicodeVersion:
138
140
  @dataclass(frozen=True)
139
141
  class TableEntry:
140
142
  """An entry of a unicode table."""
141
- code_range: tuple[int, int] | None
143
+
144
+ code_range: Optional[tuple[int, int]]
142
145
  properties: tuple[str, ...]
143
146
  comment: str
144
147
 
@@ -255,6 +258,7 @@ class UnicodeTableRenderCtx(RenderContext):
255
258
  @dataclass
256
259
  class RenderDefinition:
257
260
  """Base class, do not instantiate it directly."""
261
+
258
262
  jinja_filename: str
259
263
  output_filename: str
260
264
  render_context: RenderContext
@@ -330,6 +334,7 @@ class UnicodeTableRenderDef(RenderDefinition):
330
334
  @dataclass(frozen=True)
331
335
  class GraphemeTableRenderCtx(RenderContext):
332
336
  """Render context for grapheme tables (latest version only)."""
337
+
333
338
  unicode_version: str
334
339
  tables: Mapping[str, TableDef]
335
340
 
@@ -880,7 +885,6 @@ def fetch_table_grapheme_data() -> GraphemeTableRenderCtx:
880
885
  tables.update(parse_indic_syllabic_category(
881
886
  UnicodeDataFile.IndicSyllabicCategory(latest_version)
882
887
  ))
883
-
884
888
  return GraphemeTableRenderCtx(str(latest_version), tables)
885
889
 
886
890
 
@@ -895,6 +899,7 @@ class UnicodeDataFile:
895
899
  TestEmojiVariationSequences, these files should be forcefully re-fetched CLI argument '--no-
896
900
  check-last-modified'.
897
901
  """
902
+
898
903
  URL_DERIVED_AGE = 'https://www.unicode.org/Public/UCD/latest/ucd/DerivedAge.txt'
899
904
  URL_EASTASIAN_WIDTH = 'https://www.unicode.org/Public/{version}/ucd/EastAsianWidth.txt'
900
905
  URL_DERIVED_CATEGORY = 'https://www.unicode.org/Public/{version}/ucd/extracted/DerivedGeneralCategory.txt'
@@ -65,6 +65,7 @@ Category code was changed from 'Mc' to 'Lo':
65
65
  +DerivedGeneralCategory-8.0.0.txt:19B0..19C9 ; Lo # [26] NEW TAI LUE VOWEL SIGN VOWEL SHORTENER..NEW TAI LUE TONE MARK-2
66
66
 
67
67
  """
68
+
68
69
  # std imports
69
70
  import logging
70
71
 
@@ -376,6 +376,7 @@ class Screen:
376
376
 
377
377
  class Pager:
378
378
  """A less(1)-like browser for browsing unicode characters."""
379
+
379
380
  # pylint: disable=too-many-instance-attributes
380
381
 
381
382
  #: screen state for next draw method(s).
@@ -520,14 +521,13 @@ class Pager:
520
521
  """
521
522
  Pager entry point.
522
523
 
523
- In interactive mode (terminal is a tty), run until
524
- ``process_keystroke()`` detects quit keystroke ('q'). In
525
- non-interactive mode, exit after displaying all unicode points.
524
+ In interactive mode (terminal is a tty), run until ``process_keystroke()`` detects quit
525
+ keystroke ('q'). In non-interactive mode, exit after displaying all unicode points.
526
526
 
527
527
  :param writer: callable writes to output stream, receiving unicode.
528
528
  :type writer: callable
529
- :param reader: callable reads keystrokes from input stream, sending
530
- instance of blessed.keyboard.Keystroke.
529
+ :param reader: callable reads keystrokes from input stream, sending instance of
530
+ blessed.keyboard.Keystroke.
531
531
  :type reader: callable
532
532
  """
533
533
  self.initialize_page_data()
@@ -720,8 +720,8 @@ class Pager:
720
720
  """
721
721
  Conditionally redraw screen when ``dirty`` attribute is valued REFRESH.
722
722
 
723
- When Pager attribute ``dirty`` is ``STATE_REFRESH``, cursor is moved
724
- to (0,0), screen is cleared, and heading is displayed.
723
+ When Pager attribute ``dirty`` is ``STATE_REFRESH``, cursor is moved to (0,0), screen is
724
+ cleared, and heading is displayed.
725
725
 
726
726
  :param callable writer: callable writes to output stream, receiving unicode.
727
727
  :return: True if class attribute ``dirty`` is ``STATE_REFRESH``.
@@ -787,8 +787,8 @@ class Pager:
787
787
  """
788
788
  Generator yields text to be displayed for the current unicode pageview.
789
789
 
790
- :param list[(unicode, unicode)] data: The current page's data as tuple
791
- of ``(ucs, name)``.
790
+ :param list[(unicode, unicode)] data: The current page's data as tuple of ``(ucs,
791
+ name)``.
792
792
  :returns: generator for full-page text for display
793
793
  """
794
794
  if self.term.is_a_tty:
@@ -13,6 +13,7 @@ platforms -- usually conforming only to unicode specification 1.0 or 2.0.
13
13
  This program accepts one optional command-line argument, the unicode version
14
14
  level for our library to use when comparing to libc.
15
15
  """
16
+
16
17
  # pylint: disable=C0103
17
18
  # Invalid module name "wcwidth-libc-comparator"
18
19
 
@@ -4,7 +4,7 @@ Exports grapheme cluster break property tables for Unicode version {{ unicode_ve
4
4
  This module provides lookup tables for Unicode grapheme cluster break properties as defined in UAX
5
5
  #29: Unicode Text Segmentation.
6
6
 
7
- This code generated by {{this_filepath}} on {{utc_now}}.
7
+ This code generated by python wcwidth project.
8
8
  """
9
9
  # pylint: disable=duplicate-code
10
10
  {%- for var_name, table_def in tables.items() %}
@@ -1,7 +1,7 @@
1
1
  """
2
2
  Exports {{ variable_name }} table keyed by supporting unicode version level.
3
3
 
4
- This code generated by {{this_filepath}} on {{utc_now}}.
4
+ This code generated by python wcwidth project.
5
5
  """
6
6
  # pylint: disable=duplicate-code
7
7
  {{ variable_name }} = {
@@ -1,7 +1,7 @@
1
1
  """
2
2
  Exports function list_versions() for unicode version level support.
3
3
 
4
- This code generated by {{this_filepath}} on {{utc_now}}.
4
+ This code generated by python wcwidth project.
5
5
  """
6
6
 
7
7
  from __future__ import annotations
@@ -36,4 +36,12 @@ requirements.txt or equivalent. Their signatures will never change.
36
36
 
37
37
  .. autofunction:: wcwidth.list_versions
38
38
 
39
+ .. autofunction:: wcwidth.Hyperlink
40
+
41
+ .. autofunction:: wcwidth.HyperlinkParams
42
+
43
+ .. autofunction:: wcwidth.TextSizing
44
+
45
+ .. autofunction:: wcwidth.TextSizingParams
46
+
39
47
  .. _SEMVER: https://semver.org