|
1 | 1 | from __future__ import annotations |
2 | 2 |
|
3 | 3 | import math |
| 4 | +import unicodedata |
4 | 5 |
|
5 | 6 | from dataclasses import dataclass |
6 | 7 | from difflib import SequenceMatcher |
| 8 | +from functools import lru_cache |
7 | 9 | from html.parser import HTMLParser |
8 | 10 |
|
9 | 11 |
|
@@ -105,3 +107,56 @@ def format_time(secs: float) -> str: |
105 | 107 | (fmt for fmt in _TIME_FORMATS if secs < fmt.threshold), _TIME_FORMATS[-1] |
106 | 108 | ) |
107 | 109 | return time_format.apply(secs) |
| 110 | + |
| 111 | + |
| 112 | +@lru_cache(100) |
| 113 | +def wcwidth(c: str) -> int: |
| 114 | + """Determine how many columns are needed to display a character in a terminal. |
| 115 | +
|
| 116 | + Returns -1 if the character is not printable. |
| 117 | + Returns 0, 1 or 2 for other characters. |
| 118 | + """ |
| 119 | + o = ord(c) |
| 120 | + |
| 121 | + # ASCII fast path. |
| 122 | + if 0x20 <= o < 0x07F: |
| 123 | + return 1 |
| 124 | + |
| 125 | + # Some Cf/Zp/Zl characters which should be zero-width. |
| 126 | + if ( |
| 127 | + o == 0x0000 |
| 128 | + or 0x200B <= o <= 0x200F |
| 129 | + or 0x2028 <= o <= 0x202E |
| 130 | + or 0x2060 <= o <= 0x2063 |
| 131 | + ): |
| 132 | + return 0 |
| 133 | + |
| 134 | + category = unicodedata.category(c) |
| 135 | + |
| 136 | + # Control characters. |
| 137 | + if category == "Cc": |
| 138 | + return -1 |
| 139 | + |
| 140 | + # Combining characters with zero width. |
| 141 | + if category in ("Me", "Mn"): |
| 142 | + return 0 |
| 143 | + |
| 144 | + # Full/Wide east asian characters. |
| 145 | + if unicodedata.east_asian_width(c) in ("F", "W"): |
| 146 | + return 2 |
| 147 | + |
| 148 | + return 1 |
| 149 | + |
| 150 | + |
| 151 | +def wcswidth(s: str) -> int: |
| 152 | + """Determine how many columns are needed to display a string in a terminal. |
| 153 | +
|
| 154 | + Returns -1 if the string contains non-printable characters. |
| 155 | + """ |
| 156 | + width = 0 |
| 157 | + for c in unicodedata.normalize("NFC", s): |
| 158 | + wc = wcwidth(c) |
| 159 | + if wc < 0: |
| 160 | + return -1 |
| 161 | + width += wc |
| 162 | + return width |
0 commit comments