wcwidth.py 1.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455
  1. import unicodedata
  2. from functools import lru_cache
  3. @lru_cache(100)
  4. def wcwidth(c: str) -> int:
  5. """Determine how many columns are needed to display a character in a terminal.
  6. Returns -1 if the character is not printable.
  7. Returns 0, 1 or 2 for other characters.
  8. """
  9. o = ord(c)
  10. # ASCII fast path.
  11. if 0x20 <= o < 0x07F:
  12. return 1
  13. # Some Cf/Zp/Zl characters which should be zero-width.
  14. if (
  15. o == 0x0000
  16. or 0x200B <= o <= 0x200F
  17. or 0x2028 <= o <= 0x202E
  18. or 0x2060 <= o <= 0x2063
  19. ):
  20. return 0
  21. category = unicodedata.category(c)
  22. # Control characters.
  23. if category == "Cc":
  24. return -1
  25. # Combining characters with zero width.
  26. if category in ("Me", "Mn"):
  27. return 0
  28. # Full/Wide east asian characters.
  29. if unicodedata.east_asian_width(c) in ("F", "W"):
  30. return 2
  31. return 1
  32. def wcswidth(s: str) -> int:
  33. """Determine how many columns are needed to display a string in a terminal.
  34. Returns -1 if the string contains non-printable characters.
  35. """
  36. width = 0
  37. for c in unicodedata.normalize("NFC", s):
  38. wc = wcwidth(c)
  39. if wc < 0:
  40. return -1
  41. width += wc
  42. return width