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

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) -> Iterator[str]:
 59def break_line(
 60    line: str, limit: int, non_first_limit: int | 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    used = 0
 80    current = ""
 81    sequences = ""
 82
 83    if non_first_limit is None:
 84        non_first_limit = limit
 85
 86    for token in markup.tokenize_ansi(line):
 87        if token.sequence is None:
 88            assert isinstance(token.data, str)
 89            for char in token.data:
 90                if char == "\n" or used >= limit:
 91                    if sequences != "":
 92                        current += "\x1b[0m"
 93
 94                    yield current
 95
 96                    current = sequences
 97                    used = 0
 98
 99                    limit = non_first_limit
100
101                if char != "\n":
102                    current += char
103                    used += 1
104
105            continue
106
107        if token.sequence == "\x1b[0m":
108            sequences = "\x1b[0m"
109
110            if len(current) > 0:
111                current += sequences
112
113            continue
114
115        sequences += token.sequence
116        current += token.sequence
117
118    if current == "":
119        return
120
121    if sequences != "" and not current.endswith("\x1b[0m"):
122        current += "\x1b[0m"
123
124    yield 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.