pytermgui.helpers

Helper methods and functions for pytermgui.

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

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]:
View Source
 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

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.