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.