pytermgui.helpers
Helper methods and functions for pytermgui.
1"""Helper methods and functions for pytermgui.""" 2 3from __future__ import annotations 4 5from typing import Iterator 6 7from .ansi_interface import reset 8from .colors import Color 9from .parser import Token, TokenType, markup 10from .regex import real_length 11 12__all__ = [ 13 "get_applied_sequences", 14 "break_line", 15] 16 17 18def get_applied_sequences(text: str) -> str: 19 """Extracts ANSI sequences from text. 20 21 Args: 22 text: The text to operate on. 23 24 Returns: 25 All sequences found. 26 """ 27 28 tokens: list[Token] = [] 29 reset_char = reset() 30 for token in markup.tokenize_ansi(text): 31 if not token.ttype is TokenType.UNSETTER: 32 tokens.append(token) 33 continue 34 35 assert token.sequence is not None 36 if token.sequence == reset_char: 37 tokens = [] 38 continue 39 40 name = token.name.lstrip("/") 41 for style in tokens.copy(): 42 if name in ["fg", "bg"] and style.ttype is TokenType.COLOR: 43 color = style.data 44 assert isinstance(color, Color) 45 46 if name == "fg" and not color.background: 47 tokens.remove(style) 48 elif name == "bg" and color.background: 49 tokens.remove(style) 50 51 elif style.name == name: 52 tokens.remove(style) 53 54 continue 55 56 return "".join(token.sequence or "" for token in tokens) 57 58 59def break_line( 60 line: str, limit: int, non_first_limit: int | None = None, fill: str | None = None 61) -> Iterator[str]: 62 """Breaks a line into a `list[str]` with maximum `limit` length per line. 63 64 It keeps ongoing ANSI sequences between lines, and inserts a reset sequence 65 at the end of each style-containing line. 66 67 At the moment it splits strings exactly on the limit, and not on word 68 boundaries. That functionality would be preferred, so it will end up being 69 implemented at some point. 70 71 Args: 72 line: The line to split. May or may not contain ANSI sequences. 73 limit: The maximum amount of characters allowed in each line, excluding 74 non-printing sequences. 75 non_first_limit: The limit after the first line. If not given, defaults 76 to `limit`. 77 """ 78 79 if line in ["", "\x1b[0m"]: 80 yield "" 81 return 82 83 def _pad(line: str) -> str: 84 if fill is None: 85 return line 86 87 count = limit - real_length(line) 88 return line + count * fill 89 90 used = 0 91 current = "" 92 sequences = "" 93 94 if non_first_limit is None: 95 non_first_limit = limit 96 97 for token in markup.tokenize_ansi(line): 98 if token.sequence is None: 99 assert isinstance(token.data, str) 100 for char in token.data: 101 if char == "\n" or used >= limit: 102 if sequences != "": 103 current += "\x1b[0m" 104 105 yield _pad(current) 106 107 current = sequences 108 used = 0 109 110 limit = non_first_limit 111 112 if char != "\n": 113 current += char 114 used += 1 115 116 continue 117 118 if token.sequence == "\x1b[0m": 119 sequences = "\x1b[0m" 120 121 if len(current) > 0: 122 current += sequences 123 124 continue 125 126 sequences += token.sequence 127 current += token.sequence 128 129 if current == "": 130 return 131 132 if sequences != "" and not current.endswith("\x1b[0m"): 133 current += "\x1b[0m" 134 135 yield _pad(current)
def
get_applied_sequences(text: str) -> str:
19def get_applied_sequences(text: str) -> str: 20 """Extracts ANSI sequences from text. 21 22 Args: 23 text: The text to operate on. 24 25 Returns: 26 All sequences found. 27 """ 28 29 tokens: list[Token] = [] 30 reset_char = reset() 31 for token in markup.tokenize_ansi(text): 32 if not token.ttype is TokenType.UNSETTER: 33 tokens.append(token) 34 continue 35 36 assert token.sequence is not None 37 if token.sequence == reset_char: 38 tokens = [] 39 continue 40 41 name = token.name.lstrip("/") 42 for style in tokens.copy(): 43 if name in ["fg", "bg"] and style.ttype is TokenType.COLOR: 44 color = style.data 45 assert isinstance(color, Color) 46 47 if name == "fg" and not color.background: 48 tokens.remove(style) 49 elif name == "bg" and color.background: 50 tokens.remove(style) 51 52 elif style.name == name: 53 tokens.remove(style) 54 55 continue 56 57 return "".join(token.sequence or "" for token in tokens)
Extracts ANSI sequences from text.
Args
- text: The text to operate on.
Returns
All sequences found.
def
break_line( line: str, limit: int, non_first_limit: int | None = None, fill: str | None = None) -> Iterator[str]:
60def break_line( 61 line: str, limit: int, non_first_limit: int | None = None, fill: str | None = None 62) -> Iterator[str]: 63 """Breaks a line into a `list[str]` with maximum `limit` length per line. 64 65 It keeps ongoing ANSI sequences between lines, and inserts a reset sequence 66 at the end of each style-containing line. 67 68 At the moment it splits strings exactly on the limit, and not on word 69 boundaries. That functionality would be preferred, so it will end up being 70 implemented at some point. 71 72 Args: 73 line: The line to split. May or may not contain ANSI sequences. 74 limit: The maximum amount of characters allowed in each line, excluding 75 non-printing sequences. 76 non_first_limit: The limit after the first line. If not given, defaults 77 to `limit`. 78 """ 79 80 if line in ["", "\x1b[0m"]: 81 yield "" 82 return 83 84 def _pad(line: str) -> str: 85 if fill is None: 86 return line 87 88 count = limit - real_length(line) 89 return line + count * fill 90 91 used = 0 92 current = "" 93 sequences = "" 94 95 if non_first_limit is None: 96 non_first_limit = limit 97 98 for token in markup.tokenize_ansi(line): 99 if token.sequence is None: 100 assert isinstance(token.data, str) 101 for char in token.data: 102 if char == "\n" or used >= limit: 103 if sequences != "": 104 current += "\x1b[0m" 105 106 yield _pad(current) 107 108 current = sequences 109 used = 0 110 111 limit = non_first_limit 112 113 if char != "\n": 114 current += char 115 used += 1 116 117 continue 118 119 if token.sequence == "\x1b[0m": 120 sequences = "\x1b[0m" 121 122 if len(current) > 0: 123 current += sequences 124 125 continue 126 127 sequences += token.sequence 128 current += token.sequence 129 130 if current == "": 131 return 132 133 if sequences != "" and not current.endswith("\x1b[0m"): 134 current += "\x1b[0m" 135 136 yield _pad(current)
Breaks a line into a list[str]
with maximum limit
length per line.
It keeps ongoing ANSI sequences between lines, and inserts a reset sequence at the end of each style-containing line.
At the moment it splits strings exactly on the limit, and not on word boundaries. That functionality would be preferred, so it will end up being implemented at some point.
Args
- line: The line to split. May or may not contain ANSI sequences.
- limit: The maximum amount of characters allowed in each line, excluding non-printing sequences.
- non_first_limit: The limit after the first line. If not given, defaults
to
limit
.