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
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
1from __future__ import annotations
3from functools import lru_cache
4from typing import Callable
6from ._cell_widths import CELL_WIDTHS
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]
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)
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
33@lru_cache(4096)
34def cached_cell_len(text: str) -> int:
35 """Get the number of cells required to display text.
37 This method always caches, which may use up a lot of memory. It is recommended to use
38 `cell_len` over this method.
40 Args:
41 text (str): Text to display.
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))
51def cell_len(text: str, _cell_len: Callable[[str], int] = cached_cell_len) -> int:
52 """Get the number of cells required to display text.
54 Args:
55 text (str): Text to display.
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))
67@lru_cache(maxsize=4096)
68def get_character_cell_size(character: str) -> int:
69 """Get the cell size of a character.
71 Args:
72 character (str): A single character.
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
96def set_cell_size(text: str, total: int) -> str:
97 """Set the length of a string to fit within given number of cells."""
99 if _is_single_cell_widths(text):
100 size = len(text)
101 if size < total:
102 return text + " " * (total - size)
103 return text[:total]
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)
113 start = 0
114 end = len(text)
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
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.
137 Args:
138 text: The text to fold such that it fits in the given width.
139 width: The width available (number of cells).
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]] = [[]]
148 append_new_line = lines.append
149 append_to_last_line = lines[-1].append
151 total_width = 0
153 for character in text:
154 cell_width = _get_character_cell_size(character)
155 char_doesnt_fit = total_width + cell_width > width
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
165 return ["".join(line) for line in lines]
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)