Module refinery.lib.colors

Functions to help with ANSI colored terminal text.

Expand source code Browse git
"""
Functions to help with ANSI colored terminal text.
"""
from __future__ import annotations

from unicodedata import combining, east_asian_width
from io import StringIO

from refinery.lib.patterns import AnsiColor as ac


def unicode_char_width(c: str) -> int:
    if combining(c):
        return 0
    elif east_asian_width(c) in 'WF':
        return 2
    else:
        return 1


def colored_text_bleach(string: str):
    """
    Removes all ANSI color code sequences from the input string.
    """
    return ac.str.sub('', string)


def colored_text_length(string: str) -> int:
    """
    Compute the display length of text containing ANSI color codes.
    """
    if string.isascii():
        n = len(string)
    else:
        n = sum(unicode_char_width(c) for c in string)
    return n - sum(m.end() - m.start() for m in ac.finditer(string))


def colored_text_truncate_ascii(string: str, end: int):
    """
    Truncate a colored text string to the given display length.
    """
    length = 0
    cursor = 0
    clipat = end
    for match in ac.finditer(string):
        a, b = match.span()
        length += a - cursor
        if length >= end:
            break
        cursor = b
        clipat += b - a
    return string[:clipat]


def colored_text_truncate(string: str, end: int) -> str:
    """
    Truncate a colored text string to the given display length, accounting for the display width of
    Unicode characters such as East Asian wide characters.
    """
    if string.isascii():
        return colored_text_truncate_ascii(string, end)

    length = 0
    cursor = 0
    parts = StringIO()

    def consume(segment: str) -> bool:
        nonlocal length
        if segment.isascii():
            remaining = end - length
            if len(segment) <= remaining:
                length += len(segment)
                parts.write(segment)
                return False
            parts.write(segment[:remaining])
            return True
        for k, c in enumerate(segment):
            w = unicode_char_width(c)
            if length + w > end:
                parts.write(segment[:k])
                if length < end:
                    parts.write(' ')
                return True
            length += w
        parts.write(segment)
        return False

    for match in ac.finditer(string):
        a, b = match.span()
        if consume(string[cursor:a]):
            return parts.getvalue()
        parts.write(match.group())
        cursor = b

    consume(string[cursor:])
    return parts.getvalue()

Functions

def unicode_char_width(c)
Expand source code Browse git
def unicode_char_width(c: str) -> int:
    if combining(c):
        return 0
    elif east_asian_width(c) in 'WF':
        return 2
    else:
        return 1
def colored_text_bleach(string)

Removes all ANSI color code sequences from the input string.

Expand source code Browse git
def colored_text_bleach(string: str):
    """
    Removes all ANSI color code sequences from the input string.
    """
    return ac.str.sub('', string)
def colored_text_length(string)

Compute the display length of text containing ANSI color codes.

Expand source code Browse git
def colored_text_length(string: str) -> int:
    """
    Compute the display length of text containing ANSI color codes.
    """
    if string.isascii():
        n = len(string)
    else:
        n = sum(unicode_char_width(c) for c in string)
    return n - sum(m.end() - m.start() for m in ac.finditer(string))
def colored_text_truncate_ascii(string, end)

Truncate a colored text string to the given display length.

Expand source code Browse git
def colored_text_truncate_ascii(string: str, end: int):
    """
    Truncate a colored text string to the given display length.
    """
    length = 0
    cursor = 0
    clipat = end
    for match in ac.finditer(string):
        a, b = match.span()
        length += a - cursor
        if length >= end:
            break
        cursor = b
        clipat += b - a
    return string[:clipat]
def colored_text_truncate(string, end)

Truncate a colored text string to the given display length, accounting for the display width of Unicode characters such as East Asian wide characters.

Expand source code Browse git
def colored_text_truncate(string: str, end: int) -> str:
    """
    Truncate a colored text string to the given display length, accounting for the display width of
    Unicode characters such as East Asian wide characters.
    """
    if string.isascii():
        return colored_text_truncate_ascii(string, end)

    length = 0
    cursor = 0
    parts = StringIO()

    def consume(segment: str) -> bool:
        nonlocal length
        if segment.isascii():
            remaining = end - length
            if len(segment) <= remaining:
                length += len(segment)
                parts.write(segment)
                return False
            parts.write(segment[:remaining])
            return True
        for k, c in enumerate(segment):
            w = unicode_char_width(c)
            if length + w > end:
                parts.write(segment[:k])
                if length < end:
                    parts.write(' ')
                return True
            length += w
        parts.write(segment)
        return False

    for match in ac.finditer(string):
        a, b = match.span()
        if consume(string[cursor:a]):
            return parts.getvalue()
        parts.write(match.group())
        cursor = b

    consume(string[cursor:])
    return parts.getvalue()