Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/rich/cells.py: 86%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

78 statements  

1from __future__ import annotations 

2 

3from functools import lru_cache 

4from typing import Callable 

5 

6from ._cell_widths import CELL_WIDTHS 

7 

8# Ranges of unicode ordinals that produce a 1-cell wide character 

9# This is non-exhaustive, but covers most common Western characters 

10_SINGLE_CELL_UNICODE_RANGES: list[tuple[int, int]] = [ 

11 (0x20, 0x7E), # Latin (excluding non-printable) 

12 (0xA0, 0xAC), 

13 (0xAE, 0x002FF), 

14 (0x00370, 0x00482), # Greek / Cyrillic 

15 (0x02500, 0x025FC), # Box drawing, box elements, geometric shapes 

16 (0x02800, 0x028FF), # Braille 

17] 

18 

19# A set of characters that are a single cell wide 

20_SINGLE_CELLS = frozenset( 

21 [ 

22 character 

23 for _start, _end in _SINGLE_CELL_UNICODE_RANGES 

24 for character in map(chr, range(_start, _end + 1)) 

25 ] 

26) 

27 

28# When called with a string this will return True if all 

29# characters are single-cell, otherwise False 

30_is_single_cell_widths: Callable[[str], bool] = _SINGLE_CELLS.issuperset 

31 

32 

33@lru_cache(4096) 

34def cached_cell_len(text: str) -> int: 

35 """Get the number of cells required to display text. 

36 

37 This method always caches, which may use up a lot of memory. It is recommended to use 

38 `cell_len` over this method. 

39 

40 Args: 

41 text (str): Text to display. 

42 

43 Returns: 

44 int: Get the number of cells required to display text. 

45 """ 

46 if _is_single_cell_widths(text): 

47 return len(text) 

48 return sum(map(get_character_cell_size, text)) 

49 

50 

51def cell_len(text: str, _cell_len: Callable[[str], int] = cached_cell_len) -> int: 

52 """Get the number of cells required to display text. 

53 

54 Args: 

55 text (str): Text to display. 

56 

57 Returns: 

58 int: Get the number of cells required to display text. 

59 """ 

60 if len(text) < 512: 

61 return _cell_len(text) 

62 if _is_single_cell_widths(text): 

63 return len(text) 

64 return sum(map(get_character_cell_size, text)) 

65 

66 

67@lru_cache(maxsize=4096) 

68def get_character_cell_size(character: str) -> int: 

69 """Get the cell size of a character. 

70 

71 Args: 

72 character (str): A single character. 

73 

74 Returns: 

75 int: Number of cells (0, 1 or 2) occupied by that character. 

76 """ 

77 codepoint = ord(character) 

78 _table = CELL_WIDTHS 

79 lower_bound = 0 

80 upper_bound = len(_table) - 1 

81 index = (lower_bound + upper_bound) // 2 

82 while True: 

83 start, end, width = _table[index] 

84 if codepoint < start: 

85 upper_bound = index - 1 

86 elif codepoint > end: 

87 lower_bound = index + 1 

88 else: 

89 return 0 if width == -1 else width 

90 if upper_bound < lower_bound: 

91 break 

92 index = (lower_bound + upper_bound) // 2 

93 return 1 

94 

95 

96def set_cell_size(text: str, total: int) -> str: 

97 """Set the length of a string to fit within given number of cells.""" 

98 

99 if _is_single_cell_widths(text): 

100 size = len(text) 

101 if size < total: 

102 return text + " " * (total - size) 

103 return text[:total] 

104 

105 if total <= 0: 

106 return "" 

107 cell_size = cell_len(text) 

108 if cell_size == total: 

109 return text 

110 if cell_size < total: 

111 return text + " " * (total - cell_size) 

112 

113 start = 0 

114 end = len(text) 

115 

116 # Binary search until we find the right size 

117 while True: 

118 pos = (start + end) // 2 

119 before = text[: pos + 1] 

120 before_len = cell_len(before) 

121 if before_len == total + 1 and cell_len(before[-1]) == 2: 

122 return before[:-1] + " " 

123 if before_len == total: 

124 return before 

125 if before_len > total: 

126 end = pos 

127 else: 

128 start = pos 

129 

130 

131def chop_cells( 

132 text: str, 

133 width: int, 

134) -> list[str]: 

135 """Split text into lines such that each line fits within the available (cell) width. 

136 

137 Args: 

138 text: The text to fold such that it fits in the given width. 

139 width: The width available (number of cells). 

140 

141 Returns: 

142 A list of strings such that each string in the list has cell width 

143 less than or equal to the available width. 

144 """ 

145 _get_character_cell_size = get_character_cell_size 

146 lines: list[list[str]] = [[]] 

147 

148 append_new_line = lines.append 

149 append_to_last_line = lines[-1].append 

150 

151 total_width = 0 

152 

153 for character in text: 

154 cell_width = _get_character_cell_size(character) 

155 char_doesnt_fit = total_width + cell_width > width 

156 

157 if char_doesnt_fit: 

158 append_new_line([character]) 

159 append_to_last_line = lines[-1].append 

160 total_width = cell_width 

161 else: 

162 append_to_last_line(character) 

163 total_width += cell_width 

164 

165 return ["".join(line) for line in lines] 

166 

167 

168if __name__ == "__main__": # pragma: no cover 

169 print(get_character_cell_size("😽")) 

170 for line in chop_cells("""这是对亚洲语言支持的测试。面对模棱两可的想法,拒绝猜测的诱惑。""", 8): 

171 print(line) 

172 for n in range(80, 1, -1): 

173 print(set_cell_size("""这是对亚洲语言支持的测试。面对模棱两可的想法,拒绝猜测的诱惑。""", n) + "|") 

174 print("x" * n)