def truncate_at_space(text: str, width: int) -> Tuple[str, str]: p = truncate_point_for_length(text, width) if p < len(text): i = text.rfind(' ', 0, p + 1) if i > 0 and p - i < 12: p = i + 1 return text[:p], text[p:]
def split_to_size(line, width): if not line: yield line while line: p = truncate_point_for_length(line, width) yield line[:p] line = line[p:]
def split_to_size(line: str, width: int) -> Generator[str, None, None]: if not line: yield line while line: p = truncate_point_for_length(line, width) yield line[:p] line = line[p:]
def write_para(text: str) -> None: text = re.sub(r'\s+', ' ', text) while text: sp = truncate_point_for_length(text, sz) self.write(text[:sp]) next_line() text = text[sp:]
def truncate_points(line: str, width: int) -> Generator[int, None, None]: pos = 0 sz = len(line) while True: pos = truncate_point_for_length(line, width, pos) if pos < sz: yield pos else: break
def truncate_points(line, width): pos = 0 sz = len(line) while True: pos = truncate_point_for_length(line, width, pos) if pos < sz: yield pos else: break
def add_text(self, text: str) -> None: if self.current_input: x = truncate_point_for_length( self.current_input, self.cursor_pos) if self.cursor_pos else 0 self.current_input = self.current_input[: x] + text + self.current_input[ x:] else: self.current_input = text self.cursor_pos += wcswidth(text)
def reduce_to_single_grapheme(text: str) -> str: limit = len(text) if limit < 2: return text x = 1 while x < limit: pos = truncate_point_for_length(text, x) if pos > 0: return text[:pos] x += 1 return text
def render_path_in_width(path: str, width: int) -> str: if os.altsep: path = path.replace(os.altsep, os.sep) if wcswidth(path) <= width: return path parts = path.split(os.sep) reduced = os.sep.join(map(reduce_to_single_grapheme, parts[:-1])) path = os.path.join(reduced, parts[-1]) if wcswidth(path) <= width: return path x = truncate_point_for_length(path, width - 1) return path[:x] + '…'
def word_right(self) -> Position: line = unstyled(self.lines[self.point.line - 1]) pos = truncate_point_for_length(line, self.point.x) if pos < len(line): pred = (self._is_word_char if self._is_word_char(line[pos]) else self._is_word_separator) new_pos = pos + len(''.join(takewhile(pred, line[pos:]))) return Position(wcswidth(line[:new_pos]), self.point.y, self.point.top_line) if self.point.y < self.screen_size.rows - 1: return Position(0, self.point.y + 1, self.point.top_line) if self.point.top_line + self.point.y < len(self.lines): return Position(0, self.point.y, self.point.top_line + 1) return self.point
def word_left(self) -> Position: if self.point.x > 0: line = unstyled(self.lines[self.point.line - 1]) pos = truncate_point_for_length(line, self.point.x) pred = (self._is_word_char if self._is_word_char(line[pos - 1]) else self._is_word_separator) new_pos = pos - len(''.join(takewhile(pred, reversed(line[:pos])))) return Position(wcswidth(line[:new_pos]), self.point.y, self.point.top_line) if self.point.y > 0: return Position( wcswidth(unstyled(self.lines[self.point.line - 2])), self.point.y - 1, self.point.top_line) if self.point.top_line > 1: return Position( wcswidth(unstyled(self.lines[self.point.line - 2])), self.point.y, self.point.top_line - 1) return self.point
def split_at_cursor(self, delta: int = 0) -> Tuple[str, str]: pos = max(0, self.cursor_pos + delta) x = truncate_point_for_length(self.current_input, pos) if pos else 0 before, after = self.current_input[:x], self.current_input[x:] return before, after
def limit_length(text: str, limit: int = 32) -> str: x = truncate_point_for_length(text, limit - 1) if x >= len(text): return text return f'{text[:x]}…'