wcwidth 0.6.0__tar.gz → 0.8.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 (151) hide show
  1. {wcwidth-0.6.0 → wcwidth-0.8.0}/.pylintrc +2 -0
  2. {wcwidth-0.6.0 → wcwidth-0.8.0}/PKG-INFO +289 -39
  3. wcwidth-0.8.0/bin/show-sequences +23 -0
  4. wcwidth-0.8.0/bin/strip-sequences +18 -0
  5. {wcwidth-0.6.0 → wcwidth-0.8.0}/bin/update-tables.py +603 -57
  6. {wcwidth-0.6.0 → wcwidth-0.8.0}/bin/verify-table-integrity.py +1 -0
  7. {wcwidth-0.6.0 → wcwidth-0.8.0}/bin/wcwidth-browser.py +9 -9
  8. {wcwidth-0.6.0 → wcwidth-0.8.0}/bin/wcwidth-libc-comparator.py +1 -0
  9. wcwidth-0.8.0/code_templates/grapheme_override_per_terminal.py.j2 +13 -0
  10. wcwidth-0.8.0/code_templates/grapheme_override_table.py.j2 +18 -0
  11. wcwidth-0.8.0/code_templates/grapheme_registry.py.j2 +13 -0
  12. {wcwidth-0.6.0 → wcwidth-0.8.0}/code_templates/grapheme_table.py.j2 +1 -1
  13. {wcwidth-0.6.0 → wcwidth-0.8.0}/code_templates/python_table.py.j2 +1 -1
  14. wcwidth-0.8.0/code_templates/table_overrides.py.j2 +37 -0
  15. wcwidth-0.8.0/code_templates/term_programs.py.j2 +22 -0
  16. {wcwidth-0.6.0 → wcwidth-0.8.0}/code_templates/unicode_versions.py.j2 +1 -1
  17. {wcwidth-0.6.0 → wcwidth-0.8.0}/docs/api.rst +14 -0
  18. {wcwidth-0.6.0 → wcwidth-0.8.0}/docs/intro.rst +287 -38
  19. {wcwidth-0.6.0 → wcwidth-0.8.0}/docs/requirements.txt +10 -11
  20. {wcwidth-0.6.0 → wcwidth-0.8.0}/docs/specs.rst +60 -25
  21. {wcwidth-0.6.0 → wcwidth-0.8.0}/docs/unicode_version.rst +15 -0
  22. {wcwidth-0.6.0 → wcwidth-0.8.0}/pyproject.toml +5 -1
  23. {wcwidth-0.6.0 → wcwidth-0.8.0}/requirements-tests38.in +1 -2
  24. {wcwidth-0.6.0 → wcwidth-0.8.0}/requirements-tests38.txt +4 -9
  25. {wcwidth-0.6.0 → wcwidth-0.8.0}/requirements-tests39.in +2 -1
  26. {wcwidth-0.6.0 → wcwidth-0.8.0}/requirements-tests39.txt +8 -12
  27. {wcwidth-0.6.0 → wcwidth-0.8.0}/requirements-update.in +2 -1
  28. {wcwidth-0.6.0 → wcwidth-0.8.0}/requirements-update.txt +10 -8
  29. wcwidth-0.8.0/tests/conftest.py +38 -0
  30. {wcwidth-0.6.0 → wcwidth-0.8.0}/tests/test_ambiguous.py +1 -0
  31. {wcwidth-0.6.0 → wcwidth-0.8.0}/tests/test_benchmarks.py +256 -7
  32. wcwidth-0.8.0/tests/test_clip.py +454 -0
  33. wcwidth-0.8.0/tests/test_clip_cjk_emoji.py +47 -0
  34. wcwidth-0.8.0/tests/test_clip_overtyping.py +159 -0
  35. {wcwidth-0.6.0 → wcwidth-0.8.0}/tests/test_core.py +74 -21
  36. {wcwidth-0.6.0 → wcwidth-0.8.0}/tests/test_emojis.py +1 -0
  37. {wcwidth-0.6.0 → wcwidth-0.8.0}/tests/test_grapheme.py +1 -0
  38. wcwidth-0.8.0/tests/test_hyperlink.py +75 -0
  39. {wcwidth-0.6.0 → wcwidth-0.8.0}/tests/test_justify.py +1 -0
  40. {wcwidth-0.6.0 → wcwidth-0.8.0}/tests/test_sgr_state.py +1 -0
  41. wcwidth-0.8.0/tests/test_term_overrides.py +493 -0
  42. wcwidth-0.8.0/tests/test_text_sizing.py +328 -0
  43. {wcwidth-0.6.0 → wcwidth-0.8.0}/tests/test_textwrap.py +39 -69
  44. {wcwidth-0.6.0 → wcwidth-0.8.0}/tests/test_ucslevel.py +1 -0
  45. {wcwidth-0.6.0 → wcwidth-0.8.0}/tests/test_width.py +154 -22
  46. {wcwidth-0.6.0 → wcwidth-0.8.0}/tox.ini +29 -27
  47. wcwidth-0.8.0/ucs-detect/data/AbsoluteTelnetSSH.yaml +789 -0
  48. wcwidth-0.8.0/ucs-detect/data/_linuxfbdev.yaml +50 -0
  49. wcwidth-0.8.0/ucs-detect/data/_syncterm.yaml +38 -0
  50. wcwidth-0.8.0/ucs-detect/data/alacritty.yaml +8755 -0
  51. wcwidth-0.8.0/ucs-detect/data/bobcat.yaml +10780 -0
  52. wcwidth-0.8.0/ucs-detect/data/conemu.exe.yaml +29154 -0
  53. wcwidth-0.8.0/ucs-detect/data/contour.yaml +16580 -0
  54. wcwidth-0.8.0/ucs-detect/data/coolretroterm.yaml +10905 -0
  55. wcwidth-0.8.0/ucs-detect/data/extraterm.yaml +27161 -0
  56. wcwidth-0.8.0/ucs-detect/data/foot.yaml +8336 -0
  57. wcwidth-0.8.0/ucs-detect/data/ghostty.yaml +1001 -0
  58. wcwidth-0.8.0/ucs-detect/data/gnometerminal.yaml +13095 -0
  59. wcwidth-0.8.0/ucs-detect/data/hyper.yaml +12481 -0
  60. wcwidth-0.8.0/ucs-detect/data/iterm2.yaml +6576 -0
  61. wcwidth-0.8.0/ucs-detect/data/kitty.yaml +15571 -0
  62. wcwidth-0.8.0/ucs-detect/data/konsole.yaml +6187 -0
  63. wcwidth-0.8.0/ucs-detect/data/libvterm.yaml +11631 -0
  64. wcwidth-0.8.0/ucs-detect/data/lxterminal.yaml +12887 -0
  65. wcwidth-0.8.0/ucs-detect/data/mintty.yaml +27871 -0
  66. wcwidth-0.8.0/ucs-detect/data/mlterm.yaml +14706 -0
  67. wcwidth-0.8.0/ucs-detect/data/putty.yaml +10658 -0
  68. wcwidth-0.8.0/ucs-detect/data/qterminal.yaml +8394 -0
  69. wcwidth-0.8.0/ucs-detect/data/rio.yaml +9924 -0
  70. wcwidth-0.8.0/ucs-detect/data/rxvtunicode.yaml +10826 -0
  71. wcwidth-0.8.0/ucs-detect/data/screen.yaml +18026 -0
  72. wcwidth-0.8.0/ucs-detect/data/securecrt.yaml +16450 -0
  73. wcwidth-0.8.0/ucs-detect/data/st.yaml +10759 -0
  74. wcwidth-0.8.0/ucs-detect/data/tabby.yaml +12135 -0
  75. wcwidth-0.8.0/ucs-detect/data/teraterm.yaml +6678 -0
  76. wcwidth-0.8.0/ucs-detect/data/terminalapp.yaml +10895 -0
  77. wcwidth-0.8.0/ucs-detect/data/terminalexe.yaml +5649 -0
  78. wcwidth-0.8.0/ucs-detect/data/terminator.yaml +13065 -0
  79. wcwidth-0.8.0/ucs-detect/data/terminology.yaml +26742 -0
  80. wcwidth-0.8.0/ucs-detect/data/tmux.yaml +6895 -0
  81. wcwidth-0.8.0/ucs-detect/data/vscodeterminal.yaml +11720 -0
  82. wcwidth-0.8.0/ucs-detect/data/warp.yaml +11038 -0
  83. wcwidth-0.8.0/ucs-detect/data/westonterminal.yaml +26416 -0
  84. wcwidth-0.8.0/ucs-detect/data/wezterm.yaml +10565 -0
  85. wcwidth-0.8.0/ucs-detect/data/xfce4terminal.yaml +13020 -0
  86. wcwidth-0.8.0/ucs-detect/data/xterm.yaml +10881 -0
  87. wcwidth-0.8.0/ucs-detect/data/zellij.yaml +12096 -0
  88. wcwidth-0.8.0/ucs-detect/data/zutty.yaml +10797 -0
  89. wcwidth-0.8.0/wcwidth/__init__.py +81 -0
  90. wcwidth-0.8.0/wcwidth/_clip.py +835 -0
  91. wcwidth-0.8.0/wcwidth/_constants.py +151 -0
  92. wcwidth-0.8.0/wcwidth/_wcswidth.py +427 -0
  93. wcwidth-0.8.0/wcwidth/_wcwidth.py +162 -0
  94. wcwidth-0.8.0/wcwidth/_width.py +503 -0
  95. wcwidth-0.8.0/wcwidth/align.py +165 -0
  96. {wcwidth-0.6.0 → wcwidth-0.8.0}/wcwidth/bisearch.py +3 -2
  97. wcwidth-0.8.0/wcwidth/escape_sequences.py +194 -0
  98. {wcwidth-0.6.0 → wcwidth-0.8.0}/wcwidth/grapheme.py +72 -7
  99. wcwidth-0.8.0/wcwidth/hyperlink.py +142 -0
  100. {wcwidth-0.6.0 → wcwidth-0.8.0}/wcwidth/sgr_state.py +2 -1
  101. {wcwidth-0.6.0 → wcwidth-0.8.0}/wcwidth/table_ambiguous.py +1 -1
  102. {wcwidth-0.6.0 → wcwidth-0.8.0}/wcwidth/table_grapheme.py +54 -1
  103. wcwidth-0.8.0/wcwidth/table_grapheme_overrides/__init__.py +37 -0
  104. wcwidth-0.8.0/wcwidth/table_grapheme_overrides/_known_27e0693f.py +2677 -0
  105. wcwidth-0.8.0/wcwidth/table_grapheme_overrides/_known_45d92e98.py +1969 -0
  106. wcwidth-0.8.0/wcwidth/table_grapheme_overrides/_known_4cdf59ce.py +42 -0
  107. wcwidth-0.8.0/wcwidth/table_grapheme_overrides/_known_50bf0759.py +2571 -0
  108. wcwidth-0.8.0/wcwidth/table_grapheme_overrides/_known_529fbb4a.py +1799 -0
  109. wcwidth-0.8.0/wcwidth/table_grapheme_overrides/_known_5bfac390.py +2571 -0
  110. wcwidth-0.8.0/wcwidth/table_grapheme_overrides/_known_813fee16.py +2570 -0
  111. wcwidth-0.8.0/wcwidth/table_grapheme_overrides/_known_8589765c.py +1310 -0
  112. wcwidth-0.8.0/wcwidth/table_grapheme_overrides/_known_8f94b404.py +2581 -0
  113. wcwidth-0.8.0/wcwidth/table_grapheme_overrides/_known_970dbe10.py +2839 -0
  114. wcwidth-0.8.0/wcwidth/table_grapheme_overrides/_known_c0a2cdbf.py +3617 -0
  115. wcwidth-0.8.0/wcwidth/table_grapheme_overrides/_known_c0d5dc9e.py +6427 -0
  116. wcwidth-0.8.0/wcwidth/table_grapheme_overrides/_known_c2157f7e.py +1153 -0
  117. wcwidth-0.8.0/wcwidth/table_grapheme_overrides/_known_c3db41c0.py +3401 -0
  118. wcwidth-0.8.0/wcwidth/table_grapheme_overrides/_known_da9ceb0a.py +1910 -0
  119. wcwidth-0.8.0/wcwidth/table_grapheme_overrides/_known_e08bd75e.py +2641 -0
  120. wcwidth-0.8.0/wcwidth/table_grapheme_overrides/_known_e22030f3.py +6396 -0
  121. wcwidth-0.8.0/wcwidth/table_grapheme_overrides/_known_fcc05a0f.py +6648 -0
  122. wcwidth-0.8.0/wcwidth/table_grapheme_overrides/_known_fd9d4c44.py +3671 -0
  123. wcwidth-0.8.0/wcwidth/table_grapheme_overrides/_registry.py +32 -0
  124. {wcwidth-0.6.0 → wcwidth-0.8.0}/wcwidth/table_mc.py +1 -1
  125. wcwidth-0.8.0/wcwidth/table_overrides.py +714 -0
  126. wcwidth-0.8.0/wcwidth/table_term_programs.py +49 -0
  127. wcwidth-0.8.0/wcwidth/table_vs15.py +104 -0
  128. {wcwidth-0.6.0 → wcwidth-0.8.0}/wcwidth/table_vs16.py +1 -1
  129. {wcwidth-0.6.0 → wcwidth-0.8.0}/wcwidth/table_wide.py +3 -3
  130. {wcwidth-0.6.0 → wcwidth-0.8.0}/wcwidth/table_zero.py +1 -1
  131. wcwidth-0.8.0/wcwidth/text_sizing.py +202 -0
  132. {wcwidth-0.6.0 → wcwidth-0.8.0}/wcwidth/textwrap.py +54 -54
  133. {wcwidth-0.6.0 → wcwidth-0.8.0}/wcwidth/unicode_versions.py +1 -1
  134. wcwidth-0.8.0/wcwidth/wcwidth.py +88 -0
  135. wcwidth-0.6.0/bin/new-wide-by-version.py +0 -48
  136. wcwidth-0.6.0/tests/conftest.py +0 -15
  137. wcwidth-0.6.0/tests/test_clip.py +0 -262
  138. wcwidth-0.6.0/wcwidth/__init__.py +0 -43
  139. wcwidth-0.6.0/wcwidth/escape_sequences.py +0 -69
  140. wcwidth-0.6.0/wcwidth/wcwidth.py +0 -1030
  141. {wcwidth-0.6.0 → wcwidth-0.8.0}/.gitignore +0 -0
  142. {wcwidth-0.6.0 → wcwidth-0.8.0}/LICENSE +0 -0
  143. {wcwidth-0.6.0 → wcwidth-0.8.0}/README.rst +0 -0
  144. {wcwidth-0.6.0 → wcwidth-0.8.0}/code_templates/unicode_version.rst.j2 +0 -0
  145. {wcwidth-0.6.0 → wcwidth-0.8.0}/docs/conf.py +0 -0
  146. {wcwidth-0.6.0 → wcwidth-0.8.0}/docs/index.rst +0 -0
  147. {wcwidth-0.6.0 → wcwidth-0.8.0}/requirements-develop.txt +0 -0
  148. {wcwidth-0.6.0 → wcwidth-0.8.0}/requirements-docs.in +0 -0
  149. {wcwidth-0.6.0 → wcwidth-0.8.0}/tests/__init__.py +0 -0
  150. {wcwidth-0.6.0 → wcwidth-0.8.0}/wcwidth/control_codes.py +0 -0
  151. {wcwidth-0.6.0 → wcwidth-0.8.0}/wcwidth/py.typed +0 -0
@@ -26,6 +26,8 @@ disable=
26
26
  too-many-boolean-expressions,
27
27
  redundant-u-string-prefix,
28
28
  consider-using-f-string,
29
+ consider-using-max-builtin,
30
+ consider-using-min-builtin,
29
31
 
30
32
  [FORMAT]
31
33
  max-line-length: 100
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wcwidth
3
- Version: 0.6.0
3
+ Version: 0.8.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>
@@ -20,6 +20,7 @@ Classifier: Programming Language :: Python :: 3.11
20
20
  Classifier: Programming Language :: Python :: 3.12
21
21
  Classifier: Programming Language :: Python :: 3.13
22
22
  Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Programming Language :: Python :: 3.15
23
24
  Classifier: Topic :: Software Development :: Internationalization
24
25
  Classifier: Topic :: Software Development :: Libraries
25
26
  Classifier: Topic :: Software Development :: Localization
@@ -28,6 +29,7 @@ Classifier: Typing :: Typed
28
29
  Requires-Python: >=3.8
29
30
  Description-Content-Type: text/x-rst
30
31
 
32
+
31
33
  |pypi_downloads| |codecov| |license|
32
34
 
33
35
  ============
@@ -65,33 +67,49 @@ Some examples of **incorrect results**:
65
67
  Solution
66
68
  --------
67
69
 
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.
70
+ The lowest-level functions in this library are derived from POSIX.1-2001 and POSIX.1-2008
71
+ `wcwidth(3)`_ and `wcswidth(3)`_, which this library precisely copies by interface as `wcwidth()`_
72
+ and `wcswidth()`_. These functions return -1 when C0 and C1 control codes are present.
71
73
 
72
74
  An easy-to-use `width()`_ function is provided as a wrapper of `wcswidth()`_ that is also capable of
73
75
  measuring most terminal control codes and sequences, like colors, bold, tabstops, and horizontal
74
- cursor movement.
76
+ cursor movement. `width()`_ argument ``term_program`` may provide more accurate terminal measurement
77
+ Corrections_ as a wrapper of `wcstwidth()`_.
78
+
79
+ Text-justification is solved by the sequence-aware functions `ljust()`_, `rjust()`_, `center()`_,
80
+ and the grapheme-aware function `wrap()`_, serving as drop-in replacements to python standard
81
+ functions.
75
82
 
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.
83
+ The `clip()`_ function extracts substrings by their displayed column positions, and
84
+ `strip_sequences()`_ removes terminal escape sequences from text altogether.
79
85
 
80
86
  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.
87
+ grapheme and terminal control sequence boundaries as required by editors or REPLs with cursor
88
+ control. `iter_graphemes_reverse()`_ and `grapheme_boundary_before()`_ are necessary for backward
89
+ cursor control over complex unicode.
85
90
 
86
91
  Discrepancies
87
92
  -------------
88
93
 
89
94
  You may find that support *varies* for complex unicode sequences or codepoints.
90
95
 
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,
96
+ This library may be considered to presume the terminal is enabled for DEC Private Mode 2027
97
+ ("Grapheme Clustering") by default, which may require to be enabled by a TUI application but
98
+ is often the default mode for those terminals that support it: Windows Terminal, WezTerm, ghostty,
99
+ contour, and foot. This library does support any specific "legacy width" measurement, but does
100
+ provide Corrections_ for those terminals without grapheme support.
101
+
102
+ See also:
103
+
104
+ - `Grapheme Clusters and Terminal Emulators`_
105
+ - `terminal-unicode-core.tex`_
106
+ - `State of Terminal Emulators in 2025`_
107
+ - `Report of terminals supporting Graphemes (2027)`_
108
+
109
+ The `jquast/ucs-detect`_ project publishes automatic results of compliance to our standard for Wide
110
+ character, Languages, grapheme clustering, complex or combining scripts, emojis, zero-width joiner,
93
111
  variations, and regional indicator (flags) as a `General Tabulated Summary`_ by terminal emulator
94
- software and version.
112
+ software and version. The results of the ucs-detect project create our correction tables.
95
113
 
96
114
  ========
97
115
  Overview
@@ -138,40 +156,95 @@ Use function `wcswidth()`_ to determine the length of many, a *string of unicode
138
156
  See specification_ of character measurements. Note that ``-1`` is returned if control codes occurs
139
157
  anywhere in the string.
140
158
 
159
+ wcstwidth()
160
+ -----------
161
+
162
+ Same behavior as `wcswidth()`_ with automatic terminal-specific Corrections_, reading
163
+ ``TERM_PROGRAM`` or ``TERM`` when ``True`` (default), or caller can provide terminal query
164
+ XTVERSION_ or ENQ_ response:
165
+
166
+ .. code-block:: python
167
+
168
+ >>> # '♀️' emoji w/vs-16, uncorrected:
169
+ >>> wcwidth.wcswidth('\u2640\ufe0f')
170
+ 2
171
+ >>> # corrected,
172
+ >>> wcwidth.wcstwidth('\u2640\ufe0f', term_program='vte')
173
+ 1
174
+
141
175
  width()
142
176
  -------
143
177
 
144
- Use function `width()`_ to measure a string with improved handling of ``control_codes``.
178
+ Use function `width()`_ to measure a string with improved handling of ``control_codes`` and
179
+ measurement Corrections_ through ``term_program``:.
145
180
 
146
181
  .. code-block:: python
147
182
 
148
183
  >>> # same support as wcswidth(), eg. regional indicator flag:
149
184
  >>> wcwidth.width('\U0001F1FF\U0001F1FC')
150
185
  2
151
- >>> # but also supports SGR colored text, 'WARN', followed by SGR reset
186
+ >>> # set term_program=True to use wcstwidth()
187
+ >>> wcwidth.width('\U0001F1FF\U0001F1FC', term_program=True)
188
+ 1
189
+ >>> # or set term_program for measurement of a specific terminal
190
+ >>> wcwidth.width('\U0001F1FF\U0001F1FC', term_program='contour')
191
+ 2
192
+ >>> # but also supports sequences, like SGR colored text, "WARN", followed by reset
152
193
  >>> wcwidth.width('\x1b[38;2;255;150;100mWARN\x1b[0m')
153
194
  4
154
- >>> # tabs,
195
+ >>> # tabs are measured as though the string begins at a tabstop,
155
196
  >>> wcwidth.width('\t', tabsize=4)
156
197
  4
157
- >>> # or, tab and all other control characters can be ignored
158
- >>> wcwidth.width('\t', control_codes='ignore')
198
+ >>> # or, all control characters can be ignored (including tab)
199
+ >>> wcwidth.width('\t\n\a\r', control_codes='ignore')
159
200
  0
160
- >>> # "vertical" control characters are ignored
161
- >>> wcwidth.width('\n')
162
- 0
163
- >>> # as well as sequences with "indeterminate" effects like Home + Clear
201
+ >>> # sequences with "indeterminate" effects like Home + Clear are zero-width
164
202
  >>> wcwidth.width('\x1b[H\x1b[2J')
165
203
  0
204
+ >>> # horizontal cursor movements are parsed,
205
+ >>> wcwidth.width('hello\b\b\b\b\bworld')
206
+ 5
207
+ >>> wcwidth.width('hello\x1b[5Dworld')
208
+ 5
209
+ >>> # or ignored,
210
+ >>> wcwidth.width('hello\x1b[5Dworld', control_codes='ignore')
211
+ 10
212
+ >>> # Measure width of text using kitty text sizing protocol (OSC 66),
213
+ >>> width('\x1b]66;w=2;XY\x07')
214
+ 2
215
+ >>> # Scaled text sizing: each grapheme occupies 'scale' cells
216
+ >>> width('\x1b]66;s=2;ABC\x07')
217
+ 6
218
+
219
+ Use ``control_codes='ignore'`` when the input is known not to contain any control characters or
220
+ terminal sequences for slightly improved performance. Note that TAB (``'\t'``) is a control
221
+ character and is also ignored, you may want to use `str.expandtabs()`_, first.
222
+
223
+ Use ``control_codes='strict'`` when input is known to contain some control sequences, such as
224
+ SGR color, bold, hyperlinks and cursor movement. Any sequence that cannot be accurately parsed
225
+ for horizontal measurement, such as clearing the screen, vertical, or absolute cursor movement will
226
+ raise ``ValueError``:
227
+
228
+ .. code-block:: python
229
+
166
230
  >>> # or, raise ValueError for "indeterminate" effects using control_codes='strict'
167
231
  >>> wcwidth.width('\n', control_codes='strict')
168
232
  Traceback (most recent call last):
169
233
  ...
170
234
  ValueError: Vertical movement character 0xa at position 0
171
235
 
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.
236
+
237
+ >>> wcwidth.width('\x1b[H\x1b[2J', control_codes='strict')
238
+ Traceback (most recent call last):
239
+ ...
240
+ ValueError: Indeterminate cursor sequence at position 0, '\x1b[H'
241
+
242
+
243
+ >>> # cursor left movement beyond string start raises in strict mode,
244
+ >>> wcwidth.width('a\x1b[5Da', control_codes='strict')
245
+ Traceback (most recent call last):
246
+ ...
247
+ ValueError: Cursor left movement at position 1 would move 5 cells left from column 1, exceeding string start
175
248
 
176
249
  iter_sequences()
177
250
  ----------------
@@ -290,9 +363,29 @@ Use `clip()`_ to extract a substring by column positions, preserving terminal se
290
363
  >>> clip('\x1b[1;31mHello world\x1b[0m', 6, 11)
291
364
  '\x1b[1;31mworld\x1b[0m'
292
365
 
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'
366
+ >>> # Disable SGR propagation to preserve sequence order outside of clip boundary
367
+ >>> clip('\x1b[31m中文\x1b[32m', 0, 3, propagate_sgr=False)
368
+ '\x1b[31m中 \x1b[32m'
369
+
370
+ >>> # Cursor-left overwrites previous text (painter's algorithm)
371
+ >>> clip('hello\x1b[2DXY', 0, 5)
372
+ 'helXY'
373
+ >>> # Carriage return resets to column 0, overwriting earlier cells
374
+ >>> clip('abc\rXY', 0, 5)
375
+ 'XYc'
376
+
377
+ >>> # even OSC 8 hyperlink text may be clipped, 'Click This link' -> 'is link' !
378
+ >>> clip('\x1b]8;;http://example.com\x07Click This link\x1b]8;;\x07', 8, 15)
379
+ '\x1b]8;;http://example.com\x07is link\x1b]8;;\x07'
380
+
381
+ >>> # and OSC 66 kitty text sizing, supporting width and scale, 'Look' -> '...ook'
382
+ >>> clip('\x1b]66;w=4:s=4;Look\x07', 1, 16, fillchar='.')
383
+ '...\x1b]66;s=4:w=3;ook\x07'
384
+
385
+ Use ``overtyping=False`` when the input is known not to contain any cursor movement characters
386
+ (``\b``, ``\r``, ``CSI C``, ``CSI D``, ``CSI G``) for improved performance. When
387
+ ``overtyping=None`` (default), a slower "Painter's algorithm" may be used after testing for the
388
+ presence of these characters. ``overtyping`` has no effect when ``control_codes='ignore'``.
296
389
 
297
390
  strip_sequences()
298
391
  -----------------
@@ -307,7 +400,7 @@ Use `strip_sequences()`_ to remove all terminal escape sequences from text.
307
400
 
308
401
  .. _ambiguous_width:
309
402
 
310
- ambiguous_width
403
+ Ambiguous Width
311
404
  ---------------
312
405
 
313
406
  Some Unicode characters have "East Asian Ambiguous" (A) width. These characters display as 1 cell by
@@ -315,6 +408,9 @@ default, matching Western terminal contexts, but many CJK (Chinese, Japanese, Ko
315
408
  may have a preference for 2 cells. This is often found as boolean option, "Ambiguous width as wide"
316
409
  in Terminal Emulator software preferences.
317
410
 
411
+ The ``ambiguous_width`` parameter is available on all width-measuring functions: `wcwidth()`_,
412
+ `wcswidth()`_, `width()`_, `ljust()`_, `rjust()`_, `center()`_, `wrap()`_, and `clip()`_.
413
+
318
414
  By default, wcwidth treats ambiguous characters as narrow (width 1). For CJK environments where your
319
415
  terminal is configured to display ambiguous characters as double-width, pass ``ambiguous_width=2``:
320
416
 
@@ -326,9 +422,6 @@ terminal is configured to display ambiguous characters as double-width, pass ``a
326
422
  >>> wcwidth.width('\u2460', ambiguous_width=2)
327
423
  2
328
424
 
329
- The ``ambiguous_width`` parameter is available on all width-measuring functions: `wcwidth()`_,
330
- `wcswidth()`_, `width()`_, `ljust()`_, `rjust()`_, `center()`_, `wrap()`_, and `clip()`_.
331
-
332
425
  **Terminal Detection**
333
426
 
334
427
  The most reliable method to detect whether a terminal profile is set for "Ambiguous width as wide"
@@ -336,7 +429,7 @@ mode is to display an ambiguous character surrounded by a pair of Cursor Positio
336
429
  queries with a terminal in cooked or raw mode, and to parse the responses for their ``(y, x)``
337
430
  locations and measure the difference ``x``.
338
431
 
339
- This code should also be careful check whether it is attached to a terminal and be careful of
432
+ This code should also be careful to check whether it is attached to a terminal and be careful of
340
433
  possible timeout, slow network, or non-response when working with "dumb terminals" like a CI build.
341
434
 
342
435
  `jquast/blessed`_ library provides such a helping `Terminal.detect_ambiguous_width()`_ method:
@@ -352,6 +445,90 @@ possible timeout, slow network, or non-response when working with "dumb terminal
352
445
  >>> awidth('\u2460')
353
446
  1
354
447
 
448
+ Corrections
449
+ -----------
450
+
451
+ Corrections are automatically applied depending on detected or given terminal software name
452
+ beginning with wcwidth release 0.8.0. This allows to correct widths for terminal software that
453
+ differs from the standard. These corrections are sourced from the `jquast/ucs-detect`_ project.
454
+
455
+ The ``term_program`` parameter is available on all width-measuring functions: `wcstwidth()`_,
456
+ `width()`_, `ljust()`_, `rjust()`_, `center()`_, `wrap()`_, and `clip()`_.
457
+
458
+ `wcstwidth()`_ defaults to ``term_program=True``, auto-detecting the terminal from the
459
+ ``TERM_PROGRAM`` or ``TERM`` environment variable. All other functions default to
460
+ ``term_program=False``, disabling corrections. Use ``term_program=True`` for automatic
461
+ detection by environment values of ``TERM`` and ``TERM_PROGRAM``.
462
+
463
+ .. code-block:: python
464
+
465
+ # VTE terminals (Gnome Terminal Et al.) still render trigrams as narrow (1 cell), but their
466
+ # definition was changed to wide in Unicode 16 (September 2024).
467
+ >>> wcwidth.wcswidth('\u2630')
468
+ 2
469
+ >>> wcwidth.wcstwidth('\u2630', term_program='vte')
470
+ 1
471
+
472
+ # account for Alacritty non-support of emoji ZWJ:
473
+ # man + ZWJ + woman + ZWJ + girl + ZWJ + boy
474
+ >>> family = '\U0001F468\u200D\U0001F469\u200D\U0001F467\u200D\U0001F466'
475
+ >>> wcwidth.wcswidth(family)
476
+ 2
477
+ >>> wcwidth.wcstwidth(family, term_program='alacritty')
478
+ 8
479
+
480
+ Only detectable_ terminals are included: those that identify themselves by XTVERSION_, ENQ_, any
481
+ ``TERM_PROGRAM`` or a unique ``TERM`` environment value. For the most accurate correction tables,
482
+ query the terminal's software version via XTVERSION_ (``CSI > q``) using a higher-level interactive
483
+ terminal library like `jquast/blessed`_:
484
+
485
+ .. code-block:: python
486
+
487
+ >>> import blessed, wcwidth
488
+ >>> term = blessed.Terminal()
489
+ >>> sw_ver = term.get_software_version()
490
+ >>> print(sw_ver)
491
+ SoftwareVersion(name='VTE', version='7600')
492
+ >>> wcwidth.width('\u2630', term_program=sw_ver.name)
493
+ 1
494
+
495
+ This is important because ``TERM_PROGRAM`` is not forwarded for remote hosts, like SSH, and many
496
+ terminals may only be identified using XTVERSION_ or ENQ_. Use `list_term_programs()`_ to see all
497
+ recognized names:
498
+
499
+ .. BEGIN_LIST_TERM_PROGRAMS
500
+ .. code-block:: python
501
+
502
+ >>> wcwidth.list_term_programs()
503
+ ('alacritty', 'apple_terminal', 'bobcat', 'contour', 'extraterm', 'foot',
504
+ 'ghostty', 'hyper', 'iterm.app', 'iterm2', 'kitty', 'konsole', 'mintty',
505
+ 'mlterm', 'pterm', 'putty', 'rio', 'rxvt', 'rxvt-unicode-256color', 'st',
506
+ 'st-256color', 'tabby', 'terminology', 'urxvt', 'vscode', 'vte', 'warp',
507
+ 'warpterminal', 'wezterm', 'xterm', 'xterm-ghostty', 'xterm-kitty',
508
+ 'xterm.js')
509
+
510
+ .. END_LIST_TERM_PROGRAMS
511
+
512
+ ``term_program=False`` (the default for `width()`_, `ljust()`_, `rjust()`_, `center()`_,
513
+ `wrap()`_, and `clip()`_) disables terminal corrections. `wcstwidth()`_ defaults to
514
+ ``term_program=True`` for auto-detection.
515
+
516
+ For automatic tests and other purposes that require cross-environment consistency, set static values
517
+ or unset ``TERM`` and ``TERM_PROGRAM`` environment values, such as in ``conftest.py`` with pytest:
518
+
519
+ .. code-block:: python
520
+
521
+ @pytest.fixture(autouse=True)
522
+ def _clear_term_program():
523
+ """unset TERM/TERM_PROGRAM before each test."""
524
+ saved_term = os.environ.pop('TERM', None)
525
+ saved_tprog = os.environ.pop('TERM_PROGRAM', None)
526
+ yield
527
+ if saved_term is not None:
528
+ os.environ['TERM'] = saved_term
529
+ if saved_tprog is not None:
530
+ os.environ['TERM_PROGRAM'] = saved_tprog
531
+
355
532
  ==========
356
533
  Developing
357
534
  ==========
@@ -429,9 +606,18 @@ This library is used in:
429
606
  - `jquast/blessed`_: a thin, practical wrapper around terminal capabilities in
430
607
  Python.
431
608
 
609
+ - `jquast/telix`_: A Modern telnet client especially designed for BBSs and MUDs.
610
+
432
611
  - `prompt-toolkit/python-prompt-toolkit`_: a Library for building powerful
433
612
  interactive command lines in Python.
434
613
 
614
+ - `urwid/urwid`_: Console user interface library for Python
615
+
616
+ - `prettytable/prettytable`_: Display tabular data in a visually appealing ASCII table format
617
+
618
+ - `leviathan0992/Pylsy`_: Pylsy is a simple python library draw tables in the Terminal. Just two
619
+ lines of code.
620
+
435
621
  - `dbcli/pgcli`_: Postgres CLI with autocompletion and syntax highlighting.
436
622
 
437
623
  - `thomasballinger/curtsies`_: a Curses-like terminal wrapper with a display
@@ -448,8 +634,8 @@ This library is used in:
448
634
  - `nbedos/termtosvg`_: Terminal recorder that renders sessions as SVG
449
635
  animations.
450
636
 
451
- - `peterbrittain/asciimatics`_: Package to help people create full-screen text
452
- UIs.
637
+ - `peterbrittain/asciimatics`_: A cross platform package to do curses-like operations, plus higher
638
+ level APIs and widgets to create text UIs and ASCII art animations
453
639
 
454
640
  - `python-cmd2/cmd2`_: A tool for building interactive command line apps
455
641
 
@@ -466,9 +652,18 @@ This library is used in:
466
652
  Other Languages
467
653
  ===============
468
654
 
469
- There are similar implementations of the `wcwidth()`_ and `wcswidth()`_ functions in other
470
- languages.
655
+ The following libraries provide grapheme and emoji support and closely align with our
656
+ specification_:
657
+
658
+ - `jacobsandlund/uucode`_ Zig
659
+ - `contour-terminal/libunicode`_ C++20
471
660
 
661
+ There are similar implementations of at least the `wcwidth()`_ and `wcswidth()`_ functions in other
662
+ languages:
663
+
664
+ - `ridiculousfish/widecharwidth`_: Python
665
+ - `termux/wcwidth`_: C
666
+ - `powerman/wcwidth-icons`_: C
472
667
  - `timoxley/wcwidth`_: JavaScript
473
668
  - `janlelis/unicode-display_width`_: Ruby
474
669
  - `alecrabbit/php-wcwidth`_: PHP
@@ -478,6 +673,9 @@ languages.
478
673
  - `grepsuzette/wcwidth`_: Haxe
479
674
  - `aperezdc/lua-wcwidth`_: Lua
480
675
  - `joachimschmidt557/zig-wcwidth`_: Zig
676
+ - `mycoboco/wcwidth.js`_: JavaScript
677
+ - `ainame/swift-displaywidth`_: Swift
678
+ - `pmonks/clj-wcwidth`_: Clojure
481
679
  - `fumiyas/wcwidth-cjk`_: `LD_PRELOAD` override
482
680
  - `joshuarubin/wcwidth9`_: Unicode version 9 in C
483
681
  - `spectreconsole/wcwidth`_: C#
@@ -486,6 +684,29 @@ languages.
486
684
  History
487
685
  =======
488
686
 
687
+ 0.8.0 *(unreleased)*
688
+ * **New** support for Variation Selector 15 Emojis as narrow, `Issue #211`_.
689
+ * **New** argument, ``term_program`` for `wcstwidth()`_, `width()`_, `clip()`_, `wrap()`_,
690
+ `ljust()`_, `rjust()`_, and `center()`_. ``False`` disables corrections; ``True``
691
+ auto-detects by ``TERM_PROGRAM`` or ``TERM``; string values accept canonical names matching
692
+ `list_term_programs()`_. `wcstwidth()`_ defaults to ``True``; all other functions
693
+ default to ``False``.
694
+ * **Improved** performance on Python 3.15 using standard library iter_graphemes() `PR #206`_.
695
+ * **Improved** memory usage and import time for Python 3.15 using lazy imports `PR #221`_.
696
+ * **Bugfix** Invisible_Stacker viramas now form conjuncts (Burmese, Khmer, etc.) and
697
+ change some Virama width calculations to match `jacobsandlund/uucode`_ (ghostty) `PR #223`_.
698
+ * **Updated** graphemes width maximum now 2, matching Ghostty, foot, and Windows Terminal `PR
699
+ #224`_.
700
+
701
+ 0.7.0 *2026-05-02*
702
+ * **New** support for `kitty text sizing protocol`_ (OSC 66) in `width()`_ and `clip()`_.
703
+ * **New** `clip()`_ parameter ``control_codes='parse'``, ``'ignore'``, and ``'strict'``. `clip()`_
704
+ is now able to clip OSC 8 hyperlinks and OSC 66 text sizing sequences.
705
+ * **Improved** `clip()`_ and `width()`_ to support horizontal cursor sequences (``cub``, ``cuf``,
706
+ ``hpa``). Cursor-left (``cub``) or backspace (``\b``) now overwrites text. ``column_address``
707
+ (``hpa``) and carriage return (``\r``) are now parsed, and more values conditionally raise
708
+ ``ValueError`` when ``control_codes='strict'``.
709
+
489
710
  0.6.0 *2026-02-06*
490
711
  * **New** Parameters ``expand_tabs``, ``replace_whitespace``, ``fix_sentence_endings``,
491
712
  ``drop_whitespace``, ``max_lines``, and ``placeholder`` for `wrap()`_, completing stdlib
@@ -707,10 +928,16 @@ https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c::
707
928
  .. _`PR #200`: https://github.com/jquast/wcwidth/pull/200
708
929
  .. _`PR #202`: https://github.com/jquast/wcwidth/pull/202
709
930
  .. _`PR #204`: https://github.com/jquast/wcwidth/pull/204
931
+ .. _`PR #206`: https://github.com/jquast/wcwidth/pull/206
932
+ .. _`PR #221`: https://github.com/jquast/wcwidth/pull/221
933
+ .. _`PR #223`: https://github.com/jquast/wcwidth/pull/223
934
+ .. _`PR #224`: https://github.com/jquast/wcwidth/pull/224
710
935
  .. _`Issue #101`: https://github.com/jquast/wcwidth/issues/101
711
936
  .. _`Issue #155`: https://github.com/jquast/wcwidth/issues/155
712
937
  .. _`Issue #190`: https://github.com/jquast/wcwidth/issues/190
938
+ .. _`Issue #211`: https://github.com/jquast/wcwidth/issues/211
713
939
  .. _`jquast/blessed`: https://github.com/jquast/blessed
940
+ .. _`jquast/telix`: https://github.com/jquast/telix
714
941
  .. _`selectel/pyte`: https://github.com/selectel/pyte
715
942
  .. _`thomasballinger/curtsies`: https://github.com/thomasballinger/curtsies
716
943
  .. _`dbcli/pgcli`: https://github.com/dbcli/pgcli
@@ -735,10 +962,21 @@ https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c::
735
962
  .. _`fumiyas/wcwidth-cjk`: https://github.com/fumiyas/wcwidth-cjk
736
963
  .. _`joshuarubin/wcwidth9`: https://github.com/joshuarubin/wcwidth9
737
964
  .. _`spectreconsole/wcwidth`: https://github.com/spectreconsole/wcwidth
965
+ .. _`contour-terminal/libunicode`: https://github.com/contour-terminal/libunicode
966
+ .. _`jacobsandlund/uucode`: https://github.com/jacobsandlund/uucode
967
+ .. _`ridiculousfish/widecharwidth`: https://github.com/ridiculousfish/widecharwidth
968
+ .. _`termux/wcwidth`: https://github.com/termux/wcwidth
969
+ .. _`powerman/wcwidth-icons`: https://github.com/powerman/wcwidth-icons
970
+ .. _`mycoboco/wcwidth.js`: https://github.com/mycoboco/wcwidth.js
971
+ .. _`ainame/swift-displaywidth`: https://github.com/ainame/swift-displaywidth
972
+ .. _`pmonks/clj-wcwidth`: https://github.com/pmonks/clj-wcwidth
738
973
  .. _`python-cmd2/cmd2`: https://github.com/python-cmd2/cmd2
739
974
  .. _`stratis-storage/stratis-cli`: https://github.com/stratis-storage/stratis-cli
740
975
  .. _`ihabunek/toot`: https://github.com/ihabunek/toot
741
976
  .. _`saulpw/visidata`: https://github.com/saulpw/visidata
977
+ .. _`urwid/urwid`: https://github.com/urwid/urwid
978
+ .. _`prettytable/prettytable`: https://github.com/prettytable/prettytable
979
+ .. _`leviathan0992/Pylsy`: https://github.com/leviathan0992/Pylsy
742
980
  .. _`pip-tools`: https://pip-tools.readthedocs.io/
743
981
  .. _`sphinx`: https://www.sphinx-doc.org/
744
982
  .. _`textwrap.wrap()`: https://docs.python.org/3/library/textwrap.html#textwrap.wrap
@@ -749,6 +987,7 @@ https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c::
749
987
  .. _`General Tabulated Summary`: https://ucs-detect.readthedocs.io/results.html#tabulated-results
750
988
  .. _`wcwidth()`: https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.wcwidth
751
989
  .. _`wcswidth()`: https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.wcswidth
990
+ .. _`wcstwidth()`: https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.wcstwidth
752
991
  .. _`width()`: https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.width
753
992
  .. _`iter_graphemes()`: https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.iter_graphemes
754
993
  .. _`iter_graphemes_reverse()`: https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.iter_graphemes_reverse
@@ -760,11 +999,22 @@ https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c::
760
999
  .. _`clip()`: https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.clip
761
1000
  .. _`strip_sequences()`: https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.strip_sequences
762
1001
  .. _`propagate_sgr()`: https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.propagate_sgr
1002
+ .. _`TextSizing`: https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.TextSizing
1003
+ .. _`TextSizingParams`: https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.TextSizingParams
763
1004
  .. _`iter_sequences()`: https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.iter_sequences
764
1005
  .. _`list_versions()`: https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.list_versions
1006
+ .. _`list_term_programs()`: https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.list_term_programs
765
1007
  .. _`Unicode Standard Annex #29`: https://www.unicode.org/reports/tr29/
766
1008
  .. _`Terminal.detect_ambiguous_width()`: https://blessed.readthedocs.io/en/latest/api/terminal.html#blessed.terminal.Terminal.detect_ambiguous_width
767
1009
  .. _`parity padding`: https://jazcap53.github.io/pythons-eccentric-strcenter.html
1010
+ .. _`kitty text sizing protocol`: https://sw.kovidgoyal.net/kitty/text-sizing-protocol/
1011
+ .. _`Grapheme Clusters and Terminal Emulators`: https://mitchellh.com/writing/grapheme-clusters-in-terminals
1012
+ .. _`terminal-unicode-core.tex`: https://github.com/contour-terminal/terminal-unicode-core/blob/master/spec/terminal-unicode-core.tex
1013
+ .. _`State of Terminal Emulators in 2025`: https://www.jeffquast.com/post/state-of-terminal-emulation-2025/
1014
+ .. _`Report of terminals supporting Graphemes (2027)`: https://ucs-detect.readthedocs.io/results.html#terminal-features
1015
+ .. _XTVERSION: https://vtdn.dev/docs/dcs/xtversion/
1016
+ .. _ENQ: https://documentation.help/PuTTY/config-answerback.html
1017
+ .. _detectable: https://ucs-detect.readthedocs.io/results.html#terminal-identification
768
1018
  .. |pypi_downloads| image:: https://img.shields.io/pypi/dm/wcwidth.svg?logo=pypi
769
1019
  :alt: Downloads
770
1020
  :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))