1 import unicodedata

2 from functools import lru_cache

3

4

5@lru_cache(100)

6 def wcwidth(c: str) -> int:

7 """Determine how many columns are needed to display a character in a terminal.

8

9 Returns -1 if the character is not printable.

10 Returns 0, 1 or 2 for other characters.

11 """

12 o = ord(c)

13

14 # ASCII fast path.

15 if 0x20 <= o < 0x07F:

16 return 1

17

18 # Some Cf/Zp/Zl characters which should be zero-width.

19 if (

20 o == 0x0000

21 or 0x200B <= o <= 0x200F

22 or 0x2028 <= o <= 0x202E

23 or 0x2060 <= o <= 0x2063

24 ):

25 return 0

26

27 category = unicodedata.category(c)

28

29 # Control characters.

30 if category == "Cc":

31 return -1

32

33 # Combining characters with zero width.

34 if category in ("Me", "Mn"):

35 return 0

36

37 # Full/Wide east asian characters.

38 if unicodedata.east_asian_width(c) in ("F", "W"):

39 return 2

40

41 return 1

42

43

44 def wcswidth(s: str) -> int:

45 """Determine how many columns are needed to display a string in a terminal.

46

47 Returns -1 if the string contains non-printable characters.

48 """

49 width = 0

50 for c in unicodedata.normalize("NFC", s):

51 wc = wcwidth(c)

52 if wc < 0:

53 return -1

54 width += wc

55 return width